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

[BugFix] Multiboot Database duplicate files

When the sd card is mounted into the root namespace the database
picks up files through both paths

previously we hid the mounted drive but this causes issues with users
databases when the drive letter changes

Adds a way to keep track of volumes mounted in the root namespace

Hides the enumerated volume in root

Database:
we can just parse the root directory ('/') and get to any mounted
volume but we can also enumerate a volume in the root directory
when this occurs it leads to multiple entries since the files can
be reached through multiple paths ex, /Foo could also be /SD1/Foo
Instead we will attempt to rewrite the root with any non-hidden volumes
failing that just leave the paths alone

Change-Id: I7bdba8cfaf63902d2a3852d28484bcf8ca317ebd

+198 -51
+62 -23
apps/filetree.c
··· 46 46 #include "strnatcmp.h" 47 47 #include "keyboard.h" 48 48 49 + #ifdef HAVE_MULTIVOLUME 50 + #include "mv.h" 51 + #endif 52 + 49 53 #if CONFIG_TUNER 50 54 #include "radio.h" 51 55 #endif ··· 471 475 settings_apply_skins(); 472 476 } 473 477 478 + int ft_assemble_path(char *buf, size_t bufsz, const char* currdir, const char* filename) 479 + { 480 + int len; 481 + if (!filename) 482 + filename = ""; 483 + #ifdef HAVE_MULTIVOLUME 484 + if (currdir && currdir[0] && currdir[1]) /* Not in / */ 485 + { 486 + if (currdir[1] != VOL_START_TOK) 487 + { 488 + len = snprintf(buf, bufsz, "%s%s/%s", root_realpath(), currdir, filename); 489 + } 490 + else 491 + len = snprintf(buf, bufsz, "%s/%s", currdir, filename); 492 + } 493 + else /* In / */ 494 + { 495 + if (filename[0] != VOL_START_TOK) 496 + { 497 + len = snprintf(buf, bufsz, "%s/%s",root_realpath(), filename); 498 + } 499 + else 500 + len = snprintf(buf, bufsz, "/%s", filename); 501 + } 502 + #else 503 + if (currdir && currdir[0] && currdir[1]) /* Not in / */ 504 + { 505 + len = snprintf(buf, bufsz, "%s%s/%s", root_realpath(), currdir, filename); 506 + } 507 + else /* In / */ 508 + { 509 + len = snprintf(buf, bufsz, "%s/%s",root_realpath(), filename); 510 + } 511 + #endif 512 + if ((unsigned) len > bufsz) 513 + splash(HZ, ID2P(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR)); 514 + return len; 515 + } 516 + 474 517 int ft_enter(struct tree_context* c) 475 518 { 476 519 int rc = GO_TO_PREVIOUS; ··· 484 527 } 485 528 486 529 int file_attr = file->attr; 487 - int len; 488 - 489 - if (c->currdir[1]) 490 - { 491 - len = snprintf(buf,sizeof(buf),"%s/%s",c->currdir, file->name); 492 - if ((unsigned) len > sizeof(buf)) 493 - splash(HZ, ID2P(LANG_PLAYLIST_ACCESS_ERROR)); 494 - } 495 - else 496 - snprintf(buf,sizeof(buf),"/%s",file->name); 530 + ft_assemble_path(buf, sizeof(buf), c->currdir, file->name); 497 531 498 532 if (file_attr & ATTR_DIRECTORY) { 499 533 memcpy(c->currdir, buf, sizeof(c->currdir)); ··· 537 571 PLAYLIST_INSERT_LAST, true, true); 538 572 splash(HZ, ID2P(LANG_QUEUE_LAST)); 539 573 } 540 - else if (playlist_create(c->currdir, NULL) != -1) 574 + else 541 575 { 542 - start_index = ft_build_playlist(c, c->selected_item); 543 - if (global_settings.playlist_shuffle) 576 + /* use the assembled path sans filename */ 577 + char * fp = strrchr(buf, PATH_SEPCH); 578 + if (fp) 579 + *fp = '\0'; 580 + if (playlist_create(buf, NULL) != -1) 544 581 { 545 - start_index = playlist_shuffle(seed, start_index); 546 - 547 - /* when shuffling dir.: play all files 548 - even if the file selected by user is 549 - not the first one */ 550 - if (!global_settings.play_selected) 551 - start_index = 0; 582 + start_index = ft_build_playlist(c, c->selected_item); 583 + if (global_settings.playlist_shuffle) 584 + { 585 + start_index = playlist_shuffle(seed, start_index); 586 + /* when shuffling dir.: play all files 587 + even if the file selected by user is 588 + not the first one */ 589 + if (!global_settings.play_selected) 590 + start_index = 0; 591 + } 592 + playlist_start(start_index, 0, 0); 593 + play = true; 552 594 } 553 - 554 - playlist_start(start_index, 0, 0); 555 - play = true; 556 595 } 557 596 break; 558 597 }
+2
apps/filetree.h
··· 25 25 int ft_load(struct tree_context* c, const char* tempdir); 26 26 int ft_enter(struct tree_context* c); 27 27 int ft_exit(struct tree_context* c); 28 + int ft_assemble_path(char *buf, size_t bufsz, 29 + const char* currdir, const char* filename); 28 30 int ft_build_playlist(struct tree_context* c, int start_index); 29 31 bool ft_play_playlist(char* pathname, char* dirname, 30 32 char* filename, bool skip_warn_and_bookmarks);
+47
apps/tagcache.c
··· 4990 4990 4991 4991 roots_ll[0].path = path[0]; 4992 4992 roots_ll[0].next = NULL; 4993 + 4994 + #if defined HAVE_MULTIVOLUME && !defined(SIMULATOR) && !defined(__PCTOOL__) 4995 + extern bool ns_volume_is_visible(int volume); /*rb_namespace.c*/ 4996 + /* i is for the path vector, j for the roots_ll array */ 4997 + int i = 1, j = 1; 4998 + bool added = false; 4999 + char volnamebuf[NUM_VOLUMES][VOL_MAX_LEN + 1]; 5000 + /* we can just parse the root directory ('/') and get to any mounted 5001 + * volume but we can also enumerate a volume in the root directory 5002 + * when this occurs it leads to multiple entries since the files can 5003 + * be reached through multiple paths ex, /Foo could also be /SD1/Foo 5004 + * we used to hide the volume that was mapped but then when you switch 5005 + * from the sd to the internal the paths don't map to the right volume 5006 + * instead we will attempt to rewrite the root with any non-hidden volumes 5007 + * failing that just leave the paths alone */ 5008 + if (!strcmp(PATH_ROOTSTR, path[0])) 5009 + { 5010 + i = 0; 5011 + j = 0; 5012 + } 5013 + /* path can be skipped , but root_ll entries can't */ 5014 + for(; path[i] && j < MAX_STATIC_ROOTS; i++) 5015 + { 5016 + /* check if the link target is inside of an existing search root 5017 + * don't add if target is inside, we'll scan it later */ 5018 + if (!added && !strcmp(PATH_ROOTSTR, path[i])) 5019 + { 5020 + for (int v = 0; v < NUM_VOLUMES; v++) 5021 + { 5022 + if (ns_volume_is_visible(v)) 5023 + { 5024 + make_volume_root(v, volnamebuf[v]); 5025 + roots_ll[j].path = volnamebuf[v]; 5026 + if (j > 0) 5027 + roots_ll[j-1].next = &roots_ll[j]; 5028 + j++; 5029 + added = true; 5030 + } 5031 + } 5032 + if(!added) 5033 + j = 1; 5034 + added = true; 5035 + continue; 5036 + } 5037 + #else 4993 5038 /* i is for the path vector, j for the roots_ll array 4994 5039 * path can be skipped , but root_ll entries can't */ 4995 5040 for(int i = 1, j = 1; path[i] && j < MAX_STATIC_ROOTS; i++) 4996 5041 { 5042 + #endif /*def HAVE_MULTIVOLUME*/ 4997 5043 if (search_root_exists(path[i])) /* skip this path */ 4998 5044 continue; 4999 5045 ··· 5006 5052 /* check_dir might add new roots */ 5007 5053 for(this = &roots_ll[0]; this; this = this->next) 5008 5054 { 5055 + logf("Search root %s", this->path); 5009 5056 strmemccpy(curpath, this->path, sizeof(curpath)); 5010 5057 ret = ret && check_dir(this->path, true); 5011 5058 }
+1 -10
apps/tree.c
··· 646 646 { 647 647 int numentries=0; 648 648 char buf[MAX_PATH]; 649 - int len; 650 649 int button; 651 650 int oldbutton; 652 651 bool reload_root = false; ··· 857 856 858 857 attr = entry->attr; 859 858 860 - if (currdir[1]) /* Not in / */ 861 - { 862 - len = snprintf(buf, sizeof buf, "%s/%s", 863 - currdir, entry->name); 859 + ft_assemble_path(buf, sizeof(buf), currdir, entry->name); 864 860 865 - if ((unsigned) len > sizeof(buf)) 866 - splash(HZ, ID2P(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR)); 867 - } 868 - else /* In / */ 869 - snprintf(buf, sizeof buf, "/%s", entry->name); 870 861 } 871 862 onplay_result = onplay(buf, attr, curr_context, hotkey); 872 863 }
+6
firmware/common/dir.c
··· 344 344 file_error: 345 345 return (struct dirinfo){ .attribute = 0 }; 346 346 } 347 + 348 + const char* root_realpath(void) 349 + { 350 + /* Native only, for APP and SIM see respective filesystem-.c files */ 351 + return root_get_realpath(); /* rb_namespace.c */ 352 + }
+58 -16
firmware/common/rb_namespace.c
··· 23 23 #include "fileobj_mgr.h" 24 24 #include "rb_namespace.h" 25 25 #include "file_internal.h" 26 + #include <stdio.h> /*snprintf*/ 26 27 27 28 /* Define LOGF_ENABLE to enable logf output in this file */ 28 29 //#define LOGF_ENABLE ··· 82 83 set_root_item_state(item, 0); 83 84 } 84 85 86 + static char *root_realpath_internal(void) 87 + { 88 + static char root_realpath[ROOT_MAX_REALPATH]; 89 + return root_realpath; 90 + } 91 + const char* root_get_realpath(void) 92 + { 93 + return root_realpath_internal(); 94 + } 95 + 85 96 /* mount the directory that enumerates into the root namespace */ 86 97 int root_mount_path(const char *path, unsigned int flags) 87 98 { 99 + const char *folder = NULL; /* is a folder enumerated in the root? */ 88 100 #ifdef HAVE_MULTIVOLUME 89 - int volume = path_strip_volume(path, NULL, false); 101 + int volume = path_strip_volume(path, &folder, false); 90 102 if (volume == ROOT_VOLUME) 91 103 return -EINVAL; 92 - 93 104 if (!CHECK_VOL(volume)) 94 105 return -ENOENT; 106 + char volname[VOL_MAX_LEN+2]; 107 + make_volume_root(volume, volname); 95 108 #else 109 + const char volname = PATH_ROOTSTR; 96 110 if (!path_is_absolute(path)) 97 111 { 98 112 logf("Path not absolute %s", path); 99 113 return -ENOENT; 100 114 } 115 + path_dirname(path, &folder); 101 116 #endif /* HAVE_MULTIVOLUME */ 102 - 103 117 bool contents = flags & NSITEM_CONTENTS; 104 - int item = contents ? ROOT_CONTENTS_INDEX : IF_MV_VOL(volume); 118 + int item = IF_MV_VOL(volume); 105 119 unsigned int state = get_root_item_state(item); 106 - 107 120 logf("%s: item:%d, st:%u, %s", __func__, item, state, path); 108 - 109 - if (state) 110 - return -EBUSY; 111 - 112 - if (contents) 121 + if (contents && state) /* volume must be mounted to enumerate into the root namespace */ 113 122 { 123 + if (get_root_item_state(ROOT_CONTENTS_INDEX)) 124 + return -EBUSY; /* error something is already enumerated */ 114 125 /* cache information about the target */ 115 126 struct filestr_base stream; 116 127 struct path_component_info compinfo; 117 - 118 128 int e = errno; 119 129 int rc = open_stream_internal(path, FF_DIR | FF_PROBE | FF_INFO | 120 130 FF_DEVPATH, &stream, &compinfo); ··· 124 134 errno = e; 125 135 return rc; 126 136 } 127 - 128 137 if (!fileobj_mount(&compinfo.info, FO_DIRECTORY, &root_bindp)) 129 138 return -EBUSY; 130 - } 139 + int root_state = NSITEM_MOUNTED | (flags & (NSITEM_HIDDEN|NSITEM_CONTENTS)); 140 + set_root_item_state(ROOT_CONTENTS_INDEX, root_state); 141 + flags |= state; /* preserve the state of the mounted volume */ 142 + if (!folder) 143 + { 144 + folder = ""; 145 + } 146 + else 147 + { 148 + /*if a folder has been enumerated don't mark the whole volume */ 149 + if (folder[0] != '\0' && folder[1] != '\0') 150 + flags &= ~NSITEM_CONTENTS; 131 151 152 + } 153 + snprintf(root_realpath_internal(), ROOT_MAX_REALPATH,"%s%s", volname, folder); 154 + } 155 + else if (state) /* error volume already mounted */ 156 + return -EBUSY; 132 157 state = NSITEM_MOUNTED | (flags & (NSITEM_HIDDEN|NSITEM_CONTENTS)); 133 158 set_root_item_state(item, state); 159 + return 0; 160 + } 134 161 135 - return 0; 162 + /* check if volume in path is mounted in the root namespace */ 163 + bool ns_volume_is_visible(IF_MV_NONVOID(int volume)) 164 + { 165 + int item = IF_MV_VOL(volume); 166 + if ((item == ROOT_VOLUME) || !CHECK_VOL(item)) 167 + return false; 168 + unsigned int state = get_root_item_state(item); 169 + return state && (((state & NSITEM_HIDDEN) == 0) || (state & NSITEM_CONTENTS)); 136 170 } 137 171 138 172 /* inform root that an entire volume is being unmounted */ ··· 154 188 uint32_t state = get_root_item_state(ROOT_CONTENTS_INDEX); 155 189 if (state && (volume < 0 || BASEINFO_VOL(&root_bindp->info) == volume)) 156 190 #endif 191 + { 157 192 unmount_item(ROOT_CONTENTS_INDEX); 193 + root_realpath_internal()[0] = '\0'; 194 + } 158 195 } 159 196 160 197 /* parse the root part of a path */ ··· 268 305 state = get_root_item_state(item); 269 306 if ((state & (NSITEM_MOUNTED|NSITEM_HIDDEN)) == NSITEM_MOUNTED) 270 307 { 271 - logf("Found mounted item: %d %s", item, entry->d_name); 272 - break; 308 + #if 1 /* hide the volume enumerated into the root namespace */ 309 + if (item == ROOT_CONTENTS_INDEX || (state & NSITEM_CONTENTS) == 0) 310 + { 311 + logf("Found mounted item: %d %s", item, entry->d_name); 312 + break; 313 + } 314 + #endif 273 315 } 274 316 275 317 item++;
+6
firmware/include/dir.h
··· 63 63 #ifndef dir_exists 64 64 #define dir_exists FS_PREFIX(dir_exists) 65 65 #endif 66 + #ifndef root_realpath 67 + #define root_realpath FS_PREFIX(root_realpath) 68 + #endif 66 69 #endif /* !DIRFUNCTIONS_DEFINED */ 67 70 68 71 #ifndef DIRENT_DEFINED ··· 83 86 #ifndef DIRFUNCTIONS_DECLARED 84 87 /* TIP: set errno to zero before calling to see if anything failed */ 85 88 struct dirinfo dir_get_info(DIR *dirp, struct DIRENT *entry); 89 + const char* root_realpath(void); 86 90 #endif /* !DIRFUNCTIONS_DECLARED */ 91 + 92 + 87 93 88 94 #endif /* _DIR_H_ */
+3 -2
firmware/include/dircache_redirect.h
··· 139 139 140 140 static inline void volume_onmount_internal(IF_MV_NONVOID(int volume)) 141 141 { 142 - #if defined(HAVE_MULTIBOOT) && !defined(SIMULATOR) && !defined(BOOTLOADER) 142 + #if (defined(HAVE_MULTIVOLUME) || (defined(HAVE_MULTIBOOT) && !defined(BOOTLOADER))) 143 143 char path[VOL_MAX_LEN+2]; 144 + #endif 145 + #if defined(HAVE_MULTIBOOT) && !defined(SIMULATOR) && !defined(BOOTLOADER) 144 146 char rtpath[MAX_PATH / 2]; 145 147 make_volume_root(volume, path); 146 148 ··· 183 185 root_mount_path(RB_ROOT_CONTENTS_DIR, NSITEM_CONTENTS); 184 186 } 185 187 #elif defined(HAVE_MULTIVOLUME) 186 - char path[VOL_MAX_LEN+2]; 187 188 make_volume_root(volume, path); 188 189 root_mount_path(path, RB_ROOT_VOL_HIDDEN(volume) ? NSITEM_HIDDEN : 0); 189 190 if (volume == path_strip_volume(RB_ROOT_CONTENTS_DIR, NULL, false))
+3
firmware/include/rb_namespace.h
··· 37 37 }; 38 38 39 39 /* root functions */ 40 + #define ROOT_MAX_REALPATH 80 41 + const char* root_get_realpath(void); 40 42 int root_mount_path(const char *path, unsigned int flags); 41 43 void root_unmount_volume(IF_MV_NONVOID(int volume)); 42 44 int root_readdir_dirent(struct filestr_base *stream, ··· 49 51 struct file_base_info *infop, uint16_t *attrp); 50 52 int ns_open_stream(const char *path, unsigned int callflags, 51 53 struct filestr_base *stream, struct ns_scan_info *scanp); 54 + bool ns_volume_is_visible(IF_MV_NONVOID(int volume)); 52 55 53 56 /* closes the namespace stream */ 54 57 static inline int ns_close_stream(struct filestr_base *stream)
+5
firmware/target/hosted/filesystem-app.c
··· 600 600 601 601 return 0; 602 602 } 603 + 604 + const char* app_root_realpath(void) 605 + { 606 + return PATH_ROOTSTR; 607 + }
+5
uisimulator/common/filesystem-sim.c
··· 843 843 844 844 return sim_get_os_path(buffer, tmpbuf, bufsize); 845 845 } 846 + 847 + const char* sim_root_realpath(void) 848 + { 849 + return PATH_ROOTSTR; 850 + }