@@ -12,7 +12,6 @@ import SwiftFoundation
12
12
#else
13
13
import Foundation
14
14
#endif
15
- import SQLite3
16
15
17
16
/*!
18
17
@enum URLCache.StoragePolicy
@@ -130,24 +129,10 @@ open class CachedURLResponse : NSObject, NSSecureCoding, NSCopying {
130
129
open class URLCache : NSObject {
131
130
132
131
private static let sharedSyncQ = DispatchQueue ( label: " org.swift.URLCache.sharedSyncQ " )
133
-
134
- private static var sharedCache : URLCache ? {
135
- willSet {
136
- URLCache . sharedCache? . syncQ. sync {
137
- URLCache . sharedCache? . _databaseClient? . close ( )
138
- URLCache . sharedCache? . flushDatabase ( )
139
- }
140
- }
141
- didSet {
142
- URLCache . sharedCache? . syncQ. sync {
143
- URLCache . sharedCache? . setupCacheDatabaseIfNotExist ( )
144
- }
145
- }
146
- }
132
+ private static var sharedCache : URLCache ?
147
133
148
134
private let syncQ = DispatchQueue ( label: " org.swift.URLCache.syncQ " )
149
- private let _baseDiskPath : String ?
150
- private var _databaseClient : _CacheSQLiteClient ?
135
+ private var persistence : CachePersistence ?
151
136
152
137
/*!
153
138
@method sharedURLCache
@@ -174,19 +159,31 @@ open class URLCache : NSObject {
174
159
} else {
175
160
let fourMegaByte = 4 * 1024 * 1024
176
161
let twentyMegaByte = 20 * 1024 * 1024
177
- let cacheDirectoryPath = FileManager . default. urls ( for: . cachesDirectory, in: . userDomainMask) . first? . path ?? " \( NSHomeDirectory ( ) ) /Library/Caches/ "
178
- let path = " \( cacheDirectoryPath) \( Bundle . main. bundleIdentifier ?? UUID ( ) . uuidString) "
179
- let cache = URLCache ( memoryCapacity: fourMegaByte, diskCapacity: twentyMegaByte, diskPath: path)
162
+ let bundleIdentifier = Bundle . main. bundleIdentifier ?? UUID ( ) . uuidString
163
+ let cacheDirectoryPath = FileManager . default. urls ( for: . cachesDirectory, in: . userDomainMask) . first? . path ?? " \( NSHomeDirectory ( ) ) /Library/Caches "
164
+ let diskPath = cacheDirectoryPath + " / " + bundleIdentifier
165
+ let cache = URLCache ( memoryCapacity: fourMegaByte, diskCapacity: twentyMegaByte, diskPath: diskPath)
180
166
sharedCache = cache
167
+ cache. persistence? . setupCacheDirectory ( )
181
168
return cache
182
169
}
183
170
}
184
171
}
185
172
set {
186
- sharedSyncQ. sync { sharedCache = newValue }
173
+ guard newValue !== sharedCache else { return }
174
+
175
+ sharedSyncQ. sync {
176
+ // Remove previous data before resetting new URLCache instance
177
+ URLCache . sharedCache? . persistence? . deleteAllCaches ( )
178
+ sharedCache = newValue
179
+ newValue. persistence? . setupCacheDirectory ( )
180
+ }
187
181
}
188
182
}
189
183
184
+ private var allCaches : [ String : CachedURLResponse ] = [ : ]
185
+ private var cacheSizeInMemory : Int = 0
186
+
190
187
/*!
191
188
@method initWithMemoryCapacity:diskCapacity:diskPath:
192
189
@abstract Initializes an URLCache with the given capacity and
@@ -203,8 +200,11 @@ open class URLCache : NSObject {
203
200
public init ( memoryCapacity: Int , diskCapacity: Int , diskPath path: String ? ) {
204
201
self . memoryCapacity = memoryCapacity
205
202
self . diskCapacity = diskCapacity
206
- self . _baseDiskPath = path
207
-
203
+
204
+ if let _path = path {
205
+ self . persistence = CachePersistence ( path: _path)
206
+ }
207
+
208
208
super. init ( )
209
209
}
210
210
@@ -277,7 +277,9 @@ open class URLCache : NSObject {
277
277
usage of the in-memory cache.
278
278
@result the current usage of the in-memory cache of the receiver.
279
279
*/
280
- open var currentMemoryUsage : Int { NSUnimplemented ( ) }
280
+ open var currentMemoryUsage : Int {
281
+ return self . syncQ. sync { self . cacheSizeInMemory }
282
+ }
281
283
282
284
/*!
283
285
@method currentDiskUsage
@@ -287,19 +289,10 @@ open class URLCache : NSObject {
287
289
usage of the on-disk cache.
288
290
@result the current usage of the on-disk cache of the receiver.
289
291
*/
290
- open var currentDiskUsage : Int { NSUnimplemented ( ) }
291
-
292
- private func flushDatabase( ) {
293
- guard let path = _baseDiskPath else { return }
294
-
295
- do {
296
- let dbPath = path. appending ( " /Cache.db " )
297
- try FileManager . default. removeItem ( atPath: dbPath)
298
- } catch {
299
- fatalError ( " Unable to flush database for URLCache: \( error. localizedDescription) " )
300
- }
292
+ open var currentDiskUsage : Int {
293
+ return self . syncQ. sync { self . persistence? . cacheSizeInDesk ?? 0 }
301
294
}
302
-
295
+
303
296
}
304
297
305
298
extension URLCache {
@@ -308,112 +301,79 @@ extension URLCache {
308
301
public func removeCachedResponse( for dataTask: URLSessionDataTask ) { NSUnimplemented ( ) }
309
302
}
310
303
311
- extension URLCache {
312
-
313
- private func setupCacheDatabaseIfNotExist( ) {
314
- guard let path = _baseDiskPath else { return }
315
-
316
- if !FileManager. default. fileExists ( atPath: path) {
317
- do {
318
- try FileManager . default. createDirectory ( atPath: path, withIntermediateDirectories: true )
319
- } catch {
320
- fatalError ( " Unable to create directories for URLCache: \( error. localizedDescription) " )
304
+ fileprivate struct CachePersistence {
305
+
306
+ let path : String
307
+
308
+ var cacheSizeInDesk : Int {
309
+ do {
310
+ let subFiles = try FileManager . default. subpathsOfDirectory ( atPath: path)
311
+ var total : Int = 0
312
+ for fileName in subFiles {
313
+ let attributes = try FileManager . default. attributesOfItem ( atPath: path. appending ( fileName) )
314
+ total += ( attributes [ . size] as? Int ) ?? 0
321
315
}
322
- }
323
-
324
- // Close the currently opened database connection(if any), before creating/replacing the db file
325
- _databaseClient? . close ( )
326
-
327
- let dbPath = path. appending ( " /Cache.db " )
328
- if !FileManager. default. createFile ( atPath: dbPath, contents: nil , attributes: nil ) {
329
- fatalError ( " Unable to setup database for URLCache " )
330
- }
331
-
332
- _databaseClient = _CacheSQLiteClient ( databasePath: dbPath)
333
- if _databaseClient == nil {
334
- _databaseClient? . close ( )
335
- flushDatabase ( )
336
- fatalError ( " Unable to setup database for URLCache " )
337
- }
338
-
339
- if !createTables( ) {
340
- _databaseClient? . close ( )
341
- flushDatabase ( )
342
- fatalError ( " Unable to setup database for URLCache: Tables not created " )
343
- }
344
-
345
- if !createIndicesForTables( ) {
346
- _databaseClient? . close ( )
347
- flushDatabase ( )
348
- fatalError ( " Unable to setup database for URLCache: Indices not created for tables " )
316
+ return total
317
+ } catch {
318
+ return 0
349
319
}
350
320
}
351
-
352
- private func createTables( ) -> Bool {
353
- guard _databaseClient != nil else {
354
- fatalError ( " Cannot create table before database setup " )
355
- }
356
-
357
- let tableSQLs = [
358
- " 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) " ,
359
- " CREATE TABLE cfurl_cache_receiver_data(entry_ID INTEGER PRIMARY KEY, isDataOnFS INTEGER, receiver_data BLOB) " ,
360
- " CREATE TABLE cfurl_cache_blob_data(entry_ID INTEGER PRIMARY KEY, response_object BLOB, request_object BLOB, proto_props BLOB, user_info BLOB) " ,
361
- " CREATE TABLE cfurl_cache_schema_version(schema_version INTEGER) "
362
- ]
363
-
364
- for sql in tableSQLs {
365
- if let isSuccess = _databaseClient? . execute ( sql: sql) , !isSuccess {
366
- return false
321
+
322
+ func setupCacheDirectory( ) {
323
+ do {
324
+ if FileManager . default. fileExists ( atPath: path) {
325
+ try FileManager . default. removeItem ( atPath: path)
367
326
}
327
+
328
+ try FileManager . default. createDirectory ( atPath: path, withIntermediateDirectories: true )
329
+ } catch {
330
+ fatalError ( " Unable to create directories for URLCache: \( error. localizedDescription) " )
368
331
}
369
-
370
- return true
371
332
}
372
-
373
- private func createIndicesForTables( ) -> Bool {
374
- guard _databaseClient != nil else {
375
- fatalError ( " Cannot create table before database setup " )
376
- }
377
-
378
- let indicesSQLs = [
379
- " CREATE INDEX proto_props_index ON cfurl_cache_blob_data(entry_ID) " ,
380
- " CREATE INDEX receiver_data_index ON cfurl_cache_receiver_data(entry_ID) " ,
381
- " CREATE INDEX request_key_index ON cfurl_cache_response(request_key) " ,
382
- " CREATE INDEX time_stamp_index ON cfurl_cache_response(time_stamp) "
383
- ]
384
-
385
- for sql in indicesSQLs {
386
- if let isSuccess = _databaseClient? . execute ( sql: sql) , !isSuccess {
387
- return false
388
- }
333
+
334
+ func saveCachedResponse( _ response: CachedURLResponse , for request: URLRequest ) {
335
+ let archivedData = NSKeyedArchiver . archivedData ( withRootObject: response)
336
+ do {
337
+ let cacheFileURL = urlForFileIdentifier ( request. cacheFileIdentifier)
338
+ try archivedData. write ( to: cacheFileURL)
339
+ } catch {
340
+ fatalError ( " Unable to save cache data: \( error. localizedDescription) " )
389
341
}
390
-
391
- return true
392
342
}
393
-
394
- }
395
343
396
- fileprivate struct _CacheSQLiteClient {
397
-
398
- private var database : OpaquePointer ?
399
-
400
- init ? ( databasePath: String ) {
401
- if sqlite3_open_v2 ( databasePath, & database, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, nil ) != SQLITE_OK {
344
+ func cachedResponse( for request: URLRequest ) -> CachedURLResponse ? {
345
+ let url = urlForFileIdentifier ( request. cacheFileIdentifier)
346
+ guard let data = try ? Data ( contentsOf: url) ,
347
+ let response = NSKeyedUnarchiver . unarchiveObject ( with: data) as? CachedURLResponse else {
402
348
return nil
403
349
}
350
+
351
+ return response
404
352
}
405
-
406
- func execute( sql: String ) -> Bool {
407
- guard let db = database else { return false }
408
-
409
- return sqlite3_exec ( db, sql, nil , nil , nil ) == SQLITE_OK
353
+
354
+ func deleteAllCaches( ) {
355
+ do {
356
+ try FileManager . default. removeItem ( atPath: path)
357
+ } catch {
358
+ fatalError ( " Unable to flush database for URLCache: \( error. localizedDescription) " )
359
+ }
410
360
}
411
-
412
- mutating func close( ) {
413
- guard let db = database else { return }
414
-
415
- sqlite3_close_v2 ( db)
416
- database = nil
361
+
362
+ private func urlForFileIdentifier( _ identifier: String ) -> URL {
363
+ return URL ( fileURLWithPath: path + " / " + identifier)
417
364
}
418
-
365
+
366
+ }
367
+
368
+ fileprivate extension URLRequest {
369
+
370
+ var cacheFileIdentifier : String {
371
+ guard let urlString = url? . absoluteString else {
372
+ fatalError ( " Unable to create cache identifier for request: \( self ) " )
373
+ }
374
+
375
+ let method = httpMethod ?? " GET "
376
+ return urlString + method
377
+ }
378
+
419
379
}
0 commit comments