core: faster tracking of active subscriptions; ui: only track in foreground (#4446)

* core: faster tracking of active subscriptions

* combine db transaction

* optimizations of queries from UI

* ios: track when active

* ios: disable log

---------

Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
This commit is contained in:
Evgeny Poberezkin
2024-07-13 08:44:51 +01:00
committed by GitHub
parent 23f24b1677
commit 3e873fcb32
8 changed files with 47 additions and 95 deletions

View File

@@ -266,23 +266,19 @@ struct ChatListView: View {
}
struct SubsStatusIndicator: View {
@State private var subs: SMPServerSubs = SMPServerSubs.newSMPServerSubs
@State private var sess: ServerSessions = ServerSessions.newServerSessions
@State private var serversSummary: PresentedServersSummary?
@State private var timer: Timer? = nil
@State private var timerCounter = 0
@State private var showServersSummary = false
@AppStorage(DEFAULT_SHOW_SUBSCRIPTION_PERCENTAGE) private var showSubscriptionPercentage = false
// Constants for the intervals
let initialInterval: TimeInterval = 1.0
let regularInterval: TimeInterval = 3.0
let initialPhaseDuration: TimeInterval = 10.0 // Duration for initial phase in seconds
var body: some View {
Button {
showServersSummary = true
} label: {
let subs = serversSummary?.allUsersSMP.smpTotals.subs ?? SMPServerSubs.newSMPServerSubs
let sess = serversSummary?.allUsersSMP.smpTotals.sessions ?? ServerSessions.newServerSessions
HStack(spacing: 4) {
SubscriptionStatusIndicatorView(subs: subs, sess: sess)
if showSubscriptionPercentage {
@@ -291,34 +287,24 @@ struct SubsStatusIndicator: View {
}
}
.onAppear {
startInitialTimer()
startTimer()
}
.onDisappear {
stopTimer()
}
.sheet(isPresented: $showServersSummary) {
ServersSummaryView()
ServersSummaryView(serversSummary: $serversSummary)
}
}
private func startInitialTimer() {
timer = Timer.scheduledTimer(withTimeInterval: initialInterval, repeats: true) { _ in
getServersSummary()
timerCounter += 1
// Switch to the regular timer after the initial phase
if timerCounter * Int(initialInterval) >= Int(initialPhaseDuration) {
switchToRegularTimer()
private func startTimer() {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
if AppChatState.shared.value == .active {
getServersSummary()
}
}
}
func switchToRegularTimer() {
timer?.invalidate()
timer = Timer.scheduledTimer(withTimeInterval: regularInterval, repeats: true) { _ in
getServersSummary()
}
}
func stopTimer() {
timer?.invalidate()
timer = nil
@@ -326,8 +312,7 @@ struct SubsStatusIndicator: View {
private func getServersSummary() {
do {
let summ = try getAgentServersSummary()
(subs, sess) = (summ.allUsersSMP.smpTotals.subs, summ.allUsersSMP.smpTotals.sessions)
serversSummary = try getAgentServersSummary()
} catch let error {
logger.error("getAgentServersSummary error: \(responseError(error))")
}

View File

@@ -11,12 +11,12 @@ import SimpleXChat
struct ServersSummaryView: View {
@EnvironmentObject var m: ChatModel
@State private var serversSummary: PresentedServersSummary? = nil
@EnvironmentObject var theme: AppTheme
@Binding var serversSummary: PresentedServersSummary?
@State private var selectedUserCategory: PresentedUserCategory = .allUsers
@State private var selectedServerType: PresentedServerType = .smp
@State private var selectedSMPServer: String? = nil
@State private var selectedXFTPServer: String? = nil
@State private var timer: Timer? = nil
@State private var alert: SomeAlert?
@AppStorage(DEFAULT_SHOW_SUBSCRIPTION_PERCENTAGE) private var showSubscriptionPercentage = false
@@ -47,26 +47,10 @@ struct ServersSummaryView: View {
if m.users.filter({ u in u.user.activeUser || !u.user.hidden }).count == 1 {
selectedUserCategory = .currentUser
}
getServersSummary()
startTimer()
}
.onDisappear {
stopTimer()
}
.alert(item: $alert) { $0.alert }
}
private func startTimer() {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
getServersSummary()
}
}
func stopTimer() {
timer?.invalidate()
timer = nil
}
private func shareButton() -> some View {
Button {
if let serversSummary = serversSummary {
@@ -183,6 +167,8 @@ struct ServersSummaryView: View {
}
} else {
Text("No info, try to reload")
.foregroundColor(theme.colors.secondary)
.background(theme.colors.background)
}
}
@@ -369,7 +355,6 @@ struct ServersSummaryView: View {
Task {
do {
try await resetAgentServersStats()
getServersSummary()
} catch let error {
alert = SomeAlert(
alert: mkAlert(
@@ -389,14 +374,6 @@ struct ServersSummaryView: View {
Text("Reset all statistics")
}
}
private func getServersSummary() {
do {
serversSummary = try getAgentServersSummary()
} catch let error {
logger.error("getAgentServersSummary error: \(responseError(error))")
}
}
}
struct SubscriptionStatusIndicatorView: View {
@@ -734,5 +711,7 @@ struct DetailedXFTPStatsView: View {
}
#Preview {
ServersSummaryView()
ServersSummaryView(
serversSummary: Binding.constant(nil)
)
}

View File

@@ -6,8 +6,6 @@ import android.net.LocalServerSocket
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.fragment.app.FragmentActivity
import chat.simplex.common.*
import chat.simplex.common.platform.*
import java.io.*
import java.lang.ref.WeakReference
import java.util.*
@@ -24,6 +22,8 @@ var isAppOnForeground: Boolean = false
@Suppress("ConstantLocale")
val defaultLocale: Locale = Locale.getDefault()
actual fun isAppVisibleAndFocused(): Boolean = isAppOnForeground
@SuppressLint("StaticFieldLeak")
lateinit var androidAppContext: Context
var mainActivity: WeakReference<FragmentActivity> = WeakReference(null)

View File

@@ -3,7 +3,6 @@ package chat.simplex.common.platform
import chat.simplex.common.BuildConfigCommon
import chat.simplex.common.model.*
import chat.simplex.common.ui.theme.DefaultTheme
import java.io.File
import java.util.*
enum class AppPlatform {
@@ -20,6 +19,8 @@ expect val appPlatform: AppPlatform
expect val deviceName: String
expect fun isAppVisibleAndFocused(): Boolean
val appVersionInfo: Pair<String, Int?> = if (appPlatform == AppPlatform.ANDROID)
BuildConfigCommon.ANDROID_VERSION_NAME to BuildConfigCommon.ANDROID_VERSION_CODE
else

View File

@@ -266,44 +266,29 @@ private fun ChatListToolbar(drawerState: DrawerState, userPickerState: MutableSt
fun SubscriptionStatusIndicator(serversSummary: MutableState<PresentedServersSummary?>, click: (() -> Unit)) {
var subs by remember { mutableStateOf(SMPServerSubs.newSMPServerSubs) }
var sess by remember { mutableStateOf(ServerSessions.newServerSessions) }
var timer: Job? by remember { mutableStateOf(null) }
val fetchInterval: Duration = 1.seconds
val scope = rememberCoroutineScope()
fun setServersSummary() {
withBGApi {
serversSummary.value = chatModel.controller.getAgentServersSummary(chatModel.remoteHostId())
suspend fun setServersSummary() {
serversSummary.value = chatModel.controller.getAgentServersSummary(chatModel.remoteHostId())
serversSummary.value?.let {
subs = it.allUsersSMP.smpTotals.subs
sess = it.allUsersSMP.smpTotals.sessions
}
serversSummary.value?.let {
subs = it.allUsersSMP.smpTotals.subs
sess = it.allUsersSMP.smpTotals.sessions
}
}
LaunchedEffect(Unit) {
setServersSummary()
timer = timer ?: scope.launch {
while (true) {
delay(fetchInterval.inWholeMilliseconds)
setServersSummary()
scope.launch {
while (isActive) {
delay(1.seconds)
if ((appPlatform.isDesktop || chatModel.chatId.value == null) && !ModalManager.start.hasModalsOpen() && !ModalManager.fullscreen.hasModalsOpen() && isAppVisibleAndFocused()) {
setServersSummary()
}
}
}
}
fun stopTimer() {
timer?.cancel()
timer = null
}
DisposableEffect(Unit) {
onDispose {
stopTimer()
}
}
SimpleButtonFrame(click = click) {
SubscriptionStatusIndicatorView(subs = subs, sess = sess)
}

View File

@@ -1,6 +1,7 @@
package chat.simplex.common.platform
import chat.simplex.common.model.*
import chat.simplex.common.simplexWindowState
import chat.simplex.common.views.call.RcvCallInvitation
import chat.simplex.common.views.helpers.*
import java.util.*
@@ -10,6 +11,8 @@ actual val appPlatform = AppPlatform.DESKTOP
actual val deviceName = generalGetString(MR.strings.desktop_device)
actual fun isAppVisibleAndFocused() = simplexWindowState.windowFocused.value
@Suppress("ConstantLocale")
val defaultLocale: Locale = Locale.getDefault()

View File

@@ -2260,11 +2260,14 @@ processChatCommand' vr = \case
DebugEvent event -> toView event >> ok_
GetAgentServersSummary userId -> withUserId userId $ \user -> do
agentServersSummary <- lift $ withAgent' getAgentServersSummary
users <- withStore' getUsers
smpServers <- getUserProtocolServers user SPSMP
xftpServers <- getUserProtocolServers user SPXFTP
cfg <- asks config
(users, smpServers, xftpServers) <-
withStore' $ \db -> (,,) <$> getUsers db <*> getServers db cfg user SPSMP <*> getServers db cfg user SPXFTP
let presentedServersSummary = toPresentedServersSummary agentServersSummary users user smpServers xftpServers
pure $ CRAgentServersSummary user presentedServersSummary
where
getServers :: (ProtocolTypeI p, UserProtocol p) => DB.Connection -> ChatConfig -> User -> SProtocolType p -> IO (NonEmpty (ProtocolServer p))
getServers db cfg user p = L.map (\ServerCfg {server} -> protoServer server) . useServers cfg p <$> getProtocolServers db user
ResetAgentServersStats -> withAgent resetAgentServersStats >> ok_
GetAgentWorkers -> lift $ CRAgentWorkersSummary <$> withAgent' getAgentWorkersSummary
GetAgentWorkersDetails -> lift $ CRAgentWorkersDetails <$> withAgent' getAgentWorkersDetails
@@ -3223,7 +3226,8 @@ receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete}
S.toList $ S.fromList $ concatMap (\FD.FileChunk {replicas} -> map (\FD.FileChunkReplica {server} -> server) replicas) chunks
getUnknownSrvs :: [XFTPServer] -> CM [XFTPServer]
getUnknownSrvs srvs = do
knownSrvs <- getUserProtocolServers user SPXFTP
cfg <- asks config
knownSrvs <- L.map (\ServerCfg {server} -> protoServer server) . useServers cfg SPXFTP <$> withStore' (`getProtocolServers` user)
pure $ filter (`notElem` knownSrvs) srvs
ipProtectedForSrvs :: [XFTPServer] -> CM Bool
ipProtectedForSrvs srvs = do
@@ -3235,11 +3239,6 @@ receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete}
forM_ aci_ $ \aci -> toView $ CRChatItemUpdated user aci
throwChatError $ CEFileNotApproved fileId unknownSrvs
getUserProtocolServers :: (ProtocolTypeI p, UserProtocol p) => User -> SProtocolType p -> CM (NonEmpty (ProtocolServer p))
getUserProtocolServers user p = do
cfg <- asks config
L.map (\ServerCfg {server} -> protoServer server) . useServers cfg p <$> withStore' (`getProtocolServers` user)
getNetworkConfig :: CM' NetworkConfig
getNetworkConfig = withAgent' $ liftIO . getNetworkConfig'

View File

@@ -150,7 +150,7 @@ toPresentedServersSummary agentSummary users currentUser userSMPSrvs userXFTPSrv
AgentServersSummary {statsStartedAt, smpServersSessions, smpServersSubs, smpServersStats, xftpServersSessions, xftpServersStats, xftpRcvInProgress, xftpSndInProgress, xftpDelInProgress} = agentSummary
countUserInAll auId = countUserInAllStats (AgentUserId auId) currentUser users
accSMPTotals :: Map SMPServer SMPServerSummary -> SMPTotals
accSMPTotals = M.foldr addTotals initialTotals
accSMPTotals = M.foldr' addTotals initialTotals
where
initialTotals = SMPTotals {sessions = ServerSessions 0 0 0, subs = SMPServerSubs 0 0, stats = newAgentSMPServerStatsData}
addTotals SMPServerSummary {sessions, subs, stats} SMPTotals {sessions = accSess, subs = accSubs, stats = accStats} =
@@ -160,7 +160,7 @@ toPresentedServersSummary agentSummary users currentUser userSMPSrvs userXFTPSrv
stats = maybe accStats (accStats `addSMPStatsData`) stats
}
accXFTPTotals :: Map XFTPServer XFTPServerSummary -> XFTPTotals
accXFTPTotals = M.foldr addTotals initialTotals
accXFTPTotals = M.foldr' addTotals initialTotals
where
initialTotals = XFTPTotals {sessions = ServerSessions 0 0 0, stats = newAgentXFTPServerStatsData}
addTotals XFTPServerSummary {sessions, stats} XFTPTotals {sessions = accSess, stats = accStats} =
@@ -169,7 +169,7 @@ toPresentedServersSummary agentSummary users currentUser userSMPSrvs userXFTPSrv
stats = maybe accStats (accStats `addXFTPStatsData`) stats
}
smpSummsIntoCategories :: Map SMPServer SMPServerSummary -> ([SMPServerSummary], [SMPServerSummary], [SMPServerSummary])
smpSummsIntoCategories = foldr partitionSummary ([], [], [])
smpSummsIntoCategories = M.foldr' partitionSummary ([], [], [])
where
partitionSummary srvSumm (curr, prev, prox)
| isCurrentlyUsed srvSumm = (srvSumm : curr, prev, prox)
@@ -183,7 +183,7 @@ toPresentedServersSummary agentSummary users currentUser userSMPSrvs userXFTPSrv
Just AgentSMPServerStatsData {_sentDirect, _sentProxied, _sentDirectAttempts, _sentProxiedAttempts, _recvMsgs, _connCreated, _connSecured, _connSubscribed, _connSubAttempts} ->
_sentDirect > 0 || _sentProxied > 0 || _sentDirectAttempts > 0 || _sentProxiedAttempts > 0 || _recvMsgs > 0 || _connCreated > 0 || _connSecured > 0 || _connSubscribed > 0 || _connSubAttempts > 0
xftpSummsIntoCategories :: Map XFTPServer XFTPServerSummary -> ([XFTPServerSummary], [XFTPServerSummary])
xftpSummsIntoCategories = foldr partitionSummary ([], [])
xftpSummsIntoCategories = M.foldr' partitionSummary ([], [])
where
partitionSummary srvSumm (curr, prev)
| isCurrentlyUsed srvSumm = (srvSumm : curr, prev)