From 1eea96eefc0e5e9416ef8e60108aecd53b230f0c Mon Sep 17 00:00:00 2001 From: Naomi Date: Fri, 13 May 2011 13:27:25 -0400 Subject: [PATCH 01/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention naming convention in fallback order stor/artist/album/track - title.ext stor/artist/album/title.ext stor/artist/album/originalfilename.ext stor/artist/track - title.ext stor/artist/title.ext stor/artist/originalfilename.ext stor/originalfilename.ext --- airtime_mvc/application/models/StoredFile.php | 71 +++++++++++++++++-- 1 file changed, 65 insertions(+), 6 deletions(-) diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index d5d4faf80..32ced891d 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -773,6 +773,31 @@ class StoredFile { return $rows; } + private function ensureDir($dir) + { + if (!is_dir($dir)) { + mkdir($dir, 02775); + chmod($dir, 02775); + } + } + + private function createUniqueFilename($base, $ext) + { + if(file_exists("$base.$ext")) { + $i = 1; + while(true) { + if(file_exists("$base($i).$ext")) { + $i = $i+1; + } + else { + return "$base($i).$ext"; + } + } + } + + return "$base.$ext"; + } + /** * Generate the location to store the file. * It creates the subdirectory if needed. @@ -780,14 +805,48 @@ class StoredFile { private function generateFilePath() { global $CC_CONFIG, $CC_DBC; - $resDir = $CC_CONFIG['storageDir']."/".substr($this->gunid, 0, 3); - if (!is_dir($resDir)) { - mkdir($resDir, 02775); - chmod($resDir, 02775); - } + + $storageDir = $CC_CONFIG['storageDir']; $info = pathinfo($this->name); + $origName = $info['filename']; $fileExt = strtolower($info["extension"]); - return "{$resDir}/{$this->gunid}.{$fileExt}"; + + $this->loadMetadata(); + + $artist = $this->md["dc:creator"]; + $album = $this->md["dc:source"]; + $title = $this->md["dc:title"]; + $track_num = $this->md["ls:track_num"]; + + if(isset($artist) && $artist != "") { + $base = "$storageDir/$artist"; + $this->ensureDir($base); + + if(isset($album) && $album != "") { + $base = "$base/$album"; + $this->ensureDir($base); + } + + if(isset($title) && $title != "") { + if(isset($track_num) && $track_num != "") { + if($track_num < 10 && strlen($track_num) == 1) { + $track_num = "0$track_num"; + } + $base = "$base/$track_num - $title"; + } + else { + $base = "$base/$title"; + } + } + else { + $base = "$base/$origName"; + } + } + else { + $base = "$storageDir/$origName"; + } + + return $this->createUniqueFilename($base, $fileExt); } /** From ea1b254ebd4db8743c6feff49298124a461858d3 Mon Sep 17 00:00:00 2001 From: Naomi Date: Fri, 13 May 2011 18:03:34 -0400 Subject: [PATCH 02/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention searching database by filename to retrieve file. Checking if is supported audio file/temp audio file. --- .../application/controllers/ApiController.php | 7 +-- airtime_mvc/application/models/StoredFile.php | 4 +- python_apps/pytag-fs/MediaMonitor.py | 45 +++++++++++-------- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/airtime_mvc/application/controllers/ApiController.php b/airtime_mvc/application/controllers/ApiController.php index a6f0825b7..23073c351 100644 --- a/airtime_mvc/application/controllers/ApiController.php +++ b/airtime_mvc/application/controllers/ApiController.php @@ -64,7 +64,7 @@ class ApiController extends Zend_Controller_Action $api_key = $this->_getParam('api_key'); $downlaod = $this->_getParam('download'); - + if(!in_array($api_key, $CC_CONFIG["apiKey"])) { header('HTTP/1.0 401 Unauthorized'); @@ -331,8 +331,9 @@ class ApiController extends Zend_Controller_Action } $md = $this->_getParam('md'); - - $file = StoredFile::Recall(null, $md['gunid']); + $filepath = $md['filepath']; + $filepath = str_replace("\\", "", $filepath); + $file = StoredFile::Recall(null, null, null, $filepath); if (PEAR::isError($file) || is_null($file)) { $this->view->response = "File not in Airtime's Database"; return; diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index 32ced891d..1657f2318 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -701,7 +701,7 @@ class StoredFile { * @return StoredFile|Playlist|NULL * Return NULL if the object doesnt exist in the DB. */ - public static function Recall($p_id=null, $p_gunid=null, $p_md5sum=null) + public static function Recall($p_id=null, $p_gunid=null, $p_md5sum=null, $p_filepath=null) { global $CC_DBC; global $CC_CONFIG; @@ -711,6 +711,8 @@ class StoredFile { $cond = "gunid='$p_gunid'"; } elseif (!is_null($p_md5sum)) { $cond = "md5='$p_md5sum'"; + } elseif (!is_null($p_filepath)) { + $cond = "filepath='$p_filepath'"; } else { return null; } diff --git a/python_apps/pytag-fs/MediaMonitor.py b/python_apps/pytag-fs/MediaMonitor.py index 38423fcc9..d63e7c3b5 100644 --- a/python_apps/pytag-fs/MediaMonitor.py +++ b/python_apps/pytag-fs/MediaMonitor.py @@ -70,7 +70,7 @@ class AirtimeNotifier(Notifier): "isrc_number": "isrc",\ "copyright": "copyright",\ } - + schedule_exchange = Exchange("airtime-media-monitor", "direct", durable=True, auto_delete=True) schedule_queue = Queue("media-monitor", exchange=schedule_exchange, key="filesystem") self.connection = BrokerConnection(config["rabbitmq_host"], config["rabbitmq_user"], config["rabbitmq_password"], "/") @@ -86,7 +86,7 @@ class AirtimeNotifier(Notifier): logger = logging.getLogger('root') logger.info("Received md from RabbitMQ: " + body) - m = json.loads(message.body) + m = json.loads(message.body) airtime_file = mutagen.File(m['filepath'], easy=True) del m['filepath'] for key in m.keys() : @@ -123,21 +123,19 @@ class MediaMonitor(ProcessEvent): "copyright": "copyright",\ } + self.supported_file_formats = ['mp3', 'ogg'] self.logger = logging.getLogger('root') - self.temp_files = {} def update_airtime(self, event): self.logger.info("Updating Change to Airtime") - try: + try: f = open(event.pathname, 'rb') m = hashlib.md5() m.update(f.read()) - md5 = m.hexdigest() - gunid = event.name.split('.')[0] - md = {'gunid':gunid, 'md5':md5} + md = {'filepath':event.pathname, 'md5':md5} file_info = mutagen.File(event.pathname, easy=True) attrs = self.mutagen2airtime @@ -152,12 +150,28 @@ class MediaMonitor(ProcessEvent): except Exception, e: self.logger.info("%s", e) + def is_temp_file(self, filename): + info = filename.split(".") + + if(info[-2] in self.supported_file_formats): + return True + else : + return False + + def is_audio_file(self, filename): + info = filename.split(".") + + if(info[-1] in self.supported_file_formats): + return True + else : + return False + + def process_IN_CREATE(self, event): if not event.dir : - filename_info = event.name.split(".") #file created is a tmp file which will be modified and then moved back to the original filename. - if len(filename_info) > 2 : + if self.is_temp_file(event.name) : self.temp_files[event.pathname] = None #This is a newly imported file. else : @@ -165,18 +179,13 @@ class MediaMonitor(ProcessEvent): self.logger.info("%s: %s", event.maskname, event.pathname) - #event.path : /srv/airtime/stor/bd2 - #event.name : bd2aa73b58d9c8abcced989621846e99.mp3 - #event.pathname : /srv/airtime/stor/bd2/bd2aa73b58d9c8abcced989621846e99.mp3 def process_IN_MODIFY(self, event): if not event.dir : - filename_info = event.name.split(".") - #file modified is not a tmp file. - if len(filename_info) == 2 : - self.update_airtime(event) + if self.is_audio_file(event.name) : + self.update_airtime(event) - self.logger.info("%s: path: %s name: %s", event.maskname, event.path, event.name) + self.logger.info("%s: %s", event.maskname, event.pathname) def process_IN_MOVED_FROM(self, event): if event.pathname in self.temp_files : @@ -189,7 +198,7 @@ class MediaMonitor(ProcessEvent): if event.cookie in self.temp_files : del self.temp_files[event.cookie] self.update_airtime(event) - + self.logger.info("%s: %s", event.maskname, event.pathname) def process_default(self, event): From efc8d0664cea0f85b8902a1b4cd61218cdc61387 Mon Sep 17 00:00:00 2001 From: Naomi Date: Fri, 13 May 2011 13:27:25 -0400 Subject: [PATCH 03/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention naming convention in fallback order stor/artist/album/track - title.ext stor/artist/album/title.ext stor/artist/album/originalfilename.ext stor/artist/track - title.ext stor/artist/title.ext stor/artist/originalfilename.ext stor/originalfilename.ext --- airtime_mvc/application/models/StoredFile.php | 71 +++++++++++++++++-- 1 file changed, 65 insertions(+), 6 deletions(-) diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index 370b27cdb..ba152f49e 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -782,6 +782,31 @@ class StoredFile { return $rows; } + private function ensureDir($dir) + { + if (!is_dir($dir)) { + mkdir($dir, 02775); + chmod($dir, 02775); + } + } + + private function createUniqueFilename($base, $ext) + { + if(file_exists("$base.$ext")) { + $i = 1; + while(true) { + if(file_exists("$base($i).$ext")) { + $i = $i+1; + } + else { + return "$base($i).$ext"; + } + } + } + + return "$base.$ext"; + } + /** * Generate the location to store the file. * It creates the subdirectory if needed. @@ -789,14 +814,48 @@ class StoredFile { private function generateFilePath() { global $CC_CONFIG, $CC_DBC; - $resDir = $CC_CONFIG['storageDir']."/".substr($this->gunid, 0, 3); - if (!is_dir($resDir)) { - mkdir($resDir, 02775); - chmod($resDir, 02775); - } + + $storageDir = $CC_CONFIG['storageDir']; $info = pathinfo($this->name); + $origName = $info['filename']; $fileExt = strtolower($info["extension"]); - return "{$resDir}/{$this->gunid}.{$fileExt}"; + + $this->loadMetadata(); + + $artist = $this->md["dc:creator"]; + $album = $this->md["dc:source"]; + $title = $this->md["dc:title"]; + $track_num = $this->md["ls:track_num"]; + + if(isset($artist) && $artist != "") { + $base = "$storageDir/$artist"; + $this->ensureDir($base); + + if(isset($album) && $album != "") { + $base = "$base/$album"; + $this->ensureDir($base); + } + + if(isset($title) && $title != "") { + if(isset($track_num) && $track_num != "") { + if($track_num < 10 && strlen($track_num) == 1) { + $track_num = "0$track_num"; + } + $base = "$base/$track_num - $title"; + } + else { + $base = "$base/$title"; + } + } + else { + $base = "$base/$origName"; + } + } + else { + $base = "$storageDir/$origName"; + } + + return $this->createUniqueFilename($base, $fileExt); } /** From 6dfad55b89489dfaeab178e45818c93b5cc36a14 Mon Sep 17 00:00:00 2001 From: Naomi Date: Fri, 13 May 2011 18:03:34 -0400 Subject: [PATCH 04/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention searching database by filename to retrieve file. Checking if is supported audio file/temp audio file. --- .../application/controllers/ApiController.php | 13 +++--- airtime_mvc/application/models/StoredFile.php | 4 +- python_apps/media-monitor/MediaMonitor.py | 45 +++++++++++-------- 3 files changed, 37 insertions(+), 25 deletions(-) diff --git a/airtime_mvc/application/controllers/ApiController.php b/airtime_mvc/application/controllers/ApiController.php index dce293b0d..3441668da 100644 --- a/airtime_mvc/application/controllers/ApiController.php +++ b/airtime_mvc/application/controllers/ApiController.php @@ -63,7 +63,7 @@ class ApiController extends Zend_Controller_Action $this->_helper->viewRenderer->setNoRender(true); $api_key = $this->_getParam('api_key'); - $download = $this->_getParam('download'); + $downlaod = $this->_getParam('download'); if(!in_array($api_key, $CC_CONFIG["apiKey"])) { @@ -100,15 +100,15 @@ class ApiController extends Zend_Controller_Action // !! binary mode !! $fp = fopen($filepath, 'rb'); - + //We can have multiple levels of output buffering. Need to //keep looping until all have been disabled!!! //http://www.php.net/manual/en/function.ob-end-flush.php while (@ob_end_flush()); - + fpassthru($fp); fclose($fp); - + return; } } @@ -357,8 +357,9 @@ class ApiController extends Zend_Controller_Action } $md = $this->_getParam('md'); - - $file = StoredFile::Recall(null, $md['gunid']); + $filepath = $md['filepath']; + $filepath = str_replace("\\", "", $filepath); + $file = StoredFile::Recall(null, null, null, $filepath); if (PEAR::isError($file) || is_null($file)) { $this->view->response = "File not in Airtime's Database"; return; diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index ba152f49e..cdc17423e 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -703,7 +703,7 @@ class StoredFile { * @return StoredFile|Playlist|NULL * Return NULL if the object doesnt exist in the DB. */ - public static function Recall($p_id=null, $p_gunid=null, $p_md5sum=null) + public static function Recall($p_id=null, $p_gunid=null, $p_md5sum=null, $p_filepath=null) { global $CC_DBC; global $CC_CONFIG; @@ -713,6 +713,8 @@ class StoredFile { $cond = "gunid='$p_gunid'"; } elseif (!is_null($p_md5sum)) { $cond = "md5='$p_md5sum'"; + } elseif (!is_null($p_filepath)) { + $cond = "filepath='$p_filepath'"; } else { return null; } diff --git a/python_apps/media-monitor/MediaMonitor.py b/python_apps/media-monitor/MediaMonitor.py index afb249f73..5b34455e7 100644 --- a/python_apps/media-monitor/MediaMonitor.py +++ b/python_apps/media-monitor/MediaMonitor.py @@ -71,7 +71,7 @@ class AirtimeNotifier(Notifier): "isrc_number": "isrc",\ "copyright": "copyright",\ } - + schedule_exchange = Exchange("airtime-media-monitor", "direct", durable=True, auto_delete=True) schedule_queue = Queue("media-monitor", exchange=schedule_exchange, key="filesystem") self.connection = BrokerConnection(config["rabbitmq_host"], config["rabbitmq_user"], config["rabbitmq_password"], "/") @@ -87,7 +87,7 @@ class AirtimeNotifier(Notifier): logger = logging.getLogger('root') logger.info("Received md from RabbitMQ: " + body) - m = json.loads(message.body) + m = json.loads(message.body) airtime_file = mutagen.File(m['filepath'], easy=True) del m['filepath'] for key in m.keys() : @@ -124,21 +124,19 @@ class MediaMonitor(ProcessEvent): "copyright": "copyright",\ } + self.supported_file_formats = ['mp3', 'ogg'] self.logger = logging.getLogger('root') - self.temp_files = {} def update_airtime(self, event): self.logger.info("Updating Change to Airtime") - try: + try: f = open(event.pathname, 'rb') m = hashlib.md5() m.update(f.read()) - md5 = m.hexdigest() - gunid = event.name.split('.')[0] - md = {'gunid':gunid, 'md5':md5} + md = {'filepath':event.pathname, 'md5':md5} file_info = mutagen.File(event.pathname, easy=True) attrs = self.mutagen2airtime @@ -153,12 +151,28 @@ class MediaMonitor(ProcessEvent): except Exception, e: self.logger.info("%s", e) + def is_temp_file(self, filename): + info = filename.split(".") + + if(info[-2] in self.supported_file_formats): + return True + else : + return False + + def is_audio_file(self, filename): + info = filename.split(".") + + if(info[-1] in self.supported_file_formats): + return True + else : + return False + + def process_IN_CREATE(self, event): if not event.dir : - filename_info = event.name.split(".") #file created is a tmp file which will be modified and then moved back to the original filename. - if len(filename_info) > 2 : + if self.is_temp_file(event.name) : self.temp_files[event.pathname] = None #This is a newly imported file. else : @@ -166,18 +180,13 @@ class MediaMonitor(ProcessEvent): self.logger.info("%s: %s", event.maskname, event.pathname) - #event.path : /srv/airtime/stor/bd2 - #event.name : bd2aa73b58d9c8abcced989621846e99.mp3 - #event.pathname : /srv/airtime/stor/bd2/bd2aa73b58d9c8abcced989621846e99.mp3 def process_IN_MODIFY(self, event): if not event.dir : - filename_info = event.name.split(".") - #file modified is not a tmp file. - if len(filename_info) == 2 : - self.update_airtime(event) + if self.is_audio_file(event.name) : + self.update_airtime(event) - self.logger.info("%s: path: %s name: %s", event.maskname, event.path, event.name) + self.logger.info("%s: %s", event.maskname, event.pathname) def process_IN_MOVED_FROM(self, event): if event.pathname in self.temp_files : @@ -190,7 +199,7 @@ class MediaMonitor(ProcessEvent): if event.cookie in self.temp_files : del self.temp_files[event.cookie] self.update_airtime(event) - + self.logger.info("%s: %s", event.maskname, event.pathname) def process_default(self, event): From f271f31ad88157a5efce0415b8e445fc20598828 Mon Sep 17 00:00:00 2001 From: Naomi Aro Date: Fri, 3 Jun 2011 14:26:11 -0400 Subject: [PATCH 05/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention adding ability to drag music into stor folder. --- python_apps/media-monitor/MediaMonitor.py | 76 ++++++++++++++++++++- python_apps/media-monitor/media-monitor.cfg | 2 +- 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/python_apps/media-monitor/MediaMonitor.py b/python_apps/media-monitor/MediaMonitor.py index 5b34455e7..92e5e8dfe 100644 --- a/python_apps/media-monitor/MediaMonitor.py +++ b/python_apps/media-monitor/MediaMonitor.py @@ -8,6 +8,7 @@ import os import sys import hashlib import json +import shutil from subprocess import Popen, PIPE, STDOUT @@ -22,6 +23,9 @@ from kombu.connection import BrokerConnection from kombu.messaging import Exchange, Queue, Consumer, Producer from api_clients import api_client +global storage_directory +storage_directory = "/srv/airtime/stor" + # configure logging try: logging.config.fileConfig("logging.cfg") @@ -42,6 +46,7 @@ list of supported easy tags in mutagen version 1.20 ['albumartistsort', 'musicbrainz_albumstatus', 'lyricist', 'releasecountry', 'date', 'performer', 'musicbrainz_albumartistid', 'composer', 'encodedby', 'tracknumber', 'musicbrainz_albumid', 'album', 'asin', 'musicbrainz_artistid', 'mood', 'copyright', 'author', 'media', 'length', 'version', 'artistsort', 'titlesort', 'discsubtitle', 'website', 'musicip_fingerprint', 'conductor', 'compilation', 'barcode', 'performer:*', 'composersort', 'musicbrainz_discid', 'musicbrainz_albumtype', 'genre', 'isrc', 'discnumber', 'musicbrainz_trmid', 'replaygain_*_gain', 'musicip_puid', 'artist', 'title', 'bpm', 'musicbrainz_trackid', 'arranger', 'albumsort', 'replaygain_*_peak', 'organization'] """ + def checkRabbitMQ(notifier): try: notifier.connection.drain_events(timeout=int(config["check_airtime_events"])) @@ -128,6 +133,72 @@ class MediaMonitor(ProcessEvent): self.logger = logging.getLogger('root') self.temp_files = {} + def ensure_dir(self, filepath): + + directory = os.path.dirname(filepath) + + if ((not os.path.exists(directory)) or ((os.path.exists(directory) and not os.path.isdir(directory)))): + os.makedirs(directory, 02775) + + def create_unique_filename(self, filepath): + + file_dir = os.path.dirname(filepath) + print file_dir + filename = os.path.basename(filepath).split(".")[0] + print filename + file_ext = os.path.splitext(filepath)[1] + print file_ext + + if(os.path.exists(filepath)): + i = 1; + while(True): + new_filepath = "%s/%s(%s).%s" % (file_dir, filename, i, file_ext) + + if(os.path.exists(new_filepath)): + i = i+1; + else: + return new_filepath + + return filepath + + def create_file_path(self, imported_filepath): + + global storage_directory + + original_name = os.path.basename(imported_filepath) + file_ext = os.path.splitext(imported_filepath)[1] + file_info = mutagen.File(imported_filepath, easy=True) + + metadata = {'artist':None, + 'album':None, + 'title':None, + 'tracknumber':None} + + for key in metadata.keys(): + if key in file_info: + metadata[key] = file_info[key][0] + + if metadata['artist'] is not None: + base = "%s/%s" % (storage_directory, metadata['artist']) + if metadata['album'] is not None: + base = "%s/%s" % (base, metadata['album']) + if metadata['title'] is not None: + if metadata['tracknumber'] is not None: + metadata['tracknumber'] = "%02d" % (int(metadata['tracknumber'])) + base = "%s/%s - %s" % (base, metadata['tracknumber'], metadata['title']) + else: + base = "%s/%s" % (base, metadata['title']) + else: + base = "%s/%s" % (base, original_name) + else: + base = "%s/%s" % (storage_directory, original_name) + + base = "%s%s" % (base, file_ext) + + filepath = self.create_unique_filename(base) + self.ensure_dir(filepath) + shutil.move(imported_filepath, filepath) + def update_airtime(self, event): self.logger.info("Updating Change to Airtime") try: @@ -145,7 +216,6 @@ class MediaMonitor(ProcessEvent): md[attrs[key]] = file_info[key][0] data = {'md': md} - response = self.api_client.update_media_metadata(data) except Exception, e: @@ -176,7 +246,7 @@ class MediaMonitor(ProcessEvent): self.temp_files[event.pathname] = None #This is a newly imported file. else : - pass + self.create_file_path(event.pathname) self.logger.info("%s: %s", event.maskname, event.pathname) @@ -213,7 +283,7 @@ if __name__ == '__main__': #mask = pyinotify.ALL_EVENTS wm = WatchManager() - wdd = wm.add_watch('/srv/airtime/stor', mask, rec=True, auto_add=True) + wdd = wm.add_watch(storage_directory, mask, rec=True, auto_add=True) notifier = AirtimeNotifier(wm, MediaMonitor(), read_freq=int(config["check_filesystem_events"]), timeout=1) notifier.coalesce_events() diff --git a/python_apps/media-monitor/media-monitor.cfg b/python_apps/media-monitor/media-monitor.cfg index 5f2a79a83..38b44cea7 100644 --- a/python_apps/media-monitor/media-monitor.cfg +++ b/python_apps/media-monitor/media-monitor.cfg @@ -32,5 +32,5 @@ rabbitmq_password = 'guest' ############################################ # Media-Monitor preferences # ############################################ -check_filesystem_events = 30 #how long to queue up events performed on the files themselves. +check_filesystem_events = 5 #how long to queue up events performed on the files themselves. check_airtime_events = 30 #how long to queue metadata input from airtime. From f7e86100af1535b8a7b35d3bf7156ba6300c03b5 Mon Sep 17 00:00:00 2001 From: Naomi Aro Date: Mon, 6 Jun 2011 10:53:08 +0200 Subject: [PATCH 06/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention enabling media monitor changes. --- install/include/AirtimeIni.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/install/include/AirtimeIni.php b/install/include/AirtimeIni.php index 7f2ff24e8..a5adf6faf 100644 --- a/install/include/AirtimeIni.php +++ b/install/include/AirtimeIni.php @@ -33,7 +33,8 @@ class AirtimeIni $configFiles = array(AirtimeIni::CONF_FILE_AIRTIME, AirtimeIni::CONF_FILE_PYPO, AirtimeIni::CONF_FILE_RECORDER, - AirtimeIni::CONF_FILE_LIQUIDSOAP); + AirtimeIni::CONF_FILE_LIQUIDSOAP, + AirtimeIni::CONF_FILE_MEDIAMONITOR); $exist = false; foreach ($configFiles as $conf) { if (file_exists($conf)) { @@ -102,9 +103,9 @@ class AirtimeIni } //wait until Airtime 1.9.0 - //if (file_exists(AirtimeIni::CONF_FILE_MEDIAMONITOR)){ - // unlink(AirtimeIni::CONF_FILE_MEDIAMONITOR); - //} + if (file_exists(AirtimeIni::CONF_FILE_MEDIAMONITOR)){ + unlink(AirtimeIni::CONF_FILE_MEDIAMONITOR); + } if (file_exists("etc/airtime")){ rmdir("/etc/airtime/"); @@ -157,8 +158,8 @@ class AirtimeIni foreach ($lines as &$line) { if ($line[0] != "#"){ $key_value = split("=", $line); - $key = trim($key_value[0]); - + $key = trim($key_value[0]); + if ($key == $p_property){ $line = "$p_property = $p_value".PHP_EOL; } @@ -185,7 +186,7 @@ class AirtimeIni AirtimeIni::UpdateIniValue(AirtimeIni::CONF_FILE_AIRTIME, 'airtime_dir', AirtimeInstall::CONF_DIR_WWW); AirtimeIni::UpdateIniValue(AirtimeIni::CONF_FILE_PYPO, 'api_key', "'$api_key'"); AirtimeIni::UpdateIniValue(AirtimeIni::CONF_FILE_RECORDER, 'api_key', "'$api_key'"); - //AirtimeIni::UpdateIniValue(AirtimeIni::CONF_FILE_MEDIAMONITOR, 'api_key', "'$api_key'"); + AirtimeIni::UpdateIniValue(AirtimeIni::CONF_FILE_MEDIAMONITOR, 'api_key', "'$api_key'"); AirtimeIni::UpdateIniValue(AirtimeInstall::CONF_DIR_WWW.'/build/build.properties', 'project.home', AirtimeInstall::CONF_DIR_WWW); } From b1d4b3442b7ecf186e7613a2b01b87b5251421d6 Mon Sep 17 00:00:00 2001 From: Naomi Aro Date: Mon, 6 Jun 2011 10:56:50 +0200 Subject: [PATCH 07/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention enabling uninstall of media monitor. --- install/airtime-uninstall | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/install/airtime-uninstall b/install/airtime-uninstall index 4168eb8e7..b65700d00 100755 --- a/install/airtime-uninstall +++ b/install/airtime-uninstall @@ -12,12 +12,12 @@ php ${SCRIPTPATH}/airtime-uninstall.php echo -e "\n*** Uninstalling Pypo ***" python ${SCRIPTPATH}/../python_apps/pypo/install/pypo-uninstall.py -#echo -e "\n*** Uninstalling Media Monitor ***" -#python ${SCRIPTPATH}/../python_apps/pytag-fs/install/media-monitor-uninstall.py - echo -e "\n*** Uninstalling Show Recorder ***" python ${SCRIPTPATH}/../python_apps/show-recorder/install/recorder-uninstall.py +echo -e "\n*** Uninstalling Media Monitor ***" +python ${SCRIPTPATH}/../python_apps/media-monitor/install/media-monitor-uninstall.py + echo -e "\n*** Removing Pypo User ***" python ${SCRIPTPATH}/../python_apps/remove-pypo-user.py From 7c65623477f3cc8a509e301a3b856f55f11dab14 Mon Sep 17 00:00:00 2001 From: Naomi Aro Date: Mon, 6 Jun 2011 11:06:34 +0200 Subject: [PATCH 08/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention --- python_apps/media-monitor/MediaMonitor.py | 39 +++++++++++++++-------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/python_apps/media-monitor/MediaMonitor.py b/python_apps/media-monitor/MediaMonitor.py index 92e5e8dfe..dce10f579 100644 --- a/python_apps/media-monitor/MediaMonitor.py +++ b/python_apps/media-monitor/MediaMonitor.py @@ -132,6 +132,15 @@ class MediaMonitor(ProcessEvent): self.supported_file_formats = ['mp3', 'ogg'] self.logger = logging.getLogger('root') self.temp_files = {} + self.imported_renamed_files = {} + + def get_md5(self, filepath): + f = open(filepath, 'rb') + m = hashlib.md5() + m.update(f.read()) + md5 = m.hexdigest() + + return md5 def ensure_dir(self, filepath): @@ -143,11 +152,8 @@ class MediaMonitor(ProcessEvent): def create_unique_filename(self, filepath): file_dir = os.path.dirname(filepath) - print file_dir filename = os.path.basename(filepath).split(".")[0] - print filename file_ext = os.path.splitext(filepath)[1] - print file_ext if(os.path.exists(filepath)): i = 1; @@ -157,7 +163,9 @@ class MediaMonitor(ProcessEvent): if(os.path.exists(new_filepath)): i = i+1; else: - return new_filepath + filepath = new_filepath + + self.imported_renamed_files[filepath] = 0 return filepath @@ -202,11 +210,7 @@ class MediaMonitor(ProcessEvent): def update_airtime(self, event): self.logger.info("Updating Change to Airtime") try: - f = open(event.pathname, 'rb') - m = hashlib.md5() - m.update(f.read()) - md5 = m.hexdigest() - + md5 = self.get_md5(event.pathname) md = {'filepath':event.pathname, 'md5':md5} file_info = mutagen.File(event.pathname, easy=True) @@ -221,6 +225,13 @@ class MediaMonitor(ProcessEvent): except Exception, e: self.logger.info("%s", e) + def is_renamed_file(self, filename): + if filename in self.imported_renamed_files: + del self.imported_renamed_files[filename] + return True + + return False + def is_temp_file(self, filename): info = filename.split(".") @@ -239,16 +250,17 @@ class MediaMonitor(ProcessEvent): def process_IN_CREATE(self, event): - if not event.dir : + if not event.dir: #file created is a tmp file which will be modified and then moved back to the original filename. if self.is_temp_file(event.name) : self.temp_files[event.pathname] = None #This is a newly imported file. else : + #if not is_renamed_file(event.pathname): self.create_file_path(event.pathname) - self.logger.info("%s: %s", event.maskname, event.pathname) + self.logger.info("%s: %s", event.maskname, event.pathname) def process_IN_MODIFY(self, event): if not event.dir : @@ -285,10 +297,11 @@ if __name__ == '__main__': wm = WatchManager() wdd = wm.add_watch(storage_directory, mask, rec=True, auto_add=True) + logger = logging.getLogger('root') + logger.info("Added watch to %s", storage_directory) + notifier = AirtimeNotifier(wm, MediaMonitor(), read_freq=int(config["check_filesystem_events"]), timeout=1) notifier.coalesce_events() notifier.loop(callback=checkRabbitMQ) except KeyboardInterrupt: notifier.stop() - - From bd2859dc1bc0c972821137605378cca8fa8f1dad Mon Sep 17 00:00:00 2001 From: Naomi Date: Fri, 13 May 2011 18:03:34 -0400 Subject: [PATCH 09/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention searching database by filename to retrieve file. Checking if is supported audio file/temp audio file. --- python_apps/media-monitor/MediaMonitor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python_apps/media-monitor/MediaMonitor.py b/python_apps/media-monitor/MediaMonitor.py index dce10f579..7489b67bf 100644 --- a/python_apps/media-monitor/MediaMonitor.py +++ b/python_apps/media-monitor/MediaMonitor.py @@ -251,7 +251,6 @@ class MediaMonitor(ProcessEvent): def process_IN_CREATE(self, event): if not event.dir: - #file created is a tmp file which will be modified and then moved back to the original filename. if self.is_temp_file(event.name) : self.temp_files[event.pathname] = None From 2e03698f02613aee29c742faa4fcd4dc17e4624b Mon Sep 17 00:00:00 2001 From: Naomi Aro Date: Fri, 3 Jun 2011 14:26:11 -0400 Subject: [PATCH 10/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention adding ability to drag music into stor folder. --- python_apps/media-monitor/MediaMonitor.py | 74 +++++++++++++++++++++-- 1 file changed, 70 insertions(+), 4 deletions(-) diff --git a/python_apps/media-monitor/MediaMonitor.py b/python_apps/media-monitor/MediaMonitor.py index 7489b67bf..4f42cc91e 100644 --- a/python_apps/media-monitor/MediaMonitor.py +++ b/python_apps/media-monitor/MediaMonitor.py @@ -207,6 +207,72 @@ class MediaMonitor(ProcessEvent): self.ensure_dir(filepath) shutil.move(imported_filepath, filepath) + def ensure_dir(self, filepath): + + directory = os.path.dirname(filepath) + + if ((not os.path.exists(directory)) or ((os.path.exists(directory) and not os.path.isdir(directory)))): + os.makedirs(directory, 02775) + + def create_unique_filename(self, filepath): + + file_dir = os.path.dirname(filepath) + print file_dir + filename = os.path.basename(filepath).split(".")[0] + print filename + file_ext = os.path.splitext(filepath)[1] + print file_ext + + if(os.path.exists(filepath)): + i = 1; + while(True): + new_filepath = "%s/%s(%s).%s" % (file_dir, filename, i, file_ext) + + if(os.path.exists(new_filepath)): + i = i+1; + else: + return new_filepath + + return filepath + + def create_file_path(self, imported_filepath): + + global storage_directory + + original_name = os.path.basename(imported_filepath) + file_ext = os.path.splitext(imported_filepath)[1] + file_info = mutagen.File(imported_filepath, easy=True) + + metadata = {'artist':None, + 'album':None, + 'title':None, + 'tracknumber':None} + + for key in metadata.keys(): + if key in file_info: + metadata[key] = file_info[key][0] + + if metadata['artist'] is not None: + base = "%s/%s" % (storage_directory, metadata['artist']) + if metadata['album'] is not None: + base = "%s/%s" % (base, metadata['album']) + if metadata['title'] is not None: + if metadata['tracknumber'] is not None: + metadata['tracknumber'] = "%02d" % (int(metadata['tracknumber'])) + base = "%s/%s - %s" % (base, metadata['tracknumber'], metadata['title']) + else: + base = "%s/%s" % (base, metadata['title']) + else: + base = "%s/%s" % (base, original_name) + else: + base = "%s/%s" % (storage_directory, original_name) + + base = "%s%s" % (base, file_ext) + + filepath = self.create_unique_filename(base) + self.ensure_dir(filepath) + shutil.move(imported_filepath, filepath) + def update_airtime(self, event): self.logger.info("Updating Change to Airtime") try: @@ -256,10 +322,10 @@ class MediaMonitor(ProcessEvent): self.temp_files[event.pathname] = None #This is a newly imported file. else : - #if not is_renamed_file(event.pathname): - self.create_file_path(event.pathname) + if not is_renamed_file(event.pathname): + self.create_file_path(event.pathname) - self.logger.info("%s: %s", event.maskname, event.pathname) + self.logger.info("%s: %s", event.maskname, event.pathname) def process_IN_MODIFY(self, event): if not event.dir : @@ -267,7 +333,7 @@ class MediaMonitor(ProcessEvent): if self.is_audio_file(event.name) : self.update_airtime(event) - self.logger.info("%s: %s", event.maskname, event.pathname) + self.logger.info("%s: %s", event.maskname, event.pathname) def process_IN_MOVED_FROM(self, event): if event.pathname in self.temp_files : From fb117fb61738e4e47b690fb9334efd34978a4c3b Mon Sep 17 00:00:00 2001 From: Naomi Aro Date: Mon, 6 Jun 2011 11:06:34 +0200 Subject: [PATCH 11/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention --- python_apps/media-monitor/MediaMonitor.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/python_apps/media-monitor/MediaMonitor.py b/python_apps/media-monitor/MediaMonitor.py index 4f42cc91e..843ea572a 100644 --- a/python_apps/media-monitor/MediaMonitor.py +++ b/python_apps/media-monitor/MediaMonitor.py @@ -217,11 +217,8 @@ class MediaMonitor(ProcessEvent): def create_unique_filename(self, filepath): file_dir = os.path.dirname(filepath) - print file_dir filename = os.path.basename(filepath).split(".")[0] - print filename file_ext = os.path.splitext(filepath)[1] - print file_ext if(os.path.exists(filepath)): i = 1; @@ -231,7 +228,9 @@ class MediaMonitor(ProcessEvent): if(os.path.exists(new_filepath)): i = i+1; else: - return new_filepath + filepath = new_filepath + + self.imported_renamed_files[filepath] = 0 return filepath @@ -325,7 +324,7 @@ class MediaMonitor(ProcessEvent): if not is_renamed_file(event.pathname): self.create_file_path(event.pathname) - self.logger.info("%s: %s", event.maskname, event.pathname) + self.logger.info("%s: %s", event.maskname, event.pathname) def process_IN_MODIFY(self, event): if not event.dir : From 3c689efe06e31e9e7a58eab0fb01d32eca052d22 Mon Sep 17 00:00:00 2001 From: Naomi Aro Date: Mon, 6 Jun 2011 17:34:52 +0200 Subject: [PATCH 12/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention check if file is new or exists --- .../application/controllers/ApiController.php | 19 +++++-- python_apps/media-monitor/MediaMonitor.py | 56 +++++-------------- 2 files changed, 28 insertions(+), 47 deletions(-) diff --git a/airtime_mvc/application/controllers/ApiController.php b/airtime_mvc/application/controllers/ApiController.php index 422fa608c..1f8b5d5ee 100644 --- a/airtime_mvc/application/controllers/ApiController.php +++ b/airtime_mvc/application/controllers/ApiController.php @@ -369,18 +369,25 @@ class ApiController extends Zend_Controller_Action $filepath = $md['filepath']; $filepath = str_replace("\\", "", $filepath); $file = StoredFile::Recall(null, null, null, $filepath); - if (PEAR::isError($file) || is_null($file)) { - $this->view->response = "File not in Airtime's Database"; + if (PEAR::isError($file)) { + $this->view->response = "Problem recalling file in Airtime"; return; } - $res = $file->replaceDbMetadata($md); + //New file added to Airtime + if (is_null($file)) { - if (PEAR::isError($res)) { - $this->view->response = "Metadata Change Failed"; } + //Updating a metadata change. else { - $this->view->response = "Success!"; + $res = $file->replaceDbMetadata($md); + + if (PEAR::isError($res)) { + $this->view->response = "Metadata Change Failed"; + } + else { + $this->view->response = "Success!"; + } } } } diff --git a/python_apps/media-monitor/MediaMonitor.py b/python_apps/media-monitor/MediaMonitor.py index 843ea572a..2da47f1f5 100644 --- a/python_apps/media-monitor/MediaMonitor.py +++ b/python_apps/media-monitor/MediaMonitor.py @@ -142,6 +142,11 @@ class MediaMonitor(ProcessEvent): return md5 + ## mutagen_length is in seconds with the format (d+).dd + ## return format hh:mm:ss. + def format_length(self, mutagen_length): + time = mutagen_length.split(".") + def ensure_dir(self, filepath): directory = os.path.dirname(filepath) @@ -181,6 +186,7 @@ class MediaMonitor(ProcessEvent): 'album':None, 'title':None, 'tracknumber':None} +<<<<<<< HEAD for key in metadata.keys(): if key in file_info: @@ -234,44 +240,6 @@ class MediaMonitor(ProcessEvent): return filepath - def create_file_path(self, imported_filepath): - - global storage_directory - - original_name = os.path.basename(imported_filepath) - file_ext = os.path.splitext(imported_filepath)[1] - file_info = mutagen.File(imported_filepath, easy=True) - - metadata = {'artist':None, - 'album':None, - 'title':None, - 'tracknumber':None} - - for key in metadata.keys(): - if key in file_info: - metadata[key] = file_info[key][0] - - if metadata['artist'] is not None: - base = "%s/%s" % (storage_directory, metadata['artist']) - if metadata['album'] is not None: - base = "%s/%s" % (base, metadata['album']) - if metadata['title'] is not None: - if metadata['tracknumber'] is not None: - metadata['tracknumber'] = "%02d" % (int(metadata['tracknumber'])) - base = "%s/%s - %s" % (base, metadata['tracknumber'], metadata['title']) - else: - base = "%s/%s" % (base, metadata['title']) - else: - base = "%s/%s" % (base, original_name) - else: - base = "%s/%s" % (storage_directory, original_name) - - base = "%s%s" % (base, file_ext) - - filepath = self.create_unique_filename(base) - self.ensure_dir(filepath) - shutil.move(imported_filepath, filepath) - def update_airtime(self, event): self.logger.info("Updating Change to Airtime") try: @@ -284,6 +252,11 @@ class MediaMonitor(ProcessEvent): if key in attrs : md[attrs[key]] = file_info[key][0] + md['mime'] = file_info.mime[0] + md['bitrate'] = file_info.info.bitrate + md['samplerate'] = file_info.info.sample_rate + + data = {'md': md} response = self.api_client.update_media_metadata(data) @@ -321,14 +294,13 @@ class MediaMonitor(ProcessEvent): self.temp_files[event.pathname] = None #This is a newly imported file. else : - if not is_renamed_file(event.pathname): + if not self.is_renamed_file(event.pathname): self.create_file_path(event.pathname) - self.logger.info("%s: %s", event.maskname, event.pathname) + self.logger.info("%s: %s", event.maskname, event.pathname) def process_IN_MODIFY(self, event): if not event.dir : - if self.is_audio_file(event.name) : self.update_airtime(event) @@ -369,3 +341,5 @@ if __name__ == '__main__': notifier.loop(callback=checkRabbitMQ) except KeyboardInterrupt: notifier.stop() + except Exception, e: + logger.error('Exception: %s', e) From 975f001c86b170d603c91178526dd49233d21e87 Mon Sep 17 00:00:00 2001 From: Naomi Aro Date: Wed, 8 Jun 2011 10:15:35 +0200 Subject: [PATCH 13/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention refactoring Storedfile --- airtime_mvc/application/configs/constants.php | 37 +- airtime_mvc/application/models/StoredFile.php | 1515 +++-------------- python_apps/media-monitor/MediaMonitor.py | 72 +- 3 files changed, 263 insertions(+), 1361 deletions(-) diff --git a/airtime_mvc/application/configs/constants.php b/airtime_mvc/application/configs/constants.php index 95c8b3263..dcb381b43 100644 --- a/airtime_mvc/application/configs/constants.php +++ b/airtime_mvc/application/configs/constants.php @@ -4,19 +4,30 @@ define('AIRTIME_VERSION', '1.9.0-devel'); define('AIRTIME_COPYRIGHT_DATE', '2010-2011'); define('AIRTIME_REST_VERSION', '1.1'); -// Metadata Keys -define('UI_MDATA_KEY_TITLE', 'dc:title'); -define('UI_MDATA_KEY_CREATOR', 'dc:creator'); -define('UI_MDATA_KEY_SOURCE', 'dc:source'); -define('UI_MDATA_KEY_DURATION', 'dcterms:extent'); -define('UI_MDATA_KEY_URL', 'ls:url'); -define('UI_MDATA_KEY_FORMAT', 'dc:format'); -define('UI_MDATA_KEY_DESCRIPTION', 'dc:description'); -define('UI_MDATA_KEY_CHANNELS', 'ls:channels'); -define('UI_MDATA_KEY_SAMPLERATE', 'ls:samplerate'); -define('UI_MDATA_KEY_BITRATE', 'ls:bitrate'); -define('UI_MDATA_KEY_ENCODER', 'ls:encoder'); -define('UI_MDATA_KEY_FILENAME', 'ls:filename'); +// Metadata Keys for files +define('MDATA_KEY_FILEPATH', 'filepath'); +define('MDATA_KEY_MD5', 'md5'); +define('MDATA_KEY_TITLE', 'track_title'); +define('MDATA_KEY_CREATOR', 'artist_name'); +define('MDATA_KEY_SOURCE', 'album_title'); +define('MDATA_KEY_DURATION', 'length'); +define('MDATA_KEY_MIME', 'mime'); +define('MDATA_KEY_URL', 'url'); +define('MDATA_KEY_GENRE', 'genre'); +define('MDATA_KEY_MOOD', 'mood'); +define('MDATA_KEY_LABEL', 'label'); +define('MDATA_KEY_COMPOSER', 'composer'); +define('MDATA_KEY_FORMAT', 'format'); +define('MDATA_KEY_DESCRIPTION', 'description'); +define('MDATA_KEY_SAMPLERATE', 'sample_rate'); +define('MDATA_KEY_BITRATE', 'bit_rate'); +define('MDATA_KEY_ENCODER', 'encoded_by'); +define('MDATA_KEY_ISRC', 'isrc_number'); +define('MDATA_KEY_COPYRIGHT', 'copyright'); +define('MDATA_KEY_YEAR', 'year'); +define('MDATA_KEY_BPM', 'bpm'); +define('MDATA_KEY_TRACKNUMBER', 'track_number'); +define('MDATA_KEY_CONDUCTOR', 'conductor'); define('UI_MDATA_VALUE_FORMAT_FILE', 'File'); define('UI_MDATA_VALUE_FORMAT_STREAM', 'live stream'); diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index 84556ce78..b9ef0e054 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -1,329 +1,8 @@ "ftype", - "dc:format" => "format", - "ls:bitrate" => "bit_rate", - "ls:samplerate" => "sample_rate", - "dcterms:extent" => "length", - "dc:title" => "track_title", - "dc:description" => "comments", - "dc:type" => "genre", - "dc:creator" => "artist_name", - "dc:source" => "album_title", - "ls:channels" => "channels", - "ls:filename" => "name", - "ls:year" => "year", - "ls:url" => "url", - "ls:track_num" => "track_number", - "ls:mood" => "mood", - "ls:bpm" => "bpm", - "ls:disc_num" => "disc_number", - "ls:rating" => "rating", - "ls:encoded_by" => "encoded_by", - "dc:publisher" => "label", - "ls:composer" => "composer", - "ls:encoder" => "encoder", - "ls:crc" => "checksum", - "ls:lyrics" => "lyrics", - "ls:orchestra" => "orchestra", - "ls:conductor" => "conductor", - "ls:lyricist" => "lyricist", - "ls:originallyricist" => "original_lyricist", - "ls:radiostationname" => "radio_station_name", - "ls:audiofileinfourl" => "info_url", - "ls:artisturl" => "artist_url", - "ls:audiosourceurl" => "audio_source_url", - "ls:radiostationurl" => "radio_station_url", - "ls:buycdurl" => "buy_this_url", - "ls:isrcnumber" => "isrc_number", - "ls:catalognumber" => "catalog_number", - "ls:originalartist" => "original_artist", - "dc:rights" => "copyright", - "dcterms:temporal" => "report_datetime", - "dcterms:spatial" => "report_location", - "dcterms:entity" => "report_organization", - "dc:subject" => "subject", - "dc:contributor" => "contributor", - "dc:language" => "language"); - - public static function GetMapMetadataXmlToDb() { - return Metadata::$MAP_METADATA_XML_TO_DB; - } - - /** - * Track numbers in metadata tags can come in many formats: - * "1 of 20", "1/20", "20/1". This function parses the track - * number and gets the real number so that we can sort by it - * in the database. - * - * @param string $p_trackNumber - * @return int - */ - public static function ParseTrackNumber($p_trackNumber) - { - $num = trim($p_trackNumber); - if (!is_numeric($num)) { - $matches = preg_match("/\s*([0-9]+)([^0-9]*)([0-9]*)\s*/", $num, $results); - $trackNum = 0; - foreach ($results as $result) { - if (is_numeric($result)) { - if ($trackNum == 0) { - $trackNum = $result; - } elseif ($result < $trackNum) { - $trackNum = $result; - } - } - } - } else { - $trackNum = $num; - } - return $trackNum; - } - - - /** - * Add data to the array $p_mdata. - * - * Converts the given string ($val) into UTF-8. - * - * @param array $p_mdata - * The array to add the metadata to. - * @param string $p_key - * Metadata key. - * @param string $p_val - * Metadata value. - * @param string $p_inputEncoding - * Encoding type of the input value. - */ - public static function AddToArray(&$p_mdata, $p_key, $p_val, $p_inputEncoding='iso-8859-1') - { - if (!is_null($p_val)) { - $data = $p_val; - $outputEncoding = 'UTF-8'; - //if (function_exists('iconv') && ($p_inputEncoding != $outputEncoding) ) { - if (function_exists('iconv') && is_string($p_val)) { - $newData = @iconv($p_inputEncoding, $outputEncoding, $data); - if ($newData === FALSE) { - echo "Warning: convert $key data to unicode failed\n"; - } elseif ($newData != $data) { - echo "Converted string: '$data' (".gettype($data).") -> '$newData' (".gettype($newData).").\n"; - $data = $newData; - } - } - $p_mdata[$p_key] = trim($data); - } - } - - - /** - * Return an array with the given audio file's ID3 tags. The keys in the - * array can be: - *
-     * 		dc:format ("mime type")
-     * 		dcterms:extent ("duration")
-     * 		dc:title
-     * 		dc:creator ("artist")
-     * 		dc:source ("album")
-     *      dc:type ("genre")
-     * 		ls:bitrate
-     * 		ls:encoded_by
-     * 		ls:track_num
-     * 		ls:channels
-     * 		ls:year
-     * 		ls:filename
-     * 
- * - * @param string $p_filename - * @param boolean $p_testonly - * For diagnostic and debugging purposes - setting this to TRUE - * will print out the values found in the file and the ones assigned - * to the return array. - * @return array|PEAR_Error - */ - public static function LoadFromFile($p_filename, $p_testonly = false) - { - $getID3 = new getID3(); - $infoFromFile = $getID3->analyze($p_filename); - if (PEAR::isError($infoFromFile)) { - return $infoFromFile; - } - if (isset($infoFromFile['error'])) { - return new PEAR_Error(array_pop($infoFromFile['error'])); - } - if (!$infoFromFile['bitrate']) { - return new PEAR_Error("File given is not an audio file."); - } - - if ($p_testonly) { - print_r($infoFromFile); - } - $titleKey = 'dc:title'; - $flds = array( - 'dc:format' => array( - array('path'=>"['mime_type']", 'ignoreEnc'=>TRUE), - ), - 'ls:bitrate' => array( - array('path'=>"['bitrate']", 'ignoreEnc'=>TRUE), - array('path'=>"['audio']['bitrate']", 'ignoreEnc'=>TRUE), - ), - 'ls:samplerate' => array( - array('path'=>"['audio']['sample_rate']", 'ignoreEnc'=>TRUE), - ), - 'ls:encoder' => array( - array('path'=>"['audio']['codec']", 'ignoreEnc'=>TRUE), - ), - 'dcterms:extent'=> array( - array('path'=>"['playtime_seconds']", 'ignoreEnc'=>TRUE), - ), - 'ls:composer'=> array( - array('path'=>"['id3v2']['comments']['composer']", 'dataPath'=>"[0]", 'ignoreEnc'=>TRUE), - array('path'=>"['id3v2']['TCOM'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), - array('path'=>"['tags']['id3v2']['composer']", 'dataPath'=>"[0]", 'ignoreEnc'=>TRUE), - array('path'=>"['ogg']['comments']['composer']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), - array('path'=>"['tags']['vorbiscomment']['composer']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), - ), - 'dc:description'=> array( - array('path'=>"['id3v1']['comments']['comment']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), - array('path'=>"['id3v2']['comments']['comments']", 'dataPath'=>"[0]", 'ignoreEnc'=>TRUE), - array('path'=>"['id3v2']['COMM'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), - array('path'=>"['tags']['id3v2']['comments']", 'dataPath'=>"[0]", 'ignoreEnc'=>TRUE), - array('path'=>"['ogg']['comments']['comment']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), - array('path'=>"['tags']['vorbiscomment']['comment']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), - ), - 'dc:type'=> array( - array('path'=>"['id3v1']", 'dataPath'=>"['genre']", 'encPath'=>"['encoding']"), - array('path'=>"['id3v2']['comments']['content_type']", 'dataPath'=>"[0]", 'ignoreEnc'=>TRUE), - array('path'=>"['id3v2']['TCON'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), - array('path'=>"['ogg']['comments']['genre']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), - array('path'=>"['tags']['vorbiscomment']['genre']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), - ), - 'dc:title' => array( - array('path'=>"['id3v2']['comments']['title']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), - array('path'=>"['id3v2']['TIT2'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), - array('path'=>"['id3v2']['TT2'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), - array('path'=>"['id3v1']", 'dataPath'=>"['title']", 'encPath'=>"['encoding']"), - array('path'=>"['ogg']['comments']['title']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), - array('path'=>"['tags']['vorbiscomment']['title']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), - ), - 'dc:creator' => array( - array('path'=>"['id3v2']['comments']['artist']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), - array('path'=>"['id3v2']['TPE1'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), - array('path'=>"['id3v2']['TP1'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), - array('path'=>"['id3v1']", 'dataPath'=>"['artist']", 'encPath'=>"['encoding']"), - array('path'=>"['ogg']['comments']['artist']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), - array('path'=>"['tags']['vorbiscomment']['artist']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), - ), - 'dc:source' => array( - array('path'=>"['id3v2']['comments']['album']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), - array('path'=>"['id3v2']['TALB'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), - array('path'=>"['id3v2']['TAL'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), - array('path'=>"['ogg']['comments']['album']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), - array('path'=>"['tags']['vorbiscomment']['album']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), - ), - 'ls:encoded_by' => array( - array('path'=>"['id3v2']['TENC'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), - array('path'=>"['id3v2']['TEN'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), - array('path'=>"['ogg']['comments']['encoded-by']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), - array('path'=>"['tags']['vorbiscomment']['encoded-by']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), - ), - 'ls:track_num' => array( - array('path'=>"['id3v2']['TRCK'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), - array('path'=>"['id3v2']['TRK'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), - array('path'=>"['ogg']['comments']['tracknumber']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), - array('path'=>"['tags']['vorbiscomment']['tracknumber']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), - ), - // 'ls:genre' => array( - // array('path'=>"['id3v1']", 'dataPath'=>"['genre']", 'encPath'=>"['encoding']"), - // array('path'=>"['id3v2']['TCON'][0]", 'dataPath'=>"['data']", 'encPath'=>"['encoding']"), - // array('path'=>"['id3v2']['comments']['content_type']", 'dataPath'=>"[0]", 'ignoreEnc'=>TRUE), - // array('path'=>"['ogg']['comments']['genre']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), - // array('path'=>"['tags']['vorbiscomment']['genre']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), - // ), - 'ls:channels' => array( - array('path'=>"['audio']['channels']", 'ignoreEnc'=>TRUE), - ), - 'ls:year' => array( - array('path'=>"['comments']['date']"), - array('path'=>"['ogg']['comments']['date']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), - array('path'=>"['tags']['vorbiscomment']['date']", 'dataPath'=>"[0]", 'encPath'=>"['encoding']"), - ), - 'ls:filename' => array( - array('path'=>"['filename']"), - ), - ); - $mdata = array(); - if (isset($infoFromFile['audio'])) { - $mdata['audio'] = $infoFromFile['audio']; - } - if (isset($infoFromFile['playtime_seconds'])) { - $mdata['playtime_seconds'] = $infoFromFile['playtime_seconds']; - } - - $titleHaveSet = FALSE; - foreach ($flds as $key => $getid3keys) { - foreach ($getid3keys as $getid3key) { - $path = $getid3key["path"]; - $ignoreEnc = isset($getid3key["ignoreEnc"])? - $getid3key["ignoreEnc"]:FALSE; - $dataPath = isset($getid3key["dataPath"])?$getid3key["dataPath"]:""; - $encPath = isset($getid3key["encPath"])?$getid3key["encPath"]:""; - $enc = "UTF-8"; - - $tagElement = "\$infoFromFile$path$dataPath"; - eval("\$tagExists = isset($tagElement);"); - if ($tagExists) { - //echo "ignore encoding: ".($ignoreEnc?"yes":"no")."\n"; - //echo "tag exists\n"; - //echo "encode path: $encPath\n"; - eval("\$data = $tagElement;"); - if (!$ignoreEnc && $encPath != "") { - $encodedElement = "\$infoFromFile$path$encPath"; - eval("\$encodedElementExists = isset($encodedElement);"); - if ($encodedElementExists) { - eval("\$enc = $encodedElement;"); - } - } - - // Special case handling for track number - if ($key == "ls:track_num") { - $data = Metadata::ParseTrackNumber($data); - } - Metadata::AddToArray($mdata, $key, $data, $enc); - if ($key == $titleKey) { - $titleHaveSet = TRUE; - } - break; - } - } - } - if ($p_testonly) { - var_dump($mdata); - } - - if (!$titleHaveSet || trim($mdata[$titleKey]) == '') { - Metadata::AddToArray($mdata, $titleKey, basename($p_filename)); - } - return $mdata; - } -} - /** * StoredFile class * - * Airtime file storage support class.
- * Represents one virtual file in storage. Virtual file has up to two parts: - *
    - *
  • metadata in database
  • - *
  • binary media data in real file
  • - *
- * * @package Airtime * @subpackage StorageServer * @copyright 2010 Sourcefabric O.P.S. @@ -332,603 +11,163 @@ class Metadata { */ class StoredFile { - // *** Variables stored in the database *** + /** + * @holds propel database object + */ + private $_file; /** - * @var int + * array of db metadata -> propel */ - private $id; + private $_dbMD = array ( + "track_title" => "DbTrackTitle", + "artist_name" => "DbArtistName", + "album_title" => "DbAlbumTitle", + "genre" => "DbGenre", + "mood" => "DbMood", + "track_number" => "DbTrackNumber", + "bpm" => "DbBpm", + "label" => "DbLabel", + "composer" => "DbComposer", + "encoded_by" => "DbEncodedBy", + "conductor" => "DbConductor", + "year" => "DbYear", + "info_url" => "DbInfoUrl", + "isrc_number" => "DbIsrcNumber", + "copyright" => "DbCopyright", + "length" => "DbLength", + "bit_rate" => "DbBitRate", + "sample_rate" => "DbSampleRate", + "mime" => "DbMime", + "filepath" => "DbFilepath", + "md5" => "DbMd5" + ); - /** - * Unique ID for the file. This is stored in HEX format. It is - * converted to a bigint whenever it is used in a database call. - * - * @var string - */ - public $gunid; - - /** - * The unique ID of the file as it is stored in the database. - * This is for debugging purposes and may not always exist in this - * class. - * - * @var string - */ - //private $gunidBigint; - - /** - * @var string - */ - private $name; - - /** - * @var string - */ - private $mime; - - /** - * Can be 'audioclip'...others might be coming, like webstream. - * - * @var string - */ - private $ftype; - - /** - * Can be 'ready', 'edited', 'incomplete'. - * - * @var string - */ - private $state; - - /** - * @var int - */ - private $currentlyaccessing; - - /** - * @var int - */ - private $editedby; - - /** - * @var timestamp - */ - private $mtime; - - /** - * @var string - */ - private $md5; - - /** - * @var string - */ - private $filepath; - - - // *** Variables NOT stored in the database *** - - /** - * Directory where the file is located. - * - * @var string - */ - private $resDir; - - /** - * @var boolean - */ - private $exists; - - /** - * @var MetaData - */ - public $md; - - /* ========================================================== constructor */ - /** - * Constructor, but shouldn't be externally called - * - * @param string $p_gunid - * globally unique id of file - */ - public function __construct($p_gunid=NULL) + public function __construct($md) { - $this->gunid = $p_gunid; - if (empty($this->gunid)) { - $this->gunid = StoredFile::generateGunid(); + $this->_file = new CcFiles(); + $this->_file->setDbGunid(md5(uniqid("", true))); + + $this->setMetadata($md); + } + + public function getId() { + return $this->_file->getDbId(); + } + + /** + * Set multiple metadata values using defined metadata constants. + * + * @param array $p_md + * example: $p_md['MDATA_KEY_URL'] = 'http://www.fake.com' + */ + public function setMetadata($p_md=null) + { + if (is_null($p_md)) { + $this->setDbColMetadata(); } else { - $this->loadMetadata(); - $this->exists = is_file($this->filepath) && is_readable($this->filepath); - } - } - - /** - * For testing only, do not use. - */ - public function __setGunid($p_guid) { - $this->gunid = $p_guid; - } - - /** - * Convert XML name to database column name. Used for backwards compatibility - * with old code. - * - * @param string $p_category - * @return string|null - */ - public static function xmlCategoryToDbColumn($p_category) - { - $map = Metadata::GetMapMetadataXmlToDb(); - if (array_key_exists($p_category, $map)) { - return $map[$p_category]; - } - return null; - } - - - /** - * Convert database column name to XML name. - * - * @param string $p_dbColumn - * @return string|null - */ - public static function dbColumnToXmlCatagory($p_dbColumn) - { - $str = array_search($p_dbColumn, Metadata::GetMapMetadataXmlToDb()); - // make return value consistent with xmlCategoryToDbColumn() - if ($str === FALSE) { - $str = null; - } - return $str; - } - - - /** - * GUNID needs to be set before you call this function. - * - */ - public function loadMetadata() - { - global $CC_CONFIG, $CC_DBC; - $escapedValue = pg_escape_string($this->gunid); - $sql = "SELECT * FROM ".$CC_CONFIG["filesTable"] - ." WHERE gunid='$escapedValue'"; - - $this->md = $CC_DBC->getRow($sql); - - if (PEAR::isError($this->md)) { - $error = $this->md; - $this->md = null; - return $error; - } - $this->filepath = $this->md["filepath"]; - if (is_null($this->md)) { - $this->md = array(); - return; - } - $compatibilityData = array(); - foreach ($this->md as $key => $value) { - if ($xmlName = StoredFile::dbColumnToXmlCatagory($key)) { - $compatibilityData[$xmlName] = $value; + $dbMd = array(); + foreach ($p_md as $mdConst => $mdValue) { + $dbMd[constant($mdConst)] = $mdValue; } + $this->setDbColMetadata($dbMd); } - - $this->md = array_merge($this->md, $compatibilityData); - //$this->md = $compatibilityData; - } - - public function setFormat($p_value) - { - $this->md["format"] = $p_value; - } - - public function replaceMetadata($p_values) - { - global $CC_CONFIG, $CC_DBC; - foreach ($p_values as $category => $value) { - $escapedValue = pg_escape_string($value); - $columnName = StoredFile::xmlCategoryToDbColumn($category); - if (!is_null($columnName)) { - $sql = "UPDATE ".$CC_CONFIG["filesTable"] - ." SET $columnName='$escapedValue'" - ." WHERE gunid = '".$this->gunid."'"; - $CC_DBC->query($sql); - } - } - $this->loadMetadata(); - } - - public function replaceDbMetadata($p_values) - { - global $CC_CONFIG, $CC_DBC; - - $data = array(); - foreach ($p_values as $category => $value) { - if (isset($value) && ($value != '')) { - $escapedValue = pg_escape_string($value); - $columnName = $category; - if (!is_null($columnName)) { - $data[] = "$columnName='$escapedValue'"; - } - } - } - - $data = join(",", $data); - $sql = "UPDATE ".$CC_CONFIG["filesTable"] - ." SET $data" - ." WHERE gunid = '".$this->gunid."'"; - $res = $CC_DBC->query($sql); - if (PEAR::isError($res)) { - $CC_DBC->query("ROLLBACK"); - return $res; - } - } - - public function clearMetadata() - { - $metadataColumns = array("format", "bit_rate", "sample_rate", "length", - "track_title", "comments", "genre", "artist_name", "channels", "name", - "year", "url", "track_number"); - foreach ($metadataColumns as $columnName) { - if (!is_null($columnName)) { - $sql = "UPDATE ".$CC_CONFIG["filesTable"] - ." SET $columnName=''" - ." WHERE gunid = '".$this->gunid."'"; - $CC_DBC->query($sql); - } - } - } - - - /** - * Create instance of StoredFile object and insert new file - * - * @param array $p_values - * "filepath" - required, local path to media file (where it is before import) - * "id" - optional, local object id, will be generated if not given - * "gunid" - optional, unique id, for insert file with gunid, will be generated if not given - * "filename" - optional, will use "filepath" if not given - * "metadata" - optional, array of extra metadata, will be automatically calculated if not given. - * "mime" - optional, MIME type, highly recommended to pass in, will be automatically calculated if not given. - * "md5" - optional, MD5 sum, highly recommended to pass in, will be automatically calculated if not given. - * - * @param boolean $p_copyMedia - * copy the media file if true, make symlink if false - * - * @return StoredFile|NULL|PEAR_Error - */ - public static function Insert($p_values, $p_copyMedia=TRUE) - { - global $CC_CONFIG, $CC_DBC; - - if (!isset($p_values["filepath"])) { - return new PEAR_Error("StoredFile::Insert: filepath not set."); - } - if (!file_exists($p_values['filepath'])) { - return PEAR::raiseError("StoredFile::Insert: ". - "media file not found ({$p_values['filepath']})"); - } - - $gunid = isset($p_values['gunid'])?$p_values['gunid']:NULL; - - // Create the StoredFile object - $storedFile = new StoredFile($gunid); - - // Get metadata - if (isset($p_values["metadata"])) { - $metadata = $p_values['metadata']; - } else { - $metadata = Metadata::LoadFromFile($p_values["filepath"]); - } - - $storedFile->name = isset($p_values['filename']) ? $p_values['filename'] : $p_values["filepath"]; - $storedFile->id = isset($p_values['id']) && is_integer($p_values['id'])?(int)$p_values['id']:null; - // NOTE: POSTGRES-SPECIFIC KEYWORD "DEFAULT" BEING USED, WOULD BE "NULL" IN MYSQL - $sqlId = !is_null($storedFile->id)?"'".$storedFile->id."'":'DEFAULT'; - $storedFile->ftype = isset($p_values['filetype']) ? strtolower($p_values['filetype']) : "audioclip"; - $storedFile->mime = (isset($p_values["mime"]) ? $p_values["mime"] : NULL ); - // $storedFile->filepath = $p_values['filepath']; - if (isset($p_values['md5'])) { - $storedFile->md5 = $p_values['md5']; - } elseif (file_exists($p_values['filepath'])) { - //echo "StoredFile::Insert: WARNING: Having to recalculate MD5 value\n"; - $storedFile->md5 = md5_file($p_values['filepath']); - } - - // Check for duplicates -- return duplicate - $duplicate = StoredFile::RecallByMd5($storedFile->md5); - if ($duplicate) { - return $duplicate; - } - - $storedFile->exists = FALSE; - - // Insert record into the database - $escapedName = pg_escape_string($storedFile->name); - $escapedFtype = pg_escape_string($storedFile->ftype); - $sql = "INSERT INTO ".$CC_CONFIG['filesTable'] - ."(id, name, gunid, mime, state, ftype, mtime, md5)" - ."VALUES ({$sqlId}, '{$escapedName}', " - ." '{$storedFile->gunid}'," - ." '{$storedFile->mime}', 'incomplete', '$escapedFtype'," - ." now(), '{$storedFile->md5}')"; - //$_SESSION["debug"] .= "sql: ".$sql."
"; - //echo $sql."\n"; - $res = $CC_DBC->query($sql); - if (PEAR::isError($res)) { - $CC_DBC->query("ROLLBACK"); - return $res; - } - - if (!is_integer($storedFile->id)) { - // NOTE: POSTGRES-SPECIFIC - $sql = "SELECT currval('".$CC_CONFIG["filesSequence"]."_seq')"; - $storedFile->id = $CC_DBC->getOne($sql); - } - $storedFile->setMetadataBatch($metadata); - - // Save media file - $res = $storedFile->addFile($p_values['filepath'], $p_copyMedia); - if (PEAR::isError($res)) { - echo "StoredFile::Insert -- addFile(): '".$res->getMessage()."'\n"; - return $res; - } - - if (empty($storedFile->mime)) { - //echo "StoredFile::Insert: WARNING: Having to recalculate MIME value\n"; - $storedFile->setMime($storedFile->getMime()); - } - - // Save state - $storedFile->setState('ready'); - - // Recall the object to get all the proper values - $storedFile = StoredFile::RecallByGunid($storedFile->gunid); - return $storedFile; } /** - * Fetch instance of StoreFile object.
- * Should be supplied with only ONE parameter, all the rest should - * be NULL. + * Set multiple metadata values using database columns as indexes. * - * @param int $p_id - * local id - * @param string $p_gunid - * global unique id of file - * @param string $p_md5sum - * MD5 sum of the file - * @return StoredFile|Playlist|NULL - * Return NULL if the object doesnt exist in the DB. + * @param array $p_md + * example: $p_md['url'] = 'http://www.fake.com' */ - public static function Recall($p_id=null, $p_gunid=null, $p_md5sum=null, $p_filepath=null) + public function setDbColMetadata($p_md=null) { - global $CC_DBC; - global $CC_CONFIG; - if (!is_null($p_id)) { - $cond = "id='".intval($p_id)."'"; - } elseif (!is_null($p_gunid)) { - $cond = "gunid='$p_gunid'"; - } elseif (!is_null($p_md5sum)) { - $cond = "md5='$p_md5sum'"; - } elseif (!is_null($p_filepath)) { - $cond = "filepath='$p_filepath'"; - } else { - return null; - } - $sql = "SELECT *" - ." FROM ".$CC_CONFIG['filesTable'] - ." WHERE $cond"; - - $row = $CC_DBC->getRow($sql); - if (PEAR::isError($row) || is_null($row)) { - return $row; - } - $gunid = $row['gunid']; - $storedFile = new StoredFile($gunid); - $storedFile->id = $row['id']; - $storedFile->name = $row['name']; - $storedFile->mime = $row['mime']; - $storedFile->ftype = $row['ftype']; - $storedFile->state = $row['state']; - $storedFile->currentlyaccessing = $row['currentlyaccessing']; - $storedFile->editedby = $row['editedby']; - $storedFile->mtime = $row['mtime']; - $storedFile->md5 = $row['md5']; - $storedFile->filepath = $row['filepath']; - - if(file_exists($row['filepath'])) { - $storedFile->exists = true; - } - else { - $storedFile->exists = false; - } - - $storedFile->setFormat($row['ftype']); - return $storedFile; - } - - /** - * Create instance of StoreFile object and recall existing file - * by gunid. - * - * @param string $p_gunid - * global unique id of file - * @return StoredFile - */ - public static function RecallByGunid($p_gunid='') - { - return StoredFile::Recall(null, $p_gunid); - } - - - /** - * Fetch the StoredFile by looking up the MD5 value. - * - * @param string $p_md5sum - * @return StoredFile|NULL|PEAR_Error - */ - public static function RecallByMd5($p_md5sum) - { - return StoredFile::Recall(null, null, $p_md5sum); - } - - - public static function GetAll() - { - global $CC_CONFIG, $CC_DBC; - $sql = "SELECT * FROM ".$CC_CONFIG["filesTable"]; - $rows = $CC_DBC->GetAll($sql); - return $rows; - } - - private function ensureDir($dir) - { - if (!is_dir($dir)) { - mkdir($dir, 02775); - chmod($dir, 02775); - } - } - - private function createUniqueFilename($base, $ext) - { - if(file_exists("$base.$ext")) { - $i = 1; - while(true) { - if(file_exists("$base($i).$ext")) { - $i = $i+1; - } - else { - return "$base($i).$ext"; - } - } - } - - return "$base.$ext"; - } - - /** - * Generate the location to store the file. - * It creates the subdirectory if needed. - */ - private function generateFilePath() - { - global $CC_CONFIG, $CC_DBC; - - $storageDir = $CC_CONFIG['storageDir']; - $info = pathinfo($this->name); - $origName = $info['filename']; - $fileExt = strtolower($info["extension"]); - - $this->loadMetadata(); - - $artist = $this->md["dc:creator"]; - $album = $this->md["dc:source"]; - $title = $this->md["dc:title"]; - $track_num = $this->md["ls:track_num"]; - - if(isset($artist) && $artist != "") { - $base = "$storageDir/$artist"; - $this->ensureDir($base); - - if(isset($album) && $album != "") { - $base = "$base/$album"; - $this->ensureDir($base); - } - - if(isset($title) && $title != "") { - if(isset($track_num) && $track_num != "") { - if($track_num < 10 && strlen($track_num) == 1) { - $track_num = "0$track_num"; - } - $base = "$base/$track_num - $title"; - } - else { - $base = "$base/$title"; - } - } - else { - $base = "$base/$origName"; + if (is_null($p_md)) { + foreach ($this->_dbMD as $dbColumn => $propelColumn) { + $method = "set$propelColumn"; + $this->_file->$method(null); } } else { - $base = "$storageDir/$origName"; - } - - return $this->createUniqueFilename($base, $fileExt); - } - - /** - * Insert media file to filesystem - * - * @param string $p_localFilePath - * local path - * @param boolean $p_copyMedia - * copy the media file if true, make symlink if false - * @return TRUE|PEAR_Error - */ - public function addFile($p_localFilePath, $p_copyMedia=TRUE) - { - global $CC_CONFIG, $CC_DBC; - if ($this->exists) { - return FALSE; - } - // for files downloaded from remote instance: - if ($p_localFilePath == $this->filepath) { - $this->exists = TRUE; - return TRUE; - } - umask(0002); - $dstFile = ''; - if ($p_copyMedia) { - $dstFile = $this->generateFilePath(); - $r = @copy($p_localFilePath, $dstFile); - if (!$r) { - $this->exists = FALSE; - return PEAR::raiseError( - "StoredFile::addFile: file save failed". - " ($p_localFilePath, {$this->filepath})",GBERR_FILEIO - ); + foreach ($p_md as $dbColumn => $mdValue) { + $propelColumn = $this->_dbMD[$dbColumn]; + $method = "set$propelColumn"; + $this->_file->$method($mdValue); } - } else { - $dstFile = $p_localFilePath; - $r = TRUE; } - $this->filepath = $dstFile; - $sqlPath = pg_escape_string($this->filepath); - $sql = "UPDATE ".$CC_CONFIG["filesTable"] - ." SET filepath='{$sqlPath}'" - ." WHERE id={$this->id}"; - //echo $sql."\n"; - $res = $CC_DBC->query($sql); - if (PEAR::isError($res)) { - return $res; - } - $this->exists = TRUE; - return TRUE; - } + $this->_file->save(); + } /** - * Find and return the first exact match for the original file name - * that was used on import. - * @param string $p_name + * Set metadata element value + * + * @param string $category + * Metadata element by metadata constant + * @param string $value + * value to store, if NULL then delete record */ - public static function findByOriginalName($p_name) + public function setMetadataValue($p_category, $p_value) { - global $CC_CONFIG, $CC_DBC; - $sql = "SELECT id FROM ".$CC_CONFIG["filesTable"] - ." WHERE name='".pg_escape_string($p_name)."'"; - $id = $CC_DBC->getOne($sql); - if (is_numeric($id)) { - return StoredFile::Recall($id); - } else { - return NULL; - } + $this->setDbColMetadataValue(constant($p_category), $p_value); } + /** + * Set metadata element value + * + * @param string $category + * Metadata element by db column + * @param string $value + * value to store, if NULL then delete record + */ + public function setDbColMetadataValue($p_category, $p_value) + { + $propelColumn = $this->_dbMD[$p_category]; + $method = "set$propelColumn"; + $this->_file->$method($p_value); + $this->_file->save(); + } + + /** + * Get one metadata value. + * + * @param string $p_category (MDATA_KEY_URL) + * @return string + */ + public function getMetadataValue($p_category) + { + 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. + * + * @return array + */ + public function getDbColMetadata() + { + $md = array(); + foreach ($this->_dbMD as $dbColumn => $propelColumn) { + $method = "get$propelColumn"; + $md[$dbColumn] = $this->_file->$method(); + } + + return $md; + } /** * Delete and insert media file @@ -954,192 +193,6 @@ class StoredFile { return $this->addFile($p_localFilePath, $p_copyMedia); } - - /** - * Return true if file corresponding to the object exists - * - * @return boolean - */ - public function existsFile() - { - return $this->exists; - } - - - /** - * Create instance of StoredFile object and make copy of existing file - * - * @param StoredFile $p_src - * source object - * @param int $p_nid - * new local id - * @return StoredFile - */ - public static function CopyOf(&$p_src, $p_nid) - { - $values = array( - "id" => $p_nid, - "filename" => $p_src->name, - "filepath" => $p_src->getRealFilePath(), - "filetype" => $p_src->getType() - ); - $storedFile = StoredFile::Insert($values); - if (PEAR::isError($storedFile)) { - return $storedFile; - } - $storedFile->replaceMetadata($p_src->getAllMetadata(), 'string'); - return $storedFile; - } - - - private static function NormalizeExtent($v) - { - if (!preg_match("|^\d{2}:\d{2}:\d{2}.\d{6}$|", $v)) { - $s = Playlist::playlistTimeToSeconds($v); - $t = Playlist::secondsToPlaylistTime($s); - return $t; - } - return $v; - } - - /** - * Set metadata element value - * - * @param string $category - * Metadata element identification (e.g. dc:title) - * @param string $value - * value to store, if NULL then delete record - * @return boolean - */ - public function setMetadataValue($p_category, $p_value) - { - global $CC_CONFIG, $CC_DBC; - if (!is_string($p_category) || is_array($p_value)) { - return FALSE; - } - if ($p_category == 'dcterms:extent') { - $p_value = StoredFile::NormalizeExtent($p_value); - } - $columnName = StoredFile::xmlCategoryToDbColumn($p_category); // Get column name - - if (!is_null($columnName)) { - $escapedValue = pg_escape_string($p_value); - $sql = "UPDATE ".$CC_CONFIG["filesTable"] - ." SET $columnName='$escapedValue'" - ." WHERE id={$this->id}"; - //var_dump($sql); - $res = $CC_DBC->query($sql); - if (PEAR::isError($res)) { - return $res; - } - } - return TRUE; - } - - - /** - * Set metadata values in 'batch' mode - * - * @param array $values - * array of key/value pairs - * (e.g. 'dc:title'=>'New title') - * @return boolean - */ - public function setMetadataBatch($values) - { - global $CC_CONFIG, $CC_DBC; - if (!is_array($values)) { - $values = array($values); - } - if (count($values) == 0) { - return true; - } - foreach ($values as $category => $oneValue) { - $columnName = StoredFile::xmlCategoryToDbColumn($category); - if (!is_null($columnName)) { - if ($category == 'dcterms:extent') { - $oneValue = StoredFile::NormalizeExtent($oneValue); - } - // Since track_number is an integer, you cannot set - // it to be the empty string, so we NULL it instead. - if ($columnName == 'track_number' && empty($oneValue)) { - $sqlPart = "$columnName = NULL"; - } elseif (($columnName == 'length') && (strlen($oneValue) > 8)) { - // Postgres doesnt like it if you try to store really large hour - // values. TODO: We need to fix the underlying problem of getting the - // right values. - $parts = explode(':', $oneValue); - $hour = intval($parts[0]); - if ($hour > 24) { - continue; - } else { - $sqlPart = "$columnName = '$oneValue'"; - } - } else { - $escapedValue = pg_escape_string($oneValue); - $sqlPart = "$columnName = '$escapedValue'"; - } - $sqlValues[] = $sqlPart; - } - } - if (count($sqlValues)==0) { - return TRUE; - } - $sql = "UPDATE ".$CC_CONFIG["filesTable"] - ." SET ".join(",", $sqlValues) - ." WHERE id={$this->id}"; - $CC_DBC->query($sql); - return TRUE; - } - - - /** - * Get metadata as array, indexed by the column names in the database. - * - * @return array - */ - public function getMetadata() - { - return $this->md; - } - - /** - * Get one metadata value. - * - * @param string $p_name - * @return string - */ - public function getMetadataValue($p_name) - { - if (isset($this->md[$p_name])){ - return $this->md[$p_name]; - } else { - return ""; - } - } - - /** - * Rename stored virtual file - * - * @param string $p_newname - * @return TRUE|PEAR_Error - */ - public function setName($p_newname) - { - global $CC_CONFIG, $CC_DBC; - $escapedName = pg_escape_string($p_newname); - $sql = "UPDATE ".$CC_CONFIG['filesTable'] - ." SET name='$escapedName', mtime=now()" - ." WHERE gunid='{$this->gunid}'"; - $res = $CC_DBC->query($sql); - if (PEAR::isError($res)) { - return $res; - } - $this->name = $p_newname; - return TRUE; - } - - /** * Set state of virtual file * @@ -1166,53 +219,6 @@ class StoredFile { return TRUE; } - /** - * Set mime-type of virtual file - * - * @param string $p_mime - * mime-type - * @return boolean|PEAR_Error - */ - public function setMime($p_mime) - { - global $CC_CONFIG, $CC_DBC; - if (!is_string($p_mime)) { - $p_mime = 'application/octet-stream'; - } - $escapedMime = pg_escape_string($p_mime); - $sql = "UPDATE ".$CC_CONFIG['filesTable'] - ." SET mime='$escapedMime', mtime=now()" - ." WHERE gunid='{$this->gunid}'"; - $res = $CC_DBC->query($sql); - if (PEAR::isError($res)) { - return $res; - } - $this->mime = $p_mime; - return TRUE; - } - - - /** - * Set md5 of virtual file - * - * @param string $p_md5sum - * @return boolean|PEAR_Error - */ - public function setMd5($p_md5sum) - { - global $CC_CONFIG, $CC_DBC; - $escapedMd5 = pg_escape_string($p_md5sum); - $sql = "UPDATE ".$CC_CONFIG['filesTable'] - ." SET md5='$escapedMd5', mtime=now()" - ." WHERE gunid='{$this->gunid}'"; - $res = $CC_DBC->query($sql); - if (PEAR::isError($res)) { - return $res; - } - $this->md5 = $p_md5sum; - return TRUE; - } - /** * Delete media file from filesystem. * You cant delete a file if it is being accessed. @@ -1224,7 +230,7 @@ class StoredFile { public function deleteFile() { global $CC_CONFIG; - if (!$this->exists) { + if (!$this->exists()) { return FALSE; } if ($this->isAccessed()) { @@ -1287,35 +293,6 @@ class StoredFile { return TRUE; } - - public static function deleteById($p_id) - { - global $CC_CONFIG, $CC_DBC; - if (!is_numeric($p_id)) { - return FALSE; - } - $sql = "DELETE FROM ".$CC_CONFIG["filesTable"]." WHERE id=$p_id"; - - $res = $CC_DBC->query($sql); - if (PEAR::isError($res)) { - return $res; - } - return TRUE; - } - - public static function deleteAll() - { - global $CC_CONFIG, $CC_DBC; - $files = StoredFile::getAll(); - foreach ($files as $file) { - $media = StoredFile::Recall($file["id"]); - $result = $media->delete(); - if (PEAR::isError($result)) { - return $result; - } - } - } - /** * Returns an array of playlist objects that this file is a part of. * @return array @@ -1370,68 +347,11 @@ class StoredFile { * playlist global unique ID * @return boolean */ - public function isEdited($p_playlistId=NULL) + public function isEdited() { - if (is_null($p_playlistId)) { - return ($this->state == 'edited'); - } - $state = $this->getState($p_playlistId); - if ($state != 'edited') { - return FALSE; - } - return TRUE; + } - - /** - * Returns id of user editing playlist - * - * @param string $p_playlistId - * playlist global unique ID - * @return int|null|PEAR_Error - * id of user editing it - */ - public function isEditedBy($p_playlistId=NULL) - { - global $CC_CONFIG, $CC_DBC; - if (is_null($p_playlistId)) { - $p_playlistId = $this->gunid; - } - $sql = "SELECT editedBy FROM ".$CC_CONFIG['filesTable'] - ." WHERE gunid='$p_playlistId'"; - $ca = $CC_DBC->getOne($sql); - if (PEAR::isError($ca)) { - return $ca; - } - if (is_null($ca)) { - return $ca; - } - return intval($ca); - } - - - /** - * Return local ID of virtual file. - * - * @return int - */ - public function getId() - { - return $this->id; - } - - - /** - * Return global ID of virtual file. - * - * @return string - */ - public function getGunid() - { - return $this->gunid; - } - - /** * Returns true if raw media file exists * @return boolean|PEAR_Error @@ -1455,86 +375,25 @@ class StoredFile { return TRUE; } + public function existsFile() { - /** - * Create new global unique id - * @return string - */ - public static function generateGunid() - { - return md5(uniqid("", true)); + if (!isset($p_md["filepath"]) || !file_exists($p_md['filepath']) || !is_readable($p_md['filepath'])) { + return false; + } + else { + return true; + } } - /** * Return suitable extension. * - * @todo make it general - is any tool for it? - * * @return string * file extension without a dot */ public function getFileExtension() { - $fname = $this->getName(); - $pos = strrpos($fname, '.'); - if ($pos !== FALSE) { - $ext = substr($fname, $pos+1); - if ($ext !== FALSE) { - return $ext; - } - } - switch (strtolower($this->mime)) { - case "audio/mpeg": - $ext = "mp3"; - break; - case "audio/x-wav": - case "audio/x-wave": - $ext = "wav"; - break; - case "audio/x-ogg": - case "application/x-ogg": - $ext = "ogg"; - break; - default: - $ext = "bin"; - break; - } - return $ext; - } - - /** - * Get mime-type stored in the file. - * Warning: this function is slow! - * - * @return string - */ - public function getMime() - { - $a = Metadata::LoadFromFile($this->filepath); - if (PEAR::isError($a)) { - return $a; - } - if (isset($a['dc:format'])) { - return $a['dc:format']; - } - return ''; - } - - - /** - * Convenience function. - * @return string - */ - public function getTitle() - { - return $this->md["title"]; - } - - public function getType() - { - return $this->ftype; } /** @@ -1545,15 +404,9 @@ class StoredFile { * @return string * see install() */ - public function getState($p_gunid=NULL) + public function getState() { - global $CC_CONFIG, $CC_DBC; - if (is_null($p_gunid)) { - return $this->state; - } - $sql = "SELECT state FROM ".$CC_CONFIG['filesTable'] - ." WHERE gunid='$p_gunid'"; - return $CC_DBC->getOne($sql); + } @@ -1564,17 +417,10 @@ class StoredFile { * global unique id of file * @return string */ - public function getName($p_gunid=NULL) + public function getName() { - global $CC_CONFIG, $CC_DBC; - if (is_null($p_gunid)) { - return $this->name; - } - $sql = "SELECT name FROM ".$CC_CONFIG['filesTable'] - ." WHERE gunid='$p_gunid'"; - return $CC_DBC->getOne($sql); - } + } /** * Get real filename of raw media data @@ -1583,7 +429,7 @@ class StoredFile { */ public function getRealFilePath() { - return $this->filepath; + return $this->_file->getDbFilepath(); } /** @@ -1596,30 +442,75 @@ class StoredFile { } /** - * Get real filename of metadata file + * Fetch instance of StoreFile object.
+ * Should be supplied with only ONE parameter, all the rest should + * be NULL. * - * @return string - * @see MetaData + * @param int $p_id + * local id + * @param string $p_gunid + * global unique id of file + * @param string $p_md5sum + * MD5 sum of the file + * @return StoredFile|Playlist|NULL + * Return NULL if the object doesnt exist in the DB. */ - public function getRealMetadataFileName() + public static function Recall($p_id=null, $p_gunid=null, $p_md5sum=null, $p_filepath=null) { - //return $this->md->getFileName(); - return $this->md["name"]; + if (isset($p_id)) { + $storedFile = CcFilesQuery::create()->findPK(intval($p_id)); + } + else if (isset($p_gunid)) { + $storedFile = CcFilesQuery::create()->findByDbGunid($p_gunid); + } + else if (isset($p_md5sum)) { + $storedFile = CcFilesQuery::create()->findByDbMd5($p_md5sum); + } + else if (isset($p_filepath)) { + $storedFile = CcFilesQuery::create()->findByDbFilepath($p_filepath); + } + else { + return null; + } + + return $storedFile; + } + + /** + * Create instance of StoreFile object and recall existing file + * by gunid. + * + * @param string $p_gunid + * global unique id of file + * @return StoredFile|NULL + */ + public static function RecallByGunid($p_gunid) + { + return StoredFile::Recall(null, $p_gunid); } /** - * Create and return name for temporary symlink. + * Fetch the StoredFile by looking up the MD5 value. * - * @todo Should be more unique - * @return string + * @param string $p_md5sum + * @return StoredFile|NULL */ - private function _getAccessFileName($p_token, $p_ext='EXT') + public static function RecallByMd5($p_md5sum) { - global $CC_CONFIG; - return $CC_CONFIG['accessDir']."/$p_token.$p_ext"; + return StoredFile::Recall(null, null, $p_md5sum); } + /** + * Fetch the StoredFile by looking up its filepath. + * + * @param string $p_filepath path of file stored in Airtime. + * @return StoredFile|NULL + */ + public static function RecallByFilepath($p_filepath) + { + return StoredFile::Recall(null, null, null, $p_filepath); + } public static function searchFilesForPlaylistBuilder($datatables) { global $CC_CONFIG; @@ -1754,7 +645,7 @@ class StoredFile { return array("sEcho" => intval($data["sEcho"]), "iTotalDisplayRecords" => $totalDisplayRows, "iTotalRecords" => $totalRows, "aaData" => $results); } - public static function uploadFile($p_targetDir) + public static function uploadFile($p_targetDir) { // HTTP headers for no cache etc header('Content-type: text/plain; charset=UTF-8'); diff --git a/python_apps/media-monitor/MediaMonitor.py b/python_apps/media-monitor/MediaMonitor.py index 2da47f1f5..6fb8b4b7d 100644 --- a/python_apps/media-monitor/MediaMonitor.py +++ b/python_apps/media-monitor/MediaMonitor.py @@ -60,21 +60,21 @@ class AirtimeNotifier(Notifier): Notifier.__init__(self, watch_manager, default_proc_fun, read_freq, threshold, timeout) self.airtime2mutagen = {\ - "track_title": "title",\ - "artist_name": "artist",\ - "album_title": "album",\ - "genre": "genre",\ - "mood": "mood",\ - "track_number": "tracknumber",\ - "bpm": "bpm",\ - "label": "organization",\ - "composer": "composer",\ - "encoded_by": "encodedby",\ - "conductor": "conductor",\ - "year": "date",\ - "info_url": "website",\ - "isrc_number": "isrc",\ - "copyright": "copyright",\ + "MDATA_KEY_TITLE": "title",\ + "MDATA_KEY_CREATOR": "artist",\ + "MDATA_KEY_SOURCE": "album",\ + "MDATA_KEY_GENRE": "genre",\ + "MDATA_KEY_MOOD": "mood",\ + "MDATA_KEY_TRACKNUMBER": "tracknumber",\ + "MDATA_KEY_BPM": "bpm",\ + "MDATA_KEY_LABEL": "organization",\ + "MDATA_KEY_COMPOSER": "composer",\ + "MDATA_KEY_ENCODER": "encodedby",\ + "MDATA_KEY_CONDUCTOR": "conductor",\ + "MDATA_KEY_YEAR": "date",\ + "MDATA_KEY_URL": "website",\ + "MDATA_KEY_ISRC": "isrc",\ + "MDATA_KEY_COPYRIGHT": "copyright",\ } schedule_exchange = Exchange("airtime-media-monitor", "direct", durable=True, auto_delete=True) @@ -112,21 +112,21 @@ class MediaMonitor(ProcessEvent): self.api_client = api_client.api_client_factory(config) self.mutagen2airtime = {\ - "title": "track_title",\ - "artist": "artist_name",\ - "album": "album_title",\ - "genre": "genre",\ - "mood": "mood",\ - "tracknumber": "track_number",\ - "bpm": "bpm",\ - "organization": "label",\ - "composer": "composer",\ - "encodedby": "encoded_by",\ - "conductor": "conductor",\ - "date": "year",\ - "website": "info_url",\ - "isrc": "isrc_number",\ - "copyright": "copyright",\ + "title": "MDATA_KEY_TITLE",\ + "artist": "MDATA_KEY_CREATOR",\ + "album": "MDATA_KEY_SOURCE",\ + "genre": "MDATA_KEY_GENRE",\ + "mood": "MDATA_KEY_MOOD",\ + "tracknumber": "MDATA_KEY_TRACKNUMBER",\ + "bpm": "MDATA_KEY_BPM",\ + "organization": "MDATA_KEY_LABEL",\ + "composer": "MDATA_KEY_COMPOSER",\ + "encodedby": "MDATA_KEY_ENCODER",\ + "conductor": "MDATA_KEY_CONDUCTOR",\ + "date": "MDATA_KEY_YEAR",\ + "website": "MDATA_KEY_URL",\ + "isrc": "MDATA_KEY_ISRC",\ + "copyright": "MDATA_KEY_COPYRIGHT",\ } self.supported_file_formats = ['mp3', 'ogg'] @@ -186,7 +186,6 @@ class MediaMonitor(ProcessEvent): 'album':None, 'title':None, 'tracknumber':None} -<<<<<<< HEAD for key in metadata.keys(): if key in file_info: @@ -244,7 +243,8 @@ class MediaMonitor(ProcessEvent): self.logger.info("Updating Change to Airtime") try: md5 = self.get_md5(event.pathname) - md = {'filepath':event.pathname, 'md5':md5} + md['MDATA_KEY_FILEPATH'] = event.pathname + md['MDATA_KEY_MD5'] = md5 file_info = mutagen.File(event.pathname, easy=True) attrs = self.mutagen2airtime @@ -252,10 +252,10 @@ class MediaMonitor(ProcessEvent): if key in attrs : md[attrs[key]] = file_info[key][0] - md['mime'] = file_info.mime[0] - md['bitrate'] = file_info.info.bitrate - md['samplerate'] = file_info.info.sample_rate - + md['MDATA_KEY_MIME'] = file_info.mime[0] + md['MDATA_KEY_BITRATE'] = file_info.info.bitrate + md['MDATA_KEY_SAMPLERATE'] = file_info.info.sample_rated + md['MDATA_KEY_DURATION'] = self.format_length(file_info.info.length) data = {'md': md} response = self.api_client.update_media_metadata(data) From f06613538092c608843c7fa144d3a463f39aa057 Mon Sep 17 00:00:00 2001 From: Naomi Aro Date: Wed, 8 Jun 2011 11:59:48 +0200 Subject: [PATCH 14/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention plupload is not set up with media monitor yet, need to finish import to airtime by dragging into stor folder. --- airtime_mvc/application/models/StoredFile.php | 273 +++++++----------- 1 file changed, 106 insertions(+), 167 deletions(-) diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index b9ef0e054..c55e67f34 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -51,10 +51,21 @@ class StoredFile { $this->setMetadata($md); } - public function getId() { + public function getId() + { return $this->_file->getDbId(); } + public function getFormat() + { + return $this->_file->getDbFtype(); + } + + public function setFormat($p_format) + { + $this->_file->setDbFtype($p_format); + } + /** * Set multiple metadata values using defined metadata constants. * @@ -219,80 +230,6 @@ class StoredFile { return TRUE; } - /** - * Delete media file from filesystem. - * You cant delete a file if it is being accessed. - * You cant delete a file if it is scheduled to be played in the future. - * The file will be removed from all playlists it is a part of. - * - * @return boolean|PEAR_Error - */ - public function deleteFile() - { - global $CC_CONFIG; - if (!$this->exists()) { - return FALSE; - } - if ($this->isAccessed()) { - return PEAR::raiseError( - 'Cannot delete a file that is currently accessed.' - ); - } - - // Check if the file is scheduled to be played in the future - if (Schedule::IsFileScheduledInTheFuture($this->id)) { - return PEAR::raiseError( - 'Cannot delete a file that is scheduled in the future.' - ); - } - - // Only delete the file from filesystem if it has been copied to the - // storage directory. (i.e. dont delete linked files) - if (substr($this->filepath, 0, strlen($CC_CONFIG["storageDir"])) == $CC_CONFIG["storageDir"]) { - // Delete the file - if (!file_exists($this->filepath) || @unlink($this->filepath)) { - $this->exists = FALSE; - return TRUE; - } - else { - return PEAR::raiseError("StoredFile::deleteFile: unlink failed ({$this->filepath})"); - } - } - else { - $this->exists = FALSE; - return TRUE; - } - } - - - /** - * Delete stored virtual file - * - * @param boolean $p_deleteFile - * - * @return TRUE|PEAR_Error - */ - public function delete($p_deleteFile = true) - { - global $CC_CONFIG, $CC_DBC; - if ($p_deleteFile) { - $res = $this->deleteFile(); - if (PEAR::isError($res)) { - return $res; - } - - Playlist::DeleteFileFromAllPlaylists($this->id); - } - - $sql = "DELETE FROM ".$CC_CONFIG['filesTable'] - ." WHERE gunid='{$this->gunid}'"; - $res = $CC_DBC->query($sql); - if (PEAR::isError($res)) { - return $res; - } - return TRUE; - } - /** * Returns an array of playlist objects that this file is a part of. * @return array @@ -312,72 +249,85 @@ class StoredFile { return $playlists; } - /** - * Returns true if virtual file is currently in use.
- * Static or dynamic call is possible. + * Delete stored virtual file * - * @param string $p_gunid - * optional (for static call), global unique id - * @return boolean|PEAR_Error + * @param boolean $p_deleteFile + * + * @return void|PEAR_Error */ - public function isAccessed($p_gunid=NULL) + public function delete() { - global $CC_CONFIG, $CC_DBC; - if (is_null($p_gunid)) { - return ($this->currentlyaccessing > 0); + if (!$this->exists()) { + return PEAR::raiseError('File does not exist.'); } - $sql = "SELECT currentlyAccessing FROM ".$CC_CONFIG['filesTable'] - ." WHERE gunid='$p_gunid'"; - $ca = $CC_DBC->getOne($sql); - if (is_null($ca)) { - return PEAR::raiseError( - "StoredFile::isAccessed: invalid gunid ($p_gunid)", - GBERR_FOBJNEX - ); + + if ($this->getFormat() == 'audioclip') { + $res = $this->deleteFile(); + if (PEAR::isError($res)) { + return $res; + } } - return ($ca > 0); + + Playlist::DeleteFileFromAllPlaylists($this->getId()); + + $this->_file->delete(); } + /** + * Delete media file from filesystem. + * You cant delete a file if it is being accessed. + * You cant delete a file if it is scheduled to be played in the future. + * The file will be removed from all playlists it is a part of. + * + * @return void|PEAR_Error + */ + public function deleteFile() + { + global $CC_CONFIG; + + if ($this->isAccessed()) { + return PEAR::raiseError('Cannot delete a file that is currently accessed.'); + } + + // Check if the file is scheduled to be played in the future + if (Schedule::IsFileScheduledInTheFuture($this->getId())) { + return PEAR::raiseError('Cannot delete a file that is scheduled in the future.'); + } + + // Only delete the file from filesystem if it has been copied to the storage directory + if (substr($this->getFilePath(), 0, strlen($CC_CONFIG["storageDir"])) == $CC_CONFIG["storageDir"]) { + // Delete the file + $res = unlink($this->getFilePath()); + if (!$res) { + return PEAR::raiseError("StoredFile::deleteFile: unlink failed ({$this->getFilePath()})"); + } + } + } /** - * Returns true if virtual file is edited - * - * @param string $p_playlistId - * playlist global unique ID + * Returns true if media file exists * @return boolean */ - public function isEdited() + public function exists() { - + if ($this->_file->isDeleted()) { + return false; + } + if ($this->getFormat() == 'audioclip') { + return $this->existsFile(); + } } /** * Returns true if raw media file exists - * @return boolean|PEAR_Error + * @return boolean */ - public function exists() - { - global $CC_CONFIG, $CC_DBC; - $sql = "SELECT gunid " - ." FROM ".$CC_CONFIG['filesTable'] - ." WHERE gunid='{$this->gunid}'"; - $indb = $CC_DBC->getRow($sql); - if (PEAR::isError($indb)) { - return $indb; - } - if (is_null($indb)) { - return FALSE; - } - if ($this->ftype == 'audioclip') { - return $this->existsFile(); - } - return TRUE; - } - public function existsFile() { - if (!isset($p_md["filepath"]) || !file_exists($p_md['filepath']) || !is_readable($p_md['filepath'])) { + $filepath = $this->_file->getDbFilepath(); + + if (!isset($filepath) || !file_exists($filepath) || !is_readable($filepath)) { return false; } else { @@ -385,6 +335,16 @@ class StoredFile { } } + /** + * Returns true if virtual file is currently in use.
+ * + * @return boolean + */ + public function isAccessed() + { + return ($this->_file->getDbCurrentlyaccessing() > 0); + } + /** * Return suitable extension. * @@ -393,33 +353,14 @@ class StoredFile { */ public function getFileExtension() { + $mime = $this->_file->getDbMime(); - } - - /** - * Get storage-internal file state - * - * @param string $p_gunid - * global unique id of file - * @return string - * see install() - */ - public function getState() - { - - } - - - /** - * Get mnemonic file name - * - * @param string $p_gunid - * global unique id of file - * @return string - */ - public function getName() - { - + if ($mime == "audio/vorbis") { + return "ogg"; + } + else if ($mime == "audio/mp3") { + return "mp3"; + } } /** @@ -427,7 +368,7 @@ class StoredFile { * * @return string */ - public function getRealFilePath() + public function getFilePath() { return $this->_file->getDbFilepath(); } @@ -515,33 +456,31 @@ class StoredFile { public static function searchFilesForPlaylistBuilder($datatables) { global $CC_CONFIG; + $displayData = array("track_title", "artist_name", "album_title", "track_number", "length", "ftype"); + $plSelect = "SELECT "; $fileSelect = "SELECT "; - foreach (Metadata::GetMapMetadataXmlToDb() as $key => $val){ + foreach ($displayData as $key){ - if($key === "dc:title"){ - $plSelect .= "name AS ".$val.", "; - $fileSelect .= $val.", "; + if($key === "track_title"){ + $plSelect .= "name AS ".$key.", "; + $fileSelect .= $key.", "; } - else if ($key === "ls:type"){ - $plSelect .= "'playlist' AS ".$val.", "; - $fileSelect .= $val.", "; + else if ($key === "ftype"){ + $plSelect .= "'playlist' AS ".$key.", "; + $fileSelect .= $key.", "; } - else if ($key === "dc:creator"){ - $plSelect .= "creator AS ".$val.", "; - $fileSelect .= $val.", "; + else if ($key === "artist_name"){ + $plSelect .= "creator AS ".$key.", "; + $fileSelect .= $key.", "; } - else if ($key === "dcterms:extent"){ - $plSelect .= "length, "; - $fileSelect .= "length, "; - } - else if ($key === "dc:description"){ - $plSelect .= "text(description) AS ".$val.", "; - $fileSelect .= $val.", "; + else if ($key === "length"){ + $plSelect .= $key.", "; + $fileSelect .= $key.", "; } else { - $plSelect .= "NULL AS ".$val.", "; - $fileSelect .= $val.", "; + $plSelect .= "NULL AS ".$key.", "; + $fileSelect .= $key.", "; } } From 11d18ad8e87978769389ba7b19f74dac40e1038e Mon Sep 17 00:00:00 2001 From: Naomi Aro Date: Wed, 8 Jun 2011 18:24:17 +0200 Subject: [PATCH 15/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention can drag a bunch of songs into stor, and they are organized and imported to airtime. Need to fix length property. --- .../application/controllers/ApiController.php | 65 ++++++++++++------- .../controllers/PluploadController.php | 8 ++- airtime_mvc/application/models/StoredFile.php | 40 +++--------- python_apps/media-monitor/MediaMonitor.py | 42 +++++++++--- python_apps/media-monitor/media-monitor.cfg | 3 + 5 files changed, 92 insertions(+), 66 deletions(-) diff --git a/airtime_mvc/application/controllers/ApiController.php b/airtime_mvc/application/controllers/ApiController.php index 1f8b5d5ee..a545aaa20 100644 --- a/airtime_mvc/application/controllers/ApiController.php +++ b/airtime_mvc/application/controllers/ApiController.php @@ -10,6 +10,7 @@ class ApiController extends Zend_Controller_Action $context->addActionContext('version', 'json') ->addActionContext('recorded-shows', 'json') ->addActionContext('upload-recorded', 'json') + ->addActionContext('media-item-status', 'json') ->addActionContext('reload-metadata', 'json') ->initContext(); } @@ -293,17 +294,17 @@ class ApiController extends Zend_Controller_Action print 'You are not allowed to access this resource.'; exit; } - + $showCanceled = false; $show_instance = $this->_getParam('show_instance'); - + $upload_dir = ini_get("upload_tmp_dir"); $file = StoredFile::uploadFile($upload_dir); - + $show_name = ""; try { $show_inst = new ShowInstance($show_instance); - + $show_inst->setRecordedFile($file->getId()); $show_name = $show_inst->getName(); $show_genre = $show_inst->getGenre(); @@ -317,12 +318,12 @@ class ApiController extends Zend_Controller_Action //the library), now lets just return. $showCanceled = true; } - + $tmpTitle = !(empty($show_name))?$show_name."-":""; $tmpTitle .= $file->getName(); - + $file->setMetadataValue(UI_MDATA_KEY_TITLE, $tmpTitle); - + if (!$showCanceled && Application_Model_Preference::GetDoSoundCloudUpload()) { for ($i=0; $i<$CC_CONFIG['soundcloud-connection-retries']; $i++) { @@ -353,8 +354,35 @@ class ApiController extends Zend_Controller_Action $this->view->id = $file->getId(); } - public function reloadMetadataAction() { + public function mediaItemStatusAction() { + global $CC_CONFIG; + // disable the view and the layout + $this->view->layout()->disableLayout(); + $this->_helper->viewRenderer->setNoRender(true); + + $api_key = $this->_getParam('api_key'); + if (!in_array($api_key, $CC_CONFIG["apiKey"])) + { + header('HTTP/1.0 401 Unauthorized'); + print 'You are not allowed to access this resource.'; + exit; + } + + $md5 = $this->_getParam('md5'); + $file = StoredFile::RecallByMd5($md5); + + //New file added to Airtime + if (is_null($file)) { + $this->view->airtime_status = 0; + } + else { + $this->view->airtime_status = 1; + } + + } + + public function reloadMetadataAction() { global $CC_CONFIG; $api_key = $this->_getParam('api_key'); @@ -366,29 +394,20 @@ class ApiController extends Zend_Controller_Action } $md = $this->_getParam('md'); - $filepath = $md['filepath']; + $filepath = $md['MDATA_KEY_FILEPATH']; $filepath = str_replace("\\", "", $filepath); - $file = StoredFile::Recall(null, null, null, $filepath); - if (PEAR::isError($file)) { - $this->view->response = "Problem recalling file in Airtime"; - return; - } + $file = StoredFile::RecallByFilepath($filepath); //New file added to Airtime if (is_null($file)) { - + $file = new StoredFile($md); } //Updating a metadata change. else { - $res = $file->replaceDbMetadata($md); - - if (PEAR::isError($res)) { - $this->view->response = "Metadata Change Failed"; - } - else { - $this->view->response = "Success!"; - } + $file->setMetadata($md); } + + $this->view->id = $file->getId(); } } diff --git a/airtime_mvc/application/controllers/PluploadController.php b/airtime_mvc/application/controllers/PluploadController.php index 56c07bc23..d8fc10c08 100644 --- a/airtime_mvc/application/controllers/PluploadController.php +++ b/airtime_mvc/application/controllers/PluploadController.php @@ -25,9 +25,13 @@ class PluploadController extends Zend_Controller_Action public function uploadAction() { $upload_dir = ini_get("upload_tmp_dir") . DIRECTORY_SEPARATOR . "plupload"; - $file = StoredFile::uploadFile($upload_dir); + $res = StoredFile::uploadFile($upload_dir); - die('{"jsonrpc" : "2.0", "id" : '.$file->getId().' }'); + if (isset($res)) { + die('{"jsonrpc" : "2.0", "id" : '.$file->getMessage().' }'); + } + + die('{"jsonrpc" : "2.0"}'); } } diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index c55e67f34..1c8dcef42 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -402,13 +402,19 @@ class StoredFile { $storedFile = CcFilesQuery::create()->findPK(intval($p_id)); } else if (isset($p_gunid)) { - $storedFile = CcFilesQuery::create()->findByDbGunid($p_gunid); + $storedFile = CcFilesQuery::create() + ->filterByDbGunid($p_gunid) + ->findOne(); } else if (isset($p_md5sum)) { - $storedFile = CcFilesQuery::create()->findByDbMd5($p_md5sum); + $storedFile = CcFilesQuery::create() + ->filterByDbMd5($p_md5sum) + ->findOne(); } else if (isset($p_filepath)) { - $storedFile = CcFilesQuery::create()->findByDbFilepath($p_filepath); + $storedFile = CcFilesQuery::create() + ->filterByDbFilepath($p_filepath) + ->findOne(); } else { return null; @@ -697,34 +703,6 @@ class StoredFile { } } - $metadata = Metadata::LoadFromFile($audio_file); - - if (PEAR::isError($metadata)) { - die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": ' + $metadata->getMessage() + '}}'); - } - - // no id3 title tag -> use the original filename for title - if (empty($metadata[UI_MDATA_KEY_TITLE])) { - $metadata[UI_MDATA_KEY_TITLE] = basename($audio_file); - $metadata[UI_MDATA_KEY_FILENAME] = basename($audio_file); - } - - $values = array( - "filename" => basename($audio_file), - "filepath" => $audio_file, - "filetype" => "audioclip", - "mime" => $metadata[UI_MDATA_KEY_FORMAT], - "md5" => $md5 - ); - $storedFile = StoredFile::Insert($values); - - if (PEAR::isError($storedFile)) { - die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": ' + $storedFile->getMessage() + '}}'); - } - - $storedFile->setMetadataBatch($metadata); - - return $storedFile; } } diff --git a/python_apps/media-monitor/MediaMonitor.py b/python_apps/media-monitor/MediaMonitor.py index dcb4793d2..05704e724 100644 --- a/python_apps/media-monitor/MediaMonitor.py +++ b/python_apps/media-monitor/MediaMonitor.py @@ -9,6 +9,7 @@ import sys import hashlib import json import shutil +import math from subprocess import Popen, PIPE, STDOUT @@ -143,9 +144,20 @@ class MediaMonitor(ProcessEvent): return md5 ## mutagen_length is in seconds with the format (d+).dd - ## return format hh:mm:ss. + ## return format hh:mm:ss.uuu def format_length(self, mutagen_length): - time = mutagen_length.split(".") + t = float(mutagen_length) + h = int(math.floor(t/3600)) + + t = t % 3600 + m = int(math.floor(t/60)) + + # will be ss.uuu + s = t % 60 + + length = "%s:%s:%s" % (h, m, s) + + return length def ensure_dir(self, filepath): @@ -210,16 +222,19 @@ class MediaMonitor(ProcessEvent): filepath = self.create_unique_filename(base) self.ensure_dir(filepath) - shutil.move(imported_filepath, filepath) - def update_airtime(self, event): + return filepath + + + def update_airtime(self, filepath): self.logger.info("Updating Change to Airtime") + md = {} try: - md5 = self.get_md5(event.pathname) - md['MDATA_KEY_FILEPATH'] = event.pathname + md5 = self.get_md5(filepath) + md['MDATA_KEY_FILEPATH'] = filepath md['MDATA_KEY_MD5'] = md5 - file_info = mutagen.File(event.pathname, easy=True) + file_info = mutagen.File(filepath, easy=True) attrs = self.mutagen2airtime for key in file_info.keys() : if key in attrs : @@ -227,7 +242,7 @@ class MediaMonitor(ProcessEvent): md['MDATA_KEY_MIME'] = file_info.mime[0] md['MDATA_KEY_BITRATE'] = file_info.info.bitrate - md['MDATA_KEY_SAMPLERATE'] = file_info.info.sample_rated + md['MDATA_KEY_SAMPLERATE'] = file_info.info.sample_rate md['MDATA_KEY_DURATION'] = self.format_length(file_info.info.length) data = {'md': md} @@ -268,14 +283,21 @@ class MediaMonitor(ProcessEvent): #This is a newly imported file. else : if not self.is_renamed_file(event.pathname): - self.create_file_path(event.pathname) + md5 = self.get_md5(event.pathname) + response = self.api_client.check_media_status(md5) + + #this file is new, md5 does not exist in Airtime. + if(response['airtime_status'] == 0): + filepath = self.create_file_path(event.pathname) + shutil.move(event.pathname, filepath) + self.update_airtime(filepath) self.logger.info("%s: %s", event.maskname, event.pathname) def process_IN_MODIFY(self, event): if not event.dir : if self.is_audio_file(event.name) : - self.update_airtime(event) + self.update_airtime(event.pathname) self.logger.info("%s: %s", event.maskname, event.pathname) diff --git a/python_apps/media-monitor/media-monitor.cfg b/python_apps/media-monitor/media-monitor.cfg index 38b44cea7..a590f7c21 100644 --- a/python_apps/media-monitor/media-monitor.cfg +++ b/python_apps/media-monitor/media-monitor.cfg @@ -19,6 +19,9 @@ api_base = 'api' # URL to get the version number of the server API version_url = 'version/api_key/%%api_key%%' +# URL to check Airtime's status of a file +media_status_url = 'media-item-status/format/json/api_key/%%api_key%%/md5/%%md5%%' + # URL to tell Airtime to update file's meta data update_media_url = 'reload-metadata/format/json/api_key/%%api_key%%' From 16cfae10ed8416fd79612b011bc5d369fa4f08da Mon Sep 17 00:00:00 2001 From: Naomi Aro Date: Wed, 8 Jun 2011 19:33:16 +0200 Subject: [PATCH 16/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention working on proper length saving. need to fix substring method to ensure all get same number of subseconds to be consistent. --- airtime_mvc/application/models/StoredFile.php | 1 + python_apps/media-monitor/MediaMonitor.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index 1c8dcef42..7e2102677 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -47,6 +47,7 @@ class StoredFile { { $this->_file = new CcFiles(); $this->_file->setDbGunid(md5(uniqid("", true))); + $this->_file->save(); $this->setMetadata($md); } diff --git a/python_apps/media-monitor/MediaMonitor.py b/python_apps/media-monitor/MediaMonitor.py index 05704e724..3b5899f31 100644 --- a/python_apps/media-monitor/MediaMonitor.py +++ b/python_apps/media-monitor/MediaMonitor.py @@ -148,12 +148,13 @@ class MediaMonitor(ProcessEvent): def format_length(self, mutagen_length): t = float(mutagen_length) h = int(math.floor(t/3600)) - t = t % 3600 m = int(math.floor(t/60)) - # will be ss.uuu s = t % 60 + # will be ss.uuu + s = str(s) + s = s[:6] length = "%s:%s:%s" % (h, m, s) From 1f9a39f22d9cfa13d438da4ed4ff31d74063d57e Mon Sep 17 00:00:00 2001 From: Naomi Aro Date: Thu, 9 Jun 2011 10:28:56 +0200 Subject: [PATCH 17/30] CC-1799Put Airtime Storage into a Human Readable File Naming Convention new API method to check media status. --- .../application/controllers/ApiController.php | 19 ++++++++++++++++ python_apps/api_clients/api_client.py | 22 +++++++++++++++++++ python_apps/media-monitor/MediaMonitor.py | 4 +++- 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/airtime_mvc/application/controllers/ApiController.php b/airtime_mvc/application/controllers/ApiController.php index a545aaa20..8bcec1d4c 100644 --- a/airtime_mvc/application/controllers/ApiController.php +++ b/airtime_mvc/application/controllers/ApiController.php @@ -354,6 +354,25 @@ class ApiController extends Zend_Controller_Action $this->view->id = $file->getId(); } + public function mediaMonitorSetupAction() { + global $CC_CONFIG; + + // disable the view and the layout + $this->view->layout()->disableLayout(); + $this->_helper->viewRenderer->setNoRender(true); + + $api_key = $this->_getParam('api_key'); + if (!in_array($api_key, $CC_CONFIG["apiKey"])) + { + header('HTTP/1.0 401 Unauthorized'); + print 'You are not allowed to access this resource.'; + exit; + } + + $this->view->stor = $CC_CONFIG['storageDir']; + $this->view->plupload = ini_get("upload_tmp_dir") . DIRECTORY_SEPARATOR . "plupload"; + } + public function mediaItemStatusAction() { global $CC_CONFIG; diff --git a/python_apps/api_clients/api_client.py b/python_apps/api_clients/api_client.py index 84e1a4d43..a0381a99e 100644 --- a/python_apps/api_clients/api_client.py +++ b/python_apps/api_clients/api_client.py @@ -117,6 +117,9 @@ class ApiClientInterface: def upload_recorded_show(self): pass + def check_media_status(self, md5): + pass + def update_media_metadata(self, md): pass @@ -356,6 +359,25 @@ class AirTimeApiClient(ApiClientInterface): return response + def check_media_status(self, md5): + logger = logging.getLogger() + + response = None + try: + url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["media_status_url"]) + url = url.replace("%%api_key%%", self.config["api_key"]) + url = url.replace("%%md5%%", md5) + logger.debug(url) + + response = urllib.urlopen(url) + response = json.loads(response.read()) + logger.info("Json Media Status %s", response) + + except Exception, e: + logger.error("Exception: %s", e) + + return response + def update_media_metadata(self, md): logger = logging.getLogger() response = None diff --git a/python_apps/media-monitor/MediaMonitor.py b/python_apps/media-monitor/MediaMonitor.py index 3b5899f31..5a7e07a83 100644 --- a/python_apps/media-monitor/MediaMonitor.py +++ b/python_apps/media-monitor/MediaMonitor.py @@ -332,7 +332,9 @@ if __name__ == '__main__': logger = logging.getLogger('root') logger.info("Added watch to %s", storage_directory) - notifier = AirtimeNotifier(wm, MediaMonitor(), read_freq=int(config["check_filesystem_events"]), timeout=1) + mm = MediaMonitor() + + notifier = AirtimeNotifier(wm, mm, read_freq=int(config["check_filesystem_events"]), timeout=1) notifier.coalesce_events() notifier.loop(callback=checkRabbitMQ) except KeyboardInterrupt: From a4b92fe72d64c4cf1613f5fc72c26484e258a29a Mon Sep 17 00:00:00 2001 From: Naomi Aro Date: Thu, 9 Jun 2011 11:50:03 +0200 Subject: [PATCH 18/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention regression testing with refactored storedfile, having permission problems unlinking a file. --- .../application/controllers/ApiController.php | 4 +- .../controllers/LibraryController.php | 8 ++-- airtime_mvc/application/models/StoredFile.php | 48 ++++++++++++++----- python_apps/media-monitor/MediaMonitor.py | 2 + 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/airtime_mvc/application/controllers/ApiController.php b/airtime_mvc/application/controllers/ApiController.php index 8bcec1d4c..adffb8616 100644 --- a/airtime_mvc/application/controllers/ApiController.php +++ b/airtime_mvc/application/controllers/ApiController.php @@ -78,7 +78,7 @@ class ApiController extends Zend_Controller_Action if (ctype_alnum($file_id) && strlen($file_id) == 32) { $media = StoredFile::RecallByGunid($file_id); if ($media != null && !PEAR::isError($media)) { - $filepath = $media->getRealFilePath(); + $filepath = $media->getFilePath(); if(!is_file($filepath)) { header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found"); @@ -419,7 +419,7 @@ class ApiController extends Zend_Controller_Action //New file added to Airtime if (is_null($file)) { - $file = new StoredFile($md); + $file = StoredFile::Insert($md); } //Updating a metadata change. else { diff --git a/airtime_mvc/application/controllers/LibraryController.php b/airtime_mvc/application/controllers/LibraryController.php index 180402d7c..7af715338 100644 --- a/airtime_mvc/application/controllers/LibraryController.php +++ b/airtime_mvc/application/controllers/LibraryController.php @@ -78,7 +78,7 @@ class LibraryController extends Zend_Controller_Action $file_id = $this->_getParam('id', null); $file = StoredFile::Recall($file_id); - $url = $file->getFileURL().'/api_key/'.$CC_CONFIG["apiKey"][0].'/download/true'; + $url = $file->getFileUrl().'/api_key/'.$CC_CONFIG["apiKey"][0].'/download/true'; $menu[] = array('action' => array('type' => 'gourl', 'url' => $url), 'title' => 'Download'); @@ -162,10 +162,10 @@ class LibraryController extends Zend_Controller_Action if ($form->isValid($request->getPost())) { $formdata = $form->getValues(); - $file->replaceDbMetadata($formdata); + $file->setDbColMetadata($formdata); $data = $formdata; - $data['filepath'] = $file->getRealFilePath(); + $data['filepath'] = $file->getFilePath(); //wait for 1.9.0 release //RabbitMq::SendFileMetaData($data); @@ -173,7 +173,7 @@ class LibraryController extends Zend_Controller_Action } } - $form->populate($file->md); + $form->populate($file->getDbColMetadata()); $this->view->form = $form; } diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index 7e2102677..acde9dae5 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -43,13 +43,9 @@ class StoredFile { "md5" => "DbMd5" ); - public function __construct($md) + public function __construct() { - $this->_file = new CcFiles(); - $this->_file->setDbGunid(md5(uniqid("", true))); - $this->_file->save(); - $this->setMetadata($md); } public function getId() @@ -57,6 +53,10 @@ class StoredFile { return $this->_file->getDbId(); } + public function getGunId() { + return $this->_file->getDbGunid(); + } + public function getFormat() { return $this->_file->getDbFtype(); @@ -380,7 +380,30 @@ class StoredFile { public function getFileUrl() { global $CC_CONFIG; - return "http://$CC_CONFIG[baseUrl]:$CC_CONFIG[basePort]/api/get-media/file/".$this->gunid.".".$this->getFileExtension(); + return "http://$CC_CONFIG[baseUrl]:$CC_CONFIG[basePort]/api/get-media/file/".$this->getGunId().".".$this->getFileExtension(); + } + + public static function Insert($md=null) + { + $file = new CcFiles(); + $file->setDbGunid(md5(uniqid("", true))); + $file->save(); + + if(isset($md)) { + if (preg_match("/mp3/i", $md['MDATA_KEY_MIME'])) { + $file->setDbFtype("audioclip"); + } + else if (preg_match("/vorbis/i", $md['MDATA_KEY_MIME'])) { + $file->setDbFtype("audioclip"); + } + + $this->setMetadata($md); + } + + $storedFile = new StoredFile(); + $storedFile->_file = $file; + + return $storedFile; } /** @@ -394,26 +417,26 @@ class StoredFile { * global unique id of file * @param string $p_md5sum * MD5 sum of the file - * @return StoredFile|Playlist|NULL + * @return StoredFile|NULL * Return NULL if the object doesnt exist in the DB. */ public static function Recall($p_id=null, $p_gunid=null, $p_md5sum=null, $p_filepath=null) { if (isset($p_id)) { - $storedFile = CcFilesQuery::create()->findPK(intval($p_id)); + $file = CcFilesQuery::create()->findPK(intval($p_id)); } else if (isset($p_gunid)) { - $storedFile = CcFilesQuery::create() + $file = CcFilesQuery::create() ->filterByDbGunid($p_gunid) ->findOne(); } else if (isset($p_md5sum)) { - $storedFile = CcFilesQuery::create() + $file = CcFilesQuery::create() ->filterByDbMd5($p_md5sum) ->findOne(); } else if (isset($p_filepath)) { - $storedFile = CcFilesQuery::create() + $file = CcFilesQuery::create() ->filterByDbFilepath($p_filepath) ->findOne(); } @@ -421,6 +444,9 @@ class StoredFile { return null; } + $storedFile = new StoredFile(); + $storedFile->_file = $file; + return $storedFile; } diff --git a/python_apps/media-monitor/MediaMonitor.py b/python_apps/media-monitor/MediaMonitor.py index 5a7e07a83..7fa9d5ee5 100644 --- a/python_apps/media-monitor/MediaMonitor.py +++ b/python_apps/media-monitor/MediaMonitor.py @@ -275,6 +275,8 @@ class MediaMonitor(ProcessEvent): else : return False + def setUpMediaMonitor(self, event): + pass def process_IN_CREATE(self, event): if not event.dir: From 9da314979e25d21b0917da8ad0e4a0b5999c2bd0 Mon Sep 17 00:00:00 2001 From: Naomi Aro Date: Thu, 9 Jun 2011 12:57:30 +0200 Subject: [PATCH 19/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention fixing problem with StoredFile::Insert() --- .../application/controllers/ApiController.php | 5 +---- airtime_mvc/application/models/StoredFile.php | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/airtime_mvc/application/controllers/ApiController.php b/airtime_mvc/application/controllers/ApiController.php index adffb8616..5d241409f 100644 --- a/airtime_mvc/application/controllers/ApiController.php +++ b/airtime_mvc/application/controllers/ApiController.php @@ -376,10 +376,6 @@ class ApiController extends Zend_Controller_Action public function mediaItemStatusAction() { global $CC_CONFIG; - // disable the view and the layout - $this->view->layout()->disableLayout(); - $this->_helper->viewRenderer->setNoRender(true); - $api_key = $this->_getParam('api_key'); if (!in_array($api_key, $CC_CONFIG["apiKey"])) { @@ -415,6 +411,7 @@ class ApiController extends Zend_Controller_Action $md = $this->_getParam('md'); $filepath = $md['MDATA_KEY_FILEPATH']; $filepath = str_replace("\\", "", $filepath); + $file = StoredFile::RecallByFilepath($filepath); //New file added to Airtime diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index acde9dae5..27ecc597d 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -389,6 +389,9 @@ class StoredFile { $file->setDbGunid(md5(uniqid("", true))); $file->save(); + $storedFile = new StoredFile(); + $storedFile->_file = $file; + if(isset($md)) { if (preg_match("/mp3/i", $md['MDATA_KEY_MIME'])) { $file->setDbFtype("audioclip"); @@ -397,12 +400,9 @@ class StoredFile { $file->setDbFtype("audioclip"); } - $this->setMetadata($md); + $storedFile->setMetadata($md); } - $storedFile = new StoredFile(); - $storedFile->_file = $file; - return $storedFile; } @@ -444,10 +444,15 @@ class StoredFile { return null; } - $storedFile = new StoredFile(); - $storedFile->_file = $file; + if (isset($file)) { + $storedFile = new StoredFile(); + $storedFile->_file = $file; - return $storedFile; + return $storedFile; + } + else{ + return null; + } } /** From 2900ef4bb11ae2ab467cf09879e2c8723a30938c Mon Sep 17 00:00:00 2001 From: Naomi Aro Date: Thu, 9 Jun 2011 15:17:17 +0200 Subject: [PATCH 20/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention change for proper file permissions on directories created. --- python_apps/media-monitor/MediaMonitor.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/python_apps/media-monitor/MediaMonitor.py b/python_apps/media-monitor/MediaMonitor.py index 7fa9d5ee5..29542013e 100644 --- a/python_apps/media-monitor/MediaMonitor.py +++ b/python_apps/media-monitor/MediaMonitor.py @@ -164,8 +164,13 @@ class MediaMonitor(ProcessEvent): directory = os.path.dirname(filepath) - if ((not os.path.exists(directory)) or ((os.path.exists(directory) and not os.path.isdir(directory)))): - os.makedirs(directory, 02775) + try: + omask = os.umask(0) + if ((not os.path.exists(directory)) or ((os.path.exists(directory) and not os.path.isdir(directory)))): + os.makedirs(directory, 02775) + finally: + os.umask(omask) + def create_unique_filename(self, filepath): @@ -293,6 +298,7 @@ class MediaMonitor(ProcessEvent): if(response['airtime_status'] == 0): filepath = self.create_file_path(event.pathname) shutil.move(event.pathname, filepath) + #must change self.update_airtime(filepath) self.logger.info("%s: %s", event.maskname, event.pathname) From 79049315c7bfcf2a458e011bcc5f16703170cd7d Mon Sep 17 00:00:00 2001 From: Naomi Aro Date: Thu, 9 Jun 2011 17:50:51 +0200 Subject: [PATCH 21/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention adding way to change user to pypo --- python_apps/media-monitor/MediaMonitor.py | 48 +++++++++++++---------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/python_apps/media-monitor/MediaMonitor.py b/python_apps/media-monitor/MediaMonitor.py index 29542013e..eba290ab5 100644 --- a/python_apps/media-monitor/MediaMonitor.py +++ b/python_apps/media-monitor/MediaMonitor.py @@ -11,6 +11,7 @@ import json import shutil import math +from pwd import getpwnam from subprocess import Popen, PIPE, STDOUT from configobj import ConfigObj @@ -235,27 +236,23 @@ class MediaMonitor(ProcessEvent): def update_airtime(self, filepath): self.logger.info("Updating Change to Airtime") md = {} - try: - md5 = self.get_md5(filepath) - md['MDATA_KEY_FILEPATH'] = filepath - md['MDATA_KEY_MD5'] = md5 + md5 = self.get_md5(filepath) + md['MDATA_KEY_FILEPATH'] = filepath + md['MDATA_KEY_MD5'] = md5 - file_info = mutagen.File(filepath, easy=True) - attrs = self.mutagen2airtime - for key in file_info.keys() : - if key in attrs : - md[attrs[key]] = file_info[key][0] + file_info = mutagen.File(filepath, easy=True) + attrs = self.mutagen2airtime + for key in file_info.keys() : + if key in attrs : + md[attrs[key]] = file_info[key][0] - md['MDATA_KEY_MIME'] = file_info.mime[0] - md['MDATA_KEY_BITRATE'] = file_info.info.bitrate - md['MDATA_KEY_SAMPLERATE'] = file_info.info.sample_rate - md['MDATA_KEY_DURATION'] = self.format_length(file_info.info.length) + md['MDATA_KEY_MIME'] = file_info.mime[0] + md['MDATA_KEY_BITRATE'] = file_info.info.bitrate + md['MDATA_KEY_SAMPLERATE'] = file_info.info.sample_rate + md['MDATA_KEY_DURATION'] = self.format_length(file_info.info.length) - data = {'md': md} - response = self.api_client.update_media_metadata(data) - - except Exception, e: - self.logger.info("%s", e) + data = {'md': md} + response = self.api_client.update_media_metadata(data) def is_renamed_file(self, filename): if filename in self.imported_renamed_files: @@ -297,14 +294,23 @@ class MediaMonitor(ProcessEvent): #this file is new, md5 does not exist in Airtime. if(response['airtime_status'] == 0): filepath = self.create_file_path(event.pathname) - shutil.move(event.pathname, filepath) - #must change + #shutil.move(event.pathname, filepath) + os.rename(event.pathname, filepath) + + try: + #set the owner of the imported file. + pypo_uid = getpwnam('pypo')[2] + os.chown(filepath, pypo_uid, -1) + except Exception, e: + self.logger.debug("Cannot change owner of file.") + self.logger.debug("Error: %s:", e) + self.update_airtime(filepath) self.logger.info("%s: %s", event.maskname, event.pathname) def process_IN_MODIFY(self, event): - if not event.dir : + if not event.dir and os.path.exists(event.pathname): if self.is_audio_file(event.name) : self.update_airtime(event.pathname) From 71d853567e5979c1a751d9acfb677d70e8d8044d Mon Sep 17 00:00:00 2001 From: Naomi Aro Date: Fri, 10 Jun 2011 16:43:30 +0200 Subject: [PATCH 22/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention selecting a mode to prevent some cases where a duplicate could be entered for only 1 file. --- .../application/controllers/ApiController.php | 35 ++++++++---- .../controllers/LibraryController.php | 4 +- airtime_mvc/application/models/RabbitMq.php | 5 -- airtime_mvc/application/models/StoredFile.php | 2 +- python_apps/api_clients/api_client.py | 3 +- python_apps/media-monitor/MediaMonitor.py | 54 ++++++++++++++----- python_apps/media-monitor/media-monitor.cfg | 2 +- 7 files changed, 72 insertions(+), 33 deletions(-) diff --git a/airtime_mvc/application/controllers/ApiController.php b/airtime_mvc/application/controllers/ApiController.php index 5d241409f..f709071c2 100644 --- a/airtime_mvc/application/controllers/ApiController.php +++ b/airtime_mvc/application/controllers/ApiController.php @@ -409,18 +409,35 @@ class ApiController extends Zend_Controller_Action } $md = $this->_getParam('md'); - $filepath = $md['MDATA_KEY_FILEPATH']; - $filepath = str_replace("\\", "", $filepath); + $mode = $this->_getParam('mode'); - $file = StoredFile::RecallByFilepath($filepath); + if ($mode == "create") { + $md5 = $md['MDATA_KEY_MD5']; - //New file added to Airtime - if (is_null($file)) { - $file = StoredFile::Insert($md); + $file = StoredFile::RecallByMd5($md5); + + if (is_null($file)) { + $file = StoredFile::Insert($md); + } + else { + $this->view->error = "File already exists in Airtime."; + return; + } } - //Updating a metadata change. - else { - $file->setMetadata($md); + else if ($mode == "modify") { + $filepath = $md['MDATA_KEY_FILEPATH']; + $filepath = str_replace("\\", "", $filepath); + $file = StoredFile::RecallByFilepath($filepath); + + //File is not in database anymore. + if (is_null($file)) { + $this->view->error = "File does not exist in Airtime."; + return; + } + //Updating a metadata change. + else { + $file->setMetadata($md); + } } $this->view->id = $file->getId(); diff --git a/airtime_mvc/application/controllers/LibraryController.php b/airtime_mvc/application/controllers/LibraryController.php index 7af715338..cc45408bf 100644 --- a/airtime_mvc/application/controllers/LibraryController.php +++ b/airtime_mvc/application/controllers/LibraryController.php @@ -166,8 +166,8 @@ class LibraryController extends Zend_Controller_Action $data = $formdata; $data['filepath'] = $file->getFilePath(); - //wait for 1.9.0 release - //RabbitMq::SendFileMetaData($data); + + RabbitMq::SendFileMetaData($data); $this->_helper->redirector('index'); } diff --git a/airtime_mvc/application/models/RabbitMq.php b/airtime_mvc/application/models/RabbitMq.php index 170395f76..ee70ae6c5 100644 --- a/airtime_mvc/application/models/RabbitMq.php +++ b/airtime_mvc/application/models/RabbitMq.php @@ -40,9 +40,6 @@ class RabbitMq } } -/* -* wait for 1.9.0 release - public static function SendFileMetaData($md) { global $CC_CONFIG; @@ -64,7 +61,5 @@ class RabbitMq $channel->close(); $conn->close(); } -*/ - } diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index 27ecc597d..c8613a052 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -450,7 +450,7 @@ class StoredFile { return $storedFile; } - else{ + else { return null; } } diff --git a/python_apps/api_clients/api_client.py b/python_apps/api_clients/api_client.py index a0381a99e..4a21487c6 100644 --- a/python_apps/api_clients/api_client.py +++ b/python_apps/api_clients/api_client.py @@ -378,13 +378,14 @@ class AirTimeApiClient(ApiClientInterface): return response - def update_media_metadata(self, md): + def update_media_metadata(self, md, mode): logger = logging.getLogger() response = None try: url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["update_media_url"]) logger.debug(url) url = url.replace("%%api_key%%", self.config["api_key"]) + url = url.replace("%%mode%%", mode) data = recursive_urlencode(md) req = urllib2.Request(url, data) diff --git a/python_apps/media-monitor/MediaMonitor.py b/python_apps/media-monitor/MediaMonitor.py index eba290ab5..ac455c9b5 100644 --- a/python_apps/media-monitor/MediaMonitor.py +++ b/python_apps/media-monitor/MediaMonitor.py @@ -25,6 +25,9 @@ from kombu.connection import BrokerConnection from kombu.messaging import Exchange, Queue, Consumer, Producer from api_clients import api_client +MODE_CREATE = "create" +MODE_MODIFY = "modify" + global storage_directory storage_directory = "/srv/airtime/stor" @@ -48,14 +51,6 @@ list of supported easy tags in mutagen version 1.20 ['albumartistsort', 'musicbrainz_albumstatus', 'lyricist', 'releasecountry', 'date', 'performer', 'musicbrainz_albumartistid', 'composer', 'encodedby', 'tracknumber', 'musicbrainz_albumid', 'album', 'asin', 'musicbrainz_artistid', 'mood', 'copyright', 'author', 'media', 'length', 'version', 'artistsort', 'titlesort', 'discsubtitle', 'website', 'musicip_fingerprint', 'conductor', 'compilation', 'barcode', 'performer:*', 'composersort', 'musicbrainz_discid', 'musicbrainz_albumtype', 'genre', 'isrc', 'discnumber', 'musicbrainz_trmid', 'replaygain_*_gain', 'musicip_puid', 'artist', 'title', 'bpm', 'musicbrainz_trackid', 'arranger', 'albumsort', 'replaygain_*_peak', 'organization'] """ - -def checkRabbitMQ(notifier): - try: - notifier.connection.drain_events(timeout=int(config["check_airtime_events"])) - except Exception, e: - logger = logging.getLogger('root') - logger.info("%s", e) - class AirtimeNotifier(Notifier): def __init__(self, watch_manager, default_proc_fun=None, read_freq=0, threshold=0, timeout=None): @@ -136,6 +131,14 @@ class MediaMonitor(ProcessEvent): self.temp_files = {} self.imported_renamed_files = {} + schedule_exchange = Exchange("airtime-media-monitor", "direct", durable=True, auto_delete=True) + schedule_queue = Queue("media-monitor", exchange=schedule_exchange, key="filesystem") + connection = BrokerConnection(config["rabbitmq_host"], config["rabbitmq_user"], config["rabbitmq_password"], "/") + channel = connection.channel() + + self.producer = Producer(channel, exchange=schedule_exchange, serializer="json") + #producer.publish({"name": "/tmp/lolcat1.avi", "size": 1301013}) + def get_md5(self, filepath): f = open(filepath, 'rb') m = hashlib.md5() @@ -233,7 +236,7 @@ class MediaMonitor(ProcessEvent): return filepath - def update_airtime(self, filepath): + def update_airtime(self, filepath, mode): self.logger.info("Updating Change to Airtime") md = {} md5 = self.get_md5(filepath) @@ -252,7 +255,7 @@ class MediaMonitor(ProcessEvent): md['MDATA_KEY_DURATION'] = self.format_length(file_info.info.length) data = {'md': md} - response = self.api_client.update_media_metadata(data) + response = self.api_client.update_media_metadata(data, mode) def is_renamed_file(self, filename): if filename in self.imported_renamed_files: @@ -305,14 +308,14 @@ class MediaMonitor(ProcessEvent): self.logger.debug("Cannot change owner of file.") self.logger.debug("Error: %s:", e) - self.update_airtime(filepath) + self.update_airtime(filepath, MODE_CREATE) self.logger.info("%s: %s", event.maskname, event.pathname) def process_IN_MODIFY(self, event): if not event.dir and os.path.exists(event.pathname): if self.is_audio_file(event.name) : - self.update_airtime(event.pathname) + self.update_airtime(event.pathname, MODE_MODIFY) self.logger.info("%s: %s", event.maskname, event.pathname) @@ -330,14 +333,37 @@ class MediaMonitor(ProcessEvent): self.logger.info("%s: %s", event.maskname, event.pathname) + def process_IN_DELETE(self, event): + + self.producer.publish({"name": "Hi!"}) + + self.logger.info("%s: %s", event.maskname, event.pathname) + + def process_IN_DELETE_SELF(self, event): + + self.logger.info("%s: %s", event.maskname, event.pathname) + def process_default(self, event): self.logger.info("%s: %s", event.maskname, event.pathname) + + def check_rabbit_MQ(self, notifier): + try: + notifier.connection.drain_events(timeout=int(config["check_airtime_events"])) + except Exception, e: + logger = logging.getLogger('root') + logger.info("%s", e) + if __name__ == '__main__': try: # watched events - mask = pyinotify.IN_CREATE | pyinotify.IN_MODIFY | pyinotify.IN_MOVED_FROM | pyinotify.IN_MOVED_TO + mask = pyinotify.IN_CREATE | \ + pyinotify.IN_MODIFY | \ + pyinotify.IN_MOVED_FROM | \ + pyinotify.IN_MOVED_TO | \ + pyinotify.IN_DELETE | \ + pyinotify.IN_DELETE_SELF #mask = pyinotify.ALL_EVENTS wm = WatchManager() @@ -350,7 +376,7 @@ if __name__ == '__main__': notifier = AirtimeNotifier(wm, mm, read_freq=int(config["check_filesystem_events"]), timeout=1) notifier.coalesce_events() - notifier.loop(callback=checkRabbitMQ) + notifier.loop(callback=mm.check_rabbit_MQ) except KeyboardInterrupt: notifier.stop() except Exception, e: diff --git a/python_apps/media-monitor/media-monitor.cfg b/python_apps/media-monitor/media-monitor.cfg index a590f7c21..4a683d130 100644 --- a/python_apps/media-monitor/media-monitor.cfg +++ b/python_apps/media-monitor/media-monitor.cfg @@ -23,7 +23,7 @@ version_url = 'version/api_key/%%api_key%%' media_status_url = 'media-item-status/format/json/api_key/%%api_key%%/md5/%%md5%%' # URL to tell Airtime to update file's meta data -update_media_url = 'reload-metadata/format/json/api_key/%%api_key%%' +update_media_url = 'reload-metadata/format/json/api_key/%%api_key%%/mode/%%mode%%' ############################################ # RabbitMQ settings # From 8d9c0dab1a1faaa3585f8044afce7afb0419ccd0 Mon Sep 17 00:00:00 2001 From: Naomi Aro Date: Mon, 13 Jun 2011 12:10:25 +0200 Subject: [PATCH 23/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention adding setup to get stor folder etc. --- .../application/controllers/ApiController.php | 1 + airtime_mvc/application/models/StoredFile.php | 4 ++- python_apps/api_clients/api_client.py | 19 +++++++++++ python_apps/media-monitor/MediaMonitor.py | 32 ++++++++++++------- python_apps/media-monitor/media-monitor.cfg | 3 ++ 5 files changed, 47 insertions(+), 12 deletions(-) diff --git a/airtime_mvc/application/controllers/ApiController.php b/airtime_mvc/application/controllers/ApiController.php index f709071c2..1ad510f2d 100644 --- a/airtime_mvc/application/controllers/ApiController.php +++ b/airtime_mvc/application/controllers/ApiController.php @@ -10,6 +10,7 @@ class ApiController extends Zend_Controller_Action $context->addActionContext('version', 'json') ->addActionContext('recorded-shows', 'json') ->addActionContext('upload-recorded', 'json') + ->addActionContext('media-monitor-setup', 'json') ->addActionContext('media-item-status', 'json') ->addActionContext('reload-metadata', 'json') ->initContext(); diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index c8613a052..9f6eb7ad8 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -633,7 +633,6 @@ class StoredFile { header("Pragma: no-cache"); // Settings - //$p_targetDir = ini_get("upload_tmp_dir"); //. DIRECTORY_SEPARATOR . "plupload"; $cleanupTargetDir = false; // Remove old files $maxFileAge = 60 * 60; // Temp file age in seconds @@ -734,6 +733,9 @@ class StoredFile { } } } + else { + + } } diff --git a/python_apps/api_clients/api_client.py b/python_apps/api_clients/api_client.py index 4a21487c6..2b2e85430 100644 --- a/python_apps/api_clients/api_client.py +++ b/python_apps/api_clients/api_client.py @@ -359,6 +359,25 @@ class AirTimeApiClient(ApiClientInterface): return response + def setup_media_monitor(self): + logger = logging.getLogger() + + response = None + try: + url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["media_setup_url"]) + url = url.replace("%%api_key%%", self.config["api_key"]) + logger.debug(url) + + response = urllib.urlopen(url) + response = json.loads(response.read()) + logger.debug("Json Media Setup %s", response) + + except Exception, e: + response = None + logger.error("Exception: %s", e) + + return response + def check_media_status(self, md5): logger = logging.getLogger() diff --git a/python_apps/media-monitor/MediaMonitor.py b/python_apps/media-monitor/MediaMonitor.py index ac455c9b5..dae8549d9 100644 --- a/python_apps/media-monitor/MediaMonitor.py +++ b/python_apps/media-monitor/MediaMonitor.py @@ -29,7 +29,7 @@ MODE_CREATE = "create" MODE_MODIFY = "modify" global storage_directory -storage_directory = "/srv/airtime/stor" +global plupload_directory # configure logging try: @@ -136,7 +136,7 @@ class MediaMonitor(ProcessEvent): connection = BrokerConnection(config["rabbitmq_host"], config["rabbitmq_user"], config["rabbitmq_password"], "/") channel = connection.channel() - self.producer = Producer(channel, exchange=schedule_exchange, serializer="json") + #self.producer = Producer(channel, exchange=schedule_exchange, serializer="json") #producer.publish({"name": "/tmp/lolcat1.avi", "size": 1301013}) def get_md5(self, filepath): @@ -280,9 +280,6 @@ class MediaMonitor(ProcessEvent): else : return False - def setUpMediaMonitor(self, event): - pass - def process_IN_CREATE(self, event): if not event.dir: #file created is a tmp file which will be modified and then moved back to the original filename. @@ -335,7 +332,7 @@ class MediaMonitor(ProcessEvent): def process_IN_DELETE(self, event): - self.producer.publish({"name": "Hi!"}) + #self.producer.publish({"name": "Hi!"}) self.logger.info("%s: %s", event.maskname, event.pathname) @@ -366,14 +363,27 @@ if __name__ == '__main__': pyinotify.IN_DELETE_SELF #mask = pyinotify.ALL_EVENTS - wm = WatchManager() - wdd = wm.add_watch(storage_directory, mask, rec=True, auto_add=True) - logger = logging.getLogger('root') - logger.info("Added watch to %s", storage_directory) - mm = MediaMonitor() + response = None + while response is None: + response = mm.api_client.setup_media_monitor() + time.sleep(5) + + storage_directory = response["stor"] + plupload_directory = response["plupload"] + + wm = WatchManager() + + wdd = wm.add_watch(storage_directory, mask, rec=True, auto_add=True) + logger.info("Added watch to %s", storage_directory) + logger.info("wdd result %s", wdd[storage_directory]) + + wdd = wm.add_watch(plupload_directory, mask, rec=False, auto_add=True) + logger.info("Added watch to %s", plupload_directory) + logger.info("wdd result %s", wdd[plupload_directory]) + notifier = AirtimeNotifier(wm, mm, read_freq=int(config["check_filesystem_events"]), timeout=1) notifier.coalesce_events() notifier.loop(callback=mm.check_rabbit_MQ) diff --git a/python_apps/media-monitor/media-monitor.cfg b/python_apps/media-monitor/media-monitor.cfg index 4a683d130..e1f36619f 100644 --- a/python_apps/media-monitor/media-monitor.cfg +++ b/python_apps/media-monitor/media-monitor.cfg @@ -19,6 +19,9 @@ api_base = 'api' # URL to get the version number of the server API version_url = 'version/api_key/%%api_key%%' +# URL to setup the media monitor +media_setup_url = 'media-monitor-setup/format/json/api_key/%%api_key%%' + # URL to check Airtime's status of a file media_status_url = 'media-item-status/format/json/api_key/%%api_key%%/md5/%%md5%%' From 8a15144dc8bbf09332fe5cef9b06adc2eb3bdc33 Mon Sep 17 00:00:00 2001 From: Naomi Aro Date: Mon, 13 Jun 2011 12:41:30 +0200 Subject: [PATCH 24/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention experimenting with plupload. --- airtime_mvc/application/models/StoredFile.php | 5 ++++- python_apps/media-monitor/MediaMonitor.py | 17 ++++++----------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index 9f6eb7ad8..4758535da 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -734,7 +734,10 @@ class StoredFile { } } else { - + global $CC_CONFIG; + $stor = $CC_CONFIG["storageDir"]; + $audio_stor = $stor . DIRECTORY_SEPARATOR . $fileName; + $r = @copy($audio_file, $audio_stor); } } diff --git a/python_apps/media-monitor/MediaMonitor.py b/python_apps/media-monitor/MediaMonitor.py index dae8549d9..20fe59214 100644 --- a/python_apps/media-monitor/MediaMonitor.py +++ b/python_apps/media-monitor/MediaMonitor.py @@ -29,7 +29,6 @@ MODE_CREATE = "create" MODE_MODIFY = "modify" global storage_directory -global plupload_directory # configure logging try: @@ -297,13 +296,13 @@ class MediaMonitor(ProcessEvent): #shutil.move(event.pathname, filepath) os.rename(event.pathname, filepath) - try: + #try: #set the owner of the imported file. - pypo_uid = getpwnam('pypo')[2] - os.chown(filepath, pypo_uid, -1) - except Exception, e: - self.logger.debug("Cannot change owner of file.") - self.logger.debug("Error: %s:", e) + #pypo_uid = getpwnam('pypo')[2] + #os.chown(filepath, pypo_uid, -1) + #except Exception, e: + #self.logger.debug("Cannot change owner of file.") + #self.logger.debug("Error: %s:", e) self.update_airtime(filepath, MODE_CREATE) @@ -380,10 +379,6 @@ if __name__ == '__main__': logger.info("Added watch to %s", storage_directory) logger.info("wdd result %s", wdd[storage_directory]) - wdd = wm.add_watch(plupload_directory, mask, rec=False, auto_add=True) - logger.info("Added watch to %s", plupload_directory) - logger.info("wdd result %s", wdd[plupload_directory]) - notifier = AirtimeNotifier(wm, mm, read_freq=int(config["check_filesystem_events"]), timeout=1) notifier.coalesce_events() notifier.loop(callback=mm.check_rabbit_MQ) From 84dd5dac62121ef88c17856f0a8caeb4197412a0 Mon Sep 17 00:00:00 2001 From: Naomi Aro Date: Mon, 13 Jun 2011 16:41:57 +0200 Subject: [PATCH 25/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention delete is working, can upload from plupload. --- .../application/controllers/ApiController.php | 13 ++ airtime_mvc/application/models/StoredFile.php | 15 +-- python_apps/api_clients/api_client.py | 1 + python_apps/media-monitor/MediaMonitor.py | 124 ++++++++---------- 4 files changed, 73 insertions(+), 80 deletions(-) diff --git a/airtime_mvc/application/controllers/ApiController.php b/airtime_mvc/application/controllers/ApiController.php index 1ad510f2d..1d9f821ec 100644 --- a/airtime_mvc/application/controllers/ApiController.php +++ b/airtime_mvc/application/controllers/ApiController.php @@ -440,6 +440,19 @@ class ApiController extends Zend_Controller_Action $file->setMetadata($md); } } + else if ($mode == "delete") { + $filepath = $md['MDATA_KEY_FILEPATH']; + $filepath = str_replace("\\", "", $filepath); + $file = StoredFile::RecallByFilepath($filepath); + + if (is_null($file)) { + $this->view->error = "File doesn't exist in Airtime."; + return; + } + else { + $file->delete(); + } + } $this->view->id = $file->getId(); } diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index 4758535da..73145c9e3 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -259,19 +259,16 @@ class StoredFile { */ public function delete() { - if (!$this->exists()) { - return PEAR::raiseError('File does not exist.'); - } - - if ($this->getFormat() == 'audioclip') { - $res = $this->deleteFile(); - if (PEAR::isError($res)) { - return $res; + if ($this->exists()) { + if ($this->getFormat() == 'audioclip') { + $res = $this->deleteFile(); + if (PEAR::isError($res)) { + return $res; + } } } Playlist::DeleteFileFromAllPlaylists($this->getId()); - $this->_file->delete(); } diff --git a/python_apps/api_clients/api_client.py b/python_apps/api_clients/api_client.py index 2b2e85430..016041255 100644 --- a/python_apps/api_clients/api_client.py +++ b/python_apps/api_clients/api_client.py @@ -414,6 +414,7 @@ class AirTimeApiClient(ApiClientInterface): response = json.loads(response) except Exception, e: + response = None logger.error("Exception: %s", e) return response diff --git a/python_apps/media-monitor/MediaMonitor.py b/python_apps/media-monitor/MediaMonitor.py index 20fe59214..8c9ddb18e 100644 --- a/python_apps/media-monitor/MediaMonitor.py +++ b/python_apps/media-monitor/MediaMonitor.py @@ -11,6 +11,7 @@ import json import shutil import math +from collections import deque from pwd import getpwnam from subprocess import Popen, PIPE, STDOUT @@ -27,6 +28,7 @@ from api_clients import api_client MODE_CREATE = "create" MODE_MODIFY = "modify" +MODE_DELETE = "delete" global storage_directory @@ -128,16 +130,13 @@ class MediaMonitor(ProcessEvent): self.supported_file_formats = ['mp3', 'ogg'] self.logger = logging.getLogger('root') self.temp_files = {} - self.imported_renamed_files = {} + self.file_events = deque() schedule_exchange = Exchange("airtime-media-monitor", "direct", durable=True, auto_delete=True) schedule_queue = Queue("media-monitor", exchange=schedule_exchange, key="filesystem") connection = BrokerConnection(config["rabbitmq_host"], config["rabbitmq_user"], config["rabbitmq_password"], "/") channel = connection.channel() - #self.producer = Producer(channel, exchange=schedule_exchange, serializer="json") - #producer.publish({"name": "/tmp/lolcat1.avi", "size": 1301013}) - def get_md5(self, filepath): f = open(filepath, 'rb') m = hashlib.md5() @@ -191,8 +190,6 @@ class MediaMonitor(ProcessEvent): else: filepath = new_filepath - self.imported_renamed_files[filepath] = 0 - return filepath def create_file_path(self, imported_filepath): @@ -236,39 +233,40 @@ class MediaMonitor(ProcessEvent): def update_airtime(self, filepath, mode): - self.logger.info("Updating Change to Airtime") - md = {} - md5 = self.get_md5(filepath) - md['MDATA_KEY_FILEPATH'] = filepath - md['MDATA_KEY_MD5'] = md5 - file_info = mutagen.File(filepath, easy=True) - attrs = self.mutagen2airtime - for key in file_info.keys() : - if key in attrs : - md[attrs[key]] = file_info[key][0] + if ((os.path.exists(filepath) and (mode == MODE_CREATE or mode == MODE_MODIFY)) or mode == MODE_DELETE): + self.logger.info("Updating Change to Airtime") + md = {} + md['MDATA_KEY_FILEPATH'] = filepath - md['MDATA_KEY_MIME'] = file_info.mime[0] - md['MDATA_KEY_BITRATE'] = file_info.info.bitrate - md['MDATA_KEY_SAMPLERATE'] = file_info.info.sample_rate - md['MDATA_KEY_DURATION'] = self.format_length(file_info.info.length) + if(mode == MODE_CREATE or mode == MODE_MODIFY): + md5 = self.get_md5(filepath) + md['MDATA_KEY_MD5'] = md5 - data = {'md': md} - response = self.api_client.update_media_metadata(data, mode) + file_info = mutagen.File(filepath, easy=True) + attrs = self.mutagen2airtime + for key in file_info.keys() : + if key in attrs : + md[attrs[key]] = file_info[key][0] - def is_renamed_file(self, filename): - if filename in self.imported_renamed_files: - del self.imported_renamed_files[filename] - return True + md['MDATA_KEY_MIME'] = file_info.mime[0] + md['MDATA_KEY_BITRATE'] = file_info.info.bitrate + md['MDATA_KEY_SAMPLERATE'] = file_info.info.sample_rate + md['MDATA_KEY_DURATION'] = self.format_length(file_info.info.length) - return False + data = {'md': md} + + response = None + while response is None: + response = self.api_client.update_media_metadata(data, mode) + time.sleep(5) def is_temp_file(self, filename): info = filename.split(".") if(info[-2] in self.supported_file_formats): return True - else : + else: return False def is_audio_file(self, filename): @@ -276,74 +274,60 @@ class MediaMonitor(ProcessEvent): if(info[-1] in self.supported_file_formats): return True - else : + else: return False def process_IN_CREATE(self, event): if not event.dir: + self.logger.info("%s: %s", event.maskname, event.pathname) #file created is a tmp file which will be modified and then moved back to the original filename. if self.is_temp_file(event.name) : self.temp_files[event.pathname] = None #This is a newly imported file. else : - if not self.is_renamed_file(event.pathname): - md5 = self.get_md5(event.pathname) - response = self.api_client.check_media_status(md5) + md5 = self.get_md5(event.pathname) + response = self.api_client.check_media_status(md5) - #this file is new, md5 does not exist in Airtime. - if(response['airtime_status'] == 0): - filepath = self.create_file_path(event.pathname) - #shutil.move(event.pathname, filepath) - os.rename(event.pathname, filepath) - - #try: - #set the owner of the imported file. - #pypo_uid = getpwnam('pypo')[2] - #os.chown(filepath, pypo_uid, -1) - #except Exception, e: - #self.logger.debug("Cannot change owner of file.") - #self.logger.debug("Error: %s:", e) - - self.update_airtime(filepath, MODE_CREATE) - - self.logger.info("%s: %s", event.maskname, event.pathname) + #this file is new, md5 does not exist in Airtime. + if(response['airtime_status'] == 0): + filepath = self.create_file_path(event.pathname) + self.file_events.append({'old_filepath': event.pathname, 'mode': MODE_CREATE, 'filepath': filepath}) def process_IN_MODIFY(self, event): - if not event.dir and os.path.exists(event.pathname): - if self.is_audio_file(event.name) : - self.update_airtime(event.pathname, MODE_MODIFY) - + if not event.dir: self.logger.info("%s: %s", event.maskname, event.pathname) + if self.is_audio_file(event.name) : + self.file_events.append({'filepath': event.pathname, 'mode': MODE_MODIFY}) def process_IN_MOVED_FROM(self, event): - if event.pathname in self.temp_files : + self.logger.info("%s: %s", event.maskname, event.pathname) + if event.pathname in self.temp_files: del self.temp_files[event.pathname] self.temp_files[event.cookie] = event.pathname - self.logger.info("%s: %s", event.maskname, event.pathname) - def process_IN_MOVED_TO(self, event): - if event.cookie in self.temp_files : - del self.temp_files[event.cookie] - self.update_airtime(event) - self.logger.info("%s: %s", event.maskname, event.pathname) + if event.cookie in self.temp_files: + del self.temp_files[event.cookie] + self.file_events.append({'filepath': event.pathname, 'mode': MODE_MODIFY}) def process_IN_DELETE(self, event): - - #self.producer.publish({"name": "Hi!"}) - - self.logger.info("%s: %s", event.maskname, event.pathname) - - def process_IN_DELETE_SELF(self, event): - self.logger.info("%s: %s", event.maskname, event.pathname) + self.file_events.append({'filepath': event.pathname, 'mode': MODE_DELETE}) def process_default(self, event): self.logger.info("%s: %s", event.maskname, event.pathname) + def notifier_loop_callback(self, notifier): + + while len(self.file_events) > 0: + file_info = self.file_events.popleft() + + if(file_info['mode'] == MODE_CREATE): + os.rename(file_info['old_filepath'], file_info['filepath']) + + self.update_airtime(file_info['filepath'], file_info['mode']) - def check_rabbit_MQ(self, notifier): try: notifier.connection.drain_events(timeout=int(config["check_airtime_events"])) except Exception, e: @@ -371,17 +355,15 @@ if __name__ == '__main__': time.sleep(5) storage_directory = response["stor"] - plupload_directory = response["plupload"] wm = WatchManager() - wdd = wm.add_watch(storage_directory, mask, rec=True, auto_add=True) logger.info("Added watch to %s", storage_directory) logger.info("wdd result %s", wdd[storage_directory]) notifier = AirtimeNotifier(wm, mm, read_freq=int(config["check_filesystem_events"]), timeout=1) notifier.coalesce_events() - notifier.loop(callback=mm.check_rabbit_MQ) + notifier.loop(callback=mm.notifier_loop_callback) except KeyboardInterrupt: notifier.stop() except Exception, e: From 51b1f00ac9d4cd05792325d034e27d24d725c8e4 Mon Sep 17 00:00:00 2001 From: Naomi Aro Date: Mon, 13 Jun 2011 18:38:11 +0200 Subject: [PATCH 26/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention starting to better work with renamed files. --- python_apps/media-monitor/MediaMonitor.py | 53 ++++++++++++++--------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/python_apps/media-monitor/MediaMonitor.py b/python_apps/media-monitor/MediaMonitor.py index 8c9ddb18e..00bc5206e 100644 --- a/python_apps/media-monitor/MediaMonitor.py +++ b/python_apps/media-monitor/MediaMonitor.py @@ -130,13 +130,26 @@ class MediaMonitor(ProcessEvent): self.supported_file_formats = ['mp3', 'ogg'] self.logger = logging.getLogger('root') self.temp_files = {} + self.moved_files = {} self.file_events = deque() + self.mask = pyinotify.IN_CREATE | \ + pyinotify.IN_MODIFY | \ + pyinotify.IN_MOVED_FROM | \ + pyinotify.IN_MOVED_TO | \ + pyinotify.IN_DELETE | \ + pyinotify.IN_DELETE_SELF + + self.wm = WatchManager() + schedule_exchange = Exchange("airtime-media-monitor", "direct", durable=True, auto_delete=True) schedule_queue = Queue("media-monitor", exchange=schedule_exchange, key="filesystem") connection = BrokerConnection(config["rabbitmq_host"], config["rabbitmq_user"], config["rabbitmq_password"], "/") channel = connection.channel() + def watch_directory(self, directory): + return self.wm.add_watch(directory, self.mask, rec=True, auto_add=True) + def get_md5(self, filepath): f = open(filepath, 'rb') m = hashlib.md5() @@ -163,24 +176,22 @@ class MediaMonitor(ProcessEvent): return length def ensure_dir(self, filepath): - directory = os.path.dirname(filepath) try: omask = os.umask(0) if ((not os.path.exists(directory)) or ((os.path.exists(directory) and not os.path.isdir(directory)))): os.makedirs(directory, 02775) + self.watch_directory(directory) finally: os.umask(omask) - def create_unique_filename(self, filepath): - file_dir = os.path.dirname(filepath) - filename = os.path.basename(filepath).split(".")[0] - file_ext = os.path.splitext(filepath)[1] - if(os.path.exists(filepath)): + file_dir = os.path.dirname(filepath) + filename = os.path.basename(filepath).split(".")[0] + file_ext = os.path.splitext(filepath)[1] i = 1; while(True): new_filepath = "%s/%s(%s).%s" % (file_dir, filename, i, file_ext) @@ -329,7 +340,7 @@ class MediaMonitor(ProcessEvent): self.update_airtime(file_info['filepath'], file_info['mode']) try: - notifier.connection.drain_events(timeout=int(config["check_airtime_events"])) + notifier.connection.drain_events(timeout=1) except Exception, e: logger = logging.getLogger('root') logger.info("%s", e) @@ -337,15 +348,6 @@ class MediaMonitor(ProcessEvent): if __name__ == '__main__': try: - # watched events - mask = pyinotify.IN_CREATE | \ - pyinotify.IN_MODIFY | \ - pyinotify.IN_MOVED_FROM | \ - pyinotify.IN_MOVED_TO | \ - pyinotify.IN_DELETE | \ - pyinotify.IN_DELETE_SELF - #mask = pyinotify.ALL_EVENTS - logger = logging.getLogger('root') mm = MediaMonitor() @@ -355,15 +357,26 @@ if __name__ == '__main__': time.sleep(5) storage_directory = response["stor"] + plupload_directory = response["plupload"] - wm = WatchManager() - wdd = wm.add_watch(storage_directory, mask, rec=True, auto_add=True) + wdd = mm.watch_directory(storage_directory) logger.info("Added watch to %s", storage_directory) logger.info("wdd result %s", wdd[storage_directory]) + wdd = mm.watch_directory(plupload_directory) + logger.info("Added watch to %s", plupload_directory) + logger.info("wdd result %s", wdd[plupload_directory]) - notifier = AirtimeNotifier(wm, mm, read_freq=int(config["check_filesystem_events"]), timeout=1) + notifier = AirtimeNotifier(mm.wm, mm, read_freq=int(config["check_filesystem_events"]), timeout=1) notifier.coalesce_events() - notifier.loop(callback=mm.notifier_loop_callback) + + #notifier.loop(callback=mm.notifier_loop_callback) + + while True: + if(notifier.check_events(1)): + notifier.read_events() + notifier.process_events() + mm.notifier_loop_callback(notifier) + except KeyboardInterrupt: notifier.stop() except Exception, e: From 17d895861d31e557a9c7b0995209ba6d316bbba3 Mon Sep 17 00:00:00 2001 From: Naomi Aro Date: Tue, 14 Jun 2011 12:07:21 +0200 Subject: [PATCH 27/30] CC-1799 Put Airtime Storage into a Human Readable File Naming ConventionPut Airtime Storage into a Human Readable File Naming Convention put initial stub file in when a user uploads via plupload so they aren't wondering why they don't see anything in their playlist builder. Metadata will be updated when the file event completes. --- .../application/controllers/ApiController.php | 13 +++ airtime_mvc/application/models/StoredFile.php | 22 +++-- python_apps/media-monitor/MediaMonitor.py | 84 +++++++++++++------ 3 files changed, 87 insertions(+), 32 deletions(-) diff --git a/airtime_mvc/application/controllers/ApiController.php b/airtime_mvc/application/controllers/ApiController.php index 1d9f821ec..48f667c84 100644 --- a/airtime_mvc/application/controllers/ApiController.php +++ b/airtime_mvc/application/controllers/ApiController.php @@ -440,6 +440,19 @@ class ApiController extends Zend_Controller_Action $file->setMetadata($md); } } + else if ($mode == "moved") { + $filepath = $md['MDATA_KEY_FILEPATH']; + $filepath = str_replace("\\", "", $filepath); + $file = StoredFile::RecallByFilepath($filepath); + + if (is_null($file)) { + $this->view->error = "File doesn't exist in Airtime."; + return; + } + else { + $file->setMetadataValue('MDATA_KEY_FILEPATH', $md['new_filepath']); + } + } else if ($mode == "delete") { $filepath = $md['MDATA_KEY_FILEPATH']; $filepath = str_replace("\\", "", $filepath); diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index 73145c9e3..87366138e 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -390,11 +390,13 @@ class StoredFile { $storedFile->_file = $file; if(isset($md)) { - if (preg_match("/mp3/i", $md['MDATA_KEY_MIME'])) { - $file->setDbFtype("audioclip"); - } - else if (preg_match("/vorbis/i", $md['MDATA_KEY_MIME'])) { - $file->setDbFtype("audioclip"); + if(isset($md['MDATA_KEY_MIME'])) { + if (preg_match("/mp3/i", $md['MDATA_KEY_MIME'])) { + $file->setDbFtype("audioclip"); + } + else if (preg_match("/vorbis/i", $md['MDATA_KEY_MIME'])) { + $file->setDbFtype("audioclip"); + } } $storedFile->setMetadata($md); @@ -731,10 +733,18 @@ class StoredFile { } } else { + global $CC_CONFIG; $stor = $CC_CONFIG["storageDir"]; $audio_stor = $stor . DIRECTORY_SEPARATOR . $fileName; - $r = @copy($audio_file, $audio_stor); + $r = @rename($audio_file, $audio_stor); + + $md = array(); + $md['MDATA_KEY_MD5'] = $md5; + $md['MDATA_KEY_FILEPATH'] = $audio_stor; + $md['MDATA_KEY_TITLE'] = $fileName; + + StoredFile::Insert($md); } } diff --git a/python_apps/media-monitor/MediaMonitor.py b/python_apps/media-monitor/MediaMonitor.py index 00bc5206e..e7fcdc86d 100644 --- a/python_apps/media-monitor/MediaMonitor.py +++ b/python_apps/media-monitor/MediaMonitor.py @@ -28,9 +28,11 @@ from api_clients import api_client MODE_CREATE = "create" MODE_MODIFY = "modify" +MODE_MOVED = "moved" MODE_DELETE = "delete" global storage_directory +global plupload_directory # configure logging try: @@ -150,6 +152,9 @@ class MediaMonitor(ProcessEvent): def watch_directory(self, directory): return self.wm.add_watch(directory, self.mask, rec=True, auto_add=True) + def is_parent_directory(self, filepath, directory): + return (directory == filepath[0:len(directory)]) + def get_md5(self, filepath): f = open(filepath, 'rb') m = hashlib.md5() @@ -242,31 +247,50 @@ class MediaMonitor(ProcessEvent): return filepath + def get_mutagen_info(self, filepath): + md = {} + md5 = self.get_md5(filepath) + md['MDATA_KEY_MD5'] = md5 - def update_airtime(self, filepath, mode): + file_info = mutagen.File(filepath, easy=True) + attrs = self.mutagen2airtime + for key in file_info.keys() : + if key in attrs : + md[attrs[key]] = file_info[key][0] - if ((os.path.exists(filepath) and (mode == MODE_CREATE or mode == MODE_MODIFY)) or mode == MODE_DELETE): - self.logger.info("Updating Change to Airtime") - md = {} - md['MDATA_KEY_FILEPATH'] = filepath + md['MDATA_KEY_MIME'] = file_info.mime[0] + md['MDATA_KEY_BITRATE'] = file_info.info.bitrate + md['MDATA_KEY_SAMPLERATE'] = file_info.info.sample_rate + md['MDATA_KEY_DURATION'] = self.format_length(file_info.info.length) - if(mode == MODE_CREATE or mode == MODE_MODIFY): - md5 = self.get_md5(filepath) - md['MDATA_KEY_MD5'] = md5 + return md - file_info = mutagen.File(filepath, easy=True) - attrs = self.mutagen2airtime - for key in file_info.keys() : - if key in attrs : - md[attrs[key]] = file_info[key][0] - md['MDATA_KEY_MIME'] = file_info.mime[0] - md['MDATA_KEY_BITRATE'] = file_info.info.bitrate - md['MDATA_KEY_SAMPLERATE'] = file_info.info.sample_rate - md['MDATA_KEY_DURATION'] = self.format_length(file_info.info.length) + def update_airtime(self, **kwargs): + filepath = kwargs['filepath'] + mode = kwargs['mode'] + + data = None + md = {} + md['MDATA_KEY_FILEPATH'] = filepath + + if (os.path.exists(filepath) and (mode == MODE_CREATE)): + mutagen = self.get_mutagen_info(filepath) + md.update(mutagen) + data = {'md': md} + elif (os.path.exists(filepath) and (mode == MODE_MODIFY)): + mutagen = self.get_mutagen_info(filepath) + md.update(mutagen) + data = {'md': md} + elif (mode == MODE_MOVED): + md['new_filepath'] = kwargs['new_filepath'] + data = {'md': md} + elif (mode == MODE_DELETE): data = {'md': md} + if data is not None: + self.logger.info("Updating Change to Airtime") response = None while response is None: response = self.api_client.update_media_metadata(data, mode) @@ -296,13 +320,16 @@ class MediaMonitor(ProcessEvent): self.temp_files[event.pathname] = None #This is a newly imported file. else : - md5 = self.get_md5(event.pathname) - response = self.api_client.check_media_status(md5) + global plupload_directory + #files that have been added through plupload have a placeholder already put in Airtime's database. + if not self.is_parent_directory(event.pathname, plupload_directory) + md5 = self.get_md5(event.pathname) + response = self.api_client.check_media_status(md5) - #this file is new, md5 does not exist in Airtime. - if(response['airtime_status'] == 0): - filepath = self.create_file_path(event.pathname) - self.file_events.append({'old_filepath': event.pathname, 'mode': MODE_CREATE, 'filepath': filepath}) + #this file is new, md5 does not exist in Airtime. + if(response['airtime_status'] == 0): + filepath = self.create_file_path(event.pathname) + self.file_events.append({'old_filepath': event.pathname, 'mode': MODE_CREATE, 'filepath': filepath}) def process_IN_MODIFY(self, event): if not event.dir: @@ -315,12 +342,18 @@ class MediaMonitor(ProcessEvent): if event.pathname in self.temp_files: del self.temp_files[event.pathname] self.temp_files[event.cookie] = event.pathname + else: + self.moved_files[event.cookie] = event.pathname def process_IN_MOVED_TO(self, event): self.logger.info("%s: %s", event.maskname, event.pathname) if event.cookie in self.temp_files: del self.temp_files[event.cookie] self.file_events.append({'filepath': event.pathname, 'mode': MODE_MODIFY}) + elif event.cookie in self.moved_files: + old_filepath = self.moved_files[event.cookie] + del self.moved_files[event.cookie] + self.file_events.append({'filepath': old_filepath,'new_filepath': event.pathname, 'mode': MODE_MOVED}) def process_IN_DELETE(self, event): self.logger.info("%s: %s", event.maskname, event.pathname) @@ -337,13 +370,12 @@ class MediaMonitor(ProcessEvent): if(file_info['mode'] == MODE_CREATE): os.rename(file_info['old_filepath'], file_info['filepath']) - self.update_airtime(file_info['filepath'], file_info['mode']) + self.update_airtime(file_info) try: notifier.connection.drain_events(timeout=1) except Exception, e: - logger = logging.getLogger('root') - logger.info("%s", e) + self.logger.info("%s", e) if __name__ == '__main__': From af4ec7b8727e8396c60281faca03a8b90ba735c9 Mon Sep 17 00:00:00 2001 From: Naomi Aro Date: Tue, 14 Jun 2011 18:35:07 +0200 Subject: [PATCH 28/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention plupload is now working how I want with moving files. stub files are added then their metadata updated as the web receives info. --- .../application/controllers/ApiController.php | 17 +++++---- airtime_mvc/application/models/StoredFile.php | 2 +- python_apps/media-monitor/MediaMonitor.py | 35 +++++++++++++------ 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/airtime_mvc/application/controllers/ApiController.php b/airtime_mvc/application/controllers/ApiController.php index 48f667c84..c6d7dfc13 100644 --- a/airtime_mvc/application/controllers/ApiController.php +++ b/airtime_mvc/application/controllers/ApiController.php @@ -370,8 +370,15 @@ class ApiController extends Zend_Controller_Action exit; } + $plupload_dir = ini_get("upload_tmp_dir") . DIRECTORY_SEPARATOR . "plupload"; + + //need to make sure plupload dir exists so we can watch it. + if(!file_exists($plupload_dir)) { + @mkdir($plupload_dir, 0755); + } + $this->view->stor = $CC_CONFIG['storageDir']; - $this->view->plupload = ini_get("upload_tmp_dir") . DIRECTORY_SEPARATOR . "plupload"; + $this->view->plupload = $plupload_dir; } public function mediaItemStatusAction() { @@ -414,7 +421,6 @@ class ApiController extends Zend_Controller_Action if ($mode == "create") { $md5 = $md['MDATA_KEY_MD5']; - $file = StoredFile::RecallByMd5($md5); if (is_null($file)) { @@ -441,16 +447,15 @@ class ApiController extends Zend_Controller_Action } } else if ($mode == "moved") { - $filepath = $md['MDATA_KEY_FILEPATH']; - $filepath = str_replace("\\", "", $filepath); - $file = StoredFile::RecallByFilepath($filepath); + $md5 = $md['MDATA_KEY_MD5']; + $file = StoredFile::RecallByMd5($md5); if (is_null($file)) { $this->view->error = "File doesn't exist in Airtime."; return; } else { - $file->setMetadataValue('MDATA_KEY_FILEPATH', $md['new_filepath']); + $file->setMetadata($md); } } else if ($mode == "delete") { diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index 87366138e..a9a5d82ff 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -737,7 +737,6 @@ class StoredFile { global $CC_CONFIG; $stor = $CC_CONFIG["storageDir"]; $audio_stor = $stor . DIRECTORY_SEPARATOR . $fileName; - $r = @rename($audio_file, $audio_stor); $md = array(); $md['MDATA_KEY_MD5'] = $md5; @@ -745,6 +744,7 @@ class StoredFile { $md['MDATA_KEY_TITLE'] = $fileName; StoredFile::Insert($md); + $r = @rename($audio_file, $audio_stor); } } diff --git a/python_apps/media-monitor/MediaMonitor.py b/python_apps/media-monitor/MediaMonitor.py index e7fcdc86d..02fd21b71 100644 --- a/python_apps/media-monitor/MediaMonitor.py +++ b/python_apps/media-monitor/MediaMonitor.py @@ -266,10 +266,10 @@ class MediaMonitor(ProcessEvent): return md - def update_airtime(self, **kwargs): + def update_airtime(self, d): - filepath = kwargs['filepath'] - mode = kwargs['mode'] + filepath = d['filepath'] + mode = d['mode'] data = None md = {} @@ -284,7 +284,8 @@ class MediaMonitor(ProcessEvent): md.update(mutagen) data = {'md': md} elif (mode == MODE_MOVED): - md['new_filepath'] = kwargs['new_filepath'] + mutagen = self.get_mutagen_info(filepath) + md.update(mutagen) data = {'md': md} elif (mode == MODE_DELETE): data = {'md': md} @@ -322,7 +323,7 @@ class MediaMonitor(ProcessEvent): else : global plupload_directory #files that have been added through plupload have a placeholder already put in Airtime's database. - if not self.is_parent_directory(event.pathname, plupload_directory) + if not self.is_parent_directory(event.pathname, plupload_directory): md5 = self.get_md5(event.pathname) response = self.api_client.check_media_status(md5) @@ -334,8 +335,11 @@ class MediaMonitor(ProcessEvent): def process_IN_MODIFY(self, event): if not event.dir: self.logger.info("%s: %s", event.maskname, event.pathname) - if self.is_audio_file(event.name) : - self.file_events.append({'filepath': event.pathname, 'mode': MODE_MODIFY}) + global plupload_directory + #files that have been added through plupload have a placeholder already put in Airtime's database. + if not self.is_parent_directory(event.pathname, plupload_directory): + if self.is_audio_file(event.name) : + self.file_events.append({'filepath': event.pathname, 'mode': MODE_MODIFY}) def process_IN_MOVED_FROM(self, event): self.logger.info("%s: %s", event.maskname, event.pathname) @@ -353,11 +357,22 @@ class MediaMonitor(ProcessEvent): elif event.cookie in self.moved_files: old_filepath = self.moved_files[event.cookie] del self.moved_files[event.cookie] - self.file_events.append({'filepath': old_filepath,'new_filepath': event.pathname, 'mode': MODE_MOVED}) + + global plupload_directory + #add a modify event to send md to Airtime for the plupload file. + if self.is_parent_directory(old_filepath, plupload_directory): + #file renamed from /tmp/plupload does not have a path in our naming scheme yet. + md_filepath = self.create_file_path(event.pathname) + #move the file a second time to its correct Airtime naming schema. + os.rename(event.pathname, md_filepath) + self.file_events.append({'filepath': md_filepath, 'mode': MODE_MOVED}) + else: + self.file_events.append({'filepath': event.pathname, 'mode': MODE_MOVED}) def process_IN_DELETE(self, event): - self.logger.info("%s: %s", event.maskname, event.pathname) - self.file_events.append({'filepath': event.pathname, 'mode': MODE_DELETE}) + if not event.dir: + self.logger.info("%s: %s", event.maskname, event.pathname) + self.file_events.append({'filepath': event.pathname, 'mode': MODE_DELETE}) def process_default(self, event): self.logger.info("%s: %s", event.maskname, event.pathname) From ff61972e82382db5d0ffa8baf70399fb6d107a7a Mon Sep 17 00:00:00 2001 From: Naomi Aro Date: Tue, 14 Jun 2011 18:52:43 +0200 Subject: [PATCH 29/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention changing ftype calculation to the python script. --- airtime_mvc/application/configs/constants.php | 1 + airtime_mvc/application/models/StoredFile.php | 12 ++---------- python_apps/media-monitor/MediaMonitor.py | 7 ++++++- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/airtime_mvc/application/configs/constants.php b/airtime_mvc/application/configs/constants.php index dcb381b43..df5ef7741 100644 --- a/airtime_mvc/application/configs/constants.php +++ b/airtime_mvc/application/configs/constants.php @@ -12,6 +12,7 @@ define('MDATA_KEY_CREATOR', 'artist_name'); define('MDATA_KEY_SOURCE', 'album_title'); define('MDATA_KEY_DURATION', 'length'); define('MDATA_KEY_MIME', 'mime'); +define('MDATA_KEY_FTYPE', 'ftype'); define('MDATA_KEY_URL', 'url'); define('MDATA_KEY_GENRE', 'genre'); define('MDATA_KEY_MOOD', 'mood'); diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index a9a5d82ff..3cafb1640 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -40,7 +40,8 @@ class StoredFile { "sample_rate" => "DbSampleRate", "mime" => "DbMime", "filepath" => "DbFilepath", - "md5" => "DbMd5" + "md5" => "DbMd5", + "ftype" => "DbFtype" ); public function __construct() @@ -390,15 +391,6 @@ class StoredFile { $storedFile->_file = $file; if(isset($md)) { - if(isset($md['MDATA_KEY_MIME'])) { - if (preg_match("/mp3/i", $md['MDATA_KEY_MIME'])) { - $file->setDbFtype("audioclip"); - } - else if (preg_match("/vorbis/i", $md['MDATA_KEY_MIME'])) { - $file->setDbFtype("audioclip"); - } - } - $storedFile->setMetadata($md); } diff --git a/python_apps/media-monitor/MediaMonitor.py b/python_apps/media-monitor/MediaMonitor.py index 02fd21b71..46c167cd2 100644 --- a/python_apps/media-monitor/MediaMonitor.py +++ b/python_apps/media-monitor/MediaMonitor.py @@ -258,10 +258,15 @@ class MediaMonitor(ProcessEvent): if key in attrs : md[attrs[key]] = file_info[key][0] - md['MDATA_KEY_MIME'] = file_info.mime[0] md['MDATA_KEY_BITRATE'] = file_info.info.bitrate md['MDATA_KEY_SAMPLERATE'] = file_info.info.sample_rate md['MDATA_KEY_DURATION'] = self.format_length(file_info.info.length) + md['MDATA_KEY_MIME'] = file_info.mime[0] + + if "mp3" in md['MDATA_KEY_MIME']: + md['MDATA_KEY_FTYPE'] = "audioclip" + elif "vorbis" in md['MDATA_KEY_MIME']: + md['MDATA_KEY_FTYPE'] = "audioclip" return md From c06b15b9649f2b31bf2b6bb9e15ad44a44a719a2 Mon Sep 17 00:00:00 2001 From: Naomi Aro Date: Wed, 15 Jun 2011 09:19:41 +0200 Subject: [PATCH 30/30] CC-1799 Put Airtime Storage into a Human Readable File Naming Convention about to merge, need to test more with blank metadata/change paths according to spec. Should test case about moving to trash from UI. --- airtime_mvc/application/models/StoredFile.php | 9 +++ install/airtime-install | 4 +- install/airtime-install.php | 2 - install/include/AirtimeInstall.php | 55 +++++++------ python_apps/media-monitor/MediaMonitor.py | 78 +++++++++++-------- 5 files changed, 83 insertions(+), 65 deletions(-) diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index 3cafb1640..3e27f4393 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -104,6 +104,10 @@ class StoredFile { } else { foreach ($p_md as $dbColumn => $mdValue) { + //don't blank out name, defaults to original filename on first insertion to database. + if($p_category == "track_title" && (is_null($p_value) || $p_value == "")) { + continue; + } $propelColumn = $this->_dbMD[$dbColumn]; $method = "set$propelColumn"; $this->_file->$method($mdValue); @@ -136,6 +140,10 @@ class StoredFile { */ public function setDbColMetadataValue($p_category, $p_value) { + //don't blank out name, defaults to original filename on first insertion to database. + if($p_category == "track_title" && (is_null($p_value) || $p_value == "")) { + return; + } $propelColumn = $this->_dbMD[$p_category]; $method = "set$propelColumn"; $this->_file->$method($p_value); @@ -736,6 +744,7 @@ class StoredFile { $md['MDATA_KEY_TITLE'] = $fileName; StoredFile::Insert($md); + $r = @chmod($audio_file, 0666); $r = @rename($audio_file, $audio_stor); } diff --git a/install/airtime-install b/install/airtime-install index b86f03ad8..bc2187d68 100755 --- a/install/airtime-install +++ b/install/airtime-install @@ -11,11 +11,11 @@ SCRIPTPATH=`dirname $SCRIPT` echo -e "\n******************************** Install Begin *********************************" -php ${SCRIPTPATH}/airtime-install.php $@ - echo -e "\n*** Creating Pypo User ***" python ${SCRIPTPATH}/../python_apps/create-pypo-user.py +php ${SCRIPTPATH}/airtime-install.php $@ + echo -e "\n*** Pypo Installation ***" python ${SCRIPTPATH}/../python_apps/pypo/install/pypo-install.py diff --git a/install/airtime-install.php b/install/airtime-install.php index 8a0a6f741..cb4a0c90f 100644 --- a/install/airtime-install.php +++ b/install/airtime-install.php @@ -121,8 +121,6 @@ if ($db_install) { AirtimeInstall::InstallStorageDirectory(); -AirtimeInstall::ChangeDirOwnerToWebserver($CC_CONFIG["storageDir"]); - AirtimeInstall::CreateSymlinksToUtils(); AirtimeInstall::CreateZendPhpLogFile(); diff --git a/install/include/AirtimeInstall.php b/install/include/AirtimeInstall.php index c1d4a9e7a..dbbff57b6 100644 --- a/install/include/AirtimeInstall.php +++ b/install/include/AirtimeInstall.php @@ -116,42 +116,39 @@ class AirtimeInstall } } - public static function ChangeDirOwnerToWebserver($filePath) - { - global $CC_CONFIG; - echo "* Giving Apache permission to access $filePath".PHP_EOL; - - $success = chgrp($filePath, $CC_CONFIG["webServerUser"]); - $fileperms=@fileperms($filePath); - $fileperms = $fileperms | 0x0010; // group write bit - $fileperms = $fileperms | 0x0400; // group sticky bit - chmod($filePath, $fileperms); - } - public static function InstallStorageDirectory() { global $CC_CONFIG, $CC_DBC; echo "* Storage directory setup".PHP_EOL; - foreach (array('baseFilesDir', 'storageDir') as $d) { - if ( !file_exists($CC_CONFIG[$d]) ) { - @mkdir($CC_CONFIG[$d], 02775, true); - if (file_exists($CC_CONFIG[$d])) { - $rp = realpath($CC_CONFIG[$d]); - echo "* Directory $rp created".PHP_EOL; - } else { - echo "* Failed creating {$CC_CONFIG[$d]}".PHP_EOL; - exit(1); - } - } elseif (is_writable($CC_CONFIG[$d])) { - $rp = realpath($CC_CONFIG[$d]); - echo "* Skipping directory already exists: $rp".PHP_EOL; + $stor_dir = $CC_CONFIG['storageDir']; + + if (!file_exists($stor_dir)) { + @mkdir($stor_dir, 02777, true); + if (file_exists($stor_dir)) { + $rp = realpath($stor_dir); + echo "* Directory $rp created".PHP_EOL; } else { - $rp = realpath($CC_CONFIG[$d]); - echo "* WARNING: Directory already exists, but is not writable: $rp".PHP_EOL; + echo "* Failed creating {$stor_dir}".PHP_EOL; + exit(1); } - $CC_CONFIG[$d] = $rp; } + else if (is_writable($stor_dir)) { + $rp = realpath($stor_dir); + echo "* Skipping directory already exists: $rp".PHP_EOL; + } + else { + $rp = realpath($stor_dir); + echo "* WARNING: Directory already exists, but is not writable: $rp".PHP_EOL; + return; + } + + echo "* Giving Apache permission to access $rp".PHP_EOL; + $success = chgrp($rp, $CC_CONFIG["webServerUser"]); + $success = chown($rp, "pypo"); + $success = chmod($rp, 02777); + $CC_CONFIG['storageDir'] = $rp; + } public static function CreateDatabaseUser() @@ -300,7 +297,7 @@ class AirtimeInstall echo "* Installing airtime-update-db-settings".PHP_EOL; $dir = AirtimeInstall::CONF_DIR_BINARIES."/utils/airtime-update-db-settings"; exec("ln -s $dir /usr/bin/airtime-update-db-settings"); - + echo "* Installing airtime-check-system".PHP_EOL; $dir = AirtimeInstall::CONF_DIR_BINARIES."/utils/airtime-check-system"; exec("ln -s $dir /usr/bin/airtime-check-system"); diff --git a/python_apps/media-monitor/MediaMonitor.py b/python_apps/media-monitor/MediaMonitor.py index 46c167cd2..a909e2874 100644 --- a/python_apps/media-monitor/MediaMonitor.py +++ b/python_apps/media-monitor/MediaMonitor.py @@ -186,7 +186,7 @@ class MediaMonitor(ProcessEvent): try: omask = os.umask(0) if ((not os.path.exists(directory)) or ((os.path.exists(directory) and not os.path.isdir(directory)))): - os.makedirs(directory, 02775) + os.makedirs(directory, 02777) self.watch_directory(directory) finally: os.umask(omask) @@ -212,38 +212,49 @@ class MediaMonitor(ProcessEvent): global storage_directory - original_name = os.path.basename(imported_filepath) - file_ext = os.path.splitext(imported_filepath)[1] - file_info = mutagen.File(imported_filepath, easy=True) + try: + #get rid of file extention from original name, name might have more than 1 '.' in it. + original_name = os.path.basename(imported_filepath) + #self.logger.info('original name: %s', original_name) + original_name = original_name.split(".")[0:-1] + #self.logger.info('original name: %s', original_name) + original_name = ''.join(original_name) + #self.logger.info('original name: %s', original_name) - metadata = {'artist':None, - 'album':None, - 'title':None, - 'tracknumber':None} + file_ext = os.path.splitext(imported_filepath)[1] + file_info = mutagen.File(imported_filepath, easy=True) - for key in metadata.keys(): - if key in file_info: - metadata[key] = file_info[key][0] + metadata = {'artist':None, + 'album':None, + 'title':None, + 'tracknumber':None} - if metadata['artist'] is not None: - base = "%s/%s" % (storage_directory, metadata['artist']) - if metadata['album'] is not None: - base = "%s/%s" % (base, metadata['album']) - if metadata['title'] is not None: - if metadata['tracknumber'] is not None: - metadata['tracknumber'] = "%02d" % (int(metadata['tracknumber'])) - base = "%s/%s - %s" % (base, metadata['tracknumber'], metadata['title']) + for key in metadata.keys(): + if key in file_info: + metadata[key] = file_info[key][0] + + if metadata['artist'] is not None: + base = "%s/%s" % (storage_directory, metadata['artist']) + if metadata['album'] is not None: + base = "%s/%s" % (base, metadata['album']) + if metadata['title'] is not None: + if metadata['tracknumber'] is not None: + metadata['tracknumber'] = "%02d" % (int(metadata['tracknumber'])) + base = "%s/%s - %s" % (base, metadata['tracknumber'], metadata['title']) + else: + base = "%s/%s" % (base, metadata['title']) else: - base = "%s/%s" % (base, metadata['title']) + base = "%s/%s" % (base, original_name) else: - base = "%s/%s" % (base, original_name) - else: - base = "%s/%s" % (storage_directory, original_name) + base = "%s/%s" % (storage_directory, original_name) - base = "%s%s" % (base, file_ext) + base = "%s%s" % (base, file_ext) - filepath = self.create_unique_filename(base) - self.ensure_dir(filepath) + filepath = self.create_unique_filename(base) + self.ensure_dir(filepath) + + except Exception, e: + self.logger.error('Exception: %s', e) return filepath @@ -335,7 +346,8 @@ class MediaMonitor(ProcessEvent): #this file is new, md5 does not exist in Airtime. if(response['airtime_status'] == 0): filepath = self.create_file_path(event.pathname) - self.file_events.append({'old_filepath': event.pathname, 'mode': MODE_CREATE, 'filepath': filepath}) + os.rename(event.pathname, filepath) + self.file_events.append({'mode': MODE_CREATE, 'filepath': filepath}) def process_IN_MODIFY(self, event): if not event.dir: @@ -364,7 +376,6 @@ class MediaMonitor(ProcessEvent): del self.moved_files[event.cookie] global plupload_directory - #add a modify event to send md to Airtime for the plupload file. if self.is_parent_directory(old_filepath, plupload_directory): #file renamed from /tmp/plupload does not have a path in our naming scheme yet. md_filepath = self.create_file_path(event.pathname) @@ -374,6 +385,13 @@ class MediaMonitor(ProcessEvent): else: self.file_events.append({'filepath': event.pathname, 'mode': MODE_MOVED}) + else: + #TODO need to pass in if md5 exists to this file creation function, identical files will just replace current files not have a (1) etc. + #file has been most likely dropped into stor folder from an unwatched location. (from gui, mv command not cp) + md_filepath = self.create_file_path(event.pathname) + os.rename(event.pathname, md_filepath) + self.file_events.append({'mode': MODE_CREATE, 'filepath': md_filepath}) + def process_IN_DELETE(self, event): if not event.dir: self.logger.info("%s: %s", event.maskname, event.pathname) @@ -386,10 +404,6 @@ class MediaMonitor(ProcessEvent): while len(self.file_events) > 0: file_info = self.file_events.popleft() - - if(file_info['mode'] == MODE_CREATE): - os.rename(file_info['old_filepath'], file_info['filepath']) - self.update_airtime(file_info) try: