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 * @interface 33 */ 34WebInspector.OutputStreamDelegate = function() 35{ 36} 37 38WebInspector.OutputStreamDelegate.prototype = { 39 onTransferStarted: function() { }, 40 41 onTransferFinished: function() { }, 42 43 /** 44 * @param {!WebInspector.ChunkedReader} reader 45 */ 46 onChunkTransferred: function(reader) { }, 47 48 /** 49 * @param {!WebInspector.ChunkedReader} reader 50 */ 51 onError: function(reader, event) { }, 52} 53 54/** 55 * @interface 56 */ 57WebInspector.OutputStream = function() 58{ 59} 60 61WebInspector.OutputStream.prototype = { 62 /** 63 * @param {string} data 64 * @param {function(!WebInspector.OutputStream)=} callback 65 */ 66 write: function(data, callback) { }, 67 68 close: function() { } 69} 70 71/** 72 * @interface 73 */ 74WebInspector.ChunkedReader = function() 75{ 76} 77 78WebInspector.ChunkedReader.prototype = { 79 /** 80 * @return {number} 81 */ 82 fileSize: function() { }, 83 84 /** 85 * @return {number} 86 */ 87 loadedSize: function() { }, 88 89 /** 90 * @return {string} 91 */ 92 fileName: function() { }, 93 94 cancel: function() { } 95} 96 97/** 98 * @constructor 99 * @implements {WebInspector.ChunkedReader} 100 * @param {!File} file 101 * @param {number} chunkSize 102 * @param {!WebInspector.OutputStreamDelegate} delegate 103 */ 104WebInspector.ChunkedFileReader = function(file, chunkSize, delegate) 105{ 106 this._file = file; 107 this._fileSize = file.size; 108 this._loadedSize = 0; 109 this._chunkSize = chunkSize; 110 this._delegate = delegate; 111 this._isCanceled = false; 112} 113 114WebInspector.ChunkedFileReader.prototype = { 115 /** 116 * @param {!WebInspector.OutputStream} output 117 */ 118 start: function(output) 119 { 120 this._output = output; 121 122 this._reader = new FileReader(); 123 this._reader.onload = this._onChunkLoaded.bind(this); 124 this._reader.onerror = this._delegate.onError.bind(this._delegate, this); 125 this._delegate.onTransferStarted(); 126 this._loadChunk(); 127 }, 128 129 cancel: function() 130 { 131 this._isCanceled = true; 132 }, 133 134 /** 135 * @return {number} 136 */ 137 loadedSize: function() 138 { 139 return this._loadedSize; 140 }, 141 142 /** 143 * @return {number} 144 */ 145 fileSize: function() 146 { 147 return this._fileSize; 148 }, 149 150 /** 151 * @return {string} 152 */ 153 fileName: function() 154 { 155 return this._file.name; 156 }, 157 158 /** 159 * @param {?Event} event 160 */ 161 _onChunkLoaded: function(event) 162 { 163 if (this._isCanceled) 164 return; 165 166 if (event.target.readyState !== FileReader.DONE) 167 return; 168 169 var data = event.target.result; 170 this._loadedSize += data.length; 171 172 this._output.write(data); 173 if (this._isCanceled) 174 return; 175 this._delegate.onChunkTransferred(this); 176 177 if (this._loadedSize === this._fileSize) { 178 this._file = null; 179 this._reader = null; 180 this._output.close(); 181 this._delegate.onTransferFinished(); 182 return; 183 } 184 185 this._loadChunk(); 186 }, 187 188 _loadChunk: function() 189 { 190 var chunkStart = this._loadedSize; 191 var chunkEnd = Math.min(this._fileSize, chunkStart + this._chunkSize) 192 var nextPart = this._file.slice(chunkStart, chunkEnd); 193 this._reader.readAsText(nextPart); 194 } 195} 196 197/** 198 * @constructor 199 * @implements {WebInspector.ChunkedReader} 200 * @param {string} url 201 * @param {!WebInspector.OutputStreamDelegate} delegate 202 */ 203WebInspector.ChunkedXHRReader = function(url, delegate) 204{ 205 this._url = url; 206 this._delegate = delegate; 207 this._fileSize = 0; 208 this._loadedSize = 0; 209 this._isCanceled = false; 210} 211 212WebInspector.ChunkedXHRReader.prototype = { 213 /** 214 * @param {!WebInspector.OutputStream} output 215 */ 216 start: function(output) 217 { 218 this._output = output; 219 220 this._xhr = new XMLHttpRequest(); 221 this._xhr.open("GET", this._url, true); 222 this._xhr.onload = this._onLoad.bind(this); 223 this._xhr.onprogress = this._onProgress.bind(this); 224 this._xhr.onerror = this._delegate.onError.bind(this._delegate, this); 225 this._xhr.send(null); 226 227 this._delegate.onTransferStarted(); 228 }, 229 230 cancel: function() 231 { 232 this._isCanceled = true; 233 this._xhr.abort(); 234 }, 235 236 /** 237 * @return {number} 238 */ 239 loadedSize: function() 240 { 241 return this._loadedSize; 242 }, 243 244 /** 245 * @return {number} 246 */ 247 fileSize: function() 248 { 249 return this._fileSize; 250 }, 251 252 /** 253 * @return {string} 254 */ 255 fileName: function() 256 { 257 return this._url; 258 }, 259 260 /** 261 * @param {?Event} event 262 */ 263 _onProgress: function(event) 264 { 265 if (this._isCanceled) 266 return; 267 268 if (event.lengthComputable) 269 this._fileSize = event.total; 270 271 var data = this._xhr.responseText.substring(this._loadedSize); 272 if (!data.length) 273 return; 274 275 this._loadedSize += data.length; 276 this._output.write(data); 277 if (this._isCanceled) 278 return; 279 this._delegate.onChunkTransferred(this); 280 }, 281 282 /** 283 * @param {?Event} event 284 */ 285 _onLoad: function(event) 286 { 287 this._onProgress(event); 288 289 if (this._isCanceled) 290 return; 291 292 this._output.close(); 293 this._delegate.onTransferFinished(); 294 } 295} 296 297/** 298 * @param {function(!File)} callback 299 * @return {!Node} 300 */ 301WebInspector.createFileSelectorElement = function(callback) { 302 var fileSelectorElement = document.createElement("input"); 303 fileSelectorElement.type = "file"; 304 fileSelectorElement.style.display = "none"; 305 fileSelectorElement.setAttribute("tabindex", -1); 306 fileSelectorElement.onchange = onChange; 307 function onChange(event) 308 { 309 callback(fileSelectorElement.files[0]); 310 }; 311 return fileSelectorElement; 312} 313 314/** 315 * @param {string} source 316 * @param {number=} startIndex 317 * @param {number=} lastIndex 318 */ 319WebInspector.findBalancedCurlyBrackets = function(source, startIndex, lastIndex) { 320 lastIndex = lastIndex || source.length; 321 startIndex = startIndex || 0; 322 var counter = 0; 323 var inString = false; 324 325 for (var index = startIndex; index < lastIndex; ++index) { 326 var character = source[index]; 327 if (inString) { 328 if (character === "\\") 329 ++index; 330 else if (character === "\"") 331 inString = false; 332 } else { 333 if (character === "\"") 334 inString = true; 335 else if (character === "{") 336 ++counter; 337 else if (character === "}") { 338 if (--counter === 0) 339 return index + 1; 340 } 341 } 342 } 343 return -1; 344} 345 346/** 347 * @constructor 348 * @implements {WebInspector.OutputStream} 349 */ 350WebInspector.FileOutputStream = function() 351{ 352} 353 354WebInspector.FileOutputStream.prototype = { 355 /** 356 * @param {string} fileName 357 * @param {function(boolean)} callback 358 */ 359 open: function(fileName, callback) 360 { 361 this._closed = false; 362 this._writeCallbacks = []; 363 this._fileName = fileName; 364 365 /** 366 * @param {boolean} accepted 367 * @this {WebInspector.FileOutputStream} 368 */ 369 function callbackWrapper(accepted) 370 { 371 if (accepted) 372 WebInspector.fileManager.addEventListener(WebInspector.FileManager.EventTypes.AppendedToURL, this._onAppendDone, this); 373 callback(accepted); 374 } 375 WebInspector.fileManager.save(this._fileName, "", true, callbackWrapper.bind(this)); 376 }, 377 378 /** 379 * @param {string} data 380 * @param {function(!WebInspector.OutputStream)=} callback 381 */ 382 write: function(data, callback) 383 { 384 this._writeCallbacks.push(callback); 385 WebInspector.fileManager.append(this._fileName, data); 386 }, 387 388 close: function() 389 { 390 this._closed = true; 391 if (this._writeCallbacks.length) 392 return; 393 WebInspector.fileManager.removeEventListener(WebInspector.FileManager.EventTypes.AppendedToURL, this._onAppendDone, this); 394 WebInspector.fileManager.close(this._fileName); 395 }, 396 397 /** 398 * @param {?Event} event 399 */ 400 _onAppendDone: function(event) 401 { 402 if (event.data !== this._fileName) 403 return; 404 var callback = this._writeCallbacks.shift(); 405 if (callback) 406 callback(this); 407 if (!this._writeCallbacks.length) { 408 if (this._closed) { 409 WebInspector.fileManager.removeEventListener(WebInspector.FileManager.EventTypes.AppendedToURL, this._onAppendDone, this); 410 WebInspector.fileManager.close(this._fileName); 411 } 412 } 413 } 414} 415