1 /*
2  * Copyright (c) 2010, 2011 Ryan Flannery <ryan.flannery@gmail.com>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16 
17 #include "e_commands.h"
18 
19 const struct ecmd ECMD_PATH[] = {
20    { "init",      ecmd_init,     ecmd_help_init},
21    { "add",       ecmd_add,      ecmd_help_add },
22    { "addurl",    ecmd_addurl,   ecmd_help_addurl },
23    { "check",     ecmd_check,    ecmd_help_check },
24    { "rmfile",    ecmd_rmfile,   ecmd_help_rmfile },
25    { "rm",        ecmd_rmfile,   ecmd_help_rmfile },
26    { "update",    ecmd_update,   ecmd_help_update },
27    { "flush",     ecmd_flush,    ecmd_help_flush },
28    { "tag",       ecmd_tag,      ecmd_help_tag },
29    { "help",      ecmd_help,     NULL }
30 };
31 const int ECMD_PATH_SIZE = (sizeof(ECMD_PATH) / sizeof(struct ecmd));
32 
33 int
ecmd_init(int argc,char * argv[])34 ecmd_init(int argc, char *argv[])
35 {
36    if (argc != 1)
37       errx(1, "usage: -e %s", argv[0]);
38 
39    printf("Creating all necessary files and directories for vitunes...\n");
40    medialib_setup_files(vitunes_dir, db_file, playlist_dir);
41 
42    printf("\nNow use 'vitunes -e add dir1 dir2 ...' to add files to vitunes.\n");
43    return 0;
44 }
45 
46 int
ecmd_update(int argc,char * argv[])47 ecmd_update(int argc, char *argv[])
48 {
49    bool show_skipped = false;
50 
51    if (argc == 2 && strcmp(argv[1], "-s") == 0)
52       show_skipped = true;
53 
54    if (argc != 1 && !show_skipped)
55       errx(1, "usage: -e %s [-s]", argv[0]);
56 
57    printf("Loading existing database...\n");
58    medialib_load(db_file, playlist_dir);
59 
60    printf("Updating existing database...\n");
61    medialib_db_update(show_skipped);
62 
63    medialib_destroy();
64    return 0;
65 }
66 
67 int
ecmd_add(int argc,char * argv[])68 ecmd_add(int argc, char *argv[])
69 {
70    if (argc == 1)
71       errx(1, "usage: -e %s /path/to/filesORdirs [ ... ] ", argv[0]);
72 
73    printf("Loading existing database...\n");
74    medialib_load(db_file, playlist_dir);
75 
76    printf("Scanning directories for files to add to database...\n");
77    medialib_db_scan_dirs(argv + 1);
78 
79    medialib_destroy();
80    return 0;
81 }
82 
83 int
ecmd_addurl(int argc,char * argv[])84 ecmd_addurl(int argc, char *argv[])
85 {
86    meta_info   *m;
87    bool         found;
88    int          found_idx;
89    char         input[255];
90    int          field, i;
91 
92    if (argc != 2)
93       errx(1, "usage: -e %s filename|URL", argv[0]);
94 
95    /* start new record, set filename */
96    m = mi_new();
97    m->is_url = true;
98    if ((m->filename = strdup(argv[1])) == NULL)
99       err(1, "%s: strdup failed (filename)", argv[0]);
100 
101    /* get fields from user */
102    for (field = 0; field < MI_NUM_CINFO; field++) {
103 
104       printf("%10.10s: ", MI_CINFO_NAMES[field]);
105       if (fgets(input, sizeof(input), stdin) == NULL) {
106          warnx("Operation canceled. Database unchanged.");
107          mi_free(m);
108          return 0;
109       }
110 
111       if (input[strlen(input) - 1] == '\n')
112          input[strlen(input) - 1] = '\0';
113 
114       if ((m->cinfo[field] = strdup(input)) == NULL)
115          err(1, "%s: strdup failed (field)", argv[0]);
116    }
117 
118    /* load existing database and see if file/URL already exists */
119    medialib_load(db_file, playlist_dir);
120 
121    /* does the URL already exist in the database? */
122    found = false;
123    found_idx = -1;
124    for (i = 0; i < mdb.library->nfiles && !found; i++) {
125       if (strcmp(m->filename, mdb.library->files[i]->filename) == 0) {
126          found = true;
127          found_idx = i;
128       }
129    }
130 
131    if (found) {
132       printf("Warning: file/URL '%s' already in the database.\n", argv[0]);
133       printf("Do you want to replace the existing record? [y/n] ");
134 
135       if (fgets(input, sizeof(input), stdin) == NULL
136       || (strcasecmp(input, "yes\n") != 0 && strcasecmp(input, "y\n") != 0)) {
137          warnx("Operation Canceled.  Database unchanged.");
138          mi_free(m);
139          medialib_destroy();
140          return 0;
141       }
142 
143       mi_sanitize(m);
144       playlist_file_replace(mdb.library, found_idx, m);
145    } else {
146       mi_sanitize(m);
147       playlist_files_append(mdb.library, &m, 1, false);
148    }
149 
150    medialib_db_save(db_file);
151    medialib_destroy();
152 
153    return 0;
154 }
155 
156 int
ecmd_check(int argc,char * argv[])157 ecmd_check(int argc, char *argv[])
158 {
159    meta_info *mi;
160    bool   show_raw, show_sanitized, show_database;
161    bool   found;
162    char   ch;
163    char   realfile[PATH_MAX];
164    char **files;
165    int    nfiles;
166    int    f, i;
167 
168    if (argc < 3)
169       errx(1, "usage: -e %s [-rsd] file1 [ file2 ... ]", argv[0]);
170 
171    /* parse options to see which version to show */
172    show_raw = false;
173    show_sanitized = false;
174    show_database = false;
175    optreset = 1;
176    optind = 0;
177    while ((ch = getopt(argc, argv, "rsd")) != -1) {
178       switch (ch) {
179          case 'r':
180             show_raw = true;
181             break;
182          case 's':
183             show_sanitized = true;
184             break;
185          case 'd':
186             show_database = true;
187             break;
188          case 'h':
189          case '?':
190          default:
191             errx(1, "usage: -e %s [-rsd] file1 [ file2 ... ]", argv[0]);
192       }
193    }
194    files = argv + optind;
195    nfiles = argc - optind;
196 
197    if (!show_raw && !show_sanitized && !show_database)
198       errx(1, "%s: must specify at least one of -r, -s, or -d", argv[0]);
199 
200    if (nfiles == 0)
201       errx(1, "%s: must provide at least one file to check.", argv[0]);
202 
203    /* scan through files... */
204    for (f = 0; f < nfiles; f++) {
205 
206       printf("Checking: '%s'\n", files[f]);
207 
208       /* show raw or sanitized information */
209       if (show_raw || show_sanitized) {
210          mi = mi_extract(files[f]);
211          if (mi == NULL)
212             warnx("Failed to extract any meta-information from '%s'", files[f]);
213          else {
214             /* show raw info */
215             if (show_raw) {
216                printf("\tThe RAW meta-information from the file is:\n");
217                for (i = 0; i < MI_NUM_CINFO; i++)
218                   printf("\t%10.10s: '%s'\n", MI_CINFO_NAMES[i], mi->cinfo[i]);
219             }
220 
221             /* show sanitized info */
222             if (show_sanitized) {
223                mi_sanitize(mi);
224                printf("\tThe SANITIZED meta-information from the file is:\n");
225                for (i = 0; i < MI_NUM_CINFO; i++)
226                   printf("\t%10.10s: '%s'\n", MI_CINFO_NAMES[i], mi->cinfo[i]);
227             }
228          }
229       }
230 
231       /* check if it's in the database */
232       if (show_database) {
233 
234          /* get absolute filename */
235          if (realpath(files[f], realfile) == NULL) {
236             warn("%s: realpath failed for %s: skipping", argv[0], files[f]);
237             continue;
238          }
239 
240          /* check if file is in database */
241          medialib_load(db_file, playlist_dir);
242 
243          found = false;
244          mi = NULL;
245          for (i = 0; i < mdb.library->nfiles && !found; i++) {
246             if (strcmp(realfile, mdb.library->files[i]->filename) == 0) {
247                found = true;
248                mi = mdb.library->files[i];
249             }
250          }
251 
252          if (!found)
253             warnx("File '%s' does NOT exist in the database", files[f]);
254          else {
255             printf("\tThe meta-information in the DATABASE is:\n");
256             for (i = 0; i < MI_NUM_CINFO; i++)
257                printf("\t%10.10s: '%s'\n", MI_CINFO_NAMES[i], mi->cinfo[i]);
258          }
259 
260          medialib_destroy();
261       }
262 
263    }
264 
265    return 0;
266 }
267 
268 int
ecmd_rmfile(int argc,char * argv[])269 ecmd_rmfile(int argc, char *argv[])
270 {
271    char *filename;
272    char  input[255];
273    bool  forced;
274    bool  found;
275    int   found_idx;
276    int   i;
277 
278    if (argc < 2 || argc > 3)
279       errx(1, "usage: -e %s [-f] filename|URL", argv[0]);
280 
281    /* get filename and if this remove is forced */
282    forced = false;
283    if (argc == 3) {
284       if (strcmp(argv[1], "-f") != 0)
285          errx(1, "usage: -e %s [-f] filename|URL", argv[0]);
286       else
287          forced = true;
288 
289       filename = argv[2];
290    } else
291       filename = argv[1];
292 
293 
294    /* load database and search for record */
295    medialib_load(db_file, playlist_dir);
296    found = false;
297    found_idx = -1;
298    for (i = 0; i < mdb.library->nfiles && !found; i++) {
299       if (strcmp(filename, mdb.library->files[i]->filename) == 0) {
300          found = true;
301          found_idx = i;
302       }
303    }
304 
305    /* if not found then error */
306    if (!found) {
307       i = (forced ? 0 : 1);
308       errx(i, "%s: %s: No such file or URL", argv[0], filename);
309    }
310 
311    /* if not forced, prompt user if they are sure */
312    if (!forced) {
313       printf("Are you sure you want to delete '%s'? [y/n] ", filename);
314       if (fgets(input, sizeof(input), stdin) == NULL
315       || (strcasecmp(input, "yes\n") != 0 && strcasecmp(input, "y\n") != 0))
316          errx(1, "%s: operation canceled.  Database unchanged.", argv[0]);
317    }
318 
319    playlist_files_remove(mdb.library, found_idx, 1, false);
320    medialib_db_save(db_file);
321    medialib_destroy();
322    return 0;
323 }
324 
325 int
ecmd_flush(int argc,char * argv[])326 ecmd_flush(int argc, char *argv[])
327 {
328    char ch;
329    char *time_format = "%Y %m %d %H:%M:%S";
330 
331    optreset = 1;
332    optind = 0;
333    while ((ch = getopt(argc, argv, "t:")) != -1) {
334       switch (ch) {
335          case 't':
336             if ((time_format = strdup(optarg)) == NULL)
337                err(1, "%s: strdup of time_format failed", argv[0]);
338             break;
339          case '?':
340          case 'h':
341          default:
342             errx(1, "usage: %s [-t format]", argv[0]);
343       }
344    }
345 
346    medialib_load(db_file, playlist_dir);
347    medialib_db_flush(stdout, time_format);
348    medialib_destroy();
349    return 0;
350 }
351 
352 int
ecmd_tag(int argc,char * argv[])353 ecmd_tag(int argc, char *argv[])
354 {
355    TagLib_File *tag_file;
356    TagLib_Tag  *tag;
357    bool  set_artist  = false;
358    bool  set_album   = false;
359    bool  set_title   = false;
360    bool  set_genre   = false;
361    bool  set_track   = false;
362    bool  set_year    = false;
363    bool  set_comment = false;
364    char *artist = NULL, *album = NULL, *title = NULL, *genre = NULL,
365         *comment = NULL;
366    const char *errstr = NULL;
367    unsigned int track = 0, year = 0;
368    char   ch;
369    char **files;
370    int nfiles, f;
371 
372    static struct option longopts[] = {
373       { "artist",  required_argument, NULL, 'a' },
374       { "album",   required_argument, NULL, 'A' },
375       { "title",   required_argument, NULL, 't' },
376       { "genre",   required_argument, NULL, 'g' },
377       { "track",   required_argument, NULL, 'T' },
378       { "year",    required_argument, NULL, 'y' },
379       { "comment", required_argument, NULL, 'c' },
380       { NULL,     0,                 NULL,  0  }
381    };
382 
383    /* parse options and get list of files */
384    optreset = 1;
385    optind = 0;
386    while ((ch = getopt_long_only(argc, argv, "a:A:t:g:T:y:c:", longopts, NULL)) != -1) {
387       switch (ch) {
388          case 'a':
389             set_artist = true;
390             if ((artist = strdup(optarg)) == NULL)
391                err(1, "%s: strdup ARTIST failed", argv[0]);
392             break;
393          case 'A':
394             set_album = true;
395             if ((album = strdup(optarg)) == NULL)
396                err(1, "%s: strdup ALBUM failed", argv[0]);
397             break;
398          case 't':
399             set_title = true;
400             if ((title = strdup(optarg)) == NULL)
401                err(1, "%s: strdup TITLE failed", argv[0]);
402             break;
403          case 'g':
404             set_genre = true;
405             if ((genre = strdup(optarg)) == NULL)
406                err(1, "%s: strdup GENRE failed", argv[0]);
407             break;
408          case 'T':
409             set_track = true;
410             track = (unsigned int) strtonum(optarg, 0, INT_MAX, &errstr);
411             if (errstr != NULL)
412                errx(1, "%s: invalid track '%s': %s", argv[0], optarg, errstr);
413             break;
414          case 'y':
415             set_year = true;
416             year = (unsigned int) strtonum(optarg, 0, INT_MAX, &errstr);
417             if (errstr != NULL)
418                errx(1, "%s: invalid year '%s': %s", argv[0], optarg, errstr);
419             break;
420          case 'c':
421             set_comment = true;
422             if ((comment = strdup(optarg)) == NULL)
423                err(1, "%s: strdup COMMENT failed", argv[0]);
424             break;
425          case 'h':
426          case '?':
427          default:
428             errx(1, "usage: see 'vitunes -e help tag'");
429       }
430    }
431    files  = argv + optind;
432    nfiles = argc - optind;
433 
434    /* be verbose, indicate what we're setting... */
435    printf("Setting the following tags to all files:\n");
436    if (set_artist) printf("%10.10s => '%s'\n", "artist", artist);
437    if (set_album) printf("%10.10s => '%s'\n", "album", album);
438    if (set_title) printf("%10.10s => '%s'\n", "title", title);
439    if (set_genre) printf("%10.10s => '%s'\n", "genre", genre);
440    if (set_track) printf("%10.10s => %i\n", "track", track);
441    if (set_year) printf("%10.10s => %i\n", "year", year);
442    if (set_comment) printf("%10.10s => '%s'\n", "comment", comment);
443 
444    if (!set_artist && !set_album && !set_title && !set_genre
445    &&  !set_track && !set_year && !set_comment)
446       errx(1, "%s: nothing to set!  See 'vitunes -e help tag'", argv[0]);
447 
448    if (nfiles == 0)
449       errx(1, "%s: must provide at least one file to tag.", argv[0]);
450 
451    /* tag files ... */
452    taglib_set_strings_unicode(false);
453    for (f = 0; f < nfiles; f++) {
454       printf("tagging: '%s'\n", files[f]);
455 
456       /* extract taglib stuff */
457       if ((tag_file = taglib_file_new(files[f])) == NULL) {
458          warnx("TagLib: failed to open file '%s': skipping.", files[f]);
459          printf("  => Causes: format not supported by TagLib or format doesn't support tags\n");
460          fflush(stdout);
461          continue;
462       }
463 
464       tag = taglib_file_tag(tag_file);
465 
466       /* apply changes */
467       if (set_artist) taglib_tag_set_artist(tag, artist);
468       if (set_album) taglib_tag_set_album(tag, album);
469       if (set_title) taglib_tag_set_title(tag, title);
470       if (set_genre) taglib_tag_set_genre(tag, genre);
471       if (set_track) taglib_tag_set_track(tag, track);
472       if (set_year) taglib_tag_set_year(tag, year);
473       if (set_comment) taglib_tag_set_comment(tag, comment);
474 
475 
476       /* save changes and cleanup */
477       taglib_file_save(tag_file);
478       taglib_tag_free_strings();
479       taglib_file_free(tag_file);
480    }
481 
482    return 0;
483 }
484 
485 void
ecmd_help_init(void)486 ecmd_help_init(void)
487 {
488    printf("\
489 VITUNES COMMAND:\n\tinit - initialize vitunes directories/database\n\n\
490 SYNOPSIS:\n\tinit\n\n\
491 DESCRIPTION:\n\
492    The init command is used to setup the initial files and directories\n\
493    used by vitunes.  This includes:\n\n\
494       ~/.vitunes              The vitunes core directory where everything\n\
495                               is stored.\n\
496       ~/.vitunes/vitunes.db   The library database containing the meta\n\
497                               information of all media files.\n\
498       ~/.vitunes/playlists/   Directory where all playlists are stored.\n\n\
499    The database created is initially empty.\n\
500    If any of the above files/directories exists, nothing will be changed.\n\
501    This command takes no parameters.\n\n\
502    It is not strictly necessary to run this command to start using\n\
503    vitunes, as you could use an existing installation, or create the\n\
504    files/directories yourself.  It is provided for convenience.\n\n\
505 EXAMPLE:\n\
506       $ vitunes -e init\n\n\
507 ");
508 }
509 
510 void
ecmd_help_add(void)511 ecmd_help_add(void)
512 {
513    printf("\
514 VITUNES COMMAND:\n\tadd - add files to the vitunes database\n\n\
515 SYNOPSIS:\n\tadd /path/to/files1 [ path2 ... ]\n\n\
516 DESCRIPTION:\n\
517    The add command is used to add files to the database used by vitunes.\n\
518    For every file/directory provided as a parameter, vitunes will scan that\n\
519    file or directory (recursively) searching for media files that contain\n\
520    any meta information.  Any such files found are added to the database\n\
521    used by vitunes.  If any of the files found are already in the database\n\
522    then they will be re-scanned and any changes will be updated.\n\n\
523    Note that vitunes only maintains information about the file, and not the\n\
524    file itself.  It does NOT move/copy/modify the files in its database in\n\
525    any way.\n\n\
526    The information vitunes stores for each file includes:\n\
527       *  Filename                *  Track Number\n\
528       *  Artist Name             *  Year\n\
529       *  Album Name              *  Genre\n\
530       *  Song/Video Title        *  Play Length (seconds)\n\n\
531    The filename stored is the absolute pathname obtained from realpath(3),\n\
532    and serves as the key-field within the database.\n\n\
533    If any file encountered has no meta information, it is NOT added to the\n\
534    database.\n\n\
535 EXAMPLE:\n\
536    $ vitunes -e add ~/music /usr/local/share/music\n\n\
537 ");
538 }
539 
540 void
ecmd_help_addurl(void)541 ecmd_help_addurl(void)
542 {
543    printf("\
544 VITUNES COMMAND:\n\taddurl - add a URL (or other) to the vitunes database\n\n\
545 SYNOPSIS:\n\taddurl URL\n\n\
546 DESCRIPTION:\n\
547    To add non-standard-files to the vitunes database (things like URL's for\n\
548    Internet radio streams), one can use the addurl command. It takes a single\n\
549    parameter: the URL/filename to be added to the database. After that, you\n\
550    will be prompted to enter meta-information for each field vitunes indexes\n\
551    (artist, album, title, track, year, and genre). You may leave any/all of\n\
552    the fields blank.\n\n\
553    Basically, anything \"foo\" that mplayer can play by a simple:\n\
554       $ mplayer foo\n\
555    can be added to the database using this command.  Although regular files\n\
556    could also be added using this command, the add command is preferred, as\n\
557    it attempts to extract meta information automatically.\n\n\
558    Note that files added to the database using the addurl command are NOT\n\
559    checked for updates during an update command.\n\n\
560    Note that the addurl command can also be used to change the meta-\n\
561    information of an existing URL within the database.\n\n\
562 EXAMPLE:\n\
563    $ vitunes -e addurl \"http://198.234.121.118:80\"\n\
564    Artist: WVXU Online Radio<ENTER>\n\
565     Album: Cincinnati Public Radio<ENTER>\n\
566     Title: NPR<ENTER>\n\
567     Track: <ENTER>\n\
568      Year: <ENTER>\n\
569     Genre: Radio<ENTER>\n\
570    Length: INF<ENTER>\n\n\
571 NOTES:\n\
572    When the vitunes database has to be re-built (because of say, an upgrade\n\
573    where the database format has changed, or you simply deleted your\n\
574    database), re-adding URL's can be tedious.  To ease this, consider using\n\
575    a shell script such as the 'add_urls.sh' script found on the vitunes\n\
576    website for storing & adding all of your URL's.  The script simply\n\
577    executes the addurl command with all meta-information provided.  As an\n\
578    example, the above EXAMPLE could be automated as:\n\n\
579       #!/bin/sh\n\
580       echo \"WVXU Online Radio\\n\\\n\
581       Cincinnati Public Radio\\n\\\n\
582       NPR\\n\\\n\
583       \\n\\\n\
584       \\n\\\n\
585       Radio\\n\\\n\
586       INF\\n\" | vitunes -e addurl \"http://198.234.121.118:80\"\n\n\
587 ");
588 }
589 
590 void
ecmd_help_check(void)591 ecmd_help_check(void)
592 {
593    printf("\
594 VITUNES COMMAND:\n\tcheck - check files for meta-info and if they're in the DB\n\n\
595 SYNOPSIS:\n\tcheck [-rsd] file1 [ file2 ... ]\n\n\
596 DESCRIPTION:\n\
597    The check command will scan each filename provided to see to see if\n\
598    vitunes can extract any meta-information, to see how vitunes \"sanitizes\"\n\
599    the meta-information (see below), or what information vitunes currently has\n\
600    in the database for this file.\n\n\
601    Note that at least one of the -r, -s, or -d flags must be present.\n\n\
602    The options are as follows:\n\n\
603    -r    Show the raw information extracted directly from each file.\n\n\
604    -s    Show the sanitized information after extracting it from each file.\n\
605          See the section SANITATION below for details on what this is.\n\n\
606    -d    Load the vitunes database and check if the each exists within it.\n\
607          If so, show the information in the vitunes database.\n\n\
608    If multiple files are provided, each will be checked in order.\n\n\
609 SANITATION:\n\
610    By \"sanitation\" above, the following is meant: some media files have\n\
611    non-printable characters in their meta information for one reason or\n\
612    another (often, it's a faulty ripper/tagging application).  Sometimes\n\
613    these non-printable characters can be control sequences used by [n]curses\n\
614    and thus cause problems with the curses display of vitunes.  The way\n\
615    vitunes sanitizes such data is to replace all such control characters with\n\
616    an '?'.\n\n\
617 EXAMPLE:\n\
618    $ vitunes -e check -r /path/to/file.mp3\n\n\
619 ");
620 }
621 
622 void
ecmd_help_rmfile(void)623 ecmd_help_rmfile(void)
624 {
625    printf("\
626 VITUNES COMMAND:\n\trm - remove a single file/URL from the vitunes database\n\n\
627 SYNOPSIS:\n\trm [-f] filename/URL\n\n\
628 DESCRIPTION:\n\
629    To remove a single file/URL from the vitunes database, the rm command may\n\
630    be used.  It takes a single parameter: the filename/URL of the file to\n\
631    remove.  Normally, you will be prompted if you are sure you want to\n\
632    remove the file as a safety measure.  This prompt can be avoided using\n\
633    the '-f' flag, to force the removal.\n\n\
634    Note that to remove files, the full, absolute path to the file must be\n\
635    provided, as obtained from realpath(3).\n\n\
636 EXAMPLE:\n\
637    $ vitunes -e rm -f \"http://198.234.121.118/listen.pls\"\n\n\
638 ALIAS:\n\trmfile  -  As e-commands, \"rmfile\" and \"rm\" are interchangeable.\n\n\
639 ");
640 }
641 
642 void
ecmd_help_update(void)643 ecmd_help_update(void)
644 {
645    printf("\
646 VITUNES COMMAND:\n\tupdate - update vitunes database\n\n\
647 SYNOPSIS:\n\tupdate [-s]\n\n\
648 DESCRIPTION:\n\
649    The update command loads the existing meta information database used\n\
650    by vitunes and for each media file listed in the database, the file is\n\
651    checked to see if it has been removed or modified since it was added\n\
652    to the database.\n\n\
653    If the file has been removed, it will be removed from the database.  If\n\
654    the file has been modified, it's meta information will be extracted\n\
655    again and the database will be updated.\n\
656    Note that if there are errors while checking the file, the error will be\n\
657    reported but the file, and its meta information, will remain in the\n\
658    vitunes database.\n\n\
659       -s       When present, files that are skipped because they have not\n\
660                been modified will also be reported to stdout.  Normally,\n\
661                only files that are updated are reported.\n\n\
662    In short, anytime you remove/modify media files already in the vitunes\n\
663    database, you should run this command.\n\n\
664 NOTE ABOUT URLS:\n\
665    Note that files added to the database using the 'addurl' e-command will\n\
666    NOT be checked/updated in any way, for obvious reasons.\n\n\
667 EXAMPLE:\n\
668       $ vitunes -e update\n\n\
669 ");
670 }
671 
672 void
ecmd_help_flush(void)673 ecmd_help_flush(void)
674 {
675    printf("\
676 VITUNES COMMAND:\n\tflush - dump output of vitunes database to terminal\n\n\
677 SYNOPSIS:\n\tflush [-t time-format]\n\n\
678 DESCRIPTION:\n\
679    The flush command simply outputs the contents of the meta-information\n\
680    database used by vitunes to stdout, in a fairly easy to read (but very\n\
681    easy to parse/grep through) format.\n\n\
682    The one optional parameter, time-format, can be any string acceptable\n\
683    to strftime(3) and is used when displaying the 'last-updated' field of\n\
684    each record in the database (when the file was last checked for meta-\n\
685    information).\n\n\
686    The format used is a simple comma-separated-value (CSV) one, where most\n\
687    fields (any that can contain spaces/commas) are within double quotes.\n\
688    The first line contains the field names, and those fields that are quoted\n\
689    are also quoted in this header row.\n\n\
690 EXAMPLE:\n\
691    $ vitunes -e flush\n\n\
692    To see which files were last updated this month:\n\
693    $ vitunes -e flush -t \"%%M %%Y\" | grep \"January 2010\"\n\n\
694 REFERENCES:\n\
695    For some quick sed(1) one-liners on how to parse CSV data like the\n\
696    output of this command, you can visit the following website:\n\
697                http://sed.sourceforge.net/sedfaq4.html\n\n\
698 ");
699 }
700 
701 void
ecmd_help_tag(void)702 ecmd_help_tag(void)
703 {
704    printf("\
705 VITUNES COMMAND:\n\ttag - set meta-information tags to raw files\n\n\
706 SYNOPSIS:\n\ttag [--artist=string] [-a string] [--album=string] [-A value]\n\
707 \t    [--title=string] [-t string] [--genre=string] [-g string]\n\
708 \t    [--track=number] [-T number] [--year=number] [-y number]\n\
709 \t    [--comment=string] [-c string]\n\
710 \t    file1 [ file2 ... ]\n\n\
711 DESCRIPTION:\n\
712    The tag command is provided to add/change the meta-information tags of\n\
713    media files.  The meta-information fields that can be set are: artist,\n\
714    album, title, genre, track, and year.\n\n\
715    Please note that this command only changes the meta-information in the\n\
716    raw files themselves and NOT in the vitunes database.  To update the\n\
717    vitunes database after tagging, use the 'update' e-command.\n\n\
718    The tag command takes a series of field/value specifiers and any number\n\
719    of files.  For each file specified, the given field will be set to the\n\
720    provided value.\n\n\
721    The options are as follows:\n\n\
722    --artist=string\n\
723    -a string            Sets the artist field to the provided string.\n\n\
724    --album=string\n\
725    -A string            Sets the album field to the provided string.\n\n\
726    --title=string\n\
727    -t string            Sets the title field to the provided string.\n\n\
728    --genre=string\n\
729    -g string            Sets the genre field to the provided string.\n\n\
730    --comment=string\n\
731    -c string            Sets the comment field to the provided string.\n\n\
732    --track=number\n\
733    -T number            Sets the track field to the provided number.\n\
734                         Note that the number must be between 0 and\n\
735                         INT_MAX.\n\n\
736    --year=number\n\
737    -y number            Sets the year field to the provided number.\n\
738                         Note that the number must be between 0 and\n\
739                         INT_MAX.\n\n\
740    At least one tag option must be provided and at least one file must\n\
741    be provided.\n\n\
742 IMPORTANT NOTE:\n\
743    Just to reiterate a comment above, this command only changes the meta-\n\
744    information in the raw files themselves and NOT in the vitunes database.\n\
745    To update the vitunes database after tagging, use the 'update' or 'add'\n\
746    e-commands.\n\n\
747 EXAMPLE:\n\
748    CD rippers frequently pull information from CDDB (or other databases)\n\
749    where, for example, a \"The\" is missing from an artist/album name\n\
750    when it is, in fact, appropriate.  Below is an example of correcting\n\
751    this and then updating the vitunes database:\n\n\
752    $ vitunes -e tag --artist=\"The White Stripes\" /path/to/De_Stijl/*\n\
753    $ vitunes -e update\n\n\
754 ");
755 }
756 
757 int
ecmd_help(int argc,char * argv[])758 ecmd_help(int argc, char *argv[])
759 {
760    bool found;
761    int  i;
762 
763    if (argc > 2)
764       errx(1, "usage: -e %s [command]", argv[0]);
765 
766    /* no help requested for a specific command, give a list of all e-cmds */
767    if (argc == 1) {
768       printf("\
769 The following is a list of e-commands supported by vitunes.\n\
770 Each command is executed by doing:\n\n\
771    $ vitunes -e COMMAND [args ...]\n\n\
772 The complete manual for each can be obtained by doing:\n\n\
773    $ vitunes -e help COMMAND\n\n\
774 The list of available commands are:\n\n\
775    COMMAND     BRIEF DESCRIPTION\n\
776    -------     ------------------------------------------------------------\n\
777    init        Create the initial files used by vitunes.\n\n\
778    update      Load the existing database and check all files contained\n\
779                within to see they have been removed or modified.  The\n\
780                library is updated accordingly.\n\n\
781    add         Scan the list of provided files/directories for files to add\n\
782                to the database used by vitunes.\n\n\
783    addurl      Add a URL to the database, where you provide your own meta\n\
784                information.\n\n\
785    check       Check files to see what meta-information vitunes can extract,\n\
786                sanitize, and whether or not it's in the database.\n\n\
787    rm          Remove a file/URL from the database.\n\n\
788    rmfile      Alias for \"rm\".\n\n\
789    tag         Add/modify meta-information tags of raw files.\n\n\
790    flush       Load the existing database and dump it's information in an\n\
791                easy-to-parse format to stdout.\n\n\
792    help        This command.\n\n\
793 ");
794       return 0;
795    }
796 
797    /* if reach here, help fora specific command was requested */
798    found = false;
799    for (i = 0; i < ECMD_PATH_SIZE && !found; i++) {
800       if (strcmp(argv[1], "help") == 0) {
801          printf("You're a damn fool if you need help with help.\n");
802          found = true;
803       }
804       else if (strcmp(argv[1], ECMD_PATH[i].name) == 0) {
805          (ECMD_PATH[i].help)();
806          found = true;
807       }
808    }
809 
810    if (!found)
811       printf("Unknown command \"%s\".  See \"vitunes -e help\" for list.\n", argv[1]);
812 
813    return 0;
814 }
815 
816