diff options
Diffstat (limited to 'contexts/data/lib/closure-library/closure/goog/net/filedownloader.js')
-rw-r--r-- | contexts/data/lib/closure-library/closure/goog/net/filedownloader.js | 741 |
1 files changed, 0 insertions, 741 deletions
diff --git a/contexts/data/lib/closure-library/closure/goog/net/filedownloader.js b/contexts/data/lib/closure-library/closure/goog/net/filedownloader.js deleted file mode 100644 index da551a1..0000000 --- a/contexts/data/lib/closure-library/closure/goog/net/filedownloader.js +++ /dev/null @@ -1,741 +0,0 @@ -// Copyright 2011 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview A class for downloading remote files and storing them - * locally using the HTML5 FileSystem API. - * - * The directory structure is of the form /HASH/URL/BASENAME: - * - * The HASH portion is a three-character slice of the hash of the URL. Since the - * filesystem has a limit of about 5000 files per directory, this should divide - * the downloads roughly evenly among about 5000 directories, thus allowing for - * at most 5000^2 downloads. - * - * The URL portion is the (sanitized) full URL used for downloading the file. - * This is used to ensure that each file ends up in a different location, even - * if the HASH and BASENAME are the same. - * - * The BASENAME portion is the basename of the URL. It's used for the filename - * proper so that the local filesystem: URL will be downloaded to a file with a - * recognizable name. - * - */ - -goog.provide('goog.net.FileDownloader'); -goog.provide('goog.net.FileDownloader.Error'); - -goog.require('goog.Disposable'); -goog.require('goog.asserts'); -goog.require('goog.async.Deferred'); -goog.require('goog.crypt.hash32'); -goog.require('goog.debug.Error'); -goog.require('goog.events.EventHandler'); -goog.require('goog.fs'); -goog.require('goog.fs.DirectoryEntry.Behavior'); -goog.require('goog.fs.Error.ErrorCode'); -goog.require('goog.fs.FileSaver.EventType'); -goog.require('goog.net.EventType'); -goog.require('goog.net.XhrIo.ResponseType'); -goog.require('goog.net.XhrIoPool'); - - - -/** - * A class for downloading remote files and storing them locally using the - * HTML5 filesystem API. - * - * @param {!goog.fs.DirectoryEntry} dir The directory in which the downloaded - * files are stored. This directory should be solely managed by - * FileDownloader. - * @param {goog.net.XhrIoPool=} opt_pool The pool of XhrIo objects to use for - * downloading files. - * @constructor - * @extends {goog.Disposable} - */ -goog.net.FileDownloader = function(dir, opt_pool) { - goog.base(this); - - /** - * The directory in which the downloaded files are stored. - * @type {!goog.fs.DirectoryEntry} - * @private - */ - this.dir_ = dir; - - /** - * The pool of XHRs to use for capturing. - * @type {!goog.net.XhrIoPool} - * @private - */ - this.pool_ = opt_pool || new goog.net.XhrIoPool(); - - /** - * A map from URLs to active downloads running for those URLs. - * @type {!Object.<!goog.net.FileDownloader.Download_>} - * @private - */ - this.downloads_ = {}; - - /** - * The handler for URL capturing events. - * @type {!goog.events.EventHandler} - * @private - */ - this.eventHandler_ = new goog.events.EventHandler(this); -}; -goog.inherits(goog.net.FileDownloader, goog.Disposable); - - -/** - * Download a remote file and save its contents to the filesystem. A given file - * is uniquely identified by its URL string; this means that the relative and - * absolute URLs for a single file are considered different for the purposes of - * the FileDownloader. - * - * Returns a Deferred that will contain the downloaded blob. If there's an error - * while downloading the URL, this Deferred will be passed the - * {@link goog.net.FileDownloader.Error} object as an errback. - * - * If a download is already in progress for the given URL, this will return the - * deferred blob for that download. If the URL has already been downloaded, this - * will fail once it tries to save the downloaded blob. - * - * When a download is in progress, all Deferreds returned for that download will - * be branches of a single parent. If all such branches are cancelled, or if one - * is cancelled with opt_deepCancel set, then the download will be cancelled as - * well. - * - * @param {string} url The URL of the file to download. - * @return {!goog.async.Deferred} The deferred result blob. - */ -goog.net.FileDownloader.prototype.download = function(url) { - if (this.isDownloading(url)) { - return this.downloads_[url].deferred.branch(true /* opt_propagateCancel */); - } - - var download = new goog.net.FileDownloader.Download_(url, this); - this.downloads_[url] = download; - this.pool_.getObject(goog.bind(this.gotXhr_, this, download)); - return download.deferred.branch(true /* opt_propagateCancel */); -}; - - -/** - * Return a Deferred that will fire once no download is active for a given URL. - * If there's no download active for that URL when this is called, the deferred - * will fire immediately; otherwise, it will fire once the download is complete, - * whether or not it succeeds. - * - * @param {string} url The URL of the download to wait for. - * @return {!goog.async.Deferred} The Deferred that will fire when the download - * is complete. - */ -goog.net.FileDownloader.prototype.waitForDownload = function(url) { - var deferred = new goog.async.Deferred(); - if (this.isDownloading(url)) { - this.downloads_[url].deferred.addBoth(function() { - deferred.callback(null); - }, this); - } else { - deferred.callback(null); - } - return deferred; -}; - - -/** - * Returns whether or not there is an active download for a given URL. - * - * @param {string} url The URL of the download to check. - * @return {boolean} Whether or not there is an active download for the URL. - */ -goog.net.FileDownloader.prototype.isDownloading = function(url) { - return url in this.downloads_; -}; - - -/** - * Load a downloaded blob from the filesystem. Will fire a deferred error if the - * given URL has not yet been downloaded. - * - * @param {string} url The URL of the blob to load. - * @return {!goog.async.Deferred} The deferred Blob object. The callback will be - * passed the blob. If a file API error occurs while loading the blob, that - * error will be passed to the errback. - */ -goog.net.FileDownloader.prototype.getDownloadedBlob = function(url) { - return this.getFile_(url). - addCallback(function(fileEntry) { return fileEntry.file(); }); -}; - - -/** - * Get the local filesystem: URL for a downloaded file. This is different from - * the blob: URL that's available from getDownloadedBlob(). If the end user - * accesses the filesystem: URL, the resulting file's name will be determined by - * the download filename as opposed to an arbitrary GUID. In addition, the - * filesystem: URL is connected to a filesystem location, so if the download is - * removed then that URL will become invalid. - * - * Warning: in Chrome 12, some filesystem: URLs are opened inline. This means - * that e.g. HTML pages given to the user via filesystem: URLs will be opened - * and processed by the browser. - * - * @param {string} url The URL of the file to get the URL of. - * @return {!goog.async.Deferred} The deferred filesystem: URL. The callback - * will be passed the URL. If a file API error occurs while loading the - * blob, that error will be passed to the errback. - */ -goog.net.FileDownloader.prototype.getLocalUrl = function(url) { - return this.getFile_(url). - addCallback(function(fileEntry) { return fileEntry.toUrl(); }); -}; - - -/** - * Return (deferred) whether or not a URL has been downloaded. Will fire a - * deferred error if something goes wrong when determining this. - * - * @param {string} url The URL to check. - * @return {!goog.async.Deferred} The deferred boolean. The callback will be - * passed the boolean. If a file API error occurs while checking the - * existence of the downloaded URL, that error will be passed to the - * errback. - */ -goog.net.FileDownloader.prototype.isDownloaded = function(url) { - var deferred = new goog.async.Deferred(); - var blobDeferred = this.getDownloadedBlob(url); - blobDeferred.addCallback(function() { - deferred.callback(true); - }); - blobDeferred.addErrback(function(err) { - if (err.code == goog.fs.Error.ErrorCode.NOT_FOUND) { - deferred.callback(false); - } else { - deferred.errback(err); - } - }); - return deferred; -}; - - -/** - * Remove a URL from the FileDownloader. - * - * This returns a Deferred. If the removal is completed successfully, its - * callback will be called without any value. If the removal fails, its errback - * will be called with the {@link goog.fs.Error}. - * - * @param {string} url The URL to remove. - * @return {!goog.async.Deferred} The deferred used for registering callbacks on - * success or on error. - */ -goog.net.FileDownloader.prototype.remove = function(url) { - return this.getDir_(url, goog.fs.DirectoryEntry.Behavior.DEFAULT). - addCallback(function(dir) { return dir.removeRecursively(); }); -}; - - -/** - * Save a blob for a given URL. This works just as through the blob were - * downloaded form that URL, except you specify the blob and no HTTP request is - * made. - * - * If the URL is currently being downloaded, it's indeterminate whether the blob - * being set or the blob being downloaded will end up in the filesystem. - * Whichever one doesn't get saved will have an error. To ensure that one or the - * other takes precedence, use {@link #waitForDownload} to allow the download to - * complete before setting the blob. - * - * @param {string} url The URL at which to set the blob. - * @param {!Blob} blob The blob to set. - * @param {string=} opt_name The name of the file. If this isn't given, it's - * determined from the URL. - * @return {!goog.async.Deferred} The deferred used for registering callbacks on - * success or on error. This can be cancelled just like a {@link #download} - * Deferred. The objects passed to the errback will be - * {@link goog.net.FileDownloader.Error}s. - */ -goog.net.FileDownloader.prototype.setBlob = function(url, blob, opt_name) { - var name = this.sanitize_(opt_name || this.urlToName_(url)); - var download = new goog.net.FileDownloader.Download_(url, this); - this.downloads_[url] = download; - download.blob = blob; - this.getDir_(download.url, goog.fs.DirectoryEntry.Behavior.CREATE_EXCLUSIVE). - addCallback(function(dir) { - return dir.getFile( - name, goog.fs.DirectoryEntry.Behavior.CREATE_EXCLUSIVE); - }). - addCallback(goog.bind(this.fileSuccess_, this, download)). - addErrback(goog.bind(this.error_, this, download)); - return download.deferred.branch(true /* opt_propagateCancel */); -}; - - -/** - * The callback called when an XHR becomes available from the XHR pool. - * - * @param {!goog.net.FileDownloader.Download_} download The download object for - * this download. - * @param {!goog.net.XhrIo} xhr The XhrIo object for downloading the page. - * @private - */ -goog.net.FileDownloader.prototype.gotXhr_ = function(download, xhr) { - if (download.cancelled) { - this.freeXhr_(xhr); - return; - } - - this.eventHandler_.listen( - xhr, goog.net.EventType.SUCCESS, - goog.bind(this.xhrSuccess_, this, download)); - this.eventHandler_.listen( - xhr, [goog.net.EventType.ERROR, goog.net.EventType.ABORT], - goog.bind(this.error_, this, download)); - this.eventHandler_.listen( - xhr, goog.net.EventType.READY, - goog.bind(this.freeXhr_, this, xhr)); - - download.xhr = xhr; - xhr.setResponseType(goog.net.XhrIo.ResponseType.ARRAY_BUFFER); - xhr.send(download.url); -}; - - -/** - * The callback called when an XHR succeeds in downloading a remote file. - * - * @param {!goog.net.FileDownloader.Download_} download The download object for - * this download. - * @private - */ -goog.net.FileDownloader.prototype.xhrSuccess_ = function(download) { - if (download.cancelled) { - return; - } - - var name = this.sanitize_(this.getName_( - /** @type {!goog.net.XhrIo} */ (download.xhr))); - var resp = /** @type {ArrayBuffer} */ (download.xhr.getResponse()); - if (!resp) { - // This should never happen - it indicates the XHR hasn't completed, has - // failed or has been cleaned up. If it does happen (eg. due to a bug - // somewhere) we don't want to pass null to getBlob - it's not valid and - // triggers a bug in some versions of WebKit causing it to crash. - this.error_(download); - return; - } - - download.blob = goog.fs.getBlob(resp); - delete download.xhr; - - this.getDir_(download.url, goog.fs.DirectoryEntry.Behavior.CREATE_EXCLUSIVE). - addCallback(function(dir) { - return dir.getFile( - name, goog.fs.DirectoryEntry.Behavior.CREATE_EXCLUSIVE); - }). - addCallback(goog.bind(this.fileSuccess_, this, download)). - addErrback(goog.bind(this.error_, this, download)); -}; - - -/** - * The callback called when a file that will be used for saving a file is - * successfully opened. - * - * @param {!goog.net.FileDownloader.Download_} download The download object for - * this download. - * @param {!goog.fs.FileEntry} file The newly-opened file object. - * @private - */ -goog.net.FileDownloader.prototype.fileSuccess_ = function(download, file) { - if (download.cancelled) { - file.remove(); - return; - } - - download.file = file; - file.createWriter(). - addCallback(goog.bind(this.fileWriterSuccess_, this, download)). - addErrback(goog.bind(this.error_, this, download)); -}; - - -/** - * The callback called when a file writer is succesfully created for writing a - * file to the filesystem. - * - * @param {!goog.net.FileDownloader.Download_} download The download object for - * this download. - * @param {!goog.fs.FileWriter} writer The newly-created file writer object. - * @private - */ -goog.net.FileDownloader.prototype.fileWriterSuccess_ = function( - download, writer) { - if (download.cancelled) { - download.file.remove(); - return; - } - - download.writer = writer; - writer.write(/** @type {!Blob} */ (download.blob)); - this.eventHandler_.listenOnce( - writer, - goog.fs.FileSaver.EventType.WRITE_END, - goog.bind(this.writeEnd_, this, download)); -}; - - -/** - * The callback called when file writing ends, whether or not it's successful. - * - * @param {!goog.net.FileDownloader.Download_} download The download object for - * this download. - * @private - */ -goog.net.FileDownloader.prototype.writeEnd_ = function(download) { - if (download.cancelled || download.writer.getError()) { - this.error_(download, download.writer.getError()); - return; - } - - delete this.downloads_[download.url]; - download.deferred.callback(download.blob); -}; - - -/** - * The error callback for all asynchronous operations. Ensures that all stages - * of a given download are cleaned up, and emits the error event. - * - * @param {!goog.net.FileDownloader.Download_} download The download object for - * this download. - * @param {goog.fs.Error=} opt_err The file error object. Only defined if the - * error was raised by the file API. - * @private - */ -goog.net.FileDownloader.prototype.error_ = function(download, opt_err) { - if (download.file) { - download.file.remove(); - } - - if (download.cancelled) { - return; - } - - delete this.downloads_[download.url]; - download.deferred.errback( - new goog.net.FileDownloader.Error(download, opt_err)); -}; - - -/** - * Abort the download of the given URL. - * - * @param {!goog.net.FileDownloader.Download_} download The download to abort. - * @private - */ -goog.net.FileDownloader.prototype.cancel_ = function(download) { - goog.dispose(download); - delete this.downloads_[download.url]; -}; - - -/** - * Get the directory for a given URL. If the directory already exists when this - * is called, it will contain exactly one file: the downloaded file. - * - * This not only calls the FileSystem API's getFile method, but attempts to - * distribute the files so that they don't overload the filesystem. The spec - * says directories can't contain more than 5000 files - * (http://www.w3.org/TR/file-system-api/#directories), so this ensures that - * each file is put into a subdirectory based on its SHA1 hash. - * - * All parameters are the same as in the FileSystem API's Entry#getFile method. - * - * @param {string} url The URL corresponding to the directory to get. - * @param {goog.fs.DirectoryEntry.Behavior} behavior The behavior to pass to the - * underlying method. - * @return {!goog.async.Deferred} The deferred DirectoryEntry object. - * @private - */ -goog.net.FileDownloader.prototype.getDir_ = function(url, behavior) { - // 3 hex digits provide 16**3 = 4096 different possible dirnames, which is - // less than the maximum of 5000 entries. Downloaded files should be - // distributed roughly evenly throughout the directories due to the hash - // function, allowing many more than 5000 files to be downloaded. - // - // The leading ` ensures that no illegal dirnames are accidentally used. % was - // previously used, but Chrome has a bug (as of 12.0.725.0 dev) where - // filenames are URL-decoded before checking their validity, so filenames - // containing e.g. '%3f' (the URL-encoding of :, an invalid character) are - // rejected. - var dirname = '`' + Math.abs(goog.crypt.hash32.encodeString(url)). - toString(16).substring(0, 3); - - return this.dir_. - getDirectory(dirname, goog.fs.DirectoryEntry.Behavior.CREATE). - addCallback(function(dir) { - return dir.getDirectory(this.sanitize_(url), behavior); - }, this); -}; - - -/** - * Get the file for a given URL. This will only retrieve files that have already - * been saved; it shouldn't be used for creating the file in the first place. - * This is because the filename isn't necessarily determined by the URL, but by - * the headers of the XHR response. - * - * @param {string} url The URL corresponding to the file to get. - * @return {!goog.async.Deferred} The deferred FileEntry object. - * @private - */ -goog.net.FileDownloader.prototype.getFile_ = function(url) { - return this.getDir_(url, goog.fs.DirectoryEntry.Behavior.DEFAULT). - addCallback(function(dir) { - return dir.listDirectory().addCallback(function(files) { - goog.asserts.assert(files.length == 1); - // If the filesystem somehow gets corrupted and we end up with an - // empty directory here, it makes sense to just return the normal - // file-not-found error. - return files[0] || dir.getFile('file'); - }); - }); -}; - - -/** - * Sanitize a string so it can be safely used as a file or directory name for - * the FileSystem API. - * - * @param {string} str The string to sanitize. - * @return {string} The sanitized string. - * @private - */ -goog.net.FileDownloader.prototype.sanitize_ = function(str) { - // Add a prefix, since certain prefixes are disallowed for paths. None of the - // disallowed prefixes start with '`'. We use ` rather than % for escaping the - // filename due to a Chrome bug (as of 12.0.725.0 dev) where filenames are - // URL-decoded before checking their validity, so filenames containing e.g. - // '%3f' (the URL-encoding of :, an invalid character) are rejected. - return '`' + str.replace(/[\/\\<>:?*"|%`]/g, encodeURIComponent). - replace(/%/g, '`'); -}; - - -/** - * Gets the filename specified by the XHR. This first attempts to parse the - * Content-Disposition header for a filename and, failing that, falls back on - * deriving the filename from the URL. - * - * @param {!goog.net.XhrIo} xhr The XHR containing the response headers. - * @return {string} The filename. - * @private - */ -goog.net.FileDownloader.prototype.getName_ = function(xhr) { - var disposition = xhr.getResponseHeader('Content-Disposition'); - var match = disposition && - disposition.match(/^attachment *; *filename="(.*)"$/i); - if (match) { - // The Content-Disposition header allows for arbitrary backslash-escaped - // characters (usually " and \). We want to unescape them before using them - // in the filename. - return match[1].replace(/\\(.)/g, '$1'); - } - - return this.urlToName_(xhr.getLastUri()); -}; - - -/** - * Extracts the basename from a URL. - * - * @param {string} url The URL. - * @return {string} The basename. - * @private - */ -goog.net.FileDownloader.prototype.urlToName_ = function(url) { - var segments = url.split('/'); - return segments[segments.length - 1]; -}; - - -/** - * Remove all event listeners for an XHR and release it back into the pool. - * - * @param {!goog.net.XhrIo} xhr The XHR to free. - * @private - */ -goog.net.FileDownloader.prototype.freeXhr_ = function(xhr) { - goog.events.removeAll(xhr); - this.pool_.addFreeObject(xhr); -}; - - -/** @override */ -goog.net.FileDownloader.prototype.disposeInternal = function() { - delete this.dir_; - goog.dispose(this.eventHandler_); - delete this.eventHandler_; - goog.object.forEach(this.downloads_, function(download) { - download.deferred.cancel(); - }, this); - delete this.downloads_; - goog.dispose(this.pool_); - delete this.pool_; - - goog.base(this, 'disposeInternal'); -}; - - - -/** - * The error object for FileDownloader download errors. - * - * @param {!goog.net.FileDownloader.Download_} download The download object for - * the download in question. - * @param {goog.fs.Error=} opt_fsErr The file error object, if this was a file - * error. - * - * @constructor - * @extends {goog.debug.Error} - */ -goog.net.FileDownloader.Error = function(download, opt_fsErr) { - goog.base(this, 'Error capturing URL ' + download.url); - - /** - * The URL the event relates to. - * @type {string} - */ - this.url = download.url; - - if (download.xhr) { - this.xhrStatus = download.xhr.getStatus(); - this.xhrErrorCode = download.xhr.getLastErrorCode(); - this.message += ': XHR failed with status ' + this.xhrStatus + - ' (error code ' + this.xhrErrorCode + ')'; - } else if (opt_fsErr) { - this.fileError = opt_fsErr; - this.message += ': file API failed (' + opt_fsErr.message + ')'; - } -}; -goog.inherits(goog.net.FileDownloader.Error, goog.debug.Error); - - -/** - * The status of the XHR. Only set if the error was caused by an XHR failure. - * @type {number|undefined} - */ -goog.net.FileDownloader.Error.prototype.xhrStatus; - - -/** - * The error code of the XHR. Only set if the error was caused by an XHR - * failure. - * @type {goog.net.ErrorCode|undefined} - */ -goog.net.FileDownloader.Error.prototype.xhrErrorCode; - - -/** - * The file API error. Only set if the error was caused by the file API. - * @type {goog.fs.Error|undefined} - */ -goog.net.FileDownloader.Error.prototype.fileError; - - - -/** - * A struct containing the data for a single download. - * - * @param {string} url The URL for the file being downloaded. - * @param {!goog.net.FileDownloader} downloader The parent FileDownloader. - * @extends {goog.Disposable} - * @constructor - * @private - */ -goog.net.FileDownloader.Download_ = function(url, downloader) { - goog.base(this); - - /** - * The URL for the file being downloaded. - * @type {string} - */ - this.url = url; - - /** - * The Deferred that will be fired when the download is complete. - * @type {!goog.async.Deferred} - */ - this.deferred = new goog.async.Deferred( - goog.bind(downloader.cancel_, downloader, this)); - - /** - * Whether this download has been cancelled by the user. - * @type {boolean} - */ - this.cancelled = false; - - /** - * The XhrIo object for downloading the file. Only set once it's been - * retrieved from the pool. - * @type {goog.net.XhrIo} - */ - this.xhr = null; - - /** - * The name of the blob being downloaded. Only sey once the XHR has completed, - * if it completed successfully. - * @type {?string} - */ - this.name = null; - - /** - * The downloaded blob. Only set once the XHR has completed, if it completed - * successfully. - * @type {Blob} - */ - this.blob = null; - - /** - * The file entry where the blob is to be stored. Only set once it's been - * loaded from the filesystem. - * @type {goog.fs.FileEntry} - */ - this.file = null; - - /** - * The file writer for writing the blob to the filesystem. Only set once it's - * been loaded from the filesystem. - * @type {goog.fs.FileWriter} - */ - this.writer = null; -}; -goog.inherits(goog.net.FileDownloader.Download_, goog.Disposable); - - -/** @override */ -goog.net.FileDownloader.Download_.prototype.disposeInternal = function() { - this.cancelled = true; - if (this.xhr) { - this.xhr.abort(); - } else if (this.writer && this.writer.getReadyState() == - goog.fs.FileSaver.ReadyState.WRITING) { - this.writer.abort(); - } - - goog.base(this, 'disposeInternal'); -}; |