Skip to content

Commit e345e4b

Browse files
committed
URLCache store and fetch methods implemented using NSKeyedArchiver and NSKeyedUnarchiver
1 parent 3f6f90e commit e345e4b

File tree

2 files changed

+108
-199
lines changed

2 files changed

+108
-199
lines changed

Diff for: Foundation/URLCache.swift

+90-130
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import SwiftFoundation
1212
#else
1313
import Foundation
1414
#endif
15-
import SQLite3
1615

1716
/*!
1817
@enum URLCache.StoragePolicy
@@ -130,24 +129,10 @@ open class CachedURLResponse : NSObject, NSSecureCoding, NSCopying {
130129
open class URLCache : NSObject {
131130

132131
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?
147133

148134
private let syncQ = DispatchQueue(label: "org.swift.URLCache.syncQ")
149-
private let _baseDiskPath: String?
150-
private var _databaseClient: _CacheSQLiteClient?
135+
private var persistence: CachePersistence?
151136

152137
/*!
153138
@method sharedURLCache
@@ -174,19 +159,31 @@ open class URLCache : NSObject {
174159
} else {
175160
let fourMegaByte = 4 * 1024 * 1024
176161
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)
180166
sharedCache = cache
167+
cache.persistence?.setupCacheDirectory()
181168
return cache
182169
}
183170
}
184171
}
185172
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+
}
187181
}
188182
}
189183

184+
private var allCaches: [String: CachedURLResponse] = [:]
185+
private var cacheSizeInMemory: Int = 0
186+
190187
/*!
191188
@method initWithMemoryCapacity:diskCapacity:diskPath:
192189
@abstract Initializes an URLCache with the given capacity and
@@ -203,8 +200,11 @@ open class URLCache : NSObject {
203200
public init(memoryCapacity: Int, diskCapacity: Int, diskPath path: String?) {
204201
self.memoryCapacity = memoryCapacity
205202
self.diskCapacity = diskCapacity
206-
self._baseDiskPath = path
207-
203+
204+
if let _path = path {
205+
self.persistence = CachePersistence(path: _path)
206+
}
207+
208208
super.init()
209209
}
210210

@@ -277,7 +277,9 @@ open class URLCache : NSObject {
277277
usage of the in-memory cache.
278278
@result the current usage of the in-memory cache of the receiver.
279279
*/
280-
open var currentMemoryUsage: Int { NSUnimplemented() }
280+
open var currentMemoryUsage: Int {
281+
return self.syncQ.sync { self.cacheSizeInMemory }
282+
}
281283

282284
/*!
283285
@method currentDiskUsage
@@ -287,19 +289,10 @@ open class URLCache : NSObject {
287289
usage of the on-disk cache.
288290
@result the current usage of the on-disk cache of the receiver.
289291
*/
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 }
301294
}
302-
295+
303296
}
304297

305298
extension URLCache {
@@ -308,112 +301,79 @@ extension URLCache {
308301
public func removeCachedResponse(for dataTask: URLSessionDataTask) { NSUnimplemented() }
309302
}
310303

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
321315
}
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
349319
}
350320
}
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)
367326
}
327+
328+
try FileManager.default.createDirectory(atPath: path, withIntermediateDirectories: true)
329+
} catch {
330+
fatalError("Unable to create directories for URLCache: \(error.localizedDescription)")
368331
}
369-
370-
return true
371332
}
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)")
389341
}
390-
391-
return true
392342
}
393-
394-
}
395343

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 {
402348
return nil
403349
}
350+
351+
return response
404352
}
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+
}
410360
}
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)
417364
}
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+
419379
}

0 commit comments

Comments
 (0)