From 5d2e6f3e77856692a949774e4a58d671d6924a30 Mon Sep 17 00:00:00 2001 From: "vladimir%pobox.com" Date: Sun, 7 Nov 2004 23:48:13 +0000 Subject: [PATCH] in-memory and mozStorage calICalendar providers initial checkin git-svn-id: svn://10.0.0.236/trunk@165087 18797224-902f-48f8-a5cc-f745e15eee43 --- mozilla/calendar/providers/memory/Makefile.in | 49 ++ .../providers/memory/calMemoryCalendar.js | 316 +++++++++++ .../calendar/providers/storage/Makefile.in | 49 ++ .../providers/storage/calStorageCalendar.js | 523 ++++++++++++++++++ 4 files changed, 937 insertions(+) create mode 100644 mozilla/calendar/providers/memory/Makefile.in create mode 100644 mozilla/calendar/providers/memory/calMemoryCalendar.js create mode 100644 mozilla/calendar/providers/storage/Makefile.in create mode 100644 mozilla/calendar/providers/storage/calStorageCalendar.js diff --git a/mozilla/calendar/providers/memory/Makefile.in b/mozilla/calendar/providers/memory/Makefile.in new file mode 100644 index 00000000000..e07e04e463e --- /dev/null +++ b/mozilla/calendar/providers/memory/Makefile.in @@ -0,0 +1,49 @@ +# +# ***** 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 the Oracle Corporation code. +# +# The Initial Developer of the Original Code is Oracle Corporation. +# Portions created by the Initial Developer are Copyright (C) 2004 +# 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 ***** + +DEPTH = ../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = calMemoryCalendar + +EXTRA_COMPONENTS = calMemoryCalendar.js + +include $(topsrcdir)/config/rules.mk diff --git a/mozilla/calendar/providers/memory/calMemoryCalendar.js b/mozilla/calendar/providers/memory/calMemoryCalendar.js new file mode 100644 index 00000000000..4e00b9ae666 --- /dev/null +++ b/mozilla/calendar/providers/memory/calMemoryCalendar.js @@ -0,0 +1,316 @@ + +// +// calMemoryCalendar.js +// + +function calMemoryCalendar() { +} + +calMemoryCalendar.prototype = { + // + // private members + // + mObservers: Array(), + mItems: Array(), + + // + // nsISupports interface + // + QueryInterface: function (aIID) { + if (!aIID.equals(Components.interfaces.nsISupports) && + !aIID.equals(Components.interfaces.calICalendar)) + { + throw Components.results.NS_ERROR_NO_INTERFACE; + } + + return this; + }, + + // + // nsICalendar interface + // + + mBatchMode: false, + // attribute boolean batchMode; + get batchMode() { return this.mBatchMode; }, + set batchMode(aBatchMode) { + this.mBatchMode = aBatchMode; + }, + + // attribute nsIURI uri; + get uri() { return false; }, + set uri(aURI) { throw Components.results.NS_ERROR_NOT_IMPLEMENTED; }, + + // attribute boolean suppressAlarms; + get suppressAlarms() { return false; }, + set suppressAlarms(aSuppressAlarms) { throw Components.results.NS_ERROR_NOT_IMPLEMENTED; }, + + // XXX what happens when we add an observer that already exists with a different filter? + // we can add them separately, but when we remove, we'll remove all instances + // void addObserver( in calIObserver observer, in unsigned long aItemFilter ); + addObserver: function (aObserver, aItemFilter) { + for (var i = 0; i < this.mObservers.length; i++) { + if (this.mObservers[i].observer == aObserver && + this.mObservers[i].filter == aItemFilter) + { + return; + } + } + + this.mObservers.push( {observer: aObserver, filter: aItemFilter} ); + }, + + // void removeObserver( in calIObserver observer ); + removeObserver: function (aObserver) { + var newObservers = Array(); + for (var i = 0; i < this.mObservers.length; i++) { + if (this.mObservers[i].observer != aObserver) + newObservers.push(mObservers[i]); + } + this.mObservers = newObservers; + }, + + // void addItem( in calIItemBase aItem, in calIOperationListener aListener ); + addItem: function (aItem, aListener) { + if (aItem.id == null) { + // is this an error? Or should we generate an IID? + } + if (this.mItems[aItem.id] != null) { + // is this an error? + if (aListener) + aListener.onOperationComplete (Components.results.NS_ERROR_FAILURE, + aItem.id, + aListener.ADD, + "ID already eists for addItem"); + return; + } + + this.mItems[aItem.id] = aItem; + + // notify observers + this.observeAddItem(aItem); + + // notify the listener + if (aListener) + aListener.onOperationComplete (Components.results.NS_OK, + aItem.id, + aListener.ADD, + aItem); + }, + + // void modifyItem( in calIItemBase aItem, in calIOperationListener aListener ); + modifyItem: function (aItem, aListener) { + if (aItem.id == null || + this.mItems[aItem.id] == null) + { + // this is definitely an error + if (aListener) + aListener.onOperationComplete (Components.results.NS_ERROR_FAILURE, + aItem.id, + aListener.MODIFY, + "ID for modifyItem doesn't exist or is null"); + return; + } + + // technically, they should be the same item + var modifiedItem = this.mItems[aItem.id]; + this.mItems[aItem.id] = aItem; + + // notify observers + this.observeModifyItem(modifiedItem, aItem); + + if (aListener) + aListener.onOperationComplete (Components.results.NS_OK, + aItem.id, + aListener.MODIFY, + aItem); + }, + + // void deleteItem( in string id, in calIOperationListener aListener ); + deleteItem: function (aId, aListener) { + if (aId == null || + this.mItems[aId] == null) + { + if (aListener) + aListener.onOperationComplete (Components.results.NS_ERROR_FAILURE, + aId, + aListener.DELETE, + "ID doesn't exist for deleteItem"); + return; + } + + deletedItem = this.mItems[aId]; + delete this.mItems[aId]; + + // notify observers + observeDeleteItem(deletedItem); + + if (aListener) + aListener.onOperationComplete (Components.results.NS_OK, + aId, + aListener.DELETE, + null); + + }, + + // void getItem( in string id, in calIOperationListener aListener ); + getItem: function (aId, aListener) { + if (aId == null || + this.mItems[aId] == null) + { + if (aListener) + aListener.onOperationComplete (Components.results.NS_ERROR_FAILURE, + aId, + aListener.GET, + "ID doesn't exist for getItem"); + return; + } + + if (aListener) + aListener.onOperationComplete (Components.results.NS_OK, + aId, + aListener.GET, + this.mItems[aId]); + }, + + // void getItems( in nsIIDRef aItemType, in unsigned long aItemFilter, + // in unsigned long aCount, in calIDateTime aRangeStart, + // in calIDateTime aRangeEnd, + // in calIOperationListener aListener ); + getItems: function (aItemType, aItemFilter, aCount, + aRangeStart, aRangeEnd, aListener) + { + // we ignore aItemFilter; we always return all items. if + // aCount != 0, we don't attempt to sort anything, and instead + // return the first aCount items that match. + + var itemsFound = Array(); + var startTime = 0; + // endTime needs to be the max value a PRTime can be + var endTime = 0x7fffffffffffffff; + if (aRangeStart) + startTime = aRangeStart.utcTime; + if (aRangeEnd) + endTime = aRangeEnd.utcTime; + + const calIEvent = Components.interfaces.calIEvent; + const calITodo = Components.interfaces.calITodo; + + for (var i = 0; i < this.mItems.length; i++) { + var item = this.mItems[i]; + + if (aItemType) + item = item.QueryInterface(aItemType); + if (item) { + var itemStartTime = 0; + var itemEndTime = 0; + + var tmpitem = item; + if (aItemType == calIEvent || + (tmpitem = item.QueryInterface(calIEvent)) != null) + { + itemStartTime = tmpitem.startDate.utcTime; + itemEndTime = tmpitem.endDate.utcTime; + } else if (aItemType == calITodo || + (tmpitem = item.QueryInterface(calITodo)) != null) + { + itemStartTime = tmpitem.entryTime.utcTime; + itemEndTime = tmpitem.entryTime.utcTime; + } else { + // XXX unknown item type, wth do we do? + continue; + } + + // determine whether any endpoint falls within the range + if ((itemStartTime >= startTime && itemEndTime <= endTime) || + (itemStartTime <= startTime && itemEndTime > startTime) || + (itemStartTime <= endTime && itemEndTime >= endTime)) + { + itemsFound.push(item); + } + + if (aCount && itemsFound.length >= aCount) + break; + } + } + + if (aListener) + aListener.onGetComplete (Components.results.NS_OK, + aItemType, + null, + itemsFound); + }, + + // void reportError( in unsigned long errorid, in AUTF8String aMessage ); + reportError: function (aErrorId, aMessage) + { + }, + + // + // Helper functions + // + observeBatchChange: function (aNewBatchMode) { + for (var i = 0; i < this.mObservers.length; i++) { + if (aNewBatchMode) + this.mObservers[i].onStartBatch (); + else + this.mObservers[i].onEndBatch (); + } + }, + + observeAddItem: function (aItem) { + for (var i = 0; i < this.mObservers.length; i++) + this.mObservers[i].onAddItem (aItem); + }, + + observeModifyItem: function (aOldItem, aNewItem) { + for (var i = 0; i < this.mObservers.length; i++) + this.mObservers[i].onModifyItem (aOldItem, aNewItem); + }, + + observeDeleteItem: function (aDeletedItem) { + for (var i = 0; i < this.mObservers.length; i++) + this.mObservers[i].onDeleteItem (aDeletedItem); + }, +} + +/**** + **** module registration + ****/ + +var calStorageCalendarModule = { + mCID: Components.ID("{bda0dd7f-0a2f-4fcf-ba08-5517e6fbf133}"), + mContractID: "@mozilla.org/calendar/calendar;1&type=memory", + + registerSelf: function (compMgr, fileSpec, location, type) { + compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar); + compMgr.registerFactoryLocation(this.mCID, + "Calendar in-memory back-end", + this.mContractID, + fileSpec, + location, + type); + }, + + getClassObject: function (compMgr, cid, iid) { + if (!cid.equals(this.mCID)) + throw Components.results.NS_ERROR_NO_INTERFACE; + + if (!iid.equals(Components.interfaces.nsIFactory)) + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + + return this.mFactory; + }, + + mFactory: { + createInstance: function (outer, iid) { + if (outer != null) + throw Components.results.NS_ERROR_NO_AGGREGATION; + return (new calStorageCalendar()).QueryInterface(iid); + } + }, + + canUnload: function(compMgr) { + return true; + } +}; diff --git a/mozilla/calendar/providers/storage/Makefile.in b/mozilla/calendar/providers/storage/Makefile.in new file mode 100644 index 00000000000..47162e490f5 --- /dev/null +++ b/mozilla/calendar/providers/storage/Makefile.in @@ -0,0 +1,49 @@ +# +# ***** 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 the Oracle Corporation code. +# +# The Initial Developer of the Original Code is Oracle Corporation. +# Portions created by the Initial Developer are Copyright (C) 2004 +# 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 ***** + +DEPTH = ../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = calStorageCalendar + +EXTRA_COMPONENTS = calStorageCalendar.js + +include $(topsrcdir)/config/rules.mk diff --git a/mozilla/calendar/providers/storage/calStorageCalendar.js b/mozilla/calendar/providers/storage/calStorageCalendar.js new file mode 100644 index 00000000000..dce296e3cc1 --- /dev/null +++ b/mozilla/calendar/providers/storage/calStorageCalendar.js @@ -0,0 +1,523 @@ + +// +// calStorageCalendar.js +// + +const kStorageServiceContractID = "@mozilla.org/storage/service;1"; +const kStorageServiceIID = Components.interfaces.mozIStorageService; + +const kCalEventContractID = "@mozilla.org/calendar/item;1&type=event"; +const kCalEventIID = Components.interfaces.calIEvent; + +const kCalDateTimeContractID = "@mozilla.org/calendar/datetime;1"; +const kCalDateTimeIID = Components.interfaces.calIDateTime; + +// column ids in cal_events +const EV_HASHID = 0; +const EV_TITLE = 1; +const EV_DESCRIPTION = 2; +const EV_LOCATION = 3; +const EV_CATEGORIES = 4; +const EV_TIME_START = 5; +const EV_TIME_END = 6; +const EV_FLAGS = 7; +const EV_LAST_MODIFIED = 8; +const EV_ALARM_DATA = 9; +const EV_ALARM_TIME = 10; +const EV_RECUR_TYPE = 11; +const EV_RECUR_INTERVAL = 12; +const EV_RECUR_DATA = 13; +const EV_RECUR_END = 14; +const EV_RECUR_FLAGS = 15; + +// helpers +function newDateTime(aPRTime) { + var t = Components.classes[kCalDateTimeContractID] + .createInstance(kCalDateTimeIID); + t.utcTime = aPRTime; + return t; +} + +function calStorageCalendar() { +} + +calStorageCalendar.prototype = { + // + // private members + // + mObservers: Array(), + mDB: null, + mItemCache: Array(), + + // database statements + mSelectEventByHash: null, + mSelectEventByOid: null, + mSelectEventsByRange: null, + mSelectEventsByRangeWithLimit: null, + mDeleteEventByHash: null, + mInsertEvent: null, + + // + // nsISupports interface + // + QueryInterface: function (aIID) { + if (!aIID.equals(Components.interfaces.nsISupports) && + !aIID.equals(Components.interfaces.calICalendar)) + { + throw Components.results.NS_ERROR_NO_INTERFACE; + } + + return this; + }, + + // + // nsICalendar interface + // + + mBatchMode: false, + // attribute boolean batchMode; + get batchMode() { return this.mBatchMode; }, + set batchMode(aBatchMode) { + this.mBatchMode = aBatchMode; + + }, + + mURI: null, + // attribute nsIURI uri; + get uri() { return this.mURI; }, + set uri(aURI) { + // we can only load once + if (this.mURI) + throw Components.results.NS_ERROR_FAILURE; + + // we can only load storage bits from a file + this.mURI = aURI; + var fileURL = aURI.QueryInterface(Components.interfaces.nsIFileURL); + if (!fileURL) + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + + // open the database + var dbService = Components.classes[kStorageServiceContractID] + .getService(kStorageServiceIID); + this.mDB = dbService.openDatabase (fileURL.file); + + initDB(); + }, + + // attribute boolean suppressAlarms; + get suppressAlarms() { return false; }, + set suppressAlarms(aSuppressAlarms) { throw Components.results.NS_ERROR_NOT_IMPLEMENTED; }, + + // XXX what happens when we add an observer that already exists with a different filter? + // we can add them separately, but when we remove, we'll remove all instances + // void addObserver( in calIObserver observer, in unsigned long aItemFilter ); + addObserver: function (aObserver, aItemFilter) { + for (var i = 0; i < this.mObservers.length; i++) { + if (this.mObservers[i].observer == aObserver && + this.mObservers[i].filter == aItemFilter) + { + return; + } + } + + this.mObservers.push( {observer: aObserver, filter: aItemFilter} ); + }, + + // void removeObserver( in calIObserver observer ); + removeObserver: function (aObserver) { + var newObservers = Array(); + for (var i = 0; i < this.mObservers.length; i++) { + if (this.mObservers[i].observer != aObserver) + newObservers.push(this.mObservers[i]); + } + this.mObservers = newObservers; + }, + + // void addItem( in calIItemBase aItem, in calIOperationListener aListener ); + addItem: function (aItem, aListener) { + if (aItem.id == null) { + // is this an error? Or should we generate an IID? + } + if (this.mItems[aItem.id] != null) { + // is this an error? + if (aListener) + aListener.onOperationComplete (Components.results.NS_ERROR_FAILURE, + aItem.id, + aListener.ADD, + "ID already eists for addItem"); + return; + } + + // is this item an event? + var event = aItem.QueryInterface(kCalEventIID); + if (event) { + flushEvent (event); + } else { + aListener.onOperationComplete (Components.results.NS_ERROR_FAILURE, + aItem.id, + aListener.ADD, + "Don't know how to add items of the given type"); + return; + } + + // notify observers + observeAddItem(aItem); + + // notify the listener + if (aListener) + aListener.onOperationComplete (Components.results.NS_OK, + aItem.id, + aListener.ADD, + aItem); + }, + + // void modifyItem( in calIItemBase aItem, in calIOperationListener aListener ); + modifyItem: function (aItem, aListener) { + if (aItem.id == null) + { + // this is definitely an error + if (aListener) + aListener.onOperationComplete (Components.results.NS_ERROR_FAILURE, + aItem.id, + aListener.MODIFY, + "ID for modifyItem item is null"); + return; + } + + // is this item an event? + var event = aItem.QueryInterface(kCalEventIID); + if (event) { + flushEvent (event); + } else { + aListener.onOperationComplete (Components.results.NS_ERROR_FAILURE, + aItem.id, + aListener.MODIFY, + "Don't know how to modify items of the given type"); + return; + } + + // notify observers + observeModifyItem(modifiedItem, aItem); + + if (aListener) + aListener.onOperationComplete (Components.results.NS_OK, + aItem.id, + aListener.MODIFY, + aItem); + }, + + // void deleteItem( in string id, in calIOperationListener aListener ); + deleteItem: function (aId, aListener) { + if (aId == null) + { + if (aListener) + aListener.onOperationComplete (Components.results.NS_ERROR_FAILURE, + aId, + aListener.DELETE, + "ID is null for deleteItem"); + return; + } + + // is this item an event? + var event = aItem.QueryInterface(kCalEventIID); + if (event) { + deleteEvent (aId); + } else { + aListener.onOperationComplete (Components.results.NS_ERROR_FAILURE, + aItem.id, + aListener.DELETE, + "Don't know how to delete items of the given type"); + return; + } + + // notify observers XXX + //observeDeleteItem(deletedItem); + + if (aListener) + aListener.onOperationComplete (Components.results.NS_OK, + aId, + aListener.DELETE, + null); + + }, + + // void getItem( in string id, in calIOperationListener aListener ); + getItem: function (aId, aListener) { + if (!aListener) + return; + + // XXX what do we load from? this thing is horribly underspecified + var item = getEventByHash (aId); + if (item) { + aListener.onOperationComplete (Components.results.NS_OK, + aId, + aListener.GET, + item); + } else { + aListener.onOperationComplete (Components.results.NS_ERROR_FAILURE, + aId, + aListener.GET, + "ID not found"); + } + }, + + // void getItems( in nsIIDRef aItemType, in unsigned long aItemFilter, + // in unsigned long aCount, in calIDateTime aRangeStart, + // in calIDateTime aRangeEnd, + // in calIOperationListener aListener ); + getItems: function (aItemType, aItemFilter, aCount, + aRangeStart, aRangeEnd, aListener) + { + if (!aListener) + return; + + // we ignore aItemFilter; we always return all items. if + // aCount != 0, we don't attempt to sort anything, and instead + // return the first aCount items that match. + + var itemsFound = Array(); + var startTime = 0; + // endTime needs to be the max value a PRTime can be + var endTime = 0x7fffffffffffffff; + if (aRangeStart) + startTime = aRangeStart.utcTime; + if (aRangeEnd) + endTime = aRangeEnd.utcTime; + + if (aItemType != null && aItemType != kCalEventIID) { + // we don't know what to do with anything but events for now + aListener.onGetComplete(Components.results.NS_ERROR_FAILURE, + aItemType, + "Don't know how to get the requested item type", + aCount, Array()); + return; + } + + var events = getEventsByRange(); + + if (aListener) + aListener.onGetComplete (Components.results.NS_OK, + aItemType, + null, + itemsFound); + }, + + // void reportError( in unsigned long errorid, in AUTF8String aMessage ); + reportError: function (aErrorId, aMessage) + { + }, + + // + // Helper functions + // + observeLoad: function () { + for (var i = 0; i < mObservers.length; i++) + mObservers[i].onLoad (); + }, + + observeBatchChange: function (aNewBatchMode) { + for (var i = 0; i < mObservers.length; i++) { + if (aNewBatchMode) + mObservers[i].onStartBatch (); + else + mObservers[i].onEndBatch (); + } + }, + + observeAddItem: function (aItem) { + for (var i = 0; i < mObservers.length; i++) + mObservers[i].onAddItem (aItem); + }, + + observeModifyItem: function (aOldItem, aNewItem) { + for (var i = 0; i < mObservers.length; i++) + mObservers[i].onModifyItem (aOldItem, aNewItem); + }, + + observeDeleteItem: function (aDeletedItem) { + for (var i = 0; i < mObservers.length; i++) + mObservers[i].onDeleteItem (aDeletedItem); + }, + + // database initialization + // assumes mDB is valid + initDB: function () { + try { + mDB.createTable ("cal_events", + "hashid, title, description, location, categories, " + + "time_start, time_end, flags, last_modified, " + + "alarm_data, alarm_time, " + + "recur_type, recur_interval, recur_data, recur_end, recur_flags"); + mDB.createTable ("cal_event_recur_exceptions", + "event_id, skip_date"); + mDB.createTable ("cal_event_attendees", + "event_id, attendee, attendee_status"); + mDB.createTable ("cal_evnent_attachments", + "event_id, attachment_type, attachment_data"); + + mDB.executeSimpleSQL ("CREATE INDEX cal_events_hash_idx ON cal_events.hashid"); + } catch (e) { + // ... + } + + mSelectEventByHash = mDB.createStatement ("SELECT * FROM cal_events WHERE hashid = ? LIMIT 1"); + mSelectEventByOid = mDB.createStatement ("SELECT * FROM cal_events WHERE oid = ? LIMIT 1"); + // this needs to have the range bound like this: start, end, start, start, end, end + mSelectEventsByRange = mDB.createStatement ("SELECT * FROM cal_events WHERE (time_start >= ? AND time_end <= ?) OR (time_start <= ? AND time_end > ?) OR (time_start <= ? AND time_end <= ?)"); + mSelectEventsByRangeAndLimit = mDB.createStatement ("SELECT * FROM cal_events WHERE (time_start >= ? AND time_end <= ?) OR (time_start <= ? AND time_end > ?) OR (time_start <= ? AND time_end <= ?) LIMIT ?"); + + mDeleteEventByHash = mDB.createStatement ("DELETE FROM cal_events WHERE hashid = ?"); + + // XXX create a function helper for this, too easy to screw up with all the ?'s + mInsertEvent = mDB.createStatement ("INSERT INTO cal_events VALUES (?,?,?,?,?, ?,?,?,?,?, ?,?,?,?,?, ?)"); + }, + + // database helper functions + getEventFromRow: function (aDBRow) { + // create a new generic event + var e = Components.classes[kCalEventContractID] + .createInstance(kCalEventIID); + + // read the data from the statement row + // XXX do I need getAsPRTime? + e.startDate = newDateTime (aDBRow.getAsInt64(EV_TIME_START)); + e.endDate = newDateTime (aDBRow.getAsInt64(EV_TIME_END)); + e.title = aDBRow.getAsString(EV_TITLE); + e.description = aDBRow.getAsString(EV_DESCRIPTION); + e.location = aDBRow.getAsString(EV_LOCATION); + e.categories = aDBRow.getAsString(EV_CATEGORIES); + + e.id = aID; + e.parent = this; + + return e; + }, + + // get an event from the db or cache with the given ID + getEventByHash: function (aID) { + // cached? + if (mEventHash[aID] != null) + return mEventHash[aID]; + + var retval; + + // not cached; need to read from the db + mSelectEventByHash.bindCStringParameter (0, aID); + if (mSelectEventByHash.executeStep()) { + var e = getEventFromRow (mSelectEventByHash); + retval = e; + // stick into cache + mEventHash[aID] = e; + } else { + // not found + // throw? return null? + retval = null; + } + + mSelectEventByHash.reset(); + + return retval; + }, + + // get a list of events from the db in the given range + getEventsByRange: function (aRangeStart, aRangeEnd, aCount) { + var stmt = mSelectEventsByRange; + if (aCount) + stmt = mSelectEventsByRangeAndLimit; + + stmt.bindInt64Parameter(0, aRangeStart); + stmt.bindInt64Parameter(1, aRangeEnd); + stmt.bindInt64Parameter(2, aRangeStart); + stmt.bindInt64Parameter(3, aRangeStart); + stmt.bindInt64Parameter(4, aRangeEnd); + stmt.bindInt64Parameter(5, aRangeEnd); + if (aCount) + stmt.bindInt32Parameter(6, aCount); + + var events = Array(); + while (stmt.executeStep()) { + var e = getEventFromRow(stmt); + events.push (e); + mEventHash[e.id] = e; + } + + return events; + }, + + // write this event's changed data to the db + flushEvent: function (aEvent) { + // set up statements before executing the transaction + mDeleteEventByHash.bindCStringParameter (0, aEvent.id); + + mInsertEvent.bindCStringParameter (EV_HASHID, aEvent.id); + + mInsertEvent.bindInt64Parameter (EV_TIME_START, aEvent.start.utcTime); + mInsertEvent.bindInt64Parameter (EV_TIME_END, aEvent.end.utcTime); + + mInsertEvent.bindStringParameter (EV_TITLE, aEvent.title); + mInsertEvent.bindStringParameter (EV_DESCRIPTION, aEvent.description); + mInsertEvent.bindStringParameter (EV_LOCATION, aEvent.location); + mInsertEvent.bindStringParameter (EV_CATEGORIES, aEvent.categories); + + mDB.beginTransaction(); + mDeleteEventByHash.execute(); + mInsertEvent.execute(); + mDB.commitTransaction(); + + mDeleteEventByHash.reset(); + mInsertEvent.reset(); + + mEventHash[aEvent.id] = aEvent; + }, + + // delete the event with the given uid + deleteEvent: function (aID) { + mDeleteEventByHash.bindCStringParameter (0, aID); + mDeleteEventByHash.execute (); + mDeleteEventByHash.reset (); + + delete mEventHash[aID]; + }, +} + + +/**** + **** module registration + ****/ + +var calStorageCalendarModule = { + mCID: Components.ID("{b3eaa1c4-5dfe-4c0a-b62a-b3a514218461}"), + mContractID: "@mozilla.org/calendar/calendar;1&type=storage", + + registerSelf: function (compMgr, fileSpec, location, type) { + compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar); + compMgr.registerFactoryLocation(this.mCID, + "Calendar mozStorage/SQL back-end", + this.mContractID, + fileSpec, + location, + type); + }, + + getClassObject: function (compMgr, cid, iid) { + if (!cid.equals(this.mCID)) + throw Components.results.NS_ERROR_NO_INTERFACE; + + if (!iid.equals(Components.interfaces.nsIFactory)) + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + + return this.mFactory; + }, + + mFactory: { + createInstance: function (outer, iid) { + if (outer != null) + throw Components.results.NS_ERROR_NO_AGGREGATION; + return (new calStorageCalendar()).QueryInterface(iid); + } + }, + + canUnload: function(compMgr) { + return true; + } +};