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 */
36WebInspector.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
44WebInspector.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
71WebInspector.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);
131                else
132                    this._requestEntries(domFileSystem, entry.fullPath, innerCallback.bind(this));
133            }
134        }
135    },
136
137    /**
138     * @param {string} path
139     * @param {function(?string)} callback
140     */
141    requestFileContent: function(path, callback)
142    {
143        this._requestFileSystem(fileSystemLoaded.bind(this));
144
145        /**
146         * @param {DOMFileSystem} domFileSystem
147         */
148        function fileSystemLoaded(domFileSystem)
149        {
150            domFileSystem.root.getFile(path, null, fileEntryLoaded, errorHandler);
151        }
152
153        /**
154         * @param {FileEntry} entry
155         */
156        function fileEntryLoaded(entry)
157        {
158            entry.file(fileLoaded, errorHandler);
159        }
160
161        /**
162         * @param {!Blob} file
163         */
164        function fileLoaded(file)
165        {
166            var reader = new FileReader();
167            reader.onloadend = readerLoadEnd;
168            reader.readAsText(file);
169        }
170
171        /**
172         * @this {FileReader}
173         */
174        function readerLoadEnd()
175        {
176            callback(/** @type {string} */ (this.result));
177        }
178
179        function errorHandler(error)
180        {
181            if (error.code === FileError.NOT_FOUND_ERR) {
182                callback(null);
183                return;
184            }
185
186            var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error);
187            console.error(errorMessage + " when getting content for file '" + (this._path + "/" + path) + "'");
188            callback(null);
189        }
190    },
191
192    /**
193     * @param {string} path
194     * @param {string} content
195     * @param {function()} callback
196     */
197    setFileContent: function(path, content, callback)
198    {
199        this._requestFileSystem(fileSystemLoaded);
200
201        /**
202         * @param {DOMFileSystem} domFileSystem
203         */
204        function fileSystemLoaded(domFileSystem)
205        {
206            domFileSystem.root.getFile(path, { create: true }, fileEntryLoaded, errorHandler);
207        }
208
209        /**
210         * @param {FileEntry} entry
211         */
212        function fileEntryLoaded(entry)
213        {
214            entry.createWriter(fileWriterCreated, errorHandler);
215        }
216
217        /**
218         * @param {FileWriter} fileWriter
219         */
220        function fileWriterCreated(fileWriter)
221        {
222            fileWriter.onerror = errorHandler;
223            fileWriter.onwriteend = fileTruncated;
224            fileWriter.truncate(0);
225
226            function fileTruncated()
227            {
228                fileWriter.onwriteend = writerEnd;
229                var blob = new Blob([content], { type: "text/plain" });
230                fileWriter.write(blob);
231            }
232        }
233
234        function writerEnd()
235        {
236            callback();
237        }
238
239        function errorHandler(error)
240        {
241            var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error);
242            console.error(errorMessage + " when setting content for file '" + (this._path + "/" + path) + "'");
243            callback();
244        }
245    },
246
247    /**
248     * @param {DirectoryEntry} dirEntry
249     * @param {function(Array.<FileEntry>)} callback
250     */
251    _readDirectory: function(dirEntry, callback)
252    {
253        var dirReader = dirEntry.createReader();
254        var entries = [];
255
256        function innerCallback(results)
257        {
258            if (!results.length)
259                callback(entries.sort());
260            else {
261                entries = entries.concat(toArray(results));
262                dirReader.readEntries(innerCallback, errorHandler);
263            }
264        }
265
266        function toArray(list)
267        {
268            return Array.prototype.slice.call(list || [], 0);
269        }
270
271        dirReader.readEntries(innerCallback, errorHandler);
272
273        function errorHandler(error)
274        {
275            var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error);
276            console.error(errorMessage + " when reading directory '" + dirEntry.fullPath + "'");
277            callback([]);
278        }
279    },
280
281    /**
282     * @param {DOMFileSystem} domFileSystem
283     * @param {string} path
284     * @param {function(Array.<FileEntry>)} callback
285     */
286    _requestEntries: function(domFileSystem, path, callback)
287    {
288        domFileSystem.root.getDirectory(path, null, innerCallback.bind(this), errorHandler);
289
290        function innerCallback(dirEntry)
291        {
292            this._readDirectory(dirEntry, callback)
293        }
294
295        function errorHandler(error)
296        {
297            var errorMessage = WebInspector.IsolatedFileSystem.errorMessage(error);
298            console.error(errorMessage + " when requesting entry '" + path + "'");
299            callback([]);
300        }
301    }
302}
303