| | |
| | | var provider = CLOUDFS.fetchProvider(mount); |
| | | if (provider) { |
| | | mount.opts.provider = provider; |
| | | if (!mount.opts.scope) { |
| | | // backwards compat |
| | | mount.opts.scope = mount.opts.cloud.applicationtoken; |
| | | } |
| | | mount.opts.cloud.scope = mount.opts.scope; |
| | | Module.print('Cloud provider vendor: ' + provider.vendor); |
| | | if (!provider.isAvailable(mount.opts.cloud)) { |
| | | mount.opts.disabled = true; |
| | | Module.print("WARNING: Cloud not available. Disabling Cloud Sync"); |
| | | } |
| | | } else { |
| | | mount.opts.disabled = true; |
| | | Module.print("WARNING: Cloud provider not available. Disabling Sync"); |
| | | Module.print("WARNING: Cloud provider not available. Disabling Cloud Sync"); |
| | | } |
| | | 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 |
| | |
| | | var provider = CLOUD_PROVIDERS[provider_name]; |
| | | if (provider === undefined) return false; |
| | | |
| | | var requiredMethods = ['allFiles', 'read', 'write', 'rm']; |
| | | var requiredMethods = ['allFiles', 'read', 'write', 'rm','isAvailable']; |
| | | return requiredMethods.every(function(method) { |
| | | return (method in provider); |
| | | }); |
| | |
| | | return false; |
| | | } |
| | | if (CLOUDFS.validateProvider( mount.opts.provider ) ) { |
| | | var provider = CLOUD_PROVIDERS[mount.opts.provider]; |
| | | Module.print('Cloud provider vendor: ' + provider.vendor); |
| | | return provider; |
| | | return CLOUD_PROVIDERS[mount.opts.provider]; |
| | | } else { |
| | | return false; |
| | | } |
| | | }, |
| | | populateDirs: function(entries, f, toAbsolute) { |
| | | if (f.path.indexOf('/') !== -1) { |
| | | // we have folders.. stuff them in the list |
| | | var parts = f.path.split('/'), |
| | | prefix = ''; |
| | | // remove the "file" from the end |
| | | parts.pop(); |
| | | // remove the empty directory from the beginning |
| | | if (parts[0] == '') parts.shift(); |
| | | |
| | | parts.forEach(function(e) { |
| | | var p = prefix.length ? PATH.join2(prefix, e) : e, |
| | | abs = toAbsolute(p); |
| | | if (!(abs in entries)) { |
| | | entries[abs] = { |
| | | path: p, |
| | | type: 'dir', |
| | | timestamp: f.timestamp |
| | | }; |
| | | } |
| | | 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 path = check.pop(), |
| | | stat, |
| | | keep = true, |
| | | is_dir = false, |
| | | abs_path = PATH.join2(mount.mountpoint, path); |
| | | |
| | | try { |
| | |
| | | |
| | | if (FS.isDir(stat.mode)) { |
| | | check.push.apply(check, FS.readdir(abs_path).filter(isRealDir).map(toAbsolute(path))); |
| | | is_dir = true; |
| | | } else if (shouldFilter) { |
| | | keep = checkPath(path); |
| | | } |
| | |
| | | if (keep) { |
| | | entries[abs_path] = { |
| | | timestamp: stat.mtime, |
| | | path: path |
| | | path: path, |
| | | type: is_dir ? 'dir' : 'file' |
| | | }; |
| | | } |
| | | } |
| | | |
| | | return callback(null, { type: 'local', entries: entries }); |
| | | }, |
| | | getIDBSet: function(mount, callback) { |
| | | var entries = {}, |
| | | toAbsolute = function(p) { return PATH.join2(mount.mountpoint, p); }; |
| | | |
| | | CLOUDFS.getDB(function(err, db) { |
| | | if (err) return callback(err); |
| | | |
| | | var transaction = db.transaction([CLOUDFS.DB_STORE_NAME], 'readonly'); |
| | | transaction.onerror = function() { callback(this.error); }; |
| | | |
| | | var store = transaction.objectStore(CLOUDFS.DB_STORE_NAME); |
| | | var index = store.index('scope'); |
| | | |
| | | 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[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(); |
| | | }; |
| | | }); |
| | | }, |
| | | getRemoteSet: function(mount, callback) { |
| | | mount.opts.provider.allFiles(mount.opts.cloud, function(data) { |
| | |
| | | for(var k in data) { |
| | | var f = data[k]; |
| | | |
| | | if (f.path.indexOf('/') !== -1) { |
| | | // we have folders.. stuff them in the list |
| | | var parts = f.path.split('/'), |
| | | prefix = ''; |
| | | // remove the "file" from the end |
| | | parts.pop(); |
| | | // remove the empty directory from the beginning |
| | | if (parts[0] == '') parts.shift(); |
| | | CLOUDFS.populateDirs(entries, f, toAbsolute); |
| | | |
| | | parts.forEach(function(e) { |
| | | var p = prefix.length ? PATH.join2(prefix, e) : e, |
| | | abs = toAbsolute(p); |
| | | if (!(abs in entries)) { |
| | | entries[abs] = { |
| | | path: p, |
| | | type: 'dir', |
| | | timestamp: f.timestamp |
| | | }; |
| | | } |
| | | prefix = p; |
| | | }); |
| | | } |
| | | var p = toAbsolute(f.path); |
| | | entries[p] = { |
| | | url: f.url, |
| | |
| | | callback(e || new Error('failed request')); |
| | | }); |
| | | }, |
| | | // Fetching local and remote files |
| | | loadLocalEntry: function(path, callback) { |
| | | var stat, node; |
| | | // Local file access |
| | | 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')); |
| | | } |
| | | }, |
| | | loadRemoteEntry: function(mount, 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: {{{ cDefine('S_IFREG') | 0777 }}} }); |
| | | }, |
| | | function(e) { |
| | | callback(e); |
| | | }); |
| | | storeLocalEntry: function(mount, ctx, pathinfo, entry, callback) { |
| | | var path = PATH.join2(mount.mountpoint, pathinfo.path); |
| | | try { |
| | | if (FS.isDir(entry.mode)) { |
| | | try { |
| | | FS.mkdir(path, entry.mode); |
| | | } catch(e) { |
| | | // ignore existing dirs |
| | | } |
| | | } else if (FS.isFile(entry.mode)) { |
| | | FS.writeFile(path, entry.contents, { encoding: 'binary', canOwn: true }); |
| | | } else { |
| | | callback(null, { timestamp: pathinfo.timestamp, mode: {{{ cDefine('S_IFDIR') | 0777 }}} }); |
| | | } |
| | | }, |
| | | // storing local and remote files |
| | | storeLocalEntry: function(path, entry, callback) { |
| | | try { |
| | | if (FS.isDir(entry.mode)) { |
| | | try { |
| | | FS.mkdir(path, entry.mode); |
| | | } catch(e) { |
| | | // ignore existing dirs |
| | | } |
| | | } else if (FS.isFile(entry.mode)) { |
| | | FS.writeFile(path, entry.contents, { encoding: 'binary', canOwn: true }); |
| | | } else { |
| | | return callback(new Error('node type not supported')); |
| | | } |
| | | |
| | | FS.utime(path, entry.timestamp, entry.timestamp); |
| | | } catch (e) { |
| | | return callback(e); |
| | | return callback(new Error('node type not supported')); |
| | | } |
| | | |
| | | callback(null); |
| | | FS.utime(path, entry.timestamp, entry.timestamp); |
| | | } catch (e) { |
| | | return callback(e); |
| | | } |
| | | |
| | | callback(null); |
| | | }, |
| | | storeRemoteEntry: function(mount, pathinfo, entry, callback) { |
| | | if (FS.isFile(entry.mode)) { |
| | | mount.opts.provider.write(mount.opts.cloud, pathinfo, entry.contents, function() { |
| | | callback(null); |
| | | }, |
| | | function(e) { |
| | | callback(e); |
| | | }) |
| | | } |
| | | }, |
| | | // remove local and remote files |
| | | 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); |
| | | }, |
| | | removeRemoteEntry: function(mount, pathinfo, callback) { |
| | | // 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, ctx, pathinfo, callback) { |
| | | if (pathinfo.type == 'file') { |
| | | mount.opts.provider.read(mount.opts.cloud, pathinfo.url, |
| | | function(data) { |
| | | // 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); |
| | | }); |
| | | } else { |
| | | callback(null, { timestamp: pathinfo.timestamp, mode: CLOUDFS._DIR_MODE }); |
| | | } |
| | | }, |
| | | 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); |
| | | }, |
| | | function(e) { |
| | | callback(e); |
| | | }) |
| | | } |
| | | }, |
| | | removeRemoteEntry: function(mount, ctx, pathinfo, callback) { |
| | | if (pathinfo.type == 'file') { |
| | | mount.opts.provider.rm(mount.opts.cloud, pathinfo, function() { |
| | | callback(null); |
| | |
| | | callback(e); |
| | | }); |
| | | } |
| | | } |
| | | }, |
| | | _FILE_MODE: {{{ cDefine('S_IFREG') | 0777 }}}, |
| | | _DIR_MODE: {{{ cDefine('S_IFDIR') | 0777 }}} |
| | | } |
| | | }; |
| | | |