Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Inherit from calStorageCalendar

25 views
Skip to first unread message

john.b...@googlemail.com

unread,
Apr 26, 2017, 2:40:45 PM4/26/17
to
Hi,

I am developing an AddOn, that creates calendars using:

let newCalendar = calManager.createCalendar("storage", cal.makeURL('moz-storage-calendar://'));

These calendars depend on external sources, at the moment they are only updated during load. I also would like to trigger an update, if the user clicks on the big lightning synchronize button.

If I understood correctly, I have to create a custom provider which returns true for canRefresh and implements refresh().


I have three questions:

1. How do I generate a unique ID for my provider, can I just pick one?

2. How do I inherit my custom provider from calStorageCalendar? I do not want to implement all the methods by myself, I want to use everything from the default StorageCalender. So if something changes in future versions of Thunderbird, I do not have to catch up.

3. Do I have to choose a different URL schema, or can I use "moz-storage-calendar://" as well?


This is what I use at the moment in my Manifest:

component {B99A6D64-2C89-11DC-9698-529656D89593} components/calMyCustomProvider.js
contract @mozilla.org/calendar/calendar;1?type=mycustomprovider {B99A6D64-2C89-11DC-9698-529656D89593}

And this is calMyCustomProvider.js:

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://calendar/modules/calUtils.jsm");

function calMyCustomProvider() {
this.initProviderBase();
}

calMyCustomProvider.prototype = {
__proto__: cal.ProviderBase.prototype,

classDescription: "My Custom Calendar Provider",
contractID: "@mozilla.org/calendar/calendar;1?type=mycustomprovider",
classID: Components.ID("{B99A6D64-2C89-11DC-9698-529656D89593}"),

QueryInterface: function cTBS_QueryInterface(aIID) {
if (!aIID.equals(Components.interfaces.nsISupports) &&
!aIID.equals(Components.interfaces.calICalendar)) {
throw Components.results.NS_ERROR_NO_INTERFACE;
}
return this;
},

get type() {
return "mycustomprovider";
},

get canRefresh() {
return true;
},

refresh: function cTBS_refresh() {
// DO SOMETHING HERE
// tell observers to reload everything
this.mObservers.notify("onLoad", [this]);
}
};

However, creating a calendar with:

let newCalendar = calManager.createCalendar("mycustomprovider", cal.makeURL('moz-storage-calendar://'));

gives a fatal error on calendar creation. Any hint on what I am doing wrong would be great.

Thanks,
John

Philipp Kewisch

unread,
Apr 26, 2017, 6:32:11 PM4/26/17
to john.b...@googlemail.com
Hi John,

what you want to do is implement a provider that forces use of the cache
("offline mode"). This way you will have a storage calendar available
for synchronization, but can implement the sync methods separately. I'd
suggest to look at the Provider for Google Calendar as a template.

Please check
https://dxr.mozilla.org/comm-central/source/calendar/providers/gdata for
the source code. Note that the directory structure does not match the
xpi directory structure, if you need that please look at the final xpi
from addons.mozilla.org.

To answer your questions specifically:

> 1. How do I generate a unique ID for my provider, can I just pick one?

This is usually a uuid, you can use cal.getUUID()

>
> 2. How do I inherit my custom provider from calStorageCalendar? I do not want to implement all the methods by myself, I want to use everything from the default StorageCalender. So if something changes in future versions of Thunderbird, I do not have to catch up.

See above, you should use an offline cached calendar.


>
> 3. Do I have to choose a different URL schema, or can I use "moz-storage-calendar://" as well?

You should use something different. I used googleapi:// for the Provider
for Google Calendar.

Philipp

john.b...@googlemail.com

unread,
Apr 27, 2017, 3:36:19 AM4/27/17
to
Thanks for your help! I can see in

https://dxr.mozilla.org/comm-central/source/calendar/providers/gdata/components/calGoogleCalendar.js

that you use this.mOfflineStorage to re-implement some of the interface methods:

getItem: function(aId, aListener) {
this.mOfflineStorage.getItem.apply(this.mOfflineStorage, arguments);
},

so I *have* to implement such pass-through-methods for

- getItem
- getItems
- deleteItem
- modifyItem
- adoptItem
- addItem

even though I do not want to add any functionality there?

The property getter and setter look different:

setProperty: function(aName, aValue) {
return this.__proto__.__proto__.setProperty.apply(this, arguments);
},

do I *have* to implement such pass-through-methods for the property setter and getter, even if I do not want to add any functionality there?

Is there any other place where I have to use this.mOfflineStorage in order to implement a provider that forces use of the cache ("offline mode")?

Thanks for your help,
John

Philipp Kewisch

unread,
Apr 27, 2017, 6:33:53 AM4/27/17
to john.b...@googlemail.com
On 4/27/17 9:36 AM, john.b...@googlemail.com wrote:
> Thanks for your help! I can see in
>
> https://dxr.mozilla.org/comm-central/source/calendar/providers/gdata/components/calGoogleCalendar.js
>
> that you use this.mOfflineStorage to re-implement some of the interface methods:
>
> getItem: function(aId, aListener) {
> this.mOfflineStorage.getItem.apply(this.mOfflineStorage, arguments);
> },
>
> so I *have* to implement such pass-through-methods for
>
> - getItem
> - getItems
> - deleteItem
> - modifyItem
> - adoptItem
> - addItem
>
> even though I do not want to add any functionality there?

We don't yet have a provider that relies fully on sync, i.e. always
adding to the local cache then sending updated items in replayChangesOn.
I don't know exactly what your provider does, but I would suggest to
implement deleteItem/modifyItem/adoptItem/addItem to directly add to the
remote calendar, as the Provider for Google Calendar does.

If you want to pass through to calProviderBase, then you don't need to
implement the method, if you want to pass to this.mOfflineStorage, then
you need to implement a pass through method.

You'll therefore need a passthrough for getItem/getItems.


>
> The property getter and setter look different:
>
> setProperty: function(aName, aValue) {
> return this.__proto__.__proto__.setProperty.apply(this, arguments);
> },
>
> do I *have* to implement such pass-through-methods for the property setter and getter, even if I do not want to add any functionality there?
As previously mentioned, you don't have to do this if you are passing
through to calProviderBase. If there is a getter/setter combination,
then you need to implement both the getter or setter even if you just
want to extend one of them. You don't need to implement either if you
are not planning to change them of course.


>
> Is there any other place where I have to use this.mOfflineStorage in order to implement a provider that forces use of the cache ("offline mode")?
I couldn't say off hand, I'd just start with a stripped down version of
the Provider for Google Calendar and implement things as you need them.

john.b...@googlemail.com

unread,
Apr 27, 2017, 8:07:38 AM4/27/17
to
Ok, I see that it would be helpful, if you knew what my AddOn (TbSync) is doing. It is a sync provider for Exchange Active Sync (EAS), it can sync contacts, calendars and I plan to add tasks and email sync as well.

The general concept is, that the user is not adding an EAS-calendar to lightning and an EAS-adressbook to the TB-adressbook, but that he just adds an EAS account to Thunderbird. This is done via a stand-alone TbSync Account Manager.

https://raw.githubusercontent.com/jobisoft/TbSync/master/screenshots/TbSync_005.png

The TbSync Account Manager lists all available folders for a given account and the user may subscribe to one or more folders and the AddOn will create local addressbooks / calendars as needed and fill them with the elements found on the server.

Local changes are logged via Observers (onItemAdd, onItemDelete, ...) and are added to a changelog and next time the user syncs, those changes will be send to the server. Lightning does not know anything about this sync.

As you can see, there is actually no need for a custom calendar provider. The only thing I want is to have a way, to init a TbSync-sync-process (which has nothing to do with lightning), if the user clicks on the Lightning-Sync button.

In my first post I wrote, that the calendar is only updated during load, that is not correct of course, but I did not want to make the introduction to complicated. The calendar gets updates everytime the user hits the sync button in the TbSync account Manager, or if autosync is enabled.

I was now able to get my custom provider running, it was missing the line with the XPCOMUtils.generateNSGetFactory. So this is my minimal provider now:


Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://calendar/modules/calProviderUtils.jsm");
Components.utils.import("resource://calendar/modules/calUtils.jsm");

/**
* calTbSyncCalendar
* This implements the calICalendar interface adapted to the TbSync Provider.
*
* @class
* @constructor
*/
function calTbSyncCalendar() {
this.initProviderBase();
}

var calTbSyncCalendarClassID = Components.ID("{7eb8f992-3956-4607-95ac-b860ebd51f5a}");
var calTbSyncCalendarInterfaces = [
Components.interfaces.calICalendar,
Components.interfaces.calIChangeLog
];
calTbSyncCalendar.prototype = {
// Inherit from calProviderBase for the the nice helpers
__proto__: cal.ProviderBase.prototype,

classID: calTbSyncCalendarClassID,
QueryInterface: XPCOMUtils.generateQI(calTbSyncCalendarInterfaces),
classInfo: XPCOMUtils.generateCI({
classDescription: "TbSync Calendar Provider",
contractID: "@mozilla.org/calendar/calendar;1?type=TbSync",
classID: calTbSyncCalendarClassID,
interfaces: calTbSyncCalendarInterfaces
}),

/*
* Implement calICalendar
*
* The following code is heavily inspired by the google calendar provider.
* See http://mxr.mozilla.org/mozilla1.8/source/calendar/providers/gdata/
*/

get type() {
return "TbSync";
},

get providerID() {
return "{7eb8f992-3956-4607-95ac-b860ebd51f5a}";
},

getProperty: function cTBS_getProperty(aName) {

switch (aName) {
case "cache.enabled":
case "cache.always":
return true;
};
return this.__proto__.__proto__.getProperty.apply(this, arguments);
},

setProperty: function cTBS_setProperty(aName, aValue) {
return this.__proto__.__proto__.setProperty.apply(this, arguments);
},

get canRefresh() {
return true;
},

adoptItem: function cTBS_adoptItem(aItem, aListener) {
this.mOfflineStorage.addItem.apply(this.mOfflineStorage, arguments);
},

addItem: function cTBS_addItem(aItem, aListener) {
this.mOfflineStorage.adoptItem.apply(this.mOfflineStorage, arguments);
},

modifyItem: function cTBS_modifyItem(aNewItem, aOldItem, aListener) {
this.mOfflineStorage.modifyItem.apply(this.mOfflineStorage, arguments);
},

deleteItem: function cTBS_deleteItem(aItem, aListener) {
this.mOfflineStorage.deleteItem.apply(this.mOfflineStorage, arguments);
},

getItem: function cTBS_getItem(aId, aListener) {
this.mOfflineStorage.getItem.apply(this.mOfflineStorage, arguments);
},

getItems: function cTBS_getItems(aItemFilter, aCount, aRangeStart, aRangeEnd, aListener) {
this.mOfflineStorage.getItems.apply(this.mOfflineStorage, arguments);
},

refresh: function cTBS_refresh() {
Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService).logStringMessage("[TbSync] REFRESH REQUEST");
// tell observers to reload everything
this.mObservers.notify("onLoad", [this]);
},



/**
* Implement calIChangeLog
*/
get offlineStorage() { return this.mOfflineStorage; },
set offlineStorage(val) {
this.mOfflineStorage = val;
return val;
},

replayChangesOn: function(aListener) {
},

};


if (XPCOMUtils.generateNSGetFactory) {
var NSGetFactory = XPCOMUtils.generateNSGetFactory([calTbSyncCalendar]);
}


I have absolutely no idea, what replayChangesOn is needed for and what I need to put in there. He complains, if I do not include it. Running with this provider will show all events twice in the calendar list until I switch to another calendar and back. So something is still wrong/missing.

What is cache.enabled and cache.always actually doing?

Thanks for your time,
John
0 new messages