We are no longer offering accounts on this server. Consider https://gitlab.freedesktop.org/ as a place to host projects.

Commit bc149f45 authored by Jonas Haraldsson's avatar Jonas Haraldsson

Merge branch 'player-2.0'

parents 33a2fef0 5f46521d
How to install phpdoc and generate api docs gnu-fm (on Debian Squeeze)
----------
---
1. Install dependencies and phpDocumentor-alpha:
---
# aptitude install php-pear php5-xsl
# pear channel-discover pear.phpdoc.org
# pear install phpdoc/phpDocumentor-alpha
for other ways to install, see http://www.phpdoc.org/docs/latest/for-users/installation.html
---
2. Create dir which will be holding the docs, and generate docs:
---
cd /path/to/gnu-fm/nixtape
mkdir docs
phpdoc -d 2.0/ -t docs/
The docs can now be found at http://mynixtapedomain.tld/docs/
Add something similar to this to your crontab to keep the docs up-to-date:
0 0 * * * phpdoc -q -d /path/to/gnu-fm/nixtape/2.0/ -t /path/to/gnu-fm/nixtape/docs
......@@ -198,6 +198,7 @@ if (isset($_POST['install'])) {
userid INTEGER REFERENCES Users(uniqueid),
sessionid VARCHAR(32) PRIMARY KEY,
client CHAR(3),
api_key VARCHAR(32),
expires INTEGER)',
'CREATE TABLE Now_Playing(
......@@ -378,17 +379,22 @@ if (isset($_POST['install'])) {
}
$adodb->Execute("CREATE INDEX scrobbles_time_idx ON Scrobbles(time)");
$adodb->Execute("CREATE INDEX scrobbles_userid_idx ON Scrobbles(userid)");
$adodb->Execute("CREATE INDEX scrobbles_userid_time_idx ON Scrobbles(userid, time)");
$adodb->Execute("CREATE INDEX scrobbles_track_idx on Scrobbles(track)");
$adodb->Execute("CREATE INDEX scrobble_track_name_idx ON Scrobble_Track(name)");
$adodb->Execute("CREATE INDEX track_streamable_idx on Track(streamable);");
$adodb->Execute("CREATE INDEX track_name_idx ON Track(name)");
$adodb->Execute("CREATE INDEX album_name_idx ON Album(name)");
$adodb->Execute("CREATE INDEX artist_name_idx ON Artist(name)");
if(strtolower(substr($dbms, 0, 5)) == 'pgsql') {
// MySQL doesn't support the use of lower() to create case-insensitive indexes
$adodb->Execute("CREATE INDEX album_artistname_idx ON Album(lower(artist_name))");
$adodb->Execute("CREATE INDEX track_artist_idx ON Track(lower(artist_name))");
$adodb->Execute("CREATE INDEX track_name_idx ON Track(lower(name))");
$adodb->Execute("CREATE INDEX scrobbles_artist_idx on Scrobbles(lower(artist))");
$adodb->Execute("CREATE INDEX scrobbles_track_idx on Scrobbles(lower(track))");
$adodb->Execute("CREATE INDEX groups_groupname_idx ON Groups(lower(groupname))");
$adodb->Execute("CREATE INDEX album_lower_artistname_idx ON Album(lower(artist_name))");
$adodb->Execute("CREATE INDEX track_lower_artist_idx ON Track(lower(artist_name))");
$adodb->Execute("CREATE INDEX track_lower_name_idx ON Track(lower(name))");
$adodb->Execute("CREATE INDEX scrobbles_lower_artist_idx on Scrobbles(lower(artist))");
$adodb->Execute("CREATE INDEX scrobbles_lower_track_idx on Scrobbles(lower(track))");
$adodb->Execute("CREATE INDEX groups_lower_groupname_idx ON Groups(lower(groupname))");
// PostgreSQL stored functions
$adodb->Execute("CREATE OR REPLACE LANGUAGE plpgsql;");
......
......@@ -2,11 +2,19 @@ Options +FollowSymLinks -MultiViews
RewriteEngine on
RewriteRule ^user/([^/]+)/?$ user-profile.php?user=$1 [NC,QSA]
RewriteRule ^user/([^/]+)/journal/?$ user-journal.php?user=$1 [NC,QSA]
RewriteRule ^user/([^/]+)/groups/?$ user-groups.php?user=$1 [NC,QSA]
RewriteRule ^user/([^/]+)/recent-tracks/?$ user-recent-tracks.php?user=$1 [NC,QSA]
RewriteRule ^user/([^/]+)/stats/?$ user-stats.php?user=$1 [NC,QSA]
RewriteRule ^user/([^/]+)/station/?$ user-station.php?user=$1 [NC,QSA]
RewriteRule ^user/([^/]+)/library/?$ user-library.php?user=$1 [NC,QSA]
RewriteRule ^user/([^/]+)/library/music/?$ user-library.php?user=$1&section=music [NC,QSA]
RewriteRule ^user/([^/]+)/library/scrobbles/?$ user-library.php?user=$1&section=scrobbles [NC,QSA]
RewriteRule ^user/([^/]+)/library/loved/?$ user-library.php?user=$1&section=loved [NC,QSA]
RewriteRule ^user/([^/]+)/library/banned/?$ user-library.php?user=$1&section=banned [NC,QSA]
RewriteRule ^user/([^/]+)/library/tags/?$ user-library.php?user=$1&section=tags [NC,QSA]
RewriteRule ^user/([^/]+)/library/tags/([^/]+)/?$ user-library.php?user=$1&section=tags&tag=$2 [NC,QSA]
RewriteRule ^user/([^/]+)/library/music/([^/]+)/?$ user-library.php?user=$1&section=music&artist=$2 [NC,QSA]
RewriteRule ^user/([^/]+)/library/music/([^/]+)/([^/]+)/?$ user-library.php?user=$1&section=music&artist=$2&album=$3 [NC,QSA]
RewriteRule ^user/([^/]+)/library/music/([^/]+)/_/([^/]+)/?$ user-library.php?user=$1&section=music&artist=$2&track=$3 [NC,QSA]
RewriteRule ^artist/([^/]+)/track/([^/]+)/edit/?$ track-add.php?artist=$1&track=$2 [NC,QSA]
RewriteRule ^artist/([^/]+)/track/([^/]+)/tag/?$ track-tag.php?artist=$1&track=$2 [NC,QSA]
RewriteRule ^artist/([^/]+)/track/([^/]+)/?$ track.php?artist=$1&track=$2 [NC,QSA]
......@@ -20,12 +28,8 @@ RewriteRule ^artist/([^/]+)/album/([^/]+)/?$ album.php?artist=$1&album=$2 [
RewriteRule ^artist/([^/]+)/?$ artist.php?artist=$1 [NC,QSA]
RewriteRule ^artist/([^/]+)/manage/?$ artist-manage.php?artist=$1 [NC,QSA]
RewriteRule ^artist/([^/]+)/tag/?$ artist-tag.php?artist=$1 [NC,QSA]
RewriteRule ^group/new$ edit_group.php?group=new [NC,QSA]
RewriteRule ^group/([^/]+)/?$ group.php?group=$1 [NC,QSA]
RewriteRule ^group/?$ group.php [NC,QSA]
RewriteRule ^country/([^/]+)/?$ location.php?country=$1 [NC,QSA]
RewriteRule ^logout login.php?action=logout [NC,QSA]
RewriteRule ^listen listen.php [NC,QSA]
RewriteRule ^music popular.php [NC,QSA]
RewriteRule ^users users.php [NC,QSA]
RewriteRule ^tag/([^/]+)/?$ tag.php?tag=$1 [NC,QSA]
......@@ -120,6 +120,8 @@ $method_map = array(
'track.love' => method_track_love,
'track.unlove' => method_track_unlove,
'track.unban' => method_track_unban,
'track.updatenowplaying' => method_track_updateNowPlaying,
'track.scrobble' => method_track_scrobble,
);
/**
......@@ -895,10 +897,10 @@ function method_auth_getSession() {
* Remove a scrobble from user's library
*
* #Parameters
* **timestamp** (required) : Timestamp in Unix time.
* **artist** (required) : Artist name.
* **track** (required) : Track name.
* **sk** (required) : Session key.
* * **timestamp** (required) : Timestamp in Unix time.
* * **artist** (required) : Artist name.
* * **track** (required) : Track name.
* * **sk** (required) : Session key.
* * **format** (optional) : Format of response, **xml** or **json**. Default is xml.
*
* #Additional info
......@@ -1356,6 +1358,124 @@ function method_track_unban() {
respond($xml);
}
/**
* track.updatenowplaying : Submits the user's currently playing track.
*
* ###Description
* Submits the user's currently playing track.
*
* ###Parameters
* * **artist** (required) : Artist name.
* * **track** (required) : Track name.
* * **sk** (required) : Session key.
* * **album** (optional) : Album name.
* * **tracknumber (optional) : Track's number on the album.
* * **context** (optional) : TODO
* * **mbid** (optional) : Track's musicbrainz ID.
* * **duration** (optional) : Length of the track in seconds.
* * **albumartist (optional) : Album's artist.
* * **api_key (optional) : Client API key.
* * **format** (optional) : Format of response, **xml** or **json**. Default is xml.
*
* ###Additional info
* **This method requires authentication**.
*
* **HTTP request method** : POST.
* - - -
*
* @todo context parameter not used
* @todo tracknumber parameter not stored in db
* @todo albumartist parameter not stored in db
* @package Webservice
* @subpackage Track
* @api
*/
function method_track_updateNowPlaying() {
if (!isset($_POST['artist']) || !isset($_POST['track'])) {
report_failure(LFM_INVALID_PARAMS);
}
$_POST_lower = array_change_key_case($_POST, CASE_LOWER);
$userid = get_userid();
$xml = TrackXML::updateNowPlaying($userid,
$_POST['artist'],
$_POST['track'],
$_POST['album'],
$_POST_lower['tracknumber'],
$_POST['context'],
$_POST['mbid'],
$_POST['duration'],
$_POST_lower['albumartist'],
$_POST['api_key']
);
respond($xml);
}
/**
* track.scrobble : Submits a track for scrobbling.
*
* ###Description
*
* Submits a track or a batch of tracks for scrobbling.
*
* ###Parameters
* * **artist[i]** (required) : Artist name.
* * **track[i]** (required) : Track name.
* * **sk** (required) : Session key.
* * **timestamp[i]** (required) : The time the track started playing (in UNIX time).
* * **album[i]** (optional) : Album name.
* * **context[i]** (optional) : TODO
* * **streamid[i]** (optional) : TODO
* * **chosenbyuser[i]** (optional) : TODO
* * **tracknumber[i]** (optional) : Track's number on album.
* * **mbid[i]** (optional) : Track's Musicbrainz ID.
* * **albumartist[i]** (optional) : Album's artist.
* * **duration[i]** (optional) : Length of the track in seconds.
* * **api_key (optional) : Client API key.
* * **format** (optional) : Format of response, **xml** or **json**. Default is xml.
*
* ###Additional info
* **This method requires authentication**.
*
* **HTTP request method** : POST.
* - - -
*
* @todo context parameter not used
* @todo streamid parameter not used
* @todo chosenbyuser parameter not used
* @todo tracknumber parameter not stored in db
* @todo albumartist parameter not stored in db
* @package Webservice
* @subpackage Track
* @api
*/
function method_track_scrobble() {
if (!isset($_POST['artist']) || !isset($_POST['track']) || !isset($_POST['timestamp'])) {
report_failure(LFM_INVALID_PARAMS);
}
$_POST_lower = array_change_key_case($_POST, CASE_LOWER);
$userid = get_userid();
$xml = TrackXML::scrobble($userid,
$_POST['artist'],
$_POST['track'],
$_POST['timestamp'],
$_POST['album'],
$_POST['context'],
$_POST_lower['streamid'],
$_POST_lower['chosenbyuser'],
$_POST_lower['tracknumber'],
$_POST['mbid'],
$_POST_lower['albumartist'],
$_POST['duration'],
$_POST['api_key']
);
respond($xml);
}
/**
* tag.gettoptags : Get the top tags.
*
......
......@@ -58,4 +58,3 @@ if (isset($this_user) && $this_user->manages($artist->name)) {
}
$smarty->assign('pagetitle', $artist->name . ' : ' . $album->name);
$smarty->assign('headerfile', 'album-header.tpl');
......@@ -20,6 +20,7 @@
require_once($install_path . '/database.php');
require_once($install_path . '/data/Track.php');
require_once($install_path . '/scrobble-utils.php');
require_once('xml.php');
/**
......@@ -34,24 +35,32 @@ class TrackXML {
public static function addTags($userid, $artist, $album, $trackName, $tags) {
try {
$track = new Track($trackName, $artist);
$track->addTags($tags, $userid);
$res = $track->addTags($tags, $userid);
} catch (Exception $e) {
return(XML::error('failed', '7', 'Invalid resource specified'));
}
$xml = new SimpleXMLElement('<lfm status="ok"></lfm>');
if(!$res) {
$xml = XML::error('failed', '7', 'Invalid resource specified');
} else {
$xml = new SimpleXMLElement('<lfm status="ok"></lfm>');
}
return $xml;
}
public static function removeTag($userid, $artist, $trackName, $tag) {
try {
$track = new Track($trackName, $artist);
$track->removeTag($tag, $userid);
$res = $track->removeTag($tag, $userid);
} catch (Exception $e) {
return(XML::error('failed', '7', 'Invalid resource specified'));
}
$xml = new SimpleXMLElement('<lfm status="ok"></lfm>');
if(!$res) {
$xml = XML::error('failed', '7', 'Invalid resource specified');
} else {
$xml = new SimpleXMLElement('<lfm status="ok"></lfm>');
}
return $xml;
}
......@@ -98,7 +107,6 @@ class TrackXML {
$root->addAttribute('artist', $track->artist_name);
$root->addAttribute('track', $track->name);
$i = $offset + 1;
foreach($res as &$row) {
try {
$user = new User($row['username']);
......@@ -114,7 +122,6 @@ class TrackXML {
$image_large->addAttribute('size', 'large');
$user_node->addChild('weight', $row['freq']);
} catch (Exception $e) {}
$i++;
}
return $xml;
......@@ -149,56 +156,283 @@ class TrackXML {
}
public static function ban($artist, $name, $userid) {
global $adodb;
try {
$res = $adodb->Execute('INSERT INTO Banned_Tracks VALUES ('
. $userid . ', '
. $adodb->qstr($name) . ', '
. $adodb->qstr($artist) . ', '
. time() . ')');
} catch (Exception $e) {}
$xml = new SimpleXMLElement('<lfm status="ok"></lfm>');
$track = new Track($name, $artist);
$res = $track->ban($userid);
} catch (Exception $e) {
return XML::error('failed', '7', 'Invalid resource specified');
}
if(!$res) {
$xml = XML::error('failed', '7', 'Invalid resource specified');
} else {
$xml = new SimpleXMLElement('<lfm status="ok"></lfm>');
}
return $xml;
}
public static function love($artist, $name, $userid) {
try {
$track = new Track($name, $artist);
$track->love($userid);
$res = $track->love($userid);
} catch (Exception $e) {
return(XML::error('failed', '7', 'Invalid resource specified'));
return XML::error('failed', '7', 'Invalid resource specified');
}
$xml = new SimpleXMLElement('<lfm status="ok"></lfm>');
if(!$res) {
$xml = XML::error('failed', '7', 'Invalid resource specified');
} else {
$xml = new SimpleXMLElement('<lfm status="ok"></lfm>');
}
return $xml;
}
public static function unban($artist, $name, $userid) {
try {
$track = new Track($name, $artist);
$res = $track->unban($userid);
} catch (Exception $e) {
return XML::error('failed', '7', 'Invalid resource specified');
}
if(!$res) {
$xml = XML::error('failed', '7', 'Invalid resource specified');
} else {
$xml = new SimpleXMLElement('<lfm status="ok"></lfm>');
}
return $xml;
}
public static function unlove($artist, $name, $userid) {
try {
$track = new Track($name, $artist);
$res = $track->unlove($userid);
} catch (Exception $e) {
return XML::error('failed', '7', 'Invalid resource specified');
}
if(!$res) {
$xml = XML::error('failed', '7', 'Invalid resource specified');
} else {
$xml = new SimpleXMLElement('<lfm status="ok"></lfm>');
}
return $xml;
}
public static function updateNowPlaying($userid, $artist, $track, $album, $tracknumber, $context, $mbid, $duration, $albumartist, $api_key) {
global $adodb;
$sessionid = getScrobbleSessionID($userid, $api_key);
$t = array(
'artist' => $artist,
'track' => $track,
'album' => $album,
'tracknumber' => $tracknumber,
'mbid' => $mbid,
'duration' => $duration,
'albumartist' => $albumartist
);
$t = prepareTrack($userid, $t, 'nowplaying');
// Delete last played track
$query = 'DELETE FROM Now_Playing WHERE sessionid = ?';
$params = array($sessionid);
try {
$res = $adodb->Execute('DELETE FROM Banned_Tracks WHERE userid=' . $userid . ' AND track=' . $adodb->qstr($name) . ' AND artist=' . $adodb->qstr($artist));
$adodb->Execute($query, $params);
} catch (Exception $e) {}
// Calculate expiry time
if (!$t['duration'] || ($t['duration'] > 5400)) {
// Default expiry time of 300 seconds if duration is false or above 5400 seconds
$expires = time() + 300;
} else {
$expires = time() + $t['duration'];
}
if ($t['ignored_code'] === 0) {
// Clean up expired tracks in now_playing table
$params = array(time());
$query = 'DELETE FROM Now_Playing WHERE expires < ?';
$adodb->Execute($query, $params);
$adodb->StartTrans();
try {
// getTrackID will create the track in Track table if it doesnt exist
getTrackID($t['artist'], $t['album'], $t['track'], $t['mbid'], $t['duration']);
$params = array($sessionid, $t['track'], $t['artist'], $t['album'], $t['mbid'], $expires);
$query = 'INSERT INTO Now_Playing(sessionid, track, artist, album, mbid, expires) VALUES (?,?,?,?,?,?)';
$adodb->Execute($query, $params);
} catch (Exception $e) {
$adodb->FailTrans();
$adodb->CompleteTrans();
reportError($e->getMessage(), $e->getTraceAsString());
return XML::error('failed', '16', 'The service is temporarily unavailable, please try again.');
}
$adodb->CompleteTrans();
}
$xml = new SimpleXMLElement('<lfm status="ok"></lfm>');
$root = $xml->addChild('nowplaying', null);
$track_node = $root->addChild('track', repamp($t['track']));
$track_node->addAttribute('corrected', $t['track_corrected']);
$artist_node = $root->addChild('artist', repamp($t['artist']));
$artist_node->addAttribute('corrected', $t['artist_corrected']);
$album_node = $root->addChild('album', repamp($t['album']));
$album_node->addAttribute('corrected', $t['album_corrected']);
$albumartist_node = $root->addChild('albumArtist', repamp($t['albumartist']));
$albumartist_node->addAttribute('corrected', $t['albumartist_corrected']);
$ignoredmessage_node = $root->addChild('ignoredMessage', $t['ignored_message']);
$ignoredmessage_node->addAttribute('code', $t['ignored_code']);
return $xml;
}
public static function unlove($artist, $name, $userid) {
try {
$track = new Track($name, $artist);
$track->unlove($userid);
} catch (Exception $e) {
return(XML::error('failed', '7', 'Invalid resource specified'));
public static function scrobble($userid, $artist, $track, $timestamp, $album, $context, $streamid, $chosenbyuser, $tracknumber, $mbid, $albumartist, $duration, $api_key) {
global $adodb;
$sessionid = getScrobbleSessionID($userid, $api_key);
$accepted_count = 0;
$ignored_count = 0;
$tracks_array = array();
// Convert input to track arrays and add them to tracks_array
if (is_array($artist)) {
for ($i = 0; $i < count($artist); $i++) {
$tracks_array[$i] = array(
'artist' => $artist[$i],
'track' => $track[$i],
'timestamp' => $timestamp[$i],
'album' => $album[$i],
'tracknumber' => $tracknumber[$i],
'mbid' => $mbid[$i],
'albumartist' => $albumartist[$i],
'duration' => $duration[$i],
);
}
} else {
$tracks_array[0] = array(
'artist' => $artist,
'track' => $track,
'timestamp' => $timestamp,
'album' => $album,
'tracknumber' => $tracknumber,
'mbid' => $mbid,
'albumartist' => $albumartist,
'duration' => $duration,
);
}
// Correct and inspect scrobbles to see if some should be ignored
for ($i = 0; $i < count($tracks_array); $i++) {
$tracks_array[$i] = prepareTrack($userid, $tracks_array[$i], 'scrobble');
}
$adodb->StartTrans();
for ($i = 0; $i < count($tracks_array); $i++) {
$t = $tracks_array[$i];
if ($t['ignored_code'] === 0) {
try {
// Create artist, album and track if not already in db
$t['track_id'] = getTrackID($t['artist'], $t['album'], $t['track'], $t['mbid'], $t['duration']);
$t['scrobbletrack_id'] = getScrobbleTrackID($t['artist'], $t['album'], $t['track'], $t['mbid'], $t['duration'], $t['track_id']);
} catch (Exception $e) {
// Roll back database entries, log error and respond with error message
$adodb->FailTrans();
$adodb->CompleteTrans();
reportError($e->getMessage(), $e->getTraceAsString());
return XML::error('failed', '16', 'The service is temporarily unavailable, please try again.');
}
try {
// Scrobble
// TODO last.fm spec says we shouldnt scrobble corrected values,
// so maybe we should only use corrected values for validation and in xml
$query = 'INSERT INTO Scrobbles (userid, artist, album, track, time, mbid, source, rating, length, stid) VALUES (?,?,?,?,?,?,?,?,?,?)';
$params = array(
$userid,
$t['artist'],
$t['album'],
$t['track'],
$t['timestamp'],
$t['mbid'],
null,
null,
$t['duration'],
$t['scrobbletrack_id']
);
$adodb->Execute($query, $params);
} catch (Exception $e) {
// Roll back database entries, log error and respond with error message
$adodb->FailTrans();
$adodb->CompleteTrans();
reportError($e->getMessage(), $e->getTraceAsString());
return XML::error('failed', '16', 'The service is temporarily unavailable, please try again.');
}
}
$tracks_array[$i] = $t;
}
$adodb->CompleteTrans();
// Check if forwarding is enabled before looping through array
$params = array($userid);
$query = 'SELECT userid FROM Service_Connections WHERE userid = ? AND forward = 1';
$forward_enabled = $adodb->CacheGetOne(600, $query, $params);
if ($forward_enabled) {
for ($i = 0; $i < count($tracks_array); $i++) {
$t = $tracks_array[$i];
if ($t['ignored_code'] === 0) {
/* Forward scrobbles, we are forwarding unmodified input submitted by user,
* but only the scrobbles that passed our ignore filters, see prepareTrack(). */
forwardScrobble($userid,
$t['artist_old'],
$t['album_old'],
$t['track_old'],
$t['timestamp_old'],
$t['mbid_old'],
'',
'',
$t['duration_old']);
}
}
}
// Build xml
$xml = new SimpleXMLElement('<lfm status="ok"></lfm>');
$root = $xml->addChild('scrobbles', null);
for ($i = 0; $i < count($tracks_array); $i++) {
$t = $tracks_array[$i];
$scrobble = $root->addChild('scrobble', null);
$track_node = $scrobble->addChild('track', repamp($t['track']));