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

Conflicts:
	python_apps/media-monitor/airtimefilemonitor/airtimemediamonitorbootstrap.py
	python_apps/media-monitor/airtimefilemonitor/mediamonitorcommon.py
This commit is contained in:
Martin Konecny 2012-07-05 21:57:12 -04:00
commit 70a0dce26e
9 changed files with 117 additions and 64 deletions

View File

@ -573,8 +573,9 @@ class ApiController extends Zend_Controller_Action
Application_Model_Preference::SetImportTimestamp(); Application_Model_Preference::SetImportTimestamp();
if ($mode == "create") { if ($mode == "create") {
$filepath = $md['MDATA_KEY_FILEPATH']; $filepath = $md['MDATA_KEY_FILEPATH'];
$filepath = str_replace("\\", "", $filepath); //$filepath = str_replace("\\", "", $filepath);
$filepath = str_replace("//", "/", $filepath); //$filepath = str_replace("//", "/", $filepath);
$filepath = Application_Common_OsPath::normpath($filepath);
$file = Application_Model_StoredFile::RecallByFilepath($filepath); $file = Application_Model_StoredFile::RecallByFilepath($filepath);
if (is_null($file)) { if (is_null($file)) {
@ -595,7 +596,7 @@ class ApiController extends Zend_Controller_Action
} }
else if ($mode == "modify") { else if ($mode == "modify") {
$filepath = $md['MDATA_KEY_FILEPATH']; $filepath = $md['MDATA_KEY_FILEPATH'];
$filepath = str_replace("\\", "", $filepath); //$filepath = str_replace("\\", "", $filepath);
$file = Application_Model_StoredFile::RecallByFilepath($filepath); $file = Application_Model_StoredFile::RecallByFilepath($filepath);
//File is not in database anymore. //File is not in database anymore.
@ -618,13 +619,13 @@ class ApiController extends Zend_Controller_Action
} }
else { else {
$filepath = $md['MDATA_KEY_FILEPATH']; $filepath = $md['MDATA_KEY_FILEPATH'];
$filepath = str_replace("\\", "", $filepath); //$filepath = str_replace("\\", "", $filepath);
$file->setFilePath($filepath); $file->setFilePath($filepath);
} }
} }
else if ($mode == "delete") { else if ($mode == "delete") {
$filepath = $md['MDATA_KEY_FILEPATH']; $filepath = $md['MDATA_KEY_FILEPATH'];
$filepath = str_replace("\\", "", $filepath); //$filepath = str_replace("\\", "", $filepath);
$file = Application_Model_StoredFile::RecallByFilepath($filepath); $file = Application_Model_StoredFile::RecallByFilepath($filepath);
if (is_null($file)) { if (is_null($file)) {
@ -637,7 +638,7 @@ class ApiController extends Zend_Controller_Action
} }
else if ($mode == "delete_dir") { else if ($mode == "delete_dir") {
$filepath = $md['MDATA_KEY_FILEPATH']; $filepath = $md['MDATA_KEY_FILEPATH'];
$filepath = str_replace("\\", "", $filepath); //$filepath = str_replace("\\", "", $filepath);
$files = Application_Model_StoredFile::RecallByPartialFilepath($filepath); $files = Application_Model_StoredFile::RecallByPartialFilepath($filepath);
foreach($files as $file){ foreach($files as $file){

View File

@ -40,7 +40,7 @@ class PluploadController extends Zend_Controller_Action
$tempname = $this->_getParam('tempname'); $tempname = $this->_getParam('tempname');
$result = Application_Model_StoredFile::copyFileToStor($upload_dir, $filename, $tempname); $result = Application_Model_StoredFile::copyFileToStor($upload_dir, $filename, $tempname);
if (!is_null($result)) if (!is_null($result))
die('{"jsonrpc" : "2.0", "error" : {"code": '.$result['code'].', "message" : "'.$result['message'].'"}}'); die('{"jsonrpc" : "2.0", "error" : '.json_encode($result).'}');
die('{"jsonrpc" : "2.0"}'); die('{"jsonrpc" : "2.0"}');
} }

View File

@ -88,6 +88,18 @@ var AIRTIME = (function(AIRTIME){
}); });
}; };
mod.dblClickAdd = function(id, type) {
var i,
aMediaIds = [];
//process selected files/playlists.
if (type === "audioclip") {
aMediaIds.push(id);
}
AIRTIME.playlist.fnAddItems(aMediaIds, undefined, 'after');
};
mod.setupLibraryToolbar = function() { mod.setupLibraryToolbar = function() {
var $toolbar = $(".lib-content .fg-toolbar:first"); var $toolbar = $(".lib-content .fg-toolbar:first");

View File

@ -87,6 +87,35 @@ var AIRTIME = (function(AIRTIME){
}); });
}; };
mod.dblClickAdd = function(id, type) {
var i,
length,
temp,
aMediaIds = [],
aSchedIds = [],
aData = [];
//process selected files/playlists.
aMediaIds.push({"id": id, "type": type});
$("#show_builder_table tr.cursor-selected-row").each(function(i, el){
aData.push($(el).prev().data("aData"));
});
//process selected schedule rows to add media after.
for (i=0, length = aData.length; i < length; i++) {
temp = aData[i];
aSchedIds.push({"id": temp.id, "instance": temp.instance, "timestamp": temp.timestamp});
}
if(aSchedIds.length == 0){
alert("Please select a cursor position on timeline.");
return false;
}
AIRTIME.showbuilder.fnAdd(aMediaIds, aSchedIds);
};
mod.setupLibraryToolbar = function() { mod.setupLibraryToolbar = function() {
var $toolbar = $(".lib-content .fg-toolbar:first"); var $toolbar = $(".lib-content .fg-toolbar:first");

View File

@ -428,11 +428,32 @@ var AIRTIME = (function(AIRTIME) {
return false; return false;
}); });
alreadyclicked=false;
//call the context menu so we can prevent the event from propagating. //call the context menu so we can prevent the event from propagating.
$(nRow).find('td:not(.library_checkbox, .library_type)').click(function(e){ $(nRow).find('td:not(.library_checkbox, .library_type)').click(function(e){
var el=$(this);
if (alreadyclicked)
{
alreadyclicked=false; // reset
clearTimeout(alreadyclickedTimeout); // prevent this from happening
// do what needs to happen on double click.
$(this).contextMenu({x: e.pageX, y: e.pageY}); $tr = $(el).parent();
data = $tr.data("aData");
AIRTIME.library.dblClickAdd(data.id, data.ftype);
//AIRTIME.playlist.fnAddItems([data.id], undefined, 'after');
}
else
{
alreadyclicked=true;
alreadyclickedTimeout=setTimeout(function(){
alreadyclicked=false; // reset when it happens
// do what needs to happen on single click.
// use el instead of $(this) because $(this) is
// no longer the element
el.contextMenu({x: e.pageX, y: e.pageY});
},300); // <-- dblclick tolerance here
}
return false; return false;
}); });

View File

@ -74,10 +74,8 @@ class AirtimeMediaMonitorBootstrap():
dir -- pathname of the directory dir -- pathname of the directory
""" """
def sync_database_to_filesystem(self, dir_id, dir): def sync_database_to_filesystem(self, dir_id, dir):
# TODO: is this line even necessary?
dir = os.path.normpath(dir) + "/" dir = os.path.normpath(dir)+"/"
""" """
set to hold new and/or modified files. We use a set to make it ok if files are added set to hold new and/or modified files. We use a set to make it ok if files are added
twice. This is because some of the tests for new files return result sets that are not twice. This is because some of the tests for new files return result sets that are not
@ -91,24 +89,23 @@ class AirtimeMediaMonitorBootstrap():
for file in files['files']: for file in files['files']:
db_known_files_set.add(file) db_known_files_set.add(file)
all_files = self.mmc.scan_dir_for_new_files(dir) all_files = self.mmc.clean_dirty_file_paths( self.mmc.scan_dir_for_new_files(dir) )
all_files_set = set() all_files_set = set()
for file_path in all_files: for file_path in all_files:
file_path = file_path.strip(" \n") if self.config.problem_directory not in file_path:
if len(file_path) > 0 and self.config.problem_directory not in file_path:
all_files_set.add(file_path[len(dir):]) all_files_set.add(file_path[len(dir):])
# if dir doesn't exists, update db # if dir doesn't exists, update db
if not os.path.exists(dir): if not os.path.exists(dir):
self.pe.handle_watched_dir_missing(dir) self.pe.handle_stdout_files(dir)
if os.path.exists(self.mmc.timestamp_file): if os.path.exists(self.mmc.timestamp_file):
"""find files that have been modified since the last time media-monitor process started.""" """find files that have been modified since the last time media-monitor process started."""
time_diff_sec = time.time() - os.path.getmtime(self.mmc.timestamp_file) time_diff_sec = time.time() - os.path.getmtime(self.mmc.timestamp_file)
command = "find '%s' -iname '*.ogg' -o -iname '*.mp3' -type f -readable -mmin -%d" % (dir, time_diff_sec / 60 + 1) command = self.mmc.find_command(directory=dir, extra_arguments=("-type f -readable -mmin -%d" % (time_diff_sec/60+1)))
else: else:
command = "find '%s' -iname '*.ogg' -o -iname '*.mp3' -type f -readable" % dir command = self.mmc.find_command(directory=dir, extra_arguments="-type f -readable")
self.logger.debug(command) self.logger.debug(command)
stdout = self.mmc.exec_command(command) stdout = self.mmc.exec_command(command)
@ -117,12 +114,11 @@ class AirtimeMediaMonitorBootstrap():
self.logger.error("Unrecoverable error when syncing db to filesystem.") self.logger.error("Unrecoverable error when syncing db to filesystem.")
return return
new_files = stdout.splitlines() new_files = self.mmc.clean_dirty_file_paths(stdout.splitlines())
new_and_modified_files = set() new_and_modified_files = set()
for file_path in new_files: for file_path in new_files:
file_path = file_path.strip(" \n") if self.config.problem_directory not in file_path:
if len(file_path) > 0 and self.config.problem_directory not in file_path:
new_and_modified_files.add(file_path[len(dir):]) new_and_modified_files.add(file_path[len(dir):])
""" """
@ -153,16 +149,12 @@ class AirtimeMediaMonitorBootstrap():
self.logger.debug(full_file_path) self.logger.debug(full_file_path)
self.pe.handle_removed_file(False, full_file_path) self.pe.handle_removed_file(False, full_file_path)
for file_path in new_files_set:
self.logger.debug("new file")
full_file_path = os.path.join(dir, file_path)
self.logger.debug(full_file_path)
if os.path.exists(full_file_path):
self.pe.handle_created_file(False, full_file_path, os.path.basename(full_file_path))
for file_path in modified_files_set: for file_set, debug_message, handle_attribute in [(new_files_set, "new file", "handle_created_file"),
self.logger.debug("modified file") (modified_files_set, "modified file", "handle_modified_file")]:
full_file_path = "%s%s" % (dir, file_path) for file_path in file_set:
self.logger.debug(full_file_path) self.logger.debug(debug_message)
if os.path.exists(full_file_path): full_file_path = os.path.join(dir, file_path)
self.pe.handle_modified_file(False, full_file_path, os.path.basename(full_file_path)) self.logger.debug(full_file_path)
if os.path.exists(full_file_path):
getattr(self.pe,handle_attribute)(False,full_file_path, os.path.basename(full_file_path))

View File

@ -99,16 +99,12 @@ class AirtimeNotifier(Notifier):
self.bootstrap.sync_database_to_filesystem(new_storage_directory_id, new_storage_directory) self.bootstrap.sync_database_to_filesystem(new_storage_directory_id, new_storage_directory)
self.config.storage_directory = os.path.normpath(new_storage_directory) self.config.storage_directory = os.path.normpath(new_storage_directory)
self.config.imported_directory = os.path.normpath(new_storage_directory + '/imported') self.config.imported_directory = os.path.normpath(os.path.join(new_storage_directory, '/imported'))
self.config.organize_directory = os.path.normpath(new_storage_directory + '/organize') self.config.organize_directory = os.path.normpath(os.path.join(new_storage_directory, '/organize'))
self.mmc.ensure_is_dir(self.config.storage_directory) for directory in [self.config.storage_directory, self.config.imported_directory, self.config.organize_directory]:
self.mmc.ensure_is_dir(self.config.imported_directory) self.mmc.ensure_is_dir(directory)
self.mmc.ensure_is_dir(self.config.organize_directory) self.mmc.is_readable(directory, True)
self.mmc.is_readable(self.config.storage_directory, True)
self.mmc.is_readable(self.config.imported_directory, True)
self.mmc.is_readable(self.config.organize_directory, True)
self.watch_directory(new_storage_directory) self.watch_directory(new_storage_directory)
elif m['event_type'] == "file_delete": elif m['event_type'] == "file_delete":
@ -144,7 +140,7 @@ class AirtimeNotifier(Notifier):
mode = event['mode'] mode = event['mode']
md = {} md = {}
md['MDATA_KEY_FILEPATH'] = filepath md['MDATA_KEY_FILEPATH'] = os.path.normpath(filepath)
if 'data' in event: if 'data' in event:
file_md = event['data'] file_md = event['data']

View File

@ -15,14 +15,26 @@ import pyinotify
class MediaMonitorCommon: class MediaMonitorCommon:
timestamp_file = "/var/tmp/airtime/media-monitor/last_index" timestamp_file = "/var/tmp/airtime/media-monitor/last_index"
supported_file_formats = ['mp3', 'ogg']
def __init__(self, airtime_config, wm=None): def __init__(self, airtime_config, wm=None):
self.supported_file_formats = ['mp3', 'ogg']
self.logger = logging.getLogger() self.logger = logging.getLogger()
self.config = airtime_config self.config = airtime_config
self.md_manager = AirtimeMetadata() self.md_manager = AirtimeMetadata()
self.wm = wm self.wm = wm
def clean_dirty_file_paths(self, dirty_files):
""" clean dirty file paths by removing blanks and removing trailing/leading whitespace"""
return filter(lambda e: len(e) > 0, [ f.strip(" \n") for f in dirty_files ])
def find_command(self, directory, extra_arguments=""):
""" Builds a find command that respects supported_file_formats list
Note: Use single quotes to quote arguments """
ext_globs = [ "-iname '*.%s'" % ext for ext in self.supported_file_formats ]
find_glob = ' -o '.join(ext_globs)
return "find '%s' %s %s" % (directory, find_glob, extra_arguments)
def is_parent_directory(self, filepath, directory): def is_parent_directory(self, filepath, directory):
filepath = os.path.normpath(filepath) filepath = os.path.normpath(filepath)
directory = os.path.normpath(directory) directory = os.path.normpath(directory)
@ -30,7 +42,6 @@ class MediaMonitorCommon:
def is_temp_file(self, filename): def is_temp_file(self, filename):
info = filename.split(".") info = filename.split(".")
# if file doesn't have any extension, info[-2] throws exception # if file doesn't have any extension, info[-2] throws exception
# Hence, checking length of info before we do anything # Hence, checking length of info before we do anything
if(len(info) >= 2): if(len(info) >= 2):
@ -40,20 +51,19 @@ class MediaMonitorCommon:
def is_audio_file(self, filename): def is_audio_file(self, filename):
info = filename.split(".") info = filename.split(".")
if len(info) < 2: return false # handle cases like filename="mp3"
return info[-1].lower() in self.supported_file_formats return info[-1].lower() in self.supported_file_formats
#check if file is readable by "nobody" #check if file is readable by "nobody"
def is_user_readable(self, filepath, euid='nobody', egid='nogroup'): def is_user_readable(self, filepath, euid='nobody', egid='nogroup'):
f = None
try: try:
uid = pwd.getpwnam(euid)[2] uid = pwd.getpwnam(euid)[2]
gid = grp.getgrnam(egid)[2] gid = grp.getgrnam(egid)[2]
#drop root permissions and become "nobody" #drop root permissions and become "nobody"
os.setegid(gid) os.setegid(gid)
os.seteuid(uid) os.seteuid(uid)
f = open(filepath)
open(filepath)
readable = True readable = True
except IOError: except IOError:
self.logger.warn("File does not have correct permissions: '%s'", filepath) self.logger.warn("File does not have correct permissions: '%s'", filepath)
@ -64,17 +74,16 @@ class MediaMonitorCommon:
self.logger.error("traceback: %s", traceback.format_exc()) self.logger.error("traceback: %s", traceback.format_exc())
finally: finally:
#reset effective user to root #reset effective user to root
if f: f.close()
os.seteuid(0) os.seteuid(0)
os.setegid(0) os.setegid(0)
return readable return readable
# the function only changes the permission if its not readable by www-data # the function only changes the permission if its not readable by www-data
def is_readable(self, item, is_dir): def is_readable(self, item, is_dir):
try: try:
return self.is_user_readable(item, 'www-data', 'www-data') \ return self.is_user_readable(item, 'www-data', 'www-data')
and self.is_user_readable(item, 'pypo', 'pypo') except Exception, e:
except Exception:
self.logger.warn(u"Failed to check owner/group/permissions for %s", item) self.logger.warn(u"Failed to check owner/group/permissions for %s", item)
return False return False
@ -241,7 +250,7 @@ class MediaMonitorCommon:
show_name = '-'.join(title[3:]) show_name = '-'.join(title[3:])
new_md = {} new_md = {}
new_md["MDATA_KEY_FILEPATH"] = original_path new_md['MDATA_KEY_FILEPATH'] = os.path.normpath(original_path)
new_md['MDATA_KEY_TITLE'] = '%s-%s-%s:%s:%s' % (show_name, orig_md['MDATA_KEY_YEAR'], show_hour, show_min, show_sec) new_md['MDATA_KEY_TITLE'] = '%s-%s-%s:%s:%s' % (show_name, orig_md['MDATA_KEY_YEAR'], show_hour, show_min, show_sec)
self.md_manager.save_md_to_file(new_md) self.md_manager.save_md_to_file(new_md)
@ -279,7 +288,7 @@ class MediaMonitorCommon:
return stdout return stdout
def scan_dir_for_new_files(self, dir): def scan_dir_for_new_files(self, dir):
command = 'find "%s" -iname "*.ogg" -o -iname "*.mp3" -type f -readable' % dir.replace('"', '\\"') command = self.find_command(directory=dir, extra_arguments="-type f -readable")
self.logger.debug(command) self.logger.debug(command)
stdout = self.exec_command(command) stdout = self.exec_command(command)
@ -315,13 +324,10 @@ class MediaMonitorCommon:
if return_code != 0: if return_code != 0:
#print pathname for py-interpreter.log #print pathname for py-interpreter.log
print pathname print pathname
return (return_code == 0) return (return_code == 0)
def move_to_problem_dir(self, source): def move_to_problem_dir(self, source):
dest = os.path.join(self.config.problem_directory, os.path.basename(source)) dest = os.path.join(self.config.problem_directory, os.path.basename(source))
try: try:
omask = os.umask(0) omask = os.umask(0)
os.rename(source, dest) os.rename(source, dest)

View File

@ -54,14 +54,10 @@ try:
#create log dir #create log dir
create_dir(config['log_dir']) create_dir(config['log_dir'])
os.system("chown -R pypo:pypo "+config["log_dir"])
#copy python files #copy python files
copy_dir("%s/.."%current_script_dir, config["bin_dir"]) copy_dir("%s/.."%current_script_dir, config["bin_dir"])
#set executable permissions on python files
os.system("chown -R pypo:pypo "+config["bin_dir"])
#copy init.d script #copy init.d script
shutil.copy(config["bin_dir"]+"/airtime-media-monitor-init-d", "/etc/init.d/airtime-media-monitor") shutil.copy(config["bin_dir"]+"/airtime-media-monitor-init-d", "/etc/init.d/airtime-media-monitor")