A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 1020 lines 28 kB view raw
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(&current_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}