fix(@desktop/communities): Lazy loading token holders
Token holders are not fetched when application starts. They are fetched only when token details screen is opened. Fix #14974
This commit is contained in:
parent
146a6e8501
commit
a7b9a62745
|
@ -178,3 +178,9 @@ proc declineOwnership*(self: Controller, communityId: string) =
|
|||
|
||||
proc asyncGetOwnerTokenOwnerAddress*(self: Controller, chainId: int, contractAddress: string) =
|
||||
self.communityTokensService.asyncGetOwnerTokenOwnerAddress(chainId, contractAddress)
|
||||
|
||||
proc startTokenHoldersManagement*(self: Controller, chainId: int, contractAddress: string) =
|
||||
self.communityTokensService.startTokenHoldersManagement(chainId, contractAddress)
|
||||
|
||||
proc stopTokenHoldersManagement*(self: Controller) =
|
||||
self.communityTokensService.stopTokenHoldersManagement()
|
|
@ -118,4 +118,10 @@ method onOwnerTokenOwnerAddress*(self: AccessInterface, chainId: int, contractAd
|
|||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method asyncGetOwnerTokenDetails*(self: AccessInterface, communityId: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method startTokenHoldersManagement*(self: AccessInterface, chainId: int, contractAddress: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method stopTokenHoldersManagement*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
|
@ -385,3 +385,9 @@ method onOwnerTokenOwnerAddress*(self: Module, chainId: int, contractAddress: st
|
|||
"contractAddress": contractAddress
|
||||
}
|
||||
self.view.setOwnerTokenDetails($jsonObj)
|
||||
|
||||
method startTokenHoldersManagement*(self: Module, chainId: int, contractAddress: string) =
|
||||
self.controller.startTokenHoldersManagement(chainId, contractAddress)
|
||||
|
||||
method stopTokenHoldersManagement*(self: Module) =
|
||||
self.controller.stopTokenHoldersManagement()
|
|
@ -145,3 +145,9 @@ QtObject:
|
|||
QtProperty[string] ownerTokenDetails:
|
||||
read = getOwnerTokenDetails
|
||||
notify = ownerTokenDetailsChanged
|
||||
|
||||
proc startTokenHoldersManagement*(self: View, chainId: int, contractAddress: string) {.slot.} =
|
||||
self.communityTokensModule.startTokenHoldersManagement(chainId, contractAddress)
|
||||
|
||||
proc stopTokenHoldersManagement*(self: View) {.slot.} =
|
||||
self.communityTokensModule.stopTokenHoldersManagement()
|
|
@ -1,4 +1,4 @@
|
|||
import NimQml, Tables, chronicles, json, stint, strutils, sugar, sequtils, stew/shims/strformat
|
||||
import NimQml, Tables, chronicles, json, stint, strutils, sugar, sequtils, stew/shims/strformat, times
|
||||
import ../../../app/global/global_singleton
|
||||
import ../../../app/core/eventemitter
|
||||
import ../../../app/core/tasks/[qt, threadpool]
|
||||
|
@ -287,9 +287,6 @@ QtObject:
|
|||
communityService: community_service.Service
|
||||
currencyService: currency_service.Service
|
||||
|
||||
tokenOwnersTimer: QTimer
|
||||
tokenOwners1SecTimer: QTimer # used to update 1 sec after changes in owners
|
||||
tempTokenOwnersToFetch: CommunityTokenDto # used by 1sec timer
|
||||
tokenOwnersCache: Table[ContractTuple, seq[CommunityCollectibleOwner]]
|
||||
|
||||
tempFeeTable: Table[int, SuggestedFeesDto] # fees per chain, filled during gas computation, used during operation (deployment, mint, burn)
|
||||
|
@ -298,19 +295,26 @@ QtObject:
|
|||
|
||||
communityTokensCache: seq[CommunityTokenDto]
|
||||
|
||||
communityDataLoaded: bool
|
||||
allCommunityTokensLoaded: bool
|
||||
# keep times when token holders list for contracts were updated
|
||||
tokenHoldersLastUpdateMap: Table[ContractTuple, int64]
|
||||
# timer which fetches token holders
|
||||
tokenHoldersTimer: QTimer
|
||||
# token for which token holders are fetched
|
||||
tokenHoldersToken: CommunityTokenDto
|
||||
# flag to indicate that token holders management started
|
||||
tokenHoldersManagementStarted: bool
|
||||
|
||||
# Forward declaration
|
||||
proc getAllCommunityTokensAsync*(self: Service)
|
||||
proc fetchAllTokenOwners(self: Service)
|
||||
proc getCommunityTokenOwners*(self: Service, communityId: string, chainId: int, contractAddress: string): seq[CommunityCollectibleOwner]
|
||||
proc getCommunityToken*(self: Service, chainId: int, address: string): CommunityTokenDto
|
||||
proc findContractByUniqueId*(self: Service, contractUniqueKey: string): CommunityTokenDto
|
||||
proc restartTokenHoldersTimer(self: Service, chainId: int, contractAddress: string)
|
||||
proc refreshTokenHolders(self: Service, token: CommunityTokenDto)
|
||||
|
||||
|
||||
proc delete*(self: Service) =
|
||||
delete(self.tokenOwnersTimer)
|
||||
delete(self.tokenOwners1SecTimer)
|
||||
delete(self.tokenHoldersTimer)
|
||||
self.QObject.delete
|
||||
|
||||
proc newService*(
|
||||
|
@ -335,13 +339,10 @@ QtObject:
|
|||
result.acService = acService
|
||||
result.communityService = communityService
|
||||
result.currencyService = currencyService
|
||||
result.tokenOwnersTimer = newQTimer()
|
||||
result.tokenOwnersTimer.setInterval(5*60*1000)
|
||||
signalConnect(result.tokenOwnersTimer, "timeout()", result, "onRefreshTransferableTokenOwners()", 2)
|
||||
result.tokenOwners1SecTimer = newQTimer()
|
||||
result.tokenOwners1SecTimer.setInterval(1000)
|
||||
result.tokenOwners1SecTimer.setSingleShot(true)
|
||||
signalConnect(result.tokenOwners1SecTimer, "timeout()", result, "onFetchTempTokenOwners()", 2)
|
||||
|
||||
result.tokenHoldersTimer = newQTimer()
|
||||
result.tokenHoldersTimer.setSingleShot(true)
|
||||
signalConnect(result.tokenHoldersTimer, "timeout()", result, "onTokenHoldersTimeout()", 2)
|
||||
|
||||
# cache functions
|
||||
proc updateCommunityTokenCache(self: Service, chainId: int, address: string, tokenToUpdate: CommunityTokenDto) =
|
||||
|
@ -507,8 +508,7 @@ QtObject:
|
|||
|
||||
# update owners list if airdrop was successfull
|
||||
if signalArgs.success:
|
||||
self.tempTokenOwnersToFetch = signalArgs.communityToken
|
||||
self.tokenOwners1SecTimer.start()
|
||||
self.refreshTokenHolders(signalArgs.communityToken)
|
||||
except Exception as e:
|
||||
error "Error processing airdrop pending transaction event", msg=e.msg
|
||||
|
||||
|
@ -520,8 +520,7 @@ QtObject:
|
|||
|
||||
# update owners list if remote destruct was successfull
|
||||
if signalArgs.success:
|
||||
self.tempTokenOwnersToFetch = signalArgs.communityToken
|
||||
self.tokenOwners1SecTimer.start()
|
||||
self.refreshTokenHolders(signalArgs.communityToken)
|
||||
except Exception as e:
|
||||
error "Error processing collectible self destruct pending transaction event", msg=e.msg
|
||||
|
||||
|
@ -574,24 +573,16 @@ QtObject:
|
|||
proc processCommunityTokenAction(self: Service, signalArgs: CommunityTokenActionSignal) =
|
||||
case signalArgs.actionType
|
||||
of CommunityTokenActionType.Airdrop:
|
||||
self.tempTokenOwnersToFetch = signalArgs.communityToken
|
||||
self.tokenOwners1SecTimer.start()
|
||||
self.refreshTokenHolders(signalArgs.communityToken)
|
||||
of CommunityTokenActionType.Burn:
|
||||
self.updateCommunityTokenCache(signalArgs.communityToken.chainId, signalArgs.communityToken.address, signalArgs.communityToken)
|
||||
let data = RemoteDestructArgs(communityToken: signalArgs.communityToken)
|
||||
self.events.emit(SIGNAL_BURN_ACTION_RECEIVED, data)
|
||||
of CommunityTokenActionType.RemoteDestruct:
|
||||
self.tempTokenOwnersToFetch = signalArgs.communityToken
|
||||
self.tokenOwners1SecTimer.start()
|
||||
self.refreshTokenHolders(signalArgs.communityToken)
|
||||
else:
|
||||
warn "Unknown token action", actionType=signalArgs.actionType
|
||||
|
||||
proc tryFetchOwners(self: Service) =
|
||||
# both communities and tokens should be loaded
|
||||
if self.allCommunityTokensLoaded and self.communityDataLoaded:
|
||||
self.fetchAllTokenOwners()
|
||||
self.tokenOwnersTimer.start()
|
||||
|
||||
proc init*(self: Service) =
|
||||
self.getAllCommunityTokensAsync()
|
||||
|
||||
|
@ -602,10 +593,6 @@ QtObject:
|
|||
elif data.eventType == tokens_backend.eventCommunityTokenReceived:
|
||||
self.processReceivedCommunityTokenWalletEvent(data.message, data.accounts)
|
||||
|
||||
self.events.on(SIGNAL_COMMUNITY_DATA_LOADED) do(e:Args):
|
||||
self.communityDataLoaded = true
|
||||
self.tryFetchOwners()
|
||||
|
||||
self.events.on(SignalType.CommunityTokenAction.event) do(e:Args):
|
||||
let receivedData = CommunityTokenActionSignal(e)
|
||||
self.processCommunityTokenAction(receivedData)
|
||||
|
@ -753,8 +740,6 @@ QtObject:
|
|||
self.communityTokensCache = map(responseJson["response"]["result"].getElems(),
|
||||
proc(x: JsonNode): CommunityTokenDto = x.toCommunityTokenDto())
|
||||
|
||||
self.allCommunityTokensLoaded = true
|
||||
self.tryFetchOwners()
|
||||
except RpcException as e:
|
||||
error "Error getting all community tokens async", message = e.msg
|
||||
|
||||
|
@ -1315,6 +1300,9 @@ QtObject:
|
|||
let data = CommunityTokenOwnersArgs(chainId: chainId, contractAddress: contractAddress, communityId: communityId, owners: communityTokenOwners)
|
||||
self.events.emit(SIGNAL_COMMUNITY_TOKEN_OWNERS_FETCHED, data)
|
||||
|
||||
# restart token holders timer
|
||||
self.restartTokenHoldersTimer(chainId, contractAddress)
|
||||
|
||||
# get owners from cache
|
||||
proc getCommunityTokenOwners*(self: Service, communityId: string, chainId: int, contractAddress: string): seq[CommunityCollectibleOwner] =
|
||||
return self.tokenOwnersCache.getOrDefault((chainId: chainId, address: contractAddress))
|
||||
|
@ -1323,25 +1311,6 @@ QtObject:
|
|||
let community = self.communityService.getCommunityById(communityId)
|
||||
return community.isPrivilegedUser()
|
||||
|
||||
# update in 5 minute intervals, only transferable tokens
|
||||
proc onRefreshTransferableTokenOwners*(self:Service) {.slot.} =
|
||||
let allTokens = self.getAllCommunityTokens()
|
||||
for token in allTokens:
|
||||
if token.transferable and self.iAmCommunityPrivilegedUser(token.communityId):
|
||||
self.fetchCommunityOwners(token)
|
||||
|
||||
# used after airdrop or remote destruct
|
||||
proc onFetchTempTokenOwners*(self: Service) {.slot.} =
|
||||
self.fetchCommunityOwners(self.tempTokenOwnersToFetch)
|
||||
|
||||
# used in init
|
||||
proc fetchAllTokenOwners(self: Service) =
|
||||
let allTokens = self.getAllCommunityTokens()
|
||||
for token in allTokens:
|
||||
if not self.iAmCommunityPrivilegedUser(token.communityId):
|
||||
continue
|
||||
self.fetchCommunityOwners(token)
|
||||
|
||||
# used when community members changed
|
||||
proc fetchCommunityTokenOwners*(self: Service, communityId: string) =
|
||||
if not self.iAmCommunityPrivilegedUser(communityId):
|
||||
|
@ -1409,3 +1378,59 @@ QtObject:
|
|||
discard tokens_backend.reTrackOwnerTokenDeploymentTransaction(chainId, contractAddress)
|
||||
except Exception:
|
||||
error "can't retrack token transaction", message = getCurrentExceptionMsg()
|
||||
|
||||
# ran also when holders are fetched
|
||||
proc restartTokenHoldersTimer(self: Service, chainId: int, contractAddress: string) =
|
||||
if not self.tokenHoldersManagementStarted:
|
||||
return
|
||||
self.tokenHoldersTimer.stop()
|
||||
|
||||
let tokenTupleKey = (chainId: chainId, address: contractAddress)
|
||||
var nextTimerShotInSeconds = int64(0)
|
||||
if self.tokenHoldersLastUpdateMap.hasKey(tokenTupleKey):
|
||||
let lastUpdateTime = self.tokenHoldersLastUpdateMap[tokenTupleKey]
|
||||
const intervalInSecs = int64(5*60)
|
||||
let nowInSeconds = now().toTime().toUnix()
|
||||
nextTimerShotInSeconds = intervalInSecs - (nowInSeconds - lastUpdateTime)
|
||||
if nextTimerShotInSeconds < 0:
|
||||
nextTimerShotInSeconds = 0
|
||||
|
||||
self.tokenHoldersTimer.setInterval(int(nextTimerShotInSeconds * 1000))
|
||||
self.tokenHoldersTimer.start()
|
||||
|
||||
# executed when Token page with holders is opened
|
||||
proc startTokenHoldersManagement*(self: Service, chainId: int, contractAddress: string) =
|
||||
let communityToken = self.getCommunityToken(chainId, contractAddress)
|
||||
if not self.iAmCommunityPrivilegedUser(communityToken.communityId):
|
||||
warn "can't get token holders - not privileged user"
|
||||
return
|
||||
|
||||
self.tokenHoldersToken = communityToken
|
||||
self.tokenHoldersManagementStarted = true
|
||||
self.restartTokenHoldersTimer(chainId, contractAddress)
|
||||
|
||||
# executed when Token page with holders is closed
|
||||
proc stopTokenHoldersManagement*(self: Service) =
|
||||
self.tokenHoldersManagementStarted = false
|
||||
self.tokenHoldersTimer.stop()
|
||||
|
||||
proc onTokenHoldersTimeout(self: Service) {.slot.} =
|
||||
# update last fetch time
|
||||
let tokenTupleKey = (chainId: self.tokenHoldersToken.chainId, address: self.tokenHoldersToken.address)
|
||||
let nowInSeconds = now().toTime().toUnix()
|
||||
self.tokenHoldersLastUpdateMap[tokenTupleKey] = nowInSeconds
|
||||
# run async calls to fetch holders
|
||||
self.fetchCommunityOwners(self.tokenHoldersToken)
|
||||
|
||||
# executed when there was some change and holders needs to be fetched again
|
||||
proc refreshTokenHolders(self: Service, token: CommunityTokenDto) =
|
||||
let tokenTupleKey = (chainId: token.chainId, address: token.address)
|
||||
self.tokenHoldersLastUpdateMap.del(tokenTupleKey)
|
||||
if not self.tokenHoldersManagementStarted:
|
||||
# not need to get holders now
|
||||
return
|
||||
let holdersTokenTuple = (chainId: self.tokenHoldersToken.chainId, address: self.tokenHoldersToken.address)
|
||||
if (tokenTupleKey != holdersTokenTuple):
|
||||
# different token is opened now
|
||||
return
|
||||
self.restartTokenHoldersTimer(token.chainId, token.address)
|
|
@ -79,6 +79,9 @@ StackView {
|
|||
signal registerSelfDestructFeesSubscriber(var feeSubscriber)
|
||||
signal registerBurnTokenFeesSubscriber(var feeSubscriber)
|
||||
|
||||
signal startTokenHoldersManagement(int chainId, string address)
|
||||
signal stopTokenHoldersManagement()
|
||||
|
||||
function navigateBack() {
|
||||
pop(StackView.Immediate)
|
||||
}
|
||||
|
@ -541,6 +544,9 @@ StackView {
|
|||
tokenOwnersModel: tokenViewPage.tokenOwnersModel
|
||||
isOwnerTokenItem: tokenViewPage.isOwnerTokenItem
|
||||
|
||||
onStartTokenHoldersManagement: root.startTokenHoldersManagement(chainId, address)
|
||||
onStopTokenHoldersManagement: root.stopTokenHoldersManagement()
|
||||
|
||||
onGeneralAirdropRequested: {
|
||||
root.airdropToken(view.airdropKey,
|
||||
"1" + "0".repeat(view.token.multiplierIndex),
|
||||
|
|
|
@ -368,6 +368,10 @@ StatusSectionLayout {
|
|||
|
||||
onRegisterBurnTokenFeesSubscriber: d.feesBroker.registerBurnFeesSubscriber(feeSubscriber)
|
||||
|
||||
onStartTokenHoldersManagement: communityTokensStore.startTokenHoldersManagement(chainId, address)
|
||||
|
||||
onStopTokenHoldersManagement: communityTokensStore.stopTokenHoldersManagement()
|
||||
|
||||
onMintCollectible:
|
||||
communityTokensStore.deployCollectible(
|
||||
root.community.id, collectibleItem)
|
||||
|
|
|
@ -78,6 +78,20 @@ StatusScrollView {
|
|||
signal kickRequested(string name, string contactId, string address)
|
||||
signal banRequested(string name, string contactId, string address)
|
||||
|
||||
signal startTokenHoldersManagement(int chainId, string address)
|
||||
signal stopTokenHoldersManagement()
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
root.startTokenHoldersManagement(root.chainId, root.token.tokenAddress)
|
||||
} else {
|
||||
root.stopTokenHoldersManagement()
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: root.startTokenHoldersManagement(root.chainId, root.token.tokenAddress)
|
||||
Component.onDestruction: root.stopTokenHoldersManagement()
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
|
|
|
@ -220,4 +220,12 @@ QtObject {
|
|||
function asyncGetOwnerTokenDetails(communityId) {
|
||||
communityTokensModuleInst.asyncGetOwnerTokenDetails(communityId)
|
||||
}
|
||||
|
||||
function startTokenHoldersManagement(chainId, contractAddress) {
|
||||
communityTokensModuleInst.startTokenHoldersManagement(chainId, contractAddress)
|
||||
}
|
||||
|
||||
function stopTokenHoldersManagement() {
|
||||
communityTokensModuleInst.stopTokenHoldersManagement()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue