paul@4 | 1 | /* FBURL provider extension, retrieving a free/busy URL from LDAP and |
paul@4 | 2 | * presenting the resource's content to the free/busy mechanism. |
paul@4 | 3 | * |
paul@4 | 4 | * Copyright (C) 2014, 2016 Paul Boddie <paul@boddie.org.uk> |
paul@4 | 5 | * |
paul@4 | 6 | * This Source Code Form is subject to the terms of the Mozilla Public |
paul@4 | 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
paul@4 | 8 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
paul@4 | 9 | |
paul@0 | 10 | var EXPORTED_SYMBOLS = [ "fburl" ]; |
paul@0 | 11 | |
paul@0 | 12 | const Cc = Components.classes; |
paul@0 | 13 | const Ci = Components.interfaces; |
paul@0 | 14 | const Cu = Components.utils; |
paul@0 | 15 | |
paul@0 | 16 | Cu.import("resource://calendar/modules/calUtils.jsm"); |
paul@3 | 17 | Cu.import("resource://calendar/modules/calIteratorUtils.jsm"); |
paul@1 | 18 | Cu.import("resource:///modules/mailServices.js"); |
paul@1 | 19 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
paul@0 | 20 | |
paul@0 | 21 | if ("undefined" == typeof(fburl)) { |
paul@0 | 22 | var fburl = {}; |
paul@0 | 23 | }; |
paul@0 | 24 | |
paul@0 | 25 | const calIFreeBusyInterval = Ci.calIFreeBusyInterval; |
paul@0 | 26 | const calIDateTime = Ci.calIDateTime; |
paul@0 | 27 | |
paul@5 | 28 | /* Initialise the provider. |
paul@5 | 29 | */ |
paul@1 | 30 | fburl.fbUrlProvider = function(cal) { |
paul@1 | 31 | this.cal = cal; |
paul@1 | 32 | this._query = null; |
paul@1 | 33 | this._context = null; |
paul@1 | 34 | this._result = null; |
paul@1 | 35 | this._searched = false; |
paul@1 | 36 | this._error = null; |
paul@2 | 37 | this._listener = null; |
paul@2 | 38 | this._url = null; |
paul@3 | 39 | this._calId = null; |
paul@3 | 40 | this._periods = null; |
paul@1 | 41 | }; |
paul@0 | 42 | |
paul@5 | 43 | /* The free/busy provider for FBURL-specified resources. |
paul@5 | 44 | * This implements the calIFreeBusyProvider interface, returning free/busy |
paul@5 | 45 | * intervals when getFreeBusyIntervals is called. |
paul@5 | 46 | */ |
paul@0 | 47 | fburl.fbUrlProvider.prototype = { |
paul@1 | 48 | classID: Components.ID("{11291d94-b457-4322-bfba-ae9df4b6a3c1}"), |
paul@0 | 49 | |
paul@5 | 50 | /* Called by the calFreeBusyService, this function performs a search in |
paul@5 | 51 | * LDAP address books and returns any intervals found. |
paul@5 | 52 | */ |
paul@0 | 53 | getFreeBusyIntervals: function (aCalId, aRangeStart, aRangeEnd, aBusyTypes, aListener) { |
paul@0 | 54 | |
paul@3 | 55 | this._listener = aListener; |
paul@3 | 56 | this._calId = aCalId; |
paul@3 | 57 | this._periods = null; |
paul@3 | 58 | this.startSearch(aCalId); |
paul@1 | 59 | }, |
paul@1 | 60 | |
paul@5 | 61 | /* Access the address book manager and obtain all LDAP directory address |
paul@5 | 62 | * books. |
paul@5 | 63 | */ |
paul@1 | 64 | getLDAPAddressBooks: function () { |
paul@1 | 65 | |
paul@1 | 66 | var abManager = Cc["@mozilla.org/abmanager;1"].getService(Ci.nsIAbManager); |
paul@1 | 67 | var allAddressBooks = abManager.directories; |
paul@1 | 68 | var books = []; |
paul@1 | 69 | |
paul@1 | 70 | while (allAddressBooks.hasMoreElements()) { |
paul@1 | 71 | var addressBook = allAddressBooks.getNext().QueryInterface(Ci.nsIAbDirectory); |
paul@1 | 72 | if (addressBook instanceof Ci.nsIAbLDAPDirectory) { |
paul@1 | 73 | books.push(addressBook); |
paul@1 | 74 | } |
paul@1 | 75 | } |
paul@1 | 76 | |
paul@1 | 77 | return books; |
paul@1 | 78 | }, |
paul@1 | 79 | |
paul@5 | 80 | /* Initiate a search, obtaining LDAP address books and querying each one for |
paul@5 | 81 | * an identity having the given primary e-mail address. |
paul@5 | 82 | */ |
paul@1 | 83 | startSearch: function (aCalId) { |
paul@1 | 84 | |
paul@1 | 85 | var service = Cc["@mozilla.org/network/ldap-service;1"].getService(Ci.nsILDAPService); |
paul@1 | 86 | var books = this.getLDAPAddressBooks(); |
paul@1 | 87 | |
paul@1 | 88 | this._result = null; |
paul@1 | 89 | aCalId = aCalId.replace(/^mailto:/i, ""); |
paul@1 | 90 | |
paul@1 | 91 | for each (var book in books) { |
paul@1 | 92 | var ldap = book.QueryInterface(Ci.nsIAbLDAPDirectory); |
paul@1 | 93 | var attrmap = Cc["@mozilla.org/addressbook/ldap-attribute-map;1"].createInstance(Ci.nsIAbLDAPAttributeMap); |
paul@1 | 94 | var args = Cc["@mozilla.org/addressbook/directory/query-arguments;1"].createInstance(Ci.nsIAbDirectoryQueryArguments); |
paul@1 | 95 | |
paul@1 | 96 | attrmap.setAttributeList("PrimaryEmail", "mail", true); |
paul@1 | 97 | attrmap.setAttributeList("FBURL", "calFbUrl", true); |
paul@1 | 98 | args.filter = service.createFilter(aCalId.length * 2, "(%a=%v)", "", "", "mail", aCalId); |
paul@1 | 99 | args.typeSpecificArg = attrmap; |
paul@1 | 100 | args.querySubDirectories = true; |
paul@1 | 101 | |
paul@1 | 102 | this._query = Cc["@mozilla.org/addressbook/ldap-directory-query;1"].createInstance(Ci.nsIAbDirectoryQuery); |
paul@1 | 103 | this._context = this._query.doQuery(ldap, args, this, ldap.maxHits, 0); |
paul@1 | 104 | this._searched = true; |
paul@1 | 105 | |
paul@1 | 106 | // NOTE: Do only one query for now. |
paul@1 | 107 | |
paul@1 | 108 | break; |
paul@1 | 109 | } |
paul@1 | 110 | }, |
paul@1 | 111 | |
paul@5 | 112 | /* Stop any currently-initiated search. |
paul@5 | 113 | */ |
paul@1 | 114 | stopSearch: function stopSearch() { |
paul@1 | 115 | if (this._context) { |
paul@1 | 116 | this._query.stopQuery(this._context); |
paul@1 | 117 | this._context = null; |
paul@2 | 118 | this._listener = null; |
paul@1 | 119 | } |
paul@1 | 120 | }, |
paul@1 | 121 | |
paul@5 | 122 | /* Search finished callback. |
paul@5 | 123 | */ |
paul@1 | 124 | onSearchFinished: function (aResult, aErrorMsg) { |
paul@1 | 125 | this._context = null; |
paul@1 | 126 | this._error = aErrorMsg; |
paul@1 | 127 | }, |
paul@1 | 128 | |
paul@5 | 129 | /* Search success callback, indicating any found address book card. The |
paul@5 | 130 | * supplied card should provide the FBURL property which is then used to |
paul@5 | 131 | * retrieve the referenced free/busy resource. |
paul@5 | 132 | * |
paul@5 | 133 | * Upon retrieval, the resource is parsed using the calICSService and the |
paul@5 | 134 | * free/busy periods obtained. Finally, each period is encapsulated in an |
paul@5 | 135 | * interval object and the collection of such objects presented to the |
paul@5 | 136 | * originally-specified result callback. |
paul@5 | 137 | */ |
paul@1 | 138 | onSearchFoundCard: function (aCard) { |
paul@1 | 139 | |
paul@2 | 140 | this._result = aCard; |
paul@2 | 141 | |
paul@2 | 142 | // Obtain the URL from the card. |
paul@2 | 143 | |
paul@2 | 144 | this._url = aCard.getProperty("FBURL", ""); |
paul@2 | 145 | |
paul@2 | 146 | // Retrieve the resource and parse it. |
paul@1 | 147 | |
paul@2 | 148 | var parser = Cc["@mozilla.org/calendar/ics-service;1"].getService(Ci.calIICSService); |
paul@2 | 149 | //var parser = Cc["@mozilla.org/calendar/ics-parser;1"].createInstance(Ci.calIIcsParser); |
paul@2 | 150 | var service = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); |
paul@2 | 151 | var stream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream); |
paul@2 | 152 | var channel = service.newChannelFromURI(service.newURI(this._url, null, null)); |
paul@2 | 153 | |
paul@2 | 154 | stream.init(channel.open()); |
paul@3 | 155 | var resource = parser.parseICS(stream.read(-1), null); |
paul@3 | 156 | |
paul@3 | 157 | // Obtain the free/busy periods. |
paul@3 | 158 | |
paul@3 | 159 | this._periods = []; |
paul@3 | 160 | var fbTypeMap = {}; |
paul@3 | 161 | |
paul@3 | 162 | fbTypeMap["FREE"] = calIFreeBusyInterval.FREE; |
paul@3 | 163 | fbTypeMap["BUSY"] = calIFreeBusyInterval.BUSY; |
paul@3 | 164 | fbTypeMap["BUSY-UNAVAILABLE"] = calIFreeBusyInterval.BUSY_UNAVAILABLE; |
paul@3 | 165 | fbTypeMap["BUSY-TENTATIVE"] = calIFreeBusyInterval.BUSY_TENTATIVE; |
paul@3 | 166 | |
paul@3 | 167 | // Iterate over components in the response. |
paul@3 | 168 | |
paul@3 | 169 | for (var comp in cal.ical.calendarComponentIterator(resource)) { |
paul@3 | 170 | |
paul@3 | 171 | // Iterate over free/busy properties. |
paul@3 | 172 | |
paul@3 | 173 | for (var fbProp in cal.ical.propertyIterator(comp, "FREEBUSY")) { |
paul@3 | 174 | |
paul@3 | 175 | // Assign the stated type or busy otherwise. |
paul@3 | 176 | |
paul@3 | 177 | var fbType = fbProp.getParameter("FBTYPE"); |
paul@3 | 178 | |
paul@3 | 179 | if (fbType) { |
paul@3 | 180 | fbType = fbTypeMap[fbType]; |
paul@3 | 181 | } else { |
paul@3 | 182 | fbType = calIFreeBusyInterval.BUSY; |
paul@3 | 183 | } |
paul@3 | 184 | |
paul@3 | 185 | var parts = fbProp.value.split("/"); |
paul@3 | 186 | var begin = cal.createDateTime(parts[0]); |
paul@3 | 187 | var end; |
paul@3 | 188 | |
paul@3 | 189 | // Support durations. |
paul@3 | 190 | |
paul@3 | 191 | if (parts[1].charAt(0) == "P") { |
paul@3 | 192 | end = begin.clone(); |
paul@3 | 193 | end.addDuration(cal.createDuration(parts[1])); |
paul@3 | 194 | |
paul@3 | 195 | // Support plain datetimes. |
paul@3 | 196 | |
paul@3 | 197 | } else { |
paul@3 | 198 | end = cal.createDateTime(parts[1]); |
paul@3 | 199 | } |
paul@3 | 200 | |
paul@3 | 201 | interval = new cal.FreeBusyInterval(this._calId, fbType, begin, end); |
paul@3 | 202 | this._periods.push(interval); |
paul@3 | 203 | } |
paul@3 | 204 | } |
paul@3 | 205 | |
paul@3 | 206 | this._listener.onResult(null, this._periods); |
paul@1 | 207 | }, |
paul@1 | 208 | |
paul@1 | 209 | QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsIAbDirSearchListener]) |
paul@0 | 210 | }; |
paul@0 | 211 | |
paul@5 | 212 | /* Provide a way of adding the provider to the free/busy service. |
paul@5 | 213 | */ |
paul@0 | 214 | fburl.initProvider = function() { |
paul@0 | 215 | cal.getFreeBusyService().addProvider(new fburl.fbUrlProvider(cal)); |
paul@0 | 216 | } |
paul@1 | 217 | |
paul@1 | 218 | fburl.test = function(aCalId) { |
paul@1 | 219 | var fbp = new fburl.fbUrlProvider(cal); |
paul@1 | 220 | fbp.startSearch(aCalId); |
paul@1 | 221 | return fbp; |
paul@1 | 222 | } |
paul@1 | 223 | |
paul@1 | 224 | const NSGetFactory = XPCOMUtils.generateNSGetFactory([fburl.fbUrlProvider]); |