A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 1405 lines 46 kB view raw
1/*************************************************************************** 2 * __________ __ ___. 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 * \/ \/ \/ \/ \/ 8 * 9 * Copyright (C)2003 by Benjamin Metzler 10 * 11 * This program is free software; you can redistribute it and/or 12 * modify it under the terms of the GNU General Public License 13 * as published by the Free Software Foundation; either version 2 14 * of the License, or (at your option) any later version. 15 * 16 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 17 * KIND, either express or implied. 18 * 19 ****************************************************************************/ 20 21#include <stdio.h> 22#include <stdlib.h> 23#include <string.h> 24#include <stdbool.h> 25 26#include "config.h" 27#include "action.h" 28#include "audio.h" 29#include "playlist.h" 30#include "settings.h" 31#include "tree.h" 32#include "bookmark.h" 33#include "system.h" 34#include "icons.h" 35#include "menu.h" 36#include "lang.h" 37#include "talk.h" 38#include "misc.h" 39#include "splash.h" 40#include "yesno.h" 41#include "list.h" 42#include "plugin.h" 43#include "file.h" 44#include "pathfuncs.h" 45#include "playlist_menu.h" 46 47/*#define LOGF_ENABLE*/ 48#include "logf.h" 49 50#define MAX_BOOKMARKS 10 51#define MAX_BOOKMARK_SIZE 350 52#define RECENT_BOOKMARK_FILE ROCKBOX_DIR "/most-recent.bmark" 53#define BOOKMARK_IGNORE "/bookmark.ignore" 54#define BOOKMARK_UNIGNORE "/bookmark.unignore" 55 56/* flags for write_bookmark() */ 57#define BMARK_WRITE 0x0 58#define BMARK_ASK_USER 0x1 59#define BMARK_CREATE_FILE 0x2 60#define BMARK_CHECK_IGNORE 0x4 61 62/* Used to buffer bookmarks while displaying the bookmark list. */ 63struct bookmark_list 64{ 65 const char* filename; 66 size_t buffer_size; 67 int start; 68 int count; 69 int total_count; 70 bool show_dont_resume; 71 bool reload; 72 bool show_playlist_name; 73 char* items[]; 74}; 75 76/* flags for optional bookmark tokens */ 77#define BM_PITCH 0x01 78#define BM_SPEED 0x02 79 80/* bookmark values */ 81struct resume_info{ 82 const struct mp3entry *id3; 83 int resume_index; 84 unsigned long resume_offset; 85 int resume_seed; 86 long resume_elapsed; 87 int repeat_mode; 88 bool shuffle; 89 /* optional values */ 90 int pitch; 91 int speed; 92}; 93 94/* Temp buffer used for reading, create_bookmark and filename creation */ 95#define TEMP_BUF_SIZE (MAX(MAX_BOOKMARK_SIZE, MAX_PATH + 1)) 96static char global_temp_buffer[TEMP_BUF_SIZE]; 97 98static inline void get_hash(const char *key, uint32_t *hash, int len) 99{ 100 *hash = crc_32(key, len, *hash); /* this is probably sufficient */ 101} 102 103static const char* skip_tokens(const char* s, int ntokens) 104{ 105 for (int i = 0; i < ntokens; i++) 106 { 107 while (*s && *s != ';') 108 { 109 s++; 110 } 111 112 if (*s) 113 { 114 s++; 115 } 116 } 117 return s; 118} 119 120static int int_token(const char **s) 121{ 122 int ret = atoi(*s); 123 *s = skip_tokens(*s, 1); 124 return ret; 125} 126 127static long long_token(const char **s) 128{ 129 /* Should be atol, but we don't have it. */ 130 return int_token(s); 131} 132 133/*-------------------------------------------------------------------------*/ 134/* Get the name of the playlist and the name of the track from a bookmark. */ 135/* Returns true iff both were extracted. */ 136/*-------------------------------------------------------------------------*/ 137static bool bookmark_get_playlist_and_track_hash(const char *bookmark, 138 uint32_t *pl_hash, 139 uint32_t *track_hash) 140{ 141 *pl_hash = 0; 142 *track_hash = 0; 143 int pl_len; 144 const char *pl_start, *pl_end, *track; 145 146 logf("%s", __func__); 147 148 pl_start = strchr(bookmark,'/'); 149 if (!(pl_start)) 150 return false; 151 152 pl_end = skip_tokens(pl_start, 1) - 1; 153 pl_len = pl_end - pl_start; 154 155 track = pl_end + 1; 156 get_hash(pl_start, pl_hash, pl_len); 157 158 if (global_settings.usemrb == BOOKMARK_ONE_PER_TRACK) 159 { 160 get_hash(track, track_hash, strlen(track)); 161 } 162 163 164 return true; 165} 166 167/* ----------------------------------------------------------------------- */ 168/* This function takes a bookmark and parses it. This function also */ 169/* validates the bookmark. Valid filenamebuf indicates whether */ 170/* the filename tokens are to be extracted. */ 171/* Returns true on successful bookmark parse. */ 172/* ----------------------------------------------------------------------- */ 173static bool parse_bookmark(char *filenamebuf, 174 size_t filenamebufsz, 175 const char *bookmark, 176 struct resume_info *resume_info, 177 const bool strip_dir) 178{ 179 const char* s = bookmark; 180 const char* end; 181 182#define GET_INT_TOKEN(var) var = int_token(&s) 183#define GET_LONG_TOKEN(var) var = long_token(&s) 184#define GET_BOOL_TOKEN(var) var = (int_token(&s) != 0) 185 186 /* if new format bookmark, extract the optional content flags, 187 otherwise treat as an original format bookmark */ 188 int opt_flags = 0; 189 int opt_pitch = 0; 190 int opt_speed = 0; 191 int old_format = ((strchr(s, '>') == s) ? 0 : 1); 192 if (old_format == 0) /* this is a new format bookmark */ 193 { 194 s++; 195 GET_INT_TOKEN(opt_flags); 196 opt_pitch = (opt_flags & BM_PITCH) ? 1:0; 197 opt_speed = (opt_flags & BM_SPEED) ? 1:0; 198 } 199 200 /* extract all original bookmark tokens */ 201 if (resume_info) 202 { 203 GET_INT_TOKEN(resume_info->resume_index); 204 GET_LONG_TOKEN(resume_info->resume_offset); 205 GET_INT_TOKEN(resume_info->resume_seed); 206 207 s = skip_tokens(s, old_format); /* skip deprecated token */ 208 209 GET_LONG_TOKEN(resume_info->resume_elapsed); 210 GET_INT_TOKEN(resume_info->repeat_mode); 211 GET_BOOL_TOKEN(resume_info->shuffle); 212 213 /* extract all optional bookmark tokens */ 214 if (opt_pitch != 0) 215 GET_INT_TOKEN(resume_info->pitch); 216 if (opt_speed != 0) 217 GET_INT_TOKEN(resume_info->speed); 218 } 219 else /* no resume info we just want the file name strings */ 220 { 221 #define DEFAULT_BM_TOKENS 6 222 int skipct = DEFAULT_BM_TOKENS + old_format + opt_pitch + opt_speed; 223 s = skip_tokens(s, skipct); 224 #undef DEFAULT_BM_TOKENS 225 } 226 227 if (*s == 0) 228 { 229 return false; 230 } 231 232 end = strchr(s, ';'); 233 234 /* extract file names */ 235 if(filenamebuf) 236 { 237 size_t len = (end == NULL) ? strlen(s) : (size_t) (end - s); 238 len = MIN(TEMP_BUF_SIZE - 1, len); 239 strmemccpy(global_temp_buffer, s, len + 1); 240 241 if (end != NULL) 242 { 243 end++; 244 if (strip_dir) 245 { 246 s = strrchr(end, '/'); 247 if (s) 248 { 249 end = s; 250 end++; 251 } 252 } 253 strmemccpy(filenamebuf, end, filenamebufsz); 254 } 255 } 256 257 return true; 258} 259 260/* ------------------------------------------------------------------------- */ 261/* This function takes a filename and appends .tmp. This function also opens */ 262/* the resulting file based on oflags, filename will be in buf on return */ 263/* Returns file descriptor */ 264/* --------------------------------------------------------------------------*/ 265static int open_temp_bookmark(char *buf, 266 size_t bufsz, 267 int oflags, 268 const char* filename) 269{ 270 if(filename[0] == '/') 271 filename++; 272 /* Opening up a temp bookmark file */ 273 int fd = open_pathfmt(buf, bufsz, oflags, "/%s.tmp", filename); 274#ifdef LOGF_ENABLE 275 if (oflags & O_PATH) 276 logf("tempfile path %s", buf); 277 else 278 logf("opening tempfile %s", buf); 279#endif 280 return fd; 281} 282 283/* ----------------------------------------------------------------------- */ 284/* This function adds a bookmark to a file. */ 285/* Returns true on successful bookmark add. */ 286/* ------------------------------------------------------------------------*/ 287static bool add_bookmark(const char* bookmark_file_name, 288 const char* bookmark, 289 bool most_recent) 290{ 291 char fnamebuf[MAX_PATH]; 292 int temp_bookmark_file = 0; 293 int bookmark_file = 0; 294 int bookmark_count = 0; 295 bool comp_playlist = false; 296 bool comp_track = false; 297 bool equal; 298 uint32_t pl_hash, pl_track_hash; 299 uint32_t bm_pl_hash, bm_pl_track_hash; 300 301 if (!bookmark) 302 return false; /* no bookmark */ 303 304 /* Opening up a temp bookmark file */ 305 temp_bookmark_file = open_temp_bookmark(fnamebuf, 306 sizeof(fnamebuf), 307 O_WRONLY | O_CREAT | O_TRUNC, 308 bookmark_file_name); 309 310 if (temp_bookmark_file < 0) 311 return false; /* can't open the temp file */ 312 313 if (most_recent && ((global_settings.usemrb == BOOKMARK_ONE_PER_PLAYLIST) 314 || (global_settings.usemrb == BOOKMARK_ONE_PER_TRACK))) 315 { 316 317 if (bookmark_get_playlist_and_track_hash(bookmark, &pl_hash, &pl_track_hash)) 318 { 319 comp_playlist = true; 320 comp_track = (global_settings.usemrb == BOOKMARK_ONE_PER_TRACK); 321 } 322 } 323 324 logf("adding bookmark to %s [%s]", fnamebuf, bookmark); 325 /* Writing the new bookmark to the begining of the temp file */ 326 write(temp_bookmark_file, bookmark, strlen(bookmark)); 327 write(temp_bookmark_file, "\n", 1); 328 bookmark_count++; 329 330 /* WARNING underlying buffer to *bookmrk gets overwritten after this point! */ 331 332 /* Reading in the previous bookmarks and writing them to the temp file */ 333 logf("opening old bookmark %s", bookmark_file_name); 334 bookmark_file = open(bookmark_file_name, O_RDONLY); 335 if (bookmark_file >= 0) 336 { 337 while (read_line(bookmark_file, global_temp_buffer, 338 sizeof(global_temp_buffer)) > 0) 339 { 340 /* The MRB has a max of MAX_BOOKMARKS in it */ 341 /* This keeps it from getting too large */ 342 if (most_recent && (bookmark_count >= MAX_BOOKMARKS)) 343 break; 344 345 if (!parse_bookmark(NULL, 0, global_temp_buffer, NULL, false)) 346 break; 347 348 equal = false; 349 if (comp_playlist) 350 { 351 if (bookmark_get_playlist_and_track_hash(global_temp_buffer, 352 &bm_pl_hash, &bm_pl_track_hash)) 353 { 354 equal = (pl_hash == bm_pl_hash); 355 if (equal && comp_track) 356 { 357 equal = (pl_track_hash == bm_pl_track_hash); 358 } 359 } 360 } 361 if (!equal) 362 { 363 bookmark_count++; 364 /*logf("copying old bookmark [%s]", global_temp_buffer);*/ 365 write(temp_bookmark_file, global_temp_buffer, 366 strlen(global_temp_buffer)); 367 write(temp_bookmark_file, "\n", 1); 368 } 369 } 370 close(bookmark_file); 371 } 372 close(temp_bookmark_file); 373 374 remove(bookmark_file_name); 375 rename(fnamebuf, bookmark_file_name); 376 377 return true; 378} 379 380/* ----------------------------------------------------------------------- */ 381/* This function is used by multiple functions and is used to generate a */ 382/* bookmark named based off of the input. */ 383/* Changing this function could result in how the bookmarks are stored. */ 384/* it would be here that the centralized/decentralized bookmark code */ 385/* could be placed. */ 386/* Returns true if the file name is generated, false if it was too long */ 387/* ----------------------------------------------------------------------- */ 388static bool generate_bookmark_file_name(char *filenamebuf, 389 size_t filenamebufsz, 390 const char *bmarknamein, 391 size_t bmarknamelen) 392{ 393 /* if this is a root dir MP3, rename the bookmark file root_dir.bmark */ 394 /* otherwise, name it based on the bmarknamein variable */ 395 if (!strncmp("/", bmarknamein, bmarknamelen)) 396 strmemccpy(filenamebuf, "/root_dir.bmark", filenamebufsz); 397 else 398 { 399 size_t buflen, len; 400 /* strmemccpy considers the NULL so bmarknamelen is one off */ 401 buflen = MIN(filenamebufsz -1 , bmarknamelen); 402 if (buflen >= filenamebufsz) 403 return false; 404 405 strmemccpy(filenamebuf, bmarknamein, buflen + 1); 406 407 len = strlen(filenamebuf); 408 409#ifdef HAVE_MULTIVOLUME 410 /* The "root" of an extra volume need special handling too. */ 411 const char *filename; 412 path_strip_volume(filenamebuf, &filename, true); 413 bool volume_root = *filename == '\0'; 414#endif 415 if(filenamebuf[len-1] == '/') { 416 filenamebuf[len-1] = '\0'; 417 } 418 419 const char *name = ".bmark"; 420#ifdef HAVE_MULTIVOLUME 421 if (volume_root) 422 name = "/volume_dir.bmark"; 423#endif 424 len = strlcat(filenamebuf, name, filenamebufsz); 425 426 if(len >= filenamebufsz) 427 return false; 428 } 429 logf ("generated name '%s' from '%.*s'", 430 filenamebuf, (int)bmarknamelen, bmarknamein); 431 return true; 432} 433 434/* GCC 7 and up complain about the snprintf in create_bookmark() when 435 compiled with -D_FORTIFY_SOURCE or -Wformat-truncation 436 This is a false positive, so disable it here only */ 437/* SHOULD NO LONGER BE NEEDED --Bilgus 11-2022 */ 438#if 0 /* __GNUC__ >= 7 */ 439#pragma GCC diagnostic push 440#pragma GCC diagnostic ignored "-Wformat-truncation" 441#endif 442/* ----------------------------------------------------------------------- */ 443/* This function takes the system resume data and formats it into a valid */ 444/* bookmark. */ 445/* playlist name and name len are passed back through the name/namelen */ 446/* Return is not NULL on successful bookmark format. */ 447/* ----------------------------------------------------------------------- */ 448static char* create_bookmark(char **name, 449 size_t *namelen, 450 struct resume_info *resume_info) 451{ 452 const char *file; 453 char *buf = global_temp_buffer; 454 size_t bufsz = sizeof(global_temp_buffer); 455 456 if(!resume_info->id3) 457 return NULL; 458 459 size_t bmarksz= snprintf(buf, bufsz, 460 /* new optional bookmark token descriptors should 461 be inserted just after ';"' in this line... */ 462#if defined(HAVE_PITCHCONTROL) 463 ">%d;%d;%ld;%d;%ld;%d;%d;%ld;%ld;", 464#else 465 ">%d;%d;%ld;%d;%ld;%d;%d;", 466#endif 467 /* ... their flags should go here ... */ 468#if defined(HAVE_PITCHCONTROL) 469 BM_PITCH | BM_SPEED, 470#else 471 0, 472#endif 473 resume_info->resume_index, 474 resume_info->id3->offset, 475 resume_info->resume_seed, 476 resume_info->id3->elapsed, 477 resume_info->repeat_mode, 478 resume_info->shuffle, 479 /* ...and their values should go here */ 480#if defined(HAVE_PITCHCONTROL) 481 (long)resume_info->pitch, 482 (long)resume_info->speed 483#endif 484 ); /*sprintf*/ 485/* mandatory tokens */ 486 if (bmarksz >= bufsz) /* include NULL*/ 487 return NULL; 488 buf += bmarksz; 489 bufsz -= bmarksz; 490 491 /* create the bookmark */ 492 playlist_get_name(NULL, buf, bufsz); 493 bmarksz = strlen(buf); 494 495 if (bmarksz == 0 || (bmarksz + 1) >= bufsz) /* include the separator & NULL*/ 496 return NULL; 497 498 *name = buf; /* return the playlist name through the *pointer */ 499 *namelen = bmarksz; /* return the name length through the pointer */ 500 501 /* Get the currently playing file minus the path */ 502 /* This is used when displaying the available bookmarks */ 503 file = strrchr(resume_info->id3->path,'/'); 504 if(NULL == file) 505 return NULL; 506 507 if (buf[bmarksz - 1] != '/') 508 file = resume_info->id3->path; 509 else file++; 510 511 buf += bmarksz; 512 bufsz -= (bmarksz + 1); 513 buf[0] = ';'; 514 buf[1] = '\0'; 515 516 strlcat(buf, file, bufsz); 517 518 logf("%s [%s]", __func__, global_temp_buffer); 519 /* checking to see if the bookmark is valid */ 520 if (parse_bookmark(NULL, 0, global_temp_buffer, NULL, false)) 521 return global_temp_buffer; 522 else 523 return NULL; 524} 525#if 0/* __GNUC__ >= 7*/ 526#pragma GCC diagnostic pop /* -Wformat-truncation */ 527#endif 528 529/* ----------------------------------------------------------------------- */ 530/* This function gets some basic resume information for the current song */ 531/* from rockbox, */ 532/* ----------------------------------------------------------------------- */ 533static void get_track_resume_info(struct resume_info *resume_info) 534{ 535 if (global_settings.playlist_shuffle) 536 playlist_get_resume_info(&(resume_info->resume_index)); 537 else 538 resume_info->resume_index = playlist_get_display_index() - 1; 539 540 resume_info->resume_seed = playlist_get_seed(NULL); 541 resume_info->id3 = audio_current_track(); 542 resume_info->repeat_mode = global_settings.repeat_mode; 543 resume_info->shuffle = global_settings.playlist_shuffle; 544#if defined(HAVE_PITCHCONTROL) 545 resume_info->pitch = sound_get_pitch(); 546 resume_info->speed = dsp_get_timestretch(); 547#endif 548} 549 550/* ----------------------------------------------------------------------- */ 551/* This function checks for bookmark ignore and unignore files to allow */ 552/* directories to be ignored or included in bookmarks */ 553/* ----------------------------------------------------------------------- */ 554static bool bookmark_has_ignore(struct resume_info *resume_info) 555{ 556 if (!resume_info->id3) 557 return false; 558 559 char *buf = global_temp_buffer; 560 size_t bufsz = sizeof(global_temp_buffer); 561 562 strmemccpy(buf, resume_info->id3->path, bufsz); 563 564 char *slash; 565 while ((slash = strrchr(buf, '/'))) 566 { 567 size_t rem = bufsz - (slash - buf); 568 if (strmemccpy(slash, BOOKMARK_UNIGNORE, rem) != NULL && file_exists(buf)) 569 { 570 /* unignore exists we want bookmarks */ 571 logf("unignore bookmark found %s\n", buf); 572 return false; 573 } 574 if (strmemccpy(slash, BOOKMARK_IGNORE, rem) != NULL && file_exists(buf)) 575 { 576 /* ignore exists we do not want bookmarks */ 577 logf("ignore bookmark found %s\n", buf); 578 return true; 579 } 580 *slash = '\0'; 581 } 582 return false; 583} 584 585/* ----------------------------------------------------------------------- */ 586/* This function takes the current current resume information and writes */ 587/* that to the beginning of the bookmark file. */ 588/* This file will contain N number of bookmarks in the following format: */ 589/* resume_index*resume_offset*resume_seed*resume_first_index* */ 590/* resume_file*milliseconds*MP3 Title* */ 591/* Returns true on successful bookmark write. */ 592/* Returns false if any part of the bookmarking process fails. It is */ 593/* possible that a bookmark is successfully added to the most recent */ 594/* bookmark list but fails to be added to the bookmark file or vice versa. */ 595/* ------------------------------------------------------------------------*/ 596static bool write_bookmark(unsigned int flags) 597{ 598 logf("%s flags: %d", __func__, flags); 599 char bm_filename[MAX_PATH]; 600 bool ret=true; 601 602 bool create_bookmark_file = flags & BMARK_CREATE_FILE; 603 bool check_ignore = flags & BMARK_CHECK_IGNORE; 604 bool ask_user = flags & BMARK_ASK_USER; 605 bool usemrb = global_settings.usemrb; 606 607 char *name = NULL; 608 size_t namelen = 0; 609 char* bm; 610 struct resume_info resume_info; 611 612 if (bookmark_is_bookmarkable_state()) 613 { 614 get_track_resume_info(&resume_info); 615 616 if (check_ignore 617 && (create_bookmark_file || ask_user || usemrb)) 618 { 619 if (bookmark_has_ignore(&resume_info)) 620 return false; 621 } 622 623 if (ask_user) 624 { 625 if (yesno_pop(ID2P(LANG_AUTO_BOOKMARK_QUERY))) 626 { 627 if (global_settings.autocreatebookmark != BOOKMARK_RECENT_ONLY_ASK) 628 create_bookmark_file = true; 629 } 630 else 631 return false; 632 } 633 634 /* writing the most recent bookmark */ 635 if (usemrb) 636 { 637 /* since we use the same buffer bookmark needs created each time */ 638 bm = create_bookmark(&name, &namelen, &resume_info); 639 ret = add_bookmark(RECENT_BOOKMARK_FILE, bm, true); 640 } 641 642 /* writing the directory bookmark */ 643 if (create_bookmark_file) 644 { 645 bm = create_bookmark(&name, &namelen, &resume_info); 646 if (generate_bookmark_file_name(bm_filename, 647 sizeof(bm_filename), name, namelen)) 648 { 649 ret &= add_bookmark(bm_filename, bm, false); 650 } 651 else 652 { 653 ret = false; /* generating bookmark file failed */ 654 } 655 } 656 } 657 else 658 ret = false; 659 660 splash(HZ, ret ? ID2P(LANG_BOOKMARK_CREATE_SUCCESS) 661 : ID2P(LANG_BOOKMARK_CREATE_FAILURE)); 662 663 return ret; 664} 665 666static int get_bookmark_count(const char* bookmark_file_name) 667{ 668 int read_count = 0; 669 int file = open(bookmark_file_name, O_RDONLY); 670 671 if(file < 0) 672 return -1; 673 674 while(read_line(file, global_temp_buffer, sizeof(global_temp_buffer)) > 0) 675 { 676 read_count++; 677 } 678 679 close(file); 680 return read_count; 681} 682 683static int buffer_bookmarks(struct bookmark_list* bookmarks, int first_line) 684{ 685 char* dest = ((char*) bookmarks) + bookmarks->buffer_size - 1; 686 int read_count = 0; 687 int file = open(bookmarks->filename, O_RDONLY); 688 689 if (file < 0) 690 { 691 return -1; 692 } 693 694 if ((first_line != 0) && ((size_t) filesize(file) < bookmarks->buffer_size 695 - sizeof(*bookmarks) - (sizeof(char*) * bookmarks->total_count))) 696 { 697 /* Entire file fits in buffer */ 698 first_line = 0; 699 } 700 701 bookmarks->start = first_line; 702 bookmarks->count = 0; 703 bookmarks->reload = false; 704 705 while(read_line(file, global_temp_buffer, sizeof(global_temp_buffer)) > 0) 706 { 707 read_count++; 708 709 if (read_count >= first_line) 710 { 711 dest -= strlen(global_temp_buffer) + 1; 712 713 if (dest < ((char*) bookmarks) + sizeof(*bookmarks) 714 + (sizeof(char*) * (bookmarks->count + 1))) 715 { 716 break; 717 } 718 719 strcpy(dest, global_temp_buffer); 720 bookmarks->items[bookmarks->count] = dest; 721 bookmarks->count++; 722 } 723 } 724 725 close(file); 726 return bookmarks->start + bookmarks->count; 727} 728 729static const char* get_bookmark_info(int list_index, 730 void* data, 731 char *buffer, 732 size_t buffer_len) 733{ 734 char fnamebuf[MAX_PATH]; 735 struct resume_info resume_info; 736 struct bookmark_list* bookmarks = (struct bookmark_list*) data; 737 int index = list_index / 2; 738 739 if (bookmarks->show_dont_resume) 740 { 741 if (index == 0) 742 { 743 return list_index % 2 == 0 744 ? (char*) str(LANG_BOOKMARK_DONT_RESUME) : " "; 745 } 746 747 index--; 748 } 749 750 if (bookmarks->reload || (index >= bookmarks->start + bookmarks->count) 751 || (index < bookmarks->start)) 752 { 753 int read_index = index; 754 755 /* Using count as a guide on how far to move could possibly fail 756 * sometimes. Use byte count if that is a problem? 757 */ 758 759 if (read_index != 0) 760 { 761 /* Move count * 3 / 4 items in the direction the user is moving, 762 * but don't go too close to the end. 763 */ 764 int offset = bookmarks->count; 765 int max = bookmarks->total_count - (bookmarks->count / 2); 766 767 if (read_index < bookmarks->start) 768 { 769 offset *= 3; 770 } 771 772 read_index = index - offset / 4; 773 774 if (read_index > max) 775 { 776 read_index = max; 777 } 778 779 if (read_index < 0) 780 { 781 read_index = 0; 782 } 783 } 784 785 if (buffer_bookmarks(bookmarks, read_index) <= index) 786 { 787 return ""; 788 } 789 } 790 791 if (!parse_bookmark(fnamebuf, sizeof(fnamebuf), 792 bookmarks->items[index - bookmarks->start], &resume_info, true)) 793 { 794 return list_index % 2 == 0 ? (char*) str(LANG_BOOKMARK_INVALID) : " "; 795 } 796 797 if (list_index % 2 == 0) 798 { 799 char *name; 800 char *format; 801 int len = strlen(global_temp_buffer); 802 803 if (bookmarks->show_playlist_name && len > 0) 804 { 805 name = global_temp_buffer; 806 len--; 807 808 if (name[len] != '/') 809 { 810 strrsplt(name, '.'); 811 } 812 else if (len > 1) 813 { 814 name[len] = '\0'; 815 } 816 817 if (len > 1) 818 { 819 name = strrsplt(name, '/'); 820 } 821 822 format = "%s : %s"; 823 } 824 else 825 { 826 name = fnamebuf; 827 format = "%s"; 828 } 829 830 strrsplt(fnamebuf, '.'); 831 snprintf(buffer, buffer_len, format, name, fnamebuf); 832 return buffer; 833 } 834 else 835 { 836 char time_buf[32]; 837 838 format_time(time_buf, sizeof(time_buf), resume_info.resume_elapsed); 839 snprintf(buffer, buffer_len, "%s, %d%s", time_buf, 840 resume_info.resume_index + 1, 841 resume_info.shuffle ? (char*) str(LANG_BOOKMARK_SHUFFLE) : ""); 842 return buffer; 843 } 844} 845 846/* ----------------------------------------------------------------------- */ 847/* This function parses a bookmark, says the voice UI part of it. */ 848/* ------------------------------------------------------------------------*/ 849static void say_bookmark(const char* bookmark, 850 int bookmark_id, 851 bool show_playlist_name) 852{ 853 char fnamebuf[MAX_PATH]; 854 struct resume_info resume_info; 855 if (!parse_bookmark(fnamebuf, sizeof(fnamebuf), bookmark, &resume_info, false)) 856 { 857 talk_id(LANG_BOOKMARK_INVALID, false); 858 return; 859 } 860 861 talk_number(bookmark_id + 1, false); 862 863 bool is_dir = (global_temp_buffer[0] 864 && global_temp_buffer[strlen(global_temp_buffer)-1] == '/'); 865 866 /* HWCODEC cannot enqueue voice file entries and .talk thumbnails 867 together, because there is no guarantee that the same mp3 868 parameters are used. */ 869 if(show_playlist_name) 870 { /* It's useful to know which playlist this is */ 871 if(is_dir) 872 talk_dir_or_spell(global_temp_buffer, 873 TALK_IDARRAY(VOICE_DIR), true); 874 else talk_file_or_spell(NULL, global_temp_buffer, 875 TALK_IDARRAY(LANG_PLAYLIST), true); 876 } 877 878 if(resume_info.shuffle) 879 talk_id(LANG_SHUFFLE, true); 880 881 talk_id(VOICE_BOOKMARK_SELECT_INDEX_TEXT, true); 882 talk_number(resume_info.resume_index + 1, true); 883 talk_id(LANG_TIME, true); 884 talk_value(resume_info.resume_elapsed / 1000, UNIT_TIME, true); 885 886 /* Track filename */ 887 if(!is_dir) 888 global_temp_buffer[0] = 0; 889 talk_file_or_spell(global_temp_buffer, fnamebuf, 890 TALK_IDARRAY(VOICE_FILE), true); 891} 892 893static int bookmark_list_voice_cb(int list_index, void* data) 894{ 895 struct bookmark_list* bookmarks = (struct bookmark_list*) data; 896 int index = list_index / 2; 897 898 if (bookmarks->show_dont_resume) 899 { 900 if (index == 0) 901 return talk_id(LANG_BOOKMARK_DONT_RESUME, false); 902 index--; 903 } 904 say_bookmark(bookmarks->items[index - bookmarks->start], index, 905 bookmarks->show_playlist_name); 906 return 0; 907} 908 909/* ----------------------------------------------------------------------- */ 910/* This function takes a location in a bookmark file and deletes that */ 911/* bookmark. */ 912/* Returns true on successful bookmark deletion. */ 913/* ------------------------------------------------------------------------*/ 914static bool delete_bookmark(const char* bookmark_file_name, int bookmark_id) 915{ 916 int temp_bookmark_file = 0; 917 int bookmark_file = 0; 918 int bookmark_count = 0; 919 920 /* Opening up a temp bookmark file */ 921 temp_bookmark_file = open_temp_bookmark(global_temp_buffer, 922 sizeof(global_temp_buffer), 923 O_WRONLY | O_CREAT | O_TRUNC, 924 bookmark_file_name); 925 926 if (temp_bookmark_file < 0) 927 return false; /* can't open the temp file */ 928 929 /* Reading in the previous bookmarks and writing them to the temp file */ 930 bookmark_file = open(bookmark_file_name, O_RDONLY); 931 if (bookmark_file >= 0) 932 { 933 while (read_line(bookmark_file, global_temp_buffer, 934 sizeof(global_temp_buffer)) > 0) 935 { 936 if (bookmark_id != bookmark_count) 937 { 938 write(temp_bookmark_file, global_temp_buffer, 939 strlen(global_temp_buffer)); 940 write(temp_bookmark_file, "\n", 1); 941 } 942 bookmark_count++; 943 } 944 close(bookmark_file); 945 } 946 close(temp_bookmark_file); 947 948 /* only retrieve the path*/ 949 close(open_temp_bookmark(global_temp_buffer, 950 sizeof(global_temp_buffer), 951 O_PATH, 952 bookmark_file_name)); 953 954 remove(bookmark_file_name); 955 rename(global_temp_buffer, bookmark_file_name); 956 957 return true; 958} 959 960/* ----------------------------------------------------------------------- */ 961/* This displays the bookmarks in a file and allows the user to */ 962/* select one to play. */ 963/* *selected_bookmark contains a non NULL value on successful bookmark */ 964/* selection. */ 965/* Returns BOOKMARK_SUCCESS on successful bookmark selection, BOOKMARK_FAIL*/ 966/* if no selection was made and BOOKMARK_USB_CONNECTED if the selection */ 967/* menu is forced to exit due to a USB connection. */ 968/* ------------------------------------------------------------------------*/ 969static int select_bookmark(const char* bookmark_file_name, 970 bool show_dont_resume, 971 char** selected_bookmark) 972{ 973 struct bookmark_list* bookmarks; 974 struct gui_synclist list; 975 int item = 0; 976 int action; 977 size_t size; 978 bool exit = false; 979 bool refresh = true; 980 int ret = BOOKMARK_FAIL; 981 982 bookmarks = plugin_get_buffer(&size); 983 bookmarks->buffer_size = size; 984 bookmarks->show_dont_resume = show_dont_resume; 985 bookmarks->filename = bookmark_file_name; 986 bookmarks->start = 0; 987 bookmarks->show_playlist_name 988 = (strcmp(bookmark_file_name, RECENT_BOOKMARK_FILE) == 0); 989 990 gui_synclist_init(&list, &get_bookmark_info, 991 (void*) bookmarks, false, 2, NULL); 992 993 if(global_settings.talk_menu) 994 gui_synclist_set_voice_callback(&list, bookmark_list_voice_cb); 995 996 while (!exit) 997 { 998 999 if (refresh) 1000 { 1001 int count = get_bookmark_count(bookmark_file_name); 1002 bookmarks->total_count = count; 1003 1004 if (bookmarks->total_count < 1) 1005 { 1006 /* No more bookmarks, delete file and exit */ 1007 splash(HZ, ID2P(LANG_BOOKMARK_LOAD_EMPTY)); 1008 remove(bookmark_file_name); 1009 *selected_bookmark = NULL; 1010 return BOOKMARK_FAIL; 1011 } 1012 1013 if (bookmarks->show_dont_resume) 1014 { 1015 count++; 1016 item++; 1017 } 1018 1019 gui_synclist_set_nb_items(&list, count * 2); 1020 1021 if (item >= count) 1022 { 1023 /* Selected item has been deleted */ 1024 item = count - 1; 1025 gui_synclist_select_item(&list, item * 2); 1026 } 1027 1028 buffer_bookmarks(bookmarks, bookmarks->start); 1029 gui_synclist_set_title(&list, str(LANG_BOOKMARK_SELECT_BOOKMARK), 1030 Icon_Bookmark); 1031 gui_synclist_draw(&list); 1032 cond_talk_ids_fq(VOICE_EXT_BMARK); 1033 gui_synclist_speak_item(&list); 1034 refresh = false; 1035 } 1036 1037 list_do_action(CONTEXT_BOOKMARKSCREEN, HZ / 2, &list, &action); 1038 item = gui_synclist_get_sel_pos(&list) / 2; 1039 1040 if (bookmarks->show_dont_resume) 1041 { 1042 item--; 1043 } 1044 1045 if (action == ACTION_STD_CONTEXT) 1046 { 1047 MENUITEM_STRINGLIST(menu_items, ID2P(LANG_BOOKMARK_CONTEXT_MENU), 1048 NULL, ID2P(LANG_BOOKMARK_CONTEXT_RESUME), 1049 ID2P(LANG_DELETE)); 1050 static const int menu_actions[] = 1051 { 1052 ACTION_STD_OK, ACTION_BMS_DELETE 1053 }; 1054 int selection = do_menu(&menu_items, NULL, NULL, false); 1055 1056 refresh = true; 1057 1058 if (selection >= 0 && selection <= 1059 (int) (sizeof(menu_actions) / sizeof(menu_actions[0]))) 1060 { 1061 action = menu_actions[selection]; 1062 } 1063 } 1064 1065 switch (action) 1066 { 1067 case ACTION_STD_OK: 1068 if (item >= 0) 1069 { 1070 talk_shutup(); 1071 *selected_bookmark = bookmarks->items[item - bookmarks->start]; 1072 return BOOKMARK_SUCCESS; 1073 } 1074 exit = true; 1075 ret = BOOKMARK_SUCCESS; 1076 break; 1077 1078 case ACTION_TREE_WPS: 1079 case ACTION_STD_CANCEL: 1080 exit = true; 1081 break; 1082 1083 case ACTION_BMS_DELETE: 1084 if (item >= 0) 1085 { 1086 if (confirm_delete_yesno("") == YESNO_YES) 1087 { 1088 delete_bookmark(bookmark_file_name, item); 1089 bookmarks->reload = true; 1090 } 1091 refresh = true; 1092 } 1093 break; 1094 1095 default: 1096 if (default_event_handler(action) == SYS_USB_CONNECTED) 1097 { 1098 ret = BOOKMARK_USB_CONNECTED; 1099 exit = true; 1100 } 1101 1102 break; 1103 } 1104 } 1105 1106 talk_shutup(); 1107 *selected_bookmark = NULL; 1108 return ret; 1109} 1110 1111/* ----------------------------------------------------------------------- */ 1112/* This function parses a bookmark and then plays it. */ 1113/* Returns true on successful bookmark play. */ 1114/* ------------------------------------------------------------------------*/ 1115static bool play_bookmark(const char* bookmark) 1116{ 1117 char fnamebuf[MAX_PATH]; 1118 struct resume_info resume_info; 1119#if defined(HAVE_PITCHCONTROL) 1120 /* preset pitch and speed to 100% in case bookmark doesn't have info */ 1121 resume_info.pitch = sound_get_pitch(); 1122 resume_info.speed = dsp_get_timestretch(); 1123#endif 1124 1125 if (parse_bookmark(fnamebuf, sizeof(fnamebuf), bookmark, &resume_info, true)) 1126 { 1127 global_settings.repeat_mode = resume_info.repeat_mode; 1128 global_settings.playlist_shuffle = resume_info.shuffle; 1129#if defined(HAVE_PITCHCONTROL) 1130 sound_set_pitch(resume_info.pitch); 1131 dsp_set_timestretch(resume_info.speed); 1132#endif 1133 if (!warn_on_pl_erase()) 1134 return false; 1135 bool success = bookmark_play(global_temp_buffer, resume_info.resume_index, 1136 resume_info.resume_elapsed, resume_info.resume_offset, 1137 resume_info.resume_seed, fnamebuf); 1138 if (success) /* verify we loaded the correct track */ 1139 { 1140 const struct mp3entry *id3 = audio_current_track(); 1141 if (id3) 1142 { 1143 const char *path; 1144 const char *track; 1145 path_basename(id3->path, &path); 1146 path_basename(fnamebuf, &track); 1147 if (strcmp(path, track) == 0) 1148 { 1149 return true; 1150 } 1151 } 1152 audio_stop(); 1153 } 1154 } 1155 1156 return false; 1157} 1158 1159/*-------------------------------------------------------------------------*/ 1160/* PUBLIC INTERFACE -------------------------------------------------------*/ 1161/*-------------------------------------------------------------------------*/ 1162 1163 1164/* ----------------------------------------------------------------------- */ 1165/* This is an interface function from the context menu. */ 1166/* Returns true on successful bookmark creation. */ 1167/* ----------------------------------------------------------------------- */ 1168bool bookmark_create_menu(void) 1169{ 1170 if (!bookmark_is_bookmarkable_state()) 1171 save_playlist_screen(NULL); 1172 1173 return write_bookmark(BMARK_CREATE_FILE); 1174} 1175/* ----------------------------------------------------------------------- */ 1176/* This function acts as the load interface from the context menu. */ 1177/* This function determines the bookmark file name and then loads that file*/ 1178/* for the user. The user can then select or delete previous bookmarks. */ 1179/* This function returns BOOKMARK_SUCCESS on the selection of a track to */ 1180/* resume, BOOKMARK_FAIL if the menu is exited without a selection and */ 1181/* BOOKMARK_USB_CONNECTED if the menu is forced to exit due to a USB */ 1182/* connection. */ 1183/* ----------------------------------------------------------------------- */ 1184int bookmark_load_menu(void) 1185{ 1186 char bm_filename[MAX_PATH]; 1187 char* bookmark; 1188 int ret = BOOKMARK_FAIL; 1189 1190 /* To prevent root dir ("/") bookmarks from being displayed 1191 when 'List Bookmarks' hotkey or button is pressed, check: 1192 */ 1193 if (playlist_dynamic_only()) 1194 { 1195 splash(HZ, ID2P(LANG_BOOKMARK_LOAD_EMPTY)); 1196 return ret; 1197 } 1198 1199 push_current_activity(ACTIVITY_BOOKMARKSLIST); 1200 1201 char* name = playlist_get_name(NULL, global_temp_buffer, 1202 sizeof(global_temp_buffer)); 1203 if (generate_bookmark_file_name(bm_filename, sizeof(bm_filename), name, -1)) 1204 { 1205 ret = select_bookmark(bm_filename, false, &bookmark); 1206 if (bookmark != NULL) 1207 { 1208 ret = play_bookmark(bookmark) ? BOOKMARK_SUCCESS : BOOKMARK_FAIL; 1209 } 1210 } 1211 1212 pop_current_activity(); 1213 return ret; 1214} 1215 1216/* ----------------------------------------------------------------------- */ 1217/* Gives the user a list of the Most Recent Bookmarks. This is an */ 1218/* interface function */ 1219/* Returns true on the successful selection of a recent bookmark. */ 1220/* ----------------------------------------------------------------------- */ 1221bool bookmark_mrb_load() 1222{ 1223 char* bookmark; 1224 bool ret = false; 1225 1226 push_current_activity(ACTIVITY_BOOKMARKSLIST); 1227 select_bookmark(RECENT_BOOKMARK_FILE, false, &bookmark); 1228 if (bookmark != NULL) 1229 { 1230 ret = play_bookmark(bookmark); 1231 } 1232 1233 pop_current_activity(); 1234 return ret; 1235} 1236 1237/* ----------------------------------------------------------------------- */ 1238/* This function handles an autobookmark creation. This is an interface */ 1239/* function. */ 1240/* Returns true on successful bookmark creation. */ 1241/* ----------------------------------------------------------------------- */ 1242bool bookmark_autobookmark(bool prompt_ok) 1243{ 1244 logf("%s", __func__); 1245 bool update; 1246 1247 if (!bookmark_is_bookmarkable_state()) 1248 return false; 1249 1250 audio_pause(); /* first pause playback */ 1251 update = (global_settings.autoupdatebookmark && bookmark_exists()); 1252 1253 if (update) 1254 return write_bookmark(BMARK_CREATE_FILE | BMARK_CHECK_IGNORE); 1255 1256 switch (global_settings.autocreatebookmark) 1257 { 1258 case BOOKMARK_YES: 1259 return write_bookmark(BMARK_CREATE_FILE | BMARK_CHECK_IGNORE); 1260 1261 case BOOKMARK_NO: 1262 return false; 1263 1264 case BOOKMARK_RECENT_ONLY_YES: 1265 return write_bookmark(BMARK_CHECK_IGNORE); 1266 } 1267 1268 if(prompt_ok) 1269 return write_bookmark(BMARK_ASK_USER | BMARK_CHECK_IGNORE); 1270 1271 return false; 1272} 1273 1274/* ----------------------------------------------------------------------- */ 1275/* This function will determine if an autoload is necessary. This is an */ 1276/* interface function. */ 1277/* Returns */ 1278/* BOOKMARK_DO_RESUME on bookmark load or bookmark selection. */ 1279/* BOOKMARK_DONT_RESUME if we're not going to resume */ 1280/* BOOKMARK_CANCEL if user canceled */ 1281/* ------------------------------------------------------------------------*/ 1282int bookmark_autoload(const char* file) 1283{ 1284 logf("%s", __func__); 1285 char bm_filename[MAX_PATH]; 1286 char* bookmark; 1287 1288 if(global_settings.autoloadbookmark == BOOKMARK_NO) 1289 return BOOKMARK_DONT_RESUME; 1290 1291 /*Checking to see if a bookmark file exists.*/ 1292 if(!generate_bookmark_file_name(bm_filename, sizeof(bm_filename), file, -1)) 1293 { 1294 return BOOKMARK_DONT_RESUME; 1295 } 1296 1297 if(!file_exists(bm_filename)) 1298 return BOOKMARK_DONT_RESUME; 1299 1300 if(global_settings.autoloadbookmark == BOOKMARK_YES) 1301 { 1302 return (bookmark_load(bm_filename, true) 1303 ? BOOKMARK_DO_RESUME : BOOKMARK_DONT_RESUME); 1304 } 1305 else 1306 { 1307 int ret = select_bookmark(bm_filename, true, &bookmark); 1308 1309 if (bookmark != NULL) 1310 { 1311 if (!play_bookmark(bookmark)) 1312 return BOOKMARK_CANCEL; 1313 return BOOKMARK_DO_RESUME; 1314 } 1315 1316 return (ret != BOOKMARK_SUCCESS) ? BOOKMARK_CANCEL : BOOKMARK_DONT_RESUME; 1317 } 1318} 1319 1320/* ----------------------------------------------------------------------- */ 1321/* This function loads the bookmark information into the resume memory. */ 1322/* This is an interface function. */ 1323/* Returns true on successful bookmark load. */ 1324/* ------------------------------------------------------------------------*/ 1325bool bookmark_load(const char* file, bool autoload) 1326{ 1327 logf("%s", __func__); 1328 int fd; 1329 char* bookmark = NULL; 1330 1331 if(autoload) 1332 { 1333 fd = open(file, O_RDONLY); 1334 if(fd >= 0) 1335 { 1336 if(read_line(fd, global_temp_buffer, sizeof(global_temp_buffer)) > 0) 1337 bookmark=global_temp_buffer; 1338 close(fd); 1339 } 1340 } 1341 else 1342 { 1343 /* This is not an auto-load, so list the bookmarks */ 1344 select_bookmark(file, false, &bookmark); 1345 } 1346 1347 if (bookmark != NULL) 1348 { 1349 if (!play_bookmark(bookmark)) 1350 { 1351 /* Selected bookmark not found. */ 1352 if (!autoload) 1353 { 1354 splash(HZ*2, ID2P(LANG_NOTHING_TO_RESUME)); 1355 } 1356 1357 return false; 1358 } 1359 } 1360 1361 return true; 1362} 1363 1364/* ----------------------------------------------------------------------- */ 1365/* Returns true if a bookmark file exists for the current playlist. */ 1366/* This is an interface function. */ 1367/* ----------------------------------------------------------------------- */ 1368bool bookmark_exists(void) 1369{ 1370 char bm_filename[MAX_PATH]; 1371 bool exist=false; 1372 1373 char* name = playlist_get_name(NULL, global_temp_buffer, 1374 sizeof(global_temp_buffer)); 1375 if (!playlist_dynamic_only() && 1376 generate_bookmark_file_name(bm_filename, sizeof(bm_filename), name, -1)) 1377 { 1378 exist = file_exists(bm_filename); 1379 } 1380 return exist; 1381} 1382 1383/* ----------------------------------------------------------------------- */ 1384/* Checks the current state of the system and returns true if it is in a */ 1385/* bookmarkable state. */ 1386/* This is an interface funtion. */ 1387/* ----------------------------------------------------------------------- */ 1388bool bookmark_is_bookmarkable_state(void) 1389{ 1390 int resume_index = 0; 1391 1392 if (!(audio_status() && audio_current_track()) || 1393 /* no track playing */ 1394 (playlist_get_resume_info(&resume_index) == -1) || 1395 /* invalid queue info */ 1396 (playlist_modified(NULL)) || 1397 /* can't bookmark playlists modified by user */ 1398 (playlist_dynamic_only())) 1399 /* can't bookmark playlists without associated folder or playlist file */ 1400 { 1401 return false; 1402 } 1403 1404 return true; 1405}