1/*
2 * Copyright (C) 2012 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 * @extends {WebInspector.Object}
34 */
35WebInspector.FileSystemModel = function()
36{
37    WebInspector.Object.call(this);
38
39    this._fileSystemsForOrigin = {};
40
41    WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.SecurityOriginAdded, this._securityOriginAdded, this);
42    WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.SecurityOriginRemoved, this._securityOriginRemoved, this);
43
44    FileSystemAgent.enable();
45
46    this._reset();
47}
48
49WebInspector.FileSystemModel.prototype = {
50    _reset: function()
51    {
52        for (var securityOrigin in this._fileSystemsForOrigin)
53            this._removeOrigin(securityOrigin);
54        var securityOrigins = WebInspector.resourceTreeModel.securityOrigins();
55        for (var i = 0; i < securityOrigins.length; ++i)
56            this._addOrigin(securityOrigins[i]);
57    },
58
59    /**
60     * @param {WebInspector.Event} event
61     */
62    _securityOriginAdded: function(event)
63    {
64        var securityOrigin = /** @type {string} */ (event.data);
65        this._addOrigin(securityOrigin);
66    },
67
68    /**
69     * @param {WebInspector.Event} event
70     */
71    _securityOriginRemoved: function(event)
72    {
73        var securityOrigin = /** @type {string} */ (event.data);
74        this._removeOrigin(securityOrigin);
75    },
76
77    /**
78     * @param {string} securityOrigin
79     */
80    _addOrigin: function(securityOrigin)
81    {
82        this._fileSystemsForOrigin[securityOrigin] = {};
83
84        var types = ["persistent", "temporary"];
85        for (var i = 0; i < types.length; ++i)
86            this._requestFileSystemRoot(securityOrigin, types[i], this._fileSystemRootReceived.bind(this, securityOrigin, types[i], this._fileSystemsForOrigin[securityOrigin]));
87    },
88
89    /**
90     * @param {string} securityOrigin
91     */
92    _removeOrigin: function(securityOrigin)
93    {
94        for (var type in this._fileSystemsForOrigin[securityOrigin]) {
95            var fileSystem = this._fileSystemsForOrigin[securityOrigin][type];
96            delete this._fileSystemsForOrigin[securityOrigin][type];
97            this._fileSystemRemoved(fileSystem);
98        }
99        delete this._fileSystemsForOrigin[securityOrigin];
100    },
101
102    /**
103     * @param {string} origin
104     * @param {string} type
105     * @param {function(number, FileSystemAgent.Entry=)} callback
106     */
107    _requestFileSystemRoot: function(origin, type, callback)
108    {
109        /**
110         * @param {?Protocol.Error} error
111         * @param {number} errorCode
112         * @param {FileSystemAgent.Entry=} backendRootEntry
113         */
114        function innerCallback(error, errorCode, backendRootEntry)
115        {
116            if (error) {
117                callback(FileError.SECURITY_ERR);
118                return;
119            }
120
121            callback(errorCode, backendRootEntry);
122        }
123
124        FileSystemAgent.requestFileSystemRoot(origin, type, innerCallback.bind(this));
125    },
126
127    /**
128     * @param {WebInspector.FileSystemModel.FileSystem} fileSystem
129     */
130    _fileSystemAdded: function(fileSystem)
131    {
132        this.dispatchEventToListeners(WebInspector.FileSystemModel.EventTypes.FileSystemAdded, fileSystem);
133    },
134
135    /**
136     * @param {WebInspector.FileSystemModel.FileSystem} fileSystem
137     */
138    _fileSystemRemoved: function(fileSystem)
139    {
140        this.dispatchEventToListeners(WebInspector.FileSystemModel.EventTypes.FileSystemRemoved, fileSystem);
141    },
142
143    refreshFileSystemList: function()
144    {
145        this._reset();
146    },
147
148    /**
149     * @param {string} origin
150     * @param {string} type
151     * @param {Object.<WebInspector.FileSystemModel.FileSystem>} store
152     * @param {number} errorCode
153     * @param {FileSystemAgent.Entry=} backendRootEntry
154     */
155    _fileSystemRootReceived: function(origin, type, store, errorCode, backendRootEntry)
156    {
157        if (!errorCode && backendRootEntry && this._fileSystemsForOrigin[origin] === store) {
158            var fileSystem = new WebInspector.FileSystemModel.FileSystem(this, origin, type, backendRootEntry);
159            store[type] = fileSystem;
160            this._fileSystemAdded(fileSystem);
161        }
162    },
163
164    /**
165     * @param {WebInspector.FileSystemModel.Directory} directory
166     * @param {function(number, Array.<WebInspector.FileSystemModel.Entry>=)} callback
167     */
168    requestDirectoryContent: function(directory, callback)
169    {
170        this._requestDirectoryContent(directory.url, this._directoryContentReceived.bind(this, directory, callback));
171    },
172
173    /**
174     * @param {string} url
175     * @param {function(number, Array.<FileSystemAgent.Entry>=)} callback
176     */
177    _requestDirectoryContent: function(url, callback)
178    {
179        /**
180         * @param {?Protocol.Error} error
181         * @param {number} errorCode
182         * @param {Array.<FileSystemAgent.Entry>=} backendEntries
183         */
184        function innerCallback(error, errorCode, backendEntries)
185        {
186            if (error) {
187                callback(FileError.SECURITY_ERR);
188                return;
189            }
190
191            if (errorCode !== 0) {
192                callback(errorCode, null);
193                return;
194            }
195
196            callback(errorCode, backendEntries);
197        }
198
199        FileSystemAgent.requestDirectoryContent(url, innerCallback.bind(this));
200    },
201
202    /**
203     * @param {WebInspector.FileSystemModel.Directory} parentDirectory
204     * @param {function(number, Array.<WebInspector.FileSystemModel.Entry>=)} callback
205     * @param {number} errorCode
206     * @param {Array.<FileSystemAgent.Entry>=} backendEntries
207     */
208    _directoryContentReceived: function(parentDirectory, callback, errorCode, backendEntries)
209    {
210        var entries = [];
211        for (var i = 0; i < backendEntries.length; ++i) {
212            if (backendEntries[i].isDirectory)
213                entries.push(new WebInspector.FileSystemModel.Directory(this, parentDirectory.fileSystem, backendEntries[i]));
214            else
215                entries.push(new WebInspector.FileSystemModel.File(this, parentDirectory.fileSystem, backendEntries[i]));
216        }
217
218        callback(errorCode, entries);
219    },
220
221    /**
222     * @param {WebInspector.FileSystemModel.Entry} entry
223     * @param {function(number, FileSystemAgent.Metadata=)} callback
224     */
225    requestMetadata: function(entry, callback)
226    {
227        /**
228         * @param {?Protocol.Error} error
229         * @param {number} errorCode
230         * @param {FileSystemAgent.Metadata=} metadata
231         */
232        function innerCallback(error, errorCode, metadata)
233        {
234            if (error) {
235                callback(FileError.SECURITY_ERR);
236                return;
237            }
238
239            callback(errorCode, metadata);
240        }
241
242        FileSystemAgent.requestMetadata(entry.url, innerCallback.bind(this));
243    },
244
245    /**
246     * @param {WebInspector.FileSystemModel.File} file
247     * @param {boolean} readAsText
248     * @param {number=} start
249     * @param {number=} end
250     * @param {string=} charset
251     * @param {function(number, string=, string=)=} callback
252     */
253    requestFileContent: function(file, readAsText, start, end, charset, callback)
254    {
255        this._requestFileContent(file.url, readAsText, start, end, charset, callback);
256    },
257
258    /**
259     * @param {string} url
260     * @param {boolean} readAsText
261     * @param {number=} start
262     * @param {number=} end
263     * @param {string=} charset
264     * @param {function(number, string=, string=)=} callback
265     */
266    _requestFileContent: function(url, readAsText, start, end, charset, callback)
267    {
268        /**
269         * @param {?Protocol.Error} error
270         * @param {number} errorCode
271         * @param {string=} content
272         * @param {string=} charset
273         */
274        function innerCallback(error, errorCode, content, charset)
275        {
276            if (error) {
277                if (callback)
278                    callback(FileError.SECURITY_ERR);
279                return;
280            }
281
282            if (callback)
283                callback(errorCode, content, charset);
284        }
285
286        FileSystemAgent.requestFileContent(url, readAsText, start, end, charset, innerCallback.bind(this));
287    },
288    /**
289     * @param {WebInspector.FileSystemModel.Entry} entry
290     * @param {function(number)=} callback
291     */
292    deleteEntry: function(entry, callback)
293    {
294        var fileSystemModel = this;
295        if (entry === entry.fileSystem.root)
296            this._deleteEntry(entry.url, hookFileSystemDeletion);
297        else
298            this._deleteEntry(entry.url, callback);
299
300        function hookFileSystemDeletion(errorCode)
301        {
302            callback(errorCode);
303            if (!errorCode)
304                fileSystemModel._removeFileSystem(entry.fileSystem);
305        }
306    },
307
308    /**
309     * @param {string} url
310     * @param {function(number)=} callback
311     */
312    _deleteEntry: function(url, callback)
313    {
314        /**
315         * @param {?Protocol.Error} error
316         * @param {number} errorCode
317         */
318        function innerCallback(error, errorCode)
319        {
320            if (error) {
321                if (callback)
322                    callback(FileError.SECURITY_ERR);
323                return;
324            }
325
326            if (callback)
327                callback(errorCode);
328        }
329
330        FileSystemAgent.deleteEntry(url, innerCallback.bind(this));
331    },
332
333    /**
334     * @param {WebInspector.FileSystemModel.FileSystem} fileSystem
335     */
336    _removeFileSystem: function(fileSystem)
337    {
338        var origin = fileSystem.origin;
339        var type = fileSystem.type;
340        if (this._fileSystemsForOrigin[origin] && this._fileSystemsForOrigin[origin][type]) {
341            delete this._fileSystemsForOrigin[origin][type];
342            this._fileSystemRemoved(fileSystem);
343
344            if (Object.isEmpty(this._fileSystemsForOrigin[origin]))
345                delete this._fileSystemsForOrigin[origin];
346        }
347    },
348
349    __proto__: WebInspector.Object.prototype
350}
351
352
353WebInspector.FileSystemModel.EventTypes = {
354    FileSystemAdded: "FileSystemAdded",
355    FileSystemRemoved: "FileSystemRemoved"
356}
357
358/**
359 * @constructor
360 * @param {WebInspector.FileSystemModel} fileSystemModel
361 * @param {string} origin
362 * @param {string} type
363 * @param {FileSystemAgent.Entry} backendRootEntry
364 */
365WebInspector.FileSystemModel.FileSystem = function(fileSystemModel, origin, type, backendRootEntry)
366{
367    this.origin = origin;
368    this.type = type;
369
370    this.root = new WebInspector.FileSystemModel.Directory(fileSystemModel, this, backendRootEntry);
371}
372
373WebInspector.FileSystemModel.FileSystem.prototype = {
374    /**
375     * @type {string}
376     */
377    get name()
378    {
379        return "filesystem:" + this.origin + "/" + this.type;
380    }
381}
382
383/**
384 * @constructor
385 * @param {WebInspector.FileSystemModel} fileSystemModel
386 * @param {WebInspector.FileSystemModel.FileSystem} fileSystem
387 * @param {FileSystemAgent.Entry} backendEntry
388 */
389WebInspector.FileSystemModel.Entry = function(fileSystemModel, fileSystem, backendEntry)
390{
391    this._fileSystemModel = fileSystemModel;
392    this._fileSystem = fileSystem;
393
394    this._url = backendEntry.url;
395    this._name = backendEntry.name;
396    this._isDirectory = backendEntry.isDirectory;
397}
398
399/**
400 * @param {WebInspector.FileSystemModel.Entry} x
401 * @param {WebInspector.FileSystemModel.Entry} y
402 * @return {number}
403 */
404WebInspector.FileSystemModel.Entry.compare = function(x, y)
405{
406    if (x.isDirectory != y.isDirectory)
407        return y.isDirectory ? 1 : -1;
408    return x.name.compareTo(y.name);
409}
410
411WebInspector.FileSystemModel.Entry.prototype = {
412    /**
413     * @type {WebInspector.FileSystemModel}
414     */
415    get fileSystemModel()
416    {
417        return this._fileSystemModel;
418    },
419
420    /**
421     * @type {WebInspector.FileSystemModel.FileSystem}
422     */
423    get fileSystem()
424    {
425        return this._fileSystem;
426    },
427
428    /**
429     * @type {string}
430     */
431    get url()
432    {
433        return this._url;
434    },
435
436    /**
437     * @type {string}
438     */
439    get name()
440    {
441        return this._name;
442    },
443
444    /**
445     * @type {boolean}
446     */
447    get isDirectory()
448    {
449        return this._isDirectory;
450    },
451
452    /**
453     * @param {function(number, FileSystemAgent.Metadata)} callback
454     */
455    requestMetadata: function(callback)
456    {
457        this.fileSystemModel.requestMetadata(this, callback);
458    },
459
460    /**
461     * @param {function(number)} callback
462     */
463    deleteEntry: function(callback)
464    {
465        this.fileSystemModel.deleteEntry(this, callback);
466    }
467}
468
469/**
470 * @constructor
471 * @extends {WebInspector.FileSystemModel.Entry}
472 * @param {WebInspector.FileSystemModel} fileSystemModel
473 * @param {WebInspector.FileSystemModel.FileSystem} fileSystem
474 * @param {FileSystemAgent.Entry} backendEntry
475 */
476WebInspector.FileSystemModel.Directory = function(fileSystemModel, fileSystem, backendEntry)
477{
478    WebInspector.FileSystemModel.Entry.call(this, fileSystemModel, fileSystem, backendEntry);
479}
480
481WebInspector.FileSystemModel.Directory.prototype = {
482    /**
483     * @param {function(number, Array.<WebInspector.FileSystemModel.Directory>)} callback
484     */
485    requestDirectoryContent: function(callback)
486    {
487        this.fileSystemModel.requestDirectoryContent(this, callback);
488    },
489
490    __proto__: WebInspector.FileSystemModel.Entry.prototype
491}
492
493/**
494 * @constructor
495 * @extends {WebInspector.FileSystemModel.Entry}
496 * @param {WebInspector.FileSystemModel} fileSystemModel
497 * @param {WebInspector.FileSystemModel.FileSystem} fileSystem
498 * @param {FileSystemAgent.Entry} backendEntry
499 */
500WebInspector.FileSystemModel.File = function(fileSystemModel, fileSystem, backendEntry)
501{
502    WebInspector.FileSystemModel.Entry.call(this, fileSystemModel, fileSystem, backendEntry);
503
504    this._mimeType = backendEntry.mimeType;
505    this._resourceType = WebInspector.resourceTypes[backendEntry.resourceType];
506    this._isTextFile = backendEntry.isTextFile;
507}
508
509WebInspector.FileSystemModel.File.prototype = {
510    /**
511     * @type {string}
512     */
513    get mimeType()
514    {
515        return this._mimeType;
516    },
517
518    /**
519     * @type {WebInspector.ResourceType}
520     */
521    get resourceType()
522    {
523        return this._resourceType;
524    },
525
526    /**
527     * @type {boolean}
528     */
529    get isTextFile()
530    {
531        return this._isTextFile;
532    },
533
534    /**
535     * @param {boolean} readAsText
536     * @param {number=} start
537     * @param {number=} end
538     * @param {string=} charset
539     * @param {function(number, string=)=} callback
540     */
541    requestFileContent: function(readAsText, start, end, charset, callback)
542    {
543        this.fileSystemModel.requestFileContent(this, readAsText, start, end, charset, callback);
544    },
545
546    __proto__: WebInspector.FileSystemModel.Entry.prototype
547}
548