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