--- a/calendar/base/public/calIICSService.idl
+++ b/calendar/base/public/calIICSService.idl
@@ -113,29 +113,29 @@ interface calIIcalComponent : nsISupport
attribute calIDateTime completedTime;
attribute calIDateTime lastModified;
/**
* The recurrence ID, a.k.a. DTSTART-of-calculated-occurrence,
* or null if this isn't an occurrence.
*/
attribute calIDateTime recurrenceId;
-
+
AUTF8String serializeToICS();
/**
* Return a string representation of this instance.
*/
AUTF8String toString();
/**
* Serializes this component (and subcomponents) directly to an
* input stream. Typically used for performance to avoid
* unnecessary conversions and XPConnect traversals.
- *
+ *
* @result an input stream which can be read to get the serialized
* version of this component, encoded in UTF-8. Implements
* nsISeekableStream so that it can be used with
* nsIUploadChannel.
*/
nsIInputStream serializeToICSStream();
void addSubcomponent(in calIIcalComponent comp);
@@ -188,17 +188,17 @@ interface calIIcalProperty : nsISupports
* @exception Any libical error will be thrown as an calIError::ICS_ error.
*/
readonly attribute AUTF8String icalString;
/**
* Return a string representation of this instance.
*/
AUTF8String toString();
-
+
/**
* The value of the property as string.
* The exception for properties of TEXT or X- type, those will be unescaped
* when getting, and also expects an unescaped string when setting.
* Datetime, numeric and other non-text types are represented as ical string
*/
attribute AUTF8String value;
@@ -233,31 +233,57 @@ interface calIIcalProperty : nsISupports
/**
* Returns the icalcompoment this property belongs to. Please note
* that the returned object is a raw pointer to the appropriate
* component and is owned by libical.
*/
[noscript,notxpcom] icalcomponentptr getIcalComponent();
};
+[scriptable,uuid(41fb4cbd-9977-41c7-b179-ab567e4b9395)]
+interface calIIcsComponentParsingListener : nsISupports
+{
+ /**
+ * Called when the parsing has completed.
+ *
+ * @param rc The result code of parsing
+ * @param rootComp The root ical component that was parsed
+ */
+ void onParsingComplete(in nsresult rc, in calIIcalComponent rootComp);
+};
+
[scriptable,uuid(ae4ca6c3-981b-4f66-a0ce-2f2c218ad9e3)]
interface calIICSService : nsISupports
{
/**
* Parses an ICS string and uses the passed tzProvider instance to
* resolve timezones not contained withing the VCALENDAR.
*
* @param serialized an ICS string
* @param tzProvider timezone provider used to resolve TZIDs
* not contained within the VCALENDAR;
* if null is passed, parsing falls back to
* using the timezone service
*/
calIIcalComponent parseICS(in AUTF8String serialized,
in calITimezoneProvider tzProvider);
+ /**
+ * Asynchronously parse an ICS string
+ *
+ * @param serialized an ICS string
+ * @param tzProvider timezone provider used to resolve TZIDs
+ * not contained within the VCALENDAR;
+ * if null is passed, parsing falls back to
+ * using the timezone service
+ * @param listener The listener that notifies the root component
+ */
+ void parseICSAsync(in AUTF8String serialized,
+ in calITimezoneProvider tzProvider,
+ in calIIcsComponentParsingListener listener);
+
calIIcalComponent createIcalComponent(in AUTF8String kind);
calIIcalProperty createIcalProperty(in AUTF8String kind);
/* I wish I could write this function atop libical!
boolean isLegalParameterValue(in AUTF8String paramKind,
in AUTF8String paramValue);
*/
};
--- a/calendar/base/src/Makefile.in
+++ b/calendar/base/src/Makefile.in
@@ -81,17 +81,16 @@ EXTRA_SCRIPTS = \
calAttachment.js \
calAttendee.js \
calCalendarManager.js \
calCachedCalendar.js \
calDateTimeFormatter.js \
calEvent.js \
calFilter.js \
calIcsParser.js \
- calIcsParser-worker.js \
calIcsSerializer.js \
calItemBase.js \
calItipItem.js \
calProtocolHandler.js \
calRecurrenceInfo.js \
calRelation.js \
calStartupService.js \
calTodo.js \
--- a/calendar/base/src/calICSService.cpp
+++ b/calendar/base/src/calICSService.cpp
@@ -54,18 +54,17 @@ extern "C" {
calIcalProperty::~calIcalProperty()
{
if (!mParent) {
icalproperty_free(mProperty);
}
}
NS_IMPL_CLASSINFO(calIcalProperty, NULL, 0, CAL_ICALPROPERTY_CID)
-NS_IMPL_ISUPPORTS1(calIcalProperty, calIIcalProperty)
-NS_IMPL_CI_INTERFACE_GETTER1(calIcalProperty, calIIcalProperty)
+NS_IMPL_ISUPPORTS1_CI(calIcalProperty, calIIcalProperty)
NS_IMETHODIMP_(icalproperty *)
calIcalProperty::GetIcalProperty()
{
return mProperty;
}
NS_IMETHODIMP_(icalcomponent *)
@@ -94,17 +93,17 @@ calIcalProperty::ToString(nsACString& aR
{
return GetIcalString(aResult);
}
NS_IMETHODIMP
calIcalProperty::GetValue(nsACString &str)
{
icalvalue *value = icalproperty_get_value(mProperty);
- icalvalue_kind valuekind = icalvalue_isa(value);
+ icalvalue_kind valuekind = icalvalue_isa(value);
const char *icalstr;
if (valuekind == ICAL_TEXT_VALUE) {
icalstr = icalvalue_get_text(value);
} else if (valuekind == ICAL_X_VALUE) {
icalstr = icalvalue_get_x(value);
} else if (valuekind == ICAL_ATTACH_VALUE) {
icalattach *attach = icalvalue_get_attach(value);
@@ -120,17 +119,17 @@ calIcalProperty::GetValue(nsACString &st
if (!icalstr) {
if (icalerrno == ICAL_BADARG_ERROR) {
str.Truncate();
// Set string to null, because we don't have a value
// (which is something different then an empty value)
str.SetIsVoid(PR_TRUE);
return NS_OK;
}
-
+
#ifdef DEBUG
fprintf(stderr, "Error getting string value: %d (%s)\n",
icalerrno, icalerror_strerror(icalerrno));
#endif
return NS_ERROR_FAILURE;
}
str.Assign(icalstr);
@@ -166,32 +165,32 @@ calIcalProperty::GetValueAsIcalString(ns
if (!icalstr) {
if (icalerrno == ICAL_BADARG_ERROR) {
str.Truncate();
// Set string to null, because we don't have a value
// (which is something different then an empty value)
str.SetIsVoid(PR_TRUE);
return NS_OK;
}
-
+
#ifdef DEBUG
fprintf(stderr, "Error getting string value: %d (%s)\n",
icalerrno, icalerror_strerror(icalerrno));
#endif
return NS_ERROR_FAILURE;
}
str.Assign(icalstr);
return NS_OK;
}
NS_IMETHODIMP
calIcalProperty::SetValueAsIcalString(const nsACString &str)
{
- const char *kindstr =
+ const char *kindstr =
icalvalue_kind_to_string(icalproperty_kind_to_value_kind(icalproperty_isa(mProperty)));
icalproperty_set_value_from_string(mProperty,
PromiseFlatCString(str).get(),
kindstr);
return NS_OK;
}
NS_IMETHODIMP
@@ -221,17 +220,17 @@ FindParameter(icalproperty *prop, const
}
return nsnull;
}
NS_IMETHODIMP
calIcalProperty::GetParameter(const nsACString ¶m, nsACString &value)
{
// More ridiculous parameter/X-PARAMETER handling.
- icalparameter_kind paramkind =
+ icalparameter_kind paramkind =
icalparameter_string_to_kind(PromiseFlatCString(param).get());
if (paramkind == ICAL_NO_PARAMETER)
return NS_ERROR_INVALID_ARG;
const char *icalstr = nsnull;
if (paramkind == ICAL_X_PARAMETER) {
icalparameter *icalparam = FindParameter(mProperty, param, ICAL_X_PARAMETER);
@@ -253,17 +252,17 @@ calIcalProperty::GetParameter(const nsAC
value.Assign(icalstr);
}
return NS_OK;
}
NS_IMETHODIMP
calIcalProperty::SetParameter(const nsACString ¶m, const nsACString &value)
{
- icalparameter_kind paramkind =
+ icalparameter_kind paramkind =
icalparameter_string_to_kind(PromiseFlatCString(param).get());
if (paramkind == ICAL_NO_PARAMETER)
return NS_ERROR_INVALID_ARG;
// Because libical's support for manipulating parameters is weak, and
// X-PARAMETERS doubly so, we walk the list looking for an existing one of
// that name, and reset its value if found.
@@ -285,33 +284,33 @@ calIcalProperty::SetParameter(const nsAC
// If not found, fall through to adding a new parameter below.
} else {
// We could try getting an existing parameter here and resetting its
// value, but this is easier and I don't care that much about parameter
// performance at this point.
RemoveParameter(param);
}
- icalparameter *icalparam =
+ icalparameter *icalparam =
icalparameter_new_from_value_string(paramkind,
PromiseFlatCString(value).get());
if (!icalparam)
return NS_ERROR_OUT_OF_MEMORY;
- // You might ask me "why does libical not do this for us?" and I would
+ // You might ask me "why does libical not do this for us?" and I would
// just nod knowingly but sadly at you in return.
//
// You might also, if you were not too distracted by the first question,
// ask why we have icalproperty_set_x_name but icalparameter_set_xname.
// More nodding would ensue.
if (paramkind == ICAL_X_PARAMETER)
icalparameter_set_xname(icalparam, PromiseFlatCString(param).get());
else if (paramkind == ICAL_IANA_PARAMETER)
icalparameter_set_iana_name(icalparam, PromiseFlatCString(param).get());
-
+
icalproperty_add_parameter(mProperty, icalparam);
// XXX check ical errno
return NS_OK;
}
static nsresult
FillParameterName(icalparameter *icalparam, nsACString &name)
{
@@ -1127,22 +1126,22 @@ calIcalComponent::GetFirstProperty(const
icalproperty_kind propkind =
icalproperty_string_to_kind(PromiseFlatCString(kind).get());
if (propkind == ICAL_NO_PROPERTY)
return NS_ERROR_INVALID_ARG;
icalproperty *icalprop = nsnull;
if (propkind == ICAL_X_PROPERTY) {
- for (icalprop =
+ for (icalprop =
icalcomponent_get_first_property(mComponent, ICAL_X_PROPERTY);
icalprop;
icalprop = icalcomponent_get_next_property(mComponent,
ICAL_X_PROPERTY)) {
-
+
if (kind.Equals(icalproperty_get_x_name(icalprop)))
break;
}
} else {
icalprop = icalcomponent_get_first_property(mComponent, propkind);
}
if (!icalprop) {
@@ -1163,22 +1162,22 @@ calIcalComponent::GetNextProperty(const
icalproperty_kind propkind =
icalproperty_string_to_kind(PromiseFlatCString(kind).get());
if (propkind == ICAL_NO_PROPERTY)
return NS_ERROR_INVALID_ARG;
icalproperty *icalprop = nsnull;
if (propkind == ICAL_X_PROPERTY) {
- for (icalprop =
+ for (icalprop =
icalcomponent_get_next_property(mComponent, ICAL_X_PROPERTY);
icalprop;
icalprop = icalcomponent_get_next_property(mComponent,
ICAL_X_PROPERTY)) {
-
+
if (kind.Equals(icalproperty_get_x_name(icalprop)))
break;
}
} else {
icalprop = icalcomponent_get_next_property(mComponent, propkind);
}
if (!icalprop) {
@@ -1263,20 +1262,71 @@ calICSService::ParseICS(const nsACString
icalcomponent_free(ical);
return NS_ERROR_OUT_OF_MEMORY;
}
NS_ADDREF(*component = comp);
return NS_OK;
}
NS_IMETHODIMP
+calICSService::ParserWorker::Run()
+{
+ icalcomponent *ical =
+ icalparser_parse_string(PromiseFlatCString(mString).get());
+ nsresult status = NS_OK;
+ calIIcalComponent *comp = nsnull;
+
+ if (ical) {
+ comp = new calIcalComponent(ical, nsnull, mProvider);
+ if (!comp) {
+ icalcomponent_free(ical);
+ status = NS_ERROR_OUT_OF_MEMORY;
+ }
+ } else {
+ status = calIErrors::ICS_ERROR_BASE + icalerrno;
+ }
+
+ nsCOMPtr<nsIRunnable> completer = new ParserWorkerCompleter(status, comp, mListener);
+ mThread->Dispatch(completer, NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+calICSService::ParserWorker::ParserWorkerCompleter::Run()
+{
+ mListener->OnParsingComplete(mStatus, mComp);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+calICSService::ParseICSAsync(const nsACString& serialized,
+ calITimezoneProvider *tzProvider,
+ calIIcsComponentParsingListener *listener)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(listener);
+
+ nsCOMPtr<nsIThread> workerThread;
+ nsCOMPtr<nsIThread> currentThread;
+ rv = NS_GetCurrentThread(getter_AddRefs(currentThread));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIRunnable> worker = new ParserWorker(currentThread, serialized, tzProvider, listener);
+ NS_ENSURE_TRUE(worker, NS_ERROR_OUT_OF_MEMORY);
+ rv = NS_NewThread(getter_AddRefs(workerThread), worker);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
calICSService::CreateIcalComponent(const nsACString &kind, calIIcalComponent **comp)
{
NS_ENSURE_ARG_POINTER(comp);
- icalcomponent_kind compkind =
+ icalcomponent_kind compkind =
icalcomponent_string_to_kind(PromiseFlatCString(kind).get());
// Maybe someday I'll support X-COMPONENTs
if (compkind == ICAL_NO_COMPONENT || compkind == ICAL_X_COMPONENT)
return NS_ERROR_INVALID_ARG;
icalcomponent *ical = icalcomponent_new(compkind);
if (!ical)
@@ -1291,17 +1341,17 @@ calICSService::CreateIcalComponent(const
NS_ADDREF(*comp);
return NS_OK;
}
NS_IMETHODIMP
calICSService::CreateIcalProperty(const nsACString &kind, calIIcalProperty **prop)
{
NS_ENSURE_ARG_POINTER(prop);
- icalproperty_kind propkind =
+ icalproperty_kind propkind =
icalproperty_string_to_kind(PromiseFlatCString(kind).get());
if (propkind == ICAL_NO_PROPERTY)
return NS_ERROR_INVALID_ARG;
icalproperty *icalprop = icalproperty_new(propkind);
if (!icalprop)
return NS_ERROR_OUT_OF_MEMORY; // XXX translate
--- a/calendar/base/src/calICSService.h
+++ b/calendar/base/src/calICSService.h
@@ -37,26 +37,63 @@
* ***** END LICENSE BLOCK ***** */
#if !defined(INCLUDED_CALICSSERVICE_H)
#define INCLUDED_CALICSSERVICE_H
#include "nsCOMPtr.h"
#include "calIICSService.h"
#include "calITimezoneProvider.h"
#include "nsInterfaceHashtable.h"
+#include "nsThreadUtils.h"
#include "calUtils.h"
extern "C" {
#include "ical.h"
}
class calICSService : public calIICSService,
public nsIClassInfo,
public cal::XpcomBase
{
+protected:
+ class ParserWorker : public nsRunnable {
+ public:
+ ParserWorker(nsIThread *callingThread,
+ const nsACString &icsString,
+ calITimezoneProvider *tzProvider,
+ calIIcsComponentParsingListener *listener) :
+ mString(icsString), mProvider(tzProvider),
+ mListener(listener), mThread(callingThread)
+ {
+ }
+
+ NS_DECL_NSIRUNNABLE
+
+ protected:
+ nsCString mString;
+ nsCOMPtr<calITimezoneProvider> mProvider;
+ nsCOMPtr<calIIcsComponentParsingListener> mListener;
+ nsCOMPtr<nsIThread> mThread;
+
+ class ParserWorkerCompleter : public nsRunnable {
+ public:
+ ParserWorkerCompleter(nsresult status,
+ calIIcalComponent *component,
+ calIIcsComponentParsingListener *listener) :
+ mListener(listener), mComp(component), mStatus(status)
+ {
+ }
+
+ NS_DECL_NSIRUNNABLE
+ protected:
+ nsCOMPtr<calIIcsComponentParsingListener> mListener;
+ nsCOMPtr<calIIcalComponent> mComp;
+ nsresult mStatus;
+ };
+ };
public:
calICSService();
NS_DECL_ISUPPORTS
NS_DECL_NSICLASSINFO
NS_DECL_CALIICSSERVICE
};
@@ -99,17 +136,17 @@ public:
{
mReferencedTimezones.Init();
}
// VTIMEZONE ctor
calIcalComponent(icaltimezone * icaltz, icalcomponent * ical) : mComponent(ical), mTimezone(icaltz) {
mReferencedTimezones.Init();
}
-
+
NS_DECL_ISUPPORTS
NS_DECL_NSICLASSINFO
NS_DECL_CALIICALCOMPONENT
protected:
virtual ~calIcalComponent();
calITimezoneProvider * getTzProvider() const {
deleted file mode 100644
--- a/calendar/base/src/calIcsParser-worker.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/* ***** 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
- * Philipp Kewisch <mozilla@kewis.ch>
- * Portions created by the Initial Developer are Copyright (C) 2010-2011
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *
- * 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 ***** */
-
-/**
- * Worker to parse ics service messages
- * NOTE: Since this is a ChromeWorker, access to xpcom is limited!
- */
-
-function onmessage(event) {
- let icsService = XPCOM.getService("@mozilla.org/calendar/ics-service;1");
- let rootComp = icsService.parseICS(event.data.icsString, event.data.tzProvider);
-
- postMessage(rootComp);
-}
--- a/calendar/base/src/calIcsParser.js
+++ b/calendar/base/src/calIcsParser.js
@@ -65,184 +65,124 @@ calIcsParser.prototype = {
getHelperForLanguage: function ip_getHelperForLanguage(language) {
return null;
},
implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT,
flags: Components.interfaces.nsIClassInfo.THREADSAFE,
QueryInterface: function ip_QueryInterface(aIID) {
- return doQueryInterface(this, calIcsParser.prototype, aIID, null, this);
+ return cal.doQueryInterface(this, calIcsParser.prototype, aIID, null, this);
},
- processIcalComponent: function ip_processIcalComponent(rootComp) {
+ processIcalComponent: function ip_processIcalComponent(rootComp, aAsyncParsing) {
let calComp;
// libical returns the vcalendar component if there is just one vcalendar.
// If there are multiple vcalendars, it returns an xroot component, with
- // those vcalendar children. We need to handle both.
+ // vcalendar children. We need to handle both cases.
if (rootComp) {
if (rootComp.componentType == 'VCALENDAR') {
calComp = rootComp;
} else {
calComp = rootComp.getFirstSubcomponent('VCALENDAR');
}
}
if (!calComp) {
- cal.ERROR("Parser Error. Could not find 'VCALENDAR' component in: \n" + rootComp + "\nStack: \n" + cal.STACK(10));
+ cal.ERROR("Parser Error. Could not find 'VCALENDAR' component: \n" +
+ rootComp + "\nStack: \n" + cal.STACK(10));
}
- let uid2parent = {};
- let excItems = [];
- let fakedParents = {};
-
- let tzErrors = {};
- function checkTimezone(item, dt) {
- if (dt && cal.isPhantomTimezone(dt.timezone)) {
- let tzid = dt.timezone.tzid;
- let hid = item.hashId + "#" + tzid;
- if (tzErrors[hid] === undefined) {
- // For now, publish errors to console and alert user.
- // In future, maybe make them available through an interface method
- // so this UI code can be removed from the parser, and caller can
- // choose whether to alert, or show user the problem items and ask
- // for fixes, or something else.
- let msg = (calGetString("calendar", "unknownTimezoneInItem",
- [tzid, item.title, cal.getDateFormatter().formatDateTime(dt)]) +
- "\n" + item.icalString);
- cal.ERROR(msg);
- tzErrors[hid] = true;
- }
- }
- }
+ let self = this;
+ let state = new parserState(this, aAsyncParsing);
while (calComp) {
-
- // Get unknown properties
- this.mProperties = [ prop for (prop in cal.ical.propertyIterator(calComp))
- if (prop.propertyName != "VERSION" &&
- prop.propertyName != "PRODID") ];
-
- let prodId = calComp.getFirstProperty("PRODID");
- let isFromOldSunbird = (prodId && prodId.value == "-//Mozilla.org/NONSGML Mozilla Calendar V1.0//EN");
+ // Get unknown properties from the VCALENDAR
+ this.mProperties = this.mProperties.concat(
+ [ prop for (prop in cal.ical.propertyIterator(calComp))
+ if (prop.propertyName != "VERSION" &&
+ prop.propertyName != "PRODID") ]);
for (let subComp in cal.ical.subcomponentIterator(calComp)) {
- let item = null;
- switch (subComp.componentType) {
- case "VEVENT":
- item = cal.createEvent();
- item.icalComponent = subComp;
- checkTimezone(item, item.startDate);
- checkTimezone(item, item.endDate);
- break;
- case "VTODO":
- item = cal.createTodo();
- item.icalComponent = subComp;
- checkTimezone(item, item.entryDate);
- checkTimezone(item, item.dueDate);
- // completed is defined to be in UTC
- break;
- case "VTIMEZONE":
- // this should already be attached to the relevant
- // events in the calendar, so there's no need to
- // do anything with it here.
- break;
- default:
- this.mComponents.push(subComp);
- break;
- }
-
- if (item) {
- // Only try to fix ICS from Sunbird 0.2 (and earlier) if it
- // has an EXDATE.
- let hasExdate = subComp.getFirstProperty("EXDATE");
- if (isFromOldSunbird && hasExdate) {
- item = fixOldSunbirdExceptions(item);
- }
-
- let rid = item.recurrenceId;
- if (!rid) {
- this.mItems.push(item);
- if (item.recurrenceInfo) {
- uid2parent[item.id] = item;
- }
- } else {
- excItems.push(item);
- }
- }
+ state.submit(subComp);
}
calComp = rootComp.getNextSubcomponent("VCALENDAR");
}
- // tag "exceptions", i.e. items with rid:
- for each (let item in excItems) {
- let parent = uid2parent[item.id];
+ state.join(function() {
+ let fakedParents = {};
+ // tag "exceptions", i.e. items with rid:
+ for each (let item in state.excItems) {
+ let parent = state.uid2parent[item.id];
- if (!parent) { // a parentless one, fake a master and override it's occurrence
- parent = isEvent(item) ? createEvent() : createTodo();
- parent.id = item.id;
- parent.setProperty("DTSTART", item.recurrenceId);
- parent.setProperty("X-MOZ-FAKED-MASTER", "1"); // this tag might be useful in the future
- parent.recurrenceInfo = cal.createRecurrenceInfo(parent);
- fakedParents[item.id] = true;
- uid2parent[item.id] = parent;
- this.mItems.push(parent);
- }
- if (item.id in fakedParents) {
- let rdate = Components.classes["@mozilla.org/calendar/recurrence-date;1"]
- .createInstance(Components.interfaces.calIRecurrenceDate);
- rdate.date = item.recurrenceId;
- parent.recurrenceInfo.appendRecurrenceItem(rdate);
- // we'll keep the parentless-API until we switch over using itip-process for import (e.g. in dnd code)
- this.mParentlessItems.push(item);
+ if (!parent) { // a parentless one, fake a master and override it's occurrence
+ parent = isEvent(item) ? createEvent() : createTodo();
+ parent.id = item.id;
+ parent.setProperty("DTSTART", item.recurrenceId);
+ parent.setProperty("X-MOZ-FAKED-MASTER", "1"); // this tag might be useful in the future
+ parent.recurrenceInfo = cal.createRecurrenceInfo(parent);
+ fakedParents[item.id] = true;
+ state.uid2parent[item.id] = parent;
+ state.items.push(parent);
+ }
+ if (item.id in fakedParents) {
+ let rdate = Components.classes["@mozilla.org/calendar/recurrence-date;1"]
+ .createInstance(Components.interfaces.calIRecurrenceDate);
+ rdate.date = item.recurrenceId;
+ parent.recurrenceInfo.appendRecurrenceItem(rdate);
+ // we'll keep the parentless-API until we switch over using itip-process for import (e.g. in dnd code)
+ self.mParentlessItems.push(item);
+ }
+
+ parent.recurrenceInfo.modifyException(item, true);
}
- parent.recurrenceInfo.modifyException(item, true);
- }
+ if (Object.keys(state.tzErrors).length > 0) {
+ // Use an alert rather than a prompt because problems may appear in
+ // remote subscribed calendars the user cannot change.
+ if (Components.classes["@mozilla.org/alerts-service;1"]) {
+ let notifier = Components.classes["@mozilla.org/alerts-service;1"]
+ .getService(Components.interfaces.nsIAlertsService);
+ let title = calGetString("calendar", "TimezoneErrorsAlertTitle")
+ let text = calGetString("calendar", "TimezoneErrorsSeeConsole");
+ notifier.showAlertNotification("", title, text, false, null, null, title);
+ }
+ }
- for (let e in tzErrors) { // if any error has occurred
- // Use an alert rather than a prompt because problems may appear in
- // remote subscribed calendars the user cannot change.
- if (Components.classes["@mozilla.org/alerts-service;1"]) {
- let notifier = Components.classes["@mozilla.org/alerts-service;1"]
- .getService(Components.interfaces.nsIAlertsService);
- let title = calGetString("calendar", "TimezoneErrorsAlertTitle")
- let text = calGetString("calendar", "TimezoneErrorsSeeConsole");
- notifier.showAlertNotification("", title, text, false, null, null, title);
+ // We are done, push the items to the parser and notify the listener
+ self.mItems = self.mItems.concat(state.items);
+ self.mComponents = self.mComponents.concat(state.extraComponents);
+
+ if (aAsyncParsing) {
+ aAsyncParsing.onParsingComplete(Components.results.NS_OK, self);
}
- break;
- }
+ });
},
parseString: function ip_parseString(aICSString, aTzProvider, aAsyncParsing) {
if (aAsyncParsing) {
- let this_ = this;
- let rootComp = null;
+ let self = this;
- let wf = Components.classes["@mozilla.org/threads/workerfactory;1"]
- .createInstance(Components.interfaces.nsIWorkerFactory);
-
- let worker = wf.newChromeWorker("resource://calendar/calendar-js/calIcsParser-worker.js");
+ let start = new Date();
- worker.onmessage = {
- handleEvent: function(event) {
- let rootComp = event.data.QueryInterface(Components.interfaces.calIIcalComponent);
- this_.processIcalComponent(rootComp);
- aAsyncParsing.onParsingComplete(Components.results.NS_OK, this_);
+ // We are using two types of very similar listeners here:
+ // aAsyncParsing is a calIcsParsingListener that returns the ics
+ // parser containing the processed items.
+ // The listener passed to parseICSAsync is a calICsComponentParsingListener
+ // required by the ics service, that receives the parsed root component.
+ cal.getIcsService().parseICSAsync(aICSString, aTzProvider, {
+ onParsingComplete: function(rc, rootComp) {
+ if (Components.isSuccessCode(rc)) {
+ self.processIcalComponent(rootComp, aAsyncParsing);
+ } else {
+ cal.ERROR("Error Parsing ICS: " + rc);
+ aAsyncParsing.onParsingComplete(rc, self);
+ }
}
- };
-
- worker.onerror = {
- handleEvent: function(error) {
- cal.ERROR("Error Parsing ICS: " + error.message);
- aAsyncParsing.onParsingComplete(Components.results.NS_ERROR_FAILURE, this_);
- }
- };
-
- worker.postMessage({ icsString: aICSString, tzProvider: aTzProvider });
+ });
} else {
this.processIcalComponent(cal.getIcsService().parseICS(aICSString, aTzProvider));
}
},
parseFromStream: function ip_parseFromStream(aStream, aTzProvider, aAsyncParsing) {
// Read in the string. Note that it isn't a real string at this point,
// because likely, the file is utf8. The multibyte chars show up as multiple
@@ -293,40 +233,145 @@ calIcsParser.prototype = {
},
getComponents: function ip_getComponents(aCount) {
aCount.value = this.mComponents.length;
return this.mComponents.concat([]); //clone
}
};
+/**
+ * The parser state, which helps process ical components without clogging up the
+ * event queue.
+ *
+ * @param aParser The parser that is using this state
+ */
+function parserState(aParser) {
+ this.parser = aParser;
-// Helper function to deal with the busted exdates from Sunbird 0.2
-// When Sunbird 0.2 (and earlier) creates EXDATEs, they are set to
-// 00:00:00 floating rather than to the item's DTSTART. This fixes that.
-// (bug 354073)
-function fixOldSunbirdExceptions(aItem) {
- const kCalIRecurrenceDate = Components.interfaces.calIRecurrenceDate;
+ this.extraComponents = [];
+ this.items = [];
+ this.uid2parent = {};
+ this.excItems = [];
+ this.tzErrors = {};
+}
+
+parserState.prototype = {
+ parser: null,
+ joinFunc: null,
+ threadCount: 0,
- let item = aItem;
- let ritems = aItem.recurrenceInfo.getRecurrenceItems({});
- for each (let ritem in ritems) {
- // EXDATEs are represented as calIRecurrenceDates, which are
- // negative and finite.
- if (calInstanceOf(ritem, kCalIRecurrenceDate) &&
- ritem.isNegative &&
- ritem.isFinite) {
- // Only mess with the exception if its time is wrong.
- let oldDate = aItem.startDate || aItem.entryDate;
- if (ritem.date.compare(oldDate) != 0) {
- let newRitem = ritem.clone();
- // All we want from aItem is the time and timezone.
- newRitem.date.timezone = oldDate.timezone;
- newRitem.date.hour = oldDate.hour;
- newRitem.date.minute = oldDate.minute;
- newRitem.date.second = oldDate.second;
- item.recurrenceInfo.appendRecurrenceItem(newRitem);
- item.recurrenceInfo.deleteRecurrenceItem(ritem);
+ extraComponents: null,
+ items: null,
+ uid2parent: null,
+ excItems: null,
+ tzErrors: null,
+
+ /**
+ * Checks if the timezones are missing and notifies the user via error console
+ *
+ * @param item The item to check for
+ * @param dt The datetime object to check with
+ */
+ checkTimezone: function checkTimezone(item, dt) {
+ if (dt && cal.isPhantomTimezone(dt.timezone)) {
+ let tzid = dt.timezone.tzid;
+ let hid = item.hashId + "#" + tzid;
+ if (!(hid in this.tzErrors)) {
+ // For now, publish errors to console and alert user.
+ // In future, maybe make them available through an interface method
+ // so this UI code can be removed from the parser, and caller can
+ // choose whether to alert, or show user the problem items and ask
+ // for fixes, or something else.
+ let msg = (calGetString("calendar", "unknownTimezoneInItem",
+ [tzid, item.title, cal.getDateFormatter().formatDateTime(dt)]) +
+ "\n" + item.icalString);
+ cal.ERROR(msg);
+ this.tzErrors[hid] = true;
}
}
+ },
+
+ /**
+ * Submit processing of a subcomponent to the event queue
+ *
+ * @param subComp The component to process
+ */
+ submit: function submit(subComp) {
+ let state = this;
+ let runner = {
+ run: function run() {
+ let item = null;
+ switch (subComp.componentType) {
+ case "VEVENT":
+ item = cal.createEvent();
+ item.icalComponent = subComp;
+ state.checkTimezone(item, item.startDate);
+ state.checkTimezone(item, item.endDate);
+ break;
+ case "VTODO":
+ item = cal.createTodo();
+ item.icalComponent = subComp;
+ state.checkTimezone(item, item.entryDate);
+ state.checkTimezone(item, item.dueDate);
+ // completed is defined to be in UTC
+ break;
+ case "VTIMEZONE":
+ // this should already be attached to the relevant
+ // events in the calendar, so there's no need to
+ // do anything with it here.
+ break;
+ default:
+ state.extraComponents.push(subComp);
+ break;
+ }
+
+ if (item) {
+ let rid = item.recurrenceId;
+ if (!rid) {
+ state.items.push(item);
+ if (item.recurrenceInfo) {
+ state.uid2parent[item.id] = item;
+ }
+ } else {
+ state.excItems.push(item);
+ }
+ }
+ state.threadCount--;
+ state.checkCompletion();
+ }
+ };
+
+ this.threadCount++;
+ if (this.listener) {
+ // If we have a listener, we are doing this asynchronously. Go ahead
+ // and use the thread manager to dispatch the above runner
+ Services.tm.currentThread.dispatch(runner, Components.interfaces.nsIEventTarget.DISPATCH_NORMAL);
+ } else {
+ // No listener means synchonous. Just run the runner instead
+ runner.run();
+ }
+ },
+
+ /**
+ * Checks if the processing of all events has completed. If a join function
+ * has been set, this function is called.
+ *
+ * @return True, if all tasks have been completed
+ */
+ checkCompletion: function() {
+ if (this.joinFunc && this.threadCount == 0) {
+ this.joinFunc();
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Sets a join function that is called when all tasks have been completed
+ *
+ * @param joinFunc The join function to call
+ */
+ join: function join(joinFunc) {
+ this.joinFunc = joinFunc;
+ this.checkCompletion();
}
- return item;
-}
+};
--- a/calendar/base/src/calRecurrenceDate.cpp
+++ b/calendar/base/src/calRecurrenceDate.cpp
@@ -178,17 +178,17 @@ calRecurrenceDate::GetOccurrences(calIDa
{
NS_ENSURE_ARG_POINTER(aStartTime);
NS_ENSURE_ARG_POINTER(aRangeStart);
PRInt32 r1, r2;
if (mDate) {
if (NS_SUCCEEDED(mDate->Compare(aRangeStart, &r1)) && r1 >= 0 &&
- (!aRangeEnd || NS_SUCCEEDED(mDate->Compare(aRangeEnd, &r2)) && r2 < 0))
+ (!aRangeEnd || (NS_SUCCEEDED(mDate->Compare(aRangeEnd, &r2)) && r2 < 0)))
{
calIDateTime **dates = (calIDateTime **) nsMemory::Alloc(sizeof(calIDateTime*));
NS_ADDREF (dates[0] = mDate);
*aDates = dates;
*aCount = 1;
return NS_OK;
}
}