rb-lastfm-source.c 66 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350
  1. /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  2. *
  3. * arch-tag: Implementation of last.fm station source object
  4. *
  5. * Copyright (C) 2006 Matt Novenstern <fisxoj@gmail.com>
  6. * Copyright (C) 2008 Jonathan Matthew <jonathan@d14n.org>
  7. *
  8. * This program is free software; you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation; either version 2 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * The Rhythmbox authors hereby grant permission for non-GPL compatible
  14. * GStreamer plugins to be used and distributed together with GStreamer
  15. * and Rhythmbox. This permission is above and beyond the permissions granted
  16. * by the GPL license by which Rhythmbox is covered. If you modify this code
  17. * you may extend this exception to your version of the code, but you are not
  18. * obligated to do so. If you do not wish to do so, delete this exception
  19. * statement from your version.
  20. *
  21. * This program is distributed in the hope that it will be useful,
  22. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  23. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  24. * GNU General Public License for more details.
  25. *
  26. * You should have received a copy of the GNU General Public License
  27. * along with this program; if not, write to the Free Software
  28. * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  29. *
  30. */
  31. /* The author would like to extend thanks to Iain Holmes, author of Last Exit,
  32. * an alternative last.fm player written in C#, the code of which was
  33. * extraordinarily useful in the creation of this code
  34. */
  35. /* TODO List
  36. * - "recommendation radio" with percentage setting (0=obscure, 100=popular)
  37. * - watch username gconf entries, create/update neighbour station
  38. */
  39. #include <config.h>
  40. #include <string.h>
  41. #include <math.h>
  42. #include <unistd.h>
  43. #include <glib/gi18n.h>
  44. #include <glib/gstdio.h>
  45. #include <gtk/gtk.h>
  46. #include <glade/glade.h>
  47. #include <gconf/gconf-value.h>
  48. #include <totem-pl-parser.h>
  49. #include <libsoup/soup.h>
  50. #include "eel-gconf-extensions.h"
  51. #include "rb-proxy-config.h"
  52. #include "rb-preferences.h"
  53. #include "rb-audioscrobbler.h"
  54. #include "rb-lastfm-source.h"
  55. #include "rhythmdb-query-model.h"
  56. #include "rb-glade-helpers.h"
  57. #include "rb-stock-icons.h"
  58. #include "rb-entry-view.h"
  59. #include "rb-property-view.h"
  60. #include "rb-util.h"
  61. #include "rb-file-helpers.h"
  62. #include "rb-preferences.h"
  63. #include "rb-dialog.h"
  64. #include "rb-debug.h"
  65. #include "eel-gconf-extensions.h"
  66. #include "gedit-message-area.h"
  67. #include "rb-shell-player.h"
  68. #include "rb-play-order.h"
  69. #include "rb-lastfm-play-order.h"
  70. #define LASTFM_URL "ws.audioscrobbler.com"
  71. #define RB_LASTFM_PLATFORM "linux"
  72. #define RB_LASTFM_VERSION "1.5"
  73. #define USER_AGENT "Rhythmbox/" VERSION
  74. #define LASTFM_NO_COVER_IMAGE "http://cdn.last.fm/depth/catalogue/noimage/cover_med.gif"
  75. #define EPSILON (0.0001f)
  76. /* request queue stuff */
  77. typedef SoupMessage *(*CreateRequestFunc) (RBLastfmSource *source, RhythmDBEntry *entry);
  78. typedef void (*HandleResponseFunc) (RBLastfmSource *source, const char *body, RhythmDBEntry *entry);
  79. typedef struct
  80. {
  81. RBLastfmSource *source;
  82. RhythmDBEntry *entry;
  83. CreateRequestFunc create_request;
  84. HandleResponseFunc handle_response;
  85. const char *description;
  86. } RBLastfmAction;
  87. static void free_action (RBLastfmAction *action);
  88. static void queue_action (RBLastfmSource *source,
  89. CreateRequestFunc create_request,
  90. HandleResponseFunc handle_response,
  91. RhythmDBEntry *entry,
  92. const char *description);
  93. static void process_queue (RBLastfmSource *source);
  94. static void queue_handshake (RBLastfmSource *source);
  95. static void queue_change_station (RBLastfmSource *source, RhythmDBEntry *station);
  96. static void queue_get_playlist (RBLastfmSource *source, RhythmDBEntry *station);
  97. static void queue_get_playlist_and_skip (RBLastfmSource *source, RhythmDBEntry *station);
  98. static void queue_love_track (RBLastfmSource *source);
  99. static void queue_ban_track (RBLastfmSource *source);
  100. static void rb_lastfm_source_class_init (RBLastfmSourceClass *klass);
  101. static void rb_lastfm_source_init (RBLastfmSource *source);
  102. static GObject *rb_lastfm_source_constructor (GType type, guint n_construct_properties,
  103. GObjectConstructParam *construct_properties);
  104. static void rb_lastfm_source_finalize (GObject *object);
  105. static void rb_lastfm_source_set_property (GObject *object,
  106. guint prop_id,
  107. const GValue *value,
  108. GParamSpec *pspec);
  109. static void rb_lastfm_source_get_property (GObject *object,
  110. guint prop_id,
  111. GValue *value,
  112. GParamSpec *pspec);
  113. static void rb_lastfm_source_songs_view_sort_order_changed_cb (RBEntryView *view,
  114. RBLastfmSource *source);
  115. /* source-specific methods */
  116. static void rb_lastfm_source_drag_cb (GtkWidget *widget,
  117. GdkDragContext *dc,
  118. gint x, gint y,
  119. GtkSelectionData *selection_data,
  120. guint info, guint time,
  121. RBLastfmSource *source);
  122. static void rb_lastfm_source_station_selection_cb (RBEntryView *stations,
  123. RBLastfmSource *source);
  124. static void rb_lastfm_source_station_activated_cb (RBEntryView *stations,
  125. RhythmDBEntry *station,
  126. RBLastfmSource *source);
  127. static void rb_lastfm_source_dispose (GObject *object);
  128. /* RBSource implementation methods */
  129. static void impl_delete (RBSource *asource);
  130. static GList *impl_get_ui_actions (RBSource *source);
  131. static RBEntryView *impl_get_entry_view (RBSource *asource);
  132. static void impl_get_status (RBSource *asource, char **text, char **progress_text, float *progress);
  133. static gboolean impl_receive_drag (RBSource *source, GtkSelectionData *data);
  134. static void impl_activate (RBSource *source);
  135. static gboolean impl_show_popup (RBSource *source);
  136. static guint impl_want_uri (RBSource *source, const char *uri);
  137. static gboolean impl_add_uri (RBSource *source, const char *uri, const char *title, const char *genre);
  138. static RBSourceEOFType impl_handle_eos (RBSource *asource);
  139. static void rb_lastfm_source_new_station (const char *uri, const char *title, RBLastfmSource *source);
  140. static void rb_lastfm_source_love_track (GtkAction *action, RBLastfmSource *source);
  141. static void rb_lastfm_source_ban_track (GtkAction *action, RBLastfmSource *source);
  142. static void rb_lastfm_source_download_track (GtkAction *action, RBLastfmSource *source);
  143. static void rb_lastfm_source_delete_station (GtkAction *action, RBLastfmSource *source);
  144. static char *rb_lastfm_source_title_from_uri (const char *uri);
  145. static void rb_lastfm_source_add_station_cb (GtkButton *button, gpointer *data);
  146. static void rb_lastfm_source_entry_added_cb (RhythmDB *db, RhythmDBEntry *entry, RBLastfmSource *source);
  147. static void show_entry_popup (RBEntryView *view,
  148. gboolean over_entry,
  149. RBSource *source);
  150. static void playing_song_changed_cb (RBShellPlayer *player,
  151. RhythmDBEntry *entry,
  152. RBLastfmSource *source);
  153. static GValue * coverart_uri_request (RhythmDB *db,
  154. RhythmDBEntry *entry,
  155. RBLastfmSource *source);
  156. static void extra_metadata_gather_cb (RhythmDB *db,
  157. RhythmDBEntry *entry,
  158. RBStringValueMap *map,
  159. RBLastfmSource *source);
  160. static const char* const radio_options[][3] = {
  161. {N_("Similar Artists radio"), "lastfm://artist/%s/similarartists", N_("Artists similar to %s")},
  162. {N_("Tag radio"), "lastfm://globaltags/%s", N_("Tracks tagged with %s")},
  163. {N_("Artist Fan radio"), "lastfm://artist/%s/fans", N_("Artists liked by fans of %s")},
  164. {N_("Group radio"), "lastfm://group/%s", N_("Tracks liked by the %s group")},
  165. {N_("Neighbour radio"), "lastfm://user/%s/neighbours", N_("%s's Neighbour Radio")},
  166. {N_("Personal radio"), "lastfm://user/%s/personal", N_("%s's Personal Radio")},
  167. {N_("Loved tracks"), "lastfm://user/%s/loved", N_("%s's Loved Tracks")},
  168. {N_("Recommended tracks"), "lastfm://user/%s/recommended/100", N_("Tracks recommended to %s")},
  169. {NULL, NULL, NULL}
  170. };
  171. typedef struct
  172. {
  173. gboolean played; /* tracks can only be played once */
  174. char *image_url;
  175. char *track_auth; /* not used yet; for submission protocol 1.2 */
  176. char *download_url;
  177. } RBLastfmTrackEntryData;
  178. struct RBLastfmSourcePrivate
  179. {
  180. GtkWidget *main_box;
  181. GtkWidget *paned;
  182. GtkWidget *message_area;
  183. GtkWidget *txtbox;
  184. GtkWidget *typecombo;
  185. GtkWidget *config_widget;
  186. RhythmDB *db;
  187. GtkActionGroup *action_group;
  188. RBEntryView *stations;
  189. RBEntryView *tracks;
  190. RBShellPlayer *shell_player;
  191. RhythmDBEntryType station_entry_type;
  192. RhythmDBEntryType track_entry_type;
  193. char *session_id;
  194. RhythmDBEntry *current_station;
  195. RBPlayOrder *play_order;
  196. RhythmDBQueryModel *query_model;
  197. RhythmDBEntry *last_entry;
  198. gboolean subscriber;
  199. char *base_url;
  200. char *base_path;
  201. enum {
  202. NOT_CONNECTED = 0,
  203. CONNECTED,
  204. BANNED,
  205. LOGIN_FAILED,
  206. STATION_FAILED
  207. } state;
  208. guint notification_username_id;
  209. guint notification_password_id;
  210. GQueue *action_queue;
  211. gboolean request_outstanding;
  212. const char *request_description;
  213. const char *station_failed_reason;
  214. SoupSession *soup_session;
  215. RBProxyConfig *proxy_config;
  216. guint emit_coverart_id;
  217. };
  218. /* these are just for debug output */
  219. static const char *state_name[] = {
  220. "not logged in",
  221. "connected",
  222. "client is banned",
  223. "login failed",
  224. "station unavailable"
  225. };
  226. G_DEFINE_TYPE (RBLastfmSource, rb_lastfm_source, RB_TYPE_STREAMING_SOURCE);
  227. enum
  228. {
  229. PROP_0,
  230. PROP_ENTRY_TYPE,
  231. PROP_STATION_ENTRY_TYPE,
  232. PROP_PROXY_CONFIG,
  233. PROP_PLAY_ORDER
  234. };
  235. static GtkActionEntry rb_lastfm_source_actions [] =
  236. {
  237. { "LastfmLoveSong", "emblem-favorite", N_("Love"), NULL,
  238. N_("Mark this song as loved"),
  239. G_CALLBACK (rb_lastfm_source_love_track) },
  240. { "LastfmBanSong", GTK_STOCK_CANCEL, N_("Ban"), NULL,
  241. N_("Ban the current track from being played again"),
  242. G_CALLBACK (rb_lastfm_source_ban_track) },
  243. { "LastfmStationDelete", GTK_STOCK_DELETE, N_("Delete Station"), NULL,
  244. N_("Delete the selected station"),
  245. G_CALLBACK (rb_lastfm_source_delete_station) },
  246. { "LastfmDownloadSong", NULL, N_("Download song"), NULL,
  247. N_("Download this song"),
  248. G_CALLBACK (rb_lastfm_source_download_track) }
  249. };
  250. static const GtkTargetEntry lastfm_drag_types[] = {
  251. { "text/plain", 0, 0 },
  252. { "_NETSCAPE_URL", 0, 1 }
  253. };
  254. static void
  255. rb_lastfm_source_class_init (RBLastfmSourceClass *klass)
  256. {
  257. GObjectClass *object_class = G_OBJECT_CLASS (klass);
  258. RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
  259. object_class->finalize = rb_lastfm_source_finalize;
  260. object_class->dispose = rb_lastfm_source_dispose;
  261. object_class->constructor = rb_lastfm_source_constructor;
  262. object_class->set_property = rb_lastfm_source_set_property;
  263. object_class->get_property = rb_lastfm_source_get_property;
  264. source_class->impl_can_copy = (RBSourceFeatureFunc) rb_false_function;
  265. source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
  266. source_class->impl_can_pause = (RBSourceFeatureFunc) rb_false_function;
  267. source_class->impl_delete = impl_delete;
  268. source_class->impl_get_entry_view = impl_get_entry_view;
  269. source_class->impl_get_status = impl_get_status;
  270. source_class->impl_get_ui_actions = impl_get_ui_actions;
  271. source_class->impl_receive_drag = impl_receive_drag;
  272. source_class->impl_activate = impl_activate;
  273. source_class->impl_show_popup = impl_show_popup;
  274. source_class->impl_want_uri = impl_want_uri;
  275. source_class->impl_add_uri = impl_add_uri;
  276. source_class->impl_handle_eos = impl_handle_eos;
  277. source_class->impl_try_playlist = (RBSourceFeatureFunc) rb_false_function;
  278. g_object_class_install_property (object_class,
  279. PROP_ENTRY_TYPE,
  280. g_param_spec_boxed ("entry-type",
  281. "Entry type",
  282. "Entry type for last.fm tracks",
  283. RHYTHMDB_TYPE_ENTRY_TYPE,
  284. G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
  285. g_object_class_install_property (object_class,
  286. PROP_STATION_ENTRY_TYPE,
  287. g_param_spec_boxed ("station-entry-type",
  288. "Entry type",
  289. "Entry type for last.fm stations",
  290. RHYTHMDB_TYPE_ENTRY_TYPE,
  291. G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
  292. g_object_class_install_property (object_class,
  293. PROP_PROXY_CONFIG,
  294. g_param_spec_object ("proxy-config",
  295. "RBProxyConfig",
  296. "RBProxyConfig object",
  297. RB_TYPE_PROXY_CONFIG,
  298. G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
  299. g_object_class_override_property (object_class,
  300. PROP_PLAY_ORDER,
  301. "play-order");
  302. g_type_class_add_private (klass, sizeof (RBLastfmSourcePrivate));
  303. }
  304. static void
  305. on_gconf_changed_cb (GConfClient *client,
  306. guint cnxn_id,
  307. GConfEntry *entry,
  308. RBLastfmSource *source)
  309. {
  310. rb_debug ("GConf key updated: \"%s\"", entry->key);
  311. if (source->priv->state == CONNECTED) {
  312. return;
  313. }
  314. if (strcmp (entry->key, CONF_AUDIOSCROBBLER_USERNAME) == 0
  315. || strcmp (entry->key, CONF_AUDIOSCROBBLER_PASSWORD) == 0) {
  316. source->priv->state = NOT_CONNECTED;
  317. queue_handshake (source);
  318. } else {
  319. rb_debug ("Unhandled GConf key updated: \"%s\"", entry->key);
  320. }
  321. }
  322. static void
  323. rb_lastfm_source_init (RBLastfmSource *source)
  324. {
  325. source->priv = G_TYPE_INSTANCE_GET_PRIVATE ((source), RB_TYPE_LASTFM_SOURCE, RBLastfmSourcePrivate);
  326. source->priv->action_queue = g_queue_new ();
  327. source->priv->notification_username_id =
  328. eel_gconf_notification_add (CONF_AUDIOSCROBBLER_USERNAME,
  329. (GConfClientNotifyFunc) on_gconf_changed_cb,
  330. source);
  331. source->priv->notification_password_id =
  332. eel_gconf_notification_add (CONF_AUDIOSCROBBLER_PASSWORD,
  333. (GConfClientNotifyFunc) on_gconf_changed_cb,
  334. source);
  335. }
  336. static void
  337. rb_lastfm_source_dispose (GObject *object)
  338. {
  339. RBLastfmSource *source;
  340. source = RB_LASTFM_SOURCE (object);
  341. if (source->priv->db) {
  342. g_object_unref (source->priv->db);
  343. source->priv->db = NULL;
  344. }
  345. if (source->priv->proxy_config != NULL) {
  346. g_object_unref (source->priv->proxy_config);
  347. source->priv->proxy_config = NULL;
  348. }
  349. if (source->priv->soup_session != NULL) {
  350. soup_session_abort (source->priv->soup_session);
  351. g_object_unref (source->priv->soup_session);
  352. source->priv->soup_session = NULL;
  353. }
  354. if (source->priv->play_order != NULL) {
  355. g_object_unref (source->priv->play_order);
  356. source->priv->play_order = NULL;
  357. }
  358. if (source->priv->query_model != NULL) {
  359. g_object_unref (source->priv->query_model);
  360. source->priv->query_model = NULL;
  361. }
  362. if (source->priv->notification_username_id != 0) {
  363. eel_gconf_notification_remove (source->priv->notification_username_id);
  364. source->priv->notification_username_id = 0;
  365. }
  366. if (source->priv->notification_password_id != 0) {
  367. eel_gconf_notification_remove (source->priv->notification_password_id);
  368. source->priv->notification_password_id = 0;
  369. }
  370. /* kill entries here? */
  371. G_OBJECT_CLASS (rb_lastfm_source_parent_class)->dispose (object);
  372. }
  373. static void
  374. rb_lastfm_source_finalize (GObject *object)
  375. {
  376. RBLastfmSource *source;
  377. source = RB_LASTFM_SOURCE (object);
  378. /* get rid of any pending actions */
  379. g_queue_foreach (source->priv->action_queue,
  380. (GFunc) free_action,
  381. NULL);
  382. g_queue_free (source->priv->action_queue);
  383. g_free (source->priv->session_id);
  384. G_OBJECT_CLASS (rb_lastfm_source_parent_class)->finalize (object);
  385. }
  386. static GObject *
  387. rb_lastfm_source_constructor (GType type, guint n_construct_properties,
  388. GObjectConstructParam *construct_properties)
  389. {
  390. RBLastfmSource *source;
  391. RBLastfmSourceClass *klass;
  392. RBShell *shell;
  393. GtkWidget *editor_vbox;
  394. GtkWidget *editor_box;
  395. GtkWidget *add_button;
  396. GtkWidget *instructions;
  397. GPtrArray *query;
  398. RhythmDBQueryModel *station_query_model;
  399. int i;
  400. klass = RB_LASTFM_SOURCE_CLASS (g_type_class_peek (RB_TYPE_LASTFM_SOURCE));
  401. source = RB_LASTFM_SOURCE (G_OBJECT_CLASS (rb_lastfm_source_parent_class)
  402. ->constructor (type, n_construct_properties, construct_properties));
  403. g_object_get (G_OBJECT (source), "shell", &shell, NULL);
  404. g_object_get (G_OBJECT (shell),
  405. "db", &source->priv->db,
  406. "shell-player", &source->priv->shell_player,
  407. NULL);
  408. g_object_unref (G_OBJECT (shell));
  409. g_signal_connect_object (source->priv->db,
  410. "entry-added",
  411. G_CALLBACK (rb_lastfm_source_entry_added_cb),
  412. source, 0);
  413. g_signal_connect_object (source->priv->db,
  414. "entry-extra-metadata-request::" RHYTHMDB_PROP_COVER_ART_URI,
  415. G_CALLBACK (coverart_uri_request),
  416. source, 0);
  417. g_signal_connect_object (source->priv->db,
  418. "entry-extra-metadata-gather",
  419. G_CALLBACK (extra_metadata_gather_cb),
  420. source, 0);
  421. g_signal_connect_object (source->priv->shell_player,
  422. "playing-song-changed",
  423. G_CALLBACK (playing_song_changed_cb),
  424. source, 0);
  425. /* set up station tuner */
  426. editor_vbox = gtk_vbox_new (FALSE, 5);
  427. editor_box = gtk_hbox_new (FALSE, 5);
  428. /* awful */
  429. instructions = gtk_label_new (_("Enter the item to build a Last.fm station out of:"));
  430. g_object_set (instructions, "xalign", 0.0, NULL);
  431. add_button = gtk_button_new_with_label (_("Add"));
  432. g_signal_connect_object (G_OBJECT (add_button),
  433. "clicked",
  434. G_CALLBACK (rb_lastfm_source_add_station_cb),
  435. source, 0);
  436. source->priv->typecombo = gtk_combo_box_new_text ();
  437. for (i = 0; radio_options[i][0] != NULL; i++) {
  438. gtk_combo_box_append_text (GTK_COMBO_BOX (source->priv->typecombo), _(radio_options[i][0]));
  439. }
  440. gtk_combo_box_set_active (GTK_COMBO_BOX (source->priv->typecombo), 0);
  441. source->priv->txtbox = gtk_entry_new ();
  442. g_signal_connect_object (G_OBJECT (source->priv->txtbox),
  443. "activate",
  444. G_CALLBACK (rb_lastfm_source_add_station_cb),
  445. source, 0);
  446. gtk_box_pack_end (GTK_BOX (editor_box), add_button, TRUE, TRUE, 0);
  447. gtk_box_pack_end (GTK_BOX (editor_box), source->priv->txtbox, TRUE, TRUE, 0);
  448. gtk_box_pack_start (GTK_BOX (editor_box), source->priv->typecombo, TRUE, TRUE, 0);
  449. gtk_box_pack_end (GTK_BOX (editor_vbox), editor_box, TRUE, TRUE, 0);
  450. gtk_box_pack_end (GTK_BOX (editor_vbox), instructions, TRUE, TRUE, 0);
  451. source->priv->paned = gtk_vpaned_new ();
  452. /* set up stations view */
  453. source->priv->stations = rb_entry_view_new (source->priv->db,
  454. G_OBJECT (source->priv->shell_player),
  455. NULL, /* sort key? */
  456. FALSE, FALSE);
  457. rb_entry_view_append_column (source->priv->stations, RB_ENTRY_VIEW_COL_TITLE, TRUE);
  458. rb_entry_view_append_column (source->priv->stations, RB_ENTRY_VIEW_COL_RATING, TRUE);
  459. rb_entry_view_append_column (source->priv->stations, RB_ENTRY_VIEW_COL_LAST_PLAYED, TRUE);
  460. g_signal_connect_object (source->priv->stations,
  461. "sort-order-changed",
  462. G_CALLBACK (rb_lastfm_source_songs_view_sort_order_changed_cb),
  463. source, 0);
  464. g_signal_connect_object (source->priv->stations,
  465. "show_popup",
  466. G_CALLBACK (show_entry_popup),
  467. source, 0);
  468. g_signal_connect_object (source->priv->stations,
  469. "drag_data_received",
  470. G_CALLBACK (rb_lastfm_source_drag_cb),
  471. source, 0);
  472. g_signal_connect_object (source->priv->stations,
  473. "entry-activated",
  474. G_CALLBACK (rb_lastfm_source_station_activated_cb),
  475. source, 0);
  476. g_signal_connect_object (source->priv->stations,
  477. "selection-changed",
  478. G_CALLBACK (rb_lastfm_source_station_selection_cb),
  479. source, 0);
  480. gtk_drag_dest_set (GTK_WIDGET (source->priv->stations),
  481. GTK_DEST_DEFAULT_ALL,
  482. lastfm_drag_types, 2,
  483. GDK_ACTION_COPY | GDK_ACTION_MOVE);
  484. /* tracklist view */
  485. source->priv->tracks = rb_entry_view_new (source->priv->db,
  486. G_OBJECT (source->priv->shell_player),
  487. NULL,
  488. FALSE, FALSE);
  489. rb_entry_view_append_column (source->priv->tracks, RB_ENTRY_VIEW_COL_TITLE, TRUE);
  490. rb_entry_view_append_column (source->priv->tracks, RB_ENTRY_VIEW_COL_ARTIST, FALSE);
  491. rb_entry_view_append_column (source->priv->tracks, RB_ENTRY_VIEW_COL_ALBUM, FALSE);
  492. rb_entry_view_append_column (source->priv->tracks, RB_ENTRY_VIEW_COL_DURATION, FALSE);
  493. rb_entry_view_set_columns_clickable (source->priv->tracks, FALSE);
  494. gtk_paned_pack1 (GTK_PANED (source->priv->paned), GTK_WIDGET (source->priv->stations), TRUE, TRUE);
  495. gtk_paned_pack2 (GTK_PANED (source->priv->paned), GTK_WIDGET (source->priv->tracks), TRUE, TRUE);
  496. source->priv->main_box = gtk_vbox_new (FALSE, 5);
  497. gtk_box_pack_start (GTK_BOX (source->priv->main_box), editor_vbox, FALSE, FALSE, 5);
  498. gtk_box_pack_start (GTK_BOX (source->priv->main_box), source->priv->paned, TRUE, TRUE, 0);
  499. gtk_container_add (GTK_CONTAINER (source), source->priv->main_box);
  500. gtk_widget_show_all (GTK_WIDGET (source));
  501. source->priv->action_group = _rb_source_register_action_group (RB_SOURCE (source),
  502. "LastfmActions",
  503. rb_lastfm_source_actions,
  504. G_N_ELEMENTS (rb_lastfm_source_actions),
  505. source);
  506. /* play order */
  507. source->priv->play_order = rb_lastfm_play_order_new (source->priv->shell_player);
  508. /* set up station query model */
  509. query = rhythmdb_query_parse (source->priv->db,
  510. RHYTHMDB_QUERY_PROP_EQUALS,
  511. RHYTHMDB_PROP_TYPE,
  512. source->priv->station_entry_type,
  513. RHYTHMDB_QUERY_END);
  514. station_query_model = rhythmdb_query_model_new_empty (source->priv->db);
  515. rhythmdb_do_full_query_parsed (source->priv->db,
  516. RHYTHMDB_QUERY_RESULTS (station_query_model),
  517. query);
  518. rhythmdb_query_free (query);
  519. rb_entry_view_set_model (source->priv->stations, station_query_model);
  520. g_object_unref (station_query_model);
  521. source->priv->query_model = rhythmdb_query_model_new_empty (source->priv->db);
  522. rb_entry_view_set_model (source->priv->tracks, source->priv->query_model);
  523. g_object_set (source, "query-model", source->priv->query_model, NULL);
  524. return G_OBJECT (source);
  525. }
  526. static void
  527. rb_lastfm_source_set_property (GObject *object,
  528. guint prop_id,
  529. const GValue *value,
  530. GParamSpec *pspec)
  531. {
  532. RBLastfmSource *source = RB_LASTFM_SOURCE (object);
  533. switch (prop_id) {
  534. case PROP_ENTRY_TYPE:
  535. source->priv->track_entry_type = g_value_get_boxed (value);
  536. break;
  537. case PROP_STATION_ENTRY_TYPE:
  538. source->priv->station_entry_type = g_value_get_boxed (value);
  539. break;
  540. case PROP_PROXY_CONFIG:
  541. source->priv->proxy_config = g_value_get_object (value);
  542. g_object_ref (G_OBJECT (source->priv->proxy_config));
  543. break;
  544. default:
  545. G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
  546. break;
  547. }
  548. }
  549. static void
  550. rb_lastfm_source_get_property (GObject *object,
  551. guint prop_id,
  552. GValue *value,
  553. GParamSpec *pspec)
  554. {
  555. RBLastfmSource *source = RB_LASTFM_SOURCE (object);
  556. switch (prop_id) {
  557. case PROP_ENTRY_TYPE:
  558. g_value_set_boxed (value, source->priv->track_entry_type);
  559. break;
  560. case PROP_STATION_ENTRY_TYPE:
  561. g_value_set_boxed (value, source->priv->station_entry_type);
  562. break;
  563. case PROP_PLAY_ORDER:
  564. g_value_set_object (value, source->priv->play_order);
  565. break;
  566. default:
  567. G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
  568. break;
  569. }
  570. }
  571. /* entry data stuff */
  572. static void
  573. destroy_track_data (RhythmDBEntry *entry, gpointer meh)
  574. {
  575. RBLastfmTrackEntryData *data;
  576. data = RHYTHMDB_ENTRY_GET_TYPE_DATA(entry, RBLastfmTrackEntryData);
  577. g_free (data->image_url);
  578. g_free (data->track_auth);
  579. g_free (data->download_url);
  580. }
  581. RBSource *
  582. rb_lastfm_source_new (RBPlugin *plugin,
  583. RBShell *shell)
  584. {
  585. RBSource *source;
  586. RBProxyConfig *proxy_config;
  587. RhythmDBEntryType station_entry_type;
  588. RhythmDBEntryType track_entry_type;
  589. RhythmDB *db;
  590. g_object_get (G_OBJECT (shell), "db", &db, NULL);
  591. /* register entry types if they're not already registered */
  592. station_entry_type = rhythmdb_entry_type_get_by_name (db, "lastfm-station");
  593. if (station_entry_type == RHYTHMDB_ENTRY_TYPE_INVALID) {
  594. station_entry_type = rhythmdb_entry_register_type (db, "lastfm-station");
  595. station_entry_type->save_to_disk = TRUE;
  596. station_entry_type->can_sync_metadata = (RhythmDBEntryCanSyncFunc) rb_true_function;
  597. station_entry_type->sync_metadata = (RhythmDBEntrySyncFunc) rb_null_function;
  598. station_entry_type->get_playback_uri = (RhythmDBEntryStringFunc) rb_null_function; /* can't play stations, exactly */
  599. station_entry_type->category = RHYTHMDB_ENTRY_CONTAINER;
  600. }
  601. track_entry_type = rhythmdb_entry_type_get_by_name (db, "lastfm-track");
  602. if (track_entry_type == RHYTHMDB_ENTRY_TYPE_INVALID) {
  603. track_entry_type = rhythmdb_entry_register_type (db, "lastfm-track");
  604. track_entry_type->save_to_disk = FALSE;
  605. track_entry_type->category = RHYTHMDB_ENTRY_NORMAL;
  606. track_entry_type->entry_type_data_size = sizeof (RBLastfmTrackEntryData);
  607. track_entry_type->pre_entry_destroy = destroy_track_data;
  608. }
  609. g_object_get (G_OBJECT (shell), "proxy-config", &proxy_config, NULL);
  610. source = RB_SOURCE (g_object_new (RB_TYPE_LASTFM_SOURCE,
  611. "plugin", plugin,
  612. "name", _("Last.fm"),
  613. "shell", shell,
  614. "station-entry-type", station_entry_type,
  615. "entry-type", track_entry_type,
  616. "proxy-config", proxy_config,
  617. "source-group", RB_SOURCE_GROUP_LIBRARY,
  618. NULL));
  619. rb_shell_register_entry_type_for_source (shell, source, track_entry_type);
  620. g_object_unref (db);
  621. g_object_unref (proxy_config);
  622. return source;
  623. }
  624. static GList*
  625. impl_get_ui_actions (RBSource *source)
  626. {
  627. GList *actions = NULL;
  628. actions = g_list_prepend (actions, g_strdup ("LastfmLoveSong"));
  629. actions = g_list_prepend (actions, g_strdup ("LastfmBanSong"));
  630. return actions;
  631. }
  632. static RBEntryView *
  633. impl_get_entry_view (RBSource *asource)
  634. {
  635. RBLastfmSource *source = RB_LASTFM_SOURCE (asource);
  636. return source->priv->tracks;
  637. }
  638. static void
  639. set_message_area_text_and_icon (RBLastfmSource *source,
  640. const char *icon_stock_id,
  641. const char *primary_text,
  642. const char *secondary_text)
  643. {
  644. GtkWidget *hbox_content;
  645. GtkWidget *image;
  646. GtkWidget *vbox;
  647. char *primary_markup;
  648. char *secondary_markup;
  649. GtkWidget *primary_label;
  650. GtkWidget *secondary_label;
  651. hbox_content = gtk_hbox_new (FALSE, 8);
  652. gtk_widget_show (hbox_content);
  653. image = gtk_image_new_from_stock (icon_stock_id, GTK_ICON_SIZE_DIALOG);
  654. gtk_widget_show (image);
  655. gtk_box_pack_start (GTK_BOX (hbox_content), image, FALSE, FALSE, 0);
  656. gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0);
  657. vbox = gtk_vbox_new (FALSE, 6);
  658. gtk_widget_show (vbox);
  659. gtk_box_pack_start (GTK_BOX (hbox_content), vbox, TRUE, TRUE, 0);
  660. primary_markup = g_strdup_printf ("<b>%s</b>", primary_text);
  661. primary_label = gtk_label_new (primary_markup);
  662. g_free (primary_markup);
  663. gtk_widget_show (primary_label);
  664. gtk_box_pack_start (GTK_BOX (vbox), primary_label, TRUE, TRUE, 0);
  665. gtk_label_set_use_markup (GTK_LABEL (primary_label), TRUE);
  666. gtk_label_set_line_wrap (GTK_LABEL (primary_label), TRUE);
  667. gtk_misc_set_alignment (GTK_MISC (primary_label), 0, 0.5);
  668. GTK_WIDGET_SET_FLAGS (primary_label, GTK_CAN_FOCUS);
  669. gtk_label_set_selectable (GTK_LABEL (primary_label), TRUE);
  670. if (secondary_text != NULL) {
  671. secondary_markup = g_strdup_printf ("<small>%s</small>",
  672. secondary_text);
  673. secondary_label = gtk_label_new (secondary_markup);
  674. g_free (secondary_markup);
  675. gtk_widget_show (secondary_label);
  676. gtk_box_pack_start (GTK_BOX (vbox), secondary_label, TRUE, TRUE, 0);
  677. GTK_WIDGET_SET_FLAGS (secondary_label, GTK_CAN_FOCUS);
  678. gtk_label_set_use_markup (GTK_LABEL (secondary_label), TRUE);
  679. gtk_label_set_line_wrap (GTK_LABEL (secondary_label), TRUE);
  680. gtk_label_set_selectable (GTK_LABEL (secondary_label), TRUE);
  681. gtk_misc_set_alignment (GTK_MISC (secondary_label), 0, 0.5);
  682. }
  683. gtk_widget_show (source->priv->message_area);
  684. gedit_message_area_set_contents (GEDIT_MESSAGE_AREA (source->priv->message_area),
  685. hbox_content);
  686. }
  687. static void
  688. set_message_area (RBLastfmSource *source,
  689. GtkWidget *area)
  690. {
  691. if (source->priv->message_area == area) {
  692. return;
  693. }
  694. if (source->priv->message_area) {
  695. gtk_widget_destroy (source->priv->message_area);
  696. }
  697. source->priv->message_area = area;
  698. if (area == NULL) {
  699. return;
  700. }
  701. gtk_box_pack_end (GTK_BOX (source->priv->main_box),
  702. source->priv->message_area,
  703. FALSE, FALSE, 0);
  704. #if 0
  705. gtk_box_reorder_child (GTK_BOX (source->priv->view_box),
  706. source->priv->message_area, 0);
  707. #endif
  708. g_object_add_weak_pointer (G_OBJECT (source->priv->message_area),
  709. (gpointer) &(source->priv->message_area));
  710. }
  711. static void
  712. on_message_area_response (GeditMessageArea *area,
  713. int response_id,
  714. RBLastfmSource *source)
  715. {
  716. RBPlugin *plugin;
  717. GtkWidget *dialog;
  718. g_object_get (source, "plugin", &plugin, NULL);
  719. dialog = rb_plugin_create_configure_dialog (plugin);
  720. g_object_unref (plugin);
  721. }
  722. static void
  723. show_error_message (RBLastfmSource *source,
  724. const char *primary_text,
  725. const char *secondary_text)
  726. {
  727. GtkWidget *area;
  728. if (source->priv->message_area != NULL) {
  729. return;
  730. }
  731. area = gedit_message_area_new_with_buttons (_("Account Settings"),
  732. GTK_RESPONSE_ACCEPT,
  733. NULL);
  734. set_message_area (source, area);
  735. set_message_area_text_and_icon (source,
  736. "gtk-dialog-error",
  737. primary_text,
  738. secondary_text);
  739. g_signal_connect (area,
  740. "response",
  741. G_CALLBACK (on_message_area_response),
  742. source);
  743. }
  744. static void
  745. update_message_area (RBLastfmSource *source)
  746. {
  747. char *primary_text;
  748. char *secondary_text;
  749. primary_text = NULL;
  750. secondary_text = NULL;
  751. switch (source->priv->state) {
  752. case LOGIN_FAILED:
  753. primary_text = g_strdup (_("Account details are needed before you can connect. Check your settings."));
  754. break;
  755. case BANNED:
  756. primary_text = g_strdup (_("This version of Rhythmbox has been banned from Last.fm."));
  757. break;
  758. case STATION_FAILED:
  759. primary_text = g_strdup (_("Unable to connect"));
  760. secondary_text = g_strdup (source->priv->station_failed_reason);
  761. break;
  762. case NOT_CONNECTED:
  763. case CONNECTED:
  764. set_message_area (source, NULL);
  765. break;
  766. default:
  767. g_assert_not_reached ();
  768. break;
  769. }
  770. if (primary_text != NULL) {
  771. show_error_message (source, primary_text, secondary_text);
  772. }
  773. }
  774. static void
  775. impl_get_status (RBSource *asource, char **text, char **progress_text, float *progress)
  776. {
  777. RBLastfmSource *source = RB_LASTFM_SOURCE (asource);
  778. RhythmDBQueryModel *model;
  779. switch (source->priv->state) {
  780. case LOGIN_FAILED:
  781. case BANNED:
  782. case STATION_FAILED:
  783. break;
  784. case NOT_CONNECTED:
  785. case CONNECTED:
  786. g_object_get (asource, "query-model", &model, NULL);
  787. *text = rhythmdb_query_model_compute_status_normal (model, "%d songs", "%d songs");
  788. g_object_unref (model);
  789. break;
  790. }
  791. update_message_area (source);
  792. rb_streaming_source_get_progress (RB_STREAMING_SOURCE (source), progress_text, progress);
  793. /* pulse progressbar if there's something going on */
  794. if (source->priv->request_outstanding && fabsf (*progress) < EPSILON) {
  795. *progress_text = g_strdup (source->priv->request_description);
  796. *progress = -1.0f;
  797. }
  798. }
  799. static void
  800. impl_delete (RBSource *asource)
  801. {
  802. RBLastfmSource *source = RB_LASTFM_SOURCE (asource);
  803. GList *sel;
  804. GList *l;
  805. /* this one is meant to delete tracks.. but maybe that shouldn't be possible? */
  806. sel = rb_entry_view_get_selected_entries (source->priv->tracks);
  807. for (l = sel; l != NULL; l = g_list_next (l)) {
  808. RhythmDBEntry *track;
  809. RBLastfmTrackEntryData *track_data;
  810. track = (RhythmDBEntry *)l->data;
  811. track_data = RHYTHMDB_ENTRY_GET_TYPE_DATA (track, RBLastfmTrackEntryData);
  812. rhythmdb_entry_delete (source->priv->db, track);
  813. }
  814. rhythmdb_commit (source->priv->db);
  815. g_list_foreach (sel, (GFunc)rhythmdb_entry_unref, NULL);
  816. g_list_free (sel);
  817. }
  818. static void
  819. rb_lastfm_source_songs_view_sort_order_changed_cb (RBEntryView *view,
  820. RBLastfmSource *source)
  821. {
  822. rb_debug ("sort order changed");
  823. rb_entry_view_resort_model (view);
  824. }
  825. static void
  826. rb_lastfm_source_new_station (const char *uri, const char *title, RBLastfmSource *source)
  827. {
  828. RhythmDBEntry *entry;
  829. GValue v = {0,};
  830. rb_debug ("adding lastfm: %s, %s", uri, title);
  831. entry = rhythmdb_entry_lookup_by_location (source->priv->db, uri);
  832. if (entry) {
  833. rb_debug ("uri %s already in db", uri);
  834. return;
  835. }
  836. entry = rhythmdb_entry_new (source->priv->db, source->priv->station_entry_type, uri);
  837. g_value_init (&v, G_TYPE_STRING);
  838. g_value_set_string (&v, title);
  839. rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_TITLE, &v);
  840. g_value_unset (&v);
  841. g_value_init (&v, G_TYPE_DOUBLE);
  842. g_value_set_double (&v, 0.0);
  843. rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_RATING, &v);
  844. rhythmdb_commit (source->priv->db);
  845. }
  846. /* cover art bits */
  847. static const char *
  848. get_image_url_for_entry (RBLastfmSource *source, RhythmDBEntry *entry)
  849. {
  850. RBLastfmTrackEntryData *data;
  851. if (entry == NULL) {
  852. return NULL;
  853. }
  854. if (rhythmdb_entry_get_entry_type (entry) != source->priv->track_entry_type) {
  855. return NULL;
  856. }
  857. data = RHYTHMDB_ENTRY_GET_TYPE_DATA(entry, RBLastfmTrackEntryData);
  858. return data->image_url;
  859. }
  860. static GValue *
  861. coverart_uri_request (RhythmDB *db, RhythmDBEntry *entry, RBLastfmSource *source)
  862. {
  863. const char *image_url;
  864. image_url = get_image_url_for_entry (source, entry);
  865. if (image_url != NULL) {
  866. GValue *v;
  867. v = g_new0 (GValue, 1);
  868. g_value_init (v, G_TYPE_STRING);
  869. rb_debug ("requested cover image %s", image_url);
  870. g_value_set_string (v, image_url);
  871. return v;
  872. }
  873. return NULL;
  874. }
  875. static void
  876. extra_metadata_gather_cb (RhythmDB *db, RhythmDBEntry *entry, RBStringValueMap *map, RBLastfmSource *source)
  877. {
  878. const char *image_url;
  879. image_url = get_image_url_for_entry (source, entry);
  880. if (image_url != NULL) {
  881. GValue v = {0,};
  882. g_value_init (&v, G_TYPE_STRING);
  883. g_value_set_string (&v, image_url);
  884. rb_debug ("gathered cover image %s", image_url);
  885. rb_string_value_map_set (map, "rb:coverArt-uri", &v);
  886. g_value_unset (&v);
  887. }
  888. }
  889. static gboolean
  890. emit_coverart_uri_cb (RBLastfmSource *source)
  891. {
  892. RhythmDBEntry *entry;
  893. const char *image_url;
  894. source->priv->emit_coverart_id = 0;
  895. entry = rb_shell_player_get_playing_entry (source->priv->shell_player);
  896. image_url = get_image_url_for_entry (source, entry);
  897. if (image_url != NULL) {
  898. GValue v = {0,};
  899. g_value_init (&v, G_TYPE_STRING);
  900. g_value_set_string (&v, image_url);
  901. rhythmdb_emit_entry_extra_metadata_notify (source->priv->db,
  902. entry,
  903. "rb:coverArt-uri",
  904. &v);
  905. g_value_unset (&v);
  906. }
  907. return FALSE;
  908. }
  909. static void
  910. playing_song_changed_cb (RBShellPlayer *player,
  911. RhythmDBEntry *entry,
  912. RBLastfmSource *source)
  913. {
  914. GtkAction *action;
  915. /* re-enable love/ban */
  916. action = gtk_action_group_get_action (source->priv->action_group, "LastfmLoveSong");
  917. gtk_action_set_sensitive (action, TRUE);
  918. action = gtk_action_group_get_action (source->priv->action_group, "LastfmBanSong");
  919. gtk_action_set_sensitive (action, TRUE);
  920. if (source->priv->emit_coverart_id != 0) {
  921. g_source_remove (source->priv->emit_coverart_id);
  922. source->priv->emit_coverart_id = 0;
  923. }
  924. if (entry != NULL && rhythmdb_entry_get_entry_type (entry) == source->priv->track_entry_type) {
  925. /* look through the playlist for the current station.
  926. * if all tracks have been played, update the playlist.
  927. */
  928. RBLastfmTrackEntryData *track_data;
  929. track_data = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RBLastfmTrackEntryData);
  930. if (track_data->played == FALSE) {
  931. if (source->priv->current_station != NULL && entry == source->priv->last_entry) {
  932. GList *sel;
  933. RhythmDBEntry *selected_station = NULL;
  934. /* if a new station has been selected, change station before
  935. * refreshing the playlist.
  936. */
  937. sel = rb_entry_view_get_selected_entries (source->priv->stations);
  938. if (sel != NULL) {
  939. selected_station = (RhythmDBEntry *)sel->data;
  940. if (selected_station != source->priv->current_station) {
  941. rb_debug ("changing to station %s",
  942. rhythmdb_entry_get_string (selected_station, RHYTHMDB_PROP_LOCATION));
  943. queue_change_station (source, selected_station);
  944. }
  945. queue_get_playlist (source, selected_station);
  946. } else {
  947. queue_get_playlist (source, source->priv->current_station);
  948. }
  949. g_list_foreach (sel, (GFunc)rhythmdb_entry_unref, NULL);
  950. g_list_free (sel);
  951. }
  952. track_data->played = TRUE;
  953. }
  954. /* emit cover art notification */
  955. source->priv->emit_coverart_id = g_idle_add ((GSourceFunc) emit_coverart_uri_cb, source);
  956. }
  957. }
  958. static void
  959. rb_lastfm_source_love_track (GtkAction *run_action, RBLastfmSource *source)
  960. {
  961. GtkAction *action;
  962. queue_love_track (source);
  963. /* disable love/ban */
  964. action = gtk_action_group_get_action (source->priv->action_group, "LastfmLoveSong");
  965. gtk_action_set_sensitive (action, FALSE);
  966. action = gtk_action_group_get_action (source->priv->action_group, "LastfmBanSong");
  967. gtk_action_set_sensitive (action, FALSE);
  968. }
  969. static void
  970. rb_lastfm_source_ban_track (GtkAction *run_action, RBLastfmSource *source)
  971. {
  972. GtkAction *action;
  973. queue_ban_track (source);
  974. /* disable love/ban */
  975. action = gtk_action_group_get_action (source->priv->action_group, "LastfmLoveSong");
  976. gtk_action_set_sensitive (action, FALSE);
  977. action = gtk_action_group_get_action (source->priv->action_group, "LastfmBanSong");
  978. gtk_action_set_sensitive (action, FALSE);
  979. rb_shell_player_do_next (source->priv->shell_player, NULL);
  980. }
  981. static void
  982. rb_lastfm_source_delete_station (GtkAction *run_action, RBLastfmSource *asource)
  983. {
  984. RBLastfmSource *source = RB_LASTFM_SOURCE (asource);
  985. GList *sel;
  986. GList *l;
  987. sel = rb_entry_view_get_selected_entries (source->priv->stations);
  988. for (l = sel; l != NULL; l = g_list_next (l)) {
  989. rhythmdb_entry_delete (source->priv->db, l->data);
  990. }
  991. rhythmdb_commit (source->priv->db);
  992. g_list_foreach (sel, (GFunc)rhythmdb_entry_unref, NULL);
  993. g_list_free (sel);
  994. }
  995. static void
  996. rb_lastfm_source_download_track (GtkAction *action, RBLastfmSource *source)
  997. {
  998. /* etc. */
  999. }
  1000. static void
  1001. rb_lastfm_source_drag_cb (GtkWidget *widget,
  1002. GdkDragContext *dc,
  1003. gint x, gint y,
  1004. GtkSelectionData *selection_data,
  1005. guint info, guint time,
  1006. RBLastfmSource *source)
  1007. {
  1008. impl_receive_drag (RB_SOURCE (source), selection_data);
  1009. }
  1010. static gboolean
  1011. impl_receive_drag (RBSource *asource, GtkSelectionData *selection_data)
  1012. {
  1013. char *uri;
  1014. char *title = NULL;
  1015. RBLastfmSource *source = RB_LASTFM_SOURCE (asource);
  1016. uri = (char *)selection_data->data;
  1017. rb_debug ("parsing uri %s", uri);
  1018. if (strstr (uri, "lastfm://") == NULL)
  1019. return FALSE;
  1020. title = rb_lastfm_source_title_from_uri (uri);
  1021. rb_lastfm_source_new_station (uri, title, source);
  1022. return TRUE;
  1023. }
  1024. static void
  1025. rb_lastfm_source_station_activated_cb (RBEntryView *stations, RhythmDBEntry *station, RBLastfmSource *source)
  1026. {
  1027. queue_change_station (source, station);
  1028. queue_get_playlist_and_skip (source, station);
  1029. }
  1030. static void
  1031. rb_lastfm_source_station_selection_cb (RBEntryView *stations,
  1032. RBLastfmSource *source)
  1033. {
  1034. GList *sel;
  1035. RhythmDBEntry *selected;
  1036. sel = rb_entry_view_get_selected_entries (stations);
  1037. if (sel == NULL) {
  1038. return;
  1039. }
  1040. selected = (RhythmDBEntry *)sel->data;
  1041. if (source->priv->current_station == selected) {
  1042. rb_debug ("station %s already selected",
  1043. rhythmdb_entry_get_string (selected, RHYTHMDB_PROP_LOCATION));
  1044. } else {
  1045. rb_debug ("station %s selected",
  1046. rhythmdb_entry_get_string (selected, RHYTHMDB_PROP_LOCATION));
  1047. /* if this is the first station selected, update the playlist */
  1048. if (source->priv->last_entry == NULL) {
  1049. queue_change_station (source, selected);
  1050. queue_get_playlist (source, selected);
  1051. }
  1052. }
  1053. g_list_foreach (sel, (GFunc)rhythmdb_entry_unref, NULL);
  1054. g_list_free (sel);
  1055. }
  1056. static char *
  1057. rb_lastfm_source_title_from_uri (const char *uri)
  1058. {
  1059. char *title = NULL;
  1060. char *unesc_title;
  1061. gchar **data = g_strsplit (uri, "/", 0);
  1062. if (strstr (uri, "globaltags") != NULL)
  1063. title = g_strdup_printf (_("Global Tag %s"), data[3]);
  1064. if (title == NULL && strcmp (data[2], "artist") == 0) {
  1065. /* Check if the station is from an artist page, if not, it is a similar
  1066. * artist station, and the server should return a name that change_station
  1067. * will handle for us.
  1068. */
  1069. if (data[4] != NULL) {
  1070. if (strcmp (data[4], "similarartists") == 0) {
  1071. title = g_strdup_printf (_("Artists similar to %s"), data[3]);
  1072. } if (strcmp (data[4], "fans") == 0) {
  1073. title = g_strdup_printf (_("Artists liked by fans of %s"), data[3]);
  1074. }
  1075. }
  1076. }
  1077. if (title == NULL && strcmp (data[2], "user") == 0) {
  1078. if (strcmp (data[4], "neighbours") == 0) {
  1079. title = g_strdup_printf (_("%s's Neighbour Radio"), data[3]);
  1080. } else if (strcmp (data[4], "recommended") == 0) {
  1081. title = g_strdup_printf (_("%s's Recommended Radio: %s percent"), data[3], data[5]);
  1082. } else if (strcmp (data[4], "personal") == 0) {
  1083. title = g_strdup_printf (_("%s's Personal Radio"), data[3]);
  1084. } else if (strcmp (data[4], "loved") == 0) {
  1085. title = g_strdup_printf (_("%s's Loved Tracks"), data[3]);
  1086. } else if (strcmp (data[4], "playlist") == 0) {
  1087. title = g_strdup_printf (_("%s's Playlist"), data[3]);
  1088. }
  1089. }
  1090. if (title == NULL && strcmp (data[2], "usertags") == 0) {
  1091. /* Translators: variables are 1: user name, 2: tag name; for user tag radio */
  1092. title = g_strdup_printf (_("%s's %s Radio"), data[3], data[4]);
  1093. }
  1094. if (title == NULL && strcmp(data[2], "group") == 0) {
  1095. title = g_strdup_printf (_("%s Group Radio"), data[3]);
  1096. }
  1097. if (title == NULL) {
  1098. title = g_strstrip (g_strdup (uri));
  1099. }
  1100. g_strfreev (data);
  1101. unesc_title = g_uri_unescape_string (title, NULL);
  1102. g_free (title);
  1103. return unesc_title;
  1104. }
  1105. static void
  1106. rb_lastfm_source_entry_added_cb (RhythmDB *db,
  1107. RhythmDBEntry *entry,
  1108. RBLastfmSource *source)
  1109. {
  1110. const char *title;
  1111. const char *genre;
  1112. GValue v = {0,};
  1113. if (rhythmdb_entry_get_entry_type (entry) != source->priv->station_entry_type)
  1114. return;
  1115. /* move station name from genre to title */
  1116. title = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE);
  1117. if (title != NULL && title[0] != '\0')
  1118. return;
  1119. genre = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_GENRE);
  1120. if (genre == NULL || genre[0] == '\0')
  1121. return;
  1122. g_value_init (&v, G_TYPE_STRING);
  1123. g_value_set_string (&v, genre);
  1124. rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_TITLE, &v);
  1125. g_value_unset (&v);
  1126. g_value_init (&v, G_TYPE_STRING);
  1127. g_value_set_string (&v, "");
  1128. rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_GENRE, &v);
  1129. g_value_unset (&v);
  1130. /* recursive commit? really? */
  1131. rhythmdb_commit (source->priv->db);
  1132. }
  1133. static void
  1134. rb_lastfm_source_add_station_cb (GtkButton *button, gpointer *data)
  1135. {
  1136. RBLastfmSource *source = RB_LASTFM_SOURCE (data);
  1137. const gchar *add;
  1138. char *title;
  1139. char *uri;
  1140. int selection;
  1141. add = gtk_entry_get_text (GTK_ENTRY (source->priv->txtbox));
  1142. if (add == NULL || *add == '\0')
  1143. return;
  1144. selection = gtk_combo_box_get_active (GTK_COMBO_BOX (source->priv->typecombo));
  1145. uri = g_strdup_printf(radio_options[selection][1], add);
  1146. title = g_strdup_printf(radio_options[selection][2], add);
  1147. rb_lastfm_source_new_station (uri, title, source);
  1148. gtk_entry_set_text (GTK_ENTRY (source->priv->txtbox), "");
  1149. g_free(uri);
  1150. g_free(title);
  1151. }
  1152. static void
  1153. impl_activate (RBSource *source)
  1154. {
  1155. queue_handshake (RB_LASTFM_SOURCE (source));
  1156. }
  1157. static gboolean
  1158. impl_show_popup (RBSource *source)
  1159. {
  1160. /*_rb_source_show_popup (source, "/LastfmSourcePopup");*/
  1161. return TRUE;
  1162. }
  1163. static void
  1164. show_entry_popup (RBEntryView *view,
  1165. gboolean over_entry,
  1166. RBSource *source)
  1167. {
  1168. if (over_entry) {
  1169. _rb_source_show_popup (source, "/LastfmStationViewPopup");
  1170. } else {
  1171. rb_source_show_popup (source);
  1172. }
  1173. }
  1174. guint
  1175. impl_want_uri (RBSource *source, const char *uri)
  1176. {
  1177. if (g_str_has_prefix (uri, "lastfm://"))
  1178. return 100;
  1179. return 0;
  1180. }
  1181. static gboolean
  1182. impl_add_uri (RBSource *source, const char *uri, const char *title, const char *genre)
  1183. {
  1184. char *name;
  1185. if (strstr (uri, "lastfm://") == NULL)
  1186. return FALSE;
  1187. name = rb_lastfm_source_title_from_uri (uri);
  1188. rb_lastfm_source_new_station (uri, name, RB_LASTFM_SOURCE (source));
  1189. return TRUE;
  1190. }
  1191. static RBSourceEOFType
  1192. impl_handle_eos (RBSource *asource)
  1193. {
  1194. return RB_SOURCE_EOF_NEXT;
  1195. }
  1196. /* request queue */
  1197. static void
  1198. free_action (RBLastfmAction *action)
  1199. {
  1200. if (action->entry != NULL) {
  1201. rhythmdb_entry_unref (action->entry);
  1202. }
  1203. g_free (action);
  1204. }
  1205. static void
  1206. http_response_cb (SoupSession *session, SoupMessage *req, gpointer user_data)
  1207. {
  1208. RBLastfmAction *action = (RBLastfmAction *)user_data;
  1209. RBLastfmSource *source = action->source;
  1210. char *free_body;
  1211. const char *body;
  1212. free_body = NULL;
  1213. if (req->response_body->length == 0) {
  1214. rb_debug ("server failed to respond");
  1215. body = NULL;
  1216. } else {
  1217. body = req->response_body->data;
  1218. }
  1219. /* call the action's response handler */
  1220. if (action->handle_response != NULL) {
  1221. (*action->handle_response) (source, body, action->entry);
  1222. }
  1223. g_free (free_body);
  1224. free_action (action);
  1225. source->priv->request_outstanding = FALSE;
  1226. process_queue (source);
  1227. }
  1228. static void
  1229. proxy_config_changed_cb (RBProxyConfig *config,
  1230. RBLastfmSource *source)
  1231. {
  1232. SoupURI *uri;
  1233. if (source->priv->soup_session) {
  1234. uri = rb_proxy_config_get_libsoup_uri (config);
  1235. g_object_set (G_OBJECT (source->priv->soup_session),
  1236. "proxy-uri", uri,
  1237. NULL);
  1238. if (uri)
  1239. soup_uri_free (uri);
  1240. }
  1241. }
  1242. static void
  1243. process_queue (RBLastfmSource *source)
  1244. {
  1245. RBLastfmAction *action;
  1246. SoupMessage *msg;
  1247. if (source->priv->request_outstanding) {
  1248. rb_debug ("request already in progress");
  1249. return;
  1250. }
  1251. msg = NULL;
  1252. while (msg == NULL) {
  1253. /* grab an action to perform */
  1254. action = g_queue_pop_head (source->priv->action_queue);
  1255. if (action == NULL) {
  1256. /* ran out */
  1257. break;
  1258. }
  1259. /* create the HTTP request */
  1260. msg = (*action->create_request) (source, action->entry);
  1261. if (msg == NULL) {
  1262. rb_debug ("action didn't want to create a message..");
  1263. free_action (action);
  1264. }
  1265. }
  1266. if (msg == NULL) {
  1267. rb_debug ("request queue is empty");
  1268. return;
  1269. }
  1270. if (source->priv->soup_session == NULL) {
  1271. SoupURI *uri;
  1272. uri = rb_proxy_config_get_libsoup_uri (source->priv->proxy_config);
  1273. source->priv->soup_session = soup_session_async_new_with_options ("proxy-uri", uri, NULL);
  1274. if (uri)
  1275. soup_uri_free (uri);
  1276. g_signal_connect_object (G_OBJECT (source->priv->proxy_config),
  1277. "config-changed",
  1278. G_CALLBACK (proxy_config_changed_cb),
  1279. source, 0);
  1280. }
  1281. soup_message_headers_append (msg->request_headers, "User-Agent", USER_AGENT);
  1282. soup_session_queue_message (source->priv->soup_session,
  1283. msg,
  1284. http_response_cb,
  1285. action);
  1286. source->priv->request_outstanding = TRUE;
  1287. source->priv->request_description = action->description;
  1288. rb_source_notify_status_changed (RB_SOURCE(source));
  1289. }
  1290. static void
  1291. queue_action (RBLastfmSource *source,
  1292. CreateRequestFunc create_request,
  1293. HandleResponseFunc handle_response,
  1294. RhythmDBEntry *entry,
  1295. const char *description)
  1296. {
  1297. RBLastfmAction *action;
  1298. action = g_new0 (RBLastfmAction, 1);
  1299. action->source = source; /* hmm, needs a ref? */
  1300. action->create_request = create_request;
  1301. action->handle_response = handle_response;
  1302. action->entry = entry; /* must already have been ref'd */
  1303. action->description = description;
  1304. g_queue_push_tail (source->priv->action_queue, action);
  1305. process_queue (source);
  1306. }
  1307. /* common protocol utility stuff */
  1308. static char *
  1309. auth_challenge (RBLastfmSource *source)
  1310. {
  1311. /* um, yeah, having the client generate the auth challenge
  1312. * seems a bit dumb, unless they've got some replay
  1313. * protection on the server side..
  1314. */
  1315. return g_strdup_printf ("%ld", time (NULL));
  1316. }
  1317. static gchar *
  1318. mkmd5 (char *string, char *string2)
  1319. {
  1320. GChecksum *checksum;
  1321. gchar *md5_result;
  1322. checksum = g_checksum_new(G_CHECKSUM_MD5);
  1323. g_checksum_update(checksum, (guchar *)string, -1);
  1324. if (string2 != NULL) {
  1325. g_checksum_update(checksum, (guchar *)string2, -1);
  1326. }
  1327. md5_result = g_strdup(g_checksum_get_string(checksum));
  1328. g_checksum_free(checksum);
  1329. return (md5_result);
  1330. }
  1331. static gboolean
  1332. station_is_subscriber_only (const char *uri)
  1333. {
  1334. /* loved-tracks radio */
  1335. if (g_str_has_prefix (uri, "lastfm://user/") &&
  1336. g_str_has_suffix (uri, "/loved")) {
  1337. return TRUE;
  1338. }
  1339. /* user tag radio */
  1340. if (g_str_has_prefix (uri, "lastfm://usertags/"))
  1341. return TRUE;
  1342. /* anything else? */
  1343. return FALSE;
  1344. }
  1345. /* handshake request */
  1346. static SoupMessage *
  1347. create_handshake_request (RBLastfmSource *source, RhythmDBEntry *entry)
  1348. {
  1349. SoupMessage *req;
  1350. char *password;
  1351. char *username;
  1352. char *md5password;
  1353. char *handshake_url;
  1354. switch (source->priv->state) {
  1355. case NOT_CONNECTED:
  1356. rb_debug ("logging in");
  1357. break;
  1358. case CONNECTED:
  1359. rb_debug ("already logged in");
  1360. return NULL;
  1361. default:
  1362. rb_debug ("can't log in: %s",
  1363. state_name[source->priv->state]);
  1364. return NULL;
  1365. }
  1366. username = eel_gconf_get_string (CONF_AUDIOSCROBBLER_USERNAME);
  1367. if (username == NULL) {
  1368. rb_debug ("no last.fm username");
  1369. source->priv->state = LOGIN_FAILED;
  1370. return NULL;
  1371. }
  1372. password = eel_gconf_get_string (CONF_AUDIOSCROBBLER_PASSWORD);
  1373. if (password == NULL) {
  1374. rb_debug ("no last.fm password");
  1375. source->priv->state = LOGIN_FAILED;
  1376. return NULL;
  1377. }
  1378. md5password = mkmd5 (password, NULL);
  1379. g_free (password);
  1380. handshake_url = g_strdup_printf ("http://%s/radio/handshake.php?"
  1381. "version=" RB_LASTFM_VERSION "&"
  1382. "platform=" RB_LASTFM_PLATFORM "&"
  1383. "username=%s&"
  1384. "passwordmd5=%s&"
  1385. "debug=0&"
  1386. "partner=",
  1387. LASTFM_URL,
  1388. username,
  1389. md5password);
  1390. g_free (username);
  1391. g_free (md5password);
  1392. req = soup_message_new ("GET", handshake_url);
  1393. g_free (handshake_url);
  1394. return req;
  1395. }
  1396. static void
  1397. _subscriber_station_visibility_cb (RhythmDBEntry *entry, RBLastfmSource *source)
  1398. {
  1399. gboolean hidden;
  1400. const char *uri;
  1401. GValue v = {0,};
  1402. uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
  1403. if (source->priv->subscriber) {
  1404. hidden = FALSE;
  1405. } else {
  1406. hidden = station_is_subscriber_only (uri);
  1407. }
  1408. g_value_init (&v, G_TYPE_BOOLEAN);
  1409. g_value_set_boolean (&v, hidden);
  1410. rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_HIDDEN, &v);
  1411. g_value_unset (&v);
  1412. }
  1413. static void
  1414. handle_handshake_response (RBLastfmSource *source, const char *body, RhythmDBEntry *entry)
  1415. {
  1416. char *username;
  1417. char **pieces;
  1418. int i;
  1419. if (body == NULL) {
  1420. rb_debug ("login failed: no response");
  1421. source->priv->state = NOT_CONNECTED;
  1422. return;
  1423. }
  1424. rb_debug ("response body: %s", body);
  1425. pieces = g_strsplit (body, "\n", 0);
  1426. for (i = 0; pieces[i] != NULL; i++) {
  1427. gchar **values = g_strsplit (pieces[i], "=", 2);
  1428. if (values[0] == NULL) {
  1429. rb_debug ("unexpected response content: %s", pieces[i]);
  1430. } else if (strcmp (values[0], "session") == 0) {
  1431. if (strcmp (values[1], "FAILED") == 0) {
  1432. source->priv->state = LOGIN_FAILED;
  1433. rb_debug ("login failed");
  1434. } else {
  1435. source->priv->state = CONNECTED;
  1436. g_free (source->priv->session_id);
  1437. source->priv->session_id = g_strdup (values[1]);
  1438. rb_debug ("session ID: %s", source->priv->session_id);
  1439. }
  1440. } else if (strcmp (values[0], "stream_url") == 0) {
  1441. /* don't really care about the stream url now */
  1442. /*source->priv->stream_url = g_strdup (values[1]);*/
  1443. rb_debug ("stream url: %s", values[1]);
  1444. } else if (strcmp (values[0], "subscriber") == 0) {
  1445. if (strcmp (values[1], "0") == 0) {
  1446. source->priv->subscriber = FALSE;
  1447. } else {
  1448. source->priv->subscriber = TRUE;
  1449. }
  1450. } else if (strcmp (values[0], "base_url") ==0) {
  1451. source->priv->base_url = g_strdup (values[1]);
  1452. } else if (strcmp (values[0], "base_path") ==0) {
  1453. source->priv->base_path = g_strdup (values[1]);
  1454. } else if (strcmp (values[0], "banned") ==0) {
  1455. if (strcmp (values[1], "0") != 0) {
  1456. source->priv->state = BANNED;
  1457. }
  1458. }
  1459. g_strfreev (values);
  1460. }
  1461. g_strfreev (pieces);
  1462. if (source->priv->state != CONNECTED) {
  1463. return;
  1464. }
  1465. /* create default stations */
  1466. username = eel_gconf_get_string (CONF_AUDIOSCROBBLER_USERNAME);
  1467. if (username != NULL) {
  1468. char *uri;
  1469. RhythmDBEntry *entry;
  1470. /* neighbour radio */
  1471. uri = g_strdup_printf ("lastfm://user/%s/neighbours", username);
  1472. entry = rhythmdb_entry_lookup_by_location (source->priv->db, uri);
  1473. if (entry == NULL) {
  1474. rb_lastfm_source_new_station (uri, _("Neighbour Radio"), RB_LASTFM_SOURCE (source));
  1475. }
  1476. g_free (uri);
  1477. /* personal radio (subscriber only) */
  1478. uri = g_strdup_printf ("lastfm://user/%s/personal", username);
  1479. entry = rhythmdb_entry_lookup_by_location (source->priv->db, uri);
  1480. if (entry == NULL) {
  1481. rb_lastfm_source_new_station (uri, _("Personal Radio"), RB_LASTFM_SOURCE (source));
  1482. }
  1483. g_free (uri);
  1484. g_free (username);
  1485. }
  1486. /* update subscriber-only station visibility */
  1487. rhythmdb_entry_foreach_by_type (source->priv->db,
  1488. source->priv->station_entry_type,
  1489. (GFunc) _subscriber_station_visibility_cb,
  1490. source);
  1491. rhythmdb_commit (source->priv->db);
  1492. }
  1493. static void
  1494. queue_handshake (RBLastfmSource *source)
  1495. {
  1496. queue_action (source,
  1497. create_handshake_request,
  1498. handle_handshake_response,
  1499. NULL,
  1500. _("Logging in"));
  1501. }
  1502. /* change station */
  1503. static SoupMessage *
  1504. create_station_request (RBLastfmSource *source, RhythmDBEntry *entry)
  1505. {
  1506. SoupMessage *req;
  1507. char *url;
  1508. char *lastfm_url;
  1509. if (source->priv->state != CONNECTED &&
  1510. source->priv->state != STATION_FAILED) {
  1511. rb_debug ("can't change station: %s",
  1512. state_name[source->priv->state]);
  1513. return NULL;
  1514. }
  1515. if (source->priv->current_station == entry) {
  1516. rb_debug ("already on station %s",
  1517. rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
  1518. return NULL;
  1519. }
  1520. lastfm_url = g_uri_escape_string (rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION),
  1521. G_URI_RESERVED_CHARS_ALLOWED_IN_PATH,
  1522. FALSE);
  1523. url = g_strdup_printf("http://%s%s/adjust.php?session=%s&url=%s&debug=0",
  1524. source->priv->base_url ? source->priv->base_url : LASTFM_URL,
  1525. source->priv->base_path,
  1526. source->priv->session_id,
  1527. lastfm_url);
  1528. rb_debug ("change station request: %s", url);
  1529. req = soup_message_new ("GET", url);
  1530. g_free (url);
  1531. g_free (lastfm_url);
  1532. return req;
  1533. }
  1534. static void
  1535. set_station_failed_reason (RBLastfmSource *source, RhythmDBEntry *station, const char *reason)
  1536. {
  1537. GValue v = {0,};
  1538. /* set playback error on the station entry */
  1539. g_value_init (&v, G_TYPE_STRING);
  1540. g_value_set_string (&v, reason);
  1541. rhythmdb_entry_set (source->priv->db, station, RHYTHMDB_PROP_PLAYBACK_ERROR, &v);
  1542. g_value_unset (&v);
  1543. /* set our status */
  1544. source->priv->state = STATION_FAILED;
  1545. source->priv->station_failed_reason = reason;
  1546. rb_source_notify_status_changed (RB_SOURCE (source));
  1547. }
  1548. static void
  1549. handle_station_response (RBLastfmSource *source, const char *body, RhythmDBEntry *entry)
  1550. {
  1551. char **pieces;
  1552. int i;
  1553. if (body == NULL) {
  1554. rb_debug ("couldn't change session: no response");
  1555. set_station_failed_reason (source, entry, _("Server did not respond")); /* crap message */
  1556. return;
  1557. }
  1558. rb_debug ("response body: %s", body);
  1559. pieces = g_strsplit (body, "\n", 0);
  1560. for (i = 0; pieces[i] != NULL; i++) {
  1561. gchar **values = g_strsplit (pieces[i], "=", 2);
  1562. if (values[0] == NULL) {
  1563. rb_debug ("unexpected response content: %s", pieces[i]);
  1564. } else if (strcmp (values[0], "response") == 0) {
  1565. if (source->priv->current_station != NULL) {
  1566. rhythmdb_entry_unref (source->priv->current_station);
  1567. source->priv->current_station = NULL;
  1568. }
  1569. if (strcmp (values[1], "OK") == 0) {
  1570. RhythmDBEntry *playing_entry;
  1571. GtkTreeIter iter;
  1572. GList *remove = NULL;
  1573. GList *i;
  1574. source->priv->state = CONNECTED;
  1575. source->priv->current_station = rhythmdb_entry_ref (entry);
  1576. /* remove existing unplayed entries, as they
  1577. * will have been invalidated when we switched away from it.
  1578. * (it sort of seems like this is no longer true, actually)
  1579. */
  1580. playing_entry = rb_shell_player_get_playing_entry (source->priv->shell_player);
  1581. if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (source->priv->query_model), &iter)) {
  1582. do {
  1583. RhythmDBEntry *track;
  1584. track = rhythmdb_query_model_iter_to_entry (source->priv->query_model, &iter);
  1585. if (track == playing_entry) {
  1586. rhythmdb_entry_unref (track);
  1587. } else if (track != NULL) {
  1588. remove = g_list_prepend (remove, track);
  1589. }
  1590. } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (source->priv->query_model), &iter));
  1591. }
  1592. for (i = remove; i != NULL; i = i->next) {
  1593. RhythmDBEntry *track;
  1594. RBLastfmTrackEntryData *track_data;
  1595. track = (RhythmDBEntry *)i->data;
  1596. track_data = RHYTHMDB_ENTRY_GET_TYPE_DATA (track, RBLastfmTrackEntryData);
  1597. rhythmdb_entry_delete (source->priv->db, track);
  1598. rhythmdb_entry_unref (track);
  1599. }
  1600. rhythmdb_commit (source->priv->db);
  1601. }
  1602. } else if (strcmp (values[0], "error") == 0) {
  1603. int errorcode;
  1604. errorcode = strtoul (values[1], NULL, 0);
  1605. switch (errorcode) {
  1606. case 1: /* not enough content */
  1607. case 2: /* not enough members in group */
  1608. case 3: /* not enough fans of artist */
  1609. case 4: /* unavailable */
  1610. case 6: /* too few neighbours */
  1611. set_station_failed_reason (source, entry,
  1612. _("There is not enough content available to play this station."));
  1613. break;
  1614. case 5: /* subscriber only */
  1615. set_station_failed_reason (source, entry,
  1616. _("This station is available to subscribers only."));
  1617. break;
  1618. case 7:
  1619. case 8:
  1620. default:
  1621. set_station_failed_reason (source, entry,
  1622. _("The streaming system is offline for maintenance, please try again later."));
  1623. break;
  1624. }
  1625. } else if (strcmp (values[0], "url") == 0) {
  1626. /* might have some use for this, I guess? */
  1627. } else if (strcmp (values[0], "stationname") == 0) {
  1628. /* um, might want to use this stuff at some point, I guess
  1629. gchar **data = g_strsplit (g_strdown(pieces[i - 1]), "=",2);
  1630. RhythmDBEntry *entry;
  1631. GValue titlestring = {0,};
  1632. rb_debug ("Received station name from server: %s", values[1]);
  1633. entry = rhythmdb_entry_lookup_by_location (source->priv->db, data[1]);
  1634. g_value_init (&titlestring, G_TYPE_STRING);
  1635. g_value_set_string (&titlestring, values[1]);
  1636. if (entry == NULL) {
  1637. entry = rhythmdb_entry_new (source->priv->db, source->priv->station_entry_type, data[1]);
  1638. }
  1639. rhythmdb_entry_set (source->priv->db, entry, RHYTHMDB_PROP_TITLE, &titlestring);
  1640. g_value_unset (&titlestring);
  1641. rhythmdb_commit (source->priv->db);
  1642. */
  1643. }
  1644. g_strfreev (values);
  1645. }
  1646. g_strfreev (pieces);
  1647. }
  1648. static void
  1649. queue_change_station (RBLastfmSource *source, RhythmDBEntry *station)
  1650. {
  1651. queue_action (source,
  1652. create_station_request,
  1653. handle_station_response,
  1654. rhythmdb_entry_ref (station),
  1655. _("Changing station"));
  1656. }
  1657. /* get playlist */
  1658. static SoupMessage *
  1659. create_playlist_request (RBLastfmSource *source, RhythmDBEntry *entry)
  1660. {
  1661. SoupMessage *req;
  1662. char *xspf_url;
  1663. if (source->priv->state != CONNECTED &&
  1664. source->priv->state != STATION_FAILED) {
  1665. rb_debug ("can't get playlist: %s",
  1666. state_name[source->priv->state]);
  1667. return NULL;
  1668. }
  1669. if (source->priv->current_station != entry) {
  1670. rb_debug ("can't get playlist: station not selected");
  1671. return NULL;
  1672. }
  1673. xspf_url = g_strdup_printf ("http://%s%s/xspf.php?sk=%s&discovery=0&desktop=%s",
  1674. source->priv->base_url ? source->priv->base_url : LASTFM_URL,
  1675. source->priv->base_path,
  1676. source->priv->session_id,
  1677. RB_LASTFM_VERSION);
  1678. rb_debug ("playlist request: %s", xspf_url);
  1679. req = soup_message_new ("GET", xspf_url);
  1680. g_free (xspf_url);
  1681. return req;
  1682. }
  1683. static void
  1684. xspf_entry_parsed (TotemPlParser *parser, const char *uri, GHashTable *metadata, RBLastfmSource *source)
  1685. {
  1686. RhythmDBEntry *track_entry;
  1687. RBLastfmTrackEntryData *track_data;
  1688. const char *value;
  1689. GValue v = {0,};
  1690. int i;
  1691. struct {
  1692. const char *field;
  1693. RhythmDBPropType prop;
  1694. } field_mapping[] = {
  1695. { TOTEM_PL_PARSER_FIELD_TITLE, RHYTHMDB_PROP_TITLE },
  1696. { TOTEM_PL_PARSER_FIELD_AUTHOR, RHYTHMDB_PROP_ARTIST },
  1697. { TOTEM_PL_PARSER_FIELD_ALBUM, RHYTHMDB_PROP_ALBUM },
  1698. };
  1699. /* create db entry if it doesn't already exist */
  1700. track_entry = rhythmdb_entry_lookup_by_location (source->priv->db, uri);
  1701. if (track_entry == NULL) {
  1702. rb_debug ("creating new track entry for %s", uri);
  1703. track_entry = rhythmdb_entry_new (source->priv->db,
  1704. source->priv->track_entry_type,
  1705. uri);
  1706. } else {
  1707. rb_debug ("track entry %s already exists", uri);
  1708. }
  1709. track_data = RHYTHMDB_ENTRY_GET_TYPE_DATA (track_entry, RBLastfmTrackEntryData);
  1710. /* straightforward string copying */
  1711. for (i = 0; i < G_N_ELEMENTS (field_mapping); i++) {
  1712. value = g_hash_table_lookup (metadata, field_mapping[i].field);
  1713. if (value != NULL) {
  1714. g_value_init (&v, G_TYPE_STRING);
  1715. g_value_set_string (&v, value);
  1716. rhythmdb_entry_set (source->priv->db, track_entry, field_mapping[i].prop, &v);
  1717. g_value_unset (&v);
  1718. }
  1719. }
  1720. /* duration needs some conversion */
  1721. value = g_hash_table_lookup (metadata, TOTEM_PL_PARSER_FIELD_DURATION_MS);
  1722. if (value != NULL) {
  1723. gint64 duration;
  1724. duration = totem_pl_parser_parse_duration (value, FALSE);
  1725. if (duration > 0) {
  1726. g_value_init (&v, G_TYPE_ULONG);
  1727. g_value_set_ulong (&v, (gulong) duration / 1000); /* ms -> s */
  1728. rhythmdb_entry_set (source->priv->db, track_entry, RHYTHMDB_PROP_DURATION, &v);
  1729. g_value_unset (&v);
  1730. }
  1731. }
  1732. /* image URL and track auth ID are stored in entry type specific data */
  1733. value = g_hash_table_lookup (metadata, TOTEM_PL_PARSER_FIELD_IMAGE_URL);
  1734. if (value != NULL && (strcmp (value, LASTFM_NO_COVER_IMAGE) != 0)) {
  1735. track_data->image_url = g_strdup (value);
  1736. }
  1737. value = g_hash_table_lookup (metadata, TOTEM_PL_PARSER_FIELD_ID);
  1738. if (value != NULL) {
  1739. track_data->track_auth = g_strdup (value);
  1740. }
  1741. value = g_hash_table_lookup (metadata, TOTEM_PL_PARSER_FIELD_DOWNLOAD_URL);
  1742. if (value != NULL) {
  1743. track_data->download_url = g_strdup (value);
  1744. rb_debug ("track %s has a download url: %s", uri, track_data->download_url);
  1745. }
  1746. /* what happens if it's already in there? need to use move_entry instead? */
  1747. rhythmdb_query_model_add_entry (source->priv->query_model, track_entry, -1);
  1748. source->priv->last_entry = track_entry;
  1749. }
  1750. static gboolean
  1751. handle_playlist_response (RBLastfmSource *source, const char *body, RhythmDBEntry *station)
  1752. {
  1753. int tmp_fd;
  1754. char *tmp_name;
  1755. char *tmp_uri = NULL;
  1756. GIOChannel *channel = NULL;
  1757. TotemPlParser *parser = NULL;
  1758. TotemPlParserResult result;
  1759. GError *error = NULL;
  1760. gboolean ret = FALSE;
  1761. time_t now;
  1762. GValue value = {0,};
  1763. if (body == NULL) {
  1764. rb_debug ("didn't get a response");
  1765. return FALSE;
  1766. }
  1767. /* until totem-pl-parser can parse playlists from in-memory data, we save it to a
  1768. * temporary file.
  1769. */
  1770. tmp_fd = g_file_open_tmp ("rb-lastfm-playlist-XXXXXX.xspf", &tmp_name, &error);
  1771. if (error != NULL) {
  1772. rb_debug ("unable to save playlist: %s", error->message);
  1773. goto cleanup;
  1774. }
  1775. channel = g_io_channel_unix_new (tmp_fd);
  1776. g_io_channel_write_chars (channel, body, strlen (body), NULL, &error);
  1777. if (error != NULL) {
  1778. rb_debug ("unable to save playlist: %s", error->message);
  1779. goto cleanup;
  1780. }
  1781. g_io_channel_flush (channel, NULL); /* ignore errors.. */
  1782. tmp_uri = g_filename_to_uri (tmp_name, NULL, &error);
  1783. if (error != NULL) {
  1784. rb_debug ("unable to parse playlist: %s", error->message);
  1785. goto cleanup;
  1786. }
  1787. rb_debug ("parsing playlist %s", tmp_uri);
  1788. parser = totem_pl_parser_new ();
  1789. g_signal_connect_data (parser, "entry-parsed", G_CALLBACK (xspf_entry_parsed), source, NULL, 0);
  1790. result = totem_pl_parser_parse (parser, tmp_uri, FALSE);
  1791. switch (result) {
  1792. case TOTEM_PL_PARSER_RESULT_UNHANDLED:
  1793. case TOTEM_PL_PARSER_RESULT_IGNORED:
  1794. case TOTEM_PL_PARSER_RESULT_ERROR:
  1795. rb_debug ("playlist didn't parse");
  1796. break;
  1797. case TOTEM_PL_PARSER_RESULT_SUCCESS:
  1798. /* update the station's last played time */
  1799. g_value_init (&value, G_TYPE_ULONG);
  1800. time (&now);
  1801. g_value_set_ulong (&value, now);
  1802. rhythmdb_entry_set (source->priv->db,
  1803. source->priv->current_station,
  1804. RHYTHMDB_PROP_LAST_PLAYED,
  1805. &value);
  1806. g_value_unset (&value);
  1807. rhythmdb_commit (source->priv->db);
  1808. ret = TRUE;
  1809. break;
  1810. }
  1811. cleanup:
  1812. if (channel != NULL) {
  1813. g_io_channel_unref (channel);
  1814. }
  1815. if (parser != NULL) {
  1816. g_object_unref (parser);
  1817. }
  1818. if (error != NULL) {
  1819. g_error_free (error);
  1820. }
  1821. close (tmp_fd);
  1822. g_unlink (tmp_name);
  1823. g_free (tmp_name);
  1824. g_free (tmp_uri);
  1825. return ret;
  1826. }
  1827. static void
  1828. handle_playlist_response_and_skip (RBLastfmSource *source, const char *body, RhythmDBEntry *station)
  1829. {
  1830. if (handle_playlist_response (source, body, station)) {
  1831. /* ignore errors, sadly */
  1832. rb_shell_player_do_next (source->priv->shell_player, NULL);
  1833. }
  1834. }
  1835. static void
  1836. queue_get_playlist (RBLastfmSource *source, RhythmDBEntry *station)
  1837. {
  1838. queue_action (source,
  1839. create_playlist_request,
  1840. (HandleResponseFunc) handle_playlist_response,
  1841. rhythmdb_entry_ref (station),
  1842. _("Retrieving playlist"));
  1843. }
  1844. static void
  1845. queue_get_playlist_and_skip (RBLastfmSource *source, RhythmDBEntry *station)
  1846. {
  1847. queue_action (source,
  1848. create_playlist_request,
  1849. handle_playlist_response_and_skip,
  1850. rhythmdb_entry_ref (station),
  1851. _("Retrieving playlist"));
  1852. }
  1853. /* XMLRPC requests */
  1854. static SoupMessage *
  1855. create_action_request (RBLastfmSource *source, RhythmDBEntry *entry, const char *action)
  1856. {
  1857. SoupMessage *req;
  1858. char *url;
  1859. char *username;
  1860. char *password;
  1861. char *md5password;
  1862. char *challenge;
  1863. char *md5challenge;
  1864. if (source->priv->state != CONNECTED) {
  1865. rb_debug ("can't perform %s action: %s",
  1866. action, state_name[source->priv->state]);
  1867. return NULL;
  1868. }
  1869. username = eel_gconf_get_string (CONF_AUDIOSCROBBLER_USERNAME);
  1870. if (username == NULL) {
  1871. rb_debug ("no last.fm username");
  1872. return NULL;
  1873. }
  1874. password = eel_gconf_get_string (CONF_AUDIOSCROBBLER_PASSWORD);
  1875. if (password == NULL) {
  1876. rb_debug ("no last.fm password");
  1877. return NULL;
  1878. }
  1879. md5password = mkmd5 (password, NULL);
  1880. challenge = auth_challenge (source);
  1881. md5challenge = mkmd5 (md5password, challenge);
  1882. url = g_strdup_printf ("http://%s/1.0/rw/xmlrpc.php",
  1883. source->priv->base_url ? source->priv->base_url : LASTFM_URL);
  1884. req = soup_xmlrpc_request_new (url, action,
  1885. G_TYPE_STRING, username,
  1886. G_TYPE_STRING, challenge,
  1887. G_TYPE_STRING, md5challenge,
  1888. G_TYPE_STRING, rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST),
  1889. G_TYPE_STRING, rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE),
  1890. G_TYPE_INVALID);
  1891. g_free (username);
  1892. g_free (password);
  1893. g_free (md5password);
  1894. g_free (md5challenge);
  1895. g_free (url);
  1896. return req;
  1897. }
  1898. static void
  1899. handle_xmlrpc_response (RBLastfmSource *source, const char *body, RhythmDBEntry *entry)
  1900. {
  1901. GError *error = NULL;
  1902. GValue v = {0,};
  1903. if (body == NULL) {
  1904. rb_debug ("didn't get a response to an xmlrpc request");
  1905. return;
  1906. }
  1907. soup_xmlrpc_parse_method_response (body, strlen (body), &v, &error);
  1908. if (error != NULL) {
  1909. rb_debug ("got error in xmlrpc response: %s", error->message);
  1910. g_error_free (error);
  1911. }
  1912. /* do something with the return value? */
  1913. g_value_unset (&v);
  1914. }
  1915. /* XMLRPC: banTrack */
  1916. static SoupMessage *
  1917. create_ban_request (RBLastfmSource *source, RhythmDBEntry *track)
  1918. {
  1919. return create_action_request (source, track, "banTrack");
  1920. }
  1921. static void
  1922. queue_ban_track (RBLastfmSource *source)
  1923. {
  1924. queue_action (source,
  1925. create_ban_request,
  1926. handle_xmlrpc_response,
  1927. rb_shell_player_get_playing_entry (source->priv->shell_player),
  1928. _("Banning song"));
  1929. }
  1930. /* XMLRPC: loveTrack */
  1931. static SoupMessage *
  1932. create_love_request (RBLastfmSource *source, RhythmDBEntry *track)
  1933. {
  1934. return create_action_request (source, track, "loveTrack");
  1935. }
  1936. static void
  1937. queue_love_track (RBLastfmSource *source)
  1938. {
  1939. queue_action (source,
  1940. create_love_request,
  1941. handle_xmlrpc_response,
  1942. rb_shell_player_get_playing_entry (source->priv->shell_player),
  1943. _("Adding song to your Loved tracks")); /* ugh */
  1944. }
  1945. /* and maybe some more */