Merge branch 'cc-3936-3' into devel
This commit is contained in:
commit
1a13731216
|
@ -31,6 +31,7 @@ define('MDATA_KEY_TRACKNUMBER', 'track_number');
|
|||
define('MDATA_KEY_CONDUCTOR', 'conductor');
|
||||
define('MDATA_KEY_LANGUAGE', 'language');
|
||||
define('MDATA_KEY_REPLAYGAIN', 'replay_gain');
|
||||
define('MDATA_KEY_OWNER_ID', 'owner_id');
|
||||
|
||||
define('UI_MDATA_VALUE_FORMAT_FILE', 'File');
|
||||
define('UI_MDATA_VALUE_FORMAT_STREAM', 'live stream');
|
||||
|
|
|
@ -122,9 +122,14 @@ class Application_Model_StoredFile
|
|||
$p_md["MDATA_KEY_YEAR"] = $year;
|
||||
}
|
||||
|
||||
# Translate metadata attributes from media monitor (MDATA_KEY_*)
|
||||
# to their counterparts in constants.php (usually the column names)
|
||||
foreach ($p_md as $mdConst => $mdValue) {
|
||||
if (defined($mdConst)) {
|
||||
$dbMd[constant($mdConst)] = $mdValue;
|
||||
} else {
|
||||
Logging::info("Warning: using metadata that is not defined.
|
||||
[$mdConst] => [$mdValue]");
|
||||
}
|
||||
}
|
||||
$this->setDbColMetadata($dbMd);
|
||||
|
@ -150,10 +155,9 @@ class Application_Model_StoredFile
|
|||
if(!$owner) { // no owner detected, we try to assign one.
|
||||
// if MDATA_OWNER_ID is not set then we default to the
|
||||
// first admin user we find
|
||||
if (!array_key_exists('MDATA_OWNER_ID', $p_md)) {
|
||||
if (!array_key_exists('MDATA_KEY_OWNER_ID', $p_md)) {
|
||||
//$admins = Application_Model_User::getUsers(array('A'));
|
||||
$admins = Application_Model_User::getUsersOfType('A');
|
||||
//$admins = array();
|
||||
if (count($admins) > 0) { // found admin => pick first one
|
||||
$owner = $admins[0];
|
||||
}
|
||||
|
@ -183,7 +187,6 @@ class Application_Model_StoredFile
|
|||
if (isset($this->_dbMD[$dbColumn])) {
|
||||
$propelColumn = $this->_dbMD[$dbColumn];
|
||||
$method = "set$propelColumn";
|
||||
Logging::info($method);
|
||||
$this->_file->$method($mdValue);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,6 @@ 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"
|
||||
|
|
|
@ -1,17 +1,24 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import abc
|
||||
import media.monitor.pure as mmp
|
||||
import media.monitor.pure as mmp
|
||||
import media.monitor.owners as owners
|
||||
from media.monitor.pure import LazyProperty
|
||||
from media.monitor.metadata import Metadata
|
||||
from media.monitor.log import Loggable
|
||||
from media.monitor.exceptions import BadSongFile
|
||||
|
||||
class PathChannel(object):
|
||||
"""
|
||||
Simple struct to hold a 'signal' string and a related 'path'. Basically
|
||||
used as a named tuple
|
||||
"""
|
||||
def __init__(self, signal, path):
|
||||
self.signal = signal
|
||||
self.path = path
|
||||
|
||||
# TODO : Move this to it's file. Also possible unsingleton and use it as a
|
||||
# simple module just like m.m.owners
|
||||
class EventRegistry(object):
|
||||
"""
|
||||
This class's main use is to keep track all events with a cookie attribute.
|
||||
|
@ -56,10 +63,16 @@ class BaseEvent(Loggable):
|
|||
self._raw_event = raw_event
|
||||
self.path = os.path.normpath(raw_event.pathname)
|
||||
else: self.path = raw_event
|
||||
self.owner = owners.get_owner(self.path)
|
||||
self._pack_hook = lambda: None # no op
|
||||
# into another event
|
||||
|
||||
def reset_hook(self):
|
||||
"""
|
||||
Resets the hook that is called after an event is packed. Before
|
||||
resetting the hook we execute it to make sure that whatever cleanup
|
||||
operations were queued are executed.
|
||||
"""
|
||||
self._pack_hook()
|
||||
self._pack_hook = lambda: None
|
||||
|
||||
|
@ -85,11 +98,12 @@ class BaseEvent(Loggable):
|
|||
events that must catch their own BadSongFile exceptions since generate
|
||||
a set of exceptions instead of a single one
|
||||
"""
|
||||
# pack will only throw an exception if it processes one file but this
|
||||
# is a little bit hacky
|
||||
try:
|
||||
self._pack_hook()
|
||||
ret = self.pack()
|
||||
# Remove owner of this file only after packing. Otherwise packing
|
||||
# will not serialize the owner correctly into the airtime request
|
||||
owners.remove_file_owner(self.path)
|
||||
return ret
|
||||
except BadSongFile as e: return [e]
|
||||
|
||||
|
@ -100,8 +114,18 @@ class BaseEvent(Loggable):
|
|||
self.path = evt.path
|
||||
self.__class__ = evt.__class__
|
||||
# We don't transfer the _pack_hook over to the new event
|
||||
# TODO : perhaps we should call the old events pack_hook just to make
|
||||
# sure everything is done cleanly?
|
||||
return self
|
||||
|
||||
def assign_owner(self,req):
|
||||
"""
|
||||
Packs self.owner to req if the owner is valid. I.e. it's not -1. This
|
||||
method is used by various events that would like to pass owner as a
|
||||
parameter. NewFile for example.
|
||||
"""
|
||||
if self.owner != -1: req['MDATA_KEY_OWNER_ID'] = self.owner
|
||||
|
||||
class FakePyinotify(object):
|
||||
"""
|
||||
sometimes we must create our own pyinotify like objects to
|
||||
|
@ -111,12 +135,20 @@ class FakePyinotify(object):
|
|||
def __init__(self, path): self.pathname = path
|
||||
|
||||
class OrganizeFile(BaseEvent, HasMetaData):
|
||||
"""
|
||||
The only kind of event that does support the pack protocol. It's used
|
||||
internally with mediamonitor to move files in the organize directory.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(OrganizeFile, self).__init__(*args, **kwargs)
|
||||
def pack(self):
|
||||
raise AttributeError("You can't send organize events to airtime!!!")
|
||||
|
||||
class NewFile(BaseEvent, HasMetaData):
|
||||
"""
|
||||
NewFile events are the only events that contain MDATA_KEY_OWNER_ID metadata
|
||||
in them.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NewFile, self).__init__(*args, **kwargs)
|
||||
def pack(self):
|
||||
|
@ -125,10 +157,16 @@ class NewFile(BaseEvent, HasMetaData):
|
|||
"""
|
||||
req_dict = self.metadata.extract()
|
||||
req_dict['mode'] = u'create'
|
||||
self.assign_owner(req_dict)
|
||||
req_dict['MDATA_KEY_FILEPATH'] = unicode( self.path )
|
||||
return [req_dict]
|
||||
|
||||
class DeleteFile(BaseEvent):
|
||||
"""
|
||||
DeleteFile event only contains the path to be deleted. No other metadata
|
||||
can be or is included. (This is because this event is fired after the
|
||||
deletion occurs).
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DeleteFile, self).__init__(*args, **kwargs)
|
||||
def pack(self):
|
||||
|
@ -161,6 +199,10 @@ class ModifyFile(BaseEvent, HasMetaData):
|
|||
return [req_dict]
|
||||
|
||||
def map_events(directory, constructor):
|
||||
"""
|
||||
Walks 'directory' and creates an event using 'constructor'. Returns a list
|
||||
of the constructed events.
|
||||
"""
|
||||
# -unknown-path should not appear in the path here but more testing
|
||||
# might be necessary
|
||||
for f in mmp.walk_supported(directory, clean_empties=False):
|
||||
|
@ -169,18 +211,30 @@ def map_events(directory, constructor):
|
|||
except BadSongFile as e: yield e
|
||||
|
||||
class DeleteDir(BaseEvent):
|
||||
"""
|
||||
A DeleteDir event unfolds itself into a list of DeleteFile events for every
|
||||
file in the directory.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DeleteDir, self).__init__(*args, **kwargs)
|
||||
def pack(self):
|
||||
return map_events( self.path, DeleteFile )
|
||||
|
||||
class MoveDir(BaseEvent):
|
||||
"""
|
||||
A MoveDir event unfolds itself into a list of MoveFile events for every
|
||||
file in the directory.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MoveDir, self).__init__(*args, **kwargs)
|
||||
def pack(self):
|
||||
return map_events( self.path, MoveFile )
|
||||
|
||||
class DeleteDirWatch(BaseEvent):
|
||||
"""
|
||||
Deleting a watched directory is different from deleting any other
|
||||
directory. Hence we must have a separate event to handle this case
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DeleteDirWatch, self).__init__(*args, **kwargs)
|
||||
def pack(self):
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import media.monitor.pure as mmp
|
||||
import media.monitor.pure as mmp
|
||||
import media.monitor.owners as owners
|
||||
from media.monitor.handler import ReportHandler
|
||||
from media.monitor.log import Loggable
|
||||
from media.monitor.exceptions import BadSongFile
|
||||
from media.monitor.events import OrganizeFile
|
||||
|
||||
class Organizer(ReportHandler,Loggable):
|
||||
"""
|
||||
|
@ -38,6 +40,8 @@ class Organizer(ReportHandler,Loggable):
|
|||
directory and place it in the correct path (starting with
|
||||
self.target_path)
|
||||
"""
|
||||
# Only handle this event type
|
||||
assert isinstance(event, OrganizeFile) == True
|
||||
try:
|
||||
# We must select the target_path based on whether file was recorded
|
||||
# by airtime or not.
|
||||
|
@ -47,6 +51,7 @@ class Organizer(ReportHandler,Loggable):
|
|||
new_path = mmp.organized_path(event.path, target_path,
|
||||
event.metadata.extract())
|
||||
mmp.magic_move(event.path, new_path)
|
||||
owners.add_file_owner(new_path, mmp.owner_id(event.path) )
|
||||
self.logger.info('Organized: "%s" into "%s"' %
|
||||
(event.path, new_path))
|
||||
except BadSongFile as e:
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from media.monitor.log import get_logger
|
||||
log = get_logger()
|
||||
# hash: 'filepath' => owner_id
|
||||
owners = {}
|
||||
|
||||
def reset_owners():
|
||||
"""
|
||||
Wipes out all file => owner associations
|
||||
"""
|
||||
global owners
|
||||
owners = {}
|
||||
|
||||
|
||||
def get_owner(f):
|
||||
"""
|
||||
Get the owner id of the file 'f'
|
||||
"""
|
||||
return owners[f] if f in owners else -1
|
||||
|
||||
|
||||
def add_file_owner(f,owner):
|
||||
"""
|
||||
Associate file f with owner. If owner is -1 then do we will not record it
|
||||
because -1 means there is no owner. Returns True if f is being stored after
|
||||
the function. False otherwise.
|
||||
"""
|
||||
if owner == -1: return False
|
||||
if f in owners:
|
||||
if owner != owners[f]: # check for fishiness
|
||||
log.info("Warning ownership of file '%s' changed from '%d' to '%d'"
|
||||
% (f, owners[f], owner))
|
||||
else: return True
|
||||
owners[f] = owner
|
||||
return True
|
||||
|
||||
def has_owner(f):
|
||||
"""
|
||||
True if f is owned by somebody. False otherwise.
|
||||
"""
|
||||
return f in owners
|
||||
|
||||
def remove_file_owner(f):
|
||||
"""
|
||||
Try and delete any association made with file f. Returns true if the the
|
||||
association was actually deleted. False otherwise.
|
||||
"""
|
||||
if f in owners:
|
||||
del owners[f]
|
||||
return True
|
||||
else: return False
|
||||
|
|
@ -418,6 +418,26 @@ def sub_path(directory,f):
|
|||
common = os.path.commonprefix([ normalized, normpath(f) ])
|
||||
return common == normalized
|
||||
|
||||
def owner_id(original_path):
|
||||
"""
|
||||
Given 'original_path' return the file name of the of 'identifier' file.
|
||||
return the id that is contained in it. If no file is found or nothing is
|
||||
read then -1 is returned. File is deleted after the number has been read
|
||||
"""
|
||||
fname = "%s.identifier" % original_path
|
||||
owner_id = -1
|
||||
try:
|
||||
f = open(fname)
|
||||
for line in f:
|
||||
owner_id = int(line)
|
||||
break
|
||||
f.close()
|
||||
except Exception: pass
|
||||
else:
|
||||
try: os.unlink(fname)
|
||||
except Exception: raise
|
||||
return owner_id
|
||||
|
||||
if __name__ == '__main__':
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import unittest
|
||||
import media.monitor.owners as owners
|
||||
|
||||
class TestMMP(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.f = "test.mp3"
|
||||
|
||||
def test_has_owner(self):
|
||||
owners.reset_owners()
|
||||
o = 12345
|
||||
self.assertTrue( owners.add_file_owner(self.f,o) )
|
||||
self.assertTrue( owners.has_owner(self.f) )
|
||||
|
||||
def test_add_file_owner(self):
|
||||
owners.reset_owners()
|
||||
self.assertFalse( owners.add_file_owner('testing', -1) )
|
||||
self.assertTrue( owners.add_file_owner(self.f, 123) )
|
||||
self.assertTrue( owners.add_file_owner(self.f, 123) )
|
||||
self.assertTrue( owners.add_file_owner(self.f, 456) )
|
||||
|
||||
def test_remove_file_owner(self):
|
||||
owners.reset_owners()
|
||||
self.assertTrue( owners.add_file_owner(self.f, 123) )
|
||||
self.assertTrue( owners.remove_file_owner(self.f) )
|
||||
self.assertFalse( owners.remove_file_owner(self.f) )
|
||||
|
||||
def test_get_owner(self):
|
||||
owners.reset_owners()
|
||||
self.assertTrue( owners.add_file_owner(self.f, 123) )
|
||||
self.assertEqual( owners.get_owner(self.f), 123, "file is owned" )
|
||||
self.assertEqual( owners.get_owner("random_stuff.txt"), -1,
|
||||
"file is not owned" )
|
||||
|
||||
if __name__ == '__main__': unittest.main()
|
||||
|
|
@ -82,4 +82,16 @@ class TestMMP(unittest.TestCase):
|
|||
self.assertEqual( mmp.parse_int("123asf"), "123" )
|
||||
self.assertEqual( mmp.parse_int("asdf"), None )
|
||||
|
||||
def test_owner_id(self):
|
||||
start_path = "testing.mp3"
|
||||
id_path = "testing.mp3.identifier"
|
||||
o_id = 123
|
||||
f = open(id_path, 'w')
|
||||
f.write("123")
|
||||
f.close()
|
||||
possible_id = mmp.owner_id(start_path)
|
||||
self.assertFalse( os.path.exists(id_path) )
|
||||
self.assertEqual( possible_id, o_id )
|
||||
self.assertEqual( -1, mmp.owner_id("something.random") )
|
||||
|
||||
if __name__ == '__main__': unittest.main()
|
||||
|
|
Loading…
Reference in New Issue