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++; } }
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; }
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; }
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; } }
/* 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); }
/* 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; }
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); }
/* 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; }
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; }
/* 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); } }
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; }