A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 728 lines 20 kB view raw
1/*************************************************************************** 2 * __________ __ ___. 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 * 8 * $Id$ 9 * 10 * Copyright (C) 2007 Jonathan Gordon 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 <string.h> 24#include <stdlib.h> 25#include <stdbool.h> 26#include "string.h" 27#include <ctype.h> 28 29#include "settings.h" 30#include "debug.h" 31#include "lang.h" 32#include "kernel.h" 33#include "plugin.h" 34#include "filetypes.h" 35#include "screens.h" 36#include "dir.h" 37#include "file.h" 38#include "splash.h" 39#include "core_alloc.h" 40#include "icons.h" 41/*#define LOGF_ENABLE*/ 42#include "logf.h" 43 44/* max filetypes (plugins & icons stored here) */ 45#define MAX_FILETYPES 192 46/* max viewer plugins */ 47#define MAX_VIEWERS 56 48 49static void fill_from_builtin(const char*,int) INIT_ATTR; 50static void read_builtin_types_init(void) INIT_ATTR; 51static void read_viewers_config_init(void) INIT_ATTR; 52static void read_config_init(int fd) INIT_ATTR; 53 54/* string array for known audio file types (tree_attr == FILE_ATTR_AUDIO) */ 55static const char* inbuilt_audio_filetypes[] = { 56 "mp3", "mp2", "mpa", "mp1", "ogg", "oga", "wma", "wmv", "asf", "wav", 57 "flac", "ac3", "a52", "mpc", "wv", "m4a", "m4b", "mp4", "mod", "mpga", 58 "shn", "aif", "aiff", "spx", "opus", "sid", "adx", "nsf", "nsfe", "spc", 59 "ape", "mac", "sap", "rm", "ra", "rmvb", "cmc", "cm3", "cmr", "cms", "dmc", 60 "dlt", "mpt", "mpd", "rmt", "tmc", "tm8", "tm2", "oma", "aa3", "at3", "mmf", 61 "au", "snd", "vox", "w64", "tta", "ay", "vtx", "gbs", "hes", "sgc", "vgm", 62 "vgz", "kss", "aac", 63}; 64 65struct filetype_inbuilt { 66 const char* extension; 67 int tree_attr; 68}; 69 70/* a table for the known file types, besides audio */ 71static const struct filetype_inbuilt inbuilt_filetypes[] = { 72 { "m3u", FILE_ATTR_M3U }, 73 { "m3u8", FILE_ATTR_M3U }, 74 { "cfg", FILE_ATTR_CFG }, 75 { "wps", FILE_ATTR_WPS }, 76#ifdef HAVE_REMOTE_LCD 77 { "rwps", FILE_ATTR_RWPS }, 78#endif 79#if CONFIG_TUNER 80 { "fmr", FILE_ATTR_FMR }, 81 { "fms", FILE_ATTR_FMS }, 82#endif 83 { "log", FILE_ATTR_LOG }, 84 { "lng", FILE_ATTR_LNG }, 85 { "rock", FILE_ATTR_ROCK }, 86 { "lua", FILE_ATTR_LUA }, 87 { "opx", FILE_ATTR_OPX }, 88 { "fnt", FILE_ATTR_FONT }, 89 { "kbd", FILE_ATTR_KBD }, 90 { "bmark",FILE_ATTR_BMARK }, 91 { "cue", FILE_ATTR_CUE }, 92 { "sbs", FILE_ATTR_SBS }, 93#ifdef HAVE_REMOTE_LCD 94 { "rsbs", FILE_ATTR_RSBS }, 95#if CONFIG_TUNER 96 { "rfms", FILE_ATTR_RFMS }, 97#endif 98#endif 99#ifdef BOOTFILE_EXT 100 { BOOTFILE_EXT, FILE_ATTR_MOD }, 101#endif 102#ifdef BOOTFILE_EXT2 103 { BOOTFILE_EXT2, FILE_ATTR_MOD }, 104#endif 105}; 106 107struct fileattr_icon_voice { 108 int tree_attr; 109 uint16_t icon; 110 uint16_t voiceclip; 111}; 112 113/* a table for the known file types icons & voice clips */ 114static const struct fileattr_icon_voice inbuilt_attr_icons_voices[] = { 115 { FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA }, 116 { FILE_ATTR_M3U, Icon_Playlist, LANG_PLAYLIST }, 117 { FILE_ATTR_CFG, Icon_Config, VOICE_EXT_CFG }, 118 { FILE_ATTR_WPS, Icon_Wps, VOICE_EXT_WPS }, 119#ifdef HAVE_REMOTE_LCD 120 {FILE_ATTR_RWPS, Icon_Wps, VOICE_EXT_RWPS }, 121#endif 122#if CONFIG_TUNER 123 { FILE_ATTR_FMR, Icon_Preset, LANG_FMR }, 124 { FILE_ATTR_FMS, Icon_Wps, VOICE_EXT_FMS }, 125#endif 126 { FILE_ATTR_LNG, Icon_Language, LANG_LANGUAGE }, 127 { FILE_ATTR_ROCK, Icon_Plugin, VOICE_EXT_ROCK }, 128 { FILE_ATTR_LUA, Icon_Plugin, VOICE_EXT_ROCK }, 129 { FILE_ATTR_OPX, Icon_Plugin, VOICE_EXT_ROCK }, 130 { FILE_ATTR_FONT, Icon_Font, VOICE_EXT_FONT }, 131 { FILE_ATTR_KBD, Icon_Keyboard, VOICE_EXT_KBD }, 132 { FILE_ATTR_BMARK, Icon_Bookmark, VOICE_EXT_BMARK }, 133 { FILE_ATTR_CUE, Icon_Bookmark, VOICE_EXT_CUESHEET }, 134 { FILE_ATTR_SBS, Icon_Wps, VOICE_EXT_SBS }, 135#ifdef HAVE_REMOTE_LCD 136 { FILE_ATTR_RSBS, Icon_Wps, VOICE_EXT_RSBS }, 137#if CONFIG_TUNER 138 { FILE_ATTR_RFMS, Icon_Wps, VOICE_EXT_RFMS }, 139#endif 140#endif 141#if defined(BOOTFILE_EXT) || defined(BOOTFILE_EXT2) 142 { FILE_ATTR_MOD, Icon_Firmware, VOICE_EXT_AJZ }, 143#endif 144}; 145 146static int filetype_inbuilt_index(int tree_attr) 147{ 148 size_t count = ARRAY_SIZE(inbuilt_attr_icons_voices); 149 /* try to find a inbuilt index for the extension, if known */ 150 tree_attr &= FILE_ATTR_MASK; /* file type */ 151 152 for (size_t i = count - 1; i < count; i--) 153 { 154 if (tree_attr == inbuilt_attr_icons_voices[i].tree_attr) 155 { 156 logf("%s found attr %d id", __func__, tree_attr); 157 return i; 158 } 159 } 160 logf("%s not found attr %d", __func__, tree_attr); 161 return -1; 162} 163 164long tree_get_filetype_voiceclip(int attr) 165{ 166 if (global_settings.talk_filetype) 167 { 168 int index = filetype_inbuilt_index(attr); 169 if (index >= 0) 170 { 171 logf("%s found attr %d id %d", __func__, attr, 172 inbuilt_attr_icons_voices[index].voiceclip); 173 return inbuilt_attr_icons_voices[index].voiceclip; 174 } 175 } 176 logf("%s not found attr %d", __func__, attr); 177 return -1; 178} 179 180#define ROCK_EXTENSION "rock" 181 182struct file_type { 183 enum themable_icons icon; /* the icon which shall be used for it, NOICON if unknown */ 184 unsigned char attr; /* FILE_ATTR_MASK >> 8 */ 185 const char* plugin; /* Which plugin to use, NULL if unknown, or builtin */ 186 const char* extension; /* NULL for none */ 187}; 188 189static struct file_type filetypes[MAX_FILETYPES]; 190 191static enum themable_icons custom_filetype_icons[MAX_FILETYPES]; 192static bool custom_icons_loaded = false; 193 194#ifdef HAVE_LCD_COLOR 195static int custom_colors[MAX_FILETYPES]; 196struct filetype_unknown { 197 enum themable_icons icon; 198 int color; 199}; 200static struct filetype_unknown unknown_file = { 201 .icon = Icon_NOICON, 202 .color = -1, 203}; 204#else 205struct filetype_unknown { enum themable_icons icon; }; 206static struct filetype_unknown unknown_file = { .icon = Icon_NOICON }; 207#endif 208 209/* index array to filetypes used in open with list. */ 210static int viewers[MAX_VIEWERS]; 211static int filetype_count = 0; 212static unsigned char highest_attr = 0; 213static int viewer_count = 0; 214 215static int strdup_handle, strdup_cur_idx; 216static size_t strdup_bufsize; 217static int move_callback(int handle, void* current, void* new) 218{ 219 /*could compare to strdup_handle, but ops is only used once */ 220 (void)handle; 221 size_t diff = new - current; 222#define FIX_PTR(x) \ 223 { if ((void*)x >= current && (void*)x < (current+strdup_bufsize)) x+= diff; } 224 for(int i = 0; i < filetype_count; i++) 225 { 226 FIX_PTR(filetypes[i].extension); 227 FIX_PTR(filetypes[i].plugin); 228 } 229 return BUFLIB_CB_OK; 230} 231 232static struct buflib_callbacks ops = { 233 .move_callback = move_callback, 234 .shrink_callback = NULL, 235}; 236 237static const char *filetypes_strdup(const char* string) 238{ 239 char *buffer = core_get_data(strdup_handle) + strdup_cur_idx; 240 strdup_cur_idx += strlcpy(buffer, string, strdup_bufsize-strdup_cur_idx)+1; 241 return buffer; 242} 243 244static const char *filetypes_store_plugin(const char *plugin, int n) 245{ 246 int i; 247 /* if the plugin is in the list already, use it. */ 248 for (i=0; i<viewer_count; i++) 249 { 250 if (!strcmp(filetypes[viewers[i]].plugin, plugin)) 251 return filetypes[viewers[i]].plugin; 252 } 253 /* otherwise, allocate buffer */ 254 if (viewer_count < MAX_VIEWERS) 255 viewers[viewer_count++] = n; 256 return filetypes_strdup(plugin); 257} 258 259static int find_extension(const char* extension) 260{ 261 if (extension) 262 { 263 for (int i=1; i<filetype_count; i++) 264 { 265 if (filetypes[i].extension && 266 !strcasecmp(extension, filetypes[i].extension)) 267 return i; 268 } 269 } 270 return -1; 271} 272 273#ifdef HAVE_LCD_COLOR 274/* Colors file format is similar to icons: 275 * ext:hex_color 276 * load a colors file from a theme with: 277 * filetype colours: filename.colours */ 278void read_color_theme_file(void) { 279 char buffer[MAX_PATH]; 280 int fd; 281 char *ext, *color; 282 int i; 283 for (i = 0; i < MAX_FILETYPES; i++) { 284 custom_colors[i] = -1; 285 } 286 unknown_file.color = -1; 287 if (!global_settings.colors_file[0] || global_settings.colors_file[0] == '-') 288 return; 289 290 fd = open_pathfmt(buffer, sizeof(buffer), O_RDONLY, 291 THEME_DIR "/%s.colours", global_settings.colors_file); 292 if (fd < 0) 293 return; 294 while (read_line(fd, buffer, MAX_PATH) > 0) 295 { 296 if (!settings_parseline(buffer, &ext, &color)) 297 continue; 298 if (!strcasecmp(ext, "folder")) 299 { 300 hex_to_rgb(color, &custom_colors[0]); 301 continue; 302 } 303 if (!strcmp(ext, "???")) 304 { 305 hex_to_rgb(color, &unknown_file.color); 306 continue; 307 } 308 i = find_extension(ext); 309 if (i >= 0) 310 hex_to_rgb(color, &custom_colors[i]); 311 } 312 close(fd); 313} 314#endif 315 316static int parse_icon(const char *line, enum themable_icons *icon) 317{ 318 int num = -1; 319 if (*line == '*') 320 { 321 num = atoi(line+1); 322 *icon = num; 323 } 324 else if (*line == '-') 325 { 326 *icon = Icon_NOICON; 327 } 328 else if (*line >= '0' && *line <= '9') 329 { 330 num = atoi(line); 331 *icon = Icon_Last_Themeable + num; 332 } 333 return num; 334} 335 336void read_viewer_theme_file(void) 337{ 338 char buffer[MAX_PATH]; 339 int fd; 340 char *ext, *icon; 341 int i; 342 enum themable_icons *icon_dest; 343 global_status.viewer_icon_count = 0; 344 custom_icons_loaded = false; 345 /*custom_filetype_icons[0] = Icon_Folder; filetypes[0] is folder icon.. */ 346 for (i=0; i<filetype_count; i++) 347 { 348 custom_filetype_icons[i] = filetypes[i].icon; 349 } 350 351 fd = open_pathfmt(buffer, sizeof(buffer), O_RDONLY, 352 ICON_DIR "/%s.icons", global_settings.viewers_icon_file); 353 if (fd < 0) 354 return; 355 356 while (read_line(fd, buffer, MAX_PATH) > 0) 357 { 358 if (!settings_parseline(buffer, &ext, &icon)) 359 continue; 360 i = find_extension(ext); 361 if (i >= 0) 362 icon_dest = &custom_filetype_icons[i]; 363 else if (!strcmp(ext, "???")) 364 icon_dest = &unknown_file.icon; 365 else 366 icon_dest = NULL; 367 368 if (icon_dest) 369 { 370 if (parse_icon(icon, icon_dest) > global_status.viewer_icon_count) 371 global_status.viewer_icon_count++; 372 } 373 } 374 close(fd); 375 custom_icons_loaded = true; 376} 377 378static void read_viewers_config_init(void) 379{ 380 int fd = open(VIEWERS_CONFIG, O_RDONLY); 381 if(fd < 0) 382 return; 383 384 off_t filesz = filesize(fd); 385 if(filesz <= 0) 386 goto out; 387 388 /* estimate bufsize with the filesize, will not be larger */ 389 strdup_bufsize = (size_t)filesz; 390 strdup_handle = core_alloc_ex(strdup_bufsize, &ops); 391 if(strdup_handle <= 0) 392 goto out; 393 394 read_config_init(fd); 395 core_shrink(strdup_handle, NULL, strdup_cur_idx); 396 397 out: 398 close(fd); 399} 400 401void filetype_init(void) 402{ 403 /* set the directory item first */ 404 filetypes[0].extension = NULL; 405 filetypes[0].plugin = NULL; 406 filetypes[0].attr = 0; 407 filetypes[0].icon = Icon_Folder; 408 409 viewer_count = 0; 410 filetype_count = 1; 411 412 read_builtin_types_init(); 413 read_viewers_config_init(); 414 read_viewer_theme_file(); 415#ifdef HAVE_LCD_COLOR 416 read_color_theme_file(); 417#endif 418} 419 420/* remove all white spaces from string */ 421static void rm_whitespaces(char* str) 422{ 423 char *s = str; 424 while (*str) 425 { 426 if (!isspace(*str)) 427 { 428 *s = *str; 429 s++; 430 } 431 str++; 432 } 433 *s = '\0'; 434} 435 436static void fill_from_builtin(const char *ext, int tree_attr) 437{ 438 if (filetype_count >= MAX_FILETYPES) 439 return; 440 441 struct file_type *filetype = &filetypes[filetype_count]; 442 filetype->icon = unknown_file.icon; 443 filetype->attr = tree_attr>>8; 444 filetype->plugin = NULL; 445 filetype->extension = ext; 446 447 if (filetype->attr > highest_attr) 448 highest_attr = filetype->attr; 449 450 int index = filetype_inbuilt_index(tree_attr); 451 if (index >= 0) 452 { 453 filetype->icon = inbuilt_attr_icons_voices[index].icon; 454 } 455 456 filetype_count++; 457} 458 459static void read_builtin_types_init(void) 460{ 461 for(size_t i = 0; (i < ARRAY_SIZE(inbuilt_audio_filetypes)); i++) 462 { 463 fill_from_builtin(inbuilt_audio_filetypes[i], FILE_ATTR_AUDIO); 464 } 465 466 for(size_t i = 0; (i < ARRAY_SIZE(inbuilt_filetypes)); i++) 467 { 468 fill_from_builtin(inbuilt_filetypes[i].extension, 469 inbuilt_filetypes[i].tree_attr); 470 } 471} 472 473static void read_config_init(int fd) 474{ 475 char line[64], *s, *e; 476 const char *extension, *plugin; 477 /* config file is in the format 478 <extension>,<plugin>,<icon code> 479 ignore line if either of the first two are missing */ 480 while (read_line(fd, line, sizeof line) > 0) 481 { 482 if (filetype_count >= MAX_FILETYPES) 483 { 484 splash(HZ, ID2P(LANG_FILETYPES_FULL)); 485 break; 486 } 487 rm_whitespaces(line); 488 /* get the extension */ 489 s = line; 490 e = strchr(s, ','); 491 if (!e) 492 continue; 493 *e = '\0'; 494 extension = s; 495 496 /* get the plugin */ 497 s = e+1; 498 e = strchr(s, ','); 499 if (!e) 500 continue; 501 *e = '\0'; 502 plugin = s; 503 504 if (!strcmp("???", extension)) 505 { 506 /* get the icon */ 507 s = e+1; 508 parse_icon(s, &unknown_file.icon); 509 continue; 510 } 511 512 /* ok, store this plugin/extension, check icon after */ 513 struct file_type *file_type = &filetypes[filetype_count]; 514 file_type->extension = filetypes_strdup(extension); 515 file_type->plugin = filetypes_store_plugin(plugin, filetype_count); 516 file_type->attr = highest_attr +1; 517 file_type->icon = Icon_Questionmark; 518 highest_attr++; 519 /* get the icon */ 520 s = e+1; 521 parse_icon(s, &file_type->icon); 522 filetype_count++; 523 } 524} 525 526static int file_find_extension(const char* file) 527{ 528 char *extension = strrchr(file, '.'); 529 if (extension) 530 extension++; 531 return find_extension(extension); 532} 533 534int filetype_get_attr(const char* file) 535{ 536 int i = file_find_extension(file); 537 if (i >= 0) 538 return (filetypes[i].attr<<8)&FILE_ATTR_MASK; 539 return 0; 540} 541 542static int find_attr(int attr) 543{ 544 int i; 545 /* skip the directory item */ 546 if ((attr & ATTR_DIRECTORY)==ATTR_DIRECTORY) 547 return 0; 548 for (i=1; i<filetype_count; i++) 549 { 550 if ((attr>>8) == filetypes[i].attr) 551 return i; 552 } 553 return -1; 554} 555 556#ifdef HAVE_LCD_COLOR 557int filetype_get_color(const char * name, int attr) 558{ 559 if ((attr & ATTR_DIRECTORY)==ATTR_DIRECTORY) 560 return custom_colors[0]; 561 int i = file_find_extension(name); 562 if (i <= 0) 563 return unknown_file.color; 564 return custom_colors[i]; 565} 566#endif 567 568int filetype_get_icon(int attr) 569{ 570 int index = find_attr(attr); 571 if (index < 0) 572 return unknown_file.icon; 573 if (custom_icons_loaded) 574 return custom_filetype_icons[index]; 575 return filetypes[index].icon; 576} 577 578static int filetype_get_plugin_index(int attr) 579{ 580 int index = find_attr(attr); 581 if (index < 0) 582 return -1; 583 struct file_type *ft_indexed = &filetypes[index]; 584 585 /* attempt to find a suitable viewer by file extension */ 586 if(ft_indexed->plugin == NULL && ft_indexed->extension != NULL) 587 { 588 struct file_type *ft; 589 int i = filetype_count; 590 while (--i > index) 591 { 592 ft = &filetypes[i]; 593 if (ft->plugin == NULL || ft->extension == NULL) 594 continue; 595 else if (ft->plugin != NULL && 596 strcmp(ft->extension, ft_indexed->extension) == 0) 597 { 598 /*splashf(HZ*3, "Found %d %s %s", i, ft->extension, ft->plugin);*/ 599 return i; 600 } 601 } 602 } 603 if (ft_indexed->plugin == NULL) 604 index = -1; 605 return index; /* Not Found */ 606} 607 608char* filetype_get_plugin(int attr, char *buffer, size_t buffer_len) 609{ 610 int index = filetype_get_plugin_index(attr); 611 if (index < 0 || !buffer) 612 return NULL; 613 614 struct file_type *ft_indexed = &filetypes[index]; 615 616 snprintf(buffer, buffer_len, "%s/%s." ROCK_EXTENSION, 617 PLUGIN_DIR, ft_indexed->plugin); 618 return buffer; 619} 620 621bool filetype_supported(int attr) 622{ 623 return find_attr(attr) >= 0; 624} 625 626/**** Open With Screen ****/ 627struct cb_data { 628 const char *current_file; 629}; 630 631static enum themable_icons openwith_get_icon(int selected_item, void * data) 632{ 633 (void)data; 634 return filetypes[viewers[selected_item]].icon; 635} 636 637static const char* openwith_get_name(int selected_item, void * data, 638 char * buffer, size_t buffer_len) 639{ 640 (void)data; (void)buffer; (void)buffer_len; 641 const char *s = strrchr(filetypes[viewers[selected_item]].plugin, '/'); 642 if (s) 643 return s+1; 644 else return filetypes[viewers[selected_item]].plugin; 645} 646 647static int openwith_get_talk(int selected_item, void * data) 648{ 649 (void)data; 650 char viewer_filename[MAX_FILENAME]; 651 snprintf(viewer_filename, MAX_FILENAME, "%s." ROCK_EXTENSION, 652 filetypes[viewers[selected_item]].plugin); 653 talk_file_or_spell(PLUGIN_DIR, viewer_filename, 654 NULL, false); 655 return 0; 656} 657 658char* filetype_get_viewer(char *buffer, size_t buffer_len, const char* current_file) 659{ 660 int attr = filetype_get_attr(current_file); 661 662 struct simplelist_info info; 663 simplelist_info_init(&info, str(LANG_ONPLAY_OPEN_WITH), viewer_count, NULL); 664 665 int default_index = filetype_get_plugin_index(attr); 666 667 if (default_index >= 0) 668 { 669 for (int i = 0; i < viewer_count; i++) 670 if (viewers[i] == default_index) 671 { 672 info.selection = i; 673 break; 674 } 675 } 676 677 info.get_name = openwith_get_name; 678 info.get_icon = global_settings.show_icons?openwith_get_icon:NULL; 679 info.get_talk = openwith_get_talk; 680 681 simplelist_show_list(&info); 682 683 if (info.selection >= 0) /* run user selected viewer */ 684 { 685 int i = viewers[info.selection]; 686 snprintf(buffer, buffer_len, "%s/%s." ROCK_EXTENSION, 687 PLUGIN_DIR, filetypes[i].plugin); 688 return buffer; 689 } 690 return NULL; 691 692} 693 694int filetype_list_viewers(const char* current_file) 695{ 696 int ret = PLUGIN_ERROR; 697 char plugin[MAX_PATH]; 698 if (filetype_get_viewer(plugin, sizeof(plugin), current_file) != NULL) 699 ret = plugin_load(plugin, current_file); 700 return ret; 701} 702 703int filetype_load_plugin(const char* plugin, const char* file) 704{ 705 int i; 706 char plugin_name[MAX_PATH]; 707 char *s; 708 709 for (i=1;i<filetype_count;i++) 710 { 711 if (filetypes[i].plugin) 712 { 713 s = strrchr(filetypes[i].plugin, '/'); 714 if (s) 715 { 716 if (!strcmp(s+1, plugin)) 717 break; 718 } 719 else if (!strcmp(filetypes[i].plugin, plugin)) 720 break; 721 } 722 } 723 if (i >= filetype_count) 724 return PLUGIN_ERROR; 725 snprintf(plugin_name, MAX_PATH, "%s/%s." ROCK_EXTENSION, 726 PLUGIN_DIR, filetypes[i].plugin); 727 return plugin_load(plugin_name, file); 728}