summaryrefslogtreecommitdiff
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
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
-rw-r--r--apps/SOURCES1
-rw-r--r--apps/fileop.c606
-rw-r--r--apps/fileop.h70
-rw-r--r--apps/misc.c18
-rw-r--r--apps/misc.h4
-rw-r--r--apps/onplay.c784
6 files changed, 844 insertions, 639 deletions
diff --git a/apps/SOURCES b/apps/SOURCES
index 444951bbcb..2c002b51cd 100644
--- a/apps/SOURCES
+++ b/apps/SOURCES
@@ -10,6 +10,7 @@ bookmark.c
10core_keymap.c 10core_keymap.c
11debug_menu.c 11debug_menu.c
12filetypes.c 12filetypes.c
13fileop.c
13language.c 14language.c
14main.c 15main.c
15menu.c 16menu.c
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}
diff --git a/apps/fileop.h b/apps/fileop.h
new file mode 100644
index 0000000000..f8237dc64f
--- /dev/null
+++ b/apps/fileop.h
@@ -0,0 +1,70 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2002 Björn Stenberg
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#ifndef FILEOP_H
22#define FILEOP_H
23
24#include "file.h"
25
26/* result codes of various file operations */
27enum fileop_result_code
28{
29 FORC_READ_FAILURE = -7,
30 FORC_WRITE_FAILURE = -6,
31 FORC_NO_BUFFER_AVAIL = -5,
32 FORC_TOO_MANY_SUBDIRS = -4,
33 FORC_PATH_TOO_LONG = -3,
34 FORC_PATH_NOT_EXIST = -2,
35 FORC_UNKNOWN_FAILURE = -1,
36 /* Anything < 0 is failure */
37 FORC_SUCCESS = 0, /* All operations completed successfully */
38 FORC_NOOP = 1, /* Operation didn't need to do anything */
39 FORC_CANCELLED = 2, /* Operation was cancelled by user */
40 FORC_NOOVERWRT = 3,
41};
42
43enum file_op_flags
44{
45 PASTE_CUT = 0x00, /* Is a move (cut) operation (default) */
46 PASTE_COPY = 0x01, /* Is a copy operation */
47 PASTE_OVERWRITE = 0x02, /* Overwrite destination */
48 PASTE_EXDEV = 0x04, /* Actually copy/move across volumes */
49};
50
51enum file_op_current
52{
53 FOC_NONE = 0,
54 FOC_COUNT,
55 FOC_MOVE,
56 FOC_COPY,
57 FOC_DELETE,
58 FOC_CREATE,
59};
60
61int create_dir(void);
62
63int rename_file(const char *selected_file);
64
65int delete_fileobject(const char *selected_file);
66
67int copy_move_fileobject(const char *src_path, const char *dst_path,
68 unsigned int flags);
69
70#endif /* FILEOP_H */
diff --git a/apps/misc.c b/apps/misc.c
index dd73c98a69..d8caabd397 100644
--- a/apps/misc.c
+++ b/apps/misc.c
@@ -1999,4 +1999,22 @@ long from_normalized_volume(long norm, long min_vol, long max_vol, long max_norm
1999 1999
2000 return vol >> NVOL_FRACBITS; 2000 return vol >> NVOL_FRACBITS;
2001} 2001}
2002
2003void clear_screen_buffer(bool update)
2004{
2005 struct viewport vp;
2006 struct viewport *last_vp;
2007 FOR_NB_SCREENS(i)
2008 {
2009 struct screen * screen = &screens[i];
2010 viewport_set_defaults(&vp, screen->screen_type);
2011 last_vp = screen->set_viewport(&vp);
2012 screen->clear_viewport();
2013 if (update) {
2014 screen->update_viewport();
2015 }
2016 screen->set_viewport(last_vp);
2017 }
2018}
2019
2002#endif /* ndef __PCTOOL__ */ 2020#endif /* ndef __PCTOOL__ */
diff --git a/apps/misc.h b/apps/misc.h
index c6485db4ff..e5fb7a3d1f 100644
--- a/apps/misc.h
+++ b/apps/misc.h
@@ -277,4 +277,8 @@ long to_normalized_volume(long vol, long min_vol, long max_vol, long max_norm);
277 * for the given normalized volume. */ 277 * for the given normalized volume. */
278long from_normalized_volume(long norm, long min_vol, long max_vol, long max_norm); 278long from_normalized_volume(long norm, long min_vol, long max_vol, long max_norm);
279 279
280/* clear the lcd output buffer, if update is true the cleared buffer
281 * will be written to the lcd */
282void clear_screen_buffer(bool update);
283
280#endif /* MISC_H */ 284#endif /* MISC_H */
diff --git a/apps/onplay.c b/apps/onplay.c
index 572138e583..4880af58f3 100644
--- a/apps/onplay.c
+++ b/apps/onplay.c
@@ -26,7 +26,6 @@
26 26
27#include "debug.h" 27#include "debug.h"
28#include "lcd.h" 28#include "lcd.h"
29#include "file.h"
30#include "audio.h" 29#include "audio.h"
31#include "menu.h" 30#include "menu.h"
32#include "lang.h" 31#include "lang.h"
@@ -43,6 +42,7 @@
43#include "talk.h" 42#include "talk.h"
44#include "onplay.h" 43#include "onplay.h"
45#include "filetypes.h" 44#include "filetypes.h"
45#include "fileop.h"
46#include "open_plugin.h" 46#include "open_plugin.h"
47#include "plugin.h" 47#include "plugin.h"
48#include "bookmark.h" 48#include "bookmark.h"
@@ -84,31 +84,6 @@ extern struct menu_item_ex file_menu; /* settings_menu.c */
84 MENU_ITEM_COUNT(sizeof( name##_)/sizeof(*name##_)), \ 84 MENU_ITEM_COUNT(sizeof( name##_)/sizeof(*name##_)), \
85 { (void*)name##_},{.callback_and_desc = & name##__}}; 85 { (void*)name##_},{.callback_and_desc = & name##__}};
86 86
87/* Used for directory move, copy and delete */
88struct dirrecurse_params
89{
90 char path[MAX_PATH]; /* Buffer for full path */
91 size_t append; /* Append position in 'path' for stack push */
92};
93
94enum clipboard_op_flags
95{
96 PASTE_CUT = 0x00, /* Is a move (cut) operation (default) */
97 PASTE_COPY = 0x01, /* Is a copy operation */
98 PASTE_OVERWRITE = 0x02, /* Overwrite destination */
99 PASTE_EXDEV = 0x04, /* Actually copy/move across volumes */
100};
101
102/* result codec of various onplay operations */
103enum onplay_result_code
104{
105 /* Anything < 0 is failure */
106 OPRC_SUCCESS = 0, /* All operations completed successfully */
107 OPRC_NOOP = 1, /* Operation didn't need to do anything */
108 OPRC_CANCELLED = 2, /* Operation was cancelled by user */
109 OPRC_NOOVERWRT = 3,
110};
111
112static struct clipboard 87static struct clipboard
113{ 88{
114 char path[MAX_PATH]; /* Clipped file's path */ 89 char path[MAX_PATH]; /* Clipped file's path */
@@ -191,8 +166,6 @@ static int bookmark_menu_callback(int action,
191 return action; 166 return action;
192} 167}
193 168
194
195
196/* CONTEXT_WPS playlist options */ 169/* CONTEXT_WPS playlist options */
197static bool shuffle_playlist(void) 170static bool shuffle_playlist(void)
198{ 171{
@@ -244,46 +217,82 @@ MAKE_ONPLAYMENU( wps_playlist_menu, ID2P(LANG_CURRENT_PLAYLIST),
244 ); 217 );
245 218
246/* argument for add_to_playlist (for use by menu callbacks) */ 219/* argument for add_to_playlist (for use by menu callbacks) */
220#define PL_NONE 0x00
221#define PL_QUEUE 0x01
222#define PL_REPLACE 0x02
247struct add_to_pl_param 223struct add_to_pl_param
248{ 224{
249 int8_t position; 225 int8_t position;
250 unsigned int queue: 1; 226 uint8_t flags;
251 unsigned int replace: 1;
252}; 227};
253 228
254static struct add_to_pl_param addtopl_insert = {PLAYLIST_INSERT, 0, 0}; 229static struct add_to_pl_param addtopl_insert = {PLAYLIST_INSERT, PL_NONE};
255static struct add_to_pl_param addtopl_insert_first = {PLAYLIST_INSERT_FIRST, 0, 0}; 230static struct add_to_pl_param addtopl_insert_first = {PLAYLIST_INSERT_FIRST, PL_NONE};
256static struct add_to_pl_param addtopl_insert_last = {PLAYLIST_INSERT_LAST, 0, 0}; 231static struct add_to_pl_param addtopl_insert_last = {PLAYLIST_INSERT_LAST, PL_NONE};
257static struct add_to_pl_param addtopl_insert_shuf = {PLAYLIST_INSERT_SHUFFLED, 0, 0}; 232static struct add_to_pl_param addtopl_insert_shuf = {PLAYLIST_INSERT_SHUFFLED, PL_NONE};
258static struct add_to_pl_param addtopl_insert_last_shuf = {PLAYLIST_INSERT_LAST_SHUFFLED, 0, 0}; 233static struct add_to_pl_param addtopl_insert_last_shuf = {PLAYLIST_INSERT_LAST_SHUFFLED, PL_NONE};
259 234
260static struct add_to_pl_param addtopl_queue = {PLAYLIST_INSERT, 1, 0}; 235static struct add_to_pl_param addtopl_queue = {PLAYLIST_INSERT, PL_QUEUE};
261static struct add_to_pl_param addtopl_queue_first = {PLAYLIST_INSERT_FIRST, 1, 0}; 236static struct add_to_pl_param addtopl_queue_first = {PLAYLIST_INSERT_FIRST, PL_QUEUE};
262static struct add_to_pl_param addtopl_queue_last = {PLAYLIST_INSERT_LAST, 1, 0}; 237static struct add_to_pl_param addtopl_queue_last = {PLAYLIST_INSERT_LAST, PL_QUEUE};
263static struct add_to_pl_param addtopl_queue_shuf = {PLAYLIST_INSERT_SHUFFLED, 1, 0}; 238static struct add_to_pl_param addtopl_queue_shuf = {PLAYLIST_INSERT_SHUFFLED, PL_QUEUE};
264static struct add_to_pl_param addtopl_queue_last_shuf = {PLAYLIST_INSERT_LAST_SHUFFLED, 1, 0}; 239static struct add_to_pl_param addtopl_queue_last_shuf = {PLAYLIST_INSERT_LAST_SHUFFLED, PL_QUEUE};
265 240
266static struct add_to_pl_param addtopl_replace = {PLAYLIST_INSERT, 0, 1}; 241static struct add_to_pl_param addtopl_replace = {PLAYLIST_INSERT, PL_REPLACE};
267static struct add_to_pl_param addtopl_replace_shuffled = {PLAYLIST_INSERT_LAST_SHUFFLED, 0, 1}; 242static struct add_to_pl_param addtopl_replace_shuffled = {PLAYLIST_INSERT_LAST_SHUFFLED, PL_REPLACE};
243
244static void op_playlist_insert_selected(int position, bool queue)
245{
246#ifdef HAVE_TAGCACHE
247 if (context == CONTEXT_STD && ctx_current_playlist_insert != NULL)
248 {
249 ctx_current_playlist_insert(position, queue, false);
250 return;
251 }
252#endif
253 if ((selected_file_attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO)
254 playlist_insert_track(NULL, selected_file, position, queue, true);
255 else if ((selected_file_attr & FILE_ATTR_MASK) == FILE_ATTR_M3U)
256 playlist_insert_playlist(NULL, selected_file, position, queue);
257 else if (selected_file_attr & ATTR_DIRECTORY)
258 {
259#ifdef HAVE_TAGCACHE
260 if (context == CONTEXT_ID3DB)
261 {
262 tagtree_current_playlist_insert(position, queue);
263 return;
264 }
265#endif
266 bool recurse = (global_settings.recursive_dir_insert == RECURSE_ON);
267 if (global_settings.recursive_dir_insert == RECURSE_ASK)
268 {
269
270 const char *lines[] = {
271 ID2P(LANG_RECURSE_DIRECTORY_QUESTION),
272 selected_file
273 };
274 const struct text_message message={lines, 2};
275 /* Ask if user wants to recurse directory */
276 recurse = (gui_syncyesno_run(&message, NULL, NULL)==YESNO_YES);
277 }
278
279 playlist_insert_directory(NULL, selected_file, position, queue,
280 recurse == RECURSE_ON);
281 }
282}
268 283
269/* CONTEXT_[TREE|ID3DB|STD] playlist options */ 284/* CONTEXT_[TREE|ID3DB|STD] playlist options */
270static int add_to_playlist(void* arg) 285static int add_to_playlist(void* arg)
271{ 286{
272 struct add_to_pl_param* param = arg; 287 struct add_to_pl_param* param = arg;
273 int position = param->position; 288 int position = param->position;
274 bool new_playlist = !!param->replace; 289 bool new_playlist = (param->flags & PL_REPLACE) == PL_REPLACE;
275 bool queue = !!param->queue; 290 bool queue = (param->flags & PL_QUEUE) == PL_QUEUE;
276 291
277 /* warn if replacing the playlist */ 292 /* warn if replacing the playlist */
278 if (new_playlist && !warn_on_pl_erase()) 293 if (new_playlist && !warn_on_pl_erase())
279 return 0; 294 return 0;
280 295
281 const char *lines[] = {
282 ID2P(LANG_RECURSE_DIRECTORY_QUESTION),
283 selected_file
284 };
285 const struct text_message message={lines, 2};
286
287 splash(0, ID2P(LANG_WAIT)); 296 splash(0, ID2P(LANG_WAIT));
288 297
289 if (new_playlist && global_settings.keep_current_track_on_replace_playlist) 298 if (new_playlist && global_settings.keep_current_track_on_replace_playlist)
@@ -307,38 +316,7 @@ static int add_to_playlist(void* arg)
307 playlist_set_last_shuffled_start(); 316 playlist_set_last_shuffled_start();
308 } 317 }
309 318
310#ifdef HAVE_TAGCACHE 319 op_playlist_insert_selected(position, queue);
311 if ((context == CONTEXT_ID3DB) && (selected_file_attr & ATTR_DIRECTORY))
312 {
313 tagtree_current_playlist_insert(position, queue);
314 }
315 else if (context == CONTEXT_STD && ctx_current_playlist_insert != NULL)
316 {
317 ctx_current_playlist_insert(position, queue, false);
318 }
319 else
320#endif
321 {
322 if ((selected_file_attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO)
323 playlist_insert_track(NULL, selected_file, position, queue, true);
324 else if (selected_file_attr & ATTR_DIRECTORY)
325 {
326 bool recurse = false;
327
328 if (global_settings.recursive_dir_insert != RECURSE_ASK)
329 recurse = (bool)global_settings.recursive_dir_insert;
330 else
331 {
332 /* Ask if user wants to recurse directory */
333 recurse = (gui_syncyesno_run(&message, NULL, NULL)==YESNO_YES);
334 }
335
336 playlist_insert_directory(NULL, selected_file, position, queue,
337 recurse);
338 }
339 else if ((selected_file_attr & FILE_ATTR_MASK) == FILE_ATTR_M3U)
340 playlist_insert_playlist(NULL, selected_file, position, queue);
341 }
342 320
343 if (new_playlist && (playlist_amount() > 0)) 321 if (new_playlist && (playlist_amount() > 0))
344 { 322 {
@@ -447,18 +425,21 @@ MAKE_ONPLAYMENU(tree_playlist_menu, ID2P(LANG_PLAYING_NEXT),
447 &replace_pl_item, 425 &replace_pl_item,
448 &replace_shuf_pl_item 426 &replace_shuf_pl_item
449 ); 427 );
428
450static int treeplaylist_callback(int action, 429static int treeplaylist_callback(int action,
451 const struct menu_item_ex *this_item, 430 const struct menu_item_ex *this_item,
452 struct gui_synclist *this_list) 431 struct gui_synclist *this_list)
453{ 432{
454 (void)this_list; 433 (void)this_list;
434 int sel_file_attr = (selected_file_attr & FILE_ATTR_MASK);
435
455 switch (action) 436 switch (action)
456 { 437 {
457 case ACTION_REQUEST_MENUITEM: 438 case ACTION_REQUEST_MENUITEM:
458 if (this_item == &tree_playlist_menu) 439 if (this_item == &tree_playlist_menu)
459 { 440 {
460 if ((selected_file_attr & FILE_ATTR_MASK) != FILE_ATTR_AUDIO && 441 if (sel_file_attr != FILE_ATTR_AUDIO &&
461 (selected_file_attr & FILE_ATTR_MASK) != FILE_ATTR_M3U && 442 sel_file_attr != FILE_ATTR_M3U &&
462 (selected_file_attr & ATTR_DIRECTORY) == 0) 443 (selected_file_attr & ATTR_DIRECTORY) == 0)
463 return ACTION_EXIT_MENUITEM; 444 return ACTION_EXIT_MENUITEM;
464 } 445 }
@@ -476,7 +457,7 @@ static int treeplaylist_callback(int action,
476 { 457 {
477 struct add_to_pl_param *param = this_item->function_param->param; 458 struct add_to_pl_param *param = this_item->function_param->param;
478 459
479 if (param->queue) 460 if ((param->flags & PL_QUEUE) == PL_QUEUE)
480 { 461 {
481 if (global_settings.show_queue_options != QUEUE_SHOW_AT_TOPLEVEL && 462 if (global_settings.show_queue_options != QUEUE_SHOW_AT_TOPLEVEL &&
482 !in_queue_submenu) 463 !in_queue_submenu)
@@ -489,12 +470,12 @@ static int treeplaylist_callback(int action,
489 if (!global_settings.show_shuffled_adding_options) 470 if (!global_settings.show_shuffled_adding_options)
490 return ACTION_EXIT_MENUITEM; 471 return ACTION_EXIT_MENUITEM;
491 472
492 if ((selected_file_attr & FILE_ATTR_MASK) != FILE_ATTR_M3U && 473 if (sel_file_attr != FILE_ATTR_M3U &&
493 (selected_file_attr & ATTR_DIRECTORY) == 0) 474 (selected_file_attr & ATTR_DIRECTORY) == 0)
494 return ACTION_EXIT_MENUITEM; 475 return ACTION_EXIT_MENUITEM;
495 } 476 }
496 477
497 if (!param->replace) 478 if ((param->flags & PL_REPLACE) != PL_REPLACE)
498 { 479 {
499 if (!(audio_status() & AUDIO_STATUS_PLAY)) 480 if (!(audio_status() & AUDIO_STATUS_PLAY))
500 return ACTION_EXIT_MENUITEM; 481 return ACTION_EXIT_MENUITEM;
@@ -583,482 +564,17 @@ static int cat_playlist_callback(int action,
583 return action; 564 return action;
584} 565}
585 566
586static void draw_slider(void)
587{
588 struct viewport *last_vp;
589 FOR_NB_SCREENS(i)
590 {
591 struct viewport vp;
592 int slider_height = 2*screens[i].getcharheight();
593 viewport_set_defaults(&vp, i);
594 last_vp = screens[i].set_viewport(&vp);
595 show_busy_slider(&screens[i], 1, vp.height - slider_height,
596 vp.width-2, slider_height-1);
597 screens[i].update_viewport();
598 screens[i].set_viewport(last_vp);
599 }
600}
601
602static void clear_display(bool update)
603{
604 struct viewport vp;
605 struct viewport *last_vp;
606 FOR_NB_SCREENS(i)
607 {
608 struct screen * screen = &screens[i];
609 viewport_set_defaults(&vp, screen->screen_type);
610 last_vp = screen->set_viewport(&vp);
611 screen->clear_viewport();
612 if (update) {
613 screen->update_viewport();
614 }
615 screen->set_viewport(last_vp);
616 }
617}
618
619static void splash_path(const char *path)
620{
621 clear_display(false);
622 path_basename(path, &path);
623 splash(0, path);
624 draw_slider();
625}
626
627/* Splashes the path and checks the keys */
628static bool poll_cancel_action(const char *path)
629{
630 splash_path(path);
631 return ACTION_STD_CANCEL == get_action(CONTEXT_STD, TIMEOUT_NOBLOCK);
632}
633
634static bool check_new_name(const char *basename)
635{
636 /* at least prevent escapes out of the base directory from keyboard-
637 entered filenames; the file code should reject other invalidities */
638 return *basename != '\0' && !strchr(basename, PATH_SEPCH) &&
639 !is_dotdir_name(basename);
640}
641
642static void splash_cancelled(void) 567static void splash_cancelled(void)
643{ 568{
644 clear_display(true); 569 clear_screen_buffer(true);
645 splash(HZ, ID2P(LANG_CANCEL)); 570 splash(HZ, ID2P(LANG_CANCEL));
646} 571}
647 572
648static void splash_failed(int lang_what) 573static void splash_failed(int lang_what, int err)
649{ 574{
650 cond_talk_ids_fq(lang_what, LANG_FAILED); 575 cond_talk_ids_fq(lang_what, LANG_FAILED);
651 clear_display(true); 576 clear_screen_buffer(true);
652 splashf(HZ*2, "%s %s", str(lang_what), str(LANG_FAILED)); 577 splashf(HZ*2, "%s %s (%d)", str(lang_what), str(LANG_FAILED), err);
653}
654
655/* helper function to remove a non-empty directory */
656static int remove_dir(struct dirrecurse_params *parm)
657{
658 DIR *dir = opendir(parm->path);
659 if (!dir) {
660 return -1; /* open error */
661 }
662
663 size_t append = parm->append;
664 int rc = OPRC_SUCCESS;
665
666 /* walk through the directory content */
667 while (rc == OPRC_SUCCESS) {
668 errno = 0; /* distinguish failure from eod */
669 struct dirent *entry = readdir(dir);
670 if (!entry) {
671 if (errno) {
672 rc = -1;
673 }
674 break;
675 }
676
677 struct dirinfo info = dir_get_info(dir, entry);
678 if ((info.attribute & ATTR_DIRECTORY) &&
679 is_dotdir_name(entry->d_name)) {
680 continue; /* skip these */
681 }
682
683 /* append name to current directory */
684 parm->append = append + path_append(&parm->path[append],
685 PA_SEP_HARD, entry->d_name,
686 sizeof (parm->path) - append);
687 if (parm->append >= sizeof (parm->path)) {
688 rc = -1;
689 break; /* no space left in buffer */
690 }
691
692 if (info.attribute & ATTR_DIRECTORY) {
693 /* remove a subdirectory */
694 rc = remove_dir(parm);
695 } else {
696 /* remove a file */
697 if (poll_cancel_action(parm->path)) {
698 rc = OPRC_CANCELLED;
699 break;
700 }
701
702 rc = remove(parm->path);
703 }
704
705 /* Remove basename we added above */
706 parm->path[append] = '\0';
707 }
708
709 closedir(dir);
710
711 if (rc == 0) {
712 /* remove the now empty directory */
713 if (poll_cancel_action(parm->path)) {
714 rc = OPRC_CANCELLED;
715 } else {
716 rc = rmdir(parm->path);
717 }
718 }
719
720 return rc;
721}
722
723/* share code for file and directory deletion, saves space */
724static int delete_file_dir(void)
725{
726 const char *to_delete=selected_file;
727 const int to_delete_attr=selected_file_attr;
728 if (confirm_delete_yesno(to_delete) != YESNO_YES) {
729 return 1;
730 }
731
732 clear_display(true);
733 splash(HZ/2, ID2P(LANG_DELETING));
734
735 int rc = -1;
736
737 if (to_delete_attr & ATTR_DIRECTORY) { /* true if directory */
738 struct dirrecurse_params parm;
739 parm.append = strlcpy(parm.path, to_delete, sizeof (parm.path));
740
741 if (parm.append < sizeof (parm.path)) {
742 cpu_boost(true);
743 rc = remove_dir(&parm);
744 cpu_boost(false);
745 }
746 } else {
747 rc = remove(to_delete);
748 }
749
750 if (rc < OPRC_SUCCESS) {
751 splash_failed(LANG_DELETE);
752 } else if (rc == OPRC_CANCELLED) {
753 splash_cancelled();
754 }
755
756 if (rc != OPRC_NOOP) {
757 /* Could have failed after some but not all needed changes; reload */
758 onplay_result = ONPLAY_RELOAD_DIR;
759 }
760
761 return 1;
762}
763
764static int rename_file(void)
765{
766 int rc = -1;
767 char newname[MAX_PATH];
768 const char *oldbase, *selection = selected_file;
769
770 path_basename(selection, &oldbase);
771 size_t pathlen = oldbase - selection;
772 char *newbase = newname + pathlen;
773
774 if (strmemccpy(newname, selection, sizeof (newname)) == NULL) {
775 /* Too long */
776 } else if (kbd_input(newbase, sizeof (newname) - pathlen, NULL) < 0) {
777 rc = OPRC_CANCELLED;
778 } else if (!strcmp(oldbase, newbase)) {
779 rc = OPRC_NOOP; /* No change at all */
780 } else if (check_new_name(newbase)) {
781 switch (relate(selection, newname))
782 {
783 case RELATE_DIFFERENT:
784 if (file_exists(newname)) {
785 break; /* don't overwrite */
786 }
787 /* Fall-through */
788 case RELATE_SAME:
789 rc = rename(selection, newname);
790 break;
791 case RELATE_PREFIX:
792 default:
793 break;
794 }
795 }
796
797 if (rc < OPRC_SUCCESS) {
798 splash_failed(LANG_RENAME);
799 } else if (rc == OPRC_CANCELLED) {
800 /* splash_cancelled(); kbd_input() splashes it */
801 } else if (rc == OPRC_SUCCESS) {
802 onplay_result = ONPLAY_RELOAD_DIR;
803 }
804
805 return 1;
806}
807
808static int create_dir(void)
809{
810 int rc = -1;
811 char dirname[MAX_PATH];
812 size_t pathlen = path_append(dirname, getcwd(NULL, 0), PA_SEP_HARD,
813 sizeof (dirname));
814 char *basename = dirname + pathlen;
815
816 if (pathlen >= sizeof (dirname)) {
817 /* Too long */
818 } else if (kbd_input(basename, sizeof (dirname) - pathlen, NULL) < 0) {
819 rc = OPRC_CANCELLED;
820 } else if (check_new_name(basename)) {
821 rc = mkdir(dirname);
822 }
823
824 if (rc < OPRC_SUCCESS) {
825 splash_failed(LANG_CREATE_DIR);
826 } else if (rc == OPRC_CANCELLED) {
827 /* splash_cancelled(); kbd_input() splashes it */
828 } else if (rc == OPRC_SUCCESS) {
829 onplay_result = ONPLAY_RELOAD_DIR;
830 }
831
832 return 1;
833}
834
835/* Paste a file */
836static int clipboard_pastefile(const char *src, const char *target,
837 unsigned int flags)
838{
839 int rc = -1;
840
841 while (!(flags & (PASTE_COPY | PASTE_EXDEV))) {
842 if ((flags & PASTE_OVERWRITE) || !file_exists(target)) {
843 /* Rename and possibly overwrite the file */
844 if (poll_cancel_action(src)) {
845 rc = OPRC_CANCELLED;
846 } else {
847 rc = rename(src, target);
848 }
849
850 #ifdef HAVE_MULTIVOLUME
851 if (rc < 0 && errno == EXDEV) {
852 /* Failed because cross volume rename doesn't work; force
853 a move instead */
854 flags |= PASTE_EXDEV;
855 break;
856 }
857 #endif /* HAVE_MULTIVOLUME */
858 }
859
860 return rc;
861 }
862
863 /* See if we can get the plugin buffer for the file copy buffer */
864 size_t buffersize;
865 char *buffer = (char *) plugin_get_buffer(&buffersize);
866 if (buffer == NULL || buffersize < 512) {
867 /* Not large enough, try for a disk sector worth of stack
868 instead */
869 buffersize = 512;
870 buffer = (char *)alloca(buffersize);
871 }
872
873 if (buffer == NULL) {
874 return -1;
875 }
876
877 buffersize &= ~0x1ff; /* Round buffer size to multiple of sector
878 size */
879
880 int src_fd = open(src, O_RDONLY);
881 if (src_fd >= 0) {
882 int oflag = O_WRONLY|O_CREAT;
883
884 if (!(flags & PASTE_OVERWRITE)) {
885 oflag |= O_EXCL;
886 }
887
888 int target_fd = open(target, oflag, 0666);
889 if (target_fd >= 0) {
890 off_t total_size = 0;
891 off_t next_cancel_test = 0; /* No excessive button polling */
892
893 rc = OPRC_SUCCESS;
894
895 while (rc == OPRC_SUCCESS) {
896 if (total_size >= next_cancel_test) {
897 next_cancel_test = total_size + 0x10000;
898 if (poll_cancel_action(src)) {
899 rc = OPRC_CANCELLED;
900 break;
901 }
902 }
903
904 ssize_t bytesread = read(src_fd, buffer, buffersize);
905 if (bytesread <= 0) {
906 if (bytesread < 0) {
907 rc = -1;
908 }
909 /* else eof on buffer boundary; nothing to write */
910 break;
911 }
912
913 ssize_t byteswritten = write(target_fd, buffer, bytesread);
914 if (byteswritten < bytesread) {
915 /* Some I/O error */
916 rc = -1;
917 break;
918 }
919
920 total_size += byteswritten;
921
922 if (bytesread < (ssize_t)buffersize) {
923 /* EOF with trailing bytes */
924 break;
925 }
926 }
927
928 if (rc == OPRC_SUCCESS) {
929 /* If overwriting, set the correct length if original was
930 longer */
931 rc = ftruncate(target_fd, total_size);
932 }
933
934 close(target_fd);
935
936 if (rc != OPRC_SUCCESS) {
937 /* Copy failed. Cleanup. */
938 remove(target);
939 }
940 }
941
942 close(src_fd);
943 }
944
945 if (rc == OPRC_SUCCESS && !(flags & PASTE_COPY)) {
946 /* Remove the source file */
947 rc = remove(src);
948 }
949
950 return rc;
951}
952
953/* Paste a directory */
954static int clipboard_pastedirectory(struct dirrecurse_params *src,
955 struct dirrecurse_params *target,
956 unsigned int flags)
957{
958 int rc = -1;
959
960 while (!(flags & (PASTE_COPY | PASTE_EXDEV))) {
961 if ((flags & PASTE_OVERWRITE) || !file_exists(target->path)) {
962 /* Just try to move the directory */
963 if (poll_cancel_action(src->path)) {
964 rc = OPRC_CANCELLED;
965 } else {
966 rc = rename(src->path, target->path);
967 }
968
969 if (rc < 0) {
970 int errnum = errno;
971 if (errnum == ENOTEMPTY && (flags & PASTE_OVERWRITE)) {
972 /* Directory is not empty thus rename() will not do a quick
973 overwrite */
974 break;
975 }
976 #ifdef HAVE_MULTIVOLUME
977 else if (errnum == EXDEV) {
978 /* Failed because cross volume rename doesn't work; force
979 a move instead */
980 flags |= PASTE_EXDEV;
981 break;
982 }
983 #endif /* HAVE_MULTIVOLUME */
984 }
985 }
986
987 return rc;
988 }
989
990 DIR *srcdir = opendir(src->path);
991
992 if (srcdir) {
993 /* Make a directory to copy things to */
994 rc = mkdir(target->path);
995 if (rc < 0 && errno == EEXIST && (flags & PASTE_OVERWRITE)) {
996 /* Exists and overwrite was approved */
997 rc = OPRC_SUCCESS;
998 }
999 }
1000
1001 size_t srcap = src->append, targetap = target->append;
1002
1003 /* Walk through the directory content; this loop will exit as soon as
1004 there's a problem */
1005 while (rc == OPRC_SUCCESS) {
1006 errno = 0; /* Distinguish failure from eod */
1007 struct dirent *entry = readdir(srcdir);
1008 if (!entry) {
1009 if (errno) {
1010 rc = -1;
1011 }
1012 break;
1013 }
1014
1015 struct dirinfo info = dir_get_info(srcdir, entry);
1016 if ((info.attribute & ATTR_DIRECTORY) &&
1017 is_dotdir_name(entry->d_name)) {
1018 continue; /* Skip these */
1019 }
1020
1021 /* Append names to current directories */
1022 src->append = srcap +
1023 path_append(&src->path[srcap], PA_SEP_HARD, entry->d_name,
1024 sizeof(src->path) - srcap);
1025
1026 target->append = targetap +
1027 path_append(&target->path[targetap], PA_SEP_HARD, entry->d_name,
1028 sizeof (target->path) - targetap);
1029
1030 if (src->append >= sizeof (src->path) ||
1031 target->append >= sizeof (target->path)) {
1032 rc = -1; /* No space left in buffer */
1033 break;
1034 }
1035
1036 if (poll_cancel_action(src->path)) {
1037 rc = OPRC_CANCELLED;
1038 break;
1039 }
1040
1041 DEBUGF("Copy %s to %s\n", src->path, target->path);
1042
1043 if (info.attribute & ATTR_DIRECTORY) {
1044 /* Copy/move a subdirectory */
1045 rc = clipboard_pastedirectory(src, target, flags); /* recursion */
1046 } else {
1047 /* Copy/move a file */
1048 rc = clipboard_pastefile(src->path, target->path, flags);
1049 }
1050
1051 /* Remove basenames we added above */
1052 src->path[srcap] = target->path[targetap] = '\0';
1053 }
1054
1055 if (rc == OPRC_SUCCESS && !(flags & PASTE_COPY)) {
1056 /* Remove the now empty directory */
1057 rc = rmdir(src->path);
1058 }
1059
1060 closedir(srcdir);
1061 return rc;
1062} 578}
1063 579
1064static bool clipboard_cut(void) 580static bool clipboard_cut(void)
@@ -1079,82 +595,27 @@ static int clipboard_paste(void)
1079 if (!clipboard.path[0]) 595 if (!clipboard.path[0])
1080 return 1; 596 return 1;
1081 597
1082 int rc = -1; 598 int rc = copy_move_fileobject(clipboard.path, getcwd(NULL, 0), clipboard.flags);
1083
1084 struct dirrecurse_params src, target;
1085 unsigned int flags = clipboard.flags;
1086
1087 /* Figure out the name of the selection */
1088 const char *nameptr;
1089 path_basename(clipboard.path, &nameptr);
1090 599
1091 /* Final target is current directory plus name of selection */
1092 target.append = path_append(target.path, getcwd(NULL, 0),
1093 nameptr, sizeof (target.path));
1094 600
1095 switch (target.append < sizeof (target.path) ? 601 clear_screen_buffer(true);
1096 relate(clipboard.path, target.path) : -1)
1097 {
1098 case RELATE_SAME:
1099 rc = OPRC_NOOP;
1100 break;
1101
1102 case RELATE_DIFFERENT:
1103 if (file_exists(target.path)) {
1104 /* If user chooses not to overwrite, cancel */
1105 if (confirm_overwrite_yesno() == YESNO_NO) {
1106 rc = OPRC_NOOVERWRT;
1107 break;
1108 }
1109
1110 flags |= PASTE_OVERWRITE;
1111 }
1112
1113 clear_display(true);
1114 splash(HZ/2, (flags & PASTE_COPY) ? ID2P(LANG_COPYING) :
1115 ID2P(LANG_MOVING));
1116
1117 /* Now figure out what we're doing */
1118 cpu_boost(true);
1119
1120 if (clipboard.attr & ATTR_DIRECTORY) {
1121 /* Copy or move a subdirectory */
1122 src.append = strlcpy(src.path, clipboard.path,
1123 sizeof (src.path));
1124 if (src.append < sizeof (src.path)) {
1125 rc = clipboard_pastedirectory(&src, &target, flags);
1126 }
1127 } else {
1128 /* Copy or move a file */
1129 rc = clipboard_pastefile(clipboard.path, target.path, flags);
1130 }
1131
1132 cpu_boost(false);
1133 break;
1134
1135 case RELATE_PREFIX:
1136 default: /* Some other relation / failure */
1137 break;
1138 }
1139
1140 clear_display(true);
1141 602
1142 switch (rc) 603 switch (rc)
1143 { 604 {
1144 case OPRC_CANCELLED: 605 case FORC_CANCELLED:
1145 splash_cancelled(); 606 splash_cancelled();
1146 /* Fallthrough */ 607 /* Fallthrough */
1147 case OPRC_SUCCESS: 608 case FORC_SUCCESS:
1148 onplay_result = ONPLAY_RELOAD_DIR; 609 onplay_result = ONPLAY_RELOAD_DIR;
1149 /* Fallthrough */ 610 /* Fallthrough */
1150 case OPRC_NOOP: 611 case FORC_NOOP:
1151 clipboard_clear_selection(&clipboard); 612 clipboard_clear_selection(&clipboard);
1152 /* Fallthrough */ 613 /* Fallthrough */
1153 case OPRC_NOOVERWRT: 614 case FORC_NOOVERWRT:
1154 break; 615 break;
1155 default: 616 default:
1156 if (rc < OPRC_SUCCESS) { 617 if (rc < FORC_SUCCESS) {
1157 splash_failed(LANG_PASTE); 618 splash_failed(LANG_PASTE, rc);
1158 onplay_result = ONPLAY_RELOAD_DIR; 619 onplay_result = ONPLAY_RELOAD_DIR;
1159 } 620 }
1160 } 621 }
@@ -1249,13 +710,57 @@ MENUITEM_FUNCTION(pitch_screen_item, 0, ID2P(LANG_PITCH),
1249 gui_syncpitchscreen_run, NULL, Icon_Audio); 710 gui_syncpitchscreen_run, NULL, Icon_Audio);
1250#endif 711#endif
1251 712
713static int clipboard_delete_selected_fileobject(void)
714{
715 int rc = delete_fileobject(selected_file);
716 if (rc < FORC_SUCCESS) {
717 splash_failed(LANG_DELETE, rc);
718 } else if (rc == FORC_CANCELLED) {
719 splash_cancelled();
720 }
721 if (rc != FORC_NOOP) {
722 /* Could have failed after some but not all needed changes; reload */
723 onplay_result = ONPLAY_RELOAD_DIR;
724 }
725 return 1;
726}
727
728static void show_result(int rc, int lang_what)
729{
730 if (rc < FORC_SUCCESS) {
731 splash_failed(lang_what, rc);
732 } else if (rc == FORC_CANCELLED) {
733 /* splash_cancelled(); kbd_input() splashes it */
734 } else if (rc == FORC_SUCCESS) {
735 onplay_result = ONPLAY_RELOAD_DIR;
736 }
737}
738
739static int clipboard_create_dir(void)
740{
741 int rc = create_dir();
742
743 show_result(rc, LANG_CREATE_DIR);
744
745 return 1;
746}
747
748static int clipboard_rename_selected_file(void)
749{
750 int rc = rename_file(selected_file);
751
752 show_result(rc, LANG_RENAME);
753
754 return 1;
755}
756
1252/* CONTEXT_[TREE|ID3DB] items */ 757/* CONTEXT_[TREE|ID3DB] items */
1253static int clipboard_callback(int action, 758static int clipboard_callback(int action,
1254 const struct menu_item_ex *this_item, 759 const struct menu_item_ex *this_item,
1255 struct gui_synclist *this_list); 760 struct gui_synclist *this_list);
1256 761
1257MENUITEM_FUNCTION(rename_file_item, 0, ID2P(LANG_RENAME), 762MENUITEM_FUNCTION(rename_file_item, 0, ID2P(LANG_RENAME),
1258 rename_file, clipboard_callback, Icon_NOICON); 763 clipboard_rename_selected_file, clipboard_callback, Icon_NOICON);
1259MENUITEM_FUNCTION(clipboard_cut_item, 0, ID2P(LANG_CUT), 764MENUITEM_FUNCTION(clipboard_cut_item, 0, ID2P(LANG_CUT),
1260 clipboard_cut, clipboard_callback, Icon_NOICON); 765 clipboard_cut, clipboard_callback, Icon_NOICON);
1261MENUITEM_FUNCTION(clipboard_copy_item, 0, ID2P(LANG_COPY), 766MENUITEM_FUNCTION(clipboard_copy_item, 0, ID2P(LANG_COPY),
@@ -1263,11 +768,11 @@ MENUITEM_FUNCTION(clipboard_copy_item, 0, ID2P(LANG_COPY),
1263MENUITEM_FUNCTION(clipboard_paste_item, 0, ID2P(LANG_PASTE), 768MENUITEM_FUNCTION(clipboard_paste_item, 0, ID2P(LANG_PASTE),
1264 clipboard_paste, clipboard_callback, Icon_NOICON); 769 clipboard_paste, clipboard_callback, Icon_NOICON);
1265MENUITEM_FUNCTION(delete_file_item, 0, ID2P(LANG_DELETE), 770MENUITEM_FUNCTION(delete_file_item, 0, ID2P(LANG_DELETE),
1266 delete_file_dir, clipboard_callback, Icon_NOICON); 771 clipboard_delete_selected_fileobject, clipboard_callback, Icon_NOICON);
1267MENUITEM_FUNCTION(delete_dir_item, 0, ID2P(LANG_DELETE_DIR), 772MENUITEM_FUNCTION(delete_dir_item, 0, ID2P(LANG_DELETE_DIR),
1268 delete_file_dir, clipboard_callback, Icon_NOICON); 773 clipboard_delete_selected_fileobject, clipboard_callback, Icon_NOICON);
1269MENUITEM_FUNCTION(create_dir_item, 0, ID2P(LANG_CREATE_DIR), 774MENUITEM_FUNCTION(create_dir_item, 0, ID2P(LANG_CREATE_DIR),
1270 create_dir, clipboard_callback, Icon_NOICON); 775 clipboard_create_dir, clipboard_callback, Icon_NOICON);
1271 776
1272/* other items */ 777/* other items */
1273static bool list_viewers(void) 778static bool list_viewers(void)
@@ -1337,12 +842,18 @@ MENUITEM_FUNCTION(add_to_faves_item, 0, ID2P(LANG_ADD_TO_FAVES),
1337 onplay_add_to_shortcuts, 842 onplay_add_to_shortcuts,
1338 clipboard_callback, Icon_NOICON); 843 clipboard_callback, Icon_NOICON);
1339 844
845static void set_dir_helper(char* dirnamebuf, size_t bufsz)
846{
847 path_append(dirnamebuf, selected_file, PA_SEP_HARD, bufsz);
848 settings_save();
849}
850
1340#if LCD_DEPTH > 1 851#if LCD_DEPTH > 1
1341static bool set_backdrop(void) 852static bool set_backdrop(void)
1342{ 853{
1343 path_append(global_settings.backdrop_file, selected_file, 854 set_dir_helper(global_settings.backdrop_file,
1344 PA_SEP_HARD, sizeof(global_settings.backdrop_file)); 855 sizeof(global_settings.backdrop_file));
1345 settings_save(); 856
1346 skin_backdrop_load_setting(); 857 skin_backdrop_load_setting();
1347 skin_backdrop_show(sb_get_backdrop(SCREEN_MAIN)); 858 skin_backdrop_show(sb_get_backdrop(SCREEN_MAIN));
1348 return true; 859 return true;
@@ -1353,9 +864,8 @@ MENUITEM_FUNCTION(set_backdrop_item, 0, ID2P(LANG_SET_AS_BACKDROP),
1353#ifdef HAVE_RECORDING 864#ifdef HAVE_RECORDING
1354static bool set_recdir(void) 865static bool set_recdir(void)
1355{ 866{
1356 path_append(global_settings.rec_directory, selected_file, 867 set_dir_helper(global_settings.rec_directory,
1357 PA_SEP_HARD, sizeof(global_settings.rec_directory)); 868 sizeof(global_settings.rec_directory));
1358 settings_save();
1359 return false; 869 return false;
1360} 870}
1361MENUITEM_FUNCTION(set_recdir_item, 0, ID2P(LANG_RECORDING_DIR), 871MENUITEM_FUNCTION(set_recdir_item, 0, ID2P(LANG_RECORDING_DIR),
@@ -1363,10 +873,8 @@ MENUITEM_FUNCTION(set_recdir_item, 0, ID2P(LANG_RECORDING_DIR),
1363#endif 873#endif
1364static bool set_startdir(void) 874static bool set_startdir(void)
1365{ 875{
1366 path_append(global_settings.start_directory, selected_file, 876 set_dir_helper(global_settings.start_directory,
1367 PA_SEP_HARD, sizeof(global_settings.start_directory)); 877 sizeof(global_settings.start_directory));
1368
1369 settings_save();
1370 return false; 878 return false;
1371} 879}
1372MENUITEM_FUNCTION(set_startdir_item, 0, ID2P(LANG_START_DIR), 880MENUITEM_FUNCTION(set_startdir_item, 0, ID2P(LANG_START_DIR),
@@ -1384,16 +892,14 @@ MENUITEM_FUNCTION(set_catalogdir_item, 0, ID2P(LANG_PLAYLIST_DIR),
1384#ifdef HAVE_TAGCACHE 892#ifdef HAVE_TAGCACHE
1385static bool set_databasedir(void) 893static bool set_databasedir(void)
1386{ 894{
1387 path_append(global_settings.tagcache_db_path, selected_file,
1388 PA_SEP_SOFT, sizeof(global_settings.tagcache_db_path));
1389
1390 struct tagcache_stat *tc_stat = tagcache_get_stat(); 895 struct tagcache_stat *tc_stat = tagcache_get_stat();
1391 if (strcasecmp(global_settings.tagcache_db_path, tc_stat->db_path)) 896 if (strcasecmp(selected_file, tc_stat->db_path))
1392 { 897 {
1393 splash(HZ, ID2P(LANG_PLEASE_REBOOT)); 898 splash(HZ, ID2P(LANG_PLEASE_REBOOT));
1394 } 899 }
1395 900
1396 settings_save(); 901 set_dir_helper(global_settings.tagcache_db_path,
902 sizeof(global_settings.tagcache_db_path));
1397 return false; 903 return false;
1398} 904}
1399MENUITEM_FUNCTION(set_databasedir_item, 0, ID2P(LANG_DATABASE_DIR), 905MENUITEM_FUNCTION(set_databasedir_item, 0, ID2P(LANG_DATABASE_DIR),
@@ -1589,7 +1095,7 @@ static bool hotkey_delete_item(void)
1589 return false; 1095 return false;
1590#endif 1096#endif
1591 1097
1592 return delete_file_dir(); 1098 return clipboard_delete_selected_fileobject();
1593} 1099}
1594 1100
1595static bool hotkey_open_with(void) 1101static bool hotkey_open_with(void)