def _add_tracks(self, playlist_id, nodes): if len(nodes) < 1: warn(self, 'Empty list...') return False step = 50 start = 0 numtracks = len(nodes) if numtracks > 1000: notifyH('Qobuz', 'Max tracks per playlist reached (1000)' '\nSkipping %s tracks' % (numtracks - 1000), 'icon-error-256') numtracks = 1000 while start < numtracks: if (start + step) > numtracks: step = numtracks - start str_tracks = '' info(self, "Adding tracks start: %s, end: %s" % (start, start + step)) for i in range(start, start + step): node = nodes[i] if node.nt != Flag.TRACK: warn(self, "Not a Node_track node") continue str_tracks += '%s,' % (str(node.nid)) if not api.playlist_addTracks( playlist_id=playlist_id, track_ids=str_tracks): return False start += step return True
def find_cell(self, val=""): ''' Gets data from cell with value of val. Returns none if cell cannot be found. :param val: Str :return: Cell | None ''' # Throw error if val is not a Str assert type(val) == str, "'val' parameter passed '" + str( val) + "' should be Str but isn't" # sheet.find() throws an error if cell cannot be found, so this is handled by returning 'none' try: # Try to get cell dbg.log("Attempting to find cell with value '" + val + "'") cell = self.sheet.find(val) # Log success if cell if found dbg.success("Found cell with value '" + val + "'") return cell except gs_exceptions.CellNotFound: dbg.warn("Could not find cell with value '" + val + "'") return None
def _add_tracks(self, playlist_id, nodes): if len(nodes) < 1: warn(self, 'Empty list...') return False step = 50 start = 0 numtracks = len(nodes) if numtracks > 1000: notifyH( 'Qobuz', 'Max tracks per playlist reached (1000)' '\nSkipping %s tracks' % (numtracks - 1000), 'icon-error-256') numtracks = 1000 while start < numtracks: if (start + step) > numtracks: step = numtracks - start str_tracks = '' info(self, "Adding tracks start: %s, end: %s" % (start, start + step)) for i in range(start, start + step): node = nodes[i] if node.nt != Flag.TRACK: warn(self, "Not a Node_track node") continue str_tracks += '%s,' % (str(node.nid)) if not api.playlist_addTracks(playlist_id=playlist_id, track_ids=str_tracks): return False start += step return True
def scan_table(env, opinfo): val = opinfo.operands[0] tab_addr = opinfo.operands[1] tab_len = opinfo.operands[2] if len(opinfo.operands) > 3: form = opinfo.operands[3] else: form = 0x82 val_size = (form >> 7) + 1 # word or byte field_len = form & 127 addr = 0 for i in xrange(tab_len): test_addr = tab_addr + i * field_len if val_size == 2: test_val = env.u16(test_addr) else: test_val = env.u8(test_addr) if val == test_val: addr = test_addr break found = addr != 0 set_var(env, opinfo.store_var, addr) if found == opinfo.branch_on: handle_branch(env, opinfo.branch_offset) if DBG: warn(' found', found) warn(' addr', addr)
def remove(self): name = self.get_parameter("query") if name == "qobuz.com": return False if not name: return False user = self.get_user_data() if not user: return False friends = user["player_settings"] if not "friends" in friends: notifyH("Qobuz", "You don't have friend", "icon-error-256") warn(self, "No friends in user/player_settings") return False friends = friends["friends"] if not name in friends: notifyH("Qobuz", "You're not friend with %s" % (name), "icon-error-256") warn(self, "Friend " + repr(name) + " not in friends data") return False del friends[friends.index(name)] newdata = {"friends": friends} if not api.user_update(player_settings=json.dumps(newdata)): notifyH("Qobuz", "Friend %s added" % (name)) notifyH("Qobuz", "Cannot updata friend's list...", "icon-error-256") return False notifyH("Qobuz", "Friend %s removed" % (name)) self.delete_cache() executeBuiltin(containerRefresh()) return True
def _load_cache_data(self): cache = self.get_cache_path() debug(self, "Load: " + cache) if not os.path.exists(cache): return None mtime = None try: mtime = os.path.getmtime(cache) except: warn(self, "Cannot stat cache file: " + cache) refresh = self.get_cache_refresh() if refresh and refresh != -1: diff = time.time() - mtime if diff > refresh: debug(self, "Refreshing cache") self.hook_pre_refresh() return None data = None with open(cache, 'rb') as f: f = open(cache, 'rb') try: data = pickle.load(f) except: warn(self, "Picke can't load data from file") return None return data
def readPositionFromSavegame(self, savegamelist=None): import VS import Director plr=VS.getCurrentPlayer() self.InitPlayer(plr) player = self.players[plr] player.savegame=[] player.current=self.root if savegamelist: length=len(savegamelist) else: length=Director.getSaveDataLength(plr,self.name) for i in range(length): if savegamelist: newnodenum=int(savegamelist[i]) else: newnodenum=int(Director.getSaveData(plr,self.name,i)) if newnodenum>=0: if newnodenum>=len(player.current.subnodes): debug.debug(str(player.savegame)+': current has '+str(player.current.subnodes)) debug.warn('Error: save game index out of bounds: '+str(newnodenum)) return player.current=player.current.subnodes[newnodenum] elif newnodenum==-2: if not player.current.contingency: debug.debug(str(player.savegame)) debug.warn('Error: save game moves to invalid contengency node!') return player.current=player.current.contingency player.savegame.append(newnodenum) if VS.isserver(): custom.run("campaign", [self.name, "setsavegame"] + player.savegame, None) debug.debug('*** read position from save game: for '+self.name+': '+str(player.savegame)) debug.debug(player.current)
def makeListItem(self, replaceItems=False): colorItem = getSetting('item_default_color') colorPl = getSetting('item_section_color') label = self.get_label() image = self.get_image() owner = self.get_owner() url = self.make_url() if not self.is_my_playlist: label = '%s - %s' % (color(colorItem,owner), label) if self.b_is_current: label = '-o] %s [o-' % (color(colorPl, label)) #label = color(colorPl, label) item = xbmcgui.ListItem(label, owner, image, image, url) if not item: warn(self, "Error: Cannot make xbmc list item") return None item.setPath(url) ctxMenu = contextMenu() self.attach_context_menu(item, ctxMenu) item.addContextMenuItems(ctxMenu.getTuples(), replaceItems) return item
def __add_pagination(self, data): """build_down helper: Add pagination data when needed """ if not data: return False paginated = ['albums', 'labels', 'tracks', 'artists', 'playlists', 'playlist'] items = None need_pagination = False for p in paginated: if not p in data or data[p] is None: warn(self, 'No pagination data') continue items = data[p] if items['limit'] is None: continue if items['total'] > (items['offset'] + items['limit']): need_pagination = True break if not need_pagination: return False url = self.make_url(offset=items['offset'] + items['limit']) self.pagination_next = url self.pagination_total = items['total'] self.pagination_offset = items['offset'] self.pagination_limit = items['limit'] self.pagination_next_offset = items['offset'] + items['limit']
def makeListItem(self, replaceItems=False): import xbmcgui # @UnresolvedImport image = self.get_image() url = self.make_url() name = self.get_label() item = xbmcgui.ListItem(name, name, image, image, url) if not item: warn(self, "Error: Cannot make xbmc list item") return None item.setPath(url) item.setInfo('music', infoLabels={ # 'genre': 'reggae', # self.get_genre(), # 'year': '2000', # self.get_year(), 'artist': self.get_artist(), # 'album': self.get_title(), 'comment': self.get_description() # 'Artist_Description': 'coucou' }) ctxMenu = contextMenu() self.attach_context_menu(item, ctxMenu) item.addContextMenuItems(ctxMenu.getTuples(), replaceItems) return item
def remove(self): name = self.get_parameter('query') if name == 'qobuz.com': return False if not name: return False user = self.get_user_data() if not user: return False friends = user['player_settings'] if not 'friends' in friends: notifyH('Qobuz', "You don't have friend", 'icon-error-256') warn(self, "No friends in user/player_settings") return False friends = friends['friends'] if not name in friends: notifyH('Qobuz', "You're not friend with %s" % (name), 'icon-error-256') warn(self, "Friend " + repr(name) + " not in friends data") return False del friends[friends.index(name)] newdata = {'friends': friends} if not api.user_update(player_settings=json.dumps(newdata)): notifyH('Qobuz', 'Friend %s added' % (name)) notifyH('Qobuz', "Cannot updata friend's list...", 'icon-error-256') return False notifyH('Qobuz', 'Friend %s removed' % (name)) self.delete_cache() executeBuiltin(containerRefresh()) return True
def insert_obj(env, opinfo): obj = opinfo.operands[0] dest = opinfo.operands[1] if not obj or not dest: return # it doesn't say explicitly to make obj's parent # field say dest, but *surely* that's the right # thing to do. Right? # (based on what the ops seems to expect, I think so) # Also, should I remove it from its old parent? # Looks like, based on the current bug I have. _remove_obj(env, obj) # Ok, Yep. That totally fixed things. dest_child = get_child_num(env, dest) set_parent_num(env, obj, dest) set_sibling_num(env, obj, dest_child) set_child_num(env, dest, obj) if DBG: warn(' obj', obj, '(', get_obj_str(env, obj), ')') warn(' dest', dest, '(', get_obj_str(env, dest), ')')
def launch_new_wave(self): un = VS.getPlayer() if (vsrandom.randrange(0, 4) == 0): if (un): currentsystem = VS.getSystemFile() numadj = VS.GetNumAdjacentSystems(currentsystem) if (numadj): cursys = VS.GetAdjacentSystem( currentsystem, vsrandom.randrange(0, numadj)) else: cursys = 'enigma_sector/heavens_gate' debug.info("TJ: jumping to " + cursys) un.JumpTo(cursys) else: debug.warn("TJ: jumping to [ERROR: you are null]") return else: siglist = universe.significantUnits() if len(siglist) == 0: debug.info("TJ: siglist empty") return sig = siglist[vsrandom.randrange(0, len(siglist))] if (not sig): debug.info("TJ: sig null") return debug.info("TJ: autopiloting to " + sig.getName()) un.AutoPilotTo(sig, True) un.SetTarget(sig)
def __add_pagination(self, data): """build_down helper: Add pagination data when needed """ if not data: return False paginated = [ 'albums', 'labels', 'tracks', 'artists', 'playlists', 'playlist' ] items = None need_pagination = False for p in paginated: if not p in data or data[p] is None: warn(self, 'No pagination data') continue items = data[p] if items['limit'] is None: continue if items['total'] > (items['offset'] + items['limit']): need_pagination = True break if not need_pagination: return False url = self.make_url(offset=items['offset'] + items['limit']) self.pagination_next = url self.pagination_total = items['total'] self.pagination_offset = items['offset'] self.pagination_limit = items['limit'] self.pagination_next_offset = items['offset'] + items['limit']
def onPlayBackStarted(self): # workaroung bug, we are sometimes called multiple times. if self.trackId: if self.getProperty(keyTrackId) != self.trackId: self.trackId = None else: warn(self, "Already monitoring song id: %s" % (self.trackId)) return False nid = self.getProperty(keyTrackId) if not nid: warn(self, "No track id set by the player...") return False self.trackId = nid elapsed = 0 while elapsed <= 10: if not self.isPlayingAudio(): self.trackId = None return False if self.getProperty(keyTrackId) != self.trackId: self.trackId = None return False elapsed += 1 xbmc.sleep(1000) api.track_resportStreamingStart(nid) self.trackId = None return False
def launch_new_wave(self): un = VS.getPlayer() if (vsrandom.randrange(0,4)==0): if (un): currentsystem = VS.getSystemFile() numadj=VS.GetNumAdjacentSystems(currentsystem) if (numadj): cursys=VS.GetAdjacentSystem(currentsystem,vsrandom.randrange(0,numadj)) else: cursys = 'enigma_sector/heavens_gate' debug.info("TJ: jumping to "+cursys) un.JumpTo(cursys) else: debug.warn("TJ: jumping to [ERROR: you are null]") return else: siglist=universe.significantUnits() if len(siglist)==0: debug.info("TJ: siglist empty") return sig=siglist[vsrandom.randrange(0,len(siglist))] if (not sig): debug.info("TJ: sig null") return debug.info("TJ: autopiloting to "+sig.getName()) un.AutoPilotTo(sig,True) un.SetTarget(sig)
def fetch(self, Dir, lvl, whiteFlag, blackFlag): data = api.get('/album/get', album_id=self.nid) if not data: warn(self, "Cannot fetch product data") return False self.data = data return True
def to_s(cls, flag): if not flag: warn(cls, "Missing flag parameter") return '' flag = int(flag) if flag & cls.TRACK == cls.TRACK: return "track" elif flag & cls.PLAYLIST == cls.PLAYLIST: return "playlist" elif flag & cls.USERPLAYLISTS == cls.USERPLAYLISTS: return "user_playlists" elif flag & cls.RECOMMENDATION == cls.RECOMMENDATION: return "recommendation" elif flag & cls.ROOT == cls.ROOT: return "root" elif flag & cls.ALBUM == cls.ALBUM: return "album" elif flag & cls.PURCHASES == cls.PURCHASES: return "purchases" elif flag & cls.PURCHASE == cls.PURCHASE: return "purchase" elif flag & cls.FAVORITES == cls.FAVORITES: return "favorites" elif flag & cls.FAVORITE == cls.FAVORITE: return "favorite" elif flag & cls.SEARCH == cls.SEARCH: return "search" elif flag & cls.ARTIST == cls.ARTIST: return "artist" elif flag & cls.SIMILAR_ARTIST == cls.SIMILAR_ARTIST: return "similar_artist" elif flag & cls.FRIEND == cls.FRIEND: return "friend" elif flag & cls.FRIENDS == cls.FRIENDS: return "friends" elif flag & cls.GENRE == cls.GENRE: return "genre" elif flag & cls.LABEL == cls.LABEL: return "label" elif flag & cls.NODE == cls.NODE: return "inode" elif flag & cls.STOPBUILD == cls.STOPBUILD: return "stop_build_down" elif flag & cls.ARTICLES == cls.ARTICLES: return "articles" elif flag & cls.ARTICLE == cls.ARTICLE: return "article" elif flag & cls.PUBLIC_PLAYLISTS == cls.PUBLIC_PLAYLISTS: return "public_playlists" elif flag & cls.ARTICLE_RUBRICS == cls.ARTICLE_RUBRICS: return "article_rubrics" elif flag & cls.ALBUMS_BY_ARTIST == cls.ALBUMS_BY_ARTIST: return "albums_by_artist" elif flag & cls.COLLECTION == cls.COLLECTION: return "collection" elif flag & cls.COLLECTIONS == cls.COLLECTIONS: return "collections" else: raise QobuzXbmcError( who=cls, what='invalid_flag', additional=repr(flag))
def setCurrentNode(self,room,newnodenum): debug.warn('*** Going to branch number '+str(newnodenum)) import VS import Director plr = VS.getCurrentPlayer() if not self.checkPlayer(plr): return ["failure","player %d not initialized yet!"%plr] player = self.players[plr] if newnodenum>=0: if newnodenum>=len(player.current.subnodes): debug.debug('Error: cannot go to node '+str(newnodenum)) debug.debug('Failed node has text:') debug.debug(str(player.current.text)) return ["failure", "Attempt to go to an invalid subnode"] player.current=player.current.subnodes[newnodenum] player.savegame.append(newnodenum) Director.pushSaveData(VS.getCurrentPlayer(),self.name,float(newnodenum)) elif newnodenum==-2: if not player.current.contingency: debug.debug('Error: cannot go to contingency node!') debug.debug('Failed node has text:') debug.debug(str(player.current.text)) return ["failure", "Failed attempt to go to contingency"] debug.debug('*** Going to contingency!!!') player.current=player.current.contingency player.savegame.append(-2) Director.pushSaveData(VS.getCurrentPlayer(),self.name,float(-2)) if VS.isserver(): self.sendGotoMessage(newnodenum) if player.current.acceptClientInput() or not VS.networked(): if newnodenum != -1: player.current.evaluate(room) return ["success"]
def get_checked_parameters(): """Parse parameters passed to xbmc plugin as sys.argv """ d = dog() rparam = {} if len(sys.argv) <= 1: return rparam paramstring = sys.argv[2] if len(paramstring) >= 2: params = sys.argv[2] cleanedparams = params.replace('?', '') if (params[len(params) - 1] == '/'): params = params[0:len(params) - 2] pairsofparams = cleanedparams.split('&') for i in range(len(pairsofparams)): splitparams = {} splitparams = pairsofparams[i].split('=') if (len(splitparams)) == 2: if d.kv_is_ok(splitparams[0], splitparams[1]): rparam[splitparams[0]] = splitparams[1] else: warn('[DOG]', "--- Invalid key: %s / value: %s" % (splitparams[0], splitparams[1])) return rparam
def scan(self): import sys from node.flag import Flag """Building tree when using Xbmc library scanning feature """ from gui.directory import Directory if not self.set_root_node(): warn(self, "Cannot set root node ('%s')" % (str(self.node_type))) return False handle = qobuz.boot.handle Dir = Directory(self.root, self.nodes, withProgress=False) Dir.handle = int(sys.argv[1]) Dir.asList = False Dir.asLocalURL = True if self.root.nt & Flag.TRACK: self.root.fetch(None, None, Flag.TRACK, Flag.NONE) Dir.add_node(self.root) else: self.root.populating(Dir, self.depth, self.whiteFlag, self.blackFlag) Dir.set_content(self.root.content_type) Dir.end_of_directory() notifyH('Scanning results', str(Dir.total_put) + ' items where scanned', 3000) return True
def print_obj(env, opinfo): obj = opinfo.operands[0] string = get_obj_str(env, obj) write(env, string) if DBG: warn(' obj', obj, '(', get_obj_str(env, obj), ')')
def safeSyncBranch(self, **kwargs): # use kwargs # debug.log("""Alright, checking if we need to switch head refs... # debug.log("""Switching head refs""") # # if we do need to switch, we will first back up the current state of the current branch. if kwargs.has_key("cmsg"): cmsg = kwargs["cmsg"] else: cmsg = "Incremental Commit" if self.cachemeta: debug.log("Caching metadata") self.repo.backupMetadata() self.repo.gitAddAll() self.repo.gitCommitAll(cmsg) for remote in self.repo.gkremotes: toSync = self.repo.gkremotes[remote] toSync.pullRebase(self) if toSync.isWriteable(): toSync.push(self) else: debug.warn("Remote :" + toSync.name + " is not writeable - cannot sync!")
def gitCommitAll(self, cmsg): cmd = ["git", "commit", "-a", "-m", cmsg] try: self._native_exec(cmd) except GitPyCommandError: debug.warn("There was nothing to do")
def fetch(self, Dir, lvl, whiteFlag, blackFlag): limit = getSetting('pagination_limit') stype = self.search_type query = self.get_parameter('query', unQuote=True) if not query: from gui.util import Keyboard k = Keyboard('', stype) k.doModal() if not k.isConfirmed(): return False query = k.getText() query.strip() data = api.get('/search/getResults', query=query, type=stype, limit=limit, offset=self.offset) if not data: warn(self, "Search return no data") return False if data[stype]['total'] == 0: return False if not 'items' in data[stype]: return False self.set_parameter('query', query, quote=True) self.data = data return True
def scan(self): import sys from node.flag import Flag """Building tree when using Xbmc library scanning feature """ from gui.directory import Directory if not self.set_root_node(): warn(self, "Cannot set root node ('%s')" % (str(self.node_type))) return False handle = qobuz.boot.handle Dir = Directory(self.root, self.nodes, withProgress=False) Dir.handle = int(sys.argv[1]) Dir.asList = False Dir.asLocalURL = True if self.root.nt & Flag.TRACK: self.root.fetch(None, None, Flag.TRACK, Flag.NONE) Dir.add_node(self.root) else: self.root.populating(Dir, self.depth, self.whiteFlag, self.blackFlag) Dir.set_content(self.root.content_type) Dir.end_of_directory() notifyH("Scanning results", str(Dir.total_put) + " items where scanned", 3000) return True
def item_add_playing_property(self, item): mime = self.get_mimetype() if not mime: warn(self, "Cannot set item streaming url") return False item.setProperty('mimetype', mime) item.setPath(self.get_streaming_url()) return True
def __getFileUrl(self): format_id = 6 if getSetting('streamtype') == 'flac' else 5 data = api.get('/track/getFileUrl', format_id=format_id, track_id=self.nid, user_id=api.user_id) if not data: warn(self, "Cannot get stream type for track (network problem?)") return None return data
def _cache_path_exists(self, path): if not path: warn(self, "Cache directory is not set") return False if not os.path.exists(path): warn(self, "Cache directory doesn't seem to exist") return False return True
def fetch(self, Dir, lvl, whiteFlag, blackFlag): limit = getSetting("pagination_limit") data = api.get("/artist/getSimilarArtist", artist_id=self.nid, limit=limit, offset=self.offset, extra="albums") if not data: warn(self, "Cannot fetch albums for artist: " + self.get_label()) return False self.data = data return True
def mkdir(self, dir): if not os.path.isdir(dir): try: os.makedirs(dir) except: warn("Cannot create directory: " + dir) exit(2) info(self, "Directory created: " + dir)
def get_channel_by_name(self, name): debug.debug('Searching for channel ' + name + '...') if name in self._channels: debug.debug("Channel found " + name) return self._channels[name] else: debug.warn("Channel " + name + " not found !") return None
def _print_addr(env, addr): packed_string = read_packed_string(env, addr) string = unpack_string(env, packed_string) write(env, string) if DBG: warn(' helper: _print_addr') warn(' addr', addr)
def _delete_files(self, list): sw = safe_write() ret = True for file in list: if not sw.unlink(file): warn(self, "Cannot delete file: " + file) ret = False return ret
def inc(env, opinfo): var_num = opinfo.operands[0] var_val = to_signed_word(get_var(env, var_num)) var_val = var_val + 1 & 0xffff set_var(env, var_num, var_val) if DBG: warn(' var', get_var_name(var_num)) warn(' new_val', to_signed_word(var_val))
def dec(env, opinfo): var_num = opinfo.operands[0] var_val = to_signed_word(get_var(env, var_num)) var_val = var_val - 1 & 0xffff set_var(env, var_num, var_val) if DBG: warn(' var_num', var_num) warn(' new_val', to_signed_word(var_val))
def get_parent(env, opinfo): obj = opinfo.operands[0] parent_num = get_parent_num(env, obj) set_var(env, opinfo.store_var, parent_num) if DBG: warn(' obj', obj, '(', get_obj_str(env, obj), ')') warn(' parent', parent_num, '(', get_obj_str(env, parent_num), ')')
def fetch(self, Dir, lvl, whiteFlag, blackFlag): limit = getSetting('pagination_limit') data = api.get('/playlist/getUserPlaylists', limit=limit, offset=self.offset, user_id=api.user_id) if not data: warn(self, "Build-down: Cannot fetch user playlists data") return False self.data = data return True
def fetch(self, Dir, lvl, whiteFlag, blackFlag): limit = getSetting('pagination_limit') data = api.get('/purchase/getUserPurchases', limit=limit, offset=self.offset, user_id=api.user_id) if not data: warn(self, "Cannot fetch purchases data") return False self.data = data return True
def set_as_current(self, playlist_id = None): if not playlist_id: playlist_id = self.nid if not playlist_id: warn(self, 'Cannot set current playlist without id') return False userdata = self.get_user_storage() userdata['current_playlist'] = int(self.nid) return userdata.sync()
def fetch(self, Dir, lvl, whiteFlag, blackFlag): limit = getSetting('pagination_limit') data = api.get('/artist/get', artist_id=self.nid, limit=limit, offset=self.offset, extra='albums') if not data: warn(self, "Build-down: Cannot fetch artist data") return False self.data = data return True
def fetch(self, Dir, lvl, whiteFlag, blackFlag): limit = getSetting('pagination_limit', isInt=True) data = api.get('/playlist/get',playlist_id=self.nid, offset=self.offset, limit=limit, extra='tracks') if not data: warn(self, "Build-down: Cannot fetch playlist data") return False self.data = data return True
def backupTime(file): if (os.path.exists(file)): atime=str(os.stat(file).st_atime) mtime=str(os.stat(file).st_mtime) time=(atime,mtime) else: debug.warn ("file "+file+" does not exist") return time
def close(self): """Close our progress dialog """ if not self.is_enable: return True try: return super(Progress, self).close() except: warn(self, "Cannot close progress bar") return False
def art_shift(env, opinfo): number = to_signed_word(opinfo.operands[0]) places = to_signed_word(opinfo.operands[1]) if places < 0: result = number >> abs(places) else: result = number << places set_var(env, opinfo.store_var, result) if DBG: warn(' result', result)
def fetch(self, Dir, lvl, whiteFlag, blackFlag): limit = getSetting('pagination_limit') data = api.get('/favorite/getUserFavorites', user_id=api.user_id, limit=limit, offset=self.offset) if not data: warn(self, "Build-down: Cannot fetch favorites data") return False self.data = data return True