mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
Merge pull request #88 from DSheirer/#86_playlist_file_corruption_on_save
#86 - updates playlist configuration file save operations to 1)create…
This commit is contained in:
@@ -1,48 +1,26 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
*
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package playlist;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.Marshaller;
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
|
||||
import audio.broadcast.BroadcastEvent;
|
||||
import audio.broadcast.BroadcastModel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import playlist.version1.PlaylistConverterV1ToV2;
|
||||
import properties.SystemProperties;
|
||||
import sample.Listener;
|
||||
import alias.AliasEvent;
|
||||
import alias.AliasModel;
|
||||
import audio.broadcast.BroadcastEvent;
|
||||
import audio.broadcast.BroadcastModel;
|
||||
import controller.ThreadPoolManager;
|
||||
import controller.channel.Channel.ChannelType;
|
||||
import controller.channel.ChannelEvent;
|
||||
@@ -50,358 +28,413 @@ import controller.channel.ChannelEventListener;
|
||||
import controller.channel.ChannelModel;
|
||||
import controller.channel.map.ChannelMapEvent;
|
||||
import controller.channel.map.ChannelMapModel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import playlist.version1.PlaylistConverterV1ToV2;
|
||||
import properties.SystemProperties;
|
||||
import sample.Listener;
|
||||
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.Marshaller;
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class PlaylistManager implements ChannelEventListener
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( PlaylistManager.class );
|
||||
private final static Logger mLog = LoggerFactory.getLogger(PlaylistManager.class);
|
||||
|
||||
private ThreadPoolManager mThreadPoolManager;
|
||||
private AliasModel mAliasModel;
|
||||
private BroadcastModel mBroadcastModel;
|
||||
private ChannelModel mChannelModel;
|
||||
private ChannelMapModel mChannelMapModel;
|
||||
private Path mCurrentPlaylistPath;
|
||||
private AtomicBoolean mPlaylistSavePending = new AtomicBoolean();
|
||||
private boolean mPlaylistLoading = false;
|
||||
|
||||
/**
|
||||
* Playlist manager - manages all channel configurations, channel maps, and
|
||||
* alias lists and handles loading or persisting to a playlist.xml file
|
||||
*
|
||||
* Monitors playlist changes to automatically save configuration changes
|
||||
* after they occur.
|
||||
*
|
||||
* @param threadPoolManager
|
||||
* @param channelModel
|
||||
*/
|
||||
public PlaylistManager( ThreadPoolManager threadPoolManager,
|
||||
AliasModel aliasModel,
|
||||
BroadcastModel broadcastModel,
|
||||
ChannelModel channelModel,
|
||||
ChannelMapModel channelMapModel)
|
||||
{
|
||||
mThreadPoolManager = threadPoolManager;
|
||||
mAliasModel = aliasModel;
|
||||
mBroadcastModel = broadcastModel;
|
||||
mChannelModel = channelModel;
|
||||
mChannelMapModel = channelMapModel;
|
||||
private ThreadPoolManager mThreadPoolManager;
|
||||
private AliasModel mAliasModel;
|
||||
private BroadcastModel mBroadcastModel;
|
||||
private ChannelModel mChannelModel;
|
||||
private ChannelMapModel mChannelMapModel;
|
||||
|
||||
//Register for alias, channel and channel map events so that we can
|
||||
//save the playlist when there are any changes
|
||||
mChannelModel.addListener( this );
|
||||
|
||||
mAliasModel.addListener( new Listener<AliasEvent>()
|
||||
{
|
||||
@Override
|
||||
public void receive( AliasEvent t )
|
||||
{
|
||||
schedulePlaylistSave();
|
||||
}
|
||||
} );
|
||||
|
||||
mChannelMapModel.addListener( new Listener<ChannelMapEvent>()
|
||||
{
|
||||
@Override
|
||||
public void receive( ChannelMapEvent t )
|
||||
{
|
||||
schedulePlaylistSave();
|
||||
}
|
||||
} );
|
||||
private Path mPlaylistFolderPath;
|
||||
private Path mPlaylistCurrentPath;
|
||||
private Path mPlaylistBackupPath;
|
||||
private Path mPlaylistLockPath;
|
||||
|
||||
mBroadcastModel.addListener(new Listener<BroadcastEvent>()
|
||||
{
|
||||
@Override
|
||||
public void receive(BroadcastEvent broadcastEvent)
|
||||
{
|
||||
schedulePlaylistSave();
|
||||
}
|
||||
});
|
||||
}
|
||||
private AtomicBoolean mPlaylistSavePending = new AtomicBoolean();
|
||||
private boolean mPlaylistLoading = false;
|
||||
|
||||
/**
|
||||
* Loads playlist from the current playlist file, or the default playlist file,
|
||||
* as specified in the current SDRTRunk system settings
|
||||
*/
|
||||
public void init()
|
||||
{
|
||||
SystemProperties props = SystemProperties.getInstance();
|
||||
|
||||
Path playlistFolder = props.getApplicationFolder( "playlist" );
|
||||
|
||||
String defaultPlaylistFile =
|
||||
props.get( "playlist.v2.defaultfilename", "playlist_v2.xml" );
|
||||
|
||||
String playlistFile =
|
||||
props.get( "playlist.v2.currentfilename", defaultPlaylistFile );
|
||||
/**
|
||||
* Playlist manager - manages all channel configurations, channel maps, and
|
||||
* alias lists and handles loading or persisting to a playlist.xml file
|
||||
*
|
||||
* Monitors playlist changes to automatically save configuration changes
|
||||
* after they occur.
|
||||
*
|
||||
* @param threadPoolManager
|
||||
* @param channelModel
|
||||
*/
|
||||
public PlaylistManager(ThreadPoolManager threadPoolManager,
|
||||
AliasModel aliasModel,
|
||||
BroadcastModel broadcastModel,
|
||||
ChannelModel channelModel,
|
||||
ChannelMapModel channelMapModel)
|
||||
{
|
||||
mThreadPoolManager = threadPoolManager;
|
||||
mAliasModel = aliasModel;
|
||||
mBroadcastModel = broadcastModel;
|
||||
mChannelModel = channelModel;
|
||||
mChannelMapModel = channelMapModel;
|
||||
|
||||
mCurrentPlaylistPath = playlistFolder.resolve( playlistFile );
|
||||
//Register for alias, channel and channel map events so that we can
|
||||
//save the playlist when there are any changes
|
||||
mChannelModel.addListener(this);
|
||||
|
||||
PlaylistV2 playlist = load( mCurrentPlaylistPath );
|
||||
|
||||
boolean saveRequired = false;
|
||||
|
||||
if( playlist == null )
|
||||
{
|
||||
mLog.info( "Couldn't find version 2 playlist - looking for "
|
||||
+ "version 1 playlist to convert" );
|
||||
|
||||
Path playlistV1Path = playlistFolder.resolve( "playlist.xml" );
|
||||
|
||||
PlaylistConverterV1ToV2 converter =
|
||||
new PlaylistConverterV1ToV2( playlistV1Path );
|
||||
|
||||
if( converter.hasErrorMessages() )
|
||||
{
|
||||
mLog.error( "Playlist version 1 conversion errors: " +
|
||||
converter.getErrorMessages() );
|
||||
}
|
||||
|
||||
playlist = converter.getConvertedPlaylist();
|
||||
|
||||
saveRequired = true;
|
||||
}
|
||||
|
||||
transferPlaylistToModels( playlist );
|
||||
|
||||
if( saveRequired )
|
||||
{
|
||||
schedulePlaylistSave();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfers data from persisted playlist into system models
|
||||
*/
|
||||
private void transferPlaylistToModels( PlaylistV2 playlist )
|
||||
{
|
||||
if( playlist != null )
|
||||
{
|
||||
mPlaylistLoading = true;
|
||||
|
||||
mAliasModel.addAliases( playlist.getAliases() );
|
||||
mBroadcastModel.addBroadcastConfigurations(playlist.getBroadcastConfigurations());
|
||||
mChannelModel.addChannels( playlist.getChannels() );
|
||||
mChannelMapModel.addChannelMaps( playlist.getChannelMaps() );
|
||||
|
||||
mPlaylistLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Channel event listener method. Monitors channel events for events that
|
||||
* indicate that the playlist has changed and queues automatic playlist
|
||||
* saving.
|
||||
*/
|
||||
@Override
|
||||
public void channelChanged( ChannelEvent event )
|
||||
{
|
||||
//Only save playlist for changes to standard channels (not traffic)
|
||||
if( event.getChannel().getChannelType() == ChannelType.STANDARD )
|
||||
{
|
||||
switch( event.getEvent() )
|
||||
{
|
||||
case NOTIFICATION_ADD:
|
||||
case NOTIFICATION_CONFIGURATION_CHANGE:
|
||||
case NOTIFICATION_DELETE:
|
||||
case NOTIFICATION_PROCESSING_START:
|
||||
case NOTIFICATION_PROCESSING_STOP:
|
||||
schedulePlaylistSave();
|
||||
break;
|
||||
case NOTIFICATION_ENABLE_REJECTED:
|
||||
case NOTIFICATION_SELECTION_CHANGE:
|
||||
case NOTIFICATION_STATE_RESET:
|
||||
case REQUEST_DELETE:
|
||||
case REQUEST_DESELECT:
|
||||
case REQUEST_DISABLE:
|
||||
case REQUEST_ENABLE:
|
||||
case REQUEST_SELECT:
|
||||
//Do nothing for these event types
|
||||
break;
|
||||
default:
|
||||
//When a new event enum entry is added and received, throw an
|
||||
//exception here to ensure developer adds support for the
|
||||
//use case
|
||||
throw new IllegalArgumentException( "Unrecognized Channel "
|
||||
+ "Event [" + event.getEvent().name() + "]" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void save()
|
||||
{
|
||||
PlaylistV2 playlist = new PlaylistV2();
|
||||
|
||||
playlist.setAliases( mAliasModel.getAliases() );
|
||||
playlist.setBroadcastConfigurations(mBroadcastModel.getBroadcastConfigurations());
|
||||
playlist.setChannels( mChannelModel.getChannels() );
|
||||
playlist.setChannelMaps( mChannelMapModel.getChannelMaps() );
|
||||
|
||||
JAXBContext context = null;
|
||||
|
||||
if( mCurrentPlaylistPath == null )
|
||||
{
|
||||
SystemProperties props = SystemProperties.getInstance();
|
||||
|
||||
Path playlistPath = props.getApplicationFolder( "playlist" );
|
||||
|
||||
String playlistDefault = props.get( "playlist.defaultfilename",
|
||||
"playlist_v2.xml" );
|
||||
|
||||
String playlistCurrent = props.get( "playlist.currentfilename",
|
||||
playlistDefault );
|
||||
|
||||
mCurrentPlaylistPath = playlistPath.resolve( playlistCurrent );
|
||||
}
|
||||
|
||||
File outputFile = new File( mCurrentPlaylistPath.toString() );
|
||||
|
||||
try
|
||||
{
|
||||
if( !outputFile.exists() )
|
||||
{
|
||||
outputFile.createNewFile();
|
||||
}
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
mLog.error( "couldn't create file to save "
|
||||
+ "playlist [" + mCurrentPlaylistPath.toString() + "]" );
|
||||
}
|
||||
|
||||
OutputStream out = null;
|
||||
|
||||
try
|
||||
mAliasModel.addListener(new Listener<AliasEvent>()
|
||||
{
|
||||
out = new FileOutputStream( outputFile );
|
||||
|
||||
try
|
||||
{
|
||||
context = JAXBContext.newInstance( PlaylistV2.class );
|
||||
@Override
|
||||
public void receive(AliasEvent t)
|
||||
{
|
||||
schedulePlaylistSave();
|
||||
}
|
||||
});
|
||||
|
||||
Marshaller m = context.createMarshaller();
|
||||
|
||||
m.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, true );
|
||||
|
||||
m.marshal( playlist, out );
|
||||
}
|
||||
catch ( JAXBException e )
|
||||
{
|
||||
mLog.error( "jaxb exception while saving playlist: ", e );
|
||||
}
|
||||
}
|
||||
catch ( Exception e )
|
||||
mChannelMapModel.addListener(new Listener<ChannelMapEvent>()
|
||||
{
|
||||
mLog.error( "couldn't open outputstream to save playlist [" +
|
||||
mCurrentPlaylistPath.toString() + "]", e );
|
||||
@Override
|
||||
public void receive(ChannelMapEvent t)
|
||||
{
|
||||
schedulePlaylistSave();
|
||||
}
|
||||
});
|
||||
|
||||
mBroadcastModel.addListener(new Listener<BroadcastEvent>()
|
||||
{
|
||||
@Override
|
||||
public void receive(BroadcastEvent broadcastEvent)
|
||||
{
|
||||
schedulePlaylistSave();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads playlist from the current playlist file, or the default playlist file,
|
||||
* as specified in the current SDRTRunk system settings
|
||||
*/
|
||||
public void init()
|
||||
{
|
||||
PlaylistV2 playlist = load();
|
||||
|
||||
boolean saveRequired = false;
|
||||
|
||||
if(playlist == null)
|
||||
{
|
||||
mLog.info("Couldn't find version 2 playlist - looking for version 1 playlist to convert");
|
||||
|
||||
Path playlistV1Path = getPlaylistFolderPath().resolve("playlist.xml");
|
||||
|
||||
PlaylistConverterV1ToV2 converter = new PlaylistConverterV1ToV2(playlistV1Path);
|
||||
|
||||
if(converter.hasErrorMessages())
|
||||
{
|
||||
mLog.error("Playlist version 1 conversion errors: " + converter.getErrorMessages());
|
||||
}
|
||||
|
||||
playlist = converter.getConvertedPlaylist();
|
||||
|
||||
saveRequired = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if( out != null )
|
||||
{
|
||||
try
|
||||
|
||||
transferPlaylistToModels(playlist);
|
||||
|
||||
if(saveRequired)
|
||||
{
|
||||
schedulePlaylistSave();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfers data from persisted playlist into system models
|
||||
*/
|
||||
private void transferPlaylistToModels(PlaylistV2 playlist)
|
||||
{
|
||||
if(playlist != null)
|
||||
{
|
||||
mPlaylistLoading = true;
|
||||
|
||||
mAliasModel.addAliases(playlist.getAliases());
|
||||
mBroadcastModel.addBroadcastConfigurations(playlist.getBroadcastConfigurations());
|
||||
mChannelModel.addChannels(playlist.getChannels());
|
||||
mChannelMapModel.addChannelMaps(playlist.getChannelMaps());
|
||||
|
||||
mPlaylistLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Channel event listener method. Monitors channel events for events that indicate that the playlist has changed
|
||||
* and queues automatic playlist saving.
|
||||
*/
|
||||
@Override
|
||||
public void channelChanged(ChannelEvent event)
|
||||
{
|
||||
//Only save playlist for changes to standard channels (not traffic)
|
||||
if(event.getChannel().getChannelType() == ChannelType.STANDARD)
|
||||
{
|
||||
switch(event.getEvent())
|
||||
{
|
||||
case NOTIFICATION_ADD:
|
||||
case NOTIFICATION_CONFIGURATION_CHANGE:
|
||||
case NOTIFICATION_DELETE:
|
||||
schedulePlaylistSave();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Folder where playlist, backup and lock file are stored
|
||||
*/
|
||||
private Path getPlaylistFolderPath()
|
||||
{
|
||||
if(mPlaylistFolderPath == null)
|
||||
{
|
||||
SystemProperties props = SystemProperties.getInstance();
|
||||
|
||||
mPlaylistFolderPath = props.getApplicationFolder("playlist");
|
||||
}
|
||||
|
||||
return mPlaylistFolderPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Path to current playlist
|
||||
*/
|
||||
private Path getPlaylistPath()
|
||||
{
|
||||
if(mPlaylistCurrentPath == null)
|
||||
{
|
||||
SystemProperties props = SystemProperties.getInstance();
|
||||
|
||||
String playlistDefault = props.get("playlist.defaultfilename", "playlist_v2.xml");
|
||||
|
||||
String playlistCurrent = props.get("playlist.currentfilename", playlistDefault);
|
||||
|
||||
mPlaylistCurrentPath = getPlaylistFolderPath().resolve(playlistCurrent);
|
||||
}
|
||||
|
||||
return mPlaylistCurrentPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Path to most recent playlist backup
|
||||
*/
|
||||
private Path getPlaylistBackupPath()
|
||||
{
|
||||
if(mPlaylistBackupPath == null)
|
||||
{
|
||||
SystemProperties props = SystemProperties.getInstance();
|
||||
|
||||
String playlistDefault = props.get("playlist.defaultfilename", "playlist_v2.xml");
|
||||
|
||||
String playlistCurrent = props.get("playlist.currentfilename", playlistDefault);
|
||||
|
||||
String playlistBackup = playlistCurrent.replace(".xml", ".bak");
|
||||
|
||||
mPlaylistBackupPath = getPlaylistFolderPath().resolve(playlistBackup);
|
||||
}
|
||||
|
||||
return mPlaylistBackupPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Path to playlist lock file that is created prior to saving a playlist and removed immediately thereafter.
|
||||
* Presence of a lock file indicates an incomplete or corrupt playlist file on startup.
|
||||
*/
|
||||
private Path getPlaylistLockPath()
|
||||
{
|
||||
if(mPlaylistLockPath == null)
|
||||
{
|
||||
SystemProperties props = SystemProperties.getInstance();
|
||||
|
||||
String playlistDefault = props.get("playlist.defaultfilename", "playlist_v2.xml");
|
||||
|
||||
String playlistCurrent = props.get("playlist.currentfilename", playlistDefault);
|
||||
|
||||
String playlistLock = playlistCurrent.replace(".xml", ".lock");
|
||||
|
||||
mPlaylistLockPath = getPlaylistFolderPath().resolve(playlistLock);
|
||||
}
|
||||
|
||||
return mPlaylistLockPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the current playlist
|
||||
*/
|
||||
private void save()
|
||||
{
|
||||
PlaylistV2 playlist = new PlaylistV2();
|
||||
|
||||
playlist.setAliases(mAliasModel.getAliases());
|
||||
playlist.setBroadcastConfigurations(mBroadcastModel.getBroadcastConfigurations());
|
||||
playlist.setChannels(mChannelModel.getChannels());
|
||||
playlist.setChannelMaps(mChannelMapModel.getChannelMaps());
|
||||
|
||||
JAXBContext context = null;
|
||||
|
||||
//Create a backup copy of the current playlist
|
||||
if(Files.exists(getPlaylistPath()))
|
||||
{
|
||||
try
|
||||
{
|
||||
Files.copy(getPlaylistPath(), getPlaylistBackupPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error creating backup copy of current playlist prior to saving updates [" +
|
||||
getPlaylistPath().toString() + "]", e);
|
||||
}
|
||||
}
|
||||
|
||||
//Create a temporary lock file to signify that we're in the process of updating the playlist
|
||||
if(!Files.exists(getPlaylistLockPath()))
|
||||
{
|
||||
try
|
||||
{
|
||||
Files.createFile(getPlaylistLockPath());
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
mLog.error("Error creating temporary lock file prior to saving playlist [" +
|
||||
getPlaylistLockPath().toString() + "]", e);
|
||||
}
|
||||
}
|
||||
|
||||
try(OutputStream out = Files.newOutputStream(getPlaylistPath()))
|
||||
{
|
||||
context = JAXBContext.newInstance(PlaylistV2.class);
|
||||
|
||||
Marshaller m = context.createMarshaller();
|
||||
|
||||
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
|
||||
|
||||
m.marshal(playlist, out);
|
||||
|
||||
out.flush();
|
||||
|
||||
//Remove the playlist lock file to indicate that we successfully saved the file
|
||||
if(Files.exists(getPlaylistLockPath()))
|
||||
{
|
||||
Files.delete(getPlaylistLockPath());
|
||||
}
|
||||
}
|
||||
catch(JAXBException je)
|
||||
{
|
||||
mLog.error("JAXB exception while serializing the playlist to a file [" + getPlaylistPath().toString() +
|
||||
"]", je);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("IO error while writing the playlist to a file [" + getPlaylistPath().toString() + "]", ioe);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error while saving playlist [" + getPlaylistPath().toString() + "]", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a version 2 playlist
|
||||
*/
|
||||
public PlaylistV2 load()
|
||||
{
|
||||
mLog.info("loading version 2 playlist file [" + getPlaylistPath().toString() + "]");
|
||||
|
||||
PlaylistV2 playlist = null;
|
||||
|
||||
//Check for a lock file that indicates the previous save attempt was incomplete or had an error
|
||||
if(Files.exists(getPlaylistLockPath()))
|
||||
{
|
||||
try
|
||||
{
|
||||
//Remove the previous playlist
|
||||
Files.delete(getPlaylistPath());
|
||||
|
||||
//Copy the backup file to restore the previous playlist
|
||||
if(Files.exists(getPlaylistBackupPath()))
|
||||
{
|
||||
out.close();
|
||||
Files.copy(getPlaylistBackupPath(), getPlaylistPath());
|
||||
}
|
||||
catch ( IOException e )
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a version 2 playlist
|
||||
*/
|
||||
public PlaylistV2 load( Path playlistPath )
|
||||
{
|
||||
mLog.info( "loading version 2 playlist file [" + playlistPath.toString() + "]" );
|
||||
|
||||
PlaylistV2 playlist = null;
|
||||
//Remove the lock file
|
||||
Files.delete(getPlaylistLockPath());
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Previous playlist save attempt was incomplete and there was an error restoring the " +
|
||||
"playlist backup file", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
if( Files.exists( playlistPath ) )
|
||||
{
|
||||
JAXBContext context = null;
|
||||
|
||||
InputStream in = null;
|
||||
|
||||
try
|
||||
{
|
||||
in = new FileInputStream( playlistPath.toString() );
|
||||
|
||||
try
|
||||
{
|
||||
context = JAXBContext.newInstance( PlaylistV2.class );
|
||||
if(Files.exists(getPlaylistPath()))
|
||||
{
|
||||
JAXBContext context = null;
|
||||
|
||||
Unmarshaller m = context.createUnmarshaller();
|
||||
try(InputStream in = Files.newInputStream(getPlaylistPath()))
|
||||
{
|
||||
context = JAXBContext.newInstance(PlaylistV2.class);
|
||||
|
||||
playlist = (PlaylistV2)m.unmarshal( in );
|
||||
}
|
||||
catch ( JAXBException e )
|
||||
{
|
||||
mLog.error( "jaxb exception while loading playlist: ", e );
|
||||
}
|
||||
}
|
||||
catch ( Exception e )
|
||||
{
|
||||
mLog.error( "couldn't open input stream to load playlist [" + playlistPath.toString() + "]", e );
|
||||
}
|
||||
finally
|
||||
{
|
||||
if( in != null )
|
||||
{
|
||||
try
|
||||
{
|
||||
in.close();
|
||||
}
|
||||
catch ( IOException e )
|
||||
{
|
||||
mLog.error( "exception while closing " +
|
||||
"the playlist file inputstream reader", e );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.info( "PlaylistManager - playlist not found at [" +
|
||||
playlistPath.toString() + "]" );
|
||||
}
|
||||
Unmarshaller m = context.createUnmarshaller();
|
||||
|
||||
return playlist;
|
||||
}
|
||||
playlist = (PlaylistV2) m.unmarshal(in);
|
||||
}
|
||||
catch(JAXBException je)
|
||||
{
|
||||
mLog.error("JAXB exception while loading/unmarshalling playlist", je);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("IO error while reading playlist file", ioe);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.info("PlaylistManager - playlist not found at [" + getPlaylistPath().toString() + "]");
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a playlist save task. Subsequent calls to this method will be
|
||||
* ignored until the save event occurs, thus limiting repetitive playlist
|
||||
* saving to a minimum.
|
||||
*/
|
||||
private void schedulePlaylistSave()
|
||||
{
|
||||
if( !mPlaylistLoading )
|
||||
{
|
||||
if( mPlaylistSavePending.compareAndSet( false, true ) )
|
||||
{
|
||||
mThreadPoolManager.scheduleOnce( new PlaylistSaveTask(),
|
||||
2, TimeUnit.SECONDS );
|
||||
}
|
||||
}
|
||||
}
|
||||
return playlist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the playlist save pending flag to false and proceeds to save the
|
||||
* playlist.
|
||||
*/
|
||||
public class PlaylistSaveTask implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
mPlaylistSavePending.set( false );
|
||||
|
||||
save();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Schedules a playlist save task. Subsequent calls to this method will be ignored until the save event occurs,
|
||||
* thus limiting repetitive playlist saving to a minimum.
|
||||
*/
|
||||
private void schedulePlaylistSave()
|
||||
{
|
||||
if(!mPlaylistLoading)
|
||||
{
|
||||
if(mPlaylistSavePending.compareAndSet(false, true))
|
||||
{
|
||||
mThreadPoolManager.scheduleOnce(new PlaylistSaveTask(), 2, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the playlist save pending flag to false and proceeds to save the playlist.
|
||||
*/
|
||||
public class PlaylistSaveTask implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
mPlaylistSavePending.set(false);
|
||||
|
||||
save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user