@@ -8,9 +8,16 @@ import os
88
99@MainActor
1010final class FileColumnLayoutPersister : ColumnLayoutPersisting {
11+ static let shared : FileColumnLayoutPersister = {
12+ let persister = FileColumnLayoutPersister ( )
13+ persister. performScopeMigration ( )
14+ return persister
15+ } ( )
16+
1117 private static let logger = Logger ( subsystem: " com.TablePro " , category: " ColumnLayoutPersister " )
12- private static let legacyKeyPrefix = " com.TablePro.columns.layout. "
13- private static let migrationCompleteKey = " com.TablePro.columnLayoutMigrationComplete "
18+ private static let legacyUserDefaultsPrefix = " com.TablePro.columns.layout. "
19+ private static let legacyVisibilityPrefix = " com.TablePro.columns.hiddenColumns. "
20+ private static let scopeMigrationKey = " com.TablePro.columnLayoutSchemaScopeMigrationComplete "
1421
1522 private struct PersistedColumnLayout : Codable {
1623 var columnWidths : [ String : CGFloat ]
@@ -34,44 +41,42 @@ final class FileColumnLayoutPersister: ColumnLayoutPersisting {
3441 } catch {
3542 Self . logger. error ( " Failed to create storage directory: \( error. localizedDescription) " )
3643 }
37-
38- Self . performMigrationIfNeeded ( storageDirectory: self . storageDirectory)
3944 }
4045
41- func save( _ layout: ColumnLayoutState , for tableName : String , connectionId : UUID ) {
46+ func save( _ layout: ColumnLayoutState , for key : ColumnLayoutTableKey ) {
4247 guard !layout. columnWidths. isEmpty else { return }
4348
4449 let persisted = PersistedColumnLayout (
4550 columnWidths: layout. columnWidths,
4651 columnOrder: layout. columnOrder
4752 )
4853
49- var entries = loadEntries ( for: connectionId)
50- entries [ tableName ] = persisted
51- cache [ connectionId] = entries
52- writeEntries ( entries, for: connectionId)
54+ var entries = loadEntries ( for: key . connectionId)
55+ entries [ key . storageKey ] = persisted
56+ cache [ key . connectionId] = entries
57+ writeEntries ( entries, for: key . connectionId)
5358 }
5459
55- func load( for tableName : String , connectionId : UUID ) -> ColumnLayoutState ? {
56- let entries = loadEntries ( for: connectionId)
57- guard let persisted = entries [ tableName ] else { return nil }
60+ func load( for key : ColumnLayoutTableKey ) -> ColumnLayoutState ? {
61+ let entries = loadEntries ( for: key . connectionId)
62+ guard let persisted = entries [ key . storageKey ] else { return nil }
5863
5964 var state = ColumnLayoutState ( )
6065 state. columnWidths = persisted. columnWidths
6166 state. columnOrder = persisted. columnOrder
6267 return state
6368 }
6469
65- func clear( for tableName : String , connectionId : UUID ) {
66- var entries = loadEntries ( for: connectionId)
67- guard entries. removeValue ( forKey: tableName ) != nil else { return }
70+ func clear( for key : ColumnLayoutTableKey ) {
71+ var entries = loadEntries ( for: key . connectionId)
72+ guard entries. removeValue ( forKey: key . storageKey ) != nil else { return }
6873
6974 if entries. isEmpty {
70- cache [ connectionId] = [ : ]
71- removeFile ( for: connectionId)
75+ cache [ key . connectionId] = [ : ]
76+ removeFile ( for: key . connectionId)
7277 } else {
73- cache [ connectionId] = entries
74- writeEntries ( entries, for: connectionId)
78+ cache [ key . connectionId] = entries
79+ writeEntries ( entries, for: key . connectionId)
7580 }
7681 }
7782
@@ -136,53 +141,26 @@ final class FileColumnLayoutPersister: ColumnLayoutPersisting {
136141 . appendingPathComponent ( " ColumnLayout " , isDirectory: true )
137142 }
138143
139- private static func performMigrationIfNeeded ( storageDirectory : URL ) {
144+ private func performScopeMigration ( ) {
140145 let defaults = UserDefaults . standard
141- guard !defaults. bool ( forKey: migrationCompleteKey) else { return }
142-
143- let allKeys = defaults. dictionaryRepresentation ( ) . keys
144- let legacyKeys = allKeys. filter { $0. hasPrefix ( legacyKeyPrefix) }
145-
146- var grouped : [ UUID : [ String : PersistedColumnLayout ] ] = [ : ]
147- let decoder = JSONDecoder ( )
148-
149- for key in legacyKeys {
150- let suffix = String ( key. dropFirst ( legacyKeyPrefix. count) )
151- guard let dotIndex = suffix. firstIndex ( of: " . " ) else { continue }
152-
153- let uuidString = String ( suffix [ ..< dotIndex] )
154- let tableName = String ( suffix [ suffix. index ( after: dotIndex) ... ] )
155-
156- guard let connectionId = UUID ( uuidString: uuidString) ,
157- let data = defaults. data ( forKey: key) ,
158- let persisted = try ? decoder. decode ( PersistedColumnLayout . self, from: data) else {
159- defaults. removeObject ( forKey: key)
160- continue
146+ guard !defaults. bool ( forKey: Self . scopeMigrationKey) else { return }
147+
148+ if let files = try ? FileManager . default. contentsOfDirectory (
149+ at: storageDirectory,
150+ includingPropertiesForKeys: nil
151+ ) {
152+ for file in files where file. pathExtension == " json " {
153+ try ? FileManager . default. removeItem ( at: file)
161154 }
162-
163- grouped [ connectionId, default: [ : ] ] [ tableName] = persisted
164155 }
165156
166- let encoder = JSONEncoder ( )
167- for (connectionId, entries) in grouped {
168- let fileURL = storageDirectory. appendingPathComponent ( " \( connectionId. uuidString) .json " )
169- do {
170- let data = try encoder. encode ( entries)
171- try data. write ( to: fileURL, options: . atomic)
172- } catch {
173- logger. error (
174- " Migration failed for \( connectionId) : \( error. localizedDescription) "
175- )
176- }
157+ let legacyKeys = defaults. dictionaryRepresentation ( ) . keys. filter {
158+ $0. hasPrefix ( Self . legacyUserDefaultsPrefix) || $0. hasPrefix ( Self . legacyVisibilityPrefix)
177159 }
178-
179160 for key in legacyKeys {
180161 defaults. removeObject ( forKey: key)
181162 }
182- defaults. set ( true , forKey: migrationCompleteKey)
183163
184- if !grouped. isEmpty {
185- logger. trace ( " Migrated \( grouped. count) connection(s) of column layouts to file storage " )
186- }
164+ defaults. set ( true , forKey: Self . scopeMigrationKey)
187165 }
188166}
0 commit comments