Skip to content

Commit 66f23d5

Browse files
authored
Add Storage API to limit upload chunk size (#10609)
1 parent d6e18b9 commit 66f23d5

File tree

4 files changed

+110
-1
lines changed

4 files changed

+110
-1
lines changed

FirebaseStorage/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# 10.5.0
2+
- [added] Added Storage API to limit upload chunk size. (#10137)
3+
14
# 10.3.0
25
- [fixed] Use dedicated serial queue for Storage uploads and downloads instead of a (concurrent) global queue.
36
Fixes regression introduced in 10.0.0. (#10487)

FirebaseStorage/Sources/Storage.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,12 @@ import FirebaseAuthInterop
123123
}
124124
}
125125

126+
/**
127+
* Specify the maximum upload chunk size. Values less than 256K (262144) will be rounded up to 256K. Values
128+
* above 256K will be rounded down to the nearest 256K multiple. The default is no maximum.
129+
*/
130+
@objc public var uploadChunkSizeBytes: Int64 = .max
131+
126132
/**
127133
* A `DispatchQueue` that all developer callbacks are fired on. Defaults to the main queue.
128134
*/

FirebaseStorage/Sources/StorageUploadTask.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ import Foundation
7676
let uploadFetcher = GTMSessionUploadFetcher(
7777
request: request,
7878
uploadMIMEType: contentType,
79-
chunkSize: Int64.max,
79+
chunkSize: self.reference.storage.uploadChunkSizeBytes,
8080
fetcherService: self.fetcherService
8181
)
8282
if let data = self.uploadData {

FirebaseStorage/Tests/Integration/StorageIntegration.swift

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,106 @@ class StorageResultTests: StorageIntegrationCommon {
265265
waitForExpectations()
266266
}
267267

268+
func testPutFileLimitedChunk() throws {
269+
defer {
270+
// Reset since tests share storage instance.
271+
storage.uploadChunkSizeBytes = Int64.max
272+
}
273+
let expectation = self.expectation(description: #function)
274+
let putFileExpectation = self.expectation(description: "putFile")
275+
let ref = storage.reference(withPath: "ios/public/testPutFilePauseResume")
276+
let bundle = Bundle(for: StorageIntegrationCommon.self)
277+
let filePath = try XCTUnwrap(bundle.path(forResource: "1mb", ofType: "dat"),
278+
"Failed to get filePath")
279+
let data = try XCTUnwrap(try Data(contentsOf: URL(fileURLWithPath: filePath)),
280+
"Failed to load file")
281+
let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory())
282+
let fileURL = tmpDirURL.appendingPathComponent("LargePutFile.txt")
283+
var progressCount = 0
284+
285+
try data.write(to: fileURL, options: .atomicWrite)
286+
287+
// Limit the upload chunk size
288+
storage.uploadChunkSizeBytes = 256_000
289+
290+
let task = ref.putFile(from: fileURL) { result in
291+
XCTAssertGreaterThanOrEqual(progressCount, 4)
292+
self.assertResultSuccess(result)
293+
putFileExpectation.fulfill()
294+
}
295+
296+
task.observe(StorageTaskStatus.success) { snapshot in
297+
XCTAssertEqual(snapshot.description, "<State: Success>")
298+
expectation.fulfill()
299+
}
300+
301+
var uploadedBytes: Int64 = -1
302+
303+
task.observe(StorageTaskStatus.progress) { snapshot in
304+
XCTAssertTrue(snapshot.description.starts(with: "<State: Progress") ||
305+
snapshot.description.starts(with: "<State: Resume"))
306+
guard let progress = snapshot.progress else {
307+
XCTFail("Failed to get snapshot.progress")
308+
return
309+
}
310+
progressCount = progressCount + 1
311+
XCTAssertGreaterThanOrEqual(progress.completedUnitCount, uploadedBytes)
312+
uploadedBytes = progress.completedUnitCount
313+
}
314+
waitForExpectations()
315+
}
316+
317+
func testPutFileTinyChunk() throws {
318+
defer {
319+
// Reset since tests share storage instance.
320+
storage.uploadChunkSizeBytes = Int64.max
321+
}
322+
let expectation = self.expectation(description: #function)
323+
let putFileExpectation = self.expectation(description: "putFile")
324+
let ref = storage.reference(withPath: "ios/public/testPutFilePauseResume")
325+
let bundle = Bundle(for: StorageIntegrationCommon.self)
326+
let filePath = try XCTUnwrap(bundle.path(forResource: "1mb", ofType: "dat"),
327+
"Failed to get filePath")
328+
let data = try XCTUnwrap(try Data(contentsOf: URL(fileURLWithPath: filePath)),
329+
"Failed to load file")
330+
let tmpDirURL = URL(fileURLWithPath: NSTemporaryDirectory())
331+
let fileURL = tmpDirURL.appendingPathComponent("LargePutFile.txt")
332+
var progressCount = 0
333+
334+
try data.write(to: fileURL, options: .atomicWrite)
335+
336+
// Limit the upload chunk size. This should behave exactly like the previous
337+
// test since small chunk sizes are rounded up to 256K.
338+
storage.uploadChunkSizeBytes = 1
339+
340+
let task = ref.putFile(from: fileURL) { result in
341+
XCTAssertGreaterThanOrEqual(progressCount, 4)
342+
XCTAssertLessThanOrEqual(progressCount, 6)
343+
self.assertResultSuccess(result)
344+
putFileExpectation.fulfill()
345+
}
346+
347+
task.observe(StorageTaskStatus.success) { snapshot in
348+
XCTAssertEqual(snapshot.description, "<State: Success>")
349+
expectation.fulfill()
350+
}
351+
352+
var uploadedBytes: Int64 = -1
353+
354+
task.observe(StorageTaskStatus.progress) { snapshot in
355+
XCTAssertTrue(snapshot.description.starts(with: "<State: Progress") ||
356+
snapshot.description.starts(with: "<State: Resume"))
357+
guard let progress = snapshot.progress else {
358+
XCTFail("Failed to get snapshot.progress")
359+
return
360+
}
361+
progressCount = progressCount + 1
362+
XCTAssertGreaterThanOrEqual(progress.completedUnitCount, uploadedBytes)
363+
uploadedBytes = progress.completedUnitCount
364+
}
365+
waitForExpectations()
366+
}
367+
268368
func testAttemptToUploadDirectoryShouldFail() throws {
269369
// This `.numbers` file is actually a directory.
270370
let fileName = "HomeImprovement.numbers"

0 commit comments

Comments
 (0)