Move all Core related code to another dispatch queue. requires sdk built with feature/swift_wrapper_async_helpers

This commit is contained in:
QuentinArguillere 2023-11-03 17:31:59 +01:00
parent 3fe7dd8884
commit a3befe61cf
9 changed files with 245 additions and 190 deletions

View file

@ -540,6 +540,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
@ -600,6 +601,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";

View file

@ -38,16 +38,16 @@ final class ContactsManager: ObservableObject {
}
func fetchContacts() {
DispatchQueue.global().async {
if self.coreContext.mCore.globalState == GlobalState.Shutdown || self.coreContext.mCore.globalState == GlobalState.Off {
coreContext.doOnCoreQueue { core in
if core.globalState == GlobalState.Shutdown || core.globalState == GlobalState.Off {
print("$TAG Core is being stopped or already destroyed, abort")
} else {
print("$TAG ${friends.size} friends created")
self.friendList = self.coreContext.mCore.getFriendListByName(name: self.nativeAddressBookFriendList)
self.friendList = core.getFriendListByName(name: self.nativeAddressBookFriendList)
if self.friendList == nil {
do {
self.friendList = try self.coreContext.mCore.createFriendList()
self.friendList = try core.createFriendList()
} catch let error {
print("Failed to enumerate contact", error)
}
@ -61,7 +61,7 @@ final class ContactsManager: ObservableObject {
self.friendList!.databaseStorageEnabled = false // We don't want to store local address-book in DB
self.friendList!.displayName = self.nativeAddressBookFriendList
self.coreContext.mCore.addFriendList(list: self.friendList!)
core.addFriendList(list: self.friendList!)
} else {
print(
"$TAG Friend list [$LINPHONE_ADDRESS_BOOK_FRIEND_LIST] found, removing existing friends if any"
@ -159,52 +159,58 @@ final class ContactsManager: ObservableObject {
}
awaitDataWrite(data: data, name: name) { _, result in
do {
let friend = try self.coreContext.mCore.createFriend()
friend.edit()
try friend.setName(newValue: contact.firstName + " " + contact.lastName)
friend.organization = contact.organizationName
var friendAddresses: [Address] = []
contact.sipAddresses.forEach { sipAddress in
let address = self.coreContext.mCore.interpretUrl(url: sipAddress, applyInternationalPrefix: true)
self.coreContext.doOnCoreQueue() { core in
do {
var friend = try core.createFriend()
if address != nil && ((friendAddresses.firstIndex(where: {$0.asString() == address?.asString()})) == nil) {
friend.addAddress(address: address!)
friendAddresses.append(address!)
}
}
var friendPhoneNumbers: [PhoneNumber] = []
contact.phoneNumbers.forEach { phone in
do {
if (friendPhoneNumbers.firstIndex(where: {$0.numLabel == phone.numLabel})) == nil {
let labelDrop = String(phone.numLabel.dropFirst(4).dropLast(4))
let phoneNumber = try Factory.Instance.createFriendPhoneNumber(phoneNumber: phone.num, label: labelDrop)
friend.addPhoneNumberWithLabel(phoneNumber: phoneNumber)
friendPhoneNumbers.append(phone)
friend.edit()
try friend.setName(newValue: contact.firstName + " " + contact.lastName)
friend.organization = contact.organizationName
var friendAddresses: [Address] = []
contact.sipAddresses.forEach { sipAddress in
let address = core.interpretUrl(url: sipAddress, applyInternationalPrefix: true)
if address != nil && ((friendAddresses.firstIndex(where: {$0.asString() == address?.asString()})) == nil) {
friend.addAddress(address: address!)
friendAddresses.append(address!)
}
} catch let error {
print("Failed to enumerate contact", error)
}
var friendPhoneNumbers: [PhoneNumber] = []
contact.phoneNumbers.forEach { phone in
do {
if (friendPhoneNumbers.firstIndex(where: {$0.numLabel == phone.numLabel})) == nil {
let labelDrop = String(phone.numLabel.dropFirst(4).dropLast(4))
let phoneNumber = try Factory.Instance.createFriendPhoneNumber(phoneNumber: phone.num, label: labelDrop)
friend.addPhoneNumberWithLabel(phoneNumber: phoneNumber)
friendPhoneNumbers.append(phone)
}
} catch let error {
print("Failed to enumerate contact", error)
}
}
let contactImage = result.dropFirst(8)
friend.photo = "file:/" + contactImage
friend.organization = contact.organizationName
friend.done()
DispatchQueue.main.async {
_ = self.friendList!.addLocalFriend(linphoneFriend: friend)
self.friendList!.updateSubscriptions()
}
} catch let error {
print("Failed to enumerate contact", error)
}
let contactImage = result.dropFirst(8)
friend.photo = "file:/" + contactImage
friend.organization = contact.organizationName
friend.done()
_ = self.friendList!.addLocalFriend(linphoneFriend: friend)
self.friendList!.updateSubscriptions()
} catch let error {
print("Failed to enumerate contact", error)
}
}
}
}
func awaitDataWrite(data: Data, name: String, completion: @escaping ((), String) -> Void) {
let directory = FileManager.default.temporaryDirectory

View file

@ -17,74 +17,104 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// swiftlint:disable large_tuple
import linphonesw
import Combine
final class CoreContext: ObservableObject {
static let shared = CoreContext()
var mCore: Core!
var mRegistrationDelegate: CoreDelegate!
var mConfigurationDelegate: CoreDelegate!
var coreVersion: String = Core.getVersion
@Published var loggedIn: Bool = false
@Published var loggingInProgress: Bool = false
@Published var toastMessage: String = ""
@Published var defaultAccount: Account?
private var mCore: Core!
private var mIteratePublisher: AnyCancellable?
private init() {}
func initialiseCore() async throws {
func doOnCoreQueue(synchronous : Bool = false, lambda: @escaping (Core) -> Void) {
if synchronous {
coreQueue.sync {
lambda(self.mCore)
}
} else {
coreQueue.async {
lambda(self.mCore)
}
}
}
func initialiseCore() throws {
LoggingService.Instance.logLevel = LogLevel.Debug
let factory = Factory.Instance
let configDir = factory.getConfigDir(context: nil)
try? mCore = Factory.Instance.createCore(configPath: "\(configDir)/MyConfig", factoryConfigPath: "", systemContext: nil)
mCore.friendsDatabasePath = "\(configDir)/friends.db"
try? mCore.start()
// Create a Core listener to listen for the callback we need
// In this case, we want to know about the account registration status
mRegistrationDelegate =
CoreDelegateStub(
onConfiguringStatus: {(_: Core, state: Config.ConfiguringState, message: String) in
NSLog("New configuration state is \(state) = \(message)\n")
if state == .Successful {
coreQueue.async {
let configDir = Factory.Instance.getConfigDir(context: nil)
try? self.mCore = Factory.Instance.createCore(configPath: "\(configDir)/MyConfig", factoryConfigPath: "", systemContext: nil)
self.mCore.autoIterateEnabled = false
self.mCore.friendsDatabasePath = "\(configDir)/friends.db"
self.mCore.publisher?.onGlobalStateChanged?.postOnMainQueue { (cbVal: (core: Core, state: GlobalState, message: String)) in
if cbVal.state == GlobalState.On {
self.defaultAccount = self.mCore.defaultAccount
} else if cbVal.state == GlobalState.Off {
self.defaultAccount = nil
}
}
try? self.mCore.start()
// Create a Core listener to listen for the callback we need
// In this case, we want to know about the account registration status
self.mCore.publisher?.onConfiguringStatus?.postOnMainQueue { (cbVal: (core: Core, status: Config.ConfiguringState, message: String)) in
NSLog("New configuration state is \(cbVal.status) = \(cbVal.message)\n")
if cbVal.status == Config.ConfiguringState.Successful {
self.toastMessage = "Successful"
} else {
self.toastMessage = "Failed"
}
},
}
onAccountRegistrationStateChanged: {(_: Core, account: Account, state: RegistrationState, message: String) in
self.mCore.publisher?.onAccountRegistrationStateChanged?.postOnMainQueue { (cbVal: (core: Core, account: Account, state: RegistrationState, message: String)) in
// If account has been configured correctly, we will go through Progress and Ok states
// Otherwise, we will be Failed.
NSLog("New registration state is \(state) for user id \( String(describing: account.params?.identityAddress?.asString())) = \(message)\n")
if state == .Ok {
NSLog("New registration state is \(cbVal.state) for user id " +
"\( String(describing: cbVal.account.params?.identityAddress?.asString())) = \(cbVal.message)\n")
if cbVal.state == .Ok {
self.loggingInProgress = false
self.loggedIn = true
} else if state == .Progress {
} else if cbVal.state == .Progress {
self.loggingInProgress = true
} else {
self.toastMessage = "Registration failed"
self.loggingInProgress = false
self.loggedIn = false
let params = account.params
}
}.postOnCoreQueue { (cbVal: (core: Core, account: Account, state: RegistrationState, message: String)) in
// If registration failed, remove account from core
if cbVal.state != .Ok && cbVal.state != .Progress {
let params = cbVal.account.params
let clonedParams = params?.clone()
clonedParams?.registerEnabled = false
account.params = clonedParams
cbVal.account.params = clonedParams
self.mCore!.removeAccount(account: account)
self.mCore!.clearAccounts()
self.mCore!.clearAllAuthInfo()
cbVal.core.removeAccount(account: cbVal.account)
cbVal.core.clearAccounts()
cbVal.core.clearAllAuthInfo()
}
}
)
mCore.addDelegate(delegate: mRegistrationDelegate)
self.mIteratePublisher = Timer.publish(every: 0.02, on: .main, in: .common)
.autoconnect()
.receive(on: coreQueue)
.sink { _ in
self.mCore.iterate()
}
}
}
}
// swiftlint:enable large_tuple

View file

@ -32,10 +32,10 @@ struct LinphoneApp: App {
if isActive {
if !sharedMainViewModel.welcomeViewDisplayed {
WelcomeView(sharedMainViewModel: sharedMainViewModel)
} else if coreContext.mCore.defaultAccount == nil || sharedMainViewModel.displayProfileMode {
} else if coreContext.defaultAccount == nil || sharedMainViewModel.displayProfileMode {
AssistantView(sharedMainViewModel: sharedMainViewModel)
.toast(isShowing: $coreContext.toastMessage)
} else if coreContext.mCore.defaultAccount != nil {
} else if coreContext.defaultAccount != nil {
ContentView(contactViewModel: ContactViewModel(), historyViewModel: HistoryViewModel())
.toast(isShowing: $coreContext.toastMessage)
}

View file

@ -40,7 +40,7 @@ struct SplashScreen: View {
.ignoresSafeArea(.all)
.onAppear {
Task {
try await coreContext.initialiseCore()
try coreContext.initialiseCore()
withAnimation {
self.isActive = true
}

View file

@ -33,83 +33,95 @@ class AccountLoginViewModel: ObservableObject {
init() {}
func login() {
do {
// Get the transport protocol to use.
// TLS is strongly recommended
// Only use UDP if you don't have the choice
var transport: TransportType
if transportType == "TLS" {
transport = TransportType.Tls
} else if transportType == "TCP" {
transport = TransportType.Tcp
} else { transport = TransportType.Udp }
// To configure a SIP account, we need an Account object and an AuthInfo object
// The first one is how to connect to the proxy server, the second one stores the credentials
// The auth info can be created from the Factory as it's only a data class
// userID is set to null as it's the same as the username in our case
// ha1 is set to null as we are using the clear text password. Upon first register, the hash will be computed automatically.
// The realm will be determined automatically from the first register, as well as the algorithm
let authInfo = try Factory.Instance.createAuthInfo(username: username, userid: "", passwd: passwd, ha1: "", realm: "", domain: domain)
// Account object replaces deprecated ProxyConfig object
// Account object is configured through an AccountParams object that we can obtain from the Core
let accountParams = try coreContext.mCore!.createAccountParams()
// A SIP account is identified by an identity address that we can construct from the username and domain
let identity = try Factory.Instance.createAddress(addr: String("sip:" + username + "@" + domain))
try accountParams.setIdentityaddress(newValue: identity)
// We also need to configure where the proxy server is located
let address = try Factory.Instance.createAddress(addr: String("sip:" + domain))
// We use the Address object to easily set the transport protocol
try address.setTransport(newValue: transport)
try accountParams.setServeraddress(newValue: address)
// And we ensure the account will start the registration process
accountParams.registerEnabled = true
// Now that our AccountParams is configured, we can create the Account object
let account = try coreContext.mCore!.createAccount(params: accountParams)
// Now let's add our objects to the Core
coreContext.mCore!.addAuthInfo(info: authInfo)
try coreContext.mCore!.addAccount(account: account)
// Also set the newly added account as default
coreContext.mCore!.defaultAccount = account
} catch { NSLog(error.localizedDescription) }
coreContext.doOnCoreQueue { core in
do {
// Get the transport protocol to use.
// TLS is strongly recommended
// Only use UDP if you don't have the choice
var transport: TransportType
if self.transportType == "TLS" {
transport = TransportType.Tls
} else if self.transportType == "TCP" {
transport = TransportType.Tcp
} else { transport = TransportType.Udp }
// To configure a SIP account, we need an Account object and an AuthInfo object
// The first one is how to connect to the proxy server, the second one stores the credentials
// The auth info can be created from the Factory as it's only a data class
// userID is set to null as it's the same as the username in our case
// ha1 is set to null as we are using the clear text password. Upon first register, the hash will be computed automatically.
// The realm will be determined automatically from the first register, as well as the algorithm
let authInfo = try Factory.Instance.createAuthInfo(username: self.username, userid: "", passwd: self.passwd, ha1: "", realm: "", domain: self.domain)
// Account object replaces deprecated ProxyConfig object
// Account object is configured through an AccountParams object that we can obtain from the Core
let accountParams = try core.createAccountParams()
// A SIP account is identified by an identity address that we can construct from the username and domain
let identity = try Factory.Instance.createAddress(addr: String("sip:" + self.username + "@" + self.domain))
try accountParams.setIdentityaddress(newValue: identity)
// We also need to configure where the proxy server is located
let address = try Factory.Instance.createAddress(addr: String("sip:" + self.domain))
// We use the Address object to easily set the transport protocol
try address.setTransport(newValue: transport)
try accountParams.setServeraddress(newValue: address)
// And we ensure the account will start the registration process
accountParams.registerEnabled = true
// Now that our AccountParams is configured, we can create the Account object
let account = try core.createAccount(params: accountParams)
// Now let's add our objects to the Core
core.addAuthInfo(info: authInfo)
try core.addAccount(account: account)
// Also set the newly added account as default
core.defaultAccount = account
DispatchQueue.main.async {
self.coreContext.defaultAccount = account
}
} catch { NSLog(error.localizedDescription) }
}
}
func unregister() {
// Here we will disable the registration of our Account
if let account = coreContext.mCore!.defaultAccount {
let params = account.params
// Returned params object is const, so to make changes we first need to clone it
let clonedParams = params?.clone()
// Now let's make our changes
clonedParams?.registerEnabled = false
// And apply them
account.params = clonedParams
coreContext.doOnCoreQueue { core in
// Here we will disable the registration of our Account
if let account = core.defaultAccount {
let params = account.params
// Returned params object is const, so to make changes we first need to clone it
let clonedParams = params?.clone()
// Now let's make our changes
clonedParams?.registerEnabled = false
// And apply them
account.params = clonedParams
}
}
}
func delete() {
// To completely remove an Account
if let account = coreContext.mCore!.defaultAccount {
coreContext.mCore!.removeAccount(account: account)
// To remove all accounts use
coreContext.mCore!.clearAccounts()
// Same for auth info
coreContext.mCore!.clearAllAuthInfo()
coreContext.doOnCoreQueue { core in
// To completely remove an Account
if let account = core.defaultAccount {
core.removeAccount(account: account)
DispatchQueue.main.async {
self.coreContext.defaultAccount = nil
}
// To remove all accounts use
core.clearAccounts()
// Same for auth info
core.clearAllAuthInfo()
}
}
}
}

View file

@ -70,14 +70,11 @@ class Coordinator: NSObject, AVCaptureMetadataOutputObjectsDelegate {
if let url = NSURL(string: result) {
if UIApplication.shared.canOpenURL(url as URL) {
lastResult = result
do {
try coreContext.mCore.setProvisioninguri(newValue: result)
coreContext.mCore.stop()
try coreContext.mCore.start()
} catch {
coreContext.doOnCoreQueue { core in
try? core.setProvisioninguri(newValue: result)
core.stop()
try? core.start()
}
} else {
coreContext.toastMessage = "Invalide URI"
}

View file

@ -416,11 +416,11 @@ struct ContentView: View {
if isShowDeletePopup {
PopupView(sharedMainViewModel: SharedMainViewModel(), isShowPopup: $isShowDeletePopup,
title: Text(
contactViewModel.selectedFriend != nil
? "Delete \(contactViewModel.selectedFriend!.name!)?"
: (contactViewModel.displayedFriend != nil
? "Delete \(contactViewModel.displayedFriend!.name!)?"
: "Error Name")),
contactViewModel.selectedFriend != nil
? "Delete \(contactViewModel.selectedFriend!.name!)?"
: (contactViewModel.displayedFriend != nil
? "Delete \(contactViewModel.displayedFriend!.name!)?"
: "Error Name")),
content: Text("This contact will be deleted definitively."),
titleFirstButton: Text("Cancel"),
actionFirstButton: {self.isShowDeletePopup.toggle()},

View file

@ -25,9 +25,8 @@ final class MagicSearchSingleton: ObservableObject {
private var coreContext = CoreContext.shared
private var magicSearch: MagicSearch!
var magicSearchDelegate: MagicSearchDelegate?
@objc var currentFilter: String = ""
var currentFilter: String = ""
var previousFilter: String?
var needUpdateLastSearchContacts = false
@ -35,36 +34,45 @@ final class MagicSearchSingleton: ObservableObject {
@Published var lastSearch: [SearchResult] = []
private var limitSearchToLinphoneAccounts = true
@Published var allContact = false
private var domainDefaultAccount = ""
@Published var allContact = false
private var domainDefaultAccount = ""
private init() {
domainDefaultAccount = coreContext.mCore.defaultAccount!.params!.domain!
magicSearch = try? coreContext.mCore.createMagicSearch()
magicSearch.limitedSearch = false
magicSearchDelegate = MagicSearchDelegateStub(onSearchResultsReceived: { (magicSearch: MagicSearch) in
self.needUpdateLastSearchContacts = true
self.lastSearch = magicSearch.lastSearch
})
magicSearch.addDelegate(delegate: magicSearchDelegate!)
coreContext.doOnCoreQueue{ core in
self.domainDefaultAccount = core.defaultAccount?.params?.domain ?? ""
self.magicSearch = try? core.createMagicSearch()
self.magicSearch.limitedSearch = false
self.magicSearch.publisher?.onSearchResultsReceived?.postOnMainQueue { (magicSearch: MagicSearch) in
self.needUpdateLastSearchContacts = true
self.lastSearch = magicSearch.lastSearch
}
}
}
func searchForContacts(sourceFlags: Int) {
if let oldFilter = previousFilter {
if oldFilter.count > currentFilter.count || oldFilter != currentFilter {
magicSearch.resetSearchCache()
coreContext.doOnCoreQueue{ core in
var needResetCache = false
DispatchQueue.main.sync {
if let oldFilter = self.previousFilter {
if oldFilter.count > self.currentFilter.count || oldFilter != self.currentFilter {
needResetCache = true
}
}
self.previousFilter = self.currentFilter
}
if needResetCache {
self.magicSearch.resetSearchCache()
}
self.magicSearch.getContactsListAsync(
filter: self.currentFilter,
domain: self.allContact ? "" : self.domainDefaultAccount,
sourceFlags: sourceFlags,
aggregation: MagicSearch.Aggregation.Friend)
}
previousFilter = currentFilter
magicSearch.getContactsListAsync(
filter: currentFilter,
domain: allContact ? "" : domainDefaultAccount,
sourceFlags: sourceFlags,
aggregation: MagicSearch.Aggregation.Friend)
}
}