Skip to content

Instantly share code, notes, and snippets.

@creativepsyco
Created June 4, 2011 19:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save creativepsyco/1008250 to your computer and use it in GitHub Desktop.
Save creativepsyco/1008250 to your computer and use it in GitHub Desktop.
calcachedCalendar [First version]
/* ***** 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 Sun Microsystems, Inc. code.
*
* The Initial Developers of the Original Code are
* Philipp Kewisch <mozilla@kewis.ch>
* Daniel Boelzle <daniel.boelzle@sun.com>
* Portions created by the Initial Developer are Copyright (C) 2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* 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 ***** */
Components.utils.import("resource://calendar/modules/calProviderUtils.jsm");
function calCachedCalendarObserverHelper(home, isCachedObserver) {
this.home = home;
this.isCachedObserver = isCachedObserver;
}
calCachedCalendarObserverHelper.prototype = {
isCachedObserver: false,
onStartBatch: function() {
this.home.mObservers.notify("onStartBatch");
},
onEndBatch: function() {
this.home.mObservers.notify("onEndBatch");
},
onLoad: function(calendar) {
if (this.isCachedObserver) {
this.home.mObservers.notify("onLoad", [this.home]);
} else {
// start sync action after uncached calendar has been loaded.
// xxx todo, think about:
// although onAddItem et al have been called, we need to fire
// an additional onLoad completing the refresh call (->composite)
var home = this.home;
home.synchronize(
function(status) {
home.mObservers.notify("onLoad", [home]);
});
}
},
onAddItem: function(aItem) {
if (this.isCachedObserver) {
this.home.mObservers.notify("onAddItem", arguments);
}
},
onModifyItem: function(aNewItem, aOldItem) {
if (this.isCachedObserver) {
this.home.mObservers.notify("onModifyItem", arguments);
}
},
onDeleteItem: function(aItem) {
if (this.isCachedObserver) {
this.home.mObservers.notify("onDeleteItem", arguments);
}
},
onError: function(aCalendar, aErrNo, aMessage) {
this.home.mObservers.notify("onError", arguments);
},
onPropertyChanged: function(aCalendar, aName, aValue, aOldValue) {
if (!this.isCachedObserver) {
this.home.mObservers.notify("onPropertyChanged", [this.home, aName, aValue, aOldValue]);
}
},
onPropertyDeleting: function(aCalendar, aName) {
if (!this.isCachedObserver) {
this.home.mObservers.notify("onPropertyDeleting", [this.home, aName]);
}
}
};
function calCachedCalendar(uncachedCalendar) {
this.wrappedJSObject = this;
this.mSyncQueue = [];
this.mObservers = new cal.ObserverBag(Components.interfaces.calIObserver);
uncachedCalendar.superCalendar = this;
uncachedCalendar.addObserver(new calCachedCalendarObserverHelper(this, false));
this.mUncachedCalendar = uncachedCalendar;
this.setupCachedCalendar();
}
calCachedCalendar.prototype = {
QueryInterface: function cCC_QueryInterface(aIID) {
if (aIID.equals(Components.interfaces.calISchedulingSupport)) {
// check whether uncached calendar supports it:
if (this.mUncachedCalendar.QueryInterface(aIID)) {
return this;
}
}
return doQueryInterface(this, calCachedCalendar.prototype, aIID,
[Components.interfaces.calICalendar,
Components.interfaces.nsISupports]);
},
mCachedCalendar: null,
mCachedObserver: null,
mUncachedCalendar: null,
mObservers: null,
mSuperCalendar: null,
onCalendarUnregistering: function() {
if (this.mCachedCalendar) {
this.mCachedCalendar.removeObserver(this.mCachedObserver);
// Although this doesn't really follow the spec, we know the
// storage calendar's deleteCalendar method is synchronous.
// TODO put changes into a different calendar and delete
// afterwards.
this.mCachedCalendar.QueryInterface(Components.interfaces.calICalendarProvider)
.deleteCalendar(this.mCachedCalendar, null);
this.mCachedCalendar = null;
}
},
setupCachedCalendar: function cCC_setupCachedCalendar() {
try {
if (this.mCachedCalendar) { // this is actually a resetupCachedCalendar:
// Although this doesn't really follow the spec, we know the
// storage calendar's deleteCalendar method is synchronous.
// TODO put changes into a different calendar and delete
// afterwards.
this.mCachedCalendar.QueryInterface(Components.interfaces.calICalendarProvider)
.deleteCalendar(this.mCachedCalendar, null);
if (this.supportsChangeLog) {
// start with full sync:
this.mUncachedCalendar.resetLog();
}
} else {
let calType = getPrefSafe("calendar.cache.type", "storage");
// While technically, the above deleteCalendar should delete the
// whole calendar, this is nothing more than deleting all events
// todos and properties. Therefore the initialization can be
// skipped.
let cachedCalendar = Components.classes["@mozilla.org/calendar/calendar;1?type=" + calType]
.createInstance(Components.interfaces.calICalendar);
switch (calType) {
case "memory":
if (this.supportsChangeLog) {
// start with full sync:
this.mUncachedCalendar.resetLog();
}
break;
case "storage":
let file = getCalendarDirectory();
file.append("cache.sqlite");
cachedCalendar.uri = getIOService().newFileURI(file);
cachedCalendar.id = this.id;
break;
default:
throw new Error("unsupported cache calendar type: " + calType);
}
cachedCalendar.transientProperties = true;
cachedCalendar.setProperty("relaxedMode", true);
cachedCalendar.superCalendar = this;
if (!this.mCachedObserver) {
this.mCachedObserver = new calCachedCalendarObserverHelper(this, true);
}
cachedCalendar.addObserver(this.mCachedObserver);
this.mCachedCalendar = cachedCalendar;
}
} catch (exc) {
Components.utils.reportError(exc);
}
},
mPendingSync: null,
mSyncQueue: null,
synchronize: function cCC_synchronize(respFunc) {
this.mSyncQueue.push(respFunc);
if (this.mSyncQueue.length > 1) { // don't use mPendingSync here
LOG("[calCachedCalendar] sync in action/pending.");
return this.mPendingSync;
}
var this_ = this;
function emptyQueue(status) {
var queue = this_.mSyncQueue;
this_.mSyncQueue = [];
function execResponseFunc(func) {
try {
func(status);
} catch (exc) {
ASSERT(false, exc);
}
}
queue.forEach(execResponseFunc);
LOG("[calCachedCalendar] sync queue empty.");
var op = this_.mPendingSync;
this_.mPendingSync = null;
return op;
}
if (this.offline) {
return emptyQueue(Components.results.NS_OK);
}
if (this.supportsChangeLog) {
LOG("[calCachedCalendar] Doing changelog based sync for calendar " + this.uri.spec);
var opListener = {
thisCalendar : this,
onResult: function(op, result) {
if (!op || !op.isPending) {
var status = (op ? op.status : Components.results.NS_OK);
ASSERT(Components.isSuccessCode(status), "replay action failed: " + (op ? op.id : "<unknown>")+", uri=" + this.thisCalendar.uri.spec + ", result=" +result + ", op=" + op);
LOG("[calCachedCalendar] replayChangesOn finished.");
emptyQueue(status);
}
}
};
this.mPendingSync = this.mUncachedCalendar.replayChangesOn(this.mCachedCalendar, opListener);
return this.mPendingSync;
}
LOG("[calCachedCalendar] Doing full sync for calendar " + this.uri.spec);
// TODO put changes into a different calendar and delete
// afterwards.
var completeListener = {
hasRenewedCalendar: false,
onGetResult: function cCC_oOC_cL_onGetResult(aCalendar,
aStatus,
aItemType,
aDetail,
aCount,
aItems) {
if (Components.isSuccessCode(aStatus)) {
if (!this.hasRenewedCalendar) {
// TODO instead of deleting the calendar and creating a new
// one, maybe we want to do a "real" sync between the
// existing local calendar and the remote calendar.
this_.setupCachedCalendar();
this.hasRenewedCalendar = true;
}
for each (var item in aItems) {
this_.mCachedCalendar.addItem(item, null);
}
}
},
onOperationComplete: function cCC_oOC_cL_onOperationComplete(aCalendar,
aStatus,
aOpType,
aId,
aDetail) {
ASSERT(Components.isSuccessCode(aStatus), "getItems failed: " + aStatus);
emptyQueue(aStatus);
}
};
this.mPendingSync = this.mUncachedCalendar.getItems(Components.interfaces.calICalendar.ITEM_FILTER_ALL_ITEMS,
0, null, null, completeListener);
return this.mPendingSync;
},
onOfflineStatusChanged: function cCC_onOfflineStatusChanged(aNewState) {
var prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
if (aNewState) {
// Going offline: (XXX get items before going offline?) => we may ask the user to stay online a bit longer
} else {
// Going online (start replaying changes to the remote calendar)
//replaying changes
var this_ = this;
//prompts.alert(null,"hi",this.bOfflineSyncDone);
if(!this.bOfflineSyncDone)
{
//Need to refresh offline
//Check pending refreshes
let file = getCalendarDirectory();
//prompts.alert(null,"t",file);
file.append("ABCEvent.cache");
//this.syncEventuri = getIOService().newFileURI(file);
//prompts.alert(null,"t",file.path);
Components.utils.import("resource://gre/modules/NetUtil.jsm");
NetUtil.asyncFetch(file, function(inputStream, status) {
if (!Components.isSuccessCode(status)) {
// Handle error!
return;
}
// The file data is contained within inputStream.
var data = NetUtil.readInputStreamToString(inputStream, inputStream.available());
//prompts.alert(null,"hi",data);
//Real sync starts here
//Check for implementation of ICSparser
var icsParser = Components.classes["@mozilla.org/calendar/ics-parser;1"]
.createInstance(Components.interfaces.calIIcsParser);
icsParser.parseString(data,null,null);
//prompts.alert(null,"here","Parsed");
var ncount = {};
var itemsArray = [];
itemsArray = icsParser.getItems(ncount);
//var this_ = this;
item = itemsArray[0];
item.calendar = this_.mUncachedCalendar;
listener = null;
var opListener = {
onGetResult: function(calendar, status, itemType, detail, count, items) {
ASSERT(false, "unexpected!");
},
onOperationComplete: function(calendar, status, opType, id, detail) {
// prompts.alert(null,"yo",status);
if (Components.isSuccessCode(status)) {
this_.bOfflineSyncDone = true;
//this_.mCachedCalendar.deleteItem(item, listener);
} else if (listener) {
listener.onOperationComplete(this_, status, opType, id, detail);
}
}
}
//prompts.alert(null,"here","abc");
this_.deleteItem(item, opListener);
this_.refresh();
});
}
}
},
get superCalendar() {
return this.mSuperCalendar && this.mSuperCalendar.superCalendar || this;
},
set superCalendar(val) {
return (this.mSuperCalendar = val);
},
get offline() {
return getIOService().offline;
},
get supportsChangeLog() {
return calInstanceOf(this.mUncachedCalendar, Components.interfaces.calIChangeLog);
},
get canRefresh() { // enable triggering sync using the reload button
return true;
},
refresh: function() {
if (this.mUncachedCalendar.canRefresh && !this.offline) {
return this.mUncachedCalendar.refresh(); // will trigger synchronize once the calendar is loaded
} else {
var this_ = this;
return this.synchronize(
function(status) { // fire completing onLoad for this refresh call
this_.mCachedObserver.onLoad(this_.mCachedCalendar);
});
}
},
addObserver: function(aObserver) {
this.mObservers.add(aObserver);
},
removeObserver: function(aObserver) {
this.mObservers.remove(aObserver);
},
addItem: function(item, listener) {
return this.adoptItem(item.clone(), listener);
},
adoptItem: function(item, listener) {
if (this.offline) {
ASSERT(false, "unexpected!");
if (listener) {
listener.onOperationComplete(this, Components.interfaces.calIErrors.CAL_IS_READONLY,
Components.interfaces.calIOperation.ADD, null, null);
}
return null;
}
// Forwarding add/modify/delete to the cached calendar using the calIObserver
// callbacks would be advantageous, because the uncached provider could implement
// a true push mechanism firing without being triggered from within the program.
// But this would mean the uncached provider fires on the passed
// calIOperationListener, e.g. *before* it fires on calIObservers
// (because that order is undefined). Firing onOperationComplete before onAddItem et al
// would result in this facade firing onOperationComplete even though the modification
// hasn't yet been performed on the cached calendar (which happens in onAddItem et al).
// Result is that we currently stick to firing onOperationComplete if the cached calendar
// has performed the modification, see below:
var this_ = this;
var opListener = {
onGetResult: function(calendar, status, itemType, detail, count, items) {
ASSERT(false, "unexpected!");
},
onOperationComplete: function(calendar, status, opType, id, detail) {
if (Components.isSuccessCode(status)) {
this_.mCachedCalendar.addItem(detail, listener);
} else if (listener) {
listener.onOperationComplete(this_, status, opType, id, detail);
}
}
}
return this.mUncachedCalendar.adoptItem(item, opListener);
},
modifyItem: function(newItem, oldItem, listener) {
if (this.offline) {
ASSERT(false, "unexpected!");
if (listener) {
listener.onOperationComplete(this, Components.interfaces.calIErrors.CAL_IS_READONLY,
Components.interfaces.calIOperation.MODIFY, null, null);
}
return null;
}
// Forwarding add/modify/delete to the cached calendar using the calIObserver
// callbacks would be advantageous, because the uncached provider could implement
// a true push mechanism firing without being triggered from within the program.
// But this would mean the uncached provider fires on the passed
// calIOperationListener, e.g. *before* it fires on calIObservers
// (because that order is undefined). Firing onOperationComplete before onAddItem et al
// would result in this facade firing onOperationComplete even though the modification
// hasn't yet been performed on the cached calendar (which happens in onAddItem et al).
// Result is that we currently stick to firing onOperationComplete if the cached calendar
// has performed the modification, see below:
var this_ = this;
var opListener = {
onGetResult: function(calendar, status, itemType, detail, count, items) {
ASSERT(false, "unexpected!");
},
onOperationComplete: function(calendar, status, opType, id, detail) {
if (Components.isSuccessCode(status)) {
this_.mCachedCalendar.modifyItem(detail, oldItem, listener);
} else if (listener) {
listener.onOperationComplete(this_, status, opType, id, detail);
}
}
}
return this.mUncachedCalendar.modifyItem(newItem, oldItem, opListener);
},
syncEventuri : null,
bOfflineSyncDone : true,
deleteItem: function(item, listener) {
var prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
if(this.offline) //Do the necessary calculations
{
let _this = this;
//prompts.alert(null, "Title of this Dialog", item.icalString);
let file = getCalendarDirectory();
//prompts.alert(null,"t",file);
file.append("ABCEvent.cache");
this.syncEventuri = getIOService().newFileURI(file);
//prompts.alert(null,"t",file.path);
//writing something to the file
Components.utils.import("resource://gre/modules/NetUtil.jsm");
Components.utils.import("resource://gre/modules/FileUtils.jsm");
// file is nsIFile, data is a string
// You can also optionally pass a flags parameter here. It defaults to
// FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE;
var ostream = FileUtils.openSafeFileOutputStream(file)
var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].
createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
var istream = converter.convertToInputStream(item.icalString);
// The last argument (the callback) is optional.
NetUtil.asyncCopy(istream, ostream, function(status) {
if (!Components.isSuccessCode(status)) {
// Handle error!
return;
}
// Data has been written to the file.
//change the sync flag
_this.bOfflineSyncDone = false;
var tmpItem = item.clone();
tmpItem.calendar = _this.mCachedCalendar;
_this.mCachedCalendar.deleteItem(tmpItem,null);
});
}
else
{
// Forwarding add/modify/delete to the cached calendar using the calIObserver
// callbacks would be advantageous, because the uncached provider could implement
// a true push mechanism firing without being triggered from within the program.
// But this would mean the uncached provider fires on the passed
// calIOperationListener, e.g. *before* it fires on calIObservers
// (because that order is undefined). Firing onOperationComplete before onAddItem et al
// would result in this facade firing onOperationComplete even though the modification
// hasn't yet been performed on the cached calendar (which happens in onAddItem et al).
// Result is that we currently stick to firing onOperationComplete if the cached calendar
// has performed the modification, see below:
var this_ = this;
var opListener = {
onGetResult: function(calendar, status, itemType, detail, count, items) {
ASSERT(false, "unexpected!");
},
onOperationComplete: function(calendar, status, opType, id, detail) {
if (Components.isSuccessCode(status)) {
this_.mCachedCalendar.deleteItem(item, listener);
} else if (listener) {
listener.onOperationComplete(this_, status, opType, id, detail);
}
}
}
return this.mUncachedCalendar.deleteItem(item, opListener);
}
/*if (this.offline) {
ASSERT(false, "unexpected!");
if (listener) {
listener.onOperationComplete(this, Components.interfaces.calIErrors.CAL_IS_READONLY,
Components.interfaces.calIOperation.DELETE, null, null);
}
return null;
}*/
return null;
}
};
(function() {
function defineForwards(proto, targetName, functions, getters, gettersAndSetters) {
function defineForwardGetter(attr) {
proto.__defineGetter__(attr, function() { return this[targetName][attr]; });
}
function defineForwardGetterAndSetter(attr) {
defineForwardGetter(attr);
proto.__defineSetter__(attr, function(value) { return (this[targetName][attr] = value); });
}
function defineForwardFunction(funcName) {
proto[funcName] = function() {
var obj = this[targetName];
return obj[funcName].apply(obj, arguments);
};
}
functions.forEach(defineForwardFunction);
getters.forEach(defineForwardGetter);
gettersAndSetters.forEach(defineForwardGetterAndSetter);
}
defineForwards(calCachedCalendar.prototype, "mUncachedCalendar",
["getProperty", "setProperty", "deleteProperty",
"isInvitation", "getInvitedAttendee", "canNotify"],
["type"],
["id", "name", "uri", "readOnly"]);
defineForwards(calCachedCalendar.prototype, "mCachedCalendar",
["getItem", "getItems", "startBatch", "endBatch"], [], []);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment