Home | History | Annotate | Download | only in workspace
      1 /*
      2  * Copyright (C) 2013 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 /**
     32  * @constructor
     33  * @param {!WebInspector.IsolatedFileSystemManager} manager
     34  * @param {string} path
     35  * @param {string} name
     36  * @param {string} rootURL
     37  */
     38 WebInspector.IsolatedFileSystem = function(manager, path, name, rootURL)
     39 {
     40     this._manager = manager;
     41     this._path = path;
     42     this._name = name;
     43     this._rootURL = rootURL;
     44 }
     45 
     46 /**
     47  * @param {!FileError} error
     48  * @return {string}
     49  */
     50 WebInspector.IsolatedFileSystem.errorMessage = function(error)
     51 {
     52     return WebInspector.UIString("File system error: %s", error.message);
     53 }
     54 
     55 /**
     56  * @param {string} fileSystemPath
     57  * @return {string}
     58  */
     59 WebInspector.IsolatedFileSystem.normalizePath = function(fileSystemPath)
     60 {
     61     if (WebInspector.isWin())
     62         return fileSystemPath.replace(/\\/g, "/");
     63     return fileSystemPath;
     64 }
     65 
     66 WebInspector.IsolatedFileSystem.prototype = {
     67     /**
     68      * @return {string}
     69      */
     70     path: function()
     71     {
     72         return this._path;
     73     },
     74 
     75     /**
     76      * @return {string}
     77      */
     78     normalizedPath: function()
     79     {
     80         if (this._normalizedPath)
     81             return this._normalizedPath;
     82         this._normalizedPath = WebInspector.IsolatedFileSystem.normalizePath(this._path);
     83         return this._normalizedPath;
     84     },
     85 
     86     /**
     87      * @return {string}
     88      */
     89     name: function()
     90     {
     91         return this._name;
     92     },
     93 
     94     /**
     95      * @return {string}
     96      */
     97     rootURL: function()
     98     {
     99         return this._rootURL;
    100     },
    101 
    102     /**
    103      * @param {function(?DOMFileSystem)} callback
    104      */
    105     _requestFileSystem: function(callback)
    106     {
    107         this._manager.requestDOMFileSystem(this._path, callback);
    108     },
    109 
    110     /**
    111      * @param {string} path
    112      * @param {function(string)} fileCallback
    113      * @param {function()=} finishedCallback
    114      */
    115     requestFilesRecursive: function(path, fileCallback, finishedCallback)
    116     {
    117         var domFileSystem;
    118         var pendingRequests = 0;
    119         this._requestFileSystem(fileSystemLoaded.bind(this));
    120         /**
    121          * @param {?DOMFileSystem} fs
    122          * @this {WebInspector.IsolatedFileSystem}
    123          */
    124         function fileSystemLoaded(fs)
    125         {
    126             domFileSystem = /** @type {!DOMFileSystem} */ (fs);
    127             console.assert(domFileSystem);
    128             ++pendingRequests;
    129             this._requestEntries(domFileSystem, path, innerCallback.bind(this));
    130         }
    131 
    132         /**
    133          * @param {!Array.<!FileEntry>} entries
    134          * @this {WebInspector.IsolatedFileSystem}
    135          */
    136         function innerCallback(entries)
    137         {
    138             for (var i = 0; i < entries.length; ++i) {
    139                 var entry = entries[i];
    140                 if (!entry.isDirectory) {
    141                     if (this._manager.mapping().isFileExcluded(this._path, entry.fullPath))
    142                         continue;
    143                     fileCallback(entry.fullPath.substr(1));
    144                 }
    145                 else {
    146                     if (this._manager.mapping().isFileExcluded(this._path, entry.fullPath + "/"))
    147                         continue;
    148                     ++pendingRequests;
    149                     this._requestEntries(domFileSystem, entry.fullPath, innerCallback.bind(this));
    150                 }
    151             }
    152             if (finishedCallback && (--pendingRequests === 0))
    153                 finishedCallback();
    154         }
    155     },
    156 
    157     /**
    158      * @param {string} path
    159      * @param {?string} name
    160      * @param {function(?string)} callback
    161      */
    162     createFile: function(path, name, callback)
    163     {
    164         this._requestFileSystem(fileSystemLoaded.bind(this));
    165         var newFileIndex = 1;
    166         if (!name)
    167             name = "NewFile";
    168         var nameCandidate;
    169 
    170         /**
    171          * @param {?DOMFileSystem} fs
    172          * @this {WebInspector.IsolatedFileSystem}
    173          */
    174         function fileSystemLoaded(fs)
    175         {
    176             var domFileSystem = /** @type {!DOMFileSystem} */ (fs);
    177             console.assert(domFileSystem);
    178             domFileSystem.root.getDirectory(path, null, dirEntryLoaded.bind(this), errorHandler.bind(this));
    179         }
    180 
    181         /**
    182          * @param {!DirectoryEntry} dirEntry
    183          * @this {WebInspector.IsolatedFileSystem}
    184          */
    185         function dirEntryLoaded(dirEntry)
    186         {
    187             var nameCandidate = name;
    188             if (newFileIndex > 1)
    189                 nameCandidate += newFileIndex;
    190             ++newFileIndex;
    191             dirEntry.getFile(nameCandidate, { create: true, exclusive: true }, fileCreated, fileCreationError.bind(this));
    192 
    193             function fileCreated(entry)
    194             {
    195                 callback(entry.fullPath.substr(1));
    196             }
    197 
    198             /**
    199              * @this {WebInspector.IsolatedFileSystem}
    200              */
    201             function fileCreationError(error)
    202             {
    203                 if (error.code === FileError.INVALID_MODIFICATION_ERR) {
    204                     dirEntryLoaded.call(this, dirEntry);
    205                     return;
    206                 }
    207 
    208                 var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error);
    209                 console.error(errorMessage + " when testing if file exists '" + (this._path + "/" + path + "/" + nameCandidate) + "'");
    210                 callback(null);
    211             }
    212         }
    213 
    214         /**
    215          * @this {WebInspector.IsolatedFileSystem}
    216          */
    217         function errorHandler(error)
    218         {
    219             var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error);
    220             var filePath = this._path + "/" + path;
    221             if (nameCandidate)
    222                 filePath += "/" + nameCandidate;
    223             console.error(errorMessage + " when getting content for file '" + (filePath) + "'");
    224             callback(null);
    225         }
    226     },
    227 
    228     /**
    229      * @param {string} path
    230      */
    231     deleteFile: function(path)
    232     {
    233         this._requestFileSystem(fileSystemLoaded.bind(this));
    234 
    235         /**
    236          * @param {?DOMFileSystem} fs
    237          * @this {WebInspector.IsolatedFileSystem}
    238          */
    239         function fileSystemLoaded(fs)
    240         {
    241             var domFileSystem = /** @type {!DOMFileSystem} */ (fs);
    242             console.assert(domFileSystem);
    243             domFileSystem.root.getFile(path, null, fileEntryLoaded.bind(this), errorHandler.bind(this));
    244         }
    245 
    246         /**
    247          * @param {!FileEntry} fileEntry
    248          * @this {WebInspector.IsolatedFileSystem}
    249          */
    250         function fileEntryLoaded(fileEntry)
    251         {
    252             fileEntry.remove(fileEntryRemoved, errorHandler.bind(this));
    253         }
    254 
    255         function fileEntryRemoved()
    256         {
    257         }
    258 
    259         /**
    260          * @param {!FileError} error
    261          * @this {WebInspector.IsolatedFileSystem}
    262          */
    263         function errorHandler(error)
    264         {
    265             var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error);
    266             console.error(errorMessage + " when deleting file '" + (this._path + "/" + path) + "'");
    267         }
    268     },
    269 
    270     /**
    271      * @param {string} path
    272      * @param {function(?Date, ?number)} callback
    273      */
    274     requestMetadata: function(path, callback)
    275     {
    276         this._requestFileSystem(fileSystemLoaded);
    277 
    278         /**
    279          * @param {?DOMFileSystem} fs
    280          */
    281         function fileSystemLoaded(fs)
    282         {
    283             var domFileSystem = /** @type {!DOMFileSystem} */ (fs);
    284             console.assert(domFileSystem);
    285             domFileSystem.root.getFile(path, null, fileEntryLoaded, errorHandler);
    286         }
    287 
    288         /**
    289          * @param {!FileEntry} entry
    290          */
    291         function fileEntryLoaded(entry)
    292         {
    293             entry.getMetadata(successHandler, errorHandler);
    294         }
    295 
    296         /**
    297          * @param {!Metadata} metadata
    298          */
    299         function successHandler(metadata)
    300         {
    301             callback(metadata.modificationTime, metadata.size);
    302         }
    303 
    304         /**
    305          * @param {!FileError} error
    306          */
    307         function errorHandler(error)
    308         {
    309             callback(null, null);
    310         }
    311     },
    312 
    313     /**
    314      * @param {string} path
    315      * @param {function(?string)} callback
    316      */
    317     requestFileContent: function(path, callback)
    318     {
    319         this._requestFileSystem(fileSystemLoaded.bind(this));
    320 
    321         /**
    322          * @param {?DOMFileSystem} fs
    323          * @this {WebInspector.IsolatedFileSystem}
    324          */
    325         function fileSystemLoaded(fs)
    326         {
    327             var domFileSystem = /** @type {!DOMFileSystem} */ (fs);
    328             console.assert(domFileSystem);
    329             domFileSystem.root.getFile(path, null, fileEntryLoaded.bind(this), errorHandler.bind(this));
    330         }
    331 
    332         /**
    333          * @param {!FileEntry} entry
    334          * @this {WebInspector.IsolatedFileSystem}
    335          */
    336         function fileEntryLoaded(entry)
    337         {
    338             entry.file(fileLoaded, errorHandler.bind(this));
    339         }
    340 
    341         /**
    342          * @param {!Blob} file
    343          */
    344         function fileLoaded(file)
    345         {
    346             var reader = new FileReader();
    347             reader.onloadend = readerLoadEnd;
    348             reader.readAsText(file);
    349         }
    350 
    351         /**
    352          * @this {!FileReader}
    353          */
    354         function readerLoadEnd()
    355         {
    356             callback(/** @type {string} */ (this.result));
    357         }
    358 
    359         /**
    360          * @this {WebInspector.IsolatedFileSystem}
    361          */
    362         function errorHandler(error)
    363         {
    364             if (error.code === FileError.NOT_FOUND_ERR) {
    365                 callback(null);
    366                 return;
    367             }
    368 
    369             var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error);
    370             console.error(errorMessage + " when getting content for file '" + (this._path + "/" + path) + "'");
    371             callback(null);
    372         }
    373     },
    374 
    375     /**
    376      * @param {string} path
    377      * @param {string} content
    378      * @param {function()} callback
    379      */
    380     setFileContent: function(path, content, callback)
    381     {
    382         this._requestFileSystem(fileSystemLoaded.bind(this));
    383         WebInspector.userMetrics.FileSavedInWorkspace.record();
    384 
    385         /**
    386          * @param {?DOMFileSystem} fs
    387          * @this {WebInspector.IsolatedFileSystem}
    388          */
    389         function fileSystemLoaded(fs)
    390         {
    391             var domFileSystem = /** @type {!DOMFileSystem} */ (fs);
    392             console.assert(domFileSystem);
    393             domFileSystem.root.getFile(path, { create: true }, fileEntryLoaded.bind(this), errorHandler.bind(this));
    394         }
    395 
    396         /**
    397          * @param {!FileEntry} entry
    398          * @this {WebInspector.IsolatedFileSystem}
    399          */
    400         function fileEntryLoaded(entry)
    401         {
    402             entry.createWriter(fileWriterCreated.bind(this), errorHandler.bind(this));
    403         }
    404 
    405         /**
    406          * @param {!FileWriter} fileWriter
    407          * @this {WebInspector.IsolatedFileSystem}
    408          */
    409         function fileWriterCreated(fileWriter)
    410         {
    411             fileWriter.onerror = errorHandler.bind(this);
    412             fileWriter.onwriteend = fileTruncated;
    413             fileWriter.truncate(0);
    414 
    415             function fileTruncated()
    416             {
    417                 fileWriter.onwriteend = writerEnd;
    418                 var blob = new Blob([content], { type: "text/plain" });
    419                 fileWriter.write(blob);
    420             }
    421         }
    422 
    423         function writerEnd()
    424         {
    425             callback();
    426         }
    427 
    428         /**
    429          * @this {WebInspector.IsolatedFileSystem}
    430          */
    431         function errorHandler(error)
    432         {
    433             var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error);
    434             console.error(errorMessage + " when setting content for file '" + (this._path + "/" + path) + "'");
    435             callback();
    436         }
    437     },
    438 
    439     /**
    440      * @param {string} path
    441      * @param {string} newName
    442      * @param {function(boolean, string=)} callback
    443      */
    444     renameFile: function(path, newName, callback)
    445     {
    446         newName = newName ? newName.trim() : newName;
    447         if (!newName || newName.indexOf("/") !== -1) {
    448             callback(false);
    449             return;
    450         }
    451         var fileEntry;
    452         var dirEntry;
    453         var newFileEntry;
    454         this._requestFileSystem(fileSystemLoaded.bind(this));
    455 
    456         /**
    457          * @param {?DOMFileSystem} fs
    458          * @this {WebInspector.IsolatedFileSystem}
    459          */
    460         function fileSystemLoaded(fs)
    461         {
    462             var domFileSystem = /** @type {!DOMFileSystem} */ (fs);
    463             console.assert(domFileSystem);
    464             domFileSystem.root.getFile(path, null, fileEntryLoaded.bind(this), errorHandler.bind(this));
    465         }
    466 
    467         /**
    468          * @param {!FileEntry} entry
    469          * @this {WebInspector.IsolatedFileSystem}
    470          */
    471         function fileEntryLoaded(entry)
    472         {
    473             if (entry.name === newName) {
    474                 callback(false);
    475                 return;
    476             }
    477 
    478             fileEntry = entry;
    479             fileEntry.getParent(dirEntryLoaded.bind(this), errorHandler.bind(this));
    480         }
    481 
    482         /**
    483          * @param {!Entry} entry
    484          * @this {WebInspector.IsolatedFileSystem}
    485          */
    486         function dirEntryLoaded(entry)
    487         {
    488             dirEntry = entry;
    489             dirEntry.getFile(newName, null, newFileEntryLoaded, newFileEntryLoadErrorHandler.bind(this));
    490         }
    491 
    492         /**
    493          * @param {!FileEntry} entry
    494          */
    495         function newFileEntryLoaded(entry)
    496         {
    497             callback(false);
    498         }
    499 
    500         /**
    501          * @this {WebInspector.IsolatedFileSystem}
    502          */
    503         function newFileEntryLoadErrorHandler(error)
    504         {
    505             if (error.code !== FileError.NOT_FOUND_ERR) {
    506                 callback(false);
    507                 return;
    508             }
    509             fileEntry.moveTo(dirEntry, newName, fileRenamed, errorHandler.bind(this));
    510         }
    511 
    512         /**
    513          * @param {!FileEntry} entry
    514          */
    515         function fileRenamed(entry)
    516         {
    517             callback(true, entry.name);
    518         }
    519 
    520         /**
    521          * @this {WebInspector.IsolatedFileSystem}
    522          */
    523         function errorHandler(error)
    524         {
    525             var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error);
    526             console.error(errorMessage + " when renaming file '" + (this._path + "/" + path) + "' to '" + newName + "'");
    527             callback(false);
    528         }
    529     },
    530 
    531     /**
    532      * @param {!DirectoryEntry} dirEntry
    533      * @param {function(!Array.<!FileEntry>)} callback
    534      */
    535     _readDirectory: function(dirEntry, callback)
    536     {
    537         var dirReader = dirEntry.createReader();
    538         var entries = [];
    539 
    540         function innerCallback(results)
    541         {
    542             if (!results.length)
    543                 callback(entries.sort());
    544             else {
    545                 entries = entries.concat(toArray(results));
    546                 dirReader.readEntries(innerCallback, errorHandler);
    547             }
    548         }
    549 
    550         function toArray(list)
    551         {
    552             return Array.prototype.slice.call(list || [], 0);
    553         }
    554 
    555         dirReader.readEntries(innerCallback, errorHandler);
    556 
    557         function errorHandler(error)
    558         {
    559             var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error);
    560             console.error(errorMessage + " when reading directory '" + dirEntry.fullPath + "'");
    561             callback([]);
    562         }
    563     },
    564 
    565     /**
    566      * @param {!DOMFileSystem} domFileSystem
    567      * @param {string} path
    568      * @param {function(!Array.<!FileEntry>)} callback
    569      */
    570     _requestEntries: function(domFileSystem, path, callback)
    571     {
    572         domFileSystem.root.getDirectory(path, null, innerCallback.bind(this), errorHandler);
    573 
    574         /**
    575          * @param {!DirectoryEntry} dirEntry
    576          * @this {WebInspector.IsolatedFileSystem}
    577          */
    578         function innerCallback(dirEntry)
    579         {
    580             this._readDirectory(dirEntry, callback)
    581         }
    582 
    583         function errorHandler(error)
    584         {
    585             var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error);
    586             console.error(errorMessage + " when requesting entry '" + path + "'");
    587             callback([]);
    588         }
    589     }
    590 }
    591