diff --git a/calendar/base/build/calBaseModule.cpp b/calendar/base/build/calBaseModule.cpp
index 92bbcdcc932..fca80816357 100644
--- a/calendar/base/build/calBaseModule.cpp
+++ b/calendar/base/build/calBaseModule.cpp
@@ -40,7 +40,6 @@
#include "calDateTime.h"
#include "calICSService.h"
-#include "calRecurrenceInfo.h"
#include "calRecurrenceRule.h"
#include "calRecurrenceDate.h"
#include "calRecurrenceDateSet.h"
@@ -49,37 +48,73 @@
NS_GENERIC_FACTORY_CONSTRUCTOR(calDateTime)
NS_GENERIC_FACTORY_CONSTRUCTOR(calICSService)
-NS_GENERIC_FACTORY_CONSTRUCTOR(calRecurrenceInfo)
NS_GENERIC_FACTORY_CONSTRUCTOR(calRecurrenceRule)
NS_GENERIC_FACTORY_CONSTRUCTOR(calRecurrenceDate)
NS_GENERIC_FACTORY_CONSTRUCTOR(calRecurrenceDateSet)
+NS_DECL_CLASSINFO(calDateTime)
+NS_DECL_CLASSINFO(calICSService)
+NS_DECL_CLASSINFO(calRecurrenceRule)
+NS_DECL_CLASSINFO(calRecurrenceDate)
+NS_DECL_CLASSINFO(calRecurrenceDateSet)
+
static const nsModuleComponentInfo components[] =
{
{ "Calendar DateTime Object",
CAL_DATETIME_CID,
CAL_DATETIME_CONTRACTID,
- calDateTimeConstructor },
+ calDateTimeConstructor,
+ NULL,
+ NULL,
+ NULL,
+ NS_CI_INTERFACE_GETTER_NAME(calDateTime),
+ NULL,
+ &NS_CLASSINFO_NAME(calDateTime)
+ },
{ "ICS parser/serializer",
CAL_ICSSERVICE_CID,
CAL_ICSSERVICE_CONTRACTID,
- calICSServiceConstructor },
- { "Calendar Recurrence Object",
- CAL_RECURRENCEINFO_CID,
- CAL_RECURRENCEINFO_CONTRACTID,
- calRecurrenceInfoConstructor },
+ calICSServiceConstructor,
+ NULL,
+ NULL,
+ NULL,
+ NS_CI_INTERFACE_GETTER_NAME(calICSService),
+ NULL,
+ &NS_CLASSINFO_NAME(calICSService)
+ },
{ "Calendar Recurrence Rule",
CAL_RECURRENCERULE_CID,
CAL_RECURRENCERULE_CONTRACTID,
- calRecurrenceRuleConstructor },
+ calRecurrenceRuleConstructor,
+ NULL,
+ NULL,
+ NULL,
+ NS_CI_INTERFACE_GETTER_NAME(calRecurrenceRule),
+ NULL,
+ &NS_CLASSINFO_NAME(calRecurrenceRule)
+ },
{ "Calendar Recurrence Date",
CAL_RECURRENCEDATE_CID,
CAL_RECURRENCEDATE_CONTRACTID,
- calRecurrenceDateConstructor },
+ calRecurrenceDateConstructor,
+ NULL,
+ NULL,
+ NULL,
+ NS_CI_INTERFACE_GETTER_NAME(calRecurrenceDate),
+ NULL,
+ &NS_CLASSINFO_NAME(calRecurrenceDate)
+ },
{ "Calendar Recurrence Date Set",
CAL_RECURRENCEDATESET_CID,
CAL_RECURRENCEDATESET_CONTRACTID,
- calRecurrenceDateSetConstructor }
+ calRecurrenceDateSetConstructor,
+ NULL,
+ NULL,
+ NULL,
+ NS_CI_INTERFACE_GETTER_NAME(calRecurrenceDateSet),
+ NULL,
+ &NS_CLASSINFO_NAME(calRecurrenceDateSet)
+ }
};
NS_IMPL_NSGETMODULE(calBaseModule, components)
diff --git a/calendar/base/content/calendar-event-dialog.js b/calendar/base/content/calendar-event-dialog.js
index de10c3cb531..3ed47bd2e1b 100644
--- a/calendar/base/content/calendar-event-dialog.js
+++ b/calendar/base/content/calendar-event-dialog.js
@@ -76,9 +76,13 @@ function onAccept()
{
// if this event isn't mutable, we need to clone it like a sheep
var originalEvent = window.calendarEvent;
- var event = null;
+ var event = originalEvent;
- event = (originalEvent.isMutable) ? originalEvent : originalEvent.clone();
+ if (!event.isMutable) {
+ event = event.clone();
+ } else {
+ dump ("#### modifyEvent is mutable already?\n");
+ }
saveDialog(event);
@@ -137,12 +141,20 @@ function loadDialog()
}
/* recurrence */
- if (event.recurrenceInfo) {
+ /* if the item is a proxy occurrence/instance, a few things aren't valid:
+ * - Setting recurrence on the item
+ * - changing the calendar
+ */
+ if (event.parentItem != event) {
+ setElementValue("event-recurrence", "true", "disabled");
+ setElementValue("set-recurrence", "true", "disabled");
+ setElementValue("event-calendar", "true", "disabled");
+ } else if (event.recurrenceInfo) {
setElementValue("event-recurrence", "true", "checked");
}
/* alarms */
- if (event.hasAlarm) {
+ if (event.alarmTime) {
var alarmLength = event.getProperty("alarmLength");
if (alarmLength != null) {
setElementValue("alarm-length-field", alarmLength);
@@ -193,8 +205,8 @@ function saveDialog(event)
}
/* alarms */
- event.hasAlarm = (getElementValue("event-alarm") != "none");
- if (!event.hasAlarm) {
+ var hasAlarm = (getElementValue("event-alarm") != "none");
+ if (!hasAlarm) {
event.deleteProperty("alarmLength");
event.deleteProperty("alarmUnits");
event.deleteProperty("alarmRelated");
diff --git a/calendar/base/content/calendar-multiday-view.css b/calendar/base/content/calendar-multiday-view.css
index 516908e963e..bc601d52bd5 100644
--- a/calendar/base/content/calendar-multiday-view.css
+++ b/calendar/base/content/calendar-multiday-view.css
@@ -43,10 +43,16 @@ calendar-event-column {
.fgdragcontainer[dragging="true"] {
display: -moz-box;
+ /* This is a workaround for a stack bug and display: hidden in underlying
+ * elements -- the display: hidden bits get misrendered as being on top.
+ * Setting an opacity here forces a view to be created for this element, too.
+ */
+ opacity: 0.9999;
}
.fgdragbox-label {
font-weight: bold;
+ overflow: hidden;
}
/*== calendar-event-box ==*/
@@ -58,6 +64,7 @@ calendar-event-box {
.calendar-event-box-container {
background: #4e84c2;
padding: 2px;
+ overflow: hidden;
}
.calendar-event-box-container[parentorient="vertical"] {
diff --git a/calendar/base/content/calendar-multiday-view.xml b/calendar/base/content/calendar-multiday-view.xml
index 09174ddd332..2fdd71eb4fe 100644
--- a/calendar/base/content/calendar-multiday-view.xml
+++ b/calendar/base/content/calendar-multiday-view.xml
@@ -110,7 +110,7 @@
if (!orient) orient = "horizontal";
if (orient == "vertical") otherorient = "horizontal";
- dump ("calendar-time-bar: orient: " + orient + " other: " + otherorient + "\n");
+ //dump ("calendar-time-bar: orient: " + orient + " other: " + otherorient + "\n");
function makeTimeBox(timestr, size) {
var box = createXULElement("box");
@@ -242,7 +242,7 @@
-
+
@@ -408,7 +408,9 @@
>> drag is: " + this.mDragState.dragType + "\n");
+ //dump (">>> drag is: " + this.mDragState.dragType + "\n");
window.addEventListener("mousemove", this.onEventSweepMouseMove, false);
window.addEventListener("mouseup", this.onEventSweepMouseUp, false);
@@ -1160,7 +1164,7 @@
-
+
@@ -1224,10 +1228,15 @@
]]>
@@ -1237,10 +1246,10 @@
onset="return (this.mParentColumn = val);"/>
+ onget="if (!this.mOccurrence) return 0; return this.mOccurrence.startDate.hour * 60 + this.mOccurrence.startDate.minute"/>
+ onget="if (!this.mOccurrence) return 0; return this.mOccurrence.endDate.hour * 60 + this.mOccurrence.endDate.minute"/>
@@ -1248,11 +1257,6 @@
event.preventBubble();
this.calendarView.selectedOccurrence = this.mOccurrence;
- if (this.mOccurrence.item.recurrenceInfo) {
- // XXXvv can't drag recurring events yet, FIXME
- return;
- }
-
this.mInMouseDown = true;
this.mMouseX = event.screenX;
this.mMouseY = event.screenY;
@@ -1357,7 +1361,7 @@
this.calView.refresh();
},
onAddItem: function (aItem) {
- //dump ("++ AddItem\n");
+ //dump ("++ AddItem " + aItem + "\n");
if (!(aItem instanceof Components.interfaces.calIEvent))
return;
aItem = aItem.QueryInterface(Components.interfaces.calIEvent);
@@ -1365,6 +1369,7 @@
var occs = aItem.getOccurrencesBetween(this.calView.startDate,
this.calView.endDate,
{});
+ //dump ("occs: " + occs.length + "\n");
for each (var occ in occs) {
this.calView.doAddEvent(occ);
}
@@ -1591,7 +1596,11 @@
if (this.mSelectedOccurrence != val) {
if (this.mSelectedOccurrence) {
var col = this.findColumnForEvent(this.mSelectedOccurrence);
- col.column.selectOccurrence(null);
+ if (col) {
+ col.column.selectOccurrence(null);
+ } else {
+ dump ("Thought I had a selected occurrence (id: " + this.mSelectedOccurrence.id + "), but couldn't find a column for it!\n");
+ }
}
if (val) {
@@ -1675,7 +1684,7 @@
labelbox.setAttribute("height", 30);
labelbox.removeAttribute("width");
- var timebarWidth = 50;
+ var timebarWidth = 100;
timebar.setAttribute("width", timebarWidth);
timebar.removeAttribute("height");
headertimespacer.setAttribute("width", timebarWidth);
@@ -1689,7 +1698,7 @@
labelbox.setAttribute("width", 30);
labelbox.removeAttribute("height");
- var timebarHeight = 30;
+ var timebarHeight = 40;
timebar.setAttribute("height", timebarHeight);
timebar.removeAttribute("width");
headertimespacer.setAttribute("height", timebarHeight);
@@ -1833,8 +1842,8 @@
aStartTime
- calIItemOccurrence getNextOccurrence (in calIDateTime aOccurrenceTime);
+ /**
+ * This is a shortcut to appending or removing a single negative
+ * date assertion. This shortcut may or may not cause problems
+ * later on, but hey, that's fixable later!
+ */
+ void removeOccurrenceAt (in calIDateTime aRecurrenceId);
+ void restoreOccurrenceAt (in calIDateTime aRecurrenceId);
- // return array of calIItemOccurrence representing all
- // occurrences of this event between start (inclusive) and end (non-inclusive).
+ /*
+ * exceptions
+ */
+
+ /**
+ * Modify an a particular occurrence with the given exception proxy
+ * item. If the recurrenceId isn't an already existing exception item,
+ * a new exception is added. Otherwise, the existing exception
+ * is modified.
+ *
+ * The item's parentItem must be equal to this RecurrenceInfo's
+ * item. <-- XXX check this, compare by calendar/id only
+ */
+ void modifyException (in calIItemBase anItem);
+
+ /**
+ * Return an existing exception item for the given recurrence ID.
+ * If an exception does not exist, and aCreate is set, a new one
+ * is created and returned. Otherwise, null is returned.
+ */
+ calIItemBase getExceptionFor (in calIDateTime aRecurrenceId, in boolean aCreate);
+
+ /**
+ * Removes an exception item for the given recurrence ID, if
+ * any exist.
+ */
+ void removeExceptionFor (in calIDateTime aRecurrenceId);
+
+ /**
+ * Returns a list of all recurrence ids that have exceptions.
+ */
+ void getExceptionIds (out unsigned long aCount, [array,size_is(aCount),retval] out calIDateTime aIds);
+
+ /*
+ * recurrence calculation
+ */
+
+ /*
+ * Get the occurrence at the given recurrence ID; if there is no
+ * exception, then create a new proxy object with the normal occurrence.
+ * Otherwise, return the exception.
+ */
+ calIItemBase getOccurrenceFor (in calIDateTime aRecurrenceId);
+
+ /**
+ * Return the next start calIDateTime of the recurrence specified by
+ * this RecurrenceInfo, after aOccurrenceTime.
+ */
+ calIDateTime getNextOccurrenceDate (in calIDateTime aOccurrenceTime);
+
+ /**
+ * Return the next item specified by this RecurrenceInfo, after aOccurrenceTime.
+ */
+ calIItemBase getNextOccurrence (in calIDateTime aOccurrenceTime);
+
+ /**
+ * Return an array of calIDateTime representing all start times of this event
+ * between start (inclusive) and end (non-inclusive).
+ */
+ void getOccurrenceDates (in calIDateTime aRangeStart,
+ in calIDateTime aRangeEnd,
+ in unsigned long aMaxCount,
+ out unsigned long aCount, [array,size_is(aCount),retval] out calIDateTime aDates);
+
+ /**
+ * Return an array of calIItemOccurrence representing all
+ * occurrences of this event between start (inclusive) and end (non-inclusive).
+ */
void getOccurrences (in calIDateTime aRangeStart,
in calIDateTime aRangeEnd,
in unsigned long aMaxCount,
- out unsigned long aCount, [array,size_is(aCount),retval] out calIItemOccurrence aItems);
+ out unsigned long aCount, [array,size_is(aCount),retval] out calIItemBase aItems);
};
diff --git a/calendar/base/public/calIRecurrenceItem.idl b/calendar/base/public/calIRecurrenceItem.idl
index 7e75ab50ad6..83ea857aeef 100644
--- a/calendar/base/public/calIRecurrenceItem.idl
+++ b/calendar/base/public/calIRecurrenceItem.idl
@@ -44,7 +44,7 @@ interface calIItemOccurrence;
interface calIIcalProperty;
-[scriptable, uuid(d438b44a-9d6e-4ebb-bb03-321c9b81b216)]
+[scriptable, uuid(943be334-4995-477e-b325-f0c2319183e8)]
interface calIRecurrenceItem : nsISupports
{
// returns true if this thing is able to be modified;
@@ -62,6 +62,10 @@ interface calIRecurrenceItem : nsISupports
// as a negative rule (e.g. exceptions instead of rdates)
attribute boolean isNegative;
+ // returns whether this item has a finite number of dates
+ // or not (e.g. a rule with no end date)
+ readonly attribute boolean isFinite;
+
// return the next start time after aOccurrencetime for this
// recurrence, starting at aStartTime.
calIDateTime getNextOccurrence (in calIDateTime aStartTime,
diff --git a/calendar/base/public/calIRecurrenceRule.idl b/calendar/base/public/calIRecurrenceRule.idl
index b02c446d49f..b226808e4c5 100644
--- a/calendar/base/public/calIRecurrenceRule.idl
+++ b/calendar/base/public/calIRecurrenceRule.idl
@@ -42,7 +42,6 @@
interface calIItemBase;
interface calIDateTime;
-interface calIItemOccurrence;
// an interface implementing a RRULE
diff --git a/calendar/base/src/Makefile.in b/calendar/base/src/Makefile.in
index 48f20eb194f..3a6cdf169fd 100644
--- a/calendar/base/src/Makefile.in
+++ b/calendar/base/src/Makefile.in
@@ -61,9 +61,13 @@ REQUIRES = xpcom \
# sqlite3 \
# $(NULL)
+XPIDL_MODULE = calbaseinternal
+XPIDLSRCS = \
+ calInternalInterfaces.idl \
+ $(NULL)
+
CPPSRCS = calDateTime.cpp \
calICSService.cpp \
- calRecurrenceInfo.cpp \
calRecurrenceRule.cpp \
calRecurrenceDate.cpp \
calRecurrenceDateSet.cpp \
@@ -75,6 +79,7 @@ EXTRA_COMPONENTS = \
calAttachment.js \
calAttendee.js \
calCalendarManager.js \
+ calRecurrenceInfo.js \
calEvent.js \
calItemBase.js \
calItemModule.js \
diff --git a/calendar/base/src/calAlarmService.js b/calendar/base/src/calAlarmService.js
index abcf8201507..53cfa170f98 100644
--- a/calendar/base/src/calAlarmService.js
+++ b/calendar/base/src/calAlarmService.js
@@ -80,13 +80,13 @@ function calAlarmService() {
onEndBatch: function() { },
onLoad: function() { },
onAddItem: function(aItem) {
- if (aItem.hasAlarm)
+ if (aItem.alarmTime)
this.alarmService.addAlarm(aItem, false);
},
onModifyItem: function(aNewItem, aOldItem) {
this.alarmService.removeAlarm(aOldItem);
- if (aNewItem.hasAlarm)
+ if (aNewItem.alarmTime)
this.alarmService.addAlarm(aNewItem, false);
},
onDeleteItem: function(aDeletedItem) {
@@ -112,12 +112,13 @@ function calAlarmService() {
};
}
-calAlarmServiceClassInfo = {
+var calAlarmServiceClassInfo = {
getInterfaces: function (count) {
var ifaces = [
Components.interfaces.nsISupports,
Components.interfaces.calIAlarmService,
- Components.interfaces.nsIObserver
+ Components.interfaces.nsIObserver,
+ Components.interfaces.nsIClassInfo
];
count.value = ifaces.length;
return ifaces;
@@ -334,7 +335,7 @@ calAlarmService.prototype = {
onGetResult: function(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
for (var i = 0; i < aCount; ++i) {
var item = aItems[i];
- if (item.hasAlarm) {
+ if (item.alarmTime) {
this.alarmService.addAlarm(item, false);
}
}
diff --git a/calendar/base/src/calAttachment.js b/calendar/base/src/calAttachment.js
index 56003153fa4..935e064531f 100644
--- a/calendar/base/src/calAttachment.js
+++ b/calendar/base/src/calAttachment.js
@@ -40,11 +40,12 @@
//
function calAttachment() { }
-calAttachmentClassInfo = {
+var calAttachmentClassInfo = {
getInterfaces: function (count) {
var ifaces = [
Components.interfaces.nsISupports,
- Components.interfaces.calIAttachment
+ Components.interfaces.calIAttachment,
+ Components.interfaces.nsIClassInfo
];
count.value = ifaces.length;
return ifaces;
diff --git a/calendar/base/src/calAttendee.js b/calendar/base/src/calAttendee.js
index b55c71dfda4..ceda92bf0a5 100644
--- a/calendar/base/src/calAttendee.js
+++ b/calendar/base/src/calAttendee.js
@@ -42,11 +42,12 @@ function calAttendee() {
createInstance(Components.interfaces.nsIWritablePropertyBag);
}
-calAttendeeClassInfo = {
+var calAttendeeClassInfo = {
getInterfaces: function (count) {
var ifaces = [
Components.interfaces.nsISupports,
- Components.interfaces.calIAttendee
+ Components.interfaces.calIAttendee,
+ Components.interfaces.nsIClassInfo
];
count.value = ifaces.length;
return ifaces;
diff --git a/calendar/base/src/calCalendarManager.js b/calendar/base/src/calCalendarManager.js
index 87feb2465df..3ab43615dc2 100644
--- a/calendar/base/src/calCalendarManager.js
+++ b/calendar/base/src/calCalendarManager.js
@@ -66,11 +66,12 @@ function makeURI(uriString)
return ioservice.newURI(uriString, null, null);
}
-calCalendarManagerClassInfo = {
+var calCalendarManagerClassInfo = {
getInterfaces: function (count) {
var ifaces = [
Components.interfaces.nsISupports,
- Components.interfaces.calICalendarManager
+ Components.interfaces.calICalendarManager,
+ Components.interfaces.nsIClassInfo
];
count.value = ifaces.length;
return ifaces;
diff --git a/calendar/base/src/calDateTime.cpp b/calendar/base/src/calDateTime.cpp
index 04fc649492c..7f79bbbf742 100644
--- a/calendar/base/src/calDateTime.cpp
+++ b/calendar/base/src/calDateTime.cpp
@@ -54,7 +54,7 @@ extern "C" {
static NS_DEFINE_CID(kCalICSService, CAL_ICSSERVICE_CID);
-NS_IMPL_ISUPPORTS2(calDateTime, calIDateTime, nsIXPCScriptable)
+NS_IMPL_ISUPPORTS2_CI(calDateTime, calIDateTime, nsIXPCScriptable)
calDateTime::calDateTime()
: mImmutable(PR_FALSE),
@@ -107,7 +107,7 @@ NS_IMETHODIMP
calDateTime::MakeImmutable()
{
if (mImmutable)
- return NS_ERROR_CALENDAR_IMMUTABLE;
+ return NS_ERROR_OBJECT_IS_IMMUTABLE;
mImmutable = PR_TRUE;
return NS_OK;
@@ -243,7 +243,7 @@ calDateTime::AddDuration(calIDateTime *aDuration)
mLastModified = PR_Now();
- return SetTimeInTimezone(mNativeTime + nativeDur, mTimezone);
+ return SetNativeTime(mNativeTime + nativeDur);
}
NS_IMETHODIMP
diff --git a/calendar/base/src/calEvent.js b/calendar/base/src/calEvent.js
index 7e7d7a0b579..3a62ca34665 100644
--- a/calendar/base/src/calEvent.js
+++ b/calendar/base/src/calEvent.js
@@ -48,17 +48,26 @@ function calEvent() {
this.wrappedJSObject = this;
this.initItemBase();
this.initEvent();
+
+ this.eventPromotedProps = {
+ "DTSTART": true,
+ "DTEND": true,
+ "DTSTAMP": true,
+ __proto__: this.itemBasePromotedProps
+ }
}
// var trickery to suppress lib-as-component errors from loader
var calItemBase;
-calEventClassInfo = {
+var calEventClassInfo = {
getInterfaces: function (count) {
var ifaces = [
Components.interfaces.nsISupports,
Components.interfaces.calIItemBase,
- Components.interfaces.calIEvent
+ Components.interfaces.calIEvent,
+ Components.interfaces.calIInternalShallowCopy,
+ Components.interfaces.nsIClassInfo
];
count.value = ifaces.length;
return ifaces;
@@ -79,7 +88,8 @@ calEvent.prototype = {
__proto__: calItemBase ? (new calItemBase()) : {},
QueryInterface: function (aIID) {
- if (aIID.equals(Components.interfaces.calIEvent))
+ if (aIID.equals(Components.interfaces.calIEvent) ||
+ aIID.equals(Components.interfaces.calIInternalShallowCopy))
return this;
if (aIID.equals(Components.interfaces.nsIClassInfo))
@@ -88,36 +98,55 @@ calEvent.prototype = {
return this.__proto__.__proto__.QueryInterface.call(this, aIID);
},
- clone: function () {
+ cloneShallow: function (aNewParent) {
var m = new calEvent();
- this.cloneItemBaseInto(m);
- m.mStartDate = this.mStartDate.clone();
- m.mEndDate = this.mEndDate.clone();
- m.isAllDay = this.isAllDay;
+ this.cloneItemBaseInto(m, aNewParent);
+
+ return m;
+ },
+
+ clone: function () {
+ var m;
+
+ if (this.mParentItem) {
+ var clonedParent = this.mParentItem.clone();
+ m = clonedParent.recurrenceInfo.getOccurrenceFor (this.recurrenceId);
+ } else {
+ m = this.cloneShallow(null);
+ }
+
+ return m;
+ },
+
+ createProxy: function () {
+ if (this.mIsProxy) {
+ calDebug("Tried to create a proxy for an existing proxy!\n");
+ throw Components.results.NS_ERROR_UNEXPECTED;
+ }
+
+ var m = new calEvent();
+ m.initializeProxy(this);
return m;
},
makeImmutable: function () {
- this.mStartDate.makeImmutable();
- this.mEndDate.makeImmutable();
-
this.makeItemBaseImmutable();
},
initEvent: function() {
- this.mStartDate = new CalDateTime();
- this.mEndDate = new CalDateTime();
+ this.startDate = new CalDateTime();
+ this.endDate = new CalDateTime();
},
get duration() {
var dur = new CalDateTime();
- dur.setTimeInTimezone (this.mEndDate.nativeTime - this.mStartDate.nativeTime, "floating");
+ dur.setTimeInTimezone (this.endDate.nativeTime - this.startDate.nativeTime, "floating");
return dur;
},
get recurrenceStartDate() {
- return this.mStartDate;
+ return this.startDate;
},
icsEventPropMap: [
@@ -163,6 +192,8 @@ calEvent.prototype = {
return icalcomp;
},
+ eventPromotedProps: null,
+
set icalComponent(event) {
this.modify();
if (event.componentType != "VEVENT") {
@@ -173,19 +204,17 @@ calEvent.prototype = {
this.setItemBaseFromICS(event);
this.mapPropsFromICS(event, this.icsEventPropMap);
- this.mIsAllDay = this.mStartDate && this.mStartDate.isDate;
+ this.mIsAllDay = this.startDate && this.startDate.isDate;
- var promotedProps = {
- "DTSTART": true,
- "DTEND": true,
- "DTSTAMP": true,
- __proto__: this.itemBasePromotedProps
- };
- this.importUnpromotedProperties(event, promotedProps);
+ this.importUnpromotedProperties(event, eventPromotedProps);
// Importing didn't really change anything
this.mDirty = false;
},
+ isPropertyPromoted: function (name) {
+ return (this.eventPromotedProps[name]);
+ },
+
getOccurrencesBetween: function(aStartDate, aEndDate, aCount) {
if (this.recurrenceInfo) {
return this.recurrenceInfo.getOccurrences(aStartDate, aEndDate, 0, aCount);
@@ -194,10 +223,8 @@ calEvent.prototype = {
if ((this.startDate.compare(aStartDate) >= 0 && this.startDate.compare(aEndDate) <= 0) ||
(this.endDate.compare(aStartDate) >= 0 && this.endDate.compare(aEndDate) <= 0))
{
- var occ = Components.classes["@mozilla.org/calendar/item-occurrence;1"].createInstance(Components.interfaces.calIItemOccurrence);
- occ.initialize(this, this.startDate, this.endDate);
aCount.value = 1;
- return ([ occ ]);
+ return ([ this ]);
}
aCount.value = 0;
@@ -209,7 +236,8 @@ calEvent.prototype = {
var makeMemberAttr;
if (makeMemberAttr) {
- makeMemberAttr(calEvent, "mStartDate", null, "startDate");
- makeMemberAttr(calEvent, "mEndDate", null, "endDate");
+ makeMemberAttr(calEvent, "DTSTART", null, "startDate", true);
+ makeMemberAttr(calEvent, "DTEND", null, "endDate", true);
+ // XXX get rid of this
makeMemberAttr(calEvent, "mIsAllDay", false, "isAllDay");
}
diff --git a/calendar/base/src/calICSService.cpp b/calendar/base/src/calICSService.cpp
index ea55dd2e838..dfd78168811 100644
--- a/calendar/base/src/calICSService.cpp
+++ b/calendar/base/src/calICSService.cpp
@@ -948,7 +948,7 @@ calIcalComponent::RemoveProperty(calIIcalProperty *prop)
return NS_OK;
}
-NS_IMPL_ISUPPORTS1(calICSService, calIICSService)
+NS_IMPL_ISUPPORTS1_CI(calICSService, calIICSService)
calICSService::calICSService()
{
diff --git a/calendar/base/src/calItemBase.js b/calendar/base/src/calItemBase.js
index f83015f1545..3201524cea9 100644
--- a/calendar/base/src/calItemBase.js
+++ b/calendar/base/src/calItemBase.js
@@ -42,10 +42,23 @@
//
const ICAL = Components.interfaces.calIIcalComponent;
+const kHashPropertyBagContractID = "@mozilla.org/hash-property-bag;1";
+const kIWritablePropertyBag = Components.interfaces.nsIWritablePropertyBag;
+const HashPropertyBag = new Components.Constructor(kHashPropertyBagContractID, kIWritablePropertyBag);
-function calItemBase() { }
+function NewCalDateTime(aJSDate) {
+ var c = new CalDateTime();
+ if (aJSDate)
+ c.jsDate = c;
+ return c;
+}
+
+function calItemBase() {
+}
calItemBase.prototype = {
+ mIsProxy: false,
+
QueryInterface: function (aIID) {
if (!aIID.equals(Components.interfaces.nsISupports) &&
!aIID.equals(Components.interfaces.calIItemBase))
@@ -56,21 +69,63 @@ calItemBase.prototype = {
return this;
},
+ mParentItem: null,
+ get parentItem() {
+ if (this.mParentItem)
+ return this.mParentItem;
+ else
+ return this;
+ },
+ set parentItem(value) {
+ if (this.mImmutable)
+ throw Components.results.NS_ERROR_OBJECT_IS_IMMUTABLE;
+ this.mIsProxy = true;
+ this.mParentItem = value;
+ },
+
+ initializeProxy: function (aParentItem) {
+ if (this.mImmutable)
+ throw Components.results.NS_ERROR_OBJECT_IS_IMMUTABLE;
+
+ if (this.mParentItem != null)
+ throw Components.results.NS_ERROR_FAILURE;
+
+ this.mParentItem = aParentItem;
+ this.mCalendar = aParentItem.mCalendar;
+ this.mIsProxy = true;
+ },
+
+ //
+ // calIItemBase
+ //
mImmutable: false,
get isMutable() { return !this.mImmutable; },
mDirty: false,
modify: function() {
if (this.mImmutable)
- // Components.results.NS_ERROR_CALENDAR_IMMUTABLE;
- throw Components.results.NS_ERROR_FAILURE;
+ throw Components.results.NS_ERROR_OBJECT_IS_IMMUTABLE;
this.mDirty = true;
},
- makeItemBaseImmutable: function() {
- // make all our components immutable
- this.mCreationDate.makeImmutable();
+ ensureNotDirty: function() {
+ if (!this.mDirty)
+ return;
+ if (this.mImmutable) {
+ dump ("### Something tried to undirty a dirty immutable event!\n");
+ throw Components.results.NS_ERROR_OBJECT_IS_IMMUTABLE;
+ }
+
+ this.setProperty("LAST-MODIFIED", NewCalDateTime(new Date()));
+ this.mDirty = false;
+ },
+
+ makeItemBaseImmutable: function() {
+ if (this.mImmutable)
+ throw Components.results.NS_ERROR_OBJECT_IS_IMMUTABLE;
+
+ // make all our components immutable
if (this.mRecurrenceInfo)
this.mRecurrenceInfo.makeImmutable();
if (this.mAlarmTime)
@@ -80,25 +135,31 @@ calItemBase.prototype = {
this.mOrganizer.makeImmutable();
for (var i = 0; i < this.mAttendees.length; i++)
this.mAttendees[i].makeImmutable();
+
+ var e = this.mProperties.enumerator;
+ while (e.hasMoreElements()) {
+ var prop = e.getNext().QueryInterface(Components.interfaces.nsIProperty);
+ var val = prop.value;
+
+ if (prop.value instanceof Components.interfaces.calIDateTime) {
+ if (prop.value.isMutable)
+ prop.value.makeImmutable();
+ }
+ }
+
+ this.ensureNotDirty();
this.mImmutable = true;
},
// initialize this class's members
initItemBase: function () {
- this.mCreationDate = new CalDateTime();
- this.mAlarmTime = new CalDateTime();
- this.mLastModifiedTime = new CalDateTime();
- this.mStampTime = new CalDateTime();
+ var now = new Date();
- this.mCreationDate.jsDate = new Date();
- this.mLastModifiedTime.jsDate = new Date();
- this.mStampTime.jsDate = new Date();
-
- this.mProperties = Components.classes["@mozilla.org/hash-property-bag;1"].
- createInstance(Components.interfaces.nsIWritablePropertyBag);
+ this.mProperties = new HashPropertyBag();
- this.mAttachments = Components.classes["@mozilla.org/array;1"].
- createInstance(Components.interfaces.nsIArray);
+ this.setProperty("CREATED", NewCalDateTime(now));
+ this.setProperty("LAST-MODIFIED", NewCalDateTime(now));
+ this.setProperty("DTSTAMP", NewCalDateTime(now));
this.mAttendees = [];
@@ -108,82 +169,173 @@ calItemBase.prototype = {
},
// for subclasses to use; copies the ItemBase's values
- // into m
- cloneItemBaseInto: function (m) {
- var suppressDCE = this.lastModifiedTime;
- suppressDCE = this.stampTime;
+ // into m. aNewParent is optional
+ cloneItemBaseInto: function (m, aNewParent) {
+ this.updateStampTime();
+ this.ensureNotDirty();
m.mImmutable = false;
- m.mGeneration = this.mGeneration;
- m.mLastModifiedTime = this.mLastModifiedTime.clone();
- m.mCalendar = this.mCalendar;
- m.mId = this.mId;
- m.mTitle = this.mTitle;
- m.mPriority = this.mPriority;
- m.mPrivacy = this.mPrivacy;
- m.mStatus = this.mStatus;
- m.mHasAlarm = this.mHasAlarm;
+ m.mIsProxy = this.mIsProxy;
+ m.mParentItem = aNewParent || this.mParentItem;
- m.mCreationDate = this.mCreationDate.clone();
- m.mStampTime = this.mStampTime.clone();
+ m.mCalendar = this.mCalendar;
if (this.mRecurrenceInfo) {
m.mRecurrenceInfo = this.mRecurrenceInfo.clone();
- dump ("old recurType: " + this.mRecurrenceInfo.recurType + " new type: " + m.mRecurrenceInfo.recurType + "\n");
+ m.mRecurrenceInfo.item = m;
}
- if (this.mAlarmTime)
- m.mAlarmTime = this.mAlarmTime.clone();
m.mAttendees = [];
for (var i = 0; i < this.mAttendees.length; i++)
m.mAttendees[i] = this.mAttendees[i].clone();
- // these need fixing
- m.mAttachments = this.mAttachments;
-
m.mProperties = Components.classes["@mozilla.org/hash-property-bag;1"].
createInstance(Components.interfaces.nsIWritablePropertyBag);
var e = this.mProperties.enumerator;
while (e.hasMoreElements()) {
var prop = e.getNext().QueryInterface(Components.interfaces.nsIProperty);
- m.mProperties.setProperty (prop.name, prop.value);
+ var val = prop.value;
+
+ if (prop.value instanceof Components.interfaces.calIDateTime)
+ val = prop.value.clone();
+
+ m.mProperties.setProperty (prop.name, val);
}
- m.mDirty = this.mDirty;
+ m.mDirty = false;
+ // these need fixing
+ m.mAttachments = this.mAttachments;
return m;
},
get lastModifiedTime() {
- if (this.mDirty) {
- this.mLastModifiedTime.jsDate = new Date();
- this.mDirty = false;
- }
- return this.mLastModifiedTime;
+ this.ensureNotDirty();
+ return this.getProperty("LAST-MODIFIED");
},
- mStampTime: null,
get stampTime() {
- if (this.mStampTime.isValid)
- return this.mStampTime;
- return this.mLastModifiedTime;
+ var prop = this.getProperty("DTSTAMP");
+ if (prop && prop.isValid)
+ return prop;
+ return this.getProperty("LAST-MODIFIED");
},
updateStampTime: function() {
+ // can't update the stamp time on an immutable event
+ if (this.mImmutable)
+ return;
+
this.modify();
- this.mStampTime.jsDate = new Date();
+ this.setProperty("DTSTAMP", NewCalDateTime(new Date()));
},
- get propertyEnumerator() { return this.mProperties.enumerator; },
+ get unproxiedPropertyEnumerator() {
+ return this.mProperties.enumerator;
+ },
+
+ get propertyEnumerator() {
+ if (this.mIsProxy) {
+ // nsISimpleEnumerator sucks. It really, really sucks.
+ // The interface is badly defined, it's not clear
+ // what happens if you just keep calling getNext() without
+ // calling hasMoreElements in between, which seems like more
+ // of an informational thing. An interface with
+ // "advance()" which returns true or false, and with "item()",
+ // which returns the item the enumerator is pointing to, makes
+ // far more sense. Right now we have getNext() doing both
+ // item returning and enumerator advancing, which makes
+ // no sense.
+ return {
+ firstEnumerator: this.mProperties.eumerator,
+ secondEnumerator: this.mParentItem.propertyEnumerator,
+ handledProperties: { },
+
+ currentItem: null,
+
+ QueryInterface: function(aIID) {
+ if (!aIID.equals(Components.interfaces.nsISimpleEnumerator) ||
+ !aIID.equals(Components.interfaces.nsISupports))
+ {
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ }
+ return this;
+ },
+
+ hasMoreElements: function() {
+ if (!this.secondEnumerator)
+ return false;
+
+ if (this.firstEnumerator) {
+ var moreFirst = this.firstEnumerator.hasMoreElements();
+ if (moreFirst) {
+ this.currentItem = this.firstEnumerator.getNext();
+ this.handledProperties[this.currentItem.name] = true;
+ return true;
+ }
+ this.firstEnumerator = null;
+ }
+
+ var moreSecond = this.secondEnumerator.hasMoreElements();
+ if (moreSecond) {
+ while (this.currentItem.name in this.handledProperties &&
+ this.secondEnumerator.hasMoreElements())
+ do {
+ this.currentItem = this.secondEnumerator.getNext();
+ } while (this.currentItem.name in this.handledProperties &&
+ ((this.currentItem = null) == null) && // hack
+ this.secondEnumerator.hasMoreElements());
+
+ if (!this.currentItem)
+ return false;
+
+ return true;
+ }
+
+ this.secondEnumerator = null;
+
+ return false;
+ },
+
+ getNext: function() {
+ if (!currentItem)
+ throw Components.results.NS_ERROR_UNEXPECTED;
+
+ var rval = this.currentItem;
+ this.currentItem = null;
+ return rval;
+ }
+ };
+ } else {
+ return this.mProperties.enumerator;
+ }
+ },
getProperty: function (aName) {
try {
return this.mProperties.getProperty(aName);
} catch (e) {
+ try {
+ if (this.mIsProxy) {
+ return this.mParentItem.getProperty(aName);
+ }
+ } catch (e) {}
+
return null;
}
},
+ getUnproxiedProperty: function (aName) {
+ try {
+ return this.mProperties.getProperty(aName);
+ } catch (e) { }
+ return null;
+ },
+
+ hasProperty: function (aName) {
+ return (this.getProperty(aName) != null);
+ },
+
setProperty: function (aName, aValue) {
this.modify();
this.mProperties.setProperty(aName, aValue);
@@ -193,8 +345,7 @@ calItemBase.prototype = {
this.modify();
try {
this.mProperties.deleteProperty(aName);
- } catch (e) {
- }
+ } catch (e) { }
},
getAttendees: function (countObj) {
@@ -240,8 +391,7 @@ calItemBase.prototype = {
set calendar (v) {
if (this.mImmutable)
- // Components.results.NS_ERROR_CALENDAR_IMMUTABLE;
- throw Components.results.NS_ERROR_FAILURE;
+ throw Components.results.NS_ERROR_OBJECT_IS_IMMUTABLE;
this.mCalendar = v;
},
@@ -280,38 +430,37 @@ calItemBase.prototype = {
"RDATE": true,
"ATTENDEE": true,
"ORGANIZER": true,
+ "RECURRENCE-ID": true,
},
+ icsBasePropMap: [
+ { cal: "CREATED", ics: "createdTime" },
+ { cal: "LAST-MODIFIED", ics: "lastModified" },
+ { cal: "DTSTAMP", ics: "stampTime" },
+ { cal: "UID", ics: "uid" },
+ { cal: "SUMMARY", ics: "summary" },
+ { cal: "PRIORITY", ics: "priority" },
+ { cal: "STATUS", ics: "status" },
+ { cal: "CLASS", ics: "icalClass" } ],
+
mapPropsFromICS: function(icalcomp, propmap) {
for (var i = 0; i < propmap.length; i++) {
var prop = propmap[i];
var val = icalcomp[prop.ics];
if (val != null && val != ICAL.INVALID_VALUE)
- this[prop.cal] = val;
+ this.setProperty(prop.cal, val);
}
},
mapPropsToICS: function(icalcomp, propmap) {
for (var i = 0; i < propmap.length; i++) {
var prop = propmap[i];
- if (!(prop.cal in this))
- continue;
- var val = this[prop.cal];
+ var val = this.getProperty(prop.cal);
if (val != null && val != ICAL.INVALID_VALUE)
icalcomp[prop.ics] = val;
}
},
- icsBasePropMap: [
- { cal: "mCreationDate", ics: "createdTime" },
- { cal: "mLastModifiedTime", ics: "lastModified" },
- { cal: "mStampTime", ics: "stampTime" },
- { cal: "mId", ics: "uid" },
- { cal: "mTitle", ics: "summary" },
- { cal: "mPriority", ics: "priority" },
- { cal: "mStatus", ics: "status" },
- { cal: "mPrivacy", ics: "icalClass" }],
-
setItemBaseFromICS: function (icalcomp) {
this.modify();
@@ -360,7 +509,7 @@ calItemBase.prototype = {
if (!rec) {
rec = new CalRecurrenceInfo();
- rec.initialize(this);
+ rec.item = this;
}
rec.appendRecurrenceItem(ritem);
@@ -380,14 +529,19 @@ calItemBase.prototype = {
}
},
+ isPropertyPromoted: function (name) {
+ return (this.itemBasePromotedProps[name]);
+ },
+
get icalComponent() {
throw Components.results.NS_NOT_IMPLEMENTED;
},
fillIcalComponentFromBase: function (icalcomp) {
// Make sure that the LMT and ST are updated
- var suppressDCE = this.lastModifiedTime;
- suppressDCE = this.stampTime;
+ this.updateStampTime();
+ this.ensureNotDirty();
+
this.mapPropsToICS(icalcomp, this.icsBasePropMap);
if (this.mOrganizer)
@@ -415,107 +569,35 @@ calItemBase.prototype = {
}
};
-function calItemOccurrence () {
- this.wrappedJSObject = this;
+makeMemberAttr(calItemBase, "X-MOZILLA-GENERATION", 0, "generation", true);
+makeMemberAttr(calItemBase, "CREATED", null, "creationDate", true);
+makeMemberAttr(calItemBase, "UID", null, "id", true);
+makeMemberAttr(calItemBase, "SUMMARY", null, "title", true);
+makeMemberAttr(calItemBase, "PRIORITY", 0, "priority", true);
+makeMemberAttr(calItemBase, "CLASS", "PUBLIC", "privacy", true);
+makeMemberAttr(calItemBase, "STATUS", null, "status", true);
+makeMemberAttr(calItemBase, "ALARMTIME", null, "alarmTime", true);
+makeMemberAttr(calItemBase, "RECURRENCE-ID", null, "recurrenceId", true);
- this.occurrenceStartDate = new CalDateTime();
- this.occurrenceEndDate = new CalDateTime();
-}
-
-calItemOccurrenceClassInfo = {
- getInterfaces: function (count) {
- var ifaces = [
- Components.interfaces.nsISupports,
- Components.interfaces.calIItemOccurrence
- ];
- count.value = ifaces.length;
- return ifaces;
- },
-
- getHelperForLanguage: function (language) {
- return null;
- },
-
- contractID: "@mozilla.org/calendar/item-occurrence;1",
- classDescription: "Calendar Item Occurrence",
- classID: Components.ID("{bad672b3-30b8-4ecd-8075-7153313d1f2c}"),
- implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT,
- flags: 0
-};
-
-calItemOccurrence.prototype = {
- QueryInterface: function (aIID) {
- if (aIID.equals(Components.interfaces.nsIClassInfo))
- return calItemOccurrenceClassInfo;
-
- if (!aIID.equals(Components.interfaces.nsISupports) &&
- !aIID.equals(Components.interfaces.calIItemOccurrence))
- {
- throw Components.results.NS_ERROR_NO_INTERFACE;
- }
-
- return this;
- },
-
- initialize: function (aItem, aStartDate, aEndDate) {
- this.item = aItem;
- this.occurrenceStartDate = aStartDate.clone();
- this.occurrenceStartDate.makeImmutable();
- this.occurrenceEndDate = aEndDate.clone();
- this.occurrenceEndDate.makeImmutable();
- },
-
- item: null,
- occurrenceStartDate: null,
- occurrenceEndDate: null,
-
- get next() {
- if (this.item.recurrenceInfo)
- return this.item.recurrenceInfo.getNextOccurrence(this.item, aEndDate);
- return null;
- },
- get previous() {
- if (this.item.recurrenceInfo)
- return this.item.recurrenceInfo.getPreviousOccurrence(this.item, aStartDate);
- return null;
- },
-
- equals: function(aOther) {
- if (this.item.id != aOther.item.id)
- return false;
-
- if (this.occurrenceStartDate.compare(aOther.occurrenceStartDate) != 0)
- return false;
-
- if (this.occurrenceEndDate.compare(aOther.occurrenceEndDate) != 0)
- return false;
-
- return true;
- }
-};
-
-makeMemberAttr(calItemBase, "mGeneration", 0, "generation");
-makeMemberAttr(calItemBase, "mCreationDate", null, "creationDate");
-makeMemberAttr(calItemBase, "mId", null, "id");
-makeMemberAttr(calItemBase, "mTitle", null, "title");
-makeMemberAttr(calItemBase, "mPriority", 0, "priority");
-makeMemberAttr(calItemBase, "mPrivacy", "PUBLIC", "privacy");
-makeMemberAttr(calItemBase, "mStatus", null, "status");
-makeMemberAttr(calItemBase, "mHasAlarm", false, "hasAlarm");
-makeMemberAttr(calItemBase, "mAlarmTime", null, "alarmTime");
makeMemberAttr(calItemBase, "mRecurrenceInfo", null, "recurrenceInfo");
makeMemberAttr(calItemBase, "mAttachments", null, "attachments");
makeMemberAttr(calItemBase, "mProperties", null, "properties");
-function makeMemberAttr(ctor, varname, dflt, attr)
+function makeMemberAttr(ctor, varname, dflt, attr, asProperty)
{
- ctor.prototype[varname] = dflt;
+ // XXX handle defaults!
var getter = function () {
- return this[varname];
+ if (asProperty)
+ return this.getProperty(varname);
+ else
+ return this[varname];
};
var setter = function (v) {
this.modify();
- this[varname] = v;
+ if (asProperty)
+ this.setProperty(varname, v);
+ else
+ this[varname] = v;
};
ctor.prototype.__defineGetter__(attr, getter);
ctor.prototype.__defineSetter__(attr, setter);
diff --git a/calendar/base/src/calItemModule.js b/calendar/base/src/calItemModule.js
index a9def26e1e9..443ff72d327 100644
--- a/calendar/base/src/calItemModule.js
+++ b/calendar/base/src/calItemModule.js
@@ -105,11 +105,6 @@ const componentData =
script: "calTodo.js",
constructor: "calTodo"},
- {cid: Components.ID("{bad672b3-30b8-4ecd-8075-7153313d1f2c}"),
- contractid: "@mozilla.org/calendar/item-occurrence;1",
- script: null,
- constructor: "calItemOccurrence"},
-
{cid: Components.ID("{5c8dcaa3-170c-4a73-8142-d531156f664d}"),
contractid: "@mozilla.org/calendar/attendee;1",
script: "calAttendee.js",
@@ -118,7 +113,12 @@ const componentData =
{cid: Components.ID("{5f76b352-ab75-4c2b-82c9-9206dbbf8571}"),
contractid: "@mozilla.org/calendar/attachment;1",
script: "calAttachment.js",
- constructor: "calAttachment"}
+ constructor: "calAttachment"},
+
+ {cid: Components.ID("{04027036-5884-4a30-b4af-f2cad79f6edf}"),
+ contractid: "@mozilla.org/calendar/recurrence-info;1",
+ script: "calRecurrenceInfo.js",
+ constructor: "calRecurrenceInfo"}
];
var calItemModule = {
@@ -151,8 +151,13 @@ var calItemModule = {
var f = appdir.clone();
f.append(scriptName);
- var fileurl = iosvc.newFileURI(f);
- loader.loadSubScript(fileurl.spec, null);
+ try {
+ var fileurl = iosvc.newFileURI(f);
+ loader.loadSubScript(fileurl.spec, null);
+ } catch (e) {
+ dump("Error while loading " + fileurl.spec + "\n");
+ throw e;
+ }
}
this.mScriptsLoaded = true;
diff --git a/calendar/base/src/calRecurrenceDate.cpp b/calendar/base/src/calRecurrenceDate.cpp
index 0f020ed832a..bfd7b073abc 100644
--- a/calendar/base/src/calRecurrenceDate.cpp
+++ b/calendar/base/src/calRecurrenceDate.cpp
@@ -48,7 +48,7 @@ extern "C" {
#include "ical.h"
}
-NS_IMPL_ISUPPORTS2(calRecurrenceDate, calIRecurrenceItem, calIRecurrenceDate)
+NS_IMPL_ISUPPORTS2_CI(calRecurrenceDate, calIRecurrenceItem, calIRecurrenceDate)
calRecurrenceDate::calRecurrenceDate()
: mImmutable(PR_FALSE),
@@ -112,6 +112,15 @@ calRecurrenceDate::SetIsNegative(PRBool aIsNegative)
return NS_OK;
}
+/* readonly attribute boolean isFinite; */
+NS_IMETHODIMP
+calRecurrenceDate::GetIsFinite(PRBool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = PR_TRUE;
+ return NS_OK;
+}
+
NS_IMETHODIMP
calRecurrenceDate::GetDate(calIDateTime **aDate)
{
diff --git a/calendar/base/src/calRecurrenceDateSet.cpp b/calendar/base/src/calRecurrenceDateSet.cpp
index 8c75c8b1f8a..d2969b25979 100644
--- a/calendar/base/src/calRecurrenceDateSet.cpp
+++ b/calendar/base/src/calRecurrenceDateSet.cpp
@@ -48,7 +48,7 @@ extern "C" {
#include "ical.h"
}
-NS_IMPL_ISUPPORTS2(calRecurrenceDateSet, calIRecurrenceItem, calIRecurrenceDateSet)
+NS_IMPL_ISUPPORTS2_CI(calRecurrenceDateSet, calIRecurrenceItem, calIRecurrenceDateSet)
calRecurrenceDateSet::calRecurrenceDateSet()
: mImmutable(PR_FALSE),
@@ -121,6 +121,15 @@ calRecurrenceDateSet::SetIsNegative(PRBool aIsNegative)
return NS_OK;
}
+/* readonly attribute boolean isFinite; */
+NS_IMETHODIMP
+calRecurrenceDateSet::GetIsFinite(PRBool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = PR_TRUE;
+ return NS_OK;
+}
+
NS_IMETHODIMP
calRecurrenceDateSet::GetDates(PRUint32 *aCount, calIDateTime ***aDates)
{
diff --git a/calendar/base/src/calRecurrenceInfo.cpp b/calendar/base/src/calRecurrenceInfo.cpp
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/calendar/base/src/calRecurrenceInfo.h b/calendar/base/src/calRecurrenceInfo.h
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/calendar/base/src/calRecurrenceInfo.js b/calendar/base/src/calRecurrenceInfo.js
new file mode 100644
index 00000000000..324644fe5aa
--- /dev/null
+++ b/calendar/base/src/calRecurrenceInfo.js
@@ -0,0 +1,646 @@
+/* -*- 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 lightning code.
+ *
+ * The Initial Developer of the Original Code is
+ * Oracle Corporation
+ * Portions created by the Initial Developer are Copyright (C) 2005
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Vladimir Vukicevic
+ *
+ * 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 ***** */
+
+function calRecurrenceInfo() {
+ this.mRecurrenceItems = new Array();
+ this.mExceptions = new Array();
+}
+
+function calDebug() {
+ dump.apply(null, arguments);
+}
+
+var calRecurrenceInfoClassInfo = {
+ getInterfaces: function (count) {
+ var ifaces = [
+ Components.interfaces.nsISupports,
+ Components.interfaces.calIRecurrenceInfo,
+ Components.interfaces.nsIClassInfo
+ ];
+ count.value = ifaces.length;
+ return ifaces;
+ },
+
+ getHelperForLanguage: function (language) {
+ return null;
+ },
+
+ contractID: "@mozilla.org/calendar/recurrence-info;1",
+ classDescription: "Calendar Recurrence Info",
+ classID: Components.ID("{04027036-5884-4a30-b4af-f2cad79f6edf}"),
+ implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT,
+ flags: 0
+};
+
+calRecurrenceInfo.prototype = {
+ // QI with CI
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Components.interfaces.nsISupports) ||
+ aIID.equals(Components.interfaces.calIRecurrenceInfo))
+ return this;
+
+ if (aIID.equals(Components.interfaces.nsIClassInfo))
+ return calRecurrenceInfoClassInfo;
+
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ //
+ // Mutability bits
+ //
+ mImmutable: false,
+ get isMutable() { return !this.mImmutable; },
+ makeImmutable: function() {
+ if (this.mImmutable)
+ return;
+
+ for each (ritem in this.mRecurrenceItems) {
+ if (ritem.isMutable)
+ ritem.makeImmutable();
+ }
+
+ for each (ex in this.mExceptions) {
+ if (ex.item.isMutable)
+ ex.item.makeImmutable();
+ }
+
+ this.mImmutable = true;
+ },
+
+ clone: function() {
+ var cloned = new calRecurrenceInfo();
+ cloned.mBaseItem = this.mBaseItem;
+
+ var clonedItems = [];
+ for each (ritem in this.mRecurrenceItems)
+ clonedItems.push(ritem.clone());
+ cloned.mRecurrenceItems = clonedItems;
+
+ var clonedExceptions = [];
+ for each (exitem in this.mExceptions) {
+ var c = exitem.item.cloneShallow(this.mBaseItem);
+ clonedExceptions.push( { id: exitem.id, item: c } );
+ }
+ cloned.mExceptions = clonedExceptions;
+
+ return cloned;
+ },
+
+ //
+ // calIRecurrenceInfo impl
+ //
+ mBaseItem: null,
+
+ get item() {
+ return this.mBaseItem;
+ },
+
+ set item(value) {
+ if (this.mImmutable)
+ throw Components.results.NS_ERROR_OBJECT_IS_IMMUTABLE;
+
+ this.mBaseItem = value;
+ },
+
+ mRecurrenceItems: null,
+
+ get isFinite() {
+ if (!this.mBaseItem)
+ throw Components.results.NS_ERROR_NOT_INITIALIZED;
+
+ for each (ritem in this.mRecurrenceItems) {
+ if (!ritem.isFinite)
+ return false;
+ }
+
+ return true;
+ },
+
+ getRecurrenceItems: function(aCount) {
+ if (!this.mBaseItem)
+ throw Components.results.NS_ERROR_NOT_INITIALIZED;
+
+ aCount.value = this.mRecurrenceItems.length;
+ return this.mRecurrenceItems;
+ },
+
+ setRecurrenceItems: function(aCount, aItems) {
+ if (!this.mBaseItem)
+ throw Components.results.NS_ERROR_NOT_INITIALIZED;
+
+ if (this.mImmutable)
+ throw Components.results.NS_ERROR_OBJECT_IS_IMMUTABLE;
+
+ // should we clone these?
+ this.mRecurrenceItems = aItems;
+ },
+
+ countRecurrenceItems: function() {
+ if (!this.mBaseItem)
+ throw Components.results.NS_ERROR_NOT_INITIALIZED;
+
+ return this.mRecurrenceItems.length;
+ },
+
+ getRecurrenceItemAt: function(aIndex) {
+ if (!this.mBaseItem)
+ throw Components.results.NS_ERROR_NOT_INITIALIZED;
+
+ if (aIndex < 0 || aIndex >= mRecurrenceItems.length)
+ throw Components.results.NS_ERROR_INVALID_ARG;
+
+ return this.mRecurrenceItems[aIndex];
+ },
+
+ appendRecurrenceItem: function(aItem) {
+ if (!this.mBaseItem)
+ throw Components.results.NS_ERROR_NOT_INITIALIZED;
+
+ if (this.mImmutable)
+ throw Components.results.NS_ERROR_OBJECT_IS_IMMUTABLE;
+
+ this.mRecurrenceItems.push(aItem);
+ },
+
+ deleteRecurrenceItemAt: function(aIndex) {
+ if (!this.mBaseItem)
+ throw Components.results.NS_ERROR_NOT_INITIALIZED;
+
+ if (this.mImmutable)
+ throw Components.results.NS_ERROR_OBJECT_IS_IMMUTABLE;
+
+ if (aIndex < 0 || aIndex >= this.mRecurrenceItems.length)
+ throw Components.results.NS_ERROR_INVALID_ARG;
+
+ this.mRecurrenceItems.splice(aIndex, 1);
+ },
+
+ insertRecurrenceItemAt: function(aItem, aIndex) {
+ if (!this.mBaseItem)
+ throw Components.results.NS_ERROR_NOT_INITIALIZED;
+
+ if (this.mImmutable)
+ throw Components.results.NS_ERROR_OBJECT_IS_IMMUTABLE;
+
+ if (aIndex < 0 || aIndex >= this.mRecurrenceItems.length)
+ throw Components.results.NS_ERROR_INVALID_ARG;
+
+ this.mRecurrenceItems.splice(aIndex, 0, aItem);
+ },
+
+ clearRecurrenceItems: function() {
+ if (!this.mBaseItem)
+ throw Components.results.NS_ERROR_NOT_INITIALIZED;
+
+ if (this.mImmutable)
+ throw Components.results.NS_ERROR_OBJECT_IS_IMMUTABLE;
+
+ this.mRecurrenceItems = new Array();
+ },
+
+ //
+ // calculations
+ //
+
+ getNextOccurrenceDate: function (aTime) {
+ if (!this.mBaseItem)
+ throw Components.results.NS_ERROR_NOT_INITIALIZED;
+
+ var startDate = this.mBaseItem.getRecurrenceStartDate();
+ var dates = [];
+
+ for each (ritem in this.mRecurrenceItems) {
+ var date = ritem.getNextOccurrence(startDate, aTime);
+ if (!date)
+ continue;
+
+ if (ritem.isNegative)
+ dates = dates.filter(function (d) { return (d.compare(date) != 0); });
+ else
+ dates = dates.push(date);
+ }
+
+ // if no dates, there's no next
+ if (dates.length == 0)
+ return null;
+
+ // find the earliest date
+ var earliestDate = dates[0];
+ dates.forEach(function (d) { if (d.compare(earliestDate) < 0) earliestDate = d; });
+
+ return earliestDate;
+ },
+
+ getNextOccurrence: function (aTime) {
+ var earliestDate = this.getNextOccurrenceDate (aTime);
+ if (!earliestDate)
+ return null;
+
+ if (this.mExceptions) {
+ // scan exceptions for any dates earlier than
+ // earliestDate (but still after aTime)
+ this.mExceptions.forEach (function (ex) {
+ var dtstart = ex.item.getProperty("DTSTART");
+ if (aTime.compare(dtstart) <= 0 &&
+ earliestDate.compare(dtstart) > 0)
+ {
+ earliestDate = dtstart;
+ }
+ });
+ }
+
+ var startDate = earliestDate.clone();
+ var endDate = null;
+
+ if (this.mBaseItem.hasProperty("DTEND")) {
+ endDate = earliestDate.clone();
+ endDate.addDuration(this.mBaseItem.duration);
+ }
+
+ var proxy = this.mBaseItem.createProxy();
+ proxy.setRecurrenceId(earliestDate);
+
+ proxy.setProperty("DTSTART", startDate);
+ if (endDate)
+ proxy.setProperty("DTEND", endDate);
+
+ return proxy;
+ },
+
+ // internal helper function;
+ calculateDates: function (aRangeStart, aRangeEnd,
+ aMaxCount, aIncludeExceptions, aReturnRIDs)
+ {
+ if (!this.mBaseItem)
+ throw Components.results.NS_ERROR_NOT_INITIALIZED;
+
+ var startDate = this.mBaseItem.recurrenceStartDate;
+ var dates = [];
+
+ for each (ritem in this.mRecurrenceItems) {
+ var cur_dates;
+
+ // if both range start and end are specified, we ask for all of the occurrences,
+ // to make sure we catch all possible exceptions. If aRangeEnd isn't specified,
+ // then we have to ask for aMaxCount, and hope for the best.
+ if (aRangeStart && aRangeEnd)
+ cur_dates = ritem.getOccurrences(startDate, aRangeStart, aRangeEnd, 0, {});
+ else
+ cur_dates = ritem.getOccurrences(startDate, aRangeStart, aRangeEnd, aMaxCount, {});
+
+ if (cur_dates.length == 0)
+ continue;
+
+ if (ritem.isNegative) {
+ // if this is negative, we look for any of the given dates
+ // in the existing set, and remove them if they're
+ // present.
+
+ // XXX: i'm pretty sure negative dates can't really have exceptions
+ // (like, you can't make a date "real" by defining an RECURRENCE-ID which
+ // is an EXDATE, and then giving it a real DTSTART) -- so we don't
+ // check exceptions here
+ cur_dates.forEach (function (dateToRemove) {
+ dates = dates.filter(function (d) { return d.compare(dateToRemove) != 0; });
+ });
+ } else {
+ // if positive, we just add these date to the existing set,
+ // but only if they're not already there
+ var datesToAdd = [];
+ var rinfo = this;
+ cur_dates.forEach (function (dateToAdd) {
+ if (!dates.some(function (d) { return d.compare(dateToAdd) == 0; })) {
+ if (aIncludeExceptions && rinfo.mExceptions)
+ {
+ // only add if there's no exception for this;
+ // we'll test all exception dates later on
+ if (!rinfo.getExceptionFor(dateToAdd, false))
+ dates.push(dateToAdd);
+ } else {
+ dates.push(dateToAdd);
+ }
+ }
+ });
+ }
+ }
+
+ // Now toss in exceptions into this list; we just scan them all.
+ if (aIncludeExceptions && this.mExceptions) {
+ this.mExceptions.forEach(function(ex) {
+ var dtstart = ex.item.getProperty("DTSTART");
+ var dateToReturn;
+ if (aReturnRIDs)
+ dateToReturn = ex.id;
+ else
+ dateToReturn = dtstart;
+ // is our startdate within the range?
+ if ((!aRangeStart || aRangeStart.compare(dtstart) <= 0) &&
+ (!aRangeEnd || aRangeEnd.compare(dtstart) > 0))
+ {
+ dates.push(dateToReturn);
+ return;
+ }
+
+ // is our end date within the range?
+ var dtend = ex.item.getProperty("DTEND");
+ if ((!aRangeStart || aRangeStart.compare(dtend) <= 0) &&
+ (!aRangeEnd || aRangeEnd.compare(dtend) > 0))
+ {
+ dates.push(dateToReturn);
+ return;
+ }
+
+ // is the range in the middle of a long event?
+ if (aRangeStart && aRangeEnd &&
+ aRangeStart.compare(dtstart) >= 0 &&
+ aRangeEnd.compare(dtend) <= 0)
+ {
+ dates.push(dateToReturn);
+ return;
+ }
+ });
+ }
+
+ // now sort the list
+ dates.sort(function (a,b) { return a.compare(b); });
+
+ // chop anything over aMaxCount, if specified
+ if (aMaxCount && dates.length > aMaxCount)
+ dates = dates.splice(aMaxCount, dates.length - aMaxCount);
+
+ return dates;
+ },
+
+ getOccurrenceDates: function (aRangeStart, aRangeEnd,
+ aMaxCount, aCount)
+ {
+ var dates = this.calculateDates(aRangeStart, aRangeEnd, aMaxCount, true, false);
+ aCount.value = dates.length;
+ return dates;
+ },
+
+ getOccurrences: function (aRangeStart, aRangeEnd,
+ aMaxCount,
+ aCount)
+ {
+ var dates = this.calculateDates(aRangeStart, aRangeEnd, aMaxCount, true, true);
+ if (dates.length == 0) {
+ aCount.value = 0;
+ return [];
+ }
+
+ var count = aMaxCount;
+ if (!count)
+ count = dates.length;
+
+ var results = [];
+
+ for (var i = 0; i < count; i++) {
+ var proxy = this.getOccurrenceFor(dates[i]);
+ results.push(proxy);
+ }
+
+ aCount.value = results.length;
+ return results;
+ },
+
+ getOccurrenceFor: function (aRecurrenceId) {
+ var proxy = this.getExceptionFor(aRecurrenceId, false);
+ if (!proxy) {
+ var duration = null;
+ if (this.mBaseItem.hasProperty("DTEND"))
+ duration = this.mBaseItem.duration;
+
+ proxy = this.mBaseItem.createProxy();
+ proxy.recurrenceId = aRecurrenceId;
+ proxy.setProperty("DTSTART", aRecurrenceId.clone());
+ if (duration) {
+ var enddate = aRecurrenceId.clone();
+ enddate.addDuration(duration);
+ proxy.setProperty("DTEND", enddate);
+ }
+ if (!this.mBaseItem.isMutable)
+ proxy.makeImmutable();
+ }
+ return proxy;
+ },
+
+ removeOccurrenceAt: function (aRecurrenceId) {
+ if (!this.mBaseItem)
+ throw Components.results.NS_ERROR_NOT_INITIALIZED;
+
+ if (this.mImmutable)
+ throw Components.results.NS_ERROR_OBJECT_IS_IMMUTABLE;
+
+ var d = Components.classes["@mozilla.org/calendar/recurrence-date;1"].createInstance(Components.interfaces.calIRecurrenceDate);
+ d.isNegative = true;
+ d.date = aRecurrenceId.clone();
+
+ return this.appendRecurrenceItem(d);
+ },
+
+ restoreOccurrenceAt: function (aRecurrenceId) {
+ if (!this.mBaseItem)
+ throw Components.results.NS_ERROR_NOT_INITIALIZED;
+
+ if (this.mImmutable)
+ throw Components.results.NS_ERROR_OBJECT_IS_IMMUTABLE;
+
+ for (var i = 0; i < this.mRecurrenceItems.length; i++) {
+ if (this.mRecurrenceItems[i] instanceof Components.interfaces.calIRecurrenceDate) {
+ var rd = this.mRecurrenceItems[i].QueryInterface(Components.interfaces.calIRecurrenceDate);
+ if (rd.isNegative && rd.date.compare(aRecurrenceId) == 0) {
+ return this.deleteRecurrenceItemAt(i);
+ }
+ }
+ }
+
+ throw Components.results.NS_ERROR_INVALID_ARG;
+ },
+
+ //
+ // exceptions
+ //
+
+ //
+ // Some notes:
+ //
+ // The way I read ICAL, RECURRENCE-ID is used to specify a
+ // particular instance of a recurring event, according to the
+ // RRULEs/RDATEs/etc. specified in the base event. If one of
+ // these is to be changed ("an exception"), then it can be
+ // referenced via the UID of the original event, and a
+ // RECURRENCE-ID of the start time of the instance to change.
+ // This, to me, means that an event where one of the instances has
+ // changed to a different time has a RECURRENCE-ID of the original
+ // start time, and a DTSTART/DTEND representing the new time.
+ //
+ // ITIP, however, seems to want something different -- you're
+ // supposed to use UID/RECURRENCE-ID to select from the current
+ // set of occurrences of an event. If you change the DTSTART for
+ // an instance, you're supposed to use the old (original) DTSTART
+ // as the RECURRENCE-ID, and put the new time as the DTSTART.
+ // However, after that change, to refer to that instance in the
+ // future, you have to use the modified DTSTART as the
+ // RECURRENCE-ID. This madness is described in ITIP end of
+ // section 3.7.1.
+ //
+ // This implementation does the first approach (RECURRENCE-ID will
+ // never change even if DTSTART for that instance changes), which
+ // I think is the right thing to do for CalDAV; I don't know what
+ // we'll do for incoming ITIP events though.
+ //
+
+ mExceptions: null,
+
+ modifyException: function (anItem) {
+ if (!this.mBaseItem)
+ throw Components.results.NS_ERROR_NOT_INITIALIZED;
+
+ // the item must be an occurrence
+ if (anItem.parentItem == anItem)
+ throw Components.results.NS_ERROR_UNEXPECTED;
+
+ if (anItem.parentItem.calendar != this.mBaseItem.calendar &&
+ anItem.parentItem.id != this.mBaseItem.id)
+ {
+ calDebug ("recurrenceInfo::addException: item parentItem != this.mBaseItem (calendar/id)!\n");
+ throw Components.results.NS_ERROR_INVALID_ARG;
+ }
+
+ if (anItem.recurrenceId == null) {
+ calDebug ("recurrenceInfo::addException: item with null recurrenceId!\n");
+ throw Components.results.NS_ERROR_INVALID_ARG;
+ }
+
+ var itemtoadd;
+ if (anItem.isMutable) {
+ itemtoadd = anItem.cloneShallow(this.mBaseItem);
+ itemtoadd.makeImmutable();
+ } else {
+ itemtoadd = anItem;
+ }
+
+ // we're going to assume that the recurrenceId is valid here,
+ // because presumably the item came from one of our functions
+
+ // remove any old one, if present
+ this.removeExceptionFor(anItem.recurrenceId);
+
+ this.mExceptions.push( { id: itemtoadd.recurrenceId, item: itemtoadd } );
+ },
+
+ createExceptionFor: function (aRecurrenceId) {
+ if (!this.mBaseItem)
+ throw Components.results.NS_ERROR_NOT_INITIALIZED;
+
+ // XX should it be an error to createExceptionFor
+ // an already-existing recurrenceId?
+ var existing = this.getExceptionFor(aRecurrenceId, false);
+ if (existing)
+ return existing;
+
+ // check if aRecurrenceId is valid.
+
+ // this is a bit of a hack; we know that ranges are defined as [start, end),
+ // so we do a search on aRecurrenceId and aRecurrenceId.seconds + 1.
+ var rangeStart = aRecurrenceId;
+ var rangeEnd = aRecurrenceId.clone();
+ rangeEnd.second += 1;
+ rangeEnd.normalize();
+
+ var dates = this.getOccurrenceDates (rangeStart, rangeEnd, 1, {});
+ var found = false;
+ for each (d in dates) {
+ if (d.compare(aRecurrenceId) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ // not found; the recurrence id is invalid
+ if (!found)
+ throw Components.results.NS_ERROR_INVALID_ARG;
+
+ var rid = aRecurrenceId.clone();
+ rid.makeImmutable();
+
+ var newex = this.mBaseItem.createProxy();
+ newex.recurrenceId = rid;
+
+ this.mExceptions.push({id: rid, item: newex});
+
+ return newex;
+ },
+
+ getExceptionFor: function (aRecurrenceId, aCreate) {
+ if (!this.mBaseItem)
+ throw Components.results.NS_ERROR_NOT_INITIALIZED;
+
+ for each (ex in this.mExceptions) {
+ if (ex.id.compare(aRecurrenceId) == 0)
+ return ex.item;
+ }
+
+ if (aCreate) {
+ return this.createExceptionFor(aRecurrenceId);
+ }
+ return null;
+ },
+
+ removeExceptionFor: function (aRecurrenceId) {
+ if (!this.mBaseItem)
+ throw Components.results.NS_ERROR_NOT_INITIALIZED;
+
+ this.mExceptions = this.mExceptions.filter (function(ex) {
+ return (ex.id.compare(aRecurrenceId) != 0);
+ });
+ },
+
+ getExceptionIds: function (aCount) {
+ if (!this.mBaseItem)
+ throw Components.results.NS_ERROR_NOT_INITIALIZED;
+
+ var ids = this.mExceptions.map (function(ex) {
+ return ex.id;
+ });
+
+ aCount.value = ids.length;
+ return ids;
+ },
+};
diff --git a/calendar/base/src/calRecurrenceRule.cpp b/calendar/base/src/calRecurrenceRule.cpp
index 8a6f24cac21..d86ef77ece3 100644
--- a/calendar/base/src/calRecurrenceRule.cpp
+++ b/calendar/base/src/calRecurrenceRule.cpp
@@ -50,7 +50,7 @@ extern "C" {
#include "ical.h"
}
-NS_IMPL_ISUPPORTS2(calRecurrenceRule, calIRecurrenceItem, calIRecurrenceRule)
+NS_IMPL_ISUPPORTS2_CI(calRecurrenceRule, calIRecurrenceItem, calIRecurrenceRule)
calRecurrenceRule::calRecurrenceRule()
: mImmutable(PR_FALSE),
@@ -85,6 +85,21 @@ calRecurrenceRule::MakeImmutable()
return NS_OK;
}
+NS_IMETHODIMP
+calRecurrenceRule::Clone(calIRecurrenceItem **aResult)
+{
+ calRecurrenceRule *crc = new calRecurrenceRule;
+ if (!crc)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ crc->mIsNegative = mIsNegative;
+ crc->mIsByCount = mIsByCount;
+ *(crc->mIcalRecur) = *mIcalRecur;
+
+ NS_ADDREF(*aResult = crc);
+ return NS_OK;
+}
+
/* attribute boolean isNegative; */
NS_IMETHODIMP
calRecurrenceRule::GetIsNegative(PRBool *_retval)
@@ -105,18 +120,19 @@ calRecurrenceRule::SetIsNegative(PRBool aIsNegative)
return NS_OK;
}
+/* readonly attribute boolean isFinite; */
NS_IMETHODIMP
-calRecurrenceRule::Clone(calIRecurrenceItem **aResult)
+calRecurrenceRule::GetIsFinite(PRBool *_retval)
{
- calRecurrenceRule *crc = new calRecurrenceRule;
- if (!crc)
- return NS_ERROR_OUT_OF_MEMORY;
+ NS_ENSURE_ARG_POINTER(_retval);
- crc->mIsNegative = mIsNegative;
- crc->mIsByCount = mIsByCount;
- *(crc->mIcalRecur) = *mIcalRecur;
-
- NS_ADDREF(*aResult = crc);
+ if ((mIsByCount && mIcalRecur->count == 0) ||
+ (!mIsByCount && icaltime_is_null_time(mIcalRecur->until)))
+ {
+ *_retval = PR_FALSE;
+ } else {
+ *_retval = PR_TRUE;
+ }
return NS_OK;
}
@@ -169,6 +185,9 @@ calRecurrenceRule::GetCount(PRInt32 *aRecurCount)
{
NS_ENSURE_ARG_POINTER(aRecurCount);
+ if (!mIsByCount)
+ return NS_ERROR_FAILURE;
+
if (mIcalRecur->count == 0 && icaltime_is_null_time(mIcalRecur->until)) {
*aRecurCount = -1;
} else if (mIcalRecur->count) {
@@ -203,6 +222,9 @@ calRecurrenceRule::GetEndDate(calIDateTime * *aRecurEnd)
{
NS_ENSURE_ARG_POINTER(aRecurEnd);
+ if (mIsByCount)
+ return NS_ERROR_FAILURE;
+
if (!icaltime_is_null_time(mIcalRecur->until)) {
calDateTime *cdt = new calDateTime(&mIcalRecur->until);
if (!cdt)
@@ -389,7 +411,6 @@ calDateTimeComparator (calIDateTime *aElement1,
return result;
}
-/* void getOccurrences(in calIDateTime aStartTime, in calIDateTime aEndTime, out unsigned long aCount, [array, size_is (aCount), retval] out calIItemOccurrence aItems); */
NS_IMETHODIMP
calRecurrenceRule::GetOccurrences(calIDateTime *aStartTime,
calIDateTime *aRangeStart,
diff --git a/calendar/base/src/calTodo.js b/calendar/base/src/calTodo.js
index eed9b86c686..ad95a3afd02 100644
--- a/calendar/base/src/calTodo.js
+++ b/calendar/base/src/calTodo.js
@@ -47,17 +47,29 @@ function calTodo() {
this.wrappedJSObject = this;
this.initItemBase();
this.initTodo();
+
+ this.todoPromotedProps = {
+ "DTSTART": true,
+ "DTEND": true,
+ "DTSTAMP": true,
+ "DUE": true,
+ "COMPLETED": true,
+ "PERCENT-COMPLETE": true,
+ __proto__: this.itemBasePromotedProps
+ };
}
// var trickery to suppress lib-as-component errors from loader
var calItemBase;
-calTodoClassInfo = {
+var calTodoClassInfo = {
getInterfaces: function (count) {
var ifaces = [
Components.interfaces.nsISupports,
Components.interfaces.calIItemBase,
- Components.interfaces.calITodo
+ Components.interfaces.calITodo,
+ Components.interfaces.calIInternalShallowCopy,
+ Components.interfaces.nsIClassInfo
];
count.value = ifaces.length;
return ifaces;
@@ -78,7 +90,8 @@ calTodo.prototype = {
__proto__: calItemBase ? (new calItemBase()) : {},
QueryInterface: function (aIID) {
- if (aIID.equals(Components.interfaces.calITodo))
+ if (aIID.equals(Components.interfaces.calITodo) ||
+ aIID.equals(Components.interfaces.calIInternalShallowCopy))
return this;
if (aIID.equals(Components.interfaces.nsIClassInfo))
@@ -94,13 +107,32 @@ calTodo.prototype = {
this.mPercentComplete = 0;
},
- clone: function () {
+ cloneShallow: function (aNewParent) {
var m = new calTodo();
- this.cloneItemBaseInto(m);
+ this.cloneItemBaseInto(m, aNewParent);
m.mEntryDate = this.mEntryDate.clone();
m.mDueDate = this.mDueDate.clone();
m.mCompletedDate = this.mCompletedDate.clone();
m.mPercentComplete = this.mPercentComplete;
+ return m;
+ },
+
+ clone: function () {
+ var m;
+
+ if (this.parentItem != this) {
+ var clonedParent = this.mParentItem.clone();
+ m = clonedParent.recurrenceInfo.getExceptionFor (this.recurrenceId, true);
+ } else {
+ m = this.cloneShallow(null);
+ }
+
+ return m;
+ },
+
+ createProxy: function () {
+ var m = new calTodo();
+ m.initializeProxy(this);
return m;
},
@@ -165,6 +197,8 @@ calTodo.prototype = {
return icalcomp;
},
+ todoPromotedProps: null,
+
set icalComponent(todo) {
this.modify();
if (todo.componentType != "VTODO") {
@@ -177,19 +211,14 @@ calTodo.prototype = {
this.mapPropsFromICS(todo, this.icsEventPropMap);
this.mIsAllDay = this.mStartDate && this.mStartDate.isDate;
- var promotedProps = {
- "DTSTART": true,
- "DTEND": true,
- "DTSTAMP": true,
- "DUE": true,
- "COMPLETED": true,
- "PERCENT-COMPLETE": true,
- __proto__: this.itemBasePromotedProps
- };
- this.importUnpromotedProperties(todo, promotedProps);
+ this.importUnpromotedProperties(todo, todoPromotedProps);
// Importing didn't really change anything
this.mDirty = false;
},
+
+ isPropertyPromoted: function (name) {
+ return (this.todoPromotedProps[name]);
+ },
};
// var decl to prevent spurious error messages when loaded as component
diff --git a/calendar/lightning/content/agenda-tree.js b/calendar/lightning/content/agenda-tree.js
index 8ea3f1a796d..50aa44c0149 100644
--- a/calendar/lightning/content/agenda-tree.js
+++ b/calendar/lightning/content/agenda-tree.js
@@ -94,7 +94,7 @@ function rebuildAgendaView(invalidate)
if (e instanceof Synthetic)
dump(" " + e.title + "\n");
else
- dump(" " + e.item.title + " @ " + e.occurrenceStartDate + "\n");
+ dump(" " + e.title + " @ " + e.occurrenceStartDate + "\n");
});
*/
this.forceTreeRebuild();
@@ -143,12 +143,13 @@ function getCellText(row, column)
if (column.id == "col-agenda-item") {
if (event instanceof Synthetic)
return event.title;
- return event.item.title;
+ return event.title;
}
if (event instanceof Synthetic)
return "";
- return event.occurrenceStartDate.toString();
+ var start = event.startDate || event.entryDate;
+ return start.toString();
};
agendaTreeView.getLevel =
@@ -223,10 +224,7 @@ function hasNextSibling(row, afterIndex)
agendaTreeView.findPeriodForItem =
function findPeriodForItem(item)
{
- var start = item.occurrenceStartDate;
- if (start.compare(this.today.start) < 0 || start.compare(this.soon.end) > 0)
- return null;
-
+ var start = item.startDate || item.entryDate;
if (start.compare(this.today.end) <= 0)
return this.today;
@@ -236,7 +234,7 @@ function findPeriodForItem(item)
if (start.compare(this.soon.end) <= 0)
return this.soon;
- void(item.item.title + " @ " + start + " not in range " +
+ void(item.title + " @ " + start + " not in range " +
"(" + this.today.start + " - " + this.soon.end + ")\n");
return null;
@@ -248,7 +246,7 @@ function addItem(item)
var when = this.findPeriodForItem(item);
if (!when)
return;
- void(item.item.title + " @ " + item.occurrenceStartDate + " -> " + when.title + "\n");
+ void(item.title + " @ " + item.occurrenceStartDate + " -> " + when.title + "\n");
when.events.push(item);
this.calendarUpdateComplete();
};
@@ -263,11 +261,16 @@ function deleteItem(item)
}
void("deleting item " + item + " from " + when.title + "\n");
- void("before: " + when.events.map(function (e) { return e.item.title; }).join(" ") + "\n");
+ void("before: " + when.events.map(function (e) { return e.title; }).join(" ") + "\n");
when.events = when.events.filter(function (e) {
- return !e.equals(item);
- });
- void("after: " + when.events.map(function (e) { return e.item.title; }).join(" ") + "\n");
+ if (e.id != item.id)
+ return true;
+ if (e.recurrenceId && item.recurrenceId &&
+ e.recurrenceId.compare(item.recurrenceId) != 0)
+ return true;
+ return false;
+ });
+ void("after: " + when.events.map(function (e) { return e.title; }).join(" ") + "\n");
this.rebuildAgendaView(true);
};
@@ -276,7 +279,9 @@ function calendarUpdateComplete()
{
[this.today, this.tomorrow, this.soon].forEach(function(when) {
function compare(a, b) {
- return a.occurrenceStartDate.compare(b.occurrenceStartDate);
+ var ad = a.startDate || a.entryDate;
+ var bd = b.startDate || b.entryDate;
+ return ad.compare(bd);
}
when.events.sort(compare);
});
diff --git a/calendar/lightning/content/calendar-management.js b/calendar/lightning/content/calendar-management.js
index 7387b7b21ec..85428078e98 100644
--- a/calendar/lightning/content/calendar-management.js
+++ b/calendar/lightning/content/calendar-management.js
@@ -113,29 +113,28 @@ var ltnCalendarViewController = {
},
modifyOccurrence: function (aOccurrence, aNewStartTime, aNewEndTime) {
- if (aOccurrence.recurrenceInfo) {
- dump ("*** Don't know what to do in modifyOccurrence for a recurring event!\n");
- return;
- }
-
+ // if we can modify this thing directly (e.g. just the time changed),
+ // then do so; otherwise pop up the dialog
if (aNewStartTime && aNewEndTime && !aNewStartTime.isDate && !aNewEndTime.isDate) {
- var newEvent = aOccurrence.item.clone();
- newEvent.startDate = aNewStartTime;
- newEvent.endDate = aNewEndTime;
- newEvent.calendar.modifyItem(newEvent, aOccurrence.item, null);
+ var instance = aOccurrence.clone();
+
+ instance.startDate = aNewStartTime;
+ instance.endDate = aNewEndTime;
+
+ instance.calendar.modifyItem(instance, aOccurrence, null);
} else {
- modifyEventWithDialog(aOccurrence.item);
+ modifyEventWithDialog(aOccurrence);
}
},
deleteOccurrence: function (aOccurrence) {
- dump ("+++ deleteOccurrence\n");
- if (aOccurrence.recurrenceInfo) {
- dump ("*** Don't know what do in deleteOccurrence for a recurring event!\n");
- return;
+ if (aOccurrence.parentItem) {
+ var event = aOccurrence.parentItem.clone();
+ event.recurrenceInfo.removeOccurrenceAt(aOccurrence.recurrenceId);
+ event.calendar.modifyItem(event, aOccurrence, null);
+ } else {
+ aOccurrence.calendar.deleteItem(aOccurrence, null);
}
-
- aOccurrence.item.calendar.deleteItem(aOccurrence.item, null);
}
};
diff --git a/calendar/lightning/content/lightning-utils.js b/calendar/lightning/content/lightning-utils.js
index e9a2fa4df1d..c3c221e5059 100644
--- a/calendar/lightning/content/lightning-utils.js
+++ b/calendar/lightning/content/lightning-utils.js
@@ -2,13 +2,33 @@
function ltnCreateInstance(cid, iface) {
if (!iface)
iface = "nsISupports";
- return Components.classes[cid].createInstance(Components.interfaces[iface]);
+ try {
+ return Components.classes[cid].createInstance(Components.interfaces[iface]);
+ } catch(e) {
+ dump("#### ltnCreateInstance failed for: " + cid + "\n");
+ var frame = Components.stack;
+ for (var i = 0; frame && (i < 4); i++) {
+ dump(i + ": " + frame + "\n");
+ frame = frame.caller;
+ }
+ throw e;
+ }
}
function ltnGetService(cid, iface) {
if (!iface)
iface = "nsISupports";
- return Components.classes[cid].getService(Components.interfaces[iface]);
+ try {
+ return Components.classes[cid].getService(Components.interfaces[iface]);
+ } catch(e) {
+ dump("#### ltnGetService failed for: " + cid + "\n");
+ var frame = Components.stack;
+ for (var i = 0; frame && (i < 4); i++) {
+ dump(i + ": " + frame + "\n");
+ frame = frame.caller;
+ }
+ throw e;
+ }
}
var atomSvc;
diff --git a/calendar/lightning/content/messenger-overlay-sidebar.js b/calendar/lightning/content/messenger-overlay-sidebar.js
index c9b379b1f9b..7e545f307f7 100644
--- a/calendar/lightning/content/messenger-overlay-sidebar.js
+++ b/calendar/lightning/content/messenger-overlay-sidebar.js
@@ -86,7 +86,6 @@ function showCalendar(jumpToToday)
var d = Components.classes['@mozilla.org/calendar/datetime;1'].createInstance(Components.interfaces.calIDateTime);
d.jsDate = new Date();
d = d.getInTimezone(calendarDefaultTimezone());
-
view.showDate(d);
}
diff --git a/calendar/lightning/content/messenger-overlay-sidebar.xul b/calendar/lightning/content/messenger-overlay-sidebar.xul
index a26a779d018..3110d510e75 100644
--- a/calendar/lightning/content/messenger-overlay-sidebar.xul
+++ b/calendar/lightning/content/messenger-overlay-sidebar.xul
@@ -31,8 +31,8 @@
-
-
+
+
diff --git a/calendar/providers/memory/calMemoryCalendar.js b/calendar/providers/memory/calMemoryCalendar.js
index da43d80b3c9..73bf821e230 100644
--- a/calendar/providers/memory/calMemoryCalendar.js
+++ b/calendar/providers/memory/calMemoryCalendar.js
@@ -61,12 +61,10 @@ function calMemoryCalendar() {
function makeOccurrence(item, start, end)
{
- var occ = Components.classes["@mozilla.org/calendar/item-occurrence;1"].
- createInstance(Components.interfaces.calIItemOccurrence);
- // XXX poor form
- occ.wrappedJSObject.item = item;
- occ.wrappedJSObject.occurrenceStartDate = start;
- occ.wrappedJSObject.occurrenceEndDate = end;
+ var occ = item.createProxy();
+ occ.recurrenceId = start;
+ occ.startDate = start;
+ occ.endDate = end;
return occ;
}
@@ -122,21 +120,15 @@ calMemoryCalendar.prototype = {
// void addObserver( in calIObserver observer );
addObserver: function (aObserver, aItemFilter) {
- for each (obs in this.mObservers) {
- if (obs == aObserver)
- return;
- }
+ if (this.mObservers.any(function(o) { return (o == aObserver); }))
+ return;
this.mObservers.push(aObserver);
},
// void removeObserver( in calIObserver observer );
removeObserver: function (aObserver) {
- var newObservers = Array();
- for each (obs in this.mObservers) {
- if (obs != aObserver)
- newObservers.push(obs);
- }
+ var newObservers = this.mObservers.filter(function(o) { return (o != aObserver); });
this.mObservers = newObservers;
},
@@ -243,7 +235,7 @@ calMemoryCalendar.prototype = {
modifiedItem.id,
modifiedItem);
// notify observers
- this.observeModifyItem(aOldItem, modifiedItem);
+ this.observeModifyItem(modifiedItem, aOldItem);
},
// void deleteItem( in calIItemBase aItem, in calIOperationListener aListener );
@@ -376,7 +368,7 @@ calMemoryCalendar.prototype = {
// figure out the return interface type
var typeIID = null;
if (itemReturnOccurrences) {
- typeIID = calIItemOccurrence;
+ typeIID = calIItemBase;
} else {
if (wantEvents && wantTodos) {
typeIID = calIItemBase;
@@ -425,9 +417,7 @@ calMemoryCalendar.prototype = {
{
// there might be some recurrences here that we need to handle
var recs = item.recurrenceInfo.getOccurrences (aRangeStart, aRangeEnd, 0, {});
- for (var i = 0; i < recs.length; i++) {
- itemsFound.push(recs[i]);
- }
+ itemsFound = concat(itemsFound, recs);
} else if (itemEndTime >= startTime) {
// no occurrences
if (itemReturnOccurrences)
diff --git a/calendar/providers/storage/calStorageCalendar.js b/calendar/providers/storage/calStorageCalendar.js
index 2528eae9bd7..9cb2b98e3f4 100644
--- a/calendar/providers/storage/calStorageCalendar.js
+++ b/calendar/providers/storage/calStorageCalendar.js
@@ -64,10 +64,6 @@ const kCalAttendeeContractID = "@mozilla.org/calendar/attendee;1";
const kCalIAttendee = Components.interfaces.calIAttendee;
var CalAttendee;
-const kCalItemOccurrenceContractID = "@mozilla.org/calendar/item-occurrence;1";
-const kCalIItemOccurrence = Components.interfaces.calIItemOccurrence;
-var CalItemOccurrence;
-
const kCalRecurrenceInfoContractID = "@mozilla.org/calendar/recurrence-info;1";
const kCalIRecurrenceInfo = Components.interfaces.calIRecurrenceInfo;
var CalRecurrenceInfo;
@@ -101,13 +97,13 @@ const CAL_ITEM_FLAG_HAS_ATTENDEES = 2;
const CAL_ITEM_FLAG_HAS_PROPERTIES = 4;
const CAL_ITEM_FLAG_EVENT_ALLDAY = 8;
const CAL_ITEM_FLAG_HAS_RECURRENCE = 16;
+const CAL_ITEM_FLAG_HAS_EXCEPTIONS = 32;
function initCalStorageCalendarComponent() {
CalEvent = new Components.Constructor(kCalEventContractID, kCalIEvent);
CalTodo = new Components.Constructor(kCalTodoContractID, kCalITodo);
CalDateTime = new Components.Constructor(kCalDateTimeContractID, kCalIDateTime);
CalAttendee = new Components.Constructor(kCalAttendeeContractID, kCalIAttendee);
- CalItemOccurrence = new Components.Constructor(kCalItemOccurrenceContractID, kCalIItemOccurrence);
CalRecurrenceInfo = new Components.Constructor(kCalRecurrenceInfoContractID, kCalIRecurrenceInfo);
CalRecurrenceRule = new Components.Constructor(kCalRecurrenceRuleContractID, kCalIRecurrenceRule);
CalRecurrenceDateSet = new Components.Constructor(kCalRecurrenceDateSetContractID, kCalIRecurrenceDateSet);
@@ -134,15 +130,25 @@ function createStatement (dbconn, sql) {
}
function textToDate(d) {
- var dval = parseInt(d.substr(2));
+ var dval;
+ var tz = "UTC";
+
+ if (d[0] == 'Z') {
+ var strs = d.substr(2).split(":");
+ dval = parseInt(strs[0]);
+ tz = strs[1].replace(/%:/g, ":").replace(/%%/g, "%");
+ } else {
+ dval = parseInt(d.substr(2));
+ }
+
var date;
- if (d[0] == 'U') {
- // isutc
- date = newDateTime(dval, "UTC");
+ if (d[0] == 'U' || d[0] == 'Z') {
+ date = newDateTime(dval, tz);
} else if (d[0] == 'L') {
// is local time
date = newDateTime(dval, "floating");
}
+
if (d[1] == 'D')
date.isDate = true;
return date;
@@ -150,8 +156,14 @@ function textToDate(d) {
function dateToText(d) {
var datestr;
+ var tz = null;
if (d.timezone != "floating") {
- datestr = "U";
+ if (d.timezone == "UTC") {
+ datestr = "U";
+ } else {
+ datestr = "Z";
+ tz = d.timezone;
+ }
} else {
datestr = "L";
}
@@ -163,6 +175,13 @@ function dateToText(d) {
}
datestr += d.nativeTime;
+
+ if (tz) {
+ // replace '%' with '%%', then replace ':' with '%:'
+ tz = tz.replace(/%/g, "%%");
+ tz = tz.replace(/:/g, "%:");
+ datestr += ":" + tz;
+ }
return datestr;
}
@@ -182,16 +201,6 @@ function newDateTime(aNativeTime, aTimezone) {
return t;
}
-function makeOccurrence(item, start, end)
-{
- var occ = CalItemOccurrence();
- // XXX poor form
- occ.wrappedJSObject.item = item;
- occ.wrappedJSObject.occurrenceStartDate = start;
- occ.wrappedJSObject.occurrenceEndDate = end;
- return occ;
-}
-
//
// calStorageCalendar
//
@@ -318,6 +327,14 @@ calStorageCalendar.prototype = {
// void addItem( in calIItemBase aItem, in calIOperationListener aListener );
addItem: function (aItem, aListener) {
+ // Ensure that we're looking at the base item
+ // if we were given an occurrence. Later we can
+ // optimize this.
+ if (aItem.parentItem != aItem) {
+ aItem.parentItem.recurrenceInfo.modifyException(aItem);
+ }
+ aItem = aItem.parentItem;
+
if (aItem.id == null) {
// is this an error? Or should we generate an IID?
aItem.id = "uuid:" + (new Date()).getTime();
@@ -354,20 +371,34 @@ calStorageCalendar.prototype = {
},
// void modifyItem( in calIItemBase aOldItem, in calIItemBase aNewItem, in calIOperationListener aListener );
- modifyItem: function (aOldItem, aNewItem, aListener) {
- if (aNewItem.id == null) {
- // this is definitely an error
+ modifyItem: function (aNewItem, aOldItem, aListener) {
+ function reportError(errId, errStr) {
if (aListener)
aListener.onOperationComplete (this,
- Components.results.NS_ERROR_FAILURE,
+ errId ? errId : Components.results.NS_ERROR_FAILURE,
aListener.MODIFY,
aNewItem.id,
- "ID for modifyItem item is null");
+ errStr);
+ }
+
+ if (aNewItem.id == null) {
+ // this is definitely an error
+ reportError (null, "ID for modifyItem item is null");
return;
}
- // make sure we've got an old item
- if (!aOldItem || !this.getItemById(aOldItem.id)) {
+ // Ensure that we're looking at the base item
+ // if we were given an occurrence. Later we can
+ // optimize this.
+ if (aNewItem.parentItem != aNewItem) {
+ aNewItem.parentItem.recurrenceInfo.modifyException(aNewItem);
+ }
+
+ aNewItem = aNewItem.parentItem;
+
+ // get the old item
+ var olditem = this.getItemById(aOldItem.id);
+ if (!olditem) {
// no old item found? should be using addItem, then.
if (aListener)
aListener.onOperationComplete (this,
@@ -407,6 +438,11 @@ calStorageCalendar.prototype = {
// void deleteItem( in string id, in calIOperationListener aListener );
deleteItem: function (aItem, aListener) {
+ if (aItem.parentItem != aItem) {
+ aItem.parentItem.recurrenceInfo.removeExceptionFor(aItem.recurrenceId);
+ return;
+ }
+
if (aItem.id == null) {
if (aListener)
aListener.onOperationComplete (this,
@@ -541,10 +577,8 @@ calStorageCalendar.prototype = {
// helper function to handle converting a row to an item,
// expanding occurrences, and queue the items for the listener
- function handleResultItems(theRow, theConversionFunction, theIID) {
- var flags = {};
- var item = theConversionFunction.call(self, theRow, flags);
- self.getAdditionalDataForItem(item, flags.value);
+ function handleResultItem(item, flags, theIID) {
+ self.getAdditionalDataForItem(item, flags);
item.makeImmutable();
var expandedItems;
@@ -552,13 +586,10 @@ calStorageCalendar.prototype = {
if (asOccurrences && item.recurrenceInfo) {
expandedItems = item.recurrenceInfo.getOccurrences (aRangeStart, aRangeEnd, 0, {});
} else {
- if (asOccurrences)
- expandedItems = [ makeOccurrence(item, item.startDate, item.endDate) ];
- else
- expandedItems = [ item ];
+ expandedItems = [ item ];
}
- queueItems (expandedItems, asOccurrences ? kCalIItemOccurrence : theIID);
+ queueItems (expandedItems, theIID);
return expandedItems.length;
}
@@ -583,7 +614,6 @@ calStorageCalendar.prototype = {
return false;
}
- //
// First fetch all the events
if (wantEvents) {
// this will contain a lookup table of item ids that we've already dealt with,
@@ -591,6 +621,8 @@ calStorageCalendar.prototype = {
var handledRecurringEvents = { };
var sp; // stmt params
+ var resultItems = [];
+
// first get non-recurring events and recurring events that happen
// to fall within the range
sp = this.mSelectEventsByRange.params;
@@ -600,16 +632,16 @@ calStorageCalendar.prototype = {
while (this.mSelectEventsByRange.step()) {
var row = this.mSelectEventsByRange.row;
- count += handleResultItems(row, this.getEventFromRow, kCalIEvent);
- if (asOccurrences && row.flags & CAL_ITEM_FLAG_HAS_RECURRENCE)
- handledRecurringEvents[row.id] = true;
+ var flags = {};
+ var item = this.getEventFromRow(row, flags);
+ flags = flags.value;
- if (aCount && count >= aCount)
- break;
+ resultItems.push({item: item, flags: flags});
+
+ if (asOccurrences && flags & CAL_ITEM_FLAG_HAS_RECURRENCE)
+ handledRecurringEvents[row.id] = true;
}
this.mSelectEventsByRange.reset();
- if (checkCount())
- return;
// then, if we want occurrences, we need to query database-wide.. yuck
if (asOccurrences) {
@@ -621,16 +653,24 @@ calStorageCalendar.prototype = {
if (handledRecurringEvents[row.id] == true)
continue;
- count += handleResultItems(row, this.getEventFromRow, kCalIEvent);
- if (aCount && count >= aCount)
- break;
+ var flags = {};
+ var item = this.getEventFromRow(row, flags);
+ flags = flags.value;
+
+ resultItems.push({item: item, flags: flags});
}
this.mSelectEventsWithRecurrence.reset();
+ }
+
+ // process the events
+ for each (var evitem in resultItems) {
+ count += handleResultItem(evitem.item, evitem.flags, kCalIEvent);
if (checkCount())
return;
}
}
+
// if todos are wanted, do them next
if (wantTodos) {
// this will contain a lookup table of item ids that we've already dealt with,
@@ -638,6 +678,8 @@ calStorageCalendar.prototype = {
var handledRecurringTodos = { };
var sp; // stmt params
+ var resultItems = [];
+
// first get non-recurring todos and recurring todos that happen
// to fall within the range
sp = this.mSelectTodosByRange.params;
@@ -647,15 +689,15 @@ calStorageCalendar.prototype = {
while (this.mSelectTodosByRange.step()) {
var row = this.mSelectTodosByRange.row;
- count += handleResultItems(row, this.getTodoFromRow, kCalITodo);
+ var flags = {};
+ var item = this.getTodoFromRow(row, flags);
+ flags = flags.value;
+
+ resultItems.push({item: item, flags: flags});
if (asOccurrences && row.flags & CAL_ITEM_FLAG_HAS_RECURRENCE)
handledRecurringTodos[row.id] = true;
- if (aCount && count >= aCount)
- break;
}
this.mSelectTodosByRange.reset();
- if (checkCount())
- return;
// then, if we want occurrences, we need to query database-wide.. yuck
if (asOccurrences) {
@@ -667,11 +709,18 @@ calStorageCalendar.prototype = {
if (handledRecurringTodos[row.id] == true)
continue;
- count += handleResultItems(row, this.getTodoFromRow, kCalITodo);
- if (aCount && count >= aCount)
- break;
+ var flags = {};
+ var item = this.getTodoFromRow(row, flags);
+ flags = flags.value;
+
+ resultItems.push({item: item, flags: flags});
}
this.mSelectTodosWithRecurrence.reset();
+ }
+
+ // process the todos
+ for each (var todoitem in resultItems) {
+ count += handleResultItem(todoitem.item, todoitem.flags, kCalITodo);
if (checkCount())
return;
}
@@ -742,7 +791,7 @@ calStorageCalendar.prototype = {
},
// check db version
- DB_SCHEMA_VERSION: 3,
+ DB_SCHEMA_VERSION: 4,
versionCheck: function () {
var version = -1;
var selectSchemaVersion;
@@ -765,11 +814,13 @@ calStorageCalendar.prototype = {
upgradeDB: function (oldVersion) {
// some common helpers
- function addColumn(tableName, colName, colType) {
- this.mDB.executeSimpleSQL("ALTER TABLE " + tableName + " ADD COLUMN " + colName + " " + colType);
+ function addColumn(db, tableName, colName, colType) {
+ db.executeSimpleSQL("ALTER TABLE " + tableName + " ADD COLUMN " + colName + " " + colType);
}
- if (oldVersion == 2 && this.DB_SCHEMA_VERSION == 3) {
+ if (oldVersion == 2 && this.DB_SCHEMA_VERSION >= 3) {
+ dump ("**** Upgrading schema from 2 -> 3\n");
+
this.mDB.beginTransaction();
try {
// the change between 2 and 3 includes the splitting of cal_items into
@@ -778,18 +829,11 @@ calStorageCalendar.prototype = {
// These need to default to "UTC" if their corresponding time is
// given, since that's what the default was for v2 calendars
- dump ("**** Upgrading schema from 2 -> 3\n");
-
// create the two new tables
- dump ("dropping\n");
try { this.mDB.executeSimpleSQL("DROP TABLE cal_events; DROP TABLE cal_todos;"); } catch (e) { }
- dump ("cal_events\n");
this.mDB.createTable("cal_events", sqlTables["cal_events"]);
- dump ("cal_todos\n");
this.mDB.createTable("cal_todos", sqlTables["cal_todos"]);
- dump ("copy stuff over\n");
-
// copy stuff over
var eventCols = ["cal_id", "id", "time_created", "last_modified", "title",
"priority", "privacy", "ical_status", "flags",
@@ -805,8 +849,6 @@ calStorageCalendar.prototype = {
" SELECT " + todoCols.join(",") +
" FROM cal_items WHERE item_type = 1");
- dump ("update stuff\n");
-
// now fix up the new _tz columns
this.mDB.executeSimpleSQL("UPDATE cal_events SET event_start_tz = 'UTC' WHERE event_start IS NOT NULL");
this.mDB.executeSimpleSQL("UPDATE cal_events SET event_end_tz = 'UTC' WHERE event_end IS NOT NULL");
@@ -818,14 +860,51 @@ calStorageCalendar.prototype = {
this.mDB.executeSimpleSQL("DELETE FROM cal_calendar_schema_version; INSERT INTO cal_calendar_schema_version VALUES (3);");
this.mDB.commitTransaction();
+
+ oldVersion = 3;
} catch (e) {
dump ("+++++++++++++++++ DB Error: " + this.mDB.lastErrorString + "\n");
Components.reportError("Upgrade failed! DB Error: " + this.mDB.lastErrorString);
this.mDB.rollbackTransaction();
throw e;
}
- } else {
- throw "Can't update calendar schema!";
+ }
+
+ if (oldVersion == 3 && this.DB_SCHEMA_VERSION >= 4) {
+ dump ("**** Upgrading schema from 3 -> 4\n");
+
+ this.mDB.beginTransaction();
+ try {
+ // the change between 3 and 4 is the addition of
+ // recurrence_id and recurrence_id_tz columns to
+ // cal_events, cal_todos, cal_attendees, and cal_properties
+ addColumn(this.mDB, "cal_events", "recurrence_id", "INTEGER");
+ addColumn(this.mDB, "cal_events", "recurrence_id_tz", "VARCHAR");
+
+ addColumn(this.mDB, "cal_todos", "recurrence_id", "INTEGER");
+ addColumn(this.mDB, "cal_todos", "recurrence_id_tz", "VARCHAR");
+
+ addColumn(this.mDB, "cal_attendees", "recurrence_id", "INTEGER");
+ addColumn(this.mDB, "cal_attendees", "recurrence_id_tz", "VARCHAR");
+
+ addColumn(this.mDB, "cal_properties", "recurrence_id", "INTEGER");
+ addColumn(this.mDB, "cal_properties", "recurrence_id_tz", "VARCHAR");
+
+ this.mDB.executeSimpleSQL("DELETE FROM cal_calendar_schema_version; INSERT INTO cal_calendar_schema_version VALUES (4);");
+ this.mDB.commitTransaction();
+
+ oldVersion = 4;
+ } catch (e) {
+ dump ("+++++++++++++++++ DB Error: " + this.mDB.lastErrorString + "\n");
+ Components.reportError("Upgrade failed! DB Error: " + this.mDB.lastErrorString);
+ this.mDB.rollbackTransaction();
+ throw e;
+ }
+ }
+
+ if (oldVersion != 4) {
+ dump ("#######!!!!! calStorageCalendar Schema Update failed -- db version: " + oldVersion + " this version: " + this.DB_SCHEMA_VERSION + "\n");
+ throw Components.results.NS_ERROR_FAILURE;
}
},
@@ -844,14 +923,14 @@ calStorageCalendar.prototype = {
this.mSelectEvent = createStatement (
this.mDB,
"SELECT * FROM cal_events " +
- "WHERE id = :id "+
+ "WHERE id = :id AND recurrence_id IS NULL " +
"LIMIT 1"
);
this.mSelectTodo = createStatement (
this.mDB,
"SELECT * FROM cal_todos " +
- "WHERE id = :id "+
+ "WHERE id = :id AND recurrence_id IS NULL " +
"LIMIT 1"
);
@@ -859,28 +938,40 @@ calStorageCalendar.prototype = {
this.mDB,
"SELECT * FROM cal_events " +
"WHERE event_end >= :event_start AND event_start < :event_end " +
- " AND cal_id = :cal_id"
+ " AND cal_id = :cal_id AND recurrence_id IS NULL"
);
this.mSelectTodosByRange = createStatement(
this.mDB,
"SELECT * FROM cal_todos " +
"WHERE todo_entry >= :todo_start AND todo_entry < :todo_end " +
- " AND cal_id = :cal_id"
+ " AND cal_id = :cal_id AND recurrence_id IS NULL"
);
this.mSelectEventsWithRecurrence = createStatement(
this.mDB,
"SELECT * FROM cal_events " +
" WHERE flags & 16 == 16 " +
- " AND cal_id = :cal_id"
+ " AND cal_id = :cal_id AND recurrence_id is NULL"
);
this.mSelectTodosWithRecurrence = createStatement(
this.mDB,
"SELECT * FROM cal_todos " +
" WHERE flags & 16 == 16 " +
- " AND cal_id = :cal_id"
+ " AND cal_id = :cal_id AND recurrence_id IS NULL"
+ );
+
+ this.mSelectEventExceptions = createStatement (
+ this.mDB,
+ "SELECT * FROM cal_events " +
+ "WHERE id = :id AND recurrence_id IS NOT NULL"
+ );
+
+ this.mSelectTodoExceptions = createStatement (
+ this.mDB,
+ "SELECT * FROM cal_todos " +
+ "WHERE id = :id AND recurrence_id IS NOT NULL"
);
// For the extra-item data, note that we use mDBTwo, so that
@@ -888,13 +979,13 @@ calStorageCalendar.prototype = {
this.mSelectAttendeesForItem = createStatement(
this.mDBTwo,
"SELECT * FROM cal_attendees " +
- "WHERE item_id = :item_id"
+ "WHERE item_id = :item_id AND recurrence_id = :recurrence_id AND recurrence_id_tz = :recurrence_id_tz"
);
this.mSelectPropertiesForItem = createStatement(
this.mDBTwo,
"SELECT * FROM cal_properties " +
- "WHERE item_id = :item_id"
+ "WHERE item_id = :item_id AND recurrence_id = :recurrence_id AND recurrence_id_tz = :recurrence_id_tz"
);
this.mSelectRecurrenceForItem = createStatement(
@@ -911,11 +1002,11 @@ calStorageCalendar.prototype = {
" (cal_id, id, time_created, last_modified, " +
" title, priority, privacy, ical_status, flags, " +
" event_start, event_start_tz, event_end, event_end_tz, event_stamp, " +
- " alarm_time, alarm_time_tz) " +
+ " alarm_time, alarm_time_tz, recurrence_id, recurrence_id_tz) " +
"VALUES (:cal_id, :id, :time_created, :last_modified, " +
" :title, :priority, :privacy, :ical_status, :flags, " +
" :event_start, :event_start_tz, :event_end, :event_end_tz, :event_stamp, " +
- " :alarm_time, :alarm_time_tz)"
+ " :alarm_time, :alarm_time_tz, :recurrence_id, :recurrence_id_tz)"
);
this.mInsertTodo = createStatement (
@@ -925,23 +1016,23 @@ calStorageCalendar.prototype = {
" title, priority, privacy, ical_status, flags, " +
" todo_entry, todo_entry_tz, todo_due, todo_due_tz, todo_completed, " +
" todo_completed_tz, todo_complete, " +
- " alarm_time, alarm_time_tz) " +
+ " alarm_time, alarm_time_tz, recurrence_id, recurrence_id_tz) " +
"VALUES (:cal_id, :id, :time_created, :last_modified, " +
" :title, :priority, :privacy, :ical_status, :flags, " +
" :todo_entry, :todo_entry_tz, :todo_due, :todo_due_tz, " +
" :todo_completed, :todo_completed_tz, :todo_complete, " +
- " :alarm_time, :alarm_time_tz)"
+ " :alarm_time, :alarm_time_tz, :recurrence_id, :recurrence_id_tz)"
);
this.mInsertProperty = createStatement (
this.mDB,
- "INSERT INTO cal_properties (item_id, key, value) " +
- "VALUES (:item_id, :key, :value)"
+ "INSERT INTO cal_properties (item_id, recurrence_id, recurrence_id_tz, key, value) " +
+ "VALUES (:item_id, :recurrence_id, :recurrence_id_tz, :key, :value)"
);
this.mInsertAttendee = createStatement (
this.mDB,
"INSERT INTO cal_attendees " +
- " (item_id, attendee_id, common_name, rsvp, role, status, type) " +
- "VALUES (:item_id, :attendee_id, :common_name, :rsvp, :role, :status, :type)"
+ " (item_id, recurrence_id, recurrence_id_tz, attendee_id, common_name, rsvp, role, status, type) " +
+ "VALUES (:item_id, :recurrence_id, :recurrence_id_tz, :attendee_id, :common_name, :rsvp, :role, :status, :type)"
);
this.mInsertRecurrence = createStatement (
this.mDB,
@@ -974,26 +1065,33 @@ calStorageCalendar.prototype = {
},
- // database helper functions
+ //
+ // database reading functions
+ //
// read in the common ItemBase attributes from aDBRow, and stick
// them on item
getItemBaseFromRow: function (row, flags, item) {
- item.creationDate = newDateTime(row.time_created, "UTC");
- item.lastModifiedTime = newDateTime(row.last_modified, "UTC");
+ if (row.time_created)
+ item.creationDate = newDateTime(row.time_created, "UTC");
+ if (row.last_modified)
+ item.lastModifiedTime = newDateTime(row.last_modified, "UTC");
item.calendar = this;
item.id = row.id;
- item.title = row.title;
- item.priority = row.priority;
- item.privacy = row.privacy;
- item.status = row.ical_status;
+ if (row.title)
+ item.title = row.title;
+ if (row.priority)
+ item.priority = row.priority;
+ if (row.privacy)
+ item.privacy = row.privacy;
+ if (row.ical_status)
+ item.status = row.ical_status;
- if (row.alarm_time) {
+ if (row.alarm_time)
item.alarmTime = newDateTime(row.alarm_time, row.alarm_time_tz);
- item.hasAlarm = true;
- } else {
- item.hasAlarm = false;
- }
+
+ if (row.recurrence_id)
+ item.recurrenceId = newDateTime(row.recurrence_id, row.recurrence_id_tz);
if (flags)
flags.value = row.flags;
@@ -1004,9 +1102,12 @@ calStorageCalendar.prototype = {
this.getItemBaseFromRow (row, flags, item);
- item.startDate = newDateTime(row.event_start, row.event_start_tz);
- item.endDate = newDateTime(row.event_end, row.event_end_tz);
- item.stampTime = newDateTime(row.event_stamp, "UTC");
+ if (row.event_start)
+ item.startDate = newDateTime(row.event_start, row.event_start_tz);
+ if (row.event_end)
+ item.endDate = newDateTime(row.event_end, row.event_end_tz);
+ if (row.event_stamp)
+ item.stampTime = newDateTime(row.event_stamp, "UTC");
if ((row.flags & CAL_ITEM_FLAG_EVENT_ALLDAY) != 0) {
item.isAllDay = true;
item.startDate.isDate = true;
@@ -1021,10 +1122,14 @@ calStorageCalendar.prototype = {
this.getItemBaseFromRow (row, flags, item);
- item.entryDate = newDateTime(row.todo_entry, row.todo_entry_tz);
- item.dueDate = newDateTime(row.todo_due, row.todo_due_tz);
- item.completedDate = newDateTime(row.todo_completed, row.todo_completed_tz);
- item.percentComplete = row.todo_complete;
+ if (row.todo_entry)
+ item.entryDate = newDateTime(row.todo_entry, row.todo_entry_tz);
+ if (row.todo_due)
+ item.dueDate = newDateTime(row.todo_due, row.todo_due_tz);
+ if (row.todo_completed)
+ item.completedDate = newDateTime(row.todo_completed, row.todo_completed_tz);
+ if (row.todo_complete)
+ item.percentComplete = row.todo_complete;
return item;
},
@@ -1039,6 +1144,7 @@ calStorageCalendar.prototype = {
getAdditionalDataForItem: function (item, flags) {
if (flags & CAL_ITEM_FLAG_HAS_ATTENDEES) {
this.mSelectAttendeesForItem.params.item_id = item.id;
+ this.setDateParamHelper(this.mSelectAttendeesForItem.params, "recurrence_id", item.recurrenceId);
while (this.mSelectAttendeesForItem.step()) {
var attendee = this.getAttendeeFromRow(this.mSelectAttendeesForItem.row);
item.addAttendee(attendee);
@@ -1049,6 +1155,7 @@ calStorageCalendar.prototype = {
var row;
if (flags & CAL_ITEM_FLAG_HAS_PROPERTIES) {
this.mSelectPropertiesForItem.params.item_id = item.id;
+ this.setDateParamHelper(this.mSelectPropertiesForItem.params, "recurrence_id", item.recurrenceId);
while (this.mSelectPropertiesForItem.step()) {
row = this.mSelectPropertiesForItem.row;
item.setProperty (row.key, row.value);
@@ -1058,6 +1165,9 @@ calStorageCalendar.prototype = {
var i;
if (flags & CAL_ITEM_FLAG_HAS_RECURRENCE) {
+ if (item.recurrenceId)
+ throw Components.results.NS_ERROR_UNEXPECTED;
+
var rec = null;
this.mSelectRecurrenceForItem.params.item_id = item.id;
@@ -1119,7 +1229,7 @@ calStorageCalendar.prototype = {
ritem.isNegative = true;
if (rec == null) {
rec = new CalRecurrenceInfo();
- rec.initialize(item);
+ rec.item = item;
}
rec.appendRecurrenceItem(ritem);
}
@@ -1131,6 +1241,44 @@ calStorageCalendar.prototype = {
this.mSelectRecurrenceForItem.reset();
}
+
+ if (flags & CAL_ITEM_FLAG_HAS_EXCEPTIONS) {
+ if (item.recurrenceId)
+ throw Components.results.NS_ERROR_UNEXPECTED;
+
+ var rec = item.recurrenceInfo;
+
+ var exceptions = [];
+
+ if (item instanceof kCalIEvent) {
+ this.mSelectEventExceptions.params.id = item.id;
+ while (this.mSelectEventExceptions.step()) {
+ var row = this.mSelectEventExceptions.row;
+ var flags = {};
+ var exc = this.getEventFromRow(row, flags);
+ exceptions.push({item: exc, flags: flags.value});
+ }
+ this.mSelectEventExceptions.reset();
+ } else if (item instanceof kCalITodo) {
+ this.mSelectTodoExceptions.params.id = item.id;
+ while (this.mSelectTodoExceptions.step()) {
+ var row = this.mSelectTodoExceptions.row;
+ var flags = {};
+ var exc = this.getTodoFromRow(row, flags);
+
+ exceptions.push({item: exc, flags: flags.value});
+ }
+ this.mSelectTodoExceptions.reset();
+ } else {
+ throw Components.results.NS_ERROR_UNEXPECTED;
+ }
+
+ for each (var exc in exceptions) {
+ this.getAdditionalDataForItem(exc.item, exc.flags);
+ exc.item.parentItem = item;
+ rec.modifyException(exc.item);
+ }
+ }
},
getAttendeeFromRow: function (row) {
@@ -1188,14 +1336,42 @@ calStorageCalendar.prototype = {
return item;
},
- // write this item's changed data to the db. olditem may be null
- flushItem: function (item, olditem) {
- // for now, we just delete and insert
- // set up params before transaction
- var oldItemDeleteStmt;
- var newItemType;
+ //
+ // database writing functions
+ //
+ setDateParamHelper: function (params, entryname, cdt) {
+ if (cdt) {
+ params[entryname] = cdt.nativeTime;
+ params[entryname + "_tz"] = cdt.timezone;
+ } else {
+ params[entryname] = null;
+ params[entryname + "_tz"] = null;
+ }
+ },
+
+ flushItem: function (item, olditem) {
+ this.mDB.beginTransaction();
+ try {
+ this.writeItem(item, olditem);
+ this.mDB.commitTransaction();
+ } catch (e) {
+ dump("flushItem DB error: " + this.mDB.lastErrorString + "\n");
+ Components.reportError("flushItem DB error: " + this.mDB.lastErrorString);
+ this.mDB.rollbackTransaction();
+ throw e;
+ }
+
+ this.mItemCache[item.id] = item;
+ },
+
+ //
+ // Nuke olditem, if any
+ //
+
+ deleteOldItem: function (item, olditem) {
if (olditem) {
+ var oldItemDeleteStmt;
if (olditem instanceof kCalIEvent)
oldItemDeleteStmt = this.mDeleteEvent;
else if (olditem instanceof kCalITodo)
@@ -1205,184 +1381,251 @@ calStorageCalendar.prototype = {
this.mDeleteAttendees.params.item_id = olditem.id;
this.mDeleteProperties.params.item_id = olditem.id;
this.mDeleteRecurrence.params.item_id = olditem.id;
+
+ this.mDeleteRecurrence.execute();
+ this.mDeleteProperties.execute();
+ this.mDeleteAttendees.execute();
+ oldItemDeleteStmt.execute();
}
-
- // the insert params
- var ip;
-
- if (item instanceof kCalIEvent) {
- newItemType = CAL_ITEM_TYPE_EVENT;
- ip = this.mInsertEvent.params;
- } else if (item instanceof kCalITodo) {
- newItemType = CAL_ITEM_TYPE_TODO;
- ip = this.mInsertTodo.params;
- }
-
- ip.cal_id = this.mCalId;
- ip.id = item.id;
- ip.time_created = item.creationDate.nativeTime;
- ip.last_modified = item.lastModifiedTime.nativeTime;
- ip.title = item.title;
- ip.priority = item.priority;
- ip.privacy = item.privacy;
- ip.ical_status = item.status;
-
- function setDateParamHelper(params, entryname, cdt) {
- params[entryname] = cdt.nativeTime;
- params[entryname + "_tz"] = cdt.timezone;
- }
-
- // build flags up as we go
- var flags = 0;
-
- if (newItemType == CAL_ITEM_TYPE_EVENT) {
- if (item.isAllDay)
- flags |= CAL_ITEM_FLAG_EVENT_ALLDAY;
-
- setDateParamHelper(ip, "event_start", item.startDate);
- setDateParamHelper(ip, "event_end", item.endDate);
- // we want this to always be utc
- ip.event_stamp = item.stampTime.nativeTime;
- } else if (newItemType == CAL_ITEM_TYPE_TODO) {
- setDateParamHelper(ip, "todo_entry", item.entryDate);
- setDateParamHelper(ip, "todo_due", item.dueDate);
- setDateParamHelper(ip, "todo_completed", item.completedDate);
- ip.todo_complete = item.percentComplete;
- }
-
- if (item.hasAlarm) {
- setDateParamHelper(ip, "alarm_time", item.alarmTime);
- }
-
- // start the transaction
- this.mDB.beginTransaction();
- try {
- // first delete the old item's data
- if (olditem) {
- this.mDeleteRecurrence.execute();
- this.mDeleteAttendees.execute();
- this.mDeleteProperties.execute();
- oldItemDeleteStmt.execute();
- }
-
- // then add in auxiliary bits (attendees, properties, recurrence)
-
- var attendees = item.getAttendees({});
- if (attendees && attendees.length > 0) {
- flags |= CAL_ITEM_FLAG_HAS_ATTENDEES;
- for each (var att in attendees) {
- var ap = this.mInsertAttendee.params;
- ap.item_id = item.id;
- ap.attendee_id = att.id;
- ap.common_name = att.commonName;
- ap.rsvp = att.rsvp;
- ap.role = att.role;
- ap.status = att.participationStatus;
- ap.type = att.userType;
-
- this.mInsertAttendee.execute();
- }
- }
-
- var propEnumerator = item.propertyEnumerator;
- while (propEnumerator.hasMoreElements()) {
- flags |= CAL_ITEM_FLAG_HAS_PROPERTIES;
-
- var prop = propEnumerator.getNext().QueryInterface(Components.interfaces.nsIProperty);
-
- var pp = this.mInsertProperty.params;
- pp.item_id = item.id;
- pp.key = prop.name;
- pp.value = prop.value;
-
- this.mInsertProperty.execute();
- }
-
- var rec = item.recurrenceInfo;
- if (rec) {
- flags |= CAL_ITEM_FLAG_HAS_RECURRENCE;
-
- var ritems = rec.getRecurrenceItems ({});
- for (i in ritems) {
- var ritem = ritems[i];
- ap = this.mInsertRecurrence.params;
- ap.item_id = item.id;
- ap.recur_index = i;
- ap.is_negative = ritem.isNegative;
- if (ritem instanceof kCalIRecurrenceDate) {
- ritem = ritem.QueryInterface(kCalIRecurrenceDate);
- ap.recur_type = "x-date";
- ap.dates = dateToText(ritem.date);
-
- } else if (ritem instanceof kCalIRecurrenceDateSet) {
- ritem = ritem.QueryInterface(kCalIRecurrenceDateSet);
- ap.recur_type = "x-dateset";
-
- var rdates = ritem.getDates({});
- var datestr = "";
- for (j in rdates) {
- if (j != 0)
- datestr += ",";
-
- datestr += dateToText(rdates[j]);
- }
-
- ap.dates = datestr;
-
- } else if (ritem instanceof kCalIRecurrenceRule) {
- ritem = ritem.QueryInterface(kCalIRecurrenceRule);
- ap.recur_type = ritem.type;
-
- if (ritem.isByCount)
- ap.count = ritem.count;
- else
- ap.end_date = ritem.endDate.nativeTime;
-
- ap.interval = ritem.interval;
-
- var rtypes = ["second",
- "minute",
- "hour",
- "day",
- "monthday",
- "yearday",
- "weekno",
- "month",
- "setpos"];
- for (j = 0; j < rtypes.length; j++) {
- var comp = "BY" + rtypes[i].toUpperCase();
- var comps = ritem.getComponent(comp, {});
- if (comps && comps.length > 0) {
- var compstr = comps.join(",");
- ap[rtypes[j]] = compstr;
- }
- }
- } else {
- dump ("XXX Don't know how to serialize recurrence item " + ritem + "!\n");
- }
-
- this.mInsertRecurrence.execute();
- }
- }
-
- // finally insert the item itself
- ip.flags = flags;
- if (newItemType == CAL_ITEM_TYPE_EVENT)
- this.mInsertEvent.execute();
- else if (newItemType == CAL_ITEM_TYPE_TODO)
- this.mInsertTodo.execute();
-
- this.mDB.commitTransaction();
- } catch (e) {
- Components.reportError("flushItem DB error: " + this.mDB.lastErrorString);
- this.mDB.rollbackTransaction();
- throw e;
- }
-
- this.mItemCache[item.id] = item;
},
- // delete the event with the given uid
+ //
+ // The write* functions execute the database bits
+ // to write the given item type. They're to return
+ // any bits they want or'd into flags, which will be passed
+ // to writeEvent/writeTodo to actually do the writing.
+ //
+
+ writeItem: function (item, olditem) {
+ var flags = 0;
+
+ this.deleteOldItem(item, olditem);
+
+ flags |= this.writeAttendees(item, olditem);
+ flags |= this.writeRecurrence(item, olditem);
+ flags |= this.writeProperties(item, olditem);
+ flags |= this.writeAttachments(item, olditem);
+
+ if (item instanceof kCalIEvent)
+ this.writeEvent(item, olditem, flags);
+ else if (item instanceof kCalITodo)
+ this.writeTodo(item, olditem, flags);
+ else
+ throw Components.results.NS_ERROR_UNEXPECTED;
+ },
+
+ writeEvent: function (item, olditem, flags) {
+ var ip = this.mInsertEvent.params;
+ this.setupItemBaseParams(item, olditem,ip);
+
+ var tmp;
+
+ tmp = item.getUnproxiedProperty("DTSTART");
+ //if (tmp instanceof kCalIDateTime) {}
+ this.setDateParamHelper(ip, "event_start", tmp);
+ tmp = item.getUnproxiedProperty("DTEND");
+ //if (tmp instanceof kCalIDateTime) {}
+ this.setDateParamHelper(ip, "event_end", tmp);
+
+ if (item.isAllDay)
+ flags |= CAL_ITEM_FLAG_EVENT_ALLDAY;
+
+ ip.flags = flags;
+
+ this.mInsertEvent.execute();
+ },
+
+ writeTodo: function (item, olditem, flags) {
+ var ip = this.mInsertTodo.params;
+
+ this.setupItemBaseParams(item, olditem,ip);
+
+ this.setDateParamHelper(ip, "todo_entry", item.getUnproxiedProperty("DTSTART"));
+ this.setDateParamHelper(ip, "todo_due", item.getUnproxiedProperty("DUE"));
+ this.setDateParamHelper(ip, "todo_completed", item.getUnproxiedProperty("COMPLETED"));
+
+ ip.todo_complete = item.getUnproxiedProperty("PERCENT-COMPLETED");
+
+ ip.flags = flags;
+
+ this.mInsertTodo.execute();
+ },
+
+ setupItemBaseParams: function (item, olditem, ip) {
+ ip.cal_id = this.mCalId;
+ ip.id = item.id;
+
+ if (item.recurrenceId)
+ this.setDateParamHelper(ip, "recurrence_id", item.recurrenceId);
+
+ var tmp;
+
+ if (tmp = item.getUnproxiedProperty("CREATED"))
+ ip.time_created = tmp.nativeTime;
+ if (tmp = item.getUnproxiedProperty("LAST-MODIFIED"))
+ ip.last_modified = tmp.nativeTime;
+
+ ip.title = item.getUnproxiedProperty("SUMMARY");
+ ip.priority = item.getUnproxiedProperty("PRIROITY");
+ ip.privacy = item.getUnproxiedProperty("PRIVACY");
+ ip.ical_status = item.getUnproxiedProperty("STATUS");
+
+ if (!item.parentItem)
+ ip.event_stamp = item.stampTime.nativeTime;
+
+ if (tmp = item.getUnproxiedProperty("ALARMTIME"))
+ this.setDateParamHelper(ip, "alarm_Time", tmp);
+ },
+
+ writeAttendees: function (item, olditem) {
+ // XXX how does this work for proxy stuffs?
+ var attendees = item.getAttendees({});
+ if (attendees && attendees.length > 0) {
+ flags |= CAL_ITEM_FLAG_HAS_ATTENDEES;
+ for each (var att in attendees) {
+ var ap = this.mInsertAttendee.params;
+ ap.item_id = item.id;
+ this.setDateParamHelper(ap, "recurrence_id", item.recurrenceId);
+ ap.attendee_id = att.id;
+ ap.common_name = att.commonName;
+ ap.rsvp = att.rsvp;
+ ap.role = att.role;
+ ap.status = att.participationStatus;
+ ap.type = att.userType;
+
+ this.mInsertAttendee.execute();
+ }
+
+ return CAL_ITEM_FLAG_HAS_ATTENDEES;
+ }
+
+ return 0;
+ },
+
+ writeProperties: function (item, olditem) {
+ var ret = 0;
+ var propEnumerator = item.unproxiedPropertyEnumerator;
+ while (propEnumerator.hasMoreElements()) {
+ ret = CAL_ITEM_FLAG_HAS_PROPERTIES;
+
+ var prop = propEnumerator.getNext().QueryInterface(Components.interfaces.nsIProperty);
+
+ if (item.isPropertyPromoted(prop.name))
+ continue;
+
+ var pp = this.mInsertProperty.params;
+
+ pp.key = prop.name;
+ var pval = prop.value;
+ if (pval instanceof kCalIDateTime) {
+ pp.value = pval.nativeTime;
+ } else {
+ pp.value = pval;
+ }
+ pp.item_id = item.id;
+ this.setDateParamHelper(pp, "recurrence_id", item.recurrenceId);
+
+ this.mInsertProperty.execute();
+ }
+
+ return ret;
+ },
+
+ writeRecurrence: function (item, olditem) {
+ var flags = 0;
+
+ var rec = item.recurrenceInfo;
+ if (rec) {
+ flags = CAL_ITEM_FLAG_HAS_RECURRENCE;
+ var ritems = rec.getRecurrenceItems ({});
+ for (i in ritems) {
+ var ritem = ritems[i];
+ ap = this.mInsertRecurrence.params;
+ ap.item_id = item.id;
+ ap.recur_index = i;
+ ap.is_negative = ritem.isNegative;
+ if (ritem instanceof kCalIRecurrenceDate) {
+ ritem = ritem.QueryInterface(kCalIRecurrenceDate);
+ ap.recur_type = "x-date";
+ ap.dates = dateToText(ritem.date);
+
+ } else if (ritem instanceof kCalIRecurrenceDateSet) {
+ ritem = ritem.QueryInterface(kCalIRecurrenceDateSet);
+ ap.recur_type = "x-dateset";
+
+ var rdates = ritem.getDates({});
+ var datestr = "";
+ for (j in rdates) {
+ if (j != 0)
+ datestr += ",";
+
+ datestr += dateToText(rdates[j]);
+ }
+
+ ap.dates = datestr;
+
+ } else if (ritem instanceof kCalIRecurrenceRule) {
+ ritem = ritem.QueryInterface(kCalIRecurrenceRule);
+ ap.recur_type = ritem.type;
+
+ if (ritem.isByCount)
+ ap.count = ritem.count;
+ else
+ ap.end_date = ritem.endDate.nativeTime;
+
+ ap.interval = ritem.interval;
+
+ var rtypes = ["second",
+ "minute",
+ "hour",
+ "day",
+ "monthday",
+ "yearday",
+ "weekno",
+ "month",
+ "setpos"];
+ for (j = 0; j < rtypes.length; j++) {
+ var comp = "BY" + rtypes[i].toUpperCase();
+ var comps = ritem.getComponent(comp, {});
+ if (comps && comps.length > 0) {
+ var compstr = comps.join(",");
+ ap[rtypes[j]] = compstr;
+ }
+ }
+ } else {
+ dump ("##### Don't know how to serialize recurrence item " + ritem + "!\n");
+ }
+
+ this.mInsertRecurrence.execute();
+ }
+
+ var exceptions = rec.getExceptionIds ({});
+ if (exceptions.length > 0) {
+ flags |= CAL_ITEM_FLAG_HAS_EXCEPTIONS;
+
+ // we need to serialize each exid as a separate
+ // event/todo; setupItemBase will handle
+ // writing the recurrenceId for us
+ for each (exid in exceptions) {
+ var ex = rec.getExceptionFor(exid, false);
+ if (!ex)
+ throw Components.results.NS_ERROR_UNEXPECTED;
+ this.writeItem(ex, null);
+ }
+ }
+ }
+
+ return flags;
+ },
+
+ writeAttachments: function (item, olditem) {
+ // XXX write me?
+ return 0;
+ },
+
+ //
+ // delete the item with the given uid
+ //
deleteItemById: function (aID) {
this.mDB.beginTransaction();
try {
@@ -1477,11 +1720,14 @@ var sqlTables = {
" priority INTEGER," +
" privacy STRING," +
" ical_status STRING," +
+ " recurrence_id INTEGER," +
+ " recurrence_id_tz VARCHAR," +
/* CAL_ITEM_FLAG_PRIVATE = 1 */
/* CAL_ITEM_FLAG_HAS_ATTENDEES = 2 */
/* CAL_ITEM_FLAG_HAS_PROPERTIES = 4 */
/* CAL_ITEM_FLAG_EVENT_ALLDAY = 8 */
/* CAL_ITEM_FLAG_HAS_RECURRENCE = 16 */
+ /* CAL_ITEM_FLAG_HAS_EXCEPTIONS = 32 */
" flags INTEGER," +
/* Event bits */
" event_start INTEGER," +
@@ -1505,11 +1751,14 @@ var sqlTables = {
" priority INTEGER," +
" privacy STRING," +
" ical_status STRING," +
+ " recurrence_id INTEGER," +
+ " recurrence_id_tz VARCHAR," +
/* CAL_ITEM_FLAG_PRIVATE = 1 */
/* CAL_ITEM_FLAG_HAS_ATTENDEES = 2 */
/* CAL_ITEM_FLAG_HAS_PROPERTIES = 4 */
/* CAL_ITEM_FLAG_EVENT_ALLDAY = 8 */
/* CAL_ITEM_FLAG_HAS_RECURRENCE = 16 */
+ /* CAL_ITEM_FLAG_HAS_EXCEPTIONS = 32 */
" flags INTEGER," +
/* Todo bits */
/* date the todo is to be displayed */
@@ -1530,6 +1779,8 @@ var sqlTables = {
cal_attendees:
" item_id STRING," +
+ " recurrence_id INTEGER," +
+ " recurrence_id_tz VARCHAR," +
" attendee_id STRING," +
" common_name STRING," +
" rsvp INTEGER," +
@@ -1570,6 +1821,8 @@ var sqlTables = {
cal_properties:
" item_id STRING," +
+ " recurrence_id INTEGER," +
+ " recurrence_id_tz VARCHAR," +
" key STRING," +
" value BLOB" +
"",
diff --git a/calendar/providers/storage/schema.sql b/calendar/providers/storage/schema.sql
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/calendar/resources/content/datetimepickers/minimonth.xml b/calendar/resources/content/datetimepickers/minimonth.xml
index 669f30b3c0e..ffbbc264740 100644
--- a/calendar/resources/content/datetimepickers/minimonth.xml
+++ b/calendar/resources/content/datetimepickers/minimonth.xml
@@ -263,7 +263,7 @@
this.mEditorDate = aDate;
if (this.mSelected) {
- this.mSelected.setAttribute("selected", "");
+ this.mSelected.removeAttribute("selected");
this.mSelected = null;
}
@@ -308,7 +308,7 @@
if (aDate.getMonth() != date.getMonth()) {
day.setAttribute("othermonth", "true");
} else {
- day.setAttribute("othermonth", "");
+ day.removeAttribute("othermonth");
}
// highlight the current date
@@ -438,7 +438,7 @@