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()
self.updateGifColumns(data) if data.len > 0:
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()
self.updateGifColumns(data) if data.len > 0:
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,127 +29,192 @@ 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
settingsService: settings_service.Service gifs*: seq[GifDto]
favorites: seq[GifDto] error*: string
recents: seq[GifDto]
proc delete*(self: Service) = QtObject:
discard type
Service* = ref object of QObject
threadpool: ThreadPool
settingsService: settings_service.Service
favorites: seq[GifDto]
recents: seq[GifDto]
events: EventEmitter
proc newService*(settingsService: settings_service.Service): Service = proc delete*(self: Service) =
result = Service() discard
result.settingsService = settingsService
result.favorites = @[]
result.recents = @[]
proc setTenorAPIKey(self: Service) = proc newService*(settingsService: settings_service.Service, events: EventEmitter, threadpool: ThreadPool): Service =
try: result = Service()
let response = backend.setTenorAPIKey(TENOR_API_KEY_RESOLVED) result.QObject.setup
if(not response.error.isNil): result.settingsService = settingsService
error "error setTenorAPIKey: ", errDescription = response.error.message result.events = events
result.threadpool = threadpool
result.favorites = @[]
result.recents = @[]
except Exception as e: proc setTenorAPIKey(self: Service) =
error "error: ", procName="setTenorAPIKey", errName = e.name, errDesription = e.msg try:
let response = backend.setTenorAPIKey(TENOR_API_KEY_RESOLVED)
if(not response.error.isNil):
error "error setTenorAPIKey: ", errDescription = response.error.message
proc getRecentGifs(self: Service) = except Exception as e:
try: error "error: ", procName="setTenorAPIKey", errName = e.name, errDesription = e.msg
let response = backend.getRecentGifs()
if(not response.error.isNil): proc getRecentGifs(self: Service) =
error "error getRecentGifs: ", errDescription = response.error.message try:
let response = backend.getRecentGifs()
self.recents = map(response.result.getElems(), settingToGifDto) if(not response.error.isNil):
error "error getRecentGifs: ", errDescription = response.error.message
except Exception as e: self.recents = map(response.result.getElems(), settingToGifDto)
error "error: ", procName="getRecentGifs", errName = e.name, errDesription = e.msg
proc getFavoriteGifs(self: Service) = except Exception as e:
try: error "error: ", procName="getRecentGifs", errName = e.name, errDesription = e.msg
let response = backend.getFavoriteGifs()
if(not response.error.isNil): proc getFavoriteGifs(self: Service) =
error "error getFavoriteGifs: ", errDescription = response.error.message try:
let response = backend.getFavoriteGifs()
self.favorites = map(response.result.getElems(), settingToGifDto) if(not response.error.isNil):
error "error getFavoriteGifs: ", errDescription = response.error.message
except Exception as e: self.favorites = map(response.result.getElems(), settingToGifDto)
error "error: ", procName="getFavoriteGifs", errName = e.name, errDesription = e.msg
proc init*(self: Service) = except Exception as e:
# set Tenor API Key error "error: ", procName="getFavoriteGifs", errName = e.name, errDesription = e.msg
self.setTenorAPIKey()
# get recent and favorite gifs on the database proc asyncLoadRecentGifs*(self: Service) =
self.getRecentGifs() self.events.emit(SIGNAL_LOAD_RECENT_GIFS_STARTED, Args())
self.getFavoriteGifs() 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 tenorQuery(self: Service, path: string): seq[GifDto] = proc onAsyncGetRecentGifsDone*(self: Service, response: string) {.slot.} =
try: try:
let response = backend.fetchGifs(path) let rpcResponseObj = response.parseJson
let doc = response.result.str.parseJson() if (rpcResponseObj{"error"}.kind != JNull):
let error = Json.decode($rpcResponseObj["error"], RpcError)
error "error loading recent gifs", msg = error.message
return
var items: seq[GifDto] = @[] self.recents = map(rpcResponseObj{"result"}.getElems(), settingToGifDto)
for json in doc["results"]: self.events.emit(SIGNAL_LOAD_RECENT_GIFS_DONE, GifsArgs(gifs: self.recents))
items.add(tenorToGifDto(json)) except Exception as e:
let errMsg = e.msg
error "error: ", errMsg
return items proc asyncLoadFavoriteGifs*(self: Service) =
except: self.events.emit(SIGNAL_LOAD_FAVORITE_GIFS_STARTED, Args())
return @[] 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 search*(self: Service, query: string): seq[GifDto] = proc onAsyncGetFavoriteGifsDone*(self: Service, response: string) {.slot.} =
return self.tenorQuery(fmt("search?q={encodeUrl(query)}")) 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
proc getTrendings*(self: Service): seq[GifDto] = self.favorites = map(rpcResponseObj{"result"}.getElems(), settingToGifDto)
return self.tenorQuery("trending?") self.events.emit(SIGNAL_LOAD_FAVORITE_GIFS_DONE, GifsArgs(gifs: self.favorites))
except Exception as e:
let errMsg = e.msg
error "error: ", errMsg
proc getRecents*(self: Service): seq[GifDto] = proc init*(self: Service) =
return self.recents # set Tenor API Key
self.setTenorAPIKey()
proc addToRecents*(self: Service, gifDto: GifDto) = proc tenorQuery(self: Service, path: string): seq[GifDto] =
let recents = self.getRecents() try:
var newRecents: seq[GifDto] = @[gifDto] let response = backend.fetchGifs(path)
var idx = 0 let doc = response.result.str.parseJson()
while idx < MAX_RECENT - 1: var items: seq[GifDto] = @[]
if idx >= recents.len: for json in doc["results"]:
break items.add(tenorToGifDto(json))
if recents[idx].id == gifDto.id: return items
except:
return @[]
proc search*(self: Service, query: string): seq[GifDto] =
return self.tenorQuery(fmt("search?q={encodeUrl(query)}"))
proc getTrendings*(self: Service): seq[GifDto] =
return self.tenorQuery("trending?")
proc getRecents*(self: Service): seq[GifDto] =
return self.recents
proc addToRecents*(self: Service, gifDto: GifDto) =
let recents = self.getRecents()
var newRecents: seq[GifDto] = @[gifDto]
var idx = 0
while idx < MAX_RECENT - 1:
if idx >= recents.len:
break
if recents[idx].id == gifDto.id:
idx += 1
continue
newRecents.add(recents[idx])
idx += 1 idx += 1
continue
newRecents.add(recents[idx]) self.recents = newRecents
idx += 1 let recent = %*{"items": map(newRecents, toJsonNode)}
discard backend.updateRecentGifs(recent)
self.recents = newRecents proc getFavorites*(self: Service): seq[GifDto] =
let recent = %*{"items": map(newRecents, toJsonNode)} return self.favorites
discard backend.updateRecentGifs(recent)
proc getFavorites*(self: Service): seq[GifDto] = proc isFavorite*(self: Service, gifDto: GifDto): bool =
return self.favorites for favorite in self.favorites:
if favorite.id == gifDto.id:
return true
return false
proc isFavorite*(self: Service, gifDto: GifDto): bool = proc toggleFavorite*(self: Service, gifDto: GifDto) =
for favorite in self.favorites: var newFavorites: seq[GifDto] = @[]
if favorite.id == gifDto.id: var found = false
return true
return false
proc toggleFavorite*(self: Service, gifDto: GifDto) = for favoriteGif in self.getFavorites():
var newFavorites: seq[GifDto] = @[] if favoriteGif.id == gifDto.id:
var found = false found = true
continue
for favoriteGif in self.getFavorites(): newFavorites.add(favoriteGif)
if favoriteGif.id == gifDto.id:
found = true
continue
newFavorites.add(favoriteGif) if not found:
newFavorites.add(gifDto)
if not found: self.favorites = newFavorites
newFavorites.add(gifDto) let favorites = %*{"items": map(newFavorites, toJsonNode)}
discard backend.updateFavoriteGifs(favorites)
self.favorites = newFavorites
let favorites = %*{"items": map(newFavorites, toJsonNode)}
discard backend.updateFavoriteGifs(favorites)

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)