Remove adapters (src, tests, docs), fix providers for node

This commit is contained in:
David Humphrey (:humph) david.humphrey@senecacollege.ca 2014-05-23 14:36:23 -04:00
parent 375719b49b
commit d9ed65602a
13 changed files with 140 additions and 569 deletions

View File

@ -161,33 +161,6 @@ if( Filer.FileSystem.providers.WebSQL.isSupported() ) {
You can also write your own provider if you need a different backend. See the code in `src/providers` for details. You can also write your own provider if you need a different backend. See the code in `src/providers` for details.
####Filer.FileSystem.adapters - Adapters for Storage Providers
Filer based file systems can acquire new functionality by using adapters. These wrapper objects extend the abilities
of storage providers without altering them in anway. An adapter can be used with any provider, and multiple
adapters can be used together in order to compose complex functionality on top of a provider.
There are currently 2 adapters available:
* `FileSystem.adapters.Compression(provider)` - a compression adapter that uses [Zlib](https://github.com/imaya/zlib.js)
* `FileSystem.adapters.Encryption(passphrase, provider)` - an encryption adapter that uses [AES encryption](http://code.google.com/p/crypto-js/#AES)
```javascript
var FileSystem = Filer.FileSystem;
var providers = FileSystem.providers;
var adapters = FileSystem.adapters;
// Create a WebSQL-based, Encrypted, Compressed File System by
// composing a provider and adatpers.
var webSQLProvider = new providers.WebSQL();
var encryptionAdatper = new adapters.Encryption('super-secret-passphrase', webSQLProvider);
var compressionAdatper = new adatpers.Compression(encryptionAdapter);
var fs = new FileSystem({ provider: compressionAdapter });
```
You can also write your own adapter if you need to add new capabilities to the providers. Adapters share the same
interface as providers. See the code in `src/providers` and `src/adapters` for many examples.
####Filer.Path<a name="FilerPath"></a> ####Filer.Path<a name="FilerPath"></a>
The node.js [path module](http://nodejs.org/api/path.html) is available via the `Filer.Path` object. It is The node.js [path module](http://nodejs.org/api/path.html) is available via the `Filer.Path` object. It is

View File

@ -1,8 +0,0 @@
define(function(require) {
return {
Compression: require('src/adapters/zlib'),
Encryption: require('src/adapters/crypto')
};
});

View File

@ -1,124 +0,0 @@
define(function(require) {
// AES encryption, see http://code.google.com/p/crypto-js/#AES
require("crypto-js/rollups/aes");
// Move back and forth from Uint8Arrays and CryptoJS WordArray
// See http://code.google.com/p/crypto-js/#The_Cipher_Input and
// https://groups.google.com/forum/#!topic/crypto-js/TOb92tcJlU0
var WordArray = CryptoJS.lib.WordArray;
function fromWordArray(wordArray) {
var words = wordArray.words;
var sigBytes = wordArray.sigBytes;
var u8 = new Uint8Array(sigBytes);
var b;
for (var i = 0; i < sigBytes; i++) {
b = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
u8[i] = b;
}
return u8;
}
function toWordArray(u8arr) {
var len = u8arr.length;
var words = [];
for (var i = 0; i < len; i++) {
words[i >>> 2] |= (u8arr[i] & 0xff) << (24 - (i % 4) * 8);
}
return WordArray.create(words, len);
}
// UTF8 Text De/Encoders
require('encoding');
function encode(str) {
return (new TextEncoder('utf-8')).encode(str);
}
function decode(u8arr) {
return (new TextDecoder('utf-8')).decode(u8arr);
}
function CryptoContext(context, encrypt, decrypt) {
this.context = context;
this.encrypt = encrypt;
this.decrypt = decrypt;
}
CryptoContext.prototype.clear = function(callback) {
this.context.clear(callback);
};
CryptoContext.prototype.get = function(key, callback) {
var decrypt = this.decrypt;
this.context.get(key, function(err, value) {
if(err) {
callback(err);
return;
}
if(value) {
value = decrypt(value);
}
callback(null, value);
});
};
CryptoContext.prototype.put = function(key, value, callback) {
var encryptedValue = this.encrypt(value);
this.context.put(key, encryptedValue, callback);
};
CryptoContext.prototype.delete = function(key, callback) {
this.context.delete(key, callback);
};
// It is up to the app using this wrapper how the passphrase is acquired, probably by
// prompting the user to enter it when the file system is being opened.
function CryptoAdapter(passphrase, provider) {
this.provider = provider;
// Cache cipher algorithm we'll use in encrypt/decrypt
var cipher = CryptoJS.AES;
// To encrypt:
// 1) accept a buffer (Uint8Array) containing binary data
// 2) convert the buffer to a CipherJS WordArray
// 3) encrypt the WordArray using the chosen cipher algorithm + passphrase
// 4) convert the resulting ciphertext to a UTF8 encoded Uint8Array and return
this.encrypt = function(buffer) {
var wordArray = toWordArray(buffer);
var encrypted = cipher.encrypt(wordArray, passphrase);
var utf8EncodedBuf = encode(encrypted);
return utf8EncodedBuf;
};
// To decrypt:
// 1) accept a buffer (Uint8Array) containing a UTF8 encoded Uint8Array
// 2) convert the buffer to string (i.e., the ciphertext we got from encrypting)
// 3) decrypt the ciphertext string
// 4) convert the decrypted cipherParam object to a UTF8 string
// 5) encode the UTF8 string to a Uint8Array buffer and return
this.decrypt = function(buffer) {
var encryptedStr = decode(buffer);
var decrypted = cipher.decrypt(encryptedStr, passphrase);
var decryptedUtf8 = decrypted.toString(CryptoJS.enc.Utf8);
var utf8EncodedBuf = encode(decryptedUtf8);
return utf8EncodedBuf;
};
}
CryptoAdapter.isSupported = function() {
return true;
};
CryptoAdapter.prototype.open = function(callback) {
this.provider.open(callback);
};
CryptoAdapter.prototype.getReadOnlyContext = function() {
return new CryptoContext(this.provider.getReadOnlyContext(),
this.encrypt,
this.decrypt);
};
CryptoAdapter.prototype.getReadWriteContext = function() {
return new CryptoContext(this.provider.getReadWriteContext(),
this.encrypt,
this.decrypt);
};
return CryptoAdapter;
});

View File

@ -1,63 +0,0 @@
define(function(require) {
// Zlib compression, see
// https://github.com/imaya/zlib.js/blob/master/bin/zlib.min.js
require("zlib");
var Inflate = Zlib.Inflate;
function inflate(compressed) {
return (new Inflate(compressed)).decompress();
}
var Deflate = Zlib.Deflate;
function deflate(buffer) {
return (new Deflate(buffer)).compress();
}
function ZlibContext(context) {
this.context = context;
}
ZlibContext.prototype.clear = function(callback) {
this.context.clear(callback);
};
ZlibContext.prototype.get = function(key, callback) {
this.context.get(key, function(err, result) {
if(err) {
callback(err);
return;
}
// Deal with result being null
if(result) {
result = inflate(result);
}
callback(null, result);
});
};
ZlibContext.prototype.put = function(key, value, callback) {
value = deflate(value);
this.context.put(key, value, callback);
};
ZlibContext.prototype.delete = function(key, callback) {
this.context.delete(key, callback);
};
function ZlibAdapter(provider, inflate, deflate) {
this.provider = provider;
}
ZlibAdapter.isSupported = function() {
return true;
};
ZlibAdapter.prototype.open = function(callback) {
this.provider.open(callback);
};
ZlibAdapter.prototype.getReadOnlyContext = function() {
return new ZlibContext(this.provider.getReadOnlyContext());
};
ZlibAdapter.prototype.getReadWriteContext = function() {
return new ZlibContext(this.provider.getReadWriteContext());
};
return ZlibAdapter;
});

View File

@ -10,11 +10,7 @@ var FS_READY = Constants.FS_READY;
var FS_PENDING = Constants.FS_PENDING; var FS_PENDING = Constants.FS_PENDING;
var FS_ERROR = Constants.FS_ERROR; var FS_ERROR = Constants.FS_ERROR;
// TODO: fix adapters + providers for node.js... var providers = require('../providers/index.js');
//var providers = require('../providers/providers.js');
var providers = {};
//var adapters = require('../adapters/adapters.js');
var adapters = {};
var Shell = require('../shell/shell.js'); var Shell = require('../shell/shell.js');
var Intercom = require('../../lib/intercom.js'); var Intercom = require('../../lib/intercom.js');
@ -218,9 +214,6 @@ function FileSystem(options, callback) {
// Expose storage providers on FileSystem constructor // Expose storage providers on FileSystem constructor
FileSystem.providers = providers; FileSystem.providers = providers;
// Expose adatpers on FileSystem constructor
FileSystem.adapters = adapters;
/** /**
* Public API for FileSystem * Public API for FileSystem
*/ */

35
src/providers/index.js Normal file
View File

@ -0,0 +1,35 @@
var IndexedDB = require('./indexeddb.js');
var WebSQL = require('./websql.js');
var Memory = require('./memory.js');
module.exports = {
IndexedDB: IndexedDB,
WebSQL: WebSQL,
Memory: Memory,
/**
* Convenience Provider references
*/
// The default provider to use when none is specified
Default: IndexedDB,
// The Fallback provider does automatic fallback checks
Fallback: (function() {
if(IndexedDB.isSupported()) {
return IndexedDB;
}
if(WebSQL.isSupported()) {
return WebSQL;
}
function NotSupported() {
throw "[Filer Error] Your browser doesn't support IndexedDB or WebSQL.";
}
NotSupported.isSupported = function() {
return false;
};
return NotSupported;
}())
};

View File

@ -1,17 +1,14 @@
define(function(require) { (function(global) {
var FILE_SYSTEM_NAME = require('src/constants').FILE_SYSTEM_NAME; var FILE_SYSTEM_NAME = require('../constants.js').FILE_SYSTEM_NAME;
var FILE_STORE_NAME = require('src/constants').FILE_STORE_NAME; var FILE_STORE_NAME = require('../constants.js').FILE_STORE_NAME;
var IDB_RW = require('../constants.js').IDB_RW;
var IDB_RO = require('../constants.js').IDB_RO;
var Errors = require('../errors.js');
var indexedDB = (function(window) { var indexedDB = global.indexedDB ||
return window.indexedDB || global.mozIndexedDB ||
window.mozIndexedDB || global.webkitIndexedDB ||
window.webkitIndexedDB || global.msIndexedDB;
window.msIndexedDB;
}(this));
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) { function IndexedDBContext(db, mode) {
var transaction = db.transaction(FILE_STORE_NAME, mode); var transaction = db.transaction(FILE_STORE_NAME, mode);
@ -128,5 +125,6 @@ define(function(require) {
return new IndexedDBContext(this.db, IDB_RW); return new IndexedDBContext(this.db, IDB_RW);
}; };
return IndexedDB; module.exports = IndexedDB;
});
}(this));

View File

@ -1,11 +1,10 @@
define(function(require) { var FILE_SYSTEM_NAME = require('../constants.js').FILE_SYSTEM_NAME;
var FILE_SYSTEM_NAME = require('src/constants').FILE_SYSTEM_NAME; var asyncCallback = require('../../lib/async.js').nextTick;
var asyncCallback = require('async').nextTick;
/** /**
* Make shared in-memory DBs possible when using the same name. * Make shared in-memory DBs possible when using the same name.
*/ */
var createDB = (function() { var createDB = (function() {
var pool = {}; var pool = {};
return function getOrCreate(name) { return function getOrCreate(name) {
var firstAccess = !pool.hasOwnProperty(name); var firstAccess = !pool.hasOwnProperty(name);
@ -17,13 +16,13 @@ define(function(require) {
db: pool[name] db: pool[name]
}; };
}; };
}()); }());
function MemoryContext(db, readOnly) { function MemoryContext(db, readOnly) {
this.readOnly = readOnly; this.readOnly = readOnly;
this.objectStore = db; this.objectStore = db;
} }
MemoryContext.prototype.clear = function(callback) { MemoryContext.prototype.clear = function(callback) {
if(this.readOnly) { if(this.readOnly) {
asyncCallback(function() { asyncCallback(function() {
callback("[MemoryContext] Error: write operation on read only context"); callback("[MemoryContext] Error: write operation on read only context");
@ -35,14 +34,14 @@ define(function(require) {
delete objectStore[key]; delete objectStore[key];
}); });
asyncCallback(callback); asyncCallback(callback);
}; };
MemoryContext.prototype.get = function(key, callback) { MemoryContext.prototype.get = function(key, callback) {
var that = this; var that = this;
asyncCallback(function() { asyncCallback(function() {
callback(null, that.objectStore[key]); callback(null, that.objectStore[key]);
}); });
}; };
MemoryContext.prototype.put = function(key, value, callback) { MemoryContext.prototype.put = function(key, value, callback) {
if(this.readOnly) { if(this.readOnly) {
asyncCallback(function() { asyncCallback(function() {
callback("[MemoryContext] Error: write operation on read only context"); callback("[MemoryContext] Error: write operation on read only context");
@ -51,8 +50,8 @@ define(function(require) {
} }
this.objectStore[key] = value; this.objectStore[key] = value;
asyncCallback(callback); asyncCallback(callback);
}; };
MemoryContext.prototype.delete = function(key, callback) { MemoryContext.prototype.delete = function(key, callback) {
if(this.readOnly) { if(this.readOnly) {
asyncCallback(function() { asyncCallback(function() {
callback("[MemoryContext] Error: write operation on read only context"); callback("[MemoryContext] Error: write operation on read only context");
@ -61,29 +60,28 @@ define(function(require) {
} }
delete this.objectStore[key]; delete this.objectStore[key];
asyncCallback(callback); asyncCallback(callback);
}; };
function Memory(name) { function Memory(name) {
this.name = name || FILE_SYSTEM_NAME; this.name = name || FILE_SYSTEM_NAME;
} }
Memory.isSupported = function() { Memory.isSupported = function() {
return true; return true;
}; };
Memory.prototype.open = function(callback) { Memory.prototype.open = function(callback) {
var result = createDB(this.name); var result = createDB(this.name);
this.db = result.db; this.db = result.db;
asyncCallback(function() { asyncCallback(function() {
callback(null, result.firstAccess); callback(null, result.firstAccess);
}); });
}; };
Memory.prototype.getReadOnlyContext = function() { Memory.prototype.getReadOnlyContext = function() {
return new MemoryContext(this.db, true); return new MemoryContext(this.db, true);
}; };
Memory.prototype.getReadWriteContext = function() { Memory.prototype.getReadWriteContext = function() {
return new MemoryContext(this.db, false); return new MemoryContext(this.db, false);
}; };
return Memory; module.exports = Memory;
});

View File

@ -1,38 +0,0 @@
define(function(require) {
var IndexedDB = require('src/providers/indexeddb');
var WebSQL = require('src/providers/websql');
var Memory = require('src/providers/memory');
return {
IndexedDB: IndexedDB,
WebSQL: WebSQL,
Memory: Memory,
/**
* Convenience Provider references
*/
// The default provider to use when none is specified
Default: IndexedDB,
// The Fallback provider does automatic fallback checks
Fallback: (function() {
if(IndexedDB.isSupported()) {
return IndexedDB;
}
if(WebSQL.isSupported()) {
return WebSQL;
}
function NotSupported() {
throw "[Filer Error] Your browser doesn't support IndexedDB or WebSQL.";
}
NotSupported.isSupported = function() {
return false;
};
return NotSupported;
}())
};
});

View File

@ -1,11 +1,11 @@
define(function(require) { (function(global) {
var FILE_SYSTEM_NAME = require('src/constants').FILE_SYSTEM_NAME; var FILE_SYSTEM_NAME = require('../constants.js').FILE_SYSTEM_NAME;
var FILE_STORE_NAME = require('src/constants').FILE_STORE_NAME; var FILE_STORE_NAME = require('../constants.js').FILE_STORE_NAME;
var WSQL_VERSION = require('src/constants').WSQL_VERSION; var WSQL_VERSION = require('../constants.js').WSQL_VERSION;
var WSQL_SIZE = require('src/constants').WSQL_SIZE; var WSQL_SIZE = require('../constants.js').WSQL_SIZE;
var WSQL_DESC = require('src/constants').WSQL_DESC; var WSQL_DESC = require('../constants.js').WSQL_DESC;
var u8toArray = require('src/shared').u8toArray; var u8toArray = require('../shared.js').u8toArray;
var Errors = require('src/errors'); var Errors = require('../errors.js');
function WebSQLContext(db, isReadOnly) { function WebSQLContext(db, isReadOnly) {
var that = this; var that = this;
@ -98,7 +98,7 @@ define(function(require) {
this.db = null; this.db = null;
} }
WebSQL.isSupported = function() { WebSQL.isSupported = function() {
return typeof window === 'undefined' ? false : !!window.openDatabase; return !!global.openDatabase;
}; };
WebSQL.prototype.open = function(callback) { WebSQL.prototype.open = function(callback) {
@ -110,7 +110,7 @@ define(function(require) {
return; return;
} }
var db = window.openDatabase(that.name, WSQL_VERSION, WSQL_DESC, WSQL_SIZE); var db = global.openDatabase(that.name, WSQL_VERSION, WSQL_DESC, WSQL_SIZE);
if(!db) { if(!db) {
callback("[WebSQL] Unable to open database."); callback("[WebSQL] Unable to open database.");
return; return;
@ -156,5 +156,6 @@ define(function(require) {
return new WebSQLContext(this.db, false); return new WebSQLContext(this.db, false);
}; };
return WebSQL; module.exports = WebSQL;
});
}(this));

View File

@ -1,175 +0,0 @@
define(["Filer", "util"], function(Filer, util) {
// We reuse the same set of tests for all adapters.
// buildTestsFor() creates a set of tests bound to an
// adapter, and uses the provider set on the query string
// (defaults to best available/supported provider, see test-utils.js).
function buildTestsFor(adapterName, buildAdapter) {
function encode(str) {
// TextEncoder is either native, or shimmed by Filer
return (new TextEncoder("utf-8")).encode(str);
}
// Make some string + binary buffer versions of things we'll need
var valueStr = "value", valueBuffer = encode(valueStr);
var value1Str = "value1", value1Buffer = encode(value1Str);
var value2Str = "value2", value2Buffer = encode(value2Str);
function createProvider() {
return buildAdapter(util.provider().provider);
}
describe("Filer.FileSystem.adapters." + adapterName, function() {
beforeEach(util.setup);
afterEach(util.cleanup);
it("is supported -- if it isn't, none of these tests can run.", function() {
// Allow for combined adapters (e.g., 'Encryption+Compression') joined by '+'
adapterName.split('+').forEach(function(name) {
expect(Filer.FileSystem.adapters[name].isSupported()).to.be.true;
});
});
it("has open, getReadOnlyContext, and getReadWriteContext instance methods", function() {
var provider = createProvider();
expect(provider.open).to.be.a('function');
expect(provider.getReadOnlyContext).to.be.a('function');
expect(provider.getReadWriteContext).to.be.a('function');
});
});
describe("open a provider with an " + adapterName + " adapter", function() {
beforeEach(util.setup);
afterEach(util.cleanup);
it("should open a new database", function(done) {
var provider = createProvider();
provider.open(function(error, firstAccess) {
expect(error).not.to.exist;
// NOTE: we test firstAccess logic in the individual provider tests
// (see tests/spec/providers/*) but can't easily/actually test it here,
// since the provider-agnostic code in test-utils pre-creates a
// FileSystem object, thus eating the first access info.
// See https://github.com/js-platform/filer/issues/127
done();
});
});
});
describe("Read/Write operations on a provider with an " + adapterName + " adapter", function() {
beforeEach(util.setup);
afterEach(util.cleanup);
it("should allow put() and get()", function(done) {
var provider = createProvider();
provider.open(function(error, firstAccess) {
if(error) throw error;
var context = provider.getReadWriteContext();
context.put("key", valueBuffer, function(error, result) {
if(error) throw error;
context.get("key", function(error, result) {
expect(error).not.to.exist;
expect(util.typedArrayEqual(result, valueBuffer)).to.be.true;
done();
});
});
});
});
it("should allow delete()", function(done) {
var provider = createProvider();
provider.open(function(error, firstAccess) {
if(error) throw error;
var context = provider.getReadWriteContext();
context.put("key", valueBuffer, function(error, result) {
if(error) throw error;
context.delete("key", function(error, result) {
if(error) throw error;
context.get("key", function(error, result) {
expect(error).not.to.exist;
expect(result).not.to.exist;
done();
});
});
});
});
});
it("should allow clear()", function(done) {
var provider = createProvider();
provider.open(function(error, firstAccess) {
if(error) throw error;
var context = provider.getReadWriteContext();
context.put("key1", value1Buffer, function(error, result) {
if(error) throw error;
context.put("key2", value2Buffer, function(error, result) {
if(error) throw error;
context.clear(function(err) {
if(error) throw error;
context.get("key1", function(error, result) {
if(error) throw error;
expect(result).not.to.exist;
context.get("key2", function(error, result) {
expect(error).not.to.exist;
expect(result).not.to.exist;
done();
});
});
});
});
});
});
});
/**
* With issue 123 (see https://github.com/js-platform/filer/issues/128) we had to
* start using readwrite contexts everywhere with IndexedDB. As such, we can't
* easily test this here, without knowing which provider we have. We test this
* in the actual providers, so this isn't really needed. Skipping for now.
*/
it.skip("should fail when trying to write on ReadOnlyContext", function(done) {
var provider = createProvider();
provider.open(function(error, firstAccess) {
if(error) throw error;
var context = provider.getReadOnlyContext();
context.put("key1", value1Buffer, function(error, result) {
expect(error).to.exist;
expect(result).not.to.exist;
done();
});
});
});
});
}
// Encryption
buildTestsFor('Encryption', function buildAdapter(provider) {
var passphrase = '' + Date.now();
return new Filer.FileSystem.adapters.Encryption(passphrase, provider);
});
// Compression
buildTestsFor('Compression', function buildAdapter(provider) {
return new Filer.FileSystem.adapters.Compression(provider);
});
// Encryption + Compression together
buildTestsFor('Encryption+Compression', function buildAdapter(provider) {
var passphrase = '' + Date.now();
var compression = new Filer.FileSystem.adapters.Compression(provider);
var encryptionWithCompression = new Filer.FileSystem.adapters.Encryption(passphrase, compression);
return encryptionWithCompression;
});
});

View File

@ -1,15 +0,0 @@
define(["Filer"], function(Filer) {
describe("Filer.FileSystem.adapters", function() {
it("is defined", function() {
expect(Filer.FileSystem.adapters).to.exist;
});
it("has a default Encryption constructor", function() {
expect(Filer.FileSystem.adapters.Encryption).to.be.a('function');
});
it("has a default Compression constructor", function() {
expect(Filer.FileSystem.adapters.Compression).to.be.a('function');
});
});
});

View File

@ -45,10 +45,6 @@ define([
"spec/providers/providers.indexeddb.spec", "spec/providers/providers.indexeddb.spec",
"spec/providers/providers.websql.spec", "spec/providers/providers.websql.spec",
// Filer.FileSystem.adapters.*
"spec/adapters/adapters.spec",
"spec/adapters/adapters.general.spec",
// Filer.FileSystemShell.* // Filer.FileSystemShell.*
"spec/shell/cd.spec", "spec/shell/cd.spec",
"spec/shell/touch.spec", "spec/shell/touch.spec",