A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 523 lines 17 kB view raw
1/*************************************************************************** 2 * __________ __ ___. 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 * \/ \/ \/ \/ \/ 8 * $Id$ 9 * 10 * Copyright (C) 2020 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#ifndef __PCTOOL__ 23 24#include "plugin.h" 25#include "open_plugin.h" 26#include "pathfuncs.h" 27#include "splash.h" 28#include "lang.h" 29#include "filetypes.h" 30 31/* Define LOGF_ENABLE to enable logf output in this file */ 32/*#define LOGF_ENABLE*/ 33#include "logf.h" 34 35static const uint32_t open_plugin_csum = OPEN_PLUGIN_CHECKSUM; 36 37static const int op_entry_sz = sizeof(struct open_plugin_entry_t); 38 39static const char* strip_rockbox_root(const char *path) 40{ 41 int dlen = ROCKBOX_DIR_LEN; 42 if (strncmp(path, ROCKBOX_DIR, dlen) == 0) 43 path+= dlen; 44 return path; 45} 46 47static inline void op_clear_entry(struct open_plugin_entry_t *entry) 48{ 49 if (entry == NULL) 50 return; 51 memset(entry, 0, op_entry_sz); 52 entry->lang_id = OPEN_PLUGIN_LANG_INVALID; 53} 54 55static int op_entry_checksum(struct open_plugin_entry_t *entry) 56{ 57/*Note: since we use langids as checksums everytime someone moves the lang file 58* around it could mess with our indexing so invalidate entries when this occurs 59*/ 60 if (entry == NULL || entry->checksum != open_plugin_csum + 61 (entry->lang_id <= OPEN_PLUGIN_LANG_INVALID ? 0 : LANG_LAST_INDEX_IN_ARRAY)) 62 { 63 logf("OP entry bad checksum"); 64 return 0; 65 } 66 return 1; 67} 68 69static int op_find_entry(int fd, struct open_plugin_entry_t *entry, 70 uint32_t hash, int32_t lang_id) 71{ 72 int ret = OPEN_PLUGIN_NOT_FOUND; 73 int record = 0; 74 if (hash == 0) 75 hash = OPEN_PLUGIN_SEED; 76 if (fd >= 0) 77 { 78 logf("OP find_entry *Searching* hash: %x lang_id: %d", hash, lang_id); 79 80 while (read(fd, entry, op_entry_sz) == op_entry_sz) 81 { 82 if (entry->lang_id == lang_id || entry->hash == hash || 83 (lang_id == OPEN_PLUGIN_LANG_IGNOREALL))/* return first entry found */ 84 { 85#if (CONFIG_STORAGE & STORAGE_ATA) 86 /* may have invalid entries but we append the file so continue looking*/ 87 if (op_entry_checksum(entry) <= 0) 88 { 89 ret = OPEN_PLUGIN_INVALID_ENTRY; 90 continue; 91 } 92#endif 93 ret = record; 94 /* NULL terminate fields NOTE -- all are actually +1 larger */ 95 entry->name[OPEN_PLUGIN_NAMESZ] = '\0'; 96 /*entry->key[OPEN_PLUGIN_BUFSZ] = '\0';*/ 97 entry->path[OPEN_PLUGIN_BUFSZ] = '\0'; 98 entry->param[OPEN_PLUGIN_BUFSZ] = '\0'; 99 logf("OP find_entry *Found* hash: %x lang_id: %d", 100 entry->hash, entry->lang_id); 101 logf("OP find_entry rec: %d name: %s %s %s", record, 102 entry->name, entry->path, entry->param); 103 break; 104 } 105 record++; 106 } 107 } 108 109 /* sanity check */ 110#if (CONFIG_STORAGE & STORAGE_ATA) 111 if (ret == OPEN_PLUGIN_INVALID_ENTRY || 112#else 113 if( 114#endif 115 (ret > OPEN_PLUGIN_NOT_FOUND && op_entry_checksum(entry) <= 0)) 116 { 117 splashf(HZ * 2, "%s Invalid entry", str(LANG_OPEN_PLUGIN)); 118 ret = OPEN_PLUGIN_NOT_FOUND; 119 } 120 if (ret == OPEN_PLUGIN_NOT_FOUND) 121 op_clear_entry(entry); 122 123 return ret; 124} 125 126static int op_update_dat(struct open_plugin_entry_t *entry, bool clear) 127{ 128 int fd; 129 uint32_t hash; 130 int32_t lang_id; 131 if (entry == NULL|| entry->hash == 0) 132 { 133 logf("OP update *No entry*"); 134 return OPEN_PLUGIN_NOT_FOUND; 135 } 136 137 hash = entry->hash; 138 lang_id = entry->lang_id; 139 if (lang_id <= OPEN_PLUGIN_LANG_INVALID) 140 lang_id = OPEN_PLUGIN_LANG_IGNORE; 141 142 logf("OP update hash: %x lang_id: %d", hash, lang_id); 143 logf("OP update name: %s clear: %d", entry->name, (int) clear); 144 logf("OP update %s %s %s", entry->name, entry->path, entry->param); 145 146#if (CONFIG_STORAGE & STORAGE_ATA) /* Harddrive -- update existing */ 147 logf("OP update *Updating entries* %s", OPEN_PLUGIN_DAT); 148 fd = open(OPEN_PLUGIN_DAT, O_RDWR | O_CREAT, 0666); 149 150 if (fd < 0) 151 return OPEN_PLUGIN_NOT_FOUND; 152 /* Only read the hash lang id and checksum */ 153 uint32_t hash_langid_csum[3] = {0}; 154 const off_t hlc_sz = sizeof(hash_langid_csum); 155 156 uint32_t csum = open_plugin_csum + 157 (lang_id <= OPEN_PLUGIN_LANG_INVALID ? 0 : LANG_LAST_INDEX_IN_ARRAY); 158 159 while (read(fd, &hash_langid_csum, hlc_sz) == hlc_sz) 160 { 161 if ((hash_langid_csum[0] == hash || (int32_t)hash_langid_csum[1] == lang_id) && 162 hash_langid_csum[2] == csum) 163 { 164 logf("OP update *Entry Exists* hash: %x langid: %d", 165 hash_langid_csum[0], (int32_t)hash_langid_csum[1]); 166 lseek(fd, 0-hlc_sz, SEEK_CUR);/* back to the start of record */ 167 break; 168 } 169 lseek(fd, op_entry_sz - hlc_sz, SEEK_CUR); /* finish record */ 170 } 171 write(fd, entry, op_entry_sz); 172 close(fd); 173#else /* Everyone else make a temp file */ 174 logf("OP update *Copying entries* %s", OPEN_PLUGIN_DAT ".tmp"); 175 fd = open(OPEN_PLUGIN_DAT ".tmp", O_RDWR | O_CREAT | O_TRUNC, 0666); 176 177 if (fd < 0) 178 return OPEN_PLUGIN_NOT_FOUND; 179 write(fd, entry, op_entry_sz); 180 181 int fd1 = open(OPEN_PLUGIN_DAT, O_RDONLY); 182 if (fd1 >= 0) 183 { 184 /* copy non-duplicate entries back from original */ 185 while (read(fd1, entry, op_entry_sz) == op_entry_sz) 186 { 187 if (entry->hash != hash && entry->lang_id != lang_id && 188 op_entry_checksum(entry) > 0) 189 { 190 write(fd, entry, op_entry_sz); 191 } 192 } 193 close(fd1); 194 remove(OPEN_PLUGIN_DAT); 195 } 196 if (!clear) /* retrieve original entry */ 197 { 198 logf("OP update *Loading original entry*"); 199 lseek(fd, 0, SEEK_SET); 200 op_find_entry(fd, entry, hash, lang_id); 201 } 202 close(fd); 203 rename(OPEN_PLUGIN_DAT ".tmp", OPEN_PLUGIN_DAT); 204#endif 205 206 if (clear) 207 { 208 logf("OP update *Clearing entry*"); 209 op_clear_entry(entry); 210 } 211 212 return 0; 213} 214 215static int op_load_entry(uint32_t hash, int32_t lang_id, 216 struct open_plugin_entry_t *entry, const char *dat_file) 217{ 218 int opret = OPEN_PLUGIN_NOT_FOUND; 219 220 if (entry != NULL) 221 { 222 /* Is the entry we want already loaded? */ 223 if(hash != 0 && entry->hash == hash) 224 return OPEN_PLUGIN_NEEDS_FLUSHED; 225 226 if(lang_id <= OPEN_PLUGIN_LANG_INVALID) 227 { 228 lang_id = OPEN_PLUGIN_LANG_IGNORE; 229 if (hash == 0)/* no hash or langid -- returns first entry found */ 230 lang_id = OPEN_PLUGIN_LANG_IGNOREALL; 231 } 232 else if(entry->lang_id == lang_id) 233 { 234 return OPEN_PLUGIN_NEEDS_FLUSHED; 235 } 236 237 /* if another entry is loaded; flush it to disk before we destroy it */ 238 239 op_update_dat(open_plugin_get_entry(), true); 240 241 logf("OP get_entry hash: %x lang id: %d db: %s", hash, lang_id, dat_file); 242 243 int fd = open(dat_file, O_RDONLY); 244 if(fd < 0) 245 return OPEN_PLUGIN_NOT_FOUND; 246 opret = op_find_entry(fd, entry, hash, lang_id); 247 close(fd); 248 } 249 250 return opret; 251} 252 253/******************************************************************************/ 254/******************************************************************************/ 255/* ************************************************************************** */ 256/* * PUBLIC INTERFACE FUNCTIONS * *********************************************/ 257/* ************************************************************************** */ 258/******************************************************************************/ 259/******************************************************************************/ 260 261/* open_plugin_get_entry() 262* returns the internal open_plugin_entry 263*/ 264struct open_plugin_entry_t * open_plugin_get_entry(void) 265{ 266 /* holds entry data to load/run/store */ 267 static struct open_plugin_entry_t open_plugin_entry = {0}; 268 return &open_plugin_entry; 269} 270 271/* open_plugin_add_path() 272* adds a plugin path and calling parameters to open_plugin_entry 273* hash of the key is created for later recall of the plugin path and parameters 274* returns hash of the key or 0 on error 275*/ 276uint32_t open_plugin_add_path(const char *key, const char *plugin, const char *parameter) 277{ 278 uint32_t hash; 279 int32_t lang_id; 280 char *pos = "\0"; 281 struct open_plugin_entry_t *op_entry = open_plugin_get_entry(); 282 283 if(key == NULL) 284 { 285 logf("OP add_path No Key, *Clearing entry*"); 286 op_clear_entry(op_entry); 287 return 0; 288 } 289 290 lang_id = P2ID((unsigned char*)key); 291 const char *skey = P2STR((unsigned char *)key); 292 logf("OP add_path key: %s lang id: %d", skey, lang_id); 293 open_plugin_get_hash(strip_rockbox_root(skey), &hash); 294 295 if(op_entry->hash != hash) 296 { 297 logf("OP add_path *Flush entry*"); 298 /* the entry in ram needs saved */ 299 op_update_dat(op_entry, true); 300 } 301 302 while (plugin) 303 { 304 int fattr = filetype_get_attr(plugin); 305 306 /* name */ 307 if (path_basename(plugin, (const char **)&pos) == 0) 308 pos = "\0"; 309 310 strlcpy(op_entry->name, pos, OPEN_PLUGIN_NAMESZ); 311 312 if (fattr == FILE_ATTR_ROCK) 313 { 314 /* path */ 315 strmemccpy(op_entry->path, plugin, OPEN_PLUGIN_BUFSZ); 316 317 if(!parameter) 318 parameter = ""; 319 strmemccpy(op_entry->param, parameter, OPEN_PLUGIN_BUFSZ); 320 } 321 else if (fattr == FILE_ATTR_OPX) 322 { 323 /* get the entry from the opx file */ 324 op_load_entry(0, OPEN_PLUGIN_LANG_IGNORE, op_entry, plugin); 325 } 326 else if(!parameter && lang_id != LANG_SHORTCUTS) 327 { 328 strmemccpy(op_entry->param, plugin, OPEN_PLUGIN_BUFSZ); 329 plugin = filetype_get_viewer(op_entry->path, OPEN_PLUGIN_BUFSZ, plugin); 330 if (!plugin) 331 { 332 logf("OP no plugin found to run %s", op_entry->param); 333 break; 334 } 335 } 336 else 337 { 338 break; 339 } 340 op_entry->hash = hash; 341 op_entry->lang_id = lang_id; 342 op_entry->checksum = open_plugin_csum + 343 (lang_id <= OPEN_PLUGIN_LANG_INVALID ? 0 : LANG_LAST_INDEX_IN_ARRAY); 344 logf("OP add_path name: %s %s %s", 345 op_entry->name, op_entry->path, op_entry->param); 346 return hash; 347 } 348 349 logf("OP add_path Invalid, *Clearing entry*"); 350 if (lang_id != LANG_SHORTCUTS) /* from shortcuts menu */ 351 splashf(HZ * 2, ID2P(LANG_OPEN_PLUGIN_NOT_A_PLUGIN), pos); 352 op_clear_entry(op_entry); 353 return 0; 354} 355 356/* only displays directories, .rock, and .opx files */ 357static bool callback_show_item(char *name, int attr, struct tree_context *tc) 358{ 359 (void)name; 360 (void)tc; 361#if 0 362 if(attr & ATTR_DIRECTORY) 363 { 364 if (strstr(tc->currdir, PLUGIN_DIR) != NULL) 365 return true; 366 tc->is_browsing = false; /* exit immediately */ 367 } 368 else if(attr & FILE_ATTR_ROCK) 369 { 370 return true; 371 } 372 else if(attr & FILE_ATTR_OPX) 373 { 374 return true; 375 } 376 return false; 377#endif 378 return attr & ATTR_DIRECTORY || 379 (filetype_supported(attr) && (attr & FILE_ATTR_AUDIO) != FILE_ATTR_AUDIO); 380} 381 382/* open_plugin_browse() 383* allows the user to browse for a plugin to set to a supplied key 384* if key is a lang_id that is used otherwise a hash of the key is created 385* for later recall of the plugin path 386*/ 387void open_plugin_browse(const char *key) 388{ 389 logf("%s", __func__); 390 391 char tmp_buf[OPEN_PLUGIN_BUFSZ+1]; 392 open_plugin_load_entry(key); 393 struct open_plugin_entry_t *op_entry = open_plugin_get_entry(); 394 395 logf("OP browse key: %s name: %s", 396 (key ? P2STR((unsigned char *)key):"No Key"), op_entry->name); 397 logf("OP browse %s %s", op_entry->path, op_entry->param); 398 399 if (op_entry->path[0] == '\0' || !file_exists(op_entry->path)) 400 strcpy(op_entry->path, PLUGIN_DIR"/"); 401 402 struct browse_context browse = { 403 .dirfilter = SHOW_ALL, 404 .flags = BROWSE_SELECTONLY | BROWSE_NO_CONTEXT_MENU | BROWSE_DIRFILTER, 405 .title = str(LANG_OPEN_PLUGIN), 406 .icon = Icon_Plugin, 407 .root = op_entry->path, 408 .buf = tmp_buf, 409 .bufsize = sizeof(tmp_buf), 410 .callback_show_item = callback_show_item, 411 }; 412 413 if (rockbox_browse(&browse) == GO_TO_PREVIOUS) 414 { 415 open_plugin_add_path(key, tmp_buf, NULL); 416 const char *path = tmp_buf; 417#ifdef HAVE_MULTIVOLUME 418 path = strstr(path, ROCKBOX_DIR); 419 if (!path) 420 path = tmp_buf; 421#endif 422 /* if this is a viewer ask the user if they want to set a parameter */ 423 if (op_entry->lang_id > 0 424 && strncmp(path, VIEWERS_DIR, sizeof(VIEWERS_DIR) -1) == 0) 425 { 426 if (yesno_pop(ID2P(LANG_PARAMETER))) 427 { 428 strcpy(op_entry->param, str(op_entry->lang_id)); 429 op_update_dat(op_entry, true); /* flush to disk so plugin can find it */ 430 plugin_load(VIEWERS_DIR "/open_plugins.rock", 431 P2STR((unsigned char *)key)); 432 } 433 } 434 } 435} 436 437/* open_plugin_load_entry() 438* recall of the plugin path and parameters based on supplied key 439* returns the index in OPEN_PLUGIN_DAT where the entry was found (>= 0) 440* if the entry was found but has not been saved returns OPEN_PLUGIN_NEEDS_FLUSHED 441* otherwise returns OPEN_PLUGIN_NOT_FOUND (< 0) if key was not found 442*/ 443int open_plugin_load_entry(const char *key) 444{ 445 if (key == NULL) 446 key = ID2P(LANG_OPEN_PLUGIN_NOT_A_PLUGIN); /* won't be found */ 447 448 struct open_plugin_entry_t *op_entry = open_plugin_get_entry(); 449 int opret; 450 uint32_t hash = 0; 451 int32_t lang_id = P2ID((unsigned char *)key); 452 const char* skey = P2STR((unsigned char *)key); /* string|LANGPTR => string */ 453 454 /*Note: P2ID() returns -1 if key isnt a valid lang_id */ 455 if (lang_id <= OPEN_PLUGIN_LANG_INVALID) 456 open_plugin_get_hash(strip_rockbox_root(skey), &hash); /* in open_plugin.h */ 457 458 opret = op_load_entry(hash, lang_id, op_entry, OPEN_PLUGIN_DAT); 459 logf("OP entry hash: %x lang id: %d ret: %d key: %s", hash, lang_id, opret, skey); 460 461 if (opret == OPEN_PLUGIN_NOT_FOUND && lang_id > OPEN_PLUGIN_LANG_INVALID) 462 { /* try rb defaults */ 463 opret = op_load_entry(hash, lang_id, op_entry, OPEN_RBPLUGIN_DAT); 464 logf("OP rb_entry hash: %x lang id: %d ret: %d key: %s", hash, lang_id, opret, skey); 465 /* add to the user plugin.dat file if found */ 466 op_update_dat(op_entry, false); 467 468 } 469 logf("OP entry ret: %s", (opret == OPEN_PLUGIN_NOT_FOUND ? "Not Found":"Found")); 470 return opret; 471} 472 473/* open_plugin_run() 474* recall of the plugin path and parameters based on supplied key 475* runs the plugin using plugin_load see plugin_load for return values 476*/ 477int open_plugin_run(const char *key) 478{ 479 int ret = 0; 480 int opret = open_plugin_load_entry(key); 481 struct open_plugin_entry_t *op_entry = open_plugin_get_entry(); 482 if (opret == OPEN_PLUGIN_NEEDS_FLUSHED) 483 op_update_dat(op_entry, false); 484 const char *path = op_entry->path; 485 const char *param = op_entry->param; 486 487 logf("OP run key: %s ret: %d name: %s", 488 (key ? P2STR((unsigned char *)key):"No Key"), opret, op_entry->name); 489 logf("OP run: %s %s %s", op_entry->name, path, param); 490 491 if (param[0] == '\0') 492 param = NULL; 493 if (path[0] == '\0' && key) 494 path = P2STR((unsigned char *)key); 495 496 ret = plugin_load(path, param); 497 498 if (ret != GO_TO_PLUGIN) 499 op_clear_entry(op_entry); 500 501 return ret; 502} 503 504/* open_plugin_cache_flush() 505* saves the current open_plugin_entry to disk 506*/ 507void open_plugin_cache_flush(void) 508{ 509 logf("%s", __func__); 510 struct open_plugin_entry_t *op_entry = open_plugin_get_entry(); 511 /* start_in_screen == 0 is 'Previous Screen' it is actually 512 * defined as (GO_TO_PREVIOUS = -2) + 2 for *Legacy?* reasons AFAICT */ 513 if (global_settings.start_in_screen == 0 && 514 global_status.last_screen == GO_TO_PLUGIN && 515 op_entry->lang_id > OPEN_PLUGIN_LANG_INVALID) 516 { 517 /* flush the last item as LANG_PREVIOUS_SCREEN if the user wants to resume */ 518 op_entry->lang_id = LANG_PREVIOUS_SCREEN; 519 } 520 op_update_dat(op_entry, true); 521} 522 523#endif /* ndef __PCTOOL__ */