def insertGame(self, game_row, isUpdate, gameId, allowUpdate): #HACK: delete first element as we do not want to insert or update the id del game_row[0] # Check if exists and insert/update as appropriate; move this functionality to the Game object try: if not isUpdate: log.info(u"Game does not exist in database. Insert game: %s" % game_row[DataBaseObject.COL_NAME]) Game(self.gdb).insert(game_row) return self.gdb.cursor.lastrowid else: if allowUpdate: # check if we are allowed to update with null values allowOverwriteWithNullvalues = __addon__.getSetting( util.SETTING_RCB_ALLOWOVERWRITEWITHNULLVALUES).upper() == 'TRUE' log.info("allowOverwriteWithNullvalues: {0}".format(allowOverwriteWithNullvalues)) log.info(u"Game does exist in database. Update game: %s" % game_row[DataBaseObject.COL_NAME]) #remove id from column list columns = Game.FIELDNAMES[1:] Game(self.gdb).update(columns, game_row, gameId, allowOverwriteWithNullvalues) else: log.info( u"Game does exist in database but update is not allowed for current rom collection. game: %s" % game_row[DataBaseObject.COL_NAME]) return gameId except Exception as exc: log.error(u"An error occured while adding game '%s'. Error: %s" % (game_row[DataBaseObject.COL_NAME], exc)) return None
def __extract_files(self, archive_file, filenames, directory_to): log.info("ArchiveHandler.__extract_files") files_out = list() if self.__is_7z_on_windows(archive_file): return self.__getArchives7z(archive_file, filenames, directory_to) archive_path = self.__get_archive_file_name(archive_file) for ff in filenames: file_from = os.path.join(archive_path, ff).replace('\\', '/') success = xbmcvfs.copy( file_from, os.path.join(xbmcvfs.translatePath(directory_to), ff)) if not success: log.error( 'Error extracting file %(ff)s from archive %(archive_file)s' % { 'ff': ff, 'archive_file': archive_file }) else: log.debug( 'Extracted file %(ff)s from archive %(archive_file)s' % { 'ff': ff, 'archive_file': archive_file }) files_out.append( os.path.join(xbmcvfs.translatePath(directory_to), ff)) return files_out
def __handleCompressedFile(self, gui, filext, rom, emuParams): log.info("__handleCompressedFile") # Note: Trying to delete temporary files (from zip or 7z extraction) from last run # Do this before launching a new game. Otherwise game could be deleted before launch tempDir = os.path.join(util.getTempDir(), 'extracted', self.romCollection.name) # check if folder exists if not xbmcvfs.exists(tempDir +'\\'): log.info("Create temporary folder: " +tempDir) xbmcvfs.mkdir(tempDir) try: if xbmcvfs.exists(tempDir +'\\'): log.info("Trying to delete temporary rom files") #can't use xbmcvfs.listdir here as it seems to cache the file list and RetroPlayer won't find newly created files anymore files = os.listdir(tempDir) for f in files: #RetroPlayer places savestate files next to the roms. Don't delete these files. fname, ext = os.path.splitext(f) if(ext not in ('.sav', '.xml', '.png')): xbmcvfs.delete(os.path.join(tempDir, f)) except Exception, (exc): log.error("Error deleting files after launch emu: " + str(exc)) gui.writeMsg(util.localize(32036) + ": " + str(exc))
def get_scraper_by_name(self, sname): """Given a scraper name, returns the scraper class Args: sname: Name of the scraper, e.g. thegamesdb.net or MAME Raises: ConfigScraperSiteDoesNotExistException: No scraper matches the name """ try: target = self.scrapers[sname] except KeyError as e: raise ConfigScraperSiteDoesNotExistException( "Unsupported scraper: {0}".format(sname)) log.debug("Instantiating scraper class {0} - {1}".format( sname, target)) try: module = __import__(target.lower()) class_ = getattr(module, target) instance = class_() except ImportError: log.error("Unable to find scraper {0}".format(sname)) raise return instance
def __getArchives7z(self, filepath, archiveList, directory_to): try: import py7zlib except ImportError: # 32039 = Error launching .7z file. # 32129 = Please check kodi.log for details. message = "%s[CR]%s" % (util.localize(32039), util.localize(32129)) xbmcgui.Dialog().ok(util.SCRIPTNAME, message) msg = ( "You have tried to launch a .7z file but you are missing required libraries to extract the file. " "You can download the latest RCB version from RCBs project page. It contains all required libraries." ) log.error(msg) return None fp = open(str(filepath), 'rb') archive = py7zlib.Archive7z(fp) archivesDecompressed = [(name, archive.getmember(name).read()) for name in archiveList] fp.close() if archivesDecompressed is None: log.warn("Error handling compressed file") return [] for archive in archivesDecompressed: newPath = os.path.join(directory_to, archive[0]) log.info("Putting extracted file in %s" % newPath) fo = open(str(newPath), 'wb') fo.write(archive[1]) fo.close() return archivesDecompressed
def search(self, gamename, platform=None): #use description to search for the game name as the name attribute also contains the region #FIXME TODO #currently not working with MAME xml files as the rom files don't use the friendly game name pattern = "\<description\>(.*%s.*)\</description\>" % GameNameUtil( ).prepare_gamename_for_searchrequest(gamename) results = [] try: with io.open(self._get_xml_path(), 'r', encoding="utf-8") as xmlfile: for line in xmlfile: result = re.search(pattern, line) if result: gamename = result.groups()[0] results.append({ 'id': gamename, 'title': gamename, 'SearchKey': [gamename] }) except Exception as e: log.error(e) raise return results
def __handleCompressedFile(self, gui, filext, rom, emuParams): log.info("__handleCompressedFile") # Note: Trying to delete temporary files (from zip or 7z extraction) from last run # Do this before launching a new game. Otherwise game could be deleted before launch tempDir = os.path.join(util.getTempDir(), 'extracted', self.romCollection.name) # check if folder exists if not xbmcvfs.exists(tempDir + '\\'): log.info("Create temporary folder: " + tempDir) xbmcvfs.mkdir(tempDir) try: if xbmcvfs.exists(tempDir + '\\'): log.info("Trying to delete temporary rom files") #can't use xbmcvfs.listdir here as it seems to cache the file list and RetroPlayer won't find newly created files anymore files = os.listdir(tempDir) for f in files: #RetroPlayer places savestate files next to the roms. Don't delete these files. fname, ext = os.path.splitext(f) if (ext not in ('.sav', '.xml', '.png')): xbmcvfs.delete(os.path.join(tempDir, f)) except Exception, (exc): log.error("Error deleting files after launch emu: " + str(exc)) gui.writeMsg(util.localize(32036) + ": " + str(exc))
def search(self, gamename, platform=None): #use description to search for the game name as the name attribute also contains the region pattern = "\<description\>(.*%s.*)\</description\>" % GameNameUtil( ).prepare_gamename_for_searchrequest(gamename) if platform in self._MAME_style_platforms: pattern = 'name="(.*%s.*)"' % GameNameUtil( ).prepare_gamename_for_searchrequest(gamename) results = [] try: fh = xbmcvfs.File(self._get_xml_path()) text = fh.read() text = util.html_unescape(text) fh.close() for line in text.splitlines(): #HACK: Apostrophes are removed in prepare_gamename_for_searchrequest. So we also have to do it here. result = re.search(pattern, line.replace("'", "")) if result: gamename = result.groups()[0] results.append({ 'id': gamename, 'title': gamename, 'SearchKey': [gamename] }) except Exception as e: log.error(e) raise return results
def _parseSearchResults(self, response): """ Parse the json response from the Games/ByGameName API call Returns a list of dicts with id, title and releaseDate """ results = [] code = response['code'] status = response['status'] if code != 200 or status != "Success": log.error("thegamesdb returned an error. Code = %s, Status = %s" % (code, status)) return results data = response['data'] self.resultdata = {} for result in data['games']: results.append({ 'id': result['id'], 'title': result['game_title'], 'releaseDate': result['release_date'], 'SearchKey': [result['game_title']] }) #store result for later use in retrieve method self.resultdata[result['id']] = result log.info(u"Found {0} results using json response: {1}".format( len(results), results)) return results
def buildMediaTypeList(self, configObj, isUpdate): #build fileTypeList fileTypeList = [] if isUpdate: fileTypes = configObj.tree.findall('FileTypes/FileType') else: #build fileTypeList configFile = util.joinPath(util.getAddonInstallPath(), 'resources', 'database', 'config_template.xml') if not xbmcvfs.exists(configFile): log.error( "File config_template.xml does not exist. Place a valid config file here: " + str(configFile)) return None, util.localize(32040) tree = ElementTree().parse(configFile) fileTypes = tree.findall('FileTypes/FileType') for fileType in fileTypes: name = fileType.attrib.get('name') if name != None: mediaType = fileType.find('type') if mediaType != None and mediaType.text == 'video': name = name + ' (video)' fileTypeList.append(name) return fileTypeList, ''
def get_scraper_by_name(self, sname): """Given a scraper name, returns the scraper class Args: sname: Name of the scraper, e.g. thegamesdb.net or MAME Raises: ConfigScraperSiteDoesNotExistException: No scraper matches the name """ try: target = self.scrapers[sname] except KeyError as e: raise ConfigScraperSiteDoesNotExistException("Unsupported scraper: {0}".format(sname)) log.debug("Instantiating scraper class {0} - {1}".format(sname, target)) try: module = __import__(target.lower()) class_ = getattr(module, target) instance = class_() except ImportError: log.error("Unable to find scraper {0}".format(sname)) raise return instance
def search(self, gamename, platform=None): """ Ignore platform """ # Newer versions support multi systems, $info=XXX indicates an arcade ROM startmarker = "$info=%s," %gamename gamedata = False data = "" historyfile_path = self._get_history_path() try: with io.open(historyfile_path, 'r', encoding="utf-8") as historyfile: for line in historyfile: if line.startswith(startmarker): gamedata = True if gamedata: data += line if line.startswith("$end"): gamedata = False except Exception as e: log.error(e) raise try: # Note the regex has to search for either Windows-style line endings (\r\n) or Unix-style (\n) # Earlier history.dat files had 3 line breaks, newer versions have 2 # We also rename manufacturer and Publisher, and Description is all data between the bio heading and the first # subheading (e.g. - TECHNICAL - or - CONTRIBUTE -). The romset entry is delimited by the $end. # Japanese (or other foreign titles) have the translated name in brackets underneath. # Newer versions have an age-based reference (e.g. A 24-year-old SNK Neo-Geo MVS Cart) between the $bio # and title line pattern = r"\$bio(\r?\n){2}" \ "(?P<AgeRef>.*?(\r?\n){2})?" \ "(?P<Game>.*?) \(c\) (?P<ReleaseYear>\d{4}) (?P<Publisher>.*?)\.(\r?\n)" \ "(\((?P<Translation>.*?)\))?(\r?\n){1,2}" \ "(?P<Description>.*?)(\r?\n){2,3}" \ "- [A-Z]" rdata = re.search(pattern, data, re.DOTALL | re.MULTILINE | re.UNICODE) if rdata is None: raise ScraperNoSearchResultsFoundException("Unable to find %s in MAME history dat file" %gamename) except Exception as e: print "Error searching for game %s using regex: %s" %(gamename, str(e)) return [] self.resultdata = [rdata.groupdict()] self.resultdata[0]['id'] = self.resultdata[0]['Game'] self.resultdata[0]['SearchKey'] = self.resultdata[0]['Game'] # HACK - This is only used to retain backwards compatibility with existing scraper, where each key value was a # list, even if only one value is in that list for k, v in self.resultdata[0].iteritems(): self.resultdata[0][k] = [v] return self.resultdata
def getControlById(self, controlId): try: control = self.getControl(controlId) except: log.error("Control with id: %s could not be found. Check WindowXML file." % str(controlId)) self.writeMsg(util.localize(32025) % str(controlId)) return None return control
def getThumbFromOnlineSource(self, gamedescription, fileType, fileName, artworkurls): log.info("Get thumb from online source") try: # maybe we got a thumb url from desc parser thumbKey = 'Filetype' + fileType log.info("using key: %s" % thumbKey) thumbUrl = self.resolveParseResult(gamedescription, thumbKey) if thumbUrl == '': return True, artworkurls artworkurls[fileType] = thumbUrl log.info("Get thumb from url: %s" % thumbUrl) rootExtFile = os.path.splitext(fileName) rootExtUrl = os.path.splitext(thumbUrl) files = [] if len(rootExtUrl) == 2 and len(rootExtFile) != 0: fileName = rootExtFile[0] + rootExtUrl[1] gameName = rootExtFile[0] + ".*" files = self.getFilesByWildcard(gameName) del rootExtFile, rootExtUrl if len(files) > 0: log.info("File already exists. Won't download again.") return True, artworkurls log.info("File %s does not exist, starting download" % fileName) # Dialog Status Art Download # Update progress dialog to state we are downloading art try: #32123 = Importing Game #32210 = downloading artwork msg = "%s: %s[CR]%s: %s" % (util.localize(32123), self._guiDict["gameNameKey"], self._guiDict["scraperSiteKey"][thumbKey], util.localize(32210)) self._gui.writeMsg(msg, self._guiDict["fileCountKey"]) except KeyError: log.warn("Unable to retrieve key from GUI dict") try: self.download_thumb(thumbUrl, fileName) except Exception as exc: log.error("Could not create file: '%s'. Error message: '%s'" % (fileName, exc)) # xbmcgui.Dialog().ok(util.localize(32012), util.localize(32011)) return False, artworkurls log.info("Download finished.") except Exception as exc: log.warn("Error in getThumbFromOnlineSource: %s" % exc) return True, artworkurls
def onClick(self, controlID): log.info('onClick') if controlID == CONTROL_BUTTON_EXIT: # Close window button log.info('Close') self.close() elif controlID == CONTROL_BUTTON_CANCEL: # Cancel button self.close() elif controlID == CONTROL_BUTTON_SAVE: # OK log.info('Save') # Store selectedRomCollection if self.selectedRomCollection is not None: # Code to Remove Roms log.info('Removing Roms') self._setDeleteStatus(True) # Code to Remove Collection if self.romDelete == 'RCollection': self._setRCDeleteStatus(True) Logutil.log('Removing Rom Collection', util.LOG_LEVEL_INFO) configWriterRCDel = ConfigXmlWriter(False) RCName = str(self.selectedRomCollection.name) success, message = configWriterRCDel.removeRomCollection( RCName) if success is False: log.error(message) #32019 = Error #32020 = Error Deleting Rom Collection xbmcgui.Dialog().ok(util.localize(32019), util.localize(32020)) log.info('Click Close') self.close() elif self.selectedControlId in (CONTROL_BUTTON_RC_DOWN, CONTROL_BUTTON_RC_UP): # Changing selection in Rom Collection list if self.selectedRomCollection is not None: # Store previous selectedRomCollections state self.romCollections[ self.selectedRomCollection.id] = self.selectedRomCollection # HACK: add a little wait time as XBMC needs some ms to execute the MoveUp/MoveDown actions from the skin xbmc.sleep(util.WAITTIME_UPDATECONTROLS) self.updateControls() elif self.selectedControlId in (CONTROL_BUTTON_DEL_DOWN, CONTROL_BUTTON_DEL_UP): # Changing selection in Delete Option list control = self.getControlById(CONTROL_LIST_DELETEOPTIONS) selectedDeleteOption = str(control.getSelectedItem().getLabel2()) log.info('selectedDeleteOption = {0}'.format(selectedDeleteOption)) self.romDelete = selectedDeleteOption
def insertGame(self, gameName, description, romCollectionId, publisherId, developerId, reviewerId, yearId, players, rating, votes, url, region, media, perspective, controller, originalTitle, alternateTitle, translatedBy, version, isFavorite, launchCount, isUpdate, gameId, allowUpdate): # Check if exists and insert/update as appropriate; move this functionality to the Game object try: if not isUpdate: log.info(u"Game does not exist in database. Insert game: %s" % gameName) Game(self.gdb).insert( (gameName, description, None, None, romCollectionId, publisherId, developerId, reviewerId, yearId, players, rating, votes, url, region, media, perspective, controller, int(isFavorite), int(launchCount), originalTitle, alternateTitle, translatedBy, version)) return self.gdb.cursor.lastrowid else: if allowUpdate: # check if we are allowed to update with null values allowOverwriteWithNullvalues = __addon__.getSetting( util.SETTING_RCB_ALLOWOVERWRITEWITHNULLVALUES).upper( ) == 'TRUE' log.info("allowOverwriteWithNullvalues: {0}".format( allowOverwriteWithNullvalues)) log.info(u"Game does exist in database. Update game: %s" % gameName) Game(self.gdb).update( ('name', 'description', 'romCollectionId', 'publisherId', 'developerId', 'reviewerId', 'yearId', 'maxPlayers', 'rating', 'numVotes', 'url', 'region', 'media', 'perspective', 'controllerType', 'originalTitle', 'alternateTitle', 'translatedBy', 'version', 'isFavorite', 'launchCount'), (gameName, description, romCollectionId, publisherId, developerId, reviewerId, yearId, players, rating, votes, url, region, media, perspective, controller, originalTitle, alternateTitle, translatedBy, version, int(isFavorite), int(launchCount)), gameId, allowOverwriteWithNullvalues) else: log.info( u"Game does exist in database but update is not allowed for current rom collection. game: %s" % gameName) return gameId except Exception, (exc): log.error(u"An error occured while adding game '%s'. Error: %s" % (gameName, exc)) return None
def __delete_temp_files(self, temp_dir): log.info("ArchiveHandler.__delete_temp_files") try: if xbmcvfs.exists(temp_dir + '\\'): log.info("Trying to delete temporary rom files") #can't use xbmcvfs.listdir here as it seems to cache the file list and RetroPlayer won't find newly created files anymore files = os.listdir(temp_dir) for f in files: #RetroPlayer places savestate files next to the roms. Don't delete these files. fname, ext = os.path.splitext(f) if ext not in ('.sav', '.xml', '.png'): xbmcvfs.delete(os.path.join(temp_dir, f)) except Exception as exc: log.error("Error deleting files after launch emu: " + str(exc)) self.gui.writeMsg(util.localize(32036) + ": " + str(exc))
def onClick(self, controlID): log.info('onClick') if controlID == CONTROL_BUTTON_EXIT: # Close window button log.info('Close') self.close() elif controlID == CONTROL_BUTTON_CANCEL: # Cancel button self.close() elif controlID == CONTROL_BUTTON_SAVE: # OK log.info('Save') # Store selectedRomCollection if self.selectedRomCollection is not None: # Code to Remove Roms log.info('Removing Roms') self._setDeleteStatus(True) # Code to Remove Collection if self.romDelete == 'RCollection': self._setRCDeleteStatus(True) Logutil.log('Removing Rom Collection', util.LOG_LEVEL_INFO) configWriterRCDel = ConfigXmlWriter(False) RCName = str(self.selectedRomCollection.name) success, message = configWriterRCDel.removeRomCollection(RCName) if success is False: log.error(message) xbmcgui.Dialog().ok(util.localize(32019), util.localize(32020)) log.info('Click Close') self.close() elif self.selectedControlId in (CONTROL_BUTTON_RC_DOWN, CONTROL_BUTTON_RC_UP): # Changing selection in Rom Collection list if self.selectedRomCollection is not None: # Store previous selectedRomCollections state self.romCollections[self.selectedRomCollection.id] = self.selectedRomCollection # HACK: add a little wait time as XBMC needs some ms to execute the MoveUp/MoveDown actions from the skin xbmc.sleep(util.WAITTIME_UPDATECONTROLS) self.updateControls() elif self.selectedControlId in (CONTROL_BUTTON_DEL_DOWN, CONTROL_BUTTON_DEL_UP): # Changing selection in Delete Option list control = self.getControlById(CONTROL_LIST_DELETEOPTIONS) selectedDeleteOption = str(control.getSelectedItem().getLabel2()) log.info('selectedDeleteOption = {0}'.format(selectedDeleteOption)) self.romDelete = selectedDeleteOption
def __getArchives7z(self, filepath, archiveList): try: import py7zlib except ImportError: xbmcgui.Dialog().ok(util.SCRIPTNAME, util.localize(32039), util.localize(32129)) msg = ("You have tried to launch a .7z file but you are missing required libraries to extract the file. " "You can download the latest RCB version from RCBs project page. It contains all required libraries.") log.error(msg) return None fp = open(str(filepath), 'rb') archive = py7zlib.Archive7z(fp) archivesDecompressed = [(name, archive.getmember(name).read())for name in archiveList] fp.close() return archivesDecompressed
def __getNames7z(self, filepath): try: import py7zlib except ImportError as e: xbmcgui.Dialog().ok(util.SCRIPTNAME, util.localize(32039), util.localize(32129)) msg = ("You have tried to launch a .7z file but you are missing required libraries to extract the file. " "You can download the latest RCB version from RCBs project page. It contains all required libraries.") log.error(msg) log.error("Error: " + str(e)) return None fp = open(str(filepath), 'rb') archive = py7zlib.Archive7z(fp) names = archive.getnames() fp.close() return names
def launch_game(self, gameid, listitem): log.info("AbstractLauncher.launch_game()") try: launcher = self.get_launcher_by_gameid(gameid) if (launcher is None): log.error("Launcher could not be created.") return precmd, postcmd, cmd, roms = launcher.prepare( self.romCollection, self.gameRow) launcher.pre_launch(self.romCollection, self.gameRow, precmd) launcher.launch(self.romCollection, self.gameRow, cmd, roms, listitem) launcher.post_launch(self.romCollection, self.gameRow, postcmd) except Exception as exc: error = "%s: %s" % (util.localize(32035), str(exc)) traceback.print_exc() self.gui.writeMsg(error)
def __getArchives7z(self, filepath, archiveList): try: import py7zlib except ImportError: # 32039 = Error launching .7z file. # 32129 = Please check kodi.log for details. message = "%s[CR]%s" % (util.localize(32039), util.localize(32129)) xbmcgui.Dialog().ok(util.SCRIPTNAME, message) msg = ("You have tried to launch a .7z file but you are missing required libraries to extract the file. " "You can download the latest RCB version from RCBs project page. It contains all required libraries.") log.error(msg) return None fp = open(str(filepath), 'rb') archive = py7zlib.Archive7z(fp) archivesDecompressed = [(name, archive.getmember(name).read()) for name in archiveList] fp.close() return archivesDecompressed
def __getNames7z(self, filepath): try: import py7zlib except ImportError as e: xbmcgui.Dialog().ok(util.SCRIPTNAME, util.localize(32039), util.localize(32129)) msg = ( "You have tried to launch a .7z file but you are missing required libraries to extract the file. " "You can download the latest RCB version from RCBs project page. It contains all required libraries." ) log.error(msg) log.error("Error: " + str(e)) return None fp = open(str(filepath), 'rb') archive = py7zlib.Archive7z(fp) names = archive.getnames() fp.close() return names
def search(self, gamename, platform=None): #use description to search for the game name as the name attribute also contains the region #FIXME TODO #currently not working with MAME xml files as the rom files don't use the friendly game name pattern = "\<description\>(.*%s.*)\</description\>" % GameNameUtil().prepare_gamename_for_searchrequest(gamename) results = [] try: with io.open(self._get_xml_path(), 'r', encoding="utf-8") as xmlfile: for line in xmlfile: result = re.search(pattern, line) if result: gamename = result.groups()[0] results.append({'id': gamename, 'title': gamename, 'SearchKey': [gamename]}) except Exception as e: log.error(e) raise return results
def get_scraper_by_name(self, sname): """Given a scraper name, returns the scraper class Args: sname: Name of the scraper, e.g. thegamesdb.net or MAME Raises: ConfigScraperSiteDoesNotExistException: No scraper matches the name """ try: target = self.scrapers[sname] except KeyError: raise ConfigScraperSiteDoesNotExistException( "Unsupported scraper: {0}".format(sname)) #check if we already have instantiated this scraper instance = None try: instance = self._instantiated_scrapers[sname] log.debug( "Using previously instantiated scraper class {0} - {1}".format( sname, target)) except KeyError: pass if not instance: log.debug("Instantiating scraper class {0} - {1}".format( sname, target)) try: module = __import__(target.lower()) class_ = getattr(module, target) instance = class_() self._instantiated_scrapers[sname] = instance except ImportError: log.error("Unable to find scraper {0}".format(sname)) raise return instance
def insertGame(self, gameName, description, romCollectionId, publisherId, developerId, reviewerId, yearId, players, rating, votes, url, region, media, perspective, controller, originalTitle, alternateTitle, translatedBy, version, isFavorite, launchCount, isUpdate, gameId, allowUpdate): # Check if exists and insert/update as appropriate; move this functionality to the Game object try: if not isUpdate: log.info(u"Game does not exist in database. Insert game: %s" % gameName) Game(self.gdb).insert( (gameName, description, None, None, romCollectionId, publisherId, developerId, reviewerId, yearId, players, rating, votes, url, region, media, perspective, controller, int(isFavorite), int(launchCount), originalTitle, alternateTitle, translatedBy, version)) return self.gdb.cursor.lastrowid else: if allowUpdate: # check if we are allowed to update with null values allowOverwriteWithNullvalues = __addon__.getSetting( util.SETTING_RCB_ALLOWOVERWRITEWITHNULLVALUES).upper() == 'TRUE' log.info("allowOverwriteWithNullvalues: {0}".format(allowOverwriteWithNullvalues)) log.info(u"Game does exist in database. Update game: %s" % gameName) Game(self.gdb).update(('name', 'description', 'romCollectionId', 'publisherId', 'developerId', 'reviewerId', 'yearId', 'maxPlayers', 'rating', 'numVotes', 'url', 'region', 'media', 'perspective', 'controllerType', 'originalTitle', 'alternateTitle', 'translatedBy', 'version', 'isFavorite', 'launchCount'), (gameName, description, romCollectionId, publisherId, developerId, reviewerId, yearId, players, rating, votes, url, region, media, perspective, controller, originalTitle, alternateTitle, translatedBy, version, int(isFavorite), int(launchCount)), gameId, allowOverwriteWithNullvalues) else: log.info( u"Game does exist in database but update is not allowed for current rom collection. game: %s" % gameName) return gameId except Exception, (exc): log.error(u"An error occured while adding game '%s'. Error: %s" % (gameName, exc)) return None
def __getNames7z(self, filepath): try: import py7zlib except ImportError as e: # 32039 = Error launching .7z file. # 32129 = Please check kodi.log for details. message = "%s[CR]%s" % (util.localize(32039), util.localize(32129)) xbmcgui.Dialog().ok(util.SCRIPTNAME, message) msg = ( "You have tried to launch a .7z file but you are missing required libraries to extract the file. " "You can download the latest RCB version from RCBs project page. It contains all required libraries." ) log.error(msg) log.error("Error: " + str(e)) return None fp = open(str(filepath), 'rb') archive = py7zlib.Archive7z(fp) names = archive.getnames() log.debug('names = {0}'.format(names)) fp.close() return names
def __handle_indexed_roms(self, disk_prefix, names, emu_params, rom, temp_dir): log.info("ArchiveHandler.__handle_indexed_roms") if disk_prefix == '': return None match = re.search(disk_prefix.lower(), str(names).lower()) if '%I%' not in emu_params or not match: return None log.info("Extracting %d files" % len(names)) try: extracted_files = self.__extract_files(rom, names, temp_dir) except Exception as exc: log.error("Error handling compressed file: " + str(exc)) return None if extracted_files is None: log.warning("Error handling compressed file") return None return extracted_files
def get_launcher_by_gameid(self, gameid): """Returns the launcher class based on romCollection.useBuiltinEmulator Args: gameid: the id of the game we want to launch """ log.info("AbstractLauncher.get_launcher_by_gameid()") self.gameRow = GameView(self.gdb).getObjectById(gameid) if self.gameRow is None: log.error("Game with id %s could not be found in database" % gameid) return None try: self.romCollection = self.config.romCollections[str( self.gameRow[GameView.COL_romCollectionId])] except KeyError: log.error("Cannot get rom collection with id: " + str(self.gameRow[GameView.COL_romCollectionId])) self.gui.writeMsg(util.localize(32034)) return self.gui.writeMsg( util.localize(32163) + " " + self.gameRow[DataBaseObject.COL_NAME]) launchername = self.CMD_LAUNCHER if (self.romCollection.useBuiltinEmulator): launchername = self.RETROPLAYER_LAUNCHER #check if we already have instantiated this launcher instance = None try: instance = self._instantiated_launcher[launchername] log.debug( "Using previously instantiated launcher class {0}".format( launchername)) except KeyError: pass if not instance: log.debug("Instantiating launcher class {0}".format(launchername)) try: module = __import__(launchername.lower()) class_ = getattr(module, launchername) instance = class_() self._instantiated_launcher[launchername] = instance except ImportError: log.error("Unable to find launcher {0}".format(launchername)) raise instance.gdb = self.gdb instance.gui = self.gui instance.config = self.config return instance
def extract_archive(self, rom_collection, rom, emu_params): log.info("ArchiveHandler.extract_archive") temp_dir = self.__get_temp_dir_path(rom_collection.name) self.__delete_temp_files(temp_dir) try: files_in_archive = self.__get_rom_names_from_archive(rom) except Exception as exc: log.error("Error handling compressed file: " + str(exc)) return [] if files_in_archive is None or len(files_in_archive) == 0: log.error("Error handling compressed file") return [] roms = self.__handle_indexed_roms(rom_collection.diskPrefix, files_in_archive, emu_params, rom, temp_dir) if roms: return roms try: # Extract all files to %TMP% extracted_files = self.__extract_files(rom, files_in_archive, temp_dir) except Exception as exc: log.error("Error handling compressed file: " + str(exc)) return [] if extracted_files is None: log.warn("Error handling compressed file") return [] chosen_rom = 0 if len(files_in_archive) > 1: log.info("The Archive has %d files" % len(files_in_archive)) chosen_rom = xbmcgui.Dialog().select('Choose a ROM', files_in_archive) # Point file name to the chosen file and continue as usual roms = [os.path.join(temp_dir, files_in_archive[chosen_rom])] log.debug("roms decompressed = " + str(roms)) if len(roms) == 0: return [] return roms
def useSingleScrapers(self, romCollection, romFile, gamenameFromFile, progDialogRCHeader, fileCount): """Scrape site for game metadata Args: romCollection: gamenameFromFile: progDialogRCHeader: fileCount: Returns: dict for the game result: {'SearchKey': ['Chrono Trigger'], 'Publisher': ['Squaresoft'], 'Description': ["The millennium. A portal is opened. The chain of time is broken...], 'Players': ['1'], 'Platform': ['Super Nintendo (SNES)'], 'Game': ['Chrono Trigger'], 'Filetypeboxfront': ['http://thegamesdb.net/banners/boxart/original/front/1255-1.jpg'], 'Filetypeboxback': ['http://thegamesdb.net/banners/boxart/original/back/1255-1.jpg'], 'Filetypescreenshot': ['http://thegamesdb.net/banners/screenshots/1255-1.jpg', 'http://thegamesdb.net/banners/screenshots/1255-2.jpg', 'http://thegamesdb.net/banners/screenshots/1255-3.jpg', 'http://thegamesdb.net/banners/screenshots/1255-4.jpg', 'http://thegamesdb.net/banners/screenshots/1255-5.jpg'], 'Filetypefanart': ['http://thegamesdb.net/banners/fanart/original/1255-1.jpg', 'http://thegamesdb.net/banners/fanart/original/1255-10.jpg', 'http://thegamesdb.net/banners/fanart/original/1255-11.jpg', 'http://thegamesdb.net/banners/fanart/original/1255-2.jpg', 'http://thegamesdb.net/banners/fanart/original/1255-3.jpg', 'http://thegamesdb.net/banners/fanart/original/1255-4.jpg', 'http://thegamesdb.net/banners/fanart/original/1255-5.jpg', 'http://thegamesdb.net/banners/fanart/original/1255-6.jpg', 'http://thegamesdb.net/banners/fanart/original/1255-7.jpg', 'http://thegamesdb.net/banners/fanart/original/1255-8.jpg', 'http://thegamesdb.net/banners/fanart/original/1255-9.jpg'], 'Genre': ['Role-Playing'], 'Developer': ['Squaresoft']} dict for artwork urls: {'Filetypefanart': 'thegamesdb.net', 'Filetypeboxback': 'thegamesdb.net', 'Filetypescreenshot': 'thegamesdb.net', 'Filetypeboxfront': 'thegamesdb.net'} Note - this only contains entries for artwork that was found (i.e. is not empty list) """ gameresult = {} artScrapers = {} scraperSite = romCollection.scraperSites[0] try: #first check if a local nfo file is available nfoscraper = NFO_Scraper() nfofile = nfoscraper.get_nfo_path(gamenameFromFile, romCollection.name, romFile) if xbmcvfs.exists(nfofile) and __addon__.getSetting(util.SETTING_RCB_PREFERLOCALNFO).upper() == 'TRUE': log.info("Found local nfo file. Using this to scrape info.") newscraper = nfoscraper else: newscraper = AbstractScraper().get_scraper_by_name(scraperSite.name) #set path to desc file (only useful for offline scrapers) newscraper.path = scraperSite.path results = newscraper.search(gamenameFromFile, romCollection.name) log.debug(u"Searching for %s - found %s results: %s" % (gamenameFromFile, len(results), results)) except ScraperExceededAPIQuoteException as ke: # API key is invalid - we need to stop scraping log.error("Scraper exceeded API key, stopping scraping") raise except Exception as e: log.error("Error searching for %s using scraper %s - %s %s" % ( gamenameFromFile, scraperSite.name, type(e), e)) return gameresult, artScrapers if results == []: log.warn("No search results found for %s using scraper %s" % (gamenameFromFile, scraperSite.name)) return gameresult, artScrapers matched = Matcher().getBestResults(results, gamenameFromFile) if matched is None: log.error("No matches found for %s, skipping" % gamenameFromFile) return gameresult, artScrapers log.debug("After matching: %s" % matched) try: retrievedresult = newscraper.retrieve(matched['id'], romCollection.name) log.debug(u"Retrieving %s - found %s" % (matched['id'], retrievedresult)) except Exception as e: # FIXME TODO Catch exceptions specifically log.error("Error retrieving %s - %s %s" % (matched['id'], type(e), e)) return gameresult, artScrapers # Update the gameresult with any new fields gameresult = self.addNewElements(gameresult, retrievedresult) self._gui.writeMsg(progDialogRCHeader, util.localize(32123) + ": " + gamenameFromFile, scraperSite.name + " - " + util.localize(32131), fileCount) # Find Filetypes and Scrapers for Art Download # FIXME TODO The following is kept to keep artwork downloading working as it currently is. We already have # the URLs and so could handle/download here, rather than deferring if len(gameresult) > 0: for path in romCollection.mediaPaths: thumbKey = 'Filetype' + path.fileType.name if len(self.resolveParseResult(gameresult, thumbKey)) > 0: if (thumbKey in artScrapers) == 0: artScrapers[thumbKey] = scraperSite.name log.debug(u"After scraping, result = %s, artscrapers = %s" % (gameresult, artScrapers)) return gameresult, artScrapers
def updateDB(self, gdb, gui, romCollections, isRescrape): self.gdb = gdb self._gui = gui log.info("Start Update DB") log.info("Iterating Rom Collections") rccount = 1 # always do full reimports when in rescrape-mode enableFullReimport = isRescrape or __addon__.getSetting(util.SETTING_RCB_ENABLEFULLREIMPORT).upper() == 'TRUE' log.info("enableFullReimport: {0}".format(enableFullReimport)) continueUpdate = True # Added variable to allow user to continue on errors ignoreErrors = False for romCollection in romCollections.values(): # timestamp1 = time.clock() # check if import was canceled if not continueUpdate: log.info("Game import canceled") break # prepare Header for ProgressDialog progDialogRCHeader = util.localize(32122) + " (%i / %i): %s" % ( rccount, len(romCollections), romCollection.name) rccount += 1 log.info("current Rom Collection: {0}".format(romCollection.name)) # Read settings for current Rom Collection log.info("ignoreOnScan: {0}".format(romCollection.ignoreOnScan)) if romCollection.ignoreOnScan: log.info("current Rom Collection will be ignored.") # self.scrapeResultsFile.write('Rom Collection will be ignored.\n') continue log.info("update is allowed for current rom collection: {0}".format(romCollection.allowUpdate)) log.info("max folder depth: {0}".format(romCollection.maxFolderDepth)) files = self.getRomFilesByRomCollection(romCollection, enableFullReimport) if len(files) == 0: log.info(u"No files found for rom collection {0}, skipping".format(romCollection.name)) continue log.info(u"Found {0} game files for rom collection {1}".format(len(files), romCollection.name)) # itemCount is used for percentage in ProgressDialogGUI self._gui.itemCount = len(files) + 1 successfulFiles = 0 lastgamename = '' lastGameId = None for fileidx, filename in enumerate(files): try: #Give kodi a chance to interrupt the process #HACK: we should use monitor.abortRequested() or monitor.waitForAbort() #but for some reason only xbmc.abortRequested returns True if monitor.abortRequested() or xbmc.abortRequested: log.info("Kodi requests abort. Cancel Update.") break log.info("Scraping for %s" % filename) gamenameFromFile = romCollection.getGamenameFromFilename(filename) # check if we are handling one of the additional disks of a multi rom game isMultiRomGame = (gamenameFromFile == lastgamename) lastgamename = gamenameFromFile if isMultiRomGame: # Add this entry as a file under the game ID and move on log.info("Detected %s as a multirom game (previous game was %s)" % (filename, lastgamename)) if lastGameId is None: log.error("Game detected as multi rom game, but lastGameId is None.") continue fileType = FileType() fileType.id, fileType.name, fileType.parent = 0, "rcb_rom", "game" self.insertFile(filename, lastGameId, fileType, None, None, None) self.gdb.commit() del fileType continue log.info("Start scraping info for game: %s" % gamenameFromFile) continueUpdate = self._gui.writeMsg(progDialogRCHeader, util.localize(32123) + ": " + gamenameFromFile, "", fileidx + 1) if not continueUpdate: log.info("Game import canceled by user") break # check if this file already exists in DB continueUpdate, isUpdate, gameId = self.checkRomfileAlreadyExists(filename, enableFullReimport) if not continueUpdate: continue results = {} foldername = self.getFoldernameFromRomFilename(filename) artScrapers = {} results, artScrapers = self.useSingleScrapers(romCollection, filename, gamenameFromFile, progDialogRCHeader, fileidx + 1) if len(results) == 0: # lastgamename = "" results = None # Variables to process Art Download Info self._guiDict.update({'dialogHeaderKey': progDialogRCHeader, 'gameNameKey': gamenameFromFile, 'scraperSiteKey': artScrapers, 'fileCountKey': (fileidx + 1)}) del artScrapers #Give kodi a chance to interrupt the process #HACK: we should use monitor.abortRequested() or monitor.waitForAbort() #but for some reason only xbmc.abortRequested returns True if monitor.abortRequested() or xbmc.abortRequested: log.info("Kodi requests abort. Cancel Update.") break # Add 'gui' and 'dialogDict' parameters to function lastGameId = self.insertGameFromDesc(results, gamenameFromFile, romCollection, [filename], foldername, isUpdate, gameId) del results, foldername if lastGameId is not None: log.info("Successfully added %s" % gamenameFromFile) successfulFiles += 1 # Check if all first 10 games have errors - Modified to allow user to continue on errors if fileidx > 9 and successfulFiles == 0 and not ignoreErrors: options = [util.localize(32124), util.localize(32125), util.localize(32126)] answer = xbmcgui.Dialog().select(util.localize(32127), options) if answer == 1: # Continue and ignore errors ignoreErrors = True elif answer == 2: # Cancel xbmcgui.Dialog().ok(util.SCRIPTNAME, util.localize(32128), util.localize(32129)) continueUpdate = False break except ScraperExceededAPIQuoteException as ke: xbmcgui.Dialog().ok(util.localize(32128), "The API key for a scraper was exceeded") # Abort the scraping entirely break except Exception as exc: log.warn(u"An error occurred while adding game %s: %s" % (gamenameFromFile, exc)) self.missingDescFile.add_entry(gamenameFromFile) continue # timestamp2 = time.clock() # diff = (timestamp2 - timestamp1) * 1000 # print "load %i games in %d ms" % (self.getListSize(), diff) self._gui.writeMsg("Done.", "", "", self._gui.itemCount) log.info("Update finished") return True, ''
def getThumbFromOnlineSource(self, gamedescription, fileType, fileName, artworkurls): log.info("Get thumb from online source") try: # maybe we got a thumb url from desc parser thumbKey = 'Filetype' + fileType log.info("using key: %s" % thumbKey) thumbUrl = self.resolveParseResult(gamedescription, thumbKey) if thumbUrl == '': return True, artworkurls artworkurls[fileType] = thumbUrl log.info("Get thumb from url: %s" % thumbUrl) rootExtFile = os.path.splitext(fileName) rootExtUrl = os.path.splitext(thumbUrl) files = [] if len(rootExtUrl) == 2 and len(rootExtFile) != 0: fileName = rootExtFile[0] + rootExtUrl[1] gameName = rootExtFile[0] + ".*" files = self.getFilesByWildcard(gameName) del rootExtFile, rootExtUrl if len(files) > 0: log.info("File already exists. Won't download again.") return True, artworkurls # Create folder if it doesn't already exist dirname = os.path.join( os.path.dirname(fileName), '') # Add the trailing slash that xbmcvfs.exists expects log.debug("Checking for artwork directory %s" % dirname) if KodiVersions.getKodiVersion() >= KodiVersions.KRYPTON: exists = xbmcvfs.exists(dirname) else: exists = os.path.exists(dirname) if not exists: log.info("Artwork directory %s doesn't exist, creating it" % dirname) success = xbmcvfs.mkdirs(dirname) log.info("Directory successfully created: %s" % success) if not success: #HACK: check if directory was really not created. directoryExists = xbmcvfs.exists(dirname) log.info("Directory exists: %s" % directoryExists) if not directoryExists: log.error("Could not create artwork directory: '%s'" % dirname) xbmcgui.Dialog().ok(util.localize(32010), util.localize(32011)) del dirname return False, artworkurls log.info("File %s does not exist, starting download" % fileName) # Dialog Status Art Download # Update progress dialog to state we are downloading art try: msg = "%s: %s" % (util.localize(32123), self._guiDict["gameNameKey"]) submsg = "%s - downloading art" % self._guiDict[ "scraperSiteKey"][thumbKey] self._gui.writeMsg(self._guiDict["dialogHeaderKey"], msg, submsg, self._guiDict["fileCountKey"]) except KeyError: log.warn("Unable to retrieve key from GUI dict") try: self.download_thumb(thumbUrl, fileName) except Exception, (exc): log.error("Could not create file: '%s'. Error message: '%s'" % (fileName, exc)) # xbmcgui.Dialog().ok(util.localize(32012), util.localize(32011)) return False, artworkurls Logutil.log("Download finished.", util.LOG_LEVEL_INFO)
def search(self, gamename, platform=None): """ Ignore platform """ # Newer versions support multi systems, $info=XXX indicates an arcade ROM startmarker = "$info=%s," % gamename gamedata = False data = "" historyfile_path = self._get_history_path() try: fh = xbmcvfs.File(historyfile_path) historyfile = fh.read() historyfile = util.convertToUnicodeString(historyfile) fh.close() for line in historyfile.splitlines(): if line.startswith(startmarker): gamedata = True if gamedata: data += line + os.linesep if line.startswith("$end"): gamedata = False except Exception as e: log.error(e) raise try: # Note the regex has to search for either Windows-style line endings (\r\n) or Unix-style (\n) # Earlier history.dat files had 3 line breaks, newer versions have 2 # We also rename manufacturer and Publisher, and Description is all data between the bio heading and the first # subheading (e.g. - TECHNICAL - or - CONTRIBUTE -). The romset entry is delimited by the $end. # Japanese (or other foreign titles) have the translated name in brackets underneath. # Newer versions have an age-based reference (e.g. A 24-year-old SNK Neo-Geo MVS Cart) between the $bio # and title line pattern = r"\$bio(\r?\n){2}" \ "(?P<AgeRef>.*?(\r?\n){2})?" \ "(?P<Game>.*?) \(c\) (?P<ReleaseYear>\d{4}) (?P<Publisher>.*?)\.(\r?\n)" \ "(\((?P<Translation>.*?)\))?(\r?\n){1,2}" \ "(?P<Description>.*?)(\r?\n){2,3}" \ "- [A-Z]" rdata = re.search(pattern, data, re.DOTALL | re.MULTILINE | re.UNICODE) if rdata is None: raise ScraperNoSearchResultsFoundException("Unable to find %s in MAME history dat file" % gamename) except Exception as e: print ("Error searching for game %s using regex: %s" % (gamename, str(e))) return [] self.resultdata = [rdata.groupdict()] self.resultdata[0]['id'] = self.resultdata[0]['Game'] #don't use a search key as we should always have 1 result that perfectly fits #search key is used in matcher to compare the rom name with the game name and this will usually not match #self.resultdata[0]['SearchKey'] = self.resultdata[0]['Game'] # HACK - This is only used to retain backwards compatibility with existing scraper, where each key value was a # list, even if only one value is in that list for k, v in self.resultdata[0].items(): self.resultdata[0][k] = [v] return self.resultdata
fname, ext = os.path.splitext(f) if(ext not in ('.sav', '.xml', '.png')): xbmcvfs.delete(os.path.join(tempDir, f)) except Exception, (exc): log.error("Error deleting files after launch emu: " + str(exc)) gui.writeMsg(util.localize(32036) + ": " + str(exc)) roms = [] log.info("Treating file as a compressed archive") compressed = True try: names = self.__getNames(filext, rom) except Exception, (exc): log.error("Error handling compressed file: " + str(exc)) return [] if names is None: log.error("Error handling compressed file") return [] chosenROM = -1 # check if we should handle multiple roms match = False if self.romCollection.diskPrefix != '': match = re.search(self.romCollection.diskPrefix.lower(), str(names).lower()) if '%I%' in emuParams and match: log.info("Loading %d archives" % len(names))
def __handleCompressedFile(self, gui, filext, rom, emuParams): log.info("__handleCompressedFile") # Note: Trying to delete temporary files (from zip or 7z extraction) from last run # Do this before launching a new game. Otherwise game could be deleted before launch tempDir = os.path.join(util.getTempDir(), 'extracted', self.romCollection.name) # check if folder exists if not xbmcvfs.exists(tempDir + '\\'): log.info("Create temporary folder: " + tempDir) xbmcvfs.mkdir(tempDir) try: if xbmcvfs.exists(tempDir + '\\'): log.info("Trying to delete temporary rom files") #can't use xbmcvfs.listdir here as it seems to cache the file list and RetroPlayer won't find newly created files anymore files = os.listdir(tempDir) for f in files: #RetroPlayer places savestate files next to the roms. Don't delete these files. fname, ext = os.path.splitext(f) if ext not in ('.sav', '.xml', '.png'): xbmcvfs.delete(os.path.join(tempDir, f)) except Exception as exc: log.error("Error deleting files after launch emu: " + str(exc)) gui.writeMsg(util.localize(32036) + ": " + str(exc)) roms = [] log.info("Treating file as a compressed archive") try: names = self.__getNames(filext, rom) except Exception as exc: log.error("Error handling compressed file: " + str(exc)) return [] if names is None: log.error("Error handling compressed file") return [] chosenROM = -1 # check if we should handle multiple roms match = False if self.romCollection.diskPrefix != '': match = re.search(self.romCollection.diskPrefix.lower(), str(names).lower()) if '%I%' in emuParams and match: log.info("Loading %d archives" % len(names)) try: archives = self.__getArchives(filext, rom, names) except Exception as exc: log.error("Error handling compressed file: " + str(exc)) return [] if archives is None: log.warning("Error handling compressed file") return [] for archive in archives: newPath = os.path.join(tempDir, archive[0]) fp = open(newPath, 'wb') fp.write(archive[1]) fp.close() roms.append(newPath) elif len(names) > 1: log.info("The Archive has %d files" % len(names)) chosenROM = xbmcgui.Dialog().select('Choose a ROM', names) elif len(names) == 1: log.info("Archive only has one file inside; picking that one") chosenROM = 0 else: log.error("Archive had no files inside!") return [] if chosenROM != -1: # Extract all files to %TMP% archives = self.__getArchives(filext, rom, names) if archives is None: log.warn("Error handling compressed file") return [] for archive in archives: newPath = os.path.join(tempDir, archive[0]) log.info("Putting extracted file in %s" % newPath) fo = open(str(newPath), 'wb') fo.write(archive[1]) fo.close() # Point file name to the chosen file and continue as usual roms = [os.path.join(tempDir, names[chosenROM])] return roms
def launchEmu(self, gdb, gui, gameId, config, listitem): log.info("Begin launcher.launchEmu") gameRow = Game(gdb).getObjectById(gameId) if gameRow is None: log.error("Game with id %s could not be found in database" % gameId) return try: self.romCollection = config.romCollections[str( gameRow[util.GAME_romCollectionId])] except KeyError: log.error("Cannot get rom collection with id: " + str(gameRow[util.GAME_romCollectionId])) gui.writeMsg(util.localize(32034)) return gui.writeMsg(util.localize(32163) + " " + gameRow[util.ROW_NAME]) # Remember viewstate gui.saveViewState(False) cmd = "" precmd = "" postcmd = "" filenameRows = File(gdb).getRomsByGameId(gameRow[util.ROW_ID]) log.info("files for current game: " + str(filenameRows)) cmd, precmd, postcmd, roms = self.__buildCmd(gui, filenameRows, gameRow, False) if not self.romCollection.useBuiltinEmulator: if cmd == '': log.info("No cmd created. Game will not be launched.") return if precmd.strip() == '' or precmd.strip() == 'call': log.info("No precmd created.") if postcmd.strip() == '' or postcmd.strip() == 'call': log.info("No postcmd created.") # solo mode if self.romCollection.useEmuSolo: self.__copyLauncherScriptsToUserdata() # communicate with service via settings __addon__.setSetting(util.SETTING_RCB_LAUNCHONSTARTUP, 'true') # invoke script file that kills xbmc before launching the emulator basePath = os.path.join(util.getAddonDataPath(), 'scriptfiles') if self.env == "win32": if __addon__.getSetting(util.SETTING_RCB_USEVBINSOLOMODE ).lower() == 'true': # There is a problem with quotes passed as argument to windows command shell. This only works with "call" # use vb script to restart xbmc cmd = 'call \"' + os.path.join( basePath, 'applaunch-vbs.bat') + '\" ' + cmd else: # There is a problem with quotes passed as argument to windows command shell. This only works with "call" cmd = 'call \"' + os.path.join( basePath, 'applaunch.bat') + '\" ' + cmd else: cmd = os.path.join(basePath, 'applaunch.sh ') + cmd else: # use call to support paths with whitespaces if self.env == "win32": cmd = 'call ' + cmd # update LaunchCount launchCount = gameRow[util.GAME_launchCount] Game(gdb).update(('launchCount', ), (launchCount + 1, ), gameRow[util.ROW_ID], True) gdb.commit() log.info("cmd: " + cmd) log.info("precmd: " + precmd) log.info("postcmd: " + postcmd) try: self.__launchNonXbox(cmd, gameRow, precmd, postcmd, roms, gui, listitem) gui.writeMsg("") except Exception, (exc): log.error("Error while launching emu: " + str(exc)) gui.writeMsg(util.localize(32035) + ": " + str(exc))
fname, ext = os.path.splitext(f) if (ext not in ('.sav', '.xml', '.png')): xbmcvfs.delete(os.path.join(tempDir, f)) except Exception, (exc): log.error("Error deleting files after launch emu: " + str(exc)) gui.writeMsg(util.localize(32036) + ": " + str(exc)) roms = [] log.info("Treating file as a compressed archive") compressed = True try: names = self.__getNames(filext, rom) except Exception, (exc): log.error("Error handling compressed file: " + str(exc)) return [] if names is None: log.error("Error handling compressed file") return [] chosenROM = -1 # check if we should handle multiple roms match = False if self.romCollection.diskPrefix != '': match = re.search(self.romCollection.diskPrefix.lower(), str(names).lower()) if '%I%' in emuParams and match:
def launchEmu(self, gdb, gui, gameId, config, listitem): log.info("Begin launcher.launchEmu") gameRow = Game(gdb).getObjectById(gameId) if gameRow is None: log.error("Game with id %s could not be found in database" % gameId) return try: self.romCollection = config.romCollections[str(gameRow[util.GAME_romCollectionId])] except KeyError: log.error("Cannot get rom collection with id: " + str(gameRow[util.GAME_romCollectionId])) gui.writeMsg(util.localize(32034)) return gui.writeMsg(util.localize(32163) + " " + gameRow[util.ROW_NAME]) # Remember viewstate gui.saveViewState(False) cmd = "" precmd = "" postcmd = "" filenameRows = File(gdb).getRomsByGameId(gameRow[util.ROW_ID]) log.info("files for current game: " + str(filenameRows)) cmd, precmd, postcmd, roms = self.__buildCmd(gui, filenameRows, gameRow, False) if not self.romCollection.useBuiltinEmulator: if cmd == '': log.info("No cmd created. Game will not be launched.") return if precmd.strip() == '' or precmd.strip() == 'call': log.info("No precmd created.") if postcmd.strip() == '' or postcmd.strip() == 'call': log.info("No postcmd created.") # solo mode if self.romCollection.useEmuSolo: self.__copyLauncherScriptsToUserdata() # communicate with service via settings __addon__.setSetting(util.SETTING_RCB_LAUNCHONSTARTUP, 'true') # invoke script file that kills xbmc before launching the emulator basePath = os.path.join(util.getAddonDataPath(), 'scriptfiles') if self.env == "win32": if __addon__.getSetting(util.SETTING_RCB_USEVBINSOLOMODE).lower() == 'true': # There is a problem with quotes passed as argument to windows command shell. This only works with "call" # use vb script to restart xbmc cmd = 'call \"' + os.path.join(basePath, 'applaunch-vbs.bat') + '\" ' + cmd else: # There is a problem with quotes passed as argument to windows command shell. This only works with "call" cmd = 'call \"' + os.path.join(basePath, 'applaunch.bat') + '\" ' + cmd else: cmd = os.path.join(basePath, 'applaunch.sh ') + cmd else: # use call to support paths with whitespaces if self.env == "win32": cmd = 'call ' + cmd # update LaunchCount launchCount = gameRow[util.GAME_launchCount] Game(gdb).update(('launchCount',), (launchCount + 1,), gameRow[util.ROW_ID], True) gdb.commit() log.info("cmd: " + cmd) log.info("precmd: " + precmd) log.info("postcmd: " + postcmd) try: self.__launchNonXbox(cmd, gameRow, precmd, postcmd, roms, gui, listitem) gui.writeMsg("") except Exception, (exc): log.error("Error while launching emu: " + str(exc)) gui.writeMsg(util.localize(32035) + ": " + str(exc))
def useSingleScrapers(self, romCollection, romFile, gamenameFromFile, progDialogRCHeader, fileCount): """Scrape site for game metadata Args: romCollection: romFile: gamenameFromFile: progDialogRCHeader: fileCount: Returns: dict for the game result: {'SearchKey': ['Chrono Trigger'], 'Publisher': ['Squaresoft'], 'Description': ["The millennium. A portal is opened. The chain of time is broken...], 'Players': ['1'], 'Platform': ['Super Nintendo (SNES)'], 'Game': ['Chrono Trigger'], 'Filetypeboxfront': ['http://thegamesdb.net/banners/boxart/original/front/1255-1.jpg'], 'Filetypeboxback': ['http://thegamesdb.net/banners/boxart/original/back/1255-1.jpg'], 'Filetypescreenshot': ['http://thegamesdb.net/banners/screenshots/1255-1.jpg', 'http://thegamesdb.net/banners/screenshots/1255-2.jpg', 'http://thegamesdb.net/banners/screenshots/1255-3.jpg', 'http://thegamesdb.net/banners/screenshots/1255-4.jpg', 'http://thegamesdb.net/banners/screenshots/1255-5.jpg'], 'Filetypefanart': ['http://thegamesdb.net/banners/fanart/original/1255-1.jpg', 'http://thegamesdb.net/banners/fanart/original/1255-10.jpg', 'http://thegamesdb.net/banners/fanart/original/1255-11.jpg', 'http://thegamesdb.net/banners/fanart/original/1255-2.jpg', 'http://thegamesdb.net/banners/fanart/original/1255-3.jpg', 'http://thegamesdb.net/banners/fanart/original/1255-4.jpg', 'http://thegamesdb.net/banners/fanart/original/1255-5.jpg', 'http://thegamesdb.net/banners/fanart/original/1255-6.jpg', 'http://thegamesdb.net/banners/fanart/original/1255-7.jpg', 'http://thegamesdb.net/banners/fanart/original/1255-8.jpg', 'http://thegamesdb.net/banners/fanart/original/1255-9.jpg'], 'Genre': ['Role-Playing'], 'Developer': ['Squaresoft']} dict for artwork urls: {'Filetypefanart': 'thegamesdb.net', 'Filetypeboxback': 'thegamesdb.net', 'Filetypescreenshot': 'thegamesdb.net', 'Filetypeboxfront': 'thegamesdb.net'} Note - this only contains entries for artwork that was found (i.e. is not empty list) """ gameresult = {} artScrapers = {} scraperSite = None #search for default scraper if there are more than one for site in romCollection.scraperSites: if site.default: scraperSite = site break #if no default site was found, just use the first one if not scraperSite: if len(romCollection.scraperSites) >= 1: scraperSite = romCollection.scraperSites[0] try: #first check if a local nfo file is available nfoscraper = NFO_Scraper() nfofile = nfoscraper.get_nfo_path(gamenameFromFile, romCollection.name, romFile) if xbmcvfs.exists(nfofile) and __addon__.getSetting(util.SETTING_RCB_PREFERLOCALNFO).upper() == 'TRUE': log.info("Found local nfo file. Using this to scrape info.") newscraper = nfoscraper else: newscraper = AbstractScraper().get_scraper_by_name(scraperSite.name) #set path to desc file (only useful for offline scrapers) newscraper.path = scraperSite.path log.info("Using scraper: %s" % newscraper.name) # 32123 = Importing Game # 32131 = downloading info msg = "%s: %s[CR]%s: %s" %(util.localize(32123), gamenameFromFile, newscraper.name, util.localize(32131)) self._gui.writeMsg(msg, fileCount) results = newscraper.search(gamenameFromFile, romCollection.name) log.debug(u"Searching for %s - found %s results: %s" % (gamenameFromFile, len(results), results)) except ScraperExceededAPIQuoteException as ke: # API key is invalid - we need to stop scraping log.error("Scraper exceeded API key, stopping scraping") raise except Exception as e: log.error("Error searching for %s using scraper %s - %s %s" % ( gamenameFromFile, scraperSite.name, type(e), e)) return gameresult, artScrapers if results == []: log.warn("No search results found for %s using scraper %s" % (gamenameFromFile, scraperSite.name)) return gameresult, artScrapers matched = Matcher().getBestResults(results, gamenameFromFile) if matched is None: log.error("No matches found for %s, skipping" % gamenameFromFile) return gameresult, artScrapers log.debug("After matching: %s" % matched) try: retrievedresult = newscraper.retrieve(matched['id'], romCollection.name) log.debug(u"Retrieving %s - found %s" % (matched['id'], retrievedresult)) except Exception as e: # FIXME TODO Catch exceptions specifically log.error("Error retrieving %s - %s %s" % (matched['id'], type(e), e)) return gameresult, artScrapers # Update the gameresult with any new fields gameresult = self.addNewElements(gameresult, retrievedresult) # Find Filetypes and Scrapers for Art Download # FIXME TODO The following is kept to keep artwork downloading working as it currently is. We already have # the URLs and so could handle/download here, rather than deferring if len(gameresult) > 0: for path in romCollection.mediaPaths: thumbKey = 'Filetype' + path.fileType.name if len(self.resolveParseResult(gameresult, thumbKey)) > 0: if (thumbKey in artScrapers) == 0: artScrapers[thumbKey] = scraperSite.name log.debug(u"After scraping, result = %s, artscrapers = %s" % (gameresult, artScrapers)) return gameresult, artScrapers
def updateDB(self, gdb, gui, romCollections, isRescrape): self.gdb = gdb self._gui = gui log.info("Start Update DB") #at start, check if we need to create any artwork directories if not helper.createArtworkDirectories(romCollections): #32010: Error: Could not create artwork directory. return False, util.localize(32010) log.info("Iterating Rom Collections") rccount = 1 # always do full reimports when in rescrape-mode enableFullReimport = isRescrape or __addon__.getSetting(util.SETTING_RCB_ENABLEFULLREIMPORT).upper() == 'TRUE' log.info("enableFullReimport: {0}".format(enableFullReimport)) continueUpdate = True # Added variable to allow user to continue on errors ignoreErrors = False for romCollection in list(romCollections.values()): # timestamp1 = time.clock() # check if import was canceled if not continueUpdate: log.info("Game import canceled") break # prepare Header for ProgressDialog # 32122 = Importing Rom Collection progDialogRCHeader = util.localize(32122) + " (%i / %i): %s" % ( rccount, len(romCollections), romCollection.name) rccount += 1 log.info("current Rom Collection: {0}".format(romCollection.name)) # Read settings for current Rom Collection log.info("ignoreOnScan: {0}".format(romCollection.ignoreOnScan)) if romCollection.ignoreOnScan: log.info("current Rom Collection will be ignored.") # self.scrapeResultsFile.write('Rom Collection will be ignored.\n') continue log.info("update is allowed for current rom collection: {0}".format(romCollection.allowUpdate)) log.info("max folder depth: {0}".format(romCollection.maxFolderDepth)) files = self.getRomFilesByRomCollection(romCollection, enableFullReimport) if len(files) == 0: log.info(u"No files found for rom collection {0}, skipping".format(romCollection.name)) continue log.info(u"Found {0} game files for rom collection {1}".format(len(files), romCollection.name)) # itemCount is used for percentage in ProgressDialogGUI self._gui.itemCount = len(files) + 1 successfulFiles = 0 lastgamename = '' lastGameId = None for fileidx, filename in enumerate(files): try: #Give kodi a chance to interrupt the process #HACK: we should use monitor.abortRequested() or monitor.waitForAbort() #but for some reason only xbmc.abortRequested returns True if monitor.abortRequested(): log.info("Kodi requests abort. Cancel Update.") break log.info("Scraping for %s" % filename) gamenameFromFile = romCollection.getGamenameFromFilename(filename) # check if we are handling one of the additional disks of a multi rom game isMultiRomGame = (gamenameFromFile == lastgamename) lastgamename = gamenameFromFile if isMultiRomGame: # Add this entry as a file under the game ID and move on log.info("Detected %s as a multirom game (previous game was %s)" % (filename, lastgamename)) if lastGameId is None: log.error("Game detected as multi rom game, but lastGameId is None.") continue fileType = FileType() fileType.id, fileType.name, fileType.parent = 0, "rcb_rom", "game" self.insertFile(filename, lastGameId, fileType, None, None, None) self.gdb.commit() del fileType continue log.info("Start scraping info for game: %s" % gamenameFromFile) # 32123 = Importing Game msg = "%s: %s" %(util.localize(32123), gamenameFromFile) continueUpdate = self._gui.writeMsg(msg, fileidx + 1) if not continueUpdate: log.info("Game import canceled by user") break # check if this file already exists in DB continueUpdate, isUpdate, gameId = self.checkRomfileAlreadyExists(filename, enableFullReimport) if not continueUpdate: continue foldername = self.getFoldernameFromRomFilename(filename) results, artScrapers = self.useSingleScrapers(romCollection, filename, gamenameFromFile, progDialogRCHeader, fileidx + 1) if len(results) == 0: # lastgamename = "" results = None # Variables to process Art Download Info self._guiDict.update({'dialogHeaderKey': progDialogRCHeader, 'gameNameKey': gamenameFromFile, 'scraperSiteKey': artScrapers, 'fileCountKey': (fileidx + 1)}) del artScrapers #Give kodi a chance to interrupt the process #HACK: we should use monitor.abortRequested() or monitor.waitForAbort() #but for some reason only xbmc.abortRequested returns True if monitor.abortRequested(): log.info("Kodi requests abort. Cancel Update.") break # Add 'gui' and 'dialogDict' parameters to function lastGameId = self.insertGameFromDesc(results, gamenameFromFile, romCollection, [filename], foldername, isUpdate, gameId) del results, foldername if lastGameId is not None: log.info("Successfully added %s" % gamenameFromFile) successfulFiles += 1 # Check if all first 10 games have errors - Modified to allow user to continue on errors if fileidx > 9 and successfulFiles == 0 and not ignoreErrors: #32124 = Continue #32125 = Continue and Ignore Errors #32126 = Cancel #32127 = First 10 games could not be imported. options = [util.localize(32124), util.localize(32125), util.localize(32126)] answer = xbmcgui.Dialog().select(util.localize(32127), options) if answer == 1: # Continue and ignore errors ignoreErrors = True elif answer == 2: # Cancel #32128 = Import canceled. #32129 = Please check kodi.log for details. message = "%s[CR]%s" % (util.localize(32128), util.localize(32129)) xbmcgui.Dialog().ok(util.SCRIPTNAME, message) continueUpdate = False break except ScraperExceededAPIQuoteException: #32128 = Import canceled. #32043 = API quota for current scraper exceeded. xbmcgui.Dialog().ok(util.localize(32128), util.localize(32043)) # Abort the scraping entirely break except Exception as exc: log.warn(u"An error occurred while adding game %s: %s" % (gamenameFromFile, exc)) self.missingDescFile.add_entry(gamenameFromFile) continue # timestamp2 = time.clock() # diff = (timestamp2 - timestamp1) * 1000 # print "load %i games in %d ms" % (self.getListSize(), diff) self._gui.writeMsg("Done.", self._gui.itemCount) log.info("Update finished") return True, ''
def getThumbFromOnlineSource(self, gamedescription, fileType, fileName, artworkurls): log.info("Get thumb from online source") try: # maybe we got a thumb url from desc parser thumbKey = 'Filetype' + fileType log.info("using key: %s" % thumbKey) thumbUrl = self.resolveParseResult(gamedescription, thumbKey) if thumbUrl == '': return True, artworkurls artworkurls[fileType] = thumbUrl log.info("Get thumb from url: %s" % thumbUrl) rootExtFile = os.path.splitext(fileName) rootExtUrl = os.path.splitext(thumbUrl) files = [] if len(rootExtUrl) == 2 and len(rootExtFile) != 0: fileName = rootExtFile[0] + rootExtUrl[1] gameName = rootExtFile[0] + ".*" files = self.getFilesByWildcard(gameName) del rootExtFile, rootExtUrl if len(files) > 0: log.info("File already exists. Won't download again.") return True, artworkurls # Create folder if it doesn't already exist dirname = os.path.join(os.path.dirname(fileName), '') # Add the trailing slash that xbmcvfs.exists expects log.debug("Checking for artwork directory %s" % dirname) if KodiVersions.getKodiVersion() >= KodiVersions.KRYPTON: exists = xbmcvfs.exists(dirname) else: exists = os.path.exists(dirname) if not exists: log.info("Artwork directory %s doesn't exist, creating it" % dirname) success = xbmcvfs.mkdirs(dirname) log.info("Directory successfully created: %s" %success) if not success: #HACK: check if directory was really not created. directoryExists = xbmcvfs.exists(dirname) log.info("Directory exists: %s" %directoryExists) if not directoryExists: log.error("Could not create artwork directory: '%s'" % dirname) xbmcgui.Dialog().ok(util.localize(32010), util.localize(32011)) del dirname return False, artworkurls log.info("File %s does not exist, starting download" % fileName) # Dialog Status Art Download # Update progress dialog to state we are downloading art try: msg = "%s: %s" % (util.localize(32123), self._guiDict["gameNameKey"]) submsg = "%s - downloading art" % self._guiDict["scraperSiteKey"][thumbKey] self._gui.writeMsg(self._guiDict["dialogHeaderKey"], msg, submsg, self._guiDict["fileCountKey"]) except KeyError: log.warn("Unable to retrieve key from GUI dict") try: self.download_thumb(thumbUrl, fileName) except Exception, (exc): log.error("Could not create file: '%s'. Error message: '%s'" % (fileName, exc)) # xbmcgui.Dialog().ok(util.localize(32012), util.localize(32011)) return False, artworkurls Logutil.log("Download finished.", util.LOG_LEVEL_INFO)
def showGame(self): log.info("Begin showGameList UIGameInfoView") self.clearList() game = GameView(self.gdb).getGameById(self.selectedGameId) item = xbmcgui.ListItem(game[DataBaseObject.COL_NAME], str(game[GameView.COL_ID])) romcollection_id = str(game[GameView.COL_romCollectionId]) try: romCollection = self.config.romCollections[romcollection_id] except KeyError: log.error('Cannot get rom collection with id: ' + romcollection_id) item.setProperty('romCollectionId', romcollection_id) item.setProperty('romcollection', romCollection.name) item.setProperty('console', romCollection.name) item.setProperty('gameId', str(game[GameView.COL_ID])) item.setProperty('plot', game[GameView.COL_description]) item.setProperty('developer', game[GameView.COL_developer]) item.setProperty('publisher', game[GameView.COL_publisher]) item.setProperty('year', game[GameView.COL_year]) item.setProperty('genre', game[GameView.COL_genre]) item.setProperty('gameCmd', game[GameView.COL_gameCmd]) item.setProperty('alternateGameCmd', game[GameView.COL_alternateGameCmd]) item.setProperty('playcount', str(game[GameView.COL_launchCount])) item.setProperty('originalTitle', game[GameView.COL_originalTitle]) item.setProperty('alternateTitle', game[GameView.COL_alternateTitle]) item.setProperty('rating', str(game[GameView.COL_rating])) item.setProperty('media', str(game[GameView.COL_media])) item.setProperty('controllertype', str(game[GameView.COL_controllerType])) item.setProperty('region', str(game[GameView.COL_region])) item.setProperty('maxplayers', str(game[GameView.COL_maxPlayers])) item.setProperty('url', str(game[GameView.COL_url])) if game[GameView.COL_isFavorite] == 1: item.setProperty('isfavorite', '1') else: item.setProperty('isfavorite', '') item.setArt({ 'icon': helper.get_file_for_control_from_db( romCollection.imagePlacingMain.fileTypesForGameList, game), 'thumb': helper.get_file_for_control_from_db( romCollection.imagePlacingMain.fileTypesForGameListSelected, game), IMAGE_CONTROL_BACKGROUND: helper.get_file_for_control_from_db( romCollection.imagePlacingInfo.fileTypesForMainViewBackground, game), IMAGE_CONTROL_GAMEINFO_BIG: helper.get_file_for_control_from_db( romCollection.imagePlacingInfo.fileTypesForMainViewGameInfoBig, game), IMAGE_CONTROL_GAMEINFO_UPPERLEFT: helper.get_file_for_control_from_db( romCollection.imagePlacingInfo.fileTypesForMainViewGameInfoUpperLeft, game), IMAGE_CONTROL_GAMEINFO_UPPERRIGHT: helper.get_file_for_control_from_db( romCollection.imagePlacingInfo.fileTypesForMainViewGameInfoUpperRight, game), IMAGE_CONTROL_GAMEINFO_LOWERLEFT: helper.get_file_for_control_from_db( romCollection.imagePlacingInfo.fileTypesForMainViewGameInfoLowerLeft, game), IMAGE_CONTROL_GAMEINFO_LOWERRIGHT: helper.get_file_for_control_from_db( romCollection.imagePlacingInfo.fileTypesForMainViewGameInfoLowerRight, game), IMAGE_CONTROL_GAMEINFO_UPPER: helper.get_file_for_control_from_db( romCollection.imagePlacingInfo.fileTypesForMainViewGameInfoUpper, game), IMAGE_CONTROL_GAMEINFO_LOWER: helper.get_file_for_control_from_db( romCollection.imagePlacingInfo.fileTypesForMainViewGameInfoLower, game), IMAGE_CONTROL_GAMEINFO_LEFT: helper.get_file_for_control_from_db( romCollection.imagePlacingInfo.fileTypesForMainViewGameInfoLeft, game), IMAGE_CONTROL_GAMEINFO_RIGHT: helper.get_file_for_control_from_db( romCollection.imagePlacingInfo.fileTypesForMainViewGameInfoRight, game), IMAGE_CONTROL_1: helper.get_file_for_control_from_db( romCollection.imagePlacingInfo.fileTypesForMainView1, game), IMAGE_CONTROL_2: helper.get_file_for_control_from_db( romCollection.imagePlacingInfo.fileTypesForMainView2, game), IMAGE_CONTROL_3: helper.get_file_for_control_from_db( romCollection.imagePlacingInfo.fileTypesForMainView3, game) }) #add item to listcontrol listcontrol = self.getControlById(CONTROL_GAME_LIST) listcontrol.addItem(item) self.writeMsg("") log.info("End showGameList UIGameInfoView")