summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apps/SOURCES1
-rw-r--r--apps/debug_menu.c96
-rw-r--r--apps/fileop.c606
-rw-r--r--apps/fileop.h70
-rw-r--r--apps/lang/deutsch.lang118
-rw-r--r--apps/misc.c18
-rw-r--r--apps/misc.h4
-rw-r--r--apps/onplay.c784
-rw-r--r--apps/plugins/fft/fft.c5
-rw-r--r--bootloader/rocker_linux.c60
-rw-r--r--firmware/SOURCES11
-rw-r--r--firmware/drivers/ata.c10
-rw-r--r--firmware/target/arm/s5l8702/nor-target.h28
-rwxr-xr-xtools/configure2
14 files changed, 1120 insertions, 693 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/debug_menu.c b/apps/debug_menu.c
index 2b14de8714..627d7f7bea 100644
--- a/apps/debug_menu.c
+++ b/apps/debug_menu.c
@@ -132,6 +132,10 @@
132#include "rb-loader.h" 132#include "rb-loader.h"
133#endif 133#endif
134 134
135#if defined(IPOD_6G)
136#include "nor-target.h"
137#endif
138
135#define SCREEN_MAX_CHARS (LCD_WIDTH / SYSFONT_WIDTH) 139#define SCREEN_MAX_CHARS (LCD_WIDTH / SYSFONT_WIDTH)
136 140
137static const char* threads_getname(int selected_item, void *data, 141static const char* threads_getname(int selected_item, void *data,
@@ -2581,6 +2585,95 @@ static bool dbg_boot_data(void)
2581} 2585}
2582#endif /* defined(HAVE_BOOTDATA) && !defined(SIMULATOR) */ 2586#endif /* defined(HAVE_BOOTDATA) && !defined(SIMULATOR) */
2583 2587
2588#if defined(IPOD_6G) && !defined(SIMULATOR)
2589#define SYSCFG_MAX_ENTRIES 10 // 9 on iPod Classic/6G
2590
2591static bool dbg_syscfg(void) {
2592 struct simplelist_info info;
2593 struct SysCfgHeader syscfg_hdr;
2594 size_t syscfg_hdr_size = sizeof(struct SysCfgHeader);
2595 size_t syscfg_entry_size = sizeof(struct SysCfgEntry);
2596 struct SysCfgEntry syscfg_entries[SYSCFG_MAX_ENTRIES];
2597
2598 simplelist_info_init(&info, "SysCfg NOR contents", 1, NULL);
2599 simplelist_set_line_count(0);
2600
2601 bootflash_init(SPI_PORT);
2602 bootflash_read(SPI_PORT, 0, syscfg_hdr_size, &syscfg_hdr);
2603
2604 if (syscfg_hdr.magic != SYSCFG_MAGIC) {
2605 simplelist_addline("SCfg magic not found");
2606 bootflash_close(SPI_PORT);
2607 return simplelist_show_list(&info);
2608 }
2609
2610 simplelist_addline("Total size: %u bytes", syscfg_hdr.size);
2611 simplelist_addline("Entries: %u", syscfg_hdr.num_entries);
2612
2613 size_t calculated_syscfg_size = syscfg_hdr_size + syscfg_entry_size * syscfg_hdr.num_entries;
2614
2615 if (syscfg_hdr.size != calculated_syscfg_size) {
2616 simplelist_addline("Wrong size: expected %u, got %u", calculated_syscfg_size, syscfg_hdr.size);
2617 bootflash_close(SPI_PORT);
2618 return simplelist_show_list(&info);
2619 }
2620
2621 if (syscfg_hdr.num_entries > SYSCFG_MAX_ENTRIES) {
2622 simplelist_addline("Too many entries, showing first %u", syscfg_hdr.num_entries);
2623 }
2624
2625 size_t syscfg_num_entries = MIN(syscfg_hdr.num_entries, SYSCFG_MAX_ENTRIES);
2626 size_t syscfg_entries_size = syscfg_entry_size * syscfg_num_entries;
2627
2628 bootflash_read(SPI_PORT, syscfg_hdr_size, syscfg_entries_size, &syscfg_entries);
2629 bootflash_close(SPI_PORT);
2630
2631 for (size_t i = 0; i < syscfg_num_entries; i++) {
2632 struct SysCfgEntry* entry = &syscfg_entries[i];
2633 char* tag = (char *)&entry->tag;
2634 uint32_t* data32 = (uint32_t *)entry->data;
2635
2636 switch (entry->tag) {
2637 case SYSCFG_TAG_SRNM:
2638 simplelist_addline("Serial number (SrNm): %s", entry->data);
2639 break;
2640 case SYSCFG_TAG_FWID:
2641 simplelist_addline("Firmware ID (FwId): %07X", data32[1] & 0x0FFFFFFF);
2642 break;
2643 case SYSCFG_TAG_HWID:
2644 simplelist_addline("Hardware ID (HwId): %08X", data32[0]);
2645 break;
2646 case SYSCFG_TAG_HWVR:
2647 simplelist_addline("Hardware version (HwVr): %06X", data32[1]);
2648 break;
2649 case SYSCFG_TAG_CODC:
2650 simplelist_addline("Codec (Codc): %s", entry->data);
2651 break;
2652 case SYSCFG_TAG_SWVR:
2653 simplelist_addline("Software version (SwVr): %s", entry->data);
2654 break;
2655 case SYSCFG_TAG_MLBN:
2656 simplelist_addline("Logic board serial number (MLBN): %s", entry->data);
2657 break;
2658 case SYSCFG_TAG_MODN:
2659 simplelist_addline("Model number (Mod#): %s", entry->data);
2660 break;
2661 case SYSCFG_TAG_REGN:
2662 simplelist_addline("Sales region (Regn): %08X %08X", data32[0], data32[1]);
2663 break;
2664 default:
2665 simplelist_addline("%c%c%c%c: %08X %08X %08X %08X",
2666 tag[3], tag[2], tag[1], tag[0],
2667 data32[0], data32[1], data32[2], data32[3]
2668 );
2669 break;
2670 }
2671 }
2672
2673 return simplelist_show_list(&info);
2674}
2675#endif
2676
2584/****** The menu *********/ 2677/****** The menu *********/
2585static const struct { 2678static const struct {
2586 unsigned char *desc; /* string or ID */ 2679 unsigned char *desc; /* string or ID */
@@ -2691,6 +2784,9 @@ static const struct {
2691#if defined(HAVE_BOOTDATA) && !defined(SIMULATOR) 2784#if defined(HAVE_BOOTDATA) && !defined(SIMULATOR)
2692 {"Boot data", dbg_boot_data }, 2785 {"Boot data", dbg_boot_data },
2693#endif 2786#endif
2787#if defined(IPOD_6G) && !defined(SIMULATOR)
2788 {"View SysCfg", dbg_syscfg },
2789#endif
2694}; 2790};
2695 2791
2696static int menu_action_callback(int btn, struct gui_synclist *lists) 2792static int menu_action_callback(int btn, struct gui_synclist *lists)
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/lang/deutsch.lang b/apps/lang/deutsch.lang
index 4a3ceda374..096a945bd9 100644
--- a/apps/lang/deutsch.lang
+++ b/apps/lang/deutsch.lang
@@ -4,7 +4,6 @@
4# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 4# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
5# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 5# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
6# \/ \/ \/ \/ \/ 6# \/ \/ \/ \/ \/
7# $Id$
8# 7#
9# This program is free software; you can redistribute it and/or 8# This program is free software; you can redistribute it and/or
10# modify it under the terms of the GNU General Public License 9# modify it under the terms of the GNU General Public License
@@ -35,6 +34,7 @@
35# - Kai Posadowsky 34# - Kai Posadowsky
36# - Udo Schläpfer 35# - Udo Schläpfer
37# - Johannes Rauh 36# - Johannes Rauh
37# - Wilfried Winkler
38<phrase> 38<phrase>
39 id: LANG_SET_BOOL_YES 39 id: LANG_SET_BOOL_YES
40 desc: bool true representation 40 desc: bool true representation
@@ -12130,10 +12130,10 @@
12130 desc: playing time screen 12130 desc: playing time screen
12131 user: core 12131 user: core
12132 <source> 12132 <source>
12133 *: "Track remaining: %s" 12133 *: "Track remaining:"
12134 </source> 12134 </source>
12135 <dest> 12135 <dest>
12136 *: "Titel verbleibend: %s" 12136 *: "Titel verbleibend:"
12137 </dest> 12137 </dest>
12138 <voice> 12138 <voice>
12139 *: "Titel verbleibend" 12139 *: "Titel verbleibend"
@@ -12256,10 +12256,10 @@
12256 desc: playing time screen 12256 desc: playing time screen
12257 user: core 12257 user: core
12258 <source> 12258 <source>
12259 *: "Playlist elapsed: %s / %s %ld%%" 12259 *: "Playlist elapsed:"
12260 </source> 12260 </source>
12261 <dest> 12261 <dest>
12262 *: "Wiedergabeliste vergangen: %s / %s %ld%%" 12262 *: "Wiedergabeliste vergangen:"
12263 </dest> 12263 </dest>
12264 <voice> 12264 <voice>
12265 *: "Wiedergabeliste vergangen" 12265 *: "Wiedergabeliste vergangen"
@@ -12430,10 +12430,10 @@
12430 desc: playing time screen 12430 desc: playing time screen
12431 user: core 12431 user: core
12432 <source> 12432 <source>
12433 *: "Track %d / %d %d%%" 12433 *: "Track:"
12434 </source> 12434 </source>
12435 <dest> 12435 <dest>
12436 *: "Titel %d / %d %d%%" 12436 *: "Titel:"
12437 </dest> 12437 </dest>
12438 <voice> 12438 <voice>
12439 *: "Titel" 12439 *: "Titel"
@@ -13118,10 +13118,10 @@
13118 desc: playing time screen 13118 desc: playing time screen
13119 user: core 13119 user: core
13120 <source> 13120 <source>
13121 *: "Storage: %s (done %s, remaining %s)" 13121 *: "Storage (Done / Remaining):"
13122 </source> 13122 </source>
13123 <dest> 13123 <dest>
13124 *: "Speicher: %s (done %s, remaining %s)" 13124 *: "Speicher (fertig/ verbleibend):"
13125 </dest> 13125 </dest>
13126 <voice> 13126 <voice>
13127 *: "Speicher" 13127 *: "Speicher"
@@ -13581,10 +13581,10 @@
13581 desc: playing time screen 13581 desc: playing time screen
13582 user: core 13582 user: core
13583 <source> 13583 <source>
13584 *: "Playlist remaining: %s" 13584 *: "Playlist remaining:"
13585 </source> 13585 </source>
13586 <dest> 13586 <dest>
13587 *: "Wiedrgabeliste verbleibend: %s" 13587 *: "Wiedrgabeliste verbleibend:"
13588 </dest> 13588 </dest>
13589 <voice> 13589 <voice>
13590 *: "Wiedrgabeliste verbleibend" 13590 *: "Wiedrgabeliste verbleibend"
@@ -13873,10 +13873,10 @@
13873 desc: playing time screen 13873 desc: playing time screen
13874 user: core 13874 user: core
13875 <source> 13875 <source>
13876 *: "Track elapsed: %s / %s %ld%%" 13876 *: "Track elapsed:"
13877 </source> 13877 </source>
13878 <dest> 13878 <dest>
13879 *: "Titel vergangen: %s / %s %ld%%" 13879 *: "Titel vergangen:"
13880 </dest> 13880 </dest>
13881 <voice> 13881 <voice>
13882 *: "Titel vergangen" 13882 *: "Titel vergangen"
@@ -13929,10 +13929,10 @@
13929 desc: playing time screen 13929 desc: playing time screen
13930 user: core 13930 user: core
13931 <source> 13931 <source>
13932 *: "Average bitrate: %ld kbps" 13932 *: "Average bitrate:"
13933 </source> 13933 </source>
13934 <dest> 13934 <dest>
13935 *: "Durchschnittliche Bitrate: %ld kbps" 13935 *: "Durchschnittliche Bitrate:"
13936 </dest> 13936 </dest>
13937 <voice> 13937 <voice>
13938 *: "Durchschnittliche Bitrate" 13938 *: "Durchschnittliche Bitrate"
@@ -14050,10 +14050,10 @@
14050 desc: playing time screen 14050 desc: playing time screen
14051 user: core 14051 user: core
14052 <source> 14052 <source>
14053 *: "Average track size: %s" 14053 *: "Average track size:"
14054 </source> 14054 </source>
14055 <dest> 14055 <dest>
14056 *: "Durchschnittliche Titelgröße: %s" 14056 *: "Durchschnittliche Titelgröße:"
14057 </dest> 14057 </dest>
14058 <voice> 14058 <voice>
14059 *: "Durchschnittliche Titelgröße" 14059 *: "Durchschnittliche Titelgröße"
@@ -16345,3 +16345,87 @@
16345 *: "Deustch" 16345 *: "Deustch"
16346 </voice> 16346 </voice>
16347</phrase> 16347</phrase>
16348<phrase>
16349 id: LANG_ERROR_FORMATSTR
16350 desc: for general use
16351 user: core
16352 <source>
16353 *: "Error: %s"
16354 </source>
16355 <dest>
16356 *: "Fehler: %s"
16357 </dest>
16358 <voice>
16359 *: "Fehler"
16360 </voice>
16361</phrase>
16362<phrase>
16363 id: LANG_MIKMOD_SETTINGS
16364 desc: mikmod plugin
16365 user: core
16366 <source>
16367 *: "Mikmod Settings"
16368 </source>
16369 <dest>
16370 *: "Mikmod Einstellungen"
16371 </dest>
16372 <voice>
16373 *: "Mik mod Einstellungen"
16374 </voice>
16375</phrase>
16376<phrase>
16377 id: LANG_MIKMOD_MENU
16378 desc: mikmod plugin
16379 user: core
16380 <source>
16381 *: "Mikmod Menu"
16382 </source>
16383 <dest>
16384 *: "Mikmod Menü"
16385 </dest>
16386 <voice>
16387 *: "Mik mod Menü"
16388 </voice>
16389</phrase>
16390<phrase>
16391 id: LANG_CHESSBOX_MENU
16392 desc: chessbox plugin
16393 user: core
16394 <source>
16395 *: "Chessbox Menu"
16396 </source>
16397 <dest>
16398 *: "Schachbox Menü"
16399 </dest>
16400 <voice>
16401 *: "Schach box Menü"
16402 </voice>
16403</phrase>
16404<phrase>
16405 id: VOICE_INVALID_VOICE_FILE
16406 desc: played if the voice file fails to load
16407 user: core
16408 <source>
16409 *: ""
16410 </source>
16411 <dest>
16412 *: ""
16413 </dest>
16414 <voice>
16415 *: "Ungültige Sprach Datei"
16416 </voice>
16417</phrase>
16418<phrase>
16419 id: LANG_PERCENT_FORMAT
16420 desc: percent formatting ( `10%` is default , for `10 %` use '%ld %%' , for `%10` use '%%%ld' and so on)
16421 user: core
16422 <source>
16423 *: "%ld%%"
16424 </source>
16425 <dest>
16426 *: "~%ld%%"
16427 </dest>
16428 <voice>
16429 *: none
16430 </voice>
16431</phrase>
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)
diff --git a/apps/plugins/fft/fft.c b/apps/plugins/fft/fft.c
index 4d36302ddf..99d9e033fc 100644
--- a/apps/plugins/fft/fft.c
+++ b/apps/plugins/fft/fft.c
@@ -33,8 +33,13 @@
33#include "lib/osd.h" 33#include "lib/osd.h"
34 34
35#ifndef HAVE_LCD_COLOR 35#ifndef HAVE_LCD_COLOR
36#if defined(SIMULATOR)
37// Use the one in pluginlib/osd
38extern GREY_INFO_STRUCT
39#else
36GREY_INFO_STRUCT 40GREY_INFO_STRUCT
37#endif 41#endif
42#endif
38 43
39#include "lib/pluginlib_actions.h" 44#include "lib/pluginlib_actions.h"
40 45
diff --git a/bootloader/rocker_linux.c b/bootloader/rocker_linux.c
index c0cba2776c..4d095b8885 100644
--- a/bootloader/rocker_linux.c
+++ b/bootloader/rocker_linux.c
@@ -145,6 +145,7 @@ enum boot_mode
145 BOOT_OF, 145 BOOT_OF,
146 BOOT_COUNT, 146 BOOT_COUNT,
147 BOOT_STOP, /* power down/suspend */ 147 BOOT_STOP, /* power down/suspend */
148 BOOT_CANARY,
148}; 149};
149 150
150static void display_text_center(int y, const char *text) 151static void display_text_center(int y, const char *text)
@@ -165,16 +166,17 @@ static void display_text_centerf(int y, const char *format, ...)
165} 166}
166 167
167/* get timeout before taking action if the user doesn't touch the device */ 168/* get timeout before taking action if the user doesn't touch the device */
168static int get_inactivity_tmo(void) 169static int get_inactivity_tmo(int same_as_last)
169{ 170{
170#if defined(HAS_BUTTON_HOLD) 171#if defined(HAS_BUTTON_HOLD)
171 if(button_hold()) 172 if(button_hold())
172 return 5 * HZ; /* Inactivity timeout when on hold */ 173 return 5 * HZ; /* Inactivity timeout when on hold */
173 else 174 else
174#endif 175#endif
175 return 10 * HZ; /* Inactivity timeout when not on hold */ 176 if (same_as_last)
176 177 return 1 * HZ; /* Timeout when mode is the same as the previous mode */
177 // XXX if booting the last selection, use a short timeout? 178 else
179 return 10 * HZ; /* Default timeout */
178} 180}
179 181
180/* return action on idle timeout */ 182/* return action on idle timeout */
@@ -188,34 +190,38 @@ static enum boot_mode inactivity_action(enum boot_mode cur_selection)
188 return cur_selection; /* return last choice */ 190 return cur_selection; /* return last choice */
189} 191}
190 192
191/* we store the boot mode in a file in /tmp so we can reload it between 'boots' 193static int mounted = 0;
192 * (since the mostly suspends instead of powering down) */
193static enum boot_mode load_boot_mode(enum boot_mode mode)
194{
195 int fd = open(BASE_DIR "/.rockbox/rb_bl_mode.txt", O_RDONLY);
196 if(fd >= 0)
197 {
198 read(fd, &mode, sizeof(mode));
199 close(fd);
200 }
201 return mode;
202}
203 194
204static void mount_storage(int enable) 195static void mount_storage(int enable)
205{ 196{
206 if (enable) { 197 if (enable && !mounted) {
207 system("/bin/mkdir -p " BASE_DIR); 198 system("/bin/mkdir -p " BASE_DIR);
208 if (system("/bin/mount /dev/mmcblk0 " BASE_DIR)) 199 if (system("/bin/mount /dev/mmcblk0 " BASE_DIR))
209 system("/bin/mount /dev/mmcblk0p1 " BASE_DIR); 200 system("/bin/mount /dev/mmcblk0p1 " BASE_DIR);
210 // XXX possibly invoke sys_serv -> "MOUNT:MOUNT:%s %s", blkdev, mntpoint 201 // XXX possibly invoke sys_serv -> "MOUNT:MOUNT:%s %s", blkdev, mntpoint
211 } else { 202 } else if (!enable && mounted) {
212 system("/bin/unmount " BASE_DIR); 203 system("/bin/unmount " BASE_DIR);
213 // XXX possibly invoke sys_serv -> "MOUNT:UNMOUNT:%s %s", mntpoint 204 // XXX possibly invoke sys_serv -> "MOUNT:UNMOUNT:%s %s", mntpoint
214 } 205 }
206 mounted = enable;
207}
208
209/* we store the boot mode in a file so we can reload it between 'boots' */
210static enum boot_mode load_boot_mode(enum boot_mode mode)
211{
212 mount_storage(true);
213 int fd = open(BASE_DIR "/.rockbox/rb_bl_mode.txt", O_RDONLY);
214 if(fd >= 0)
215 {
216 read(fd, &mode, sizeof(mode));
217 close(fd);
218 }
219 return mode;
215} 220}
216 221
217static void save_boot_mode(enum boot_mode mode) 222static void save_boot_mode(enum boot_mode mode)
218{ 223{
224 mount_storage(true);
219 int fd = open(BASE_DIR "/.rockbox/rb_bl_mode.txt", O_RDWR | O_CREAT | O_TRUNC); 225 int fd = open(BASE_DIR "/.rockbox/rb_bl_mode.txt", O_RDWR | O_CREAT | O_TRUNC);
220 if(fd >= 0) 226 if(fd >= 0)
221 { 227 {
@@ -227,9 +233,9 @@ static void save_boot_mode(enum boot_mode mode)
227static enum boot_mode get_boot_mode(void) 233static enum boot_mode get_boot_mode(void)
228{ 234{
229 /* load previous mode, or start with rockbox if none */ 235 /* load previous mode, or start with rockbox if none */
230 enum boot_mode init_mode = load_boot_mode(BOOT_ROCKBOX); 236 enum boot_mode init_mode = load_boot_mode(BOOT_CANARY);
231 /* wait for user action */ 237 /* wait for user action */
232 enum boot_mode mode = init_mode; 238 enum boot_mode mode = (init_mode == BOOT_CANARY) ? BOOT_ROCKBOX : init_mode;
233 int last_activity = current_tick; 239 int last_activity = current_tick;
234#if defined(HAS_BUTTON_HOLD) 240#if defined(HAS_BUTTON_HOLD)
235 bool hold_status = button_hold(); 241 bool hold_status = button_hold();
@@ -244,7 +250,7 @@ static enum boot_mode get_boot_mode(void)
244 return mode; 250 return mode;
245 } 251 }
246 /* inactivity detection */ 252 /* inactivity detection */
247 int timeout = last_activity + get_inactivity_tmo(); 253 int timeout = last_activity + get_inactivity_tmo(init_mode == mode);
248 if(TIME_AFTER(current_tick, timeout)) 254 if(TIME_AFTER(current_tick, timeout))
249 { 255 {
250 /* save last choice */ 256 /* save last choice */
@@ -314,10 +320,14 @@ static enum boot_mode get_boot_mode(void)
314 if(btn == BUTTON_SELECT) 320 if(btn == BUTTON_SELECT)
315 break; 321 break;
316 /* left/right/up/down: change mode */ 322 /* left/right/up/down: change mode */
317 if(btn == BUTTON_LEFT || btn == BUTTON_DOWN) 323 if(btn == BUTTON_LEFT || btn == BUTTON_DOWN) {
318 mode = (mode + BOOT_COUNT - 1) % BOOT_COUNT; 324 mode = (mode + BOOT_COUNT - 1) % BOOT_COUNT;
319 if(btn == BUTTON_RIGHT || btn == BUTTON_UP) 325 init_mode = BOOT_CANARY;
326 }
327 if(btn == BUTTON_RIGHT || btn == BUTTON_UP) {
320 mode = (mode + 1) % BOOT_COUNT; 328 mode = (mode + 1) % BOOT_COUNT;
329 init_mode = BOOT_CANARY;
330 }
321 } 331 }
322 332
323 /* save mode */ 333 /* save mode */
@@ -642,8 +652,8 @@ int main(int argc, char **argv)
642 system("/bin/chmod +x /tmp/" BOOTFILE); 652 system("/bin/chmod +x /tmp/" BOOTFILE);
643 execl("/tmp/" BOOTFILE, BOOTFILE, NULL); 653 execl("/tmp/" BOOTFILE, BOOTFILE, NULL);
644 printf("execvp failed: %s\n", strerror(errno)); 654 printf("execvp failed: %s\n", strerror(errno));
645 /* fallback to OF in case of failure */ 655 error_screen("Cannot boot Rockbox!");
646 error_screen("Cannot boot Rockbox"); 656 mode = BOOT_TOOLS;
647 sleep(2 * HZ); 657 sleep(2 * HZ);
648 } 658 }
649 else 659 else
diff --git a/firmware/SOURCES b/firmware/SOURCES
index e827f964f5..75ea8b136e 100644
--- a/firmware/SOURCES
+++ b/firmware/SOURCES
@@ -94,14 +94,12 @@ target/hosted/filesystem-unix.c
94target/hosted/filesystem-app.c 94target/hosted/filesystem-app.c
95#endif /* APPLICATION */ 95#endif /* APPLICATION */
96 96
97#if defined(SAMSUNG_YPR0) || defined(SAMSUNG_YPR1) 97#if (defined(SAMSUNG_YPR0) || defined(SAMSUNG_YPR1)) && !defined(SIMULATOR)
98target/hosted/kernel-unix.c 98target/hosted/kernel-unix.c
99target/hosted/filesystem-unix.c 99target/hosted/filesystem-unix.c
100#ifndef SIMULATOR
101target/hosted/lc-unix.c 100target/hosted/lc-unix.c
102drivers/lcd-memframe.c 101drivers/lcd-memframe.c
103target/hosted/samsungypr/lcd-ypr.c 102target/hosted/samsungypr/lcd-ypr.c
104#endif
105target/hosted/samsungypr/gpio-ypr.c 103target/hosted/samsungypr/gpio-ypr.c
106#if CONFIG_TUNER 104#if CONFIG_TUNER
107target/hosted/samsungypr/radio-ypr.c 105target/hosted/samsungypr/radio-ypr.c
@@ -1568,6 +1566,9 @@ target/arm/s5l8702/clocking-s5l8702.c
1568target/arm/s5l8702/ipod6g/lcd-6g.c 1566target/arm/s5l8702/ipod6g/lcd-6g.c
1569target/arm/s5l8702/ipod6g/lcd-asm-6g.S 1567target/arm/s5l8702/ipod6g/lcd-asm-6g.S
1570target/arm/s5l8702/ipod6g/piezo-6g.c 1568target/arm/s5l8702/ipod6g/piezo-6g.c
1569target/arm/s5l8702/spi-s5l8702.c
1570target/arm/s5l8702/crypto-s5l8702.c
1571target/arm/s5l8702/nor-s5l8702.c
1571#if 0 //TODO 1572#if 0 //TODO
1572target/arm/s5l8702/postmortemstub.S 1573target/arm/s5l8702/postmortemstub.S
1573#endif 1574#endif
@@ -1587,10 +1588,6 @@ target/arm/s5l8702/debug-s5l8702.c
1587target/arm/s5l8702/pcm-s5l8702.c 1588target/arm/s5l8702/pcm-s5l8702.c
1588target/arm/s5l8702/ipod6g/audio-6g.c 1589target/arm/s5l8702/ipod6g/audio-6g.c
1589target/arm/s5l8702/ipod6g/cscodec-6g.c 1590target/arm/s5l8702/ipod6g/cscodec-6g.c
1590#else
1591target/arm/s5l8702/spi-s5l8702.c
1592target/arm/s5l8702/crypto-s5l8702.c
1593target/arm/s5l8702/nor-s5l8702.c
1594#endif /* BOOTLOADER */ 1591#endif /* BOOTLOADER */
1595#endif /* IPOD_6G */ 1592#endif /* IPOD_6G */
1596 1593
diff --git a/firmware/drivers/ata.c b/firmware/drivers/ata.c
index b245cbc09e..d82fb173cc 100644
--- a/firmware/drivers/ata.c
+++ b/firmware/drivers/ata.c
@@ -962,8 +962,8 @@ static int perform_soft_reset(void)
962 if (identify()) 962 if (identify())
963 return -5; 963 return -5;
964 964
965 if (set_features()) 965 if ((ret = set_features()))
966 return -2; 966 return -60 + ret;
967 967
968 if (set_multiple_mode(multisectors)) 968 if (set_multiple_mode(multisectors))
969 return -3; 969 return -3;
@@ -1013,7 +1013,7 @@ static int ata_power_on(void)
1013 1013
1014 rc = set_features(); 1014 rc = set_features();
1015 if (rc) 1015 if (rc)
1016 return rc * 10 - 2; 1016 return -60 + rc;
1017 1017
1018 if (set_multiple_mode(multisectors)) 1018 if (set_multiple_mode(multisectors))
1019 return -3; 1019 return -3;
@@ -1284,7 +1284,7 @@ int STORAGE_INIT_ATTR ata_init(void)
1284 goto error; 1284 goto error;
1285 } 1285 }
1286 1286
1287 rc = set_features(); 1287 rc = set_features(); // rror codes are between -1 and -49
1288 if (rc) { 1288 if (rc) {
1289 rc = -60 + rc; 1289 rc = -60 + rc;
1290 goto error; 1290 goto error;
@@ -1321,7 +1321,7 @@ int STORAGE_INIT_ATTR ata_init(void)
1321 } 1321 }
1322 rc = set_multiple_mode(multisectors); 1322 rc = set_multiple_mode(multisectors);
1323 if (rc) 1323 if (rc)
1324 rc = -70 + rc; 1324 rc = -100 + rc;
1325 1325
1326error: 1326error:
1327 mutex_unlock(&ata_mtx); 1327 mutex_unlock(&ata_mtx);
diff --git a/firmware/target/arm/s5l8702/nor-target.h b/firmware/target/arm/s5l8702/nor-target.h
index 4ebe1d58d4..3d2790d123 100644
--- a/firmware/target/arm/s5l8702/nor-target.h
+++ b/firmware/target/arm/s5l8702/nor-target.h
@@ -87,6 +87,34 @@ int bootflash_compare(int port, int offset, void* addr, int size);
87void bootflash_erase_blocks(int port, int first, int n); 87void bootflash_erase_blocks(int port, int first, int n);
88void bootflash_close(int port); 88void bootflash_close(int port);
89 89
90/*
91 * SysCfg
92 */
93struct SysCfgHeader {
94 uint32_t magic; // always 'SCfg'
95 uint32_t size;
96 uint32_t unknown1; // 0x00000200 on iPod classic
97 uint32_t version; // maybe? 0x00010001 on iPod classic
98 uint32_t unknown2; // 0x00000000 on iPod classic
99 uint32_t num_entries;
100}; // 0x18
101
102struct SysCfgEntry {
103 uint32_t tag;
104 uint8_t data[0x10];
105};
106
107#define SYSCFG_MAGIC 0x53436667 // SCfg
108
109#define SYSCFG_TAG_SRNM 0x53724e6d // SrNm
110#define SYSCFG_TAG_FWID 0x46774964 // FwId
111#define SYSCFG_TAG_HWID 0x48774964 // HwId
112#define SYSCFG_TAG_HWVR 0x48775672 // HwVr
113#define SYSCFG_TAG_CODC 0x436f6463 // Codc
114#define SYSCFG_TAG_SWVR 0x53775672 // SwVr
115#define SYSCFG_TAG_MLBN 0x4d4c424e // MLBN
116#define SYSCFG_TAG_MODN 0x4d6f6423 // Mod#
117#define SYSCFG_TAG_REGN 0x5265676e // Regn
90 118
91/* 119/*
92 * IM3 120 * IM3
diff --git a/tools/configure b/tools/configure
index 43f02f5643..35f4b6d107 100755
--- a/tools/configure
+++ b/tools/configure
@@ -311,6 +311,8 @@ simcc () {
311 # add cross-compiler option(s) 311 # add cross-compiler option(s)
312 GCCOPTS="$GCCOPTS -Wno-format" 312 GCCOPTS="$GCCOPTS -Wno-format"
313 LDOPTS="$LDOPTS -mconsole -static" 313 LDOPTS="$LDOPTS -mconsole -static"
314 GLOBAL_LDOPTS=`echo $GLOBAL_LDOPTS | sed -e s/\-Wl,-z,defs//`
315 LDOPTS=`echo $LDOPTS | sed -e s/-ldl// -e s/-lrt// -e s/-lasound//`
314 output="$output.exe" 316 output="$output.exe"
315 winbuild="yes" 317 winbuild="yes"
316 318