···7878 int speed;
7979} bm;
80808181-static bool add_bookmark(const char* bookmark_file_name, const char* bookmark,
8282- bool most_recent);
8383-static char* create_bookmark(void);
8484-static bool delete_bookmark(const char* bookmark_file_name, int bookmark_id);
8585-static void say_bookmark(const char* bookmark,
8686- int bookmark_id, bool show_playlist_name);
8787-static bool play_bookmark(const char* bookmark);
8888-static bool generate_bookmark_file_name(const char *in);
8989-static bool parse_bookmark(const char *bookmark, const bool get_filenames, const bool strip_dir);
9090-static int buffer_bookmarks(struct bookmark_list* bookmarks, int first_line);
9191-static const char* get_bookmark_info(int list_index,
9292- void* data,
9393- char *buffer,
9494- size_t buffer_len);
9595-static int select_bookmark(const char* bookmark_file_name, bool show_dont_resume, char** selected_bookmark);
9696-static bool write_bookmark(bool create_bookmark_file, const char *bookmark);
9797-static int get_bookmark_count(const char* bookmark_file_name);
9898-9981#define TEMP_BUF_SIZE (MAX_PATH + 1)
10082static char global_temp_buffer[TEMP_BUF_SIZE];
10183/* File name created by generate_bookmark_file_name */
···10688/* Filename from parsed bookmark (can be made local where needed) */
10789static char global_filename[MAX_PATH];
10890109109-/* ----------------------------------------------------------------------- */
110110-/* This is an interface function from the context menu. */
111111-/* Returns true on successful bookmark creation. */
112112-/* ----------------------------------------------------------------------- */
113113-bool bookmark_create_menu(void)
9191+static const char* skip_token(const char* s)
11492{
115115- return write_bookmark(true, create_bookmark());
116116-}
9393+ while (*s && *s != ';')
9494+ {
9595+ s++;
9696+ }
11797118118-/* ----------------------------------------------------------------------- */
119119-/* This function acts as the load interface from the context menu. */
120120-/* This function determines the bookmark file name and then loads that file*/
121121-/* for the user. The user can then select or delete previous bookmarks. */
122122-/* This function returns BOOKMARK_SUCCESS on the selection of a track to */
123123-/* resume, BOOKMARK_FAIL if the menu is exited without a selection and */
124124-/* BOOKMARK_USB_CONNECTED if the menu is forced to exit due to a USB */
125125-/* connection. */
126126-/* ----------------------------------------------------------------------- */
127127-int bookmark_load_menu(void)
128128-{
129129- char* bookmark;
130130- int ret = BOOKMARK_FAIL;
131131-132132- push_current_activity(ACTIVITY_BOOKMARKSLIST);
133133-134134- char* name = playlist_get_name(NULL, global_temp_buffer,
135135- sizeof(global_temp_buffer));
136136- if (generate_bookmark_file_name(name))
9898+ if (*s)
13799 {
138138- ret = select_bookmark(global_bookmark_file_name, false, &bookmark);
139139- if (bookmark != NULL)
140140- {
141141- ret = play_bookmark(bookmark) ? BOOKMARK_SUCCESS : BOOKMARK_FAIL;
142142- }
100100+ s++;
143101 }
144102145145- pop_current_activity();
146146- return ret;
103103+ return s;
147104}
148105149149-/* ----------------------------------------------------------------------- */
150150-/* Gives the user a list of the Most Recent Bookmarks. This is an */
151151-/* interface function */
152152-/* Returns true on the successful selection of a recent bookmark. */
153153-/* ----------------------------------------------------------------------- */
154154-bool bookmark_mrb_load()
106106+static const char* int_token(const char* s, int* dest)
155107{
156156- char* bookmark;
157157- bool ret = false;
108108+ *dest = atoi(s);
109109+ return skip_token(s);
110110+}
158111159159- push_current_activity(ACTIVITY_BOOKMARKSLIST);
160160- select_bookmark(RECENT_BOOKMARK_FILE, false, &bookmark);
161161- if (bookmark != NULL)
162162- {
163163- ret = play_bookmark(bookmark);
164164- }
112112+static const char* long_token(const char* s, long* dest)
113113+{
114114+ *dest = atoi(s); /* Should be atol, but we don't have it. */
115115+ return skip_token(s);
116116+}
165117166166- pop_current_activity();
167167- return ret;
118118+/*-------------------------------------------------------------------------*/
119119+/* Get the name of the playlist and the name of the track from a bookmark. */
120120+/* Returns true iff both were extracted. */
121121+/*-------------------------------------------------------------------------*/
122122+static bool get_playlist_and_track(const char *bookmark,
123123+ char **pl_start,
124124+ char **pl_end,
125125+ char **track)
126126+{
127127+ *pl_start = strchr(bookmark,'/');
128128+ if (!(*pl_start))
129129+ return false;
130130+ *pl_end = strrchr(bookmark,';');
131131+ *track = *pl_end + 1;
132132+ return true;
168133}
169134170135/* ----------------------------------------------------------------------- */
171171-/* This function handles an autobookmark creation. This is an interface */
172172-/* function. */
173173-/* Returns true on successful bookmark creation. */
136136+/* This function takes a bookmark and parses it. This function also */
137137+/* validates the bookmark. The parse_filenames flag indicates whether */
138138+/* the filename tokens are to be extracted. */
139139+/* Returns true on successful bookmark parse. */
174140/* ----------------------------------------------------------------------- */
175175-bool bookmark_autobookmark(bool prompt_ok)
141141+static bool parse_bookmark(const char *bookmark,
142142+ const bool parse_filenames,
143143+ const bool strip_dir)
176144{
177177- char* bookmark;
178178- bool update;
145145+ const char* s = bookmark;
146146+ const char* end;
179147180180- if (!bookmark_is_bookmarkable_state())
181181- return false;
148148+#define GET_INT_TOKEN(var) s = int_token(s, &var)
149149+#define GET_LONG_TOKEN(var) s = long_token(s, &var)
150150+#define GET_BOOL_TOKEN(var) var = (atoi(s)!=0); s = skip_token(s)
182151183183- audio_pause(); /* first pause playback */
184184- update = (global_settings.autoupdatebookmark && bookmark_exists());
185185- bookmark = create_bookmark();
186186-187187- if (update)
188188- return write_bookmark(true, bookmark);
189189-190190- switch (global_settings.autocreatebookmark)
152152+ /* if new format bookmark, extract the optional content flags,
153153+ otherwise treat as an original format bookmark */
154154+ int opt_flags = 0;
155155+ bool new_format = (strchr(s, '>') == s);
156156+ if (new_format)
191157 {
192192- case BOOKMARK_YES:
193193- return write_bookmark(true, bookmark);
158158+ s++;
159159+ GET_INT_TOKEN(opt_flags);
160160+ }
194161195195- case BOOKMARK_NO:
196196- return false;
162162+ /* extract all original bookmark tokens */
163163+ GET_INT_TOKEN(bm.resume_index);
164164+ GET_LONG_TOKEN(bm.resume_offset);
165165+ GET_INT_TOKEN(bm.resume_seed);
166166+ if (!new_format) /* skip deprecated token */
167167+ s = skip_token(s);
168168+ GET_LONG_TOKEN(bm.resume_time);
169169+ GET_INT_TOKEN(bm.repeat_mode);
170170+ GET_BOOL_TOKEN(bm.shuffle);
197171198198- case BOOKMARK_RECENT_ONLY_YES:
199199- return write_bookmark(false, bookmark);
200200- }
201201- const char *lines[]={ID2P(LANG_AUTO_BOOKMARK_QUERY)};
202202- const struct text_message message={lines, 1};
172172+ /* extract all optional bookmark tokens */
173173+ if (opt_flags & BM_PITCH)
174174+ GET_INT_TOKEN(bm.pitch);
175175+ if (opt_flags & BM_SPEED)
176176+ GET_INT_TOKEN(bm.speed);
203177204204- if(prompt_ok && gui_syncyesno_run(&message, NULL, NULL)==YESNO_YES)
178178+ if (*s == 0)
205179 {
206206- if (global_settings.autocreatebookmark == BOOKMARK_RECENT_ONLY_ASK)
207207- return write_bookmark(false, bookmark);
208208- else
209209- return write_bookmark(true, bookmark);
180180+ return false;
210181 }
211211- return false;
212212-}
213182214214-/* ----------------------------------------------------------------------- */
215215-/* This function takes the current current resume information and writes */
216216-/* that to the beginning of the bookmark file. */
217217-/* This file will contain N number of bookmarks in the following format: */
218218-/* resume_index*resume_offset*resume_seed*resume_first_index* */
219219-/* resume_file*milliseconds*MP3 Title* */
220220-/* Returns true on successful bookmark write. */
221221-/* Returns false if any part of the bookmarking process fails. It is */
222222-/* possible that a bookmark is successfully added to the most recent */
223223-/* bookmark list but fails to be added to the bookmark file or vice versa. */
224224-/* ------------------------------------------------------------------------*/
225225-static bool write_bookmark(bool create_bookmark_file, const char *bookmark)
226226-{
227227- bool ret=true;
183183+ end = strchr(s, ';');
228184229229- if (!bookmark)
230230- {
231231- ret = false; /* something didn't happen correctly, do nothing */
232232- }
233233- else
185185+ /* extract file names */
186186+ if (parse_filenames)
234187 {
235235- if (global_settings.usemrb)
236236- ret = add_bookmark(RECENT_BOOKMARK_FILE, bookmark, true);
237237-188188+ size_t len = (end == NULL) ? strlen(s) : (size_t) (end - s);
189189+ len = MIN(TEMP_BUF_SIZE - 1, len);
190190+ strmemccpy(global_temp_buffer, s, len + 1);
238191239239- /* writing the bookmark */
240240- if (create_bookmark_file)
192192+ if (end != NULL)
241193 {
242242- char* name = playlist_get_name(NULL, global_temp_buffer,
243243- sizeof(global_temp_buffer));
244244- if (generate_bookmark_file_name(name))
194194+ end++;
195195+ if (strip_dir)
245196 {
246246- ret = ret & add_bookmark(global_bookmark_file_name, bookmark, false);
197197+ s = strrchr(end, '/');
198198+ if (s)
199199+ {
200200+ end = s;
201201+ end++;
202202+ }
247203 }
248248- else
249249- {
250250- ret = false; /* generating bookmark file failed */
251251- }
204204+ strmemccpy(global_filename, end, MAX_PATH);
252205 }
253253- }
254254-255255- splash(HZ, ret ? ID2P(LANG_BOOKMARK_CREATE_SUCCESS)
256256- : ID2P(LANG_BOOKMARK_CREATE_FAILURE));
257257-258258- return ret;
259259-}
206206+ }
260207261261-/* Get the name of the playlist and the name of the track from a bookmark. */
262262-/* Returns true iff both were extracted. */
263263-static bool get_playlist_and_track(const char *bookmark, char **pl_start,
264264- char **pl_end, char **track)
265265-{
266266- *pl_start = strchr(bookmark,'/');
267267- if (!(*pl_start))
268268- return false;
269269- *pl_end = strrchr(bookmark,';');
270270- *track = *pl_end + 1;
271208 return true;
272209}
273210···275212/* This function adds a bookmark to a file. */
276213/* Returns true on successful bookmark add. */
277214/* ------------------------------------------------------------------------*/
278278-static bool add_bookmark(const char* bookmark_file_name, const char* bookmark,
215215+static bool add_bookmark(const char* bookmark_file_name,
216216+ const char* bookmark,
279217 bool most_recent)
280218{
281219 int temp_bookmark_file = 0;
···357295 return true;
358296}
359297360360-/* GCC 7 and up complain about the snprintf in create_bookmark() when
361361- compiled with -D_FORTIFY_SOURCE or -Wformat-truncation
362362- This is a false positive, so disable it here only */
363363-#if __GNUC__ >= 7
364364-#pragma GCC diagnostic push
365365-#pragma GCC diagnostic ignored "-Wformat-truncation"
366366-#endif
367298/* ----------------------------------------------------------------------- */
368368-/* This function takes the system resume data and formats it into a valid */
369369-/* bookmark. */
370370-/* Returns not NULL on successful bookmark format. */
299299+/* This function is used by multiple functions and is used to generate a */
300300+/* bookmark named based off of the input. */
301301+/* Changing this function could result in how the bookmarks are stored. */
302302+/* it would be here that the centralized/decentralized bookmark code */
303303+/* could be placed. */
304304+/* Returns true if the file name is generated, false if it was too long */
371305/* ----------------------------------------------------------------------- */
372372-static char* create_bookmark()
306306+static bool generate_bookmark_file_name(const char *in)
373307{
374374- int resume_index = 0;
375375- char *file;
376376-377377- if (!bookmark_is_bookmarkable_state())
378378- return NULL; /* something didn't happen correctly, do nothing */
379379-380380- /* grab the currently playing track */
381381- struct mp3entry *id3 = audio_current_track();
382382- if(!id3)
383383- return NULL;
384384-385385- /* Get some basic resume information */
386386- /* queue_resume and queue_resume_index are not used and can be ignored.*/
387387- playlist_get_resume_info(&resume_index);
388388-389389- /* Get the currently playing file minus the path */
390390- /* This is used when displaying the available bookmarks */
391391- file = strrchr(id3->path,'/');
392392- if(NULL == file)
393393- return NULL;
394394-395395- /* create the bookmark */
396396- playlist_get_name(NULL, global_temp_buffer, sizeof(global_temp_buffer));
397397- if (global_temp_buffer[strlen(global_temp_buffer) - 1] != '/')
398398- file = id3->path;
399399- else file++;
400400- snprintf(global_bookmark, sizeof(global_bookmark),
401401- /* new optional bookmark token descriptors should be inserted
402402- just before the "%s;%s" in this line... */
403403-#if defined(HAVE_PITCHCONTROL)
404404- ">%d;%d;%ld;%d;%ld;%d;%d;%ld;%ld;%s;%s",
405405-#else
406406- ">%d;%d;%ld;%d;%ld;%d;%d;%s;%s",
407407-#endif
408408- /* ... their flags should go here ... */
409409-#if defined(HAVE_PITCHCONTROL)
410410- BM_PITCH | BM_SPEED,
411411-#else
412412- 0,
413413-#endif
414414- resume_index,
415415- id3->offset,
416416- playlist_get_seed(NULL),
417417- id3->elapsed,
418418- global_settings.repeat_mode,
419419- global_settings.playlist_shuffle,
420420- /* ...and their values should go here */
421421-#if defined(HAVE_PITCHCONTROL)
422422- (long)sound_get_pitch(),
423423- (long)dsp_get_timestretch(),
424424-#endif
425425- /* more mandatory tokens */
426426- global_temp_buffer,
427427- file);
428428-429429- /* checking to see if the bookmark is valid */
430430- if (parse_bookmark(global_bookmark, false, false))
431431- return global_bookmark;
308308+ /* if this is a root dir MP3, rename the bookmark file root_dir.bmark */
309309+ /* otherwise, name it based on the in variable */
310310+ if (!strcmp("/", in))
311311+ strcpy(global_bookmark_file_name, "/root_dir.bmark");
432312 else
433433- return NULL;
434434-}
435435-#if __GNUC__ >= 7
436436-#pragma GCC diagnostic pop /* -Wformat-truncation */
313313+ {
314314+#ifdef HAVE_MULTIVOLUME
315315+ /* The "root" of an extra volume need special handling too. */
316316+ const char *filename;
317317+ path_strip_volume(in, &filename, true);
318318+ bool volume_root = *filename == '\0';
437319#endif
320320+ size_t len = strlcpy(global_bookmark_file_name, in, MAX_PATH);
321321+ if(len >= MAX_PATH)
322322+ return false;
438323439439-/* ----------------------------------------------------------------------- */
440440-/* This function will determine if an autoload is necessary. This is an */
441441-/* interface function. */
442442-/* Returns */
443443-/* BOOKMARK_DO_RESUME on bookmark load or bookmark selection. */
444444-/* BOOKMARK_DONT_RESUME if we're not going to resume */
445445-/* BOOKMARK_CANCEL if user canceled */
446446-/* ------------------------------------------------------------------------*/
447447-int bookmark_autoload(const char* file)
448448-{
449449- char* bookmark;
324324+ if(global_bookmark_file_name[len-1] == '/') {
325325+ global_bookmark_file_name[len-1] = '\0';
326326+ len--;
327327+ }
450328451451- if(global_settings.autoloadbookmark == BOOKMARK_NO)
452452- return BOOKMARK_DONT_RESUME;
329329+#ifdef HAVE_MULTIVOLUME
330330+ if (volume_root)
331331+ len = strlcat(global_bookmark_file_name, "/volume_dir.bmark", MAX_PATH);
332332+ else
333333+#endif
334334+ len = strlcat(global_bookmark_file_name, ".bmark", MAX_PATH);
453335454454- /*Checking to see if a bookmark file exists.*/
455455- if(!generate_bookmark_file_name(file))
456456- {
457457- return BOOKMARK_DONT_RESUME;
336336+ if(len >= MAX_PATH)
337337+ return false;
458338 }
459339460460- if(!file_exists(global_bookmark_file_name))
461461- return BOOKMARK_DONT_RESUME;
462462-463463- if(global_settings.autoloadbookmark == BOOKMARK_YES)
464464- {
465465- return bookmark_load(global_bookmark_file_name, true) ? BOOKMARK_DO_RESUME :
466466- BOOKMARK_DONT_RESUME;
467467- }
468468- else
469469- {
470470- int ret = select_bookmark(global_bookmark_file_name, true, &bookmark);
471471-472472- if (bookmark != NULL)
473473- {
474474- if (!play_bookmark(bookmark))
475475- {
476476- /* Selected bookmark not found. */
477477- splash(HZ*2, ID2P(LANG_NOTHING_TO_RESUME));
478478- }
479479-480480- /* Act as if autoload was done even if it failed, since the
481481- * user did make an active selection.
482482- */
483483- return BOOKMARK_DO_RESUME;
484484- }
485485-486486- return ret != BOOKMARK_SUCCESS ? BOOKMARK_CANCEL : BOOKMARK_DONT_RESUME;
487487- }
340340+ return true;
488341}
489342490343/* ----------------------------------------------------------------------- */
491491-/* This function loads the bookmark information into the resume memory. */
492492-/* This is an interface function. */
493493-/* Returns true on successful bookmark load. */
344344+/* This function takes the current current resume information and writes */
345345+/* that to the beginning of the bookmark file. */
346346+/* This file will contain N number of bookmarks in the following format: */
347347+/* resume_index*resume_offset*resume_seed*resume_first_index* */
348348+/* resume_file*milliseconds*MP3 Title* */
349349+/* Returns true on successful bookmark write. */
350350+/* Returns false if any part of the bookmarking process fails. It is */
351351+/* possible that a bookmark is successfully added to the most recent */
352352+/* bookmark list but fails to be added to the bookmark file or vice versa. */
494353/* ------------------------------------------------------------------------*/
495495-bool bookmark_load(const char* file, bool autoload)
354354+static bool write_bookmark(bool create_bookmark_file, const char *bookmark)
496355{
497497- int fd;
498498- char* bookmark = NULL;
356356+ bool ret=true;
499357500500- if(autoload)
358358+ if (!bookmark)
501359 {
502502- fd = open(file, O_RDONLY);
503503- if(fd >= 0)
504504- {
505505- if(read_line(fd, global_read_buffer, sizeof(global_read_buffer)) > 0)
506506- bookmark=global_read_buffer;
507507- close(fd);
508508- }
360360+ ret = false; /* something didn't happen correctly, do nothing */
509361 }
510362 else
511363 {
512512- /* This is not an auto-load, so list the bookmarks */
513513- select_bookmark(file, false, &bookmark);
514514- }
364364+ if (global_settings.usemrb)
365365+ ret = add_bookmark(RECENT_BOOKMARK_FILE, bookmark, true);
515366516516- if (bookmark != NULL)
517517- {
518518- if (!play_bookmark(bookmark))
367367+368368+ /* writing the bookmark */
369369+ if (create_bookmark_file)
519370 {
520520- /* Selected bookmark not found. */
521521- if (!autoload)
371371+ char* name = playlist_get_name(NULL, global_temp_buffer,
372372+ sizeof(global_temp_buffer));
373373+ if (generate_bookmark_file_name(name))
374374+ {
375375+ ret = ret & add_bookmark(global_bookmark_file_name, bookmark, false);
376376+ }
377377+ else
522378 {
523523- splash(HZ*2, ID2P(LANG_NOTHING_TO_RESUME));
379379+ ret = false; /* generating bookmark file failed */
524380 }
525525-526526- return false;
527381 }
528382 }
529383530530- return true;
531531-}
384384+ splash(HZ, ret ? ID2P(LANG_BOOKMARK_CREATE_SUCCESS)
385385+ : ID2P(LANG_BOOKMARK_CREATE_FAILURE));
532386387387+ return ret;
388388+}
533389534390static int get_bookmark_count(const char* bookmark_file_name)
535391{
···707563 }
708564}
709565566566+/* ----------------------------------------------------------------------- */
567567+/* This function parses a bookmark, says the voice UI part of it. */
568568+/* ------------------------------------------------------------------------*/
569569+static void say_bookmark(const char* bookmark,
570570+ int bookmark_id,
571571+ bool show_playlist_name)
572572+{
573573+ if (!parse_bookmark(bookmark, true, false))
574574+ {
575575+ talk_id(LANG_BOOKMARK_INVALID, false);
576576+ return;
577577+ }
578578+579579+ talk_number(bookmark_id + 1, false);
580580+581581+ bool is_dir = (global_temp_buffer[0]
582582+ && global_temp_buffer[strlen(global_temp_buffer)-1] == '/');
583583+584584+ /* HWCODEC cannot enqueue voice file entries and .talk thumbnails
585585+ together, because there is no guarantee that the same mp3
586586+ parameters are used. */
587587+ if(show_playlist_name)
588588+ { /* It's useful to know which playlist this is */
589589+ if(is_dir)
590590+ talk_dir_or_spell(global_temp_buffer,
591591+ TALK_IDARRAY(VOICE_DIR), true);
592592+ else talk_file_or_spell(NULL, global_temp_buffer,
593593+ TALK_IDARRAY(LANG_PLAYLIST), true);
594594+ }
595595+596596+ if(bm.shuffle)
597597+ talk_id(LANG_SHUFFLE, true);
598598+599599+ talk_id(VOICE_BOOKMARK_SELECT_INDEX_TEXT, true);
600600+ talk_number(bm.resume_index + 1, true);
601601+ talk_id(LANG_TIME, true);
602602+ talk_value(bm.resume_time / 1000, UNIT_TIME, true);
603603+604604+ /* Track filename */
605605+ if(!is_dir)
606606+ global_temp_buffer[0] = 0;
607607+ talk_file_or_spell(global_temp_buffer, global_filename,
608608+ TALK_IDARRAY(VOICE_FILE), true);
609609+}
610610+710611static int bookmark_list_voice_cb(int list_index, void* data)
711612{
712613 struct bookmark_list* bookmarks = (struct bookmark_list*) data;
···724625}
725626726627/* ----------------------------------------------------------------------- */
628628+/* This function takes a location in a bookmark file and deletes that */
629629+/* bookmark. */
630630+/* Returns true on successful bookmark deletion. */
631631+/* ------------------------------------------------------------------------*/
632632+static bool delete_bookmark(const char* bookmark_file_name, int bookmark_id)
633633+{
634634+ int temp_bookmark_file = 0;
635635+ int bookmark_file = 0;
636636+ int bookmark_count = 0;
637637+638638+ /* Opening up a temp bookmark file */
639639+ temp_bookmark_file = open_pathfmt(global_temp_buffer, sizeof(global_temp_buffer),
640640+ O_WRONLY | O_CREAT | O_TRUNC, "%s.tmp", bookmark_file_name);
641641+642642+ if (temp_bookmark_file < 0)
643643+ return false; /* can't open the temp file */
644644+645645+ /* Reading in the previous bookmarks and writing them to the temp file */
646646+ bookmark_file = open(bookmark_file_name, O_RDONLY);
647647+ if (bookmark_file >= 0)
648648+ {
649649+ while (read_line(bookmark_file, global_read_buffer,
650650+ sizeof(global_read_buffer)) > 0)
651651+ {
652652+ if (bookmark_id != bookmark_count)
653653+ {
654654+ write(temp_bookmark_file, global_read_buffer,
655655+ strlen(global_read_buffer));
656656+ write(temp_bookmark_file, "\n", 1);
657657+ }
658658+ bookmark_count++;
659659+ }
660660+ close(bookmark_file);
661661+ }
662662+ close(temp_bookmark_file);
663663+664664+ remove(bookmark_file_name);
665665+ rename(global_temp_buffer, bookmark_file_name);
666666+667667+ return true;
668668+}
669669+670670+/* ----------------------------------------------------------------------- */
727671/* This displays the bookmarks in a file and allows the user to */
728672/* select one to play. */
729673/* *selected_bookmark contains a non NULL value on successful bookmark */
···732676/* if no selection was made and BOOKMARK_USB_CONNECTED if the selection */
733677/* menu is forced to exit due to a USB connection. */
734678/* ------------------------------------------------------------------------*/
735735-static int select_bookmark(const char* bookmark_file_name, bool show_dont_resume, char** selected_bookmark)
679679+static int select_bookmark(const char* bookmark_file_name,
680680+ bool show_dont_resume,
681681+ char** selected_bookmark)
736682{
737683 struct bookmark_list* bookmarks;
738684 struct gui_synclist list;
···880826}
881827882828/* ----------------------------------------------------------------------- */
883883-/* This function takes a location in a bookmark file and deletes that */
884884-/* bookmark. */
885885-/* Returns true on successful bookmark deletion. */
886886-/* ------------------------------------------------------------------------*/
887887-static bool delete_bookmark(const char* bookmark_file_name, int bookmark_id)
888888-{
889889- int temp_bookmark_file = 0;
890890- int bookmark_file = 0;
891891- int bookmark_count = 0;
892892-893893- /* Opening up a temp bookmark file */
894894- temp_bookmark_file = open_pathfmt(global_temp_buffer, sizeof(global_temp_buffer),
895895- O_WRONLY | O_CREAT | O_TRUNC, "%s.tmp", bookmark_file_name);
896896-897897- if (temp_bookmark_file < 0)
898898- return false; /* can't open the temp file */
899899-900900- /* Reading in the previous bookmarks and writing them to the temp file */
901901- bookmark_file = open(bookmark_file_name, O_RDONLY);
902902- if (bookmark_file >= 0)
903903- {
904904- while (read_line(bookmark_file, global_read_buffer,
905905- sizeof(global_read_buffer)) > 0)
906906- {
907907- if (bookmark_id != bookmark_count)
908908- {
909909- write(temp_bookmark_file, global_read_buffer,
910910- strlen(global_read_buffer));
911911- write(temp_bookmark_file, "\n", 1);
912912- }
913913- bookmark_count++;
914914- }
915915- close(bookmark_file);
916916- }
917917- close(temp_bookmark_file);
918918-919919- remove(bookmark_file_name);
920920- rename(global_temp_buffer, bookmark_file_name);
921921-922922- return true;
923923-}
924924-925925-/* ----------------------------------------------------------------------- */
926926-/* This function parses a bookmark, says the voice UI part of it. */
927927-/* ------------------------------------------------------------------------*/
928928-static void say_bookmark(const char* bookmark,
929929- int bookmark_id, bool show_playlist_name)
930930-{
931931- if (!parse_bookmark(bookmark, true, false))
932932- {
933933- talk_id(LANG_BOOKMARK_INVALID, false);
934934- return;
935935- }
936936-937937- talk_number(bookmark_id + 1, false);
938938-939939- bool is_dir = (global_temp_buffer[0]
940940- && global_temp_buffer[strlen(global_temp_buffer)-1] == '/');
941941-942942- /* HWCODEC cannot enqueue voice file entries and .talk thumbnails
943943- together, because there is no guarantee that the same mp3
944944- parameters are used. */
945945- if(show_playlist_name)
946946- { /* It's useful to know which playlist this is */
947947- if(is_dir)
948948- talk_dir_or_spell(global_temp_buffer,
949949- TALK_IDARRAY(VOICE_DIR), true);
950950- else talk_file_or_spell(NULL, global_temp_buffer,
951951- TALK_IDARRAY(LANG_PLAYLIST), true);
952952- }
953953-954954- if(bm.shuffle)
955955- talk_id(LANG_SHUFFLE, true);
956956-957957- talk_id(VOICE_BOOKMARK_SELECT_INDEX_TEXT, true);
958958- talk_number(bm.resume_index + 1, true);
959959- talk_id(LANG_TIME, true);
960960- talk_value(bm.resume_time / 1000, UNIT_TIME, true);
961961-962962- /* Track filename */
963963- if(!is_dir)
964964- global_temp_buffer[0] = 0;
965965- talk_file_or_spell(global_temp_buffer, global_filename,
966966- TALK_IDARRAY(VOICE_FILE), true);
967967-}
968968-969969-/* ----------------------------------------------------------------------- */
970829/* This function parses a bookmark and then plays it. */
971830/* Returns true on successful bookmark play. */
972831/* ------------------------------------------------------------------------*/
···995854 return false;
996855}
997856998998-static const char* skip_token(const char* s)
857857+/* GCC 7 and up complain about the snprintf in create_bookmark() when
858858+ compiled with -D_FORTIFY_SOURCE or -Wformat-truncation
859859+ This is a false positive, so disable it here only */
860860+#if __GNUC__ >= 7
861861+#pragma GCC diagnostic push
862862+#pragma GCC diagnostic ignored "-Wformat-truncation"
863863+#endif
864864+/* ----------------------------------------------------------------------- */
865865+/* This function takes the system resume data and formats it into a valid */
866866+/* bookmark. */
867867+/* Returns not NULL on successful bookmark format. */
868868+/* ----------------------------------------------------------------------- */
869869+static char* create_bookmark(void)
999870{
10001000- while (*s && *s != ';')
10011001- {
10021002- s++;
10031003- }
871871+ int resume_index = 0;
872872+ char *file;
100487310051005- if (*s)
10061006- {
10071007- s++;
10081008- }
874874+ if (!bookmark_is_bookmarkable_state())
875875+ return NULL; /* something didn't happen correctly, do nothing */
876876+877877+ /* grab the currently playing track */
878878+ struct mp3entry *id3 = audio_current_track();
879879+ if(!id3)
880880+ return NULL;
881881+882882+ /* Get some basic resume information */
883883+ /* queue_resume and queue_resume_index are not used and can be ignored.*/
884884+ playlist_get_resume_info(&resume_index);
100988510101010- return s;
886886+ /* Get the currently playing file minus the path */
887887+ /* This is used when displaying the available bookmarks */
888888+ file = strrchr(id3->path,'/');
889889+ if(NULL == file)
890890+ return NULL;
891891+892892+ /* create the bookmark */
893893+ playlist_get_name(NULL, global_temp_buffer, sizeof(global_temp_buffer));
894894+ if (global_temp_buffer[strlen(global_temp_buffer) - 1] != '/')
895895+ file = id3->path;
896896+ else file++;
897897+ snprintf(global_bookmark, sizeof(global_bookmark),
898898+ /* new optional bookmark token descriptors should be inserted
899899+ just before the "%s;%s" in this line... */
900900+#if defined(HAVE_PITCHCONTROL)
901901+ ">%d;%d;%ld;%d;%ld;%d;%d;%ld;%ld;%s;%s",
902902+#else
903903+ ">%d;%d;%ld;%d;%ld;%d;%d;%s;%s",
904904+#endif
905905+ /* ... their flags should go here ... */
906906+#if defined(HAVE_PITCHCONTROL)
907907+ BM_PITCH | BM_SPEED,
908908+#else
909909+ 0,
910910+#endif
911911+ resume_index,
912912+ id3->offset,
913913+ playlist_get_seed(NULL),
914914+ id3->elapsed,
915915+ global_settings.repeat_mode,
916916+ global_settings.playlist_shuffle,
917917+ /* ...and their values should go here */
918918+#if defined(HAVE_PITCHCONTROL)
919919+ (long)sound_get_pitch(),
920920+ (long)dsp_get_timestretch(),
921921+#endif
922922+ /* more mandatory tokens */
923923+ global_temp_buffer,
924924+ file);
925925+926926+ /* checking to see if the bookmark is valid */
927927+ if (parse_bookmark(global_bookmark, false, false))
928928+ return global_bookmark;
929929+ else
930930+ return NULL;
1011931}
932932+#if __GNUC__ >= 7
933933+#pragma GCC diagnostic pop /* -Wformat-truncation */
934934+#endif
101293510131013-static const char* int_token(const char* s, int* dest)
936936+/*-------------------------------------------------------------------------*/
937937+/* PUBLIC INTERFACE -------------------------------------------------------*/
938938+/*-------------------------------------------------------------------------*/
939939+940940+941941+/* ----------------------------------------------------------------------- */
942942+/* This is an interface function from the context menu. */
943943+/* Returns true on successful bookmark creation. */
944944+/* ----------------------------------------------------------------------- */
945945+bool bookmark_create_menu(void)
1014946{
10151015- *dest = atoi(s);
10161016- return skip_token(s);
947947+ return write_bookmark(true, create_bookmark());
1017948}
101894910191019-static const char* long_token(const char* s, long* dest)
950950+/* ----------------------------------------------------------------------- */
951951+/* This function acts as the load interface from the context menu. */
952952+/* This function determines the bookmark file name and then loads that file*/
953953+/* for the user. The user can then select or delete previous bookmarks. */
954954+/* This function returns BOOKMARK_SUCCESS on the selection of a track to */
955955+/* resume, BOOKMARK_FAIL if the menu is exited without a selection and */
956956+/* BOOKMARK_USB_CONNECTED if the menu is forced to exit due to a USB */
957957+/* connection. */
958958+/* ----------------------------------------------------------------------- */
959959+int bookmark_load_menu(void)
1020960{
10211021- *dest = atoi(s); /* Should be atol, but we don't have it. */
10221022- return skip_token(s);
961961+ char* bookmark;
962962+ int ret = BOOKMARK_FAIL;
963963+964964+ push_current_activity(ACTIVITY_BOOKMARKSLIST);
965965+966966+ char* name = playlist_get_name(NULL, global_temp_buffer,
967967+ sizeof(global_temp_buffer));
968968+ if (generate_bookmark_file_name(name))
969969+ {
970970+ ret = select_bookmark(global_bookmark_file_name, false, &bookmark);
971971+ if (bookmark != NULL)
972972+ {
973973+ ret = play_bookmark(bookmark) ? BOOKMARK_SUCCESS : BOOKMARK_FAIL;
974974+ }
975975+ }
976976+977977+ pop_current_activity();
978978+ return ret;
1023979}
10249801025981/* ----------------------------------------------------------------------- */
10261026-/* This function takes a bookmark and parses it. This function also */
10271027-/* validates the bookmark. The parse_filenames flag indicates whether */
10281028-/* the filename tokens are to be extracted. */
10291029-/* Returns true on successful bookmark parse. */
982982+/* Gives the user a list of the Most Recent Bookmarks. This is an */
983983+/* interface function */
984984+/* Returns true on the successful selection of a recent bookmark. */
1030985/* ----------------------------------------------------------------------- */
10311031-static bool parse_bookmark(const char *bookmark, const bool parse_filenames, const bool strip_dir)
986986+bool bookmark_mrb_load()
1032987{
10331033- const char* s = bookmark;
10341034- const char* end;
988988+ char* bookmark;
989989+ bool ret = false;
103599010361036-#define GET_INT_TOKEN(var) s = int_token(s, &var)
10371037-#define GET_LONG_TOKEN(var) s = long_token(s, &var)
10381038-#define GET_BOOL_TOKEN(var) var = (atoi(s)!=0); s = skip_token(s)
991991+ push_current_activity(ACTIVITY_BOOKMARKSLIST);
992992+ select_bookmark(RECENT_BOOKMARK_FILE, false, &bookmark);
993993+ if (bookmark != NULL)
994994+ {
995995+ ret = play_bookmark(bookmark);
996996+ }
103999710401040- /* if new format bookmark, extract the optional content flags,
10411041- otherwise treat as an original format bookmark */
10421042- int opt_flags = 0;
10431043- bool new_format = (strchr(s, '>') == s);
10441044- if (new_format)
998998+ pop_current_activity();
999999+ return ret;
10001000+}
10011001+10021002+/* ----------------------------------------------------------------------- */
10031003+/* This function handles an autobookmark creation. This is an interface */
10041004+/* function. */
10051005+/* Returns true on successful bookmark creation. */
10061006+/* ----------------------------------------------------------------------- */
10071007+bool bookmark_autobookmark(bool prompt_ok)
10081008+{
10091009+ char* bookmark;
10101010+ bool update;
10111011+10121012+ if (!bookmark_is_bookmarkable_state())
10131013+ return false;
10141014+10151015+ audio_pause(); /* first pause playback */
10161016+ update = (global_settings.autoupdatebookmark && bookmark_exists());
10171017+ bookmark = create_bookmark();
10181018+10191019+ if (update)
10201020+ return write_bookmark(true, bookmark);
10211021+10221022+ switch (global_settings.autocreatebookmark)
10451023 {
10461046- s++;
10471047- GET_INT_TOKEN(opt_flags);
10241024+ case BOOKMARK_YES:
10251025+ return write_bookmark(true, bookmark);
10261026+10271027+ case BOOKMARK_NO:
10281028+ return false;
10291029+10301030+ case BOOKMARK_RECENT_ONLY_YES:
10311031+ return write_bookmark(false, bookmark);
10481032 }
10331033+ const char *lines[]={ID2P(LANG_AUTO_BOOKMARK_QUERY)};
10341034+ const struct text_message message={lines, 1};
1049103510501050- /* extract all original bookmark tokens */
10511051- GET_INT_TOKEN(bm.resume_index);
10521052- GET_LONG_TOKEN(bm.resume_offset);
10531053- GET_INT_TOKEN(bm.resume_seed);
10541054- if (!new_format) /* skip deprecated token */
10551055- s = skip_token(s);
10561056- GET_LONG_TOKEN(bm.resume_time);
10571057- GET_INT_TOKEN(bm.repeat_mode);
10581058- GET_BOOL_TOKEN(bm.shuffle);
10361036+ if(prompt_ok && gui_syncyesno_run(&message, NULL, NULL)==YESNO_YES)
10371037+ {
10381038+ if (global_settings.autocreatebookmark == BOOKMARK_RECENT_ONLY_ASK)
10391039+ return write_bookmark(false, bookmark);
10401040+ else
10411041+ return write_bookmark(true, bookmark);
10421042+ }
10431043+ return false;
10441044+}
1059104510601060- /* extract all optional bookmark tokens */
10611061- if (opt_flags & BM_PITCH)
10621062- GET_INT_TOKEN(bm.pitch);
10631063- if (opt_flags & BM_SPEED)
10641064- GET_INT_TOKEN(bm.speed);
10461046+/* ----------------------------------------------------------------------- */
10471047+/* This function will determine if an autoload is necessary. This is an */
10481048+/* interface function. */
10491049+/* Returns */
10501050+/* BOOKMARK_DO_RESUME on bookmark load or bookmark selection. */
10511051+/* BOOKMARK_DONT_RESUME if we're not going to resume */
10521052+/* BOOKMARK_CANCEL if user canceled */
10531053+/* ------------------------------------------------------------------------*/
10541054+int bookmark_autoload(const char* file)
10551055+{
10561056+ char* bookmark;
10571057+10581058+ if(global_settings.autoloadbookmark == BOOKMARK_NO)
10591059+ return BOOKMARK_DONT_RESUME;
1065106010661066- if (*s == 0)
10611061+ /*Checking to see if a bookmark file exists.*/
10621062+ if(!generate_bookmark_file_name(file))
10671063 {
10681068- return false;
10641064+ return BOOKMARK_DONT_RESUME;
10691065 }
1070106610711071- end = strchr(s, ';');
10671067+ if(!file_exists(global_bookmark_file_name))
10681068+ return BOOKMARK_DONT_RESUME;
1072106910731073- /* extract file names */
10741074- if (parse_filenames)
10701070+ if(global_settings.autoloadbookmark == BOOKMARK_YES)
10751071 {
10761076- size_t len = (end == NULL) ? strlen(s) : (size_t) (end - s);
10771077- len = MIN(TEMP_BUF_SIZE - 1, len);
10781078- strmemccpy(global_temp_buffer, s, len + 1);
10721072+ return bookmark_load(global_bookmark_file_name, true) ? BOOKMARK_DO_RESUME :
10731073+ BOOKMARK_DONT_RESUME;
10741074+ }
10751075+ else
10761076+ {
10771077+ int ret = select_bookmark(global_bookmark_file_name, true, &bookmark);
1079107810801080- if (end != NULL)
10791079+ if (bookmark != NULL)
10811080 {
10821082- end++;
10831083- if (strip_dir)
10811081+ if (!play_bookmark(bookmark))
10841082 {
10851085- s = strrchr(end, '/');
10861086- if (s)
10871087- {
10881088- end = s;
10891089- end++;
10901090- }
10831083+ /* Selected bookmark not found. */
10841084+ splash(HZ*2, ID2P(LANG_NOTHING_TO_RESUME));
10911085 }
10921092- strmemccpy(global_filename, end, MAX_PATH);
10861086+10871087+ /* Act as if autoload was done even if it failed, since the
10881088+ * user did make an active selection.
10891089+ */
10901090+ return BOOKMARK_DO_RESUME;
10931091 }
10941094- }
1095109210961096- return true;
10931093+ return ret != BOOKMARK_SUCCESS ? BOOKMARK_CANCEL : BOOKMARK_DONT_RESUME;
10941094+ }
10971095}
1098109610991097/* ----------------------------------------------------------------------- */
11001100-/* This function is used by multiple functions and is used to generate a */
11011101-/* bookmark named based off of the input. */
11021102-/* Changing this function could result in how the bookmarks are stored. */
11031103-/* it would be here that the centralized/decentralized bookmark code */
11041104-/* could be placed. */
11051105-/* Returns true if the file name is generated, false if it was too long */
11061106-/* ----------------------------------------------------------------------- */
11071107-static bool generate_bookmark_file_name(const char *in)
10981098+/* This function loads the bookmark information into the resume memory. */
10991099+/* This is an interface function. */
11001100+/* Returns true on successful bookmark load. */
11011101+/* ------------------------------------------------------------------------*/
11021102+bool bookmark_load(const char* file, bool autoload)
11081103{
11091109- /* if this is a root dir MP3, rename the bookmark file root_dir.bmark */
11101110- /* otherwise, name it based on the in variable */
11111111- if (!strcmp("/", in))
11121112- strcpy(global_bookmark_file_name, "/root_dir.bmark");
11041104+ int fd;
11051105+ char* bookmark = NULL;
11061106+11071107+ if(autoload)
11081108+ {
11091109+ fd = open(file, O_RDONLY);
11101110+ if(fd >= 0)
11111111+ {
11121112+ if(read_line(fd, global_read_buffer, sizeof(global_read_buffer)) > 0)
11131113+ bookmark=global_read_buffer;
11141114+ close(fd);
11151115+ }
11161116+ }
11131117 else
11141118 {
11151115-#ifdef HAVE_MULTIVOLUME
11161116- /* The "root" of an extra volume need special handling too. */
11171117- const char *filename;
11181118- path_strip_volume(in, &filename, true);
11191119- bool volume_root = *filename == '\0';
11201120-#endif
11211121- size_t len = strlcpy(global_bookmark_file_name, in, MAX_PATH);
11221122- if(len >= MAX_PATH)
11231123- return false;
11191119+ /* This is not an auto-load, so list the bookmarks */
11201120+ select_bookmark(file, false, &bookmark);
11211121+ }
1124112211251125- if(global_bookmark_file_name[len-1] == '/') {
11261126- global_bookmark_file_name[len-1] = '\0';
11271127- len--;
11281128- }
11291129-11301130-#ifdef HAVE_MULTIVOLUME
11311131- if (volume_root)
11321132- len = strlcat(global_bookmark_file_name, "/volume_dir.bmark", MAX_PATH);
11331133- else
11341134-#endif
11351135- len = strlcat(global_bookmark_file_name, ".bmark", MAX_PATH);
11231123+ if (bookmark != NULL)
11241124+ {
11251125+ if (!play_bookmark(bookmark))
11261126+ {
11271127+ /* Selected bookmark not found. */
11281128+ if (!autoload)
11291129+ {
11301130+ splash(HZ*2, ID2P(LANG_NOTHING_TO_RESUME));
11311131+ }
1136113211371137- if(len >= MAX_PATH)
11381133 return false;
11341134+ }
11391135 }
1140113611411137 return true;
···1179117511801176 return true;
11811177}
11821182-