@@ -5,7 +5,8 @@ import { join, split } from './path'
5
5
import RemoteHandlerAbstract from './abstract'
6
6
import { pRetry } from 'promise-toolbox'
7
7
import { asyncEach } from '@vates/async-each'
8
- import { readChunkStrict } from '@vates/read-chunk'
8
+ import { readChunk } from '@vates/read-chunk'
9
+ import copyStreamToBuffer from './_copyStreamToBuffer'
9
10
10
11
createLogger ( 'xo:fs:azure' )
11
12
const MAX_BLOCK_SIZE = 1024 * 1024 * 4000 // 4000 MiB
@@ -74,66 +75,47 @@ export default class AzureHandler extends RemoteHandlerAbstract {
74
75
return ( value ?. segment ?. blobItems ?. length ?? 0 ) > 0
75
76
}
76
77
77
- async #streamToBuffer( readableStream , buffer ) {
78
- return new Promise ( ( resolve , reject ) => {
79
- let bytesRead = 0
80
-
81
- readableStream . on ( 'data' , data => {
82
- if ( bytesRead + data . length <= buffer . length ) {
83
- data . copy ( buffer , bytesRead )
84
- bytesRead += data . length
85
- } else {
86
- reject ( new Error ( 'Buffer size exceeded' ) )
87
- }
88
- } )
89
- readableStream . on ( 'end' , ( ) => {
90
- resolve ( bytesRead )
91
- } )
92
- readableStream . on ( 'error' , reject )
93
- } )
94
- }
95
-
96
78
async _sync ( ) {
97
79
await this . #containerClient. createIfNotExists ( )
98
80
await super . _sync ( )
99
81
}
100
82
101
83
async _outputStream ( path , input , { streamLength, maxStreamLength = streamLength , validator } ) {
84
+ const blobClient = this . #containerClient. getBlockBlobClient ( path )
102
85
let blockSize
103
-
104
86
if ( maxStreamLength === undefined ) {
105
87
warn (
106
88
`Writing ${ path } to a azure blob storage without a max size set will cut it to ${ MAX_BLOCK_COUNT * MAX_BLOCK_SIZE } bytes` ,
107
89
{ path }
108
90
)
109
91
blockSize = MIN_BLOCK_SIZE
110
- } else if ( maxStreamLength < MIN_BLOCK_SIZE ) {
111
- await this . _writeFile ( path , input )
112
- return
113
92
} else {
114
93
const minBlockSize = Math . ceil ( maxStreamLength / MAX_BLOCK_COUNT ) // Minimal possible block size for the block count allowed
115
94
const adjustedMinSize = Math . max ( minBlockSize , MIN_BLOCK_SIZE ) // Block size must be larger than minimum block size allowed
116
95
blockSize = Math . min ( adjustedMinSize , MAX_BLOCK_SIZE ) // Block size must be smaller than max block size allowed
117
96
}
118
97
119
- const blobClient = this . #containerClient. getBlockBlobClient ( path )
120
- const blockCount = Math . ceil ( streamLength / blockSize )
121
-
122
- if ( blockCount > MAX_BLOCK_COUNT ) {
123
- throw new Error ( `File too large. Maximum upload size is ${ MAX_BLOCK_SIZE * MAX_BLOCK_COUNT } bytes` )
124
- }
125
-
126
98
const blockIds = [ ]
99
+ let blockIndex = 0
100
+ let done = false
127
101
128
- for ( let i = 0 ; i < blockCount ; i ++ ) {
129
- const blockId = Buffer . from ( i . toString ( ) . padStart ( 6 , '0' ) ) . toString ( 'base64' )
130
- const chunk = await readChunkStrict ( input , blockSize )
131
- await blobClient . stageBlock ( blockId , chunk , chunk . length )
102
+ while ( ! done ) {
103
+ if ( ! input . readable ) {
104
+ input . readableEnded = true
105
+ break
106
+ }
107
+ const chunk = await readChunk ( input , blockSize )
108
+ if ( ! chunk || chunk . length === 0 ) {
109
+ done = true
110
+ break
111
+ }
112
+
113
+ const blockId = Buffer . from ( blockIndex . toString ( ) . padStart ( 6 , '0' ) ) . toString ( 'base64' )
132
114
blockIds . push ( blockId )
115
+ await blobClient . stageBlock ( blockId , chunk , chunk . length )
116
+ blockIndex ++
133
117
}
134
-
135
118
await blobClient . commitBlockList ( blockIds )
136
-
137
119
if ( validator !== undefined ) {
138
120
try {
139
121
await validator . call ( this , path )
@@ -143,6 +125,7 @@ export default class AzureHandler extends RemoteHandlerAbstract {
143
125
}
144
126
}
145
127
}
128
+
146
129
// list blobs in container
147
130
async _list ( path , options = { } ) {
148
131
const { ignoreMissing = false } = options
@@ -169,19 +152,9 @@ export default class AzureHandler extends RemoteHandlerAbstract {
169
152
}
170
153
171
154
// uploads a file to a blob
172
- async _outputFile ( file , data ) {
155
+ async _writeFile ( file , data ) {
173
156
const blobClient = this . #containerClient. getBlockBlobClient ( file )
174
- if ( await blobClient . exists ( ) ) {
175
- const error = new Error ( `EEXIST: file already exists, mkdir '${ file } '` )
176
- error . code = 'EEXIST'
177
- error . path = file
178
- throw error
179
- }
180
- if ( data . length > MAX_BLOCK_SIZE ) {
181
- await this . _outputStream ( file , data )
182
- } else {
183
- await blobClient . upload ( data , data . length )
184
- }
157
+ await blobClient . upload ( data , data . length )
185
158
}
186
159
187
160
async _createReadStream ( file ) {
@@ -215,7 +188,7 @@ export default class AzureHandler extends RemoteHandlerAbstract {
215
188
}
216
189
}
217
190
}
218
- // the thrown error only worked when I renamed the function from _rename to rename due to how it's being called in abstract.js
191
+
219
192
async rename ( oldPath , newPath ) {
220
193
await this . _copy ( oldPath , newPath )
221
194
await this . _unlink ( oldPath )
@@ -256,17 +229,8 @@ export default class AzureHandler extends RemoteHandlerAbstract {
256
229
}
257
230
try {
258
231
const blobClient = this . #containerClient. getBlobClient ( file )
259
- const blobSize = ( await blobClient . getProperties ( ) ) . contentLength
260
- if ( blobSize === 0 ) {
261
- console . warn ( `Blob is empty: ${ file } ` )
262
- return { bytesRead : 0 , buffer }
263
- }
264
- if ( position >= blobSize ) {
265
- throw new Error ( `Requested range starts beyond blob size: ${ blobSize } ` )
266
- }
267
-
268
- const downloadResponse = await blobClient . download ( position , Math . min ( blobSize - position , buffer . length ) )
269
- const bytesRead = await this . #streamToBuffer( downloadResponse . readableStreamBody , buffer )
232
+ const downloadResponse = await blobClient . download ( position , buffer . length )
233
+ const bytesRead = await copyStreamToBuffer ( downloadResponse . readableStreamBody , buffer )
270
234
return { bytesRead, buffer }
271
235
} catch ( e ) {
272
236
if ( e . statusCode === 404 ) {
@@ -290,9 +254,29 @@ export default class AzureHandler extends RemoteHandlerAbstract {
290
254
throw error
291
255
}
292
256
}
293
- // writeFile is used in the backups while _outputFile was used in the tests
294
- async _writeFile ( path ) {
295
- await this . _outputFile ( path )
257
+ async _writeFd ( file , buffer , position ) {
258
+ if ( typeof file !== 'string' ) {
259
+ file = file . fd
260
+ }
261
+
262
+ const blobClient = this . #containerClient. getBlockBlobClient ( file )
263
+ const blockSize = MIN_BLOCK_SIZE
264
+ const blockIds = [ ]
265
+ let totalWritten = 0
266
+ let blockIndex = 0
267
+
268
+ while ( totalWritten < buffer . length ) {
269
+ const chunkSize = Math . min ( blockSize , buffer . length - totalWritten )
270
+ const chunk = buffer . slice ( totalWritten , totalWritten + chunkSize )
271
+
272
+ const blockId = Buffer . from ( blockIndex . toString ( ) . padStart ( 6 , '0' ) ) . toString ( 'base64' )
273
+ blockIds . push ( blockId )
274
+ await blobClient . stageBlock ( blockId , chunk , chunkSize )
275
+ totalWritten += chunkSize
276
+ blockIndex ++
277
+ }
278
+
279
+ await blobClient . commitBlockList ( blockIds )
296
280
}
297
281
298
282
async _openFile ( path , flags ) {
0 commit comments