diff options
-rw-r--r-- | apps/SOURCES | 1 | ||||
-rw-r--r-- | apps/debug_menu.c | 96 | ||||
-rw-r--r-- | apps/fileop.c | 606 | ||||
-rw-r--r-- | apps/fileop.h | 70 | ||||
-rw-r--r-- | apps/lang/deutsch.lang | 118 | ||||
-rw-r--r-- | apps/misc.c | 18 | ||||
-rw-r--r-- | apps/misc.h | 4 | ||||
-rw-r--r-- | apps/onplay.c | 784 | ||||
-rw-r--r-- | apps/plugins/fft/fft.c | 5 | ||||
-rw-r--r-- | bootloader/rocker_linux.c | 60 | ||||
-rw-r--r-- | firmware/SOURCES | 11 | ||||
-rw-r--r-- | firmware/drivers/ata.c | 10 | ||||
-rw-r--r-- | firmware/target/arm/s5l8702/nor-target.h | 28 | ||||
-rwxr-xr-x | tools/configure | 2 |
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 | |||
10 | core_keymap.c | 10 | core_keymap.c |
11 | debug_menu.c | 11 | debug_menu.c |
12 | filetypes.c | 12 | filetypes.c |
13 | fileop.c | ||
13 | language.c | 14 | language.c |
14 | main.c | 15 | main.c |
15 | menu.c | 16 | menu.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 | ||
137 | static const char* threads_getname(int selected_item, void *data, | 141 | static 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 | |||
2591 | static 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 *********/ |
2585 | static const struct { | 2678 | static 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 | ||
2696 | static int menu_action_callback(int btn, struct gui_synclist *lists) | 2792 | static 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 */ | ||
44 | struct 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 | |||
53 | static 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 | |||
78 | static 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 */ | ||
98 | static 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(¶m->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 | |||
198 | static 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 | |||
205 | static 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 | |||
213 | int 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 */ | ||
234 | static 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 | |||
258 | int delete_fileobject(const char *selected_file) | ||
259 | { | ||
260 | struct file_op_params param; | ||
261 | if (init_file_op(¶m, selected_file, NULL)->is_dir == true) | ||
262 | { | ||
263 | int rc = check_count_fileobjects(¶m); | ||
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(¶m); | ||
270 | } | ||
271 | |||
272 | int 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 | |||
308 | static 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 */ | ||
347 | static 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 */ | ||
451 | static 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 | |||
541 | int 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 */ | ||
27 | enum 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 | |||
43 | enum 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 | |||
51 | enum 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 | |||
61 | int create_dir(void); | ||
62 | |||
63 | int rename_file(const char *selected_file); | ||
64 | |||
65 | int delete_fileobject(const char *selected_file); | ||
66 | |||
67 | int 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 | |||
2003 | void 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. */ |
278 | long from_normalized_volume(long norm, long min_vol, long max_vol, long max_norm); | 278 | long 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 */ | ||
282 | void 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 */ | ||
88 | struct 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 | |||
94 | enum 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 */ | ||
103 | enum 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 | |||
112 | static struct clipboard | 87 | static 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 */ |
197 | static bool shuffle_playlist(void) | 170 | static 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 | ||
247 | struct add_to_pl_param | 223 | struct 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 | ||
254 | static struct add_to_pl_param addtopl_insert = {PLAYLIST_INSERT, 0, 0}; | 229 | static struct add_to_pl_param addtopl_insert = {PLAYLIST_INSERT, PL_NONE}; |
255 | static struct add_to_pl_param addtopl_insert_first = {PLAYLIST_INSERT_FIRST, 0, 0}; | 230 | static struct add_to_pl_param addtopl_insert_first = {PLAYLIST_INSERT_FIRST, PL_NONE}; |
256 | static struct add_to_pl_param addtopl_insert_last = {PLAYLIST_INSERT_LAST, 0, 0}; | 231 | static struct add_to_pl_param addtopl_insert_last = {PLAYLIST_INSERT_LAST, PL_NONE}; |
257 | static struct add_to_pl_param addtopl_insert_shuf = {PLAYLIST_INSERT_SHUFFLED, 0, 0}; | 232 | static struct add_to_pl_param addtopl_insert_shuf = {PLAYLIST_INSERT_SHUFFLED, PL_NONE}; |
258 | static struct add_to_pl_param addtopl_insert_last_shuf = {PLAYLIST_INSERT_LAST_SHUFFLED, 0, 0}; | 233 | static struct add_to_pl_param addtopl_insert_last_shuf = {PLAYLIST_INSERT_LAST_SHUFFLED, PL_NONE}; |
259 | 234 | ||
260 | static struct add_to_pl_param addtopl_queue = {PLAYLIST_INSERT, 1, 0}; | 235 | static struct add_to_pl_param addtopl_queue = {PLAYLIST_INSERT, PL_QUEUE}; |
261 | static struct add_to_pl_param addtopl_queue_first = {PLAYLIST_INSERT_FIRST, 1, 0}; | 236 | static struct add_to_pl_param addtopl_queue_first = {PLAYLIST_INSERT_FIRST, PL_QUEUE}; |
262 | static struct add_to_pl_param addtopl_queue_last = {PLAYLIST_INSERT_LAST, 1, 0}; | 237 | static struct add_to_pl_param addtopl_queue_last = {PLAYLIST_INSERT_LAST, PL_QUEUE}; |
263 | static struct add_to_pl_param addtopl_queue_shuf = {PLAYLIST_INSERT_SHUFFLED, 1, 0}; | 238 | static struct add_to_pl_param addtopl_queue_shuf = {PLAYLIST_INSERT_SHUFFLED, PL_QUEUE}; |
264 | static struct add_to_pl_param addtopl_queue_last_shuf = {PLAYLIST_INSERT_LAST_SHUFFLED, 1, 0}; | 239 | static struct add_to_pl_param addtopl_queue_last_shuf = {PLAYLIST_INSERT_LAST_SHUFFLED, PL_QUEUE}; |
265 | 240 | ||
266 | static struct add_to_pl_param addtopl_replace = {PLAYLIST_INSERT, 0, 1}; | 241 | static struct add_to_pl_param addtopl_replace = {PLAYLIST_INSERT, PL_REPLACE}; |
267 | static struct add_to_pl_param addtopl_replace_shuffled = {PLAYLIST_INSERT_LAST_SHUFFLED, 0, 1}; | 242 | static struct add_to_pl_param addtopl_replace_shuffled = {PLAYLIST_INSERT_LAST_SHUFFLED, PL_REPLACE}; |
243 | |||
244 | static 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 */ |
270 | static int add_to_playlist(void* arg) | 285 | static 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 | |||
450 | static int treeplaylist_callback(int action, | 429 | static 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 | ||
586 | static 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 | |||
602 | static 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 | |||
619 | static 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 */ | ||
628 | static 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 | |||
634 | static 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 | |||
642 | static void splash_cancelled(void) | 567 | static 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 | ||
648 | static void splash_failed(int lang_what) | 573 | static 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 */ | ||
656 | static 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 */ | ||
724 | static 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 | |||
764 | static 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 | |||
808 | static 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 */ | ||
836 | static 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 */ | ||
954 | static 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 | ||
1064 | static bool clipboard_cut(void) | 580 | static 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 | ||
713 | static 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 | |||
728 | static 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 | |||
739 | static 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 | |||
748 | static 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 */ |
1253 | static int clipboard_callback(int action, | 758 | static 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 | ||
1257 | MENUITEM_FUNCTION(rename_file_item, 0, ID2P(LANG_RENAME), | 762 | MENUITEM_FUNCTION(rename_file_item, 0, ID2P(LANG_RENAME), |
1258 | rename_file, clipboard_callback, Icon_NOICON); | 763 | clipboard_rename_selected_file, clipboard_callback, Icon_NOICON); |
1259 | MENUITEM_FUNCTION(clipboard_cut_item, 0, ID2P(LANG_CUT), | 764 | MENUITEM_FUNCTION(clipboard_cut_item, 0, ID2P(LANG_CUT), |
1260 | clipboard_cut, clipboard_callback, Icon_NOICON); | 765 | clipboard_cut, clipboard_callback, Icon_NOICON); |
1261 | MENUITEM_FUNCTION(clipboard_copy_item, 0, ID2P(LANG_COPY), | 766 | MENUITEM_FUNCTION(clipboard_copy_item, 0, ID2P(LANG_COPY), |
@@ -1263,11 +768,11 @@ MENUITEM_FUNCTION(clipboard_copy_item, 0, ID2P(LANG_COPY), | |||
1263 | MENUITEM_FUNCTION(clipboard_paste_item, 0, ID2P(LANG_PASTE), | 768 | MENUITEM_FUNCTION(clipboard_paste_item, 0, ID2P(LANG_PASTE), |
1264 | clipboard_paste, clipboard_callback, Icon_NOICON); | 769 | clipboard_paste, clipboard_callback, Icon_NOICON); |
1265 | MENUITEM_FUNCTION(delete_file_item, 0, ID2P(LANG_DELETE), | 770 | MENUITEM_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); |
1267 | MENUITEM_FUNCTION(delete_dir_item, 0, ID2P(LANG_DELETE_DIR), | 772 | MENUITEM_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); |
1269 | MENUITEM_FUNCTION(create_dir_item, 0, ID2P(LANG_CREATE_DIR), | 774 | MENUITEM_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 */ |
1273 | static bool list_viewers(void) | 778 | static 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 | ||
845 | static 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 |
1341 | static bool set_backdrop(void) | 852 | static 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 |
1354 | static bool set_recdir(void) | 865 | static 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 | } |
1361 | MENUITEM_FUNCTION(set_recdir_item, 0, ID2P(LANG_RECORDING_DIR), | 871 | MENUITEM_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 |
1364 | static bool set_startdir(void) | 874 | static 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 | } |
1372 | MENUITEM_FUNCTION(set_startdir_item, 0, ID2P(LANG_START_DIR), | 880 | MENUITEM_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 |
1385 | static bool set_databasedir(void) | 893 | static 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 | } |
1399 | MENUITEM_FUNCTION(set_databasedir_item, 0, ID2P(LANG_DATABASE_DIR), | 905 | MENUITEM_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 | ||
1595 | static bool hotkey_open_with(void) | 1101 | static 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 | ||
38 | extern GREY_INFO_STRUCT | ||
39 | #else | ||
36 | GREY_INFO_STRUCT | 40 | GREY_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 | ||
150 | static void display_text_center(int y, const char *text) | 151 | static 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 */ |
168 | static int get_inactivity_tmo(void) | 169 | static 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' | 193 | static int mounted = 0; |
192 | * (since the mostly suspends instead of powering down) */ | ||
193 | static 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 | ||
204 | static void mount_storage(int enable) | 195 | static 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' */ | ||
210 | static 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 | ||
217 | static void save_boot_mode(enum boot_mode mode) | 222 | static 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) | |||
227 | static enum boot_mode get_boot_mode(void) | 233 | static 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 | |||
94 | target/hosted/filesystem-app.c | 94 | target/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) |
98 | target/hosted/kernel-unix.c | 98 | target/hosted/kernel-unix.c |
99 | target/hosted/filesystem-unix.c | 99 | target/hosted/filesystem-unix.c |
100 | #ifndef SIMULATOR | ||
101 | target/hosted/lc-unix.c | 100 | target/hosted/lc-unix.c |
102 | drivers/lcd-memframe.c | 101 | drivers/lcd-memframe.c |
103 | target/hosted/samsungypr/lcd-ypr.c | 102 | target/hosted/samsungypr/lcd-ypr.c |
104 | #endif | ||
105 | target/hosted/samsungypr/gpio-ypr.c | 103 | target/hosted/samsungypr/gpio-ypr.c |
106 | #if CONFIG_TUNER | 104 | #if CONFIG_TUNER |
107 | target/hosted/samsungypr/radio-ypr.c | 105 | target/hosted/samsungypr/radio-ypr.c |
@@ -1568,6 +1566,9 @@ target/arm/s5l8702/clocking-s5l8702.c | |||
1568 | target/arm/s5l8702/ipod6g/lcd-6g.c | 1566 | target/arm/s5l8702/ipod6g/lcd-6g.c |
1569 | target/arm/s5l8702/ipod6g/lcd-asm-6g.S | 1567 | target/arm/s5l8702/ipod6g/lcd-asm-6g.S |
1570 | target/arm/s5l8702/ipod6g/piezo-6g.c | 1568 | target/arm/s5l8702/ipod6g/piezo-6g.c |
1569 | target/arm/s5l8702/spi-s5l8702.c | ||
1570 | target/arm/s5l8702/crypto-s5l8702.c | ||
1571 | target/arm/s5l8702/nor-s5l8702.c | ||
1571 | #if 0 //TODO | 1572 | #if 0 //TODO |
1572 | target/arm/s5l8702/postmortemstub.S | 1573 | target/arm/s5l8702/postmortemstub.S |
1573 | #endif | 1574 | #endif |
@@ -1587,10 +1588,6 @@ target/arm/s5l8702/debug-s5l8702.c | |||
1587 | target/arm/s5l8702/pcm-s5l8702.c | 1588 | target/arm/s5l8702/pcm-s5l8702.c |
1588 | target/arm/s5l8702/ipod6g/audio-6g.c | 1589 | target/arm/s5l8702/ipod6g/audio-6g.c |
1589 | target/arm/s5l8702/ipod6g/cscodec-6g.c | 1590 | target/arm/s5l8702/ipod6g/cscodec-6g.c |
1590 | #else | ||
1591 | target/arm/s5l8702/spi-s5l8702.c | ||
1592 | target/arm/s5l8702/crypto-s5l8702.c | ||
1593 | target/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 | ||
1326 | error: | 1326 | error: |
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); | |||
87 | void bootflash_erase_blocks(int port, int first, int n); | 87 | void bootflash_erase_blocks(int port, int first, int n); |
88 | void bootflash_close(int port); | 88 | void bootflash_close(int port); |
89 | 89 | ||
90 | /* | ||
91 | * SysCfg | ||
92 | */ | ||
93 | struct 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 | |||
102 | struct 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 | ||