зеркало из https://github.com/mozilla/pjs.git
Bug 382840: fixing alarms in ics files, r=mvl
This commit is contained in:
Родитель
c047ea93fd
Коммит
b59281acec
|
@ -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) {
|
||||
|
|
Загрузка…
Ссылка в новой задаче