releases-comm-central/calendar/base/content/import-export.js

365 строки
14 KiB
JavaScript

/* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Calendar code.
*
* The Initial Developer of the Original Code is
* ArentJan Banck <ajbanck@planet.nl>.
* Portions created by the Initial Developer are Copyright (C) 2002
* the Initial Developer. All Rights Reserved.
*
* Contributor(s): ArentJan Banck <ajbanck@planet.nl>
* Steve Hampton <mvgrad78@yahoo.com>
* Eric Belhaire <belhaire@ief.u-psud.fr>
* Jussi Kukkonen <jussi.kukkonen@welho.com>
* Michiel van Leeuwen <mvl@exedo.nl>
* Stefan Sitter <ssitter@gmail.com>
* Philipp Kewisch <mozilla@kewis.ch>
* Archaeopteryx <archaeopteryx@coole-files.de>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// File constants copied from file-utils.js
const MODE_RDONLY = 0x01;
const MODE_WRONLY = 0x02;
const MODE_RDWR = 0x04;
const MODE_CREATE = 0x08;
const MODE_APPEND = 0x10;
const MODE_TRUNCATE = 0x20;
const MODE_SYNC = 0x40;
const MODE_EXCL = 0x80;
/**
* Shows a file dialog, reads the selected file(s) and tries to parse events from it.
*
* @param aCalendar (optional) If specified, the items will be imported directly
* into the calendar
*/
function loadEventsFromFile(aCalendar) {
const nsIFilePicker = Components.interfaces.nsIFilePicker;
let fp = Components.classes["@mozilla.org/filepicker;1"]
.createInstance(nsIFilePicker);
fp.init(window,
calGetString("calendar", "filepickerTitleImport"),
nsIFilePicker.modeOpen);
fp.defaultExtension = "ics";
// Get a list of exporters
let contractids = new Array();
let catman = Components.classes["@mozilla.org/categorymanager;1"]
.getService(Components.interfaces.nsICategoryManager);
let catenum = catman.enumerateCategory('cal-importers');
let currentListLength = 0;
while (catenum.hasMoreElements()) {
let entry = catenum.getNext();
entry = entry.QueryInterface(Components.interfaces.nsISupportsCString);
let contractid = catman.getCategoryEntry('cal-importers', entry);
let exporter = Components.classes[contractid]
.getService(Components.interfaces.calIImporter);
let types = exporter.getFileTypes({});
let type;
for each (type in types) {
fp.appendFilter(type.description, type.extensionFilter);
if (type.extensionFilter=="*." + fp.defaultExtension) {
fp.filterIndex = currentListLength;
}
contractids.push(contractid);
currentListLength++;
}
}
fp.show();
if (fp.file && fp.file.path && fp.file.path.length > 0) {
let filePath = fp.file.path;
let importer = Components.classes[contractids[fp.filterIndex]]
.getService(Components.interfaces.calIImporter);
const nsIFileInputStream = Components.interfaces.nsIFileInputStream;
const nsIScriptableInputStream = Components.interfaces.nsIScriptableInputStream;
let inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
.createInstance(nsIFileInputStream);
let items = [];
try {
inputStream.init( fp.file, MODE_RDONLY, 0444, {});
items = importer.importFromStream(inputStream, {});
} catch(ex) {
switch (ex.result) {
case Components.interfaces.calIErrors.INVALID_TIMEZONE:
showError(calGetString("calendar", "timezoneError", [filePath]));
break;
default:
showError(calGetString("calendar", "unableToRead") + filePath + "\n"+ ex);
}
} finally {
inputStream.close();
}
if (aCalendar) {
putItemsIntoCal(aCalendar, items);
return;
}
let calendars = cal.getCalendarManager().getCalendars({});
calendars = calendars.filter(isCalendarWritable);
if (calendars.length < 1) {
// XXX alert something?
return;
} else if (calendars.length == 1) {
// There's only one calendar, so it's silly to ask what calendar
// the user wants to import into.
putItemsIntoCal(calendars[0], items, filePath);
} else {
// Ask what calendar to import into
let args = new Object();
args.onOk = function putItems(aCal) { putItemsIntoCal(aCal, items, filePath); };
args.calendars = calendars;
args.promptText = calGetString("calendar", "importPrompt");
openDialog("chrome://calendar/content/chooseCalendarDialog.xul",
"_blank", "chrome,titlebar,modal,resizable", args);
}
}
}
/**
* Put items into a certain calendar, catching errors and showing them to the
* user.
*
* @param destCal The destination calendar.
* @param aItems An array of items to put into the calendar.
* @param aFilePath The original file path, for error messages.
*/
function putItemsIntoCal(destCal, aItems, aFilePath) {
// Set batch for the undo/redo transaction manager
startBatchTransaction();
// And set batch mode on the calendar, to tell the views to not
// redraw until all items are imported
destCal.startBatch();
// This listener is needed to find out when the last addItem really
// finished. Using a counter to find the last item (which might not
// be the last item added)
var count = 0;
var failedCount = 0;
var duplicateCount = 0;
// Used to store the last error. Only the last error, because we don't
// wan't to bomb the user with thousands of error messages in case
// something went really wrong.
// (example of something very wrong: importing the same file twice.
// quite easy to trigger, so we really should do this)
var lastError;
var listener = {
onOperationComplete: function(aCalendar, aStatus, aOperationType, aId, aDetail) {
count++;
if (!Components.isSuccessCode(aStatus)) {
if (aStatus == Components.interfaces.calIErrors.DUPLICATE_ID) {
duplicateCount++;
} else {
failedCount++;
lastError = aStatus;
}
}
// See if it is time to end the calendar's batch.
if (count == aItems.length) {
destCal.endBatch();
if (!failedCount && duplicateCount) {
showError(calGetString("calendar", "duplicateError", [duplicateCount, aFilePath]));
} else if (failedCount) {
showError(calGetString("calendar", "importItemsFailed", [failedCount, lastError.toString()]));
}
}
}
}
for each (let item in aItems) {
// XXX prompt when finding a duplicate.
try {
destCal.addItem(item, listener);
} catch(e) {
failedCount++;
lastError = e;
// Call the listener's operationComplete, to increase the
// counter and not miss failed items. Otherwise, endBatch might
// never be called.
listener.onOperationComplete(null, null, null, null, null);
Components.utils.reportError("Import error: "+e);
}
}
// End transmgr batch
endBatchTransaction();
}
/**
* Save data to a file. Create the file or overwrite an existing file.
*
* @param calendarEventArray (required) Array of calendar events that should
* be saved to file.
* @param aDefaultFileName (optional) Initial filename shown in SaveAs dialog.
*/
function saveEventsToFile(calendarEventArray, aDefaultFileName) {
if (!calendarEventArray || !calendarEventArray.length) {
return;
}
// Show the 'Save As' dialog and ask for a filename to save to
const nsIFilePicker = Components.interfaces.nsIFilePicker;
let fp = Components.classes["@mozilla.org/filepicker;1"]
.createInstance(nsIFilePicker);
fp.init(window,
calGetString("calendar", "filepickerTitleExport"),
nsIFilePicker.modeSave);
if (aDefaultFileName && aDefaultFileName.length && aDefaultFileName.length > 0) {
fp.defaultString = aDefaultFileName;
} else if (calendarEventArray.length == 1 && calendarEventArray[0].title) {
fp.defaultString = calendarEventArray[0].title;
} else {
fp.defaultString = calGetString("calendar", "defaultFileName");
}
fp.defaultExtension = "ics";
// Get a list of exporters
let contractids = new Array();
let catman = Components.classes["@mozilla.org/categorymanager;1"]
.getService(Components.interfaces.nsICategoryManager);
let catenum = catman.enumerateCategory('cal-exporters');
currentListLength = 0;
while (catenum.hasMoreElements()) {
let entry = catenum.getNext();
entry = entry.QueryInterface(Components.interfaces.nsISupportsCString);
let contractid = catman.getCategoryEntry('cal-exporters', entry);
let exporter = Components.classes[contractid]
.getService(Components.interfaces.calIExporter);
let types = exporter.getFileTypes({});
let type;
for each (let type in types) {
fp.appendFilter(type.description, type.extensionFilter);
if (type.extensionFilter=="*." + fp.defaultExtension) {
fp.filterIndex = currentListLength;
}
contractids.push(contractid);
currentListLength++;
}
}
fp.show();
// Now find out as what to save, convert the events and save to file.
if (fp.file && fp.file.path.length > 0) {
const UTF8 = "UTF-8";
let aDataStream;
let extension;
let charset;
let exporter = Components.classes[contractids[fp.filterIndex]]
.getService(Components.interfaces.calIExporter);
let filePath = fp.file.path;
if (filePath.indexOf(".") == -1) {
filePath += "."+exporter.getFileTypes({})[0].defaultExtension;
}
const nsILocalFile = Components.interfaces.nsILocalFile;
const nsIFileOutputStream = Components.interfaces.nsIFileOutputStream;
let outputStream;
let localFileInstance = Components.classes["@mozilla.org/file/local;1"]
.createInstance(nsILocalFile);
localFileInstance.initWithPath(filePath);
outputStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
.createInstance(nsIFileOutputStream);
try {
outputStream.init(localFileInstance, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, 0664, 0);
// XXX Do the right thing with unicode and stuff. Or, again, should the
// exporter handle that?
exporter.exportToStream(outputStream,
calendarEventArray.length,
calendarEventArray,
null);
outputStream.close();
} catch(ex) {
showError(calGetString("calendar", "unableToWrite") + filePath);
}
}
}
/**
* Exports all the events and tasks in a calendar. If aCalendar is not specified,
* the user will be prompted with a list of calendars to choose which one to export.
*
* @param aCalendar (optional) A specific calendar to export
*/
function exportEntireCalendar(aCalendar) {
var itemArray = [];
var getListener = {
onOperationComplete: function(aCalendar, aStatus, aOperationType, aId, aDetail)
{
saveEventsToFile(itemArray, aCalendar.name);
},
onGetResult: function(aCalendar, aStatus, aItemType, aDetail, aCount, aItems)
{
for each (item in aItems) {
itemArray.push(item);
}
}
};
function getItemsFromCal(aCal) {
aCal.getItems(Components.interfaces.calICalendar.ITEM_FILTER_ALL_ITEMS,
0, null, null, getListener);
}
if (!aCalendar) {
var count = new Object();
var calendars = getCalendarManager().getCalendars(count);
if (count.value == 1) {
// There's only one calendar, so it's silly to ask what calendar
// the user wants to import into.
getItemsFromCal(calendars[0]);
} else {
// Ask what calendar to import into
var args = new Object();
args.onOk = getItemsFromCal;
args.promptText = calGetString("calendar", "exportPrompt");
openDialog("chrome://calendar/content/chooseCalendarDialog.xul",
"_blank", "chrome,titlebar,modal,resizable", args);
}
} else {
getItemsFromCal(aCalendar);
}
}