Merge branch 'devel' of dev.sourcefabric.org:airtime into devel

This commit is contained in:
Martin Konecny 2012-09-19 13:23:14 -04:00
commit f717efd7fb
12 changed files with 123 additions and 75 deletions

View File

@ -240,33 +240,6 @@ class Application_Model_StoredFile
} }
} }
/**
* Get one metadata value.
*
* @param string $p_category (MDATA_KEY_URL)
* @return string
*/
public function getMetadataValue($p_category)
{
// constant() was used because it gets quoted constant name value from
// api_client.py. This is the wrapper funtion
return $this->getDbColMetadataValue(constant($p_category));
}
/**
* Get one metadata value.
*
* @param string $p_category (url)
* @return string
*/
public function getDbColMetadataValue($p_category)
{
$propelColumn = $this->_dbMD[$p_category];
$method = "get$propelColumn";
return $this->_file->$method();
}
/** /**
* Get metadata as array, indexed by the column names in the database. * Get metadata as array, indexed by the column names in the database.
* *
@ -423,7 +396,6 @@ SQL;
return $possible_ext; return $possible_ext;
} }
// We fallback to guessing the extension from the mimetype if we // We fallback to guessing the extension from the mimetype if we
// cannot extract it from the file name // cannot extract it from the file name
@ -1020,15 +992,18 @@ SQL;
/** /**
* *
* Enter description here ... * Enter description here ...
* @param $dir_id - if this is not provided, it returns all files with full path constructed. * @param $dir_id - if this is not provided, it returns all files with full
* path constructed.
*/ */
public static function listAllFiles($dir_id=null, $all) public static function listAllFiles($dir_id=null, $all)
{ {
$con = Propel::getConnection(); $con = Propel::getConnection();
$sql = "SELECT filepath as fp" $sql = <<<SQL
." FROM CC_FILES as f" SELECT filepath AS fp
." WHERE f.directory = :dir_id"; FROM CC_FILES AS f
WHERE f.directory = :dir_id
SQL;
if (!$all) { if (!$all) {
$sql .= " AND f.file_exists = 'TRUE'"; $sql .= " AND f.file_exists = 'TRUE'";

View File

@ -39,7 +39,7 @@
<?php if (count($watched_dirs) > 0): ?> <?php if (count($watched_dirs) > 0): ?>
<?php foreach($watched_dirs as $watched_dir): ?> <?php foreach($watched_dirs as $watched_dir): ?>
<dd class="block-display selected-item"> <dd class="block-display selected-item">
<?php echo ($watched_dir->getExistsFlag())?"":"<span class='ui-icon-alert'><img src='/css/images/warning-icon.png'></span>"?><span id="folderPath"><?php echo $watched_dir->getDirectory();?></span></span> <?php echo ($watched_dir->getExistsFlag())?"":"<span class='ui-icon-alert'><img src='/css/images/warning-icon.png'></span>"?><span id="folderPath" style="display:block; width:350px"><?php echo $watched_dir->getDirectory();?></span></span>
<span title="Rescan watched directory (This is useful if it is network mount and may be out of sync with Airtime)" class="ui-icon ui-icon-refresh"></span> <span title="Rescan watched directory (This is useful if it is network mount and may be out of sync with Airtime)" class="ui-icon ui-icon-refresh"></span>
<span title="Remove watched directory" class="ui-icon ui-icon-close"></span> <span title="Remove watched directory" class="ui-icon ui-icon-close"></span>

View File

@ -1,7 +1,10 @@
<div class="wrapper"> <div class="wrapper">
<div id="library_content" class="lib-content tabs ui-widget ui-widget-content block-shadow alpha-block padded"> <div id="library_content" class="lib-content tabs ui-widget ui-widget-content block-shadow alpha-block padded">
<div id="import_status" style="display:none">File import in progress...</div> <div id="import_status" style="display:none">File import in progress...</div>
<div id="advanced_search" class="advanced_search form-horizontal"></div> <fieldset class="toggle" id="filter_options">
<legend style="cursor: pointer;"><span class="ui-icon ui-icon-triangle-2-n-s"></span>Advanced Search Options</legend>
<div id="advanced_search" class="advanced_search form-horizontal"></div>
</fieldset>
<table id="library_display" cellpadding="0" cellspacing="0" class="datatable"></table> <table id="library_display" cellpadding="0" cellspacing="0" class="datatable"></table>
</div> </div>
<div id="show_builder" class="sb-content ui-widget ui-widget-content block-shadow omega-block padded"> <div id="show_builder" class="sb-content ui-widget ui-widget-content block-shadow omega-block padded">

View File

@ -38,6 +38,7 @@ var AIRTIME = (function(AIRTIME) {
"track_num" : "n", "track_num" : "n",
"year" : "n", "year" : "n",
"owner_id" : "s", "owner_id" : "s",
"info_url" : "s",
"replay_gain" : "n" "replay_gain" : "n"
}; };
@ -1041,6 +1042,7 @@ function addQtipToSCIcons(){
*/ */
function validateAdvancedSearch(divs) { function validateAdvancedSearch(divs) {
var valid = true, var valid = true,
allValid = true,
fieldName, fieldName,
fields, fields,
searchTerm = Array(), searchTerm = Array(),
@ -1053,7 +1055,6 @@ function validateAdvancedSearch(divs) {
searchTerm[0] = ""; searchTerm[0] = "";
searchTerm[1] = ""; searchTerm[1] = "";
$.each(divs, function(i, div){ $.each(divs, function(i, div){
fieldName = $(div).children(':nth-child(2)').attr('id'); fieldName = $(div).children(':nth-child(2)').attr('id');
fields = $(div).children().find('input'); fields = $(div).children().find('input');
@ -1085,6 +1086,7 @@ function validateAdvancedSearch(divs) {
//string fields do not need validation //string fields do not need validation
if (searchTermType !== "s") { if (searchTermType !== "s") {
valid = regExpr.test(searchTerm[i]); valid = regExpr.test(searchTerm[i]);
if (!valid) allValid = false;
} }
addRemoveValidationIcons(valid, $(field)); addRemoveValidationIcons(valid, $(field));
@ -1107,13 +1109,9 @@ function validateAdvancedSearch(divs) {
return false; return false;
} }
}); });
if (!valid) {
return false;
}
}); });
return valid; return allValid;
} }
function addRemoveValidationIcons(valid, field) { function addRemoveValidationIcons(valid, field) {

View File

@ -428,6 +428,12 @@ var AIRTIME = (function(AIRTIME){
if (isStatic) { if (isStatic) {
$.each(data, function(index, ele){ $.each(data, function(index, ele){
if (ele.track_title !== undefined) { if (ele.track_title !== undefined) {
if (ele.creator === null) {
ele.creator = "";
}
if (ele.track_title === null) {
ele.track_title = "";
}
$html += "<li>" + $html += "<li>" +
"<span class='block-item-title'>"+ele.track_title+" - </span>" + "<span class='block-item-title'>"+ele.track_title+" - </span>" +
"<span class='block-item-author'>"+ele.creator+"</span>" + "<span class='block-item-author'>"+ele.creator+"</span>" +

View File

@ -2,6 +2,45 @@ from media.monitor.log import Loggable
from media.monitor.events import DeleteFile from media.monitor.events import DeleteFile
class EventContractor(Loggable): class EventContractor(Loggable):
def __init__(self):
self.store = {}
def event_registered(self, evt):
"""
returns true if the event is registered which means that there is
another "unpacked" event somewhere out there with the same path
"""
return evt.path in self.store
def get_old_event(self, evt):
"""
get the previously registered event with the same path as 'evt'
"""
return self.store[ evt.path ]
def register(self, evt):
if self.event_registered(evt):
ev_proxy = self.get_old_event(evt)
if ev_proxy.same_event(evt):
ev_proxy.merge_proxy(evt)
return False
# delete overrides any other event
elif evt.is_event(DeleteFile):
ev_proxy.merge_proxy(evt)
return False
else:
ev_proxy.run_hook()
ev_proxy.reset_hook()
self.store[ evt.path ] = evt
evt.set_pack_hook( lambda : self.__unregister(evt) )
return True
def __unregister(self, evt):
del self.store[evt.path]
# Delete this class when done using
class EventContractor2(Loggable):
""" """
This class is responsible for "contracting" events together to ease the This class is responsible for "contracting" events together to ease the
load on airtime. It does this by morphing old events into newer ones load on airtime. It does this by morphing old events into newer ones

View File

@ -42,6 +42,43 @@ class EventRegistry(object):
raise Exception("You can instantiate this class. Must only use class \ raise Exception("You can instantiate this class. Must only use class \
methods") methods")
class EventProxy(object):
"""
A container object for instances of BaseEvent (or it's subclasses) used for
event contractor
"""
def __init__(self, orig_evt):
self.orig_evt = orig_evt
self.evt = orig_evt
self.reset_hook()
if hasattr(orig_evt, 'path'): self.path = orig_evt.path
def set_pack_hook(self, l):
self._pack_hook = l
def reset_hook(self):
self._pack_hook = lambda : None
def run_hook(self):
self._pack_hook()
def safe_pack(self):
self.run_hook()
# make sure that cleanup hook is never called twice for the same event
self.reset_hook()
return self.evt.safe_pack()
def merge_proxy(self, proxy):
self.evt = proxy.evt
def is_event(self, real_event):
return isinstance(self.evt, real_event)
def same_event(self, proxy):
return self.evt.__class__ == proxy.evt.__class__
class HasMetaData(object): class HasMetaData(object):
""" """
Any class that inherits from this class gains the metadata attribute that Any class that inherits from this class gains the metadata attribute that
@ -91,6 +128,9 @@ class BaseEvent(Loggable):
""" """
self._pack_hook = k self._pack_hook = k
def proxify(self):
return EventProxy(self)
# As opposed to unsafe_pack... # As opposed to unsafe_pack...
def safe_pack(self): def safe_pack(self):
""" """

View File

@ -490,7 +490,6 @@ def file_playable(pathname):
command = ("airtime-liquidsoap -c 'output.dummy" + \ command = ("airtime-liquidsoap -c 'output.dummy" + \
"(audio_to_stereo(single(\"%s\")))' > /dev/null 2>&1") % \ "(audio_to_stereo(single(\"%s\")))' > /dev/null 2>&1") % \
pathname.replace("'", "'\\''") pathname.replace("'", "'\\''")
print(command)
return True return True
return_code = subprocess.call(command, shell=True) return_code = subprocess.call(command, shell=True)
return (return_code == 0) return (return_code == 0)

View File

@ -8,6 +8,7 @@ from media.monitor.log import Loggable
from media.monitor.exceptions import BadSongFile from media.monitor.exceptions import BadSongFile
from media.monitor.pure import LazyProperty from media.monitor.pure import LazyProperty
from media.monitor.eventcontractor import EventContractor from media.monitor.eventcontractor import EventContractor
from media.monitor.events import EventProxy
import api_clients.api_client as ac import api_clients.api_client as ac
@ -125,7 +126,7 @@ class WatchSyncer(ReportHandler,Loggable):
try: try:
# If there is a strange bug anywhere in the code the next line # If there is a strange bug anywhere in the code the next line
# should be a suspect # should be a suspect
if self.contractor.register(event): self.push_queue( event ) if self.contractor.register(EventProxy(event)): self.push_queue( event )
#self.push_queue( event ) #self.push_queue( event )
except BadSongFile as e: except BadSongFile as e:
self.fatal_exception("Received bas song file '%s'" % e.path, e) self.fatal_exception("Received bas song file '%s'" % e.path, e)

View File

@ -1,19 +1,15 @@
import shutil
import subprocess
# The tests rely on a lot of absolute paths and other garbage so this file # The tests rely on a lot of absolute paths and other garbage so this file
# configures all of that # configures all of that
music_folder = u'/home/rudi/music' music_folder = u'/home/rudi/music'
o_path = u'/home/rudi/throwaway/ACDC_-_Back_In_Black-sample-64kbps.ogg' o_path = u'/home/rudi/throwaway/ACDC_-_Back_In_Black-sample-64kbps.ogg'
watch_path = u'/home/rudi/throwaway/fucking_around/watch/', watch_path = u'/home/rudi/throwaway/fucking_around/watch/',
real_path1 = u'/home/rudi/throwaway/fucking_around/watch/unknown/unknown/ACDC_-_Back_In_Black-sample-64kbps-64kbps.ogg' real_path1 = u'/home/rudi/throwaway/fucking_around/watch/unknown/unknown/ACDC_-_Back_In_Black-sample-64kbps-64kbps.ogg'
opath = u"/home/rudi/Airtime/python_apps/media-monitor2/tests/" opath = u"/home/rudi/Airtime/python_apps/media-monitor2/tests/"
ppath = u"/home/rudi/Airtime/python_apps/media-monitor2/media/" ppath = u"/home/rudi/Airtime/python_apps/media-monitor2/media/"
sample_config = u'/home/rudi/Airtime/python_apps/media-monitor2/tests/api_client.cfg'
real_config = u'/home/rudi/Airtime/python_apps/media-monitor2/tests/live_client.cfg'
api_client_path = '/etc/airtime/api_client.cfg' api_client_path = '/etc/airtime/api_client.cfg'
# holdover from the time we had a special config for testing
if __name__ == "__main__": sample_config = api_client_path
shutil.copy(api_client_path, real_config) real_config = api_client_path
# TODO : fix this to use liberal permissions
subprocess.call(["chown","rudi",real_config])

View File

@ -9,7 +9,8 @@ import prepare_tests
class TestApiClient(unittest.TestCase): class TestApiClient(unittest.TestCase):
def setUp(self): def setUp(self):
test_path = prepare_tests.real_config test_path = prepare_tests.api_client_path
print("Running from api_config: %s" % test_path)
if not os.path.exists(test_path): if not os.path.exists(test_path):
print("path for config does not exist: '%s' % test_path") print("path for config does not exist: '%s' % test_path")
# TODO : is there a cleaner way to exit the unit testing? # TODO : is there a cleaner way to exit the unit testing?

View File

@ -3,48 +3,37 @@ from media.monitor.eventcontractor import EventContractor
#from media.monitor.exceptions import BadSongFile #from media.monitor.exceptions import BadSongFile
from media.monitor.events import FakePyinotify, NewFile, MoveFile, \ from media.monitor.events import FakePyinotify, NewFile, MoveFile, \
DeleteFile DeleteFile
from mock import patch
class TestMMP(unittest.TestCase): class TestMMP(unittest.TestCase):
def test_event_registered(self): def test_event_registered(self):
ev = EventContractor() ev = EventContractor()
e1 = NewFile( FakePyinotify('bullshit.mp3') ) e1 = NewFile( FakePyinotify('bullshit.mp3') ).proxify()
e2 = MoveFile( FakePyinotify('bullshit.mp3') ) e2 = MoveFile( FakePyinotify('bullshit.mp3') ).proxify()
ev.register(e1) ev.register(e1)
self.assertTrue( ev.event_registered(e2) ) self.assertTrue( ev.event_registered(e2) )
def test_get_old_event(self): def test_get_old_event(self):
ev = EventContractor() ev = EventContractor()
e1 = NewFile( FakePyinotify('bullshit.mp3') ) e1 = NewFile( FakePyinotify('bullshit.mp3') ).proxify()
e2 = MoveFile( FakePyinotify('bullshit.mp3') ) e2 = MoveFile( FakePyinotify('bullshit.mp3') ).proxify()
ev.register(e1) ev.register(e1)
self.assertEqual( ev.get_old_event(e2), e1 ) self.assertEqual( ev.get_old_event(e2), e1 )
def test_register(self): def test_register(self):
ev = EventContractor() ev = EventContractor()
e1 = NewFile( FakePyinotify('bullshit.mp3') ) e1 = NewFile( FakePyinotify('bullshit.mp3') ).proxify()
e2 = DeleteFile( FakePyinotify('bullshit.mp3') ) e2 = DeleteFile( FakePyinotify('bullshit.mp3') ).proxify()
self.assertTrue( ev.register(e1) ) 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) ) self.assertFalse( ev.register(e2) )
# We did not an element
self.assertTrue( len(ev.store.keys()) == 1 ) self.assertEqual( len(ev.store.keys()), 1 )
morphed = ev.get_old_event(e2)
self.assertTrue( isinstance(morphed, DeleteFile) )
delete_ev = e1.safe_pack()[0] delete_ev = e1.safe_pack()[0]
self.assertEqual( delete_ev['mode'], u'delete') self.assertEqual( delete_ev['mode'], u'delete')
self.assertTrue( len(ev.store.keys()) == 0 ) self.assertEqual( len(ev.store.keys()), 0 )
e3 = DeleteFile( FakePyinotify('horseshit.mp3') ) e3 = DeleteFile( FakePyinotify('horseshit.mp3') ).proxify()
self.assertTrue( ev.register(e3) ) self.assertTrue( ev.register(e3) )
self.assertTrue( ev.register(e2) ) self.assertTrue( ev.register(e2) )
@ -58,11 +47,12 @@ class TestMMP(unittest.TestCase):
DeleteFile( FakePyinotify(p) ), DeleteFile( FakePyinotify(p) ),
NewFile( FakePyinotify(p) ), NewFile( FakePyinotify(p) ),
NewFile( FakePyinotify(p) ), ] NewFile( FakePyinotify(p) ), ]
events = map(lambda x: x.proxify(), events)
actual_events = [] actual_events = []
for e in events: for e in events:
if ev.register(e): if ev.register(e):
actual_events.append(e) actual_events.append(e)
self.assertEqual( len(ev.store.keys()), 1 ) self.assertEqual( len(ev.store.keys()), 1 )
packed = [ x.safe_pack() for x in actual_events ] #packed = [ x.safe_pack() for x in actual_events ]
if __name__ == '__main__': unittest.main() if __name__ == '__main__': unittest.main()