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.settingsService, result.nodeConfigurationService, statusFoundation.fleetConfiguration)
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.settingsService, result.walletAccountService, result.transactionService,
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/gif/service as gif_service
import ../../../../../../app_service/service/gif/dto
import ../../../../../core/eventemitter
type
Controller* = ref object of RootObj
delegate: io_interface.AccessInterface
sectionId: string
events: EventEmitter
chatId: string
belongsToCommunity: bool
communityService: community_service.Service
@ -17,6 +19,7 @@ type
proc newController*(
delegate: io_interface.AccessInterface,
events: EventEmitter,
sectionId: string,
chatId: string,
belongsToCommunity: bool,
@ -26,6 +29,7 @@ proc newController*(
): Controller =
result = Controller()
result.delegate = delegate
result.events = events
result.sectionId = chatId
result.chatId = chatId
result.belongsToCommunity = belongsToCommunity
@ -37,7 +41,13 @@ proc delete*(self: Controller) =
discard
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 =
return self.chatId
@ -84,6 +94,12 @@ proc getTrendingsGifs*(self: Controller): seq[GifDto] =
proc getRecentsGifs*(self: Controller): seq[GifDto] =
return self.gifService.getRecents()
proc loadRecentGifs*(self: Controller) =
self.gifService.asyncLoadRecentGifs()
proc loadFavoriteGifs*(self: Controller) =
self.gifService.asyncLoadFavoriteGifs()
proc getFavoritesGifs*(self: Controller): seq[GifDto] =
return self.gifService.getFavorites()

View File

@ -50,9 +50,21 @@ method getTrendingsGifs*(self: AccessInterface): seq[GifDto] {.base.} =
method getRecentsGifs*(self: AccessInterface): seq[GifDto] {.base.} =
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.} =
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.} =
raise newException(ValueError, "No implementation available")

View File

@ -3,6 +3,7 @@ import io_interface
import ../io_interface as delegate_interface
import view, controller
import ../../../../../global/global_singleton
import ../../../../../core/eventemitter
import ../../../../../../app_service/service/chat/service as chat_service
import ../../../../../../app_service/service/community/service as community_service
@ -21,6 +22,7 @@ type
proc newModule*(
delegate: delegate_interface.AccessInterface,
events: EventEmitter,
sectionId: string,
chatId: string,
belongsToCommunity: bool,
@ -33,7 +35,7 @@ proc newModule*(
result.delegate = delegate
result.view = view.newView(result)
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
method delete*(self: Module) =
@ -98,9 +100,21 @@ method getTrendingsGifs*(self: Module): seq[GifDto] =
method getRecentsGifs*(self: Module): seq[GifDto] =
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] =
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) =
self.controller.toggleFavoriteGif(item)

View File

@ -80,7 +80,7 @@ QtObject:
read = getGifColumnC
notify = gifLoaded
proc updateGifColumns(self: View, data: seq[GifDto]) =
proc updateGifColumns*(self: View, data: seq[GifDto]) =
var columnAData: seq[GifDto] = @[]
var columnAHeight = 0
var columnBData: seq[GifDto] = @[]
@ -115,11 +115,21 @@ QtObject:
proc getRecentsGifs*(self: View) {.slot.} =
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.} =
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 =
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)
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,
contactService, communityService, chatService, messageService, mailserversService)
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 chronicles
import sequtils
import NimQml
import ../settings/service as settings_service
import ../../../app/core/eventemitter
import ../../../backend/backend
import ../../../app/core/tasks/[qt, threadpool]
import ../../../app/core/[main]
import ./dto
include ./async_tasks
logScope:
topics = "gif-service"
@ -23,127 +29,192 @@ let TENOR_API_KEY_RESOLVED =
else:
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
Service* = ref object of RootObj
settingsService: settings_service.Service
favorites: seq[GifDto]
recents: seq[GifDto]
GifsArgs* = ref object of Args
gifs*: seq[GifDto]
error*: string
proc delete*(self: Service) =
discard
QtObject:
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 =
result = Service()
result.settingsService = settingsService
result.favorites = @[]
result.recents = @[]
proc delete*(self: Service) =
discard
proc setTenorAPIKey(self: Service) =
try:
let response = backend.setTenorAPIKey(TENOR_API_KEY_RESOLVED)
if(not response.error.isNil):
error "error setTenorAPIKey: ", errDescription = response.error.message
proc newService*(settingsService: settings_service.Service, events: EventEmitter, threadpool: ThreadPool): Service =
result = Service()
result.QObject.setup
result.settingsService = settingsService
result.events = events
result.threadpool = threadpool
result.favorites = @[]
result.recents = @[]
except Exception as e:
error "error: ", procName="setTenorAPIKey", errName = e.name, errDesription = e.msg
proc setTenorAPIKey(self: Service) =
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) =
try:
let response = backend.getRecentGifs()
except Exception as e:
error "error: ", procName="setTenorAPIKey", errName = e.name, errDesription = e.msg
if(not response.error.isNil):
error "error getRecentGifs: ", errDescription = response.error.message
proc getRecentGifs(self: Service) =
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:
error "error: ", procName="getRecentGifs", errName = e.name, errDesription = e.msg
self.recents = map(response.result.getElems(), settingToGifDto)
proc getFavoriteGifs(self: Service) =
try:
let response = backend.getFavoriteGifs()
except Exception as e:
error "error: ", procName="getRecentGifs", errName = e.name, errDesription = e.msg
if(not response.error.isNil):
error "error getFavoriteGifs: ", errDescription = response.error.message
proc getFavoriteGifs(self: Service) =
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:
error "error: ", procName="getFavoriteGifs", errName = e.name, errDesription = e.msg
self.favorites = map(response.result.getElems(), settingToGifDto)
proc init*(self: Service) =
# set Tenor API Key
self.setTenorAPIKey()
except Exception as e:
error "error: ", procName="getFavoriteGifs", errName = e.name, errDesription = e.msg
# get recent and favorite gifs on the database
self.getRecentGifs()
self.getFavoriteGifs()
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 tenorQuery(self: Service, path: string): seq[GifDto] =
try:
let response = backend.fetchGifs(path)
let doc = response.result.str.parseJson()
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
var items: seq[GifDto] = @[]
for json in doc["results"]:
items.add(tenorToGifDto(json))
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
return items
except:
return @[]
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 search*(self: Service, query: string): seq[GifDto] =
return self.tenorQuery(fmt("search?q={encodeUrl(query)}"))
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
proc getTrendings*(self: Service): seq[GifDto] =
return self.tenorQuery("trending?")
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 getRecents*(self: Service): seq[GifDto] =
return self.recents
proc init*(self: Service) =
# set Tenor API Key
self.setTenorAPIKey()
proc addToRecents*(self: Service, gifDto: GifDto) =
let recents = self.getRecents()
var newRecents: seq[GifDto] = @[gifDto]
var idx = 0
proc tenorQuery(self: Service, path: string): seq[GifDto] =
try:
let response = backend.fetchGifs(path)
let doc = response.result.str.parseJson()
while idx < MAX_RECENT - 1:
if idx >= recents.len:
break
var items: seq[GifDto] = @[]
for json in doc["results"]:
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
continue
newRecents.add(recents[idx])
idx += 1
self.recents = newRecents
let recent = %*{"items": map(newRecents, toJsonNode)}
discard backend.updateRecentGifs(recent)
self.recents = newRecents
let recent = %*{"items": map(newRecents, toJsonNode)}
discard backend.updateRecentGifs(recent)
proc getFavorites*(self: Service): seq[GifDto] =
return self.favorites
proc getFavorites*(self: Service): seq[GifDto] =
return self.favorites
proc isFavorite*(self: Service, gifDto: GifDto): bool =
for favorite in self.favorites:
if favorite.id == gifDto.id:
return true
return false
proc isFavorite*(self: Service, gifDto: GifDto): bool =
for favorite in self.favorites:
if favorite.id == gifDto.id:
return true
return false
proc toggleFavorite*(self: Service, gifDto: GifDto) =
var newFavorites: seq[GifDto] = @[]
var found = false
proc toggleFavorite*(self: Service, gifDto: GifDto) =
var newFavorites: seq[GifDto] = @[]
var found = false
for favoriteGif in self.getFavorites():
if favoriteGif.id == gifDto.id:
found = true
continue
for favoriteGif in self.getFavorites():
if favoriteGif.id == gifDto.id:
found = true
continue
newFavorites.add(favoriteGif)
newFavorites.add(favoriteGif)
if not found:
newFavorites.add(gifDto)
if not found:
newFavorites.add(gifDto)
self.favorites = newFavorites
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)