+ The document "'.$request."" doesn't exist. Are you sure you know what you're doing?";
+ ?>
+
+
+
diff --git a/www/rompr/INSTALL.txt b/www/rompr/INSTALL.txt
new file mode 100644
index 0000000..13698d8
--- /dev/null
+++ b/www/rompr/INSTALL.txt
@@ -0,0 +1,22 @@
+==================
+= Installing RompR
+==================
+Please read the installation instructions here:
+
+http://sourceforge.net/p/rompr/wiki/Installation/
+
+MacOS X Users
+=============
+
+Please read the instructions here:
+
+http://sourceforge.net/p/rompr/wiki/Installation%20on%20Mac%20OS%20X/
+
+Windows Users
+=============
+
+First, install a better operating system, then read this file again :)
+Actually, it may well be possible to get this to run on Windows, if mpd or mopidy can be got
+to work then there's probably a LAMP stack for Windows that will run apache and php
+OK. But I don't intend to go buy a Windows computer just to find out.
+
diff --git a/www/rompr/LICENSE.txt b/www/rompr/LICENSE.txt
new file mode 100644
index 0000000..a509ed1
--- /dev/null
+++ b/www/rompr/LICENSE.txt
@@ -0,0 +1,23 @@
+
+# (C) Fat German Productions/Mark Greenwood 2017
+
+SEE includes/license.html
+
+CREDITS
+=======
+
+This program makes use of lots of other stuff which are all released
+under their own licenses, just to make it complicated.
+
+The original inspiration came from phpMP, and the original core of this app was lifted
+directly from there. It's grown quite a lot since then.
+
+jquery (http://jquery.com) and the jquery UI framework (http://jqueryui.com)
+are probably released under the GPL or something. I dunno, their websites aren't
+very clear on that point.
+
+The md5 hashing algorithm code was taken from http://pajhome.org.uk/crypt/md5
+and is released under a BSD license
+
+The jquery form plugin comes from http://malsup.com/jquery/form/
+and also has two licenses.
diff --git a/www/rompr/README.md b/www/rompr/README.md
new file mode 100644
index 0000000..732866b
--- /dev/null
+++ b/www/rompr/README.md
@@ -0,0 +1,23 @@
+# RompЯ
+This is a browser-based client for Mopidy and MPD, which are both music players.
+
+You can use RompЯ to control a music player on another device or on your computer. Because it runs in a web browser you can run it ony any device - your laptop,tablet, or phone can all be used to control your music player.
+It has a rich and beautiful interface which is intended to sort your music, manage radio stations, browse and subscribe to podcasts.
+When used with Mopidy you can listen to Spotify and make use of RompЯ's incredible music discovery features which will help to introduce you to new music.
+
+## Installation from GitHub
+Please see the [New Project Homepage](https://fatg3erman.github.io/RompR/)
+
+![](docs/images/rompr-1.png)
+
+![](docs/images/rompr-on-a-phone.png)
+
+### What people are saying about RompЯ
+
+* “Best interface to mpd / mopidy ever. A real must!”
+* “Best browser based frontend to mpd I've ever seen!! Thank you!”
+* “Rompr is a wonderful web based interface client to mpd.”
+* “Great Project! I use it everyday already. I hope for VK-support soon. Thanks!”
+* “I tired now a fiew clients to control my mopidy server running on Raspberry. rompr is far the best one, easy to install, very fast, good design and useful features.”
+* “This application is amazing and easy to setup. I love it.”
+* “Wow! Great stuff! I was searching for something like that for months!”
\ No newline at end of file
diff --git a/www/rompr/README_ru.md b/www/rompr/README_ru.md
new file mode 100644
index 0000000..84ca0cb
--- /dev/null
+++ b/www/rompr/README_ru.md
@@ -0,0 +1,13 @@
+# RompЯ
+Это браузерный клиент для музыкальных проигрывателей Mopidy и MPD.
+
+RompЯ можно использовать для управления музыкальным проигрывателем на удалённом или локальном компьютере. Поскольку он работает в веб-браузере, вы можете запускать его на любом устройстве - ноутбуке, планшете или телефоне для управления вашим музыкальным проигрывателем..
+Он имеет богатый и красивый интерфейс, который предназначен для сортировки музыки, управления радиостанциями, просмотра и подписки на подкасты.
+При использовании с Mopidy вы можете слушать Spotify, которые помогут вам познакомиться с новой музыкой.
+
+## Установка с GitHub
+Инструкция [Новая Страница проекта](https://fatg3erman.github.io/RompR/)
+
+![](docs/images/rompr-1.png)
+
+![](docs/images/rompr-on-a-phone.png)
diff --git a/www/rompr/REC/REC.png b/www/rompr/REC/REC.png
new file mode 100644
index 0000000..10c1dca
Binary files /dev/null and b/www/rompr/REC/REC.png differ
diff --git a/www/rompr/REC/bootstrap.css b/www/rompr/REC/bootstrap.css
new file mode 100644
index 0000000..52b4a68
--- /dev/null
+++ b/www/rompr/REC/bootstrap.css
@@ -0,0 +1,450 @@
+/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */
+
+/* Document
+ ========================================================================== */
+
+/**
+ * 1. Correct the line height in all browsers.
+ * 2. Prevent adjustments of font size after orientation changes in
+ * IE on Windows Phone and in iOS.
+ */
+
+html {
+ line-height: 1.15; /* 1 */
+ -ms-text-size-adjust: 100%; /* 2 */
+ -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+/* Sections
+ ========================================================================== */
+
+/**
+ * Remove the margin in all browsers (opinionated).
+ margin: 0;
+ */
+
+body {
+ margin-left:auto;
+ margin-right:auto;
+ width:98%;
+}
+
+/**
+ * Add the correct display in IE 9-.
+ */
+
+article,
+aside,
+footer,
+header,
+nav,
+section {
+ display: block;
+}
+
+/**
+ * Correct the font size and margin on `h1` elements within `section` and
+ * `article` contexts in Chrome, Firefox, and Safari.
+ */
+
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0;
+}
+
+/* Grouping content
+ ========================================================================== */
+
+/**
+ * Add the correct display in IE 9-.
+ * 1. Add the correct display in IE.
+ */
+
+figcaption,
+figure,
+main { /* 1 */
+ display: block;
+}
+
+/**
+ * Add the correct margin in IE 8.
+ */
+
+figure {
+ margin: 1em 40px;
+}
+
+/**
+ * 1. Add the correct box sizing in Firefox.
+ * 2. Show the overflow in Edge and IE.
+ */
+
+hr {
+ box-sizing: content-box; /* 1 */
+ height: 0; /* 1 */
+ overflow: visible; /* 2 */
+}
+
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+pre {
+ font-family: monospace, monospace; /* 1 */
+ font-size: 1em; /* 2 */
+}
+
+/* Text-level semantics
+ ========================================================================== */
+
+/**
+ * 1. Remove the gray background on active links in IE 10.
+ * 2. Remove gaps in links underline in iOS 8+ and Safari 8+.
+ */
+
+a {
+ background-color: transparent; /* 1 */
+ -webkit-text-decoration-skip: objects; /* 2 */
+}
+
+/**
+ * 1. Remove the bottom border in Chrome 57- and Firefox 39-.
+ * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
+ */
+
+abbr[title] {
+ border-bottom: none; /* 1 */
+ text-decoration: underline; /* 2 */
+ text-decoration: underline dotted; /* 2 */
+}
+
+/**
+ * Prevent the duplicate application of `bolder` by the next rule in Safari 6.
+ */
+
+b,
+strong {
+ font-weight: inherit;
+}
+
+/**
+ * Add the correct font weight in Chrome, Edge, and Safari.
+ */
+
+b,
+strong {
+ font-weight: bolder;
+}
+
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+code,
+kbd,
+samp {
+ font-family: monospace, monospace; /* 1 */
+ font-size: 1em; /* 2 */
+}
+
+/**
+ * Add the correct font style in Android 4.3-.
+ */
+
+dfn {
+ font-style: italic;
+}
+
+/**
+ * Add the correct background and color in IE 9-.
+ */
+
+mark {
+ background-color: #ff0;
+ color: #000;
+}
+
+/**
+ * Add the correct font size in all browsers.
+ */
+
+small {
+ font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` elements from affecting the line height in
+ * all browsers.
+ */
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+sup {
+ top: -0.5em;
+}
+
+/* Embedded content
+ ========================================================================== */
+
+/**
+ * Add the correct display in IE 9-.
+ */
+
+audio,
+video {
+ display: inline-block;
+}
+
+/**
+ * Add the correct display in iOS 4-7.
+ */
+
+audio:not([controls]) {
+ display: none;
+ height: 0;
+}
+
+/**
+ * Remove the border on images inside links in IE 10-.
+ */
+
+img {
+ border-style: none;
+}
+
+/**
+ * Hide the overflow in IE.
+ */
+
+svg:not(:root) {
+ overflow: hidden;
+}
+
+/* Forms
+ ========================================================================== */
+
+/**
+ * 1. Change the font styles in all browsers (opinionated).
+ * 2. Remove the margin in Firefox and Safari.
+ */
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ font-family: sans-serif; /* 1 */
+ font-size: 100%; /* 1 */
+ line-height: 1.15; /* 1 */
+ margin: 0; /* 2 */
+}
+
+/**
+ * Show the overflow in IE.
+ * 1. Show the overflow in Edge.
+ */
+
+button,
+input { /* 1 */
+ overflow: visible;
+}
+
+/**
+ * Remove the inheritance of text transform in Edge, Firefox, and IE.
+ * 1. Remove the inheritance of text transform in Firefox.
+ */
+
+button,
+select { /* 1 */
+ text-transform: none;
+}
+
+/**
+ * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
+ * controls in Android 4.
+ * 2. Correct the inability to style clickable types in iOS and Safari.
+ */
+
+button,
+html [type="button"], /* 1 */
+[type="reset"],
+[type="submit"] {
+ -webkit-appearance: button; /* 2 */
+}
+
+/**
+ * Remove the inner border and padding in Firefox.
+ */
+
+button::-moz-focus-inner,
+[type="button"]::-moz-focus-inner,
+[type="reset"]::-moz-focus-inner,
+[type="submit"]::-moz-focus-inner {
+ border-style: none;
+ padding: 0;
+}
+
+/**
+ * Restore the focus styles unset by the previous rule.
+ */
+
+button:-moz-focusring,
+[type="button"]:-moz-focusring,
+[type="reset"]:-moz-focusring,
+[type="submit"]:-moz-focusring {
+ outline: 1px dotted ButtonText;
+}
+
+/**
+ * Correct the padding in Firefox.
+ */
+
+fieldset {
+ padding: 0.35em 0.75em 0.625em;
+}
+
+/**
+ * 1. Correct the text wrapping in Edge and IE.
+ * 2. Correct the color inheritance from `fieldset` elements in IE.
+ * 3. Remove the padding so developers are not caught out when they zero out
+ * `fieldset` elements in all browsers.
+ */
+
+legend {
+ box-sizing: border-box; /* 1 */
+ color: inherit; /* 2 */
+ display: table; /* 1 */
+ max-width: 100%; /* 1 */
+ padding: 0; /* 3 */
+ white-space: normal; /* 1 */
+}
+
+/**
+ * 1. Add the correct display in IE 9-.
+ * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera.
+ */
+
+progress {
+ display: inline-block; /* 1 */
+ vertical-align: baseline; /* 2 */
+}
+
+/**
+ * Remove the default vertical scrollbar in IE.
+ */
+
+textarea {
+ overflow: auto;
+}
+
+/**
+ * 1. Add the correct box sizing in IE 10-.
+ * 2. Remove the padding in IE 10-.
+ */
+
+[type="checkbox"],
+[type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * Correct the cursor style of increment and decrement buttons in Chrome.
+ */
+
+[type="number"]::-webkit-inner-spin-button,
+[type="number"]::-webkit-outer-spin-button {
+ height: auto;
+}
+
+/**
+ * 1. Correct the odd appearance in Chrome and Safari.
+ * 2. Correct the outline style in Safari.
+ */
+
+[type="search"] {
+ -webkit-appearance: textfield; /* 1 */
+ outline-offset: -2px; /* 2 */
+}
+
+/**
+ * Remove the inner padding and cancel buttons in Chrome and Safari on macOS.
+ */
+
+[type="search"]::-webkit-search-cancel-button,
+[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/**
+ * 1. Correct the inability to style clickable types in iOS and Safari.
+ * 2. Change font properties to `inherit` in Safari.
+ */
+
+::-webkit-file-upload-button {
+ -webkit-appearance: button; /* 1 */
+ font: inherit; /* 2 */
+}
+
+/* Interactive
+ ========================================================================== */
+
+/*
+ * Add the correct display in IE 9-.
+ * 1. Add the correct display in Edge, IE, and Firefox.
+ */
+
+details, /* 1 */
+menu {
+ display: block;
+}
+
+/*
+ * Add the correct display in all browsers.
+ */
+
+summary {
+ display: list-item;
+}
+
+/* Scripting
+ ========================================================================== */
+
+/**
+ * Add the correct display in IE 9-.
+ */
+
+canvas {
+ display: inline-block;
+}
+
+/**
+ * Add the correct display in IE.
+ */
+
+template {
+ display: none;
+}
+
+/* Hidden
+ ========================================================================== */
+
+/**
+ * Add the correct display in IE 10-.
+ */
+
+[hidden] {
+ display: none;
+}
diff --git a/www/rompr/REC/fond.jpg b/www/rompr/REC/fond.jpg
new file mode 100644
index 0000000..a24b90e
Binary files /dev/null and b/www/rompr/REC/fond.jpg differ
diff --git a/www/rompr/REC/youtube-dl.php b/www/rompr/REC/youtube-dl.php
new file mode 100644
index 0000000..ad1d71d
--- /dev/null
+++ b/www/rompr/REC/youtube-dl.php
@@ -0,0 +1,128 @@
+[REC]';
+ zcopylink += ' *';
+ lines[0].text += zcopylink;
+// ZEEBOX HACK ZONE
+*/
+// RUN CLI if($argc>1) parse_str(implode('&',array_slice($argv, 1)), $_REQUEST);
+$search=$radio=$artist=$title=$lnk=$lnkform=$cmd=$len="";
+$search = trim(urldecode($_REQUEST['q']));
+
+$radio = urldecode($_REQUEST['radio']);
+$artist = urldecode($_REQUEST['artist']);
+$title = urldecode($_REQUEST['title']);
+$lnk = trim(urldecode($_REQUEST['lnk']));
+$cmd="$radio|$artist|$title";
+$len=strlen($artist.$title);
+
+//$result=' ';
+$result=' ';
+if( $radio == "" ) { $radio = "CopyLaRadio"; }
+if( $radio == "Nova zz" ) {
+ $artist = "undefined";
+ $title = "undefined";
+}
+// Write request for copy.sh triggering
+if( $search == "REC" ) {
+///////////////////
+ $result.='
♫ '.$radio.' ♫
';
+ // LINK RECEIVED
+ if ($lnk) {
+ $artist="";
+ shell_exec('/home/pi/G1sms+/_CopyLaRadio/parle.sh "Lien reçu."');
+ // Not making double request
+ if( ! exec('grep '.escapeshellarg($lnk).' /tmp/ytdl.list') ) {
+ file_put_contents("/tmp/ytdl.list","CopyLibre||$lnk\n", FILE_APPEND);
+ }
+ $result.='
\n";
+
+}
+
+?>
diff --git a/www/rompr/albums.php b/www/rompr/albums.php
new file mode 100644
index 0000000..ea85401
--- /dev/null
+++ b/www/rompr/albums.php
@@ -0,0 +1,331 @@
+ /dev/null
+// where currenthost is the name of one of the Players defined in the Configuration menu
+// and player_backend MUST be mpd or mopidy, depending on what your player is.
+// You can also use eg -b "debug_enabled=8;currenthost=MPD;player_backend=mpd"
+// to get more debug info in the webserver error log.
+
+require_once ("includes/vars.php");
+require_once ("includes/functions.php");
+require_once ("utils/imagefunctions.php");
+require_once ("international.php");
+require_once ("backends/sql/backend.php");
+$error = 0;
+
+logger::trace("TIMINGS", "======================================================================");
+$initmem = memory_get_usage();
+logger::trace("COLLECTION", "Memory Used is ".$initmem);
+$now2 = time();
+
+switch (true) {
+
+ case array_key_exists('item', $_REQUEST):
+ logit('item');
+ // Populate a dropdown in the collection or search results
+ dumpAlbums($_REQUEST['item']);
+ break;
+
+ case array_key_exists('mpdsearch', $_REQUEST):
+ logit('mpdsearch');
+ // Handle an mpd-style search request
+ require_once ("player/".$prefs['player_backend']."/player.php");
+ require_once ("collection/collection.php");
+ $trackbytrack = true;
+ $doing_search = true;
+ mpd_search();
+ break;
+
+ case array_key_exists('browsealbum', $_REQUEST):
+ logit('browsealbum');
+ // Populate a spotify album in mopidy's search results - as spotify doesn't return all tracks
+ require_once ("player/".$prefs['player_backend']."/player.php");
+ require_once ("collection/collection.php");
+ $trackbytrack = true;
+ $doing_search = true;
+ browse_album();
+ break;
+
+ case array_key_exists("rawterms", $_REQUEST):
+ logit('rawterms');
+ // Handle an mpd-style search request requiring tl_track format results
+ // Note that raw_search uses the collection models but not the database
+ // hence $trackbytrack must be false
+ logger::log("MPD SEARCH", "Doing RAW search");
+ require_once ("player/".$prefs['player_backend']."/player.php");
+ require_once ("collection/collection.php");
+ require_once ("collection/dbsearch.php");
+ $doing_search = true;
+ raw_search();
+ break;
+
+ case array_key_exists('terms', $_REQUEST):
+ logit('terms');
+ // SQL database search request
+ require_once ("player/".$prefs['player_backend']."/player.php");
+ require_once ("collection/collection.php");
+ require_once ("collection/dbsearch.php");
+ $doing_search = true;
+ database_search();
+ break;
+
+ case array_key_exists('rebuild', $_REQUEST):
+ logit('rebuild');
+ // This is a request to rebuild the music collection
+ require_once ("player/".$prefs['player_backend']."/player.php");
+ require_once ("collection/collection.php");
+ $trackbytrack = true;
+ update_collection();
+ break;
+
+ default:
+ logger::fail("ALBUMS", "Couldn't figure out what to do!");
+ break;
+
+}
+
+logger::trace("TIMINGS", "== Collection Update And Send took ".format_time(time() - $now2));
+$peakmem = memory_get_peak_usage();
+$ourmem = $peakmem - $initmem;
+logger::trace("TIMINGS", "Peak Memory Used Was ".number_format($peakmem)." bytes - meaning we used ".number_format($ourmem)." bytes.");
+logger::trace("TIMINGS", "======================================================================");
+
+function logit($key) {
+ logger::log("COLLECTION", "Request is",$key,"=",$_REQUEST[$key]);
+}
+
+function checkDomains($d) {
+ if (array_key_exists('domains', $d)) {
+ return $d['domains'];
+ }
+ logger::debug("SEARCH", "No search domains in use");
+ return false;
+}
+
+function mpd_search() {
+ global $dbterms, $skin, $PLAYER_TYPE;
+ // If we're searching for tags or ratings it would seem sensible to only search the database
+ // HOWEVER - we could be searching for genre or performer or composer - which will not match in the database
+ // For those cases ONLY, controller.js will call into this instead of database_search, and we set $dbterms
+ // to make the collection check everything it finds against the database
+ $cmd = $_REQUEST['command'];
+ $domains = checkDomains($_REQUEST);
+ foreach ($_REQUEST['mpdsearch'] as $key => $term) {
+ switch ($key) {
+ case 'tag':
+ case 'rating':
+ $dbterms[$key] = $term;
+ break;
+
+ case 'any':
+ // This makes a search term of 'Madness My Girl' into
+ // search any Madness any My any Girl
+ // which seems to produce better results with Spotify. But probably doesn't with Google Play, which
+ // only uses the first term. Soundcloud concatenates them all back into one term again. What does MPD do?
+ foreach ($term as $t) {
+ $terms = explode(' ',$t);
+ foreach ($terms as $tom) {
+ $cmd .= " ".$key.' "'.format_for_mpd(html_entity_decode(trim($tom))).'"';
+ }
+ }
+ break;
+
+ default:
+ foreach ($term as $t) {
+ $cmd .= " ".$key.' "'.format_for_mpd(html_entity_decode(trim($t))).'"';
+ }
+ break;
+
+ }
+ }
+ logger::log("MPD SEARCH", "Search command : ".$cmd);
+ if ($_REQUEST['resultstype'] == "tree") {
+ require_once ("player/mpd/filetree.php");
+ require_once ("skins/".$skin."/ui_elements.php");
+ $player = new fileCollector();
+ $player->doFileSearch($cmd, $domains);
+ } else {
+ cleanSearchTables();
+ prepareCollectionUpdate();
+ $collection = new musicCollection();
+ $player = new $PLAYER_TYPE();
+ $player->populate_collection($cmd, $domains, $collection);
+ $collection->tracks_to_database();
+ close_transaction();
+ dumpAlbums($_REQUEST['dump']);
+ remove_findtracks();
+ }
+}
+
+function browse_album() {
+ global $PLAYER_TYPE, $skin;
+ $a = preg_match('/(a|b)(.*?)(\d+|root)/', $_REQUEST['browsealbum'], $matches);
+ if (!$a) {
+ print '
'.get_int_text("label_general_error").'
';
+ logger::error("DUMPALBUMS", "Browse Album Failed - regexp failed to match", $_REQUEST['browsealbum']);
+ return false;
+ }
+ $why = $matches[1];
+ $what = $matches[2];
+ $who = $matches[3];
+ $albumlink = get_albumlink($who);
+ if (substr($albumlink, 0, 8) == 'podcast+') {
+ require_once ('includes/podcastfunctions.php');
+ logger::log("ALBUMS", "Browsing For Podcast ".substr($albumlink, 9));
+ $podid = getNewPodcast(substr($albumlink, 8), 0, false);
+ logger::trace("ALBUMS", "Ouputting Podcast ID ".$podid);
+ outputPodcast($podid, false);
+ } else {
+ if (preg_match('/^.+?:artist:/', $albumlink)) {
+ remove_album_from_database($who);
+ }
+ $player = new $PLAYER_TYPE();
+ $collection = new musicCollection();
+ $cmd = 'find file "'.$albumlink.'"';
+ logger::log("MPD", "Doing Album Browse : ".$cmd);
+ prepareCollectionUpdate();
+ $player->populate_collection($cmd, false, $collection);
+ $collection->tracks_to_database(true);
+ close_transaction();
+ remove_findtracks();
+ if (preg_match('/^.+?:album:/', $albumlink)) {
+ // Just occasionally, the spotify album originally returned by search has an incorrect AlbumArtist
+ // When we browse the album the new tracks therefore get added to a new album, while the original tracks
+ // remain attached to the old one. This is where we use do_tracks_from_database with an array of albumids
+ // which joins them together into a virtual album, with the track ordering correct
+ print do_tracks_from_database($why, $what, find_justadded_albums(), true);
+ } else {
+ $artistarray = find_justadded_artists();
+ $do_controlheader = true;
+ foreach ($artistarray as $artistid) {
+ do_albums_from_database($why, 'album', $artistid, false, false, true, $do_controlheader);
+ $do_controlheader = false;
+ }
+ }
+ }
+}
+
+function raw_search() {
+ global $PLAYER_TYPE, $doing_search;
+ $domains = checkDomains($_REQUEST);
+ $collection = new musicCollection();
+ $found = 0;
+ logger::trace("MPD SEARCH", "checkdb is ".$_REQUEST['checkdb']);
+ if ($_REQUEST['checkdb'] !== 'false') {
+ logger::trace("MPD SEARCH", " ... checking database first ");
+ $found = doDbCollection($_REQUEST['rawterms'], $domains, "RAW", $collection);
+ if ($found > 0) {
+ logger::log("MPD SEARCH", " ... found ".$found." matches in database");
+ }
+ }
+ if ($found == 0) {
+ $cmd = $_REQUEST['command'];
+ foreach ($_REQUEST['rawterms'] as $key => $term) {
+ $cmd .= " ".$key.' "'.format_for_mpd(html_entity_decode($term[0])).'"';
+ }
+ logger::log("MPD SEARCH", "Search command : ".$cmd);
+ $doing_search = true;
+ $player = new $PLAYER_TYPE();
+ $player->populate_collection($cmd, $domains, $collection);
+
+ // For backends that don't support multiple parameters (Google Play)
+ // This'll return nothing for Spotify, so it's OK. It might help SoundCloud too.
+
+ $cmd = $_REQUEST['command'].' any ';
+ $parms = array();
+ if (array_key_exists('artist', $_REQUEST['rawterms'])) {
+ $parms[] = format_for_mpd(html_entity_decode($_REQUEST['rawterms']['artist'][0]));
+ }
+ if (array_key_exists('title', $_REQUEST['rawterms'])) {
+ $parms[] = format_for_mpd(html_entity_decode($_REQUEST['rawterms']['title'][0]));
+ }
+ if (count($parms) > 0) {
+ $cmd .= '"'.implode(' ',$parms).'"';
+ logger::log("MPD SEARCH", "Search command : ".$cmd);
+ $doing_search = true;
+ $collection->filter_duplicate_tracks();
+ $player->populate_collection($cmd, $domains, $collection);
+ }
+
+ }
+ print json_encode($collection->tracks_as_array());
+}
+
+function database_search() {
+ $tree = null;
+ $domains = checkDomains($_REQUEST);
+ if ($_REQUEST['resultstype'] == "tree") {
+ $tree = new mpdlistthing(null);
+ } else {
+ cleanSearchTables();
+ open_transaction();
+ }
+ $fcount = doDbCollection($_REQUEST['terms'], $domains, $_REQUEST['resultstype'], $tree);
+ if ($_REQUEST['resultstype'] == "tree") {
+ printFileSearch($tree, $fcount);
+ } else {
+ close_transaction();
+ dumpAlbums($_REQUEST['dump']);
+ }
+}
+
+function update_collection() {
+ global $PLAYER_TYPE;
+
+ // Check that an update is not currently in progress
+ // and create the update lock if not
+ if (collectionUpdateRunning()) {
+ header('HTTP/1.1 500 Internal Server Error');
+ print get_int_text('error_nocol');
+ exit(0);
+ }
+
+ if (file_exists('prefs/monitor')) {
+ unlink('prefs/monitor');
+ }
+ // Send some dummy data back to the browser, then close the connection
+ // so that the browser doesn't time out and retry
+ $sapi_type = php_sapi_name();
+ logger::log('COLLECTION','SAPI Name is',$sapi_type);
+ if (preg_match('/fpm/', $sapi_type) || preg_match('/fcgi/', $sapi_type)) {
+ logger::mark('COLLECTION', 'Closing Request The FastCGI Way');
+ print('');
+ fastcgi_finish_request();
+ } else {
+ logger::mark('COLLECTION', 'Closing Request The Apache Way');
+ ob_end_clean();
+ ignore_user_abort(true); // just to be safe
+ ob_start();
+ print('');
+ $size = ob_get_length();
+ header("Content-Length: $size");
+ header("Content-Encoding: none");
+ header("Connection: close");
+ ob_end_flush();
+ ob_flush();
+ flush();
+ if (ob_get_contents()) {
+ ob_end_clean();
+ }
+ }
+
+ if (session_id()) {
+ session_write_close();
+ }
+
+ // Browser is now happy. Now we can do our work in peace.
+ cleanSearchTables();
+ prepareCollectionUpdate();
+ $player = new $PLAYER_TYPE();
+ $player->musicCollectionUpdate();
+ tidy_database();
+ remove_findtracks();
+ // Add a marker to the monitor file to say we've finished
+ $player->collectionUpdateDone();
+ // Clear the update lock
+ clearUpdateLock();
+
+}
+
+?>
diff --git a/www/rompr/backends/sql/backend.php b/www/rompr/backends/sql/backend.php
new file mode 100644
index 0000000..f3ddf1d
--- /dev/null
+++ b/www/rompr/backends/sql/backend.php
@@ -0,0 +1,1953 @@
+ 4,
+ 'Rating' => 0,
+ 'Tags' => array()
+);
+
+// So what are Hidden tracks?
+// These are used to count plays from online sources when those tracks are not in the collection.
+// Doing this does increase the size of the database. Quite a lot. But without it the stats for charts
+// and fave artists etc don't make a lot of sense in a world where a proportion of your listening
+// is in response to searches of Spotify or youtube etc.
+
+// Wishlist items have Uri as NULL. Each wishlist track is in a distinct album - this makes stuff
+// easier for the wishlist viewer
+
+// Assumptions are made in the code that Wishlist items will not be hidden tracks and that hidden
+// tracks have no metadata apart from a Playcount. Always be aware of this.
+
+// For tracks, LastModified controls whether a collection update will update any of its data.
+// Tracks added by hand (by tagging or rating, via userRatings.php) must have LastModified as NULL
+// - this is how we prevent the collection update from removing them.
+
+// Search:
+// Tracktable.isSearchResult is set to:
+// 1 on any existing track that comes up in the search
+// 2 for any track that comes up the search and has to be added - i.e it's not part of the main collection.
+// 3 for any hidden track that comes up in search so it can be re-hidden later.
+// Note that there is arithmetical logic to the values used here, they're not arbitrary flags
+
+// Collection:
+// justAdded is automatically set to 1 for any track that has just been added
+// when updating the collection we set them all to 0 and then set to 1 on any existing track we find,
+// then we can easily remove old tracks.
+
+function create_new_track(&$data) {
+
+ // create_new_track
+ // Creates a new track, along with artists and album if necessary
+ // Returns: TTindex
+
+ global $mysqlc;
+
+ if ($data['albumai'] == null) {
+ // Does the albumartist exist?
+ $data['albumai'] = check_artist($data['albumartist']);
+ }
+
+ // Does the track artist exist?
+ if ($data['trackai'] == null) {
+ if ($data['artist'] != $data['albumartist']) {
+ $data['trackai'] = check_artist($data['artist']);
+ } else {
+ $data['trackai'] = $data['albumai'];
+ }
+ }
+
+ if ($data['albumai'] == null || $data['trackai'] == null) {
+ logger::fail("MYSQL", "Trying to create new track but failed to get an artist index");
+ return null;
+ }
+
+ if ($data['albumindex'] == null) {
+ // Does the album exist?
+ if ($data['album'] == null) {
+ $data['album'] = 'rompr_wishlist_'.microtime('true');
+ }
+ $data['albumindex'] = check_album($data);
+ if ($data['albumindex'] == null) {
+ logger::fail("MYSQL", "Trying to create new track but failed to get an album index");
+ return null;
+ }
+ }
+
+ $data['sourceindex'] = null;
+ if ($data['uri'] === null && array_key_exists('streamuri', $data) && $data['streamuri'] !== null) {
+ $data['sourceindex'] = check_radio_source($data);
+ }
+
+ if (sql_prepare_query(true, null, null, null,
+ "INSERT INTO
+ Tracktable
+ (Title, Albumindex, Trackno, Duration, Artistindex, Disc, Uri, LastModified, Hidden, isSearchResult, Sourceindex, isAudiobook)
+ VALUES
+ (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ $data['title'], $data['albumindex'], $data['trackno'], $data['duration'], $data['trackai'],
+ $data['disc'], $data['uri'], $data['lastmodified'], $data['hidden'], $data['searchflag'], $data['sourceindex'], $data['isaudiobook']))
+ {
+ return $mysqlc->lastInsertId();
+ }
+ return null;
+}
+
+function check_radio_source($data) {
+ global $mysqlc;
+ $index = simple_query('Sourceindex', 'WishlistSourcetable', 'SourceUri', $data['streamuri'], null);
+ if ($index === null) {
+ logger::log("SQL", "Creating Wishlist Source",$data['streamname']);
+ if (sql_prepare_query(true, null, null, null,
+ "INSERT INTO WishlistSourcetable (SourceName, SourceImage, SourceUri) VALUES (?, ?, ?)",
+ $data['streamname'], $data['streamimage'], $data['streamuri']))
+ {
+ $index = $mysqlc->lastInsertId();
+ }
+ }
+ return $index;
+}
+
+function check_artist($artist) {
+
+ // check_artist:
+ // Checks for the existence of an artist by name in the Artisttable and creates it if necessary
+ // Returns: Artistindex
+
+ $index = sql_prepare_query(false, null, 'Artistindex', null, "SELECT Artistindex FROM Artisttable WHERE LOWER(Artistname) = LOWER(?)", $artist);
+ if ($index === null) {
+ $index = create_new_artist($artist);
+ }
+ return $index;
+}
+
+function create_new_artist($artist) {
+
+ // create_new_artist
+ // Creates a new artist
+ // Returns: Artistindex
+
+ global $mysqlc;
+ $retval = null;
+ if (sql_prepare_query(true, null, null, null, "INSERT INTO Artisttable (Artistname) VALUES (?)", $artist)) {
+ $retval = $mysqlc->lastInsertId();
+ logger::trace("MYSQL", "Created artist",$artist,"with Artistindex",$retval);
+ }
+ return $retval;
+}
+
+function best_value($a, $b) {
+
+ // best_value
+ // Used by check_album to determine the best value to use when updating album details
+ // Returns: value
+
+ if ($b == null || $b == "") {
+ return $a;
+ } else {
+ return $b;
+ }
+}
+
+function check_album(&$data) {
+
+ // check_album:
+ // Checks for the existence of an album and creates it if necessary
+ // Returns: Albumindex
+
+ global $prefs, $trackbytrack, $doing_search;
+ $index = null;
+ $year = null;
+ $img = null;
+ $mbid = null;
+ $result = sql_prepare_query(false, PDO::FETCH_OBJ, null, null,
+ "SELECT
+ Albumindex,
+ Year,
+ Image,
+ AlbumUri,
+ mbid
+ FROM
+ Albumtable
+ WHERE
+ LOWER(Albumname) = LOWER(?)
+ AND AlbumArtistindex = ?
+ AND Domain = ?", $data['album'], $data['albumai'], $data['domain']);
+ $obj = array_shift($result);
+
+ if ($prefs['preferlocalfiles'] && $trackbytrack && !$doing_search && $data['domain'] == 'local' && !$obj) {
+ // Does the album exist on a different, non-local, domain? The checks above ensure we only do this
+ // during a collection update
+ $result = sql_prepare_query(false, PDO::FETCH_OBJ, null, null,
+ "SELECT
+ Albumindex,
+ Year,
+ Image,
+ AlbumUri,
+ mbid,
+ Domain
+ FROM
+ Albumtable
+ WHERE
+ LOWER(Albumname) = LOWER(?)
+ AND AlbumArtistindex = ?", $data['album'], $data['albumai']);
+ $obj = array_shift($result);
+ if ($obj) {
+ logger::log("MYSQL", "Album ".$data['album']." was found on domain ".$obj->Domain.". Changing to local");
+ $index = $obj->Albumindex;
+ if (sql_prepare_query(true, null, null, null, "UPDATE Albumtable SET AlbumUri=NULL, Domain=?, justUpdated=? WHERE Albumindex=?", 'local', 1, $index)) {
+ $obj->AlbumUri = null;
+ logger::debug("MYSQL", " ...Success");
+ } else {
+ logger::fail("MYSQL", " Album ".$data['album']." update FAILED");
+ return false;
+ }
+ }
+ }
+
+ if ($obj) {
+ $index = $obj->Albumindex;
+ $year = best_value($obj->Year, $data['date']);
+ $img = best_value($obj->Image, $data['image']);
+ $uri = best_value($obj->AlbumUri, $data['albumuri']);
+ $mbid = best_value($obj->mbid, $data['ambid']);
+ if ($year != $obj->Year || $img != $obj->Image || $uri != $obj->AlbumUri || $mbid != $obj->mbid) {
+
+ if ($prefs['debug_enabled'] > 6) {
+ logger::log("BACKEND", "Updating Details For Album ".$data['album']." (index ".$index.")" );
+ logger::log("BACKEND", " Old Date : ".$obj->Year);
+ logger::log("BACKEND", " New Date : ".$year);
+ logger::log("BACKEND", " Old Image : ".$obj->Image);
+ logger::log("BACKEND", " New Image : ".$img);
+ logger::log("BACKEND", " Old Uri : ".$obj->AlbumUri);
+ logger::log("BACKEND", " New Uri : ".$uri);
+ logger::log("BACKEND", " Old MBID : ".$obj->mbid);
+ logger::log("BACKEND", " New MBID : ".$mbid);
+ }
+
+ if (sql_prepare_query(true, null, null, null, "UPDATE Albumtable SET Year=?, Image=?, AlbumUri=?, mbid=?, justUpdated=1 WHERE Albumindex=?",$year, $img, $uri, $mbid, $index)) {
+ logger::debug("BACKEND", " ...Success");
+ } else {
+ logger::fail("BACKEND", " Album ".$data['album']." update FAILED");
+ return false;
+ }
+ }
+ } else {
+ $index = create_new_album($data);
+ }
+ return $index;
+}
+
+function create_new_album($data) {
+
+ // create_new_album
+ // Creates an album
+ // Returns: Albumindex
+
+ global $mysqlc;
+ $retval = null;
+ $im = array(
+ 'searched' => $data['image'] ? 1: 0,
+ 'image' => $data['image']
+ );
+ if (sql_prepare_query(true, null, null, null,
+ "INSERT INTO
+ Albumtable
+ (Albumname, AlbumArtistindex, AlbumUri, Year, Searched, ImgKey, mbid, Domain, Image)
+ VALUES
+ (?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ $data['album'], $data['albumai'], $data['albumuri'], $data['date'], $im['searched'], $data['imagekey'], $data['ambid'], $data['domain'], $im['image'])) {
+ $retval = $mysqlc->lastInsertId();
+ logger::log("BACKEND", "Created Album ".$data['album']." with Albumindex ".$retval);
+ }
+ return $retval;
+}
+
+function remove_ttid($ttid) {
+
+ // Remove a track from the database.
+ // Doesn't do any cleaning up - call remove_cruft afterwards to remove orphaned artists and albums
+
+ // Deleting tracks will delete their associated playcounts. While it might seem like a good idea
+ // to hide them instead, in fact this results in a situation where we have tracks in our database
+ // that no longer exist in physical form - eg if local tracks are removed. This is really bad if we then
+ // later play those tracks from an online source and rate them. romprmetadata::find_item will return the hidden local track,
+ // which will get rated and appear back in the collection. So now we have an unplayable track in our collection.
+ // There's no real way round it, (without creating some godwaful lookup table of backends it's safe to do this with)
+ // so we just delete the track and lose the playcount information.
+
+ // If it's a search result, it must be a manually added track (we can't delete collection tracks)
+ // and we might still need it in the search, so set it to a 2 instead of deleting it.
+
+ logger::log("BACKEND", "Removing track ".$ttid);
+ $result = false;
+ if (generic_sql_query("DELETE FROM Tracktable WHERE isSearchResult != 1 AND TTindex = '".$ttid."'",true)) {
+ if (generic_sql_query("UPDATE Tracktable SET isSearchResult = 2 WHERE isSearchResult = 1 AND TTindex = '".$ttid."'", true)) {
+ $result = true;;
+ }
+ }
+ return $result;
+}
+
+function list_tags() {
+
+ // list_tags
+ // Return a sorted lst of tag names. Used by the UI for creating the tag menu
+
+ $tags = array();
+ $result = generic_sql_query("SELECT Name FROM Tagtable ORDER BY LOWER(Name)");
+ foreach ($result as $r) {
+ $tags[] = $r['Name'];
+ }
+ return $tags;
+}
+
+function get_rating_headers($sortby) {
+
+ $ratings = array();
+
+ switch ($sortby) {
+
+ case 'Rating':
+ $ratings = generic_sql_query("SELECT Rating AS Name, COUNT(TTindex) AS NumTracks FROM Ratingtable GROUP BY Rating ORDER BY Rating");
+ break;
+
+ case 'Tag':
+ $ratings = generic_sql_query('SELECT Name, COUNT(TTindex) AS NumTracks FROM Tagtable JOIN TagListtable USING (Tagindex) GROUP BY Name ORDER BY Name');
+ break;
+
+ case 'AlbumArtist':
+ // It's actually Track Artist, but sod changing it now.
+ $qstring = "SELECT DISTINCT Artistname AS Name, COUNT(DISTINCT TTindex) AS NumTracks
+ FROM
+ Artisttable AS a JOIN Tracktable AS tt USING (Artistindex)
+ LEFT JOIN Ratingtable USING (TTindex)
+ LEFT JOIN `TagListtable` USING (TTindex)
+ LEFT JOIN Tagtable AS t USING (Tagindex)
+ WHERE Hidden = 0 AND isSearchResult < 2 AND (Rating IS NOT NULL OR t.Name IS NOT NULL)
+ GROUP BY Artistname
+ ORDER BY ";
+ $qstring .= sort_artists_by_name();
+ $ratings = generic_sql_query($qstring);
+ break;
+
+ case 'Tags':
+ $ratings = generic_sql_query("SELECT DISTINCT ".SQL_TAG_CONCAT." AS Name, 0 AS NumTracks FROM
+ (SELECT Tagindex, TTindex FROM TagListtable ORDER BY Tagindex) AS tagorder
+ JOIN Tagtable AS t USING (Tagindex)
+ GROUP BY TTindex
+ ORDER By Name");
+ break;
+
+ default:
+ $ratings = array('INTERNAL ERROR!');
+ break;
+
+ }
+
+ return $ratings;
+}
+
+function sortletter_mangler() {
+ global $prefs;
+ $qstring = '';
+ if (count($prefs['nosortprefixes']) > 0) {
+ $qstring .= "CASE ";
+ foreach($prefs['nosortprefixes'] AS $p) {
+ $phpisshitsometimes = strlen($p)+2;
+ $qstring .= "WHEN LOWER(a.Artistname) LIKE '".strtolower($p).
+ " %' THEN UPPER(SUBSTR(a.Artistname,".$phpisshitsometimes.",1)) ";
+ }
+ $qstring .= "ELSE UPPER(SUBSTR(a.Artistname,1,1)) END AS SortLetter";
+ } else {
+ $qstring .= "UPPER(SUBSTR(a.Artistname,1,1)) AS SortLetter";
+ }
+ return $qstring;
+}
+
+function get_rating_info($sortby, $value) {
+
+ global $prefs;
+
+ // Tuned SQL queries for each type, for speed, otherwise it's unuseable
+
+ switch ($sortby) {
+ case 'Rating':
+ $qstring = "SELECT
+ r.Rating AS Rating,
+ IFNULL(".SQL_TAG_CONCAT.", 'No Tags') AS Tags,
+ tr.TTindex,
+ tr.TrackNo,
+ tr.Title,
+ tr.Duration,
+ tr.Uri,
+ a.Artistname,
+ aa.Artistname AS AlbumArtist,
+ al.Albumname,
+ al.Image, ";
+ $qstring .= sortletter_mangler();
+
+ $qstring .= " FROM
+ Ratingtable AS r
+ JOIN Tracktable AS tr ON tr.TTindex = r.TTindex
+ LEFT JOIN TagListtable AS tl ON tr.TTindex = tl.TTindex
+ LEFT JOIN Tagtable AS t USING (Tagindex)
+ JOIN Albumtable AS al USING (Albumindex)
+ JOIN Artisttable AS a ON (tr.Artistindex = a.Artistindex)
+ JOIN Artisttable AS aa ON (al.AlbumArtistindex = aa.Artistindex)
+ WHERE r.Rating = ".$value." AND tr.isSearchResult < 2 AND tr.Uri IS NOT NULL";
+ break;
+
+ case 'Tag':
+ $qstring = "SELECT
+ IFNULL(r.Rating, 0) AS Rating,
+ IFNULL(".SQL_TAG_CONCAT.", 'No Tags') AS Tags,
+ tr.TTindex,
+ tr.TrackNo,
+ tr.Title,
+ tr.Duration,
+ tr.Uri,
+ a.Artistname,
+ aa.Artistname AS AlbumArtist,
+ al.Albumname,
+ al.Image, ";
+ $qstring .= sortletter_mangler();
+
+ $qstring .= " FROM
+ Tracktable AS tr
+ LEFT JOIN Ratingtable AS r ON tr.TTindex = r.TTindex
+ LEFT JOIN TagListtable AS tl ON tr.TTindex = tl.TTindex
+ LEFT JOIN Tagtable AS t USING (Tagindex)
+ JOIN Albumtable AS al USING (Albumindex)
+ JOIN Artisttable AS a ON (tr.Artistindex = a.Artistindex)
+ JOIN Artisttable AS aa ON (al.AlbumArtistindex = aa.Artistindex)
+ WHERE tr.isSearchResult < 2 AND tr.Uri IS NOT NULL AND tr.TTindex IN (SELECT TTindex FROM TagListtable JOIN Tagtable USING (Tagindex) WHERE Name = '".$value."')";
+ break;
+
+ case 'AlbumArtist':
+ // It's actually Track Artist, but sod changing it now.
+ $qstring = "SELECT
+ IFNULL(r.Rating, 0) AS Rating,
+ IFNULL(".SQL_TAG_CONCAT.", 'No Tags') AS Tags,
+ tr.TTindex,
+ tr.TrackNo,
+ tr.Title,
+ tr.Duration,
+ tr.Uri,
+ a.Artistname,
+ aa.Artistname AS AlbumArtist,
+ al.Albumname,
+ al.Image ";
+
+ $qstring .= " FROM
+ Tracktable AS tr
+ LEFT JOIN Ratingtable AS r ON tr.TTindex = r.TTindex
+ LEFT JOIN TagListtable AS tl ON tr.TTindex = tl.TTindex
+ LEFT JOIN Tagtable AS t USING (Tagindex)
+ JOIN Albumtable AS al USING (Albumindex)
+ JOIN Artisttable AS a ON (tr.Artistindex = a.Artistindex)
+ JOIN Artisttable AS aa ON (al.AlbumArtistindex = aa.Artistindex)
+ WHERE (r.Rating IS NOT NULL OR t.Name IS NOT NULL) AND tr.Uri IS NOT NULL AND tr.isSearchResult < 2 AND a.Artistname = '".$value."'";
+ break;
+
+ case 'Tags':
+ $qstring = "SELECT
+ IFNULL(r.Rating, 0) AS Rating,
+ ".SQL_TAG_CONCAT." AS Tags,
+ tr.TTindex,
+ tr.TrackNo,
+ tr.Title,
+ tr.Duration,
+ tr.Uri,
+ a.Artistname,
+ aa.Artistname AS AlbumArtist,
+ al.Albumname,
+ COUNT(tr.TTindex) AS count,
+ al.Image, ";
+ $qstring .= sortletter_mangler();
+
+ $qstring .= " FROM
+ TagListtable AS tl
+ JOIN Tracktable AS tr USING (TTindex)
+ JOIN Tagtable AS t USING (Tagindex)
+ LEFT JOIN Ratingtable AS r ON tr.TTindex = r.TTindex
+ JOIN Albumtable AS al USING (Albumindex)
+ JOIN Artisttable AS a ON (tr.Artistindex = a.Artistindex)
+ JOIN Artisttable AS aa ON (al.AlbumArtistindex = aa.Artistindex)
+ WHERE ";
+ $tags = explode(', ',$value);
+ foreach ($tags as $i => $t) {
+ $tags[$i] = "tr.TTindex IN (SELECT TTindex FROM TagListtable JOIN Tagtable USING (Tagindex) WHERE Name='".$t."')";
+ }
+ $qstring .= implode(' AND ', $tags);
+ $qstring .= " AND tr.isSearchResult < 2 AND tr.Uri IS NOT NULL";
+ break;
+ }
+
+ $qstring .= " GROUP BY tr.TTindex ORDER BY ";
+ $qstring .= sort_artists_by_name();
+ $qstring .= ", al.Albumname, tr.TrackNo";
+
+ $t = microtime(true);
+ $ratings = generic_sql_query($qstring);
+ $took = microtime(true) - $t;
+ logger::debug("TIMINGS", " :: Getting rating data took ".$took." seconds");
+ // Our query for Tags (Tag List) will get eg if we're looking for 'tag1' it'll get tracks with 'tag1' and 'tag2'
+ // So we check how many tags there are in each result and only use the ones that match the number of tags we're looking for
+ if ($sortby == 'Tags') {
+ $temp = array();
+ $c = count($tags);
+ foreach ($ratings as $rat) {
+ if ($rat['count'] == $c) {
+ $temp[] = $rat;
+ }
+ }
+ $ratings = $temp;
+ }
+
+ return $ratings;
+
+}
+
+function clear_wishlist() {
+ return generic_sql_query("DELETE FROM Tracktable WHERE Uri IS NULL", true);
+}
+
+function num_collection_tracks($albumindex) {
+ // Returns the number of tracks this album contains that were added by a collection update
+ // (i.e. not added manually). We do this because editing year or album artist for those albums
+ // won't hold across a collection update, so we just forbid it.
+ return generic_sql_query("SELECT COUNT(TTindex) AS cnt FROM Tracktable WHERE Albumindex = ".$albumindex." AND LastModified IS NOT NULL AND Hidden = 0 AND Uri IS NOT NULL AND isSearchResult < 2", false, null, 'cnt', 0);
+
+}
+
+function get_all_data($ttid) {
+
+ // Misleadingly named function which should be used to get ratings and tags
+ // (and whatever else we might add) based on a TTindex
+ global $nodata;
+ $data = $nodata;
+ $result = generic_sql_query("SELECT
+ IFNULL(r.Rating, 0) AS Rating,
+ IFNULL(p.Playcount, 0) AS Playcount,
+ ".sql_to_unixtime('p.LastPlayed')." AS LastTime,
+ ".sql_to_unixtime('tr.DateAdded')." AS DateAdded,
+ IFNULL(".SQL_TAG_CONCAT.", '') AS Tags,
+ tr.isSearchResult,
+ tr.Hidden
+ FROM
+ Tracktable AS tr
+ LEFT JOIN Ratingtable AS r ON tr.TTindex = r.TTindex
+ LEFT JOIN Playcounttable AS p ON tr.TTindex = p.TTindex
+ LEFT JOIN TagListtable AS tl ON tr.TTindex = tl.TTindex
+ LEFT JOIN Tagtable AS t USING (Tagindex)
+ WHERE tr.TTindex = ".$ttid."
+ GROUP BY tr.TTindex
+ ORDER BY t.Name"
+ );
+ if (count($result) > 0) {
+ $data = array_shift($result);
+ $data['Tags'] = ($data['Tags'] == '') ? array() : explode(', ', $data['Tags']);
+ if ($data['LastTime'] != null && $data['LastTime'] != 0 && $data['LastTime'] != '0') {
+ $data['Last'] = $data['LastTime'];
+ }
+ }
+ return $data;
+}
+
+// Looking up this way is hugely faster than looking up by Uri
+function get_extra_track_info(&$filedata) {
+ $data = array();;
+ $result = sql_prepare_query(false, PDO::FETCH_ASSOC, null, null,
+ 'SELECT Uri, TTindex, Disc, Artistname AS AlbumArtist, Albumtable.Image AS "X-AlbumImage", mbid AS MUSICBRAINZ_ALBUMID, Searched, IFNULL(Playcount, 0) AS Playcount
+ FROM
+ Tracktable
+ JOIN Albumtable USING (Albumindex)
+ JOIN Artisttable ON Albumtable.AlbumArtistindex = Artisttable.Artistindex
+ LEFT JOIN Playcounttable USING (TTindex)
+ WHERE Title = ?
+ AND TrackNo = ?
+ AND Albumname = ?',
+ $filedata['Title'], $filedata['Track'], $filedata['Album']
+ );
+ foreach ($result as $tinfo) {
+ if ($tinfo['Uri'] == $filedata['file']) {
+ logger::trace("EXTRAINFO", "Found Track In Collection");
+ $data = array_filter($tinfo, function($v) {
+ if ($v === null || $v == '') {
+ return false;
+ }
+ return true;
+ });
+ break;
+ }
+ }
+ if (count($data) == 0) {
+ $result = sql_prepare_query(false, PDO::FETCH_ASSOC, null, null,
+ 'SELECT Albumtable.Image AS "X-AlbumImage", mbid AS MUSICBRAINZ_ALBUMID, Searched
+ FROM
+ Albumtable
+ JOIN Artisttable ON Albumtable.AlbumArtistindex = Artisttable.Artistindex
+ WHERE Albumname = ?
+ AND Artistname = ?',
+ $filedata['Album'], concatenate_artist_names($filedata['AlbumArtist'])
+ );
+ foreach ($result as $tinfo) {
+ logger::trace("EXTRAINFO", "Found Album In Collection");
+ $data = array_filter($tinfo, function($v) {
+ if ($v === null || $v == '') {
+ return false;
+ }
+ return true;
+ });
+ break;
+ }
+ }
+ return $data;
+}
+
+function get_imagesearch_info($key) {
+
+ // Used by getalbumcover.php to get album and artist names etc based on an Image Key
+
+ $retval = array('artist' => null, 'album' => null, 'mbid' => null, 'albumpath' => null, 'albumuri' => null);
+ $result = generic_sql_query(
+ "SELECT DISTINCT
+ Artisttable.Artistname,
+ Albumname,
+ mbid,
+ Albumindex,
+ AlbumUri,
+ isSearchResult
+ FROM
+ Albumtable
+ JOIN Artisttable ON AlbumArtistindex = Artisttable.Artistindex
+ JOIN Tracktable USING (Albumindex)
+ WHERE ImgKey = '".$key."' AND isSearchResult < 2 AND Hidden = 0", false, PDO::FETCH_OBJ
+ );
+ // This can come back with multiple results if we have the same album on multiple backends
+ // So we make sure we combine the data to get the best possible set
+ foreach ($result as $obj) {
+ if ($retval['artist'] == null) {
+ $retval['artist'] = $obj->Artistname;
+ }
+ if ($retval['album'] == null) {
+ $retval['album'] = $obj->Albumname;
+ }
+ if ($retval['mbid'] == null || $retval['mbid'] == "") {
+ $retval['mbid'] = $obj->mbid;
+ }
+ if ($retval['albumpath'] == null) {
+ $retval['albumpath'] = get_album_directory($obj->Albumindex, $obj->AlbumUri);
+ }
+ if ($retval['albumuri'] == null || $retval['albumuri'] == "") {
+ $retval['albumuri'] = $obj->AlbumUri;
+ }
+ logger::log("GETALBUMCOVER", "Found album",$retval['album'],",in collection");
+ }
+
+ $result = generic_sql_query(
+ "SELECT DISTINCT
+ Artisttable.Artistname,
+ Albumname,
+ mbid,
+ Albumindex,
+ AlbumUri,
+ isSearchResult
+ FROM
+ Albumtable
+ JOIN Artisttable ON AlbumArtistindex = Artisttable.Artistindex
+ JOIN Tracktable USING (Albumindex)
+ WHERE ImgKey = '".$key."' AND isSearchResult > 1", false, PDO::FETCH_OBJ
+ );
+ // This can come back with multiple results if we have the same album on multiple backends
+ // So we make sure we combine the data to get the best possible set
+ foreach ($result as $obj) {
+ if ($retval['artist'] == null) {
+ $retval['artist'] = $obj->Artistname;
+ }
+ if ($retval['album'] == null) {
+ $retval['album'] = $obj->Albumname;
+ }
+ if ($retval['mbid'] == null || $retval['mbid'] == "") {
+ $retval['mbid'] = $obj->mbid;
+ }
+ if ($retval['albumpath'] == null) {
+ $retval['albumpath'] = get_album_directory($obj->Albumindex, $obj->AlbumUri);
+ }
+ if ($retval['albumuri'] == null || $retval['albumuri'] == "") {
+ $retval['albumuri'] = $obj->AlbumUri;
+ }
+ logger::log("GETALBUMCOVER", "Found album",$retval['album'],"in search results or hidden tracks");
+ }
+ return $retval;
+}
+
+function get_albumlink($albumindex) {
+ return simple_query('AlbumUri', 'Albumtable', 'Albumindex', $albumindex, "");
+}
+
+function get_album_directory($albumindex, $uri) {
+ global $prefs;
+ $retval = null;
+ // Get album directory by using the Uri of one of its tracks, making sure we choose only local tracks
+ if (getDomain($uri) == 'local') {
+ $result = generic_sql_query("SELECT Uri FROM Tracktable WHERE Albumindex = ".$albumindex." LIMIT 1");
+ foreach ($result as $obj2) {
+ $retval = dirname($obj2['Uri']);
+ $retval = preg_replace('#^local:track:#', '', $retval);
+ $retval = preg_replace('#^file://#', '', $retval);
+ $retval = preg_replace('#^beetslocal:\d+:'.$prefs['music_directory_albumart'].'/#', '', $retval);
+ logger::log("SQL", "Got album directory using track Uri :",$retval);
+ }
+ }
+ return $retval;
+}
+
+function update_image_db($key, $found, $imagefile) {
+ $val = ($found) ? $imagefile : null;
+ if (sql_prepare_query(true, null, null, null, "UPDATE Albumtable SET Image = ?, Searched = 1 WHERE ImgKey = ?", $val, $key)) {
+ logger::log("MYSQL", " Database Image URL Updated");
+ } else {
+ logger::fail("MYSQL", " Failed To Update Database Image URL",$val,$key);
+ }
+}
+
+function track_is_hidden($ttid) {
+ $h = simple_query('Hidden', 'Tracktable', 'TTindex', $ttid, 0);
+ return ($h != 0) ? true : false;
+}
+
+function track_is_searchresult($ttid) {
+ // This is for detecting tracks that were added as part of a search, or un-hidden as part of a search
+ $h = simple_query('isSearchResult', 'Tracktable', 'TTindex', $ttid, 0);
+ return ($h > 1) ? true : false;
+}
+
+function track_is_wishlist($ttid) {
+ $u = simple_query('Uri', 'Tracktable', 'TTindex', $ttid, '');
+ if ($u === null) {
+ logger::mark("USERRATING", "Track",$ttid,"is wishlist. Discarding");
+ generic_sql_query("DELETE FROM Playcounttable WHERE TTindex=".$ttid, true);
+ generic_sql_query("DELETE FROM Tracktable WHERE TTindex=".$ttid, true);
+ return true;
+ }
+ return false;
+}
+
+function check_for_wishlist_track(&$data) {
+ $result = sql_prepare_query(false, PDO::FETCH_ASSOC, null, null, "SELECT TTindex FROM Tracktable JOIN Artisttable USING (Artistindex)
+ WHERE Artistname=? AND Title=? AND Uri IS NULL",$data['artist'],$data['title']);
+ foreach ($result as $obj) {
+ logger::mark("USERRATING", "Wishlist Track",$obj['TTindex'],"matches the one we're adding");
+ $meta = get_all_data($obj['TTindex']);
+ $data['attributes'] = array();
+ $data['attributes'][] = array('attribute' => 'Rating', 'value' => $meta['Rating']);
+ $data['attributes'][] = array('attribute' => 'Tags', 'value' => $meta['Tags']);
+ generic_sql_query("DELETE FROM Tracktable WHERE TTindex=".$obj['TTindex'], true);
+ }
+}
+
+function sort_artists_by_name() {
+ global $prefs;
+ $qstring = '';
+ foreach ($prefs['artistsatstart'] as $a) {
+ $qstring .= "CASE WHEN LOWER(a.Artistname) = LOWER('".$a."') THEN 1 ELSE 2 END, ";
+ }
+ if (count($prefs['nosortprefixes']) > 0) {
+ $qstring .= "(CASE ";
+ foreach($prefs['nosortprefixes'] AS $p) {
+ $phpisshitsometimes = strlen($p)+2;
+ $qstring .= "WHEN LOWER(a.Artistname) LIKE '".strtolower($p).
+ " %' THEN LOWER(SUBSTR(a.Artistname,".$phpisshitsometimes.")) ";
+ }
+ $qstring .= "ELSE LOWER(a.Artistname) END)";
+ } else {
+ $qstring .= "LOWER(a.Artistname)";
+ }
+ return $qstring;
+}
+
+function albumartist_sort_query($flag) {
+ // This query gives us album artists only. It also makes sure we only get artists for whom we
+ // have actual tracks (no album artists who appear only on the wishlist or who have only hidden tracks)
+ // Using GROUP BY is faster than using SELECT DISTINCT
+ // USING IN is faster than the double JOIN
+ global $prefs;
+ switch ($flag) {
+ case 'a':
+ $sflag = "AND isSearchResult < 2 AND isAudiobook = 0";
+ break;
+
+ case 'b':
+ $sflag = "AND isSearchResult > 0 AND isAudiobook = 0";
+ break;
+
+ case 'z':
+ $sflag = "AND isSearchResult < 2 AND isAudiobook = 1";
+ break;
+
+ case 'c':
+ // Special case for album art manager
+ $sflag = "AND isSearchResult < 2";
+ break;
+
+ }
+
+ $qstring = "SELECT Artistname, Artistindex
+ FROM Artisttable AS a
+ WHERE
+ Artistindex IN
+ (SELECT AlbumArtistindex FROM Albumtable JOIN Tracktable USING (Albumindex)
+ WHERE Uri IS NOT NULL
+ AND Hidden = 0
+ ".track_date_check($prefs['collectionrange'], $flag)."
+ ".$sflag."
+ GROUP BY AlbumArtistindex)
+ ORDER BY ";
+ $qstring .= sort_artists_by_name();
+ return $qstring;
+}
+
+function do_artists_from_database($why, $what, $who) {
+ global $divtype;
+ $singleheader = array();
+ logger::trace("DUMPALBUMS", "Generating artist",$why.$what.$who,"from database");
+ $singleheader['type'] = 'insertAfter';
+ $singleheader['where'] = 'fothergill';
+ $count = 0;
+ $t = microtime(true);
+ $result = generic_sql_query(albumartist_sort_query($why), false, PDO::FETCH_ASSOC);
+ $at = microtime(true) - $t;
+ logger::debug("TIMINGS", " -- Album Artist SQL query took ".$at." seconds");
+ $t = microtime(true);
+ foreach($result as $obj) {
+ if ($who == "root") {
+ print artistHeader($why.$what.$obj['Artistindex'], $obj['Artistname']);
+ $count++;
+ } else {
+ if ($obj['Artistindex'] != $who) {
+ $singleheader['type'] = 'insertAfter';
+ $singleheader['where'] = $why.$what.$obj['Artistindex'];
+ } else {
+ $singleheader['html'] = artistHeader($why.$what.$obj['Artistindex'], $obj['Artistname']);
+ $singleheader['id'] = $who;
+ $at = microtime(true) - $t;
+ logger::debug("TIMINGS", " -- Generating Artist Header took ".$at." seconds");
+ return $singleheader;
+ }
+ }
+ $divtype = ($divtype == "album1") ? "album2" : "album1";
+ }
+ $at = microtime(true) - $t;
+ logger::debug("TIMINGS", " -- Generating Artist List took ".$at." seconds");
+ return $count;
+}
+
+function get_list_of_artists() {
+ return generic_sql_query(albumartist_sort_query("c"));
+}
+
+function album_sort_query($why, $what, $who) {
+ global $prefs;
+ $sflag = ($why == "b") ? "AND Tracktable.isSearchResult > 0" : "AND Tracktable.isSearchResult < 2";
+ $sflag .= ($why == 'z') ? " AND Tracktable.isAudiobook = 1" : " AND Tracktable.isAudiobook = 0";
+
+ $qstring = "SELECT Albumtable.*, Artisttable.Artistname FROM Albumtable JOIN Artisttable ON
+ (Albumtable.AlbumArtistindex = Artisttable.Artistindex) WHERE ";
+
+ if ($who != "root") {
+ $qstring .= "AlbumArtistindex = '".$who."' AND ";
+ }
+ $qstring .= "Albumindex IN (SELECT Albumindex FROM Tracktable WHERE
+ Tracktable.Albumindex = Albumtable.Albumindex AND ";
+
+ $qstring .= "Tracktable.Uri IS NOT NULL AND Tracktable.Hidden = 0 ".track_date_check($prefs['collectionrange'], $why)." ".$sflag.")";
+ $qstring .= " ORDER BY ";
+ if ($prefs['sortcollectionby'] == "albumbyartist" && $who == "root") {
+ foreach ($prefs['artistsatstart'] as $a) {
+ $qstring .= "CASE WHEN LOWER(Artisttable.Artistname) = LOWER('".$a."') THEN 1 ELSE 2 END, ";
+ }
+ if (count($prefs['nosortprefixes']) > 0) {
+ $qstring .= "(CASE ";
+ foreach($prefs['nosortprefixes'] AS $p) {
+ $phpisshitsometimes = strlen($p)+2;
+ $qstring .= "WHEN LOWER(Artisttable.Artistname) LIKE '".strtolower($p).
+ " %' THEN LOWER(SUBSTR(Artisttable.Artistname,".$phpisshitsometimes.")) ";
+ }
+ $qstring .= "ELSE LOWER(Artisttable.Artistname) END),";
+ } else {
+ $qstring .= "LOWER(Artisttable.Artistname),";
+ }
+ }
+ $qstring .= " CASE WHEN Albumname LIKE '".get_int_text('label_allartist')."%' THEN 1 ELSE 2 END,";
+ if ($prefs['sortbydate']) {
+ if ($prefs['notvabydate']) {
+ $qstring .= " CASE WHEN Artisttable.Artistname = 'Various Artists' THEN LOWER(Albumname) ELSE Year END,";
+ } else {
+ $qstring .= ' Year,';
+ }
+ }
+ $qstring .= ' LOWER(Albumname)';
+ return $qstring;
+}
+
+function do_artist_banner($why, $what, $who) {
+ logger::debug("BACKEND", "Creating Banner ".$why." ".$what." ".$who);
+ $singleheader['type'] = 'insertAfter';
+ $singleheader['where'] = 'fothergill';
+ $qstring = album_sort_query($why, $what, 'root');
+ $result = generic_sql_query($qstring, false, PDO::FETCH_OBJ);
+ foreach ($result as $obj) {
+ if ($obj->AlbumArtistindex != $who) {
+ $singleheader['where'] = 'aalbum'.$obj->Albumindex;
+ $singleheader['type'] = 'insertAfter';
+ } else {
+ $singleheader['html'] = artistBanner($obj->Artistname, $obj->AlbumArtistindex, $why);
+ $singleheader['id'] = $obj->AlbumArtistindex;
+ return $singleheader;
+ }
+ }
+}
+
+function do_albums_from_database($why, $what, $who, $fragment = false, $use_artistindex = false, $force_artistname = false, $do_controlheader = true) {
+ global $prefs;
+ $singleheader = array();
+ $singleheader['why'] = $why;
+ if ($prefs['sortcollectionby'] == "artist") {
+ $singleheader['type'] = 'insertAtStart';
+ $singleheader['where'] = $why.'artist'.$who;
+ } else {
+ $singleheader['type'] = 'insertAfter';
+ $singleheader['where'] = 'fothergill';
+ }
+ logger::log("DUMPALBUMS", "Generating albums for",$why.$what.$who,"from database");
+
+ $qstring = album_sort_query($why, $what, $who);
+ logger::debug("DUMPALBUMS", "Query String Is ".$qstring);
+
+ $count = 0;
+ $currart = "";
+ $currban = "";
+ $result = generic_sql_query($qstring);
+ if ($do_controlheader && count($result) > 0) {
+ print albumControlHeader($fragment, $why, $what, $who, $result[0]['Artistname']);
+ }
+ foreach ($result as $obj) {
+ $artistbanner = ($prefs['sortcollectionby'] == 'albumbyartist' && $prefs['showartistbanners']) ? $obj['Artistname'] : null;
+ $obj['Artistname'] = ($force_artistname || $prefs['sortcollectionby'] == "album") ? $obj['Artistname'] : null;
+ $obj['why'] = $why;
+ $obj['id'] = $why.$what.$obj['Albumindex'];
+ $obj['class'] = 'album';
+ if ($fragment === false) {
+ if ($artistbanner !== null && $artistbanner !== $currban) {
+ print artistBanner($artistbanner, $obj['AlbumArtistindex'], $why);
+ }
+ print albumHeader($obj);
+ } else {
+ if ($obj['Albumindex'] != $fragment) {
+ $singleheader['where'] = $why.'album'.$obj['Albumindex'];
+ $singleheader['type'] = 'insertAfter';
+ } else {
+ $singleheader['html'] = albumHeader($obj);
+ $singleheader['id'] = $fragment;
+ return $singleheader;
+ }
+ }
+ $currart = $obj['Artistname'];
+ $currban = $artistbanner;
+ $count++;
+ }
+ logger::log("DUMPALBUMS", "... Found ".$count." albums");
+ if ($count == 0 && !($why == 'a' && $who == 'root')) {
+ noAlbumsHeader();
+ }
+ return $count;
+}
+
+function artistBanner($a, $i, $why) {
+ return '
'.$a.'
';
+}
+
+function remove_album_from_database($albumid) {
+ generic_sql_query("DELETE FROM Tracktable WHERE Albumindex = ".$albumid, true);
+ generic_sql_query("DELETE FROM Albumtable WHERE Albumindex = ".$albumid, true);
+}
+
+function get_list_of_albums($aid) {
+ $qstring = "SELECT * FROM Albumtable WHERE AlbumArtistindex = '".$aid."' AND ";
+ $qstring .= "Albumindex IN (SELECT Albumindex FROM Tracktable WHERE Tracktable.Albumindex =
+ Albumtable.Albumindex AND Tracktable.Uri IS NOT NULL AND Tracktable.Hidden = 0 AND
+ Tracktable.isSearchResult < 2)";
+ $qstring .= ' ORDER BY LOWER(Albumname)';
+ return generic_sql_query($qstring);
+}
+
+function get_album_tracks_from_database($index, $cmd, $flag) {
+ global $prefs;
+ $retarr = array();
+ $qstring = null;
+ $cmd = ($cmd === null) ? 'add' : $cmd;
+ switch ($flag) {
+ // Really need to make these flags simpler, but it does work.
+ case "b":
+ // b - tracks from search results
+ $action = "SELECT";
+ $sflag = "AND isSearchResult > 0";
+ $rflag = "";
+ break;
+
+ case "r":
+ // r - only tracks with ratings
+ $action = "SELECT";
+ $sflag = "AND isSearchResult < 2 AND (LinkChecked = 0 OR LinkChecked = 2)";
+ $rflag = " JOIN Ratingtable USING (TTindex)";
+ break;
+
+ case "t":
+ // t - only tracks with tags
+ $action = "SELECT DISTINCT";
+ $sflag = "AND isSearchResult < 2 AND (LinkChecked = 0 OR LinkChecked = 2)";
+ $rflag = " JOIN TagListtable USING (TTindex)";
+ break;
+
+ case "y":
+ // y = only tracks with tags and ratings
+ $action = "SELECT DISTINCT";
+ $sflag = "AND isSearchResult < 2 AND (LinkChecked = 0 OR LinkChecked = 2)";
+ $rflag = " JOIN Ratingtable USING (TTindex)";
+ $rflag .= " JOIN TagListtable USING (TTindex)";
+ break;
+
+ case "u":
+ // u - only tracks with tags or ratings
+ $qstring = "SELECT Uri, Disc, Trackno FROM
+ Tracktable JOIN Ratingtable USING (TTindex)
+ WHERE Albumindex = '".$index.
+ "' AND Uri IS NOT NULL
+ AND Hidden = 0
+ AND (LinkChecked = 0 OR LinkChecked = 2)
+ AND isSearchResult <2 "
+ .track_date_check($prefs['collectionrange'], $flag).
+ "UNION SELECT Uri, Disc, TrackNo FROM
+ Tracktable JOIN TagListtable USING (TTindex)
+ WHERE Albumindex = '".$index.
+ "' AND Uri IS NOT NULL
+ AND Hidden = 0
+ AND (LinkChecked = 0 OR LinkChecked = 2)
+ AND isSearchResult <2 "
+ .track_date_check($prefs['collectionrange'], $flag).
+ "ORDER BY Disc, TrackNo;";
+ break;
+
+ case "z":
+ // z - Audiobooks
+ $action = "SELECT";
+ $sflag = "AND isSearchResult < 2 AND isAudiobook = 1";
+ $rflag = "";
+ break;
+
+ default:
+ // anything else - tracks from collection
+ $action = "SELECT";
+ $sflag = "AND isSearchResult < 2 AND isAudiobook = 0";
+ $rflag = "";
+ break;
+ }
+ logger::log("GET TRACKS", "Getting Album Tracks for Albumindex",$index);
+ if ($qstring === null) {
+ $qstring = $action." Uri FROM Tracktable".$rflag." WHERE Albumindex = '".$index."' AND Uri IS NOT NULL AND Hidden = 0 ".track_date_check($prefs['collectionrange'], $flag)." ".$sflag." ORDER BY Disc, TrackNo";
+ }
+ $result = generic_sql_query($qstring);
+ foreach($result as $a) {
+ $retarr[] = $cmd.' "'.$a['Uri'].'"';
+ }
+ return $retarr;
+}
+
+function get_artist_tracks_from_database($index, $cmd, $flag) {
+ global $prefs;
+ $retarr = array();
+ logger::log("GET TRACKS", "Getting Tracks for AlbumArtist",$index);
+ $qstring = "SELECT Albumindex FROM Albumtable JOIN Artisttable ON
+ (Albumtable.AlbumArtistindex = Artisttable.Artistindex) WHERE AlbumArtistindex = ".$index." ORDER BY";
+ if ($prefs['sortbydate']) {
+ if ($prefs['notvabydate']) {
+ $qstring .= " CASE WHEN Artistname = 'Various Artists' THEN LOWER(Albumname) ELSE Year END,";
+ } else {
+ $qstring .= ' Year,';
+ }
+ }
+ $qstring .= ' LOWER(Albumname)';
+ $result = generic_sql_query($qstring);
+ foreach ($result as $a) {
+ $retarr = array_merge($retarr, get_album_tracks_from_database($a['Albumindex'], $cmd, $flag));
+ }
+ return $retarr;
+}
+
+function do_tracks_from_database($why, $what, $whom, $fragment = false) {
+ // This function can accept multiple album ids ($whom can be an array)
+ // in which case it will combine them all into one 'virtual album' - see browse_album()
+ global $prefs;
+ $who = getArray($whom);
+ logger::log("GET TRACKS", "Generating tracks for album(s)",$who,"from database");
+ if ($fragment) {
+ ob_start();
+ }
+ $t = ($why == "b") ? "AND isSearchResult > 0" : "AND isSearchResult < 2";
+ $trackarr = generic_sql_query(
+ // This looks like a wierd way of doing it but the obvious way doesn't work with mysql
+ // due to table aliases being used.
+ "SELECT
+ ".SQL_TAG_CONCAT." AS tags,
+ r.Rating AS rating,
+ pr.Progress AS progress,
+ tr.TTindex AS ttid,
+ tr.Title AS title,
+ tr.TrackNo AS trackno,
+ tr.Duration AS time,
+ tr.LastModified AS lm,
+ tr.Disc AS disc,
+ tr.Uri AS uri,
+ tr.LinkChecked AS playable,
+ ta.Artistname AS artist,
+ tr.Artistindex AS trackartistindex,
+ al.AlbumArtistindex AS albumartistindex
+ FROM
+ (Tracktable AS tr, Artisttable AS ta, Albumtable AS al)
+ LEFT JOIN TagListtable AS tl ON tr.TTindex = tl.TTindex
+ LEFT JOIN Tagtable AS t USING (Tagindex)
+ LEFT JOIN Ratingtable AS r ON tr.TTindex = r.TTindex
+ LEFT JOIN Progresstable AS pr ON tr.TTindex = pr.TTindex
+ WHERE (".implode(' OR ', array_map('do_fiddle', $who)).")
+ AND uri IS NOT NULL
+ AND tr.Hidden = 0
+ ".track_date_check($prefs['collectionrange'], $why)."
+ ".$t."
+ AND tr.Artistindex = ta.Artistindex
+ AND al.Albumindex = tr.Albumindex
+ GROUP BY tr.TTindex
+ ORDER BY CASE WHEN title LIKE 'Album: %' THEN 1 ELSE 2 END, disc, trackno"
+ );
+ $numtracks = count($trackarr);
+ $numdiscs = get_highest_disc($trackarr);
+ $currdisc = -1;
+ trackControlHeader($why, $what, $who[0], get_album_details($who[0]));
+ foreach ($trackarr as $arr) {
+ if ($numdiscs > 1 && $arr['disc'] != $currdisc && $arr['disc'] > 0) {
+ $currdisc = $arr['disc'];
+ print '
";
+ return $html;
+}
+
+function searchStats() {
+ $numartists = generic_sql_query(
+ "SELECT COUNT(*) AS NumArtists FROM (SELECT DISTINCT AlbumArtistIndex FROM Albumtable
+ INNER JOIN Tracktable USING (Albumindex) WHERE Albumname IS NOT NULL AND Uri IS NOT
+ NULL AND Hidden = 0 AND isSearchResult > 0) AS t", false, null, 'NumArtists', 0);
+
+ $numalbums = generic_sql_query(
+ "SELECT COUNT(*) AS NumAlbums FROM (SELECT DISTINCT Albumindex FROM Albumtable
+ INNER JOIN Tracktable USING (Albumindex) WHERE Albumname IS NOT NULL AND Uri IS NOT
+ NULL AND Hidden = 0 AND isSearchResult > 0) AS t", false, null, 'NumAlbums', 0);
+
+ $numtracks = generic_sql_query(
+ "SELECT COUNT(*) AS NumTracks FROM Tracktable WHERE Uri IS NOT NULL
+ AND Hidden=0 AND isSearchResult > 0", false, null, 'NumTracks', 0);
+
+ $numtime = generic_sql_query(
+ "SELECT SUM(Duration) AS TotalTime FROM Tracktable WHERE Uri IS NOT NULL AND
+ Hidden=0 AND isSearchResult > 0", false, null, 'TotalTime', 0);
+
+ print '