Fix bug 501689 - Investigate making storage asynchronous. r=philipp,a=Standard8

This commit is contained in:
Reid Anderson 2014-08-16 22:06:30 +02:00
Родитель e8e8d228c8
Коммит 40ea8e518a
5 изменённых файлов: 1688 добавлений и 2052 удалений

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
Components.utils.import("resource://calendar/modules/calUtils.jsm");
Components.utils.import("resource://gre/modules/Preferences.jsm");
var EXPORTED_SYMBOLS = [
"CAL_ITEM_FLAG",
@ -11,23 +12,23 @@ var EXPORTED_SYMBOLS = [
"textToDate",
"calStorageTimezone",
"getTimezone",
"newDateTime"
"newDateTime",
"setDateParamHelper",
"itemQueue"
];
// Storage flags. These are used in the Database |flags| column to give
// information about the item's features. For example, if the item has
// attachments, the HAS_ATTACHMENTS flag is added to the flags column.
var CAL_ITEM_FLAG = {
PRIVATE: 1,
// Storage flags.
const CAL_ITEM_FLAG = {
PRIVATE: 1, /* obsolete */
HAS_ATTENDEES: 2,
HAS_PROPERTIES: 4,
HAS_PROPERTIES: 4, /* deprecated */
EVENT_ALLDAY: 8,
HAS_RECURRENCE: 16,
HAS_EXCEPTIONS: 32,
HAS_ATTACHMENTS: 64,
HAS_RELATIONS: 128,
HAS_ALARMS: 256,
RECURRENCE_ID_ALLDAY: 512
RECURRENCE_ID_ALLDAY: 512, /* deprecated */
};
// The cache of foreign timezones
@ -184,3 +185,75 @@ function newDateTime(aNativeTime, aTimezone) {
}
return t;
}
function setDateParamHelper(params, entryname, cdt) {
if (cdt) {
params[entryname] = cdt.nativeTime;
let tz = cdt.timezone;
let ownTz = cal.getTimezoneService().getTimezone(tz.tzid);
if (ownTz) { // if we know that TZID, we use it
params[entryname + "_tz"] = ownTz.tzid;
} else if (!tz.icalComponent) { // timezone component missing
params[entryname + "_tz"] = "floating";
} else { // foreign one
params[entryname + "_tz"] = tz.icalComponent.serializeToICS();
}
} else {
params[entryname] = null;
params[entryname + "_tz"] = null;
}
}
function itemQueue(maxCount, latency) {
this.mMaxCount = maxCount;
this.mTotalCount = 0;
this.mLatency = latency || Preferences.get("calendar.threading.latency");
this.mItems = [];
}
itemQueue.prototype = {
get completed() this.mMaxCount && this.mTotalCount >= this.mMaxCount,
get totalItems() this.mTotalCount,
enqueue: function enqueue(aItems) {
if (this.completed) {
// Ignore any further results
return;
}
let itemCount = this.mItems.length;
let newItems = aItems;
if (this.mMaxCount && this.mTotalCount + itemCount > this.mMaxCount) {
newItems = aItems.slice(0, this.mMaxCount - this.mTotalCount);
}
if (itemCount == 0) {
this.mStartProcessing = (new Date()).getTime();
}
this.mTotalCount++;
this.mItems = this.mItems.concat(newItems);
this.checkProcessTime();
return this.completed;
},
checkProcessTime: function checkProcessTime() {
let now = (new Date()).getTime();
if (now - this.mStartProcessing > this.mLatency) {
this.finish();
}
},
unleash: function(items) {
// This function should be overridden!
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
},
finish: function finish() {
this.unleash(this.mItems);
this.mItems = [];
}
};

Просмотреть файл

@ -72,7 +72,7 @@ Components.utils.import("resource://calendar/modules/calStorageHelpers.jsm");
// The current database version. Be sure to increment this when you create a new
// updater.
var DB_SCHEMA_VERSION = 22;
var DB_SCHEMA_VERSION = 23;
var EXPORTED_SYMBOLS = ["DB_SCHEMA_VERSION", "getSql", "getAllSql", "getSqlTable", "upgradeDB", "backupDB"];
@ -102,7 +102,11 @@ function getSql(tblName, tblData, alternateName) {
} else {
sql = "CREATE TABLE " + altName + " (\n";
for (let [key, type] in Iterator(tblData[tblName])) {
sql += " " + key + " " + type + ",\n";
if (key.substr(0, 4) == "key_") {
sql += key.substr(4) + " " + type + ",\n";
} else {
sql += " " + key + " " + type + ",\n";
}
}
}
@ -262,7 +266,10 @@ function createDBDelegate(funcName) {
return db[funcName].apply(db, args);
} catch (e) {
cal.ERROR("Error calling '" + funcName + "' db error: '" +
lastErrorString(db) + "'.\nException: " + e);
lastErrorString(db) +
"' Args: " + args.join("---") + " " +
" DB: " + db +
"\nException: " + e);
cal.WARN(cal.STACK(10));
}
}
@ -323,6 +330,40 @@ function createIndex(tblData, tblName, colNameArray, db) {
" ON " + idxOn);
}
/**
* Removes the index described by columns from the table
*
* @param tblData The table data object to remove the index from.
* @param tblName The name of the table the index is on.
* @param colNameArray The columns that make up the index (order counts).
* @param db (optional) The database to create the index on.
*/
function deleteIndex(tblData, tblName, colNameArray, db) {
let idxName = "idx_" + tblName + "_" + colNameArray.join("_");
delete tblData[idxName];
return executeSimpleSQL(db, "DROP INDEX IF EXISTS " + idxName);
}
/**
* Sets a specific key on the table, ie foreign or primary key.
*
* @param tblData The table data object to set the key on
* @param tblName The table to set the key on
* @param keyName The name of the key, i.e "PRIMARY KEY"
* @param keyData The data of the key, i.e "(cal_id, id")"
* @param db (optional) The database to create the index on.
*/
function addKey(tblData, tblName, keyName, keyData, db) {
let keyName = "key_" + keyName;
tblData[tblName][keyName] = keyData;
// alterTypes recreates the table, doing so without changing types will
// allow updating the key.
alterTypes(tblData, tblName, [], null, db);
}
/**
* Often in an upgrader we want to log something only if there is a database. To
* make code less cludgy, here a helper function.
@ -361,46 +402,39 @@ function reportErrorAndRollback(db, e) {
function ensureUpdatedTimezones(db) {
// check if timezone version has changed:
let selectTzVersion = createStatement(db, "SELECT version FROM cal_tz_version LIMIT 1");
let tzServiceVersion = cal.getTimezoneService().version;
let tzs = cal.getTimezoneService();
let version;
try {
version = (selectTzVersion.executeStep() ? selectTzVersion.row.version : null);
let step = selectTzVersion.executeStep();
version = (step ? selectTzVersion.row.version : null);
} finally {
selectTzVersion.reset();
selectTzVersion.finalize();
}
let versionComp = 1;
if (version) {
versionComp = Services.vc.compare(tzServiceVersion, version);
}
let versionComp = (version ? Services.vc.compare(tzs.version, version) : 1);
if (versionComp != 0) {
cal.LOG("[calStorageCalendar] Timezones have been changed from " + version + " to " + tzServiceVersion + ", updating calendar data.");
cal.ERROR("[calStorageCalendar] Timezones have been changed from " +
version + " to " + tzs.version + ", updating calendar " +
"data. This might take a moment...");
let zonesToUpdate = [];
let zonesToUpdate = {};
let getZones = createStatement(db,
"SELECT DISTINCT(zone) FROM ("+
"SELECT recurrence_id_tz AS zone FROM cal_attendees WHERE recurrence_id_tz IS NOT NULL UNION " +
"SELECT recurrence_id_tz AS zone FROM cal_events WHERE recurrence_id_tz IS NOT NULL UNION " +
"SELECT event_start_tz AS zone FROM cal_events WHERE event_start_tz IS NOT NULL UNION " +
"SELECT event_end_tz AS zone FROM cal_events WHERE event_end_tz IS NOT NULL UNION " +
"SELECT recurrence_id_tz AS zone FROM cal_properties WHERE recurrence_id_tz IS NOT NULL UNION " +
"SELECT recurrence_id_tz AS zone FROM cal_todos WHERE recurrence_id_tz IS NOT NULL UNION " +
"SELECT todo_entry_tz AS zone FROM cal_todos WHERE todo_entry_tz IS NOT NULL UNION " +
"SELECT todo_due_tz AS zone FROM cal_todos WHERE todo_due_tz IS NOT NULL UNION " +
"SELECT recurrence_id_tz AS zone FROM cal_alarms WHERE recurrence_id_tz IS NOT NULL UNION " +
"SELECT recurrence_id_tz AS zone FROM cal_relations WHERE recurrence_id_tz IS NOT NULL UNION " +
"SELECT recurrence_id_tz AS zone FROM cal_attachments WHERE recurrence_id_tz IS NOT NULL" +
"SELECT DISTINCT(zone) FROM ("+
"SELECT event_start_tz AS zone FROM cal_events WHERE event_start_tz IS NOT NULL UNION " +
"SELECT event_end_tz AS zone FROM cal_events WHERE event_end_tz IS NOT NULL UNION " +
"SELECT todo_entry_tz AS zone FROM cal_todos WHERE todo_entry_tz IS NOT NULL UNION " +
"SELECT todo_due_tz AS zone FROM cal_todos WHERE todo_due_tz IS NOT NULL" +
");");
try {
while (getZones.executeStep()) {
let zone = getZones.row.zone;
// Send the timezones off to the timezone service to attempt conversion:
let tz = getTimezone(zone);
let tz = tzs.getTimezone(zone);
if (tz) {
let refTz = cal.getTimezoneService().getTimezone(tz.tzid);
let refTz = tzs.getTimezone(tz.tzid);
if (refTz && refTz.tzid != zone) {
zonesToUpdate.push({ oldTzId: zone, newTzId: refTz.tzid });
zonestoUpdate[zone] = refTz;
}
}
}
@ -409,36 +443,215 @@ function ensureUpdatedTimezones(db) {
"\nDB Error " + lastErrorString(db));
} finally {
getZones.reset();
getZones.finalize();
}
createFunction(db, "updateTimezone", 2, {
onFunctionCall: function(storArgs) {
try {
let [icalString, componentType] = mapStorageArgs(storArgs);
let item;
if (componentType == "VEVENT") {
item = cal.createEvent(icalString);
} else if (componentType == "VTODO") {
item = cal.createTodo(icalString);
} else {
item = { icalString: icalString };
}
let start = item[cal.calGetStartDateProp(item)];
let end = item[cal.calGetEndDateProp(item)];
if (start && start.timezone && start.timezone.tzid in zonesToUpdate) {
start.timezone = zonesToUpdate[start.timezone.tzid];
}
if (end && end.timezone && end.timezone.tzid in zonesToUpdate) {
end.timezone = zonesToUpdate[end.timezone.tzid];
}
return item.icalString;
} catch (e) {
cal.ERROR("Error updating timezone: " + e);
throw e;
}
}
});
let updateZones = [ k for (k in zonesToUpdate) ];
if (updateZones.length) {
updateZones = '("' + updateZones.join('","') + '")';
} else {
updateZones = null;
}
beginTransaction(db);
try {
for each (let update in zonesToUpdate) {
beginTransaction(db);
if (updateZones) {
executeSimpleSQL(db,
"UPDATE cal_attendees SET recurrence_id_tz = '" + update.newTzId + "' WHERE recurrence_id_tz = '" + update.oldTzId + "'; " +
"UPDATE cal_events SET recurrence_id_tz = '" + update.newTzId + "' WHERE recurrence_id_tz = '" + update.oldTzId + "'; " +
"UPDATE cal_events SET event_start_tz = '" + update.newTzId + "' WHERE event_start_tz = '" + update.oldTzId + "'; " +
"UPDATE cal_events SET event_end_tz = '" + update.newTzId + "' WHERE event_end_tz = '" + update.oldTzId + "'; " +
"UPDATE cal_properties SET recurrence_id_tz = '" + update.newTzId + "' WHERE recurrence_id_tz = '" + update.oldTzId + "'; " +
"UPDATE cal_todos SET recurrence_id_tz = '" + update.newTzId + "' WHERE recurrence_id_tz = '" + update.oldTzId + "'; " +
"UPDATE cal_todos SET todo_entry_tz = '" + update.newTzId + "' WHERE todo_entry_tz = '" + update.oldTzId + "'; " +
"UPDATE cal_todos SET todo_due_tz = '" + update.newTzId + "' WHERE todo_due_tz = '" + update.oldTzId + "'; " +
"UPDATE cal_alarms SET recurrence_id_tz = '" + update.newTzId + "' WHERE recurrence_id_tz = '" + update.oldTzId + "'; " +
"UPDATE cal_relations SET recurrence_id_tz = '" + update.newTzId + "' WHERE recurrence_id_tz = '" + update.oldTzId + "'; " +
"UPDATE cal_attachments SET recurrence_id_tz = '" + update.newTzId + "' WHERE recurrence_id_tz = '" + update.oldTzId + "';");
"UPDATE cal_item_base" +
" SET icalString = updateTimezone(icalString,componentType)" +
" WHERE componentType = 'VEVENT'" +
" AND EXISTS (SELECT 1" +
" FROM cal_events AS e" +
" WHERE (event_start_tz IN " + updateZones +
" OR event_end_tz IN " + updateZones + ")" +
" AND e.cal_id = cal_item_base.cal_id" +
" AND e.id = cal_item_base.item_id)"
);
executeSimpleSQL(db,
"UPDATE cal_item_base" +
" SET icalString = updateTimezone(icalString,componentType)" +
" WHERE componentType = 'VTODO'" +
" AND EXISTS (SELECT 1" +
" FROM cal_todos AS t" +
" WHERE (todo_entry_tz IN " + updateZones +
" OR todo_due_tz IN " + updateZones +
" OR todo_completed_tz IN " + updateZones + ")" +
" AND t.cal_id = cal_item_base.cal_id" +
" AND t.id = cal_item_base.item_id)"
);
}
executeSimpleSQL(db, "DELETE FROM cal_tz_version; " +
"INSERT INTO cal_tz_version VALUES ('" +
cal.getTimezoneService().version + "');");
tzs.version + "');");
commitTransaction(db);
} catch (e) {
cal.ASSERT(false, "Timezone update failed! DB Error: " + lastErrorString(db));
cal.ERROR("Timezone update failed! DB Error: " + lastErrorString(db));
rollbackTransaction(db);
throw e;
}
}
}
function migrateStorageSDB() {
// First, we need to check if this is from 0.9, i.e we need to migrate from
// storage.sdb to local.sqlite.
const nILF = Components.interfaces.nsILocalFile;
const mISC = Components.interfaces.mozIStorageConnection;
let storageSdb = Services.dirsvc.get("ProfD", nILF);
storageSdb.append("storage.sdb");
let sdb = Services.storage.openDatabase(storageSdb);
if (sdb.tableExists("cal_events")) {
cal.LOG("[calStorageCalendar] Migrating storage.sdb -> local.sqlite");
upgradeDB(sdb); // upgrade schema before migating data
let attachStmt = sdb.createStatement(
"ATTACH DATABASE :file_path AS local_sqlite"
);
this.runStatement(attachStmt, {
handleInit: function(params) {
params.file_path = localDB.databaseFile.path;
}
}, "prepareInitDB attachStatement.execute exception", true);
attachStmt.finalize();
try {
sdb.beginTransactionAs(mISC.TRANSACTION_EXCLUSIVE);
try {
if (sdb.tableExists("cal_events")) { // check again (with lock)
// take over data and drop from storage.sdb tables:
for (let table in getSqlTable(DB_SCHEMA_VERSION)) {
if (table.substr(0, 4) != "idx_") {
let sql = " CREATE TABLE local_sqlite." + table + " AS" +
" SELECT * FROM " + table + ";" +
" DROP TABLE IF EXISTS " + table;
sdb.executeSimpleSQL(sql);
}
}
sdb.commitTransaction();
} else { // migration done in the meantime
sdb.rollbackTransaction();
}
} catch (exc) {
cal.ERROR("prepareInitDB storage.sdb migration exception" + exc);
sdb.rollbackTransaction();
throw exc;
}
} finally {
this.mDB.executeSimpleSQL("DETACH DATABASE local_sqlite");
}
}
}
/**
* Migrate moz-profile-calendar:// to moz-storage-calendar://. This is needed
* due to bug 479867 and its regression bug 561735. The first calendar created
* before v19 already has a moz-profile-calendar:// uri without an ?id=
* parameters (the id in the database is 0). We need to migrate this special
* calendar differently.
*
* @param db The database to operatie on
* @param calendar The calendar to set migration data on
*/
function migrateURLFormat(db, calendar) {
function migrateTables(newCalId, oldCalId) {
for each (let tbl in ["cal_item_base", "cal_events", "cal_todos"]) {
let stmt;
try {
stmt = db.createStatement("UPDATE " + tbl +
" SET cal_id = :cal_id" +
" WHERE cal_id = :old_cal_id");
stmt.params.cal_id = newCalId;
stmt.params.old_cal_id = oldCalId;
stmt.executeStep();
} catch (e) {
// Pass error through to enclosing try/catch block
throw e;
} finally {
if (stmt) {
stmt.reset();
stmt.finalize();
}
}
}
}
db.beginTransactionAs(mISC.TRANSACTION_EXCLUSIVE);
try {
let id = 0;
let path = calendar.uri.path;
let pos = path.indexOf("?id=");
if (pos != -1) {
// There is an "id" parameter in the uri. This calendar
// has not been migrated to using the uuid as its cal_id.
pos = calendar.uri.path.indexOf("?id=");
if (pos != -1) {
cal.LOG("[calStorageCalendar] Migrating numeric cal_id to uuid");
id = parseInt(path.substr(pos + 4), 10);
migrateTables(calendar.id, id);
// Now remove the id from the uri to make sure we don't do this
// again. Remeber the id, so we can recover in case something
// goes wrong.
calendar.setProperty("uri", "moz-storage-calendar://");
calendar.setProperty("old_calendar_id", id);
db.commitTransaction();
} else {
db.rollbackTransaction();
}
} else {
// For some reason, the first storage calendar before the
// v19 upgrade has cal_id=0. If we still have a
// moz-profile-calendar here, then this is the one and we
// need to move all events with cal_id=0 to this id.
cal.LOG("[calStorageCalendar] Migrating stray cal_id=0 calendar to uuid");
migrateTables(db, calendar.id, 0);
calendar.setProperty("uri", "moz-storage-calendar://");
calendar.setProperty("old_calendar_id", 0);
db.commitTransaction();
}
} catch (exc) {
cal.ERROR("prepareInitDB moz-profile-calendar migration exception" + exc);
db.rollbackTransaction();
throw exc;
}
}
/**
* Adds a column to the given table.
*
@ -471,7 +684,7 @@ function deleteColumns(tblData, tblName, colNameArray, db) {
delete tblData[tblName][colName];
}
let columns = [ k for (k in tblData[tblName]) ];
let columns = [ k for (k in tblData[tblName]) if (k.substr(0, 4) != "key_") ];
executeSimpleSQL(db, getSql(tblName, tblData, tblName + "_temp"));
executeSimpleSQL(db, "INSERT INTO " + tblName + "_temp" +
" (" + columns.join(",") + ") " +
@ -499,7 +712,7 @@ function copyTable(tblData, tblName, newTblName, db, condition, selectOptions) {
tblData[newTblName] = objcopy(tblData[tblName]);
let columns = [ k for (k in tblData[newTblName]) ];
let columns = [ k for (k in tblData[tblName]) if (k.substr(0, 4) != "key_") ];
executeSimpleSQL(db, getSql(newTblName, tblData));
executeSimpleSQL(db, "INSERT INTO " + newTblName +
" (" + columns.join(",") + ") " +
@ -523,7 +736,7 @@ function alterTypes(tblData, tblName, colNameArray, newType, db) {
tblData[tblName][colName] = newType;
}
let columns = [ k for (k in tblData[tblName]) ];
let columns = [ k for (k in tblData[tblName]) if (k.substr(0, 4) != "key_") ];
executeSimpleSQL(db, getSql(tblName, tblData, tblName + "_temp"));
executeSimpleSQL(db, "INSERT INTO " + tblName + "_temp" +
" (" + columns.join(",") + ") " +
@ -1021,6 +1234,7 @@ upgrade.v13 = function upgrade_v13(db, version) {
}
finally {
stmt.reset();
stmt.finalize();
}
}
}
@ -1428,6 +1642,9 @@ upgrade.v21 = function upgrade_v21(db, version) {
updateStmt.execute();
} while (db.affectedRows > 0);
insertStmt.finalize();
updateStmt.finalize();
// Finally we can delete the x-dateset rows. Note this will leave
// gaps in recur_index, but thats ok since its only used for
// ordering anyway and will be overwritten on the next item write.
@ -1631,3 +1848,386 @@ upgrade.v22 = function upgrade_v22(db, version) {
}
return tbl;
};
upgrade.v23 = function upgrade_v23(db, version) {
let tbl = upgrade.v22(version < 22 && db, version);
LOGdb(db, "Storage: Upgrading to v23, this may take a while...");
beginTransaction(db);
try {
addTable(tbl, "cal_item_base", {
cal_id: "TEXT",
item_id: "TEXT",
flags: "INTEGER",
offline_journal: "INTEGER",
componentType: "TEXT",
icalString: "TEXT",
"key_PRIMARY KEY": "(cal_id, item_id)"
}, db);
if (db) {
function createItemFunction(itemGetter, currentCols, name) {
return {
onFunctionCall: function translateItem(storArgs) {
try {
let row = {};
let mappedArgs = mapStorageArgs(storArgs);
for (let i in currentCols) {
row[currentCols[i]] = mappedArgs[i];
}
let item = itemGetter(db, row, false);
return item.icalString;
} catch (e) {
cal.ERROR("Error converting " + name + ":" + e);
throw e;
}
}
};
}
let eventCols = [ k for (k in tbl.cal_events) ];
let eventFunction = createItemFunction(getEventFromRow, eventCols, "event");
createFunction(db, "translateEvent", eventCols.length, eventFunction);
let insertEventSQL = "INSERT INTO cal_item_base" +
" (cal_id, item_id, offline_journal, flags, componentType, icalString) " +
"SELECT cal_id, id, offline_journal, flags, 'VEVENT', " +
" translateEvent(" + eventCols.join(",") + ")" +
" FROM cal_events" +
" WHERE recurrence_id IS NULL";
executeSimpleSQL(db, insertEventSQL);
let todoCols = [ k for (k in tbl.cal_todos) ];
let todoFunction = createItemFunction (getTodoFromRow, todoCols, "todo");
createFunction(db, "translateTask", todoCols.length, todoFunction);
let insertTaskSQL = "INSERT INTO cal_item_base" +
" (cal_id, item_id, offline_journal, flags, componentType, icalString) " +
"SELECT cal_id, id, offline_journal, flags, 'VTODO', " +
" translateTask(" + todoCols.join(",") + ")" +
" FROM cal_todos" +
" WHERE recurrence_id IS NULL";
executeSimpleSQL(db, insertTaskSQL);
}
// Delete the columns we no longer use or have moved to the base table
deleteColumns(tbl, "cal_events", [
"time_created", "last_modified", "title", "priority", "privacy",
"ical_status", "flags", "event_stamp", "recurrence_id",
"recurrence_id_tz", "alarm_last_ack", "offline_journal"
], db);
deleteColumns(tbl, "cal_todos", [
"time_created", "last_modified", "title", "priority", "privacy",
"ical_status", "flags", "todo_stamp", "recurrence_id",
"recurrence_id_tz", "alarm_last_ack", "todo_complete",
"offline_journal"
], db);
// These tables are not needed anymore
let dropTbls = ["cal_alarms", "cal_attachments", "cal_attendees",
"cal_properties", "cal_recurrence", "cal_relations"];
dropTbls.forEach(function(x) dropTable(tbl, x, db));
// We have a shitload of indexes we need to get rid of
let simpleIds = ["cal_id", "item_id"];
let allIds = simpleIds.concat(["recurrence_id", "recurrence_id_tz"]);
// Alarms, Attachments, Attendees, Relations
for each (let tblName in ["alarms", "attachments", "attendees", "relations"]) {
deleteIndex(tbl, "cal_" + tblName, allIds, db);
}
// Events and Tasks. The real index is taken care of by FOREIGN KEY
for each (let tblName in ["events", "todos"]) {
deleteIndex(tbl, "cal_" + tblName, ["flags", "cal_id", "recurrence_id"], db);
deleteIndex(tbl, "cal_" + tblName, ["id", "cal_id", "recurrence_id"], db);
}
// Metadata, properties, recurrence
deleteIndex(tbl, "cal_metadata", simpleIds, db);
deleteIndex(tbl, "cal_properties", allIds, db);
deleteIndex(tbl, "cal_recurrence", simpleIds, db);
// Add foreign keys to events and tasks tables
addKey(tbl, "cal_events", "FOREIGN KEY", "(cal_id, id) " +
"REFERENCES cal_item_base(cal_id, item_id) ON DELETE CASCADE", db);
addKey(tbl, "cal_todos", "FOREIGN KEY", "(cal_id, id) " +
"REFERENCES cal_item_base(cal_id, item_id) ON DELETE CASCADE", db);
addKey(tbl, "cal_metadata", "PRIMARY KEY", "(cal_id, item_id) ", db);
setDbVersionAndCommit(db, 23);
} catch (e) {
throw reportErrorAndRollback(db, e);
}
return tbl;
};
// SPECIAL FUNCTIONS FOR UPGRADE V23 //
function setIf(row, item, itemAttr, rowAttr) {
rowAttr = rowAttr || itemAttr;
if (row[rowAttr]) {
item[itemAttr] = row[rowAttr];
}
}
function setIfDT(row, item, itemAttr, rowAttr, timezone) {
rowAttr = rowAttr || itemAttr;
if (row[rowAttr]) {
item[itemAttr] = newDateTime(row[rowAttr], timezone);
}
}
function setIfPropDT(row, item, itemProp, rowAttr, timezone) {
if (row[rowAttr]) {
item.setProperty(itemProp, newDateTime(row[rowAttr], timezone));
}
}
function getEventFromRow(db, row, isException) {
let item = cal.createEvent();
let setIfEvent = setIf.bind(null, row, item);
let setIfEventDT = setIfDT.bind(null, row, item);
let setIfEventPropDT = setIfPropDT.bind(null, row, item);
setIfEventDT("startDate", "event_start", row.event_start_tz);
setIfEventDT("endDate", "event_end", row.event_end_tz);
setIfEventPropDT("DTSTAMP", "event_stamp", "UTC");
if ((row.flags & CAL_ITEM_FLAG.EVENT_ALLDAY) != 0) {
item.startDate.isDate = true;
item.endDate.isDate = true;
}
// This must be done last to keep the modification time intact.
getItemBaseFromRow(db, row, item);
getAdditionalDataForItem(db, row, item);
return item;
}
function getTodoFromRow(db, row, isException) {
let item = cal.createTodo();
let setIfTodo = setIf.bind(null, row, item);
let setIfTodoDT = setIfDT.bind(null, row, item);
let setIfTodoPropDT = setIfPropDT.bind(null, row, item);
setIfTodoDT("entryDate", "todo_entry", row.todo_entry_tz);
setIfTodoDT("dueDate", "todo_due", row.todo_due_tz);
setIfTodoPropDT("DTSTAMP", "todo_stamp", "UTC");
setIfTodoDT("completedDate", "todo_completed", row.todo_completed_tz);
setIfTodo("percentComplete", "todo_complete");
// This must be done last to keep the modification time intact.
getItemBaseFromRow(db, row, item);
getAdditionalDataForItem(db, row, item);
return item;
}
function getItemBaseFromRow(db, row, item) {
let setIfBase = setIf.bind(null, row, item);
let setIfBaseDT = setIfDT.bind(null, row, item);
let setIfBasePropDT = setIfPropDT.bind(null, row, item);
item.id = row.id;
setIfBase("title");
setIfBase("priority");
setIfBase("privacy");
setIfBase("status", "ical_status");
setIfBaseDT("alarmLastAck", "alarm_last_ack", "UTC");
setIfBaseDT("recurrenceId", "recurrence_id", row.recurrence_id_tz);
if (row.recurrence_id && ((row.flags & CAL_ITEM_FLAG.RECURRENCE_ID_ALLDAY) != 0)) {
item.recurrenceId.isDate = true;
}
setIfBasePropDT("CREATED", "time_created", "UTC");
setIfBasePropDT("LAST-MODIFIED", "last_modified", "UTC");
}
function getAdditionalDataForItem(db, baseRow, item) {
function runStatement(occurrenceSQL, masterSQL, walker) {
let stmt;
if (item.recurrenceId) {
if (!occurrenceSQL) {
throw Components.results.NS_ERROR_UNEXPECTED;
}
stmt = createStatement(db, occurrenceSQL);
setDateParamHelper(stmt.params, "recurrence_id", item.recurrenceId);
} else {
if (!masterSQL) {
throw Components.results.NS_ERROR_UNEXPECTED;
}
stmt = createStatement(db, masterSQL);
}
stmt.params.cal_id = baseRow.cal_id;
stmt.params.item_id = item.id;
try {
while (stmt.executeStep()) {
walker(stmt.row);
}
} catch (e) {
cal.ERROR("Error getting additional data for item '" +
item.title + "' (" + item.id + "):" + e);
} finally {
stmt.reset();
stmt.finalize();
}
}
function standardOccurrenceSQL(tbl) {
return "SELECT * FROM " + tbl +
" WHERE item_id = :item_id" +
" AND cal_id = :cal_id" +
" AND recurrence_id = :recurrence_id" +
" AND recurrence_id_tz = :recurrence_id_tz";
}
function standardMasterSQL(tbl) {
return "SELECT * FROM " + tbl +
" WHERE item_id = :item_id" +
" AND cal_id = :cal_id" +
" AND recurrence_id IS NULL";
}
// This is needed to keep the modification time intact.
let savedLastModifiedTime = item.lastModifiedTime;
let flags = baseRow.flags;
if (flags & CAL_ITEM_FLAG.HAS_ATTENDEES) {
runStatement(
standardOccurrenceSQL("cal_attendees"),
standardMasterSQL("cal_attendees"),
function iterateAttendeesRow(row) {
let attendee = cal.createAttendee(row.icalString);
if (attendee && attendee.id) {
if (attendee.isOrganizer) {
item.organizer = attendee;
} else {
item.addAttendee(attendee);
}
}
}
);
}
if (flags & CAL_ITEM_FLAG.HAS_PROPERTIES) {
runStatement(
standardOccurrenceSQL("cal_properties"),
standardMasterSQL("cal_properties"),
function iteratePropertiesRow(row) {
switch (row.key) {
case "DURATION":
// for events DTEND/DUE is enforced by
// calEvent/calTodo, so suppress DURATION:
break;
case "CATEGORIES": {
let cats = cal.categoriesStringToArray(row.value);
item.setCategories(cats.length, cats);
break;
}
default:
item.setProperty(row.key, row.value);
break;
}
}
);
}
if (flags & CAL_ITEM_FLAG.HAS_RECURRENCE) {
let recInfo = cal.createRecurrenceInfo(item);
item.recurrenceInfo = recInfo;
runStatement(null,
/* Statement for parent items */
"SELECT * FROM cal_recurrence" +
" WHERE item_id = :item_id" +
" AND cal_id = :cal_id",
/* Function called to iterate rows */
function(row) {
let ritem;
let prop = cal.getIcsService().createIcalPropertyFromString(row.icalString);
switch (prop.propertyName) {
case "RDATE":
case "EXDATE":
ritem = Components.classes["@mozilla.org/calendar/recurrence-date;1"]
.createInstance(Components.interfaces.calIRecurrenceDate);
break;
case "RRULE":
case "EXRULE":
ritem = cal.createRecurrenceRule();
break;
default:
throw "Unknown recurrence item: " + prop.propertyName;
break;
}
ritem.icalProperty = prop;
recInfo.appendRecurrenceItem(ritem);
}
);
}
if (flags & CAL_ITEM_FLAG.HAS_EXCEPTIONS) {
runStatement(null,
/* Statement for parent items */
"SELECT * FROM cal_events" +
" WHERE id = :item_id" +
" AND cal_id = :cal_id" +
" AND recurrence_id IS NOT NULL",
function(row) {
let exc = getEventFromRow(db, row, true /*isException*/);
item.recurrenceInfo.modifyException(exc, true);
}
);
runStatement(null,
/* Statement for parent items */
"SELECT * FROM cal_todos" +
" WHERE id = :item_id" +
" AND cal_id = :cal_id" +
" AND recurrence_id IS NOT NULL",
/* Function called to iterate rows */
function(row) {
let exc = getTodoFromRow(db, row, true /*isException*/);
item.recurrenceInfo.modifyException(exc, true);
}
);
}
if (flags & CAL_ITEM_FLAG.HAS_ATTACHMENTS) {
runStatement(
standardOccurrenceSQL("cal_attachments"),
standardMasterSQL("cal_attachments"),
function iterateAttachmentsRow(row) {
let attach = cal.createAttachment(row.icalString);
item.addAttachment(attach);
}
);
}
if (flags & CAL_ITEM_FLAG.HAS_RELATIONS) {
runStatement(
standardOccurrenceSQL("cal_relations"),
standardMasterSQL("cal_relations"),
function iterateRelationsRow(row) {
let relation = cal.createRelation(row.icalString);
item.addRelation(relation);
}
);
}
if (flags & CAL_ITEM_FLAG.HAS_ALARMS) {
runStatement(
standardOccurrenceSQL("cal_alarms"),
standardMasterSQL("cal_alarms"),
function iterateAlarmsRow(row) {
let alarm = cal.createAlarm(row.icalString);
item.addAlarm(alarm);
}
);
}
// Restore the saved modification time
item.setProperty("LAST-MODIFIED", savedLastModifiedTime);
}

Просмотреть файл

@ -0,0 +1,72 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
function run_test() {
var f = cal.createEvent();
var str =
["BEGIN:VCALENDAR",
"PRODID:-//RDU Software//NONSGML HandCal//EN",
"VERSION:2.0",
"BEGIN:VTIMEZONE",
"TZID:America/New_York",
"BEGIN:STANDARD",
"DTSTART:19981025T020000",
"TZOFFSETFROM:-0400",
"TZOFFSETTO:-0500",
"TZNAME:EST",
"END:STANDARD",
"BEGIN:DAYLIGHT",
"DTSTART:19990404T020000",
"TZOFFSETFROM:-0500",
"TZOFFSETTO:-0400",
"TZNAME:EDT",
"END:DAYLIGHT",
"END:VTIMEZONE",
"BEGIN:VEVENT",
"DTSTAMP:19980309T231000Z",
"UID:guid-1.example.com",
"ORGANIZER:mailto:mrbig@example.com",
"ATTENDEE;RSVP=TRUE;ROLE=REQ-PARTICIPANT;CUTYPE=GROUP:",
" mailto:employee-A@example.com",
"DESCRIPTION:Project XYZ Review Meeting",
"CATEGORIES:MEETING",
"CLASS:PUBLIC",
"CREATED:19980309T130000Z",
"SUMMARY:XYZ Project Review",
"DTSTART;TZID=America/New_York:19980312T083000",
"DTEND;TZID=America/New_York:19980312T093000",
"LOCATION:1CP Conference Room 4350",
"END:VEVENT",
"END:VCALENDAR",].join("\r\n");
var strTz =
["BEGIN:VTIMEZONE",
"TZID:America/New_York",
"BEGIN:STANDARD",
"DTSTART:19981025T020000",
"TZOFFSETFROM:-0400",
"TZOFFSETTO:-0500",
"TZNAME:EST",
"END:STANDARD",
"BEGIN:DAYLIGHT",
"DTSTART:19990404T020000",
"TZOFFSETFROM:-0500",
"TZOFFSETTO:-0400",
"TZNAME:EDT",
"END:DAYLIGHT",
"END:VTIMEZONE",].join("\r\n");
let tzs = cal.getTimezoneService();
f.icalString = str;
let g = f.startDate;
let h = f.endDate;
g.timezone = tzs.getTimezone(g.timezone.tzid);
h.timezone = tzs.getTimezone(h.timezone.tzid);
do_check_neq(strTz,g.timezone.toString());
}

Просмотреть файл

@ -35,6 +35,7 @@ run-sequentially = Avoid bustage.
[test_search_service.js]
[test_startup_service.js]
[test_storage.js]
[test_timezone.js]
[test_utils.js]
[test_calmgr.js]
[test_deleted_items.js]