1/* 2 davfs.js - High-level JavaScript WebDAV client implementation 3 Copyright (C) 2004-2007 Guido Wesdorp 4 email johnny@johnnydebris.net 5 6 This program is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with this program; if not, write to the Free Software 18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 20 This is a high-level WebDAV interface, part of the 'davlib' JS package. 21 22 For more details, see davclient.js in the same package, or visit 23 http://johnnydebris.net. 24 25*/ 26 27//----------------------------------------------------------------------------- 28// ResourceCache - simple cache to help with the FS 29//----------------------------------------------------------------------------- 30 31var global = this; 32if (!global.davlib) { 33 alert('Error: davclient.js needs to be loaded before davfs.js'); 34 try { 35 var exc = new exception.MissingDependency('davclient.js', 'davfs.js'); 36 } catch(e) { 37 var exc = 'Missing Dependency: davclient.js (from davfs.js)'; 38 }; 39 throw(exc); 40}; 41 42davlib.ResourceCache = function() { 43}; 44 45davlib.ResourceCache.prototype.initialize = function() { 46 this._cache = {}; 47}; 48 49davlib.ResourceCache.prototype.addResource = function(path, resource) { 50 this._cache[path] = resource; 51}; 52 53davlib.ResourceCache.prototype.getResource = function(path) { 54 return this._cache[path]; 55}; 56 57davlib.ResourceCache.prototype.invalidate = function(path) { 58 delete this._cache[path]; 59}; 60 61//----------------------------------------------------------------------------- 62// DavFS - high-level DAV client library 63//----------------------------------------------------------------------------- 64 65davlib.DavFS = function() { 66 /* High level implementation of a WebDAV client */ 67}; 68 69davlib.DavFS.prototype.initialize = function(host, port, protocol) { 70 this._client = new davlib.DavClient(); 71 this._client.initialize(host, port, protocol); 72 this._cache = new davlib.ResourceCache(); 73 this._cache.initialize(); 74}; 75 76davlib.DavFS.prototype.read = function(path, handler, context) { 77 /* get the contents of a file 78 79 when done, handler is called with 2 arguments, the status code 80 and the content, optionally in context <context> 81 */ 82 this._client.GET(path, this._wrapHandler(handler, 83 this._prepareArgsRead, context), this); 84}; 85 86davlib.DavFS.prototype.write = function(path, content, handler, context, 87 locktoken) { 88 /* write the new contents of an existing or new file 89 90 when done handler will be called with one argument, 91 the status code of the response, optionally in context 92 <context> 93 */ 94 this._client.PUT(path, content, this._wrapHandler(handler, 95 this._prepareArgsSimpleError, context), this, 96 locktoken); 97}; 98 99davlib.DavFS.prototype.remove = function(path, handler, context, locktoken) { 100 /* remove a file or directory recursively from the fs 101 102 when done handler will be called with one argument, 103 the status code of the response, optionally in context 104 <context> 105 106 when the status code is 'Multi-Status', the handler will 107 get a second argument passed in, which is a parsed tree of 108 the multi-status response body 109 */ 110 this._client.DELETE(path, this._wrapHandler(handler, 111 this._prepareArgsMultiError, context), this, 112 locktoken); 113}; 114 115davlib.DavFS.prototype.mkDir = function(path, handler, context, locktoken) { 116 /* create a dir (collection) 117 118 when done, handler is called with 2 arguments, the status code 119 and the content, optionally in context <context> 120 */ 121 this._client.MKCOL(path, this._wrapHandler(handler, 122 this._prepareArgsSimpleError, context), this, 123 locktoken); 124}; 125 126davlib.DavFS.prototype.copy = function(path, topath, handler, context, 127 overwrite, locktoken) { 128 /* copy an item (resource or collection) to another location 129 130 when done, handler is called with 1 argument, the status code, 131 optionally in context <context> 132 */ 133 // XXX not really sure if we should send the lock token for the from or 134 // the to path... 135 this._client.COPY(path, topath, this._wrapHandler(handler, 136 this._prepareArgsMultiError, context), this, 137 overwrite, locktoken); 138}; 139 140davlib.DavFS.prototype.move = function(path, topath, handler, context, 141 locktoken) { 142 /* move an item (resource or collection) to another location 143 144 when done, handler is called with 1 argument, the status code, 145 optionally in context <context> 146 */ 147 this._client.MOVE(path, topath, this._wrapHandler(handler, 148 this._prepareArgsMultiError, context), this, 149 locktoken); 150}; 151 152davlib.DavFS.prototype.listDir = function(path, handler, context, cached) { 153 /* list the contents of a collection 154 155 when done, handler is called with 2 arguments, the status code 156 and an array with filenames, optionally in context <context> 157 */ 158 if (cached) { 159 var item = this._cache.getResource(path); 160 // XXX perhaps we should keep items set to null so we know 161 // the difference between an item that isn't scanned and one 162 // that just has no children 163 if (item && item.items.length > 0) { 164 var dir = []; 165 for (var i=0; i < item.items.length; i++) { 166 dir.push(item.items[i].href); 167 }; 168 handler.apply('200', dir); 169 return; 170 }; 171 }; 172 this._client.PROPFIND(path, this._wrapHandler(handler, 173 this._prepareArgsListDir, context), this, 1); 174}; 175 176davlib.DavFS.prototype.getProps = function(path, handler, context, cached) { 177 /* get the value of one or more properties */ 178 if (cached) { 179 var item = this._cache.getResource(path); 180 // XXX perhaps we should keep items set to null so we know 181 // the difference between an item that isn't scanned and one 182 // that just has no children 183 if (item) { 184 timer_instance.registerFunction(context, handler, 0, 185 null, item.properties); 186 return; 187 }; 188 }; 189 this._client.PROPFIND(path, this._wrapHandler(handler, 190 this._prepareArgsGetProps, context), this, 0); 191}; 192 193davlib.DavFS.prototype.setProps = function(path, setprops, delprops, 194 handler, context, locktoken) { 195 this._client.PROPPATCH(path, 196 this._wrapHandler(handler, 197 this._prepareArgsMultiError, context), 198 this, setprops, delprops, locktoken); 199}; 200 201davlib.DavFS.prototype.lock = function(path, owner, handler, context, 202 scope, type, depth, timeout, 203 locktoken) { 204 /* Lock an item 205 206 when done, handler is called with 2 arguments, the status code 207 and an array with filenames, optionally in context <context> 208 209 optional args: 210 211 <owner> is a URL that identifies the owner, <type> can currently 212 only be 'write' (according to the DAV specs), <depth> should be 213 either 1 or 'Infinity' (default), timeout is in seconds and 214 locktoken is the result of a previous lock (serves to refresh a 215 lock) 216 */ 217 this._client.LOCK(path, owner, 218 this._wrapHandlerLock(handler, 219 this._prepareArgsMultiError, context), 220 this, scope, depth, timeout, locktoken); 221}; 222 223davlib.DavFS.prototype.unlock = function(path, locktoken, handler, context) { 224 /* Release a lock from an item 225 226 when done, handler is called with 1 argument, the status code, 227 optionally in context <context> 228 229 <locktoken> is a lock token returned by a previous DavFS.lock() 230 call 231 */ 232 this._client.UNLOCK(path, locktoken, 233 this._wrapHandler(handler, 234 this._prepareArgsMultiError, context), 235 this); 236}; 237 238// TODO... :\ 239/* 240davlib.DavFS.prototype.isReadable = function(path, handler, context) {}; 241 242davlib.DavFS.prototype.isWritable = function(path, handler, context) {}; 243 244davlib.DavFS.prototype.isLockable = function(path, handler, context) {}; 245*/ 246 247davlib.DavFS.prototype.isLocked = function(path, handler, context, cached) { 248 function sub(error, content) { 249 if (!error) { 250 var ns = content['DAV:']; 251 if (!ns || !ns['lockdiscovery'] || 252 !ns['lockdiscovery'].documentElement.getElementsByTagName( 253 'activelock').length) { 254 content = false; 255 } else { 256 content = true; 257 }; 258 handler(error, content); 259 }; 260 }; 261 this.getProps(path, sub, context, cached); 262}; 263 264davlib.DavFS.prototype._prepareArgsRead = function(status, statusstring, 265 content) { 266 var error; 267 if (status != '200' && status != '201' && status != '204') { 268 error = statusstring; 269 }; 270 return new Array(error, content); 271}; 272 273davlib.DavFS.prototype._prepareArgsSimpleError = function(status, statusstring, 274 content) { 275 var error; 276 // ignore weird IE status code 1223... 277 if (status != '200' && status != '201' && 278 status != '204' && status != '1223') { 279 error = statusstring; 280 }; 281 return new Array(error); 282}; 283 284davlib.DavFS.prototype._prepareArgsMultiError = function(status, statusstring, 285 content) { 286 var error = null; 287 if (status == '207') { 288 error = this._getErrorsFromMultiStatusTree(content); 289 if (!error.length) { 290 error = null; 291 }; 292 } else if (status != '200' && status != '201' && status != '204') { 293 error = statusstring; 294 }; 295 return new Array(error); 296}; 297 298davlib.DavFS.prototype._prepareArgsListDir = function(status, statusstring, 299 content) { 300 var error; 301 if (status == '207') { 302 error = this._getErrorsFromMultiStatusTree(content); 303 if (error.length == 0) { 304 error = undefined; 305 // some caching tricks, store the current item and also 306 // all its children (can't be deeper in the current setup) 307 // in the cache, the children don't contain subchildren yet 308 // but they do contain properties 309 this._cache.addResource(content.href, content); 310 for (var i=0; i < content.items.length; i++) { 311 var child = content.items[i]; 312 this._cache.addResource(child.href, child); 313 }; 314 // the caller is interested only in the filenames 315 // (we assume ;) 316 content = this._getDirFromMultiStatusTree(content); 317 }; 318 } else if (status != '200' && status != '201' && status != '204') { 319 error = statusstring; 320 }; 321 return new Array(error, content); 322}; 323 324davlib.DavFS.prototype._prepareArgsGetProps = function(status, statusstring, 325 content) { 326 var error; 327 if (status == '207') { 328 error = this._getErrorsFromMultiStatusTree(content); 329 if (error.length == 0) { 330 error = undefined; 331 this._cache.addResource(content.href, content); 332 content = content.properties; 333 }; 334 } else if (status != '200' && status != '201' && status != '204') { 335 error = statusstring; 336 }; 337 return new Array(error, content); 338}; 339 340davlib.DavFS.prototype._wrapHandler = function(handler, prepareargs, context) { 341 /* return the handler wrapped in some class that fixes the context 342 and arguments 343 */ 344 function sub(status, statusstring, content) { 345 var args = prepareargs.call(this, status, statusstring, 346 content); 347 handler.apply(context, args); 348 }; 349 return sub; 350}; 351 352davlib.DavFS.prototype._wrapHandlerLock = function(handler, prepareargs, 353 context) { 354 /* return the handler wrapped in some class that fixes the context 355 and arguments 356 */ 357 function sub(status, statusstring, content) { 358 var error; 359 if (status == '207') { 360 error = this._getErrorsFromMultiStatusTree(content); 361 if (error.length == 0) { 362 error = undefined; 363 this._cache.addResource(content.href, content); 364 content = content.properties; 365 }; 366 } else if (status != '200') { 367 error = statusstring; 368 } else { 369 content = content.locktoken; 370 }; 371 handler.apply(context, (new Array(error, content))); 372 }; 373 return sub; 374}; 375 376davlib.DavFS.prototype._getErrorsFromMultiStatusTree = function(curritem) { 377 var errors = new Array(); 378 var status = curritem.status; 379 if (status && status != '200' && status != '204') { 380 errors.push(STATUS_CODES[status]); 381 }; 382 for (var i=0; i < curritem.items.length; i++) { 383 var itemerrors = this._getErrorsFromMultiStatusTree( 384 curritem.items[i]); 385 if (itemerrors) { 386 for (var j=0; j < itemerrors.length; j++) { 387 errors.push(itemerrors[j]); 388 }; 389 }; 390 }; 391 return errors; 392}; 393 394davlib.DavFS.prototype._getDirFromMultiStatusTree = function(root) { 395 var list = new Array(); 396 this._cache.addResource(root.href, root); 397 for (var i=0; i < root.items.length; i++) { 398 var item = root.items[i]; 399 list.push(item.href); 400 }; 401 list.sort(); 402 return list; 403}; 404 405