Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / servers / exchange / storage / e-folder-tree.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /* e-folder-set.c
3  *
4  * Copyright (C) 2000, 2001 Ximian, Inc.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of version 2 of the GNU Lesser General Public
8  * License as published by the Free Software Foundation.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  *
20  * Author: Ettore Perazzoli
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include "e-folder-tree.h"
28
29 #include <string.h>
30 #include <glib.h>
31
32 struct Folder {
33         struct Folder *parent;
34         char *path;
35         void *data;
36         GList *subfolders;
37 };
38 typedef struct Folder Folder;
39
40 struct EFolderTree {
41         GHashTable *path_to_folder;
42         GHashTable *data_to_path;
43
44         EFolderDestroyNotify folder_destroy_notify;
45         void *folder_destroy_notify_closure;
46 };
47
48 /* Utility functions.  */
49
50 static char *
51 get_parent_path (const char *path)
52 {
53         const char *last_separator;
54
55         g_assert (g_path_is_absolute (path));
56
57         last_separator = strrchr (path, '/');
58
59         if (last_separator == path)
60                 return g_strdup ("/");
61
62         return g_strndup (path, last_separator - path);
63 }
64
65 static void
66 traverse_subtree (EFolderTree *tree,
67                   Folder *root_folder,
68                   EFolderTreeForeachFunc foreach_func,
69                   void *data)
70 {
71         GList *p;
72
73         g_assert (foreach_func != NULL);
74
75         (* foreach_func) (tree, root_folder->path, root_folder->data, data);
76
77         for (p = root_folder->subfolders; p != NULL; p = p->next) {
78                 Folder *folder;
79
80                 folder = (Folder *) p->data;
81                 traverse_subtree (tree, folder, foreach_func, data);
82         }
83 }
84
85 /* Folder handling.  */
86
87 static Folder *
88 folder_new (const char *path,
89             void *data)
90 {
91         Folder *folder;
92
93         folder = g_new0 (Folder, 1);
94         folder->path       = g_strdup (path);
95         folder->data       = data;
96
97         return folder;
98 }
99
100 static void
101 folder_remove_subfolder (Folder *folder,
102                          Folder *subfolder)
103 {
104         folder->subfolders = g_list_remove (folder->subfolders, subfolder);
105         subfolder->parent = NULL;
106 }
107
108 static void
109 folder_add_subfolder (Folder *folder,
110                       Folder *subfolder)
111 {
112         folder->subfolders = g_list_prepend (folder->subfolders, subfolder);
113         subfolder->parent = folder;
114 }
115
116 static void
117 folder_destroy (Folder *folder)
118 {
119         g_assert (folder->subfolders == NULL);
120
121         if (folder->parent != NULL)
122                 folder_remove_subfolder (folder->parent, folder);
123
124         g_free (folder->path);
125
126         g_free (folder);
127 }
128
129 static void
130 remove_folder (EFolderTree *folder_tree,
131                Folder *folder)
132 {
133         if (folder->subfolders != NULL) {
134                 GList *p;
135
136                 for (p = folder->subfolders; p != NULL; p = p->next) {
137                         Folder *subfolder;
138
139                         subfolder = (Folder *) p->data;
140                         subfolder->parent = NULL;
141                         remove_folder (folder_tree, subfolder);
142                 }
143
144                 g_list_free (folder->subfolders);
145                 folder->subfolders = NULL;
146         }
147
148         g_hash_table_remove (folder_tree->path_to_folder, folder->path);
149         g_hash_table_remove (folder_tree->data_to_path, folder->data);
150
151         if (folder_tree->folder_destroy_notify != NULL)
152                 (* folder_tree->folder_destroy_notify) (folder_tree,
153                                                         folder->path,
154                                                         folder->data,
155                                                         folder_tree->folder_destroy_notify_closure);
156
157         folder_destroy (folder);
158 }
159
160 /**
161  * e_folder_tree_new:
162  * @folder_destroy_notify: Function to be called when a folder gets removed from the tree
163  * @closure: Additional data to pass to @folder_destroy_notify
164  * 
165  * Create a new EFolderTree.
166  * 
167  * Return value: A pointer to the newly created EFolderTree.
168  **/
169 EFolderTree *
170 e_folder_tree_new (EFolderDestroyNotify folder_destroy_notify,
171                    void *closure)
172 {
173         EFolderTree *new;
174
175         new = g_new0 (EFolderTree, 1);
176
177         new->folder_destroy_notify         = folder_destroy_notify;
178         new->folder_destroy_notify_closure = closure;
179
180         new->path_to_folder = g_hash_table_new (g_str_hash, g_str_equal);
181         new->data_to_path = g_hash_table_new (g_direct_hash, g_direct_equal);
182
183         e_folder_tree_add (new, "/", NULL);
184
185         return new;
186 }
187
188 /**
189  * e_folder_tree_destroy:
190  * @folder_tree: A pointer to an EFolderTree
191  * 
192  * Destroy @folder_tree.
193  **/
194 void
195 e_folder_tree_destroy (EFolderTree *folder_tree)
196 {
197         Folder *root_folder;
198
199         g_return_if_fail (folder_tree != NULL);
200
201         root_folder = g_hash_table_lookup (folder_tree->path_to_folder, "/");
202         remove_folder (folder_tree, root_folder);
203
204         g_hash_table_destroy (folder_tree->path_to_folder);
205         g_hash_table_destroy (folder_tree->data_to_path);
206
207         g_free (folder_tree);
208 }
209
210 /**
211  * e_folder_tree_add:
212  * @folder_tree: A pointer to an EFolderTree
213  * @path: Path at which the new folder must be added
214  * @data: Data associated with the new folder
215  * 
216  * Insert a new folder at @path, with the specified @data.
217  *
218  * Return value: %TRUE if successful, %FALSE if failed.
219  **/
220 gboolean
221 e_folder_tree_add (EFolderTree *folder_tree,
222                    const char *path,
223                    void *data)
224 {
225         Folder *parent_folder;
226         Folder *folder;
227         const char *existing_path;
228         char *parent_path;
229
230         g_return_val_if_fail (folder_tree != NULL, FALSE);
231         g_return_val_if_fail (path != NULL, FALSE);
232         g_return_val_if_fail (g_path_is_absolute (path), FALSE);
233
234         /* Can only "add" a new root folder if the tree is empty */
235         if (! strcmp (path, "/")) {
236                 folder = g_hash_table_lookup (folder_tree->path_to_folder, path);
237                 if (folder) {
238                         if (folder->subfolders) {
239                                 g_warning ("e_folder_tree_add() -- Trying to change root folder after adding children");
240                                 return FALSE;
241                         }
242                         remove_folder (folder_tree, folder);
243                 }
244
245                 folder = folder_new (path, data);
246                 g_hash_table_insert (folder_tree->path_to_folder, folder->path, folder);
247                 g_hash_table_insert (folder_tree->data_to_path, data, folder->path);
248                 return TRUE;
249         }
250
251         parent_path = get_parent_path (path);
252
253         parent_folder = g_hash_table_lookup (folder_tree->path_to_folder, parent_path);
254         if (parent_folder == NULL) {
255                 g_warning ("e_folder_tree_add() -- Trying to add a subfolder to a path that does not exist yet -- %s",
256                            parent_path);
257                 g_free (parent_path);
258                 return FALSE;
259         }
260         g_free (parent_path);
261
262         folder = g_hash_table_lookup (folder_tree->path_to_folder, path);
263         if (folder != NULL) {
264                 g_warning ("e_folder_tree_add() -- Trying to add a subfolder for a path that already exists -- %s",
265                            path);
266                 return FALSE;
267         }
268
269         existing_path = g_hash_table_lookup (folder_tree->data_to_path, data);
270         if (existing_path != NULL) {
271                 g_warning ("e_folder_tree_add() -- Trying to add a folder with duplicate data -- %s",
272                            path);
273                 return FALSE;
274         }
275
276         folder = folder_new (path, data);
277         folder_add_subfolder (parent_folder, folder);
278
279         g_hash_table_insert (folder_tree->path_to_folder, folder->path, folder);
280         g_hash_table_insert (folder_tree->data_to_path, data, folder->path);
281
282         return TRUE;
283 }
284
285 /**
286  * e_folder_tree_remove:
287  * @folder_tree: A pointer to an EFolderTree
288  * @path: Path of the folder to remove
289  * 
290  * Remove the folder at @path from @folder_tree.
291  *
292  * Return value: %TRUE if successful, %FALSE if failed.
293  **/
294 gboolean
295 e_folder_tree_remove (EFolderTree *folder_tree,
296                       const char *path)
297 {
298         Folder *folder;
299
300         g_return_val_if_fail (folder_tree != NULL, FALSE);
301         g_return_val_if_fail (path != NULL, FALSE);
302         g_return_val_if_fail (g_path_is_absolute (path), FALSE);
303
304         folder = g_hash_table_lookup (folder_tree->path_to_folder, path);
305         if (folder == NULL)
306                 return FALSE;
307
308         remove_folder (folder_tree, folder);
309         return TRUE;
310 }
311
312 static void
313 count_nodes (EFolderTree *tree,
314              const char *path,
315              void *data,
316              void *closure)
317 {
318         int *count = closure;
319         
320         (*count)++;
321 }
322
323 /**
324  * e_folder_tree_get_count:
325  * @folder_tree: A pointer to an EFolderTree
326  * 
327  * Gets the number of folders in the tree
328  * 
329  * Return value: The number of folders in the tree
330  **/
331 int
332 e_folder_tree_get_count (EFolderTree *folder_tree)
333 {
334         int count = 0;
335         
336         e_folder_tree_foreach (folder_tree, count_nodes, &count);
337         
338         return count;
339 }
340                                               
341 /**
342  * e_folder_tree_get_folder:
343  * @folder_tree: A pointer to an EFolderTree
344  * @path: Path of the folder for which we want to get the data
345  * 
346  * Get the data for the folder at @path.
347  * 
348  * Return value: The pointer to the data for the folder at @path.
349  **/
350 void *
351 e_folder_tree_get_folder (EFolderTree *folder_tree,
352                           const char *path)
353 {
354         Folder *folder;
355
356         g_return_val_if_fail (folder_tree != NULL, NULL);
357         g_return_val_if_fail (path != NULL, NULL);
358         g_return_val_if_fail (g_path_is_absolute (path), NULL);
359
360         folder = g_hash_table_lookup (folder_tree->path_to_folder, path);
361         if (folder == NULL)
362                 return NULL;
363
364         return folder->data;
365 }
366
367 /**
368  * e_folder_tree_get_subfolders:
369  * @folder_tree: A pointer to an EFolderTree
370  * @path: A path in @folder_tree
371  * 
372  * Get a list of the paths of the subfolders of @path.
373  * 
374  * Return value: A list of pointers to the paths of the subfolders.  The list
375  * and the strings must be freed by the caller.
376  **/
377 GList *
378 e_folder_tree_get_subfolders (EFolderTree *folder_tree,
379                               const char *path)
380 {
381         Folder *folder;
382         GList *list;
383         GList *p;
384
385         g_return_val_if_fail (folder_tree != NULL, NULL);
386         g_return_val_if_fail (path != NULL, NULL);
387         g_return_val_if_fail (g_path_is_absolute (path), NULL);
388
389         folder = g_hash_table_lookup (folder_tree->path_to_folder, path);
390         if (folder == NULL)
391                 return NULL;
392
393         list = NULL;
394         for (p = folder->subfolders; p != NULL; p = p->next) {
395                 const Folder *folder;
396
397                 folder = (const Folder *) p->data;
398                 list = g_list_prepend (list, g_strdup (folder->path));
399         }
400
401         return list;
402 }
403
404 /**
405  * e_folder_tree_foreach:
406  * @folder_tree: 
407  * @foreach_func: 
408  * @data: 
409  * 
410  * Call @foreach_func with the specified @data for all the folders
411  * in @folder_tree, starting at the root node.
412  **/
413 void
414 e_folder_tree_foreach (EFolderTree *folder_tree,
415                        EFolderTreeForeachFunc foreach_func,
416                        void *data)
417 {
418         Folder *root_node;
419
420         g_return_if_fail (folder_tree != NULL);
421         g_return_if_fail (foreach_func != NULL);
422
423         root_node = g_hash_table_lookup (folder_tree->path_to_folder, "/");
424         if (root_node == NULL) {
425                 g_warning ("e_folder_tree_foreach -- What?!  No root node!?");
426                 return;
427         }
428
429         traverse_subtree (folder_tree, root_node, foreach_func, data);
430 }
431
432
433 /**
434  * e_folder_tree_get_path_for_data:
435  * @folder_tree: A pointer to an EFolderTree
436  * @data: The data for the folder for which the path is needed
437  * 
438  * Look up the path for the specified @data.
439  * 
440  * Return value: The path for the folder that holds that @data.
441  **/
442 const char *
443 e_folder_tree_get_path_for_data  (EFolderTree *folder_tree,
444                                   const void *data)
445 {
446         g_return_val_if_fail (folder_tree != NULL, NULL);
447         g_return_val_if_fail (data != NULL, NULL);
448
449         return (const char *) g_hash_table_lookup (folder_tree->data_to_path, data);
450 }