1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 *
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
8 *
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
12 * License.
13 *
14 * The Original Code is Calendar migration code
15 *
16 * The Initial Developer of the Original Code is
17 * Joey Minta <jminta@gmail.com>
18 * Portions created by the Initial Developer are Copyright (C) 2006
19 * the Initial Developer. All Rights Reserved.
20 *
21 * Contributor(s):
22 * Matthew Willis <mattwillis@gmail.com>
23 * Clint Talbert <cmtalbert@myfastmail.com>
24 * Stefan Sitter <ssitter@gmail.com>
25 *
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
37 *
38 * ***** END LICENSE BLOCK ***** */
39
40 const SUNBIRD_UID = "{718e30fb-e89b-41dd-9da7-e25a45638b28}";
41 const FIREFOX_UID = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
42
43 //
44 // The front-end wizard bits.
45 //
46 var gMigrateWizard = {
47 /**
48 * Called from onload of the migrator window. Takes all of the migrators
49 * that were passed in via window.arguments and adds them to checklist. The
50 * user can then check these off to migrate the data from those sources.
51 */
52 loadMigrators: function gmw_load() {
53 var listbox = document.getElementById("datasource-list");
54
55 //XXX Once we have branding for lightning, this hack can go away
56 var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
57 .getService(Components.interfaces.nsIStringBundleService);
58
59 var props = sbs.createBundle("chrome://calendar/locale/migration.properties");
60
61 if (gDataMigrator.isLightning()) {
62 var wizard = document.getElementById("migration-wizard");
63 var desc = document.getElementById("wizard-desc");
64 // Since we don't translate "Lightning"...
65 wizard.title = props.formatStringFromName("migrationTitle",
66 ["Lightning"],
67 1);
68 desc.textContent = props.formatStringFromName("migrationDescription",
69 ["Lightning"],
70 1);
71 }
72
73 LOG("migrators: " + window.arguments.length);
74 for each (var migrator in window.arguments[0]) {
75 var listItem = document.createElement("listitem");
76 listItem.setAttribute("type", "checkbox");
77 listItem.setAttribute("checked", true);
78 listItem.setAttribute("label", migrator.title);
79 listItem.migrator = migrator;
80 listbox.appendChild(listItem);
81 }
82 },
83
84 /**
85 * Called from the second page of the wizard. Finds all of the migrators
86 * that were checked and begins migrating their data. Also controls the
87 * progress dialog so the user can see what is happening. (somewhat)
88 */
89 migrateChecked: function gmw_migrate() {
90 var migrators = [];
91
92 // Get all the checked migrators into an array
93 var listbox = document.getElementById("datasource-list");
94 for (var i = listbox.childNodes.length-1; i >= 0; i--) {
95 LOG("Checking child node: " + listbox.childNodes[i]);
96 if (listbox.childNodes[i].getAttribute("checked")) {
97 LOG("Adding migrator");
98 migrators.push(listbox.childNodes[i].migrator);
99 }
100 }
101
102 // If no migrators were checked, then we're done
103 if (migrators.length == 0) {
104 window.close();
105 }
106
107 // Don't let the user get away while we're migrating
108 //XXX may want to wire this into the 'cancel' function once that's
109 // written
110 var wizard = document.getElementById("migration-wizard");
111 wizard.canAdvance = false;
112 wizard.canRewind = false;
113
114 // We're going to need this for the progress meter's description
115 var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
116 .getService(Components.interfaces.nsIStringBundleService);
117 var props = sbs.createBundle("chrome://calendar/locale/migration.properties");
118 var label = document.getElementById("progress-label");
119 var meter = document.getElementById("migrate-progressmeter");
120
121 var i = 0;
122 // Because some of our migrators involve async code, we need this
123 // call-back function so we know when to start the next migrator.
124 function getNextMigrator() {
125 if (migrators[i]) {
126 var mig = migrators[i];
127
128 // Increment i to point to the next migrator
129 i++;
130 LOG("starting migrator: " + mig.title);
131 label.value = props.formatStringFromName("migratingApp",
132 [mig.title],
133 1);
134 meter.value = (i-1)/migrators.length*100;
135 mig.args.push(getNextMigrator);
136
137 try {
138 mig.migrate.apply(mig, mig.args);
139 } catch (e) {
140 LOG("Failed to migrate: " + mig.title);
141 LOG(e);
142 getNextMigrator();
143 }
144 } else {
145 LOG("migration done");
146 wizard.canAdvance = true;
147 label.value = props.GetStringFromName("finished");
148 meter.value = 100;
149 gMigrateWizard.setCanRewindFalse();
150 }
151 }
152
153 // And get the first migrator
154 getNextMigrator();
155 },
156
157 setCanRewindFalse: function gmw_finish() {
158 document.getElementById('migration-wizard').canRewind = false;
159 }
160 };
161
162 //
163 // The more back-end data detection bits
164 //
165 function dataMigrator(aTitle, aMigrateFunction, aArguments) {
166 this.title = aTitle;
167 this.migrate = aMigrateFunction;
168 this.args = aArguments;
169 }
170
171 var gDataMigrator = {
172 mIsLightning: null,
173 mIsInFirefox: false,
174 mPlatform: null,
175 mDirService: null,
176 mIoService: null,
177
178 /**
179 * Properly caches the service so that it doesn't load on startup
180 */
181 get dirService() {
182 if (!this.mDirService) {
183 this.mDirService = Components.classes["@mozilla.org/file/directory_service;1"]
184 .getService(Components.interfaces.nsIProperties);
185 }
186 return this.mDirService;
187 },
188
189 get ioService() {
190 if (!this.mIoService) {
191 this.mIoService = Components.classes["@mozilla.org/network/io-service;1"]
192 .getService(Components.interfaces.nsIIOService);
193 }
194 return this.mIoService;
195 },
196
197 /**
198 * Gets the value for mIsLightning, and sets it if this.mIsLightning is
199 * not initialized. This is used by objects outside gDataMigrator to
200 * access the mIsLightning member.
201 */
202 isLightning: function is_ltn() {
203 if (this.mIsLightning == null) {
204 var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]
205 .getService(Components.interfaces.nsIXULAppInfo);
206 this.mIsLightning = !(appInfo.ID == SUNBIRD_UID);
207 return this.mIsLightning;
208 }
209 // else mIsLightning is initialized, return the value
210 return this.mIsLightning;
211 },
212
213 /**
214 * Call to do a general data migration (for a clean profile) Will run
215 * through all of the known migrator-checkers. These checkers will return
216 * an array of valid dataMigrator objects, for each kind of data they find.
217 * If there is at least one valid migrator, we'll pop open the migration
218 * wizard, otherwise, we'll return silently.
219 */
220 checkAndMigrate: function gdm_migrate() {
221 var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]
222 .getService(Components.interfaces.nsIXULAppInfo);
223 this.mIsLightning = !(appInfo.ID == SUNBIRD_UID)
224 LOG("mIsLightning is: " + this.mIsLightning);
225 if (appInfo.ID == FIREFOX_UID) {
226 this.mIsInFirefox = true;
227 // We can't handle Firefox Lightning yet
228 LOG("Holy cow, you're Firefox-Lightning! sorry, can't help.");
229 return;
230 }
231
232 var xulRuntime = Components.classes["@mozilla.org/xre/app-info;1"]
233 .getService(Components.interfaces.nsIXULRuntime);
234 this.mPlatform = xulRuntime.OS.toLowerCase();
235
236 LOG("mPlatform is: " + this.mPlatform);
237
238 var DMs = [];
239 var migrators = [this.checkOldCal, this.checkEvolution,
240 this.checkIcal];
241 // XXX also define a category and an interface here for pluggability
242 for each (var migrator in migrators) {
243 var migs = migrator.call(this);
244 for each (var dm in migs) {
245 DMs.push(dm);
246 }
247 }
248
249 if (DMs.length == 0) {
250 // No migration available
251 return;
252 }
253 LOG("DMs: " + DMs.length);
254
255 var url = "chrome://calendar/content/migration.xul";
256 #ifdef XP_MACOSX
257 var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
258 .getService(Components.interfaces.nsIWindowMediator);
259 var win = wm.getMostRecentWindow("Calendar:MigrationWizard");
260 if (win) {
261 win.focus();
262 } else {
263 openDialog(url, "migration", "centerscreen,chrome,resizable=no", DMs);
264 }
265 #else
266 openDialog(url, "migration", "modal,centerscreen,chrome,resizable=no", DMs);
267 #endif
268 },
269
270 /**
271 * Checks to see if we can find any traces of an older moz-cal program.
272 * This could be either the old calendar-extension, or Sunbird 0.2. If so,
273 * it offers to move that data into our new storage format. Also, if we're
274 * if we're Lightning, it will disable the old calendar extension, since it
275 * conflicts with us.
276 */
277 checkOldCal: function gdm_calold() {
278 LOG("Checking for the old calendar extension/app");
279
280 // First things first. If we are Lightning and the calendar extension
281 // is installed, we have to nuke it. The old extension defines some of
282 // the same paths as we do, and the resulting file conflicts result in
283 // first-class badness. getCompositeCalendar is a conflicting function
284 // that exists in Lighnting's version of calUtils.js. If it isn't
285 // defined, we have a conflict.
286 if (this.isLightning() && !("getCompositeCalendar" in window)) {
287
288 // We can't use our normal helper-functions, because those might
289 // conflict too.
290 var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
291 .getService(Components.interfaces.nsIStringBundleService);
292 var props = sbs.createBundle("chrome://calendar/locale/migration.properties");
293 var brand = sbs.createBundle("chrome://branding/locale/brand.properties");
294 var appName = brand.GetStringFromName("brandShortName");
295 // Tell the user we're going to disable and restart
296 var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
297 .getService(Components.interfaces.nsIPromptService);
298 promptService.alert(window,
299 props.GetStringFromName("disableExtTitle"),
300 props.formatStringFromName("disableExtText",
301 [brand],1));
302
303 // Kiiillllll...
304 var em = Components.classes["@mozilla.org/extensions/manager;1"]
305 .getService(Components.interfaces.nsIExtensionManager);
306 em.disableItem("{8e117890-a33f-424b-a2ea-deb272731365}");
307 promptService.alert(window, getString("disableDoneTitle"),
308 getString("disableExtDone"));
309 var startup = Components.classes["@mozilla.org/toolkit/app-startup;1"]
310 .getService(Components.interfaces.nsIAppStartup);
311 startup.quit(Components.interfaces.nsIAppStartup.eRestart |
312 Components.interfaces.nsIAppStartup.eAttemptQuit);
313 }
314
315 // This is the function that the migration wizard will call to actually
316 // migrate the data. It's defined here because we may use it multiple
317 // times (with different aProfileDirs), for instance if there is both
318 // a Thunderbird and Firefox cal-extension
319 function extMigrator(aProfileDir, aCallback) {
320 // Get the old datasource
321 var dataSource = aProfileDir.clone();
322 dataSource.append("CalendarManager.rdf");
323 if (!dataSource.exists()) {
324 return;
325 }
326
327 // Let this be a lesson to anyone designing APIs. The RDF API is so
328 // impossibly confusing that it's actually simpler/cleaner/shorter
329 // to simply parse as XML and use the better DOM APIs.
330 var req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
331 .createInstance(Components.interfaces.nsIXMLHttpRequest);
332 req.open('GET', "file://" + dataSource.path, true);
333 req.onreadystatechange = function calext_onreadychange() {
334 if (req.readyState == 4) {
335 LOG(req.responseText);
336 parseAndMigrate(req.responseXML, aCallback)
337 }
338 };
339 req.send(null);
340 }
341
342 // Callback from the XHR above. Parses CalendarManager.rdf and imports
343 // the data describe therein.
344 function parseAndMigrate(aDoc, aCallback) {
345 // For duplicate detection
346 var calManager = getCalendarManager();
347 var uris = [];
348 for each (var oldCal in calManager.getCalendars({})) {
349 uris.push(oldCal.uri);
350 }
351
352 function getRDFAttr(aNode, aAttr) {
353 return aNode.getAttributeNS("http://home.netscape.com/NC-rdf#",
354 aAttr);
355 }
356
357 const RDFNS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
358 var nodes = aDoc.getElementsByTagNameNS(RDFNS, "Description");
359 LOG("nodes: " + nodes.length);
360 for (var i = 0; i < nodes.length; i++) {
361 LOG("Beginning cal node");
362 var cal;
363 var node = nodes[i];
364 if (getRDFAttr(node, "remote") == "false") {
365 LOG("not remote");
366 var localFile = Components.classes["@mozilla.org/file/local;1"]
367 .createInstance(Components.interfaces.nsILocalFile);
368 localFile.initWithPath(getRDFAttr(node, "path"));
369 cal = gDataMigrator.importICSToStorage(localFile);
370 } else {
371 // Remote subscription
372 // XXX check for duplicates
373 var url = makeURL(getRDFAttr(node, "remotePath"));
374 cal = calManager.createCalendar("ics", url);
375 }
376 cal.name = getRDFAttr(node, "name");
377 cal.setProperty("color", getRDFAttr(node, "color"));
378 getCompositeCalendar().addCalendar(cal);
379 }
380 aCallback();
381 }
382
383 var migrators = [];
384
385 // Look in our current profile directory, in case we're upgrading in
386 // place
387 var profileDir = this.dirService.get("ProfD", Components.interfaces.nsILocalFile);
388 profileDir.append("Calendar");
389 if (profileDir.exists()) {
390 LOG("Found old extension directory in current app");
391 var title;
392 if (this.mIsLightning) {
393 title = "Mozilla Calendar Extension";
394 } else {
395 title = "Sunbird 0.2";
396 }
397 migrators.push(new dataMigrator(title, extMigrator, [profileDir]));
398 }
399
400 // Check the profiles of the various other moz-apps for calendar data
401 var profiles = [];
402
403 // Do they use Firefox?
404 var ffProf, sbProf, tbProf;
405 if ((ffProf = this.getFirefoxProfile())) {
406 profiles.push(ffProf);
407 }
408
409 if (this.mIsLightning) {
410 // If we're lightning, check Sunbird
411 if ((sbProf = this.getSunbirdProfile())) {
412 profiles.push(sbProf);
413 }
414 } else {
415 // Otherwise, check Thunderbird
416 if ((tbProf = this.getThunderbirdProfile())) {
417 profiles.push(tbProf);
418 }
419 }
420
421 // Now check all of the profiles in each of these folders for data
422 for each (var prof in profiles) {
423 var dirEnum = prof.directoryEntries;
424 while (dirEnum.hasMoreElements()) {
425 var profile = dirEnum.getNext().QueryInterface(Components.interfaces.nsIFile);
426 if (profile.isFile()) {
427 continue;
428 } else {
429 profile.append("Calendar");
430 if (profile.exists()) {
431 LOG("Found old extension directory at" + profile.path);
432 var title = "Mozilla Calendar";
433 migrators.push(new dataMigrator(title, extMigrator, [profile]));
434 }
435 }
436 }
437 }
438
439 return migrators;
440 },
441
442 /**
443 * Checks to see if Apple's iCal is installed and offers to migrate any data
444 * the user has created in it.
445 */
446 checkIcal: function gdm_ical() {
447 LOG("Checking for ical data");
448
449 function icalMigrate(aDataDir, aCallback) {
450 aDataDir.append("Sources");
451 var dirs = aDataDir.directoryEntries;
452 var calManager = getCalendarManager();
453
454 var i = 1;
455 while(dirs.hasMoreElements()) {
456 var dataDir = dirs.getNext().QueryInterface(Components.interfaces.nsIFile);
457 var dataStore = dataDir.clone();
458 dataStore.append("corestorage.ics");
459 if (!dataStore.exists()) {
460 continue;
461 }
462
463 var chars = [];
464 var fileStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
465 .createInstance(Components.interfaces.nsIFileInputStream);
466
467 fileStream.init(dataStore, 0x01, 0444, {});
468 var convStream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
469 .getService(Components.interfaces.nsIConverterInputStream);
470 convStream.init(fileStream, 'UTF-8', 0, 0x0000);
471 var tmpStr = {};
472 var str = "";
473 while (convStream.readString(-1, tmpStr)) {
474 str += tmpStr.value;
475 }
476
477 // Strip out the timezone definitions, since it makes the file
478 // invalid otherwise
479 var index = str.indexOf(";TZID=");
480 while (index != -1) {
481 var endIndex = str.indexOf(':', index);
482 var otherEnd = str.indexOf(';', index+2);
483 if (otherEnd < endIndex) {
484 endIndex = otherEnd;
485 }
486 var sub = str.substring(index, endIndex);
487 str = str.replace(sub, "", "g");
488 index = str.indexOf(";TZID=");
489 }
490 var tempFile = gDataMigrator.dirService.get("TmpD", Components.interfaces.nsIFile);
491 tempFile.append("icalTemp.ics");
492 tempFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0600);
493 var tempUri = gDataMigrator.ioService.newFileURI(tempFile);
494
495 var stream = Components.classes["@mozilla.org/network/file-output-stream;1"]
496 .createInstance(Components.interfaces.nsIFileOutputStream);
497 stream.init(tempFile, 0x2A, 0600, 0);
498 var convStream = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
499 .getService(Components.interfaces.nsIConverterOutputStream);
500 convStream.init(stream, 'UTF-8', 0, 0x0000);
501 convStream.writeString(str);
502
503 var cal = gDataMigrator.importICSToStorage(tempFile);
504 cal.name = "iCalendar"+i;
505 i++;
506 }
507 LOG("icalMig making callback");
508 aCallback();
509 }
510 var profileDir = this.dirService.get("ProfD", Components.interfaces.nsILocalFile);
511 var icalSpec = profileDir.path;
512 var icalFile;
513 if (!this.isLightning()) {
514 var diverge = icalSpec.indexOf("Sunbird");
515 if (diverge == -1) {
516 return [];
517 }
518 icalSpec = icalSpec.substr(0, diverge);
519 icalFile = Components.classes["@mozilla.org/file/local;1"]
520 .createInstance(Components.interfaces.nsILocalFile);
521 icalFile.initWithPath(icalSpec);
522 } else {
523 var diverge = icalSpec.indexOf("Thunderbird");
524 if (diverge == -1) {
525 return [];
526 }
527 icalSpec = icalSpec.substr(0, diverge);
528 icalFile = Components.classes["@mozilla.org/file/local;1"]
529 .createInstance(Components.interfaces.nsILocalFile);
530 icalFile.initWithPath(icalSpec);
531 icalFile.append("Application Support");
532 }
533
534 icalFile.append("iCal");
535 if (icalFile.exists()) {
536 return [new dataMigrator("Apple iCal", icalMigrate, [icalFile])];
537 }
538
539 return [];
540 },
541
542 /**
543 * Checks to see if Evolution is installed and offers to migrate any data
544 * stored there.
545 */
546 checkEvolution: function gdm_evolution() {
547 LOG("Checking for evolution data");
548
549 function evoMigrate(aDataDir, aCallback) {
550 aDataDir.append("Sources");
551 var dirs = aDataDir.directoryEntries;
552 var calManager = getCalendarManager();
553
554 var i = 1;
555 while(dirs.hasMoreElements()) {
556 var dataDir = dirs.getNext().QueryInterface(Components.interfaces.nsIFile);
557 var dataStore = dataDir.clone();
558 dataStore.append("calendar.ics");
559 if (!dataStore.exists()) {
560 continue;
561 }
562
563 var cal = gDataMigrator.importICSToStorage(dataStore);
564 //XXX
565 cal.name = "Evolution"+i;
566 i++;
567 }
568 aCallback();
569 }
570
571 var profileDir = this.dirService.get("ProfD", Components.interfaces.nsILocalFile);
572 var evoSpec = profileDir.path;
573 var evoFile;
574 if (!this.mIsLightning) {
575 var diverge = evoSpec.indexOf(".mozilla");
576 if (diverge == -1) {
577 return [];
578 }
579 evoSpec = evoSpec.substr(0, diverge);
580 evoFile = Components.classes["@mozilla.org/file/local;1"]
581 .createInstance(Components.interfaces.nsILocalFile);
582 evoFile.initWithPath(evoSpec);
583 } else {
584 var diverge = evoSpec.indexOf(".thunderbird");
585 if (diverge == -1) {
586 return [];
587 }
588 evoSpec = evoSpec.substr(0, diverge);
589 evoFile = Components.classes["@mozilla.org/file/local;1"]
590 .createInstance(Components.interfaces.nsILocalFile);
591 evoFile.initWithPath(evoSpec);
592 }
593 evoFile.append(".evolution");
594 evoFile.append("calendar");
595 evoFile.append("local");
596 evoFile.append("system");
597 if (evoFile.exists()) {
598 return [new dataMigrator("Evolution", evoMigrate, [evoFile])];
599 }
600 return [];
601 },
602
603 importICSToStorage: function migrateIcsStorage(icsFile) {
604 var calManager = getCalendarManager();
605 var uris = [];
606 for each (var oldCal in calManager.getCalendars({})) {
607 uris.push(oldCal.uri.spec);
608 }
609 var uri = 'moz-profile-calendar://?id=';
610 var i = 1;
611 while (uris.indexOf(uri+i) != -1) {
612 i++;
613 }
614
615 var cal = calManager.createCalendar("storage", makeURL(uri+i));
616 var icsImporter = Components.classes["@mozilla.org/calendar/import;1?type=ics"]
617 .getService(Components.interfaces.calIImporter);
618
619 var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
620 .createInstance(Components.interfaces.nsIFileInputStream);
621 var items = [];
622
623 try {
624 inputStream.init(icsFile, MODE_RDONLY, 0444, {});
625 items = icsImporter.importFromStream(inputStream, {});
626 } catch(ex) {
627 switch (ex.result) {
628 case Components.interfaces.calIErrors.INVALID_TIMEZONE:
629 showError(calGetString("calendar", "timezoneError", [icsFile.path] , 1));
630 break;
631 default:
632 showError(calGetString("calendar", "unableToRead") + icsFile.path + "\n"+ ex);
633 }
634 } finally {
635 inputStream.close();
636 }
637
638 // Defined in import-export.js
639 putItemsIntoCal(cal, items);
640
641 calManager.registerCalendar(cal);
642 getCompositeCalendar().addCalendar(cal);
643 return cal;
644 },
645
646 /**
647 * Helper functions for getting the profile directory of various MozApps
648 * (Getting the profile dir is way harder than it should be.)
649 *
650 * Sunbird:
651 * Unix: ~jdoe/.mozilla/sunbird/
652 * Windows: %APPDATA%\Mozilla\Sunbird\Profiles
653 * Mac OS X: ~jdoe/Library/Application Support/Sunbird/Profiles
654 *
655 * Firefox:
656 * Unix: ~jdoe/.mozilla/firefox/
657 * Windows: %APPDATA%\Mozilla\Firefox\Profiles
658 * Mac OS X: ~jdoe/Library/Application Support/Firefox/Profiles
659 *
660 * Thunderbird:
661 * Unix: ~jdoe/.thunderbird/
662 * Windows: %APPDATA%\Thunderbird\Profiles
663 * Mac OS X: ~jdoe/Library/Thunderbird/Profiles
664 *
665 * Notice that Firefox and Sunbird follow essentially the same pattern, so
666 * we group them with getNormalProfile
667 */
668 getFirefoxProfile: function gdm_getFF() {
669 return this.getNormalProfile("Firefox");
670 },
671
672 getThunderbirdProfile: function gdm_getTB() {
673 var localFile;
674 var profileRoot = this.dirService.get("DefProfRt", Components.interfaces.nsILocalFile);
675 LOG("profileRoot = " + profileRoot.path);
676 if (this.mIsLightning) {
677 localFile = profileRoot;
678 } else {
679 // Now it gets ugly
680 switch (this.mPlatform) {
681 case "darwin": // Mac OS X
682 case "winnt":
683 localFile = profileRoot.parent.parent.parent;
684 localFile.append("Thunderbird");
685 localFile.append("Profiles");
686 break;
687 default: // Unix
688 localFile = profileRoot.parent.parent;
689 localFile.append(".thunderbird");
690 }
691 }
692 LOG("searching for Thunderbird in " + localFile.path);
693 return localFile.exists() ? localFile : null;
694 },
695
696 getSunbirdProfile: function gdm_getSB() {
697 return this.getNormalProfile("Sunbird");
698 },
699
700 getNormalProfile: function gdm_getNorm(aAppName) {
701 var localFile;
702 var profileRoot = this.dirService.get("DefProfRt", Components.interfaces.nsILocalFile);
703 LOG("profileRoot = " + profileRoot.path);
704
705 if (this.isLightning()) { // We're in Thunderbird
706 switch (this.mPlatform) {
707 case "darwin": // Mac OS X
708 localFile = profileRoot.parent.parent;
709 localFile.append("Application Support");
710 localFile.append(aAppName);
711 localFile.append("Profiles");
712 break;
713 case "winnt":
714 localFile = profileRoot.parent.parent;
715 localFile.append("Mozilla");
716 localFile.append(aAppName);
717 localFile.append("Profiles");
718 break;
719 default: // Unix
720 localFile = profileRoot.parent;
721 localFile.append(".mozilla");
722 localFile.append(aAppName.toLowerCase());
723 break;
724 }
725 } else {
726 switch (this.mPlatform) {
727 // On Mac and Windows, we can just remove the "Sunbird" and
728 // replace it with "Firefox" to get to Firefox
729 case "darwin": // Mac OS X
730 case "winnt":
731 localFile = profileRoot.parent.parent;
732 localFile.append(aAppName);
733 localFile.append("Profiles");
734 break;
735 default: // Unix
736 localFile = profileRoot.parent;
737 localFile.append(aAppName.toLowerCase());
738 break;
739 }
740 }
741 LOG("searching for " + aAppName + " in " + localFile.path);
742 return localFile.exists() ? localFile : null;
743 }
744 };
745
746 function LOG(aString) {
747 if (!getPrefSafe("calendar.migration.log", false)) {
748 return;
749 }
750 var consoleService = Components.classes["@mozilla.org/consoleservice;1"]
751 .getService(Components.interfaces.nsIConsoleService);
752 consoleService.logStringMessage(aString);
753 dump(aString+"\n");
754 }