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 * @implements {WebInspector.ProjectDelegate}
34 * @extends {WebInspector.Object}
35 * @param {string} type
36 */
37WebInspector.ContentProviderBasedProjectDelegate = function(type)
38{
39    this._type = type;
40    /** @type {!Object.<string, !WebInspector.ContentProvider>} */
41    this._contentProviders = {};
42    /** @type {!Object.<string, boolean>} */
43    this._isContentScriptMap = {};
44}
45
46WebInspector.ContentProviderBasedProjectDelegate.prototype = {
47    /**
48     * @return {string}
49     */
50    id: function()
51    {
52        // Overriddden by subclasses
53        return "";
54    },
55
56    /**
57     * @return {string}
58     */
59    type: function()
60    {
61        return this._type;
62    },
63
64    /**
65     * @return {string}
66     */
67    displayName: function()
68    {
69        // Overriddden by subclasses
70        return "";
71    },
72
73    /**
74     * @param {string} path
75     * @param {function(?Date, ?number)} callback
76     */
77    requestMetadata: function(path, callback)
78    {
79        callback(null, null);
80    },
81
82    /**
83     * @param {string} path
84     * @param {function(?string)} callback
85     */
86    requestFileContent: function(path, callback)
87    {
88        var contentProvider = this._contentProviders[path];
89        contentProvider.requestContent(callback);
90
91        /**
92         * @param {?string} content
93         * @param {boolean} encoded
94         * @param {string} mimeType
95         */
96        function innerCallback(content, encoded, mimeType)
97        {
98            callback(content);
99        }
100    },
101
102    /**
103     * @return {boolean}
104     */
105    canSetFileContent: function()
106    {
107        return false;
108    },
109
110    /**
111     * @param {string} path
112     * @param {string} newContent
113     * @param {function(?string)} callback
114     */
115    setFileContent: function(path, newContent, callback)
116    {
117        callback(null);
118    },
119
120    /**
121     * @return {boolean}
122     */
123    canRename: function()
124    {
125        return false;
126    },
127
128    /**
129     * @param {string} path
130     * @param {string} newName
131     * @param {function(boolean, string=, string=, string=, !WebInspector.ResourceType=)} callback
132     */
133    rename: function(path, newName, callback)
134    {
135        this.performRename(path, newName, innerCallback.bind(this));
136
137        /**
138         * @param {boolean} success
139         * @param {string=} newName
140         * @this {WebInspector.ContentProviderBasedProjectDelegate}
141         */
142        function innerCallback(success, newName)
143        {
144            if (success)
145                this._updateName(path, /** @type {string} */ (newName));
146            callback(success, newName);
147        }
148    },
149
150    /**
151     * @param {string} path
152     */
153    refresh: function(path)
154    {
155    },
156
157    /**
158     * @param {string} path
159     */
160    excludeFolder: function(path)
161    {
162    },
163
164    /**
165     * @param {string} path
166     * @param {?string} name
167     * @param {string} content
168     * @param {function(?string)} callback
169     */
170    createFile: function(path, name, content, callback)
171    {
172    },
173
174    /**
175     * @param {string} path
176     */
177    deleteFile: function(path)
178    {
179    },
180
181    remove: function()
182    {
183    },
184
185    /**
186     * @param {string} path
187     * @param {string} newName
188     * @param {function(boolean, string=)} callback
189     */
190    performRename: function(path, newName, callback)
191    {
192        callback(false);
193    },
194
195    /**
196     * @param {string} path
197     * @param {string} newName
198     */
199    _updateName: function(path, newName)
200    {
201        var oldPath = path;
202        var copyOfPath = path.split("/");
203        copyOfPath[copyOfPath.length - 1] = newName;
204        var newPath = copyOfPath.join("/");
205        this._contentProviders[newPath] = this._contentProviders[oldPath];
206        delete this._contentProviders[oldPath];
207    },
208
209    /**
210     * @param {string} path
211     * @param {string} query
212     * @param {boolean} caseSensitive
213     * @param {boolean} isRegex
214     * @param {function(!Array.<!WebInspector.ContentProvider.SearchMatch>)} callback
215     */
216    searchInFileContent: function(path, query, caseSensitive, isRegex, callback)
217    {
218        var contentProvider = this._contentProviders[path];
219        contentProvider.searchInContent(query, caseSensitive, isRegex, callback);
220    },
221
222    /**
223     * @param {!Array.<string>} queries
224     * @param {!Array.<string>} fileQueries
225     * @param {boolean} caseSensitive
226     * @param {boolean} isRegex
227     * @param {!WebInspector.Progress} progress
228     * @param {function(!Array.<string>)} callback
229     */
230    findFilesMatchingSearchRequest: function(queries, fileQueries, caseSensitive, isRegex, progress, callback)
231    {
232        var result = [];
233        var paths = Object.keys(this._contentProviders);
234        var totalCount = paths.length;
235        if (totalCount === 0) {
236            // searchInContent should call back later.
237            setTimeout(doneCallback, 0);
238            return;
239        }
240
241        /**
242         * @param {string} path
243         * @this {WebInspector.ContentProviderBasedProjectDelegate}
244         */
245        function filterOutContentScripts(path)
246        {
247            return !this._isContentScriptMap[path];
248        }
249
250        if (!WebInspector.settings.searchInContentScripts.get())
251            paths = paths.filter(filterOutContentScripts.bind(this));
252
253        var fileRegexes = [];
254        for (var i = 0; i < fileQueries.length; ++i)
255            fileRegexes.push(new RegExp(fileQueries[i], caseSensitive ? "" : "i"));
256
257        /**
258         * @param {!string} file
259         */
260        function filterOutNonMatchingFiles(file)
261        {
262            for (var i = 0; i < fileRegexes.length; ++i) {
263                if (!file.match(fileRegexes[i]))
264                    return false;
265            }
266            return true;
267        }
268
269        paths = paths.filter(filterOutNonMatchingFiles);
270        var barrier = new CallbackBarrier();
271        progress.setTotalWork(paths.length);
272        for (var i = 0; i < paths.length; ++i)
273            searchInContent.call(this, paths[i], barrier.createCallback(searchInContentCallback.bind(this, paths[i])));
274        barrier.callWhenDone(doneCallback);
275
276        /**
277         * @param {string} path
278         * @param {function(boolean)} callback
279         * @this {WebInspector.ContentProviderBasedProjectDelegate}
280         */
281        function searchInContent(path, callback)
282        {
283            var queriesToRun = queries.slice();
284            searchNextQuery.call(this);
285
286            /**
287             * @this {WebInspector.ContentProviderBasedProjectDelegate}
288             */
289            function searchNextQuery()
290            {
291                if (!queriesToRun.length) {
292                    callback(true);
293                    return;
294                }
295                var query = queriesToRun.shift();
296                this._contentProviders[path].searchInContent(query, caseSensitive, isRegex, contentCallback.bind(this));
297            }
298
299            /**
300             * @param {!Array.<!WebInspector.ContentProvider.SearchMatch>} searchMatches
301             * @this {WebInspector.ContentProviderBasedProjectDelegate}
302             */
303            function contentCallback(searchMatches)
304            {
305                if (!searchMatches.length) {
306                    callback(false);
307                    return;
308                }
309                searchNextQuery.call(this);
310            }
311        }
312
313        /**
314         * @param {string} path
315         * @param {boolean} matches
316         */
317        function searchInContentCallback(path, matches)
318        {
319            if (matches)
320                result.push(path);
321            progress.worked(1);
322        }
323
324        function doneCallback()
325        {
326            callback(result);
327            progress.done();
328        }
329    },
330
331    /**
332     * @param {!WebInspector.Progress} progress
333     * @param {function()} callback
334     */
335    indexContent: function(progress, callback)
336    {
337        setTimeout(innerCallback, 0);
338
339        function innerCallback()
340        {
341            progress.done();
342            callback();
343        }
344    },
345
346    /**
347     * @param {string} parentPath
348     * @param {string} name
349     * @param {string} url
350     * @param {!WebInspector.ContentProvider} contentProvider
351     * @param {boolean} isEditable
352     * @param {boolean=} isContentScript
353     * @return {string}
354     */
355    addContentProvider: function(parentPath, name, url, contentProvider, isEditable, isContentScript)
356    {
357        var path = parentPath ? parentPath + "/" + name : name;
358        var fileDescriptor = new WebInspector.FileDescriptor(parentPath, name, url, url, contentProvider.contentType(), isEditable, isContentScript);
359        this._contentProviders[path] = contentProvider;
360        this._isContentScriptMap[path] = isContentScript || false;
361        this.dispatchEventToListeners(WebInspector.ProjectDelegate.Events.FileAdded, fileDescriptor);
362        return path;
363    },
364
365    /**
366     * @param {string} path
367     */
368    removeFile: function(path)
369    {
370        delete this._contentProviders[path];
371        delete this._isContentScriptMap[path];
372        this.dispatchEventToListeners(WebInspector.ProjectDelegate.Events.FileRemoved, path);
373    },
374
375    /**
376     * @return {!Object.<string, !WebInspector.ContentProvider>}
377     */
378    contentProviders: function()
379    {
380        return this._contentProviders;
381    },
382
383    reset: function()
384    {
385        this._contentProviders = {};
386        this._isContentScriptMap = {};
387        this.dispatchEventToListeners(WebInspector.ProjectDelegate.Events.Reset, null);
388    },
389
390    __proto__: WebInspector.Object.prototype
391}
392