diff --git a/README.md b/README.md
index c7a3633..51032ce 100644
--- a/README.md
+++ b/README.md
@@ -277,6 +277,7 @@ var fs = new Filer.FileSystem();
* [fs.readlink(path, callback)](#readlink)
* [fs.realpath(path, [cache], callback)](#realpath)
* [fs.unlink(path, callback)](#unlink)
+* [fs.mknod(path, mode, callback)](#mknod)
* [fs.rmdir(path, callback)](#rmdir)
* [fs.mkdir(path, [mode], callback)](#mkdir)
* [fs.readdir(path, callback)](#readdir)
@@ -574,6 +575,26 @@ fs.unlink('/backup.old', function(err) {
});
```
+#### fs.mknod(path, mode, callback)
+
+Creates a node at `path` based on the mode passed which is either `FILE` or `DIRECTORY`. Asynchronous [mknod(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/mknod.html). Callback gets no additional arguments.
+
+Example:
+
+```javascript
+// Create a /dir directory
+fs.mknod('/dir', 'DIRECTORY', function(err) {
+ if(err) throw err;
+ // /dir is now created
+
+ // Create a file inside /dir
+ fs.mknod('/dir/myfile', 'FILE', function(err) {
+ if(err) throw err;
+ // /dir/myfile now exists
+ });
+});
+```
+
#### fs.rmdir(path, callback)
Removes the directory at `path`. Asynchronous [rmdir(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/rmdir.html).
diff --git a/dist/filer.js b/dist/filer.js
index 8da42ce..bf9b7c4 100644
--- a/dist/filer.js
+++ b/dist/filer.js
@@ -25,7 +25,6 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
}
}( this, function() {
-
/**
* almond 0.2.5 Copyright (c) 2011-2012, The Dojo Foundation All Rights Reserved.
* Available via the MIT or new BSD license.
@@ -537,6 +536,1979 @@ define('nodash',['require'],function(require) {
});
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// Based on https://github.com/joyent/node/blob/41e53e557992a7d552a8e23de035f9463da25c99/lib/path.js
+define('src/path',[],function() {
+
+ // resolves . and .. elements in a path array with directory names there
+ // must be no slashes, empty elements, or device names (c:\) in the array
+ // (so also no leading and trailing slashes - it does not distinguish
+ // relative and absolute paths)
+ function normalizeArray(parts, allowAboveRoot) {
+ // if the path tries to go above the root, `up` ends up > 0
+ var up = 0;
+ for (var i = parts.length - 1; i >= 0; i--) {
+ var last = parts[i];
+ if (last === '.') {
+ parts.splice(i, 1);
+ } else if (last === '..') {
+ parts.splice(i, 1);
+ up++;
+ } else if (up) {
+ parts.splice(i, 1);
+ up--;
+ }
+ }
+
+ // if the path is allowed to go above the root, restore leading ..s
+ if (allowAboveRoot) {
+ for (; up--; up) {
+ parts.unshift('..');
+ }
+ }
+
+ return parts;
+ }
+
+ // Split a filename into [root, dir, basename, ext], unix version
+ // 'root' is just a slash, or nothing.
+ var splitPathRe =
+ /^(\/?)([\s\S]+\/(?!$)|\/)?((?:\.{1,2}$|[\s\S]+?)?(\.[^.\/]*)?)$/;
+ var splitPath = function(filename) {
+ var result = splitPathRe.exec(filename);
+ return [result[1] || '', result[2] || '', result[3] || '', result[4] || ''];
+ };
+
+ // path.resolve([from ...], to)
+ function resolve() {
+ var resolvedPath = '',
+ resolvedAbsolute = false;
+
+ for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
+ // XXXidbfs: we don't have process.cwd() so we use '/' as a fallback
+ var path = (i >= 0) ? arguments[i] : '/';
+
+ // Skip empty and invalid entries
+ if (typeof path !== 'string' || !path) {
+ continue;
+ }
+
+ resolvedPath = path + '/' + resolvedPath;
+ resolvedAbsolute = path.charAt(0) === '/';
+ }
+
+ // At this point the path should be resolved to a full absolute path, but
+ // handle relative paths to be safe (might happen when process.cwd() fails)
+
+ // Normalize the path
+ resolvedPath = normalizeArray(resolvedPath.split('/').filter(function(p) {
+ return !!p;
+ }), !resolvedAbsolute).join('/');
+
+ return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
+ }
+
+ // path.normalize(path)
+ function normalize(path) {
+ var isAbsolute = path.charAt(0) === '/',
+ trailingSlash = path.substr(-1) === '/';
+
+ // Normalize the path
+ path = normalizeArray(path.split('/').filter(function(p) {
+ return !!p;
+ }), !isAbsolute).join('/');
+
+ if (!path && !isAbsolute) {
+ path = '.';
+ }
+ /*
+ if (path && trailingSlash) {
+ path += '/';
+ }
+ */
+
+ return (isAbsolute ? '/' : '') + path;
+ }
+
+ function join() {
+ var paths = Array.prototype.slice.call(arguments, 0);
+ return normalize(paths.filter(function(p, index) {
+ return p && typeof p === 'string';
+ }).join('/'));
+ }
+
+ // path.relative(from, to)
+ function relative(from, to) {
+ from = exports.resolve(from).substr(1);
+ to = exports.resolve(to).substr(1);
+
+ function trim(arr) {
+ var start = 0;
+ for (; start < arr.length; start++) {
+ if (arr[start] !== '') break;
+ }
+
+ var end = arr.length - 1;
+ for (; end >= 0; end--) {
+ if (arr[end] !== '') break;
+ }
+
+ if (start > end) return [];
+ return arr.slice(start, end - start + 1);
+ }
+
+ var fromParts = trim(from.split('/'));
+ var toParts = trim(to.split('/'));
+
+ var length = Math.min(fromParts.length, toParts.length);
+ var samePartsLength = length;
+ for (var i = 0; i < length; i++) {
+ if (fromParts[i] !== toParts[i]) {
+ samePartsLength = i;
+ break;
+ }
+ }
+
+ var outputParts = [];
+ for (var i = samePartsLength; i < fromParts.length; i++) {
+ outputParts.push('..');
+ }
+
+ outputParts = outputParts.concat(toParts.slice(samePartsLength));
+
+ return outputParts.join('/');
+ }
+
+ function dirname(path) {
+ var result = splitPath(path),
+ root = result[0],
+ dir = result[1];
+
+ if (!root && !dir) {
+ // No dirname whatsoever
+ return '.';
+ }
+
+ if (dir) {
+ // It has a dirname, strip trailing slash
+ dir = dir.substr(0, dir.length - 1);
+ }
+
+ return root + dir;
+ }
+
+ function basename(path, ext) {
+ var f = splitPath(path)[2];
+ // TODO: make this comparison case-insensitive on windows?
+ if (ext && f.substr(-1 * ext.length) === ext) {
+ f = f.substr(0, f.length - ext.length);
+ }
+ // XXXidbfs: node.js just does `return f`
+ return f === "" ? "/" : f;
+ }
+
+ function extname(path) {
+ return splitPath(path)[3];
+ }
+
+ function isAbsolute(path) {
+ if(path.charAt(0) === '/') {
+ return true;
+ }
+ return false;
+ }
+
+ function isNull(path) {
+ if (('' + path).indexOf('\u0000') !== -1) {
+ return true;
+ }
+ return false;
+ }
+
+ // XXXidbfs: we don't support path.exists() or path.existsSync(), which
+ // are deprecated, and need a FileSystem instance to work. Use fs.stat().
+
+ return {
+ normalize: normalize,
+ resolve: resolve,
+ join: join,
+ relative: relative,
+ sep: '/',
+ delimiter: ':',
+ dirname: dirname,
+ basename: basename,
+ extname: extname,
+ isAbsolute: isAbsolute,
+ isNull: isNull
+ };
+
+});
+
+/*
+CryptoJS v3.0.2
+code.google.com/p/crypto-js
+(c) 2009-2012 by Jeff Mott. All rights reserved.
+code.google.com/p/crypto-js/wiki/License
+*/
+var CryptoJS=CryptoJS||function(i,p){var f={},q=f.lib={},j=q.Base=function(){function a(){}return{extend:function(h){a.prototype=this;var d=new a;h&&d.mixIn(h);d.$super=this;return d},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var d in a)a.hasOwnProperty(d)&&(this[d]=a[d]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.$super.extend(this)}}}(),k=q.WordArray=j.extend({init:function(a,h){a=
+this.words=a||[];this.sigBytes=h!=p?h:4*a.length},toString:function(a){return(a||m).stringify(this)},concat:function(a){var h=this.words,d=a.words,c=this.sigBytes,a=a.sigBytes;this.clamp();if(c%4)for(var b=0;b>>2]|=(d[b>>>2]>>>24-8*(b%4)&255)<<24-8*((c+b)%4);else if(65535>>2]=d[b>>>2];else h.push.apply(h,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,b=this.sigBytes;a[b>>>2]&=4294967295<<32-8*(b%4);a.length=i.ceil(b/4)},clone:function(){var a=
+j.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var b=[],d=0;d>>2]>>>24-8*(c%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var b=a.length,d=[],c=0;c>>3]|=parseInt(a.substr(c,2),16)<<24-4*(c%8);return k.create(d,b/2)}},s=r.Latin1={stringify:function(a){for(var b=
+a.words,a=a.sigBytes,d=[],c=0;c>>2]>>>24-8*(c%4)&255));return d.join("")},parse:function(a){for(var b=a.length,d=[],c=0;c>>2]|=(a.charCodeAt(c)&255)<<24-8*(c%4);return k.create(d,b)}},g=r.Utf8={stringify:function(a){try{return decodeURIComponent(escape(s.stringify(a)))}catch(b){throw Error("Malformed UTF-8 data");}},parse:function(a){return s.parse(unescape(encodeURIComponent(a)))}},b=q.BufferedBlockAlgorithm=j.extend({reset:function(){this._data=k.create();
+this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=g.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var b=this._data,d=b.words,c=b.sigBytes,e=this.blockSize,f=c/(4*e),f=a?i.ceil(f):i.max((f|0)-this._minBufferSize,0),a=f*e,c=i.min(4*a,c);if(a){for(var g=0;ge;)f(b)&&(8>e&&(k[e]=g(i.pow(b,0.5))),r[e]=g(i.pow(b,1/3)),e++),b++})();var m=[],j=j.SHA256=f.extend({_doReset:function(){this._hash=q.create(k.slice(0))},_doProcessBlock:function(f,g){for(var b=this._hash.words,e=b[0],a=b[1],h=b[2],d=b[3],c=b[4],i=b[5],j=b[6],k=b[7],l=0;64>
+l;l++){if(16>l)m[l]=f[g+l]|0;else{var n=m[l-15],o=m[l-2];m[l]=((n<<25|n>>>7)^(n<<14|n>>>18)^n>>>3)+m[l-7]+((o<<15|o>>>17)^(o<<13|o>>>19)^o>>>10)+m[l-16]}n=k+((c<<26|c>>>6)^(c<<21|c>>>11)^(c<<7|c>>>25))+(c&i^~c&j)+r[l]+m[l];o=((e<<30|e>>>2)^(e<<19|e>>>13)^(e<<10|e>>>22))+(e&a^e&h^a&h);k=j;j=i;i=c;c=d+n|0;d=h;h=a;a=e;e=n+o|0}b[0]=b[0]+e|0;b[1]=b[1]+a|0;b[2]=b[2]+h|0;b[3]=b[3]+d|0;b[4]=b[4]+c|0;b[5]=b[5]+i|0;b[6]=b[6]+j|0;b[7]=b[7]+k|0},_doFinalize:function(){var f=this._data,g=f.words,b=8*this._nDataBytes,
+e=8*f.sigBytes;g[e>>>5]|=128<<24-e%32;g[(e+64>>>9<<4)+15]=b;f.sigBytes=4*g.length;this._process()}});p.SHA256=f._createHelper(j);p.HmacSHA256=f._createHmacHelper(j)})(Math);
+
+define("crypto-js/rollups/sha256", function(){});
+
+define('src/shared',['require','crypto-js/rollups/sha256'],function(require) {
+
+ require("crypto-js/rollups/sha256"); var Crypto = CryptoJS;
+
+ function guid() {
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+ return v.toString(16);
+ }).toUpperCase();
+ }
+
+ function hash(string) {
+ return Crypto.SHA256(string).toString(Crypto.enc.hex);
+ }
+
+ function nop() {}
+
+ /**
+ * Convert a Uint8Array to a regular array
+ */
+ function u8toArray(u8) {
+ var array = [];
+ var len = u8.length;
+ for(var i = 0; i < len; i++) {
+ array[i] = u8[i];
+ }
+ return array;
+ }
+
+ return {
+ guid: guid,
+ hash: hash,
+ u8toArray: u8toArray,
+ nop: nop
+ };
+
+});
+
+define('src/constants',['require'],function(require) {
+
+ var O_READ = 'READ';
+ var O_WRITE = 'WRITE';
+ var O_CREATE = 'CREATE';
+ var O_EXCLUSIVE = 'EXCLUSIVE';
+ var O_TRUNCATE = 'TRUNCATE';
+ var O_APPEND = 'APPEND';
+ var XATTR_CREATE = 'CREATE';
+ var XATTR_REPLACE = 'REPLACE';
+
+ return {
+ FILE_SYSTEM_NAME: 'local',
+
+ FILE_STORE_NAME: 'files',
+
+ IDB_RO: 'readonly',
+ IDB_RW: 'readwrite',
+
+ WSQL_VERSION: "1",
+ WSQL_SIZE: 5 * 1024 * 1024,
+ WSQL_DESC: "FileSystem Storage",
+
+ MODE_FILE: 'FILE',
+ MODE_DIRECTORY: 'DIRECTORY',
+ MODE_SYMBOLIC_LINK: 'SYMLINK',
+ MODE_META: 'META',
+
+ SYMLOOP_MAX: 10,
+
+ BINARY_MIME_TYPE: 'application/octet-stream',
+ JSON_MIME_TYPE: 'application/json',
+
+ ROOT_DIRECTORY_NAME: '/', // basename(normalize(path))
+
+ // FS Mount Flags
+ FS_FORMAT: 'FORMAT',
+ FS_NOCTIME: 'NOCTIME',
+ FS_NOMTIME: 'NOMTIME',
+
+ // FS File Open Flags
+ O_READ: O_READ,
+ O_WRITE: O_WRITE,
+ O_CREATE: O_CREATE,
+ O_EXCLUSIVE: O_EXCLUSIVE,
+ O_TRUNCATE: O_TRUNCATE,
+ O_APPEND: O_APPEND,
+
+ O_FLAGS: {
+ 'r': [O_READ],
+ 'r+': [O_READ, O_WRITE],
+ 'w': [O_WRITE, O_CREATE, O_TRUNCATE],
+ 'w+': [O_WRITE, O_READ, O_CREATE, O_TRUNCATE],
+ 'wx': [O_WRITE, O_CREATE, O_EXCLUSIVE, O_TRUNCATE],
+ 'wx+': [O_WRITE, O_READ, O_CREATE, O_EXCLUSIVE, O_TRUNCATE],
+ 'a': [O_WRITE, O_CREATE, O_APPEND],
+ 'a+': [O_WRITE, O_READ, O_CREATE, O_APPEND],
+ 'ax': [O_WRITE, O_CREATE, O_EXCLUSIVE, O_APPEND],
+ 'ax+': [O_WRITE, O_READ, O_CREATE, O_EXCLUSIVE, O_APPEND]
+ },
+
+ XATTR_CREATE: XATTR_CREATE,
+ XATTR_REPLACE: XATTR_REPLACE,
+
+ FS_READY: 'READY',
+ FS_PENDING: 'PENDING',
+ FS_ERROR: 'ERROR',
+
+ SUPER_NODE_ID: '00000000-0000-0000-0000-000000000000',
+
+ //Reserved FileDescriptors for streams
+ STDIN: 0,
+ STDOUT: 1,
+ STDERR: 2,
+ FIRST_DESCRIPTOR: 3,
+
+ ENVIRONMENT: {
+ TMP: '/tmp',
+ PATH: ''
+ }
+ };
+
+});
+define('src/errors',['require'],function(require) {
+ var errors = {};
+ [
+ /**
+ * node.js errors
+ */
+ '-1:UNKNOWN:unknown error',
+ '0:OK:success',
+ '1:EOF:end of file',
+ '2:EADDRINFO:getaddrinfo error',
+ '3:EACCES:permission denied',
+ '4:EAGAIN:resource temporarily unavailable',
+ '5:EADDRINUSE:address already in use',
+ '6:EADDRNOTAVAIL:address not available',
+ '7:EAFNOSUPPORT:address family not supported',
+ '8:EALREADY:connection already in progress',
+ '9:EBADF:bad file descriptor',
+ '10:EBUSY:resource busy or locked',
+ '11:ECONNABORTED:software caused connection abort',
+ '12:ECONNREFUSED:connection refused',
+ '13:ECONNRESET:connection reset by peer',
+ '14:EDESTADDRREQ:destination address required',
+ '15:EFAULT:bad address in system call argument',
+ '16:EHOSTUNREACH:host is unreachable',
+ '17:EINTR:interrupted system call',
+ '18:EINVAL:invalid argument',
+ '19:EISCONN:socket is already connected',
+ '20:EMFILE:too many open files',
+ '21:EMSGSIZE:message too long',
+ '22:ENETDOWN:network is down',
+ '23:ENETUNREACH:network is unreachable',
+ '24:ENFILE:file table overflow',
+ '25:ENOBUFS:no buffer space available',
+ '26:ENOMEM:not enough memory',
+ '27:ENOTDIR:not a directory',
+ '28:EISDIR:illegal operation on a directory',
+ '29:ENONET:machine is not on the network',
+ // errno 30 skipped, as per https://github.com/rvagg/node-errno/blob/master/errno.js
+ '31:ENOTCONN:socket is not connected',
+ '32:ENOTSOCK:socket operation on non-socket',
+ '33:ENOTSUP:operation not supported on socket',
+ '34:ENOENT:no such file or directory',
+ '35:ENOSYS:function not implemented',
+ '36:EPIPE:broken pipe',
+ '37:EPROTO:protocol error',
+ '38:EPROTONOSUPPORT:protocol not supported',
+ '39:EPROTOTYPE:protocol wrong type for socket',
+ '40:ETIMEDOUT:connection timed out',
+ '41:ECHARSET:invalid Unicode character',
+ '42:EAIFAMNOSUPPORT:address family for hostname not supported',
+ // errno 43 skipped, as per https://github.com/rvagg/node-errno/blob/master/errno.js
+ '44:EAISERVICE:servname not supported for ai_socktype',
+ '45:EAISOCKTYPE:ai_socktype not supported',
+ '46:ESHUTDOWN:cannot send after transport endpoint shutdown',
+ '47:EEXIST:file already exists',
+ '48:ESRCH:no such process',
+ '49:ENAMETOOLONG:name too long',
+ '50:EPERM:operation not permitted',
+ '51:ELOOP:too many symbolic links encountered',
+ '52:EXDEV:cross-device link not permitted',
+ '53:ENOTEMPTY:directory not empty',
+ '54:ENOSPC:no space left on device',
+ '55:EIO:i/o error',
+ '56:EROFS:read-only file system',
+ '57:ENODEV:no such device',
+ '58:ESPIPE:invalid seek',
+ '59:ECANCELED:operation canceled',
+
+ /**
+ * Filer specific errors
+ */
+ '1000:ENOTMOUNTED:not mounted',
+ '1001:EFILESYSTEMERROR:missing super node, use \'FORMAT\' flag to format filesystem.',
+ '1002:ENOATTR:attribute does not exist'
+ ].forEach(function(e) {
+ e = e.split(':');
+ var errno = e[0],
+ err = e[1],
+ message = e[2];
+
+ function ctor(m) {
+ this.message = m || message;
+ }
+ var proto = ctor.prototype = new Error();
+ proto.errno = errno;
+ proto.code = err;
+ proto.constructor = ctor;
+
+ // We expose the error as both Errors.EINVAL and Errors[18]
+ errors[err] = errors[errno] = ctor;
+ });
+
+ return errors;
+});
+
+define('src/providers/indexeddb',['require','src/constants','src/constants','src/constants','src/constants','src/errors'],function(require) {
+ var FILE_SYSTEM_NAME = require('src/constants').FILE_SYSTEM_NAME;
+ var FILE_STORE_NAME = require('src/constants').FILE_STORE_NAME;
+
+ var indexedDB = window.indexedDB ||
+ window.mozIndexedDB ||
+ window.webkitIndexedDB ||
+ window.msIndexedDB;
+
+ var IDB_RW = require('src/constants').IDB_RW;
+ var IDB_RO = require('src/constants').IDB_RO;
+ var Errors = require('src/errors');
+
+ function IndexedDBContext(db, mode) {
+ var transaction = db.transaction(FILE_STORE_NAME, mode);
+ this.objectStore = transaction.objectStore(FILE_STORE_NAME);
+ }
+ IndexedDBContext.prototype.clear = function(callback) {
+ try {
+ var request = this.objectStore.clear();
+ request.onsuccess = function(event) {
+ callback();
+ };
+ request.onerror = function(error) {
+ callback(error);
+ };
+ } catch(e) {
+ callback(e);
+ }
+ };
+ IndexedDBContext.prototype.get = function(key, callback) {
+ try {
+ var request = this.objectStore.get(key);
+ request.onsuccess = function onsuccess(event) {
+ var result = event.target.result;
+ callback(null, result);
+ };
+ request.onerror = function onerror(error) {
+ callback(error);
+ };
+ } catch(e) {
+ callback(e);
+ }
+ };
+ IndexedDBContext.prototype.put = function(key, value, callback) {
+ try {
+ var request = this.objectStore.put(value, key);
+ request.onsuccess = function onsuccess(event) {
+ var result = event.target.result;
+ callback(null, result);
+ };
+ request.onerror = function onerror(error) {
+ callback(error);
+ };
+ } catch(e) {
+ callback(e);
+ }
+ };
+ IndexedDBContext.prototype.delete = function(key, callback) {
+ try {
+ var request = this.objectStore.delete(key);
+ request.onsuccess = function onsuccess(event) {
+ var result = event.target.result;
+ callback(null, result);
+ };
+ request.onerror = function(error) {
+ callback(error);
+ };
+ } catch(e) {
+ callback(e);
+ }
+ };
+
+
+ function IndexedDB(name) {
+ this.name = name || FILE_SYSTEM_NAME;
+ this.db = null;
+ }
+ IndexedDB.isSupported = function() {
+ return !!indexedDB;
+ };
+
+ IndexedDB.prototype.open = function(callback) {
+ var that = this;
+
+ // Bail if we already have a db open
+ if( that.db ) {
+ callback(null, false);
+ return;
+ }
+
+ // Keep track of whether we're accessing this db for the first time
+ // and therefore needs to get formatted.
+ var firstAccess = false;
+
+ // NOTE: we're not using versioned databases.
+ var openRequest = indexedDB.open(that.name);
+
+ // If the db doesn't exist, we'll create it
+ openRequest.onupgradeneeded = function onupgradeneeded(event) {
+ var db = event.target.result;
+
+ if(db.objectStoreNames.contains(FILE_STORE_NAME)) {
+ db.deleteObjectStore(FILE_STORE_NAME);
+ }
+ db.createObjectStore(FILE_STORE_NAME);
+
+ firstAccess = true;
+ };
+
+ openRequest.onsuccess = function onsuccess(event) {
+ that.db = event.target.result;
+ callback(null, firstAccess);
+ };
+ openRequest.onerror = function onerror(error) {
+ callback(new Errors.EINVAL('IndexedDB cannot be accessed. If private browsing is enabled, disable it.'));
+ };
+ };
+ IndexedDB.prototype.getReadOnlyContext = function() {
+ // Due to timing issues in Chrome with readwrite vs. readonly indexeddb transactions
+ // always use readwrite so we can make sure pending commits finish before callbacks.
+ // See https://github.com/js-platform/filer/issues/128
+ return new IndexedDBContext(this.db, IDB_RW);
+ };
+ IndexedDB.prototype.getReadWriteContext = function() {
+ return new IndexedDBContext(this.db, IDB_RW);
+ };
+
+ return IndexedDB;
+});
+
+define('src/providers/websql',['require','src/constants','src/constants','src/constants','src/constants','src/constants','src/shared','src/errors'],function(require) {
+ var FILE_SYSTEM_NAME = require('src/constants').FILE_SYSTEM_NAME;
+ var FILE_STORE_NAME = require('src/constants').FILE_STORE_NAME;
+ var WSQL_VERSION = require('src/constants').WSQL_VERSION;
+ var WSQL_SIZE = require('src/constants').WSQL_SIZE;
+ var WSQL_DESC = require('src/constants').WSQL_DESC;
+ var u8toArray = require('src/shared').u8toArray;
+ var Errors = require('src/errors');
+
+ function WebSQLContext(db, isReadOnly) {
+ var that = this;
+ this.getTransaction = function(callback) {
+ if(that.transaction) {
+ callback(that.transaction);
+ return;
+ }
+ // Either do readTransaction() (read-only) or transaction() (read/write)
+ db[isReadOnly ? 'readTransaction' : 'transaction'](function(transaction) {
+ that.transaction = transaction;
+ callback(transaction);
+ });
+ };
+ }
+ WebSQLContext.prototype.clear = function(callback) {
+ function onError(transaction, error) {
+ callback(error);
+ }
+ function onSuccess(transaction, result) {
+ callback(null);
+ }
+ this.getTransaction(function(transaction) {
+ transaction.executeSql("DELETE FROM " + FILE_STORE_NAME + ";",
+ [], onSuccess, onError);
+ });
+ };
+ WebSQLContext.prototype.get = function(key, callback) {
+ function onSuccess(transaction, result) {
+ // If the key isn't found, return null
+ var value = result.rows.length === 0 ? null : result.rows.item(0).data;
+ try {
+ if(value) {
+ value = JSON.parse(value);
+ // Deal with special-cased flattened typed arrays in WebSQL (see put() below)
+ if(value.__isUint8Array) {
+ value = new Uint8Array(value.__array);
+ }
+ }
+ callback(null, value);
+ } catch(e) {
+ callback(e);
+ }
+ }
+ function onError(transaction, error) {
+ callback(error);
+ }
+ this.getTransaction(function(transaction) {
+ transaction.executeSql("SELECT data FROM " + FILE_STORE_NAME + " WHERE id = ?;",
+ [key], onSuccess, onError);
+ });
+ };
+ WebSQLContext.prototype.put = function(key, value, callback) {
+ // We do extra work to make sure typed arrays survive
+ // being stored in the db and still get the right prototype later.
+ if(Object.prototype.toString.call(value) === "[object Uint8Array]") {
+ value = {
+ __isUint8Array: true,
+ __array: u8toArray(value)
+ };
+ }
+ value = JSON.stringify(value);
+ function onSuccess(transaction, result) {
+ callback(null);
+ }
+ function onError(transaction, error) {
+ callback(error);
+ }
+ this.getTransaction(function(transaction) {
+ transaction.executeSql("INSERT OR REPLACE INTO " + FILE_STORE_NAME + " (id, data) VALUES (?, ?);",
+ [key, value], onSuccess, onError);
+ });
+ };
+ WebSQLContext.prototype.delete = function(key, callback) {
+ function onSuccess(transaction, result) {
+ callback(null);
+ }
+ function onError(transaction, error) {
+ callback(error);
+ }
+ this.getTransaction(function(transaction) {
+ transaction.executeSql("DELETE FROM " + FILE_STORE_NAME + " WHERE id = ?;",
+ [key], onSuccess, onError);
+ });
+ };
+
+
+ function WebSQL(name) {
+ this.name = name || FILE_SYSTEM_NAME;
+ this.db = null;
+ }
+ WebSQL.isSupported = function() {
+ return !!window.openDatabase;
+ };
+
+ WebSQL.prototype.open = function(callback) {
+ var that = this;
+
+ // Bail if we already have a db open
+ if(that.db) {
+ callback(null, false);
+ return;
+ }
+
+ var db = window.openDatabase(that.name, WSQL_VERSION, WSQL_DESC, WSQL_SIZE);
+ if(!db) {
+ callback("[WebSQL] Unable to open database.");
+ return;
+ }
+
+ function onError(transaction, error) {
+ if (error.code === 5) {
+ callback(new Errors.EINVAL('WebSQL cannot be accessed. If private browsing is enabled, disable it.'));
+ }
+ callback(error);
+ }
+ function onSuccess(transaction, result) {
+ that.db = db;
+
+ function gotCount(transaction, result) {
+ var firstAccess = result.rows.item(0).count === 0;
+ callback(null, firstAccess);
+ }
+ function onError(transaction, error) {
+ callback(error);
+ }
+ // Keep track of whether we're accessing this db for the first time
+ // and therefore needs to get formatted.
+ transaction.executeSql("SELECT COUNT(id) AS count FROM " + FILE_STORE_NAME + ";",
+ [], gotCount, onError);
+ }
+
+ // Create the table and index we'll need to store the fs data.
+ db.transaction(function(transaction) {
+ function createIndex(transaction) {
+ transaction.executeSql("CREATE INDEX IF NOT EXISTS idx_" + FILE_STORE_NAME + "_id" +
+ " on " + FILE_STORE_NAME + " (id);",
+ [], onSuccess, onError);
+ }
+ transaction.executeSql("CREATE TABLE IF NOT EXISTS " + FILE_STORE_NAME + " (id unique, data TEXT);",
+ [], createIndex, onError);
+ });
+ };
+ WebSQL.prototype.getReadOnlyContext = function() {
+ return new WebSQLContext(this.db, true);
+ };
+ WebSQL.prototype.getReadWriteContext = function() {
+ return new WebSQLContext(this.db, false);
+ };
+
+ return WebSQL;
+});
+
+/*global setImmediate: false, setTimeout: false, console: false */
+
+/**
+ * https://raw.github.com/caolan/async/master/lib/async.js Feb 18, 2014
+ * Used under MIT - https://github.com/caolan/async/blob/master/LICENSE
+ */
+
+(function () {
+
+ var async = {};
+
+ // global on the server, window in the browser
+ var root, previous_async;
+
+ root = this;
+ if (root != null) {
+ previous_async = root.async;
+ }
+
+ async.noConflict = function () {
+ root.async = previous_async;
+ return async;
+ };
+
+ function only_once(fn) {
+ var called = false;
+ return function() {
+ if (called) throw new Error("Callback was already called.");
+ called = true;
+ fn.apply(root, arguments);
+ }
+ }
+
+ //// cross-browser compatiblity functions ////
+
+ var _each = function (arr, iterator) {
+ if (arr.forEach) {
+ return arr.forEach(iterator);
+ }
+ for (var i = 0; i < arr.length; i += 1) {
+ iterator(arr[i], i, arr);
+ }
+ };
+
+ var _map = function (arr, iterator) {
+ if (arr.map) {
+ return arr.map(iterator);
+ }
+ var results = [];
+ _each(arr, function (x, i, a) {
+ results.push(iterator(x, i, a));
+ });
+ return results;
+ };
+
+ var _reduce = function (arr, iterator, memo) {
+ if (arr.reduce) {
+ return arr.reduce(iterator, memo);
+ }
+ _each(arr, function (x, i, a) {
+ memo = iterator(memo, x, i, a);
+ });
+ return memo;
+ };
+
+ var _keys = function (obj) {
+ if (Object.keys) {
+ return Object.keys(obj);
+ }
+ var keys = [];
+ for (var k in obj) {
+ if (obj.hasOwnProperty(k)) {
+ keys.push(k);
+ }
+ }
+ return keys;
+ };
+
+ //// exported async module functions ////
+
+ //// nextTick implementation with browser-compatible fallback ////
+ if (typeof process === 'undefined' || !(process.nextTick)) {
+ if (typeof setImmediate === 'function') {
+ async.nextTick = function (fn) {
+ // not a direct alias for IE10 compatibility
+ setImmediate(fn);
+ };
+ async.setImmediate = async.nextTick;
+ }
+ else {
+ async.nextTick = function (fn) {
+ setTimeout(fn, 0);
+ };
+ async.setImmediate = async.nextTick;
+ }
+ }
+ else {
+ async.nextTick = process.nextTick;
+ if (typeof setImmediate !== 'undefined') {
+ async.setImmediate = function (fn) {
+ // not a direct alias for IE10 compatibility
+ setImmediate(fn);
+ };
+ }
+ else {
+ async.setImmediate = async.nextTick;
+ }
+ }
+
+ async.each = function (arr, iterator, callback) {
+ callback = callback || function () {};
+ if (!arr.length) {
+ return callback();
+ }
+ var completed = 0;
+ _each(arr, function (x) {
+ iterator(x, only_once(function (err) {
+ if (err) {
+ callback(err);
+ callback = function () {};
+ }
+ else {
+ completed += 1;
+ if (completed >= arr.length) {
+ callback(null);
+ }
+ }
+ }));
+ });
+ };
+ async.forEach = async.each;
+
+ async.eachSeries = function (arr, iterator, callback) {
+ callback = callback || function () {};
+ if (!arr.length) {
+ return callback();
+ }
+ var completed = 0;
+ var iterate = function () {
+ iterator(arr[completed], function (err) {
+ if (err) {
+ callback(err);
+ callback = function () {};
+ }
+ else {
+ completed += 1;
+ if (completed >= arr.length) {
+ callback(null);
+ }
+ else {
+ iterate();
+ }
+ }
+ });
+ };
+ iterate();
+ };
+ async.forEachSeries = async.eachSeries;
+
+ async.eachLimit = function (arr, limit, iterator, callback) {
+ var fn = _eachLimit(limit);
+ fn.apply(null, [arr, iterator, callback]);
+ };
+ async.forEachLimit = async.eachLimit;
+
+ var _eachLimit = function (limit) {
+
+ return function (arr, iterator, callback) {
+ callback = callback || function () {};
+ if (!arr.length || limit <= 0) {
+ return callback();
+ }
+ var completed = 0;
+ var started = 0;
+ var running = 0;
+
+ (function replenish () {
+ if (completed >= arr.length) {
+ return callback();
+ }
+
+ while (running < limit && started < arr.length) {
+ started += 1;
+ running += 1;
+ iterator(arr[started - 1], function (err) {
+ if (err) {
+ callback(err);
+ callback = function () {};
+ }
+ else {
+ completed += 1;
+ running -= 1;
+ if (completed >= arr.length) {
+ callback();
+ }
+ else {
+ replenish();
+ }
+ }
+ });
+ }
+ })();
+ };
+ };
+
+
+ var doParallel = function (fn) {
+ return function () {
+ var args = Array.prototype.slice.call(arguments);
+ return fn.apply(null, [async.each].concat(args));
+ };
+ };
+ var doParallelLimit = function(limit, fn) {
+ return function () {
+ var args = Array.prototype.slice.call(arguments);
+ return fn.apply(null, [_eachLimit(limit)].concat(args));
+ };
+ };
+ var doSeries = function (fn) {
+ return function () {
+ var args = Array.prototype.slice.call(arguments);
+ return fn.apply(null, [async.eachSeries].concat(args));
+ };
+ };
+
+
+ var _asyncMap = function (eachfn, arr, iterator, callback) {
+ var results = [];
+ arr = _map(arr, function (x, i) {
+ return {index: i, value: x};
+ });
+ eachfn(arr, function (x, callback) {
+ iterator(x.value, function (err, v) {
+ results[x.index] = v;
+ callback(err);
+ });
+ }, function (err) {
+ callback(err, results);
+ });
+ };
+ async.map = doParallel(_asyncMap);
+ async.mapSeries = doSeries(_asyncMap);
+ async.mapLimit = function (arr, limit, iterator, callback) {
+ return _mapLimit(limit)(arr, iterator, callback);
+ };
+
+ var _mapLimit = function(limit) {
+ return doParallelLimit(limit, _asyncMap);
+ };
+
+ // reduce only has a series version, as doing reduce in parallel won't
+ // work in many situations.
+ async.reduce = function (arr, memo, iterator, callback) {
+ async.eachSeries(arr, function (x, callback) {
+ iterator(memo, x, function (err, v) {
+ memo = v;
+ callback(err);
+ });
+ }, function (err) {
+ callback(err, memo);
+ });
+ };
+ // inject alias
+ async.inject = async.reduce;
+ // foldl alias
+ async.foldl = async.reduce;
+
+ async.reduceRight = function (arr, memo, iterator, callback) {
+ var reversed = _map(arr, function (x) {
+ return x;
+ }).reverse();
+ async.reduce(reversed, memo, iterator, callback);
+ };
+ // foldr alias
+ async.foldr = async.reduceRight;
+
+ var _filter = function (eachfn, arr, iterator, callback) {
+ var results = [];
+ arr = _map(arr, function (x, i) {
+ return {index: i, value: x};
+ });
+ eachfn(arr, function (x, callback) {
+ iterator(x.value, function (v) {
+ if (v) {
+ results.push(x);
+ }
+ callback();
+ });
+ }, function (err) {
+ callback(_map(results.sort(function (a, b) {
+ return a.index - b.index;
+ }), function (x) {
+ return x.value;
+ }));
+ });
+ };
+ async.filter = doParallel(_filter);
+ async.filterSeries = doSeries(_filter);
+ // select alias
+ async.select = async.filter;
+ async.selectSeries = async.filterSeries;
+
+ var _reject = function (eachfn, arr, iterator, callback) {
+ var results = [];
+ arr = _map(arr, function (x, i) {
+ return {index: i, value: x};
+ });
+ eachfn(arr, function (x, callback) {
+ iterator(x.value, function (v) {
+ if (!v) {
+ results.push(x);
+ }
+ callback();
+ });
+ }, function (err) {
+ callback(_map(results.sort(function (a, b) {
+ return a.index - b.index;
+ }), function (x) {
+ return x.value;
+ }));
+ });
+ };
+ async.reject = doParallel(_reject);
+ async.rejectSeries = doSeries(_reject);
+
+ var _detect = function (eachfn, arr, iterator, main_callback) {
+ eachfn(arr, function (x, callback) {
+ iterator(x, function (result) {
+ if (result) {
+ main_callback(x);
+ main_callback = function () {};
+ }
+ else {
+ callback();
+ }
+ });
+ }, function (err) {
+ main_callback();
+ });
+ };
+ async.detect = doParallel(_detect);
+ async.detectSeries = doSeries(_detect);
+
+ async.some = function (arr, iterator, main_callback) {
+ async.each(arr, function (x, callback) {
+ iterator(x, function (v) {
+ if (v) {
+ main_callback(true);
+ main_callback = function () {};
+ }
+ callback();
+ });
+ }, function (err) {
+ main_callback(false);
+ });
+ };
+ // any alias
+ async.any = async.some;
+
+ async.every = function (arr, iterator, main_callback) {
+ async.each(arr, function (x, callback) {
+ iterator(x, function (v) {
+ if (!v) {
+ main_callback(false);
+ main_callback = function () {};
+ }
+ callback();
+ });
+ }, function (err) {
+ main_callback(true);
+ });
+ };
+ // all alias
+ async.all = async.every;
+
+ async.sortBy = function (arr, iterator, callback) {
+ async.map(arr, function (x, callback) {
+ iterator(x, function (err, criteria) {
+ if (err) {
+ callback(err);
+ }
+ else {
+ callback(null, {value: x, criteria: criteria});
+ }
+ });
+ }, function (err, results) {
+ if (err) {
+ return callback(err);
+ }
+ else {
+ var fn = function (left, right) {
+ var a = left.criteria, b = right.criteria;
+ return a < b ? -1 : a > b ? 1 : 0;
+ };
+ callback(null, _map(results.sort(fn), function (x) {
+ return x.value;
+ }));
+ }
+ });
+ };
+
+ async.auto = function (tasks, callback) {
+ callback = callback || function () {};
+ var keys = _keys(tasks);
+ if (!keys.length) {
+ return callback(null);
+ }
+
+ var results = {};
+
+ var listeners = [];
+ var addListener = function (fn) {
+ listeners.unshift(fn);
+ };
+ var removeListener = function (fn) {
+ for (var i = 0; i < listeners.length; i += 1) {
+ if (listeners[i] === fn) {
+ listeners.splice(i, 1);
+ return;
+ }
+ }
+ };
+ var taskComplete = function () {
+ _each(listeners.slice(0), function (fn) {
+ fn();
+ });
+ };
+
+ addListener(function () {
+ if (_keys(results).length === keys.length) {
+ callback(null, results);
+ callback = function () {};
+ }
+ });
+
+ _each(keys, function (k) {
+ var task = (tasks[k] instanceof Function) ? [tasks[k]]: tasks[k];
+ var taskCallback = function (err) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ if (args.length <= 1) {
+ args = args[0];
+ }
+ if (err) {
+ var safeResults = {};
+ _each(_keys(results), function(rkey) {
+ safeResults[rkey] = results[rkey];
+ });
+ safeResults[k] = args;
+ callback(err, safeResults);
+ // stop subsequent errors hitting callback multiple times
+ callback = function () {};
+ }
+ else {
+ results[k] = args;
+ async.setImmediate(taskComplete);
+ }
+ };
+ var requires = task.slice(0, Math.abs(task.length - 1)) || [];
+ var ready = function () {
+ return _reduce(requires, function (a, x) {
+ return (a && results.hasOwnProperty(x));
+ }, true) && !results.hasOwnProperty(k);
+ };
+ if (ready()) {
+ task[task.length - 1](taskCallback, results);
+ }
+ else {
+ var listener = function () {
+ if (ready()) {
+ removeListener(listener);
+ task[task.length - 1](taskCallback, results);
+ }
+ };
+ addListener(listener);
+ }
+ });
+ };
+
+ async.waterfall = function (tasks, callback) {
+ callback = callback || function () {};
+ if (tasks.constructor !== Array) {
+ var err = new Error('First argument to waterfall must be an array of functions');
+ return callback(err);
+ }
+ if (!tasks.length) {
+ return callback();
+ }
+ var wrapIterator = function (iterator) {
+ return function (err) {
+ if (err) {
+ callback.apply(null, arguments);
+ callback = function () {};
+ }
+ else {
+ var args = Array.prototype.slice.call(arguments, 1);
+ var next = iterator.next();
+ if (next) {
+ args.push(wrapIterator(next));
+ }
+ else {
+ args.push(callback);
+ }
+ async.setImmediate(function () {
+ iterator.apply(null, args);
+ });
+ }
+ };
+ };
+ wrapIterator(async.iterator(tasks))();
+ };
+
+ var _parallel = function(eachfn, tasks, callback) {
+ callback = callback || function () {};
+ if (tasks.constructor === Array) {
+ eachfn.map(tasks, function (fn, callback) {
+ if (fn) {
+ fn(function (err) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ if (args.length <= 1) {
+ args = args[0];
+ }
+ callback.call(null, err, args);
+ });
+ }
+ }, callback);
+ }
+ else {
+ var results = {};
+ eachfn.each(_keys(tasks), function (k, callback) {
+ tasks[k](function (err) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ if (args.length <= 1) {
+ args = args[0];
+ }
+ results[k] = args;
+ callback(err);
+ });
+ }, function (err) {
+ callback(err, results);
+ });
+ }
+ };
+
+ async.parallel = function (tasks, callback) {
+ _parallel({ map: async.map, each: async.each }, tasks, callback);
+ };
+
+ async.parallelLimit = function(tasks, limit, callback) {
+ _parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback);
+ };
+
+ async.series = function (tasks, callback) {
+ callback = callback || function () {};
+ if (tasks.constructor === Array) {
+ async.mapSeries(tasks, function (fn, callback) {
+ if (fn) {
+ fn(function (err) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ if (args.length <= 1) {
+ args = args[0];
+ }
+ callback.call(null, err, args);
+ });
+ }
+ }, callback);
+ }
+ else {
+ var results = {};
+ async.eachSeries(_keys(tasks), function (k, callback) {
+ tasks[k](function (err) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ if (args.length <= 1) {
+ args = args[0];
+ }
+ results[k] = args;
+ callback(err);
+ });
+ }, function (err) {
+ callback(err, results);
+ });
+ }
+ };
+
+ async.iterator = function (tasks) {
+ var makeCallback = function (index) {
+ var fn = function () {
+ if (tasks.length) {
+ tasks[index].apply(null, arguments);
+ }
+ return fn.next();
+ };
+ fn.next = function () {
+ return (index < tasks.length - 1) ? makeCallback(index + 1): null;
+ };
+ return fn;
+ };
+ return makeCallback(0);
+ };
+
+ async.apply = function (fn) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ return function () {
+ return fn.apply(
+ null, args.concat(Array.prototype.slice.call(arguments))
+ );
+ };
+ };
+
+ var _concat = function (eachfn, arr, fn, callback) {
+ var r = [];
+ eachfn(arr, function (x, cb) {
+ fn(x, function (err, y) {
+ r = r.concat(y || []);
+ cb(err);
+ });
+ }, function (err) {
+ callback(err, r);
+ });
+ };
+ async.concat = doParallel(_concat);
+ async.concatSeries = doSeries(_concat);
+
+ async.whilst = function (test, iterator, callback) {
+ if (test()) {
+ iterator(function (err) {
+ if (err) {
+ return callback(err);
+ }
+ async.whilst(test, iterator, callback);
+ });
+ }
+ else {
+ callback();
+ }
+ };
+
+ async.doWhilst = function (iterator, test, callback) {
+ iterator(function (err) {
+ if (err) {
+ return callback(err);
+ }
+ if (test()) {
+ async.doWhilst(iterator, test, callback);
+ }
+ else {
+ callback();
+ }
+ });
+ };
+
+ async.until = function (test, iterator, callback) {
+ if (!test()) {
+ iterator(function (err) {
+ if (err) {
+ return callback(err);
+ }
+ async.until(test, iterator, callback);
+ });
+ }
+ else {
+ callback();
+ }
+ };
+
+ async.doUntil = function (iterator, test, callback) {
+ iterator(function (err) {
+ if (err) {
+ return callback(err);
+ }
+ if (!test()) {
+ async.doUntil(iterator, test, callback);
+ }
+ else {
+ callback();
+ }
+ });
+ };
+
+ async.queue = function (worker, concurrency) {
+ if (concurrency === undefined) {
+ concurrency = 1;
+ }
+ function _insert(q, data, pos, callback) {
+ if(data.constructor !== Array) {
+ data = [data];
+ }
+ _each(data, function(task) {
+ var item = {
+ data: task,
+ callback: typeof callback === 'function' ? callback : null
+ };
+
+ if (pos) {
+ q.tasks.unshift(item);
+ } else {
+ q.tasks.push(item);
+ }
+
+ if (q.saturated && q.tasks.length === concurrency) {
+ q.saturated();
+ }
+ async.setImmediate(q.process);
+ });
+ }
+
+ var workers = 0;
+ var q = {
+ tasks: [],
+ concurrency: concurrency,
+ saturated: null,
+ empty: null,
+ drain: null,
+ push: function (data, callback) {
+ _insert(q, data, false, callback);
+ },
+ unshift: function (data, callback) {
+ _insert(q, data, true, callback);
+ },
+ process: function () {
+ if (workers < q.concurrency && q.tasks.length) {
+ var task = q.tasks.shift();
+ if (q.empty && q.tasks.length === 0) {
+ q.empty();
+ }
+ workers += 1;
+ var next = function () {
+ workers -= 1;
+ if (task.callback) {
+ task.callback.apply(task, arguments);
+ }
+ if (q.drain && q.tasks.length + workers === 0) {
+ q.drain();
+ }
+ q.process();
+ };
+ var cb = only_once(next);
+ worker(task.data, cb);
+ }
+ },
+ length: function () {
+ return q.tasks.length;
+ },
+ running: function () {
+ return workers;
+ }
+ };
+ return q;
+ };
+
+ async.cargo = function (worker, payload) {
+ var working = false,
+ tasks = [];
+
+ var cargo = {
+ tasks: tasks,
+ payload: payload,
+ saturated: null,
+ empty: null,
+ drain: null,
+ push: function (data, callback) {
+ if(data.constructor !== Array) {
+ data = [data];
+ }
+ _each(data, function(task) {
+ tasks.push({
+ data: task,
+ callback: typeof callback === 'function' ? callback : null
+ });
+ if (cargo.saturated && tasks.length === payload) {
+ cargo.saturated();
+ }
+ });
+ async.setImmediate(cargo.process);
+ },
+ process: function process() {
+ if (working) return;
+ if (tasks.length === 0) {
+ if(cargo.drain) cargo.drain();
+ return;
+ }
+
+ var ts = typeof payload === 'number'
+ ? tasks.splice(0, payload)
+ : tasks.splice(0);
+
+ var ds = _map(ts, function (task) {
+ return task.data;
+ });
+
+ if(cargo.empty) cargo.empty();
+ working = true;
+ worker(ds, function () {
+ working = false;
+
+ var args = arguments;
+ _each(ts, function (data) {
+ if (data.callback) {
+ data.callback.apply(null, args);
+ }
+ });
+
+ process();
+ });
+ },
+ length: function () {
+ return tasks.length;
+ },
+ running: function () {
+ return working;
+ }
+ };
+ return cargo;
+ };
+
+ var _console_fn = function (name) {
+ return function (fn) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ fn.apply(null, args.concat([function (err) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ if (typeof console !== 'undefined') {
+ if (err) {
+ if (console.error) {
+ console.error(err);
+ }
+ }
+ else if (console[name]) {
+ _each(args, function (x) {
+ console[name](x);
+ });
+ }
+ }
+ }]));
+ };
+ };
+ async.log = _console_fn('log');
+ async.dir = _console_fn('dir');
+ /*async.info = _console_fn('info');
+ async.warn = _console_fn('warn');
+ async.error = _console_fn('error');*/
+
+ async.memoize = function (fn, hasher) {
+ var memo = {};
+ var queues = {};
+ hasher = hasher || function (x) {
+ return x;
+ };
+ var memoized = function () {
+ var args = Array.prototype.slice.call(arguments);
+ var callback = args.pop();
+ var key = hasher.apply(null, args);
+ if (key in memo) {
+ callback.apply(null, memo[key]);
+ }
+ else if (key in queues) {
+ queues[key].push(callback);
+ }
+ else {
+ queues[key] = [callback];
+ fn.apply(null, args.concat([function () {
+ memo[key] = arguments;
+ var q = queues[key];
+ delete queues[key];
+ for (var i = 0, l = q.length; i < l; i++) {
+ q[i].apply(null, arguments);
+ }
+ }]));
+ }
+ };
+ memoized.memo = memo;
+ memoized.unmemoized = fn;
+ return memoized;
+ };
+
+ async.unmemoize = function (fn) {
+ return function () {
+ return (fn.unmemoized || fn).apply(null, arguments);
+ };
+ };
+
+ async.times = function (count, iterator, callback) {
+ var counter = [];
+ for (var i = 0; i < count; i++) {
+ counter.push(i);
+ }
+ return async.map(counter, iterator, callback);
+ };
+
+ async.timesSeries = function (count, iterator, callback) {
+ var counter = [];
+ for (var i = 0; i < count; i++) {
+ counter.push(i);
+ }
+ return async.mapSeries(counter, iterator, callback);
+ };
+
+ async.compose = function (/* functions... */) {
+ var fns = Array.prototype.reverse.call(arguments);
+ return function () {
+ var that = this;
+ var args = Array.prototype.slice.call(arguments);
+ var callback = args.pop();
+ async.reduce(fns, args, function (newargs, fn, cb) {
+ fn.apply(that, newargs.concat([function () {
+ var err = arguments[0];
+ var nextargs = Array.prototype.slice.call(arguments, 1);
+ cb(err, nextargs);
+ }]))
+ },
+ function (err, results) {
+ callback.apply(that, [err].concat(results));
+ });
+ };
+ };
+
+ var _applyEach = function (eachfn, fns /*args...*/) {
+ var go = function () {
+ var that = this;
+ var args = Array.prototype.slice.call(arguments);
+ var callback = args.pop();
+ return eachfn(fns, function (fn, cb) {
+ fn.apply(that, args.concat([cb]));
+ },
+ callback);
+ };
+ if (arguments.length > 2) {
+ var args = Array.prototype.slice.call(arguments, 2);
+ return go.apply(this, args);
+ }
+ else {
+ return go;
+ }
+ };
+ async.applyEach = doParallel(_applyEach);
+ async.applyEachSeries = doSeries(_applyEach);
+
+ async.forever = function (fn, callback) {
+ function next(err) {
+ if (err) {
+ if (callback) {
+ return callback(err);
+ }
+ throw err;
+ }
+ fn(next);
+ }
+ next();
+ };
+
+ // AMD / RequireJS
+ if (typeof define !== 'undefined' && define.amd) {
+ define('async',[], function () {
+ return async;
+ });
+ }
+ // Node.js
+ else if (typeof module !== 'undefined' && module.exports) {
+ module.exports = async;
+ }
+ // included directly via