@ -7,10 +7,12 @@ import GRDB
import SignalCoreKit
open class Storage {
public static let queuePrefix : String = " SessionDatabase "
private static let dbFileName : String = " Session.sqlite "
private static let keychainService : String = " TSKeyChainService "
private static let dbCipherKeySpecKey : String = " GRDBDatabaseCipherKeySpec "
private static let kSQLCipherKeySpecLength : Int = 48
private static let writeWarningThreadshold : TimeInterval = 3
private static var sharedDatabaseDirectoryPath : String { " \( OWSFileSystem . appSharedDataDirectoryPath ( ) ) /database " }
private static var databasePath : String { " \( Storage . sharedDatabaseDirectoryPath ) / \( Storage . dbFileName ) " }
@ -78,6 +80,7 @@ open class Storage {
// C o n f i g u r e t h e d a t a b a s e a n d c r e a t e t h e D a t a b a s e P o o l f o r i n t e r a c t i n g w i t h t h e d a t a b a s e
var config = Configuration ( )
config . label = Storage . queuePrefix
config . maximumReaderCount = 10 // I n c r e a s e t h e m a x r e a d c o n n e c t i o n l i m i t - D e f a u l t i s 5
config . observesSuspensionNotifications = true // M i n i m i s e ` 0 x D E A D 1 0 C C ` e x c e p t i o n s
config . prepareDatabase { db in
@ -365,7 +368,29 @@ open class Storage {
try SSKDefaultKeychainStorage . shared . remove ( service : keychainService , key : dbCipherKeySpecKey )
}
// MARK: - F u n c t i o n s
// MARK: - L o g g i n g F u n c t i o n s
typealias CallInfo = ( file : String , function : String , line : Int )
private static func logSlowWrites < T > (
info : CallInfo ,
updates : @ escaping ( Database ) throws -> T
) -> ( Database ) throws -> T {
return { db in
let timeout : Timer = Timer . scheduledTimerOnMainThread ( withTimeInterval : writeWarningThreadshold ) {
$0 . invalidate ( )
// D o n ' t w a n t t o l o g o n t h e m a i n t h r e a d a s t o a v o i d c o n f u s i o n w h e n d e b u g g i n g i s s u e s
DispatchQueue . global ( qos : . default ) . async {
let fileName : String = ( info . file . components ( separatedBy : " / " ) . last . map { " \( $0 ) : \( info . line ) " } ? ? " " )
SNLog ( " [Storage \( fileName ) ] Slow write taking longer than \( writeWarningThreadshold ) s - \( info . function ) " )
}
}
defer { timeout . invalidate ( ) }
return try updates ( db )
}
}
private static func logIfNeeded ( _ error : Error , isWrite : Bool ) {
switch error {
@ -382,22 +407,50 @@ open class Storage {
return nil
}
@ discardableResult public final func write < T > ( updates : ( Database ) throws -> T ? ) -> T ? {
// MARK: - F u n c t i o n s
@ discardableResult public final func write < T > (
fileName : String = #file ,
functionName : String = #function ,
lineNumber : Int = #line ,
updates : @ escaping ( Database ) throws -> T ?
) -> T ? {
guard isValid , let dbWriter : DatabaseWriter = dbWriter else { return nil }
do { return try dbWriter . write ( updates ) }
let info : CallInfo = ( fileName , functionName , lineNumber )
do { return try dbWriter . write ( Storage . logSlowWrites ( info : info , updates : updates ) ) }
catch { return Storage . logIfNeeded ( error , isWrite : true ) }
}
open func writeAsync < T > ( updates : @ escaping ( Database ) throws -> T ) {
writeAsync ( updates : updates , completion : { _ , _ in } )
open func writeAsync < T > (
fileName : String = #file ,
functionName : String = #function ,
lineNumber : Int = #line ,
updates : @ escaping ( Database ) throws -> T
) {
writeAsync (
fileName : fileName ,
functionName : functionName ,
lineNumber : lineNumber ,
updates : updates ,
completion : { _ , _ in }
)
}
open func writeAsync < T > ( updates : @ escaping ( Database ) throws -> T , completion : @ escaping ( Database , Swift . Result < T , Error > ) throws -> Void ) {
open func writeAsync < T > (
fileName : String = #file ,
functionName : String = #function ,
lineNumber : Int = #line ,
updates : @ escaping ( Database ) throws -> T ,
completion : @ escaping ( Database , Swift . Result < T , Error > ) throws -> Void
) {
guard isValid , let dbWriter : DatabaseWriter = dbWriter else { return }
let info : CallInfo = ( fileName , functionName , lineNumber )
dbWriter . asyncWrite (
updates ,
Storage. logSlowWrites ( info : info , updates: updates ) ,
completion : { db , result in
switch result {
case . failure ( let error ) : Storage . logIfNeeded ( error , isWrite : true )
@ -410,6 +463,9 @@ open class Storage {
}
open func writePublisher < T > (
fileName : String = #file ,
functionName : String = #function ,
lineNumber : Int = #line ,
updates : @ escaping ( Database ) throws -> T
) -> AnyPublisher < T , Error > {
guard isValid , let dbWriter : DatabaseWriter = dbWriter else {
@ -417,6 +473,8 @@ open class Storage {
. eraseToAnyPublisher ( )
}
let info : CallInfo = ( fileName , functionName , lineNumber )
// / * * N o t e : * * G R D B d o e s h a v e a ` w r i t e P u b l i s h e r ` m e t h o d b u t i t a p p e a r s t o a s y n c h r o n o u s l y t r i g g e r
// / b o t h t h e ` o u t p u t ` a n d ` c o m p l e t e ` c l o s u r e s a t t h e s a m e t i m e w h i c h c a u s e s a l o t o f u n e x p e c t e d
// / b e h a v i o u r s ( t h i s b e h a v i o u r i s a p p a r e n t l y e x p e c t e d b u t s t i l l c a u s e s a n u m b e r o f o d d b e h a v i o u r s i n o u r c o d e
@ -426,7 +484,7 @@ open class Storage {
// / w h i c h b e h a v e s i n a m u c h m o r e e x p e c t e d w a y t h a n t h e G R D B ` w r i t e P u b l i s h e r ` d o e s
return Deferred {
Future { resolver in
do { resolver ( Result . success ( try dbWriter . write ( updates) ) ) }
do { resolver ( Result . success ( try dbWriter . write ( Storage. logSlowWrites ( info : info , updates: updates ) ) ) ) }
catch {
Storage . logIfNeeded ( error , isWrite : true )
resolver ( Result . failure ( error ) )