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 * 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__ */