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

Configure Feed

Select the types of activity you want to include in your feed.

at master 659 lines 21 kB view raw
1/*************************************************************************** 2 * __________ __ ___. 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 * \/ \/ \/ \/ \/ 8 * $Id$ 9 * 10 * Copyright (C) 2002 Björn Stenberg 11 * Copyright (C) 2024 William Wilgus 12 * 13 * This program is free software; you can redistribute it and/or 14 * modify it under the terms of the GNU General Public License 15 * as published by the Free Software Foundation; either version 2 16 * of the License, or (at your option) any later version. 17 * 18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 19 * KIND, either express or implied. 20 * 21 ****************************************************************************/ 22#include <stdbool.h> 23#include <errno.h> 24#include <stdio.h> 25#include <stdlib.h> 26#include <string.h> 27#include "string-extra.h" 28#include "debug.h" 29#include "powermgmt.h" 30 31#include "misc.h" 32#include "plugin.h" 33#include "dir.h" 34#include "tree.h" 35#include "fileop.h" 36#include "pathfuncs.h" 37 38#include "settings.h" 39#include "lang.h" 40#include "yesno.h" 41#include "splash.h" 42#include "keyboard.h" 43 44/* Used for directory move, copy and delete */ 45struct file_op_params 46{ 47 char path[MAX_PATH]; /* Buffer for full path */ 48 const char* toplevel_name; 49 bool is_dir; 50 int objects; /* how many files and subdirectories*/ 51 int processed; 52 unsigned long long total_size; 53 unsigned long long processed_size; 54 size_t append; /* Append position in 'path' for stack push */ 55 size_t extra_len; /* Length added by dst path compared to src */ 56}; 57 58static int prompt_name(char* buf, size_t bufsz) 59{ 60 if (kbd_input(buf, bufsz, NULL) < 0) 61 return FORC_CANCELLED; 62 /* at least prevent escapes out of the base directory from keyboard- 63 entered filenames; the file code should reject other invalidities */ 64 if (*buf != '\0' && !strchr(buf, PATH_SEPCH) && !is_dotdir_name(buf)) 65 return FORC_SUCCESS; 66 return FORC_UNKNOWN_FAILURE; 67} 68 69static bool poll_cancel_action(int operation, struct file_op_params *param) 70{ 71 static unsigned long last_tick; 72 73 if (operation == FOC_COUNT) 74 { 75 if (param->objects <= 1) 76 last_tick = current_tick; 77 else if (TIME_AFTER(current_tick, last_tick + HZ/2)) 78 { 79 clear_screen_buffer(false); 80 splashf(0, "%s (%d)", str(LANG_SCANNING_DISK), param->objects); 81 last_tick = current_tick; 82 } 83 } 84 else 85 { 86 const char *op_str = (operation == FOC_DELETE) ? str(LANG_DELETING) : 87 (operation == FOC_COPY) ? str(LANG_COPYING) : 88 str(LANG_MOVING); 89 90 if ((operation == FOC_DELETE || !param->total_size) && 91 param->objects > 0) 92 { 93 splash_progress(param->processed, param->objects, 94 "%s %s", op_str, param->toplevel_name); 95 } 96 else if (param->total_size >= 10 * 1024 * 1024) 97 { 98 int total_shft = (int) (param->total_size >> 15); 99 int current_shft = (int) (param->processed_size >> 15); 100 const char *unit_str = str(LANG_MEBIBYTE); 101 splash_progress(current_shft, total_shft, 102 "%s %s (%d %s)\n%d %s", 103 op_str, param->toplevel_name, 104 total_shft >> 5, unit_str, 105 current_shft >> 5, unit_str); 106 } 107 else if (param->total_size >= 1024) 108 { 109 int total_kib = (int) (param->total_size >> 10); 110 int current_kib = (int) (param->processed_size >> 10); 111 const char *unit_str = str(LANG_KIBIBYTE); 112 splash_progress(current_kib, total_kib, 113 "%s %s (%d %s)\n%d %s", 114 op_str, param->toplevel_name, 115 total_kib, unit_str, 116 current_kib, unit_str); 117 } 118 } 119 return ACTION_STD_CANCEL == get_action(CONTEXT_STD, TIMEOUT_NOBLOCK); 120} 121 122static void init_file_op(struct file_op_params *param, 123 const char *basename, 124 const char *selected_file) 125{ 126 /* Assumes: basename will never be NULL */ 127 if (selected_file == NULL) 128 { 129 param->append = strlcpy(param->path, basename, sizeof (param->path)); 130 path_basename(basename, &basename); 131 param->toplevel_name = basename; 132 } 133 else 134 { 135 param->append = path_append(param->path, basename, 136 selected_file, sizeof (param->path)); 137 param->toplevel_name = selected_file; 138 } 139 param->is_dir = dir_exists(param->path); 140 param->extra_len = 0; 141 param->objects = 0; /* how many files and subdirectories*/ 142 param->processed = 0; 143 param->total_size = 0; 144 param->processed_size = 0; 145} 146 147/* counts file objects, deletes file objects */ 148static int directory_fileop(struct file_op_params *param, enum file_op_current fileop) 149{ 150 errno = 0; 151 DIR *dir = opendir(param->path); 152 if (!dir) { 153 if (errno == EMFILE) { 154 return FORC_TOO_MANY_SUBDIRS; 155 } 156 return FORC_PATH_NOT_EXIST; /* open error */ 157 } 158 int rc = FORC_SUCCESS; 159 size_t append = param->append; 160 161 /* walk through the directory content */ 162 while (rc == FORC_SUCCESS) { 163 errno = 0; /* distinguish failure from eod */ 164 struct dirent *entry = readdir(dir); 165 if (!entry) { 166 if (errno) { 167 rc = FORC_PATH_NOT_EXIST; 168 } 169 break; 170 } 171 172 struct dirinfo info = dir_get_info(dir, entry); 173 if ((info.attribute & ATTR_DIRECTORY) && 174 is_dotdir_name(entry->d_name)) { 175 continue; /* skip these */ 176 } 177 178 /* append name to current directory */ 179 param->append = append + path_append(&param->path[append], 180 PA_SEP_HARD, entry->d_name, 181 sizeof (param->path) - append); 182 183 if (fileop == FOC_DELETE) 184 { 185 param->processed++; 186 /* at this point we've already counted and never had a path too long 187 in the other branch so we 'should not' encounter one here either */ 188 189 if (param->processed > param->objects) 190 { 191 rc = FORC_UNKNOWN_FAILURE; 192 break; 193 } 194 195 if (info.attribute & ATTR_DIRECTORY) { 196 /* remove a subdirectory */ 197 rc = directory_fileop(param, fileop); /* recursion */ 198 } else { 199 /* remove a file */ 200 if (poll_cancel_action(FOC_DELETE, param)) 201 { 202 rc = FORC_CANCELLED; 203 break; 204 } 205 rc = remove(param->path); 206 } 207 } 208 else /* count objects */ 209 { 210 param->objects++; 211 param->total_size += info.size; 212 213 if (poll_cancel_action(FOC_COUNT, param)) 214 { 215 rc = FORC_CANCELLED; 216 break; 217 } 218 219 if (param->append + param->extra_len >= sizeof (param->path)) { 220 rc = FORC_PATH_TOO_LONG; 221 break; /* no space left in buffer */ 222 } 223 224 if (info.attribute & ATTR_DIRECTORY) { 225 /* enter subdirectory */ 226 rc = directory_fileop(param, FOC_COUNT); /* recursion */ 227 } 228 } 229 param->append = append; /* other functions may use param, reset append */ 230 /* Remove basename we added above */ 231 param->path[append] = '\0'; 232 } 233 234 closedir(dir); 235 236 if (fileop == FOC_DELETE && rc == FORC_SUCCESS) { 237 /* remove the now empty directory */ 238 if (poll_cancel_action(FOC_DELETE, param)) 239 { 240 rc = FORC_CANCELLED; 241 } else { 242 rc = rmdir(param->path); 243 } 244 } 245 246 return rc; 247} 248 249/* Walk a directory tree and count the number of objects (dirs & files) 250 * also check that enough resources exist to do an operation */ 251static int check_count_fileobjects(struct file_op_params *param) 252{ 253 cpu_boost(true); 254 int rc = directory_fileop(param, FOC_COUNT); 255 cpu_boost(false); 256 DEBUGF("%s res:(%d) objects %d \n", __func__, rc, param->objects); 257 return rc; 258} 259 260/* Attempt to just rename a file or directory */ 261static int move_by_rename(struct file_op_params *src, 262 const char *dst_path, 263 unsigned int *pflags) 264{ 265 unsigned int flags = *pflags; 266 int rc = FORC_UNKNOWN_FAILURE; 267 reset_poweroff_timer(); 268 if (!(flags & (PASTE_COPY | PASTE_EXDEV))) { 269 if ((flags & PASTE_OVERWRITE) || !file_exists(dst_path)) { 270 /* Just try to move the directory / file */ 271 rc = rename(src->path, dst_path); 272#ifdef HAVE_MULTIVOLUME 273 if (rc < FORC_SUCCESS && errno == EXDEV) { 274 /* Failed because cross volume rename doesn't work */ 275 *pflags |= PASTE_EXDEV; /* force a move instead */ 276 } 277#endif /* HAVE_MULTIVOLUME */ 278 /* if (errno == ENOTEMPTY && (flags & PASTE_OVERWRITE)) { 279 * Directory is not empty thus rename() will not do a quick overwrite */ 280 } 281 282 } 283 return rc; 284} 285 286/* Paste a file */ 287static int copy_move_file(struct file_op_params *src, const char *dst_path, 288 unsigned int flags) 289{ 290 /* Try renaming first */ 291 int rc = move_by_rename(src, dst_path, &flags); 292 if (rc == FORC_SUCCESS) 293 { 294 src->total_size = 0; /* switch from counting size to number of items */ 295 return rc; 296 } 297 298 /* See if we can get the plugin buffer for the file copy buffer */ 299 size_t buffersize; 300 char *buffer = (char *) plugin_get_buffer(&buffersize); 301 if (buffer == NULL || buffersize < 512) { 302 /* Not large enough, try for a disk sector worth of stack 303 instead */ 304 buffersize = 512; 305 buffer = (char *)alloca(buffersize); 306 } 307 308 if (buffer == NULL) { 309 return FORC_NO_BUFFER_AVAIL; 310 } 311 312 buffersize &= ~0x1ff; /* Round buffer size to multiple of sector size */ 313 314 int src_fd = open(src->path, O_RDONLY); 315 if (src_fd >= 0) { 316 off_t src_sz = lseek(src_fd, 0, SEEK_END); 317 if (!src->total_size && !src->processed) /* single file copy */ 318 src->total_size = src_sz; 319 lseek(src_fd, 0, SEEK_SET); 320 321 int oflag = O_WRONLY|O_CREAT; 322 323 if (!(flags & PASTE_OVERWRITE)) { 324 oflag |= O_EXCL; 325 } 326 327 int dst_fd = open(dst_path, oflag, 0666); 328 if (dst_fd >= 0) { 329 off_t total_size = 0; 330 off_t next_cancel_test = 0; /* No excessive button polling */ 331 332 rc = FORC_SUCCESS; 333 334 while (rc == FORC_SUCCESS) { 335 if (total_size >= next_cancel_test) { 336 next_cancel_test = total_size + 0x10000; 337 if (poll_cancel_action(!(flags & PASTE_COPY) ? 338 FOC_MOVE : FOC_COPY, src)) 339 { 340 rc = FORC_CANCELLED; 341 break; 342 } 343 } 344 345 ssize_t bytesread = read(src_fd, buffer, buffersize); 346 if (bytesread <= 0) { 347 if (bytesread < 0) { 348 rc = FORC_READ_FAILURE; 349 } 350 /* else eof on buffer boundary; nothing to write */ 351 break; 352 } 353 354 ssize_t byteswritten = write(dst_fd, buffer, bytesread); 355 if (byteswritten < bytesread) { 356 /* Some I/O error */ 357 rc = FORC_WRITE_FAILURE; 358 break; 359 } 360 361 total_size += byteswritten; 362 src->processed_size += byteswritten; 363 364 if (bytesread < (ssize_t)buffersize) { 365 /* EOF with trailing bytes */ 366 break; 367 } 368 } 369 370 if (rc == FORC_SUCCESS) { 371 if (total_size != src_sz) 372 rc = FORC_UNKNOWN_FAILURE; 373 else { 374 /* If overwriting, set the correct length if original was longer */ 375 rc = ftruncate(dst_fd, total_size) * 10; 376 } 377 } 378 379 close(dst_fd); 380 381 if (rc != FORC_SUCCESS) { 382 /* Copy failed. Cleanup. */ 383 remove(dst_path); 384 } 385 } 386 387 close(src_fd); 388 } 389 390 if (rc == FORC_SUCCESS && !(flags & PASTE_COPY)) { 391 /* Remove the source file */ 392 rc = remove(src->path) * 10; 393 } 394 395 return rc; 396} 397 398/* Paste a directory */ 399static int copy_move_directory(struct file_op_params *src, 400 struct file_op_params *dst, 401 unsigned int flags) 402{ 403 DIR *srcdir = opendir(src->path); 404 405 if (!srcdir) 406 return FORC_PATH_NOT_EXIST; 407 408 /* Make a directory to copy things to */ 409 int rc = mkdir(dst->path) * 10; 410 if (rc < 0 && errno == EEXIST && (flags & PASTE_OVERWRITE)) { 411 /* Exists and overwrite was approved */ 412 rc = FORC_SUCCESS; 413 } 414 415 size_t srcap = src->append, dstap = dst->append; 416 417 /* Walk through the directory content; this loop will exit as soon as 418 there's a problem */ 419 while (rc == FORC_SUCCESS) { 420 errno = 0; /* Distinguish failure from eod */ 421 struct dirent *entry = readdir(srcdir); 422 if (!entry) { 423 if (errno) { 424 rc = FORC_PATH_NOT_EXIST; 425 } 426 break; 427 } 428 429 struct dirinfo info = dir_get_info(srcdir, entry); 430 if ((info.attribute & ATTR_DIRECTORY) && 431 is_dotdir_name(entry->d_name)) { 432 continue; /* Skip these */ 433 } 434 435 /* Append names to current directories */ 436 src->append = srcap + 437 path_append(&src->path[srcap], PA_SEP_HARD, entry->d_name, 438 sizeof (src->path) - srcap); 439 440 dst->append = dstap + 441 path_append(&dst->path[dstap], PA_SEP_HARD, entry->d_name, 442 sizeof (dst->path) - dstap); 443 /* src length was already checked by check_count_fileobjects() */ 444 if (dst->append >= sizeof (dst->path)) { 445 rc = FORC_PATH_TOO_LONG; /* No space left in buffer */ 446 break; 447 } 448 449 src->processed++; 450 if (src->processed > src->objects) 451 { 452 rc = FORC_UNKNOWN_FAILURE; 453 break; 454 } 455 456 if (poll_cancel_action(!(flags & PASTE_COPY) ? 457 FOC_MOVE : FOC_COPY, src)) 458 { 459 rc = FORC_CANCELLED; 460 break; 461 } 462 463 DEBUGF("Copy %s to %s\n", src->path, dst->path); 464 465 if (info.attribute & ATTR_DIRECTORY) { 466 src->processed_size += info.size; 467 /* Copy/move a subdirectory */ 468 rc = copy_move_directory(src, dst, flags); /* recursion */; 469 } else { 470 /* Copy/move a file */ 471 rc = copy_move_file(src, dst->path, flags); 472 } 473 474 /* Remove basenames we added above */ 475 src->path[srcap] = '\0'; 476 dst->path[dstap] = '\0'; 477 } 478 479 if (rc == FORC_SUCCESS && !(flags & PASTE_COPY)) { 480 /* Remove the now empty directory */ 481 rc = rmdir(src->path) * 10; 482 } 483 484 closedir(srcdir); 485 return rc; 486} 487 488/************************************************************************************/ 489/* PUBLIC FUNCTIONS */ 490/************************************************************************************/ 491 492/* Copy or move a file or directory see: file_op_flags */ 493int copy_move_fileobject(const char *src_path, const char *dst_path, unsigned int flags) 494{ 495 if (!src_path[0]) 496 return FORC_NOOP; 497 498 struct file_op_params src, dst; 499 500 /* Figure out the name of the selection */ 501 const char *nameptr; 502 path_basename(src_path, &nameptr); 503 504 /* Final target is current directory plus name of selection */ 505 init_file_op(&dst, dst_path, nameptr); 506 if (dst.append >= sizeof (dst.path)) 507 return FORC_PATH_TOO_LONG; 508 509 int rel = relate(src_path, dst.path); 510 if (rel == RELATE_SAME) 511 return FORC_NOOP; 512 513 if (rel == RELATE_DIFFERENT) { 514 int rc; 515 if (file_exists(dst.path)) { 516 /* If user chooses not to overwrite, cancel */ 517 if (!yesno_pop(ID2P(LANG_REALLY_OVERWRITE))) 518 { 519 splash(HZ, ID2P(LANG_CANCEL)); 520 return FORC_NOOVERWRT; 521 } 522 523 flags |= PASTE_OVERWRITE; 524 } 525 526 init_file_op(&src, src_path, NULL); 527 if (src.append >= sizeof (src.path)) 528 return FORC_PATH_TOO_LONG; 529 /* Now figure out what we're doing */ 530 cpu_boost(true); 531 if (src.is_dir) { 532 /* Copy or move a subdirectory */ 533 /* Try renaming first */ 534 rc = move_by_rename(&src, dst.path, &flags); 535 if (rc < FORC_SUCCESS) { 536 int extra_len = dst.append - src.append; 537 if (extra_len > 0) 538 src.extra_len = extra_len; 539 540 rc = check_count_fileobjects(&src); 541 if (rc == FORC_SUCCESS) { 542 rc = copy_move_directory(&src, &dst, flags); 543 } 544 } 545 } else { 546 /* Copy or move a file */ 547 rc = copy_move_file(&src, dst.path, flags); 548 } 549 550 cpu_boost(false); 551 DEBUGF("%s res: %d, ct: %d/%d %s\n", 552 __func__, rc, src.objects, src.processed, src.path); 553 return rc; 554 } 555 556 /* Else Some other relation / failure */ 557 DEBUGF("%s res: %d, rel: %d\n", __func__, FORC_UNKNOWN_FAILURE, rel); 558 return FORC_UNKNOWN_FAILURE; 559} 560 561int create_dir(void) 562{ 563 int rc; 564 char dirname[MAX_PATH]; 565 size_t pathlen = path_append(dirname, getcwd(NULL, 0), PA_SEP_HARD, 566 sizeof (dirname)); 567 char *basename = dirname + pathlen; 568 569 if (pathlen >= sizeof (dirname)) 570 return FORC_PATH_TOO_LONG; 571 572 rc = prompt_name(basename, sizeof (dirname) - pathlen); 573 if (rc == FORC_SUCCESS) 574 rc = mkdir(dirname) * 10; 575 return rc; 576} 577 578/* share code for file and directory deletion, saves space */ 579int delete_fileobject(const char *selected_file) 580{ 581 int rc; 582 struct file_op_params param; 583 init_file_op(&param, selected_file, NULL); 584 if (param.append >= sizeof (param.path)) 585 return FORC_PATH_TOO_LONG; 586 587 /* Note: delete_fileobject() will happily delete whatever 588 * path is passed (after confirmation) */ 589 if (confirm_delete_yesno(param.path, param.toplevel_name) != YESNO_YES) 590 return FORC_CANCELLED; 591 592 if (param.is_dir) { 593 int rc = check_count_fileobjects(&param); 594 DEBUGF("%s res: %d, ct: %d, %s", __func__, rc, param.objects, param.path); 595 if (rc != FORC_SUCCESS) 596 return rc; 597 } 598 599 clear_screen_buffer(true); 600 601 if (param.is_dir) { /* if directory */ 602 cpu_boost(true); 603 rc = directory_fileop(&param, FOC_DELETE); 604 cpu_boost(false); 605 } else { 606 param.objects = param.processed = 1; 607 if (poll_cancel_action(FOC_DELETE, &param)) 608 return FORC_CANCELLED; 609 rc = remove(param.path) * 10; 610 } 611 612 return rc; 613} 614 615int rename_file(const char *selected_file) 616{ 617 int rc; 618 char newname[MAX_PATH]; 619 char *newext = NULL; 620 const char *oldbase, *selection = selected_file; 621 622 path_basename(selection, &oldbase); 623 size_t pathlen = oldbase - selection; 624 char *newbase = newname + pathlen; 625 626 if (strmemccpy(newname, selection, sizeof (newname)) == NULL) 627 return FORC_PATH_TOO_LONG; 628 629 if ((*tree_get_context()->dirfilter > NUM_FILTER_MODES) && 630 (newext = strrchr(newbase, '.'))) 631 /* hide extension when renaming in lists restricted to a 632 single file format, such as in the Playlists menu */ 633 *newext = '\0'; 634 635 rc = prompt_name(newbase, sizeof (newname) - pathlen); 636 637 if (rc != FORC_SUCCESS) 638 return rc; 639 640 if (newext) /* re-add original extension */ 641 strlcat(newbase, strrchr(selection, '.'), sizeof (newname) - pathlen); 642 643 if (!strcmp(oldbase, newbase)) 644 return FORC_NOOP; /* No change at all */ 645 646 int rel = relate(selection, newname); 647 if (rel == RELATE_DIFFERENT) 648 { 649 if (file_exists(newname)) { /* don't overwrite */ 650 return FORC_PATH_EXISTS; 651 } 652 return rename(selection, newname) * 10; 653 } 654 if (rel == RELATE_SAME) 655 return rename(selection, newname) * 10; 656 657 /* Else Some other relation / failure */ 658 return FORC_UNKNOWN_FAILURE; 659}