From c45440482a6a5f8522237468034d216147e27afb Mon Sep 17 00:00:00 2001 From: drigato Date: Wed, 30 Jan 2013 08:57:59 -0500 Subject: [PATCH 01/51] CC-4887: Show whether track is scheduled in the future on Library page --- airtime_mvc/application/models/Schedule.php | 15 +++++++++++++++ airtime_mvc/application/models/StoredFile.php | 5 +++++ .../public/js/airtime/library/library.js | 19 +++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/airtime_mvc/application/models/Schedule.php b/airtime_mvc/application/models/Schedule.php index 481f22b2d..c0fd53f4b 100644 --- a/airtime_mvc/application/models/Schedule.php +++ b/airtime_mvc/application/models/Schedule.php @@ -20,6 +20,21 @@ SQL; return (is_numeric($count) && ($count != '0')); } + public static function getAllFutureScheduledFiles() + { + $con = Propel::getConnection(); + $sql = << now() AT TIME ZONE 'UTC' +SQL; + $files = $con->query($sql)->fetchAll(); + $real_files = array(); + foreach ($files as $f) { + $real_files[] = $f['file_id']; + } + return $real_files; + } /** * Returns data related to the scheduled items. * diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index bd50f2826..fd9c3708a 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -771,6 +771,8 @@ SQL; } $results = Application_Model_Datatables::findEntries($con, $displayColumns, $fromTable, $datatables); + + $futureScheduledFiles = Application_Model_Schedule::getAllFutureScheduledFiles(); //Used by the audio preview functionality in the library. foreach ($results['aaData'] as &$row) { @@ -812,6 +814,9 @@ SQL; if ($type == "au") { $row['audioFile'] = $row['id'].".".pathinfo($row['filepath'], PATHINFO_EXTENSION); $row['image'] = ''; + if (in_array($row['id'], $futureScheduledFiles)) { + $row['checkbox'] .= ''; + } } elseif ($type == "pl") { $row['image'] = ''; } elseif ($type == "st") { diff --git a/airtime_mvc/public/js/airtime/library/library.js b/airtime_mvc/public/js/airtime/library/library.js index ed28d0c53..c6c5f589b 100644 --- a/airtime_mvc/public/js/airtime/library/library.js +++ b/airtime_mvc/public/js/airtime/library/library.js @@ -605,6 +605,25 @@ var AIRTIME = (function(AIRTIME) { } return false; }); + + $(nRow).find(".track-future").qtip({ + content: { + text: $.i18n._("Track is scheduled in the future") + }, + position:{ + adjust: { + resize: true, + method: "flip flip" + }, + at: "right center", + my: "left top", + viewport: $(window) + }, + style: { + classes: "ui-tooltip-dark" + }, + show: 'mousedown' + }); // add a tool tip to appear when the user clicks on the type // icon. From 9d99b8ae90831d4a5eef7012dfd4b5748f7afc40 Mon Sep 17 00:00:00 2001 From: denise Date: Wed, 30 Jan 2013 10:33:26 -0500 Subject: [PATCH 02/51] CC-4887: Show whether track is scheduled in the future on Library page --- .../public/js/airtime/library/library.js | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/airtime_mvc/public/js/airtime/library/library.js b/airtime_mvc/public/js/airtime/library/library.js index c6c5f589b..4280b3b4c 100644 --- a/airtime_mvc/public/js/airtime/library/library.js +++ b/airtime_mvc/public/js/airtime/library/library.js @@ -608,21 +608,23 @@ var AIRTIME = (function(AIRTIME) { $(nRow).find(".track-future").qtip({ content: { - text: $.i18n._("Track is scheduled in the future") + text: $.i18n._("This track is scheduled in the future") }, - position:{ - adjust: { - resize: true, - method: "flip flip" - }, - at: "right center", - my: "left top", - viewport: $(window) + hide: { + delay: 500, + fixed: true }, style: { - classes: "ui-tooltip-dark" + border: { + width: 0, + radius: 4 + }, + classes: "ui-tooltip-dark ui-tooltip-rounded" + }, + position: { + my: "left bottom", + at: "right center" }, - show: 'mousedown' }); // add a tool tip to appear when the user clicks on the type From 043fbd1dfb2145e87fb0a62a2585be5355fb8ef1 Mon Sep 17 00:00:00 2001 From: denise Date: Wed, 30 Jan 2013 10:55:24 -0500 Subject: [PATCH 03/51] CC-4886: Warn if track is part of a playlist when deleting from library. -done --- airtime_mvc/application/models/Block.php | 14 +++++++ airtime_mvc/application/models/Playlist.php | 15 +++++++ airtime_mvc/application/models/StoredFile.php | 11 +++-- .../public/js/airtime/library/library.js | 42 ++++++++++++++++++- 4 files changed, 78 insertions(+), 4 deletions(-) diff --git a/airtime_mvc/application/models/Block.php b/airtime_mvc/application/models/Block.php index 7053e658d..cda10c1a3 100644 --- a/airtime_mvc/application/models/Block.php +++ b/airtime_mvc/application/models/Block.php @@ -1434,6 +1434,20 @@ SQL; return $output; } + public static function getAllBlockContent() + { + $con = Propel::getConnection(); + $sql = <<query($sql)->fetchAll(); + $real_files = array(); + foreach ($files as $f) { + $real_files[] = $f['file_id']; + } + return $real_files; + } // smart block functions end } diff --git a/airtime_mvc/application/models/Playlist.php b/airtime_mvc/application/models/Playlist.php index 1c446f182..ea3bf5b53 100644 --- a/airtime_mvc/application/models/Playlist.php +++ b/airtime_mvc/application/models/Playlist.php @@ -955,6 +955,21 @@ SQL; return $result; } + public static function getAllPlaylistContent() + { + $con = Propel::getConnection(); + $sql = <<query($sql)->fetchAll(); + $real_files = array(); + foreach ($files as $f) { + $real_files[] = $f['file_id']; + } + return $real_files; + } + } // class Playlist class PlaylistNotFoundException extends Exception {} diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index fd9c3708a..db7454d6c 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -773,7 +773,8 @@ SQL; $results = Application_Model_Datatables::findEntries($con, $displayColumns, $fromTable, $datatables); $futureScheduledFiles = Application_Model_Schedule::getAllFutureScheduledFiles(); - + $playlistBlockFiles = array_merge(Application_Model_Playlist::getAllPlaylistContent(), + Application_Model_Block::getAllBlockContent()); //Used by the audio preview functionality in the library. foreach ($results['aaData'] as &$row) { $row['id'] = intval($row['id']); @@ -814,8 +815,12 @@ SQL; if ($type == "au") { $row['audioFile'] = $row['id'].".".pathinfo($row['filepath'], PATHINFO_EXTENSION); $row['image'] = ''; - if (in_array($row['id'], $futureScheduledFiles)) { - $row['checkbox'] .= ''; + if (in_array($row['id'], $futureScheduledFiles) && in_array($row['id'], $playlistBlockFiles)) { + $row['checkbox'] .= ''; + } elseif (in_array($row['id'], $futureScheduledFiles)) { + $row['checkbox'] .= ''; + } elseif (in_array($row['id'], $playlistBlockFiles)) { + $row['checkbox'] .= ''; } } elseif ($type == "pl") { $row['image'] = ''; diff --git a/airtime_mvc/public/js/airtime/library/library.js b/airtime_mvc/public/js/airtime/library/library.js index 4280b3b4c..b73185292 100644 --- a/airtime_mvc/public/js/airtime/library/library.js +++ b/airtime_mvc/public/js/airtime/library/library.js @@ -606,7 +606,7 @@ var AIRTIME = (function(AIRTIME) { return false; }); - $(nRow).find(".track-future").qtip({ + $(nRow).find(".track-scheduled").qtip({ content: { text: $.i18n._("This track is scheduled in the future") }, @@ -626,6 +626,46 @@ var AIRTIME = (function(AIRTIME) { at: "right center" }, }); + $(nRow).find(".track-sched-pl-bl").qtip({ + content: { + text: $.i18n._("This track is scheduled in the future and belongs to a playlist or smart block") + }, + hide: { + delay: 500, + fixed: true + }, + style: { + border: { + width: 0, + radius: 4 + }, + classes: "ui-tooltip-dark ui-tooltip-rounded" + }, + position: { + my: "left bottom", + at: "right center" + }, + }); + $(nRow).find(".track-pl-bl").qtip({ + content: { + text: $.i18n._("This track belongs to a playlist or smart block") + }, + hide: { + delay: 500, + fixed: true + }, + style: { + border: { + width: 0, + radius: 4 + }, + classes: "ui-tooltip-dark ui-tooltip-rounded" + }, + position: { + my: "left bottom", + at: "right center" + }, + }); // add a tool tip to appear when the user clicks on the type // icon. From 4f5d7869d8958e4c44cba20a64172f639b193b54 Mon Sep 17 00:00:00 2001 From: denise Date: Wed, 30 Jan 2013 15:38:25 -0500 Subject: [PATCH 04/51] Removed html from Library/contents-feed and StoredFile model Added new Library column to indicate if a file is scheduled in the future or belongs to a playlist/smart block --- .../controllers/LibraryController.php | 17 ------ airtime_mvc/application/models/Preference.php | 4 +- airtime_mvc/application/models/StoredFile.php | 55 ++++++++----------- .../public/js/airtime/library/library.js | 45 +++++++++++++-- 4 files changed, 66 insertions(+), 55 deletions(-) diff --git a/airtime_mvc/application/controllers/LibraryController.php b/airtime_mvc/application/controllers/LibraryController.php index 2b38349dd..a180be814 100644 --- a/airtime_mvc/application/controllers/LibraryController.php +++ b/airtime_mvc/application/controllers/LibraryController.php @@ -382,23 +382,6 @@ class LibraryController extends Zend_Controller_Action # terrible name for the method below. it does not only search files. $r = Application_Model_StoredFile::searchLibraryFiles($params); - //TODO move this to the datatables row callback. - foreach ($r["aaData"] as &$data) { - - if ($data['ftype'] == 'audioclip') { - $file = Application_Model_StoredFile::Recall($data['id']); - $scid = $file->getSoundCloudId(); - - if ($scid == "-2") { - $data['track_title'] .= ''; - } elseif ($scid == "-3") { - $data['track_title'] .= ''; - } elseif (!is_null($scid)) { - $data['track_title'] .= ''; - } - } - } - $this->view->sEcho = $r["sEcho"]; $this->view->iTotalDisplayRecords = $r["iTotalDisplayRecords"]; $this->view->iTotalRecords = $r["iTotalRecords"]; diff --git a/airtime_mvc/application/models/Preference.php b/airtime_mvc/application/models/Preference.php index 08bd14137..1d56313f7 100644 --- a/airtime_mvc/application/models/Preference.php +++ b/airtime_mvc/application/models/Preference.php @@ -1223,9 +1223,9 @@ class Application_Model_Preference $num_columns = count(Application_Model_StoredFile::getLibraryColumns()); $new_columns_num = count($settings['abVisCols']); - if ($num_columns != $new_columns_num) { + /*if ($num_columns != $new_columns_num) { throw new Exception("Trying to write a user column preference with incorrect number of columns!"); - } + }*/ $data = serialize($settings); $v = self::setValue("library_datatable", $data, true); diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index db7454d6c..a9bc15955 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -775,7 +775,7 @@ SQL; $futureScheduledFiles = Application_Model_Schedule::getAllFutureScheduledFiles(); $playlistBlockFiles = array_merge(Application_Model_Playlist::getAllPlaylistContent(), Application_Model_Block::getAllBlockContent()); - //Used by the audio preview functionality in the library. + foreach ($results['aaData'] as &$row) { $row['id'] = intval($row['id']); @@ -788,6 +788,24 @@ SQL; $formatter = new BitrateFormatter($row['bit_rate']); $row['bit_rate'] = $formatter->format(); + + //soundcloud status + $file = Application_Model_StoredFile::Recall($row['id']); + $row['soundcloud_status'] = $file->getSoundCloudId(); + + //file 'in use' status + if (in_array($row['id'], $futureScheduledFiles) && in_array($row['id'], $playlistBlockFiles)) { + $row['status_scheduled_pl_bl'] = true; + } elseif (in_array($row['id'], $futureScheduledFiles)) { + $row['status_scheduled'] = true; + } elseif (in_array($row['id'], $playlistBlockFiles)) { + $row['status_pl_bl'] = true; + } + + // for audio preview + $row['audioFile'] = $row['id'].".".pathinfo($row['filepath'], PATHINFO_EXTENSION); + } else { + $row['audioFile'] = $row['id']; } //convert mtime and utime to localtime @@ -798,38 +816,13 @@ SQL; $row['utime']->setTimeZone(new DateTimeZone(date_default_timezone_get())); $row['utime'] = $row['utime']->format('Y-m-d H:i:s'); - // add checkbox row - $row['checkbox'] = ""; - - $type = substr($row['ftype'], 0, 2); + // we need to initalize the checkbox and image row because we do not retrieve + // any data from the db for these and datatables will complain + $row['checkbox'] = ""; + $row['image'] = ""; + $row['status'] = ""; $row['tr_id'] = "{$type}_{$row['id']}"; - - //TODO url like this to work on both playlist/showbuilder - //screens. datatable stuff really needs to be pulled out and - //generalized within the project access to zend view methods - //to access url helpers is needed. - - // TODO : why is there inline html here? breaks abstraction and is - // ugly - if ($type == "au") { - $row['audioFile'] = $row['id'].".".pathinfo($row['filepath'], PATHINFO_EXTENSION); - $row['image'] = ''; - if (in_array($row['id'], $futureScheduledFiles) && in_array($row['id'], $playlistBlockFiles)) { - $row['checkbox'] .= ''; - } elseif (in_array($row['id'], $futureScheduledFiles)) { - $row['checkbox'] .= ''; - } elseif (in_array($row['id'], $playlistBlockFiles)) { - $row['checkbox'] .= ''; - } - } elseif ($type == "pl") { - $row['image'] = ''; - } elseif ($type == "st") { - $row['audioFile'] = $row['id']; - $row['image'] = ''; - } elseif ($type == "bl") { - $row['image'] = ''; - } } return $results; diff --git a/airtime_mvc/public/js/airtime/library/library.js b/airtime_mvc/public/js/airtime/library/library.js index b73185292..4f64b6f70 100644 --- a/airtime_mvc/public/js/airtime/library/library.js +++ b/airtime_mvc/public/js/airtime/library/library.js @@ -439,6 +439,7 @@ var AIRTIME = (function(AIRTIME) { /* ftype */ { "sTitle" : "" , "mDataProp" : "ftype" , "bSearchable" : false , "bVisible" : false } , /* Checkbox */ { "sTitle" : "" , "mDataProp" : "checkbox" , "bSortable" : false , "bSearchable" : false , "sWidth" : "25px" , "sClass" : "library_checkbox" } , /* Type */ { "sTitle" : "" , "mDataProp" : "image" , "bSearchable" : false , "sWidth" : "25px" , "sClass" : "library_type" , "iDataSort" : 0 } , + /* Status */ { "sTitle" : "" , "mDataProp" : "status" , "bSearchable" : false , "sWidth" : "25px" , "sClass" : "library_status" , "iDataSort" : 0 } , /* Title */ { "sTitle" : $.i18n._("Title") , "mDataProp" : "track_title" , "sClass" : "library_title" , "sWidth" : "170px" } , /* Creator */ { "sTitle" : $.i18n._("Creator") , "mDataProp" : "artist_name" , "sClass" : "library_creator" , "sWidth" : "160px" } , /* Album */ { "sTitle" : $.i18n._("Album") , "mDataProp" : "album_title" , "sClass" : "library_album" , "sWidth" : "150px" } , @@ -554,12 +555,47 @@ var AIRTIME = (function(AIRTIME) { }, "fnRowCallback": AIRTIME.library.fnRowCallback, "fnCreatedRow": function( nRow, aData, iDataIndex ) { - + //add soundcloud icon + if (aData.soundcloud_status !== undefined) { + if (aData.soundcloud_status === "-2") { + $(nRow).find("td.library_title").append(''); + } else if (aData.soundcloud_status === "-3") { + $(nRow).find("td.library_title").append(''); + } else if (aData.soundcloud_status !== null) { + $(nRow).find("td.library_title").append(''); + } + } + + // add checkbox + $(nRow).find('td.library_checkbox').html(""); + + // add audio preview image/button + if (aData.ftype === "audioclip") { + $(nRow).find('td.library_type').html(''); + } else if (aData.ftype === "playlist") { + $(nRow).find('td.library_type').html(''); + } else if (aData.ftype === "block") { + $(nRow).find('td.library_type').html(''); + } else if (aData.ftype === "stream") { + $(nRow).find('td.library_type').html(''); + } + + // add status icon + $status = $(nRow).find("td.library_status"); + if (aData.status_scheduled_pl_bl !== null && aData.status_scheduled_pl_bl) { + $status.html(''); + } + if (aData.status_scheduled !== null && aData.status_scheduled) { + $status.html(''); + } + if (aData.status_pl_bl !== null && aData.status_pl_bl) { + $status.html(''); + } + // add the play function to the library_type td $(nRow).find('td.library_type').click(function(){ if (aData.ftype === 'playlist' && aData.length !== '0.0'){ - playlistIndex = $(this).parent().attr('id').substring(3); - open_playlist_preview(playlistIndex, 0); + open_playlist_preview(aData.audioFile, 0); } else if (aData.ftype === 'audioclip') { if (isAudioSupported(aData.mime)) { open_audio_preview(aData.ftype, aData.audioFile, aData.track_title, aData.artist_name); @@ -569,8 +605,7 @@ var AIRTIME = (function(AIRTIME) { open_audio_preview(aData.ftype, aData.audioFile, aData.track_title, aData.artist_name); } } else if (aData.ftype == 'block' && aData.bl_type == 'static') { - blockIndex = $(this).parent().attr('id').substring(3); - open_block_preview(blockIndex, 0); + open_block_preview(aData.audioFile, 0); } return false; }); From cf2ed25bfb5ac4dcc1c604d811a3f78765016405 Mon Sep 17 00:00:00 2001 From: denise Date: Wed, 30 Jan 2013 16:53:59 -0500 Subject: [PATCH 05/51] CC-4900: Indicate in the library if a webstream is scheduled in the future or belongs to a playlist -done --- airtime_mvc/application/models/Playlist.php | 19 +++++- airtime_mvc/application/models/Schedule.php | 20 +++++++ airtime_mvc/application/models/StoredFile.php | 33 +++++++++-- airtime_mvc/public/css/styles.css | 2 +- .../public/js/airtime/library/library.js | 58 ++----------------- 5 files changed, 71 insertions(+), 61 deletions(-) diff --git a/airtime_mvc/application/models/Playlist.php b/airtime_mvc/application/models/Playlist.php index ea3bf5b53..7aaea5243 100644 --- a/airtime_mvc/application/models/Playlist.php +++ b/airtime_mvc/application/models/Playlist.php @@ -955,12 +955,13 @@ SQL; return $result; } - public static function getAllPlaylistContent() + public static function getAllPlaylistFiles() { $con = Propel::getConnection(); $sql = <<query($sql)->fetchAll(); $real_files = array(); @@ -970,6 +971,22 @@ SQL; return $real_files; } + public static function getAllPlaylistStreams() + { + $con = Propel::getConnection(); + $sql = <<query($sql)->fetchAll(); + $real_streams = array(); + foreach ($streams as $s) { + $real_streams[] = $s['stream_id']; + } + return $real_streams; + } + } // class Playlist class PlaylistNotFoundException extends Exception {} diff --git a/airtime_mvc/application/models/Schedule.php b/airtime_mvc/application/models/Schedule.php index c0fd53f4b..ca31a4378 100644 --- a/airtime_mvc/application/models/Schedule.php +++ b/airtime_mvc/application/models/Schedule.php @@ -27,14 +27,34 @@ SQL; SELECT distinct(file_id) FROM cc_schedule WHERE ends > now() AT TIME ZONE 'UTC' +AND file_id is not null SQL; $files = $con->query($sql)->fetchAll(); $real_files = array(); foreach ($files as $f) { $real_files[] = $f['file_id']; } + return $real_files; } + + public static function getAllFutureScheduledWebstreams() + { + $con = Propel::getConnection(); + $sql = << now() AT TIME ZONE 'UTC' +AND stream_id is not null +SQL; + $streams = $con->query($sql)->fetchAll(); + $real_streams = array(); + foreach ($streams as $s) { + $real_streams[] = $s['stream_id']; + } + + return $real_streams; + } /** * Returns data related to the scheduled items. * diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index a9bc15955..92a46f9c0 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -773,9 +773,14 @@ SQL; $results = Application_Model_Datatables::findEntries($con, $displayColumns, $fromTable, $datatables); $futureScheduledFiles = Application_Model_Schedule::getAllFutureScheduledFiles(); - $playlistBlockFiles = array_merge(Application_Model_Playlist::getAllPlaylistContent(), + // we are only interested in which files belong to playlists and blocks + $playlistBlockFiles = array_merge(Application_Model_Playlist::getAllPlaylistFiles(), Application_Model_Block::getAllBlockContent()); + $futureScheduledStreams = Application_Model_Schedule::getAllFutureScheduledWebstreams(); + // here we are only interested in which streams belong to a playlist + $playlistStreams = Application_Model_Playlist::getAllPlaylistStreams(); + foreach ($results['aaData'] as &$row) { $row['id'] = intval($row['id']); @@ -792,18 +797,34 @@ SQL; //soundcloud status $file = Application_Model_StoredFile::Recall($row['id']); $row['soundcloud_status'] = $file->getSoundCloudId(); - + //file 'in use' status if (in_array($row['id'], $futureScheduledFiles) && in_array($row['id'], $playlistBlockFiles)) { - $row['status_scheduled_pl_bl'] = true; + $row['status_in_use'] = true; + $row['status_msg'] = _("This track is scheduled in the future and belongs to a playlist or smart block"); } elseif (in_array($row['id'], $futureScheduledFiles)) { - $row['status_scheduled'] = true; + $row['status_in_use'] = true; + $row['status_msg'] = _("This track is scheduled in the future"); } elseif (in_array($row['id'], $playlistBlockFiles)) { - $row['status_pl_bl'] = true; + $row['status_in_use'] = true; + $row['status_msg'] = _("This track belongs to a playlist or smart block"); } - + // for audio preview $row['audioFile'] = $row['id'].".".pathinfo($row['filepath'], PATHINFO_EXTENSION); + } else if ($row['ftype'] === "stream") { + $row['audioFile'] = $row['id']; + + if (in_array($row['id'], $futureScheduledStreams) && in_array($row['id'], $playlistStreams)) { + $row['status_in_use'] = true; + $row['status_msg'] = _("This webstream is scheduled in the future and belongs to a playlist"); + } elseif (in_array($row['id'], $futureScheduledStreams)) { + $row['status_in_use'] = true; + $row['status_msg'] = _("This webstream is scheduled in the future"); + } elseif (in_array($row['id'], $playlistStreams)) { + $row['status_in_use'] = true; + $row['status_msg'] = _("This webstream belongs to a playlist"); + } } else { $row['audioFile'] = $row['id']; } diff --git a/airtime_mvc/public/css/styles.css b/airtime_mvc/public/css/styles.css index bd42e4da6..31b85797d 100644 --- a/airtime_mvc/public/css/styles.css +++ b/airtime_mvc/public/css/styles.css @@ -1999,7 +1999,7 @@ span.errors.sp-errors{ .small-icon.show-empty { background:url(images/icon_alert_cal_alt.png) no-repeat 0 0; } -.small-icon.show-partial-filled { +.small-icon.show-partial-filled, .small-icon.media-item-in-use { background:url(images/icon_alert_cal_alt2.png) no-repeat 0 0; } .medium-icon { diff --git a/airtime_mvc/public/js/airtime/library/library.js b/airtime_mvc/public/js/airtime/library/library.js index 4f64b6f70..042a7d1cf 100644 --- a/airtime_mvc/public/js/airtime/library/library.js +++ b/airtime_mvc/public/js/airtime/library/library.js @@ -580,16 +580,8 @@ var AIRTIME = (function(AIRTIME) { $(nRow).find('td.library_type').html(''); } - // add status icon - $status = $(nRow).find("td.library_status"); - if (aData.status_scheduled_pl_bl !== null && aData.status_scheduled_pl_bl) { - $status.html(''); - } - if (aData.status_scheduled !== null && aData.status_scheduled) { - $status.html(''); - } - if (aData.status_pl_bl !== null && aData.status_pl_bl) { - $status.html(''); + if (aData.status_in_use !== null && aData.status_in_use) { + $(nRow).find("td.library_status").html(''); } // add the play function to the library_type td @@ -641,9 +633,9 @@ var AIRTIME = (function(AIRTIME) { return false; }); - $(nRow).find(".track-scheduled").qtip({ + $(nRow).find(".media-item-in-use").qtip({ content: { - text: $.i18n._("This track is scheduled in the future") + text: aData.status_msg }, hide: { delay: 500, @@ -661,47 +653,7 @@ var AIRTIME = (function(AIRTIME) { at: "right center" }, }); - $(nRow).find(".track-sched-pl-bl").qtip({ - content: { - text: $.i18n._("This track is scheduled in the future and belongs to a playlist or smart block") - }, - hide: { - delay: 500, - fixed: true - }, - style: { - border: { - width: 0, - radius: 4 - }, - classes: "ui-tooltip-dark ui-tooltip-rounded" - }, - position: { - my: "left bottom", - at: "right center" - }, - }); - $(nRow).find(".track-pl-bl").qtip({ - content: { - text: $.i18n._("This track belongs to a playlist or smart block") - }, - hide: { - delay: 500, - fixed: true - }, - style: { - border: { - width: 0, - radius: 4 - }, - classes: "ui-tooltip-dark ui-tooltip-rounded" - }, - position: { - my: "left bottom", - at: "right center" - }, - }); - + // add a tool tip to appear when the user clicks on the type // icon. $(nRow).find("td:not(.library_checkbox, .library_type)").qtip({ From e1c69ebca20ed87fd2cc18083b3de7c3654dbc97 Mon Sep 17 00:00:00 2001 From: denise Date: Thu, 31 Jan 2013 11:28:54 -0500 Subject: [PATCH 06/51] CC-4902: Library -> Status column does not update when you add a file to a show -fixed --- airtime_mvc/public/js/airtime/showbuilder/builder.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/airtime_mvc/public/js/airtime/showbuilder/builder.js b/airtime_mvc/public/js/airtime/showbuilder/builder.js index b7d4bda7f..a20661bcb 100644 --- a/airtime_mvc/public/js/airtime/showbuilder/builder.js +++ b/airtime_mvc/public/js/airtime/showbuilder/builder.js @@ -251,10 +251,11 @@ var AIRTIME = (function(AIRTIME){ mod.fnItemCallback = function(json) { checkError(json); - mod.getSelectedCursors(); + mod.getSelectedCursors(); oSchedTable.fnDraw(); - + mod.enableUI(); + $("#library_content").find("#library_display").dataTable().fnStandingRedraw(); }; mod.getSelectedCursors = function() { From 3bc2876f2725a1ce21632c56a0f1a57b49556e2b Mon Sep 17 00:00:00 2001 From: denise Date: Thu, 31 Jan 2013 11:41:00 -0500 Subject: [PATCH 07/51] CC-4903: Library -> Show/hide columns -> Status Column needs label -fixed --- airtime_mvc/public/js/airtime/library/library.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airtime_mvc/public/js/airtime/library/library.js b/airtime_mvc/public/js/airtime/library/library.js index 042a7d1cf..f56cf7d7a 100644 --- a/airtime_mvc/public/js/airtime/library/library.js +++ b/airtime_mvc/public/js/airtime/library/library.js @@ -439,7 +439,7 @@ var AIRTIME = (function(AIRTIME) { /* ftype */ { "sTitle" : "" , "mDataProp" : "ftype" , "bSearchable" : false , "bVisible" : false } , /* Checkbox */ { "sTitle" : "" , "mDataProp" : "checkbox" , "bSortable" : false , "bSearchable" : false , "sWidth" : "25px" , "sClass" : "library_checkbox" } , /* Type */ { "sTitle" : "" , "mDataProp" : "image" , "bSearchable" : false , "sWidth" : "25px" , "sClass" : "library_type" , "iDataSort" : 0 } , - /* Status */ { "sTitle" : "" , "mDataProp" : "status" , "bSearchable" : false , "sWidth" : "25px" , "sClass" : "library_status" , "iDataSort" : 0 } , + /* Status */ { "sTitle" : $.i18n._("Status") , "mDataProp" : "status" , "bSearchable" : false , "sWidth" : "60px" , "sClass" : "library_status" , "iDataSort" : 0 } , /* Title */ { "sTitle" : $.i18n._("Title") , "mDataProp" : "track_title" , "sClass" : "library_title" , "sWidth" : "170px" } , /* Creator */ { "sTitle" : $.i18n._("Creator") , "mDataProp" : "artist_name" , "sClass" : "library_creator" , "sWidth" : "160px" } , /* Album */ { "sTitle" : $.i18n._("Album") , "mDataProp" : "album_title" , "sClass" : "library_album" , "sWidth" : "150px" } , From d4e89df94e6e66a6f6cb61631713c710d3a3a0b1 Mon Sep 17 00:00:00 2001 From: denise Date: Thu, 31 Jan 2013 14:06:45 -0500 Subject: [PATCH 08/51] CC-4895: Remove all json die() statements from code --- .../application/controllers/ApiController.php | 4 ++-- .../controllers/LibraryController.php | 2 +- .../controllers/ListenerstatController.php | 2 +- .../controllers/PlaylistController.php | 18 ++++++++--------- .../controllers/PluploadController.php | 6 +++--- .../controllers/PreferenceController.php | 20 +++++++++---------- .../controllers/ScheduleController.php | 4 ++-- .../controllers/UserController.php | 12 +++++------ 8 files changed, 34 insertions(+), 34 deletions(-) diff --git a/airtime_mvc/application/controllers/ApiController.php b/airtime_mvc/application/controllers/ApiController.php index 6bbe480da..ffbae08aa 100644 --- a/airtime_mvc/application/controllers/ApiController.php +++ b/airtime_mvc/application/controllers/ApiController.php @@ -411,7 +411,7 @@ class ApiController extends Zend_Controller_Action $result = Application_Model_StoredFile::copyFileToStor($upload_dir, $fileName, $tempFileName); if (!is_null($result)) { - die('{"jsonrpc" : "2.0", "error" : {"code": '.$result['code'].', "message" : "'.$result['message'].'"}}'); + $this->_helper->json->sendJson('{"jsonrpc" : "2.0", "error" : {"code": '.$result['code'].', "message" : "'.$result['message'].'"}}'); } } @@ -600,7 +600,7 @@ class ApiController extends Zend_Controller_Action $response['key'] = $k; array_push($responses, $response); } - die( json_encode($responses) ); + $this->_helper->json->sendJson( json_encode($responses) ); } public function listAllFilesAction() diff --git a/airtime_mvc/application/controllers/LibraryController.php b/airtime_mvc/application/controllers/LibraryController.php index a180be814..2974d85a1 100644 --- a/airtime_mvc/application/controllers/LibraryController.php +++ b/airtime_mvc/application/controllers/LibraryController.php @@ -507,7 +507,7 @@ class LibraryController extends Zend_Controller_Action $id = $this->_getParam('id'); Application_Model_Soundcloud::uploadSoundcloud($id); // we should die with ui info - die(); + $this->_helper->json->sendJson(null); } public function getUploadToSoundcloudStatusAction() diff --git a/airtime_mvc/application/controllers/ListenerstatController.php b/airtime_mvc/application/controllers/ListenerstatController.php index 817f6ca11..f64ea8bfb 100644 --- a/airtime_mvc/application/controllers/ListenerstatController.php +++ b/airtime_mvc/application/controllers/ListenerstatController.php @@ -76,6 +76,6 @@ class ListenerstatController extends Zend_Controller_Action $endsDT = DateTime::createFromFormat("U", $ends_epoch, new DateTimeZone("UTC")); $data = Application_Model_ListenerStat::getDataPointsWithinRange($startsDT->format("Y-m-d H:i:s"), $endsDT->format("Y-m-d H:i:s"), $mountName); - die(json_encode($data)); + $this->_helper->json->sendJson(json_encode($data)); } } diff --git a/airtime_mvc/application/controllers/PlaylistController.php b/airtime_mvc/application/controllers/PlaylistController.php index 19c2756e7..789d4a904 100644 --- a/airtime_mvc/application/controllers/PlaylistController.php +++ b/airtime_mvc/application/controllers/PlaylistController.php @@ -132,7 +132,7 @@ class PlaylistController extends Zend_Controller_Action if (!$p_isJson) { $this->createFullResponse(null); } else { - die(json_encode(array("error"=>$this->view->error, "result"=>1, "html"=>$this->createFullResponse(null, $p_isJson)))); + $this->_helper->json->sendJson(json_encode(array("error"=>$this->view->error, "result"=>1, "html"=>$this->createFullResponse(null, $p_isJson)))); } } @@ -488,7 +488,7 @@ class PlaylistController extends Zend_Controller_Action } $result["modified"] = $this->view->modified; - die(json_encode($result)); + $this->_helper->json->sendJson(json_encode($result)); } public function smartBlockGenerateAction() @@ -504,7 +504,7 @@ class PlaylistController extends Zend_Controller_Action $form->startForm($params['obj_id']); if ($form->isValid($params)) { $result = $bl->generateSmartBlock($params['data']); - die(json_encode(array("result"=>0, "html"=>$this->createFullResponse($bl, true, true)))); + $this->_helper->json->sendJson(json_encode(array("result"=>0, "html"=>$this->createFullResponse($bl, true, true)))); } else { $this->view->obj = $bl; $this->view->id = $bl->getId(); @@ -512,7 +512,7 @@ class PlaylistController extends Zend_Controller_Action $viewPath = 'playlist/smart-block.phtml'; $result['html'] = $this->view->render($viewPath); $result['result'] = 1; - die(json_encode($result)); + $this->_helper->json->sendJson(json_encode($result)); } } catch (BlockNotFoundException $e) { $this->playlistNotFound('block', true); @@ -531,9 +531,9 @@ class PlaylistController extends Zend_Controller_Action $result = $bl->shuffleSmartBlock(); if ($result['result'] == 0) { - die(json_encode(array("result"=>0, "html"=>$this->createFullResponse($bl, true)))); + $this->_helper->json->sendJson(json_encode(array("result"=>0, "html"=>$this->createFullResponse($bl, true)))); } else { - die(json_encode($result)); + $this->_helper->json->sendJson(json_encode($result)); } } catch (BlockNotFoundException $e) { $this->playlistNotFound('block', true); @@ -551,9 +551,9 @@ class PlaylistController extends Zend_Controller_Action $result = $pl->shuffle(); if ($result['result'] == 0) { - die(json_encode(array("result"=>0, "html"=>$this->createFullResponse($pl, true)))); + $this->_helper->json->sendJson(json_encode(array("result"=>0, "html"=>$this->createFullResponse($pl, true)))); } else { - die(json_encode($result)); + $this->_helper->json->sendJson(json_encode($result)); } } catch (PlaylistNotFoundException $e) { $this->playlistNotFound('block', true); @@ -574,7 +574,7 @@ class PlaylistController extends Zend_Controller_Action $out = $bl->getCriteria(); $out['isStatic'] = false; } - die(json_encode($out)); + $this->_helper->json->sendJson(json_encode($out)); } } class WrongTypeToBlockException extends Exception {} diff --git a/airtime_mvc/application/controllers/PluploadController.php b/airtime_mvc/application/controllers/PluploadController.php index ed7403693..49b706644 100644 --- a/airtime_mvc/application/controllers/PluploadController.php +++ b/airtime_mvc/application/controllers/PluploadController.php @@ -32,7 +32,7 @@ class PluploadController extends Zend_Controller_Action $tempFilePath = Application_Model_StoredFile::uploadFile($upload_dir); $tempFileName = basename($tempFilePath); - die('{"jsonrpc" : "2.0", "tempfilepath" : "'.$tempFileName.'" }'); + $this->_helper->json->sendJson(array("jsonrpc" => "2.0", "tempfilepath" => $tempFileName)); } public function copyfileAction() @@ -43,8 +43,8 @@ class PluploadController extends Zend_Controller_Action $result = Application_Model_StoredFile::copyFileToStor($upload_dir, $filename, $tempname); if (!is_null($result)) - die('{"jsonrpc" : "2.0", "error" : '.json_encode($result).'}'); + $this->_helper->json->sendJson('{"jsonrpc" : "2.0", "error" : '.json_encode($result).'}'); - die('{"jsonrpc" : "2.0"}'); + $this->_helper->json->sendJson('{"jsonrpc" : "2.0"}'); } } diff --git a/airtime_mvc/application/controllers/PreferenceController.php b/airtime_mvc/application/controllers/PreferenceController.php index 269215783..af78f996a 100644 --- a/airtime_mvc/application/controllers/PreferenceController.php +++ b/airtime_mvc/application/controllers/PreferenceController.php @@ -70,10 +70,10 @@ class PreferenceController extends Zend_Controller_Action $this->view->statusMsg = "
". _("Preferences updated.")."
"; $this->view->form = $form; - die(json_encode(array("valid"=>"true", "html"=>$this->view->render('preference/index.phtml')))); + $this->_helper->json->sendJson(json_encode(array("valid"=>"true", "html"=>$this->view->render('preference/index.phtml')))); } else { $this->view->form = $form; - die(json_encode(array("valid"=>"false", "html"=>$this->view->render('preference/index.phtml')))); + $this->_helper->json->sendJson(json_encode(array("valid"=>"false", "html"=>$this->view->render('preference/index.phtml')))); } } $this->view->form = $form; @@ -322,7 +322,7 @@ class PreferenceController extends Zend_Controller_Action $this->view->form = $form; $this->view->num_stream = $num_of_stream; $this->view->statusMsg = "
"._("Stream Setting Updated.")."
"; - die(json_encode(array( + $this->_helper->json->sendJson(json_encode(array( "valid"=>"true", "html"=>$this->view->render('preference/stream-setting.phtml'), "s1_set_admin_pass"=>$s1_set_admin_pass, @@ -334,7 +334,7 @@ class PreferenceController extends Zend_Controller_Action $this->view->enable_stream_conf = Application_Model_Preference::GetEnableStreamConf(); $this->view->form = $form; $this->view->num_stream = $num_of_stream; - die(json_encode(array("valid"=>"false", "html"=>$this->view->render('preference/stream-setting.phtml')))); + $this->_helper->json->sendJson(json_encode(array("valid"=>"false", "html"=>$this->view->render('preference/stream-setting.phtml')))); } } @@ -378,7 +378,7 @@ class PreferenceController extends Zend_Controller_Action } ksort($result); //returns format serverBrowse is looking for. - die(json_encode($result)); + $this->_helper->json->sendJson($result); } public function changeStorDirectoryAction() @@ -420,7 +420,7 @@ class PreferenceController extends Zend_Controller_Action Application_Model_RabbitMq::SendMessageToMediaMonitor('rescan_watch', $data); Logging::info("Unhiding all files belonging to:: $dir_path"); $dir->unhideFiles(); - die(); # Get rid of this ugliness later + $this->_helper->json->sendJson(null); } public function removeWatchDirectoryAction() @@ -440,7 +440,7 @@ class PreferenceController extends Zend_Controller_Action if (Application_Model_Preference::GetImportTimestamp()+10 > $now) { $res = true; } - die(json_encode($res)); + $this->_helper->json->sendJson(json_encode($res)); } public function getLiquidsoapStatusAction() @@ -455,7 +455,7 @@ class PreferenceController extends Zend_Controller_Action } $out[] = array("id"=>$i, "status"=>$status); } - die(json_encode($out)); + $this->_helper->json->sendJson(json_encode($out)); } public function setSourceConnectionUrlAction() @@ -473,7 +473,7 @@ class PreferenceController extends Zend_Controller_Action Application_Model_Preference::SetLiveDjConnectionUrlOverride($override); } - die(); + $this->_helper->json->sendJson(null); } public function getAdminPasswordStatusAction() @@ -486,6 +486,6 @@ class PreferenceController extends Zend_Controller_Action $out["s".$i] = true; } } - die(json_encode($out)); + $this->_helper->json->sendJson(json_encode($out)); } } diff --git a/airtime_mvc/application/controllers/ScheduleController.php b/airtime_mvc/application/controllers/ScheduleController.php index 15dca0621..22257025d 100644 --- a/airtime_mvc/application/controllers/ScheduleController.php +++ b/airtime_mvc/application/controllers/ScheduleController.php @@ -218,7 +218,7 @@ class ScheduleController extends Zend_Controller_Action $id = $file->getId(); Application_Model_Soundcloud::uploadSoundcloud($id); // we should die with ui info - die(); + $this->_helper->json->sendJson(null); } public function makeContextMenuAction() @@ -897,7 +897,7 @@ class ScheduleController extends Zend_Controller_Action 'title' => _('Download')); //returns format jjmenu is looking for. - die(json_encode($menu)); + $this->_helper->json->sendJson(json_encode($menu)); } /** diff --git a/airtime_mvc/application/controllers/UserController.php b/airtime_mvc/application/controllers/UserController.php index 395156f97..14728512a 100644 --- a/airtime_mvc/application/controllers/UserController.php +++ b/airtime_mvc/application/controllers/UserController.php @@ -53,7 +53,7 @@ class UserController extends Zend_Controller_Action && $formData['user_id'] != 0) { $this->view->form = $form; $this->view->successMessage = "
"._("Specific action is not allowed in demo version!")."
"; - die(json_encode(array("valid"=>"false", "html"=>$this->view->render('user/add-user.phtml')))); + $this->_helper->json->sendJson(json_encode(array("valid"=>"false", "html"=>$this->view->render('user/add-user.phtml')))); } elseif ($form->validateLogin($formData)) { $user = new Application_Model_User($formData['user_id']); if (empty($formData['user_id'])) { @@ -89,14 +89,14 @@ class UserController extends Zend_Controller_Action $this->view->successMessage = "
"._("User updated successfully!")."
"; } - die(json_encode(array("valid"=>"true", "html"=>$this->view->render('user/add-user.phtml')))); + $this->_helper->json->sendJson(json_encode(array("valid"=>"true", "html"=>$this->view->render('user/add-user.phtml')))); } else { $this->view->form = $form; - die(json_encode(array("valid"=>"false", "html"=>$this->view->render('user/add-user.phtml')))); + $this->_helper->json->sendJson(json_encode(array("valid"=>"false", "html"=>$this->view->render('user/add-user.phtml')))); } } else { $this->view->form = $form; - die(json_encode(array("valid"=>"false", "html"=>$this->view->render('user/add-user.phtml')))); + $this->_helper->json->sendJson(json_encode(array("valid"=>"false", "html"=>$this->view->render('user/add-user.phtml')))); } } @@ -115,7 +115,7 @@ class UserController extends Zend_Controller_Action $post = $this->getRequest()->getPost(); $users = Application_Model_User::getUsersDataTablesInfo($post); - die(json_encode($users)); + $this->_helper->json->sendJson($users); } public function getUserDataAction() @@ -135,7 +135,7 @@ class UserController extends Zend_Controller_Action && $formData['cu_login'] == 'admin') { $this->view->form = $form; $this->view->successMessage = "
"._("Specific action is not allowed in demo version!")."
"; - die(json_encode(array("html"=>$this->view->render('user/edit-user.phtml')))); + $this->_helper->json->sendJson(json_encode(array("html"=>$this->view->render('user/edit-user.phtml')))); } else if ($form->isValid($formData) && $form->validateLogin($formData['cu_login'], $formData['cu_user_id'])) { $user = new Application_Model_User($formData['cu_user_id']); From 384298f6804e10ebd6e00e5eb74cd7b08432d57f Mon Sep 17 00:00:00 2001 From: denise Date: Wed, 23 Jan 2013 12:09:21 -0500 Subject: [PATCH 09/51] CC-4109: Playlist Builder: Add a button to remove all the items in the current editing playlist --- .../controllers/PlaylistController.php | 21 +++++++++++++++++++ airtime_mvc/application/models/Block.php | 5 ++++- airtime_mvc/application/models/Playlist.php | 4 ++++ .../views/scripts/playlist/playlist.phtml | 3 +++ .../views/scripts/playlist/smart-block.phtml | 3 +++ airtime_mvc/public/js/airtime/library/spl.js | 6 ++++++ .../js/airtime/playlist/smart_blockbuilder.js | 2 +- 7 files changed, 42 insertions(+), 2 deletions(-) diff --git a/airtime_mvc/application/controllers/PlaylistController.php b/airtime_mvc/application/controllers/PlaylistController.php index 789d4a904..11d397bc3 100644 --- a/airtime_mvc/application/controllers/PlaylistController.php +++ b/airtime_mvc/application/controllers/PlaylistController.php @@ -26,6 +26,7 @@ class PlaylistController extends Zend_Controller_Action ->addActionContext('smart-block-shuffle', 'json') ->addActionContext('get-block-info', 'json') ->addActionContext('shuffle', 'json') + ->addActionContext('empty-content', 'json') ->initContext(); } @@ -336,6 +337,26 @@ class PlaylistController extends Zend_Controller_Action $this->playlistUnknownError($e); } } + + public function emptyContentAction() + { + $type = $this->_getParam('obj_type'); + try { + $obj = $this->getPlaylist($type); + if ($type == 'playlist') { + $obj->deleteAllFilesFromPlaylist(); + } else { + $obj->deleteAllFilesFromBlock(); + } + $this->createUpdateResponse($obj); + } catch (PlaylistOutDatedException $e) { + $this->playlistOutdated($e); + } catch (PlaylistNotFoundException $e) { + $this->playlistNotFound($type); + } catch (Exception $e) { + $this->playlistUnknownError($e); + } + } public function setCueAction() { diff --git a/airtime_mvc/application/models/Block.php b/airtime_mvc/application/models/Block.php index cda10c1a3..cb57f0715 100644 --- a/airtime_mvc/application/models/Block.php +++ b/airtime_mvc/application/models/Block.php @@ -1001,7 +1001,10 @@ SQL; public function deleteAllFilesFromBlock() { CcBlockcontentsQuery::create()->findByDbBlockId($this->id)->delete(); - $this->block->reload(); + //$this->block->reload(); + $this->block->setDbMtime(new DateTime("now", new DateTimeZone("UTC"))); + $this->block->save($this->con); + $this->con->commit(); } // smart block functions start diff --git a/airtime_mvc/application/models/Playlist.php b/airtime_mvc/application/models/Playlist.php index 7aaea5243..9eec7ee4f 100644 --- a/airtime_mvc/application/models/Playlist.php +++ b/airtime_mvc/application/models/Playlist.php @@ -930,6 +930,10 @@ SQL; public function deleteAllFilesFromPlaylist() { CcPlaylistcontentsQuery::create()->findByDbPlaylistId($this->id)->delete(); + + $this->pl->setDbMtime(new DateTime("now", new DateTimeZone("UTC"))); + $this->pl->save($this->con); + $this->con->commit(); } public function shuffle() diff --git a/airtime_mvc/application/views/scripts/playlist/playlist.phtml b/airtime_mvc/application/views/scripts/playlist/playlist.phtml index f8496d926..3d388bf36 100644 --- a/airtime_mvc/application/views/scripts/playlist/playlist.phtml +++ b/airtime_mvc/application/views/scripts/playlist/playlist.phtml @@ -16,6 +16,9 @@ if (isset($this->obj)) { obj)) : ?> +
+ +
diff --git a/airtime_mvc/application/views/scripts/playlist/smart-block.phtml b/airtime_mvc/application/views/scripts/playlist/smart-block.phtml index b1ca48da5..1b5b648c3 100644 --- a/airtime_mvc/application/views/scripts/playlist/smart-block.phtml +++ b/airtime_mvc/application/views/scripts/playlist/smart-block.phtml @@ -16,6 +16,9 @@ if (isset($this->obj)) { obj)) : ?> +
+ +
diff --git a/airtime_mvc/public/js/airtime/library/spl.js b/airtime_mvc/public/js/airtime/library/spl.js index 2839ab45d..6c49443ff 100644 --- a/airtime_mvc/public/js/airtime/library/spl.js +++ b/airtime_mvc/public/js/airtime/library/spl.js @@ -749,6 +749,12 @@ var AIRTIME = (function(AIRTIME){ } ); }); + + $("#pl-bl-clear-content").live("click", function(event) { + var sUrl = baseUrl+"playlist/empty-content", + oData = {}; + playlistRequest(sUrl, oData); + }); } function setUpPlaylist() { diff --git a/airtime_mvc/public/js/airtime/playlist/smart_blockbuilder.js b/airtime_mvc/public/js/airtime/playlist/smart_blockbuilder.js index 68bb71fd5..a06ee081e 100644 --- a/airtime_mvc/public/js/airtime/playlist/smart_blockbuilder.js +++ b/airtime_mvc/public/js/airtime/playlist/smart_blockbuilder.js @@ -351,7 +351,7 @@ function setupUI() { * It is only active if playlist is not empty */ var plContents = $('#spl_sortable').children(); - var shuffleButton = $('button[id="shuffle_button"], button[id="playlist_shuffle_button"]'); + var shuffleButton = $('button[id="shuffle_button"], button[id="playlist_shuffle_button"], button[id="pl-bl-clear-content"]'); if (!plContents.hasClass('spl_empty')) { if (shuffleButton.hasClass('ui-state-disabled')) { From 8309593a0f10f1e2264966caa709c60898eac4ad Mon Sep 17 00:00:00 2001 From: denise Date: Fri, 1 Feb 2013 17:47:07 -0500 Subject: [PATCH 10/51] CC-4904: Library -> Sort by status doesn't work - added 2 columns to cc_files (is_scheduled, is_playlist) - split library status column into two columns (scheduled, playlist) - is_scheduled gets updated when a track plays out, or when a file gets added/removed from/to a show - is_playlist gets updated when a file gets added/removed from/to a playlist/block, when a playlist/block gets deleted, or when a playlist/block's contents is cleared --- .../controllers/ScheduleController.php | 8 ++ airtime_mvc/application/models/Block.php | 57 ++++++++- airtime_mvc/application/models/Playlist.php | 52 +++++++++ airtime_mvc/application/models/Scheduler.php | 17 +++ airtime_mvc/application/models/StoredFile.php | 73 ++++++------ .../models/airtime/map/CcFilesTableMap.php | 2 + .../models/airtime/om/BaseCcFiles.php | 110 +++++++++++++++++- .../models/airtime/om/BaseCcFilesPeer.php | 36 +++--- .../models/airtime/om/BaseCcFilesQuery.php | 42 +++++++ airtime_mvc/build/build.properties | 2 +- airtime_mvc/build/schema.xml | 2 + airtime_mvc/build/sql/schema.sql | 2 + .../public/js/airtime/library/library.js | 18 ++- .../public/js/airtime/showbuilder/builder.js | 14 ++- 14 files changed, 376 insertions(+), 59 deletions(-) diff --git a/airtime_mvc/application/controllers/ScheduleController.php b/airtime_mvc/application/controllers/ScheduleController.php index 22257025d..1a22506b4 100644 --- a/airtime_mvc/application/controllers/ScheduleController.php +++ b/airtime_mvc/application/controllers/ScheduleController.php @@ -33,6 +33,7 @@ class ScheduleController extends Zend_Controller_Action ->addActionContext('dj-edit-show', 'json') ->addActionContext('calculate-duration', 'json') ->addActionContext('get-current-show', 'json') + ->addActionContext('update-future-is-scheduled', 'json') ->initContext(); $this->sched_sess = new Zend_Session_Namespace("schedule"); @@ -950,4 +951,11 @@ class ScheduleController extends Zend_Controller_Action echo Zend_Json::encode($result); exit(); } + + public function updateFutureIsScheduledAction() + { + $schedId = $this->_getParam('schedId'); + $redrawLibTable = Application_Model_StoredFile::setIsScheduled($schedId, false); + $this->_helper->json->sendJson(json_encode(array("redrawLibTable" => $redrawLibTable))); + } } diff --git a/airtime_mvc/application/models/Block.php b/airtime_mvc/application/models/Block.php index cb57f0715..de30522d6 100644 --- a/airtime_mvc/application/models/Block.php +++ b/airtime_mvc/application/models/Block.php @@ -476,10 +476,19 @@ SQL; try { if (is_array($ac) && $ac[1] == 'audioclip') { $res = $this->insertBlockElement($this->buildEntry($ac[0], $pos)); + + // update is_playlist flag in cc_files to indicate the + // file belongs to a playlist or block (in this case a block) + $db_file = CcFilesQuery::create()->findPk($ac[0], $this->con); + $db_file->setDbIsPlaylist(true)->save($this->con); + $pos = $pos + 1; } elseif (!is_array($ac)) { $res = $this->insertBlockElement($this->buildEntry($ac, $pos)); $pos = $pos + 1; + + $db_file = CcFilesQuery::create()->findPk($ac, $this->con); + $db_file->setDbIsPlaylist(true)->save($this->con); } } catch (Exception $e) { Logging::info($e->getMessage()); @@ -592,10 +601,21 @@ SQL; try { + // we need to get the file id of the item we are deleting + // before the item gets deleted from the block + $itemsToDelete = CcBlockcontentsQuery::create() + ->filterByPrimaryKeys($p_items) + ->filterByDbFileId(null, Criteria::NOT_EQUAL) + ->find($this->con); + CcBlockcontentsQuery::create() ->findPKs($p_items) ->delete($this->con); + // now that the items have been deleted we can update the + // is_playlist flag in cc_files + Application_Model_StoredFile::setIsPlaylist($itemsToDelete, 'block', false); + $contents = CcBlockcontentsQuery::create() ->filterByDbBlockId($this->id) ->orderByDbPosition() @@ -965,16 +985,36 @@ SQL; $user = new Application_Model_User($userInfo->id); $isAdminOrPM = $user->isUserType(array(UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER)); + // get only the files from the blocks + // we are about to delete + $itemsToDelete = CcBlockcontentsQuery::create() + ->filterByDbBlockId($p_ids) + ->filterByDbFileId(null, Criteria::NOT_EQUAL) + ->find(); + + $updateIsPlaylistFlag = false; + if (!$isAdminOrPM) { $leftOver = self::blocksNotOwnedByUser($p_ids, $p_userId); if (count($leftOver) == 0) { CcBlockQuery::create()->findPKs($p_ids)->delete(); + $updateIsPlaylistFlag = true; } else { throw new BlockNoPermissionException; } } else { CcBlockQuery::create()->findPKs($p_ids)->delete(); + $updateIsPlaylistFlag = true; + } + + if ($updateIsPlaylistFlag) { + // update is_playlist flag in cc_files + Application_Model_StoredFile::setIsPlaylist( + $itemsToDelete, + 'block', + false + ); } } @@ -1000,7 +1040,22 @@ SQL; */ public function deleteAllFilesFromBlock() { + // get only the files from the playlist + // we are about to clear out + $itemsToDelete = CcBlockcontentsQuery::create() + ->filterByDbBlockId($this->id) + ->filterByDbFileId(null, Criteria::NOT_EQUAL) + ->find(); + CcBlockcontentsQuery::create()->findByDbBlockId($this->id)->delete(); + + // update is_playlist flag in cc_files + Application_Model_StoredFile::setIsPlaylist( + $itemsToDelete, + 'block', + false + ); + //$this->block->reload(); $this->block->setDbMtime(new DateTime("now", new DateTimeZone("UTC"))); $this->block->save($this->con); @@ -1437,7 +1492,7 @@ SQL; return $output; } - public static function getAllBlockContent() + public static function getAllBlockFiles() { $con = Propel::getConnection(); $sql = <<insertPlaylistElement($this->buildEntry($ac, $pos)); + + // update is_playlist flag in cc_files to indicate the + // file belongs to a playlist or block (in this case a playlist) + $db_file = CcFilesQuery::create()->findPk($ac[0], $this->con); + $db_file->setDbIsPlaylist(true)->save($this->con); + $pos = $pos + 1; Logging::info("Adding $ac[1] $ac[0]"); @@ -574,10 +580,21 @@ SQL; try { + // we need to get the file id of the item we are deleting + // before the item gets deleted from the playlist + $itemsToDelete = CcPlaylistcontentsQuery::create() + ->filterByPrimaryKeys($p_items) + ->filterByDbFileId(null, Criteria::NOT_EQUAL) + ->find($this->con); + CcPlaylistcontentsQuery::create() ->findPKs($p_items) ->delete($this->con); + // now that the items have been deleted we can update the + // is_playlist flag in cc_files + Application_Model_StoredFile::setIsPlaylist($itemsToDelete, 'playlist', false); + $contents = CcPlaylistcontentsQuery::create() ->filterByDbPlaylistId($this->id) ->orderByDbPosition() @@ -894,15 +911,36 @@ SQL; $user = new Application_Model_User($userInfo->id); $isAdminOrPM = $user->isUserType(array(UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER)); + // get only the files from the playlists + // we are about to delete + $itemsToDelete = CcPlaylistcontentsQuery::create() + ->filterByDbPlaylistId($p_ids) + ->filterByDbFileId(null, Criteria::NOT_EQUAL) + ->find(); + + $updateIsPlaylistFlag = false; + if (!$isAdminOrPM) { $leftOver = self::playlistsNotOwnedByUser($p_ids, $p_userId); if (count($leftOver) == 0) { CcPlaylistQuery::create()->findPKs($p_ids)->delete(); + $updateIsPlaylistFlag = true; + } else { throw new PlaylistNoPermissionException; } } else { CcPlaylistQuery::create()->findPKs($p_ids)->delete(); + $updateIsPlaylistFlag = true; + } + + if ($updateIsPlaylistFlag) { + // update is_playlist flag in cc_files + Application_Model_StoredFile::setIsPlaylist( + $itemsToDelete, + 'playlist', + false + ); } } @@ -929,8 +967,22 @@ SQL; */ public function deleteAllFilesFromPlaylist() { + // get only the files from the playlist + // we are about to clear out + $itemsToDelete = CcPlaylistcontentsQuery::create() + ->filterByDbPlaylistId($this->id) + ->filterByDbFileId(null, Criteria::NOT_EQUAL) + ->find(); + CcPlaylistcontentsQuery::create()->findByDbPlaylistId($this->id)->delete(); + // update is_playlist flag in cc_files + Application_Model_StoredFile::setIsPlaylist( + $itemsToDelete, + 'playlist', + false + ); + $this->pl->setDbMtime(new DateTime("now", new DateTimeZone("UTC"))); $this->pl->save($this->con); $this->con->commit(); diff --git a/airtime_mvc/application/models/Scheduler.php b/airtime_mvc/application/models/Scheduler.php index 2530eb7bf..4aa14bfff 100644 --- a/airtime_mvc/application/models/Scheduler.php +++ b/airtime_mvc/application/models/Scheduler.php @@ -514,6 +514,13 @@ class Application_Model_Scheduler $instance->updateScheduleStatus($this->con); } + // update is_scheduled flag for each cc_file + foreach ($schedFiles as $file) { + $db_file = CcFilesQuery::create()->findPk($file['id'], $this->con); + $db_file->setDbIsScheduled(true); + $db_file->save($this->con); + } + $endProfile = microtime(true); Logging::debug("updating show instances status."); Logging::debug(floatval($endProfile) - floatval($startProfile)); @@ -718,6 +725,16 @@ class Application_Model_Scheduler } else { $removedItem->delete($this->con); } + + // update is_scheduled in cc_files but only if + // the file is not scheduled somewhere else + $fileId = $removedItem->getDbFileId(); + // check if the removed item is scheduled somewhere else + $futureScheduledFiles = Application_Model_Schedule::getAllFutureScheduledFiles(); + if (!is_null($fileId) && !in_array($fileId, $futureScheduledFiles)) { + $db_file = CcFilesQuery::create()->findPk($fileId, $this->con); + $db_file->setDbIsScheduled(false)->save($this->con); + } } if ($adjustSched === true) { diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index 92a46f9c0..98b1bfe6c 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -645,7 +645,7 @@ SQL; "track_number", "mood", "bpm", "composer", "info_url", "bit_rate", "sample_rate", "isrc_number", "encoded_by", "label", "copyright", "mime", "language", "filepath", "owner_id", - "conductor", "replay_gain", "lptime" ); + "conductor", "replay_gain", "lptime", "is_playlist", "is_scheduled" ); } public static function searchLibraryFiles($datatables) @@ -697,6 +697,11 @@ SQL; $blSelect[] = "NULL::TIMESTAMP AS ".$key; $fileSelect[] = $key; $streamSelect[] = $key; + } elseif ($key === "is_scheduled" || $key === "is_playlist") { + $plSelect[] = "NULL::boolean AS ".$key; + $blSelect[] = "NULL::boolean AS ".$key; + $fileSelect[] = $key; + $streamSelect[] = "NULL::boolean AS ".$key; } //same columns in each table. else if (in_array($key, array("length", "utime", "mtime"))) { @@ -771,15 +776,6 @@ SQL; } $results = Application_Model_Datatables::findEntries($con, $displayColumns, $fromTable, $datatables); - - $futureScheduledFiles = Application_Model_Schedule::getAllFutureScheduledFiles(); - // we are only interested in which files belong to playlists and blocks - $playlistBlockFiles = array_merge(Application_Model_Playlist::getAllPlaylistFiles(), - Application_Model_Block::getAllBlockContent()); - - $futureScheduledStreams = Application_Model_Schedule::getAllFutureScheduledWebstreams(); - // here we are only interested in which streams belong to a playlist - $playlistStreams = Application_Model_Playlist::getAllPlaylistStreams(); foreach ($results['aaData'] as &$row) { $row['id'] = intval($row['id']); @@ -798,33 +794,8 @@ SQL; $file = Application_Model_StoredFile::Recall($row['id']); $row['soundcloud_status'] = $file->getSoundCloudId(); - //file 'in use' status - if (in_array($row['id'], $futureScheduledFiles) && in_array($row['id'], $playlistBlockFiles)) { - $row['status_in_use'] = true; - $row['status_msg'] = _("This track is scheduled in the future and belongs to a playlist or smart block"); - } elseif (in_array($row['id'], $futureScheduledFiles)) { - $row['status_in_use'] = true; - $row['status_msg'] = _("This track is scheduled in the future"); - } elseif (in_array($row['id'], $playlistBlockFiles)) { - $row['status_in_use'] = true; - $row['status_msg'] = _("This track belongs to a playlist or smart block"); - } - // for audio preview $row['audioFile'] = $row['id'].".".pathinfo($row['filepath'], PATHINFO_EXTENSION); - } else if ($row['ftype'] === "stream") { - $row['audioFile'] = $row['id']; - - if (in_array($row['id'], $futureScheduledStreams) && in_array($row['id'], $playlistStreams)) { - $row['status_in_use'] = true; - $row['status_msg'] = _("This webstream is scheduled in the future and belongs to a playlist"); - } elseif (in_array($row['id'], $futureScheduledStreams)) { - $row['status_in_use'] = true; - $row['status_msg'] = _("This webstream is scheduled in the future"); - } elseif (in_array($row['id'], $playlistStreams)) { - $row['status_in_use'] = true; - $row['status_msg'] = _("This webstream belongs to a playlist"); - } } else { $row['audioFile'] = $row['id']; } @@ -841,7 +812,6 @@ SQL; // any data from the db for these and datatables will complain $row['checkbox'] = ""; $row['image'] = ""; - $row['status'] = ""; $row['tr_id'] = "{$type}_{$row['id']}"; } @@ -1309,6 +1279,37 @@ SQL; } } } + + public static function setIsPlaylist($p_playlistItems, $p_type, $p_status) { + foreach ($p_playlistItems as $item) { + $file = self::Recall($item->getDbFileId()); + $fileId = $file->_file->getDbId(); + if ($p_type == 'playlist') { + // we have to check if the file is in another playlist before + // we can update + if (!is_null($fileId) && !in_array($fileId, Application_Model_Playlist::getAllPlaylistFiles())) { + $file->_file->setDbIsPlaylist($p_status)->save(); + } + } elseif ($p_type == 'block') { + if (!is_null($fileId) && !in_array($fileId, Application_Model_Block::getAllBlockFiles())) { + $file->_file->setDbIsPlaylist($p_status)->save(); + } + } + } + } + + public static function setIsScheduled($p_scheduleItem, $p_status) { + $fileId = Application_Model_Schedule::GetFileId($p_scheduleItem); + $file = self::Recall($fileId); + $updateIsScheduled = false; + + if (!is_null($fileId) && !in_array($fileId, Application_Model_Schedule::getAllFutureScheduledFiles())) { + $file->_file->setDbIsScheduled($p_status)->save(); + $updateIsScheduled = true; + } + + return $updateIsScheduled; + } } class DeleteScheduledFileException extends Exception {} diff --git a/airtime_mvc/application/models/airtime/map/CcFilesTableMap.php b/airtime_mvc/application/models/airtime/map/CcFilesTableMap.php index 46f79b71e..56e7a5fff 100644 --- a/airtime_mvc/application/models/airtime/map/CcFilesTableMap.php +++ b/airtime_mvc/application/models/airtime/map/CcFilesTableMap.php @@ -106,6 +106,8 @@ class CcFilesTableMap extends TableMap { $this->addColumn('CUEOUT', 'DbCueout', 'VARCHAR', false, null, '00:00:00'); $this->addColumn('SILAN_CHECK', 'DbSilanCheck', 'BOOLEAN', false, null, false); $this->addColumn('HIDDEN', 'DbHidden', 'BOOLEAN', false, null, false); + $this->addColumn('IS_SCHEDULED', 'DbIsScheduled', 'BOOLEAN', false, null, false); + $this->addColumn('IS_PLAYLIST', 'DbIsPlaylist', 'BOOLEAN', false, null, false); // validators } // initialize() diff --git a/airtime_mvc/application/models/airtime/om/BaseCcFiles.php b/airtime_mvc/application/models/airtime/om/BaseCcFiles.php index da3b2cc20..55f868769 100644 --- a/airtime_mvc/application/models/airtime/om/BaseCcFiles.php +++ b/airtime_mvc/application/models/airtime/om/BaseCcFiles.php @@ -444,6 +444,20 @@ abstract class BaseCcFiles extends BaseObject implements Persistent */ protected $hidden; + /** + * The value for the is_scheduled field. + * Note: this column has a database default value of: false + * @var boolean + */ + protected $is_scheduled; + + /** + * The value for the is_playlist field. + * Note: this column has a database default value of: false + * @var boolean + */ + protected $is_playlist; + /** * @var CcSubjs */ @@ -513,6 +527,8 @@ abstract class BaseCcFiles extends BaseObject implements Persistent $this->cueout = '00:00:00'; $this->silan_check = false; $this->hidden = false; + $this->is_scheduled = false; + $this->is_playlist = false; } /** @@ -1297,6 +1313,26 @@ abstract class BaseCcFiles extends BaseObject implements Persistent return $this->hidden; } + /** + * Get the [is_scheduled] column value. + * + * @return boolean + */ + public function getDbIsScheduled() + { + return $this->is_scheduled; + } + + /** + * Get the [is_playlist] column value. + * + * @return boolean + */ + public function getDbIsPlaylist() + { + return $this->is_playlist; + } + /** * Set the value of [id] column. * @@ -2785,6 +2821,46 @@ abstract class BaseCcFiles extends BaseObject implements Persistent return $this; } // setDbHidden() + /** + * Set the value of [is_scheduled] column. + * + * @param boolean $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setDbIsScheduled($v) + { + if ($v !== null) { + $v = (boolean) $v; + } + + if ($this->is_scheduled !== $v || $this->isNew()) { + $this->is_scheduled = $v; + $this->modifiedColumns[] = CcFilesPeer::IS_SCHEDULED; + } + + return $this; + } // setDbIsScheduled() + + /** + * Set the value of [is_playlist] column. + * + * @param boolean $v new value + * @return CcFiles The current object (for fluent API support) + */ + public function setDbIsPlaylist($v) + { + if ($v !== null) { + $v = (boolean) $v; + } + + if ($this->is_playlist !== $v || $this->isNew()) { + $this->is_playlist = $v; + $this->modifiedColumns[] = CcFilesPeer::IS_PLAYLIST; + } + + return $this; + } // setDbIsPlaylist() + /** * Indicates whether the columns in this object are only set to default values. * @@ -2843,6 +2919,14 @@ abstract class BaseCcFiles extends BaseObject implements Persistent return false; } + if ($this->is_scheduled !== false) { + return false; + } + + if ($this->is_playlist !== false) { + return false; + } + // otherwise, everything was equal, so return TRUE return true; } // hasOnlyDefaultValues() @@ -2933,6 +3017,8 @@ abstract class BaseCcFiles extends BaseObject implements Persistent $this->cueout = ($row[$startcol + 65] !== null) ? (string) $row[$startcol + 65] : null; $this->silan_check = ($row[$startcol + 66] !== null) ? (boolean) $row[$startcol + 66] : null; $this->hidden = ($row[$startcol + 67] !== null) ? (boolean) $row[$startcol + 67] : null; + $this->is_scheduled = ($row[$startcol + 68] !== null) ? (boolean) $row[$startcol + 68] : null; + $this->is_playlist = ($row[$startcol + 69] !== null) ? (boolean) $row[$startcol + 69] : null; $this->resetModified(); $this->setNew(false); @@ -2941,7 +3027,7 @@ abstract class BaseCcFiles extends BaseObject implements Persistent $this->ensureConsistency(); } - return $startcol + 68; // 68 = CcFilesPeer::NUM_COLUMNS - CcFilesPeer::NUM_LAZY_LOAD_COLUMNS). + return $startcol + 70; // 70 = CcFilesPeer::NUM_COLUMNS - CcFilesPeer::NUM_LAZY_LOAD_COLUMNS). } catch (Exception $e) { throw new PropelException("Error populating CcFiles object", $e); @@ -3578,6 +3664,12 @@ abstract class BaseCcFiles extends BaseObject implements Persistent case 67: return $this->getDbHidden(); break; + case 68: + return $this->getDbIsScheduled(); + break; + case 69: + return $this->getDbIsPlaylist(); + break; default: return null; break; @@ -3670,6 +3762,8 @@ abstract class BaseCcFiles extends BaseObject implements Persistent $keys[65] => $this->getDbCueout(), $keys[66] => $this->getDbSilanCheck(), $keys[67] => $this->getDbHidden(), + $keys[68] => $this->getDbIsScheduled(), + $keys[69] => $this->getDbIsPlaylist(), ); if ($includeForeignObjects) { if (null !== $this->aFkOwner) { @@ -3916,6 +4010,12 @@ abstract class BaseCcFiles extends BaseObject implements Persistent case 67: $this->setDbHidden($value); break; + case 68: + $this->setDbIsScheduled($value); + break; + case 69: + $this->setDbIsPlaylist($value); + break; } // switch() } @@ -4008,6 +4108,8 @@ abstract class BaseCcFiles extends BaseObject implements Persistent if (array_key_exists($keys[65], $arr)) $this->setDbCueout($arr[$keys[65]]); if (array_key_exists($keys[66], $arr)) $this->setDbSilanCheck($arr[$keys[66]]); if (array_key_exists($keys[67], $arr)) $this->setDbHidden($arr[$keys[67]]); + if (array_key_exists($keys[68], $arr)) $this->setDbIsScheduled($arr[$keys[68]]); + if (array_key_exists($keys[69], $arr)) $this->setDbIsPlaylist($arr[$keys[69]]); } /** @@ -4087,6 +4189,8 @@ abstract class BaseCcFiles extends BaseObject implements Persistent if ($this->isColumnModified(CcFilesPeer::CUEOUT)) $criteria->add(CcFilesPeer::CUEOUT, $this->cueout); if ($this->isColumnModified(CcFilesPeer::SILAN_CHECK)) $criteria->add(CcFilesPeer::SILAN_CHECK, $this->silan_check); if ($this->isColumnModified(CcFilesPeer::HIDDEN)) $criteria->add(CcFilesPeer::HIDDEN, $this->hidden); + if ($this->isColumnModified(CcFilesPeer::IS_SCHEDULED)) $criteria->add(CcFilesPeer::IS_SCHEDULED, $this->is_scheduled); + if ($this->isColumnModified(CcFilesPeer::IS_PLAYLIST)) $criteria->add(CcFilesPeer::IS_PLAYLIST, $this->is_playlist); return $criteria; } @@ -4215,6 +4319,8 @@ abstract class BaseCcFiles extends BaseObject implements Persistent $copyObj->setDbCueout($this->cueout); $copyObj->setDbSilanCheck($this->silan_check); $copyObj->setDbHidden($this->hidden); + $copyObj->setDbIsScheduled($this->is_scheduled); + $copyObj->setDbIsPlaylist($this->is_playlist); if ($deepCopy) { // important: temporarily setNew(false) because this affects the behavior of @@ -5121,6 +5227,8 @@ abstract class BaseCcFiles extends BaseObject implements Persistent $this->cueout = null; $this->silan_check = null; $this->hidden = null; + $this->is_scheduled = null; + $this->is_playlist = null; $this->alreadyInSave = false; $this->alreadyInValidation = false; $this->clearAllReferences(); diff --git a/airtime_mvc/application/models/airtime/om/BaseCcFilesPeer.php b/airtime_mvc/application/models/airtime/om/BaseCcFilesPeer.php index 714cec847..82ebc7851 100644 --- a/airtime_mvc/application/models/airtime/om/BaseCcFilesPeer.php +++ b/airtime_mvc/application/models/airtime/om/BaseCcFilesPeer.php @@ -26,7 +26,7 @@ abstract class BaseCcFilesPeer { const TM_CLASS = 'CcFilesTableMap'; /** The total number of columns. */ - const NUM_COLUMNS = 68; + const NUM_COLUMNS = 70; /** The number of lazy-loaded columns. */ const NUM_LAZY_LOAD_COLUMNS = 0; @@ -235,6 +235,12 @@ abstract class BaseCcFilesPeer { /** the column name for the HIDDEN field */ const HIDDEN = 'cc_files.HIDDEN'; + /** the column name for the IS_SCHEDULED field */ + const IS_SCHEDULED = 'cc_files.IS_SCHEDULED'; + + /** the column name for the IS_PLAYLIST field */ + const IS_PLAYLIST = 'cc_files.IS_PLAYLIST'; + /** * An identiy map to hold any loaded instances of CcFiles objects. * This must be public so that other peer classes can access this when hydrating from JOIN @@ -251,12 +257,12 @@ abstract class BaseCcFilesPeer { * e.g. self::$fieldNames[self::TYPE_PHPNAME][0] = 'Id' */ private static $fieldNames = array ( - BasePeer::TYPE_PHPNAME => array ('DbId', 'DbName', 'DbMime', 'DbFtype', 'DbDirectory', 'DbFilepath', 'DbState', 'DbCurrentlyaccessing', 'DbEditedby', 'DbMtime', 'DbUtime', 'DbLPtime', 'DbMd5', 'DbTrackTitle', 'DbArtistName', 'DbBitRate', 'DbSampleRate', 'DbFormat', 'DbLength', 'DbAlbumTitle', 'DbGenre', 'DbComments', 'DbYear', 'DbTrackNumber', 'DbChannels', 'DbUrl', 'DbBpm', 'DbRating', 'DbEncodedBy', 'DbDiscNumber', 'DbMood', 'DbLabel', 'DbComposer', 'DbEncoder', 'DbChecksum', 'DbLyrics', 'DbOrchestra', 'DbConductor', 'DbLyricist', 'DbOriginalLyricist', 'DbRadioStationName', 'DbInfoUrl', 'DbArtistUrl', 'DbAudioSourceUrl', 'DbRadioStationUrl', 'DbBuyThisUrl', 'DbIsrcNumber', 'DbCatalogNumber', 'DbOriginalArtist', 'DbCopyright', 'DbReportDatetime', 'DbReportLocation', 'DbReportOrganization', 'DbSubject', 'DbContributor', 'DbLanguage', 'DbFileExists', 'DbSoundcloudId', 'DbSoundcloudErrorCode', 'DbSoundcloudErrorMsg', 'DbSoundcloudLinkToFile', 'DbSoundCloundUploadTime', 'DbReplayGain', 'DbOwnerId', 'DbCuein', 'DbCueout', 'DbSilanCheck', 'DbHidden', ), - BasePeer::TYPE_STUDLYPHPNAME => array ('dbId', 'dbName', 'dbMime', 'dbFtype', 'dbDirectory', 'dbFilepath', 'dbState', 'dbCurrentlyaccessing', 'dbEditedby', 'dbMtime', 'dbUtime', 'dbLPtime', 'dbMd5', 'dbTrackTitle', 'dbArtistName', 'dbBitRate', 'dbSampleRate', 'dbFormat', 'dbLength', 'dbAlbumTitle', 'dbGenre', 'dbComments', 'dbYear', 'dbTrackNumber', 'dbChannels', 'dbUrl', 'dbBpm', 'dbRating', 'dbEncodedBy', 'dbDiscNumber', 'dbMood', 'dbLabel', 'dbComposer', 'dbEncoder', 'dbChecksum', 'dbLyrics', 'dbOrchestra', 'dbConductor', 'dbLyricist', 'dbOriginalLyricist', 'dbRadioStationName', 'dbInfoUrl', 'dbArtistUrl', 'dbAudioSourceUrl', 'dbRadioStationUrl', 'dbBuyThisUrl', 'dbIsrcNumber', 'dbCatalogNumber', 'dbOriginalArtist', 'dbCopyright', 'dbReportDatetime', 'dbReportLocation', 'dbReportOrganization', 'dbSubject', 'dbContributor', 'dbLanguage', 'dbFileExists', 'dbSoundcloudId', 'dbSoundcloudErrorCode', 'dbSoundcloudErrorMsg', 'dbSoundcloudLinkToFile', 'dbSoundCloundUploadTime', 'dbReplayGain', 'dbOwnerId', 'dbCuein', 'dbCueout', 'dbSilanCheck', 'dbHidden', ), - BasePeer::TYPE_COLNAME => array (self::ID, self::NAME, self::MIME, self::FTYPE, self::DIRECTORY, self::FILEPATH, self::STATE, self::CURRENTLYACCESSING, self::EDITEDBY, self::MTIME, self::UTIME, self::LPTIME, self::MD5, self::TRACK_TITLE, self::ARTIST_NAME, self::BIT_RATE, self::SAMPLE_RATE, self::FORMAT, self::LENGTH, self::ALBUM_TITLE, self::GENRE, self::COMMENTS, self::YEAR, self::TRACK_NUMBER, self::CHANNELS, self::URL, self::BPM, self::RATING, self::ENCODED_BY, self::DISC_NUMBER, self::MOOD, self::LABEL, self::COMPOSER, self::ENCODER, self::CHECKSUM, self::LYRICS, self::ORCHESTRA, self::CONDUCTOR, self::LYRICIST, self::ORIGINAL_LYRICIST, self::RADIO_STATION_NAME, self::INFO_URL, self::ARTIST_URL, self::AUDIO_SOURCE_URL, self::RADIO_STATION_URL, self::BUY_THIS_URL, self::ISRC_NUMBER, self::CATALOG_NUMBER, self::ORIGINAL_ARTIST, self::COPYRIGHT, self::REPORT_DATETIME, self::REPORT_LOCATION, self::REPORT_ORGANIZATION, self::SUBJECT, self::CONTRIBUTOR, self::LANGUAGE, self::FILE_EXISTS, self::SOUNDCLOUD_ID, self::SOUNDCLOUD_ERROR_CODE, self::SOUNDCLOUD_ERROR_MSG, self::SOUNDCLOUD_LINK_TO_FILE, self::SOUNDCLOUD_UPLOAD_TIME, self::REPLAY_GAIN, self::OWNER_ID, self::CUEIN, self::CUEOUT, self::SILAN_CHECK, self::HIDDEN, ), - BasePeer::TYPE_RAW_COLNAME => array ('ID', 'NAME', 'MIME', 'FTYPE', 'DIRECTORY', 'FILEPATH', 'STATE', 'CURRENTLYACCESSING', 'EDITEDBY', 'MTIME', 'UTIME', 'LPTIME', 'MD5', 'TRACK_TITLE', 'ARTIST_NAME', 'BIT_RATE', 'SAMPLE_RATE', 'FORMAT', 'LENGTH', 'ALBUM_TITLE', 'GENRE', 'COMMENTS', 'YEAR', 'TRACK_NUMBER', 'CHANNELS', 'URL', 'BPM', 'RATING', 'ENCODED_BY', 'DISC_NUMBER', 'MOOD', 'LABEL', 'COMPOSER', 'ENCODER', 'CHECKSUM', 'LYRICS', 'ORCHESTRA', 'CONDUCTOR', 'LYRICIST', 'ORIGINAL_LYRICIST', 'RADIO_STATION_NAME', 'INFO_URL', 'ARTIST_URL', 'AUDIO_SOURCE_URL', 'RADIO_STATION_URL', 'BUY_THIS_URL', 'ISRC_NUMBER', 'CATALOG_NUMBER', 'ORIGINAL_ARTIST', 'COPYRIGHT', 'REPORT_DATETIME', 'REPORT_LOCATION', 'REPORT_ORGANIZATION', 'SUBJECT', 'CONTRIBUTOR', 'LANGUAGE', 'FILE_EXISTS', 'SOUNDCLOUD_ID', 'SOUNDCLOUD_ERROR_CODE', 'SOUNDCLOUD_ERROR_MSG', 'SOUNDCLOUD_LINK_TO_FILE', 'SOUNDCLOUD_UPLOAD_TIME', 'REPLAY_GAIN', 'OWNER_ID', 'CUEIN', 'CUEOUT', 'SILAN_CHECK', 'HIDDEN', ), - BasePeer::TYPE_FIELDNAME => array ('id', 'name', 'mime', 'ftype', 'directory', 'filepath', 'state', 'currentlyaccessing', 'editedby', 'mtime', 'utime', 'lptime', 'md5', 'track_title', 'artist_name', 'bit_rate', 'sample_rate', 'format', 'length', 'album_title', 'genre', 'comments', 'year', 'track_number', 'channels', 'url', 'bpm', 'rating', 'encoded_by', 'disc_number', 'mood', 'label', 'composer', 'encoder', 'checksum', 'lyrics', 'orchestra', 'conductor', 'lyricist', 'original_lyricist', 'radio_station_name', 'info_url', 'artist_url', 'audio_source_url', 'radio_station_url', 'buy_this_url', 'isrc_number', 'catalog_number', 'original_artist', 'copyright', 'report_datetime', 'report_location', 'report_organization', 'subject', 'contributor', 'language', 'file_exists', 'soundcloud_id', 'soundcloud_error_code', 'soundcloud_error_msg', 'soundcloud_link_to_file', 'soundcloud_upload_time', 'replay_gain', 'owner_id', 'cuein', 'cueout', 'silan_check', 'hidden', ), - BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, ) + BasePeer::TYPE_PHPNAME => array ('DbId', 'DbName', 'DbMime', 'DbFtype', 'DbDirectory', 'DbFilepath', 'DbState', 'DbCurrentlyaccessing', 'DbEditedby', 'DbMtime', 'DbUtime', 'DbLPtime', 'DbMd5', 'DbTrackTitle', 'DbArtistName', 'DbBitRate', 'DbSampleRate', 'DbFormat', 'DbLength', 'DbAlbumTitle', 'DbGenre', 'DbComments', 'DbYear', 'DbTrackNumber', 'DbChannels', 'DbUrl', 'DbBpm', 'DbRating', 'DbEncodedBy', 'DbDiscNumber', 'DbMood', 'DbLabel', 'DbComposer', 'DbEncoder', 'DbChecksum', 'DbLyrics', 'DbOrchestra', 'DbConductor', 'DbLyricist', 'DbOriginalLyricist', 'DbRadioStationName', 'DbInfoUrl', 'DbArtistUrl', 'DbAudioSourceUrl', 'DbRadioStationUrl', 'DbBuyThisUrl', 'DbIsrcNumber', 'DbCatalogNumber', 'DbOriginalArtist', 'DbCopyright', 'DbReportDatetime', 'DbReportLocation', 'DbReportOrganization', 'DbSubject', 'DbContributor', 'DbLanguage', 'DbFileExists', 'DbSoundcloudId', 'DbSoundcloudErrorCode', 'DbSoundcloudErrorMsg', 'DbSoundcloudLinkToFile', 'DbSoundCloundUploadTime', 'DbReplayGain', 'DbOwnerId', 'DbCuein', 'DbCueout', 'DbSilanCheck', 'DbHidden', 'DbIsScheduled', 'DbIsPlaylist', ), + BasePeer::TYPE_STUDLYPHPNAME => array ('dbId', 'dbName', 'dbMime', 'dbFtype', 'dbDirectory', 'dbFilepath', 'dbState', 'dbCurrentlyaccessing', 'dbEditedby', 'dbMtime', 'dbUtime', 'dbLPtime', 'dbMd5', 'dbTrackTitle', 'dbArtistName', 'dbBitRate', 'dbSampleRate', 'dbFormat', 'dbLength', 'dbAlbumTitle', 'dbGenre', 'dbComments', 'dbYear', 'dbTrackNumber', 'dbChannels', 'dbUrl', 'dbBpm', 'dbRating', 'dbEncodedBy', 'dbDiscNumber', 'dbMood', 'dbLabel', 'dbComposer', 'dbEncoder', 'dbChecksum', 'dbLyrics', 'dbOrchestra', 'dbConductor', 'dbLyricist', 'dbOriginalLyricist', 'dbRadioStationName', 'dbInfoUrl', 'dbArtistUrl', 'dbAudioSourceUrl', 'dbRadioStationUrl', 'dbBuyThisUrl', 'dbIsrcNumber', 'dbCatalogNumber', 'dbOriginalArtist', 'dbCopyright', 'dbReportDatetime', 'dbReportLocation', 'dbReportOrganization', 'dbSubject', 'dbContributor', 'dbLanguage', 'dbFileExists', 'dbSoundcloudId', 'dbSoundcloudErrorCode', 'dbSoundcloudErrorMsg', 'dbSoundcloudLinkToFile', 'dbSoundCloundUploadTime', 'dbReplayGain', 'dbOwnerId', 'dbCuein', 'dbCueout', 'dbSilanCheck', 'dbHidden', 'dbIsScheduled', 'dbIsPlaylist', ), + BasePeer::TYPE_COLNAME => array (self::ID, self::NAME, self::MIME, self::FTYPE, self::DIRECTORY, self::FILEPATH, self::STATE, self::CURRENTLYACCESSING, self::EDITEDBY, self::MTIME, self::UTIME, self::LPTIME, self::MD5, self::TRACK_TITLE, self::ARTIST_NAME, self::BIT_RATE, self::SAMPLE_RATE, self::FORMAT, self::LENGTH, self::ALBUM_TITLE, self::GENRE, self::COMMENTS, self::YEAR, self::TRACK_NUMBER, self::CHANNELS, self::URL, self::BPM, self::RATING, self::ENCODED_BY, self::DISC_NUMBER, self::MOOD, self::LABEL, self::COMPOSER, self::ENCODER, self::CHECKSUM, self::LYRICS, self::ORCHESTRA, self::CONDUCTOR, self::LYRICIST, self::ORIGINAL_LYRICIST, self::RADIO_STATION_NAME, self::INFO_URL, self::ARTIST_URL, self::AUDIO_SOURCE_URL, self::RADIO_STATION_URL, self::BUY_THIS_URL, self::ISRC_NUMBER, self::CATALOG_NUMBER, self::ORIGINAL_ARTIST, self::COPYRIGHT, self::REPORT_DATETIME, self::REPORT_LOCATION, self::REPORT_ORGANIZATION, self::SUBJECT, self::CONTRIBUTOR, self::LANGUAGE, self::FILE_EXISTS, self::SOUNDCLOUD_ID, self::SOUNDCLOUD_ERROR_CODE, self::SOUNDCLOUD_ERROR_MSG, self::SOUNDCLOUD_LINK_TO_FILE, self::SOUNDCLOUD_UPLOAD_TIME, self::REPLAY_GAIN, self::OWNER_ID, self::CUEIN, self::CUEOUT, self::SILAN_CHECK, self::HIDDEN, self::IS_SCHEDULED, self::IS_PLAYLIST, ), + BasePeer::TYPE_RAW_COLNAME => array ('ID', 'NAME', 'MIME', 'FTYPE', 'DIRECTORY', 'FILEPATH', 'STATE', 'CURRENTLYACCESSING', 'EDITEDBY', 'MTIME', 'UTIME', 'LPTIME', 'MD5', 'TRACK_TITLE', 'ARTIST_NAME', 'BIT_RATE', 'SAMPLE_RATE', 'FORMAT', 'LENGTH', 'ALBUM_TITLE', 'GENRE', 'COMMENTS', 'YEAR', 'TRACK_NUMBER', 'CHANNELS', 'URL', 'BPM', 'RATING', 'ENCODED_BY', 'DISC_NUMBER', 'MOOD', 'LABEL', 'COMPOSER', 'ENCODER', 'CHECKSUM', 'LYRICS', 'ORCHESTRA', 'CONDUCTOR', 'LYRICIST', 'ORIGINAL_LYRICIST', 'RADIO_STATION_NAME', 'INFO_URL', 'ARTIST_URL', 'AUDIO_SOURCE_URL', 'RADIO_STATION_URL', 'BUY_THIS_URL', 'ISRC_NUMBER', 'CATALOG_NUMBER', 'ORIGINAL_ARTIST', 'COPYRIGHT', 'REPORT_DATETIME', 'REPORT_LOCATION', 'REPORT_ORGANIZATION', 'SUBJECT', 'CONTRIBUTOR', 'LANGUAGE', 'FILE_EXISTS', 'SOUNDCLOUD_ID', 'SOUNDCLOUD_ERROR_CODE', 'SOUNDCLOUD_ERROR_MSG', 'SOUNDCLOUD_LINK_TO_FILE', 'SOUNDCLOUD_UPLOAD_TIME', 'REPLAY_GAIN', 'OWNER_ID', 'CUEIN', 'CUEOUT', 'SILAN_CHECK', 'HIDDEN', 'IS_SCHEDULED', 'IS_PLAYLIST', ), + BasePeer::TYPE_FIELDNAME => array ('id', 'name', 'mime', 'ftype', 'directory', 'filepath', 'state', 'currentlyaccessing', 'editedby', 'mtime', 'utime', 'lptime', 'md5', 'track_title', 'artist_name', 'bit_rate', 'sample_rate', 'format', 'length', 'album_title', 'genre', 'comments', 'year', 'track_number', 'channels', 'url', 'bpm', 'rating', 'encoded_by', 'disc_number', 'mood', 'label', 'composer', 'encoder', 'checksum', 'lyrics', 'orchestra', 'conductor', 'lyricist', 'original_lyricist', 'radio_station_name', 'info_url', 'artist_url', 'audio_source_url', 'radio_station_url', 'buy_this_url', 'isrc_number', 'catalog_number', 'original_artist', 'copyright', 'report_datetime', 'report_location', 'report_organization', 'subject', 'contributor', 'language', 'file_exists', 'soundcloud_id', 'soundcloud_error_code', 'soundcloud_error_msg', 'soundcloud_link_to_file', 'soundcloud_upload_time', 'replay_gain', 'owner_id', 'cuein', 'cueout', 'silan_check', 'hidden', 'is_scheduled', 'is_playlist', ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, ) ); /** @@ -266,12 +272,12 @@ abstract class BaseCcFilesPeer { * e.g. self::$fieldNames[BasePeer::TYPE_PHPNAME]['Id'] = 0 */ private static $fieldKeys = array ( - BasePeer::TYPE_PHPNAME => array ('DbId' => 0, 'DbName' => 1, 'DbMime' => 2, 'DbFtype' => 3, 'DbDirectory' => 4, 'DbFilepath' => 5, 'DbState' => 6, 'DbCurrentlyaccessing' => 7, 'DbEditedby' => 8, 'DbMtime' => 9, 'DbUtime' => 10, 'DbLPtime' => 11, 'DbMd5' => 12, 'DbTrackTitle' => 13, 'DbArtistName' => 14, 'DbBitRate' => 15, 'DbSampleRate' => 16, 'DbFormat' => 17, 'DbLength' => 18, 'DbAlbumTitle' => 19, 'DbGenre' => 20, 'DbComments' => 21, 'DbYear' => 22, 'DbTrackNumber' => 23, 'DbChannels' => 24, 'DbUrl' => 25, 'DbBpm' => 26, 'DbRating' => 27, 'DbEncodedBy' => 28, 'DbDiscNumber' => 29, 'DbMood' => 30, 'DbLabel' => 31, 'DbComposer' => 32, 'DbEncoder' => 33, 'DbChecksum' => 34, 'DbLyrics' => 35, 'DbOrchestra' => 36, 'DbConductor' => 37, 'DbLyricist' => 38, 'DbOriginalLyricist' => 39, 'DbRadioStationName' => 40, 'DbInfoUrl' => 41, 'DbArtistUrl' => 42, 'DbAudioSourceUrl' => 43, 'DbRadioStationUrl' => 44, 'DbBuyThisUrl' => 45, 'DbIsrcNumber' => 46, 'DbCatalogNumber' => 47, 'DbOriginalArtist' => 48, 'DbCopyright' => 49, 'DbReportDatetime' => 50, 'DbReportLocation' => 51, 'DbReportOrganization' => 52, 'DbSubject' => 53, 'DbContributor' => 54, 'DbLanguage' => 55, 'DbFileExists' => 56, 'DbSoundcloudId' => 57, 'DbSoundcloudErrorCode' => 58, 'DbSoundcloudErrorMsg' => 59, 'DbSoundcloudLinkToFile' => 60, 'DbSoundCloundUploadTime' => 61, 'DbReplayGain' => 62, 'DbOwnerId' => 63, 'DbCuein' => 64, 'DbCueout' => 65, 'DbSilanCheck' => 66, 'DbHidden' => 67, ), - BasePeer::TYPE_STUDLYPHPNAME => array ('dbId' => 0, 'dbName' => 1, 'dbMime' => 2, 'dbFtype' => 3, 'dbDirectory' => 4, 'dbFilepath' => 5, 'dbState' => 6, 'dbCurrentlyaccessing' => 7, 'dbEditedby' => 8, 'dbMtime' => 9, 'dbUtime' => 10, 'dbLPtime' => 11, 'dbMd5' => 12, 'dbTrackTitle' => 13, 'dbArtistName' => 14, 'dbBitRate' => 15, 'dbSampleRate' => 16, 'dbFormat' => 17, 'dbLength' => 18, 'dbAlbumTitle' => 19, 'dbGenre' => 20, 'dbComments' => 21, 'dbYear' => 22, 'dbTrackNumber' => 23, 'dbChannels' => 24, 'dbUrl' => 25, 'dbBpm' => 26, 'dbRating' => 27, 'dbEncodedBy' => 28, 'dbDiscNumber' => 29, 'dbMood' => 30, 'dbLabel' => 31, 'dbComposer' => 32, 'dbEncoder' => 33, 'dbChecksum' => 34, 'dbLyrics' => 35, 'dbOrchestra' => 36, 'dbConductor' => 37, 'dbLyricist' => 38, 'dbOriginalLyricist' => 39, 'dbRadioStationName' => 40, 'dbInfoUrl' => 41, 'dbArtistUrl' => 42, 'dbAudioSourceUrl' => 43, 'dbRadioStationUrl' => 44, 'dbBuyThisUrl' => 45, 'dbIsrcNumber' => 46, 'dbCatalogNumber' => 47, 'dbOriginalArtist' => 48, 'dbCopyright' => 49, 'dbReportDatetime' => 50, 'dbReportLocation' => 51, 'dbReportOrganization' => 52, 'dbSubject' => 53, 'dbContributor' => 54, 'dbLanguage' => 55, 'dbFileExists' => 56, 'dbSoundcloudId' => 57, 'dbSoundcloudErrorCode' => 58, 'dbSoundcloudErrorMsg' => 59, 'dbSoundcloudLinkToFile' => 60, 'dbSoundCloundUploadTime' => 61, 'dbReplayGain' => 62, 'dbOwnerId' => 63, 'dbCuein' => 64, 'dbCueout' => 65, 'dbSilanCheck' => 66, 'dbHidden' => 67, ), - BasePeer::TYPE_COLNAME => array (self::ID => 0, self::NAME => 1, self::MIME => 2, self::FTYPE => 3, self::DIRECTORY => 4, self::FILEPATH => 5, self::STATE => 6, self::CURRENTLYACCESSING => 7, self::EDITEDBY => 8, self::MTIME => 9, self::UTIME => 10, self::LPTIME => 11, self::MD5 => 12, self::TRACK_TITLE => 13, self::ARTIST_NAME => 14, self::BIT_RATE => 15, self::SAMPLE_RATE => 16, self::FORMAT => 17, self::LENGTH => 18, self::ALBUM_TITLE => 19, self::GENRE => 20, self::COMMENTS => 21, self::YEAR => 22, self::TRACK_NUMBER => 23, self::CHANNELS => 24, self::URL => 25, self::BPM => 26, self::RATING => 27, self::ENCODED_BY => 28, self::DISC_NUMBER => 29, self::MOOD => 30, self::LABEL => 31, self::COMPOSER => 32, self::ENCODER => 33, self::CHECKSUM => 34, self::LYRICS => 35, self::ORCHESTRA => 36, self::CONDUCTOR => 37, self::LYRICIST => 38, self::ORIGINAL_LYRICIST => 39, self::RADIO_STATION_NAME => 40, self::INFO_URL => 41, self::ARTIST_URL => 42, self::AUDIO_SOURCE_URL => 43, self::RADIO_STATION_URL => 44, self::BUY_THIS_URL => 45, self::ISRC_NUMBER => 46, self::CATALOG_NUMBER => 47, self::ORIGINAL_ARTIST => 48, self::COPYRIGHT => 49, self::REPORT_DATETIME => 50, self::REPORT_LOCATION => 51, self::REPORT_ORGANIZATION => 52, self::SUBJECT => 53, self::CONTRIBUTOR => 54, self::LANGUAGE => 55, self::FILE_EXISTS => 56, self::SOUNDCLOUD_ID => 57, self::SOUNDCLOUD_ERROR_CODE => 58, self::SOUNDCLOUD_ERROR_MSG => 59, self::SOUNDCLOUD_LINK_TO_FILE => 60, self::SOUNDCLOUD_UPLOAD_TIME => 61, self::REPLAY_GAIN => 62, self::OWNER_ID => 63, self::CUEIN => 64, self::CUEOUT => 65, self::SILAN_CHECK => 66, self::HIDDEN => 67, ), - BasePeer::TYPE_RAW_COLNAME => array ('ID' => 0, 'NAME' => 1, 'MIME' => 2, 'FTYPE' => 3, 'DIRECTORY' => 4, 'FILEPATH' => 5, 'STATE' => 6, 'CURRENTLYACCESSING' => 7, 'EDITEDBY' => 8, 'MTIME' => 9, 'UTIME' => 10, 'LPTIME' => 11, 'MD5' => 12, 'TRACK_TITLE' => 13, 'ARTIST_NAME' => 14, 'BIT_RATE' => 15, 'SAMPLE_RATE' => 16, 'FORMAT' => 17, 'LENGTH' => 18, 'ALBUM_TITLE' => 19, 'GENRE' => 20, 'COMMENTS' => 21, 'YEAR' => 22, 'TRACK_NUMBER' => 23, 'CHANNELS' => 24, 'URL' => 25, 'BPM' => 26, 'RATING' => 27, 'ENCODED_BY' => 28, 'DISC_NUMBER' => 29, 'MOOD' => 30, 'LABEL' => 31, 'COMPOSER' => 32, 'ENCODER' => 33, 'CHECKSUM' => 34, 'LYRICS' => 35, 'ORCHESTRA' => 36, 'CONDUCTOR' => 37, 'LYRICIST' => 38, 'ORIGINAL_LYRICIST' => 39, 'RADIO_STATION_NAME' => 40, 'INFO_URL' => 41, 'ARTIST_URL' => 42, 'AUDIO_SOURCE_URL' => 43, 'RADIO_STATION_URL' => 44, 'BUY_THIS_URL' => 45, 'ISRC_NUMBER' => 46, 'CATALOG_NUMBER' => 47, 'ORIGINAL_ARTIST' => 48, 'COPYRIGHT' => 49, 'REPORT_DATETIME' => 50, 'REPORT_LOCATION' => 51, 'REPORT_ORGANIZATION' => 52, 'SUBJECT' => 53, 'CONTRIBUTOR' => 54, 'LANGUAGE' => 55, 'FILE_EXISTS' => 56, 'SOUNDCLOUD_ID' => 57, 'SOUNDCLOUD_ERROR_CODE' => 58, 'SOUNDCLOUD_ERROR_MSG' => 59, 'SOUNDCLOUD_LINK_TO_FILE' => 60, 'SOUNDCLOUD_UPLOAD_TIME' => 61, 'REPLAY_GAIN' => 62, 'OWNER_ID' => 63, 'CUEIN' => 64, 'CUEOUT' => 65, 'SILAN_CHECK' => 66, 'HIDDEN' => 67, ), - BasePeer::TYPE_FIELDNAME => array ('id' => 0, 'name' => 1, 'mime' => 2, 'ftype' => 3, 'directory' => 4, 'filepath' => 5, 'state' => 6, 'currentlyaccessing' => 7, 'editedby' => 8, 'mtime' => 9, 'utime' => 10, 'lptime' => 11, 'md5' => 12, 'track_title' => 13, 'artist_name' => 14, 'bit_rate' => 15, 'sample_rate' => 16, 'format' => 17, 'length' => 18, 'album_title' => 19, 'genre' => 20, 'comments' => 21, 'year' => 22, 'track_number' => 23, 'channels' => 24, 'url' => 25, 'bpm' => 26, 'rating' => 27, 'encoded_by' => 28, 'disc_number' => 29, 'mood' => 30, 'label' => 31, 'composer' => 32, 'encoder' => 33, 'checksum' => 34, 'lyrics' => 35, 'orchestra' => 36, 'conductor' => 37, 'lyricist' => 38, 'original_lyricist' => 39, 'radio_station_name' => 40, 'info_url' => 41, 'artist_url' => 42, 'audio_source_url' => 43, 'radio_station_url' => 44, 'buy_this_url' => 45, 'isrc_number' => 46, 'catalog_number' => 47, 'original_artist' => 48, 'copyright' => 49, 'report_datetime' => 50, 'report_location' => 51, 'report_organization' => 52, 'subject' => 53, 'contributor' => 54, 'language' => 55, 'file_exists' => 56, 'soundcloud_id' => 57, 'soundcloud_error_code' => 58, 'soundcloud_error_msg' => 59, 'soundcloud_link_to_file' => 60, 'soundcloud_upload_time' => 61, 'replay_gain' => 62, 'owner_id' => 63, 'cuein' => 64, 'cueout' => 65, 'silan_check' => 66, 'hidden' => 67, ), - BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, ) + BasePeer::TYPE_PHPNAME => array ('DbId' => 0, 'DbName' => 1, 'DbMime' => 2, 'DbFtype' => 3, 'DbDirectory' => 4, 'DbFilepath' => 5, 'DbState' => 6, 'DbCurrentlyaccessing' => 7, 'DbEditedby' => 8, 'DbMtime' => 9, 'DbUtime' => 10, 'DbLPtime' => 11, 'DbMd5' => 12, 'DbTrackTitle' => 13, 'DbArtistName' => 14, 'DbBitRate' => 15, 'DbSampleRate' => 16, 'DbFormat' => 17, 'DbLength' => 18, 'DbAlbumTitle' => 19, 'DbGenre' => 20, 'DbComments' => 21, 'DbYear' => 22, 'DbTrackNumber' => 23, 'DbChannels' => 24, 'DbUrl' => 25, 'DbBpm' => 26, 'DbRating' => 27, 'DbEncodedBy' => 28, 'DbDiscNumber' => 29, 'DbMood' => 30, 'DbLabel' => 31, 'DbComposer' => 32, 'DbEncoder' => 33, 'DbChecksum' => 34, 'DbLyrics' => 35, 'DbOrchestra' => 36, 'DbConductor' => 37, 'DbLyricist' => 38, 'DbOriginalLyricist' => 39, 'DbRadioStationName' => 40, 'DbInfoUrl' => 41, 'DbArtistUrl' => 42, 'DbAudioSourceUrl' => 43, 'DbRadioStationUrl' => 44, 'DbBuyThisUrl' => 45, 'DbIsrcNumber' => 46, 'DbCatalogNumber' => 47, 'DbOriginalArtist' => 48, 'DbCopyright' => 49, 'DbReportDatetime' => 50, 'DbReportLocation' => 51, 'DbReportOrganization' => 52, 'DbSubject' => 53, 'DbContributor' => 54, 'DbLanguage' => 55, 'DbFileExists' => 56, 'DbSoundcloudId' => 57, 'DbSoundcloudErrorCode' => 58, 'DbSoundcloudErrorMsg' => 59, 'DbSoundcloudLinkToFile' => 60, 'DbSoundCloundUploadTime' => 61, 'DbReplayGain' => 62, 'DbOwnerId' => 63, 'DbCuein' => 64, 'DbCueout' => 65, 'DbSilanCheck' => 66, 'DbHidden' => 67, 'DbIsScheduled' => 68, 'DbIsPlaylist' => 69, ), + BasePeer::TYPE_STUDLYPHPNAME => array ('dbId' => 0, 'dbName' => 1, 'dbMime' => 2, 'dbFtype' => 3, 'dbDirectory' => 4, 'dbFilepath' => 5, 'dbState' => 6, 'dbCurrentlyaccessing' => 7, 'dbEditedby' => 8, 'dbMtime' => 9, 'dbUtime' => 10, 'dbLPtime' => 11, 'dbMd5' => 12, 'dbTrackTitle' => 13, 'dbArtistName' => 14, 'dbBitRate' => 15, 'dbSampleRate' => 16, 'dbFormat' => 17, 'dbLength' => 18, 'dbAlbumTitle' => 19, 'dbGenre' => 20, 'dbComments' => 21, 'dbYear' => 22, 'dbTrackNumber' => 23, 'dbChannels' => 24, 'dbUrl' => 25, 'dbBpm' => 26, 'dbRating' => 27, 'dbEncodedBy' => 28, 'dbDiscNumber' => 29, 'dbMood' => 30, 'dbLabel' => 31, 'dbComposer' => 32, 'dbEncoder' => 33, 'dbChecksum' => 34, 'dbLyrics' => 35, 'dbOrchestra' => 36, 'dbConductor' => 37, 'dbLyricist' => 38, 'dbOriginalLyricist' => 39, 'dbRadioStationName' => 40, 'dbInfoUrl' => 41, 'dbArtistUrl' => 42, 'dbAudioSourceUrl' => 43, 'dbRadioStationUrl' => 44, 'dbBuyThisUrl' => 45, 'dbIsrcNumber' => 46, 'dbCatalogNumber' => 47, 'dbOriginalArtist' => 48, 'dbCopyright' => 49, 'dbReportDatetime' => 50, 'dbReportLocation' => 51, 'dbReportOrganization' => 52, 'dbSubject' => 53, 'dbContributor' => 54, 'dbLanguage' => 55, 'dbFileExists' => 56, 'dbSoundcloudId' => 57, 'dbSoundcloudErrorCode' => 58, 'dbSoundcloudErrorMsg' => 59, 'dbSoundcloudLinkToFile' => 60, 'dbSoundCloundUploadTime' => 61, 'dbReplayGain' => 62, 'dbOwnerId' => 63, 'dbCuein' => 64, 'dbCueout' => 65, 'dbSilanCheck' => 66, 'dbHidden' => 67, 'dbIsScheduled' => 68, 'dbIsPlaylist' => 69, ), + BasePeer::TYPE_COLNAME => array (self::ID => 0, self::NAME => 1, self::MIME => 2, self::FTYPE => 3, self::DIRECTORY => 4, self::FILEPATH => 5, self::STATE => 6, self::CURRENTLYACCESSING => 7, self::EDITEDBY => 8, self::MTIME => 9, self::UTIME => 10, self::LPTIME => 11, self::MD5 => 12, self::TRACK_TITLE => 13, self::ARTIST_NAME => 14, self::BIT_RATE => 15, self::SAMPLE_RATE => 16, self::FORMAT => 17, self::LENGTH => 18, self::ALBUM_TITLE => 19, self::GENRE => 20, self::COMMENTS => 21, self::YEAR => 22, self::TRACK_NUMBER => 23, self::CHANNELS => 24, self::URL => 25, self::BPM => 26, self::RATING => 27, self::ENCODED_BY => 28, self::DISC_NUMBER => 29, self::MOOD => 30, self::LABEL => 31, self::COMPOSER => 32, self::ENCODER => 33, self::CHECKSUM => 34, self::LYRICS => 35, self::ORCHESTRA => 36, self::CONDUCTOR => 37, self::LYRICIST => 38, self::ORIGINAL_LYRICIST => 39, self::RADIO_STATION_NAME => 40, self::INFO_URL => 41, self::ARTIST_URL => 42, self::AUDIO_SOURCE_URL => 43, self::RADIO_STATION_URL => 44, self::BUY_THIS_URL => 45, self::ISRC_NUMBER => 46, self::CATALOG_NUMBER => 47, self::ORIGINAL_ARTIST => 48, self::COPYRIGHT => 49, self::REPORT_DATETIME => 50, self::REPORT_LOCATION => 51, self::REPORT_ORGANIZATION => 52, self::SUBJECT => 53, self::CONTRIBUTOR => 54, self::LANGUAGE => 55, self::FILE_EXISTS => 56, self::SOUNDCLOUD_ID => 57, self::SOUNDCLOUD_ERROR_CODE => 58, self::SOUNDCLOUD_ERROR_MSG => 59, self::SOUNDCLOUD_LINK_TO_FILE => 60, self::SOUNDCLOUD_UPLOAD_TIME => 61, self::REPLAY_GAIN => 62, self::OWNER_ID => 63, self::CUEIN => 64, self::CUEOUT => 65, self::SILAN_CHECK => 66, self::HIDDEN => 67, self::IS_SCHEDULED => 68, self::IS_PLAYLIST => 69, ), + BasePeer::TYPE_RAW_COLNAME => array ('ID' => 0, 'NAME' => 1, 'MIME' => 2, 'FTYPE' => 3, 'DIRECTORY' => 4, 'FILEPATH' => 5, 'STATE' => 6, 'CURRENTLYACCESSING' => 7, 'EDITEDBY' => 8, 'MTIME' => 9, 'UTIME' => 10, 'LPTIME' => 11, 'MD5' => 12, 'TRACK_TITLE' => 13, 'ARTIST_NAME' => 14, 'BIT_RATE' => 15, 'SAMPLE_RATE' => 16, 'FORMAT' => 17, 'LENGTH' => 18, 'ALBUM_TITLE' => 19, 'GENRE' => 20, 'COMMENTS' => 21, 'YEAR' => 22, 'TRACK_NUMBER' => 23, 'CHANNELS' => 24, 'URL' => 25, 'BPM' => 26, 'RATING' => 27, 'ENCODED_BY' => 28, 'DISC_NUMBER' => 29, 'MOOD' => 30, 'LABEL' => 31, 'COMPOSER' => 32, 'ENCODER' => 33, 'CHECKSUM' => 34, 'LYRICS' => 35, 'ORCHESTRA' => 36, 'CONDUCTOR' => 37, 'LYRICIST' => 38, 'ORIGINAL_LYRICIST' => 39, 'RADIO_STATION_NAME' => 40, 'INFO_URL' => 41, 'ARTIST_URL' => 42, 'AUDIO_SOURCE_URL' => 43, 'RADIO_STATION_URL' => 44, 'BUY_THIS_URL' => 45, 'ISRC_NUMBER' => 46, 'CATALOG_NUMBER' => 47, 'ORIGINAL_ARTIST' => 48, 'COPYRIGHT' => 49, 'REPORT_DATETIME' => 50, 'REPORT_LOCATION' => 51, 'REPORT_ORGANIZATION' => 52, 'SUBJECT' => 53, 'CONTRIBUTOR' => 54, 'LANGUAGE' => 55, 'FILE_EXISTS' => 56, 'SOUNDCLOUD_ID' => 57, 'SOUNDCLOUD_ERROR_CODE' => 58, 'SOUNDCLOUD_ERROR_MSG' => 59, 'SOUNDCLOUD_LINK_TO_FILE' => 60, 'SOUNDCLOUD_UPLOAD_TIME' => 61, 'REPLAY_GAIN' => 62, 'OWNER_ID' => 63, 'CUEIN' => 64, 'CUEOUT' => 65, 'SILAN_CHECK' => 66, 'HIDDEN' => 67, 'IS_SCHEDULED' => 68, 'IS_PLAYLIST' => 69, ), + BasePeer::TYPE_FIELDNAME => array ('id' => 0, 'name' => 1, 'mime' => 2, 'ftype' => 3, 'directory' => 4, 'filepath' => 5, 'state' => 6, 'currentlyaccessing' => 7, 'editedby' => 8, 'mtime' => 9, 'utime' => 10, 'lptime' => 11, 'md5' => 12, 'track_title' => 13, 'artist_name' => 14, 'bit_rate' => 15, 'sample_rate' => 16, 'format' => 17, 'length' => 18, 'album_title' => 19, 'genre' => 20, 'comments' => 21, 'year' => 22, 'track_number' => 23, 'channels' => 24, 'url' => 25, 'bpm' => 26, 'rating' => 27, 'encoded_by' => 28, 'disc_number' => 29, 'mood' => 30, 'label' => 31, 'composer' => 32, 'encoder' => 33, 'checksum' => 34, 'lyrics' => 35, 'orchestra' => 36, 'conductor' => 37, 'lyricist' => 38, 'original_lyricist' => 39, 'radio_station_name' => 40, 'info_url' => 41, 'artist_url' => 42, 'audio_source_url' => 43, 'radio_station_url' => 44, 'buy_this_url' => 45, 'isrc_number' => 46, 'catalog_number' => 47, 'original_artist' => 48, 'copyright' => 49, 'report_datetime' => 50, 'report_location' => 51, 'report_organization' => 52, 'subject' => 53, 'contributor' => 54, 'language' => 55, 'file_exists' => 56, 'soundcloud_id' => 57, 'soundcloud_error_code' => 58, 'soundcloud_error_msg' => 59, 'soundcloud_link_to_file' => 60, 'soundcloud_upload_time' => 61, 'replay_gain' => 62, 'owner_id' => 63, 'cuein' => 64, 'cueout' => 65, 'silan_check' => 66, 'hidden' => 67, 'is_scheduled' => 68, 'is_playlist' => 69, ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, ) ); /** @@ -411,6 +417,8 @@ abstract class BaseCcFilesPeer { $criteria->addSelectColumn(CcFilesPeer::CUEOUT); $criteria->addSelectColumn(CcFilesPeer::SILAN_CHECK); $criteria->addSelectColumn(CcFilesPeer::HIDDEN); + $criteria->addSelectColumn(CcFilesPeer::IS_SCHEDULED); + $criteria->addSelectColumn(CcFilesPeer::IS_PLAYLIST); } else { $criteria->addSelectColumn($alias . '.ID'); $criteria->addSelectColumn($alias . '.NAME'); @@ -480,6 +488,8 @@ abstract class BaseCcFilesPeer { $criteria->addSelectColumn($alias . '.CUEOUT'); $criteria->addSelectColumn($alias . '.SILAN_CHECK'); $criteria->addSelectColumn($alias . '.HIDDEN'); + $criteria->addSelectColumn($alias . '.IS_SCHEDULED'); + $criteria->addSelectColumn($alias . '.IS_PLAYLIST'); } } diff --git a/airtime_mvc/application/models/airtime/om/BaseCcFilesQuery.php b/airtime_mvc/application/models/airtime/om/BaseCcFilesQuery.php index 775895eb8..fe42ad88c 100644 --- a/airtime_mvc/application/models/airtime/om/BaseCcFilesQuery.php +++ b/airtime_mvc/application/models/airtime/om/BaseCcFilesQuery.php @@ -74,6 +74,8 @@ * @method CcFilesQuery orderByDbCueout($order = Criteria::ASC) Order by the cueout column * @method CcFilesQuery orderByDbSilanCheck($order = Criteria::ASC) Order by the silan_check column * @method CcFilesQuery orderByDbHidden($order = Criteria::ASC) Order by the hidden column + * @method CcFilesQuery orderByDbIsScheduled($order = Criteria::ASC) Order by the is_scheduled column + * @method CcFilesQuery orderByDbIsPlaylist($order = Criteria::ASC) Order by the is_playlist column * * @method CcFilesQuery groupByDbId() Group by the id column * @method CcFilesQuery groupByDbName() Group by the name column @@ -143,6 +145,8 @@ * @method CcFilesQuery groupByDbCueout() Group by the cueout column * @method CcFilesQuery groupByDbSilanCheck() Group by the silan_check column * @method CcFilesQuery groupByDbHidden() Group by the hidden column + * @method CcFilesQuery groupByDbIsScheduled() Group by the is_scheduled column + * @method CcFilesQuery groupByDbIsPlaylist() Group by the is_playlist column * * @method CcFilesQuery leftJoin($relation) Adds a LEFT JOIN clause to the query * @method CcFilesQuery rightJoin($relation) Adds a RIGHT JOIN clause to the query @@ -247,6 +251,8 @@ * @method CcFiles findOneByDbCueout(string $cueout) Return the first CcFiles filtered by the cueout column * @method CcFiles findOneByDbSilanCheck(boolean $silan_check) Return the first CcFiles filtered by the silan_check column * @method CcFiles findOneByDbHidden(boolean $hidden) Return the first CcFiles filtered by the hidden column + * @method CcFiles findOneByDbIsScheduled(boolean $is_scheduled) Return the first CcFiles filtered by the is_scheduled column + * @method CcFiles findOneByDbIsPlaylist(boolean $is_playlist) Return the first CcFiles filtered by the is_playlist column * * @method array findByDbId(int $id) Return CcFiles objects filtered by the id column * @method array findByDbName(string $name) Return CcFiles objects filtered by the name column @@ -316,6 +322,8 @@ * @method array findByDbCueout(string $cueout) Return CcFiles objects filtered by the cueout column * @method array findByDbSilanCheck(boolean $silan_check) Return CcFiles objects filtered by the silan_check column * @method array findByDbHidden(boolean $hidden) Return CcFiles objects filtered by the hidden column + * @method array findByDbIsScheduled(boolean $is_scheduled) Return CcFiles objects filtered by the is_scheduled column + * @method array findByDbIsPlaylist(boolean $is_playlist) Return CcFiles objects filtered by the is_playlist column * * @package propel.generator.airtime.om */ @@ -2045,6 +2053,40 @@ abstract class BaseCcFilesQuery extends ModelCriteria return $this->addUsingAlias(CcFilesPeer::HIDDEN, $dbHidden, $comparison); } + /** + * Filter the query on the is_scheduled column + * + * @param boolean|string $dbIsScheduled The value to use as filter. + * Accepts strings ('false', 'off', '-', 'no', 'n', and '0' are false, the rest is true) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByDbIsScheduled($dbIsScheduled = null, $comparison = null) + { + if (is_string($dbIsScheduled)) { + $is_scheduled = in_array(strtolower($dbIsScheduled), array('false', 'off', '-', 'no', 'n', '0')) ? false : true; + } + return $this->addUsingAlias(CcFilesPeer::IS_SCHEDULED, $dbIsScheduled, $comparison); + } + + /** + * Filter the query on the is_playlist column + * + * @param boolean|string $dbIsPlaylist The value to use as filter. + * Accepts strings ('false', 'off', '-', 'no', 'n', and '0' are false, the rest is true) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcFilesQuery The current query, for fluid interface + */ + public function filterByDbIsPlaylist($dbIsPlaylist = null, $comparison = null) + { + if (is_string($dbIsPlaylist)) { + $is_playlist = in_array(strtolower($dbIsPlaylist), array('false', 'off', '-', 'no', 'n', '0')) ? false : true; + } + return $this->addUsingAlias(CcFilesPeer::IS_PLAYLIST, $dbIsPlaylist, $comparison); + } + /** * Filter the query by a related CcSubjs object * diff --git a/airtime_mvc/build/build.properties b/airtime_mvc/build/build.properties index 9c8ff5de0..1a83f8732 100644 --- a/airtime_mvc/build/build.properties +++ b/airtime_mvc/build/build.properties @@ -1,6 +1,6 @@ #Note: project.home is automatically generated by the propel-install script. #Any manual changes to this value will be overwritten. -project.home = /home/rudi/reps/Airtime/airtime_mvc +project.home = /home/denise/airtime/airtime_mvc project.build = ${project.home}/build #Database driver diff --git a/airtime_mvc/build/schema.xml b/airtime_mvc/build/schema.xml index a67043b3a..e83958017 100644 --- a/airtime_mvc/build/schema.xml +++ b/airtime_mvc/build/schema.xml @@ -80,6 +80,8 @@ + + diff --git a/airtime_mvc/build/sql/schema.sql b/airtime_mvc/build/sql/schema.sql index ebd0d6c5b..468b830fc 100644 --- a/airtime_mvc/build/sql/schema.sql +++ b/airtime_mvc/build/sql/schema.sql @@ -98,6 +98,8 @@ CREATE TABLE "cc_files" "cueout" interval default '00:00:00', "silan_check" BOOLEAN default 'f', "hidden" BOOLEAN default 'f', + "is_scheduled" BOOLEAN default 'f', + "is_playlist" BOOLEAN default 'f', PRIMARY KEY ("id") ); diff --git a/airtime_mvc/public/js/airtime/library/library.js b/airtime_mvc/public/js/airtime/library/library.js index f56cf7d7a..6ec4a1540 100644 --- a/airtime_mvc/public/js/airtime/library/library.js +++ b/airtime_mvc/public/js/airtime/library/library.js @@ -439,7 +439,8 @@ var AIRTIME = (function(AIRTIME) { /* ftype */ { "sTitle" : "" , "mDataProp" : "ftype" , "bSearchable" : false , "bVisible" : false } , /* Checkbox */ { "sTitle" : "" , "mDataProp" : "checkbox" , "bSortable" : false , "bSearchable" : false , "sWidth" : "25px" , "sClass" : "library_checkbox" } , /* Type */ { "sTitle" : "" , "mDataProp" : "image" , "bSearchable" : false , "sWidth" : "25px" , "sClass" : "library_type" , "iDataSort" : 0 } , - /* Status */ { "sTitle" : $.i18n._("Status") , "mDataProp" : "status" , "bSearchable" : false , "sWidth" : "60px" , "sClass" : "library_status" , "iDataSort" : 0 } , + /* Is Scheduled */ { "sTitle" : $.i18n._("Scheduled") , "mDataProp" : "is_scheduled" , "bSearchable" : false , "sWidth" : "90px" , "sClass" : "library_is_scheduled"} , + /* Is Playlist */ { "sTitle" : $.i18n._("Playlist") , "mDataProp" : "is_playlist" , "bSearchable" : false , "sWidth" : "70px" , "sClass" : "library_is_playlist"} , /* Title */ { "sTitle" : $.i18n._("Title") , "mDataProp" : "track_title" , "sClass" : "library_title" , "sWidth" : "170px" } , /* Creator */ { "sTitle" : $.i18n._("Creator") , "mDataProp" : "artist_name" , "sClass" : "library_creator" , "sWidth" : "160px" } , /* Album */ { "sTitle" : $.i18n._("Album") , "mDataProp" : "album_title" , "sClass" : "library_album" , "sWidth" : "150px" } , @@ -580,8 +581,15 @@ var AIRTIME = (function(AIRTIME) { $(nRow).find('td.library_type').html(''); } - if (aData.status_in_use !== null && aData.status_in_use) { - $(nRow).find("td.library_status").html(''); + if (aData.is_scheduled) { + $(nRow).find("td.library_is_scheduled").html(''); + } else if (!aData.is_scheduled) { + $(nRow).find("td.library_is_scheduled").html(''); + } + if (aData.is_playlist) { + $(nRow).find("td.library_is_playlist").html(''); + } else if (!aData.is_playlist) { + $(nRow).find("td.library_is_playlist").html(''); } // add the play function to the library_type td @@ -633,7 +641,7 @@ var AIRTIME = (function(AIRTIME) { return false; }); - $(nRow).find(".media-item-in-use").qtip({ + /*$(nRow).find(".media-item-in-use").qtip({ content: { text: aData.status_msg }, @@ -652,7 +660,7 @@ var AIRTIME = (function(AIRTIME) { my: "left bottom", at: "right center" }, - }); + });*/ // add a tool tip to appear when the user clicks on the type // icon. diff --git a/airtime_mvc/public/js/airtime/showbuilder/builder.js b/airtime_mvc/public/js/airtime/showbuilder/builder.js index a20661bcb..2b2e0f358 100644 --- a/airtime_mvc/public/js/airtime/showbuilder/builder.js +++ b/airtime_mvc/public/js/airtime/showbuilder/builder.js @@ -81,8 +81,18 @@ var AIRTIME = (function(AIRTIME){ return mod.showInstances; }; - mod.refresh = function() { + mod.refresh = function(schedId) { mod.resetTimestamp(); + + // once a track plays out we need to check if we can update + // the is_scheduled flag in cc_files + $.post(baseUrl+"schedule/update-future-is-scheduled", + {"format": "json", "schedId": schedId}, function(json) { + var data = $.parseJSON(json); + if (data.redrawLibTable) { + $("#library_content").find("#library_display").dataTable().fnStandingRedraw(); + } + }); oSchedTable.fnDraw(); }; @@ -797,7 +807,7 @@ var AIRTIME = (function(AIRTIME){ if(refreshInterval > maxRefreshInterval){ refreshInterval = maxRefreshInterval; } - mod.timeout = setTimeout(mod.refresh, refreshInterval); //need refresh in milliseconds + mod.timeout = setTimeout(function() {mod.refresh(aData.id)}, refreshInterval); //need refresh in milliseconds break; } } From 8db71eac529a287fce9989a19455a37363dd6b10 Mon Sep 17 00:00:00 2001 From: denise Date: Tue, 5 Feb 2013 16:52:25 -0500 Subject: [PATCH 11/51] CC-4914: Update is_scheduled after tracks play out -done --- airtime_mvc/application/models/StoredFile.php | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index 403ba5d53..ef96fe049 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -775,6 +775,9 @@ SQL; $fromTable = $unionTable; } + // update is_scheduled to false for tracks that + // have already played out + self::updatePastFilesIsScheduled(); $results = Application_Model_Datatables::findEntries($con, $displayColumns, $fromTable, $datatables); foreach ($results['aaData'] as &$row) { @@ -1298,8 +1301,12 @@ SQL; } } - public static function setIsScheduled($p_scheduleItem, $p_status) { - $fileId = Application_Model_Schedule::GetFileId($p_scheduleItem); + public static function setIsScheduled($p_scheduleItem, $p_status, $p_fileId=null) { + if (is_null($p_fileId)) { + $fileId = Application_Model_Schedule::GetFileId($p_scheduleItem); + } else { + $fileId = $p_fileId; + } $file = self::Recall($fileId); $updateIsScheduled = false; @@ -1311,6 +1318,22 @@ SQL; return $updateIsScheduled; } + public static function updatePastFilesIsScheduled() + { + $con = Propel::getConnection(); + $sql = <<query($sql)->fetchAll(); + foreach ($files as $file) { + if (!is_null($file['file_id'])) { + self::setIsScheduled(null, false, $file['file_id']); + } + } + + } + public function getRealClipLength($p_cuein, $p_cueout) { $sql = "SELECT :cueout::INTERVAL - :cuein::INTERVAL"; From 0ed5d18e9540143c52bcca4bd6031f7da8866982 Mon Sep 17 00:00:00 2001 From: denise Date: Wed, 6 Feb 2013 13:25:48 -0500 Subject: [PATCH 12/51] CC-4920: Now Playling js error -fixed --- airtime_mvc/public/js/airtime/showbuilder/builder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airtime_mvc/public/js/airtime/showbuilder/builder.js b/airtime_mvc/public/js/airtime/showbuilder/builder.js index 2b2e0f358..489e4240e 100644 --- a/airtime_mvc/public/js/airtime/showbuilder/builder.js +++ b/airtime_mvc/public/js/airtime/showbuilder/builder.js @@ -89,7 +89,7 @@ var AIRTIME = (function(AIRTIME){ $.post(baseUrl+"schedule/update-future-is-scheduled", {"format": "json", "schedId": schedId}, function(json) { var data = $.parseJSON(json); - if (data.redrawLibTable) { + if (data.redrawLibTable !== undefined && data.redrawLibTable) { $("#library_content").find("#library_display").dataTable().fnStandingRedraw(); } }); From 525b2dcb33b34a0638914b74eaa7c3b0d5d604e6 Mon Sep 17 00:00:00 2001 From: denise Date: Thu, 7 Feb 2013 15:41:47 -0500 Subject: [PATCH 13/51] CC-4895: Remove all json die() statements from code - removed json_encode --- .../application/controllers/ApiController.php | 6 ++++-- .../controllers/ListenerstatController.php | 2 +- .../controllers/PlaylistController.php | 18 +++++++++--------- .../controllers/PluploadController.php | 4 ++-- .../controllers/PreferenceController.php | 17 +++++++++-------- .../controllers/ScheduleController.php | 4 ++-- .../application/controllers/UserController.php | 10 +++++----- .../public/js/airtime/library/plupload.js | 3 +-- airtime_mvc/public/js/airtime/library/spl.js | 9 +++------ .../js/airtime/listenerstat/listenerstat.js | 1 - .../js/airtime/playlist/smart_blockbuilder.js | 5 ++--- .../js/airtime/preferences/preferences.js | 3 +-- .../js/airtime/preferences/streamsetting.js | 6 ++---- .../public/js/airtime/showbuilder/builder.js | 3 +-- airtime_mvc/public/js/airtime/user/user.js | 3 +-- 15 files changed, 43 insertions(+), 51 deletions(-) diff --git a/airtime_mvc/application/controllers/ApiController.php b/airtime_mvc/application/controllers/ApiController.php index ffbae08aa..cda2e9dcb 100644 --- a/airtime_mvc/application/controllers/ApiController.php +++ b/airtime_mvc/application/controllers/ApiController.php @@ -411,7 +411,9 @@ class ApiController extends Zend_Controller_Action $result = Application_Model_StoredFile::copyFileToStor($upload_dir, $fileName, $tempFileName); if (!is_null($result)) { - $this->_helper->json->sendJson('{"jsonrpc" : "2.0", "error" : {"code": '.$result['code'].', "message" : "'.$result['message'].'"}}'); + $this->_helper->json->sendJson( + array("jsonrpc" => "2.0", "error" => array("code" => $result['code'], "message" => $result['message'])) + ); } } @@ -600,7 +602,7 @@ class ApiController extends Zend_Controller_Action $response['key'] = $k; array_push($responses, $response); } - $this->_helper->json->sendJson( json_encode($responses) ); + $this->_helper->json->sendJson($responses); } public function listAllFilesAction() diff --git a/airtime_mvc/application/controllers/ListenerstatController.php b/airtime_mvc/application/controllers/ListenerstatController.php index f64ea8bfb..bb280378d 100644 --- a/airtime_mvc/application/controllers/ListenerstatController.php +++ b/airtime_mvc/application/controllers/ListenerstatController.php @@ -76,6 +76,6 @@ class ListenerstatController extends Zend_Controller_Action $endsDT = DateTime::createFromFormat("U", $ends_epoch, new DateTimeZone("UTC")); $data = Application_Model_ListenerStat::getDataPointsWithinRange($startsDT->format("Y-m-d H:i:s"), $endsDT->format("Y-m-d H:i:s"), $mountName); - $this->_helper->json->sendJson(json_encode($data)); + $this->_helper->json->sendJson($data); } } diff --git a/airtime_mvc/application/controllers/PlaylistController.php b/airtime_mvc/application/controllers/PlaylistController.php index 11d397bc3..e474b9177 100644 --- a/airtime_mvc/application/controllers/PlaylistController.php +++ b/airtime_mvc/application/controllers/PlaylistController.php @@ -133,7 +133,7 @@ class PlaylistController extends Zend_Controller_Action if (!$p_isJson) { $this->createFullResponse(null); } else { - $this->_helper->json->sendJson(json_encode(array("error"=>$this->view->error, "result"=>1, "html"=>$this->createFullResponse(null, $p_isJson)))); + $this->_helper->json->sendJson(array("error"=>$this->view->error, "result"=>1, "html"=>$this->createFullResponse(null, $p_isJson))); } } @@ -509,7 +509,7 @@ class PlaylistController extends Zend_Controller_Action } $result["modified"] = $this->view->modified; - $this->_helper->json->sendJson(json_encode($result)); + $this->_helper->json->sendJson($result); } public function smartBlockGenerateAction() @@ -525,7 +525,7 @@ class PlaylistController extends Zend_Controller_Action $form->startForm($params['obj_id']); if ($form->isValid($params)) { $result = $bl->generateSmartBlock($params['data']); - $this->_helper->json->sendJson(json_encode(array("result"=>0, "html"=>$this->createFullResponse($bl, true, true)))); + $this->_helper->json->sendJson(array("result"=>0, "html"=>$this->createFullResponse($bl, true, true))); } else { $this->view->obj = $bl; $this->view->id = $bl->getId(); @@ -533,7 +533,7 @@ class PlaylistController extends Zend_Controller_Action $viewPath = 'playlist/smart-block.phtml'; $result['html'] = $this->view->render($viewPath); $result['result'] = 1; - $this->_helper->json->sendJson(json_encode($result)); + $this->_helper->json->sendJson($result); } } catch (BlockNotFoundException $e) { $this->playlistNotFound('block', true); @@ -552,9 +552,9 @@ class PlaylistController extends Zend_Controller_Action $result = $bl->shuffleSmartBlock(); if ($result['result'] == 0) { - $this->_helper->json->sendJson(json_encode(array("result"=>0, "html"=>$this->createFullResponse($bl, true)))); + $this->_helper->json->sendJson(array("result"=>0, "html"=>$this->createFullResponse($bl, true))); } else { - $this->_helper->json->sendJson(json_encode($result)); + $this->_helper->json->sendJson($result); } } catch (BlockNotFoundException $e) { $this->playlistNotFound('block', true); @@ -572,9 +572,9 @@ class PlaylistController extends Zend_Controller_Action $result = $pl->shuffle(); if ($result['result'] == 0) { - $this->_helper->json->sendJson(json_encode(array("result"=>0, "html"=>$this->createFullResponse($pl, true)))); + $this->_helper->json->sendJson(array("result"=>0, "html"=>$this->createFullResponse($pl, true))); } else { - $this->_helper->json->sendJson(json_encode($result)); + $this->_helper->json->sendJson($result); } } catch (PlaylistNotFoundException $e) { $this->playlistNotFound('block', true); @@ -595,7 +595,7 @@ class PlaylistController extends Zend_Controller_Action $out = $bl->getCriteria(); $out['isStatic'] = false; } - $this->_helper->json->sendJson(json_encode($out)); + $this->_helper->json->sendJson($out); } } class WrongTypeToBlockException extends Exception {} diff --git a/airtime_mvc/application/controllers/PluploadController.php b/airtime_mvc/application/controllers/PluploadController.php index 49b706644..9698b163a 100644 --- a/airtime_mvc/application/controllers/PluploadController.php +++ b/airtime_mvc/application/controllers/PluploadController.php @@ -43,8 +43,8 @@ class PluploadController extends Zend_Controller_Action $result = Application_Model_StoredFile::copyFileToStor($upload_dir, $filename, $tempname); if (!is_null($result)) - $this->_helper->json->sendJson('{"jsonrpc" : "2.0", "error" : '.json_encode($result).'}'); + $this->_helper->json->sendJson(array("jsonrpc" => "2.0", "error" => $result)); - $this->_helper->json->sendJson('{"jsonrpc" : "2.0"}'); + $this->_helper->json->sendJson(array("jsonrpc" => "2.0")); } } diff --git a/airtime_mvc/application/controllers/PreferenceController.php b/airtime_mvc/application/controllers/PreferenceController.php index 33a021aa4..d30a0d292 100644 --- a/airtime_mvc/application/controllers/PreferenceController.php +++ b/airtime_mvc/application/controllers/PreferenceController.php @@ -70,10 +70,10 @@ class PreferenceController extends Zend_Controller_Action $this->view->statusMsg = "
". _("Preferences updated.")."
"; $this->view->form = $form; - $this->_helper->json->sendJson(json_encode(array("valid"=>"true", "html"=>$this->view->render('preference/index.phtml')))); + $this->_helper->json->sendJson(array("valid"=>"true", "html"=>$this->view->render('preference/index.phtml'))); } else { $this->view->form = $form; - $this->_helper->json->sendJson(json_encode(array("valid"=>"false", "html"=>$this->view->render('preference/index.phtml')))); + $this->_helper->json->sendJson(array("valid"=>"false", "html"=>$this->view->render('preference/index.phtml'))); } } $this->view->form = $form; @@ -323,19 +323,19 @@ class PreferenceController extends Zend_Controller_Action $this->view->form = $form; $this->view->num_stream = $num_of_stream; $this->view->statusMsg = "
"._("Stream Setting Updated.")."
"; - $this->_helper->json->sendJson(json_encode(array( + $this->_helper->json->sendJson(array( "valid"=>"true", "html"=>$this->view->render('preference/stream-setting.phtml'), "s1_set_admin_pass"=>$s1_set_admin_pass, "s2_set_admin_pass"=>$s2_set_admin_pass, "s3_set_admin_pass"=>$s3_set_admin_pass, - ))); + )); } else { $live_stream_subform->updateVariables(); $this->view->enable_stream_conf = Application_Model_Preference::GetEnableStreamConf(); $this->view->form = $form; $this->view->num_stream = $num_of_stream; - $this->_helper->json->sendJson(json_encode(array("valid"=>"false", "html"=>$this->view->render('preference/stream-setting.phtml')))); + $this->_helper->json->sendJson(array("valid"=>"false", "html"=>$this->view->render('preference/stream-setting.phtml'))); } } @@ -441,7 +441,7 @@ class PreferenceController extends Zend_Controller_Action if (Application_Model_Preference::GetImportTimestamp()+10 > $now) { $res = true; } - $this->_helper->json->sendJson(json_encode($res)); + $this->_helper->json->sendJson($res); } public function getLiquidsoapStatusAction() @@ -456,7 +456,7 @@ class PreferenceController extends Zend_Controller_Action } $out[] = array("id"=>$i, "status"=>$status); } - $this->_helper->json->sendJson(json_encode($out)); + $this->_helper->json->sendJson($out); } public function setSourceConnectionUrlAction() @@ -479,6 +479,7 @@ class PreferenceController extends Zend_Controller_Action public function getAdminPasswordStatusAction() { + Logging::info("11111111111"); $out = array(); for ($i=1; $i<=3; $i++) { if (Application_Model_StreamSetting::getAdminPass('s'.$i)=='') { @@ -487,6 +488,6 @@ class PreferenceController extends Zend_Controller_Action $out["s".$i] = true; } } - $this->_helper->json->sendJson(json_encode($out)); + $this->_helper->json->sendJson($out); } } diff --git a/airtime_mvc/application/controllers/ScheduleController.php b/airtime_mvc/application/controllers/ScheduleController.php index 3ce212513..c4fd65205 100644 --- a/airtime_mvc/application/controllers/ScheduleController.php +++ b/airtime_mvc/application/controllers/ScheduleController.php @@ -925,7 +925,7 @@ class ScheduleController extends Zend_Controller_Action 'title' => _('Download')); //returns format jjmenu is looking for. - $this->_helper->json->sendJson(json_encode($menu)); + $this->_helper->json->sendJson($menu); } /** @@ -983,6 +983,6 @@ class ScheduleController extends Zend_Controller_Action { $schedId = $this->_getParam('schedId'); $redrawLibTable = Application_Model_StoredFile::setIsScheduled($schedId, false); - $this->_helper->json->sendJson(json_encode(array("redrawLibTable" => $redrawLibTable))); + $this->_helper->json->sendJson(array("redrawLibTable" => $redrawLibTable)); } } diff --git a/airtime_mvc/application/controllers/UserController.php b/airtime_mvc/application/controllers/UserController.php index 14728512a..319c57767 100644 --- a/airtime_mvc/application/controllers/UserController.php +++ b/airtime_mvc/application/controllers/UserController.php @@ -53,7 +53,7 @@ class UserController extends Zend_Controller_Action && $formData['user_id'] != 0) { $this->view->form = $form; $this->view->successMessage = "
"._("Specific action is not allowed in demo version!")."
"; - $this->_helper->json->sendJson(json_encode(array("valid"=>"false", "html"=>$this->view->render('user/add-user.phtml')))); + $this->_helper->json->sendJson(array("valid"=>"false", "html"=>$this->view->render('user/add-user.phtml'))); } elseif ($form->validateLogin($formData)) { $user = new Application_Model_User($formData['user_id']); if (empty($formData['user_id'])) { @@ -89,14 +89,14 @@ class UserController extends Zend_Controller_Action $this->view->successMessage = "
"._("User updated successfully!")."
"; } - $this->_helper->json->sendJson(json_encode(array("valid"=>"true", "html"=>$this->view->render('user/add-user.phtml')))); + $this->_helper->json->sendJson(array("valid"=>"true", "html"=>$this->view->render('user/add-user.phtml'))); } else { $this->view->form = $form; - $this->_helper->json->sendJson(json_encode(array("valid"=>"false", "html"=>$this->view->render('user/add-user.phtml')))); + $this->_helper->json->sendJson(array("valid"=>"false", "html"=>$this->view->render('user/add-user.phtml'))); } } else { $this->view->form = $form; - $this->_helper->json->sendJson(json_encode(array("valid"=>"false", "html"=>$this->view->render('user/add-user.phtml')))); + $this->_helper->json->sendJson(array("valid"=>"false", "html"=>$this->view->render('user/add-user.phtml'))); } } @@ -135,7 +135,7 @@ class UserController extends Zend_Controller_Action && $formData['cu_login'] == 'admin') { $this->view->form = $form; $this->view->successMessage = "
"._("Specific action is not allowed in demo version!")."
"; - $this->_helper->json->sendJson(json_encode(array("html"=>$this->view->render('user/edit-user.phtml')))); + $this->_helper->json->sendJson(array("html"=>$this->view->render('user/edit-user.phtml'))); } else if ($form->isValid($formData) && $form->validateLogin($formData['cu_login'], $formData['cu_user_id'])) { $user = new Application_Model_User($formData['cu_user_id']); diff --git a/airtime_mvc/public/js/airtime/library/plupload.js b/airtime_mvc/public/js/airtime/library/plupload.js index b429c5602..91fdc63ee 100644 --- a/airtime_mvc/public/js/airtime/library/plupload.js +++ b/airtime_mvc/public/js/airtime/library/plupload.js @@ -30,8 +30,7 @@ $(document).ready(function() { var tempFileName = j.tempfilepath; $.get(baseUrl+'Plupload/copyfile/format/json/name/'+ encodeURIComponent(file.name)+'/tempname/' + - encodeURIComponent(tempFileName), function(json){ - var jr = jQuery.parseJSON(json); + encodeURIComponent(tempFileName), function(jr){ if(jr.error !== undefined) { var row = $("") .append('' + file.name +'') diff --git a/airtime_mvc/public/js/airtime/library/spl.js b/airtime_mvc/public/js/airtime/library/spl.js index 6c49443ff..4f61517e5 100644 --- a/airtime_mvc/public/js/airtime/library/spl.js +++ b/airtime_mvc/public/js/airtime/library/spl.js @@ -450,9 +450,8 @@ var AIRTIME = (function(AIRTIME){ if ($(this).hasClass('close')) { var sUrl = baseUrl+"playlist/get-block-info"; mod.disableUI(); - $.post(sUrl, {format:"json", id:blockId}, function(json){ + $.post(sUrl, {format:"json", id:blockId}, function(data){ $html = ""; - var data = $.parseJSON(json); var isStatic = data.isStatic; delete data.type; if (isStatic) { @@ -643,8 +642,7 @@ var AIRTIME = (function(AIRTIME){ obj_id = $('input[id="obj_id"]').val(); url = baseUrl+"Playlist/shuffle"; enableLoadingIcon(); - $.post(url, {format: "json", obj_id: obj_id}, function(data){ - var json = $.parseJSON(data) + $.post(url, {format: "json", obj_id: obj_id}, function(json){ if (json.error !== undefined) { alert(json.error); @@ -727,8 +725,7 @@ var AIRTIME = (function(AIRTIME){ enableLoadingIcon(); $.post(save_action, {format: "json", data: criteria, name: block_name, description: block_desc, obj_id: obj_id, type: obj_type, modified: lastMod}, - function(data){ - var json = $.parseJSON(data); + function(json){ if (json.error !== undefined) { alert(json.error); } diff --git a/airtime_mvc/public/js/airtime/listenerstat/listenerstat.js b/airtime_mvc/public/js/airtime/listenerstat/listenerstat.js index 926368966..bebfd1471 100644 --- a/airtime_mvc/public/js/airtime/listenerstat/listenerstat.js +++ b/airtime_mvc/public/js/airtime/listenerstat/listenerstat.js @@ -23,7 +23,6 @@ $(document).ready(function() { function getDataAndPlot(startTimestamp, endTimestamp){ // get data $.get(baseUrl+'Listenerstat/get-data', {startTimestamp: startTimestamp, endTimestamp: endTimestamp}, function(data){ - data = JSON.parse(data); out = new Object(); $.each(data, function(mpName, v){ plotData = new Object(); diff --git a/airtime_mvc/public/js/airtime/playlist/smart_blockbuilder.js b/airtime_mvc/public/js/airtime/playlist/smart_blockbuilder.js index a06ee081e..022e93148 100644 --- a/airtime_mvc/public/js/airtime/playlist/smart_blockbuilder.js +++ b/airtime_mvc/public/js/airtime/playlist/smart_blockbuilder.js @@ -480,9 +480,8 @@ function getCriteriaOptionType(e) { return criteriaTypes[criteria]; } -function callback(data, type) { - var json = $.parseJSON(data), - dt = $('table[id="library_display"]').dataTable(); +function callback(json, type) { + var dt = $('table[id="library_display"]').dataTable(); if (type == 'shuffle' || type == 'generate') { if (json.error !== undefined) { diff --git a/airtime_mvc/public/js/airtime/preferences/preferences.js b/airtime_mvc/public/js/airtime/preferences/preferences.js index eec5e1c7e..50466f462 100644 --- a/airtime_mvc/public/js/airtime/preferences/preferences.js +++ b/airtime_mvc/public/js/airtime/preferences/preferences.js @@ -108,8 +108,7 @@ $(document).ready(function() { var data = $('#pref_form').serialize(); var url = baseUrl+'Preference/index'; - $.post(url, {format: "json", data: data}, function(data){ - var json = $.parseJSON(data); + $.post(url, {format: "json", data: data}, function(json){ $('#content').empty().append(json.html); $.cookie("default_airtime_locale", $('#locale').val(), {path: '/'}); setTimeout(removeSuccessMsg, 5000); diff --git a/airtime_mvc/public/js/airtime/preferences/streamsetting.js b/airtime_mvc/public/js/airtime/preferences/streamsetting.js index 6e76b693c..0fdd82f2c 100644 --- a/airtime_mvc/public/js/airtime/preferences/streamsetting.js +++ b/airtime_mvc/public/js/airtime/preferences/streamsetting.js @@ -77,8 +77,7 @@ function showForIcecast(ele){ function checkLiquidsoapStatus(){ var url = baseUrl+'Preference/get-liquidsoap-status/format/json'; var id = $(this).attr("id"); - $.post(url, function(json){ - var json_obj = jQuery.parseJSON(json); + $.post(url, function(json_obj){ for(var i=0;i Date: Thu, 7 Feb 2013 15:56:35 -0500 Subject: [PATCH 14/51] Removed log message --- airtime_mvc/application/controllers/PreferenceController.php | 1 - 1 file changed, 1 deletion(-) diff --git a/airtime_mvc/application/controllers/PreferenceController.php b/airtime_mvc/application/controllers/PreferenceController.php index d30a0d292..7f6d6b8b5 100644 --- a/airtime_mvc/application/controllers/PreferenceController.php +++ b/airtime_mvc/application/controllers/PreferenceController.php @@ -479,7 +479,6 @@ class PreferenceController extends Zend_Controller_Action public function getAdminPasswordStatusAction() { - Logging::info("11111111111"); $out = array(); for ($i=1; $i<=3; $i++) { if (Application_Model_StreamSetting::getAdminPass('s'.$i)=='') { From 2315e3473ddc6754758e9ab0c5326e6f4b6e8654 Mon Sep 17 00:00:00 2001 From: denise Date: Thu, 7 Feb 2013 17:33:11 -0500 Subject: [PATCH 15/51] CC-4935: Smart Block -> Error when saving sometimes -fixed --- airtime_mvc/public/js/airtime/library/spl.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airtime_mvc/public/js/airtime/library/spl.js b/airtime_mvc/public/js/airtime/library/spl.js index 4f61517e5..24faa2e87 100644 --- a/airtime_mvc/public/js/airtime/library/spl.js +++ b/airtime_mvc/public/js/airtime/library/spl.js @@ -734,7 +734,7 @@ var AIRTIME = (function(AIRTIME){ } setModified(json.modified); if (obj_type == "block") { - callback(data, "save"); + callback(json, "save"); } else { $('.success').text($.i18n._('Playlist saved')); $('.success').show(); From 50613dd9a8baa86555ad8314e8bc1d9237f86fd3 Mon Sep 17 00:00:00 2001 From: denise Date: Tue, 12 Feb 2013 17:21:21 -0500 Subject: [PATCH 16/51] CC-4907: Library -> Adding items to show doesn't add the correct item sometimes -fixed --- airtime_mvc/application/models/StoredFile.php | 1 + 1 file changed, 1 insertion(+) diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index ef96fe049..4f61c3592 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -816,6 +816,7 @@ SQL; $row['checkbox'] = ""; $row['image'] = ""; + $type = substr($row['ftype'], 0, 2); $row['tr_id'] = "{$type}_{$row['id']}"; } From 4ff1e8e78a7423bf2c9ca86239dc4599bf25f10a Mon Sep 17 00:00:00 2001 From: denise Date: Wed, 13 Feb 2013 11:29:43 -0500 Subject: [PATCH 17/51] CC-4932: Library -> Update Scheduled column on 'Cancel current show' action -fixed --- airtime_mvc/application/controllers/ScheduleController.php | 1 + airtime_mvc/public/js/airtime/showbuilder/builder.js | 1 + 2 files changed, 2 insertions(+) diff --git a/airtime_mvc/application/controllers/ScheduleController.php b/airtime_mvc/application/controllers/ScheduleController.php index c4fd65205..f15f9e099 100644 --- a/airtime_mvc/application/controllers/ScheduleController.php +++ b/airtime_mvc/application/controllers/ScheduleController.php @@ -895,6 +895,7 @@ class ScheduleController extends Zend_Controller_Action try { $scheduler = new Application_Model_Scheduler(); $scheduler->cancelShow($id); + Application_Model_StoredFile::updatePastFilesIsScheduled(); // send kick out source stream signal to pypo $data = array("sourcename"=>"live_dj"); Application_Model_RabbitMq::SendMessageToPypo("disconnect_source", $data); diff --git a/airtime_mvc/public/js/airtime/showbuilder/builder.js b/airtime_mvc/public/js/airtime/showbuilder/builder.js index d4b70abc2..d40f4f349 100644 --- a/airtime_mvc/public/js/airtime/showbuilder/builder.js +++ b/airtime_mvc/public/js/airtime/showbuilder/builder.js @@ -1076,6 +1076,7 @@ var AIRTIME = (function(AIRTIME){ url: url, data: {format: "json", id: data.instance}, success: function(data){ + $("#library_content").find("#library_display").dataTable().fnStandingRedraw(); var oTable = $sbTable.dataTable(); oTable.fnDraw(); } From eb38503d6eeab8cc073e2e86f5375caac16f8487 Mon Sep 17 00:00:00 2001 From: denise Date: Wed, 13 Feb 2013 11:51:25 -0500 Subject: [PATCH 18/51] CC-4946: Now Playing -> Apache error when empty show starts -we were trying to update the is_scheduled flag on a non-schedule item --- .../public/js/airtime/showbuilder/builder.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/airtime_mvc/public/js/airtime/showbuilder/builder.js b/airtime_mvc/public/js/airtime/showbuilder/builder.js index d40f4f349..9bbc4db92 100644 --- a/airtime_mvc/public/js/airtime/showbuilder/builder.js +++ b/airtime_mvc/public/js/airtime/showbuilder/builder.js @@ -86,13 +86,15 @@ var AIRTIME = (function(AIRTIME){ // once a track plays out we need to check if we can update // the is_scheduled flag in cc_files - $.post(baseUrl+"schedule/update-future-is-scheduled", - {"format": "json", "schedId": schedId}, function(data) { - if (data.redrawLibTable !== undefined && data.redrawLibTable) { - $("#library_content").find("#library_display").dataTable().fnStandingRedraw(); - } - }); - oSchedTable.fnDraw(); + if (schedId > 0) { + $.post(baseUrl+"schedule/update-future-is-scheduled", + {"format": "json", "schedId": schedId}, function(data) { + if (data.redrawLibTable !== undefined && data.redrawLibTable) { + $("#library_content").find("#library_display").dataTable().fnStandingRedraw(); + } + }); + oSchedTable.fnDraw(); + } }; mod.checkSelectButton = function() { From 76cb04e2960ea1ec1b6685ff5974e9acb73a2767 Mon Sep 17 00:00:00 2001 From: denise Date: Wed, 13 Feb 2013 11:55:27 -0500 Subject: [PATCH 19/51] CC-4538: Cannot edit title or change time of repeating show if first instance has ended - fixed by populating the edit show form with the next repeating show that is not in the past --- .../controllers/ScheduleController.php | 16 ++++++++++--- airtime_mvc/application/models/Show.php | 24 +++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/airtime_mvc/application/controllers/ScheduleController.php b/airtime_mvc/application/controllers/ScheduleController.php index f15f9e099..dc16d657d 100644 --- a/airtime_mvc/application/controllers/ScheduleController.php +++ b/airtime_mvc/application/controllers/ScheduleController.php @@ -627,7 +627,11 @@ class ScheduleController extends Zend_Controller_Action if (!$showInstance->getShow()->isRepeating()) { $formWhen->disableStartDateAndTime(); } else { - $formWhen->getElement('add_show_start_date')->setOptions(array('disabled' => true)); + $nextFutureRepeatShow = $show->getNextFutureRepeatShowTime(); + $formWhen->getElement('add_show_start_date')->setValue($nextFutureRepeatShow["starts"]->format("Y-m-d")); + $formWhen->getElement('add_show_start_time')->setValue($nextFutureRepeatShow["starts"]->format("H:i")); + $formWhen->getElement('add_show_end_date_no_repeat')->setValue($nextFutureRepeatShow["ends"]->format("Y-m-d")); + $formWhen->getElement('add_show_end_time')->setValue($nextFutureRepeatShow["ends"]->format("H:i")); } } @@ -802,10 +806,16 @@ class ScheduleController extends Zend_Controller_Action } $data['add_show_record'] = $show->isRecorded(); - $origianlShowStartDateTime = Application_Common_DateHelper::ConvertToLocalDateTime($show->getStartDateAndTime()); + if ($show->isRepeating()) { + $nextFutureRepeatShow = $show->getNextFutureRepeatShowTime(); + $originalShowStartDateTime = $nextFutureRepeatShow["starts"]; + } else { + $originalShowStartDateTime = Application_Common_DateHelper::ConvertToLocalDateTime( + $show->getStartDateAndTime()); + } $success = Application_Model_Schedule::addUpdateShow($data, $this, - $validateStartDate, $origianlShowStartDateTime, true, + $validateStartDate, $originalShowStartDateTime, true, $data['add_show_instance_id']); if ($success) { diff --git a/airtime_mvc/application/models/Show.php b/airtime_mvc/application/models/Show.php index 933c2c841..f485e41bc 100644 --- a/airtime_mvc/application/models/Show.php +++ b/airtime_mvc/application/models/Show.php @@ -663,6 +663,30 @@ SQL; $con->exec($sql); } + public function getNextFutureRepeatShowTime() + { + $sql = << now() at time zone 'UTC' +AND show_id = :showId +ORDER BY starts +LIMIT 1 +SQL; + $result = Application_Common_Database::prepareAndExecute( $sql, + array( 'showId' => $this->getId() ), 'all' ); + + foreach ($result as $r) { + $show["starts"] = new DateTime($r["starts"], new DateTimeZone('UTC')); + $show["ends"] = new DateTime($r["ends"], new DateTimeZone('UTC')); + } + $currentUser = Application_Model_User::getCurrentUser(); + $currentUserId = $currentUser->getId(); + $userTimezone = Application_Model_Preference::GetUserTimezone($currentUserId); + $show["starts"]->setTimezone(new DateTimeZone($userTimezone)); + $show["ends"]->setTimezone(new DateTimeZone($userTimezone)); + return $show; + } + /** * Get the start date of the current show in UTC timezone. * From 04ed64075667917e813a01ab5b2208195180e2f9 Mon Sep 17 00:00:00 2001 From: denise Date: Wed, 13 Feb 2013 15:44:52 -0500 Subject: [PATCH 20/51] CC-3885: Deleted open playlist is not closed until a new item is added to it -fixed --- .../controllers/PlaylistController.php | 8 +++ .../public/js/airtime/library/library.js | 50 +++++++++++++------ 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/airtime_mvc/application/controllers/PlaylistController.php b/airtime_mvc/application/controllers/PlaylistController.php index e474b9177..3f0dd66b7 100644 --- a/airtime_mvc/application/controllers/PlaylistController.php +++ b/airtime_mvc/application/controllers/PlaylistController.php @@ -14,6 +14,7 @@ class PlaylistController extends Zend_Controller_Action ->addActionContext('new', 'json') ->addActionContext('edit', 'json') ->addActionContext('delete', 'json') + ->addActionContext('close-playlist', 'json') ->addActionContext('play', 'json') ->addActionContext('set-playlist-fades', 'json') ->addActionContext('get-playlist-fades', 'json') @@ -246,6 +247,13 @@ class PlaylistController extends Zend_Controller_Action } } + public function closePlaylistAction() { + $type = $this->_getParam('type'); + $obj = null; + Application_Model_Library::changePlaylist($obj, $type); + $this->createFullResponse($obj); + } + public function addItemsAction() { $ids = $this->_getParam('aItems', array()); diff --git a/airtime_mvc/public/js/airtime/library/library.js b/airtime_mvc/public/js/airtime/library/library.js index 6ec4a1540..5d835125e 100644 --- a/airtime_mvc/public/js/airtime/library/library.js +++ b/airtime_mvc/public/js/airtime/library/library.js @@ -322,22 +322,40 @@ var AIRTIME = (function(AIRTIME) { }; mod.fnDeleteSelectedItems = function() { - if (confirm($.i18n._('Are you sure you want to delete the selected item(s)?'))) { - var aData = AIRTIME.library.getSelectedData(), - item, - temp, - aMedia = []; - - // process selected files/playlists. - for (item in aData) { - temp = aData[item]; - if (temp !== null && temp.hasOwnProperty('id') ) { - aMedia.push({"id": temp.id, "type": temp.ftype}); - } - } - - AIRTIME.library.fnDeleteItems(aMedia); - } + if (confirm($.i18n._('Are you sure you want to delete the selected item(s)?'))) { + var aData = AIRTIME.library.getSelectedData(), + item, + temp, + aMedia = [], + currentObjId = $("#side_playlist").find("#obj_id").val(), + currentObjType = $("#side_playlist").find("#obj_type").val(), + closeObj = false; + + // process selected files/playlists. + for (item in aData) { + temp = aData[item]; + if (temp !== null && temp.hasOwnProperty('id') ) { + aMedia.push({"id": temp.id, "type": temp.ftype}); + if ( (temp.id == currentObjId && temp.ftype === currentObjType) || + temp.id == currentObjId && temp.ftype === "stream" && currentObjType === "webstream") { + closeObj = true; + } + } + } + + AIRTIME.library.fnDeleteItems(aMedia); + + // close the object (playlist/block/webstream) + // on the right side if it was just deleted + // from the library + if (closeObj) { + $.post(baseUrl+"playlist/close-playlist", + {"format": "json", "type": currentObjType}, + function(json) { + $("#side_playlist").empty().append(json.html); + }); + } + } }; libraryInit = function() { From 6bbb603bcd8d0c3008a56742467ba3443daa399d Mon Sep 17 00:00:00 2001 From: denise Date: Tue, 22 Jan 2013 13:27:20 -0500 Subject: [PATCH 21/51] CC-3627: PlaylistBuilder->Playlist Panel -> it would be very handy to have "Close playlist" functionality --- .../views/scripts/playlist/playlist.phtml | 1 + .../views/scripts/playlist/smart-block.phtml | 1 + .../views/scripts/webstream/webstream.phtml | 1 + airtime_mvc/public/js/airtime/library/spl.js | 26 ++++++++++++++++--- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/airtime_mvc/application/views/scripts/playlist/playlist.phtml b/airtime_mvc/application/views/scripts/playlist/playlist.phtml index 5a31ba2b5..ead87942e 100644 --- a/airtime_mvc/application/views/scripts/playlist/playlist.phtml +++ b/airtime_mvc/application/views/scripts/playlist/playlist.phtml @@ -4,6 +4,7 @@ if (isset($this->obj)) { $count = count($contents); } ?> +
-
+showPlaylist) { + $display = ""; +} else { + $display = "display:none"; +} +?> +
type == 'block') { echo $this->render('playlist/smart-block.phtml'); } else if ($this->type == 'playlist') { diff --git a/airtime_mvc/public/js/airtime/library/spl.js b/airtime_mvc/public/js/airtime/library/spl.js index 534c5d5b7..f0f0c32a0 100644 --- a/airtime_mvc/public/js/airtime/library/spl.js +++ b/airtime_mvc/public/js/airtime/library/spl.js @@ -12,7 +12,7 @@ var AIRTIME = (function(AIRTIME){ viewport, $lib, $pl, - $togglePl = $(""+$.i18n._("Open Playlist")+""), + $togglePl = $(""), widgetHeight, resizeTimeout, width; @@ -720,12 +720,35 @@ var AIRTIME = (function(AIRTIME){ $lib.on("click", "#pl_edit", function() { openPlaylistPanel(); + $.ajax( { + url : baseUrl+"usersettings/set-library-screen-settings", + type : "POST", + data : { + settings : { + playlist : true + }, + format : "json" + }, + dataType : "json" + }); }); $pl.on("click", "#lib_pl_close", function() { var screenWidth = Math.floor(viewport.width - 40); $pl.hide(); $lib.width(screenWidth).find("#library_display_length").append($togglePl.show()); + + $.ajax( { + url : baseUrl+"usersettings/set-library-screen-settings", + type : "POST", + data : { + settings : { + playlist : false + }, + format : "json" + }, + dataType : "json" + }); }); $('#save_button').live("click", function(event){ @@ -1099,12 +1122,18 @@ var AIRTIME = (function(AIRTIME){ mod.onReady = function() { $lib = $("#library_content"); $pl = $("#side_playlist"); + + setWidgetSize(); AIRTIME.library.libraryInit(); AIRTIME.playlist.init(); - + + if ($pl.is(':hidden')) { + $lib.find("#library_display_length").append($togglePl.show()); + } + $pl.find(".ui-icon-alert").qtip({ content: { text: $.i18n._("Airtime is unsure about the status of this file. This can happen when the file is on a remote drive that is unaccessible or the file is in a directory that isn't 'watched' anymore.") From c0c6ac5d753acde6b242c834ab9f960047fa340b Mon Sep 17 00:00:00 2001 From: denise Date: Tue, 19 Feb 2013 11:37:22 -0500 Subject: [PATCH 27/51] CC-4954: Please make Library page remember the status per user - it was only working if there was a playlist object set - fixed so it works even if there isnt --- .../controllers/LibraryController.php | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/airtime_mvc/application/controllers/LibraryController.php b/airtime_mvc/application/controllers/LibraryController.php index d63b6d4e3..a6f6b4921 100644 --- a/airtime_mvc/application/controllers/LibraryController.php +++ b/airtime_mvc/application/controllers/LibraryController.php @@ -77,21 +77,23 @@ class LibraryController extends Zend_Controller_Action } } - //get user settings and determine if we need to hide - // or show the playlist editor - $showPlaylist = false; - $data = Application_Model_Preference::getLibraryScreenSettings(); - if (!is_null($data)) { - if ($data["playlist"] == "true") { - $showLib = true; - } - } - $this->view->showPlaylist = $showPlaylist; - $formatter = new LengthFormatter($obj->getLength()); $this->view->length = $formatter->format(); $this->view->type = $obj_sess->type; } + + //get user settings and determine if we need to hide + // or show the playlist editor + $showPlaylist = false; + $data = Application_Model_Preference::getLibraryScreenSettings(); + Logging::info($data); + if (!is_null($data)) { + Logging::info($data); + if ($data["playlist"] == "true") { + $showPlaylist = true; + } + } + $this->view->showPlaylist = $showPlaylist; } catch (PlaylistNotFoundException $e) { $this->playlistNotFound($obj_sess->type); } catch (Exception $e) { From 9113853e41d9b086294a5eac27ed466d08886cf3 Mon Sep 17 00:00:00 2001 From: denise Date: Tue, 19 Feb 2013 11:51:26 -0500 Subject: [PATCH 28/51] Removed logging messages from Library controller, index action --- airtime_mvc/application/controllers/LibraryController.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/airtime_mvc/application/controllers/LibraryController.php b/airtime_mvc/application/controllers/LibraryController.php index a6f6b4921..79dd4b2be 100644 --- a/airtime_mvc/application/controllers/LibraryController.php +++ b/airtime_mvc/application/controllers/LibraryController.php @@ -86,9 +86,7 @@ class LibraryController extends Zend_Controller_Action // or show the playlist editor $showPlaylist = false; $data = Application_Model_Preference::getLibraryScreenSettings(); - Logging::info($data); if (!is_null($data)) { - Logging::info($data); if ($data["playlist"] == "true") { $showPlaylist = true; } From 49dc3140a8ad7a6c263ad59c1899dd9060f79020 Mon Sep 17 00:00:00 2001 From: denise Date: Tue, 19 Feb 2013 11:59:17 -0500 Subject: [PATCH 29/51] CC-4962: Library -> Library and playlist editor have different heights sometimes Fixed: needed to set height when we open the playlist editor --- airtime_mvc/public/js/airtime/library/spl.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/airtime_mvc/public/js/airtime/library/spl.js b/airtime_mvc/public/js/airtime/library/spl.js index f0f0c32a0..93d1cd80d 100644 --- a/airtime_mvc/public/js/airtime/library/spl.js +++ b/airtime_mvc/public/js/airtime/library/spl.js @@ -366,8 +366,12 @@ var AIRTIME = (function(AIRTIME){ function openPlaylistPanel() { var screenWidth = Math.floor(viewport.width - 40); + viewport = AIRTIME.utilities.findViewportDimensions(); + widgetHeight = viewport.height - 185; + $lib.width(Math.floor(screenWidth * 0.53)); $pl.show().width(Math.floor(screenWidth * 0.44)); + $pl.height(widgetHeight); $("#pl_edit").hide(); } From 8bdf3508b0d98adf29acb888c29fb30e0f0800ab Mon Sep 17 00:00:00 2001 From: denise Date: Tue, 19 Feb 2013 13:38:35 -0500 Subject: [PATCH 30/51] CC-4951: Add cue in and cue out to library filter columns Done --- airtime_mvc/application/models/StoredFile.php | 18 +++++++++++++++--- .../public/js/airtime/library/library.js | 2 ++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index 1c8d83873..87d3bd62f 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -645,7 +645,8 @@ SQL; "track_number", "mood", "bpm", "composer", "info_url", "bit_rate", "sample_rate", "isrc_number", "encoded_by", "label", "copyright", "mime", "language", "filepath", "owner_id", - "conductor", "replay_gain", "lptime", "is_playlist", "is_scheduled" ); + "conductor", "replay_gain", "lptime", "is_playlist", "is_scheduled", + "cuein", "cueout" ); } public static function searchLibraryFiles($datatables) @@ -702,6 +703,11 @@ SQL; $blSelect[] = "NULL::boolean AS ".$key; $fileSelect[] = $key; $streamSelect[] = "NULL::boolean AS ".$key; + } elseif ($key === "cuein" || $key === "cueout") { + $plSelect[] = "NULL::INTERVAL AS ".$key; + $blSelect[] = "NULL::INTERVAL AS ".$key; + $fileSelect[] = $key; + $streamSelect[] = "NULL::INTERVAL AS ".$key; } //same columns in each table. else if (in_array($key, array("length", "utime", "mtime"))) { @@ -783,8 +789,14 @@ SQL; foreach ($results['aaData'] as &$row) { $row['id'] = intval($row['id']); - $formatter = new LengthFormatter($row['length']); - $row['length'] = $formatter->format(); + $len_formatter = new LengthFormatter($row['length']); + $row['length'] = $len_formatter->format(); + + $cuein_formatter = new LengthFormatter($row["cuein"]); + $row["cuein"] = $cuein_formatter->format(); + + $cueout_formatter = new LengthFormatter($row["cueout"]); + $row["cueout"] = $cueout_formatter->format(); if ($row['ftype'] === "audioclip") { $formatter = new SamplerateFormatter($row['sample_rate']); diff --git a/airtime_mvc/public/js/airtime/library/library.js b/airtime_mvc/public/js/airtime/library/library.js index 89a03a90f..af30bbfa7 100644 --- a/airtime_mvc/public/js/airtime/library/library.js +++ b/airtime_mvc/public/js/airtime/library/library.js @@ -467,6 +467,8 @@ var AIRTIME = (function(AIRTIME) { /* Composer */ { "sTitle" : $.i18n._("Composer") , "mDataProp" : "composer" , "bVisible" : false , "sClass" : "library_composer" , "sWidth" : "150px" }, /* Conductor */ { "sTitle" : $.i18n._("Conductor") , "mDataProp" : "conductor" , "bVisible" : false , "sClass" : "library_conductor" , "sWidth" : "125px" }, /* Copyright */ { "sTitle" : $.i18n._("Copyright") , "mDataProp" : "copyright" , "bVisible" : false , "sClass" : "library_copyright" , "sWidth" : "125px" }, + /* Cue In */ { "sTitle" : $.i18n._("Cue In") , "mDataProp" : "cuein" , "bVisible" : false , "sClass" : "library_length" , "sWidth" : "80px" }, + /* Cue Out */ { "sTitle" : $.i18n._("Cue Out") , "mDataProp" : "cueout" , "bVisible" : false , "sClass" : "library_length" , "sWidth" : "80px" }, /* Encoded */ { "sTitle" : $.i18n._("Encoded By") , "mDataProp" : "encoded_by" , "bVisible" : false , "sClass" : "library_encoded" , "sWidth" : "150px" }, /* Genre */ { "sTitle" : $.i18n._("Genre") , "mDataProp" : "genre" , "bVisible" : false , "sClass" : "library_genre" , "sWidth" : "100px" }, /* ISRC Number */ { "sTitle" : $.i18n._("ISRC") , "mDataProp" : "isrc_number" , "bVisible" : false , "sClass" : "library_isrc" , "sWidth" : "150px" }, From 32fc5eb75845cbb2af0bfb3e0a1d2deba5684aff Mon Sep 17 00:00:00 2001 From: denise Date: Tue, 19 Feb 2013 14:44:32 -0500 Subject: [PATCH 31/51] CC-4951: Add cue in and cue out to library filter columns Configured advanced search for these columns --- airtime_mvc/public/js/airtime/library/library.js | 2 ++ .../public/js/datatables/plugin/dataTables.columnFilter.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/airtime_mvc/public/js/airtime/library/library.js b/airtime_mvc/public/js/airtime/library/library.js index af30bbfa7..c738d900c 100644 --- a/airtime_mvc/public/js/airtime/library/library.js +++ b/airtime_mvc/public/js/airtime/library/library.js @@ -1346,6 +1346,8 @@ var validationTypes = { "composer" : "s", "conductor" : "s", "copyright" : "s", + "cuein" : "l", + "cueout" : "l", "encoded_by" : "s", "utime" : "t", "mtime" : "t", diff --git a/airtime_mvc/public/js/datatables/plugin/dataTables.columnFilter.js b/airtime_mvc/public/js/datatables/plugin/dataTables.columnFilter.js index 07c8bba72..fbf333082 100644 --- a/airtime_mvc/public/js/datatables/plugin/dataTables.columnFilter.js +++ b/airtime_mvc/public/js/datatables/plugin/dataTables.columnFilter.js @@ -187,7 +187,7 @@ label = $.i18n._("kbps"); } else if (th.attr('id') == "utime" || th.attr('id') == "mtime" || th.attr('id') == "lptime") { label = $.i18n._("yyyy-mm-dd"); - } else if (th.attr('id') == "length") { + } else if (th.attr('id') == "length" || th.attr('id') == "cuein" || th.attr('id') == "cueout") { label = $.i18n._("hh:mm:ss.t"); } else if (th.attr('id') == "sample_rate") { label = $.i18n._("kHz"); From 959839e18001c1de83e3bde3914bdf16b9b4392f Mon Sep 17 00:00:00 2001 From: denise Date: Tue, 19 Feb 2013 15:09:24 -0500 Subject: [PATCH 32/51] Renamed criteriaTypes to libraryColumnTypes for library advanced search --- airtime_mvc/public/js/airtime/library/library.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/airtime_mvc/public/js/airtime/library/library.js b/airtime_mvc/public/js/airtime/library/library.js index c738d900c..942cd39dc 100644 --- a/airtime_mvc/public/js/airtime/library/library.js +++ b/airtime_mvc/public/js/airtime/library/library.js @@ -7,8 +7,13 @@ var AIRTIME = (function(AIRTIME) { LIB_SELECTED_CLASS = "lib-selected", chosenItems = {}, visibleChosenItems = {}; - - var criteriaTypes = { + + // we need to know whether the criteria value is string or + // numeric in order to provide a single textbox or range textboxes + // in the advanced search + // s => string + // n => numberic + var libraryColumnTypes = { 0 : "", "album_title" : "s", "artist_name" : "s", @@ -18,6 +23,8 @@ var AIRTIME = (function(AIRTIME) { "composer" : "s", "conductor" : "s", "copyright" : "s", + "cuein" : "n", + "cueout" : "n", "utime" : "n", "mtime" : "n", "lptime" : "n", @@ -399,7 +406,7 @@ var AIRTIME = (function(AIRTIME) { var inputClass = 'filter_column filter_number_text'; var labelStyle = "style='margin-right:35px;'"; - if (criteriaTypes[ele.mDataProp] != "s") { + if (libraryColumnTypes[ele.mDataProp] != "s") { inputClass = 'filterColumn filter_number_range'; labelStyle = ""; } @@ -418,7 +425,7 @@ var AIRTIME = (function(AIRTIME) { "
"); } - if (criteriaTypes[ele.mDataProp] == "s") { + if (libraryColumnTypes[ele.mDataProp] == "s") { var obj = { sSelector: "#"+ele.mDataProp } } else { var obj = { sSelector: "#"+ele.mDataProp, type: "number-range" } From c8d7d23d62950c99c2d635113339254672684ccd Mon Sep 17 00:00:00 2001 From: denise Date: Tue, 19 Feb 2013 16:38:58 -0500 Subject: [PATCH 33/51] CC-4963: Add cue in and cue out to smart block criteria list Done --- .../application/forms/SmartBlockCriteria.php | 65 +++++++++++-------- airtime_mvc/application/models/Block.php | 6 +- .../js/airtime/playlist/smart_blockbuilder.js | 6 +- 3 files changed, 48 insertions(+), 29 deletions(-) diff --git a/airtime_mvc/application/forms/SmartBlockCriteria.php b/airtime_mvc/application/forms/SmartBlockCriteria.php index f47117b63..d981fdb49 100644 --- a/airtime_mvc/application/forms/SmartBlockCriteria.php +++ b/airtime_mvc/application/forms/SmartBlockCriteria.php @@ -5,6 +5,11 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm private $stringCriteriaOptions; private $numericCriteriaOptions; private $limitOptions; + + /* We need to know if the criteria value will be a string + * or numeric value in order to populate the modifier + * select list + */ private $criteriaTypes = array( 0 => "", "album_title" => "s", @@ -13,6 +18,8 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm "composer" => "s", "conductor" => "s", "copyright" => "s", + "cuein" => "n", + "cueout" => "n", "artist_name" => "s", "encoded_by" => "s", "utime" => "n", @@ -45,6 +52,8 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm "composer" => _("Composer"), "conductor" => _("Conductor"), "copyright" => _("Copyright"), + "cuein" => _("Cue In"), + "cueout" => _("Cue Out"), "artist_name" => _("Creator"), "encoded_by" => _("Encoded By"), "genre" => _("Genre"), @@ -416,32 +425,34 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm $isValid = true; $data = $this->preValidation($params); $criteria2PeerMap = array( - 0 => "Select criteria", - "album_title" => "DbAlbumTitle", - "artist_name" => "DbArtistName", - "bit_rate" => "DbBitRate", - "bpm" => "DbBpm", - "composer" => "DbComposer", - "conductor" => "DbConductor", - "copyright" => "DbCopyright", - "encoded_by" => "DbEncodedBy", - "utime" => "DbUtime", - "mtime" => "DbMtime", - "lptime" => "DbLPtime", - "genre" => "DbGenre", - "info_url" => "DbInfoUrl", - "isrc_number" => "DbIsrcNumber", - "label" => "DbLabel", - "language" => "DbLanguage", - "length" => "DbLength", - "mime" => "DbMime", - "mood" => "DbMood", - "owner_id" => "DbOwnerId", - "replay_gain" => "DbReplayGain", - "sample_rate" => "DbSampleRate", - "track_title" => "DbTrackTitle", - "track_number" => "DbTrackNumber", - "year" => "DbYear" + 0 => "Select criteria", + "album_title" => "DbAlbumTitle", + "artist_name" => "DbArtistName", + "bit_rate" => "DbBitRate", + "bpm" => "DbBpm", + "composer" => "DbComposer", + "conductor" => "DbConductor", + "copyright" => "DbCopyright", + "cuein" => "DbCuein", + "cueout" => "DbCueout", + "encoded_by" => "DbEncodedBy", + "utime" => "DbUtime", + "mtime" => "DbMtime", + "lptime" => "DbLPtime", + "genre" => "DbGenre", + "info_url" => "DbInfoUrl", + "isrc_number" => "DbIsrcNumber", + "label" => "DbLabel", + "language" => "DbLanguage", + "length" => "DbLength", + "mime" => "DbMime", + "mood" => "DbMood", + "owner_id" => "DbOwnerId", + "replay_gain" => "DbReplayGain", + "sample_rate" => "DbSampleRate", + "track_title" => "DbTrackTitle", + "track_number" => "DbTrackNumber", + "year" => "DbYear" ); // things we need to check @@ -492,7 +503,7 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm } else { $column = CcFilesPeer::getTableMap()->getColumnByPhpName($criteria2PeerMap[$d['sp_criteria_field']]); // validation on type of column - if ($d['sp_criteria_field'] == 'length') { + if (in_array($d['sp_criteria_field'], array('length', 'cuein', 'cueout'))) { 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/Block.php b/airtime_mvc/application/models/Block.php index 6ba02ccb5..b364e5eb5 100644 --- a/airtime_mvc/application/models/Block.php +++ b/airtime_mvc/application/models/Block.php @@ -63,6 +63,8 @@ class Application_Model_Block implements Application_Model_LibraryEditable "composer" => "DbComposer", "conductor" => "DbConductor", "copyright" => "DbCopyright", + "cuein" => "DbCuein", + "cueout" => "DbCueout", "encoded_by" => "DbEncodedBy", "utime" => "DbUtime", "mtime" => "DbMtime", @@ -1278,6 +1280,8 @@ SQL; "composer" => _("Composer"), "conductor" => _("Conductor"), "copyright" => _("Copyright"), + "cuein" => _("Cue In"), + "cueout" => _("Cue Out"), "artist_name" => _("Creator"), "encoded_by" => _("Encoded By"), "genre" => _("Genre"), @@ -1370,7 +1374,7 @@ SQL; * user only sees the rounded version (i.e. 4:02.7 is 4:02.761625 * in the database) */ - } elseif ($spCriteria == 'length' && $spCriteriaModifier == "is") { + } elseif (in_array($spCriteria, array('length', 'cuein', 'cueout')) && $spCriteriaModifier == "is") { $spCriteriaModifier = "starts with"; $spCriteria = $spCriteria.'::text'; $spCriteriaValue = $criteria['value']; diff --git a/airtime_mvc/public/js/airtime/playlist/smart_blockbuilder.js b/airtime_mvc/public/js/airtime/playlist/smart_blockbuilder.js index 022e93148..f47a7b8ed 100644 --- a/airtime_mvc/public/js/airtime/playlist/smart_blockbuilder.js +++ b/airtime_mvc/public/js/airtime/playlist/smart_blockbuilder.js @@ -559,7 +559,9 @@ function enableLoadingIcon() { function disableLoadingIcon() { $("#side_playlist").unblock() } - +// We need to know if the criteria value will be a string +// or numeric value in order to populate the modifier +// select list var criteriaTypes = { 0 : "", "album_title" : "s", @@ -568,6 +570,8 @@ var criteriaTypes = { "composer" : "s", "conductor" : "s", "copyright" : "s", + "cuein" : "n", + "cueout" : "n", "artist_name" : "s", "encoded_by" : "s", "utime" : "n", From 176e78632f605c357eb5ea42a2825fa9a902cf5a Mon Sep 17 00:00:00 2001 From: denise Date: Tue, 19 Feb 2013 16:43:47 -0500 Subject: [PATCH 34/51] Added a comment about referring to the wiki when adding a new datatable column --- airtime_mvc/public/js/airtime/library/library.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/airtime_mvc/public/js/airtime/library/library.js b/airtime_mvc/public/js/airtime/library/library.js index 942cd39dc..fa9de60f0 100644 --- a/airtime_mvc/public/js/airtime/library/library.js +++ b/airtime_mvc/public/js/airtime/library/library.js @@ -460,6 +460,9 @@ var AIRTIME = (function(AIRTIME) { // put hidden columns at the top to insure they can never be visible // on the table through column reordering. + + //IMPORTANT: WHEN ADDING A NEW COLUMN PLEASE CONSULT WITH THE WIKI + // https://wiki.sourcefabric.org/display/CC/Adding+a+new+library+datatable+column "aoColumns": [ /* ftype */ { "sTitle" : "" , "mDataProp" : "ftype" , "bSearchable" : false , "bVisible" : false } , /* Checkbox */ { "sTitle" : "" , "mDataProp" : "checkbox" , "bSortable" : false , "bSearchable" : false , "sWidth" : "25px" , "sClass" : "library_checkbox" } , From baf693d1a97ed97f59105e3f580b12a1314c77c9 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Wed, 27 Feb 2013 17:36:43 +0100 Subject: [PATCH 35/51] CC-4980: Create graphic with 4 switches for Airtime Dashboard --- airtime_mvc/public/css/masterpanel.css | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/airtime_mvc/public/css/masterpanel.css b/airtime_mvc/public/css/masterpanel.css index 39c95044f..a391ce306 100644 --- a/airtime_mvc/public/css/masterpanel.css +++ b/airtime_mvc/public/css/masterpanel.css @@ -24,7 +24,7 @@ margin-left:0; } .source-info-block ul { - margin:0 0 0 7px; + margin:-3px 0 0 7px; padding:0; } .source-info-block li { @@ -35,14 +35,14 @@ height:15px; padding-right:0; overflow:visible; + padding-bottom:3px; } .source-info-block li:first-child { background-position: right -50px; - padding-bottom:3px; } .source-info-block li:last-child { background-position: right -150px; - padding-top:3px; + padding-top:0; } .source-info-block li div.source-label { margin-right:0; @@ -51,7 +51,7 @@ height:15px; line-height:14px; padding:0 2px 0 3px; - background:#3e3e3e; + background:#333; -webkit-border-radius:2px 0 0 2px; -moz-border-radius:2px 0 0 2px; border-radius:2px 0 0 2px; @@ -66,11 +66,11 @@ float:left; height:15px; padding:0; - border-left:2px solid #3e3e3e; + border-left:2px solid #333; background:url(images/source_to_switch_lines.png) repeat-x 0 0; } .source-info-block li .line-to-switch.off { - border-color:#3e3e3e; + border-color:#333; background:url(images/source_to_switch_lines.png) repeat-x 0 0; } .source-info-block li .line-to-switch.on { From 943538ed9dbf6007a7b1be8ff2486d4e6d061ed7 Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Wed, 27 Feb 2013 14:35:35 -0500 Subject: [PATCH 36/51] Revert "CC-4980: Create graphic with 4 switches for Airtime Dashboard" This reverts commit baf693d1a97ed97f59105e3f580b12a1314c77c9. --- airtime_mvc/public/css/masterpanel.css | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/airtime_mvc/public/css/masterpanel.css b/airtime_mvc/public/css/masterpanel.css index a391ce306..39c95044f 100644 --- a/airtime_mvc/public/css/masterpanel.css +++ b/airtime_mvc/public/css/masterpanel.css @@ -24,7 +24,7 @@ margin-left:0; } .source-info-block ul { - margin:-3px 0 0 7px; + margin:0 0 0 7px; padding:0; } .source-info-block li { @@ -35,14 +35,14 @@ height:15px; padding-right:0; overflow:visible; - padding-bottom:3px; } .source-info-block li:first-child { background-position: right -50px; + padding-bottom:3px; } .source-info-block li:last-child { background-position: right -150px; - padding-top:0; + padding-top:3px; } .source-info-block li div.source-label { margin-right:0; @@ -51,7 +51,7 @@ height:15px; line-height:14px; padding:0 2px 0 3px; - background:#333; + background:#3e3e3e; -webkit-border-radius:2px 0 0 2px; -moz-border-radius:2px 0 0 2px; border-radius:2px 0 0 2px; @@ -66,11 +66,11 @@ float:left; height:15px; padding:0; - border-left:2px solid #333; + border-left:2px solid #3e3e3e; background:url(images/source_to_switch_lines.png) repeat-x 0 0; } .source-info-block li .line-to-switch.off { - border-color:#333; + border-color:#3e3e3e; background:url(images/source_to_switch_lines.png) repeat-x 0 0; } .source-info-block li .line-to-switch.on { From c9b755e851e54e2eae7b2bbe9a0265f3ee2fdc9f Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Fri, 1 Mar 2013 18:21:06 -0500 Subject: [PATCH 37/51] CC-1999: Backup & Restore scripts -initial barebones commit --- utils/airtime-backup.py | 52 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 utils/airtime-backup.py diff --git a/utils/airtime-backup.py b/utils/airtime-backup.py new file mode 100644 index 000000000..8838d3c88 --- /dev/null +++ b/utils/airtime-backup.py @@ -0,0 +1,52 @@ +import os +import sys +import shutil + +#check if root +if os.geteuid() != 0: + print 'Must be a root user.' + sys.exit(1) + +#ask if we should backup config files +backup_config = True + +#ask if we should backup database +backup_database = True + +#ask if we should backup stor directory +backup_stor = True + +#ask if we should backup all watched directories +backup_watched = True + +#create airtime-backup directory +os.mkdir("airtime_backup") + +if backup_config: + backup_config_dir = "airtime_backup/config" + os.mkdir(backup_config_dir) + #TODO check if directory exists + config_dir = "/etc/airtime" + files = os.listdir() + for f in files: + shutil.copy(os.path.join(config_dir, f), \ + os.path.join(backup_config_dir, f) + +if backup_database: + os.mkdir("airtime_backup/database") + #TODO: get database name + #TODO use abs path + "pg_dump airtime > database.dump.sql" + +#TODO this might not be necessary +os.mkdir("airtime_backup/files") + +if backup_stor: + #TODO use abs path + backup_stor_dir = "airtime_backup/files/stor" + os.mkdir(backup_stor_dir) + shutil.copytree("/srv/airtime/stor", backup_stor_dir) + +if backup_watched: + pass + From f7168a20d3a554487df1cee4b7d6140ce728b949 Mon Sep 17 00:00:00 2001 From: Daniel James Date: Thu, 7 Mar 2013 14:43:18 +0000 Subject: [PATCH 38/51] Add versioned dependency on Silan 0.3.1 for Airtime 2.4.x series [#CC-5006] --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index b5ea18954..fc1fc9756 100644 --- a/debian/control +++ b/debian/control @@ -41,7 +41,7 @@ Depends: apache2, pwgen, python, rabbitmq-server, - silan, + silan (>= 0.3.1~), sudo, sysv-rc, tar (>= 1.22), From 91d1243554554cee9c9fc571eaf2fa78b0d0e743 Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Thu, 14 Mar 2013 16:50:55 -0400 Subject: [PATCH 39/51] CC-1469: Crossfading support (non-equal power) -initial commit --- .../pypo/liquidsoap_scripts/ls_script.liq | 22 +- python_apps/pypo/pypocli.py | 15 +- python_apps/pypo/pypofetch.py | 16 +- python_apps/pypo/pypoliqqueue.py | 104 +++++++++ python_apps/pypo/pypopush.py | 199 +++++++++++++----- python_apps/pypo/telnetliquidsoap.py | 74 +++++++ python_apps/pypo/testpypoliqqueue.py | 98 +++++++++ 7 files changed, 463 insertions(+), 65 deletions(-) create mode 100644 python_apps/pypo/pypoliqqueue.py create mode 100644 python_apps/pypo/telnetliquidsoap.py create mode 100644 python_apps/pypo/testpypoliqqueue.py diff --git a/python_apps/pypo/liquidsoap_scripts/ls_script.liq b/python_apps/pypo/liquidsoap_scripts/ls_script.liq index 566df0d9e..75935a6dd 100644 --- a/python_apps/pypo/liquidsoap_scripts/ls_script.liq +++ b/python_apps/pypo/liquidsoap_scripts/ls_script.liq @@ -35,7 +35,27 @@ just_switched = ref false %include "ls_lib.liq" -queue = audio_to_stereo(id="queue_src", request.equeue(id="queue", length=0.5)) +sources = ref [] +source_id = ref 0 + +def create_source() + sources := list.append([request.equeue(id="s#{!source_id}", length=0.5)], !sources) + source_id := !source_id + 1 +end + +create_source() +create_source() +create_source() +create_source() + +create_source() +create_source() +create_source() +create_source() + +queue = add(!sources) + +queue = audio_to_stereo(id="queue_src", queue) queue = cue_cut(queue) queue = amplify(1., override="replay_gain", queue) diff --git a/python_apps/pypo/pypocli.py b/python_apps/pypo/pypocli.py index 941f3610a..9fed011fe 100644 --- a/python_apps/pypo/pypocli.py +++ b/python_apps/pypo/pypocli.py @@ -13,11 +13,12 @@ import signal import logging import locale import os -from Queue import Queue +from Queue import Queue from threading import Lock from pypopush import PypoPush +from pypoliqqueue import PypoLiqQueue from pypofetch import PypoFetch from pypofile import PypoFile from recorder import Recorder @@ -63,7 +64,7 @@ try: LogWriter.override_std_err(logger) except Exception, e: print "Couldn't configure logging" - sys.exit() + sys.exit(1) def configure_locale(): logger.debug("Before %s", locale.nl_langinfo(locale.CODESET)) @@ -228,8 +229,16 @@ if __name__ == '__main__': stat.daemon = True stat.start() + pypoLiq_q = Queue() + liq_queue_tracker = dict() + telnet_liquidsoap = TelnetLiquidsoap() + plq = PypoLiqQueue(pypoLiq_q, telnet_lock, logger, liq_queue_tracker, \ + telnet_liquidsoap) + plq.daemon = True + plq.start() + # all join() are commented out because we want to exit entire pypo - # if pypofetch is exiting + # if pypofetch terminates #pmh.join() #recorder.join() #pp.join() diff --git a/python_apps/pypo/pypofetch.py b/python_apps/pypo/pypofetch.py index f10c8c958..06552765e 100644 --- a/python_apps/pypo/pypofetch.py +++ b/python_apps/pypo/pypofetch.py @@ -7,16 +7,16 @@ import logging.config import json import telnetlib import copy -from threading import Thread import subprocess +import datetime from Queue import Empty +from threading import Thread +from subprocess import Popen, PIPE +from configobj import ConfigObj from api_clients import api_client from std_err_override import LogWriter -from subprocess import Popen, PIPE - -from configobj import ConfigObj # configure logging logging_cfg = os.path.join(os.path.dirname(__file__), "logging.cfg") @@ -481,6 +481,7 @@ class PypoFetch(Thread): except Exception, e: pass + media_copy = {} for key in media: media_item = media[key] if (media_item['type'] == 'file'): @@ -490,12 +491,17 @@ class PypoFetch(Thread): media_item['file_ready'] = False media_filtered[key] = media_item + media_item['start'] = datetime.strptime(media_item['start'], "%Y-%m-%d-%H-%M-%S") + media_item['end'] = datetime.strptime(media_item['end'], "%Y-%m-%d-%H-%M-%S") + media_copy[media_item['start']] = media_item + + self.media_prepare_queue.put(copy.copy(media_filtered)) except Exception, e: self.logger.error("%s", e) # Send the data to pypo-push self.logger.debug("Pushing to pypo-push") - self.push_queue.put(media) + self.push_queue.put(media_copy) # cleanup diff --git a/python_apps/pypo/pypoliqqueue.py b/python_apps/pypo/pypoliqqueue.py new file mode 100644 index 000000000..53e9bbeec --- /dev/null +++ b/python_apps/pypo/pypoliqqueue.py @@ -0,0 +1,104 @@ +from threading import Thread +from collections import deque +from datetime import datetime + +import traceback +import sys + +from Queue import Empty + +import signal +def keyboardInterruptHandler(signum, frame): + logger = logging.getLogger() + logger.info('\nKeyboard Interrupt\n') + sys.exit(0) +signal.signal(signal.SIGINT, keyboardInterruptHandler) + +class PypoLiqQueue(Thread): + def __init__(self, q, telnet_lock, logger, liq_queue_tracker, \ + telnet_liquidsoap): + Thread.__init__(self) + self.queue = q + self.telnet_lock = telnet_lock + self.logger = logger + self.liq_queue_tracker = liq_queue_tracker + self.telnet_liquidsoap = telnet_liquidsoap + + def main(self): + time_until_next_play = None + schedule_deque = deque() + media_schedule = None + + while True: + try: + if time_until_next_play is None: + media_schedule = self.queue.get(block=True) + else: + media_schedule = self.queue.get(block=True, timeout=time_until_next_play) + except Empty, e: + #Time to push a scheduled item. + media_item = schedule_deque.popleft() + self.telnet_to_liquidsoap(media_item) + if len(schedule_deque): + time_until_next_play = \ + self.date_interval_to_seconds( + schedule_deque[0]['start'] - datetime.utcnow()) + else: + time_until_next_play = None + else: + #new schedule received. Replace old one with this. + schedule_deque.clear() + + keys = sorted(media_schedule.keys()) + for i in keys: + schedule_deque.append(media_schedule[i]) + + time_until_next_play = self.date_interval_to_seconds(\ + keys[0] - datetime.utcnow()) + + def is_media_item_finished(self, media_item): + return datetime.utcnow() > media_item['end'] + + def telnet_to_liquidsoap(self, media_item): + """ + telnets to liquidsoap and pushes the media_item to its queue. Push the + show name of every media_item as well, just to keep Liquidsoap up-to-date + about which show is playing. + """ + + available_queue = None + for i in self.liq_queue_tracker: + mi = self.liq_queue_tracker[i] + if mi == None or self.is_media_item_finished(mi): + #queue "i" is available. Push to this queue + available_queue = i + + if available_queue == None: + raise NoQueueAvailableException() + + try: + self.telnet_liquidsoap.queue_push(available_queue, media_item) + self.liq_queue_tracker[available_queue] = media_item + except Exception as e: + self.logger.error(e) + raise + + def date_interval_to_seconds(self, interval): + """ + Convert timedelta object into int representing the number of seconds. If + number of seconds is less than 0, then return 0. + """ + seconds = (interval.microseconds + \ + (interval.seconds + interval.days * 24 * 3600) * 10 ** 6) / float(10 ** 6) + if seconds < 0: seconds = 0 + + return seconds + + + def run(self): + try: self.main() + except Exception, e: + self.logger.error('PypoLiqQueue Exception: %s', traceback.format_exc()) + +class NoQueueAvailableException(Exception): + pass diff --git a/python_apps/pypo/pypopush.py b/python_apps/pypo/pypopush.py index f438b3bb1..ea51d0724 100644 --- a/python_apps/pypo/pypopush.py +++ b/python_apps/pypo/pypopush.py @@ -9,9 +9,15 @@ import logging.config import telnetlib import calendar import math +import traceback import os -from pypofetch import PypoFetch +from pypofetch import PypoFetch +from telnetliquidsoap import TelnetLiquidsoap +from pypoliqqueue import PypoLiqQueue + + +import Queue from Queue import Empty from threading import Thread @@ -58,6 +64,27 @@ class PypoPush(Thread): self.pushed_objects = {} self.logger = logging.getLogger('push') self.current_prebuffering_stream_id = None + self.queue_id = 0 + self.telnet_liquidsoap = TelnetLiquidsoap(telnet_lock, \ + self.logger,\ + LS_HOST,\ + LS_PORT\ + ) + + liq_queue_tracker = { + "s0": None, + "s1": None, + "s2": None, + "s3": None, + } + + self.pypoLiq_q = Queue() + self.plq = PypoLiqQueue(self.pypoLiq_q, \ + telnet_lock, \ + liq_queue_tracker, \ + self.telnet_liquidsoap) + plq.daemon = True + plq.start() def main(self): loops = 0 @@ -74,50 +101,9 @@ class PypoPush(Thread): media_schedule = self.queue.get(block=True) else: media_schedule = self.queue.get(block=True, timeout=time_until_next_play) - - chains = self.get_all_chains(media_schedule) - - #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) - - if len(current_event_chain) > 0: - try: - chains.remove(original_chain) - except ValueError, e: - self.logger.error(str(e)) - - #At this point we know that Liquidsoap is playing something, and that something - #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, liquidsoap_stream_id, current_event_chain) - - - #At this point everything in the present has been taken care of and Liquidsoap - #is playing whatever is scheduled. - #Now we need to prepare ourselves for future scheduled events. - # - next_media_item_chain = self.get_next_schedule_chain(chains, tnow) - - self.logger.debug("Next schedule chain: %s", next_media_item_chain) - if next_media_item_chain is not None: - try: - chains.remove(next_media_item_chain) - except ValueError, e: - self.logger.error(str(e)) - - chain_start = datetime.strptime(next_media_item_chain[0]['start'], "%Y-%m-%d-%H-%M-%S") - time_until_next_play = self.date_interval_to_seconds(chain_start - datetime.utcnow()) - self.logger.debug("Blocking %s seconds until show start", time_until_next_play) - else: - self.logger.debug("Blocking indefinitely since no show scheduled") - time_until_next_play = None except Empty, e: #We only get here when a new chain of tracks are ready to be played. + #"timeout" has parameter has been reached. self.push_to_liquidsoap(next_media_item_chain) next_media_item_chain = self.get_next_schedule_chain(chains, datetime.utcnow()) @@ -126,7 +112,8 @@ class PypoPush(Thread): chains.remove(next_media_item_chain) except ValueError, e: self.logger.error(str(e)) - chain_start = datetime.strptime(next_media_item_chain[0]['start'], "%Y-%m-%d-%H-%M-%S") + + chain_start = next_media_item_chain[0]['start'] time_until_next_play = self.date_interval_to_seconds(chain_start - datetime.utcnow()) self.logger.debug("Blocking %s seconds until show start", time_until_next_play) else: @@ -134,12 +121,111 @@ class PypoPush(Thread): time_until_next_play = None except Exception, e: self.logger.error(str(e)) + else: + #separate media_schedule list into currently_playing and + #scheduled_for_future lists + currently_playing, scheduled_for_future = \ + self.separate_present_future(media_schedule) + + self.verify_correct_present_media(currently_playing) + + self.future_scheduled_queue.put(scheduled_for_future) + + self.pypoLiq_q.put(scheduled_for_future) + + """ + #queue.get timeout never had a chance to expire. Instead a new + #schedule was received. Let's parse this schedule and generate + #a new timeout. + try: + chains = self.get_all_chains(media_schedule) + + #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) + + if len(current_event_chain) > 0: + try: + chains.remove(original_chain) + except ValueError, e: + self.logger.error(str(e)) + + #At this point we know that Liquidsoap is playing something, and that something + #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, \ + liquidsoap_stream_id, \ + current_event_chain) + + #At this point everything in the present has been taken care of and Liquidsoap + #is playing whatever is scheduled. + #Now we need to prepare ourselves for future scheduled events. + next_media_item_chain = self.get_next_schedule_chain(chains, tnow) + + self.logger.debug("Next schedule chain: %s", next_media_item_chain) + if next_media_item_chain is not None: + try: + chains.remove(next_media_item_chain) + except ValueError, e: + self.logger.error(str(e)) + + chain_start = datetime.strptime(next_media_item_chain[0]['start'], "%Y-%m-%d-%H-%M-%S") + time_until_next_play = self.date_interval_to_seconds(chain_start - datetime.utcnow()) + self.logger.debug("Blocking %s seconds until show start", time_until_next_play) + else: + self.logger.debug("Blocking indefinitely since no show scheduled") + time_until_next_play = None + except Exception, e: + self.logger.error(str(e)) + """ if loops % heartbeat_period == 0: self.logger.info("heartbeat") loops = 0 loops += 1 + + def separate_present_future(self, media_schedule): + tnow = datetime.utcnow() + + present = {} + future = {} + + sorted_keys = sorted(media_schedule.keys()) + for mkey in sorted_keys: + media_item = media_schedule[mkey] + + media_item_start = media_item['start'] + diff_td = tnow - media_item_start + diff_sec = self.date_interval_to_seconds(diff_td) + + if diff_sec >= 0: + present[media_item['start']] = media_item + else: + future[media_item['start']] = media_item + + return present, future + + def verify_correct_present_media(self, currently_playing): + #verify whether Liquidsoap is currently playing the correct items. + #if we find an item that Liquidsoap is not playing, then push it + #into one of Liquidsoap's queues. If Liquidsoap is already playing + #it do nothing. If Liquidsoap is playing a track that isn't in + #currently_playing then stop it. + + #Check for Liquidsoap media we should source.skip + #get liquidsoap items for each queue. Since each queue can only have one + #item, we should have a max of 8 items. + #TODO + + #Check for media Liquidsoap should start playing + #TODO + def get_current_stream_id_from_liquidsoap(self): response = "-1" try: @@ -167,7 +253,7 @@ class PypoPush(Thread): self.telnet_lock.acquire() tn = telnetlib.Telnet(LS_HOST, LS_PORT) - msg = 'queue.queue\n' + msg = 's0.queue\n' tn.write(msg) response = tn.read_until("\r\n").strip(" \r\n") tn.write('exit\n') @@ -355,7 +441,7 @@ class PypoPush(Thread): def modify_cue_point(self, link): tnow = datetime.utcnow() - link_start = datetime.strptime(link['start'], "%Y-%m-%d-%H-%M-%S") + link_start = link['start'] diff_td = tnow - link_start diff_sec = self.date_interval_to_seconds(diff_td) @@ -399,8 +485,8 @@ class PypoPush(Thread): for chain in chains: iteration = 0 for link in chain: - link_start = datetime.strptime(link['start'], "%Y-%m-%d-%H-%M-%S") - link_end = datetime.strptime(link['end'], "%Y-%m-%d-%H-%M-%S") + link_start = link['start'] + link_end = link['end'] self.logger.debug("tnow %s, chain_start %s", tnow, link_start) if link_start <= tnow and tnow < link_end: @@ -423,10 +509,12 @@ class PypoPush(Thread): closest_start = None closest_chain = None for chain in chains: - chain_start = datetime.strptime(chain[0]['start'], "%Y-%m-%d-%H-%M-%S") - chain_end = datetime.strptime(chain[-1]['end'], "%Y-%m-%d-%H-%M-%S") + chain_start = chain[0]['start'] + chain_end = chain[-1]['end'] self.logger.debug("tnow %s, chain_start %s", tnow, chain_start) - if (closest_start == None or chain_start < closest_start) and (chain_start > tnow or (chain_start < tnow and chain_end > tnow)): + if (closest_start == None or chain_start < closest_start) and \ + (chain_start > tnow or \ + (chain_start < tnow and chain_end > tnow)): closest_start = chain_start closest_chain = chain @@ -482,6 +570,8 @@ class PypoPush(Thread): self.stop_web_stream_output(media_item) except Exception, e: self.logger.error('Pypo Push Exception: %s', e) + finally: + self.queue_id = (self.queue_id + 1) % 8 def start_web_stream_buffer(self, media_item): @@ -640,7 +730,7 @@ class PypoPush(Thread): self.logger.debug(msg) tn.write(msg) - msg = "queue.queue\n" + msg = "s0.queue\n" self.logger.debug(msg) tn.write(msg) @@ -687,10 +777,8 @@ class PypoPush(Thread): self.telnet_lock.acquire() tn = telnetlib.Telnet(LS_HOST, LS_PORT) - #tn.write(("vars.pypo_data %s\n"%liquidsoap_data["schedule_id"]).encode('utf-8')) - annotation = self.create_liquidsoap_annotation(media_item) - msg = 'queue.push %s\n' % annotation.encode('utf-8') + msg = 's%s.push %s\n' % (self.queue_id, annotation.encode('utf-8')) self.logger.debug(msg) tn.write(msg) queue_id = tn.read_until("\r\n").strip("\r\n") @@ -722,7 +810,6 @@ class PypoPush(Thread): def run(self): try: self.main() except Exception, e: - import traceback top = traceback.format_exc() self.logger.error('Pypo Push Exception: %s', top) diff --git a/python_apps/pypo/telnetliquidsoap.py b/python_apps/pypo/telnetliquidsoap.py new file mode 100644 index 000000000..5131d2c62 --- /dev/null +++ b/python_apps/pypo/telnetliquidsoap.py @@ -0,0 +1,74 @@ +import telnetlib + +def create_liquidsoap_annotation(media): + # We need liq_start_next value in the annotate. That is the value that controls overlap duration of crossfade. + return 'annotate:media_id="%s",liq_start_next="0",liq_fade_in="%s",liq_fade_out="%s",liq_cue_in="%s",liq_cue_out="%s",schedule_table_id="%s",replay_gain="%s dB":%s' \ + % (media['id'], float(media['fade_in']) / 1000, float(media['fade_out']) / 1000, float(media['cue_in']), float(media['cue_out']), media['row_id'], media['replay_gain'], media['dst']) + +class TelnetLiquidsoap: + + def __init__(self, telnet_lock, logger, ls_host, ls_port): + self.telnet_lock = telnet_lock + self.ls_host = ls_host + self.ls_port = ls_port + self.logger = logger + + def __connect(self): + return telnetlib.Telnet(self.ls_host, self.ls_port) + + def __is_empty(self, tn, queue_id): + return True + + + def queue_push(self, queue_id, media_item): + try: + self.telnet_lock.acquire() + tn = self.__connect() + + if not self.__is_empty(tn, queue_id): + raise QueueNotEmptyException() + + annotation = create_liquidsoap_annotation(media_item) + msg = '%s.push %s\n' % (queue_id, annotation.encode('utf-8')) + self.logger.debug(msg) + tn.write(msg) + + show_name = media_item['show_name'] + msg = 'vars.show_name %s\n' % show_name.encode('utf-8') + tn.write(msg) + self.logger.debug(msg) + + tn.write("exit\n") + self.logger.debug(tn.read_all()) + except Exception: + raise + finally: + self.telnet_lock.release() + +class DummyTelnetLiquidsoap: + + def __init__(self, telnet_lock, logger): + self.telnet_lock = telnet_lock + self.liquidsoap_mock_queues = {} + self.logger = logger + + for i in range(4): + self.liquidsoap_mock_queues["s"+str(i)] = [] + + def queue_push(self, queue_id, media_item): + try: + self.telnet_lock.acquire() + + self.logger.info("Pushing %s to queue %s" % (media_item, queue_id)) + from datetime import datetime + print "Time now: %s" % datetime.utcnow() + + annotation = create_liquidsoap_annotation(media_item) + self.liquidsoap_mock_queues[queue_id].append(annotation) + except Exception: + raise + finally: + self.telnet_lock.release() + +class QueueNotEmptyException(Exception): + pass diff --git a/python_apps/pypo/testpypoliqqueue.py b/python_apps/pypo/testpypoliqqueue.py new file mode 100644 index 000000000..f1847b34f --- /dev/null +++ b/python_apps/pypo/testpypoliqqueue.py @@ -0,0 +1,98 @@ +from pypoliqqueue import PypoLiqQueue +from telnetliquidsoap import DummyTelnetLiquidsoap, TelnetLiquidsoap + + +from Queue import Queue +from threading import Lock + +import sys +import signal +import logging +from datetime import datetime +from datetime import timedelta + +def keyboardInterruptHandler(signum, frame): + logger = logging.getLogger() + logger.info('\nKeyboard Interrupt\n') + sys.exit(0) +signal.signal(signal.SIGINT, keyboardInterruptHandler) + +# configure logging +format = '%(levelname)s - %(pathname)s - %(lineno)s - %(asctime)s - %(message)s' +logging.basicConfig(level=logging.DEBUG, format=format) +logging.captureWarnings(True) + +telnet_lock = Lock() +pypoPush_q = Queue() + + +pypoLiq_q = Queue() +liq_queue_tracker = { + "s0": None, + "s1": None, + "s2": None, + "s3": None, + } + +#dummy_telnet_liquidsoap = DummyTelnetLiquidsoap(telnet_lock, logging) +dummy_telnet_liquidsoap = TelnetLiquidsoap(telnet_lock, logging, \ + "localhost", \ + 1234) + +plq = PypoLiqQueue(pypoLiq_q, telnet_lock, logging, liq_queue_tracker, \ + dummy_telnet_liquidsoap) +plq.daemon = True +plq.start() + + +print "Time now: %s" % datetime.utcnow() + +media_schedule = {} + +start_dt = datetime.utcnow() + timedelta(seconds=1) +end_dt = datetime.utcnow() + timedelta(seconds=6) + +media_schedule[start_dt] = {"id": 5, \ + "type":"file", \ + "row_id":9, \ + "uri":"", \ + "dst":"/home/martin/Music/ipod/Hot Chocolate - You Sexy Thing.mp3", \ + "fade_in":0, \ + "fade_out":0, \ + "cue_in":0, \ + "cue_out":300, \ + "start": start_dt, \ + "end": end_dt, \ + "show_name":"Untitled", \ + "replay_gain": 0, \ + "independent_event": True \ + } + + + +start_dt = datetime.utcnow() + timedelta(seconds=2) +end_dt = datetime.utcnow() + timedelta(seconds=6) + +media_schedule[start_dt] = {"id": 5, \ + "type":"file", \ + "row_id":9, \ + "uri":"", \ + "dst":"/home/martin/Music/ipod/Good Charlotte - bloody valentine.mp3", \ + "fade_in":0, \ + "fade_out":0, \ + "cue_in":0, \ + "cue_out":300, \ + "start": start_dt, \ + "end": end_dt, \ + "show_name":"Untitled", \ + "replay_gain": 0, \ + "independent_event": True \ + } +pypoLiq_q.put(media_schedule) + +plq.join() + + + + + From 2b7ebafa94e45de557a04ad774baed790dbbb5bd Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Thu, 14 Mar 2013 18:29:52 -0400 Subject: [PATCH 40/51] CC-1469: Crossfading support (non-equal power) -further implementation.. --- python_apps/pypo/pypoliqqueue.py | 21 ++++++++---- python_apps/pypo/pypopush.py | 48 +++++++++++++++++++++------- python_apps/pypo/telnetliquidsoap.py | 15 +++++++++ 3 files changed, 66 insertions(+), 18 deletions(-) diff --git a/python_apps/pypo/pypoliqqueue.py b/python_apps/pypo/pypoliqqueue.py index 53e9bbeec..027a2cd5f 100644 --- a/python_apps/pypo/pypoliqqueue.py +++ b/python_apps/pypo/pypoliqqueue.py @@ -43,6 +43,8 @@ class PypoLiqQueue(Thread): time_until_next_play = \ self.date_interval_to_seconds( schedule_deque[0]['start'] - datetime.utcnow()) + if time_until_next_play < 0: + time_until_next_play = 0 else: time_until_next_play = None else: @@ -59,13 +61,7 @@ class PypoLiqQueue(Thread): def is_media_item_finished(self, media_item): return datetime.utcnow() > media_item['end'] - def telnet_to_liquidsoap(self, media_item): - """ - telnets to liquidsoap and pushes the media_item to its queue. Push the - show name of every media_item as well, just to keep Liquidsoap up-to-date - about which show is playing. - """ - + def find_available_queue(self): available_queue = None for i in self.liq_queue_tracker: mi = self.liq_queue_tracker[i] @@ -76,6 +72,17 @@ class PypoLiqQueue(Thread): if available_queue == None: raise NoQueueAvailableException() + return available_queue + + def telnet_to_liquidsoap(self, media_item): + """ + telnets to liquidsoap and pushes the media_item to its queue. Push the + show name of every media_item as well, just to keep Liquidsoap up-to-date + about which show is playing. + """ + + available_queue = self.find_available_queue() + try: self.telnet_liquidsoap.queue_push(available_queue, media_item) self.liq_queue_tracker[available_queue] = media_item diff --git a/python_apps/pypo/pypopush.py b/python_apps/pypo/pypopush.py index ea51d0724..b6f83623e 100644 --- a/python_apps/pypo/pypopush.py +++ b/python_apps/pypo/pypopush.py @@ -71,15 +71,15 @@ class PypoPush(Thread): LS_PORT\ ) - liq_queue_tracker = { + self.liq_queue_tracker = { "s0": None, "s1": None, "s2": None, "s3": None, } - self.pypoLiq_q = Queue() - self.plq = PypoLiqQueue(self.pypoLiq_q, \ + self.future_scheduled_queue = Queue() + self.plq = PypoLiqQueue(self.future_scheduled_queue, \ telnet_lock, \ liq_queue_tracker, \ self.telnet_liquidsoap) @@ -131,8 +131,6 @@ class PypoPush(Thread): self.future_scheduled_queue.put(scheduled_for_future) - self.pypoLiq_q.put(scheduled_for_future) - """ #queue.get timeout never had a chance to expire. Instead a new #schedule was received. Let's parse this schedule and generate @@ -193,7 +191,7 @@ class PypoPush(Thread): def separate_present_future(self, media_schedule): tnow = datetime.utcnow() - present = {} + present = [] future = {} sorted_keys = sorted(media_schedule.keys()) @@ -205,13 +203,13 @@ class PypoPush(Thread): diff_sec = self.date_interval_to_seconds(diff_td) if diff_sec >= 0: - present[media_item['start']] = media_item + present.append(media_item) else: future[media_item['start']] = media_item return present, future - def verify_correct_present_media(self, currently_playing): + def verify_correct_present_media(self, scheduled_now): #verify whether Liquidsoap is currently playing the correct items. #if we find an item that Liquidsoap is not playing, then push it #into one of Liquidsoap's queues. If Liquidsoap is already playing @@ -221,10 +219,38 @@ class PypoPush(Thread): #Check for Liquidsoap media we should source.skip #get liquidsoap items for each queue. Since each queue can only have one #item, we should have a max of 8 items. - #TODO - #Check for media Liquidsoap should start playing - #TODO + schedule_ids = set() + for i in scheduled_now: + schedule_ids.add(i["row_id"]) + + liq_queue_ids = set() + for i in self.liq_queue_tracker: + mi = self.liq_queue_tracker[i] + if not self.plq.is_media_item_finished(mi): + liq_queue_ids.add(mi["row_id"]) + + to_be_added = schedule_ids - liq_queue_ids + to_be_removed = liq_queue_ids - schedule_ids + + if len(to_be_removed): + self.logger.info("Need to remove items from Liquidsoap: %s" % \ + to_be_removed) + + for i in self.liq_queue_tracker: + mi = self.liq_queue_tracker[i] + if mi["row_id"] in to_be_removed: + self.telnet_liquidsoap.queue_remove(i) + + + if len(to_be_added): + self.logger.info("Need to add items to Liquidsoap *now*: %s" % \ + to_be_added) + + for i in scheduled_now: + if i["row_id"] in to_be_added: + queue_id = self.plq.find_available_queue() + self.telnet_liquidsoap.queue_push(queue_id) def get_current_stream_id_from_liquidsoap(self): response = "-1" diff --git a/python_apps/pypo/telnetliquidsoap.py b/python_apps/pypo/telnetliquidsoap.py index 5131d2c62..2896c982a 100644 --- a/python_apps/pypo/telnetliquidsoap.py +++ b/python_apps/pypo/telnetliquidsoap.py @@ -19,6 +19,21 @@ class TelnetLiquidsoap: def __is_empty(self, tn, queue_id): return True + def queue_remove(self, queue_id): + try: + self.telnet_lock.acquire() + tn = self.__connect() + + + #TODO: Need a source.skip for each queue + + tn.write("exit\n") + self.logger.debug(tn.read_all()) + except Exception: + raise + finally: + self.telnet_lock.release() + def queue_push(self, queue_id, media_item): try: From dd7fc61e23994e80f32b968d902c8340014a8249 Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Fri, 15 Mar 2013 12:50:23 -0400 Subject: [PATCH 41/51] CC-1469: Crossfading support (non-equal power) -further implementation.. --- .../pypo/liquidsoap_scripts/ls_lib.liq | 46 ++++++++++--------- .../pypo/liquidsoap_scripts/ls_script.liq | 11 ++++- python_apps/pypo/pypocli.py | 14 +++--- python_apps/pypo/pypofetch.py | 2 +- python_apps/pypo/pypoliqqueue.py | 16 +++++-- python_apps/pypo/pypopush.py | 23 +++++----- python_apps/pypo/telnetliquidsoap.py | 20 ++++++-- 7 files changed, 83 insertions(+), 49 deletions(-) diff --git a/python_apps/pypo/liquidsoap_scripts/ls_lib.liq b/python_apps/pypo/liquidsoap_scripts/ls_lib.liq index 5aa77cac8..ccb37026f 100644 --- a/python_apps/pypo/liquidsoap_scripts/ls_lib.liq +++ b/python_apps/pypo/liquidsoap_scripts/ls_lib.liq @@ -354,28 +354,32 @@ end # Add a skip function to a source # when it does not have one # by default -def add_skip_command(s) - # A command to skip - def skip(_) - # get playing (active) queue and flush it - l = list.hd(server.execute("queue.secondary_queue")) - l = string.split(separator=" ",l) - list.iter(fun (rid) -> ignore(server.execute("queue.remove #{rid}")), l) +#def add_skip_command(s) +# # A command to skip +# def skip(_) +# # get playing (active) queue and flush it +# l = list.hd(server.execute("queue.secondary_queue")) +# l = string.split(separator=" ",l) +# list.iter(fun (rid) -> ignore(server.execute("queue.remove #{rid}")), l) +# +# l = list.hd(server.execute("queue.primary_queue")) +# l = string.split(separator=" ", l) +# if list.length(l) > 0 then +# source.skip(s) +# "Skipped" +# else +# "Not skipped" +# end +# end +# # Register the command: +# server.register(namespace="source", +# usage="skip", +# description="Skip the current song.", +# "skip",fun(s) -> begin log("source.skip") skip(s) end) +#end - l = list.hd(server.execute("queue.primary_queue")) - l = string.split(separator=" ", l) - if list.length(l) > 0 then - source.skip(s) - "Skipped" - else - "Not skipped" - end - end - # Register the command: - server.register(namespace="source", - usage="skip", - description="Skip the current song.", - "skip",fun(s) -> begin log("source.skip") skip(s) end) +def clear_queue(s) + source.skip(s) end def set_dynamic_source_id(id) = diff --git a/python_apps/pypo/liquidsoap_scripts/ls_script.liq b/python_apps/pypo/liquidsoap_scripts/ls_script.liq index 75935a6dd..353b9c2c5 100644 --- a/python_apps/pypo/liquidsoap_scripts/ls_script.liq +++ b/python_apps/pypo/liquidsoap_scripts/ls_script.liq @@ -39,7 +39,14 @@ sources = ref [] source_id = ref 0 def create_source() - sources := list.append([request.equeue(id="s#{!source_id}", length=0.5)], !sources) + l = request.equeue(id="s#{!source_id}", length=0.5) + sources := list.append([l], !sources) + server.register(namespace="queues", + "s#{!source_id}_skip", + fun (s) -> begin log("queues.s#{!source_id}_skip") + clear_queue(l) + "Done" + end) source_id := !source_id + 1 end @@ -268,7 +275,7 @@ end # Attach a skip command to the source s: -add_skip_command(s) +#add_skip_command(s) server.register(namespace="streams", description="Stop Master DJ source.", diff --git a/python_apps/pypo/pypocli.py b/python_apps/pypo/pypocli.py index 9fed011fe..02dfe6c46 100644 --- a/python_apps/pypo/pypocli.py +++ b/python_apps/pypo/pypocli.py @@ -229,13 +229,13 @@ if __name__ == '__main__': stat.daemon = True stat.start() - pypoLiq_q = Queue() - liq_queue_tracker = dict() - telnet_liquidsoap = TelnetLiquidsoap() - plq = PypoLiqQueue(pypoLiq_q, telnet_lock, logger, liq_queue_tracker, \ - telnet_liquidsoap) - plq.daemon = True - plq.start() + #pypoLiq_q = Queue() + #liq_queue_tracker = dict() + #telnet_liquidsoap = TelnetLiquidsoap() + #plq = PypoLiqQueue(pypoLiq_q, telnet_lock, logger, liq_queue_tracker, \ + #telnet_liquidsoap) + #plq.daemon = True + #plq.start() # all join() are commented out because we want to exit entire pypo # if pypofetch terminates diff --git a/python_apps/pypo/pypofetch.py b/python_apps/pypo/pypofetch.py index 06552765e..3804b3458 100644 --- a/python_apps/pypo/pypofetch.py +++ b/python_apps/pypo/pypofetch.py @@ -8,7 +8,7 @@ import json import telnetlib import copy import subprocess -import datetime +from datetime import datetime from Queue import Empty from threading import Thread diff --git a/python_apps/pypo/pypoliqqueue.py b/python_apps/pypo/pypoliqqueue.py index 027a2cd5f..87458d4df 100644 --- a/python_apps/pypo/pypoliqqueue.py +++ b/python_apps/pypo/pypoliqqueue.py @@ -32,9 +32,13 @@ class PypoLiqQueue(Thread): while True: try: if time_until_next_play is None: + self.logger.info("waiting indefinitely for schedule") media_schedule = self.queue.get(block=True) else: - media_schedule = self.queue.get(block=True, timeout=time_until_next_play) + self.logger.info("waiting %ss until next scheduled item" % \ + time_until_next_play) + media_schedule = self.queue.get(block=True, \ + timeout=time_until_next_play) except Empty, e: #Time to push a scheduled item. media_item = schedule_deque.popleft() @@ -55,11 +59,15 @@ class PypoLiqQueue(Thread): for i in keys: schedule_deque.append(media_schedule[i]) - time_until_next_play = self.date_interval_to_seconds(\ - keys[0] - datetime.utcnow()) + if len(keys): + time_until_next_play = self.date_interval_to_seconds(\ + keys[0] - datetime.utcnow()) def is_media_item_finished(self, media_item): - return datetime.utcnow() > media_item['end'] + if media_item is None: + return True + else: + return datetime.utcnow() > media_item['end'] def find_available_queue(self): available_queue = None diff --git a/python_apps/pypo/pypopush.py b/python_apps/pypo/pypopush.py index b6f83623e..0b4371042 100644 --- a/python_apps/pypo/pypopush.py +++ b/python_apps/pypo/pypopush.py @@ -17,8 +17,7 @@ from telnetliquidsoap import TelnetLiquidsoap from pypoliqqueue import PypoLiqQueue -import Queue -from Queue import Empty +from Queue import Empty, Queue from threading import Thread @@ -81,10 +80,11 @@ class PypoPush(Thread): self.future_scheduled_queue = Queue() self.plq = PypoLiqQueue(self.future_scheduled_queue, \ telnet_lock, \ - liq_queue_tracker, \ + self.logger, \ + self.liq_queue_tracker, \ self.telnet_liquidsoap) - plq.daemon = True - plq.start() + self.plq.daemon = True + self.plq.start() def main(self): loops = 0 @@ -198,8 +198,7 @@ class PypoPush(Thread): for mkey in sorted_keys: media_item = media_schedule[mkey] - media_item_start = media_item['start'] - diff_td = tnow - media_item_start + diff_td = tnow - media_item['start'] diff_sec = self.date_interval_to_seconds(diff_td) if diff_sec >= 0: @@ -230,8 +229,8 @@ class PypoPush(Thread): if not self.plq.is_media_item_finished(mi): liq_queue_ids.add(mi["row_id"]) - to_be_added = schedule_ids - liq_queue_ids to_be_removed = liq_queue_ids - schedule_ids + to_be_added = schedule_ids - liq_queue_ids if len(to_be_removed): self.logger.info("Need to remove items from Liquidsoap: %s" % \ @@ -239,8 +238,9 @@ class PypoPush(Thread): for i in self.liq_queue_tracker: mi = self.liq_queue_tracker[i] - if mi["row_id"] in to_be_removed: + if mi is not None and mi["row_id"] in to_be_removed: self.telnet_liquidsoap.queue_remove(i) + self.liq_queue_tracker[i] = None if len(to_be_added): @@ -249,8 +249,10 @@ class PypoPush(Thread): for i in scheduled_now: if i["row_id"] in to_be_added: + self.modify_cue_point(i) queue_id = self.plq.find_available_queue() - self.telnet_liquidsoap.queue_push(queue_id) + self.telnet_liquidsoap.queue_push(queue_id, i) + self.liq_queue_tracker[queue_id] = i def get_current_stream_id_from_liquidsoap(self): response = "-1" @@ -554,7 +556,6 @@ class PypoPush(Thread): """ seconds = (interval.microseconds + \ (interval.seconds + interval.days * 24 * 3600) * 10 ** 6) / float(10 ** 6) - if seconds < 0: seconds = 0 return seconds diff --git a/python_apps/pypo/telnetliquidsoap.py b/python_apps/pypo/telnetliquidsoap.py index 2896c982a..7bc7e362d 100644 --- a/python_apps/pypo/telnetliquidsoap.py +++ b/python_apps/pypo/telnetliquidsoap.py @@ -24,9 +24,10 @@ class TelnetLiquidsoap: self.telnet_lock.acquire() tn = self.__connect() - - #TODO: Need a source.skip for each queue - + msg = 'queues.%s_skip\n' % queue_id + self.logger.debug(msg) + tn.write(msg) + tn.write("exit\n") self.logger.debug(tn.read_all()) except Exception: @@ -85,5 +86,18 @@ class DummyTelnetLiquidsoap: finally: self.telnet_lock.release() + def queue_remove(self, queue_id): + try: + self.telnet_lock.acquire() + + self.logger.info("Purging queue %s" % queue_id) + from datetime import datetime + print "Time now: %s" % datetime.utcnow() + + except Exception: + raise + finally: + self.telnet_lock.release() + class QueueNotEmptyException(Exception): pass From 445573dcdbedfdb1bd4048c439eec962d5af0eaf Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Fri, 15 Mar 2013 15:07:55 -0400 Subject: [PATCH 42/51] CC-1469: Crossfading support (non-equal power) -webstreams scheduled in the future are now working... --- python_apps/pypo/eventtypes.py | 6 + python_apps/pypo/pypofetch.py | 2 +- python_apps/pypo/pypoliqqueue.py | 65 ++- python_apps/pypo/pypopush.py | 612 +-------------------------- python_apps/pypo/telnetliquidsoap.py | 90 ++++ 5 files changed, 177 insertions(+), 598 deletions(-) create mode 100644 python_apps/pypo/eventtypes.py diff --git a/python_apps/pypo/eventtypes.py b/python_apps/pypo/eventtypes.py new file mode 100644 index 000000000..5f9c871db --- /dev/null +++ b/python_apps/pypo/eventtypes.py @@ -0,0 +1,6 @@ +FILE = "file" +EVENT = "event" +STREAM_BUFFER_START = "stream_buffer_start" +STREAM_OUTPUT_START = "stream_output_start" +STREAM_BUFFER_END = "stream_buffer_end" +STREAM_OUTPUT_END = "stream_output_end" diff --git a/python_apps/pypo/pypofetch.py b/python_apps/pypo/pypofetch.py index 3804b3458..66243a15a 100644 --- a/python_apps/pypo/pypofetch.py +++ b/python_apps/pypo/pypofetch.py @@ -540,7 +540,7 @@ class PypoFetch(Thread): #check if this file is opened (sometimes Liquidsoap is still #playing the file due to our knowledge of the track length #being incorrect!) - if not self.is_file_opened(): + if not self.is_file_opened(path): os.remove(path) except Exception, e: self.logger.error(e) diff --git a/python_apps/pypo/pypoliqqueue.py b/python_apps/pypo/pypoliqqueue.py index 87458d4df..54d7c5d93 100644 --- a/python_apps/pypo/pypoliqqueue.py +++ b/python_apps/pypo/pypoliqqueue.py @@ -1,9 +1,13 @@ from threading import Thread from collections import deque from datetime import datetime +from pypofetch import PypoFetch + +import eventtypes import traceback import sys +import time from Queue import Empty @@ -52,6 +56,8 @@ class PypoLiqQueue(Thread): else: time_until_next_play = None else: + self.logger.info("New schedule received: %s", media_schedule) + #new schedule received. Replace old one with this. schedule_deque.clear() @@ -62,6 +68,8 @@ class PypoLiqQueue(Thread): if len(keys): time_until_next_play = self.date_interval_to_seconds(\ keys[0] - datetime.utcnow()) + else: + time_until_next_play = None def is_media_item_finished(self, media_item): if media_item is None: @@ -88,15 +96,53 @@ class PypoLiqQueue(Thread): show name of every media_item as well, just to keep Liquidsoap up-to-date about which show is playing. """ - - available_queue = self.find_available_queue() - try: - self.telnet_liquidsoap.queue_push(available_queue, media_item) - self.liq_queue_tracker[available_queue] = media_item - except Exception as e: - self.logger.error(e) - raise + if media_item["type"] == eventtypes.FILE: + self.handle_file_type(media_item) + elif media_item["type"] == eventtypes.EVENT: + self.handle_event_type(media_item) + elif media_item["type"] == eventtypes.STREAM_BUFFER_START: + self.telnet_liquidsoap.start_web_stream_buffer(media_item) + elif media_item["type"] == eventtypes.STREAM_OUTPUT_START: + if media_item['row_id'] != self.telnet_liquidsoap.current_prebuffering_stream_id: + #this is called if the stream wasn't scheduled sufficiently ahead of time + #so that the prebuffering stage could take effect. Let's do the prebuffering now. + self.telnet_liquidsoap.start_web_stream_buffer(media_item) + self.telnet_liquidsoap.start_web_stream(media_item) + elif media_item['type'] == eventtypes.STREAM_BUFFER_END: + self.telnet_liquidsoap.stop_web_stream_buffer(media_item) + elif media_item['type'] == eventtypes.STREAM_OUTPUT_END: + self.telnet_liquidsoap.stop_web_stream_output(media_item) + else: raise UnknownMediaItemType(str(media_item)) + + def handle_event_type(self, media_item): + if media_item['event_type'] == "kick_out": + PypoFetch.disconnect_source(self.logger, self.telnet_lock, "live_dj") + elif media_item['event_type'] == "switch_off": + PypoFetch.switch_source(self.logger, self.telnet_lock, "live_dj", "off") + + + def handle_file_type(self, media_item): + """ + Wait maximum 5 seconds (50 iterations) for file to become ready, + otherwise give up on it. + """ + iter_num = 0 + while not media_item['file_ready'] and iter_num < 50: + time.sleep(0.1) + iter_num += 1 + + if media_item['file_ready']: + available_queue = self.find_available_queue() + + try: + self.telnet_liquidsoap.queue_push(available_queue, media_item) + self.liq_queue_tracker[available_queue] = media_item + except Exception as e: + self.logger.error(e) + raise + else: + self.logger.warn("File %s did not become ready in less than 5 seconds. Skipping...", media_item['dst']) def date_interval_to_seconds(self, interval): """ @@ -117,3 +163,6 @@ class PypoLiqQueue(Thread): class NoQueueAvailableException(Exception): pass + +class UnknownMediaItemType(Exception): + pass diff --git a/python_apps/pypo/pypopush.py b/python_apps/pypo/pypopush.py index 0b4371042..6ae3e27d1 100644 --- a/python_apps/pypo/pypopush.py +++ b/python_apps/pypo/pypopush.py @@ -16,7 +16,6 @@ from pypofetch import PypoFetch from telnetliquidsoap import TelnetLiquidsoap from pypoliqqueue import PypoLiqQueue - from Queue import Empty, Queue from threading import Thread @@ -41,7 +40,6 @@ try: LS_HOST = config['ls_host'] LS_PORT = config['ls_port'] PUSH_INTERVAL = 2 - MAX_LIQUIDSOAP_QUEUE_LENGTH = 2 except Exception, e: logger.error('Error loading config file %s', e) sys.exit() @@ -90,37 +88,14 @@ class PypoPush(Thread): loops = 0 heartbeat_period = math.floor(30 / PUSH_INTERVAL) - next_media_item_chain = None media_schedule = None - time_until_next_play = None - chains = None while True: try: - if time_until_next_play is None: - media_schedule = self.queue.get(block=True) - else: - media_schedule = self.queue.get(block=True, timeout=time_until_next_play) - except Empty, e: - #We only get here when a new chain of tracks are ready to be played. - #"timeout" has parameter has been reached. - self.push_to_liquidsoap(next_media_item_chain) - - next_media_item_chain = self.get_next_schedule_chain(chains, datetime.utcnow()) - if next_media_item_chain is not None: - try: - chains.remove(next_media_item_chain) - except ValueError, e: - self.logger.error(str(e)) - - chain_start = next_media_item_chain[0]['start'] - time_until_next_play = self.date_interval_to_seconds(chain_start - datetime.utcnow()) - self.logger.debug("Blocking %s seconds until show start", time_until_next_play) - else: - self.logger.debug("Blocking indefinitely since no show scheduled next") - time_until_next_play = None + media_schedule = self.queue.get(block=True) except Exception, e: self.logger.error(str(e)) + raise else: #separate media_schedule list into currently_playing and #scheduled_for_future lists @@ -128,60 +103,8 @@ class PypoPush(Thread): self.separate_present_future(media_schedule) self.verify_correct_present_media(currently_playing) - self.future_scheduled_queue.put(scheduled_for_future) - """ - #queue.get timeout never had a chance to expire. Instead a new - #schedule was received. Let's parse this schedule and generate - #a new timeout. - try: - chains = self.get_all_chains(media_schedule) - - #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) - - if len(current_event_chain) > 0: - try: - chains.remove(original_chain) - except ValueError, e: - self.logger.error(str(e)) - - #At this point we know that Liquidsoap is playing something, and that something - #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, \ - liquidsoap_stream_id, \ - current_event_chain) - - #At this point everything in the present has been taken care of and Liquidsoap - #is playing whatever is scheduled. - #Now we need to prepare ourselves for future scheduled events. - next_media_item_chain = self.get_next_schedule_chain(chains, tnow) - - self.logger.debug("Next schedule chain: %s", next_media_item_chain) - if next_media_item_chain is not None: - try: - chains.remove(next_media_item_chain) - except ValueError, e: - self.logger.error(str(e)) - - chain_start = datetime.strptime(next_media_item_chain[0]['start'], "%Y-%m-%d-%H-%M-%S") - time_until_next_play = self.date_interval_to_seconds(chain_start - datetime.utcnow()) - self.logger.debug("Blocking %s seconds until show start", time_until_next_play) - else: - self.logger.debug("Blocking indefinitely since no show scheduled") - time_until_next_play = None - except Exception, e: - self.logger.error(str(e)) - """ - if loops % heartbeat_period == 0: self.logger.info("heartbeat") loops = 0 @@ -209,7 +132,7 @@ class PypoPush(Thread): return present, future def verify_correct_present_media(self, scheduled_now): - #verify whether Liquidsoap is currently playing the correct items. + #verify whether Liquidsoap is currently playing the correct files. #if we find an item that Liquidsoap is not playing, then push it #into one of Liquidsoap's queues. If Liquidsoap is already playing #it do nothing. If Liquidsoap is playing a track that isn't in @@ -219,6 +142,9 @@ class PypoPush(Thread): #get liquidsoap items for each queue. Since each queue can only have one #item, we should have a max of 8 items. + #TODO: Verify start, end, replay_gain is the same + #TODO: Verify this is a file or webstream and also handle webstreams + schedule_ids = set() for i in scheduled_now: schedule_ids.add(i["row_id"]) @@ -273,198 +199,24 @@ class PypoPush(Thread): return response - def get_queue_items_from_liquidsoap(self): - """ - This function connects to Liquidsoap to find what media items are in its queue. - """ - try: - self.telnet_lock.acquire() - tn = telnetlib.Telnet(LS_HOST, LS_PORT) + #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 liquidsoap_stream_id == "-1") + #else: + #if is_file(media_item): + #if len(liquidsoap_queue_approx) == 0: + #correct = False + #else: + #correct = liquidsoap_queue_approx[0]['start'] == media_item['start'] and \ + #liquidsoap_queue_approx[0]['row_id'] == media_item['row_id'] and \ + #liquidsoap_queue_approx[0]['end'] == media_item['end'] and \ + #liquidsoap_queue_approx[0]['replay_gain'] == media_item['replay_gain'] + #elif is_stream(media_item): + #correct = liquidsoap_stream_id == str(media_item['row_id']) - msg = 's0.queue\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() - - liquidsoap_queue_approx = [] - - if len(response) > 0: - items_in_queue = response.split(" ") - - self.logger.debug("items_in_queue: %s", items_in_queue) - - for item in items_in_queue: - if item in self.pushed_objects: - liquidsoap_queue_approx.append(self.pushed_objects[item]) - else: - """ - We should only reach here if Pypo crashed and restarted (because self.pushed_objects was reset). In this case - let's clear the entire Liquidsoap queue. - """ - self.logger.error("ID exists in liquidsoap queue that does not exist in our pushed_objects queue: " + item) - self.clear_liquidsoap_queue() - liquidsoap_queue_approx = [] - break - - return 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 liquidsoap_stream_id == "-1") - else: - if is_file(media_item): - if len(liquidsoap_queue_approx) == 0: - correct = False - else: - correct = liquidsoap_queue_approx[0]['start'] == media_item['start'] and \ - liquidsoap_queue_approx[0]['row_id'] == media_item['row_id'] and \ - liquidsoap_queue_approx[0]['end'] == media_item['end'] and \ - liquidsoap_queue_approx[0]['replay_gain'] == media_item['replay_gain'] - elif is_stream(media_item): - correct = liquidsoap_stream_id == str(media_item['row_id']) - - self.logger.debug("Is current item correct?: %s", str(correct)) - return correct - - - #clear all webstreams and files from Liquidsoap - def clear_all_liquidsoap_items(self): - self.remove_from_liquidsoap_queue(0, None) - self.stop_web_stream_all() - - 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 - has changed. If the schedule has changed, this function's job is to - call other functions that will connect to Liquidsoap and alter its - queue. - """ - 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(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, liquidsoap_stream_id): - self.clear_all_liquidsoap_items() - if is_stream(current_item): - if current_item['row_id'] != self.current_prebuffering_stream_id: - #this is called if the stream wasn't scheduled sufficiently ahead of time - #so that the prebuffering stage could take effect. Let's do the prebuffering now. - self.start_web_stream_buffer(current_item) - self.start_web_stream(current_item) - if is_file(current_item): - file_chain = self.modify_first_link_cue_point(file_chain) - self.push_to_liquidsoap(file_chain) - #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, liquidsoap_stream_id): - #Liquidsoap is playing something even though it shouldn't be - self.clear_all_liquidsoap_items() - - - #If the current item scheduled is a file, then files come in chains, and - #therefore we need to make sure the entire chain is correct. - if len(current_event_chain) > 0 and is_file(current_event_chain[0]): - problem_at_iteration = self.find_removed_items(media_schedule, liquidsoap_queue_approx) - - if problem_at_iteration is not None: - #Items that are in Liquidsoap's queue aren't scheduled anymore. We need to connect - #and remove these items. - self.logger.debug("Change in link %s of current chain", problem_at_iteration) - self.remove_from_liquidsoap_queue(problem_at_iteration, liquidsoap_queue_approx[problem_at_iteration:]) - - if problem_at_iteration is None and len(file_chain) > len(liquidsoap_queue_approx): - self.logger.debug("New schedule has longer current chain.") - problem_at_iteration = len(liquidsoap_queue_approx) - - if problem_at_iteration is not None: - self.logger.debug("Change in chain at link %s", problem_at_iteration) - - chain_to_push = file_chain[problem_at_iteration:] - if len(chain_to_push) > 0: - chain_to_push = self.modify_first_link_cue_point(chain_to_push) - self.push_to_liquidsoap(chain_to_push) - - - """ - Compare whats in the liquidsoap_queue to the new schedule we just - received in media_schedule. This function only iterates over liquidsoap_queue_approx - and finds if every item in that list is still scheduled in "media_schedule". It doesn't - take care of the case where media_schedule has more items than liquidsoap_queue_approx - """ - def find_removed_items(self, media_schedule, liquidsoap_queue_approx): - #iterate through the items we got from the liquidsoap queue and - #see if they are the same as the newly received schedule - iteration = 0 - problem_at_iteration = None - for queue_item in liquidsoap_queue_approx: - if queue_item['start'] in media_schedule.keys(): - media_item = media_schedule[queue_item['start']] - if queue_item['row_id'] == media_item['row_id']: - if queue_item['end'] == media_item['end']: - #Everything OK for this iteration. - pass - else: - problem_at_iteration = iteration - break - else: - #A different item has been scheduled at the same time! Need to remove - #all tracks from the Liquidsoap queue starting at this point, and re-add - #them. - problem_at_iteration = iteration - break - else: - #There are no more items scheduled for this time! The user has shortened - #the playlist, so we simply need to remove tracks from the queue. - problem_at_iteration = iteration - break - iteration += 1 - return problem_at_iteration - - - - def get_all_chains(self, media_schedule): - chains = [] - - current_chain = [] - - sorted_keys = sorted(media_schedule.keys()) - - for mkey in sorted_keys: - media_item = media_schedule[mkey] - if media_item['independent_event']: - if len(current_chain) > 0: - chains.append(current_chain) - - chains.append([media_item]) - current_chain = [] - elif len(current_chain) == 0: - current_chain.append(media_item) - elif media_item['start'] == current_chain[-1]['end']: - current_chain.append(media_item) - else: - #current item is not a continuation of the chain. - #Start a new one instead - chains.append(current_chain) - current_chain = [media_item] - - if len(current_chain) > 0: - chains.append(current_chain) - - return chains + #self.logger.debug("Is current item correct?: %s", str(correct)) + #return correct def modify_cue_point(self, link): tnow = datetime.utcnow() @@ -479,75 +231,6 @@ class PypoPush(Thread): original_cue_in_td = timedelta(seconds=float(link['cue_in'])) link['cue_in'] = self.date_interval_to_seconds(original_cue_in_td) + diff_sec - def modify_first_link_cue_point(self, chain): - if not len(chain): - return [] - - first_link = chain[0] - - self.modify_cue_point(first_link) - if float(first_link['cue_in']) >= float(first_link['cue_out']): - chain = chain [1:] - - return chain - - """ - Returns two chains, original chain and current_chain. current_chain is a subset of - original_chain but can also be equal to original chain. - - We return original chain because the user of this function may want to clean - up the input 'chains' list - - chain, original = get_current_chain(chains) - - and - chains.remove(chain) can throw a ValueError exception - - but - chains.remove(original) won't - """ - def get_current_chain(self, chains, tnow): - current_chain = [] - original_chain = None - - for chain in chains: - iteration = 0 - for link in chain: - link_start = link['start'] - link_end = link['end'] - - self.logger.debug("tnow %s, chain_start %s", tnow, link_start) - if link_start <= tnow and tnow < link_end: - current_chain = chain[iteration:] - original_chain = chain - break - iteration += 1 - - return current_chain, original_chain - - """ - The purpose of this function is to take a look at the last received schedule from - pypo-fetch and return the next chain of media_items. A chain is defined as a sequence - of media_items where the end time of media_item 'n' is the start time of media_item - 'n+1' - """ - def get_next_schedule_chain(self, chains, tnow): - #all media_items are now divided into chains. Let's find the one that - #starts closest in the future. - closest_start = None - closest_chain = None - for chain in chains: - chain_start = chain[0]['start'] - chain_end = chain[-1]['end'] - self.logger.debug("tnow %s, chain_start %s", tnow, chain_start) - if (closest_start == None or chain_start < closest_start) and \ - (chain_start > tnow or \ - (chain_start < tnow and chain_end > tnow)): - closest_start = chain_start - closest_chain = chain - - return closest_chain - def date_interval_to_seconds(self, interval): """ @@ -559,94 +242,6 @@ class PypoPush(Thread): return seconds - def push_to_liquidsoap(self, event_chain): - - try: - for media_item in event_chain: - if media_item['type'] == "file": - - """ - Wait maximum 5 seconds (50 iterations) for file to become ready, otherwise - give up on it. - """ - iter_num = 0 - while not media_item['file_ready'] and iter_num < 50: - time.sleep(0.1) - iter_num += 1 - - if media_item['file_ready']: - self.telnet_to_liquidsoap(media_item) - else: - self.logger.warn("File %s did not become ready in less than 5 seconds. Skipping...", media_item['dst']) - elif media_item['type'] == "event": - if media_item['event_type'] == "kick_out": - PypoFetch.disconnect_source(self.logger, self.telnet_lock, "live_dj") - elif media_item['event_type'] == "switch_off": - PypoFetch.switch_source(self.logger, self.telnet_lock, "live_dj", "off") - elif media_item['type'] == 'stream_buffer_start': - self.start_web_stream_buffer(media_item) - elif media_item['type'] == "stream_output_start": - if media_item['row_id'] != self.current_prebuffering_stream_id: - #this is called if the stream wasn't scheduled sufficiently ahead of time - #so that the prebuffering stage could take effect. Let's do the prebuffering now. - self.start_web_stream_buffer(media_item) - self.start_web_stream(media_item) - elif media_item['type'] == "stream_buffer_end": - self.stop_web_stream_buffer(media_item) - elif media_item['type'] == "stream_output_end": - self.stop_web_stream_output(media_item) - except Exception, e: - self.logger.error('Pypo Push Exception: %s', e) - finally: - self.queue_id = (self.queue_id + 1) % 8 - - - def start_web_stream_buffer(self, media_item): - try: - self.telnet_lock.acquire() - tn = telnetlib.Telnet(LS_HOST, LS_PORT) - - msg = 'dynamic_source.id %s\n' % media_item['row_id'] - self.logger.debug(msg) - tn.write(msg) - - #msg = 'dynamic_source.read_start %s\n' % media_item['uri'].encode('latin-1') - msg = 'http.restart %s\n' % media_item['uri'].encode('latin-1') - self.logger.debug(msg) - tn.write(msg) - - tn.write("exit\n") - self.logger.debug(tn.read_all()) - - self.current_prebuffering_stream_id = media_item['row_id'] - except Exception, e: - self.logger.error(str(e)) - finally: - self.telnet_lock.release() - - - def start_web_stream(self, media_item): - try: - self.telnet_lock.acquire() - tn = telnetlib.Telnet(LS_HOST, LS_PORT) - - #TODO: DO we need this? - msg = 'streams.scheduled_play_start\n' - tn.write(msg) - - msg = 'dynamic_source.output_start\n' - self.logger.debug(msg) - tn.write(msg) - - tn.write("exit\n") - self.logger.debug(tn.read_all()) - - self.current_prebuffering_stream_id = None - except Exception, e: - self.logger.error(str(e)) - finally: - self.telnet_lock.release() - def stop_web_stream_all(self): try: self.telnet_lock.acquire() @@ -673,167 +268,6 @@ class PypoPush(Thread): finally: self.telnet_lock.release() - def stop_web_stream_buffer(self, media_item): - try: - self.telnet_lock.acquire() - 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['row_id'] - msg = 'http.stop\n' - 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()) - - except Exception, e: - self.logger.error(str(e)) - finally: - self.telnet_lock.release() - - def stop_web_stream_output(self, media_item): - try: - self.telnet_lock.acquire() - tn = telnetlib.Telnet(LS_HOST, LS_PORT) - #dynamic_source.stop http://87.230.101.24:80/top100station.mp3 - - msg = 'dynamic_source.output_stop\n' - self.logger.debug(msg) - tn.write(msg) - - tn.write("exit\n") - self.logger.debug(tn.read_all()) - - except Exception, e: - self.logger.error(str(e)) - finally: - self.telnet_lock.release() - - def clear_liquidsoap_queue(self): - self.logger.debug("Clearing Liquidsoap queue") - try: - self.telnet_lock.acquire() - tn = telnetlib.Telnet(LS_HOST, LS_PORT) - msg = "source.skip\n" - tn.write(msg) - tn.write("exit\n") - tn.read_all() - except Exception, e: - self.logger.error(str(e)) - finally: - self.telnet_lock.release() - - def remove_from_liquidsoap_queue(self, problem_at_iteration, liquidsoap_queue_approx): - - try: - self.telnet_lock.acquire() - tn = telnetlib.Telnet(LS_HOST, LS_PORT) - - if problem_at_iteration == 0: - msg = "source.skip\n" - self.logger.debug(msg) - tn.write(msg) - else: - # Remove things in reverse order. - queue_copy = liquidsoap_queue_approx[::-1] - - for queue_item in queue_copy: - msg = "queue.remove %s\n" % queue_item['queue_id'] - self.logger.debug(msg) - tn.write(msg) - response = tn.read_until("\r\n").strip("\r\n") - - if "No such request in my queue" in response: - """ - Cannot remove because Liquidsoap started playing the item. Need - to use source.skip instead - """ - msg = "source.skip\n" - self.logger.debug(msg) - tn.write(msg) - - msg = "s0.queue\n" - self.logger.debug(msg) - tn.write(msg) - - tn.write("exit\n") - self.logger.debug(tn.read_all()) - except Exception, e: - self.logger.error(str(e)) - finally: - self.telnet_lock.release() - - def sleep_until_start(self, media_item): - """ - The purpose of this function is to look at the difference between - "now" and when the media_item starts, and sleep for that period of time. - After waking from sleep, this function returns. - """ - - mi_start = media_item['start'][0:19] - - #strptime returns struct_time in local time - epoch_start = calendar.timegm(time.strptime(mi_start, '%Y-%m-%d-%H-%M-%S')) - - #Return the time as a floating point number expressed in seconds since the epoch, in UTC. - epoch_now = time.time() - - self.logger.debug("Epoch start: %s" % epoch_start) - self.logger.debug("Epoch now: %s" % epoch_now) - - sleep_time = epoch_start - epoch_now - - if sleep_time < 0: - sleep_time = 0 - - self.logger.debug('sleeping for %s s' % (sleep_time)) - time.sleep(sleep_time) - - def telnet_to_liquidsoap(self, media_item): - """ - telnets to liquidsoap and pushes the media_item to its queue. Push the - show name of every media_item as well, just to keep Liquidsoap up-to-date - about which show is playing. - """ - try: - self.telnet_lock.acquire() - tn = telnetlib.Telnet(LS_HOST, LS_PORT) - - annotation = self.create_liquidsoap_annotation(media_item) - msg = 's%s.push %s\n' % (self.queue_id, annotation.encode('utf-8')) - self.logger.debug(msg) - tn.write(msg) - queue_id = tn.read_until("\r\n").strip("\r\n") - - #remember the media_item's queue id which we may use - #later if we need to remove it from the queue. - media_item['queue_id'] = queue_id - - #add media_item to the end of our queue - self.pushed_objects[queue_id] = media_item - - show_name = media_item['show_name'] - msg = 'vars.show_name %s\n' % show_name.encode('utf-8') - tn.write(msg) - self.logger.debug(msg) - - tn.write("exit\n") - self.logger.debug(tn.read_all()) - except Exception, e: - self.logger.error(str(e)) - finally: - self.telnet_lock.release() - - def create_liquidsoap_annotation(self, media): - # We need liq_start_next value in the annotate. That is the value that controls overlap duration of crossfade. - return 'annotate:media_id="%s",liq_start_next="0",liq_fade_in="%s",liq_fade_out="%s",liq_cue_in="%s",liq_cue_out="%s",schedule_table_id="%s",replay_gain="%s dB":%s' \ - % (media['id'], float(media['fade_in']) / 1000, float(media['fade_out']) / 1000, float(media['cue_in']), float(media['cue_out']), media['row_id'], media['replay_gain'], media['dst']) - def run(self): try: self.main() except Exception, e: diff --git a/python_apps/pypo/telnetliquidsoap.py b/python_apps/pypo/telnetliquidsoap.py index 7bc7e362d..019a62b29 100644 --- a/python_apps/pypo/telnetliquidsoap.py +++ b/python_apps/pypo/telnetliquidsoap.py @@ -12,6 +12,7 @@ class TelnetLiquidsoap: self.ls_host = ls_host self.ls_port = ls_port self.logger = logger + self.current_prebuffering_stream_id = None def __connect(self): return telnetlib.Telnet(self.ls_host, self.ls_port) @@ -61,6 +62,95 @@ class TelnetLiquidsoap: finally: self.telnet_lock.release() + + def stop_web_stream_buffer(self, media_item): + try: + self.telnet_lock.acquire() + tn = telnetlib.Telnet(self.ls_host, self.ls_port) + #dynamic_source.stop http://87.230.101.24:80/top100station.mp3 + + #msg = 'dynamic_source.read_stop %s\n' % media_item['row_id'] + msg = 'http.stop\n' + 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()) + + except Exception, e: + self.logger.error(str(e)) + finally: + self.telnet_lock.release() + + def stop_web_stream_output(self, media_item): + try: + self.telnet_lock.acquire() + tn = telnetlib.Telnet(self.ls_host, self.ls_port) + #dynamic_source.stop http://87.230.101.24:80/top100station.mp3 + + msg = 'dynamic_source.output_stop\n' + self.logger.debug(msg) + tn.write(msg) + + tn.write("exit\n") + self.logger.debug(tn.read_all()) + + except Exception, e: + self.logger.error(str(e)) + finally: + self.telnet_lock.release() + + def start_web_stream(self, media_item): + try: + self.telnet_lock.acquire() + tn = telnetlib.Telnet(self.ls_host, self.ls_port) + + #TODO: DO we need this? + msg = 'streams.scheduled_play_start\n' + tn.write(msg) + + msg = 'dynamic_source.output_start\n' + self.logger.debug(msg) + tn.write(msg) + + tn.write("exit\n") + self.logger.debug(tn.read_all()) + + self.current_prebuffering_stream_id = None + except Exception, e: + self.logger.error(str(e)) + finally: + self.telnet_lock.release() + + def start_web_stream_buffer(self, media_item): + try: + self.telnet_lock.acquire() + tn = telnetlib.Telnet(self.ls_host, self.ls_port) + + msg = 'dynamic_source.id %s\n' % media_item['row_id'] + self.logger.debug(msg) + tn.write(msg) + + #msg = 'dynamic_source.read_start %s\n' % media_item['uri'].encode('latin-1') + msg = 'http.restart %s\n' % media_item['uri'].encode('latin-1') + self.logger.debug(msg) + tn.write(msg) + + tn.write("exit\n") + self.logger.debug(tn.read_all()) + + #TODO.. + self.current_prebuffering_stream_id = media_item['row_id'] + except Exception, e: + self.logger.error(str(e)) + finally: + self.telnet_lock.release() + + class DummyTelnetLiquidsoap: def __init__(self, telnet_lock, logger): From 6dae7d3973b76e89d2ad5e22b4d001ee8189fb4c Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Fri, 15 Mar 2013 17:00:36 -0400 Subject: [PATCH 43/51] CC-1469: Crossfading support (non-equal power) -cleanup --- python_apps/pypo/pypopush.py | 4 ++++ python_apps/pypo/telnetliquidsoap.py | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/python_apps/pypo/pypopush.py b/python_apps/pypo/pypopush.py index 6ae3e27d1..8e85a8e01 100644 --- a/python_apps/pypo/pypopush.py +++ b/python_apps/pypo/pypopush.py @@ -168,6 +168,8 @@ class PypoPush(Thread): self.telnet_liquidsoap.queue_remove(i) self.liq_queue_tracker[i] = None + #liquidsoap.stop_play(mi) + if len(to_be_added): self.logger.info("Need to add items to Liquidsoap *now*: %s" % \ @@ -180,6 +182,8 @@ class PypoPush(Thread): self.telnet_liquidsoap.queue_push(queue_id, i) self.liq_queue_tracker[queue_id] = i + #liquidsoap.start_play(i) + def get_current_stream_id_from_liquidsoap(self): response = "-1" try: diff --git a/python_apps/pypo/telnetliquidsoap.py b/python_apps/pypo/telnetliquidsoap.py index 019a62b29..bfd49ec99 100644 --- a/python_apps/pypo/telnetliquidsoap.py +++ b/python_apps/pypo/telnetliquidsoap.py @@ -135,7 +135,6 @@ class TelnetLiquidsoap: self.logger.debug(msg) tn.write(msg) - #msg = 'dynamic_source.read_start %s\n' % media_item['uri'].encode('latin-1') msg = 'http.restart %s\n' % media_item['uri'].encode('latin-1') self.logger.debug(msg) tn.write(msg) From c33b0cb980aba7d437ce05e097d9fc67940a9d14 Mon Sep 17 00:00:00 2001 From: Daniel James Date: Tue, 19 Mar 2013 17:45:09 +0000 Subject: [PATCH 44/51] Don't run the airtime-install script if Apache has not been configured [#CC-5020] --- debian/changelog | 17 ++++++- debian/postinst | 113 ++++++++++++++++++++++++++--------------------- 2 files changed, 77 insertions(+), 53 deletions(-) diff --git a/debian/changelog b/debian/changelog index 137c4a1b0..3eaa9a6b2 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,21 @@ +airtime (2.4.0-1) unstable; urgency=low + + * Nightly development snapshot of Airtime 2.4.x series + + -- Daniel James Tue, 19 Mar 2013 16:39:23 +0000 + +airtime (2.3.0-2) unstable; urgency=low + + * Don't run the airtime-install script if the user has chosen not to + set up Apache + + -- Daniel James Tue, 19 Mar 2013 16:39:23 +0000 + airtime (2.3.0-1) unstable; urgency=low - * Nightly development snapshot of Airtime 2.3.x + * Upstream 2.3.0 release - -- Daniel James Tue, 22 Jan 2013 11:44:57 +0000 + -- Daniel James Tue, 12 Feb 2013 11:44:57 +0000 airtime (2.2.1-1) unstable; urgency=low diff --git a/debian/postinst b/debian/postinst index e6d5169f1..c2549b495 100755 --- a/debian/postinst +++ b/debian/postinst @@ -12,7 +12,7 @@ includefile="${configdir}/apache.conf" a2tplfile="${configdir}/apache.vhost.tpl" phpinifile="${configdir}/airtime.ini" OLDVERSION="$2" -NEWVERSION="2.3.0" +NEWVERSION="2.4.0" case "$1" in configure|reconfigure) @@ -20,39 +20,30 @@ case "$1" in webserver="apache2" php="php5" - # clean up previous configurations - if [ -L /etc/$webserver/conf.d/airtime.conf ]; then - rm -f /etc/$webserver/conf.d/airtime.conf - fi - - if [ -f /etc/$webserver/sites-available/airtime-vhost ]; then - a2dissite airtime-vhost - fi - # this file in 1.8.2 is a directory path in 1.9.3 if [ -f /var/www/airtime/utils/airtime-import ]; then rm -f /var/www/airtime/utils/airtime-import fi - # APACHE config - echo "Setting up apache2..." - - # create the document root if it doesn't exist - if [ ! -d $wwwdir/public/ ]; then - install -d -m755 $wwwdir/public/ - fi - - # set up the virtual host + # do we set up a virtual host? db_get airtime/apache-setup - if [ "$RET" == "system-wide (all vhosts)" ]; then - if [ ! -d /etc/$webserver/conf.d/ ]; then - install -d -m755 /etc/$webserver/conf.d/ - fi - if [ ! -e /etc/$webserver/conf.d/airtime.conf ]; then - ln -s ${includefile} /etc/$webserver/conf.d/airtime.conf + APACHESETUP=$RET + if [ "${APACHESETUP}" == "no thanks" ]; then + echo "Not setting up ${webserver} and ${php}..." + + elif [ "${APACHESETUP}" == "dedicated v-host" ]; then + echo "Setting up ${webserver}..." + + # create the document root if it doesn't exist + if [ ! -d $wwwdir/public/ ]; then + install -d -m755 $wwwdir/public/ + fi + + # temporarily disable an existing virtual host + if [ -f /etc/$webserver/sites-available/airtime-vhost ]; then + a2dissite airtime-vhost fi - elif [ "$RET" == "dedicated v-host" ]; then db_get airtime/apache-servername SN=$RET db_get airtime/apache-serveradmin @@ -69,30 +60,49 @@ case "$1" in if [ $RETVAL -eq 0 ]; then a2ensite airtime-vhost fi - fi - # enable the rewrite module - command -v a2enmod > /dev/null - RETVAL=$? - if [ $RETVAL -eq 0 ]; then - a2enmod rewrite - fi + # insert a specific hostname, if provided, into API configuration + if [ "${SN}" != "localhost" ]; then - # remove the default site, if requested to - db_get airtime/apache-deldefault - if [ "$RET" == "remove default" ]; then - if [ -f /etc/apache2/sites-available/default ]; then - a2dissite default + # new installs + if [ -f /var/lib/airtime/tmp/airtime_mvc/build/airtime.conf -a -f /var/lib/airtime/tmp/python_apps/api_clients/api_client.cfg ]; then + sed -i "s/base_url = localhost/base_url = ${SN}/" /var/lib/airtime/tmp/airtime_mvc/build/airtime.conf + sed -i "s/host = 'localhost'/host = '${SN}'/" /var/lib/airtime/tmp/python_apps/api_clients/api_client.cfg + fi + + # upgrades + if [ -f /etc/airtime/airtime.conf -a -f /etc/airtime/api_client.cfg ]; then + sed -i "s/base_url = localhost/base_url = ${SN}/" /etc/airtime/airtime.conf + sed -i "s/host = 'localhost'/host = '${SN}'/" /etc/airtime/api_client.cfg + fi fi - fi - # PHP config - echo "Configuring php5..." - if [ ! -d /etc/$php/conf.d/ ]; then - install -d -m755 /etc/$php/conf.d/ - fi - if [ ! -e /etc/$php/conf.d/airtime.ini ]; then - ln -s ${phpinifile} /etc/$php/conf.d/airtime.ini + # enable the rewrite module + command -v a2enmod > /dev/null + RETVAL=$? + if [ $RETVAL -eq 0 ]; then + a2enmod rewrite + fi + + # remove the default site, if requested to + db_get airtime/apache-deldefault + if [ "$RET" == "remove default" ]; then + if [ -f /etc/apache2/sites-available/default ]; then + a2dissite default + fi + fi + + # PHP config + echo "Configuring php5..." + if [ ! -d /etc/$php/conf.d/ ]; then + install -d -m755 /etc/$php/conf.d/ + fi + if [ ! -e /etc/$php/conf.d/airtime.ini ]; then + ln -s ${phpinifile} /etc/$php/conf.d/airtime.ini + fi + + # restart apache + invoke-rc.d apache2 restart fi # XXX ICECAST XXX @@ -186,9 +196,6 @@ case "$1" in echo "The rabbitmq-server package does not appear to be installed on this server." fi - # restart apache - invoke-rc.d apache2 restart - # fix the Liquidsoap symlink if it doesn't point to standard location if [ -h /usr/bin/airtime-liquidsoap ]; then SYMLINK_TARGET=`readlink /usr/bin/airtime-liquidsoap` @@ -214,8 +221,12 @@ case "$1" in echo "Reconfiguration complete." else - if [ -n "$OLDVERSION" ] && [[ "${OLDVERSION:0:3}" < "1.9" ]]; then - echo "Upgrades from Airtime versions before 1.9.0 are not supported. Please back up your files and perform a clean install." + if [ -n "$OLDVERSION" ] && [[ "${OLDVERSION:0:3}" < "2.1" ]]; then + echo "Upgrades from Airtime versions before 2.1.0 are not supported. Please back up your files and perform a clean install." + + elif [ "${APACHESETUP}" == "no thanks" ]; then + echo "Please run the ${tmpdir}/install_minimal/airtime-install script after you have set up the web server." + else mkdir -p /var/log/airtime From 33ca2e7c9c1fba64abe2bb07b0e0dc57eaca3de8 Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Fri, 22 Mar 2013 12:16:17 -0400 Subject: [PATCH 45/51] CC-1469: Crossfading support (non-equal power) -refactor --- python_apps/pypo/airtime-playout | 10 +- python_apps/pypo/pypocli.py | 9 -- python_apps/pypo/pypofetch.py | 9 +- python_apps/pypo/pypoliqqueue.py | 91 +----------- python_apps/pypo/pypoliquidsoap.py | 214 +++++++++++++++++++++++++++ python_apps/pypo/pypopush.py | 94 ++---------- python_apps/pypo/recorder.py | 2 - python_apps/pypo/telnetliquidsoap.py | 26 +++- 8 files changed, 261 insertions(+), 194 deletions(-) create mode 100644 python_apps/pypo/pypoliquidsoap.py diff --git a/python_apps/pypo/airtime-playout b/python_apps/pypo/airtime-playout index 56aa587cd..5521c91ed 100755 --- a/python_apps/pypo/airtime-playout +++ b/python_apps/pypo/airtime-playout @@ -3,14 +3,14 @@ virtualenv_bin="/usr/lib/airtime/airtime_virtualenv/bin/" . ${virtualenv_bin}activate -pypo_user="pypo" +# Absolute path to this script +SCRIPT=`readlink -f $0` +# Absolute directory this script is in +pypo_path=`dirname $SCRIPT` -# Location of pypo_cli.py Python script -pypo_path="/usr/lib/airtime/pypo/bin/" api_client_path="/usr/lib/airtime/" pypo_script="pypocli.py" cd ${pypo_path} -exec 2>&1 set +e cat /etc/default/locale | grep -i "LANG=.*UTF-\?8" @@ -26,6 +26,6 @@ export LC_ALL=`cat /etc/default/locale | grep "LANG=" | cut -d= -f2 | tr -d "\n\ export TERM=xterm -exec python ${pypo_path}${pypo_script} > /var/log/airtime/pypo/py-interpreter.log 2>&1 +exec python ${pypo_path}/${pypo_script} > /var/log/airtime/pypo/py-interpreter.log 2>&1 # EOF diff --git a/python_apps/pypo/pypocli.py b/python_apps/pypo/pypocli.py index 02dfe6c46..de30d65dd 100644 --- a/python_apps/pypo/pypocli.py +++ b/python_apps/pypo/pypocli.py @@ -18,7 +18,6 @@ from Queue import Queue from threading import Lock from pypopush import PypoPush -from pypoliqqueue import PypoLiqQueue from pypofetch import PypoFetch from pypofile import PypoFile from recorder import Recorder @@ -229,14 +228,6 @@ if __name__ == '__main__': stat.daemon = True stat.start() - #pypoLiq_q = Queue() - #liq_queue_tracker = dict() - #telnet_liquidsoap = TelnetLiquidsoap() - #plq = PypoLiqQueue(pypoLiq_q, telnet_lock, logger, liq_queue_tracker, \ - #telnet_liquidsoap) - #plq.daemon = True - #plq.start() - # all join() are commented out because we want to exit entire pypo # if pypofetch terminates #pmh.join() diff --git a/python_apps/pypo/pypofetch.py b/python_apps/pypo/pypofetch.py index 66243a15a..17d5e5898 100644 --- a/python_apps/pypo/pypofetch.py +++ b/python_apps/pypo/pypofetch.py @@ -8,6 +8,7 @@ import json import telnetlib import copy import subprocess +import signal from datetime import datetime from Queue import Empty @@ -24,6 +25,12 @@ logging.config.fileConfig(logging_cfg) logger = logging.getLogger() LogWriter.override_std_err(logger) +def keyboardInterruptHandler(signum, frame): + logger = logging.getLogger() + logger.info('\nKeyboard Interrupt\n') + sys.exit(0) +signal.signal(signal.SIGINT, keyboardInterruptHandler) + #need to wait for Python 2.7 for this.. #logging.captureWarnings(True) @@ -34,8 +41,6 @@ try: LS_PORT = config['ls_port'] #POLL_INTERVAL = int(config['poll_interval']) POLL_INTERVAL = 1800 - - except Exception, e: logger.error('Error loading config file: %s', e) sys.exit() diff --git a/python_apps/pypo/pypoliqqueue.py b/python_apps/pypo/pypoliqqueue.py index 54d7c5d93..439255704 100644 --- a/python_apps/pypo/pypoliqqueue.py +++ b/python_apps/pypo/pypoliqqueue.py @@ -1,14 +1,12 @@ from threading import Thread from collections import deque from datetime import datetime -from pypofetch import PypoFetch - -import eventtypes import traceback import sys import time + from Queue import Empty import signal @@ -19,14 +17,11 @@ def keyboardInterruptHandler(signum, frame): signal.signal(signal.SIGINT, keyboardInterruptHandler) class PypoLiqQueue(Thread): - def __init__(self, q, telnet_lock, logger, liq_queue_tracker, \ - telnet_liquidsoap): + def __init__(self, q, pypo_liquidsoap, logger): Thread.__init__(self) self.queue = q - self.telnet_lock = telnet_lock self.logger = logger - self.liq_queue_tracker = liq_queue_tracker - self.telnet_liquidsoap = telnet_liquidsoap + self.pypo_liquidsoap = pypo_liquidsoap def main(self): time_until_next_play = None @@ -46,7 +41,7 @@ class PypoLiqQueue(Thread): except Empty, e: #Time to push a scheduled item. media_item = schedule_deque.popleft() - self.telnet_to_liquidsoap(media_item) + self.pypo_liquidsoap.push_item(media_item) if len(schedule_deque): time_until_next_play = \ self.date_interval_to_seconds( @@ -71,78 +66,6 @@ class PypoLiqQueue(Thread): else: time_until_next_play = None - def is_media_item_finished(self, media_item): - if media_item is None: - return True - else: - return datetime.utcnow() > media_item['end'] - - def find_available_queue(self): - available_queue = None - for i in self.liq_queue_tracker: - mi = self.liq_queue_tracker[i] - if mi == None or self.is_media_item_finished(mi): - #queue "i" is available. Push to this queue - available_queue = i - - if available_queue == None: - raise NoQueueAvailableException() - - return available_queue - - def telnet_to_liquidsoap(self, media_item): - """ - telnets to liquidsoap and pushes the media_item to its queue. Push the - show name of every media_item as well, just to keep Liquidsoap up-to-date - about which show is playing. - """ - - if media_item["type"] == eventtypes.FILE: - self.handle_file_type(media_item) - elif media_item["type"] == eventtypes.EVENT: - self.handle_event_type(media_item) - elif media_item["type"] == eventtypes.STREAM_BUFFER_START: - self.telnet_liquidsoap.start_web_stream_buffer(media_item) - elif media_item["type"] == eventtypes.STREAM_OUTPUT_START: - if media_item['row_id'] != self.telnet_liquidsoap.current_prebuffering_stream_id: - #this is called if the stream wasn't scheduled sufficiently ahead of time - #so that the prebuffering stage could take effect. Let's do the prebuffering now. - self.telnet_liquidsoap.start_web_stream_buffer(media_item) - self.telnet_liquidsoap.start_web_stream(media_item) - elif media_item['type'] == eventtypes.STREAM_BUFFER_END: - self.telnet_liquidsoap.stop_web_stream_buffer(media_item) - elif media_item['type'] == eventtypes.STREAM_OUTPUT_END: - self.telnet_liquidsoap.stop_web_stream_output(media_item) - else: raise UnknownMediaItemType(str(media_item)) - - def handle_event_type(self, media_item): - if media_item['event_type'] == "kick_out": - PypoFetch.disconnect_source(self.logger, self.telnet_lock, "live_dj") - elif media_item['event_type'] == "switch_off": - PypoFetch.switch_source(self.logger, self.telnet_lock, "live_dj", "off") - - - def handle_file_type(self, media_item): - """ - Wait maximum 5 seconds (50 iterations) for file to become ready, - otherwise give up on it. - """ - iter_num = 0 - while not media_item['file_ready'] and iter_num < 50: - time.sleep(0.1) - iter_num += 1 - - if media_item['file_ready']: - available_queue = self.find_available_queue() - - try: - self.telnet_liquidsoap.queue_push(available_queue, media_item) - self.liq_queue_tracker[available_queue] = media_item - except Exception as e: - self.logger.error(e) - raise - else: - self.logger.warn("File %s did not become ready in less than 5 seconds. Skipping...", media_item['dst']) def date_interval_to_seconds(self, interval): """ @@ -155,14 +78,10 @@ class PypoLiqQueue(Thread): return seconds - def run(self): try: self.main() except Exception, e: self.logger.error('PypoLiqQueue Exception: %s', traceback.format_exc()) -class NoQueueAvailableException(Exception): - pass -class UnknownMediaItemType(Exception): - pass + diff --git a/python_apps/pypo/pypoliquidsoap.py b/python_apps/pypo/pypoliquidsoap.py new file mode 100644 index 000000000..97390b93f --- /dev/null +++ b/python_apps/pypo/pypoliquidsoap.py @@ -0,0 +1,214 @@ +from pypofetch import PypoFetch +from telnetliquidsoap import TelnetLiquidsoap + +from datetime import datetime +from datetime import timedelta + +import eventtypes +import time + +class PypoLiquidsoap(): + def __init__(self, logger, telnet_lock, host, port): + self.logger = logger + self.liq_queue_tracker = { + "s0": None, + "s1": None, + "s2": None, + "s3": None, + } + + self.telnet_liquidsoap = TelnetLiquidsoap(telnet_lock, \ + logger,\ + host,\ + port) + + + def push_item(self, media_item): + if media_item["type"] == eventtypes.FILE: + self.handle_file_type(media_item) + elif media_item["type"] == eventtypes.EVENT: + self.handle_event_type(media_item) + elif media_item["type"] == eventtypes.STREAM_BUFFER_START: + self.telnet_liquidsoap.start_web_stream_buffer(media_item) + elif media_item["type"] == eventtypes.STREAM_OUTPUT_START: + if media_item['row_id'] != self.telnet_liquidsoap.current_prebuffering_stream_id: + #this is called if the stream wasn't scheduled sufficiently ahead of time + #so that the prebuffering stage could take effect. Let's do the prebuffering now. + self.telnet_liquidsoap.start_web_stream_buffer(media_item) + self.telnet_liquidsoap.start_web_stream(media_item) + elif media_item['type'] == eventtypes.STREAM_BUFFER_END: + self.telnet_liquidsoap.stop_web_stream_buffer() + elif media_item['type'] == eventtypes.STREAM_OUTPUT_END: + self.telnet_liquidsoap.stop_web_stream_output() + else: raise UnknownMediaItemType(str(media_item)) + + def handle_file_type(self, media_item): + """ + Wait maximum 5 seconds (50 iterations) for file to become ready, + otherwise give up on it. + """ + iter_num = 0 + while not media_item['file_ready'] and iter_num < 50: + time.sleep(0.1) + iter_num += 1 + + if media_item['file_ready']: + available_queue = self.find_available_queue() + + try: + self.telnet_liquidsoap.queue_push(available_queue, media_item) + self.liq_queue_tracker[available_queue] = media_item + except Exception as e: + self.logger.error(e) + raise + else: + self.logger.warn("File %s did not become ready in less than 5 seconds. Skipping...", media_item['dst']) + + def handle_event_type(self, media_item): + if media_item['event_type'] == "kick_out": + PypoFetch.disconnect_source(self.logger, self.telnet_lock, "live_dj") + elif media_item['event_type'] == "switch_off": + PypoFetch.switch_source(self.logger, self.telnet_lock, "live_dj", "off") + + + def is_media_item_finished(self, media_item): + if media_item is None: + return True + else: + return datetime.utcnow() > media_item['end'] + + def find_available_queue(self): + available_queue = None + for i in self.liq_queue_tracker: + mi = self.liq_queue_tracker[i] + if mi == None or self.is_media_item_finished(mi): + #queue "i" is available. Push to this queue + available_queue = i + + if available_queue == None: + raise NoQueueAvailableException() + + return available_queue + + def get_queues(): + return self.liq_queue_tracker + + + def verify_correct_present_media(self, scheduled_now): + #verify whether Liquidsoap is currently playing the correct files. + #if we find an item that Liquidsoap is not playing, then push it + #into one of Liquidsoap's queues. If Liquidsoap is already playing + #it do nothing. If Liquidsoap is playing a track that isn't in + #currently_playing then stop it. + + #Check for Liquidsoap media we should source.skip + #get liquidsoap items for each queue. Since each queue can only have one + #item, we should have a max of 8 items. + + #TODO: Verify start, end, replay_gain is the same + #TODO: Verify this is a file or webstream and also handle webstreams + #TODO: Do webstreams span a time or do they have an instantaneous point? + + #2013-03-21-22-56-00_0: { + #id: 1, + #type: "stream_output_start", + #row_id: 41, + #uri: "http://stream2.radioblackout.org:80/blackout.ogg", + #start: "2013-03-21-22-56-00", + #end: "2013-03-21-23-26-00", + #show_name: "Untitled Show", + #independent_event: true + #}, + + + scheduled_now_files = \ + filter(lambda x: x["type"] == eventtypes.FILE, scheduled_now) + + scheduled_now_webstream = \ + filter(lambda x: x["type"] == eventtypes.STREAM_OUTPUT_START, \ + scheduled_now) + + schedule_ids = set(map(lambda x: i["row_id"], scheduled_now_files)) + + + liq_queue_ids = set() + for i in self.liq_queue_tracker: + mi = self.liq_queue_tracker[i] + if not self.is_media_item_finished(mi): + liq_queue_ids.add(mi["row_id"]) + + to_be_removed = liq_queue_ids - schedule_ids + to_be_added = schedule_ids - liq_queue_ids + + if len(to_be_removed): + self.logger.info("Need to remove items from Liquidsoap: %s" % \ + to_be_removed) + + #remove files from Liquidsoap's queue + for i in self.liq_queue_tracker: + mi = self.liq_queue_tracker[i] + if mi is not None and mi["row_id"] in to_be_removed: + self.stop(i) + + #stop any webstreams + if self.telnet_liquidsoap.get_current_stream_id() in to_be_removed: + self.telnet_liquidsoap.stop_web_stream_buffer() + self.telnet_liquidsoap.stop_web_stream_output() + + if len(to_be_added): + self.logger.info("Need to add items to Liquidsoap *now*: %s" % \ + to_be_added) + + for i in scheduled_now: + if i["row_id"] in to_be_added: + self.modify_cue_point(i) + self.play(i) + + current_stream_id = self.telnet_liquidsoap.get_current_stream_id() + #if len(scheduled_now_webstream): + #if current_stream_id != scheduled_now_webstream[0]: + #self.play(scheduled_now_webstream[0]) + + def play(self, media_item): + self.telnet_liquidsoap.push_item(media_item) + + def stop(self, queue): + self.telnet_liquidsoap.queue_remove(queue) + self.liq_queue_tracker[queue] = None + + def is_file(self, media_item): + return media_item["type"] == eventtypes.FILE + + def modify_cue_point(self, link): + if not self.is_file(link): + return + + tnow = datetime.utcnow() + + link_start = link['start'] + + diff_td = tnow - link_start + diff_sec = self.date_interval_to_seconds(diff_td) + + if diff_sec > 0: + self.logger.debug("media item was supposed to start %s ago. Preparing to start..", diff_sec) + original_cue_in_td = timedelta(seconds=float(link['cue_in'])) + link['cue_in'] = self.date_interval_to_seconds(original_cue_in_td) + diff_sec + + def date_interval_to_seconds(self, interval): + """ + Convert timedelta object into int representing the number of seconds. If + number of seconds is less than 0, then return 0. + """ + seconds = (interval.microseconds + \ + (interval.seconds + interval.days * 24 * 3600) * 10 ** 6) / float(10 ** 6) + if seconds < 0: seconds = 0 + + return seconds + + +class UnknownMediaItemType(Exception): + pass + +class NoQueueAvailableException(Exception): + pass diff --git a/python_apps/pypo/pypopush.py b/python_apps/pypo/pypopush.py index 8e85a8e01..4d6c21964 100644 --- a/python_apps/pypo/pypopush.py +++ b/python_apps/pypo/pypopush.py @@ -13,8 +13,8 @@ import traceback import os from pypofetch import PypoFetch -from telnetliquidsoap import TelnetLiquidsoap from pypoliqqueue import PypoLiqQueue +from pypoliquidsoap import PypoLiquidsoap from Queue import Empty, Queue @@ -62,28 +62,18 @@ class PypoPush(Thread): self.logger = logging.getLogger('push') self.current_prebuffering_stream_id = None self.queue_id = 0 - self.telnet_liquidsoap = TelnetLiquidsoap(telnet_lock, \ - self.logger,\ - LS_HOST,\ - LS_PORT\ - ) - - self.liq_queue_tracker = { - "s0": None, - "s1": None, - "s2": None, - "s3": None, - } self.future_scheduled_queue = Queue() + self.pypo_liquidsoap = PypoLiquidsoap(self.logger, telnet_lock,\ + LS_HOST, LS_PORT) + self.plq = PypoLiqQueue(self.future_scheduled_queue, \ - telnet_lock, \ - self.logger, \ - self.liq_queue_tracker, \ - self.telnet_liquidsoap) + self.pypo_liquidsoap, \ + self.logger) self.plq.daemon = True self.plq.start() + def main(self): loops = 0 heartbeat_period = math.floor(30 / PUSH_INTERVAL) @@ -97,12 +87,13 @@ class PypoPush(Thread): self.logger.error(str(e)) raise else: + self.logger.debug(media_schedule) #separate media_schedule list into currently_playing and #scheduled_for_future lists currently_playing, scheduled_for_future = \ self.separate_present_future(media_schedule) - self.verify_correct_present_media(currently_playing) + self.pypo_liquidsoap.verify_correct_present_media(currently_playing) self.future_scheduled_queue.put(scheduled_for_future) if loops % heartbeat_period == 0: @@ -131,59 +122,6 @@ class PypoPush(Thread): return present, future - def verify_correct_present_media(self, scheduled_now): - #verify whether Liquidsoap is currently playing the correct files. - #if we find an item that Liquidsoap is not playing, then push it - #into one of Liquidsoap's queues. If Liquidsoap is already playing - #it do nothing. If Liquidsoap is playing a track that isn't in - #currently_playing then stop it. - - #Check for Liquidsoap media we should source.skip - #get liquidsoap items for each queue. Since each queue can only have one - #item, we should have a max of 8 items. - - #TODO: Verify start, end, replay_gain is the same - #TODO: Verify this is a file or webstream and also handle webstreams - - schedule_ids = set() - for i in scheduled_now: - schedule_ids.add(i["row_id"]) - - liq_queue_ids = set() - for i in self.liq_queue_tracker: - mi = self.liq_queue_tracker[i] - if not self.plq.is_media_item_finished(mi): - liq_queue_ids.add(mi["row_id"]) - - to_be_removed = liq_queue_ids - schedule_ids - to_be_added = schedule_ids - liq_queue_ids - - if len(to_be_removed): - self.logger.info("Need to remove items from Liquidsoap: %s" % \ - to_be_removed) - - for i in self.liq_queue_tracker: - mi = self.liq_queue_tracker[i] - if mi is not None and mi["row_id"] in to_be_removed: - self.telnet_liquidsoap.queue_remove(i) - self.liq_queue_tracker[i] = None - - #liquidsoap.stop_play(mi) - - - if len(to_be_added): - self.logger.info("Need to add items to Liquidsoap *now*: %s" % \ - to_be_added) - - for i in scheduled_now: - if i["row_id"] in to_be_added: - self.modify_cue_point(i) - queue_id = self.plq.find_available_queue() - self.telnet_liquidsoap.queue_push(queue_id, i) - self.liq_queue_tracker[queue_id] = i - - #liquidsoap.start_play(i) - def get_current_stream_id_from_liquidsoap(self): response = "-1" try: @@ -222,20 +160,6 @@ class PypoPush(Thread): #self.logger.debug("Is current item correct?: %s", str(correct)) #return correct - def modify_cue_point(self, link): - tnow = datetime.utcnow() - - link_start = link['start'] - - diff_td = tnow - link_start - diff_sec = self.date_interval_to_seconds(diff_td) - - if diff_sec > 0: - self.logger.debug("media item was supposed to start %s ago. Preparing to start..", diff_sec) - original_cue_in_td = timedelta(seconds=float(link['cue_in'])) - link['cue_in'] = self.date_interval_to_seconds(original_cue_in_td) + diff_sec - - def date_interval_to_seconds(self, interval): """ Convert timedelta object into int representing the number of seconds. If diff --git a/python_apps/pypo/recorder.py b/python_apps/pypo/recorder.py index 70d99144d..b3818f32d 100644 --- a/python_apps/pypo/recorder.py +++ b/python_apps/pypo/recorder.py @@ -303,8 +303,6 @@ class Recorder(Thread): heartbeat_period = math.floor(30 / PUSH_INTERVAL) while True: - if self.loops % heartbeat_period == 0: - self.logger.info("heartbeat") if self.loops * PUSH_INTERVAL > 3600: self.loops = 0 """ diff --git a/python_apps/pypo/telnetliquidsoap.py b/python_apps/pypo/telnetliquidsoap.py index bfd49ec99..28e034ef8 100644 --- a/python_apps/pypo/telnetliquidsoap.py +++ b/python_apps/pypo/telnetliquidsoap.py @@ -63,13 +63,12 @@ class TelnetLiquidsoap: self.telnet_lock.release() - def stop_web_stream_buffer(self, media_item): + def stop_web_stream_buffer(self): try: self.telnet_lock.acquire() tn = telnetlib.Telnet(self.ls_host, self.ls_port) #dynamic_source.stop http://87.230.101.24:80/top100station.mp3 - #msg = 'dynamic_source.read_stop %s\n' % media_item['row_id'] msg = 'http.stop\n' self.logger.debug(msg) tn.write(msg) @@ -86,7 +85,7 @@ class TelnetLiquidsoap: finally: self.telnet_lock.release() - def stop_web_stream_output(self, media_item): + def stop_web_stream_output(self): try: self.telnet_lock.acquire() tn = telnetlib.Telnet(self.ls_host, self.ls_port) @@ -142,13 +141,30 @@ class TelnetLiquidsoap: tn.write("exit\n") self.logger.debug(tn.read_all()) - #TODO.. self.current_prebuffering_stream_id = media_item['row_id'] except Exception, e: self.logger.error(str(e)) finally: self.telnet_lock.release() - + + def get_current_stream_id(): + try: + self.telnet_lock.acquire() + tn = telnetlib.Telnet(self.ls_host, self.ls_port) + + msg = 'dynamic_source.get_id\n' + self.logger.debug(msg) + tn.write(msg) + + tn.write("exit\n") + stream_id = tn.read_all().splitlines()[0] + self.logger.debug("stream_id: %s" % stream_id) + + return stream_id + except Exception, e: + self.logger.error(str(e)) + finally: + self.telnet_lock.release() class DummyTelnetLiquidsoap: From 8a5c82d71a78a02679da1e2e4d110722cc3fa17d Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Fri, 22 Mar 2013 15:21:28 -0400 Subject: [PATCH 46/51] CC-1469: Crossfading support (non-equal power) -fix few bugs --- python_apps/pypo/pypoliquidsoap.py | 22 ++++++++++------------ python_apps/pypo/telnetliquidsoap.py | 4 ++-- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/python_apps/pypo/pypoliquidsoap.py b/python_apps/pypo/pypoliquidsoap.py index 97390b93f..762d20091 100644 --- a/python_apps/pypo/pypoliquidsoap.py +++ b/python_apps/pypo/pypoliquidsoap.py @@ -23,7 +23,7 @@ class PypoLiquidsoap(): port) - def push_item(self, media_item): + def play(self, media_item): if media_item["type"] == eventtypes.FILE: self.handle_file_type(media_item) elif media_item["type"] == eventtypes.EVENT: @@ -128,7 +128,7 @@ class PypoLiquidsoap(): filter(lambda x: x["type"] == eventtypes.STREAM_OUTPUT_START, \ scheduled_now) - schedule_ids = set(map(lambda x: i["row_id"], scheduled_now_files)) + schedule_ids = set(map(lambda x: x["row_id"], scheduled_now_files)) liq_queue_ids = set() @@ -150,10 +150,7 @@ class PypoLiquidsoap(): if mi is not None and mi["row_id"] in to_be_removed: self.stop(i) - #stop any webstreams - if self.telnet_liquidsoap.get_current_stream_id() in to_be_removed: - self.telnet_liquidsoap.stop_web_stream_buffer() - self.telnet_liquidsoap.stop_web_stream_output() + if len(to_be_added): self.logger.info("Need to add items to Liquidsoap *now*: %s" % \ @@ -165,12 +162,13 @@ class PypoLiquidsoap(): self.play(i) current_stream_id = self.telnet_liquidsoap.get_current_stream_id() - #if len(scheduled_now_webstream): - #if current_stream_id != scheduled_now_webstream[0]: - #self.play(scheduled_now_webstream[0]) - - def play(self, media_item): - self.telnet_liquidsoap.push_item(media_item) + if len(scheduled_now_webstream): + if current_stream_id != scheduled_now_webstream[0]: + self.play(scheduled_now_webstream[0]) + elif current_stream_id != "-1": + #something is playing and it shouldn't be. + self.telnet_liquidsoap.stop_web_stream_buffer() + self.telnet_liquidsoap.stop_web_stream_output() def stop(self, queue): self.telnet_liquidsoap.queue_remove(queue) diff --git a/python_apps/pypo/telnetliquidsoap.py b/python_apps/pypo/telnetliquidsoap.py index 28e034ef8..223bc475e 100644 --- a/python_apps/pypo/telnetliquidsoap.py +++ b/python_apps/pypo/telnetliquidsoap.py @@ -147,8 +147,8 @@ class TelnetLiquidsoap: finally: self.telnet_lock.release() - def get_current_stream_id(): - try: + def get_current_stream_id(self): + try: self.telnet_lock.acquire() tn = telnetlib.Telnet(self.ls_host, self.ls_port) From a5c8b7624eb63e1f85f00c16090c13cd24af693c Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Fri, 22 Mar 2013 15:56:05 -0400 Subject: [PATCH 47/51] CC-1469: Crossfading support (non-equal power) -handle file attributes changing (end time, replaygain etc.) --- .../pypo/liquidsoap_scripts/ls_script.liq | 2 +- python_apps/pypo/pypoliquidsoap.py | 32 +++++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/python_apps/pypo/liquidsoap_scripts/ls_script.liq b/python_apps/pypo/liquidsoap_scripts/ls_script.liq index beefca20e..1a7981318 100644 --- a/python_apps/pypo/liquidsoap_scripts/ls_script.liq +++ b/python_apps/pypo/liquidsoap_scripts/ls_script.liq @@ -40,6 +40,7 @@ source_id = ref 0 def create_source() l = request.equeue(id="s#{!source_id}", length=0.5) + l = cue_cut(l) sources := list.append([l], !sources) server.register(namespace="queues", "s#{!source_id}_skip", @@ -63,7 +64,6 @@ create_source() queue = add(!sources) queue = audio_to_stereo(id="queue_src", queue) -queue = cue_cut(queue) queue = amplify(1., override="replay_gain", queue) # the crossfade function controls fade in/out diff --git a/python_apps/pypo/pypoliquidsoap.py b/python_apps/pypo/pypoliquidsoap.py index 762d20091..8a9554516 100644 --- a/python_apps/pypo/pypoliquidsoap.py +++ b/python_apps/pypo/pypoliquidsoap.py @@ -106,8 +106,6 @@ class PypoLiquidsoap(): #item, we should have a max of 8 items. #TODO: Verify start, end, replay_gain is the same - #TODO: Verify this is a file or webstream and also handle webstreams - #TODO: Do webstreams span a time or do they have an instantaneous point? #2013-03-21-22-56-00_0: { #id: 1, @@ -130,15 +128,36 @@ class PypoLiquidsoap(): schedule_ids = set(map(lambda x: x["row_id"], scheduled_now_files)) - + row_id_map = {} liq_queue_ids = set() for i in self.liq_queue_tracker: mi = self.liq_queue_tracker[i] if not self.is_media_item_finished(mi): liq_queue_ids.add(mi["row_id"]) + row_id_map[mi["row_id"]] = mi - to_be_removed = liq_queue_ids - schedule_ids - to_be_added = schedule_ids - liq_queue_ids + to_be_removed = set() + to_be_added = set() + + #Iterate over the new files, and compare them to currently scheduled + #tracks. If already in liquidsoap queue still need to make sure they don't + #have different attributes such replay_gain etc. + for i in scheduled_now_files: + if i["row_id"] in row_id_map: + mi = row_id_map[i["row_id"]] + correct = mi['start'] == i['start'] and \ + mi['end'] == i['end'] and \ + mi['replay_gain'] == i['replay_gain'] + + if not correct: + #need to re-add + self.logger.info("Track %s found to have new attr." % i) + to_be_removed.add(i["row_id"]) + to_be_added.add(i["row_id"]) + + + to_be_removed.update(liq_queue_ids - schedule_ids) + to_be_added.update(schedule_ids - liq_queue_ids) if len(to_be_removed): self.logger.info("Need to remove items from Liquidsoap: %s" % \ @@ -150,8 +169,6 @@ class PypoLiquidsoap(): if mi is not None and mi["row_id"] in to_be_removed: self.stop(i) - - if len(to_be_added): self.logger.info("Need to add items to Liquidsoap *now*: %s" % \ to_be_added) @@ -161,6 +178,7 @@ class PypoLiquidsoap(): self.modify_cue_point(i) self.play(i) + #handle webstreams current_stream_id = self.telnet_liquidsoap.get_current_stream_id() if len(scheduled_now_webstream): if current_stream_id != scheduled_now_webstream[0]: From d12f793578755f3d5c9f7200a37ffd0645a0ce4e Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Fri, 22 Mar 2013 18:05:34 -0400 Subject: [PATCH 48/51] remove potential password change exploit in airtime-demo --- airtime_mvc/application/controllers/UserController.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/airtime_mvc/application/controllers/UserController.php b/airtime_mvc/application/controllers/UserController.php index 319c57767..a67249c06 100644 --- a/airtime_mvc/application/controllers/UserController.php +++ b/airtime_mvc/application/controllers/UserController.php @@ -49,8 +49,7 @@ class UserController extends Zend_Controller_Action if ($form->isValid($formData)) { if (isset($CC_CONFIG['demo']) && $CC_CONFIG['demo'] == 1 - && $formData['login'] == 'admin' - && $formData['user_id'] != 0) { + && $formData['login'] == 'admin') { $this->view->form = $form; $this->view->successMessage = "
"._("Specific action is not allowed in demo version!")."
"; $this->_helper->json->sendJson(array("valid"=>"false", "html"=>$this->view->render('user/add-user.phtml'))); From b1160afeab39fd8954e1f8bb7b2f823b8a4f30a1 Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Fri, 22 Mar 2013 18:05:49 -0400 Subject: [PATCH 49/51] cleanup --- .../application/controllers/PreferenceController.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/airtime_mvc/application/controllers/PreferenceController.php b/airtime_mvc/application/controllers/PreferenceController.php index a93635f0f..77f615528 100644 --- a/airtime_mvc/application/controllers/PreferenceController.php +++ b/airtime_mvc/application/controllers/PreferenceController.php @@ -247,12 +247,9 @@ class PreferenceController extends Zend_Controller_Action /* If the admin password values are empty then we should not * set the pseudo password ('xxxxxx') on the front-end */ - $s1_set_admin_pass = true; - $s2_set_admin_pass = true; - $s3_set_admin_pass = true; - if (empty($values["s1_data"]["admin_pass"])) $s1_set_admin_pass = false; - if (empty($values["s2_data"]["admin_pass"])) $s2_set_admin_pass = false; - if (empty($values["s3_data"]["admin_pass"])) $s3_set_admin_pass = false; + $s1_set_admin_pass = !empty($values["s1_data"]["admin_pass"]); + $s2_set_admin_pass = !empty($values["s2_data"]["admin_pass"]); + $s3_set_admin_pass = !empty($values["s3_data"]["admin_pass"]); // this goes into cc_pref table Application_Model_Preference::SetStreamLabelFormat($values['streamFormat']); From 6ae8da98e4b64fb783960807a6445ad1fb99b730 Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Mon, 1 Apr 2013 19:14:55 -0400 Subject: [PATCH 50/51] removing logging --- airtime_mvc/application/models/Playlist.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/airtime_mvc/application/models/Playlist.php b/airtime_mvc/application/models/Playlist.php index 1c446f182..f4ea01e4a 100644 --- a/airtime_mvc/application/models/Playlist.php +++ b/airtime_mvc/application/models/Playlist.php @@ -601,8 +601,6 @@ SQL; public function getFadeInfo($pos) { - Logging::info("Getting fade info for pos {$pos}"); - $row = CcPlaylistcontentsQuery::create() ->joinWith(CcFilesPeer::OM_CLASS) ->filterByDbPlaylistId($this->id) From 811abc4baf5d4d5ccc45ba0df2a4276940cd07f7 Mon Sep 17 00:00:00 2001 From: Martin Konecny Date: Mon, 1 Apr 2013 19:31:13 -0400 Subject: [PATCH 51/51] don't change monit monitor state on service start/stop --- .../airtime-media-monitor-init-d | 25 +++++--- .../install/media-monitor-initialize.py | 2 +- python_apps/pypo/airtime-liquidsoap-init-d | 60 +++++++++++-------- python_apps/pypo/airtime-playout-init-d | 30 ++++++---- python_apps/pypo/install/pypo-initialize.py | 4 +- 5 files changed, 77 insertions(+), 44 deletions(-) diff --git a/python_apps/media-monitor/airtime-media-monitor-init-d b/python_apps/media-monitor/airtime-media-monitor-init-d index 2f37a49fa..ab8e0131a 100755 --- a/python_apps/media-monitor/airtime-media-monitor-init-d +++ b/python_apps/media-monitor/airtime-media-monitor-init-d @@ -17,19 +17,24 @@ DAEMON=/usr/lib/airtime/media-monitor/airtime-media-monitor PIDFILE=/var/run/airtime-media-monitor.pid start () { - start-stop-daemon --start --background --quiet --chuid $USERID:$GROUPID --make-pidfile --pidfile $PIDFILE --startas $DAEMON - monit monitor airtime-media-monitor >/dev/null 2>&1 + start-stop-daemon --start --background --quiet --chuid $USERID:$GROUPID \ + --make-pidfile --pidfile $PIDFILE --startas $DAEMON } stop () { # Send TERM after 5 seconds, wait at most 30 seconds. - monit unmonitor airtime-media-monitor >/dev/null 2>&1 start-stop-daemon --stop --oknodo --retry TERM/5/0/30 --quiet --pidfile $PIDFILE rm -f $PIDFILE } -start_no_monit() { - start-stop-daemon --start --background --quiet --chuid $USERID:$GROUPID --make-pidfile --pidfile $PIDFILE --startas $DAEMON +start_with_monit() { + start + monit monitor airtime-media-monitor >/dev/null 2>&1 +} + +stop_with_monit() { + monit unmonitor airtime-media-monitor >/dev/null 2>&1 + stop } @@ -53,10 +58,16 @@ case "${1:-''}" in start echo "Done." ;; - 'start-no-monit') + 'start-with-monit') # restart commands here echo -n "Starting $NAME: " - start_no_monit + start_with_monit + echo "Done." + ;; + 'stop-with-monit') + # restart commands here + echo -n "Stopping $NAME: " + stop_with_monit echo "Done." ;; 'status') diff --git a/python_apps/media-monitor/install/media-monitor-initialize.py b/python_apps/media-monitor/install/media-monitor-initialize.py index be3d39b85..8cc7b6149 100644 --- a/python_apps/media-monitor/install/media-monitor-initialize.py +++ b/python_apps/media-monitor/install/media-monitor-initialize.py @@ -16,6 +16,6 @@ try: #Start media-monitor daemon if "airtime_service_start" in os.environ and os.environ["airtime_service_start"] == "t": print "* Waiting for media-monitor processes to start..." - subprocess.call("invoke-rc.d airtime-media-monitor start-no-monit", shell=True) + subprocess.call("invoke-rc.d airtime-media-monitor start", shell=True) except Exception, e: print e diff --git a/python_apps/pypo/airtime-liquidsoap-init-d b/python_apps/pypo/airtime-liquidsoap-init-d index 37956a265..fcf3a0742 100755 --- a/python_apps/pypo/airtime-liquidsoap-init-d +++ b/python_apps/pypo/airtime-liquidsoap-init-d @@ -17,22 +17,6 @@ DAEMON=/usr/lib/airtime/pypo/bin/airtime-liquidsoap PIDFILE=/var/run/airtime-liquidsoap.pid start () { - start_no_monit - monit monitor airtime-liquidsoap >/dev/null 2>&1 -} - -stop () { - monit unmonitor airtime-liquidsoap >/dev/null 2>&1 - #send term signal after 10 seconds - timeout -k 5 10 /usr/lib/airtime/airtime_virtualenv/bin/python \ - /usr/lib/airtime/pypo/bin/liquidsoap_scripts/liquidsoap_prepare_terminate.py - # Send TERM after 5 seconds, wait at most 30 seconds. - start-stop-daemon --stop --oknodo --retry=TERM/10/KILL/5 --quiet --pidfile $PIDFILE - rm -f $PIDFILE - sleep 3 -} - -start_no_monit() { chown pypo:pypo /var/log/airtime/pypo chown pypo:pypo /var/log/airtime/pypo-liquidsoap chown pypo:pypo /etc/airtime/liquidsoap.cfg @@ -45,21 +29,43 @@ start_no_monit() { --pidfile $PIDFILE --nicelevel -15 --startas $DAEMON } +stop () { + #send term signal after 10 seconds + timeout -k 5 10 /usr/lib/airtime/airtime_virtualenv/bin/python \ + /usr/lib/airtime/pypo/bin/liquidsoap_scripts/liquidsoap_prepare_terminate.py + # Send TERM after 5 seconds, wait at most 30 seconds. + start-stop-daemon --stop --oknodo --retry=TERM/10/KILL/5 --quiet --pidfile $PIDFILE + rm -f $PIDFILE + sleep 3 +} + +start_with_monit () { + start + monit monitor airtime-liquidsoap >/dev/null 2>&1 +} + +stop_with_monit() { + monit unmonitor airtime-liquidsoap >/dev/null 2>&1 + stop +} + + + case "${1:-''}" in 'stop') - echo -n "Stopping Liquidsoap: " + echo -n "Stopping $NAME: " stop echo "Done." ;; 'start') - echo -n "Starting Liquidsoap: " + echo -n "Starting $NAME: " start echo "Done." ;; 'restart') # restart commands here - echo -n "Restarting Liquidsoap: " + echo -n "Restarting $NAME: " stop start echo "Done." @@ -69,22 +75,28 @@ case "${1:-''}" in if [ -f "$PIDFILE" ]; then pid=`cat $PIDFILE` if [ -d "/proc/$pid" ]; then - echo "Liquidsoap is running" + echo "$NAME is running" exit 0 fi fi - echo "Liquidsoap is not running" + echo "$NAME is not running" exit 1 ;; - 'start-no-monit') + 'start-with-monit') # restart commands here echo -n "Starting $NAME: " - start_no_monit + start_with_monit + echo "Done." + ;; + 'stop-with-monit') + # restart commands here + echo -n "Stopping $NAME: " + stop_with_monit echo "Done." ;; *) # no parameter specified - echo "Usage: $SELF start|stop|restart" + echo "Usage: $SELF start|stop|restart|status" exit 1 ;; diff --git a/python_apps/pypo/airtime-playout-init-d b/python_apps/pypo/airtime-playout-init-d index 499f6e1d7..13b46cf70 100755 --- a/python_apps/pypo/airtime-playout-init-d +++ b/python_apps/pypo/airtime-playout-init-d @@ -10,7 +10,7 @@ ### END INIT INFO USERID=root -NAME="Airtime Scheduler Engine" +NAME="Airtime Scheduler" DAEMON=/usr/lib/airtime/pypo/bin/airtime-playout PIDFILE=/var/run/airtime-playout.pid @@ -18,21 +18,24 @@ PIDFILE=/var/run/airtime-playout.pid start () { chown pypo:pypo /etc/airtime chown pypo:pypo /etc/airtime/liquidsoap.cfg - - start-stop-daemon --start --background --quiet --chuid $USERID:$USERID --make-pidfile --pidfile $PIDFILE --startas $DAEMON - monit monitor airtime-playout >/dev/null 2>&1 + start-stop-daemon --start --background --quiet --chuid $USERID:$USERID \ + --make-pidfile --pidfile $PIDFILE --startas $DAEMON } stop () { # Send TERM after 5 seconds, wait at most 30 seconds. - - monit unmonitor airtime-playout >/dev/null 2>&1 start-stop-daemon --stop --oknodo --retry TERM/5/0/30 --quiet --pidfile $PIDFILE rm -f $PIDFILE } -start_no_monit() { - start-stop-daemon --start --background --quiet --chuid $USERID:$USERID --make-pidfile --pidfile $PIDFILE --startas $DAEMON +start_with_monit() { + start + monit monitor airtime-playout >/dev/null 2>&1 +} + +stop_with_monit() { + monit unmonitor airtime-playout >/dev/null 2>&1 + stop } case "${1:-''}" in @@ -55,12 +58,19 @@ case "${1:-''}" in start echo "Done." ;; - 'start-no-monit') + 'start-with-monit') # restart commands here echo -n "Starting $NAME: " - start_no_monit + start_with_monit echo "Done." ;; + 'stop-with-monit') + # restart commands here + echo -n "Stopping $NAME: " + stop_with_monit + echo "Done." + ;; + 'status') # status commands here /usr/bin/airtime-check-system diff --git a/python_apps/pypo/install/pypo-initialize.py b/python_apps/pypo/install/pypo-initialize.py index 6581eef72..18ab708a0 100644 --- a/python_apps/pypo/install/pypo-initialize.py +++ b/python_apps/pypo/install/pypo-initialize.py @@ -88,8 +88,8 @@ try: if "airtime_service_start" in os.environ and os.environ["airtime_service_start"] == "t": print "* Waiting for pypo processes to start..." - subprocess.call("invoke-rc.d airtime-playout start-no-monit > /dev/null 2>&1", shell=True) - subprocess.call("invoke-rc.d airtime-liquidsoap start-no-monit > /dev/null 2>&1", shell=True) + subprocess.call("invoke-rc.d airtime-playout start > /dev/null 2>&1", shell=True) + subprocess.call("invoke-rc.d airtime-liquidsoap start > /dev/null 2>&1", shell=True) except Exception, e: print e