summaryrefslogtreecommitdiffstats
path: root/comm/calendar/base/content/publish.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/calendar/base/content/publish.js')
-rw-r--r--comm/calendar/base/content/publish.js239
1 files changed, 239 insertions, 0 deletions
diff --git a/comm/calendar/base/content/publish.js b/comm/calendar/base/content/publish.js
new file mode 100644
index 0000000000..cad9123843
--- /dev/null
+++ b/comm/calendar/base/content/publish.js
@@ -0,0 +1,239 @@
+/* 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/. */
+
+/* exported publishCalendarData, publishCalendarDataDialogResponse,
+ * publishEntireCalendar, publishEntireCalendarDialogResponse
+ */
+
+/* import-globals-from ../../base/content/calendar-views-utils.js */
+
+var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm");
+
+/**
+ * Show publish dialog, ask for URL and publish all selected items.
+ */
+function publishCalendarData() {
+ let args = {};
+
+ args.onOk = self.publishCalendarDataDialogResponse;
+
+ openDialog(
+ "chrome://calendar/content/publishDialog.xhtml",
+ "caPublishEvents",
+ "chrome,titlebar,modal,resizable",
+ args
+ );
+}
+
+/**
+ * Callback method for publishCalendarData() that is called when the user
+ * presses the OK button in the publish dialog.
+ */
+function publishCalendarDataDialogResponse(CalendarPublishObject, aProgressDialog) {
+ publishItemArray(
+ currentView().getSelectedItems(),
+ CalendarPublishObject.remotePath,
+ aProgressDialog
+ );
+}
+
+/**
+ * Show publish dialog, ask for URL and publish all items from the calendar.
+ *
+ * @param {?calICalendar} aCalendar - The calendar that will be published.
+ * If not specified, the user will be prompted to select a calendar.
+ */
+function publishEntireCalendar(aCalendar) {
+ if (!aCalendar) {
+ let calendars = cal.manager.getCalendars();
+
+ if (calendars.length == 1) {
+ // Do not ask user for calendar if only one calendar exists
+ aCalendar = calendars[0];
+ } else {
+ // Ask user to select the calendar that should be published.
+ // publishEntireCalendar() will be called again if OK is pressed
+ // in the dialog and the selected calendar will be passed in.
+ // Therefore return after openDialog().
+ let args = {};
+ args.onOk = publishEntireCalendar;
+ args.promptText = cal.l10n.getCalString("publishPrompt");
+ openDialog(
+ "chrome://calendar/content/chooseCalendarDialog.xhtml",
+ "_blank",
+ "chrome,titlebar,modal,resizable",
+ args
+ );
+ return;
+ }
+ }
+
+ let args = {};
+ let publishObject = {};
+
+ args.onOk = self.publishEntireCalendarDialogResponse;
+
+ publishObject.calendar = aCalendar;
+
+ // restore the remote ics path preference from the calendar passed in
+ let remotePath = aCalendar.getProperty("remote-ics-path");
+ if (remotePath) {
+ publishObject.remotePath = remotePath;
+ }
+
+ args.publishObject = publishObject;
+ openDialog(
+ "chrome://calendar/content/publishDialog.xhtml",
+ "caPublishEvents",
+ "chrome,titlebar,modal,resizable",
+ args
+ );
+}
+
+/**
+ * Callback method for publishEntireCalendar() that is called when the user
+ * presses the OK button in the publish dialog.
+ */
+async function publishEntireCalendarDialogResponse(CalendarPublishObject, aProgressDialog) {
+ // store the selected remote ics path as a calendar preference
+ CalendarPublishObject.calendar.setProperty("remote-ics-path", CalendarPublishObject.remotePath);
+
+ aProgressDialog.onStartUpload();
+ let oldCalendar = CalendarPublishObject.calendar;
+ let items = await oldCalendar.getItemsAsArray(
+ Ci.calICalendar.ITEM_FILTER_ALL_ITEMS,
+ 0,
+ null,
+ null
+ );
+ publishItemArray(items, CalendarPublishObject.remotePath, aProgressDialog);
+}
+
+function publishItemArray(aItemArray, aPath, aProgressDialog) {
+ let outputStream;
+ let inputStream;
+ let storageStream;
+
+ let icsURL = Services.io.newURI(aPath);
+
+ let channel = Services.io.newChannelFromURI(
+ icsURL,
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null,
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER
+ );
+ if (icsURL.schemeIs("webcal")) {
+ icsURL.scheme = "http";
+ }
+ if (icsURL.schemeIs("webcals")) {
+ icsURL.scheme = "https";
+ }
+
+ switch (icsURL.scheme) {
+ case "http":
+ case "https":
+ channel = channel.QueryInterface(Ci.nsIHttpChannel);
+ break;
+ case "file":
+ channel = channel.QueryInterface(Ci.nsIFileChannel);
+ break;
+ default:
+ dump("No such scheme\n");
+ return;
+ }
+
+ let uploadChannel = channel.QueryInterface(Ci.nsIUploadChannel);
+ uploadChannel.notificationCallbacks = notificationCallbacks;
+
+ storageStream = Cc["@mozilla.org/storagestream;1"].createInstance(Ci.nsIStorageStream);
+ storageStream.init(32768, 0xffffffff, null);
+ outputStream = storageStream.getOutputStream(0);
+
+ let serializer = Cc["@mozilla.org/calendar/ics-serializer;1"].createInstance(
+ Ci.calIIcsSerializer
+ );
+ serializer.addItems(aItemArray);
+ // Outlook requires METHOD:PUBLISH property:
+ let methodProp = cal.icsService.createIcalProperty("METHOD");
+ methodProp.value = "PUBLISH";
+ serializer.addProperty(methodProp);
+ serializer.serializeToStream(outputStream);
+ outputStream.close();
+
+ inputStream = storageStream.newInputStream(0);
+
+ uploadChannel.setUploadStream(inputStream, "text/calendar", -1);
+ try {
+ channel.asyncOpen(new PublishingListener(aProgressDialog));
+ } catch (e) {
+ Services.prompt.alert(
+ null,
+ cal.l10n.getCalString("genericErrorTitle"),
+ cal.l10n.getCalString("otherPutError", [e.message])
+ );
+ }
+}
+
+/** @implements {nsIInterfaceRequestor} */
+var notificationCallbacks = {
+ getInterface(iid, instance) {
+ if (iid.equals(Ci.nsIAuthPrompt2)) {
+ if (!this.calAuthPrompt) {
+ return new cal.auth.Prompt();
+ }
+ }
+ if (iid.equals(Ci.nsIAuthPrompt)) {
+ // use the window watcher service to get a nsIAuthPrompt impl
+ return Services.ww.getNewAuthPrompter(null);
+ }
+
+ throw Components.Exception(`${iid} not implemented`, Cr.NS_ERROR_NO_INTERFACE);
+ },
+};
+
+/**
+ * Listener object to pass to `channel.asyncOpen()`. A reference to the current dialog window
+ * passed to the constructor provides access to the dialog once the request is done.
+ *
+ * @implements {nsIStreamListener}
+ */
+class PublishingListener {
+ QueryInterface = ChromeUtils.generateQI(["nsIStreamListener"]);
+
+ constructor(progressDialog) {
+ this.progressDialog = progressDialog;
+ }
+
+ onStartRequest(request) {}
+ onStopRequest(request, status) {
+ let channel;
+ let requestSucceeded;
+ try {
+ channel = request.QueryInterface(Ci.nsIHttpChannel);
+ requestSucceeded = channel.requestSucceeded;
+ } catch (e) {
+ // Don't fail if it is not an http channel, will be handled below.
+ }
+
+ if (channel && !requestSucceeded) {
+ this.progressDialog.wrappedJSObject.onStopUpload(0);
+ let body = cal.l10n.getCalString("httpPutError", [
+ channel.responseStatus,
+ channel.responseStatusText,
+ ]);
+ Services.prompt.alert(null, cal.l10n.getCalString("genericErrorTitle"), body);
+ } else if (!channel && !Components.isSuccessCode(request.status)) {
+ this.progressDialog.wrappedJSObject.onStopUpload(0);
+ // XXX this should be made human-readable.
+ let body = cal.l10n.getCalString("otherPutError", [request.status.toString(16)]);
+ Services.prompt.alert(null, cal.l10n.getCalString("genericErrorTitle"), body);
+ } else {
+ this.progressDialog.wrappedJSObject.onStopUpload(100);
+ }
+ }
+
+ onDataAvailable(request, inStream, sourceOffset, count) {}
+}