diff --git a/src/controller/channel/ChannelEditor.java b/src/controller/channel/ChannelEditor.java index 6df2eb4e..7879b03d 100644 --- a/src/controller/channel/ChannelEditor.java +++ b/src/controller/channel/ChannelEditor.java @@ -74,6 +74,8 @@ public class ChannelEditor extends JPanel private PlaylistManager mPlaylistManager; private SourceManager mSourceManager; + private boolean mChannelEnableRequested = false; + public ChannelEditor( ChannelModel channelModel, PlaylistManager playlistManager, SourceManager sourceManager ) @@ -108,7 +110,7 @@ public class ChannelEditor extends JPanel mChannelName.setText( "Channel: " + channel.getName() ); mEnableButton.setText( channel.getEnabled() ? "Disable" : "Enable" ); mEnableButton.setEnabled( true ); - mEnableButton.setBackground( channel.getEnabled() ? Color.RED : Color.GREEN ); + mEnableButton.setBackground( channel.getEnabled() ? Color.GREEN : getBackground() ); } else { @@ -126,6 +128,7 @@ public class ChannelEditor extends JPanel { setLayout( new MigLayout( "fill,wrap 2", "[right,grow][grow]", "[][][][][grow][]" ) ); + mEnableButton.addActionListener( this ); mEnableButton.setEnabled( false ); add( mEnableButton, "growx,push" ); add( mChannelName, "growx,push" ); @@ -175,15 +178,28 @@ public class ChannelEditor extends JPanel case NOTIFICATION_PROCESSING_START: case NOTIFICATION_PROCESSING_STOP: setChannel( mChannel ); + mChannelEnableRequested = false; break; case NOTIFICATION_DELETE: mChannel = null; setChannel( mChannel ); break; + case NOTIFICATION_ENABLE_REJECTED: + if( mChannelEnableRequested ) + { + JOptionPane.showMessageDialog( ChannelEditor.this, + "Channel could not be enabled. This is likely because " + + "there are no tuner channels available", "Couldn't " + + "enable channel", JOptionPane.INFORMATION_MESSAGE ); + + mChannelEnableRequested = false; + } + break; default: break; } } + } @Override @@ -191,7 +207,22 @@ public class ChannelEditor extends JPanel { String command = e.getActionCommand(); - if( command.contentEquals( "Save" ) ) + if( command.contentEquals( "Enable" ) ) + { + if( mChannel != null ) + { + mChannelEnableRequested = true; + mChannelModel.broadcast( new ChannelEvent( mChannel, Event.REQUEST_ENABLE ) ); + } + } + else if( command.contentEquals( "Disable" ) ) + { + if( mChannel != null ) + { + mChannelModel.broadcast( new ChannelEvent( mChannel, Event.REQUEST_DISABLE ) ); + } + } + else if( command.contentEquals( "Save" ) ) { save(); } diff --git a/src/controller/channel/ChannelModel.java b/src/controller/channel/ChannelModel.java index 1f0d6821..751c374e 100644 --- a/src/controller/channel/ChannelModel.java +++ b/src/controller/channel/ChannelModel.java @@ -170,6 +170,11 @@ public class ChannelModel implements IChannelEventBroadcaster, TableModel } broadcast( new ChannelEvent( channel, Event.NOTIFICATION_ADD ) ); + + if( channel.getEnabled() ) + { + broadcast( new ChannelEvent( channel, Event.REQUEST_ENABLE ) ); + } } /** diff --git a/src/controller/channel/ChannelProcessingManager.java b/src/controller/channel/ChannelProcessingManager.java index 5d589766..610c4437 100644 --- a/src/controller/channel/ChannelProcessingManager.java +++ b/src/controller/channel/ChannelProcessingManager.java @@ -140,6 +140,13 @@ public class ChannelProcessingManager implements ChannelEventListener stopProcessing( channel, true ); } break; + case NOTIFICATION_CONFIGURATION_CHANGE: + if( isProcessing( channel ) ) + { + stopProcessing( channel, false ); + startProcessing( channel ); + } + break; default: break; } diff --git a/src/gui/SDRTrunk.java b/src/gui/SDRTrunk.java index 6de05d02..4447562f 100644 --- a/src/gui/SDRTrunk.java +++ b/src/gui/SDRTrunk.java @@ -1,6 +1,6 @@ /******************************************************************************* * SDR Trunk - * Copyright (C) 2014 Dennis Sheirer + * Copyright (C) 2014-2016 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 @@ -120,7 +120,7 @@ public class SDRTrunk ChannelModel channelModel = new ChannelModel(); - PlaylistManager playlistManager = new PlaylistManager( channelModel ); + PlaylistManager playlistManager = new PlaylistManager( threadPoolManager, channelModel ); RecorderManager recorderManager = new RecorderManager( threadPoolManager ); @@ -146,6 +146,9 @@ public class SDRTrunk new ChannelSelectionManager( channelModel ); channelModel.addListener( channelSelectionManager ); +//TODO: move this to the end once we add the alias model, otherwise the +//configuration tree won't load correctly + //Initialize the playlist manager to load the saved playlist playlistManager.init(); diff --git a/src/instrument/gui/DecoderSelectionFrame.java b/src/instrument/gui/DecoderSelectionFrame.java index 998191b1..8c242b66 100644 --- a/src/instrument/gui/DecoderSelectionFrame.java +++ b/src/instrument/gui/DecoderSelectionFrame.java @@ -13,6 +13,7 @@ import module.decode.config.DecodeConfiguration; import net.miginfocom.swing.MigLayout; import playlist.PlaylistManager; import source.IControllableFileSource; +import controller.ThreadPoolManager; import controller.channel.ChannelModel; public class DecoderSelectionFrame extends JInternalFrame @@ -29,7 +30,10 @@ public class DecoderSelectionFrame extends JInternalFrame IControllableFileSource source ) { ChannelModel channelModel = new ChannelModel(); - mPlaylistManager = new PlaylistManager( channelModel ); + + ThreadPoolManager tpm = new ThreadPoolManager(); + + mPlaylistManager = new PlaylistManager( tpm, channelModel ); mDesktop = desktop; mSource = source; diff --git a/src/playlist/PlaylistManager.java b/src/playlist/PlaylistManager.java index 9d43c496..dea60a8c 100644 --- a/src/playlist/PlaylistManager.java +++ b/src/playlist/PlaylistManager.java @@ -1,6 +1,6 @@ /******************************************************************************* * SDR Trunk - * Copyright (C) 2014 Dennis Sheirer + * Copyright (C) 2014-2016 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 @@ -25,6 +25,8 @@ 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; @@ -39,8 +41,16 @@ import controller.channel.Channel; import controller.channel.ChannelModel; import controller.site.Site; import controller.system.SystemList; +import controller.ThreadPoolManager; +import controller.channel.Channel; +import controller.channel.Channel.ChannelType; +import controller.channel.ChannelEvent; +import controller.channel.ChannelEventListener; +import controller.channel.ChannelModel; +import controller.site.Site; +import controller.system.SystemList; -public class PlaylistManager +public class PlaylistManager implements ChannelEventListener { private final static Logger mLog = LoggerFactory.getLogger( PlaylistManager.class ); @@ -50,8 +60,31 @@ public class PlaylistManager private ChannelModel mChannelModel; public PlaylistManager( ChannelModel channelModel ) + private ThreadPoolManager mThreadPoolManager; + private ChannelModel mChannelModel; + + private AtomicBoolean mPlaylistSavePending = new AtomicBoolean(); + private boolean mPlaylistLoading = false; + + /** + * Playlist manager - manages all channel configurationc, 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, + ChannelModel channelModel ) { + mThreadPoolManager = threadPoolManager; mChannelModel = channelModel; + + //Register for channel events so that we can save the playlist when + //any channels change + mChannelModel.addListener( this ); } public Playlist getPlayist() @@ -64,8 +97,11 @@ public class PlaylistManager */ private void transferPlaylistToModels() { - //Load channels + mPlaylistLoading = true; + mChannelModel.addChannels( mPlaylist.getChannels() ); + + mPlaylistLoading = false; } /** @@ -88,7 +124,46 @@ public class PlaylistManager transferPlaylistToModels(); } - + + /** + * 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() { @@ -284,12 +359,87 @@ public class PlaylistManager } else { - mLog.info( "playlist does not exist [" + playlistPath.toString() + "]" ); + mLog.info( "PlaylistManager - playlist not found at [" + + playlistPath.toString() + "]" ); } if( mPlaylist == null ) { mPlaylist = new Playlist(); + save(); + } + + //Check for and convert from legacy play list format + if( mPlaylist.hasSystemList() ) + { + convertPlaylistFormat(); + } + } + + /** + * Converts playlist data over to new format + */ + private void convertPlaylistFormat() + { + mLog.info( "Legacy playlist format detected - converting ..." ); + + createBackupPlaylist(); + + //Playlist version 1 to version 2 format conversion. In order to keep + //backwards compatibility, transfer all of the System-Site-Channel + //objects over to the new channel list format + SystemList systemList = mPlaylist.getSystemList(); + + for( controller.system.System system: systemList.getSystem() ) + { + for( Site site: system.getSite() ) + { + for( Channel channel: site.getChannel() ) + { + channel.setSystem( system.getName() ); + channel.setSite( site.getName() ); + + mPlaylist.getChannels().add( channel ); + } + } + } + + mPlaylist.getSystemList().clearSystems(); + + mLog.info( "Converted [" + mPlaylist.getChannels().size() + + "] channels to new playlist format" ); + + 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 ); + } + } + } + + /** + * Saves the playlist and resets the playlist save pending flag to false + */ + public class PlaylistSaveTask implements Runnable + { + @Override + public void run() + { + save(); + + mPlaylistSavePending.set( false ); } //Check for and convert from legacy play list format