diff --git a/airtime_mvc/application/controllers/ApiController.php b/airtime_mvc/application/controllers/ApiController.php index 6572774cc..1a1876ce9 100644 --- a/airtime_mvc/application/controllers/ApiController.php +++ b/airtime_mvc/application/controllers/ApiController.php @@ -490,6 +490,10 @@ class ApiController extends Zend_Controller_Action $file->setFileExistsFlag(true); $file->setMetadata($md); } + if ($md['is_record'] != 0) { + $this->uploadRecordedActionParam($md['MDATA_KEY_TRACKNUMBER'], $file->getId()); + } + } elseif ($mode == "modify") { $filepath = $md['MDATA_KEY_FILEPATH']; $file = Application_Model_StoredFile::RecallByFilepath($filepath); @@ -562,7 +566,6 @@ class ApiController extends Zend_Controller_Action // least 1 digit if ( !preg_match('/^md\d+$/', $k) ) { continue; } $info_json = json_decode($raw_json, $assoc = true); - unset( $info_json["is_record"] ); // Log invalid requests if ( !array_key_exists('mode', $info_json) ) { Logging::info("Received bad request(key=$k), no 'mode' parameter. Bad request is:"); diff --git a/airtime_mvc/application/forms/SmartBlockCriteria.php b/airtime_mvc/application/forms/SmartBlockCriteria.php index 59c375bf7..55a28c79a 100644 --- a/airtime_mvc/application/forms/SmartBlockCriteria.php +++ b/airtime_mvc/application/forms/SmartBlockCriteria.php @@ -454,7 +454,7 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm $column = CcFilesPeer::getTableMap()->getColumnByPhpName($criteria2PeerMap[$d['sp_criteria_field']]); // validation on type of column if ($d['sp_criteria_field'] == 'length') { - if (!preg_match("/(\d{2}):(\d{2}):(\d{2})/", $d['sp_criteria_value'])) { + if (!preg_match("/^(\d{2}):(\d{2}):(\d{2})/", $d['sp_criteria_value'])) { $element->addError("'Length' should be in '00:00:00' format"); $isValid = false; } diff --git a/airtime_mvc/application/models/Schedule.php b/airtime_mvc/application/models/Schedule.php index 1102efaff..1f2e98810 100644 --- a/airtime_mvc/application/models/Schedule.php +++ b/airtime_mvc/application/models/Schedule.php @@ -713,6 +713,7 @@ SQL; 'end' => $stream_end, 'uri' => $uri, 'type' => 'stream_buffer_end', + 'row_id' => $item["id"], 'independent_event' => true ); self::appendScheduleItem($data, $stream_end, $schedule_item); @@ -1127,7 +1128,6 @@ SQL; } } else { if ($isAdminOrPM) { - Logging::info( $data ); Application_Model_Show::create($data); } diff --git a/airtime_mvc/application/models/ShowBuilder.php b/airtime_mvc/application/models/ShowBuilder.php index e1e2382aa..90a67a1d6 100644 --- a/airtime_mvc/application/models/ShowBuilder.php +++ b/airtime_mvc/application/models/ShowBuilder.php @@ -198,7 +198,9 @@ class Application_Model_ShowBuilder } elseif (intval($p_item["si_record"]) === 1) { $row["record"] = true; - if (Application_Model_Preference::GetUploadToSoundcloudOption()) { + // at the time of creating on show, the recorded file is not in the DB yet. + // therefore, 'si_file_id' is null. So we need to check it. + if (Application_Model_Preference::GetUploadToSoundcloudOption() && isset($p_item['si_file_id'])) { $file = Application_Model_StoredFile::Recall( $p_item['si_file_id']); if (isset($file)) { @@ -398,7 +400,7 @@ class Application_Model_ShowBuilder //see if the displayed show instances have changed. (deleted, //empty schedule etc) - if ($outdated === false && count($instances) + if ($outdated === false && count($instances) !== count($currentInstances)) { Logging::debug("show instances have changed."); $outdated = true; @@ -459,7 +461,7 @@ class Application_Model_ShowBuilder $display_items[] = $row; } - if ($current_id !== -1 && + if ($current_id !== -1 && !in_array($current_id, $this->showInstances)) { $this->showInstances[] = $current_id; } diff --git a/airtime_mvc/application/models/airtime/CcSchedule.php b/airtime_mvc/application/models/airtime/CcSchedule.php index e051df6c3..db7fb19a7 100644 --- a/airtime_mvc/application/models/airtime/CcSchedule.php +++ b/airtime_mvc/application/models/airtime/CcSchedule.php @@ -127,9 +127,9 @@ class CcSchedule extends BaseCcSchedule { } if ($microsecond == 0) { - $this->fadein = $dt->format('H:i:s.u'); + $this->fade_in = $dt->format('H:i:s.u'); } else { - $this->fadein = $dt->format('H:i:s').".".$microsecond; + $this->fade_in = $dt->format('H:i:s').".".$microsecond; } $this->modifiedColumns[] = CcSchedulePeer::FADE_IN; @@ -164,9 +164,9 @@ class CcSchedule extends BaseCcSchedule { } if ($microsecond == 0) { - $this->fadeout = $dt->format('H:i:s.u'); + $this->fade_out = $dt->format('H:i:s.u'); } else { - $this->fadeout = $dt->format('H:i:s').".".$microsecond; + $this->fade_out = $dt->format('H:i:s').".".$microsecond; } $this->modifiedColumns[] = CcSchedulePeer::FADE_OUT; diff --git a/airtime_mvc/public/js/airtime/showbuilder/builder.js b/airtime_mvc/public/js/airtime/showbuilder/builder.js index 9839df06c..fc8463481 100644 --- a/airtime_mvc/public/js/airtime/showbuilder/builder.js +++ b/airtime_mvc/public/js/airtime/showbuilder/builder.js @@ -283,7 +283,7 @@ var AIRTIME = (function(AIRTIME){ mod.fnRemove = function(aItems) { mod.disableUI(); - if (confirm("Delete selected item(s)?")) { + if (confirm("Remove selected scheduled item(s)?")) { $.post( "/showbuilder/schedule-remove", {"items": aItems, "format": "json"}, mod.fnItemCallback diff --git a/install_minimal/upgrades/airtime-2.2.0/common/UpgradeCommon.php b/install_minimal/upgrades/airtime-2.2.0/common/UpgradeCommon.php index 77c297a65..496a576a2 100644 --- a/install_minimal/upgrades/airtime-2.2.0/common/UpgradeCommon.php +++ b/install_minimal/upgrades/airtime-2.2.0/common/UpgradeCommon.php @@ -111,6 +111,10 @@ class UpgradeCommon{ $old = "list_all_db_files = 'list-all-files/format/json/api_key/%%api_key%%/dir_id/%%dir_id%%'"; $new = "list_all_db_files = 'list-all-files/format/json/api_key/%%api_key%%/dir_id/%%dir_id%%/all/%%all%%'"; exec("sed -i \"s#$old#$new#g\" /etc/airtime/api_client.cfg"); + + $old = "update_start_playing_url = 'notify-media-item-start-play/api_key/%%api_key%%/media_id/%%media_id%%/schedule_id/%%schedule_id%%'"; + $new = "update_start_playing_url = 'notify-media-item-start-play/api_key/%%api_key%%/media_id/%%media_id%%/'"; + exec("sed -i \"s#$old#$new#g\" /etc/airtime/api_client.cfg"); } /** diff --git a/python_apps/media-monitor2/media/monitor/events.py b/python_apps/media-monitor2/media/monitor/events.py index a8ccc3b54..0b2a92e14 100644 --- a/python_apps/media-monitor2/media/monitor/events.py +++ b/python_apps/media-monitor2/media/monitor/events.py @@ -199,6 +199,7 @@ class NewFile(BaseEvent, HasMetaData): """ req_dict = self.metadata.extract() req_dict['mode'] = u'create' + req_dict['is_record'] = self.metadata.is_recorded() self.assign_owner(req_dict) req_dict['MDATA_KEY_FILEPATH'] = unicode( self.path ) return [req_dict] diff --git a/python_apps/pypo/liquidsoap_scripts/ls_lib.liq b/python_apps/pypo/liquidsoap_scripts/ls_lib.liq index 4e9167638..9e7903751 100644 --- a/python_apps/pypo/liquidsoap_scripts/ls_lib.liq +++ b/python_apps/pypo/liquidsoap_scripts/ls_lib.liq @@ -402,6 +402,11 @@ def set_dynamic_source_id(id) = string_of(!current_dyn_id) end +def get_dynamic_source_id() = + string_of(!current_dyn_id) +end + + # Function to create a playlist source and output it. def create_dynamic_source(uri) = # The playlist source @@ -413,7 +418,7 @@ def create_dynamic_source(uri) = # We register both source and output # in the list of sources dyn_sources := - list.append([(uri,s),(uri,active_dyn_out)], !dyn_sources) + list.append([(!current_dyn_id, s),(!current_dyn_id, active_dyn_out)], !dyn_sources) notify([("schedule_table_id", !current_dyn_id)]) "Done!" @@ -421,7 +426,62 @@ end # A function to destroy a dynamic source -def destroy_dynamic_source_all(uri) = +def destroy_dynamic_source(id) = + # We need to find the source in the list, + # remove it and destroy it. Currently, the language + # lacks some nice operators for that so we do it + # the functional way + + # This function is executed on every item in the list + # of dynamic sources + def parse_list(ret, current_element) = + # ret is of the form: (matching_sources, remaining_sources) + # We extract those two: + matching_sources = fst(ret) + remaining_sources = snd(ret) + + # current_element is of the form: ("uri", source) so + # we check the first element + current_id = fst(current_element) + if current_id == id then + # In this case, we add the source to the list of + # matched sources + (list.append( [snd(current_element)], + matching_sources), + remaining_sources) + else + # In this case, we put the element in the list of remaining + # sources + (matching_sources, + list.append([current_element], + remaining_sources)) + end + end + + # Now we execute the function: + result = list.fold(parse_list, ([], []), !dyn_sources) + matching_sources = fst(result) + remaining_sources = snd(result) + + # We store the remaining sources in dyn_sources + dyn_sources := remaining_sources + + # If no source matched, we return an error + if list.length(matching_sources) == 0 then + "Error: no matching sources!" + else + # We stop all sources + list.iter(source.shutdown, matching_sources) + # And return + "Done!" + end +end + + + + +# A function to destroy a dynamic source +def destroy_dynamic_source_all() = # We need to find the source in the list, # remove it and destroy it. Currently, the language # lacks some nice operators for that so we do it @@ -466,57 +526,3 @@ end - -# A function to destroy a dynamic source -def destroy_dynamic_source(uri) = - # We need to find the source in the list, - # remove it and destroy it. Currently, the language - # lacks some nice operators for that so we do it - # the functional way - - # This function is executed on every item in the list - # of dynamic sources - def parse_list(ret, current_element) = - # ret is of the form: (matching_sources, remaining_sources) - # We extract those two: - matching_sources = fst(ret) - remaining_sources = snd(ret) - - # current_element is of the form: ("uri", source) so - # we check the first element - current_uri = fst(current_element) - if current_uri == uri then - # In this case, we add the source to the list of - # matched sources - (list.append( [snd(current_element)], - matching_sources), - remaining_sources) - else - # In this case, we put the element in the list of remaining - # sources - (matching_sources, - list.append([current_element], - remaining_sources)) - end - end - - # Now we execute the function: - result = list.fold(parse_list, ([], []), !dyn_sources) - matching_sources = fst(result) - remaining_sources = snd(result) - - # We store the remaining sources in dyn_sources - dyn_sources := remaining_sources - - # If no source matched, we return an error - if list.length(matching_sources) == 0 then - "Error: no matching sources!" - else - # We stop all sources - list.iter(source.shutdown, matching_sources) - # And return - "Done!" - end -end - - diff --git a/python_apps/pypo/liquidsoap_scripts/ls_script.liq b/python_apps/pypo/liquidsoap_scripts/ls_script.liq index fbb8e9870..7a6d8c61d 100644 --- a/python_apps/pypo/liquidsoap_scripts/ls_script.liq +++ b/python_apps/pypo/liquidsoap_scripts/ls_script.liq @@ -19,7 +19,7 @@ queue = amplify(1., override="replay_gain", queue) #live stream setup set("harbor.bind_addr", "0.0.0.0") -current_dyn_id = ref '' +current_dyn_id = ref '-1' pypo_data = ref '0' stream_metadata_type = ref 0 @@ -95,21 +95,28 @@ server.register(namespace="dynamic_source", usage="id ", "id", fun (s) -> begin log("dynamic_source.id") set_dynamic_source_id(s) end) + +server.register(namespace="dynamic_source", + description="Get the cc_schedule row id", + usage="get_id", + "get_id", + fun (s) -> begin log("dynamic_source.get_id") get_dynamic_source_id() end) + server.register(namespace="dynamic_source", description="Start a new dynamic source.", usage="start ", "read_start", - fun (s) -> begin log("dynamic_source.read_start") create_dynamic_source(s) end) + fun (uri) -> begin log("dynamic_source.read_start") create_dynamic_source(uri) end) server.register(namespace="dynamic_source", description="Stop a dynamic source.", - usage="stop ", + usage="stop ", "read_stop", fun (s) -> begin log("dynamic_source.read_stop") destroy_dynamic_source(s) end) server.register(namespace="dynamic_source", description="Stop a dynamic source.", - usage="stop ", + usage="stop ", "read_stop_all", - fun (s) -> begin log("dynamic_source.read_stop") destroy_dynamic_source_all(s) end) + fun (s) -> begin log("dynamic_source.read_stop") destroy_dynamic_source_all() end) default = amplify(id="silence_src", 0.00001, noise()) default = rewrite_metadata([("artist","Airtime"), ("title", "offline")], default) diff --git a/python_apps/pypo/pypopush.py b/python_apps/pypo/pypopush.py index 87e1704bf..5bcce21b6 100644 --- a/python_apps/pypo/pypopush.py +++ b/python_apps/pypo/pypopush.py @@ -55,7 +55,6 @@ class PypoPush(Thread): self.pushed_objects = {} self.logger = logging.getLogger('push') - self.current_stream_info = None self.current_prebuffering_stream_id = None def main(self): @@ -78,6 +77,7 @@ class PypoPush(Thread): #We get to the following lines only if a schedule was received. liquidsoap_queue_approx = self.get_queue_items_from_liquidsoap() + liquidsoap_stream_id = self.get_current_stream_id_from_liquidsoap() tnow = datetime.utcnow() current_event_chain, original_chain = self.get_current_chain(chains, tnow) @@ -92,7 +92,7 @@ class PypoPush(Thread): #is scheduled. We need to verify whether the schedule we just received matches #what Liquidsoap is playing, and if not, correct it. - self.handle_new_schedule(media_schedule, liquidsoap_queue_approx, current_event_chain) + self.handle_new_schedule(media_schedule, liquidsoap_queue_approx, liquidsoap_stream_id, current_event_chain) #At this point everything in the present has been taken care of and Liquidsoap @@ -134,6 +134,25 @@ class PypoPush(Thread): loops = 0 loops += 1 + def get_current_stream_id_from_liquidsoap(self): + response = "-1" + try: + self.telnet_lock.acquire() + tn = telnetlib.Telnet(LS_HOST, LS_PORT) + + msg = 'dynamic_source.get_id\n' + tn.write(msg) + response = tn.read_until("\r\n").strip(" \r\n") + tn.write('exit\n') + tn.read_all() + except Exception, e: + self.logger.error("Error connecting to Liquidsoap: %s", e) + response = [] + finally: + self.telnet_lock.release() + + return response + def get_queue_items_from_liquidsoap(self): """ This function connects to Liquidsoap to find what media items are in its queue. @@ -175,10 +194,10 @@ class PypoPush(Thread): return liquidsoap_queue_approx - def is_correct_current_item(self, media_item, liquidsoap_queue_approx): + def is_correct_current_item(self, media_item, liquidsoap_queue_approx, liquidsoap_stream_id): correct = False if media_item is None: - correct = (len(liquidsoap_queue_approx) == 0 and self.current_stream_info is None) + correct = (len(liquidsoap_queue_approx) == 0 and liquidsoap_stream_id == "-1") else: if is_file(media_item): if len(liquidsoap_queue_approx) == 0: @@ -188,10 +207,7 @@ class PypoPush(Thread): liquidsoap_queue_approx[0]['row_id'] == media_item['row_id'] and \ liquidsoap_queue_approx[0]['end'] == media_item['end'] elif is_stream(media_item): - if self.current_stream_info is None: - correct = False - else: - correct = self.current_stream_info['row_id'] == media_item['row_id'] + correct = liquidsoap_stream_id == str(media_item['row_id']) self.logger.debug("Is current item correct?: %s", str(correct)) return correct @@ -202,7 +218,7 @@ class PypoPush(Thread): self.remove_from_liquidsoap_queue(0, None) self.stop_web_stream_all() - def handle_new_schedule(self, media_schedule, liquidsoap_queue_approx, current_event_chain): + def handle_new_schedule(self, media_schedule, liquidsoap_queue_approx, liquidsoap_stream_id, current_event_chain): """ This function's purpose is to gracefully handle situations where Liquidsoap already has a track in its queue, but the schedule @@ -213,14 +229,13 @@ class PypoPush(Thread): file_chain = filter(lambda item: (item["type"] == "file"), current_event_chain) stream_chain = filter(lambda item: (item["type"] == "stream_output_start"), current_event_chain) - self.logger.debug(self.current_stream_info) self.logger.debug(current_event_chain) #Take care of the case where the current playing may be incorrect if len(current_event_chain) > 0: current_item = current_event_chain[0] - if not self.is_correct_current_item(current_item, liquidsoap_queue_approx): + if not self.is_correct_current_item(current_item, liquidsoap_queue_approx, liquidsoap_stream_id): self.clear_all_liquidsoap_items() if is_stream(current_item): if current_item['row_id'] != self.current_prebuffering_stream_id: @@ -234,7 +249,7 @@ class PypoPush(Thread): #we've changed the queue, so let's refetch it liquidsoap_queue_approx = self.get_queue_items_from_liquidsoap() - elif not self.is_correct_current_item(None, liquidsoap_queue_approx): + elif not self.is_correct_current_item(None, liquidsoap_queue_approx, liquidsoap_stream_id): #Liquidsoap is playing something even though it shouldn't be self.clear_all_liquidsoap_items() @@ -455,6 +470,7 @@ class PypoPush(Thread): tn = telnetlib.Telnet(LS_HOST, LS_PORT) msg = 'dynamic_source.id %s\n' % media_item['row_id'] + self.logger.debug(msg) tn.write(msg) #example: dynamic_source.read_start http://87.230.101.24:80/top100station.mp3 @@ -489,7 +505,6 @@ class PypoPush(Thread): self.logger.debug(tn.read_all()) self.current_prebuffering_stream_id = None - self.current_stream_info = media_item except Exception, e: self.logger.error(str(e)) finally: @@ -508,10 +523,13 @@ class PypoPush(Thread): self.logger.debug(msg) tn.write(msg) + msg = 'dynamic_source.id -1\n' + self.logger.debug(msg) + tn.write(msg) + tn.write("exit\n") self.logger.debug(tn.read_all()) - self.current_stream_info = None except Exception, e: self.logger.error(str(e)) finally: @@ -523,14 +541,17 @@ class PypoPush(Thread): tn = telnetlib.Telnet(LS_HOST, LS_PORT) #dynamic_source.stop http://87.230.101.24:80/top100station.mp3 - msg = 'dynamic_source.read_stop %s\n' % media_item['uri'].encode('latin-1') + msg = 'dynamic_source.read_stop %s\n' % media_item['row_id'] + self.logger.debug(msg) + tn.write(msg) + + msg = 'dynamic_source.id -1\n' self.logger.debug(msg) tn.write(msg) tn.write("exit\n") self.logger.debug(tn.read_all()) - self.current_stream_info = None except Exception, e: self.logger.error(str(e)) finally: @@ -549,7 +570,6 @@ class PypoPush(Thread): tn.write("exit\n") self.logger.debug(tn.read_all()) - self.current_stream_info = None except Exception, e: self.logger.error(str(e)) finally: