/* * Copyright (c) 2010, 2011 Ryan Flannery * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "playlist.h" int history_size = DEFAULT_HISTORY_SIZE; void playlist_increase_capacity(playlist *p) { meta_info **new_files; size_t nbytes; p->capacity += PLAYLIST_CHUNK_SIZE; nbytes = p->capacity * sizeof(meta_info*); if ((new_files = realloc(p->files, nbytes)) == NULL) err(1, "%s: failed to realloc(3) files", __FUNCTION__); p->files = new_files; } /* * Allocate a new playlist and return a pointer to it. The resulting * structure must be free(2)'d using playlist_free(). */ playlist * playlist_new(void) { playlist *p; if ((p = malloc(sizeof(playlist))) == NULL) err(1, "playlist_new: failed to allocate playlist"); if ((p->files = calloc(PLAYLIST_CHUNK_SIZE, sizeof(meta_info*))) == NULL) err(1, "playlist_new: failed to allocate files"); p->capacity = PLAYLIST_CHUNK_SIZE; p->filename = NULL; p->name = NULL; p->nfiles = 0; p->history = playlist_history_new(); p->hist_present = -1; p->needs_saving = false; return p; } /* Free all of the memory consumed by a given playlist. */ void playlist_free(playlist *p) { if (p->filename != NULL) free(p->filename); if (p->name != NULL) free(p->name); if (p->files != NULL) free(p->files); playlist_history_free(p); free(p); } /* * Duplicate an existing playlist, returning a pointer to the newly allocated * playlist. The filename and name of the new playlist must be specified. */ playlist * playlist_dup(const playlist *original, const char *filename, const char *name) { playlist *newplist; int i; /* create new playlist and copy simple members */ newplist = playlist_new(); newplist->nfiles = original->nfiles; newplist->capacity = original->nfiles; if (name != NULL) { if ((newplist->name = strdup(name)) == NULL) err(1, "playlist_dup: strdup name failed"); } if (filename != NULL) { if ((newplist->filename = strdup(filename)) == NULL) err(1, "playlist_dup: strdup filename failed"); } /* copy all of the files */ newplist->files = calloc(original->nfiles, sizeof(meta_info*)); if (newplist->files == NULL) err(1, "playlist_dup: failed to allocate files"); for (i = 0; i < original->nfiles; i++) newplist->files[i] = original->files[i]; return newplist; } /* * Add files to a playlist at the index specified by start. Note that if * start is the length of the files array the files are appended to the end. */ void playlist_files_add(playlist *p, meta_info **f, int start, int size, bool record) { playlist_changeset *changes; int i; if (start < 0 || start > p->nfiles) errx(1, "playlist_file_add: index %d out of range", start); while (p->capacity <= p->nfiles + size) playlist_increase_capacity(p); /* push everything after start back size places */ for (i = p->nfiles + size; i > start; i--) p->files[i] = p->files[i - size]; /* add the files */ for (i = 0; i < size; i++) p->files[start + i] = f[i]; p->nfiles += size; /* update the history for this playlist */ if (record) { changes = changeset_create(CHANGE_ADD, size, f, start); playlist_history_push(p, changes); p->needs_saving = true; } } /* Append a file to the end of a playlist */ void playlist_files_append(playlist *p, meta_info **f, int size, bool record) { return playlist_files_add(p, f, p->nfiles, size, record); } /* Remove a file at a given index from a playlist. */ void playlist_files_remove(playlist *p, int start, int size, bool record) { playlist_changeset *changes; int i; if (start < 0 || start >= p->nfiles) errx(1, "playlist_remove_file: index %d out of range", start); if (record) { changes = changeset_create(CHANGE_REMOVE, size, &(p->files[start]), start); playlist_history_push(p, changes); p->needs_saving = true; } for (i = start; i < p->nfiles; i++) p->files[i] = p->files[i + size]; p->nfiles -= size; } /* Replaces the file at a given index in a playlist with a new file */ void playlist_file_replace(playlist *p, int index, meta_info *newEntry) { if (index < 0 || index >= p->nfiles) errx(1, "playlist_file_replace: index %d out of range", index); p->files[index] = newEntry; } /* Used with bsearch to find playlist entry by filename. */ static int cmp_fn_mi(const void *ai, const void *bi) { const char *a = (const char *) ai; const meta_info **b2 = (const meta_info **) bi; const meta_info *b = (const meta_info *) *b2; return strcmp(a, b->filename); } /* * Loads a playlist from the provided filename. The files within the playlist * are compared against the given meta-information-database to see if they * exist there. If they do, the corresponding entry in the playlist structure * built is simply a pointer to the existing entry. Otherwise, that file's * meta information is set to NULL and the file is copied (allocated) in the * playlist structure. * * A newly allocated playlist is returned. */ playlist * playlist_load(const char *filename, meta_info **db, int ndb) { meta_info *mi, **mit; FILE *fin; char *period; char entry[PATH_MAX + 1]; /* open file */ if ((fin = fopen(filename, "r")) == NULL) err(1, "playlist_load: failed to open playlist '%s'", filename); /* create playlist and setup */ playlist *p = playlist_new(); p->filename = strdup(filename); p->name = strdup(basename(filename)); if (p->filename == NULL || p->name == NULL) err(1, "playlist_load: failed to allocate info for playlist '%s'", filename); /* hack to remove '.playlist' from name */ period = strrchr(p->name, '.'); *period = '\0'; /* read each line from the file and copy into playlist object */ while (fgets(entry, PATH_MAX, fin) != NULL) { /* sanitize */ entry[strcspn(entry, "\n")] = '\0'; /* check if file exists in the meta info. db */ mit = bsearch(entry, db, ndb, sizeof(meta_info *), cmp_fn_mi); mi = *mit; if (mi != NULL) /* file DOES exist in DB */ playlist_files_append(p, &mi, 1, false); else { /* file does NOT exist in DB */ /* create empty meta-info object with just the file name */ mi = mi_new(); mi->filename = strdup(entry); if (mi->filename == NULL) err(1, "playlist_load: failed to strdup filename"); /* add new record to the db and link it to the playlist */ playlist_files_append(p, &mi, 1, false); warnx("playlist \"%s\", file \"%s\" is NOT in media database (added for now)", p->name, entry); } } fclose(fin); return p; } /* * Save a playlist to file. The filename used is whatever is in the * playlist. */ void playlist_save(const playlist *p) { FILE *fout; int i; if ((fout = fopen(p->filename, "w")) == NULL) err(1, "playlist_save: failed to open playlist \"%s\"", p->filename); /* write each song to file */ for (i = 0; i < p->nfiles; i++) { if (fprintf(fout, "%s\n", p->files[i]->filename) == -1) err(1, "playlist_save: failed to record playlist \"%s\"", p->filename); } fclose(fout); } /* * Deletes playlist from disk and destroy's the object, free()'ing all * memory. */ void playlist_delete(playlist *p) { /* delete file if the playlist is stored in a file */ if (p->filename != NULL && unlink(p->filename) != 0) err(1, "playlist_delete: failed to delete playlist \"%s\"", p->filename); /* destroy/free() all memory */ playlist_free(p); } /* * Filter a playlist. After a query string is setup using the meta_info * function "mi_query_set(..)" function, this function can be used to * filter out all of the entried of a playlist that match/do-not-match * that query string. * * The results are in the new playlist that is returned. * If no query is set with mi_query_init(), NULL is returned. * * The 'm' parameter controls if records matching should be returned * (m = true) or if records not matching should be returned (m=false) */ playlist * playlist_filter(const playlist *p, bool m) { playlist *results; int i; if (!mi_query_isset()) return NULL; results = playlist_new(); for (i = 0; i < p->nfiles; i++) { if (mi_match(p->files[i])) { if (m) playlist_files_append(results, &(p->files[i]), 1, false); } else { if (!m) playlist_files_append(results, &(p->files[i]), 1, false); } } return results; } /* * Builds an array of all files in the given directory with a '.playlist' * extension, returning the number of such files found. * * Parameters: * dirname C-string of directory containing playlist files * * filenames Pointer to an array of C-strings that will be allocated * and built in this function. This array is where the * filename of each playlist will be stored. * Returns: * The number of files with a '.playlist' extension that were found. * * Notes: * All allocation of the filenames array is handled here. It is the * responsibility of the caller to free() * 1. each element of that array * 2. the array itself. */ int retrieve_playlist_filenames(const char *dirname, char ***fnames) { char *glob_pattern; int fcount; glob_t files; int globbed; /* build the search pattern */ if (asprintf(&glob_pattern, "%s/*.playlist", dirname) == -1) errx(1, "failed in building glob pattern"); /* get the files */ globbed = glob(glob_pattern, 0, NULL, &files); if (globbed != 0 && globbed != GLOB_NOMATCH && errno != 0) err(1, "failed to glob playlists directory"); /* allocate & copy each of the filenames found into the filenames array */ if ((*fnames = calloc(files.gl_pathc, sizeof(char*))) == NULL) err(1, "failed to allocate playlist filenames array"); for (fcount = 0; fcount < files.gl_pathc; fcount++) { if (asprintf(&((*fnames)[fcount]), "%s", files.gl_pathv[fcount]) == -1) errx(1, "failed to allocate filename for playlist"); } /* cleanup */ globfree(&files); free(glob_pattern); return fcount; } playlist_changeset* changeset_create(short type, size_t size, meta_info **files, int loc) { size_t i; playlist_changeset *c; if ((c = malloc(sizeof(playlist_changeset))) == NULL) err(1, "%s: malloc(3) failed", __FUNCTION__); if ((c->files = calloc(size, sizeof(meta_info*))) == NULL) err(1, "%s: calloc(3) failed", __FUNCTION__); c->type = type; c->size = size; c->location = loc; for (i = 0; i < size; i++) c->files[i] = files[i]; return c; } void changeset_free(playlist_changeset *c) { free(c->files); free(c); } playlist_changeset** playlist_history_new(void) { playlist_changeset **h; int i; if ((h = calloc(history_size, sizeof(playlist_changeset*))) == NULL) err(1, "%s: calloc(3) failed", __FUNCTION__); for (i = 0; i < history_size; i++) h[i] = NULL; return h; } void playlist_history_free_future(playlist *p) { int i; for (i = p->hist_present + 1; i < history_size; i++) { if (p->history[i] != NULL) { changeset_free(p->history[i]); p->history[i] = NULL; } } } void playlist_history_free(playlist *p) { p->hist_present = 0; playlist_history_free_future(p); } void playlist_history_push(playlist *p, playlist_changeset *c) { int i; if (p->hist_present < history_size - 1) playlist_history_free_future(p); else { for (i = 0; i < history_size - 1; i++) p->history[i] = p->history[i + 1]; p->hist_present--; } p->hist_present++; p->history[p->hist_present] = c; } /* returns 0 if successfull, 1 if there was no history to undo */ int playlist_undo(playlist *p) { playlist_changeset *c; if (p->hist_present == -1) return 1; c = p->history[p->hist_present]; switch (c->type) { case CHANGE_ADD: playlist_files_remove(p, c->location, c->size, false); break; case CHANGE_REMOVE: playlist_files_add(p, c->files, c->location, c->size, false); break; default: errx(1, "%s: invalid change type", __FUNCTION__); } p->hist_present--; return 0; } /* returns 0 if successfull, 1 if there was no history to re-do */ int playlist_redo(playlist *p) { playlist_changeset *c; if (p->hist_present == history_size - 1 || p->history[p->hist_present + 1] == NULL) return 1; c = p->history[p->hist_present + 1]; switch (c->type) { case CHANGE_ADD: playlist_files_add(p, c->files, c->location, c->size, false); break; case CHANGE_REMOVE: playlist_files_remove(p, c->location, c->size, false); break; default: errx(1, "%s: invalid change type", __FUNCTION__); } p->hist_present++; return 0; }