A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 646 lines 18 kB view raw
1/*************************************************************************** 2 * __________ __ ___. 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 * \/ \/ \/ \/ \/ 8 * $Id$ 9 * 10 * Copyright (C) 2006 Jonathan Gordon 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#include "config.h" 22#include "plugin.h" 23#include "file.h" 24 25 26 27static bool cancel; 28static int fd; 29static int dirs_count; 30static int lasttick; 31#define RFA_FILE ROCKBOX_DIR "/folder_advance_list.dat" 32#define RFADIR_FILE ROCKBOX_DIR "/folder_advance_dir.txt" 33#define RFA_FILE_TEXT ROCKBOX_DIR "/folder_advance_list.txt" 34#define MAX_REMOVED_DIRS 10 35 36char *buffer = NULL; 37size_t buffer_size; 38int num_replaced_dirs = 0; 39char removed_dirs[MAX_REMOVED_DIRS][MAX_PATH]; 40struct file_format { 41 int count; 42 char folder[][MAX_PATH]; 43}; 44struct file_format *list = NULL; 45 46static void update_screen(bool clear) 47{ 48 char buf[15]; 49 50 rb->snprintf(buf,sizeof(buf),"Folders: %d",dirs_count); 51 FOR_NB_SCREENS(i) 52 { 53 if(clear) 54 rb->screens[i]->clear_display(); 55 rb->screens[i]->putsxy(0,0,buf); 56 rb->screens[i]->update(); 57 } 58} 59 60static void traversedir(char* location, char* name) 61{ 62 struct dirent *entry; 63 DIR* dir; 64 char fullpath[MAX_PATH], path[MAX_PATH]; 65 bool check = false; 66 int i; 67 68 /* behave differently if we're at root to avoid 69 duplication of the initial slash later on */ 70 if (location[0] == '\0' && name[0] == '\0') { 71 rb->strcpy(fullpath, ""); 72 dir = rb->opendir("/"); 73 } else { 74 rb->snprintf(fullpath, sizeof(fullpath), "%s/%s", location, name); 75 dir = rb->opendir(fullpath); 76 } 77 if (dir) { 78 entry = rb->readdir(dir); 79 while (entry) { 80 if (cancel) 81 break; 82 /* Skip .. and . */ 83 if (entry->d_name[0] == '.') 84 { 85 if ( !rb->strcmp(entry->d_name,".") 86 || !rb->strcmp(entry->d_name,"..") 87 || !rb->strcmp(entry->d_name,".rockbox")) 88 check = false; 89 else check = true; 90 } 91 else check = true; 92 93 /* check if path is removed directory, if so dont enter it */ 94 rb->snprintf(path, MAX_PATH, "%s/%s", fullpath, entry->d_name); 95 while(path[0] == '/') 96 rb->strlcpy(path, path + 1, sizeof(path)); 97 for(i = 0; i < num_replaced_dirs; i++) 98 { 99 if(!rb->strcmp(path, removed_dirs[i])) 100 { 101 check = false; 102 break; 103 } 104 } 105 106 if (check) 107 { 108 struct dirinfo info = rb->dir_get_info(dir, entry); 109 if (info.attribute & ATTR_DIRECTORY) { 110 char *start; 111 dirs_count++; 112 rb->snprintf(path,MAX_PATH,"%s/%s",fullpath,entry->d_name); 113 start = &path[rb->strlen(path)]; 114 rb->memset(start,0,&path[MAX_PATH-1]-start); 115 rb->write(fd,path,MAX_PATH); 116 traversedir(fullpath, entry->d_name); 117 } 118 } 119 if (*rb->current_tick - lasttick > (HZ/2)) { 120 update_screen(false); 121 lasttick = *rb->current_tick; 122 if (rb->action_userabort(TIMEOUT_NOBLOCK)) 123 { 124 cancel = true; 125 break; 126 } 127 } 128 129 entry = rb->readdir(dir); 130 } 131 rb->closedir(dir); 132 } 133} 134 135static bool custom_dir(void) 136{ 137 DIR* dir_check; 138 char *starts, line[MAX_PATH], formatted_line[MAX_PATH]; 139 static int fd2; 140 char buf[11]; 141 int i, errors = 0; 142 143 /* populate removed dirs array */ 144 if((fd2 = rb->open(RFADIR_FILE,O_RDONLY)) >= 0) 145 { 146 while ((rb->read_line(fd2, line, MAX_PATH - 1)) > 0) 147 { 148 if ((line[0] == '-') && (line[1] == '/') && 149 (num_replaced_dirs < MAX_REMOVED_DIRS)) 150 { 151 num_replaced_dirs ++; 152 rb->strlcpy(removed_dirs[num_replaced_dirs - 1], line + 2, 153 sizeof(line)); 154 } 155 } 156 rb->close(fd2); 157 } 158 159 if((fd2 = rb->open(RFADIR_FILE,O_RDONLY)) >= 0) 160 { 161 while ((rb->read_line(fd2, line, MAX_PATH - 1)) > 0) 162 { 163 /* blank lines and removed dirs ignored */ 164 if (rb->strlen(line) && ((line[0] != '-') || (line[1] != '/'))) 165 { 166 /* remove preceeding '/'s from the line */ 167 while(line[0] == '/') 168 rb->strlcpy(line, line + 1, sizeof(line)); 169 170 rb->snprintf(formatted_line, MAX_PATH, "/%s", line); 171 172 dir_check = rb->opendir(formatted_line); 173 174 if (dir_check) 175 { 176 rb->closedir(dir_check); 177 starts = &formatted_line[rb->strlen(formatted_line)]; 178 rb->memset(starts, 0, &formatted_line[MAX_PATH-1]-starts); 179 bool write_line = true; 180 181 for(i = 0; i < num_replaced_dirs; i++) 182 { 183 if(!rb->strcmp(line, removed_dirs[i])) 184 { 185 write_line = false; 186 break; 187 } 188 } 189 190 if(write_line) 191 { 192 dirs_count++; 193 rb->write(fd, formatted_line, MAX_PATH); 194 } 195 196 traversedir("", line); 197 } 198 else 199 { 200 errors ++; 201 rb->snprintf(buf,sizeof(buf),"Not found:"); 202 FOR_NB_SCREENS(i) 203 { 204 rb->screens[i]->puts(0,0,buf); 205 rb->screens[i]->puts(0, errors, line); 206 } 207 update_screen(false); 208 } 209 } 210 } 211 rb->close(fd2); 212 if(errors) 213 /* Press button to continue */ 214 rb->get_action(CONTEXT_STD, TIMEOUT_BLOCK); 215 } 216 else 217 return false; 218 return true; 219} 220 221static void generate(void) 222{ 223 dirs_count = 0; 224 cancel = false; 225 fd = rb->open(RFA_FILE,O_CREAT|O_WRONLY, 0666); 226 rb->write(fd,&dirs_count,sizeof(int)); 227 if (fd < 0) 228 { 229 rb->splashf(HZ, "Couldnt open %s", RFA_FILE); 230 return; 231 } 232 update_screen(true); 233 lasttick = *rb->current_tick; 234 235 if(!custom_dir()) 236 traversedir("", ""); 237 238 rb->lseek(fd,0,SEEK_SET); 239 rb->write(fd,&dirs_count,sizeof(int)); 240 rb->close(fd); 241 rb->splash(HZ, "Done"); 242} 243 244static const char* list_get_name_cb(int selected_item, void* data, 245 char* buf, size_t buf_len) 246{ 247 (void)data; 248 rb->strlcpy(buf, list->folder[selected_item], buf_len); 249 return buf; 250} 251 252static int load_list(void) 253{ 254 int myfd = rb->open(RFA_FILE,O_RDONLY); 255 if (myfd < 0) 256 return -1; 257 buffer = rb->plugin_get_audio_buffer(&buffer_size); 258 if (!buffer) 259 { 260 return -2; 261 } 262 263 rb->read(myfd,buffer,buffer_size); 264 rb->close(myfd); 265 list = (struct file_format *)buffer; 266 267 return 0; 268} 269 270static int save_list(void) 271{ 272 int myfd = rb->creat(RFA_FILE, 0666); 273 if (myfd < 0) 274 { 275 rb->splash(HZ, "Could Not Open " RFA_FILE); 276 return -1; 277 } 278 int dirs_count = 0, i = 0; 279 rb->write(myfd,&dirs_count,sizeof(int)); 280 for ( ;i<list->count;i++) 281 { 282 if (list->folder[i][0] != ' ') 283 { 284 dirs_count++; 285 rb->write(myfd,list->folder[i],MAX_PATH); 286 } 287 } 288 rb->lseek(myfd,0,SEEK_SET); 289 rb->write(myfd,&dirs_count,sizeof(int)); 290 rb->close(myfd); 291 292 return 1; 293} 294 295static int edit_list(void) 296{ 297 struct gui_synclist lists; 298 bool exit = false; 299 int button,i; 300 int selection, ret = 0; 301 302 /* load the dat file if not already done */ 303 if ((list == NULL || list->count == 0) && (i = load_list()) != 0) 304 { 305 rb->splashf(HZ*2, "Could not load %s, rv = %d", RFA_FILE, i); 306 return -1; 307 } 308 309 dirs_count = list->count; 310 311 rb->gui_synclist_init(&lists,list_get_name_cb,0, false, 1, NULL); 312 rb->gui_synclist_set_nb_items(&lists,list->count); 313 rb->gui_synclist_select_item(&lists, 0); 314 315 while (!exit) 316 { 317 rb->gui_synclist_draw(&lists); 318 button = rb->get_action(CONTEXT_LIST,TIMEOUT_BLOCK); 319 if (rb->gui_synclist_do_button(&lists, &button)) 320 continue; 321 selection = rb->gui_synclist_get_sel_pos(&lists); 322 switch (button) 323 { 324 case ACTION_STD_OK: 325 list->folder[selection][0] = ' '; 326 list->folder[selection][1] = '\0'; 327 break; 328 case ACTION_STD_CONTEXT: 329 { 330 int len; 331 MENUITEM_STRINGLIST(menu, "Remove Menu", NULL, 332 "Remove Folder", "Remove Folder Tree"); 333 334 switch (rb->do_menu(&menu, NULL, NULL, false)) 335 { 336 case 0: 337 list->folder[selection][0] = ' '; 338 list->folder[selection][1] = '\0'; 339 break; 340 case 1: 341 { 342 char temp[MAX_PATH]; 343 rb->strcpy(temp,list->folder[selection]); 344 len = rb->strlen(temp); 345 for (i=0;i<list->count;i++) 346 { 347 if (!rb->strncmp(list->folder[i],temp,len)) 348 { 349 list->folder[i][0] = ' '; 350 list->folder[i][1] = '\0'; 351 } 352 } 353 } 354 break; 355 } 356 } 357 break; 358 case ACTION_STD_CANCEL: 359 { 360 MENUITEM_STRINGLIST(menu, "Exit Menu", NULL, 361 "Save and Exit", "Ignore Changes and Exit"); 362 363 switch (rb->do_menu(&menu, NULL, NULL, false)) 364 { 365 case 0: 366 save_list(); 367 /* fallthrough */ 368 case 1: 369 exit = true; 370 ret = -2; 371 } 372 } 373 break; 374 } 375 } 376 return ret; 377} 378 379static int export_list_to_file_text(void) 380{ 381 int i = 0; 382 /* load the dat file if not already done */ 383 if ((list == NULL || list->count == 0) && (i = load_list()) != 0) 384 { 385 rb->splashf(HZ*2, "Could not load %s, rv = %d", RFA_FILE, i); 386 return 0; 387 } 388 389 if (list->count <= 0) 390 { 391 rb->splashf(HZ*2, "no dirs in list file: %s", RFA_FILE); 392 return 0; 393 } 394 395 /* create and open the file */ 396 int myfd = rb->creat(RFA_FILE_TEXT, 0666); 397 if (myfd < 0) 398 { 399 rb->splashf(HZ*4, "failed to open: fd = %d, file = %s", 400 myfd, RFA_FILE_TEXT); 401 return -1; 402 } 403 404 /* write each directory to file */ 405 for (i = 0; i < list->count; i++) 406 { 407 if (list->folder[i][0] != ' ') 408 { 409 rb->fdprintf(myfd, "%s\n", list->folder[i]); 410 } 411 } 412 413 rb->close(myfd); 414 rb->splash(HZ, "Done"); 415 return 1; 416} 417 418static int import_list_from_file_text(void) 419{ 420 char line[MAX_PATH]; 421 422 buffer = rb->plugin_get_audio_buffer(&buffer_size); 423 if (buffer == NULL) 424 { 425 rb->splash(HZ*2, "failed to get audio buffer"); 426 return -1; 427 } 428 429 int myfd = rb->open(RFA_FILE_TEXT, O_RDONLY); 430 if (myfd < 0) 431 { 432 rb->splashf(HZ*2, "failed to open: %s", RFA_FILE_TEXT); 433 return -1; 434 } 435 436 /* set the list structure, and initialize count */ 437 list = (struct file_format *)buffer; 438 list->count = 0; 439 440 while ((rb->read_line(myfd, line, MAX_PATH - 1)) > 0) 441 { 442 /* copy the dir name, and skip the newline */ 443 int len = rb->strlen(line); 444 /* remove CRs */ 445 if (len > 0) 446 { 447 if (line[len-1] == 0x0A || line[len-1] == 0x0D) 448 line[len-1] = 0x00; 449 if (len > 1 && 450 (line[len-2] == 0x0A || line[len-2] == 0x0D)) 451 line[len-2] = 0x00; 452 } 453 454 rb->strcpy(list->folder[list->count++], line); 455 } 456 457 rb->close(myfd); 458 459 if (list->count == 0) 460 { 461 load_list(); 462 } 463 else 464 { 465 save_list(); 466 } 467 rb->splash(HZ, "Done"); 468 return list->count; 469} 470 471static int start_shuffled_play(void) 472{ 473 int *order; 474 size_t max_shuffle_size; 475 int i = 0; 476 477 /* get memory for shuffling */ 478 order=rb->plugin_get_buffer(&max_shuffle_size); 479 max_shuffle_size/=sizeof(int); 480 if (order==NULL || max_shuffle_size==0) 481 { 482 rb->splashf(HZ*2, "Not enough memory for shuffling"); 483 return 0; 484 } 485 486 /* load the dat file if not already done */ 487 if ((list == NULL || list->count == 0) && (i = load_list()) != 0) 488 { 489 rb->splashf(HZ*2, "Could not load %s, rv = %d", RFA_FILE, i); 490 return 0; 491 } 492 493 if (list->count <= 0) 494 { 495 rb->splashf(HZ*2, "no dirs in list file: %s", RFA_FILE); 496 return 0; 497 } 498 499 /* shuffle the thing */ 500 rb->srand(*rb->current_tick); 501 if(list->count>(int)max_shuffle_size) 502 { 503 rb->splashf(HZ*2, "Too many folders: %d (room for %d)", list->count,(int)max_shuffle_size); 504 return 0; 505 } 506 for(i=0;i<list->count;i++) 507 order[i]=i; 508 509 for(i = list->count - 1; i >= 0; i--) 510 { 511 /* the rand is from 0 to RAND_MAX, so adjust to our value range */ 512 int candidate = rb->rand() % (i + 1); 513 514 /* now swap the values at the 'i' and 'candidate' positions */ 515 int store = order[candidate]; 516 order[candidate] = order[i]; 517 order[i] = store; 518 } 519 520 /* We don't want whatever is playing */ 521 if (!(rb->playlist_remove_all_tracks(NULL) == 0 522 && rb->playlist_create(NULL, NULL) == 0)) 523 { 524 rb->splashf(HZ*2, "Could not clear playlist"); 525 return 0; 526 } 527 528 /* add the lot to the playlist */ 529 for (i = 0; i < list->count; i++) 530 { 531 if (list->folder[order[i]][0] != ' ') 532 { 533 rb->playlist_insert_directory(NULL,list->folder[order[i]],PLAYLIST_INSERT_LAST,false,false); 534 } 535 if (rb->action_userabort(TIMEOUT_NOBLOCK)) 536 { 537 break; 538 } 539 } 540 rb->splash(HZ, "Done"); 541 /* the core needs the audio buffer back in order to start playback. */ 542 list = NULL; 543 rb->plugin_release_audio_buffer(); 544 rb->playlist_start(0, 0, 0); 545 return 1; 546} 547 548static enum plugin_status main_menu(void) 549{ 550 bool exit = false; 551 MENUITEM_STRINGLIST(menu, "Main Menu", NULL, 552 "Generate Folder List", 553 "Edit Folder List", 554 "Export List To Textfile", 555 "Import List From Textfile", 556 "Play Shuffled", 557 "Quit"); 558 559 while (!exit) 560 { 561 switch (rb->do_menu(&menu, NULL, NULL, false)) 562 { 563 case 0: /* generate */ 564#ifdef HAVE_ADJUSTABLE_CPU_FREQ 565 rb->cpu_boost(true); 566#endif 567 generate(); 568#ifdef HAVE_ADJUSTABLE_CPU_FREQ 569 rb->cpu_boost(false); 570#endif 571#ifdef HAVE_BACKLIGHT 572#ifdef HAVE_REMOTE_LCD 573 rb->remote_backlight_on(); 574#endif 575 rb->backlight_on(); 576#endif 577 break; 578 case 1: 579#ifdef HAVE_ADJUSTABLE_CPU_FREQ 580 rb->cpu_boost(true); 581#endif 582 if (edit_list() < 0) 583 exit = true; 584#ifdef HAVE_ADJUSTABLE_CPU_FREQ 585 rb->cpu_boost(false); 586#endif 587#ifdef HAVE_BACKLIGHT 588#ifdef HAVE_REMOTE_LCD 589 rb->remote_backlight_on(); 590#endif 591 rb->backlight_on(); 592#endif 593 break; 594 case 2: /* export to textfile */ 595#ifdef HAVE_ADJUSTABLE_CPU_FREQ 596 rb->cpu_boost(true); 597#endif 598 export_list_to_file_text(); 599#ifdef HAVE_ADJUSTABLE_CPU_FREQ 600 rb->cpu_boost(false); 601#endif 602#ifdef HAVE_BACKLIGHT 603#ifdef HAVE_REMOTE_LCD 604 rb->remote_backlight_on(); 605#endif 606 rb->backlight_on(); 607#endif 608 break; 609 case 3: /* import from textfile */ 610#ifdef HAVE_ADJUSTABLE_CPU_FREQ 611 rb->cpu_boost(true); 612#endif 613 import_list_from_file_text(); 614#ifdef HAVE_ADJUSTABLE_CPU_FREQ 615 rb->cpu_boost(false); 616#endif 617#ifdef HAVE_BACKLIGHT 618#ifdef HAVE_REMOTE_LCD 619 rb->remote_backlight_on(); 620#endif 621 rb->backlight_on(); 622#endif 623 break; 624 case 4: 625 if (!start_shuffled_play()) 626 return PLUGIN_ERROR; 627 else 628 return PLUGIN_GOTO_WPS; 629 case 5: 630 return PLUGIN_OK; 631 } 632 } 633 return PLUGIN_OK; 634} 635 636enum plugin_status plugin_start(const void* parameter) 637{ 638 (void)parameter; 639#ifdef HAVE_TOUCHSCREEN 640 rb->touchscreen_set_mode(rb->global_settings->touch_mode); 641#endif 642 643 cancel = false; 644 645 return main_menu(); 646}