1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
9 * Copyright (C) 2012 Jonathan Gordon
10 * Copyright (C) 2012 Thomas Martitz
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.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
27 #include "core_alloc.h"
28 #include "filetypes.h"
36 * Order for changing child states:
37 * 1) expand folder (skip to 3 if empty, skip to 4 if cannot be opened)
38 * 2) collapse and select
39 * 3) unselect (skip to 1)
52 struct folder
*folder
;
53 enum child_state state
;
58 struct child
*children
;
62 struct folder
* previous
;
65 static char *buffer_front
, *buffer_end
;
66 static char* folder_alloc(size_t size
)
70 size
= (size
+ 3) & ~3;
71 if (buffer_front
+ size
> buffer_end
)
75 retval
= buffer_front
;
80 static char* folder_alloc_from_end(size_t size
)
82 if (buffer_end
- size
< buffer_front
)
90 static void get_full_path_r(struct folder
*start
, char* dst
)
93 get_full_path_r(start
->previous
, dst
);
95 if (start
->name
&& start
->name
[0] && strcmp(start
->name
, "/"))
97 strlcat(dst
, "/", MAX_PATH
);
98 strlcat(dst
, start
->name
, MAX_PATH
);
102 static char* get_full_path(struct folder
*start
)
104 static char buffer
[MAX_PATH
];
106 if (strcmp(start
->name
, "/"))
109 get_full_path_r(start
, buffer
);
111 else /* get_full_path_r() does the wrong thing for / */
117 /* support function for qsort() */
118 static int compare(const void* p1
, const void* p2
)
120 struct child
*left
= (struct child
*)p1
;
121 struct child
*right
= (struct child
*)p2
;
122 return strcasecmp(left
->name
, right
->name
);
125 static struct folder
* load_folder(struct folder
* parent
, char *folder
)
128 char* path
= get_full_path(parent
);
129 char fullpath
[MAX_PATH
];
130 struct dirent
*entry
;
131 struct folder
* this = (struct folder
*)folder_alloc(sizeof(struct folder
));
133 char *first_child
= NULL
;
135 if (!strcmp(folder
,"/"))
136 strlcpy(fullpath
, folder
, 2);
138 snprintf(fullpath
, MAX_PATH
, "%s/%s", parent
? path
: "", folder
);
142 dir
= opendir(fullpath
);
145 this->previous
= parent
;
147 this->children
= NULL
;
148 this->children_count
= 0;
149 this->depth
= parent
? parent
->depth
+ 1 : 0;
151 while ((entry
= readdir(dir
))) {
152 int len
= strlen((char *)entry
->d_name
);
155 info
= dir_get_info(dir
, entry
);
157 /* skip anything not a directory */
158 if ((info
.attribute
& ATTR_DIRECTORY
) == 0) {
161 /* skip directories . and .. */
162 if ((!strcmp((char *)entry
->d_name
, ".")) ||
163 (!strcmp((char *)entry
->d_name
, ".."))) {
166 char *name
= folder_alloc_from_end(len
+1);
169 memcpy(name
, (char *)entry
->d_name
, len
+1);
174 /* now put the names in the array */
175 this->children
= (struct child
*)folder_alloc(sizeof(struct child
) * child_count
);
181 this->children
[this->children_count
].name
= first_child
;
182 this->children
[this->children_count
].folder
= NULL
;
183 this->children
[this->children_count
].state
= COLLAPSED
;
184 this->children_count
++;
185 first_child
+= strlen(first_child
) + 1;
188 qsort(this->children
, this->children_count
, sizeof(struct child
), compare
);
193 struct folder
* load_root(void)
195 static struct child root_child
;
197 root_child
.name
= "/";
198 root_child
.folder
= NULL
;
199 root_child
.state
= COLLAPSED
;
201 static struct folder root
= {
203 .children
= &root_child
,
212 static int count_items(struct folder
*start
)
217 for (i
=0; i
<start
->children_count
; i
++)
219 struct child
*foo
= &start
->children
[i
];
220 if (foo
->state
== EXPANDED
)
221 count
+= count_items(foo
->folder
);
227 static struct child
* find_index(struct folder
*start
, int index
, struct folder
**parent
)
233 while (i
< start
->children_count
)
235 struct child
*foo
= &start
->children
[i
];
242 if (foo
->state
== EXPANDED
)
244 struct child
*bar
= find_index(foo
->folder
, index
- i
, parent
);
249 index
-= count_items(foo
->folder
);
255 static const char * folder_get_name(int selected_item
, void * data
,
256 char * buffer
, size_t buffer_len
)
258 struct folder
*root
= (struct folder
*)data
;
259 struct folder
*parent
;
260 struct child
*this = find_index(root
, selected_item
, &parent
);
264 if (parent
->depth
>= 0)
265 for(int i
= 0; i
<= parent
->depth
; i
++)
266 strcat(buffer
, "\t");
268 strlcat(buffer
, this->name
, buffer_len
);
270 if (this->state
== EACCESS
)
271 { /* append error message to the entry if unaccessible */
272 size_t len
= strlcat(buffer
, " (", buffer_len
);
273 if (buffer_len
> len
)
275 snprintf(&buffer
[len
], buffer_len
- len
, str(LANG_READ_FAILED
),
277 strlcat(buffer
, ")", buffer_len
);
284 static enum themable_icons
folder_get_icon(int selected_item
, void * data
)
286 struct folder
*root
= (struct folder
*)data
;
287 struct folder
*parent
;
288 struct child
*this = find_index(root
, selected_item
, &parent
);
299 return Icon_Questionmark
;
304 static int folder_action_callback(int action
, struct gui_synclist
*list
)
306 struct folder
*root
= (struct folder
*)list
->data
;
307 struct folder
*parent
;
308 struct child
*this = find_index(root
, list
->selected_item
, &parent
), *child
;
311 if (action
== ACTION_STD_OK
)
316 this->state
= SELECTED
;
319 this->state
= COLLAPSED
;
322 if (this->folder
== NULL
)
323 this->folder
= load_folder(parent
, this->name
);
324 this->state
= this->folder
? (this->folder
->children_count
== 0 ?
325 SELECTED
: EXPANDED
) : EACCESS
;
328 /* cannot open, do nothing */
331 list
->nb_items
= count_items(root
);
332 return ACTION_REDRAW
;
334 else if (action
== ACTION_STD_CONTEXT
)
339 for (i
= 0; i
< this->folder
->children_count
; i
++)
341 child
= &this->folder
->children
[i
];
342 if (child
->state
== SELECTED
||
343 child
->state
== EXPANDED
)
344 child
->state
= COLLAPSED
;
345 else if (child
->state
== COLLAPSED
)
346 child
->state
= SELECTED
;
351 if (this->folder
== NULL
)
352 this->folder
= load_folder(parent
, this->name
);
353 this->state
= this->folder
? (this->folder
->children_count
== 0 ?
354 SELECTED
: EXPANDED
) : EACCESS
;
355 if (this->state
== EACCESS
)
357 for (i
= 0; i
< this->folder
->children_count
; i
++)
359 child
= &this->folder
->children
[i
];
360 child
->state
= SELECTED
;
364 /* cannot open, do nothing */
367 list
->nb_items
= count_items(root
);
368 return ACTION_REDRAW
;
375 static struct child
* find_from_filename(char* filename
, struct folder
*root
)
377 char *slash
= strchr(filename
, '/');
386 /* filenames beginning with a / are specially treated as the
387 * loop below can't handle them. they can only occur on the first,
388 * and not recursive, calls to this function.*/
389 if (slash
== filename
)
391 /* filename begins with /. in this case root must be the
392 * top level folder */
393 this = &root
->children
[0];
395 { /* filename == "/" */
400 /* filename == "/XXX/YYY". cascade down */
402 this->folder
= load_folder(root
, this->name
);
403 this->state
= EXPANDED
;
404 /* recurse with XXX/YYY */
405 return find_from_filename(slash
+1, this->folder
);
409 while (i
< root
->children_count
)
411 this = &root
->children
[i
];
412 if (!strcasecmp(this->name
, filename
))
415 { /* filename == XXX */
420 /* filename == XXX/YYY. cascade down */
422 this->folder
= load_folder(root
, this->name
);
423 this->state
= EXPANDED
;
424 return find_from_filename(slash
+1, this->folder
);
433 int select_paths(struct folder
* root
, char* buf
)
435 struct child
*item
= find_from_filename(buf
, root
);
437 item
->state
= SELECTED
;
441 static void save_folders_r(struct folder
*root
, char* dst
, size_t maxlen
)
445 while (i
< root
->children_count
)
447 struct child
*this = &root
->children
[i
];
448 if (this->state
== SELECTED
)
451 snprintf(buffer_front
, buffer_end
- buffer_front
,
452 "%s:", get_full_path(this->folder
));
455 char *p
= get_full_path(root
);
456 snprintf(buffer_front
, buffer_end
- buffer_front
,
457 "%s/%s:", strcmp(p
, "/") ? p
: "",
458 strcmp(this->name
, "/") ? this->name
: "");
460 strlcat(dst
, buffer_front
, maxlen
);
462 else if (this->state
== EXPANDED
)
463 save_folders_r(this->folder
, dst
, maxlen
);
468 static void save_folders(struct folder
*root
, char* dst
, size_t maxlen
)
472 save_folders_r(root
, dst
, maxlen
);
474 /* fix trailing ':' */
475 if (len
> 1) dst
[len
-1] = '\0';
478 bool folder_select(char* setting
, int setting_len
)
481 struct simplelist_info info
;
483 /* 32 separate folders should be Enough For Everybody(TM) */
485 char copy
[setting_len
];
488 /* copy onto stack as split_string() modifies it */
489 strlcpy(copy
, setting
, setting_len
);
490 nb_items
= split_string(copy
, ':', vect
, ARRAYLEN(vect
));
492 buffer_front
= plugin_get_buffer(&buf_size
);
493 buffer_end
= buffer_front
+ buf_size
;
498 for(int i
= 0; i
< nb_items
; i
++)
499 select_paths(root
, vect
[i
]);
502 simplelist_info_init(&info
, str(LANG_SELECT_FOLDER
),
503 count_items(root
), root
);
504 info
.get_name
= folder_get_name
;
505 info
.action_callback
= folder_action_callback
;
506 info
.get_icon
= folder_get_icon
;
507 simplelist_show_list(&info
);
509 /* done editing. check for changes */
510 save_folders(root
, copy
, setting_len
);
511 if (strcmp(copy
, setting
))
512 { /* prompt for saving changes and commit if yes */
513 if (yesno_pop(ID2P(LANG_SAVE_CHANGES
)))
515 strcpy(setting
, copy
);