refactor: load gifs asynchronously

Instead of loading recent gifs right on startup eagerly,
we postpone the task to when it's actually needed (when the gif popup is
opened and the recent gifs tab is activated), and on top of that
we also load the data asynchronously to keep the amount of work
that needs to be done in a single tick as short as possible.

This needs: status-im/status-go#3170

Closes #9437
This commit is contained in:
Pascal Precht 2023-03-07 10:15:26 +01:00 committed by Jonathan Rainville
parent 2ac1216b68
commit 919f3dc6f7
9 changed files with 253 additions and 98 deletions

View File

@ -209,7 +209,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
result.mailserversService = mailservers_service.newService(statusFoundation.events, statusFoundation.threadpool, result.mailserversService = mailservers_service.newService(statusFoundation.events, statusFoundation.threadpool,
result.settingsService, result.nodeConfigurationService, statusFoundation.fleetConfiguration) result.settingsService, result.nodeConfigurationService, statusFoundation.fleetConfiguration)
result.nodeService = node_service.newService(statusFoundation.events, result.settingsService, result.nodeConfigurationService) result.nodeService = node_service.newService(statusFoundation.events, result.settingsService, result.nodeConfigurationService)
result.gifService = gif_service.newService(result.settingsService) result.gifService = gif_service.newService(result.settingsService, statusFoundation.events, statusFoundation.threadpool)
result.ensService = ens_service.newService(statusFoundation.events, statusFoundation.threadpool, result.ensService = ens_service.newService(statusFoundation.events, statusFoundation.threadpool,
result.settingsService, result.walletAccountService, result.transactionService, result.settingsService, result.walletAccountService, result.transactionService,
result.networkService, result.tokenService) result.networkService, result.tokenService)

View File

@ -4,11 +4,13 @@ import ../../../../../../app_service/service/community/service as community_serv
import ../../../../../../app_service/service/chat/service as chat_service import ../../../../../../app_service/service/chat/service as chat_service
import ../../../../../../app_service/service/gif/service as gif_service import ../../../../../../app_service/service/gif/service as gif_service
import ../../../../../../app_service/service/gif/dto import ../../../../../../app_service/service/gif/dto
import ../../../../../core/eventemitter
type type
Controller* = ref object of RootObj Controller* = ref object of RootObj
delegate: io_interface.AccessInterface delegate: io_interface.AccessInterface
sectionId: string sectionId: string
events: EventEmitter
chatId: string chatId: string
belongsToCommunity: bool belongsToCommunity: bool
communityService: community_service.Service communityService: community_service.Service
@ -17,6 +19,7 @@ type
proc newController*( proc newController*(
delegate: io_interface.AccessInterface, delegate: io_interface.AccessInterface,
events: EventEmitter,
sectionId: string, sectionId: string,
chatId: string, chatId: string,
belongsToCommunity: bool, belongsToCommunity: bool,
@ -26,6 +29,7 @@ proc newController*(
): Controller = ): Controller =
result = Controller() result = Controller()
result.delegate = delegate result.delegate = delegate
result.events = events
result.sectionId = chatId result.sectionId = chatId
result.chatId = chatId result.chatId = chatId
result.belongsToCommunity = belongsToCommunity result.belongsToCommunity = belongsToCommunity
@ -37,7 +41,13 @@ proc delete*(self: Controller) =
discard discard
proc init*(self: Controller) = proc init*(self: Controller) =
discard self.events.on(SIGNAL_LOAD_RECENT_GIFS_DONE) do(e:Args):
let args = GifsArgs(e)
self.delegate.loadRecentGifsDone(args.gifs)
self.events.on(SIGNAL_LOAD_FAVORITE_GIFS_DONE) do(e:Args):
let args = GifsArgs(e)
self.delegate.loadFavoriteGifsDone(args.gifs)
proc getChatId*(self: Controller): string = proc getChatId*(self: Controller): string =
return self.chatId return self.chatId
@ -84,6 +94,12 @@ proc getTrendingsGifs*(self: Controller): seq[GifDto] =
proc getRecentsGifs*(self: Controller): seq[GifDto] = proc getRecentsGifs*(self: Controller): seq[GifDto] =
return self.gifService.getRecents() return self.gifService.getRecents()
proc loadRecentGifs*(self: Controller) =
self.gifService.asyncLoadRecentGifs()
proc loadFavoriteGifs*(self: Controller) =
self.gifService.asyncLoadFavoriteGifs()
proc getFavoritesGifs*(self: Controller): seq[GifDto] = proc getFavoritesGifs*(self: Controller): seq[GifDto] =
return self.gifService.getFavorites() return self.gifService.getFavorites()

View File

@ -50,9 +50,21 @@ method getTrendingsGifs*(self: AccessInterface): seq[GifDto] {.base.} =
method getRecentsGifs*(self: AccessInterface): seq[GifDto] {.base.} = method getRecentsGifs*(self: AccessInterface): seq[GifDto] {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method loadRecentGifs*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method loadRecentGifsDone*(self: AccessInterface, gifs: seq[GifDto]) {.base.} =
raise newException(ValueError, "No implementation available")
method getFavoritesGifs*(self: AccessInterface): seq[GifDto] {.base.} = method getFavoritesGifs*(self: AccessInterface): seq[GifDto] {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method loadFavoriteGifs*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method loadFavoriteGifsDone*(self: AccessInterface, gifs: seq[GifDto]) {.base.} =
raise newException(ValueError, "No implementation available")
method toggleFavoriteGif*(self: AccessInterface, item: GifDto) {.base.} = method toggleFavoriteGif*(self: AccessInterface, item: GifDto) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")

View File

@ -3,6 +3,7 @@ import io_interface
import ../io_interface as delegate_interface import ../io_interface as delegate_interface
import view, controller import view, controller
import ../../../../../global/global_singleton import ../../../../../global/global_singleton
import ../../../../../core/eventemitter
import ../../../../../../app_service/service/chat/service as chat_service import ../../../../../../app_service/service/chat/service as chat_service
import ../../../../../../app_service/service/community/service as community_service import ../../../../../../app_service/service/community/service as community_service
@ -21,6 +22,7 @@ type
proc newModule*( proc newModule*(
delegate: delegate_interface.AccessInterface, delegate: delegate_interface.AccessInterface,
events: EventEmitter,
sectionId: string, sectionId: string,
chatId: string, chatId: string,
belongsToCommunity: bool, belongsToCommunity: bool,
@ -33,7 +35,7 @@ proc newModule*(
result.delegate = delegate result.delegate = delegate
result.view = view.newView(result) result.view = view.newView(result)
result.viewVariant = newQVariant(result.view) result.viewVariant = newQVariant(result.view)
result.controller = controller.newController(result, sectionId, chatId, belongsToCommunity, chatService, communityService, gifService) result.controller = controller.newController(result, events, sectionId, chatId, belongsToCommunity, chatService, communityService, gifService)
result.moduleLoaded = false result.moduleLoaded = false
method delete*(self: Module) = method delete*(self: Module) =
@ -98,9 +100,21 @@ method getTrendingsGifs*(self: Module): seq[GifDto] =
method getRecentsGifs*(self: Module): seq[GifDto] = method getRecentsGifs*(self: Module): seq[GifDto] =
return self.controller.getRecentsGifs() return self.controller.getRecentsGifs()
method loadRecentGifs*(self: Module) =
self.controller.loadRecentGifs()
method loadRecentGifsDone*(self: Module, gifs: seq[GifDto]) =
self.view.updateGifColumns(gifs)
method getFavoritesGifs*(self: Module): seq[GifDto] = method getFavoritesGifs*(self: Module): seq[GifDto] =
return self.controller.getFavoritesGifs() return self.controller.getFavoritesGifs()
method loadFavoriteGifs*(self: Module) =
self.controller.loadFavoriteGifs()
method loadFavoriteGifsDone*(self: Module, gifs: seq[GifDto]) =
self.view.updateGifColumns(gifs)
method toggleFavoriteGif*(self: Module, item: GifDto) = method toggleFavoriteGif*(self: Module, item: GifDto) =
self.controller.toggleFavoriteGif(item) self.controller.toggleFavoriteGif(item)

View File

@ -80,7 +80,7 @@ QtObject:
read = getGifColumnC read = getGifColumnC
notify = gifLoaded notify = gifLoaded
proc updateGifColumns(self: View, data: seq[GifDto]) = proc updateGifColumns*(self: View, data: seq[GifDto]) =
var columnAData: seq[GifDto] = @[] var columnAData: seq[GifDto] = @[]
var columnAHeight = 0 var columnAHeight = 0
var columnBData: seq[GifDto] = @[] var columnBData: seq[GifDto] = @[]
@ -115,11 +115,21 @@ QtObject:
proc getRecentsGifs*(self: View) {.slot.} = proc getRecentsGifs*(self: View) {.slot.} =
let data = self.delegate.getRecentsGifs() let data = self.delegate.getRecentsGifs()
if data.len > 0:
self.updateGifColumns(data) self.updateGifColumns(data)
return
# recent gifs were not loaded yet, so we do it now
self.delegate.loadRecentGifs()
proc getFavoritesGifs*(self: View) {.slot.} = proc getFavoritesGifs*(self: View) {.slot.} =
let data = self.delegate.getFavoritesGifs() let data = self.delegate.getFavoritesGifs()
if data.len > 0:
self.updateGifColumns(data) self.updateGifColumns(data)
return
# favorite gifs were not loaded yet, so we do it now
self.delegate.loadFavoriteGifs()
proc findGifDto(self: View, id: string): GifDto = proc findGifDto(self: View, id: string): GifDto =
for item in self.gifColumnAModel.gifs: for item in self.gifColumnAModel.gifs:

View File

@ -52,7 +52,7 @@ proc newModule*(delegate: delegate_interface.AccessInterface, events: EventEmitt
isUsersListAvailable, settingsService, nodeConfigurationService, contactService, chatService, communityService, messageService) isUsersListAvailable, settingsService, nodeConfigurationService, contactService, chatService, communityService, messageService)
result.moduleLoaded = false result.moduleLoaded = false
result.inputAreaModule = input_area_module.newModule(result, sectionId, chatId, belongsToCommunity, chatService, communityService, gifService) result.inputAreaModule = input_area_module.newModule(result, events, sectionId, chatId, belongsToCommunity, chatService, communityService, gifService)
result.messagesModule = messages_module.newModule(result, events, sectionId, chatId, belongsToCommunity, result.messagesModule = messages_module.newModule(result, events, sectionId, chatId, belongsToCommunity,
contactService, communityService, chatService, messageService, mailserversService) contactService, communityService, chatService, messageService, mailserversService)
result.usersModule = result.usersModule =

View File

@ -0,0 +1,19 @@
include ../../common/json_utils
include ../../../app/core/tasks/common
import ../../../backend/gifs as status_go
type
AsyncGetRecentGifsTaskArg = ref object of QObjectTaskArg
const asyncGetRecentGifsTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[AsyncGetRecentGifsTaskArg](argEncoded)
let response = status_go.getRecentGifs()
arg.finish(response)
type
AsyncGetFavoriteGifsTaskArg = ref object of QObjectTaskArg
const asyncGetFavoriteGifsTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[AsyncGetFavoriteGifsTaskArg](argEncoded)
let response = status_go.getFavoriteGifs()
arg.finish(response)

View File

@ -4,11 +4,17 @@ import os
import uri import uri
import chronicles import chronicles
import sequtils import sequtils
import NimQml
import ../settings/service as settings_service import ../settings/service as settings_service
import ../../../app/core/eventemitter
import ../../../backend/backend import ../../../backend/backend
import ../../../app/core/tasks/[qt, threadpool]
import ../../../app/core/[main]
import ./dto import ./dto
include ./async_tasks
logScope: logScope:
topics = "gif-service" topics = "gif-service"
@ -23,18 +29,35 @@ let TENOR_API_KEY_RESOLVED =
else: else:
TENOR_API_KEY TENOR_API_KEY
const SIGNAL_LOAD_RECENT_GIFS_STARTED* = "loadRecentGifsStarted"
const SIGNAL_LOAD_RECENT_GIFS_DONE* = "loadRecentGifsDone"
const SIGNAL_LOAD_FAVORITE_GIFS_STARTED* = "loadFavoriteGifsStarted"
const SIGNAL_LOAD_FAVORITE_GIFS_DONE* = "loadFavoriteGifsDone"
type type
Service* = ref object of RootObj GifsArgs* = ref object of Args
gifs*: seq[GifDto]
error*: string
QtObject:
type
Service* = ref object of QObject
threadpool: ThreadPool
settingsService: settings_service.Service settingsService: settings_service.Service
favorites: seq[GifDto] favorites: seq[GifDto]
recents: seq[GifDto] recents: seq[GifDto]
events: EventEmitter
proc delete*(self: Service) = proc delete*(self: Service) =
discard discard
proc newService*(settingsService: settings_service.Service): Service = proc newService*(settingsService: settings_service.Service, events: EventEmitter, threadpool: ThreadPool): Service =
result = Service() result = Service()
result.QObject.setup
result.settingsService = settingsService result.settingsService = settingsService
result.events = events
result.threadpool = threadpool
result.favorites = @[] result.favorites = @[]
result.recents = @[] result.recents = @[]
@ -71,14 +94,62 @@ proc getFavoriteGifs(self: Service) =
except Exception as e: except Exception as e:
error "error: ", procName="getFavoriteGifs", errName = e.name, errDesription = e.msg error "error: ", procName="getFavoriteGifs", errName = e.name, errDesription = e.msg
proc asyncLoadRecentGifs*(self: Service) =
self.events.emit(SIGNAL_LOAD_RECENT_GIFS_STARTED, Args())
try:
let arg = AsyncGetRecentGifsTaskArg(
tptr: cast[ByteAddress](asyncGetRecentGifsTask),
vptr: cast[ByteAddress](self.vptr),
slot: "onAsyncGetRecentGifsDone"
)
self.threadpool.start(arg)
except Exception as e:
error "Error loading recent gifs", msg = e.msg
proc onAsyncGetRecentGifsDone*(self: Service, response: string) {.slot.} =
try:
let rpcResponseObj = response.parseJson
if (rpcResponseObj{"error"}.kind != JNull):
let error = Json.decode($rpcResponseObj["error"], RpcError)
error "error loading recent gifs", msg = error.message
return
self.recents = map(rpcResponseObj{"result"}.getElems(), settingToGifDto)
self.events.emit(SIGNAL_LOAD_RECENT_GIFS_DONE, GifsArgs(gifs: self.recents))
except Exception as e:
let errMsg = e.msg
error "error: ", errMsg
proc asyncLoadFavoriteGifs*(self: Service) =
self.events.emit(SIGNAL_LOAD_FAVORITE_GIFS_STARTED, Args())
try:
let arg = AsyncGetFavoriteGifsTaskArg(
tptr: cast[ByteAddress](asyncGetFavoriteGifsTask),
vptr: cast[ByteAddress](self.vptr),
slot: "onAsyncGetFavoriteGifsDone"
)
self.threadpool.start(arg)
except Exception as e:
error "Error loading favorite gifs", msg = e.msg
proc onAsyncGetFavoriteGifsDone*(self: Service, response: string) {.slot.} =
try:
let rpcResponseObj = response.parseJson
if (rpcResponseObj{"error"}.kind != JNull):
let error = Json.decode($rpcResponseObj["error"], RpcError)
error "error loading favorite gifs", msg = error.message
return
self.favorites = map(rpcResponseObj{"result"}.getElems(), settingToGifDto)
self.events.emit(SIGNAL_LOAD_FAVORITE_GIFS_DONE, GifsArgs(gifs: self.favorites))
except Exception as e:
let errMsg = e.msg
error "error: ", errMsg
proc init*(self: Service) = proc init*(self: Service) =
# set Tenor API Key # set Tenor API Key
self.setTenorAPIKey() self.setTenorAPIKey()
# get recent and favorite gifs on the database
self.getRecentGifs()
self.getFavoriteGifs()
proc tenorQuery(self: Service, path: string): seq[GifDto] = proc tenorQuery(self: Service, path: string): seq[GifDto] =
try: try:
let response = backend.fetchGifs(path) let response = backend.fetchGifs(path)

13
src/backend/gifs.nim Normal file
View File

@ -0,0 +1,13 @@
import json, strutils
import core
import response_type
export response_type
proc getRecentGifs*(): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* []
result = callPrivateRPC("gif_getRecentGifs", payload)
proc getFavoriteGifs*(): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* []
result = callPrivateRPC("gif_getFavoriteGifs", payload)