-
Notifications
You must be signed in to change notification settings - Fork 2
/
index.js
159 lines (144 loc) · 4.26 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
var mkdirp = require('mkdirp')
var through = require('through')
var crypto = require('crypto')
var path = require('path')
var fs = require('graceful-fs')
function createHashers (alg) {
var createHash
= 'string' === typeof alg
? crypto.createHash.bind(null, alg)
: alg
return {
shastream: function (enc) {
var hash = createHash()
return through(function (data) {
hash.update(data, enc)
this.push(data)
}, function () {
this.hash = hash.digest('hex')
this.push(null)
}, {autoDestroy: false})
},
shasum: function (data, enc) {
return createHash().update(data, enc).digest('hex')
}
}
}
module.exports = function (dir, alg) {
var h = createHashers(alg || 'sha256')
//construct a regexp to test hex strings of the correct length.
var rxHash = new RegExp('^[0-9a-f]{' + h.shasum('', 'utf8').length + '}$')
if(!dir)
throw new Error('content-addressable-store needs a directory to work in')
var init = false, db
function toPath (hash) {
return path.join(dir, hash.substring(0, 2), hash.substring(2))
}
function randomPath () {
return path.join(dir, 'tmp', Date.now() + '-' + Math.random().toString().substring(2))
}
//make sure the /tmp directory exists (first write only)
function prepare (stream, ready) {
if(init) return ready()
stream.pause()
mkdirp(path.join(dir, 'tmp'), function (err) {
if(err) return stream.emit('error', err)
init = true; ready(); stream.resume()
})
}
function writeFile (filename, data, enc, cb) {
if(!init)
mkdirp(path.dirname(filename), function (err) {
if(err) return cb(err)
init = true
fs.writeFile(filename, data, enc, cb)
})
else
fs.writeFile(filename, data, enc, cb)
}
return db = {
get: function (hash, opts, cb) {
if(!cb) cb = opts, opts = null
fs.readFile(toPath(hash), opts && opts.encoding, cb)
},
add: function (data, opts, cb) {
if(!cb) cb = opts, opts = null
var encoding = opts && opts.encoding
var hash = h.shasum(data, encoding)
var tmpfile = randomPath(), target = toPath(hash)
db.has(hash, function (err) {
//this is already in the database
if(!err) return cb(null, hash, true)
writeFile(tmpfile, data, encoding, function (err) {
if(err) return cb(err)
mkdirp(path.dirname(target), function (err) {
if(err) return cb(err)
fs.rename(tmpfile, target, function (err) {
cb(err, hash)
})
})
})
})
},
addStream: function (opts) {
var enc = opts && opts.encoding
var stream = h.shastream(enc)
prepare(stream, function () {
var tmpfile = randomPath()
var closed = false
stream
.pipe(fs.createWriteStream(tmpfile))
.on('error', function (err) {
stream.emit('error', err)
})
.on('close', onClose)
function onClose () {
if(closed) return
closed = true
var target = toPath(stream.hash)
mkdirp(path.dirname(target), function (err) {
if(err) return stream.emit('error', err)
fs.rename(tmpfile, target, function (err) {
if(err) return stream.emit('error', err)
stream.emit('finish')
stream.emit('close')
})
})
}
})
return stream
},
getStream: function (hash) {
return fs.createReadStream(toPath(hash))
},
createStream: function (hash) {
return this.getStream(hash)
},
has: function (hash, cb) {
fs.stat(toPath(hash), cb)
},
all: function (cb) {
var all = [], n = 0
fs.readdir(dir, function (err, ls) {
if(err) return done(err)
n = ls.length
ls.forEach(function (d) {
fs.readdir(path.join(dir, d), function (err, ls) {
ls.forEach(function (e) {
all.push(d + e)
})
done()
})
})
})
function done (err) {
if(err && n >= 0) return n = -1, cb(err)
if(--n) return
cb(null, all.sort())
}
},
isHash: function (string) {
return rxHash.test(string)
}
}
}