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.
*
@ -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'";

View File

@ -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>

View File

@ -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">

View File

@ -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) {

View File

@ -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>" +

View File

@ -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

View File

@ -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):
"""

View File

@ -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)

View File

@ -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)

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
# 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

View File

@ -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?

View File

@ -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()