UNPKG

caslet

Version:

A simple content addressable system

122 lines (119 loc) 3.42 kB
loglet = require 'loglet' fs = require 'fs' path = require 'path' crypto = require 'crypto' uuid = require 'node-uuid' funclet = require 'funclet' filelet = require 'filelet' _ = require 'underscore' class FSCas @initialize: (options, cb) -> defaultOpts = baseDir: path.join process.env.HOME, '.caslet' options = _.extend {}, defaultOpts, options cas = new @ options cas.initialize cb constructor: (@options) -> @pathsDir = path.join @options.baseDir, 'paths' @contentsDir = path.join @options.baseDir, 'contents' @tempDir = path.join @options.baseDir, 'temp' # for moving the files... @cache = {} initialize: (cb) -> funclet .each [ @pathsDir, @contentsDir , @tempDir ], (dirPath, next) -> filelet.mkdirp dirPath, next .catch(cb) .done () => cb null, @ get: (option, cb) -> if option.path @_getViaPath option.path, cb else if option.hash @_getViaHash option.hash, cb else cb {error: 'invalid_argument', args: [ option ]} _getViaPath: (pathsPath, cb) -> if @cache.hasOwnProperty(pathsPath) @_getViaHash @cache[pathsPath].hash, cb else funclet .start (next) => normalized = @_pathsFilePath pathsPath fs.readFile normalized, 'utf8', next .catch(cb) .done (data) => parsed = JSON.parse data @cache[pathsPath] = parsed @_getViaHash parsed.hash, cb _getViaHash: (hash, cb) -> try hashPath = @_hashFilePath hash stream = fs.createReadStream hashPath cb null, stream catch e cb e store: (instream, cb) -> try tempFilePath = @_tempFilePath() digest = crypto.createHash 'sha1' digest.setEncoding 'hex' outstream = fs.createWriteStream tempFilePath instream.once 'close', () => try digest.end() outstream.end() hash = digest.read() instream.close() outstream.close() @_moveTempFile tempFilePath, hash, cb catch e cb e instream.once 'error', cb outstream.once 'error', cb digest.once 'error', cb instream.pipe digest instream.pipe outstream catch e instream.close() outstream.close() cb e storeWithPath: (instream, destPath, cb) -> hash = null funclet .start (next) => @store instream, next .then (val, next) => hash = val @_storePathMapping hash, destPath, next .catch(cb) .done () -> cb null, hash _storePathMapping: (hash, destPath, cb) -> normalizedPath = @_pathsFilePath(destPath) result = type: 'fs' hash: hash filelet.writeFile normalizedPath, JSON.stringify(result, null, 2), 'utf8', (err) => if err cb err else @cache[destPath] = result cb null _moveTempFile: (tempFilePath, hash, cb) -> try hashPath = @_hashFilePath hash filelet.move tempFilePath, hashPath, (err) -> if err cb err else cb null, hash catch e cb e _tempFilePath: () -> path.join @tempDir, uuid.v4() _pathsFilePath: (filePath) -> path.join @pathsDir, filePath _hashFilePath: (hash) -> parentDir = hash.substring(0, 3) fileName = hash.substring(3) path.join @contentsDir, parentDir, fileName module.exports = FSCas