Skip to content

Commit 067b5f6

Browse files
authored
feat(copy): added cacache.get.copy api for fast copies (#107)
1 parent cc0fd2f commit 067b5f6

File tree

9 files changed

+135
-2
lines changed

9 files changed

+135
-2
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
language: node_js
22
sudo: false
33
node_js:
4-
- "7"
4+
- "8"
55
- "6"
66
- "4"

appveyor.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
environment:
22
matrix:
3-
- nodejs_version: "7"
3+
- nodejs_version: "8"
44
- nodejs_version: "6"
55
- nodejs_version: "4"
66

get.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const BB = require('bluebird')
44

5+
const fs = require('fs')
56
const index = require('./lib/entry-index')
67
const memo = require('./lib/memoization')
78
const pipe = require('mississippi').pipe
@@ -152,3 +153,38 @@ function info (cache, key, opts) {
152153
}
153154

154155
module.exports.hasContent = read.hasContent
156+
157+
module.exports.copy = function cp (cache, key, dest, opts) {
158+
return copy(false, cache, key, dest, opts)
159+
}
160+
module.exports.copy.byDigest = function cpDigest (cache, digest, dest, opts) {
161+
return copy(true, cache, digest, dest, opts)
162+
}
163+
function copy (byDigest, cache, key, dest, opts) {
164+
opts = opts || {}
165+
if (read.copy) {
166+
return (
167+
byDigest ? BB.resolve(null) : index.find(cache, key, opts)
168+
).then(entry => {
169+
if (!entry && !byDigest) {
170+
throw new index.NotFoundError(cache, key)
171+
}
172+
return read.copy(
173+
cache, byDigest ? key : entry.integrity, dest, opts
174+
).then(() => byDigest ? key : {
175+
metadata: entry.metadata,
176+
size: entry.size,
177+
integrity: entry.integrity
178+
})
179+
})
180+
} else {
181+
return getData(byDigest, cache, key, opts).then(res => {
182+
return fs.writeFileAsync(dest, byDigest ? res : res.data)
183+
.then(() => byDigest ? key : {
184+
metadata: res.metadata,
185+
size: res.size,
186+
integrity: res.integrity
187+
})
188+
})
189+
}
190+
}

lib/content/read.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,18 @@ function readStream (cache, integrity, opts) {
5252
return stream
5353
}
5454

55+
if (fs.copyFile) {
56+
module.exports.copy = copy
57+
}
58+
function copy (cache, integrity, dest, opts) {
59+
opts = opts || {}
60+
return pickContentSri(cache, integrity).then(content => {
61+
const sri = content.sri
62+
const cpath = contentPath(cache, sri)
63+
return fs.copyFileAsync(cpath, dest).then(() => content.size)
64+
})
65+
}
66+
5567
module.exports.hasContent = hasContent
5668
function hasContent (cache, integrity) {
5769
if (!integrity) { return BB.resolve(false) }

locales/en.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ x.get = (cache, key, opts) => get(cache, key, opts)
2020
x.get.byDigest = (cache, hash, opts) => get.byDigest(cache, hash, opts)
2121
x.get.stream = (cache, key, opts) => get.stream(cache, key, opts)
2222
x.get.stream.byDigest = (cache, hash, opts) => get.stream.byDigest(cache, hash, opts)
23+
x.get.copy = (cache, key, dest, opts) => get.copy(cache, key, dest, opts)
24+
x.get.copy.byDigest = (cache, hash, dest, opts) => get.copy.byDigest(cache, hash, dest, opts)
2325
x.get.info = (cache, key) => get.info(cache, key)
2426
x.get.hasContent = (cache, hash) => get.hasContent(cache, hash)
2527

locales/es.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ x.saca = (cache, clave, ops) => get(cache, clave, ops)
2020
x.saca.porHacheo = (cache, hacheo, ops) => get.byDigest(cache, hacheo, ops)
2121
x.saca.flujo = (cache, clave, ops) => get.stream(cache, clave, ops)
2222
x.saca.flujo.porHacheo = (cache, hacheo, ops) => get.stream.byDigest(cache, hacheo, ops)
23+
x.sava.copia = (cache, clave, destino, opts) => get.copy(cache, clave, destino, opts)
24+
x.sava.copia.porHacheo = (cache, hacheo, destino, opts) => get.copy.byDigest(cache, hacheo, destino, opts)
2325
x.saca.info = (cache, clave) => get.info(cache, clave)
2426
x.saca.tieneDatos = (cache, hacheo) => get.hasContent(cache, hacheo)
2527

test/benchmarks/content.read.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
'use strict'
22

3+
const BB = require('bluebird')
4+
35
const CacheContent = require('../util/cache-content')
6+
const fs = BB.promisifyAll(require('fs'))
7+
const path = require('path')
48
const Tacks = require('tacks')
59
const ssri = require('ssri')
610

@@ -58,6 +62,32 @@ module.exports = (suite, CACHE) => {
5862
}
5963
})
6064

65+
suite.add('content.read.copy()', {
66+
defer: true,
67+
setup () {
68+
const fixture = new Tacks(CacheContent({
69+
[BIGINTEGRITY]: BIGCONTENT
70+
}))
71+
fixture.create(CACHE)
72+
},
73+
fn (deferred) {
74+
if (read.copy) {
75+
read.copy(CACHE, BIGINTEGRITY, path.join(CACHE, 'bigdata'))
76+
.then(
77+
() => deferred.resolve(),
78+
err => deferred.reject(err)
79+
)
80+
} else {
81+
read(CACHE, BIGINTEGRITY)
82+
.then(data => fs.writeFileAsync(path.join(CACHE, 'bigdata'), data))
83+
.then(
84+
() => deferred.resolve(),
85+
err => deferred.reject(err)
86+
)
87+
}
88+
}
89+
})
90+
6191
suite.add('content.read.stream() small data', {
6292
defer: true,
6393
setup () {

test/content.read.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ const Buffer = require('safe-buffer').Buffer
44
const BB = require('bluebird')
55

66
const finished = BB.promisify(require('mississippi').finished)
7+
const fs = require('fs')
78
const path = require('path')
89
const ssri = require('ssri')
910
const Tacks = require('tacks')
1011
const test = require('tap').test
1112
const testDir = require('./util/test-dir')(__filename)
1213

14+
BB.promisifyAll(fs)
15+
1316
const CACHE = path.join(testDir, 'cache')
1417
const CacheContent = require('./util/cache-content')
1518

@@ -147,3 +150,20 @@ test('hasContent: returns { sri, size } when a cache file exists', function (t)
147150
})
148151
)
149152
})
153+
154+
test('copy: copies content to a destination path', {
155+
skip: !fs.copyFile && 'Not supported on node versions without fs.copyFile'
156+
}, t => {
157+
const CONTENT = Buffer.from('foobarbaz')
158+
const INTEGRITY = ssri.fromData(CONTENT)
159+
const DEST = path.join(CACHE, 'foobar-file')
160+
const fixture = new Tacks(CacheContent({
161+
[INTEGRITY]: CONTENT
162+
}))
163+
fixture.create(CACHE)
164+
return read.copy(CACHE, INTEGRITY, DEST).then(() => {
165+
return fs.readFileAsync(DEST)
166+
}).then(data => {
167+
t.deepEqual(data, CONTENT, 'file successfully copied')
168+
})
169+
})

test/get.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const Buffer = require('safe-buffer').Buffer
44
const BB = require('bluebird')
55

66
const finished = BB.promisify(require('mississippi').finished)
7+
const fs = require('fs')
78
const index = require('../lib/entry-index')
89
const memo = require('../lib/memoization')
910
const path = require('path')
@@ -13,6 +14,8 @@ const test = require('tap').test
1314
const testDir = require('./util/test-dir')(__filename)
1415
const ssri = require('ssri')
1516

17+
BB.promisifyAll(fs)
18+
1619
const CacheContent = require('./util/cache-content')
1720

1821
const CACHE = path.join(testDir, 'cache')
@@ -104,6 +107,34 @@ test('basic stream get', t => {
104107
})
105108
})
106109

110+
test('get.copy', t => {
111+
const DEST = path.join(CACHE, 'copymehere')
112+
const fixture = new Tacks(CacheContent({
113+
[INTEGRITY]: CONTENT
114+
}))
115+
fixture.create(CACHE)
116+
return index.insert(CACHE, KEY, INTEGRITY, opts())
117+
.then(() => get.copy(CACHE, KEY, DEST))
118+
.then(res => {
119+
t.deepEqual(res, {
120+
metadata: METADATA,
121+
integrity: INTEGRITY,
122+
size: SIZE
123+
}, 'copy operation returns basic metadata')
124+
return fs.readFileAsync(DEST)
125+
})
126+
.then(data => {
127+
t.deepEqual(data, CONTENT, 'data copied by key matches')
128+
return rimraf(DEST)
129+
})
130+
.then(() => get.copy.byDigest(CACHE, INTEGRITY, DEST))
131+
.then(() => fs.readFileAsync(DEST))
132+
.then(data => {
133+
t.deepEqual(data, CONTENT, 'data copied by digest matches')
134+
return rimraf(DEST)
135+
})
136+
})
137+
107138
test('ENOENT if not found', t => {
108139
return get(CACHE, KEY).then(() => {
109140
throw new Error('lookup should fail')

0 commit comments

Comments
 (0)