Skip to content

Instantly share code, notes, and snippets.

@happybeing
Created October 12, 2016 16:46
Show Gist options
  • Save happybeing/50a49bd1d5bc76128f6e50eaf1fae5b3 to your computer and use it in GitHub Desktop.
Save happybeing/50a49bd1d5bc76128f6e50eaf1fae5b3 to your computer and use it in GitHub Desktop.
using safeNFS.getDir
// For now, the safenetwork.js backend requires RS.js built with different LAUNCHER_URL (see below)
// according to the test environment.
//
// Note: during SAFEnetwork testing the SAFE API is changing regularly, so if you want to build RS.js with this backend
// yourself, you will need to make sure that the version of safestore.js is in step with that API of the SAFEnetwork
// you are connecting to at the time (which changes periodically). If in doubt ask webalyst (aka happybeing).
LAUNCHER_URL = 'http://localhost:8100'; // For local tests - but use http://api.safenet when live on SAFEnetwork
//LAUNCHER_URL = 'http://api.safenet'; // For live tests using Firefox/Chrome with proxy configured, and running SAFE Launcher locally
//LAUNCHER_URL = 'safe://api.safenet'; // For SAFE Beaker Browser, no proxy needed, and running SAFE Launcher locally
(function (global) {
/**
* Class: RemoteStorage.SafeNetwork
*
* WORK IN PROGRESS, NOT RECOMMENDED FOR PRODUCTION USE
*
* SAFE Network backend for RemoteStorage.js mrhTODO: ??? This file exposes a
* get/put/delete interface which is compatible with
* <RemoteStorage.WireClient>.
*
* When remoteStorage.backend is set to 'safenetwork', this backend will
* initialize and replace remoteStorage.remote with remoteStorage.safenetwork.
*
* mrhTODO: ??? In order to ensure compatibility with the public folder,
* <BaseClient.getItemURL> gets hijacked to return the Dropbox public share
* URL.
*
* mrhTODO: To use this backend, you need to specify the app's client ID like so:
*
* (start code)
*
* remoteStorage.setApiKeys('safenetwork', {
* clientId: 'your-client-id'
* });
*
* (end code)
*
* An client ID can be obtained by registering your app in the Google
* Developers Console: https://developers.google.com/drive/web/auth/web-client
*
* Docs: https://developers.google.com/drive/web/auth/web-client#create_a_client_id_and_client_secret
**/
var RS = RemoteStorage;
var hasLocalStorage;// mrhTODO look at use of this
var SETTINGS_KEY = 'remotestorage:safenetwork';
var cleanPath = RS.WireClient.cleanPath;
var PATH_PREFIX = '/remotestorage/'; // mrhTODO app configurable?
var RS_DIR_MIME_TYPE = 'application/json; charset=UTF-8';
function parentPath(path) {
return path.replace(/[^\/]+\/?$/, '');
}
// Used to cache file info
var Cache = function (maxAge) {
this.maxAge = maxAge;
this._items = {};
};
Cache.prototype = {
get: function (key) {
var item = this._items[key];
var now = new Date().getTime();
return (item && item.t >= (now - this.maxAge)) ? item.v : undefined;
},
set: function (key, value) {
this._items[key] = {
v: value,
t: new Date().getTime()
};
}
};
var onErrorCb;
RS.SafeNetwork = function (remoteStorage, clientId) {
this.rs = remoteStorage;
this.clientId = clientId;
this._fileInfoCache = new Cache(60 * 5); // mrhTODO: info expires after 5 minutes (is this a good idea?)
this.connected = false;
var self = this;
onErrorCb = function (error){
if (error instanceof RemoteStorage.Unauthorized) {
// mrhTODO store auth info here (e.g. token, safeURL?) - CHECK API: WireClient.configure
// Delete all the settings - see the documentation of
// wireclient.configure
self.configure({
// mrhTODO can probably eliminate all these - check if any apply to SAFE backend first
userAddress: null, // webfinger style address (username@server)
href: null, // server URL from webfinger
storageApi: null, // remoteStorage API dependencies in here (safenetwork.js), not server, so hardcode?
options: null, // http request headers - maybe Dropbox only?
// SAFE Launcher auth response:
token: null,
permissions: null, // List of granted SAFE Network access permssions (e.g. 'SAFE_DRIVE_ACCESS')
});
}
};
RS.eventHandling(this, 'change', 'connected', 'wire-busy', 'wire-done', 'not-connected');
//mrhTODO (was from dropbox version - stops connect doing anything) this.rs.on('error', onErrorCb);
// mrhTODO port dropbox style load/save settings from localStorage
};
RS.SafeNetwork.prototype = {
connected: false,
online: true,
isPathShared: true, // App private storage mrhTODO shared or private? app able to control?
launcherUrl: LAUNCHER_URL, // Can be overridden by App
configure: function (settings) { // Settings parameter compatible with WireClient
// mrhTODO: review dropbox approach later
if (settings.token) {
localStorage['remotestorage:safenetwork:token'] = settings.token;
this.token = settings.token;
this.connected = true;
this.permissions = settings.permissions; // List of permissions approved by the user
this._emit('connected');
RS.log('SafeNetwork.configure() [CONNECTED]');
} else {
this.connected = false;
delete this.token;
this.permissions = null;
delete localStorage['remotestorage:safenetwork:token'];
RS.log('SafeNetwork.configure() [DISCONNECTED]');
}
},
connect: function () {
RS.log('SafeNetwork.connect()...');
// mrhTODO: note dropbox connect calls hookIt() if it has a token - for sync?
// mrhTODO: note dropbox connect skips auth if it has a token - enables it to remember connection across sessions
// mrhTODO: if storing Authorization consider security risk - e.g. another app could steal to access SAFE Drive?
this.rs.setBackend('safenetwork');
this.safenetworkAuthorize(this.rs.apiKeys['safenetwork']);
},
stopWaitingForToken: function () {
if (!this.connected) {
this._emit('not-connected');
}
},
safenetworkAuthorize: function (appApiKeys) {
var self = this;
self.appKeys = appApiKeys.app;
return window.safeAuth.authorise(self.appKeys).then( function(res) { // mrhTODO - am leaving off local storage key
// Save session info
self.configure({
token: res.token, // Auth token
permissions: self.appKeys.permissions, // List of permissions approved by the user
});
}, (err) => {
RS.log('SafeNetwork Authorisation Failed');
RS.log(err);
return this;
});
},
OLDsafenetworkAuthorize: function (appApiKeys) {
var self = this;
// Session data
this.launcherUrl = LAUNCHER_URL;
// App can override url by setting appApiKeys.laucherURL
if ( typeof appApiKeys.launcherURL !== 'undefined' ) { this.launcherUrl = appApiKeys.launcherURL; }
// JSON string ("payload") for POST
this.payload = appApiKeys; // App calls setApiKeys() to configure persistent part of "payload"
// The request...
var options = {
url: this.launcherUrl + '/auth',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(this.payload)
};
// POST
return RS.WireClient.request.call(this, 'POST', options.url, options).then(function (xhr) {
// Launcher responses
// 401 - Unauthorised
// 400 - Fields are missing
if (xhr && (xhr.status === 400 || xhr.status == 401) ) {
RS.log('SafeNetwork Authorisation Failed');
//mrhTODO causes error: return Promise.reject({statusCode: xhr.status});
} else {
var response = JSON.parse(xhr.responseText);
// Save session info
self.configure({
token: response.token, // Auth token
permissions: response.permissions, // List of permissions approved by the user
});
//mrhTODO: return Promise.resolve(xhr);
}
});
},
// For reference see WireClient#get (wireclient.js)
get: function (path, options) {
RS.log('SafeNetwork.get(' + path + ',...)' );
var fullPath = RS.WireClient.cleanPath(PATH_PREFIX + '/' + path);
if (path.substr(-1) === '/') {
return this._getFolder(fullPath, options);
} else {
return this._getFile(fullPath, options);
}
},
// put - create and/or update a file
//
// "The response MUST contain a strong etag header, with the document's
// new version (for instance a hash of its contents) as its value."
// Spec: https://github.com/remotestorage/spec/blob/master/release/draft-dejong-remotestorage-07.txt#L295-L296
// See WireClient#put and _request for details of what is returned
//
//
// mrhTODO bug: if the file exists (ie put is doing an update), contentType is not updated
// mrhTODO because it can only be set by _createFile (SAFE NFS API: POST)
// mrhTODO FIX: when API stable, best may be to store contentType in the file not as metadata
// mrhTODO when API stable, best may be to store contentType in the file not as metadata
put: function (path, body, contentType, options) {
RS.log('SafeNetwork.put(' + path + ',...)' );
var fullPath = RS.WireClient.cleanPath(PATH_PREFIX + '/' + path);
// putDone - handle PUT response codes, optionally decodes metadata from JSON format response
var self = this;
function putDone(response) {
RS.log('SafeNetwork.put putDone(' + response.responseText + ') for path: ' + path );
if (response.status >= 200 && response.status < 300) {
return self._getFileInfo(fullPath).then( function (fileInfo){
var etagWithoutQuotes;
if ( fileInfo.ETag === 'string') {
etagWithoutQuotes = fileInfo.ETag.substring(1, fileInfo.ETag.length-1);
}
return Promise.resolve({statusCode: 200, 'contentType': contentType, revision: etagWithoutQuotes});
});
} else if (response.status === 412) { // Precondition failed
return Promise.resolve({statusCode: 412, revision: 'conflict'});
} else {
return Promise.reject(new Error("PUT failed with status " + response.status + " (" + response.responseText + ")"));
}
}
return self._getFileInfo(fullPath).then(function (fileInfo) {
if (fileInfo) {
if (options && (options.ifNoneMatch === '*')) {
return putDone({ status: 412 }); // Precondition failed
}
return self._updateFile(fullPath, body, contentType, options).then(putDone);
} else {
return self._createFile(fullPath, body, contentType, options).then(putDone);
}
});
},
// mrhTODO: delete bug - when the last file or folder in a folder is deleted that folder
// mrhTODO: must no longer appear in listings of the parent folder, and so should
// mrhTODO: be deleted from the SAFE NFS drive, and so on for its parent folder as
// mrhTODO: needed. This is not done currently.
//
'delete': function (path, options) {
RS.log('SafeNetwork.delete(' + path + ',...)' );
var fullPath = RS.WireClient.cleanPath(PATH_PREFIX + '/' + path);
RS.log('SafeNetwork.delete: ' + fullPath + ', ...)' );
var self = this;
return self._getFileInfo(fullPath).then(function (fileInfo) {
if (!fileInfo) {
// File doesn't exist. Ignore.
return Promise.resolve({statusCode: 200});
}
var etagWithoutQuotes;
if (fileInfo.ETag === 'string') {
etagWithoutQuotes = fileInfo.ETag.substring(1, fileInfo.ETag.length-1);
}
if (options && options.ifMatch && (options.ifMatch !== etagWithoutQuotes)) {
return {statusCode: 412, revision: etagWithoutQuotes};
}
deleteFunction = ( fullPath.substr(-1)==='/' ? self.safeNFS.deleteDir : self.safeNFS.deleteFile );
return deleteFunction(self.token, fullPath, self.isPathShared).then(function (response){
if (response.status === 200 || response.status === 204) {
return Promise.resolve({statusCode: 200});
} else {
return Promise.reject("Delete failed: " + response.status + " (" + response.responseText + ")");
}
});
});
},
'OLDdelete': function (path, options) {
RS.log('SafeNetwork.delete(' + path + ',...)' );
var fullPath = RS.WireClient.cleanPath(PATH_PREFIX + '/' + path);
RS.log('SafeNetwork.delete: ' + fullPath + ', ...)' );
var self = this;
return self._getFileInfo(fullPath).then(function (fileInfo) {
if (!fileInfo) {
// File doesn't exist. Ignore.
return Promise.resolve({statusCode: 200});
}
var etagWithoutQuotes;
if (fileInfo.ETag === 'string') {
etagWithoutQuotes = fileInfo.ETag.substring(1, fileInfo.ETag.length-1);
}
if (options && options.ifMatch && (options.ifMatch !== etagWithoutQuotes)) {
return {statusCode: 412, revision: etagWithoutQuotes};
}
var NFStype = ( fullPath.substr(-1)==='/' ? '/nfs/directory/' : '/nfs/file/' );
var rootPath = ( self.isPathShared ? 'drive/' : 'app/' );
var fullUrl = self.launcherUrl + NFStype + rootPath + encodeURIComponent(fullPath);
var options = {
url: fullUrl,
headers: {
},
};
RS.log('SafeNetwork.delete calling _request( POST, ' + options.url + ', ...)' );
return self._request('DELETE', options.url, options).then(function (response) {
if (response.status === 200 || response.status === 204) {
return Promise.resolve({statusCode: 200});
} else {
return Promise.reject("Delete failed: " + response.status + " (" + response.responseText + ")");
}
});
});
},
// mrhTODO - replace _updateFile / _createFile with single _putFile (as POST /nfs/file now does both)
// mrhTODO contentType is ignored on update (to change it would require file delete and create before update)
_updateFile: function (path, body, contentType, options) {
RS.log('SafeNetwork._updateFile(' + path + ',...)' );
var self = this;
/* mrhTODO GoogleDrive only I think...
if ((!contentType.match(/charset=/)) &&
(encryptedData instanceof ArrayBuffer || RS.WireClient.isArrayBufferView(encryptedData))) {
contentType += '; charset=binary';
}
*/
// STORE/UPDATE FILE CONTENT (PUT) (https://maidsafe.readme.io/docs/nfs-update-file-content)
var queryParams = 'offset=0';
var rootPath = ( self.isPathShared ? 'drive/' : 'app/' );
var urlPUT = self.launcherUrl + '/nfs/file/' + rootPath + encodeURIComponent(path);//mrhTODO + '?' + queryParams;
// mrhTODO I'm not sure what header/content-type needed for encrypted data
// mrhTODO Should CT denote the type that is encrypted, or say it's encrypted?
var optionsPUT = {
url: urlPUT,
headers: {
'Content-Type': contentType
},
body: body,
};
// mrhTODO googledrive does two PUTs, one initiates resumable tx, the second sends data - review when streaming API avail
return self.safeNFS.createFile(self.token, path, body, contentType, body.length, null, self.isPathShared).then(function (response) {
// self._shareIfNeeded(path); // mrhTODO what's this? (was part of dropbox.js)
return response;
});
},
// mrhTODO - replace _updateFile / _createFile with single _putFile (as POST /nfs/file now does both)
// mrhTODO contentType is ignored on update (to change it would require file delete and create before update)
_OLDupdateFile: function (path, body, contentType, options) {
RS.log('SafeNetwork._updateFile(' + path + ',...)' );
var self = this;
/* mrhTODO GoogleDrive only I think...
if ((!contentType.match(/charset=/)) &&
(encryptedData instanceof ArrayBuffer || RS.WireClient.isArrayBufferView(encryptedData))) {
contentType += '; charset=binary';
}
*/
// STORE/UPDATE FILE CONTENT (PUT) (https://maidsafe.readme.io/docs/nfs-update-file-content)
var queryParams = 'offset=0';
var rootPath = ( self.isPathShared ? 'drive/' : 'app/' );
var urlPUT = self.launcherUrl + '/nfs/file/' + rootPath + encodeURIComponent(path);//mrhTODO + '?' + queryParams;
// mrhTODO I'm not sure what header/content-type needed for encrypted data
// mrhTODO Should CT denote the type that is encrypted, or say it's encrypted?
var optionsPUT = {
url: urlPUT,
headers: {
'Content-Type': contentType
},
body: body,
};
// mrhTODO googledrive does two PUTs, one initiates resumable tx, the second sends data - review when streaming API avail
return self._request('PUT', optionsPUT.url, optionsPUT).then(function (response) {
// self._shareIfNeeded(path); // mrhTODO what's this? (was part of dropbox.js)
return response;
});
},
_createFile: function (path, body, contentType, options) {
RS.log('SafeNetwork._createFile(' + path + ',...)' );
var self = this;
// Ensure path exists by recursively calling create on parent folder
return self._makeParentPath(path).then(function (parentPath) {
return self.safeNFS.createFile(self.token, self.path, self.body, self.contentType, self.body.length, null, self.isPathShared).then(function (response) {
// self._shareIfNeeded(path); // mrhTODO what's this?
if (response.status !== 200){
return Promise.reject( {statusCode: reponse.status} );
}
else {
RS.log("DEBUG _createFile() response.responseText: ", response.responseText);
return Promise.resolve(response);
}
});
});
},
_OLDcreateFile: function (path, body, contentType, options) {
RS.log('SafeNetwork._createFile(' + path + ',...)' );
var self = this;
// Ensure path exists by recursively calling create on parent folder
return self._makeParentPath(path).then(function (parentPath) {
/* mrhTODO GoogleDrive only I think...
if ((!contentType.match(/charset=/)) &&
(encryptedData instanceof ArrayBuffer || RS.WireClient.isArrayBufferView(encryptedData))) {
contentType += '; charset=binary';
}
*/
var fileMetadata = {
mimetype: contentType, // WireClient.put provides a mime type which we store as metadata for get
};
// CREATE/UPDATE FILE (POST) (https://maidsafe.readme.io/docs/nfsfile)
var queryParams = 'offset=0'; //mrhTODO???
var rootPath = ( self.isPathShared ? 'drive/' : 'app/' );
var urlPOST = self.launcherUrl + '/nfs/file/' + rootPath + encodeURIComponent(path);//mrhTODO + '?' + queryParams;
// var payloadPOST = {
//metadata: JSON.stringify(fileMetadata), mrhTODO: test this, with v0.4 API POST causes 400 from SAFE API
// };
var optionsPOST = {
url: urlPOST,
headers: {
'Content-Type': 'text/plain', // For POST - not related to contentType (file)
'Content-Length': body.length,// ???
//'Metadata: JSON.stringify(fileMetadata), mrhTODO: test this, with v0.4 API POST causes 400 from SAFE API
},
body: body,
};
return self._request('POST', optionsPOST.url, optionsPOST).then(function (response) {
// self._shareIfNeeded(path); // mrhTODO what's this?
if (response.status !== 200){
return Promise.reject( {statusCode: reponse.status} );
}
else {
RS.log("DEBUG _createFile() response.responseText: ", response.responseText);
return Promise.resolve(response);
}
});
});
},
// For reference see WireClient#get (wireclient.js)
_getFile: function (path, options) {
RS.log('SafeNetwork._getFile(' + path + ', ...)' );
if (! this.connected) { return Promise.reject("not connected (path: " + path + ")"); }
var revCache = this._revCache;
var self = this;
var rootPath = ( self.isPathShared ? 'drive/' : 'app/' );
var url = self.launcherUrl + '/nfs/file/' + rootPath + encodeURIComponent(path);//mrhTODO + '?' + queryParams;
// Check if file exists. Creates parent folder if that doesn't exist.
return self._getFileInfo(path).then(function (fileInfo) {
var etagWithoutQuotes;
if (fileInfo && typeof(fileInfo.ETag) === 'string') {
etagWithoutQuotes = fileInfo.ETag.substring(1, fileInfo.ETag.length-1);
}
// Request is only for changed file, so if eTag matches return "304 Not Modified"
if (options && options.ifNoneMatch && (etagWithoutQuotes === options.ifNoneMatch)) {
return Promise.resolve({statusCode: 304});
}
return self.safeNFS.getFile(self.token, self.path, self.isPathShared).then(function (response) {
var body;
var status = response.status;
if (status === 400 || status === 401) {
return Promise.resolve({statusCode: status});
}
body = response.responseText;
/* SAFE NFS API file-metadata - disabled for now:
var fileMetadata = response.getResponseHeader('file-metadata');
if (fileMetadata && fileMetadata.length() > 0){
fileMetadata = JSON.parse(fileMetadata);
}
RS.log('..file-metadata: ' + fileMetadata);
*/
var retResponse = {
statusCode: status,
body: body,
revision: etagWithoutQuotes,
};
if (fileInfo){
retResponse.contentType = fileInfo.mimetype;
// mrhTODO: This is intended to parse remotestorage JSON format objects, so when saving those
// mrhTODO: it may be necessary to set memeType in the saved file-metadata
if (retResponse.contentType.match(/^application\/json/)) {
retResponse.body = response.__parsedResponseBody__;
}
}
return Promise.resolve( retResponse );
});
});
},
// For reference see WireClient#get (wireclient.js)
_OLDgetFile: function (path, options) {
RS.log('SafeNetwork._getFile(' + path + ', ...)' );
if (! this.connected) { return Promise.reject("not connected (path: " + path + ")"); }
var revCache = this._revCache;
var self = this;
var rootPath = ( self.isPathShared ? 'drive/' : 'app/' );
var url = self.launcherUrl + '/nfs/file/' + rootPath + encodeURIComponent(path);//mrhTODO + '?' + queryParams;
// Check if file exists. Creates parent folder if that doesn't exist.
return self._getFileInfo(path).then(function (fileInfo) {
var etagWithoutQuotes;
if (fileInfo && typeof(fileInfo.ETag) === 'string') {
etagWithoutQuotes = fileInfo.ETag.substring(1, fileInfo.ETag.length-1);
}
// Request is only for changed file, so if eTag matches return "304 Not Modified"
if (options && options.ifNoneMatch && (etagWithoutQuotes === options.ifNoneMatch)) {
return Promise.resolve({statusCode: 304});
}
return self._request('GET', url, {}).then(function (response) {
var body;
var status = response.status;
if (status === 400 || status === 401) {
return Promise.resolve({statusCode: status});
}
body = response.responseText;
/* SAFE NFS API file-metadata - disabled for now:
var fileMetadata = response.getResponseHeader('file-metadata');
if (fileMetadata && fileMetadata.length() > 0){
fileMetadata = JSON.parse(fileMetadata);
}
RS.log('..file-metadata: ' + fileMetadata);
*/
var retResponse = {
statusCode: status,
body: body,
revision: etagWithoutQuotes,
};
if (fileInfo){
retResponse.contentType = fileInfo.mimetype;
// mrhTODO: This is intended to parse remotestorage JSON format objects, so when saving those
// mrhTODO: it may be necessary to set memeType in the saved file-metadata
if (retResponse.contentType.match(/^application\/json/)) {
try {
retResponse.body = JSON.parse(body);
} catch(e) {}
}
}
return Promise.resolve( retResponse );
});
});
},
// _getFolder - obtain folder listing and create parent folder(s) if absent
//
// For reference see WireClient#get (wireclient.js) summarised as follows:
// - parse JSON (spec example: https://github.com/remotestorage/spec/blob/master/release/draft-dejong-remotestorage-07.txt#L223-L235)
// - return a map of item names to item object mapping values for "Etag:", "Content-Type:" and "Content-Length:"
// - NOTE: dropbox.js only provides etag, safenetwork.js provides etag and Content-Length but not Content-Type
// - NOTE: safenetwork.js etag values are faked (ie not provided by launcher) but functionally adequate
_getFolder: function (path, options) {
RS.log('SafeNetwork._getFolder(' + path + ', ...)' );
var self = this;
// Check if folder exists. Create parent folder if parent doesn't exist
return self._getFileInfo(path).then(function (fileInfo) {
var query, fields, data, i, etagWithoutQuotes, itemsMap;
if (!fileInfo) {
return Promise.resolve({statusCode: 404});
}
// folder exists so obtain listing
RS.log('>>>>>>>>CALLING safeNFS.getDir(token, ' + path + ', isPathShared)' );
return window.safeNFS.getDir(self.token, path, self.isPathShared).then(function (resp) {
RS.log('>>>>>>HANDLING safeNFS.getDir() ... resp' + resp.responseText);
var sCode = resp.status;
// 401 - Unauthorized
// 400 - Fields are missing
//if (sCode === 400 || sCode === 401) {
if (sCode === 401) { // Unuathorized
return Promise.resolve({statusCode: sCode});
}
var listing, listingFiles, listingSubdirectories, body, mime, rev;
try{
// fetch version
body = resp.json();
// XmlHttpRequest version
// body = JSON.parse(resp.responseText);
} catch (e) {
return Promise.reject(e);
}
if (body.info) {
var folderETagWithoutQuotes = path + '-' + body.info.createdOn + '-' + body.info.modifiedOn;
RS.log('..folder eTag: ' + folderETagWithoutQuotes);
var folderMetadata;
if ( body.info.metadata ){
folderMetadata = body.info.metadata;
RS.log('..folder metadata: ' + folderMetadata);
}
listingFiles = body.files.reduce(function (m, item) {
var itemPath = path + item.name;
var metadata; // mrhTODO compact next few lines
if ( item.metadata.length > 0 ) {
metadata = JSON.parse(metadata);
}
else {
metadata = { mimetype: 'application/json' }; // mrhTODO should never be used
}
// mrhTODO: Until SAFE API supports eTags make them manually:
// mrhTODO: any ASCII char except double quote: https://tools.ietf.org/html/rfc7232#section-2.3
var eTagWithQuotes = '"' + itemPath + '-' + item.createdOn + '-' + item.modifiedOn + '-' + item.size + '"';
// Add file info to cache
var fileInfo = { // Structure members must pass sync.js#corruptServerItemsMap()
path: itemPath,
ETag: eTagWithQuotes,
'Content-Length': item.size,
mimetype: metadata.mimetype,
// mimetype: ??? // mrhTODO: "Content-Type" not yet supported
// mrhTODO: (would need to be stored alongside file content and set/updated by _createFile/_updateFile)
};
self._fileInfoCache.set(itemPath, fileInfo);
RS.log('_fileInfoCache.set(' + itemPath + ', ' + fileInfo + ')' );
m[item.name] = fileInfo;
return m;
}, {});
listingSubdirectories = body.subDirectories.reduce(function (m, item) {
var itemPath = path + item.name + '/';
// mrhTODO until SAFE API supports eTags make them manually:
// Create eTag manually (any ASCII char except double quote: https://tools.ietf.org/html/rfc7232#section-2.3)
var eTagWithQuotes = '"' + itemPath + '-' + item.createdOn + '-' + item.modifiedOn + '"';
// Add file info to cache
var fileInfo = {
path: itemPath,
etag: eTagWithQuotes,
};
self._fileInfoCache.set(itemPath, fileInfo);
RS.log('_fileInfoCache.set(' + itemPath + ', ' + fileInfo + ')' );
m[item.name + '/'] = fileInfo;
return m;
}, {});
// Merge into one listing
var listing = {};
for (var attrname in listingFiles) { listing[attrname] = listingFiles[attrname]; }
for (var attrname in listingSubdirectories) { listing[attrname] = listingSubdirectories[attrname]; }
}
RS.log('SafeNetwork._getFolder(' + path + ', ...) RESULT: lising contains ' + JSON.stringify( listing ) );
return Promise.resolve({statusCode: sCode, body: listing, meta: folderMetadata, contentType: RS_DIR_MIME_TYPE, revision: folderETagWithoutQuotes });
}, (err) => {
console.error(err);
RS.log('TEST!!! safeNFS.getDir("' + path + '") failed: ' + err)
return Promise.reject({statusCode: sCode});
});
});
},
_OLDgetFolder: function (path, options) {
RS.log('SafeNetwork._getFolder(' + path + ', ...)' );
var self = this;
// Check if folder exists. Create parent folder if parent doesn't exist
return self._getFileInfo(path).then(function (fileInfo) {
var query, fields, data, i, etagWithoutQuotes, itemsMap;
if (!fileInfo) {
return Promise.resolve({statusCode: 404});
}
// folder exists so obtain listing
var rootPath = ( self.isPathShared ? 'drive/' : 'app/' );
var url = self.launcherUrl + '/nfs/directory/' + rootPath + encodeURIComponent(path);
var revCache = self._revCache;
return self._request('GET', url, {}).then(function (resp) {
var sCode = resp.status;
// 401 - Unauthorized
// 400 - Fields are missing
//if (sCode === 400 || sCode === 401) {
if (sCode === 401) { // Unuathorized
return Promise.resolve({statusCode: sCode});
}
var listing, listingFiles, listingSubdirectories, body, mime, rev;
try{
body = JSON.parse(resp.responseText);
} catch (e) {
return Promise.reject(e);
}
if (body.info) {
var folderETagWithoutQuotes = path + '-' + body.info.createdOn + '-' + body.info.modifiedOn;
RS.log('..folder eTag: ' + folderETagWithoutQuotes);
var folderMetadata;
if ( body.info.metadata ){
folderMetadata = body.info.metadata;
RS.log('..folder metadata: ' + folderMetadata);
}
listingFiles = body.files.reduce(function (m, item) {
var itemPath = path + item.name;
var metadata; // mrhTODO compact next few lines
if ( item.metadata.length > 0 ) {
metadata = JSON.parse(metadata);
}
else {
metadata = { mimetype: 'application/json' }; // mrhTODO should never be used
}
// mrhTODO: Until SAFE API supports eTags make them manually:
// mrhTODO: any ASCII char except double quote: https://tools.ietf.org/html/rfc7232#section-2.3
var eTagWithQuotes = '"' + itemPath + '-' + item.createdOn + '-' + item.modifiedOn + '-' + item.size + '"';
// Add file info to cache
var fileInfo = { // Structure members must pass sync.js#corruptServerItemsMap()
path: itemPath,
ETag: eTagWithQuotes,
'Content-Length': item.size,
mimetype: metadata.mimetype,
// mimetype: ??? // mrhTODO: "Content-Type" not yet supported
// mrhTODO: (would need to be stored alongside file content and set/updated by _createFile/_updateFile)
};
self._fileInfoCache.set(itemPath, fileInfo);
RS.log('_fileInfoCache.set(' + itemPath + ', ' + fileInfo + ')' );
m[item.name] = fileInfo;
return m;
}, {});
listingSubdirectories = body.subDirectories.reduce(function (m, item) {
var itemPath = path + item.name + '/';
// mrhTODO until SAFE API supports eTags make them manually:
// Create eTag manually (any ASCII char except double quote: https://tools.ietf.org/html/rfc7232#section-2.3)
var eTagWithQuotes = '"' + itemPath + '-' + item.createdOn + '-' + item.modifiedOn + '"';
// Add file info to cache
var fileInfo = {
path: itemPath,
etag: eTagWithQuotes,
};
self._fileInfoCache.set(itemPath, fileInfo);
RS.log('_fileInfoCache.set(' + itemPath + ', ' + fileInfo + ')' );
m[item.name + '/'] = fileInfo;
return m;
}, {});
// Merge into one listing
var listing = {};
for (var attrname in listingFiles) { listing[attrname] = listingFiles[attrname]; }
for (var attrname in listingSubdirectories) { listing[attrname] = listingSubdirectories[attrname]; }
}
RS.log('SafeNetwork._getFolder(' + path + ', ...) RESULT: lising contains ' + JSON.stringify( listing ) );
return Promise.resolve({statusCode: sCode, body: listing, meta: folderMetadata, contentType: RS_DIR_MIME_TYPE, revision: folderETagWithoutQuotes });
});
});
},
// Ensure path exists by recursively calling _createFolder if the parent doesn't exist
_makeParentPath: function (path) {
RS.log('SafeNetwork._makeParentPath(' + path + ')' );
var parentFolder = parentPath(path);
var self = this;
return self._getFileInfo(parentFolder).then(function (parentInfo) {
if (parentInfo) {
return Promise.resolve(parentInfo);
} else {
return self._createFolder(parentFolder);
}
});
},
_createFolder: function (folderPath) {
RS.log('SafeNetwork._createFolder(' + folderPath + ')' );
var self = this;
var userMetadata = "";
// Recursively create parent folders
return self._makeParentPath(folderPath).then(function (parentInfo) {
// Parent exists so create 'folderPath'
//mrhTODO var needsMetadata = options && (options.ifMatch || (options.ifNoneMatch === '*'));
//mrhTODO the above line is not in the googledrive.js version (must be from my port of dropbox.js)
return window.safeNFS.createDir(self.token, self.folderPath, self.isPrivate, self.userMetadata, self.isPathShared).then(function (response) {
// self._shareIfNeeded(folderPath); // mrhTODO what's this? (was part of dropbox.js)
return Promise.resolve(response);
});
});
},
_OLDcreateFolder: function (folderPath) {
RS.log('SafeNetwork._createFolder(' + folderPath + ')' );
var self = this;
// Recursively create parent folders
return self._makeParentPath(folderPath).then(function (parentInfo) {
// Parent exists so create 'folderPath'
//mrhTODO var needsMetadata = options && (options.ifMatch || (options.ifNoneMatch === '*'));
//mrhTODO the above line is not in the googledrive.js version (must be from my port of dropbox.js)
// CREATE folder (POST) (https://maidsafe.readme.io/docs/nfsfolder)
var rootPath = ( self.isPathShared ? 'drive/' : 'app/' );
var urlPOST = self.launcherUrl + '/nfs/directory/' + rootPath + encodeURIComponent(folderPath);
var payloadPOST = {
// isPrivate: self.isPrivate, // mrhTODO is this needed
metadata: "",
};
var optionsPOST = {
url: urlPOST,
headers: {
'Content-Type': 'text/plain',
},
body: JSON.stringify(payloadPOST),
};
return self._request('POST', optionsPOST.url, optionsPOST).then(function (response) {
// self._shareIfNeeded(folderPath); // mrhTODO what's this? (was part of dropbox.js)
return Promise.resolve(response);
});
});
},
// _getFileInfo() - check if file exists and create parent folder if necessary
//
// Checks if the file/folder (fullPath) is in the _fileInfoCache(), and if not found
// obtains a parent folder listing to check if it exists. Causes update of _fileInfoCache
// with contents of its parent folder.
//
// If the parent folder does not exist it will be created as a side effect, so this
// function can be used to check if a file exists before creating or updating its content.
//
// RETURNS
// Promise() with
// if a file { path: string, ETag: string, 'Content-Length': number }
// if a folder { path: string, ETag: string }
// if root '/' { path: '/' ETag }
// or {} if file/folder doesn't exist
// See _getFolder() to confirm the above content values (as it creates fileInfo objects)
//
// Note:
// if the parent folder doesn't exist it will be created
//
_getFileInfo: function (fullPath) {
RS.log('SafeNetwork._getFileInfo(' + fullPath + ')' );
var self = this;
var info;
if (fullPath === '/' ) {
return Promise.resolve({ path: fullPath, ETag: 'root' }); // Dummy fileInfo to stop at "root"
} else if ((info = self._fileInfoCache.get(fullPath))) {
return Promise.resolve(info); // If cached we believe it exists
}
// Not yet cached or doesn't exist
// Load parent folder listing update _fileInfoCache.
return self._getFolder(parentPath(fullPath)).then(function () {
info = self._fileInfoCache.get(fullPath);
if (!info) {
// Doesn't exist yet so...
if (fullPath.substr(-1) === '/') { // folder, so create it
return self._createFolder(fullPath).then(function () {
return self._getFileInfo(fullPath);
});
}
}
return Promise.resolve(info); // Pass back info (null if doesn't exist)
});
},
_request: function (method, url, options) {
RS.log('REMOVE THIS!!! SafeNetwork._request(' + method + ', ' + url + ', ...)' );
var self = this;
if (! options.headers) { options.headers = {}; }
options.headers['Authorization'] = 'Bearer ' + self.token;
return RS.WireClient.request.call(self, method, url, options).then(function (xhr) {
RS.log('SafeNetwork._request() response: xhr.status is ' + xhr.status );
// Launcher responses
// 401 - Unauthorized
// 400 - Fields are missing
if (xhr && (xhr.status === 400 || xhr.status == 401) ) {
return Promise.reject({statusCode: xhr.status});
} else {
return Promise.resolve(xhr);
}
});
}
};
// mrhTODO see dropbox version - probably need to modify this in line with that (check with RS team)
// differences are:
// 1) config.clientId not present
// 2) it uses hookIt() (and in _rs_cleanup() unHookIt()) instead of inline assignements
// which causes dropbox version to also call hookSync() and hookGetItemURL()
RS.SafeNetwork._rs_init = function (remoteStorage) {
var config = remoteStorage.apiKeys.safenetwork;
if (config) {
remoteStorage.safenetwork = new RS.SafeNetwork(remoteStorage, config.clientId);
if (remoteStorage.backend === 'safenetwork') {
remoteStorage._origRemote = remoteStorage.remote;
remoteStorage.remote = remoteStorage.safenetwork;
}
}
};
RS.SafeNetwork._rs_supported = function (rs) {
return true;
};
// mrhTODO see dropbox version
RS.SafeNetwork._rs_cleanup = function (remoteStorage) {
remoteStorage.setBackend(undefined);
if (remoteStorage._origRemote) {
remoteStorage.remote = remoteStorage._origRemote;
delete remoteStorage._origRemote;
}
};
})(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment