/* Set the time tags field for the item. */ void plist_set_item_time (struct plist *plist, const int num, const int time) { int old_time; assert (plist != NULL); assert (LIMIT(num, plist->num)); if (!plist->items[num].tags) { plist->items[num].tags = tags_new (); old_time = -1; } else if (plist->items[num].tags->time != -1) old_time = plist->items[num].tags->time; else old_time = -1; if (old_time != -1) { plist->total_time -= old_time; plist->items_with_time--; } if (time != -1) { plist->total_time += time; plist->items_with_time++; } plist->items[num].tags->time = time; plist->items[num].tags->filled |= TAGS_TIME; }
struct file_tags *tags_dup (const struct file_tags *tags) { struct file_tags *dtags; assert (tags != NULL); dtags = tags_new(); tags_copy (dtags, tags); return dtags; }
/* Read selected tags for a file into tags structure (or create it if NULL). * If some tags are already present, don't read them. * If present_tags is NULL, allocate new tags. */ struct file_tags *read_file_tags (const char *file, struct file_tags *present_tags, const int tags_sel) { struct file_tags *tags; struct decoder *df; int needed_tags; assert (file != NULL); if (present_tags) { tags = present_tags; needed_tags = ~tags->filled & tags_sel; } else { tags = tags_new (); needed_tags = tags_sel; } if (file_type(file) == F_URL) return tags; df = get_decoder (file); if (!df) { logit ("Can't find decoder functions for %s", file); return tags; } if (needed_tags) { /* This makes sure that we don't cause a memory leak */ assert (!((needed_tags & TAGS_COMMENTS) && (tags->title || tags->artist || tags->album))); df->info (file, tags, needed_tags); tags->filled |= tags_sel; } else debug ("No need to read any tags"); return tags; }
/* 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); }
/* Read time tags for a file into tags structure (or create it if NULL). */ struct file_tags *read_missing_tags (const char *file, struct file_tags *tags, int tags_sel) { 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); return tags; }
/* 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); } }
/* Load PLS file into plist. Return the number of items read. */ static int plist_load_pls (struct plist *plist, const char *fname, const char *cwd) { FILE *file; char *line; long i, nitems, added = 0; char *e; if (!(file = fopen(fname, "r"))) { error ("Can't open playlist file: %s", strerror(errno)); return 0; } line = read_ini_value (file, "playlist", "NumberOfEntries"); if (!line) { /* Assume that it is a pls file version 1 - plist_load_m3u() * should handle it like an m3u file without the m3u extensions. */ fclose (file); return plist_load_m3u (plist, fname, cwd, 0); } nitems = strtol (line, &e, 10); if (*e) { error ("Broken PLS file"); free (line); return 0; } free (line); for (i = 1; i <= nitems; i++) { char *pls_file, *pls_title, *pls_length; char key[16]; int time; int last_added; char path[2*PATH_MAX]; sprintf (key, "File%ld", i); if (!(pls_file = read_ini_value(file, "playlist", key))) { error ("Broken PLS file"); break; } sprintf (key, "Title%ld", i); pls_title = read_ini_value(file, "playlist", key); sprintf (key, "Length%ld", i); pls_length = read_ini_value(file, "playlist", key); if (pls_length) { time = strtol (pls_length, &e, 10); if (*e) time = -1; } else time = -1; if (strlen(pls_file) <= PATH_MAX) { make_path (path, sizeof(path), cwd, pls_file); if (plist_find_fname(plist, path) == -1) { last_added = plist_add (plist, path); if (pls_title && pls_title[0]) plist_set_title_tags (plist, last_added, pls_title); if (time > 0) { plist->items[last_added].tags = tags_new (); plist->items[last_added].tags->time = time; plist->items[last_added].tags->filled |= TAGS_TIME; } } } free (pls_file); if (pls_title) free (pls_title); if (pls_length) free (pls_length); added++; } fclose (file); return added; }