Coverage for lutris.installer : 40%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
# pylint: disable=E1101, E0611
""" Custom exception for scripting errors, can be caught by modifying excepthook """
if error_type == ScriptingError: message = value.message if value.faulty_data: message += "\n<b>" + str(value.faulty_data) + "</b>" ErrorDialog(message) else: _excepthook(error_type, value, traceback)
""" Class that converts raw script data to actions """
else:
def default_target(self):
def download_cache_path(self): return os.path.join(settings.CACHE_DIR, "installer/%s" % self.game_slug)
def should_create_target(self): return (not os.path.exists(self.target_path) and 'nocreatedir' not in self.script)
if not game or not game['directory']: "You need to install {} before".format(self.requires) )
else: request = urllib2.Request(url=full_url) dlg = NoInstallerDialog(self.parent) self.parent.notify_install_success() webbrowser.open(installer_url)
""" Return True if script is usable """
if self.files: # Create cache dir if needed if not os.path.exists(self.download_cache_path): os.mkdir(self.download_cache_path)
os.makedirs(self.target_path)
if len(self.game_files) < len(self.files): logger.info( "Downloading file %d of %d", len(self.game_files) + 1, len(self.script["files"]) ) self._download_file(self.script["files"][len(self.game_files)]) else: self.current_command = 0 self._prepare_commands()
"""Download a file referenced in the installer script
Game files can be either a string, containing the location of the file to fetch or a dict with the following keys: - url : location of file, if not present, filename will be used this should be the case for local files - filename : force destination filename when url is present or path of local file """ # Setup file_id, file_uri and local filename file_id = game_file.keys()[0] if isinstance(game_file[file_id], dict): filename = game_file[file_id]['filename'] file_uri = game_file[file_id]['url'] else: file_uri = game_file[file_id] filename = os.path.basename(file_uri) if file_uri.startswith("/"): file_uri = "file://" + file_uri elif file_uri.startswith(("$WINESTEAM", "$STEAM")): # Download Steam data try: parts = file_uri.split(":", 2) steam_rel_path = parts[2].strip() except IndexError: raise ScriptingError("Malformed steam path: %s" % file_uri) if steam_rel_path == "/": steam_rel_path = "." self.steam_data = { 'appid': parts[1], 'steam_rel_path': steam_rel_path, 'file_id': file_id } if parts[0] == '$WINESTEAM': self.steam_data['platform'] = "windows" # Getting data from Wine Steam steam_runner = winesteam.winesteam() if not steam_runner.is_installed(): # Downoad Steam for Windows steam_installer_path = os.path.join( settings.TMP_PATH, "SteamInstall.msi" ) self.parent.start_download( winesteam.winesteam.installer_url, steam_installer_path, self.parent.on_steam_downloaded, self.steam_data['appid'] ) else: self.install_steam_game(winesteam.winesteam) return else: # Getting data from Linux Steam self.steam_data['platform'] = "linux" self.install_steam_game(steam.steam) return logger.debug("Fetching [%s]: %s" % (file_id, file_uri))
# Check for file availability in PGA pga_uri = pga.check_for_file(self.game_slug, file_id) if pga_uri: file_uri = pga_uri
# Setup destination path dest_file = os.path.join(self.download_cache_path, filename)
if file_uri.startswith("N/A"): # Ask the user where is located the file parts = file_uri.split(":", 1) if len(parts) == 2: else: message = "Please select file '%s'" % file_id self.current_file_id = file_id self.parent.ask_user_for_file(message) return
if os.path.exists(dest_file): logger.debug("Destination file exists") if settings.KEEP_CACHED_ASSETS: self.game_files[file_id] = dest_file self.iter_game_files() return else:
# Change parent's status self.parent.set_status('Fetching %s' % file_uri) self.game_files[file_id] = dest_file
file_id = self.current_file_id if not file_path or not os.path.exists(file_path): "Can't continue installation without file", file_id ) self.game_files[file_id] = file_path self.iter_game_files()
os.chdir(self.target_path) self._iter_commands()
if result == 'STOP': return
self.parent.add_spinner()
commands = self.script.get('installer', []) self.parent.on_install_error(repr(exception)) elif self.current_command < len(commands): command = commands[self.current_command] self.current_command += 1 method, params = self._map_command(command) if isinstance(params, dict): else: status_text = None self.parent.set_status(status_text) async_call(method, self._iter_commands, params) else:
self.parent.set_status("Writing configuration") self._write_config() self.parent.set_status("Installation finished !") self.parent.on_install_finished()
self.parent.set_status(message)
if os.path.exists(self.download_cache_path): shutil.rmtree(self.download_cache_path)
""" Substitutes values such as $GAMEDIR in a config dict """ config = {} for key in script_config: if not isinstance(key, basestring): raise ScriptingError("Game config key must be a string", key) value = script_config[key] if isinstance(value, list): config[key] = [self._substitute(i) for i in value] else: config[key] = self._substitute(value) return config
"""Write the game configuration as a Lutris launcher.""" config_filename = os.path.join(settings.CONFIG_DIR, "games/%s.yml" % self.game_slug) runner_name = self.script['runner'] 'game': {}, 'realname': self.script['name'], 'runner': runner_name } slug=self.game_slug, directory=self.target_path, installed=1, installer_slug=self.script.get('installer_slug')) if 'system' in self.script: config['system'] = self._substitute_config(self.script['system']) self.script[runner_name] ) is_64bit = platform.machine() == "x86_64" for launcher in [exe, 'iso', 'rom', 'disk', 'main_file']: if launcher in self.script: if launcher == "exe64": key = "exe" else: key = launcher game_resource = self.script[launcher] if type(game_resource) == list: resource_paths = [] if res in self.game_files: else: config['game'][key] = resource_paths else: if game_resource in self.game_files: game_resource)): game_resource) else: game_resource = game_resource
yaml_config = yaml.safe_dump(config, default_flow_style=False) with open(config_filename, "w") as config_file: config_file.write(yaml_config)
""" Converts a line from the installer directive an internal method """ else: command_params = {} % command_name)
""" Replace path aliases with real paths """ "GAMEDIR": self.target_path, "CACHE": settings.CACHE_DIR, "HOME": os.path.expanduser("~") } replacements.update(self.game_files) return substitute(template_string, replacements)
""" Validate and converts raw data passed to 'move' """ for required_param in ('dst', 'src'): if required_param not in params: "The '%s' parameter is required for 'move'" % required_param, params ) src_ref = params['src'] src = (self.game_files.get(src_ref) or self._substitute(src_ref)) if not src: dst_ref = params['dst'] dst = self._substitute(dst_ref) raise ScriptingError("Wrong value for 'dst' param", dst_ref) return (src, dst)
return self.game_files.get(fileid)
message = data.get('message', "Insert game disc to continue") requires = data.get('requires') if not requires: "missing the `requires` parameter." * 2) self.parent.wait_for_user_action(message, self.on_cd_mounted, requires) return 'STOP'
paths = ['/mnt', '/media/cdrom', '/cdrom', '/media/%s/disk' % os.getlogin()] for path in paths: required_abspath = os.path.join(path, requires) if os.path.exists(required_abspath): self._iter_commands()
filename = self._substitute(filename) os.popen('chmod +x "%s"' % filename)
"""Run an executable script""" if isinstance(data, dict): exec_id = data['file'] args = [self._substitute(arg) for arg in data.get('args', '').split()] else: exec_id = data exec_path = self._get_file(exec_id) if not os.path.exists(exec_path): exec_path) else: self.chmodx(exec_path) logger.debug("Executing %s %s" % (exec_path, args)) subprocess.call([exec_path] + args)
filename = self._get_file(data['file']) _hash = calculate_md5(filename) if _hash != data['value']: raise ScriptingError("MD5 checksum mismatch", data)
directory = self._substitute(directory) os.makedirs(directory) except OSError: logger.debug("Directory %s already exists" % directory) else:
src, dst = self._get_move_paths(params) logger.debug("Merging %s into %s" % (src, dst)) if not os.path.exists(src): raise ScriptingError("Source does not exist: %s" % src, params) if not os.path.exists(dst): os.makedirs(dst) if os.path.isfile(src): # If single file, copy it and change reference in game file so it # can be used as executable. Skip copying if the source is the same # as destination. if os.path.dirname(src) != dst: shutil.copy(src, dst) if params['src'] in self.game_files.keys(): self.game_files[params['src']] = os.path.join( dst, os.path.basename(src) ) merge_folders(src, dst)
""" Move a file or directory """ src, dst = self._get_move_paths(params) logger.debug("Moving %s to %s" % (src, dst)) if not os.path.exists(src): raise ScriptingError("I can't move %s, it does not exist" % src) # TODO: fix behavior of 'move' in existing scripts #if not os.path.exists(dst): # os.makedirs(dst) #target = os.path.join(dst, os.path.basename(src)) #if os.path.exists(target): # raise ScriptingError("Destination %s already exists" % target) if os.path.isfile(src) and os.path.dirname(src) == dst: logger.info("Source file is the same as destination, skipping") else: try: shutil.move(src, dst) except shutil.Error: % (src, dst)) if os.path.isfile(src) and params['src'] in self.game_files.keys(): # Change game file reference so it can be used as executable self.game_files['src'] = src
""" Extracts a file, guessing the compression method """ if not 'file' in data: 'extract command', data) if not filename: filename = self._substitute(data['file'])
if not os.path.exists(filename): raise ScriptingError("%s does not exists" % filename) if 'dst' in data: dest_path = self._substitute(data['dst']) else: dest_path = self.target_path msg = "Extracting %s" % filename merge_single = not 'nomerge' in data logger.debug("extracting file %s to %s", filename, dest_path) extract.extract_archive(filename, dest_path, merge_single)
steam_runner = runner_class() data_path = steam_runner.get_game_data_path(self.steam_data['appid']) if not data_path or not os.path.exists(data_path): raise ScriptingError("Unable to get Steam data for game") logger.debug("got data path: %s" % data_path) os.path.join(data_path, self.steam_data['steam_rel_path']) self.iter_game_files()
""" This action triggers a task within a runner. The 'name' parameter is mandatory. If 'args' is provided it will be passed to the runner task. """ runner_name = self.script["runner"] for key in data: data[key] = self._substitute(data[key]) task = import_task(runner_name, task_name) task(**data)
appid = self.steam_data['appid'] # Here the user must wait for the game to finish installing, a # better way to handle this would be to poll StateFlags on the # game's config to check if the game has finished installing. self.parent.wait_for_user_action( "Steam will now install game %s, " "press Ok when install is finished" % appid, self.on_steam_game_installed, appid ) else:
self.parent.wait_for_user_action( "Steam will now install, press Ok when install is finished", self.on_winesteam_installed, appid ) steam_runner = winesteam.winesteam() async_call(steam_runner.install, None, dest)
self.install_steam_game(winesteam.winesteam)
logger.debug("Steam game installed") if self.steam_data['platform'] == 'windows': runner_class = winesteam.winesteam else: runner_class = steam.steam self._append_steam_data_to_files(runner_class)
# pylint: disable=R0904 """ Gtk Dialog used during the install process """
# # Fetch assets # banner_url = settings.INSTALLER_URL + '%s.jpg' % self.game_slug # banner_dest = join(settings.DATA_DIR, "banners/%s.jpg" % self.game_slug) # http.download_asset(banner_url, banner_dest, True) # icon_url = settings.INSTALLER_URL + 'icon/%s.jpg' % self.game_slug # icon_dest = join(settings.DATA_DIR, "icons/%s.png" % self.game_slug) # http.download_asset(icon_url, icon_dest, True)
Gtk.Window.__init__(self) self.parent = parent self.game_ref = game_ref # Dialog properties self.set_size_request(600, 480) self.set_default_size(600, 480) self.set_position(Gtk.WindowPosition.CENTER) self.set_resizable(False)
self.vbox = Gtk.VBox() self.add(self.vbox)
# Default signals self.connect('destroy', self.on_destroy)
# Interpreter self.interpreter = ScriptInterpreter(game_ref, self) if not self.interpreter.script:
## GUI Setup
# Title label title_label = Gtk.Label() game_name = self.interpreter.game_name title_label.set_markup("<b>Installing {}</b>".format(game_name))
self.status_label = Gtk.Label() self.status_label.set_max_width_chars(80) self.status_label.set_property('wrap', True) self.vbox.pack_start(self.status_label, False, False, 15)
# Main widget box self.widget_box.set_margin_right(25)
self.location_entry = None
# Separator
# Buttons action_buttons_alignment = Gtk.Alignment.new(0.95, 0, 0.15, 0) action_buttons_alignment.add(self.action_buttons) self.vbox.pack_start(action_buttons_alignment, False, True, 20)
self.install_button.connect('clicked', self.on_install_clicked) self.action_buttons.add(self.install_button)
self.continue_button = Gtk.Button(label='Continue') self.continue_button.connect('clicked', self.on_file_selected) self.action_buttons.add(self.continue_button)
self.play_button.set_margin_left(20) self.play_button.connect('clicked', self.launch_game)
self.close_button = Gtk.Button(label="Close") self.close_button.set_margin_left(20) self.action_buttons.add(self.close_button)
# Target chooser self.set_message("Select installation directory") default_path = self.interpreter.default_target self.non_empty_label = Gtk.Label() self.non_empty_label.set_markup( "<b>Warning!</b> The selected path " "contains files, installation might not work property." ) self.widget_box.pack_start(self.non_empty_label, False, False, 10) else: self.set_message("Click install to continue") self.show_all() self.continue_button.hide() self.close_button.hide() self.play_button.hide() self.show_non_empty_warning()
self.interpreter.cleanup() else: Gtk.main_quit()
path = self.location_entry.get_text() if os.path.exists(path) and os.listdir(path): else: self.non_empty_label.hide()
label = Gtk.Label() label.set_max_width_chars(80) label.set_property('wrap', True) label.set_alignment(0, 0) label.show() self.widget_box.pack_start(label, False, False, 10)
if self.location_entry: self.location_entry.destroy() self.location_entry = FileChooserEntry( action=Gtk.FileChooserAction.OPEN, default=default_path ) if callback: self.location_entry.entry.connect('changed', callback) else: self.install_button.set_visible(False) self.continue_button.show() self.widget_box.pack_start(self.location_entry, False, False, 0)
""" Sets the installation target for the game """ path = text_entry.get_text() self.interpreter.target_path = path self.show_non_empty_warning()
button.set_sensitive(False) self.interpreter.iter_game_files()
self.clean_widgets() self.set_message(message) self.set_location_entry(None)
file_path = self.location_entry.get_text() logger.debug("User selected file: %s", file_path) self.interpreter.file_selected(file_path)
for child_widget in self.widget_box.get_children(): child_widget.destroy()
self.status_label.set_text(text)
self.clean_widgets() spinner = Gtk.Spinner() self.widget_box.pack_start(spinner, True, False, 10) spinner.show() spinner.start()
self.clean_widgets() self.download_progress = DownloadProgressBox( {'url': file_uri, 'dest': dest_file}, cancelable=True ) callback_function = callback or self.download_complete self.download_progress.connect('complete', callback_function, data) self.widget_box.pack_start(self.download_progress, False, False, 10) self.download_progress.show() self.download_progress.start()
time.sleep(0.3) self.clean_widgets() label = Gtk.Label(label=message) self.widget_box.add(label) label.show() button = Gtk.Button(label='Ok') button.connect('clicked', callback, data) self.widget_box.add(button) button.show()
"""Action called on a completed download""" self.interpreter.iter_game_files()
self.interpreter.complete_steam_install(widget.dest, appid)
"""Actual game installation""" self.status_label.set_text("Installation finished !") self.clean_widgets() self.notify_install_success() self.continue_button.hide() self.install_button.hide() self.play_button.show() self.close_button.show()
if self.parent: self.parent.view.emit('game-installed', self.game_ref)
self.status_label.set_text(message) self.clean_widgets() self.close_button.show()
"""Launch a game after it's been installed""" widget.set_sensitive(False) game = Game(self.interpreter.game_slug) game.play()
self.destroy() |