@@ -5,6 +5,7 @@ private struct CacheEntryMeta: Codable {
55 let version : String
66 let keyDescription : String
77 let fetchedAt : Date
8+ var lastAccessedAt : Date
89 let size : Int64
910 let deviceID : String
1011 let path : String
@@ -36,12 +37,7 @@ public actor ContentCache {
3637 public func lookup( _ key: ContentKey ) -> URL ? {
3738 let entryDir = entryDirectory ( for: key)
3839 let metaURL = entryDir. appendingPathComponent ( " meta.json " )
39- let decoder = JSONDecoder ( )
40- decoder. dateDecodingStrategy = . secondsSince1970
41- guard let metaData = try ? Data ( contentsOf: metaURL) ,
42- let meta = try ? decoder. decode ( CacheEntryMeta . self, from: metaData) else {
43- return nil
44- }
40+ guard var meta = readMeta ( at: metaURL) else { return nil }
4541 guard meta. deviceID == key. deviceID,
4642 meta. path == key. path,
4743 meta. mtimeUnix == key. mtimeUnix,
@@ -56,16 +52,33 @@ public actor ContentCache {
5652 guard let fileURL, fileManager. fileExists ( atPath: fileURL. path) else {
5753 return nil
5854 }
55+ let now = Date ( )
56+ if now. timeIntervalSince ( meta. lastAccessedAt) > 60 {
57+ meta. lastAccessedAt = now
58+ writeMeta ( meta, to: metaURL)
59+ }
5960 return fileURL
6061 }
6162
63+ private func readMeta( at url: URL ) -> CacheEntryMeta ? {
64+ let decoder = JSONDecoder ( )
65+ decoder. dateDecodingStrategy = . secondsSince1970
66+ guard let data = try ? Data ( contentsOf: url) else { return nil }
67+ return try ? decoder. decode ( CacheEntryMeta . self, from: data)
68+ }
69+
70+ private func writeMeta( _ meta: CacheEntryMeta , to url: URL ) {
71+ let encoder = JSONEncoder ( )
72+ encoder. dateEncodingStrategy = . secondsSince1970
73+ guard let data = try ? encoder. encode ( meta) else { return }
74+ try ? data. write ( to: url, options: [ . atomic] )
75+ }
76+
6277 public func store( _ source: URL , key: ContentKey , filename: String ) async throws -> URL {
6378 let entryDir = entryDirectory ( for: key)
6479 try fileManager. createDirectory ( at: entryDir, withIntermediateDirectories: true )
6580
66- let ext = ( filename as NSString ) . pathExtension
67- let destName = ext. isEmpty ? filename : " \( filename) "
68- let destURL = entryDir. appendingPathComponent ( destName)
81+ let destURL = entryDir. appendingPathComponent ( filename)
6982
7083 if fileManager. fileExists ( atPath: destURL. path) {
7184 try fileManager. removeItem ( at: destURL)
@@ -77,21 +90,20 @@ public actor ContentCache {
7790 try fileManager. copyItem ( at: source, to: destURL)
7891 }
7992
93+ let now = Date ( )
8094 let meta = CacheEntryMeta (
8195 version: " v1 " ,
8296 keyDescription: " \( key. deviceID) | \( key. path) | \( key. mtimeUnix) | \( key. size) " ,
83- fetchedAt: Date ( ) ,
97+ fetchedAt: now,
98+ lastAccessedAt: now,
8499 size: key. size,
85100 deviceID: key. deviceID,
86101 path: key. path,
87102 mtimeUnix: key. mtimeUnix,
88103 keySizeBytes: key. size
89104 )
90- let encoder = JSONEncoder ( )
91- encoder. dateEncodingStrategy = . secondsSince1970
92- let metaData = try encoder. encode ( meta)
93105 let metaURL = entryDir. appendingPathComponent ( " meta.json " )
94- try metaData . write ( to : metaURL , options : [ . atomic ] )
106+ writeMeta ( meta , to : metaURL )
95107
96108 await trimIfNeeded ( )
97109
@@ -132,26 +144,20 @@ public actor ContentCache {
132144 }
133145
134146 private func entryDirectory( for key: ContentKey ) -> URL {
135- let deviceSlug = sha256Base32 ( key. deviceID)
136- let keySlug = key. sha256Hex
137- return rootURL
138- . appendingPathComponent ( deviceSlug, isDirectory: true )
139- . appendingPathComponent ( keySlug, isDirectory: true )
147+ rootURL
148+ . appendingPathComponent ( Self . sha256Hex ( key. deviceID) , isDirectory: true )
149+ . appendingPathComponent ( key. sha256Hex, isDirectory: true )
140150 }
141151
142- private func sha256Base32( _ input: String ) -> String {
143- let digest = SHA256 . hash ( data: Data ( input. utf8) )
144- return digest. map { String ( format: " %02x " , $0) } . joined ( )
152+ private static func sha256Hex( _ input: String ) -> String {
153+ SHA256 . hash ( data: Data ( input. utf8) )
154+ . map { String ( format: " %02x " , $0) }
155+ . joined ( )
145156 }
146157
147158 private func readKey( from entryDir: URL ) -> ContentKey ? {
148159 let metaURL = entryDir. appendingPathComponent ( " meta.json " )
149- let decoder = JSONDecoder ( )
150- decoder. dateDecodingStrategy = . secondsSince1970
151- guard let data = try ? Data ( contentsOf: metaURL) ,
152- let meta = try ? decoder. decode ( CacheEntryMeta . self, from: data) else {
153- return nil
154- }
160+ guard let meta = readMeta ( at: metaURL) else { return nil }
155161 return ContentKey (
156162 deviceID: meta. deviceID,
157163 path: meta. path,
@@ -192,7 +198,7 @@ public actor ContentCache {
192198
193199 struct EntryRecord {
194200 let dir : URL
195- let fetchedAt : Date
201+ let lastAccessedAt : Date
196202 let size : Int64
197203 }
198204
@@ -207,28 +213,13 @@ public actor ContentCache {
207213 includingPropertiesForKeys: nil
208214 ) ) ?? [ ]
209215 for entryDir in entryDirs {
210- let metaURL = entryDir. appendingPathComponent ( " meta.json " )
211- let metaDecoder = JSONDecoder ( )
212- metaDecoder. dateDecodingStrategy = . secondsSince1970
213- guard let data = try ? Data ( contentsOf: metaURL) ,
214- let meta = try ? metaDecoder. decode ( CacheEntryMeta . self, from: data) else {
215- continue
216- }
217- var entrySize : Int64 = 0
218- let files = ( try ? fileManager. contentsOfDirectory (
219- at: entryDir,
220- includingPropertiesForKeys: [ . fileSizeKey]
221- ) ) ?? [ ]
222- for file in files {
223- if let sz = try ? file. resourceValues ( forKeys: [ . fileSizeKey] ) . fileSize {
224- entrySize += Int64 ( sz)
225- }
226- }
227- records. append ( EntryRecord ( dir: entryDir, fetchedAt: meta. fetchedAt, size: entrySize) )
216+ guard let meta = readMeta ( at: entryDir. appendingPathComponent ( " meta.json " ) ) else { continue }
217+ let entrySize = computeEntrySize ( at: entryDir)
218+ records. append ( EntryRecord ( dir: entryDir, lastAccessedAt: meta. lastAccessedAt, size: entrySize) )
228219 }
229220 }
230221
231- records. sort { $0. fetchedAt < $1. fetchedAt }
222+ records. sort { $0. lastAccessedAt < $1. lastAccessedAt }
232223 var freed : Int64 = 0
233224 let mustFree = total - capacityBytes
234225 for record in records {
@@ -237,4 +228,18 @@ public actor ContentCache {
237228 freed += record. size
238229 }
239230 }
231+
232+ private func computeEntrySize( at entryDir: URL ) -> Int64 {
233+ var entrySize : Int64 = 0
234+ let files = ( try ? fileManager. contentsOfDirectory (
235+ at: entryDir,
236+ includingPropertiesForKeys: [ . fileSizeKey]
237+ ) ) ?? [ ]
238+ for file in files {
239+ if let sz = try ? file. resourceValues ( forKeys: [ . fileSizeKey] ) . fileSize {
240+ entrySize += Int64 ( sz)
241+ }
242+ }
243+ return entrySize
244+ }
240245}
0 commit comments