diff --git a/airtime_mvc/application/controllers/ApiController.php b/airtime_mvc/application/controllers/ApiController.php index 6e16f6343..622d87700 100644 --- a/airtime_mvc/application/controllers/ApiController.php +++ b/airtime_mvc/application/controllers/ApiController.php @@ -484,16 +484,10 @@ class ApiController extends Zend_Controller_Action if (is_null($file)) { $file = Application_Model_StoredFile::Insert($md); } else { - // path already exist - if ($file->getFileExistsFlag()) { - // file marked as exists - $return_hash['error'] = "File already exists in Airtime."; - return $return_hash; - } else { - // file marked as not exists - $file->setFileExistsFlag(true); - $file->setMetadata($md); - } + // If the file already exists we will update and make sure that + // it's marked as 'exists'. + $file->setFileExistsFlag(true); + $file->setMetadata($md); } } else if ($mode == "modify") { diff --git a/airtime_mvc/public/js/airtime/library/spl.js b/airtime_mvc/public/js/airtime/library/spl.js index 2ca4e4732..dedd7c2b1 100644 --- a/airtime_mvc/public/js/airtime/library/spl.js +++ b/airtime_mvc/public/js/airtime/library/spl.js @@ -311,11 +311,25 @@ var AIRTIME = (function(AIRTIME){ } function setFadeIcon(){ + var contents = $("#spl_sortable"); + var show = contents.is(":visible"); var empty = $(".spl_empty"); - if (empty.length > 0) { + + if (!show || empty.length > 0) { $("#spl_crossfade").hide(); } else { - $("#spl_crossfade").show(); + //get list of playlist contents + var list = contents.children(); + + //if first and last items are blocks, hide the fade icon + var first = list.first(); + var last = list.last(); + if (first.find(':first-child').children().attr('blockid') !== undefined && + last.find(':first-child').children().attr('blockid') !== undefined) { + $("#spl_crossfade").hide(); + } else { + $("#spl_crossfade").show(); + } } } @@ -468,7 +482,9 @@ var AIRTIME = (function(AIRTIME){ fadeOut.show(); fadeOut.empty().append(json.fadeOut); } - $pl.find("#crossfade_main").show(); + if (json.fadeIn != null || json.fadeOut != null) { + $pl.find("#crossfade_main").show(); + } } }); } @@ -889,6 +905,7 @@ var AIRTIME = (function(AIRTIME){ setPlaylistEntryEvents(); setCueEvents(); setFadeEvents(); + setFadeIcon(); initialEvents(); setUpPlaylist(); diff --git a/airtime_mvc/public/js/airtime/playlist/smart_playlistbuilder.js b/airtime_mvc/public/js/airtime/playlist/smart_playlistbuilder.js index dc667f90c..ccb2492ba 100644 --- a/airtime_mvc/public/js/airtime/playlist/smart_playlistbuilder.js +++ b/airtime_mvc/public/js/airtime/playlist/smart_playlistbuilder.js @@ -239,16 +239,6 @@ function getRowIndex(ele) { return index; } -function setFadeIcon(){ - var contents = $("#spl_sortable"); - var show = contents.is(":visible"); - if (show) { - $("#spl_crossfade").show(); - } else { - $("#spl_crossfade").hide(); - } -} - /* This function appends a '+' button for the last * modifier row of each criteria. * If there are no modifier rows, the '+' button diff --git a/python_apps/media-monitor2/media/monitor/eventcontractor.py b/python_apps/media-monitor2/media/monitor/eventcontractor.py index 75fb384df..77a5045fb 100644 --- a/python_apps/media-monitor2/media/monitor/eventcontractor.py +++ b/python_apps/media-monitor2/media/monitor/eventcontractor.py @@ -29,6 +29,7 @@ class EventContractor(Loggable): some other event in the storage was morphed into this newer one. Which should mean that the old event should be discarded. """ + self.logger.info("Attempting to register: '%s'" % str(evt)) if self.event_registered(evt): old_e = self.get_old_event(evt) # TODO : Perhaps there are other events that we can "contract" @@ -42,11 +43,20 @@ class EventContractor(Loggable): elif isinstance(evt, DeleteFile): old_e.morph_into(evt) return False + # Unregister the old event anyway, because we only want to keep + # track of the old one. This means that the old event cannot be + # morphed again and new events with the same path will only be + # checked against the newest event 'evt' in this case + self.unregister( old_e ) evt.add_safe_pack_hook( lambda : self.__unregister(evt) ) self.store[ evt.path ] = evt return True # We actually added something, hence we return true. + def unregister(self, evt): + evt.reset_hook() + def __unregister(self, evt): - self.logger.info("Unregistering. Left: '%d'" % len(self.store.keys())) try: del self.store[evt.path] - except Exception as e: self.unexpected_exception(e) + except Exception as e: + self.unexpected_exception(e) + self.logger.info("Unregistering. Left: '%d'" % len(self.store.keys())) diff --git a/python_apps/media-monitor2/media/monitor/events.py b/python_apps/media-monitor2/media/monitor/events.py index 10df5e01a..43ae883f8 100644 --- a/python_apps/media-monitor2/media/monitor/events.py +++ b/python_apps/media-monitor2/media/monitor/events.py @@ -10,7 +10,7 @@ from media.monitor.exceptions import BadSongFile class PathChannel(object): def __init__(self, signal, path): self.signal = signal - self.path = path + self.path = path class EventRegistry(object): """ @@ -59,6 +59,10 @@ class BaseEvent(Loggable): self._pack_hook = lambda: None # no op # into another event + def reset_hook(self): + self._pack_hook() + self._pack_hook = lambda: None + def exists(self): return os.path.exists(self.path) @LazyProperty @@ -84,8 +88,8 @@ class BaseEvent(Loggable): # pack will only throw an exception if it processes one file but this # is a little bit hacky try: - ret = self.pack() self._pack_hook() + ret = self.pack() return ret except BadSongFile as e: return [e] @@ -95,7 +99,7 @@ class BaseEvent(Loggable): self._raw_event = evt self.path = evt.path self.__class__ = evt.__class__ - self.add_safe_pack_hook(evt._pack_hook) + # We don't transfer the _pack_hook over to the new event return self class FakePyinotify(object): @@ -104,8 +108,7 @@ class FakePyinotify(object): instantiate objects from the classes below whenever we want to turn a single event into multiple events """ - def __init__(self, path): - self.pathname = path + def __init__(self, path): self.pathname = path class OrganizeFile(BaseEvent, HasMetaData): def __init__(self, *args, **kwargs): diff --git a/python_apps/media-monitor2/media/monitor/listeners.py b/python_apps/media-monitor2/media/monitor/listeners.py index 18a1925c5..d3824a43c 100644 --- a/python_apps/media-monitor2/media/monitor/listeners.py +++ b/python_apps/media-monitor2/media/monitor/listeners.py @@ -88,7 +88,7 @@ class OrganizeListener(BaseListener, pyinotify.ProcessEvent, Loggable): dispatcher.send(signal=self.signal, sender=self, event=OrganizeFile(f)) flushed += 1 - self.logger.info("Flushed organized directory with %d files" % flushed) + #self.logger.info("Flushed organized directory with %d files" % flushed) @IncludeOnly(mmp.supported_extensions) def process_to_organize(self, event): diff --git a/python_apps/media-monitor2/media/monitor/manager.py b/python_apps/media-monitor2/media/monitor/manager.py index fb4bc58df..31cd3ab09 100644 --- a/python_apps/media-monitor2/media/monitor/manager.py +++ b/python_apps/media-monitor2/media/monitor/manager.py @@ -20,7 +20,7 @@ class ManagerTimeout(threading.Thread,Loggable): while True: time.sleep(3) self.manager.flush_organize() - self.logger.info("Force flushed organize...") + #self.logger.info("Force flushed organize...") class Manager(Loggable): """ diff --git a/python_apps/media-monitor2/media/monitor/watchersyncer.py b/python_apps/media-monitor2/media/monitor/watchersyncer.py index c1c7a610c..86e66f3f9 100644 --- a/python_apps/media-monitor2/media/monitor/watchersyncer.py +++ b/python_apps/media-monitor2/media/monitor/watchersyncer.py @@ -19,9 +19,9 @@ class RequestSync(threading.Thread,Loggable): """ def __init__(self, watcher, requests): threading.Thread.__init__(self) - self.watcher = watcher - self.requests = requests - self.retries = 1 + self.watcher = watcher + self.requests = requests + self.retries = 1 self.request_wait = 0.3 @LazyProperty @@ -124,9 +124,8 @@ class WatchSyncer(ReportHandler,Loggable): try: # If there is a strange bug anywhere in the code the next line # should be a suspect - #if self.contractor.register( event ): - #self.push_queue( event ) - self.push_queue( event ) + if self.contractor.register(event): self.push_queue( event ) + #self.push_queue( event ) except BadSongFile as e: self.fatal_exception("Received bas song file '%s'" % e.path, e) except Exception as e: diff --git a/python_apps/media-monitor2/tests/test_eventcontractor.py b/python_apps/media-monitor2/tests/test_eventcontractor.py new file mode 100644 index 000000000..58f77856c --- /dev/null +++ b/python_apps/media-monitor2/tests/test_eventcontractor.py @@ -0,0 +1,70 @@ +import unittest +from media.monitor.eventcontractor import EventContractor +#from media.monitor.exceptions import BadSongFile +from media.monitor.events import FakePyinotify, NewFile, MoveFile, \ +DeleteFile +from mock import patch + +class TestMMP(unittest.TestCase): + def test_event_registered(self): + ev = EventContractor() + e1 = NewFile( FakePyinotify('bullshit.mp3') ) + e2 = MoveFile( FakePyinotify('bullshit.mp3') ) + ev.register(e1) + self.assertTrue( ev.event_registered(e2) ) + + def test_get_old_event(self): + ev = EventContractor() + e1 = NewFile( FakePyinotify('bullshit.mp3') ) + e2 = MoveFile( FakePyinotify('bullshit.mp3') ) + ev.register(e1) + self.assertEqual( ev.get_old_event(e2), e1 ) + + def test_register(self): + ev = EventContractor() + e1 = NewFile( FakePyinotify('bullshit.mp3') ) + e2 = DeleteFile( FakePyinotify('bullshit.mp3') ) + self.assertTrue( ev.register(e1) ) + + # Check that morph_into is called when it should be + with patch.object(NewFile, 'morph_into', return_value='kimchi') \ + as mock_method: + ret = ev.register(e2) + self.assertFalse(ret) + mock_method.assert_called_once_with(e2) + + # This time we are not patching morph + self.assertFalse( ev.register(e2) ) + # We did not an element + self.assertTrue( len(ev.store.keys()) == 1 ) + morphed = ev.get_old_event(e2) + self.assertTrue( isinstance(morphed, DeleteFile) ) + + delete_ev = e1.safe_pack()[0] + print( ev.store ) + self.assertEqual( delete_ev['mode'], u'delete') + self.assertTrue( len(ev.store.keys()) == 0 ) + + e3 = DeleteFile( FakePyinotify('horseshit.mp3') ) + self.assertTrue( ev.register(e3) ) + self.assertTrue( ev.register(e2) ) + + + def test_register2(self): + ev = EventContractor() + p = 'bullshit.mp3' + events = [ + NewFile( FakePyinotify(p) ), + NewFile( FakePyinotify(p) ), + DeleteFile( FakePyinotify(p) ), + NewFile( FakePyinotify(p) ), + NewFile( FakePyinotify(p) ), ] + actual_events = [] + for e in events: + if ev.register(e): + actual_events.append(e) + self.assertEqual( len(ev.store.keys()), 1 ) + packed = [ x.safe_pack() for x in actual_events ] + print(packed) + +if __name__ == '__main__': unittest.main() diff --git a/python_apps/python-virtualenv/airtime_virtual_env.pybundle b/python_apps/python-virtualenv/airtime_virtual_env.pybundle index c2d6efea4..4f1383c59 100644 Binary files a/python_apps/python-virtualenv/airtime_virtual_env.pybundle and b/python_apps/python-virtualenv/airtime_virtual_env.pybundle differ diff --git a/python_apps/python-virtualenv/requirements b/python_apps/python-virtualenv/requirements new file mode 100644 index 000000000..72748874a --- /dev/null +++ b/python_apps/python-virtualenv/requirements @@ -0,0 +1,12 @@ +argparse==1.2.1 +amqplib==1.0.2 +PyDispatcher==2.0.3 +anyjson==0.3.3 +kombu==2.2.6 +pyinotify==0.9.3 +poster==0.8.1 +pytz==2011k +wsgiref==0.1.2 +configobj==4.7.2 +mutagen==1.20 +docopt==0.4.2