Skip to content

Commit 0f21c7c

Browse files
committed
URLCache store and fetch methods implemented using NSKeyedArchiver and NSKeyedUnarchiver
1 parent 98b2c73 commit 0f21c7c

File tree

2 files changed

+108
-200
lines changed

2 files changed

+108
-200
lines changed

Foundation/URLCache.swift

Lines changed: 90 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
88
//
99

10-
import SQLite3
11-
1210
/*!
1311
@enum URLCache.StoragePolicy
1412

@@ -125,24 +123,10 @@ open class CachedURLResponse : NSObject, NSSecureCoding, NSCopying {
125123
open class URLCache : NSObject {
126124

127125
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?
142127

143128
private let syncQ = DispatchQueue(label: "org.swift.URLCache.syncQ")
144-
private let _baseDiskPath: String?
145-
private var _databaseClient: _CacheSQLiteClient?
129+
private var persistence: CachePersistence?
146130

147131
/*!
148132
@method sharedURLCache
@@ -169,19 +153,31 @@ open class URLCache : NSObject {
169153
} else {
170154
let fourMegaByte = 4 * 1024 * 1024
171155
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)
175160
sharedCache = cache
161+
cache.persistence?.setupCacheDirectory()
176162
return cache
177163
}
178164
}
179165
}
180166
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+
}
182175
}
183176
}
184177

178+
private var allCaches: [String: CachedURLResponse] = [:]
179+
private var cacheSizeInMemory: Int = 0
180+
185181
/*!
186182
@method initWithMemoryCapacity:diskCapacity:diskPath:
187183
@abstract Initializes an URLCache with the given capacity and
@@ -198,8 +194,11 @@ open class URLCache : NSObject {
198194
public init(memoryCapacity: Int, diskCapacity: Int, diskPath path: String?) {
199195
self.memoryCapacity = memoryCapacity
200196
self.diskCapacity = diskCapacity
201-
self._baseDiskPath = path
202-
197+
198+
if let _path = path {
199+
self.persistence = CachePersistence(path: _path)
200+
}
201+
203202
super.init()
204203
}
205204

@@ -272,7 +271,9 @@ open class URLCache : NSObject {
272271
usage of the in-memory cache.
273272
@result the current usage of the in-memory cache of the receiver.
274273
*/
275-
open var currentMemoryUsage: Int { NSUnimplemented() }
274+
open var currentMemoryUsage: Int {
275+
return self.syncQ.sync { self.cacheSizeInMemory }
276+
}
276277

277278
/*!
278279
@method currentDiskUsage
@@ -282,19 +283,10 @@ open class URLCache : NSObject {
282283
usage of the on-disk cache.
283284
@result the current usage of the on-disk cache of the receiver.
284285
*/
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 }
296288
}
297-
289+
298290
}
299291

300292
extension URLCache {
@@ -303,112 +295,79 @@ extension URLCache {
303295
public func removeCachedResponse(for dataTask: URLSessionDataTask) { NSUnimplemented() }
304296
}
305297

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
316309
}
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
344313
}
345314
}
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)
362320
}
321+
322+
try FileManager.default.createDirectory(atPath: path, withIntermediateDirectories: true)
323+
} catch {
324+
fatalError("Unable to create directories for URLCache: \(error.localizedDescription)")
363325
}
364-
365-
return true
366326
}
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)")
384335
}
385-
386-
return true
387336
}
388-
389-
}
390337

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 {
397342
return nil
398343
}
344+
345+
return response
399346
}
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+
}
405354
}
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)
412358
}
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+
414373
}

0 commit comments

Comments
 (0)