@@ -125,24 +125,10 @@ open class CachedURLResponse : NSObject, NSSecureCoding, NSCopying {
125
125
open class URLCache : NSObject {
126
126
127
127
private static let sharedSyncQ = DispatchQueue ( label: " org.swift.URLCache.sharedSyncQ " )
128
-
129
- private static var sharedCache : URLCache ? {
130
- willSet {
131
- URLCache . sharedCache? . syncQ. sync {
132
- URLCache . sharedCache? . _databaseClient? . close ( )
133
- URLCache . sharedCache? . flushDatabase ( )
134
- }
135
- }
136
- didSet {
137
- URLCache . sharedCache? . syncQ. sync {
138
- URLCache . sharedCache? . setupCacheDatabaseIfNotExist ( )
139
- }
140
- }
141
- }
128
+ private static var sharedCache : URLCache ?
142
129
143
130
private let syncQ = DispatchQueue ( label: " org.swift.URLCache.syncQ " )
144
- private let _baseDiskPath : String ?
145
- private var _databaseClient : _CacheSQLiteClient ?
131
+ private var persistence : CachePersistence ?
146
132
147
133
/*!
148
134
@method sharedURLCache
@@ -169,19 +155,31 @@ open class URLCache : NSObject {
169
155
} else {
170
156
let fourMegaByte = 4 * 1024 * 1024
171
157
let twentyMegaByte = 20 * 1024 * 1024
172
- let cacheDirectoryPath = FileManager . default. urls ( for: . cachesDirectory, in: . userDomainMask) . first? . path ?? " \( NSHomeDirectory ( ) ) /Library/Caches/ "
173
- let path = " \( cacheDirectoryPath) \( Bundle . main. bundleIdentifier ?? UUID ( ) . uuidString) "
174
- let cache = URLCache ( memoryCapacity: fourMegaByte, diskCapacity: twentyMegaByte, diskPath: path)
158
+ let bundleIdentifier = Bundle . main. bundleIdentifier ?? UUID ( ) . uuidString
159
+ let cacheDirectoryPath = FileManager . default. urls ( for: . cachesDirectory, in: . userDomainMask) . first? . path ?? " \( NSHomeDirectory ( ) ) /Library/Caches "
160
+ let diskPath = cacheDirectoryPath + " / " + bundleIdentifier
161
+ let cache = URLCache ( memoryCapacity: fourMegaByte, diskCapacity: twentyMegaByte, diskPath: diskPath)
175
162
sharedCache = cache
163
+ cache. persistence? . setupCacheDirectory ( )
176
164
return cache
177
165
}
178
166
}
179
167
}
180
168
set {
181
- sharedSyncQ. sync { sharedCache = newValue }
169
+ guard newValue !== sharedCache else { return }
170
+
171
+ sharedSyncQ. sync {
172
+ // Remove previous data before resetting new URLCache instance
173
+ URLCache . sharedCache? . persistence? . deleteAllCaches ( )
174
+ sharedCache = newValue
175
+ newValue. persistence? . setupCacheDirectory ( )
176
+ }
182
177
}
183
178
}
184
179
180
+ private var allCaches : [ String : CachedURLResponse ] = [ : ]
181
+ private var cacheSizeInMemory : Int = 0
182
+
185
183
/*!
186
184
@method initWithMemoryCapacity:diskCapacity:diskPath:
187
185
@abstract Initializes an URLCache with the given capacity and
@@ -198,8 +196,11 @@ open class URLCache : NSObject {
198
196
public init ( memoryCapacity: Int , diskCapacity: Int , diskPath path: String ? ) {
199
197
self . memoryCapacity = memoryCapacity
200
198
self . diskCapacity = diskCapacity
201
- self . _baseDiskPath = path
202
-
199
+
200
+ if let _path = path {
201
+ self . persistence = CachePersistence ( path: _path)
202
+ }
203
+
203
204
super. init ( )
204
205
}
205
206
@@ -214,7 +215,27 @@ open class URLCache : NSObject {
214
215
request, or nil if there is no NSCachedURLResponse stored with the
215
216
given request.
216
217
*/
217
- open func cachedResponse( for request: URLRequest ) -> CachedURLResponse ? { NSUnimplemented ( ) }
218
+ open func cachedResponse( for request: URLRequest ) -> CachedURLResponse ? {
219
+ var cache : CachedURLResponse ?
220
+ self . syncQ. sync {
221
+ let identifier = request. cacheFileIdentifier
222
+ if let inMemoryCache = self . allCaches [ identifier] {
223
+ cache = inMemoryCache
224
+ } else if let _persistence = self . persistence {
225
+ guard let inDiskCache = _persistence. cachedResponse ( for: request) else {
226
+ cache = nil
227
+ return
228
+ }
229
+
230
+ self . allCaches [ identifier] = inDiskCache
231
+ self . cacheSizeInMemory += inDiskCache. data. count
232
+ cache = inDiskCache
233
+ } else {
234
+ cache = nil
235
+ }
236
+ }
237
+ return cache
238
+ }
218
239
219
240
/*!
220
241
@method storeCachedResponse:forRequest:
@@ -223,7 +244,19 @@ open class URLCache : NSObject {
223
244
@param cachedResponse The cached response to store.
224
245
@param request the NSURLRequest to use as a key for the storage.
225
246
*/
226
- open func storeCachedResponse( _ cachedResponse: CachedURLResponse , for request: URLRequest ) { NSUnimplemented ( ) }
247
+ open func storeCachedResponse( _ cachedResponse: CachedURLResponse , for request: URLRequest ) {
248
+ self . syncQ. sync {
249
+ purgeOldMemoryForNewCacheIfNeeded ( cachedResponse)
250
+
251
+ let identifier = request. cacheFileIdentifier
252
+ self . allCaches [ identifier] = cachedResponse
253
+ self . cacheSizeInMemory += cachedResponse. data. count
254
+
255
+ if self . persistence != nil {
256
+ self . persistence? . saveCachedResponse ( cachedResponse, for: request)
257
+ }
258
+ }
259
+ }
227
260
228
261
/*!
229
262
@method removeCachedResponseForRequest:
@@ -272,7 +305,9 @@ open class URLCache : NSObject {
272
305
usage of the in-memory cache.
273
306
@result the current usage of the in-memory cache of the receiver.
274
307
*/
275
- open var currentMemoryUsage : Int { NSUnimplemented ( ) }
308
+ open var currentMemoryUsage : Int {
309
+ return self . syncQ. sync { self . cacheSizeInMemory }
310
+ }
276
311
277
312
/*!
278
313
@method currentDiskUsage
@@ -282,17 +317,14 @@ open class URLCache : NSObject {
282
317
usage of the on-disk cache.
283
318
@result the current usage of the on-disk cache of the receiver.
284
319
*/
285
- open var currentDiskUsage : Int { NSUnimplemented ( ) }
320
+ open var currentDiskUsage : Int {
321
+ return self . syncQ. sync { self . persistence? . cacheSizeInDesk ?? 0 }
322
+ }
286
323
287
- private func flushDatabase( ) {
288
- guard let path = _baseDiskPath else { return }
289
-
290
- do {
291
- let dbPath = path. appending ( " /Cache.db " )
292
- try FileManager . default. removeItem ( atPath: dbPath)
293
- } catch {
294
- fatalError ( " Unable to flush database for URLCache: \( error. localizedDescription) " )
295
- }
324
+ private func purgeOldMemoryForNewCacheIfNeeded( _ newCache: CachedURLResponse ) {
325
+ // 1. Check the in-memory cache size and purge old if needed
326
+ // 2. Check the disk-memory cache size and purge old if needed
327
+ NSUnimplemented ( )
296
328
}
297
329
298
330
}
@@ -303,112 +335,79 @@ extension URLCache {
303
335
public func removeCachedResponse( for dataTask: URLSessionDataTask ) { NSUnimplemented ( ) }
304
336
}
305
337
306
- extension URLCache {
307
-
308
- private func setupCacheDatabaseIfNotExist( ) {
309
- guard let path = _baseDiskPath else { return }
310
-
311
- if !FileManager. default. fileExists ( atPath: path) {
312
- do {
313
- try FileManager . default. createDirectory ( atPath: path, withIntermediateDirectories: true )
314
- } catch {
315
- fatalError ( " Unable to create directories for URLCache: \( error. localizedDescription) " )
338
+ fileprivate struct CachePersistence {
339
+
340
+ let path : String
341
+
342
+ var cacheSizeInDesk : Int {
343
+ do {
344
+ let subFiles = try FileManager . default. subpathsOfDirectory ( atPath: path)
345
+ var total : Int = 0
346
+ for fileName in subFiles {
347
+ let attributes = try FileManager . default. attributesOfItem ( atPath: path. appending ( fileName) )
348
+ total += ( attributes [ . size] as? Int ) ?? 0
316
349
}
317
- }
318
-
319
- // Close the currently opened database connection(if any), before creating/replacing the db file
320
- _databaseClient? . close ( )
321
-
322
- let dbPath = path. appending ( " /Cache.db " )
323
- if !FileManager. default. createFile ( atPath: dbPath, contents: nil , attributes: nil ) {
324
- fatalError ( " Unable to setup database for URLCache " )
325
- }
326
-
327
- _databaseClient = _CacheSQLiteClient ( databasePath: dbPath)
328
- if _databaseClient == nil {
329
- _databaseClient? . close ( )
330
- flushDatabase ( )
331
- fatalError ( " Unable to setup database for URLCache " )
332
- }
333
-
334
- if !createTables( ) {
335
- _databaseClient? . close ( )
336
- flushDatabase ( )
337
- fatalError ( " Unable to setup database for URLCache: Tables not created " )
338
- }
339
-
340
- if !createIndicesForTables( ) {
341
- _databaseClient? . close ( )
342
- flushDatabase ( )
343
- fatalError ( " Unable to setup database for URLCache: Indices not created for tables " )
350
+ return total
351
+ } catch {
352
+ return 0
344
353
}
345
354
}
346
355
347
- private func createTables( ) -> Bool {
348
- guard _databaseClient != nil else {
349
- fatalError ( " Cannot create table before database setup " )
350
- }
351
-
352
- let tableSQLs = [
353
- " CREATE TABLE cfurl_cache_response(entry_ID INTEGER PRIMARY KEY, version INTEGER, hash_value VARCHAR, storage_policy INTEGER, request_key VARCHAR, time_stamp DATETIME, partition VARCHAR) " ,
354
- " CREATE TABLE cfurl_cache_receiver_data(entry_ID INTEGER PRIMARY KEY, isDataOnFS INTEGER, receiver_data BLOB) " ,
355
- " CREATE TABLE cfurl_cache_blob_data(entry_ID INTEGER PRIMARY KEY, response_object BLOB, request_object BLOB, proto_props BLOB, user_info BLOB) " ,
356
- " CREATE TABLE cfurl_cache_schema_version(schema_version INTEGER) "
357
- ]
358
-
359
- for sql in tableSQLs {
360
- if let isSuccess = _databaseClient? . execute ( sql: sql) , !isSuccess {
361
- return false
356
+ func setupCacheDirectory( ) {
357
+ do {
358
+ if FileManager . default. fileExists ( atPath: path) {
359
+ try FileManager . default. removeItem ( atPath: path)
362
360
}
361
+
362
+ try FileManager . default. createDirectory ( atPath: path, withIntermediateDirectories: true )
363
+ } catch {
364
+ fatalError ( " Unable to create directories for URLCache: \( error. localizedDescription) " )
363
365
}
364
-
365
- return true
366
366
}
367
-
368
- private func createIndicesForTables( ) -> Bool {
369
- guard _databaseClient != nil else {
370
- fatalError ( " Cannot create table before database setup " )
371
- }
372
-
373
- let indicesSQLs = [
374
- " CREATE INDEX proto_props_index ON cfurl_cache_blob_data(entry_ID) " ,
375
- " CREATE INDEX receiver_data_index ON cfurl_cache_receiver_data(entry_ID) " ,
376
- " CREATE INDEX request_key_index ON cfurl_cache_response(request_key) " ,
377
- " CREATE INDEX time_stamp_index ON cfurl_cache_response(time_stamp) "
378
- ]
379
-
380
- for sql in indicesSQLs {
381
- if let isSuccess = _databaseClient? . execute ( sql: sql) , !isSuccess {
382
- return false
383
- }
367
+
368
+ func saveCachedResponse( _ response: CachedURLResponse , for request: URLRequest ) {
369
+ let archivedData = NSKeyedArchiver . archivedData ( withRootObject: response)
370
+ do {
371
+ let cacheFileURL = urlForFileIdentifier ( request. cacheFileIdentifier)
372
+ try archivedData. write ( to: cacheFileURL)
373
+ } catch {
374
+ fatalError ( " Unable to save cache data: \( error. localizedDescription) " )
384
375
}
385
-
386
- return true
387
376
}
388
-
389
- }
390
377
391
- fileprivate struct _CacheSQLiteClient {
392
-
393
- private var database : OpaquePointer ?
394
-
395
- init ? ( databasePath: String ) {
396
- if sqlite3_open_v2 ( databasePath, & database, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, nil ) != SQLITE_OK {
378
+ func cachedResponse( for request: URLRequest ) -> CachedURLResponse ? {
379
+ let url = urlForFileIdentifier ( request. cacheFileIdentifier)
380
+ guard let data = try ? Data ( contentsOf: url) ,
381
+ let response = NSKeyedUnarchiver . unarchiveObject ( with: data) as? CachedURLResponse else {
397
382
return nil
398
383
}
384
+
385
+ return response
399
386
}
400
-
401
- func execute( sql: String ) -> Bool {
402
- guard let db = database else { return false }
403
-
404
- return sqlite3_exec ( db, sql, nil , nil , nil ) == SQLITE_OK
387
+
388
+ func deleteAllCaches( ) {
389
+ do {
390
+ try FileManager . default. removeItem ( atPath: path)
391
+ } catch {
392
+ fatalError ( " Unable to flush database for URLCache: \( error. localizedDescription) " )
393
+ }
405
394
}
406
-
407
- mutating func close( ) {
408
- guard let db = database else { return }
409
-
410
- sqlite3_close_v2 ( db)
411
- database = nil
395
+
396
+ private func urlForFileIdentifier( _ identifier: String ) -> URL {
397
+ return URL ( fileURLWithPath: path + " / " + identifier)
412
398
}
413
-
399
+
400
+ }
401
+
402
+ fileprivate extension URLRequest {
403
+
404
+ var cacheFileIdentifier : String {
405
+ guard let urlString = url? . absoluteString else {
406
+ fatalError ( " Unable to create cache identifier for request: \( self ) " )
407
+ }
408
+
409
+ let method = httpMethod ?? " GET "
410
+ return urlString + method
411
+ }
412
+
414
413
}
0 commit comments