7
7
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
8
8
//
9
9
10
- import SQLite3
11
-
12
10
/*!
13
11
@enum URLCache.StoragePolicy
14
12
@@ -125,24 +123,10 @@ open class CachedURLResponse : NSObject, NSSecureCoding, NSCopying {
125
123
open class URLCache : NSObject {
126
124
127
125
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
- }
126
+ private static var sharedCache : URLCache ?
142
127
143
128
private let syncQ = DispatchQueue ( label: " org.swift.URLCache.syncQ " )
144
- private let _baseDiskPath : String ?
145
- private var _databaseClient : _CacheSQLiteClient ?
129
+ private var persistence : CachePersistence ?
146
130
147
131
/*!
148
132
@method sharedURLCache
@@ -169,19 +153,31 @@ open class URLCache : NSObject {
169
153
} else {
170
154
let fourMegaByte = 4 * 1024 * 1024
171
155
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)
156
+ let bundleIdentifier = Bundle . main. bundleIdentifier ?? UUID ( ) . uuidString
157
+ let cacheDirectoryPath = FileManager . default. urls ( for: . cachesDirectory, in: . userDomainMask) . first? . path ?? " \( NSHomeDirectory ( ) ) /Library/Caches "
158
+ let diskPath = cacheDirectoryPath + " / " + bundleIdentifier
159
+ let cache = URLCache ( memoryCapacity: fourMegaByte, diskCapacity: twentyMegaByte, diskPath: diskPath)
175
160
sharedCache = cache
161
+ cache. persistence? . setupCacheDirectory ( )
176
162
return cache
177
163
}
178
164
}
179
165
}
180
166
set {
181
- sharedSyncQ. sync { sharedCache = newValue }
167
+ guard newValue !== sharedCache else { return }
168
+
169
+ sharedSyncQ. sync {
170
+ // Remove previous data before resetting new URLCache instance
171
+ URLCache . sharedCache? . persistence? . deleteAllCaches ( )
172
+ sharedCache = newValue
173
+ newValue. persistence? . setupCacheDirectory ( )
174
+ }
182
175
}
183
176
}
184
177
178
+ private var allCaches : [ String : CachedURLResponse ] = [ : ]
179
+ private var cacheSizeInMemory : Int = 0
180
+
185
181
/*!
186
182
@method initWithMemoryCapacity:diskCapacity:diskPath:
187
183
@abstract Initializes an URLCache with the given capacity and
@@ -198,8 +194,11 @@ open class URLCache : NSObject {
198
194
public init ( memoryCapacity: Int , diskCapacity: Int , diskPath path: String ? ) {
199
195
self . memoryCapacity = memoryCapacity
200
196
self . diskCapacity = diskCapacity
201
- self . _baseDiskPath = path
202
-
197
+
198
+ if let _path = path {
199
+ self . persistence = CachePersistence ( path: _path)
200
+ }
201
+
203
202
super. init ( )
204
203
}
205
204
@@ -272,7 +271,9 @@ open class URLCache : NSObject {
272
271
usage of the in-memory cache.
273
272
@result the current usage of the in-memory cache of the receiver.
274
273
*/
275
- open var currentMemoryUsage : Int { NSUnimplemented ( ) }
274
+ open var currentMemoryUsage : Int {
275
+ return self . syncQ. sync { self . cacheSizeInMemory }
276
+ }
276
277
277
278
/*!
278
279
@method currentDiskUsage
@@ -282,19 +283,10 @@ open class URLCache : NSObject {
282
283
usage of the on-disk cache.
283
284
@result the current usage of the on-disk cache of the receiver.
284
285
*/
285
- open var currentDiskUsage : Int { NSUnimplemented ( ) }
286
-
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
- }
286
+ open var currentDiskUsage : Int {
287
+ return self . syncQ. sync { self . persistence? . cacheSizeInDesk ?? 0 }
296
288
}
297
-
289
+
298
290
}
299
291
300
292
extension URLCache {
@@ -303,112 +295,79 @@ extension URLCache {
303
295
public func removeCachedResponse( for dataTask: URLSessionDataTask ) { NSUnimplemented ( ) }
304
296
}
305
297
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) " )
298
+ fileprivate struct CachePersistence {
299
+
300
+ let path : String
301
+
302
+ var cacheSizeInDesk : Int {
303
+ do {
304
+ let subFiles = try FileManager . default. subpathsOfDirectory ( atPath: path)
305
+ var total : Int = 0
306
+ for fileName in subFiles {
307
+ let attributes = try FileManager . default. attributesOfItem ( atPath: path. appending ( fileName) )
308
+ total += ( attributes [ . size] as? Int ) ?? 0
316
309
}
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 " )
310
+ return total
311
+ } catch {
312
+ return 0
344
313
}
345
314
}
346
-
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
315
+
316
+ func setupCacheDirectory( ) {
317
+ do {
318
+ if FileManager . default. fileExists ( atPath: path) {
319
+ try FileManager . default. removeItem ( atPath: path)
362
320
}
321
+
322
+ try FileManager . default. createDirectory ( atPath: path, withIntermediateDirectories: true )
323
+ } catch {
324
+ fatalError ( " Unable to create directories for URLCache: \( error. localizedDescription) " )
363
325
}
364
-
365
- return true
366
326
}
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
- }
327
+
328
+ func saveCachedResponse( _ response: CachedURLResponse , for request: URLRequest ) {
329
+ let archivedData = NSKeyedArchiver . archivedData ( withRootObject: response)
330
+ do {
331
+ let cacheFileURL = urlForFileIdentifier ( request. cacheFileIdentifier)
332
+ try archivedData. write ( to: cacheFileURL)
333
+ } catch {
334
+ fatalError ( " Unable to save cache data: \( error. localizedDescription) " )
384
335
}
385
-
386
- return true
387
336
}
388
-
389
- }
390
337
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 {
338
+ func cachedResponse( for request: URLRequest ) -> CachedURLResponse ? {
339
+ let url = urlForFileIdentifier ( request. cacheFileIdentifier)
340
+ guard let data = try ? Data ( contentsOf: url) ,
341
+ let response = NSKeyedUnarchiver . unarchiveObject ( with: data) as? CachedURLResponse else {
397
342
return nil
398
343
}
344
+
345
+ return response
399
346
}
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
347
+
348
+ func deleteAllCaches( ) {
349
+ do {
350
+ try FileManager . default. removeItem ( atPath: path)
351
+ } catch {
352
+ fatalError ( " Unable to flush database for URLCache: \( error. localizedDescription) " )
353
+ }
405
354
}
406
-
407
- mutating func close( ) {
408
- guard let db = database else { return }
409
-
410
- sqlite3_close_v2 ( db)
411
- database = nil
355
+
356
+ private func urlForFileIdentifier( _ identifier: String ) -> URL {
357
+ return URL ( fileURLWithPath: path + " / " + identifier)
412
358
}
413
-
359
+
360
+ }
361
+
362
+ fileprivate extension URLRequest {
363
+
364
+ var cacheFileIdentifier : String {
365
+ guard let urlString = url? . absoluteString else {
366
+ fatalError ( " Unable to create cache identifier for request: \( self ) " )
367
+ }
368
+
369
+ let method = httpMethod ?? " GET "
370
+ return urlString + method
371
+ }
372
+
414
373
}
0 commit comments