Home | History | Annotate | Download | only in front_end
      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  */
     36 WebInspector.IsolatedFileSystem = function(manager, path, name, rootURL)
     37 {
     38     this._manager = manager;
     39     this._path = path;
     40     this._name = name;
     41     this._rootURL = rootURL;
     42 }
     43 
     44 WebInspector.IsolatedFileSystem.errorMessage = function(error)
     45 {
     46     var msg;
     47     switch (error.code) {
     48     case FileError.QUOTA_EXCEEDED_ERR:
     49         msg = "QUOTA_EXCEEDED_ERR";
     50         break;
     51     case FileError.NOT_FOUND_ERR:
     52         msg = "NOT_FOUND_ERR";
     53         break;
     54     case FileError.SECURITY_ERR:
     55         msg = "SECURITY_ERR";
     56         break;
     57     case FileError.INVALID_MODIFICATION_ERR:
     58         msg = "INVALID_MODIFICATION_ERR";
     59         break;
     60     case FileError.INVALID_STATE_ERR:
     61         msg = "INVALID_STATE_ERR";
     62         break;
     63     default:
     64         msg = "Unknown Error";
     65         break;
     66     };
     67 
     68     return "File system error: " + msg;
     69 }
     70 
     71 WebInspector.IsolatedFileSystem.prototype = {
     72     /**
     73      * @return {string}
     74      */
     75     path: function()
     76     {
     77         return this._path;
     78     },
     79 
     80     /**
     81      * @return {string}
     82      */
     83     name: function()
     84     {
     85         return this._name;
     86     },
     87 
     88     /**
     89      * @return {string}
     90      */
     91     rootURL: function()
     92     {
     93         return this._rootURL;
     94     },
     95 
     96     /**
     97      * @param {function(DOMFileSystem)} callback
     98      */
     99     _requestFileSystem: function(callback)
    100     {
    101         this._manager.requestDOMFileSystem(this._path, callback);
    102     },
    103 
    104     /**
    105      * @param {string} path
    106      * @param {function(string)} callback
    107      */
    108     requestFilesRecursive: function(path, callback)
    109     {
    110         this._requestFileSystem(fileSystemLoaded.bind(this));
    111 
    112         var domFileSystem;
    113         /**
    114          * @param {DOMFileSystem} fs
    115          */
    116         function fileSystemLoaded(fs)
    117         {
    118             domFileSystem = fs;
    119             this._requestEntries(domFileSystem, path, innerCallback.bind(this));
    120         }
    121 
    122         /**
    123          * @param {Array.<FileEntry>} entries
    124          */
    125         function innerCallback(entries)
    126         {
    127             for (var i = 0; i < entries.length; ++i) {
    128                 var entry = entries[i];
    129                 if (!entry.isDirectory)
    130                     callback(entry.fullPath.substr(1));
    131                 else
    132                     this._requestEntries(domFileSystem, entry.fullPath, innerCallback.bind(this));
    133             }
    134         }
    135     },
    136 
    137     /**
    138      * @param {string} path
    139      * @param {?string} name
    140      * @param {function(?string)} callback
    141      */
    142     createFile: function(path, name, callback)
    143     {
    144         this._requestFileSystem(fileSystemLoaded.bind(this));
    145         var newFileIndex = 1;
    146         if (!name)
    147             name = "NewFile";
    148         var nameCandidate;
    149 
    150         /**
    151          * @param {DOMFileSystem} domFileSystem
    152          */
    153         function fileSystemLoaded(domFileSystem)
    154         {
    155             domFileSystem.root.getDirectory(path, null, dirEntryLoaded.bind(this), errorHandler.bind(this));
    156         }
    157 
    158         /**
    159          * @param {DirectoryEntry} dirEntry
    160          */
    161         function dirEntryLoaded(dirEntry)
    162         {
    163             var nameCandidate = name;
    164             if (newFileIndex > 1)
    165                 nameCandidate += newFileIndex;
    166             ++newFileIndex;
    167             dirEntry.getFile(nameCandidate, { create: true, exclusive: true }, fileCreated, fileCreationError);
    168 
    169             function fileCreated(entry)
    170             {
    171                 callback(entry.fullPath.substr(1));
    172             }
    173 
    174             function fileCreationError(error)
    175             {
    176                 if (error.code === FileError.INVALID_MODIFICATION_ERR) {
    177                     dirEntryLoaded(dirEntry);
    178                     return;
    179                 }
    180 
    181                 var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error);
    182                 console.error(errorMessage + " when testing if file exists '" + (this._path + "/" + path + "/" + nameCandidate) + "'");
    183                 callback(null);
    184             }
    185         }
    186 
    187         function errorHandler(error)
    188         {
    189             var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error);
    190             var filePath = this._path + "/" + path;
    191             if (nameCandidate)
    192                 filePath += "/" + nameCandidate;
    193             console.error(errorMessage + " when getting content for file '" + (filePath) + "'");
    194             callback(null);
    195         }
    196     },
    197 
    198     /**
    199      * @param {string} path
    200      */
    201     deleteFile: function(path)
    202     {
    203         this._requestFileSystem(fileSystemLoaded.bind(this));
    204 
    205         /**
    206          * @param {DOMFileSystem} domFileSystem
    207          */
    208         function fileSystemLoaded(domFileSystem)
    209         {
    210             domFileSystem.root.getFile(path, null, fileEntryLoaded.bind(this), errorHandler.bind(this));
    211         }
    212 
    213         /**
    214          * @param {FileEntry} fileEntry
    215          */
    216         function fileEntryLoaded(fileEntry)
    217         {
    218             fileEntry.remove(fileEntryRemoved.bind(this), errorHandler.bind(this));
    219         }
    220 
    221         function fileEntryRemoved()
    222         {
    223         }
    224 
    225         /**
    226          * @param {FileError} error
    227          */
    228         function errorHandler(error)
    229         {
    230             var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error);
    231             console.error(errorMessage + " when deleting file '" + (this._path + "/" + path) + "'");
    232         }
    233     },
    234 
    235     /**
    236      * @param {string} path
    237      * @param {function(?Date, ?number)} callback
    238      */
    239     requestMetadata: function(path, callback)
    240     {
    241         this._requestFileSystem(fileSystemLoaded.bind(this));
    242 
    243         /**
    244          * @param {DOMFileSystem} domFileSystem
    245          */
    246         function fileSystemLoaded(domFileSystem)
    247         {
    248             domFileSystem.root.getFile(path, null, fileEntryLoaded, errorHandler);
    249         }
    250 
    251         /**
    252          * @param {FileEntry} entry
    253          */
    254         function fileEntryLoaded(entry)
    255         {
    256             entry.getMetadata(successHandler, errorHandler);
    257         }
    258 
    259         /**
    260          * @param {Metadata} metadata
    261          */
    262         function successHandler(metadata)
    263         {
    264             callback(metadata.modificationTime, metadata.size);
    265         }
    266 
    267         /**
    268          * @param {FileError} error
    269          */
    270         function errorHandler(error)
    271         {
    272             callback(null, null);
    273         }
    274     },
    275 
    276     /**
    277      * @param {string} path
    278      * @param {function(?string)} callback
    279      */
    280     requestFileContent: function(path, callback)
    281     {
    282         this._requestFileSystem(fileSystemLoaded.bind(this));
    283 
    284         /**
    285          * @param {DOMFileSystem} domFileSystem
    286          */
    287         function fileSystemLoaded(domFileSystem)
    288         {
    289             domFileSystem.root.getFile(path, null, fileEntryLoaded, errorHandler.bind(this));
    290         }
    291 
    292         /**
    293          * @param {FileEntry} entry
    294          */
    295         function fileEntryLoaded(entry)
    296         {
    297             entry.file(fileLoaded, errorHandler.bind(this));
    298         }
    299 
    300         /**
    301          * @param {!Blob} file
    302          */
    303         function fileLoaded(file)
    304         {
    305             var reader = new FileReader();
    306             reader.onloadend = readerLoadEnd;
    307             reader.readAsText(file);
    308         }
    309 
    310         /**
    311          * @this {FileReader}
    312          */
    313         function readerLoadEnd()
    314         {
    315             callback(/** @type {string} */ (this.result));
    316         }
    317 
    318         function errorHandler(error)
    319         {
    320             if (error.code === FileError.NOT_FOUND_ERR) {
    321                 callback(null);
    322                 return;
    323             }
    324 
    325             var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error);
    326             console.error(errorMessage + " when getting content for file '" + (this._path + "/" + path) + "'");
    327             callback(null);
    328         }
    329     },
    330 
    331     /**
    332      * @param {string} path
    333      * @param {string} content
    334      * @param {function()} callback
    335      */
    336     setFileContent: function(path, content, callback)
    337     {
    338         this._requestFileSystem(fileSystemLoaded);
    339 
    340         /**
    341          * @param {DOMFileSystem} domFileSystem
    342          */
    343         function fileSystemLoaded(domFileSystem)
    344         {
    345             domFileSystem.root.getFile(path, { create: true }, fileEntryLoaded, errorHandler.bind(this));
    346         }
    347 
    348         /**
    349          * @param {FileEntry} entry
    350          */
    351         function fileEntryLoaded(entry)
    352         {
    353             entry.createWriter(fileWriterCreated, errorHandler.bind(this));
    354         }
    355 
    356         /**
    357          * @param {FileWriter} fileWriter
    358          */
    359         function fileWriterCreated(fileWriter)
    360         {
    361             fileWriter.onerror = errorHandler.bind(this);
    362             fileWriter.onwriteend = fileTruncated;
    363             fileWriter.truncate(0);
    364 
    365             function fileTruncated()
    366             {
    367                 fileWriter.onwriteend = writerEnd;
    368                 var blob = new Blob([content], { type: "text/plain" });
    369                 fileWriter.write(blob);
    370             }
    371         }
    372 
    373         function writerEnd()
    374         {
    375             callback();
    376         }
    377 
    378         function errorHandler(error)
    379         {
    380             var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error);
    381             console.error(errorMessage + " when setting content for file '" + (this._path + "/" + path) + "'");
    382             callback();
    383         }
    384     },
    385 
    386     /**
    387      * @param {string} path
    388      * @param {string} newName
    389      * @param {function(boolean, string=)} callback
    390      */
    391     renameFile: function(path, newName, callback)
    392     {
    393         newName = newName ? newName.trim() : newName;
    394         if (!newName || newName.indexOf("/") !== -1) {
    395             callback(false);
    396             return;
    397         }
    398         var fileEntry;
    399         var dirEntry;
    400         var newFileEntry;
    401         this._requestFileSystem(fileSystemLoaded);
    402 
    403         /**
    404          * @param {DOMFileSystem} domFileSystem
    405          */
    406         function fileSystemLoaded(domFileSystem)
    407         {
    408             domFileSystem.root.getFile(path, null, fileEntryLoaded, errorHandler.bind(this));
    409         }
    410 
    411         /**
    412          * @param {FileEntry} entry
    413          */
    414         function fileEntryLoaded(entry)
    415         {
    416             if (entry.name === newName) {
    417                 callback(false);
    418                 return;
    419             }
    420 
    421             fileEntry = entry;
    422             fileEntry.getParent(dirEntryLoaded, errorHandler.bind(this));
    423         }
    424 
    425         /**
    426          * @param {Entry} entry
    427          */
    428         function dirEntryLoaded(entry)
    429         {
    430             dirEntry = entry;
    431             dirEntry.getFile(newName, null, newFileEntryLoaded, newFileEntryLoadErrorHandler);
    432         }
    433 
    434         /**
    435          * @param {FileEntry} entry
    436          */
    437         function newFileEntryLoaded(entry)
    438         {
    439             callback(false);
    440         }
    441 
    442         function newFileEntryLoadErrorHandler(error)
    443         {
    444             if (error.code !== FileError.NOT_FOUND_ERR) {
    445                 callback(false);
    446                 return;
    447             }
    448             fileEntry.moveTo(dirEntry, newName, fileRenamed, errorHandler.bind(this));
    449         }
    450 
    451         /**
    452          * @param {FileEntry} entry
    453          */
    454         function fileRenamed(entry)
    455         {
    456             callback(true, entry.name);
    457         }
    458 
    459         function errorHandler(error)
    460         {
    461             var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error);
    462             console.error(errorMessage + " when renaming file '" + (this._path + "/" + path) + "' to '" + newName + "'");
    463             callback(false);
    464         }
    465     },
    466 
    467     /**
    468      * @param {DirectoryEntry} dirEntry
    469      * @param {function(Array.<FileEntry>)} callback
    470      */
    471     _readDirectory: function(dirEntry, callback)
    472     {
    473         var dirReader = dirEntry.createReader();
    474         var entries = [];
    475 
    476         function innerCallback(results)
    477         {
    478             if (!results.length)
    479                 callback(entries.sort());
    480             else {
    481                 entries = entries.concat(toArray(results));
    482                 dirReader.readEntries(innerCallback, errorHandler);
    483             }
    484         }
    485 
    486         function toArray(list)
    487         {
    488             return Array.prototype.slice.call(list || [], 0);
    489         }
    490 
    491         dirReader.readEntries(innerCallback, errorHandler);
    492 
    493         function errorHandler(error)
    494         {
    495             var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error);
    496             console.error(errorMessage + " when reading directory '" + dirEntry.fullPath + "'");
    497             callback([]);
    498         }
    499     },
    500 
    501     /**
    502      * @param {DOMFileSystem} domFileSystem
    503      * @param {string} path
    504      * @param {function(Array.<FileEntry>)} callback
    505      */
    506     _requestEntries: function(domFileSystem, path, callback)
    507     {
    508         domFileSystem.root.getDirectory(path, null, innerCallback.bind(this), errorHandler);
    509 
    510         function innerCallback(dirEntry)
    511         {
    512             this._readDirectory(dirEntry, callback)
    513         }
    514 
    515         function errorHandler(error)
    516         {
    517             var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error);
    518             console.error(errorMessage + " when requesting entry '" + path + "'");
    519             callback([]);
    520         }
    521     }
    522 }
    523