├── conf ├── hooks │ ├── playlist │ ├── queue │ ├── output │ ├── update │ ├── mixer │ ├── stored_playlist │ ├── options │ ├── player │ ├── database │ ├── dump-outputs.rb │ ├── dump-stats.bash │ ├── dump-playlists.rb │ ├── dump-song.bash │ ├── dump-status.bash │ └── dump-queue.rb ├── modules │ ├── Makefile │ └── example.c └── mpdcron.conf ├── src ├── gmodule │ ├── stats │ │ ├── valgrind.sh │ │ ├── walrus-defs.h │ │ ├── Makefile.am │ │ ├── walrus-utils.c │ │ ├── stats-defs.h │ │ ├── tokenizer.h │ │ ├── eugene-utils.c │ │ ├── eugene-karma.c │ │ ├── homescrape.in │ │ ├── eugene-main.c │ │ ├── walrus-main.c │ │ ├── tokenizer.c │ │ ├── eugene-list.c │ │ ├── stats-file.c │ │ ├── stats-module.c │ │ ├── stats-sqlite.h │ │ ├── eugene-rate.c │ │ └── eugene-count.c │ ├── scrobbler │ │ ├── TODO │ │ ├── Makefile.am │ │ ├── scrobbler-timer.c │ │ ├── scrobbler-record.c │ │ ├── scrobbler-defs.h │ │ ├── scrobbler-file.c │ │ ├── scrobbler-journal.c │ │ └── scrobbler-module.c │ ├── Makefile.am │ ├── notification │ │ ├── Makefile.am │ │ ├── notification-cover.c │ │ ├── notification-dhms.c │ │ ├── notification-defs.h │ │ ├── notification-spawn.c │ │ ├── notification-file.c │ │ └── notification-module.c │ ├── gmodule.h │ └── utils.h ├── valgrind.sh ├── Makefile.am ├── cron-config.h ├── cron-log.c ├── cron-conf.c ├── cron-defs.h ├── cron-hooker.c ├── cron-keyfile.c ├── cron-loop.c ├── cron-event.c └── cron-main.c ├── zsh-completion ├── Makefile.am ├── _mpdcron ├── _walrus └── _eugene ├── data ├── Makefile.am ├── mpdcron.1.pdc └── mpdcron.1 ├── autogen.sh ├── README.mkd ├── .gitignore ├── NEWS.mkd ├── Makefile.am ├── m4 └── ax_check_compiler_flags.m4 └── configure.ac /conf/hooks/playlist: -------------------------------------------------------------------------------- 1 | queue -------------------------------------------------------------------------------- /src/gmodule/stats/valgrind.sh: -------------------------------------------------------------------------------- 1 | ../../valgrind.sh -------------------------------------------------------------------------------- /zsh-completion/Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS= . 2 | EXTRA_DIST= _eugene _mpdcron _walrus 3 | -------------------------------------------------------------------------------- /data/Makefile.am: -------------------------------------------------------------------------------- 1 | EXTRA_DIST = mpdcron.1.pdc mpdcron.1 2 | BUILT_SOURCES = mpdcron.1 3 | dist_man_MANS= mpdcron.1 4 | 5 | all: mpdcron.1 6 | 7 | mpdcron.1: mpdcron.1.pdc 8 | pandoc -s -S --from=markdown --to=man $< -o $@ 9 | -------------------------------------------------------------------------------- /conf/hooks/queue: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # vim: set sw=4 et sts=4 tw=80 : 3 | # Copyright 2009 Ali Polatel 4 | # Distributed under the terms of the GNU General Public License v2 5 | 6 | # Dump queue 7 | hooks/dump-queue.rb 8 | -------------------------------------------------------------------------------- /conf/hooks/output: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # vim: set sw=4 et sts=4 tw=80 : 3 | # Copyright 2009 Ali Polatel 4 | # Distributed under the terms of the GNU General Public License v2 5 | 6 | # Example output hook 7 | hooks/dump-outputs.rb 8 | -------------------------------------------------------------------------------- /conf/hooks/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # vim: set sw=4 et sts=4 tw=80 : 3 | # Copyright 2009 Ali Polatel 4 | # Distributed under the terms of the GNU General Public License v2 5 | 6 | # Example update hook 7 | hooks/dump-status.bash 8 | -------------------------------------------------------------------------------- /src/gmodule/scrobbler/TODO: -------------------------------------------------------------------------------- 1 | mpdscribble: 2 | ea3893d23bb2c25e6190146d6fbbf70eaac3355c 3 | e47f2c7ed5994f5192f4382bd39029268d7c5e63 4 | 446207c4d20e12c5938f587503762452d67f578f 5 | 698ebb4ebde53593ad6f6f11a658abe826a24907 6 | 27187d3585c59f3b8a0a08b02afb49416ed876b1 7 | -------------------------------------------------------------------------------- /conf/hooks/mixer: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # vim: set sw=4 et sts=4 tw=80 : 3 | # Copyright 2009 Ali Polatel 4 | # Distributed under the terms of the GNU General Public License v2 5 | 6 | # Example mixer hook 7 | echo "* Mixer event" 8 | hooks/dump-status.bash 9 | -------------------------------------------------------------------------------- /conf/hooks/stored_playlist: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # vim: set sw=4 et sts=4 tw=80 : 3 | # Copyright 2009 Ali Polatel 4 | # Distributed under the terms of the GNU General Public License v2 5 | 6 | # Example hook for stored_playlist 7 | hooks/dump-playlists.rb 8 | -------------------------------------------------------------------------------- /conf/hooks/options: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # vim: set sw=4 et sts=4 tw=80 : 3 | # Copyright 2009 Ali Polatel 4 | # Distributed under the terms of the GNU General Public License v2 5 | 6 | # Example options hook 7 | echo "* Options event" 8 | hooks/dump-status.bash 9 | -------------------------------------------------------------------------------- /conf/hooks/player: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # vim: set sw=4 et sts=4 tw=80 : 3 | # Copyright 2009 Ali Polatel 4 | # Distributed under the terms of the GNU General Public License v2 5 | 6 | # Example player hook. 7 | hooks/dump-status.bash 8 | hooks/dump-song.bash 9 | -------------------------------------------------------------------------------- /conf/hooks/database: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # vim: set sw=4 et sts=4 tw=80 : 3 | # Copyright 2009 Ali Polatel 4 | # Distributed under the terms of the GNU General Public License v2 5 | 6 | # Example database hook 7 | echo "* Database event" 8 | hooks/dump-stats.bash 9 | -------------------------------------------------------------------------------- /src/gmodule/Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS= . 2 | if WANT_NOTIFICATION 3 | SUBDIRS+= notification 4 | endif 5 | if WANT_SCROBBLER 6 | SUBDIRS+= scrobbler 7 | endif 8 | if WANT_STATS 9 | SUBDIRS+= stats 10 | endif 11 | 12 | noinst_HEADERS= utils.h 13 | 14 | mpdcron_module_includedir=$(includedir)/mpdcron 15 | mpdcron_module_include_HEADERS= gmodule.h 16 | -------------------------------------------------------------------------------- /conf/modules/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for example module 2 | example.so: example.c 3 | $(CC) -fPIC -shared -Wall -W -Wextra \ 4 | $(CFLAGS) $(LDFLAGS) \ 5 | $(shell pkg-config --cflags --libs glib-2.0) \ 6 | $(shell pkg-config --cflags --libs libmpdclient) \ 7 | $< -o $@ 8 | clean: 9 | rm example.so || true 10 | install: 11 | install -m644 -s example.so ~/.mpdcron/modules 12 | uninstall: 13 | rm ~/.mpdcron/modules/example.so || true 14 | -------------------------------------------------------------------------------- /src/valgrind.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # vim: set sw=4 et sts=4 tw=80 : 3 | 4 | # Notes about valgrind & mpdcron: 5 | # - Debugging stats module under valgrind fails when the module does a DNS 6 | # resolution. 7 | 8 | abspath=$(readlink -f "$0") 9 | absdir=$(dirname "$abspath") 10 | 11 | export G_SLICE=always-malloc 12 | exec valgrind \ 13 | --leak-check=full \ 14 | --track-origins=yes \ 15 | --suppressions="$absdir"/valgrind.suppressions \ 16 | "$@" 17 | -------------------------------------------------------------------------------- /zsh-completion/_mpdcron: -------------------------------------------------------------------------------- 1 | #compdef mpdcron 2 | 3 | # vim: set et sw=4 sts=4 ts=4 ft=zsh : 4 | # ZSH completion for _mpdcron 5 | # Copyright (c) 2010 Ali Polatel 6 | # Distributed under the terms of the GNU General Public License v2 7 | 8 | _arguments \ 9 | '(-h --help)'{-h,--help}'[Show help options]' \ 10 | '(-V --version)'{-V,--version}'[Display version]' \ 11 | '(-k --kill)'{-k,--kill}'[Kill daemon]' \ 12 | '(-n --no-daemon)'{-n,--no-daemon}'[Dont detach from console]' 13 | -------------------------------------------------------------------------------- /conf/hooks/dump-outputs.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # vim: set sw=2 sts=2 tw=100 et nowrap fenc=utf-8 : 3 | # Copyright 2009 Ali Polatel 4 | # Distributed under the terms of the GNU General Public License v2 5 | 6 | # Dump outputs 7 | puts "=== MPD OUTPUTS ===" 8 | i = 1 9 | loop do 10 | env_output = "MPD_OUTPUT_ID_#{i}" 11 | break unless ENV[env_output] 12 | puts "output[#{i}].id = #{ENV[env_output]}" 13 | puts "output[#{i}].enabled = #{ENV["MPD_OUTPUT_ID_#{i}_ENABLED"]}" 14 | i += 1 15 | end 16 | puts "===================" 17 | -------------------------------------------------------------------------------- /conf/hooks/dump-stats.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # vim: set sw=4 et sts=4 tw=80 : 3 | # Copyright 2009 Ali Polatel 4 | # Distributed under the terms of the GNU General Public License v2 5 | 6 | echo "=== MPD STATS ===" 7 | echo "Update Time: $MPD_DATABASE_UPDATE_TIME" 8 | echo "Artists: $MPD_DATABASE_ARTISTS" 9 | echo "Albums: $MPD_DATABASE_ALBUMS" 10 | echo "Songs: $MPD_DATABASE_SONGS" 11 | echo "Play time: $MPD_DATABASE_PLAY_TIME" 12 | echo "Uptime: $MPD_DATABASE_UPTIME" 13 | echo "Database Play time: $MPD_DATABASE_DB_PLAY_TIME" 14 | echo "=================" 15 | -------------------------------------------------------------------------------- /src/gmodule/notification/Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS= . 2 | DEFS+= -DGITHEAD=\"$(GITHEAD)\" -DLIBDIR=\"$(libdir)\" 3 | AM_CFLAGS= @MPDCRON_CFLAGS@ $(glib_CFLAGS) $(libmpdclient_CFLAGS) 4 | MODULE_DIR=$(libdir)/$(PACKAGE)-$(VERSION)/modules 5 | 6 | noinst_HEADERS= notification-defs.h 7 | notification_LTLIBRARIES= notification.la 8 | notificationdir=$(MODULE_DIR) 9 | notification_la_SOURCES= notification-cover.c notification-dhms.c notification-file.c \ 10 | notification-spawn.c notification-module.c 11 | notification_la_LDFLAGS= -module -avoid-version 12 | notification_la_LIBADD= $(glib_LIBS) $(libmpdclient_LIBS) 13 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # vim: set sw=4 et sts=4 tw=80 : 3 | 4 | die() { 5 | echo "$@" >&2 6 | exit 1 7 | } 8 | 9 | echo ">>> rm -f config.cache" 10 | rm -f config.cache 11 | echo ">>> libtoolize --copy --force --automake" 12 | libtoolize --copy --force --automake || die "libtoolize failed" 13 | echo ">>> aclocal -I m4" 14 | aclocal -I m4 || die "aclocal failed" 15 | echo ">>> autoheader" 16 | autoheader || die "autoheader failed" 17 | echo ">>> autoconf" 18 | autoconf || die "autoconf failed" 19 | echo ">>> automake --foreign --add-missing --copy" 20 | automake --foreign --add-missing --copy || die "automake failed" 21 | -------------------------------------------------------------------------------- /src/gmodule/scrobbler/Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS= . 2 | DEFS+= -DGITHEAD=\"$(GITHEAD)\" -DLIBDIR=\"$(libdir)\" 3 | AM_CFLAGS= @MPDCRON_CFLAGS@ $(glib_CFLAGS) $(libcurl_CFLAGS) $(libmpdclient_CFLAGS) 4 | MODULE_DIR=$(libdir)/$(PACKAGE)-$(VERSION)/modules 5 | 6 | noinst_HEADERS= scrobbler-defs.h 7 | scrobbler_LTLIBRARIES= scrobbler.la 8 | scrobblerdir=$(MODULE_DIR) 9 | scrobbler_la_SOURCES= scrobbler-curl.c scrobbler-file.c scrobbler-journal.c \ 10 | scrobbler-record.c scrobbler-submit.c scrobbler-timer.c \ 11 | scrobbler-module.c 12 | scrobbler_la_LDFLAGS= -module -avoid-version 13 | scrobbler_la_LIBADD= $(glib_LIBS) $(libcurl_LIBS) $(libmpdclient_LIBS) 14 | -------------------------------------------------------------------------------- /README.mkd: -------------------------------------------------------------------------------- 1 | ## What is mpdcron? 2 | [mpdcron](http://alip.github/mpdcron) is a cron like daemon for [mpd](http://mpd.wikia.com/). 3 | 4 | ## Features 5 | See [http://alip.github.com/mpdcron/#features](http://alip.github.com/mpdcron/#features) 6 | 7 | ## Configuration 8 | See [http://alip.github.com/mpdcron/configuration/](http://alip.github.com/mpdcron/configuration/) 9 | 10 | ## Requirements 11 | See [http://alip.github.com/mpdcron/#requirements](http://alip.github.com/mpdcron/#requirements) 12 | 13 | ## Contribute 14 | See [http://alip.github.com/mpdcron/#contribute](http://alip.github.com/mpdcron/#contribute) 15 | 16 | 17 | -------------------------------------------------------------------------------- /conf/hooks/dump-playlists.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # vim: set sw=2 sts=2 tw=100 et nowrap fenc=utf-8 : 3 | # Copyright 2009 Ali Polatel 4 | # Distributed under the terms of the GNU General Public License v2 5 | 6 | # Dump playlists 7 | # Playlists are listed using environment variables like: 8 | # MPD_PLAYLIST_%d_PATH = /path/to/playlist 9 | # MPD_PLAYLIST_%d_LAST_MODIFIED = PLAYLIST_LAST_MODIFIED_DATE 10 | 11 | puts "=== MPD PLAYLISTS ===" 12 | i = 1 13 | loop do 14 | envp = "MPD_PLAYLIST_#{i}_PATH" 15 | envlm = "MPD_PLAYLIST_#{i}_LAST_MODIFIED" 16 | break unless ENV[envp] 17 | puts "playlist[#{i}].path = #{ENV[envp]}" 18 | puts "playlist[#{i}].last_modified = #{ENV[envlm]}" 19 | i += 1 20 | end 21 | puts "=====================" 22 | -------------------------------------------------------------------------------- /conf/mpdcron.conf: -------------------------------------------------------------------------------- 1 | # mpdcron example configuration 2 | # vim: set et sw=4 sts=4 tw=80 ft=desktop : 3 | # Copy this file to MPDCRON_DIR/mpdcron.conf where MPDCRON_DIR is 4 | # ~/.mpdcron by default. 5 | 6 | # mpdcron related options are specified in the main group 7 | [main] 8 | # Location of the pid file. 9 | # pidfile = /var/run/mpdcron.pid 10 | # Wait this many seconds after sending signal to kill the daemon 11 | killwait = 3 12 | 13 | # Mpd related options are specified in the mpd group. 14 | [mpd] 15 | # The list of events to wait for 16 | events = database;stored_playlist;playlist;player;mixer;output;options;update 17 | # Inverval in seconds for reconnecting to Mpd. 18 | reconnect = 5 19 | # Timeout in milliseconds for Mpd timeout, 0 for default timeout of 20 | # libmpdclient. 21 | timeout = 0 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # gitignore for mpdhooker 2 | .*.swp 3 | *~ 4 | *.[oa] 5 | 6 | *.lo 7 | *.la 8 | 9 | *.tar.bz2 10 | *.sha1sum 11 | *.asc 12 | 13 | .deps 14 | .libs 15 | Makefile.in 16 | Makefile 17 | 18 | /build-aux 19 | /INSTALL 20 | /aclocal.m4 21 | /autom4te.cache 22 | /config.guess 23 | /config.h 24 | /config.h.in 25 | /config.log 26 | /config.sub 27 | /config.status 28 | /configure 29 | /compile 30 | /depcomp 31 | /install-sh 32 | /missing 33 | /stamp-h1 34 | 35 | # libtool 36 | libtool 37 | ltmain.sh 38 | m4/libtool.m4 39 | m4/ltversion.m4 40 | m4/ltversion.m4 41 | m4/lt~obsolete.m4 42 | m4/ltoptions.m4 43 | m4/ltsugar.m4 44 | 45 | # binaries 46 | src/mpdcron 47 | src/gmodule/stats/eugene 48 | src/gmodule/stats/walrus 49 | src/gmodule/stats/homescrape 50 | 51 | # GNU global 52 | GPATH 53 | GRTAGS 54 | GSYMS 55 | GTAGS 56 | -------------------------------------------------------------------------------- /zsh-completion/_walrus: -------------------------------------------------------------------------------- 1 | #compdef walrus 2 | 3 | # vim: set et sw=4 sts=4 ts=4 ft=zsh : 4 | # ZSH completion for _walrus 5 | # Requires mpc 6 | # Copyright (c) 2010 Ali Polatel 7 | # Distributed under the terms of the GNU General Public License v2 8 | 9 | _mpc_helper_files() { 10 | local -U list expl 11 | 12 | if [[ $words[CURRENT] != */* ]]; then 13 | list=( ${${(f)"$(mpc listall)"}%%/*}) 14 | _wanted files expl file compadd -qS/ -a list 15 | else 16 | list=(${(f)"$(mpc tab $words[CURRENT])"}) 17 | _wanted files expl file _multi_parts / list 18 | fi 19 | } 20 | 21 | _arguments \ 22 | '(-h --help)'{-h,--help}'[Show help options]' \ 23 | '(-V --version)'{-V,--version}'[Display version]' \ 24 | '(-d --dbpath)'{-d,--dbpath=}'[Path to the database]:file:_files' \ 25 | '(-k --keep-going)'{-k,--keep-going}'[Keep going in case of database errors]' \ 26 | '*::path:_mpc_helper_files' 27 | -------------------------------------------------------------------------------- /src/gmodule/stats/walrus-defs.h: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009 Ali Polatel 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #ifndef MPDCRON_GUARD_WALRUS_DEFS_H 21 | #define MPDCRON_GUARD_WALRUS_DEFS_H 1 22 | 23 | #include "../../cron-config.h" 24 | #include "stats-sqlite.h" 25 | 26 | char * 27 | xload_dbpath(void); 28 | 29 | #endif /* !MPDCRON_GUARD_WALRUS_DEFS_H */ 30 | -------------------------------------------------------------------------------- /src/gmodule/scrobbler/scrobbler-timer.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009 Ali Polatel 5 | * Based in part upon mpdscribble which is: 6 | * Copyright (C) 2008-2009 The Music Player Daemon Project 7 | * Copyright (C) 2005-2008 Kuno Woudt 8 | * 9 | * This file is part of the mpdcron mpd client. mpdcron is free software; 10 | * you can redistribute it and/or modify it under the terms of the GNU General 11 | * Public License version 2, as published by the Free Software Foundation. 12 | * 13 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 14 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 | * details. 17 | * 18 | * You should have received a copy of the GNU General Public License along with 19 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 20 | * Place, Suite 330, Boston, MA 02111-1307 USA 21 | */ 22 | 23 | #include "scrobbler-defs.h" 24 | 25 | #include 26 | 27 | gboolean 28 | timer_save_journal(G_GNUC_UNUSED gpointer data) 29 | { 30 | as_save_cache(); 31 | return TRUE; 32 | } 33 | -------------------------------------------------------------------------------- /conf/hooks/dump-song.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # vim: set sw=4 et sts=4 tw=80 : 3 | # Copyright 2009 Ali Polatel 4 | # Distributed under the terms of the GNU General Public License v2 5 | 6 | echo "=== MPD CURRENT SONG ===" 7 | echo "Uri: $MPD_SONG_URI" 8 | echo "Last modified: $MPD_SONG_LAST_MODIFIED" 9 | echo "Duration: $MPD_SONG_DURATION" 10 | echo "Position: $MPD_SONG_POSITION" 11 | echo "ID: $MPD_SONG_ID" 12 | # Note: Currently only the first tags are exported 13 | echo "--- TAGS ---" 14 | echo "Artist: $MPD_SONG_TAG_ARTIST" 15 | echo "Album: $MPD_SONG_TAG_ALBUM" 16 | echo "Album_Artist: $MPD_SONG_ALBUM_ARTIST" 17 | echo "Title: $MPD_SONG_TAG_TITLE" 18 | echo "Track: $MPD_SONG_TAG_TRACK" 19 | echo "Name: $MPD_SONG_TAG_NAME" 20 | echo "Genre: $MPD_SONG_TAG_GENRE" 21 | echo "Date: $MPD_SONG_TAG_GENRE" 22 | echo "Composer: $MPD_SONG_TAG_COMPOSER" 23 | echo "Performer: $MPD_SONG_TAG_PERFORMER" 24 | echo "Comment: $MPD_SONG_TAG_COMMENT" 25 | echo "Disc: $MPD_SONG_TAG_DISC" 26 | echo "Musicbrainz Artist ID: $MPD_SONG_TAG_MUSICBRAINZ_ARTISTID" 27 | echo "Musicbrainz Album ID: $MPD_SONG_TAG_MUSICBRAINZ_ALBUMID" 28 | echo "Musicbrainz AlbumArtist ID: $MPD_SONG_TAG_MUSICBRAINZ_ALBUMARTISTID" 29 | echo "Musicbrainz Track ID: $MPD_SONG_TAG_MUSICBRAINZ_TRACKID" 30 | echo "------------" 31 | echo "========================" 32 | -------------------------------------------------------------------------------- /conf/hooks/dump-status.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # vim: set sw=4 et sts=4 tw=80 : 3 | # Copyright 2009 Ali Polatel 4 | # Distributed under the terms of the GNU General Public License v2 5 | 6 | echo "==========" 7 | echo "MPD STATUS" 8 | echo "==========" 9 | echo "Volume: $MPD_STATUS_VOLUME" 10 | echo "Repeat: $MPD_STATUS_REPEAT" # Boolean, 0 or 1 11 | echo "Random: $MPD_STATUS_RANDOM" # Boolean, 0 or 1 12 | echo "Single: $MPD_STATUS_SINGLE" # Boolean, 0 or 1 13 | echo "Consume: $MPD_STATUS_CONSUME" # Boolean, 0 or 1 14 | echo "Queue version: $MPD_STATUS_QUEUE_VERSION" 15 | echo "Crossfade: $MPD_STATUS_CROSSFADE" 16 | echo "Song position: $MPD_STATUS_SONG_POS" 17 | echo "Song ID: $MPD_STATUS_SONG_ID" 18 | echo "Elapsed time: $MPD_STATUS_ELAPSED_TIME" 19 | echo "Elapsed milliseconds: $MPD_STATUS_ELAPSED_MS" 20 | echo "Total time: $MPD_STATUS_TOTAL_TIME" 21 | echo "Kbit Rate: $MPD_STATUS_KBIT_RATE" 22 | echo "Update id: $MPD_STATUS_UPDATE_ID" 23 | echo "State: $MPD_STATUS_STATE" # One of play, pause, stop, unknown 24 | echo "Audio format: $MPD_STATUS_AUDIO_FORMAT" # Boolean, 0 or 1 25 | if [[ "$MPD_STATUS_AUDIO_FORMAT" == 1 ]]; then 26 | echo "Samplerate: $MPD_STATUS_AUDIO_FORMAT_SAMPLE_RATE" 27 | echo "Bits: $MPD_STATUS_AUDIO_FORMAT_BITS" 28 | echo "Channels: $MPD_STATUS_AUDIO_FORMAT_CHANNELS" 29 | fi 30 | echo "==========" 31 | -------------------------------------------------------------------------------- /src/gmodule/notification/notification-cover.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009 Ali Polatel 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include "notification-defs.h" 21 | 22 | #include 23 | 24 | char * 25 | cover_find(const char *artist, const char *album) 26 | { 27 | char *cfile; 28 | char *cpath; 29 | 30 | cfile = g_strdup_printf("%s-%s.%s", artist, album, file_config.cover_suffix); 31 | cpath = g_build_filename(file_config.cover_path, cfile, NULL); 32 | g_free(cfile); 33 | 34 | if (g_file_test(cpath, G_FILE_TEST_EXISTS)) 35 | return cpath; 36 | g_free(cpath); 37 | return NULL; 38 | } 39 | -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | DEFS+= -DGITHEAD=\"$(GITHEAD)\" -DLIBDIR=\"$(libdir)\" 2 | AM_CFLAGS= @MPDCRON_CFLAGS@ $(glib_CFLAGS) $(libdaemon_CFLAGS) $(libmpdclient_CFLAGS) 3 | 4 | bin_PROGRAMS= mpdcron 5 | noinst_HEADERS= cron-config.h cron-defs.h 6 | mpdcron_SOURCES= cron-conf.c cron-env.c cron-event.c cron-hooker.c \ 7 | cron-keyfile.c cron-log.c cron-loop.c cron-main.c 8 | mpdcron_LDADD= $(glib_LIBS) $(libdaemon_LIBS) $(libmpdclient_LIBS) 9 | if HAVE_GMODULE 10 | AM_CFLAGS+= $(gmodule_CFLAGS) 11 | mpdcron_LDADD+= $(gmodule_LIBS) 12 | mpdcron_SOURCES+= cron-gmodule.c 13 | endif 14 | 15 | SUBDIRS= . 16 | if HAVE_GMODULE 17 | SUBDIRS+= gmodule 18 | endif 19 | 20 | CPPCHECK=cppcheck 21 | cppcheck: 22 | $(CPPCHECK) $(mpdcron_SOURCES) $(DEFS) --std=c99 --std=posix --enable=all 23 | .PHONY: cppcheck 24 | 25 | SPARSE=sparse 26 | SPARSE_CPPFLAGS= $(DEFAULT_INCLUDES) \ 27 | -D__STDC_VERSION__=199901L \ 28 | -Wbitwise -Wcast-to-as -Wdefault-bitfield-sign \ 29 | -Wparen-string -Wptr-subtraction-blows \ 30 | -Wreturn-void -Wshadow -Wtypesign -Wundef \ 31 | -I$(shell $(CC) -print-file-name=include) \ 32 | -I$(shell $(CC) -print-file-name=include-fixed) 33 | # Fix this flag for your architecture! 34 | SPARSE_CPPFLAGS+= -D__x86_64__=1 35 | 36 | sparse-check: 37 | for src in $(mpdcron_SOURCES); \ 38 | do \ 39 | $(SPARSE) $(DEFS) $(AM_CFLAGS) $(SPARSE_CPPFLAGS) $$src || exit 1; \ 40 | done 41 | .PHONY: sparse-check 42 | -------------------------------------------------------------------------------- /src/gmodule/notification/notification-dhms.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009 Ali Polatel 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include "notification-defs.h" 21 | 22 | char * 23 | dhms(unsigned long t) 24 | { 25 | int days, hours, mins, secs; 26 | 27 | #define SECSPERDAY 86400 28 | #define SECSPERHOUR 3600 29 | #define SECSPERMIN 60 30 | 31 | days = t / SECSPERDAY; 32 | t %= SECSPERDAY; 33 | hours = t / SECSPERHOUR; 34 | t %= SECSPERHOUR; 35 | mins = t / SECSPERMIN; 36 | t %= SECSPERMIN; 37 | secs = t; 38 | 39 | #undef SECSPERDAY 40 | #undef SECSPERHOUR 41 | #undef SECSPERMIN 42 | 43 | return g_strdup_printf("%d days, %d:%02d:%02d", days, hours, mins, 44 | secs); 45 | } 46 | -------------------------------------------------------------------------------- /src/gmodule/notification/notification-defs.h: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009 Ali Polatel 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #ifndef MPDCRON_GUARD_NOTIFICATION_DEFS_H 21 | #define MPDCRON_GUARD_NOTIFICATION_DEFS_H 1 22 | 23 | #ifdef HAVE_CONFIG_H 24 | #include "config.h" 25 | #endif /* !HAVE_CONFIG_H */ 26 | 27 | #define MPDCRON_MODULE "notification" 28 | #include "../gmodule.h" 29 | 30 | #include 31 | 32 | struct config { 33 | int events; 34 | char *cover_path; 35 | char *cover_suffix; 36 | char *timeout; 37 | char *type; 38 | char *urgency; 39 | char **hints; 40 | }; 41 | 42 | extern struct config file_config; 43 | 44 | char * 45 | cover_find(const char *artist, const char *album); 46 | 47 | char * 48 | dhms(unsigned long t); 49 | 50 | int 51 | file_load(GKeyFile *fd); 52 | 53 | void 54 | file_cleanup(void); 55 | 56 | void 57 | notify_send(const char *icon, const char *summary, const char *body); 58 | 59 | #endif /* !MPDCRON_GUARD_NOTIFICATION_DEFS_H */ 60 | -------------------------------------------------------------------------------- /NEWS.mkd: -------------------------------------------------------------------------------- 1 | ## News for mpdcron 2 | 3 | This file lists the major changes between versions. For a more detailed list of 4 | every change, see git log. 5 | 6 | * stats: clever handling for radio stations 7 | * scrobbler: clever handling for radio stations 8 | * scrobbler: (curl) fix operation with threaded resolver 9 | (fixes: #16) 10 | * scrobbler: (curl) fix segmentation fault with large response body 11 | * Require libmpdclient-2.2 12 | * file: ignore trailing whitespace 13 | * scrobbler: check the HTTP response status 14 | * scrobbler: better HTTP error messages 15 | * scrobbler: fix memory leak in as_songchange() 16 | * log: use ISO8601 date format 17 | * scrobbler: submit track numbers 18 | * scrobbler: (journal) don't save "(null)" values 19 | * scrobbler: (curl) prevent recursive read calls 20 | * scrobbler: (curl) Implement libCURL timeouts 21 | * stats: Add an automatic "karma" rating of songs, based on how often they are 22 | skipped or fully listened to (fixes: #13). 23 | * stats: Add a database column when a song was last played or skipped 24 | (fixes: #13). 25 | * Be more conservative when exporting variables to the environment (fixes: #3) 26 | * stats: ability to set rating regardless of existing rating. 27 | 28 | ### 0.3 29 | * Added stats module to keep statistics of played songs in a sqlite database 30 | * Added notification module to send notifications via notify-send 31 | * Added scrobbler module to submit songs to Last.fm or Libre.fm 32 | * Added module support through GModule 33 | * Added initial manpage 34 | * Changed name to mpdcron 35 | 36 | ### 0.2 37 | * Initial public release 38 | * Require Mpd's idle mode 39 | * Port to libmpdclient-2, mpdhooker now requires libmpdclient-2.1 or newer. 40 | 41 | ### 0.1 42 | * Unreleased first version 43 | 44 | 45 | -------------------------------------------------------------------------------- /data/mpdcron.1.pdc: -------------------------------------------------------------------------------- 1 | % mpdcron(1) manual 2 | % Ali Polatel 3 | % December 23, 2009 4 | 5 | # NAME 6 | mpdcron - cron like daemon for mpd 7 | 8 | # SYNOPSIS 9 | mpdcron [*option*] 10 | 11 | # DESCRIPTION 12 | mpdcron is a cron like daemon for mpd. It connects to mpd, waits for idle events. 13 | It has two interfaces: 14 | 15 | - Hooks which are executed after setting environment variables which hooks can use to get more 16 | information about the event without having to connect to mpd. 17 | - Modules, which are dynamically loaded and have direct access to mpd connection and the file 18 | descriptor of mpdcron's configuration file. 19 | 20 | By default it will get the host name and port for mpd from **MPD\_HOST** and **MPD\_PORT** 21 | environment variables. **MPD\_PASSWORD** environment variable can be set to make mpdcron connect to 22 | a password-protected mpd. If these environment variables aren't set, mpdcron connects to localhost 23 | on port 6600. 24 | 25 | # OPTIONS 26 | -?, \--help 27 | : Show help options and exit. 28 | 29 | -V, \--version 30 | : Print version information and exit. 31 | 32 | -k, \--kill 33 | : Instead of launching the daemon, attempt to kill the already running daemon. 34 | 35 | -n, \--no-daemon 36 | : Don't detach from console. This option can be used for debugging. 37 | 38 | # FILES 39 | - **~/.mpdcron/mpdcron.conf** User configuration file 40 | 41 | # SEE ALSO 42 | - `mpd`(1) 43 | - 44 | 45 | # REPORTING BUGS 46 | If you find a bug, please report it at 47 | 48 | # COPYRIGHT 49 | Copyright (c) 2009 Ali Polatel \ 50 | Free use of this software is granted under the terms of the GNU General Public License (GPL). 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/gmodule/stats/Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS= . 2 | DEFS+= -DGITHEAD=\"$(GITHEAD)\" -DLIBDIR=\"$(libdir)\" 3 | AM_CFLAGS= @MPDCRON_CFLAGS@ \ 4 | $(gio_unix_CFLAGS) $(glib_CFLAGS) $(gio_CFLAGS) \ 5 | $(libmpdclient_CFLAGS) $(sqlite_CFLAGS) 6 | MODULE_DIR=$(libdir)/$(PACKAGE)-$(VERSION)/modules 7 | 8 | noinst_HEADERS= tokenizer.h stats-defs.h stats-sqlite.h 9 | stats_LTLIBRARIES= stats.la 10 | statsdir=$(MODULE_DIR) 11 | stats_la_SOURCES= tokenizer.c \ 12 | stats-command.c stats-file.c stats-server.c \ 13 | stats-sqlite.c stats-module.c 14 | stats_la_LDFLAGS= -module -avoid-version 15 | stats_la_LIBADD= $(glib_LIBS) $(gio_unix_LIBS) $(gio_LIBS) \ 16 | $(libdaemon_LIBS) $(libmpdclient_LIBS) $(sqlite_LIBS) 17 | 18 | # I am the eggman! 19 | noinst_HEADERS+= walrus-defs.h 20 | bin_PROGRAMS= walrus 21 | walrus_SOURCES= stats-sqlite.c walrus-utils.c walrus-main.c 22 | # Hack to workaround the error: 23 | # object x created both with libtool and without. 24 | # See: http://bit.ly/libtool_both 25 | walrus_CFLAGS= $(AM_CFLAGS) 26 | walrus_LDADD= $(glib_LIBS) $(libmpdclient_LIBS) $(sqlite_LIBS) 27 | 28 | # Careful with that axe! 29 | # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa! 30 | noinst_HEADERS+= eugene-defs.h 31 | bin_PROGRAMS+= eugene 32 | eugene_SOURCES= eugene-connection.c eugene-addtag.c \ 33 | eugene-count.c eugene-karma.c eugene-kill.c eugene-list.c \ 34 | eugene-listinfo.c eugene-listtags.c eugene-love.c \ 35 | eugene-rate.c eugene-rate-absolute.c eugene-rmtag.c \ 36 | eugene-utils.c eugene-main.c \ 37 | stats-sqlite.c walrus-utils.c 38 | # Hack to workaround the error: 39 | # object x created both with libtool and without. 40 | # See: http://bit.ly/libtool_both 41 | eugene_CFLAGS= $(AM_CFLAGS) 42 | eugene_LDADD= $(gio_unix_LIBS) $(gio_LIBS) $(glib_LIBS) $(libmpdclient_LIBS) $(sqlite_LIBS) 43 | 44 | bin_SCRIPTS= homescrape 45 | -------------------------------------------------------------------------------- /src/cron-config.h: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009, 2013 Ali Polatel 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #ifndef MPDCRON_GUARD_CRON_CONFIG_H 21 | #define MPDCRON_GUARD_CRON_CONFIG_H 1 22 | 23 | #ifdef HAVE_CONFIG_H 24 | #include "config.h" 25 | #endif /* HAVE_CONFIG_H */ 26 | 27 | /* Configuration */ 28 | #define ENV_HOME_DIR "MPDCRON_DIR" 29 | #define ENV_MPD_HOST "MPD_HOST" 30 | #define ENV_MPD_PORT "MPD_PORT" 31 | #define ENV_MPD_PASSWORD "MPD_PASSWORD" 32 | 33 | #define DOT_MPDCRON "." PACKAGE 34 | #define DOT_HOOKS "hooks" 35 | #define DOT_MODULES "modules" 36 | 37 | #define MODULE_INIT_FUNC "mpdcron_init" 38 | #define MODULE_CLOSE_FUNC "mpdcron_close" 39 | #define MODULE_RUN_FUNC "mpdcron_run" 40 | 41 | #define DEFAULT_PID_KILL_WAIT 3 42 | #define DEFAULT_MPD_RECONNECT 5 43 | #define DEFAULT_MPD_TIMEOUT 0 44 | #define DEFAULT_LOG_LEVEL 0 45 | 46 | #define DEFAULT_DATE_FORMAT "%Y-%m-%dT%H:%M:%S%z" 47 | #define DEFAULT_DATE_FORMAT_SIZE 32 48 | 49 | #define MPDCRON_INTERNAL 1 50 | #include "gmodule/gmodule.h" 51 | 52 | #endif /* !MPDCRON_GUARD_CRON_CONFIG_H */ 53 | -------------------------------------------------------------------------------- /conf/hooks/dump-queue.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # vim: set sw=2 sts=2 tw=100 et nowrap fenc=utf-8 : 3 | # Copyright 2009 Ali Polatel 4 | # Distributed under the terms of the GNU General Public License v2 5 | 6 | # Dump queue 7 | puts "=== MPD QUEUE ===" 8 | i = 1 9 | loop do 10 | env_uri = "MPD_SONG_#{i}_URI" 11 | break unless ENV[env_uri] 12 | puts "song[#{i}].uri = #{ENV[env_uri]}" 13 | puts "song[#{i}].last_modified = #{ENV["MPD_SONG_#{i}_LAST_MODIFIED]"]}" 14 | puts "song[#{i}].duration = #{ENV["MPD_SONG_#{i}_DURATION"]}" 15 | puts "song[#{i}].pos = #{ENV["MPD_SONG_#{i}_POS"]}" 16 | puts "song[#{i}].id = #{ENV["MPD_SONG_#{i}_ID"]}" 17 | puts "song[#{i}].tag.artist = #{ENV["MPD_SONG_#{i}_TAG_ARTIST"]}" 18 | puts "song[#{i}].tag.album = #{ENV["MPD_SONG_#{i}_TAG_ALBUM"]}" 19 | puts "song[#{i}].tag.album_artist = #{ENV["MPD_SONG_#{i}_TAG_ALBUM_ARTIST"]}" 20 | puts "song[#{i}].tag.title = #{ENV["MPD_SONG_#{i}_TAG_TITLE"]}" 21 | puts "song[#{i}].tag.track = #{ENV["MPD_SONG_#{i}_TAG_TRACK"]}" 22 | puts "song[#{i}].tag.name = #{ENV["MPD_SONG_#{i}_TAG_NAME"]}" 23 | puts "song[#{i}].tag.genre = #{ENV["MPD_SONG_#{i}_TAG_GENRE"]}" 24 | puts "song[#{i}].tag.date = #{ENV["MPD_SONG_#{i}_TAG_DATE"]}" 25 | puts "song[#{i}].tag.composer = #{ENV["MPD_SONG_#{i}_TAG_COMPOSER"]}" 26 | puts "song[#{i}].tag.comment = #{ENV["MPD_SONG_#{i}_TAG_COMMENT"]}" 27 | puts "song[#{i}].tag.disc = #{ENV["MPD_SONG_#{i}_TAG_DISC"]}" 28 | puts "song[#{i}].tag.musicbrainz.artistid = #{ENV["MPD_SONG_#{i}_TAG_MUSICBRAINZ_ARTISTID"]}" 29 | puts "song[#{i}].tag.musicbrainz.albumid = #{ENV["MPD_SONG_#{i}_TAG_MUSICBRAINZ_ALBUMID"]}" 30 | puts "song[#{i}].tag.musicbrainz.albumartistid = #{ENV["MPD_SONG_#{i}_TAG_MUSICBRAINZ_ALBUMARTISTID"]}" 31 | puts "song[#{i}].tag.musicbrainz.trackid = #{ENV["MPD_SONG_#{i}_TAG_MUSICBRAINZ_TRACKID"]}" 32 | i += 1 33 | end 34 | puts "=================" 35 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | CLEANFILES= *~ 2 | MAINTAINERCLEANFILES= Makefile.in configure aclocal.m4 \ 3 | config.h config.h.in INSTALL 4 | ACLOCAL_AMFLAGS= -I m4 5 | AUTOMAKE_OPTIONS= dist-bzip2 no-dist-gzip std-options foreign 6 | EXTRA_DIST= autogen.sh README.mkd NEWS.mkd \ 7 | conf/mpdcron.conf \ 8 | conf/hooks/database conf/hooks/mixer conf/hooks/options \ 9 | conf/hooks/output conf/hooks/player conf/hooks/playlist \ 10 | conf/hooks/queue conf/hooks/stored_playlist conf/hooks/update \ 11 | conf/hooks/dump-outputs.rb conf/hooks/dump-playlists.rb \ 12 | conf/hooks/dump-queue.rb conf/hooks/dump-song.bash \ 13 | conf/hooks/dump-stats.bash conf/hooks/dump-status.bash \ 14 | conf/hooks/submit.rb \ 15 | conf/modules/Makefile conf/modules/example.c 16 | SUBDIRS = src data zsh-completion . 17 | doc_DATA = README.mkd NEWS.mkd 18 | doc_confdir= $(docdir)/conf 19 | doc_conf_DATA= conf/mpdcron.conf 20 | doc_conf_hooksdir= $(doc_confdir)/hooks 21 | doc_conf_hooks_DATA= conf/hooks/database conf/hooks/mixer conf/hooks/options \ 22 | conf/hooks/output conf/hooks/player conf/hooks/playlist \ 23 | conf/hooks/queue conf/hooks/stored_playlist conf/hooks/update \ 24 | conf/hooks/dump-outputs.rb conf/hooks/dump-playlists.rb \ 25 | conf/hooks/dump-queue.rb conf/hooks/dump-song.bash \ 26 | conf/hooks/dump-stats.bash conf/hooks/dump-status.bash \ 27 | conf/hooks/submit.rb 28 | doc_conf_modulesdir=$(doc_confdir)/modules 29 | doc_conf_modules_DATA= conf/modules/example.c conf/modules/Makefile 30 | 31 | checksum: dist 32 | @echo "SHA1 $(PACKAGE)-$(VERSION).tar.bz2" 33 | sha1sum $(PACKAGE)-$(VERSION).tar.bz2 > $(PACKAGE)-$(VERSION).tar.bz2.sha1sum 34 | @echo "SIGN $(PACKAGE)-$(VERSION).tar.bz2" 35 | gpg --detach-sign --armor $(PACKAGE)-$(VERSION).tar.bz2 36 | 37 | upload: 38 | @echo "UPLOAD $(PACKAGE)-$(VERSION).tar.bz2*" 39 | scp $(PACKAGE)-$(VERSION).tar.bz2* bach.exherbo.org:public_html/mpdcron 40 | -------------------------------------------------------------------------------- /data/mpdcron.1: -------------------------------------------------------------------------------- 1 | .TH mpdcron 1 "December 23, 2009" "manual" 2 | .SH NAME 3 | .PP 4 | mpdcron - cron like daemon for mpd 5 | .SH SYNOPSIS 6 | .PP 7 | mpdcron [\f[I]option\f[]] 8 | .SH DESCRIPTION 9 | .PP 10 | mpdcron is a cron like daemon for mpd. 11 | It connects to mpd, waits for idle events. 12 | It has two interfaces: 13 | .IP \[bu] 2 14 | Hooks which are executed after setting environment variables which 15 | hooks can use to get more information about the event without 16 | having to connect to mpd. 17 | .IP \[bu] 2 18 | Modules, which are dynamically loaded and have direct access to mpd 19 | connection and the file descriptor of mpdcron's configuration file. 20 | .PP 21 | By default it will get the host name and port for mpd from 22 | \f[B]MPD_HOST\f[] and \f[B]MPD_PORT\f[] environment variables. 23 | \f[B]MPD_PASSWORD\f[] environment variable can be set to make 24 | mpdcron connect to a password-protected mpd. 25 | If these environment variables aren't set, mpdcron connects to 26 | localhost on port 6600. 27 | .SH OPTIONS 28 | .TP 29 | .B -?, --help 30 | Show help options and exit. 31 | .RS 32 | .RE 33 | .TP 34 | .B -V, --version 35 | Print version information and exit. 36 | .RS 37 | .RE 38 | .TP 39 | .B -k, --kill 40 | Instead of launching the daemon, attempt to kill the already 41 | running daemon. 42 | .RS 43 | .RE 44 | .TP 45 | .B -n, --no-daemon 46 | Don't detach from console. 47 | This option can be used for debugging. 48 | .RS 49 | .RE 50 | .SH FILES 51 | .IP \[bu] 2 52 | \f[B]~/.mpdcron/mpdcron.conf\f[] User configuration file 53 | .SH SEE ALSO 54 | .IP \[bu] 2 55 | \f[B]mpd\f[](1) 56 | .IP \[bu] 2 57 | 58 | .SH REPORTING BUGS 59 | .PP 60 | If you find a bug, please report it at 61 | 62 | .SH COPYRIGHT 63 | .PP 64 | Copyright (c) 2009 Ali Polatel 65 | .PD 0 66 | .P 67 | .PD 68 | Free 69 | use of this software is granted under the terms of the GNU General 70 | Public License (GPL). 71 | .SH AUTHOR 72 | Ali Polatel 73 | -------------------------------------------------------------------------------- /src/cron-log.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2010, 2013 Ali Polatel 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include 21 | #include 22 | 23 | #include "cron-defs.h" 24 | 25 | static void 26 | log_output(const gchar *domain, GLogLevelFlags level, 27 | const gchar *message) 28 | { 29 | int dlevel; 30 | 31 | g_return_if_fail(message != NULL && message[0] != '\0'); 32 | 33 | switch (level) { 34 | case G_LOG_LEVEL_CRITICAL: 35 | dlevel = LOG_CRIT; 36 | break; 37 | case G_LOG_LEVEL_WARNING: 38 | dlevel = LOG_WARNING; 39 | break; 40 | case G_LOG_LEVEL_MESSAGE: 41 | dlevel = LOG_NOTICE; 42 | break; 43 | case G_LOG_LEVEL_INFO: 44 | dlevel = LOG_INFO; 45 | break; 46 | case G_LOG_LEVEL_DEBUG: 47 | default: 48 | dlevel = LOG_DEBUG; 49 | break; 50 | } 51 | 52 | if (domain != NULL) 53 | daemon_log(dlevel, "[%s] %s", domain, message); 54 | else 55 | daemon_log(dlevel, "%s", message); 56 | } 57 | 58 | void 59 | log_handler(const gchar *domain, GLogLevelFlags level, const gchar *message, 60 | gpointer userdata) 61 | { 62 | int clevel = GPOINTER_TO_INT(userdata); 63 | 64 | if (((level & G_LOG_LEVEL_MESSAGE) && clevel < 1) || 65 | ((level & G_LOG_LEVEL_INFO) && clevel < 2) || 66 | ((level & G_LOG_LEVEL_DEBUG) && clevel < 3)) 67 | return; 68 | 69 | log_output(domain, level, message); 70 | } 71 | -------------------------------------------------------------------------------- /src/gmodule/scrobbler/scrobbler-record.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009, 2013 Ali Polatel 5 | * Based in part upon mpdscribble which is: 6 | * Copyright (C) 2008-2009 The Music Player Daemon Project 7 | * Copyright (C) 2005-2008 Kuno Woudt 8 | * 9 | * This file is part of the mpdcron mpd client. mpdcron is free software; 10 | * you can redistribute it and/or modify it under the terms of the GNU General 11 | * Public License version 2, as published by the Free Software Foundation. 12 | * 13 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 14 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 | * details. 17 | * 18 | * You should have received a copy of the GNU General Public License along with 19 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 20 | * Place, Suite 330, Boston, MA 02111-1307 USA 21 | */ 22 | 23 | #include "scrobbler-defs.h" 24 | 25 | #include 26 | 27 | void record_copy(struct record *dest, const struct record *src) 28 | { 29 | dest->artist = g_strdup(src->artist); 30 | dest->track = g_strdup(src->track); 31 | dest->album = g_strdup(src->album); 32 | dest->number = g_strdup(src->number); 33 | dest->mbid = g_strdup(src->mbid); 34 | dest->time = g_strdup(src->time); 35 | dest->length = src->length; 36 | dest->source = src->source; 37 | } 38 | 39 | struct record *record_dup(const struct record *src) 40 | { 41 | struct record *dest = g_new(struct record, 1); 42 | record_copy(dest, src); 43 | return dest; 44 | } 45 | 46 | void record_deinit(struct record *record) 47 | { 48 | g_free(record->artist); 49 | g_free(record->track); 50 | g_free(record->album); 51 | g_free(record->number); 52 | g_free(record->mbid); 53 | g_free(record->time); 54 | } 55 | 56 | void record_free(struct record *record) 57 | { 58 | record_deinit(record); 59 | g_free(record); 60 | } 61 | 62 | void record_clear(struct record *record) 63 | { 64 | record->artist = NULL; 65 | record->track = NULL; 66 | record->album = NULL; 67 | record->number = NULL; 68 | record->mbid = NULL; 69 | record->time = NULL; 70 | record->length = 0; 71 | record->source = "P"; 72 | } 73 | -------------------------------------------------------------------------------- /src/gmodule/notification/notification-spawn.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009, 2010 Ali Polatel 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include "notification-defs.h" 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | void 28 | notify_send(const char *icon, const char *summary, const char *body) 29 | { 30 | int i, j, len; 31 | char **myargv; 32 | GError *error; 33 | 34 | i = 0; 35 | len = 8 + (file_config.hints ? g_strv_length(file_config.hints) : 0); 36 | myargv = g_malloc0(sizeof(char *) * len); 37 | myargv[i++] = g_strdup("notify-send"); 38 | if (file_config.urgency != NULL) 39 | myargv[i++] = g_strdup_printf("--urgency=%s", file_config.urgency); 40 | if (file_config.timeout != NULL) 41 | myargv[i++] = g_strdup_printf("--expire-time=%s", file_config.timeout); 42 | if (file_config.type != NULL) 43 | myargv[i++] = g_strdup_printf("--category=%s", file_config.type); 44 | if (icon != NULL) 45 | myargv[i++] = g_strdup_printf("--icon=%s", icon); 46 | myargv[i++] = g_strdup(summary); 47 | myargv[i++] = g_strdup(body); 48 | 49 | if (file_config.hints != NULL) { 50 | for (j = 0; file_config.hints[j] != NULL; j++) 51 | myargv[i++] = g_strdup_printf("--hint=%s", file_config.hints[j]); 52 | } 53 | myargv[i] = NULL; 54 | 55 | error = NULL; 56 | if (!g_spawn_async(NULL, myargv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error)) { 57 | g_warning("Failed to execute notify-send: %s", 58 | error->message); 59 | g_error_free(error); 60 | } 61 | 62 | for (; i >= 0; i--) 63 | g_free(myargv[i]); 64 | g_free(myargv); 65 | } 66 | -------------------------------------------------------------------------------- /src/gmodule/stats/walrus-utils.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009 Ali Polatel 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include "walrus-defs.h" 21 | 22 | #include 23 | 24 | #include 25 | 26 | #include "../utils.h" 27 | 28 | char * 29 | xload_dbpath(void) 30 | { 31 | char *homepath, *confpath, *dbpath; 32 | GKeyFile *kf; 33 | GError *error; 34 | 35 | /* Get home directory */ 36 | if (g_getenv(ENV_HOME_DIR)) 37 | homepath = g_strdup(g_getenv(ENV_HOME_DIR)); 38 | else if (g_getenv("HOME")) 39 | homepath = g_build_filename(g_getenv("HOME"), DOT_MPDCRON, NULL); 40 | else { 41 | g_printerr("Neither "ENV_HOME_DIR" nor HOME is set, exiting!"); 42 | exit(1); 43 | } 44 | 45 | /* Set keyfile path */ 46 | confpath = g_build_filename(homepath, PACKAGE".conf", NULL); 47 | 48 | /* Load key file */ 49 | dbpath = NULL; 50 | error = NULL; 51 | kf = g_key_file_new(); 52 | if (!g_key_file_load_from_file(kf, confpath, G_KEY_FILE_NONE, &error)) { 53 | switch (error->code) { 54 | case G_FILE_ERROR_NOENT: 55 | case G_KEY_FILE_ERROR_NOT_FOUND: 56 | g_error_free(error); 57 | g_key_file_free(kf); 58 | goto skip; 59 | default: 60 | g_printerr("Failed to parse configuration file `%s': %s", 61 | confpath, error->message); 62 | g_error_free(error); 63 | g_key_file_free(kf); 64 | exit(1); 65 | } 66 | } 67 | 68 | error = NULL; 69 | if (!load_string(kf, "stats", "dbpath", false, &dbpath, &error)) { 70 | g_printerr("%s\n", error->message); 71 | g_error_free(error); 72 | g_free(confpath); 73 | g_free(homepath); 74 | g_key_file_free(kf); 75 | exit(1); 76 | } 77 | g_key_file_free(kf); 78 | 79 | skip: 80 | if (dbpath == NULL) 81 | dbpath = g_build_filename(homepath, "stats.db", NULL); 82 | g_free(confpath); 83 | g_free(homepath); 84 | 85 | return dbpath; 86 | } 87 | -------------------------------------------------------------------------------- /src/cron-conf.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009, 2010 Ali Polatel 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include "cron-defs.h" 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | 28 | struct mpdcron_config conf; 29 | 30 | const char * 31 | conf_pid_file_proc(void) 32 | { 33 | char *name; 34 | 35 | if (conf.pid_path) 36 | return conf.pid_path; 37 | name = g_strdup_printf("%s.pid", daemon_pid_file_ident); 38 | conf.pid_path = g_build_filename(conf.home_path, name, NULL); 39 | g_free(name); 40 | return conf.pid_path; 41 | } 42 | 43 | int 44 | conf_init(void) 45 | { 46 | memset(&conf, 0, sizeof(struct mpdcron_config)); 47 | 48 | /* Get home directory */ 49 | if (g_getenv(ENV_HOME_DIR)) 50 | conf.home_path = g_strdup(g_getenv(ENV_HOME_DIR)); 51 | else if (g_getenv("HOME")) 52 | conf.home_path = g_build_filename(g_getenv("HOME"), DOT_MPDCRON, NULL); 53 | else { 54 | g_critical("Neither "ENV_HOME_DIR" nor HOME is set, exiting!"); 55 | return -1; 56 | } 57 | 58 | /* Set keyfile path */ 59 | conf.conf_path = g_build_filename(conf.home_path, PACKAGE".conf", NULL); 60 | 61 | #ifdef HAVE_GMODULE 62 | /* Set module path */ 63 | conf.mod_path = g_build_filename(conf.home_path, DOT_MODULES, NULL); 64 | #endif /* HAVE_GMODULE */ 65 | 66 | /* Get Mpd host, port, password */ 67 | if ((conf.hostname = g_getenv(ENV_MPD_HOST)) == NULL) 68 | conf.hostname = "localhost"; 69 | if ((conf.port = g_getenv(ENV_MPD_PORT)) == NULL) 70 | conf.port = "6600"; 71 | conf.password = g_getenv(ENV_MPD_PASSWORD); 72 | 73 | return 0; 74 | } 75 | 76 | void 77 | conf_free(void) 78 | { 79 | g_free(conf.home_path); 80 | g_free(conf.conf_path); 81 | g_free(conf.pid_path); 82 | #ifdef HAVE_GMODULE 83 | g_free(conf.mod_path); 84 | #endif /* HAVE_GMODULE */ 85 | memset(&conf, 0, sizeof(struct mpdcron_config)); 86 | } 87 | -------------------------------------------------------------------------------- /src/cron-defs.h: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009, 2010 Ali Polatel 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #ifndef MPDCRON_GUARD_CRON_DEFS_H 21 | #define MPDCRON_GUARD_CRON_DEFS_H 1 22 | 23 | #include "cron-config.h" 24 | 25 | #include 26 | #include 27 | 28 | extern struct mpdcron_config conf; 29 | extern GMainLoop *loop; 30 | 31 | const char * 32 | conf_pid_file_proc(void); 33 | 34 | int 35 | conf_init(void); 36 | 37 | void 38 | conf_free(void); 39 | 40 | void 41 | env_clearenv(void); 42 | 43 | void 44 | env_stats(struct mpd_stats *stats); 45 | 46 | void 47 | env_status(struct mpd_status *status); 48 | 49 | void 50 | env_status_currentsong(struct mpd_song *song, struct mpd_status *status); 51 | 52 | int 53 | event_run(struct mpd_connection *conn, enum mpd_idle event); 54 | 55 | int 56 | hooker_run_hook(const char *name); 57 | 58 | int 59 | keyfile_load(GKeyFile **cfd_r); 60 | 61 | #ifdef HAVE_GMODULE 62 | int 63 | keyfile_load_modules(GKeyFile **cfd_r); 64 | #endif /* HAVE_GMODULE */ 65 | 66 | void 67 | log_handler(const gchar *domain, GLogLevelFlags level, const gchar *message, 68 | gpointer userdata); 69 | 70 | void 71 | loop_connect(void); 72 | 73 | void 74 | loop_disconnect(void); 75 | 76 | #ifdef HAVE_GMODULE 77 | int 78 | module_load(const char *modname, GKeyFile *config_fd); 79 | 80 | void 81 | module_close(int gclose); 82 | 83 | int 84 | module_database_run(const struct mpd_connection *conn, const struct mpd_stats *stats); 85 | 86 | int 87 | module_stored_playlist_run(const struct mpd_connection *conn); 88 | 89 | int 90 | module_queue_run(const struct mpd_connection *conn); 91 | 92 | int 93 | module_player_run(const struct mpd_connection *conn, const struct mpd_song *song, 94 | const struct mpd_status *status); 95 | 96 | int 97 | module_mixer_run(const struct mpd_connection *conn, const struct mpd_status *status); 98 | 99 | int 100 | module_output_run(const struct mpd_connection *conn); 101 | 102 | int 103 | module_options_run(const struct mpd_connection *conn, const struct mpd_status *status); 104 | 105 | int 106 | module_update_run(const struct mpd_connection *conn, const struct mpd_status *status); 107 | #endif /* HAVE_GMODULE */ 108 | 109 | #endif /* !MPDCRON_GUARD_CRON_DEFS_H */ 110 | -------------------------------------------------------------------------------- /src/cron-hooker.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009, 2010 Ali Polatel 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include "cron-defs.h" 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | /* Count the number of hook calls */ 28 | static struct hook_calls { 29 | const char *name; 30 | const char *env; 31 | unsigned int ncalls; 32 | } calls[] = { 33 | {"database", "MC_CALLS_DATABASE", 0}, 34 | {"stored_playlist", "MC_CALLS_STORED_PLAYLIST", 0}, 35 | {"queue", "MC_CALLS_QUEUE", 0}, 36 | {"playlist", "MC_CALLS_PLAYLIST", 0}, 37 | {"player", "MC_CALLS_PLAYER", 0}, 38 | {"mixer", "MC_CALLS_MIXER", 0}, 39 | {"output", "MC_CALLS_OUTPUT", 0}, 40 | {"options", "MC_CALLS_OPTIONS", 0}, 41 | {"update", "MC_CALLS_UPDATE", 0}, 42 | {NULL, NULL, 0}, 43 | }; 44 | 45 | static void 46 | hooker_increment(const char *name) 47 | { 48 | char *envstr; 49 | 50 | for (unsigned int i = 0; calls[i].name != NULL; i++) { 51 | if (strcmp(name, calls[i].name) == 0) { 52 | envstr = g_strdup_printf("%u", ++calls[i].ncalls); 53 | g_setenv(calls[i].env, envstr, 1); 54 | g_debug("Setting environment variable %s=%s", calls[i].env, envstr); 55 | g_free(envstr); 56 | if (calls[i].ncalls == UINT_MAX) { 57 | g_debug("Resetting counter for %s", calls[i].env); 58 | calls[i].ncalls = 0; 59 | } 60 | break; 61 | } 62 | } 63 | } 64 | 65 | int 66 | hooker_run_hook(const char *name) 67 | { 68 | gchar **myargv; 69 | GError *error; 70 | 71 | hooker_increment(name); 72 | 73 | myargv = g_malloc(2 * sizeof(gchar *)); 74 | myargv[0] = g_build_filename(DOT_HOOKS, name, NULL); 75 | myargv[1] = NULL; 76 | 77 | g_debug("Running hook: %s home directory: %s", myargv[0], conf.home_path); 78 | 79 | error = NULL; 80 | if (!g_spawn_async(conf.home_path, myargv, NULL, 81 | G_SPAWN_LEAVE_DESCRIPTORS_OPEN | G_SPAWN_CHILD_INHERITS_STDIN, 82 | NULL, NULL, NULL, &error)) { 83 | if (error->code != G_SPAWN_ERROR_NOENT && error->code != G_SPAWN_ERROR_NOEXEC) 84 | g_warning("Failed to execute hook %s: %s", name, error->message); 85 | else 86 | g_debug("Failed to execute hook %s: %s", name, error->message); 87 | g_free(myargv[0]); 88 | g_free(myargv); 89 | g_error_free(error); 90 | return -1; 91 | } 92 | g_free(myargv[0]); 93 | g_free(myargv); 94 | return 0; 95 | } 96 | -------------------------------------------------------------------------------- /src/gmodule/stats/stats-defs.h: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009 Ali Polatel 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #ifndef MPDCRON_GUARD_STATS_DEFS_H 21 | #define MPDCRON_GUARD_STATS_DEFS_H 1 22 | 23 | #ifdef HAVE_CONFIG_H 24 | #include "config.h" 25 | #endif /* HAVE_CONFIG_H */ 26 | 27 | #ifndef MPDCRON_MODULE 28 | #define MPDCRON_MODULE "stats" 29 | #include "../gmodule.h" 30 | #endif /* !MPDCRON_MODULE */ 31 | 32 | #include 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include "stats-sqlite.h" 40 | 41 | #define PROTOCOL_VERSION "0.1" 42 | #define DEFAULT_HOST "any" 43 | #define DEFAULT_PORT 6601 44 | #define DEFAULT_MAX_CONNECTIONS 16 45 | 46 | #define PERMISSION_NONE 0 47 | #define PERMISSION_SELECT 1 48 | #define PERMISSION_UPDATE 2 49 | #define PERMISSION_ALL (PERMISSION_SELECT | PERMISSION_UPDATE) 50 | 51 | #define COMMAND_ARGV_MAX 16 52 | 53 | struct client { 54 | int id; 55 | unsigned perm; 56 | GIOStream *stream; 57 | GDataInputStream *input; 58 | GOutputStream *output; 59 | }; 60 | 61 | enum ack { 62 | ACK_ERROR_ARG = 1, 63 | ACK_ERROR_PASSWORD = 2, 64 | ACK_ERROR_PERMISSION = 3, 65 | ACK_ERROR_UNKNOWN = 4, 66 | }; 67 | 68 | enum command_return { 69 | COMMAND_RETURN_ERROR = -1, 70 | COMMAND_RETURN_OK = 0, 71 | COMMAND_RETURN_KILL = 10, 72 | COMMAND_RETURN_CLOSE = 20, 73 | }; 74 | 75 | /** 76 | * Configuration 77 | */ 78 | struct config { 79 | int max_connections; 80 | char **addrs; 81 | int port; 82 | char *dbpath; 83 | int default_permissions; 84 | GHashTable *passwords; 85 | char *mpd_hostname; 86 | char *mpd_port; 87 | char *mpd_password; 88 | }; 89 | 90 | extern struct config globalconf; 91 | 92 | bool file_load(const struct mpdcron_config *conf, GKeyFile *fd); 93 | void file_cleanup(void); 94 | 95 | /** 96 | * Remote query interface 97 | */ 98 | void server_init(void); 99 | void server_bind(const char *hostname, int port); 100 | void server_start(void); 101 | void server_close(void); 102 | void server_schedule_write(struct client *client, const gchar *data, gsize count); 103 | void server_flush_write(struct client *client); 104 | 105 | /** 106 | * Commands 107 | */ 108 | enum command_return command_process(struct client *client, char *line); 109 | 110 | #endif /* !MPDCRON_GUARD_STATS_DEFS_H */ 111 | -------------------------------------------------------------------------------- /src/gmodule/stats/tokenizer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2003-2009 The Music Player Daemon Project 3 | * http://www.musicpd.org 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along 16 | * with this program; if not, write to the Free Software Foundation, Inc., 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | */ 19 | 20 | #ifndef MPD_TOKENIZER_H 21 | #define MPD_TOKENIZER_H 22 | 23 | #include 24 | 25 | /** 26 | * Reads the next word from the input string. This function modifies 27 | * the input string. 28 | * 29 | * @param input_p the input string; this function returns a pointer to 30 | * the first non-whitespace character of the following token 31 | * @param error_r if this function returns NULL and **input_p!=0, it 32 | * optionally provides a GError object in this argument 33 | * @return a pointer to the null-terminated word, or NULL on error or 34 | * end of line 35 | */ 36 | char * 37 | tokenizer_next_word(char **input_p, GError **error_r); 38 | 39 | /** 40 | * Reads the next unquoted word from the input string. This function 41 | * modifies the input string. 42 | * 43 | * @param input_p the input string; this function returns a pointer to 44 | * the first non-whitespace character of the following token 45 | * @param error_r if this function returns NULL and **input_p!=0, it 46 | * optionally provides a GError object in this argument 47 | * @return a pointer to the null-terminated word, or NULL on error or 48 | * end of line 49 | */ 50 | char * 51 | tokenizer_next_unquoted(char **input_p, GError **error_r); 52 | 53 | /** 54 | * Reads the next quoted string from the input string. A backslash 55 | * escapes the following character. This function modifies the input 56 | * string. 57 | * 58 | * @param input_p the input string; this function returns a pointer to 59 | * the first non-whitespace character of the following token 60 | * @param error_r if this function returns NULL and **input_p!=0, it 61 | * optionally provides a GError object in this argument 62 | * @return a pointer to the null-terminated string, or NULL on error 63 | * or end of line 64 | */ 65 | char * 66 | tokenizer_next_string(char **input_p, GError **error_r); 67 | 68 | /** 69 | * Reads the next unquoted word or quoted string from the input. This 70 | * is a wrapper for tokenizer_next_unquoted() and 71 | * tokenizer_next_string(). 72 | * 73 | * @param input_p the input string; this function returns a pointer to 74 | * the first non-whitespace character of the following token 75 | * @param error_r if this function returns NULL and **input_p!=0, it 76 | * optionally provides a GError object in this argument 77 | * @return a pointer to the null-terminated string, or NULL on error 78 | * or end of line 79 | */ 80 | char * 81 | tokenizer_next_param(char **input_p, GError **error_r); 82 | 83 | #endif 84 | -------------------------------------------------------------------------------- /src/gmodule/gmodule.h: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009, 2010 Ali Polatel 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #ifndef MPDCRON_GUARD_MODULE_H 21 | #define MPDCRON_GUARD_MODULE_H 1 22 | 23 | #include 24 | 25 | #ifdef MPDCRON_MODULE 26 | #define G_LOG_DOMAIN MPDCRON_MODULE 27 | #endif /* MPDCRON_MODULE */ 28 | 29 | #include 30 | #include 31 | 32 | enum mpdcron_init_retval { 33 | MPDCRON_INIT_SUCCESS = 0, /** Success */ 34 | MPDCRON_INIT_FAILURE, /** Failure */ 35 | }; 36 | 37 | enum mpdcron_event_retval { 38 | MPDCRON_EVENT_SUCCESS = 0, /** Success **/ 39 | MPDCRON_EVENT_RECONNECT, /** Schedule a reconnection to mpd server **/ 40 | MPDCRON_EVENT_RECONNECT_NOW, /** Schedule a reconnection to mpd server immediately. **/ 41 | MPDCRON_EVENT_UNLOAD, /** Unload the module **/ 42 | }; 43 | 44 | struct mpdcron_config { 45 | char *home_path; 46 | char *conf_path; 47 | char *pid_path; 48 | char *mod_path; 49 | const char *hostname; 50 | const char *port; 51 | const char *password; 52 | 53 | int no_daemon; 54 | int timeout; 55 | int reconnect; 56 | int killwait; 57 | int loglevel; 58 | 59 | enum mpd_idle idle; 60 | }; 61 | 62 | struct mpdcron_module { 63 | /** Name of the module */ 64 | const char *name; 65 | 66 | /** Initialization function */ 67 | int (*init) (const struct mpdcron_config *, GKeyFile *); 68 | 69 | /** Cleanup function */ 70 | void (*destroy) (void); 71 | 72 | /** Function for database event */ 73 | int (*event_database) (const struct mpd_connection *conn, const struct mpd_stats *); 74 | 75 | /** Function for stored playlist event */ 76 | int (*event_stored_playlist) (const struct mpd_connection *); 77 | 78 | /** Function for queue event */ 79 | int (*event_queue) (const struct mpd_connection *); 80 | 81 | /** Function for player event */ 82 | int (*event_player) (const struct mpd_connection *, const struct mpd_song *, 83 | const struct mpd_status *); 84 | 85 | /** Function for mixer event */ 86 | int (*event_mixer) (const struct mpd_connection *, const struct mpd_status *); 87 | 88 | /** Function for output event */ 89 | int (*event_output) (const struct mpd_connection *); 90 | 91 | /** Function for options event */ 92 | int (*event_options) (const struct mpd_connection *, const struct mpd_status *); 93 | 94 | /** Function for update event */ 95 | int (*event_update) (const struct mpd_connection *, const struct mpd_status *); 96 | }; 97 | 98 | #ifndef MPDCRON_INTERNAL 99 | extern struct mpdcron_module module; 100 | #endif /* !MPDCRON_INTERNAL */ 101 | 102 | #endif /* !MPDCRON_GUARD_MODULE_H */ 103 | -------------------------------------------------------------------------------- /m4/ax_check_compiler_flags.m4: -------------------------------------------------------------------------------- 1 | # =========================================================================== 2 | # http://www.nongnu.org/autoconf-archive/ax_check_compiler_flags.html 3 | # =========================================================================== 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_CHECK_COMPILER_FLAGS(FLAGS, [ACTION-SUCCESS], [ACTION-FAILURE]) 8 | # 9 | # DESCRIPTION 10 | # 11 | # Check whether the given compiler FLAGS work with the current language's 12 | # compiler, or whether they give an error. (Warnings, however, are 13 | # ignored.) 14 | # 15 | # ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on 16 | # success/failure. 17 | # 18 | # LICENSE 19 | # 20 | # Copyright (c) 2009 Steven G. Johnson 21 | # Copyright (c) 2009 Matteo Frigo 22 | # 23 | # This program is free software: you can redistribute it and/or modify it 24 | # under the terms of the GNU General Public License as published by the 25 | # Free Software Foundation, either version 3 of the License, or (at your 26 | # option) any later version. 27 | # 28 | # This program is distributed in the hope that it will be useful, but 29 | # WITHOUT ANY WARRANTY; without even the implied warranty of 30 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 31 | # Public License for more details. 32 | # 33 | # You should have received a copy of the GNU General Public License along 34 | # with this program. If not, see . 35 | # 36 | # As a special exception, the respective Autoconf Macro's copyright owner 37 | # gives unlimited permission to copy, distribute and modify the configure 38 | # scripts that are the output of Autoconf when processing the Macro. You 39 | # need not follow the terms of the GNU General Public License when using 40 | # or distributing such scripts, even though portions of the text of the 41 | # Macro appear in them. The GNU General Public License (GPL) does govern 42 | # all other use of the material that constitutes the Autoconf Macro. 43 | # 44 | # This special exception to the GPL applies to versions of the Autoconf 45 | # Macro released by the Autoconf Archive. When you make and distribute a 46 | # modified version of the Autoconf Macro, you may extend this special 47 | # exception to the GPL to apply to your modified version as well. 48 | 49 | AC_DEFUN([AX_CHECK_COMPILER_FLAGS], 50 | [AC_PREREQ(2.59) dnl for _AC_LANG_PREFIX 51 | AC_MSG_CHECKING([whether _AC_LANG compiler accepts $1]) 52 | dnl Some hackery here since AC_CACHE_VAL can't handle a non-literal varname: 53 | AS_LITERAL_IF([$1], 54 | [AC_CACHE_VAL(AS_TR_SH(ax_cv_[]_AC_LANG_ABBREV[]_flags_[$1]), [ 55 | ax_save_FLAGS=$[]_AC_LANG_PREFIX[]FLAGS 56 | _AC_LANG_PREFIX[]FLAGS="$1" 57 | AC_COMPILE_IFELSE([AC_LANG_PROGRAM()], 58 | AS_TR_SH(ax_cv_[]_AC_LANG_ABBREV[]_flags_[$1])=yes, 59 | AS_TR_SH(ax_cv_[]_AC_LANG_ABBREV[]_flags_[$1])=no) 60 | _AC_LANG_PREFIX[]FLAGS=$ax_save_FLAGS])], 61 | [ax_save_FLAGS=$[]_AC_LANG_PREFIX[]FLAGS 62 | _AC_LANG_PREFIX[]FLAGS="$1" 63 | AC_COMPILE_IFELSE([AC_LANG_PROGRAM()], 64 | eval AS_TR_SH(ax_cv_[]_AC_LANG_ABBREV[]_flags_[$1])=yes, 65 | eval AS_TR_SH(ax_cv_[]_AC_LANG_ABBREV[]_flags_[$1])=no) 66 | _AC_LANG_PREFIX[]FLAGS=$ax_save_FLAGS]) 67 | eval ax_check_compiler_flags=$AS_TR_SH(ax_cv_[]_AC_LANG_ABBREV[]_flags_[$1]) 68 | AC_MSG_RESULT($ax_check_compiler_flags) 69 | if test "x$ax_check_compiler_flags" = xyes; then 70 | m4_default([$2], :) 71 | else 72 | m4_default([$3], :) 73 | fi 74 | ])dnl AX_CHECK_COMPILER_FLAGS 75 | -------------------------------------------------------------------------------- /zsh-completion/_eugene: -------------------------------------------------------------------------------- 1 | #compdef eugene 2 | 3 | # vim: set et sw=4 sts=4 ts=4 ft=zsh : 4 | # ZSH completion for _eugene 5 | # Copyright (c) 2010, 2013 Ali Polatel 6 | # Distributed under the terms of the GNU General Public License v2 7 | 8 | _eugene_command() { 9 | local eugene_cmds 10 | 11 | eugene_cmds=( 12 | help:"Display help and exit" 13 | version:"Display version and exit" 14 | list:"List song/artist/album/genre" 15 | listinfo:"List information of song/artist/album/genre" 16 | count:"Change play count of song/artist/album/genre" 17 | hate:"Hate song/artist/album/genre" 18 | love:"Love song/artist/album/genre" 19 | kill:"Kill song/artist/album/genre" 20 | unkill:"Unkill song/artist/album/genre" 21 | rate:"Rate song/artist/album/genre" 22 | rateabs:"Rate (absolute fashion) song/artist/album/genre" 23 | karma:"Set karma rating of song" 24 | addtag:"Add tag to song/artist/album/genre" 25 | rmtag:"Remove tag from song/artist/album/genre" 26 | listtags:"List tags of song/artist/album/genre" 27 | ) 28 | 29 | if (( CURRENT == 1 )); then 30 | _describe -t command "eugene commands" eugene_cmds 31 | else 32 | local curcontext="$curcontext" 33 | fi 34 | 35 | local cmd=$words[1] 36 | 37 | local curcontext="${curcontext%:*}:mpc-${cmd}" 38 | _call_function ret _eugene_$cmd 39 | } 40 | 41 | _eugene_helper_expr() { 42 | _arguments \ 43 | '(-h --help)'{-h,--help}'[Show help options]' \ 44 | '(-V --version)'{-V,--version}'[Display version]' \ 45 | '(-a --artist)'{-a,--artist}'[List artists instead of songs]' \ 46 | '(-A --album)'{-A,--album}'[List albums instead of songs]' \ 47 | '(-g --genre)'{-g,--genre}'[List genres instead of songs]' \ 48 | ':expression:' 49 | } 50 | 51 | _eugene_helper_number_expr() { 52 | _arguments \ 53 | '(-h --help)'{-h,--help}'[Show help options]' \ 54 | '(-V --version)'{-V,--version}'[Display version]' \ 55 | '(-a --artist)'{-a,--artist}'[List artists instead of songs]' \ 56 | '(-A --album)'{-A,--album}'[List albums instead of songs]' \ 57 | '(-g --genre)'{-g,--genre}'[List genres instead of songs]' \ 58 | ':number:' \ 59 | ':expression:' 60 | } 61 | 62 | _eugene_helper_tag_expr() { 63 | _arguments \ 64 | '(-h --help)'{-h,--help}'[Show help options]' \ 65 | '(-V --version)'{-V,--version}'[Display version]' \ 66 | '(-a --artist)'{-a,--artist}'[List artists instead of songs]' \ 67 | '(-A --album)'{-A,--album}'[List albums instead of songs]' \ 68 | '(-g --genre)'{-g,--genre}'[List genres instead of songs]' \ 69 | ':tag:' \ 70 | ':expression:' 71 | } 72 | 73 | _eugene_help() {} 74 | _eugene_version() {} 75 | 76 | _eugene_list() { 77 | _eugene_helper_expr 78 | } 79 | 80 | _eugene_listinfo() { 81 | _eugene_helper_expr 82 | } 83 | 84 | _eugene_count() { 85 | _eugene_helper_number_expr 86 | } 87 | 88 | _eugene_hate() { 89 | _eugene_helper_expr 90 | } 91 | 92 | _eugene_love() { 93 | _eugene_helper_expr 94 | } 95 | 96 | _eugene_kill() { 97 | _eugene_helper_expr 98 | } 99 | 100 | _eugene_unkill() { 101 | _eugene_helper_expr 102 | } 103 | 104 | _eugene_rate() { 105 | _eugene_helper_number_expr 106 | } 107 | 108 | _eugene_rateabs() { 109 | _eugene_helper_number_expr 110 | } 111 | 112 | _eugene_karma() { 113 | _arguments \ 114 | '(-h --help)'{-h,--help}'[Show help options]' \ 115 | ':number:' \ 116 | ':expression:' 117 | } 118 | 119 | _eugene_addtag() { 120 | _eugene_helper_tag_expr 121 | } 122 | 123 | _eugene_rmtag() { 124 | _eugene_helper_tag_expr 125 | } 126 | 127 | _eugene_listtags() { 128 | _eugene_helper_expr 129 | } 130 | 131 | _arguments \ 132 | '*::eugene command:_eugene_command' 133 | -------------------------------------------------------------------------------- /src/gmodule/stats/eugene-utils.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009, 2010, 2016 Ali Polatel 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include "eugene-defs.h" 21 | #include "walrus-defs.h" /* To add current song to the database. */ 22 | 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | 29 | char * 30 | quote(const char *src) 31 | { 32 | const char *p; 33 | GString *dest; 34 | 35 | g_return_val_if_fail(src != NULL, NULL); 36 | 37 | dest = g_string_new("'"); 38 | p = src; 39 | 40 | while (*p != 0) { 41 | if (*p == '\'') 42 | g_string_append(dest, "''"); 43 | else 44 | g_string_append_c(dest, *p); 45 | ++p; 46 | } 47 | g_string_append_c(dest, '\''); 48 | return g_string_free(dest, FALSE); 49 | } 50 | 51 | struct mpd_song * 52 | load_current_song(void) 53 | { 54 | int port; 55 | const char *hostname, *password; 56 | struct mpd_connection *conn; 57 | struct mpd_song *song; 58 | 59 | char *dbpath; 60 | GError *error; 61 | 62 | hostname = g_getenv(ENV_MPD_HOST) 63 | ? g_getenv(ENV_MPD_HOST) 64 | : "localhost"; 65 | port = g_getenv(ENV_MPD_PORT) 66 | ? atoi(g_getenv(ENV_MPD_PORT)) 67 | : 6600; 68 | password = g_getenv(ENV_MPD_PASSWORD); 69 | 70 | if ((conn = mpd_connection_new(hostname, port, 0)) == NULL) { 71 | eulog(LOG_ERR, "Error creating mpd connection: out of memory"); 72 | return NULL; 73 | } 74 | 75 | if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS) { 76 | eulog(LOG_ERR, "Failed to connect to Mpd: %s", 77 | mpd_connection_get_error_message(conn)); 78 | mpd_connection_free(conn); 79 | return NULL; 80 | } 81 | 82 | if (password != NULL) { 83 | if (!mpd_run_password(conn, password)) { 84 | eulog(LOG_ERR, "Authentication failed: %s", 85 | mpd_connection_get_error_message(conn)); 86 | mpd_connection_free(conn); 87 | return NULL; 88 | } 89 | } 90 | 91 | if ((song = mpd_run_current_song(conn)) == NULL) { 92 | if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS) { 93 | eulog(LOG_ERR, "Failed to get current song: %s", 94 | mpd_connection_get_error_message(conn)); 95 | mpd_connection_free(conn); 96 | return NULL; 97 | } 98 | eulog(LOG_WARNING, "No song playing at the moment"); 99 | mpd_connection_free(conn); 100 | return NULL; 101 | } 102 | 103 | if (!getenv("EUGENE_NODB")) { 104 | /* ^^ Eugene may be remote to the mpdcron stats database, so let 105 | * the user skip this if need be. 106 | * TODO: Make this a server command. */ 107 | if (!db_initialized()) { 108 | dbpath = xload_dbpath(); 109 | 110 | error = NULL; 111 | if (!db_init(dbpath, true, false, &error)) { 112 | g_printerr("Failed to load database `%s': %s\n", dbpath, error->message); 113 | g_error_free(error); 114 | g_free(dbpath); 115 | } 116 | g_free(dbpath); 117 | } 118 | 119 | error = NULL; 120 | if (db_initialized() && !db_process(song, false, -1, &error)) { 121 | g_printerr("Failed to process song %s: %s\n", 122 | mpd_song_get_uri(song), 123 | error->message); 124 | g_error_free(error); 125 | } 126 | } 127 | 128 | mpd_connection_free(conn); 129 | return song; 130 | } 131 | -------------------------------------------------------------------------------- /src/gmodule/stats/eugene-karma.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009, 2010 Ali Polatel 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include "eugene-defs.h" 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | static int 28 | karma_song(struct mpdcron_connection *conn, const char *expr, const char *karma) 29 | { 30 | int changes; 31 | char *esc_uri, *query_uri; 32 | struct mpd_song *song; 33 | 34 | if (expr != NULL) { 35 | if (!mpdcron_karma_expr(conn, expr, karma, &changes)) { 36 | eulog(LOG_ERR, "Failed to change karma of song: %s", 37 | conn->error->message); 38 | return 1; 39 | } 40 | } 41 | else { 42 | if ((song = load_current_song()) == NULL) 43 | return 1; 44 | 45 | esc_uri = quote(mpd_song_get_uri(song)); 46 | query_uri = g_strdup_printf("uri=%s", esc_uri); 47 | g_free(esc_uri); 48 | mpd_song_free(song); 49 | 50 | if (!mpdcron_karma_expr(conn, query_uri, karma, &changes)) { 51 | eulog(LOG_ERR, "Failed to change karma of current " 52 | "playing song: %s", 53 | conn->error->message); 54 | g_free(query_uri); 55 | return 1; 56 | } 57 | g_free(query_uri); 58 | } 59 | printf("Modified %d entries\n", changes); 60 | return 0; 61 | } 62 | 63 | static int 64 | cmd_karma_internal(const char *expr, const char *karma) 65 | { 66 | int port, ret; 67 | const char *hostname, *password; 68 | struct mpdcron_connection *conn; 69 | 70 | hostname = g_getenv(ENV_MPDCRON_HOST) 71 | ? g_getenv(ENV_MPDCRON_HOST) 72 | : DEFAULT_HOSTNAME; 73 | port = g_getenv(ENV_MPDCRON_PORT) 74 | ? atoi(g_getenv(ENV_MPDCRON_PORT)) 75 | : DEFAULT_PORT; 76 | password = g_getenv(ENV_MPDCRON_PASSWORD); 77 | 78 | conn = mpdcron_connection_new(hostname, port); 79 | if (conn->error != NULL) { 80 | eulog(LOG_ERR, "Failed to connect: %s", conn->error->message); 81 | mpdcron_connection_free(conn); 82 | return 1; 83 | } 84 | 85 | if (password != NULL) { 86 | if (!mpdcron_password(conn, password)) { 87 | eulog(LOG_ERR, "Authentication failed: %s", conn->error->message); 88 | mpdcron_connection_free(conn); 89 | return 1; 90 | } 91 | } 92 | 93 | ret = karma_song(conn, expr, karma); 94 | mpdcron_connection_free(conn); 95 | return ret; 96 | } 97 | 98 | int 99 | cmd_karma(int argc, char **argv) 100 | { 101 | int ret; 102 | GError *error = NULL; 103 | GOptionContext *ctx; 104 | 105 | ctx = g_option_context_new("KARMA [EXPRESSION]"); 106 | g_option_context_set_summary(ctx, "eugene-karma-"VERSION GITHEAD 107 | " - Set karma rating of song"); 108 | g_option_context_set_description(ctx, 109 | "By default this command works on the current playing song.\n" 110 | "For more information about the expression syntax, see:\n" 111 | "http://www.sqlite.org/lang_expr.html"); 112 | if (!g_option_context_parse(ctx, &argc, &argv, &error)) { 113 | g_printerr("Option parsing failed: %s\n", error->message); 114 | g_error_free(error); 115 | g_option_context_free(ctx); 116 | return 1; 117 | } 118 | g_option_context_free(ctx); 119 | 120 | if (argc <= 1) { 121 | g_printerr("No karma given\n"); 122 | ret = 1; 123 | } 124 | else if (argc > 2) 125 | ret = cmd_karma_internal(argv[2], argv[1]); 126 | else 127 | ret = cmd_karma_internal(NULL, argv[1]); 128 | return ret; 129 | } 130 | -------------------------------------------------------------------------------- /src/gmodule/stats/homescrape.in: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # vim: set sw=2 sts=2 tw=100 et nowrap fenc=utf-8 : 3 | # Copyright 2010 Ali Polatel 4 | # Distributed under the terms of the GNU General Public License v2 5 | 6 | %w{getoptlong net/http time uri rubygems nokogiri}.each {|m| require m } 7 | 8 | begin 9 | require 'chronic' 10 | has_chronic = true 11 | rescue LoadError 12 | has_chronic = false 13 | end 14 | 15 | MYNAME = File.basename $0, ".rb" 16 | MYVERSION = "@VERSION@" + "@GITHEAD@" 17 | 18 | class UserNotFound < StandardError; end 19 | 20 | class Scraper 21 | LASTFM_URL = 'http://www.last.fm/user/%s/tracks' 22 | LASTFM_DATE_FORMAT = '%Y-%m-%dT%H:%M:%SZ' 23 | 24 | attr_accessor :username, :url 25 | 26 | def initialize username 27 | @username = username 28 | @url = sprintf(LASTFM_URL, username) 29 | 30 | # Set up proxy 31 | @proxy_url = URI.parse(ENV['http_proxy']) if ENV['http_proxy'] 32 | @proxy_host = @proxy_url.host if @proxy_url and @proxy_url.host 33 | @proxy_port = @proxy_url.port if @proxy_url and @proxy_url.port 34 | @proxy_user, @proxy_pass = @proxy_url.userinfo.split(/:/) if @proxy_url and @proxy_url.userinfo 35 | end 36 | 37 | def fetch since, page=1, &block 38 | uri = URI.parse(@url + "?page=#{page}") 39 | req = Net::HTTP::Get.new(uri.request_uri) 40 | 41 | res = Net::HTTP::Proxy(@proxy_host, @proxy_port, 42 | @proxy_user, @proxy_pass).start(uri.host, uri.port) {|http| 43 | http.request(req) 44 | } 45 | data = res.body 46 | raise UserNotFound if data =~ /User not found/ 47 | 48 | doc = Nokogiri::HTML data 49 | 50 | if page == 1 51 | if doc.css('a.lastpage').length != 0 52 | @lastpage = doc.css('a.lastpage')[0].content.to_i 53 | else 54 | @lastpage = 1 55 | end 56 | end 57 | 58 | tags = doc.xpath(<<-EOF) 59 | //tr[ 60 | td[@class="subjectCell"] 61 | and td[@class="lovedCell"] 62 | and td[@class="dateCell last"] 63 | ] 64 | EOF 65 | 66 | tags.each do |tag| 67 | subjectCell = tag.children[2] 68 | lovedCell = tag.children[4] 69 | dateCell = tag.children[8] 70 | 71 | artist = subjectCell.children[1].content 72 | title = subjectCell.children[3].content 73 | love = lovedCell.children[1] ? true : false 74 | date = Date.strptime(dateCell.at('//abbr/@title').to_s, LASTFM_DATE_FORMAT) 75 | return if since > date 76 | block.call artist, title, love 77 | end 78 | 79 | if page <= @lastpage 80 | fetch since, page + 1, &block 81 | end 82 | end 83 | end 84 | 85 | def usage out, code 86 | out.puts < 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include "eugene-defs.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | static int verbosity = LOG_DEBUG; 34 | 35 | static void 36 | about(void) 37 | { 38 | printf("eugene-"VERSION GITHEAD "\n"); 39 | } 40 | 41 | G_GNUC_NORETURN 42 | static void 43 | usage(FILE *outf, int exitval) 44 | { 45 | fprintf(outf, "" 46 | "eugene -- mpdcron statistics client\n" 47 | "eugene COMMAND [OPTIONS]\n" 48 | "\n" 49 | "Commands:\n" 50 | "help Display help and exit\n" 51 | "version Display version and exit\n" 52 | "list List song/artist/album/genre\n" 53 | "listinfo List song/artist/album/genre\n" 54 | "count Change play count of song/artist/album/genre\n" 55 | "hate Hate song/artist/album/genre\n" 56 | "love Love song/artist/album/genre\n" 57 | "kill Kill song/artist/album/genre\n" 58 | "unkill Unkill song/artist/album/genre\n" 59 | "rate Rate song/artist/album/genre\n" 60 | "rateabs Rate (absolute fashion) song/artist/album/genre\n" 61 | "karma Set karma rating of song\n" 62 | "addtag Add tag to song/artist/album/genre\n" 63 | "rmtag Remove tag from song/artist/album/genre\n" 64 | "listtags List tags of song/artist/album/genre\n" 65 | "\n" 66 | "See eugene COMMAND --help for more information\n"); 67 | exit(exitval); 68 | } 69 | 70 | static int 71 | run_cmd(int argc, char **argv) 72 | { 73 | if (strncmp(argv[0], "help", 4) == 0) 74 | usage(stdout, 0); 75 | else if (strncmp(argv[0], "version", 8) == 0) { 76 | about(); 77 | return 0; 78 | } 79 | else if (strncmp(argv[0], "hate", 5) == 0) 80 | return cmd_hate(argc, argv); 81 | else if (strncmp(argv[0], "love", 5) == 0) 82 | return cmd_love(argc, argv); 83 | else if (strncmp(argv[0], "kill", 5) == 0) 84 | return cmd_kill(argc, argv); 85 | else if (strncmp(argv[0], "unkill", 7) == 0) 86 | return cmd_unkill(argc, argv); 87 | else if (strncmp(argv[0], "rate", 5) == 0) 88 | return cmd_rate(argc, argv); 89 | else if (strncmp(argv[0], "rateabs", 8) == 0) 90 | return cmd_rate_absolute(argc, argv); 91 | else if (strncmp(argv[0], "list", 5) == 0) 92 | return cmd_list(argc, argv); 93 | else if (strncmp(argv[0], "listinfo", 9) == 0) 94 | return cmd_listinfo(argc, argv); 95 | else if (strncmp(argv[0], "addtag", 7) == 0) 96 | return cmd_addtag(argc, argv); 97 | else if (strncmp(argv[0], "rmtag", 6) == 0) 98 | return cmd_rmtag(argc, argv); 99 | else if (strncmp(argv[0], "listtags", 9) == 0) 100 | return cmd_listtags(argc, argv); 101 | else if (strncmp(argv[0], "count", 6) == 0) 102 | return cmd_count(argc, argv); 103 | else if (strncmp(argv[0], "karma", 6) == 0) 104 | return cmd_karma(argc, argv); 105 | fprintf(stderr, "Unknown command `%s'\n", argv[0]); 106 | usage(stderr, 1); 107 | } 108 | 109 | void 110 | eulog(int level, const char *fmt, ...) 111 | { 112 | va_list args; 113 | 114 | if (level > verbosity) 115 | return; 116 | 117 | va_start(args, fmt); 118 | vfprintf(stderr, fmt, args); 119 | va_end(args); 120 | 121 | fputc('\n', stderr); 122 | } 123 | 124 | int 125 | main(int argc, char **argv) 126 | { 127 | g_type_init(); 128 | 129 | if (argc < 2) 130 | usage(stderr, 1); 131 | return run_cmd(argc - 1, argv + 1); 132 | } 133 | -------------------------------------------------------------------------------- /src/gmodule/utils.h: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009, 2016 Ali Polatel 5 | * Based in part upon mpdscribble which is: 6 | * Copyright (C) 2008-2009 The Music Player Daemon Project 7 | * Copyright (C) 2005-2008 Kuno Woudt 8 | * 9 | * This file is part of the mpdcron mpd client. mpdcron is free software; 10 | * you can redistribute it and/or modify it under the terms of the GNU General 11 | * Public License version 2, as published by the Free Software Foundation. 12 | * 13 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 14 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 | * details. 17 | * 18 | * You should have received a copy of the GNU General Public License along with 19 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 20 | * Place, Suite 330, Boston, MA 02111-1307 USA 21 | */ 22 | 23 | #ifndef MPDCRON_GUARD_UTILS_H 24 | #define MPDCRON_GUARD_UTILS_H 1 25 | 26 | #include 27 | #include 28 | 29 | #include 30 | 31 | static GQuark 32 | kf_quark(void) 33 | { 34 | return g_quark_from_static_string("keyfile"); 35 | } 36 | 37 | G_GNUC_UNUSED 38 | static bool 39 | load_string(GKeyFile *fd, const char *grp, const char *name, bool mustload, 40 | char **value_r, GError **error) 41 | { 42 | char *value; 43 | GError *e = NULL; 44 | 45 | if (*value_r != NULL) { 46 | /* already set */ 47 | return true; 48 | } 49 | 50 | value = g_key_file_get_string(fd, grp, name, &e); 51 | if (e != NULL) { 52 | switch (e->code) { 53 | case G_KEY_FILE_ERROR_GROUP_NOT_FOUND: 54 | case G_KEY_FILE_ERROR_KEY_NOT_FOUND: 55 | if (!mustload) { 56 | g_error_free(e); 57 | value = NULL; 58 | break; 59 | } 60 | /* fall through */ 61 | default: 62 | g_set_error(error, kf_quark(), e->code, 63 | "Failed to load string %s.%s: %s", 64 | grp, name, e->message); 65 | g_error_free(e); 66 | return false; 67 | } 68 | } 69 | 70 | if (value != NULL) 71 | g_strchomp(value); 72 | 73 | *value_r = value; 74 | return true; 75 | } 76 | 77 | G_GNUC_UNUSED 78 | static bool 79 | load_integer(GKeyFile *fd, const char *grp, const char *name, int mustload, 80 | int *value_r, GError **error) 81 | { 82 | int value; 83 | GError *e = NULL; 84 | 85 | if (*value_r != -1) { 86 | /* already set */ 87 | return true; 88 | } 89 | 90 | value = g_key_file_get_integer(fd, grp, name, &e); 91 | if (e != NULL) { 92 | switch (e->code) { 93 | case G_KEY_FILE_ERROR_GROUP_NOT_FOUND: 94 | case G_KEY_FILE_ERROR_KEY_NOT_FOUND: 95 | if (!mustload) { 96 | g_error_free(e); 97 | value = -1; 98 | break; 99 | } 100 | /* fall through */ 101 | default: 102 | g_set_error(error, kf_quark(), e->code, 103 | "Failed to load integer %s.%s: %s", 104 | grp, name, e->message); 105 | g_error_free(e); 106 | return false; 107 | } 108 | } 109 | 110 | *value_r = value; 111 | return true; 112 | } 113 | 114 | G_GNUC_UNUSED 115 | static bool 116 | song_check_tags(const struct mpd_song *song, char **artist_r, char **title_r) 117 | { 118 | char *artist, *title; 119 | 120 | g_assert(song != NULL); 121 | g_assert(artist_r != NULL); 122 | g_assert(title_r != NULL); 123 | 124 | artist = g_strdup(mpd_song_get_tag(song, MPD_TAG_ARTIST, 0)); 125 | title = g_strdup(mpd_song_get_tag(song, MPD_TAG_TITLE, 0)); 126 | 127 | if (!artist && !title) { 128 | return false; 129 | } else if (!artist && title) { 130 | /* Special case for radio stations. */ 131 | char *p; 132 | 133 | if (title[0] == '-') { 134 | g_free(title); 135 | return false; 136 | } 137 | 138 | p = strchr(title, '-'); 139 | 140 | if (!p) { 141 | g_free(title); 142 | return false; 143 | } 144 | 145 | if (*(p - 1) == ' ') { 146 | *(--p) = 0; 147 | *artist_r = g_strdup(title); 148 | *(p++) = ' '; 149 | } else { 150 | *p = 0; 151 | *artist_r = g_strdup(title); 152 | *p = '-'; 153 | } 154 | 155 | if (*(p + 1) == ' ') 156 | ++p; 157 | *title_r = g_strdup(p); 158 | g_free(title); 159 | } else { 160 | *artist_r = artist; 161 | *title_r = title; 162 | } 163 | 164 | return true; 165 | } 166 | 167 | #endif /* !MPDCRON_GUARD_UTILS_H */ 168 | -------------------------------------------------------------------------------- /src/gmodule/scrobbler/scrobbler-defs.h: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009, 2013 Ali Polatel 5 | * Based in part upon mpdscribble which is: 6 | * Copyright (C) 2008-2009 The Music Player Daemon Project 7 | * Copyright (C) 2005-2008 Kuno Woudt 8 | * 9 | * This file is part of the mpdcron mpd client. mpdcron is free software; 10 | * you can redistribute it and/or modify it under the terms of the GNU General 11 | * Public License version 2, as published by the Free Software Foundation. 12 | * 13 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 14 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 | * details. 17 | * 18 | * You should have received a copy of the GNU General Public License along with 19 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 20 | * Place, Suite 330, Boston, MA 02111-1307 USA 21 | */ 22 | 23 | #ifndef MPDCRON_GUARD_SCROBBLER_DEFS_H 24 | #define MPDCRON_GUARD_SCROBBLER_DEFS_H 1 25 | 26 | #ifdef HAVE_CONFIG_H 27 | #include "config.h" 28 | #endif /* !HAVE_CONFIG_H */ 29 | 30 | #define MPDCRON_MODULE "scrobbler" 31 | #include "../gmodule.h" 32 | 33 | #include 34 | #include 35 | 36 | #include 37 | 38 | struct record { 39 | char *artist; 40 | char *track; 41 | char *album; 42 | char *number; 43 | char *mbid; 44 | char *time; 45 | int length; 46 | const char *source; 47 | }; 48 | 49 | struct http_client_handler { 50 | void (*response)(size_t length, const char *data, void *ctx); 51 | void (*error)(GError *error, void *ctx); 52 | }; 53 | 54 | struct config { 55 | char *proxy; 56 | 57 | /** 58 | * The interval in seconds after which the journal is saved to 59 | * the file system. 60 | */ 61 | int journal_interval; 62 | 63 | GSList *scrobblers; 64 | }; 65 | 66 | struct scrobbler_config { 67 | /** 68 | * The name of the mpdscribble.conf section. It is used in 69 | * log messages. 70 | */ 71 | char *name; 72 | 73 | char *url; 74 | char *username; 75 | char *password; 76 | 77 | /** 78 | * The path of the journal file. It contains records which 79 | * have not been submitted yet. 80 | */ 81 | char *journal; 82 | }; 83 | 84 | extern struct config file_config; 85 | 86 | /** 87 | * Copies attributes from one record to another. Does not free 88 | * existing values in the destination record. 89 | */ 90 | void 91 | record_copy(struct record *dest, const struct record *src); 92 | /** 93 | * Duplicates a record object. 94 | */ 95 | struct record * 96 | record_dup(const struct record *src); 97 | /** 98 | * Deinitializes a record object, freeing all members. 99 | */ 100 | void 101 | record_deinit(struct record *record); 102 | /** 103 | * Frees a record object: free all members with record_deinit(), and 104 | * free the record pointer itself. 105 | */ 106 | void 107 | record_free(struct record *record); 108 | 109 | void 110 | record_clear(struct record *record); 111 | 112 | static inline bool 113 | record_is_defined(const struct record *record) 114 | { 115 | return record->artist != NULL && record->track != NULL; 116 | } 117 | 118 | bool 119 | journal_write(const char *path, GQueue *queue); 120 | 121 | void 122 | journal_read(const char *path, GQueue *queue); 123 | 124 | /** 125 | * Perform global initialization on the HTTP client library. 126 | */ 127 | int 128 | http_client_init(void); 129 | /** 130 | * Global deinitializaton. 131 | */ 132 | void 133 | http_client_finish(void); 134 | /** 135 | * Escapes URI parameters with '%'. Free the return value with 136 | * g_free(). 137 | */ 138 | char * 139 | http_client_uri_escape(const char *src); 140 | 141 | void 142 | http_client_request(const char *url, const char *post_data, 143 | const struct http_client_handler *handler, void *ctx); 144 | 145 | 146 | void 147 | as_init(GSList *scrobbler_configs); 148 | 149 | void 150 | as_cleanup(void); 151 | 152 | void 153 | as_now_playing(const char *artist, const char *track, 154 | const char *album, const char *number, 155 | const char *mbid, const int length); 156 | void 157 | as_songchange(const char *file, const char *artist, const char *track, 158 | const char *album, const char *number, 159 | const char *mbid, const int length, 160 | const char *time); 161 | 162 | void 163 | as_save_cache(void); 164 | 165 | char * 166 | as_timestamp(void); 167 | 168 | int 169 | file_load(GKeyFile *fd); 170 | 171 | void 172 | file_cleanup(void); 173 | 174 | gboolean 175 | timer_save_journal(gpointer data); 176 | 177 | #endif /* !MPDCRON_GUARD_SCROBBLER_DEFS_H */ 178 | -------------------------------------------------------------------------------- /conf/modules/example.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet ai cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009 Ali Polatel 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | /* mpdcron example module to print volume on stdout. 21 | * Compile this file like: 22 | * gcc -fPIC -shared \ 23 | * $(pkg-config --cflags --libs glib-2.0) \ 24 | * $(pkg-config --cflags --libs libmpdclient) \ 25 | * example.c -o example.so 26 | * Put it under MPDCRON_DIR/modules where MPDCRON_DIR is ~/.mpdcron by default. 27 | * Load it from your configuration file like: 28 | * [main] 29 | * modules = example 30 | */ 31 | 32 | /* Define MPDCRON_MODULE before including the file. 33 | */ 34 | #define MPDCRON_MODULE "example" 35 | #include 36 | 37 | #include 38 | 39 | #include 40 | #include 41 | 42 | static int count; 43 | static int last_volume; 44 | 45 | /* Prototypes */ 46 | int init(const struct mpdcron_config *, GKeyFile *); 47 | void destroy(void); 48 | int run(const struct mpd_connection *, const struct mpd_status *); 49 | 50 | /* Every module should define module which is of type struct mpdcron_module. 51 | * This is the initial symbol mpdcron looks for in the module. 52 | */ 53 | struct mpdcron_module module = { 54 | .name = "Example", 55 | .init = init, 56 | .destroy = destroy, 57 | .event_mixer = run, 58 | }; 59 | 60 | /** 61 | * Initialization function. 62 | * @param conf mpdcron's global configuration. 63 | * @param fd This file descriptor points to mpdcron's configuration file. 64 | * Never call g_key_file_free() on this! 65 | * @return On success this function should return MPDCRON_INIT_SUCCESS. 66 | * On failure this function should return MPDCRON_INIT_FAILURE. 67 | */ 68 | int init(G_GNUC_UNUSED const struct mpdcron_config *conf, GKeyFile *fd) 69 | { 70 | GError *parse_error; 71 | 72 | /* You may use GLib logging functions to do the logging. 73 | */ 74 | g_message("Hello from example module!"); 75 | 76 | /* Parse configuration here. */ 77 | parse_error = NULL; 78 | count = g_key_file_get_integer(fd, "example", "count", &parse_error); 79 | if (parse_error != NULL) { 80 | switch (parse_error->code) { 81 | case G_KEY_FILE_ERROR_GROUP_NOT_FOUND: 82 | case G_KEY_FILE_ERROR_KEY_NOT_FOUND: 83 | /* ignore */ 84 | g_error_free(parse_error); 85 | break; 86 | default: 87 | g_critical("Parse error: %s", parse_error->message); 88 | g_error_free(parse_error); 89 | return MPDCRON_INIT_FAILURE; 90 | } 91 | } 92 | if (count <= 0) 93 | count = 5; 94 | 95 | last_volume = -1; 96 | return MPDCRON_INIT_SUCCESS; 97 | } 98 | 99 | /** 100 | * Destroy function. 101 | * This function should be used to free any allocated data and do other 102 | * cleaning up that the module has to make. 103 | */ 104 | void destroy(void) 105 | { 106 | g_message("Bye from example module!"); 107 | /* Do the cleaning up. */ 108 | } 109 | 110 | /** 111 | * Mixer event function. 112 | * @param conn: mpd connection 113 | * @param status: mpd status 114 | * @return This function should return MPDCRON_RUN_SUCCESS on success. 115 | * This function should return MPDCRON_RUN_RECONNECT to schedule 116 | * a reconnection to mpd server. 117 | * This function should return MPDCRON_RUN_RECONNECT_NOW to 118 | * schedule a reconnection by cancelling to run any of the next 119 | * modules in the queue. 120 | * This function should return MPDCRON_RUN_UNLOAD when the 121 | * module needs to be unloaded. 122 | */ 123 | int run(G_GNUC_UNUSED const struct mpd_connection *conn, const struct mpd_status *status) 124 | { 125 | int volume; 126 | 127 | if (count-- <= 0) 128 | return MPDCRON_EVENT_UNLOAD; 129 | 130 | volume = mpd_status_get_volume(status); 131 | 132 | if (last_volume < 0) 133 | g_message("Volume set to: %d%%", volume); 134 | else 135 | g_message("Volume %s from: %d to: %d%%", 136 | (last_volume < volume) ? "increased" : "decreased", 137 | last_volume, volume); 138 | 139 | last_volume = volume; 140 | return MPDCRON_EVENT_SUCCESS; 141 | } 142 | -------------------------------------------------------------------------------- /src/gmodule/notification/notification-file.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009, 2010 Ali Polatel 5 | * Based in part upon mpdscribble which is: 6 | * Copyright (C) 2008-2009 The Music Player Daemon Project 7 | * Copyright (C) 2005-2008 Kuno Woudt 8 | * 9 | * This file is part of the mpdcron mpd client. mpdcron is free software; 10 | * you can redistribute it and/or modify it under the terms of the GNU General 11 | * Public License version 2, as published by the Free Software Foundation. 12 | * 13 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 14 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 | * details. 17 | * 18 | * You should have received a copy of the GNU General Public License along with 19 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 20 | * Place, Suite 330, Boston, MA 02111-1307 USA 21 | */ 22 | 23 | #include "notification-defs.h" 24 | 25 | #include 26 | #include 27 | 28 | #include 29 | 30 | #include "../utils.h" 31 | 32 | struct config file_config; 33 | 34 | int 35 | file_load(GKeyFile *fd) 36 | { 37 | int event; 38 | char **values; 39 | GError *error; 40 | 41 | memset(&file_config, 0, sizeof(struct config)); 42 | 43 | error = NULL; 44 | if (!load_string(fd, MPDCRON_MODULE, "cover_path", false, &file_config.cover_path, &error)) { 45 | g_critical("Failed to load "MPDCRON_MODULE".cover_path: %s", error->message); 46 | g_error_free(error); 47 | return -1; 48 | } 49 | if (!load_string(fd, MPDCRON_MODULE, "cover_suffix", false, &file_config.cover_suffix, &error)) { 50 | g_critical("Failed to load "MPDCRON_MODULE".cover_suffix: %s", error->message); 51 | g_error_free(error); 52 | return -1; 53 | } 54 | if (!load_string(fd, MPDCRON_MODULE, "timeout", false, &file_config.timeout, &error)) { 55 | g_critical("Failed to load "MPDCRON_MODULE".timeout: %s", error->message); 56 | g_error_free(error); 57 | return -1; 58 | } 59 | if (!load_string(fd, MPDCRON_MODULE, "type", false, &file_config.type, &error)) { 60 | g_critical("Failed to load "MPDCRON_MODULE".type: %s", error->message); 61 | g_error_free(error); 62 | return -1; 63 | } 64 | if (!load_string(fd, MPDCRON_MODULE, "urgency", false, &file_config.urgency, &error)) { 65 | g_critical("Failed to load "MPDCRON_MODULE".urgency: %s", error->message); 66 | g_error_free(error); 67 | return -1; 68 | } 69 | 70 | error = NULL; 71 | file_config.hints = g_key_file_get_string_list(fd, MPDCRON_MODULE, "hints", NULL, &error); 72 | if (error != NULL) { 73 | switch (error->code) { 74 | case G_KEY_FILE_ERROR_GROUP_NOT_FOUND: 75 | case G_KEY_FILE_ERROR_KEY_NOT_FOUND: 76 | g_error_free(error); 77 | break; 78 | default: 79 | g_critical("Failed to load %s.hints: %s", 80 | MPDCRON_MODULE, error->message); 81 | g_error_free(error); 82 | return -1; 83 | } 84 | } 85 | 86 | error = NULL; 87 | values = g_key_file_get_string_list(fd, MPDCRON_MODULE, "events", NULL, &error); 88 | if (error != NULL) { 89 | switch (error->code) { 90 | case G_KEY_FILE_ERROR_GROUP_NOT_FOUND: 91 | case G_KEY_FILE_ERROR_KEY_NOT_FOUND: 92 | g_error_free(error); 93 | break; 94 | default: 95 | g_critical("Failed to load "MPDCRON_MODULE".events: %s", 96 | error->message); 97 | g_error_free(error); 98 | return -1; 99 | } 100 | } 101 | 102 | if (values != NULL) { 103 | for (unsigned int i = 0; values[i] != NULL; i++) { 104 | if ((event = mpd_idle_name_parse(values[i])) < 0) 105 | g_warning("Invalid value `%s' in "MPDCRON_MODULE".events", 106 | values[i]); 107 | else if (event == MPD_IDLE_STORED_PLAYLIST || 108 | event == MPD_IDLE_QUEUE || 109 | event == MPD_IDLE_OUTPUT) 110 | g_warning("Event `%s' not a supported event", 111 | values[i]); 112 | else 113 | file_config.events |= event; 114 | } 115 | g_strfreev(values); 116 | } 117 | 118 | if (file_config.events == 0) 119 | file_config.events = MPD_IDLE_DATABASE | MPD_IDLE_PLAYER | 120 | MPD_IDLE_MIXER | MPD_IDLE_OPTIONS | MPD_IDLE_UPDATE; 121 | 122 | if (file_config.cover_path == NULL && g_getenv("HOME") != NULL) 123 | file_config.cover_path = g_build_filename(g_getenv("HOME"), ".covers", NULL); 124 | if (file_config.cover_suffix == NULL) 125 | file_config.cover_suffix = g_strdup("jpg"); 126 | return 0; 127 | } 128 | 129 | void 130 | file_cleanup(void) 131 | { 132 | g_free(file_config.cover_path); 133 | g_free(file_config.cover_suffix); 134 | g_free(file_config.timeout); 135 | g_free(file_config.type); 136 | g_free(file_config.urgency); 137 | if (file_config.hints != NULL) 138 | g_strfreev(file_config.hints); 139 | } 140 | -------------------------------------------------------------------------------- /src/gmodule/scrobbler/scrobbler-file.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009, 2010 Ali Polatel 5 | * Based in part upon mpdscribble which is: 6 | * Copyright (C) 2008-2009 The Music Player Daemon Project 7 | * Copyright (C) 2005-2008 Kuno Woudt 8 | * 9 | * This file is part of the mpdcron mpd client. mpdcron is free software; 10 | * you can redistribute it and/or modify it under the terms of the GNU General 11 | * Public License version 2, as published by the Free Software Foundation. 12 | * 13 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 14 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 | * details. 17 | * 18 | * You should have received a copy of the GNU General Public License along with 19 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 20 | * Place, Suite 330, Boston, MA 02111-1307 USA 21 | */ 22 | 23 | #include "scrobbler-defs.h" 24 | 25 | #include 26 | #include 27 | 28 | #include 29 | 30 | #include "../utils.h" 31 | 32 | struct config file_config; 33 | 34 | static struct scrobbler_config * 35 | load_scrobbler(GKeyFile *fd, const char *grp) 36 | { 37 | char *p; 38 | GError *error; 39 | struct scrobbler_config *scrobbler; 40 | 41 | if (!g_key_file_has_group(fd, grp)) 42 | return NULL; 43 | 44 | scrobbler = g_new(struct scrobbler_config, 1); 45 | 46 | error = NULL; 47 | scrobbler->name = g_strdup(grp); 48 | scrobbler->url = g_key_file_get_string(fd, grp, "url", &error); 49 | if (error != NULL) { 50 | g_critical("Error while reading url from group %s: %s", 51 | grp, error->message); 52 | g_free(scrobbler); 53 | g_error_free(error); 54 | return NULL; 55 | } 56 | 57 | error = NULL; 58 | scrobbler->username = g_key_file_get_string(fd, grp, "username", &error); 59 | if (error != NULL) { 60 | g_critical("Error while reading username from group %s: %s", 61 | grp, error->message); 62 | g_free(scrobbler); 63 | g_error_free(error); 64 | return NULL; 65 | } 66 | scrobbler->password = g_key_file_get_string(fd, grp, "password", &error); 67 | if (error != NULL) { 68 | g_critical("Error while reading password from group %s: %s", 69 | grp, error->message); 70 | g_free(scrobbler); 71 | g_error_free(error); 72 | return NULL; 73 | } 74 | /* Parse password */ 75 | if (strncmp(scrobbler->password, "md5:", 4) == 0) { 76 | p = g_strdup(scrobbler->password + 4); 77 | g_free(scrobbler->password); 78 | scrobbler->password = p; 79 | } 80 | else { 81 | p = g_compute_checksum_for_string(G_CHECKSUM_MD5, scrobbler->password, -1); 82 | g_free(scrobbler->password); 83 | scrobbler->password = p; 84 | } 85 | 86 | scrobbler->journal = g_key_file_get_string(fd, grp, "journal", NULL); 87 | return scrobbler; 88 | } 89 | 90 | static void 91 | scrobbler_config_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) 92 | { 93 | struct scrobbler_config *scrobbler = data; 94 | 95 | g_free(scrobbler->name); 96 | g_free(scrobbler->url); 97 | g_free(scrobbler->username); 98 | g_free(scrobbler->password); 99 | g_free(scrobbler->journal); 100 | g_free(scrobbler); 101 | } 102 | 103 | int 104 | file_load(GKeyFile *fd) 105 | { 106 | int s = 0; 107 | GError *error; 108 | struct scrobbler_config *scrobbler; 109 | 110 | memset(&file_config, 0, sizeof(struct config)); 111 | file_config.journal_interval = -1; 112 | 113 | error = NULL; 114 | if (!load_string(fd, MPDCRON_MODULE, "proxy", false, &file_config.proxy, &error)) { 115 | g_critical("Failed to load "MPDCRON_MODULE".proxy: %s", error->message); 116 | g_error_free(error); 117 | return -1; 118 | } 119 | if (!load_integer(fd, MPDCRON_MODULE, "journal_interval", false, &file_config.journal_interval, &error)) { 120 | g_critical("Failed to load "MPDCRON_MODULE".journal_interval: %s", error->message); 121 | g_error_free(error); 122 | return -1; 123 | } 124 | else if (file_config.journal_interval <= 0) 125 | file_config.journal_interval = 60; 126 | 127 | if ((scrobbler = load_scrobbler(fd, "libre.fm")) != NULL) { 128 | file_config.scrobblers = g_slist_prepend(file_config.scrobblers, scrobbler); 129 | ++s; 130 | } 131 | if ((scrobbler = load_scrobbler(fd, "last.fm")) != NULL) { 132 | file_config.scrobblers = g_slist_prepend(file_config.scrobblers, scrobbler); 133 | ++s; 134 | } 135 | 136 | if (s == 0) { 137 | g_critical("Neither last.fm nor libre.fm group defined"); 138 | return -1; 139 | } 140 | 141 | if (file_config.proxy == NULL && g_getenv("http_proxy")) 142 | file_config.proxy = g_strdup(g_getenv("http_proxy")); 143 | 144 | return 0; 145 | } 146 | 147 | void 148 | file_cleanup(void) 149 | { 150 | g_free(file_config.proxy); 151 | g_slist_foreach(file_config.scrobblers, scrobbler_config_free_callback, NULL); 152 | g_slist_free(file_config.scrobblers); 153 | } 154 | -------------------------------------------------------------------------------- /src/gmodule/stats/walrus-main.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009 Ali Polatel 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include "walrus-defs.h" 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | 29 | static char *dbpath = NULL; 30 | static int keepgoing = 0; 31 | 32 | static GOptionEntry options[] = { 33 | {"dbpath", 'd', 0, G_OPTION_ARG_FILENAME, &dbpath, "Path to the database", NULL}, 34 | {"keep-going", 'k', 0, G_OPTION_ARG_NONE, &keepgoing, "Keep going in case of database errors", NULL}, 35 | { NULL, 0, 0, 0, NULL, NULL, NULL }, 36 | }; 37 | 38 | static bool 39 | run_update(int kg, const char *path) 40 | { 41 | int count; 42 | const char *hostname; 43 | const char *port; 44 | const char *password; 45 | GError *error; 46 | struct mpd_connection *conn; 47 | struct mpd_entity *entity; 48 | const struct mpd_song *song; 49 | 50 | if ((hostname = g_getenv(ENV_MPD_HOST)) == NULL) 51 | hostname = "localhost"; 52 | if ((port = g_getenv(ENV_MPD_PORT)) == NULL) 53 | port = "6600"; 54 | password = g_getenv(ENV_MPD_PASSWORD); 55 | 56 | if ((conn = mpd_connection_new(hostname, atoi(port), 0)) == NULL) { 57 | g_printerr("Error creating mpd connection: out of memory\n"); 58 | return false; 59 | } 60 | 61 | if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS) { 62 | g_printerr("Failed to connect to Mpd: %s\n", 63 | mpd_connection_get_error_message(conn)); 64 | mpd_connection_free(conn); 65 | return false; 66 | } 67 | 68 | if (password != NULL) { 69 | if (!mpd_run_password(conn, password)) { 70 | g_printerr("Authentication failed: %s\n", 71 | mpd_connection_get_error_message(conn)); 72 | mpd_connection_free(conn); 73 | return false; 74 | } 75 | } 76 | 77 | if (!mpd_send_list_all_meta(conn, path)) { 78 | g_printerr("Failed to list Mpd database: %s\n", 79 | mpd_connection_get_error_message(conn)); 80 | mpd_connection_free(conn); 81 | return false; 82 | } 83 | 84 | count = 0; 85 | while ((entity = mpd_recv_entity(conn)) != NULL) { 86 | if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) { 87 | song = mpd_entity_get_song(entity); 88 | error = NULL; 89 | if (!db_process(song, false, -1, &error)) { 90 | g_printerr("Failed to process song %s: %s\n", 91 | mpd_song_get_uri(song), 92 | error->message); 93 | g_error_free(error); 94 | if (kg) 95 | continue; 96 | return false; 97 | } 98 | else if (error != NULL) { 99 | /* Song doesn't have required tags */ 100 | g_printerr("Skipped processing song %s: %s\n", 101 | mpd_song_get_uri(song), 102 | error->message); 103 | g_error_free(error); 104 | } 105 | ++count; 106 | printf("%s\n", mpd_song_get_uri(song)); 107 | } 108 | mpd_entity_free(entity); 109 | } 110 | 111 | mpd_response_finish(conn); 112 | mpd_connection_free(conn); 113 | printf("Successfully processed %d songs\n", count); 114 | return true; 115 | } 116 | 117 | int 118 | main(int argc, char **argv) 119 | { 120 | GOptionContext *ctx; 121 | GError *error; 122 | 123 | ctx = g_option_context_new("[PATH...]"); 124 | g_option_context_add_main_entries(ctx, options, "walrus"); 125 | g_option_context_set_summary(ctx, "walrus-"VERSION GITHEAD" - Create/Update mpdcron-stats database"); 126 | 127 | if (!g_option_context_parse(ctx, &argc, &argv, &error)) { 128 | g_printerr("Option parsing failed: %s\n", error->message); 129 | g_error_free(error); 130 | g_option_context_free(ctx); 131 | return 1; 132 | } 133 | g_option_context_free(ctx); 134 | 135 | if (dbpath == NULL) 136 | dbpath = xload_dbpath(); 137 | 138 | error = NULL; 139 | if (!db_init(dbpath, true, false, &error)) { 140 | g_printerr("Failed to load database `%s': %s\n", dbpath, error->message); 141 | g_error_free(error); 142 | g_free(dbpath); 143 | return 1; 144 | } 145 | g_free(dbpath); 146 | 147 | db_set_sync(false, NULL); 148 | db_start_transaction(NULL); 149 | 150 | if (argc > 1) { 151 | for (int i = 1; i < argc; i++) { 152 | fprintf(stderr, "* Updating %s\n", argv[i]); 153 | if (!run_update(keepgoing, argv[i])) { 154 | db_close(); 155 | return 1; 156 | } 157 | } 158 | } 159 | else { 160 | fprintf(stderr, "* Updating /\n"); 161 | if (!run_update(keepgoing, NULL)) { 162 | db_close(); 163 | return 1; 164 | } 165 | } 166 | 167 | db_end_transaction(NULL); 168 | db_vacuum(NULL); 169 | db_close(); 170 | 171 | return 0; 172 | } 173 | -------------------------------------------------------------------------------- /src/cron-keyfile.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009, 2010 Ali Polatel 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include "cron-defs.h" 21 | 22 | #include 23 | #include 24 | 25 | int 26 | keyfile_load(GKeyFile **cfd_r) 27 | { 28 | char *optstr; 29 | char **events; 30 | GError *error; 31 | 32 | error = NULL; 33 | if (!g_key_file_load_from_file(*cfd_r, conf.conf_path, G_KEY_FILE_NONE, &error)) { 34 | switch (error->code) { 35 | case G_FILE_ERROR_NOENT: 36 | case G_KEY_FILE_ERROR_NOT_FOUND: 37 | g_debug("Configuration file `%s' not found, skipping", 38 | conf.conf_path); 39 | g_error_free(error); 40 | 41 | /* Set defaults */ 42 | conf.killwait = DEFAULT_PID_KILL_WAIT; 43 | conf.loglevel = DEFAULT_LOG_LEVEL; 44 | conf.reconnect = DEFAULT_MPD_RECONNECT; 45 | conf.timeout = DEFAULT_MPD_TIMEOUT; 46 | 47 | return 0; 48 | default: 49 | g_critical("Failed to parse configuration file `%s': %s", 50 | conf.conf_path, error->message); 51 | g_error_free(error); 52 | return -1; 53 | } 54 | } 55 | 56 | /* Get main.pidfile */ 57 | if (conf.pid_path == NULL) 58 | conf.pid_path = g_key_file_get_string(*cfd_r, "main", "pidfile", NULL); 59 | 60 | /* Get main.killwait */ 61 | error = NULL; 62 | conf.killwait = g_key_file_get_integer(*cfd_r, "main", "killwait", &error); 63 | if (error != NULL) { 64 | switch (error->code) { 65 | case G_KEY_FILE_ERROR_INVALID_VALUE: 66 | g_warning("main.killwait not an integer: %s", error->message); 67 | g_error_free(error); 68 | return -1; 69 | default: 70 | g_error_free(error); 71 | conf.killwait = DEFAULT_PID_KILL_WAIT; 72 | break; 73 | } 74 | } 75 | 76 | if (conf.killwait <= 0) { 77 | g_warning("killwait smaller than zero, adjusting to default %d", 78 | DEFAULT_PID_KILL_WAIT); 79 | conf.killwait = DEFAULT_PID_KILL_WAIT; 80 | } 81 | 82 | /* Get main.loglevel */ 83 | error = NULL; 84 | conf.loglevel = g_key_file_get_integer(*cfd_r, "main", "loglevel", &error); 85 | if (error != NULL) { 86 | switch (error->code) { 87 | case G_KEY_FILE_ERROR_INVALID_VALUE: 88 | g_warning("main.loglevel not an integer: %s", error->message); 89 | g_error_free(error); 90 | return -1; 91 | default: 92 | g_error_free(error); 93 | conf.loglevel = DEFAULT_LOG_LEVEL; 94 | break; 95 | } 96 | } 97 | 98 | /* Get mpd.reconnect */ 99 | error = NULL; 100 | conf.reconnect = g_key_file_get_integer(*cfd_r, "mpd", "reconnect", &error); 101 | if (error != NULL) { 102 | switch (error->code) { 103 | case G_KEY_FILE_ERROR_INVALID_VALUE: 104 | g_debug("mpd.reconnect not an integer: %s", error->message); 105 | g_error_free(error); 106 | return -1; 107 | default: 108 | g_error_free(error); 109 | conf.reconnect = DEFAULT_MPD_RECONNECT; 110 | break; 111 | } 112 | } 113 | 114 | if (conf.reconnect <= 0) { 115 | g_warning("reconnect %s zero, adjusting to default %d", 116 | (conf.reconnect == 0) ? "equal to" : "smaller than", 117 | DEFAULT_MPD_RECONNECT); 118 | conf.reconnect = DEFAULT_MPD_RECONNECT; 119 | } 120 | 121 | optstr = g_strdup_printf("%d", conf.reconnect); 122 | g_setenv("MCOPT_RECONNECT", optstr, 1); 123 | g_free(optstr); 124 | 125 | /* Get mpd.timeout */ 126 | error = NULL; 127 | conf.timeout = g_key_file_get_integer(*cfd_r, "mpd", "timeout", &error); 128 | if (error != NULL) { 129 | switch (error->code) { 130 | case G_KEY_FILE_ERROR_INVALID_VALUE: 131 | g_warning("mpd.timeout not an integer: %s", error->message); 132 | g_error_free(error); 133 | return -1; 134 | default: 135 | g_error_free(error); 136 | conf.timeout = DEFAULT_MPD_TIMEOUT; 137 | break; 138 | } 139 | } 140 | 141 | if (conf.timeout < 0) { 142 | g_warning("timeout smaller than zero, adjusting to default %d", 143 | DEFAULT_MPD_TIMEOUT); 144 | conf.timeout = DEFAULT_MPD_TIMEOUT; 145 | } 146 | 147 | optstr = g_strdup_printf("%d", conf.timeout); 148 | g_setenv("MCOPT_TIMEOUT", optstr, 1); 149 | g_free(optstr); 150 | 151 | /* Get mpd.events */ 152 | if ((events = g_key_file_get_string_list(*cfd_r, "mpd", "events", NULL, NULL)) != NULL) { 153 | for (unsigned int i = 0; events[i] != NULL; i++) { 154 | enum mpd_idle parsed = mpd_idle_name_parse(events[i]); 155 | if (parsed == 0) 156 | g_warning("Unrecognized idle event: %s", events[i]); 157 | else 158 | conf.idle |= parsed; 159 | } 160 | g_strfreev(events); 161 | } 162 | 163 | return 0; 164 | } 165 | 166 | #ifdef HAVE_GMODULE 167 | int 168 | keyfile_load_modules(GKeyFile **cfd_r) 169 | { 170 | char **modules; 171 | 172 | g_assert(*cfd_r != NULL); 173 | 174 | /* Load modules */ 175 | if ((modules = g_key_file_get_string_list(*cfd_r, "main", "modules", NULL, NULL)) != NULL) { 176 | for (unsigned int i = 0; modules[i] != NULL; i++) 177 | module_load(modules[i], *cfd_r); 178 | g_strfreev(modules); 179 | } 180 | return 0; 181 | } 182 | 183 | #endif /* HAVE_GMODULE */ 184 | -------------------------------------------------------------------------------- /src/gmodule/stats/tokenizer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2003-2009 The Music Player Daemon Project 3 | * http://www.musicpd.org 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along 16 | * with this program; if not, write to the Free Software Foundation, Inc., 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | */ 19 | 20 | #include "config.h" 21 | #include "tokenizer.h" 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | G_GNUC_CONST 28 | static GQuark 29 | tokenizer_quark(void) 30 | { 31 | return g_quark_from_static_string("tokenizer"); 32 | } 33 | 34 | static inline bool 35 | valid_word_first_char(char ch) 36 | { 37 | return g_ascii_isalpha(ch); 38 | } 39 | 40 | static inline bool 41 | valid_word_char(char ch) 42 | { 43 | return g_ascii_isalnum(ch) || ch == '_'; 44 | } 45 | 46 | char * 47 | tokenizer_next_word(char **input_p, GError **error_r) 48 | { 49 | char *word, *input; 50 | 51 | assert(input_p != NULL); 52 | assert(*input_p != NULL); 53 | 54 | word = input = *input_p; 55 | 56 | if (*input == 0) 57 | return NULL; 58 | 59 | /* check the first character */ 60 | 61 | if (!valid_word_first_char(*input)) { 62 | g_set_error(error_r, tokenizer_quark(), 0, 63 | "Letter expected"); 64 | return NULL; 65 | } 66 | 67 | /* now iterate over the other characters until we find a 68 | whitespace or end-of-string */ 69 | 70 | while (*++input != 0) { 71 | if (g_ascii_isspace(*input)) { 72 | /* a whitespace: the word ends here */ 73 | *input = 0; 74 | /* skip all following spaces, too */ 75 | input = g_strchug(input + 1); 76 | break; 77 | } 78 | 79 | if (!valid_word_char(*input)) { 80 | *input_p = input; 81 | g_set_error(error_r, tokenizer_quark(), 0, 82 | "Invalid word character"); 83 | return NULL; 84 | } 85 | } 86 | 87 | /* end of string: the string is already null-terminated 88 | here */ 89 | 90 | *input_p = input; 91 | return word; 92 | } 93 | 94 | static inline bool 95 | valid_unquoted_char(char ch) 96 | { 97 | return (unsigned char)ch > 0x20 && ch != '"' && ch != '\''; 98 | } 99 | 100 | char * 101 | tokenizer_next_unquoted(char **input_p, GError **error_r) 102 | { 103 | char *word, *input; 104 | 105 | assert(input_p != NULL); 106 | assert(*input_p != NULL); 107 | 108 | word = input = *input_p; 109 | 110 | if (*input == 0) 111 | return NULL; 112 | 113 | /* check the first character */ 114 | 115 | if (!valid_unquoted_char(*input)) { 116 | g_set_error(error_r, tokenizer_quark(), 0, 117 | "Invalid unquoted character"); 118 | return NULL; 119 | } 120 | 121 | /* now iterate over the other characters until we find a 122 | whitespace or end-of-string */ 123 | 124 | while (*++input != 0) { 125 | if (g_ascii_isspace(*input)) { 126 | /* a whitespace: the word ends here */ 127 | *input = 0; 128 | /* skip all following spaces, too */ 129 | input = g_strchug(input + 1); 130 | break; 131 | } 132 | 133 | if (!valid_unquoted_char(*input)) { 134 | *input_p = input; 135 | g_set_error(error_r, tokenizer_quark(), 0, 136 | "Invalid unquoted character"); 137 | return NULL; 138 | } 139 | } 140 | 141 | /* end of string: the string is already null-terminated 142 | here */ 143 | 144 | *input_p = input; 145 | return word; 146 | } 147 | 148 | char * 149 | tokenizer_next_string(char **input_p, GError **error_r) 150 | { 151 | char *word, *dest, *input; 152 | 153 | assert(input_p != NULL); 154 | assert(*input_p != NULL); 155 | 156 | word = dest = input = *input_p; 157 | 158 | if (*input == 0) 159 | /* end of line */ 160 | return NULL; 161 | 162 | /* check for the opening " */ 163 | 164 | if (*input != '"') { 165 | g_set_error(error_r, tokenizer_quark(), 0, 166 | "'\"' expected"); 167 | return NULL; 168 | } 169 | 170 | ++input; 171 | 172 | /* copy all characters */ 173 | 174 | while (*input != '"') { 175 | if (*input == '\\') 176 | /* the backslash escapes the following 177 | character */ 178 | ++input; 179 | 180 | if (*input == 0) { 181 | /* return input-1 so the caller can see the 182 | difference between "end of line" and 183 | "error" */ 184 | *input_p = input - 1; 185 | g_set_error(error_r, tokenizer_quark(), 0, 186 | "Missing closing '\"'"); 187 | return NULL; 188 | } 189 | 190 | /* copy one character */ 191 | *dest++ = *input++; 192 | } 193 | 194 | /* the following character must be a whitespace (or end of 195 | line) */ 196 | 197 | ++input; 198 | if (*input != 0 && !g_ascii_isspace(*input)) { 199 | *input_p = input; 200 | g_set_error(error_r, tokenizer_quark(), 0, 201 | "Space expected after closing '\"'"); 202 | return NULL; 203 | } 204 | 205 | /* finish the string and return it */ 206 | 207 | *dest = 0; 208 | *input_p = g_strchug(input); 209 | return word; 210 | } 211 | 212 | char * 213 | tokenizer_next_param(char **input_p, GError **error_r) 214 | { 215 | assert(input_p != NULL); 216 | assert(*input_p != NULL); 217 | 218 | if (**input_p == '"') 219 | return tokenizer_next_string(input_p, error_r); 220 | else 221 | return tokenizer_next_unquoted(input_p, error_r); 222 | } 223 | -------------------------------------------------------------------------------- /src/cron-loop.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009, 2010 Ali Polatel 5 | * Based in part upon mpdscribble which is: 6 | * Copyright (C) 2008-2009 The Music Player Daemon Project 7 | * Copyright (C) 2005-2008 Kuno Woudt 8 | * 9 | * This file is part of the mpdcron mpd client. mpdcron is free software; 10 | * you can redistribute it and/or modify it under the terms of the GNU General 11 | * Public License version 2, as published by the Free Software Foundation. 12 | * 13 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 14 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 | * details. 17 | * 18 | * You should have received a copy of the GNU General Public License along with 19 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 20 | * Place, Suite 330, Boston, MA 02111-1307 USA 21 | */ 22 | 23 | #include "cron-defs.h" 24 | 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | 31 | static guint idle_sid, reconnect_sid; 32 | static const unsigned *version = NULL; 33 | static struct mpd_connection *conn = NULL; 34 | 35 | static void loop_schedule_reconnect(void); 36 | static void loop_schedule_idle(void); 37 | 38 | static void 39 | loop_failure(void) 40 | { 41 | char *msg; 42 | 43 | g_assert(conn != NULL); 44 | msg = g_strescape(mpd_connection_get_error_message(conn), NULL); 45 | g_warning("Mpd error: %s", msg); 46 | g_free(msg); 47 | mpd_connection_free(conn); 48 | conn = NULL; 49 | } 50 | 51 | static gboolean 52 | loop_reconnect(G_GNUC_UNUSED gpointer data) 53 | { 54 | g_message("Connecting to `%s' on port %s with timeout %d", 55 | conf.hostname, conf.port, conf.timeout); 56 | if ((conn = mpd_connection_new(conf.hostname, atoi(conf.port), conf.timeout)) == NULL) { 57 | g_critical("Error creating mpd connection: out of memory"); 58 | exit(EXIT_FAILURE); 59 | } 60 | if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS) { 61 | loop_failure(); 62 | return TRUE; 63 | } 64 | 65 | if (conf.password != NULL) { 66 | g_message("Sending password"); 67 | if (!mpd_run_password(conn, conf.password)) { 68 | g_critical("Authentication failed: %s", 69 | mpd_connection_get_error_message(conn)); 70 | mpd_connection_free(conn); 71 | conn = NULL; 72 | exit(EXIT_FAILURE); 73 | } 74 | } 75 | 76 | if ((version = mpd_connection_get_server_version(conn)) != NULL) { 77 | g_message("Connected to Mpd server, running version %d.%d.%d", 78 | version[0], version[1], version[2]); 79 | if (mpd_connection_cmp_server_version(conn, 0, 14, 0) < 0) 80 | g_warning("Mpd version 0.14.0 is required for idle command"); 81 | } 82 | else 83 | g_message("Connected to Mpd server, running version unknown"); 84 | 85 | loop_schedule_idle(); 86 | reconnect_sid = 0; 87 | return FALSE; 88 | } 89 | 90 | static gboolean 91 | loop_idle(G_GNUC_UNUSED GIOChannel *source, 92 | G_GNUC_UNUSED GIOCondition condition, 93 | G_GNUC_UNUSED gpointer data) 94 | { 95 | unsigned j; 96 | const char *name; 97 | enum mpd_idle i, myidle; 98 | 99 | g_assert(idle_sid != 0); 100 | g_assert(conn != NULL); 101 | 102 | idle_sid = 0; 103 | myidle = mpd_recv_idle(conn, false); 104 | if (!mpd_response_finish(conn)) { 105 | /* Check whether idle command is supported */ 106 | if (mpd_connection_get_error(conn) == MPD_ERROR_SERVER && 107 | mpd_connection_get_server_error(conn) == MPD_SERVER_ERROR_UNKNOWN_CMD) { 108 | g_critical("Idle command not supported by Mpd"); 109 | mpd_connection_free(conn); 110 | conn = NULL; 111 | exit(EXIT_FAILURE); 112 | } 113 | if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS) { 114 | loop_failure(); 115 | loop_schedule_reconnect(); 116 | return FALSE; 117 | } 118 | loop_schedule_idle(); 119 | return FALSE; 120 | } 121 | 122 | for (j = 0 ;; j++) { 123 | i = 1 << j; 124 | if ((name = mpd_idle_name(i)) == NULL) 125 | break; 126 | if (myidle & i) { 127 | /* Clear the environment */ 128 | env_clearenv(); 129 | /* Run the appropriate event */ 130 | if (event_run(conn, i) < 0) { 131 | loop_failure(); 132 | loop_schedule_reconnect(); 133 | return FALSE; 134 | } 135 | } 136 | } 137 | 138 | loop_schedule_idle(); 139 | return FALSE; 140 | } 141 | 142 | static void 143 | loop_schedule_reconnect(void) 144 | { 145 | g_assert(reconnect_sid == 0); 146 | g_message("Waiting for %d seconds before reconnecting", conf.reconnect); 147 | reconnect_sid = g_timeout_add_seconds(conf.reconnect, loop_reconnect, NULL); 148 | } 149 | 150 | static void 151 | loop_schedule_idle(void) 152 | { 153 | bool ret; 154 | GIOChannel *channel; 155 | 156 | g_assert(idle_sid == 0); 157 | g_assert(conn != NULL); 158 | 159 | g_debug("Sending idle command with mask 0x%x", conf.idle); 160 | ret = (conf.idle == 0) ? mpd_send_idle(conn) : mpd_send_idle_mask(conn, conf.idle); 161 | if (!ret && mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS) { 162 | loop_failure(); 163 | loop_schedule_reconnect(); 164 | return; 165 | } 166 | 167 | /* Add a GLib watch on the libmpdclient socket. */ 168 | channel = g_io_channel_unix_new(mpd_connection_get_fd(conn)); 169 | idle_sid = g_io_add_watch(channel, G_IO_IN, loop_idle, NULL); 170 | g_io_channel_unref(channel); 171 | } 172 | 173 | void 174 | loop_connect(void) 175 | { 176 | if (loop_reconnect(NULL)) 177 | loop_schedule_reconnect(); 178 | } 179 | 180 | void 181 | loop_disconnect(void) 182 | { 183 | if (idle_sid != 0) 184 | g_source_remove(idle_sid); 185 | 186 | if (reconnect_sid != 0) 187 | g_source_remove(reconnect_sid); 188 | 189 | if (conn != NULL) { 190 | mpd_connection_free(conn); 191 | conn = NULL; 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/gmodule/scrobbler/scrobbler-journal.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009, 2010, 2013 Ali Polatel 5 | * Based in part upon mpdscribble which is: 6 | * Copyright (C) 2008-2009 The Music Player Daemon Project 7 | * Copyright (C) 2005-2008 Kuno Woudt 8 | * 9 | * This file is part of the mpdcron mpd client. mpdcron is free software; 10 | * you can redistribute it and/or modify it under the terms of the GNU General 11 | * Public License version 2, as published by the Free Software Foundation. 12 | * 13 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 14 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 | * details. 17 | * 18 | * You should have received a copy of the GNU General Public License along with 19 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 20 | * Place, Suite 330, Boston, MA 02111-1307 USA 21 | */ 22 | 23 | #include "scrobbler-defs.h" 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | 33 | static int journal_file_empty; 34 | 35 | static void 36 | journal_write_string(FILE *file, char field, const char *value) 37 | { 38 | if (value != NULL) 39 | fprintf(file, "%c = %s\n", field, value); 40 | } 41 | 42 | static void 43 | journal_write_record(gpointer data, gpointer user_data) 44 | { 45 | struct record *record = data; 46 | FILE *file = user_data; 47 | 48 | assert(record->source != NULL); 49 | 50 | journal_write_string(file, 'a', record->artist); 51 | journal_write_string(file, 't', record->track); 52 | journal_write_string(file, 'b', record->album); 53 | journal_write_string(file, 'n', record->number); 54 | journal_write_string(file, 'm', record->mbid); 55 | journal_write_string(file, 'i', record->time); 56 | 57 | fprintf(file, 58 | "l = %i\no = %s\n\n", 59 | record->length, record->source); 60 | } 61 | 62 | bool journal_write(const char *path, GQueue *queue) 63 | { 64 | FILE *handle; 65 | 66 | if (g_queue_is_empty(queue) && journal_file_empty) 67 | return false; 68 | 69 | handle = fopen(path, "wb"); 70 | if (!handle) { 71 | g_warning("Failed to save %s: %s\n", 72 | path, g_strerror(errno)); 73 | return false; 74 | } 75 | 76 | g_queue_foreach(queue, journal_write_record, handle); 77 | 78 | fclose(handle); 79 | 80 | return true; 81 | } 82 | 83 | static void journal_commit_record(GQueue *queue, struct record *record) 84 | { 85 | if (record->artist != NULL && record->track != NULL) { 86 | /* append record to the queue; reuse allocated strings */ 87 | 88 | g_queue_push_tail(queue, g_memdup(record, sizeof(*record))); 89 | 90 | journal_file_empty = false; 91 | } else { 92 | /* free and clear the record, it was not used */ 93 | 94 | record_deinit(record); 95 | } 96 | 97 | record_clear(record); 98 | } 99 | 100 | /* g_time_val_from_iso8601() was introduced in GLib 2.12 */ 101 | #if GLIB_CHECK_VERSION(2,12,0) 102 | 103 | /** 104 | * Imports an old (protocol v1.2) timestamp, format "%Y-%m-%d 105 | * %H:%M:%S". 106 | */ 107 | static char * 108 | import_old_timestamp(const char *p) 109 | { 110 | char *q; 111 | bool success; 112 | GTimeVal time_val; 113 | 114 | if (strlen(p) <= 10 || p[10] != ' ') 115 | return NULL; 116 | 117 | g_debug("Importing time stamp '%s'", p); 118 | 119 | /* replace a space with 'T', as expected by 120 | g_time_val_from_iso8601() */ 121 | q = g_strdup(p); 122 | q[10] = 'T'; 123 | 124 | success = g_time_val_from_iso8601(q, &time_val); 125 | g_free(q); 126 | if (!success) { 127 | g_debug("Import of '%s' failed", p); 128 | return NULL; 129 | } 130 | 131 | g_debug("'%s' -> %ld", p, time_val.tv_sec); 132 | return g_strdup_printf("%ld", time_val.tv_sec); 133 | } 134 | 135 | #endif 136 | 137 | /** 138 | * Parses the time stamp. If needed, converts the time stamp, and 139 | * returns an allocated string. 140 | */ 141 | static char * 142 | parse_timestamp(const char *p) 143 | { 144 | #if GLIB_CHECK_VERSION(2,12,0) 145 | char *ret = import_old_timestamp(p); 146 | if (ret != NULL) 147 | return ret; 148 | #endif 149 | 150 | return g_strdup(p); 151 | } 152 | 153 | void journal_read(const char *path, GQueue *queue) 154 | { 155 | FILE *file; 156 | char line[1024]; 157 | struct record record; 158 | 159 | journal_file_empty = true; 160 | 161 | file = fopen(path, "r"); 162 | if (file == NULL) { 163 | if (errno != ENOENT) 164 | /* ENOENT is ignored silently, because the 165 | * user might be starting mpdcron for the 166 | * first time */ 167 | g_warning("Failed to load %s: %s", 168 | path, g_strerror(errno)); 169 | return; 170 | } 171 | 172 | record_clear(&record); 173 | 174 | while (fgets(line, sizeof(line), file) != NULL) { 175 | char *key, *value; 176 | 177 | key = g_strchug(line); 178 | if (*key == 0 || *key == '#') 179 | continue; 180 | 181 | value = strchr(key, '='); 182 | if (value == NULL || value == key) 183 | continue; 184 | 185 | *value++ = 0; 186 | 187 | key = g_strchomp(key); 188 | value = g_strstrip(value); 189 | 190 | if (!strcmp("a", key)) { 191 | journal_commit_record(queue, &record); 192 | record.artist = g_strdup(value); 193 | } else if (!strcmp("t", key)) 194 | record.track = g_strdup(value); 195 | else if (!strcmp("b", key)) 196 | record.album = g_strdup(value); 197 | else if (!strcmp("n", key)) 198 | record.number = g_strdup(value); 199 | else if (!strcmp("m", key)) 200 | record.mbid = g_strdup(value); 201 | else if (!strcmp("i", key)) 202 | record.time = parse_timestamp(value); 203 | else if (!strcmp("l", key)) 204 | record.length = atoi(value); 205 | else if (strcmp("o", key) == 0 && value[0] == 'R') 206 | record.source = "R"; 207 | } 208 | 209 | fclose(file); 210 | 211 | journal_commit_record(queue, &record); 212 | } 213 | -------------------------------------------------------------------------------- /src/gmodule/stats/eugene-list.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009, 2010 Ali Polatel 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include "eugene-defs.h" 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | static int 28 | list_artist(struct mpdcron_connection *conn, const char *expr) 29 | { 30 | GSList *values, *walk; 31 | 32 | g_assert(expr != NULL); 33 | 34 | values = NULL; 35 | if (!mpdcron_list_artist_expr(conn, expr, &values)) { 36 | eulog(LOG_ERR, "Failed to list artist: %s", 37 | conn->error->message); 38 | return 1; 39 | } 40 | 41 | for (walk = values; walk != NULL; walk = g_slist_next(walk)) { 42 | struct mpdcron_entity *e = walk->data; 43 | printf("%d: %s\n", e->id, e->name); 44 | g_free(e->name); 45 | g_free(e); 46 | } 47 | g_slist_free(values); 48 | return 0; 49 | } 50 | 51 | static int 52 | list_album(struct mpdcron_connection *conn, const char *expr) 53 | { 54 | GSList *values, *walk; 55 | 56 | g_assert(expr != NULL); 57 | 58 | values = NULL; 59 | if (!mpdcron_list_album_expr(conn, expr, &values)) { 60 | eulog(LOG_ERR, "Failed to list album: %s", 61 | conn->error->message); 62 | return 1; 63 | } 64 | 65 | for (walk = values; walk != NULL; walk = g_slist_next(walk)) { 66 | struct mpdcron_entity *e = walk->data; 67 | printf("%d: %s\n", e->id, e->name); 68 | g_free(e->name); 69 | g_free(e->artist); /* TODO: We don't use artist information. */ 70 | g_free(e); 71 | } 72 | g_slist_free(values); 73 | return 0; 74 | } 75 | 76 | static int 77 | list_genre(struct mpdcron_connection *conn, const char *expr) 78 | { 79 | GSList *values, *walk; 80 | 81 | g_assert(expr != NULL); 82 | 83 | values = NULL; 84 | if (!mpdcron_list_genre_expr(conn, expr, &values)) { 85 | eulog(LOG_ERR, "Failed to list genre: %s", 86 | conn->error->message); 87 | return 1; 88 | } 89 | 90 | for (walk = values; walk != NULL; walk = g_slist_next(walk)) { 91 | struct mpdcron_entity *e = walk->data; 92 | printf("%d: %s\n", e->id, e->name); 93 | g_free(e->name); 94 | g_free(e); 95 | } 96 | g_slist_free(values); 97 | return 0; 98 | } 99 | 100 | static int 101 | list_song(struct mpdcron_connection *conn, const char *expr) 102 | { 103 | GSList *values, *walk; 104 | 105 | g_assert(expr != NULL); 106 | 107 | values = NULL; 108 | if (!mpdcron_list_expr(conn, expr, &values)) { 109 | eulog(LOG_ERR, "Failed to list song: %s", 110 | conn->error->message); 111 | return 1; 112 | } 113 | 114 | for (walk = values; walk != NULL; walk = g_slist_next(walk)) { 115 | struct mpdcron_song *e = walk->data; 116 | printf("%d: %s\n", e->id, e->uri); 117 | g_free(e->uri); 118 | g_free(e); 119 | } 120 | g_slist_free(values); 121 | return 0; 122 | } 123 | 124 | static int 125 | cmd_list_internal(const char *expr, bool artist, bool album, 126 | bool genre) 127 | { 128 | int port, ret; 129 | const char *hostname, *password; 130 | struct mpdcron_connection *conn; 131 | 132 | if ((artist && album && genre) || 133 | (artist && album) || 134 | (artist && genre) || 135 | (album && genre)) { 136 | g_printerr("--artist, --album and --genre options are mutually exclusive\n"); 137 | return 1; 138 | } 139 | 140 | hostname = g_getenv(ENV_MPDCRON_HOST) 141 | ? g_getenv(ENV_MPDCRON_HOST) 142 | : DEFAULT_HOSTNAME; 143 | port = g_getenv(ENV_MPDCRON_PORT) 144 | ? atoi(g_getenv(ENV_MPDCRON_PORT)) 145 | : DEFAULT_PORT; 146 | password = g_getenv(ENV_MPDCRON_PASSWORD); 147 | 148 | conn = mpdcron_connection_new(hostname, port); 149 | if (conn->error != NULL) { 150 | eulog(LOG_ERR, "Failed to connect: %s", conn->error->message); 151 | mpdcron_connection_free(conn); 152 | return 1; 153 | } 154 | 155 | if (password != NULL) { 156 | if (!mpdcron_password(conn, password)) { 157 | eulog(LOG_ERR, "Authentication failed: %s", conn->error->message); 158 | mpdcron_connection_free(conn); 159 | return 1; 160 | } 161 | } 162 | 163 | if (artist) 164 | ret = list_artist(conn, expr); 165 | else if (album) 166 | ret = list_album(conn, expr); 167 | else if (genre) 168 | ret = list_genre(conn, expr); 169 | else 170 | ret = list_song(conn, expr); 171 | mpdcron_connection_free(conn); 172 | return ret; 173 | } 174 | 175 | int 176 | cmd_list(int argc, char **argv) 177 | { 178 | int opta = 0, optA = 0, optg = 0, ret; 179 | GError *error = NULL; 180 | GOptionEntry options[] = { 181 | {"artist", 'a', 0, G_OPTION_ARG_NONE, &opta, 182 | "List artists instead of songs", NULL}, 183 | {"album", 'A', 0, G_OPTION_ARG_NONE, &optA, 184 | "List albums instead of songs", NULL}, 185 | {"genre", 'g', 0, G_OPTION_ARG_NONE, &optg, 186 | "List genres instead of songs", NULL}, 187 | { NULL, 0, 0, 0, NULL, NULL, NULL }, 188 | }; 189 | GOptionContext *ctx; 190 | 191 | ctx = g_option_context_new("EXPRESSION"); 192 | g_option_context_add_main_entries(ctx, options, "eugene-list"); 193 | g_option_context_set_summary(ctx, "eugene-list-"VERSION GITHEAD" - List song/artist/album/genre"); 194 | g_option_context_set_description(ctx, "" 195 | "For more information about the expression syntax, see:\n" 196 | "http://www.sqlite.org/lang_expr.html"); 197 | if (!g_option_context_parse(ctx, &argc, &argv, &error)) { 198 | g_printerr("Option parsing failed: %s\n", error->message); 199 | g_error_free(error); 200 | g_option_context_free(ctx); 201 | return 1; 202 | } 203 | g_option_context_free(ctx); 204 | 205 | if (argc > 1) 206 | ret = cmd_list_internal(argv[1], opta, optA, optg); 207 | else { 208 | g_printerr("No expression given\n"); 209 | ret = 1; 210 | } 211 | return ret; 212 | } 213 | -------------------------------------------------------------------------------- /src/gmodule/stats/stats-file.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009, 2010 Ali Polatel 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111false307 USA 18 | */ 19 | 20 | #include "stats-defs.h" 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | #include "../utils.h" 28 | 29 | struct config globalconf; 30 | 31 | bool 32 | file_load(const struct mpdcron_config *conf, GKeyFile *fd) 33 | { 34 | char **values; 35 | GError *error; 36 | 37 | memset(&globalconf, 0, sizeof(struct config)); 38 | 39 | /* Load database path */ 40 | error = NULL; 41 | if (!load_string(fd, MPDCRON_MODULE, "dbpath", false, &globalconf.dbpath, &error)) { 42 | g_critical("%s", error->message); 43 | g_error_free(error); 44 | return false; 45 | } 46 | if (globalconf.dbpath == NULL) 47 | globalconf.dbpath = g_build_filename(conf->home_path, "stats.db", NULL); 48 | 49 | /* Load port */ 50 | error = NULL; 51 | globalconf.port = -1; 52 | if (!load_integer(fd, MPDCRON_MODULE, "port", false, &globalconf.port, &error)) { 53 | g_critical("%s", error->message); 54 | g_error_free(error); 55 | return false; 56 | } 57 | if (globalconf.port <= 0) 58 | globalconf.port = DEFAULT_PORT; 59 | 60 | /* Load max connections */ 61 | error = NULL; 62 | globalconf.max_connections = -1; 63 | if (!load_integer(fd, MPDCRON_MODULE, "max_connections", false, &globalconf.max_connections, &error)) { 64 | g_critical("%s", error->message); 65 | g_error_free(error); 66 | return false; 67 | } 68 | if (globalconf.max_connections <= 0) 69 | globalconf.max_connections = DEFAULT_MAX_CONNECTIONS; 70 | 71 | /* Load default permissions */ 72 | error = NULL; 73 | values = g_key_file_get_string_list(fd, MPDCRON_MODULE, "default_permissions", 74 | NULL, &error); 75 | if (error != NULL) { 76 | switch (error->code) { 77 | case G_KEY_FILE_ERROR_GROUP_NOT_FOUND: 78 | case G_KEY_FILE_ERROR_KEY_NOT_FOUND: 79 | g_error_free(error); 80 | break; 81 | default: 82 | g_critical("Failed to load " 83 | MPDCRON_MODULE".default_permissions: %s", 84 | error->message); 85 | g_error_free(error); 86 | g_free(globalconf.dbpath); 87 | return false; 88 | } 89 | } 90 | if (values != NULL) { 91 | for (unsigned int i = 0; values[i] != NULL; i++) { 92 | if (strncmp(values[i], "select", 7) == 0) 93 | globalconf.default_permissions |= PERMISSION_SELECT; 94 | else if (strncmp(values[i], "update", 7) == 0) 95 | globalconf.default_permissions |= PERMISSION_UPDATE; 96 | else if (strncmp(values[i], "none", 5) == 0) 97 | globalconf.default_permissions = 0; 98 | else 99 | g_warning("Invalid value in " 100 | MPDCRON_MODULE".default_permissions `%s'", 101 | values[i]); 102 | } 103 | g_strfreev(values); 104 | } 105 | else 106 | globalconf.default_permissions = PERMISSION_SELECT | PERMISSION_UPDATE; 107 | 108 | /* Load passwords */ 109 | error = NULL; 110 | values = g_key_file_get_string_list(fd, MPDCRON_MODULE, "passwords", 111 | NULL, &error); 112 | if (error != NULL) { 113 | switch (error->code) { 114 | case G_KEY_FILE_ERROR_GROUP_NOT_FOUND: 115 | case G_KEY_FILE_ERROR_KEY_NOT_FOUND: 116 | g_error_free(error); 117 | break; 118 | default: 119 | g_critical("Failed to load " 120 | MPDCRON_MODULE".passwords: %s", 121 | error->message); 122 | g_error_free(error); 123 | g_free(globalconf.dbpath); 124 | return false; 125 | } 126 | } 127 | 128 | globalconf.passwords = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); 129 | if (values != NULL) { 130 | char **split; 131 | 132 | for (unsigned int i = 0; values[i] != NULL; i++) { 133 | split = g_strsplit(values[i], "@", 2); 134 | if (g_strv_length(split) != 2) { 135 | g_warning("Invalid value in " 136 | MPDCRON_MODULE".passwords `%s'", 137 | values[i]); 138 | g_strfreev(split); 139 | continue; 140 | } 141 | if (strncmp(split[1], "select", 7) == 0) { 142 | g_free(split[1]); 143 | g_hash_table_insert(globalconf.passwords, split[0], 144 | GINT_TO_POINTER(PERMISSION_SELECT)); 145 | globalconf.default_permissions = globalconf.default_permissions & ~PERMISSION_SELECT; 146 | } 147 | else if (strncmp(split[1], "update", 7) == 0) { 148 | g_free(split[1]); 149 | g_hash_table_insert(globalconf.passwords, split[0], 150 | GINT_TO_POINTER(PERMISSION_UPDATE)); 151 | globalconf.default_permissions = globalconf.default_permissions & ~PERMISSION_UPDATE; 152 | } 153 | else if (strncmp(split[1], "all", 4) == 0) { 154 | g_free(split[1]); 155 | g_hash_table_insert(globalconf.passwords, split[0], 156 | GINT_TO_POINTER(PERMISSION_ALL)); 157 | globalconf.default_permissions = globalconf.default_permissions & ~PERMISSION_ALL; 158 | } 159 | else { 160 | g_warning("Invalid value in " 161 | MPDCRON_MODULE".passwords `%s'", 162 | values[i]); 163 | g_strfreev(split); 164 | } 165 | } 166 | g_strfreev(values); 167 | } 168 | 169 | /* Load addresses */ 170 | error = NULL; 171 | globalconf.addrs = g_key_file_get_string_list(fd, MPDCRON_MODULE, "bind_to_addresses", NULL, &error); 172 | if (error != NULL) { 173 | switch (error->code) { 174 | case G_KEY_FILE_ERROR_GROUP_NOT_FOUND: 175 | case G_KEY_FILE_ERROR_KEY_NOT_FOUND: 176 | g_error_free(error); 177 | break; 178 | default: 179 | g_critical("Failed to load " 180 | MPDCRON_MODULE".bind_to_address: %s", 181 | error->message); 182 | g_error_free(error); 183 | g_free(globalconf.dbpath); 184 | return false; 185 | } 186 | } 187 | if (globalconf.addrs == NULL) { 188 | globalconf.addrs = g_new0(char *, 2); 189 | globalconf.addrs[0] = g_strdup(DEFAULT_HOST); 190 | } 191 | 192 | /* Information about Mpd */ 193 | globalconf.mpd_hostname = g_strdup(conf->hostname); 194 | globalconf.mpd_port = g_strdup(conf->port); 195 | globalconf.mpd_password = g_strdup(conf->password); 196 | 197 | return true; 198 | } 199 | 200 | void 201 | file_cleanup(void) 202 | { 203 | g_free(globalconf.dbpath); 204 | g_free(globalconf.mpd_hostname); 205 | g_free(globalconf.mpd_port); 206 | g_free(globalconf.mpd_password); 207 | g_strfreev(globalconf.addrs); 208 | g_hash_table_destroy(globalconf.passwords); 209 | } 210 | -------------------------------------------------------------------------------- /src/gmodule/stats/stats-module.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009, 2010, 2016 Ali Polatel 5 | * Based in part upon mpdscribble which is: 6 | * Copyright (C) 2008-2009 The Music Player Daemon Project 7 | * Copyright (C) 2005-2008 Kuno Woudt 8 | * 9 | * This file is part of the mpdcron mpd client. mpdcron is free software; 10 | * you can redistribute it and/or modify it under the terms of the GNU General 11 | * Public License version 2, as published by the Free Software Foundation. 12 | * 13 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 14 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 | * details. 17 | * 18 | * You should have received a copy of the GNU General Public License along with 19 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 20 | * Place, Suite 330, Boston, MA 02111-1307 USA 21 | */ 22 | 23 | #include "stats-defs.h" 24 | 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | static unsigned last_id = -1; 31 | static bool was_paused = 0; 32 | static bool is_remote = 0; 33 | static struct mpd_song *prev = NULL; 34 | static GTimer *timer = NULL; 35 | 36 | static bool 37 | played_long_enough(int elapsed, int length) 38 | { 39 | return elapsed > 240 || (length >= 30 && elapsed > length / 2); 40 | } 41 | 42 | static void 43 | song_changed(const struct mpd_song *song) 44 | { 45 | const char *uri; 46 | 47 | g_assert(song != NULL); 48 | 49 | g_timer_start(timer); 50 | 51 | uri = mpd_song_get_uri(song); 52 | is_remote = !!strstr(uri, "://"); 53 | if (is_remote) 54 | g_message("New song detected with URL (%s)", uri); 55 | 56 | g_debug("New song detected (%s - %s), id: %u, pos: %u", 57 | mpd_song_get_tag(song, MPD_TAG_ARTIST, 0), 58 | mpd_song_get_tag(song, MPD_TAG_TITLE, 0), 59 | mpd_song_get_id(song), mpd_song_get_pos(song)); 60 | } 61 | 62 | static void 63 | song_started(const struct mpd_song *song) 64 | { 65 | song_changed(song); 66 | } 67 | 68 | static void 69 | song_ended(const struct mpd_song *song) 70 | { 71 | bool long_enough; 72 | int elapsed, song_duration, percent_played; 73 | GError *error; 74 | 75 | g_assert(song != NULL); 76 | 77 | elapsed = g_timer_elapsed(timer, NULL); 78 | song_duration = mpd_song_get_duration(song); 79 | long_enough = played_long_enough(elapsed, song_duration); 80 | if (song_duration > 0) 81 | percent_played = elapsed * 100 / song_duration; 82 | else 83 | percent_played = 100; 84 | 85 | g_debug("Saving old song (%s - %s), id: %u, pos: %u", 86 | mpd_song_get_tag(song, MPD_TAG_ARTIST, 0), 87 | mpd_song_get_tag(song, MPD_TAG_TITLE, 0), 88 | mpd_song_get_id(song), mpd_song_get_pos(song)); 89 | 90 | error = NULL; 91 | if (!db_process(song, long_enough, percent_played, &error)) { 92 | g_warning("Saving old song failed: %s", error->message); 93 | g_error_free(error); 94 | } 95 | else if (error != NULL) { 96 | g_warning("Skipped saving old song: %s", error->message); 97 | g_error_free(error); 98 | } 99 | } 100 | 101 | static void 102 | song_playing(const struct mpd_song *song, unsigned elapsed) 103 | { 104 | unsigned prev_elapsed = g_timer_elapsed(timer, NULL); 105 | if (prev_elapsed > elapsed) { 106 | g_debug("Repeated song detected"); 107 | song_ended(song); 108 | song_started(song); 109 | } 110 | } 111 | 112 | static void 113 | song_continued(void) 114 | { 115 | g_timer_continue(timer); 116 | } 117 | 118 | static void 119 | song_paused(void) 120 | { 121 | if (!was_paused) 122 | g_timer_stop(timer); 123 | was_paused = true; 124 | } 125 | 126 | static void 127 | song_stopped(void) 128 | { 129 | last_id = -1; 130 | was_paused = false; 131 | } 132 | 133 | /* Module functions */ 134 | static int 135 | init(const struct mpdcron_config *conf, GKeyFile *fd) 136 | { 137 | GError *error; 138 | g_debug("Initializing"); 139 | 140 | /* Load configuration */ 141 | if (!file_load(conf, fd)) 142 | return MPDCRON_INIT_FAILURE; 143 | 144 | /* Initialize database */ 145 | error = NULL; 146 | if (!db_init(globalconf.dbpath, true, false, &error)) { 147 | g_critical("Failed to initialize database `%s': %s", 148 | globalconf.dbpath, error->message); 149 | g_error_free(error); 150 | file_cleanup(); 151 | return MPDCRON_INIT_FAILURE; 152 | } 153 | 154 | /* Initialize, bind and start the server */ 155 | server_init(); 156 | for (unsigned int i = 0; globalconf.addrs[i] != NULL; i++) { 157 | if (strncmp(globalconf.addrs[i], "any", 4) == 0) 158 | server_bind(NULL, globalconf.port); 159 | else if (globalconf.addrs[i][0] == '/') 160 | server_bind(globalconf.addrs[i], -1); 161 | else 162 | server_bind(globalconf.addrs[i], globalconf.port); 163 | } 164 | server_start(); 165 | 166 | timer = g_timer_new(); 167 | return MPDCRON_INIT_SUCCESS; 168 | } 169 | 170 | static void 171 | destroy(void) 172 | { 173 | g_message("Exiting"); 174 | if (prev != NULL) 175 | mpd_song_free(prev); 176 | g_timer_destroy(timer); 177 | server_close(); 178 | db_close(); 179 | file_cleanup(); 180 | } 181 | 182 | static int 183 | event_player(G_GNUC_UNUSED const struct mpd_connection *conn, 184 | const struct mpd_song *song, const struct mpd_status *status) 185 | { 186 | enum mpd_state state; 187 | 188 | g_assert(status != NULL); 189 | 190 | state = mpd_status_get_state(status); 191 | 192 | if (state == MPD_STATE_PAUSE) { 193 | song_paused(); 194 | return MPDCRON_EVENT_SUCCESS; 195 | } 196 | else if (state != MPD_STATE_PLAY) 197 | song_stopped(); 198 | 199 | if (was_paused) { 200 | if (song != NULL && mpd_song_get_id(song) == last_id) 201 | song_continued(); 202 | was_paused = false; 203 | } 204 | 205 | /* Submit the previous song */ 206 | if (prev != NULL && 207 | (song == NULL || mpd_song_get_id(prev) != mpd_song_get_id(song) || 208 | (is_remote && 209 | mpd_song_get_tag(song, MPD_TAG_TITLE, 0) != mpd_song_get_tag(prev, MPD_TAG_TITLE, 0)))) 210 | song_ended(prev); 211 | 212 | if (song != NULL) { 213 | if (mpd_song_get_id(song) != last_id || 214 | (is_remote && prev != NULL && 215 | mpd_song_get_tag(song, MPD_TAG_TITLE, 0) != mpd_song_get_tag(prev, MPD_TAG_TITLE, 0))) { 216 | /* New song. */ 217 | song_started(song); 218 | last_id = mpd_song_get_id(song); 219 | } 220 | else { 221 | /* still playing the previous song */ 222 | song_playing(song, mpd_status_get_elapsed_time(status)); 223 | } 224 | } 225 | 226 | if (prev != NULL) { 227 | mpd_song_free(prev); 228 | prev = NULL; 229 | } 230 | if (song != NULL) { 231 | if ((prev = mpd_song_dup(song)) == NULL) { 232 | g_critical("mpd_song_dup failed: out of memory"); 233 | return MPDCRON_EVENT_UNLOAD; 234 | } 235 | } 236 | return MPDCRON_EVENT_SUCCESS; 237 | } 238 | 239 | struct mpdcron_module module = { 240 | .name = "Stats", 241 | .init = init, 242 | .destroy = destroy, 243 | .event_player = event_player, 244 | }; 245 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | dnl vim: set sw=4 sts=4 ts=4 noet ft=config foldmethod=marker foldmarker={{{,}}} : 2 | 3 | dnl {{{ program, version 4 | AC_PREREQ(2.59) 5 | 6 | m4_define([mpdcron_version_major], [0]) 7 | m4_define([mpdcron_version_minor], [3]) 8 | m4_define([mpdcron_version_full], [mpdcron_version_major.mpdcron_version_minor]) 9 | m4_define([mpdcron_version], [mpdcron_version_full]) 10 | 11 | AC_INIT([mpdcron], [mpdcron_version], 12 | [https://github.com/alip/mpdcron/issues], 13 | [mpdcron], [http://alip.github.com/mpdcron/]) 14 | 15 | AC_CONFIG_SRCDIR([src/cron-main.c]) 16 | AC_CONFIG_MACRO_DIR([m4]) 17 | AC_CONFIG_AUX_DIR([build-aux]) 18 | 19 | AM_INIT_AUTOMAKE([std-options foreign]) 20 | m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) 21 | 22 | VERSION_MAJOR=mpdcron_version_major 23 | VERSION_MINOR=mpdcron_version_minor 24 | VERSION_FULL=mpdcron_version_full 25 | VERSION=mpdcron_version 26 | 27 | AC_SUBST([VERSION_MAJOR]) 28 | AC_SUBST([VERSION_MINOR]) 29 | AC_SUBST([VERSION_FULL]) 30 | 31 | dnl {{{ git revision 32 | AC_MSG_CHECKING([for git head]) 33 | if test -d "${GIT_DIR:-${ac_top_srcdir:-./}/.git}" ; then 34 | GITHEAD=`git describe 2>/dev/null` 35 | if test -z ${GITHEAD} ; then 36 | GITHEAD=`git rev-parse --short HEAD` 37 | fi 38 | if test -n "`git diff-index -m --name-only HEAD`" ; then 39 | GITHEAD=${GITHEAD}-dirty 40 | fi 41 | if test -n "${GITHEAD}" ; then 42 | GITHEAD="-${GITHEAD}" 43 | fi 44 | fi 45 | AC_MSG_RESULT([$GITHEAD]) 46 | AC_SUBST([GITHEAD]) 47 | dnl }}} 48 | dnl }}} 49 | 50 | dnl {{{ toolchain checks 51 | AC_PROG_CC 52 | AC_PROG_CC_C99 53 | if test x"$ac_cv_prog_cc_c99" = x"no"; then 54 | AC_MSG_ERROR([mpdcron requires a C compiler that supports ISO C99!]) 55 | fi 56 | AC_PROG_INSTALL 57 | AC_PROG_LIBTOOL 58 | AC_PROG_MAKE_SET 59 | AC_PROG_SED 60 | dnl }}} 61 | 62 | dnl {{{ Make pkg-config work 63 | PKG_PROG_PKG_CONFIG([0.9.0]) 64 | dnl }}} 65 | 66 | dnl {{{ Check for libraries 67 | GLIB_REQUIRED=2.16 68 | GIO_REQUIRED=2.22 69 | LIBDAEMON_REQUIRED=0.12 70 | LIBMPDCLIENT_REQUIRED=2.2 71 | 72 | PKG_CHECK_MODULES([glib], [glib-2.0 >= $GLIB_REQUIRED],, 73 | [AC_MSG_ERROR([mpdcron requires glib-$GLIB_REQUIRED or newer])]) 74 | PKG_CHECK_MODULES([libdaemon], [libdaemon >= $LIBDAEMON_REQUIRED],, 75 | [AC_MSG_ERROR([mpdcron requires libdaemon-$LIBDAEMON_REQUIRED or newer])]) 76 | PKG_CHECK_MODULES([libmpdclient], [libmpdclient >= $LIBMPDCLIENT_REQUIRED],, 77 | [AC_MSG_ERROR([mpdcron requires libmpdclient-$LIBMPDCLIENT_REQUIRED or newer])]) 78 | dnl }}} 79 | 80 | dnl {{{ --enable-gmodule 81 | AC_MSG_CHECKING([whether module support is wanted]) 82 | AC_ARG_ENABLE([gmodule], 83 | [AS_HELP_STRING([--enable-gmodule], 84 | [enable support for C modules (via GModule)])], 85 | WANT_GMODULE="$enableval", 86 | WANT_GMODULE="no") 87 | AC_MSG_RESULT([$WANT_GMODULE]) 88 | AM_CONDITIONAL(HAVE_GMODULE, test x"$WANT_GMODULE" = x"yes") 89 | if test x"$WANT_GMODULE" = x"yes"; then 90 | PKG_CHECK_MODULES([gmodule], [gmodule-2.0 >= $GLIB_REQUIRED],, 91 | [AC_MSG_ERROR([mpdcron requires gmodule-$GLIB_REQUIRED or newer for module support])]) 92 | AC_DEFINE([HAVE_GMODULE], 1, [Define for gmodule support]) 93 | fi 94 | dnl }}} 95 | 96 | dnl {{{ standard modules 97 | ALL_STANDARD_MODULES="notification scrobbler stats" 98 | AC_MSG_CHECKING([which standard modules are wanted]) 99 | AC_ARG_WITH([standard-modules], 100 | [AS_HELP_STRING([--with-standard-modules=foo,bar,... 101 | Build the specified standard modules: 102 | all Build all available standard modules 103 | notification Build the notification module 104 | scrobbler Build the scrobbler module 105 | stats Build the stats module])], 106 | [standard_modules="`echo $with_standard_modules | tr ',' ' '`"], 107 | [standard_modules=""]) 108 | standard_modules=`echo $standard_modules | sed -e "s,^all\$,$ALL_STANDARD_MODULES,"` 109 | AC_MSG_RESULT([$standard_modules]) 110 | STANDARD_MODULES="$standard_modules" 111 | AC_SUBST([STANDARD_MODULES]) 112 | WANT_NOTIFICATION=no 113 | WANT_SCROBBLER=no 114 | WANT_STATS=no 115 | for m in $STANDARD_MODULES ; do 116 | if test x"$m" = x"notification" ; then 117 | if test x"$WANT_GMODULE" != x"yes"; then 118 | AC_MSG_WARN([GModule support is disabled, notification module will not be built]) 119 | else 120 | # Notification requires notify-send 121 | AC_CHECK_PROGS(NOTIFY_SEND, notify-send, ) 122 | if test x"$NOTIFY_SEND" = x; then 123 | AC_MSG_ERROR([notification standard module requires notify-send]) 124 | else 125 | WANT_NOTIFICATION=yes 126 | fi 127 | fi 128 | fi 129 | if test x"$m" = x"scrobbler" ; then 130 | if test x"$WANT_GMODULE" != x"yes"; then 131 | AC_MSG_WARN([GModule support is disabled, scrobbler module will not be built]) 132 | else 133 | # Scrobbler requires curl. 134 | PKG_CHECK_MODULES([libcurl], [libcurl], [WANT_SCROBBLER=yes], 135 | AC_MSG_ERROR([scrobbler standard module requires curl])) 136 | fi 137 | fi 138 | if test x"$m" = x"stats" ; then 139 | if test x"$WANT_GMODULE" != x"yes"; then 140 | AC_MSG_WARN([GModule support is disabled, stats module will not be built]) 141 | else 142 | # Stats module requires gio and sqlite3 143 | PKG_CHECK_MODULES([sqlite], [sqlite3], [WANT_STATS=yes], 144 | AC_MSG_ERROR([stats standard module requires sqlite])) 145 | PKG_CHECK_MODULES([gio_unix], [gio-unix-2.0 >= $GIO_REQUIRED], 146 | [HAVE_GIO_UNIX=yes], 147 | [HAVE_GIO_UNIX=no]) 148 | if test x"$HAVE_GIO_UNIX" = x"no" ; then 149 | PKG_CHECK_MODULES([gio], [gio-2.0 >= $GIO_REQUIRED], [WANT_STATS=yes], 150 | [AC_MSG_ERROR([stats standard module requires gio-$GIO_REQUIRED or newer])]) 151 | AC_DEFINE(HAVE_GIO_UNIX, 0, "Define for gio-unix") 152 | else 153 | AC_DEFINE(HAVE_GIO_UNIX, 1, "Define for gio-unix") 154 | fi 155 | MPDCRON_CFLAGS="$MPDCRON_CFLAGS -D_XOPEN_SOURCE=600" 156 | AC_CHECK_FUNCS(gmtime mktime strftime strptime getenv setenv) 157 | fi 158 | fi 159 | done 160 | AM_CONDITIONAL([WANT_NOTIFICATION], test x"$WANT_NOTIFICATION" = x"yes") 161 | AM_CONDITIONAL([WANT_SCROBBLER], test x"$WANT_SCROBBLER" = x"yes") 162 | AM_CONDITIONAL([WANT_STATS], test x"$WANT_STATS" = x"yes") 163 | AM_CONDITIONAL([HAVE_GIO_UNIX], test x"$HAVE_GIO_UNIX" = x"yes") 164 | dnl }}} 165 | 166 | dnl {{{ Extra CFLAGS 167 | 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" 168 | for flag in $WANTED_CFLAGS ; do 169 | AX_CHECK_COMPILER_FLAGS([$flag], [MPDCRON_CFLAGS="$MPDCRON_CFLAGS $flag"],) 170 | done 171 | AC_SUBST([MPDCRON_CFLAGS]) 172 | dnl }}} 173 | 174 | dnl {{{ Output 175 | AC_CONFIG_HEADERS([config.h]) 176 | AC_CONFIG_FILES([ 177 | Makefile 178 | src/Makefile 179 | src/gmodule/Makefile 180 | src/gmodule/notification/Makefile 181 | src/gmodule/scrobbler/Makefile 182 | src/gmodule/stats/Makefile 183 | src/gmodule/stats/homescrape 184 | data/Makefile 185 | zsh-completion/Makefile 186 | ]) 187 | AC_OUTPUT 188 | dnl }}} 189 | -------------------------------------------------------------------------------- /src/cron-event.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009, 2010, 2013 Ali Polatel 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include "cron-defs.h" 21 | 22 | #include 23 | #include 24 | 25 | static int 26 | event_database(struct mpd_connection *conn) 27 | { 28 | int ret; 29 | const char *name; 30 | struct mpd_stats *stats; 31 | 32 | /* Song database has been updated. 33 | * Send stats command and add the variables to the environment. 34 | */ 35 | name = mpd_idle_name(MPD_IDLE_DATABASE); 36 | 37 | g_debug("Sending stats command to Mpd server"); 38 | if ((stats = mpd_run_stats(conn)) == NULL) 39 | return -1; 40 | 41 | ret = 0; 42 | #ifdef HAVE_GMODULE 43 | ret = module_database_run(conn, stats); 44 | #endif /* HAVE_GMODULE */ 45 | env_stats(stats); 46 | hooker_run_hook(name); 47 | mpd_stats_free(stats); 48 | return ret; 49 | } 50 | 51 | static int 52 | event_stored_playlist(struct mpd_connection *conn) 53 | { 54 | int ret; 55 | const char *name; 56 | 57 | /* A playlist has been updated, modified or deleted. 58 | */ 59 | name = mpd_idle_name(MPD_IDLE_STORED_PLAYLIST); 60 | 61 | ret = 0; 62 | #ifdef HAVE_GMODULE 63 | /* TODO: Send some data to the module. */ 64 | ret = module_stored_playlist_run(conn); 65 | #endif /* HAVE_GMODULE */ 66 | hooker_run_hook(name); 67 | return ret; 68 | } 69 | 70 | static int 71 | event_queue(struct mpd_connection *conn G_GNUC_UNUSED) 72 | { 73 | int ret; 74 | const char *name; 75 | 76 | /* The playlist has been changed. 77 | */ 78 | name = mpd_idle_name(MPD_IDLE_QUEUE); 79 | 80 | ret = 0; 81 | #ifdef HAVE_GMODULE 82 | /* TODO: Send some data to the module. */ 83 | ret = module_queue_run(conn); 84 | #endif /* HAVE_GMODULE */ 85 | hooker_run_hook(name); 86 | return ret; 87 | } 88 | 89 | static int 90 | event_player(struct mpd_connection *conn) 91 | { 92 | int ret; 93 | const char *name; 94 | struct mpd_song *song; 95 | struct mpd_status *status; 96 | 97 | /* The player state has changed. 98 | * Send status & currentsong command and add the variables to the 99 | * environment. 100 | */ 101 | name = mpd_idle_name(MPD_IDLE_PLAYER); 102 | 103 | g_debug("Sending status & currentsong commands to Mpd server"); 104 | if (!mpd_command_list_begin(conn, true) || 105 | !mpd_send_status(conn) || 106 | !mpd_send_current_song(conn) || 107 | !mpd_command_list_end(conn)) 108 | return -1; 109 | 110 | if ((status = mpd_recv_status(conn)) == NULL) 111 | return -1; 112 | 113 | if (mpd_status_get_state(status) == MPD_STATE_PLAY || 114 | mpd_status_get_state(status) == MPD_STATE_PAUSE) { 115 | if (!mpd_response_next(conn)) { 116 | mpd_status_free(status); 117 | return -1; 118 | } 119 | 120 | if ((song = mpd_recv_song(conn)) == NULL) { 121 | mpd_status_free(status); 122 | return -1; 123 | } 124 | } 125 | else 126 | song = NULL; 127 | 128 | if (!mpd_response_finish(conn)) { 129 | if (song) 130 | mpd_song_free(song); 131 | mpd_status_free(status); 132 | return -1; 133 | } 134 | 135 | ret = 0; 136 | #ifdef HAVE_GMODULE 137 | ret = module_player_run(conn, song, status); 138 | #endif /* HAVE_GMODULE */ 139 | env_status_currentsong(song, status); 140 | hooker_run_hook(name); 141 | if (song) 142 | mpd_song_free(song); 143 | mpd_status_free(status); 144 | return ret; 145 | } 146 | 147 | static int 148 | event_mixer(struct mpd_connection *conn) 149 | { 150 | int ret; 151 | const char *name; 152 | struct mpd_status *status; 153 | 154 | /* The volume has been modified. 155 | * Send status command and add the variables to the environment. 156 | */ 157 | name = mpd_idle_name(MPD_IDLE_MIXER); 158 | 159 | g_debug("Sending status command to Mpd server"); 160 | if ((status = mpd_run_status(conn)) == NULL) 161 | return -1; 162 | 163 | ret = 0; 164 | #ifdef HAVE_GMODULE 165 | ret = module_mixer_run(conn, status); 166 | #endif /* HAVE_GMODULE */ 167 | env_status(status); 168 | hooker_run_hook(name); 169 | mpd_status_free(status); 170 | return ret; 171 | } 172 | 173 | static int 174 | event_output(struct mpd_connection *conn) 175 | { 176 | int ret; 177 | const char *name; 178 | 179 | /* Outputs have been modified. 180 | */ 181 | name = mpd_idle_name(MPD_IDLE_OUTPUT); 182 | 183 | ret = 0; 184 | #ifdef HAVE_GMODULE 185 | /* TODO: Send some data to the module. */ 186 | ret = module_output_run(conn); 187 | #endif /* HAVE_GMODULE */ 188 | hooker_run_hook(name); 189 | return ret; 190 | } 191 | 192 | static int 193 | event_options(struct mpd_connection *conn) 194 | { 195 | int ret; 196 | const char *name; 197 | struct mpd_status *status; 198 | 199 | /* One of the options has been modified. 200 | * Send status command and add the variables to the environment. 201 | */ 202 | name = mpd_idle_name(MPD_IDLE_OPTIONS); 203 | 204 | g_debug("Sending status command to Mpd server"); 205 | if ((status = mpd_run_status(conn)) == NULL) 206 | return -1; 207 | 208 | ret = 0; 209 | #ifdef HAVE_GMODULE 210 | ret = module_options_run(conn, status); 211 | #endif /* HAVE_GMODULE */ 212 | env_status(status); 213 | hooker_run_hook(name); 214 | mpd_status_free(status); 215 | return ret; 216 | } 217 | 218 | static int 219 | event_update(struct mpd_connection *conn) 220 | { 221 | int ret; 222 | const char *name; 223 | struct mpd_status *status; 224 | 225 | /* A database update has started or finished. 226 | * Send status command and add the variables to the environment. 227 | */ 228 | name = mpd_idle_name(MPD_IDLE_UPDATE); 229 | 230 | g_debug("Sending status command to Mpd server"); 231 | if ((status = mpd_run_status(conn)) == NULL) 232 | return -1; 233 | 234 | ret = 0; 235 | #ifdef HAVE_GMODULE 236 | ret = module_update_run(conn, status); 237 | #endif /* HAVE_GMODULE */ 238 | env_status(status); 239 | hooker_run_hook(name); 240 | mpd_status_free(status); 241 | return ret; 242 | } 243 | 244 | int 245 | event_run(struct mpd_connection *conn, enum mpd_idle event) 246 | { 247 | switch (event) { 248 | case MPD_IDLE_DATABASE: 249 | return event_database(conn); 250 | case MPD_IDLE_STORED_PLAYLIST: 251 | return event_stored_playlist(conn); 252 | case MPD_IDLE_PLAYER: 253 | return event_player(conn); 254 | case MPD_IDLE_QUEUE: 255 | return event_queue(conn); 256 | case MPD_IDLE_MIXER: 257 | return event_mixer(conn); 258 | case MPD_IDLE_OUTPUT: 259 | return event_output(conn); 260 | case MPD_IDLE_OPTIONS: 261 | return event_options(conn); 262 | case MPD_IDLE_UPDATE: 263 | return event_update(conn); 264 | default: 265 | g_warning("Unknown event 0x%x", event); 266 | return 0; 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/cron-main.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009, 2010 Ali Polatel 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include "cron-defs.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | 36 | GMainLoop *loop = NULL; 37 | static GKeyFile *cfd = NULL; 38 | static int optv, optk; 39 | 40 | static GOptionEntry options[] = { 41 | {"version", 'V', 0, G_OPTION_ARG_NONE, &optv, "Display version", NULL}, 42 | {"kill", 'k', 0, G_OPTION_ARG_NONE, &optk, "Kill daemon", NULL}, 43 | {"no-daemon", 'n', 0, G_OPTION_ARG_NONE, &conf.no_daemon, "Don't detach from console", NULL}, 44 | {NULL, 0, 0, 0, NULL, NULL, NULL}, 45 | }; 46 | 47 | static void 48 | about(void) 49 | { 50 | printf(PACKAGE"-"VERSION GITHEAD "\n"); 51 | } 52 | 53 | static void 54 | internal_cleanup( 55 | #ifndef HAVE_GMODULE 56 | G_GNUC_UNUSED 57 | #endif /* !HAVE_GMODULE */ 58 | bool signaled) 59 | { 60 | #ifdef HAVE_GMODULE 61 | module_close(signaled ? 0 : 1); 62 | #endif /* HAVE_GMODULE */ 63 | conf_free(); 64 | if (cfd != NULL) { 65 | g_key_file_free(cfd); 66 | cfd = NULL; 67 | } 68 | if (loop != NULL) { 69 | g_main_loop_quit(loop); 70 | g_main_loop_unref(loop); 71 | loop = NULL; 72 | } 73 | } 74 | 75 | static void 76 | cleanup(void) 77 | { 78 | internal_cleanup(false); 79 | } 80 | 81 | static void 82 | sig_cleanup(int signum) 83 | { 84 | struct sigaction action; 85 | 86 | internal_cleanup(true); 87 | 88 | sigaction(signum, NULL, &action); 89 | action.sa_handler = SIG_DFL; 90 | sigaction(signum, &action, NULL); 91 | raise(signum); 92 | } 93 | 94 | int 95 | main(int argc, char **argv) 96 | { 97 | int pid, ret; 98 | struct sigaction new_action, old_action; 99 | GOptionContext *ctx; 100 | GError *parse_err = NULL; 101 | 102 | daemon_pid_file_ident = daemon_log_ident = daemon_ident_from_argv0(argv[0]); 103 | daemon_pid_file_proc = conf_pid_file_proc; 104 | if (conf_init() < 0) 105 | return EXIT_FAILURE; 106 | 107 | ctx = g_option_context_new(""); 108 | g_option_context_add_main_entries(ctx, options, PACKAGE); 109 | g_option_context_set_summary(ctx, PACKAGE"-"VERSION GITHEAD" - mpd cron daemon"); 110 | 111 | if (!g_option_context_parse(ctx, &argc, &argv, &parse_err)) { 112 | g_printerr("option parsing failed: %s\n", parse_err->message); 113 | g_option_context_free(ctx); 114 | g_error_free(parse_err); 115 | return EXIT_FAILURE; 116 | } 117 | g_option_context_free(ctx); 118 | 119 | if (optv) { 120 | about(); 121 | cleanup(); 122 | return EXIT_SUCCESS; 123 | } 124 | 125 | #ifdef DAEMON_SET_VERBOSITY_AVAILABLE 126 | if (conf.no_daemon) 127 | daemon_set_verbosity(LOG_DEBUG); 128 | #endif /* DAEMON_SET_VERBOSITY_AVAILABLE */ 129 | 130 | /* Version to environment variable */ 131 | g_setenv("MPDCRON_PACKAGE", PACKAGE, 1); 132 | g_setenv("MPDCRON_VERSION", VERSION, 1); 133 | g_setenv("MPDCRON_GITHEAD", GITHEAD, 1); 134 | 135 | /* Command line options to environment variables */ 136 | if (conf.no_daemon) 137 | g_unsetenv("MCOPT_DAEMONIZE"); 138 | else 139 | g_setenv("MCOPT_DAEMONIZE", "1", 1); 140 | 141 | /* Important! Parse configuration file before killing the daemon 142 | * because the configuration file has a pidfile and killwait option. 143 | */ 144 | cfd = g_key_file_new(); 145 | if (keyfile_load(&cfd) < 0) { 146 | cleanup(); 147 | return EXIT_FAILURE; 148 | } 149 | 150 | if (optk) { 151 | if (daemon_pid_file_kill_wait(SIGINT, conf.killwait) < 0) { 152 | g_warning("Failed to kill daemon: %s", strerror(errno)); 153 | cleanup(); 154 | return EXIT_FAILURE; 155 | } 156 | daemon_pid_file_remove(); 157 | cleanup(); 158 | return EXIT_SUCCESS; 159 | } 160 | 161 | /* Logging */ 162 | g_log_set_default_handler(log_handler, GINT_TO_POINTER(conf.no_daemon ? 5 : conf.loglevel)); 163 | 164 | /* Signal handling */ 165 | new_action.sa_handler = sig_cleanup; 166 | sigemptyset(&new_action.sa_mask); 167 | new_action.sa_flags = 0; 168 | 169 | #define HANDLE_SIGNAL(sig) \ 170 | do { \ 171 | sigaction((sig), NULL, &old_action); \ 172 | if (old_action.sa_handler != SIG_IGN) \ 173 | sigaction((sig), &new_action, NULL); \ 174 | } while (0) 175 | 176 | HANDLE_SIGNAL(SIGABRT); 177 | HANDLE_SIGNAL(SIGSEGV); 178 | HANDLE_SIGNAL(SIGINT); 179 | HANDLE_SIGNAL(SIGTERM); 180 | 181 | #undef HANDLE_SIGNAL 182 | 183 | if (conf.no_daemon) { 184 | /* Create the main loop */ 185 | loop = g_main_loop_new(NULL, FALSE); 186 | 187 | #ifdef HAVE_GMODULE 188 | /* Load modules which may add initial events */ 189 | keyfile_load_modules(&cfd); 190 | #endif /* HAVE_GMODULE */ 191 | g_key_file_free(cfd); 192 | cfd = NULL; 193 | 194 | /* Add default initial events */ 195 | loop_connect(); 196 | 197 | /* Run the main loop */ 198 | g_main_loop_run(loop); 199 | cleanup(); 200 | return EXIT_SUCCESS; 201 | } 202 | 203 | /* Daemonize */ 204 | if ((pid = daemon_pid_file_is_running()) > 0) { 205 | g_critical("Daemon already running on PID %u", pid); 206 | return EXIT_FAILURE; 207 | } 208 | 209 | daemon_retval_init(); 210 | pid = daemon_fork(); 211 | if (pid < 0) { 212 | g_critical("Failed to fork: %s", strerror(errno)); 213 | daemon_retval_done(); 214 | return EXIT_FAILURE; 215 | } 216 | else if (pid != 0) { /* Parent */ 217 | cleanup(); 218 | 219 | if ((ret = daemon_retval_wait(2)) < 0) { 220 | g_critical("Could not receive return value from daemon process: %s", 221 | strerror(errno)); 222 | return 255; 223 | } 224 | 225 | if (ret != 0) 226 | g_critical("Daemon returned %i as return value", ret); 227 | else 228 | g_critical("Daemon returned %i as return value", ret); 229 | return ret; 230 | } 231 | else { /* Daemon */ 232 | if (daemon_close_all(-1) < 0) { 233 | g_critical("Failed to close all file descriptors: %s", 234 | strerror(errno)); 235 | daemon_retval_send(1); 236 | return EXIT_FAILURE; 237 | } 238 | 239 | if (daemon_pid_file_create() < 0) { 240 | g_critical("Failed to create PID file: %s", 241 | strerror(errno)); 242 | daemon_retval_send(2); 243 | return EXIT_FAILURE; 244 | } 245 | 246 | /* Send OK to parent process */ 247 | daemon_retval_send(0); 248 | 249 | /* Create the main loop */ 250 | loop = g_main_loop_new(NULL, FALSE); 251 | 252 | #ifdef HAVE_GMODULE 253 | /* Load modules which may add initial events */ 254 | keyfile_load_modules(&cfd); 255 | #endif /* HAVE_GMODULE */ 256 | g_key_file_free(cfd); 257 | cfd = NULL; 258 | 259 | /* Add default initial events */ 260 | loop_connect(); 261 | 262 | /* Run the main loop */ 263 | g_main_loop_run(loop); 264 | cleanup(); 265 | return EXIT_SUCCESS; 266 | } 267 | return EXIT_SUCCESS; 268 | } 269 | -------------------------------------------------------------------------------- /src/gmodule/scrobbler/scrobbler-module.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009, 2010, 2013, 2016 Ali Polatel 5 | * Based in part upon mpdscribble which is: 6 | * Copyright (C) 2008-2009 The Music Player Daemon Project 7 | * Copyright (C) 2005-2008 Kuno Woudt 8 | * 9 | * This file is part of the mpdcron mpd client. mpdcron is free software; 10 | * you can redistribute it and/or modify it under the terms of the GNU General 11 | * Public License version 2, as published by the Free Software Foundation. 12 | * 13 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 14 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 | * details. 17 | * 18 | * You should have received a copy of the GNU General Public License along with 19 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 20 | * Place, Suite 330, Boston, MA 02111-1307 USA 21 | */ 22 | 23 | #include "scrobbler-defs.h" 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | 35 | #include "../utils.h" 36 | 37 | /* Globals */ 38 | static unsigned last_id = -1; 39 | static bool was_paused = 0; 40 | static bool is_remote = 0; 41 | static struct mpd_song *prev = NULL; 42 | static GTimer *timer = NULL; 43 | static int save_source_id = -1; 44 | 45 | static bool 46 | played_long_enough(int elapsed, int length) 47 | { 48 | /* http://www.lastfm.de/api/submissions "The track must have been 49 | played for a duration of at least 240 seconds or half the track's 50 | total length, whichever comes first. Skipping or pausing the 51 | track is irrelevant as long as the appropriate amount has been 52 | played." 53 | */ 54 | return elapsed > 240 || (length >= 30 && elapsed > length / 2); 55 | } 56 | 57 | static bool 58 | song_repeated(const struct mpd_song *song, int elapsed, int prev_elapsed) 59 | { 60 | return elapsed < 60 && prev_elapsed > elapsed && 61 | played_long_enough(prev_elapsed - elapsed, 62 | mpd_song_get_duration(song)); 63 | } 64 | 65 | static void 66 | song_changed(const struct mpd_song *song) 67 | { 68 | g_assert(song != NULL); 69 | char *artist, *title; 70 | const char *uri = mpd_song_get_uri(song); 71 | 72 | is_remote = !!strstr(uri, "://"); 73 | if (is_remote) 74 | g_message("New song detected with URL (%s)", uri); 75 | 76 | if (!song_check_tags(song, &artist, &title)) { 77 | g_message("New song detected with tags missing (%s)", uri); 78 | g_timer_start(timer); 79 | return; 80 | } 81 | g_timer_start(timer); 82 | 83 | g_debug("New song detected (%s - %s), id: %u, pos: %u", 84 | artist, title, 85 | mpd_song_get_id(song), mpd_song_get_pos(song)); 86 | 87 | as_now_playing(artist, title, 88 | mpd_song_get_tag(song, MPD_TAG_ALBUM, 0), 89 | mpd_song_get_tag(song, MPD_TAG_TRACK, 0), 90 | mpd_song_get_tag(song, MPD_TAG_MUSICBRAINZ_TRACKID, 0), 91 | mpd_song_get_duration(song)); 92 | 93 | g_free(artist); 94 | g_free(title); 95 | } 96 | 97 | static void 98 | song_started(const struct mpd_song *song) 99 | { 100 | song_changed(song); 101 | } 102 | 103 | static void 104 | song_ended(const struct mpd_song *song) 105 | { 106 | int elapsed; 107 | char *artist, *title; 108 | 109 | g_assert(song != NULL); 110 | 111 | elapsed = g_timer_elapsed(timer, NULL); 112 | 113 | if (!song_check_tags(song, &artist, &title)) { 114 | g_message("Song (%s) has missing tags, skipping", 115 | mpd_song_get_uri(song)); 116 | return; 117 | } 118 | else if (!played_long_enough(elapsed, mpd_song_get_duration(song))) { 119 | g_message("Song (%s - %s), id: %u, pos: %u not played long enough, skipping", 120 | artist, title, 121 | mpd_song_get_id(song), mpd_song_get_pos(song)); 122 | g_free(artist); 123 | g_free(title); 124 | return; 125 | } 126 | 127 | g_debug("Submitting old song (%s - %s), id: %u, pos: %u", 128 | artist, title, 129 | mpd_song_get_id(song), mpd_song_get_pos(song)); 130 | 131 | /* FIXME: libmpdclient doesn't have any way to fetch the musicbrainz id. 132 | */ 133 | as_songchange(mpd_song_get_uri(song), 134 | artist, title, 135 | mpd_song_get_tag(song, MPD_TAG_ALBUM, 0), 136 | mpd_song_get_tag(song, MPD_TAG_TRACK, 0), 137 | mpd_song_get_tag(song, MPD_TAG_MUSICBRAINZ_TRACKID, 0), 138 | mpd_song_get_duration(song) > 0 139 | ? mpd_song_get_duration(song) 140 | : g_timer_elapsed(timer, NULL), 141 | NULL); 142 | 143 | g_free(artist); 144 | g_free(title); 145 | } 146 | 147 | static void 148 | song_playing(const struct mpd_song *song, unsigned elapsed) 149 | { 150 | int prev_elapsed = g_timer_elapsed(timer, NULL); 151 | if (song_repeated(song, elapsed, prev_elapsed)) { 152 | g_debug("Repeated song detected"); 153 | song_ended(song); 154 | song_started(song); 155 | } 156 | } 157 | 158 | static void 159 | song_continued(void) 160 | { 161 | g_timer_continue(timer); 162 | } 163 | 164 | static void 165 | song_paused(void) 166 | { 167 | if (!was_paused) 168 | g_timer_stop(timer); 169 | was_paused = true; 170 | } 171 | 172 | static void 173 | song_stopped(void) 174 | { 175 | last_id = -1; 176 | was_paused = false; 177 | } 178 | 179 | /* Module functions */ 180 | static int 181 | init(G_GNUC_UNUSED const struct mpdcron_config *conf, GKeyFile *fd) 182 | { 183 | /* Parse configuration */ 184 | if (file_load(fd) < 0) 185 | return MPDCRON_INIT_FAILURE; 186 | if (http_client_init() < 0) 187 | return MPDCRON_INIT_FAILURE; 188 | as_init(file_config.scrobblers); 189 | 190 | timer = g_timer_new(); 191 | save_source_id = g_timeout_add_seconds(file_config.journal_interval, timer_save_journal, NULL); 192 | 193 | return MPDCRON_INIT_SUCCESS; 194 | } 195 | 196 | static void 197 | destroy(void) 198 | { 199 | g_message("Exiting"); 200 | as_save_cache(); 201 | as_cleanup(); 202 | http_client_finish(); 203 | file_cleanup(); 204 | g_timer_destroy(timer); 205 | g_source_remove(save_source_id); 206 | if (prev != NULL) 207 | mpd_song_free(prev); 208 | } 209 | 210 | static int 211 | event_player(G_GNUC_UNUSED const struct mpd_connection *conn, 212 | const struct mpd_song *song, const struct mpd_status *status) 213 | { 214 | enum mpd_state state; 215 | 216 | state = mpd_status_get_state(status); 217 | g_assert(song != NULL || state != MPD_STATE_PLAY); 218 | 219 | if (state == MPD_STATE_PAUSE) { 220 | song_paused(); 221 | return MPDCRON_EVENT_SUCCESS; 222 | } 223 | else if (state != MPD_STATE_PLAY) 224 | song_stopped(); 225 | 226 | if (was_paused) { 227 | if (song != NULL && mpd_song_get_id(song) == last_id) 228 | song_continued(); 229 | was_paused = false; 230 | } 231 | 232 | /* Submit the previous song */ 233 | if (prev != NULL && 234 | (song == NULL || mpd_song_get_id(prev) != mpd_song_get_id(song) || 235 | (is_remote && 236 | mpd_song_get_tag(song, MPD_TAG_TITLE, 0) != mpd_song_get_tag(prev, MPD_TAG_TITLE, 0)))) 237 | song_ended(prev); 238 | 239 | if (song != NULL) { 240 | if (mpd_song_get_id(song) != last_id || 241 | (is_remote && prev != NULL && 242 | mpd_song_get_tag(song, MPD_TAG_TITLE, 0) != mpd_song_get_tag(prev, MPD_TAG_TITLE, 0))) { 243 | /* New song. */ 244 | song_started(song); 245 | last_id = mpd_song_get_id(song); 246 | } 247 | else { 248 | /* still playing the previous song */ 249 | song_playing(song, mpd_status_get_elapsed_time(status)); 250 | } 251 | } 252 | 253 | if (prev != NULL) { 254 | mpd_song_free(prev); 255 | prev = NULL; 256 | } 257 | if (song != NULL) { 258 | if ((prev = mpd_song_dup(song)) == NULL) { 259 | g_critical("mpd_song_dup failed: out of memory"); 260 | return MPDCRON_EVENT_UNLOAD; 261 | } 262 | } 263 | return MPDCRON_EVENT_SUCCESS; 264 | } 265 | 266 | struct mpdcron_module module = { 267 | .name = "Scrobbler", 268 | .init = init, 269 | .destroy = destroy, 270 | .event_player = event_player, 271 | }; 272 | -------------------------------------------------------------------------------- /src/gmodule/stats/stats-sqlite.h: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009, 2010 Ali Polatel 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #ifndef MPDCRON_GUARD_STATS_SQLITE_H 21 | #define MPDCRON_GUARD_STATS_SQLITE_H 1 22 | 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | struct db_generic_data { 31 | int id; 32 | int play_count; 33 | int love; 34 | int kill; 35 | int rating; 36 | 37 | char *name; 38 | char *artist; 39 | 40 | char **tags; 41 | }; 42 | 43 | struct db_song_data { 44 | int id; /** Id of the song */ 45 | int play_count; /** Play count of the song */ 46 | int love; /** Love count of the song */ 47 | int kill; /** Kill count of the song */ 48 | int rating; /** Rating of the song */ 49 | int karma; /** Karma (auto-rating) of the song */ 50 | 51 | char *uri; /** Uri of the song */ 52 | int duration; /** Duration of the song */ 53 | time_t last_modified; /** Last modified date of the song */ 54 | time_t last_played; /** Last played date of the song */ 55 | char *artist; /** Artist of the song */ 56 | char *album; /** Album of the song */ 57 | char *title; /** Title of the song */ 58 | char *track; /** Track number of the song */ 59 | char *name; /** Name tag of the song */ 60 | char *genre; /** Genre of the song */ 61 | char *date; /** Date tag of the song */ 62 | char *composer; /** Composer of the song */ 63 | char *performer; /** Performer of the song */ 64 | char *disc; /** Disc number tag */ 65 | char *mb_artist_id; /** Musicbrainz artist ID */ 66 | char *mb_album_id; /** Musicbrainz album ID */ 67 | char *mb_track_id; /** Musicbrainz track ID */ 68 | 69 | char **tags; 70 | }; 71 | 72 | enum dback { 73 | ACK_ERROR_DATABASE_OPEN = 50, 74 | ACK_ERROR_DATABASE_CREATE = 51, 75 | ACK_ERROR_DATABASE_VERSION = 52, 76 | ACK_ERROR_DATABASE_AUTH = 53, 77 | ACK_ERROR_DATABASE_INSERT = 54, 78 | ACK_ERROR_DATABASE_SELECT = 55, 79 | ACK_ERROR_DATABASE_UPDATE = 56, 80 | ACK_ERROR_DATABASE_PREPARE = 57, 81 | ACK_ERROR_DATABASE_BIND = 58, 82 | ACK_ERROR_DATABASE_STEP = 59, 83 | ACK_ERROR_DATABASE_RESET = 60, 84 | 85 | ACK_ERROR_INVALID_TAG = 101, 86 | ACK_ERROR_NO_TAGS = 102, 87 | }; 88 | 89 | /** 90 | * Database Interface 91 | */ 92 | void 93 | db_generic_data_free(struct db_generic_data *data); 94 | 95 | void 96 | db_song_data_free(struct db_song_data *song); 97 | 98 | bool 99 | db_initialized(void); 100 | 101 | bool 102 | db_init(const char *path, bool create, bool readonly, GError **error); 103 | 104 | void 105 | db_close(void); 106 | 107 | bool 108 | db_set_authorizer(int (*xAuth)(void *, int, const char *, const char *, const char *,const char *), 109 | void *userdata, GError **error); 110 | 111 | bool 112 | db_run_stmt(unsigned int stmt, GError **error); 113 | 114 | bool 115 | db_start_transaction(GError **error); 116 | 117 | bool 118 | db_end_transaction(GError **error); 119 | 120 | bool 121 | db_rollback_transaction(GError **error); 122 | 123 | bool 124 | db_set_sync(bool on, GError **error); 125 | 126 | bool 127 | db_vacuum(GError **error); 128 | 129 | bool 130 | db_process(const struct mpd_song *song, bool increment, int percent_played, 131 | GError **error); 132 | 133 | bool 134 | db_list_artist_expr(const char *expr, GSList **values, GError **error); 135 | 136 | bool 137 | db_list_album_expr(const char *expr, GSList **values, GError **error); 138 | 139 | bool 140 | db_list_genre_expr(const char *expr, GSList **values, GError **error); 141 | 142 | bool 143 | db_list_song_expr(const char *expr, GSList **values, GError **error); 144 | 145 | bool 146 | db_listinfo_artist_expr(const char *expr, GSList **values, GError **error); 147 | 148 | bool 149 | db_listinfo_album_expr(const char *expr, GSList **values, GError **error); 150 | 151 | bool 152 | db_listinfo_genre_expr(const char *expr, GSList **values, GError **error); 153 | 154 | bool 155 | db_listinfo_song_expr(const char *expr, GSList **values, GError **error); 156 | 157 | bool 158 | db_count_artist_expr(const char *expr, int count, int *changes, GError **error); 159 | 160 | bool 161 | db_count_album_expr(const char *expr, int count, int *changes, GError **error); 162 | 163 | bool 164 | db_count_genre_expr(const char *expr, int count, int *changes, GError **error); 165 | 166 | bool 167 | db_count_song_expr(const char *expr, int count, int *changes, GError **error); 168 | 169 | bool 170 | db_karma_song_expr(const char *expr, int karma, int *changes, GError **error); 171 | 172 | bool 173 | db_love_artist_expr(const char *expr, bool love, int *changes, GError **error); 174 | 175 | bool 176 | db_love_album_expr(const char *expr, bool love, int *changes, GError **error); 177 | 178 | bool 179 | db_love_genre_expr(const char *expr, bool love, int *changes, GError **error); 180 | 181 | bool 182 | db_love_song_expr(const char *expr, bool love, int *changes, GError **error); 183 | 184 | bool 185 | db_kill_artist_expr(const char *expr, bool kkill, int *changes, GError **error); 186 | 187 | bool 188 | db_kill_album_expr(const char *expr, bool kkill, int *changes, GError **error); 189 | 190 | bool 191 | db_kill_genre_expr(const char *expr, bool kkill, int *changes, GError **error); 192 | 193 | bool 194 | db_kill_song_expr(const char *expr, bool kkill, int *changes, GError **error); 195 | 196 | bool 197 | db_rate_artist_expr(const char *expr, int rating, int *changes, GError **error); 198 | 199 | bool 200 | db_rate_album_expr(const char *expr, int rating, int *changes, GError **error); 201 | 202 | bool 203 | db_rate_genre_expr(const char *expr, int rating, int *changes, GError **error); 204 | 205 | bool 206 | db_rate_song_expr(const char *expr, int rating, int *changes, GError **error); 207 | 208 | bool 209 | db_rate_absolute_artist_expr(const char *expr, int rating, int *changes, GError **error); 210 | 211 | bool 212 | db_rate_absolute_album_expr(const char *expr, int rating, int *changes, GError **error); 213 | 214 | bool 215 | db_rate_absolute_genre_expr(const char *expr, int rating, int *changes, GError **error); 216 | 217 | bool 218 | db_rate_absolute_song_expr(const char *expr, int rating, int *changes, GError **error); 219 | 220 | bool 221 | db_add_artist_tag_expr(const char *expr, const char *tag, int *changes, GError **error); 222 | 223 | bool 224 | db_add_album_tag_expr(const char *expr, const char *tag, int *changes, GError **error); 225 | 226 | bool 227 | db_add_genre_tag_expr(const char *expr, const char *tag, int *changes, GError **error); 228 | 229 | bool 230 | db_add_song_tag_expr(const char *expr, const char *tag, int *changes, GError **error); 231 | 232 | bool 233 | db_remove_artist_tag_expr(const char *expr, const char *tag, int *changes, GError **error); 234 | 235 | bool 236 | db_remove_album_tag_expr(const char *expr, const char *tag, int *changes, GError **error); 237 | 238 | bool 239 | db_remove_genre_tag_expr(const char *expr, const char *tag, int *changes, GError **error); 240 | 241 | bool 242 | db_remove_song_tag_expr(const char *expr, const char *tag, int *changes, GError **error); 243 | 244 | bool 245 | db_list_artist_tag_expr(const char *expr, GSList **values, GError **error); 246 | 247 | bool 248 | db_list_album_tag_expr(const char *expr, GSList **values, GError **error); 249 | 250 | bool 251 | db_list_genre_tag_expr(const char *expr, GSList **values, GError **error); 252 | 253 | bool 254 | db_list_song_tag_expr(const char *expr, GSList **values, GError **error); 255 | 256 | #endif /* !MPDCRON_GUARD_STATS_SQLITE_H */ 257 | -------------------------------------------------------------------------------- /src/gmodule/notification/notification-module.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009, 2010 Ali Polatel 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | /* Notes about the module: 21 | * Using libnotify directly doesn't work due to many reasons. 22 | * That's why we spawn notify-send. 23 | */ 24 | 25 | #include "notification-defs.h" 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | #include 35 | 36 | static bool was_paused; 37 | static unsigned last_id = -1; 38 | static GTimer *timer = NULL; 39 | 40 | /* Utility functions */ 41 | static void 42 | song_paused(void) 43 | { 44 | if (!was_paused) 45 | g_timer_stop(timer); 46 | was_paused = true; 47 | } 48 | 49 | static void 50 | song_stopped(void) 51 | { 52 | last_id = -1; 53 | was_paused = false; 54 | } 55 | 56 | static void 57 | song_continued(void) 58 | { 59 | g_timer_continue(timer); 60 | } 61 | 62 | static void 63 | song_changed(const struct mpd_song *song) 64 | { 65 | const char *summary; 66 | char *cpath, *body; 67 | 68 | assert(song != NULL); 69 | g_timer_start(timer); 70 | 71 | cpath = cover_find(mpd_song_get_tag(song, MPD_TAG_ARTIST, 0), 72 | mpd_song_get_tag(song, MPD_TAG_ALBUM, 0)); 73 | if (cpath == NULL) 74 | g_debug("Failed to find cover for album (%s - %s), suffix: %s", 75 | mpd_song_get_tag(song, MPD_TAG_ARTIST, 0), 76 | mpd_song_get_tag(song, MPD_TAG_ALBUM, 0), 77 | file_config.cover_suffix); 78 | 79 | g_debug("Sending notify for song (%s - %s), id: %u, pos: %u", 80 | mpd_song_get_tag(song, MPD_TAG_ARTIST, 0), 81 | mpd_song_get_tag(song, MPD_TAG_TITLE, 0), 82 | mpd_song_get_id(song), mpd_song_get_pos(song)); 83 | 84 | summary = mpd_song_get_tag(song, MPD_TAG_TITLE, 0) 85 | ? mpd_song_get_tag(song, MPD_TAG_TITLE, 0) 86 | : mpd_song_get_uri(song); 87 | body = g_strdup_printf("by %s - %s", 88 | mpd_song_get_tag(song, MPD_TAG_ARTIST, 0) 89 | ? mpd_song_get_tag(song, MPD_TAG_ARTIST, 0) 90 | : "Unknown", 91 | mpd_song_get_tag(song, MPD_TAG_ALBUM, 0) 92 | ? mpd_song_get_tag(song, MPD_TAG_ALBUM, 0) 93 | : "Unknown"); 94 | notify_send(cpath, summary, body); 95 | g_free(body); 96 | g_free(cpath); 97 | } 98 | 99 | static void 100 | song_started(const struct mpd_song *song) 101 | { 102 | song_changed(song); 103 | } 104 | 105 | static void 106 | song_playing(const struct mpd_song *song, unsigned elapsed) 107 | { 108 | unsigned prev_elapsed = g_timer_elapsed(timer, NULL); 109 | if (prev_elapsed > elapsed) { 110 | g_debug("Repeated song detected"); 111 | song_started(song); 112 | } 113 | } 114 | 115 | static int 116 | init(G_GNUC_UNUSED const struct mpdcron_config *conf, GKeyFile *fd) 117 | { 118 | was_paused = false; 119 | last_id = -1; 120 | 121 | /* Parse configuration */ 122 | if (file_load(fd) < 0) 123 | return MPDCRON_INIT_FAILURE; 124 | 125 | timer = g_timer_new(); 126 | g_message("Initialized"); 127 | return MPDCRON_INIT_SUCCESS; 128 | } 129 | 130 | static void 131 | destroy(void) 132 | { 133 | g_message("Exiting"); 134 | file_cleanup(); 135 | g_timer_destroy(timer); 136 | } 137 | 138 | static int 139 | event_database(G_GNUC_UNUSED const struct mpd_connection *conn, 140 | const struct mpd_stats *stats) 141 | { 142 | time_t t; 143 | const char *summary; 144 | char *play_time, *uptime, *db_play_time; 145 | char *body; 146 | 147 | g_assert(stats != NULL); 148 | 149 | if ((file_config.events | MPD_IDLE_DATABASE) == 0) 150 | return MPDCRON_EVENT_SUCCESS; 151 | 152 | play_time = dhms(mpd_stats_get_play_time(stats)); 153 | uptime = dhms(mpd_stats_get_uptime(stats)); 154 | db_play_time = dhms(mpd_stats_get_db_play_time(stats)); 155 | t = mpd_stats_get_db_update_time(stats); 156 | 157 | summary = "Mpd Database has been updated"; 158 | body = g_strdup_printf("Artists: %u\n" 159 | "Albums: %u\n" 160 | "Songs: %u\n" 161 | "\n" 162 | "Play Time: %s\n" 163 | "Uptime: %s\n" 164 | "DB Updated: %s" 165 | "DB Play Time: %s", 166 | mpd_stats_get_number_of_artists(stats), 167 | mpd_stats_get_number_of_albums(stats), 168 | mpd_stats_get_number_of_songs(stats), 169 | play_time, 170 | uptime, 171 | ctime(&t), 172 | db_play_time); 173 | 174 | notify_send(NULL, summary, body); 175 | g_free(play_time); 176 | g_free(uptime); 177 | g_free(db_play_time); 178 | g_free(body); 179 | return MPDCRON_EVENT_SUCCESS; 180 | } 181 | 182 | static int 183 | event_player(G_GNUC_UNUSED const struct mpd_connection *conn, 184 | const struct mpd_song *song, const struct mpd_status *status) 185 | { 186 | enum mpd_state state; 187 | 188 | g_assert(status != NULL); 189 | 190 | if ((file_config.events | MPD_IDLE_PLAYER) == 0) 191 | return MPDCRON_EVENT_SUCCESS; 192 | 193 | state = mpd_status_get_state(status); 194 | assert(song != NULL || state != MPD_STATE_PLAY); 195 | 196 | if (state == MPD_STATE_PAUSE) { 197 | song_paused(); 198 | return MPDCRON_EVENT_SUCCESS; 199 | } 200 | else if (state != MPD_STATE_PLAY) { 201 | song_stopped(); 202 | return MPDCRON_EVENT_SUCCESS; 203 | } 204 | 205 | if (was_paused) { 206 | if (song != NULL && mpd_song_get_id(song) == last_id) 207 | song_continued(); 208 | was_paused = false; 209 | } 210 | 211 | if (song != NULL) { 212 | if (mpd_song_get_id(song) != last_id) { 213 | /* New song */ 214 | song_started(song); 215 | last_id = mpd_song_get_id(song); 216 | } 217 | else { 218 | /* Still playing the previous song */ 219 | song_playing(song, mpd_status_get_elapsed_time(status)); 220 | } 221 | } 222 | 223 | return MPDCRON_EVENT_SUCCESS; 224 | } 225 | 226 | static int 227 | event_mixer(G_GNUC_UNUSED const struct mpd_connection *conn, 228 | const struct mpd_status *status) 229 | { 230 | char *summary; 231 | 232 | g_assert(status != NULL); 233 | 234 | if ((file_config.events | MPD_IDLE_MIXER) == 0) 235 | return MPDCRON_EVENT_SUCCESS; 236 | 237 | summary = g_strdup_printf("Mpd Volume: %d%%", mpd_status_get_volume(status)); 238 | notify_send(NULL, summary, ""); 239 | g_free(summary); 240 | return MPDCRON_EVENT_SUCCESS; 241 | } 242 | 243 | static int 244 | event_options(G_GNUC_UNUSED const struct mpd_connection *conn, 245 | const struct mpd_status *status) 246 | { 247 | char *body; 248 | 249 | g_assert(status != NULL); 250 | 251 | if ((file_config.events | MPD_IDLE_OPTIONS) == 0) 252 | return MPDCRON_EVENT_SUCCESS; 253 | 254 | body = g_strdup_printf("Repeat: %s\n" 255 | "Random: %s\n" 256 | "Single: %s\n" 257 | "Consume: %s\n" 258 | "Crossfade: %u", 259 | mpd_status_get_repeat(status) ? "on" : "off", 260 | mpd_status_get_random(status) ? "on" : "off", 261 | mpd_status_get_single(status) ? "on" : "off", 262 | mpd_status_get_consume(status) ? "on" : "off", 263 | mpd_status_get_crossfade(status)); 264 | notify_send(NULL, "Mpd Options have changed!", body); 265 | g_free(body); 266 | return MPDCRON_EVENT_SUCCESS; 267 | } 268 | 269 | static int 270 | event_update(G_GNUC_UNUSED const struct mpd_connection *conn, 271 | const struct mpd_status *status) 272 | { 273 | char *summary; 274 | 275 | g_assert(status != NULL); 276 | 277 | if ((file_config.events | MPD_IDLE_UPDATE) == 0) 278 | return MPDCRON_EVENT_SUCCESS; 279 | 280 | summary = g_strdup_printf("Mpd Update ID: %u", 281 | mpd_status_get_update_id(status)); 282 | notify_send(NULL, summary, ""); 283 | return MPDCRON_EVENT_SUCCESS; 284 | } 285 | 286 | struct mpdcron_module module = { 287 | .name = "Notification", 288 | .init = init, 289 | .destroy = destroy, 290 | .event_database = event_database, 291 | .event_player = event_player, 292 | .event_mixer = event_mixer, 293 | .event_options = event_options, 294 | .event_update = event_update, 295 | }; 296 | -------------------------------------------------------------------------------- /src/gmodule/stats/eugene-rate.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009, 2010 Ali Polatel 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include "eugene-defs.h" 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | static int 28 | rate_artist(struct mpdcron_connection *conn, const char *expr, 29 | const char *rating) 30 | { 31 | int changes; 32 | char *esc_artist, *myexpr; 33 | struct mpd_song *song; 34 | 35 | if (expr != NULL) { 36 | if (!mpdcron_rate_artist_expr(conn, expr, rating, &changes)) { 37 | eulog(LOG_ERR, "Failed to rate artist: %s", 38 | conn->error->message); 39 | return 1; 40 | } 41 | } 42 | else { 43 | if ((song = load_current_song()) == NULL) 44 | return 1; 45 | else if (mpd_song_get_tag(song, MPD_TAG_ARTIST, 0) == NULL) { 46 | eulog(LOG_ERR, "Current playing song has no artist tag!"); 47 | mpd_song_free(song); 48 | return 1; 49 | } 50 | 51 | esc_artist = quote(mpd_song_get_tag(song, MPD_TAG_ARTIST, 0)); 52 | myexpr = g_strdup_printf("name=%s", esc_artist); 53 | g_free(esc_artist); 54 | mpd_song_free(song); 55 | 56 | if (!mpdcron_rate_artist_expr(conn, myexpr, rating, &changes)) { 57 | eulog(LOG_ERR, "Failed to rate current playing artist: %s", 58 | conn->error->message); 59 | g_free(myexpr); 60 | return 1; 61 | } 62 | g_free(myexpr); 63 | } 64 | printf("Modified %d entries\n", changes); 65 | return 0; 66 | } 67 | 68 | static int 69 | rate_album(struct mpdcron_connection *conn, const char *expr, 70 | const char *rating) 71 | { 72 | int changes; 73 | char *esc_album, *myexpr; 74 | struct mpd_song *song; 75 | 76 | if (expr != NULL) { 77 | if (!mpdcron_rate_album_expr(conn, expr, rating, &changes)) { 78 | eulog(LOG_ERR, "Failed to rate album: %s", 79 | conn->error->message); 80 | return 1; 81 | } 82 | } 83 | else { 84 | if ((song = load_current_song()) == NULL) 85 | return 1; 86 | else if (mpd_song_get_tag(song, MPD_TAG_ALBUM, 0) == NULL) { 87 | eulog(LOG_ERR, "Current playing song has no album tag!"); 88 | mpd_song_free(song); 89 | return 1; 90 | } 91 | 92 | esc_album = quote(mpd_song_get_tag(song, MPD_TAG_ALBUM, 0)); 93 | myexpr = g_strdup_printf("name=%s", esc_album); 94 | g_free(esc_album); 95 | mpd_song_free(song); 96 | 97 | if (!mpdcron_rate_album_expr(conn, myexpr, rating, &changes)) { 98 | eulog(LOG_ERR, "Failed to rate current playing album: %s", 99 | conn->error->message); 100 | g_free(myexpr); 101 | return 1; 102 | } 103 | g_free(myexpr); 104 | } 105 | printf("Modified %d entries\n", changes); 106 | return 0; 107 | } 108 | 109 | static int 110 | rate_genre(struct mpdcron_connection *conn, const char *expr, 111 | const char *rating) 112 | { 113 | int changes; 114 | char *esc_genre, *myexpr; 115 | struct mpd_song *song; 116 | 117 | if (expr != NULL) { 118 | if (!mpdcron_rate_genre_expr(conn, expr, rating, &changes)) { 119 | eulog(LOG_ERR, "Failed to rate genre: %s", 120 | conn->error->message); 121 | return 1; 122 | } 123 | } 124 | else { 125 | if ((song = load_current_song()) == NULL) 126 | return 1; 127 | else if (mpd_song_get_tag(song, MPD_TAG_GENRE, 0) == NULL) { 128 | eulog(LOG_ERR, "Current playing song has no genre tag!"); 129 | mpd_song_free(song); 130 | return 1; 131 | } 132 | 133 | esc_genre = quote(mpd_song_get_tag(song, MPD_TAG_GENRE, 0)); 134 | myexpr = g_strdup_printf("name=%s", esc_genre); 135 | g_free(esc_genre); 136 | mpd_song_free(song); 137 | 138 | if (!mpdcron_rate_genre_expr(conn, myexpr, rating, &changes)) { 139 | eulog(LOG_ERR, "Failed to rate current playing genre: %s", 140 | conn->error->message); 141 | g_free(myexpr); 142 | return 1; 143 | } 144 | g_free(myexpr); 145 | } 146 | printf("Modified %d entries\n", changes); 147 | return 0; 148 | } 149 | 150 | static int 151 | rate_song(struct mpdcron_connection *conn, 152 | const char *expr, const char *rating) 153 | { 154 | int changes; 155 | char *esc_uri, *myexpr; 156 | struct mpd_song *song; 157 | 158 | if (expr != NULL) { 159 | if (!mpdcron_rate_expr(conn, expr, rating, &changes)) { 160 | eulog(LOG_ERR, "Failed to rate song: %s", 161 | conn->error->message); 162 | return 1; 163 | } 164 | } 165 | else { 166 | if ((song = load_current_song()) == NULL) 167 | return 1; 168 | 169 | esc_uri = quote(mpd_song_get_uri(song)); 170 | myexpr = g_strdup_printf("uri=%s", esc_uri); 171 | g_free(esc_uri); 172 | mpd_song_free(song); 173 | 174 | if (!mpdcron_rate_expr(conn, myexpr, rating, &changes)) { 175 | eulog(LOG_ERR, "Failed to rate current playing song: %s", 176 | conn->error->message); 177 | g_free(myexpr); 178 | return 1; 179 | } 180 | g_free(myexpr); 181 | } 182 | printf("Modified %d entries\n", changes); 183 | return 0; 184 | } 185 | 186 | static int 187 | cmd_rate_internal(const char *expr, const char *rating, 188 | bool artist, bool album, bool genre) 189 | { 190 | int port, ret; 191 | const char *hostname, *password; 192 | struct mpdcron_connection *conn; 193 | 194 | if ((artist && album && genre) || 195 | (artist && album) || 196 | (artist && genre) || 197 | (album && genre)) { 198 | g_printerr("--artist, --album and --genre options are mutually exclusive\n"); 199 | return 1; 200 | } 201 | 202 | hostname = g_getenv(ENV_MPDCRON_HOST) 203 | ? g_getenv(ENV_MPDCRON_HOST) 204 | : DEFAULT_HOSTNAME; 205 | port = g_getenv(ENV_MPDCRON_PORT) 206 | ? atoi(g_getenv(ENV_MPDCRON_PORT)) 207 | : DEFAULT_PORT; 208 | password = g_getenv(ENV_MPDCRON_PASSWORD); 209 | 210 | conn = mpdcron_connection_new(hostname, port); 211 | if (conn->error != NULL) { 212 | eulog(LOG_ERR, "Failed to connect: %s", conn->error->message); 213 | mpdcron_connection_free(conn); 214 | return 1; 215 | } 216 | 217 | if (password != NULL) { 218 | if (!mpdcron_password(conn, password)) { 219 | eulog(LOG_ERR, "Authentication failed: %s", conn->error->message); 220 | mpdcron_connection_free(conn); 221 | return 1; 222 | } 223 | } 224 | 225 | if (artist) 226 | ret = rate_artist(conn, expr, rating); 227 | else if (album) 228 | ret = rate_album(conn, expr, rating); 229 | else if (genre) 230 | ret = rate_genre(conn, expr, rating); 231 | else 232 | ret = rate_song(conn, expr, rating); 233 | mpdcron_connection_free(conn); 234 | return ret; 235 | } 236 | 237 | int 238 | cmd_rate(int argc, char **argv) 239 | { 240 | int opta = 0, optA = 0, optg = 0, ret; 241 | GError *error = NULL; 242 | GOptionEntry options[] = { 243 | {"artist", 'a', 0, G_OPTION_ARG_NONE, &opta, 244 | "Rate artists instead of songs", NULL}, 245 | {"album", 'A', 0, G_OPTION_ARG_NONE, &optA, 246 | "Rate albums instead of songs", NULL}, 247 | {"genre", 'g', 0, G_OPTION_ARG_NONE, &optg, 248 | "Rate genres instead of songs", NULL}, 249 | { NULL, 0, 0, 0, NULL, NULL, NULL }, 250 | }; 251 | GOptionContext *ctx; 252 | 253 | ctx = g_option_context_new("RATING [EXPRESSION]"); 254 | g_option_context_add_main_entries(ctx, options, "eugene-rate"); 255 | g_option_context_set_summary(ctx, "eugene-rate-"VERSION GITHEAD" - Rate song/artist/album/genre"); 256 | g_option_context_set_description(ctx, "" 257 | "By default this command works on the current playing song.\n" 258 | "For more information about the expression syntax, see:\n" 259 | "http://www.sqlite.org/lang_expr.html"); 260 | /* Ignore unknown options so that the user can pass negative numbers. 261 | */ 262 | g_option_context_set_ignore_unknown_options(ctx, TRUE); 263 | if (!g_option_context_parse(ctx, &argc, &argv, &error)) { 264 | g_printerr("Option parsing failed: %s\n", error->message); 265 | g_error_free(error); 266 | g_option_context_free(ctx); 267 | return 1; 268 | } 269 | g_option_context_free(ctx); 270 | 271 | if (argc <= 1) { 272 | g_printerr("No rating given\n"); 273 | ret = 1; 274 | } 275 | else if (argc > 2) 276 | ret = cmd_rate_internal(argv[2], argv[1], opta, optA, optg); 277 | else 278 | ret = cmd_rate_internal(NULL, argv[1], opta, optA, optg); 279 | return ret; 280 | } 281 | -------------------------------------------------------------------------------- /src/gmodule/stats/eugene-count.c: -------------------------------------------------------------------------------- 1 | /* vim: set cino= fo=croql sw=8 ts=8 sts=0 noet cin fdm=syntax : */ 2 | 3 | /* 4 | * Copyright (c) 2009, 2010 Ali Polatel 5 | * 6 | * This file is part of the mpdcron mpd client. mpdcron is free software; 7 | * you can redistribute it and/or modify it under the terms of the GNU General 8 | * Public License version 2, as published by the Free Software Foundation. 9 | * 10 | * mpdcron is distributed in the hope that it will be useful, but WITHOUT ANY 11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 | * details. 14 | * 15 | * You should have received a copy of the GNU General Public License along with 16 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple 17 | * Place, Suite 330, Boston, MA 02111-1307 USA 18 | */ 19 | 20 | #include "eugene-defs.h" 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | static int 28 | count_artist(struct mpdcron_connection *conn, const char *expr, 29 | const char *count) 30 | { 31 | int changes; 32 | char *esc_artist, *myexpr; 33 | struct mpd_song *song; 34 | 35 | if (expr != NULL) { 36 | if (!mpdcron_count_artist_expr(conn, expr, count, &changes)) { 37 | eulog(LOG_ERR, "Failed to change play count of artist: %s", 38 | conn->error->message); 39 | return 1; 40 | } 41 | } 42 | else { 43 | if ((song = load_current_song()) == NULL) 44 | return 1; 45 | else if (mpd_song_get_tag(song, MPD_TAG_ARTIST, 0) == NULL) { 46 | eulog(LOG_ERR, "Current playing song has no artist tag!"); 47 | mpd_song_free(song); 48 | return 1; 49 | } 50 | 51 | esc_artist = quote(mpd_song_get_tag(song, MPD_TAG_ARTIST, 0)); 52 | myexpr = g_strdup_printf("name=%s", esc_artist); 53 | g_free(esc_artist); 54 | mpd_song_free(song); 55 | 56 | if (!mpdcron_count_artist_expr(conn, myexpr, count, &changes)) { 57 | eulog(LOG_ERR, "Failed to change play count of current playing artist: %s", 58 | conn->error->message); 59 | g_free(myexpr); 60 | return 1; 61 | } 62 | g_free(myexpr); 63 | } 64 | printf("Modified %d entries\n", changes); 65 | return 0; 66 | } 67 | 68 | static int 69 | count_album(struct mpdcron_connection *conn, const char *expr, 70 | const char *count) 71 | { 72 | int changes; 73 | char *esc_album, *myexpr; 74 | struct mpd_song *song; 75 | 76 | if (expr != NULL) { 77 | if (!mpdcron_count_album_expr(conn, expr, count, &changes)) { 78 | eulog(LOG_ERR, "Failed to change play count of count album: %s", 79 | conn->error->message); 80 | return 1; 81 | } 82 | } 83 | else { 84 | if ((song = load_current_song()) == NULL) 85 | return 1; 86 | else if (mpd_song_get_tag(song, MPD_TAG_ALBUM, 0) == NULL) { 87 | eulog(LOG_ERR, "Current playing song has no album tag!"); 88 | mpd_song_free(song); 89 | return 1; 90 | } 91 | 92 | esc_album = quote(mpd_song_get_tag(song, MPD_TAG_ALBUM, 0)); 93 | myexpr = g_strdup_printf("name=%s", esc_album); 94 | g_free(esc_album); 95 | mpd_song_free(song); 96 | 97 | if (!mpdcron_count_album_expr(conn, myexpr, count, &changes)) { 98 | eulog(LOG_ERR, "Failed to change play count of current playing album: %s", 99 | conn->error->message); 100 | g_free(myexpr); 101 | return 1; 102 | } 103 | g_free(myexpr); 104 | } 105 | printf("Modified %d entries\n", changes); 106 | return 0; 107 | } 108 | 109 | static int 110 | count_genre(struct mpdcron_connection *conn, const char *expr, 111 | const char *count) 112 | { 113 | int changes; 114 | char *esc_genre, *myexpr; 115 | struct mpd_song *song; 116 | 117 | if (expr != NULL) { 118 | if (!mpdcron_count_genre_expr(conn, expr, count, &changes)) { 119 | eulog(LOG_ERR, "Failed to change play count of genre: %s", 120 | conn->error->message); 121 | return 1; 122 | } 123 | } 124 | else { 125 | if ((song = load_current_song()) == NULL) 126 | return 1; 127 | else if (mpd_song_get_tag(song, MPD_TAG_GENRE, 0) == NULL) { 128 | eulog(LOG_ERR, "Current playing song has no genre tag!"); 129 | mpd_song_free(song); 130 | return 1; 131 | } 132 | 133 | esc_genre = quote(mpd_song_get_tag(song, MPD_TAG_GENRE, 0)); 134 | myexpr = g_strdup_printf("name=%s", esc_genre); 135 | g_free(esc_genre); 136 | mpd_song_free(song); 137 | 138 | if (!mpdcron_count_genre_expr(conn, myexpr, count, &changes)) { 139 | eulog(LOG_ERR, "Failed to change play count of count current playing genre: %s", 140 | conn->error->message); 141 | g_free(myexpr); 142 | return 1; 143 | } 144 | g_free(myexpr); 145 | } 146 | printf("Modified %d entries\n", changes); 147 | return 0; 148 | } 149 | 150 | static int 151 | count_song(struct mpdcron_connection *conn, 152 | const char *expr, const char *count) 153 | { 154 | int changes; 155 | char *esc_uri, *myexpr; 156 | struct mpd_song *song; 157 | 158 | if (expr != NULL) { 159 | if (!mpdcron_count_expr(conn, expr, count, &changes)) { 160 | eulog(LOG_ERR, "Failed to change play count of count song: %s", 161 | conn->error->message); 162 | return 1; 163 | } 164 | } 165 | else { 166 | if ((song = load_current_song()) == NULL) 167 | return 1; 168 | 169 | esc_uri = quote(mpd_song_get_uri(song)); 170 | myexpr = g_strdup_printf("uri=%s", esc_uri); 171 | g_free(esc_uri); 172 | mpd_song_free(song); 173 | 174 | if (!mpdcron_count_expr(conn, myexpr, count, &changes)) { 175 | eulog(LOG_ERR, "Failed to change play count of current playing song: %s", 176 | conn->error->message); 177 | g_free(myexpr); 178 | return 1; 179 | } 180 | g_free(myexpr); 181 | } 182 | printf("Modified %d entries\n", changes); 183 | return 0; 184 | } 185 | 186 | static int 187 | cmd_count_internal(const char *expr, const char *count, 188 | bool artist, bool album, bool genre) 189 | { 190 | int port, ret; 191 | const char *hostname, *password; 192 | struct mpdcron_connection *conn; 193 | 194 | if ((artist && album && genre) || 195 | (artist && album) || 196 | (artist && genre) || 197 | (album && genre)) { 198 | g_printerr("--artist, --album and --genre options are mutually exclusive\n"); 199 | return 1; 200 | } 201 | 202 | hostname = g_getenv(ENV_MPDCRON_HOST) 203 | ? g_getenv(ENV_MPDCRON_HOST) 204 | : DEFAULT_HOSTNAME; 205 | port = g_getenv(ENV_MPDCRON_PORT) 206 | ? atoi(g_getenv(ENV_MPDCRON_PORT)) 207 | : DEFAULT_PORT; 208 | password = g_getenv(ENV_MPDCRON_PASSWORD); 209 | 210 | conn = mpdcron_connection_new(hostname, port); 211 | if (conn->error != NULL) { 212 | eulog(LOG_ERR, "Failed to connect: %s", conn->error->message); 213 | mpdcron_connection_free(conn); 214 | return 1; 215 | } 216 | 217 | if (password != NULL) { 218 | if (!mpdcron_password(conn, password)) { 219 | eulog(LOG_ERR, "Authentication failed: %s", conn->error->message); 220 | mpdcron_connection_free(conn); 221 | return 1; 222 | } 223 | } 224 | 225 | if (artist) 226 | ret = count_artist(conn, expr, count); 227 | else if (album) 228 | ret = count_album(conn, expr, count); 229 | else if (genre) 230 | ret = count_genre(conn, expr, count); 231 | else 232 | ret = count_song(conn, expr, count); 233 | mpdcron_connection_free(conn); 234 | return ret; 235 | } 236 | 237 | int 238 | cmd_count(int argc, char **argv) 239 | { 240 | int opta = 0, optA = 0, optg = 0, ret; 241 | GError *error = NULL; 242 | GOptionEntry options[] = { 243 | {"artist", 'a', 0, G_OPTION_ARG_NONE, &opta, 244 | "Change play count of artists instead of songs", NULL}, 245 | {"album", 'A', 0, G_OPTION_ARG_NONE, &optA, 246 | "Change play count of albums instead of songs", NULL}, 247 | {"genre", 'g', 0, G_OPTION_ARG_NONE, &optg, 248 | "Change play count of genres instead of songs", NULL}, 249 | { NULL, 0, 0, 0, NULL, NULL, NULL }, 250 | }; 251 | GOptionContext *ctx; 252 | 253 | ctx = g_option_context_new("COUNT [EXPRESSION]"); 254 | g_option_context_add_main_entries(ctx, options, "eugene-count"); 255 | g_option_context_set_summary(ctx, "eugene-count-"VERSION GITHEAD 256 | " - Change play count of song/artist/album/genre"); 257 | g_option_context_set_description(ctx, "" 258 | "By default this command works on the current playing song.\n" 259 | "For more information about the expression syntax, see:\n" 260 | "http://www.sqlite.org/lang_expr.html"); 261 | /* Ignore unknown options so that the user can pass negative numbers. 262 | */ 263 | g_option_context_set_ignore_unknown_options(ctx, TRUE); 264 | if (!g_option_context_parse(ctx, &argc, &argv, &error)) { 265 | g_printerr("Option parsing failed: %s\n", error->message); 266 | g_error_free(error); 267 | g_option_context_free(ctx); 268 | return 1; 269 | } 270 | g_option_context_free(ctx); 271 | 272 | if (argc <= 1) { 273 | g_printerr("No count given\n"); 274 | ret = 1; 275 | } 276 | else if (argc > 2) 277 | ret = cmd_count_internal(argv[2], argv[1], opta, optA, optg); 278 | else 279 | ret = cmd_count_internal(NULL, argv[1], opta, optA, optg); 280 | return ret; 281 | } 282 | --------------------------------------------------------------------------------