A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
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}