From aff6570ad6932440e654dd3b5b53fcc34b6614fa Mon Sep 17 00:00:00 2001 From: gideonthomas Date: Tue, 13 May 2014 18:10:11 -0400 Subject: [PATCH] Added mknod command to create a file or directory node --- README.md | 21 + dist/filer.js | 7546 +++++++++++++----------------- dist/filer.min.js | 12 +- src/filesystem/implementation.js | 83 + src/filesystem/interface.js | 1 + tests/spec/fs.mknod.spec.js | 80 + tests/test-manifest.js | 1 + 7 files changed, 3414 insertions(+), 4330 deletions(-) create mode 100644 tests/spec/fs.mknod.spec.js 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