data.c (12356B)
1#include "data.h" 2 3#include "tui.h" 4#include "player.h" 5#include "list.h" 6#include "log.h" 7 8#include <asm-generic/errno-base.h> 9#include <fts.h> 10#include <errno.h> 11#include <dirent.h> 12#include <sys/stat.h> 13#include <unistd.h> 14#include <stdbool.h> 15#include <string.h> 16#include <stdlib.h> 17 18const char *datadir; 19 20struct list tracks; /* struct track (link) */ 21struct list tags; /* struct track (link) */ 22struct list tags_sel; /* struct tag (link_sel) */ 23 24struct tag *trash_tag; 25 26bool playlist_outdated; 27 28static struct tag *tag_alloc(const char *path, const char *fname); 29static void tag_free(struct tag *tag); 30 31static struct track *track_alloc(const char *path, const char *fname); 32static void track_free(struct track *t); 33 34static bool tag_name_cmp(const void *p1, const void *p2, void *user); 35 36struct tag * 37tag_alloc(const char *path, const char *fname) 38{ 39 struct tag *tag; 40 41 tag = malloc(sizeof(struct tag)); 42 if (!tag) ERROR(SYSTEM, "malloc"); 43 44 tag->fpath = aprintf("%s/%s", path, fname); 45 tag->name = astrdup(fname); 46 tag->index_dirty = false; 47 tag->reordered = false; 48 tag->link = LIST_LINK_INIT; 49 tag->link_sel = LIST_LINK_INIT; 50 list_init(&tag->tracks); 51 52 return tag; 53} 54 55void 56tag_free(struct tag *tag) 57{ 58 free(tag->fpath); 59 free(tag->name); 60 list_clear(&tag->tracks); 61 free(tag); 62} 63 64struct track * 65track_alloc(const char *dir, const char *fname) 66{ 67 struct track *track; 68 69 track = malloc(sizeof(struct track)); 70 if (!track) ERROR(SYSTEM, "malloc"); 71 72 track->fpath = aprintf("%s/%s", dir, fname); 73 track->name = astrdup(fname); 74 track->tag = NULL; 75 track->link = LIST_LINK_INIT; 76 track->link_pl = LIST_LINK_INIT; 77 track->link_tt = LIST_LINK_INIT; 78 track->link_pq = LIST_LINK_INIT; 79 track->link_hs = LIST_LINK_INIT; 80 81 return track; 82} 83 84void 85track_free(struct track *t) 86{ 87 free(t->fpath); 88 free(t->name); 89 free(t); 90} 91 92bool 93tag_name_cmp(const void *p1, const void *p2, void *user) 94{ 95 const struct tag *t1 = p1, *t2 = p2; 96 97 return strcmp(t1->name, t2->name) <= 0; 98} 99 100bool 101path_exists(const char *path) 102{ 103 return access(path, F_OK) == 0; 104} 105 106bool 107make_dir(const char *path) 108{ 109 return mkdir(path, S_IRWXU | S_IRWXG | S_IRWXO) == 0; 110} 111 112bool 113move_dir(const char *src, const char *dst) 114{ 115 return rename(src, dst) == 0; 116} 117 118bool 119rm_dir(const char *path, bool recursive) 120{ 121 char *files[] = { (char *) path, NULL }; 122 FTSENT *ent; 123 FTS *fts; 124 int flags; 125 126 if (recursive) { 127 flags = FTS_NOCHDIR | FTS_PHYSICAL | FTS_XDEV; 128 fts = fts_open(files, flags, NULL); 129 if (!fts) return false; 130 131 while ((ent = fts_read(fts))) { 132 switch (ent->fts_info) { 133 case FTS_NS: 134 case FTS_DNR: 135 case FTS_ERR: 136 fts_close(fts); 137 return false; 138 case FTS_D: 139 break; 140 default: 141 if (remove(ent->fts_accpath) < 0) { 142 fts_close(fts); 143 return false; 144 } 145 break; 146 } 147 } 148 149 fts_close(fts); 150 } else { 151 if (rmdir(path) != 0) 152 return false; 153 } 154 155 return true; 156} 157 158bool 159rm_file(const char *path) 160{ 161 return unlink(path) == 0; 162} 163 164bool 165copy_file(const char *src, const char *dst) 166{ 167 FILE *in, *out; 168 char buf[4096]; 169 int nread; 170 bool ok; 171 172 ok = false; 173 in = out = NULL; 174 175 in = fopen(src, "r"); 176 if (in == NULL) goto cleanup; 177 178 out = fopen(dst, "w+"); 179 if (out == NULL) goto cleanup; 180 181 while ((nread = fread(buf, 1, sizeof(buf), in)) > 0) { 182 fwrite(buf, 1, nread, out); 183 } 184 185 if (nread < 0) 186 goto cleanup; 187 188 ok = true; 189 190cleanup: 191 if (in) fclose(in); 192 if (out) fclose(out); 193 194 return ok; 195} 196 197bool 198dup_file(const char *src, const char *dst) 199{ 200 if (link(src, dst) == 0) 201 return true; 202 203 return copy_file(src, dst); 204} 205 206bool 207move_file(const char *src, const char *dst) 208{ 209 return rename(src, dst) == 0; 210} 211 212void 213tag_clear_tracks(struct tag *tag) 214{ 215 struct list_link *link; 216 struct track *track; 217 218 while (!list_empty(&tag->tracks)) { 219 link = list_pop_front(&tag->tracks); 220 track = LIST_UPCAST(link, struct track, link_tt); 221 track_rm(track, false); 222 } 223} 224 225void 226tag_load_tracks(struct tag *tag) 227{ 228 char linebuf[1024]; 229 char *index_path; 230 FILE *file; 231 232 index_path = aprintf("%s/index", tag->fpath); 233 file = fopen(index_path, "r"); 234 if (file == NULL) { 235 tag_reindex_tracks(tag); 236 return; 237 } 238 239 while (fgets(linebuf, sizeof(linebuf), file)) { 240 if (!*linebuf) continue; 241 if (linebuf[strlen(linebuf) - 1] == '\n') 242 linebuf[strlen(linebuf) - 1] = '\0'; 243 track_add(tag, linebuf); 244 } 245 246 tag->index_dirty = false; 247 248 fclose(file); 249 free(index_path); 250} 251 252void 253tag_save_tracks(struct tag *tag) 254{ 255 struct track *track; 256 struct list_link *link; 257 char *index_path; 258 FILE *file; 259 260 /* write playlist back to index file */ 261 262 index_path = aprintf("%s/index", tag->fpath); 263 file = fopen(index_path, "w+"); 264 if (!file) { 265 WARNX(SYSTEM, "Failed to write to index file: %s", 266 index_path); 267 free(index_path); 268 return; 269 } 270 271 for (LIST_ITER(&tag->tracks, link)) { 272 track = LIST_UPCAST(link, struct track, link_tt); 273 fprintf(file, "%s\n", track->name); 274 } 275 276 tag->index_dirty = false; 277 278 fclose(file); 279 free(index_path); 280} 281 282bool 283tag_reindex_tracks(struct tag *tag) 284{ 285 struct dirent *ent; 286 DIR *dir; 287 288 dir = opendir(tag->fpath); 289 if (!dir) return false; 290 291 tag_clear_tracks(tag); 292 293 while ((ent = readdir(dir))) { 294 if (!strcmp(ent->d_name, ".")) 295 continue; 296 if (!strcmp(ent->d_name, "..")) 297 continue; 298 299 /* skip files without extension */ 300 if (!strchr(ent->d_name + 1, '.')) 301 continue; 302 303 track_add(tag, ent->d_name); 304 } 305 306 tag->index_dirty = true; 307 308 closedir(dir); 309 310 return true; 311} 312 313struct track * 314tracks_vis_track(struct list_link *link) 315{ 316 if (tracks_vis == &player.playlist) { 317 return LIST_UPCAST(link, struct track, link_pl); 318 } else { 319 return LIST_UPCAST(link, struct track, link_tt); 320 } 321} 322 323void 324playlist_clear(void) 325{ 326 while (!list_empty(&player.playlist)) 327 list_pop_front(&player.playlist); 328} 329 330void 331playlist_update(void) 332{ 333 struct list_link *link, *link2; 334 struct track *track; 335 struct tag *tag; 336 337 if (!playlist_outdated) 338 return; 339 340 playlist_clear(); 341 342 for (LIST_ITER(&tags_sel, link)) { 343 tag = LIST_UPCAST(link, struct tag, link_sel); 344 for (LIST_ITER(&tag->tracks, link2)) { 345 track = LIST_UPCAST(link2, struct track, link_tt); 346 list_link_pop(&track->link_pl); 347 list_insert_back(&player.playlist, &track->link_pl); 348 } 349 } 350 351 playlist_outdated = false; 352} 353 354struct tag * 355tag_create(const char *fname) 356{ 357 struct tag *tag; 358 char *path; 359 360 path = aprintf("%s/%s", datadir, fname); 361 if (!make_dir(path)) { 362 free(path); 363 return NULL; 364 } 365 free(path); 366 367 tag = tag_add(fname); 368 if (!tag) { 369 rm_dir(path, false); 370 return NULL; 371 } 372 373 return tag; 374} 375 376struct tag * 377tag_add(const char *fname) 378{ 379 struct tag *tag; 380 381 tag = tag_alloc(datadir, fname); 382 if (!tag) return NULL; 383 list_insert_back(&tags, &tag->link); 384 385 return tag; 386} 387 388struct tag * 389tag_find(const char *name) 390{ 391 struct list_link *link; 392 struct tag *tag; 393 394 for (LIST_ITER(&tags, link)) { 395 tag = LIST_UPCAST(link, struct tag, link); 396 if (!strcmp(tag->name, name)) 397 return tag; 398 } 399 400 return NULL; 401} 402 403bool 404tag_rm(struct tag *tag, bool sync_fs) 405{ 406 struct list_link *link; 407 struct track *track; 408 409 /* remove contained tracks */ 410 while (!list_empty(&tag->tracks)) { 411 link = list_pop_front(&tag->tracks); 412 track = LIST_UPCAST(link, struct track, link_tt); 413 if (!track_rm(track, sync_fs)) 414 return false; 415 } 416 417 /* delete dir and remaining non-track files */ 418 if (sync_fs && !rm_dir(tag->fpath, true)) 419 return false; 420 421 /* remove from selected */ 422 list_link_pop(&tag->link_sel); 423 424 /* remove from tags list */ 425 list_link_pop(&tag->link); 426 427 tag_free(tag); 428 429 return true; 430} 431 432bool 433tag_rename(struct tag *tag, const char *name) 434{ 435 struct list_link *link; 436 struct track *track; 437 char *newpath; 438 439 newpath = aprintf("%s/%s", datadir, name); 440 441 if (!move_dir(tag->fpath, newpath)) { 442 free(newpath); 443 return false; 444 } 445 446 free(tag->fpath); 447 tag->fpath = newpath; 448 449 free(tag->name); 450 tag->name = astrdup(name); 451 452 for (LIST_ITER(&tag->tracks, link)) { 453 track = LIST_UPCAST(link, struct track, link_tt); 454 free(track->fpath); 455 track->fpath = aprintf("%s/%s", newpath, track->name); 456 } 457 458 return true; 459} 460 461struct track * 462track_add(struct tag *tag, const char *fname) 463{ 464 struct track *track; 465 466 track = track_alloc(tag->fpath, fname); 467 track->tag = tag; 468 469 /* insert track into sorted tracks list */ 470 list_insert_back(&tracks, &track->link); 471 472 /* add to tag's tracks list */ 473 list_insert_back(&tag->tracks, &track->link_tt); 474 475 /* if track's tag is selected, update playlist */ 476 if (list_link_inuse(&tag->link_sel)) 477 playlist_outdated = true; 478 479 tag->index_dirty = true; 480 481 return track; 482} 483 484bool 485track_rm(struct track *track, bool sync_fs) 486{ 487 if (sync_fs && !rm_file(track->fpath)) 488 return false; 489 490 track->tag->index_dirty = true; 491 492 /* remove from tracks list */ 493 list_link_pop(&track->link); 494 495 /* remove from tag's track list */ 496 list_link_pop(&track->link_tt); 497 498 /* remove from playlist */ 499 list_link_pop(&track->link_pl); 500 501 /* remove from player queue */ 502 list_link_pop(&track->link_pq); 503 504 /* remove from player history */ 505 list_link_pop(&track->link_hs); 506 507 /* remove the reference as last used track */ 508 if (player.track == track) 509 player.track = NULL; 510 511 track_free(track); 512 513 return true; 514} 515 516bool 517track_rename(struct track *track, const char *name) 518{ 519 char *newpath; 520 521 newpath = aprintf("%s/%s", track->tag->fpath, name); 522 523 if (path_exists(newpath)) { 524 free(newpath); 525 return false; 526 } 527 528 if (!move_file(track->fpath, newpath)) { 529 free(newpath); 530 return false; 531 } 532 533 free(track->fpath); 534 track->fpath = newpath; 535 536 free(track->name); 537 track->name = astrdup(name); 538 539 track->tag->index_dirty = true; 540 541 return true; 542} 543 544bool 545track_move(struct track *track, struct tag *tag) 546{ 547 struct track *new; 548 char *newpath; 549 550 errno = 0; 551 552 newpath = aprintf("%s/%s", tag->fpath, track->name); 553 if (path_exists(newpath)) { 554 free(newpath); 555 errno = EEXIST; 556 return false; 557 } 558 559 if (!dup_file(track->fpath, newpath)) { 560 free(newpath); 561 errno = EACCES; 562 return false; 563 } 564 565 free(newpath); 566 567 new = track_add(tag, track->name); 568 if (!new) return false; 569 570 if (player.track == track) 571 player.track = new; 572 573 if (!track_rm(track, true)) { 574 track_rm(new, true); 575 errno = EACCES; 576 return false; 577 } 578 579 return true; 580} 581 582bool 583acquire_lock(const char *datadir) 584{ 585 char *lockpath, *procpath; 586 char linebuf[512]; 587 FILE *file; 588 int pid; 589 590 lockpath = aprintf("%s/.lock", datadir); 591 if (path_exists(lockpath)) { 592 file = fopen(lockpath, "r"); 593 if (file == NULL) { 594 free(lockpath); 595 return false; 596 } 597 598 fgets(linebuf, sizeof(linebuf), file); 599 pid = strtol(linebuf, NULL, 10); 600 procpath = aprintf("/proc/%i", pid); 601 if (path_exists(procpath)) { 602 free(procpath); 603 free(lockpath); 604 return false; 605 } 606 free(procpath); 607 608 fclose(file); 609 } 610 611 file = fopen(lockpath, "w+"); 612 if (file == NULL) { 613 free(lockpath); 614 return false; 615 } 616 snprintf(linebuf, sizeof(linebuf), "%i", getpid()); 617 fputs(linebuf, file); 618 fclose(file); 619 620 free(lockpath); 621 622 return true; 623} 624 625bool 626release_lock(const char *datadir) 627{ 628 char *lockpath; 629 bool status; 630 631 lockpath = aprintf("%s/.lock", datadir); 632 633 status = rm_file(lockpath); 634 635 free(lockpath); 636 637 return status; 638} 639 640void 641data_load(void) 642{ 643 struct dirent *ent; 644 struct tag *tag; 645 struct stat st; 646 char *path; 647 DIR *dir; 648 649 list_init(&tracks); 650 list_init(&tags); 651 list_init(&tags_sel); 652 653 datadir = getenv("TMUS_DATA"); 654 if (!datadir) ERRORX(USER, "TMUS_DATA not set"); 655 656 if (!acquire_lock(datadir)) 657 ERRORX(USER, "Failed to lock datadir"); 658 659 dir = opendir(datadir); 660 if (!dir) ERROR(SYSTEM, "opendir %s", datadir); 661 662 trash_tag = NULL; 663 while ((ent = readdir(dir))) { 664 if (!strcmp(ent->d_name, ".")) 665 continue; 666 if (!strcmp(ent->d_name, "..")) 667 continue; 668 669 path = aprintf("%s/%s", datadir, ent->d_name); 670 if (!stat(path, &st) && S_ISDIR(st.st_mode)) { 671 tag = tag_add(ent->d_name); 672 if (!strcmp(tag->name, "trash")) 673 trash_tag = tag; 674 tag_load_tracks(tag); 675 } 676 free(path); 677 } 678 679 list_insertion_sort(&tags, false, tag_name_cmp, 680 LIST_OFFSET(struct tag, link), NULL); 681 682 playlist_outdated = true; 683 684 closedir(dir); 685} 686 687void 688data_save(void) 689{ 690 struct list_link *link; 691 struct tag *tag; 692 693 for (LIST_ITER(&tags, link)) { 694 tag = LIST_UPCAST(link, struct tag, link); 695 if (tag->index_dirty) 696 tag_save_tracks(tag); 697 } 698 699 release_lock(datadir); 700} 701 702void 703data_free(void) 704{ 705 struct list_link *link; 706 struct tag *tag; 707 708 list_clear(&player.playlist); 709 list_clear(&player.queue); 710 list_clear(&player.history); 711 712 while (!list_empty(&tags)) { 713 link = list_pop_front(&tags); 714 tag = LIST_UPCAST(link, struct tag, link); 715 tag_rm(tag, false); 716 } 717} 718