// C o p y r i g h t © 2 0 2 3 R a n g e p r o o f P t y L t d . A l l r i g h t s r e s e r v e d .
import UIKit
import GRDB
import SessionUIKit
import SessionSnodeKit
import SessionUtil
import SessionUtilitiesKit
// MARK: - C o n v e n i e n c e
public extension LibSession {
enum Crypto {
public typealias Domain = String
}
// / T h e d e f a u l t p r i o r i t y f o r n e w l y c r e a t e d t h r e a d s - t h e d e f a u l t v a l u e i s f o r t h r e a d s t o b e h i d d e n a s w e e x p l i c i t l y m a k e t h r e a d s v i s i b l e
// / w h e n s e n d i n g o r r e c e i v i n g m e s s a g e s
static var defaultNewThreadPriority : Int32 { return hiddenPriority }
// / A ` 0 ` ` p r i o r i t y ` v a l u e i n d i c a t e s v i s i b l e , b u t n o t p i n n e d
static let visiblePriority : Int32 = 0
// / A n e g a t i v e ` p r i o r i t y ` v a l u e i n d i c a t e s h i d d e n
static let hiddenPriority : Int32 = - 1
}
internal extension LibSession {
// / T h i s i s a b u f f e r p e r i o d w i t h i n w h i c h w e w i l l p r o c e s s m e s s a g e s w h i c h w o u l d r e s u l t i n a c o n f i g c h a n g e , a n y m e s s a g e w h i c h w o u l d n o r m a l l y
// / r e s u l t i n a c o n f i g c h a n g e w h i c h w a s s e n t b e f o r e ` l a s t C o n f i g M e s s a g e . t i m e s t a m p - c o n f i g C h a n g e B u f f e r P e r i o d ` w i l l n o t
// / a c t u a l l y h a v e i t ' s c h a n g e s a p p l i e d ( i n f o m e s s a g e s w o u l d s t i l l b e i n s e r t e d t h o u g h )
static let configChangeBufferPeriod : TimeInterval = ( 2 * 60 )
static let columnsRelatedToThreads : [ ColumnExpression ] = [
SessionThread . Columns . pinnedPriority ,
SessionThread . Columns . shouldBeVisible
]
static func assignmentsRequireConfigUpdate ( _ assignments : [ ConfigColumnAssignment ] ) -> Bool {
let targetColumns : Set < ColumnKey > = Set ( assignments . map { ColumnKey ( $0 . column ) } )
let allColumnsThatTriggerConfigUpdate : Set < ColumnKey > = [ ]
. appending ( contentsOf : columnsRelatedToUserProfile )
. appending ( contentsOf : columnsRelatedToContacts )
. appending ( contentsOf : columnsRelatedToConvoInfoVolatile )
. appending ( contentsOf : columnsRelatedToUserGroups )
. appending ( contentsOf : columnsRelatedToThreads )
. appending ( contentsOf : columnsRelatedToGroupInfo )
. appending ( contentsOf : columnsRelatedToGroupMembers )
. appending ( contentsOf : columnsRelatedToGroupKeys )
. map { ColumnKey ( $0 ) }
. asSet ( )
return ! allColumnsThatTriggerConfigUpdate . isDisjoint ( with : targetColumns )
}
static func shouldBeVisible ( priority : Int32 ) -> Bool {
return ( priority >= LibSession . visiblePriority )
}
@ discardableResult static func updatingThreads < T > (
_ db : Database ,
_ updated : [ T ] ,
using dependencies : Dependencies
) throws -> [ T ] {
guard let updatedThreads : [ SessionThread ] = updated as ? [ SessionThread ] else {
throw StorageError . generic
}
// I f w e h a v e n o u p d a t e d t h r e a d s t h e n n o n e e d t o c o n t i n u e
guard ! updatedThreads . isEmpty else { return updated }
// E x c l u d e a n y " d r a f t " c o n v e r s a t i o n s f r o m u p d a t i n g ` l i b S e s s i o n ` ( w e d o n ' t w a n t t h e m t o b e
// s y n c e d u n t i l t h e y t u r n i n t o " r e a l " c o n v e r s a t i o n s )
let targetThreads : [ SessionThread ] = updatedThreads . filter {
$0 . isDraft != true
}
let userSessionId : SessionId = dependencies [ cache : . general ] . sessionId
let groupedThreads : [ SessionThread . Variant : [ SessionThread ] ] = targetThreads
. grouped ( by : \ . variant )
let urlInfo : [ String : OpenGroupUrlInfo ] = try OpenGroupUrlInfo
. fetchAll ( db , ids : targetThreads . map { $0 . id } )
. reduce ( into : [ : ] ) { result , next in result [ next . threadId ] = next }
// U p d a t e t h e u n r e a d s t a t e f o r t h e t h r e a d s f i r s t ( j u s t i n c a s e t h a t ' s w h a t c h a n g e d )
try LibSession . updateMarkedAsUnreadState ( db , threads : targetThreads , using : dependencies )
// T h e n u p d a t e t h e ` h i d d e n ` a n d ` p r i o r i t y ` v a l u e s
try groupedThreads . forEach { variant , threads in
switch variant {
case . contact :
// I f t h e ' N o t e t o S e l f ' c o n v e r s a t i o n i s p i n n e d t h e n w e n e e d t o c u s t o m h a n d l e i t
// f i r s t a s i t ' s p a r t o f t h e U s e r P r o f i l e c o n f i g
if let noteToSelf : SessionThread = threads . first ( where : { $0 . id = = userSessionId . hexString } ) {
try dependencies . mutate ( cache : . libSession ) { cache in
try cache . performAndPushChange ( db , for : . userProfile , sessionId : userSessionId ) { config in
try LibSession . updateNoteToSelf (
priority : {
guard noteToSelf . shouldBeVisible else { return LibSession . hiddenPriority }
return noteToSelf . pinnedPriority
. map { Int32 ( $0 = = 0 ? LibSession . visiblePriority : max ( $0 , 1 ) ) }
. defaulting ( to : LibSession . visiblePriority )
} ( ) ,
in : config
)
}
}
}
// R e m o v e t h e ' N o t e t o S e l f ' c o n v o f r o m t h e l i s t f o r u p d a t i n g c o n t a c t p r i o r i t i e s
let remainingThreads : [ SessionThread ] = threads . filter { $0 . id != userSessionId . hexString }
guard ! remainingThreads . isEmpty else { return }
try dependencies . mutate ( cache : . libSession ) { cache in
try cache . performAndPushChange ( db , for : . contacts , sessionId : userSessionId ) { config in
try LibSession . upsert (
contactData : remainingThreads
. map { thread in
SyncedContactInfo (
id : thread . id ,
priority : {
guard thread . shouldBeVisible else { return LibSession . hiddenPriority }
return thread . pinnedPriority
. map { Int32 ( $0 = = 0 ? LibSession . visiblePriority : max ( $0 , 1 ) ) }
. defaulting ( to : LibSession . visiblePriority )
} ( )
)
} ,
in : config ,
using : dependencies
)
}
}
case . community :
try dependencies . mutate ( cache : . libSession ) { cache in
try cache . performAndPushChange ( db , for : . userGroups , sessionId : userSessionId ) { config in
try LibSession . upsert (
communities : threads
. compactMap { thread -> CommunityInfo ? in
urlInfo [ thread . id ] . map { urlInfo in
CommunityInfo (
urlInfo : urlInfo ,
priority : thread . pinnedPriority
. map { Int32 ( $0 = = 0 ? LibSession . visiblePriority : max ( $0 , 1 ) ) }
. defaulting ( to : LibSession . visiblePriority )
)
}
} ,
in : config
)
}
}
case . legacyGroup :
try dependencies . mutate ( cache : . libSession ) { cache in
try cache . performAndPushChange ( db , for : . userGroups , sessionId : userSessionId ) { config in
try LibSession . upsert (
legacyGroups : threads
. map { thread in
LegacyGroupInfo (
id : thread . id ,
priority : thread . pinnedPriority
. map { Int32 ( $0 = = 0 ? LibSession . visiblePriority : max ( $0 , 1 ) ) }
. defaulting ( to : LibSession . visiblePriority )
)
} ,
in : config
)
}
}
case . group :
try dependencies . mutate ( cache : . libSession ) { cache in
try cache . performAndPushChange ( db , for : . userGroups , sessionId : userSessionId ) { config in
try LibSession . upsert (
groups : threads
. map { thread in
GroupUpdateInfo (
groupSessionId : thread . id ,
priority : thread . pinnedPriority
. map { Int32 ( $0 = = 0 ? LibSession . visiblePriority : max ( $0 , 1 ) ) }
. defaulting ( to : LibSession . visiblePriority )
)
} ,
in : config ,
using : dependencies
)
}
}
}
}
return updated
}
static func hasSetting (
_ db : Database ,
forKey key : String ,
using dependencies : Dependencies
) throws -> Bool {
let userSessionId : SessionId = dependencies [ cache : . general ] . sessionId
// C u r r e n t l y t h e o n l y s y n c e d s e t t i n g i s ' c h e c k F o r C o m m u n i t y M e s s a g e R e q u e s t s '
switch key {
case Setting . BoolKey . checkForCommunityMessageRequests . rawValue :
return dependencies . mutate ( cache : . libSession ) { cache in
let config : LibSession . Config ? = cache . config ( for : . userProfile , sessionId : userSessionId )
return ( ( ( try ? LibSession . rawBlindedMessageRequestValue ( in : config ) ) ? ? 0 ) >= 0 )
}
default : return false
}
}
static func updatingSetting (
_ db : Database ,
_ updated : Setting ? ,
using dependencies : Dependencies
) throws {
// D o n ' t c u r r e n t s u p p o r t a n y n u l l a b l e s e t t i n g s
guard let updatedSetting : Setting = updated else { return }
let userSessionId : SessionId = dependencies [ cache : . general ] . sessionId
// C u r r e n t l y t h e o n l y s y n c e d s e t t i n g i s ' c h e c k F o r C o m m u n i t y M e s s a g e R e q u e s t s '
switch updatedSetting . id {
case Setting . BoolKey . checkForCommunityMessageRequests . rawValue :
try dependencies . mutate ( cache : . libSession ) { cache in
try cache . performAndPushChange ( db , for : . userProfile , sessionId : userSessionId ) { config in
try LibSession . updateSettings (
checkForCommunityMessageRequests : updatedSetting . unsafeValue ( as : Bool . self ) ,
in : config
)
}
}
default : break
}
}
static func kickFromConversationUIIfNeeded ( removedThreadIds : [ String ] , using dependencies : Dependencies ) {
guard ! removedThreadIds . isEmpty else { return }
// I f t h e u s e r i s c u r r e n t l y n a v i g a t i n g s o m e w h e r e w i t h i n t h e v i e w h i e r a r c h y o f a c o n v e r s a t i o n
// w e j u s t d e l e t e d t h e n r e t u r n t o t h e h o m e s c r e e n
DispatchQueue . main . async {
guard
let rootViewController : UIViewController = dependencies [ singleton : . appContext ] . mainWindow ? . rootViewController ,
let topBannerController : TopBannerController = ( rootViewController as ? TopBannerController ) ,
! topBannerController . children . isEmpty ,
let navController : UINavigationController = topBannerController . children [ 0 ] as ? UINavigationController
else { return }
// E x t r a c t t h e o n e s w h i c h w i l l r e s p o n d t o L i b S e s s i o n c h a n g e s
let targetViewControllers : [ any LibSessionRespondingViewController ] = navController
. viewControllers
. compactMap { $0 as ? LibSessionRespondingViewController }
let presentedNavController : UINavigationController ? = ( navController . presentedViewController as ? UINavigationController )
let presentedTargetViewControllers : [ any LibSessionRespondingViewController ] = ( presentedNavController ?
. viewControllers
. compactMap { $0 as ? LibSessionRespondingViewController } )
. defaulting ( to : [ ] )
// M a k e s u r e w e h a v e a c o n v e r s a t i o n l i s t a n d t h a t o n e o f t h e r e m o v e d c o n v e r s a t i o n s a r e
// i n t h e n a v h i e r a r c h y
let rootNavControllerNeedsPop : Bool = (
targetViewControllers . count > 1 &&
targetViewControllers . contains ( where : { $0 . isConversationList } ) &&
targetViewControllers . contains ( where : { $0 . isConversation ( in : removedThreadIds ) } )
)
let presentedNavControllerNeedsPop : Bool = (
presentedTargetViewControllers . count > 1 &&
presentedTargetViewControllers . contains ( where : { $0 . isConversationList } ) &&
presentedTargetViewControllers . contains ( where : { $0 . isConversation ( in : removedThreadIds ) } )
)
// F o r c e t h e U I t o r e f r e s h i f n e e d e d ( m o s t s c r e e n s s h o u l d d o t h i s a u t o m a t i c a l l y v i a d a t a b a s e
// o b s e r v a t i o n , b u t a c o u p l e o f s c r e e n s d o n ' t s o n e e d t o b e d o n e m a n u a l l y )
targetViewControllers
. appending ( contentsOf : presentedTargetViewControllers )
. filter { $0 . isConversationList }
. forEach { $0 . forceRefreshIfNeeded ( ) }
switch ( rootNavControllerNeedsPop , presentedNavControllerNeedsPop ) {
case ( true , false ) :
// R e t u r n t o t h e c o n v e r s a t i o n l i s t a s t h e r e m o v e d c o n v e r s a t i o n w i l l b e i n v a l i d
guard
let targetViewController : UIViewController = navController . viewControllers
. last ( where : { viewController in
( ( viewController as ? LibSessionRespondingViewController ) ? . isConversationList )
. defaulting ( to : false )
} )
else { return }
if navController . presentedViewController != nil {
navController . dismiss ( animated : false ) {
navController . popToViewController ( targetViewController , animated : true )
}
}
else {
navController . popToViewController ( targetViewController , animated : true )
}
case ( false , true ) :
// R e t u r n t o t h e c o n v e r s a t i o n l i s t a s t h e r e m o v e d c o n v e r s a t i o n w i l l b e i n v a l i d
guard
let targetViewController : UIViewController = presentedNavController ?
. viewControllers
. last ( where : { viewController in
( ( viewController as ? LibSessionRespondingViewController ) ? . isConversationList )
. defaulting ( to : false )
} )
else { return }
if presentedNavController ? . presentedViewController != nil {
presentedNavController ? . dismiss ( animated : false ) {
presentedNavController ? . popToViewController ( targetViewController , animated : true )
}
}
else {
presentedNavController ? . popToViewController ( targetViewController , animated : true )
}
default : break
}
}
}
static func canPerformChange (
_ db : Database ,
threadId : String ,
targetConfig : ConfigDump . Variant ,
changeTimestampMs : Int64 ,
using dependencies : Dependencies
) -> Bool {
let targetSessionId : String = {
switch targetConfig {
case . userProfile , . contacts , . convoInfoVolatile , . userGroups :
return dependencies [ cache : . general ] . sessionId . hexString
case . groupInfo , . groupMembers , . groupKeys : return threadId
case . invalid : return " "
}
} ( )
let configDumpTimestampMs : Int64 = ( try ? ConfigDump
. filter (
ConfigDump . Columns . variant = = targetConfig &&
ConfigDump . Columns . sessionId = = targetSessionId
)
. select ( . timestampMs )
. asRequest ( of : Int64 . self )
. fetchOne ( db ) )
. defaulting ( to : 0 )
// E n s u r e t h e c h a n g e o c c u r r e d a f t e r t h e l a s t c o n f i g m e s s a g e w a s h a n d l e d ( m i n u s t h e b u f f e r p e r i o d )
return ( changeTimestampMs >= ( configDumpTimestampMs - Int64 ( LibSession . configChangeBufferPeriod * 1000 ) ) )
}
static func checkLoopLimitReached ( _ loopCounter : inout Int , for variant : ConfigDump . Variant , maxLoopCount : Int = 50000 ) throws {
loopCounter += 1
guard loopCounter < maxLoopCount else {
Log . critical ( . libSession , " Got stuck in infinite loop processing ' \( variant ) ' data " )
throw LibSessionError . processingLoopLimitReached
}
}
}
// MARK: - S t a t e A c c e s s
extension LibSession . Config {
public func pinnedPriority (
_ db : Database ,
threadId : String ,
threadVariant : SessionThread . Variant
) -> Int32 ? {
guard var cThreadId : [ CChar ] = threadId . cString ( using : . utf8 ) else {
return LibSession . defaultNewThreadPriority
}
switch ( threadVariant , self ) {
case ( _ , . userProfile ( let conf ) ) : return user_profile_get_nts_priority ( conf )
case ( _ , . contacts ( let conf ) ) :
var contact : contacts_contact = contacts_contact ( )
guard contacts_get ( conf , & contact , & cThreadId ) else {
LibSessionError . clear ( conf )
return LibSession . defaultNewThreadPriority
}
return contact . priority
case ( . community , . userGroups ( let conf ) ) :
guard
let urlInfo : LibSession . OpenGroupUrlInfo = try ? LibSession . OpenGroupUrlInfo . fetchOne ( db , id : threadId ) ,
var cBaseUrl : [ CChar ] = urlInfo . server . cString ( using : . utf8 ) ,
var cRoom : [ CChar ] = urlInfo . roomToken . cString ( using : . utf8 )
else { return LibSession . defaultNewThreadPriority }
var community : ugroups_community_info = ugroups_community_info ( )
_ = user_groups_get_community ( conf , & community , & cBaseUrl , & cRoom )
LibSessionError . clear ( conf )
return community . priority
case ( . legacyGroup , . userGroups ( let conf ) ) :
let groupInfo : UnsafeMutablePointer < ugroups_legacy_group_info > ? = user_groups_get_legacy_group ( conf , & cThreadId )
LibSessionError . clear ( conf )
defer {
if groupInfo != nil {
ugroups_legacy_group_free ( groupInfo )
}
}
return ( groupInfo ? . pointee . priority ? ? LibSession . defaultNewThreadPriority )
case ( . group , . userGroups ( let conf ) ) :
var group : ugroups_group_info = ugroups_group_info ( )
_ = user_groups_get_group ( conf , & group , & cThreadId )
LibSessionError . clear ( conf )
return group . priority
default :
Log . warn ( . libSession , " Attempted to retrieve priority for invalid combination of threadVariant: \( threadVariant ) and config variant: \( variant ) " )
return LibSession . defaultNewThreadPriority
}
}
public func disappearingMessagesConfig (
threadId : String ,
threadVariant : SessionThread . Variant
) -> DisappearingMessagesConfiguration ? {
guard var cThreadId : [ CChar ] = threadId . cString ( using : . utf8 ) else { return nil }
switch ( threadVariant , self ) {
case ( . community , _ ) : return nil
case ( _ , . userProfile ( let conf ) ) :
let targetExpiry : Int32 = user_profile_get_nts_expiry ( conf )
let targetIsEnabled : Bool = ( targetExpiry > 0 )
return DisappearingMessagesConfiguration (
threadId : threadId ,
isEnabled : targetIsEnabled ,
durationSeconds : TimeInterval ( targetExpiry ) ,
type : targetIsEnabled ? . disappearAfterSend : . unknown
)
case ( _ , . contacts ( let conf ) ) :
var contact : contacts_contact = contacts_contact ( )
guard contacts_get ( conf , & contact , & cThreadId ) else {
LibSessionError . clear ( conf )
return nil
}
return DisappearingMessagesConfiguration (
threadId : threadId ,
isEnabled : contact . exp_seconds > 0 ,
durationSeconds : TimeInterval ( contact . exp_seconds ) ,
type : DisappearingMessagesConfiguration . DisappearingMessageType (
libSessionType : contact . exp_mode
)
)
case ( . legacyGroup , . userGroups ( let conf ) ) :
let groupInfo : UnsafeMutablePointer < ugroups_legacy_group_info > ? = user_groups_get_legacy_group ( conf , & cThreadId )
LibSessionError . clear ( conf )
defer {
if groupInfo != nil {
ugroups_legacy_group_free ( groupInfo )
}
}
return groupInfo . map { info in
DisappearingMessagesConfiguration (
threadId : threadId ,
isEnabled : ( info . pointee . disappearing_timer > 0 ) ,
durationSeconds : TimeInterval ( info . pointee . disappearing_timer ) ,
type : . disappearAfterSend
)
}
case ( . group , . groupInfo ( let conf ) ) :
let durationSeconds : Int32 = groups_info_get_expiry_timer ( conf )
return DisappearingMessagesConfiguration (
threadId : threadId ,
isEnabled : ( durationSeconds > 0 ) ,
durationSeconds : TimeInterval ( durationSeconds ) ,
type : . disappearAfterSend
)
default :
Log . warn ( . libSession , " Attempted to retrieve disappearing messages config for invalid combination of threadVariant: \( threadVariant ) and config variant: \( variant ) " )
return nil
}
}
public func isAdmin ( ) -> Bool {
guard case . groupKeys ( let conf , _ , _ ) = self else { return false }
return groups_keys_is_admin ( conf )
}
}
public extension LibSession {
static func conversationInConfig (
_ db : Database ,
threadId : String ,
threadVariant : SessionThread . Variant ,
visibleOnly : Bool ,
using dependencies : Dependencies
) -> Bool {
// C u r r e n t l y b l i n d e d c o n v e r s a t i o n s c a n n o t b e c o n t a i n e d i n t h e c o n f i g , s o t h e r e i s n o
// p o i n t c h e c k i n g ( i t ' l l a l w a y s b e f a l s e )
guard
threadVariant = = . community || (
( try ? SessionId ( from : threadId ) ) ? . prefix != . blinded15 &&
( try ? SessionId ( from : threadId ) ) ? . prefix != . blinded25
)
else { return false }
let userSessionId : SessionId = dependencies [ cache : . general ] . sessionId
let configVariant : ConfigDump . Variant = {
switch threadVariant {
case . contact : return ( threadId = = userSessionId . hexString ? . userProfile : . contacts )
case . legacyGroup , . group , . community : return . userGroups
}
} ( )
return dependencies . mutate ( cache : . libSession ) { cache in
guard var cThreadId : [ CChar ] = threadId . cString ( using : . utf8 ) else { return false }
switch ( threadVariant , cache . config ( for : configVariant , sessionId : userSessionId ) ) {
case ( _ , . userProfile ( let conf ) ) :
return (
! visibleOnly ||
LibSession . shouldBeVisible ( priority : user_profile_get_nts_priority ( conf ) )
)
case ( _ , . contacts ( let conf ) ) :
var contact : contacts_contact = contacts_contact ( )
guard contacts_get ( conf , & contact , & cThreadId ) else {
LibSessionError . clear ( conf )
return false
}
// / I f t h e u s e r o p e n s a c o n v e r s a t i o n w i t h a n e x i s t i n g c o n t a c t b u t d o e s n ' t s e n d t h e m a m e s s a g e
// / t h e n t h e o n e - t o - o n e c o n v e r s a t i o n s h o u l d r e m a i n h i d d e n s o w e w a n t t o d e l e t e t h e ` S e s s i o n T h r e a d `
// / w h e n l e a v i n g t h e c o n v e r s a t i o n
return ( ! visibleOnly || LibSession . shouldBeVisible ( priority : contact . priority ) )
case ( . community , . userGroups ( let conf ) ) :
let maybeUrlInfo : OpenGroupUrlInfo ? = ( try ? OpenGroupUrlInfo
. fetchAll ( db , ids : [ threadId ] ) ) ?
. first
guard
let urlInfo : OpenGroupUrlInfo = maybeUrlInfo ,
var cBaseUrl : [ CChar ] = urlInfo . server . cString ( using : . utf8 ) ,
var cRoom : [ CChar ] = urlInfo . roomToken . cString ( using : . utf8 )
else { return false }
var community : ugroups_community_info = ugroups_community_info ( )
// / N o t h a n d l i n g t h e ` h i d d e n ` b e h a v i o u r f o r c o m m u n i t i e s s o j u s t i n d i c a t e t h e e x i s t e n c e
let result : Bool = user_groups_get_community ( conf , & community , & cBaseUrl , & cRoom )
LibSessionError . clear ( conf )
return result
case ( . legacyGroup , . userGroups ( let conf ) ) :
let groupInfo : UnsafeMutablePointer < ugroups_legacy_group_info > ? = user_groups_get_legacy_group ( conf , & cThreadId )
LibSessionError . clear ( conf )
// / N o t h a n d l i n g t h e ` h i d d e n ` b e h a v i o u r f o r l e g a c y g r o u p s s o j u s t i n d i c a t e t h e e x i s t e n c e
if groupInfo != nil {
ugroups_legacy_group_free ( groupInfo )
return true
}
return false
case ( . group , . userGroups ( let conf ) ) :
var group : ugroups_group_info = ugroups_group_info ( )
// / N o t h a n d l i n g t h e ` h i d d e n ` b e h a v i o u r f o r l e g a c y g r o u p s s o j u s t i n d i c a t e t h e e x i s t e n c e
return user_groups_get_group ( conf , & group , & cThreadId )
default : return false
}
}
}
}
// MARK: - C o l u m n K e y
internal extension LibSession {
struct ColumnKey : Equatable , Hashable {
let sourceType : Any . Type
let columnName : String
init ( _ column : ColumnExpression ) {
self . sourceType = type ( of : column )
self . columnName = column . name
}
func hash ( into hasher : inout Hasher ) {
ObjectIdentifier ( sourceType ) . hash ( into : & hasher )
columnName . hash ( into : & hasher )
}
static func = = ( lhs : ColumnKey , rhs : ColumnKey ) -> Bool {
return (
lhs . sourceType = = rhs . sourceType &&
lhs . columnName = = rhs . columnName
)
}
}
}
// MARK: - P r i o r i t y V i s i b i l i t y I n f o
extension LibSession {
struct PriorityVisibilityInfo : Codable , FetchableRecord , Identifiable {
let id : String
let variant : SessionThread . Variant
let pinnedPriority : Int32 ?
let shouldBeVisible : Bool
}
}
// MARK: - L i b S e s s i o n R e s p o n d i n g V i e w C o n t r o l l e r
public protocol LibSessionRespondingViewController {
var isConversationList : Bool { get }
func isConversation ( in threadIds : [ String ] ) -> Bool
func forceRefreshIfNeeded ( )
}
public extension LibSessionRespondingViewController {
var isConversationList : Bool { false }
func isConversation ( in threadIds : [ String ] ) -> Bool { return false }
func forceRefreshIfNeeded ( ) { }
}