remotestorage/remotestorage-server

View on GitHub
lib/main.js

Summary

Maintainability
D
2 days
Test Coverage
var sha1 = require('sha1');

module.exports = function(dataStore) {
  this.dataStore = dataStore;
};

module.exports.prototype.condMet = function(cond, username, path, cb) {
  this.getRevision(username, path, function(err, revision) {
    var i, found;
    if (err) {
      cb(err);
      return;
    }
    if (cond.ifNoneMatch === '*') {//if-none-match is either '*'...
      if (typeof(revision) !== 'undefined') {
        cb(err, false);
        return;
      }
    } else if (cond.ifNoneMatch && typeof(revision) !== 'undefined') {//or a comma-separated list of etags
      for (i=0; i<cond.ifNoneMatch.length; i++) {
        if(cond.ifNoneMatch[i].toString('utf-8') === revision.toString('utf-8')) {
          cb(err, false);
          return;
        }
      }
    }
    if (cond.ifMatch) {
      if (Buffer.isBuffer(revision)) {
        for (i=0; i<cond.ifMatch.length; i++) {
          if (cond.ifMatch[i].toString('utf-8') === revision.toString('utf-8')) {
            found = true;
          }
        }
      }
      if (!found) {
        cb(err, false);
        return;
      }
    }
    cb(err, true);
  }.bind(this));
};
module.exports.prototype.revisionsToMap = function(revisions, username, path, cb) {
  var i, items = {}, todo = 1, error, errorSent;
  function doneOne() {
    if (error) {
      if (!errorSent) {
        cb(error);
        errorSent = true;
      }
      return;
    }
    todo--;
    if (todo === 0) {
      cb(null, {
        '@context': 'http://remotestorage.io/spec/folder-description',
        items: items
      });
    }
  }
  for (i in revisions) {
    items[i] = { ETag: revisions[i] };
    items[i] = { ETag: revisions[i].toString() };
    if (i.substr(-1) !== '/') {
      todo++;
      (function(captureI) {
        this.getContentType(username, path + captureI, function(err, value) {
          if (err) {
            error = err;
            return;
          }
          items[captureI]['Content-Type'] = value.toString('utf-8');
          doneOne.bind(this)();
        });
      }.bind(this))(i);
      todo++;
      (function(captureI) {
        this.getContentLength(username, path + captureI, function(err, value) {
          if (err) {
            error = err;
            return;
          }
          items[captureI]['Content-Length'] = value;
          doneOne();
        });
      }.bind(this))(i);
    }
  }
  doneOne();
};
module.exports.prototype.getFolderDescription = function(username, path, folderFormat, cb) {
  this.getContent(username, path, function(err, content) {
    var i, itemNames, revisions = {}, todo = 1, error, errorSent;
    if (content) {
      try {
        itemNames = JSON.parse(content);
      } catch(e) {
        cb(e);
        return;
      }
    } else {//empty folder
      if (folderFormat === 'map') {
        this.revisionsToMap({}, username, path, cb);
      } else {
        cb(err, {});
      }
      return;
    }
    function doneOne() {
      if (error) {
        if (!errorSent) {
          cb(error);
          errorSent = true;
        }
        return;
      }
      todo--;
      if (todo === 0) {
        if (folderFormat === 'map') {
          this.revisionsToMap(revisions, username, path, cb);
        } else {
          cb(err, revisions);
        }
      }
    }
    for (i in itemNames) {
      todo++;
      (function(captureI) {
        this.getRevision(username, path+captureI, function(err, revision) {
          if (err) {
            error = err;
            return;
          }
          revisions[captureI] = revision.toString('utf-8');
          doneOne.bind(this)();
        }.bind(this));
      }.bind(this))(i);
    }
    doneOne.bind(this)();
  }.bind(this));
};
module.exports.prototype.exists = function(username, path, cb) {
  this.dataStore.get(username, 'revision:' + path, function(err, content) {
    if (err) {
      cb(err);
      return;
    }
    cb(err, (typeof(content) !== 'undefined'));
  });
};
module.exports.prototype.getContent = function(username, path, cb) {
  this.dataStore.get(username, 'content:' + path, cb);
};
module.exports.prototype.getContentType = function(username, path, cb) {
  this.dataStore.get(username, 'contentType:' + path, cb);
};
module.exports.prototype.getContentLength = function(username, path, cb) {
  this.dataStore.get(username, 'content:' + path, function(err, buf) {
    if (err) {
      cb(err);
      return;
    }
    if (buf) {
      cb(err, buf.length);
    } else {
      cb(err);
    }
  });
};
module.exports.prototype.getRevision = function(username, path, cb) {
  this.dataStore.get(username, 'revision:' + path, function(err, revision) {
    if (err) {
      cb(err);
      return;
    }
    if (revision) {
      cb(err, revision);
    } else if (path.substr(-1) === '/') {
      cb(err, new Buffer('empty-folder', 'utf-8'));
    } else {
      cb(err);
    }
  });
};
module.exports.prototype.setParents = function(username, pathParts, fileItself, revision, cb) {
  if (pathParts.length <= 1) {
    cb();
  } else {
    var thisPart = pathParts.pop();
    if(fileItself) {
      fileItself = false;
    } else {
      thisPart += '/';
    }
    this.dataStore.get(username, 'content:' + pathParts.join('/') + '/', function(err1, buf) {
      var obj;
      if (err1) {
        cb(err1);
        return;
      }
      if(buf === undefined) {
        obj = {};
      } else {
        try {
          obj = JSON.parse(buf.toString('utf-8'));
        } catch(e) {
          cb(e);
          return;
        }
      }
      if (revision) {
        //add
        obj[thisPart] = revision;//we need to store the revision here so that it changes the parent revision
      } else {
        //remove
        delete obj[thisPart];
      }
      this.dataStore.set(username, 'content:' + pathParts.join('/') + '/', new Buffer(JSON.stringify(obj), 'utf-8'), function(err2) {
        if (err2) {
          cb(err2);
          return;
        }
        this.dataStore.set(username, 'revision:' + pathParts.join('/') + '/', new Buffer(sha1(JSON.stringify(obj)), 'utf-8'), function(err3) {
          if (err3) {
            cb(err3);
            return;
          }
          this.setParents(username, pathParts, false, revision, cb);
        }.bind(this));
      }.bind(this));
    }.bind(this));
  }
};
module.exports.prototype.set = function(username, path, dataBuf, contentType, revision, cb) {
  this.dataStore.set(username, 'content:' + path, dataBuf, function(err1) {
    if (err1) {
      cb(err1);
      return;
    }
    this.dataStore.set(username, 'contentType:' + path, contentType, function(err2) {
      if (err2) {
        cb(err2);
        return;
      }
      this.dataStore.set(username, 'revision:' + path, revision, function(err3) {
        if (err3) {
          cb(err3);
          return;
        }
        var pathParts = path.split('/');
        var fileItself = true;
        this.setParents(username, pathParts, true, revision, cb);
      }.bind(this));
    }.bind(this));
  }.bind(this));
};