diff options
Diffstat (limited to 'apps/filetree.c')
-rw-r--r-- | apps/filetree.c | 509 |
1 files changed, 509 insertions, 0 deletions
diff --git a/apps/filetree.c b/apps/filetree.c new file mode 100644 index 0000000000..98d59b9e9a --- /dev/null +++ b/apps/filetree.c | |||
@@ -0,0 +1,509 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2002 by Björn Stenberg | ||
11 | * | ||
12 | * All files in this archive are subject to the GNU General Public License. | ||
13 | * See the file COPYING in the source tree root for full license agreement. | ||
14 | * | ||
15 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
16 | * KIND, either express or implied. | ||
17 | * | ||
18 | ****************************************************************************/ | ||
19 | #include <stdlib.h> | ||
20 | #include <file.h> | ||
21 | #include <dir.h> | ||
22 | #include <string.h> | ||
23 | #include <kernel.h> | ||
24 | #include <lcd.h> | ||
25 | #include <debug.h> | ||
26 | #include <font.h> | ||
27 | #include "bookmark.h" | ||
28 | #include "tree.h" | ||
29 | #include "settings.h" | ||
30 | #include "filetypes.h" | ||
31 | #include "talk.h" | ||
32 | #include "playlist.h" | ||
33 | #include "wps-display.h" | ||
34 | #include "lang.h" | ||
35 | #include "language.h" | ||
36 | #include "screens.h" | ||
37 | #include "plugin.h" | ||
38 | #include "rolo.h" | ||
39 | |||
40 | static int boot_size = 0; | ||
41 | static int boot_cluster; | ||
42 | extern bool boot_changed; | ||
43 | |||
44 | int ft_build_playlist(struct tree_context* c, int start_index) | ||
45 | { | ||
46 | int i; | ||
47 | int start=start_index; | ||
48 | |||
49 | struct entry *dircache = c->dircache; | ||
50 | |||
51 | for(i = 0;i < c->filesindir;i++) | ||
52 | { | ||
53 | if((dircache[i].attr & TREE_ATTR_MASK) == TREE_ATTR_MPA) | ||
54 | { | ||
55 | DEBUGF("Adding %s\n", dircache[i].name); | ||
56 | if (playlist_add(dircache[i].name) < 0) | ||
57 | break; | ||
58 | } | ||
59 | else | ||
60 | { | ||
61 | /* Adjust the start index when se skip non-MP3 entries */ | ||
62 | if(i < start) | ||
63 | start_index--; | ||
64 | } | ||
65 | } | ||
66 | |||
67 | return start_index; | ||
68 | } | ||
69 | |||
70 | /* walk a directory and check all dircache entries if a .talk file exists */ | ||
71 | static void check_file_thumbnails(struct tree_context* c) | ||
72 | { | ||
73 | int i; | ||
74 | struct dirent *entry; | ||
75 | struct entry* dircache = c->dircache; | ||
76 | DIR *dir; | ||
77 | |||
78 | dir = opendir(c->currdir); | ||
79 | if(!dir) | ||
80 | return; | ||
81 | |||
82 | for (i=0; i < c->filesindir; i++) /* mark all files as non talking, except the .talk ones */ | ||
83 | { | ||
84 | if (dircache[i].attr & ATTR_DIRECTORY) | ||
85 | continue; /* we're not touching directories */ | ||
86 | |||
87 | if (strcasecmp(file_thumbnail_ext, | ||
88 | &dircache[i].name[strlen(dircache[i].name) | ||
89 | - strlen(file_thumbnail_ext)])) | ||
90 | { /* no .talk file */ | ||
91 | dircache[i].attr &= ~TREE_ATTR_THUMBNAIL; /* clear */ | ||
92 | } | ||
93 | else | ||
94 | { /* .talk file, we later let them speak themselves */ | ||
95 | dircache[i].attr |= TREE_ATTR_THUMBNAIL; /* set */ | ||
96 | } | ||
97 | } | ||
98 | |||
99 | while((entry = readdir(dir)) != 0) /* walk directory */ | ||
100 | { | ||
101 | int ext_pos; | ||
102 | |||
103 | ext_pos = strlen(entry->d_name) - strlen(file_thumbnail_ext); | ||
104 | if (ext_pos <= 0 /* too short to carry ".talk" */ | ||
105 | || (entry->attribute & ATTR_DIRECTORY) /* no file */ | ||
106 | || strcasecmp(&entry->d_name[ext_pos], file_thumbnail_ext)) | ||
107 | { /* or doesn't end with ".talk", no candidate */ | ||
108 | continue; | ||
109 | } | ||
110 | |||
111 | /* terminate the (disposable) name in dir buffer, | ||
112 | this truncates off the ".talk" without needing an extra buffer */ | ||
113 | entry->d_name[ext_pos] = '\0'; | ||
114 | |||
115 | /* search corresponding file in dir cache */ | ||
116 | for (i=0; i < c->filesindir; i++) | ||
117 | { | ||
118 | if (!strcasecmp(dircache[i].name, entry->d_name)) | ||
119 | { /* match */ | ||
120 | dircache[i].attr |= TREE_ATTR_THUMBNAIL; /* set the flag */ | ||
121 | break; /* exit search loop, because we found it */ | ||
122 | } | ||
123 | } | ||
124 | } | ||
125 | closedir(dir); | ||
126 | } | ||
127 | |||
128 | /* support function for qsort() */ | ||
129 | static int compare(const void* p1, const void* p2) | ||
130 | { | ||
131 | struct entry* e1 = (struct entry*)p1; | ||
132 | struct entry* e2 = (struct entry*)p2; | ||
133 | int criteria; | ||
134 | |||
135 | if (e1->attr & ATTR_DIRECTORY && e2->attr & ATTR_DIRECTORY) | ||
136 | { /* two directories */ | ||
137 | criteria = global_settings.sort_dir; | ||
138 | } | ||
139 | else if (!(e1->attr & ATTR_DIRECTORY) && !(e2->attr & ATTR_DIRECTORY)) | ||
140 | { /* two files */ | ||
141 | criteria = global_settings.sort_file; | ||
142 | } | ||
143 | else /* dir and file, dir goes first */ | ||
144 | return ( e2->attr & ATTR_DIRECTORY ) - ( e1->attr & ATTR_DIRECTORY ); | ||
145 | |||
146 | switch(criteria) | ||
147 | { | ||
148 | case 3: /* sort type */ | ||
149 | { | ||
150 | int t1 = e1->attr & TREE_ATTR_MASK; | ||
151 | int t2 = e2->attr & TREE_ATTR_MASK; | ||
152 | |||
153 | if (!t1) /* unknown type */ | ||
154 | t1 = 0x7FFFFFFF; /* gets a high number, to sort after known */ | ||
155 | if (!t2) /* unknown type */ | ||
156 | t2 = 0x7FFFFFFF; /* gets a high number, to sort after known */ | ||
157 | |||
158 | if (t1 - t2) /* if different */ | ||
159 | return t1 - t2; | ||
160 | /* else fall through to alphabetical sorting */ | ||
161 | } | ||
162 | case 0: /* sort alphabetically */ | ||
163 | if (global_settings.sort_case) | ||
164 | return strncmp(e1->name, e2->name, MAX_PATH); | ||
165 | else | ||
166 | return strncasecmp(e1->name, e2->name, MAX_PATH); | ||
167 | |||
168 | case 1: /* sort date */ | ||
169 | return e1->time_write - e2->time_write; | ||
170 | |||
171 | case 2: /* sort date, newest first */ | ||
172 | return e2->time_write - e1->time_write; | ||
173 | } | ||
174 | return 0; /* never reached */ | ||
175 | } | ||
176 | |||
177 | /* load and sort directory into dircache. returns NULL on failure. */ | ||
178 | int ft_load(struct tree_context* c, bool *buffer_full) | ||
179 | { | ||
180 | extern char lastdir[]; /* from tree.c */ | ||
181 | int i; | ||
182 | DIR *dir = opendir(c->currdir); | ||
183 | if(!dir) | ||
184 | return -1; /* not a directory */ | ||
185 | |||
186 | int name_buffer_used = 0; | ||
187 | c->dirsindir = 0; | ||
188 | if (buffer_full) | ||
189 | *buffer_full = false; | ||
190 | |||
191 | for ( i=0; i < global_settings.max_files_in_dir; i++ ) { | ||
192 | int len; | ||
193 | struct dirent *entry = readdir(dir); | ||
194 | struct entry* dptr = (struct entry*)(c->dircache + i * sizeof(struct entry)); | ||
195 | if (!entry) | ||
196 | break; | ||
197 | |||
198 | len = strlen(entry->d_name); | ||
199 | |||
200 | /* skip directories . and .. */ | ||
201 | if ((entry->attribute & ATTR_DIRECTORY) && | ||
202 | (((len == 1) && | ||
203 | (!strncmp(entry->d_name, ".", 1))) || | ||
204 | ((len == 2) && | ||
205 | (!strncmp(entry->d_name, "..", 2))))) { | ||
206 | i--; | ||
207 | continue; | ||
208 | } | ||
209 | |||
210 | /* Skip FAT volume ID */ | ||
211 | if (entry->attribute & ATTR_VOLUME_ID) { | ||
212 | i--; | ||
213 | continue; | ||
214 | } | ||
215 | |||
216 | /* filter out dotfiles and hidden files */ | ||
217 | if (*c->dirfilter != SHOW_ALL && | ||
218 | ((entry->d_name[0]=='.') || | ||
219 | (entry->attribute & ATTR_HIDDEN))) { | ||
220 | i--; | ||
221 | continue; | ||
222 | } | ||
223 | |||
224 | dptr->attr = entry->attribute; | ||
225 | |||
226 | /* check for known file types */ | ||
227 | if ( !(dptr->attr & ATTR_DIRECTORY) && (len > 4) ) | ||
228 | dptr->attr |= filetype_get_attr(entry->d_name); | ||
229 | |||
230 | /* memorize/compare details about the boot file */ | ||
231 | if ((c->currdir[1] == 0) && !strcasecmp(entry->d_name, BOOTFILE)) { | ||
232 | if (boot_size) { | ||
233 | if ((entry->size != boot_size) || | ||
234 | (entry->startcluster != boot_cluster)) | ||
235 | boot_changed = true; | ||
236 | } | ||
237 | boot_size = entry->size; | ||
238 | boot_cluster = entry->startcluster; | ||
239 | } | ||
240 | |||
241 | /* filter out non-visible files */ | ||
242 | if (!(dptr->attr & ATTR_DIRECTORY) && ( | ||
243 | (*c->dirfilter == SHOW_PLAYLIST && | ||
244 | (dptr->attr & TREE_ATTR_MASK) != TREE_ATTR_M3U) || | ||
245 | ((*c->dirfilter == SHOW_MUSIC && | ||
246 | (dptr->attr & TREE_ATTR_MASK) != TREE_ATTR_MPA) && | ||
247 | (dptr->attr & TREE_ATTR_MASK) != TREE_ATTR_M3U) || | ||
248 | (*c->dirfilter == SHOW_SUPPORTED && !filetype_supported(dptr->attr)) || | ||
249 | (*c->dirfilter == SHOW_WPS && (dptr->attr & TREE_ATTR_MASK) != TREE_ATTR_WPS) || | ||
250 | (*c->dirfilter == SHOW_CFG && (dptr->attr & TREE_ATTR_MASK) != TREE_ATTR_CFG) || | ||
251 | (*c->dirfilter == SHOW_LNG && (dptr->attr & TREE_ATTR_MASK) != TREE_ATTR_LNG) || | ||
252 | (*c->dirfilter == SHOW_MOD && (dptr->attr & TREE_ATTR_MASK) != TREE_ATTR_MOD) || | ||
253 | (*c->dirfilter == SHOW_FONT && (dptr->attr & TREE_ATTR_MASK) != TREE_ATTR_FONT) || | ||
254 | (*c->dirfilter == SHOW_PLUGINS && (dptr->attr & TREE_ATTR_MASK) != TREE_ATTR_ROCK))) | ||
255 | { | ||
256 | i--; | ||
257 | continue; | ||
258 | } | ||
259 | |||
260 | if (len > c->name_buffer_size - name_buffer_used - 1) { | ||
261 | /* Tell the world that we ran out of buffer space */ | ||
262 | if (buffer_full) | ||
263 | *buffer_full = true; | ||
264 | break; | ||
265 | } | ||
266 | dptr->name = &c->name_buffer[name_buffer_used]; | ||
267 | dptr->time_write = entry->wrtdate<<16 | entry->wrttime; /* in one # */ | ||
268 | strcpy(dptr->name,entry->d_name); | ||
269 | name_buffer_used += len + 1; | ||
270 | |||
271 | if (dptr->attr & ATTR_DIRECTORY) /* count the remaining dirs */ | ||
272 | c->dirsindir++; | ||
273 | } | ||
274 | c->filesindir = i; | ||
275 | closedir(dir); | ||
276 | |||
277 | strcpy(lastdir, c->currdir); | ||
278 | |||
279 | qsort(c->dircache,i,sizeof(struct entry),compare); | ||
280 | |||
281 | /* If thumbnail talking is enabled, make an extra run to mark files with | ||
282 | associated thumbnails, so we don't do unsuccessful spinups later. */ | ||
283 | if (global_settings.talk_file == 3) | ||
284 | check_file_thumbnails(c); /* map .talk to ours */ | ||
285 | |||
286 | return 0; | ||
287 | } | ||
288 | |||
289 | int ft_enter(struct tree_context* c) | ||
290 | { | ||
291 | int rc = 0; | ||
292 | char buf[MAX_PATH]; | ||
293 | struct entry *dircache = c->dircache; | ||
294 | struct entry* file = &dircache[c->dircursor + c->dirstart]; | ||
295 | bool reload_root = false; | ||
296 | bool reload_dir = false; | ||
297 | bool start_wps = false; | ||
298 | bool exit_func = false; | ||
299 | |||
300 | if (c->currdir[1]) | ||
301 | snprintf(buf,sizeof(buf),"%s/%s",c->currdir, file->name); | ||
302 | else | ||
303 | snprintf(buf,sizeof(buf),"/%s",file->name); | ||
304 | |||
305 | if (file->attr & ATTR_DIRECTORY) { | ||
306 | memcpy(c->currdir, buf, sizeof(c->currdir)); | ||
307 | if ( c->dirlevel < MAX_DIR_LEVELS ) { | ||
308 | c->dirpos[c->dirlevel] = c->dirstart; | ||
309 | c->cursorpos[c->dirlevel] = c->dircursor; | ||
310 | } | ||
311 | c->dirlevel++; | ||
312 | c->dircursor=0; | ||
313 | c->dirstart=0; | ||
314 | } | ||
315 | else { | ||
316 | int seed = current_tick; | ||
317 | bool play = false; | ||
318 | int start_index=0; | ||
319 | |||
320 | lcd_stop_scroll(); | ||
321 | switch ( file->attr & TREE_ATTR_MASK ) { | ||
322 | case TREE_ATTR_M3U: | ||
323 | if (bookmark_autoload(buf)) | ||
324 | break; | ||
325 | |||
326 | if (playlist_create(c->currdir, file->name) != -1) | ||
327 | { | ||
328 | if (global_settings.playlist_shuffle) | ||
329 | playlist_shuffle(seed, -1); | ||
330 | start_index = 0; | ||
331 | playlist_start(start_index,0); | ||
332 | play = true; | ||
333 | } | ||
334 | break; | ||
335 | |||
336 | case TREE_ATTR_MPA: | ||
337 | if (bookmark_autoload(c->currdir)) | ||
338 | break; | ||
339 | |||
340 | if (playlist_create(c->currdir, NULL) != -1) | ||
341 | { | ||
342 | start_index = | ||
343 | ft_build_playlist(c, c->dircursor + c->dirstart); | ||
344 | if (global_settings.playlist_shuffle) | ||
345 | { | ||
346 | start_index = playlist_shuffle(seed, start_index); | ||
347 | |||
348 | /* when shuffling dir.: play all files | ||
349 | even if the file selected by user is | ||
350 | not the first one */ | ||
351 | if (!global_settings.play_selected) | ||
352 | start_index = 0; | ||
353 | } | ||
354 | |||
355 | playlist_start(start_index, 0); | ||
356 | play = true; | ||
357 | } | ||
358 | break; | ||
359 | |||
360 | /* wps config file */ | ||
361 | case TREE_ATTR_WPS: | ||
362 | wps_load(buf,true); | ||
363 | set_file(buf, global_settings.wps_file, | ||
364 | MAX_FILENAME); | ||
365 | break; | ||
366 | |||
367 | case TREE_ATTR_CFG: | ||
368 | if (!settings_load_config(buf)) | ||
369 | break; | ||
370 | lcd_clear_display(); | ||
371 | lcd_puts(0,0,str(LANG_SETTINGS_LOADED1)); | ||
372 | lcd_puts(0,1,str(LANG_SETTINGS_LOADED2)); | ||
373 | #ifdef HAVE_LCD_BITMAP | ||
374 | lcd_update(); | ||
375 | #endif | ||
376 | sleep(HZ/2); | ||
377 | break; | ||
378 | |||
379 | case TREE_ATTR_BMARK: | ||
380 | bookmark_load(buf, false); | ||
381 | reload_dir = true; | ||
382 | break; | ||
383 | |||
384 | case TREE_ATTR_LNG: | ||
385 | if(!lang_load(buf)) { | ||
386 | extern bool language_changed; /* from settings_menu.c */ | ||
387 | |||
388 | set_file(buf, global_settings.lang_file, | ||
389 | MAX_FILENAME); | ||
390 | talk_init(); /* use voice of same language */ | ||
391 | splash(HZ, true, str(LANG_LANGUAGE_LOADED)); | ||
392 | language_changed = true; | ||
393 | } | ||
394 | break; | ||
395 | |||
396 | #ifdef HAVE_LCD_BITMAP | ||
397 | case TREE_ATTR_FONT: | ||
398 | font_load(buf); | ||
399 | set_file(buf, global_settings.font_file, MAX_FILENAME); | ||
400 | break; | ||
401 | #endif | ||
402 | |||
403 | #ifndef SIMULATOR | ||
404 | /* firmware file */ | ||
405 | case TREE_ATTR_MOD: | ||
406 | rolo_load(buf); | ||
407 | break; | ||
408 | #endif | ||
409 | |||
410 | /* plugin file */ | ||
411 | case TREE_ATTR_ROCK: | ||
412 | if (plugin_load(buf,NULL) == PLUGIN_USB_CONNECTED) | ||
413 | { | ||
414 | if(*c->dirfilter > NUM_FILTER_MODES) | ||
415 | /* leave sub-browsers after usb, doing | ||
416 | otherwise might be confusing to the user */ | ||
417 | exit_func = true; | ||
418 | else | ||
419 | reload_root = true; | ||
420 | } | ||
421 | break; | ||
422 | |||
423 | default: | ||
424 | { | ||
425 | char* plugin = filetype_get_plugin(file); | ||
426 | if (plugin) | ||
427 | { | ||
428 | if (plugin_load(plugin,buf) == PLUGIN_USB_CONNECTED) | ||
429 | reload_root = true; | ||
430 | } | ||
431 | break; | ||
432 | } | ||
433 | } | ||
434 | |||
435 | if ( play ) { | ||
436 | if ( global_settings.resume ) { | ||
437 | /* the resume_index must always be the index in the | ||
438 | shuffled list in case shuffle is enabled */ | ||
439 | global_settings.resume_index = start_index; | ||
440 | global_settings.resume_offset = 0; | ||
441 | settings_save(); | ||
442 | } | ||
443 | |||
444 | start_wps = true; | ||
445 | } | ||
446 | else { | ||
447 | if (*c->dirfilter > NUM_FILTER_MODES && | ||
448 | *c->dirfilter != SHOW_FONT && | ||
449 | *c->dirfilter != SHOW_PLUGINS) | ||
450 | { | ||
451 | exit_func = true; | ||
452 | } | ||
453 | } | ||
454 | } | ||
455 | |||
456 | if (reload_dir) | ||
457 | rc = 1; | ||
458 | if (reload_root) | ||
459 | rc = 2; | ||
460 | if (start_wps) | ||
461 | rc = 3; | ||
462 | if (exit_func) | ||
463 | rc = 4; | ||
464 | |||
465 | return rc; | ||
466 | } | ||
467 | |||
468 | int ft_exit(struct tree_context* c) | ||
469 | { | ||
470 | extern char lastfile[]; /* from tree.c */ | ||
471 | char buf[MAX_PATH]; | ||
472 | int rc = 0; | ||
473 | bool exit_func = false; | ||
474 | |||
475 | int i = strlen(c->currdir); | ||
476 | if (i>1) { | ||
477 | while (c->currdir[i-1]!='/') | ||
478 | i--; | ||
479 | strcpy(buf,&c->currdir[i]); | ||
480 | if (i==1) | ||
481 | c->currdir[i]=0; | ||
482 | else | ||
483 | c->currdir[i-1]=0; | ||
484 | |||
485 | if (*c->dirfilter > NUM_FILTER_MODES && c->dirlevel < 1) | ||
486 | exit_func = true; | ||
487 | |||
488 | c->dirlevel--; | ||
489 | if ( c->dirlevel < MAX_DIR_LEVELS ) { | ||
490 | c->dirstart = c->dirpos[c->dirlevel]; | ||
491 | c->dircursor = c->cursorpos[c->dirlevel]; | ||
492 | } | ||
493 | else | ||
494 | c->dirstart = c->dircursor = 0; | ||
495 | |||
496 | if (c->dirstart == -1) | ||
497 | strcpy(lastfile, buf); | ||
498 | } | ||
499 | else | ||
500 | { | ||
501 | if (*c->dirfilter > NUM_FILTER_MODES && c->dirlevel < 1) | ||
502 | exit_func = true; | ||
503 | } | ||
504 | |||
505 | if (exit_func) | ||
506 | rc = 4; | ||
507 | |||
508 | return rc; | ||
509 | } | ||