Merge branch 'devel' of dev.sourcefabric.org:airtime into devel
This commit is contained in:
commit
f717efd7fb
|
@ -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.
|
||||
*
|
||||
|
@ -423,7 +396,6 @@ SQL;
|
|||
return $possible_ext;
|
||||
}
|
||||
|
||||
|
||||
// We fallback to guessing the extension from the mimetype if we
|
||||
// cannot extract it from the file name
|
||||
|
||||
|
@ -1020,15 +992,18 @@ SQL;
|
|||
/**
|
||||
*
|
||||
* 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)
|
||||
{
|
||||
$con = Propel::getConnection();
|
||||
|
||||
$sql = "SELECT filepath as fp"
|
||||
." FROM CC_FILES as f"
|
||||
." WHERE f.directory = :dir_id";
|
||||
$sql = <<<SQL
|
||||
SELECT filepath AS fp
|
||||
FROM CC_FILES AS f
|
||||
WHERE f.directory = :dir_id
|
||||
SQL;
|
||||
|
||||
if (!$all) {
|
||||
$sql .= " AND f.file_exists = 'TRUE'";
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
<?php if (count($watched_dirs) > 0): ?>
|
||||
<?php foreach($watched_dirs as $watched_dir): ?>
|
||||
<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="Remove watched directory" class="ui-icon ui-icon-close"></span>
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
<div class="wrapper">
|
||||
<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="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>
|
||||
</div>
|
||||
<div id="show_builder" class="sb-content ui-widget ui-widget-content block-shadow omega-block padded">
|
||||
|
|
|
@ -38,6 +38,7 @@ var AIRTIME = (function(AIRTIME) {
|
|||
"track_num" : "n",
|
||||
"year" : "n",
|
||||
"owner_id" : "s",
|
||||
"info_url" : "s",
|
||||
"replay_gain" : "n"
|
||||
};
|
||||
|
||||
|
@ -1041,6 +1042,7 @@ function addQtipToSCIcons(){
|
|||
*/
|
||||
function validateAdvancedSearch(divs) {
|
||||
var valid = true,
|
||||
allValid = true,
|
||||
fieldName,
|
||||
fields,
|
||||
searchTerm = Array(),
|
||||
|
@ -1053,7 +1055,6 @@ function validateAdvancedSearch(divs) {
|
|||
|
||||
searchTerm[0] = "";
|
||||
searchTerm[1] = "";
|
||||
|
||||
$.each(divs, function(i, div){
|
||||
fieldName = $(div).children(':nth-child(2)').attr('id');
|
||||
fields = $(div).children().find('input');
|
||||
|
@ -1085,6 +1086,7 @@ function validateAdvancedSearch(divs) {
|
|||
//string fields do not need validation
|
||||
if (searchTermType !== "s") {
|
||||
valid = regExpr.test(searchTerm[i]);
|
||||
if (!valid) allValid = false;
|
||||
}
|
||||
|
||||
addRemoveValidationIcons(valid, $(field));
|
||||
|
@ -1107,13 +1109,9 @@ function validateAdvancedSearch(divs) {
|
|||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (!valid) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return valid;
|
||||
|
||||
return allValid;
|
||||
}
|
||||
|
||||
function addRemoveValidationIcons(valid, field) {
|
||||
|
|
|
@ -428,6 +428,12 @@ var AIRTIME = (function(AIRTIME){
|
|||
if (isStatic) {
|
||||
$.each(data, function(index, ele){
|
||||
if (ele.track_title !== undefined) {
|
||||
if (ele.creator === null) {
|
||||
ele.creator = "";
|
||||
}
|
||||
if (ele.track_title === null) {
|
||||
ele.track_title = "";
|
||||
}
|
||||
$html += "<li>" +
|
||||
"<span class='block-item-title'>"+ele.track_title+" - </span>" +
|
||||
"<span class='block-item-author'>"+ele.creator+"</span>" +
|
||||
|
|
|
@ -2,6 +2,45 @@ from media.monitor.log import Loggable
|
|||
from media.monitor.events import DeleteFile
|
||||
|
||||
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
|
||||
load on airtime. It does this by morphing old events into newer ones
|
||||
|
|
|
@ -42,6 +42,43 @@ class EventRegistry(object):
|
|||
raise Exception("You can instantiate this class. Must only use class \
|
||||
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):
|
||||
"""
|
||||
Any class that inherits from this class gains the metadata attribute that
|
||||
|
@ -91,6 +128,9 @@ class BaseEvent(Loggable):
|
|||
"""
|
||||
self._pack_hook = k
|
||||
|
||||
def proxify(self):
|
||||
return EventProxy(self)
|
||||
|
||||
# As opposed to unsafe_pack...
|
||||
def safe_pack(self):
|
||||
"""
|
||||
|
|
|
@ -490,7 +490,6 @@ def file_playable(pathname):
|
|||
command = ("airtime-liquidsoap -c 'output.dummy" + \
|
||||
"(audio_to_stereo(single(\"%s\")))' > /dev/null 2>&1") % \
|
||||
pathname.replace("'", "'\\''")
|
||||
print(command)
|
||||
return True
|
||||
return_code = subprocess.call(command, shell=True)
|
||||
return (return_code == 0)
|
||||
|
|
|
@ -8,6 +8,7 @@ from media.monitor.log import Loggable
|
|||
from media.monitor.exceptions import BadSongFile
|
||||
from media.monitor.pure import LazyProperty
|
||||
from media.monitor.eventcontractor import EventContractor
|
||||
from media.monitor.events import EventProxy
|
||||
|
||||
import api_clients.api_client as ac
|
||||
|
||||
|
@ -125,7 +126,7 @@ 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 )
|
||||
if self.contractor.register(EventProxy(event)): self.push_queue( event )
|
||||
#self.push_queue( event )
|
||||
except BadSongFile as e:
|
||||
self.fatal_exception("Received bas song file '%s'" % e.path, e)
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
import shutil
|
||||
import subprocess
|
||||
# The tests rely on a lot of absolute paths and other garbage so this file
|
||||
# configures all of that
|
||||
|
||||
music_folder = u'/home/rudi/music'
|
||||
o_path = u'/home/rudi/throwaway/ACDC_-_Back_In_Black-sample-64kbps.ogg'
|
||||
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'
|
||||
opath = u"/home/rudi/Airtime/python_apps/media-monitor2/tests/"
|
||||
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'
|
||||
|
||||
if __name__ == "__main__":
|
||||
shutil.copy(api_client_path, real_config)
|
||||
# TODO : fix this to use liberal permissions
|
||||
subprocess.call(["chown","rudi",real_config])
|
||||
# holdover from the time we had a special config for testing
|
||||
sample_config = api_client_path
|
||||
real_config = api_client_path
|
||||
|
||||
|
|
|
@ -9,7 +9,8 @@ import prepare_tests
|
|||
|
||||
class TestApiClient(unittest.TestCase):
|
||||
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):
|
||||
print("path for config does not exist: '%s' % test_path")
|
||||
# TODO : is there a cleaner way to exit the unit testing?
|
||||
|
|
|
@ -3,48 +3,37 @@ 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') )
|
||||
e1 = NewFile( FakePyinotify('bullshit.mp3') ).proxify()
|
||||
e2 = MoveFile( FakePyinotify('bullshit.mp3') ).proxify()
|
||||
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') )
|
||||
e1 = NewFile( FakePyinotify('bullshit.mp3') ).proxify()
|
||||
e2 = MoveFile( FakePyinotify('bullshit.mp3') ).proxify()
|
||||
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') )
|
||||
e1 = NewFile( FakePyinotify('bullshit.mp3') ).proxify()
|
||||
e2 = DeleteFile( FakePyinotify('bullshit.mp3') ).proxify()
|
||||
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) )
|
||||
|
||||
self.assertEqual( len(ev.store.keys()), 1 )
|
||||
|
||||
delete_ev = e1.safe_pack()[0]
|
||||
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(e2) )
|
||||
|
||||
|
@ -58,11 +47,12 @@ class TestMMP(unittest.TestCase):
|
|||
DeleteFile( FakePyinotify(p) ),
|
||||
NewFile( FakePyinotify(p) ),
|
||||
NewFile( FakePyinotify(p) ), ]
|
||||
events = map(lambda x: x.proxify(), events)
|
||||
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 ]
|
||||
#packed = [ x.safe_pack() for x in actual_events ]
|
||||
|
||||
if __name__ == '__main__': unittest.main()
|
||||
|
|
Loading…
Reference in New Issue