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 '
'; } 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 '