Discussion:
[PATCH 1/3] audioscrobbler core library
Petr Holasek
2011-08-27 15:01:13 UTC
Permalink
---
as.c | 931 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
as.h | 210 +++++++++++++++
2 files changed, 1141 insertions(+), 0 deletions(-)
create mode 100644 as.c
create mode 100644 as.h

diff --git a/as.c b/as.c
new file mode 100644
index 0000000..d39ed87
--- /dev/null
+++ b/as.c
@@ -0,0 +1,931 @@
+/*
+ * Copyright 2004-2006 Timo Hirvonen
+ *
+ * as.[ch] by Frank Terbeck <***@bewatermyfriend.org>
+ * and Clay Barnes <***@hci-matters.com>
+ *
+ * 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 <pthread.h>
+#include <time.h>
+
+#include "as.h"
+#include "cmus.h"
+#include "debug.h"
+#include "keyval.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];
+ ssize_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 <= 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;
+ ssize_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;
+ 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;
+
+ if (player_info.ti == NULL) {
+ player_info_unlock();
+ return;
+ }
+
+ tracklen=player_info.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=keyvals_get_val(player_info.ti->comments, "artist");
+ str_scpy(&trackartist, tmp);
+ tmp=keyvals_get_val(player_info.ti->comments, "album");
+ str_scpy(&trackalbum, tmp);
+ tmp=keyvals_get_val(player_info.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..e1dc638
--- /dev/null
+++ b/as.h
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2004-2006 Timo Hirvonen
+ *
+ * as.[ch] by Frank Terbeck <***@bewatermyfriend.org>
+ *
+ * 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 */
--
1.7.6
Petr Holasek
2011-08-27 15:01:15 UTC
Permalink
---
Makefile | 10 +++++-----
cmus.c | 1 +
command_mode.c | 6 ++++++
lib.c | 2 ++
options.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
pl.c | 2 ++
player.c | 2 ++
tree.c | 2 ++
ui_curses.c | 26 ++++++++++++++++++++++++++
ui_curses.h | 3 +++
10 files changed, 104 insertions(+), 5 deletions(-)

diff --git a/Makefile b/Makefile
index 7e00434..7e3db00 100644
--- a/Makefile
+++ b/Makefile
@@ -20,8 +20,8 @@ CFLAGS += -D_FILE_OFFSET_BITS=64

CMUS_LIBS = $(PTHREAD_LIBS) $(NCURSES_LIBS) $(ICONV_LIBS) $(DL_LIBS) $(DISCID_LIBS) -lm $(COMPAT_LIBS)

-input.o main.o ui_curses.o pulse.lo: .version
-input.o main.o ui_curses.o pulse.lo: CFLAGS += -DVERSION=\"$(VERSION)\"
+as.o input.o main.o ui_curses.o pulse.lo: .version
+as.o input.o main.o ui_curses.o pulse.lo: CFLAGS += -DVERSION=\"$(VERSION)\"
main.o server.o: CFLAGS += -DDEFAULT_PORT=3000
discid.o: CFLAGS += $(DISCID_CFLAGS)

@@ -31,13 +31,13 @@ discid.o: CFLAGS += $(DISCID_CFLAGS)

# programs {{{
cmus-y := \
- ape.o browser.o buffer.o cache.o cmdline.o cmus.o command_mode.o comment.o \
+ ape.o as.o browser.o buffer.o cache.o cmdline.o cmus.o command_mode.o comment.o \
channelmap.o convert.lo debug.o discid.o editable.o expr.o filters.o \
format_print.o gbuf.o glob.o help.o history.o http.o id3.o input.o job.o \
- keys.o keyval.o lib.o load_dir.o locking.o mergesort.o misc.o options.o \
+ keys.o keyval.o lib.o load_dir.o locking.o mergesort.o network.o md5.o misc.o options.o \
output.o pcm.o pl.o play_queue.o player.o \
rbtree.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_info.o tree.o u_collate.o uchar.o ui_curses.o \
window.o worker.o xstrjoin.o

diff --git a/cmus.c b/cmus.c
index d156f06..008579c 100644
--- a/cmus.c
+++ b/cmus.c
@@ -32,6 +32,7 @@
#include "options.h"
#include "xmalloc.h"
#include "debug.h"
+#include "as.h"
#include "load_dir.h"
#include "ui_curses.h"
#include "cache.h"
diff --git a/command_mode.c b/command_mode.c
index ed7a5c8..f1d8ff3 100644
--- a/command_mode.c
+++ b/command_mode.c
@@ -44,6 +44,7 @@
#include "list.h"
#include "debug.h"
#include "load_dir.h"
+#include "as.h"
#include "config/datadir.h"
#include "help.h"
#include "op.h"
@@ -472,6 +473,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;
@@ -1274,6 +1277,7 @@ static void cmd_push(char *arg)

static void cmd_p_next(char *arg)
{
+ as_hook(AS_NEXT);
cmus_next();
}

@@ -1293,11 +1297,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 f92451e..0541111 100644
--- a/lib.c
+++ b/lib.c
@@ -23,6 +23,7 @@
#include "xmalloc.h"
#include "rbtree.h"
#include "debug.h"
+#include "as.h"
#include "utils.h"
#include "ui_curses.h" /* cur_view */

@@ -398,6 +399,7 @@ static struct tree_track *sorted_get_selected(void)

struct track_info *sorted_set_selected(void)
{
+ as_hook(AS_SORTED_ENTER);
return lib_set_track(sorted_get_selected());
}

diff --git a/options.c b/options.c
index dc315ec..f422c06 100644
--- a/options.c
+++ b/options.c
@@ -34,6 +34,7 @@
#include "file.h"
#include "prog.h"
#include "output.h"
+#include "as.h"
#include "input.h"
#include "config/datadir.h"
#include "track_info.h"
@@ -341,6 +342,30 @@ static void set_lib_sort(unsigned int id, const char *buf)
}
}

+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);
@@ -481,6 +506,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]);
@@ -942,6 +992,9 @@ static const struct {
unsigned int flags;
} simple_options[] = {
DT(aaa_mode)
+ DT(as_enable)
+ DN(as_pass)
+ DN(as_user)
DT(auto_reshuffle)
DN_FLAGS(device, OPT_PROGRAM_PATH)
DN(buffer_seconds)
@@ -1018,6 +1071,8 @@ static const struct {

{ "lib_sort" , "albumartist date 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 8ecaf93..c98ca26 100644
--- a/pl.c
+++ b/pl.c
@@ -20,6 +20,7 @@
#include "editable.h"
#include "options.h"
#include "xmalloc.h"
+#include "as.h"

struct editable pl_editable;
struct simple_track *pl_cur_track = NULL;
@@ -101,6 +102,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 36809ef..2c598f0 100644
--- a/player.c
+++ b/player.c
@@ -25,6 +25,7 @@
#include "xmalloc.h"
#include "debug.h"
#include "compiler.h"
+#include "as.h"
#include "options.h"

#include <stdio.h>
@@ -393,6 +394,7 @@ static inline unsigned int buffer_second_size(void)

static inline int get_next(struct track_info **ti)
{
+ as_hook(AS_AUTONEXT);
return player_cbs->get_next(ti);
}

diff --git a/tree.c b/tree.c
index 9c384e9..2b90f10 100644
--- a/tree.c
+++ b/tree.c
@@ -23,6 +23,7 @@
#include "debug.h"
#include "mergesort.h"
#include "options.h"
+#include "as.h"
#include "u_collate.h"
#include "rbtree.h"

@@ -470,6 +471,7 @@ struct tree_track *tree_get_selected(void)
if (rb_root_empty(&lib_artist_root))
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 361bf7a..6fa2b0e 100644
--- a/ui_curses.c
+++ b/ui_curses.c
@@ -1453,6 +1453,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("%s", buf);
+}
+
int yes_no_query(const char *format, ...)
{
char buffer[512];
diff --git a/ui_curses.h b/ui_curses.h
index ad3fff2..0ae706b 100644
--- a/ui_curses.h
+++ b/ui_curses.h
@@ -22,6 +22,8 @@
#include "search.h"
#include "compiler.h"

+#define MAX_ERR_LINE 512
+
enum ui_input_mode {
NORMAL_MODE,
COMMAND_MODE,
@@ -51,6 +53,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);
--
1.7.6
Petr Holasek
2011-08-27 15:01:14 UTC
Permalink
---
md5.c | 408 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
md5.h | 59 +++++++++
network.c | 305 +++++++++++++++++++++++++++++++++++++++++++++
network.h | 37 ++++++
strlib.c | 357 +++++++++++++++++++++++++++++++++++++++++++++++++++++
strlib.h | 88 +++++++++++++
6 files changed, 1254 insertions(+), 0 deletions(-)
create mode 100644 md5.c
create mode 100644 md5.h
create mode 100644 network.c
create mode 100644 network.h
create mode 100644 strlib.c
create mode 100644 strlib.h

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 <string.h>
+#include <stdio.h>
+
+#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..0f9c5c6
--- /dev/null
+++ b/md5.h
@@ -0,0 +1,59 @@
+#ifndef _MD5_H
+#define _MD5_H
+
+#include <sys/types.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 <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <errno.h>
+
+#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/strlib.c b/strlib.c
new file mode 100644
index 0000000..90fa0e0
--- /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 <stdlib.h>
+#include <string.h>
+
+#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->all<len) {
+ /* mem needed */
+ if (!(tmp = realloc(st->s, 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 */
--
1.7.6
Johannes Weißl
2011-08-27 18:15:23 UTC
Permalink
Hi Petr,
This patches introduces last.fm scrobbling support into cmus.
I've only ported old patch from https://github.com/hunner/cmus-audioscrobbler.git
for top of upstream git repo, so all credits to people mentioned in
source files.
Thanks for doing this work! However, there were several problems with
the patch that prevented inclusion, e.g. it:
- used own string and network handling functions
- used threading (Timo wanted it in main_loop)
- was huge
- code style differed from cmus
http://sourceforge.net/mailarchive/message.php?msg_id=24715749

This is why the author and former maintainer of cmus, Timo Hirvonen,
rejected the patch.
Petr Holášek
2011-08-28 08:17:15 UTC
Permalink
Hi Johannes,
Post by Johannes Weißl
Hi Petr,
This patches introduces last.fm scrobbling support into cmus.
I've only ported old patch from https://github.com/hunner/cmus-audioscrobbler.git
for top of upstream git repo, so all credits to people mentioned in
source files.
Thanks for doing this work! However, there were several problems with
- used own string and network handling functions
- used threading (Timo wanted it in main_loop)
- was huge
- code style differed from cmus
http://sourceforge.net/mailarchive/message.php?msg_id=24715749
This is why the author and former maintainer of cmus, Timo Hirvonen,
rejected the patch.
Johannes Weißl
2011-08-28 08:28:39 UTC
Permalink
Hi Petr,
I fully respect issues described above, but for my use it is better
when it is hardcoded into player. But you're probably right in the
fact that scripts provide more generality.
I'm interested, why is it better for your use to hardcode it? I always
thought scripts are the best for everyone, but maybe I've overlooked
something...
Thanks for the clarification!
Sorry for not documenting the reasons anywhere! I will update
http://cmus.sourceforge.net/wiki/doku.php?id=external_patches

Do you have a github/gitorious account or any other place where you can upload
your patch? So we can put a link to it for others who want to use it...


Johannes
Petr Holášek
2011-08-29 17:48:44 UTC
Permalink
Post by Johannes Weißl
Hi Petr,
I fully respect issues described above, but for my use it is better
when it is hardcoded into player. But you're probably right in the
fact that scripts provide more generality.
I'm interested, why is it better for your use to hardcode it? I always
thought scripts are the best for everyone, but maybe I've overlooked
something...
I had some issues with scripts functionality and only patch worked
for me well. But it was about a year ago, maybe I overlooked something,
so I am going to try script solution again.
And I strongly agree that built-in network/string handling in
scripting languages
are really more elegant solution than new modules introduced by patch.
Post by Johannes Weißl
Thanks for the clarification!
Sorry for not documenting the reasons anywhere! I will update
http://cmus.sourceforge.net/wiki/doku.php?id=external_patches
Do you have a github/gitorious account or any other place where you can upload
your patch? So we can put a link to it for others who want to use it...
I'll try to push an update into https://github.com/hunner/cmus-audioscrobbler
and let you know when it will be done.

Thanks for your answers!
Petr
Post by Johannes Weißl
Johannes
Loading...