summaryrefslogtreecommitdiff
path: root/apps/fileop.c
diff options
context:
space:
mode:
authorWilliam Wilgus <wilgus.william@gmail.com>2024-06-21 10:38:50 -0400
committerWilliam Wilgus <me.theuser@yahoo.com>2024-06-30 02:09:40 -0400
commitdc7486c7de3018b78fcfeafe7a1cc5c9da168494 (patch)
treeca5326e18d88f650e8132c2b9760199701448df2 /apps/fileop.c
parentc87c09658a7d65249affe3e8a814bd278998bb42 (diff)
downloadrockbox-dc7486c7de3018b78fcfeafe7a1cc5c9da168494.tar.gz
rockbox-dc7486c7de3018b78fcfeafe7a1cc5c9da168494.zip
[Feature] onplay.c show file progress
first some clean-up of onplay.c extend/move fileobject copy, move, delete routines in prep for other users add error checking, better error codes pre scan to make sure the operation doesn't exceed system resources show progress for file and directory copies Change-Id: Ife2e62df554892dab651bab40433bf70b27e73ff
Diffstat (limited to 'apps/fileop.c')
-rw-r--r--apps/fileop.c606
1 files changed, 606 insertions, 0 deletions
diff --git a/apps/fileop.c b/apps/fileop.c
new file mode 100644
index 0000000000..0d2dc774b9
--- /dev/null
+++ b/apps/fileop.c
@@ -0,0 +1,606 @@
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
30#include "misc.h"
31#include "plugin.h"
32#include "dir.h"
33#include "tree.h"
34#include "fileop.h"
35#include "pathfuncs.h"
36
37#include "settings.h"
38#include "lang.h"
39#include "yesno.h"
40#include "splash.h"
41#include "keyboard.h"
42
43/* Used for directory move, copy and delete */
44struct file_op_params
45{
46 char path[MAX_PATH]; /* Buffer for full path */
47 bool is_dir;
48 int objects; /* how many files and subdirectories*/
49 int processed;
50 size_t append; /* Append position in 'path' for stack push */
51};
52
53static bool poll_cancel_action(const char *path, int operation, int current, int total)
54{
55 const char *op_str = "";
56
57 clear_screen_buffer(false);
58
59 if (operation == FOC_COPY)
60 op_str = str(LANG_COPYING);
61 else if (operation == FOC_MOVE)
62 op_str = str(LANG_MOVING);
63 else if (operation == FOC_COUNT)
64 op_str = str(LANG_SCANNING_DISK);
65 else if (operation == FOC_DELETE)
66 op_str = str(LANG_DELETING);
67
68 path_basename(path, &path);
69
70 if (total <= 0)
71 splashf(0, "%s (%d) %s", op_str, current, path);
72 else
73 splash_progress(current, total, "%s %s", op_str, path);
74
75 return ACTION_STD_CANCEL == get_action(CONTEXT_STD, TIMEOUT_NOBLOCK);
76}
77
78static struct file_op_params* init_file_op(struct file_op_params *param,
79 const char *basename,
80 const char *selected_file)
81{
82 /* Assumes: basename will never be NULL */
83 if (selected_file == NULL)
84 {
85 param->append = strlcpy(param->path, basename, sizeof (param->path));
86 }
87 else
88 param->append = path_append(param->path, basename,
89 selected_file, sizeof (param->path));
90 param->is_dir = dir_exists(param->path);
91 param->objects = 0; /* how many files and subdirectories*/
92 param->processed = 0;
93
94 return param;
95}
96
97/* counts file objects, deletes file objects */
98static int directory_fileop(struct file_op_params *param, enum file_op_current fileop)
99{
100 errno = 0;
101 DIR *dir = opendir(param->path);
102 if (!dir) {
103 if (errno == EMFILE) {
104 return FORC_TOO_MANY_SUBDIRS;
105 }
106 return FORC_PATH_NOT_EXIST; /* open error */
107 }
108 int rc = FORC_SUCCESS;
109 size_t append = param->append;
110
111 /* walk through the directory content */
112 while (rc == FORC_SUCCESS) {
113 errno = 0; /* distinguish failure from eod */
114 struct dirent *entry = readdir(dir);
115 if (!entry) {
116 if (errno) {
117 rc = FORC_PATH_NOT_EXIST;
118 }
119 break;
120 }
121
122 struct dirinfo info = dir_get_info(dir, entry);
123 if ((info.attribute & ATTR_DIRECTORY) &&
124 is_dotdir_name(entry->d_name)) {
125 continue; /* skip these */
126 }
127
128 /* append name to current directory */
129 param->append = append + path_append(&param->path[append],
130 PA_SEP_HARD, entry->d_name,
131 sizeof (param->path) - append);
132
133 if (fileop == FOC_DELETE)
134 {
135 param->processed++;
136 /* at this point we've already counted and never had a path too long
137 in the other branch so we 'should not' encounter one here either */
138
139 if (param->processed > param->objects)
140 {
141 rc = FORC_UNKNOWN_FAILURE;
142 break;
143 }
144
145 if (info.attribute & ATTR_DIRECTORY) {
146 /* remove a subdirectory */
147 rc = directory_fileop(param, fileop);
148 } else {
149 /* remove a file */
150 if (poll_cancel_action(param->path, FOC_DELETE, param->processed, param->objects))
151 {
152 rc = FORC_CANCELLED;
153 break;
154 }
155 rc = remove(param->path);
156 }
157 }
158 else /* count objects */
159 {
160 param->objects++;
161
162 if (param->append >= sizeof (param->path)) {
163 rc = FORC_PATH_TOO_LONG;
164 break; /* no space left in buffer */
165 }
166
167 if (info.attribute & ATTR_DIRECTORY) {
168 /* remove a subdirectory */
169 rc = directory_fileop(param, FOC_COUNT);
170 } else {
171 if (poll_cancel_action(param->path, FOC_COUNT, param->objects, 0))
172 {
173 rc = FORC_CANCELLED;
174 break;
175 }
176 }
177 }
178 param->append = append; /* other functions may use param, reset append */
179 /* Remove basename we added above */
180 param->path[append] = '\0';
181 }
182
183 closedir(dir);
184
185 if (fileop == FOC_DELETE && rc == FORC_SUCCESS) {
186 /* remove the now empty directory */
187 if (poll_cancel_action(param->path, FOC_DELETE, param->processed, param->objects))
188 {
189 rc = FORC_CANCELLED;
190 } else {
191 rc = rmdir(param->path);
192 }
193 }
194
195 return rc;
196}
197
198static int check_count_fileobjects(struct file_op_params *param)
199{
200 int rc = directory_fileop(param, FOC_COUNT);
201 DEBUGF("%s res:(%d) objects %d \n", __func__, rc, param->objects);
202 return rc;
203}
204
205static bool check_new_name(const char *basename)
206{
207 /* at least prevent escapes out of the base directory from keyboard-
208 entered filenames; the file code should reject other invalidities */
209 return *basename != '\0' && !strchr(basename, PATH_SEPCH) &&
210 !is_dotdir_name(basename);
211}
212
213int create_dir(void)
214{
215 int rc = FORC_UNKNOWN_FAILURE;
216 char dirname[MAX_PATH];
217 size_t pathlen = path_append(dirname, getcwd(NULL, 0), PA_SEP_HARD,
218 sizeof (dirname));
219 char *basename = dirname + pathlen;
220
221 if (pathlen >= sizeof (dirname)) {
222 /* Too long */
223 } else if (kbd_input(basename, sizeof (dirname) - pathlen, NULL) < 0) {
224 rc = FORC_CANCELLED;
225 } else if (check_new_name(basename)) {
226 rc = mkdir(dirname);
227 }
228
229 return rc;
230}
231
232/************************************************************************************/
233/* share code for file and directory deletion, saves space */
234static int delete_file_dir(struct file_op_params *param)
235{
236 /* Note: delete_file_dir() will happily delete whatever
237 * path is passed (after confirmation) */
238 if (confirm_delete_yesno(param->path) != YESNO_YES) {
239 return FORC_CANCELLED;
240 }
241
242 clear_screen_buffer(true);
243 poll_cancel_action(param->path, FOC_DELETE, param->processed, param->objects);
244
245 int rc = FORC_UNKNOWN_FAILURE;
246
247 if (param->is_dir) { /* if directory */
248 cpu_boost(true);
249 rc = directory_fileop(param, FOC_DELETE);
250 cpu_boost(false);
251 } else {
252 rc = remove(param->path);
253 }
254
255 return rc;
256}
257
258int delete_fileobject(const char *selected_file)
259{
260 struct file_op_params param;
261 if (init_file_op(&param, selected_file, NULL)->is_dir == true)
262 {
263 int rc = check_count_fileobjects(&param);
264 DEBUGF("%s res: %d, ct: %d, %s", __func__, rc, param.objects, param.path);
265 if (rc != FORC_SUCCESS)
266 return rc;
267 }
268
269 return delete_file_dir(&param);
270}
271
272int rename_file(const char *selected_file)
273{
274 int rc = FORC_UNKNOWN_FAILURE;
275 char newname[MAX_PATH];
276 const char *oldbase, *selection = selected_file;
277
278 path_basename(selection, &oldbase);
279 size_t pathlen = oldbase - selection;
280 char *newbase = newname + pathlen;
281
282 if (strmemccpy(newname, selection, sizeof (newname)) == NULL) {
283 /* Too long */
284 } else if (kbd_input(newbase, sizeof (newname) - pathlen, NULL) < 0) {
285 rc = FORC_CANCELLED;
286 } else if (!strcmp(oldbase, newbase)) {
287 rc = FORC_NOOP; /* No change at all */
288 } else if (check_new_name(newbase)) {
289 switch (relate(selection, newname))
290 {
291 case RELATE_DIFFERENT:
292 if (file_exists(newname)) {
293 break; /* don't overwrite */
294 }
295 /* Fall-through */
296 case RELATE_SAME:
297 rc = rename(selection, newname);
298 break;
299 case RELATE_PREFIX:
300 default:
301 break;
302 }
303 }
304
305 return rc;
306}
307
308static int move_by_rename(const char *src_path,
309 const char *dst_path,
310 unsigned int *pflags)
311{
312 unsigned int flags = *pflags;
313 int rc = FORC_UNKNOWN_FAILURE;
314 while (!(flags & (PASTE_COPY | PASTE_EXDEV))) {
315 if ((flags & PASTE_OVERWRITE) || !file_exists(dst_path)) {
316 /* Just try to move the directory / file */
317 if (poll_cancel_action(src_path, FOC_MOVE, 0 , 0)) {
318 rc = FORC_CANCELLED;
319 } else {
320 rc = rename(src_path, dst_path);
321 }
322
323 if (rc < 0) {
324 int errnum = errno;
325 if (errnum == ENOTEMPTY && (flags & PASTE_OVERWRITE)) {
326 /* Directory is not empty thus rename() will not do a quick
327 overwrite */
328 break;
329 }
330 #ifdef HAVE_MULTIVOLUME
331 else if (errnum == EXDEV) {
332 /* Failed because cross volume rename doesn't work; force
333 a move instead */
334 *pflags |= PASTE_EXDEV;
335 break;
336 }
337 #endif /* HAVE_MULTIVOLUME */
338 }
339 }
340
341 break;
342 }
343 return rc;
344}
345
346/* Paste a file */
347static int copy_move_file(const char *src_path, const char *dst_path, unsigned int flags)
348{
349 /* Try renaming first */
350 int rc = move_by_rename(src_path, dst_path, &flags);
351 if (rc == FORC_SUCCESS)
352 return rc;
353
354 /* See if we can get the plugin buffer for the file copy buffer */
355 size_t buffersize;
356 char *buffer = (char *) plugin_get_buffer(&buffersize);
357 if (buffer == NULL || buffersize < 512) {
358 /* Not large enough, try for a disk sector worth of stack
359 instead */
360 buffersize = 512;
361 buffer = (char *)alloca(buffersize);
362 }
363
364 if (buffer == NULL) {
365 return FORC_NO_BUFFER_AVAIL;
366 }
367
368 buffersize &= ~0x1ff; /* Round buffer size to multiple of sector
369 size */
370
371 int src_fd = open(src_path, O_RDONLY);
372 if (src_fd >= 0) {
373 off_t src_sz = lseek(src_fd, 0, SEEK_END);
374 lseek(src_fd, 0, SEEK_SET);
375
376 int oflag = O_WRONLY|O_CREAT;
377
378 if (!(flags & PASTE_OVERWRITE)) {
379 oflag |= O_EXCL;
380 }
381
382 int dst_fd = open(dst_path, oflag, 0666);
383 if (dst_fd >= 0) {
384 off_t total_size = 0;
385 off_t next_cancel_test = 0; /* No excessive button polling */
386
387 rc = FORC_SUCCESS;
388
389 while (rc == FORC_SUCCESS) {
390 if (total_size >= next_cancel_test) {
391 next_cancel_test = total_size + 0x10000;
392 if (poll_cancel_action(src_path,
393 !(flags & PASTE_COPY) ? FOC_MOVE : FOC_COPY,
394 total_size, src_sz))
395 {
396 rc = FORC_CANCELLED;
397 break;
398 }
399 }
400
401 ssize_t bytesread = read(src_fd, buffer, buffersize);
402 if (bytesread <= 0) {
403 if (bytesread < 0) {
404 rc = FORC_READ_FAILURE;
405 }
406 /* else eof on buffer boundary; nothing to write */
407 break;
408 }
409
410 ssize_t byteswritten = write(dst_fd, buffer, bytesread);
411 if (byteswritten < bytesread) {
412 /* Some I/O error */
413 rc = FORC_WRITE_FAILURE;
414 break;
415 }
416
417 total_size += byteswritten;
418
419 if (bytesread < (ssize_t)buffersize) {
420 /* EOF with trailing bytes */
421 break;
422 }
423 }
424
425 if (rc == FORC_SUCCESS) {
426 /* If overwriting, set the correct length if original was
427 longer */
428 rc = ftruncate(dst_fd, total_size) * 10;
429 }
430
431 close(dst_fd);
432
433 if (rc != FORC_SUCCESS) {
434 /* Copy failed. Cleanup. */
435 remove(dst_path);
436 }
437 }
438
439 close(src_fd);
440 }
441
442 if (rc == FORC_SUCCESS && !(flags & PASTE_COPY)) {
443 /* Remove the source file */
444 rc = remove(src_path) * 10;
445 }
446
447 return rc;
448}
449
450/* Paste a directory */
451static int copy_move_directory(struct file_op_params *src,
452 struct file_op_params *dst,
453 unsigned int flags)
454{
455 int rc = FORC_UNKNOWN_FAILURE;
456
457 DIR *srcdir = opendir(src->path);
458
459 if (srcdir) {
460 /* Make a directory to copy things to */
461 rc = mkdir(dst->path) * 10;
462 if (rc < 0 && errno == EEXIST && (flags & PASTE_OVERWRITE)) {
463 /* Exists and overwrite was approved */
464 rc = FORC_SUCCESS;
465 }
466 }
467
468 size_t srcap = src->append, dstap = dst->append;
469
470 /* Walk through the directory content; this loop will exit as soon as
471 there's a problem */
472 while (rc == FORC_SUCCESS) {
473 errno = 0; /* Distinguish failure from eod */
474 struct dirent *entry = readdir(srcdir);
475 if (!entry) {
476 if (errno) {
477 rc = FORC_PATH_NOT_EXIST;
478 }
479 break;
480 }
481
482 struct dirinfo info = dir_get_info(srcdir, entry);
483 if ((info.attribute & ATTR_DIRECTORY) &&
484 is_dotdir_name(entry->d_name)) {
485 continue; /* Skip these */
486 }
487
488 /* Append names to current directories */
489 src->append = srcap +
490 path_append(&src->path[srcap], PA_SEP_HARD, entry->d_name,
491 sizeof(src->path) - srcap);
492
493 dst->append = dstap +
494 path_append(&dst->path[dstap], PA_SEP_HARD, entry->d_name,
495 sizeof (dst->path) - dstap);
496 /* src length was already checked by check_count_fileobjects() */
497 if (dst->append >= sizeof (dst->path)) {
498 rc = FORC_PATH_TOO_LONG; /* No space left in buffer */
499 break;
500 }
501
502 src->processed++;
503 if (src->processed > src->objects)
504 {
505 rc = FORC_UNKNOWN_FAILURE;
506 break;
507 }
508
509 if (poll_cancel_action(src->path,
510 !(flags & PASTE_COPY) ? FOC_MOVE : FOC_COPY,
511 src->processed, src->objects))
512 {
513 rc = FORC_CANCELLED;
514 break;
515 }
516
517 DEBUGF("Copy %s to %s\n", src->path, dst->path);
518
519 if (info.attribute & ATTR_DIRECTORY) {
520 /* Copy/move a subdirectory */
521 rc = copy_move_directory(src, dst, flags); /* recursion */;
522 } else {
523 /* Copy/move a file */
524 rc = copy_move_file(src->path, dst->path, flags);
525 }
526
527 /* Remove basenames we added above */
528 src->path[srcap] = '\0';
529 dst->path[dstap] = '\0';
530 }
531
532 if (rc == FORC_SUCCESS && !(flags & PASTE_COPY)) {
533 /* Remove the now empty directory */
534 rc = rmdir(src->path) * 10;
535 }
536
537 closedir(srcdir);
538 return rc;
539}
540
541int copy_move_fileobject(const char *src_path, const char *dst_path, unsigned int flags)
542{
543 if (!src_path[0])
544 return FORC_NOOP;
545
546 int rc = FORC_UNKNOWN_FAILURE;
547
548 struct file_op_params src, dst;
549
550 /* Figure out the name of the selection */
551 const char *nameptr;
552 path_basename(src_path, &nameptr);
553
554 /* Final target is current directory plus name of selection */
555 init_file_op(&dst, dst_path, nameptr);
556
557 switch (dst.append < sizeof (dst.path) ?
558 relate(src_path, dst.path) : FORC_PATH_TOO_LONG)
559 {
560 case RELATE_SAME:
561 rc = FORC_NOOP;
562 break;
563
564 case RELATE_DIFFERENT:
565 if (file_exists(dst.path)) {
566 /* If user chooses not to overwrite, cancel */
567 if (confirm_overwrite_yesno() == YESNO_NO) {
568 rc = FORC_NOOVERWRT;
569 break;
570 }
571
572 flags |= PASTE_OVERWRITE;
573 }
574
575 /* Now figure out what we're doing */
576 cpu_boost(true);
577
578 init_file_op(&src, src_path, NULL);
579
580 if (src.is_dir) {
581 /* Copy or move a subdirectory */
582
583 if (src.append < sizeof (src.path)) {
584 /* Try renaming first */
585 rc = move_by_rename(src.path, dst.path, &flags);
586 if (rc != FORC_SUCCESS && rc != FORC_CANCELLED) {
587 if (check_count_fileobjects(&src) == FORC_SUCCESS) {
588 rc = copy_move_directory(&src, &dst, flags);
589 }
590 }
591 }
592 } else {
593 /* Copy or move a file */
594 rc = copy_move_file(src_path, dst.path, flags);
595 }
596
597 cpu_boost(false);
598 break;
599
600 case RELATE_PREFIX:
601 default: /* Some other relation / failure */
602 break;
603 }
604
605 return rc;
606}