source: trip-planner-front/node_modules/cacache/lib/content/write.js@ ceaed42

Last change on this file since ceaed42 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 5.0 KB
Line 
1'use strict'
2
3const util = require('util')
4
5const contentPath = require('./path')
6const fixOwner = require('../util/fix-owner')
7const fs = require('fs')
8const moveFile = require('../util/move-file')
9const Minipass = require('minipass')
10const Pipeline = require('minipass-pipeline')
11const Flush = require('minipass-flush')
12const path = require('path')
13const rimraf = util.promisify(require('rimraf'))
14const ssri = require('ssri')
15const uniqueFilename = require('unique-filename')
16const { disposer } = require('./../util/disposer')
17const fsm = require('fs-minipass')
18
19const writeFile = util.promisify(fs.writeFile)
20
21module.exports = write
22
23function write (cache, data, opts = {}) {
24 const { algorithms, size, integrity } = opts
25 if (algorithms && algorithms.length > 1)
26 throw new Error('opts.algorithms only supports a single algorithm for now')
27
28 if (typeof size === 'number' && data.length !== size)
29 return Promise.reject(sizeError(size, data.length))
30
31 const sri = ssri.fromData(data, algorithms ? { algorithms } : {})
32 if (integrity && !ssri.checkData(data, integrity, opts))
33 return Promise.reject(checksumError(integrity, sri))
34
35 return disposer(makeTmp(cache, opts), makeTmpDisposer,
36 (tmp) => {
37 return writeFile(tmp.target, data, { flag: 'wx' })
38 .then(() => moveToDestination(tmp, cache, sri, opts))
39 })
40 .then(() => ({ integrity: sri, size: data.length }))
41}
42
43module.exports.stream = writeStream
44
45// writes proxied to the 'inputStream' that is passed to the Promise
46// 'end' is deferred until content is handled.
47class CacacheWriteStream extends Flush {
48 constructor (cache, opts) {
49 super()
50 this.opts = opts
51 this.cache = cache
52 this.inputStream = new Minipass()
53 this.inputStream.on('error', er => this.emit('error', er))
54 this.inputStream.on('drain', () => this.emit('drain'))
55 this.handleContentP = null
56 }
57
58 write (chunk, encoding, cb) {
59 if (!this.handleContentP) {
60 this.handleContentP = handleContent(
61 this.inputStream,
62 this.cache,
63 this.opts
64 )
65 }
66 return this.inputStream.write(chunk, encoding, cb)
67 }
68
69 flush (cb) {
70 this.inputStream.end(() => {
71 if (!this.handleContentP) {
72 const e = new Error('Cache input stream was empty')
73 e.code = 'ENODATA'
74 // empty streams are probably emitting end right away.
75 // defer this one tick by rejecting a promise on it.
76 return Promise.reject(e).catch(cb)
77 }
78 this.handleContentP.then(
79 (res) => {
80 res.integrity && this.emit('integrity', res.integrity)
81 res.size !== null && this.emit('size', res.size)
82 cb()
83 },
84 (er) => cb(er)
85 )
86 })
87 }
88}
89
90function writeStream (cache, opts = {}) {
91 return new CacacheWriteStream(cache, opts)
92}
93
94function handleContent (inputStream, cache, opts) {
95 return disposer(makeTmp(cache, opts), makeTmpDisposer, (tmp) => {
96 return pipeToTmp(inputStream, cache, tmp.target, opts)
97 .then((res) => {
98 return moveToDestination(
99 tmp,
100 cache,
101 res.integrity,
102 opts
103 ).then(() => res)
104 })
105 })
106}
107
108function pipeToTmp (inputStream, cache, tmpTarget, opts) {
109 let integrity
110 let size
111 const hashStream = ssri.integrityStream({
112 integrity: opts.integrity,
113 algorithms: opts.algorithms,
114 size: opts.size,
115 })
116 hashStream.on('integrity', i => {
117 integrity = i
118 })
119 hashStream.on('size', s => {
120 size = s
121 })
122
123 const outStream = new fsm.WriteStream(tmpTarget, {
124 flags: 'wx',
125 })
126
127 // NB: this can throw if the hashStream has a problem with
128 // it, and the data is fully written. but pipeToTmp is only
129 // called in promisory contexts where that is handled.
130 const pipeline = new Pipeline(
131 inputStream,
132 hashStream,
133 outStream
134 )
135
136 return pipeline.promise()
137 .then(() => ({ integrity, size }))
138 .catch(er => rimraf(tmpTarget).then(() => {
139 throw er
140 }))
141}
142
143function makeTmp (cache, opts) {
144 const tmpTarget = uniqueFilename(path.join(cache, 'tmp'), opts.tmpPrefix)
145 return fixOwner.mkdirfix(cache, path.dirname(tmpTarget)).then(() => ({
146 target: tmpTarget,
147 moved: false,
148 }))
149}
150
151function makeTmpDisposer (tmp) {
152 if (tmp.moved)
153 return Promise.resolve()
154
155 return rimraf(tmp.target)
156}
157
158function moveToDestination (tmp, cache, sri, opts) {
159 const destination = contentPath(cache, sri)
160 const destDir = path.dirname(destination)
161
162 return fixOwner
163 .mkdirfix(cache, destDir)
164 .then(() => {
165 return moveFile(tmp.target, destination)
166 })
167 .then(() => {
168 tmp.moved = true
169 return fixOwner.chownr(cache, destination)
170 })
171}
172
173function sizeError (expected, found) {
174 const err = new Error(`Bad data size: expected inserted data to be ${expected} bytes, but got ${found} instead`)
175 err.expected = expected
176 err.found = found
177 err.code = 'EBADSIZE'
178 return err
179}
180
181function checksumError (expected, found) {
182 const err = new Error(`Integrity check failed:
183 Wanted: ${expected}
184 Found: ${found}`)
185 err.code = 'EINTEGRITY'
186 err.expected = expected
187 err.found = found
188 return err
189}
Note: See TracBrowser for help on using the repository browser.