diff -Nru mpdcron-0.3+git20110303/configure.ac mpdcron-0.3+git20161228/configure.ac --- mpdcron-0.3+git20110303/configure.ac 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/configure.ac 2018-04-05 13:05:11.000000000 +0000 @@ -2,20 +2,32 @@ dnl {{{ program, version AC_PREREQ(2.59) -AC_INIT([src/cron-main.c]) -VERSION_MAJOR=0 -VERSION_MINOR=3 -VERSION_FULL="$VERSION_MAJOR.$VERSION_MINOR" -VERSION="$VERSION_FULL" +m4_define([mpdcron_version_major], [0]) +m4_define([mpdcron_version_minor], [3]) +m4_define([mpdcron_version_full], [mpdcron_version_major.mpdcron_version_minor]) +m4_define([mpdcron_version], [mpdcron_version_full]) + +AC_INIT([mpdcron], [mpdcron_version], + [https://github.com/alip/mpdcron/issues], + [mpdcron], [http://alip.github.com/mpdcron/]) + +AC_CONFIG_SRCDIR([src/cron-main.c]) +AC_CONFIG_MACRO_DIR([m4]) +AC_CONFIG_AUX_DIR([build-aux]) + +AM_INIT_AUTOMAKE([std-options foreign]) +m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) + +VERSION_MAJOR=mpdcron_version_major +VERSION_MINOR=mpdcron_version_minor +VERSION_FULL=mpdcron_version_full +VERSION=mpdcron_version AC_SUBST([VERSION_MAJOR]) AC_SUBST([VERSION_MINOR]) AC_SUBST([VERSION_FULL]) -AM_INIT_AUTOMAKE(mpdcron, [$VERSION_FULL]) -m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) - dnl {{{ git revision AC_MSG_CHECKING([for git head]) if test -d "${GIT_DIR:-${ac_top_srcdir:-./}/.git}" ; then @@ -55,7 +67,7 @@ GLIB_REQUIRED=2.16 GIO_REQUIRED=2.22 LIBDAEMON_REQUIRED=0.12 -LIBMPDCLIENT_REQUIRED=2.1 +LIBMPDCLIENT_REQUIRED=2.2 PKG_CHECK_MODULES([glib], [glib-2.0 >= $GLIB_REQUIRED],, [AC_MSG_ERROR([mpdcron requires glib-$GLIB_REQUIRED or newer])]) @@ -103,7 +115,7 @@ for m in $STANDARD_MODULES ; do if test x"$m" = x"notification" ; then if test x"$WANT_GMODULE" != x"yes"; then - AC_MSG_WARN([GModule support is disabled, notification module won't be built]) + AC_MSG_WARN([GModule support is disabled, notification module will not be built]) else # Notification requires notify-send AC_CHECK_PROGS(NOTIFY_SEND, notify-send, ) @@ -116,7 +128,7 @@ fi if test x"$m" = x"scrobbler" ; then if test x"$WANT_GMODULE" != x"yes"; then - AC_MSG_WARN([GModule support is disabled, scrobbler module won't be built]) + AC_MSG_WARN([GModule support is disabled, scrobbler module will not be built]) else # Scrobbler requires curl. PKG_CHECK_MODULES([libcurl], [libcurl], [WANT_SCROBBLER=yes], @@ -125,7 +137,7 @@ fi if test x"$m" = x"stats" ; then if test x"$WANT_GMODULE" != x"yes"; then - AC_MSG_WARN([GModule support is disabled, stats module won't be built]) + AC_MSG_WARN([GModule support is disabled, stats module will not be built]) else # Stats module requires gio and sqlite3 PKG_CHECK_MODULES([sqlite], [sqlite3], [WANT_STATS=yes], @@ -140,6 +152,8 @@ else AC_DEFINE(HAVE_GIO_UNIX, 1, "Define for gio-unix") fi + MPDCRON_CFLAGS="$MPDCRON_CFLAGS -D_XOPEN_SOURCE=600" + AC_CHECK_FUNCS(gmtime mktime strftime strptime getenv setenv) fi fi done @@ -150,7 +164,6 @@ dnl }}} dnl {{{ Extra CFLAGS -MPDCRON_CFLAGS= WANTED_CFLAGS="-Wall -W -Wextra -Wvla -Wformat=2 -Wformat-security -Wformat-nonliteral -Winit-self -Wfloat-equal -Wno-deprecated-declarations -Wmissing-declarations -Wmissing-noreturn -Wmissing-prototypes -Wredundant-decls -Wshadow -Wpointer-arith -Wstrict-prototypes -Wcast-qual -Wwrite-strings -pedantic" for flag in $WANTED_CFLAGS ; do AX_CHECK_COMPILER_FLAGS([$flag], [MPDCRON_CFLAGS="$MPDCRON_CFLAGS $flag"],) @@ -159,16 +172,17 @@ dnl }}} dnl {{{ Output -AM_CONFIG_HEADER(config.h) -AC_OUTPUT( - Makefile - src/Makefile - src/gmodule/Makefile - src/gmodule/notification/Makefile - src/gmodule/scrobbler/Makefile - src/gmodule/stats/Makefile - src/gmodule/stats/homescrape - data/Makefile - zsh-completion/Makefile -) +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_FILES([ + Makefile + src/Makefile + src/gmodule/Makefile + src/gmodule/notification/Makefile + src/gmodule/scrobbler/Makefile + src/gmodule/stats/Makefile + src/gmodule/stats/homescrape + data/Makefile + zsh-completion/Makefile +]) +AC_OUTPUT dnl }}} diff -Nru mpdcron-0.3+git20110303/data/Makefile.am mpdcron-0.3+git20161228/data/Makefile.am --- mpdcron-0.3+git20110303/data/Makefile.am 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/data/Makefile.am 2018-04-05 13:05:11.000000000 +0000 @@ -1,8 +1,8 @@ -EXTRA_DIST = mpdcron.1.pdc mpdcron.1 eugene.1.pdf eugene.1 -BUILT_SOURCES = mpdcron.1 eugene.1 -dist_man_MANS= mpdcron.1 eugene.1 +EXTRA_DIST = mpdcron.1.pdc mpdcron.1 +BUILT_SOURCES = mpdcron.1 +dist_man_MANS= mpdcron.1 -all: mpdcron.1 eugene.1 +all: mpdcron.1 -%.1: %.1.pdc +mpdcron.1: mpdcron.1.pdc pandoc -s -S --from=markdown --to=man $< -o $@ diff -Nru mpdcron-0.3+git20110303/debian/changelog mpdcron-0.3+git20161228/debian/changelog --- mpdcron-0.3+git20110303/debian/changelog 2018-04-03 12:35:30.000000000 +0000 +++ mpdcron-0.3+git20161228/debian/changelog 2018-04-06 13:12:37.000000000 +0000 @@ -1,8 +1,28 @@ -mpdcron (0.3+git20110303-6build1) bionic; urgency=high +mpdcron (0.3+git20161228-3) unstable; urgency=medium - * No change rebuild to pick up -fPIE compiler default + * Update debian/watch - -- Balint Reczey Tue, 03 Apr 2018 12:35:30 +0000 + -- Sebastien Delafond Fri, 06 Apr 2018 15:12:37 +0200 + +mpdcron (0.3+git20161228-2) unstable; urgency=medium + + * Correct debian/watch URL + + -- Sebastien Delafond Fri, 06 Apr 2018 08:12:58 +0200 + +mpdcron (0.3+git20161228-1) unstable; urgency=medium + + * New upstream version. + + -- Sebastien Delafond Thu, 05 Apr 2018 15:12:41 +0200 + +mpdcron (0.3+git20110303-7) unstable; urgency=medium + + * Switch to DEP 14 + * Update Vcs-* to point to salsa.d.o + * Switch to machine-readable copyright + + -- Sebastien Delafond Thu, 05 Apr 2018 14:48:42 +0200 mpdcron (0.3+git20110303-6) unstable; urgency=medium @@ -56,5 +76,5 @@ mpdcron (0.3+git20100302-1) unstable; urgency=low * Initial release (Closes: #572263). - + -- Sebastien Delafond Tue, 02 Mar 2010 20:07:00 +0100 diff -Nru mpdcron-0.3+git20110303/debian/control mpdcron-0.3+git20161228/debian/control --- mpdcron-0.3+git20110303/debian/control 2018-04-03 12:35:30.000000000 +0000 +++ mpdcron-0.3+git20161228/debian/control 2018-04-06 13:12:37.000000000 +0000 @@ -1,13 +1,12 @@ Source: mpdcron Section: sound Priority: optional -Maintainer: Ubuntu Developers -XSBC-Original-Maintainer: Sebastien Delafond +Maintainer: Sebastien Delafond Build-Depends: lintian, debhelper (>= 9~), libmpdclient-dev (>= 2.1), libdaemon-dev, libglib2.0-dev, libcurl4-gnutls-dev, libsqlite3-dev, libnotify-bin, quilt, automake, libtool -Standards-Version: 3.9.5 -Homepage: http://alip.github.com/mpdcron -Vcs-Git: git://anonscm.debian.org/collab-maint/mpdcron.git -Vcs-Browser: http://anonscm.debian.org/git/collab-maint/mpdcron.git +Standards-Version: 4.1.3 +Homepage: https://github.com/alip/mpdcron +Vcs-Git: https://salsa.debian.org/debian/mpdcron.git +Vcs-Browser: https://salsa.debian.org/debian/mpdcron Package: mpdcron Architecture: any @@ -20,7 +19,7 @@ * Uses mpd's idle mode. * Calls hooks depending on the event. * Sets special environment variables to pass data to the hooks. - * Optional support for modules via GModule. + * Optional support for modules via GModule. * Included modules: - notification + uses notify-send to send notifications. diff -Nru mpdcron-0.3+git20110303/debian/copyright mpdcron-0.3+git20161228/debian/copyright --- mpdcron-0.3+git20110303/debian/copyright 2014-09-25 12:07:45.000000000 +0000 +++ mpdcron-0.3+git20161228/debian/copyright 2018-04-06 13:12:37.000000000 +0000 @@ -1,31 +1,30 @@ -This package was originally debianized for Ubuntu by Taylor -LeMasurier-Wren on Mon, 14 Dec 2009 10:38:16 --0600, and subsequently adapted for Debian by Sebastien Delafond - on Wed, 03 Mar 2010 13:10:52 +0100. +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: mpdcron +Source: https://github.com/alip/mpdcron + +Files: * +Copyright: 2009, 2010 Ali Polatel +License: GPL-2 + +Files: src/gmodule/scrobbler/* + src/gmodule/utils.h + src/gmodule/notification/notification-file.c + src/gmodule/stats/tokenizer.c + src/gmodule/stats/stats-command.c + src/gmodule/stats/eugene-connection.c + src/gmodule/stats/stats-module.c + src/gmodule/stats/tokenizer.h +Copyright: 2005-2008 Kuno Woudt + 2003-2009 The Music Player Daemon Project +License: GPL-2 + +Files: conf/hooks/submit.rb +Copyright: John Nunemaker +License: GPL-2 + +Files: debian/* +Copyright: Sebastien Delafond +License: GPL-2 -It was downloaded from: http://alip.github.com/mpdcron/ -http://repo.or.cz/w/mpdcron.git - -Upstream Author: Ali Polatel - -All files are Copyright (c) 2009, 2010 Ali Polatel , -except for the following: - - either Copyright (c) 2005-2008 Kuno Woudt or - Copyright (C) 2003-2009 The Music Player Daemon Project: - src/gmodule/scrobbler/* - src/gmodule/utils.h - src/gmodule/notification/notification-file.c - src/gmodule/stats/tokenizer.c - src/gmodule/stats/stats-command.c - src/gmodule/stats/eugene-connection.c, - src/gmodule/stats/stats-module.c - src/gmodule/stats/tokenizer.h - - Copyright (c) John Nunemaker: - conf/hooks/submit.rb - - -mpdcron is released under the GPL-2. On Debian systems, the complete -text of the GNU General Public License v2 can be found in the file -'/usr/share/common-licenses/GPL-2'. +License: GPL-2 + See /usr/share/common-licenses/GPL-2 diff -Nru mpdcron-0.3+git20110303/debian/gbp.conf mpdcron-0.3+git20161228/debian/gbp.conf --- mpdcron-0.3+git20110303/debian/gbp.conf 1970-01-01 00:00:00.000000000 +0000 +++ mpdcron-0.3+git20161228/debian/gbp.conf 2018-04-06 13:12:37.000000000 +0000 @@ -0,0 +1,6 @@ +[DEFAULT] +debian-branch = debian/master +upstream-branch = upstream/latest + +[buildpackage] +compression = gz diff -Nru mpdcron-0.3+git20110303/debian/rules mpdcron-0.3+git20161228/debian/rules --- mpdcron-0.3+git20110303/debian/rules 2014-09-25 12:07:45.000000000 +0000 +++ mpdcron-0.3+git20161228/debian/rules 2018-04-06 13:12:37.000000000 +0000 @@ -14,10 +14,11 @@ src/gmodule/notification/Makefile.in \ src/gmodule/scrobbler/Makefile.in \ src/gmodule/stats/Makefile.in \ - zsh-completion/Makefile.in + zsh-completion/Makefile.in override_dh_install: find $(CURDIR)/debian/mpdcron/ -name *.la -exec rm {} \; + find $(CURDIR)/debian/mpdcron/ -name *.a -exec rm {} \; override_dh_auto_configure: ./autogen.sh diff -Nru mpdcron-0.3+git20110303/debian/watch mpdcron-0.3+git20161228/debian/watch --- mpdcron-0.3+git20110303/debian/watch 2014-09-25 12:07:45.000000000 +0000 +++ mpdcron-0.3+git20161228/debian/watch 2018-04-06 13:12:37.000000000 +0000 @@ -1,3 +1,3 @@ version=3 -http://githubredir.debian.net/github/alip/mpdcron /v(.*).tar.gz +https://github.com/alip/mpdcron/tags .*/v(.*).tar.gz debian uupdate diff -Nru mpdcron-0.3+git20110303/.gitignore mpdcron-0.3+git20161228/.gitignore --- mpdcron-0.3+git20110303/.gitignore 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/.gitignore 2018-04-05 13:05:11.000000000 +0000 @@ -15,6 +15,7 @@ Makefile.in Makefile +/build-aux /INSTALL /aclocal.m4 /autom4te.cache diff -Nru mpdcron-0.3+git20110303/NEWS.mkd mpdcron-0.3+git20161228/NEWS.mkd --- mpdcron-0.3+git20110303/NEWS.mkd 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/NEWS.mkd 2018-04-05 13:05:11.000000000 +0000 @@ -3,7 +3,25 @@ This file lists the major changes between versions. For a more detailed list of every change, see git log. -* stats: ability to set count regardless of existing count. +* stats: clever handling for radio stations +* scrobbler: clever handling for radio stations +* scrobbler: (curl) fix operation with threaded resolver + (fixes: #16) +* scrobbler: (curl) fix segmentation fault with large response body +* Require libmpdclient-2.2 +* file: ignore trailing whitespace +* scrobbler: check the HTTP response status +* scrobbler: better HTTP error messages +* scrobbler: fix memory leak in as_songchange() +* log: use ISO8601 date format +* scrobbler: submit track numbers +* scrobbler: (journal) don't save "(null)" values +* scrobbler: (curl) prevent recursive read calls +* scrobbler: (curl) Implement libCURL timeouts +* stats: Add an automatic "karma" rating of songs, based on how often they are + skipped or fully listened to (fixes: #13). +* stats: Add a database column when a song was last played or skipped + (fixes: #13). * Be more conservative when exporting variables to the environment (fixes: #3) * stats: ability to set rating regardless of existing rating. diff -Nru mpdcron-0.3+git20110303/src/cron-config.h mpdcron-0.3+git20161228/src/cron-config.h --- mpdcron-0.3+git20110303/src/cron-config.h 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/src/cron-config.h 2018-04-05 13:05:11.000000000 +0000 @@ -1,7 +1,7 @@ /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ /* - * Copyright (c) 2009 Ali Polatel + * Copyright (c) 2009, 2013 Ali Polatel * * This file is part of the mpdcron mpd client. mpdcron is free software; * you can redistribute it and/or modify it under the terms of the GNU General @@ -43,8 +43,8 @@ #define DEFAULT_MPD_TIMEOUT 0 #define DEFAULT_LOG_LEVEL 0 -#define DEFAULT_DATE_FORMAT "%Y-%m-%d %H-%M-%S %Z" -#define DEFAULT_DATE_FORMAT_SIZE 64 +#define DEFAULT_DATE_FORMAT "%Y-%m-%dT%H:%M:%S%z" +#define DEFAULT_DATE_FORMAT_SIZE 32 #define MPDCRON_INTERNAL 1 #include "gmodule/gmodule.h" diff -Nru mpdcron-0.3+git20110303/src/cron-env.c mpdcron-0.3+git20161228/src/cron-env.c --- mpdcron-0.3+git20110303/src/cron-env.c 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/src/cron-env.c 2018-04-05 13:05:11.000000000 +0000 @@ -141,13 +141,16 @@ const char *tag; char *envstr; time_t t; + struct tm tm; char date[DEFAULT_DATE_FORMAT_SIZE] = { 0 }; g_setenv("MPD_SONG_URI", mpd_song_get_uri(song), 1); t = mpd_song_get_last_modified(song); - strftime(date, DEFAULT_DATE_FORMAT_SIZE, DEFAULT_DATE_FORMAT, localtime(&t)); - g_setenv("MPD_SONG_LAST_MODIFIED", date, 1); + if (localtime_r(&t, &tm)) { + strftime(date, DEFAULT_DATE_FORMAT_SIZE, DEFAULT_DATE_FORMAT, &tm); + g_setenv("MPD_SONG_LAST_MODIFIED", date, 1); + } envstr = g_strdup_printf("%u", mpd_song_get_duration(song)); g_setenv("MPD_SONG_DURATION", envstr, 1); @@ -203,11 +206,14 @@ { char *envstr; time_t t; + struct tm tm; char date[DEFAULT_DATE_FORMAT_SIZE] = { 0 }; t = mpd_stats_get_db_update_time(stats); - strftime(date, DEFAULT_DATE_FORMAT_SIZE, DEFAULT_DATE_FORMAT, localtime(&t)); - g_setenv("MPD_DATABASE_UPDATE_TIME", date, 1); + if (localtime_r(&t, &tm)) { + strftime(date, DEFAULT_DATE_FORMAT_SIZE, DEFAULT_DATE_FORMAT, &tm); + g_setenv("MPD_DATABASE_UPDATE_TIME", date, 1); + } envstr = g_strdup_printf("%d", mpd_stats_get_number_of_artists(stats)); g_setenv("MPD_DATABASE_ARTISTS", envstr, 1); diff -Nru mpdcron-0.3+git20110303/src/cron-event.c mpdcron-0.3+git20161228/src/cron-event.c --- mpdcron-0.3+git20110303/src/cron-event.c 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/src/cron-event.c 2018-04-05 13:05:11.000000000 +0000 @@ -1,7 +1,7 @@ /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ /* - * Copyright (c) 2009, 2010 Ali Polatel + * Copyright (c) 2009, 2010, 2013 Ali Polatel * * This file is part of the mpdcron mpd client. mpdcron is free software; * you can redistribute it and/or modify it under the terms of the GNU General @@ -225,7 +225,7 @@ /* A database update has started or finished. * Send status command and add the variables to the environment. */ - name = mpd_idle_name(MPD_IDLE_OPTIONS); + name = mpd_idle_name(MPD_IDLE_UPDATE); g_debug("Sending status command to Mpd server"); if ((status = mpd_run_status(conn)) == NULL) diff -Nru mpdcron-0.3+git20110303/src/cron-hooker.c mpdcron-0.3+git20161228/src/cron-hooker.c --- mpdcron-0.3+git20110303/src/cron-hooker.c 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/src/cron-hooker.c 2018-04-05 13:05:11.000000000 +0000 @@ -19,6 +19,7 @@ #include "cron-defs.h" +#include #include #include @@ -52,6 +53,10 @@ g_setenv(calls[i].env, envstr, 1); g_debug("Setting environment variable %s=%s", calls[i].env, envstr); g_free(envstr); + if (calls[i].ncalls == UINT_MAX) { + g_debug("Resetting counter for %s", calls[i].env); + calls[i].ncalls = 0; + } break; } } diff -Nru mpdcron-0.3+git20110303/src/cron-log.c mpdcron-0.3+git20161228/src/cron-log.c --- mpdcron-0.3+git20110303/src/cron-log.c 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/src/cron-log.c 2018-04-05 13:05:11.000000000 +0000 @@ -1,7 +1,7 @@ /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ /* - * Copyright (c) 2010 Ali Polatel + * Copyright (c) 2010, 2013 Ali Polatel * * This file is part of the mpdcron mpd client. mpdcron is free software; * you can redistribute it and/or modify it under the terms of the GNU General @@ -39,6 +39,7 @@ break; case G_LOG_LEVEL_MESSAGE: dlevel = LOG_NOTICE; + break; case G_LOG_LEVEL_INFO: dlevel = LOG_INFO; break; diff -Nru mpdcron-0.3+git20110303/src/gmodule/scrobbler/scrobbler-curl.c mpdcron-0.3+git20161228/src/gmodule/scrobbler/scrobbler-curl.c --- mpdcron-0.3+git20110303/src/gmodule/scrobbler/scrobbler-curl.c 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/src/gmodule/scrobbler/scrobbler-curl.c 2018-04-05 13:05:11.000000000 +0000 @@ -1,7 +1,7 @@ /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ /* - * Copyright (c) 2009, 2010 Ali Polatel + * Copyright (c) 2009, 2010, 2013 Ali Polatel * Based in part upon mpdscribble which is: * Copyright (C) 2008-2009 The Music Player Daemon Project * @@ -33,8 +33,8 @@ }; struct http_request { - http_client_callback_t *callback; - void *callback_data; + const struct http_client_handler *handler; + void *handler_ctx; /** the CURL easy handle */ CURL *curl; @@ -65,8 +65,28 @@ /** a linked list of all active HTTP requests */ GSList *requests; + + /** + * Set when inside http_multi_info_read(), to prevent + * recursive invocation. + */ + bool locked; + +#if LIBCURL_VERSION_NUM >= 0x070f04 + /** + * Did CURL give us a timeout? If yes, then we need to call + * curl_multi_perform(), even if there was no event on any + * file descriptor. + */ + bool timeout; +#endif } http_client; +static inline GQuark curl_quark(void) +{ + return g_quark_from_static_string("curl"); +} + /** * Frees all resources of a #http_request object. Also unregisters * the CURL easy handle from the CURL multi handle. This function @@ -115,22 +135,21 @@ http_client_update_fds(void) { fd_set rfds, wfds, efds; - int max_fd; - CURLMcode mcode; - GSList *fds; FD_ZERO(&rfds); FD_ZERO(&wfds); FD_ZERO(&efds); - mcode = curl_multi_fdset(http_client.multi, &rfds, &wfds, &efds, &max_fd); + int max_fd; + CURLMcode mcode = curl_multi_fdset(http_client.multi, &rfds, &wfds, + &efds, &max_fd); if (mcode != CURLM_OK) { g_warning("curl_multi_fdset() failed: %s\n", curl_multi_strerror(mcode)); return; } - fds = http_client.fds; + GSList *fds = http_client.fds; http_client.fds = NULL; while (fds != NULL) { @@ -172,27 +191,29 @@ /** * Aborts and frees a running HTTP request and report an error to its - * callback. + * handler. */ -static void http_request_abort(struct http_request *request) +static void http_request_abort(struct http_request *request, GError *error) { http_client.requests = g_slist_remove(http_client.requests, request); - request->callback(0, NULL, request->callback_data); + request->handler->error(error, request->handler_ctx); http_request_free(request); } /** - * Abort and free all HTTP requests, but don't invoke their callback - * functions. + * Abort and free all HTTP requests, but don't invoke their handler + * methods. */ static void -http_client_abort_all_requests(void) +http_client_abort_all_requests(GError *error) { while (http_client.requests != NULL) { struct http_request *request = http_client.requests->data; - http_request_abort(request); + http_request_abort(request, g_error_copy(error)); } + + g_error_free(error); } /** @@ -214,14 +235,31 @@ /** * A HTTP request is finished: invoke its callback and free it. */ -static void http_request_done(struct http_request *request, CURLcode result) +static void http_request_done(struct http_request *request, CURLcode result, long status) { - /* invoke the callback function */ - if (result == CURLE_OK) - request->callback(request->body->len, request->body->str, request->callback_data); - else { - g_warning("curl failed: %s", request->error); - request->callback(0, NULL, request->callback_data); + /* invoke the handler method */ + if (result == CURLE_WRITE_ERROR && + /* handle the postponed error that was caught in + http_request_writefunction() */ + request->body->len > MAX_RESPONSE_BODY) { + GError *error = + g_error_new_literal(curl_quark(), 0, + "response body is too large"); + request->handler->error(error, request->handler_ctx); + } else if (result != CURLE_OK) { + GError *error = g_error_new(curl_quark(), result, + "curl failed: %s", + request->error); + request->handler->error(error, request->handler_ctx); + } else if (status < 200 || status >= 300) { + GError *error = g_error_new(curl_quark(), 0, + "got HTTP status %ld", + status); + request->handler->error(error, request->handler_ctx); + } else { + request->handler->response(request->body->len, + request->body->str, + request->handler_ctx); } /* remove it from the list and free resources */ @@ -238,15 +276,24 @@ CURLMsg *msg; int msgs_in_queue; + assert(!http_client.locked); + http_client.locked = true; + while ((msg = curl_multi_info_read(http_client.multi, &msgs_in_queue)) != NULL) { if (msg->msg == CURLMSG_DONE) { struct http_request *request = http_client_find_request(msg->easy_handle); assert(request != NULL); - http_request_done(request, msg->data.result); + long status = 0; + curl_easy_getinfo(msg->easy_handle, + CURLINFO_RESPONSE_CODE, &status); + + http_request_done(request, msg->data.result, status); } } + + http_client.locked = false; } /** @@ -255,17 +302,18 @@ static bool http_multi_perform(void) { CURLMcode mcode; - int running_handles; do { + int running_handles; mcode = curl_multi_perform(http_client.multi, &running_handles); } while (mcode == CURLM_CALL_MULTI_PERFORM); if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) { - g_warning("curl_multi_perform() failed: %s\n", - curl_multi_strerror(mcode)); - http_client_abort_all_requests(); + GError *error = g_error_new(curl_quark(), mcode, + "curl_multi_perform() failed: %s", + curl_multi_strerror(mcode)); + http_client_abort_all_requests(error); return false; } @@ -280,6 +328,27 @@ { http_client_update_fds(); +#if LIBCURL_VERSION_NUM >= 0x070f04 + http_client.timeout = false; + + long timeout2; + CURLMcode mcode = curl_multi_timeout(http_client.multi, &timeout2); + if (mcode == CURLM_OK) { + if (timeout2 >= 0 && timeout2 < 10) + /* CURL 7.21.1 likes to report "timeout=0", + which means we're running in a busy loop. + Quite a bad idea to waste so much CPU. + Let's use a lower limit of 10ms. */ + timeout2 = 10; + + *timeout_ = timeout2; + + http_client.timeout = timeout2 >= 0; + } else + g_warning("curl_multi_timeout() failed: %s\n", + curl_multi_strerror(mcode)); +#endif + return FALSE; } @@ -288,6 +357,16 @@ */ static gboolean curl_source_check(G_GNUC_UNUSED GSource *source) { +#if LIBCURL_VERSION_NUM >= 0x070f04 + if (http_client.timeout) { + /* when a timeout has expired, we need to call + curl_multi_perform(), even if there was no file + descriptor event. */ + http_client.timeout = false; + return TRUE; + } +#endif + for (GSList *i = http_client.fds; i != NULL; i = i->next) { GPollFD *poll_fd = i->data; if (poll_fd->revents != 0) @@ -390,37 +469,37 @@ g_string_append_len(request->body, ptr, size * nmemb); if (request->body->len > MAX_RESPONSE_BODY) - /* response body too large */ - http_request_abort(request); + return 0; return size * nmemb; } void http_client_request(const char *url, const char *post_data, - http_client_callback_t *callback, void *data) + const struct http_client_handler *handler, void *ctx) { struct http_request *request = g_new(struct http_request, 1); - CURLcode code; - CURLMcode mcode; - bool success; - request->callback = callback; - request->callback_data = data; + request->handler = handler; + request->handler_ctx = ctx; /* create a CURL request */ request->curl = curl_easy_init(); if (request->curl == NULL) { g_free(request); - callback(0, NULL, data); + GError *error = g_error_new_literal(curl_quark(), 0, + "curl_easy_init() failed"); + handler->error(error, ctx); return; } - mcode = curl_multi_add_handle(http_client.multi, request->curl); + CURLMcode mcode = curl_multi_add_handle(http_client.multi, request->curl); if (mcode != CURLM_OK) { curl_easy_cleanup(request->curl); g_free(request); - callback(0, NULL, data); + GError *error = g_error_new_literal(curl_quark(), 0, + "curl_multi_add_handle() failed"); + handler->error(error, ctx); return; } @@ -441,12 +520,14 @@ curl_easy_setopt(request->curl, CURLOPT_POSTFIELDS, request->post_data); } - code = curl_easy_setopt(request->curl, CURLOPT_URL, url); + CURLcode code = curl_easy_setopt(request->curl, CURLOPT_URL, url); if (code != CURLE_OK) { curl_multi_remove_handle(http_client.multi, request->curl); curl_easy_cleanup(request->curl); g_free(request); - callback(0, NULL, data); + GError *error = g_error_new_literal(curl_quark(), code, + "curl_easy_setopt() failed"); + handler->error(error, ctx); return; } @@ -456,13 +537,15 @@ /* initiate the transfer */ - success = http_multi_perform(); - if (!success) { + if (!http_multi_perform()) { http_client.requests = g_slist_remove(http_client.requests, request); http_request_free(request); - callback(0, NULL, data); + GError *error = g_error_new_literal(curl_quark(), code, + "http_multi_perform() failed"); + handler->error(error, ctx); return; } - http_multi_info_read(); + if (!http_client.locked) + http_multi_info_read(); } diff -Nru mpdcron-0.3+git20110303/src/gmodule/scrobbler/scrobbler-defs.h mpdcron-0.3+git20161228/src/gmodule/scrobbler/scrobbler-defs.h --- mpdcron-0.3+git20110303/src/gmodule/scrobbler/scrobbler-defs.h 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/src/gmodule/scrobbler/scrobbler-defs.h 2018-04-05 13:05:11.000000000 +0000 @@ -1,7 +1,7 @@ /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ /* - * Copyright (c) 2009 Ali Polatel + * Copyright (c) 2009, 2013 Ali Polatel * Based in part upon mpdscribble which is: * Copyright (C) 2008-2009 The Music Player Daemon Project * Copyright (C) 2005-2008 Kuno Woudt @@ -39,13 +39,17 @@ char *artist; char *track; char *album; + char *number; char *mbid; char *time; int length; const char *source; }; -typedef void http_client_callback_t(size_t, const char *, void *); +struct http_client_handler { + void (*response)(size_t length, const char *data, void *ctx); + void (*error)(GError *error, void *ctx); +}; struct config { char *proxy; @@ -136,7 +140,7 @@ void http_client_request(const char *url, const char *post_data, - http_client_callback_t * callback, void *data); + const struct http_client_handler *handler, void *ctx); void @@ -146,12 +150,14 @@ as_cleanup(void); void -as_now_playing(const char *artist, const char *track, const char *album, - const char *mbid, const int length); +as_now_playing(const char *artist, const char *track, + const char *album, const char *number, + const char *mbid, const int length); void as_songchange(const char *file, const char *artist, const char *track, - const char *album, const char *mbid, const int length, - const char *time); + const char *album, const char *number, + const char *mbid, const int length, + const char *time); void as_save_cache(void); diff -Nru mpdcron-0.3+git20110303/src/gmodule/scrobbler/scrobbler-journal.c mpdcron-0.3+git20161228/src/gmodule/scrobbler/scrobbler-journal.c --- mpdcron-0.3+git20110303/src/gmodule/scrobbler/scrobbler-journal.c 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/src/gmodule/scrobbler/scrobbler-journal.c 2018-04-05 13:05:11.000000000 +0000 @@ -1,7 +1,7 @@ /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ /* - * Copyright (c) 2009, 2010 Ali Polatel + * Copyright (c) 2009, 2010, 2013 Ali Polatel * Based in part upon mpdscribble which is: * Copyright (C) 2008-2009 The Music Player Daemon Project * Copyright (C) 2005-2008 Kuno Woudt @@ -22,6 +22,7 @@ #include "scrobbler-defs.h" +#include #include #include #include @@ -31,15 +32,30 @@ static int journal_file_empty; -static void journal_write_record(gpointer data, gpointer user_data) +static void +journal_write_string(FILE *file, char field, const char *value) +{ + if (value != NULL) + fprintf(file, "%c = %s\n", field, value); +} + +static void +journal_write_record(gpointer data, gpointer user_data) { struct record *record = data; FILE *file = user_data; + assert(record->source != NULL); + + journal_write_string(file, 'a', record->artist); + journal_write_string(file, 't', record->track); + journal_write_string(file, 'b', record->album); + journal_write_string(file, 'n', record->number); + journal_write_string(file, 'm', record->mbid); + journal_write_string(file, 'i', record->time); + fprintf(file, - "a = %s\nt = %s\nb = %s\nm = %s\n" - "i = %s\nl = %i\no = %s\n\n", record->artist, - record->track, record->album, record->mbid, record->time, + "l = %i\no = %s\n\n", record->length, record->source); } @@ -178,6 +194,8 @@ record.track = g_strdup(value); else if (!strcmp("b", key)) record.album = g_strdup(value); + else if (!strcmp("n", key)) + record.number = g_strdup(value); else if (!strcmp("m", key)) record.mbid = g_strdup(value); else if (!strcmp("i", key)) diff -Nru mpdcron-0.3+git20110303/src/gmodule/scrobbler/scrobbler-module.c mpdcron-0.3+git20161228/src/gmodule/scrobbler/scrobbler-module.c --- mpdcron-0.3+git20110303/src/gmodule/scrobbler/scrobbler-module.c 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/src/gmodule/scrobbler/scrobbler-module.c 2018-04-05 13:05:11.000000000 +0000 @@ -1,7 +1,7 @@ /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ /* - * Copyright (c) 2009, 2010 Ali Polatel + * Copyright (c) 2009, 2010, 2013, 2016 Ali Polatel * Based in part upon mpdscribble which is: * Copyright (C) 2008-2009 The Music Player Daemon Project * Copyright (C) 2005-2008 Kuno Woudt @@ -32,9 +32,12 @@ #include #include +#include "../utils.h" + /* Globals */ static unsigned last_id = -1; static bool was_paused = 0; +static bool is_remote = 0; static struct mpd_song *prev = NULL; static GTimer *timer = NULL; static int save_source_id = -1; @@ -63,26 +66,32 @@ song_changed(const struct mpd_song *song) { g_assert(song != NULL); + char *artist, *title; + const char *uri = mpd_song_get_uri(song); - if (mpd_song_get_tag(song, MPD_TAG_ARTIST, 0) == NULL || - mpd_song_get_tag(song, MPD_TAG_TITLE, 0) == NULL) { - g_message("New song detected with tags missing (%s)", - mpd_song_get_uri(song)); + is_remote = !!strstr(uri, "://"); + if (is_remote) + g_message("New song detected with URL (%s)", uri); + + if (!song_check_tags(song, &artist, &title)) { + g_message("New song detected with tags missing (%s)", uri); g_timer_start(timer); return; } g_timer_start(timer); g_debug("New song detected (%s - %s), id: %u, pos: %u", - mpd_song_get_tag(song, MPD_TAG_ARTIST, 0), - mpd_song_get_tag(song, MPD_TAG_TITLE, 0), + artist, title, mpd_song_get_id(song), mpd_song_get_pos(song)); - as_now_playing(mpd_song_get_tag(song, MPD_TAG_ARTIST, 0), - mpd_song_get_tag(song, MPD_TAG_TITLE, 0), + as_now_playing(artist, title, mpd_song_get_tag(song, MPD_TAG_ALBUM, 0), + mpd_song_get_tag(song, MPD_TAG_TRACK, 0), mpd_song_get_tag(song, MPD_TAG_MUSICBRAINZ_TRACKID, 0), mpd_song_get_duration(song)); + + g_free(artist); + g_free(title); } static void @@ -95,41 +104,44 @@ song_ended(const struct mpd_song *song) { int elapsed; + char *artist, *title; g_assert(song != NULL); elapsed = g_timer_elapsed(timer, NULL); - if (mpd_song_get_tag(song, MPD_TAG_ARTIST, 0) == NULL || - mpd_song_get_tag(song, MPD_TAG_TITLE, 0) == NULL) { + if (!song_check_tags(song, &artist, &title)) { g_message("Song (%s) has missing tags, skipping", mpd_song_get_uri(song)); return; } else if (!played_long_enough(elapsed, mpd_song_get_duration(song))) { g_message("Song (%s - %s), id: %u, pos: %u not played long enough, skipping", - mpd_song_get_tag(song, MPD_TAG_ARTIST, 0), - mpd_song_get_tag(song, MPD_TAG_TITLE, 0), + artist, title, mpd_song_get_id(song), mpd_song_get_pos(song)); + g_free(artist); + g_free(title); return; } g_debug("Submitting old song (%s - %s), id: %u, pos: %u", - mpd_song_get_tag(song, MPD_TAG_ARTIST, 0), - mpd_song_get_tag(song, MPD_TAG_TITLE, 0), + artist, title, mpd_song_get_id(song), mpd_song_get_pos(song)); /* FIXME: libmpdclient doesn't have any way to fetch the musicbrainz id. */ as_songchange(mpd_song_get_uri(song), - mpd_song_get_tag(song, MPD_TAG_ARTIST, 0), - mpd_song_get_tag(song, MPD_TAG_TITLE, 0), + artist, title, mpd_song_get_tag(song, MPD_TAG_ALBUM, 0), + mpd_song_get_tag(song, MPD_TAG_TRACK, 0), mpd_song_get_tag(song, MPD_TAG_MUSICBRAINZ_TRACKID, 0), mpd_song_get_duration(song) > 0 ? mpd_song_get_duration(song) : g_timer_elapsed(timer, NULL), NULL); + + g_free(artist); + g_free(title); } static void @@ -218,11 +230,16 @@ } /* Submit the previous song */ - if (prev != NULL && (song == NULL || mpd_song_get_id(prev) != mpd_song_get_id(song))) + if (prev != NULL && + (song == NULL || mpd_song_get_id(prev) != mpd_song_get_id(song) || + (is_remote && + mpd_song_get_tag(song, MPD_TAG_TITLE, 0) != mpd_song_get_tag(prev, MPD_TAG_TITLE, 0)))) song_ended(prev); if (song != NULL) { - if (mpd_song_get_id(song) != last_id) { + if (mpd_song_get_id(song) != last_id || + (is_remote && prev != NULL && + mpd_song_get_tag(song, MPD_TAG_TITLE, 0) != mpd_song_get_tag(prev, MPD_TAG_TITLE, 0))) { /* New song. */ song_started(song); last_id = mpd_song_get_id(song); diff -Nru mpdcron-0.3+git20110303/src/gmodule/scrobbler/scrobbler-record.c mpdcron-0.3+git20161228/src/gmodule/scrobbler/scrobbler-record.c --- mpdcron-0.3+git20110303/src/gmodule/scrobbler/scrobbler-record.c 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/src/gmodule/scrobbler/scrobbler-record.c 2018-04-05 13:05:11.000000000 +0000 @@ -1,7 +1,7 @@ /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ /* - * Copyright (c) 2009 Ali Polatel + * Copyright (c) 2009, 2013 Ali Polatel * Based in part upon mpdscribble which is: * Copyright (C) 2008-2009 The Music Player Daemon Project * Copyright (C) 2005-2008 Kuno Woudt @@ -29,6 +29,7 @@ dest->artist = g_strdup(src->artist); dest->track = g_strdup(src->track); dest->album = g_strdup(src->album); + dest->number = g_strdup(src->number); dest->mbid = g_strdup(src->mbid); dest->time = g_strdup(src->time); dest->length = src->length; @@ -47,6 +48,7 @@ g_free(record->artist); g_free(record->track); g_free(record->album); + g_free(record->number); g_free(record->mbid); g_free(record->time); } @@ -62,6 +64,7 @@ record->artist = NULL; record->track = NULL; record->album = NULL; + record->number = NULL; record->mbid = NULL; record->time = NULL; record->length = 0; diff -Nru mpdcron-0.3+git20110303/src/gmodule/scrobbler/scrobbler-submit.c mpdcron-0.3+git20161228/src/gmodule/scrobbler/scrobbler-submit.c --- mpdcron-0.3+git20110303/src/gmodule/scrobbler/scrobbler-submit.c 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/src/gmodule/scrobbler/scrobbler-submit.c 2018-04-05 13:05:11.000000000 +0000 @@ -1,7 +1,7 @@ /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ /* - * Copyright (c) 2009, 2010 Ali Polatel + * Copyright (c) 2009, 2010, 2013 Ali Polatel * Based in part upon mpdscribble which is: * Copyright (C) 2008-2009 The Music Player Daemon Project * Copyright (C) 2005-2008 Kuno Woudt @@ -285,7 +285,7 @@ return line; } -static void scrobbler_handshake_callback(size_t length, const char *response, void *data) +static void scrobbler_handshake_response(size_t length, const char *response, void *data) { struct scrobbler *scrobbler = data; const char *end = response + length; @@ -297,13 +297,6 @@ scrobbler->state = SCROBBLER_STATE_NOTHING; - if (!length) { - g_warning("[%s] handshake timed out", scrobbler->config->name); - scrobbler_increase_interval(scrobbler); - scrobbler_schedule_handshake(scrobbler); - return; - } - line = next_line(&response, end); ret = scrobbler_parse_handshake_response(scrobbler, line); g_free(line); @@ -348,6 +341,29 @@ } static void +scrobbler_handshake_error(GError *error, void *data) +{ + struct scrobbler *scrobbler = data; + + assert(scrobbler != NULL); + assert(scrobbler->state == SCROBBLER_STATE_HANDSHAKE); + + scrobbler->state = SCROBBLER_STATE_NOTHING; + + g_warning("[%s] handshake error: %s", + scrobbler->config->name, error->message); + g_error_free(error); + + scrobbler_increase_interval(scrobbler); + scrobbler_schedule_handshake(scrobbler); +} + +static const struct http_client_handler scrobbler_handshake_handler = { + .response = scrobbler_handshake_response, + .error = scrobbler_handshake_error, +}; + +static void scrobbler_queue_remove_oldest(GQueue *queue, unsigned count) { assert(count > 0); @@ -359,7 +375,7 @@ } static void -scrobbler_submit_callback(size_t length, const char *response, void *data) +scrobbler_submit_response(size_t length, const char *response, void *data) { struct scrobbler *scrobbler = data; char *newline; @@ -367,14 +383,6 @@ assert(scrobbler->state == SCROBBLER_STATE_SUBMITTING); scrobbler->state = SCROBBLER_STATE_READY; - if (!length) { - scrobbler->pending = 0; - g_warning("[%s] submit timed out", scrobbler->config->name); - scrobbler_increase_interval(scrobbler); - scrobbler_schedule_submit(scrobbler); - return; - } - newline = memchr(response, '\n', length); if (newline != NULL) length = newline - response; @@ -411,6 +419,28 @@ } } +static void +scrobbler_submit_error(GError *error, void *data) +{ + struct scrobbler *scrobbler = data; + + assert(scrobbler->state == SCROBBLER_STATE_SUBMITTING); + + scrobbler->state = SCROBBLER_STATE_READY; + + g_warning("[%s] submit error: %s", + scrobbler->config->name, error->message); + g_error_free(error); + + scrobbler_increase_interval(scrobbler); + scrobbler_schedule_submit(scrobbler); +} + +static const struct http_client_handler scrobbler_submit_handler = { + .response = scrobbler_submit_response, + .error = scrobbler_submit_error, +}; + char *as_timestamp(void) { /* create timestamp for 1.2 protocol. */ @@ -456,7 +486,8 @@ // notice ("handshake url:\n%s", url); - http_client_request(url->str, NULL, &scrobbler_handshake_callback, scrobbler); + http_client_request(url->str, NULL, + &scrobbler_handshake_handler, scrobbler); g_string_free(url, true); } @@ -486,6 +517,7 @@ static void scrobbler_send_now_playing(struct scrobbler *scrobbler, const char *artist, const char *track, const char *album, + const char *number, const char *mbid, const int length) { GString *post_data; @@ -504,14 +536,15 @@ add_var(post_data, "t", track); add_var(post_data, "b", album); add_var(post_data, "l", len); - add_var(post_data, "n", ""); + add_var(post_data, "n", number); add_var(post_data, "m", mbid); g_message("[%s] sending 'now playing' notification", scrobbler->config->name); g_debug("[%s] post data: %s", scrobbler->config->name, post_data->str); g_debug("[%s] url: %s", scrobbler->config->name, scrobbler->nowplay_url); - http_client_request(scrobbler->nowplay_url, post_data->str, scrobbler_submit_callback, scrobbler); + http_client_request(scrobbler->nowplay_url, post_data->str, + &scrobbler_submit_handler, scrobbler); g_string_free(post_data, true); } @@ -529,13 +562,15 @@ } void as_now_playing(const char *artist, const char *track, - const char *album, const char *mbid, const int length) + const char *album, const char *number, + const char *mbid, const int length) { struct record record; record.artist = g_strdup(artist); record.track = g_strdup(track); record.album = g_strdup(album); + record.number = g_strdup(number); record.mbid = g_strdup(mbid); record.time = NULL; record.length = length; @@ -563,6 +598,7 @@ scrobbler->now_playing.artist, scrobbler->now_playing.track, scrobbler->now_playing.album, + scrobbler->now_playing.number, scrobbler->now_playing.mbid, scrobbler->now_playing.length); @@ -590,7 +626,7 @@ add_var_i(post_data, "o", count, song->source); add_var_i(post_data, "r", count, ""); add_var_i(post_data, "b", count, song->album); - add_var_i(post_data, "n", count, ""); + add_var_i(post_data, "n", count, song->number); add_var_i(post_data, "m", count, song->mbid); count++; @@ -603,8 +639,8 @@ scrobbler->config->name, scrobbler->submit_url); scrobbler->pending = count; - http_client_request(scrobbler->submit_url, - post_data->str, &scrobbler_submit_callback, scrobbler); + http_client_request(scrobbler->submit_url, post_data->str, + &scrobbler_submit_handler, scrobbler); g_string_free(post_data, true); } @@ -622,8 +658,9 @@ void as_songchange(const char *file, const char *artist, const char *track, - const char *album, const char *mbid, const int length, - const char *time2) + const char *album, const char *number, + const char *mbid, const int length, + const char *time2) { struct record record; @@ -651,6 +688,7 @@ record.artist = g_strdup(artist); record.track = g_strdup(track); record.album = g_strdup(album); + record.number = g_strdup(number); record.mbid = g_strdup(mbid); record.length = length; record.time = time2 ? g_strdup(time2) : as_timestamp(); @@ -661,6 +699,8 @@ record.track, record.length); g_slist_foreach(scrobblers, scrobbler_push_callback, &record); + + record_deinit(&record); } static void scrobbler_new_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) diff -Nru mpdcron-0.3+git20110303/src/gmodule/scrobbler/TODO mpdcron-0.3+git20161228/src/gmodule/scrobbler/TODO --- mpdcron-0.3+git20110303/src/gmodule/scrobbler/TODO 1970-01-01 00:00:00.000000000 +0000 +++ mpdcron-0.3+git20161228/src/gmodule/scrobbler/TODO 2018-04-05 13:05:11.000000000 +0000 @@ -0,0 +1,6 @@ +mpdscribble: +ea3893d23bb2c25e6190146d6fbbf70eaac3355c +e47f2c7ed5994f5192f4382bd39029268d7c5e63 +446207c4d20e12c5938f587503762452d67f578f +698ebb4ebde53593ad6f6f11a658abe826a24907 +27187d3585c59f3b8a0a08b02afb49416ed876b1 diff -Nru mpdcron-0.3+git20110303/src/gmodule/stats/eugene-connection.c mpdcron-0.3+git20161228/src/gmodule/stats/eugene-connection.c --- mpdcron-0.3+git20110303/src/gmodule/stats/eugene-connection.c 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/src/gmodule/stats/eugene-connection.c 2018-04-05 13:05:11.000000000 +0000 @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -58,6 +59,26 @@ } /** + * Timezone independent variant of mktime(). See timegm(3). + */ +static time_t +mktime_utc(struct tm *tm) +{ + time_t ret; + char *tz; + tz = getenv("TZ"); + setenv("TZ", "", 1); + tzset(); + ret = mktime(tm); + if (tz) + setenv("TZ", tz, 1); + else + unsetenv("TZ"); + tzset(); + return ret; +} + +/** * Receiving data */ static char * @@ -236,6 +257,7 @@ int ret; gsize length; gchar *line; + const char *key, *value; struct mpdcron_entity *album = NULL; for (;;) { @@ -268,39 +290,41 @@ return false; default: /* We have a pair! */ - if (strcmp(conn->parser->u.pair.name, "id") == 0) { + key = conn->parser->u.pair.name; + value = conn->parser->u.pair.value; + if (strcmp(key, "id") == 0) { if (album != NULL) *values = g_slist_prepend(*values, album); album = g_new0(struct mpdcron_entity, 1); - album->id = atoi(conn->parser->u.pair.value); + album->id = atoi(value); } - else if (strcmp(conn->parser->u.pair.name, "Album") == 0) { + else if (strcmp(key, "Album") == 0) { g_assert(album != NULL); - album->name = g_strdup(conn->parser->u.pair.value); + album->name = g_strdup(value); } - else if (strcmp(conn->parser->u.pair.name, "Artist") == 0) { + else if (strcmp(key, "Artist") == 0) { g_assert(album != NULL); - album->artist = g_strdup(conn->parser->u.pair.value); + album->artist = g_strdup(value); } - else if (strcmp(conn->parser->u.pair.name, "Love") == 0) { + else if (strcmp(key, "Love") == 0) { g_assert(album != NULL); - album->love = atoi(conn->parser->u.pair.value); + album->love = atoi(value); } - else if (strcmp(conn->parser->u.pair.name, "Kill") == 0) { + else if (strcmp(key, "Kill") == 0) { g_assert(album != NULL); - album->kill = atoi(conn->parser->u.pair.value); + album->kill = atoi(value); } - else if (strcmp(conn->parser->u.pair.name, "Rating") == 0) { + else if (strcmp(key, "Rating") == 0) { g_assert(album != NULL); - album->rating = atoi(conn->parser->u.pair.value); + album->rating = atoi(value); } - else if (strcmp(conn->parser->u.pair.name, "Play Count") == 0) { + else if (strcmp(key, "Play Count") == 0) { g_assert(album != NULL); - album->play_count = atoi(conn->parser->u.pair.value); + album->play_count = atoi(value); } - else if (strcmp(conn->parser->u.pair.name, "Tag") == 0) { + else if (strcmp(key, "Tag") == 0) { g_assert(album != NULL); - album->tags = g_slist_prepend(album->tags, g_strdup(conn->parser->u.pair.value)); + album->tags = g_slist_prepend(album->tags, g_strdup(value)); } else { g_set_error(&conn->error, connection_quark(), @@ -323,6 +347,7 @@ int ret; gsize length; gchar *line; + const char *key, *value; struct mpdcron_entity *artist = NULL; for (;;) { @@ -355,35 +380,37 @@ return false; default: /* We have a pair! */ - if (strcmp(conn->parser->u.pair.name, "id") == 0) { + key = conn->parser->u.pair.name; + value = conn->parser->u.pair.value; + if (strcmp(key, "id") == 0) { if (artist != NULL) *values = g_slist_prepend(*values, artist); artist = g_new0(struct mpdcron_entity, 1); - artist->id = atoi(conn->parser->u.pair.value); + artist->id = atoi(value); } - else if (strcmp(conn->parser->u.pair.name, "Artist") == 0) { + else if (strcmp(key, "Artist") == 0) { g_assert(artist != NULL); - artist->name = g_strdup(conn->parser->u.pair.value); + artist->name = g_strdup(value); } - else if (strcmp(conn->parser->u.pair.name, "Love") == 0) { + else if (strcmp(key, "Love") == 0) { g_assert(artist != NULL); - artist->love = atoi(conn->parser->u.pair.value); + artist->love = atoi(value); } - else if (strcmp(conn->parser->u.pair.name, "Kill") == 0) { + else if (strcmp(key, "Kill") == 0) { g_assert(artist != NULL); - artist->kill = atoi(conn->parser->u.pair.value); + artist->kill = atoi(value); } - else if (strcmp(conn->parser->u.pair.name, "Rating") == 0) { + else if (strcmp(key, "Rating") == 0) { g_assert(artist != NULL); - artist->rating = atoi(conn->parser->u.pair.value); + artist->rating = atoi(value); } - else if (strcmp(conn->parser->u.pair.name, "Play Count") == 0) { + else if (strcmp(key, "Play Count") == 0) { g_assert(artist != NULL); - artist->play_count = atoi(conn->parser->u.pair.value); + artist->play_count = atoi(value); } - else if (strcmp(conn->parser->u.pair.name, "Tag") == 0) { + else if (strcmp(key, "Tag") == 0) { g_assert(artist != NULL); - artist->tags = g_slist_prepend(artist->tags, g_strdup(conn->parser->u.pair.value)); + artist->tags = g_slist_prepend(artist->tags, g_strdup(value)); } else { g_set_error(&conn->error, connection_quark(), @@ -406,6 +433,7 @@ int ret; gsize length; gchar *line; + const char *key, *value; struct mpdcron_entity *genre = NULL; for (;;) { @@ -438,36 +466,38 @@ return false; default: /* We have a pair! */ - if (strcmp(conn->parser->u.pair.name, "id") == 0) { + key = conn->parser->u.pair.name; + value = conn->parser->u.pair.value; + if (strcmp(key, "id") == 0) { if (genre != NULL) *values = g_slist_prepend(*values, genre); genre = g_new0(struct mpdcron_entity, 1); - genre->id = atoi(conn->parser->u.pair.value); + genre->id = atoi(value); } - else if (strcmp(conn->parser->u.pair.name, "Genre") == 0) { + else if (strcmp(key, "Genre") == 0) { g_assert(genre != NULL); - genre->name = g_strdup(conn->parser->u.pair.value); + genre->name = g_strdup(value); } - else if (strcmp(conn->parser->u.pair.name, "Love") == 0) { + else if (strcmp(key, "Love") == 0) { g_assert(genre != NULL); - genre->love = atoi(conn->parser->u.pair.value); + genre->love = atoi(value); } - else if (strcmp(conn->parser->u.pair.name, "Kill") == 0) { + else if (strcmp(key, "Kill") == 0) { g_assert(genre != NULL); - genre->kill = atoi(conn->parser->u.pair.value); + genre->kill = atoi(value); } - else if (strcmp(conn->parser->u.pair.name, "Rating") == 0) { + else if (strcmp(key, "Rating") == 0) { g_assert(genre != NULL); - genre->rating = atoi(conn->parser->u.pair.value); + genre->rating = atoi(value); } - else if (strcmp(conn->parser->u.pair.name, "Play Count") == 0) { + else if (strcmp(key, "Play Count") == 0) { g_assert(genre != NULL); - genre->play_count = atoi(conn->parser->u.pair.value); + genre->play_count = atoi(value); } - else if (strcmp(conn->parser->u.pair.name, "Tag") == 0) { + else if (strcmp(key, "Tag") == 0) { g_assert(genre != NULL); genre->tags = g_slist_prepend(genre->tags, - g_strdup(conn->parser->u.pair.value)); + g_strdup(value)); } else { g_set_error(&conn->error, connection_quark(), @@ -490,7 +520,9 @@ int ret; gsize length; gchar *line; + const char *key, *value; struct mpdcron_song *song = NULL; + struct tm last_played; for (;;) { line = mpdcron_recv_line(conn, &length); @@ -522,35 +554,47 @@ return false; default: /* We have a pair! */ - if (strcmp(conn->parser->u.pair.name, "id") == 0) { + key = conn->parser->u.pair.name; + value = conn->parser->u.pair.value; + if (strcmp(key, "id") == 0) { if (song != NULL) *values = g_slist_prepend(*values, song); song = g_new0(struct mpdcron_song, 1); - song->id = atoi(conn->parser->u.pair.value); + song->id = atoi(value); + } + else if (strcmp(key, "file") == 0) { + g_assert(song != NULL); + song->uri = g_strdup(value); } - else if (strcmp(conn->parser->u.pair.name, "file") == 0) { + else if (strcmp(key, "Love") == 0) { g_assert(song != NULL); - song->uri = g_strdup(conn->parser->u.pair.value); + song->love = atoi(value); } - else if (strcmp(conn->parser->u.pair.name, "Love") == 0) { + else if (strcmp(key, "Kill") == 0) { g_assert(song != NULL); - song->love = atoi(conn->parser->u.pair.value); + song->kill = atoi(value); } - else if (strcmp(conn->parser->u.pair.name, "Kill") == 0) { + else if (strcmp(key, "Rating") == 0) { g_assert(song != NULL); - song->kill = atoi(conn->parser->u.pair.value); + song->rating = atoi(value); } - else if (strcmp(conn->parser->u.pair.name, "Rating") == 0) { + else if (strcmp(key, "Play Count") == 0) { g_assert(song != NULL); - song->rating = atoi(conn->parser->u.pair.value); + song->play_count = atoi(value); } - else if (strcmp(conn->parser->u.pair.name, "Play Count") == 0) { + else if (strcmp(key, "Karma") == 0) { g_assert(song != NULL); - song->play_count = atoi(conn->parser->u.pair.value); + song->karma = atoi(value); } - else if (strcmp(conn->parser->u.pair.name, "Tag") == 0) { + else if (strcmp(key, "Last Played") == 0) { g_assert(song != NULL); - song->tags = g_slist_prepend(song->tags, g_strdup(conn->parser->u.pair.value)); + memset(&last_played, 0, sizeof(last_played)); + strptime(value, "%Y-%m-%dT%H:%M:%S%z", &last_played); + song->last_played = mktime_utc(&last_played); + } + else if (strcmp(key, "Tag") == 0) { + g_assert(song != NULL); + song->tags = g_slist_prepend(song->tags, g_strdup(value)); } else { g_set_error(&conn->error, connection_quark(), @@ -1058,54 +1102,6 @@ } bool -mpdcron_count_absolute_album_expr(struct mpdcron_connection *conn, const char *expr, - const char *rating, int *changes) -{ - g_assert(conn != NULL); - g_assert(changes != NULL); - - if (!mpdcron_send_command(conn, "count_absolute_album", expr, rating, NULL)) - return false; - return mpdcron_parse_changes(conn, changes); -} - -bool -mpdcron_count_absolute_artist_expr(struct mpdcron_connection *conn, const char *expr, - const char *rating, int *changes) -{ - g_assert(conn != NULL); - g_assert(changes != NULL); - - if (!mpdcron_send_command(conn, "count_absolute_artist", expr, rating, NULL)) - return false; - return mpdcron_parse_changes(conn, changes); -} - -bool -mpdcron_count_absolute_genre_expr(struct mpdcron_connection *conn, const char *expr, - const char *rating, int *changes) -{ - g_assert(conn != NULL); - g_assert(changes != NULL); - - if (!mpdcron_send_command(conn, "count_absolute_genre", expr, rating, NULL)) - return false; - return mpdcron_parse_changes(conn, changes); -} - -bool -mpdcron_count_absolute_expr(struct mpdcron_connection *conn, const char *expr, - const char *rating, int *changes) -{ - g_assert(conn != NULL); - g_assert(changes != NULL); - - if (!mpdcron_send_command(conn, "count_absolute", expr, rating, NULL)) - return false; - return mpdcron_parse_changes(conn, changes); -} - -bool mpdcron_addtag_expr(struct mpdcron_connection *conn, const char *expr, const char *tag, int *changes) { @@ -1320,3 +1316,16 @@ return false; return mpdcron_parse_changes(conn, changes); } + +bool +mpdcron_karma_expr(struct mpdcron_connection *conn, const char *expr, + const char *karma, int *changes) +{ + g_assert(conn != NULL); + g_assert(karma != NULL); + g_assert(changes != NULL); + + if (!mpdcron_send_command(conn, "karma", expr, karma, NULL)) + return false; + return mpdcron_parse_changes(conn, changes); +} diff -Nru mpdcron-0.3+git20110303/src/gmodule/stats/eugene-defs.h mpdcron-0.3+git20161228/src/gmodule/stats/eugene-defs.h --- mpdcron-0.3+git20110303/src/gmodule/stats/eugene-defs.h 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/src/gmodule/stats/eugene-defs.h 2018-04-05 13:05:11.000000000 +0000 @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -74,6 +75,8 @@ int love; int kill; int rating; + int karma; + time_t last_played; GSList *tags; }; @@ -236,26 +239,6 @@ int *changes); bool -mpdcron_count_absolute_album_expr(struct mpdcron_connection *conn, - const char *rating, const char *expr, - int *changes); - -bool -mpdcron_count_absolute_artist_expr(struct mpdcron_connection *conn, - const char *rating, const char *expr, - int *changes); - -bool -mpdcron_count_absolute_genre_expr(struct mpdcron_connection *conn, - const char *rating, const char *expr, - int *changes); - -bool -mpdcron_count_absolute_expr(struct mpdcron_connection *conn, - const char *rating, const char *expr, - int *changes); - -bool mpdcron_addtag_expr(struct mpdcron_connection *conn, const char *expr, const char *tag, int *changes); @@ -319,6 +302,10 @@ mpdcron_count_expr(struct mpdcron_connection *conn, const char *expr, const char *count, int *changes); +bool +mpdcron_karma_expr(struct mpdcron_connection *conn, const char *expr, + const char *karma, int *changes); + char * quote(const char *src); @@ -344,9 +331,6 @@ cmd_rate_absolute(int argc, char **argv); int -cmd_count_absolute(int argc, char **argv); - -int cmd_list(int argc, char **argv); int @@ -364,6 +348,9 @@ int cmd_count(int argc, char **argv); +int +cmd_karma(int argc, char **argv); + void eulog(int level, const char *fmt, ...); diff -Nru mpdcron-0.3+git20110303/src/gmodule/stats/eugene-karma.c mpdcron-0.3+git20161228/src/gmodule/stats/eugene-karma.c --- mpdcron-0.3+git20110303/src/gmodule/stats/eugene-karma.c 1970-01-01 00:00:00.000000000 +0000 +++ mpdcron-0.3+git20161228/src/gmodule/stats/eugene-karma.c 2018-04-05 13:05:11.000000000 +0000 @@ -0,0 +1,129 @@ +/* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ + +/* + * Copyright (c) 2009, 2010 Ali Polatel + * + * This file is part of the mpdcron mpd client. mpdcron is free software; + * you can redistribute it and/or modify it under the terms of the GNU General + * Public License version 2, as published by the Free Software Foundation. + * + * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "eugene-defs.h" + +#include +#include + +#include + +static int +karma_song(struct mpdcron_connection *conn, const char *expr, const char *karma) +{ + int changes; + char *esc_uri, *query_uri; + struct mpd_song *song; + + if (expr != NULL) { + if (!mpdcron_karma_expr(conn, expr, karma, &changes)) { + eulog(LOG_ERR, "Failed to change karma of song: %s", + conn->error->message); + return 1; + } + } + else { + if ((song = load_current_song()) == NULL) + return 1; + + esc_uri = quote(mpd_song_get_uri(song)); + query_uri = g_strdup_printf("uri=%s", esc_uri); + g_free(esc_uri); + mpd_song_free(song); + + if (!mpdcron_karma_expr(conn, query_uri, karma, &changes)) { + eulog(LOG_ERR, "Failed to change karma of current " + "playing song: %s", + conn->error->message); + g_free(query_uri); + return 1; + } + g_free(query_uri); + } + printf("Modified %d entries\n", changes); + return 0; +} + +static int +cmd_karma_internal(const char *expr, const char *karma) +{ + int port, ret; + const char *hostname, *password; + struct mpdcron_connection *conn; + + hostname = g_getenv(ENV_MPDCRON_HOST) + ? g_getenv(ENV_MPDCRON_HOST) + : DEFAULT_HOSTNAME; + port = g_getenv(ENV_MPDCRON_PORT) + ? atoi(g_getenv(ENV_MPDCRON_PORT)) + : DEFAULT_PORT; + password = g_getenv(ENV_MPDCRON_PASSWORD); + + conn = mpdcron_connection_new(hostname, port); + if (conn->error != NULL) { + eulog(LOG_ERR, "Failed to connect: %s", conn->error->message); + mpdcron_connection_free(conn); + return 1; + } + + if (password != NULL) { + if (!mpdcron_password(conn, password)) { + eulog(LOG_ERR, "Authentication failed: %s", conn->error->message); + mpdcron_connection_free(conn); + return 1; + } + } + + ret = karma_song(conn, expr, karma); + mpdcron_connection_free(conn); + return ret; +} + +int +cmd_karma(int argc, char **argv) +{ + int ret; + GError *error = NULL; + GOptionContext *ctx; + + ctx = g_option_context_new("KARMA [EXPRESSION]"); + g_option_context_set_summary(ctx, "eugene-karma-"VERSION GITHEAD + " - Set karma rating of song"); + g_option_context_set_description(ctx, + "By default this command works on the current playing song.\n" + "For more information about the expression syntax, see:\n" + "http://www.sqlite.org/lang_expr.html"); + if (!g_option_context_parse(ctx, &argc, &argv, &error)) { + g_printerr("Option parsing failed: %s\n", error->message); + g_error_free(error); + g_option_context_free(ctx); + return 1; + } + g_option_context_free(ctx); + + if (argc <= 1) { + g_printerr("No karma given\n"); + ret = 1; + } + else if (argc > 2) + ret = cmd_karma_internal(argv[2], argv[1]); + else + ret = cmd_karma_internal(NULL, argv[1]); + return ret; +} diff -Nru mpdcron-0.3+git20110303/src/gmodule/stats/eugene-listinfo.c mpdcron-0.3+git20161228/src/gmodule/stats/eugene-listinfo.c --- mpdcron-0.3+git20110303/src/gmodule/stats/eugene-listinfo.c 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/src/gmodule/stats/eugene-listinfo.c 2018-04-05 13:05:11.000000000 +0000 @@ -182,6 +182,8 @@ listinfo_song(struct mpdcron_connection *conn, const char *expr) { GSList *values, *walk; + struct tm *last_played; + char time_stamp[25]; values = NULL; if (expr != NULL) { @@ -214,9 +216,17 @@ for (walk = values; walk != NULL; walk = g_slist_next(walk)) { struct mpdcron_song *s = walk->data; - printf("%d: Play_Count:%d Love:%d Kill:%d Rating:%d %s\n", + printf("%d: Play_Count:%d Love:%d Kill:%d Rating:%d Karma:%d ", s->id, s->play_count, s->love, - s->kill, s->rating, s->uri); + s->kill, s->rating, s->karma); + if (s->last_played != 0) { + last_played = localtime(&s->last_played); + strftime(time_stamp, sizeof(time_stamp), + "%Y-%m-%dT%H:%M:%S%z", last_played); + printf("Last_Played:%s ", time_stamp); + } + puts(s->uri); + putchar('\n'); g_free(s->uri); g_free(s); } diff -Nru mpdcron-0.3+git20110303/src/gmodule/stats/eugene-main.c mpdcron-0.3+git20161228/src/gmodule/stats/eugene-main.c --- mpdcron-0.3+git20110303/src/gmodule/stats/eugene-main.c 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/src/gmodule/stats/eugene-main.c 2018-04-05 13:05:11.000000000 +0000 @@ -52,13 +52,13 @@ "list List song/artist/album/genre\n" "listinfo List song/artist/album/genre\n" "count Change play count of song/artist/album/genre\n" -"countabs Change play count (absolute fashion) of song/artist/album/genre\n" "hate Hate song/artist/album/genre\n" "love Love song/artist/album/genre\n" "kill Kill song/artist/album/genre\n" "unkill Unkill song/artist/album/genre\n" "rate Rate song/artist/album/genre\n" "rateabs Rate (absolute fashion) song/artist/album/genre\n" +"karma Set karma rating of song\n" "addtag Add tag to song/artist/album/genre\n" "rmtag Remove tag from song/artist/album/genre\n" "listtags List tags of song/artist/album/genre\n" @@ -100,8 +100,8 @@ return cmd_listtags(argc, argv); else if (strncmp(argv[0], "count", 6) == 0) return cmd_count(argc, argv); - else if (strncmp(argv[0], "countabs", 9) == 0) - return cmd_count_absolute(argc, argv); + else if (strncmp(argv[0], "karma", 6) == 0) + return cmd_karma(argc, argv); fprintf(stderr, "Unknown command `%s'\n", argv[0]); usage(stderr, 1); } diff -Nru mpdcron-0.3+git20110303/src/gmodule/stats/eugene-utils.c mpdcron-0.3+git20161228/src/gmodule/stats/eugene-utils.c --- mpdcron-0.3+git20110303/src/gmodule/stats/eugene-utils.c 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/src/gmodule/stats/eugene-utils.c 2018-04-05 13:05:11.000000000 +0000 @@ -1,7 +1,7 @@ /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ /* - * Copyright (c) 2009, 2010 Ali Polatel + * Copyright (c) 2009, 2010, 2016 Ali Polatel * * This file is part of the mpdcron mpd client. mpdcron is free software; * you can redistribute it and/or modify it under the terms of the GNU General @@ -18,6 +18,7 @@ */ #include "eugene-defs.h" +#include "walrus-defs.h" /* To add current song to the database. */ #include #include @@ -55,6 +56,9 @@ struct mpd_connection *conn; struct mpd_song *song; + char *dbpath; + GError *error; + hostname = g_getenv(ENV_MPD_HOST) ? g_getenv(ENV_MPD_HOST) : "localhost"; @@ -96,6 +100,31 @@ return NULL; } + if (!getenv("EUGENE_NODB")) { + /* ^^ Eugene may be remote to the mpdcron stats database, so let + * the user skip this if need be. + * TODO: Make this a server command. */ + if (!db_initialized()) { + dbpath = xload_dbpath(); + + error = NULL; + if (!db_init(dbpath, true, false, &error)) { + g_printerr("Failed to load database `%s': %s\n", dbpath, error->message); + g_error_free(error); + g_free(dbpath); + } + g_free(dbpath); + } + + error = NULL; + if (db_initialized() && !db_process(song, false, -1, &error)) { + g_printerr("Failed to process song %s: %s\n", + mpd_song_get_uri(song), + error->message); + g_error_free(error); + } + } + mpd_connection_free(conn); return song; } diff -Nru mpdcron-0.3+git20110303/src/gmodule/stats/Makefile.am mpdcron-0.3+git20161228/src/gmodule/stats/Makefile.am --- mpdcron-0.3+git20110303/src/gmodule/stats/Makefile.am 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/src/gmodule/stats/Makefile.am 2018-04-05 13:05:11.000000000 +0000 @@ -30,15 +30,15 @@ noinst_HEADERS+= eugene-defs.h bin_PROGRAMS+= eugene eugene_SOURCES= eugene-connection.c eugene-addtag.c \ - eugene-count.c eugene-kill.c eugene-list.c \ + eugene-count.c eugene-karma.c eugene-kill.c eugene-list.c \ eugene-listinfo.c eugene-listtags.c eugene-love.c \ eugene-rate.c eugene-rate-absolute.c eugene-rmtag.c \ - eugene-count-absolute.c eugene-utils.c eugene-main.c - + eugene-utils.c eugene-main.c \ + stats-sqlite.c walrus-utils.c # Hack to workaround the error: # object x created both with libtool and without. # See: http://bit.ly/libtool_both eugene_CFLAGS= $(AM_CFLAGS) -eugene_LDADD= $(gio_unix_LIBS) $(gio_LIBS) $(glib_LIBS) $(libmpdclient_LIBS) +eugene_LDADD= $(gio_unix_LIBS) $(gio_LIBS) $(glib_LIBS) $(libmpdclient_LIBS) $(sqlite_LIBS) bin_SCRIPTS= homescrape diff -Nru mpdcron-0.3+git20110303/src/gmodule/stats/stats-command.c mpdcron-0.3+git20161228/src/gmodule/stats/stats-command.c --- mpdcron-0.3+git20110303/src/gmodule/stats/stats-command.c 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/src/gmodule/stats/stats-command.c 2018-04-05 13:05:11.000000000 +0000 @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -367,6 +368,8 @@ { GError *error; GSList *values, *walk; + struct tm *utc; + char last_played[25]; g_assert(argc == 2); @@ -386,6 +389,13 @@ command_puts(client, "Love: %d", song->love); command_puts(client, "Kill: %d", song->kill); command_puts(client, "Rating: %d", song->rating); + command_puts(client, "Karma: %d", song->karma); + if (song->last_played != 0) { + utc = gmtime(&(song->last_played)); + strftime(last_played, sizeof(last_played), + "%Y-%m-%dT%H:%M:%S%z", utc); + command_puts(client, "Last Played: %s", last_played); + } db_song_data_free(song); } g_slist_free(values); @@ -916,174 +926,6 @@ } static enum command_return -handle_count_absolute(struct client *client, int argc, char **argv) -{ - int changes; - long rating; - char *endptr; - GError *error; - - g_assert(argc == 3); - - /* Convert second argument to number */ - errno = 0; - endptr = NULL; - rating = strtol(argv[2], &endptr, 10); - if (errno != 0) { - command_error(client, ACK_ERROR_ARG, - "Failed to convert to number: %s", - g_strerror(errno)); - return COMMAND_RETURN_ERROR; - } - else if (endptr == argv[2]) { - command_error(client, ACK_ERROR_ARG, "No digits found"); - return COMMAND_RETURN_ERROR; - } - else if (rating > INT_MAX || rating < INT_MIN) { - command_error(client, ACK_ERROR_ARG, - "Number too %s", - (rating > INT_MAX) ? "big" : "small"); - return COMMAND_RETURN_ERROR; - } - - error = NULL; - if (!db_count_absolute_song_expr(argv[1], (int)rating, &changes, &error)) { - command_error(client, error->code, "%s", error->message); - g_error_free(error); - return COMMAND_RETURN_ERROR; - } - command_puts(client, "changes: %d", changes); - command_ok(client); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_count_absolute_artist(struct client *client, int argc, char **argv) -{ - int changes; - long rating; - char *endptr; - GError *error; - - g_assert(argc == 3); - - /* Convert second argument to number */ - errno = 0; - endptr = NULL; - rating = strtol(argv[2], &endptr, 10); - if (errno != 0) { - command_error(client, ACK_ERROR_ARG, - "Failed to convert to number: %s", - g_strerror(errno)); - return COMMAND_RETURN_ERROR; - } - else if (endptr == argv[2]) { - command_error(client, ACK_ERROR_ARG, "No digits found"); - return COMMAND_RETURN_ERROR; - } - else if (rating > INT_MAX || rating < INT_MIN) { - command_error(client, ACK_ERROR_ARG, - "Number too %s", - (rating > INT_MAX) ? "big" : "small"); - return COMMAND_RETURN_ERROR; - } - - error = NULL; - if (!db_count_absolute_artist_expr(argv[1], (int)rating, &changes, &error)) { - command_error(client, error->code, "%s", error->message); - g_error_free(error); - return COMMAND_RETURN_ERROR; - } - command_puts(client, "changes: %d", changes); - command_ok(client); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_count_absolute_album(struct client *client, int argc, char **argv) -{ - int changes; - long rating; - char *endptr; - GError *error; - - g_assert(argc == 3); - - /* Convert second argument to number */ - errno = 0; - endptr = NULL; - rating = strtol(argv[2], &endptr, 10); - if (errno != 0) { - command_error(client, ACK_ERROR_ARG, - "Failed to convert to number: %s", - g_strerror(errno)); - return COMMAND_RETURN_ERROR; - } - else if (endptr == argv[2]) { - command_error(client, ACK_ERROR_ARG, "No digits found"); - return COMMAND_RETURN_ERROR; - } - else if (rating > INT_MAX || rating < INT_MIN) { - command_error(client, ACK_ERROR_ARG, - "Number too %s", - (rating > INT_MAX) ? "big" : "small"); - return COMMAND_RETURN_ERROR; - } - - error = NULL; - if (!db_count_absolute_album_expr(argv[1], (int)rating, &changes, &error)) { - command_error(client, error->code, "%s", error->message); - g_error_free(error); - return COMMAND_RETURN_ERROR; - } - command_puts(client, "changes: %d", changes); - command_ok(client); - return COMMAND_RETURN_OK; -} - -static enum command_return -handle_count_absolute_genre(struct client *client, int argc, char **argv) -{ - int changes; - long rating; - char *endptr; - GError *error; - - g_assert(argc == 3); - - /* Convert second argument to number */ - errno = 0; - endptr = NULL; - rating = strtol(argv[2], &endptr, 10); - if (errno != 0) { - command_error(client, ACK_ERROR_ARG, - "Failed to convert to number: %s", - g_strerror(errno)); - return COMMAND_RETURN_ERROR; - } - else if (endptr == argv[2]) { - command_error(client, ACK_ERROR_ARG, "No digits found"); - return COMMAND_RETURN_ERROR; - } - else if (rating > INT_MAX || rating < INT_MIN) { - command_error(client, ACK_ERROR_ARG, - "Number too %s", - (rating > INT_MAX) ? "big" : "small"); - return COMMAND_RETURN_ERROR; - } - - error = NULL; - if (!db_count_absolute_genre_expr(argv[1], (int)rating, &changes, &error)) { - command_error(client, error->code, "%s", error->message); - g_error_free(error); - return COMMAND_RETURN_ERROR; - } - command_puts(client, "changes: %d", changes); - command_ok(client); - return COMMAND_RETURN_OK; -} - -static enum command_return handle_addtag(struct client *client, int argc, char **argv) { int changes; @@ -1533,6 +1375,47 @@ } static enum command_return +handle_karma(struct client *client, int argc, char **argv) +{ + int changes; + long karma; + char *endptr; + GError *error; + + g_assert(argc == 3); + + /* Convert second argument to number */ + errno = 0; + endptr = NULL; + karma = strtol(argv[2], &endptr, 10); + if (errno != 0) { + command_error(client, ACK_ERROR_ARG, + "Failed to convert to number: %s", + g_strerror(errno)); + return COMMAND_RETURN_ERROR; + } + else if (endptr == argv[2]) { + command_error(client, ACK_ERROR_ARG, "No digits found"); + return COMMAND_RETURN_ERROR; + } + else if (karma >= 100 || karma <= 0) { + command_error(client, ACK_ERROR_ARG, "Karma '%ld' should be a " + "percentage between 0 and 100", karma); + return COMMAND_RETURN_ERROR; + } + + error = NULL; + if (!db_karma_song_expr(argv[1], (int)karma, &changes, &error)) { + command_error(client, error->code, "%s", error->message); + g_error_free(error); + return COMMAND_RETURN_ERROR; + } + command_puts(client, "changes: %d", changes); + command_ok(client); + return COMMAND_RETURN_OK; +} + +static enum command_return handle_password(struct client *client, G_GNUC_UNUSED int argc, char **argv) { gpointer perm = g_hash_table_lookup(globalconf.passwords, argv[1]); @@ -1553,12 +1436,6 @@ { "addtag_genre", PERMISSION_UPDATE, 2, 2, handle_addtag_genre }, { "count", PERMISSION_UPDATE, 2, 2, handle_count }, - - { "count_absolute", PERMISSION_UPDATE, 2, 2, handle_count_absolute }, - { "count_absolute_album", PERMISSION_UPDATE, 2, 2, handle_count_absolute_album }, - { "count_absolute_artist", PERMISSION_UPDATE, 2, 2, handle_count_absolute_artist }, - { "count_absolute_genre", PERMISSION_UPDATE, 2, 2, handle_count_absolute_genre }, - { "count_album", PERMISSION_UPDATE, 2, 2, handle_count_album }, { "count_artist", PERMISSION_UPDATE, 2, 2, handle_count_artist }, { "count_genre", PERMISSION_UPDATE, 2, 2, handle_count_genre }, @@ -1568,6 +1445,8 @@ { "hate_artist", PERMISSION_UPDATE, 1, 1, handle_love_artist }, { "hate_genre", PERMISSION_UPDATE, 1, 1, handle_love_genre }, + { "karma", PERMISSION_UPDATE, 2, 2, handle_karma }, + { "kill", PERMISSION_UPDATE, 1, 1, handle_kill }, { "kill_album", PERMISSION_UPDATE, 1, 1, handle_kill_album }, { "kill_artist", PERMISSION_UPDATE, 1, 1, handle_kill_artist }, @@ -1774,6 +1653,14 @@ if (cmd) ret = cmd->handler(client, argc, argv); + /* Disable the authorizer again */ + if (!db_set_authorizer(NULL, NULL, &error)) { + command_error(client, error->code, "%s", error->message); + current_command = NULL; + g_error_free(error); + return COMMAND_RETURN_ERROR; + } + current_command = NULL; return ret; } diff -Nru mpdcron-0.3+git20110303/src/gmodule/stats/stats-module.c mpdcron-0.3+git20161228/src/gmodule/stats/stats-module.c --- mpdcron-0.3+git20110303/src/gmodule/stats/stats-module.c 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/src/gmodule/stats/stats-module.c 2018-04-05 13:05:11.000000000 +0000 @@ -1,7 +1,7 @@ /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ /* - * Copyright (c) 2009, 2010 Ali Polatel + * Copyright (c) 2009, 2010, 2016 Ali Polatel * Based in part upon mpdscribble which is: * Copyright (C) 2008-2009 The Music Player Daemon Project * Copyright (C) 2005-2008 Kuno Woudt @@ -29,6 +29,7 @@ static unsigned last_id = -1; static bool was_paused = 0; +static bool is_remote = 0; static struct mpd_song *prev = NULL; static GTimer *timer = NULL; @@ -41,10 +42,17 @@ static void song_changed(const struct mpd_song *song) { + const char *uri; + g_assert(song != NULL); g_timer_start(timer); + uri = mpd_song_get_uri(song); + is_remote = !!strstr(uri, "://"); + if (is_remote) + g_message("New song detected with URL (%s)", uri); + g_debug("New song detected (%s - %s), id: %u, pos: %u", mpd_song_get_tag(song, MPD_TAG_ARTIST, 0), mpd_song_get_tag(song, MPD_TAG_TITLE, 0), @@ -60,20 +68,19 @@ static void song_ended(const struct mpd_song *song) { - int elapsed; + bool long_enough; + int elapsed, song_duration, percent_played; GError *error; g_assert(song != NULL); elapsed = g_timer_elapsed(timer, NULL); - - if (!played_long_enough(elapsed, mpd_song_get_duration(song))) { - g_debug("Song (%s - %s), id: %u, pos: %u not played long enough, skipping", - mpd_song_get_tag(song, MPD_TAG_ARTIST, 0), - mpd_song_get_tag(song, MPD_TAG_TITLE, 0), - mpd_song_get_id(song), mpd_song_get_pos(song)); - return; - } + song_duration = mpd_song_get_duration(song); + long_enough = played_long_enough(elapsed, song_duration); + if (song_duration > 0) + percent_played = elapsed * 100 / song_duration; + else + percent_played = 100; g_debug("Saving old song (%s - %s), id: %u, pos: %u", mpd_song_get_tag(song, MPD_TAG_ARTIST, 0), @@ -81,7 +88,7 @@ mpd_song_get_id(song), mpd_song_get_pos(song)); error = NULL; - if (!db_process(song, true, &error)) { + if (!db_process(song, long_enough, percent_played, &error)) { g_warning("Saving old song failed: %s", error->message); g_error_free(error); } @@ -131,7 +138,7 @@ g_debug("Initializing"); /* Load configuration */ - if (file_load(conf, fd) < 0) + if (!file_load(conf, fd)) return MPDCRON_INIT_FAILURE; /* Initialize database */ @@ -196,11 +203,16 @@ } /* Submit the previous song */ - if (prev != NULL && (song == NULL || mpd_song_get_id(prev) != mpd_song_get_id(song))) + if (prev != NULL && + (song == NULL || mpd_song_get_id(prev) != mpd_song_get_id(song) || + (is_remote && + mpd_song_get_tag(song, MPD_TAG_TITLE, 0) != mpd_song_get_tag(prev, MPD_TAG_TITLE, 0)))) song_ended(prev); if (song != NULL) { - if (mpd_song_get_id(song) != last_id) { + if (mpd_song_get_id(song) != last_id || + (is_remote && prev != NULL && + mpd_song_get_tag(song, MPD_TAG_TITLE, 0) != mpd_song_get_tag(prev, MPD_TAG_TITLE, 0))) { /* New song. */ song_started(song); last_id = mpd_song_get_id(song); diff -Nru mpdcron-0.3+git20110303/src/gmodule/stats/stats-sqlite.c mpdcron-0.3+git20161228/src/gmodule/stats/stats-sqlite.c --- mpdcron-0.3+git20110303/src/gmodule/stats/stats-sqlite.c 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/src/gmodule/stats/stats-sqlite.c 2018-04-05 13:05:11.000000000 +0000 @@ -1,7 +1,7 @@ /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ /* - * Copyright (c) 2009, 2010 Ali Polatel + * Copyright (c) 2009, 2010, 2016 Ali Polatel * * This file is part of the mpdcron mpd client. mpdcron is free software; * you can redistribute it and/or modify it under the terms of the GNU General @@ -17,16 +17,20 @@ * Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "stats-defs.h" #include "stats-sqlite.h" #include #include #include +#include #include #include #include +#include "../utils.h" + static sqlite3 *gdb = NULL; enum { @@ -34,21 +38,28 @@ SQL_GET_VERSION, SQL_SET_ENCODING, - SQL_DB_CREATE_SONG, - SQL_DB_CREATE_ARTIST, - SQL_DB_CREATE_ALBUM, - SQL_DB_CREATE_GENRE, -}; - -enum { SQL_BEGIN_TRANSACTION, SQL_END_TRANSACTION, + SQL_ROLLBACK_TRANSACTION, SQL_PRAGMA_SYNC_ON, SQL_PRAGMA_SYNC_OFF, SQL_VACUUM, +}; +enum { + SQL_DB_CREATE_SONG, + SQL_DB_CREATE_ARTIST, + SQL_DB_CREATE_ALBUM, + SQL_DB_CREATE_GENRE, +}; + +enum { + SQL_DB_MIGRATE_10_11, +}; + +enum { SQL_HAS_SONG, SQL_HAS_ARTIST, SQL_HAS_ALBUM, @@ -60,18 +71,38 @@ SQL_INSERT_GENRE, SQL_UPDATE_SONG, + SQL_UPDATE_SONG_PLAYED, SQL_UPDATE_ARTIST, SQL_UPDATE_ALBUM, SQL_UPDATE_GENRE, }; -#define DB_VERSION 10 +#define DB_VERSION 11 +#define DB_MINIMUM_VERSION 10 +#define DB_MIGRATE_STMT_COUNT 2 +#define DB_KARMA_DEFAULT 50 +#define DB_KARMA_DEFAULT_STR "50" + +/* Generic database schema independent statements */ static const char * const db_sql_maint[] = { - [SQL_SET_VERSION] = "PRAGMA user_version = 10;", + [SQL_SET_VERSION] = "PRAGMA user_version = 11;", [SQL_GET_VERSION] = "PRAGMA user_version;", [SQL_SET_ENCODING] = "PRAGMA encoding = \"UTF-8\";", + [SQL_BEGIN_TRANSACTION] = "BEGIN TRANSACTION;", + [SQL_END_TRANSACTION] = "END TRANSACTION;", + [SQL_ROLLBACK_TRANSACTION] = "ROLLBACK TRANSACTION;", + + [SQL_PRAGMA_SYNC_ON] = "PRAGMA synchronous=ON;", + [SQL_PRAGMA_SYNC_OFF] = "PRAGMA synchronous=OFF;", + + [SQL_VACUUM] = "VACUUM;", +}; +static sqlite3_stmt *db_stmt_maint[G_N_ELEMENTS(db_sql_maint)] = { NULL }; + +/* Statements for creating a new database */ +static const char * const db_sql_create[] = { [SQL_DB_CREATE_SONG] = "create table song(\n" "\tid INTEGER PRIMARY KEY,\n" @@ -95,7 +126,14 @@ "\tdisc TEXT,\n" "\tmb_artistid TEXT,\n" "\tmb_albumid TEXT,\n" - "\tmb_trackid TEXT);\n", + "\tmb_trackid TEXT,\n" + "\tlast_played INTEGER,\n" + "\tkarma INTEGER\n" + "\t\tNOT NULL\n" + "\t\tCONSTRAINT karma_percent " + "CHECK (karma >= 0 AND karma <= 100)\n" + "\t\tDEFAULT " DB_KARMA_DEFAULT_STR "\n" + ");\n", [SQL_DB_CREATE_ARTIST] = "create table artist(\n" "\tid INTEGER PRIMARY KEY,\n" @@ -125,17 +163,26 @@ "\tkill INTEGER,\n" "\trating INTEGER);", }; -static sqlite3_stmt *db_stmt_maint[G_N_ELEMENTS(db_sql_maint)] = { NULL }; +static sqlite3_stmt *db_stmt_create[G_N_ELEMENTS(db_sql_maint)] = { NULL }; -static const char * const db_sql[] = { - [SQL_BEGIN_TRANSACTION] = "BEGIN TRANSACTION;", - [SQL_END_TRANSACTION] = "END TRANSACTION;", - - [SQL_PRAGMA_SYNC_ON] = "PRAGMA synchronous=ON;", - [SQL_PRAGMA_SYNC_OFF] = "PRAGMA synchronous=OFF;", - - [SQL_VACUUM] = "VACUUM;", +static const char * const db_sql_migrate[][DB_MIGRATE_STMT_COUNT] = { + [SQL_DB_MIGRATE_10_11] = { + "alter table song add column\n" + "\tlast_played INTEGER\n;", + "alter table song add column\n" + "\tkarma INTEGER\n" + "\t\tNOT NULL\n" + "\t\tCONSTRAINT karma_percent " + "CHECK (karma >= 0 AND karma <= 100)\n" + "\t\tDEFAULT 50\n;", + }, +}; +static sqlite3_stmt +*db_stmt_migrate[G_N_ELEMENTS(db_sql_migrate)][DB_MIGRATE_STMT_COUNT] = { + { NULL }, +}; +static const char * const db_sql[] = { [SQL_HAS_SONG] = "select id from song where uri=?", [SQL_HAS_ARTIST] = "select id from artist where name=?", [SQL_HAS_ALBUM] = "select id from album where name=?", @@ -144,17 +191,19 @@ [SQL_INSERT_SONG] = "insert into song (" "play_count," - "love, kill, rating, tags," - "uri, duration, last_modified," + "love, kill, rating, karma, tags," + "uri, duration, last_modified, last_played," "artist, album, title," "track, name, genre," "date, composer, performer, disc," "mb_artistid, mb_albumid, mb_trackid)" - " values (?, 0, 0, 0, ':'," - "?, ?, ?, ?," + " values (?," + "0, 0, 0, ?, ':'," "?, ?, ?, ?," + "?, ?, ?," + "?, ?, ?," "?, ?, ?, ?," - "?, ?, ?, ?);", + "?, ?, ?);", [SQL_INSERT_ARTIST] = "insert into artist (" "play_count, name," @@ -191,6 +240,29 @@ "mb_albumid=?," "mb_trackid=?" " where id=?;", + [SQL_UPDATE_SONG_PLAYED] = + "update song " + "set play_count = play_count + ?," + "uri=?," + "duration=?," + "last_modified=?," + "last_played = strftime('%s')," + /* Round up to be able to reach 100% */ + "karma = (karma + ? + 1) / 2," + "artist=?," + "album=?," + "title=?," + "track=?," + "name=?," + "genre=?," + "date=?," + "composer=?," + "performer=?," + "disc=?," + "mb_artistid=?," + "mb_albumid=?," + "mb_trackid=?" + " where id=?;", [SQL_UPDATE_ARTIST] = "update artist " "set play_count = play_count + ?," @@ -467,11 +539,10 @@ * Database Inserts/Updates */ static bool -db_insert_artist(const struct mpd_song *song, bool increment, - GError **error) +db_insert_artist(const char *artist, bool increment, GError **error) { g_assert(gdb != NULL); - g_assert(song != NULL); + g_assert(artist != NULL); /* Reset the statement to its initial state */ if (sqlite3_reset(db_stmt[SQL_INSERT_ARTIST]) != SQLITE_OK) { @@ -483,7 +554,7 @@ if (sqlite3_bind_int(db_stmt[SQL_INSERT_ARTIST], 1, increment ? 1 : 0) != SQLITE_OK || sqlite3_bind_text(db_stmt[SQL_INSERT_ARTIST], - 2, mpd_song_get_tag(song, MPD_TAG_ARTIST, 0), + 2, artist, -1, SQLITE_STATIC) != SQLITE_OK) { g_set_error(error, db_quark(), ACK_ERROR_DATABASE_BIND, "sqlite3_bind: %s", sqlite3_errmsg(gdb)); @@ -545,7 +616,8 @@ /* Reset the statement to its initial state */ if (sqlite3_reset(db_stmt[SQL_INSERT_GENRE]) != SQLITE_OK) { g_set_error(error, db_quark(), ACK_ERROR_DATABASE_RESET, - "sqlite3_reset: %s", sqlite3_errmsg(gdb)); + "sqlite3_reset (%s): %s", + db_sql[SQL_INSERT_GENRE], sqlite3_errmsg(gdb)); return false; } @@ -569,9 +641,12 @@ } static bool -db_insert_song(const struct mpd_song *song, bool increment, - GError **error) +db_insert_song(const struct mpd_song *song, const char *artist, const char *title, + bool increment, int percent_played, GError **error) { + int karma; + bool played; + g_assert(gdb != NULL); g_assert(song != NULL); @@ -581,51 +656,67 @@ return false; } + if (percent_played >= 0 && percent_played <= 100) { + /* Round up to be able to reach 100% */ + karma = (DB_KARMA_DEFAULT + percent_played + 1) / 2; + played = true; + } else { + karma = DB_KARMA_DEFAULT; + played = false; + } + if (sqlite3_bind_int(db_stmt[SQL_INSERT_SONG], 1, increment ? 1 : 0) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 2, + || sqlite3_bind_int(db_stmt[SQL_INSERT_SONG], 2, karma) + != SQLITE_OK + || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 3, mpd_song_get_uri(song), -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_int(db_stmt[SQL_INSERT_SONG], 3, - mpd_song_get_duration(song)) != SQLITE_OK || sqlite3_bind_int(db_stmt[SQL_INSERT_SONG], 4, + mpd_song_get_duration(song)) != SQLITE_OK + || sqlite3_bind_int(db_stmt[SQL_INSERT_SONG], 5, mpd_song_get_last_modified(song)) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 5, - mpd_song_get_tag(song, MPD_TAG_ARTIST, 0), + || (played ? + sqlite3_bind_int(db_stmt[SQL_INSERT_SONG], 6, + time(NULL)) : + sqlite3_bind_null(db_stmt[SQL_INSERT_SONG], 6) + ) != SQLITE_OK + || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 7, + artist, -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 6, + || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 8, mpd_song_get_tag(song, MPD_TAG_ALBUM, 0), -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 7, - mpd_song_get_tag(song, MPD_TAG_TITLE, 0), + || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 9, + title, -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 8, + || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 10, mpd_song_get_tag(song, MPD_TAG_TRACK, 0), -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 9, + || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 11, mpd_song_get_tag(song, MPD_TAG_NAME, 0), -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 10, + || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 12, mpd_song_get_tag(song, MPD_TAG_GENRE, 0), -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 11, + || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 13, mpd_song_get_tag(song, MPD_TAG_DATE, 0), -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 12, + || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 14, mpd_song_get_tag(song, MPD_TAG_COMPOSER, 0), -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 13, + || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 15, mpd_song_get_tag(song, MPD_TAG_PERFORMER, 0), -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 14, + || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 16, mpd_song_get_tag(song, MPD_TAG_DISC, 0), -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 15, + || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 17, mpd_song_get_tag(song, MPD_TAG_MUSICBRAINZ_ARTISTID, 0), -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 16, + || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 18, mpd_song_get_tag(song, MPD_TAG_MUSICBRAINZ_ALBUMID, 0), -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 17, + || sqlite3_bind_text(db_stmt[SQL_INSERT_SONG], 19, mpd_song_get_tag(song, MPD_TAG_MUSICBRAINZ_TRACKID, 0), -1, SQLITE_STATIC) != SQLITE_OK) { g_set_error(error, db_quark(), ACK_ERROR_DATABASE_BIND, @@ -643,11 +734,10 @@ } static bool -db_update_artist(const struct mpd_song *song, int id, - bool increment, GError **error) +db_update_artist(const char *artist, int id, bool increment, GError **error) { g_assert(gdb != NULL); - g_assert(song != NULL); + g_assert(artist != NULL); /* Reset the statement to its initial state */ if (sqlite3_reset(db_stmt[SQL_UPDATE_ARTIST]) != SQLITE_OK) { @@ -659,7 +749,7 @@ if (sqlite3_bind_int(db_stmt[SQL_UPDATE_ARTIST], 1, increment ? 1 : 0) != SQLITE_OK || sqlite3_bind_text(db_stmt[SQL_UPDATE_ARTIST], - 2, mpd_song_get_tag(song, MPD_TAG_ARTIST, 0), + 2, artist, -1, SQLITE_STATIC) != SQLITE_OK || sqlite3_bind_int(db_stmt[SQL_UPDATE_ARTIST], 3, id) != SQLITE_OK) { @@ -751,75 +841,93 @@ } static bool -db_update_song(const struct mpd_song *song, int id, - bool increment, GError **error) +db_update_song(const struct mpd_song *song, const char *artist, const char *title, + int id, bool increment, int percent_played, GError **error) { + int update; + bool played; int ret; + int parameter = 1; g_assert(gdb != NULL); g_assert(song != NULL); - if (sqlite3_reset(db_stmt[SQL_UPDATE_SONG]) != SQLITE_OK) { + if (percent_played >= 0 && percent_played <= 100) { + played = true; + update = SQL_UPDATE_SONG_PLAYED; + } else { + played = false; + update = SQL_UPDATE_SONG; + } + + if (sqlite3_reset(db_stmt[update]) != SQLITE_OK) { g_set_error(error, db_quark(), ACK_ERROR_DATABASE_RESET, "sqlite3_reset: %s", sqlite3_errmsg(gdb)); return false; } - if (sqlite3_bind_int(db_stmt[SQL_UPDATE_SONG], 1, increment ? 1 : 0) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_UPDATE_SONG], 2, + if (sqlite3_bind_int(db_stmt[update], parameter++, increment ? 1 : 0) + != SQLITE_OK + || sqlite3_bind_text(db_stmt[update], parameter++, mpd_song_get_uri(song), -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_int(db_stmt[SQL_UPDATE_SONG], 3, + || sqlite3_bind_int(db_stmt[update], parameter++, mpd_song_get_duration(song)) != SQLITE_OK - || sqlite3_bind_int(db_stmt[SQL_UPDATE_SONG], 4, + || sqlite3_bind_int(db_stmt[update], parameter++, mpd_song_get_last_modified(song)) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_UPDATE_SONG], 5, - mpd_song_get_tag(song, MPD_TAG_ARTIST, 0), + || (played ? + sqlite3_bind_int(db_stmt[update], parameter++, + percent_played) != SQLITE_OK : + false) + || sqlite3_bind_text(db_stmt[update], parameter++, + artist, -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_UPDATE_SONG], 6, + || sqlite3_bind_text(db_stmt[update], parameter++, mpd_song_get_tag(song, MPD_TAG_ALBUM, 0), -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_UPDATE_SONG], 7, - mpd_song_get_tag(song, MPD_TAG_TITLE, 0), + || sqlite3_bind_text(db_stmt[update], parameter++, + title, -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_UPDATE_SONG], 8, + || sqlite3_bind_text(db_stmt[update], parameter++, mpd_song_get_tag(song, MPD_TAG_TRACK, 0), -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_UPDATE_SONG], 9, + || sqlite3_bind_text(db_stmt[update], parameter++, mpd_song_get_tag(song, MPD_TAG_NAME, 0), -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_UPDATE_SONG], 10, + || sqlite3_bind_text(db_stmt[update], parameter++, mpd_song_get_tag(song, MPD_TAG_GENRE, 0), -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_UPDATE_SONG], 11, + || sqlite3_bind_text(db_stmt[update], parameter++, mpd_song_get_tag(song, MPD_TAG_DATE, 0), -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_UPDATE_SONG], 12, + || sqlite3_bind_text(db_stmt[update], parameter++, mpd_song_get_tag(song, MPD_TAG_COMPOSER, 0), -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_UPDATE_SONG], 13, + || sqlite3_bind_text(db_stmt[update], parameter++, mpd_song_get_tag(song, MPD_TAG_PERFORMER, 0), -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_UPDATE_SONG], 14, + || sqlite3_bind_text(db_stmt[update], parameter++, mpd_song_get_tag(song, MPD_TAG_DISC, 0), -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_UPDATE_SONG], 15, + || sqlite3_bind_text(db_stmt[update], parameter++, mpd_song_get_tag(song, MPD_TAG_MUSICBRAINZ_ARTISTID, 0), -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_UPDATE_SONG], 16, + || sqlite3_bind_text(db_stmt[update], parameter++, mpd_song_get_tag(song, MPD_TAG_MUSICBRAINZ_ALBUMID, 0), -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_text(db_stmt[SQL_UPDATE_SONG], 17, + || sqlite3_bind_text(db_stmt[update], parameter++, mpd_song_get_tag(song, MPD_TAG_MUSICBRAINZ_TRACKID, 0), -1, SQLITE_STATIC) != SQLITE_OK - || sqlite3_bind_int(db_stmt[SQL_UPDATE_SONG], 18, id)) { + || sqlite3_bind_int(db_stmt[update], parameter++, id) + != SQLITE_OK) + { g_set_error(error, db_quark(), ACK_ERROR_DATABASE_BIND, "sqlite3_bind: %s", sqlite3_errmsg(gdb)); return false; } do { - ret = sqlite3_step(db_stmt[SQL_UPDATE_SONG]); + ret = sqlite3_step(db_stmt[update]); } while (ret == SQLITE_BUSY); if (ret != SQLITE_DONE) { @@ -899,20 +1007,20 @@ db_create(GError **error) { g_assert(gdb != NULL); - g_assert(db_stmt_maint[SQL_DB_CREATE_SONG] != NULL); - g_assert(db_stmt_maint[SQL_DB_CREATE_ARTIST] != NULL); - g_assert(db_stmt_maint[SQL_DB_CREATE_ALBUM] != NULL); - g_assert(db_stmt_maint[SQL_DB_CREATE_GENRE] != NULL); + g_assert(db_stmt_create[SQL_DB_CREATE_SONG] != NULL); + g_assert(db_stmt_create[SQL_DB_CREATE_ARTIST] != NULL); + g_assert(db_stmt_create[SQL_DB_CREATE_ALBUM] != NULL); + g_assert(db_stmt_create[SQL_DB_CREATE_GENRE] != NULL); g_assert(db_stmt_maint[SQL_SET_ENCODING] != NULL); g_assert(db_stmt_maint[SQL_SET_VERSION] != NULL); /** * Create tables */ - if (db_step(db_stmt_maint[SQL_DB_CREATE_SONG]) != SQLITE_DONE - || db_step(db_stmt_maint[SQL_DB_CREATE_ARTIST]) != SQLITE_DONE - || db_step(db_stmt_maint[SQL_DB_CREATE_ALBUM]) != SQLITE_DONE - || db_step(db_stmt_maint[SQL_DB_CREATE_GENRE]) != SQLITE_DONE) { + if (db_step(db_stmt_create[SQL_DB_CREATE_SONG]) != SQLITE_DONE + || db_step(db_stmt_create[SQL_DB_CREATE_ARTIST]) != SQLITE_DONE + || db_step(db_stmt_create[SQL_DB_CREATE_ALBUM]) != SQLITE_DONE + || db_step(db_stmt_create[SQL_DB_CREATE_GENRE]) != SQLITE_DONE) { g_set_error(error, db_quark(), ACK_ERROR_DATABASE_CREATE, "sqlite3_step: %s", sqlite3_errmsg(gdb)); return false; @@ -939,13 +1047,63 @@ return true; } +/* + * Upgrade the database schema version by executing the migration statements to + * get from one version to the next. + */ +static bool +db_migrate(int stmt_migrate, GError **error) +{ + g_assert(gdb != NULL); + + /* Prepare all statements for the migration step */ + for (unsigned int i = 0; i < DB_MIGRATE_STMT_COUNT; i++) { + if (db_sql_migrate[stmt_migrate][i] == NULL) { + db_stmt_migrate[stmt_migrate][i] = NULL; + } else if (sqlite3_prepare_v2(gdb, + db_sql_migrate[stmt_migrate][i], -1, + &db_stmt_migrate[stmt_migrate][i], NULL) != SQLITE_OK) + { + db_stmt_migrate[stmt_migrate][i] = NULL; + g_set_error(error, db_quark(), + ACK_ERROR_DATABASE_PREPARE, + "sqlite3_prepare_v2 (%s): %s", + db_sql_migrate[stmt_migrate][i], + sqlite3_errmsg(gdb)); + return false; + } + } + /* Execute all statements for the migration step */ + for (unsigned int i = 0; i < DB_MIGRATE_STMT_COUNT && + db_stmt_migrate[stmt_migrate][i] != NULL; i++) { + if (db_step(db_stmt_migrate[stmt_migrate][i]) != SQLITE_DONE) { + g_set_error(error, db_quark(), ACK_ERROR_DATABASE_STEP, + "sqlite3_step (%s): %s", + db_sql_migrate[stmt_migrate][i], + sqlite3_errmsg(gdb)); + return false; + } + } + for (unsigned int i = 0; i < DB_MIGRATE_STMT_COUNT && + db_stmt_migrate[stmt_migrate][i] != NULL; i++) { + if (db_stmt_migrate[stmt_migrate][i] != NULL) { + sqlite3_finalize(db_stmt_migrate[stmt_migrate][i]); + db_stmt_migrate[stmt_migrate][i] = NULL; + } + } + return true; +} + static bool db_check_ver(GError **error) { - int ret, version; + int ret; + int version = -1; + bool success; g_assert(gdb != NULL); g_assert(db_stmt_maint[SQL_GET_VERSION] != NULL); + g_assert(db_stmt_maint[SQL_SET_VERSION] != NULL); /** * Check version @@ -962,12 +1120,42 @@ return false; } - else if (version != DB_VERSION) { + else if (version < DB_MINIMUM_VERSION || version > DB_VERSION) { g_set_error(error, db_quark(), ACK_ERROR_DATABASE_VERSION, "Database version mismatch: %d != %d", version, DB_VERSION); return false; } + + else if (version != DB_VERSION) { + /* Upgrade lower version database to current version in one big + * transaction. If any step fails the DB should remain + * unmodified + */ + success = db_start_transaction(error); + switch (version) { + case 10: + g_debug("Upgrading database schema from version " + "%d to %d", 10, 11); + success &= db_migrate(SQL_DB_MIGRATE_10_11, error); + /* fall-through to the next version */ + } + if (db_step(db_stmt_maint[SQL_SET_VERSION]) != SQLITE_DONE) { + g_set_error(error, db_quark(), ACK_ERROR_DATABASE_CREATE, + "sqlite3_step (%s): %s", + db_sql_maint[SQL_SET_VERSION], + sqlite3_errmsg(gdb)); + success = false; + } + if (success) { + success &= db_end_transaction(error); + } else { + g_warning("Upgrade failed! Trying to roll back."); + db_rollback_transaction(error); + } + return success; + + } return true; } @@ -1005,12 +1193,23 @@ return false; } + for (unsigned int i = 0; i < G_N_ELEMENTS(db_sql_maint); i++) { + if (sqlite3_prepare_v2(gdb, db_sql_maint[i], -1, + &db_stmt_maint[i], NULL) != SQLITE_OK) { + g_set_error(error, db_quark(), ACK_ERROR_DATABASE_PREPARE, + "sqlite3_prepare_v2: %s", sqlite3_errmsg(gdb)); + db_close(); + return false; + } + } + if (new) { - for (unsigned int i = 0; i < G_N_ELEMENTS(db_sql_maint); i++) { - if (sqlite3_prepare_v2(gdb, db_sql_maint[i], -1, - &db_stmt_maint[i], NULL) != SQLITE_OK) { + for (unsigned int i = 0; i < G_N_ELEMENTS(db_sql_create); i++) { + if (sqlite3_prepare_v2(gdb, db_sql_create[i], -1, + &db_stmt_create[i], NULL) != SQLITE_OK) { g_set_error(error, db_quark(), ACK_ERROR_DATABASE_PREPARE, - "sqlite3_prepare_v2: %s", sqlite3_errmsg(gdb)); + "sqlite3_prepare_v2 (%s): %s", + db_sql_maint[i], sqlite3_errmsg(gdb)); db_close(); return false; } @@ -1020,22 +1219,20 @@ db_close(); return false; } + + for (unsigned int i = 0; i < G_N_ELEMENTS(db_sql_create); i++) { + if (db_stmt_create[i] != NULL) { + sqlite3_finalize(db_stmt_create[i]); + db_stmt_create[i] = NULL; + } + } + } else { - if (sqlite3_prepare_v2(gdb, db_sql_maint[SQL_GET_VERSION], -1, - &db_stmt_maint[SQL_GET_VERSION], - NULL) != SQLITE_OK) { - g_set_error(error, db_quark(), ACK_ERROR_DATABASE_PREPARE, - "sqlite3_prepare_v2: %s", sqlite3_errmsg(gdb)); - db_close(); - return false; - } if (!db_check_ver(error)) { db_close(); return false; } - sqlite3_finalize(db_stmt_maint[SQL_GET_VERSION]); - db_stmt_maint[SQL_GET_VERSION] = NULL; } /* Prepare common statements */ @@ -1089,20 +1286,21 @@ } /** - * Database Interaction + * Execute a single SQL statement without an expected result */ bool -db_start_transaction(GError **error) +db_run_stmt(unsigned int stmt, GError **error) { g_assert(gdb != NULL); + g_assert(stmt < G_N_ELEMENTS(db_stmt)); - if (sqlite3_reset(db_stmt[SQL_BEGIN_TRANSACTION]) != SQLITE_OK) { + if (sqlite3_reset(db_stmt_maint[stmt]) != SQLITE_OK) { g_set_error(error, db_quark(), ACK_ERROR_DATABASE_RESET, "sqlite3_reset: %s", sqlite3_errmsg(gdb)); return false; } - if (db_step(db_stmt[SQL_BEGIN_TRANSACTION]) != SQLITE_DONE) { + if (db_step(db_stmt_maint[stmt]) != SQLITE_DONE) { g_set_error(error, db_quark(), ACK_ERROR_DATABASE_STEP, "sqlite3_step: %s", sqlite3_errmsg(gdb)); return false; @@ -1111,24 +1309,25 @@ return true; } +/** + * Database Interaction + */ bool -db_end_transaction(GError **error) +db_start_transaction(GError **error) { - g_assert(gdb != NULL); - - if (sqlite3_reset(db_stmt[SQL_END_TRANSACTION]) != SQLITE_OK) { - g_set_error(error, db_quark(), ACK_ERROR_DATABASE_RESET, - "sqlite3_reset: %s", sqlite3_errmsg(gdb)); - return false; - } + return db_run_stmt(SQL_BEGIN_TRANSACTION, error); +} - if (db_step(db_stmt[SQL_END_TRANSACTION]) != SQLITE_DONE) { - g_set_error(error, db_quark(), ACK_ERROR_DATABASE_STEP, - "sqlite3_step: %s", sqlite3_errmsg(gdb)); - return false; - } +bool +db_end_transaction(GError **error) +{ + return db_run_stmt(SQL_END_TRANSACTION, error); +} - return true; +bool +db_rollback_transaction(GError **error) +{ + return db_run_stmt(SQL_ROLLBACK_TRANSACTION, error); } bool @@ -1138,7 +1337,8 @@ g_assert(gdb != NULL); - stmt = on ? db_stmt[SQL_PRAGMA_SYNC_ON] : db_stmt[SQL_PRAGMA_SYNC_OFF]; + stmt = on ? db_stmt_maint[SQL_PRAGMA_SYNC_ON] : + db_stmt_maint[SQL_PRAGMA_SYNC_OFF]; if (sqlite3_reset(stmt) != SQLITE_OK) { g_set_error(error, db_quark(), ACK_ERROR_DATABASE_RESET, @@ -1160,13 +1360,13 @@ { g_assert(gdb != NULL); - if (sqlite3_reset(db_stmt[SQL_VACUUM]) != SQLITE_OK) { + if (sqlite3_reset(db_stmt_maint[SQL_VACUUM]) != SQLITE_OK) { g_set_error(error, db_quark(), ACK_ERROR_DATABASE_RESET, "sqlite3_reset: %s", sqlite3_errmsg(gdb)); return false; } - if (db_step(db_stmt[SQL_VACUUM]) != SQLITE_DONE) { + if (db_step(db_stmt_maint[SQL_VACUUM]) != SQLITE_DONE) { g_set_error(error, db_quark(), ACK_ERROR_DATABASE_STEP, "sqlite3_step: %s", sqlite3_errmsg(gdb)); return false; @@ -1176,43 +1376,58 @@ } bool -db_process(const struct mpd_song *song, bool increment, +db_process(const struct mpd_song *song, bool increment, int percent_played, GError **error) { int id; + char *artist, *title; g_assert(gdb != NULL); g_assert(song != NULL); - if (mpd_song_get_tag(song, MPD_TAG_ARTIST, 0) == NULL || - mpd_song_get_tag(song, MPD_TAG_TITLE, 0) == NULL) { + if (!song_check_tags(song, &artist, &title)) { g_set_error(error, db_quark(), ACK_ERROR_NO_TAGS, "Song (%s) doesn't have required tags", mpd_song_get_uri(song)); return true; } - if ((id = db_has_song(mpd_song_get_uri(song), error)) < -1) - return false; - else if (id == -1) { - if (!db_insert_song(song, increment, error)) + if ((id = db_has_song(mpd_song_get_uri(song), error)) < -1) { + g_free(artist); + g_free(title); + return false; + } else if (id == -1) { + if (!db_insert_song(song, artist, title, increment, percent_played, error)) { + g_free(artist); + g_free(title); return false; + } } else { - if (!db_update_song(song, id, increment, error)) + if (!db_update_song(song, artist, title, id, increment, percent_played, error)) { + g_free(artist); + g_free(title); return false; + } } + g_free(title); - if ((id = db_has_artist(mpd_song_get_tag(song, MPD_TAG_ARTIST, 0), error)) < -1) + if ((id = db_has_artist(artist, error)) < -1) { + g_free(artist); return false; - else if (id == -1) { - if (!db_insert_artist(song, increment, error)) + } else if (id == -1) { + if (!db_insert_artist(artist, increment, error)) { + g_free(artist); return false; + } } else { - if (!db_update_artist(song, id, increment, error)) + if (!db_update_artist(artist, id, increment, error)) { + g_free(artist); return false; + } } + g_free(artist); if (mpd_song_get_tag(song, MPD_TAG_ALBUM, 0) != NULL) { if ((id = db_has_album(mpd_song_get_tag(song, MPD_TAG_ALBUM, 0), @@ -1622,7 +1837,8 @@ g_assert(values != NULL); sql = g_strdup_printf("select " - "id, play_count, love, kill, rating, uri " + "id, play_count, love, kill, rating, karma, " + "last_played, uri " "from song where %s ;", expr); if (sqlite3_prepare_v2(gdb, sql, -1, &stmt, NULL) != SQLITE_OK) { g_set_error(error, db_quark(), ACK_ERROR_DATABASE_PREPARE, @@ -1642,7 +1858,9 @@ song->love = sqlite3_column_int(stmt, 2); song->kill = sqlite3_column_int(stmt, 3); song->rating = sqlite3_column_int(stmt, 4); - song->uri = g_strdup((const char *)sqlite3_column_text(stmt, 5)); + song->karma = sqlite3_column_int(stmt, 5); + song->last_played = (time_t) sqlite3_column_int64(stmt, 6); + song->uri = g_strdup((const char *)sqlite3_column_text(stmt, 7)); *values = g_slist_prepend(*values, song); break; case SQLITE_DONE: @@ -1749,6 +1967,29 @@ return true; } +/** Set karma of a song absolutely */ +bool +db_karma_song_expr(const char *expr, int karma, int *changes, GError **error) +{ + char *stmt; + + g_assert(gdb != NULL); + g_assert(expr != NULL); + g_assert(karma >= 0 && karma <= 100); + + stmt = g_strdup_printf("karma = (%d)", karma); + if (!sql_update_song(stmt, expr, error)) { + g_free(stmt); + return false; + } + g_free(stmt); + + if (changes != NULL) + *changes = sqlite3_changes(gdb); + + return true; +} + /** * Love/Hate song/artist/album/genre */ @@ -2089,94 +2330,6 @@ if (!sql_update_song(stmt, expr, error)) { g_free(stmt); return false; - } - g_free(stmt); - - if (changes != NULL) - *changes = sqlite3_changes(gdb); - - return true; -} - -/** - * Rate song/artist/album/genre, absolute fashion (ignore previous rating - * altogether) - */ -bool -db_count_absolute_artist_expr(const char *expr, int count, int *changes, GError **error) -{ - char *stmt; - - g_assert(gdb != NULL); - g_assert(expr != NULL); - - stmt = g_strdup_printf("play_count = (%d)", count); - if (!sql_update_artist(stmt, expr, error)) { - g_free(stmt); - return false; - } - g_free(stmt); - - if (changes != NULL) - *changes = sqlite3_changes(gdb); - - return true; -} - -bool -db_count_absolute_album_expr(const char *expr, int count, int *changes, GError **error) -{ - char *stmt; - - g_assert(gdb != NULL); - g_assert(expr != NULL); - - stmt = g_strdup_printf("play_count = (%d)", count); - if (!sql_update_album(stmt, expr, error)) { - g_free(stmt); - return false; - } - g_free(stmt); - - if (changes != NULL) - *changes = sqlite3_changes(gdb); - - return true; -} - -bool -db_count_absolute_genre_expr(const char *expr, int count, int *changes, GError **error) -{ - char *stmt; - - g_assert(gdb != NULL); - g_assert(expr != NULL); - - stmt = g_strdup_printf("play_count = (%d)", count); - if (!sql_update_genre(stmt, expr, error)) { - g_free(stmt); - return false; - } - g_free(stmt); - - if (changes != NULL) - *changes = sqlite3_changes(gdb); - - return true; -} - -bool -db_count_absolute_song_expr(const char *expr, int count, int *changes, GError **error) -{ - char *stmt; - - g_assert(gdb != NULL); - g_assert(expr != NULL); - - stmt = g_strdup_printf("play_count = (%d)", count); - if (!sql_update_song(stmt, expr, error)) { - g_free(stmt); - return false; } g_free(stmt); diff -Nru mpdcron-0.3+git20110303/src/gmodule/stats/stats-sqlite.h mpdcron-0.3+git20161228/src/gmodule/stats/stats-sqlite.h --- mpdcron-0.3+git20110303/src/gmodule/stats/stats-sqlite.h 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/src/gmodule/stats/stats-sqlite.h 2018-04-05 13:05:11.000000000 +0000 @@ -46,10 +46,12 @@ int love; /** Love count of the song */ int kill; /** Kill count of the song */ int rating; /** Rating of the song */ + int karma; /** Karma (auto-rating) of the song */ char *uri; /** Uri of the song */ int duration; /** Duration of the song */ - int last_modified; /** Last modified date of the song */ + time_t last_modified; /** Last modified date of the song */ + time_t last_played; /** Last played date of the song */ char *artist; /** Artist of the song */ char *album; /** Album of the song */ char *title; /** Title of the song */ @@ -107,19 +109,26 @@ void *userdata, GError **error); bool +db_run_stmt(unsigned int stmt, GError **error); + +bool db_start_transaction(GError **error); bool db_end_transaction(GError **error); bool +db_rollback_transaction(GError **error); + +bool db_set_sync(bool on, GError **error); bool db_vacuum(GError **error); bool -db_process(const struct mpd_song *song, bool increment, GError **error); +db_process(const struct mpd_song *song, bool increment, int percent_played, + GError **error); bool db_list_artist_expr(const char *expr, GSList **values, GError **error); @@ -158,6 +167,9 @@ db_count_song_expr(const char *expr, int count, int *changes, GError **error); bool +db_karma_song_expr(const char *expr, int karma, int *changes, GError **error); + +bool db_love_artist_expr(const char *expr, bool love, int *changes, GError **error); bool @@ -206,18 +218,6 @@ db_rate_absolute_song_expr(const char *expr, int rating, int *changes, GError **error); bool -db_count_absolute_artist_expr(const char *expr, int rating, int *changes, GError **error); - -bool -db_count_absolute_album_expr(const char *expr, int rating, int *changes, GError **error); - -bool -db_count_absolute_genre_expr(const char *expr, int rating, int *changes, GError **error); - -bool -db_count_absolute_song_expr(const char *expr, int rating, int *changes, GError **error); - -bool db_add_artist_tag_expr(const char *expr, const char *tag, int *changes, GError **error); bool diff -Nru mpdcron-0.3+git20110303/src/gmodule/stats/walrus-main.c mpdcron-0.3+git20161228/src/gmodule/stats/walrus-main.c --- mpdcron-0.3+git20110303/src/gmodule/stats/walrus-main.c 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/src/gmodule/stats/walrus-main.c 2018-04-05 13:05:11.000000000 +0000 @@ -86,7 +86,7 @@ if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) { song = mpd_entity_get_song(entity); error = NULL; - if (!db_process(song, false, &error)) { + if (!db_process(song, false, -1, &error)) { g_printerr("Failed to process song %s: %s\n", mpd_song_get_uri(song), error->message); diff -Nru mpdcron-0.3+git20110303/src/gmodule/utils.h mpdcron-0.3+git20161228/src/gmodule/utils.h --- mpdcron-0.3+git20110303/src/gmodule/utils.h 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/src/gmodule/utils.h 2018-04-05 13:05:11.000000000 +0000 @@ -1,7 +1,7 @@ /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ /* - * Copyright (c) 2009 Ali Polatel + * Copyright (c) 2009, 2016 Ali Polatel * Based in part upon mpdscribble which is: * Copyright (C) 2008-2009 The Music Player Daemon Project * Copyright (C) 2005-2008 Kuno Woudt @@ -24,6 +24,7 @@ #define MPDCRON_GUARD_UTILS_H 1 #include +#include #include @@ -66,6 +67,9 @@ } } + if (value != NULL) + g_strchomp(value); + *value_r = value; return true; } @@ -107,4 +111,57 @@ return true; } +G_GNUC_UNUSED +static bool +song_check_tags(const struct mpd_song *song, char **artist_r, char **title_r) +{ + char *artist, *title; + + g_assert(song != NULL); + g_assert(artist_r != NULL); + g_assert(title_r != NULL); + + artist = g_strdup(mpd_song_get_tag(song, MPD_TAG_ARTIST, 0)); + title = g_strdup(mpd_song_get_tag(song, MPD_TAG_TITLE, 0)); + + if (!artist && !title) { + return false; + } else if (!artist && title) { + /* Special case for radio stations. */ + char *p; + + if (title[0] == '-') { + g_free(title); + return false; + } + + p = strchr(title, '-'); + + if (!p) { + g_free(title); + return false; + } + + if (*(p - 1) == ' ') { + *(--p) = 0; + *artist_r = g_strdup(title); + *(p++) = ' '; + } else { + *p = 0; + *artist_r = g_strdup(title); + *p = '-'; + } + + if (*(p + 1) == ' ') + ++p; + *title_r = g_strdup(p); + g_free(title); + } else { + *artist_r = artist; + *title_r = title; + } + + return true; +} + #endif /* !MPDCRON_GUARD_UTILS_H */ diff -Nru mpdcron-0.3+git20110303/src/Makefile.am mpdcron-0.3+git20161228/src/Makefile.am --- mpdcron-0.3+git20110303/src/Makefile.am 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/src/Makefile.am 2018-04-05 13:05:11.000000000 +0000 @@ -16,3 +16,26 @@ if HAVE_GMODULE SUBDIRS+= gmodule endif + +CPPCHECK=cppcheck +cppcheck: + $(CPPCHECK) $(mpdcron_SOURCES) $(DEFS) --std=c99 --std=posix --enable=all +.PHONY: cppcheck + +SPARSE=sparse +SPARSE_CPPFLAGS= $(DEFAULT_INCLUDES) \ + -D__STDC_VERSION__=199901L \ + -Wbitwise -Wcast-to-as -Wdefault-bitfield-sign \ + -Wparen-string -Wptr-subtraction-blows \ + -Wreturn-void -Wshadow -Wtypesign -Wundef \ + -I$(shell $(CC) -print-file-name=include) \ + -I$(shell $(CC) -print-file-name=include-fixed) +# Fix this flag for your architecture! +SPARSE_CPPFLAGS+= -D__x86_64__=1 + +sparse-check: + for src in $(mpdcron_SOURCES); \ + do \ + $(SPARSE) $(DEFS) $(AM_CFLAGS) $(SPARSE_CPPFLAGS) $$src || exit 1; \ + done +.PHONY: sparse-check diff -Nru mpdcron-0.3+git20110303/zsh-completion/_eugene mpdcron-0.3+git20161228/zsh-completion/_eugene --- mpdcron-0.3+git20110303/zsh-completion/_eugene 2011-03-03 17:06:39.000000000 +0000 +++ mpdcron-0.3+git20161228/zsh-completion/_eugene 2018-04-05 13:05:11.000000000 +0000 @@ -2,7 +2,7 @@ # vim: set et sw=4 sts=4 ts=4 ft=zsh : # ZSH completion for _eugene -# Copyright (c) 2010 Ali Polatel +# Copyright (c) 2010, 2013 Ali Polatel # Distributed under the terms of the GNU General Public License v2 _eugene_command() { @@ -19,6 +19,8 @@ kill:"Kill song/artist/album/genre" unkill:"Unkill song/artist/album/genre" rate:"Rate song/artist/album/genre" + rateabs:"Rate (absolute fashion) song/artist/album/genre" + karma:"Set karma rating of song" addtag:"Add tag to song/artist/album/genre" rmtag:"Remove tag from song/artist/album/genre" listtags:"List tags of song/artist/album/genre" @@ -103,6 +105,17 @@ _eugene_helper_number_expr } +_eugene_rateabs() { + _eugene_helper_number_expr +} + +_eugene_karma() { + _arguments \ + '(-h --help)'{-h,--help}'[Show help options]' \ + ':number:' \ + ':expression:' +} + _eugene_addtag() { _eugene_helper_tag_expr }