1/* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5var { PromiseUtils } = ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm"); 6 7/* 8 * Asynchronous tools for handling calendar operations 9 */ 10 11// NOTE: This module should not be loaded directly, it is available when 12// including calUtils.jsm under the cal.async namespace. 13 14const EXPORTED_SYMBOLS = ["calasync"]; /* exported calasync */ 15 16var cIOL = Ci.calIOperationListener; 17var cIC = Ci.calICalendar; 18 19var promisifyProxyHandler = { 20 promiseOperation(target, name, args) { 21 let deferred = PromiseUtils.defer(); 22 let listener = calasync.promiseOperationListener(deferred); 23 args.push(listener); 24 target[name](...args); 25 return deferred.promise; 26 }, 27 get(target, name) { 28 switch (name) { 29 // calICalendar methods 30 case "adoptItem": 31 case "addItem": 32 case "modifyItem": 33 case "deleteItem": 34 case "getItem": 35 case "getItems": 36 return (...args) => this.promiseOperation(target, name, args); 37 // calIOfflineStorage methods 38 case "addOfflineItem": 39 case "modifyOfflineItem": 40 case "deleteOfflineItem": 41 case "getOfflineItemFlag": 42 case "resetItemOfflineFlag": { 43 let offline = target.QueryInterface(Ci.calIOfflineStorage); 44 return (...args) => this.promiseOperation(offline, name, args); 45 } 46 47 // Special getAllItems shortcut 48 case "getAllItems": 49 return () => 50 this.promiseOperation(target, "getItems", [cIC.ITEM_FILTER_ALL_ITEMS, 0, null, null]); 51 case "proxyTarget": 52 return target; 53 default: 54 return target[name]; 55 } 56 }, 57}; 58 59var calasync = { 60 /** 61 * Creates a proxy to the given calendar where the CRUD operations are replaced 62 * with versions that return a promise and don't take a listener. 63 * 64 * Before: 65 * calendar.addItem(item, { 66 * onGetResult: function() {}, 67 * onOperationComplete: function (c,status,t,c,detail) { 68 * if (Components.isSuccessCode(status)) { 69 * handleSuccess(detail); 70 * } else { 71 * handleFailure(status); 72 * } 73 * } 74 * }); 75 * 76 * After: 77 * let pcal = promisifyCalendar(calendar); 78 * pcal.addItem(item).then(handleSuccess, handleFailure); 79 * 80 * Bonus methods in addition: 81 * pcal.getAllItems() // alias for getItems without any filters 82 * 83 * IMPORTANT: Don't pass this around thinking its like an xpcom calICalendar, 84 * otherwise code might indefinitely wait for the listener to return or there 85 * will be complaints that an argument is missing. 86 */ 87 promisifyCalendar(aCalendar) { 88 return new Proxy(aCalendar, promisifyProxyHandler); 89 }, 90 /** 91 * Create an operation listener (calIOperationListener) that resolves when 92 * the operation succeeds. Note this listener will collect the items, so it 93 * might not be a good idea in a situation where a lot of items will be 94 * retrieved. 95 * 96 * Standalone Usage: 97 * function promiseAddItem(aItem) { 98 * let deferred = PromiseUtils.defer(); 99 * let listener = cal.async.promiseOperationListener(deferred); 100 * aItem.calendar.addItem(aItem, listener); 101 * return deferred.promise; 102 * } 103 * 104 * See also promisifyCalendar, where the above can be replaced with: 105 * function promiseAddItem(aItem) { 106 * let calendar = cal.async.promisifyCalendar(aItem.calendar); 107 * return calendar.addItem(aItem); 108 * } 109 */ 110 promiseOperationListener(deferred) { 111 return { 112 QueryInterface: ChromeUtils.generateQI(["calIOperationListener"]), 113 items: [], 114 itemStatus: Cr.NS_OK, 115 onGetResult(aCalendar, aStatus, aItemType, aDetail, aItems) { 116 this.itemStatus = aStatus; 117 if (Components.isSuccessCode(aStatus)) { 118 this.items = this.items.concat(aItems); 119 } else { 120 this.itemSuccess = aStatus; 121 } 122 }, 123 124 onOperationComplete(aCalendar, aStatus, aOpType, aId, aDetail) { 125 if (!Components.isSuccessCode(aStatus)) { 126 // This function has failed, reject with the status 127 deferred.reject(aStatus); 128 } else if (!Components.isSuccessCode(this.itemStatus)) { 129 // onGetResult has failed, reject with its status 130 deferred.reject(this.itemStatus); 131 } else if (aOpType == cIOL.GET) { 132 // Success of a GET operation: resolve with array of 133 // resulting items. 134 deferred.resolve(this.items); 135 } else { 136 /* ADD,MODIFY,DELETE: resolve with 1 item */ 137 // Success of an ADD MODIFY or DELETE operation, resolve 138 // with the one item that was processed. 139 deferred.resolve(aDetail); 140 } 141 }, 142 }; 143 }, 144}; 145