shell-fm-0.7+git20100414/0000755000175000017500000000000011361424321013421 5ustar nachonachoshell-fm-0.7+git20100414/Makefile0000644000175000017500000000224111361424251015062 0ustar nachonacho DESTDIR = PREFIX := /usr MANDIR := $(PREFIX)/man DOCDIR := ${PREFIX}/share/doc .PHONY : shell-fm manual all clean tags cscope all : shell-fm manual shell-fm : $(MAKE) -C source manual : $(MAKE) -C manual install : install -m 0755 -d $(DESTDIR)$(PREFIX)/bin/ install -m 0755 -d $(DESTDIR)$(MANDIR)/man1/ install -m 0755 source/shell-fm $(DESTDIR)$(PREFIX)/bin/ install -m 0644 manual/shell-fm.1.gz $(DESTDIR)$(MANDIR)/man1/ install-extras : install -m 0755 -d $(DESTDIR)$(DOCDIR)/shell-fm/ cp -r scripts $(DESTDIR)$(DOCDIR)/shell-fm/ install-strip : install strip $(PREFIX)/bin/shell-fm uninstall : uninstall-extras rm -f $(PREFIX)/bin/shell-fm rm -f $(MANDIR)/man1/shell-fm.1.gz rmdir --ignore-fail-on-non-empty $(PREFIX)/bin rmdir --ignore-fail-on-non-empty $(MANDIR)/man1 rmdir --ignore-fail-on-non-empty $(MANDIR) rmdir --ignore-fail-on-non-empty $(PREFIX) uninstall-extras : rm -rf $(DESTDIR)$(DOCDIR)/shell-fm/ clean : $(MAKE) -C source clean $(MAKE) -C manual clean tags : cscope.files @rm -f tags xargs -n50 ctags -a < cscope.files cscope : cscope.files cscope -b cscope.files : find source -name '*.[ch]' > cscope.files shell-fm-0.7+git20100414/scripts/0000755000175000017500000000000011361424251015112 5ustar nachonachoshell-fm-0.7+git20100414/scripts/vim/0000755000175000017500000000000011361424251015705 5ustar nachonachoshell-fm-0.7+git20100414/scripts/vim/syntax/0000755000175000017500000000000011361424251017233 5ustar nachonachoshell-fm-0.7+git20100414/scripts/vim/syntax/shell-fm-rc.vim0000644000175000017500000000210311361424251022055 0ustar nachonacho" vim syntax for shell-fm if exists("b:current_syntax") finish endif let b:current_syntax = "shell-fm-rc" setlocal iskeyword+=- syn region ShellFMKey start=/^\(\s*[^#]\)\@=/ end=/=\@=/ \ contains=ShellFMKnownKey,ShellFMColorKey,ShellFMKeybindingKey syn match ShellFMEquals /=/ skipwhite \ nextgroup=ShellFMValue syn keyword ShellFMKnownKey contained \ username password default-radio np-file np-file-format np-cmd \ pp-cmd bind port extern proxy expiry device title-format minimum \ delay-change screen-format term-format download gap discovery \ preview-format screen-format term-format unix daemon syn match ShellFMColorKey /\<[atldsSALTR]-color\>/ contained syn match ShellFMKeybindingKey /key0x[0-9a-fA-F][0-9a-fA-F]/ contained syn match ShellFMValue /.*$/ contained syn match ShellFMComment "#.*" contains=perlTodo hi def link ShellFMKnownKey Keyword hi def link ShellFMColorKey Keyword hi def link ShellFMKeybindingKey Keyword hi def link ShellFMValue Constant hi def link ShellFMComment Comment shell-fm-0.7+git20100414/scripts/vim/ftdetect/0000755000175000017500000000000011361424251017507 5ustar nachonachoshell-fm-0.7+git20100414/scripts/vim/ftdetect/shell-fm-rc.vim0000644000175000017500000000007311361424251022335 0ustar nachonachoau BufNewFile,BufRead shell-fm.rc set filetype=shell-fm-rc shell-fm-0.7+git20100414/scripts/lyrics.py0000755000175000017500000000730611361424251017002 0ustar nachonacho#!/usr/bin/env python # Copyright (C) 2009 by Silviu Grijincu . # Published under the terms of the GNU General Public License (GPL). # INFO # lyrics.py fetches lyrics from http://lyricwiki.org/. # The script is triggered by a key that is not already # in use by shell-fm and must be set. (See bellow) # INSTALL # Save lyrics.py in the ~/.shell-fm/scripts/ directory. # (if the directories do not exist please create them) # To enable the plugin you must have a configuration file. # 1.Create a file named shell-fm.rc in the ~/.shell-fm/ directory. # 2.Put the following line in shell-fm.rc file : # key0x?? = /usr/bin/python ~/.shell-fm/scripts/lyrics.py %a %t # 4.Replace ?? with the ASCII hex code of the triggering key you wold like to use. # for example, to use ` (backquote) as a trigger replace ?? with 60 as bellow: # key0x60 = /usr/bin/python ~/.shell-fm/scripts/lyrics.py %a %t from urllib2 import * from sys import argv class LyricSource: "A lyric soruce" def __init__(self, artist, title): "init a Lyric source" self.artist = artist self.title = title def get_lyrics(self): "get lyrics." return (False, "this is a dummy lyric source, no lyric can be found") class DummyLyricSource(LyricSource): def __init__(self, artist, title): LyricSource.__init__(self, artist, title) def get_lyrics(self): return (False, "dummy error message from DummyLyricSource") class LyricWiki(LyricSource): def __init__(self, artist, title): LyricSource.__init__(self, artist, title) self.URL = 'http://lyricwiki.org/' def canonical(self, str): # capitalize every word after an ' ' and # replace ' ' with '_' return "_".join(map((lambda x : x.capitalize()), str.split(' '))) def get_url(self): return self.URL + self.canonical(self.artist) + ':' + self.canonical(self.title) def get_html(self, url): try: html = urlopen(url).read() return (True, html) except HTTPError, e: errmsg = 'The server couldn\'t fulfill the request for ' + url errmsg += 'Error code: ' + str(e.code) return (False, errmsg) except URLError, e: errmsg = 'We failed to reach the server ' + url errmsg += 'Reason: ' + str(e.reason) return (False, errmsg) else: return (False, "unexpected path reached in LyricWiki.get_html") def filter_html_markers(self, html): rules = ( ('
', '\n '), ('
', '\n '), ('>', '>'), ('<', '<'), (' ', ' ')) for (x, y) in rules: html = html.replace(x, y) return ' ' + html def parse_lyrics(self, html, url): instrumental = html.find('This song is an instrumental.') if instrumental <> -1: return (True, 'This song is instrumental.') start_marker = '
' found = html.find(start_marker) if found == -1: return (False, 'Lyrics not found at ' + url + '.') start = found + len(start_marker) end = html.find('\n', start) lyrics = self.filter_html_markers(html[start:end]) return (True, lyrics) def get_lyrics(self): url = self.get_url() (success, msg) = self.get_html(url) if success == False: return (False, msg) html = msg return self.parse_lyrics(html, url) debug=False def main(argv): artist = argv[1] title = argv[2] # TODO: after adding a new LyricSouce class, append # an object of that class here lyric_sources = (LyricWiki(artist, title), DummyLyricSource(artist, title)) lyrics = map( (lambda x : x.get_lyrics()), lyric_sources) # print the first lyrics found for (success, lyric) in lyrics: if success == True: print lyric return if debug: # if no lyrics found, print all error messages for (success, errmsg) in lyrics: print errmsg else: print 'Lyrics not found.' if __name__ == "__main__": main(argv) shell-fm-0.7+git20100414/scripts/shell-colors.sh0000755000175000017500000000100611361424251020054 0ustar nachonacho#!/bin/bash # shell-colors.sh, prints possible fg/bg color combinations # using escape sequences. # # Copyright (C) 2007 by Jonas Kramer. # Published under the terms of the GNU General Public License (GPL). echo -ne "FG/BG\t" for bg in `seq 40 47`; do echo -ne " $bg "; done for fg in `seq 30 37`; do echo -ne "\n$fg (N)\t" for bg in `seq 40 47`; do echo -ne "\x1B[$bg;${fg}m Normal \x1B[0m"; done echo -ne "\n$fg (B)\t" for bg in `seq 40 47`; do echo -ne "\x1B[$bg;${fg};1m Bold \x1B[0m"; done done echo shell-fm-0.7+git20100414/scripts/shell-fm-tune.sh0000755000175000017500000000150511361424251020132 0ustar nachonacho#!/bin/bash # Published under the terms of the GNU General Public License (GPL). extract () { KEY="$1" VALUE="$( sed -e "s/^$KEY\s*=\s*//" <<<"` grep -oE "^$KEY[ ]*=[ ]*[^ ]+" $HOME/.shell-fm/shell-fm.rc \ | head -n 1 `" )" echo "$VALUE" } if [ $# -eq 0 ]; then echo "Usage: shell-fm-tune lastfm://radio_url [HOST [PORT]]" exit -1 fi RADIO="$1" IP= PORT= CMD= if [ $# -gt 1 ]; then IP="$2" if [ $# -gt 2 ]; then PORT="$3"; fi elif [ -r "$HOME/.shell-fm/shell-fm.rc" ]; then IP="`extract "bind"`" PORT="`extract "port"`" fi if [ -n "`which nc 2>/dev/null`" ]; then CMD="nc" elif [ -n "`which telnet`" ]; then CMD="telnet" else echo "Netcat or telnet must be installed!" exit -1 fi [ -z "$IP" ] && IP="127.0.0.1" [ -z "$PORT" ] && PORT="54311" echo "play $RADIO" | "$CMD" "$IP" "$PORT" >/dev/null 2>&1 shell-fm-0.7+git20100414/scripts/zcontrol0000755000175000017500000000140211361424251016707 0ustar nachonacho#!/usr/bin/env zsh # zcontrol, shell-fm remote control script. Copyright (C) 2006 by Jonas Kramer. # Published under the terms of the GNU General Public License (GPL). setopt extendedglob export BIND="127.0.0.1" PORT="54311" if [[ $# -eq 0 || "$1" != (skip|love|ban|quit|play) ]]; then print "Usage: $0 (skip|love|ban|quit|play) [argument]" >&2 exit -1 fi if [[ -r "$HOME/.shell-fm/shell-fm.rc" ]]; then for LINE in ${(f)"$(<$HOME/.shell-fm/shell-fm.rc)"}; do LINE="${LINE%%\#*}" if [[ "$LINE" == (#b)(bind|port)[\ ]#"="[\ ]#(#b)([^\ ]##)* ]]; then export ${(U)match[1]}="$match[2]" fi done fi zmodload zsh/net/tcp if ! ztcp "$BIND" "$PORT"; then print "Couldn't connect to [$BIND:$PORT]." >&2 exit -1 fi print -u $REPLY "$1" $@[2,-1] ztcp -c $REPLY shell-fm-0.7+git20100414/scripts/unix.pl0000755000175000017500000000152111361424251016434 0ustar nachonacho#!/usr/bin/perl # Shell.FM UNIX Socket Control # Copyright (C) by Jonas Kramer. All rights reserved. # Published under the terms of the GNU General Public License (GPL). # # Usage: $0 # # See the shell-fm(1) manual for a list of commands. use strict; use warnings; use IO::Socket::UNIX; use IO::File; die "Usage: $0 \n" unless @ARGV; my $rc = new IO::File("$ENV{HOME}/.shell-fm/shell-fm.rc"); die "Can't open configuration. $!.\n" unless $rc; my $path; for($rc->getlines) { $path = $1 if /^\s*unix\s*=\s*([^#\s]+)/; } $rc->close; die "No socket path found.\n" unless $path; my $socket = new IO::Socket::UNIX($path); die "Failed to create socket. $!.\n" unless $socket; $socket->print("@ARGV\n") or die("Failed to send command. $!.\n"); my $reply = $socket->getline; print $reply if $reply; $socket->close; shell-fm-0.7+git20100414/scripts/shc.hs0000644000175000017500000000511211361424251016222 0ustar nachonacho -- Shell.FM control tool. -- Copyright (C) 2008-2009 by Jonas Kramer. -- Published under the terms of the GNU General Public License (GPL). import Network.Socket import System.Environment import Data.List import System.IO import System.IO.Error main = do args <- getArgs case args of [ "play", station ] -> playStation station [ "stop" ] -> stopStation [ "skip" ] -> skipTrack [ "next" ] -> skipTrack [ "love" ] -> loveTrack [ "ban" ] -> banTrack [ "quit" ] -> quit [ "pause" ] -> pause [ "tag-artist", _ ] -> tagArtist $ tail args [ "tag-album", _ ] -> tagAlbum $ tail args [ "tag-track", _ ] -> tagAlbum $ tail args [ "info", format ] -> formatString format [ "info" ] -> formatString "%a - %t" [ "artist-tags" ] -> artistTags [ "album-tags" ] -> albumTags [ "track-tags" ] -> trackTags [] -> formatString "Now playing \"%t\" by %a." _ -> putStrLn "Please read the shell-fm manual for valid commands." unixConnect = do socketPath <- getEnv "SHELLFM_UNIX_PATH" unix <- socket AF_UNIX Stream 0 connect unix (SockAddrUnix socketPath) return unix playStation station = do sendCommand $ "play " ++ (prefixedStation station) stopStation = do sendCommand "stop" skipTrack = do sendCommand "skip" loveTrack = do sendCommand "love" banTrack = do sendCommand "ban" quit = do sendCommand "quit" pause = do sendCommand "pause" artistTags = do sendCommandWithReply $ "artist-tags" albumTags = do sendCommandWithReply $ "album-tags" trackTags = do sendCommandWithReply $ "track-tags" tagArtist tagList = do tagCommand tagList "artist" tagAlbum tagList = do tagCommand tagList "album" tagTrack tagList = do tagCommand tagList "track" tagCommand tagList item = do sendCommand $ "tag-" ++ item ++ (joinTags tagList) formatString format = do sendCommandWithReply $ "info " ++ format joinTags xs = join "," xs join _ [] = "" join _ (x:[]) = x join s (x:xs) = x ++ s ++ (join s xs) sendCommand command = do unix <- unixConnect result <- send unix (command ++ "\n") return () sendCommandWithReply command = do unix <- unixConnect result <- send unix (command ++ "\n") safeReceive unix return () safeReceive unix = do result <- try action putStrLn $ either handleError replyString result where action = recvLen unix 1024 handleError error = "No reply or error occured." replyString (result, length) = result prefixedStation station = if "lastfm://" `isPrefixOf` station then station else "lastfm://" ++ station shell-fm-0.7+git20100414/BSDmakefile0000644000175000017500000000004511361424251015453 0ustar nachonachoall ${.TARGETS}: @gmake $@ shell-fm-0.7+git20100414/manual/0000755000175000017500000000000011361424251014700 5ustar nachonachoshell-fm-0.7+git20100414/manual/Makefile0000644000175000017500000000016611361424251016343 0ustar nachonacho .PHONY : clean manual : shell-fm.1.gz shell-fm.1.gz : shell-fm.1 gzip -9 -c $< > $@ clean : rm -f shell-fm.1.gz shell-fm-0.7+git20100414/manual/shell-fm.10000644000175000017500000003450511361424251016500 0ustar nachonacho.TH "shell-fm" 1 .SH NAME Shell.FM \- Lightweight, console-based player for Last.FM radio streams. .SH SYNOPSIS .B shell-fm [-d] [-i address] [-p port] [-b] [-D device] [-y proxy] [-h] lastfm://... .SH DESCRIPTION Shell.FM is a lightweight, console-based player for radio streams provided by Last.FM. .SH OPTIONS .TP .B \-d Fork to background (requires a socket interface to be set up so it can still be controlled somehow). .TP .B \-i
Enable the socket interface and bind it to the given host address (should be the host name or IP address of the host shell-fm is running on). .TP .B \-p Make the socket interface listen for incoming connections on the given port. Default is 54311. .TP .B \-b Enable batch mode (some freaky mode that makes shell-fm easier to handle from inside emacs). This was not my idea. .TP .B \-D Use the given device file as audio device. This is only used if libao support is disabled. Default is /dev/audio. .TP .B \-y Make shell-fm use the given host as proxy server for HTTP requests. .TP .B \-h Print help text and exit. .TP .B lastfm://... URI of a Last.FM radio stream to play after startup. For example: `shell-fm -d lastfm://artist/The%20Beatles/similarartists' .SH USAGE On startup, shell-fm will ask you for your Last.FM login and password (if not provided in your ~/.shell-fm/shell-fm.rc). If you've given a stream URI on the command line or there is a default radio defined in the configuration file, shell-fm will now try to play it. When the startup is done, there are lots of keys to control shell-fm. Here is a alphabetically sorted list. .TP .B a Add the currently played track to your Last.FM playlist. .TP .B A Ban the artist of the currently played track. Whenever a track of that artist is played from now on, it is automatically banned. .TP .B B Ban the currently played track. .TP .B d Enabled/disable discovery mode. I'm not sure if this has any effect, and it looks like even the Last.FM guys don't really know what it does, but I think it is meant to ensure that you get only tracks that you don't know yet. .TP .B f Jump to the fan radio station of the artist of the currently played track. .TP .B h List bookmarks. .TP .B H Bookmark the currently played radio station. You'll be asked to hit a digit key. Whenever you hit that key again from now on, shell-fm will jump to that radio station. .TP .B i Print some more information about the currently played track. .TP .B l Love the currently played track. .TP .B n Skip the currently played track. .TP .B p Pause. If you pause too long, the stream will break, which has the same effect as stopping the stream (see below). .TP .B P Enable/disable reporting played tracks to your Last.FM profile. Enabled by default. .TP .B Q Quit. .TP .B r Change radio station. This will prompt you for an Last.FM radio station URI. The tabulator key helps if you don't know what to type. Arrow-Up and Arrow-Down allow you to browse your radio history. Enter these without the "lastfm://" prefix. .RS .PP For example: `radio url> globaltags/world' .RE .TP .B R Recommend the currently played track/artist/album to another Last.FM user. .TP .B S Stop playing. .TP .B s Jump to the similar artists radio stream of the currently played tracks artist. .TP .B T Tag the currently played track/artist/album. Tabulator key completes known tags. .TP .B U Unlove the currently played track. .TP .B u Print upcoming tracks in playlist. .TP .B + Increase volume. .TP .B - Decrease volume. .PP .SH SETUP Before you start, you should have created the directories .B ~/.shell-fm and .B ~/.shell-fm/cache or you will get a lot of warnings, the tab-completion will be extremely slow and you can't make use of some features (auto-ban, history, bookmarks). You might also want to place a configuration file in .B ~/.shell-fm for a faster startup. .SH CONFIGURATION This section describes the syntax and options for the shell-fm configuration file. The file should be placed in .B ~/.shell-fm/shell-fm.rc and should consist of simple .B key = value assignments. See (far) below for a sample configuration. These are the available options. .TP .B username = your-login This is your login on Last.FM. If this is provided, shell-fm won't ask you for it on startup anymore. .TP .B password = your-password This is your (clear text) Last.FM password. If this and your login is provided in the configuration, shell-fm won't ask you on startup. .TP .B default-radio = lastfm://... If this is provided (and valid), shell-fm will play this station by default after startup. If there's another station URI given on the command line, it will override this setting. .TP .B np-file = path-to-file If this is defined, shell-fm will print information about the currently played track into the given file, whenever a new track is played. .TP .B np-file-format = format-string This defines how the information written to your now-playing file will look like. There are several format flags available. Have a look at the .TP .B preview-format = format-string Format of the track information in the playlist preview (key 'u'). .B FORMAT FLAGS section for the details. .TP .B np-cmd = shell command If this is defined, the given command will be execute whenever a new track starts. The value may contain format flags. .TP .B pp-cmd = shell command If this is defined, the given command will be execute whenever a downloading track ends. The value will have the path to the file appended. .TP .B ?-color = color This allows you to color format elements. The .B ? may be the letter of any format flag (without percent). The color is just a normal shell color code matching "[01];3[0-7]". Whenever the format element is printed to the console, it will have the given color. Have a look at the .TP .B daemon = something If this is set to something, shell-fm will start in daemon mode by default. Starting with -d as command line option will disable daemon mode. .TP .B COLORS section for a list. .TP .B key0x?? = shell command This allows you to bind shell commands to free keys (keys that are not used by shell-fm, check the .B USAGE section above for a list). .B ?? should be the hex code of the ASCII code of the key. The command you assign will be evaluated (check the .B FORMAT FLAGS section) and executed then. This "feature" allows you to implement own features, like fetching and printing the lyrics of the currently played track, etc. If you have a cool idea or even a working script, I'd be happy if you let me know. .TP .B bind = host This specifies the network interface you want shell-fm to bind to. .B host should be the host name or an IP address of host shell-fm is running on. shell-fm will open a port (see the .B port option below) on the specified interface which you can connect to to control shell-fm remotely (or from local scripts, see .B key0x?? above). Check the .B NETWORK INTERFACE COMMANDS section below for a list of known commands. .B NOTE: The network interface has no user authentication, so anyone with access to your network/host can control shell-fm. Use it only if you really need to control shell-fm over a network. Otherwise use the UNIX socket interface (see below). .TP .B unix = path If this is set to a proper path, on that path a UNIX socket will be created for local "remote" control. This socket interface takes the same commands as the TCP socket interface (see above). .TP .B port = port-number With this option you can change the port shell-fm will listen on (if .B bind is specified). Default is 54311. .TP .B extern = shell command This allows you to specify an external program or script as player for the streams. If given, shell-fm will run the command and pipe the MP3 stream into it, instead of playing the stream itself. For example, .B extern = madplay -Q - works very fine. This option is meant as a work-around for architectures that shell-fm doesn't work completly profectly on. .TP .B proxy = proxy server This allows you to specify a proxy server for the HTTP requests. .TP .B expiry = some-number This defines the number of seconds until a cached page expires. The default is 86400 seconds (24 hours). You shouldn't set a very low value here, since the Last.FM server often are very slow. This mostly affects the prompts (radio prompt, tag prompt, ...), since shell-fm fetches some feeds to get values for the tab-completion. .TP .B device = path Path to the audio device to use (see .B -D command line option). .TP .B title-format = format-string This is the format of the track string that is printed to the console for every track played. Default is 'Now playing "%t" by %a.'. .TP .B minimum = percentage With this option you can change the minimum duration a track must have been played to be scrobbled (in percent, but without the % sign). For example, if this option is set to 75, the track will not be scrobbled if it has not been played for at least 75% of its total duration. If you skip or stop the track before it has been played for 75%, it will not be scrobbled. Default is 50%, as specified in the scrobbling protocol version 1.2. .TP .B delay-change = something If this is set to anything, and you change the station with 'r', 's' or 'f', the station-change will be delayed until the currently played track finishes or is skipped. Also they key 'q' will initialize a delayed quit, so after the currently played track shell-fm will exit. 'Q' (uppercase) still quits immediately. .TP .B screen-format = format-string If this is set, shell-fm will check if the terminal it's running in is a screen session ($TERM is "screen") and set the screen windows title to the formatted string to be seen on $ESCAPE+w or $ESCAPE+". .TP .B term-format = format-string Works like screen-format, but sets the x-terminals window title. .TP .B download = format-string If this is set to a valid path (may contain format flags), and the played track is free, it is saved at the given place. .TP .B gap = seconds If this is set to a number, shell-fm will wait that amount of seconds between tracks. .TP .B discovery = something Enable discovery mode by default. .TP .B stream-timeout = seconds Users reported that in some regions in the world, Last.FM servers sometimes pretend to stream a track but then don't send anything, which makes shell-fm hang forever waiting for the track data. If you have that problem, use this option to define a stream timeout. When shell-fm is waiting for stream data, it will wait that many seconds and then skip to the next track. .TP .B no-rtp = something Start with RTP disabled. .SH FORMAT FLAGS There are several format flags allowed for some options. Here is the list. .TP .B %a Artist name. .TP .B %t Track title. .TP .B %l Album name. .TP .B %d Track duration in seconds. .TP .B %s Station name. .TP .B %S Station URL. .TP .B %A URL of the artists page on Last.FM. .TP .B %L URL of the albums page on Last.FM. .TP .B %T URL of the tracks page on Last.FM. .TP .B %R Remaining seconds of the played track. .TP .B %% A %. .SH COLORS .TP .B 0;30 Black (not very useful). .TP .B 1;30 Dark gray. .TP .B 0;31 Red. .TP .B 1;31 Light red. .TP .B 0;32 Green. .TP .B 1;32 Light green. .TP .B 0;33 Dark yellow/brown. .TP .B 1;33 Yellow. .TP .B 0;34 Blue. .TP .B 1;34 Light blue. .TP .B 0;35 Violet. .TP .B 1;35 Pink. .TP .B 0;36 Turquoise. .TP .B 1;36 Cyan. .TP .B 0;37 Gray. .TP .B 1;37 White. .SH NETWORK INTERFACE COMMANDS This section describes the commands shell-fm's network interface knows. To use the interface, you must provide a valid value to the .B bind option in your configuration or use the .B -i option on the command line. Then you can connect the specified port (54311 by default) and send one command at a time. You also have to hurry, since there is a very short timeout. Best thing would be if you used a script for accessing this interface. (See .B shell-fm-*/scripts/ for examples) This is a list of the known commands. .TP .B play lastfm://... Play the given stream. .TP .B love Love the currently played track. .TP .B ban Ban the currently played track. .TP .B skip Skip the currently played track. .TP .B quit Quit. .TP .B info some-format-string Evaluate the given format string (check the .B FORMAT FLAGS section) and return the formatted information. .TP .B pause Pause. .TP .B discovery Toggle discovery mode on/off. .TP .B tag-artist some-comma-separated-tags Tag the artist of the currently played track. .TP .B tag-album some-comma-separated-tags Tag the album of the currently played track. .TP .B tag-track some-comma-separated-tags Tag the currently played track. .TP .B artist-tags Returns the tags of the currently played tracks artist. .TP .B album-tags Returns the tags of the currently played tracks album. .TP .B track-tags Returns the tags of the currently played track. .TP .B stop Stop stream. .SH FILES This section describes the meanings of the files in $HOME/.shell-fm/. The base directory can be overriden by setting the environment variable $SHELL_FM_HOME to another directory. .TP .B autoban This file contains the auto-banned artists. .TP .B bookmarks This file contains the bookmarked stations in the format "[digit] = [url]". .TP .B cache/ This directory contains cached sites fetched from Last.FM for faster tab-completion etc. .TP .B i-template If this file exists, it will be used as a template for the output of 'i'. It may contain usual format flags. .TP .B radio-history The radio stations you have listened to. The history is used for the radio prompt. .TP .B scrobble-cache If Shell.FM can't scrobble the data of a track for any reason before you quit, it stores the track data in here and it will try to submit the tracks the next time it is run. .TP .B shell-fm.rc Your configuration file as described above. .SH EXAMPLES .TP .B Sample Configuration for shell-fm.rc .PP .RS .nf # shell-fm.rc example username = shellfmlover password = CheckFileIsOnlyReadableByOwner default-radio = lastfm://user/shellfmlover/playlist np-file = /home/shellfmlover/.shell-fm/nowplaying np-file-format = %t:%a:%S:%A minimum = 80 delay-change = true .fi .RE .TP .B shell-fm-*.*/scripts/ Includes examples of using the network interface plus a color printing script to help with choosing colors. .TP .B URL FORMAT .PP .RS .nf lastfm://user/$USER/loved lastfm://user/$USER/personal lastfm://usertags/$USER/$TAG lastfm://artist/$ARTIST/similarartists lastfm://globaltags/$TAG lastfm://user/$USER/recommended lastfm://user/$USER/playlist lastfm://tag/$TAG1*$TAG2*$TAG3 .fi .SH BUGS Please send bug reports to . .SH COPYRIGHT Copyright (C) 2006-2010 by Jonas Kramer. Published under the terms of the GNU General Public License. shell-fm-0.7+git20100414/source/0000755000175000017500000000000011361424251014723 5ustar nachonachoshell-fm-0.7+git20100414/source/interface.c0000644000175000017500000002451611361424251017037 0ustar nachonacho/* Copyright (C) 2006 by Jonas Kramer Published under the terms of the GNU General Public License (GPL). */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include "service.h" #include "hash.h" #include "interface.h" #include "autoban.h" #include "settings.h" #include "http.h" #include "split.h" #include "bookmark.h" #include "radio.h" #include "md5.h" #include "submit.h" #include "readline.h" #include "xmlrpc.h" #include "recommend.h" #include "util.h" #include "tag.h" #include "globals.h" extern time_t pausetime; void unlinknp(void); struct hash track; char * shellescape(const char *); void interface(int interactive) { if(interactive) { int key, result; char customkey[8] = { 0 }, * marked = NULL; canon(0); fflush(stderr); if((key = fetchkey(1000000)) == -1) return; if(key == 27) { int ch; while((ch = fetchkey(100000)) != -1 && !strchr("ABCDEFGHMPQRSZojmk~", ch)); return; } switch(key) { case 'l': puts(rate("L") ? "Loved." : "Sorry, failed."); break; case 'U': puts(rate("U") ? "Unloved." : "Sorry, failed."); break; case 'B': puts(rate("B") ? "Banned." : "Sorry, failed."); fflush(stdout); enable(INTERRUPTED); kill(playfork, SIGUSR1); break; case 'n': rate("S"); break; case 'q': if(haskey(& rc, "delay-change")) { delayquit = !delayquit; if(delayquit) fputs("Going to quit soon.\n", stderr); else fputs("Delayed quit cancelled.\n", stderr); } break; case 'Q': quit(); case 'i': if(playfork) { const char * path = rcpath("i-template"); if(path && !access(path, R_OK)) { char ** template = slurp(path); if(template != NULL) { unsigned n = 0; while(template[n]) { puts(meta(template[n], M_COLORED, & track)); free(template[n++]); } free(template); } } else { puts(meta("Track: \"%t\" (%T)", M_COLORED, & track)); puts(meta("Artist: \"%a\" (%A)", M_COLORED, & track)); puts(meta("Album: \"%l\" (%L)", M_COLORED, & track)); puts(meta("Station: %s", M_COLORED, & track)); } } break; case 'r': radioprompt("radio url> "); break; case 'd': toggle(DISCOVERY); printf("Discovery mode %s.\n", enabled(DISCOVERY) ? "enabled" : "disabled"); if(playfork) { printf( "%u track(s) left to play/skip until change comes into affect.\n", playlist.left ); } break; case 'A': printf(meta("Really ban all tracks by artist %a? [yN]", M_COLORED, & track)); fflush(stdout); if(fetchkey(5000000) != 'y') puts("\nAbort."); else if(autoban(value(& track, "creator"))) { printf("\n%s banned.\n", meta("%a", M_COLORED, & track)); rate("B"); } fflush(stdout); break; case 'a': result = xmlrpc( "addTrackToUserPlaylist", "ss", value(& track, "creator"), value(& track, "title") ); puts(result ? "Added to playlist." : "Sorry, failed."); break; case 'P': toggle(RTP); printf("%s RTP.\n", enabled(RTP) ? "Enabled" : "Disabled"); break; case 'f': if(playfork) { station(meta("lastfm://artist/%a/fans", 0, & track)); } break; case 's': if(playfork) { station(meta("lastfm://artist/%a/similarartists", 0, & track)); } break; case 'h': printmarks(); break; case 'H': if(playfork && currentStation) { puts("What number do you want to bookmark this stream as? [0-9]"); fflush(stdout); key = fetchkey(5000000); setmark(currentStation, key - 0x30); } break; case 'S': if(playfork) { enable(STOPPED); kill(playfork, SIGUSR1); } break; case 'T': if(playfork) tag(track); break; case 'p': if(playfork) { if(pausetime) { kill(playfork, SIGCONT); } else { time(& pausetime); kill(playfork, SIGSTOP); } } break; case 'R': if(playfork) { recommend(track); } break; case '+': if(volume < MAX_VOLUME) volume += 1; if(playpipe != 0) write(playpipe, &key, 1); break; case '-': if(volume > 0) volume -= 1; if(playpipe != 0) write(playpipe, &key, 1); break; case 'u': preview(playlist); break; case 'E': expand(& playlist); break; case '?': fputs( "a = add the track to the playlist | A = autoban artist\n" "B = ban Track | d = discovery mode\n" "E = manually expand playlist | f = fan Station\n" "h = list bookmarks | H = bookmark current radio\n" "i = current track information | l = love track\n" "n = skip track | p = pause\n" "P = enable/disable RTP | Q = quit\n" "r = change radio station | R = recommend track/artist/album\n" "S = stop | s = similiar artist\n" "T = tag track/artist/album | u = show upcoming tracks in playlist\n" "U = unlove track | + = increase volume\n" "- = decrease volume | C = reload configuration\n", stderr ); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if((marked = getmark(key - 0x30))) { station(marked); free(marked); } else { puts("Bookmark not defined."); } break; case 'C': /* Reload configuration. */ settings(rcpath("shell-fm.rc"), 0); break; default: snprintf(customkey, sizeof(customkey), "key0x%02X", key & 0xFF); if(haskey(& rc, customkey)) run(meta(value(& rc, customkey), M_SHELLESC, & track)); } } } int fetchkey(unsigned nsec) { fd_set fdset; struct timeval tv; FD_ZERO(& fdset); FD_SET(fileno(stdin), & fdset); tv.tv_usec = nsec % 1000000; tv.tv_sec = nsec / 1000000; if(select(fileno(stdin) + 1, & fdset, NULL, NULL, & tv) > 0) { char ch = -1; if(read(fileno(stdin), & ch, sizeof(char)) == sizeof(char)) return ch; } return -1; } #define remn (sizeof(string) - length - 1) const char * meta(const char * fmt, int flags, struct hash * track) { static char string[4096]; unsigned length = 0, x = 0; /* Switch off coloring when in batch mode */ if(batch) flags &= ~M_COLORED; if(!fmt) return NULL; memset(string, 0, sizeof(string)); while(fmt[x] && remn > 0) { if(fmt[x] != '%') string[length++] = fmt[x++]; else if(fmt[++x]) { if(fmt[x] == '%') { string[length++] = fmt[x++]; } else { char * val = NULL; const char * trackKey = NULL; char taggingItem = 0; const char * color = NULL; switch(fmt[x]) { case 'a': trackKey = "creator"; break; case 't': trackKey = "title"; break; case 'l': trackKey = "album"; break; case 'A': trackKey = "artistpage"; break; case 'T': trackKey = "trackpage"; break; case 'L': trackKey = "albumpage"; break; case 'd': trackKey = "duration"; break; case 's': trackKey = "station"; break; case 'S': trackKey = "stationURL"; break; case 'R': trackKey = "remain"; break; case 'I': trackKey = "image"; break; case 'Z': // artist tags taggingItem = 'a'; break; case 'D': // album tags taggingItem = 'l'; break; case 'z': // track tags taggingItem = 't'; break; } if(trackKey) { val = strdup(value(track, trackKey)); } else if(taggingItem) { val = oldtags(taggingItem, * track); if(!val) { val = strdup(""); } } if(flags & M_COLORED) { char colorkey[64] = { 0 }; snprintf(colorkey, sizeof(colorkey), "%c-color", fmt[x]); color = value(& rc, colorkey); if(color) { /* Strip leading spaces from end of color (Author: Ondrej Novy) */ char * color_st = strdup(color); size_t len = strlen(color_st) - 1; assert(color_st != NULL); while(isspace(color_st[len]) && len > 0) { color_st[len] = 0; len--; } length += snprintf(string + length, remn, "\x1B[%sm", color_st); free(color_st); } } if((flags & M_RELAXPATH) && val) { unsigned n; size_t l = strlen(val); for(n = 0; n < l; ++n) { if(val[n] == '/') val[n] = '|'; } } if(flags & M_SHELLESC) { char * escaped = shellescape(val); free(val); val = escaped; } length = strlen(strncat(string, val ? val : "(unknown)", remn)); free(val); if(color) length = strlen(strncat(string, "\x1B[0m", remn)); ++x; } } } return string; } #undef remn void run(const char * cmd) { if(!fork()) { _exit(system(cmd)); } } int rate(const char * rating) { if(playfork && rating != NULL) { if(rating[0] != 'U') set(& track, "rating", rating); switch(rating[0]) { case 'B': kill(playfork, SIGUSR1); enable(INTERRUPTED); return xmlrpc( "banTrack", "ss", value(& track, "creator"), value(& track, "title") ); case 'L': return xmlrpc( "loveTrack", "ss", value(& track, "creator"), value(& track, "title") ); case 'U': return xmlrpc( "unLoveTrack", "ss", value(& track, "creator"), value(& track, "title") ); case 'S': enable(INTERRUPTED); kill(playfork, SIGUSR1); return !0; } } return 0; } char * shellescape(const char * string) { char * escaped; unsigned length = 0, n, size; assert(string != NULL); size = strlen(string) * 2 + 1; escaped = malloc(size); memset(escaped, 0, size); assert(string != NULL); for(n = 0; n < strlen(string); ++n) { if(!isalnum(string[n])) escaped[length++] = '\\'; escaped[length++] = string[n]; escaped[length] = 0; } return escaped; } void quit() { unlink(rcpath("session")); unlinknp(); exit(EXIT_SUCCESS); } void unlinknp(void) { /* Remove now-playing file. */ if(haskey(& rc, "np-file")) { const char * np = value(& rc, "np-file"); if(np != NULL) unlink(np); } } shell-fm-0.7+git20100414/source/radio.c0000644000175000017500000001162111361424251016166 0ustar nachonacho/* Copyright (C) 2006-2010 by Jonas Kramer. Published under the terms of the GNU General Public License (GPL). */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "completion.h" #include "http.h" #include "settings.h" #include "readline.h" #include "history.h" #include "split.h" #include "feeds.h" #include "strary.h" #include "service.h" #include "tag.h" #include "globals.h" extern char ** popular; static int radiocomplete(char *, const unsigned, int); static char ** users = NULL, ** artists = NULL, ** overall = NULL; /* Prompt for a Last.FM radio station URI, providing kind of smart tab completion and a history. No return value, playback of the URI is started directly from here. */ void radioprompt(const char * prompt) { char * url, * decoded = NULL; struct prompt setup = { .prompt = prompt, .line = NULL, .history = uniq(slurp(rcpath("radio-history"))), .callback = radiocomplete, }; /* Get overall top tags. */ overall = overalltags(); /* Get user, friends and neighbors. */ users = neighbors(value(& rc, "username")); users = merge(users, friends(value(& rc, "username")), 0); users = append(users, value(& rc, "username")); /* Get top artists. */ artists = topartists(value(& rc, "username")); /* Read the line. */ url = readline(& setup); /* Free everything. */ purge(users); purge(artists); purge(overall); overall = users = artists = NULL; if(setup.history) purge(setup.history); decode(url, & decoded); station(decoded); free(decoded); } /* Callback for the radio prompt for smart completion of radio URIs. */ int radiocomplete(char * line, const unsigned max, int changed) { unsigned length = strlen(line), nsplt = 0, slash = 0, nres = 0; const char * match; char ** splt, * types [] = { "user", "usertags", "artist", "globaltags", "play", NULL }; /* Remove leading "lastfm://", if found. */ if(!strncasecmp(line, "lastfm://", 9)) { memmove(line, line + 9, 9); memset(line + 9, 0, max - (length -= 9)); } if(length > 0 && line[length - 1] == '/') { slash = !0; changed = !0; } splt = split(line, "/", & nsplt); if(!nsplt) { free(splt); return 0; } switch(nsplt + (slash ? 1 : 0)) { /* First level completions (user, usertags, artists, ...) */ case 1: /* Get next match from first level chunks and fill it in. */ if((match = nextmatch(types, changed ? splt[0] : NULL, & nres)) != NULL) { snprintf(line, max, "%s%s", match, nres == 1 ? "/" : ""); } break; /* Second level completions (user/$USER, globaltags/$TAG, ...) */ case 2: /* For URIs like "{user,usertags}/...". */ if(!strcmp(splt[0], "user") || !strcmp(splt[0], "usertags")) { /* Get next match for 2nd level user chunk (user) and fill it in. */ match = nextmatch(users, changed ? (slash ? "" : splt[1]) : NULL, & nres); if(match) snprintf(line, max, "%s/%s%s", splt[0], match, nres == 1 ? "/" : ""); } /* For URIs like "artist/...". */ else if(!strcmp(splt[0], "artist")) { /* Get next artist match for 2nd level. */ match = nextmatch(artists, changed ? (slash ? "" : splt[1]) : NULL, & nres); if(match) snprintf(line, max, "%s/%s%s", splt[0], match, nres == 1 ? "/" : ""); } /* For URIs like "globaltags/...". Simply tag completion applied here. */ else if(!strcmp(splt[0], "globaltags")) { char * lastchunk = strrchr(line, '/') + 1; popular = overalltags(); tagcomplete(lastchunk, max - (lastchunk - line), changed); purge(popular); } break; /* Third level completions (artist/$ARTIST/fans, ...) */ case 3: /* "user/$USER/{personal,neighbors,loved,recommended,playlist}" */ if(!strcmp(splt[0], "user")) { char * radios [] = { "personal", "neighbours", "loved", "recommended", "playlist", NULL }; /* Get next match for 3rd level chunk and fill it in. */ match = nextmatch(radios, changed ? (slash ? "" : splt[2]) : NULL, NULL); snprintf(line, max, "%s/%s/%s", splt[0], splt[1], match ? match : splt[2]); } /* "artist/$ARTIST/{fans,similarartists}" */ else if(!strcmp(splt[0], "artist")) { char * radios [] = { "fans", "similarartists", NULL }; /* Get next match for 3rd level chunk. */ match = nextmatch(radios, changed ? (slash ? "" : splt[2]) : NULL, NULL); snprintf(line, max, "%s/%s/%s", splt[0], splt[1], match ? match : splt[2]); } /* Simple tag completion for "usertags" stations. */ else if(!strcmp(splt[0], "usertags")) { char * lastchunk = strrchr(line, '/') + 1; popular = overalltags(); tagcomplete(lastchunk, max - (lastchunk - line), changed); purge(popular); } break; } while(nsplt--) free(splt[nsplt]); free(splt); return !0; } shell-fm-0.7+git20100414/source/Makefile0000644000175000017500000000226411361424251016367 0ustar nachonachoMAIN := main.c SOURCE := $(filter-out $(MAIN),$(wildcard *.c)) OBJECT := $(subst .c,.o,$(SOURCE)) DEP := $(subst .c,.d,$(SOURCE)) BINARY := shell-fm LIB := libshellfm.so STATIC := libshellfm.a CFLAGS += -Os -Wall -MD -W -I./include/ ifdef EXTERN_ONLY CFLAGS += -DEXTERN_ONLY else ifeq ($(shell uname -s), OpenBSD) LDLIBS += -lossaudio endif ifeq ($(shell uname -s), NetBSD) LDLIBS += -lossaudio endif ifeq ($(shell uname -s), Darwin) CFLAGS += -D__darwin__ endif CFLAGS += -DLIBAO \ $(shell pkg-config --cflags mad) \ $(shell pkg-config --cflags ao) LDLIBS += $(shell pkg-config --libs mad) \ $(shell pkg-config --libs ao) ifeq ($(shell pkg-config --exists taglib_c && echo 1), 1) CFLAGS += $(shell pkg-config --cflags taglib_c) -DTAGLIB LDLIBS += $(shell pkg-config --libs taglib_c) endif endif .PHONY: clean tags cscope all : $(BINARY) $(LIB) : $(OBJECT) $(CC) -shared -Wl,-soname,$(LIB) -o $(LIB) $(OBJECT) $(STATIC) : $(OBJECT) $(AR) -cvq $(STATIC) $(OBJECT) $(BINARY) : $(STATIC) $(MAIN) $(CC) $(CFLAGS) $(LDFLAGS) $(MAIN) $(STATIC) $(LDLIBS) -o $(BINARY) clean : rm -f $(OBJECT) $(BINARY) $(LIB) $(STATIC) $(DEP) tags cscope : $(MAKE) -C .. $@ sinclude $(DEP) shell-fm-0.7+git20100414/source/playlist.c0000644000175000017500000001017211361424251016731 0ustar nachonacho/* Copyright (C) 2006 by Jonas Kramer Published under the terms of the GNU General Public License (GPL). */ #define _GNU_SOURCE #include #include #include #include #include #include #include "hash.h" #include "http.h" #include "settings.h" #include "service.h" #include "strary.h" #include "util.h" #include "playlist.h" #include "globals.h" #include "interface.h" int expand(struct playlist * list) { char url[512], ** response, * xml = NULL; const char * fmt = "http://ws.audioscrobbler.com/radio/xspf.php" "?sk=%s&discovery=%d&desktop=0"; assert(list != NULL); memset(url, 0, sizeof(url)); snprintf( url, sizeof(url), fmt, value(& data, "session"), !!enabled(DISCOVERY) ); response = fetch(url, NULL, NULL, NULL); if(response != NULL) { int retval; xml = join(response, 0); response = NULL; retval = parsexspf(list, xml); return retval; } return 0; } void trim(char * string){ int offset = 0; while(string[offset] == ' ') offset++; if(offset) memmove(string, string + offset, strlen(string + offset) + 1); } int parsexspf(struct playlist * list, char * xml) { char * ptr = xml; unsigned i, tracks = 0; char * track; assert(list != NULL); assert(xml != NULL); while((track = strcasestr(ptr, "")) != NULL) { struct tracknode * node = NULL; char * next = strcasestr(track + 7, ""), * duration; const char * tags [] = { "location", "title", "album", "creator", "duration", "image", "lastfm:trackauth", }; const char * links [] = { "artistpage", "albumpage", "trackpage", "freeTrackURL", }; if(next) * (next - 1) = 0; node = malloc(sizeof(struct tracknode)); assert(node != NULL); memset(node, 0, sizeof(struct tracknode)); for(i = 0; i < (sizeof(tags) / sizeof(char *)); ++i) { char begin[32] = { 0 }, end[32] = { 0 }; sprintf(begin, "<%s>", tags[i]); sprintf(end, "", tags[i]); if((ptr = strcasestr(track, begin)) != NULL) { char * text = strndup( ptr + strlen(begin), (strcasestr(ptr, end)) - (ptr + strlen(begin)) ); assert(text != NULL); unhtml(text); set(& node->track, tags[i], text); free(text); } } for(i = 0; i < (sizeof(links) / sizeof(char *)); ++i) { char begin[64] = { 0 }; sprintf(begin, "", links[i]); if((ptr = strcasestr(track, begin)) != NULL) { char * text = strndup( ptr + strlen(begin), (strcasestr(ptr, "")) - (ptr + strlen(begin)) ); assert(text != NULL); set(& node->track, links[i], text); free(text); } } if(list->title) { trim(list->title); set(& node->track, "station", list->title); } else { set(& node->track, "station", "Unknown Station"); } duration = strdup(value(& node->track, "duration")); if(duration != NULL) { duration[strlen(duration) - 3] = 0; set(& node->track, "duration", duration); } push(list, node); ++tracks; if(!next) break; ptr = next; } return 1; } void freelist(struct playlist * list) { if(list->title != NULL) free(list->title); while(list->track) shift(list); memset(list, 0, sizeof(struct playlist)); } void push(struct playlist * list, struct tracknode * node) { if(!list->track) list->track = node; else { struct tracknode * last = list->track; while(last->next != NULL) last = last->next; last->next = node; } ++list->left; } void shift(struct playlist * list) { if(list->track) { struct tracknode * node = list->track; list->track = node->next; empty(& node->track); free(node); --(list->left); } } void preview(struct playlist list) { struct tracknode * node; unsigned n = 0; if (list.track != NULL) node = list.track->next; else { puts("No tracks in queue."); return; } if(node == NULL) { puts("No tracks in queue."); } else { puts("Upcoming tracks:"); while(node != NULL) { const char * format; format = haskey(& rc, "preview-format") ? value(& rc, "preview-format") : "%a - %t"; printf("%2d %s\n", n++, meta(format, M_COLORED, & node->track)); node = node->next; } } } shell-fm-0.7+git20100414/source/md5.c0000644000175000017500000002070111361424251015554 0ustar nachonacho /* $Cambridge: exim/exim-src/src/auths/md5.c,v 1.3 2006/02/07 11:19:01 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * *************************************************/ /* Copyright (c) University of Cambridge 1995 - 2006 */ /* See the file NOTICE for conditions of use and distribution. */ #include #include #include "md5.h" typedef struct md5 { unsigned int length; unsigned int abcd[4]; } md5; /************************************************* * Start off a new MD5 computation. * *************************************************/ /* Argument: pointer to md5 storage structure Returns: nothing */ static void md5_start(md5 * base) { base->abcd[0] = 0x67452301; base->abcd[1] = 0xefcdab89; base->abcd[2] = 0x98badcfe; base->abcd[3] = 0x10325476; base->length = 0; } /************************************************* * Process another 64-byte block * *************************************************/ /* This function implements central part of the algorithm which is described in RFC 1321. Arguments: base pointer to md5 storage structure text pointer to next 64 bytes of subject text Returns: nothing */ static void md5_mid(md5 * base, const unsigned char * text) { register unsigned int a = base->abcd[0]; register unsigned int b = base->abcd[1]; register unsigned int c = base->abcd[2]; register unsigned int d = base->abcd[3]; int i; unsigned int X[16]; base->length += 64; /* Load the 64 bytes into a set of working integers, treating them as 32-bit numbers in little-endian order. */ for (i = 0; i < 16; i++) { X[i] = (unsigned int)(text[0]) | ((unsigned int)(text[1]) << 8) | ((unsigned int)(text[2]) << 16) | ((unsigned int)(text[3]) << 24); text += 4; } /* For each round of processing there is a function to be applied. We define it as a macro each time round. */ /*********************************************** * Round 1 * * F(X,Y,Z) = XY v not(X) Z * * a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s) * ***********************************************/ #define OP(a, b, c, d, k, s, ti) \ a += ((b & c) | (~b & d)) + X[k] + (unsigned int)ti; \ a = b + ((a << s) | (a >> (32 - s))) OP(a, b, c, d, 0, 7, 0xd76aa478); OP(d, a, b, c, 1, 12, 0xe8c7b756); OP(c, d, a, b, 2, 17, 0x242070db); OP(b, c, d, a, 3, 22, 0xc1bdceee); OP(a, b, c, d, 4, 7, 0xf57c0faf); OP(d, a, b, c, 5, 12, 0x4787c62a); OP(c, d, a, b, 6, 17, 0xa8304613); OP(b, c, d, a, 7, 22, 0xfd469501); OP(a, b, c, d, 8, 7, 0x698098d8); OP(d, a, b, c, 9, 12, 0x8b44f7af); OP(c, d, a, b, 10, 17, 0xffff5bb1); OP(b, c, d, a, 11, 22, 0x895cd7be); OP(a, b, c, d, 12, 7, 0x6b901122); OP(d, a, b, c, 13, 12, 0xfd987193); OP(c, d, a, b, 14, 17, 0xa679438e); OP(b, c, d, a, 15, 22, 0x49b40821); #undef OP /*********************************************** * Round 2 * * F(X,Y,Z) = XZ v Y not(Z) * * a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s) * ***********************************************/ #define OP(a, b, c, d, k, s, ti) \ a += ((b & d) | (c & ~d)) + X[k] + (unsigned int)ti; \ a = b + ((a << s) | (a >> (32 - s))) OP(a, b, c, d, 1, 5, 0xf61e2562); OP(d, a, b, c, 6, 9, 0xc040b340); OP(c, d, a, b, 11, 14, 0x265e5a51); OP(b, c, d, a, 0, 20, 0xe9b6c7aa); OP(a, b, c, d, 5, 5, 0xd62f105d); OP(d, a, b, c, 10, 9, 0x02441453); OP(c, d, a, b, 15, 14, 0xd8a1e681); OP(b, c, d, a, 4, 20, 0xe7d3fbc8); OP(a, b, c, d, 9, 5, 0x21e1cde6); OP(d, a, b, c, 14, 9, 0xc33707d6); OP(c, d, a, b, 3, 14, 0xf4d50d87); OP(b, c, d, a, 8, 20, 0x455a14ed); OP(a, b, c, d, 13, 5, 0xa9e3e905); OP(d, a, b, c, 2, 9, 0xfcefa3f8); OP(c, d, a, b, 7, 14, 0x676f02d9); OP(b, c, d, a, 12, 20, 0x8d2a4c8a); #undef OP /*********************************************** * Round 3 * * F(X,Y,Z) = X xor Y xor Z * * a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s) * ***********************************************/ #define OP(a, b, c, d, k, s, ti) \ a += (b ^ c ^ d) + X[k] + (unsigned int)ti; \ a = b + ((a << s) | (a >> (32 - s))) OP(a, b, c, d, 5, 4, 0xfffa3942); OP(d, a, b, c, 8, 11, 0x8771f681); OP(c, d, a, b, 11, 16, 0x6d9d6122); OP(b, c, d, a, 14, 23, 0xfde5380c); OP(a, b, c, d, 1, 4, 0xa4beea44); OP(d, a, b, c, 4, 11, 0x4bdecfa9); OP(c, d, a, b, 7, 16, 0xf6bb4b60); OP(b, c, d, a, 10, 23, 0xbebfbc70); OP(a, b, c, d, 13, 4, 0x289b7ec6); OP(d, a, b, c, 0, 11, 0xeaa127fa); OP(c, d, a, b, 3, 16, 0xd4ef3085); OP(b, c, d, a, 6, 23, 0x04881d05); OP(a, b, c, d, 9, 4, 0xd9d4d039); OP(d, a, b, c, 12, 11, 0xe6db99e5); OP(c, d, a, b, 15, 16, 0x1fa27cf8); OP(b, c, d, a, 2, 23, 0xc4ac5665); #undef OP /*********************************************** * Round 4 * * F(X,Y,Z) = Y xor (X v not(Z)) * * a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s) * ***********************************************/ #define OP(a, b, c, d, k, s, ti) \ a += (c ^ (b | ~d)) + X[k] + (unsigned int)ti; \ a = b + ((a << s) | (a >> (32 - s))) OP(a, b, c, d, 0, 6, 0xf4292244); OP(d, a, b, c, 7, 10, 0x432aff97); OP(c, d, a, b, 14, 15, 0xab9423a7); OP(b, c, d, a, 5, 21, 0xfc93a039); OP(a, b, c, d, 12, 6, 0x655b59c3); OP(d, a, b, c, 3, 10, 0x8f0ccc92); OP(c, d, a, b, 10, 15, 0xffeff47d); OP(b, c, d, a, 1, 21, 0x85845dd1); OP(a, b, c, d, 8, 6, 0x6fa87e4f); OP(d, a, b, c, 15, 10, 0xfe2ce6e0); OP(c, d, a, b, 6, 15, 0xa3014314); OP(b, c, d, a, 13, 21, 0x4e0811a1); OP(a, b, c, d, 4, 6, 0xf7537e82); OP(d, a, b, c, 11, 10, 0xbd3af235); OP(c, d, a, b, 2, 15, 0x2ad7d2bb); OP(b, c, d, a, 9, 21, 0xeb86d391); #undef OP /* Add the new values back into the accumulators. */ base->abcd[0] += a; base->abcd[1] += b; base->abcd[2] += c; base->abcd[3] += d; } /************************************************* * Process the final text string * *************************************************/ /* The string may be of any length. It is padded out according to the rules for computing MD5 digests. The final result is then converted to text form and returned. Arguments: base pointer to the md5 storage structure text pointer to the final text vector length length of the final text vector digest points to 16 bytes in which to place the result Returns: nothing */ static void md5_end(md5 *base, const unsigned char *text, int length, unsigned char *digest) { int i; unsigned char work[64]; /* Process in chunks of 64 until we have less than 64 bytes left. */ while (length >= 64) { md5_mid(base, text); text += 64; length -= 64; } /* If the remaining string contains more than 55 bytes, we must pad it out to 64, process it, and then set up the final chunk as 56 bytes of padding. If it has less than 56 bytes, we pad it out to 56 bytes as the final chunk. */ memcpy(work, text, length); work[length] = 0x80; if (length > 55) { memset(work+length+1, 0, 63-length); md5_mid(base, work); base->length -= 64; memset(work, 0, 56); } else { memset(work+length+1, 0, 55-length); } /* The final 8 bytes of the final chunk are a 64-bit representation of the length of the input string *bits*, before padding, low order word first, and low order bytes first in each word. This implementation is designed for short strings, and so operates with a single int counter only. */ length += base->length; /* Total length in bytes */ length <<= 3; /* Total length in bits */ work[56] = (unsigned char) (length & 0xff); work[57] = (unsigned char) ((length >> 8) & 0xff); work[58] = (unsigned char) ((length >> 16) & 0xff); work[59] = (unsigned char) ((length >> 24) & 0xff); memset(work+60, 0, 4); /* Process the final 64-byte chunk */ md5_mid(base, work); /* Pass back the result, low-order byte first in each word. */ for (i = 0; i < 4; i++) { register int x = base->abcd[i]; *digest++ = (unsigned char) (x & 0xff); *digest++ = (unsigned char) ((x >> 8) & 0xff); *digest++ = (unsigned char) ((x >> 16) & 0xff); *digest++ = (unsigned char) ((x >> 24) & 0xff); } } const unsigned char * MD5(const unsigned char * string, unsigned length) { static unsigned char digest[16]; md5 base; md5_start(& base); md5_end(& base, string, length, digest); return digest; } shell-fm-0.7+git20100414/source/tag.c0000644000175000017500000001170211361424251015643 0ustar nachonacho/* Copyright (C) 2006 by Jonas Kramer Copyright (C) 2006 by Bart Trojanowski Published under the terms of the GNU General Public License (GPLv2). */ #define _GNU_SOURCE #include #include #include #include #include #include "settings.h" #include "http.h" #include "split.h" #include "interface.h" #include "completion.h" #include "md5.h" #include "feeds.h" #include "readline.h" #include "tag.h" #include "xmlrpc.h" #include "util.h" #include "globals.h" char ** popular = NULL; void tag(struct hash data) { char key, * tagstring; struct prompt setup = { .prompt = "Tags (comma separated): ", .line = NULL, .history = NULL, .callback = tagcomplete, }; if(!data.content) return; fputs("Tag (a)rtist, a(l)bum, (t)rack or (c)ancel?\n", stderr); while(!strchr("altc", (key = fetchkey(2)))); if(key == 'c') return; popular = merge(toptags(key, data), usertags(value(& rc, "username")), 0); setup.line = oldtags(key, data); assert((tagstring = strdup(readline(& setup))) != NULL); if(setup.line) { free(setup.line); setup.line = NULL; } purge(popular); popular = NULL; sendtag(key, tagstring, data); free(tagstring); } char * oldtags(char key, struct hash track) { unsigned length, x; char * tags = NULL, * url = calloc(512, sizeof(char)), * user = NULL, * artist = NULL, * arg = NULL, * file = NULL, ** resp; assert(url != NULL); switch(key) { case 'a': file = "artisttags.xml"; break; case 'l': file = "albumtags.xml"; break; case 't': default: file = "tracktags.xml"; } encode(value(& track, "creator"), & artist); stripslashes(artist); encode(value(& rc, "username"), & user); length = snprintf( url, 512, "http://ws.audioscrobbler.com/1.0/user/%s/%s?artist=%s", user, file, artist); free(user); free(artist); if(key == 'l') { encode(value(& track, "album"), & arg); /* don't request tags for a not-existing album */ if(!strlen(arg)) { free(arg); return NULL; } stripslashes(arg); length += snprintf(url + length, 512 - length, "&album=%s", arg); } else if(key == 't') { encode(value(& track, "title"), & arg); stripslashes(arg); length += snprintf(url + length, 512 - length, "&track=%s", arg); } if(arg) free(arg); resp = fetch(url, NULL, NULL, NULL); free(url); if(!resp) return NULL; for(x = 0, length = 0; resp[x]; ++x) { char * pbeg = strstr(resp[x], ""), * pend; if(pbeg) { pbeg += 6; pend = strstr(pbeg, ""); if(pend) { char * thistag = strndup(pbeg, pend - pbeg); unsigned nlength = strlen(thistag) + length; assert(thistag != NULL); if(length) ++nlength; tags = realloc(tags, nlength + 1); assert(tags != NULL); sprintf(tags + length, "%s%s", length ? "," : "", thistag); free(thistag); length = nlength; } } } purge(resp); return tags; } void stripslashes(char * string) { unsigned x = 0; while(x < strlen(string)) { if(string[x] == 0x2F) strncpy(string + x, string + x + 1, strlen(string + x + 1)); else ++x; } } int tagcomplete(char * line, const unsigned max, int changed) { unsigned length, nres = 0; int retval = 0; char * ptr = NULL; const char * match = NULL; assert(line != NULL); length = strlen(line); /* Remove spaces at the beginning of the string. */ while(isspace(line[0])) { retval = !0; memmove(line, line + 1, strlen(line + 1)); line[--length] = 0; } /* Remove spaces at the end of the string. */ while((length = strlen(line)) > 0 && isspace(line[length - 1])) { retval = !0; line[--length] = 0; } /* No need for tab completion if there are no popular tags. */ if(!popular || !popular[0]) return retval; /* Get pointer to the beginning of the last tag in tag string. */ if((ptr = strrchr(line, ',')) == NULL) ptr = line; else ++ptr; length = strlen(ptr); if(!length) changed = !0; /* Get next match in list of popular tags. */ if((match = nextmatch(popular, changed ? ptr : NULL, & nres)) != NULL) { snprintf(ptr, max - (ptr - line) - 1, "%s%s", match, nres < 2 ? "," : ""); retval = !0; } return retval; } void sendtag(char key, char * tagstring, struct hash data) { unsigned nsplt = 0; int result = 0; char ** splt = NULL; if(tagstring) { unsigned length = strlen(tagstring); /* remove trailing commas */ while(tagstring[length-1] == ',') tagstring[--length] = 0; splt = split(tagstring, ",\n", & nsplt); } switch(key) { case 'a': result = xmlrpc("tagArtist", "sas", value(& data, "creator"), splt, "set"); break; case 'l': result = xmlrpc( "tagAlbum", "ssas", value(& data, "creator"), value(& data, "album"), splt, "set" ); break; case 't': result = xmlrpc( "tagTrack", "ssas", value(& data, "creator"), value(& data, "title"), splt, "set" ); break; } if(!enabled(QUIET)) puts(result ? "Tagged." : "Sorry, failed."); purge(splt); splt = NULL; } shell-fm-0.7+git20100414/source/sckif.c0000644000175000017500000001367211361424251016177 0ustar nachonacho/* Copyright (C) 2006 by Jonas Kramer Published under the terms of the GNU General Public License (GPL). */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sckif.h" #include "http.h" #include "service.h" #include "interface.h" #include "hash.h" #include "submit.h" #include "getln.h" #include "tag.h" #include "globals.h" struct hash track; static int stcpsck = -1, sunixsck = -1; static int waitread(int, unsigned, unsigned); #define REPLYBUFSIZE 1024 int tcpsock(const char * ip, unsigned short port) { static const int one = 1; struct sockaddr_in host; struct hostent * hostent; if(!ip || !port) return 0; if(-1 == (stcpsck = socket(AF_INET, SOCK_STREAM, PF_UNSPEC))) { fputs("Failed to create socket.\n", stderr); return 0; } if(!(hostent = gethostbyname(ip))) { fprintf(stderr, "Failed to lookup host. %s.\n", hstrerror(h_errno)); return 0; } host.sin_family = PF_INET; host.sin_port = htons(port); host.sin_addr.s_addr = * (unsigned *) hostent->h_addr; if(-1 == setsockopt(stcpsck, SOL_SOCKET, SO_REUSEADDR, & one, sizeof one)) fprintf(stderr, "Couldn't make socket re-usable. %s\n", strerror(errno)); if(bind(stcpsck, (struct sockaddr *) & host, sizeof(struct sockaddr_in))) { fprintf(stderr, "Failed to bind socket. %s.\n", strerror(errno)); return 0; } listen(stcpsck, 2); return !0; } int unixsock(const char * path) { struct sockaddr_un host; if(path == NULL) return 0; if(!access(path, F_OK)) { fprintf(stderr, "%s already existing. UNIX socket not created.\n", path); return 0; } if(-1 == (sunixsck = socket(AF_UNIX, SOCK_STREAM, PF_UNSPEC))) { fputs("Failed to create socket.\n", stderr); return 0; } memset(& host, 0, sizeof(struct sockaddr_un)); strncpy(host.sun_path, path, sizeof(host.sun_path) - 1); host.sun_family = AF_UNIX; if(bind(sunixsck, (struct sockaddr *) & host, sizeof(struct sockaddr_un))) { fprintf(stderr, "Failed to bind socket. %s.\n", strerror(errno)); return 0; } listen(sunixsck, 2); return !0; } void rmsckif(void) { if(stcpsck > 0) { close(stcpsck); stcpsck = -1; } if(sunixsck > 0) { close(sunixsck); sunixsck = -1; } } void sckif(int timeout, int sck) { /* If the given socket is -1, try both sockets, unix and tcp. */ if(sck == -1) { if(stcpsck != -1) sckif(timeout, stcpsck); if(sunixsck != -1) sckif(timeout, sunixsck); return; } signal(SIGPIPE, SIG_IGN); if(waitread(sck, timeout, 0)) { struct sockaddr client; socklen_t scksize = sizeof(struct sockaddr); int csck = accept(sck, & client, & scksize); if(-1 != csck) { if(waitread(csck, 0, 100000)) { FILE * fd = fdopen(csck, "r"); if(fd) { char * line = NULL, * ptr = NULL, reply[REPLYBUFSIZE]; unsigned size = 0; getln(& line, & size, fd); if(line && size > 0 && !ferror(fd)) { if((ptr = strchr(line, 13)) || (ptr = strchr(line, 10))) * ptr = 0; execcmd(line, reply); } if(line) free(line); if(strlen(reply)) { write(csck, reply, strlen(reply)); } fclose(fd); } } shutdown(csck, SHUT_RDWR); close(csck); } } } void execcmd(const char * cmd, char * reply) { char arg[1024], * ptr; unsigned ncmd; const char * known [] = { "play", "love", "ban", "skip", "quit", "info", "pause", "discovery", "tag-artist", "tag-album", "tag-track", "artist-tags", "album-tags", "track-tags", "stop", "volume-up", "volume-down" }; memset(arg, 0, sizeof(arg)); memset(reply, 0, REPLYBUFSIZE); for(ncmd = 0; ncmd < (sizeof(known) / sizeof(char *)); ++ncmd) { if(!strncmp(known[ncmd], cmd, strlen(known[ncmd]))) break; } switch(ncmd) { case (sizeof(known) / sizeof(char *)): strncpy(reply, "ERROR", REPLYBUFSIZE); break; case 0: if(sscanf(cmd, "play %128[a-zA-Z0-9:/_ %,*.-]", arg) == 1) { char * url; decode(arg, & url); station(url); free(url); } break; case 1: rate("L"); break; case 2: rate("B"); break; case 3: rate("S"); break; case 4: quit(); case 5: if(* (cmd + 5)) strncpy(reply, meta(cmd + 5, 0, & track), REPLYBUFSIZE); else if(haskey(& rc, "np-file-format")) strncpy( reply, meta(value(& rc, "np-file-format"), 0, & track), REPLYBUFSIZE ); break; case 6: if(playfork) { if(pausetime) { kill(playfork, SIGCONT); } else { time(& pausetime); kill(playfork, SIGSTOP); } } break; case 7: toggle(DISCOVERY); break; case 8: if(sscanf(cmd, "tag-artist %128s", arg) == 1) sendtag('a', arg, track); break; case 9: if(sscanf(cmd, "tag-album %128s", arg) == 1) sendtag('l', arg, track); break; case 10: if(sscanf(cmd, "tag-track %128s", arg) == 1) sendtag('t', arg, track); break; case 11: if((ptr = oldtags('a', track)) != NULL) { strncpy(reply, ptr, REPLYBUFSIZE); free(ptr); ptr = NULL; } break; case 12: if((ptr = oldtags('l', track)) != NULL) { strncpy(reply, ptr, REPLYBUFSIZE); free(ptr); ptr = NULL; } break; case 13: if((ptr = oldtags('t', track)) != NULL) { strncpy(reply, ptr, REPLYBUFSIZE); free(ptr); ptr = NULL; } break; case 14: if(playfork) { enable(STOPPED); kill(playfork, SIGUSR1); } break; case 15: // Volume + if(volume < MAX_VOLUME) volume += 1; break; case 16: // Volume - if(volume > 0) volume -= 1; break; } } static int waitread(int fd, unsigned sec, unsigned usec) { fd_set readfd; struct timeval tv; FD_ZERO(& readfd); FD_SET(fd, & readfd); tv.tv_sec = sec; tv.tv_usec = usec; return (select(fd + 1, & readfd, NULL, NULL, & tv) > 0); } shell-fm-0.7+git20100414/source/ropen.c0000644000175000017500000000255411361424251016220 0ustar nachonacho/* Copyright (C) 2006 by Jonas Kramer Published under the terms of the GNU General Public License (GPL). */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "ropen.h" FILE * ropen(const char * host, unsigned short port) { int sck; struct hostent * entry; struct sockaddr_in addr; FILE * fd; if(!host || !port) { fputs("No host/port given.\n", stderr); return NULL; } if(!(entry = gethostbyname(host)) || !entry->h_addr) { fprintf(stderr, "%s.\n", hstrerror(h_errno)); return NULL; } if(-1 == (sck = socket(PF_INET, SOCK_STREAM, PF_UNSPEC))) { fprintf(stderr, "Couldn't create socket. %s.\n", strerror(errno)); return NULL; } memset(& addr, 0, sizeof(struct sockaddr_in)); addr.sin_addr.s_addr = * (unsigned *) entry->h_addr; addr.sin_port = htons(port); addr.sin_family = PF_INET; if(connect(sck, (struct sockaddr *) & addr, sizeof(struct sockaddr_in))) { fprintf(stderr, "Connection failed. %s.\n", strerror(errno)); return NULL; } if(!(fd = fdopen(sck, "w+"))) { fprintf(stderr, "Couldn't create file handle. %s.\n", strerror(errno)); shutdown(sck, SHUT_RDWR); close(sck); } return fd; } void fshutdown(FILE ** fd) { shutdown(fileno(* fd), SHUT_RDWR); fclose(* fd); * fd = NULL; } shell-fm-0.7+git20100414/source/service.c0000644000175000017500000001520511361424251016532 0ustar nachonacho/* Copyright (C) 2006 by Jonas Kramer Published under the terms of the GNU General Public License (GPL). */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "hash.h" #include "http.h" #include "play.h" #include "settings.h" #include "md5.h" #include "history.h" #include "service.h" #include "playlist.h" #include "ropen.h" #include "strary.h" #include "sckif.h" #include "globals.h" struct hash data; /* Warning! MUST be bzero'd ASAP or we're all gonna die! */ pid_t playfork = 0; /* PID of the decoding & playing process, if running */ int playpipe = 0; struct playlist playlist; char * currentStation = NULL; int authenticate(const char * username, const char * password) { const unsigned char * md5; char hexmd5[32 + 1] = { 0 }, url[512] = { 0 }, ** response; char * encuser = NULL; unsigned ndigit, i = 0; const char * session, * fmt = "http://ws.audioscrobbler.com/radio/handshake.php" "?version=0.1" "&platform=linux" "&username=%s" "&passwordmd5=%s" "&debug=0" "&language=en"; memset(& data, 0, sizeof(struct hash)); /* create the hash, then convert to ASCII */ md5 = MD5((const unsigned char *) password, strlen(password)); for(ndigit = 0; ndigit < 16; ++ndigit) sprintf(2 * ndigit + hexmd5, "%02x", md5[ndigit]); set(& rc, "password", hexmd5); /* escape username for URL */ encode(username, & encuser); /* put handshake URL together and fetch initial data from server */ snprintf(url, sizeof(url), fmt, encuser, hexmd5); free(encuser); response = fetch(url, NULL, NULL, NULL); if(!response) { fputs("No response.\n", stderr); return 0; } while(response[i]) { char key[64] = { 0 }, val[256] = { 0 }; sscanf(response[i], "%63[^=]=%255[^\r\n]", key, val); set(& data, key, val); free(response[i++]); } free(response); session = value(& data, "session"); if(!session || !strcmp(session, "FAILED")) { fputs("Authentication failed.\n", stderr); unset(& data, "session"); return 0; } return !0; } int station(const char * stationURL) { char url[512] = { 0 }, * encodedURL = NULL, ** response, name[512], * completeURL; unsigned i = 0, retval = !0, regular = !0; const char * fmt; const char * types[4] = {"play", "preview", "track", "playlist"}; delayquit = 0; if(playfork && haskey(& rc, "delay-change")) { if(nextstation) { /* Cancel station change if url is empty or equal to the current station. */ free(nextstation); nextstation = NULL; if(!strlen(stationURL) || !strcmp(stationURL, currentStation)) { puts("Station change cancelled."); return 0; } } /* Do nothing if the station is already played. */ else if(currentStation && !strcmp(currentStation, stationURL)) { return 0; } /* Do nothing if the station URL is empty. */ else if(!strlen(stationURL)) { return 0; } puts("\rDelayed."); nextstation = strdup(stationURL); return 0; } /* Do nothing if the station is already played. */ else if(currentStation && !strcmp(currentStation, stationURL)) { return 0; } freelist(& playlist); if(!haskey(& data, "session")) { fputs("Not authenticated, yet.\n", stderr); return 0; } if(!stationURL || !strlen(stationURL)) return 0; if(!strncasecmp(stationURL, "lastfm://", 9)) { completeURL = strdup(stationURL); stationURL += 9; } else { int size = strlen(stationURL) + 10; completeURL = malloc(size); snprintf(completeURL, size, "lastfm://%s", stationURL); } /* Check if it's a static playlist of tracks or track previews. */ for(i = 0; i < 4; ++i) if(!strncasecmp(types[i], stationURL, strlen(types[i]))) { regular = 0; break; } /* If this is not a special "one-time" stream, it's a regular radio station and we request it using the good old /adjust.php URL. If it's not a regular stream, the reply of this request already is a XSPF playlist we have to parse. */ if(regular) { fmt = "http://ws.audioscrobbler.com/radio/adjust.php?session=%s&url=%s"; } else { fmt = "http://ws.audioscrobbler.com/1.0/webclient/getresourceplaylist.php" "?sk=%s&url=%s&desktop=1"; } encode(completeURL, & encodedURL); snprintf(url, sizeof(url), fmt, value(& data, "session"), encodedURL); free(encodedURL); free(completeURL); if(!(response = fetch(url, NULL, NULL, NULL))) return 0; if(regular) { for(i = 0; response[i]; ++i) { char status[64] = { 0 }; if(sscanf(response[i], "response=%63[^\r\n]", status) > 0) if(!strncmp(status, "FAILED", 6)) retval = 0; if(sscanf(response[i], "stationname=%127[^\r\n]", name) > 0) { if(playlist.title != NULL) free(playlist.title); decode(name, & playlist.title); unhtml(playlist.title); } } purge(response); response = NULL; if(!retval) { printf("Sorry, couldn't set station to %s.\n", stationURL); return 0; } expand(& playlist); } else { char * xml = join(response, 0); response = NULL; freelist(& playlist); if(!parsexspf(& playlist, xml)) retval = 0; free(xml); } enable(CHANGED); histapp(stationURL); if(currentStation) free(currentStation); currentStation = strdup(stationURL); assert(currentStation != NULL); if(retval && playfork) { enable(INTERRUPTED); kill(playfork, SIGUSR1); } return retval; } /* Takes pointer to a playlist, forks off a playback process and tries to play the next track in the playlist. If there's already a playback process, it's killed first (which means the currently played track is skipped). */ int play(struct playlist * list) { unsigned i; int pipefd[2]; char * keys [] = { "creator", "title", "album", "duration", "station", "lastfm:trackauth", "trackpage", "artistpage", "albumpage", "image", "freeTrackURL", }; assert(list != NULL); if(!list->left) return 0; if(playfork) { kill(playfork, SIGUSR1); return !0; } enable(QUIET); empty(& track); for(i = 0; i < (sizeof(keys) / sizeof(char *)); ++i) set(& track, keys[i], value(& list->track->track, keys[i])); if(pipe(pipefd) != 0) return !0; playfork = fork(); if(!playfork) { FILE * fd = NULL; const char * location = value(& list->track->track, "location"); close(pipefd[1]); rmsckif(); subfork = 0; if(location != NULL) { fetch(location, & fd, NULL, NULL); if(fd != NULL) { /* If there was an error, tell the main process about it by sending SIGUSR2. */ if(!playback(fd, pipefd[0])) kill(getppid(), SIGUSR2); close(pipefd[0]); fshutdown(& fd); } } exit(EXIT_SUCCESS); } close(pipefd[0]); if(playpipe != 0) close(playpipe); playpipe = pipefd[1]; return !0; } shell-fm-0.7+git20100414/source/completion.c0000644000175000017500000000270611361424251017245 0ustar nachonacho/* Copyright (C) 2006 by Jonas Kramer Published under the terms of the GNU General Public License (GPL). */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "compatibility.h" #include "completion.h" const char * nextmatch(char ** list, char * needle, unsigned * nres) { static char lastneedle[64] = { 0 }; static int lastmatch = 0, matches = 0, nlen = 0; register unsigned i; if(!list) return NULL; /* Check if a new search is needed or wanted. */ if(needle != NULL) { /* Remember needle for repeated calls. */ memset(lastneedle, 0, sizeof(lastneedle)); strncpy(lastneedle, needle, sizeof(lastneedle) - 1); /* Count number of matches of needle in list. */ nlen = strlen(needle); for(matches = i = 0; list[i] != NULL; ++i) if(!strncasecmp(list[i], needle, nlen)) ++matches; /* Start search at first item. */ i = 0; } else { /* Search for the last given needle. */ needle = lastneedle; nlen = strlen(needle); /* Start search at first item after the last matched one. */ i = lastmatch + 1; } if(nres != NULL) * nres = matches; if(matches) { while(matches > 0) { if(!list[i]) i = 0; if(!strncasecmp(list[i], needle, nlen)) { lastmatch = i; return list[i]; } ++i; } } return NULL; } shell-fm-0.7+git20100414/source/feeds.c0000644000175000017500000001252711361424251016164 0ustar nachonacho/* Copyright (C) 2006 by Jonas Kramer Published under the terms of the GNU General Public License (GPL). */ #define _GNU_SOURCE #include #include #include #include "http.h" #include "feeds.h" #include "tag.h" #include "util.h" #include "strary.h" char ** neighbors(const char * user) { char * encoded = NULL, feed[128], ** names = NULL; unsigned i; assert(user != NULL); encode(user, & encoded); memset(feed, (char) 0, sizeof(feed)); snprintf( feed, sizeof(feed), "http://ws.audioscrobbler.com/1.0/user/%s/neighbours.txt", encoded ); free(encoded); names = cache(feed, "neighbors", 0); if(names != NULL) for(i = 0; names[i] != NULL; ++i) { char * ptr = strchr(names[i], ','); if(ptr != NULL) { unsigned length = strlen(ptr + 1); memmove(names[i], ptr + 1, length); names[i][length] = 0; names[i] = realloc(names[i], sizeof(char) * (length + 1)); assert(names[i] != NULL); } } return names; } char ** topartists(const char * user) { char * encoded = NULL, feed[128], ** names = NULL; unsigned i; assert(user != NULL); encode(user, & encoded); memset(feed, (char) 0, sizeof(feed)); snprintf( feed, sizeof(feed), "http://ws.audioscrobbler.com/1.0/user/%s/topartists.txt", encoded ); free(encoded); names = cache(feed, "top-artists", 0); if(names != NULL) for(i = 0; names[i] != NULL; ++i) { char * ptr = strchr(names[i], ','); if(ptr != NULL) { ptr = strchr(ptr + 1, ','); if(ptr != NULL) { unsigned length = strlen(ptr + 1); memmove(names[i], ptr + 1, length); names[i][length] = 0; names[i] = realloc(names[i], sizeof(char) * (length + 1)); assert(names[i] != NULL); } } } return names; } char ** friends(const char * user) { char * encoded = NULL, feed[128]; assert(user != NULL); encode(user, & encoded); memset(feed, (char) 0, sizeof(feed)); snprintf( feed, sizeof(feed), "http://ws.audioscrobbler.com/1.0/user/%s/friends.txt", encoded ); free(encoded); return cache(feed, "friends", 0); } char ** toptags(char key, struct hash track) { unsigned length, x, count, idx; char ** tags = NULL, url[256], * type = NULL, * artist = NULL, ** resp, cachename[512]; memset(cachename, (char) 0, sizeof(cache)); /* Get artist, album or track tags? */ type = strchr("al", key) ? "artist" : "track"; /* Prepare artist name for use in URL. */ encode(value(& track, "creator"), & artist); stripslashes(artist); /* Prepare URL for album / artist tags. */ memset(url, 0, sizeof(url)); length = snprintf( url, sizeof(url), "http://ws.audioscrobbler.com/1.0/%s/%s/", type, artist ); free(artist); /* Append title if we want track tags. */ if(key == 't') { char * title = NULL; encode(value(& track, "title"), & title); snprintf(cachename, sizeof(cachename), "tags-t-%s--%s", artist, title); stripslashes(title); length += snprintf(url + length, sizeof(url) - length, "%s/", title); free(title); } else { snprintf(cachename, sizeof(cachename), "tags-a-%s", artist); } strncpy(url + length, "toptags.xml", sizeof(url) - length - 1); /* Fetch XML document. */ if((resp = cache(url, cachename, 0)) == NULL) return NULL; /* Count tags in XML. */ for(count = x = 0; resp[x]; ++x) if(strstr(resp[x], "") != NULL) ++count; tags = calloc(count + 1, sizeof(char *)); assert(tags != NULL); tags[count] = NULL; /* Search tag names in XML document and copy them into our list. */ for(x = 0, idx = 0; resp[x] && idx < count; ++x) { char * pbeg = strstr(resp[x], ""); if(pbeg) { char * pend = strstr(pbeg += 6, ""); if(pend) { tags[idx++] = strndup(pbeg, pend - pbeg); assert(tags[idx - 1] != NULL); } } free(resp[x]); } free(resp); return tags; } char ** overalltags(void) { unsigned x, count = 0, idx; const char * url = "http://ws.audioscrobbler.com/1.0/tag/toptags.xml"; char ** tags = NULL, ** resp; if((resp = cache(url, "overall-tags", 0)) == NULL) return NULL; for(x = 0; resp[x]; ++x) if(strstr(resp[x], " #include #include #include "split.h" char ** split(char * string, const char * del, unsigned * pnsplt) { char ** splt = NULL; unsigned nsplt = 0; register char * ptr = string; if(!string) return NULL; while(* ptr) { while(* ptr && strchr(del, * ptr)) ++ptr; if(* ptr) { register unsigned length = 0; splt = realloc(splt, sizeof(char *) * (nsplt + 2)); assert(splt != NULL); splt[nsplt] = calloc(strlen(ptr) + 1, sizeof(char)); assert(splt[nsplt] != NULL); while(* ptr && !strchr(del, * ptr)) splt[nsplt][length++] = * (ptr++); splt[nsplt] = realloc(splt[nsplt], length + 1); splt[++nsplt] = NULL; } } if(pnsplt) * pnsplt = nsplt; return splt; } shell-fm-0.7+git20100414/source/history.c0000644000175000017500000000146711361424251016600 0ustar nachonacho/* Copyright (C) 2007 by Jonas Kramer Published under the terms of the GNU General Public License (GPL). */ #define _GNU_SOURCE #include #include #include #include #include "settings.h" #include "history.h" #include "getln.h" #include "strary.h" #include "util.h" void histapp(const char * radio) { char ** history; const char * path = rcpath("radio-history"); FILE * fd; if(!radio) return; if(!strncasecmp("lastfm://", radio, 9)) radio += 9; history = slurp(path); if((fd = fopen(path, "w")) != NULL) { if(history != NULL) { unsigned i; for(i = 0; history[i] != NULL; ++i) { if(strcmp(history[i], radio)) fprintf(fd, "%s\n", history[i]); } } fprintf(fd, "%s\n", radio); fclose(fd); } if(history != NULL) purge(history); } shell-fm-0.7+git20100414/source/getln.c0000644000175000017500000000241611361424251016203 0ustar nachonacho/* Copyright (C) 2006 by Jonas Kramer Published under the terms of the GNU General Public License (GPL). */ #include #include #include #include "getln.h" /* Reads a line until EOF or a line break occurs, whatever comes first. The first parameter must be a pointer to another pointer which should either be NULL or point to memory area reserved with *alloc. If it's a pointer to a memory area, that memory will be used and resized as needed. In this case, the second argument must point to an unsigned holding the size of the allocated memory area. Otherwise, the memory will be freshly allocated and it's size will be stored in the unsigned pointed to by the second argument. The third argument is a FILE pointer which the line will be read from. */ unsigned getln(char ** ptr, unsigned * size, FILE * fd) { unsigned length = 0; int ch = 0; assert(size != NULL); if(!(* ptr)) * size = 0; while(!feof(fd) && ch != (char) 10) { ch = fgetc(fd); if(ch == -1) ch = 0; if(length + 2 > * size) { * ptr = realloc(* ptr, (* size += 1024)); assert(* ptr != NULL); } (* ptr)[length++] = (char) ch; (* ptr)[length] = 0; } * size = length + 1; * ptr = realloc(* ptr, * size); assert(* ptr != NULL); return length; } shell-fm-0.7+git20100414/source/hash.c0000644000175000017500000000361111361424251016013 0ustar nachonacho/* Copyright (C) 2006 by Jonas Kramer Published under the terms of the GNU General Public License (GPL). */ #define _GNU_SOURCE #include #include #include #include #include "hash.h" void set(struct hash * hash, const char * key, const char * value) { unset(hash, key); hash->content = realloc(hash->content, sizeof(struct pair) * (hash->size + 1)); assert(hash->content != NULL); assert((hash->content[hash->size].key = strdup(key)) != NULL); assert((hash->content[hash->size].value = strdup(value)) != NULL); ++hash->size; } const char * value(struct hash * hash, const char * key) { unsigned index = haskey(hash, key); return index > 0 ? hash->content[index - 1].value : ""; } void unset(struct hash * hash, const char * key) { unsigned index = haskey(hash, key); if(index > 0) { --index; if(hash->content[index].key != NULL) free(hash->content[index].key); if(hash->content[index].value != NULL) free(hash->content[index].value); memcpy( & hash->content[index], & hash->content[--hash->size], sizeof(struct pair)); hash->content = realloc(hash->content, sizeof(struct pair) * hash->size); assert(hash->content != NULL); } } void empty(struct hash * hash) { if(hash != NULL) { if(hash->content != NULL) { while(hash->size > 0) { --hash->size; if(hash->content[hash->size].key != NULL) free(hash->content[hash->size].key); if(hash->content[hash->size].value != NULL) free(hash->content[hash->size].value); } free(hash->content); } memset(hash, 0, sizeof(struct hash)); } } int haskey(struct hash * hash, const char * key) { register unsigned x; assert(hash != NULL); assert(key != NULL); if(!hash->size || !hash->content) return 0; for(x = 0; x < hash->size; ++x) if(hash->content[x].key != NULL && !strcmp(hash->content[x].key, key)) return x + 1; return 0; } shell-fm-0.7+git20100414/source/http.c0000644000175000017500000001637711361424251016064 0ustar nachonacho/* Copyright (C) 2006 by Jonas Kramer Published under the terms of the GNU General Public License (GPL). */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "hash.h" #include "history.h" #include "settings.h" #include "getln.h" #include "http.h" #include "ropen.h" #include "util.h" #include "globals.h" #ifndef USERAGENT #define USERAGENT "Shell.FM/" PACKAGE_VERSION #endif char ** fetch(const char * url, FILE ** pHandle, const char * post, const char * type) { char ** resp = NULL, * host, * file, * port, * status = NULL, * line = NULL; char * connhost; char urlcpy[512 + 1]; unsigned short nport = 80, chunked = 0; unsigned nline = 0, nstatus = 0, size = 0; signed validHead = 0; FILE * fd; int useproxy; const char * headFormat = "%s /%s HTTP/1.1\r\n" "Host: %s\r\n" "User-Agent: " USERAGENT "\r\n" "Cookie: Session=%s\r\n" "Connection: close\r\n"; const char * proxiedheadFormat = "%s http://%s/%s HTTP/1.1\r\n" "Host: %s\r\n" "User-Agent: " USERAGENT "\r\n" "Cookie: Session=%s\r\n"; if(!batch && !enabled(QUIET)) fputs("...\r", stderr); useproxy = haskey(& rc, "proxy"); if(type == NULL) type = "application/x-www-form-urlencoded"; if(pHandle) * pHandle = NULL; strncpy(urlcpy, url, sizeof(urlcpy) - 1); host = & urlcpy[strncmp(urlcpy, "http://", 7) ? 0 : 7]; connhost = host; if(useproxy) { char proxcpy[512 + 1]; memset(proxcpy, (char) 0, sizeof(proxcpy)); strncpy(proxcpy, value(& rc, "proxy"), sizeof(proxcpy) - 1); connhost = & proxcpy[strncmp(proxcpy, "http://", 7) ? 0 : 7]; } port = strchr(connhost, 0x3A); file = strchr(host, 0x2F); fflush(stderr); * file = (char) 0; ++file; if(port && port < file) { char * ptr = NULL; * port = (char) 0; ++port; nport = (unsigned short) strtol(port, & ptr, 10); if(ptr == port) nport = 80; } if(!(fd = ropen(connhost, nport))) return NULL; if(useproxy) fprintf(fd, proxiedheadFormat, post ? "POST" : "GET", host, file ? file : "", host, value(& data, "session")); else fprintf(fd, headFormat, post ? "POST" : "GET", file ? file : "", host, value(& data, "session")); if(post != NULL) { fprintf(fd, "Content-Type: %s\r\n", type); fprintf(fd, "Content-Length: %ld\r\n\r\n%s\r\n", (long) strlen(post), post); } fputs("\r\n", fd); fflush(fd); if(getln(& status, & size, fd) >= 12) validHead = sscanf(status, "HTTP/%*f %u", & nstatus); if(nstatus != 200 && nstatus != 301 && nstatus != 302) { fshutdown(& fd); if(size) { if(validHead != 2) fprintf(stderr, "Invalid HTTP: %s from: %s\n", status, url); else fprintf(stderr, "HTTP Response: %s", status); } freeln(& status, & size); return NULL; } freeln(& status, & size); while(!0) { if(getln(& line, & size, fd) < 3) break; if(!strncasecmp(line, "Transfer-Encoding: chunked", 26)) chunked = !0; if((nstatus == 301 || nstatus == 302) && !strncasecmp(line, "Location: ", 10)) { char newurl[512 + 1]; memset(newurl, 0, sizeof(newurl)); sscanf(line, "Location: %512[^\r\n]", newurl); fshutdown(& fd); return fetch(newurl, pHandle, post, type); } } freeln(& line, & size); if(pHandle) { * pHandle = fd; if(!batch && !enabled(QUIET)) fputs("\r \r", stderr); return NULL; } if(chunked) puts("DEBUG: Chunked!"); while(!feof(fd)) { line = NULL; size = 0; if(getln(& line, & size, fd)) { char * ptr = strchr(line, 10); if(ptr != NULL) * ptr = (char) 0; resp = realloc(resp, (nline + 2) * sizeof(char *)); assert(resp != NULL); resp[nline] = line; resp[++nline] = NULL; } else if(size) free(line); } fshutdown(& fd); if(!batch && !enabled(QUIET)) fputs("\r \r", stderr); return resp; } unsigned encode(const char * orig, char ** encoded) { register unsigned i = 0, x = 0; * encoded = calloc((strlen(orig) * 3) + 1, sizeof(char)); assert(* encoded != NULL); while(i < strlen(orig)) { if(isalnum(orig[i]) || orig[i] == ' ') (* encoded)[x++] = orig[i]; else { snprintf( (* encoded) + x, strlen(orig) * 3 - strlen(* encoded) + 1, "%%%02X", (uint8_t) orig[i] ); x += 3; } ++i; } for(i = 0; i < x; ++i) if((* encoded)[i] == ' ') (* encoded)[i] = '+'; return x; } unsigned decode(const char * orig, char ** decoded) { register unsigned i = 0, x = 0; const unsigned len = strlen(orig); * decoded = calloc(len + 1, sizeof(char)); assert(decoded != NULL); while(i < len) { if(orig[i] != '%') (* decoded)[x] = orig[i]; else { unsigned hex; if(sscanf(orig + i, "%%%02x", & hex) != 1) (* decoded)[x] = orig[i]; else { (* decoded)[x] = (char) hex; i += 2; } } ++i; ++x; } * decoded = realloc(* decoded, (x + 1) * sizeof(char)); assert(decoded != NULL); for(i = 0; i < x; ++i) if((* decoded)[i] == '+') (* decoded)[i] = ' '; return x; } void freeln(char ** line, unsigned * size) { if(size) * size = 0; if(line && * line) { free(* line); * line = NULL; } } void unhtml(char * html) { unsigned i; const char * codes [] = { "&", "&", "<", "<", ">", ">", " ", " ", """, "\"", "'", "\'", }; for(i = 0; i < (sizeof(codes) / sizeof(char *)); i += 2) { unsigned length = strlen(codes[i]); char * ptr; while((ptr = strcasestr(html, codes[i])) != NULL) { * ptr = codes[i + 1][0]; memmove(ptr + 1, ptr + length, strlen(ptr + length)); * (ptr + strlen(ptr) - length + 1) = 0; } } } const char * makeurl(const char * fmt, ...) { static char url[512]; const char * src = fmt; char * ptr = NULL; unsigned pos = 0; va_list argv; va_start(argv, fmt); memset(url, 0, sizeof(url)); while(* src && pos < sizeof(url) - 1) { if(* src != '%') url[pos++] = * (src++); else if(* (src + 1)) { switch(* (++src)) { case '%': url[pos] = '%'; break; case 's': encode(va_arg(argv, char *), & ptr); pos += snprintf(& url[pos], sizeof(url) - pos, "%s", ptr); free(ptr); ptr = NULL; break; case 'i': pos += sprintf(& url[pos], "%d", va_arg(argv, int)); break; case 'u': pos += sprintf(& url[pos], "%u", va_arg(argv, unsigned)); break; } ++src; } } return url; } char ** cache(const char * url, const char * name, int refresh) { time_t expiry = 60 * 60 * 24; char path[4096]; if(haskey(& rc, "expiry")) expiry = atoi(value(& rc, "expiry")); memset(path, (char) 0, sizeof(path)); strncpy(path, rcpath("cache"), sizeof(path)); if(access(path, W_OK | X_OK)) mkdir(path, 0700); snprintf(path, sizeof(path), "%s/%s", rcpath("cache"), name); if(!refresh) { if(access(path, R_OK | W_OK)) refresh = !0; else { time_t now = time(NULL); struct stat status; stat(path, & status); if(status.st_mtime < now - expiry) refresh = !0; } } if(!refresh) return slurp(path); else { char ** data = fetch(url, NULL, NULL, NULL); if(data) { FILE * fd = fopen(path, "w"); if(fd != NULL) { unsigned line = 0; while(data[line]) fprintf(fd, "%s\n", data[line++]); fclose(fd); } else { fputs("Couldn't write cache.\n", stderr); } } return data; } } shell-fm-0.7+git20100414/source/settings.c0000644000175000017500000000336111361424251016732 0ustar nachonacho/* Copyright (C) 2006 by Jonas Kramer Published under the terms of the GNU General Public License (GPL). */ #define _GNU_SOURCE #include #include #include #include #include #include #include "hash.h" #include "getln.h" #include "settings.h" struct hash rc; /* settings read from ~/.shell-fm.rc */ int settings(const char * path, int first) { int retval = !0; FILE * fd = fopen(path, "r"); unsigned nline = 0; char * line = NULL; unsigned size = 0; if(first) memset(& rc, 0, sizeof(struct hash)); if(!fd) return 0; while(!feof(fd)) { char * ptr; if(!getln(& line, & size, fd)) continue; ++nline; if(strlen(line) < 2) continue; ptr = line; while((ptr = strchr(ptr, '#')) != NULL) if(ptr == line || ptr[-1] != '\\') * ptr = (char) 0; else if(ptr[-1] == '\\') { unsigned restlen = strlen(ptr); ptr[-1] = '#'; if(restlen > 1) memmove(ptr, ptr + 1, restlen - 1); ptr[restlen] = (char) 0; } if(strlen(line) > 1) { char key[64] = { 0 }, value[256] = { 0 }; if(sscanf(line, "%63[^= \t] = %255[^\r\n]", key, value) == 2) set(& rc, key, value); else { fprintf(stderr, "%s, line %d invalid.\n", path, nline); retval = 0; } } } fclose(fd); if(size) free(line); return retval; } void makercd(void) { mkdir(rcpath(""), 0755); mkdir(rcpath("cache"), 0755); } const char * rcpath(const char * file) { static char path[4096] = { 0 }; memset(path, 0, sizeof(path)); if(getenv("SHELL_FM_HOME") != NULL) { snprintf(path, sizeof(path), "%s/%s", getenv("SHELL_FM_HOME"), file); } else { snprintf(path, sizeof(path), "%s/.shell-fm/%s", getenv("HOME"), file); } return path; } shell-fm-0.7+git20100414/source/readline.c0000644000175000017500000000620611361424251016656 0ustar nachonacho/* Copyright (C) 2006 by Jonas Kramer Published under the terms of the GNU General Public License (GPL). */ #define _GNU_SOURCE #include #include #include #include #include #include "readline.h" #include "interface.h" static void delete(unsigned); char * readline(struct prompt * setup) { int eoln = 0, seq = 0, histsize = 0, index = -1, changed = !0; static char line[1024]; unsigned length = 0; assert(setup != NULL); /* Print prompt if present. */ if(setup->prompt) fputs(setup->prompt, stderr); /* Initialize line (empty or with preset text if given). */ memset(line, 0, sizeof(line)); if(setup->line) { strncpy(line, setup->line, sizeof(line) - 1); length = strlen(line); fputs(line, stderr); } /* Count items in history. */ for(histsize = 0; setup->history && setup->history[histsize]; ++histsize); index = histsize; canon(0); while(!eoln) { int key = fgetc(stdin); switch(key) { case 8: /* Backspace. */ case 127: /* Delete. */ /* We don't support moving the cursor from the end of the line * so handle these the same. */ if(length > 0) { delete(1); line[--length] = 0; changed = !0; } break; case 9: /* Tab. */ /* Call the callback function for completion if present. */ if(setup->callback != NULL) { if(setup->callback(line, sizeof(line), changed)) { delete(length); length = strlen(line); memset(line + length, 0, sizeof(line) - length); fprintf(stderr, "\r%s%s", setup->prompt, line); } changed = 0; } break; case 4: /* EOF (^D) */ case 10: /* Line break. */ case 13: /* Carriage return (who knows...) */ eoln = !0; break; case 23: /* ^W */ if(length > 0) { int alpha = isalpha(line[length - 1]); while(length > 0 && isalpha(line[length - 1]) == alpha) { line[--length] = 0; delete(1); } changed = !0; } break; case 27: /* Escape. */ ++seq; changed = !0; break; default: changed = !0; if(seq > 0) { if(seq < 2) ++seq; else { seq = 0; switch(key) { case 65: /* Up. */ case 66: /* Down. */ if(histsize) { if(key == 66 && index < histsize - 1) ++index; if(key == 65 && index > -1) --index; delete(length); memset(line, 0, length); length = 0; if(index > -1 && index < histsize) { strncpy( line, setup->history[index], sizeof(line) - 1 ); length = strlen(line); fputs(line, stderr); } } break; } } } else { if(length < sizeof(line)) { line[length++] = key; fputc(key, stderr); } } } } canon(!0); fputc(10, stderr); return line; } void canon(int enable) { struct termios term; tcgetattr(fileno(stdin), & term); term.c_lflag = enable ? term.c_lflag | ICANON | ECHO : term.c_lflag & ~(ICANON | ECHO); tcsetattr(fileno(stdin), TCSANOW, & term); } static void delete(unsigned n) { while(n--) fputs("\b \b", stderr); } shell-fm-0.7+git20100414/source/bookmark.c0000644000175000017500000000441511361424251016700 0ustar nachonacho/* Copyright (C) 2006 by Jonas Kramer Published under the terms of the GNU General Public License (GPL). */ #define _GNU_SOURCE #include #include #include #include #include #include "settings.h" #include "bookmark.h" #include "getln.h" #include "util.h" #include "strary.h" #include "globals.h" int cmpdigit(const void *, const void *); /* Save a stream URL with the given number as bookmark. */ void setmark(const char * streamURL, int n) { FILE * fd; char * bookmarks[10] = { NULL, }; if(!streamURL || n < 0 || n > 9) return; if((fd = fopen(rcpath("bookmarks"), "r"))) { while(!feof(fd)) { char * line = NULL; unsigned size = 0, length; int x = 0; length = getln(& line, & size, fd); if(line && length > 4) { if(sscanf(line, "%d = ", & x) == 1 && !bookmarks[x]) { bookmarks[x] = strdup(line + 4); assert(bookmarks[x] != NULL); } free(line); } } fclose(fd); } bookmarks[n] = strdup(streamURL); assert(bookmarks[n] != NULL); if((fd = fopen(rcpath("bookmarks"), "w"))) { int i; for(i = 0; i < 10; ++i) if(bookmarks[i]) { char * ptr = strchr(bookmarks[i], 10); if(ptr != NULL) * ptr = 0; fprintf(fd, "%d = %s\n", i, bookmarks[i]); free(bookmarks[i]); } fclose(fd); if(!enabled(QUIET)) puts("Bookmark saved."); } } /* Get bookmarked stream. */ char * getmark(int n) { FILE * fd = fopen(rcpath("bookmarks"), "r"); char * streamURL = NULL; if(!fd) return NULL; while(!streamURL && !feof(fd)) { char * line = NULL; unsigned size = 0, length; int x; length = getln(& line, & size, fd); if(line && length > 4) if(sscanf(line, "%d = ", & x) == 1 && x == n) { char * ptr = strchr(line, 10); if(ptr != NULL) * ptr = 0; streamURL = strdup(line + 4); assert(streamURL != NULL); } if(size && line) free(line); } fclose(fd); return streamURL; } void printmarks(void) { char ** list = slurp(rcpath("bookmarks")); unsigned n; if(list == NULL) { puts("No bookmarks."); return; } qsort(list, count(list), sizeof(char *), cmpdigit); for(n = 0; list[n] != NULL; ++n) puts(list[n]); purge(list); } int cmpdigit(const void * a, const void * b) { return (** (char **) a) - (** (char **) b); } shell-fm-0.7+git20100414/source/main.c0000644000175000017500000003311711361424251016020 0ustar nachonacho/* Copyright (C) 2006-2010 by Jonas Kramer Published under the terms of the GNU General Public License (GPL). */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "hash.h" #include "service.h" #include "interface.h" #include "settings.h" #include "autoban.h" #include "sckif.h" #include "playlist.h" #include "submit.h" #include "readline.h" #include "radio.h" #include "globals.h" #ifndef PATH_MAX #define PATH_MAX 4096 #endif #ifdef __NetBSD__ # ifndef WCONTINUED # define WCONTINUED 0 /* not available on NetBSD */ # endif # ifndef WIFCONTINUED # define WIFCONTINUED(x) ((x) == 0x13) /* SIGCONT */ # endif #endif #if !defined(WCONTINUED) || !defined(WIFCONTINUED) # undef WCONTINUED # define WCONTINUED 0 # undef WIFCONTINUED # define WIFCONTINUED(wstat) (0) #endif unsigned flags = RTP; time_t changeTime = 0, pausetime = 0; char * nextstation = NULL; int batch = 0, error = 0, delayquit = 0; static void cleanup(void); static void forcequit(int); static void help(const char *, int); static void playsig(int); static void stopsig(int); pid_t ppid = 0; int main(int argc, char ** argv) { int option, nerror = 0, background = 0, haveSocket = 0; time_t pauselength = 0; char * proxy; opterr = 0; /* Create directories. */ makercd(); /* Load settings from ~/.shell-fm/shell-fm.rc. */ settings(rcpath("shell-fm.rc"), !0); /* Enable discovery by default if it is set in configuration. */ if(haskey(& rc, "discovery")) enable(DISCOVERY); /* Disable RTP if option "no-rtp" is set to something. */ if(haskey(& rc, "no-rtp")) disable(RTP); /* If "daemon" is set in the configuration, enable daemon mode by default. */ if(haskey(& rc, "daemon")) background = !0; /* Get proxy environment variable. */ if((proxy = getenv("http_proxy")) != NULL) set(& rc, "proxy", proxy); /* Parse through command line options. */ while(-1 != (option = getopt(argc, argv, ":dbhi:p:D:y:"))) switch(option) { case 'd': /* Daemonize. */ background = !background; break; case 'i': /* IP to bind network interface to. */ set(& rc, "bind", optarg); break; case 'p': /* Port to listen on. */ if(atoi(optarg)) set(& rc, "port", optarg); else { fputs("Invalid port.\n", stderr); ++nerror; } break; case 'b': /* Batch mode */ batch = !0; break; case 'D': /* Path to audio device file. */ set(& rc, "device", optarg); break; case 'y': /* Proxy address. */ set(& rc, "proxy", optarg); break; case 'h': /* Print help text and exit. */ help(argv[0], 0); break; case ':': fprintf(stderr, "Missing argument for option -%c.\n\n", optopt); ++nerror; break; case '?': default: fprintf(stderr, "Unknown option -%c.\n", optopt); ++nerror; break; } /* The next argument, if present, is the lastfm:// URL we want to play. */ if(optind > 0 && optind < argc && argv[optind]) { const char * station = argv[optind]; set(& rc, "default-radio", station); } if(nerror) help(argv[0], EXIT_FAILURE); #ifdef EXTERN_ONLY /* Abort if EXTERN_ONLY is defined and no extern command is present */ if(!haskey(& rc, "extern")) { fputs("Can't continue without extern command.\n", stderr); exit(EXIT_FAILURE); } #else #ifndef LIBAO if(!haskey(& rc, "device")) set(& rc, "device", "/dev/audio"); #endif #endif if(!background) { puts("Shell.FM v" PACKAGE_VERSION ", (C) 2006-2010 by Jonas Kramer"); puts("Published under the terms of the GNU General Public License (GPL)."); #ifndef TUXBOX puts("\nPress ? for help.\n"); #else puts("Compiled for the use with Shell.FM Wrapper.\n"); #endif fflush(stdout); } /* Open a port so Shell.FM can be controlled over network. */ if(haskey(& rc, "bind")) { int port = 54311; if(haskey(& rc, "port")) port = atoi(value(& rc, "port")); if(tcpsock(value(& rc, "bind"), (unsigned short) port)) haveSocket = !0; } /* Open a UNIX socket for local "remote" control. */ if(haskey(& rc, "unix") && unixsock(value(& rc, "unix"))) haveSocket = !0; /* We can't daemonize if there's no possibility left to control Shell.FM. */ if(background && !haveSocket) { fputs("Can't daemonize without control socket.\n", stderr); exit(EXIT_FAILURE); } /* Ask for username/password if they weren't specified in the .rc file. */ if(!haskey(& rc, "password")) { char * password; if(!haskey(& rc, "username")) { char username[256] = { 0 }; struct prompt prompt = { .prompt = "Login: ", .line = getenv("USER"), .history = NULL, .callback = NULL, }; strncpy(username, readline(& prompt), 255); set(& rc, "username", username); } if(!(password = getpass("Password: "))) exit(EXIT_FAILURE); set(& rc, "password", password); } memset(& data, 0, sizeof(struct hash)); memset(& track, 0, sizeof(struct hash)); memset(& playlist, 0, sizeof(struct playlist)); /* Fork to background. */ if(background) { int null; pid_t pid = fork(); if(pid == -1) { fputs("Failed to daemonize.\n", stderr); exit(EXIT_FAILURE); } else if(pid) { exit(EXIT_SUCCESS); } enable(QUIET); /* Detach from TTY */ setsid(); pid = fork(); if(pid > 0) exit(EXIT_SUCCESS); /* Close stdin out and err */ close(0); close(1); close(2); /* Redirect stdin and out to /dev/null */ null = open("/dev/null", O_RDWR); dup(null); dup(null); } ppid = getpid(); atexit(cleanup); loadqueue(!0); /* Set up signal handlers for communication with the playback process. */ signal(SIGINT, forcequit); /* SIGUSR2 from playfork means it detected an error. */ signal(SIGUSR2, playsig); /* Catch SIGTSTP to set pausetime when user suspends us with ^Z. */ signal(SIGTSTP, stopsig); /* Authenticate to the Last.FM server. */ if(!authenticate(value(& rc, "username"), value(& rc, "password"))) exit(EXIT_FAILURE); /* Store session key for use by external tools. */ if(haskey(& data, "session")) { FILE * fd = fopen(rcpath("session"), "w"); if(fd) { fprintf(fd, "%s\n", value(& data, "session")); fclose(fd); } } /* Play default radio, if specified. */ if(haskey(& rc, "default-radio")) station(value(& rc, "default-radio")); else if(!background) radioprompt("radio url> "); /* The main loop. */ while(!0) { pid_t child; int status, playnext = 0; /* Check if anything died (submissions fork or playback fork). */ while((child = waitpid(-1, & status, WNOHANG | WUNTRACED | WCONTINUED)) > 0) { if(child == subfork) subdead(WEXITSTATUS(status)); else if(child == playfork) { if(WIFSTOPPED(status)) { /* time(& pausetime); */ } else { if(WIFCONTINUED(status)) { signal(SIGTSTP, stopsig); if(pausetime != 0) { pauselength += time(NULL) - pausetime; } } else { playnext = !0; unlinknp(); if(delayquit) { quit(); } } pausetime = 0; } } } /* Check if the playback process died. If so, submit the data of the last played track. Then check if there are tracks left in the playlist; if not, try to refresh the list and stop the stream if there are no new tracks to fetch. Also handle user stopping the stream here. We need to check for playnext != 0 before handling enabled(STOPPED) anyway, otherwise playfork would still be running. */ if(playnext) { playfork = 0; if(enabled(RTP)) { unsigned duration, played, minimum; duration = atoi(value(& track, "duration")); played = time(NULL) - changeTime - pauselength; /* Allow user to specify minimum playback length (min. 50%). */ if(haskey(& rc, "minimum")) { unsigned percent = atoi(value(& rc, "minimum")); if(percent < 50) percent = 50; minimum = duration * percent / 100; } else { minimum = duration / 2; } if(duration >= 30 && (played >= 240 || played > minimum)) enqueue(& track); } submit(value(& rc, "username"), value(& rc, "password")); /* Check if the user stopped the stream. */ if(enabled(STOPPED) || error) { freelist(& playlist); empty(& track); if(error) { fputs("Playback stopped with an error.\n", stderr); error = 0; } disable(STOPPED); disable(CHANGED); continue; } shift(& playlist); } if(playnext || enabled(CHANGED)) { if(nextstation != NULL) { playnext = 0; disable(CHANGED); if(!station(nextstation)) enable(STOPPED); free(nextstation); nextstation = NULL; } if(!enabled(STOPPED) && !playlist.left) { expand(& playlist); if(!playlist.left) { puts("No tracks left."); playnext = 0; disable(CHANGED); continue; } } if(!playfork) { /* If there was a track played before, and there is a gap configured, wait that many seconds before playing the next track. */ if(playnext && !enabled(INTERRUPTED) && haskey(& rc, "gap")) { int gap = atoi(value(& rc, "gap")); if(gap > 0) sleep(gap); } disable(INTERRUPTED); if(play(& playlist)) { time(& changeTime); pauselength = 0; set(& track, "stationURL", currentStation); /* Print what's currently played. (Ondrej Novy) */ if(!background) { if(enabled(CHANGED) && playlist.left > 0) { puts(meta("Receiving %s.", M_COLORED, & track)); disable(CHANGED); } if(haskey(& rc, "title-format")) printf("%s\n", meta(value(& rc, "title-format"), M_COLORED, & track)); else printf("%s\n", meta("Now playing \"%t\" by %a.", M_COLORED, & track)); } /* Write track data into a file. */ if(haskey(& rc, "np-file") && haskey(& rc, "np-file-format")) { signed np; const char * file = value(& rc, "np-file"), * fmt = value(& rc, "np-file-format"); unlink(file); if(-1 != (np = open(file, O_WRONLY | O_CREAT, 0644))) { const char * output = meta(fmt, 0, & track); if(output) write(np, output, strlen(output)); close(np); } } if(haskey(& rc, "screen-format")) { const char * term = getenv("TERM"); if(term != NULL && !strncmp(term, "screen", 6)) { const char * output = meta(value(& rc, "screen-format"), 0, & track); printf("\x1Bk%s\x1B\\", output); } } if(haskey(& rc, "term-format")) { const char * output = meta(value(& rc, "term-format"), 0, & track); printf("\x1B]2;%s\a", output); } /* Run a command with our track data. */ if(haskey(& rc, "np-unescaped-cmd")) run(meta(value(& rc, "np-unescaped-cmd"), 0, & track)); if(haskey(& rc, "np-cmd")) run(meta(value(& rc, "np-cmd"), M_SHELLESC, & track)); } else changeTime = 0; } if(banned(value(& track, "creator"))) { puts(meta("%a is banned.", M_COLORED, & track)); rate("B"); fflush(stdout); } } playnext = 0; if(playfork && changeTime && haskey(& track, "duration") && !pausetime) { unsigned duration; signed remain; char remstr[32]; duration = atoi(value(& track, "duration")); remain = (changeTime + duration) - time(NULL) + pauselength; snprintf(remstr, sizeof(remstr), "%d", remain); set(& track, "remain", remstr); if(!background) { printf( "%c%02d:%02d%c", remain < 0 ? '-' : ' ', (remain >= 0) ? (remain / 60) : (-remain / 60), (remain >= 0) ? (remain % 60) : (-remain % 60), batch ? '\n' : '\r' ); fflush(stdout); } } interface(!background); if(haveSocket) sckif(background ? 2 : 0, -1); } return 0; } static void help(const char * argv0, int errorCode) { fprintf(stderr, "Shell.FM v" PACKAGE_VERSION ", (C) 2006-2010 by Jonas Kramer\n" "Published under the terms of the GNU General Public License (GPL).\n" "\n" "%s [options] [lastfm://url]\n" "\n" " -d daemon mode.\n" " -i address to listen on.\n" " -p port to listen on.\n" " -b batch mode.\n" " -D device to play on.\n" " -y proxy url to connect through.\n" " -h this help.\n", argv0 ); exit(errorCode); } static void cleanup(void) { canon(!0); rmsckif(); if(haskey(& rc, "unix") && getpid() == ppid) unlink(value(& rc, "unix")); empty(& data); empty(& rc); empty(& track); freelist(& playlist); if(currentStation) { free(currentStation); currentStation = NULL; } if(subfork) waitpid(subfork, NULL, 0); dumpqueue(!0); /* Clean cache. */ if(!access(rcpath("cache"), R_OK | W_OK | X_OK)) { const char * cache = rcpath("cache"); DIR * directory = opendir(cache); if(directory != NULL) { time_t expiry = 24 * 60 * 60; /* Expiration after 24h. */ struct dirent * entry; struct stat status; char path[PATH_MAX]; if(haskey(& rc, "expiry")) expiry = atoi(value(& rc, "expiry")); while((entry = readdir(directory)) != NULL) { snprintf(path, sizeof(path), "%s/%s", cache, entry->d_name); if(!stat(path, & status)) { if(status.st_mtime < (time(NULL) - expiry)) { unlink(path); } } } closedir(directory); } } if(playfork) kill(playfork, SIGUSR1); } static void forcequit(int sig) { if(sig == SIGINT) { fputs("Try to press Q next time you want to quit.\n", stderr); exit(EXIT_FAILURE); } } static void playsig(int sig) { if(sig == SIGUSR2) { error = !0; enable(INTERRUPTED); } } static void stopsig(int sig) { if(sig == SIGTSTP) { time(& pausetime); signal(SIGTSTP, SIG_DFL); raise(SIGTSTP); } } shell-fm-0.7+git20100414/source/pipe.c0000644000175000017500000000144411361424251016027 0ustar nachonacho/* Copyright (C) 2006 by Jonas Kramer Published under the terms of the GNU General Public License (GPL). */ #define _GNU_SOURCE #include #include #include #include #include #include #include "interface.h" #include "pipe.h" FILE * openpipe(const char * cmd, pid_t * cpid) { pid_t pid; int fd[2] = { 0 }; assert(cmd); if(-1 == pipe(fd)) { fprintf(stderr, "Couldn't create pipe. %s.\n", strerror(errno)); return NULL; } pid = fork(); if(pid == -1) { fprintf(stderr, "Fork failed. %s.\n", strerror(errno)); close(fd[1]); close(fd[0]); return NULL; } if(!pid) { close(fd[1]); dup2(fd[0], 0); execl("/bin/sh", "sh", "-c", cmd, NULL); } if(cpid != NULL) * cpid = pid; return fdopen(fd[1], "w"); } shell-fm-0.7+git20100414/source/play.c0000644000175000017500000002726411361424251016047 0ustar nachonacho/* Copyright (C) 2006 by Jonas Kramer Published under the terms of the GNU General Public License (GPL). Some code of this is shamelessly stolen from the libmad-example player minimad by Underbit Technologies. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #ifndef EXTERN_ONLY #include #ifdef LIBAO #include #else #if (defined(__NetBSD__) || defined(__OpenBSD__)) #include #endif #if (defined(__FreeBSD__) || defined(__FreeBSD_kernel__)) #include #endif #ifdef __linux__ #include #endif #endif #ifdef TAGLIB #include #endif #endif #include "settings.h" #include "pipe.h" #include "play.h" #include "interface.h" #include "globals.h" #include "util.h" #ifndef EXTERN_ONLY struct stream { FILE * streamfd; #ifdef LIBAO int driver_id; ao_device * device; ao_sample_format fmt; #else int audiofd; #endif pid_t parent; FILE * dump; char * finpath; // final destination char * tmppath; // streaming to file int pipefd; int timeout; int preload; }; #endif #define BUFSIZE (32*1024) #ifndef EXTERN_ONLY static enum mad_flow input(void *, struct mad_stream *); static enum mad_flow output(void *, const struct mad_header *, struct mad_pcm *); inline signed scale(mad_fixed_t); static int timed_read(int, unsigned char *, int, int); #endif int killed = 0; unsigned char volume = MAX_VOLUME; static void sighand(int); /* * Taken from *BSD code * (usr.bin/patch/mkpath.c) */ int mkpath(char *path) { struct stat sb; char *slash; int done = 0; slash = path; while (!done) { slash += strspn(slash, "/"); slash += strcspn(slash, "/"); done = (*slash == '\0'); *slash = '\0'; if (stat(path, &sb)) { if (errno != ENOENT || (mkdir(path, 0777) && errno != EEXIST)) return (-1); } else if (!S_ISDIR(sb.st_mode)) return (-1); *slash = '/'; } return (0); } int playback(FILE * streamfd, int pipefd) { killed = 0; signal(SIGUSR1, sighand); #ifndef EXTERN_ONLY if(!haskey(& rc, "extern")) { const char * freetrack = NULL; struct stream data; struct mad_decoder dec; #ifdef LIBAO static int ao_initialized = 0; if(!ao_initialized) { ao_initialize(); ao_initialized = !0; } #else unsigned arg; int fd; #endif memset(& data, 0, sizeof(struct stream)); /* Check if there's a stream timeout configured and set it up for timed reads later. */ data.timeout = -1; if(haskey(& rc, "stream-timeout")) { const char * timeout = value(& rc, "stream-timeout"); data.timeout = atoi(timeout); if(data.timeout <= 0) { if(data.timeout < 0) fputs("Invalid stream-timeout.\n", stderr); data.timeout = -1; } } data.streamfd = streamfd; data.parent = getppid(); data.pipefd = pipefd; fcntl(pipefd, F_SETFL, O_NONBLOCK); #ifdef LIBAO data.driver_id = ao_default_driver_id(); if(-1 == data.driver_id) { fputs("Unable to find any usable output device!\n", stderr); return 0; } data.fmt.bits = 16; data.fmt.rate = 44100; data.fmt.channels = 2; data.fmt.byte_format = AO_FMT_NATIVE; data.device = ao_open_live(data.driver_id,&data.fmt,NULL); if(NULL == data.device) { fprintf(stderr, "Unable to open device. %s.\n", strerror(errno)); return 0; } #else data.audiofd = fd = open(value(& rc, "device"), O_WRONLY); if(-1 == data.audiofd) { fprintf( stderr, "Couldn't open %s! %s.\n", value(& rc, "device"), strerror(errno) ); return 0; } arg = 16; /* 16 bits */ ioctl(data.audiofd, SOUND_PCM_WRITE_BITS, & arg); #endif freetrack = value(& track, "freeTrackURL"); if(freetrack && strlen(freetrack) > 0 && haskey(& rc, "download")) { char * dnam; int rv; data.finpath = strdup(meta(value(& rc, "download"), M_RELAXPATH, & track)); assert(data.finpath != NULL); data.tmppath = strjoin("", data.finpath, ".streaming", NULL); assert(data.tmppath != NULL); dnam = strdup(data.tmppath); rv = dnam ? mkpath(dirname(dnam)) : -1; free(dnam); if(access(data.tmppath, R_OK) == -1) { data.dump = (rv == 0) ? fopen(data.tmppath, "w") : NULL; if(!data.dump) fprintf(stderr, "Can't write download to %s.\n", data.tmppath); } else { data.dump = NULL; } } mad_decoder_init(& dec, & data, input, NULL, NULL, output, NULL, NULL); mad_decoder_run(& dec, MAD_DECODER_MODE_SYNC); #ifndef LIBAO close(fd); #endif mad_decoder_finish(& dec); if(data.dump) { fclose(data.dump); if(killed) { unlink(data.tmppath); } else { int rv; #ifdef TAGLIB TagLib_File *tagme = taglib_file_new(data.tmppath); if(tagme != NULL) { TagLib_Tag *tag = taglib_file_tag(tagme); taglib_tag_set_title(tag, value(&track, "title")); taglib_tag_set_artist(tag, value(&track, "creator")); taglib_tag_set_album(tag, value(&track, "album")); taglib_file_save(tagme); taglib_file_free(tagme); } #endif if(haskey(& rc, "pp-cmd")) { const char *ppcmd = value(& rc, "pp-cmd"); size_t ppcmdlen = strlen(ppcmd); char *path = shellescape(data.tmppath); assert(path != NULL); size_t pathlen = strlen(path); char *command = malloc(ppcmdlen + pathlen + 2); assert(command != NULL); memcpy(command, ppcmd, ppcmdlen); command[ppcmdlen] = ' '; memcpy(command + ppcmdlen + 1, path, pathlen); command[ppcmdlen + 1 + pathlen] = 0; run(command); free(path); free(command); } rv = rename(data.tmppath, data.finpath); if (rv == -1) fprintf(stderr, "Can't rename %s to %s\n", data.tmppath, data.finpath); } free(data.tmppath); free(data.finpath); } } else #endif { pid_t ppid = getppid(), cpid = 0; const char * cmd = meta(value(& rc, "extern"), M_SHELLESC, & track); FILE * ext = openpipe(cmd, & cpid); unsigned char * buf; if(!ext) { fprintf(stderr, "Failed to execute external player (%s). %s.\n", cmd, strerror(errno)); return 0; } if(!(buf = calloc(BUFSIZE + 1, sizeof(unsigned char)))) { fputs("Couldn't allocate enough memory for input buffer.\n", stderr); fclose(ext); return 0; } while(!feof(streamfd)) { signed nbyte = fread(buf, sizeof(unsigned char), BUFSIZE, streamfd); if(nbyte > 0) { fwrite(buf, sizeof(unsigned char), nbyte, ext); fflush(ext); } if(kill(ppid, 0) == -1 && errno == ESRCH) break; if(killed) break; } free(buf); fclose(ext); waitpid(cpid, NULL, 0); } return !0; } static void sighand(int sig) { if(sig == SIGUSR1) killed = !0; } #ifndef EXTERN_ONLY static enum mad_flow input(void * data, struct mad_stream * stream) { static unsigned char buf[BUFSIZE + 1] = { 0 }; struct stream * ptr = (struct stream *) data; static int nbyte = 0; int remnbyte = 0; if(feof(ptr->streamfd)) return MAD_FLOW_STOP; if(stream->next_frame) { remnbyte = (unsigned) (& buf[nbyte] - stream->next_frame); memmove(buf, stream->next_frame, remnbyte); } if(ptr->preload) { nbyte = timed_read(fileno(ptr->streamfd), buf + remnbyte, BUFSIZE - remnbyte, ptr->timeout); if(nbyte == -1) { fputs("Stream timed out.\n", stderr); } else if(ptr->dump) fwrite(buf + remnbyte, sizeof(buf[0]), nbyte, ptr->dump); } else { while(nbyte < BUFSIZE) { int retval = timed_read(fileno(ptr->streamfd), buf + nbyte, BUFSIZE - nbyte, ptr->timeout); if(retval <= 0) break; if(ptr->dump) fwrite(buf + nbyte, sizeof(buf[0]), retval, ptr->dump); nbyte += retval; } if(!nbyte) { fputs("Stream timed out while trying to preload data.\n", stderr); } ptr->preload = !0; } if(nbyte <= 0) return MAD_FLOW_STOP; nbyte += remnbyte; mad_stream_buffer(stream, (unsigned char *) buf, nbyte); if(kill(ptr->parent, 0) == -1 && errno == ESRCH) { fclose(ptr->streamfd); killed = !0; return MAD_FLOW_STOP; } if(killed) return MAD_FLOW_STOP; return MAD_FLOW_CONTINUE; } static void read_from_pipe(int pipefd) { char readchar; ssize_t readcount; while((readcount = read(pipefd, &readchar, 1)) > 0) { switch(readchar) { // update this process's copy of the volume level case '+': if(volume < MAX_VOLUME) volume += 1; break; case '-': if(volume > 0) volume -= 1; break; } } } #ifdef LIBAO static enum mad_flow output( void * data, const struct mad_header * head, struct mad_pcm * pcm) { struct stream * ptr = (struct stream *) data; unsigned nchan = pcm->channels, rate = pcm->samplerate; register unsigned nsample = pcm->length; mad_fixed_t * left = pcm->samples[0], * right = pcm->samples[1]; char *stream, *stream_ptr; head = NULL; if((signed) rate != ptr->fmt.rate || (signed) nchan != ptr->fmt.channels) { ptr->fmt.rate = rate; ptr->fmt.channels = nchan; if(ptr->device != NULL) ao_close(ptr->device); ptr->device = ao_open_live(ptr->driver_id, & ptr->fmt, NULL); if(NULL == ptr->device) { fprintf(stderr, "Unable to open device. %s.\n", strerror(errno)); return MAD_FLOW_BREAK; } } stream_ptr = stream = malloc(pcm->length * (pcm->channels == 2 ? 4 : 2)); assert(stream != NULL); read_from_pipe(ptr->pipefd); while(nsample--) { signed int sample; sample = scale(* left++); /* to byteswap or not to byteswap? */ #ifdef WORDS_BIGENDIAN *stream_ptr++ = (sample >> 8) & 0xFF; *stream_ptr++ = (sample & 0xFF); #else *stream_ptr++ = (sample & 0xFF); *stream_ptr++ = (sample >> 8) & 0xFF; #endif if(nchan == 2) { sample = scale(* right++); #ifdef WORDS_BIGENDIAN *stream_ptr++ = (sample >> 8) & 0xFF; *stream_ptr++ = (sample & 0xFF); #else *stream_ptr++ = (sample & 0xFF); *stream_ptr++ = (sample >> 8) & 0xFF; #endif } } ao_play(ptr->device, stream, pcm->length * (pcm->channels == 2 ? 4 : 2)); free(stream); if(killed) return MAD_FLOW_STOP; return MAD_FLOW_CONTINUE; } #else static enum mad_flow output( void * data, const struct mad_header * head, struct mad_pcm * pcm) { struct stream * ptr = (struct stream *) data; unsigned nchan = pcm->channels, rate = pcm->samplerate, arg; register unsigned nsample = pcm->length; mad_fixed_t * left = pcm->samples[0], * right = pcm->samples[1]; head = NULL; read_from_pipe(ptr->pipefd); arg = rate; ioctl(ptr->audiofd, SOUND_PCM_WRITE_RATE, & arg); arg = nchan; ioctl(ptr->audiofd, SOUND_PCM_WRITE_CHANNELS, & arg); unsigned char * words, * word; words = malloc(nsample * 4); assert(words != NULL); word = words; while(nsample--) { signed sample; sample = scale(* left++); word[0] = (unsigned char) (sample & 0xFF); word[1] = (unsigned char) ((sample >> 8) & 0xFF); word += 2; if(nchan == 2) { sample = scale(* right++); word[0] = (unsigned char) (sample & 0xFF); word[1] = (unsigned char) ((sample >> 8) & 0xFF); word += 2; } } write(ptr->audiofd, words, word - words); free(words); if(killed) return MAD_FLOW_STOP; return MAD_FLOW_CONTINUE; } #endif inline signed scale(register mad_fixed_t sample) { sample += (1L << (MAD_F_FRACBITS - 16)); if(sample >= MAD_F_ONE) sample = MAD_F_ONE - 1; else if(sample < -MAD_F_ONE) sample = -MAD_F_ONE; return (sample >> (MAD_F_FRACBITS + 1 - 16)) * volume / MAX_VOLUME; } static int timed_read(int fd, unsigned char * p, int count, int timeout) { fd_set fdset; struct timeval tv; struct timeval * tvp = & tv; FD_ZERO(& fdset); FD_SET(fd, & fdset); if(timeout <= 0) { tvp = NULL; } else { tv.tv_usec = 0; tv.tv_sec = timeout; } if(select(fd + 1, & fdset, NULL, NULL, tvp) > 0) { return read(fd, p, count); } fprintf(stderr, "Track stream timed out (%d).\n", timeout); return -1; } #endif shell-fm-0.7+git20100414/source/util.c0000644000175000017500000000724611361424251016055 0ustar nachonacho/* Copyright (C) 2006 by Jonas Kramer Published under the terms of the GNU General Public License (GPL). */ #define _GNU_SOURCE #include #include #include #include #include #include "util.h" #include "getln.h" #if (defined(TUXBOX) || defined(PPC)) #include #endif /* Takes the path of a file as argument, reads the file and returns an array of strings containing the lines of the file. */ char ** slurp(const char * path) { char ** content = NULL; unsigned items = 0; FILE * fd; if((fd = fopen(path, "r")) != NULL) { char * line = NULL; unsigned size = 0; while(!feof(fd)) { if(!getln(& line, & size, fd)) continue; if(strlen(line) > 1) { char * ptr; if((ptr = strrchr(line, 10)) != NULL) * ptr = (char) 0; if(strlen(line) > 0) { content = realloc(content, sizeof(char *) * (items + 2)); assert(content != NULL); content[items] = strdup(line); assert(content[items] != NULL); content[++items] = NULL; } } } if(line) free(line); fclose(fd); } return content; } /* Takes an array of strings and returns another array of strings with all duplicate strings removed. The old array is purged from memory. */ char ** uniq(char ** list) { char ** uniqlist = NULL; if(list != NULL) { int size = 0, n = 0; while(list[n] != NULL) { if(grep(uniqlist, list[n])) { free(list[n]); list[n] = NULL; } else { uniqlist = realloc(uniqlist, (sizeof(char *)) * (size + 2)); assert(uniqlist != NULL); uniqlist[size++] = list[n]; uniqlist[size] = NULL; } ++n; } free(list); } return uniqlist; } int grep(char ** list, char * needle) { register unsigned x = 0; if(!needle) return 0; if(list != NULL) { while(list[x] != NULL) { if(!strcmp(list[x], needle)) return !0; ++x; } } return 0; } /* create a new buffer and populate with concatenation of all strings */ char * strjoin(const char *glue, ...) { va_list ap; unsigned total, count, glue_len; const char *str; char *res; va_start(ap, glue); total = count = 0; while ((str = va_arg(ap, const char *))) { total += strlen(str); count ++; } va_end(ap); glue_len = glue ? strlen(glue) : 0; total += (count - 1) * glue_len; res = (char*)malloc(total + 1); if (!res) return NULL; va_start(ap, glue); total = 0; while ((str = va_arg(ap, const char *))) { if (total && glue_len) { memcpy(res + total, glue, glue_len); total += glue_len; } strcpy(res + total, str); total += strlen(str); } va_end(ap); return res; } #ifdef __STRNDUP__ char * strndup(const char * src, size_t len) { char * tmp = (char *) malloc(len + 1); if(tmp != NULL) { strncpy(tmp, src, len); tmp[len] = 0; } return tmp; } #endif #if (defined(TUXBOX) || defined(PPC)) /* On a few system like a PPC based Dreambox libc does not include strcasestr ... */ char * strcasestr(const char * haystack, const char * needle) { int nlength; int mlength; assert(needle != NULL); assert(haystack != NULL); nlength = strlen(needle); if(nlength == 0) return NULL; mlength = strlen(haystack) - nlength + 1; /* If needle is bigger than haystack, can't match. */ if(mlength < 0) return NULL; while(mlength) { /* See if the needle matches the start of the haystack. */ int i; for(i = 0; i < nlength; i++) { if(tolower(haystack[i]) != tolower(needle[i])) break; } /* If it matched along the whole needle length, we found it. */ if(i == nlength) return (char *) haystack; mlength--; haystack++; } return NULL; } #endif shell-fm-0.7+git20100414/source/autoban.c0000644000175000017500000000227411361424251016525 0ustar nachonacho/* Copyright (C) 2006 by Jonas Kramer Published under the terms of the GNU General Public License (GPL). */ #define _GNU_SOURCE #include #include #include #include "settings.h" #include "autoban.h" #include "getln.h" /* Check if a given artist is banned completely (it appears in "~/.shell-fm/autoban"). */ int banned(const char * artist) { FILE * fd; signed match = 0; char * line = NULL; unsigned int size = 0; if(!artist) return 0; if(!(fd = fopen(rcpath("autoban"), "r"))) return 0; while(!feof(fd) && !match) { char * ptr; if(!getln(& line, & size, fd)) continue; if(strlen(line) > 1) { if((ptr = strrchr(line, 10)) != NULL) * ptr = (char) 0; match = (strlen(line) == strlen(artist)) && !strncasecmp(line, artist, strlen(line)); } } if(line) free(line); fclose(fd); return match; } /* Ban an artist by adding it to the autoban file. */ int autoban(const char * artist) { FILE * fd; const char * file = rcpath("autoban"); if(!artist) return 0; if(!(fd = fopen(file, "a"))) { printf("Sorry, %s could not be written.\n", file); return 0; } fprintf(fd, "%s\n", artist); fclose(fd); return !0; } shell-fm-0.7+git20100414/source/xmlrpc.c0000644000175000017500000000700511361424251016376 0ustar nachonacho/* Copyright (C) 2006 by Jonas Kramer Published under the terms of the GNU General Public License (GPL). */ #include #include #include #include #include #include #include "md5.h" #include "settings.h" #include "http.h" extern void purge(char **); static void append(char **, unsigned *, const char *); static void sparam(char **, unsigned *, const char *); static void aparam(char **, unsigned *, const char **); char * xmlize(const char *); int xmlrpc(const char * method, const char * fmt, ...) { unsigned size = 1024, narg, x; const unsigned char * md5; va_list argv; int result = 0; char * xml = malloc(size), ** resp, * url = "http://ws.audioscrobbler.com/1.0/rw/xmlrpc.php", md5hex[32 + 1] = { 0 }, tmp[32 + 8 + 1] = { 0 }; assert(xml != NULL); const char * challenge = "Shell.FM", * xmlhead = "\n" "\n" "\t%s\n" "\t\n"; /* generate password/challenge hash */ snprintf(tmp, sizeof(tmp), "%s%s", value(& rc, "password"), challenge); md5 = MD5((unsigned char *) tmp, sizeof(tmp) - 1); for(x = 0; x < 16; ++x) sprintf(2 * x + md5hex, "%02x", md5[x]); va_start(argv, fmt); memset(xml, (char) 0, size); snprintf(xml, size, xmlhead, method); sparam(& xml, & size, value(& rc, "username")); sparam(& xml, & size, challenge); sparam(& xml, & size, md5hex); for(narg = 0; narg < strlen(fmt); ++narg) { const char * string, ** array; switch(fmt[narg]) { case 's': /* string */ string = va_arg(argv, const char *); sparam(& xml, & size, string); break; case 'a': /* array */ array = va_arg(argv, const char **); aparam(& xml, & size, array); break; } } append(& xml, & size, "\t\n\n"); if((resp = fetch(url, NULL, xml, "text/xml")) != NULL) { for(x = 0; resp[x]; ++x) { if(strstr(resp[x], ">OK<") != NULL) result = !0; } purge(resp); } free(xml); return result; } static void append(char ** string, unsigned * size, const char * appendix) { if(string && size && appendix) { char * copy; * size = strlen(* string) + strlen(appendix) + 1; copy = realloc(* string, * size); assert(copy != NULL); if(!copy) fputs("Out of memory!\n", stderr); else { strcat(copy, appendix); copy[(* size) - 1] = 0; * string = copy; } } } static void sparam(char ** xml, unsigned * size, const char * param) { if(xml && size && param) { char * encoded = xmlize(param); append(xml, size, "\t\t"); append(xml, size, encoded); append(xml, size, "\n"); free(encoded); } } static void aparam(char ** xml, unsigned * size, const char ** param) { if(xml && size) { unsigned n; append(xml, size, "\t\t"); for(n = 0; param && param[n] != NULL; ++n) { char * encoded = xmlize(param[n]); append(xml, size, ""); append(xml, size, encoded); append(xml, size, ""); free(encoded); } append(xml, size, "\n"); } } char * xmlize(const char * orig) { register unsigned i = 0, x = 0; char * encoded = calloc((strlen(orig) * 6) + 1, sizeof(char)); assert(encoded != NULL); while(i < strlen(orig)) { if(strchr("&<>'\"", orig[i])) { snprintf( encoded + x, strlen(orig) * 6 - strlen(encoded) + 1, "&#x%02X;", (uint8_t) orig[i] ); x += 6; } else { encoded[x++] = orig[i]; } ++i; } return encoded; } shell-fm-0.7+git20100414/source/submit.c0000644000175000017500000001561711361424251016404 0ustar nachonacho/* Copyright (C) 2006 by Jonas Kramer Published under the terms of the GNU General Public License (GPL). */ #include #include #include #include #include #include #include #include #include #include "hash.h" #include "http.h" #include "md5.h" #include "settings.h" #include "split.h" #include "getln.h" #include "strary.h" #include "submit.h" #include "globals.h" #define ERROR (strerror(errno)) struct hash submission, * queue = NULL; unsigned qlength = 0, submitting = 0; int handshaked = 0; pid_t subfork = 0; extern pid_t playfork; extern struct hash data, rc, track; static int handshake(const char *, const char *); static void sliceq(unsigned); /* Add a track to the scrobble queue. */ int enqueue(struct hash * track) { const char * keys [] = { "creator", "title", "album", "duration" }; unsigned i; struct hash post; char timestamp[16], lastid[8], duration[8]; assert(track != NULL); memset(& post, 0, sizeof(struct hash)); for(i = 0; i < (sizeof(keys) / sizeof(char *)); ++i) if(!haskey(track, keys[i])) return 0; queue = realloc(queue, sizeof(struct hash) * (qlength + 1)); assert(queue != NULL); snprintf(timestamp, sizeof(timestamp), "%lu", (unsigned long) time(NULL)); snprintf(lastid, sizeof(lastid), "L%s", value(track, "lastfm:trackauth")); snprintf(duration, sizeof(duration), "%d", atoi(value(track, "duration"))); set(& post, "a", value(track, "creator")); set(& post, "t", value(track, "title")); set(& post, "i", timestamp); set(& post, "r", value(track, "rating")); set(& post, "o", lastid); set(& post, "l", duration); set(& post, "b", value(track, "album")); set(& post, "n", ""); set(& post, "m", ""); memcpy(& queue[qlength++], & post, sizeof(struct hash)); return !0; } /* Submit tracks from the queue. */ int submit(const char * user, const char * password) { char * body = NULL, ** resp; const unsigned stepsize = 1024 * sizeof(char); unsigned total = stepsize, ntrack; int retval = -1; if(!qlength || subfork > 0) return 0; submitting = qlength; subfork = fork(); if(subfork != 0) return !0; playfork = 0; enable(QUIET); signal(SIGTERM, SIG_IGN); if(!handshaked && !handshake(user, password)) { fputs("Handshake failed.\n", stderr); exit(retval); } /* Prepare POST body. */ body = malloc(stepsize); assert(body != NULL); memset(body, 0, stepsize); snprintf(body, stepsize, "s=%s", value(& submission, "session")); for(ntrack = 0; ntrack < qlength; ++ntrack) { unsigned pair; for(pair = 0; pair < queue[ntrack].size; ++pair) { char key[16], * encoded = NULL; unsigned length, bodysz = strlen(body); snprintf( key, sizeof(key), "%s[%d]", queue[ntrack].content[pair].key, ntrack ); encode(queue[ntrack].content[pair].value, & encoded); length = strlen(key) + strlen(encoded) + 2; while(bodysz + length > total) { body = realloc(body, total + stepsize); assert(body != NULL); total += stepsize; } snprintf(body + bodysz, total - bodysz, "&%s=%s", key, encoded); free(encoded); } } sliceq(qlength); resp = fetch(value(& submission, "submissions"), NULL, body, NULL); if(resp) { if(resp[0] && !strncmp(resp[0], "OK", 2)) retval = 0; purge(resp); } free(body); if(retval) puts("Couldn't scrobble track(s)."); exit(retval); } /* Remove a number of tracks from the scrobble queue. */ static void sliceq(unsigned tracks) { unsigned i; if(tracks > qlength) tracks = qlength; for(i = 0; i < tracks; ++i) empty(& queue[i]); qlength -= tracks; if(qlength > 0) { memmove(queue, & queue[tracks], sizeof(struct hash) * qlength); queue = realloc(queue, sizeof(struct hash) * qlength); assert(queue != NULL); } else { free(queue); queue = NULL; } } /* Authenticate to the submission server. */ static int handshake(const char * user, const char * password) { char temp[10 + 32 + 1], hex[32 + 1], ** resp; const char * url; const unsigned char * md5; int i, retval = 0; time_t timestamp = time(NULL); if(handshaked) empty(& submission); memset(& submission, 0, sizeof(struct hash)); handshaked = 0; assert(user != NULL); assert(password != NULL); snprintf(temp, sizeof(temp), "%s%lu", password, (unsigned long) timestamp); md5 = MD5((unsigned char *) temp, strlen(temp)); for(i = 0; i < 16; ++i) sprintf(& hex[2 * i], "%02x", md5[i]); url = makeurl( "http://post.audioscrobbler.com/" /* Base URL. */ "?hs=true" /* Handshake? Yes! */ "&p=%s" /* Protocol 1.2. */ "&c=sfm" /* Client ID (get a real one later). */ "&v=%s" /* Client version. */ "&u=%s" /* Last.FM user name. */ "&t=%u" /* Timestamp. */ "&a=%s", /* Authentication token. */ "1.2", "1.0", user, /* Last.FM user name. */ timestamp, /* UNIX timestamp. */ hex /* The authentication MD5 token calculated above. */ ); if((resp = fetch(url, NULL, NULL, NULL)) != NULL) { if(resp[0] != NULL && !strncmp("OK", resp[0], 2)) { set(& submission, "session", resp[1]); set(& submission, "now-playing", resp[2]); set(& submission, "submissions", resp[3]); retval = handshaked = !0; } purge(resp); } return retval; } void subdead(int exitcode) { if(exitcode == 0) sliceq(submitting); subfork = 0; submitting = 0; } /* Write the tracks from the scrobble queue to a file. */ void dumpqueue(int overwrite) { const char * path = rcpath("scrobble-cache"); if(path != NULL) { FILE * fd = fopen(path, overwrite ? "w" : "a+"); if(fd != NULL) { unsigned n, x; for(n = 0; n < qlength; ++n) { const char keys [] = "atirolbnm"; for(x = 0; x < sizeof(keys) - 1; ++x) { char key[2] = { keys[x], 0 }, * encoded = NULL; encode(value(& queue[n], key), & encoded); fprintf(fd, "%c=%s;", keys[x], encoded); free(encoded); } fputc(10, fd); } fclose(fd); } else { fprintf(stderr, "Couldn't open scrobble cache. %s.\n", ERROR); } } else { fprintf(stderr, "Can't find suitable scrobble queue path.\n"); } sliceq(qlength); } void loadqueue(int overwrite) { const char * path = rcpath("scrobble-cache"); if(overwrite) sliceq(qlength); if(path != NULL) { FILE * fd = fopen(path, "r"); if(fd != NULL) { while(!feof(fd)) { char * line = NULL; unsigned size = 0; if(getln(& line, & size, fd) >= 2) { unsigned n = 0; char ** splt = split(line, ";\n", & n); struct hash track; memset(& track, 0, sizeof(struct hash)); while(n--) { char key[2] = { 0 }, * value = NULL; sscanf(splt[n], "%c=", & key[0]); if(strchr("atirolbnm", key[0])) { decode(splt[n] + 2, & value); set(& track, key, value); free(value); } free(splt[n]); } free(splt); queue = realloc(queue, sizeof(struct hash) * (++qlength)); assert(queue != NULL); memcpy(& queue[qlength - 1], & track, sizeof(struct hash)); } if(line) free(line); } fclose(fd); } } } shell-fm-0.7+git20100414/source/recommend.c0000644000175000017500000000456111361424251017046 0ustar nachonacho/* Copyright (C) 2006 by Jonas Kramer Published under the terms of the GNU General Public License (GPL). */ #include #include #include #include #include "xmlrpc.h" #include "feeds.h" #include "hash.h" #include "completion.h" #include "readline.h" #include "interface.h" #include "settings.h" #include "getln.h" static char ** users = NULL; static int usercomplete(char *, const unsigned, int); void recommend(struct hash track) { char key, * message = NULL, * recipient = NULL; unsigned result = 0; struct prompt setup = { .prompt = "Recipient: ", .line = NULL, .history = NULL, .callback = usercomplete, }; struct prompt comment = { .prompt = "Comment: ", .line = NULL, .history = NULL, .callback = NULL, }; if(!track.content) return; fputs("Recommend (a)rtist, a(l)bum, (t)rack or (c)ancel?\n", stderr); while(!strchr("altc", (key = fetchkey(2)))); if(key == 'c') return; users = neighbors(value(& rc, "username")); users = merge(users, friends(value(& rc, "username")), 0); recipient = readline(& setup); purge(users); users = NULL; message = readline(& comment); switch(key) { case 'a': result = xmlrpc( "recommendArtist", "ssss", value(& track, "creator"), recipient, message, "en" ); break; case 'l': result = xmlrpc( "recommendAlbum", "sssss", value(& track, "creator"), value(& track, "album"), recipient, message, "en" ); break; case 't': result = xmlrpc( "recommendTrack", "sssss", value(& track, "creator"), value(& track, "title"), recipient, message, "en" ); break; } puts(result ? "Recommended." : "Sorry, failed."); } static int usercomplete(char * line, const unsigned max, int changed) { unsigned length, nres = 0; int retval = 0; const char * match = NULL; assert(line != NULL); length = strlen(line); /* Remove spaces at the beginning of the string. */ while(isspace(line[0])) { retval = !0; memmove(line, line + 1, strlen(line + 1)); line[--length] = 0; } /* Remove spaces at the end of the string. */ while((length = strlen(line)) > 0 && isspace(line[length - 1])) { retval = !0; line[--length] = 0; } if(!users || !users[0]) return retval; if((match = nextmatch(users, changed ? line : NULL, & nres)) != NULL) { snprintf(line, max, "%s", match); retval = !0; } return retval; } shell-fm-0.7+git20100414/source/include/0000755000175000017500000000000011361424251016346 5ustar nachonachoshell-fm-0.7+git20100414/source/include/interface.h0000644000175000017500000000105211361424251020455 0ustar nachonacho/* vim:syntax=c tabstop=2 shiftwidth=2 noexpandtab */ #ifndef SHELLFM_INTERFACE #define SHELLFM_INTERFACE #include "hash.h" #define M_COLORED 0x1 #define M_RELAXPATH 0x2 #define M_SHELLESC 0x4 extern const char * meta(const char *, int, struct hash *); extern void interface(int); extern void run(const char *); extern void canon(int); extern int fetchkey(unsigned); extern void shownp(void); extern void tag(struct hash); extern int rate(const char *); extern char * shellescape(const char *); extern void quit(); extern void unlinknp(); #endif shell-fm-0.7+git20100414/source/include/radio.h0000644000175000017500000000022411361424251017613 0ustar nachonacho/* vim:syntax=c tabstop=2 shiftwidth=2 noexpandtab */ #ifndef SHELLFM_RADIO #define SHELLFM_RADIO extern void radioprompt(const char *); #endif shell-fm-0.7+git20100414/source/include/playlist.h0000644000175000017500000000107311361424251020361 0ustar nachonacho #ifndef SHELLFM_PLAYLIST #define SHELLFM_PLAYLIST #include "hash.h" struct tracknode; struct tracknode { struct hash track; struct tracknode * next; }; struct playlist { struct tracknode * track; char * title; unsigned left; int continuous; }; extern int expand(struct playlist *); extern int parsexspf(struct playlist *, char *); extern void freelist(struct playlist *); extern void freenode(struct tracknode **); extern void push(struct playlist *, struct tracknode *); extern void shift(struct playlist *); extern void preview(struct playlist); #endif shell-fm-0.7+git20100414/source/include/md5.h0000644000175000017500000000016511361424251017206 0ustar nachonacho #ifndef SHELLFM_MD5 #define SHELLFM_MD5 extern const unsigned char * MD5(const unsigned char *, unsigned); #endif shell-fm-0.7+git20100414/source/include/tag.h0000644000175000017500000000037211361424251017274 0ustar nachonacho #ifndef SHELLFM_TAG #define SHELLFM_TAG extern void tag(struct hash); extern void stripslashes(char *); extern void sendtag(char, char *, struct hash); extern int tagcomplete(char *, const unsigned, int); char * oldtags(char, struct hash); #endif shell-fm-0.7+git20100414/source/include/compatibility.h0000644000175000017500000000041011361424251021363 0ustar nachonacho/* vim:syntax=c tabstop=2 shiftwidth=2 noexpandtab */ #ifndef SHELLFM_COMPATIBILITY #define SHELLFM_COMPATIBILITY #if !defined(__GNUC__) || __GNUC__ < 3 #define __UNUSED__ #else #define __UNUSED__ __attribute__((unused)) #endif #endif // SHELLFM_COMPATIBILITY shell-fm-0.7+git20100414/source/include/sckif.h0000644000175000017500000000045011361424251017615 0ustar nachonacho/* vim:syntax=c tabstop=2 shiftwidth=2 noexpandtab */ #ifndef SHELLFM_SCKIF #define SHELLFM_SCKIF extern int tcpsock(const char *, unsigned short); extern int unixsock(const char *); extern void rmsckif(void); extern void sckif(int, int); extern void execcmd(const char *, char *); #endif shell-fm-0.7+git20100414/source/include/ropen.h0000644000175000017500000000032411361424251017641 0ustar nachonacho/* vim:syntax=c tabstop=2 shiftwidth=2 noexpandtab */ #ifndef SHELLFM_ROPEN #define SHELLFM_ROPEN #include extern FILE * ropen(const char *, unsigned short); extern void fshutdown(FILE **); #endif shell-fm-0.7+git20100414/source/include/service.h0000644000175000017500000000051711361424251020162 0ustar nachonacho/* vim:syntax=c tabstop=2 shiftwidth=2 noexpandtab */ #ifndef SHELLFM_SERVICE #define SHELLFM_SERVICE #include "hash.h" #include "playlist.h" extern int authenticate(const char *, const char *); extern int station(const char *); extern int update(struct hash *); extern int play(struct playlist *); extern int delayquit; #endif shell-fm-0.7+git20100414/source/include/completion.h0000644000175000017500000000030111361424251020662 0ustar nachonacho/* vim:syntax=c tabstop=2 shiftwidth=2 noexpandtab */ #ifndef SHELLFM_COMPLETION #define SHELLFM_COMPLETION #include "strary.h" const char * nextmatch(char **, char *, unsigned *); #endif shell-fm-0.7+git20100414/source/include/feeds.h0000644000175000017500000000046411361424251017611 0ustar nachonacho #ifndef SHELLFM_FEEDS #define SHELLFM_FEEDS #include "hash.h" extern char ** neighbors(const char *); extern char ** topartists(const char *); extern char ** friends(const char *); extern char ** toptags(char, struct hash); extern char ** overalltags(void); extern char ** usertags(const char *); #endif shell-fm-0.7+git20100414/source/include/split.h0000644000175000017500000000024511361424251017653 0ustar nachonacho/* vim:syntax=c tabstop=2 shiftwidth=2 noexpandtab */ #ifndef SHELLFM_SPLIT #define SHELLFM_SPLIT extern char ** split(char *, const char *, unsigned *); #endif shell-fm-0.7+git20100414/source/include/history.h0000644000175000017500000000032611361424251020221 0ustar nachonacho/* vim:syntax=c tabstop=2 shiftwidth=2 noexpandtab */ #ifndef SHELLFM_HISTORY #define SHELLFM_HISTORY extern void histapp(const char *); extern char ** slurp(const char *); extern char ** uniq(char **); #endif shell-fm-0.7+git20100414/source/include/getln.h0000644000175000017500000000015211361424251017626 0ustar nachonacho #ifndef SHELLFM_GETLN #define SHELLFM_GETLN extern unsigned getln(char **, unsigned *, FILE *); #endif shell-fm-0.7+git20100414/source/include/hash.h0000644000175000017500000000070311361424251017442 0ustar nachonacho/* vim:syntax=c tabstop=2 shiftwidth=2 noexpandtab */ #ifndef SHELLFM_HASH #define SHELLFM_HASH struct pair { char * key, * value; }; struct hash { unsigned size; struct pair * content; }; extern void set(struct hash *, const char *, const char *); extern const char * value(struct hash *, const char *); extern void unset(struct hash *, const char *); extern int haskey(struct hash *, const char *); extern void empty(struct hash *); #endif shell-fm-0.7+git20100414/source/include/http.h0000644000175000017500000000102411361424251017473 0ustar nachonacho/* vim:syntax=c tabstop=2 shiftwidth=2 noexpandtab */ #ifndef SHELLFM_HTTP #define SHELLFM_HTTP #include #include #include "hash.h" extern char ** fetch(const char *, FILE **, const char *, const char *); extern char ** cache(const char *, const char *, int); extern unsigned encode(const char *, char **); extern unsigned decode(const char *, char **); extern const char * makeurl(const char *, ...); extern void unhtml(char *); extern void lag(time_t); extern void freeln(char **, unsigned *); #endif shell-fm-0.7+git20100414/source/include/settings.h0000644000175000017500000000045211361424251020360 0ustar nachonacho/* vim:syntax=c tabstop=2 shiftwidth=2 noexpandtab */ #ifndef SHELLFM_SETTINGS #define SHELLFM_SETTINGS #define PACKAGE_VERSION "0.7" #include "hash.h" extern int settings(const char *, int); extern const char * rcpath(const char *); extern void makercd(void); extern struct hash rc; #endif shell-fm-0.7+git20100414/source/include/readline.h0000644000175000017500000000035511361424251020305 0ustar nachonacho #ifndef SHELLFM_PROMPT #define SHELLFM_PROMPT struct prompt { const char * prompt; char * line, ** history; int (* callback)(char *, const unsigned, int); }; extern char * readline(struct prompt *); extern void canon(int); #endif shell-fm-0.7+git20100414/source/include/bookmark.h0000644000175000017500000000032511361424251020324 0ustar nachonacho/* vim:syntax=c tabstop=2 shiftwidth=2 noexpandtab */ #ifndef SHELLFM_BOOKMARK #define SHELLFM_BOOKMARK extern void setmark(const char *, int); extern void printmarks(void); extern char * getmark(int); #endif shell-fm-0.7+git20100414/source/include/pipe.h0000644000175000017500000000014311361424251017452 0ustar nachonacho #ifndef SHELLFM_PIPE #define SHELLFM_PIPE extern FILE * openpipe(const char *, pid_t *); #endif shell-fm-0.7+git20100414/source/include/play.h0000644000175000017500000000024111361424251017461 0ustar nachonacho/* vim:syntax=c tabstop=2 shiftwidth=2 noexpandtab */ #ifndef SHELLFM_PLAY #define SHELLFM_PLAY #include extern int playback(FILE *, int); #endif shell-fm-0.7+git20100414/source/include/util.h0000644000175000017500000000064711361424251017503 0ustar nachonacho #ifndef SHELLFM_UTIL #define SHELLFM_UTIL extern char ** slurp(const char *); extern char ** uniq(char **); extern int grep(char **, char *); extern char * strcasestr(const char *, const char *); extern char * strjoin(const char *, ...); #if ((defined(__FreeBSD__) || defined(__OpenBSD__)) || defined(__darwin__) && !defined(__STRNDUP__)) #define __STRNDUP__ extern char * strndup(const char *, size_t); #endif #endif shell-fm-0.7+git20100414/source/include/autoban.h0000644000175000017500000000026411361424251020152 0ustar nachonacho/* vim:syntax=c tabstop=2 shiftwidth=2 noexpandtab */ #ifndef SHELLFM_AUTOBAN #define SHELLFM_AUTOBAN extern int banned(const char *); extern int autoban(const char *); #endif shell-fm-0.7+git20100414/source/include/globals.h0000644000175000017500000000175211361424251020147 0ustar nachonacho #ifndef SHELLFM_GLOBALS #define SHELLFM_GLOBALS /* for pid_t */ #include #include "hash.h" #include "playlist.h" /* Track, session and settings data structures. */ extern struct hash data, track; extern struct playlist playlist; extern struct hash rc; /* Batch mode: no coloring, every line of output gets uncoditionally terminated by newline. */ extern int batch; /* Forks. */ extern pid_t playfork, subfork; extern int playpipe; extern unsigned char volume; extern char * currentStation; /* Name of the current station. */ extern unsigned submitting; /* Number of tracks currently submitted. */ extern time_t pausetime; /* Pause start time. */ #define MAX_VOLUME 64 extern unsigned flags; #define STOPPED 0x1 #define DISCOVERY 0x2 #define CHANGED 0x4 #define RTP 0x8 #define QUIET 0x10 #define INTERRUPTED 0x20 #define enabled(n) (flags & n) #define enable(n) (flags |= n) #define disable(n) (flags &= ~n) #define toggle(n) (flags ^= n) extern char * nextstation; #endif shell-fm-0.7+git20100414/source/include/xmlrpc.h0000644000175000017500000000015411361424251020024 0ustar nachonacho #ifndef SHELLFM_XMLRPC #define SHELLFM_XMLRPC extern int xmlrpc(const char *, const char *, ...); #endif shell-fm-0.7+git20100414/source/include/submit.h0000644000175000017500000000033411361424251020022 0ustar nachonacho #ifndef SHELLFM_SUBMIT #define SHELLFM_SUBMIT extern int enqueue(struct hash *); extern int submit(const char *, const char *); extern void subdead(int); extern void dumpqueue(int); extern void loadqueue(int); #endif shell-fm-0.7+git20100414/source/include/recommend.h0000644000175000017500000000015611361424251020472 0ustar nachonacho #ifndef SHELLFM_RECOMMEND #define SHELLFM_RECOMMEND #include "hash.h" void recommend(struct hash); #endif shell-fm-0.7+git20100414/source/include/strary.h0000644000175000017500000000052611361424251020046 0ustar nachonacho/* vim:syntax=c tabstop=2 shiftwidth=2 noexpandtab */ #ifndef SHELLFM_STRARRAY #define SHELLFM_STRARRAY #include #include extern unsigned count(char **); extern char ** append(char **, const char *); extern char ** merge(char **, char **, int); extern void purge(char **); extern char * join(char **, int); #endif shell-fm-0.7+git20100414/source/strary.c0000644000175000017500000000414511361424251016417 0ustar nachonacho/* Copyright (C) 2006 by Jonas Kramer Copyright (C) 2006 by Bart Trojanowski Published under the terms of the GNU General Public License (GPL). */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "compatibility.h" #include "strary.h" /* Counts the elements of a NULL-terminated array of strings. */ unsigned count(char ** list) { unsigned n = 0; if(list != NULL) while(list[n] != NULL) ++n; return n; } /* Appends a string to a NULL-terminated array of strings. */ char ** append(char ** list, const char * string) { unsigned size = count(list); list = realloc(list, sizeof(char *) * (size + 2)); assert(list != NULL); list[size++] = strdup(string); assert(list[size - 1] != NULL); list[size] = NULL; return list; } /* Merge two arrays of strings. If the third parameter is zero, the elements of the second array and the array itself are freed. */ char ** merge(char ** list, char ** appendix, int keep) { unsigned size = count(list), i; for(i = 0; appendix && appendix[i] != NULL; ++i) { list = realloc(list, sizeof(char *) * (size + 2)); assert(list != NULL); list[size++] = strdup(appendix[i]); assert(list[size - 1] != NULL); list[size] = NULL; if(!keep) free(appendix[i]); } if(appendix != NULL && !keep) free(appendix); return list; } /* Free a NULL-terminated array of strings. */ void purge(char ** list) { unsigned i = 0; if(list != NULL) { while(list[i] != NULL) free(list[i++]); free(list); } } /* Merge strings of an array to one big string. If the second argument is false, the list is purged. */ char * join(char ** list, int keep) { unsigned i = 0, length = 0; char * result = NULL; if(list != NULL) { while(list[i] != NULL) { result = realloc(result, sizeof(char) * (length + strlen(list[i]) + 1)); assert(result != NULL); strcpy(result + length, list[i]); length += strlen(list[i]); result[length] = 0; ++i; } if(!keep) purge(list); } return result; } shell-fm-0.7+git20100414/AUTHORS0000644000175000017500000000152411361424251014475 0ustar nachonacho* Jonas Kramer * Bart Trojanowski * Jacek Nitkiewicz * Helmut Maierthaler * Nguyen Thai Ngoc Duy * Ondrej Novy * Csaba Henk * Matthias Goebl * Benoît Rouits * Mike Kelly * Christian Neukirchen * Guillaume Pinot * Wirt Wolff * Alexander Monakov * Rodrigo Tartajo * Silviu Grijincu * Mark Goodall * Pierre Baillet NOTE: I've removed the information about who did what, since during the development almost everything anyone added has been changed/replaced at some time by me or others and it's impossible to track everything. So I'll just list all the names of the people who contributed something at some time here. Of course the details are still in the subversion commit log. Please mail me (jkramer at nex dot scrapping dot cc) if your name is missing here. If you want to praise or blame a developer for something particular, search the GIT log. shell-fm-0.7+git20100414/INSTALL0000644000175000017500000000141511361424251014455 0ustar nachonacho Shell.FM 0.5 Installation Requirements: * libmad * libao (optional) * libtagc (optional) Installation: 1. Run "make". 2. Run "make install" as root. 3. Read the manual for per-user setup information. NOTE: The Makefile uses /usr as PREFIX and $PREFIX/man as MANDIR by default. You can override those on the command line. For example, Debian/Ubuntu users usually have their manuals in /usr/share/man, so they would install shell-fm like this: $ make install PREFIX=/usr MANDIR=/usr/share/man NOTE: To install a stripped version of shell-fm (about 10K smaller), use "install-strip" instead of "install": $ make install-strip To install to a temporary root (for example, when building a binary package), you can use the DESTDIR variable, just like with autotools packages. shell-fm-0.7+git20100414/RELEASE0000644000175000017500000000430111361424251014424 0ustar nachonacho SHELL.FM RELEASE NOTES 0.7 - 2008-08-30 Changes: * Added "screen-format" option to set GNU screen window titles on track change. * When "term-format" is set to a format string, shell-fm will set the terminals window title on track-change now. * When option "download" is set to a path including format flags, free tracks are saved and tagged (with taglib support) * Prompt for radio URI on startup if no default radio is set and no URI is given as command line argument. * Added "gap" option for sleeping between tracks. * Added delayed quit ('q', works only if delay-change is set). * Delay of station changes works for bookmarks now. * Bookmark list is sorted. * Empty station cancels delayed station change. * $SHELL_FM_HOME is used as base directory instead of ~/.shell-fm/, if set. * Add pp-cmd that runs after a track is saved. * Small playback buffer to decrease CPU load when playing without libao. * Disable RTP at startup if "no-rtp" is set to something in the configuration. Authors / Contributors: * Benoît Rouits * Csaba Henk * Matthew Donoughe * Matthias Goebl * Mike Kelly * Silviu Grijincu * Stepan Kasal * Cedric * Jonas Kramer SHELL.FM RELEASE NOTES 0.6 - 2008-09-04 Many thanks to the contributors to this release: * Mike Kelly (fixes for FreeBSD) * Lars-Dominik Braun (bug fixes) * Rodrigo Tartajo (small improvements) * Guillaume Pinot (patches for OpenBSD) * Alexander Monakov (meta flags in extern option) * Wirt Wolff (manual update) * Åke Forslund (bug fix) New features: * delayed station change: 'r', 's' and 'f' wait until the currently played track ends before changing to the next station if "delay-change" is set. * playlist preview: 'u' prints upcoming tracks in the station queue, with 'E' you can manually load the next bunch of tracks of the playlist. With 'preview-format' you can change the format of the track list. * 'discovery' in the configuration lets you enable discovery mode by default. * UNIX socket support for local "remote" control (check manual). * Directories ~/.shell-fm/ and ~/.shell-fm/cache are created automatically. * 'extern' option has support for format flags. * Better support for *BSD. * Some minor improvements and bug fixes.