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 * $Id$
9 *
10 * Copyright (C) 2023 by William Wilgus
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22/*Plugin Includes*/
23
24#include "plugin.h"
25#include "errno.h"
26
27/* Redefinitons of ANSI C functions. */
28#include "lib/wrappers.h"
29#include "lib/helper.h"
30
31static void thread_create(void);
32static void thread(void); /* the thread running commit*/
33static void allocate_tempbuf(void);
34static void free_tempbuf(void);
35static bool do_timed_yield(void);
36static void _log(const char *fmt, ...);
37static bool logdump(bool append);
38/*Aliases*/
39#if 0
40#ifdef ROCKBOX_HAS_LOGF
41 #define logf rb->logf
42#else
43 #define logf(...) {}
44#endif
45#endif
46
47#define logf _log
48#define sleep rb->sleep
49#define qsort rb->qsort
50
51#define write(x,y,z) rb->write(x,y,z)
52#define ftruncate rb->ftruncate
53#define remove rb->remove
54#define rename rb->rename
55
56#define vsnprintf rb->vsnprintf
57#define mkdir rb->mkdir
58#define filesize rb->filesize
59
60#define strtok_r rb->strtok_r
61#define strncasecmp rb->strncasecmp
62#define strcasecmp rb->strcasecmp
63
64#define current_tick (*rb->current_tick)
65#define crc_32(x,y,z) rb->crc_32(x,y,z)
66#define plugin_get_buffer rb->plugin_get_buffer
67
68#define MAX_LOG_SIZE 16384
69
70#define EV_EXIT MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0xFF)
71#define EV_STARTUP MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 0x01)
72
73#define BACKUP_DIRECTORY PLUGIN_APPS_DATA_DIR "/db_commit"
74
75#define RC_SUCCESS 0
76#define RC_USER_CANCEL 2
77
78enum fcpy_op_flags
79{
80 FCPY_MOVE = 0x00, /* Is a move operation (default) */
81 FCPY_COPY = 0x01, /* Is a copy operation */
82 FCPY_OVERWRITE = 0x02, /* Overwrite destination */
83 FCPY_EXDEV = 0x04, /* Actually copy/move across volumes */
84};
85
86/* communication to the worker thread */
87static struct
88{
89 int user_index;
90 bool exiting; /* signal to the thread that we want to exit */
91 bool resume;
92 unsigned int id; /* worker thread id */
93 struct event_queue queue; /* thread event queue */
94 long last_useraction_tick;
95} gThread;
96
97/* status of db and commit */
98static struct
99{
100 long last_check;
101 bool do_commit;
102 bool auto_commit;
103 bool commit_ready;
104 bool db_exists;
105 bool have_backup;
106 bool bu_exists;
107} gStatus;
108
109static unsigned char logbuffer[MAX_LOG_SIZE + 1];
110static int log_font_h = -1;
111static int logindex;
112static bool logwrap;
113static bool logenabled = true;
114
115/*Support Fns*/
116/* open but with a builtin printf for assembling the path */
117int open_pathfmt(char *buf, size_t size, int oflag, const char *pathfmt, ...)
118{
119 va_list ap;
120 va_start(ap, pathfmt);
121 vsnprintf(buf, size, pathfmt, ap);
122 va_end(ap);
123 if ((oflag & O_PATH) == O_PATH)
124 return -1;
125 int handle = open(buf, oflag, 0666);
126 //logf("Open: %s %d flag: %x", buf, handle, oflag);
127 return handle;
128}
129
130static void sleep_yield(void)
131{
132 sleep(1);
133 #undef yield
134 rb->yield();
135 #define yield sleep_yield
136}
137
138/* make sure tag can be displayed by font pf */
139static bool text_is_displayable(struct font *pf, unsigned char *src)
140{
141 ucschar_t code;
142 const unsigned char *ptr = src;
143 while(*ptr)
144 {
145 ptr = rb->utf8decode(ptr, &code);
146
147 if(!rb->font_get_bits(pf, code))
148 {
149 return false;
150 }
151 }
152 return true;
153}
154
155/* callback for each tag if false returned tag will not be added */
156bool user_check_tag(int index_type, char* build_idx_buf)
157{
158 static struct font *pf = NULL;
159 if (!pf)
160 pf = rb->font_get(rb->screens[SCREEN_MAIN]->getuifont());
161
162 if (index_type == tag_artist || index_type == tag_album ||
163 index_type == tag_genre || index_type == tag_title ||
164 index_type == tag_composer || index_type == tag_comment ||
165 index_type == tag_albumartist || index_type == tag_virt_canonicalartist)
166 {
167 /* this could be expanded with more rules / transformations */
168 if (rb->utf8length(build_idx_buf) != strlen(build_idx_buf))
169 {
170 if (!text_is_displayable(pf, build_idx_buf))
171 {
172 logf("Can't display (%d) %s", index_type, build_idx_buf);
173 }
174 }
175 }
176 return true;
177}
178
179/* undef features we don't need */
180#undef HAVE_DIRCACHE
181#undef HAVE_TC_RAMCACHE
182#undef HAVE_EEPROM_SETTINGS
183/* paste the whole tagcache.c file */
184#include "../tagcache.c"
185
186static void check_logindex(void)
187{
188 if(logindex >= MAX_LOG_SIZE)
189 {
190 logdump(true);
191 //logwrap = true;
192 logindex = 0;
193 }
194}
195
196static int log_push(void *userp, int c)
197{
198 (void)userp;
199
200 logbuffer[logindex++] = c;
201 check_logindex();
202
203 return 1;
204}
205
206/* our logf function */
207static void _log(const char *fmt, ...)
208{
209 if (!logenabled)
210 {
211 rb->splash(10, "log not enabled");
212 return;
213 }
214
215 #ifdef USB_ENABLE_SERIAL
216 int old_logindex = logindex;
217 #endif
218 va_list ap;
219
220 va_start(ap, fmt);
221
222#if (CONFIG_PLATFORM & PLATFORM_HOSTED)
223 char buf[1024];
224 vsnprintf(buf, sizeof buf, fmt, ap);
225 DEBUGF("%s\n", buf);
226 /* restart va_list otherwise the result if undefined when vuprintf is called */
227 va_end(ap);
228 va_start(ap, fmt);
229#endif
230
231 rb->vuprintf(log_push, NULL, fmt, ap);
232 va_end(ap);
233
234 /* add trailing zero */
235 log_push(NULL, '\0');
236}
237
238
239int compute_nb_lines(int w, struct font* font)
240{
241 int i, nb_lines;
242 int cur_x, delta_x;
243
244 if(logindex>= MAX_LOG_SIZE || (logindex == 0 && !logwrap))
245 return 0;
246
247 if(logwrap)
248 i = logindex;
249 else
250 i = 0;
251
252 cur_x = 0;
253 nb_lines = 0;
254
255 do {
256 if(logbuffer[i] == '\0')
257 {
258 cur_x = 0;
259 nb_lines++;
260 }
261 else
262 {
263 /* does character fit on this line ? */
264 delta_x = rb->font_get_width(font, logbuffer[i]);
265
266 if(cur_x + delta_x > w)
267 {
268 cur_x = 0;
269 nb_lines++;
270 }
271
272 /* update pointer */
273 cur_x += delta_x;
274 }
275
276 i++;
277 if(i >= MAX_LOG_SIZE)
278 i = 0;
279 } while(i != logindex);
280
281 return nb_lines;
282}
283
284static bool logdisplay(void)
285{
286
287 int w, h, i, index;
288 int fontnr;
289 int cur_x, cur_y, delta_x;
290 struct font* font;
291
292 char buf[2];
293
294 fontnr = FONT_FIRSTUSERFONT;
295 font = rb->font_get(fontnr);
296 buf[1] = '\0';
297 w = LCD_WIDTH;
298 h = LCD_HEIGHT;
299
300 if (log_font_h < 0) /* init, get the horizontal size of each line */
301 {
302 rb->font_getstringsize("A", NULL, &log_font_h, fontnr);
303 /* start at the end of the log */
304 gThread.user_index = compute_nb_lines(w, font) - h/log_font_h -1;
305 /* user_index will be number of the first line to display (warning: line!=log entry) */
306 /* if negative, will be set 0 to zero later */
307 }
308
309 rb->lcd_clear_display();
310
311 if(gThread.user_index < 0 || gThread.user_index >= MAX_LOG_SIZE)
312 gThread.user_index = 0;
313
314 if(logwrap)
315 i = logindex;
316 else
317 i = 0;
318
319 index = 0;
320 cur_x = 0;
321 cur_y = 0;
322
323 /* nothing to print ? */
324 if(logindex == 0 && !logwrap)
325 goto end_print;
326
327 do {
328 if(logbuffer[i] == '\0')
329 {
330 /* should be display a newline ? */
331 if(index >= gThread.user_index)
332 cur_y += log_font_h;
333 cur_x = 0;
334 index++;
335 }
336 else
337 {
338 /* does character fit on this line ? */
339 delta_x = rb->font_get_width(font, logbuffer[i]);
340
341 if(cur_x + delta_x > w)
342 {
343 /* should be display a newline ? */
344 if(index >= gThread.user_index)
345 cur_y += log_font_h;
346 cur_x = 0;
347 index++;
348 }
349
350 /* should we print character ? */
351 if(index >= gThread.user_index)
352 {
353 buf[0] = logbuffer[i];
354 rb->lcd_putsxy(cur_x, cur_y, buf);
355 }
356
357 /* update pointer */
358 cur_x += delta_x;
359 }
360 i++;
361 /* did we fill the screen ? */
362 if(cur_y > h - log_font_h)
363 {
364 if (TIME_AFTER(current_tick, gThread.last_useraction_tick + HZ))
365 gThread.user_index++;
366 break;
367 }
368
369 if(i >= MAX_LOG_SIZE)
370 i = 0;
371 } while(i != logindex);
372
373 end_print:
374 rb->lcd_update();
375
376 return false;
377}
378
379static bool logdump(bool append)
380{
381 int fd;
382 int flags = O_CREAT|O_WRONLY|O_TRUNC;
383 /* nothing to print ? */
384 if(!logenabled || (logindex == 0 && !logwrap))
385 {
386 /* nothing is logged just yet */
387 return false;
388 }
389 if (append)
390 {
391 flags = O_CREAT|O_WRONLY|O_APPEND;
392 }
393
394 fd = open(ROCKBOX_DIR "/db_commit_log.txt", flags, 0666);
395 logenabled = false;
396 if(-1 != fd) {
397 int i;
398
399 if(logwrap)
400 i = logindex;
401 else
402 i = 0;
403
404 do {
405 if(logbuffer[i]=='\0')
406 rb->fdprintf(fd, "\n");
407 else
408 rb->fdprintf(fd, "%c", logbuffer[i]);
409
410 i++;
411 if(i >= MAX_LOG_SIZE)
412 i = 0;
413 } while(i != logindex);
414
415 close(fd);
416 }
417
418 logenabled = true;
419
420 return false;
421}
422
423static void allocate_tempbuf(void)
424{
425 tempbuf_size = 0;
426 tempbuf = rb->plugin_get_audio_buffer(&tempbuf_size);
427 tempbuf_size &= ~0x03;
428
429}
430
431static void free_tempbuf(void)
432{
433 if (tempbuf_size == 0)
434 return ;
435
436 rb->plugin_release_audio_buffer();
437 tempbuf = NULL;
438 tempbuf_size = 0;
439}
440
441static bool do_timed_yield(void)
442{
443 /* Sorting can lock up for quite a while, so yield occasionally */
444 static long wakeup_tick = 0;
445 if (TIME_AFTER(current_tick, wakeup_tick))
446 {
447 yield();
448
449 wakeup_tick = current_tick + (HZ/5);
450 return true;
451 }
452 return false;
453}
454
455/* copy/move a file */
456static int fcpy(const char *src, const char *target,
457 unsigned int flags, bool (*poll_cancel)(const char *))
458{
459 int rc = -1;
460
461 while (!(flags & (FCPY_COPY | FCPY_EXDEV))) {
462 if ((flags & FCPY_OVERWRITE) || !file_exists(target)) {
463 /* Rename and possibly overwrite the file */
464 if (poll_cancel && poll_cancel(src)) {
465 rc = RC_USER_CANCEL;
466 } else {
467 rc = rename(src, target);
468 }
469
470 #ifdef HAVE_MULTIVOLUME
471 if (rc < 0 && errno == EXDEV) {
472 /* Failed because cross volume rename doesn't work; force
473 a move instead */
474 flags |= FCPY_EXDEV;
475 break;
476 }
477 #endif /* HAVE_MULTIVOLUME */
478 }
479
480 return rc;
481 }
482
483 /* See if we can get the plugin buffer for the file copy buffer */
484 size_t buffersize;
485 char *buffer = (char *) plugin_get_buffer(&buffersize);
486 if (buffer == NULL || buffersize < 512) {
487 /* Not large enough, try for a disk sector worth of stack
488 instead */
489 buffersize = 512;
490 buffer = (char *)alloca(buffersize);
491 }
492
493 if (buffer == NULL) {
494 return -1;
495 }
496
497 buffersize &= ~0x1ff; /* Round buffer size to multiple of sector
498 size */
499
500 int src_fd = open(src, O_RDONLY);
501 if (src_fd >= 0) {
502 int oflag = O_WRONLY|O_CREAT;
503
504 if (!(flags & FCPY_OVERWRITE)) {
505 oflag |= O_EXCL;
506 }
507
508 int target_fd = open(target, oflag, 0666);
509 if (target_fd >= 0) {
510 off_t total_size = 0;
511 off_t next_cancel_test = 0; /* No excessive button polling */
512
513 rc = RC_SUCCESS;
514
515 while (rc == RC_SUCCESS) {
516 if (total_size >= next_cancel_test) {
517 next_cancel_test = total_size + 0x10000;
518 if (poll_cancel && poll_cancel(src)) {
519 rc = RC_USER_CANCEL;
520 break;
521 }
522 }
523
524 ssize_t bytesread = read(src_fd, buffer, buffersize);
525 if (bytesread <= 0) {
526 if (bytesread < 0) {
527 rc = -1;
528 }
529 /* else eof on buffer boundary; nothing to write */
530 break;
531 }
532
533 ssize_t byteswritten = write(target_fd, buffer, bytesread);
534 if (byteswritten < bytesread) {
535 /* Some I/O error */
536 rc = -1;
537 break;
538 }
539
540 total_size += byteswritten;
541
542 if (bytesread < (ssize_t)buffersize) {
543 /* EOF with trailing bytes */
544 break;
545 }
546 }
547
548 if (rc == RC_SUCCESS) {
549 /* If overwriting, set the correct length if original was
550 longer */
551 rc = ftruncate(target_fd, total_size);
552 }
553
554 close(target_fd);
555
556 if (rc != RC_SUCCESS) {
557 /* Copy failed. Cleanup. */
558 remove(target);
559 }
560 }
561
562 close(src_fd);
563 }
564
565 if (rc == RC_SUCCESS && !(flags & FCPY_COPY)) {
566 /* Remove the source file */
567 rc = remove(src);
568 }
569
570 return rc;
571}
572
573static bool backup_restore_tagcache(bool backup)
574{
575 struct master_header tcmh;
576 char path[MAX_PATH];
577 char bu_path[MAX_PATH];
578 int fd;
579 int rc;
580
581 if (backup)
582 {
583 if (!rb->dir_exists(BACKUP_DIRECTORY))
584 mkdir(BACKUP_DIRECTORY);
585 snprintf(path, sizeof(path), "%s/"TAGCACHE_FILE_MASTER, tc_stat.db_path);
586 snprintf(bu_path, sizeof(bu_path), "%s/"TAGCACHE_FILE_MASTER, BACKUP_DIRECTORY);
587 }
588 else
589 {
590 if (!rb->dir_exists(BACKUP_DIRECTORY))
591 return false;
592 snprintf(path, sizeof(path), "%s/"TAGCACHE_FILE_MASTER, BACKUP_DIRECTORY);
593 snprintf(bu_path, sizeof(bu_path), "%s/"TAGCACHE_FILE_MASTER, tc_stat.db_path);
594 }
595
596 fd = open(path, O_RDONLY, 0666);
597
598 if (fd >= 0)
599 {
600 rc = read(fd, &tcmh, sizeof(struct master_header));
601 close(fd);
602 if (rc != sizeof(struct master_header))
603 {
604 logf("master file read failed");
605 return false;
606 }
607 int entries = tcmh.tch.entry_count;
608
609 logf("master file %d entries", entries);
610 if (backup)
611 logf("backup db to %s", BACKUP_DIRECTORY);
612 else
613 logf("restore db to %s", tc_stat.db_path);
614
615 if (entries > 0)
616 {
617 logf("%s", path);
618 fcpy(path, bu_path, FCPY_COPY|FCPY_OVERWRITE, NULL);
619
620 for (int i = 0; i < TAG_COUNT; i++)
621 {
622 if (TAGCACHE_IS_NUMERIC(i))
623 continue;
624 snprintf(path, sizeof(path),
625 "%s/"TAGCACHE_FILE_INDEX, tc_stat.db_path, i);
626
627 snprintf(bu_path, sizeof(bu_path),
628 "%s/"TAGCACHE_FILE_INDEX, BACKUP_DIRECTORY, i);
629 /* Note: above we swapped paths in the snprintf call here we swap variables */
630 if (backup)
631 {
632 logf("%s", path);
633 if (fcpy(path, bu_path, FCPY_COPY|FCPY_OVERWRITE, NULL) < 0)
634 goto failed;
635 gStatus.have_backup = true;
636 }
637 else
638 {
639 logf("%s", bu_path);
640 if (fcpy(bu_path, path, FCPY_COPY|FCPY_OVERWRITE, NULL) < 0)
641 goto failed;
642 }
643 }
644 }
645 return true;
646 }
647failed:
648 if (backup)
649 {
650 logf("failed backup");
651 }
652
653 return false;
654}
655
656/* asks the user if they wish to quit */
657static bool confirm_quit(void)
658{
659 const struct text_message prompt =
660 { (const char*[]) {"Are you sure?", "This will abort commit."}, 2};
661 enum yesno_res response = rb->gui_syncyesno_run(&prompt, NULL, NULL);
662 return (response == YESNO_YES);
663}
664
665/* asks the user if they wish to backup/restore */
666static bool prompt_backup_restore(bool backup)
667{
668 const struct text_message bu_prompt = { (const char*[]) {"Backup database?"}, 1};
669 const struct text_message re_prompt =
670 { (const char*[]) {"Error committing,", "Restore database?"}, 2};
671 enum yesno_res response =
672 rb->gui_syncyesno_run(backup ? &bu_prompt:&re_prompt, NULL, NULL);
673 if(response == YESNO_YES)
674 return backup_restore_tagcache(backup);
675 return true;
676}
677
678static const char* list_get_name_cb(int selected_item, void* data,
679 char* buf, size_t buf_len)
680{
681 (void) data;
682 (void) buf;
683 (void) buf_len;
684
685 /* buf supplied isn't used so lets use it for a filename buffer */
686 if (TIME_AFTER(current_tick, gStatus.last_check))
687 {
688 snprintf(buf, buf_len, "%s/"TAGCACHE_FILE_NOCOMMIT, tc_stat.db_path);
689 gStatus.auto_commit = !file_exists(buf);
690 snprintf(buf, buf_len, "%s/"TAGCACHE_FILE_TEMP, tc_stat.db_path);
691 gStatus.commit_ready = file_exists(buf);
692 snprintf(buf, buf_len, "%s/"TAGCACHE_FILE_MASTER, tc_stat.db_path);
693 gStatus.db_exists = file_exists(buf);
694 snprintf(buf, buf_len, "%s/"TAGCACHE_FILE_MASTER, BACKUP_DIRECTORY);
695 gStatus.bu_exists = file_exists(buf);
696 gStatus.last_check = current_tick + HZ;
697 buf[0] = '\0';
698 }
699
700 switch(selected_item)
701 {
702
703 case 0: /* exit */
704 return ID2P(LANG_MENU_QUIT);
705 case 1: /*sep*/
706 return ID2P(VOICE_BLANK);
707 case 2: /*backup*/
708 if (!gStatus.db_exists)
709 return ID2P(VOICE_BLANK);
710 return "Backup";
711 case 3: /*restore*/
712 if (!gStatus.bu_exists)
713 return ID2P(VOICE_BLANK);
714 return "Restore";
715 case 4: /*sep*/
716 return ID2P(VOICE_BLANK);
717 case 5: /*auto commit*/
718 if (gStatus.auto_commit)
719 return "Disable auto commit";
720 else
721 return "Enable auto commit";
722 case 6: /*destroy*/
723 if (gStatus.db_exists)
724 return "Delete database";
725 /*fall through*/
726 case 7: /*sep*/
727 return ID2P(VOICE_BLANK);
728 case 8: /*commit*/
729 if (gStatus.commit_ready)
730 return "Commit";
731 else
732 return "Nothing to commit";
733 default:
734 return "?";
735 }
736}
737
738static int list_voice_cb(int list_index, void* data)
739{
740 #define MAX_MENU_NAME 32
741 if (!rb->global_settings->talk_menu)
742 return -1;
743 else
744 {
745 char buf[MAX_MENU_NAME];
746 const char* name = list_get_name_cb(list_index, data, buf, sizeof(buf));
747 long id = P2ID((const unsigned char *)name);
748 if(id>=0)
749 rb->talk_id(id, true);
750 else
751 rb->talk_spell(name, true);
752 }
753 return 0;
754}
755
756static int commit_menu(void)
757{
758 struct gui_synclist lists;
759 bool exit = false;
760 int button,i;
761 int selection, ret = 0;
762
763 rb->gui_synclist_init(&lists,list_get_name_cb,0, false, 1, NULL);
764 rb->gui_synclist_set_nb_items(&lists, 9);
765 rb->gui_synclist_select_item(&lists, 0);
766
767 while (!exit)
768 {
769 rb->gui_synclist_draw(&lists);
770 button = rb->get_action(CONTEXT_LIST,TIMEOUT_BLOCK);
771 if (rb->gui_synclist_do_button(&lists, &button))
772 continue;
773 selection = rb->gui_synclist_get_sel_pos(&lists);
774
775 if (button == ACTION_STD_CANCEL)
776 return 0;
777 else if (button == ACTION_STD_OK)
778 {
779 switch(selection)
780 {
781 case 0: /* exit */
782 exit = true;
783 break;
784 case 1: /*sep*/
785 continue;
786 case 2: /*backup*/
787 if (!gStatus.db_exists)
788 break;
789 if (!backup_restore_tagcache(true))
790 rb->splash(HZ, "Backup failed!");
791 else
792 {
793 rb->splash(HZ, "Backup success!");
794 gStatus.bu_exists = true;
795 }
796 break;
797 case 3: /*restore*/
798 if (!gStatus.bu_exists)
799 break;
800 if (!backup_restore_tagcache(false))
801 rb->splash(HZ, "Restore failed!");
802 else
803 rb->splash(HZ, "Restore success!");
804 break;
805 case 4: /*sep*/
806 continue;
807 case 5: /*auto commit*/
808 {
809 /* build_idx_buf supplied by tagcache.c isn't being used
810 * so lets use it for a filename buffer */
811 snprintf(build_idx_buf, build_idx_bufsz,
812 "%s/" TAGCACHE_FILE_NOCOMMIT, tc_stat.db_path);
813 if(gStatus.auto_commit)
814 close(open(build_idx_buf, O_WRONLY | O_CREAT | O_TRUNC, 0666));
815 else
816 remove(build_idx_buf);
817 gStatus.auto_commit = !file_exists(build_idx_buf);
818 break;
819 }
820 case 6: /*destroy*/
821 {
822 if (!gStatus.db_exists)
823 break;
824 const struct text_message prompt =
825 { (const char*[]) {"Are you sure?", "This will destroy database."}, 2};
826 if (rb->gui_syncyesno_run(&prompt, NULL, NULL) == YESNO_YES)
827 remove_files();
828 break;
829 }
830 case 7: /*sep*/
831 continue;
832 case 8: /*commit*/
833 if (gStatus.commit_ready)
834 {
835 gStatus.do_commit = true;
836 exit = true;
837 }
838 break;
839
840 case MENU_ATTACHED_USB:
841 return PLUGIN_USB_CONNECTED;
842 default:
843 return 0;
844 }
845 }
846 } /*while*/
847 return ret;
848}
849
850/*-----------------------------------------------------------------------------*/
851/******* plugin_start ******* */
852/*-----------------------------------------------------------------------------*/
853
854enum plugin_status plugin_start(const void* parameter)
855{
856 (void) parameter;
857
858 /* Turn off backlight timeout */
859 backlight_ignore_timeout();
860
861 memset(&gThread, 0, sizeof(gThread));
862 memset(&gStatus, 0, sizeof(gStatus));
863 memset(&tc_stat, 0, sizeof(struct tagcache_stat));
864 memset(¤t_tcmh, 0, sizeof(struct master_header));
865 filenametag_fd = -1;
866
867 strlcpy(tc_stat.db_path, rb->global_settings->tagcache_db_path, sizeof(tc_stat.db_path));
868 if (!rb->dir_exists(tc_stat.db_path)) /* on fail use default DB path */
869 strlcpy(tc_stat.db_path, ROCKBOX_DIR, sizeof(tc_stat.db_path));
870 tc_stat.initialized = true;
871 tc_stat.commit_step = -1;
872
873 logf("started");
874
875 int result = commit_menu();
876
877 if (!gStatus.do_commit)
878 {
879 /* Turn on backlight timeout (revert to settings) */
880 backlight_use_settings();
881 return PLUGIN_OK;
882 }
883
884 logdump(false);
885 allocate_tempbuf();
886 if (gStatus.db_exists && !gStatus.have_backup && !prompt_backup_restore(true))
887 rb->splash(HZ, "Backup failed!");
888 thread_create();
889 gThread.user_index = 0;
890 logdisplay(); /* get something on the screen while user waits */
891
892 while (!gThread.exiting)
893 {
894 logdisplay();
895
896 int action = rb->get_action(CONTEXT_STD, HZ/20);
897
898 switch( action )
899 {
900 case ACTION_NONE:
901 break;
902 case ACTION_STD_NEXT:
903 case ACTION_STD_NEXTREPEAT:
904 gThread.last_useraction_tick = current_tick;
905 gThread.user_index++;
906 break;
907 case ACTION_STD_PREV:
908 case ACTION_STD_PREVREPEAT:
909 gThread.last_useraction_tick = current_tick;
910 gThread.user_index--;
911 break;
912 case ACTION_STD_OK:
913 gThread.last_useraction_tick = current_tick;
914 gThread.user_index = 0;
915 break;
916 case SYS_POWEROFF:
917 case ACTION_STD_CANCEL:
918 if (tc_stat.commit_step >= 0 && !tc_stat.ready)
919 {
920 if (!confirm_quit())
921 break;
922 tc_stat.commit_delayed = true; /* Cancel the commit */
923 }
924 rb->queue_remove_from_head(&gThread.queue, EV_EXIT);
925 rb->queue_post(&gThread.queue, EV_EXIT, 0);
926 break;
927#ifdef HAVE_TOUCHSCREEN
928 case ACTION_TOUCHSCREEN:
929 {
930 gThread.last_useraction_tick = current_tick;
931 short x, y;
932 static int prev_y;
933
934 action = rb->action_get_touchscreen_press(&x, &y);
935
936 if(action & BUTTON_REL)
937 prev_y = 0;
938 else
939 {
940 if(prev_y != 0)
941 gThread.user_index += (prev_y - y) / log_font_h;
942
943 prev_y = y;
944 }
945 }
946#endif
947 default:
948 break;
949 }
950 yield();
951 }
952
953 rb->thread_wait(gThread.id);
954 rb->queue_delete(&gThread.queue);
955 free_tempbuf();
956
957 if (tc_stat.commit_delayed || !tc_stat.ready)
958 {
959 remove_files();
960 if (gStatus.bu_exists && !prompt_backup_restore(false))
961 rb->splash(HZ, "Restore failed!");
962 }
963
964 if (tc_stat.ready)
965 rb->tagcache_commit_finalize();
966
967 /* Turn on backlight timeout (revert to settings) */
968 backlight_use_settings();
969 return PLUGIN_OK;
970}
971
972/****************** main thread + helper ******************/
973static void thread(void)
974{
975 struct queue_event ev;
976 while (!gThread.exiting)
977 {
978 rb->queue_wait_w_tmo(&gThread.queue, &ev, 1);
979 switch (ev.id)
980 {
981 case SYS_USB_CONNECTED:
982 rb->usb_acknowledge(SYS_USB_CONNECTED_ACK);
983 logenabled = false;
984 break;
985 case SYS_USB_DISCONNECTED:
986 logenabled = true;
987 /*fall through*/
988 case EV_STARTUP:
989 logf("Thread Started");
990 cpu_boost(true);
991 if (commit())
992 tc_stat.ready = true;
993 cpu_boost(false);
994 logdump(true);
995 gThread.user_index++;
996 logdisplay();
997 break;
998 case EV_EXIT:
999 gThread.exiting = true;
1000 return;
1001 default:
1002 break;
1003 }
1004 yield();
1005 }
1006}
1007
1008static void thread_create(void)
1009{
1010 /* put the thread's queue in the bcast list */
1011 rb->queue_init(&gThread.queue, true);
1012 /*Note: tagcache_stack is defined in apps/tagcache.c */
1013 gThread.last_useraction_tick = current_tick;
1014 gThread.id = rb->create_thread(thread, tagcache_stack, sizeof(tagcache_stack),
1015 0, "db_commit"
1016 IF_PRIO(, PRIORITY_USER_INTERFACE)
1017 IF_COP(, CPU));
1018 rb->queue_post(&gThread.queue, EV_STARTUP, 0);
1019 yield();
1020}