10d1f535f294e3be6497b71f726f70bf16109f17
[platform/upstream/evolution-data-server.git] / services / evolution-source-registry / evolution-source-registry-migrate-sources.c
1 /*
2  * migrate-from-gconf.c
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) version 3.
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  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with the program; if not, see <http://www.gnu.org/licenses/>
16  *
17  */
18
19 #include <errno.h>
20 #include <string.h>
21 #include <glib/gstdio.h>
22 #include <camel/camel.h>
23 #include <libsoup/soup.h>
24
25 /* XXX Yeah, yeah... */
26 #define SECRET_API_SUBJECT_TO_CHANGE
27
28 #include <libsecret/secret.h>
29
30 #include <libebackend/libebackend.h>
31
32 /* These constants are collected from various e-source-*.h files
33  * throughout evolution-data-server and known extension packages. */
34 #define E_SOURCE_GROUP_NAME                     "Data Source"
35 #define E_SOURCE_EXTENSION_CONTACTS_BACKEND     "Contacts Backend"
36 #define E_SOURCE_EXTENSION_LDAP_BACKEND         "LDAP Backend"
37 #define E_SOURCE_EXTENSION_LOCAL_BACKEND        "Local Backend"
38 #define E_SOURCE_EXTENSION_VCF_BACKEND          "VCF Backend"
39 #define E_SOURCE_EXTENSION_WEATHER_BACKEND      "Weather Backend"
40 #define E_SOURCE_EXTENSION_WEBDAV_BACKEND       "WebDAV Backend"
41
42 /* These constants are copied from e-source-password.c. */
43 #define KEYRING_ITEM_ATTRIBUTE_NAME             "e-source-uid"
44 #define KEYRING_ITEM_DISPLAY_FORMAT             "Evolution Data Source %s"
45
46 typedef struct _ParseData ParseData;
47
48 typedef void            (*PropertyFunc)         (ParseData *parse_data,
49                                                  const gchar *property_name,
50                                                  const gchar *property_value);
51
52 typedef enum {
53         PARSE_TYPE_MAIL,
54         PARSE_TYPE_ADDRESSBOOK,
55         PARSE_TYPE_CALENDAR,
56         PARSE_TYPE_TASKS,
57         PARSE_TYPE_MEMOS
58 } ParseType;
59
60 typedef enum {
61         PARSE_STATE_INITIAL,
62
63         PARSE_STATE_IN_GCONF,                   /* GConf XML */
64         PARSE_STATE_IN_ACCOUNTS_ENTRY,          /* GConf XML */
65         PARSE_STATE_IN_ACCOUNTS_VALUE,          /* GConf XML */
66         PARSE_STATE_IN_SIGNATURES_ENTRY,        /* GConf XML */
67         PARSE_STATE_IN_SIGNATURES_VALUE,        /* GConf XML */
68         PARSE_STATE_IN_SOURCES_ENTRY,           /* GConf XML */
69         PARSE_STATE_IN_SOURCES_VALUE,           /* GConf XML */
70
71         PARSE_STATE_IN_ACCOUNT,                 /* EAccount XML */
72         PARSE_STATE_IN_IDENTITY,                /* EAccount XML */
73         PARSE_STATE_IN_IDENTITY_NAME,           /* EAccount XML */
74         PARSE_STATE_IN_IDENTITY_ADDR_SPEC,      /* EAccount XML */
75         PARSE_STATE_IN_IDENTITY_REPLY_TO,       /* EAccount XML */
76         PARSE_STATE_IN_IDENTITY_ORGANIZATION,   /* EAccount XML */
77         PARSE_STATE_IN_IDENTITY_SIGNATURE,      /* EAccount XML */
78         PARSE_STATE_IN_MAIL_SOURCE,             /* EAccount XML */
79         PARSE_STATE_IN_MAIL_SOURCE_URL,         /* EAccount XML */
80         PARSE_STATE_IN_MAIL_TRANSPORT,          /* EAccount XML */
81         PARSE_STATE_IN_MAIL_TRANSPORT_URL,      /* EAccount XML */
82         PARSE_STATE_IN_AUTO_CC,                 /* EAccount XML */
83         PARSE_STATE_IN_AUTO_CC_RECIPIENTS,      /* EAccount XML */
84         PARSE_STATE_IN_AUTO_BCC,                /* EAccount XML */
85         PARSE_STATE_IN_AUTO_BCC_RECIPIENTS,     /* EAccount XML */
86         PARSE_STATE_IN_DRAFTS_FOLDER,           /* EAccount XML */
87         PARSE_STATE_IN_SENT_FOLDER,             /* EAccount XML */
88         PARSE_STATE_IN_RECEIPT_POLICY,          /* EAccount XML */
89         PARSE_STATE_IN_PGP,                     /* EAccount XML */
90         PARSE_STATE_IN_PGP_KEY_ID,              /* EAccount XML */
91         PARSE_STATE_IN_SMIME,                   /* EAccount XML */
92         PARSE_STATE_IN_SMIME_SIGN_KEY_ID,       /* EAccount XML */
93         PARSE_STATE_IN_SMIME_ENCRYPT_KEY_ID,    /* EAccount XML */
94
95         PARSE_STATE_IN_SIGNATURE,               /* ESignature XML */
96         PARSE_STATE_IN_FILENAME,                /* ESignature XML */
97
98         PARSE_STATE_IN_GROUP,                   /* ESource XML */
99         PARSE_STATE_IN_SOURCE,                  /* ESource XML */
100         PARSE_STATE_IN_PROPERTIES               /* ESource XML */
101 } ParseState;
102
103 struct _ParseData {
104         ParseType type;
105         ParseState state;
106
107         /* Whether to skip writing a file
108          * for this account information. */
109         gboolean skip;
110
111         /* Set by <account>, <source> and <signature> tags. */
112         GFile *file;
113         GKeyFile *key_file;
114
115         /* Set by <account>/<source> tags. */
116         gboolean auto_bcc;
117         gboolean auto_cc;
118
119         /* Set by <identity> tags. */
120         GFile *identity_file;
121         GKeyFile *identity_key_file;
122
123         /* Set by <transport> tags. */
124         GFile *transport_file;
125         GKeyFile *transport_key_file;
126
127         /* Set by <account> tags. */
128         GFile *collection_file;
129         GKeyFile *collection_key_file;
130
131         /* Set by <signature> tags. */
132         GFile *signature_file;
133         gboolean is_script;
134
135         /* Set by <group> tags. */
136         gchar *base_uri;
137
138         /* Set by <source> tags. */
139         gchar *mangled_uri;
140         SoupURI *soup_uri;
141         PropertyFunc property_func;
142 };
143
144 /* XXX Probably want to share this with module-online-accounts.c */
145 static const SecretSchema schema = {
146         "org.gnome.Evolution.DataSource",
147         SECRET_SCHEMA_DONT_MATCH_NAME,
148         {
149                 { KEYRING_ITEM_ATTRIBUTE_NAME,
150                   SECRET_SCHEMA_ATTRIBUTE_STRING },
151                 { NULL, 0 }
152         }
153 };
154
155 /* XXX Probably want to share this with e-passwords.c */
156 static const SecretSchema e_passwords_schema = {
157         "org.gnome.Evolution.Password",
158         SECRET_SCHEMA_DONT_MATCH_NAME,
159         {
160                 { "application", SECRET_SCHEMA_ATTRIBUTE_STRING, },
161                 { "user", SECRET_SCHEMA_ATTRIBUTE_STRING, },
162                 { "server", SECRET_SCHEMA_ATTRIBUTE_STRING, },
163                 { "protocol", SECRET_SCHEMA_ATTRIBUTE_STRING, },
164         }
165 };
166
167 /* Forward Declarations */
168 void evolution_source_registry_migrate_sources (void);
169
170 static ParseData *
171 parse_data_new (ParseType parse_type)
172 {
173         ParseData *parse_data;
174
175         parse_data = g_slice_new0 (ParseData);
176         parse_data->type = parse_type;
177         parse_data->state = PARSE_STATE_INITIAL;
178
179         return parse_data;
180 }
181
182 static void
183 parse_data_free (ParseData *parse_data)
184 {
185         /* Normally the allocated data in ParseData is freed and the
186          * pointers are cleared before we get here.  But if an error
187          * occurred we may leave data behind.  This cleans it up. */
188
189         if (parse_data->file != NULL)
190                 g_object_unref (parse_data->file);
191
192         if (parse_data->key_file != NULL)
193                 g_key_file_free (parse_data->key_file);
194
195         if (parse_data->identity_file != NULL)
196                 g_object_unref (parse_data->identity_file);
197
198         if (parse_data->identity_key_file != NULL)
199                 g_key_file_free (parse_data->identity_key_file);
200
201         if (parse_data->transport_file != NULL)
202                 g_object_unref (parse_data->transport_file);
203
204         if (parse_data->transport_key_file != NULL)
205                 g_key_file_free (parse_data->transport_key_file);
206
207         if (parse_data->collection_file != NULL)
208                 g_object_unref (parse_data->collection_file);
209
210         if (parse_data->collection_key_file != NULL)
211                 g_key_file_free (parse_data->collection_key_file);
212
213         if (parse_data->signature_file != NULL)
214                 g_object_unref (parse_data->signature_file);
215
216         g_free (parse_data->base_uri);
217         g_free (parse_data->mangled_uri);
218
219         if (parse_data->soup_uri != NULL)
220                 soup_uri_free (parse_data->soup_uri);
221
222         g_slice_free (ParseData, parse_data);
223 }
224
225 static gboolean
226 is_true (const gchar *string)
227 {
228         return  (g_ascii_strcasecmp (string, "1") == 0) ||
229                 (g_ascii_strcasecmp (string, "true") == 0);
230 }
231
232 static gboolean
233 is_false (const gchar *string)
234 {
235         return  (g_ascii_strcasecmp (string, "0") == 0) ||
236                 (g_ascii_strcasecmp (string, "false") == 0);
237 }
238
239 static gboolean
240 base_uri_is_groupware (const gchar *base_uri)
241 {
242         /* Well-known scheme names from various groupware packages. */
243
244         /* We use a limited string comparsion here because the
245          * base_uri string may be 'scheme://' or just 'scheme'. */
246
247         g_return_val_if_fail (base_uri != NULL, FALSE);
248
249         if (g_ascii_strncasecmp (base_uri, "ews", 3) == 0)
250                 return TRUE;
251
252         if (g_ascii_strncasecmp (base_uri, "exchange", 8) == 0)
253                 return TRUE;
254
255         if (g_ascii_strncasecmp (base_uri, "groupwise", 9) == 0)
256                 return TRUE;
257
258         if (g_ascii_strncasecmp (base_uri, "kolab", 5) == 0)
259                 return TRUE;
260
261         if (g_ascii_strncasecmp (base_uri, "mapi", 4) == 0)
262                 return TRUE;
263
264         return FALSE;
265 }
266
267 static void
268 migrate_keyring_entry (const gchar *uid,
269                        const gchar *user,
270                        const gchar *server,
271                        const gchar *protocol)
272 {
273         GHashTable *attributes;
274         GList *found_list = NULL;
275         gchar *display_name;
276
277         /* Don't migrate entries with empty attributes */
278         if (!user || !server || !protocol) {
279               return;
280         }
281
282         /* This is a best-effort routine, so we don't really care about
283          * errors.  We leave the old keyring entry in place since it may
284          * be reused for address book or calendar migration. */
285
286         display_name = g_strdup_printf (KEYRING_ITEM_DISPLAY_FORMAT, uid);
287
288         attributes = secret_attributes_build (
289                 &e_passwords_schema,
290                 "application", "Evolution",
291                 "user", user,
292                 "server", server,
293                 "protocol", protocol,
294                 NULL);
295
296         found_list = secret_service_search_sync (
297                 NULL, &e_passwords_schema, attributes,
298                 SECRET_SEARCH_ALL |
299                 SECRET_SEARCH_UNLOCK |
300                 SECRET_SEARCH_LOAD_SECRETS,
301                 NULL, NULL);
302
303         /* Pick the first match we find. */
304         if (found_list != NULL) {
305                 SecretItem *item = found_list->data;
306                 SecretValue *secret = secret_item_get_secret (item);
307
308                 /* Sanity check. */
309                 g_return_if_fail (secret != NULL);
310
311                 secret_password_store_sync (
312                         &schema, SECRET_COLLECTION_DEFAULT, display_name,
313                         secret_value_get (secret, NULL), NULL, NULL,
314                         KEYRING_ITEM_ATTRIBUTE_NAME, uid, NULL);
315
316                 secret_value_unref (secret);
317         }
318
319         g_list_free_full (found_list, g_object_unref);
320         g_hash_table_unref (attributes);
321
322         g_free (display_name);
323 }
324
325 static gboolean
326 migrate_parse_commit_changes (ParseType parse_type,
327                               GFile *file,
328                               GKeyFile *key_file,
329                               const gchar *mangled_uri,
330                               GError **error)
331 {
332         const gchar *data_dir;
333         const gchar *cache_dir;
334         const gchar *component;
335         gchar *old_directory;
336         gchar *new_directory;
337         gchar *contents;
338         gchar *uid;
339         gsize length;
340         gboolean success;
341         gboolean old_directory_exists;
342         gboolean new_directory_exists;
343
344         data_dir = e_get_user_data_dir ();
345         cache_dir = e_get_user_cache_dir ();
346
347         uid = e_server_side_source_uid_from_file (file, error);
348
349         if (uid == NULL)
350                 return FALSE;
351
352         g_print ("  * Source: %s\n", uid);
353
354         g_print ("    Writing key file...\n");
355
356         /* Save the key file contents to disk. */
357         contents = g_key_file_to_data (key_file, &length, NULL);
358         success = g_file_replace_contents (
359                 file, contents, length, NULL, FALSE,
360                 G_FILE_CREATE_PRIVATE, NULL, NULL, error);
361         g_free (contents);
362
363         if (!success)
364                 goto exit;
365
366         /* Rename the source's local cache directory from its mangled
367          * URI to its UID.  The key file's basename contains the UID.
368          * All source types but "local" should have cache directories. */
369
370         /* Mail cache directories already use UIDs. */
371         switch (parse_type) {
372                 case PARSE_TYPE_ADDRESSBOOK:
373                         component = "addressbook";
374                         break;
375                 case PARSE_TYPE_CALENDAR:
376                         component = "calendar";
377                         break;
378                 case PARSE_TYPE_TASKS:
379                         component = "tasks";
380                         break;
381                 case PARSE_TYPE_MEMOS:
382                         component = "memos";
383                         break;
384                 default:
385                         goto exit;
386         }
387
388         g_assert (mangled_uri != NULL);
389
390         old_directory = g_build_filename (
391                 cache_dir, component, mangled_uri, NULL);
392
393         new_directory = g_build_filename (
394                 cache_dir, component, uid, NULL);
395
396         old_directory_exists = g_file_test (old_directory, G_FILE_TEST_EXISTS);
397         new_directory_exists = g_file_test (new_directory, G_FILE_TEST_EXISTS);
398
399         g_print (
400                 "    Checking for old cache dir '%s'... %s\n",
401                 old_directory,
402                 old_directory_exists ?  "found" : "not found");
403
404         if (old_directory_exists) {
405                 g_print (
406                         "    Checking for new cache dir '%s'... %s\n",
407                         new_directory,
408                         new_directory_exists ? "found" : "not found");
409
410                 if (new_directory_exists)
411                         g_print ("    Skipping cache directory rename.\n");
412                 else {
413                         g_print ("    Renaming old cache directory...\n");
414                         if (g_rename (old_directory, new_directory) < 0) {
415                                 g_set_error (
416                                         error, G_FILE_ERROR,
417                                         g_file_error_from_errno (errno),
418                                         "%s", g_strerror (errno));
419                                 success = FALSE;
420                         }
421                 }
422         }
423
424         g_free (old_directory);
425         g_free (new_directory);
426
427         if (!success)
428                 goto exit;
429
430         /* Rename the source's local data directory from its mangled
431          * URI to its UID.  The key file's basename contains the UID.
432          * Only "local" sources have local data directores. */
433
434         old_directory = g_build_filename (
435                 data_dir, component, mangled_uri, NULL);
436
437         new_directory = g_build_filename (
438                 data_dir, component, uid, NULL);
439
440         old_directory_exists = g_file_test (old_directory, G_FILE_TEST_EXISTS);
441         new_directory_exists = g_file_test (new_directory, G_FILE_TEST_EXISTS);
442
443         g_print (
444                 "    Checking for old data dir '%s'... %s\n",
445                 old_directory,
446                 old_directory_exists ?  "found" : "not found");
447
448         if (old_directory_exists) {
449                 g_print (
450                         "    Checking for new data dir '%s'... %s\n",
451                         new_directory,
452                         new_directory_exists ? "found" : "not found");
453
454                 if (new_directory_exists)
455                         g_print ("    Skipping data directory rename.\n");
456                 else {
457                         g_print ("    Renaming old data directory...\n");
458                         if (g_rename (old_directory, new_directory) < 0) {
459                                 g_set_error (
460                                         error, G_FILE_ERROR,
461                                         g_file_error_from_errno (errno),
462                                         "%s", g_strerror (errno));
463                                 success = FALSE;
464                         }
465                 }
466         }
467
468         g_free (old_directory);
469         g_free (new_directory);
470
471 exit:
472         g_free (uid);
473
474         return success;
475 }
476
477 static void
478 migrate_setup_collection (ParseData *parse_data,
479                           CamelURL *url)
480 {
481         gchar *collection_uid;
482         gchar *display_name;
483         gboolean enabled;
484
485         g_return_if_fail (parse_data->key_file != NULL);
486         g_return_if_fail (parse_data->identity_key_file != NULL);
487         g_return_if_fail (parse_data->transport_key_file != NULL);
488
489         parse_data->collection_file = e_server_side_source_new_user_file (NULL);
490         parse_data->collection_key_file = g_key_file_new ();
491
492         collection_uid = e_server_side_source_uid_from_file (
493                 parse_data->collection_file, NULL);
494
495         /* Copy the display name from the mail account source. */
496
497         display_name = g_key_file_get_string (
498                 parse_data->key_file,
499                 E_SOURCE_GROUP_NAME, "DisplayName", NULL);
500
501         g_key_file_set_string (
502                 parse_data->collection_key_file,
503                 E_SOURCE_GROUP_NAME, "DisplayName", display_name);
504
505         /* Copy the enabled state from the mail account source. */
506
507         enabled = g_key_file_get_boolean (
508                 parse_data->key_file,
509                 E_SOURCE_GROUP_NAME, "Enabled", NULL);
510
511         g_key_file_set_boolean (
512                 parse_data->collection_key_file,
513                 E_SOURCE_GROUP_NAME, "Enabled", enabled);
514
515         /* Collection sources are always top-level sources. */
516
517         g_key_file_set_string (
518                 parse_data->collection_key_file,
519                 E_SOURCE_GROUP_NAME, "Parent", "");
520
521         /* Collection backend name should match the CamelURL protocol. */
522
523         g_key_file_set_string (
524                 parse_data->collection_key_file,
525                 E_SOURCE_EXTENSION_COLLECTION,
526                 "BackendName", url->protocol);
527
528         g_key_file_set_boolean (
529                 parse_data->collection_key_file,
530                 E_SOURCE_EXTENSION_COLLECTION,
531                 "CalendarEnabled", TRUE);
532
533         g_key_file_set_boolean (
534                 parse_data->collection_key_file,
535                 E_SOURCE_EXTENSION_COLLECTION,
536                 "ContactsEnabled", TRUE);
537
538         g_key_file_set_boolean (
539                 parse_data->collection_key_file,
540                 E_SOURCE_EXTENSION_COLLECTION,
541                 "MailEnabled", TRUE);
542
543         /* Enable all mail sources since we set "MailEnabled=true" above. */
544
545         g_key_file_set_boolean (
546                 parse_data->key_file,
547                 E_SOURCE_GROUP_NAME, "Enabled", TRUE);
548
549         g_key_file_set_boolean (
550                 parse_data->identity_key_file,
551                 E_SOURCE_GROUP_NAME, "Enabled", TRUE);
552
553         g_key_file_set_boolean (
554                 parse_data->transport_key_file,
555                 E_SOURCE_GROUP_NAME, "Enabled", TRUE);
556
557         /* The other mail sources are children of the collection source. */
558
559         g_key_file_set_string (
560                 parse_data->key_file,
561                 E_SOURCE_GROUP_NAME,
562                 "Parent", collection_uid);
563
564         g_key_file_set_string (
565                 parse_data->identity_key_file,
566                 E_SOURCE_GROUP_NAME,
567                 "Parent", collection_uid);
568
569         g_key_file_set_string (
570                 parse_data->transport_key_file,
571                 E_SOURCE_GROUP_NAME,
572                 "Parent", collection_uid);
573
574         /* The collection identity has to be determined case-by-case.
575          * Some are based on user name, some are based on email address. */
576
577         if (g_strcmp0 (url->protocol, "ews") == 0)
578                 g_key_file_set_string (
579                         parse_data->collection_key_file,
580                         E_SOURCE_EXTENSION_COLLECTION,
581                         "Identity", url->user);
582
583         g_free (collection_uid);
584         g_free (display_name);
585 }
586
587 static void
588 migrate_parse_account (ParseData *parse_data,
589                        const gchar *element_name,
590                        const gchar **attribute_names,
591                        const gchar **attribute_values,
592                        GError **error)
593 {
594         const gchar *uid;
595         const gchar *name;
596         gchar *identity_uid;
597         gchar *transport_uid;
598         gboolean enabled;
599         gboolean success;
600
601         success = g_markup_collect_attributes (
602                 element_name,
603                 attribute_names,
604                 attribute_values,
605                 error,
606                 G_MARKUP_COLLECT_STRING,
607                 "uid", &uid,
608                 G_MARKUP_COLLECT_STRING,
609                 "name", &name,
610                 G_MARKUP_COLLECT_BOOLEAN,
611                 "enabled", &enabled,
612                 G_MARKUP_COLLECT_INVALID);
613
614         if (!success)
615                 return;
616
617         parse_data->file = e_server_side_source_new_user_file (uid);
618
619         /* If the file already exists, skip this source.  It may be that we
620          * already migrated it, in which case we don't want to overwrite it. */
621         if (g_file_query_exists (parse_data->file, NULL))
622                 return;
623
624         parse_data->key_file = g_key_file_new ();
625
626         parse_data->identity_file = e_server_side_source_new_user_file (NULL);
627         parse_data->identity_key_file = g_key_file_new ();
628
629         parse_data->transport_file = e_server_side_source_new_user_file (NULL);
630         parse_data->transport_key_file = g_key_file_new ();
631
632         identity_uid = e_server_side_source_uid_from_file (
633                 parse_data->identity_file, NULL);
634
635         transport_uid = e_server_side_source_uid_from_file (
636                 parse_data->transport_file, NULL);
637
638         g_key_file_set_string (
639                 parse_data->key_file,
640                 E_SOURCE_GROUP_NAME,
641                 "DisplayName", name);
642
643         g_key_file_set_boolean (
644                 parse_data->key_file,
645                 E_SOURCE_GROUP_NAME,
646                 "Enabled", enabled);
647
648         /* Mail account source references the identity source. */
649         g_key_file_set_string (
650                 parse_data->key_file,
651                 E_SOURCE_EXTENSION_MAIL_ACCOUNT,
652                 "IdentityUid", identity_uid);
653
654         /* Mail account source references the transport source. */
655         g_key_file_set_string (
656                 parse_data->identity_key_file,
657                 E_SOURCE_EXTENSION_MAIL_SUBMISSION,
658                 "TransportUid", transport_uid);
659
660         /* Identity source gets the same display name. */
661         g_key_file_set_string (
662                 parse_data->identity_key_file,
663                 E_SOURCE_GROUP_NAME,
664                 "DisplayName", name);
665
666         /* Identity source gets the same enabled state. */
667         g_key_file_set_boolean (
668                 parse_data->identity_key_file,
669                 E_SOURCE_GROUP_NAME,
670                 "Enabled", enabled);
671
672         /* Identity source is a child of the mail account. */
673         g_key_file_set_string (
674                 parse_data->identity_key_file,
675                 E_SOURCE_GROUP_NAME,
676                 "Parent", uid);
677
678         /* Transport source gets the same display name. */
679         g_key_file_set_string (
680                 parse_data->transport_key_file,
681                 E_SOURCE_GROUP_NAME,
682                 "DisplayName", name);
683
684         /* Always enable the transport source, even if the mail account
685          * is disabled.  Evolution does not currently honor the enabled
686          * setting on transports, so disabling the transport would only
687          * confuse matters should Evolution honor it in the future. */
688         g_key_file_set_boolean (
689                 parse_data->transport_key_file,
690                 E_SOURCE_GROUP_NAME,
691                 "Enabled", TRUE);
692
693         /* Transport source is a child of the mail account. */
694         g_key_file_set_string (
695                 parse_data->transport_key_file,
696                 E_SOURCE_GROUP_NAME,
697                 "Parent", uid);
698
699         g_free (identity_uid);
700         g_free (transport_uid);
701 }
702
703 static void
704 migrate_parse_pgp (ParseData *parse_data,
705                    const gchar *element_name,
706                    const gchar **attribute_names,
707                    const gchar **attribute_values,
708                    GError **error)
709 {
710         const gchar *hash_algo;
711         gboolean always_sign;
712         gboolean always_trust;
713         gboolean encrypt_to_self;
714         gboolean no_imip_sign;
715         gboolean success;
716
717         success = g_markup_collect_attributes (
718                 element_name,
719                 attribute_names,
720                 attribute_values,
721                 error,
722                 G_MARKUP_COLLECT_BOOLEAN,
723                 "always-sign", &always_sign,
724                 G_MARKUP_COLLECT_BOOLEAN,
725                 "always-trust", &always_trust,
726                 G_MARKUP_COLLECT_BOOLEAN,
727                 "encrypt-to-self", &encrypt_to_self,
728                 G_MARKUP_COLLECT_BOOLEAN,
729                 "no-imip-sign", &no_imip_sign,
730                 G_MARKUP_COLLECT_STRING |
731                 G_MARKUP_COLLECT_OPTIONAL,
732                 "hash-algo", &hash_algo,
733                 G_MARKUP_COLLECT_INVALID);
734
735         if (!success)
736                 return;
737
738         g_key_file_set_boolean (
739                 parse_data->identity_key_file,
740                 E_SOURCE_EXTENSION_OPENPGP,
741                 "AlwaysSign", always_sign);
742
743         g_key_file_set_boolean (
744                 parse_data->identity_key_file,
745                 E_SOURCE_EXTENSION_OPENPGP,
746                 "AlwaysTrust", always_trust);
747
748         g_key_file_set_boolean (
749                 parse_data->identity_key_file,
750                 E_SOURCE_EXTENSION_OPENPGP,
751                 "EncryptToSelf", encrypt_to_self);
752
753         if (hash_algo != NULL && *hash_algo != '\0')
754                 g_key_file_set_string (
755                         parse_data->identity_key_file,
756                         E_SOURCE_EXTENSION_OPENPGP,
757                         "SigningAlgorithm", hash_algo);
758
759         /* XXX Don't know why this is under the <pgp>
760          *     element, it applies to S/MIME as well.
761          *     Also note we're inverting the setting. */
762         g_key_file_set_boolean (
763                 parse_data->identity_key_file,
764                 E_SOURCE_EXTENSION_MAIL_COMPOSITION,
765                 "SignImip", !no_imip_sign);
766 }
767
768 static void
769 migrate_parse_recipients (ParseData *parse_data,
770                           const gchar *key,
771                           const gchar *recipients)
772 {
773         CamelAddress *address;
774         CamelInternetAddress *inet_address;
775         gchar **string_list;
776         gint ii, length;
777         gsize index = 0;
778
779         if (recipients == NULL || *recipients == '\0')
780                 return;
781
782         inet_address = camel_internet_address_new ();
783         address = CAMEL_ADDRESS (inet_address);
784
785         if (camel_address_decode (address, recipients) == -1)
786                 goto exit;
787
788         length = camel_address_length (address);
789         string_list = g_new0 (gchar *, length + 1);
790
791         for (ii = 0; ii < length; ii++) {
792                 const gchar *name, *addr;
793
794                 if (!camel_internet_address_get (
795                         inet_address, ii, &name, &addr))
796                         continue;
797
798                 string_list[index++] =
799                         camel_internet_address_format_address (name, addr);
800         }
801
802         g_key_file_set_string_list (
803                 parse_data->identity_key_file,
804                 E_SOURCE_EXTENSION_MAIL_COMPOSITION, key,
805                 (const gchar *const *) string_list, index);
806
807         g_strfreev (string_list);
808
809 exit:
810         g_object_unref (inet_address);
811 }
812
813 static void
814 migrate_parse_smime (ParseData *parse_data,
815                      const gchar *element_name,
816                      const gchar **attribute_names,
817                      const gchar **attribute_values,
818                      GError **error)
819 {
820         const gchar *hash_algo;
821         gboolean encrypt_default;
822         gboolean encrypt_to_self;
823         gboolean sign_default;
824         gboolean success;
825
826         success = g_markup_collect_attributes (
827                 element_name,
828                 attribute_names,
829                 attribute_values,
830                 error,
831                 G_MARKUP_COLLECT_BOOLEAN,
832                 "encrypt-default", &encrypt_default,
833                 G_MARKUP_COLLECT_BOOLEAN,
834                 "encrypt-to-self", &encrypt_to_self,
835                 G_MARKUP_COLLECT_STRING |
836                 G_MARKUP_COLLECT_OPTIONAL,
837                 "hash-algo", &hash_algo,
838                 G_MARKUP_COLLECT_BOOLEAN,
839                 "sign-default", &sign_default,
840                 G_MARKUP_COLLECT_INVALID);
841
842         if (!success)
843                 return;
844
845         g_key_file_set_boolean (
846                 parse_data->identity_key_file,
847                 E_SOURCE_EXTENSION_SMIME,
848                 "EncryptByDefault", encrypt_default);
849
850         g_key_file_set_boolean (
851                 parse_data->identity_key_file,
852                 E_SOURCE_EXTENSION_SMIME,
853                 "EncryptToSelf", encrypt_to_self);
854
855         if (hash_algo != NULL && *hash_algo != '\0')
856                 g_key_file_set_string (
857                         parse_data->identity_key_file,
858                         E_SOURCE_EXTENSION_SMIME,
859                         "SigningAlgorithm", hash_algo);
860
861         g_key_file_set_boolean (
862                 parse_data->identity_key_file,
863                 E_SOURCE_EXTENSION_SMIME,
864                 "SignByDefault", sign_default);
865 }
866
867 static void
868 migrate_parse_mail_source (ParseData *parse_data,
869                            const gchar *element_name,
870                            const gchar **attribute_names,
871                            const gchar **attribute_values,
872                            GError **error)
873 {
874         const gchar *auto_check_timeout;
875         glong interval_minutes = 0;
876         gboolean auto_check;
877         gboolean success;
878
879         /* Disregard "keep-on-server" and "save-passwd" attributes. */
880         success = g_markup_collect_attributes (
881                 element_name,
882                 attribute_names,
883                 attribute_values,
884                 error,
885                 G_MARKUP_COLLECT_BOOLEAN,
886                 "auto-check", &auto_check,
887                 G_MARKUP_COLLECT_STRING,
888                 "auto-check-timeout", &auto_check_timeout,
889                 G_MARKUP_COLLECT_BOOLEAN |
890                 G_MARKUP_COLLECT_OPTIONAL,
891                 "keep-on-server", NULL,
892                 G_MARKUP_COLLECT_BOOLEAN |
893                 G_MARKUP_COLLECT_OPTIONAL,
894                 "save-passwd", NULL,
895                 G_MARKUP_COLLECT_INVALID);
896
897         if (!success)
898                 return;
899
900         if (auto_check_timeout != NULL)
901                 interval_minutes = strtol (auto_check_timeout, NULL, 10);
902
903         g_key_file_set_boolean (
904                 parse_data->key_file,
905                 E_SOURCE_EXTENSION_REFRESH,
906                 "Enabled", auto_check);
907
908         if (interval_minutes > 0)
909                 g_key_file_set_integer (
910                         parse_data->key_file,
911                         E_SOURCE_EXTENSION_REFRESH,
912                         "IntervalMinutes", interval_minutes);
913 }
914
915 static void
916 migrate_parse_url_rename_params (CamelURL *url)
917 {
918         /* This list includes known URL parameters from built-in providers
919          * in Camel, as well as from evolution-exchange/groupwise/mapi/ews.
920          * Add more as needed. */
921         static struct {
922                 const gchar *url_parameter;
923                 const gchar *property_name;
924         } camel_url_conversion[] = {
925                 { "account_uid",                "account-uid" },
926                 { "ad_auth",                    "gc-auth-method" },
927                 { "ad_browse",                  "gc-allow-browse" },
928                 { "ad_expand_groups",           "gc-expand-groups" },
929                 { "ad_limit",                   "gc-results-limit" },
930                 { "ad_server",                  "gc-server-name" },
931                 { "all_headers",                "fetch-headers" },
932                 { "basic_headers",              "fetch-headers" },
933                 { "cachedconn"                  "concurrent-connections" },
934                 { "check_all",                  "check-all" },
935                 { "check_lsub",                 "check-subscribed" },
936                 { "command",                    "shell-command" },
937                 { "delete_after",               "delete-after-days" },
938                 { "delete_expunged",            "delete-expunged" },
939                 { "disable_extensions",         "disable-extensions" },
940                 { "dotfolders",                 "use-dot-folders" },
941                 { "filter",                     "filter-inbox" },
942                 { "filter_junk",                "filter-junk" },
943                 { "filter_junk_inbox",          "filter-junk-inbox" },
944                 { "folder_hierarchy_relative",  "folder-hierarchy-relative" },
945                 { "imap_custom_headers",        "fetch-headers-extra" },
946                 { "keep_on_server",             "keep-on-server" },
947                 { "oab_offline",                "oab-offline" },
948                 { "oal_selected",               "oal-selected" },
949                 { "offline_sync",               "stay-synchronized" },
950                 { "override_namespace",         "use-namespace" },
951                 { "owa_path",                   "owa-path" },
952                 { "owa_url",                    "owa-url" },
953                 { "password_exp_warn_period",   "password-exp-warn-period" },
954                 { "real_junk_path",             "real-junk-path" },
955                 { "real_trash_path",            "real-trash-path" },
956                 { "show_short_notation",        "short-folder-names" },
957                 { "soap_port",                  "soap-port" },
958                 { "ssl",                        "security-method" },
959                 { "sync_offline",               "stay-synchronized" },
960                 { "use_command",                "use-shell-command" },
961                 { "use_idle",                   "use-idle" },
962                 { "use_lsub",                   "use-subscriptions" },
963                 { "use_qresync",                "use-qresync" },
964                 { "use_ssl",                    "security-method" },
965                 { "xstatus",                    "use-xstatus-headers" }
966         };
967
968         const gchar *param;
969         const gchar *use_param;
970         gint ii;
971
972         for (ii = 0; ii < G_N_ELEMENTS (camel_url_conversion); ii++) {
973                 const gchar *key;
974                 gpointer value;
975
976                 key = camel_url_conversion[ii].url_parameter;
977                 value = g_datalist_get_data (&url->params, key);
978
979                 if (value == NULL)
980                         continue;
981
982                 g_datalist_remove_no_notify (&url->params, key);
983
984                 key = camel_url_conversion[ii].property_name;
985
986                 /* Deal with a few special enum cases where
987                  * the parameter value also needs renamed. */
988
989                 if (strcmp (key, "all_headers") == 0) {
990                         GEnumClass *enum_class;
991                         GEnumValue *enum_value;
992
993                         enum_class = g_type_class_ref (
994                                 CAMEL_TYPE_FETCH_HEADERS_TYPE);
995                         enum_value = g_enum_get_value (
996                                 enum_class, CAMEL_FETCH_HEADERS_ALL);
997                         if (enum_value != NULL) {
998                                 g_free (value);
999                                 value = g_strdup (enum_value->value_nick);
1000                         } else
1001                                 g_warn_if_reached ();
1002                         g_type_class_unref (enum_class);
1003                 }
1004
1005                 if (strcmp (key, "basic_headers") == 0) {
1006                         GEnumClass *enum_class;
1007                         GEnumValue *enum_value;
1008
1009                         enum_class = g_type_class_ref (
1010                                 CAMEL_TYPE_FETCH_HEADERS_TYPE);
1011                         enum_value = g_enum_get_value (
1012                                 enum_class, CAMEL_FETCH_HEADERS_BASIC);
1013                         if (enum_value != NULL) {
1014                                 g_free (value);
1015                                 value = g_strdup (enum_value->value_nick);
1016                         } else
1017                                 g_warn_if_reached ();
1018                         g_type_class_unref (enum_class);
1019                 }
1020
1021                 if (strcmp (key, "imap_custom_headers") == 0)
1022                         g_strdelimit (value, " ", ',');
1023
1024                 if (strcmp (key, "security-method") == 0) {
1025                         CamelNetworkSecurityMethod method;
1026                         GEnumClass *enum_class;
1027                         GEnumValue *enum_value;
1028
1029                         if (strcmp (value, "always") == 0)
1030                                 method = CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT;
1031                         else if (strcmp (value, "1") == 0)
1032                                 method = CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT;
1033                         else if (strcmp (value, "when-possible") == 0)
1034                                 method = CAMEL_NETWORK_SECURITY_METHOD_STARTTLS_ON_STANDARD_PORT;
1035                         else
1036                                 method = CAMEL_NETWORK_SECURITY_METHOD_NONE;
1037
1038                         enum_class = g_type_class_ref (
1039                                 CAMEL_TYPE_NETWORK_SECURITY_METHOD);
1040                         enum_value = g_enum_get_value (enum_class, method);
1041                         if (enum_value != NULL) {
1042                                 g_free (value);
1043                                 value = g_strdup (enum_value->value_nick);
1044                         } else
1045                                 g_warn_if_reached ();
1046                         g_type_class_unref (enum_class);
1047                 }
1048
1049                 g_datalist_set_data_full (&url->params, key, value, g_free);
1050         }
1051
1052         /* missing "security-method" means STARTTLS, as it was the default value in 3.4- */
1053         if (!g_datalist_get_data (&url->params, "security-method")) {
1054                 GEnumClass *enum_class;
1055                 GEnumValue *enum_value;
1056                 gchar *value = NULL;
1057
1058                 enum_class = g_type_class_ref (CAMEL_TYPE_NETWORK_SECURITY_METHOD);
1059                 enum_value = g_enum_get_value (
1060                         enum_class,
1061                         CAMEL_NETWORK_SECURITY_METHOD_STARTTLS_ON_STANDARD_PORT);
1062                 if (enum_value != NULL) {
1063                         value = g_strdup (enum_value->value_nick);
1064                 } else
1065                         g_warn_if_reached ();
1066                 g_type_class_unref (enum_class);
1067
1068                 g_datalist_set_data_full (&url->params, "security-method", value, g_free);
1069         }
1070
1071         /* A few more adjustments...
1072          *
1073          * These are all CAMEL_PROVIDER_CONF_CHECKSPIN settings.  The spin
1074          * button value is bound to "param" and the checkbox state is bound
1075          * to "use-param".  The "use-param" settings are new.  If "param"
1076          * exists but no "use-param", then set "use-param" to "true". */
1077
1078         param = g_datalist_get_data (&url->params, "gc-results-limit");
1079         use_param = g_datalist_get_data (&url->params, "use-gc-results-limit");
1080         if (param != NULL && *param != '\0' && use_param == NULL) {
1081                 g_datalist_set_data_full (
1082                         &url->params, "use-gc-results-limit",
1083                         g_strdup ("true"), (GDestroyNotify) g_free);
1084         }
1085
1086         param = g_datalist_get_data (&url->params, "kerberos");
1087         if (g_strcmp0 (param, "required") == 0) {
1088                 g_datalist_set_data_full (
1089                         &url->params, "kerberos",
1090                         g_strdup ("true"), (GDestroyNotify) g_free);
1091         }
1092
1093         param = g_datalist_get_data (
1094                 &url->params, "password-exp-warn-period");
1095         use_param = g_datalist_get_data (
1096                 &url->params, "use-password-exp-warn-period");
1097         if (param != NULL && *param != '\0' && use_param == NULL) {
1098                 g_datalist_set_data_full (
1099                         &url->params, "use-password-exp-warn-period",
1100                         g_strdup ("true"), (GDestroyNotify) g_free);
1101         }
1102
1103         param = g_datalist_get_data (&url->params, "real-junk-path");
1104         use_param = g_datalist_get_data (&url->params, "use-real-junk-path");
1105         if (param != NULL && *param != '\0' && use_param == NULL) {
1106                 g_datalist_set_data_full (
1107                         &url->params, "use-real-junk-path",
1108                         g_strdup ("true"), (GDestroyNotify) g_free);
1109         }
1110
1111         param = g_datalist_get_data (&url->params, "real-trash-path");
1112         use_param = g_datalist_get_data (&url->params, "use-real-trash-path");
1113         if (param != NULL && *param != '\0' && use_param == NULL) {
1114                 g_datalist_set_data_full (
1115                         &url->params, "use-real-trash-path",
1116                         g_strdup ("true"), (GDestroyNotify) g_free);
1117         }
1118
1119         /* Remove an empty "namespace" parameter (if present) to avoid
1120          * it being converted to "true" in migrate_parse_url_foreach(). */
1121         param = g_datalist_get_data (&url->params, "namespace");
1122         if (param != NULL && *param == '\0')
1123                 g_datalist_remove_data (&url->params, "namespace");
1124 }
1125
1126 static void
1127 migrate_parse_url_foreach (GQuark key_id,
1128                            const gchar *value,
1129                            gpointer user_data)
1130 {
1131         const gchar *param_name;
1132         const gchar *key;
1133
1134         struct {
1135                 GKeyFile *key_file;
1136                 const gchar *group_name;
1137         } *foreach_data = user_data;
1138
1139         g_return_if_fail (value != NULL);
1140
1141         param_name = g_quark_to_string (key_id);
1142         key = e_source_parameter_to_key (param_name);
1143
1144         /* If the value is empty, then the mere
1145          * presence of the parameter implies TRUE. */
1146         if (*value == '\0')
1147                 value = "true";
1148
1149         g_key_file_set_string (
1150                 foreach_data->key_file,
1151                 foreach_data->group_name,
1152                 key, value);
1153 }
1154
1155 static void
1156 migrate_parse_url (ParseData *parse_data,
1157                    GKeyFile *key_file,
1158                    GFile *file,
1159                    const gchar *group_name,
1160                    const gchar *url_string,
1161                    GError **error)
1162 {
1163         CamelURL *url;
1164         GKeyFile *backend_key_file;
1165         GFile *backend_file;
1166         const gchar *value;
1167         gboolean setup_collection;
1168         gchar *uid;
1169
1170         struct {
1171                 GKeyFile *key_file;
1172                 const gchar *group_name;
1173         } foreach_data;
1174
1175         url = camel_url_new (url_string, error);
1176         if (url == NULL || url->protocol == NULL)
1177                 return;
1178
1179         /* Rename URL params as necessary to match
1180          * their ESourceExtension property names. */
1181         migrate_parse_url_rename_params (url);
1182
1183         setup_collection =
1184                 (key_file == parse_data->key_file) &&
1185                 base_uri_is_groupware (url->protocol);
1186
1187         if (setup_collection)
1188                 migrate_setup_collection (parse_data, url);
1189
1190         /* Store backend settings in the collection GKeyFile, if one is
1191          * defined.  Otherwise store them in the GKeyFile we were passed.
1192          * Same goes for the keyring entry, which uses the GFile. */
1193         if (parse_data->collection_key_file != NULL) {
1194                 backend_key_file = parse_data->collection_key_file;
1195                 backend_file = parse_data->collection_file;
1196         } else {
1197                 backend_key_file = key_file;
1198                 backend_file = file;
1199         }
1200
1201         /* This is not a backend setting. */
1202         g_key_file_set_string (
1203                 key_file, group_name,
1204                 "BackendName", url->protocol);
1205
1206         /* Set authentication details. */
1207
1208         if (url->host != NULL)
1209                 g_key_file_set_string (
1210                         backend_key_file,
1211                         E_SOURCE_EXTENSION_AUTHENTICATION,
1212                         "Host", url->host);
1213
1214         if (url->authmech != NULL)
1215                 g_key_file_set_string (
1216                         backend_key_file,
1217                         E_SOURCE_EXTENSION_AUTHENTICATION,
1218                         "Method", url->authmech);
1219
1220         if (url->port > 0)
1221                 g_key_file_set_integer (
1222                         backend_key_file,
1223                         E_SOURCE_EXTENSION_AUTHENTICATION,
1224                         "Port", url->port);
1225
1226         if (url->user != NULL)
1227                 g_key_file_set_string (
1228                         backend_key_file,
1229                         E_SOURCE_EXTENSION_AUTHENTICATION,
1230                         "User", url->user);
1231
1232         /* Pick out particular URL parameters we know about. */
1233
1234         /* If set, this should be "true" or "false",
1235          * but we'll just write it like it's a string. */
1236         value = g_datalist_get_data (&url->params, "stay-synchronized");
1237         if (value != NULL)
1238                 g_key_file_set_string (
1239                         backend_key_file,
1240                         E_SOURCE_EXTENSION_OFFLINE,
1241                         "StaySynchronized", value);
1242         g_datalist_set_data (&url->params, "stay-synchronized", NULL);
1243
1244         value = g_datalist_get_data (&url->params, "security-method");
1245         if (value != NULL)
1246                 g_key_file_set_string (
1247                         backend_key_file,
1248                         E_SOURCE_EXTENSION_SECURITY,
1249                         "Method", value);
1250         g_datalist_set_data (&url->params, "security-method", NULL);
1251
1252         /* If we see a "goa-account-id" parameter, skip the entire
1253          * account and let the online-accounts module recreate it. */
1254         value = g_datalist_get_data (&url->params, "goa-account-id");
1255         if (value != NULL && *value != '\0')
1256                 parse_data->skip = TRUE;
1257
1258         /* The rest of the URL parameters go in the backend group. */
1259
1260         group_name = e_source_camel_get_extension_name (url->protocol);
1261
1262         foreach_data.key_file = backend_key_file;
1263         foreach_data.group_name = group_name;
1264
1265         g_datalist_foreach (
1266                 &url->params, (GDataForeachFunc)
1267                 migrate_parse_url_foreach, &foreach_data);
1268
1269         /* Local providers store their "path" as the url->path */
1270         if (g_strcmp0 (url->protocol, "mh") == 0 ||
1271             g_strcmp0 (url->protocol, "mbox") == 0 ||
1272             g_strcmp0 (url->protocol, "maildir") == 0 ||
1273             g_strcmp0 (url->protocol, "spool") == 0 ||
1274             g_strcmp0 (url->protocol, "spooldir") == 0)
1275                 g_key_file_set_string (
1276                         backend_key_file,
1277                         group_name,
1278                         "Path", url->path);
1279
1280         uid = e_server_side_source_uid_from_file (backend_file, error);
1281
1282         if (uid != NULL) {
1283                 migrate_keyring_entry (
1284                         uid, url->user, url->host, url->protocol);
1285                 g_free (uid);
1286         }
1287
1288         camel_url_free (url);
1289 }
1290
1291 static void
1292 migrate_parse_account_xml_start_element (GMarkupParseContext *context,
1293                                          const gchar *element_name,
1294                                          const gchar **attribute_names,
1295                                          const gchar **attribute_values,
1296                                          gpointer user_data,
1297                                          GError **error)
1298 {
1299         ParseData *parse_data = user_data;
1300
1301         if (g_strcmp0 (element_name, "account") == 0) {
1302                 if (parse_data->state != PARSE_STATE_IN_ACCOUNTS_VALUE)
1303                         goto invalid_content;
1304
1305                 parse_data->state = PARSE_STATE_IN_ACCOUNT;
1306
1307                 migrate_parse_account (
1308                         parse_data,
1309                         element_name,
1310                         attribute_names,
1311                         attribute_values,
1312                         error);
1313
1314                 return;
1315         }
1316
1317         if (g_strcmp0 (element_name, "addr-spec") == 0) {
1318                 if (parse_data->state != PARSE_STATE_IN_IDENTITY)
1319                         goto invalid_content;
1320
1321                 parse_data->state = PARSE_STATE_IN_IDENTITY_ADDR_SPEC;
1322
1323                 return;
1324         }
1325
1326         if (g_strcmp0 (element_name, "auto-bcc") == 0) {
1327                 if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
1328                         goto invalid_content;
1329
1330                 parse_data->state = PARSE_STATE_IN_AUTO_BCC;
1331
1332                 g_markup_collect_attributes (
1333                         element_name,
1334                         attribute_names,
1335                         attribute_values,
1336                         error,
1337                         G_MARKUP_COLLECT_BOOLEAN,
1338                         "always", &parse_data->auto_bcc,
1339                         G_MARKUP_COLLECT_INVALID);
1340
1341                 return;
1342         }
1343
1344         if (g_strcmp0 (element_name, "auto-cc") == 0) {
1345                 if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
1346                         goto invalid_content;
1347
1348                 parse_data->state = PARSE_STATE_IN_AUTO_CC;
1349
1350                 g_markup_collect_attributes (
1351                         element_name,
1352                         attribute_names,
1353                         attribute_values,
1354                         error,
1355                         G_MARKUP_COLLECT_BOOLEAN,
1356                         "always", &parse_data->auto_cc,
1357                         G_MARKUP_COLLECT_INVALID);
1358
1359                 return;
1360         }
1361
1362         if (g_strcmp0 (element_name, "drafts-folder") == 0) {
1363                 if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
1364                         goto invalid_content;
1365
1366                 parse_data->state = PARSE_STATE_IN_DRAFTS_FOLDER;
1367
1368                 return;
1369         }
1370
1371         if (g_strcmp0 (element_name, "encrypt-key-id") == 0) {
1372                 if (parse_data->state != PARSE_STATE_IN_SMIME)
1373                         goto invalid_content;
1374
1375                 parse_data->state = PARSE_STATE_IN_SMIME_ENCRYPT_KEY_ID;
1376
1377                 return;
1378         }
1379
1380         if (g_strcmp0 (element_name, "identity") == 0) {
1381                 if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
1382                         goto invalid_content;
1383
1384                 parse_data->state = PARSE_STATE_IN_IDENTITY;
1385
1386                 return;
1387         }
1388
1389         if (g_strcmp0 (element_name, "key-id") == 0) {
1390                 if (parse_data->state != PARSE_STATE_IN_PGP)
1391                         goto invalid_content;
1392
1393                 parse_data->state = PARSE_STATE_IN_PGP_KEY_ID;
1394
1395                 return;
1396         }
1397
1398         if (g_strcmp0 (element_name, "name") == 0) {
1399                 if (parse_data->state != PARSE_STATE_IN_IDENTITY)
1400                         goto invalid_content;
1401
1402                 parse_data->state = PARSE_STATE_IN_IDENTITY_NAME;
1403
1404                 return;
1405         }
1406
1407         if (g_strcmp0 (element_name, "reply-to") == 0) {
1408                 if (parse_data->state != PARSE_STATE_IN_IDENTITY)
1409                         goto invalid_content;
1410
1411                 parse_data->state = PARSE_STATE_IN_IDENTITY_REPLY_TO;
1412
1413                 return;
1414         }
1415
1416         if (g_strcmp0 (element_name, "organization") == 0) {
1417                 if (parse_data->state != PARSE_STATE_IN_IDENTITY)
1418                         goto invalid_content;
1419
1420                 parse_data->state = PARSE_STATE_IN_IDENTITY_ORGANIZATION;
1421
1422                 return;
1423         }
1424
1425         if (g_strcmp0 (element_name, "pgp") == 0) {
1426                 if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
1427                         goto invalid_content;
1428
1429                 parse_data->state = PARSE_STATE_IN_PGP;
1430
1431                 migrate_parse_pgp (
1432                         parse_data,
1433                         element_name,
1434                         attribute_names,
1435                         attribute_values,
1436                         error);
1437
1438                 return;
1439         }
1440
1441         if (g_strcmp0 (element_name, "receipt-policy") == 0) {
1442                 const gchar *policy;
1443                 gboolean success;
1444
1445                 if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
1446                         goto invalid_content;
1447
1448                 parse_data->state = PARSE_STATE_IN_RECEIPT_POLICY;
1449
1450                 success = g_markup_collect_attributes (
1451                         element_name,
1452                         attribute_names,
1453                         attribute_values,
1454                         error,
1455                         G_MARKUP_COLLECT_STRING,
1456                         "policy", &policy,
1457                         G_MARKUP_COLLECT_INVALID);
1458
1459                 /* The new enum strings match the old ones. */
1460                 if (success && policy != NULL)
1461                         g_key_file_set_string (
1462                                 parse_data->key_file,
1463                                 E_SOURCE_EXTENSION_MDN,
1464                                 "ResponsePolicy", policy);
1465
1466                 return;
1467         }
1468
1469         if (g_strcmp0 (element_name, "recipients") == 0) {
1470                 if (parse_data->state == PARSE_STATE_IN_AUTO_BCC) {
1471                         parse_data->state = PARSE_STATE_IN_AUTO_BCC_RECIPIENTS;
1472                         return;
1473                 }
1474
1475                 if (parse_data->state == PARSE_STATE_IN_AUTO_CC) {
1476                         parse_data->state = PARSE_STATE_IN_AUTO_CC_RECIPIENTS;
1477                         return;
1478                 }
1479
1480                 goto invalid_content;
1481         }
1482
1483         if (g_strcmp0 (element_name, "sent-folder") == 0) {
1484                 if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
1485                         goto invalid_content;
1486
1487                 parse_data->state = PARSE_STATE_IN_SENT_FOLDER;
1488
1489                 return;
1490         }
1491
1492         if (g_strcmp0 (element_name, "signature") == 0) {
1493                 const gchar *uid;
1494                 gboolean success;
1495
1496                 if (parse_data->state != PARSE_STATE_IN_IDENTITY)
1497                         goto invalid_content;
1498
1499                 parse_data->state = PARSE_STATE_IN_IDENTITY_SIGNATURE;
1500
1501                 success = g_markup_collect_attributes (
1502                         element_name,
1503                         attribute_names,
1504                         attribute_values,
1505                         error,
1506                         G_MARKUP_COLLECT_STRING,
1507                         "uid", &uid,
1508                         G_MARKUP_COLLECT_INVALID);
1509
1510                 if (success && uid != NULL)
1511                         g_key_file_set_string (
1512                                 parse_data->identity_key_file,
1513                                 E_SOURCE_EXTENSION_MAIL_IDENTITY,
1514                                 "SignatureUid", uid);
1515
1516                 return;
1517         }
1518
1519         if (g_strcmp0 (element_name, "sign-key-id") == 0) {
1520                 if (parse_data->state != PARSE_STATE_IN_SMIME)
1521                         goto invalid_content;
1522
1523                 parse_data->state = PARSE_STATE_IN_SMIME_SIGN_KEY_ID;
1524
1525                 return;
1526         }
1527
1528         if (g_strcmp0 (element_name, "smime") == 0) {
1529                 if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
1530                         goto invalid_content;
1531
1532                 parse_data->state = PARSE_STATE_IN_SMIME;
1533
1534                 migrate_parse_smime (
1535                         parse_data,
1536                         element_name,
1537                         attribute_names,
1538                         attribute_values,
1539                         error);
1540
1541                 return;
1542         }
1543
1544         if (g_strcmp0 (element_name, "source") == 0) {
1545                 if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
1546                         goto invalid_content;
1547
1548                 parse_data->state = PARSE_STATE_IN_MAIL_SOURCE;
1549
1550                 migrate_parse_mail_source (
1551                         parse_data,
1552                         element_name,
1553                         attribute_names,
1554                         attribute_values,
1555                         error);
1556
1557                 return;
1558         }
1559
1560         if (g_strcmp0 (element_name, "transport") == 0) {
1561                 if (parse_data->state != PARSE_STATE_IN_ACCOUNT)
1562                         goto invalid_content;
1563
1564                 parse_data->state = PARSE_STATE_IN_MAIL_TRANSPORT;
1565
1566                 return;
1567         }
1568
1569         if (g_strcmp0 (element_name, "url") == 0) {
1570                 if (parse_data->state == PARSE_STATE_IN_MAIL_SOURCE) {
1571                         parse_data->state = PARSE_STATE_IN_MAIL_SOURCE_URL;
1572                         return;
1573                 }
1574
1575                 if (parse_data->state == PARSE_STATE_IN_MAIL_TRANSPORT) {
1576                         parse_data->state = PARSE_STATE_IN_MAIL_TRANSPORT_URL;
1577                         return;
1578                 }
1579
1580                 goto invalid_content;
1581         }
1582
1583         g_set_error (
1584                 error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
1585                 "Unknown element <%s>", element_name);
1586
1587         return;
1588
1589 invalid_content:
1590
1591         g_set_error (
1592                 error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
1593                 "Element <%s> at unexpected location", element_name);
1594 }
1595
1596 static void
1597 migrate_parse_account_xml_end_element (GMarkupParseContext *context,
1598                                        const gchar *element_name,
1599                                        gpointer user_data,
1600                                        GError **error)
1601 {
1602         ParseData *parse_data = user_data;
1603
1604         if (g_strcmp0 (element_name, "account") == 0) {
1605                 if (parse_data->state == PARSE_STATE_IN_ACCOUNT) {
1606                         parse_data->state = PARSE_STATE_IN_ACCOUNTS_VALUE;
1607
1608                         /* Clean up <account> tag data. */
1609
1610                         /* The key file will be NULL if we decided to skip it.
1611                          * e.g. A file with the same UID may already exist. */
1612                         if (parse_data->key_file != NULL) {
1613                                 GError *local_error = NULL;
1614
1615                                 if (!parse_data->skip)
1616                                         migrate_parse_commit_changes (
1617                                                 parse_data->type,
1618                                                 parse_data->file,
1619                                                 parse_data->key_file,
1620                                                 NULL, &local_error);
1621
1622                                 if (local_error != NULL) {
1623                                         g_printerr (
1624                                                 "  FAILED: %s\n",
1625                                                 local_error->message);
1626                                         g_error_free (local_error);
1627                                 }
1628
1629                                 g_key_file_free (parse_data->key_file);
1630                                 parse_data->key_file = NULL;
1631                         }
1632
1633                         /* Same deal for the identity key file. */
1634                         if (parse_data->identity_key_file != NULL) {
1635                                 GError *local_error = NULL;
1636
1637                                 if (!parse_data->skip)
1638                                         migrate_parse_commit_changes (
1639                                                 parse_data->type,
1640                                                 parse_data->identity_file,
1641                                                 parse_data->identity_key_file,
1642                                                 NULL, &local_error);
1643
1644                                 if (local_error != NULL) {
1645                                         g_printerr (
1646                                                 "  FAILED: %s\n",
1647                                                 local_error->message);
1648                                         g_error_free (local_error);
1649                                 }
1650
1651                                 g_key_file_free (parse_data->identity_key_file);
1652                                 parse_data->identity_key_file = NULL;
1653                         }
1654
1655                         /* Same deal for the transport key file. */
1656                         if (parse_data->transport_key_file != NULL) {
1657                                 GError *local_error = NULL;
1658
1659                                 if (!parse_data->skip)
1660                                         migrate_parse_commit_changes (
1661                                                 parse_data->type,
1662                                                 parse_data->transport_file,
1663                                                 parse_data->transport_key_file,
1664                                                 NULL, &local_error);
1665
1666                                 if (local_error != NULL) {
1667                                         g_printerr (
1668                                                 "  FAILED: %s\n",
1669                                                 local_error->message);
1670                                         g_error_free (local_error);
1671                                 }
1672
1673                                 g_key_file_free (parse_data->transport_key_file);
1674                                 parse_data->transport_key_file = NULL;
1675                         }
1676
1677                         /* The collection key file is optional anyway. */
1678                         if (parse_data->collection_key_file != NULL) {
1679                                 GError *local_error = NULL;
1680
1681                                 if (!parse_data->skip)
1682                                         migrate_parse_commit_changes (
1683                                                 parse_data->type,
1684                                                 parse_data->collection_file,
1685                                                 parse_data->collection_key_file,
1686                                                 NULL, &local_error);
1687
1688                                 if (local_error != NULL) {
1689                                         g_printerr (
1690                                                 "  FAILED: %s\n",
1691                                                 local_error->message);
1692                                         g_error_free (local_error);
1693                                 }
1694
1695                                 g_key_file_free (parse_data->collection_key_file);
1696                                 parse_data->collection_key_file = NULL;
1697                         }
1698
1699                         if (parse_data->file != NULL) {
1700                                 g_object_unref (parse_data->file);
1701                                 parse_data->file = NULL;
1702                         }
1703
1704                         if (parse_data->identity_file != NULL) {
1705                                 g_object_unref (parse_data->identity_file);
1706                                 parse_data->identity_file = NULL;
1707                         }
1708
1709                         if (parse_data->transport_file != NULL) {
1710                                 g_object_unref (parse_data->transport_file);
1711                                 parse_data->transport_file = NULL;
1712                         }
1713
1714                         if (parse_data->collection_file != NULL) {
1715                                 g_object_unref (parse_data->collection_file);
1716                                 parse_data->collection_file = NULL;
1717                         }
1718
1719                         parse_data->skip = FALSE;
1720                 }
1721                 return;
1722         }
1723
1724         if (g_strcmp0 (element_name, "addr-spec") == 0) {
1725                 if (parse_data->state == PARSE_STATE_IN_IDENTITY_ADDR_SPEC)
1726                         parse_data->state = PARSE_STATE_IN_IDENTITY;
1727                 return;
1728         }
1729
1730         if (g_strcmp0 (element_name, "auto-bcc") == 0) {
1731                 if (parse_data->state == PARSE_STATE_IN_AUTO_BCC)
1732                         parse_data->state = PARSE_STATE_IN_ACCOUNT;
1733                 return;
1734         }
1735
1736         if (g_strcmp0 (element_name, "auto-cc") == 0) {
1737                 if (parse_data->state == PARSE_STATE_IN_AUTO_CC)
1738                         parse_data->state = PARSE_STATE_IN_ACCOUNT;
1739                 return;
1740         }
1741
1742         if (g_strcmp0 (element_name, "drafts-folder") == 0) {
1743                 if (parse_data->state == PARSE_STATE_IN_DRAFTS_FOLDER)
1744                         parse_data->state = PARSE_STATE_IN_ACCOUNT;
1745                 return;
1746         }
1747
1748         if (g_strcmp0 (element_name, "encrypt-key-id") == 0) {
1749                 if (parse_data->state == PARSE_STATE_IN_SMIME_ENCRYPT_KEY_ID)
1750                         parse_data->state = PARSE_STATE_IN_SMIME;
1751                 return;
1752         }
1753
1754         if (g_strcmp0 (element_name, "identity") == 0) {
1755                 if (parse_data->state == PARSE_STATE_IN_IDENTITY)
1756                         parse_data->state = PARSE_STATE_IN_ACCOUNT;
1757                 return;
1758         }
1759
1760         if (g_strcmp0 (element_name, "key-id") == 0) {
1761                 if (parse_data->state == PARSE_STATE_IN_PGP_KEY_ID)
1762                         parse_data->state = PARSE_STATE_IN_PGP;
1763                 return;
1764         }
1765
1766         if (g_strcmp0 (element_name, "name") == 0) {
1767                 if (parse_data->state == PARSE_STATE_IN_IDENTITY_NAME)
1768                         parse_data->state = PARSE_STATE_IN_IDENTITY;
1769                 return;
1770         }
1771
1772         if (g_strcmp0 (element_name, "organization") == 0) {
1773                 if (parse_data->state == PARSE_STATE_IN_IDENTITY_ORGANIZATION)
1774                         parse_data->state = PARSE_STATE_IN_IDENTITY;
1775                 return;
1776         }
1777
1778         if (g_strcmp0 (element_name, "pgp") == 0) {
1779                 if (parse_data->state == PARSE_STATE_IN_PGP)
1780                         parse_data->state = PARSE_STATE_IN_ACCOUNT;
1781                 return;
1782         }
1783
1784         if (g_strcmp0 (element_name, "receipt-policy") == 0) {
1785                 if (parse_data->state == PARSE_STATE_IN_RECEIPT_POLICY)
1786                         parse_data->state = PARSE_STATE_IN_ACCOUNT;
1787                 return;
1788         }
1789
1790         if (g_strcmp0 (element_name, "recipients") == 0) {
1791                 if (parse_data->state == PARSE_STATE_IN_AUTO_BCC_RECIPIENTS)
1792                         parse_data->state = PARSE_STATE_IN_AUTO_BCC;
1793                 if (parse_data->state == PARSE_STATE_IN_AUTO_CC_RECIPIENTS)
1794                         parse_data->state = PARSE_STATE_IN_AUTO_CC;
1795                 return;
1796         }
1797
1798         if (g_strcmp0 (element_name, "reply-to") == 0) {
1799                 if (parse_data->state == PARSE_STATE_IN_IDENTITY_REPLY_TO)
1800                         parse_data->state = PARSE_STATE_IN_IDENTITY;
1801                 return;
1802         }
1803
1804         if (g_strcmp0 (element_name, "sent-folder") == 0) {
1805                 if (parse_data->state == PARSE_STATE_IN_SENT_FOLDER)
1806                         parse_data->state = PARSE_STATE_IN_ACCOUNT;
1807                 return;
1808         }
1809
1810         if (g_strcmp0 (element_name, "signature") == 0) {
1811                 if (parse_data->state == PARSE_STATE_IN_IDENTITY_SIGNATURE)
1812                         parse_data->state = PARSE_STATE_IN_IDENTITY;
1813                 return;
1814         }
1815
1816         if (g_strcmp0 (element_name, "sign-key-id") == 0) {
1817                 if (parse_data->state == PARSE_STATE_IN_SMIME_SIGN_KEY_ID)
1818                         parse_data->state = PARSE_STATE_IN_SMIME;
1819                 return;
1820         }
1821
1822         if (g_strcmp0 (element_name, "smime") == 0) {
1823                 if (parse_data->state == PARSE_STATE_IN_SMIME)
1824                         parse_data->state = PARSE_STATE_IN_ACCOUNT;
1825                 return;
1826         }
1827
1828         if (g_strcmp0 (element_name, "source") == 0) {
1829                 if (parse_data->state == PARSE_STATE_IN_MAIL_SOURCE)
1830                         parse_data->state = PARSE_STATE_IN_ACCOUNT;
1831                 return;
1832         }
1833
1834         if (g_strcmp0 (element_name, "transport") == 0) {
1835                 if (parse_data->state == PARSE_STATE_IN_MAIL_TRANSPORT)
1836                         parse_data->state = PARSE_STATE_IN_ACCOUNT;
1837                 return;
1838         }
1839
1840         if (g_strcmp0 (element_name, "url") == 0) {
1841                 if (parse_data->state == PARSE_STATE_IN_MAIL_SOURCE_URL)
1842                         parse_data->state = PARSE_STATE_IN_MAIL_SOURCE;
1843                 if (parse_data->state == PARSE_STATE_IN_MAIL_TRANSPORT_URL)
1844                         parse_data->state = PARSE_STATE_IN_MAIL_TRANSPORT;
1845                 return;
1846         }
1847 }
1848
1849 static void
1850 migrate_parse_account_xml_text (GMarkupParseContext *context,
1851                                 const gchar *text,
1852                                 gsize text_len,
1853                                 gpointer user_data,
1854                                 GError **error)
1855 {
1856         ParseData *parse_data = user_data;
1857
1858         switch (parse_data->state) {
1859                 case PARSE_STATE_IN_AUTO_BCC_RECIPIENTS:
1860                         /* Disregard the recipient list if
1861                          * we're not going to auto-BCC them. */
1862                         if (parse_data->auto_bcc)
1863                                 migrate_parse_recipients (
1864                                         parse_data, "Bcc", text);
1865                         break;
1866
1867                 case PARSE_STATE_IN_AUTO_CC_RECIPIENTS:
1868                         /* Disregard the recipient list if
1869                          * we're not going to auto-CC them. */
1870                         if (parse_data->auto_cc)
1871                                 migrate_parse_recipients (
1872                                         parse_data, "Cc", text);
1873                         break;
1874
1875                 case PARSE_STATE_IN_DRAFTS_FOLDER:
1876                         g_key_file_set_string (
1877                                 parse_data->identity_key_file,
1878                                 E_SOURCE_EXTENSION_MAIL_COMPOSITION,
1879                                 "DraftsFolder", text);
1880                         break;
1881
1882                 case PARSE_STATE_IN_SMIME_ENCRYPT_KEY_ID:
1883                         g_key_file_set_string (
1884                                 parse_data->identity_key_file,
1885                                 E_SOURCE_EXTENSION_SMIME,
1886                                 "EncryptionCertificate", text);
1887                         break;
1888
1889                 case PARSE_STATE_IN_IDENTITY_ADDR_SPEC:
1890                         g_key_file_set_string (
1891                                 parse_data->identity_key_file,
1892                                 E_SOURCE_EXTENSION_MAIL_IDENTITY,
1893                                 "Address", text);
1894                         break;
1895
1896                 case PARSE_STATE_IN_IDENTITY_NAME:
1897                         g_key_file_set_string (
1898                                 parse_data->identity_key_file,
1899                                 E_SOURCE_EXTENSION_MAIL_IDENTITY,
1900                                 "Name", text);
1901                         break;
1902
1903                 case PARSE_STATE_IN_IDENTITY_ORGANIZATION:
1904                         g_key_file_set_string (
1905                                 parse_data->identity_key_file,
1906                                 E_SOURCE_EXTENSION_MAIL_IDENTITY,
1907                                 "Organization", text);
1908                         break;
1909
1910                 case PARSE_STATE_IN_IDENTITY_REPLY_TO:
1911                         g_key_file_set_string (
1912                                 parse_data->identity_key_file,
1913                                 E_SOURCE_EXTENSION_MAIL_IDENTITY,
1914                                 "ReplyTo", text);
1915                         break;
1916
1917                 case PARSE_STATE_IN_MAIL_SOURCE_URL:
1918                         /* XXX Workaround for so-called "send-only"
1919                          *     accounts, which have no source URL.
1920                          *     Their backend name is "none". */
1921                         if (text != NULL && *text == '\0')
1922                                 text = "none:";
1923
1924                         migrate_parse_url (
1925                                 parse_data,
1926                                 parse_data->key_file,
1927                                 parse_data->file,
1928                                 E_SOURCE_EXTENSION_MAIL_ACCOUNT,
1929                                 text, error);
1930                         break;
1931
1932                 case PARSE_STATE_IN_MAIL_TRANSPORT_URL:
1933                         migrate_parse_url (
1934                                 parse_data,
1935                                 parse_data->transport_key_file,
1936                                 parse_data->transport_file,
1937                                 E_SOURCE_EXTENSION_MAIL_TRANSPORT,
1938                                 text, error);
1939                         break;
1940
1941                 case PARSE_STATE_IN_PGP_KEY_ID:
1942                         g_key_file_set_string (
1943                                 parse_data->identity_key_file,
1944                                 E_SOURCE_EXTENSION_OPENPGP,
1945                                 "KeyId", text);
1946                         break;
1947
1948                 case PARSE_STATE_IN_SENT_FOLDER:
1949                         g_key_file_set_string (
1950                                 parse_data->identity_key_file,
1951                                 E_SOURCE_EXTENSION_MAIL_SUBMISSION,
1952                                 "SentFolder", text);
1953                         break;
1954
1955                 case PARSE_STATE_IN_SMIME_SIGN_KEY_ID:
1956                         g_key_file_set_string (
1957                                 parse_data->identity_key_file,
1958                                 E_SOURCE_EXTENSION_SMIME,
1959                                 "SigningCertificate", text);
1960                         break;
1961
1962                 default:
1963                         break;
1964         }
1965 }
1966
1967 static GMarkupParser account_xml_parser = {
1968         migrate_parse_account_xml_start_element,
1969         migrate_parse_account_xml_end_element,
1970         migrate_parse_account_xml_text,
1971         NULL,  /* passthrough */
1972         NULL   /* error */
1973 };
1974
1975 static void
1976 migrate_parse_signature (ParseData *parse_data,
1977                          const gchar *element_name,
1978                          const gchar **attribute_names,
1979                          const gchar **attribute_values,
1980                          GError **error)
1981 {
1982         const gchar *uid;
1983         const gchar *name;
1984         const gchar *format;
1985         const gchar *config_dir;
1986         gchar *directory;
1987         gchar *absolute_path;
1988         gboolean autogenerated;
1989         gboolean success;
1990
1991         success = g_markup_collect_attributes (
1992                 element_name,
1993                 attribute_names,
1994                 attribute_values,
1995                 error,
1996                 G_MARKUP_COLLECT_STRING,
1997                 "uid", &uid,
1998                 G_MARKUP_COLLECT_STRING,
1999                 "name", &name,
2000                 G_MARKUP_COLLECT_BOOLEAN,
2001                 "auto", &autogenerated,
2002                 G_MARKUP_COLLECT_STRING |
2003                 G_MARKUP_COLLECT_OPTIONAL,
2004                 "format", &format,
2005                 G_MARKUP_COLLECT_INVALID);
2006
2007         if (!success)
2008                 return;
2009
2010         /* Skip the "autogenerated" signature. */
2011         if (autogenerated)
2012                 return;
2013
2014         parse_data->file = e_server_side_source_new_user_file (uid);
2015
2016         config_dir = e_get_user_config_dir ();
2017         directory = g_build_filename (config_dir, "signatures", NULL);
2018         absolute_path = g_build_filename (directory, uid, NULL);
2019         parse_data->signature_file = g_file_new_for_path (absolute_path);
2020         g_mkdir_with_parents (directory, 0700);
2021         g_free (absolute_path);
2022         g_free (directory);
2023
2024         /* If the file already exists, skip this source.  It may be that we
2025          * already migrated it, in which case we don't want to overwrite it. */
2026         if (g_file_query_exists (parse_data->file, NULL))
2027                 return;
2028
2029         parse_data->key_file = g_key_file_new ();
2030
2031         g_key_file_set_string (
2032                 parse_data->key_file,
2033                 E_SOURCE_GROUP_NAME,
2034                 "DisplayName", name);
2035
2036         g_key_file_set_string (
2037                 parse_data->key_file,
2038                 E_SOURCE_EXTENSION_MAIL_SIGNATURE,
2039                 "MimeType", format);
2040 }
2041
2042 static void
2043 migrate_parse_signature_xml_start_element (GMarkupParseContext *context,
2044                                            const gchar *element_name,
2045                                            const gchar **attribute_names,
2046                                            const gchar **attribute_values,
2047                                            gpointer user_data,
2048                                            GError **error)
2049 {
2050         ParseData *parse_data = user_data;
2051
2052         if (g_strcmp0 (element_name, "filename") == 0) {
2053                 if (parse_data->state != PARSE_STATE_IN_SIGNATURE)
2054                         goto invalid_content;
2055
2056                 parse_data->state = PARSE_STATE_IN_FILENAME;
2057
2058                 g_markup_collect_attributes (
2059                         element_name,
2060                         attribute_names,
2061                         attribute_values,
2062                         error,
2063                         G_MARKUP_COLLECT_BOOLEAN |
2064                         G_MARKUP_COLLECT_OPTIONAL,
2065                         "script", &parse_data->is_script,
2066                         G_MARKUP_COLLECT_INVALID);
2067
2068                 return;
2069         }
2070
2071         if (g_strcmp0 (element_name, "signature") == 0) {
2072                 if (parse_data->state != PARSE_STATE_IN_SIGNATURES_VALUE)
2073                         goto invalid_content;
2074
2075                 parse_data->state = PARSE_STATE_IN_SIGNATURE;
2076
2077                 migrate_parse_signature (
2078                         parse_data,
2079                         element_name,
2080                         attribute_names,
2081                         attribute_values,
2082                         error);
2083
2084                 return;
2085         }
2086
2087         g_set_error (
2088                 error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
2089                 "Unknown element <%s>", element_name);
2090
2091         return;
2092
2093 invalid_content:
2094
2095         g_set_error (
2096                 error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
2097                 "Element <%s> at unexpected location", element_name);
2098 }
2099
2100 static void
2101 migrate_parse_signature_xml_end_element (GMarkupParseContext *context,
2102                                          const gchar *element_name,
2103                                          gpointer user_data,
2104                                          GError **error)
2105 {
2106         ParseData *parse_data = user_data;
2107
2108         if (g_strcmp0 (element_name, "filename") == 0) {
2109                 if (parse_data->state == PARSE_STATE_IN_FILENAME) {
2110                         parse_data->state = PARSE_STATE_IN_SIGNATURE;
2111
2112                         return;
2113                 }
2114         }
2115
2116         if (g_strcmp0 (element_name, "signature") == 0) {
2117                 if (parse_data->state == PARSE_STATE_IN_SIGNATURE) {
2118                         parse_data->state = PARSE_STATE_IN_SIGNATURES_VALUE;
2119
2120                         /* Clean up <signature> tag data. */
2121
2122                         /* The key file will be NULL if we decided to skip it.
2123                          * e.g. A file with the same UID may already exist. */
2124                         if (parse_data->key_file != NULL) {
2125                                 GError *local_error = NULL;
2126
2127                                 if (!parse_data->skip)
2128                                         migrate_parse_commit_changes (
2129                                                 parse_data->type,
2130                                                 parse_data->file,
2131                                                 parse_data->key_file,
2132                                                 NULL, &local_error);
2133
2134                                 if (local_error != NULL) {
2135                                         g_printerr (
2136                                                 "  FAILED: %s\n",
2137                                                 local_error->message);
2138                                         g_error_free (local_error);
2139                                 }
2140
2141                                 g_key_file_free (parse_data->key_file);
2142                                 parse_data->key_file = NULL;
2143                         }
2144
2145                         if (parse_data->file != NULL) {
2146                                 g_object_unref (parse_data->file);
2147                                 parse_data->file = NULL;
2148                         }
2149
2150                         parse_data->skip = FALSE;
2151
2152                         return;
2153                 }
2154         }
2155 }
2156
2157 static void
2158 migrate_parse_signature_xml_text (GMarkupParseContext *context,
2159                                   const gchar *text,
2160                                   gsize text_len,
2161                                   gpointer user_data,
2162                                   GError **error)
2163 {
2164         ParseData *parse_data = user_data;
2165
2166         if (parse_data->state == PARSE_STATE_IN_FILENAME) {
2167                 GFile *old_signature_file;
2168                 GFile *new_signature_file;
2169                 const gchar *data_dir;
2170                 gchar *absolute_path;
2171
2172                 /* Note we're moving the signature files
2173                  * from $XDG_DATA_HOME to $XDG_CONFIG_HOME. */
2174                 data_dir = e_get_user_data_dir ();
2175
2176                 /* Text should be either an absolute file name
2177                  * or a base file name with no path components. */
2178                 if (g_path_is_absolute (text))
2179                         absolute_path = g_strdup (text);
2180                 else
2181                         absolute_path = g_build_filename (
2182                                 data_dir, "signatures", text, NULL);
2183
2184                 old_signature_file = g_file_new_for_path (absolute_path);
2185                 new_signature_file = parse_data->signature_file;
2186                 parse_data->signature_file = NULL;
2187
2188                 /* If the signature is a script, we symlink to it.
2189                  * Otherwise we move and rename the regular file. */
2190                 if (parse_data->is_script)
2191                         g_file_make_symbolic_link (
2192                                 new_signature_file,
2193                                 absolute_path, NULL, error);
2194                 else
2195                         g_file_move (
2196                                 old_signature_file,
2197                                 new_signature_file,
2198                                 G_FILE_COPY_NONE,
2199                                 NULL, NULL, NULL, error);
2200
2201                 g_object_unref (old_signature_file);
2202                 g_object_unref (new_signature_file);
2203                 g_free (absolute_path);
2204         }
2205 }
2206
2207 static GMarkupParser signature_xml_parser = {
2208         migrate_parse_signature_xml_start_element,
2209         migrate_parse_signature_xml_end_element,
2210         migrate_parse_signature_xml_text,
2211         NULL,  /* passthrough */
2212         NULL   /* error */
2213 };
2214
2215 static void
2216 migrate_parse_local_calendar_property (ParseData *parse_data,
2217                                        const gchar *property_name,
2218                                        const gchar *property_value)
2219 {
2220         if (g_strcmp0 (property_name, "custom-file") == 0) {
2221                 gchar *uri;
2222
2223                 /* Property value is a local filename.  Convert it to a
2224                  * "file://" URI.
2225                  *
2226                  * Note: The key is named "CustomFile" instead of, say,
2227                  * "CustomURI" because the corresponding ESourceExtension
2228                  * property is a GFile.  The fact that ESource saves GFile
2229                  * properties as URI strings is an implementation detail. */
2230                 uri = g_filename_to_uri (property_value, NULL, NULL);
2231                 if (uri != NULL) {
2232                         g_key_file_set_string (
2233                                 parse_data->key_file,
2234                                 E_SOURCE_EXTENSION_LOCAL_BACKEND,
2235                                 "CustomFile", uri);
2236                         g_free (uri);
2237                 }
2238         }
2239 }
2240
2241 static void
2242 migrate_parse_local_source (ParseData *parse_data)
2243 {
2244         if (parse_data->type != PARSE_TYPE_ADDRESSBOOK)
2245                 parse_data->property_func =
2246                         migrate_parse_local_calendar_property;
2247
2248         /* Local ADDRESS BOOK Backend has no special properties to parse. */
2249 }
2250
2251 static void
2252 migrate_parse_caldav_property (ParseData *parse_data,
2253                                const gchar *property_name,
2254                                const gchar *property_value)
2255 {
2256         if (g_strcmp0 (property_name, "autoschedule") == 0) {
2257                 g_key_file_set_boolean (
2258                         parse_data->key_file,
2259                         E_SOURCE_EXTENSION_WEBDAV_BACKEND,
2260                         "CalendarAutoSchedule",
2261                         is_true (property_value));
2262
2263         } else if (g_strcmp0 (property_name, "usermail") == 0) {
2264                 g_key_file_set_string (
2265                         parse_data->key_file,
2266                         E_SOURCE_EXTENSION_WEBDAV_BACKEND,
2267                         "EmailAddress", property_value);
2268         }
2269 }
2270
2271 static void
2272 migrate_parse_caldav_source (ParseData *parse_data)
2273 {
2274         if (parse_data->soup_uri->host != NULL)
2275                 g_key_file_set_string (
2276                         parse_data->key_file,
2277                         E_SOURCE_EXTENSION_AUTHENTICATION,
2278                         "Host", parse_data->soup_uri->host);
2279
2280         /* We may override this later if we see an "ssl" property. */
2281         if (parse_data->soup_uri->port == 0)
2282                 parse_data->soup_uri->port = 80;
2283
2284         g_key_file_set_integer (
2285                 parse_data->key_file,
2286                 E_SOURCE_EXTENSION_AUTHENTICATION,
2287                 "Port", parse_data->soup_uri->port);
2288
2289         if (parse_data->soup_uri->user != NULL)
2290                 g_key_file_set_string (
2291                         parse_data->key_file,
2292                         E_SOURCE_EXTENSION_AUTHENTICATION,
2293                         "User", parse_data->soup_uri->user);
2294
2295         if (parse_data->soup_uri->path != NULL)
2296                 g_key_file_set_string (
2297                         parse_data->key_file,
2298                         E_SOURCE_EXTENSION_WEBDAV_BACKEND,
2299                         "ResourcePath", parse_data->soup_uri->path);
2300
2301         if (parse_data->soup_uri->query != NULL)
2302                 g_key_file_set_string (
2303                         parse_data->key_file,
2304                         E_SOURCE_EXTENSION_WEBDAV_BACKEND,
2305                         "ResourceQuery", parse_data->soup_uri->query);
2306
2307         parse_data->property_func = migrate_parse_caldav_property;
2308 }
2309
2310 static void
2311 migrate_parse_google_calendar_property (ParseData *parse_data,
2312                                         const gchar *property_name,
2313                                         const gchar *property_value)
2314 {
2315         if (g_strcmp0 (property_name, "username") == 0) {
2316                 gchar *path;
2317
2318                 g_key_file_set_string (
2319                         parse_data->key_file,
2320                         E_SOURCE_EXTENSION_AUTHENTICATION,
2321                         "User", property_value);
2322
2323                 path = g_strdup_printf (
2324                         "/calendar/dav/%s/events", property_value);
2325
2326                 g_key_file_set_string (
2327                         parse_data->key_file,
2328                         E_SOURCE_EXTENSION_WEBDAV_BACKEND,
2329                         "ResourcePath", path);
2330
2331                 g_free (path);
2332         }
2333 }
2334
2335 static void
2336 migrate_parse_google_contacts_property (ParseData *parse_data,
2337                                         const gchar *property_name,
2338                                         const gchar *property_value)
2339 {
2340         if (g_strcmp0 (property_name, "refresh-interval") == 0) {
2341                 guint64 interval_seconds;
2342
2343                 interval_seconds =
2344                         g_ascii_strtoull (property_value, NULL, 10);
2345
2346                 if (interval_seconds >= 60) {
2347                         g_key_file_set_boolean (
2348                                 parse_data->key_file,
2349                                 E_SOURCE_EXTENSION_REFRESH,
2350                                 "Enabled", TRUE);
2351                         g_key_file_set_uint64 (
2352                                 parse_data->key_file,
2353                                 E_SOURCE_EXTENSION_REFRESH,
2354                                 "IntervalMinutes",
2355                                 interval_seconds / 60);
2356                 }
2357
2358         } else if (g_strcmp0 (property_name, "username") == 0) {
2359                 g_key_file_set_string (
2360                         parse_data->key_file,
2361                         E_SOURCE_EXTENSION_AUTHENTICATION,
2362                         "User", property_value);
2363         }
2364 }
2365
2366 static void
2367 migrate_parse_google_source (ParseData *parse_data)
2368 {
2369         if (parse_data->type == PARSE_TYPE_ADDRESSBOOK)
2370                 parse_data->property_func =
2371                         migrate_parse_google_contacts_property;
2372
2373         else {
2374                 g_key_file_set_string (
2375                         parse_data->key_file,
2376                         E_SOURCE_EXTENSION_AUTHENTICATION,
2377                         "Host", "www.google.com");
2378
2379                 g_key_file_set_integer (
2380                         parse_data->key_file,
2381                         E_SOURCE_EXTENSION_AUTHENTICATION,
2382                         "Port", 443);
2383
2384                 g_key_file_set_string (
2385                         parse_data->key_file,
2386                         E_SOURCE_EXTENSION_SECURITY,
2387                         "Method", "tls");
2388
2389                 parse_data->property_func =
2390                         migrate_parse_google_calendar_property;
2391         }
2392 }
2393
2394 static void
2395 migrate_parse_ldap_property (ParseData *parse_data,
2396                              const gchar *property_name,
2397                              const gchar *property_value)
2398 {
2399         if (g_strcmp0 (property_name, "can-browse") == 0) {
2400                 g_key_file_set_boolean (
2401                         parse_data->key_file,
2402                         E_SOURCE_EXTENSION_LDAP_BACKEND,
2403                         "CanBrowse",
2404                         is_true (property_value));
2405
2406         /* This is an integer value, but we can use the string as is. */
2407         } else if (g_strcmp0 (property_name, "limit") == 0) {
2408                 g_key_file_set_string (
2409                         parse_data->key_file,
2410                         E_SOURCE_EXTENSION_LDAP_BACKEND,
2411                         "Limit", property_value);
2412         }
2413 }
2414
2415 static void
2416 migrate_parse_ldap_source (ParseData *parse_data)
2417 {
2418         if (parse_data->soup_uri->host != NULL)
2419                 g_key_file_set_string (
2420                         parse_data->key_file,
2421                         E_SOURCE_EXTENSION_AUTHENTICATION,
2422                         "Host", parse_data->soup_uri->host);
2423
2424         if (parse_data->soup_uri->port != 0)
2425                 g_key_file_set_integer (
2426                         parse_data->key_file,
2427                         E_SOURCE_EXTENSION_AUTHENTICATION,
2428                         "Port", parse_data->soup_uri->port);
2429
2430         if (parse_data->soup_uri->user != NULL)
2431                 g_key_file_set_string (
2432                         parse_data->key_file,
2433                         E_SOURCE_EXTENSION_AUTHENTICATION,
2434                         "User", parse_data->soup_uri->user);
2435
2436         /* Skip the leading slash on the URI path to get the RootDn. */
2437         if (parse_data->soup_uri->path != NULL)
2438                 g_key_file_set_string (
2439                         parse_data->key_file,
2440                         E_SOURCE_EXTENSION_LDAP_BACKEND,
2441                         "RootDn", parse_data->soup_uri->path + 1);
2442
2443         if (g_strcmp0 (parse_data->soup_uri->query, "?sub?") == 0)
2444                 g_key_file_set_string (
2445                         parse_data->key_file,
2446                         E_SOURCE_EXTENSION_LDAP_BACKEND,
2447                         "Scope", "subtree");
2448
2449         if (g_strcmp0 (parse_data->soup_uri->query, "?one?") == 0)
2450                 g_key_file_set_string (
2451                         parse_data->key_file,
2452                         E_SOURCE_EXTENSION_LDAP_BACKEND,
2453                         "Scope", "onelevel");
2454
2455         parse_data->property_func = migrate_parse_ldap_property;
2456 }
2457
2458 static void
2459 migrate_parse_vcf_source (ParseData *parse_data)
2460 {
2461         if (parse_data->soup_uri->path != NULL)
2462                 g_key_file_set_string (
2463                         parse_data->key_file,
2464                         E_SOURCE_EXTENSION_VCF_BACKEND,
2465                         "Path", parse_data->soup_uri->path);
2466
2467         /* VCF Backend has no special properties to parse. */
2468 }
2469
2470 static void
2471 migrate_parse_weather_property (ParseData *parse_data,
2472                                 const gchar *property_name,
2473                                 const gchar *property_value)
2474 {
2475         /* XXX Temperature property was replaced by units... I think. */
2476         if (g_strcmp0 (property_name, "temperature") == 0) {
2477                 gboolean metric;
2478
2479                 metric = (g_strcmp0 (property_value, "fahrenheit") != 0);
2480
2481                 g_key_file_set_string (
2482                         parse_data->key_file,
2483                         E_SOURCE_EXTENSION_WEATHER_BACKEND,
2484                         "Units", metric ? "metric" : "imperial");
2485
2486         } else if (g_strcmp0 (property_name, "units") == 0) {
2487                 gboolean metric;
2488
2489                 metric = (g_strcmp0 (property_value, "metric") == 0);
2490
2491                 g_key_file_set_string (
2492                         parse_data->key_file,
2493                         E_SOURCE_EXTENSION_WEATHER_BACKEND,
2494                         "Units", metric ? "metric" : "imperial");
2495         }
2496 }
2497
2498 static void
2499 migrate_parse_weather_source (ParseData *parse_data)
2500 {
2501         /* Oh man, we actually try to shove a weather location into
2502          * a URI!  The station code winds up as the host component,
2503          * and the location name winds up as the path component. */
2504         if (parse_data->soup_uri->host != NULL) {
2505                 gchar *location;
2506
2507                 if (parse_data->soup_uri->path != NULL)
2508                         location = g_strconcat (
2509                                 parse_data->soup_uri->host,
2510                                 parse_data->soup_uri->path, NULL);
2511                 else
2512                         location = g_strdup (parse_data->soup_uri->host);
2513
2514                 g_key_file_set_string (
2515                         parse_data->key_file,
2516                         E_SOURCE_EXTENSION_WEATHER_BACKEND,
2517                         "Location", location);
2518
2519                 g_free (location);
2520         }
2521
2522         parse_data->property_func = migrate_parse_weather_property;
2523 }
2524
2525 static void
2526 migrate_parse_webcal_source (ParseData *parse_data)
2527 {
2528         if (parse_data->soup_uri->host != NULL)
2529                 g_key_file_set_string (
2530                         parse_data->key_file,
2531                         E_SOURCE_EXTENSION_AUTHENTICATION,
2532                         "Host", parse_data->soup_uri->host);
2533
2534         /* We may override this later if we see an "ssl" property. */
2535         if (parse_data->soup_uri->port == 0)
2536                 parse_data->soup_uri->port = 80;
2537
2538         g_key_file_set_integer (
2539                 parse_data->key_file,
2540                 E_SOURCE_EXTENSION_AUTHENTICATION,
2541                 "Port", parse_data->soup_uri->port);
2542
2543         if (parse_data->soup_uri->user != NULL)
2544                 g_key_file_set_string (
2545                         parse_data->key_file,
2546                         E_SOURCE_EXTENSION_AUTHENTICATION,
2547                         "User", parse_data->soup_uri->user);
2548
2549         if (parse_data->soup_uri->path != NULL)
2550                 g_key_file_set_string (
2551                         parse_data->key_file,
2552                         E_SOURCE_EXTENSION_WEBDAV_BACKEND,
2553                         "ResourcePath", parse_data->soup_uri->path);
2554
2555         if (parse_data->soup_uri->query != NULL)
2556                 g_key_file_set_string (
2557                         parse_data->key_file,
2558                         E_SOURCE_EXTENSION_WEBDAV_BACKEND,
2559                         "ResourceQuery", parse_data->soup_uri->query);
2560
2561         /* Webcal Backend has no special properties to parse. */
2562 }
2563
2564 static void
2565 migrate_parse_webdav_property (ParseData *parse_data,
2566                                const gchar *property_name,
2567                                const gchar *property_value)
2568 {
2569         if (g_strcmp0 (property_name, "avoid_ifmatch") == 0) {
2570                 g_key_file_set_boolean (
2571                         parse_data->key_file,
2572                         E_SOURCE_EXTENSION_WEBDAV_BACKEND,
2573                         "AvoidIfmatch",
2574                         is_true (property_value));
2575         }
2576 }
2577
2578 static void
2579 migrate_parse_webdav_source (ParseData *parse_data)
2580 {
2581         if (parse_data->soup_uri->host != NULL)
2582                 g_key_file_set_string (
2583                         parse_data->key_file,
2584                         E_SOURCE_EXTENSION_AUTHENTICATION,
2585                         "Host", parse_data->soup_uri->host);
2586
2587         if (parse_data->soup_uri->port != 0)
2588                 g_key_file_set_integer (
2589                         parse_data->key_file,
2590                         E_SOURCE_EXTENSION_AUTHENTICATION,
2591                         "Port", parse_data->soup_uri->port);
2592
2593         if (parse_data->soup_uri->user != NULL)
2594                 g_key_file_set_string (
2595                         parse_data->key_file,
2596                         E_SOURCE_EXTENSION_AUTHENTICATION,
2597                         "User", parse_data->soup_uri->user);
2598
2599         if (parse_data->soup_uri->path != NULL)
2600                 g_key_file_set_string (
2601                         parse_data->key_file,
2602                         E_SOURCE_EXTENSION_WEBDAV_BACKEND,
2603                         "ResourcePath", parse_data->soup_uri->path);
2604
2605         if (parse_data->soup_uri->query != NULL)
2606                 g_key_file_set_string (
2607                         parse_data->key_file,
2608                         E_SOURCE_EXTENSION_WEBDAV_BACKEND,
2609                         "ResourceQuery", parse_data->soup_uri->query);
2610
2611         parse_data->property_func = migrate_parse_webdav_property;
2612 }
2613
2614 static void
2615 migrate_parse_group (ParseData *parse_data,
2616                      const gchar *element_name,
2617                      const gchar **attribute_names,
2618                      const gchar **attribute_values,
2619                      GError **error)
2620 {
2621         const gchar *base_uri;
2622         gboolean success;
2623
2624         success = g_markup_collect_attributes (
2625                 element_name,
2626                 attribute_names,
2627                 attribute_values,
2628                 error,
2629                 G_MARKUP_COLLECT_STRING,
2630                 "uid", NULL,
2631                 G_MARKUP_COLLECT_STRING,
2632                 "name", NULL,
2633                 G_MARKUP_COLLECT_STRING,
2634                 "base_uri", &base_uri,
2635                 G_MARKUP_COLLECT_BOOLEAN |
2636                 G_MARKUP_COLLECT_OPTIONAL,
2637                 "readonly", NULL,
2638                 G_MARKUP_COLLECT_INVALID);
2639
2640         if (!success)
2641                 return;
2642
2643         /* Convert "file://" schemes to "local:". */
2644         if (g_strcmp0 (base_uri, "file://") == 0)
2645                 base_uri = "local:";
2646
2647         parse_data->base_uri = g_strdup (base_uri);
2648 }
2649
2650 static void
2651 migrate_parse_source (ParseData *parse_data,
2652                       const gchar *element_name,
2653                       const gchar **attribute_names,
2654                       const gchar **attribute_values,
2655                       GError **error)
2656 {
2657         const gchar *uid;
2658         const gchar *name;
2659         const gchar *color_spec;
2660         const gchar *group_name;
2661         const gchar *absolute_uri;
2662         const gchar *relative_uri;
2663         gchar *backend_name;
2664         gchar *parent_name;
2665         gchar *uri_string;
2666         gchar *cp;
2667         gboolean success;
2668         gboolean is_google_calendar;
2669
2670         success = g_markup_collect_attributes (
2671                 element_name,
2672                 attribute_names,
2673                 attribute_values,
2674                 error,
2675                 G_MARKUP_COLLECT_STRING,
2676                 "uid", &uid,
2677                 G_MARKUP_COLLECT_STRING,
2678                 "name", &name,
2679                 G_MARKUP_COLLECT_STRING |
2680                 G_MARKUP_COLLECT_OPTIONAL,
2681                 "color_spec", &color_spec,
2682                 G_MARKUP_COLLECT_STRING,
2683                 "relative_uri", &relative_uri,
2684                 G_MARKUP_COLLECT_STRING |
2685                 G_MARKUP_COLLECT_OPTIONAL,
2686                 "uri", &absolute_uri,
2687                 G_MARKUP_COLLECT_INVALID);
2688
2689         if (!success)
2690                 return;
2691
2692         /* Don't try and migrate the "system" sources, as
2693          * we'll defer to the built-in "system-*" key files. */
2694         if (g_strcmp0 (relative_uri, "system") == 0)
2695                 return;
2696
2697         /* Also skip any sources with a "contacts://" base URI, which
2698          * should just be "Birthdays & Anniversaries".  We'll reset to
2699          * the built-in key file. */
2700         if (g_strcmp0 (parse_data->base_uri, "contacts://") == 0)
2701                 return;
2702
2703         /* Also skip any sources for groupware extensions, as these are
2704          * no longer saved to disk.  We do have a mechanism in place for
2705          * remembering the UIDs of memory-only sources so that cached
2706          * data can be reused, but let's not bother with it here.  Let
2707          * the sources be set up fresh. */
2708         if (base_uri_is_groupware (parse_data->base_uri))
2709                 return;
2710
2711         switch (parse_data->type) {
2712                 case PARSE_TYPE_ADDRESSBOOK:
2713                         group_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
2714                         break;
2715                 case PARSE_TYPE_CALENDAR:
2716                         group_name = E_SOURCE_EXTENSION_CALENDAR;
2717                         break;
2718                 case PARSE_TYPE_TASKS:
2719                         group_name = E_SOURCE_EXTENSION_TASK_LIST;
2720                         break;
2721                 case PARSE_TYPE_MEMOS:
2722                         group_name = E_SOURCE_EXTENSION_MEMO_LIST;
2723                         break;
2724                 default:
2725                         g_return_if_reached ();
2726         }
2727
2728         parse_data->file = e_server_side_source_new_user_file (uid);
2729
2730         /* If the file already exists, skip this source.  It may be that we
2731          * already migrated it, in which case we don't want to overwrite it. */
2732         if (g_file_query_exists (parse_data->file, NULL))
2733                 return;
2734
2735         parse_data->key_file = g_key_file_new ();
2736
2737         /* Trim ':' or '://' off the base_uri to get the backend name. */
2738         backend_name = g_strdup (parse_data->base_uri);
2739         if ((cp = strchr (backend_name, ':')) != NULL)
2740                 *cp = '\0';
2741
2742         /* The parent name is generally the backend name + "-stub". */
2743         parent_name = g_strdup_printf ("%s-stub", backend_name);
2744
2745         g_key_file_set_string (
2746                 parse_data->key_file,
2747                 E_SOURCE_GROUP_NAME,
2748                 "DisplayName", name);
2749
2750         g_key_file_set_string (
2751                 parse_data->key_file,
2752                 E_SOURCE_GROUP_NAME,
2753                 "Parent", parent_name);
2754
2755         if (color_spec != NULL)
2756                 g_key_file_set_string (
2757                         parse_data->key_file, group_name,
2758                         "Color", color_spec);
2759
2760         is_google_calendar =
2761                 (parse_data->type == PARSE_TYPE_CALENDAR) &&
2762                 (g_strcmp0 (parse_data->base_uri, "google://") == 0);
2763
2764         /* For Google Calendar sources we override the backend name. */
2765         if (is_google_calendar)
2766                 g_key_file_set_string (
2767                         parse_data->key_file, group_name,
2768                         "BackendName", "caldav");
2769         else
2770                 g_key_file_set_string (
2771                         parse_data->key_file, group_name,
2772                         "BackendName", backend_name);
2773
2774         g_free (backend_name);
2775         g_free (parent_name);
2776
2777         /* Prefer absolute URIs over relative URIs.  All these
2778          * other strange rules are for backward-compatibility. */
2779         if (absolute_uri != NULL)
2780                 uri_string = g_strdup (absolute_uri);
2781         else if (g_str_has_suffix (parse_data->base_uri, "/"))
2782                 uri_string = g_strconcat (
2783                         parse_data->base_uri, relative_uri, NULL);
2784         else if (g_strcmp0 (parse_data->base_uri, "local:") == 0)
2785                 uri_string = g_strconcat (
2786                         parse_data->base_uri, relative_uri, NULL);
2787         else
2788                 uri_string = g_strconcat (
2789                         parse_data->base_uri, "/", relative_uri, NULL);
2790
2791         parse_data->soup_uri = soup_uri_new (uri_string);
2792
2793         /* Mangle the URI to not contain invalid characters.  We'll need
2794          * this later to rename the source's cache and data directories. */
2795         parse_data->mangled_uri = g_strdelimit (uri_string, ":/", '_');
2796
2797         /* g_strdelimit() modifies the input string in place, so ParseData
2798          * now owns 'uri_string'.  Clear the pointer to emphasize that. */
2799         uri_string = NULL;
2800
2801         if (parse_data->soup_uri == NULL) {
2802                 g_warning (
2803                         "  Failed to parse source URI: %s",
2804                         (absolute_uri != NULL) ? absolute_uri : relative_uri);
2805                 g_key_file_free (parse_data->key_file);
2806                 parse_data->key_file = NULL;
2807                 return;
2808         }
2809
2810         if (g_strcmp0 (parse_data->base_uri, "local:") == 0)
2811                 migrate_parse_local_source (parse_data);
2812
2813         else if (g_strcmp0 (parse_data->base_uri, "caldav://") == 0)
2814                 migrate_parse_caldav_source (parse_data);
2815
2816         else if (g_strcmp0 (parse_data->base_uri, "google://") == 0)
2817                 migrate_parse_google_source (parse_data);
2818
2819         else if (g_strcmp0 (parse_data->base_uri, "ldap://") == 0)
2820                 migrate_parse_ldap_source (parse_data);
2821
2822         else if (g_strcmp0 (parse_data->base_uri, "vcf://") == 0)
2823                 migrate_parse_vcf_source (parse_data);
2824
2825         else if (g_strcmp0 (parse_data->base_uri, "weather://") == 0)
2826                 migrate_parse_weather_source (parse_data);
2827
2828         else if (g_strcmp0 (parse_data->base_uri, "webcal://") == 0)
2829                 migrate_parse_webcal_source (parse_data);
2830
2831         else if (g_strcmp0 (parse_data->base_uri, "webdav://") == 0)
2832                 migrate_parse_webdav_source (parse_data);
2833
2834         migrate_keyring_entry (
2835                 uid,
2836                 parse_data->soup_uri->user,
2837                 parse_data->soup_uri->host,
2838                 parse_data->soup_uri->scheme);
2839 }
2840
2841 static void
2842 migrate_parse_property (ParseData *parse_data,
2843                         const gchar *element_name,
2844                         const gchar **attribute_names,
2845                         const gchar **attribute_values,
2846                         GError **error)
2847 {
2848         const gchar *property_name;
2849         const gchar *property_value;
2850         gboolean success;
2851
2852         success = g_markup_collect_attributes (
2853                 element_name,
2854                 attribute_names,
2855                 attribute_values,
2856                 error,
2857                 G_MARKUP_COLLECT_STRING,
2858                 "name", &property_name,
2859                 G_MARKUP_COLLECT_STRING,
2860                 "value", &property_value,
2861                 G_MARKUP_COLLECT_INVALID);
2862
2863         if (!success)
2864                 return;
2865
2866         if (g_strcmp0 (property_name, "alarm") == 0) {
2867                 g_key_file_set_boolean (
2868                         parse_data->key_file,
2869                         E_SOURCE_EXTENSION_ALARMS,
2870                         "IncludeMe",
2871                         is_true (property_value));
2872
2873         } else if (g_strcmp0 (property_name, "auth") == 0) {
2874                 if (is_true (property_value))
2875                         g_key_file_set_string (
2876                                 parse_data->key_file,
2877                                 E_SOURCE_EXTENSION_AUTHENTICATION,
2878                                 "Method", "plain/password");
2879                 else if (is_false (property_value))
2880                         g_key_file_set_string (
2881                                 parse_data->key_file,
2882                                 E_SOURCE_EXTENSION_AUTHENTICATION,
2883                                 "Method", "none");
2884                 else
2885                         g_key_file_set_string (
2886                                 parse_data->key_file,
2887                                 E_SOURCE_EXTENSION_AUTHENTICATION,
2888                                 "Method", property_value);
2889
2890         } else if (g_strcmp0 (property_name, "completion") == 0) {
2891                 g_key_file_set_boolean (
2892                         parse_data->key_file,
2893                         E_SOURCE_EXTENSION_AUTOCOMPLETE,
2894                         "IncludeMe",
2895                         is_true (property_value));
2896
2897         /* If we see a "goa-account-id" property, skip the entire
2898          * source and let the online-accounts module recreate it. */
2899         } else if (g_strcmp0 (property_name, "goa-account-id") == 0) {
2900                 parse_data->skip = TRUE;
2901
2902         } else if (g_strcmp0 (property_name, "last-notified") == 0) {
2903                 g_key_file_set_string (
2904                         parse_data->key_file,
2905                         E_SOURCE_EXTENSION_ALARMS,
2906                         "LastNotified", property_value);
2907
2908         } else if (g_strcmp0 (property_name, "offline_sync") == 0) {
2909                 g_key_file_set_boolean (
2910                         parse_data->key_file,
2911                         E_SOURCE_EXTENSION_OFFLINE,
2912                         "StaySynchronized",
2913                         is_true (property_value));
2914
2915         } else if (g_strcmp0 (property_name, "refresh") == 0) {
2916                 g_key_file_set_boolean (
2917                         parse_data->key_file,
2918                         E_SOURCE_EXTENSION_REFRESH,
2919                         "Enabled", TRUE);
2920                 g_key_file_set_string (
2921                         parse_data->key_file,
2922                         E_SOURCE_EXTENSION_REFRESH,
2923                         "IntervalMinutes", property_value);
2924
2925         } else if (g_strcmp0 (property_name, "remember_password") == 0) {
2926                 g_key_file_set_boolean (
2927                         parse_data->key_file,
2928                         E_SOURCE_EXTENSION_AUTHENTICATION,
2929                         "RememberPassword",
2930                         is_true (property_value));
2931
2932         } else if (g_strcmp0 (property_name, "ssl") == 0) {
2933                 if (is_true (property_value))
2934                         g_key_file_set_string (
2935                                 parse_data->key_file,
2936                                 E_SOURCE_EXTENSION_SECURITY,
2937                                 "Method", "tls");
2938                 else if (is_false (property_value))
2939                         g_key_file_set_string (
2940                                 parse_data->key_file,
2941                                 E_SOURCE_EXTENSION_SECURITY,
2942                                 "Method", "none");
2943
2944                 /* These next two are LDAP-specific. */
2945                 else if (g_strcmp0 (property_value, "always") == 0)
2946                         g_key_file_set_string (
2947                                 parse_data->key_file,
2948                                 E_SOURCE_EXTENSION_SECURITY,
2949                                 "Method", "starttls");
2950                 else if (g_strcmp0 (property_value, "whenever_possible") == 0)
2951                         g_key_file_set_string (
2952                                 parse_data->key_file,
2953                                 E_SOURCE_EXTENSION_SECURITY,
2954                                 "Method", "ldaps");
2955
2956                 /* For WebDAV-based backends we set the port to 80
2957                  * (http://) by default.  If we see that and we're
2958                  * using a secure connection, bump the port to 443
2959                  * (https://). */
2960                 if (parse_data->soup_uri->port == 80)
2961                         if (is_true (property_value))
2962                                 g_key_file_set_integer (
2963                                         parse_data->key_file,
2964                                         E_SOURCE_EXTENSION_AUTHENTICATION,
2965                                         "Port", 443);
2966
2967         } else if (g_strcmp0 (property_name, "use_ssl") == 0) {
2968                 g_key_file_set_string (
2969                         parse_data->key_file,
2970                         E_SOURCE_EXTENSION_SECURITY,
2971                         "Method",
2972                         is_true (property_value) ?
2973                         "tls" : "none");
2974
2975                 /* For WebDAV-based backends we set the port to 80
2976                  * (http://) by default.  If we see that and we're
2977                  * using a secure connection, bump the port to 443
2978                  * (https://). */
2979                 if (parse_data->soup_uri->port == 80)
2980                         if (is_true (property_value))
2981                                 g_key_file_set_integer (
2982                                         parse_data->key_file,
2983                                         E_SOURCE_EXTENSION_AUTHENTICATION,
2984                                         "Port", 443);
2985
2986         } else if (g_strcmp0 (property_name, "use-in-contacts-calendar") == 0) {
2987                 g_key_file_set_boolean (
2988                         parse_data->key_file,
2989                         E_SOURCE_EXTENSION_CONTACTS_BACKEND,
2990                         "IncludeMe",
2991                         is_true (property_value));
2992
2993         } else if (parse_data->property_func != NULL) {
2994                 parse_data->property_func (
2995                         parse_data, property_name, property_value);
2996         }
2997 }
2998
2999 static void
3000 migrate_parse_source_xml_start_element (GMarkupParseContext *context,
3001                                         const gchar *element_name,
3002                                         const gchar **attribute_names,
3003                                         const gchar **attribute_values,
3004                                         gpointer user_data,
3005                                         GError **error)
3006 {
3007         ParseData *parse_data = user_data;
3008
3009         if (g_strcmp0 (element_name, "group") == 0) {
3010                 if (parse_data->state != PARSE_STATE_IN_SOURCES_VALUE)
3011                         goto invalid_content;
3012
3013                 parse_data->state = PARSE_STATE_IN_GROUP;
3014
3015                 migrate_parse_group (
3016                         parse_data,
3017                         element_name,
3018                         attribute_names,
3019                         attribute_values,
3020                         error);
3021
3022                 return;
3023         }
3024
3025         if (g_strcmp0 (element_name, "source") == 0) {
3026                 if (parse_data->state != PARSE_STATE_IN_GROUP)
3027                         goto invalid_content;
3028
3029                 parse_data->state = PARSE_STATE_IN_SOURCE;
3030
3031                 migrate_parse_source (
3032                         parse_data,
3033                         element_name,
3034                         attribute_names,
3035                         attribute_values,
3036                         error);
3037
3038                 return;
3039         }
3040
3041         if (g_strcmp0 (element_name, "properties") == 0) {
3042                 /* Disregard group properties, we're only
3043                  * interested in source properties. */
3044                 if (parse_data->state == PARSE_STATE_IN_GROUP)
3045                         return;
3046
3047                 if (parse_data->state != PARSE_STATE_IN_SOURCE)
3048                         goto invalid_content;
3049
3050                 parse_data->state = PARSE_STATE_IN_PROPERTIES;
3051
3052                 return;
3053         }
3054
3055         if (g_strcmp0 (element_name, "property") == 0) {
3056                 /* Disregard group properties, we're only
3057                  * interested in source properties. */
3058                 if (parse_data->state == PARSE_STATE_IN_GROUP)
3059                         return;
3060
3061                 if (parse_data->state != PARSE_STATE_IN_PROPERTIES)
3062                         goto invalid_content;
3063
3064                 /* The key file will be NULL if we decided to skip it.
3065                  * e.g. A file with the same UID may already exist. */
3066                 if (parse_data->key_file != NULL)
3067                         migrate_parse_property (
3068                                 parse_data,
3069                                 element_name,
3070                                 attribute_names,
3071                                 attribute_values,
3072                                 error);
3073
3074                 return;
3075         }
3076
3077         g_set_error (
3078                 error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
3079                 "Unknown element <%s>", element_name);
3080
3081         return;
3082
3083 invalid_content:
3084
3085         g_set_error (
3086                 error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
3087                 "Element <%s> at unexpected location", element_name);
3088 }
3089
3090 static void
3091 migrate_parse_source_xml_end_element (GMarkupParseContext *context,
3092                                       const gchar *element_name,
3093                                       gpointer user_data,
3094                                       GError **error)
3095 {
3096         ParseData *parse_data = user_data;
3097
3098         if (g_strcmp0 (element_name, "group") == 0) {
3099                 if (parse_data->state == PARSE_STATE_IN_GROUP) {
3100                         parse_data->state = PARSE_STATE_IN_SOURCES_VALUE;
3101
3102                         /* Clean up <group> tag data. */
3103
3104                         g_free (parse_data->base_uri);
3105                         parse_data->base_uri = NULL;
3106                 }
3107                 return;
3108         }
3109
3110         if (g_strcmp0 (element_name, "source") == 0) {
3111                 if (parse_data->state == PARSE_STATE_IN_SOURCE) {
3112                         parse_data->state = PARSE_STATE_IN_GROUP;
3113
3114                         /* Clean up <source> tag data. */
3115
3116                         /* The key file will be NULL if we decided to skip it.
3117                          * e.g. A file with the same UID may already exist. */
3118                         if (parse_data->key_file != NULL) {
3119                                 GError *local_error = NULL;
3120
3121                                 if (!parse_data->skip)
3122                                         migrate_parse_commit_changes (
3123                                                 parse_data->type,
3124                                                 parse_data->file,
3125                                                 parse_data->key_file,
3126                                                 parse_data->mangled_uri,
3127                                                 &local_error);
3128
3129                                 if (local_error != NULL) {
3130                                         g_printerr (
3131                                                 "  FAILED: %s\n",
3132                                                 local_error->message);
3133                                         g_error_free (local_error);
3134                                 }
3135
3136                                 g_key_file_free (parse_data->key_file);
3137                                 parse_data->key_file = NULL;
3138                         }
3139
3140                         if (parse_data->file != NULL) {
3141                                 g_object_unref (parse_data->file);
3142                                 parse_data->file = NULL;
3143                         }
3144
3145                         g_free (parse_data->mangled_uri);
3146                         parse_data->mangled_uri = NULL;
3147
3148                         if (parse_data->soup_uri != NULL) {
3149                                 soup_uri_free (parse_data->soup_uri);
3150                                 parse_data->soup_uri = NULL;
3151                         }
3152
3153                         parse_data->property_func = NULL;
3154
3155                         parse_data->skip = FALSE;
3156                 }
3157                 return;
3158         }
3159
3160         if (g_strcmp0 (element_name, "properties") == 0) {
3161                 if (parse_data->state == PARSE_STATE_IN_PROPERTIES)
3162                         parse_data->state = PARSE_STATE_IN_SOURCE;
3163                 return;
3164         }
3165 }
3166
3167 static GMarkupParser source_xml_parser = {
3168         migrate_parse_source_xml_start_element,
3169         migrate_parse_source_xml_end_element,
3170         NULL,  /* text */
3171         NULL,  /* passthrough */
3172         NULL   /* error */
3173 };
3174
3175 static void
3176 migrate_parse_gconf_xml_start_element (GMarkupParseContext *context,
3177                                        const gchar *element_name,
3178                                        const gchar **attribute_names,
3179                                        const gchar **attribute_values,
3180                                        gpointer user_data,
3181                                        GError **error)
3182 {
3183         ParseData *parse_data = user_data;
3184
3185         if (g_strcmp0 (element_name, "gconf") == 0) {
3186                 if (parse_data->state != PARSE_STATE_INITIAL)
3187                         goto invalid_content;
3188
3189                 parse_data->state = PARSE_STATE_IN_GCONF;
3190
3191                 return;
3192         }
3193
3194         if (g_strcmp0 (element_name, "entry") == 0) {
3195                 const gchar *name;
3196                 gboolean success;
3197
3198                 if (parse_data->state != PARSE_STATE_IN_GCONF)
3199                         goto invalid_content;
3200
3201                 success = g_markup_collect_attributes (
3202                         element_name,
3203                         attribute_names,
3204                         attribute_values,
3205                         error,
3206                         G_MARKUP_COLLECT_STRING,
3207                         "name", &name,
3208                         G_MARKUP_COLLECT_STRING,
3209                         "mtime", NULL,
3210                         G_MARKUP_COLLECT_STRING |
3211                         G_MARKUP_COLLECT_OPTIONAL,
3212                         "muser", NULL,
3213                         G_MARKUP_COLLECT_STRING |
3214                         G_MARKUP_COLLECT_OPTIONAL,
3215                         "type", NULL,
3216                         G_MARKUP_COLLECT_STRING |
3217                         G_MARKUP_COLLECT_OPTIONAL,
3218                         "ltype", NULL,
3219                         G_MARKUP_COLLECT_STRING |
3220                         G_MARKUP_COLLECT_OPTIONAL,
3221                         "schema", NULL,
3222                         G_MARKUP_COLLECT_STRING |
3223                         G_MARKUP_COLLECT_OPTIONAL,
3224                         "value", NULL,
3225                         G_MARKUP_COLLECT_INVALID);
3226
3227                 if (success && g_strcmp0 (name, "accounts") == 0)
3228                         parse_data->state = PARSE_STATE_IN_ACCOUNTS_ENTRY;
3229
3230                 if (success && g_strcmp0 (name, "signatures") == 0)
3231                         parse_data->state = PARSE_STATE_IN_SIGNATURES_ENTRY;
3232
3233                 if (success && g_strcmp0 (name, "sources") == 0)
3234                         parse_data->state = PARSE_STATE_IN_SOURCES_ENTRY;
3235
3236                 return;
3237         }
3238
3239         if (g_strcmp0 (element_name, "li") == 0)
3240                 return;
3241
3242         if (g_strcmp0 (element_name, "stringvalue") == 0) {
3243                 if (parse_data->state == PARSE_STATE_IN_ACCOUNTS_ENTRY)
3244                         parse_data->state = PARSE_STATE_IN_ACCOUNTS_VALUE;
3245
3246                 if (parse_data->state == PARSE_STATE_IN_SIGNATURES_ENTRY)
3247                         parse_data->state = PARSE_STATE_IN_SIGNATURES_VALUE;
3248
3249                 if (parse_data->state == PARSE_STATE_IN_SOURCES_ENTRY)
3250                         parse_data->state = PARSE_STATE_IN_SOURCES_VALUE;
3251
3252                 return;
3253         }
3254
3255         g_set_error (
3256                 error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
3257                 "Unknown element <%s>", element_name);
3258
3259         return;
3260
3261 invalid_content:
3262
3263         g_set_error (
3264                 error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
3265                 "Element <%s> at unexpected location", element_name);
3266 }
3267
3268 static void
3269 migrate_parse_gconf_xml_end_element (GMarkupParseContext *context,
3270                                      const gchar *element_name,
3271                                      gpointer user_data,
3272                                      GError **error)
3273 {
3274         ParseData *parse_data = user_data;
3275
3276         if (g_strcmp0 (element_name, "gconf") == 0) {
3277                 if (parse_data->state == PARSE_STATE_IN_GCONF)
3278                         parse_data->state = PARSE_STATE_INITIAL;
3279
3280                 return;
3281         }
3282
3283         if (g_strcmp0 (element_name, "entry") == 0) {
3284                 if (parse_data->state == PARSE_STATE_IN_ACCOUNTS_ENTRY)
3285                         parse_data->state = PARSE_STATE_IN_GCONF;
3286
3287                 if (parse_data->state == PARSE_STATE_IN_SIGNATURES_ENTRY)
3288                         parse_data->state = PARSE_STATE_IN_GCONF;
3289
3290                 if (parse_data->state == PARSE_STATE_IN_SOURCES_ENTRY)
3291                         parse_data->state = PARSE_STATE_IN_GCONF;
3292
3293                 return;
3294         }
3295
3296         if (g_strcmp0 (element_name, "stringvalue") == 0) {
3297                 if (parse_data->state == PARSE_STATE_IN_ACCOUNTS_VALUE)
3298                         parse_data->state = PARSE_STATE_IN_ACCOUNTS_ENTRY;
3299
3300                 if (parse_data->state == PARSE_STATE_IN_SIGNATURES_VALUE)
3301                         parse_data->state = PARSE_STATE_IN_SIGNATURES_ENTRY;
3302
3303                 if (parse_data->state == PARSE_STATE_IN_SOURCES_VALUE)
3304                         parse_data->state = PARSE_STATE_IN_SOURCES_ENTRY;
3305
3306                 return;
3307         }
3308 }
3309
3310 static void
3311 migrate_parse_gconf_xml_text (GMarkupParseContext *context,
3312                               const gchar *text,
3313                               gsize length,
3314                               gpointer user_data,
3315                               GError **error)
3316 {
3317         ParseData *parse_data = user_data;
3318
3319         /* The account and signature data is encoded XML stuffed into
3320          * GConf XML (yuck!).  Fortunately GMarkupParseContext decodes
3321          * the XML for us, so we just have to feed it to a nested
3322          * GMarkupParseContext. */
3323
3324         switch (parse_data->state) {
3325                 case PARSE_STATE_IN_ACCOUNTS_VALUE:
3326                         context = g_markup_parse_context_new (
3327                                 &account_xml_parser, 0, parse_data, NULL);
3328                         break;
3329
3330                 case PARSE_STATE_IN_SIGNATURES_VALUE:
3331                         context = g_markup_parse_context_new (
3332                                 &signature_xml_parser, 0, parse_data, NULL);
3333                         break;
3334
3335                 case PARSE_STATE_IN_SOURCES_VALUE:
3336                         context = g_markup_parse_context_new (
3337                                 &source_xml_parser, 0, parse_data, NULL);
3338                         break;
3339
3340                 default:
3341                         return;
3342         }
3343
3344         if (g_markup_parse_context_parse (context, text, length, error))
3345                 g_markup_parse_context_end_parse (context, error);
3346
3347         g_markup_parse_context_free (context);
3348 }
3349
3350 static GMarkupParser gconf_xml_parser = {
3351         migrate_parse_gconf_xml_start_element,
3352         migrate_parse_gconf_xml_end_element,
3353         migrate_parse_gconf_xml_text,
3354         NULL,  /* passthrough */
3355         NULL   /* error */
3356 };
3357
3358 static gboolean
3359 migrate_parse_gconf_xml (ParseType parse_type,
3360                          const gchar *contents,
3361                          gsize length,
3362                          GError **error)
3363 {
3364         GMarkupParseContext *context;
3365         ParseData *parse_data;
3366         gboolean success = FALSE;
3367
3368         parse_data = parse_data_new (parse_type);
3369
3370         context = g_markup_parse_context_new (
3371                 &gconf_xml_parser, 0, parse_data,
3372                 (GDestroyNotify) parse_data_free);
3373
3374         if (g_markup_parse_context_parse (context, contents, length, error))
3375                 if (g_markup_parse_context_end_parse (context, error))
3376                         success = TRUE;
3377
3378         g_markup_parse_context_free (context);
3379
3380         return success;
3381 }
3382
3383 static void
3384 migrate_remove_gconf_xml (const gchar *gconf_key,
3385                           const gchar *gconf_xml)
3386 {
3387         /* Remove the GConf string list so the user is not haunted by
3388          * old data sources being resurrected from leftover GConf data.
3389          * Also delete the %gconf.xml file itself.  If gconfd is running
3390          * then it will just recreate the file from memory when it exits
3391          * (which is why we invoke gconftool-2), otherwise the file will
3392          * stay deleted. */
3393
3394         gchar *path_to_program;
3395
3396         path_to_program = g_find_program_in_path ("gconftool-2");
3397
3398         if (path_to_program != NULL) {
3399                 gchar *command_line;
3400                 GError *error = NULL;
3401
3402                 command_line = g_strjoin (
3403                         " ",
3404                         path_to_program,
3405                         "--set",
3406                         "--type=list",
3407                         "--list-type=string",
3408                         gconf_key, "[]", NULL);
3409
3410                 /* We don't really care if the command worked or not,
3411                  * just check that the program got spawned successfully. */
3412                 if (!g_spawn_command_line_async (command_line, &error)) {
3413                         g_printerr (
3414                                 "Failed to spawn '%s': %s\n",
3415                                 path_to_program, error->message);
3416                         g_error_free (error);
3417                 }
3418
3419                 g_free (path_to_program);
3420                 g_free (command_line);
3421         }
3422
3423         if (g_file_test (gconf_xml, G_FILE_TEST_IS_REGULAR)) {
3424                 if (g_remove (gconf_xml) == -1) {
3425                         g_printerr (
3426                                 "Failed to remove '%s': %s\n",
3427                                 gconf_xml, g_strerror (errno));
3428                 }
3429         }
3430 }
3431
3432 static void
3433 migrate_handle_error (const GError *error)
3434 {
3435         g_return_if_fail (error != NULL);
3436
3437         if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
3438                 g_printerr ("  FAILED: %s\n", error->message);
3439 }
3440
3441 void
3442 evolution_source_registry_migrate_sources (void)
3443 {
3444         gchar *base_dir;
3445         gchar *contents;
3446         gchar *gconf_xml;
3447         gsize length;
3448         const gchar *gconf_key;
3449         GError *error = NULL;
3450
3451         base_dir = g_build_filename (
3452                 g_get_home_dir (), ".gconf", "apps", "evolution", NULL);
3453
3454         /* ------------------------------------------------------------------*/
3455
3456         g_print ("Migrating mail accounts from GConf...\n");
3457
3458         gconf_xml = g_build_filename (
3459                 base_dir, "mail", "%gconf.xml", NULL);
3460         g_file_get_contents (gconf_xml, &contents, &length, &error);
3461
3462         if (error == NULL) {
3463                 migrate_parse_gconf_xml (
3464                         PARSE_TYPE_MAIL,
3465                         contents, length, &error);
3466                 g_free (contents);
3467         }
3468
3469         if (error == NULL) {
3470                 gconf_key = "/apps/evolution/mail/accounts";
3471                 migrate_remove_gconf_xml (gconf_key, gconf_xml);
3472         } else {
3473                 migrate_handle_error (error);
3474                 g_clear_error (&error);
3475         }
3476
3477         g_free (gconf_xml);
3478
3479         /* ------------------------------------------------------------------*/
3480
3481         g_print ("Migrating addressbook sources from GConf...\n");
3482
3483         gconf_xml = g_build_filename (
3484                 base_dir, "addressbook", "%gconf.xml", NULL);
3485         g_file_get_contents (gconf_xml, &contents, &length, &error);
3486
3487         if (error == NULL) {
3488                 migrate_parse_gconf_xml (
3489                         PARSE_TYPE_ADDRESSBOOK,
3490                         contents, length, &error);
3491                 g_free (contents);
3492         }
3493
3494         if (error == NULL) {
3495                 gconf_key = "/apps/evolution/addressbook/sources";
3496                 migrate_remove_gconf_xml (gconf_key, gconf_xml);
3497         } else {
3498                 migrate_handle_error (error);
3499                 g_clear_error (&error);
3500         }
3501
3502         g_free (gconf_xml);
3503
3504         /* ------------------------------------------------------------------*/
3505
3506         g_print ("Migrating calendar sources from GConf...\n");
3507
3508         gconf_xml = g_build_filename (
3509                 base_dir, "calendar", "%gconf.xml", NULL);
3510         g_file_get_contents (gconf_xml, &contents, &length, &error);
3511
3512         if (error == NULL) {
3513                 migrate_parse_gconf_xml (
3514                         PARSE_TYPE_CALENDAR,
3515                         contents, length, &error);
3516                 g_free (contents);
3517         }
3518
3519         if (error == NULL) {
3520                 gconf_key = "/apps/evolution/calendar/sources";
3521                 migrate_remove_gconf_xml (gconf_key, gconf_xml);
3522         } else {
3523                 migrate_handle_error (error);
3524                 g_clear_error (&error);
3525         }
3526
3527         g_free (gconf_xml);
3528
3529         /* ------------------------------------------------------------------*/
3530
3531         g_print ("Migrating task list sources from GConf...\n");
3532
3533         gconf_xml = g_build_filename (
3534                 base_dir, "tasks", "%gconf.xml", NULL);
3535         g_file_get_contents (gconf_xml, &contents, &length, &error);
3536
3537         if (error == NULL) {
3538                 migrate_parse_gconf_xml (
3539                         PARSE_TYPE_TASKS,
3540                         contents, length, &error);
3541                 g_free (contents);
3542         }
3543
3544         if (error == NULL) {
3545                 gconf_key = "/apps/evolution/tasks/sources";
3546                 migrate_remove_gconf_xml (gconf_key, gconf_xml);
3547         } else {
3548                 migrate_handle_error (error);
3549                 g_clear_error (&error);
3550         }
3551
3552         g_free (gconf_xml);
3553
3554         /* ------------------------------------------------------------------*/
3555
3556         g_print ("Migrating memo list sources from GConf...\n");
3557
3558         gconf_xml = g_build_filename (
3559                 base_dir, "memos", "%gconf.xml", NULL);
3560         g_file_get_contents (gconf_xml, &contents, &length, &error);
3561
3562         if (error == NULL) {
3563                 migrate_parse_gconf_xml (
3564                         PARSE_TYPE_MEMOS,
3565                         contents, length, &error);
3566                 g_free (contents);
3567         }
3568
3569         if (error == NULL) {
3570                 gconf_key = "/apps/evolution/memos/sources";
3571                 migrate_remove_gconf_xml (gconf_key, gconf_xml);
3572         } else {
3573                 migrate_handle_error (error);
3574                 g_clear_error (&error);
3575         }
3576
3577         g_free (gconf_xml);
3578
3579         /* ------------------------------------------------------------------*/
3580
3581         g_free (base_dir);
3582 }