7
7
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
8
8
//
9
9
10
+ import SQLite3
10
11
11
12
/*!
12
13
@enum URLCache.StoragePolicy
@@ -123,6 +124,26 @@ open class CachedURLResponse : NSObject, NSSecureCoding, NSCopying {
123
124
124
125
open class URLCache : NSObject {
125
126
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
+ }
142
+
143
+ private let syncQ = DispatchQueue ( label: " org.swift.URLCache.syncQ " )
144
+ private let _baseDiskPath : String ?
145
+ private var _databaseClient : _CacheSQLiteClient ?
146
+
126
147
/*!
127
148
@method sharedURLCache
128
149
@abstract Returns the shared URLCache instance.
@@ -142,10 +163,22 @@ open class URLCache : NSObject {
142
163
*/
143
164
open class var shared : URLCache {
144
165
get {
145
- NSUnimplemented ( )
166
+ return sharedSyncQ. sync {
167
+ if let cache = sharedCache {
168
+ return cache
169
+ } else {
170
+ let fourMegaByte = 4 * 1024 * 1024
171
+ 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)
175
+ sharedCache = cache
176
+ return cache
177
+ }
178
+ }
146
179
}
147
180
set {
148
- NSUnimplemented ( )
181
+ sharedSyncQ . sync { sharedCache = newValue }
149
182
}
150
183
}
151
184
@@ -162,7 +195,13 @@ open class URLCache : NSObject {
162
195
@result an initialized URLCache, with the given capacity, backed
163
196
by disk.
164
197
*/
165
- public init ( memoryCapacity: Int , diskCapacity: Int , diskPath path: String ? ) { NSUnimplemented ( ) }
198
+ public init ( memoryCapacity: Int , diskCapacity: Int , diskPath path: String ? ) {
199
+ self . memoryCapacity = memoryCapacity
200
+ self . diskCapacity = diskCapacity
201
+ self . _baseDiskPath = path
202
+
203
+ super. init ( )
204
+ }
166
205
167
206
/*!
168
207
@method cachedResponseForRequest:
@@ -244,10 +283,132 @@ open class URLCache : NSObject {
244
283
@result the current usage of the on-disk cache of the receiver.
245
284
*/
246
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
+ }
296
+ }
297
+
247
298
}
248
299
249
300
extension URLCache {
250
301
public func storeCachedResponse( _ cachedResponse: CachedURLResponse , for dataTask: URLSessionDataTask ) { NSUnimplemented ( ) }
251
302
public func getCachedResponse( for dataTask: URLSessionDataTask , completionHandler: ( CachedURLResponse ? ) -> Void ) { NSUnimplemented ( ) }
252
303
public func removeCachedResponse( for dataTask: URLSessionDataTask ) { NSUnimplemented ( ) }
253
304
}
305
+
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) " )
316
+ }
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 " )
344
+ }
345
+ }
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
362
+ }
363
+ }
364
+
365
+ return true
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
+ }
384
+ }
385
+
386
+ return true
387
+ }
388
+
389
+ }
390
+
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 {
397
+ return nil
398
+ }
399
+ }
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
405
+ }
406
+
407
+ mutating func close( ) {
408
+ guard let db = database else { return }
409
+
410
+ sqlite3_close_v2 ( db)
411
+ database = nil
412
+ }
413
+
414
+ }
0 commit comments