From 368f1a1e24b926a853ff0b8c21ebd8b2a193c391 Mon Sep 17 00:00:00 2001 From: Taeyeon Mori Date: Thu, 29 Oct 2020 05:27:08 +0100 Subject: [PATCH] python/steamutil,steamsync,protontool: Remove space on empty lines --- bin/protontool | 24 ++++++------- lib/python/steamsync.py | 34 +++++++++--------- lib/python/steamutil.py | 80 ++++++++++++++++++++--------------------- lib/python/vdfparser.py | 52 +++++++++++++-------------- 4 files changed, 95 insertions(+), 95 deletions(-) diff --git a/bin/protontool b/bin/protontool index 124e258..939d36e 100755 --- a/bin/protontool +++ b/bin/protontool @@ -59,7 +59,7 @@ class ProtonTool: self.app = apps[0] print_info("Found game: %s" % self.app.name) - + @steamutil.CachedProperty def user(self) -> steamutil.LoginUser: if "userid" in self.args: @@ -83,7 +83,6 @@ class ProtonTool: print(" ", path) parser.exit(0) - @classmethod def parse_args(cls, args, prog=None): parser = argparse.ArgumentParser(prog=prog) @@ -133,12 +132,12 @@ class ProtonTool: @steamutil.CachedProperty def appinfo(self): return self.steam.appinfo - + @property def compat_tools(self) -> dict: self.steam.steamplay_manifest = self.appinfo[891390]["appinfo"] return self.steam.compat_tools - + @property def compat_tool(self) -> dict: return self.app.compat_tool @@ -147,9 +146,9 @@ class ProtonTool: def _format_tool_info(cls, steam, tool, toolinfo=None) -> (str, str): if toolinfo is None: toolinfo = steam.compat_tools.get(tool["name"], None) - + proton = [] - + if not toolinfo: proton.append("\033[31mNot found:\033[0m") @@ -181,7 +180,7 @@ class ProtonTool: def cmd_info(self): uc = self.user.get_app_config(self.app) - + if self.app.installed: installed = "[%s] %s" % (self.app.language, format_size(self.app.declared_install_size)) @@ -206,7 +205,7 @@ class ProtonTool: print(" @", proton_path) if self.app.is_proton_app: print("\033[1;35mProtonDrv.\033[0m:", self.app.compat_drive) - + def setup_proton_env(self, tool, wine=False): info = self.compat_tools[tool["name"]] if not info: @@ -232,9 +231,9 @@ class ProtonTool: tool = self.compat_tool self.setup_proton_env(tool, wine=True) - + return os.execvp(self.args.cmdline[0], self.args.cmdline) - + def cmd_run(self): """ Run a game executable directly, possibly using a compat tool """ # Obviously has to be installed @@ -249,7 +248,7 @@ class ProtonTool: tool = self.compat_tool else: tool = None - + # Default to native oslist if sys.platform.startswith("linux"): target_oslist = "linux" @@ -304,6 +303,7 @@ class ProtonTool: else: print_error("No matching launch configuration in appinfo") return 51 + def cmd_proton(self): # proton and wine subcommands: # Run proton respective wine with arguments @@ -361,7 +361,7 @@ class ProtonTool: print() self._pretty_dump(info.dict) - + def _pretty_dump(self, d: dict, level=0): for key, value in d.items(): print(" " * level, "\033[1m", key, "\033[0m", end="", sep="") diff --git a/lib/python/steamsync.py b/lib/python/steamsync.py index 477728c..08a766f 100644 --- a/lib/python/steamsync.py +++ b/lib/python/steamsync.py @@ -23,12 +23,12 @@ class SyncPath: Whereby target is the location being synched to and local is the prefix the data is synched from on the local machine. Common has components common to both paths. - + Usually, you'd set the local prefix and then the common part. e.g.: op.home.prefix(".my_game") / "Savegames" whereby "Savegames" is included in the resulting target path, but ".my_game" is not. - + Note that SyncPath should be considered immutable. Relevant methods return a new instance. """ @@ -52,16 +52,16 @@ class SyncPath: """ Return a new SyncPath that nas a component added """ return SyncPath(self.op, self.local, self.common / component) - ## Retrieve paths + ## Retrieve paths @property def path(self) -> Path: """ Get the local path """ return self.local / self.common - + def exists(self) -> bool: """ Chech whether local path exists """ return self.path.exists() - + @property def target_path(self) -> Path: """ Get the sync target path """ @@ -70,7 +70,7 @@ class SyncPath: ## Begin a SyncSet def __enter__(self) -> 'SyncSet': return SyncSet(self) - + def __exit__(self, type, value, traceback): # Todo: auto-commit? pass @@ -94,12 +94,12 @@ class SyncSet: self.spath = path self.local = {} self.target = {} - + @property def path(self) -> Path: """ The local path """ return self.spath.path - + @property def target_path(self) -> Path: """ The target path """ @@ -123,11 +123,11 @@ class SyncSet: self.local.update(self._collect_files(self.path, patterns)) self.target.update(self._collect_files(self.target_path, patterns)) self._inval() - + def __iadd__(self, pattern: str) -> 'SyncSet': self.add(pattern) return self - + # Calculate changes def _inval(self): for cache in "files_from_local", "files_from_target", "files_unmodified": @@ -143,19 +143,19 @@ class SyncSet: for f, (_, sst) in src_files.items() if f not in dst_files or sst.st_mtime > dst_files[f][1].st_mtime } - + @CachedProperty def files_from_local(self) -> Set[Path]: return self._sync_set(self.local, self.target) - + @CachedProperty def files_from_target(self) -> Set[Path]: return self._sync_set(self.target, self.local) - + @CachedProperty def files_unmodified(self) -> Set[Path]: return (self.local.keys() | self.target.keys()) - (self.files_from_local | self.files_from_target) - + def show_confirm(self, skip=True) -> bool: # XXX: move to SyncOp? print(" Local is newer: ", ", ".join(map(str, self.files_from_local))) @@ -182,7 +182,7 @@ class SyncSet: if self.files_from_target: operations += [(self.target_path / p, self.path / p) for p in self.files_from_target] #pylint:disable=not-an-iterable - + return self.op._do_copy(operations) @@ -342,7 +342,7 @@ class SteamSync: return SteamSyncOp(self, app) else: return AppNotFound - + def by_name(self, pattern): """ Steam App by Name """ pt = re.compile(fnmatch.translate(pattern).rstrip("\\Z"), re.IGNORECASE) @@ -355,7 +355,7 @@ class SteamSync: if app is None: return AppNotFound return SteamSyncOp(self, app) - + def generic(self, name, *, platform=None): """ Non-Steam App """ if platform is not None and platform not in sys.platform: diff --git a/lib/python/steamutil.py b/lib/python/steamutil.py index 627f764..fd4916c 100644 --- a/lib/python/steamutil.py +++ b/lib/python/steamutil.py @@ -39,7 +39,7 @@ class DictPathRoProperty: self.path = path self.default = default self.type = type - + def __get__(self, obj, cls): if obj is None: return self @@ -71,7 +71,7 @@ class DictPathProperty(DictPathRoProperty): def __set__(self, obj, value): self._get_create_parent(obj)[self.path[-1]] = value - + def __delete__(self, obj): del self._get_create_parent(obj)[self.path[-1]] @@ -88,7 +88,7 @@ class MalformedManifestError(Exception): class AppInfo: steam: 'Steam' appid: int - + def __init__(self, steam, appid, *, appinfo_data=None): self.steam = steam self.appid = appid @@ -99,7 +99,7 @@ class AppInfo: return "" % (self.appid, self.name, self.install_dir) installed = False - + # AppInfo @CachedProperty def appinfo(self): @@ -115,11 +115,11 @@ class AppInfo: install_dir = DictPathRoProperty("appinfo", ("appinfo", "config", "installdir"), default=None) languages = DictPathRoProperty("appinfo", ("appinfo", "common", "supported_languages")) gameid = DictPathRoProperty("appinfo", ("appinfo", "common", "gameid"), type=int) - + # Misc. def get_userdata_path(self, user_id: Union[int, 'LoginUser']) -> Path: return self.steam.get_userdata_path(user_id) / str(self.appid) - + @property def is_native(self): return sys.platform in self.oslist @@ -166,11 +166,11 @@ class App(AppInfo): if "AppState" not in self.manifest: raise MalformedManifestError("App manifest doesn't have AppState key", self.manifest_path) - + super().__init__(libfolder.steam, int(self.manifest["AppState"]["appid"])) - + installed = True - + def __repr__(self): return "" % (self.appid, self.name, self.install_path) @@ -178,11 +178,11 @@ class App(AppInfo): name = DictPathRoProperty("manifest", ("AppState", "name")) language = DictPathRoProperty("manifest", ("AppState", "UserConfig", "language"), None) install_dir = DictPathRoProperty("manifest", ("AppState", "installdir")) - + @CachedProperty def install_path(self) -> Path: return self.steamapps_path / "common" / self.install_dir - + # Workshop # TODO @CachedProperty @@ -195,7 +195,7 @@ class App(AppInfo): uc = self.manifest["AppState"].get("UserConfig") if uc and "platform_override_source" in uc: return uc["platform_override_source"] - + @property def is_proton_app(self): uc = self.manifest["AppState"].get("UserConfig") @@ -205,14 +205,14 @@ class App(AppInfo): @CachedProperty def compat_path(self) -> Path: return self.steamapps_path / "compatdata" / str(self.appid) - + @CachedProperty def compat_drive(self) -> Path: return self.compat_path / "pfx" / "drive_c" - + # Install size declared_install_size = DictPathRoProperty("manifest", ("AppState", "SizeOnDisk"), 0, type=int) - + def compute_install_size(self) -> int: def sum_size(p: Path): acc = 0 @@ -232,7 +232,7 @@ class LibraryFolder: def __init__(self, steam: 'Steam', path: Path): self.steam = steam self.path = path - + def __repr__(self): return "" % self.path @@ -254,7 +254,7 @@ class LibraryFolder: return found[0] # if none exists, return non-existant default name return steamapps - + @property def common_path(self) -> Path: return self.steamapps_path / "common" @@ -295,26 +295,26 @@ class UserAppConfig: def __init__(self, user, appid): self.user = user self.appid = appid - + def __repr__(self): return "" % (self.appid, self.user.account_name) - + @property def _data(self): try: return self.user.localconfig["UserLocalConfigStore"]["Software"]["Valve"]["Steam"]["Apps"][str(self.appid)] except KeyError: return {} # TODO - + @property def last_played(self) -> datetime.datetime: return datetime.datetime.fromtimestamp(int(self._data.get("LastPlayed", "0"))) - + @property def playtime(self) -> datetime.time: t = int(self._data.get("Playtime", "0")) return datetime.time(t // 60, t % 60) - + @property def playtime_two_weeks(self) -> datetime.time: t = int(self._data.get("Playtime2wks", "0")) @@ -332,31 +332,31 @@ class LoginUser: self.steam = steam self.id = id self.info = info - + def __repr__(self): return "" % (self.id , self.account_name, self.username) - + @property def account_id(self): """ 32-bit account ID """ return self.id & 0xffffffff - + account_name = DictPathRoProperty("info", ("AccountName",)) username = DictPathRoProperty("info", ("PersonaName",)) @CachedProperty def userdata_path(self) -> Path: return self.steam.get_userdata_path(self) - + @property def localconfig_vdf(self) -> Path: return self.userdata_path / "config" / "localconfig.vdf" - + @CachedProperty def localconfig(self) -> DeepDict: with open(self.localconfig_vdf, encoding="utf-8") as f: return _vdf.parse(f) - + # Game config def get_app_config(self, app: Union[int, App]) -> Optional[UserAppConfig]: if isinstance(app, App): @@ -371,7 +371,7 @@ class Steam: self.root = install_path if install_path is not None else self.find_install_path() if self.root is None: raise Exception("Could not find Steam") - + def __repr__(self): return "" % self.root @@ -416,15 +416,15 @@ class Steam: def libraryfolders_vdf(self) -> Path: """ The libraryfolders.vdf file listing all configured library locations """ return self.root / "steamapps" / "libraryfolders.vdf" - + @property def config_vdf(self) -> Path: return self.root / "config" / "config.vdf" - + @property def loginusers_vdf(self) -> Path: return self.root / "config" / "loginusers.vdf" - + # Users @CachedProperty def most_recent_user(self) -> Optional[LoginUser]: @@ -439,18 +439,18 @@ class Steam: except KeyError: pass return None - + def get_userdata_path(self, user_id: Union[int, LoginUser]) -> Path: if isinstance(user_id, LoginUser): user_id = user_id.account_id return self.root / "userdata" / str(user_id) - + # Config @CachedProperty def config(self) -> DeepDict: with open(self.config_vdf, encoding="utf-8") as f: return _vdf.parse(f) - + config_install_store = DictPathProperty("config", ("InstallConfigStore",)) config_software_steam = DictPathProperty("config", ("InstallConfigStore", "Software", "Valve", "Steam")) compat_tool_mapping = DictPathProperty("config_software_steam", ("CompatToolMapping",)) @@ -459,11 +459,11 @@ class Steam: @CachedProperty def appinfo_vdf(self): return self.root / "appcache" / "appinfo.vdf" - + @property def appinfo(self) -> AppInfoFile: return AppInfoFile.open(self.appinfo_vdf) - + @CachedProperty def steamplay_manifest(self) -> DeepDict: with self.appinfo as info: @@ -503,16 +503,16 @@ class Steam: def library_folder_paths(self) -> List[Path]: with open(self.libraryfolders_vdf, encoding="utf-8") as f: return [Path(v) for k,v in _vdf.parse(f)["LibraryFolders"].items() if k.isdigit()] - + @CachedProperty def library_folders(self) -> List[LibraryFolder]: return [LibraryFolder(self, self.root)] + [LibraryFolder(self, p) for p in self.library_folder_paths] #pylint:disable=not-an-iterable - + @property def apps(self) -> Iterable[App]: for lf in self.library_folders: #pylint:disable=not-an-iterable yield from lf.apps - + def get_app(self, id: int, installed=True) -> Optional[App]: for lf in self.library_folders: #pylint:disable=not-an-iterable app = lf.get_app(id) diff --git a/lib/python/vdfparser.py b/lib/python/vdfparser.py index 8d51723..9e108cb 100644 --- a/lib/python/vdfparser.py +++ b/lib/python/vdfparser.py @@ -47,7 +47,7 @@ class VdfParser: whitespace_chars = " \t\n" comment_char = "/" newline_char = "\n" - + def __init__(self, *, encoding=False, factory=dict, strict=True): """ @brief Construct a VdfParser instance @@ -89,7 +89,7 @@ class VdfParser: while True: c = fd.read(1) - + if not c: finish() if len(tokens) / 2 != len(tokens) // 2: @@ -97,7 +97,7 @@ class VdfParser: elif self.strict and (escape or quoted or inner): raise ValueError("Unexpected EOF: EOF encountered while not processing outermost mapping") return self._make_map(tokens) - + if escape: current.append(c) escape = False @@ -110,11 +110,11 @@ class VdfParser: finish(override=True) else: current.append(c) - + elif comment: if c == self.newline_char: comment = False - + else: if c == self.escape_char: escape = True @@ -154,11 +154,11 @@ class VdfParser: return self.parse(io.BytesIO(content)) else: return self.parse(io.StringIO(content)) - + def _make_literal(self, lit): # TODO return "\"%s\"" % (str(lit).replace("\\", "\\\\").replace("\"", "\\\"")) - + def _write_map(self, fd, dictionary, indent): if indent is None: def write(str=None, i=False, d=False, nl=False): @@ -166,7 +166,7 @@ class VdfParser: fd.write(str) if d: fd.write(" ") - + else: def write(str=None, i=False, d=False, nl=False): if not str and nl: @@ -180,7 +180,7 @@ class VdfParser: fd.write("\n") elif d: fd.write("\t\t") - + for k, v in dictionary.items(): if isinstance(v, dict): write(self._make_literal(k), i=1, d=1, nl=1) @@ -191,7 +191,7 @@ class VdfParser: write(self._make_literal(k), i=1, d=1) write(self._make_literal(v)) write(d=1, nl=1) - + def write(self, fd, dictionary: DeepDict, *, pretty=True): """ Write a dictionary to a file in VDF format @@ -223,7 +223,7 @@ class BinaryVdfParser: def __init__(self, factory=dict): self.factory = factory - + @staticmethod def _read_until(fd: io.BufferedIOBase, delim: bytes) -> bytes: pieces = [] @@ -238,7 +238,7 @@ class BinaryVdfParser: end = buf.find(delim, 0, read) pieces.append(bytes(buf[:read if end < 0 else end])) - + fd.seek(end - read + len(delim), io.SEEK_CUR) return b"".join(pieces) @@ -249,10 +249,10 @@ class BinaryVdfParser: def _read_cstring(self, fd: io.BufferedIOBase) -> str: return self._read_until(fd, b'\0').decode("utf-8", "replace") - + def _read_wstring(self, fd: io.BufferedIOBase) -> str: return self._read_until(fd, b'\0\0').decode("utf-16") - + def _read_map(self, fd: io.BufferedIOBase) -> DeepDict: map = self.factory() @@ -264,10 +264,10 @@ class BinaryVdfParser: if t in (self.T_END, self.T_END2): return map - + key, value = self._read_item(fd, t) map[key] = value - + def _read_item(self, fd: io.BufferedIOBase, t: int) -> (str, DeepDict): key = self._read_cstring(fd) @@ -287,7 +287,7 @@ class BinaryVdfParser: return key, self._read_struct(fd, self.S_FLT4)[0] else: raise ValueError("Unknown data type", fd.tell(), t) - + def parse(self, fd: io.BufferedIOBase) -> DeepDict: return self._read_map(fd) @@ -310,7 +310,7 @@ class AppInfoFile: self._close_file = close self._universe = None self._apps = None - + def _load_map(self, offset: int) -> DeepDict: self.file.seek(offset, io.SEEK_SET) return self.parser.parse(self.file) @@ -329,19 +329,19 @@ class AppInfoFile: self.appinfo = appinfo self.offset = offset self._data = None - + def __getitem__(self, key): if self._data is None: self._data = self.appinfo._load_map(self.offset) return self._data[key] - + def __getattr__(self, attr): if attr in dir(dict): if self._data is None: self._data = self.appinfo._load_map(self.offset) return getattr(self._data, attr) raise AttributeError(attr) - + @property def dict(self): if self._data is None: @@ -353,7 +353,7 @@ class AppInfoFile: if len(cs) < s: raise EOFError() return cs - + def _read_int(self) -> int: return self.S_INT4.unpack(self._read_exactly(self.S_INT4.size))[0] @@ -361,7 +361,7 @@ class AppInfoFile: magic = self._read_exactly(4) if magic != b"\x27\x44\x56\x07": raise ValueError("Wrong appinfo.vdf magic") - + self._universe = self._read_int() self._apps = {} @@ -372,7 +372,7 @@ class AppInfoFile: if read < 4: raise EOFError() - + struct = self.S_APP_HEADER.unpack(buffer) appid, size, *_ = struct @@ -402,7 +402,7 @@ class AppInfoFile: if self._apps is None: self._load() return self._apps[key] - + def __iter__(self): if self._apps is None: self._load() @@ -417,7 +417,7 @@ class AppInfoFile: # Cleanup def __enter__(self): return self - + def __exit__(self, exc, tp, tb): self.close()