1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 *
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
8 *
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
12 * License.
13 *
14 * The Original Code is Sun Microsystems code.
15 *
16 * The Initial Developer of the Original Code is Sun Microsystems.
17 * Portions created by the Initial Developer are Copyright (C) 2006
18 * the Initial Developer. All Rights Reserved.
19 *
20 * Contributor(s):
21 * Thomas Benisch <thomas.benisch@sun.com>
22 * Philipp Kewisch <mozilla@kewis.ch>
23 *
24 * Alternatively, the contents of this file may be used under the terms of
25 * either the GNU General Public License Version 2 or later (the "GPL"), or
26 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
35 *
36 * ***** END LICENSE BLOCK ***** */
37
38 var gInvitationsRequestManager = null;
39
getInvitationsRequestManager
40 function getInvitationsRequestManager() {
41 if (!gInvitationsRequestManager) {
42 gInvitationsRequestManager = new InvitationsRequestManager();
43 }
44 return gInvitationsRequestManager;
45 }
46
InvitationsRequestManager
47 function InvitationsRequestManager() {
48 this.mRequestStatusList = {};
49 }
50
51 InvitationsRequestManager.prototype = {
52 mRequestStatusList: null,
53
IRM_getRequestStatus
54 getRequestStatus: function IRM_getRequestStatus(calendar) {
55 var calendarId = this._getCalendarId(calendar);
56 if (calendarId in this.mRequestStatusList) {
57 return this.mRequestStatusList[calendarId];
58 }
59 return null;
60 },
61
IRM_addRequestStatus
62 addRequestStatus: function IRM_addRequestStatus(calendar, requestStatus) {
63 var calendarId = this._getCalendarId(calendar);
64 this.mRequestStatusList[calendarId] = requestStatus;
65 },
66
IRM_deleteRequestStatus
67 deleteRequestStatus: function IRM_deleteRequestStatus(calendar) {
68 var calendarId = this._getCalendarId(calendar);
69 if (calendarId in this.mRequestStatusList) {
70 delete this.mRequestStatusList[calendarId];
71 }
72 },
73
IRM_getPendingRequests
74 getPendingRequests: function IRM_getPendingRequests() {
75 var count = 0;
76 for each (var requestStatus in this.mRequestStatusList) {
77 var request = requestStatus.request;
78 if (request && request.isPending) {
79 count++;
80 }
81 }
82 return count;
83 },
84
IRM_cancelPendingRequests
85 cancelPendingRequests: function IRM_cancelPendingRequests() {
86 for each (var requestStatus in this.mRequestStatusList) {
87 var request = requestStatus.request;
88 if (request && request.isPending) {
89 request.cancel(null);
90 }
91 }
92 },
93
IRM__getCalendarId
94 _getCalendarId: function IRM__getCalendarId(calendar) {
95 return encodeURIComponent(calendar.uri.spec);
96 }
97 };
98
99 var gInvitationsManager = null;
100
getInvitationsManager
101 function getInvitationsManager() {
102 if (!gInvitationsManager) {
103 gInvitationsManager = new InvitationsManager();
104 }
105 return gInvitationsManager;
106 }
107
InvitationsManager
108 function InvitationsManager() {
109 this.mItemList = new Array();
110 this.mOperationListeners = new Array();
111 this.mStartDate = null;
112 this.mJobsPending = 0;
113 this.mTimer = null;
114 this.mUnregisteredCalendars = new Array();
115 var calendarManagerObserver = {
116 mInvitationsManager: this,
117 onCalendarRegistered: function(aCalendar) {
118 },
119 onCalendarUnregistering: function(aCalendar) {
120 this.mInvitationsManager.unregisterCalendar(aCalendar);
121 },
122 onCalendarDeleting: function(aCalendar) {
123 }
124 };
125 getCalendarManager().addObserver(calendarManagerObserver);
126 }
127
128 InvitationsManager.prototype = {
129 mItemList: null,
130 mOperationListeners: null,
131 mStartDate: null,
132 mJobsPending: 0,
133 mTimer: null,
134 mUnregisteredCalendars: null,
135
IM_scheduleInvitationsUpdate
136 scheduleInvitationsUpdate: function IM_scheduleInvitationsUpdate(firstDelay,
137 repeatDelay,
138 operationListener) {
139 if (this.mTimer) {
140 this.mTimer.cancel();
141 } else {
142 this.mTimer = Components.classes["@mozilla.org/timer;1"]
143 .createInstance(Components.interfaces.nsITimer);
144 }
145 var callback = {
146 mInvitationsManager: this,
147 mRepeatDelay: repeatDelay,
148 mOperationListener: operationListener,
149 notify: function(timer) {
150 if (timer.delay != this.mRepeatDelay) {
151 timer.delay = this.mRepeatDelay;
152 }
153 this.mInvitationsManager.getInvitations(
154 true, this.mOperationListener);
155 }
156 };
157 this.mTimer.initWithCallback(callback, firstDelay,
158 this.mTimer.TYPE_REPEATING_SLACK);
159 },
160
IM_cancelInvitationsUpdate
161 cancelInvitationsUpdate: function IM_cancelInvitationsUpdate() {
162 if (this.mTimer) {
163 this.mTimer.cancel();
164 }
165 },
166
IM_getInvitations
167 getInvitations: function IM_getInvitations(suppressOnError,
168 operationListener1,
169 operationListener2) {
170 if (operationListener1) {
171 this.addOperationListener(operationListener1);
172 }
173 if (operationListener2) {
174 this.addOperationListener(operationListener2);
175 }
176 this.updateStartDate();
177 var requestManager = getInvitationsRequestManager();
178 var calendars = getCalendarManager().getCalendars({});
179 for each (var calendar in calendars) {
180 try {
181 // temporary hack unless all group scheduling features are supported
182 // by the caching facade (calCachedCalendar):
183 var wcapCalendar = calendar.getProperty("private.wcapCalendar")
184 .QueryInterface(Components.interfaces.calIWcapCalendar);
185 if (!wcapCalendar.isOwnedCalendar) {
186 continue;
187 }
188 var listener = {
189 mRequestManager: requestManager,
190 mInvitationsManager: this,
191
192 QueryInterface: function(aIID) {
193 if (!aIID.equals(Components.interfaces.nsISupports) &&
194 !aIID.equals(Components.interfaces.calIOperationListener) &&
195 !aIID.equals(Components.interfaces.calIObserver)) {
196 throw Components.results.NS_ERROR_NO_INTERFACE;
197 }
198 return this;
199 },
200
201 // calIOperationListener
202 onOperationComplete: function(aCalendar,
203 aStatus,
204 aOperationType,
205 aId,
206 aDetail) {
207
208 if (aOperationType != Components.interfaces.calIOperationListener.GET &&
209 aOperationType != Components.interfaces.calIWcapCalendar.SYNC) {
210 return;
211 }
212 var requestStatus =
213 this.mRequestManager.getRequestStatus(aCalendar);
214 if (Components.isSuccessCode(aStatus)) {
215 if (requestStatus.firstRequest) {
216 requestStatus.firstRequest = false;
217 requestStatus.lastUpdate =
218 requestStatus.firstRequestStarted;
219 } else {
220 requestStatus.lastUpdate = aDetail;
221 }
222 }
223 if (this.mRequestManager.getPendingRequests() == 0) {
224 this.mInvitationsManager
225 .deleteUnregisteredCalendarItems();
226 this.mInvitationsManager.mItemList.sort(
227 function (a, b) {
228 var dateA = a.startDate.getInTimezone(calendarDefaultTimezone());
229 var dateB = b.startDate.getInTimezone(calendarDefaultTimezone());
230 return dateA.compare(dateB);
231 });
232 var listener;
233 while ((listener = this.mInvitationsManager.mOperationListeners.shift())) {
234 listener.onGetResult(
235 null,
236 Components.results.NS_OK,
237 Components.interfaces.calIItemBase,
238 null,
239 this.mInvitationsManager.mItemList.length,
240 this.mInvitationsManager.mItemList);
241 listener.onOperationComplete(
242 null,
243 Components.results.NS_OK,
244 Components.interfaces.calIOperationListener.GET,
245 null,
246 null);
247 }
248 }
249 },
250
251 onGetResult: function(aCalendar,
252 aStatus,
253 aItemType,
254 aDetail,
255 aCount,
256 aItems) {
257 if (!Components.isSuccessCode(aStatus)) {
258 return;
259 }
260 for each (var item in aItems) {
261 this.mInvitationsManager.addItem(item);
262 }
263 },
264
265 // calIObserver
266 onStartBatch: function() {
267 },
268
269 onEndBatch: function() {
270 },
271
272 onLoad: function() {
273 },
274
275 onAddItem: function(aItem) {
276 this.mInvitationsManager.addItem(aItem);
277 },
278
279 onModifyItem: function(aNewItem, aOldItem) {
280 this.mInvitationsManager.deleteItem(aNewItem);
281 this.mInvitationsManager.addItem(aNewItem);
282 },
283
284 onDeleteItem: function(aDeletedItem) {
285 this.mInvitationsManager.deleteItem(aDeletedItem);
286 },
287
288 onError: function(aErrNo, aMessage) {
289 },
290
291 onPropertyChanged: function(aCalendar, aName, aValue, aOldValue) {
292 },
293
294 onPropertyDeleting: function(aCalendar, aName) {
295 }
296 };
297 var requestStatus =
298 requestManager.getRequestStatus(wcapCalendar);
299 if (!requestStatus) {
300 requestStatus = {
301 request: null,
302 firstRequest: true,
303 firstRequestStarted: null,
304 lastUpdate: null
305 };
306 requestManager.addRequestStatus(wcapCalendar,
307 requestStatus);
308 }
309 if (!requestStatus.request ||
310 !requestStatus.request.isPending) {
311 var filter = (suppressOnError ?
312 wcapCalendar.ITEM_FILTER_SUPPRESS_ONERROR :
313 0);
314 var request;
315 if (requestStatus.firstRequest) {
316 requestStatus.firstRequestStarted = this.getDate();
317 filter |= wcapCalendar.ITEM_FILTER_REQUEST_NEEDS_ACTION;
318 request = wcapCalendar.wrappedJSObject.getItems(filter,
319 0, this.mStartDate, null, listener);
320 } else {
321 filter |= wcapCalendar.ITEM_FILTER_TYPE_EVENT;
322 request = wcapCalendar.syncChangesTo(null, filter,
323 requestStatus.lastUpdate, listener);
324 }
325 requestStatus.request = request;
326 }
327 } catch (e) {
328 }
329 }
330
331 if (requestManager.getPendingRequests() == 0) {
332 this.deleteUnregisteredCalendarItems();
333 var listener;
334 while ((listener = this.mOperationListeners.shift())) {
335 listener.onOperationComplete(
336 null,
337 Components.results.NS_ERROR_FAILURE,
338 Components.interfaces.calIOperationListener.GET,
339 null,
340 null );
341 }
342 }
343 },
344
IM_openInvitationsDialog
345 openInvitationsDialog: function IM_openInvitationsDialog(onLoadOpListener,
346 finishedCallBack) {
347 var args = new Object();
348 args.onLoadOperationListener = onLoadOpListener;
349 args.queue = new Array();
350 args.finishedCallBack = finishedCallBack;
351 args.requestManager = getInvitationsRequestManager();
352 args.invitationsManager = this;
353 // the dialog will reset this to auto when it is done loading
354 window.setCursor("wait");
355 // open the dialog modally
356 window.openDialog(
357 "chrome://calendar/content/calendar-invitations-dialog.xul",
358 "_blank",
359 "chrome,titlebar,modal,resizable",
360 args);
361 },
362
IM_processJobQueue
363 processJobQueue: function IM_processJobQueue(queue,
364 jobQueueFinishedCallBack) {
365 // TODO: undo/redo
366 var operationListener = {
367 mInvitationsManager: this,
368 mJobQueueFinishedCallBack: jobQueueFinishedCallBack,
369
370 onOperationComplete: function (aCalendar,
371 aStatus,
372 aOperationType,
373 aId,
374 aDetail) {
375 if (Components.isSuccessCode(aStatus) &&
376 aOperationType == Components.interfaces.calIOperationListener.MODIFY) {
377 this.mInvitationsManager.deleteItem(aDetail);
378 this.mInvitationsManager.addItem(aDetail);
379 }
380 this.mInvitationsManager.mJobsPending--;
381 if (this.mInvitationsManager.mJobsPending == 0 &&
382 this.mJobQueueFinishedCallBack) {
383 this.mJobQueueFinishedCallBack();
384 }
385 },
386
387 onGetResult: function(aCalendar,
388 aStatus,
389 aItemType,
390 aDetail,
391 aCount,
392 aItems) {
393
394 }
395 };
396 this.mJobsPending = 0;
397 for (var i = 0; i < queue.length; i++) {
398 var job = queue[i];
399 var oldItem = job.oldItem;
400 var newItem = job.newItem;
401 switch (job.action) {
402 case 'modify':
403 this.mJobsPending++;
404 newItem.calendar.modifyItem(newItem,
405 oldItem,
406 operationListener);
407 break;
408 default:
409 break;
410 }
411 }
412 if (this.mJobsPending == 0 && jobQueueFinishedCallBack) {
413 jobQueueFinishedCallBack();
414 }
415 },
416
IM_hasItem
417 hasItem: function IM_hasItem(item) {
418 for (var i = 0; i < this.mItemList.length; ++i) {
419 if (this.mItemList[i].hashId == item.hashId) {
420 return true;
421 }
422 }
423 return false;
424 },
425
IM_addItem
426 addItem: function IM_addItem(item) {
427 var recInfo = item.recurrenceInfo;
428 if (recInfo && this.getParticipationStatus(item) != "NEEDS-ACTION") {
429 var ids = recInfo.getExceptionIds({});
430 for each (var id in ids) {
431 var ex = recInfo.getExceptionFor(id, false);
432 if (ex && this.validateItem(ex) && !this.hasItem(ex)) {
433 this.mItemList.push(ex);
434 }
435 }
436 } else if (this.validateItem(item) && !this.hasItem(item)) {
437 this.mItemList.push(item);
438 }
439 },
440
IM_deleteItem
441 deleteItem: function IM_deleteItem(item) {
442 var i = 0;
443 while (i < this.mItemList.length) {
444 // Delete all items with the same id from the list.
445 // If item is a recurrent event, also all exceptions are deleted.
446 if (this.mItemList[i].id == item.id) {
447 this.mItemList.splice(i, 1);
448 } else {
449 i++;
450 }
451 }
452 },
453
IM_addOperationListener
454 addOperationListener: function IM_addOperationListener(operationListener) {
455 for each (var listener in this.mOperationListeners) {
456 if (listener == operationListener) {
457 return false;
458 }
459 }
460 this.mOperationListeners.push(operationListener);
461 return true;
462 },
463
IM_getDate
464 getDate: function IM_getDate() {
465 var date = Components.classes["@mozilla.org/calendar/datetime;1"]
466 .createInstance(Components.interfaces.calIDateTime);
467 date.jsDate = new Date();
468 return date;
469 },
470
IM_getStartDate
471 getStartDate: function IM_getStartDate() {
472 var date = Components.classes["@mozilla.org/calendar/datetime;1"]
473 .createInstance(Components.interfaces.calIDateTime);
474 date.jsDate = new Date();
475 date = date.getInTimezone(calendarDefaultTimezone());
476 date.hour = 0;
477 date.minute = 0;
478 date.second = 0;
479 return date;
480 },
481
IM_updateStartDate
482 updateStartDate: function IM_updateStartDate() {
483 if (!this.mStartDate) {
484 this.mStartDate = this.getStartDate();
485 } else {
486 var startDate = this.getStartDate();
487 if (startDate.compare(this.mStartDate) > 0) {
488 this.mStartDate = startDate;
489 var i = 0;
490 while (i < this.mItemList.length) {
491 if (!this.validateItem(this.mItemList[i])) {
492 this.mItemList.splice(i, 1);
493 } else {
494 i++;
495 }
496 }
497 }
498 }
499 },
500
IM_validateItem
501 validateItem: function IM_validateItem(item) {
502 var participationStatus = this.getParticipationStatus(item);
503 if (participationStatus != "NEEDS-ACTION") {
504 return false;
505 }
506 if (item.recurrenceInfo) {
507 return true;
508 } else {
509 var startDate = item.startDate
510 .getInTimezone(calendarDefaultTimezone());
511 if (startDate.compare(this.mStartDate) >= 0) {
512 return true;
513 }
514 }
515 return false;
516 },
517
IM_getParticipationStatus
518 getParticipationStatus: function IM_getParticipationStatus(item) {
519 try {
520 // temporary hack unless all group scheduling features are supported
521 // by the caching facade (calCachedCalendar):
522 var wcapCalendar = item.calendar.getProperty("private.wcapCalendar")
523 .QueryInterface(Components.interfaces.calIWcapCalendar);
524 var attendee = wcapCalendar.getInvitedAttendee(item);
525 if (attendee)
526 return attendee.participationStatus;
527 } catch (e) {}
528 return null;
529 },
530
IM_unregisterCalendar
531 unregisterCalendar: function IM_unregisterCalendar(calendar) {
532 try {
533 var wcapCalendar = calendar.QueryInterface(
534 Components.interfaces.calIWcapCalendar);
535 this.mUnregisteredCalendars.push(wcapCalendar);
536 } catch (e) {}
537 },
538
IM_deleteUnregisteredCalendarItems
539 deleteUnregisteredCalendarItems: function IM_deleteUnregisteredCalendarItems() {
540 var calendar;
541 while ((calendar = this.mUnregisteredCalendars.shift())) {
542 // delete all unregistered calendar items
543 var i = 0;
544 while (i < this.mItemList.length) {
545 if (this.mItemList[i].calendar.uri.equals(calendar.uri)) {
546 this.mItemList.splice(i, 1);
547 } else {
548 i++;
549 }
550 }
551 // delete unregistered calendar request status entry
552 getInvitationsRequestManager().deleteRequestStatus(calendar);
553 }
554 }
555 };