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
31window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
32
33/**
34 * @constructor
35 * @param {!string} dirPath
36 * @param {!string} name
37 * @param {!function(?WebInspector.TempFile)} callback
38 */
39WebInspector.TempFile = function(dirPath, name, callback)
40{
41    this._fileEntry = null;
42    this._writer = null;
43
44    /**
45     * @param {!FileSystem} fs
46     * @this {WebInspector.TempFile}
47     */
48    function didInitFs(fs)
49    {
50        fs.root.getDirectory(dirPath, { create: true }, didGetDir.bind(this), errorHandler);
51    }
52
53    /**
54     * @param {!DirectoryEntry} dir
55     * @this {WebInspector.TempFile}
56     */
57    function didGetDir(dir)
58    {
59        dir.getFile(name, { create: true }, didCreateFile.bind(this), errorHandler);
60    }
61
62    /**
63     * @param {!FileEntry} fileEntry
64     * @this {WebInspector.TempFile}
65     */
66    function didCreateFile(fileEntry)
67    {
68        this._fileEntry = fileEntry;
69        fileEntry.createWriter(didCreateWriter.bind(this), errorHandler);
70    }
71
72    /**
73     * @param {!FileWriter} writer
74     * @this {WebInspector.TempFile}
75     */
76    function didCreateWriter(writer)
77    {
78        /**
79         * @this {WebInspector.TempFile}
80         */
81        function didTruncate(e)
82        {
83            this._writer = writer;
84            writer.onwrite = null;
85            writer.onerror = null;
86            callback(this);
87        }
88
89        function onTruncateError(e)
90        {
91            WebInspector.console.error("Failed to truncate temp file " + e.code + " : " + e.message);
92            callback(null);
93        }
94
95        if (writer.length) {
96            writer.onwrite = didTruncate.bind(this);
97            writer.onerror = onTruncateError;
98            writer.truncate(0);
99        } else {
100            this._writer = writer;
101            callback(this);
102        }
103    }
104
105    function errorHandler(e)
106    {
107        WebInspector.console.error("Failed to create temp file " + e.code + " : " + e.message);
108        callback(null);
109    }
110
111    /**
112     * @this {WebInspector.TempFile}
113     */
114    function didClearTempStorage()
115    {
116        window.requestFileSystem(window.TEMPORARY, 10, didInitFs.bind(this), errorHandler);
117    }
118    WebInspector.TempFile._ensureTempStorageCleared(didClearTempStorage.bind(this));
119}
120
121WebInspector.TempFile.prototype = {
122    /**
123     * @param {!Array.<string>} strings
124     * @param {!function(boolean)} callback
125     */
126    write: function(strings, callback)
127    {
128        var blob = new Blob(strings, {type: 'text/plain'});
129        this._writer.onerror = function(e)
130        {
131            WebInspector.console.error("Failed to write into a temp file: " + e.message);
132            callback(false);
133        }
134        this._writer.onwrite = function(e)
135        {
136            callback(true);
137        }
138        this._writer.write(blob);
139    },
140
141    finishWriting: function()
142    {
143        this._writer = null;
144    },
145
146    /**
147     * @param {function(?string)} callback
148     */
149    read: function(callback)
150    {
151        this.readRange(undefined, undefined, callback);
152    },
153
154    /**
155     * @param {number|undefined} startOffset
156     * @param {number|undefined} endOffset
157     * @param {function(?string)} callback
158     */
159    readRange: function(startOffset, endOffset, callback)
160    {
161        /**
162         * @param {!Blob} file
163         */
164        function didGetFile(file)
165        {
166            var reader = new FileReader();
167
168            if (typeof startOffset === "number" || typeof endOffset === "number")
169                file = file.slice(/** @type {number} */ (startOffset), /** @type {number} */ (endOffset));
170            /**
171             * @this {FileReader}
172             */
173            reader.onloadend = function(e)
174            {
175                callback(/** @type {?string} */ (this.result));
176            }
177            reader.onerror = function(error)
178            {
179                WebInspector.console.error("Failed to read from temp file: " + error.message);
180            }
181            reader.readAsText(file);
182        }
183        function didFailToGetFile(error)
184        {
185            WebInspector.console.error("Failed to load temp file: " + error.message);
186            callback(null);
187        }
188        this._fileEntry.file(didGetFile, didFailToGetFile);
189    },
190
191    /**
192     * @param {!WebInspector.OutputStream} outputStream
193     * @param {!WebInspector.OutputStreamDelegate} delegate
194     */
195    writeToOutputSteam: function(outputStream, delegate)
196    {
197        /**
198         * @param {!File} file
199         */
200        function didGetFile(file)
201        {
202            var reader = new WebInspector.ChunkedFileReader(file, 10*1000*1000, delegate);
203            reader.start(outputStream);
204        }
205
206        function didFailToGetFile(error)
207        {
208            WebInspector.console.error("Failed to load temp file: " + error.message);
209            outputStream.close();
210        }
211
212        this._fileEntry.file(didGetFile, didFailToGetFile);
213    },
214
215    remove: function()
216    {
217        if (this._fileEntry)
218            this._fileEntry.remove(function() {});
219    }
220}
221
222/**
223 * @constructor
224 * @param {!string} dirPath
225 * @param {!string} name
226 */
227WebInspector.DeferredTempFile = function(dirPath, name)
228{
229    this._chunks = [];
230    this._tempFile = null;
231    this._isWriting = false;
232    this._finishCallback = null;
233    this._finishedWriting = false;
234    this._callsPendingOpen = [];
235    this._pendingReads = [];
236    new WebInspector.TempFile(dirPath, name, this._didCreateTempFile.bind(this));
237}
238
239WebInspector.DeferredTempFile.prototype = {
240    /**
241     * @param {!Array.<string>} strings
242     */
243    write: function(strings)
244    {
245        if (!this._chunks)
246            return;
247        if (this._finishCallback)
248            throw new Error("No writes are allowed after close.");
249        this._chunks.push.apply(this._chunks, strings);
250        if (this._tempFile && !this._isWriting)
251            this._writeNextChunk();
252    },
253
254    /**
255     * @param {!function(?WebInspector.TempFile)} callback
256     */
257    finishWriting: function(callback)
258    {
259        this._finishCallback = callback;
260        if (this._finishedWriting)
261            callback(this._tempFile);
262        else if (!this._isWriting && !this._chunks.length)
263            this._notifyFinished();
264    },
265
266    _didCreateTempFile: function(tempFile)
267    {
268        this._tempFile = tempFile;
269        var callsPendingOpen = this._callsPendingOpen;
270        this._callsPendingOpen = null;
271        for (var i = 0; i < callsPendingOpen.length; ++i)
272            callsPendingOpen[i]();
273        if (!tempFile) {
274            this._chunks = null;
275            this._notifyFinished();
276            return;
277        }
278        if (this._chunks.length)
279            this._writeNextChunk();
280    },
281
282    _writeNextChunk: function()
283    {
284        var chunks = this._chunks;
285        this._chunks = [];
286        this._isWriting = true;
287        this._tempFile.write(chunks, this._didWriteChunk.bind(this));
288    },
289
290    _didWriteChunk: function(success)
291    {
292        this._isWriting = false;
293        if (!success) {
294            this._tempFile = null;
295            this._chunks = null;
296            this._notifyFinished();
297            return;
298        }
299        if (this._chunks.length)
300            this._writeNextChunk();
301        else if (this._finishCallback)
302            this._notifyFinished();
303    },
304
305    _notifyFinished: function()
306    {
307        this._finishedWriting = true;
308        if (this._tempFile)
309            this._tempFile.finishWriting();
310        if (this._finishCallback)
311            this._finishCallback(this._tempFile);
312        var pendingReads = this._pendingReads;
313        for (var i = 0; i < this._pendingReads.length; ++i)
314            this._pendingReads[i]();
315        this._pendingReads = [];
316    },
317
318    /**
319     * @param {number|undefined} startOffset
320     * @param {number|undefined} endOffset
321     * @param {function(string?)} callback
322     */
323    readRange: function(startOffset, endOffset, callback)
324    {
325        if (!this._finishedWriting) {
326            this._pendingReads.push(this.readRange.bind(this, startOffset, endOffset, callback));
327            return;
328        }
329        if (!this._tempFile) {
330            callback(null);
331            return;
332        }
333        this._tempFile.readRange(startOffset, endOffset, callback);
334    },
335
336    /**
337     * @param {!WebInspector.OutputStream} outputStream
338     * @param {!WebInspector.OutputStreamDelegate} delegate
339     */
340    writeToOutputStream: function(outputStream, delegate)
341    {
342        if (this._callsPendingOpen) {
343            this._callsPendingOpen.push(this.writeToOutputStream.bind(this, outputStream, delegate));
344            return;
345        }
346        if (this._tempFile)
347            this._tempFile.writeToOutputSteam(outputStream, delegate);
348    },
349
350    remove: function()
351    {
352        if (this._callsPendingOpen) {
353            this._callsPendingOpen.push(this.remove.bind(this));
354            return;
355        }
356        if (this._tempFile)
357            this._tempFile.remove();
358    }
359}
360
361/**
362 * @constructor
363 */
364WebInspector.TempStorageCleaner = function()
365{
366    this._worker = Runtime.startSharedWorker("temp_storage_shared_worker", "TempStorage");
367    this._worker.onerror = this._handleError.bind(this);
368    this._callbacks = [];
369    this._worker.port.onmessage = this._handleMessage.bind(this);
370    this._worker.port.onerror = this._handleError.bind(this);
371}
372
373WebInspector.TempStorageCleaner.prototype = {
374    /**
375     * @param {!function()} callback
376     */
377    ensureStorageCleared: function(callback)
378    {
379        if (this._callbacks)
380            this._callbacks.push(callback);
381        else
382            callback();
383    },
384
385    _handleMessage: function(event)
386    {
387        if (event.data.type === "tempStorageCleared") {
388            if (event.data.error)
389                WebInspector.console.error(event.data.error);
390            this._notifyCallbacks();
391        }
392    },
393
394    _handleError: function(event)
395    {
396        WebInspector.console.error(WebInspector.UIString("Failed to clear temp storage: %s", event.data));
397        this._notifyCallbacks();
398    },
399
400    _notifyCallbacks: function()
401    {
402        var callbacks = this._callbacks;
403        this._callbacks = null;
404        for (var i = 0; i < callbacks.length; i++)
405            callbacks[i]();
406    }
407}
408
409/**
410 * @param {!function()} callback
411 */
412WebInspector.TempFile._ensureTempStorageCleared = function(callback)
413{
414    if (!WebInspector.TempFile._storageCleaner)
415        WebInspector.TempFile._storageCleaner = new WebInspector.TempStorageCleaner();
416    WebInspector.TempFile._storageCleaner.ensureStorageCleared(callback);
417}
418