@@ -13,18 +13,28 @@ import Foundation
1313/// Buffers are allocated for each new chunk so they can safely escape to other threads
1414/// Call ``close`` when you're done with this object
1515class ChunkedFile {
16-
17- static let SIZE_UNKNOWN : UInt64 = 0
18- /// The size of the file. Call ``open`` to populate this with a real value, otherwise it will be ``SIZE_UNKNOWN``
19- var fileSize : UInt64 {
20- return _fileSize
16+
17+ private struct State {
18+ var fileHandle : FileHandle
19+ var fileURL : URL
20+ var filePosition : UInt64 = 0
2121 }
22-
22+
2323 private let chunkSize : Int
24-
25- private var fileHandle : FileHandle ?
26- private var filePos : UInt64 = 0
27- private var _fileSize : UInt64 = SIZE_UNKNOWN
24+
25+ var fileManager = FileManager . default
26+
27+ private var state : State ?
28+
29+ private var fileHandle : FileHandle ? {
30+ state? . fileHandle
31+ }
32+ private var fileURL : URL ? {
33+ state? . fileURL
34+ }
35+ private var filePos : UInt64 {
36+ state? . filePosition ?? 0
37+ }
2838
2939 /// Reads the next chunk from the file, advancing the file for the next read
3040 /// This method does synchronous I/O, so call it in the background
@@ -39,20 +49,19 @@ class ChunkedFile {
3949 return Result . failure ( ChunkedFileError . fileHandle ( error) )
4050 }
4151 }
42-
52+
4353 /// Opens the internal file ahead of time. Calling this is optional, but it's available
4454 /// Calling this multiple times (on the same thread) will have no effect unless you also ``close`` it
4555 /// Throws if the file couldn't be opened
4656 func openFile( fileURL: URL ) throws {
47- if fileHandle == nil {
57+ if state == nil {
4858 do {
49- guard let fileSize = try FileManager . default. attributesOfItem ( atPath: fileURL. path) [ FileAttributeKey . size] as? UInt64 else {
50- throw ChunkedFileError . invalidState ( " Cannot retrieve file size " )
51- }
52- self . _fileSize = fileSize
53-
54- let handle = try FileHandle ( forReadingFrom: fileURL)
55- fileHandle = handle
59+ let fileSize = try fileManager. fileSizeOfItem ( atPath: fileURL. path)
60+ let fileHandle = try FileHandle ( forReadingFrom: fileURL)
61+ state = State (
62+ fileHandle: fileHandle,
63+ fileURL: fileURL
64+ )
5665 MuxUploadSDK . logger? . info ( " Opened file with len \( String ( describing: fileSize) ) at path \( fileURL. path) " )
5766 } catch {
5867 throw ChunkedFileError . fileHandle ( error)
@@ -68,23 +77,26 @@ class ChunkedFile {
6877 } catch {
6978 MuxUploadSDK . logger? . warning ( " Swallowed error closing file: \( error. localizedDescription) " )
7079 }
71- fileHandle = nil
72- filePos = 0
73- _fileSize = ChunkedFile . SIZE_UNKNOWN
80+ state = nil
7481 }
7582
7683 func seekTo( byte: UInt64 ) throws {
7784 // Worst case: we start from the begining and there's a few very quick chunk successes
7885 try fileHandle? . seek ( toOffset: byte)
79- filePos = byte
86+ state ? . filePosition = byte
8087 }
8188
8289 private func doReadNextChunk( ) throws -> FileChunk {
8390 MuxUploadSDK . logger? . info ( " --doReadNextChunk " )
84- guard let fileHandle = fileHandle else {
91+ guard let fileHandle = fileHandle, let fileURL = fileURL else {
8592 throw ChunkedFileError . invalidState ( " doReadNextChunk called without file handle. Did you call open()? " )
8693 }
8794 let data = try fileHandle. read ( upToCount: chunkSize)
95+
96+ let fileSize = try fileManager. fileSizeOfItem (
97+ atPath: fileURL. path
98+ )
99+
88100 guard let data = data else {
89101 // Called while already at the end of the file. We read zero bytes, "ending" at the end of the file
90102 return FileChunk ( startByte: fileSize, endByte: fileSize, totalFileSize: fileSize, chunkData: Data ( capacity: 0 ) )
@@ -100,7 +112,7 @@ class ChunkedFile {
100112 chunkData: data
101113 )
102114
103- self . filePos = newFilePos
115+ state ? . filePosition = newFilePos
104116
105117 return chunk
106118 }
0 commit comments