Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / servers / exchange / storage / exchange-hierarchy-webdav.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3 /* Copyright (C) 2002-2004 Novell, Inc.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of version 2 of the GNU Lesser General Public
7  * License as published by the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this program; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 /* ExchangeHierarchyWebDAV: class for a normal WebDAV folder hierarchy
21  * in the Exchange storage. Eg, the "Personal Folders" and "Public
22  * Folders" hierarchies.
23  */
24
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32
33 #include <glib.h>
34 #include <glib/gstdio.h>
35
36 #include "libedataserverui/e-passwords.h"
37 #include "libedataserver/e-source-list.h"
38
39 #include "exchange-hierarchy-webdav.h"
40 #include "exchange-account.h"
41 #include "e-folder-exchange.h"
42 #include "e2k-context.h"
43 #include "e2k-path.h"
44 #include "e2k-propnames.h"
45 #include "e2k-restriction.h"
46 #include "e2k-uri.h"
47 #include "e2k-utils.h"
48 #include "exchange-folder-size.h"
49 #include "exchange-esource.h"
50
51 #define d(x)
52
53 #define URI_ENCODE_CHARS "@;:/?=."
54
55 struct _ExchangeHierarchyWebDAVPrivate {
56         GHashTable *folders_by_internal_path;
57         gboolean deep_searchable;
58         char *trash_path;
59         gdouble total_folder_size;
60 };
61
62 #define PARENT_TYPE EXCHANGE_TYPE_HIERARCHY
63 static ExchangeHierarchyClass *parent_class = NULL;
64
65 static void folder_type_map_init (void);
66
67 static void dispose (GObject *object);
68 static void finalize (GObject *object);
69 static gboolean is_empty (ExchangeHierarchy *hier);
70 static void rescan (ExchangeHierarchy *hier);
71 static ExchangeAccountFolderResult scan_subtree  (ExchangeHierarchy *hier,
72                                                   EFolder *folder,
73                                                   int mode);
74 static ExchangeAccountFolderResult create_folder (ExchangeHierarchy *hier,
75                                                   EFolder *parent,
76                                                   const char *name,
77                                                   const char *type);
78 static ExchangeAccountFolderResult remove_folder (ExchangeHierarchy *hier,
79                                                   EFolder *folder);
80 static ExchangeAccountFolderResult xfer_folder   (ExchangeHierarchy *hier,
81                                                   EFolder *source,
82                                                   EFolder *dest_parent,
83                                                   const char *dest_name,
84                                                   gboolean remove_source);
85
86 static void hierarchy_new_folder (ExchangeHierarchy *hier, EFolder *folder,
87                                   gpointer user_data);
88 static void hierarchy_removed_folder (ExchangeHierarchy *hier, EFolder *folder,
89                                       gpointer user_data);
90
91 static void
92 class_init (GObjectClass *object_class)
93 {
94         ExchangeHierarchyClass *exchange_hierarchy_class =
95                 EXCHANGE_HIERARCHY_CLASS (object_class);
96
97         folder_type_map_init ();
98
99         parent_class = g_type_class_ref (PARENT_TYPE);
100
101         /* virtual method override */
102         object_class->dispose = dispose;
103         object_class->finalize = finalize;
104
105         exchange_hierarchy_class->is_empty = is_empty;
106         exchange_hierarchy_class->rescan = rescan;
107         exchange_hierarchy_class->scan_subtree = scan_subtree;
108         exchange_hierarchy_class->create_folder = create_folder;
109         exchange_hierarchy_class->remove_folder = remove_folder;
110         exchange_hierarchy_class->xfer_folder = xfer_folder;
111 }
112
113 static void
114 init (GObject *object)
115 {
116         ExchangeHierarchyWebDAV *hwd = EXCHANGE_HIERARCHY_WEBDAV (object);
117
118         hwd->priv = g_new0 (ExchangeHierarchyWebDAVPrivate, 1);
119         hwd->priv->folders_by_internal_path = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) g_object_unref);
120         hwd->priv->total_folder_size = 0;
121
122         g_signal_connect (object, "new_folder",
123                           G_CALLBACK (hierarchy_new_folder), NULL);
124         g_signal_connect (object, "removed_folder",
125                           G_CALLBACK (hierarchy_removed_folder), NULL);
126 }
127
128 static void
129 dispose (GObject *object)
130 {
131         G_OBJECT_CLASS (parent_class)->dispose (object);
132 }
133
134 static void
135 finalize (GObject *object)
136 {
137         ExchangeHierarchyWebDAV *hwd = EXCHANGE_HIERARCHY_WEBDAV (object);
138
139         g_hash_table_destroy (hwd->priv->folders_by_internal_path);
140         g_free (hwd->priv->trash_path);
141         g_free (hwd->priv);
142
143         G_OBJECT_CLASS (parent_class)->finalize (object);
144 }
145
146 E2K_MAKE_TYPE (exchange_hierarchy_webdav, ExchangeHierarchyWebDAV, class_init, init, PARENT_TYPE)
147
148
149 typedef struct {
150         char *contentclass, *component;
151         gboolean offline_supported;
152 } ExchangeFolderType;
153
154 static ExchangeFolderType folder_types[] = {
155         { "IPF.Note", "mail", FALSE },
156         { "IPF.Contact", "contacts", FALSE },
157         { "IPF.Appointment", "calendar", FALSE },
158         { "IPF.Task", "tasks", FALSE },
159         { NULL, NULL }
160 };
161 static GHashTable *folder_type_map;
162
163 static void
164 folder_type_map_init (void)
165 {
166         int i;
167
168         folder_type_map = g_hash_table_new (g_str_hash, g_str_equal);
169         for (i = 0; folder_types[i].contentclass; i++) {
170                 g_hash_table_insert (folder_type_map,
171                                      folder_types[i].contentclass,
172                                      &folder_types[i]);
173         }
174 }
175
176 /* We maintain the folders_by_internal_path hash table by listening
177  * to our own signal emissions. (This lets ExchangeHierarchyForeign
178  * remove its folders by just calling exchange_hierarchy_removed_folder.)
179  */
180 static void
181 hierarchy_new_folder (ExchangeHierarchy *hier, EFolder *folder,
182                       gpointer user_data)
183 {
184         const char *internal_uri ;
185         char *mf_path;
186         
187         g_return_if_fail (E_IS_FOLDER (folder));
188         internal_uri = e_folder_exchange_get_internal_uri (folder);
189
190         /* This should ideally not be needed. But, this causes a problem when the
191         server has identical folder names [ internal_uri ] for folders. Very much
192         possible in the case of favorite folders */
193         if (g_hash_table_lookup (EXCHANGE_HIERARCHY_WEBDAV (hier)->priv->folders_by_internal_path,
194                                 (char *)e2k_uri_path (internal_uri)))
195                 return;
196
197         g_hash_table_insert (EXCHANGE_HIERARCHY_WEBDAV (hier)->priv->folders_by_internal_path,
198                              (char *)e2k_uri_path (internal_uri), g_object_ref (folder));
199
200         mf_path = e_folder_exchange_get_storage_file (folder, "connector-metadata.xml");
201         e_folder_exchange_save_to_file (folder, mf_path);
202         g_free (mf_path);
203 }
204
205 static void
206 hierarchy_removed_folder (ExchangeHierarchy *hier, EFolder *folder,
207                           gpointer user_data)
208 {
209         const char *internal_uri = e_folder_exchange_get_internal_uri (folder);
210         char *mf_path;
211
212         g_hash_table_remove (EXCHANGE_HIERARCHY_WEBDAV (hier)->priv->folders_by_internal_path,
213                              (char *)e2k_uri_path (internal_uri));
214
215         mf_path = e_folder_exchange_get_storage_file (folder, "connector-metadata.xml");
216         g_unlink (mf_path);
217         g_free (mf_path);
218
219         e_path_rmdir (hier->account->storage_dir,
220                       e_folder_exchange_get_path (folder));
221 }
222
223 static gboolean
224 is_empty (ExchangeHierarchy *hier)
225 {
226         ExchangeHierarchyWebDAV *hwd = EXCHANGE_HIERARCHY_WEBDAV (hier);
227
228         /* 1, not 0, because there will always be an entry for toplevel */
229         return g_hash_table_size (hwd->priv->folders_by_internal_path) == 1;
230 }
231
232 static EFolder *
233 e_folder_webdav_new (ExchangeHierarchy *hier, const char *internal_uri,
234                      EFolder *parent, const char *name, const char *type,
235                      const char *outlook_class, int unread,
236                      gboolean offline_supported)
237 {
238         EFolder *folder;
239         char *real_type, *http_uri, *physical_uri, *fixed_name = NULL;
240
241         d( g_print ("exchange-hierarchy-webdave.c:e_folder_webdave_new: internal_uri=[%s], name=[%s], type=[%s], class=[%s]\n",
242                     internal_uri, name, type, outlook_class));
243
244         if (hier->type == EXCHANGE_HIERARCHY_PUBLIC &&
245             !strstr (type, "/public"))
246                 real_type = g_strdup_printf ("%s/public", type);
247         else if (hier->type == EXCHANGE_HIERARCHY_FOREIGN &&
248                  !strcmp (type, "calendar"))
249                 real_type = g_strdup ("calendar/public"); /* Hack */
250         else
251                 real_type = g_strdup (type);
252         
253         fixed_name = e2k_uri_encode (name, FALSE, URI_ENCODE_CHARS);
254         physical_uri = e2k_uri_concat (e_folder_get_physical_uri (parent), fixed_name);
255         g_free (fixed_name);
256
257         if (internal_uri) {
258                 folder = e_folder_exchange_new (hier, name,
259                                                 real_type, outlook_class,
260                                                 physical_uri, internal_uri);
261         } else {
262                 char *temp_name;
263                 char *encoded_name = NULL;
264                 const char *new_internal_uri;
265                 int len;
266
267                 len = strlen (name);
268
269                 /* appending "/" here, so that hash table lookup in rescan() succeeds */
270                 if (name[len-1] != '/') {
271                         encoded_name = e2k_uri_encode (name, FALSE, URI_ENCODE_CHARS);
272                 } else {
273                         temp_name = g_strndup (name, len-1);
274                         encoded_name = e2k_uri_encode (temp_name, FALSE, URI_ENCODE_CHARS);
275                         g_free (temp_name);
276                 }
277                 temp_name = g_strdup_printf ("%s/", encoded_name);
278                 g_free (encoded_name);
279
280                 new_internal_uri = e_folder_exchange_get_internal_uri (parent);
281                 http_uri = e2k_uri_concat (new_internal_uri, temp_name);
282                 d(g_print ("exchange-hierarchy-webdave.c:e_folder_webdave_new: http_uri=[%s], new_internal_uri=[%s], temp_name=[%s], name[%s]\n",
283                            http_uri, new_internal_uri, temp_name, name));
284                 g_free (temp_name);
285         
286                 folder = e_folder_exchange_new (hier, name,
287                                                 real_type, outlook_class,
288                                                 physical_uri, http_uri);
289                 g_free (http_uri);
290         }
291         g_free (physical_uri);
292         g_free (real_type);
293
294         if (unread && hier->type != EXCHANGE_HIERARCHY_PUBLIC)
295                 e_folder_set_unread_count (folder, unread);
296         if (offline_supported)
297                 e_folder_set_can_sync_offline (folder, offline_supported);
298
299         /* FIXME: set is_stock */
300
301         return folder;
302 }
303
304 static ExchangeAccountFolderResult
305 create_folder (ExchangeHierarchy *hier, EFolder *parent,
306                const char *name, const char *type)
307 {
308         EFolder *dest;
309         E2kProperties *props;
310         E2kHTTPStatus status;
311         char *permanent_url = NULL;
312         int i, mode;
313
314         exchange_account_is_offline (hier->account, &mode);
315         if (mode != ONLINE_MODE)
316                 return EXCHANGE_ACCOUNT_FOLDER_OFFLINE;
317
318         for (i = 0; folder_types[i].component; i++) {
319                 if (!strcmp (folder_types[i].component, type))
320                         break;
321         }
322         if (!folder_types[i].component)
323                 return EXCHANGE_ACCOUNT_FOLDER_UNKNOWN_TYPE;
324
325         dest = e_folder_webdav_new (hier, NULL, parent, name, type,
326                                     folder_types[i].contentclass, 0,
327                                     folder_types[i].offline_supported);
328
329         props = e2k_properties_new ();
330         e2k_properties_set_string (props, E2K_PR_EXCHANGE_FOLDER_CLASS,
331                                    g_strdup (folder_types[i].contentclass));
332
333         status = e_folder_exchange_mkcol (dest, NULL, props,
334                                           &permanent_url);
335         e2k_properties_free (props);
336
337         if (E2K_HTTP_STATUS_IS_SUCCESSFUL (status)) {
338                 e_folder_exchange_set_permanent_uri (dest, permanent_url);
339                 g_free (permanent_url);
340                 exchange_hierarchy_new_folder (hier, dest);
341                 g_object_unref (dest);
342
343                 /* update the folder size table, new folder, initialize the size to 0 */ 
344                 exchange_account_folder_size_add (hier->account, name, 0);
345                 return EXCHANGE_ACCOUNT_FOLDER_OK;
346         }
347
348         g_object_unref (dest);
349         if (status == E2K_HTTP_METHOD_NOT_ALLOWED)
350                 return EXCHANGE_ACCOUNT_FOLDER_ALREADY_EXISTS;
351         else if (status == E2K_HTTP_CONFLICT)
352                 return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
353         else if (status == E2K_HTTP_FORBIDDEN)
354                 return EXCHANGE_ACCOUNT_FOLDER_PERMISSION_DENIED;
355         else
356                 return EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR;
357 }
358
359 static ExchangeAccountFolderResult
360 remove_folder (ExchangeHierarchy *hier, EFolder *folder)
361 {
362         E2kHTTPStatus status;
363         int mode;
364
365         exchange_account_is_offline (hier->account, &mode);
366
367         if (mode != ONLINE_MODE)
368                 return EXCHANGE_ACCOUNT_FOLDER_OFFLINE; 
369
370         if (folder == hier->toplevel)
371                 return EXCHANGE_ACCOUNT_FOLDER_PERMISSION_DENIED;
372
373         status = e_folder_exchange_delete (folder, NULL);
374         if (E2K_HTTP_STATUS_IS_SUCCESSFUL (status)) {
375                 exchange_hierarchy_removed_folder (hier, folder);
376
377                 /* update the folder size info */
378                 exchange_account_folder_size_remove (hier->account, 
379                                         e_folder_get_name(folder));
380                 return EXCHANGE_ACCOUNT_FOLDER_OK;
381         } else
382                 return EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR;
383 }
384
385 static ExchangeAccountFolderResult
386 xfer_folder (ExchangeHierarchy *hier, EFolder *source,
387              EFolder *dest_parent, const char *dest_name,
388              gboolean remove_source)
389 {
390         E2kHTTPStatus status;
391         EFolder *dest;
392         char *permanent_url = NULL, *physical_uri, *source_parent;
393         const char *folder_type = NULL, *source_folder_name;
394         ExchangeAccountFolderResult ret_code;
395         int mode;
396
397         exchange_account_is_offline (hier->account, &mode);
398         if (mode != ONLINE_MODE)
399                 return EXCHANGE_ACCOUNT_FOLDER_OFFLINE;
400
401         if (source == hier->toplevel)
402                 return EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR;
403
404         dest = e_folder_webdav_new (hier, NULL, dest_parent, dest_name,
405                                     e_folder_get_type_string (source),
406                                     e_folder_exchange_get_outlook_class (source),
407                                     e_folder_get_unread_count (source),
408                                     e_folder_get_can_sync_offline (source));
409
410         status = e_folder_exchange_transfer_dir (source, NULL, dest,
411                                                  remove_source,
412                                                  &permanent_url);
413
414         if (E2K_HTTP_STATUS_IS_SUCCESSFUL (status)) {
415                 folder_type = e_folder_get_type_string (source);
416                 if (permanent_url)
417                         e_folder_exchange_set_permanent_uri (dest, permanent_url);
418                 if (remove_source)
419                         exchange_hierarchy_removed_folder (hier, source);
420                 exchange_hierarchy_new_folder (hier, dest);
421                 scan_subtree (hier, dest, mode);
422                 physical_uri = g_strdup (e_folder_get_physical_uri (source));
423                 g_object_unref (dest);
424                 ret_code = EXCHANGE_ACCOUNT_FOLDER_OK;
425
426                 /* Find if folder movement or rename.  
427                  * update folder size in case of rename.
428                  */
429
430                 source_folder_name = strrchr (physical_uri, '/') + 1;
431                 source_parent = g_strndup (physical_uri, 
432                                            source_folder_name - physical_uri); 
433                 if (!strcmp (e_folder_get_physical_uri (dest_parent), source_parent)) {
434                         /* rename - remove folder entry from hash, and 
435                          * update the hash table with new name 
436                          */
437                         exchange_account_folder_size_rename (hier->account, 
438                                 source_folder_name+1, dest_name);
439                 }
440                 g_free (source_parent);
441         } else {
442                 physical_uri = e2k_uri_concat (
443                                 e_folder_get_physical_uri (dest_parent), 
444                                 dest_name);
445                 g_object_unref (dest);
446                 if (status == E2K_HTTP_FORBIDDEN ||
447                     status == E2K_HTTP_UNAUTHORIZED)
448                         ret_code = EXCHANGE_ACCOUNT_FOLDER_PERMISSION_DENIED;
449                 else
450                         ret_code = EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR;
451         }
452
453         /* Remove the ESource of the source folder, in case of rename/move */
454         if ((hier->type == EXCHANGE_HIERARCHY_PERSONAL || 
455              hier->type == EXCHANGE_HIERARCHY_FAVORITES) && remove_source && 
456              ret_code == EXCHANGE_ACCOUNT_FOLDER_OK) {
457                 
458                 if ((strcmp (folder_type, "calendar") == 0) ||
459                     (strcmp (folder_type, "calendar/public") == 0)) {
460                         remove_folder_esource (hier->account, 
461                                                EXCHANGE_CALENDAR_FOLDER,
462                                                physical_uri);
463                 }
464                 else if ((strcmp (folder_type, "tasks") == 0) ||
465                          (strcmp (folder_type, "tasks/public") == 0)){
466                         remove_folder_esource (hier->account, 
467                                                EXCHANGE_TASKS_FOLDER,
468                                                physical_uri);
469                 }
470                 else if ((strcmp (folder_type, "contacts") == 0) ||
471                          (strcmp (folder_type, "contacts/public") == 0)) {
472                         remove_folder_esource (hier->account, 
473                                                EXCHANGE_CONTACTS_FOLDER,
474                                                physical_uri);
475                 }
476         }
477         if (physical_uri)
478                 g_free (physical_uri);
479         return ret_code;
480 }
481
482 static void
483 add_href (gpointer path, gpointer folder, gpointer hrefs)
484 {
485         const char *folder_type;
486         
487         folder_type = e_folder_get_type_string (folder);
488
489         if (!folder_type)
490                 return;
491         
492         if (!strcmp (folder_type, "noselect"))
493                 return;
494
495         g_ptr_array_add (hrefs, path);
496 }
497
498 /* E2K_PR_EXCHANGE_FOLDER_SIZE also can be used for reading folder size */
499 static const char *rescan_props[] = {
500         E2K_PR_EXCHANGE_FOLDER_SIZE,
501         E2K_PR_HTTPMAIL_UNREAD_COUNT
502 };
503 static const int n_rescan_props = sizeof (rescan_props) / sizeof (rescan_props[0]);
504
505 static void
506 rescan (ExchangeHierarchy *hier)
507 {
508         ExchangeHierarchyWebDAV *hwd = EXCHANGE_HIERARCHY_WEBDAV (hier);
509         const char *prop = E2K_PR_HTTPMAIL_UNREAD_COUNT;
510         const char *folder_size, *folder_name;
511         GPtrArray *hrefs;
512         E2kResultIter *iter;
513         E2kResult *result;
514         EFolder *folder;
515         int unread, mode;
516         gboolean personal = ( hier->type == EXCHANGE_HIERARCHY_PERSONAL );
517         gdouble fsize_d;
518
519         exchange_account_is_offline (hier->account, &mode);
520         if ( (mode != ONLINE_MODE) ||
521                 hier->type == EXCHANGE_HIERARCHY_PUBLIC)
522                 return;
523
524         hrefs = g_ptr_array_new ();
525         g_hash_table_foreach (hwd->priv->folders_by_internal_path,
526                               add_href, hrefs);
527         if (!hrefs->len) {
528                 g_ptr_array_free (hrefs, TRUE);
529                 return;
530         }
531
532         g_object_ref (hier);
533         iter = e_folder_exchange_bpropfind_start (hier->toplevel, NULL,
534                                                   (const char **)hrefs->pdata,
535                                                   hrefs->len,
536                                                   rescan_props, n_rescan_props);
537         g_ptr_array_free (hrefs, TRUE);
538
539         while ((result = e2k_result_iter_next (iter))) {
540                 folder = g_hash_table_lookup (hwd->priv->folders_by_internal_path,
541                                               e2k_uri_path (result->href));
542                 if (!folder)
543                         continue;
544
545                 prop = e2k_properties_get_prop (result->props,
546                                                 E2K_PR_HTTPMAIL_UNREAD_COUNT);
547                 if (!prop)
548                         continue;
549                 unread = atoi (prop);
550
551                 if (unread != e_folder_get_unread_count (folder))
552                         e_folder_set_unread_count (folder, unread);
553
554                 folder_size = e2k_properties_get_prop (result->props,
555                                 E2K_PR_EXCHANGE_FOLDER_SIZE);
556
557                 if (folder_size) {
558                         folder_name = e_folder_get_name (folder);
559                         fsize_d = g_ascii_strtod (folder_size, NULL)/1024;
560                         exchange_account_folder_size_add (hier->account, 
561                                                         folder_name, fsize_d);
562                         if (personal)
563                                 hwd->priv->total_folder_size = 
564                                         hwd->priv->total_folder_size + fsize_d;
565                 }
566         }
567         e2k_result_iter_free (iter);
568         g_object_unref (hier);
569 }
570
571 ExchangeAccountFolderResult
572 exchange_hierarchy_webdav_status_to_folder_result (E2kHTTPStatus status)
573 {
574         if (E2K_HTTP_STATUS_IS_SUCCESSFUL (status))
575                 return EXCHANGE_ACCOUNT_FOLDER_OK;
576         else if (status == E2K_HTTP_NOT_FOUND)
577                 return EXCHANGE_ACCOUNT_FOLDER_DOES_NOT_EXIST;
578         else if (status == E2K_HTTP_UNAUTHORIZED)
579                 return EXCHANGE_ACCOUNT_FOLDER_PERMISSION_DENIED;
580         else
581                 return EXCHANGE_ACCOUNT_FOLDER_GENERIC_ERROR;
582 }
583
584 gdouble
585 exchange_hierarchy_webdav_get_total_folder_size (ExchangeHierarchyWebDAV *hwd)
586 {
587         g_return_val_if_fail (EXCHANGE_IS_HIERARCHY_WEBDAV (hwd), -1);
588
589         return hwd->priv->total_folder_size;
590 }
591
592 EFolder *
593 exchange_hierarchy_webdav_parse_folder (ExchangeHierarchyWebDAV *hwd,
594                                         EFolder *parent,
595                                         E2kResult *result)
596 {
597         EFolder *folder;
598         ExchangeFolderType *folder_type;
599         const char *name, *prop, *outlook_class, *permanenturl;
600         int unread;
601         gboolean hassubs;
602
603         g_return_val_if_fail (EXCHANGE_IS_HIERARCHY_WEBDAV (hwd), NULL);
604         g_return_val_if_fail (E_IS_FOLDER (parent), NULL);
605         
606         /* It's possible to have a localized inbox folder named, eg,
607          * "Innboks", with children whose URIs go through "Inbox"
608          * instead. (See bugzilla 27065.) This is probably related to
609          * the IMAP "INBOX" convention. Anyway, the important bit is
610          * that you can't know a folder's parent URI just by looking
611          * at its own URI. Since we only ever scan one folder at a
612          * time here, we just keep track of what the parent was. If we
613          * were going to read multiple folders at once, we could deal
614          * with this by fetching DAV:parentname.
615          */
616
617         name = e2k_properties_get_prop (result->props,
618                                         E2K_PR_DAV_DISPLAY_NAME);
619         if (!name)
620                 return NULL;
621
622         prop = e2k_properties_get_prop (result->props,
623                                         E2K_PR_HTTPMAIL_UNREAD_COUNT);
624         unread = prop ? atoi (prop) : 0;
625         prop = e2k_properties_get_prop (result->props,
626                                         E2K_PR_DAV_HAS_SUBS);
627         hassubs = prop && atoi (prop);
628
629         outlook_class = e2k_properties_get_prop (result->props,
630                                                  E2K_PR_EXCHANGE_FOLDER_CLASS);
631         folder_type = NULL;
632         if (outlook_class)
633                 folder_type = g_hash_table_lookup (folder_type_map, outlook_class);
634         if (!folder_type)
635                 folder_type = &folder_types[0]; /* mail */
636         if (!outlook_class)
637                 outlook_class = folder_type->contentclass;
638
639         /*
640          * The permanenturl Field provides a unique identifier for an item 
641          * across the *store* and will not change as long as the item remains 
642          * in the same folder. The permanenturl Field contains the ID of the 
643          * parent folder of the item, which changes when the item is moved to a 
644          * different folder or deleted. Changing a field on an item will not 
645          * change the permanenturl Field and neither will adding more items to
646          * the folder with the same display name or message subject.
647          */
648         permanenturl = e2k_properties_get_prop (result->props,
649                                                 E2K_PR_EXCHANGE_PERMANENTURL);
650         /* Check for errors */
651
652         folder = e_folder_webdav_new (EXCHANGE_HIERARCHY (hwd),
653                                       result->href, parent,
654                                       name, folder_type->component,
655                                       outlook_class, unread,
656                                       folder_type->offline_supported);
657         if (hwd->priv->trash_path && !strcmp (e2k_uri_path (result->href), hwd->priv->trash_path))
658                 e_folder_set_custom_icon (folder, "stock_delete");
659         if (hassubs)
660                 e_folder_exchange_set_has_subfolders (folder, TRUE);
661         if (permanenturl) {
662                 /* Favorite folders and subscribed folders will not have 
663                  * permanenturl 
664                  */
665                 e_folder_exchange_set_permanent_uri (folder, permanenturl);
666         }
667
668         return folder;
669 }
670
671 static void
672 add_folders (ExchangeHierarchy *hier, EFolder *folder, gpointer folders)
673 {
674         g_object_ref (folder);
675         g_ptr_array_add (folders, folder);
676 }
677
678 static const char *folder_props[] = {
679         E2K_PR_EXCHANGE_FOLDER_CLASS,
680         E2K_PR_HTTPMAIL_UNREAD_COUNT,
681         E2K_PR_DAV_DISPLAY_NAME,
682         E2K_PR_EXCHANGE_PERMANENTURL,
683         E2K_PR_EXCHANGE_FOLDER_SIZE,
684         E2K_PR_DAV_HAS_SUBS
685 };
686 static const int n_folder_props = sizeof (folder_props) / sizeof (folder_props[0]);
687
688 static const char *pub_folder_props[] = {
689         E2K_PR_DAV_DISPLAY_NAME,
690         E2K_PR_DAV_HAS_SUBS
691 };
692 static const int n_pub_folder_props = sizeof (pub_folder_props) / sizeof (pub_folder_props[0]);
693
694 static ExchangeAccountFolderResult
695 scan_subtree (ExchangeHierarchy *hier, EFolder *parent, int mode)
696 {
697         static E2kRestriction *folders_rn;
698         ExchangeHierarchyWebDAV *hwd = EXCHANGE_HIERARCHY_WEBDAV (hier);
699         GSList *subtrees = NULL;
700         E2kResultIter *iter;
701         E2kResult *result;
702         E2kHTTPStatus status;
703         EFolder *folder, *tmp;
704         GPtrArray *folders;
705         int i;
706         gdouble fsize_d;
707         const char *name, *folder_size, *deleted_items_uri, *int_uri;
708         gboolean personal = ( EXCHANGE_HIERARCHY (hwd)->type == EXCHANGE_HIERARCHY_PERSONAL );
709
710         if (parent) {
711                 if (!e_folder_exchange_get_rescan_tree (parent)) {
712                         d(g_print ("%s(%d):%s: Donot RESCAN [%s] \n", __FILE__, __LINE__, __PRETTY_FUNCTION__,
713                                    e_folder_get_name (parent)));
714                         return EXCHANGE_ACCOUNT_FOLDER_OK;
715                 }
716         }
717
718         if (mode == OFFLINE_MODE) {
719                 folders = g_ptr_array_new ();
720                 exchange_hierarchy_webdav_offline_scan_subtree (EXCHANGE_HIERARCHY (hier), add_folders, folders);
721                 for (i = 0; i <folders->len; i++) {
722                         tmp = (EFolder *)folders->pdata[i];
723                         exchange_hierarchy_new_folder (hier, (EFolder *)folders->pdata[i]);
724                 }
725                 return EXCHANGE_ACCOUNT_FOLDER_OK;
726         }
727
728         
729         if (!folders_rn) {
730                 folders_rn =
731                         e2k_restriction_andv (
732                                 e2k_restriction_prop_bool (E2K_PR_DAV_IS_COLLECTION,
733                                                            E2K_RELOP_EQ, TRUE),
734                                 e2k_restriction_prop_bool (E2K_PR_DAV_IS_HIDDEN,
735                                                            E2K_RELOP_EQ, FALSE),
736                                 NULL);
737         }
738
739         if (hier->type == EXCHANGE_HIERARCHY_PUBLIC) 
740                 iter = e_folder_exchange_search_start (parent, NULL,
741                                                        pub_folder_props, 
742                                                        n_pub_folder_props,
743                                                        folders_rn, NULL, TRUE);
744         else
745                 iter = e_folder_exchange_search_start (parent, NULL,
746                                                        folder_props, n_folder_props,
747                                                        folders_rn, NULL, TRUE);
748
749         while ((result = e2k_result_iter_next (iter))) {
750                 folder = exchange_hierarchy_webdav_parse_folder (hwd, parent, result);
751                 if (!folder)
752                         continue;
753
754                 if (hwd->priv->deep_searchable &&
755                     e_folder_exchange_get_has_subfolders (folder)) {
756                         e_folder_exchange_set_has_subfolders (folder, FALSE);
757                         subtrees = g_slist_prepend (subtrees, folder);
758                 }
759                 exchange_hierarchy_new_folder (hier, folder);
760                 //g_object_unref (folder);
761
762                 /* Check the folder size here */
763                 if (hier->type != EXCHANGE_HIERARCHY_PUBLIC) {
764                         name = e2k_properties_get_prop (result->props,
765                                                         E2K_PR_DAV_DISPLAY_NAME);
766                         folder_size = e2k_properties_get_prop (result->props,
767                                                                E2K_PR_EXCHANGE_FOLDER_SIZE);
768
769                         /* FIXME : Find a better way of doing this */
770                         fsize_d = g_ascii_strtod (folder_size, NULL)/1024 ;
771                         exchange_account_folder_size_add (hier->account, name, fsize_d);
772                 }
773
774                 if (personal) {
775                         /* calculate mail box size only for personal folders */
776                         hwd->priv->total_folder_size = 
777                                 hwd->priv->total_folder_size + fsize_d;
778                 }
779
780         }
781
782         status = e2k_result_iter_free (iter);
783
784         deleted_items_uri = exchange_account_get_standard_uri (hier->account, "deleteditems");
785
786         while (subtrees) {
787                 folder = subtrees->data;
788                 subtrees = g_slist_remove (subtrees, folder);
789                 /* Dont scan the subtree for deleteditems folder */
790                 int_uri = e_folder_exchange_get_internal_uri (folder);
791                 if (int_uri && !strcmp (int_uri, deleted_items_uri))
792                         continue;
793                 scan_subtree (hier, folder, mode);
794         }
795
796         e_folder_exchange_set_rescan_tree (parent, FALSE);
797
798         return exchange_hierarchy_webdav_status_to_folder_result (status);
799 }
800
801 struct scan_offline_data {
802         ExchangeHierarchy *hier;
803         ExchangeHierarchyWebDAVScanCallback callback;
804         gpointer user_data;
805         GPtrArray *badpaths;
806 };
807
808 static gboolean
809 scan_offline_cb (const char *physical_path, const char *path, gpointer data)
810 {
811         struct scan_offline_data *sod = data;
812         EFolder *folder;
813         char *mf_name;
814         
815
816         mf_name = g_build_filename (physical_path, "connector-metadata.xml", NULL);
817         folder = e_folder_exchange_new_from_file (sod->hier, mf_name);
818         if (!folder) {
819                 g_unlink (mf_name);
820                 g_free (mf_name);
821                 if (!sod->badpaths)
822                         sod->badpaths = g_ptr_array_new ();
823                 g_ptr_array_add (sod->badpaths, g_strdup (path));
824                 return TRUE;
825         }
826         g_free (mf_name);
827
828         sod->callback (sod->hier, folder, sod->user_data);
829         g_object_unref (folder);
830
831         return TRUE;
832 }
833
834 /**
835  * exchange_hierarchy_webdav_offline_scan_subtree:
836  * @hier: a (webdav) hierarchy
837  * @callbackb: a callback
838  * @user_data: data for @cb
839  *
840  * Scans the offline folder tree cache for @hier and calls @cb
841  * with each folder successfully constructed from offline data
842  **/
843 void
844 exchange_hierarchy_webdav_offline_scan_subtree (ExchangeHierarchy *hier,
845                                                 ExchangeHierarchyWebDAVScanCallback callback,
846                                                 gpointer user_data)
847 {
848         struct scan_offline_data sod;
849         const char *path;
850         char *dir, *prefix;
851         int i;
852
853         g_return_if_fail (EXCHANGE_IS_HIERARCHY (hier));
854
855         sod.hier = hier;
856         sod.callback = callback;
857         sod.user_data = user_data;
858         sod.badpaths = NULL;
859
860         path = e_folder_exchange_get_path (hier->toplevel);
861         prefix = e2k_strdup_with_trailing_slash (path);
862         dir = e_path_to_physical (hier->account->storage_dir, prefix);
863         g_free (prefix);
864         e_path_find_folders (dir, scan_offline_cb, &sod);
865         
866         if (sod.badpaths) {
867                 for (i = 0; i < sod.badpaths->len; i++) {
868                         e_path_rmdir (dir, sod.badpaths->pdata[i]);
869                         g_free (sod.badpaths->pdata[i]);
870                 }
871                 g_ptr_array_free (sod.badpaths, TRUE);
872         }
873
874         g_free (dir);
875 }
876
877 void
878 exchange_hierarchy_webdav_construct (ExchangeHierarchyWebDAV *hwd,
879                                      ExchangeAccount *account,
880                                      ExchangeHierarchyType type,
881                                      const char *hierarchy_name,
882                                      const char *physical_uri_prefix,
883                                      const char *internal_uri_prefix,
884                                      const char *owner_name,
885                                      const char *owner_email,
886                                      const char *source_uri,
887                                      gboolean deep_searchable)
888 {
889         EFolder *toplevel;
890
891         g_return_if_fail (EXCHANGE_IS_HIERARCHY_WEBDAV (hwd));
892         g_return_if_fail (EXCHANGE_IS_ACCOUNT (account));
893
894         hwd->priv->deep_searchable = deep_searchable;
895
896         toplevel = e_folder_exchange_new (EXCHANGE_HIERARCHY (hwd),
897                                           hierarchy_name,
898                                           "noselect", NULL, 
899                                           physical_uri_prefix,
900                                           internal_uri_prefix);
901         e_folder_set_custom_icon (toplevel, "stock_folder");
902         e_folder_exchange_set_has_subfolders (toplevel, TRUE);
903         exchange_hierarchy_construct (EXCHANGE_HIERARCHY (hwd),
904                                       account, type, toplevel,
905                                       owner_name, owner_email, source_uri);
906         g_object_unref (toplevel);
907
908         if (type == EXCHANGE_HIERARCHY_PERSONAL) {
909                 const char *trash_uri;
910
911                 trash_uri = exchange_account_get_standard_uri (account, "deleteditems");
912                 if (trash_uri)
913                         hwd->priv->trash_path = e2k_strdup_with_trailing_slash (e2k_uri_path (trash_uri));
914         }
915 }
916
917 ExchangeHierarchy *
918 exchange_hierarchy_webdav_new (ExchangeAccount *account,
919                                ExchangeHierarchyType type,
920                                const char *hierarchy_name,
921                                const char *physical_uri_prefix,
922                                const char *internal_uri_prefix,
923                                const char *owner_name,
924                                const char *owner_email,
925                                const char *source_uri,
926                                gboolean deep_searchable)
927 {
928         ExchangeHierarchy *hier;
929
930         g_return_val_if_fail (EXCHANGE_IS_ACCOUNT (account), NULL);
931
932         hier = g_object_new (EXCHANGE_TYPE_HIERARCHY_WEBDAV, NULL);
933
934         exchange_hierarchy_webdav_construct (EXCHANGE_HIERARCHY_WEBDAV (hier),
935                                              account, type, hierarchy_name,
936                                              physical_uri_prefix,
937                                              internal_uri_prefix,
938                                              owner_name, owner_email,
939                                              source_uri, deep_searchable);
940         return hier;
941 }