Fix bug 360753 - Add migration support for migrating data from Vista's calendaring application. r=mmecca
authorPhilipp Kewisch <mozilla@kewis.ch>
Thu, 09 Aug 2012 20:20:46 +0200
changeset 13226 27959b5eab053897baf23e1c64f25cbe40785929
parent 13225 ee35bef38c141c87020b58ab410bc98bb0a22d48
child 13227 f5543c0aa4e6bc7a63c7e43d042449d6d2034810
push idunknown
push userunknown
push dateunknown
reviewersmmecca
bugs360753
Fix bug 360753 - Add migration support for migrating data from Vista's calendaring application. r=mmecca
calendar/base/content/dialogs/calendar-migration-dialog.js
calendar/test/mozmill/eventDialog/testEventDialog.js
calendar/test/mozmill/eventDialog/testEventDialogModificationPrompt.js
calendar/test/mozmill/eventDialog/testUTF8.js
calendar/test/mozmill/recurrence/testAnnualRecurrence.js
calendar/test/mozmill/recurrence/testBiweeklyRecurrence.js
calendar/test/mozmill/recurrence/testDailyRecurrence.js
calendar/test/mozmill/recurrence/testLastDayOfMonthRecurrence.js
calendar/test/mozmill/recurrence/testWeeklyNRecurrence.js
calendar/test/mozmill/recurrence/testWeeklyUntilRecurrence.js
calendar/test/mozmill/recurrence/testWeeklyWithExceptionRecurrence.js
calendar/test/mozmill/recurrenceRotated/testAnnualRecurrence.js
calendar/test/mozmill/recurrenceRotated/testBiweeklyRecurrence.js
calendar/test/mozmill/recurrenceRotated/testDailyRecurrence.js
calendar/test/mozmill/recurrenceRotated/testLastDayOfMonthRecurrence.js
calendar/test/mozmill/recurrenceRotated/testWeeklyNRecurrence.js
calendar/test/mozmill/recurrenceRotated/testWeeklyUntilRecurrence.js
calendar/test/mozmill/recurrenceRotated/testWeeklyWithExceptionRecurrence.js
calendar/test/mozmill/testAlarmDefaultValue.js
calendar/test/mozmill/views/testDayView.js
calendar/test/mozmill/views/testMonthView.js
calendar/test/mozmill/views/testMultiweekView.js
calendar/test/mozmill/views/testTaskView.js
calendar/test/mozmill/views/testWeekView.js
--- a/calendar/base/content/dialogs/calendar-migration-dialog.js
+++ b/calendar/base/content/dialogs/calendar-migration-dialog.js
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const SUNBIRD_UID = "{718e30fb-e89b-41dd-9da7-e25a45638b28}";
 const FIREFOX_UID = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
 
 Components.utils.import("resource://calendar/modules/calUtils.jsm");
+Components.utils.import("resource:///modules/Services.jsm");
 
 //
 // The front-end wizard bits.
 //
 var gMigrateWizard = {
     /**
      * Called from onload of the migrator window.  Takes all of the migrators
      * that were passed in via window.arguments and adds them to checklist. The
@@ -32,17 +33,17 @@ var gMigrateWizard = {
             wizard.title = props.formatStringFromName("migrationTitle",
                                                       ["Lightning"],
                                                       1);
             desc.textContent = props.formatStringFromName("migrationDescription",
                                                           ["Lightning"],
                                                           1);
         }
 
-        LOG("migrators: " + window.arguments.length);
+        migLOG("migrators: " + window.arguments.length);
         for each (var migrator in window.arguments[0]) {
             var listItem = document.createElement("listitem");
             listItem.setAttribute("type", "checkbox");
             listItem.setAttribute("checked", true);
             listItem.setAttribute("label", migrator.title);
             listItem.migrator = migrator;
             listbox.appendChild(listItem);
         }
@@ -54,19 +55,17 @@ var gMigrateWizard = {
      * progress dialog so the user can see what is happening. (somewhat)
      */
     migrateChecked: function gmw_migrate() {
         var migrators = [];
 
         // Get all the checked migrators into an array
         var listbox = document.getElementById("datasource-list");
         for (var i = listbox.childNodes.length-1; i >= 0; i--) {
-            LOG("Checking child node: " + listbox.childNodes[i]);
             if (listbox.childNodes[i].getAttribute("checked")) {
-                LOG("Adding migrator");
                 migrators.push(listbox.childNodes[i].migrator);
             }
         }
 
         // If no migrators were checked, then we're done
         if (migrators.length == 0) {
             window.close();
         }
@@ -89,32 +88,32 @@ var gMigrateWizard = {
         // Because some of our migrators involve async code, we need this
         // call-back function so we know when to start the next migrator.
         function getNextMigrator() {
             if (migrators[i]) {
                 var mig = migrators[i];
 
                 // Increment i to point to the next migrator
                 i++;
-                LOG("starting migrator: " + mig.title);
+                migLOG("starting migrator: " + mig.title);
                 label.value = props.formatStringFromName("migratingApp",
                                                          [mig.title],
                                                          1);
                 meter.value = (i-1)/migrators.length*100;
                 mig.args.push(getNextMigrator);
 
                 try {
                     mig.migrate.apply(mig, mig.args);
                 } catch (e) {
-                    LOG("Failed to migrate: " + mig.title);
-                    LOG(e);
+                    migLOG("Failed to migrate: " + mig.title);
+                    migLOG(e);
                     getNextMigrator();
                 }
             } else {
-                LOG("migration done");
+                migLOG("migration done");
                 wizard.canAdvance = true;
                 label.value = props.GetStringFromName("finished");
                 meter.value = 100;
                 gMigrateWizard.setCanRewindFalse();
             }
          }
 
         // And get the first migrator
@@ -140,17 +139,17 @@ var gMigrateWizard = {
  * @class
  * @param aTitle    The title of the migrator
  * @param aMigrateFunction    The function to call when migrating
  * @param aArguments          The arguments to pass in.
  */
 function dataMigrator(aTitle, aMigrateFunction, aArguments) {
     this.title = aTitle;
     this.migrate = aMigrateFunction;
-    this.args = aArguments;
+    this.args = aArguments || [];
 }
 
 var gDataMigrator = {
     mIsInFirefox: false,
     mPlatform: null,
     mDirService: null,
     mIoService: null,
 
@@ -173,42 +172,44 @@ var gDataMigrator = {
      * wizard, otherwise, we'll return silently.
      */
     checkAndMigrate: function gdm_migrate() {
         var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]
                       .getService(Components.interfaces.nsIXULAppInfo);
         if (appInfo.ID == FIREFOX_UID) {
             this.mIsInFirefox = true;
             // We can't handle Firefox Lightning yet
-            LOG("Holy cow, you're Firefox-Lightning! sorry, can't help.");
+            migLOG("Holy cow, you're Firefox-Lightning! sorry, can't help.");
             return;
         }
 
         var xulRuntime = Components.classes["@mozilla.org/xre/app-info;1"]
                          .getService(Components.interfaces.nsIXULRuntime);
         this.mPlatform = xulRuntime.OS.toLowerCase();
 
-        LOG("mPlatform is: " + this.mPlatform);
+        migLOG("mPlatform is: " + this.mPlatform);
 
         var DMs = [];
-        var migrators = [this.checkOldCal, this.checkEvolution,
+        var migrators = [this.checkOldCal,
+                         this.checkEvolution,
+                         this.checkWindowsMail,
                          this.checkIcal];
         // XXX also define a category and an interface here for pluggability
         for each (var migrator in migrators) {
             var migs = migrator.call(this);
             for each (var dm in migs) {
                 DMs.push(dm);
             }
         }
 
         if (DMs.length == 0) {
             // No migration available
             return;
         }
-        LOG("DMs: " + DMs.length);
+        migLOG("DMs: " + DMs.length);
 
         var url = "chrome://calendar/content/calendar-migration-dialog.xul";
 #ifdef XP_MACOSX
         var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
                            .getService(Components.interfaces.nsIWindowMediator);
         var win = wm.getMostRecentWindow("Calendar:MigrationWizard");
         if (win) {
             win.focus();
@@ -221,17 +222,17 @@ var gDataMigrator = {
     },
 
     /**
      * Checks to see if we can find any traces of an older moz-cal program.
      * This could be either the old calendar-extension, or Sunbird 0.2.  If so,
      * it offers to move that data into our new storage format.
      */
     checkOldCal: function gdm_calold() {
-        LOG("Checking for the old calendar extension/app");
+        migLOG("Checking for the old calendar extension/app");
 
         // This is the function that the migration wizard will call to actually
         // migrate the data.  It's defined here because we may use it multiple
         // times (with different aProfileDirs), for instance if there is both
         // a Thunderbird and Firefox cal-extension
         function extMigrator(aProfileDir, aCallback) {
             // Get the old datasource
             var dataSource = aProfileDir.clone();
@@ -243,17 +244,17 @@ var gDataMigrator = {
             // Let this be a lesson to anyone designing APIs. The RDF API is so
             // impossibly confusing that it's actually simpler/cleaner/shorter
             // to simply parse as XML and use the better DOM APIs.
             var req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
                       .createInstance(Components.interfaces.nsIXMLHttpRequest);
             req.open('GET', "file://" + dataSource.path, true);
             req.onreadystatechange = function calext_onreadychange() {
                 if (req.readyState == 4) {
-                    LOG(req.responseText);
+                    migLOG(req.responseText);
                     parseAndMigrate(req.responseXML, aCallback)
                 }
             };
             req.send(null);
         }
 
         // Callback from the XHR above.  Parses CalendarManager.rdf and imports
         // the data describe therein.
@@ -267,23 +268,23 @@ var gDataMigrator = {
 
             function getRDFAttr(aNode, aAttr) {
                 return aNode.getAttributeNS("http://home.netscape.com/NC-rdf#",
                                             aAttr);
             }
 
             const RDFNS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
             var nodes = aDoc.getElementsByTagNameNS(RDFNS, "Description");
-            LOG("nodes: " + nodes.length);
+            migLOG("nodes: " + nodes.length);
             for (var i = 0; i < nodes.length; i++) {
-                LOG("Beginning calendar node");
+                migLOG("Beginning calendar node");
                 var calendar;
                 var node = nodes[i];
                 if (getRDFAttr(node, "remote") == "false") {
-                    LOG("not remote");
+                    migLOG("not remote");
                     var localFile = Components.classes["@mozilla.org/file/local;1"]
                                     .createInstance(Components.interfaces.nsILocalFile);
                     localFile.initWithPath(getRDFAttr(node, "path"));
                     calendar = gDataMigrator.importICSToStorage(localFile);
                 } else {
                     // Remote subscription
                     // XXX check for duplicates
                     var url = makeURL(getRDFAttr(node, "remotePath"));
@@ -299,17 +300,17 @@ var gDataMigrator = {
 
         var migrators = [];
 
         // Look in our current profile directory, in case we're upgrading in
         // place
         var profileDir = this.dirService.get("ProfD", Components.interfaces.nsILocalFile);
         profileDir.append("Calendar");
         if (profileDir.exists()) {
-            LOG("Found old extension directory in current app");
+            migLOG("Found old extension directory in current app");
             var title;
             if (!cal.isSunbird()) {
                 title = "Mozilla Calendar Extension";
             } else {
                 title = "Sunbird 0.2";
             }
             migrators.push(new dataMigrator(title, extMigrator, [profileDir]));
         }
@@ -340,33 +341,33 @@ var gDataMigrator = {
             var dirEnum = prof.directoryEntries;
             while (dirEnum.hasMoreElements()) {
                 var profile = dirEnum.getNext().QueryInterface(Components.interfaces.nsIFile);
                 if (profile.isFile()) {
                     continue;
                 } else {
                     profile.append("Calendar");
                     if (profile.exists()) {
-                        LOG("Found old extension directory at" + profile.path);
+                        migLOG("Found old extension directory at" + profile.path);
                         var title = "Mozilla Calendar";
                         migrators.push(new dataMigrator(title, extMigrator, [profile]));
                     }
                 }
             }
         }
 
         return migrators;
     },
 
     /**
      * Checks to see if Apple's iCal is installed and offers to migrate any data
      * the user has created in it.
      */
     checkIcal: function gdm_ical() {
-        LOG("Checking for ical data");
+        migLOG("Checking for ical data");
 
         function icalMigrate(aDataDir, aCallback) {
             aDataDir.append("Sources");
             var dirs = aDataDir.directoryEntries;
             var calManager = getCalendarManager();
 
             var i = 1;
             while(dirs.hasMoreElements()) {
@@ -419,17 +420,17 @@ var gDataMigrator = {
                 convStream.writeString(str);
 
                 var calendar = gDataMigrator.importICSToStorage(tempFile);
                 calendar.name = "iCalendar"+i;
                 i++;
                 calManager.registerCalendar(calendar);
                 getCompositeCalendar().addCalendar(calendar);
             }
-            LOG("icalMig making callback");
+            migLOG("icalMig making callback");
             aCallback();
         }
         var profileDir = this.dirService.get("ProfD", Components.interfaces.nsILocalFile);
         var icalSpec = profileDir.path;
         var icalFile;
         if (cal.isSunbird()) {
             var diverge = icalSpec.indexOf("Sunbird");
             if (diverge == -1) {
@@ -462,17 +463,17 @@ var gDataMigrator = {
     /**
      * Checks to see if Evolution is installed and offers to migrate any data
      * stored there.
      */
     checkEvolution: function gdm_evolution() {
         function evoMigrate(aDataDir, aCallback) {
             var i = 1;
             function evoDataMigrate(dataStore) {
-                LOG("Migrating evolution data file in " + dataStore.path);
+                migLOG("Migrating evolution data file in " + dataStore.path);
                 if (dataStore.exists()) {
                     var calendar = gDataMigrator.importICSToStorage(dataStore);
                     calendar.name = "Evolution " + (i++);
                     calManager.registerCalendar(calendar);
                     getCompositeCalendar().addCalendar(calendar);
                 }
                 return dataStore.exists();
             }
@@ -491,48 +492,119 @@ var gDataMigrator = {
 
         var evoDir = this.dirService.get("Home", Components.interfaces.nsILocalFile);
         evoDir.append(".evolution");
         evoDir.append("calendar");
         evoDir.append("local");
         return (evoDir.exists() ? [new dataMigrator("Evolution", evoMigrate, [evoDir])] : []);
     },
 
+    checkWindowsMail: function gdm_windowsMail() {
+        let maildir = this.dirService.get("LocalAppData",
+                                          Components.interfaces.nsILocalFile);
+        if (!maildir || !maildir.exists()) {
+            // We are probably not on windows
+            return [];
+        }
+        maildir.append("Microsoft");
+        maildir.append("Windows Calendar");
+        maildir.append("Calendars");
+
+        let settingsxml = maildir.clone();
+        settingsxml.append("Settings.xml");
+        if (!settingsxml || !settingsxml.exists()) {
+            // No Settings.xml, maybe Windows Calendar was never started?
+            return [];
+        }
+        let settingsXmlUri = Services.io.newFileURI(settingsxml);
+
+        let req = new XMLHttpRequest();
+        req.open("GET", settingsXmlUri.spec, false);
+        req.send(null);
+        if (req.status == 0) {
+            // The file was found, it seems we are on windows vista.
+            let doc = req.responseXML;
+            let root = doc.documentElement;
+
+            // Get all calendar property tags and return the migrator.
+            let calendars = doc.getElementsByTagName("VCalendar");
+            function doMigrate(aCallback) {
+                for each (let node in Array.slice(calendars)) {
+                    let name = node.getElementsByTagName("Name")[0].textContent;
+                    let color = node.getElementsByTagName("Color")[0].textContent;
+                    let enabled = node.getElementsByTagName("Enabled")[0].textContent == "True";
+
+                    // The name is quoted, and the color also contains an alpha
+                    // value. Lets just ignore the alpha value and take the
+                    // color part.
+                    name = name.replace(/(^'|'$)/g, "");
+                    color = color.replace(/0x[0-9a-fA-F]{2}([0-9a-fA-F]{4})/, "#$1");
+
+                    let calfile = maildir.clone();
+                    calfile.append(name + ".ics");
+
+                    if (calfile.exists()) {
+                        let storage = gDataMigrator.importICSToStorage(calfile)
+
+                        storage.name = name;
+
+                        if (color) {
+                            storage.setProperty("color", color);
+                        }
+                        let calManager = cal.getCalendarManager();
+                        calManager.registerCalendar(storage);
+
+                        if (enabled) {
+                            getCompositeCalendar().addCalendar(storage);
+                        }
+                    }
+                }
+                aCallback();
+            }
+            if (calendars.length > 0) {
+                return [new dataMigrator("Windows Calendar", doMigrate)];
+            }
+        }
+        return [];
+    },
+
     /**
      * Creates and registers a storage calendar and imports the given ics file into it.
      *
      * @param icsFile     The nsI(Local)File to import.
      */
     importICSToStorage: function migrateIcsStorage(icsFile) {
         const uri = 'moz-storage-calendar://';
         let calendar = cal.getCalendarManager().createCalendar("storage", makeURL(uri));
         let icsImporter = Components.classes["@mozilla.org/calendar/import;1?type=ics"]
                                     .getService(Components.interfaces.calIImporter);
 
         let inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
                                     .createInstance(Components.interfaces.nsIFileInputStream);
         let items = [];
 
+        calendar.id = cal.getUUID();
+
         try {
             inputStream.init(icsFile, MODE_RDONLY, parseInt("0444", 8), {});
             items = icsImporter.importFromStream(inputStream, {});
         } catch(ex) {
             switch (ex.result) {
                 case Components.interfaces.calIErrors.INVALID_TIMEZONE:
                     showError(calGetString("calendar", "timezoneError", [icsFile.path]));
                     break;
                 default:
                     showError(calGetString("calendar", "unableToRead") + icsFile.path + "\n"+ ex);
             }
         } finally {
            inputStream.close();
         }
 
         // Defined in import-export.js
-        putItemsIntoCal(calendar, items);
+        putItemsIntoCal(calendar, items, icsFile.leafName);
 
         return calendar;
     },
 
     /**
      * Helper functions for getting the profile directory of various MozApps
      * (Getting the profile dir is way harder than it should be.)
      *
@@ -559,34 +631,34 @@ var gDataMigrator = {
     },
 
     /**
      * @see getFirefoxProfile
      */
     getThunderbirdProfile: function gdm_getTB() {
         var localFile;
         var profileRoot = this.dirService.get("DefProfRt", Components.interfaces.nsILocalFile);
-        LOG("profileRoot = " + profileRoot.path);
+        migLOG("profileRoot = " + profileRoot.path);
         if (!cal.isSunbird()) {
             localFile = profileRoot;
         } else {
             // Now it gets ugly
             switch (this.mPlatform) {
                 case "darwin": // Mac OS X
                 case "winnt":
                     localFile = profileRoot.parent.parent.parent;
                     localFile.append("Thunderbird");
                     localFile.append("Profiles");
                     break;
                 default: // Unix
                     localFile = profileRoot.parent.parent;
                     localFile.append(".thunderbird");
             }
         }
-        LOG("searching for Thunderbird in " + localFile.path);
+        migLOG("searching for Thunderbird in " + localFile.path);
         return localFile.exists() ? localFile : null;
     },
 
     /**
      * @see getFirefoxProfile
      */
     getSunbirdProfile: function gdm_getSB() {
         return this.getNormalProfile("Sunbird");
@@ -594,17 +666,17 @@ var gDataMigrator = {
 
     /**
      * Common function to retrieve the profile directory for a given app.
      * @see getFirefoxProfile
      */
     getNormalProfile: function gdm_getNorm(aAppName) {
         var localFile;
         var profileRoot = this.dirService.get("DefProfRt", Components.interfaces.nsILocalFile);
-        LOG("profileRoot = " + profileRoot.path);
+        migLOG("profileRoot = " + profileRoot.path);
 
         if (!cal.isSunbird()) {  // We're in Thunderbird
             switch (this.mPlatform) {
                 case "darwin": // Mac OS X
                     localFile = profileRoot.parent.parent;
                     localFile.append("Application Support");
                     localFile.append(aAppName);
                     localFile.append("Profiles");
@@ -632,30 +704,30 @@ var gDataMigrator = {
                     localFile.append("Profiles");
                     break;
                 default: // Unix
                     localFile = profileRoot.parent;
                     localFile.append(aAppName.toLowerCase());
                     break;
             }
         }
-        LOG("searching for " + aAppName + " in " + localFile.path);
+        migLOG("searching for " + aAppName + " in " + localFile.path);
         return localFile.exists() ? localFile : null;
     }
 };
 
 /**
  * logs to system and error console, depending on the calendar.migration.log
  * preference.
  *
- * XXX Consolidate with calUtils' LOG().
+ * XXX Use log4moz instead.
  *
  * @param aString   The string to log
  */
-function LOG(aString) {
+function migLOG(aString) {
     if (!getPrefSafe("calendar.migration.log", false)) {
         return;
     }
     var consoleService = Components.classes["@mozilla.org/consoleservice;1"]
                          .getService(Components.interfaces.nsIConsoleService);
     consoleService.logStringMessage(aString);
     dump(aString+"\n");
 }