refactor(gifs): lazy load tenor api key + make search async

Fixes #9949

Only calls `setTenorAPIKey` once we need it (when doing a search or getting trending). 
Also caches the trending gifs so that they are only fetched once.
Makes the search and trending calls async by create an async tenor query async task.
This commit is contained in:
Jonathan Rainville 2023-03-20 14:58:20 -04:00
parent 21d2c00b40
commit 2f3f8f4e03
9 changed files with 215 additions and 73 deletions

View File

@ -50,6 +50,26 @@ proc init*(self: Controller) =
let args = GifsArgs(e)
self.delegate.loadFavoriteGifsDone(args.gifs)
self.events.on(SIGNAL_LOAD_TRENDING_GIFS_STARTED) do(e:Args):
self.delegate.loadTrendingGifsStarted()
self.events.on(SIGNAL_LOAD_TRENDING_GIFS_DONE) do(e:Args):
let args = GifsArgs(e)
self.delegate.loadTrendingGifsDone(args.gifs)
self.events.on(SIGNAL_LOAD_TRENDING_GIFS_ERROR) do(e:Args):
self.delegate.loadTrendingGifsError()
self.events.on(SIGNAL_SEARCH_GIFS_STARTED) do(e:Args):
self.delegate.searchGifsStarted()
self.events.on(SIGNAL_SEARCH_GIFS_DONE) do(e:Args):
let args = GifsArgs(e)
self.delegate.serachGifsDone(args.gifs)
self.events.on(SIGNAL_SEARCH_GIFS_ERROR) do(e:Args):
self.delegate.searchGifsError()
proc getChatId*(self: Controller): string =
return self.chatId
@ -86,11 +106,11 @@ proc acceptRequestAddressForTransaction*(self: Controller, messageId: string, ad
proc acceptRequestTransaction*(self: Controller, transactionHash: string, messageId: string, signature: string) =
self.chatService.acceptRequestTransaction(transactionHash, messageId, signature)
proc searchGifs*(self: Controller, query: string): seq[GifDto] =
return self.gifService.search(query)
proc searchGifs*(self: Controller, query: string) =
self.gifService.search(query)
proc getTrendingsGifs*(self: Controller): seq[GifDto] =
return self.gifService.getTrendings()
proc getTrendingsGifs*(self: Controller) =
self.gifService.getTrending()
proc getRecentsGifs*(self: Controller): seq[GifDto] =
return self.gifService.getRecents()

View File

@ -41,10 +41,10 @@ method acceptRequestAddressForTransaction*(self: AccessInterface, messageId: str
method acceptRequestTransaction*(self: AccessInterface, transactionHash: string, messageId: string, signature: string) {.base.} =
raise newException(ValueError, "No implementation available")
method searchGifs*(self: AccessInterface, query: string): seq[GifDto] {.base.} =
method searchGifs*(self: AccessInterface, query: string) {.base.} =
raise newException(ValueError, "No implementation available")
method getTrendingsGifs*(self: AccessInterface): seq[GifDto] {.base.} =
method getTrendingsGifs*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method getRecentsGifs*(self: AccessInterface): seq[GifDto] {.base.} =
@ -56,6 +56,24 @@ method loadRecentGifs*(self: AccessInterface) {.base.} =
method loadRecentGifsDone*(self: AccessInterface, gifs: seq[GifDto]) {.base.} =
raise newException(ValueError, "No implementation available")
method loadTrendingGifsStarted*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method loadTrendingGifsError*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method loadTrendingGifsDone*(self: AccessInterface, gifs: seq[GifDto]) {.base.} =
raise newException(ValueError, "No implementation available")
method searchGifsStarted*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method searchGifsError*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method serachGifsDone*(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")

View File

@ -91,11 +91,11 @@ method acceptRequestAddressForTransaction*(self: Module, messageId: string, addr
method acceptRequestTransaction*(self: Module, transactionHash: string, messageId: string, signature: string) =
self.controller.acceptRequestTransaction(transactionHash, messageId, signature)
method searchGifs*(self: Module, query: string): seq[GifDto] =
return self.controller.searchGifs(query)
method searchGifs*(self: Module, query: string) =
self.controller.searchGifs(query)
method getTrendingsGifs*(self: Module): seq[GifDto] =
return self.controller.getTrendingsGifs()
method getTrendingsGifs*(self: Module) =
self.controller.getTrendingsGifs()
method getRecentsGifs*(self: Module): seq[GifDto] =
return self.controller.getRecentsGifs()
@ -106,6 +106,30 @@ method loadRecentGifs*(self: Module) =
method loadRecentGifsDone*(self: Module, gifs: seq[GifDto]) =
self.view.updateGifColumns(gifs)
method loadTrendingGifsStarted*(self: Module) =
self.view.updateGifColumns(@[])
self.view.setGifLoading(true)
method loadTrendingGifsError*(self: Module) =
# Just setting loading to false works because the UI shows an error when there are no gifs
self.view.setGifLoading(false)
method loadTrendingGifsDone*(self: Module, gifs: seq[GifDto]) =
self.view.setGifLoading(false)
self.view.updateGifColumns(gifs)
method searchGifsStarted*(self: Module) =
self.view.updateGifColumns(@[])
self.view.setGifLoading(true)
method searchGifsError*(self: Module) =
# Just setting loading to false works because the UI shows an error when there are no gifs
self.view.setGifLoading(false)
method serachGifsDone*(self: Module, gifs: seq[GifDto]) =
self.view.setGifLoading(false)
self.view.updateGifColumns(gifs)
method getFavoritesGifs*(self: Module): seq[GifDto] =
return self.controller.getFavoritesGifs()

View File

@ -12,6 +12,7 @@ QtObject:
gifColumnAModel: GifColumnModel
gifColumnBModel: GifColumnModel
gifColumnCModel: GifColumnModel
gifLoading: bool
proc delete*(self: View) =
self.model.delete
@ -25,6 +26,7 @@ QtObject:
result.gifColumnAModel = newGifColumnModel()
result.gifColumnBModel = newGifColumnModel()
result.gifColumnCModel = newGifColumnModel()
result.gifLoading = false
proc load*(self: View) =
self.delegate.viewDidLoad()
@ -105,13 +107,22 @@ QtObject:
self.gifColumnCModel.setNewData(columnCData)
self.gifLoaded()
proc gifLoadingChanged*(self: View) {.signal.}
proc setGifLoading*(self: View, value: bool) =
self.gifLoading = value
self.gifLoadingChanged()
proc getGifLoading*(self: View): bool {.slot.} =
result = self.gifLoading
QtProperty[bool] gifLoading:
read = getGifLoading
notify = gifLoadingChanged
proc searchGifs*(self: View, query: string) {.slot.} =
let data = self.delegate.searchGifs(query)
self.updateGifColumns(data)
self.delegate.searchGifs(query)
proc getTrendingsGifs*(self: View) {.slot.} =
let data = self.delegate.getTrendingsGifs()
self.updateGifColumns(data)
self.delegate.getTrendingsGifs()
proc getRecentsGifs*(self: View) {.slot.} =
let data = self.delegate.getRecentsGifs()

View File

@ -1,6 +1,5 @@
include ../../common/json_utils
include ../../../app/core/tasks/common
import ../../../backend/gifs as status_go
type
AsyncGetRecentGifsTaskArg = ref object of QObjectTaskArg
@ -17,3 +16,43 @@ const asyncGetFavoriteGifsTask: Task = proc(argEncoded: string) {.gcsafe, nimcal
let arg = decode[AsyncGetFavoriteGifsTaskArg](argEncoded)
let response = status_go.getFavoriteGifs()
arg.finish(response)
type
AsyncTenorQueryArg = ref object of QObjectTaskArg
apiKeySet: bool
apiKey: string
query: string
event: string
errorEvent: string
const asyncTenorQuery: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[AsyncTenorQueryArg](argEncoded)
try:
if not arg.apiKeySet:
let response = status_go.setTenorAPIKey(arg.apiKey)
if(not response.error.isNil):
raise newException(RpcException, response.error.message)
let response = status_go.fetchGifs(arg.query)
let doc = response.result.str.parseJson()
var items: seq[GifDto] = @[]
for json in doc["results"]:
items.add(tenorToGifDto(json))
arg.finish(%* {
"items": items,
"event": arg.event,
"errorEvent": arg.errorEvent,
"error": "",
})
except Exception as e:
error "error: ", procName="asyncTenorQuery", query = arg.query, errDesription = e.msg
arg.finish(%* {
"error": e.msg,
"event": arg.event,
"errorEvent": arg.errorEvent
})

View File

@ -8,7 +8,7 @@ import NimQml
import ../settings/service as settings_service
import ../../../app/core/eventemitter
import ../../../backend/backend
import ../../../backend/backend as status_go
import ../../../app/core/tasks/[qt, threadpool]
import ../../../app/core/[main]
import ./dto
@ -35,6 +35,14 @@ const SIGNAL_LOAD_RECENT_GIFS_DONE* = "loadRecentGifsDone"
const SIGNAL_LOAD_FAVORITE_GIFS_STARTED* = "loadFavoriteGifsStarted"
const SIGNAL_LOAD_FAVORITE_GIFS_DONE* = "loadFavoriteGifsDone"
const SIGNAL_LOAD_TRENDING_GIFS_STARTED* = "loadTrendingGifsStarted"
const SIGNAL_LOAD_TRENDING_GIFS_DONE* = "loadTrendingGifsDone"
const SIGNAL_LOAD_TRENDING_GIFS_ERROR* = "loadTrendingGifsError"
const SIGNAL_SEARCH_GIFS_STARTED* = "searchGifsStarted"
const SIGNAL_SEARCH_GIFS_DONE* = "searchGifsDone"
const SIGNAL_SEARCH_GIFS_ERROR* = "searchGifsError"
type
GifsArgs* = ref object of Args
gifs*: seq[GifDto]
@ -47,7 +55,9 @@ QtObject:
settingsService: settings_service.Service
favorites: seq[GifDto]
recents: seq[GifDto]
trending: seq[GifDto]
events: EventEmitter
apiKeySet: bool
proc delete*(self: Service) =
discard
@ -60,39 +70,7 @@ QtObject:
result.threadpool = threadpool
result.favorites = @[]
result.recents = @[]
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
except Exception as e:
error "error: ", procName="setTenorAPIKey", errName = e.name, errDesription = e.msg
proc getRecentGifs(self: Service) =
try:
let response = backend.getRecentGifs()
if(not response.error.isNil):
error "error getRecentGifs: ", errDescription = response.error.message
self.recents = map(response.result.getElems(), settingToGifDto)
except Exception as e:
error "error: ", procName="getRecentGifs", errName = e.name, errDesription = e.msg
proc getFavoriteGifs(self: Service) =
try:
let response = backend.getFavoriteGifs()
if(not response.error.isNil):
error "error getFavoriteGifs: ", errDescription = response.error.message
self.favorites = map(response.result.getElems(), settingToGifDto)
except Exception as e:
error "error: ", procName="getFavoriteGifs", errName = e.name, errDesription = e.msg
result.apiKeySet = false
proc asyncLoadRecentGifs*(self: Service) =
self.events.emit(SIGNAL_LOAD_RECENT_GIFS_STARTED, Args())
@ -147,27 +125,68 @@ QtObject:
error "error: ", errMsg
proc init*(self: Service) =
# set Tenor API Key
self.setTenorAPIKey()
discard
proc tenorQuery(self: Service, path: string): seq[GifDto] =
proc search*(self: Service, query: string) =
try:
let response = backend.fetchGifs(path)
let doc = response.result.str.parseJson()
self.events.emit(SIGNAL_SEARCH_GIFS_STARTED, Args())
let arg = AsyncTenorQueryArg(
tptr: cast[ByteAddress](asyncTenorQuery),
vptr: cast[ByteAddress](self.vptr),
slot: "onAsyncTenorQueryDone",
apiKeySet: self.apiKeySet,
apiKey: TENOR_API_KEY_RESOLVED,
query: fmt("search?q={encodeUrl(query)}"),
event: SIGNAL_SEARCH_GIFS_DONE,
errorEvent: SIGNAL_SEARCH_GIFS_ERROR,
)
self.threadpool.start(arg)
except Exception as e:
error "Error getting trending gifs", msg = e.msg
proc getTrending*(self: Service) =
if self.trending.len > 0:
self.events.emit(SIGNAL_LOAD_TRENDING_GIFS_DONE, GifsArgs(gifs: self.trending))
return
try:
self.events.emit(SIGNAL_LOAD_TRENDING_GIFS_STARTED, Args())
let arg = AsyncTenorQueryArg(
tptr: cast[ByteAddress](asyncTenorQuery),
vptr: cast[ByteAddress](self.vptr),
slot: "onAsyncTenorQueryDone",
apiKeySet: self.apiKeySet,
apiKey: TENOR_API_KEY_RESOLVED,
query: "trending?",
event: SIGNAL_LOAD_TRENDING_GIFS_DONE,
errorEvent: SIGNAL_LOAD_TRENDING_GIFS_ERROR,
)
self.threadpool.start(arg)
except Exception as e:
error "Error getting trending gifs", msg = e.msg
proc onAsyncTenorQueryDone*(self: Service, response: string) {.slot.} =
let rpcResponseObj = response.parseJson
try:
if (rpcResponseObj{"error"}.kind != JNull and rpcResponseObj{"error"}.getStr != ""):
raise newException(RpcException, rpcResponseObj{"error"}.getStr)
self.apiKeySet = true
let itemsJson = rpcResponseObj["items"]
var items: seq[GifDto] = @[]
for json in doc["results"]:
items.add(tenorToGifDto(json))
for itemJson in itemsJson.items:
items.add(itemJson.settingToGifDto)
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?")
if rpcResponseObj["event"].getStr == SIGNAL_LOAD_TRENDING_GIFS_DONE:
# Save trending gifs in a local cache to not have to fetch them multiple times
self.trending = items
self.events.emit(rpcResponseObj["event"].getStr, GifsArgs(gifs: items))
except Exception as e:
let errMsg = e.msg
error "Error requesting sending query to Tenor", msg = errMsg
self.events.emit(rpcResponseObj["errorEvent"].getStr, GifsArgs(error: errMsg))
proc getRecents*(self: Service): seq[GifDto] =
return self.recents
@ -190,7 +209,7 @@ QtObject:
self.recents = newRecents
let recent = %*{"items": map(newRecents, toJsonNode)}
discard backend.updateRecentGifs(recent)
discard status_go.updateRecentGifs(recent)
proc getFavorites*(self: Service): seq[GifDto] =
return self.favorites
@ -217,4 +236,4 @@ QtObject:
self.favorites = newFavorites
let favorites = %*{"items": map(newFavorites, toJsonNode)}
discard backend.updateFavoriteGifs(favorites)
discard status_go.updateFavoriteGifs(favorites)

View File

@ -39,6 +39,7 @@ Popup {
property alias searchString: searchBox.text
property int currentCategory: GifPopupDefinitions.Category.Trending
property int previousCategory: GifPopupDefinitions.Category.Trending
property bool loading: RootStore.gifLoading
modal: false
width: 360
@ -264,6 +265,7 @@ Popup {
EmptyPlaceholder {
currentCategory: root.currentCategory
loading: root.loading
onDoRetry: searchBox.text === ""
? RootStore.getTrendingsGifs()
: searchGif(searchBox.text)

View File

@ -1,5 +1,5 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtGraphicalEffects 1.0
import StatusQ.Core 0.1
@ -10,7 +10,8 @@ import utils 1.0
Rectangle {
id: root
/*required*/ property int currentCategory: GifPopupDefinitions.Category.Trending
required property int currentCategory;
property bool loading: false
signal doRetry()
@ -20,9 +21,13 @@ Rectangle {
id: emptyText
anchors.centerIn: parent
text: {
if(root.currentCategory === GifPopupDefinitions.Category.Favorite) {
if (root.loading) {
return qsTr("Loading gifs...")
}
if (root.currentCategory === GifPopupDefinitions.Category.Favorite) {
return qsTr("Favorite GIFs will appear here")
} else if(root.currentCategory === GifPopupDefinitions.Category.Recent) {
}
if (root.currentCategory === GifPopupDefinitions.Category.Recent) {
return qsTr("Recent GIFs will appear here")
}
@ -35,7 +40,9 @@ Rectangle {
StatusButton {
text: qsTr("Retry")
visible: root.currentCategory === GifPopupDefinitions.Category.Trending || root.currentCategory === GifPopupDefinitions.Category.Search
visible: !root.loading &&
(root.currentCategory === GifPopupDefinitions.Category.Trending ||
root.currentCategory === GifPopupDefinitions.Category.Search)
anchors.top: emptyText.bottom
anchors.topMargin: Style.current.padding

View File

@ -108,6 +108,8 @@ QtObject {
: null
property var gifColumnC: chatSectionChatContentInputAreaInst ? chatSectionChatContentInputArea.gifColumnC
: null
property bool gifLoading: chatSectionChatContentInputAreaInst ? chatSectionChatContentInputArea.gifLoading
: false
function searchGifs(query) {
if (chatSectionChatContentInputAreaInst)