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 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License
12 * as published by the Free Software Foundation; either version 2
13 * of the License, or (at your option) any later version.
14 *
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
17 *
18 ****************************************************************************/
19
20#include "plugin.h"
21#include "lib/configfile.h"
22
23
24
25/* taken from apps/gui/wps_parser.c */
26#define WPS_DEFAULTCFG WPS_DIR "/rockbox_default.wps"
27#define RWPS_DEFAULTCFG WPS_DIR "/rockbox_default.rwps"
28
29#define CONFIG_FILENAME "theme_remove.cfg"
30#define LOG_FILENAME "/theme_remove_log.txt"
31#define RB_FONTS_CONFIG VIEWERS_DIR "/rockbox-fonts.config"
32
33enum remove_option {
34 ALWAYS_REMOVE,
35 NEVER_REMOVE,
36 REMOVE_IF_NOT_USED,
37 ASK_FOR_REMOVAL,
38 NUM_REMOVE_OPTION
39};
40
41struct remove_setting {
42 const char *name;
43 const char *prefix, *suffix;
44 char value[MAX_PATH];
45 int option;
46 int (*func)(struct remove_setting *);
47 bool used;
48};
49
50static int remove_wps(struct remove_setting *);
51static int remove_icons(struct remove_setting *setting);
52
53enum remove_settings {
54 REMOVE_FONT,
55 REMOVE_WPS,
56 REMOVE_SBS,
57#ifdef HAVE_REMOTE_LCD
58 REMOVE_RWPS,
59 REMOVE_RSBS,
60#endif
61#if LCD_DEPTH > 1
62 REMOVE_BACKDROP,
63#endif
64 REMOVE_ICON,
65 REMOVE_VICON,
66#ifdef HAVE_REMOTE_LCD
67 REMOVE_RICON,
68 REMOVE_RVICON,
69#endif
70#ifdef HAVE_LCD_COLOR
71 REMOVE_COLOURS,
72#endif
73 NUM_REMOVE_ITEMS
74};
75
76static bool create_log = true;
77static struct remove_setting remove_list[NUM_REMOVE_ITEMS] = {
78 [REMOVE_FONT] = { "font", FONT_DIR "/", ".fnt", "",
79 ASK_FOR_REMOVAL, NULL, false },
80 [REMOVE_WPS] = { "wps", WPS_DIR "/", ".wps", "",
81 REMOVE_IF_NOT_USED, remove_wps, false },
82 [REMOVE_SBS] = { "sbs", SBS_DIR "/", ".sbs", "",
83 REMOVE_IF_NOT_USED, remove_wps, false },
84#ifdef HAVE_REMOTE_LCD
85 [REMOVE_RWPS] = { "rwps", WPS_DIR "/", ".rwps", "",
86 REMOVE_IF_NOT_USED, remove_wps, false },
87 [REMOVE_RSBS] = { "rsbs", SBS_DIR "/", ".rsbs", "",
88 REMOVE_IF_NOT_USED, remove_wps, false },
89#endif
90#if LCD_DEPTH > 1
91 [REMOVE_BACKDROP] = { "backdrop", BACKDROP_DIR "/", ".bmp", "",
92 REMOVE_IF_NOT_USED, NULL, false },
93#endif
94 [REMOVE_ICON] = { "iconset", ICON_DIR "/", ".bmp", "",
95 ASK_FOR_REMOVAL, NULL, false },
96 [REMOVE_VICON] = { "viewers iconset", ICON_DIR "/", ".bmp", "",
97 ASK_FOR_REMOVAL, remove_icons, false },
98#ifdef HAVE_REMOTE_LCD
99 [REMOVE_RICON] = { "remote iconset", ICON_DIR "/", ".bmp", "",
100 ASK_FOR_REMOVAL, NULL, false },
101 [REMOVE_RVICON] = { "remote viewers iconset", ICON_DIR "/", ".bmp", "",
102 ASK_FOR_REMOVAL, NULL, false },
103#endif
104#ifdef HAVE_LCD_COLOR
105 [REMOVE_COLOURS] = { "filetype colours", THEME_DIR "/", ".colours", "",
106 ASK_FOR_REMOVAL, NULL, false },
107#endif
108};
109static char *option_names[NUM_REMOVE_OPTION] = {
110 "always", "never", "not used", "ask",
111};
112static struct configdata config[] = {
113 { TYPE_INT, 0, NUM_REMOVE_OPTION,
114 { .int_p = &remove_list[REMOVE_FONT].option },
115 "remove font", option_names },
116 { TYPE_INT, 0, NUM_REMOVE_OPTION,
117 { .int_p = &remove_list[REMOVE_WPS].option },
118 "remove wps", option_names },
119 { TYPE_INT, 0, NUM_REMOVE_OPTION,
120 { .int_p = &remove_list[REMOVE_SBS].option },
121 "remove sbs", option_names },
122#ifdef HAVE_REMOTE_LCD
123 { TYPE_INT, 0, NUM_REMOVE_OPTION,
124 { .int_p = &remove_list[REMOVE_RWPS].option },
125 "remove rwps", option_names },
126 { TYPE_INT, 0, NUM_REMOVE_OPTION,
127 { .int_p = &remove_list[REMOVE_RSBS].option },
128 "remove rsbs", option_names },
129#endif
130#if LCD_DEPTH > 1
131 { TYPE_INT, 0, NUM_REMOVE_OPTION,
132 { .int_p = &remove_list[REMOVE_BACKDROP].option },
133 "remove backdrop", option_names },
134#endif
135 { TYPE_INT, 0, NUM_REMOVE_OPTION,
136 { .int_p = &remove_list[REMOVE_ICON].option },
137 "remove iconset", option_names },
138 { TYPE_INT, 0, NUM_REMOVE_OPTION,
139 { .int_p = &remove_list[REMOVE_VICON].option },
140 "remove viconset", option_names },
141#ifdef HAVE_REMOTE_LCD
142 { TYPE_INT, 0, NUM_REMOVE_OPTION,
143 { .int_p = &remove_list[REMOVE_RICON].option },
144 "remove riconset", option_names },
145 { TYPE_INT, 0, NUM_REMOVE_OPTION,
146 { .int_p = &remove_list[REMOVE_RVICON].option },
147 "remove rviconset", option_names },
148#endif
149#ifdef HAVE_LCD_COLOR
150 { TYPE_INT, 0, NUM_REMOVE_OPTION,
151 { .int_p = &remove_list[REMOVE_COLOURS].option },
152 "remove colours", option_names },
153#endif
154 {TYPE_BOOL, 0, 1, { .bool_p = &create_log },
155 "create log", NULL},
156};
157static const int nb_config = sizeof(config)/sizeof(*config);
158static char themefile[MAX_PATH];
159static int log_fd = -1;
160
161static int show_mess(const char *text, const char *file)
162{
163 static char buf[MAX_PATH*2];
164
165 if (file)
166 rb->snprintf(buf, sizeof(buf), "%s: %s", text, file);
167 else
168 rb->snprintf(buf, sizeof(buf), "%s", text);
169
170 DEBUGF("%s\n", buf);
171 if (log_fd >= 0)
172 rb->fdprintf(log_fd, "%s\n", buf);
173
174 rb->splash(0, buf);
175 rb->sleep(HZ/4);
176
177 return 0;
178}
179
180/* set full path of file. */
181static void set_file_name(char *buf, const char*file,
182 struct remove_setting *setting)
183{
184 int len1, len2;
185 if (rb->strncasecmp(file, setting->prefix, rb->strlen(setting->prefix)))
186 rb->snprintf(buf, MAX_PATH, "%s%s", setting->prefix, file);
187 else
188 rb->strlcpy(buf, file, MAX_PATH);
189 len1 = rb->strlen(buf);
190 len2 = rb->strlen(setting->suffix);
191 if (rb->strcasecmp(buf+len1-len2, setting->suffix))
192 rb->strlcpy(&buf[len1], setting->suffix, MAX_PATH-len1);
193}
194
195/* taken from apps/onplay.c */
196/* helper function to remove a non-empty directory */
197static int remove_dir(char* dirname, int len)
198{
199 int result = 0;
200 DIR* dir;
201 int dirlen = rb->strlen(dirname);
202
203 dir = rb->opendir(dirname);
204 if (!dir)
205 return -1; /* open error */
206
207 while (true)
208 {
209 struct dirent* entry;
210 /* walk through the directory content */
211 entry = rb->readdir(dir);
212 if (!entry)
213 break;
214
215 dirname[dirlen] ='\0';
216
217 /* append name to current directory */
218 rb->snprintf(dirname+dirlen, len-dirlen, "/%s", entry->d_name);
219 struct dirinfo info = rb->dir_get_info(dir, entry);
220 if (info.attribute & ATTR_DIRECTORY)
221 {
222 /* remove a subdirectory */
223 if (!rb->strcmp((char *)entry->d_name, ".") ||
224 !rb->strcmp((char *)entry->d_name, ".."))
225 continue; /* skip these */
226
227 result = remove_dir(dirname, len); /* recursion */
228 if (result)
229 break;
230 }
231 else
232 {
233 /* remove a file */
234 result = rb->remove(dirname);
235 }
236 if (ACTION_STD_CANCEL == rb->get_action(CONTEXT_STD, TIMEOUT_NOBLOCK))
237 {
238 show_mess("Canceled", NULL);
239 result = -1;
240 break;
241 }
242 }
243 rb->closedir(dir);
244
245 if (!result)
246 { /* remove the now empty directory */
247 dirname[dirlen] = '\0'; /* terminate to original length */
248
249 result = rb->rmdir(dirname);
250 show_mess("Removed", dirname);
251 }
252
253 return result;
254}
255
256static int remove_wps(struct remove_setting *setting)
257{
258 char bmpdir[MAX_PATH];
259 char *p;
260 rb->strcpy(bmpdir, setting->value);
261 p = rb->strrchr(bmpdir, '.');
262 if (p) *p = 0;
263 if (!rb->dir_exists(bmpdir))
264 return 0;
265 return remove_dir(bmpdir, MAX_PATH);
266}
267
268static int remove_icons(struct remove_setting *setting)
269{
270 char path[MAX_PATH];
271 char *p;
272 rb->strcpy(path, setting->value);
273 p = rb->strrchr(path, '.');
274 rb->strlcpy(p, ".icons", path+MAX_PATH-p);
275
276 if (!rb->file_exists(path))
277 {
278 return 0;
279 }
280 if (rb->remove(path))
281 {
282 show_mess("Failed", path);
283 return 1;
284 }
285 show_mess("Removed", path);
286 return 0;
287}
288
289static char font_file[MAX_PATH];
290
291static bool is_deny_file(const char *file)
292{
293 const char *deny_files[] = {
294 WPS_DEFAULTCFG,
295 RWPS_DEFAULTCFG,
296 font_file,
297 NULL
298 };
299 const char **p = deny_files;
300 while ( *p )
301 {
302 if (!rb->strcmp(file, *p))
303 return true;
304 p++;
305 }
306 return false;
307}
308
309static void check_whether_used_in_setting(void)
310{
311 const char *setting_files[] = {
312 rb->global_settings->font_file,
313 rb->global_settings->wps_file,
314 rb->global_settings->sbs_file,
315#ifdef HAVE_REMOTE_LCD
316 rb->global_settings->rwps_file,
317 rb->global_settings->rsbs_file,
318#endif
319#if LCD_DEPTH > 1
320 rb->global_settings->backdrop_file,
321#endif
322 rb->global_settings->icon_file,
323 rb->global_settings->viewers_icon_file,
324#ifdef HAVE_REMOTE_LCD
325 rb->global_settings->remote_icon_file,
326 rb->global_settings->remote_viewers_icon_file,
327#endif
328#ifdef HAVE_LCD_COLOR
329 rb->global_settings->colors_file,
330#endif
331 };
332 char tempfile[MAX_PATH];
333 int i;
334 for (i=0; i<NUM_REMOVE_ITEMS; i++)
335 {
336 struct remove_setting *setting = &remove_list[i];
337 if (setting->value[0])
338 {
339 set_file_name(tempfile, setting_files[i], setting);
340 if (!rb->strcasecmp(tempfile, setting->value))
341 setting->used = true;
342 }
343 }
344}
345static void check_whether_used_in_file(const char *cfgfile)
346{
347 char line[MAX_PATH];
348 char settingfile[MAX_PATH];
349 char *p;
350 int fd;
351 char *name, *value;
352 int i;
353
354 if (!rb->strcasecmp(themefile, cfgfile))
355 return;
356 fd = rb->open(cfgfile, O_RDONLY);
357 if (fd < 0)
358 return;
359 while (rb->read_line(fd, line, sizeof(line)) > 0)
360 {
361 if (!rb->settings_parseline(line, &name, &value))
362 continue;
363 /* remove trailing spaces. */
364 p = value+rb->strlen(value)-1;
365 while (*p == ' ') *p-- = 0;
366 if (*value == 0 || !rb->strcmp(value, "-"))
367 continue;
368 for (i=0; i<NUM_REMOVE_ITEMS; i++)
369 {
370 struct remove_setting *setting = &remove_list[i];
371 if (!rb->strcmp(name, setting->name))
372 {
373 if (setting->value[0])
374 {
375 set_file_name(settingfile, value, setting);
376 if (!rb->strcasecmp(settingfile, setting->value))
377 setting->used = true;
378 }
379 break;
380 }
381 }
382 }
383 rb->close(fd);
384}
385static void check_whether_used(void)
386{
387 char cfgfile[MAX_PATH];
388 DIR *dir;
389
390 check_whether_used_in_setting();
391 /* mark font files come from rockbox-font.zip as used and don't remove
392 * them automatically as themes may depend on those fonts. */
393 if (remove_list[REMOVE_FONT].option == REMOVE_IF_NOT_USED)
394 check_whether_used_in_file(RB_FONTS_CONFIG);
395
396 dir = rb->opendir(THEME_DIR);
397 if (!dir)
398 return; /* open error */
399
400 while (true)
401 {
402 struct dirent* entry;
403 char *p;
404 int i;
405 /* walk through the directory content */
406 entry = rb->readdir(dir);
407 if (!entry)
408 break;
409 p = rb->strrchr(entry->d_name, '.');
410 if (!p || rb->strcmp(p, ".cfg"))
411 continue;
412
413 rb->snprintf(cfgfile, MAX_PATH, "%s/%s", THEME_DIR, entry->d_name);
414 check_whether_used_in_file(cfgfile);
415 /* break the loop if all files need to be checked in the theme
416 * turned out to be used. */
417 for (i = 0; i < NUM_REMOVE_ITEMS; i++)
418 {
419 struct remove_setting *setting = &remove_list[i];
420 if (setting->option == REMOVE_IF_NOT_USED)
421 {
422 if (setting->value[0] && !setting->used)
423 break;
424 }
425 }
426 if (i == NUM_REMOVE_ITEMS)
427 break;
428 }
429 rb->closedir(dir);
430}
431
432static int remove_file(struct remove_setting *setting)
433{
434 if (!rb->file_exists(setting->value))
435 {
436 show_mess("Doesn't exist", setting->value);
437 return 0;
438 }
439 if (is_deny_file(setting->value))
440 {
441 show_mess("Denied", setting->value);
442 return 0;
443 }
444 switch (setting->option)
445 {
446 case ALWAYS_REMOVE:
447 break;
448 case NEVER_REMOVE:
449 show_mess("Skipped", setting->value);
450 return 0;
451 break;
452 case REMOVE_IF_NOT_USED:
453 if (setting->used)
454 {
455 show_mess("Used", setting->value);
456 return 0;
457 }
458 break;
459 case ASK_FOR_REMOVAL:
460 default:
461 {
462 const char *message_lines[] = { "Delete?", setting->value };
463 const struct text_message text_message = { message_lines, 2 };
464 if (rb->gui_syncyesno_run(&text_message, NULL, NULL) != YESNO_YES)
465 {
466 show_mess("Skipped", setting->value);
467 return 0;
468 }
469 }
470 break;
471 }
472 if (rb->remove(setting->value))
473 {
474 show_mess("Failed", setting->value);
475 return -1;
476 }
477 if (setting->func && setting->func(setting))
478 return -1;
479 show_mess("Removed", setting->value);
480 return 1;
481}
482static int remove_theme(void)
483{
484 static char line[MAX_PATH];
485 int fd;
486 int i, num_removed = 0;
487 char *name, *value;
488 bool needs_to_check_whether_used = false;
489
490 /* initialize for safe */
491 for (i=0; i<NUM_REMOVE_ITEMS; i++)
492 remove_list[i].value[0] = 0;
493
494 /* load settings */
495 fd = rb->open(themefile, O_RDONLY);
496 if (fd < 0) return fd;
497 while (rb->read_line(fd, line, sizeof(line)) > 0)
498 {
499 if (!rb->settings_parseline(line, &name, &value))
500 continue;
501 /* remove trailing spaces. */
502 char *p = value+rb->strlen(value)-1;
503 while (*p == ' ') *p-- = 0;
504 if (*value == 0 || !rb->strcmp(value, "-"))
505 continue;
506 for (i=0; i<NUM_REMOVE_ITEMS; i++)
507 {
508 struct remove_setting *setting = &remove_list[i];
509 if (!rb->strcmp(name, setting->name))
510 {
511 set_file_name(setting->value, value, setting);
512 if(setting->option == REMOVE_IF_NOT_USED)
513 needs_to_check_whether_used = true;
514 break;
515 }
516 }
517 }
518 rb->close(fd);
519
520 if(needs_to_check_whether_used)
521 check_whether_used();
522
523 /* now remove file assosiated to the theme. */
524 for (i=0; i<NUM_REMOVE_ITEMS; i++)
525 {
526 if (remove_list[i].value[0])
527 {
528 int ret = remove_file(&remove_list[i]);
529 if (ret < 0)
530 return ret;
531 num_removed += ret;
532 }
533 }
534
535 /* remove the setting file iff it is in theme directory to protect
536 * aginst accidental removal of non theme cfg file. if the file is
537 * not in the theme directory, the file may not be a theme cfg file. */
538 if (rb->strncasecmp(themefile, THEME_DIR "/", sizeof(THEME_DIR "/")-1))
539 {
540 show_mess("Skipped", themefile);
541 }
542 else if (rb->remove(themefile))
543 {
544 show_mess("Failed", themefile);
545 return -1;
546 }
547 else
548 {
549 show_mess("Removed", themefile);
550 rb->reload_directory();
551 num_removed++;
552 }
553 return num_removed;
554}
555
556static bool option_changed = false;
557static bool option_menu(void)
558{
559 MENUITEM_STRINGLIST(option_menu, "Remove Options", NULL,
560 /* same order as remove_list */
561 "Font",
562 "WPS",
563 "Statusbar Skin",
564#ifdef HAVE_REMOTE_LCD
565 "Remote WPS",
566 "Remote Statusbar Skin",
567#endif
568#if LCD_DEPTH > 1
569 "Backdrop",
570#endif
571 "Iconset", "Viewers Iconset",
572#ifdef HAVE_REMOTE_LCD
573 "Remote Iconset", "Remote Viewers Iconset",
574#endif
575#ifdef HAVE_LCD_COLOR
576 "Filetype Colours",
577#endif
578 "Create Log File");
579 struct opt_items remove_names[] = {
580 {"Always Remove", -1}, {"Never Remove", -1},
581 {"Remove if not Used", -1}, {"Ask for Removal", -1},
582 };
583 int selected = 0, result;
584
585 while (1)
586 {
587 result = rb->do_menu(&option_menu, &selected, NULL, false);
588 if (result >= 0 && result < NUM_REMOVE_ITEMS)
589 {
590 struct remove_setting *setting = &remove_list[result];
591 int prev_option = setting->option;
592 if (rb->set_option(option_menu_[result], &setting->option, RB_INT,
593 remove_names, NUM_REMOVE_OPTION, NULL))
594 return true;
595 if (prev_option != setting->option)
596 option_changed = true;
597 }
598 else if (result == NUM_REMOVE_ITEMS)
599 {
600 bool prev_value = create_log;
601 if(rb->set_bool("Create Log File", &create_log))
602 return true;
603 if (prev_value != create_log)
604 option_changed = true;
605 }
606 else if (result == MENU_ATTACHED_USB)
607 return true;
608 else
609 return false;
610 }
611
612 return false;
613}
614
615enum plugin_status plugin_start(const void* parameter)
616{
617 static char title[64];
618 char *p;
619 MENUITEM_STRINGLIST(menu, title, NULL,
620 "Remove Theme", "Remove Options",
621 "Quit");
622 int selected = 0, ret;
623 bool exit = false;
624
625 if (!parameter)
626 return PLUGIN_ERROR;
627
628 rb->snprintf(title, sizeof(title), "Remove %s",
629 rb->strrchr(parameter, '/')+1);
630 if((p = rb->strrchr(title, '.')))
631 *p = 0;
632
633 rb->snprintf(font_file, MAX_PATH, FONT_DIR "/%s.fnt",
634 rb->global_settings->font_file);
635 rb->strlcpy(themefile, parameter, MAX_PATH);
636 if (!rb->file_exists(themefile))
637 {
638 rb->splash(HZ, "File open error!");
639 return PLUGIN_ERROR;
640 }
641 configfile_load(CONFIG_FILENAME, config, nb_config, 0);
642 while (!exit)
643 {
644 switch (rb->do_menu(&menu, &selected, NULL, false))
645 {
646 case 0:
647 if(create_log)
648 {
649 log_fd = rb->open(LOG_FILENAME, O_WRONLY|O_CREAT|O_APPEND, 0666);
650 if(log_fd >= 0)
651 rb->fdprintf(log_fd, "---- %s ----\n", title);
652 else
653 show_mess("Couldn't open log file.", NULL);
654 }
655 ret = remove_theme();
656 p = (ret >= 0? "Successfully removed!": "Remove failure");
657 show_mess(p, NULL);
658 if(log_fd >= 0)
659 {
660 rb->fdprintf(log_fd, "----------------\n");
661 rb->close(log_fd);
662 log_fd = -1;
663 }
664 rb->lcd_clear_display();
665 rb->lcd_update();
666 rb->splashf(0, "%s %s", p, "Press any key to exit.");
667 rb->button_clear_queue();
668 rb->button_get(true);
669 exit = true;
670 break;
671 case 1:
672 if (option_menu())
673 return PLUGIN_USB_CONNECTED;
674 break;
675 case 2:
676 exit = true;
677 break;
678 case MENU_ATTACHED_USB:
679 return PLUGIN_USB_CONNECTED;
680 break;
681 default:
682 break;
683 }
684 }
685 if(option_changed)
686 configfile_save(CONFIG_FILENAME, config, nb_config, 0);
687 return PLUGIN_OK;
688}