Merge branch 'cc-3936-3' into devel

This commit is contained in:
Rudi Grinberg 2012-08-27 14:46:31 -04:00
commit 1a13731216
9 changed files with 190 additions and 8 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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