1 /* 2 * Copyright (c) 2008-2017 LabKey Corporation 3 * 4 * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 5 */ 6 7 /** 8 * A static class for interacting with files and filesystems 9 * @name LABKEY.FileSystem 10 * @ignore 11 * @class 12 */ 13 Ext.ns('LABKEY.FileSystem'); 14 15 16 /** 17 * A helper for manipulating URIs. This is not part of the public API, so do not rely on its existence 18 * from parseUri 1.2.1 19 * (c) 2007 Steven Levithan <stevenlevithan.com> 20 * MIT License 21 * @ignore 22 */ 23 LABKEY.URI = Ext.extend(Object, 24 { 25 constructor : function(u) 26 { 27 this.toString = function() 28 { 29 return this.protocol + "://" + this.host + this.pathname + this.search; 30 }; 31 if (typeof u == "string") 32 this.parse(u); 33 else if (typeof u == "object") 34 Ext.apply(this,u); 35 36 this.options = Ext.apply({},this.options); // clone 37 }, 38 parse: function(str) 39 { 40 var o = this.options; 41 var m = o.parser[o.strictMode ? "strict" : "loose"].exec(str); 42 var uri = this || {}; 43 var i = 14; 44 45 while (i--) 46 uri[o.key[i]] = m[i] || ""; 47 48 if (!uri.protocol) 49 { 50 var l = window.location; 51 uri.protocol = uri.protocol || l.protocol; 52 uri.port = uri.port || l.port; 53 uri.hostname = uri.hostname || l.hostname; 54 uri.host = uri.host || l.host; 55 } 56 if (uri.protocol && uri.protocol.charAt(uri.protocol.length-1) == ":") 57 uri.protocol = uri.protocol.substr(0,uri.protocol.length - 1); 58 59 uri[o.q.name] = {}; 60 uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) 61 { 62 if ($1) uri[o.q.name][$1] = $2; 63 }); 64 uri.href = this.protocol + "://" + this.host + this.pathname + this.search; 65 return uri; 66 }, 67 options: 68 { 69 strictMode: false, 70 key: ["source","protocol","host","userInfo","user","password","hostname","port","relative","pathname","directory","file","search","hash"], 71 q: 72 { 73 name: "query", 74 parser: /(?:^|&)([^&=]*)=?([^&]*)/g 75 }, 76 parser: 77 { 78 strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/, 79 loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ 80 } 81 } 82 }); 83 84 /** 85 * Static map of events used internally by LABKEY.FileSystem 86 * @memberOf LABKEY.FileSystem# 87 * @ignore 88 * @private 89 */ 90 LABKEY.FileSystem.FILESYSTEM_EVENTS = { 91 ready: "ready", 92 filesremoved: "filesremoved", 93 fileschanged: "fileschanged" 94 }; 95 96 97 /** 98 * Returns a listing of all file roots available for a given folder 99 * @memberOf LABKEY.FileSystem# 100 * @param config Configuration properties. 101 * @param {String} config.containerPath The container to test. If null, the current container will be used. 102 * @param {Function} config.success Success callback function. It will be called with the following arguments: 103 * <li>Results: A map linking the file root name to the WebDAV URL</li> 104 * <li>Response: The XMLHttpRequest object containing the response data.</li> 105 * @param {Function} [config.failure] Error callback function. It will be called with the following arguments: 106 * <li>Response: The XMLHttpRequest object containing the response data.</li> 107 * <li>Options: The parameter to the request call.</li> 108 * @param {Object} [config.scope] The scope for the callback function. Defaults to 'this' 109 */ 110 LABKEY.FileSystem.getFileRoots = function(config){ 111 config = config || {}; 112 113 LABKEY.Ajax.request({ 114 url: LABKEY.ActionURL.buildURL('filecontent', 'getFileRoots', config.containerPath), 115 scope: config.scope || this, 116 success: LABKEY.Utils.getCallbackWrapper(config.success, config.scope), 117 failure: LABKEY.Utils.getCallbackWrapper(config.failure, config.scope), 118 }); 119 } 120 121 122 /** 123 * Static map of events used internally by LABKEY.FileSystem 124 * @memberOf LABKEY.FileSystem# 125 * @ignore 126 * @private 127 */ 128 LABKEY.FileSystem.BROWSER_EVENTS = { 129 selectionchange:"selectionchange", 130 directorychange:"directorychange", 131 doubleclick:"doubleclick", 132 transferstarted:'transferstarted', 133 transfercomplete:'transfercomplete', 134 movestarted:'movestarted', 135 movecomplete:'movecomplete', 136 deletestarted:'deletestarted', 137 deletecomplete:'deletecomplete' 138 }; 139 140 LABKEY.FileSystem.FOLDER_ICON = LABKEY.contextPath + "/" + LABKEY.extJsRoot + "/resources/images/default/tree/folder.gif"; 141 142 143 /** 144 * Private heper methods used internally by LABKEY.Filesystem. 145 * @ignore 146 * @private 147 */ 148 LABKEY.FileSystem.Util = new function(){ 149 var $ = Ext.get, $h = Ext.util.Format.htmlEncode, $dom = Ext.DomHelper, 150 imgSeed = 0, 151 FATAL_INT = 50000, 152 ERROR_INT = 40000, 153 WARN_INT = 30000, 154 INFO_INT = 20000, 155 DEBUG_INT = 10000; 156 157 function formatWithCommas(value) 158 { 159 var x = value; 160 var formatted = (x == 0) ? '0' : ''; 161 var sep = ''; 162 while (x > 0) 163 { 164 // Comma separate between thousands 165 formatted = sep + formatted; 166 formatted = (x % 10) + formatted; 167 x -= (x % 10); 168 if (x > 0) 169 { 170 formatted = ((x % 100) / 10) + formatted; 171 x -= (x % 100); 172 } 173 if (x > 0) 174 { 175 formatted = ((x % 1000) / 100) + formatted; 176 x -= (x % 1000); 177 } 178 x = x / 1000; 179 sep = ','; 180 } 181 return formatted; 182 } 183 184 return { 185 startsWith : function(s, f){ 186 var len = f.length; 187 if (s.length < len) return false; 188 if (len == 0) 189 return true; 190 return s.charAt(0) == f.charAt(0) && s.charAt(len-1) == f.charAt(len-1) && s.indexOf(f) == 0; 191 }, 192 193 endsWith : function(s, f){ 194 var len = f.length; 195 var slen = s.length; 196 if (slen < len) return false; 197 if (len == 0) 198 return true; 199 return s.charAt(slen-len) == f.charAt(0) && s.charAt(slen-1) == f.charAt(len-1) && s.indexOf(f) == slen-len; 200 }, 201 202 // minor hack call with scope having decorateIcon functions 203 renderIcon : function(value, metadata, record, rowIndex, colIndex, store, decorateFN) { 204 var file = record.get("file"); 205 if (!value) 206 { 207 if (!file) 208 { 209 value = LABKEY.FileSystem.FOLDER_ICON; 210 } 211 else 212 { 213 var name = record.get("name"); 214 var i = name.lastIndexOf("."); 215 var ext = i >= 0 ? name.substring(i) : name; 216 value = LABKEY.contextPath + "/project/icon.view?name=" + ext; 217 } 218 } 219 var img = {tag:'img', width:16, height:16, src:value, id:'img'+(++imgSeed)}; 220 if (decorateFN) 221 decorateFN.defer(1,this,[img.id,record]); 222 return $dom.markup(img); 223 }, 224 225 renderFileSize : function(value, metadata, record, rowIndex, colIndex, store){ 226 if (!record.get('file')) return ""; 227 var f = Ext.util.Format.fileSize(value); 228 return "<span title='" + f + "'>" + formatWithCommas(value) + "</span>"; 229 }, 230 231 /* Used as a field renderer */ 232 renderUsage : function(value, metadata, record, rowIndex, colIndex, store){ 233 if (!value || value.length == 0) return ""; 234 var result = "<span title='"; 235 for (var i = 0; i < value.length; i++) 236 { 237 if (i > 0) 238 { 239 result = result + ", "; 240 } 241 result = result + $h(value[i].message); 242 } 243 result = result + "'>"; 244 for (i = 0; i < value.length; i++) 245 { 246 if (i > 0) 247 { 248 result = result + ", "; 249 } 250 if (value[i].href) 251 { 252 result = result + "<a href=\'" + $h(value[i].href) + "'>"; 253 } 254 result = result + $h(value[i].message); 255 if (value[i].href) 256 { 257 result = result + "</a>"; 258 } 259 } 260 result = result + "</span>"; 261 return result; 262 }, 263 264 renderDateTime : function(value, metadata, record, rowIndex, colIndex, store){ 265 if (!value) return ""; 266 if (value.getTime() == 0) return ""; 267 return "<span title='" + LABKEY.FileSystem.Util._longDateTime(value) + "'>" + LABKEY.FileSystem.Util. _rDateTime(value) + "<span>"; 268 }, 269 270 _longDateTime : Ext.util.Format.dateRenderer("l, F d, Y g:i:s A"), 271 _rDateTime : Ext.util.Format.dateRenderer("Y-m-d H:i:s"), 272 273 formatWithCommas : function(value) { 274 return formatWithCommas(value); 275 }, 276 277 _processAjaxResponse: function(response){ 278 if (response && 279 response.responseText && 280 response.getResponseHeader('Content-Type') && 281 response.getResponseHeader('Content-Type').indexOf('application/json') >= 0) 282 { 283 try 284 { 285 response.jsonResponse = Ext.util.JSON.decode(response.responseText); 286 if(response.jsonResponse.status) 287 response.status = response.jsonResponse.status 288 } 289 catch (error){ 290 //ignore 291 } 292 } 293 } 294 } 295 }; 296 297 /** 298 * This is a base class that is extended by LABKEY.FileSystem.WebdavFileSystem and others. It is not intended to be used directly. 299 * @class LABKEY.FileSystem.AbstractFileSystem 300 * @name LABKEY.FileSystem.AbstractFileSystem 301 * @param config Configuration properties. 302 */ 303 304 /** 305 * The Ext.Record type used to store files in the fileSystem 306 * @name FileRecord 307 * @fieldOf LABKEY.FileSystem.AbstractFileSystem# 308 * @description 309 * The file record should contain the following fields: 310 <li>uri (string, urlencoded)</li> 311 <li>path (string, not encoded)</li> 312 <li>name (string)</li> 313 <li>file (bool)</li> 314 <li>created (date)</li> 315 <li>modified (date)</li> 316 <li>size (int)</li> 317 <li>createdBy(string, optional)</li> 318 <li>modifiedBy(string, optional)</li> 319 <li>iconHref(string, optional)</li> 320 <li>actionHref(string, optional)</li> 321 <li>contentType(string, optional)</li> 322 <li>absolutePath(string, optional)</li> 323 */ 324 LABKEY.FileSystem.AbstractFileSystem = function(config){ 325 LABKEY.FileSystem.AbstractFileSystem.superclass.constructor.apply(this, arguments); 326 }; 327 328 Ext.extend(LABKEY.FileSystem.AbstractFileSystem, Ext.util.Observable, { 329 330 /** 331 * Set to true if the fileSystem has loaded. 332 * @type Boolean 333 * @property 334 * @memberOf LABKEY.FileSystem.AbstractFileSystem# 335 */ 336 ready : true, 337 rootPath : "/", 338 separator : "/", 339 directoryMap : {}, 340 341 constructor : function(config) 342 { 343 Ext.util.Observable.prototype.constructor.call(this); 344 this.directoryMap = {}; 345 /** 346 * @memberOf LABKEY.FileSystem.AbstractFileSystem# 347 * @event 348 * @name ready 349 * @param {Filesystem} fileSystem A reference to the fileSystem 350 * @description Fires when the file system has loaded. 351 */ 352 /** 353 * @memberOf LABKEY.FileSystem.AbstractFileSystem# 354 * @event 355 * @name fileschanged 356 * @param {FileSystem} [fileSystem] A reference to the fileSystem. 357 * @param {String} [path] The path that was changed. 358 * @description Fires when the a path has been changed. 359 */ 360 /** 361 * @memberOf LABKEY.FileSystem.AbstractFileSystem# 362 * @event 363 * @name filesremoved 364 * @param {FileSystem} [fileSystem] A reference to the fileSystem. 365 * @param {Record[]} [records] An array of Ext.Record objects representing the files that were removed. These can be files and/or directories. 366 * @description Fires when one or more files or folders have been removed, either by a delete or move action. It is not fired when files are uncached for other reasons. 367 */ 368 this.addEvents( 369 LABKEY.FileSystem.FILESYSTEM_EVENTS.filesremoved, 370 LABKEY.FileSystem.FILESYSTEM_EVENTS.fileschanged, 371 LABKEY.FileSystem.FILESYSTEM_EVENTS.ready 372 ); 373 }, 374 375 /** 376 * Will list all the contents of the supplied path. If this path has already been loaded, the local cache will be used. 377 * @param config Configuration properties. 378 * @param {String} config.path The path to load 379 * @param {Function} config.success Success callback function. It will be called with the following arguments: 380 * <li>Filesystem: A reference to the filesystem</li> 381 * <li>Path: The path that was loaded</li> 382 * <li>Records: An array of record objects</li> 383 * @param {Function} [config.failure] Error callback function. It will be called with the following arguments: 384 * <li>Response: The XMLHttpRequest object containing the response data.</li> 385 * <li>Options: The parameter to the request call.</li> 386 * @param {Object} [config.scope] The scope for the callback functions 387 * @param {Boolean} [config.forceReload] If true, the path will always be reloaded instead of relying on the cache 388 * @methodOf LABKEY.FileSystem.AbstractFileSystem# 389 */ 390 listFiles : function(config) 391 { 392 config.scope = config.scope || this; 393 var files = this.directoryFromCache(config.path); 394 if (files && !config.forceReload) 395 { 396 if (typeof config.success == "function") 397 config.success.defer(1, config.scope, [this, config.path, files]); 398 } 399 else 400 { 401 this.reloadFiles(config); 402 } 403 }, 404 405 /** 406 * A helper to test if a file of the same name exists at a given path. If this path has not already been loaded, the local cache will be used unless forceReload is true. 407 * @param config Configuration properties. 408 * @param {String} config.name The name to test. This can either be a filename or a full path. If the latter is supplied, getFileName() will be used to extract the filename 409 * @param {String} config.path The path to check 410 * @param {Function} config.success Success callback function. It will be called with the following arguments: 411 * <li>Filesystem: A reference to the filesystem</li> 412 * <li>Name: The name to be tested</li> 413 * <li>Path: The path to be checked</li> 414 * <li>Record: If a record of the same name exists, the record object will be returned. Null indicates no name conflict exists</li> 415 * @param {Function} [config.failure] Error callback function. It will be called with the following arguments: 416 * <li>Response: The XMLHttpRequest object containing the response data.</li> 417 * <li>Options: The parameter to the request call.</li> 418 * @param {Object} [config.scope] The scope for the callback function. Defaults to 'this' 419 * @param {Boolean} [config.forceReload] If true, the cache will be reloaded prior to performing the check 420 * @methodOf LABKEY.FileSystem.AbstractFileSystem# 421 */ 422 checkForNameConflict: function(config) 423 { 424 var filename = this.concatPaths(config.path, this.getFileName(config.name)); 425 config.scope = config.scope || this; 426 427 this.listFiles({ 428 path: config.path, 429 success: function (fs, path, records) { 430 if (Ext.isFunction(config.success)) 431 config.success.defer(1, config.scope, [this, config.name, config.path, this.recordFromCache(filename)]); 432 }, 433 failure: config.failure, 434 scope: this, 435 forceReload: config.forceReload 436 }); 437 }, 438 439 /** 440 * Force reload on next listFiles call 441 * @ignore 442 * @param record 443 */ 444 uncacheListing : function(record) 445 { 446 var path = (typeof record == "string") ? record : record.data.path; 447 this.directoryMap[path] = null; 448 }, 449 450 /** 451 * @ignore 452 * @param record 453 */ 454 canRead : function(record) 455 { 456 return true; 457 }, 458 459 /** 460 * @ignore 461 * @param record 462 */ 463 canWrite: function(record) 464 { 465 return true; 466 }, 467 468 /** 469 * @ignore 470 * @param record 471 */ 472 canMkdir: function(record) 473 { 474 return true; 475 }, 476 477 /** 478 * @ignore 479 * @param record 480 */ 481 canDelete : function(record) 482 { 483 return true; 484 }, 485 486 /** 487 * @ignore 488 * @param record 489 */ 490 canMove : function(record) 491 { 492 return true; 493 }, 494 495 /** 496 * @ignore 497 * @param config 498 */ 499 deletePath : function(config) // callback(filesystem, success, path) 500 { 501 return false; 502 }, 503 504 /** 505 * @ignore 506 * @param config 507 */ 508 createDirectory : function(config) // callback(filesystem, success, path) 509 { 510 }, 511 512 /** 513 * Called by listFiles(), return false on immediate fail 514 * @ignore 515 */ 516 reloadFiles : function(config) 517 { 518 return false; 519 }, 520 521 /** 522 * @ignore 523 * @param config 524 */ 525 getHistory : function(config) // callback(filesystem, success, path, history[]) 526 { 527 }, 528 529 // protected 530 531 _addFiles : function(path, records) 532 { 533 this.directoryMap[path] = records; 534 this.fireEvent(LABKEY.FileSystem.FILESYSTEM_EVENTS.fileschanged, this, path, records); 535 }, 536 537 /** 538 * For a supplied path, returns an array corresponding Ext Record from the cache 539 * @param {String} path The path of the directory 540 * @returns {Ext.Record[]} An array of Ext.Records representing the contents of the directory. Returns null if the directory is not in the cache. 541 * @methodOf LABKEY.FileSystem.AbstractFileSystem# 542 * @name directoryFromCache 543 */ 544 directoryFromCache : function(path) 545 { 546 var files = this.directoryMap[path]; 547 if (!files && path && path.length>0 && path.charAt(path.length-1) == this.separator) 548 path = path.substring(0,path.length-1); 549 files = this.directoryMap[path]; 550 return files; 551 }, 552 553 /** 554 * For a supplied path, returns the corresponding Ext Record from the cache 555 * @param {String} path The path of the file or directory 556 * @returns {Ext.Record} The Ext.Record for this file. Returns null if the file is not found. 557 * @methodOf LABKEY.FileSystem.AbstractFileSystem# 558 * @name recordFromCache 559 */ 560 recordFromCache : function(path) 561 { 562 if (!path || path == this.rootPath) 563 return this.rootRecord; 564 var parent = this.getParentPath(path) || this.rootPath; 565 var name = this.getFileName(path); 566 var files = this.directoryFromCache(parent); 567 if (!files) 568 return null; 569 for (var i=0 ; i<files.length ; i++) 570 { 571 var r = files[i]; 572 if (r.data.name == name) 573 return r; 574 } 575 return null; 576 }, 577 578 onReady : function(fn) 579 { 580 if (this.ready) 581 fn.call(); 582 else 583 this.on(LABKEY.FileSystem.FILESYSTEM_EVENTS.ready, fn); 584 }, 585 586 // util 587 588 /** 589 * A utility method to concatenate 2 strings into a normalized filepath 590 * @param {String} a The first path 591 * @param {String} b The first path 592 * @returns {String} The concatenated path 593 * @methodOf LABKEY.FileSystem.AbstractFileSystem# 594 */ 595 concatPaths : function(a,b) 596 { 597 var c = 0; 598 if (a.length > 0 && a.charAt(a.length-1)==this.separator) c++; 599 if (b.length > 0 && b.charAt(0)==this.separator) c++; 600 if (c == 0) 601 return a + this.separator + b; 602 else if (c == 1) 603 return a + b; 604 else 605 return a + b.substring(1); 606 }, 607 608 /** 609 * A utility method to extract the parent path from a file or folder path 610 * @param {String} p The path to the file or directory 611 * @returns {String} The parent path 612 * @methodOf LABKEY.FileSystem.AbstractFileSystem# 613 */ 614 getParentPath : function(p) 615 { 616 if (!p) 617 p = this.rootPath; 618 if (p.length > 1 && p.charAt(p.length-1) == this.separator) 619 p = p.substring(0,p.length-1); 620 var i = p.lastIndexOf(this.separator); 621 return i == -1 ? this.rootPath : p.substring(0,i+1); 622 }, 623 624 /** 625 * A utility method to extract the filename from a file path. 626 * @param {String} p The path to the file or directory 627 * @returns {String} The file name 628 * @methodOf LABKEY.FileSystem.AbstractFileSystem# 629 */ 630 getFileName : function(p) 631 { 632 if (!p || p == this.rootPath) 633 return this.rootPath; 634 if (p.length > 1 && p.charAt(p.length-1) == this.separator) 635 p = p.substring(0,p.length-1); 636 var i = p.lastIndexOf(this.separator); 637 if (i > -1) 638 p = p.substring(i+1); 639 return p; 640 }, 641 642 /** 643 * A utility to test if a path is a direct child of another path 644 * @param {String} a The first path to test 645 * @param {String} b The second path to test 646 * @returns {Boolean} Returns true if the first path is a direct child of the second 647 * @methodOf LABKEY.FileSystem.AbstractFileSystem# 648 */ 649 isChild: function(a, b){ 650 return a.indexOf(b) == 0; 651 //return a.match(new RegExp('^' + b + '.+', 'i')); 652 } 653 654 }); 655 656 657 /** 658 * This class enables interaction with WebDav filesystems, such as the one exposed through LabKey Server. 659 * In addition to the properties and methods documented here, all methods from LABKEY.FileSystem.AbstractFileSystem are available. 660 * @class LABKEY.FileSystem.WebdavFileSystem 661 * @augments LABKEY.FileSystem.AbstractFileSystem 662 * @constructor 663 * @param config Configuration properties. 664 * @param {String} [config.containerPath] The path to the container to load (ie. '/home') 665 * @param {String} [config.filePath] The file path, relative to the containerPath (ie. '/mySubfolder'). Optional. 666 * @param {String} [config.fileLink] A folder name that is appended after the container and filePath. By default, LabKey stores files in a subfolder called '/@files'. If the pipeline root of your container differs from the file root, the files may be stored in '/@pipeline'. This defaults to '/@files'. To omit this, use 'fileLink: null'. For non-standard URLs, also see config.baseUrl. 667 * @param {string} [config.baseUrl] Rather than supplying a containerPath and filePath, you can supply the entire baseUrl to use as the root of the webdav tree (ie. http://localhost:8080/labkey/_webdav/home/@files/), must be an ABSOLUTE URL ("/_webdav" NOT "_webdav"). If provided, this will be preferentially used instead of creating a baseUrl from containerPath, filePath and fileLink 668 * @param {string} [config.rootName] The display name for the root (ie. 'Fileset'). Optional. 669 * @param {array} [config.extraDataFields] An array of extra Ext.data.Field config objects that will be appended to the FileRecord. Optional. 670 * @example <script type="text/javascript"> 671 672 //this example loads files from: /_webdav/home/mySubfolder/@files 673 var fileSystem = new LABKEY.FileSystem.WebdavFileSystem({ 674 containerPath: '/home', 675 filePath: '/mySubfolder' //optional. 676 }); 677 678 //this would be identical to the example above 679 new LABKEY.FileSystem.WebdavFileSystem({ 680 baseUrl: "/_webdav/home/mySubfolder/@files" 681 }); 682 683 //this creates the URL: /webdav/myProject/@pipeline 684 new LABKEY.FileSystem.WebdavFileSystem({ 685 containerPath: '/myProject', 686 fileLink: '/@pipeline' 687 }); 688 689 690 fileSystem.on('ready', function(fileSystem){ 691 fileSystem.listFiles({ 692 path: '/mySubfolder', 693 success: function(fileSystem, path, records){ 694 alert('It worked!'); 695 console.log(records); 696 }, 697 scope: this 698 }, this); 699 700 fileSystem.movePath({ 701 source: '/myFile.xls', 702 destination: '/foo/myFile.xls', 703 isFile: true, 704 success: function(fileSystem, path, records){ 705 alert('It worked!'); 706 }, 707 failure: function(response, options){ 708 alert('It didnt work. The error was ' + response.statusText); 709 console.log(response); 710 }, 711 scope: this 712 }); 713 }, this); 714 715 716 </script> 717 */ 718 LABKEY.FileSystem.WebdavFileSystem = function(config) 719 { 720 config = config || {}; 721 Ext.apply(this, config, { 722 baseUrl: LABKEY.contextPath + "/_webdav", 723 rootPath: "/", 724 rootName : (LABKEY.serverName || "LabKey Server"), 725 fileLink: "/@files" 726 }); 727 this.ready = false; 728 this.initialConfig = config; 729 730 LABKEY.FileSystem.WebdavFileSystem.superclass.constructor.call(this); 731 732 this.HistoryRecord = Ext.data.Record.create(['user', 'date', 'message', 'href']); 733 this.historyReader = new Ext.data.XmlReader({record : "entry"}, this.HistoryRecord); 734 735 this.init(config); 736 this.reloadFile("/", (function() 737 { 738 this.ready = true; 739 this.fireEvent(LABKEY.FileSystem.FILESYSTEM_EVENTS.ready, this); 740 }).createDelegate(this)); 741 }; 742 743 Ext.extend(LABKEY.FileSystem.WebdavFileSystem, LABKEY.FileSystem.AbstractFileSystem, 744 { 745 /** 746 * Returns the history for the file or directory at the supplied path 747 * @param config Configuration properties. 748 * @param {String} config.path Path to the file or directory 749 * @param {Function} config.success Success callback function. It will be called with the following arguments: 750 * <li>Filesystem: A reference to the filesystem</li> 751 * <li>Path: The path that was loaded</li> 752 * <li>History: An array of records representing the history</li> 753 * @param {Function} [config.failure] Error callback function. It will be called with the following arguments: 754 * <li>Response: the response object</li> 755 * @param {Object} [config.scope] The scope of the callback function 756 * @methodOf LABKEY.FileSystem.WebdavFileSystem# 757 */ 758 getHistory : function(config) 759 { 760 config.scope = config.scope || this; 761 var body = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<propfind xmlns=\"DAV:\"><prop><history/></prop></propfind>"; 762 763 var proxy = new Ext.data.HttpProxy( 764 { 765 url: this.concatPaths(this.prefixUrl, config.path), 766 xmlData : body, 767 method: "PROPFIND", 768 headers: {"Depth" : "0"} 769 }); 770 proxy.api.read.method = 'PROPFIND'; 771 772 var cb = function(response, args, success) 773 { 774 LABKEY.FileSystem.Util._processAjaxResponse(response); 775 if (success && Ext.isFunction(config.success)) 776 config.success.call(config.scope, args.filesystem, args.path, response.records); 777 else if (!success && Ext.isFunction(config.failure)) 778 config.failure.call(config.scope, response); 779 }; 780 proxy.request('read', null, {method:"PROPFIND", depth:"0", propname : this.propNames}, this.historyReader, cb, this, {filesystem:this, path:config.path}); 781 }, 782 783 /** 784 * Returns true if the current user can read the passed file 785 * In order to obtain the record for the desired file, recordFromCache() is normally used. 786 * @param {Ext.Record} record The Ext record associated with the file. See LABKEY.AbstractFileSystem.FileRecord for more information. 787 * @methodOf LABKEY.FileSystem.WebdavFileSystem# 788 */ 789 canRead : function(record) 790 { 791 var options = record.data.options; 792 return !options || -1 != options.indexOf('GET'); 793 }, 794 795 /** 796 * Returns true if the current user can write to the passed file or location 797 * In order to obtain the record for the desired file, recordFromCache() is normally used. 798 * @param {Ext.Record} record The Ext record associated with the file. See LABKEY.AbstractFileSystem.FileRecord for more information. 799 * @methodOf LABKEY.FileSystem.WebdavFileSystem# 800 */ 801 canWrite : function(record) 802 { 803 var options = record.data.options; 804 return !options || -1 != options.indexOf("PUT"); 805 }, 806 807 /** 808 * Returns true if the current user can create a folder in the passed location 809 * In order to obtain the record for the desired file, recordFromCache() is normally used. 810 * @param {Ext.Record} record The Ext record associated with the file. See LABKEY.AbstractFileSystem.FileRecord for more information. 811 * @methodOf LABKEY.FileSystem.WebdavFileSystem# 812 */ 813 canMkdir : function(record) 814 { 815 var options = record.data.options; 816 return !options || -1 != options.indexOf("MKCOL"); 817 }, 818 819 /** 820 * Returns true if the current user can delete the passed file. 821 * In order to obtain the record for the desired file, recordFromCache() is normally used. 822 * @param {Ext.Record} record The Ext record associated with the file. See LABKEY.AbstractFileSystem.FileRecord for more information. 823 * @methodOf LABKEY.FileSystem.WebdavFileSystem# 824 */ 825 canDelete : function(record) 826 { 827 var options = record.data.options; 828 return !options || -1 != options.indexOf('DELETE'); 829 }, 830 831 /** 832 * Returns true if the current user can move or rename the passed file 833 * In order to obtain the record for the desired file, recordFromCache() is normally used. 834 * @param {Ext.Record} record The Ext record associated with the file. See LABKEY.AbstractFileSystem.FileRecord for more information. 835 * @methodOf LABKEY.FileSystem.WebdavFileSystem# 836 */ 837 canMove : function(record) 838 { 839 var options = record.data.options; 840 return !options || -1 != options.indexOf('MOVE'); 841 }, 842 843 /** 844 * Can be used to delete a file or folder. 845 * @param config Configuration properties. 846 * @param {String} config.path The source file, which should be a URL relative to the fileSystem's rootPath 847 * @param {Boolean} config.isFile Set to true is this represent a file, as opposed to a folder 848 * @param {Function} config.success Success callback function. It will be called with the following arguments: 849 * <li>Filesystem: A reference to the filesystem</li> 850 * <li>Path: The path that was loaded</li> 851 * @param {Object} [config.failure] The error callback function. It will be called with the following arguments: 852 * <li>Response: The XMLHttpRequest object containing the response data.</li> 853 * <li>Options: The parameter to the request call.</li> 854 * @param {Object} [config.scope] The scope of the callback functions 855 * @methodOf LABKEY.FileSystem.WebdavFileSystem# 856 */ 857 deletePath : function(config) 858 { 859 config.scope = config.scope || this; 860 var resourcePath = this.concatPaths(this.prefixUrl, LABKEY.ActionURL.encodePath(config.path)); 861 var fileSystem = this; 862 var connection = new Ext.data.Connection(); 863 864 connection.request({ 865 method: "DELETE", 866 url: resourcePath, 867 scope: this, 868 success: function(response, options){ 869 var success = false; 870 LABKEY.FileSystem.Util._processAjaxResponse(response); 871 if (204 == response.status || 404 == response.status) // NO_CONTENT (success) 872 success = true; 873 else if (405 == response.status) // METHOD_NOT_ALLOWED 874 success = false; 875 876 if (success) 877 { 878 fileSystem._deleteListing(config.path, config.isFile); 879 880 if (Ext.isFunction(config.success)) 881 config.success.call(config.scope, fileSystem, config.path); 882 } 883 else { 884 if (Ext.isFunction(config.failure)) 885 config.failure.call(config.scope, response, options); 886 } 887 }, 888 failure: function(response, options) 889 { 890 var success = false; 891 LABKEY.FileSystem.Util._processAjaxResponse(response); 892 if (response.status == 404) //NOT_FOUND - not sure if this is the correct behavior or not 893 success = true; 894 895 if (!success && Ext.isFunction(config.failure)) 896 config.failure.call(config.scope, response, options); 897 if (success && Ext.isFunction(config.success)) 898 config.success.call(config.scope, fileSystem, config.path); 899 }, 900 headers: { 901 'Content-Type': 'application/json' 902 } 903 }); 904 905 return true; 906 }, 907 908 //private 909 _deleteListing: function(path, isFile) 910 { 911 var deleted = []; 912 913 //always delete the record itself 914 var record = this.recordFromCache(path); 915 if (record) { 916 isFile = record.data.file; 917 deleted.push(record); 918 var parentPath = this.getParentPath(path); 919 var parentFolder = this.directoryMap[parentPath]; 920 if (parentFolder){ 921 parentFolder.remove(record); 922 } 923 } 924 925 // find all paths modified by this delete 926 if (!isFile) 927 { 928 var pathsRemoved = []; 929 for (var a in this.directoryMap) 930 { 931 if (typeof a == 'string') 932 { 933 var idx = a.indexOf(path); 934 if (idx == 0) 935 { 936 pathsRemoved.push(a); 937 var r = this.recordFromCache(a); 938 if (r) 939 deleted.push(r); 940 941 parentPath = this.getParentPath(a); 942 parentFolder = this.directoryMap[parentPath]; 943 if (parentFolder && r){ 944 parentFolder.remove(r); 945 } 946 if (this.directoryMap[a] && this.directoryMap[a].length) 947 deleted = deleted.concat(this.directoryMap[a]); 948 } 949 } 950 } 951 this.uncacheListing(path); 952 } 953 954 deleted = Ext.unique(deleted); 955 this.fireEvent(LABKEY.FileSystem.FILESYSTEM_EVENTS.filesremoved, this, path, deleted); 956 }, 957 958 /** 959 * Can be used to rename a file or folder. This is simply a convenience wrapper for movePath(). 960 * @param config Configuration properties. 961 * @param {String} config.source The source file, which should be relative to the fileSystem's rootPath 962 * @param {String} config.destination The target path, which should be the full path for the new file, relative to the fileSystem's rootPath 963 * @param {Boolean} config.isFile Set to true if the path is a file, as opposed to a directory 964 * @param {Function} config.success Success callback function. It will be called with the following arguments: 965 * <li>Filesystem: A reference to the filesystem</li> 966 * <li>SourcePath: The path to the file/folder to be renamed</li> 967 * <li>DestPath: The new path for the renamed file/folder</li> 968 * @param {Object} [config.failure] The failure callback function. Will be called with the following arguments: 969 * <li>Response: The XMLHttpRequest object containing the response data.</li> 970 * <li>Options: The parameter to the request call.</li> 971 * @param {Object} [config.scope] The scope of the callback function 972 * @param {Boolean} [config.overwrite] If true, files at the target location 973 * @methodOf LABKEY.FileSystem.WebdavFileSystem# 974 * @example <script type="text/javascript"> 975 var fileSystem = new new LABKEY.FileSystem.WebdavFileSystem({ 976 containerPath: '/home', 977 filePath: '/@files' //optional. this is the same as the default 978 }); 979 980 fileSystem.on('ready', function(fileSystem){ 981 fileSystem.listFiles({ 982 path: '/mySubfolder/', 983 success: function(fileSystem, path, records){ 984 alert('It worked!'); 985 console.log(records); 986 }, 987 scope: this 988 }, this); 989 990 fileSystem.renamePath({ 991 source: 'myFile.xls', 992 destination: 'renamedFile.xls', 993 isFile: true, 994 scope: this 995 }); 996 997 998 //if you renamed a file in a subfolder, you can optionally supply the fileName only 999 //this file will be renamed to: '/subfolder/renamedFile.xls' 1000 fileSystem.renamePath({ 1001 source: '/subfolder/myFile.xls', 1002 destination: 'renamedFile.xls', 1003 isFile: true, 1004 scope: this 1005 }); 1006 1007 //or provide the entire path 1008 fileSystem.renamePath({ 1009 source: '/subfolder/myFile.xls', 1010 destination: '/subfolder/renamedFile.xls', 1011 isFile: true, 1012 scope: this 1013 }); 1014 }, this); 1015 1016 1017 </script> 1018 */ 1019 renamePath : function(config) 1020 { 1021 //allow user to submit either full path for rename, or just the new filename 1022 if (config.source.indexOf(this.separator) > -1 && config.destination.indexOf(this.separator) == -1){ 1023 config.destination = this.concatPaths(this.getParentPath(config.source), config.destination); 1024 } 1025 1026 this.movePath({ 1027 source: config.source, 1028 destination: config.destination, 1029 isFile: config.isFile, 1030 success: config.success, 1031 failure: config.failure, 1032 scope: config.scope, 1033 overwrite: config.overwrite 1034 }); 1035 }, 1036 1037 /** 1038 * Can be used to move a file or folder from one location to another. 1039 * @param config Configuration properties. 1040 * @param {String} config.path The source file, which should be a URL relative to the fileSystem's rootPath 1041 * @param {String} config.destination The target path, which should be a URL relative to the fileSystem's rootPath 1042 * @param {Boolean} config.isFile True if the file to move is a file, as opposed to a directory 1043 * @param {Function} config.success Success callback function. It will be called with the following arguments: 1044 * <li>Filesystem: A reference to the filesystem</li> 1045 * <li>SourcePath: The path that was loaded</li> 1046 * <li>DestPath: The path that was loaded</li> 1047 * @param {Object} [config.failure] The failure callback function. Will be called with the following arguments: 1048 * <li>Response: The XMLHttpRequest object containing the response data.</li> 1049 * <li>Options: The parameter to the request call.</li> 1050 * @param {Object} [config.scope] The scope of the callbacks 1051 * @param {Boolean} [config.overwrite] If true, files at the target location 1052 * @methodOf LABKEY.FileSystem.WebdavFileSystem# 1053 */ 1054 movePath : function(config) 1055 { 1056 config.scope = config.scope || this; 1057 1058 var resourcePath = this.concatPaths(this.prefixUrl, LABKEY.ActionURL.encodePath(config.source)); 1059 var destinationPath = this.concatPaths(this.prefixUrl, LABKEY.ActionURL.encodePath(config.destination)); 1060 var fileSystem = this; 1061 var connection = new Ext.data.Connection(); 1062 1063 var cfg = { 1064 method: "MOVE", 1065 url: resourcePath, 1066 scope: this, 1067 failure: function(response){ 1068 LABKEY.FileSystem.Util._processAjaxResponse(response); 1069 if(Ext.isFunction(config.failure)) 1070 config.failure.apply(config.scope, arguments); 1071 }, 1072 success: function(response, options){ 1073 LABKEY.FileSystem.Util._processAjaxResponse(response); 1074 var success = false; 1075 if (201 == response.status || 204 == response.status) //CREATED, NO_CONTENT (success) 1076 success = true; 1077 else 1078 success = false; 1079 1080 if (success) 1081 { 1082 //the move is performed as a delete / lazy-insert 1083 fileSystem._deleteListing(config.source, config.isFile); 1084 1085 var destParent = fileSystem.getParentPath(config.destination); 1086 fileSystem.uncacheListing(destParent); //this will cover uncaching children too 1087 1088 // TODO: maybe support a config option that will to force the fileSystem to 1089 // auto-reload this location, instead just uncaching and relying on consumers to do it?? 1090 this.fireEvent(LABKEY.FileSystem.FILESYSTEM_EVENTS.fileschanged, this, destParent); 1091 1092 if (Ext.isFunction(config.success)) 1093 config.success.call(config.scope, fileSystem, config.source, config.destination); 1094 } 1095 else { 1096 if (Ext.isFunction(config.failure)) 1097 config.failure.call(config.scope, response, options); 1098 } 1099 }, 1100 headers: { 1101 Destination: destinationPath, 1102 'Content-Type': 'application/json' 1103 } 1104 }; 1105 1106 if (config.overwrite) 1107 cfg.headers.Overwrite = 'T'; 1108 1109 connection.request(cfg); 1110 1111 return true; 1112 }, 1113 1114 /** 1115 * Will create a directory at the provided location. This does not perform permission checking, which can be done using canMkDir(). 1116 * @param config Configuration properties. 1117 * @param {String} config.path The path of the folder to create. This should be relative to the rootPath of the FileSystem. See constructor for examples. 1118 * @param {Function} config.success Success callback function. It will be called with the following arguments: 1119 * <li>Filesystem: A reference to the filesystem</li> 1120 * <li>Path: The path that was created</li> 1121 * @param {Object} [config.failure] Failure callback function. It will be called with the following arguments: 1122 * <li>Response: the response object</li> 1123 * <li>Options: The parameter to the request call.</li> 1124 * @param {Object} [config.scope] The scope of the callback functions. 1125 * @methodOf LABKEY.FileSystem.WebdavFileSystem# 1126 */ 1127 createDirectory : function(config) 1128 { 1129 var fileSystem = this; 1130 config.scope = config.scope || this; 1131 1132 var resourcePath = this.concatPaths(this.prefixUrl, config.path); 1133 var connection = new Ext.data.Connection(); 1134 1135 connection.request({ 1136 method: "MKCOL", 1137 url: resourcePath, 1138 scope: this, 1139 success: function(response, options){ 1140 LABKEY.FileSystem.Util._processAjaxResponse(response); 1141 var success = false; 1142 if (200 == response.status || 201 == response.status){ // OK, CREATED 1143 if(!response.responseText) 1144 success = true; 1145 else 1146 success = false; 1147 } 1148 else if (405 == response.status) // METHOD_NOT_ALLOWED 1149 success = false; 1150 1151 if (success && Ext.isFunction(config.success)) 1152 config.success.call(config.scope, fileSystem, config.path); 1153 if (!success && Ext.isFunction(config.failure)) 1154 config.failure.call(config.scope, response, options); 1155 }, 1156 failure: function(response){ 1157 LABKEY.FileSystem.Util._processAjaxResponse(response); 1158 if (Ext.isFunction(config.failure)) 1159 config.failure.apply(config.scope, arguments); 1160 }, 1161 headers: { 1162 'Content-Type': 'application/json' 1163 } 1164 }); 1165 1166 return true; 1167 }, 1168 1169 //private 1170 // not sure why both this and reloadFiles() exist? reloadFile() seems to be used internally only 1171 reloadFile : function(path, callback) 1172 { 1173 var url = this.concatPaths(this.prefixUrl, LABKEY.ActionURL.encodePath(path)); 1174 this.connection.url = url; 1175 var args = {url: url, path: path, callback:callback}; 1176 this.proxy.doRequest("read", null, {method:"PROPFIND",depth:"0", propname : this.propNames}, this.transferReader, this.processFile, this, args); 1177 return true; 1178 }, 1179 1180 //private 1181 _updateRecord : function(update) 1182 { 1183 var path = update.data.path; 1184 if (path == '/') 1185 { 1186 Ext.apply(this.rootRecord.data, update.data); 1187 } 1188 else 1189 { 1190 var record = this.recordFromCache(path); 1191 if (record) 1192 Ext.apply(record.data, update.data); 1193 } 1194 }, 1195 1196 //private 1197 processFile : function(result, args, success) 1198 { 1199 var update = null; 1200 if (success && result && !Ext.isArray(result.records)) 1201 success = false; 1202 if (success && result.records.length == 1) 1203 { 1204 update = result.records[0]; 1205 this._updateRecord(update); 1206 } 1207 1208 if (Ext.isFunction(args.callback)) 1209 args.callback(this, success && null != update, args.path, update); 1210 }, 1211 1212 //private 1213 uncacheListing : function(record) 1214 { 1215 var path = (typeof record == "string") ? record : record.data.path; 1216 1217 // want to uncache all subfolders of the parent folder 1218 Ext.iterate(this.directoryMap, function(key, value) { 1219 if (Ext.isString(key)) { 1220 if (key.indexOf(path) === 0) { 1221 this.directoryMap[key] = null; 1222 } 1223 } 1224 }, this); 1225 1226 var args = this.pendingPropfind[path]; 1227 if (args && args.transId) 1228 { 1229 this.connection.abort(args.transId); 1230 this.connection.url = args.url; 1231 this.proxy.doRequest("read", null, {method:"PROPFIND",depth:"1", propname : this.propNames}, this.transferReader, this.processFiles, this, args); 1232 args.transId = this.connection.transId; 1233 } 1234 }, 1235 1236 //private 1237 reloadFiles : function(config) 1238 { 1239 config.scope = config.scope || this; 1240 1241 var cb = { 1242 success: config.success, 1243 failure: config.failure, 1244 scope: config.scope 1245 }; 1246 1247 var args = this.pendingPropfind[config.path]; 1248 if (args) 1249 { 1250 args.callbacks.push(cb); 1251 return; 1252 } 1253 1254 var url = this.concatPaths(this.prefixUrl, LABKEY.ActionURL.encodePath(config.path)); 1255 this.connection.url = url; 1256 this.pendingPropfind[config.path] = args = {url: url, path: config.path, callbacks:[cb]}; 1257 this.proxy.doRequest("read", null, {method:"PROPFIND",depth:"1", propname : this.propNames}, this.transferReader, this.processFiles, this, args); 1258 args.transId = this.connection.transId; 1259 return true; 1260 }, 1261 1262 //private 1263 processFiles : function(result, args, success) 1264 { 1265 delete this.pendingPropfind[args.path]; 1266 1267 var path = args.path; 1268 1269 var directory = null; 1270 var listing = []; 1271 if (success && result && !Ext.isArray(result.records)) 1272 success = false; 1273 if (success) 1274 { 1275 var records = result.records; 1276 for (var r=0 ; r<records.length ; r++) 1277 { 1278 var record = records[r]; 1279 if (record.data.path == path) 1280 directory = record; 1281 else 1282 listing.push(record); 1283 } 1284 if (directory) 1285 this._updateRecord(directory); 1286 this._addFiles(path, listing); 1287 } 1288 1289 var callbacks = args.callbacks; 1290 for (var i=0 ; i<callbacks.length ; i++) 1291 { 1292 var callback = callbacks[i]; 1293 if (Ext.isFunction(callback)) { 1294 callback(this, path, listing); 1295 } 1296 else if (typeof callback == 'object') { 1297 var scope = callback.scope || this; 1298 if (success && Ext.isFunction(callback.success)) 1299 callback.success.call(scope, this, path, listing); 1300 else if (!success && Ext.isFunction(callback.failure)) 1301 callback.failure.call(scope, args.transId.conn); 1302 } 1303 } 1304 }, 1305 1306 //private 1307 init : function(config) 1308 { 1309 //support either containerPath + path OR baseUrl (which is the concatenation of these 2) 1310 this.filePath = this.filePath || ''; 1311 this.fileLink = this.fileLink || ''; 1312 this.containerPath = this.containerPath || LABKEY.ActionURL.getContainer(); 1313 1314 if (!config.baseUrl){ 1315 this.baseUrl = this.concatPaths(LABKEY.contextPath + "/_webdav", encodeURI(this.containerPath)); 1316 this.baseUrl = this.concatPaths(this.baseUrl, encodeURI(this.filePath)); 1317 this.baseUrl = this.concatPaths(this.baseUrl, encodeURI(this.fileLink)); 1318 } 1319 1320 var prefix = this.concatPaths(this.baseUrl, this.rootPath); 1321 if (prefix.length > 0 && prefix.charAt(prefix.length-1) == this.separator) 1322 prefix = prefix.substring(0,prefix.length-1); 1323 this.prefixUrl = prefix; 1324 this.pendingPropfind = {}; 1325 1326 var prefixDecode = decodeURIComponent(prefix); 1327 1328 var getURI = function(v,rec) 1329 { 1330 var uri = rec.uriOBJECT || new LABKEY.URI(v); 1331 if (!Ext.isIE && !rec.uriOBJECT) 1332 try {rec.uriOBJECT = uri;} catch (e) {}; 1333 return uri; 1334 }; 1335 1336 this.propNames = ["creationdate", "displayname", "createdby", "getlastmodified", "modifiedby", "getcontentlength", 1337 "getcontenttype", "getetag", "resourcetype", "source", "path", "iconHref", "options", "absolutePath"]; 1338 1339 if (config.extraPropNames && config.extraPropNames.length) 1340 this.propNames = this.propNames.concat(config.extraPropNames); 1341 1342 var recordCfg = [ 1343 {name: 'uri', mapping: 'href', 1344 convert : function(v, rec) 1345 { 1346 var uri = getURI(v,rec); 1347 return uri ? uri.href : ""; 1348 } 1349 }, 1350 {name: 'fileLink', mapping: 'href', 1351 convert : function(v, rec) 1352 { 1353 var uri = getURI(v,rec); 1354 1355 if (uri && uri.file) 1356 return Ext.DomHelper.markup({ 1357 tag :'a', 1358 href : Ext.util.Format.htmlEncode(uri.href + '?contentDisposition=attachment'), 1359 html : Ext.util.Format.htmlEncode(decodeURIComponent(uri.file))}); 1360 else 1361 return ''; 1362 } 1363 }, 1364 {name: 'path', mapping: 'href', 1365 convert : function (v, rec) 1366 { 1367 var uri = getURI(v,rec); 1368 var path = decodeURIComponent(uri.pathname); 1369 if (path.length >= prefixDecode.length && path.substring(0,prefixDecode.length) == prefixDecode) 1370 path = path.substring(prefixDecode.length); 1371 return path; 1372 } 1373 }, 1374 {name: 'name', mapping: 'propstat/prop/displayname', sortType:'asUCString'}, 1375 {name: 'fileExt', mapping: 'propstat/prop/displayname', 1376 convert : function (v, rec) 1377 { 1378 // parse the file extension from the file name 1379 var idx = v.lastIndexOf('.'); 1380 if (idx != -1) 1381 return v.substring(idx+1); 1382 return ''; 1383 } 1384 }, 1385 1386 {name: 'file', mapping: 'href', type: 'boolean', 1387 convert : function (v, rec) 1388 { 1389 // UNDONE: look for <collection> 1390 var uri = getURI(v, rec); 1391 var path = uri.pathname; 1392 return path.length > 0 && path.charAt(path.length-1) != '/'; 1393 } 1394 }, 1395 {name: 'created', mapping: 'propstat/prop/creationdate', type: 'date', dateFormat : "c"}, 1396 {name: 'createdBy', mapping: 'propstat/prop/createdby'}, 1397 {name: 'modified', mapping: 'propstat/prop/getlastmodified', type: 'date'}, 1398 {name: 'modifiedBy', mapping: 'propstat/prop/modifiedby'}, 1399 {name: 'size', mapping: 'propstat/prop/getcontentlength', type: 'int'}, 1400 {name: 'absolutePath', mapping: 'propstat/prop/absolutePath', type: 'string'}, 1401 {name: 'iconHref'}, 1402 {name: 'contentType', mapping: 'propstat/prop/getcontenttype'}, 1403 {name: 'options'} 1404 ]; 1405 1406 if (config.extraDataFields && config.extraDataFields.length) 1407 recordCfg = recordCfg.concat(config.extraDataFields); 1408 1409 this.FileRecord = Ext.data.Record.create(recordCfg); 1410 this.connection = new Ext.data.Connection({method: "GET", timeout: 600000, headers: {"Method" : "PROPFIND", "Depth" : "1", propname : this.propNames}}); 1411 this.proxy = new Ext.data.HttpProxy(this.connection); 1412 this.transferReader = new Ext.data.XmlReader({record : "response", id : "href"}, this.FileRecord); 1413 1414 this.rootRecord = new this.FileRecord({ 1415 id:"/", 1416 path:"/", 1417 name: this.rootName, 1418 file:false, 1419 uri:this.prefixUrl, 1420 iconHref: LABKEY.contextPath + "/_images/labkey.png" 1421 }, "/"); 1422 } 1423 }); 1424 1425 1426 1427