gecko-dev/calendar/providers/storage/calStorageCalendar.js

1318 строки
44 KiB
JavaScript
Исходник Обычный вид История

/* -*- 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 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 <vladimir.vukicevic@oracle.com>
*
* 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 ***** */
//
// calStorageCalendar.js
//
const kStorageServiceContractID = "@mozilla.org/storage/service;1";
const kStorageServiceIID = Components.interfaces.mozIStorageService;
const kCalICalendar = Components.interfaces.calICalendar;
const kCalCalendarManagerContractID = "@mozilla.org/calendar/manager;1";
const kCalICalendarManager = Components.interfaces.calICalendarManager;
const kCalEventContractID = "@mozilla.org/calendar/event;1";
const kCalIEvent = Components.interfaces.calIEvent;
var CalEvent;
const kCalTodoContractID = "@mozilla.org/calendar/todo;1";
const kCalITodo = Components.interfaces.calITodo;
var CalTodo;
const kCalDateTimeContractID = "@mozilla.org/calendar/datetime;1";
const kCalIDateTime = Components.interfaces.calIDateTime;
var CalDateTime;
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;
const kCalRecurrenceRuleContractID = "@mozilla.org/calendar/recurrence-rule;1";
const kCalIRecurrenceRule = Components.interfaces.calIRecurrenceRule;
var CalRecurrenceRule;
const kCalRecurrenceDateSetContractID = "@mozilla.org/calendar/recurrence-date-set;1";
const kCalIRecurrenceDateSet = Components.interfaces.calIRecurrenceDateSet;
var CalRecurrenceDateSet;
const kCalRecurrenceDateContractID = "@mozilla.org/calendar/recurrence-date;1";
const kCalIRecurrenceDate = Components.interfaces.calIRecurrenceDate;
var CalRecurrenceDate;
const kMozStorageStatementWrapperContractID = "@mozilla.org/storage/statement-wrapper;1";
const kMozStorageStatementWrapperIID = Components.interfaces.mozIStorageStatementWrapper;
var MozStorageStatementWrapper;
if (!kMozStorageStatementWrapperIID) {
dump("*** mozStorage not available, calendar/storage provider will not function\n");
}
const CAL_ITEM_TYPE_EVENT = 0;
const CAL_ITEM_TYPE_TODO = 1;
// bitmasks
const CAL_ITEM_FLAG_PRIVATE = 1;
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;
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);
CalRecurrenceDate = new Components.Constructor(kCalRecurrenceDateContractID, kCalIRecurrenceDate);
MozStorageStatementWrapper = new Components.Constructor(kMozStorageStatementWrapperContractID, kMozStorageStatementWrapperIID);
}
//
// Storage helpers
//
function createStatement (dbconn, sql) {
var stmt = dbconn.createStatement(sql);
var wrapper = MozStorageStatementWrapper();
wrapper.initialize(stmt);
return wrapper;
}
function textToDate(d) {
var dval = parseInt(d.substr(2));
var date;
if (d[0] == 'U') {
// isutc
date = newDateTime(dval);
} else if (d[0] == 'L') {
// is local time
date = newDateTime(dval, true);
}
if (d[1] == 'D')
date.isDate = true;
return date;
}
function dateToText(d) {
var datestr;
if (d.isUtc) {
datestr = "U";
} else {
datestr = "L";
}
if (d.isDate) {
datestr += "D";
} else {
datestr += "T";
}
datestr += d.nativeTime;
return datestr;
}
//
// other helpers
//
function newDateTime(aPRTime, isLocalTime) {
var t = CalDateTime();
if (!isLocalTime)
t.isUtc = true;
else
t.isUtc = false;
t.nativeTime = aPRTime;
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
//
var activeCalendarManager = null;
function getCalendarManager()
{
if (!activeCalendarManager) {
activeCalendarManager =
Components.classes[kCalCalendarManagerContractID].getService(kCalICalendarManager);
}
return activeCalendarManager;
}
function calStorageCalendar() {
this.wrappedJSObject = this;
}
calStorageCalendar.prototype = {
//
// private members
//
mObservers: Array(),
mDB: null,
mDBTwo: null,
mItemCache: Array(),
mCalId: 0,
//
// 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;
},
//
// calICalendar interface
//
// attribute AUTF8String name;
get name() {
return getCalendarManager().getCalendarPref(this, "NAME");
},
set name(name) {
getCalendarManager().setCalendarPref(this, "NAME", name);
},
// readonly attribute AUTF8String type;
get type() { return "storage"; },
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;
var id = 0;
// check if there's a ?id=
var path = aURI.path;
var pos = path.indexOf("?id=");
if (pos != -1) {
id = parseInt(path.substr(pos+4));
path = path.substr(0, pos);
}
var dbService;
if (aURI.scheme == "file") {
var fileURL = aURI.QueryInterface(Components.interfaces.nsIFileURL);
if (!fileURL)
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
// open the database
dbService = Components.classes[kStorageServiceContractID].getService(kStorageServiceIID);
this.mDB = dbService.openDatabase (fileURL.file);
this.mDBTwo = dbService.openDatabase (fileURL.file);
} else if (aURI.scheme == "moz-profile-calendar") {
dbService = Components.classes[kStorageServiceContractID].getService(kStorageServiceIID);
this.mDB = dbService.getProfileStorage("profile");
this.mDBTwo = dbService.getProfileStorage("profile");
}
this.initDB();
this.mCalId = id;
this.mURI = aURI;
},
// attribute boolean readOnly;
get readOnly() { return false; },
set readOnly() { throw Components.results.NS_ERROR_NOT_IMPLEMENTED; },
// attribute boolean suppressAlarms;
get suppressAlarms() { return false; },
set suppressAlarms(aSuppressAlarms) { throw Components.results.NS_ERROR_NOT_IMPLEMENTED; },
// void addObserver( in calIObserver observer );
addObserver: function (aObserver, aItemFilter) {
for each (obs in this.mObservers) {
if (obs == 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);
}
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?
aItem.id = "uuid:" + (new Date()).getTime();
} else {
var olditem = this.getItemById(aItem.id);
if (olditem) {
if (aListener)
aListener.onOperationComplete (this,
Components.results.NS_ERROR_FAILURE,
aListener.ADD,
aItem.id,
"ID already exists for addItem");
return;
}
}
var newItem = aItem.clone();
newItem.parent = this;
newItem.generation = 1;
newItem.makeImmutable();
this.flushItem (newItem, null);
// notify the listener
if (aListener)
aListener.onOperationComplete (this,
Components.results.NS_OK,
aListener.ADD,
newItem.id,
newItem);
// notify observers
this.observeAddItem(newItem);
},
// 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 (this,
Components.results.NS_ERROR_FAILURE,
aListener.MODIFY,
aItem.id,
"ID for modifyItem item is null");
return;
}
// get the old item
var olditem = this.getItemById(aItem.id);
if (!olditem) {
// no old item found? should be using addItem, then.
if (aListener)
aListener.onOperationComplete (this,
Components.results.NS_ERROR_FAILURE,
aListener.MODIFY,
aItem.id,
"ID does not already exit for modifyItem");
return;
}
if (olditem.generation != aItem.generation) {
if (aListener)
aListener.onOperationComplete (this,
Components.results.NS_ERROR_FAILURE,
aListener.MODIFY,
aItem.id,
"generation too old for for modifyItem");
return;
}
var newItem = aItem.clone();
newItem.generation += 1;
newItem.makeImmutable();
this.flushItem (newItem, olditem);
if (aListener)
aListener.onOperationComplete (this,
Components.results.NS_OK,
aListener.MODIFY,
newItem.id,
newItem);
// notify observers
this.observeModifyItem(newItem, olditem);
},
// void deleteItem( in string id, in calIOperationListener aListener );
deleteItem: function (aItem, aListener) {
if (aItem.id == null) {
if (aListener)
aListener.onOperationComplete (this,
Components.results.NS_ERROR_FAILURE,
aListener.DELETE,
null,
"ID is null for deleteItem");
return;
}
this.deleteItemById(aItem.id);
if (aListener)
aListener.onOperationComplete (this,
Components.results.NS_OK,
aListener.DELETE,
aItem.id,
null);
// notify observers
this.observeDeleteItem(aItem);
},
// void getItem( in string id, in calIOperationListener aListener );
getItem: function (aId, aListener) {
if (!aListener)
return;
var item = this.getItemById (aId);
if (!item) {
aListener.onOperationComplete (this,
Components.results.NS_ERROR_FAILURE,
aListener.GET,
aId,
"ID doesn't exist for getItem");
}
var item_iid = null;
if (item instanceof kCalIEvent)
item_iid = kCalIEvent;
else if (item instanceof kCalITodo)
item_iid = kCalITodo;
else {
aListener.onOperationComplete (this,
Components.results.NS_ERROR_FAILURE,
aListener.GET,
aId,
"Can't deduce item type based on QI");
return;
}
aListener.onGetResult (this,
Components.results.NS_OK,
item_iid, null,
1, [item]);
aListener.onOperationComplete (this,
Components.results.NS_OK,
aListener.GET,
aId,
null);
},
// void getItems( in unsigned long aItemFilter, in unsigned long aCount,
// in calIDateTime aRangeStart, in calIDateTime aRangeEnd,
// in calIOperationListener aListener );
getItems: function (aItemFilter, aCount,
aRangeStart, aRangeEnd, aListener)
{
if (!aListener)
return;
var itemsFound = Array();
var startTime = -0x7fffffffffffffff;
// endTime needs to be the max value a PRTime can be
var endTime = 0x7fffffffffffffff;
if (aRangeStart)
startTime = aRangeStart.nativeTime;
if (aRangeEnd)
endTime = aRangeEnd.nativeTime;
// only events for now
var wantEvents = ((aItemFilter & kCalICalendar.ITEM_FILTER_TYPE_EVENT) != 0);
var wantTodos = ((aItemFilter & kCalICalendar.ITEM_FILTER_TYPE_TODO) != 0);
var asOccurrences = ((aItemFilter & kCalICalendar.ITEM_FILTER_CLASS_OCCURRENCES) != 0);
if (!wantEvents && !wantTodos) {
// nothing to do
aListener.onOperationComplete (this,
Components.results.NS_OK,
aListener.GET,
null,
null);
return;
}
var stmt;
if (asOccurrences)
stmt = this.mSelectItemsByRangeWithRecurrence;
else
stmt = this.mSelectItemsByRange;
stmt.reset();
var sp = stmt.params;
sp.cal_id = this.mCalId;
sp.event_start = startTime;
sp.event_end = endTime;
sp.todo_start = startTime;
sp.todo_end = endTime;
/*
if (asOccurrences) {
sp.event_start2 = startTime;
sp.event_end2 = endTime;
}
*/
var count = 0;
while (stmt.step()) {
var iid;
if (stmt.row.item_type == CAL_ITEM_TYPE_EVENT) {
if (!wantEvents)
continue;
iid = kCalIEvent;
} else if (stmt.row.item_type == CAL_ITEM_TYPE_TODO) {
if (!wantTodos)
continue;
iid = kCalITodo;
} else {
// errrr..
dump ("***** Unknown item type in db: " + stmt.row.item_type + "\n");
continue;
}
var flags = {};
var item = this.getItemFromRow(stmt.row, flags);
// uses mDBTwo, so can be interleaved
this.getAdditionalDataForItem(item, flags.value);
item.makeImmutable();
var items = null;
if (asOccurrences && item.recurrenceInfo) {
iid = kCalIItemOccurrence;
items = item.recurrenceInfo.getOccurrences (aRangeStart, aRangeEnd, 0, {});
} else {
if (asOccurrences) {
if (iid == kCalIEvent) {
item = makeOccurrence(item, item.startDate, item.endDate);
} else if (iid == kCalITodo) {
item = makeOccurrence(item, item.entryDate, item.entryDate);
}
iid = kCalIItemOccurrence;
}
items = [ item ];
}
aListener.onGetResult (this,
Components.results.NS_OK,
iid, null,
items.length, items);
count++;
if (aCount && count == aCount)
break;
}
stmt.reset();
aListener.onOperationComplete (this,
Components.results.NS_OK,
aListener.GET,
null,
null);
},
//
// Helper functions
//
observeLoad: function () {
for each (obs in this.mObservers)
obs.onLoad ();
},
observeBatchChange: function (aNewBatchMode) {
for each (obs in this.mObservers) {
if (aNewBatchMode)
obs.onStartBatch ();
else
obs.onEndBatch ();
}
},
observeAddItem: function (aItem) {
for each (obs in this.mObservers)
obs.onAddItem (aItem);
},
observeModifyItem: function (aOldItem, aNewItem) {
for each (obs in this.mObservers)
obs.onModifyItem (aOldItem, aNewItem);
},
observeDeleteItem: function (aDeletedItem) {
for each (obs in this.mObservers)
obs.onDeleteItem (aDeletedItem);
},
//
// database handling
//
// initialize the database schema.
// needs to do some version checking
initDBSchema: function () {
for (table in sqlTables) {
dump (table + "\n");
try {
this.mDB.executeSimpleSQL("DROP TABLE " + table);
} catch (e) { }
this.mDB.createTable(table, sqlTables[table]);
}
this.mDB.executeSimpleSQL("INSERT INTO cal_calendar_schema_version VALUES(" + this.DB_SCHEMA_VERSION + ")");
},
// check db version
DB_SCHEMA_VERSION: 2,
versionCheck: function () {
var version = -1;
try {
var selectSchemaVersion = createStatement (this.mDB, "SELECT version FROM cal_calendar_schema_version LIMIT 1");
if (selectSchemaVersion.step()) {
version = selectSchemaVersion.row.version;
}
selectSchemaVersion.reset();
} catch (e) {
// either the cal_calendar_schema_version table is not
// found, or something else happened
version = -1;
}
return version;
},
// database initialization
// assumes mDB is valid
initDB: function () {
var version = this.versionCheck();
dump ("*** Calendar schema version is: " + version + "\n");
if (version == -1) {
this.initDBSchema();
} else if (version != this.DB_SCHEMA_VERSION) {
this.upgradeDB();
}
this.mSelectItem = createStatement (
this.mDB,
"SELECT * FROM cal_items " +
"WHERE id = :id AND cal_id = :cal_id "+
"LIMIT 1"
);
// XXX rewrite this to use UNION ALL instead of OR!
this.mSelectItemsByRange = createStatement(
this.mDB,
"SELECT * FROM cal_items " +
"WHERE (item_type = 0 AND (event_end >= :event_start AND event_start < :event_end)) " +
" OR (item_type = 1 AND (todo_entry >= :todo_start AND todo_entry < :todo_end)) " +
" AND cal_id = :cal_id"
);
// XXX rewrite this to use UNION ALL instead of OR!
this.mSelectItemsByRangeWithRecurrence = createStatement(
this.mDB,
"SELECT * FROM cal_items " +
"WHERE ((flags & 16 == 16) " +
" OR (item_type = 0 AND (flags & 16 == 0) AND (event_end >= :event_start AND event_start < :event_end)) " +
" OR (item_type = 1 AND (flags & 16 == 0) AND todo_entry >= :todo_start AND todo_entry < :todo_end)) " +
" AND cal_id = :cal_id"
);
// For the extra-item data, note that we use mDBTwo, so that
// these can be executed while a selectItems is running!
this.mSelectAttendeesForItem = createStatement(
this.mDBTwo,
"SELECT * FROM cal_attendees " +
"WHERE item_id = :item_id"
);
this.mSelectPropertiesForItem = createStatement(
this.mDBTwo,
"SELECT * FROM cal_properties " +
"WHERE item_id = :item_id"
);
this.mSelectRecurrenceForItem = createStatement(
this.mDBTwo,
"SELECT * FROM cal_recurrence " +
"WHERE item_id = :item_id " +
"ORDER BY recur_index"
);
// insert statements
this.mInsertItem = createStatement (
this.mDB,
"INSERT INTO cal_items " +
" (cal_id, item_type, id, time_created, last_modified, " +
" title, priority, privacy, ical_status, flags, " +
" event_start, event_end, event_stamp, todo_entry, todo_due, " +
" todo_completed, todo_complete) " +
"VALUES (:cal_id, :item_type, :id, :time_created, :last_modified, " +
" :title, :priority, :privacy, :ical_status, :flags, " +
" :event_start, :event_end, :event_stamp, :todo_entry, :todo_due, " +
" :todo_completed, :todo_complete)"
);
this.mInsertAlarm = createStatement (
this.mDB,
"INSERT INTO cal_alarms (alarm_data) " +
"VALUES (:alarm_data)"
);
this.mInsertProperty = createStatement (
this.mDB,
"INSERT INTO cal_properties (item_id, key, value) " +
"VALUES (:item_id, :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)"
);
this.mInsertRecurrence = createStatement (
this.mDB,
"INSERT INTO cal_recurrence " +
" (item_id, recur_index, recur_type, is_negative, dates, count, end_date, interval, second, minute, hour, day, monthday, yearday, weekno, month, setpos) " +
"VALUES (:item_id, :recur_index, :recur_type, :is_negative, :dates, :count, :end_date, :interval, :second, :minute, :hour, :day, :monthday, :yearday, :weekno, :month, :setpos)"
);
// delete statements
this.mDeleteItem = createStatement (
this.mDB,
"DELETE FROM cal_items WHERE id = :id AND cal_id = :cal_id"
);
this.mDeleteAttendees = createStatement (
this.mDB,
"DELETE FROM cal_attendees WHERE item_id = :item_id"
);
this.mDeleteProperties = createStatement (
this.mDB,
"DELETE FROM cal_properties WHERE item_id = :item_id"
);
this.mDeleteRecurrence = createStatement (
this.mDB,
"DELETE FROM cal_recurrence WHERE item_id = :item_id"
);
},
// database helper functions
// read in the common ItemBase attributes from aDBRow, and stick
// them on aItemBase
getItemFromRow: function (row, flags) {
var item;
if (row.cal_id != this.mCalId) {
// the row selected doesn't belong to this calendar, hmm.
throw Components.results.NS_ERROR_FAILURE;
}
// create a new item based on the type from the db
if (row.item_type == CAL_ITEM_TYPE_EVENT)
item = CalEvent();
else if (row.item_type == CAL_ITEM_TYPE_TODO)
item = CalTodo();
else
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
// fill in common bits
item.creationDate = newDateTime(row.time_created);
2004-12-10 14:27:48 +03:00
item.lastModifiedTime = newDateTime(row.last_modified);
item.parent = this;
item.id = row.id;
item.title = row.title;
item.priority = row.priority;
item.privacy = row.privacy;
item.status = row.ical_status;
if (row.item_type == CAL_ITEM_TYPE_EVENT) {
item.startDate = newDateTime(row.event_start);
item.endDate = newDateTime(row.event_end);
item.stampTime = newDateTime(row.event_stamp);
item.isAllDay = ((row.flags & CAL_ITEM_FLAG_EVENT_ALLDAY) != 0);
} else if (row.item_type == CAL_ITEM_TYPE_TODO) {
item.entryDate = newDateTime(row.todo_entry);
item.dueDate = newDateTime(row.todo_due);
item.completedDate = newDateTime(row.todo_complete);
item.percentComplete = row.todo_complete;
}
// we need a way to do this without using wrappedJSObject
item.wrappedJSObject.storage_calendar_id = row.id;
if (flags)
flags.value = row.flags;
return item;
},
// after we get the base item, we need to check if we need to pull in
// any extra data from other tables. We do that here.
// note that we use mDBTwo for this, so this can be run while a
// select is executing; don't use any statements that aren't
// against mDBTwo in here!
getAdditionalDataForItem: function (item, flags) {
if (flags & CAL_ITEM_FLAG_HAS_ATTENDEES) {
this.mSelectAttendeesForItem.params.item_id = item.id;
while (this.mSelectAttendeesForItem.step()) {
var attendee = this.newAttendeeFromRow(this.mSelectAttendeesForItem.row);
item.addAttendee(attendee);
}
this.mSelectAttendeesForItem.reset();
}
var row;
if (flags & CAL_ITEM_FLAG_HAS_PROPERTIES) {
this.mSelectPropertiesForItem.params.item_id = item.id;
while (this.mSelectPropertiesForItem.step()) {
row = this.mSelectPropertiesForItem.row;
item.setProperty (row.key, row.value);
}
this.mSelectPropertiesForItem.reset();
}
var i;
if (flags & CAL_ITEM_FLAG_HAS_RECURRENCE) {
var rec = null;
this.mSelectRecurrenceForItem.params.item_id = item.id;
while (this.mSelectRecurrenceForItem.step()) {
row = this.mSelectRecurrenceForItem.row;
var ritem = null;
if (row.recur_type == null ||
row.recur_type == "x-dateset")
{
ritem = new CalRecurrenceDateSet();
var dates = row.dates.split(",");
for (i = 0; i < dates.length; i++) {
var date = textToDate(dates[i]);
ritem.addDate(date);
}
} else if (row.recur_type == "x-date") {
ritem = new CalRecurrenceDate();
var d = row.dates;
ritem.date = textToDate(d);
} else {
ritem = new CalRecurrenceRule();
ritem.type = row.recur_type;
if (row.count) {
ritem.count = row.count;
} else {
ritem.endDate = newDateTime(row.end_date);
}
ritem.interval = row.interval;
var rtypes = ["second",
"minute",
"hour",
"day",
"monthday",
"yearday",
"weekno",
"month",
"setpos"];
for (i = 0; i < rtypes.length; i++) {
var comp = "BY" + rtypes[i].toUpperCase();
if (row[rtypes[i]]) {
var rstr = row[rtypes[i]].split(",");
var rarray = [];
for (var j = 0; j < rstr.length; j++) {
rarray[j] = parseInt(rstr[j]);
}
ritem.setComponent (comp, rarray.length, rarray);
}
}
}
if (row.is_negative)
ritem.isNegative = true;
if (rec == null) {
rec = new CalRecurrenceInfo();
rec.initialize(item);
}
rec.appendRecurrenceItem(ritem);
}
if (rec == null) {
dump ("XXXX Expected to find recurrence, but got no items!\n");
}
item.recurrenceInfo = rec;
this.mSelectRecurrenceForItem.reset();
}
},
newAttendeeFromRow: function (row) {
var a = CalAttendee();
a.id = row.attendee_id;
a.commonName = row.common_name;
a.rsvp = (row.rsvp != 0);
a.role = row.role;
a.participationStatus = row.status;
a.userType = row.type;
return a;
},
//
// get item from db or from cache with given iid
//
getItemById: function (aID) {
// cached?
if (this.mItemCache[aID] != null)
return this.mItemCache[aID];
var retval = null;
// not cached; need to read from the db
this.mSelectItem.params.id = aID;
this.mSelectItem.params.cal_id = this.mCalId;
if (!this.mSelectItem.step()) {
// not found
return null;
}
var flags = {};
var item = this.getItemFromRow(this.mSelectItem.row, flags);
this.mSelectItem.reset();
this.getAdditionalDataForItem(item, flags.value);
item.makeImmutable();
// cache it
this.mItemCache[aID] = item;
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
if (olditem) {
this.mDeleteItem.params.id = olditem.id;
this.mDeleteItem.params.cal_id = this.mCalId;
this.mDeleteAttendees.params.item_id = olditem.id;
this.mDeleteProperties.params.item_id = olditem.id;
this.mDeleteRecurrence.params.item_id = olditem.id;
}
var ip = this.mInsertItem.params;
ip.cal_id = this.mCalId;
ip.id = item.id;
ip.time_created = item.creationDate.jsDate;
2004-12-10 14:27:48 +03:00
ip.last_modified = item.lastModifiedTime.jsDate;
ip.title = item.title;
ip.priority = item.priority;
ip.privacy = item.privacy;
ip.ical_status = item.status;
var flags = 0;
if (item instanceof kCalIEvent) {
ip.item_type = CAL_ITEM_TYPE_EVENT;
if (item.isAllDay)
flags |= CAL_ITEM_FLAG_EVENT_ALLDAY;
ip.event_start = item.startDate.jsDate;
ip.event_end = item.endDate.jsDate;
ip.event_stamp = item.stampTime.jsDate;
} else if (item instanceof kCalITodo) {
ip.item_type = CAL_ITEM_TYPE_TODO;
ip.todo_entry = item.entryDate.jsDate;
ip.todo_due = item.dueDate.jsDate;
ip.todo_completed = item.completedDate.jsDate;
ip.todo_complete = item.percentComplete;
} else {
// XXX fixme error
throw Components.results.NS_ERROR_FAILURE;
}
// start the transaction
this.mDB.beginTransaction();
try {
if (olditem) {
this.mDeleteRecurrence.step();
this.mDeleteAttendees.step();
this.mDeleteProperties.step();
this.mDeleteItem.step();
this.mDeleteRecurrence.reset();
this.mDeleteAttendees.reset();
this.mDeleteProperties.reset();
this.mDeleteItem.reset();
}
var ap;
var i;
var j;
var attendees = item.getAttendees({});
if (attendees && attendees.length > 0) {
flags |= CAL_ITEM_FLAG_HAS_ATTENDEES;
for (i = 0; i < attendees.length; i++) {
var a = attendees[i];
ap = this.mInsertAttendee.params;
ap.item_id = item.id;
ap.attendee_id = a.id;
ap.common_name = a.commonName;
ap.rsvp = a.rsvp;
ap.role = a.role;
ap.status = a.participationStatus;
ap.type = a.userType;
this.mInsertAttendee.step();
this.mInsertAttendee.reset();
}
}
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.step();
this.mInsertProperty.reset();
}
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.step();
this.mInsertRecurrence.reset();
}
}
ip.flags = flags;
this.mInsertItem.step();
this.mDB.commitTransaction();
} catch (e) {
dump ("EXCEPTION; error is: " + this.mDB.lastErrorString + "\n");
dump (e);
this.mDB.rollbackTransaction();
}
this.mItemCache[item.id] = item;
},
// delete the event with the given uid
deleteItemById: function (aID) {
this.mDeleteItem.params.id = aID;
this.mDeleteItem.params.cal_id = this.mCalId;
this.mDeleteAttendees.params.item_id = aID;
2004-12-11 01:25:27 +03:00
this.mDeleteProperties.params.item_id = aID;
this.mDeleteRecurrence.params.item_id = aID;
this.mDB.beginTransaction();
try {
this.mDeleteRecurrence.step();
this.mDeleteAttendees.step();
this.mDeleteProperties.step();
this.mDeleteItem.step();
this.mDB.commitTransaction();
} catch (e) {
dump ("EXCEPTION; error is: " + this.mDB.lastErrorString + "\n");
dump (e);
this.mDB.rollbackTransaction();
}
this.mDeleteAttendees.reset();
this.mDeleteProperties.reset();
this.mDeleteRecurrence.reset();
this.mDeleteItem.reset();
delete this.mItemCache[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;
if (!kStorageServiceIID)
throw Components.results.NS_ERROR_NOT_INITIALIZED;
if (!CalEvent) {
initCalStorageCalendarComponent();
}
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;
}
};
function NSGetModule(compMgr, fileSpec) {
return calStorageCalendarModule;
}
//
// sqlTables generated from schema.sql via makejsschema.pl
//
var sqlTables = {
cal_calendar_schema_version:
" version INTEGER" +
"",
cal_items:
/* REFERENCES cal_calendars.id, */
" cal_id INTEGER, " +
/* 0: event, 1: todo */
" item_type INTEGER," +
/* ItemBase bits */
" id STRING," +
" time_created INTEGER," +
" last_modified INTEGER," +
" title STRING," +
" priority INTEGER," +
" privacy STRING," +
" ical_status STRING," +
/* 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 */
" flags INTEGER," +
/* Event bits */
" event_start INTEGER," +
" event_end INTEGER," +
" event_stamp INTEGER," +
/* Todo bits */
" todo_entry INTEGER," +
" todo_due INTEGER," +
" todo_completed INTEGER," +
" todo_complete INTEGER," +
/* internal bits */
/* REFERENCES cal_alarms.id ON DELETE CASCADE */
" alarm_id INTEGER " +
"",
cal_attendees:
" item_id STRING," +
" attendee_id STRING," +
" common_name STRING," +
" rsvp INTEGER," +
" role STRING," +
" status STRING," +
" type STRING" +
"",
cal_alarms:
" id INTEGER PRIMARY KEY," +
" alarm_data BLOB" +
"",
cal_recurrence:
" item_id STRING," +
/* the index in the recurrence array of this thing */
" recur_index INTEGER, " +
/* values from calIRecurrenceInfo; if null, date-based. */
" recur_type STRING, " +
" is_negative BOOLEAN," +
/* */
/* these are for date-based recurrence */
/* */
/* comma-separated list of dates */
" dates STRING," +
/* */
/* these are for rule-based recurrence */
/* */
" count INTEGER," +
" end_date INTEGER," +
" interval INTEGER," +
/* components, comma-separated list or null */
" second STRING," +
" minute STRING," +
" hour STRING," +
" day STRING," +
" monthday STRING," +
" yearday STRING," +
" weekno STRING," +
" month STRING," +
" setpos STRING" +
"",
cal_properties:
" item_id STRING," +
" key STRING," +
" value BLOB" +
"",
};