diff --git a/docs/api/steam.client.cdn.rst b/docs/api/steam.client.cdn.rst index aaa8d95..a6982d6 100644 --- a/docs/api/steam.client.cdn.rst +++ b/docs/api/steam.client.cdn.rst @@ -3,5 +3,8 @@ cdn .. automodule:: steam.client.cdn :members: + :member-order: alphabetical + :undoc-members: + :inherited-members: :show-inheritance: diff --git a/docs/api/steam.client.rst b/docs/api/steam.client.rst index 641ffe2..61db034 100644 --- a/docs/api/steam.client.rst +++ b/docs/api/steam.client.rst @@ -10,6 +10,7 @@ client .. toctree:: steam.client.builtins + steam.client.cdn steam.client.gc steam.client.user diff --git a/docs/api/steam.core.rst b/docs/api/steam.core.rst index 5a2286a..1f04a8b 100644 --- a/docs/api/steam.core.rst +++ b/docs/api/steam.core.rst @@ -11,4 +11,5 @@ core steam.core.cm steam.core.connection steam.core.crypto + steam.core.manifest steam.core.msg diff --git a/requirements.txt b/requirements.txt index def0e70..d72929a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,5 +12,6 @@ pytest-cov==2.5.1 mock==1.3.0 PyYAML==5.1 vcrpy==1.7.4 -sphinx==1.3.5 +sphinx==1.8.5 +sphinx_rtd_theme cachetools>=3.0.0 diff --git a/steam/client/cdn.py b/steam/client/cdn.py index d3225d7..4d87d60 100644 --- a/steam/client/cdn.py +++ b/steam/client/cdn.py @@ -1,3 +1,85 @@ +""" +Initializing :class:`.CDNClient` requires a logged in :class:`.SteamClient` instance + +.. code:: python + + mysteam = SteamClient() + ... + + mycdn = CDNClient(mysteam) + + +Getting depot manifests for an app + +.. code:: python + + >>> mycdn.get_manifests(570) + [, + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ] + + >>> mycdn.get_manifests(570, filter_func=lambda depot_id, info: 'Dota 2 Content' in info['name']) + [, + , + , + , + , + ] + + +Listing files + +.. code:: python + + >>> file_list = mycdn.iter_files(570) + >>> list(file_list)[:10] + [, + , + , + , + , + , + , + , + , + + +Reading a file directly from SteamPipe + +.. code:: python + + >>> file_list = mycdn.iter_files(570, r'game\dota\gameinfo.gi') + >>> myfile = next(file_list) + + >>> print(myfile.read(80).decode('utf-8')) + "GameInfo" + { + game "Dota 2" + title "Dota 2" + + gamelogo 1 + type multiplayer_only + ... + +""" from zipfile import ZipFile from io import BytesIO @@ -26,9 +108,33 @@ except ImportError: from backports import lzma def decrypt_manifest_gid_2(encrypted_gid, password): + """Decrypt manifest gid v2 bytes + + :param encrypted_gid: encrypted gid v2 bytes + :type encrypted_gid: bytes + :param password: encryption password + :type password: byt + :return: manifest gid + :rtype: int + """ return struct.unpack('= self.size or self.size == 0: @@ -530,6 +779,11 @@ class CDNDepotFile(DepotFile): return data def readline(self): + """Read a single line + + :return: single file line + :rtype: bytes + """ buf = b'' for chunk in iter(lambda: self.read(256), b''): @@ -545,4 +799,9 @@ class CDNDepotFile(DepotFile): return buf def readlines(self): + """Get file contents as list of lines + + :return: list of lines + :rtype: :class:`list` [:class:`bytes`] + """ return [line for line in self] diff --git a/steam/core/manifest.py b/steam/core/manifest.py index a42b2dc..591e1ad 100644 --- a/steam/core/manifest.py +++ b/steam/core/manifest.py @@ -53,22 +53,27 @@ class DepotManifest(object): @property def depot_id(self): + """:type: int""" return self.metadata.depot_id @property def gid(self): + """:type: int""" return self.metadata.gid_manifest @property def creation_time(self): + """:type: int""" return self.metadata.creation_time @property def size_original(self): + """:type: int""" return self.metadata.cb_disk_original @property def size_compressed(self): + """:type: int""" return self.metadata.cb_disk_compressed def decrypt_filenames(self, depot_key): @@ -176,7 +181,7 @@ class DepotManifest(object): def iter_files(self, pattern=None): """ - :param pattern: unix shell wildcard pattern, see :module:`.fnmatch` + :param pattern: unix shell wildcard pattern, see :func:`.fnmatch` :type pattern: str """ for mapping in self.payload.mappings: @@ -192,6 +197,7 @@ class DepotManifest(object): class DepotFile(object): def __init__(self, manifest, file_mapping): """Depot file + :param manifest: depot manifest :type manifest: :class:`.DepotManifest` :param file_mapping: depot file mapping instance @@ -215,53 +221,56 @@ class DepotFile(object): ) @property - def filename(self): - """ - :returns: Filename with null terminator and whitespaces removed - :rtype: str + def filename_raw(self): + """Filename with null terminator and whitespaces removed + + :type: str """ return self.file_mapping.filename.rstrip('\x00 \n\t') @property - def filename_norm(self): - """ - :return: Return current OS compatible path - :rtype: str + def filename(self): + """Filename matching the OS + + :type: str """ - return os.path.join(*self.filename.split('\\')) + return os.path.join(*self.filename_raw.split('\\')) @property def size(self): - """ - :return: file size in bytes - :rtype: int + """File size in bytes + + :type: int """ return self.file_mapping.size @property def chunks(self): - """ - :return: file size in bytes - :rtype: int + """File chunks instances + + :type: :class:`list` [ContentManifestPayload.FileMapping.ChunkData] """ return self.file_mapping.chunks @property def flags(self): - """ - :returns: file flags - :rtype: :class:`.EDepotFileFlag` + """File flags + + :type: :class:`.EDepotFileFlag` """ return self.file_mapping.flags @property def is_directory(self): + """:type: bool""" return self.flags & EDepotFileFlag.Directory > 0 @property def is_symlink(self): + """:type: bool""" return not not self.file_mapping.linktarget @property def is_file(self): + """:type: bool""" return not self.is_directory and not self.is_symlink