Commit 47c0dfb9 authored by Jonas Haraldsson's avatar Jonas Haraldsson

Merge branch '2.0-scrobble' into clean-2.0-scrobble

Conflicts resolved:
	nixtape/api/TrackXML.php
parents 8e9e3ba3 7085abd0
......@@ -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;");
......
......@@ -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,
);
/**
......@@ -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.
*
......
......@@ -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');
/**
......@@ -219,4 +220,221 @@ class TrackXML {
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 {
$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 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['ignoredcode'] === 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['ignoredcode'] === 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']));
$track_node->addAttribute('corrected', $t['track_corrected']);
$artist_node = $scrobble->addChild('artist', repamp($t['artist']));
$artist_node->addAttribute('corrected', $t['artist_corrected']);
$album_node = $scrobble->addChild('album', repamp($t['album']));
$album_node->addAttribute('corrected', $t['album_corrected']);
$albumartist_node = $scrobble->addChild('albumArtist', repamp($t['albumartist']));
$albumartist_node->addAttribute('corrected', $t['albumartist_corrected']);
$scrobble->addChild('timestamp', $t['timestamp']);
$ignoredmessage_node = $scrobble->addChild('ignoredMessage', $t['ignoredmessage']);
$ignoredmessage_node->addAttribute('code', $t['ignoredcode']);
if ($t['ignoredcode'] === 0) {
$accepted_count += 1;
} else {
$ignored_count += 1;
}
}
$root->addAttribute('accepted', $accepted_count);
$root->addAttribute('ignored', $ignored_count);
return $xml;
}
}
......@@ -534,6 +534,7 @@ class Server {
n.track,
n.album,
client,
api_key,
n.mbid,
t.license
FROM Now_Playing n
......@@ -573,12 +574,14 @@ class Server {
foreach ($data as &$i) {
$row = sanitize($i);
$client = getClientData($row['client']);
if(is_array($client)) {
$row['clientname'] = $client['name'];
$row['clienturl'] = $client['url'];
$row['clientfree'] = $client['free'];
}
$client = getClientData($row['client'], $row['api_key']);
$row['clientcode'] = $client['code'];
$row['clientapi_key'] = $client['code'];
$row['clientname'] = $client['name'];
$row['clienturl'] = $client['url'];
$row['clientfree'] = $client['free'];
$row['username'] = uniqueid_to_username($row['userid']);
$row['userurl'] = Server::getUserURL($row['username']);
$row['artisturl'] = Server::getArtistURL($row['artist']);
......
This diff is collapsed.
......@@ -34,7 +34,18 @@ try {
die("Unable to connect to database.");
}
// To keep compatibility with existing code
function reportError($title, $msg) {
/**
* Write error to Error database table
*
* @param string msg Message
* @param string data Data
* @return null
*/
function reportError($msg, $data) {
global $adodb;
$adodb->Execute('INSERT INTO Error(msg, data, time) VALUES('
. $adodb->qstr($msg) . ', '
. $adodb->qstr($data) . ', '
. time() . ')');
}
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment