Bug 382840: fixing alarms in ics files, r=mvl

This commit is contained in:
daniel.boelzle%sun.com 2007-06-11 09:27:26 +00:00
Родитель c047ea93fd
Коммит b59281acec
8 изменённых файлов: 124 добавлений и 57 удалений

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

@ -392,7 +392,7 @@ interface calIObserver : nsISupports
{
void onStartBatch();
void onEndBatch();
void onLoad();
void onLoad( in calICalendar aCalendar );
void onAddItem( in calIItemBase aItem );
void onModifyItem( in calIItemBase aNewItem, in calIItemBase aOldItem );
void onDeleteItem( in calIItemBase aDeletedItem );

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

@ -51,13 +51,22 @@ function newTimerWithCallback(callback, delay, repeating)
function calAlarmService() {
this.wrappedJSObject = this;
this.mLoadedCalendars = {};
this.calendarObserver = {
alarmService: this,
// calIObserver:
onStartBatch: function() { },
onEndBatch: function() { },
onLoad: function() { },
onLoad: function co_onLoad(calendar) {
// ignore any onLoad events until initial getItems() call of startup has finished:
if (calendar && this.alarmService.mLoadedCalendars[calendar.id]) {
// a refreshed calendar signals that it has been reloaded
// (and cannot notify detailed changes), thus reget all alarms of it:
this.alarmService.initAlarms([calendar]);
}
},
onAddItem: function(aItem) {
var occs = [];
if (aItem.recurrenceInfo) {
@ -95,8 +104,13 @@ function calAlarmService() {
onCalendarRegistered: function(aCalendar) {
this.alarmService.observeCalendar(aCalendar);
// initial refresh of alarms for new calendar:
this.alarmService.initAlarms([aCalendar]);
},
onCalendarUnregistering: function(aCalendar) {
// XXX todo: we need to think about calendar unregistration;
// there may still be dangling items (-> alarm dialog),
// dismissing those alarms may write data...
this.alarmService.unobserveCalendar(aCalendar);
},
onCalendarDeleting: function(aCalendar) {},
@ -254,7 +268,6 @@ calAlarmService.prototype = {
observerSvc.addObserver(this, "xpcom-shutdown", false);
/* Tell people that we're alive so they can start monitoring alarms.
* Make sure to do this before calling findAlarms().
*/
this.notifier = Components.classes["@mozilla.org/embedcomp/appstartup-notifier;1"].getService(Components.interfaces.nsIObserver);
var notifier = this.notifier;
@ -269,15 +282,39 @@ calAlarmService.prototype = {
this.observeCalendar(calendar);
}
this.findAlarms();
/* set up a timer to update alarms every N hours */
var timerCallback = {
alarmService: this,
notify: function(timer) {
this.alarmService.findAlarms();
notify: function timer_notify() {
var now = jsDateToDateTime((new Date())).getInTimezone("UTC");
var start;
if (!this.alarmService.mRangeEnd) {
// This is our first search for alarms. We're going to look for
// alarms +/- 1 month from now. If someone sets an alarm more than
// a month ahead of an event, or doesn't start Sunbird/Lightning
// for a month, they'll miss some, but that's a slim chance
start = now.clone();
start.month -= 1;
start.normalize();
} else {
// This is a subsequent search, so we got all the past alarms before
start = this.alarmService.mRangeEnd.clone();
}
var until = now.clone();
until.month += 1;
until.normalize();
// We don't set timers for every future alarm, only those within 6 hours
var end = now.clone();
end.hour += kHoursBetweenUpdates;
end.normalize();
this.alarmService.mRangeEnd = end.getInTimezone("UTC");
this.alarmService.findAlarms(this.alarmService.calendarManager.getCalendars({}),
start, until);
}
};
timerCallback.notify();
this.mUpdateTimer = newTimerWithCallback(timerCallback, kHoursBetweenUpdates * 3600000, true);
@ -430,14 +467,19 @@ dump("alarm is in the past, and unack'd, firing now!\n");
}
},
findAlarms: function() {
findAlarms: function cas_findAlarms(calendars, start, until) {
var getListener = {
alarmService: this,
onOperationComplete: function(aCalendar, aStatus, aOperationType, aId, aDetail) {
// calendar has been loaded, so until now, onLoad events can be ignored:
this.alarmService.mLoadedCalendars[aCalendar.id] = true;
},
onGetResult: function(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
for (var i = 0; i < aCount; ++i) {
var item = aItems[i];
// assure we don't fire alarms twice, handle removed alarms as far as we can:
// e.g. we cannot purge removed items from ics files. XXX todo.
this.alarmService.removeAlarm(item);
if (this.alarmService.hasAlarm(item)) {
this.alarmService.addAlarm(item);
}
@ -445,33 +487,6 @@ dump("alarm is in the past, and unack'd, firing now!\n");
}
};
var now = jsDateToDateTime((new Date())).getInTimezone("UTC");
var start;
if (!this.mRangeEnd) {
// This is our first search for alarms. We're going to look for
// alarms +/- 1 month from now. If someone sets an alarm more than
// a month ahead of an event, or doesn't start Sunbird/Lightning
// for a month, they'll miss some, but that's a slim chance
start = now.clone();
start.month -= 1;
start.normalize();
} else {
// This is a subsequent search, so we got all the past alarms before
start = this.mRangeEnd.clone();
}
var until = now.clone();
until.month += 1;
until.normalize();
// We don't set timers for every future alarm, only those within 6 hours
var end = now.clone();
end.hour += kHoursBetweenUpdates;
end.normalize();
this.mRangeEnd = end.getInTimezone("UTC");
var calendarManager = this.calendarManager;
var calendars = calendarManager.getCalendars({});
const calICalendar = Components.interfaces.calICalendar;
var filter = calICalendar.ITEM_FILTER_COMPLETED_ALL |
calICalendar.ITEM_FILTER_CLASS_OCCURRENCES |
@ -482,6 +497,20 @@ dump("alarm is in the past, and unack'd, firing now!\n");
}
},
initAlarms: function cas_refreshAlarms(calendars) {
// Total refresh similar to startup. We're going to look for
// alarms +/- 1 month from now. If someone sets an alarm more than
// a month ahead of an event, or doesn't start Sunbird/Lightning
// for a month, they'll miss some, but that's a slim chance
var start = jsDateToDateTime((new Date())).getInTimezone("UTC");
var until = start.clone();
start.month -= 1;
start.normalize();
until.month += 1;
until.normalize();
this.findAlarms(calendars, start, until);
},
alarmFired: function(event) {
if (event.calendar.suppressAlarms)
return;

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

@ -622,7 +622,7 @@ function errorAnnouncer(calendar) {
// calIObserver:
onStartBatch: function() {},
onEndBatch: function() {},
onLoad: function() {},
onLoad: function(calendar) {},
onAddItem: function(aItem) {},
onModifyItem: function(aNewItem, aOldItem) {},
onDeleteItem: function(aDeletedItem) {},

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

@ -495,7 +495,7 @@ agendaTreeView.calendarObserver.onEndBatch = function() {
this.agendaTreeView.refreshCalendarQuery();
}
};
agendaTreeView.calendarObserver.onLoad = function() {
agendaTreeView.calendarObserver.onLoad = function(calendar) {
this.agendaTreeView.refreshCalendarQuery();
};

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

@ -148,7 +148,7 @@ var ltnCompositeCalendarObserver = {
// calIObserver
onStartBatch: function() { },
onEndBatch: function() { },
onLoad: function() { },
onLoad: function(aCalendar) { },
onAddItem: function(aItem) { },
onModifyItem: function(aNewItem, aOldItem) { },
onDeleteItem: function(aDeletedItem) { },

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

@ -74,8 +74,8 @@ calCompositeCalendarObserverHelper.prototype = {
this.notifyObservers("onEndBatch");
},
onLoad: function() {
this.notifyObservers("onLoad");
onLoad: function(calendar) {
this.notifyObservers("onLoad", [calendar]);
},
onAddItem: function(aItem) {

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

@ -170,12 +170,12 @@ calICSCalendar.prototype = {
this.refresh();
},
refresh: function() {
// Lock other changes to the item list.
this.lock();
// set to prevent writing after loading, without any changes
this.loading = true;
refresh: function calICSCalendar_refresh() {
this.queue.push({action: 'refresh'});
this.processQueue();
},
doRefresh: function calICSCalendar_doRefresh() {
var ioService = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
@ -189,6 +189,10 @@ calICSCalendar.prototype = {
var streamLoader = Components.classes["@mozilla.org/network/stream-loader;1"]
.createInstance(Components.interfaces.nsIStreamLoader);
// Lock other changes to the item list.
this.lock();
try {
if (isOnBranch) {
streamLoader.init(channel, this, this);
@ -268,7 +272,7 @@ calICSCalendar.prototype = {
this.mObserver.onError(e.result, e.toString());
}
this.mObserver.onEndBatch();
this.mObserver.onLoad();
this.mObserver.onLoad(this);
// Now that all items have been stuffed into the memory calendar
// we should add ourselves as observer. It is important that this
@ -358,8 +362,9 @@ calICSCalendar.prototype = {
listener.serializer.addProperty(prop);
}
this.getItems(calICalendar.ITEM_FILTER_TYPE_ALL | calICalendar.ITEM_FILTER_COMPLETED_ALL,
0, null, null, listener);
// don't call this.getItems, because we are locked:
this.mMemoryCalendar.getItems(calICalendar.ITEM_FILTER_TYPE_ALL | calICalendar.ITEM_FILTER_COMPLETED_ALL,
0, null, null, listener);
},
// nsIStreamListener impl
@ -432,15 +437,18 @@ calICSCalendar.prototype = {
},
getItem: function (aId, aListener) {
return this.mMemoryCalendar.getItem(aId, aListener);
this.queue.push({action:'get_item', id:aId, listener:aListener});
this.processQueue();
},
getItems: function (aItemFilter, aCount,
aRangeStart, aRangeEnd, aListener)
{
return this.mMemoryCalendar.getItems(aItemFilter, aCount,
aRangeStart, aRangeEnd,
aListener);
this.queue.push({action:'get_items',
itemFilter:aItemFilter, count:aCount,
rangeStart:aRangeStart, rangeEnd:aRangeEnd,
listener:aListener});
this.processQueue();
},
processQueue: function ()
@ -448,23 +456,53 @@ calICSCalendar.prototype = {
if (this.isLocked())
return;
var a;
var hasItems = this.queue.length;
var writeICS = false;
var refreshAction = null;
while ((a = this.queue.shift())) {
switch (a.action) {
case 'add':
this.mMemoryCalendar.addItem(a.item, a.listener);
writeICS = true;
break;
case 'modify':
this.mMemoryCalendar.modifyItem(a.newItem, a.oldItem,
a.listener);
writeICS = true;
break;
case 'delete':
this.mMemoryCalendar.deleteItem(a.item, a.listener);
writeICS = true;
break;
case 'get_item':
this.mMemoryCalendar.getItem(a.id, a.listener);
break;
case 'get_items':
this.mMemoryCalendar.getItems(a.itemFilter, a.count,
a.rangeStart, a.rangeEnd,
a.listener);
break;
case 'refresh':
refreshAction = a;
break;
}
if (refreshAction) {
// break queue processing here and wait for refresh to finish
// before processing further operations
break;
}
}
if (hasItems)
if (writeICS) {
if (refreshAction) {
// reschedule the refresh for next round, after the file has been written;
// strictly we may not need to refresh once the file has been successfully
// written, but we don't know if that write will succeed.
this.queue.unshift(refreshAction);
}
this.writeICS();
}
else if (refreshAction) {
this.doRefresh();
}
},
lock: function () {
@ -740,9 +778,9 @@ calICSObserver.prototype = {
this.mInBatch = false;
},
onLoad: function() {
onLoad: function(calendar) {
for (var i = 0; i < this.mObservers.length; i++)
this.mObservers[i].onLoad();
this.mObservers[i].onLoad(calendar);
},
onAddItem: function(aItem) {
for (var i = 0; i < this.mObservers.length; i++)

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

@ -843,7 +843,7 @@ calStorageCalendar.prototype = {
//
observeLoad: function () {
for each (obs in this.mObservers)
obs.onLoad ();
obs.onLoad (this);
},
observeBatchChange: function (aNewBatchMode) {