python/steamutil,steamsync,protontool: Remove space on empty lines

master
Taeyeon Mori 4 years ago
parent a9ec47f54a
commit 368f1a1e24
  1. 24
      bin/protontool
  2. 34
      lib/python/steamsync.py
  3. 80
      lib/python/steamutil.py
  4. 52
      lib/python/vdfparser.py

@ -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="")

@ -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:

@ -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 "<steamutil.AppInfo #%7d '%s' (%s)>" % (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 "<steamutil.App #%7d '%s' @ \"%s\">" % (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 "<steamutil.LibraryFolder @ \"%s\">" % 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 "<steamutil.UserAppConfig appid=%d for account %s>" % (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 "<steamutil.LoginUser %d %s '%s'>" % (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 "<steamutil.Steam @ \"%s\">" % 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)

@ -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()

Loading…
Cancel
Save