2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 2
5 * of the License, or (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
16 * See the COPYING file for license information.
18 * Guillaume Chazarain <guichaz@yahoo.fr>
21 /************************************************************
22 * The filenames tree, shown in images menus and serialized *
23 ************************************************************/
25 #include <string.h> /* strchr(), strrchr(), strlen() */
29 #include "str_utils.h"
30 #include "files_list.h"
31 #include "timestamp.h"
34 static GNode
*last_tree
= NULL
;
36 /* Its creation date. */
37 static DECLARE_TIMESTAMP(timestamp
);
39 /* We make sure there is only one user. */
40 static volatile gboolean cancel
= FALSE
;
41 G_LOCK_DEFINE_STATIC(tree
);
43 void end_using_tree(void)
49 void cancel_using_tree(void)
54 gboolean
canceled_using_tree(void)
60 static tree_item
*new_item(gchar
* name
, gchar
* path
)
64 item
= g_new(tree_item
, 1);
67 item
->thumb_key
= NULL
;
79 /* Whether dir1 is parent, equal or different from the dir where file2 is. */
80 static dir_type
dir_wrt_file(const gchar
* dir1
, const gchar
* file2
)
83 gchar
*ptr2
, *last_slash
;
90 if (dir1
[0] == '/' && dir1
[1] == '\0' && file2
[0] == '/')
91 /* dir1 == "/" and file2 == "/...". */
92 return strchr(file2
+ 1, '/') == NULL
? DIR_EQUAL
: DIR_PARENT
;
94 /* We temporarily cut the filename to have the dirname. */
95 ptr2
= (gchar
*) file2
;
96 last_slash
= strrchr(file2
, '/');
102 /* Skip identical characters in the paths. */
103 while (*ptr1
!= '\0' && *ptr1
== *ptr2
) {
111 else if (*ptr1
== '\0' && *ptr2
== '/')
123 /* When file == parent"/a/b/c...", it returns "a". */
124 static gchar
*first_dir(const gchar
* file
, const gchar
* parent
)
129 if (parent
[0] == '\0' && file
[0] == '/')
130 return g_strdup("/");
132 /* Skip identical characters in the paths. */
133 while (*file
== *parent
) {
138 /* Suppress the leading separator. */
144 /* Keep only the first dir by looking for the second one, or the file */
145 end
= strchr(file
, '/');
147 /* and throwing it away. */
148 return g_strndup(res
, end
- res
);
151 static void make_tree_rec(GNode
* parent
, gchar
*** array_ptr
)
159 this_dir
= item
->path
;
161 while (**array_ptr
!= NULL
) {
162 switch (dir_wrt_file(this_dir
, **array_ptr
)) {
164 /* Subdirectory => add it, then recursive call. */
165 name
= first_dir(**array_ptr
, this_dir
);
166 path
= g_build_filename(this_dir
, name
, NULL
);
167 item
= new_item(name
, path
);
169 node
= g_node_new(item
);
170 g_node_append(parent
, node
);
172 make_tree_rec(node
, array_ptr
);
176 /* Same directory => add the filename, stay in the loop. */
177 name
= **array_ptr
+ strlen(this_dir
);
178 while (name
[0] == '/')
181 item
= new_item(g_strdup(name
), **array_ptr
);
182 g_node_append_data(parent
, item
);
186 /* case DIR_DIFFERENT: */
188 /* Return from the recursive calls to find a common directory. */
194 static gboolean
destroy_node(GNode
* node
)
196 tree_item
*item
= node
->data
;
198 if (G_NODE_IS_LEAF(node
))
199 g_free(item
->thumb_key
);
208 static void set_parent(GNode
* node
, gpointer parent
)
210 node
->parent
= parent
;
213 static void compress_node(GNode
* node
)
216 tree_item
*item
, *child_item
;
219 if (g_node_nth_child(node
, 1) != NULL
)
220 /* More than one child */
225 child
= g_node_first_child(node
);
226 child_item
= child
->data
;
228 /* Skip one level. */
229 g_node_children_foreach(child
, G_TRAVERSE_ALL
, set_parent
, node
);
230 node
->children
= child
->children
;
232 /* Concatenate the names. */
233 new_name
= g_build_filename(item
->name
, child_item
->name
, NULL
);
235 item
->name
= new_name
;
237 /* Use the child path. */
239 item
->path
= child_item
->path
;
240 child_item
->path
= NULL
;
242 /* Get rid of the compressed child. */
244 child
->parent
= NULL
;
245 child
->children
= NULL
;
246 g_node_destroy(child
);
249 static gboolean
to_utf8(GNode
* node
)
255 converted
= (gchar
*) filename_to_utf8(item
->name
);
256 if (item
->name
!= converted
) {
258 item
->name
= g_strdup(converted
);
265 * We change the tree structure so it is
266 * safer to use our own traverse function.
268 static void compress_tree(GNode
* node
)
270 if (G_NODE_IS_LEAF(node
) == FALSE
) {
271 g_node_children_foreach(node
, G_TRAVERSE_ALL
,
272 (GNodeForeachFunc
) compress_tree
, NULL
);
277 static void print_tree(GNode
* tree
, gint level
);
279 /* Can we avoid rebuilding a tree? */
280 gboolean
last_tree_ok(timestamp_t menu_timestamp
)
282 timestamp_t list_timestamp
= get_list_timestamp();
284 if (last_tree
== NULL
)
285 /* No tree to reuse. */
288 if (up_to_date(timestamp
, list_timestamp
) == FALSE
)
289 /* The list has changed. */
293 return up_to_date(menu_timestamp
, timestamp
);
298 GNode
*make_tree(void)
305 if (G_TRYLOCK(tree
) == FALSE
)
312 invalidate_last_tree();
314 array_ptr
= array
= get_sorted_files_array();
320 item
= new_item(g_strdup(""), g_strdup(""));
321 tree
= g_node_new(item
);
323 /* Build the tree. */
324 while (*array_ptr
!= NULL
)
325 make_tree_rec(tree
, &array_ptr
);
331 g_node_traverse(tree
, G_POST_ORDER
, G_TRAVERSE_ALL
, -1,
332 (GNodeTraverseFunc
) to_utf8
, NULL
);
341 void destroy_tree(GNode
* tree
)
343 g_node_traverse(tree
, G_POST_ORDER
, G_TRAVERSE_ALL
, -1,
344 (GNodeTraverseFunc
) destroy_node
, NULL
);
346 g_node_destroy(tree
);
349 void invalidate_last_tree(void)
352 destroy_tree(last_tree
);
354 reset_timestamp(×tamp
);
358 static void print_tree(GNode
* tree
, gint level
)
362 tree_item
*item
= tree
->data
;
364 for (i
= 0; i
< level
; i
++)
367 printf("name: \"%s\", path: \"%s\"\n", item
->name
, item
->path
);
370 g_node_children_foreach(tree
, G_TRAVERSE_ALL
,
371 print_tree
, GINT_TO_POINTER(level
));