diff --git a/Makefile b/Makefile index 10da2c3..37e41c6 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION = 2.1.0 +VERSION = 2.1.0-ft all: main plugins man @@ -9,8 +9,8 @@ CFLAGS += -D_FILE_OFFSET_BITS=64 CMUS_LIBS = $(PTHREAD_LIBS) $(NCURSES_LIBS) $(ICONV_LIBS) $(DL_LIBS) -lm $(COMPAT_LIBS) -input.o main.o ui_curses.o: .version -input.o main.o ui_curses.o: CFLAGS += -DVERSION=\"$(VERSION)\" +as.o input.o main.o ui_curses.o: .version +as.o input.o main.o ui_curses.o: CFLAGS += -DVERSION=\"$(VERSION)\" main.o server.o: CFLAGS += -DDEFAULT_PORT=3000 .version: Makefile @@ -19,13 +19,13 @@ main.o server.o: CFLAGS += -DDEFAULT_PORT=3000 # programs {{{ cmus-y := \ - browser.o buffer.o cmdline.o cmus.o command_mode.o comment.o \ + as.o browser.o buffer.o cmdline.o cmus.o command_mode.o comment.o \ db.o debug.o editable.o expr.o filters.o \ format_print.o glob.o help.o history.o http.o input.o \ - keys.o lib.o load_dir.o locking.o mergesort.o misc.o options.o \ + keys.o lib.o load_dir.o locking.o network.o md5.o mergesort.o misc.o options.o \ output.o pcm.o pl.o play_queue.o player.o \ read_wrapper.o server.o search.o \ - search_mode.o spawn.o tabexp.o tabexp_file.o \ + search_mode.o spawn.o strlib.o tabexp.o tabexp_file.o \ track.o track_db.o track_info.o tree.o uchar.o ui_curses.o window.o \ worker.o xstrjoin.o diff --git a/as.c b/as.c new file mode 100644 index 0000000..d737b96 --- /dev/null +++ b/as.c @@ -0,0 +1,931 @@ +/* + * Copyright 2004-2006 Timo Hirvonen + * + * as.[ch] by Frank Terbeck + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +/* + * see as.h and http://www.audioscrobbler.net + */ + +#include +#include + +#include "as.h" +#include "cmus.h" +#include "debug.h" +#include "list.h" +#include "locking.h" +#include "md5.h" +#include "network.h" +#include "player.h" +#include "strlib.h" +#include "utils.h" + +struct as_authinfo as_authinfo; +struct as_hsinfo as_hsinfo; +const char *as_numbers[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" }; +time_t as_lasttimestamp = 0; +int as_enable = 0; +int running = 0; + +static pthread_mutex_t as_mutex = CMUS_MUTEX_INITIALIZER; +static pthread_t as_thread; +static LIST_HEAD(as_queue_head); + +static unsigned int as_count_entries(void) +{ /*{{{*/ + /* make sure as_lock() was called. */ + struct as_queue *entry; + int count=0; + list_for_each_entry(entry, &as_queue_head, node) { + ++count; + } + return(count); +} /*}}}*/ + +static struct as_queue *as_get_last_entry(void) +{ /*{{{*/ + /* make sure as_lock() was called. */ + struct as_queue *entry; + list_for_each_entry(entry, &as_queue_head, node) { } + return(entry); +} /*}}}*/ + +static void as_urlencode(string_t *str) +{ /*{{{*/ + const char *transtab[] = + { /*{{{*/ + " ", "%20", /* 00 01 */ + "!", "%21", /* 02 03 */ + "\"", "%22", /* 04 05 */ + "#", "%23", /* 06 07 */ + "%", "%25", /* 08 09 */ + "&", "%26", /* 10 11 */ + "'", "%27", /* 12 13 */ + "(", "%28", /* 14 15 */ + ")", "%29", /* 16 17 */ + "*", "%2A", /* 18 19 */ + "-", "%2D", /* 20 21 */ + "/", "%2F", /* 22 23 */ + ":", "%3A", /* 24 25 */ + ";", "%3B", /* 26 27 */ + "<", "%3C", /* 28 29 */ + "=", "%3D", /* 30 31 */ + ">", "%3E", /* 32 33 */ + "?", "%3F", /* 34 35 */ + "@", "%40", /* 36 37 */ + "[", "%5B", /* 38 39 */ + "\\", "%5C", /* 40 41 */ + "]", "%5D", /* 42 43 */ + "^", "%5E", /* 44 45 */ + "`", "%60", /* 46 47 */ + "{", "%7B", /* 48 49 */ + "|", "%7C", /* 50 51 */ + "}", "%7D", /* 52 53 */ + "~", "%7E", /* 54 55 */ + }; /*}}}*/ + int i, e, idx=-1; + for (i=0; i<=str->len-1; ++i) { + if (mustquote(str->s[i])) { + for (e=0; e<=55; e+=2) { + if (transtab[e][0] == str->s[i]) { + idx=e+1; + break; + } + } + if (idx >= 0) + str_nreplace(str, i, 1, transtab[idx], 3); + } + } +} /*}}}*/ + +static void as_update_timestamp(void) +{ /*{{{*/ + as_lasttimestamp=time(NULL); +} /*}}}*/ + +static void as_handshake(void) +{ /*{{{*/ + int sockfd; + unsigned long int idx, odx; + char buf[AS_BUFSIZE+1]; + size_t n; + string_t msg, tmp; + + /* no authentication values set, don't do anything */ + if (as_authinfo.user[0] == '\0' || as_authinfo.pass[0] == '\0') + return; + + /* prepare handshake request */ + str_init(&msg); + str_init(&tmp); + str_ncat(&msg, "GET ", 4); str_scat(&msg, AS_HS_URI); + str_ncat(&msg, "&p=", 3); str_scat(&msg, AS_PROTO); + str_ncat(&msg, "&c=", 3); str_ncat(&msg, AS_CLIENT_ID, 3); + str_ncat(&msg, "&v=", 3); str_scat(&msg, VERSION); /* cmus' Version, that is */ + as_lock(); + str_scat(&tmp, as_authinfo.user); + as_unlock(); + as_urlencode(&tmp); + str_ncat(&msg, "&u=", 3); str_cat(&msg, &tmp); + str_ncat(&msg, " HTTP/1.1\r\nHost: ", 17); str_scat(&msg, AS_HOSTNAME); + str_ncat(&msg, "\r\nConnection: CLOSE\r\n\r\n", 23); + + /* connect() */ + if ( (sockfd = tcp_connect(AS_HOSTNAME, AS_HOSTPORT)) < 0 ) { + d_print("tcp_connect() failed!\n"); + as_hsinfo.failed++; + goto cleanup; + } + + /* submit our handshake request */ + xwriten(sockfd, msg.s, msg.len); + + msg.s[0] = '\0'; + msg.len = 0; + + /* read server's response */ + for (;;) { + if ( (n = xreadn(sockfd, buf, AS_BUFSIZE) ) <= 0) + break; + buf[n] = '\0'; + str_ncat(&msg, buf, n); + } + xclose(sockfd); + +#ifdef AS_HEAVY_DEBUG + d_print("Serverreply:\n\"%s\"\n", msg.s); +#endif + + /* parse the response we read */ + as_lock(); + if (str_ngetidx(&msg, 0, "\r\n\r\n", 4, &idx)) { + str_nreplace(&msg, 0, idx+4, "", 0); + } + else { + d_print("broken reply, couldn't find end of headers (\\r\\n\\r\\n)\n"); + as_hsinfo.failed++; + goto cleanup; + } + + /* + * UPTODATE/UPDATE/FAILED/BADUSER + */ + idx=odx=0; + if (!str_ngetidx(&msg, odx, "\n", 1, &idx)) { + d_print("Broken input: \"%s\"\n", msg.s); + as_hsinfo.failed++; + goto cleanup; + } + str_ncpy(&tmp, msg.s+odx, idx-odx); + if (str_ngetidx(&tmp, 0, "UPTODATE", 8, NULL)) { + d_print("received UPTODATE, good.\n"); + } + else if (str_ngetidx(&tmp, 0, "UPDATE", 6, NULL)) { + d_print("received UPDATE, get a new client, baby.\n"); + } + else if (str_ngetidx(&tmp, 0, "FAILED", 6, NULL)) { + d_print("received FAILED, crap :-/\n"); + as_hsinfo.failed++; + goto cleanup; + } + else if (str_ngetidx(&tmp, 0, "BADUSER", 7, NULL)) { + d_print("received BADUSER, err, check as_user setting.\n"); + as_hsinfo.failed++; + goto cleanup; + } + else { + d_print("Unknown response: \"%s\"\n", tmp.s); + as_hsinfo.failed++; + goto cleanup; + } + + /* + * -challenge- + */ + odx=idx+1; + if (!str_ngetidx(&msg, odx, "\n", 1, &idx)) { + d_print("Broken input: \"%s\"\n", msg.s); + as_hsinfo.failed++; + goto cleanup; + } + str_ncpy(&tmp, msg.s+odx, idx-odx); + str_cpy(&(as_hsinfo.challenge), &tmp); + + /* + * -url-to-submit-script + */ + odx=idx+1; + if (!str_ngetidx(&msg, odx, "\n", 1, &idx)) { + d_print("Broken input: \"%s\"\n", msg.s); + as_hsinfo.failed++; + goto cleanup; + } + str_ncpy(&tmp, msg.s+odx, idx-odx); + odx=idx+1; + if (str_ngetidx(&tmp, 0, "://", 3, &idx)) { + if (idx+3 <= tmp.len) + str_nreplace(&tmp, 0, idx+3, "", 0); + } + else + d_print("hrm, no service mnemonic? (%s)\n)", tmp.s); + + if (str_ngetidx(&tmp, 0, "/", 1, &idx) && (idx <= tmp.len)) { + str_ncpy(&as_hsinfo.path, tmp.s+idx, tmp.len-idx); + str_nreplace(&tmp, idx, tmp.len-idx, "", 0); + } + else { + d_print("um, no path? (%s)\n", tmp.s); + as_hsinfo.failed++; + } + + if (str_ngetidx(&tmp, 0, ":", 1, &idx)) { + strncpy(as_hsinfo.port, tmp.s+idx+1, (tmp.len-idx < AS_MAXTMPLEN ? tmp.len-idx : AS_MAXTMPLEN)); + as_hsinfo.port[(tmp.len-idx < AS_MAXTMPLEN ? tmp.len-idx : AS_MAXTMPLEN)] = '\0'; + } + else { + d_print("well, no port definition? That, would be okay, default: 80. (%s)", tmp.s); + as_hsinfo.port[0] = '8'; as_hsinfo.port[1] = '0'; as_hsinfo.port[2] = '\0'; + } + str_ncpy(&(as_hsinfo.host), tmp.s, idx); + +#ifdef AS_HEAVY_DEBUG + d_print("debug as_hsinfo:\n\thost: \"%s\"\n\tpath: \"%s\"\n\tport: \"%s\"\n", + as_hsinfo.host.s, as_hsinfo.path.s, as_hsinfo.port); +#endif + + /* + * -INTERVAL- + */ + odx=idx+1; + if (!str_ngetidx(&msg, odx, "\n", 1, &idx)) { + d_print("Broken input: \"%s\"\n", msg.s); + goto cleanup; + } + str_ncpy(&tmp, msg.s+odx, idx-odx); + if (str_ngetidx(&tmp, 0, "INTERVAL ", 9, NULL)) { + as_hsinfo.interval=atoi(tmp.s+9); + d_print("Our Interval is: %d\n", (int)as_hsinfo.interval); + } + + /* handshake suceeded, all values set. */ + as_hsinfo.need_handshake=0; + +cleanup: + as_unlock(); + str_free(&tmp); + str_free(&msg); +} /*}}}*/ + +static int as_submit_allowed(void) +{ /*{{{*/ + /* + * honor INTERVAL response from the server + */ + const time_t now = time(NULL); + time_t at; + as_lock(); + at = as_hsinfo.interval + as_lasttimestamp; + as_unlock(); + if (now >= at) + return(1); + return(0); +} /*}}}*/ + +static int as_submit_forced(void) +{ /*{{{*/ + /* + * queue maximum is 10, so we force submission at 8 entries + */ + unsigned int n; + as_lock(); + n = as_count_entries(); + as_unlock(); + if (n >= AS_FORCE_SUBMIT) + return(1); + return(0); +} /*}}}*/ + +static void as_del_queue(struct list_head *item) +{ /*{{{*/ + /* make sure as_lock() was called. */ + struct as_queue *tmp; + + tmp=list_entry(item, struct as_queue, node); + d_print(" string: %s\n", tmp->s.s); + d_print(" NEEDRN: %d\n", tmp->needrenumber); + d_print(" SUBMIT: %d\n", tmp->sc); + d_print(" num : %d\n", tmp->num); + str_free(&(tmp->s)); + list_del(item); + free(tmp); +} /*}}}*/ + +static void as_destroy_queue(int scmask) +{ /*{{{*/ + /* make sure as_lock() was called. */ + struct as_queue *pos, *tmp; + + list_for_each_entry_safe(pos, tmp, &as_queue_head, node) { + if (pos->sc & scmask) + as_del_queue(&(pos->node)); + } +} /*}}}*/ + +static void as_renumberentry(string_t *entry, unsigned short int num) +{ /*{{{*/ + unsigned long idx, odx = 0; + while (str_ngetidx(entry, odx, "]=", 2, &idx)) { + if (idx > 0) { + d_print("replacing %c with %c\n", entry->s[idx-1], as_numbers[num][0]); + entry->s[idx-1] = as_numbers[num][0]; + } + odx=idx+2; + } +} /*}}}*/ + +static unsigned short int as_uint2string(unsigned int integer, char *str) +{ /*{{{*/ + unsigned short int dn, i, digit; + unsigned int tmp; + + if (integer >= 0 && integer <= 9) { + dn=1; + str[0] = as_numbers[integer][0]; + str[1] = '\0'; + } + else { + dn=0; + tmp=integer; + while (tmp) { + ++dn; + tmp/=10; + } + } + for (i=dn; i > 0 ; --i) { + digit=integer%10; + integer/=10; + str[i-1] = as_numbers[digit][0]; + } + str[dn] = '\0'; + return(dn); +} /*}}}*/ + +static void as_digest2hex(unsigned char *hex, const unsigned char *digest) +{ /*{{{*/ + char hc[] = "0123456789abcdef"; + unsigned int i, e=0; + + memset(hex, 0, 33*sizeof(unsigned char)); + + for(i = 0; i < 16; i++) { + hex[e++] = hc[(digest[i] >> 4) & 0x0f]; + hex[e++] = hc[ digest[i] & 0x0f]; + } + hex[32] = '\0'; +} /*}}}*/ + +static void as_submit(void) +{ /*{{{*/ + string_t msg_header, msg_content, stmp, msg; + struct as_queue *entry; + unsigned char digest[16], tmp[65], md5response[33]; + char i2s[AS_MAXTMPLEN]; + char buf[AS_BUFSIZE]; + unsigned long int idx, odx, submissions=0; + size_t n; + int sockfd; + int testval; + int ign=0; + + /* + * we need authentication setup, data to submit in our queue + * and the handshake must have taken place already. abort otherwise. + */ + as_lock(); + testval = (as_authinfo.user[0] == '\0' || as_authinfo.pass[0] == '\0' + || as_hsinfo.need_handshake == 1 || list_empty(&as_queue_head)); + as_unlock(); + if (testval) + return; + + /* + * creating the md5response described in the protocol v1.1 definition + * - create md5 checksum of $as_pass and covert it to hex + * - append the md5challenge received in handshake to the just created string + * - create md5 checksum of the concatenated string and convert it to hex. + * That's it. + */ + as_lock(); + md5_csum(as_authinfo.pass, strlen(as_authinfo.pass), digest); + as_digest2hex(tmp, digest); + strncpy(tmp+32, as_hsinfo.challenge.s, 32); + tmp[64] = '\0'; + md5_csum(tmp, 64, digest); + as_digest2hex(md5response, digest); + + str_init(&msg_header); + str_init(&msg_content); + str_init(&stmp); + str_init(&msg); + /* + * assemble our payload + */ + str_ncat(&msg_content, "u=", 2); str_scat(&msg_content, as_authinfo.user); + str_ncat(&msg_content, "&s=", 3); str_scat(&msg_content, md5response); + list_for_each_entry(entry, &as_queue_head, node) { + if (ign && entry->sc == AS_SC_DO_SUBMIT) { + entry->num-=ign; + entry->needrenumber=1; + } + if (entry->needrenumber) { + as_renumberentry(&(entry->s), entry->num); + entry->needrenumber=0; + } + if (entry->sc == AS_SC_DO_SUBMIT) { + str_cat(&msg_content, &(entry->s)); + entry->sc = AS_SC_SUBMITTED; + ++submissions; + d_print("scrobbling: \"%s\"\n", entry->s.s); + } + else { + ++ign; + d_print("ignoring: \"%s\"\n", entry->s.s); + } + } + if (!submissions) { + /* no submissions, eh? */ + as_unlock(); + goto cleanup; + } + as_uint2string(msg_content.len, i2s); /* note the length of our payload for later use */ + str_ncat(&msg_content, "\n", 1); + + /* create http/1.1 header for our submission */ + str_ncat(&msg_header, "POST ", 5); str_cat(&msg_header, &(as_hsinfo.path)); str_ncat(&msg_header, " HTTP/1.1\r\n", 11); + str_ncat(&msg_header, "Host: ", 6); str_cat(&msg_header, &(as_hsinfo.host)); str_ncat(&msg_header, "\r\n", 2); + str_ncat(&msg_header, AS_UASTRING, AS_UASTRINGLEN); + str_ncat(&msg_header, "Pragma: no-cache", 16); + str_ncat(&msg_header, "\r\nContent-Type: application/x-www-form-urlencoded", 49); + str_ncat(&msg_header, "\r\nContent-Length: ", 18); str_scat(&msg_header, i2s); + str_ncat(&msg_header, "\r\nConnection: CLOSE\r\n\r\n", 23); + + /* turn payload's special chars to utf8 */ + /*str_lat1_to_utf8(&msg, &msg_content); + str_cpy(&msg_content, &msg);*/ + +#ifdef AS_HEAVY_DEBUG + d_print("POSTING: \"%s%s\"\n", msg_header.s, msg_content.s); +#endif + + as_unlock(); + + /* + * connect to the server we got in the handshake + */ + if ( (sockfd = tcp_connect(as_hsinfo.host.s, as_hsinfo.port)) < 0 ) { + d_print("tcp_connect() failed!\n"); + as_lock(); + as_hsinfo.failed++; + as_unlock(); + goto cleanup; + } + + /* + * send our submission to the server + */ + str_cpy(&msg, &msg_header); + str_cat(&msg, &msg_content); + xwriten(sockfd, msg.s, msg.len); + str_free(&msg); + + msg_content.s[0] = '\0'; + msg_content.len = 0; + + /* read response to our submission */ + for (;;) { + if ( (n = xreadn(sockfd, buf, AS_BUFSIZE) ) <= 0) + break; + buf[n] = '\0'; + str_ncat(&msg_content, buf, n); + } + xclose(sockfd); + +#ifdef AS_HEAVY_DEBUG + d_print("Serverreply:\n\"%s\"\n", msg_content.s); +#endif + + /* parse the response */ + as_lock(); + if (str_ngetidx(&msg_content, 0, "\r\n\r\n", 4, &idx)) { + str_nreplace(&msg_content, 0, idx+4, "", 0); + } + else { + d_print("broken reply, couldn't find end of headers (\\r\\n\\r\\n)\n"); + as_hsinfo.failed++; + as_unlock(); + goto cleanup; + } + odx=0; + if (!str_ngetidx(&msg_content, odx, "\n", 1, &idx)) { + d_print("Broken input: \"%s\"\n", msg_content.s); + as_hsinfo.failed++; + as_unlock(); + goto cleanup; + } + str_ncpy(&stmp, msg_content.s+odx, idx-odx); + + /* OK/FAILED/BADAUTH */ + if (str_ngetidx(&stmp, 0, "OK", 2, NULL)) { + d_print("received an OK, good.\n"); + } + else if (str_ngetidx(&stmp, 0, "FAILED", 6, NULL)) { + stmp.s[idx] = '\0'; + d_print("received FAILED (%s)\n", stmp.s+odx); + as_hsinfo.failed++; + as_unlock(); + goto cleanup; + } + else if (str_ngetidx(&stmp, 0, "BADAUTH", 7, NULL)) { + d_print("received BADUSER, err, check as_user setting.\n"); + as_hsinfo.failed++; + as_unlock(); + goto cleanup; + } + else { + d_print("Unknown response: \"%s\"\n", stmp.s); + as_hsinfo.failed++; + as_unlock(); + goto cleanup; + } + + /* INTERVAL is optional here. */ + odx=idx+1; + if (str_ngetidx(&msg_content, odx, "\n", 1, &idx)) { + str_ncpy(&stmp, msg_content.s+odx, idx-odx); + if (str_ngetidx(&stmp, 0, "INTERVAL ", 9, NULL)) { + as_hsinfo.interval=atoi(stmp.s+9); + d_print("Our Interval is: %d\n", (int)as_hsinfo.interval); + } + } + as_unlock(); + + /* + * submission suceeded + * destroy the part of the queue, that holds these + */ + as_destroy_queue(AS_SC_SUBMITTED); +cleanup: + if (as_hsinfo.failed) { + /* resetting AS_SC_SUBMITTED, because something went wrong */ + list_for_each_entry(entry, &as_queue_head, node) { + if (entry->sc == AS_SC_SUBMITTED) + entry->sc = AS_SC_DO_SUBMIT; + } + } + str_free(&msg); + str_free(&stmp); + str_free(&msg_header); + str_free(&msg_content); +} /*}}}*/ + +static void as_gettimestring(char *str, int maxlen) +{ /*{{{*/ + const time_t now = time(NULL); + const struct tm *timeinfo = gmtime(&now); + strftime(str, maxlen, "%Y%%2d%m%%2d%d%%20%H%%3a%M%%3a%S", timeinfo); +} /*}}}*/ + +static void as_add_queue(string_t *trackartist, + string_t *trackalbum, + string_t *trackname, + int tracklen, int submissioncode) +{ /*{{{*/ + /* + * Description: + * This adds a track to the submission-queue. + * Only 10 Tracks are allowed to be submitted at once. + * So, if we run past this limit, delete this first item of the list + * and append this track to the end of the list. + */ + struct as_queue *ptr, *tmp; + char tmpstr[AS_MAXTMPLEN+1]; + int tmplen; + int queue_size; + + d_print("adding track to as_queue:\n"); + d_print(" Artist: %s\n", trackartist->s); + d_print(" Album : %s\n", trackalbum->s); + d_print(" Name : %s\n", trackname->s); + d_print(" Length: %d\n", tracklen); + + if (!(ptr=malloc(sizeof(struct as_queue)))) { + d_print("uark! out of memory? I give up."); + return; + } + + as_lock(); + + if (list_empty(&as_queue_head)) { + ptr->num=0; + list_add_tail(&(ptr->node), &as_queue_head); + } + else { + /* let's find our place in the queue */ + queue_size=as_count_entries(); + tmp = as_get_last_entry(); + if (queue_size >= AS_QUEUE_MAX) { + as_del_queue(as_queue_head.next); + ptr->num=AS_QUEUE_MAX; + list_add_tail(&(ptr->node), &as_queue_head); + list_for_each_entry(tmp, &as_queue_head, node) { + tmp->num--; + tmp->needrenumber=1; + } + } + else { + list_add_tail(&(ptr->node), &as_queue_head); + ptr->num=queue_size; + } + } + + str_init(&(ptr->s)); + ptr->sc=submissioncode; + ptr->needrenumber=0; + ptr->len=tracklen; + /* set up the request string */ + as_urlencode(trackartist); + as_urlencode(trackalbum); + as_urlencode(trackname); + as_addtag(ptr->s, trackartist, "&a["); + as_addtag(ptr->s, trackname, "&t["); + as_addtag(ptr->s, trackalbum, "&b["); + /* + * MusicBrainz ID, I'm just sending an empty one, because the + * protocol definition says "don't skip it" + */ + str_readya(&(ptr->s), 6); + str_ncat( &(ptr->s), "&m[", 3); + str_ncat( &(ptr->s), as_numbers[ptr->num], 1); + str_ncat( &(ptr->s), "]=", 2); + tmplen = as_uint2string(ptr->len, tmpstr); + as_addntag(ptr->s, tmpstr, tmplen, "&l["); + as_gettimestring(tmpstr, AS_MAXTMPLEN); + as_addntag(ptr->s, tmpstr, AS_MAXTMPLEN-1, "&i["); + as_unlock(); +} /*}}}*/ + +void as_hook(enum as_actions action) +{ /*{{{*/ + /* + * When do we need to call as_hook()? + * + stop + * + seek + * + next + * + prev + * + automatic song change + * + * What do we need to know? + * + track length + * + track position (we query player_info for that) + * + the action that called us + * + track name + * + artist + * + album + */ + static int lastwasinvalid = 0; /* these 2 static ints are there */ + static int alreadysubmitted = 0; /* to handle AS_SEEK properly. */ + int trackpos, tracklen; + int testval; + const char *tmp; + struct track_info *ti; + string_t trackartist, trackalbum, trackname; + +#ifdef AS_HEAVY_DEBUG + d_print("-- ping -----------------HOOOOK!1!!-------------------------------------\n"); +#endif + + as_lock(); + testval=running; + as_unlock(); + if (!testval) + return; + + if (lastwasinvalid) { + /* + * last was invalid; if something else than SEEK + * was done, this would submit the invalid track, so we + * reset lastwasinvalid here and return. + * + * if SEEK was invoked again, we just return, because + * the track was marked as invalid before. + */ + if (action != AS_SEEK) + lastwasinvalid = 0; + return; + } + if (alreadysubmitted) { + if (action != AS_SEEK) + alreadysubmitted = 0; + return; + } + + player_info_lock(); + trackpos=player_info.pos; + ti=cmus_get_track_info(player_info.filename); + + if (ti == NULL) { + player_info_unlock(); + return; + } + + tracklen=ti->duration; + + if (tracklen < AS_TRACK_MINLEN) { /* track is not long enough */ + player_info_unlock(); + return; + } + if (trackpos < AS_TRACK_MINSECS && (tracklen/2 > trackpos)) { /* track did not play long enough */ + /* + * if we were called in NEXT, PREV, STOP or *_ENTER + * we already did skip to the next track (or stopped), + * which means, we don't need to save this one for later review. + * So, we return. + */ + player_info_unlock(); + if (action != AS_SEEK) + return; + + lastwasinvalid = 1; + return; + } + + str_init(&trackartist); + str_init(&trackalbum); + str_init(&trackname); + tmp=comments_get_val(ti->comments, "artist"); + str_scpy(&trackartist, tmp); + tmp=comments_get_val(ti->comments, "album"); + str_scpy(&trackalbum, tmp); + tmp=comments_get_val(ti->comments, "title"); + str_scpy(&trackname, tmp); + player_info_unlock(); + + if (trackartist.len == 0 || trackalbum.len == 0 || trackname.len == 0) { + /* One of the tags we must supply is empty */ + return; + } + + as_add_queue(&trackartist, &trackalbum, &trackname, tracklen, AS_SC_DO_SUBMIT); + + if (action == AS_SEEK) + alreadysubmitted = 1; + + str_free(&trackartist); + str_free(&trackalbum); + str_free(&trackname); +} /*}}}*/ + +static void as_sleep(void) +{ /*{{{*/ + struct timespec r; + r.tv_sec=AS_SLEEPSEC; + r.tv_nsec=AS_SLEEPNANO; + nanosleep(&r, NULL); +} /*}}}*/ + +void *as_loop(void *arg) +{ /*{{{*/ + /* + * main audio scrobbler loop + * Here, all communication is done. + * Needed information is provided by *as_queue, + * which is filled by as_hook() + * + * TODO: AS_SC_DONT_SUBMIT cases must be handled (eg. the user used seek on a track before it fitted our needs). + */ + static int last_as_state=0; + int testval; + struct as_queue *e; + + for (;;) { +#ifdef AS_HEAVY_DEBUG + d_print("-- ping ----------------------------------------------------------------\n"); +#endif + as_update_timestamp(); + as_lock(); + testval = list_empty(&as_queue_head); + as_unlock(); + if (testval) { + /* nothing to submit */ + if (!running) { + /* + * running isn't set, + * seems like the user switched us off + */ + return(NULL); + } + as_sleep(); + } + else { + as_lock(); + list_for_each_entry(e, &as_queue_head, node) { + d_print("--DEBUG--\n"); + d_print(" Number: %d\n", e->num); + d_print(" SCode : %d\n", e->sc); + d_print(" Length: %d\n", e->len); + d_print(" NEEDRN: %d\n", e->needrenumber); + d_print(" string: %s\n\n", e->s.s); + } + as_unlock(); + as_sleep(); + as_lock(); + testval = as_hsinfo.need_handshake; + as_unlock(); + if (testval) { + as_handshake(); + } + as_lock(); + testval = as_hsinfo.need_handshake; + as_unlock(); + if (!testval) { + /* + * we are enabled, got data to submit and don't need to handshake. + * check, if we may (or need to force to) submit our data, + * and do so, if needed. + * + * XXX: as_submit_allowed() and as_submit_forced() do locking on their own. + */ + if (as_submit_allowed() || as_submit_forced()) { + as_submit(); + } + } + } + + as_lock(); + if (!as_enable && !last_as_state) { + /* + * user didn't enable us in the first place. + * so, set running to 0 and rerun loop, so we can exit + */ + running=0; + } + else if (!as_enable && last_as_state) { + /* as_enable was on before, so terminate session */ + running=0; + last_as_state=0; + } + else if (!last_as_state && as_enable) { + /* (re)start as session */ + running=1; + as_hsinfo.need_handshake=1; + last_as_state=1; + } + as_unlock(); + } +} /*}}}*/ + +void as_init(void) +{ /*{{{*/ + int rc; + as_lock(); + running=1; + str_init(&(as_hsinfo.challenge)); + str_init(&(as_hsinfo.host)); + str_init(&(as_hsinfo.path)); + as_hsinfo.need_handshake = 1; + as_hsinfo.interval = 0; + as_hsinfo.failed = 0; + as_lasttimestamp = 0; + as_unlock(); + rc = pthread_create(&as_thread, NULL, as_loop, NULL); + BUG_ON(rc); +} /*}}}*/ + +void as_exit(void) +{ /*{{{*/ + as_lock(); + running = 0; + as_destroy_queue(AS_SC_ALL); + str_free(&(as_hsinfo.challenge)); + str_free(&(as_hsinfo.host)); + str_free(&(as_hsinfo.path)); + as_unlock(); + pthread_join(as_thread, NULL); +} /*}}}*/ diff --git a/as.h b/as.h new file mode 100644 index 0000000..40b2ebd --- /dev/null +++ b/as.h @@ -0,0 +1,210 @@ +/* + * Copyright 2004-2006 Timo Hirvonen + * + * as.[ch] by Frank Terbeck + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef x__AS_H +#define x__AS_H + +/* + * audioscrobbler code for cmus {{{ + * (read: get cmus properly working with http://last.fm/ + * + * When are songs submitted? + * + Each song should be posted to the server when it is 50% + * or 240 seconds complete, whichever comes first. + * + * + If a user seeks (i.e. manually changes position) within a song + * before the song is due to be submitted, do not submit that song. + * + * + Songs with a duration of less than 30 seconds should not be + * submitted (so, we don't do that). + * + * + Do not try to guess info from the filename - if there are no + * tags, don't submit the file (okeydoke, herman). + * + * + If a MusicBrainz ID is present in the file (as defined here), + * then you should send it (whatever that is...[1]). + * + * + If a user is playing a stream instead of a regular file, do + * not submit that stream/song. + * + ********************************************************************* + * + * [1] About musicbrainz.org: + * I don't know what that is, nor do I care. Feel free to + * implement support for it, if you need it desperately. + *}}} + */ + +#include "list.h" +#include "options.h" +#include "strlib.h" +#include "track_info.h" + +/* #defines */ + +/* We are currently testing things, so "tst" is okay. */ +#define AS_UASTRING "User-Agent: AudioScrobbler/1.1 cmus-scrobbler code v0.01\r\n" +#define AS_UASTRINGLEN 58 +#define AS_CLIENT_ID "tst" +#define AS_PROTO "1.1" + +#define AS_HOSTNAME "post.audioscrobbler.com" +#define AS_HOSTPORT "80" +#define AS_HS_URI "/?hs=true" + +#define AS_BUFSIZE 4096 + +/* AS_QUEUE_MAX must of an element of [1..10] */ +#define AS_QUEUE_MAX 10 + +/* + * AS_FORCE_SUBMIT: + * if our queue holds this many entries, force submission, + * no matter what the last INTERVAL response said. + */ +#define AS_FORCE_SUBMIT 8 + +/* AS_DO_SUBMIT marks a track for submission */ +#define AS_SC_DO_SUBMIT 1 + +/* AS_DONT_SUBMIT is used if seek was used at the beginning of a track */ +#define AS_SC_DONT_SUBMIT 2 + +/* AS_SC_SUBMITTED marks tracks, that where included in a submission msg */ +#define AS_SC_SUBMITTED 4 + +/* AS_SC_ALL is the same as ORing together all other AS_SC_* macros */ +#define AS_SC_ALL 15 + +/* The minimum length of a track to be considered for submission */ +#define AS_TRACK_MINLEN 30 + +/* A track must have run 50% of its time or AS_TRACK_MINSECS seconds */ +#define AS_TRACK_MINSECS 240 + +/* AS_SLEEP* sets up the resolution of as_loop() */ +#define AS_SLEEPSEC 5 +#define AS_SLEEPNANO 0 + +/* + * for temporary static strings, that need to be long enough + * to hold an url_encoded time string (25+1); this is used for + * strings that are converted integers. + */ +#define AS_MAXTMPLEN 30 + +/* data types */ +enum as_actions { + AS_AUTONEXT=1, + AS_SEEK, + AS_NEXT, + AS_PREV, + AS_STOP, + AS_TREE_ENTER, + AS_SORTED_ENTER, + AS_PL_ENTER +}; + +struct as_queue { + struct list_head node; + string_t s; + unsigned int len; + unsigned short int sc; /* AS_SC_* */ + unsigned short int needrenumber; /* we must renumber 'string_t s' */ + unsigned short int num; /* number of the current entry */ +}; + +struct as_hsinfo { + unsigned int need_handshake; + unsigned int failed; + time_t interval; + string_t challenge; + string_t host; + string_t path; + char port[AS_MAXTMPLEN+1]; /* yes, port as a string. tcp_connect expects its port as char[] */ +}; + +struct as_authinfo { + char user[OPTION_MAX_SIZE]; + char pass[OPTION_MAX_SIZE]; +}; + +/* function prototypes */ +void as_hook(enum as_actions action); +void *as_loop(void *arg); +void as_exit(void); +void as_init(void); + +/* macros */ +#define as_addtag(dest, src, tag) \ + str_readya(&(dest), 6); \ + str_ncat( &(dest), tag, 3); \ + str_ncat( &(dest), as_numbers[ptr->num], 1); \ + str_ncat( &(dest), "]=", 2); \ + str_readya(&(dest), src->len); \ + str_cat( &(dest), src) + +#define as_addntag(dest, src, len, tag) \ + str_readya(&(dest), 6); \ + str_ncat( &(dest), tag, 3); \ + str_ncat( &(dest), as_numbers[ptr->num], 1); \ + str_ncat( &(dest), "]=", 2); \ + str_readya(&(dest), len); \ + str_ncat( &(dest), src, len) + +#define mustquote(c) \ + c == ' ' || \ + c == '!' || \ + c == '\"' || \ + c == '#' || \ + c == '%' || \ + c == '&' || \ + c == '\'' || \ + c == '(' || \ + c == ')' || \ + c == '*' || \ + c == '-' || \ + c == '/' || \ + c == ':' || \ + c == ';' || \ + c == '<' || \ + c == '=' || \ + c == '>' || \ + c == '?' || \ + c == '@' || \ + c == '[' || \ + c == '\\' || \ + c == ']' || \ + c == '^' || \ + c == '`' || \ + c == '{' || \ + c == '|' || \ + c == '}' || \ + c == '~' + +#define as_lock() cmus_mutex_lock(&as_mutex) +#define as_unlock() cmus_mutex_unlock(&as_mutex) + +/* exported variables */ +extern int as_enable; +extern struct as_authinfo as_authinfo; + +#endif /* x__AS_H */ diff --git a/cmus.c b/cmus.c index 50aafaf..55f1c53 100644 --- a/cmus.c +++ b/cmus.c @@ -14,6 +14,7 @@ #include "xmalloc.h" #include "xstrjoin.h" #include "debug.h" +#include "as.h" #include "load_dir.h" #include diff --git a/command_mode.c b/command_mode.c index 420c788..4c94da0 100644 --- a/command_mode.c +++ b/command_mode.c @@ -45,6 +45,7 @@ #include "list.h" #include "debug.h" #include "load_dir.h" +#include "as.h" #include "config/datadir.h" #include "help.h" @@ -454,6 +455,8 @@ static void cmd_seek(char *arg) int relative = 0; int seek = 0, sign = 1, count; + as_hook(AS_SEEK); + switch (*arg) { case '-': sign = -1; @@ -1191,6 +1194,7 @@ static void cmd_view(char *arg) static void cmd_p_next(char *arg) { + as_hook(AS_NEXT); cmus_next(); } @@ -1210,11 +1214,13 @@ static void cmd_p_play(char *arg) static void cmd_p_prev(char *arg) { + as_hook(AS_PREV); cmus_prev(); } static void cmd_p_stop(char *arg) { + as_hook(AS_STOP); player_stop(); } diff --git a/lib.c b/lib.c index 3c33780..6204585 100644 --- a/lib.c +++ b/lib.c @@ -8,6 +8,7 @@ #include "options.h" #include "xmalloc.h" #include "debug.h" +#include "as.h" #include #include @@ -358,6 +359,7 @@ struct track_info *sorted_set_selected(void) if (list_empty(&lib_editable.head)) return NULL; + as_hook(AS_SORTED_ENTER); window_get_sel(lib_editable.win, &sel); return lib_set_track(iter_to_sorted_track(&sel)); } diff --git a/md5.c b/md5.c new file mode 100644 index 0000000..5b31899 --- /dev/null +++ b/md5.c @@ -0,0 +1,408 @@ +/* + * RFC 1321 compliant MD5 implementation + * + * Copyright (C) 2003-2006 Christophe Devine + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License, version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +/* + * The MD5 algorithm was designed by Ron Rivest in 1991. + * + * http://www.ietf.org/rfc/rfc1321.txt + */ + +#ifndef _CRT_SECURE_NO_DEPRECATE +#define _CRT_SECURE_NO_DEPRECATE 1 +#endif + +#include +#include + +#include "md5.h" + +/* + * 32-bit integer manipulation macros (little endian) + */ +#ifndef GET_UINT32_LE +#define GET_UINT32_LE(n,b,i) \ +{ \ + (n) = ( (ulong) (b)[(i) ] ) \ + | ( (ulong) (b)[(i) + 1] << 8 ) \ + | ( (ulong) (b)[(i) + 2] << 16 ) \ + | ( (ulong) (b)[(i) + 3] << 24 ); \ +} +#endif +#ifndef PUT_UINT32_LE +#define PUT_UINT32_LE(n,b,i) \ +{ \ + (b)[(i) ] = (uchar) ( (n) ); \ + (b)[(i) + 1] = (uchar) ( (n) >> 8 ); \ + (b)[(i) + 2] = (uchar) ( (n) >> 16 ); \ + (b)[(i) + 3] = (uchar) ( (n) >> 24 ); \ +} +#endif + +/* + * Core MD5 functions + */ +void md5_starts( md5_context *ctx ) +{ + ctx->total[0] = 0; + ctx->total[1] = 0; + + ctx->state[0] = 0x67452301; + ctx->state[1] = 0xEFCDAB89; + ctx->state[2] = 0x98BADCFE; + ctx->state[3] = 0x10325476; +} + +static void md5_process( md5_context *ctx, uchar data[64] ) +{ + ulong X[16], A, B, C, D; + + GET_UINT32_LE( X[0], data, 0 ); + GET_UINT32_LE( X[1], data, 4 ); + GET_UINT32_LE( X[2], data, 8 ); + GET_UINT32_LE( X[3], data, 12 ); + GET_UINT32_LE( X[4], data, 16 ); + GET_UINT32_LE( X[5], data, 20 ); + GET_UINT32_LE( X[6], data, 24 ); + GET_UINT32_LE( X[7], data, 28 ); + GET_UINT32_LE( X[8], data, 32 ); + GET_UINT32_LE( X[9], data, 36 ); + GET_UINT32_LE( X[10], data, 40 ); + GET_UINT32_LE( X[11], data, 44 ); + GET_UINT32_LE( X[12], data, 48 ); + GET_UINT32_LE( X[13], data, 52 ); + GET_UINT32_LE( X[14], data, 56 ); + GET_UINT32_LE( X[15], data, 60 ); + +#define S(x,n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n))) + +#define P(a,b,c,d,k,s,t) \ +{ \ + a += F(b,c,d) + X[k] + t; a = S(a,s) + b; \ +} + + A = ctx->state[0]; + B = ctx->state[1]; + C = ctx->state[2]; + D = ctx->state[3]; + +#define F(x,y,z) (z ^ (x & (y ^ z))) + + P( A, B, C, D, 0, 7, 0xD76AA478 ); + P( D, A, B, C, 1, 12, 0xE8C7B756 ); + P( C, D, A, B, 2, 17, 0x242070DB ); + P( B, C, D, A, 3, 22, 0xC1BDCEEE ); + P( A, B, C, D, 4, 7, 0xF57C0FAF ); + P( D, A, B, C, 5, 12, 0x4787C62A ); + P( C, D, A, B, 6, 17, 0xA8304613 ); + P( B, C, D, A, 7, 22, 0xFD469501 ); + P( A, B, C, D, 8, 7, 0x698098D8 ); + P( D, A, B, C, 9, 12, 0x8B44F7AF ); + P( C, D, A, B, 10, 17, 0xFFFF5BB1 ); + P( B, C, D, A, 11, 22, 0x895CD7BE ); + P( A, B, C, D, 12, 7, 0x6B901122 ); + P( D, A, B, C, 13, 12, 0xFD987193 ); + P( C, D, A, B, 14, 17, 0xA679438E ); + P( B, C, D, A, 15, 22, 0x49B40821 ); + +#undef F + +#define F(x,y,z) (y ^ (z & (x ^ y))) + + P( A, B, C, D, 1, 5, 0xF61E2562 ); + P( D, A, B, C, 6, 9, 0xC040B340 ); + P( C, D, A, B, 11, 14, 0x265E5A51 ); + P( B, C, D, A, 0, 20, 0xE9B6C7AA ); + P( A, B, C, D, 5, 5, 0xD62F105D ); + P( D, A, B, C, 10, 9, 0x02441453 ); + P( C, D, A, B, 15, 14, 0xD8A1E681 ); + P( B, C, D, A, 4, 20, 0xE7D3FBC8 ); + P( A, B, C, D, 9, 5, 0x21E1CDE6 ); + P( D, A, B, C, 14, 9, 0xC33707D6 ); + P( C, D, A, B, 3, 14, 0xF4D50D87 ); + P( B, C, D, A, 8, 20, 0x455A14ED ); + P( A, B, C, D, 13, 5, 0xA9E3E905 ); + P( D, A, B, C, 2, 9, 0xFCEFA3F8 ); + P( C, D, A, B, 7, 14, 0x676F02D9 ); + P( B, C, D, A, 12, 20, 0x8D2A4C8A ); + +#undef F + +#define F(x,y,z) (x ^ y ^ z) + + P( A, B, C, D, 5, 4, 0xFFFA3942 ); + P( D, A, B, C, 8, 11, 0x8771F681 ); + P( C, D, A, B, 11, 16, 0x6D9D6122 ); + P( B, C, D, A, 14, 23, 0xFDE5380C ); + P( A, B, C, D, 1, 4, 0xA4BEEA44 ); + P( D, A, B, C, 4, 11, 0x4BDECFA9 ); + P( C, D, A, B, 7, 16, 0xF6BB4B60 ); + P( B, C, D, A, 10, 23, 0xBEBFBC70 ); + P( A, B, C, D, 13, 4, 0x289B7EC6 ); + P( D, A, B, C, 0, 11, 0xEAA127FA ); + P( C, D, A, B, 3, 16, 0xD4EF3085 ); + P( B, C, D, A, 6, 23, 0x04881D05 ); + P( A, B, C, D, 9, 4, 0xD9D4D039 ); + P( D, A, B, C, 12, 11, 0xE6DB99E5 ); + P( C, D, A, B, 15, 16, 0x1FA27CF8 ); + P( B, C, D, A, 2, 23, 0xC4AC5665 ); + +#undef F + +#define F(x,y,z) (y ^ (x | ~z)) + + P( A, B, C, D, 0, 6, 0xF4292244 ); + P( D, A, B, C, 7, 10, 0x432AFF97 ); + P( C, D, A, B, 14, 15, 0xAB9423A7 ); + P( B, C, D, A, 5, 21, 0xFC93A039 ); + P( A, B, C, D, 12, 6, 0x655B59C3 ); + P( D, A, B, C, 3, 10, 0x8F0CCC92 ); + P( C, D, A, B, 10, 15, 0xFFEFF47D ); + P( B, C, D, A, 1, 21, 0x85845DD1 ); + P( A, B, C, D, 8, 6, 0x6FA87E4F ); + P( D, A, B, C, 15, 10, 0xFE2CE6E0 ); + P( C, D, A, B, 6, 15, 0xA3014314 ); + P( B, C, D, A, 13, 21, 0x4E0811A1 ); + P( A, B, C, D, 4, 6, 0xF7537E82 ); + P( D, A, B, C, 11, 10, 0xBD3AF235 ); + P( C, D, A, B, 2, 15, 0x2AD7D2BB ); + P( B, C, D, A, 9, 21, 0xEB86D391 ); + +#undef F + + ctx->state[0] += A; + ctx->state[1] += B; + ctx->state[2] += C; + ctx->state[3] += D; +} + +void md5_update( md5_context *ctx, uchar *input, uint length ) +{ + ulong left, fill; + + if( ! length ) return; + + left = ctx->total[0] & 0x3F; + fill = 64 - left; + + ctx->total[0] += length; + ctx->total[0] &= 0xFFFFFFFF; + + if( ctx->total[0] < length ) + ctx->total[1]++; + + if( left && length >= fill ) + { + memcpy( (void *) (ctx->buffer + left), + (void *) input, fill ); + md5_process( ctx, ctx->buffer ); + length -= fill; + input += fill; + left = 0; + } + + while( length >= 64 ) + { + md5_process( ctx, input ); + length -= 64; + input += 64; + } + + if( length ) + { + memcpy( (void *) (ctx->buffer + left), + (void *) input, length ); + } +} + +static uchar md5_padding[64] = +{ + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +void md5_finish( md5_context *ctx, uchar digest[16] ) +{ + ulong last, padn; + ulong high, low; + uchar msglen[8]; + + high = ( ctx->total[0] >> 29 ) + | ( ctx->total[1] << 3 ); + low = ( ctx->total[0] << 3 ); + + PUT_UINT32_LE( low, msglen, 0 ); + PUT_UINT32_LE( high, msglen, 4 ); + + last = ctx->total[0] & 0x3F; + padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last ); + + md5_update( ctx, md5_padding, padn ); + md5_update( ctx, msglen, 8 ); + + PUT_UINT32_LE( ctx->state[0], digest, 0 ); + PUT_UINT32_LE( ctx->state[1], digest, 4 ); + PUT_UINT32_LE( ctx->state[2], digest, 8 ); + PUT_UINT32_LE( ctx->state[3], digest, 12 ); +} + +/* + * Output MD5(file contents), returns 0 if successful. + */ +int md5_file( char *filename, uchar digest[16] ) +{ + FILE *f; + size_t n; + md5_context ctx; + uchar buf[1024]; + + if( ( f = fopen( filename, "rb" ) ) == NULL ) + return( 1 ); + + md5_starts( &ctx ); + + while( ( n = fread( buf, 1, sizeof( buf ), f ) ) > 0 ) + md5_update( &ctx, buf, (uint) n ); + + md5_finish( &ctx, digest ); + + fclose( f ); + return( 0 ); +} + +/* + * Output MD5(buf) + */ +void md5_csum( uchar *buf, uint buflen, uchar digest[16] ) +{ + md5_context ctx; + + md5_starts( &ctx ); + md5_update( &ctx, buf, buflen ); + md5_finish( &ctx, digest ); +} + +/* + * Output HMAC-MD5(key,buf) + */ +void md5_hmac( uchar *key, uint keylen, uchar *buf, uint buflen, + uchar digest[16] ) +{ + uint i; + md5_context ctx; + uchar k_ipad[64]; + uchar k_opad[64]; + uchar tmpbuf[16]; + + memset( k_ipad, 0x36, 64 ); + memset( k_opad, 0x5C, 64 ); + + for( i = 0; i < keylen; i++ ) + { + if( i >= 64 ) break; + + k_ipad[i] ^= key[i]; + k_opad[i] ^= key[i]; + } + + md5_starts( &ctx ); + md5_update( &ctx, k_ipad, 64 ); + md5_update( &ctx, buf, buflen ); + md5_finish( &ctx, tmpbuf ); + + md5_starts( &ctx ); + md5_update( &ctx, k_opad, 64 ); + md5_update( &ctx, tmpbuf, 16 ); + md5_finish( &ctx, digest ); + + memset( k_ipad, 0, 64 ); + memset( k_opad, 0, 64 ); + memset( tmpbuf, 0, 16 ); + memset( &ctx, 0, sizeof( md5_context ) ); +} + +#ifdef SELF_TEST +/* + * RFC 1321 test vectors + */ +static char *md5_test_str[7] = +{ + "", + "a", + "abc", + "message digest", + "abcdefghijklmnopqrstuvwxyz", + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", + "12345678901234567890123456789012345678901234567890123456789012" \ + "345678901234567890" +}; + +static uchar md5_test_sum[7][16] = +{ + { 0xD4, 0x1D, 0x8C, 0xD9, 0x8F, 0x00, 0xB2, 0x04, + 0xE9, 0x80, 0x09, 0x98, 0xEC, 0xF8, 0x42, 0x7E }, + { 0x0C, 0xC1, 0x75, 0xB9, 0xC0, 0xF1, 0xB6, 0xA8, + 0x31, 0xC3, 0x99, 0xE2, 0x69, 0x77, 0x26, 0x61 }, + { 0x90, 0x01, 0x50, 0x98, 0x3C, 0xD2, 0x4F, 0xB0, + 0xD6, 0x96, 0x3F, 0x7D, 0x28, 0xE1, 0x7F, 0x72 }, + { 0xF9, 0x6B, 0x69, 0x7D, 0x7C, 0xB7, 0x93, 0x8D, + 0x52, 0x5A, 0x2F, 0x31, 0xAA, 0xF1, 0x61, 0xD0 }, + { 0xC3, 0xFC, 0xD3, 0xD7, 0x61, 0x92, 0xE4, 0x00, + 0x7D, 0xFB, 0x49, 0x6C, 0xCA, 0x67, 0xE1, 0x3B }, + { 0xD1, 0x74, 0xAB, 0x98, 0xD2, 0x77, 0xD9, 0xF5, + 0xA5, 0x61, 0x1C, 0x2C, 0x9F, 0x41, 0x9D, 0x9F }, + { 0x57, 0xED, 0xF4, 0xA2, 0x2B, 0xE3, 0xC9, 0x55, + 0xAC, 0x49, 0xDA, 0x2E, 0x21, 0x07, 0xB6, 0x7A } +}; + +/* + * Checkup routine + */ +int md5_self_test( void ) +{ + int i; + uchar md5sum[16]; + + for( i = 0; i < 7; i++ ) + { + printf( " MD5 test #%d: ", i + 1 ); + + md5_csum( (uchar *) md5_test_str[i], + strlen( md5_test_str[i] ), md5sum ); + + if( memcmp( md5sum, md5_test_sum[i], 16 ) != 0 ) + { + printf( "failed\n" ); + return( 1 ); + } + + printf( "passed\n" ); + } + + printf( "\n" ); + return( 0 ); +} +#else +int md5_self_test( void ) +{ + printf( "MD5 self-test not available\n\n" ); + return( 1 ); +} +#endif diff --git a/md5.h b/md5.h new file mode 100644 index 0000000..cefa7c4 --- /dev/null +++ b/md5.h @@ -0,0 +1,57 @@ +#ifndef _MD5_H +#define _MD5_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _STD_TYPES +#define _STD_TYPES + +#define uchar unsigned char +#define uint unsigned int +#define ulong unsigned long int + +#endif + +typedef struct +{ + ulong total[2]; + ulong state[4]; + uchar buffer[64]; +} +md5_context; + +/* + * Core MD5 functions + */ +void md5_starts( md5_context *ctx ); +void md5_update( md5_context *ctx, uchar *input, uint length ); +void md5_finish( md5_context *ctx, uchar digest[16] ); + +/* + * Output MD5(file contents), returns 0 if successful. + */ +int md5_file( char *filename, uchar digest[16] ); + +/* + * Output MD5(buf) + */ +void md5_csum( uchar *buf, uint buflen, uchar digest[16] ); + +/* + * Output HMAC-MD5(key,buf) + */ +void md5_hmac( uchar *key, uint keylen, uchar *buf, uint buflen, + uchar digest[16] ); + +/* + * Checkup routine + */ +int md5_self_test( void ); + +#ifdef __cplusplus +} +#endif + +#endif /* md5.h */ diff --git a/network.c b/network.c new file mode 100644 index 0000000..8392ff6 --- /dev/null +++ b/network.c @@ -0,0 +1,305 @@ +/* + * most of this code is base on or copied from the + * "UNIX Network Programming" Book by W.Richard Stevens, Bill Fenner + * and Andrew M. Rudoff (ISBN: 0-13-141155-1) + */ + +#include "debug.h" +#include "network.h" + +/* + * tcp_connect() + * connect to host:serv + */ +int +tcp_connect(const char *host, const char *serv) +{ /*{{{*/ + int sockfd, n; + struct addrinfo hints, *res, *ressave; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + if ( (n = getaddrinfo(host, serv, &hints, &res)) != 0) { + errno_msg("tcp_connect() error for %s, %s: %s", + host, serv, gai_strerror(n)); + return(-1); + } + ressave = res; + + do { + sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (sockfd < 0) + continue; /* ignore this one */ + + if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0) + break; /* success */ + + xclose(sockfd); /* ignore this one */ + } while ( (res = res->ai_next) != NULL); + + if (res == NULL) /* errno set from final connect() */ + errno_msg("tcp_connect() error for %s, %s", host, serv); + + freeaddrinfo(ressave); + + return(sockfd); +} /*}}}*/ + +/* + * sock_ntop() + * protocol (IPv4 vs. IPv6) independent version of inet_ntop + */ +char * +sock_ntop(const struct sockaddr *sa, socklen_t salen) +{ /*{{{*/ + char portstr[8]; + static char str[128]; /* Unix domain is largest */ + struct sockaddr_in *sin = (struct sockaddr_in *) sa; +#ifdef AF_INET6 + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa; +#endif + + switch (sa->sa_family) { + case AF_INET: + if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL) + return(NULL); + if (ntohs(sin->sin_port) != 0) { + snprintf(portstr, sizeof(portstr), ":%d", ntohs(sin->sin_port)); + strcat(str, portstr); + } + return(str); + +#ifdef AF_INET6 + case AF_INET6: + str[0] = '['; + if (inet_ntop(AF_INET6, &sin6->sin6_addr, str + 1, sizeof(str) - 1) == NULL) + return(NULL); + if (ntohs(sin6->sin6_port) != 0) { + snprintf(portstr, sizeof(portstr), "]:%d", ntohs(sin6->sin6_port)); + strcat(str, portstr); + return(str); + } + return (str + 1); +#endif + + default: + snprintf(str, sizeof(str), "sock_ntop: unknown AF_xxx: %d, len %d", + sa->sa_family, salen); + return(str); + } + return (NULL); +} /*}}}*/ + +/* wrapper for sock_ntop(), that handles error codes */ +char * +xsock_ntop(const struct sockaddr *sa, socklen_t salen) +{ /*{{{*/ + char *ptr; + + if ( (ptr = sock_ntop(sa, salen)) == NULL) + errno_msg("sock_ntop() error"); + return(ptr); +} /*}}}*/ + +/* + * sock_ntop_host() + * similar to sock_ntop(), but only returns the host portion (not the port) + */ +char * +sock_ntop_host(const struct sockaddr *sa, socklen_t salen) +{ /*{{{*/ + static char str[128]; /* Unix domain is largest */ + struct sockaddr_in *sin = (struct sockaddr_in *) sa; +#ifdef AF_INET6 + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa; +#endif + + switch (sa->sa_family) { + case AF_INET: + if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL) + return(NULL); + return(str); + +#ifdef AF_INET6 + case AF_INET6: + if (inet_ntop(AF_INET6, &sin6->sin6_addr, str, sizeof(str)) == NULL) + return(NULL); + return(str); +#endif + + default: + snprintf(str, sizeof(str), "sock_ntop_host: unknown AF_xxx: %d, len %d", + sa->sa_family, salen); + return(str); + } + return (NULL); +} /*}}}*/ + +/* wrapper for sock_ntop_host(), that handles error codes */ +char * +xsock_ntop_host(const struct sockaddr *sa, socklen_t salen) +{ /*{{{*/ + char *ptr; + + if ( (ptr = sock_ntop_host(sa, salen)) == NULL) + errno_msg("sock_ntop_host() error"); /* inet_ntop() sets errno */ + return(ptr); +} /*}}}*/ + +/* + * sock_get_port() + * protocol independent way to get the port of a socket + */ +int +sock_get_port(const struct sockaddr *sa, socklen_t salen) +{ /*{{{*/ + struct sockaddr_in *sin = (struct sockaddr_in *) sa; +#ifdef AF_INET6 + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa; +#endif + + switch (sa->sa_family) { + case AF_INET: + return(sin->sin_port); + +#ifdef AF_INET6 + case AF_INET6: + return(sin6->sin6_port); +#endif + } + return(-1); +} /*}}}*/ + +/* + * sock_set_addr() + * protocol independent way to set the address of a socket + */ +void +sock_set_addr(struct sockaddr *sa, socklen_t salen, const void *addr) +{ /*{{{*/ + struct sockaddr_in *sin = (struct sockaddr_in *) sa; +#ifdef AF_INET6 + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa; +#endif + + switch (sa->sa_family) { + case AF_INET: + memcpy(&sin->sin_addr, addr, sizeof(struct in_addr)); + return; + +#ifdef AF_INET6 + case AF_INET6: + memcpy(&sin6->sin6_addr, addr, sizeof(struct in6_addr)); + return; +#endif + } +} /*}}}*/ + +/* + * sock_set_addr() + * protocol independent way to set the port of a socket + */ +void +sock_set_port(struct sockaddr *sa, socklen_t salen, int port) +{ /*{{{*/ + struct sockaddr_in *sin = (struct sockaddr_in *) sa; +#ifdef AF_INET6 + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa; +#endif + + switch (sa->sa_family) { + case AF_INET: + sin->sin_port = port; + return; + +#ifdef AF_INET6 + case AF_INET6: + sin6->sin6_port = port; + return; +#endif + } + return; +} /*}}}*/ + +/* + * readn() + * read 'n' bytes from a filedescriptor + */ +ssize_t +readn(int fd, void *vptr, size_t n) +{ /*{{{*/ + size_t nleft; + ssize_t nread; + char *ptr; + + ptr = vptr; + nleft = n; + while (nleft > 0) { + if ( (nread = read(fd, ptr, nleft)) < 0) { + if (errno == EINTR) + nread = 0; /* and call read() again */ + else + return(-1); + } + else if (nread == 0) { + break; /* EOF */ + } + + nleft -= nread; + ptr += nread; + } + return(n - nleft); /* return >= 0 */ +} /*}}}*/ + +ssize_t +xreadn(int fd, void *ptr, size_t nbytes) +{ /*{{{*/ + ssize_t n; + + if ( (n = readn(fd, ptr, nbytes)) < 0) + errno_msg("readn() error"); + return(n); +} /*}}}*/ + +/* + * writen + * write 'n' bytes to a descriptor + */ +ssize_t +writen(int fd, const void *vptr, size_t n) +{ /*{{{*/ + size_t nleft; + ssize_t nwritten; + const char *ptr; + + ptr = vptr; + nleft = n; + while (nleft > 0) { + if ( (nwritten = write(fd, ptr, nleft)) <= 0) { + if (nwritten < 0 && errno == EINTR) + nwritten = 0; /* and call write() again */ + else + return(-1); /* error */ + } + + nleft -= nwritten; + ptr += nwritten; + } + return(n); +} /*}}}*/ + +void +xwriten(int fd, void *ptr, size_t nbytes) +{ /*{{{*/ + if (writen(fd, ptr, nbytes) != nbytes) + errno_msg("writen() error"); +} /*}}}*/ + +void +xclose(int fd) +{ /*{{{*/ + if (close(fd) == -1) + errno_msg("close() error"); +} /*}}}*/ diff --git a/network.h b/network.h new file mode 100644 index 0000000..1b9cd65 --- /dev/null +++ b/network.h @@ -0,0 +1,37 @@ +#ifndef x__NETWORK_H +#define x__NETWORK_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compiler.h" + +#define MAX_ERR_LINE 512 + +int tcp_connect(const char *host, const char *serv); +char *sock_ntop(const struct sockaddr *sa, socklen_t salen); +char *sock_ntop_host(const struct sockaddr *sa, socklen_t salen); +int sock_get_port(const struct sockaddr *sa, socklen_t salen); +void sock_set_addr(struct sockaddr *sa, socklen_t salen, const void *ptr); +void sock_set_port(struct sockaddr *sa, socklen_t salen, int port); +ssize_t readn(int fd, void *vptr, size_t n); +ssize_t writen(int fd, const void *vptr, size_t n); + +/* wrapper functions */ +char *xsock_ntop(const struct sockaddr *sa, socklen_t salen); +char *xsock_ntop_host(const struct sockaddr *sa, socklen_t salen); +ssize_t xreadn(int fd, void *ptr, size_t nbytes); +void xwriten(int fd, void *ptr, size_t nbytes); +void xclose(int fd); + +/* helper functions */ +void errno_msg(const char *fmt, ...) __FORMAT(1, 2); + +#endif /* x__NETWORK_H */ diff --git a/options.c b/options.c index f3a27a7..fc1990c 100644 --- a/options.c +++ b/options.c @@ -21,6 +21,7 @@ #include "file.h" #include "prog.h" #include "output.h" +#include "as.h" #include "config/datadir.h" #include @@ -256,6 +257,30 @@ static void set_lib_sort(unsigned int id, const char *buf) editable_set_sort_keys(&lib_editable, keys); } +static void get_as_pass(unsigned int id, char *buf) +{ + strncpy(buf, as_authinfo.pass, OPTION_MAX_SIZE-1); + buf[OPTION_MAX_SIZE-1]='\0'; +} + +static void set_as_pass(unsigned int id, const char *buf) +{ + strncpy(as_authinfo.pass, buf, OPTION_MAX_SIZE-1); + as_authinfo.pass[OPTION_MAX_SIZE-1]='\0'; +} + +static void get_as_user(unsigned int id, char *buf) +{ + strncpy(buf, as_authinfo.user, OPTION_MAX_SIZE-1); + buf[OPTION_MAX_SIZE-1]='\0'; +} + +static void set_as_user(unsigned int id, const char *buf) +{ + strncpy(as_authinfo.user, buf, OPTION_MAX_SIZE-1); + as_authinfo.pass[OPTION_MAX_SIZE-1]='\0'; +} + static void get_pl_sort(unsigned int id, char *buf) { strcpy(buf, pl_editable.sort_str); @@ -378,6 +403,31 @@ static void toggle_auto_reshuffle(unsigned int id) auto_reshuffle ^= 1; } +static void get_as_enable(unsigned int id, char *buf) +{ + strcpy(buf, bool_names[as_enable]); +} + +static void set_as_enable(unsigned int id, const char *buf) +{ + int old=as_enable; + if (parse_bool(buf, &as_enable) && !old) { + /* switch from off to on */ + as_init(); + return; + } + if (!as_enable && old) { + /* switch from on to off */ + as_exit(); + } +} + +static void toggle_as_enable(unsigned int id) +{ + if (as_enable) set_as_enable(id, "false"); + else set_as_enable(id, "true"); +} + static void get_continue(unsigned int id, char *buf) { strcpy(buf, bool_names[player_cont]); @@ -695,6 +745,9 @@ static const struct { opt_toggle_cb toggle; } simple_options[] = { DT(aaa_mode) + DT(as_enable) + DN(as_pass) + DN(as_user) DT(auto_reshuffle) DN(buffer_seconds) DT(confirm_run) @@ -757,9 +810,10 @@ static const struct { { "format_playlist", " %-20a %02n. %t%= %y %d " }, { "format_title", "%a - %l - %t (%y)" }, { "format_trackwin", " %02n. %t%= %y %d " }, - { "lib_sort" , "artist album discnumber tracknumber title filename" }, { "pl_sort", "" }, + { "as_pass", "" }, + { "as_user", "" }, { "id3_default_charset","ISO-8859-1" }, { NULL, NULL } }; diff --git a/pl.c b/pl.c index da460cf..dea4079 100644 --- a/pl.c +++ b/pl.c @@ -6,6 +6,7 @@ #include "editable.h" #include "options.h" #include "xmalloc.h" +#include "as.h" struct editable pl_editable; struct simple_track *pl_cur_track = NULL; @@ -86,6 +87,7 @@ struct track_info *pl_set_selected(void) if (list_empty(&pl_editable.head)) return NULL; + as_hook(AS_PL_ENTER); window_get_sel(pl_editable.win, &sel); return set_track(iter_to_simple_track(&sel)); } diff --git a/player.c b/player.c index a68f89d..55fd732 100644 --- a/player.c +++ b/player.c @@ -26,6 +26,7 @@ #include "xmalloc.h" #include "debug.h" #include "compiler.h" +#include "as.h" #include #include @@ -207,6 +208,7 @@ static inline unsigned int buffer_second_size(void) static inline int get_next(char **filename) { + as_hook(AS_AUTONEXT); return player_cbs->get_next(filename); } diff --git a/strlib.c b/strlib.c new file mode 100644 index 0000000..cb183d1 --- /dev/null +++ b/strlib.c @@ -0,0 +1,357 @@ +/* + * strlib.[ch] - a string handling library + * See strlib.h for notes about libowfat (the code this is based on). + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include +#include + +#include "debug.h" +#include "strlib.h" + +/* + * {con,de}structor + */ +void str_init(string_t *st) +{ /*{{{*/ + st->s=0; + st->len=st->all=0; +} /*}}}*/ + +void str_free(string_t *st) +{ /*{{{*/ + if (st->s && st->all > 0) free(st->s); + st->s=0; + st->len=st->all=0; +} /*}}}*/ + +/* + * allocation functions + */ +int str_ready(string_t *st, unsigned long int len) +{ /*{{{*/ + /* + * makes sure st can take up at least len bytes. + * this code allocates a few more bytes tthan needed, + * so that small additions can be made without realloc()s + * return 1 on success 0 otherwise (as in: realloc() fails) + */ + int c=len+(len>>3)+30; + char *tmp; + if (!st->s || st->alls, c))) return 0; + st->all=c; + st->s=tmp; + } + return 1; +} /*}}}*/ + +int str_readya(string_t *st, unsigned long int len) +{ /*{{{*/ + /* + * adds at least len bytes to the current length of *st + */ + if (st->s) { + if (st->len + len < len) return 0; + return str_ready(st, st->len + len); + } + else + return str_ready(st, len); +} /*}}}*/ + +/* + * string copy functions + */ +int str_ncpy(string_t *st, const char *buf, unsigned long int len) +{ /*{{{*/ + /* + * copy len bytes from *buf into *st + * return 1 on success, 0 otherwise. + */ + unsigned long int i; + if (buf == NULL || len == 0) { + st->len=0; + if (st->all != 0) + st->s[0] = '\0'; + return(1); + } + if (!str_ready(st, len)) return(0); + /* + * FIXME: these copying loops should be put into a sep. function + * to keep the code maintainable. + */ + for (i=0; i <= len - 1; i++) { + st->s[i] = buf[i]; + } + st->s[len] = '\0'; + st->len=len; + return(1); +} /*}}}*/ + +int str_cpy(string_t *st0, string_t *st1) +{ /*{{{*/ + /* + * copy *st1's string into *st0 + */ + return(str_ncpy(st0, st1->s, st1->len)); +} /*}}}*/ + +/* + * concatenation functions + */ +int str_ncat(string_t *st, const char *buf, unsigned long int len) +{ /*{{{*/ + /* + * add *buf to *st + */ + unsigned long int i; + if (len == 0 || buf == NULL) { + return(1); + } + if (!str_readya(st, len+1)) return(0); + for (i=st->len; i <= st->len + len - 1; i++) { + st->s[i] = buf[i - st->len]; + } + st->len+=len; + st->s[st->len]='\0'; + return(1); +} /*}}}*/ + +int str_cat(string_t *st0, string_t *st1) +{ /*{{{*/ + /* + * add *st1 to *st0 (*st1 must be allocated already); + */ + return(str_ncat(st0, st1->s, st1->len)); +} /*}}}*/ + +/* + * comparison functions + */ +int str_compstart(string_t *st, const char *buf) +{ /*{{{*/ + /* + * check if *st starts with *buf. + * return 1 if that's the case, 0 otherwise + */ + unsigned long int i; + unsigned long int len=strlen(buf) - 1; + if (st->len <= len) return(0); + for (i=0; i<=len; i++) + if (st->s[i]!=buf[i]) return(0); + return(1); +} /*}}}*/ + +int str_scomp(string_t *st, const char *buf) +{ /*{{{*/ + /* + * lexically compare *st with the *buf character array. + * return -1, 1, or 0 + */ + unsigned long int i, j; + for (i=0;;++i) { + if (i==st->len) + return( !buf[i] ? 0 : -1 ); + if (!buf[i]) + return 1; + j = (unsigned char)(st->s[i]) - (unsigned char)(buf[i]); + if (j) + return( j>0 ? 1 : -1 ); + } + return(0); +} /*}}}*/ + +int str_comp(string_t *st0, string_t *st1) +{ /*{{{*/ + /* + * lexically compare *st0 with *st1 + * return -1, 1 or 0 + */ + return(str_scomp(st0, st1->s)); +} /*}}}*/ + +/* + * replacement functions + */ +int str_nreplace(string_t *st, unsigned long int idx, unsigned long int offset, const char *replacement, unsigned long int rlen) +{ /*{{{*/ + /* + * replace offset bytes from idx in *st with len bytes from + * replacement; return 1 on success, 0 on memory problems, negative + * values upon logical errors in parameters. + */ + char *rest; + int restlen; + + if (idx > st->len) + return(-2); + if (idx+offset > st->len) + return(-3); + + restlen = st->len - idx - offset; + if (!(rest = malloc(restlen * sizeof(char) + 1))) + return(0); + + strncpy(rest, st->s+idx+offset, restlen); /* st->s+st->len is '\0' */ + rest[restlen] = '\0'; + if (rlen > offset) { + /* we might need to allocate more memory */ + if (!str_readya(st, rlen-offset)) { + free(rest); + return(0); + } + } + st->s[idx] = '\0'; + st->len = idx; + str_ncat(st, replacement, rlen); + str_ncat(st, rest, restlen); + free(rest); + return(1); +} /*}}}*/ + +int str_replace(string_t *st, unsigned long int idx, unsigned long int offset, string_t *replacement) +{ /*{{{*/ + return(str_nreplace(st, idx, offset, replacement->s, replacement->len)); +} /*}}}*/ + +/* + * search functions + */ +int +str_ngetidx(string_t *haystack, unsigned long int offset, const char *needle, unsigned long int nlen, unsigned long int *idx) +{ /*{{{*/ + unsigned long int i, j; + if (nlen == 0 || haystack->len == 0 || haystack->len < offset || nlen > haystack->len-offset) + return(0); + + for (i=0; i<= haystack->len-offset-1; ++i) { + if (haystack->s[offset+i] == needle[0]) { + /* potential hit */ + if (nlen == 1) { + if (idx != NULL) + *idx=i+offset; + return(1); + } + for (j=1; j<=nlen-1; ++j) { + if (haystack->s[offset+i+j] != needle[j]) + break; + if (haystack->s[offset+i+j] == needle[j] && j == nlen-1) { + if (idx != NULL) + *idx=i+offset; + return(1); + } + } + } + } + return (0); +} /*}}}*/ + +int +str_getidx(string_t *haystack, unsigned long int offset, string_t *needle, unsigned long int *idx) +{ /*{{{*/ + return(str_ngetidx(haystack, offset, needle->s, needle->len, idx)); +} /*}}}*/ + +/* + * conversion functions + */ +int +str_nlat1_to_utf8(string_t *converted, const unsigned char *input, unsigned long len) +{ /*{{{*/ + unsigned long int nc=0, olen=0, ilen; + unsigned char *in; + + in = (unsigned char *)input; + ilen = 0; + while (ilen++ <= len) + if (*(in++) > 0x7f) + ++nc; + + if (nc) { + if (!str_readya(converted, nc)) + return(0); + } + else + return(1); + + in = (unsigned char *)input; + ilen = 0; + while (ilen++ <= len) { + if (*in > 0x7f) { + converted->s[olen++] = 0xC0 | (*in >> 6); + *in = (0x80 | (*in & 0x3F)); + } + converted->s[olen++] = *in; + in++; + } + converted->s[olen] = '\0'; + converted->len = olen; + return(1); +} /*}}}*/ + +int +str_lat1_to_utf8(string_t *converted, string_t *input) +{ /*{{{*/ + return(str_nlat1_to_utf8(converted, input->s, input->len)); +} /*}}}*/ + +/* + * Perl lookalikes + */ +int str_chop(string_t *st) +{ /*{{{*/ + /* + * remove last character of *st + * return removed byte or -1 if *st if empty + */ + int r; + if (!st->len) + return(-1); + r=st->s[st->len-1]; + st->s[st->len--]=0; + return(r); +} /*}}}*/ + +int str_chomp(string_t *st) +{ /*{{{*/ + /* + * remove "\r" "\n" "\r\n" from *st's end. + * return count of rem. chars (poss. values: 0,1,2); + */ + if (!st->len) + return(0); + if (st->len==1) { + if (st->s[st->len-1] == '\r' || st->s[st->len-1] == '\n') { + st->s[0]=0; st->len=0; + return(1); + } + else + return(0); + } + if (st->s[st->len-1] == '\r' || st->s[st->len-1] == '\n') { + if (st->s[st->len-1] == '\n' && st->s[st->len-2]) { + st->s[st->len-2]=0; + st->len-=2; + return(2); + } + st->s[--st->len]=0; + return(1); + } + return(0); +} /*}}}*/ diff --git a/strlib.h b/strlib.h new file mode 100644 index 0000000..31c0194 --- /dev/null +++ b/strlib.h @@ -0,0 +1,88 @@ +/* + * strlib.[ch] - a string handling library + * Some time ago, I've been writing a few functions to handle strings, + * mainly as an exercise. When someone hinted me at fefe's libowfat[1], + * I found that the basic idea was quite similar, but libowfat was + * doing a lot of things quite smart, so I rewrote my functions while + * looking at the libowfat sources. So, this code is heavily based + * on these sources. + * + * Note, that libowfat is not only about strings, it does lots of + * things, you might find useful. Take a look at it! + * + * [1] http://www.fefe.de/libowfat/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef x__STRLIB_H +#define x__STRLIB_H + +/* internal structure */ +typedef struct { + /* + * s: string + * len: length of string + * all: allocated bytes for *s + */ + char *s; + unsigned long int len; + unsigned long int all; +} string_t; + +/* {con,de}structor */ +void str_init(string_t *st); +void str_free(string_t *st); + +/* allocation functions */ +int str_ready(string_t *st, unsigned long int len); +int str_readya(string_t *st, unsigned long int len); + +/* string copy functions */ +int str_ncpy(string_t *st, const char *buf, unsigned long int len); +#define str_scpy(st, buf) str_ncpy(st, buf, ( buf == NULL ? 0 : strlen(buf) )) +int str_cpy(string_t *st0, string_t *st1); + +/* concatenation functions */ +int str_ncat(string_t *st, const char *buf, unsigned long int len); +#define str_scat(st, buf) str_ncat(st, buf, ( buf == NULL ? 0 : strlen(buf) )) +int str_cat(string_t *st0, string_t *st1); + +/* comparison functions */ +int str_compstart(string_t *st, const char *buf); +int str_scomp(string_t *st, const char *buf); +int str_comp(string_t *st0, string_t *st1); + +/* replacement functions */ +int str_nreplace(string_t *st, unsigned long int idx, unsigned long int offset, const char *replacement, unsigned long int rlen); +#define str_sreplace(st, idx, offset, repl) str_nreplace(st, idx, offset, repl, strlen(repl)) +int str_replace(string_t *st, unsigned long int idx, unsigned long int offset, string_t *replacement); + +/* search functions */ +int str_ngetidx(string_t *haystack, unsigned long int offset, const char *needle, unsigned long int nlen, unsigned long int *idx); +#define str_sgetidx(hs, offset, n, idx) str_ngetidx(hs, offset, n, strlen(n), idx) +int str_getidx(string_t *haystack, unsigned long int offset, string_t *needle, unsigned long int *idx); + +/* conversion functions */ +int str_nlat1_to_utf8(string_t *converted, const unsigned char *input, unsigned long len); +#define str_slat1_to_utf8(c, s) str_nlat1_to_utf8(c, s, strlen(s)) +int str_lat1_to_utf8(string_t *converted, string_t *input); + +/* Perl lookalikes */ +int str_chop(string_t *st); +int str_chomp(string_t *st); + +#endif /* x__STRLIB_H */ diff --git a/tree.c b/tree.c index 27cca61..054bf53 100644 --- a/tree.c +++ b/tree.c @@ -8,6 +8,7 @@ #include "comment.h" #include "utils.h" #include "debug.h" +#include "as.h" struct searchable *tree_searchable; struct window *lib_tree_win; @@ -365,6 +366,7 @@ struct track_info *tree_set_selected(void) if (list_empty(&lib_artist_head)) return NULL; + as_hook(AS_TREE_ENTER); tree_win_get_selected(&artist, &album); if (album == NULL) { /* only artist selected, track window is empty diff --git a/ui_curses.c b/ui_curses.c index 031196e..87eac1d 100644 --- a/ui_curses.c +++ b/ui_curses.c @@ -1312,6 +1312,32 @@ void error_msg(const char *format, ...) } } +/* + * errno_msg() + * print an error message plus the current errno message + */ +void +errno_msg(const char *format, ...) +{ + /* + * This function is a little limited, as is outputs lines of + * MAX_ERR_LINE chars at most. It's useful, though, and could + * probably be used more widespread. + */ + char buf[MAX_ERR_LINE]; + va_list ap; + int errno_save = errno; + int n; + + va_start(ap, format); + vsnprintf(buf, MAX_ERR_LINE, format, ap); + va_end(ap); + n = strlen(buf); + snprintf(buf + n, MAX_ERR_LINE - n, ": %s", strerror(errno_save)); + strcat(buf, "\n"); + error_msg(buf); +} + int yes_no_query(const char *format, ...) { char buffer[512]; diff --git a/ui_curses.h b/ui_curses.h index 55e07e6..7f43960 100644 --- a/ui_curses.h +++ b/ui_curses.h @@ -23,6 +23,8 @@ #include "search.h" #include "compiler.h" +#define MAX_ERR_LINE 512 + enum ui_input_mode { NORMAL_MODE, COMMAND_MODE, @@ -44,6 +46,7 @@ void update_colors(void); void update_full(void); void info_msg(const char *format, ...) __FORMAT(1, 2); void error_msg(const char *format, ...) __FORMAT(1, 2); +void errno_msg(const char *format, ...) __FORMAT(1, 2); int yes_no_query(const char *format, ...) __FORMAT(1, 2); void search_not_found(void); void set_view(int view);