2 * Copyright (C) 2013 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
31 window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
36 WebInspector.TempFile = function()
38 this._fileEntry = null;
43 * @param {string} dirPath
44 * @param {string} name
45 * @return {!Promise.<!WebInspector.TempFile>}
47 WebInspector.TempFile.create = function(dirPath, name)
49 var file = new WebInspector.TempFile();
51 function requestTempFileSystem()
53 return new Promise(window.requestFileSystem.bind(window, window.TEMPORARY, 10));
57 * @param {!FileSystem} fs
59 function getDirectoryEntry(fs)
61 return new Promise(fs.root.getDirectory.bind(fs.root, dirPath, { create: true }));
65 * @param {!DirectoryEntry} dir
67 function getFileEntry(dir)
69 return new Promise(dir.getFile.bind(dir, name, { create: true }));
73 * @param {!FileEntry} fileEntry
75 function createFileWriter(fileEntry)
77 file._fileEntry = fileEntry;
78 return new Promise(fileEntry.createWriter.bind(fileEntry));
82 * @param {!FileWriter} writer
84 function truncateFile(writer)
87 file._writer = writer;
88 return Promise.resolve(file);
92 * @param {function(?)} fulfill
93 * @param {function(*)} reject
95 function truncate(fulfill, reject)
97 writer.onwriteend = fulfill;
98 writer.onerror = reject;
102 function didTruncate()
104 file._writer = writer;
105 writer.onwriteend = null;
106 writer.onerror = null;
107 return Promise.resolve(file);
110 function onTruncateError(e)
112 writer.onwriteend = null;
113 writer.onerror = null;
117 return new Promise(truncate).then(didTruncate, onTruncateError);
120 return WebInspector.TempFile.ensureTempStorageCleared()
121 .then(requestTempFileSystem)
122 .then(getDirectoryEntry)
124 .then(createFileWriter)
128 WebInspector.TempFile.prototype = {
130 * @param {!Array.<string>} strings
131 * @param {function(number)} callback
133 write: function(strings, callback)
135 var blob = new Blob(strings, {type: 'text/plain'});
136 this._writer.onerror = function(e)
138 WebInspector.console.error("Failed to write into a temp file: " + e.target.error.message);
141 this._writer.onwriteend = function(e)
143 callback(e.target.length);
145 this._writer.write(blob);
148 finishWriting: function()
154 * @param {function(?string)} callback
156 read: function(callback)
158 this.readRange(undefined, undefined, callback);
162 * @param {number|undefined} startOffset
163 * @param {number|undefined} endOffset
164 * @param {function(?string)} callback
166 readRange: function(startOffset, endOffset, callback)
169 * @param {!Blob} file
171 function didGetFile(file)
173 var reader = new FileReader();
175 if (typeof startOffset === "number" || typeof endOffset === "number")
176 file = file.slice(/** @type {number} */ (startOffset), /** @type {number} */ (endOffset));
180 reader.onloadend = function(e)
182 callback(/** @type {?string} */ (this.result));
184 reader.onerror = function(error)
186 WebInspector.console.error("Failed to read from temp file: " + error.message);
188 reader.readAsText(file);
190 function didFailToGetFile(error)
192 WebInspector.console.error("Failed to load temp file: " + error.message);
195 this._fileEntry.file(didGetFile, didFailToGetFile);
199 * @param {!WebInspector.OutputStream} outputStream
200 * @param {!WebInspector.OutputStreamDelegate} delegate
202 writeToOutputSteam: function(outputStream, delegate)
205 * @param {!File} file
207 function didGetFile(file)
209 var reader = new WebInspector.ChunkedFileReader(file, 10*1000*1000, delegate);
210 reader.start(outputStream);
213 function didFailToGetFile(error)
215 WebInspector.console.error("Failed to load temp file: " + error.message);
216 outputStream.close();
219 this._fileEntry.file(didGetFile, didFailToGetFile);
225 this._fileEntry.remove(function() {});
231 * @param {string} dirPath
232 * @param {string} name
234 WebInspector.DeferredTempFile = function(dirPath, name)
236 /** @type {!Array.<!{strings: !Array.<string>, callback: function(number)}>} */
238 this._tempFile = null;
239 this._isWriting = false;
240 this._finishCallback = null;
241 this._finishedWriting = false;
242 this._callsPendingOpen = [];
243 this._pendingReads = [];
244 WebInspector.TempFile.create(dirPath, name)
245 .then(this._didCreateTempFile.bind(this), this._failedToCreateTempFile.bind(this));
248 WebInspector.DeferredTempFile.prototype = {
250 * @param {!Array.<string>} strings
251 * @param {function(number)=} callback
253 write: function(strings, callback)
257 if (this._finishCallback)
258 throw new Error("No writes are allowed after close.");
259 this._chunks.push({strings: strings, callback: callback});
260 if (this._tempFile && !this._isWriting)
261 this._writeNextChunk();
265 * @param {function(?WebInspector.TempFile)} callback
267 finishWriting: function(callback)
269 this._finishCallback = callback;
270 if (this._finishedWriting)
271 callback(this._tempFile);
272 else if (!this._isWriting && !this._chunks.length)
273 this._notifyFinished();
279 _failedToCreateTempFile: function(e)
281 WebInspector.console.error("Failed to create temp file " + e.code + " : " + e.message);
282 this._notifyFinished();
286 * @param {!WebInspector.TempFile} tempFile
288 _didCreateTempFile: function(tempFile)
290 this._tempFile = tempFile;
291 var callsPendingOpen = this._callsPendingOpen;
292 this._callsPendingOpen = null;
293 for (var i = 0; i < callsPendingOpen.length; ++i)
294 callsPendingOpen[i]();
295 if (this._chunks.length)
296 this._writeNextChunk();
299 _writeNextChunk: function()
301 // File was deleted while create or write was in-flight.
304 var chunk = this._chunks.shift();
305 this._isWriting = true;
306 this._tempFile.write(/** @type {!Array.<string>} */(chunk.strings), this._didWriteChunk.bind(this, chunk.callback));
310 * @param {?function(number)} callback
311 * @param {number} size
313 _didWriteChunk: function(callback, size)
315 this._isWriting = false;
317 this._tempFile = null;
318 this._notifyFinished();
323 if (this._chunks.length)
324 this._writeNextChunk();
325 else if (this._finishCallback)
326 this._notifyFinished();
329 _notifyFinished: function()
331 this._finishedWriting = true;
333 this._tempFile.finishWriting();
334 var chunks = this._chunks;
336 for (var i = 0; i < chunks.length; ++i) {
337 if (chunks[i].callback)
338 chunks[i].callback(-1);
340 if (this._finishCallback)
341 this._finishCallback(this._tempFile);
342 var pendingReads = this._pendingReads;
343 this._pendingReads = [];
344 for (var i = 0; i < pendingReads.length; ++i)
349 * @param {number|undefined} startOffset
350 * @param {number|undefined} endOffset
351 * @param {function(string?)} callback
353 readRange: function(startOffset, endOffset, callback)
355 if (!this._finishedWriting) {
356 this._pendingReads.push(this.readRange.bind(this, startOffset, endOffset, callback));
359 if (!this._tempFile) {
363 this._tempFile.readRange(startOffset, endOffset, callback);
367 * @param {!WebInspector.OutputStream} outputStream
368 * @param {!WebInspector.OutputStreamDelegate} delegate
370 writeToOutputStream: function(outputStream, delegate)
372 if (this._callsPendingOpen) {
373 this._callsPendingOpen.push(this.writeToOutputStream.bind(this, outputStream, delegate));
377 this._tempFile.writeToOutputSteam(outputStream, delegate);
382 if (this._callsPendingOpen) {
383 this._callsPendingOpen.push(this.remove.bind(this));
387 this._tempFile.remove();
388 this._tempFile = null;
393 * @param {function(?)} fulfill
394 * @param {function(*)} reject
396 WebInspector.TempFile._clearTempStorage = function(fulfill, reject)
399 * @param {!Event} event
401 function handleError(event)
403 WebInspector.console.error(WebInspector.UIString("Failed to clear temp storage: %s", event.data));
408 * @param {!Event} event
410 function handleMessage(event)
412 if (event.data.type === "tempStorageCleared") {
413 if (event.data.error)
414 WebInspector.console.error(event.data.error);
423 var worker = new WorkerRuntime.Worker("temp_storage_shared_worker", "TempStorageCleaner");
424 worker.onerror = handleError;
425 worker.port.onmessage = handleMessage;
426 worker.port.onerror = handleError;
428 if (e.name === "URLMismatchError")
429 console.log("Shared worker wasn't started due to url difference. " + e);
436 * @return {!Promise.<undefined>}
438 WebInspector.TempFile.ensureTempStorageCleared = function()
440 if (!WebInspector.TempFile._storageCleanerPromise)
441 WebInspector.TempFile._storageCleanerPromise = new Promise(WebInspector.TempFile._clearTempStorage);
442 return WebInspector.TempFile._storageCleanerPromise;
447 * @implements {WebInspector.BackingStorage}
448 * @param {string} dirName
450 WebInspector.TempFileBackingStorage = function(dirName)
452 this._dirName = dirName;
459 * startOffset: number,
463 WebInspector.TempFileBackingStorage.Chunk;
465 WebInspector.TempFileBackingStorage.prototype = {
468 * @param {string} string
470 appendString: function(string)
472 this._strings.push(string);
473 this._stringsLength += string.length;
474 var flushStringLength = 10 * 1024 * 1024;
475 if (this._stringsLength > flushStringLength)
481 * @param {string} string
482 * @return {function():!Promise.<?string>}
484 appendAccessibleString: function(string)
487 this._strings.push(string);
488 var chunk = /** @type {!WebInspector.TempFileBackingStorage.Chunk} */ (this._flush(true));
491 * @param {!WebInspector.TempFileBackingStorage.Chunk} chunk
492 * @param {!WebInspector.DeferredTempFile} file
493 * @return {!Promise.<?string>}
495 function readString(chunk, file)
498 return /** @type {!Promise.<?string>} */ (Promise.resolve(chunk.string));
500 console.assert(chunk.endOffset);
501 if (!chunk.endOffset)
502 return Promise.reject("Nor string nor offset to the string in the file were found.");
505 * @param {function(?string)} fulfill
506 * @param {function(*)} reject
508 function readRange(fulfill, reject)
510 // FIXME: call reject for null strings.
511 file.readRange(chunk.startOffset, chunk.endOffset, fulfill);
514 return new Promise(readRange);
517 return readString.bind(null, chunk, this._file);
521 * @param {boolean} createChunk
522 * @return {?WebInspector.TempFileBackingStorage.Chunk}
524 _flush: function(createChunk)
526 if (!this._strings.length)
531 console.assert(this._strings.length === 1);
533 string: this._strings[0],
540 * @this {WebInspector.TempFileBackingStorage}
541 * @param {?WebInspector.TempFileBackingStorage.Chunk} chunk
542 * @param {number} fileSize
544 function didWrite(chunk, fileSize)
549 chunk.startOffset = this._fileSize;
550 chunk.endOffset = fileSize;
553 this._fileSize = fileSize;
556 this._file.write(this._strings, didWrite.bind(this, chunk));
558 this._stringsLength = 0;
565 finishWriting: function()
568 this._file.finishWriting(function() {});
578 this._file = new WebInspector.DeferredTempFile(this._dirName, String(Date.now()));
580 * @type {!Array.<string>}
583 this._stringsLength = 0;
588 * @param {!WebInspector.OutputStream} outputStream
589 * @param {!WebInspector.OutputStreamDelegate} delegate
591 writeToStream: function(outputStream, delegate)
593 this._file.writeToOutputStream(outputStream, delegate);