@@ -45,7 +45,6 @@ extension PersistenceReaderKey {
4545/// Use ``PersistenceReaderKey/fileStorage(_:decoder:encoder:)`` to create values of this type.
4646public final class FileStorageKey < Value: Sendable > : PersistenceKey , Sendable {
4747 private let storage : FileStorage
48- private let isSetting = LockIsolated ( false )
4948 private let url : URL
5049 private let decode : @Sendable ( Data) throws -> Value
5150 private let encode : @Sendable ( Value) throws -> Data
@@ -83,7 +82,6 @@ public final class FileStorageKey<Value: Sendable>: PersistenceKey, Sendable {
8382 public func save( _ value: Value ) {
8483 self . state. withValue { state in
8584 if state. workItem == nil {
86- self . isSetting. setValue ( true )
8785 try ? self . storage. save ( encode ( value) , self . url)
8886 let workItem = DispatchWorkItem { [ weak self] in
8987 guard let self else { return }
@@ -94,7 +92,6 @@ public final class FileStorageKey<Value: Sendable>: PersistenceKey, Sendable {
9492 }
9593 guard let value = state. value
9694 else { return }
97- self . isSetting. setValue ( true )
9895 try ? self . storage. save ( self . encode ( value) , self . url)
9996 }
10097 }
@@ -125,14 +122,12 @@ public final class FileStorageKey<Value: Sendable>: PersistenceKey, Sendable {
125122 try ? self . storage. save ( Data ( ) , self . url)
126123 }
127124 let writeCancellable = self . storage. fileSystemSource ( self . url, [ . write] ) {
125+ // TODO: Improve this by fingerprinting (by adding extra bytes?) the file we write to the
126+ // file system so that we can early out of this closure.
128127 self . state. withValue { state in
129- if self . isSetting. value == true {
130- self . isSetting. setValue ( false )
131- } else {
132- state. workItem? . cancel ( )
133- state. workItem = nil
134- didSet ( self . load ( initialValue: initialValue) )
135- }
128+ guard state. workItem == nil
129+ else { return }
130+ didSet ( self . load ( initialValue: initialValue) )
136131 }
137132 }
138133 let deleteCancellable = self . storage. fileSystemSource ( self . url, [ . delete, . rename] ) {
@@ -265,15 +260,6 @@ public struct FileStorage: Hashable, Sendable {
265260 let load : @Sendable ( URL) throws -> Data
266261 @_spi ( Internals) public let save : @Sendable ( Data, URL) throws -> Void
267262
268- /// File storage that interacts directly with the file system for saving, loading and listening
269- /// for file changes.
270- ///
271- /// This is the version of the ``Dependencies/DependencyValues/defaultFileStorage`` dependency
272- /// that is used by default when running your app in the simulator or on device.
273- public static let fileSystem = fileSystem (
274- queue: DispatchQueue ( label: " co.pointfree.ComposableArchitecture.FileStorage " )
275- )
276-
277263 /// File storage that emulates a file system without actually writing anything to disk.
278264 ///
279265 /// This is the version of the ``Dependencies/DependencyValues/defaultFileStorage`` dependency
@@ -282,32 +268,35 @@ public struct FileStorage: Hashable, Sendable {
282268 inMemory ( fileSystem: LockIsolated ( [ : ] ) )
283269 }
284270
285- @_spi ( Internals) public static func fileSystem( queue: DispatchQueue ) -> Self {
286- Self (
287- id: AnyHashableSendable ( queue) ,
288- async : { queue. async ( execute: $0) } ,
289- asyncAfter: { queue. asyncAfter ( deadline: . now( ) + $0, execute: $1) } ,
290- createDirectory: {
291- try FileManager . default. createDirectory ( at: $0, withIntermediateDirectories: $1)
292- } ,
293- fileExists: { FileManager . default. fileExists ( atPath: $0. path) } ,
294- fileSystemSource: {
295- let source = DispatchSource . makeFileSystemObjectSource (
296- fileDescriptor: open ( $0. path, O_EVTONLY) ,
297- eventMask: $1,
298- queue: queue
299- )
300- source. setEventHandler ( handler: $2)
301- source. resume ( )
302- return AnyCancellable {
303- source. cancel ( )
304- close ( source. handle)
305- }
306- } ,
307- load: { try Data ( contentsOf: $0) } ,
308- save: { try $0. write ( to: $1) }
309- )
310- }
271+ /// File storage that interacts directly with the file system for saving, loading and listening
272+ /// for file changes.
273+ ///
274+ /// This is the version of the ``Dependencies/DependencyValues/defaultFileStorage`` dependency
275+ /// that is used by default when running your app in the simulator or on device.
276+ public static let fileSystem = Self (
277+ id: AnyHashableSendable ( DispatchQueue . main) ,
278+ async : { DispatchQueue . main. async ( execute: $0) } ,
279+ asyncAfter: { DispatchQueue . main. asyncAfter ( deadline: . now( ) + $0, execute: $1) } ,
280+ createDirectory: {
281+ try FileManager . default. createDirectory ( at: $0, withIntermediateDirectories: $1)
282+ } ,
283+ fileExists: { FileManager . default. fileExists ( atPath: $0. path) } ,
284+ fileSystemSource: {
285+ let source = DispatchSource . makeFileSystemObjectSource (
286+ fileDescriptor: open ( $0. path, O_EVTONLY) ,
287+ eventMask: $1,
288+ queue: . main
289+ )
290+ source. setEventHandler ( handler: $2)
291+ source. resume ( )
292+ return AnyCancellable {
293+ source. cancel ( )
294+ close ( source. handle)
295+ }
296+ } ,
297+ load: { try Data ( contentsOf: $0) } ,
298+ save: { try $0. write ( to: $1) }
299+ )
311300
312301 @_spi ( Internals) public static func inMemory(
313302 fileSystem: LockIsolated < [ URL : Data ] > ,
0 commit comments