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) let args = GifsArgs(e)
self.delegate.loadFavoriteGifsDone(args.gifs) 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 = proc getChatId*(self: Controller): string =
return self.chatId return self.chatId
@ -86,11 +106,11 @@ proc acceptRequestAddressForTransaction*(self: Controller, messageId: string, ad
proc acceptRequestTransaction*(self: Controller, transactionHash: string, messageId: string, signature: string) = proc acceptRequestTransaction*(self: Controller, transactionHash: string, messageId: string, signature: string) =
self.chatService.acceptRequestTransaction(transactionHash, messageId, signature) self.chatService.acceptRequestTransaction(transactionHash, messageId, signature)
proc searchGifs*(self: Controller, query: string): seq[GifDto] = proc searchGifs*(self: Controller, query: string) =
return self.gifService.search(query) self.gifService.search(query)
proc getTrendingsGifs*(self: Controller): seq[GifDto] = proc getTrendingsGifs*(self: Controller) =
return self.gifService.getTrendings() self.gifService.getTrending()
proc getRecentsGifs*(self: Controller): seq[GifDto] = proc getRecentsGifs*(self: Controller): seq[GifDto] =
return self.gifService.getRecents() 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.} = method acceptRequestTransaction*(self: AccessInterface, transactionHash: string, messageId: string, signature: string) {.base.} =
raise newException(ValueError, "No implementation available") 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") raise newException(ValueError, "No implementation available")
method getTrendingsGifs*(self: AccessInterface): seq[GifDto] {.base.} = method getTrendingsGifs*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method getRecentsGifs*(self: AccessInterface): seq[GifDto] {.base.} = method getRecentsGifs*(self: AccessInterface): seq[GifDto] {.base.} =
@ -56,6 +56,24 @@ method loadRecentGifs*(self: AccessInterface) {.base.} =
method loadRecentGifsDone*(self: AccessInterface, gifs: seq[GifDto]) {.base.} = method loadRecentGifsDone*(self: AccessInterface, gifs: seq[GifDto]) {.base.} =
raise newException(ValueError, "No implementation available") 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.} = method getFavoritesGifs*(self: AccessInterface): seq[GifDto] {.base.} =
raise newException(ValueError, "No implementation available") 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) = method acceptRequestTransaction*(self: Module, transactionHash: string, messageId: string, signature: string) =
self.controller.acceptRequestTransaction(transactionHash, messageId, signature) self.controller.acceptRequestTransaction(transactionHash, messageId, signature)
method searchGifs*(self: Module, query: string): seq[GifDto] = method searchGifs*(self: Module, query: string) =
return self.controller.searchGifs(query) self.controller.searchGifs(query)
method getTrendingsGifs*(self: Module): seq[GifDto] = method getTrendingsGifs*(self: Module) =
return self.controller.getTrendingsGifs() self.controller.getTrendingsGifs()
method getRecentsGifs*(self: Module): seq[GifDto] = method getRecentsGifs*(self: Module): seq[GifDto] =
return self.controller.getRecentsGifs() return self.controller.getRecentsGifs()
@ -106,6 +106,30 @@ method loadRecentGifs*(self: Module) =
method loadRecentGifsDone*(self: Module, gifs: seq[GifDto]) = method loadRecentGifsDone*(self: Module, gifs: seq[GifDto]) =
self.view.updateGifColumns(gifs) 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] = method getFavoritesGifs*(self: Module): seq[GifDto] =
return self.controller.getFavoritesGifs() return self.controller.getFavoritesGifs()

View File

@ -12,6 +12,7 @@ QtObject:
gifColumnAModel: GifColumnModel gifColumnAModel: GifColumnModel
gifColumnBModel: GifColumnModel gifColumnBModel: GifColumnModel
gifColumnCModel: GifColumnModel gifColumnCModel: GifColumnModel
gifLoading: bool
proc delete*(self: View) = proc delete*(self: View) =
self.model.delete self.model.delete
@ -25,6 +26,7 @@ QtObject:
result.gifColumnAModel = newGifColumnModel() result.gifColumnAModel = newGifColumnModel()
result.gifColumnBModel = newGifColumnModel() result.gifColumnBModel = newGifColumnModel()
result.gifColumnCModel = newGifColumnModel() result.gifColumnCModel = newGifColumnModel()
result.gifLoading = false
proc load*(self: View) = proc load*(self: View) =
self.delegate.viewDidLoad() self.delegate.viewDidLoad()
@ -105,13 +107,22 @@ QtObject:
self.gifColumnCModel.setNewData(columnCData) self.gifColumnCModel.setNewData(columnCData)
self.gifLoaded() 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.} = proc searchGifs*(self: View, query: string) {.slot.} =
let data = self.delegate.searchGifs(query) self.delegate.searchGifs(query)
self.updateGifColumns(data)
proc getTrendingsGifs*(self: View) {.slot.} = proc getTrendingsGifs*(self: View) {.slot.} =
let data = self.delegate.getTrendingsGifs() self.delegate.getTrendingsGifs()
self.updateGifColumns(data)
proc getRecentsGifs*(self: View) {.slot.} = proc getRecentsGifs*(self: View) {.slot.} =
let data = self.delegate.getRecentsGifs() let data = self.delegate.getRecentsGifs()

View File

@ -1,6 +1,5 @@
include ../../common/json_utils include ../../common/json_utils
include ../../../app/core/tasks/common include ../../../app/core/tasks/common
import ../../../backend/gifs as status_go
type type
AsyncGetRecentGifsTaskArg = ref object of QObjectTaskArg AsyncGetRecentGifsTaskArg = ref object of QObjectTaskArg
@ -17,3 +16,43 @@ const asyncGetFavoriteGifsTask: Task = proc(argEncoded: string) {.gcsafe, nimcal
let arg = decode[AsyncGetFavoriteGifsTaskArg](argEncoded) let arg = decode[AsyncGetFavoriteGifsTaskArg](argEncoded)
let response = status_go.getFavoriteGifs() let response = status_go.getFavoriteGifs()
arg.finish(response) 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 ../settings/service as settings_service
import ../../../app/core/eventemitter import ../../../app/core/eventemitter
import ../../../backend/backend import ../../../backend/backend as status_go
import ../../../app/core/tasks/[qt, threadpool] import ../../../app/core/tasks/[qt, threadpool]
import ../../../app/core/[main] import ../../../app/core/[main]
import ./dto 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_STARTED* = "loadFavoriteGifsStarted"
const SIGNAL_LOAD_FAVORITE_GIFS_DONE* = "loadFavoriteGifsDone" 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 type
GifsArgs* = ref object of Args GifsArgs* = ref object of Args
gifs*: seq[GifDto] gifs*: seq[GifDto]
@ -47,7 +55,9 @@ QtObject:
settingsService: settings_service.Service settingsService: settings_service.Service
favorites: seq[GifDto] favorites: seq[GifDto]
recents: seq[GifDto] recents: seq[GifDto]
trending: seq[GifDto]
events: EventEmitter events: EventEmitter
apiKeySet: bool
proc delete*(self: Service) = proc delete*(self: Service) =
discard discard
@ -60,39 +70,7 @@ QtObject:
result.threadpool = threadpool result.threadpool = threadpool
result.favorites = @[] result.favorites = @[]
result.recents = @[] result.recents = @[]
result.apiKeySet = false
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
proc asyncLoadRecentGifs*(self: Service) = proc asyncLoadRecentGifs*(self: Service) =
self.events.emit(SIGNAL_LOAD_RECENT_GIFS_STARTED, Args()) self.events.emit(SIGNAL_LOAD_RECENT_GIFS_STARTED, Args())
@ -147,27 +125,68 @@ QtObject:
error "error: ", errMsg error "error: ", errMsg
proc init*(self: Service) = proc init*(self: Service) =
# set Tenor API Key discard
self.setTenorAPIKey()
proc tenorQuery(self: Service, path: string): seq[GifDto] = proc search*(self: Service, query: string) =
try: try:
let response = backend.fetchGifs(path) self.events.emit(SIGNAL_SEARCH_GIFS_STARTED, Args())
let doc = response.result.str.parseJson() 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] = @[] var items: seq[GifDto] = @[]
for json in doc["results"]: for itemJson in itemsJson.items:
items.add(tenorToGifDto(json)) items.add(itemJson.settingToGifDto)
return items if rpcResponseObj["event"].getStr == SIGNAL_LOAD_TRENDING_GIFS_DONE:
except: # Save trending gifs in a local cache to not have to fetch them multiple times
return @[] self.trending = items
proc search*(self: Service, query: string): seq[GifDto] = self.events.emit(rpcResponseObj["event"].getStr, GifsArgs(gifs: items))
return self.tenorQuery(fmt("search?q={encodeUrl(query)}")) except Exception as e:
let errMsg = e.msg
proc getTrendings*(self: Service): seq[GifDto] = error "Error requesting sending query to Tenor", msg = errMsg
return self.tenorQuery("trending?") self.events.emit(rpcResponseObj["errorEvent"].getStr, GifsArgs(error: errMsg))
proc getRecents*(self: Service): seq[GifDto] = proc getRecents*(self: Service): seq[GifDto] =
return self.recents return self.recents
@ -190,7 +209,7 @@ QtObject:
self.recents = newRecents self.recents = newRecents
let recent = %*{"items": map(newRecents, toJsonNode)} let recent = %*{"items": map(newRecents, toJsonNode)}
discard backend.updateRecentGifs(recent) discard status_go.updateRecentGifs(recent)
proc getFavorites*(self: Service): seq[GifDto] = proc getFavorites*(self: Service): seq[GifDto] =
return self.favorites return self.favorites
@ -217,4 +236,4 @@ QtObject:
self.favorites = newFavorites self.favorites = newFavorites
let favorites = %*{"items": map(newFavorites, toJsonNode)} 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 alias searchString: searchBox.text
property int currentCategory: GifPopupDefinitions.Category.Trending property int currentCategory: GifPopupDefinitions.Category.Trending
property int previousCategory: GifPopupDefinitions.Category.Trending property int previousCategory: GifPopupDefinitions.Category.Trending
property bool loading: RootStore.gifLoading
modal: false modal: false
width: 360 width: 360
@ -264,6 +265,7 @@ Popup {
EmptyPlaceholder { EmptyPlaceholder {
currentCategory: root.currentCategory currentCategory: root.currentCategory
loading: root.loading
onDoRetry: searchBox.text === "" onDoRetry: searchBox.text === ""
? RootStore.getTrendingsGifs() ? RootStore.getTrendingsGifs()
: searchGif(searchBox.text) : searchGif(searchBox.text)

View File

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

View File

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