Commit 2fc4ae6f authored by Jonas Haraldsson's avatar Jonas Haraldsson

Merge branch 'smarty3upgrade'

parents d05c5585 7a89bb06
......@@ -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(
......@@ -340,7 +341,11 @@ if (isset($_POST['install'])) {
'CREATE TABLE User_Stats (
userid INTEGER REFERENCES Users(uniqueid) ON DELETE CASCADE,
scrobble_count INTEGER NOT NULL,
PRIMARY KEY (userid))'
PRIMARY KEY (userid))',
'CREATE TABLE Domain_Blacklist (
domain TEXT,
expires INTEGER)'
);
foreach ($stage_one_queries as $query) {
......@@ -378,17 +383,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;");
......
<?php
/* GNUkebox -- a free software server for recording your listening habits
Copyright (C) 2009 Free Software Foundation, Inc
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
require_once($_SERVER['DOCUMENT_ROOT'] . '/config.php');
require_once($install_path . 'database.php');
/**
* Validate authentication using a web services token.
*
* @param string $username User name.
* @param string $api_key 32 character API key.
* @param string $sk Web services token.
* @return bool
*/
function check_web_auth($username, $api_key, $sk) {
global $adodb;
// Using the valid_api_key function from nixtape/2.0/index.php would be appropriate here
if (strlen($api_key) != 32) {
return false;
}
$query = 'SELECT username FROM Auth WHERE sk = ?';
$params = array($sk);
$result = $adodb->GetOne($query, $params);
if (!$result) {
// TODO: Log failures somewhere
return false;
}
return $result == $username;
}
/**
* Validates authentication using a standard authentication token.
*
* @param string $username User name.
* @param string $token Token.
* @param int $timestamp Timestamp in seconds since Epoch.
* @return bool
*/
function check_standard_auth($username, $token, $timestamp) {
// Validates authentication using a standard authentication token
global $adodb;
$query = 'SELECT password FROM Users WHERE lower(username) = lower(?)';
$params = array($username);
$pass = $adodb->GetOne($query, $params);
if (!$pass) {
// TODO: Log failures somewhere
return false;
}
$check_token = md5($pass . $timestamp);
return $check_token == $token;
}
/**
* Checks if the session is still valid.
*
* @param $sessionid Scrobble session id.
* @return bool True if session exists and is still valid.
*/
function check_session($sessionid) {
global $adodb;
$query = 'SELECT expires FROM Scrobble_Sessions WHERE sessionid = ? AND expires >= ?';
$params = array($sessionid, time());
$session = $adodb->GetOne($query, $params);
return (bool) $session;
}
<?php
/* GNUkebox -- a free software server for recording your listening habits
Copyright (C) 2009 Free Software Foundation, Inc
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
require_once($_SERVER['DOCUMENT_ROOT'] . '/config.php');
require_once($install_path . 'database.php');
require_once($install_path . 'scrobble-utils.php');
require_once($install_path . '1.x/auth-utils.php');
header('Content-Type: text/plain');
if (!isset($_POST['s']) || !isset($_POST['a']) || !isset($_POST['t'])) {
die("FAILED Required POST parameters are not set\n");
}
$sessionid = trim($_POST['s']);
if (!check_session($sessionid)) {
die("BADSESSION\n");
}
$t = array(
'artist' => $_POST['a'],
'track' => $_POST['t'],
'album' => $_POST['b'],
'tracknumber' => $_POST['n'],
'mbid' => $_POST['m'],
'duration' => $_POST['l'],
'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());
die('FAILED');
}
$adodb->CompleteTrans();
}
die("OK\n");
<?php
/* GNUkebox -- a free software server for recording your listening habits
Copyright (C) 2009 Free Software Foundation, Inc
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Implements the submissions handshake protocol 1.1 as detailed at: http://www.audioscrobbler.net/wiki/Protocol1.1.merged
*
* By sending the timestamp as the md5 challenge then creating the session key from md5(md5($password) . $timestamp) we can
* force a 1.1 client to give us a session key that can be used by the 1.2 protocol handler, so we only handle handshakes for
* 1.1 then pass all submissions off to the 1.2 handler.
*/
require_once($_SERVER['DOCUMENT_ROOT'] . '/config.php');
require_once($install_path . '1.x/auth-utils.php');
require_once($install_path . 'temp-utils.php');
header('Content-Type: text/plain');
$supported_protocols = array('1.1');
if (!isset($_REQUEST['p']) || !isset($_REQUEST['u']) || !isset($_REQUEST['c'])) {
die("FAILED\n");
}
$protocol = $_REQUEST['p'];
$username = $_REQUEST['u'];
$client = $_REQUEST['c'];
if (!in_array($protocol, $supported_protocols)) {
die("FAILED Unsupported protocol version\n");
}
$timestamp = time();
$select_query = 'SELECT uniqueid, password FROM Users WHERE lower(username) = lower(?)';
$select_params = array($username);
try {
list($userid, $password) = $adodb->GetRow($select_query, $select_params);
} catch (Exception $e) {
die('FAILED ' . $e->getMessage() . "\n");
}
if (!$password) {
die("BADUSER\n");
}
$sessionid = md5($password . $timestamp);
$insert_query = 'INSERT INTO Scrobble_Sessions(userid, sessionid, client, expires) VALUES (?,?,?,?)';
$insert_params = array($userid, $sessionid, $client, time() + 86400);
try {
$res = $adodb->Execute($insert_query, $insert_params);
} catch (Exception $e) {
die('FAILED ' . $e->getMessage() . "\n");
}
echo "UPTODATE\n";
echo $timestamp . "\n";
echo $base_url . "/1.x/submissions/1.2/\n";
echo "INTERVAL 1\n";
<?php
/* GNUkebox -- a free software server for recording your listening habits
Copyright (C) 2009 Free Software Foundation, Inc
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Implements the submissions handshake protocol as detailed at: http://www.last.fm/api/submissions
require_once($_SERVER['DOCUMENT_ROOT'] . '/config.php');
require_once($install_path . 'data/Server.php');
require_once($install_path . '1.x/auth-utils.php');
require_once($install_path . 'temp-utils.php');
header('Content-Type: text/plain');
$supported_protocols = array('1.2', '1.2.1');
if (!isset($_REQUEST['p']) || !isset($_REQUEST['u']) || !isset($_REQUEST['t']) || !isset($_REQUEST['a']) || !isset($_REQUEST['c'])) {
die("BADAUTH\n");
}
$protocol = $_REQUEST['p'];
$username = $_REQUEST['u'];
$timestamp = $_REQUEST['t'];
$auth_token = $_REQUEST['a'];
$client = $_REQUEST['c'];
if (!in_array($protocol, $supported_protocols)) {
die("FAILED Unsupported protocol version\n");
}
if (abs($timestamp - time()) > 300) {
die("BADTIME\n"); // let's try a 5-minute tolerance
}
if (isset($_REQUEST['api_key']) && isset($_REQUEST['sk'])) {
$authed = check_web_auth($username, $_REQUEST['api_key'], $_REQUEST['sk']);
} else {
$authed = check_standard_auth($username, $auth_token, $timestamp);
}
if (!$authed) {
die("BADAUTH\n");
}
$userid = username_to_uniqueid($username);
$session_id = Server::getScrobbleSession($userid, $client);
if ($session_id) {
echo "OK\n";
echo $session_id . "\n";
echo $base_url . "/1.x/nowplaying/1.2/\n";
echo $base_url . "/1.x/submissions/1.2/\n";
} else {
echo "FAILED\n";
}
<?php
/* GNUkebox -- a free software server for recording your listening habits
Copyright (C) 2009 Free Software Foundation, Inc
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
require_once($_SERVER['DOCUMENT_ROOT'] . '/config.php');
require_once($install_path . 'database.php');
require_once($install_path . 'scrobble-utils.php');
require_once($install_path . 'temp-utils.php');
if (!isset($_POST['s']) || !isset($_POST['a']) || !isset($_POST['t']) || !isset($_POST['i'])) {
die("FAILED Required POST parameters are not set\n");
}
if (empty($_POST['s']) || empty($_POST['a']) || empty($_POST['t']) || empty($_POST['i'])) {
die("FAILED Required POST parameters are empty\n");
}
if (!is_array($_POST['a']) || !is_array($_POST['t']) || !is_array($_POST['i'])) {
die("FAILED Track parameters must be arrays\n");
}
$session_id = $_POST['s'];
$userid = useridFromSID($session_id);
$artist = $_POST['a'];
$track = $_POST['t'];
$timestamp = $_POST['i'];
//$source = $_POST['o'];
//$rating = $_POST['r'];
$duration = $_POST['l'];
$album = $_POST['b'];
$tracknumber = $_POST['n'];
$mbid = $_POST['m'];
// Convert timestamps to unix time if needed
for ($i = 0; $i < count($timestamp); $i++) {
if (is_numeric($timestamp[$i])) {
$timestamp[$i] = (int) $timestamp[$i];
} else {
// 1.1 time format
date_default_timezone_set('UTC');
$timestamp[$i] = strtotime($timestamp[$i]);
}
}
$tracks_array = 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());
die('FAILED');
}
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());
die('FAILED');
}
}
$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'],
null,
null,
$t['duration_old']);
}
}
}
die("OK\n");
......@@ -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 = Server::getScrobbleSession($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'];