A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd

New GUI browser to select one (or more) folders.

The browser lets the user pick one or more directories in a convinient
GUI browser. The initial directory list is read from a string
(separated by colons) and the resulting list is written back to the same
string (again separated by colons).

Note: The work was initially done by Jonathan Gordon, however I changed
it substantially so I claim autorship.

This selector is going to be used for autoresume and database scan folders.

Change-Id: Id1d3186dad783411eb5c6056ce93f5b6123c7aa0

+556
+1
apps/SOURCES
··· 86 86 #ifdef HAVE_QUICKSCREEN 87 87 gui/quickscreen.c 88 88 #endif 89 + gui/folder_select.c 89 90 90 91 gui/wps.c 91 92 gui/scrollbar.c
+474
apps/gui/folder_select.c
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * 9 + * Copyright (C) 2012 Jonathan Gordon 10 + * Copyright (C) 2012 Thomas Martitz 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 + #include <stdio.h> 23 + #include <stdlib.h> 24 + #include <string.h> 25 + #include "inttypes.h" 26 + #include "config.h" 27 + #include "core_alloc.h" 28 + #include "filetypes.h" 29 + #include "lang.h" 30 + #include "language.h" 31 + #include "list.h" 32 + #include "plugin.h" 33 + 34 + 35 + /* 36 + * Order for changing child states: 37 + * 1) expand folder (skip to 3 if empty, skip to 4 if cannot be opened) 38 + * 2) collapse and select 39 + * 3) unselect (skip to 1) 40 + * 4) do nothing 41 + */ 42 + 43 + enum child_state { 44 + EXPANDED, 45 + SELECTED, 46 + COLLAPSED, 47 + EACCESS, 48 + }; 49 + 50 + struct child { 51 + char* name; 52 + struct folder *folder; 53 + enum child_state state; 54 + }; 55 + 56 + struct folder { 57 + char *name; 58 + struct child *children; 59 + int children_count; 60 + int depth; 61 + 62 + struct folder* previous; 63 + }; 64 + 65 + static char *buffer_front, *buffer_end; 66 + static char* folder_alloc(size_t size) 67 + { 68 + char* retval; 69 + /* 32-bit aligned */ 70 + size = (size + 3) & ~3; 71 + if (buffer_front + size > buffer_end) 72 + { 73 + return NULL; 74 + } 75 + retval = buffer_front; 76 + buffer_front += size; 77 + return retval; 78 + } 79 + 80 + static char* folder_alloc_from_end(size_t size) 81 + { 82 + if (buffer_end - size < buffer_front) 83 + { 84 + return NULL; 85 + } 86 + buffer_end -= size; 87 + return buffer_end; 88 + } 89 + 90 + static void get_full_path_r(struct folder *start, char* dst) 91 + { 92 + if (start->previous) 93 + get_full_path_r(start->previous, dst); 94 + 95 + if (start->name && start->name[0] && strcmp(start->name, "/")) 96 + { 97 + strlcat(dst, "/", MAX_PATH); 98 + strlcat(dst, start->name, MAX_PATH); 99 + } 100 + } 101 + 102 + static char* get_full_path(struct folder *start) 103 + { 104 + static char buffer[MAX_PATH]; 105 + 106 + buffer[0] = '\0'; 107 + 108 + get_full_path_r(start, buffer); 109 + 110 + return buffer; 111 + } 112 + 113 + /* support function for qsort() */ 114 + static int compare(const void* p1, const void* p2) 115 + { 116 + struct child *left = (struct child*)p1; 117 + struct child *right = (struct child*)p2; 118 + return strcasecmp(left->name, right->name); 119 + } 120 + 121 + static struct folder* load_folder(struct folder* parent, char *folder) 122 + { 123 + DIR *dir; 124 + char* path = get_full_path(parent); 125 + char fullpath[MAX_PATH]; 126 + struct dirent *entry; 127 + struct folder* this = (struct folder*)folder_alloc(sizeof(struct folder)); 128 + int child_count = 0; 129 + char *first_child = NULL; 130 + 131 + if (!strcmp(folder,"/")) 132 + strlcpy(fullpath, folder, 2); 133 + else 134 + snprintf(fullpath, MAX_PATH, "%s/%s", parent ? path : "", folder); 135 + 136 + if (!this) 137 + return NULL; 138 + dir = opendir(fullpath); 139 + if (!dir) 140 + return NULL; 141 + this->previous = parent; 142 + this->name = folder; 143 + this->children = NULL; 144 + this->children_count = 0; 145 + this->depth = parent ? parent->depth + 1 : 0; 146 + 147 + while ((entry = readdir(dir))) { 148 + int len = strlen((char *)entry->d_name); 149 + struct dirinfo info; 150 + 151 + info = dir_get_info(dir, entry); 152 + 153 + /* skip anything not a directory */ 154 + if ((info.attribute & ATTR_DIRECTORY) == 0) { 155 + continue; 156 + } 157 + /* skip directories . and .. */ 158 + if ((!strcmp((char *)entry->d_name, ".")) || 159 + (!strcmp((char *)entry->d_name, ".."))) { 160 + continue; 161 + } 162 + char *name = folder_alloc_from_end(len+1); 163 + if (!name) 164 + return NULL; 165 + memcpy(name, (char *)entry->d_name, len+1); 166 + child_count++; 167 + first_child = name; 168 + } 169 + closedir(dir); 170 + /* now put the names in the array */ 171 + this->children = (struct child*)folder_alloc(sizeof(struct child) * child_count); 172 + 173 + if (!this->children) 174 + return NULL; 175 + while (child_count) 176 + { 177 + this->children[this->children_count].name = first_child; 178 + this->children[this->children_count].folder = NULL; 179 + this->children[this->children_count].state = COLLAPSED; 180 + this->children_count++; 181 + first_child += strlen(first_child) + 1; 182 + child_count--; 183 + } 184 + qsort(this->children, this->children_count, sizeof(struct child), compare); 185 + 186 + return this; 187 + } 188 + 189 + struct folder* load_root(void) 190 + { 191 + static struct child root_child; 192 + 193 + root_child.name = "/"; 194 + root_child.folder = NULL; 195 + root_child.state = COLLAPSED; 196 + 197 + static struct folder root = { 198 + .name = "", 199 + .children = &root_child, 200 + .children_count = 1, 201 + .depth = -1, 202 + .previous = NULL, 203 + }; 204 + 205 + return &root; 206 + } 207 + 208 + static int count_items(struct folder *start) 209 + { 210 + int count = 0; 211 + int i; 212 + 213 + for (i=0; i<start->children_count; i++) 214 + { 215 + struct child *foo = &start->children[i]; 216 + if (foo->state == EXPANDED) 217 + count += count_items(foo->folder); 218 + count++; 219 + } 220 + return count; 221 + } 222 + 223 + static struct child* find_index(struct folder *start, int index, struct folder **parent) 224 + { 225 + int i = 0; 226 + 227 + *parent = NULL; 228 + 229 + while (i < start->children_count) 230 + { 231 + struct child *foo = &start->children[i]; 232 + if (i == index) 233 + { 234 + *parent = start; 235 + return foo; 236 + } 237 + i++; 238 + if (foo->state == EXPANDED) 239 + { 240 + struct child *bar = find_index(foo->folder, index - i, parent); 241 + if (bar) 242 + { 243 + return bar; 244 + } 245 + index -= count_items(foo->folder); 246 + } 247 + } 248 + return NULL; 249 + } 250 + 251 + static const char * folder_get_name(int selected_item, void * data, 252 + char * buffer, size_t buffer_len) 253 + { 254 + struct folder *root = (struct folder*)data; 255 + struct folder *parent; 256 + struct child *this = find_index(root, selected_item , &parent); 257 + 258 + buffer[0] = '\0'; 259 + 260 + if (parent->depth >= 0) 261 + for(int i = 0; i <= parent->depth; i++) 262 + strcat(buffer, "\t"); 263 + 264 + strlcat(buffer, this->name, buffer_len); 265 + 266 + if (this->state == EACCESS) 267 + { /* append error message to the entry if unaccessible */ 268 + size_t len = strlcat(buffer, " (", buffer_len); 269 + if (buffer_len > len) 270 + { 271 + snprintf(&buffer[len], buffer_len - len, str(LANG_READ_FAILED), 272 + this->name); 273 + strlcat(buffer, ")", buffer_len); 274 + } 275 + } 276 + 277 + return buffer; 278 + } 279 + 280 + static enum themable_icons folder_get_icon(int selected_item, void * data) 281 + { 282 + struct folder *root = (struct folder*)data; 283 + struct folder *parent; 284 + struct child *this = find_index(root, selected_item, &parent); 285 + 286 + switch (this->state) 287 + { 288 + case SELECTED: 289 + return Icon_Cursor; 290 + case COLLAPSED: 291 + return Icon_Folder; 292 + case EXPANDED: 293 + return Icon_Submenu; 294 + case EACCESS: 295 + return Icon_Questionmark; 296 + } 297 + return Icon_NOICON; 298 + } 299 + 300 + static int folder_action_callback(int action, struct gui_synclist *list) 301 + { 302 + struct folder *root = (struct folder*)list->data; 303 + 304 + if (action == ACTION_STD_OK) 305 + { 306 + struct folder *parent; 307 + struct child *this = find_index(root, list->selected_item, &parent); 308 + switch (this->state) 309 + { 310 + case EXPANDED: 311 + this->state = SELECTED; 312 + break; 313 + case SELECTED: 314 + this->state = COLLAPSED; 315 + break; 316 + case COLLAPSED: 317 + if (this->folder == NULL) 318 + this->folder = load_folder(parent, this->name); 319 + this->state = this->folder ? (this->folder->children_count == 0 ? 320 + SELECTED : EXPANDED) : EACCESS; 321 + break; 322 + case EACCESS: 323 + /* cannot open, do nothing */ 324 + break; 325 + } 326 + list->nb_items = count_items(root); 327 + return ACTION_REDRAW; 328 + } 329 + return action; 330 + } 331 + 332 + static struct child* find_from_filename(char* filename, struct folder *root) 333 + { 334 + char *slash = strchr(filename, '/'); 335 + int i = 0; 336 + if (slash) 337 + *slash = '\0'; 338 + if (!root) 339 + return NULL; 340 + 341 + struct child *this; 342 + 343 + /* filenames beginning with a / are specially treated as the 344 + * loop below can't handle them. they can only occur on the first, 345 + * and not recursive, calls to this function.*/ 346 + if (slash == filename) 347 + { 348 + /* filename begins with /. in this case root must be the 349 + * top level folder */ 350 + this = &root->children[0]; 351 + if (!slash[1]) 352 + { /* filename == "/" */ 353 + return this; 354 + } 355 + else 356 + { 357 + /* filename == "/XXX/YYY". cascade down */ 358 + if (!this->folder) 359 + this->folder = load_folder(root, this->name); 360 + this->state = EXPANDED; 361 + /* recurse with XXX/YYY */ 362 + return find_from_filename(slash+1, this->folder); 363 + } 364 + } 365 + 366 + while (i < root->children_count) 367 + { 368 + this = &root->children[i]; 369 + if (!strcasecmp(this->name, filename)) 370 + { 371 + if (!slash) 372 + { /* filename == XXX */ 373 + return this; 374 + } 375 + else 376 + { 377 + /* filename == XXX/YYY. cascade down */ 378 + if (!this->folder) 379 + this->folder = load_folder(root, this->name); 380 + this->state = EXPANDED; 381 + return find_from_filename(slash+1, this->folder); 382 + } 383 + } 384 + i++; 385 + } 386 + return NULL; 387 + } 388 + 389 + /* _modifies_ buf */ 390 + int select_paths(struct folder* root, char* buf) 391 + { 392 + struct child *item = find_from_filename(buf, root); 393 + if (item) 394 + item->state = SELECTED; 395 + return 0; 396 + } 397 + 398 + static void save_folders_r(struct folder *root, char* dst, size_t maxlen) 399 + { 400 + int i = 0; 401 + 402 + while (i < root->children_count) 403 + { 404 + struct child *this = &root->children[i]; 405 + if (this->state == SELECTED) 406 + { 407 + if (this->folder) 408 + snprintf(buffer_front, buffer_end - buffer_front, 409 + "%s:", get_full_path(this->folder)); 410 + else 411 + snprintf(buffer_front, buffer_end - buffer_front, 412 + "%s/%s:", get_full_path(root), this->name); 413 + strlcat(dst, buffer_front, maxlen); 414 + } 415 + else if (this->state == EXPANDED) 416 + save_folders_r(this->folder, dst, maxlen); 417 + i++; 418 + } 419 + } 420 + 421 + static void save_folders(struct folder *root, char* dst, size_t maxlen) 422 + { 423 + int len; 424 + dst[0] = '\0'; 425 + save_folders_r(root, dst, maxlen); 426 + len = strlen(dst); 427 + /* fix trailing ':' */ 428 + if (len > 1) dst[len-1] = '\0'; 429 + } 430 + 431 + bool folder_select(char* setting, int setting_len) 432 + { 433 + struct folder *root; 434 + struct simplelist_info info; 435 + size_t buf_size; 436 + /* 32 separate folders should be Enough For Everybody(TM) */ 437 + char *vect[32]; 438 + char copy[setting_len]; 439 + int nb_items; 440 + 441 + /* copy onto stack as split_string() modifies it */ 442 + strlcpy(copy, setting, setting_len); 443 + nb_items = split_string(copy, ':', vect, ARRAYLEN(vect)); 444 + 445 + buffer_front = plugin_get_buffer(&buf_size); 446 + buffer_end = buffer_front + buf_size; 447 + root = load_root(); 448 + 449 + if (nb_items > 0) 450 + { 451 + for(int i = 0; i < nb_items; i++) 452 + select_paths(root, vect[i]); 453 + } 454 + 455 + simplelist_info_init(&info, str(LANG_SELECT_FOLDER), 456 + count_items(root), root); 457 + info.get_name = folder_get_name; 458 + info.action_callback = folder_action_callback; 459 + info.get_icon = folder_get_icon; 460 + simplelist_show_list(&info); 461 + 462 + /* done editing. check for changes */ 463 + save_folders(root, copy, setting_len); 464 + if (strcmp(copy, setting)) 465 + { /* prompt for saving changes and commit if yes */ 466 + if (yesno_pop(ID2P(LANG_SAVE_CHANGES))) 467 + { 468 + strcpy(setting, copy); 469 + settings_save(); 470 + return true; 471 + } 472 + } 473 + return false; 474 + }
+35
apps/gui/folder_select.h
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * 9 + * Copyright (C) 2011 Jonathan Gordon 10 + * 11 + * This program is free software; you can redistribute it and/or 12 + * modify it under the terms of the GNU General Public License 13 + * as published by the Free Software Foundation; either version 2 14 + * of the License, or (at your option) any later version. 15 + * 16 + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 17 + * KIND, either express or implied. 18 + * 19 + ****************************************************************************/ 20 + 21 + #ifndef __FOLDER_SELECT_H__ 22 + #define __FOLDER_SELECT_H__ 23 + 24 + /** 25 + * A GUI browser to select folders from the file system 26 + * 27 + * It reads a list of folders, separated by colons (:) from setting 28 + * and pre-selects them in the UI. If the user is done it writes the new 29 + * list back to setting (again separated by colons), assuming the 30 + * user confirms the yesno dialog. 31 + * 32 + * Returns true if the the folder list has changed, otherwise false */ 33 + bool folder_select(char* setting, int setting_len); 34 + 35 + #endif /* __FOLDER_SELECT_H__ */
+14
apps/lang/english.lang
··· 13086 13086 swcodec: "Custom" 13087 13087 </voice> 13088 13088 </phrase> 13089 + <phrase> 13090 + id: LANG_SELECT_FOLDER 13091 + desc: in settings_menu 13092 + user: core 13093 + <source> 13094 + *: "Select one or more directories" 13095 + </source> 13096 + <dest> 13097 + *: "Select one or more directories" 13098 + </dest> 13099 + <voice> 13100 + *: "Select one or more directories" 13101 + </voice> 13102 + </phrase>
+31
apps/misc.c
··· 1075 1075 } 1076 1076 } 1077 1077 1078 + /** 1079 + * Splits str at each occurence of split_char and puts the substrings into vector, 1080 + * but at most vector_lenght items. Empty substrings are ignored. 1081 + * 1082 + * Modifies str by replacing each split_char following a substring with nul 1083 + * 1084 + * Returns the number of substrings found, i.e. the number of valid strings 1085 + * in vector 1086 + */ 1087 + int split_string(char *str, const char split_char, char *vector[], const int vector_length) 1088 + { 1089 + int i; 1090 + char *p = str; 1091 + 1092 + /* skip leading splitters */ 1093 + while(*p == split_char) p++; 1094 + 1095 + /* *p in the condition takes care of trailing splitters */ 1096 + for(i = 0; p && *p && i < vector_length; i++) 1097 + { 1098 + vector[i] = p; 1099 + if ((p = strchr(p, split_char))) 1100 + { 1101 + *p++ = '\0'; 1102 + while(*p == split_char) p++; /* skip successive splitters */ 1103 + } 1104 + } 1105 + 1106 + return i; 1107 + } 1108 + 1078 1109 1079 1110 /** Open a UTF-8 file and set file descriptor to first byte after BOM. 1080 1111 * If no BOM is present this behaves like open().
+1
apps/misc.h
··· 73 73 #define BOM_UTF_16_BE "\xfe\xff" 74 74 #define BOM_UTF_16_SIZE 2 75 75 76 + int split_string(char *str, const char needle, char *vector[], int vector_length); 76 77 int open_utf8(const char* pathname, int flags); 77 78 78 79 #ifdef BOOTFILE