PageRenderTime 26ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/plugins/lastfm/xnoise-lastfm.vala

https://code.google.com/p/xnoise/
Vala | 487 lines | 365 code | 63 blank | 59 comment | 62 complexity | 15acf1bb597873de934fe75238e12f3c MD5 | raw file
Possible License(s): GPL-2.0
  1. /* xnoise-mpris.vala
  2. *
  3. * Copyright (C) 2011-2012 J??rn Magens
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * The Xnoise authors hereby grant permission for non-GPL compatible
  11. * GStreamer plugins to be used and distributed together with GStreamer
  12. * and Xnoise. This permission is above and beyond the permissions granted
  13. * by the GPL license by which Xnoise is covered. If you modify this code
  14. * you may extend this exception to your version of the code, but you are not
  15. * obligated to do so. If you do not wish to do so, delete this exception
  16. * statement from your version.
  17. *
  18. * This program is distributed in the hope that it will be useful,
  19. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. * GNU General Public License for more details.
  22. *
  23. * You should have received a copy of the GNU General Public License
  24. * along with this program; if not, write to the Free Software
  25. * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  26. *
  27. * Author:
  28. * J??rn Magens
  29. */
  30. using Gtk;
  31. using Lastfm;
  32. using Xnoise;
  33. using Xnoise.Services;
  34. using Xnoise.PluginModule;
  35. public class Xnoise.Lfm : GLib.Object, IPlugin, IAlbumCoverImageProvider {
  36. public Main xn { get; set; }
  37. private unowned PluginModule.Container _owner;
  38. private Session session;
  39. private uint scrobble_source = 0;
  40. private uint now_play_source = 0;
  41. private int WAIT_TIME_BEFORE_SCROBBLE = 25;
  42. private int WAIT_TIME_BEFORE_NOW_PLAYING = 5;
  43. private ulong c = 0;
  44. private ulong d = 0;
  45. public PluginModule.Container owner {
  46. get {
  47. return _owner;
  48. }
  49. set {
  50. _owner = value;
  51. }
  52. }
  53. public string name { get { return "lastfm"; } }
  54. public signal void login_state_change();
  55. public bool init() {
  56. owner.sign_deactivated.connect(clean_up);
  57. session = new Lastfm.Session(
  58. Lastfm.Session.AuthenticationType.MOBILE, // session authentication type
  59. "a39db9ab0d1fb9a18fabab96e20b0a34", // xnoise api_key for noncomercial use
  60. "55993a9f95470890c6806271085159a3", // secret
  61. null//"de" // language TODO
  62. );
  63. c = session.notify["logged-in"].connect( () => {
  64. Idle.add( () => {
  65. login_state_change();
  66. return false;
  67. });
  68. });
  69. d = session.login_successful.connect( (sender, un) => {
  70. print("Lastfm plugin logged in %s successfully\n", un); // TODO: real feedback needed
  71. });
  72. string username = Xnoise.Params.get_string_value("lfm_user");
  73. string password = Xnoise.Params.get_string_value("lfm_pass");
  74. if(username != EMPTYSTRING && password != EMPTYSTRING)
  75. this.login(username, password);
  76. global.notify["current-title"].connect(on_current_track_changed);
  77. global.notify["current-artist"].connect(on_current_track_changed);
  78. global.uri_changed.connect(on_current_uri_changed);
  79. global.player_in_shutdown.connect( () => { clean_up(); });
  80. return true;
  81. }
  82. public void uninit() {
  83. clean_up();
  84. }
  85. private void clean_up() {
  86. if(session != null) {
  87. session.abort();
  88. session.disconnect(c);
  89. session.disconnect(d);
  90. session = null;
  91. }
  92. scrobble_track = null;
  93. now_play_track = null;
  94. }
  95. ~Lfm() {
  96. }
  97. public Gtk.Widget? get_settings_widget() {
  98. var w = new LfmWidget(this);
  99. return w;
  100. }
  101. public bool has_settings_widget() {
  102. return true;
  103. }
  104. public void login(string username, string password) {
  105. Idle.add( () => {
  106. session.login(username, password);
  107. return false;
  108. });
  109. }
  110. public bool logged_in() {
  111. return this.session.logged_in;
  112. }
  113. private Track scrobble_track;
  114. private Track now_play_track;
  115. private struct ScrobbleData {
  116. public string? uri;
  117. public string? artist;
  118. public string? album;
  119. public string? title;
  120. public int64 playtime;
  121. }
  122. private ScrobbleData sd_last;
  123. private void on_current_uri_changed(GLib.Object sender, string? p) {
  124. //scrobble
  125. if(sd_last.title != null && sd_last.artist != null) {
  126. if(session == null || !session.logged_in)
  127. return;
  128. if(scrobble_source != 0)
  129. Source.remove(scrobble_source);
  130. scrobble_source = Timeout.add(500, () => {
  131. var dt = new DateTime.now_utc();
  132. int64 pt = dt.to_unix();
  133. if((pt - sd_last.playtime) < WAIT_TIME_BEFORE_SCROBBLE)
  134. return false;
  135. // Use session's 'factory method to get Track
  136. scrobble_track = session.factory_make_track(sd_last.artist, sd_last.album, sd_last.title);
  137. // SCROBBLE TRACK
  138. scrobble_track.scrobble(sd_last.playtime);
  139. scrobble_source = 0;
  140. return false;
  141. });
  142. }
  143. }
  144. private void on_current_track_changed(GLib.Object sender, ParamSpec p) {
  145. if(global.current_title != null && global.current_artist != null) {
  146. if(session == null || !session.logged_in)
  147. return;
  148. //updateNowPlaying
  149. if(now_play_source != 0)
  150. Source.remove(now_play_source);
  151. now_play_source = Timeout.add_seconds(WAIT_TIME_BEFORE_NOW_PLAYING, () => {
  152. // Use session's 'factory method to get Track
  153. if(global.current_title == null || global.current_artist == null) {
  154. now_play_source = 0;
  155. return false;
  156. }
  157. now_play_track = session.factory_make_track(global.current_artist, global.current_album, global.current_title);
  158. sd_last = ScrobbleData();
  159. sd_last.uri = global.current_uri;
  160. sd_last.artist = global.current_artist;
  161. sd_last.album = global.current_album;
  162. sd_last.title = global.current_title;
  163. var dt = new DateTime.now_utc();
  164. sd_last.playtime = dt.to_unix();
  165. // UPDATE NOW PLAYING TRACK
  166. now_play_track.updateNowPlaying();
  167. now_play_source = 0;
  168. return false;
  169. });
  170. }
  171. }
  172. public Xnoise.IAlbumCoverImage from_tags(string artist, string album) {
  173. return new LastFmCovers(artist, album, this.session);
  174. }
  175. }
  176. /**
  177. * The LastFmCovers class tries to find cover images on
  178. * lastFm.
  179. * The images are downloaded to a local folder below ~/.xnoise
  180. * The download folder is returned via a signal together with
  181. * the artist name and the album name for identification.
  182. *
  183. * This class should be called from a closure to work with full
  184. * mainloop integration. No threads needed!
  185. * Copying is also done asynchonously.
  186. */
  187. public class Xnoise.LastFmCovers : GLib.Object, IAlbumCoverImage {
  188. private const int SECONDS_FOR_TIMEOUT = 12;
  189. // Maybe add this key as a construct only property. Then it can be an individual key for each user
  190. // private const string lastfmKey = "b25b959554ed76058ac220b7b2e0a026";
  191. private const string INIFOLDER = ".xnoise";
  192. // private SessionAsync session;
  193. private string artist;
  194. private string album;
  195. private File f = null;
  196. private string image_path;
  197. private string[] sizes;
  198. private File[] image_sources;
  199. private uint timeout;
  200. private bool timeout_done;
  201. private unowned Lastfm.Session session;
  202. private Lastfm.Album alb;
  203. public LastFmCovers(string _artist, string _album, Lastfm.Session session) {
  204. this.artist = _artist;
  205. this.album = _album;
  206. this.session = session;
  207. image_path = GLib.Path.build_filename(data_folder(),
  208. "album_images",
  209. null
  210. );
  211. image_sources = {};
  212. sizes = {"medium", "extralarge"}; //Two are enough
  213. timeout = 0;
  214. timeout_done = false;
  215. alb = this.session.factory_make_album(artist, album);
  216. alb.received_info.connect( (sender, al) => {
  217. print("got album info: %s , %s\n", sender.artist_name, al);
  218. //print("image extralarge: %s\n", sender.image_uris.lookup("extralarge"));
  219. string default_size = "medium";
  220. string uri_image;
  221. foreach(string s in sizes) {
  222. f = get_albumimage_for_artistalbum(artist, album, s);
  223. if(default_size == s) uri_image = f.get_path();
  224. string pth = EMPTYSTRING;
  225. File f_path = f.get_parent();
  226. if(!f_path.query_exists(null)) {
  227. try {
  228. f_path.make_directory_with_parents(null);
  229. }
  230. catch(GLib.Error e) {
  231. print("Error with create image directory: %s\npath: %s", e.message, pth);
  232. remove_timeout();
  233. this.unref();
  234. return;
  235. }
  236. }
  237. if(!f.query_exists(null)) {
  238. var remote_file = File.new_for_uri(sender.image_uris.lookup(s));
  239. image_sources += remote_file;
  240. }
  241. else {
  242. //print("Local file already exists\n");
  243. continue; //Local file exists
  244. }
  245. }
  246. this.copy_covers_async(sender.reply_artist.down(), sender.reply_album.down());
  247. });
  248. }
  249. ~LastFmCovers() {
  250. if(timeout != 0)
  251. Source.remove(timeout);
  252. }
  253. private void remove_timeout() {
  254. if(timeout != 0)
  255. Source.remove(timeout);
  256. }
  257. public void find_image() {
  258. //print("find_lastfm_image to %s - %s\n", artist, album);
  259. if((artist==UNKNOWN_ARTIST)||
  260. (album==UNKNOWN_ALBUM)) {
  261. sign_image_fetched(artist, album, EMPTYSTRING);
  262. this.unref();
  263. return;
  264. }
  265. alb.get_info(); // no login required
  266. //Add timeout for response
  267. timeout = Timeout.add_seconds(SECONDS_FOR_TIMEOUT, timeout_elapsed);
  268. }
  269. private bool timeout_elapsed() {
  270. this.timeout_done = true;
  271. this.unref();
  272. return false;
  273. }
  274. private async void copy_covers_async(string _reply_artist, string _reply_album) {
  275. File destination;
  276. bool buf = false;
  277. string default_path = EMPTYSTRING;
  278. int i = 0;
  279. string reply_artist = _reply_artist;
  280. string reply_album = _reply_album;
  281. foreach(File f in image_sources) {
  282. var s = sizes[i];
  283. destination = get_albumimage_for_artistalbum(reply_artist, reply_album, s);
  284. try {
  285. if(f.query_exists(null)) { //remote file exist
  286. buf = yield f.copy_async(destination,
  287. FileCopyFlags.OVERWRITE,
  288. Priority.DEFAULT,
  289. null,
  290. null);
  291. }
  292. else {
  293. continue;
  294. }
  295. if(sizes[i] == "medium") default_path = destination.get_path();
  296. i++;
  297. }
  298. catch(GLib.Error e) {
  299. print("Error: %s\n", e.message);
  300. i++;
  301. continue;
  302. }
  303. }
  304. // signal finish with artist, album in order to identify the sent image
  305. sign_image_fetched(reply_artist, reply_album, default_path);
  306. remove_timeout();
  307. if(!this.timeout_done) {
  308. this.unref(); // After this point LastFmCovers downloader can safely be removed
  309. }
  310. return;
  311. }
  312. }
  313. public class Xnoise.LfmWidget: Gtk.Box {
  314. private unowned Main xn;
  315. private unowned Xnoise.Lfm lfm;
  316. private Entry user_entry;
  317. private Entry pass_entry;
  318. private CheckButton use_scrobble_check;
  319. private Label feedback_label;
  320. private Button b;
  321. private string username_last;
  322. private string password_last;
  323. public LfmWidget(Xnoise.Lfm lfm) {
  324. GLib.Object(orientation:Gtk.Orientation.VERTICAL, spacing:10);
  325. this.lfm = lfm;
  326. this.xn = Main.instance;
  327. setup_widgets();
  328. this.lfm.login_state_change.connect(do_user_feedback);
  329. this.set_vexpand(true);
  330. this.set_hexpand(true);
  331. user_entry.text = Xnoise.Params.get_string_value("lfm_user");
  332. pass_entry.text = Xnoise.Params.get_string_value("lfm_pass");
  333. use_scrobble_check.set_active(Xnoise.Params.get_int_value("lfm_use_scrobble") != 0);
  334. use_scrobble_check.toggled.connect(on_use_scrobble_toggled);
  335. b.clicked.connect(on_entry_changed);
  336. }
  337. //show if user is logged in
  338. private void do_user_feedback() {
  339. //print("do_user_feedback\n");
  340. if(this.lfm.logged_in()) {
  341. feedback_label.set_markup("<b><i>%s</i></b>".printf(_("User logged in!")));
  342. feedback_label.set_use_markup(true);
  343. }
  344. else {
  345. feedback_label.set_markup("<b><i>%s</i></b>".printf(_("User not logged in!")));
  346. feedback_label.set_use_markup(true);
  347. }
  348. }
  349. private void on_use_scrobble_toggled(ToggleButton sender) {
  350. if(sender.get_active())
  351. Xnoise.Params.set_int_value("lfm_use_scrobble", 1);
  352. else
  353. Xnoise.Params.set_int_value("lfm_use_scrobble", 0);
  354. }
  355. private void on_entry_changed() {
  356. //print("take over entry\n");
  357. string username = EMPTYSTRING, password = EMPTYSTRING;
  358. if(user_entry.text != null)
  359. username = user_entry.text.strip();
  360. if(pass_entry.text != null)
  361. password = pass_entry.text.strip();
  362. if(username_last == user_entry.text.strip() && password_last == pass_entry.text.strip())
  363. return; // no need to spam!
  364. if(username != EMPTYSTRING && password != EMPTYSTRING) {
  365. //print("got login data\n");
  366. Xnoise.Params.set_string_value("lfm_user", username);
  367. Xnoise.Params.set_string_value("lfm_pass", password);
  368. username_last = username;
  369. password_last = password;
  370. Idle.add( () => {
  371. Xnoise.Params.write_all_parameters_to_file();
  372. return false;
  373. });
  374. do_user_feedback();
  375. lfm.login(username, password);
  376. }
  377. }
  378. private void setup_widgets() {
  379. var title_label = new Label("<b>%s</b>".printf(_("Please enter your lastfm username and password.")));
  380. title_label.set_use_markup(true);
  381. title_label.set_single_line_mode(true);
  382. title_label.set_alignment(0.5f, 0.5f);
  383. title_label.set_ellipsize(Pango.EllipsizeMode.END);
  384. title_label.ypad = 10;
  385. this.pack_start(title_label, false, false, 0);
  386. var hbox1 = new Box(Orientation.HORIZONTAL, 2);
  387. var user_label = new Label("%s".printf(_("Username:")));
  388. user_label.xalign = 0.0f;
  389. hbox1.pack_start(user_label, false, false, 0);
  390. user_entry = new Entry();
  391. hbox1.pack_start(user_entry, true, true, 0);
  392. var hbox2 = new Box(Orientation.HORIZONTAL, 2);
  393. var pass_label = new Label("%s".printf(_("Password:")));
  394. pass_label.xalign = 0.0f;
  395. hbox2.pack_start(pass_label, false, false, 0);
  396. pass_entry = new Entry();
  397. pass_entry.set_visibility(false);
  398. hbox2.pack_start(pass_entry, true, true, 0);
  399. var sizegroup = new Gtk.SizeGroup(SizeGroupMode.HORIZONTAL);
  400. sizegroup.add_widget(user_label);
  401. sizegroup.add_widget(pass_label);
  402. this.pack_start(hbox1, false, false, 4);
  403. this.pack_start(hbox2, false, false, 4);
  404. use_scrobble_check = new CheckButton.with_label(_("Scrobble played tracks on lastfm"));
  405. this.pack_start(use_scrobble_check, false, false, 0);
  406. //feedback
  407. feedback_label = new Label("<b><i>%s</i></b>".printf(_("User not logged in!")));
  408. if(this.lfm.logged_in()) {
  409. feedback_label.set_markup("<b><i>%s</i></b>".printf(_("User logged in!")));
  410. }
  411. else {
  412. feedback_label.set_markup("<b><i>%s</i></b>".printf(_("User not logged in!")));
  413. }
  414. feedback_label.set_use_markup(true);
  415. feedback_label.set_single_line_mode(true);
  416. feedback_label.set_alignment(0.5f, 0.5f);
  417. feedback_label.ypad = 20;
  418. this.pack_start(feedback_label, false, false, 0);
  419. b = new Button();
  420. b.set_label(_("Apply"));
  421. this.pack_start(b, true, true, 0);
  422. this.border_width = 4;
  423. }
  424. }