Exemple #1
0
void plist_set_tags (struct plist *plist, const int num,
		const struct file_tags *tags)
{
	int old_time;

	assert (plist != NULL);
	assert (LIMIT(num, plist->num));
	assert (tags != NULL);

	if (plist->items[num].tags && plist->items[num].tags->time != -1)
		old_time = plist->items[num].tags->time;
	else
		old_time = -1;

	if (plist->items[num].tags)
		tags_free (plist->items[num].tags);
	plist->items[num].tags = tags_dup (tags);

	if (old_time != -1) {
		plist->total_time -= old_time;
		plist->items_with_time--;
	}

	if (tags->time != -1) {
		plist->total_time += tags->time;
		plist->items_with_time++;
	}
}
Exemple #2
0
void plist_discard_tags (struct plist *plist)
{
	int i;

	assert (plist != NULL);

	for (i = 0; i < plist->num; i++)
		if (!plist_deleted(plist, i) && plist->items[i].tags) {
			tags_free (plist->items[i].tags);
			plist->items[i].tags = NULL;
		}

	plist->items_with_time = 0;
	plist->total_time = 0;
}
Exemple #3
0
static void *locked_read_add (struct tags_cache *c, const char *file,
                              const int tags_sel, const int client_id,
                              DBT *key, DBT *serialized_cache_rec)
{
	int ret;
	struct file_tags *tags = NULL;

	assert (c->db != NULL);

	ret = c->db->get (c->db, NULL, key, serialized_cache_rec, 0);
	if (ret && ret != DB_NOTFOUND)
		logit ("Cache DB get error: %s", db_strerror (ret));

	/* If this entry is already present in the cache, we have 3 options:
	 * we must read different tags (TAGS_*) or the tags are outdated
	 * or this is an immediate tags read (client_id == -1) */
	if (ret == 0) {
		struct cache_record rec;

		if (cache_record_deserialize (&rec, serialized_cache_rec->data,
		                              serialized_cache_rec->size, 0)) {
			time_t curr_mtime = get_mtime (file);

			if (rec.mod_time != curr_mtime) {
				debug ("Tags in the cache are outdated");
				tags_free (rec.tags);  /* remove them and reread tags */
			}
			else if ((rec.tags->filled & tags_sel) == tags_sel
					&& client_id == -1) {
				debug ("Tags are in the cache.");
				return rec.tags;
			}
			else {
				debug ("Tags in the cache are not what we want");
				tags = rec.tags;  /* read additional tags */
			}
		}
	}

	tags = read_missing_tags (file, tags, tags_sel);
	tags_cache_add (c, file, key, tags);

	return tags;
}
Exemple #4
0
void plist_free_item_fields (struct plist_item *item)
{
	if (item->file) {
		free (item->file);
		item->file = NULL;
	}
	if (item->title_tags) {
		free (item->title_tags);
		item->title_tags = NULL;
	}
	if (item->title_file) {
		free (item->title_file);
		item->title_file = NULL;
	}
	if (item->tags) {
		tags_free (item->tags);
		item->tags = NULL;
	}
}
Exemple #5
0
/* Update tags if tags from the decoder or the stream are available. */
static void update_tags (const struct decoder *f, void *decoder_data,
		struct io_stream *s)
{
	char *stream_title = NULL;
	int tags_changed = 0;
	struct file_tags *new_tags;

	new_tags = tags_new ();

	LOCK (curr_tags_mut);
	if (f->current_tags && f->current_tags(decoder_data, new_tags)
			&& new_tags->title) {
		tags_changed = 1;
		tags_copy (curr_tags, new_tags);
		logit ("Tags change from the decoder");
		tags_source = TAGS_SOURCE_DECODER;
		show_tags (curr_tags);
	}
	else if (s && (stream_title = io_get_metadata_title(s))) {
		if (curr_tags && curr_tags->title
				&& tags_source == TAGS_SOURCE_DECODER) {
			logit ("New IO stream tags, ignored because there are "
					"decoder tags present");
			free (stream_title);
		}
		else {
			tags_clear (curr_tags);
			curr_tags->title = stream_title;
			show_tags (curr_tags);
			tags_changed = 1;
			logit ("New IO stream tags");
			tags_source = TAGS_SOURCE_METADATA;
		}
	}

	if (tags_changed)
		tags_change ();

	tags_free (new_tags);

	UNLOCK (curr_tags_mut);
}
Exemple #6
0
/* Send tags to the client. Return 0 on error. */
static int req_get_tags (struct client *cli)
{
	struct file_tags *tags;
	int res = 1;

	debug ("Sending tags to client with fd %d...", cli->socket);

	if (!send_int(cli->socket, EV_DATA)) {
		logit ("Error when sending EV_DATA");
		return 0;
	}

	tags = audio_get_curr_tags ();
	if (!send_tags(cli->socket, tags)) {
		logit ("Error when sending tags");
		res = 0;
	}

	if (tags)
		tags_free (tags);

	return res;
}
Exemple #7
0
void tags_cache_add_request (struct tags_cache *c, const char *file,
		const int tags_sel, const int client_id)
{
	DBT serialized_cache_rec;
	DBT key;
	int db_ret;
	int got_lock;
	DB_LOCK lock;

	assert (c != NULL);
	assert (file != NULL);
	assert (client_id >= 0 && client_id < CLIENTS_MAX);

	debug ("Request for tags for %s from client %d", file, client_id);

	memset (&key, 0, sizeof(key));
	key.data = (void *)file;
	key.size = strlen(file);

	memset (&serialized_cache_rec, 0, sizeof(serialized_cache_rec));
	serialized_cache_rec.flags = DB_DBT_MALLOC;

	db_ret = c->db_env->lock_get (c->db_env, c->locker, 0,
			&key, DB_LOCK_WRITE, &lock);
	if (db_ret) {
		got_lock = 0;
		logit ("Can't get DB lock: %s", db_strerror(db_ret));
	}
	else {
		got_lock = 1;
	}

	if (c->db) {
		db_ret = c->db->get(c->db, NULL, &key, &serialized_cache_rec, 0);

		if (db_ret && db_ret != DB_NOTFOUND)
			error ("Cache DB search error: %s", db_strerror(db_ret));
	}
	else
		db_ret = DB_NOTFOUND;

	if (db_ret == 0) {
		struct cache_record rec;

		if (cache_record_deserialize(&rec, serialized_cache_rec.data,
					serialized_cache_rec.size, 0)) {
			if (rec.mod_time == get_mtime(file)
					&& (rec.tags->filled & tags_sel) == tags_sel) {
				tags_response (client_id, file, rec.tags);
				tags_free (rec.tags);
				debug ("Tags are present in the cache");
				goto end;
			}

			tags_free (rec.tags);
			debug ("Found outdated or incomplete tags in the cache");
		}
	}

	LOCK (c->mutex);
	request_queue_add (&c->queues[client_id], file, tags_sel);
	pthread_cond_signal (&c->request_cond);
	UNLOCK (c->mutex);

end:
	if (got_lock) {
		db_ret = c->db_env->lock_put (c->db_env, &lock);
		if (db_ret)
			logit ("Can't release DB lock: %s", db_strerror(db_ret));
	}

	if (serialized_cache_rec.data)
		free (serialized_cache_rec.data);
}
Exemple #8
0
/* Read the selected tags for this file and add it to the cache.
 * If client_id != -1, the server is notified using tags_response().
 * If client_id == -1, copy of file_tags is returned. */
static struct file_tags *tags_cache_read_add (struct tags_cache *c,
		const int client_id, const char *file, int tags_sel)
{
	struct file_tags *tags = NULL;
	DBT key;
	DBT serialized_cache_rec;
	DB_LOCK lock;
	int got_lock = 0;
	int ret;

	assert (c != NULL);
	assert (c->db != NULL);
	assert (file != NULL);

	debug ("Getting tags for %s", file);

	memset (&key, 0, sizeof(key));
	memset (&serialized_cache_rec, 0, sizeof(serialized_cache_rec));

	key.data = (void *)file;
	key.size = strlen(file);
	serialized_cache_rec.flags = DB_DBT_MALLOC;

	ret = c->db_env->lock_get (c->db_env, c->locker, 0,
			&key, DB_LOCK_WRITE, &lock);
	if (ret) {
		logit ("Can't get DB lock: %s", db_strerror(ret));
	}
	else {
		got_lock = 1;
		ret = c->db->get (c->db, NULL, &key, &serialized_cache_rec, 0);
		if (ret && ret != DB_NOTFOUND)
			logit ("Cache DB get error: %s", db_strerror(ret));
	}

	/* If this entry is already present in the cache, we have 3 options:
	 * we must read different tags (TAGS_*) or the tags are outdated
	 * or this is an immediate tags read (client_id == -1) */
	if (ret == 0) {
		struct cache_record rec;

		if (cache_record_deserialize (&rec, serialized_cache_rec.data,
		                              serialized_cache_rec.size, 0)) {
			time_t curr_mtime = get_mtime (file);

			if (rec.mod_time != curr_mtime) {

				/* outdated tags - remove them and reread */
				tags_free (rec.tags);

				debug ("Tags in the cache are outdated");
			}
			else if ((rec.tags->filled & tags_sel) == tags_sel
					&& client_id == -1) {
				debug ("Tags are in the cache.");
				tags = rec.tags;

				goto end;
			}
			else {
				tags = rec.tags; /* read tags in addition to already
						      present tags */
				debug ("Tags in the cache are not what we want.");
			}
		}
	}

	if (tags == NULL)
		tags = tags_new ();

	if (tags_sel & TAGS_TIME) {
		int time;

		/* Try to get it from the server's playlist first. */
		time = audio_get_ftime (file);

		if (time != -1) {
			tags->time = time;
			tags->filled |= TAGS_TIME;
			tags_sel &= ~TAGS_TIME;
		}
	}

	tags = read_file_tags (file, tags, tags_sel);

	debug ("Adding/updating cache object");
	tags_cache_add (c, file, tags);

	if (client_id != -1) {
		tags_response (client_id, file, tags);
		tags_free (tags);
		tags = NULL;
	}

	/* TODO: Remove the oldest items from the cache if we exceeded the maximum
	 * cache size */

end:
	if (got_lock) {
		ret = c->db_env->lock_put (c->db_env, &lock);
		if (ret)
			logit ("Can't release DB lock: %s", db_strerror(ret));
	}

	if (serialized_cache_rec.data)
		free (serialized_cache_rec.data);

	return tags;
}
Exemple #9
0
static int cache_record_deserialize (struct cache_record *rec,
		const char *serialized, const size_t size,
		const int skip_tags)
{
	const char *p = serialized;
	size_t bytes_left = size;
	size_t str_len;

	assert (rec != NULL);
	assert (serialized != NULL);

	if (!skip_tags)
		rec->tags = tags_new ();
	else
		rec->tags = NULL;

#define extract_num(var)			\
	if (bytes_left < sizeof(var))		\
		goto err;			\
	memcpy (&var, p, sizeof(var));		\
	bytes_left -= sizeof(var);		\
	p += sizeof(var);

#define extract_str(var)			\
	if (bytes_left < sizeof(str_len))	\
		goto err;			\
	memcpy (&str_len, p, sizeof(str_len));	\
	p += sizeof(str_len);			\
	if (bytes_left < str_len)		\
		goto err;			\
	var = xmalloc (str_len + 1);		\
	memcpy (var, p, str_len);		\
	var[str_len] = '\0';			\
	p += str_len;

	extract_num (rec->mod_time);
	extract_num (rec->atime);

	if (!skip_tags) {
		extract_str (rec->tags->artist);
		extract_str (rec->tags->album);
		extract_str (rec->tags->title);
		extract_num (rec->tags->track);
		extract_num (rec->tags->time);

		if (rec->tags->title)
			rec->tags->filled |= TAGS_COMMENTS;
		else {
			if (rec->tags->artist)
				free (rec->tags->artist);
			rec->tags->artist = NULL;

			if (rec->tags->album)
				free (rec->tags->album);
			rec->tags->album = NULL;

		}

		if (rec->tags->time >= 0)
			rec->tags->filled |= TAGS_TIME;
	}

	return 1;

err:
	logit ("Cache record deserialization error at %ldB", (long) (p - serialized));
	tags_free (rec->tags);
	rec->tags = NULL;
	return 0;
}
Exemple #10
0
/* Decoder loop for already opened and probably running for some time decoder.
 * next_file will be precached at eof. */
static void decode_loop (const struct decoder *f, void *decoder_data,
		const char *next_file, struct out_buf *out_buf,
		struct sound_params *sound_params, struct md5_data *md5,
		const float already_decoded_sec)
{
	bool eof = false;
	bool stopped = false;
	char buf[PCM_BUF_SIZE];
	int decoded = 0;
	struct sound_params new_sound_params;
	bool sound_params_change = false;
	float decode_time = already_decoded_sec; /* the position of the decoder
						    (in seconds) */

	out_buf_set_free_callback (out_buf, buf_free_callback);

	LOCK (curr_tags_mut);
	curr_tags = tags_new ();
	UNLOCK (curr_tags_mut);

	if (f->get_stream) {
		LOCK (decoder_stream_mut);
		decoder_stream = f->get_stream (decoder_data);
		UNLOCK (decoder_stream_mut);
	}
	else
		logit ("No get_stream() function");

	status_msg ("Playing...");

	while (1) {
		debug ("loop...");

		LOCK (request_cond_mutex);
		if (!eof && !decoded) {
			struct decoder_error err;

			UNLOCK (request_cond_mutex);

			if (decoder_stream && out_buf_get_fill(out_buf)
					< PREBUFFER_THRESHOLD) {
				prebuffering = 1;
				io_prebuffer (decoder_stream,
						options_get_int("Prebuffering")
						* 1024);
				prebuffering = 0;
				status_msg ("Playing...");
			}

			decoded = f->decode (decoder_data, buf, sizeof(buf),
					&new_sound_params);

			if (decoded)
				decode_time += decoded / (float)(sfmt_Bps(
							new_sound_params.fmt) *
						new_sound_params.rate *
						new_sound_params.channels);

			f->get_error (decoder_data, &err);
			if (err.type != ERROR_OK) {
				md5->okay = false;
				if (err.type != ERROR_STREAM
						|| options_get_int(
							"ShowStreamErrors"))
					error ("%s", err.err);
				decoder_error_clear (&err);
			}

			if (!decoded) {
				eof = true;
				logit ("EOF from decoder");
			}
			else {
				debug ("decoded %d bytes", decoded);
				if (!sound_params_eq(new_sound_params, *sound_params))
					sound_params_change = true;

				bitrate_list_add (&bitrate_list, decode_time,
						f->get_bitrate(decoder_data));
				update_tags (f, decoder_data, decoder_stream);
			}
		}

		/* Wait, if there is no space in the buffer to put the decoded
		 * data or EOF occurred and there is something in the buffer. */
		else if (decoded > out_buf_get_free(out_buf)
					|| (eof && out_buf_get_fill(out_buf))) {
			debug ("waiting...");
			if (eof && !precache.file && next_file
					&& file_type(next_file) == F_SOUND
					&& options_get_int("Precache")
					&& options_get_bool("AutoNext"))
				start_precache (&precache, next_file);
			pthread_cond_wait (&request_cond, &request_cond_mutex);
			UNLOCK (request_cond_mutex);
		}
		else
			UNLOCK (request_cond_mutex);

		/* When clearing request, we must make sure, that another
		 * request will not arrive at the moment, so we check if
		 * the request has changed. */
		if (request == REQ_STOP) {
			logit ("stop");
			stopped = true;
			md5->okay = false;
			out_buf_stop (out_buf);

			LOCK (request_cond_mutex);
			if (request == REQ_STOP)
				request = REQ_NOTHING;
			UNLOCK (request_cond_mutex);

			break;
		}
		else if (request == REQ_SEEK) {
			int decoder_seek;

			logit ("seeking");
			md5->okay = false;
			req_seek = MAX(0, req_seek);
			if ((decoder_seek = f->seek(decoder_data, req_seek))
					== -1)
				logit ("error when seeking");
			else {
				out_buf_stop (out_buf);
				out_buf_reset (out_buf);
				out_buf_time_set (out_buf, decoder_seek);
				bitrate_list_empty (&bitrate_list);
				decode_time = decoder_seek;
				eof = false;
				decoded = 0;
			}

			LOCK (request_cond_mutex);
			if (request == REQ_SEEK)
				request = REQ_NOTHING;
			UNLOCK (request_cond_mutex);

		}
		else if (!eof && decoded <= out_buf_get_free(out_buf)
				&& !sound_params_change) {
			debug ("putting into the buffer %d bytes", decoded);
#if !defined(NDEBUG) && defined(DEBUG)
			if (md5->okay) {
				md5->len += decoded;
				md5_process_bytes (buf, decoded, &md5->ctx);
			}
#endif
			audio_send_buf (buf, decoded);
			decoded = 0;
		}
		else if (!eof && sound_params_change
				&& out_buf_get_fill(out_buf) == 0) {
			logit ("Sound parameters have changed.");
			*sound_params = new_sound_params;
			sound_params_change = false;
			set_info_channels (sound_params->channels);
			set_info_rate (sound_params->rate / 1000);
			out_buf_wait (out_buf);
			if (!audio_open(sound_params)) {
				md5->okay = false;
				break;
			}
		}
		else if (eof && out_buf_get_fill(out_buf) == 0) {
			logit ("played everything");
			break;
		}
	}

	status_msg ("");

	LOCK (decoder_stream_mut);
	decoder_stream = NULL;
	f->close (decoder_data);
	UNLOCK (decoder_stream_mut);

	bitrate_list_destroy (&bitrate_list);

	LOCK (curr_tags_mut);
	if (curr_tags) {
		tags_free (curr_tags);
		curr_tags = NULL;
	}
	UNLOCK (curr_tags_mut);

	out_buf_wait (out_buf);

	if (precache.ok && (stopped || !options_get_bool ("AutoNext"))) {
		precache_wait (&precache);
		precache.f->close (precache.decoder_data);
		precache_reset (&precache);
	}
}
Exemple #11
0
static void on_song_change ()
{
	static char *last_file = NULL;
	static lists_t_strs *on_song_change = NULL;

	int ix;
	bool same_file, unpaused;
	char *curr_file, **args;
	struct file_tags *curr_tags;
	lists_t_strs *arg_list;

	/* We only need to do OnSongChange tokenisation once. */
	if (on_song_change == NULL) {
		char *command;

		on_song_change = lists_strs_new (4);
		command = options_get_str ("OnSongChange");

		if (command)
			lists_strs_tokenise (on_song_change, command);
	}

	if (lists_strs_empty (on_song_change))
		return;

	curr_file = audio_get_sname ();

	if (curr_file == NULL)
		return;

	same_file = (last_file && !strcmp (last_file, curr_file));
	unpaused = (audio_get_prev_state () == STATE_PAUSE);
	if (same_file && (unpaused || !options_get_bool ("RepeatSongChange"))) {
		free (curr_file);
		return;
	}

	curr_tags = tags_cache_get_immediate (tags_cache, curr_file,
	                                      TAGS_COMMENTS | TAGS_TIME);
	arg_list = lists_strs_new (lists_strs_size (on_song_change));
	for (ix = 0; ix < lists_strs_size (on_song_change); ix += 1) {
		char *arg, *str;

		arg = lists_strs_at (on_song_change, ix);
		if (arg[0] != '%')
			lists_strs_append (arg_list, arg);
		else if (!curr_tags)
			lists_strs_append (arg_list, "");
		else {
			switch (arg[1]) {
			case 'a':
				str = curr_tags->artist ? curr_tags->artist : "";
				lists_strs_append (arg_list, str);
				break;
			case 'r':
				str = curr_tags->album ? curr_tags->album : "";
				lists_strs_append (arg_list, str);
				break;
			case 't':
				str = curr_tags->title ? curr_tags->title : "";
				lists_strs_append (arg_list, str);
				break;
			case 'n':
				if (curr_tags->track >= 0) {
					str = (char *) xmalloc (sizeof (char) * 4);
					snprintf (str, 4, "%d", curr_tags->track);
					lists_strs_push (arg_list, str);
				}
				else
					lists_strs_append (arg_list, "");
				break;
			case 'f':
				lists_strs_append (arg_list, curr_file);
				break;
			case 'D':
				if (curr_tags->time >= 0) {
					str = (char *) xmalloc (sizeof (char) * 10);
					snprintf (str, 10, "%d", curr_tags->time);
					lists_strs_push (arg_list, str);
				}
				else
					lists_strs_append (arg_list, "");
				break;
			case 'd':
				if (curr_tags->time >= 0) {
					str = (char *) xmalloc (sizeof (char) * 12);
					sec_to_min (str, curr_tags->time);
					lists_strs_push (arg_list, str);
				}
				else
					lists_strs_append (arg_list, "");
				break;
			default:
				lists_strs_append (arg_list, arg);
			}
		}
	}
	tags_free (curr_tags);

#ifndef NDEBUG
	{
		char *cmd;

		cmd = lists_strs_fmt (arg_list, " %s");
		debug ("Running command: %s", cmd);
		free (cmd);
	}
#endif

	switch (fork ()) {
	case 0:
		args = lists_strs_save (arg_list);
		execve (args[0], args, environ);
		exit (EXIT_FAILURE);
	case -1:
		log_errno ("Failed to fork()", errno);
	}

	lists_strs_free (arg_list);
	free (last_file);
	last_file = curr_file;
}