From 61f68c08e8ea1560cf6bdb5ece2371a57b73ffec Mon Sep 17 00:00:00 2001 From: denny Date: Sun, 24 Jan 2016 07:19:30 -0500 Subject: [PATCH] Adds support to channel model and processing manager to auto-start channels upon loading the playlist. Processing manager now detects channel configuration changes and restarts the channel if it is currently processing. Channel editor enable button now shows green if it is processing, or default when not processing. --- src/controller/channel/ChannelEditor.java | 35 +++- src/controller/channel/ChannelModel.java | 5 + .../channel/ChannelProcessingManager.java | 7 + src/gui/SDRTrunk.java | 7 +- src/instrument/gui/DecoderSelectionFrame.java | 6 +- src/playlist/PlaylistManager.java | 160 +++++++++++++++++- 6 files changed, 210 insertions(+), 10 deletions(-) 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