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 @@