File: | medialib.c |
Location: | line 121, column 36 |
Description: | Array access (via field 'playlists') results in a null pointer dereference |
1 | /* | |||
2 | * Copyright (c) 2010, 2011, 2012 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 "medialib.h" | |||
18 | ||||
19 | /* The global media library struct */ | |||
20 | medialib mdb; | |||
21 | ||||
22 | /* | |||
23 | * Load the global media library from disk. The location of the database file | |||
24 | * and the directory containing all of the playlists must be specified. | |||
25 | */ | |||
26 | void | |||
27 | medialib_load(const char *db_file, const char *playlist_dir) | |||
28 | { | |||
29 | playlist *p; | |||
30 | char **pfiles; | |||
31 | int npfiles; | |||
32 | int i; | |||
33 | ||||
34 | /* copy file/directory names */ | |||
35 | mdb.db_file = strdup(db_file); | |||
36 | mdb.playlist_dir = strdup(playlist_dir); | |||
37 | if (mdb.db_file == NULL((void *)0) || mdb.playlist_dir == NULL((void *)0)) | |||
38 | err(1, "failed to strdup db file and playlist dir in medialib_init"); | |||
39 | ||||
40 | /* setup pseudo-playlists */ | |||
41 | mdb.library = playlist_new(); | |||
42 | mdb.library->filename = NULL((void *)0); | |||
43 | mdb.library->name = strdup("--LIBRARY--"); | |||
44 | ||||
45 | mdb.filter_results = playlist_new(); | |||
46 | mdb.filter_results->filename = NULL((void *)0); | |||
47 | mdb.filter_results->name = strdup("--FILTER--"); | |||
48 | ||||
49 | if (mdb.library->name == NULL((void *)0) || mdb.filter_results->name == NULL((void *)0)) | |||
50 | err(1, "failed to strdup pseudo-names in medialib_load"); | |||
51 | ||||
52 | /* load the actual database */ | |||
53 | medialib_db_load(db_file); | |||
54 | ||||
55 | /* setup initial record keeping for playlists */ | |||
56 | mdb.nplaylists = 0; | |||
57 | mdb.playlists_capacity = 2; | |||
58 | mdb.playlists = calloc(2, sizeof(playlist*)); | |||
59 | if (mdb.playlists == NULL((void *)0)) | |||
60 | err(1, "medialib_load: failed to allocate initial playlists"); | |||
61 | ||||
62 | /* add library/filter pseudo-playlists */ | |||
63 | medialib_playlist_add(mdb.library); | |||
64 | medialib_playlist_add(mdb.filter_results); | |||
65 | ||||
66 | /* load the rest */ | |||
67 | npfiles = retrieve_playlist_filenames(mdb.playlist_dir, &pfiles); | |||
68 | for (i = 0; i < npfiles; i++) { | |||
69 | p = playlist_load(pfiles[i], mdb.library->files, mdb.library->nfiles); | |||
70 | medialib_playlist_add(p); | |||
71 | free(pfiles[i]); | |||
72 | } | |||
73 | ||||
74 | /* set all playlists as saved initially */ | |||
75 | for (i = 0; i < mdb.nplaylists; i++) | |||
76 | mdb.playlists[i]->needs_saving = false0; | |||
77 | ||||
78 | free(pfiles); | |||
79 | } | |||
80 | ||||
81 | /* free() all memory associated with global media library */ | |||
82 | void | |||
83 | medialib_destroy() | |||
84 | { | |||
85 | int i; | |||
86 | ||||
87 | /* free the database */ | |||
88 | for (i = 0; i < mdb.library->nfiles; i++) | |||
89 | mi_free(mdb.library->files[i]); | |||
90 | ||||
91 | /* free all the playlists */ | |||
92 | for (i = 0; i < mdb.nplaylists; i++) | |||
93 | playlist_free(mdb.playlists[i]); | |||
94 | ||||
95 | /* free all other allocated mdb members */ | |||
96 | free(mdb.playlists); | |||
97 | free(mdb.db_file); | |||
98 | free(mdb.playlist_dir); | |||
99 | ||||
100 | /* reset counters */ | |||
101 | mdb.nplaylists = 0; | |||
102 | mdb.playlists_capacity = 0; | |||
103 | } | |||
104 | ||||
105 | /* add a new playlist to the media library */ | |||
106 | void | |||
107 | medialib_playlist_add(playlist *p) | |||
108 | { | |||
109 | playlist **new_playlists; | |||
110 | ||||
111 | /* check to see if we need to resize the array */ | |||
112 | if (mdb.nplaylists == mdb.playlists_capacity) { | |||
| ||||
113 | mdb.playlists_capacity += MEDIALIB_PLAYLISTS_CHUNK_SIZE100; | |||
114 | int size = mdb.playlists_capacity * sizeof(playlist*); | |||
115 | if ((new_playlists = realloc(mdb.playlists, size)) == NULL((void *)0)) | |||
116 | err(1, "medialib_playlist_add: realloc failed"); | |||
117 | ||||
118 | mdb.playlists = new_playlists; | |||
119 | } | |||
120 | ||||
121 | mdb.playlists[mdb.nplaylists++] = p; | |||
| ||||
122 | } | |||
123 | ||||
124 | /* | |||
125 | * remove a playlist from the media library, and disk, given the playlist's | |||
126 | * index in the playlists array. | |||
127 | */ | |||
128 | void | |||
129 | medialib_playlist_remove(int pindex) | |||
130 | { | |||
131 | int i; | |||
132 | ||||
133 | if (pindex < 0 || pindex >= mdb.nplaylists) | |||
134 | errx(1, "medialib_playlist_remove: index %d out of range", pindex); | |||
135 | ||||
136 | playlist_delete(mdb.playlists[pindex]); | |||
137 | ||||
138 | /* reorder */ | |||
139 | for (i = pindex + 1; i < mdb.nplaylists; i++) | |||
140 | mdb.playlists[i - 1] = mdb.playlists[i]; | |||
141 | ||||
142 | mdb.nplaylists--; | |||
143 | } | |||
144 | ||||
145 | /* | |||
146 | * create the vitunes directory, database file (initially empty, and | |||
147 | * playlists directory. | |||
148 | */ | |||
149 | void | |||
150 | medialib_setup_files(const char *vitunes_dir, const char *db_file, | |||
151 | const char *playlist_dir) | |||
152 | { | |||
153 | struct stat sb; | |||
154 | ||||
155 | /* create vitunes directory */ | |||
156 | if (mkdir(vitunes_dir, S_IRWXU0000700) == -1) { | |||
157 | if (errno(*__errno()) == EEXIST17) | |||
158 | warnx("vitunes directory '%s' already exists (OK)", vitunes_dir); | |||
159 | else | |||
160 | err(1, "unable to create vitunes directory '%s'", vitunes_dir); | |||
161 | } else | |||
162 | warnx("vitunes directory '%s' created", vitunes_dir); | |||
163 | ||||
164 | /* create playlists directory */ | |||
165 | if (mkdir(playlist_dir, S_IRWXU0000700) == -1) { | |||
166 | if (errno(*__errno()) == EEXIST17) | |||
167 | warnx("playlists directory '%s' already exists (OK)", playlist_dir); | |||
168 | else | |||
169 | err(1, "unable to create playlists directory '%s'", playlist_dir); | |||
170 | } else | |||
171 | warnx("playlists directory '%s' created", playlist_dir); | |||
172 | ||||
173 | /* create database file */ | |||
174 | if (stat(db_file, &sb) < 0) { | |||
175 | if (errno(*__errno()) == ENOENT2) { | |||
176 | ||||
177 | int version[3] = {DB_VERSION_MAJOR2, DB_VERSION_MINOR1, DB_VERSION_OTHER0}; | |||
178 | FILE *f; | |||
179 | ||||
180 | /* open for writing */ | |||
181 | if ((f = fopen(db_file, "w")) == NULL((void *)0)) | |||
182 | err(1, "failed to create database file '%s'", db_file); | |||
183 | ||||
184 | /* save header & version */ | |||
185 | fwrite("vitunes", strlen("vitunes"), 1, f); | |||
186 | fwrite(version, sizeof(version), 1, f); | |||
187 | ||||
188 | warnx("empty database at '%s' created", db_file); | |||
189 | fclose(f); | |||
190 | } else | |||
191 | err(1, "database file '%s' exists, but cannot access it", db_file); | |||
192 | } else | |||
193 | warnx("database file '%s' already exists (OK)", db_file); | |||
194 | } | |||
195 | ||||
196 | /* used to sort media db by filenames. */ | |||
197 | static int mi_cmp_fn(const void *ai, const void *bi) | |||
198 | { | |||
199 | const meta_info **a2 = (const meta_info **) ai; | |||
200 | const meta_info **b2 = (const meta_info **) bi; | |||
201 | const meta_info *a = (const meta_info *) *a2; | |||
202 | const meta_info *b = (const meta_info *) *b2; | |||
203 | ||||
204 | return strcmp(a->filename, b->filename); | |||
205 | } | |||
206 | ||||
207 | /* load the library database into the global media library */ | |||
208 | void | |||
209 | medialib_db_load(const char *db_file) | |||
210 | { | |||
211 | meta_info *mi; | |||
212 | FILE *fin; | |||
213 | char header[255] = { 0 }; | |||
214 | int version[3]; | |||
215 | ||||
216 | if ((fin = fopen(db_file, "r")) == NULL((void *)0)) | |||
217 | err(1, "Failed to open database file '%s'", db_file); | |||
218 | ||||
219 | /* read and check header & version */ | |||
220 | fread(header, strlen("vitunes"), 1, fin); | |||
221 | if (strncmp(header, "vitunes", strlen("vitunes")) != 0) | |||
222 | errx(1, "Database file '%s' NOT a vitunes database", db_file); | |||
223 | ||||
224 | fread(version, sizeof(version), 1, fin); | |||
225 | if (version[0] != DB_VERSION_MAJOR2 || version[1] != DB_VERSION_MINOR1 | |||
226 | || version[2] != DB_VERSION_OTHER0) { | |||
227 | printf("Loading vitunes database: old database version detected.\n"); | |||
228 | printf("\tExisting database at '%s' is of version %d.%d.%d\n", | |||
229 | db_file, version[0], version[1], version[2]); | |||
230 | printf("\tThis version of vitunes only works with version %d.%d.%d\n", | |||
231 | DB_VERSION_MAJOR2, DB_VERSION_MINOR1, DB_VERSION_OTHER0); | |||
232 | printf("Remove the existing database and rebuild by doing:\n"); | |||
233 | printf("\t$ rm %s\n", db_file); | |||
234 | printf("\t$ vitunes -e init\n"); | |||
235 | printf("\t$ vitunes -e add path [...]\n"); | |||
236 | fflush(stdout(&__sF[1])); | |||
237 | exit(1); | |||
238 | } | |||
239 | ||||
240 | /* read rest of records */ | |||
241 | while (!feof(fin)(!__isthreaded ? (((fin)->_flags & 0x0020) != 0) : (feof )(fin))) { | |||
242 | mi = mi_new(); | |||
243 | mi_fread(mi, fin); | |||
244 | if (feof(fin)(!__isthreaded ? (((fin)->_flags & 0x0020) != 0) : (feof )(fin))) | |||
245 | mi_free(mi); | |||
246 | else if (ferror(fin)(!__isthreaded ? (((fin)->_flags & 0x0040) != 0) : (ferror )(fin))) | |||
247 | err(1, "Error loading database file '%s'", db_file); | |||
248 | else | |||
249 | playlist_files_append(mdb.library, &mi, 1, false0); | |||
250 | } | |||
251 | ||||
252 | fclose(fin); | |||
253 | ||||
254 | /* sort library by filenames */ | |||
255 | qsort(mdb.library->files, mdb.library->nfiles, sizeof(meta_info*), mi_cmp_fn); | |||
256 | } | |||
257 | ||||
258 | /* save the library database from the global media library to disk */ | |||
259 | void | |||
260 | medialib_db_save(const char *db_file) | |||
261 | { | |||
262 | FILE *fout; | |||
263 | int version[3] = {DB_VERSION_MAJOR2, DB_VERSION_MINOR1, DB_VERSION_OTHER0}; | |||
264 | int i; | |||
265 | ||||
266 | if ((fout = fopen(db_file, "w")) == NULL((void *)0)) | |||
267 | err(1, "medialib_db_save: failed to open database file '%s'", db_file); | |||
268 | ||||
269 | /* save header & version */ | |||
270 | fwrite("vitunes", strlen("vitunes"), 1, fout); | |||
271 | fwrite(version, sizeof(version), 1, fout); | |||
272 | ||||
273 | /* save records */ | |||
274 | for (i = 0; i < mdb.library->nfiles; i++) { | |||
275 | mi_fwrite(mdb.library->files[i], fout); | |||
276 | if (ferror(fout)(!__isthreaded ? (((fout)->_flags & 0x0040) != 0) : (ferror )(fout))) | |||
277 | err(1, "medialib_db_save: error saving database"); | |||
278 | } | |||
279 | ||||
280 | fclose(fout); | |||
281 | } | |||
282 | ||||
283 | /* flush the library to stdout in a csv format */ | |||
284 | void | |||
285 | medialib_db_flush(FILE *fout, const char *timefmt) | |||
286 | { | |||
287 | meta_info *mi; | |||
288 | struct tm *ltime; | |||
289 | char stime[255]; | |||
290 | int f, i; | |||
291 | size_t len; | |||
292 | ||||
293 | /* header row */ | |||
294 | fprintf(fout, "filename, "); | |||
295 | for (i = 0; i < MI_NUM_CINFO8; i++) | |||
296 | fprintf(fout, "\"%s\", ", MI_CINFO_NAMES[i]); | |||
297 | ||||
298 | fprintf(fout, "length-seconds, is_url, \"last-updated\"\n"); | |||
299 | fflush(fout); | |||
300 | ||||
301 | /* start output of db */ | |||
302 | for (f = 0; f < mdb.library->nfiles; f++) { | |||
303 | ||||
304 | /* get record */ | |||
305 | mi = mdb.library->files[f]; | |||
306 | ||||
307 | /* output record */ | |||
308 | fprintf(fout, "%s, ", mi->filename); | |||
309 | for (i = 0; i < MI_NUM_CINFO8; i++) | |||
310 | fprintf(fout, "\"%s\", ", mi->cinfo[i]); | |||
311 | ||||
312 | /* convert last-updated time to string */ | |||
313 | ltime = localtime(&(mi->last_updated)); | |||
314 | len = strftime(stime, sizeof(stime), timefmt, ltime); | |||
315 | stime[len] = '\0'; | |||
316 | ||||
317 | fprintf(fout, "%i, %s, \"%s\"\n", | |||
318 | mi->length, (mi->is_url ? "true" : "false"), stime); | |||
319 | ||||
320 | fflush(fout); | |||
321 | } | |||
322 | } | |||
323 | ||||
324 | /* | |||
325 | * AFTER loading the global media library using medialib_load(), this function | |||
326 | * is used to re-scan all files that exist in the database and re-check their | |||
327 | * meta_info. Any files that no longer exist are removed, and any meta | |||
328 | * information that has changed is updated. | |||
329 | * | |||
330 | * The database is then re-saved to disk. | |||
331 | */ | |||
332 | void | |||
333 | medialib_db_update(bool_Bool show_skipped, bool_Bool force_update) | |||
334 | { | |||
335 | meta_info *mi; | |||
336 | struct stat sb; | |||
337 | char *filename; | |||
338 | int i; | |||
339 | ||||
340 | /* stat counters */ | |||
341 | int count_removed_file_gone = 0; | |||
342 | int count_removed_meta_gone = 0; | |||
343 | int count_skipped_not_updated = 0; | |||
344 | int count_updated = 0; | |||
345 | int count_errors = 0; | |||
346 | int count_urls = 0; | |||
347 | ||||
348 | for (i = 0; i < mdb.library->nfiles; i++) { | |||
349 | ||||
350 | filename = mdb.library->files[i]->filename; | |||
351 | ||||
352 | /* skip url's */ | |||
353 | if (mdb.library->files[i]->is_url) { | |||
354 | printf("s %s\n", filename); | |||
355 | count_urls++; | |||
356 | continue; | |||
357 | } | |||
358 | ||||
359 | if (stat(filename, &sb) == -1) { | |||
360 | ||||
361 | /* file was removed -or- stat() failed */ | |||
362 | ||||
363 | if (errno(*__errno()) == ENOENT2) { | |||
364 | /* file was removed, remove from library */ | |||
365 | playlist_files_remove(mdb.library, i, 1, false0); | |||
366 | i--; /* since removed a file, we want to decrement i */ | |||
367 | printf("x %s\n", filename); | |||
368 | count_removed_file_gone++; | |||
369 | } else { | |||
370 | /* stat() failed for some reason - unknown error */ | |||
371 | printf("? %s\n", filename); | |||
372 | count_errors++; | |||
373 | } | |||
374 | ||||
375 | } else { | |||
376 | ||||
377 | /* | |||
378 | * file still exists... check if it has been modified since we | |||
379 | * last extracted meta-info from it (otherwise we ignore) | |||
380 | */ | |||
381 | ||||
382 | if (force_update || | |||
383 | (sb.st_mtimest_mtim.tv_sec > mdb.library->files[i]->last_updated)) { | |||
384 | ||||
385 | mi = mi_extract(filename); | |||
386 | if (mi == NULL((void *)0)) { | |||
387 | /* file now has no meta-info, remove from library */ | |||
388 | playlist_files_remove(mdb.library, i, 1, false0); | |||
389 | i--; /* since removed a file, we want to decrement i */ | |||
390 | printf("- %s\n", filename); | |||
391 | count_removed_meta_gone++; | |||
392 | } else { | |||
393 | /* file's meta-info has changed, update it */ | |||
394 | mi_sanitize(mi); | |||
395 | playlist_file_replace(mdb.library, i, mi); | |||
396 | printf("u %s\n", filename); | |||
397 | count_updated++; | |||
398 | } | |||
399 | } else { | |||
400 | count_skipped_not_updated++; | |||
401 | if (show_skipped) | |||
402 | printf(". %s\n", filename); | |||
403 | } | |||
404 | ||||
405 | } | |||
406 | } | |||
407 | ||||
408 | /* save to file */ | |||
409 | medialib_db_save(mdb.db_file); | |||
410 | ||||
411 | /* output some of our stats */ | |||
412 | printf("--------------------------------------------------\n"); | |||
413 | printf("Results of updating database...\n"); | |||
414 | printf("(s) %9d url's skipped\n", count_urls); | |||
415 | printf("(u) %9d files updated\n", count_updated); | |||
416 | printf("(x) %9d files removed (file no longer exists)\n", | |||
417 | count_removed_file_gone); | |||
418 | printf("(-) %9d files removed (meta-info gone)\n", | |||
419 | count_removed_meta_gone); | |||
420 | printf("(.) %9d files skipped (file unchanged since last checked)\n", | |||
421 | count_skipped_not_updated); | |||
422 | printf("(?) %9d files with errors (couldn't stat, but kept)\n", | |||
423 | count_errors); | |||
424 | } | |||
425 | ||||
426 | /* | |||
427 | * AFTER loading the global media library using medialib_load(), this function | |||
428 | * will scan the list of directories specified in the parameter and | |||
429 | */ | |||
430 | void | |||
431 | medialib_db_scan_dirs(char *dirlist[]) | |||
432 | { | |||
433 | FTS *fts; | |||
434 | FTSENT *ftsent; | |||
435 | meta_info *mi; | |||
436 | char fullname[PATH_MAX1024]; | |||
437 | int i, idx; | |||
438 | ||||
439 | /* stat counters */ | |||
440 | int count_removed_lost_info = 0; | |||
441 | int count_updated = 0; | |||
442 | int count_skipped_no_info = 0; | |||
443 | int count_skipped_dir = 0; | |||
444 | int count_skipped_error = 0; | |||
445 | int count_skipped_not_updated = 0; | |||
446 | int count_added = 0; | |||
447 | ||||
448 | ||||
449 | ||||
450 | fts = fts_open(dirlist, FTS_LOGICAL0x0002 | FTS_NOCHDIR0x0004, NULL((void *)0)); | |||
451 | if (fts == NULL((void *)0)) | |||
452 | err(1, "medialib_db_scan_dirs: fts_open failed"); | |||
453 | ||||
454 | while ((ftsent = fts_read(fts)) != NULL((void *)0)) { | |||
455 | ||||
456 | switch (ftsent->fts_info) { /* file type */ | |||
457 | case FTS_D1: /* TYPE: directory (going in) */ | |||
458 | printf("Checking Directory: %s\n", ftsent->fts_path); | |||
459 | break; | |||
460 | ||||
461 | case FTS_DP6: /* TYPE: directory (coming out) */ | |||
462 | break; | |||
463 | ||||
464 | case FTS_DNR4: /* TYPE: unreadable directory */ | |||
465 | printf("Directory '%s' Unreadable\n", ftsent->fts_accpath); | |||
466 | count_skipped_dir++; | |||
467 | break; | |||
468 | ||||
469 | case FTS_NS10: /* TYPE: file/dir that couldn't be stat(2) */ | |||
470 | case FTS_ERR7: /* TYPE: other error */ | |||
471 | printf("? %s\n", ftsent->fts_path); | |||
472 | count_skipped_error++; | |||
473 | break; | |||
474 | ||||
475 | case FTS_F8: /* TYPE: regular file */ | |||
476 | ||||
477 | /* get the full name for the file */ | |||
478 | if (realpath(ftsent->fts_accpath, fullname) == NULL((void *)0)) { | |||
479 | err(1, "medialib_db_scan_dirs: realpath failed for '%s'", | |||
480 | ftsent->fts_accpath); | |||
481 | } | |||
482 | ||||
483 | /* check if the file already exists in the db */ | |||
484 | idx = -1; | |||
485 | for (i = 0; i < mdb.library->nfiles; i++) { | |||
486 | if (strcmp(fullname, mdb.library->files[i]->filename) == 0) | |||
487 | idx = i; | |||
488 | } | |||
489 | ||||
490 | if (idx != -1) { | |||
491 | /* file already exists in library database - update */ | |||
492 | ||||
493 | if (ftsent->fts_statp->st_mtimest_mtim.tv_sec > | |||
494 | mdb.library->files[idx]->last_updated) { | |||
495 | ||||
496 | /* file has been modified since we last extracted info */ | |||
497 | ||||
498 | mi = mi_extract(ftsent->fts_accpath); | |||
499 | ||||
500 | if (mi == NULL((void *)0)) { | |||
501 | /* file now has no meta-info, remove from library */ | |||
502 | playlist_files_remove(mdb.library, idx, 1, false0); | |||
503 | printf("- %s\n", ftsent->fts_accpath); | |||
504 | count_removed_lost_info++; | |||
505 | } else { | |||
506 | /* file's meta-info has changed, update it */ | |||
507 | mi_sanitize(mi); | |||
508 | playlist_file_replace(mdb.library, idx, mi); | |||
509 | printf("u %s\n", ftsent->fts_accpath); | |||
510 | count_updated++; | |||
511 | } | |||
512 | } else { | |||
513 | printf(". %s\n", ftsent->fts_accpath); | |||
514 | count_skipped_not_updated++; | |||
515 | } | |||
516 | ||||
517 | } else { | |||
518 | ||||
519 | /* file does NOT exists in library database - add it */ | |||
520 | ||||
521 | mi = mi_extract(ftsent->fts_accpath); | |||
522 | ||||
523 | if (mi == NULL((void *)0)) { | |||
524 | /* file has no info */ | |||
525 | printf("s %s\n", ftsent->fts_accpath); | |||
526 | count_skipped_no_info++; | |||
527 | } else { | |||
528 | /* file does have info, add it to library */ | |||
529 | mi_sanitize(mi); | |||
530 | playlist_files_append(mdb.library, &mi, 1, false0); | |||
531 | printf("+ %s\n", ftsent->fts_accpath); | |||
532 | count_added++; | |||
533 | } | |||
534 | } | |||
535 | } | |||
536 | } | |||
537 | ||||
538 | if (fts_close(fts) == -1) | |||
539 | err(1, "medialib_db_scan_dirs: failed to close file heirarchy"); | |||
540 | ||||
541 | /* save to file */ | |||
542 | medialib_db_save(mdb.db_file); | |||
543 | ||||
544 | /* output some of our stats */ | |||
545 | printf("--------------------------------------------------\n"); | |||
546 | printf("Results of scanning directories...\n"); | |||
547 | printf("(+) %9d files added\n", count_added); | |||
548 | printf("(u) %9d files updated\n", count_updated); | |||
549 | printf("(-) %9d files removed (was in DB, but no longer has meta-info)\n", | |||
550 | count_removed_lost_info); | |||
551 | printf("(.) %9d files skipped (in DB, file unchanged since last checked)\n", | |||
552 | count_skipped_not_updated); | |||
553 | printf("(s) %9d files skipped (no info)\n", count_skipped_no_info); | |||
554 | printf("(?) %9d files skipped (other error)\n", count_skipped_error); | |||
555 | printf(" %9d directories skipped (couldn't read)\n", count_skipped_dir); | |||
556 | } |