| | |
| | | return MEMFS.mount.apply(null, arguments); |
| | | }, |
| | | syncfs: function(mount, populate, callback) { |
| | | if (mount.opts.disabled) { |
| | | return callback(new Error("Syncing Disabled")); |
| | | if (mount.opts.disabled) { |
| | | return callback(new Error("Syncing Disabled")); |
| | | } |
| | | |
| | | if (populate) { |
| | | if (!mount.opts.disabled) { |
| | | CLOUDFS.syncfs_load_from_cloud(mount, function (err) { |
| | | if (err) return callback(err); |
| | | CLOUDFS.syncfs_load_from_idb(mount, callback); |
| | | }); |
| | | } else { |
| | | CLOUDFS.syncfs_load_from_idb(mount, callback); |
| | | } |
| | | } else { |
| | | CLOUDFS.syncfs_save_to_idb(mount, function(err) { |
| | | if (err) return callback(err); |
| | | if (!mount.opts.disabled) { |
| | | CLOUDFS.syncfs_save_to_cloud(mount, callback); |
| | | } |
| | | }); |
| | | } |
| | | }, |
| | | syncfs_load_from_cloud: function(mount, callback) { |
| | | CLOUDFS.getRemoteSet(mount, function(err, remote) { |
| | | if (err) return callback(err); |
| | | |
| | | CLOUDFS.getIDBSet(mount, function(err, idb) { |
| | | if (err) return callback(err); |
| | | |
| | | CLOUDFS.reconcile(mount, remote, idb, callback); |
| | | }); |
| | | }); |
| | | }, |
| | | syncfs_load_from_idb: function(mount, callback) { |
| | | CLOUDFS.getIDBSet(mount, function(err, idb) { |
| | | if (err) return callback(err); |
| | | |
| | | CLOUDFS.getLocalSet(mount, function(err, local) { |
| | | if (err) return callback(err); |
| | | if (err) return callback(err); |
| | | |
| | | CLOUDFS.getRemoteSet(mount, function(err, remote) { |
| | | if (err) return callback(err); |
| | | |
| | | var src = populate ? remote : local; |
| | | var dst = populate ? local : remote; |
| | | |
| | | CLOUDFS.reconcile(mount, src, dst, callback); |
| | | }); |
| | | CLOUDFS.reconcile(mount, idb, local, callback); |
| | | }); |
| | | }); |
| | | }, |
| | | syncfs_save_to_idb: function(mount, callback) { |
| | | CLOUDFS.getLocalSet(mount, function(err, local) { |
| | | if (err) return callback(err); |
| | | |
| | | CLOUDFS.getIDBSet(mount, function(err, idb) { |
| | | if (err) return callback(err); |
| | | |
| | | CLOUDFS.reconcile(mount, local, idb, callback); |
| | | }); |
| | | }); |
| | | }, |
| | | syncfs_save_to_cloud: function(mount, callback) { |
| | | CLOUDFS.getIDBSet(mount, function(err, idb) { |
| | | if (err) return callback(err); |
| | | |
| | | CLOUDFS.getRemoteSet(mount, function(err, remote) { |
| | | if (err) return callback(err); |
| | | |
| | | CLOUDFS.reconcile(mount, idb, remote, callback); |
| | | }); |
| | | }); |
| | | }, |
| | | // handling the diffing of "haves" and "have nots" |
| | | reconcile: function(mount, src, dst, callback) { |
| | |
| | | } |
| | | }; |
| | | |
| | | var srcFunc = CLOUDFS.getFuncSet(src.type), |
| | | dstFunc = CLOUDFS.getFuncSet(dst.type); |
| | | var srcCtx = srcFunc.start(mount, src, done), |
| | | dstCtx = dstFunc.start(mount, dst, done); |
| | | |
| | | // sort paths in ascending order so directory entries are created |
| | | // before the files inside them |
| | | create.sort().forEach(function (path) { |
| | | var pathinfo = src.entries[path]; |
| | | if (dst.type === 'local') { |
| | | CLOUDFS.loadRemoteEntry(mount, pathinfo, function (err, entry) { |
| | | if (err) return done(err); |
| | | CLOUDFS.storeLocalEntry(path, entry, done); |
| | | }); |
| | | } else { |
| | | CLOUDFS.loadLocalEntry(path, function (err, entry) { |
| | | if (err) return done(err); |
| | | CLOUDFS.storeRemoteEntry(mount, pathinfo, entry, done); |
| | | }); |
| | | } |
| | | srcFunc.load(mount, srcCtx, pathinfo, function(err, entry) { |
| | | if (err) return done(err); |
| | | dstFunc.store(mount, dstCtx, pathinfo, entry, done); |
| | | }); |
| | | }); |
| | | |
| | | // sort paths in descending order so files are deleted before their |
| | | // parent directories |
| | | remove.sort().reverse().forEach(function(path) { |
| | | if (dst.type === 'local') { |
| | | CLOUDFS.removeLocalEntry(path, done); |
| | | } else { |
| | | CLOUDFS.removeRemoteEntry(mount, dst.entries[path], done); |
| | | } |
| | | dstFunc.remove(mount, dstCtx, dst.entries[path], done); |
| | | }); |
| | | }, |
| | | // Utility functions |
| | |
| | | prefix = p; |
| | | }); |
| | | } |
| | | }, |
| | | getFuncSet: function(type) { |
| | | if (type == 'local') { |
| | | return { |
| | | start: function() {}, |
| | | load: CLOUDFS.loadLocalEntry, |
| | | store: CLOUDFS.storeLocalEntry, |
| | | remove: CLOUDFS.removeLocalEntry |
| | | }; |
| | | } else if (type == 'remote') { |
| | | return { |
| | | start: function() {}, |
| | | load: CLOUDFS.loadRemoteEntry, |
| | | store: CLOUDFS.storeRemoteEntry, |
| | | remove: CLOUDFS.removeRemoteEntry |
| | | }; |
| | | } else if (type == 'idb') { |
| | | return { |
| | | start: CLOUDFS.startIDBEntry, |
| | | load: CLOUDFS.loadIDBEntry, |
| | | store: CLOUDFS.storeIDBEntry, |
| | | remove: CLOUDFS.removeIDBEntry |
| | | }; |
| | | } |
| | | }, |
| | | // Indexed DB Utility functions |
| | | db: null, |
| | | indexedDB: function() { |
| | | if (typeof indexedDB !== 'undefined') return indexedDB; |
| | | var ret = null; |
| | | if (typeof window === 'object') ret = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; |
| | | assert(ret, 'CLOUDFS used, but indexedDB not supported'); |
| | | return ret; |
| | | }, |
| | | DB_VERSION: 1, |
| | | DB_NAME: 'CLOUDFS', |
| | | DB_STORE_NAME: 'FILE_DATA', |
| | | getDB: function(callback) { |
| | | // check the cache first |
| | | var db = CLOUDFS.db; |
| | | if (db) { |
| | | return callback(null, db); |
| | | } |
| | | |
| | | var req; |
| | | try { |
| | | req = CLOUDFS.indexedDB().open(CLOUDFS.DB_NAME, CLOUDFS.DB_VERSION); |
| | | } catch (e) { |
| | | return callback(e); |
| | | } |
| | | req.onupgradeneeded = function(e) { |
| | | var db = e.target.result; |
| | | var transaction = e.target.transaction; |
| | | |
| | | var fileStore; |
| | | |
| | | if (db.objectStoreNames.contains(CLOUDFS.DB_STORE_NAME)) { |
| | | fileStore = transaction.objectStore(CLOUDFS.DB_STORE_NAME); |
| | | } else { |
| | | fileStore = db.createObjectStore(CLOUDFS.DB_STORE_NAME);//, {keyPath: 'key'}); |
| | | } |
| | | |
| | | fileStore.createIndex('scope','scope',{ unique: false }); |
| | | }; |
| | | req.onsuccess = function() { |
| | | db = req.result; |
| | | |
| | | // add to the cache |
| | | CLOUDFS.db = db; |
| | | callback(null, db); |
| | | }; |
| | | req.onerror = function() { |
| | | callback(this.error); |
| | | }; |
| | | }, |
| | | // Getting list of entities |
| | | getLocalSet: function(mount, callback) { |
| | |
| | | var store = transaction.objectStore(CLOUDFS.DB_STORE_NAME); |
| | | var index = store.index('scope'); |
| | | |
| | | index.openKeyCursor(IDBKeyRange.only(mount.opts.scope)).onsuccess = function(event) { |
| | | index.openCursor(IDBKeyRange.only(mount.opts.scope)).onsuccess = function(event) { |
| | | var cursor = event.target.result; |
| | | |
| | | if (!cursor) { |
| | | return callback(null, { type: 'idb', db: db, entries: entries }); |
| | | } |
| | | |
| | | entries[cursor.primaryKey] = { timestamp: cursor.key }; |
| | | entries[PATH.join2(mount.mountpoint, cursor.value.path)] = { |
| | | path: cursor.value.path, |
| | | type: FS.isDir(cursor.value.mode) ? 'dir' : 'file', |
| | | timestamp: cursor.value.timestamp |
| | | }; |
| | | |
| | | cursor.continue(); |
| | | }; |
| | |
| | | }); |
| | | }, |
| | | // Local file access |
| | | loadLocalEntry: function(path, callback) { |
| | | var stat, node; |
| | | loadLocalEntry: function(mount, ctx, pathinfo, callback) { |
| | | var stat, node, |
| | | path = PATH.join2(mount.mountpoint, pathinfo.path); |
| | | |
| | | try { |
| | | var lookup = FS.lookupPath(path); |
| | |
| | | return callback(new Error('node type not supported')); |
| | | } |
| | | }, |
| | | storeLocalEntry: function(path, entry, callback) { |
| | | storeLocalEntry: function(mount, ctx, pathinfo, entry, callback) { |
| | | var path = PATH.join2(mount.mountpoint, pathinfo.path); |
| | | try { |
| | | if (FS.isDir(entry.mode)) { |
| | | try { |
| | |
| | | |
| | | callback(null); |
| | | }, |
| | | removeLocalEntry: function(path, callback) { |
| | | removeLocalEntry: function(mount, ctx, pathinfo, callback) { |
| | | var path = PATH.join2(mount.mountpoint, pathinfo.path); |
| | | try { |
| | | var lookup = FS.lookupPath(path); |
| | | var stat = FS.stat(path); |
| | |
| | | |
| | | callback(null); |
| | | }, |
| | | // IDB File access |
| | | buildIDBKey: function(mount, pathinfo) { |
| | | return (mount.opts.scope || '') + ':' + pathinfo.path; |
| | | }, |
| | | startIDBEntry: function(mount, dataset, ondone) { |
| | | return {db: dataset.db}; |
| | | }, |
| | | loadIDBEntry: function(mount, ctx, pathinfo, callback) { |
| | | var tx = ctx.db.transaction([CLOUDFS.DB_STORE_NAME], 'readwrite'); |
| | | tx.onerror = function() { callback(this.error); }; |
| | | var store = tx.objectStore(CLOUDFS.DB_STORE_NAME); |
| | | |
| | | var req = store.get(CLOUDFS.buildIDBKey(mount, pathinfo)); |
| | | req.onsuccess = function(event) { callback(null, event.target.result); }; |
| | | req.onerror = function() { callback(this.error); }; |
| | | }, |
| | | storeIDBEntry: function(mount, ctx, pathinfo, entry, callback) { |
| | | var tx = ctx.db.transaction([CLOUDFS.DB_STORE_NAME], 'readwrite'); |
| | | tx.onerror = function() { callback(this.error); }; |
| | | var store = tx.objectStore(CLOUDFS.DB_STORE_NAME); |
| | | |
| | | // keep scope with entry |
| | | var d = { |
| | | scope: mount.opts.scope, |
| | | path: pathinfo.path, |
| | | mode: entry.mode, |
| | | timestamp: entry.timestamp |
| | | }; |
| | | if (entry.contents) d.contents = entry.contents; |
| | | |
| | | var req = store.put(d, CLOUDFS.buildIDBKey(mount, pathinfo)); |
| | | req.onsuccess = function() { callback(null); }; |
| | | req.onerror = function() { callback(this.error); }; |
| | | }, |
| | | removeIDBEntry: function(mount, ctx, pathinfo, callback) { |
| | | var tx = ctx.db.transaction([CLOUDFS.DB_STORE_NAME], 'readwrite'); |
| | | tx.onerror = function() { callback(this.error); }; |
| | | var store = tx.objectStore(CLOUDFS.DB_STORE_NAME); |
| | | |
| | | var req = store.delete(CLOUDFS.buildIDBKey(mount, pathinfo)); |
| | | req.onsuccess = function() { callback(null); }; |
| | | req.onerror = function() { callback(this.error); }; |
| | | }, |
| | | // Remote file access |
| | | loadRemoteEntry: function(mount, pathinfo, callback) { |
| | | loadRemoteEntry: function(mount, ctx, pathinfo, callback) { |
| | | if (pathinfo.type == 'file') { |
| | | mount.opts.provider.read(mount.opts.cloud, pathinfo.url, |
| | | function(data) { |
| | | callback(null, { contents: data, timestamp: pathinfo.timestamp, mode: CLOUDFS._FILE_MODE }); |
| | | // ensure data is in Uint8Array |
| | | var u8data = new Uint8Array(data); |
| | | callback(null, { contents: u8data, timestamp: pathinfo.timestamp, mode: CLOUDFS._FILE_MODE }); |
| | | }, |
| | | function(e) { |
| | | callback(e); |
| | |
| | | callback(null, { timestamp: pathinfo.timestamp, mode: CLOUDFS._DIR_MODE }); |
| | | } |
| | | }, |
| | | storeRemoteEntry: function(mount, pathinfo, entry, callback) { |
| | | storeRemoteEntry: function(mount, ctx, pathinfo, entry, callback) { |
| | | if (FS.isFile(entry.mode)) { |
| | | mount.opts.provider.write(mount.opts.cloud, pathinfo, entry.contents, function() { |
| | | callback(null); |
| | |
| | | }) |
| | | } |
| | | }, |
| | | removeRemoteEntry: function(mount, pathinfo, callback) { |
| | | removeRemoteEntry: function(mount, ctx, pathinfo, callback) { |
| | | if (pathinfo.type == 'file') { |
| | | mount.opts.provider.rm(mount.opts.cloud, pathinfo, function() { |
| | | callback(null); |