Ejemplo n.º 1
0
void handle_keyboard_input() {
	int key, result;
	char customkey[8] = { 0 }, * marked = NULL;

	fflush(stderr);
	if((key = fgetc(stdin)) == -1)
		return;

	if(key == 27) {
		int ch;
		while((ch = fgetc(stdin)) != -1 && !strchr("ABCDEFGHMPQRSZojmk~", ch));
		return;
	}

	snprintf(customkey, sizeof(customkey), "key0x%02X", key & 0xFF);
	if(haskey(& rc, customkey))
		run(meta(value(& rc, customkey), M_SHELLESC, & track));

	switch(key) {
		case 'l':
			puts(rate("L") ? "Loved." : "Sorry, failed.");
			break;

		case 'U':
			puts(rate("U") ? "Unloved." : "Sorry, failed.");
			break;

		case 'B':
			puts(rate("B") ? "Banned." : "Sorry, failed.");
			fflush(stdout);
			enable(INTERRUPTED);
			kill(playfork, SIGUSR1);
			break;

		case 'n':
			rate("S");
			break;

		case 'q':
			if(haskey(& rc, "delay-change")) {
				delayquit = !delayquit;
				if(delayquit)
					fputs("Going to quit soon.\n", stderr);
				else
					fputs("Delayed quit cancelled.\n", stderr);
			}
			break;

		case 'Q':
			quit();

		case 'i':
			if(playfork) {
				const char * path = rcpath("i-template");
				if(path && !access(path, R_OK)) {
					char ** template = slurp(path);
					if(template != NULL) {
Ejemplo n.º 2
0
static void cleanup(void) {
	rmsckif();

	if(haskey(& rc, "unix") && getpid() == ppid)
		unlink(value(& rc, "unix"));

	empty(& data);
	empty(& rc);
	empty(& track);

	freelist(& playlist);

	if(current_station) {
		free(current_station);
		current_station = NULL;
	}

	if(subfork)
		waitpid(subfork, NULL, 0);

	dump_queue();

	/* Clean cache. */
	if(!access(rcpath("cache"), R_OK | W_OK | X_OK)) {
		const char * cache = rcpath("cache");
		DIR * directory = opendir(cache);

		if(directory != NULL) {
			time_t expiry = 24 * 60 * 60; /* Expiration after 24h. */
			struct dirent * entry;
			struct stat status;
			char path[PATH_MAX];

			if(haskey(& rc, "expiry"))
				expiry = atoi(value(& rc, "expiry"));

			while((entry = readdir(directory)) != NULL) {
				snprintf(path, sizeof(path), "%s/%s", cache, entry->d_name);

				if(!stat(path, & status)) {
					if(status.st_mtime < (time(NULL) - expiry)) {
						unlink(path);
					}
				}
			}

			closedir(directory);
		}
	}

	if(playfork)
		kill(playfork, SIGUSR1);
}
Ejemplo n.º 3
0
unsigned resolve_artist(const char * name) {
  unsigned artist_id = 0;
  char * response = fetch(
      makeurl("http://www.last.fm/ajax/getResource?type=artist&name=%s", name),
      NULL, NULL, NULL
      );

  debug("response=<\n%s\n>\n", response);

  if(response != NULL) {
    struct hash h = { 0, NULL };
    json_value * json;

    assert((json = json_parse(response)) != NULL);

    json_hash(json, & h, NULL);

    if(haskey(& h, "resource.id"))
      artist_id = atol(value(& h, "resource.id"));

    json_value_free(json);
    empty(& h);
  }

  free(response);

  return artist_id;
}
Ejemplo n.º 4
0
void preview(struct playlist list) {
	struct tracknode * node;
	unsigned n = 0;

	if (list.track != NULL)
		node = list.track->next;
	else
	{
		puts("No tracks in queue.");
		return;
	}

	if(node == NULL) {
		puts("No tracks in queue.");
	}
	else {
		puts("Upcoming tracks:");
		while(node != NULL) {
			const char * format;

			format = haskey(& rc, "preview-format")
				? value(& rc, "preview-format")
				: "%a - %t";

			printf("%2d %s\n", n++, meta(format, M_COLORED, & node->track));

			node = node->next;
		}
	}
}
Ejemplo n.º 5
0
/* Add a track to the scrobble queue. */
int enqueue(struct hash * track) {
	const char * keys [] = { "creator", "title", "album", "duration" };
	unsigned i;
	struct hash post;
	char timestamp[16], lastid[8];

	assert(track != NULL);

	memset(& post, 0, sizeof(struct hash));

	for(i = 0; i < (sizeof(keys) / sizeof(char *)); ++i)
		if(!haskey(track, keys[i]))
			return 0;

	queue = realloc(queue, sizeof(struct hash) * (qlength + 1));
	assert(queue != NULL);

	snprintf(timestamp, sizeof(timestamp), "%lu", (unsigned long) time(NULL));
	snprintf(lastid, sizeof(lastid), "L%s", value(track, "lastfm:trackauth"));

	copy_track_data(track, & post);

	set(& post, "i", timestamp);
	set(& post, "r", value(track, "rating"));
	set(& post, "o", lastid);

	memcpy(& queue[qlength++], & post, sizeof(struct hash));

	return !0;
}
Ejemplo n.º 6
0
Archivo: vesa.c Proyecto: Haifisch/axle
void vesa_screen_refresh(Screen* screen) {
	//check if there are any keys pending
	while (haskey()) {
		char ch = getchar();
		if (ch == 'q') {
			//quit xserv
			gfx_teardown(screen);
			switch_to_text();
			return;
		}
	}

	if (!screen->finished_drawing) return;

	//if no changes occured this refresh, don't bother writing the screen
	if (xserv_draw(screen)) {
		write_screen(screen);
	}
}
Ejemplo n.º 7
0
void unset(struct hash * hash, const char * key) {
	unsigned index = haskey(hash, key);
	if(index > 0) {
		--index;

		if(hash->content[index].key != NULL)
			free(hash->content[index].key);

		if(hash->content[index].value != NULL)
			free(hash->content[index].value);

		memcpy(
				& hash->content[index],
				& hash->content[--hash->size],
				sizeof(struct pair));

		hash->content = realloc(hash->content, sizeof(struct pair) * hash->size);
		assert(hash->content != NULL);
	}
}
Ejemplo n.º 8
0
int playback(FILE * streamfd, int pipefd) {
	killed = 0;
	signal(SIGUSR1, sighand);

#ifndef EXTERN_ONLY
	if(!haskey(& rc, "extern")) {
		const char * freetrack = NULL;

		struct stream data;
		struct mad_decoder dec;

#ifdef LIBAO
		static int ao_initialized = 0;

		if(!ao_initialized) {
			ao_initialize();
			ao_initialized = !0;
		}
#else
		unsigned arg;
		int fd;
#endif

		memset(& data, 0, sizeof(struct stream));

		/*
			Check if there's a stream timeout configured and set it up for timed
			reads later.
		*/
		data.timeout = -1;
		if(haskey(& rc, "stream-timeout")) {
			const char * timeout = value(& rc, "stream-timeout");
			data.timeout = atoi(timeout);

			if(data.timeout <= 0) {
				if(data.timeout < 0) 
					fputs("Invalid stream-timeout.\n", stderr);

				data.timeout = -1;
			}
		}


		data.streamfd = streamfd;
		data.parent = getppid();
		data.pipefd = pipefd;
		fcntl(pipefd, F_SETFL, O_NONBLOCK);

#ifdef LIBAO
		data.driver_id = ao_default_driver_id();

		if(-1 == data.driver_id) {
			fputs("Unable to find any usable output device!\n", stderr);
			return 0;
		}

		data.fmt.bits = 16;
		data.fmt.rate = 44100;
		data.fmt.channels = 2;
		data.fmt.byte_format = AO_FMT_NATIVE;
		data.device = ao_open_live(data.driver_id,&data.fmt,NULL);

		if(NULL == data.device) {
			fprintf(stderr, "Unable to open device. %s.\n", strerror(errno));
			return 0;
		}
#else
		data.audiofd = fd = open(value(& rc, "device"), O_WRONLY);

		if(-1 == data.audiofd) {
			fprintf(
					stderr, "Couldn't open %s! %s.\n",
					value(& rc, "device"), strerror(errno)
			);
			return 0;
		}

		arg = 16; /* 16 bits */
		ioctl(data.audiofd, SOUND_PCM_WRITE_BITS, & arg);
#endif

		freetrack = value(& track, "freeTrackURL");

		if(freetrack && strlen(freetrack) > 0 && haskey(& rc, "download")) {
			char * dnam;
			int rv;

			data.finpath = strdup(meta(value(& rc, "download"), M_RELAXPATH, & track));
			assert(data.finpath != NULL);

			data.tmppath = strjoin("", data.finpath, ".streaming", NULL);
			assert(data.tmppath != NULL);

			dnam = strdup(data.tmppath);
			rv = dnam ? mkpath(dirname(dnam)) : -1;
			free(dnam);

			if(access(data.tmppath, R_OK) == -1) {
				data.dump = (rv == 0) ? fopen(data.tmppath, "w") : NULL;

				if(!data.dump)
					fprintf(stderr, "Can't write download to %s.\n", data.tmppath);
			}
			else {
				data.dump = NULL;
			}
		}

		mad_decoder_init(& dec, & data, input, NULL, NULL, output, NULL, NULL);
		mad_decoder_run(& dec, MAD_DECODER_MODE_SYNC);
#ifndef LIBAO
		close(fd);
#endif
		mad_decoder_finish(& dec);

		if(data.dump) {
			fclose(data.dump);

			if(killed) {
				unlink(data.tmppath);
			} else {
				int rv;
#ifdef TAGLIB
				TagLib_File *tagme = taglib_file_new(data.tmppath);
				if(tagme != NULL) {
					TagLib_Tag *tag = taglib_file_tag(tagme);
					taglib_tag_set_title(tag, value(&track, "title"));
					taglib_tag_set_artist(tag, value(&track, "creator"));
					taglib_tag_set_album(tag, value(&track, "album"));
					taglib_file_save(tagme);
					taglib_file_free(tagme);
				}
#endif
				if(haskey(& rc, "pp-cmd")) {
					const char *ppcmd = value(& rc, "pp-cmd");
					size_t ppcmdlen = strlen(ppcmd);
					char *path = shellescape(data.tmppath);
					assert(path != NULL);
					size_t pathlen = strlen(path);
					char *command = malloc(ppcmdlen + pathlen + 2);
					assert(command != NULL);
					memcpy(command, ppcmd, ppcmdlen);
					command[ppcmdlen] = ' ';
					memcpy(command + ppcmdlen + 1, path, pathlen);
					command[ppcmdlen + 1 + pathlen] = 0;
					run(command);
					free(path);
					free(command);
				}

				rv = rename(data.tmppath, data.finpath);
				if (rv == -1)
					fprintf(stderr, "Can't rename %s to %s\n",
							data.tmppath, data.finpath);
			}

			free(data.tmppath);
			free(data.finpath);
		}
	}
	else
#endif
	{
		pid_t ppid = getppid(), cpid = 0;
		const char * cmd = meta(value(& rc, "extern"), M_SHELLESC, & track);
		FILE * ext = openpipe(cmd, & cpid);
		unsigned char * buf;

		if(!ext) {
			fprintf(stderr, "Failed to execute external player (%s). %s.\n",
					cmd, strerror(errno));
			return 0;
		}

		if(!(buf = calloc(BUFSIZE + 1, sizeof(unsigned char)))) {
			fputs("Couldn't allocate enough memory for input buffer.\n", stderr);
			fclose(ext);
			return 0;
		}

		while(!feof(streamfd)) {
			signed nbyte = fread(buf, sizeof(unsigned char), BUFSIZE, streamfd);

			if(nbyte > 0) {
				fwrite(buf, sizeof(unsigned char), nbyte, ext);
				fflush(ext);
			}

			if(kill(ppid, 0) == -1 && errno == ESRCH)
				break;

			if(killed)
				break;
		}

		free(buf);
		fclose(ext);

		waitpid(cpid, NULL, 0);
	}

	return !0;
}
Ejemplo n.º 9
0
void execcmd(const char * cmd, char * reply) {
	char arg[1024], * ptr;
	unsigned ncmd;
	const char * known [] = {
		"play",
		"love",
		"ban",
		"skip",
		"quit",
		"info",
		"pause",
		"discovery",
		"tag-artist",
		"tag-album",
		"tag-track",
		"artist-tags",
		"album-tags",
		"track-tags",
		"stop",
	};

	memset(arg, 0, sizeof(arg));
	memset(reply, 0, REPLYBUFSIZE);

	for(ncmd = 0; ncmd < (sizeof(known) / sizeof(char *)); ++ncmd) {
		if(!strncmp(known[ncmd], cmd, strlen(known[ncmd])))
			break;
	}

	switch(ncmd) {
		case (sizeof(known) / sizeof(char *)):
			strncpy(reply, "ERROR", REPLYBUFSIZE);
			break;

		case 0:
			if(sscanf(cmd, "play %128[a-zA-Z0-9:/_ %,*.-]", arg) == 1) {
				char * url;
				decode(arg, & url);
				station(url);
				free(url);
			}
			break;

		case 1:
			rate("L");
			break;

		case 2:
			rate("B");
			break;

		case 3:
			rate("S");
			break;

		case 4:
			quit();

		case 5:
			if(* (cmd + 5))
				strncpy(reply, meta(cmd + 5, 0, & track), REPLYBUFSIZE);
			else if(haskey(& rc, "np-file-format"))
				strncpy(
					reply,
					meta(value(& rc, "np-file-format"), 0, & track),
					REPLYBUFSIZE
				);

			break;

		case 6:
			if(playfork) {
				if(pausetime) {
					kill(playfork, SIGCONT);
				}
				else {
					time(& pausetime);
					kill(playfork, SIGSTOP);
				}
			}
			break;

		case 7:
			toggle(DISCOVERY);
			break;

		case 8:
			if(sscanf(cmd, "tag-artist %128s", arg) == 1)
				sendtag('a', arg, track);
			break;

		case 9:
			if(sscanf(cmd, "tag-album %128s", arg) == 1)
				sendtag('l', arg, track);
			break;

		case 10:
			if(sscanf(cmd, "tag-track %128s", arg) == 1)
				sendtag('t', arg, track);
			break;

		case 11:
			if((ptr = oldtags('a', track)) != NULL) {
				strncpy(reply, ptr, REPLYBUFSIZE);
				free(ptr);
				ptr = NULL;
			}
			break;

		case 12:
			if((ptr = oldtags('l', track)) != NULL) {
				strncpy(reply, ptr, REPLYBUFSIZE);
				free(ptr);
				ptr = NULL;
			}
			break;

		case 13:
			if((ptr = oldtags('t', track)) != NULL) {
				strncpy(reply, ptr, REPLYBUFSIZE);
				free(ptr);
				ptr = NULL;
			}
			break;

		case 14:
			if(playfork) {
				enable(STOPPED);
				kill(playfork, SIGUSR1);
			}
			break;
	}
}
Ejemplo n.º 10
0
int main(int argc, char ** argv) {
	int option, nerror = 0, background = 0, have_socket = 0;
	time_t pauselength = 0;
	char * proxy;
	opterr = 0;

	/* Create directories. */
	makercd();

	/* Load settings from ~/.shell-fm/shell-fm.rc. */
	settings(rcpath("shell-fm.rc"), !0);

	/* Enable discovery by default if it is set in configuration. */
	if(haskey(& rc, "discovery"))
		enable(DISCOVERY);

	/* Disable RTP if option "no-rtp" is set to something. */
	if(haskey(& rc, "no-rtp"))
		disable(RTP);

	/* If "daemon" is set in the configuration, enable daemon mode by default. */
	if(haskey(& rc, "daemon"))
		background = !0;

	/* Get proxy environment variable. */
	if((proxy = getenv("http_proxy")) != NULL)
		set(& rc, "proxy", proxy);


	/* Parse through command line options. */
	while(-1 != (option = getopt(argc, argv, ":dbhi:p:D:y:")))
		switch(option) {
			case 'd': /* Daemonize. */
				background = !background;
				break;

			case 'i': /* IP to bind network interface to. */
				set(& rc, "bind", optarg);
				break;

			case 'p': /* Port to listen on. */
				if(atoi(optarg))
					set(& rc, "port", optarg);
				else {
					fputs("Invalid port.\n", stderr);
					++nerror;
				}
				break;

			case 'b': /* Batch mode */
				batch = !0;
				break;

			case 'D': /* Path to audio device file. */
				set(& rc, "device", optarg);
				break;

			case 'y': /* Proxy address. */
				set(& rc, "proxy", optarg);
				break;

			case 'h': /* Print help text and exit. */
				help(argv[0], 0);
				break;

			case ':':
				fprintf(stderr, "Missing argument for option -%c.\n\n", optopt);
				++nerror;
				break;

			case '?':
			default:
				fprintf(stderr, "Unknown option -%c.\n", optopt);
				++nerror;
				break;
		}

	/* The next argument, if present, is the lastfm:// URL we want to play. */
	if(optind > 0 && optind < argc && argv[optind]) {
		const char * station = argv[optind];

		set(& rc, "default-radio", station);
	}


	if(nerror)
		help(argv[0], EXIT_FAILURE);

#ifdef EXTERN_ONLY
	/* Abort if EXTERN_ONLY is defined and no extern command is present */
	if(!haskey(& rc, "extern")) {
		fputs("Can't continue without extern command.\n", stderr);
		exit(EXIT_FAILURE);
	}
#else
#ifndef LIBAO
	if(!haskey(& rc, "device"))
		set(& rc, "device", "/dev/audio");
#endif
#endif

	if(!background) {
		puts("Shell.FM v" PACKAGE_VERSION ", (C) 2006-2010 by Jonas Kramer");
		puts("Published under the terms of the GNU General Public License (GPL).");

#ifndef TUXBOX
		puts("\nPress ? for help.\n");
#else
		puts("Compiled for the use with Shell.FM Wrapper.\n");
#endif
		fflush(stdout);
	}


	/* Open a port so Shell.FM can be controlled over network. */
	if(haskey(& rc, "bind")) {
		int port = 54311;

		if(haskey(& rc, "port"))
			port = atoi(value(& rc, "port"));

		if(tcpsock(value(& rc, "bind"), (unsigned short) port))
			have_socket = !0;
	}


	/* Open a UNIX socket for local "remote" control. */
	if(haskey(& rc, "unix") && unixsock(value(& rc, "unix")))
		have_socket = !0;


	/* We can't daemonize if there's no possibility left to control Shell.FM. */
	if(background && !have_socket) {
		fputs("Can't daemonize without control socket.\n", stderr);
		exit(EXIT_FAILURE);
	}


	/* Ask for username/password if they weren't specified in the .rc file. */
	if(!haskey(& rc, "password")) {
		char * password;

		if(!haskey(& rc, "username")) {
			char username[256] = { 0 };

			struct prompt prompt = {
				.prompt = "Login: "******"USER"), .history = NULL, .callback = NULL,
			};

			strncpy(username, readline(& prompt), 255);

			set(& rc, "username", username);
		}

		if(!(password = getpass("Password: "******"password", password);
	}
Ejemplo n.º 11
0
int station(const char * stationURL) {
  char url[512] = { 0 }, * encodedURL = NULL, ** response, name[512], * completeURL;
  unsigned i = 0, retval = !0, regular = !0;
  const char * fmt; 
  const char * types[4] = {"play", "preview", "track", "playlist"};

  delayquit = 0;

  if(playfork && haskey(& rc, "delay-change")) {
    if(nextstation) {
      /*
         Cancel station change if url is empty or equal to the current
         station.
         */
      free(nextstation);
      nextstation = NULL;

      if(!strlen(stationURL) || !strcmp(stationURL, current_station)) {
        puts("Station change cancelled.");
        return 0;
      }
    }

    /* Do nothing if the station is already played. */
    else if(current_station && !strcmp(current_station, stationURL)) {
      return 0;
    }

    /* Do nothing if the station URL is empty. */
    else if(!strlen(stationURL)) {
      return 0;
    }

    puts("\rDelayed.");
    nextstation = strdup(stationURL);

    return 0;
  }

  /* Do nothing if the station is already played. */
  else if(current_station && !strcmp(current_station, stationURL)) {
    return 0;
  }

  freelist(& playlist);

  if(!haskey(& data, "session")) {
    fputs("Not authenticated, yet.\n", stderr);
    return 0;
  }

  if(!strncmp(HTTP_STATION_PREFIX, stationURL, strlen(HTTP_STATION_PREFIX))) {
    stationURL += strlen(HTTP_STATION_PREFIX);
  }

  if(!stationURL || !strlen(stationURL))
    return 0;

  if(!strncasecmp(stationURL, "lastfm://", 9)) {
    completeURL = strdup(stationURL);
    stationURL += 9;
  }
  else {
    int size = strlen(stationURL) + 10;
    completeURL = malloc(size);
    snprintf(completeURL, size, "lastfm://%s", stationURL);
  }

  /* Check if it's a static playlist of tracks or track previews. */
  for(i = 0; i < 4; ++i)
    if(!strncasecmp(types[i], stationURL, strlen(types[i]))) {
      regular = 0;
      break;
    }

  /*
     If this is not a special "one-time" stream, it's a regular radio
     station and we request it using the good old /adjust.php URL.
     If it's not a regular stream, the reply of this request already is
     a XSPF playlist we have to parse.
     */
  if(regular) {
    fmt = "http://ws.audioscrobbler.com/radio/adjust.php?session=%s&url=%s";
  }
  else {
    fmt =
      "http://ws.audioscrobbler.com/1.0/webclient/getresourceplaylist.php"
      "?sk=%s&url=%s&desktop=1";
  }

  encode(completeURL, & encodedURL);
  snprintf(url, sizeof(url), fmt, value(& data, "session"), encodedURL);

  free(encodedURL);
  free(completeURL);

  if(!(response = NULL)) //fetch(url, NULL, NULL, NULL)))
    return 0;

  if(regular) {
    for(i = 0; response[i]; ++i) {
      char status[64] = { 0 };

      if(sscanf(response[i], "response=%63[^\r\n]", status) > 0)
        if(!strncmp(status, "FAILED", 6))
          retval = 0;

      if(sscanf(response[i], "stationname=%127[^\r\n]", name) > 0) {
        if(playlist.title != NULL)
          free(playlist.title);

        decode(name, & playlist.title);
        unhtml(playlist.title);
      }
    }

    purge(response);
    response = NULL;

    if(!retval) {
      printf("Sorry, couldn't set station to %s.\n", stationURL);
      return 0;
    }

    expand(& playlist);
  }
  else {
    char * xml = join(response, 0);

    response = NULL;

    freelist(& playlist);

    //if(!parsexspf(& playlist, xml))
    if(NULL)
      retval = 0;

    free(xml);
  }

  enable(CHANGED);
  histapp(stationURL);

  if(current_station)
    free(current_station);

  current_station = strdup(stationURL);
  assert(current_station != NULL);

  if(retval && playfork) {
    enable(INTERRUPTED);
    kill(playfork, SIGUSR1);
  }

  return retval;
}
Ejemplo n.º 12
0
const char * value(struct hash * hash, const char * key) {
	unsigned index = haskey(hash, key);
	return index > 0 ? hash->content[index - 1].value : "";
}
Ejemplo n.º 13
0
int execcmd(const char * cmd, char * reply) {
	char arg[1024], * ptr;
	unsigned ncmd;
	const char * known [] = {
		"play",
		"love",
		"ban",
		"skip",
		"quit",
		"info",
		"pause",
		"discovery",
		"tag-artist",
		"tag-album",
		"tag-track",
		"artist-tags",
		"album-tags",
		"track-tags",
		"stop",
		"volume-up",
		"volume-down",
		"volume",
		"rtp",
		"status",
		"unlove",
		"detach"
	};

	memset(arg, 0, sizeof(arg));
	memset(reply, 0, BUFSIZE);

	for(ncmd = 0; ncmd < (sizeof(known) / sizeof(char *)); ++ncmd) {
		if(!strncmp(known[ncmd], cmd, strlen(known[ncmd])))
			break;
	}

	switch(ncmd) {
		case (sizeof(known) / sizeof(char *)):
			strncpy(reply, "ERROR", BUFSIZE);
			break;

		/* "play lastfm://station" */
		case 0:
			if(sscanf(cmd, "play %128[a-zA-Z0-9:/_ %,*.+-]", arg) == 1) {
				char * url;
				decode(arg, & url);
				station(url);
				free(url);
			}
			break;

		/* Love currently played track. */
		case 1:
			rate("L");
			break;

		/* Ban currently played track. */
		case 2:
			rate("B");
			break;

		/* Skip track. */
		case 3:
			rate("S");
			break;

		/* Kill Shell.FM. */
		case 4:
			quit();

		/* "info FORMAT" - returns the format string with the meta data filled in. */
		case 5:
			if(* (cmd + 5))
				strncpy(reply, meta(cmd + 5, 0, & track), BUFSIZE);
			else if(haskey(& rc, "np-file-format"))
				strncpy(
					reply,
					meta(value(& rc, "np-file-format"), 0, & track),
					BUFSIZE
				);

			break;

		/* Pause playback. */
		case 6:
			if(playfork) {
				if(pausetime) {
					kill(playfork, SIGCONT);
				}
				else {
					time(& pausetime);
					kill(playfork, SIGSTOP);
				}
			}
			break;

		/* Toggle discovery mode. Returns "DISCOVERY <ON|OFF>" */
		case 7:
			toggle(DISCOVERY);
			snprintf(
				reply,
				BUFSIZE,
				"DISCOVERY %s",
				enabled(DISCOVERY) ? "ON" : "OFF"
			);
			break;

		/* "tag-artist tag1,tag2,..." - tag the artist of the current track. */
		case 8:
			if(sscanf(cmd, "tag-artist %128s", arg) == 1)
				sendtag('a', arg, track);
			break;

		/* "tag-album tag1,tag2,..." - tag the album of the current track. */
		case 9:
			if(sscanf(cmd, "tag-album %128s", arg) == 1)
				sendtag('l', arg, track);
			break;

		/* "tag-track tag1,tag2,..." - tag the current track. */
		case 10:
			if(sscanf(cmd, "tag-track %128s", arg) == 1)
				sendtag('t', arg, track);
			break;

		/* Return comma-separated list of the current artists tags. */
		case 11:
			if((ptr = oldtags('a', track)) != NULL) {
				strncpy(reply, ptr, BUFSIZE);
				free(ptr);
				ptr = NULL;
			}
			break;

		/* Return comma-separated list of the current albums tags. */
		case 12:
			if((ptr = oldtags('l', track)) != NULL) {
				strncpy(reply, ptr, BUFSIZE);
				free(ptr);
				ptr = NULL;
			}
			break;

		/* Return comma-separated list of the current tracks tags. */
		case 13:
			if((ptr = oldtags('t', track)) != NULL) {
				strncpy(reply, ptr, BUFSIZE);
				free(ptr);
				ptr = NULL;
			}
			break;


		/* Stop playback. */
		case 14:
			if(playfork) {
				enable(STOPPED);
				kill(playfork, SIGUSR1);
			}
			break;

		/* Increase absolute volume (0-64) by 1. */
		case 15:
			volume_up();
			break;

		/* Decrease absolute volume (0-64) by 1. */
		case 16:
			volume_down();
			break;

		/*
			Set volume.
			"volume 32" - set absolute volume (0-64) to 32 (50%).
			"volume %50" - same, but using percentual volume.
			"volume +1" - same as "volume_up".
			"volume -1" - same as "volume_down".

			Returns absolute volume ("VOLUME 32").
		*/
		case 17:
			parse_volume(cmd);
			snprintf(reply, BUFSIZE, "VOLUME %d", volume);
			break;

		/* Toggle RTP (report to profile, "scrobbling"). Returns "RTP <ON|OFF>". */
		case 18:
			/* RTP on/off */
			toggle(RTP);
			snprintf(reply, BUFSIZE, "RTP %s", enabled(RTP) ? "ON" : "OFF");
			break;

		/* Get current status. Returns on of "PAUSE", "PLAYING" and "STOPPED". */
		case 19:
			strncpy(reply, PLAYBACK_STATUS, BUFSIZE);
			break;

		/* Unlove currently played track. */
		case 20:
			rate("U");
			break;
		
		/* Detach from network interface. */
		case 21:
			return 1;	
	}

	return 0;
}
Ejemplo n.º 14
0
int main(int argc, char ** argv) {
	int option, nerror = 0, background = 0, quiet = 0, have_socket = 0;
	time_t pauselength = 0;
	char * proxy;
	opterr = 0;

	/* Create directories. */
	makercd();

	/* Load settings from ~/.shell-fm/shell-fm.rc. */
	settings(rcpath("shell-fm.rc"), !0);

	/* Enable discovery by default if it is set in configuration. */
	if(haskey(& rc, "discovery"))
		enable(DISCOVERY);

	/* Disable RTP if option "no-rtp" is set to something. */
	if(haskey(& rc, "no-rtp"))
		disable(RTP);

	/* If "daemon" is set in the configuration, enable daemon mode by default. */
	if(haskey(& rc, "daemon"))
		background = !0;

	/* If "quiet" is set in the configuration, enable quiet mode by default. */
	if(haskey(& rc, "quiet"))
		quiet = !0;

	/* Get proxy environment variable. */
	if((proxy = getenv("http_proxy")) != NULL)
		set(& rc, "proxy", proxy);


	/* Parse through command line options. */
	while(-1 != (option = getopt(argc, argv, ":dbhqi:p:D:y:Y:")))
		switch(option) {
			case 'd': /* Daemonize. */
				background = !background;
				break;

			case 'i': /* IP to bind network interface to. */
				set(& rc, "bind", optarg);
				break;

			case 'p': /* Port to listen on. */
				if(atoi(optarg))
					set(& rc, "port", optarg);
				else {
					fputs("Invalid port.\n", stderr);
					++nerror;
				}
				break;

			case 'b': /* Batch mode */
				batch = !0;
				break;

			case 'D': /* Path to audio device file. */
				set(& rc, "device", optarg);
				break;

			case 'y': /* Proxy address. */
				set(& rc, "proxy", optarg);
				break;

			case 'Y': /* SOCKS proxy address. */
				set(& rc, "socks-proxy", optarg);
				break;

			case 'q': /* Quiet mode. */
				quiet = !quiet;
				break;

			case 'h': /* Print help text and exit. */
				help(argv[0], 0);
				break;

			case ':':
				fprintf(stderr, "Missing argument for option -%c.\n\n", optopt);
				++nerror;
				break;

			case '?':
			default:
				fprintf(stderr, "Unknown option -%c.\n", optopt);
				++nerror;
				break;
		}

	/* The next argument, if present, is the lastfm:// URL we want to play. */
	if(optind > 0 && optind < argc && argv[optind]) {
		const char * station = argv[optind];

		set(& rc, "default-radio", station);
	}


	if(nerror)
		help(argv[0], EXIT_FAILURE);

#ifdef EXTERN_ONLY
	/* Abort if EXTERN_ONLY is defined and no extern command is present */
	if(!haskey(& rc, "extern")) {
		fputs("Can't continue without extern command.\n", stderr);
		exit(EXIT_FAILURE);
	}
#else
#ifndef LIBAO
	if(!haskey(& rc, "device"))
		set(& rc, "device", "/dev/audio");
#endif
#endif

	if(!background && !quiet) {
		puts("Shell.FM v" PACKAGE_VERSION ", (C) 2006-2012 by Jonas Kramer");
		puts("Published under the terms of the GNU General Public License (GPL).");

#ifndef TUXBOX
		puts("\nPress ? for help.\n");
#else
		puts("Compiled for the use with Shell.FM Wrapper.\n");
#endif
		fflush(stdout);
	}


	/* Open a port so Shell.FM can be controlled over network. */
	if(haskey(& rc, "bind")) {
		int port = 54311;

		if(haskey(& rc, "port"))
			port = atoi(value(& rc, "port"));

		if(tcpsock(value(& rc, "bind"), (unsigned short) port))
			have_socket = !0;
	}


	/* Open a UNIX socket for local "remote" control. */
	if(haskey(& rc, "unix") && unixsock(value(& rc, "unix")))
		have_socket = !0;


	/* We can't daemonize if there's no possibility left to control Shell.FM. */
	if(background && !have_socket) {
		fputs("Can't daemonize without control socket.\n", stderr);
		exit(EXIT_FAILURE);
	}

	memset(& data, 0, sizeof(struct hash));
	memset(& track, 0, sizeof(struct hash));
	memset(& playlist, 0, sizeof(struct playlist));

	/* Fork to background. */
	if(background) {
		int null;
		pid_t pid = fork();

		if(pid == -1) {
			fputs("Failed to daemonize.\n", stderr);
			exit(EXIT_FAILURE);
		} else if(pid) {
			exit(EXIT_SUCCESS);
		}

		enable(QUIET);

		/* Detach from TTY */
		setsid();
		pid = fork();

		if(pid > 0)
			exit(EXIT_SUCCESS);

		/* Close stdin out and err */
		close(0);
		close(1);
		close(2);

		/* Redirect  stdin and out to /dev/null */
		null = open("/dev/null", O_RDWR);
		dup(null);
		dup(null);
	}

	ppid = getpid();

	atexit(cleanup);
	load_queue();

	/* Set up signal handlers for communication with the playback process. */
	signal(SIGINT, forcequit);

	/* SIGUSR2 from playfork means it detected an error. */
	signal(SIGUSR2, playsig);

	/* Catch SIGTSTP to set pausetime when user suspends us with ^Z. */
	signal(SIGTSTP, stopsig);

	/* Authenticate to the Last.FM server. */
	create_session();

	if(!background) {
		struct input keyboard = { 0, KEYBOARD };
		register_handle(keyboard);
		canon(0);
		atexit(cleanup_term);
	}


	/* Play default radio, if specified. */
	if(haskey(& rc, "default-radio")) {
		if(!strcmp(value(& rc, "default-radio"), "last")) {
			char ** history = load_history(), * last = NULL, ** p;

			for(p = history; * p != NULL; ++p) {
				last = * p;
			}

			set(& rc, "default-radio", last);
			purge(history);
		}

		station(value(& rc, "default-radio"));
	}

	else if(!background)
		radioprompt("radio url> ");

	/* The main loop. */
	while(!0) {
		pid_t child;
		int status, playnext = 0;

		/* Check if anything died (submissions fork or playback fork). */
		while((child = waitpid(-1, & status, WNOHANG | WUNTRACED | WCONTINUED)) > 0) {
			if(child == subfork)
				subdead(WEXITSTATUS(status));
			else if(child == playfork) {
				if(WIFSTOPPED(status)) {
					/* time(& pausetime); */
				}
				else {
					if(WIFCONTINUED(status)) {
						signal(SIGTSTP, stopsig);
						if(pausetime != 0) {
							pauselength += time(NULL) - pausetime;
						}
					}
					else {
						playnext = !0;
						unlinknp();

						if(delayquit) {
							quit();
						}
					}
					pausetime = 0;
				}
			}
		}

		/*
			Check if the playback process died. If so, submit the data
			of the last played track. Then check if there are tracks left
			in the playlist; if not, try to refresh the list and stop the
			stream if there are no new tracks to fetch.
			Also handle user stopping the stream here.  We need to check for
			playnext != 0 before handling enabled(STOPPED) anyway, otherwise
			playfork would still be running.
		*/
		if(playnext) {
			playfork = 0;

			if(enabled(RTP)) {
				unsigned duration, played, minimum;

				duration = atoi(value(& track, "duration"));
				played = time(NULL) - change_time - pauselength;

				/* Allow user to specify minimum playback length (min. 50%). */
				if(haskey(& rc, "minimum")) {
					unsigned percent = atoi(value(& rc, "minimum"));
					if(percent < 50)
						percent = 50;
					minimum = duration * percent / 100;
				}
				else {
					minimum = duration / 2;
				}

				if(duration >= 30 && (played >= 240 || played > minimum)) {
					debug("adding track to scrobble queue\n");
					enqueue(& track);
				}
				else {
					debug("track too short (duration %d, played %d, minimum %d) - not scrobbling\n", duration, played, minimum);
				}
			}

			submit();

			/* Check if the user stopped the stream. */
			if(enabled(STOPPED) || error) {
				freelist(& playlist);
				empty(& track);

				if(error) {
					fputs("Playback stopped with an error.\n", stderr);
					error = 0;
				}

				disable(STOPPED);
				disable(CHANGED);

				continue;
			}

			shift(& playlist);
		}


		if(playnext || enabled(CHANGED)) {
			if(nextstation != NULL) {
				playnext = 0;
				disable(CHANGED);

				if(!station(nextstation))
					enable(STOPPED);

				free(nextstation);
				nextstation = NULL;
			}

			if(!enabled(STOPPED) && !playlist.left) {
				expand(& playlist);
				if(!playlist.left) {
					puts("No tracks left.");
					playnext = 0;
					disable(CHANGED);
					continue;
				}
			}

			if(!playfork) {
				/*
					If there was a track played before, and there is a gap
					configured, wait that many seconds before playing the next
					track.
				*/
				if(playnext && !enabled(INTERRUPTED) && haskey(& rc, "gap")) {
					int gap = atoi(value(& rc, "gap"));

					if(gap > 0)
						sleep(gap);
				}

				disable(INTERRUPTED);

				if(play(& playlist)) {
					time(& change_time);
					pauselength = 0;

					set(& track, "stationURL", current_station);

					/* Print what's currently played. (Ondrej Novy) */
					if(!background) {
						if(enabled(CHANGED) && playlist.left > 0) {
							puts(meta("Receiving %s.", M_COLORED, & track));
							disable(CHANGED);
						}

						if(haskey(& rc, "title-format"))
							printf("%s\n", meta(value(& rc, "title-format"), M_COLORED, & track));
						else
							printf("%s\n", meta("Now playing \"%t\" by %a.", M_COLORED, & track));
					}

					if(enabled(RTP)) {
						notify_now_playing(& track);
					}


					/* Write track data into a file. */
					if(haskey(& rc, "np-file") && haskey(& rc, "np-file-format")) {
						signed np;
						const char
							* file = value(& rc, "np-file"),
							* fmt = value(& rc, "np-file-format");

						unlink(file);
						if(-1 != (np = open(file, O_WRONLY | O_CREAT, 0644))) {
							const char * output = meta(fmt, 0, & track);
							if(output)
								write(np, output, strlen(output));
							close(np);
						}
					}


					if(haskey(& rc, "screen-format")) {
						const char * term = getenv("TERM");
						if(term != NULL && !strncmp(term, "screen", 6)) {
							const char * output =
								meta(value(& rc, "screen-format"), 0, & track);
							printf("\x1Bk%s\x1B\\", output);
						}
					}


					if(haskey(& rc, "term-format")) {
						const char * output =
							meta(value(& rc, "term-format"), 0, & track);
						printf("\x1B]2;%s\a", output);
					}


					/* Run a command with our track data. */
					if(haskey(& rc, "np-unescaped-cmd"))
						run(meta(value(& rc, "np-unescaped-cmd"), 0, & track));
					if(haskey(& rc, "np-cmd"))
						run(meta(value(& rc, "np-cmd"), M_SHELLESC, & track));
				} else
					change_time = 0;
			}

			if(banned(value(& track, "creator"))) {
				puts(meta("%a is banned.", M_COLORED, & track));
				rate(RATING_BAN);
				fflush(stdout);
			}
		}

		playnext = 0;

		if(playfork && change_time && haskey(& track, "duration") && !pausetime) {
			unsigned duration;
			signed remain;
			char remstr[32];

			duration = atoi(value(& track, "duration"));

			remain = (change_time + duration) - time(NULL) + pauselength;

			snprintf(remstr, sizeof(remstr), "%d", remain);
			set(& track, "remain", remstr);

			if(!background) {
				printf(
					"%s%c",
					meta("%r", M_COLORED, & track),
					// strdup(meta("%v", M_COLORED, & track)),
					batch ? '\n' : '\r'
				);
				fflush(stdout);
			}
		}

		handle_input(1000000);
	}

	return 0;
}