Skip to content

Commit 45fa856

Browse files
authored
Merge pull request #14 from MAK-Relic-Tool/bugfix-and-cli-help
Bugfix and new `info` cli
2 parents 38920b9 + 0c7597f commit 45fa856

7 files changed

Lines changed: 158 additions & 12 deletions

File tree

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ relic.cli.sga =
3939
unpack = relic.sga.core.cli:RelicSgaUnpackCli
4040
pack = relic.sga.core.cli:RelicSgaPackCli
4141
repack = relic.sga.core.cli:RelicSgaRepackCli
42+
info = relic.sga.core.cli:RelicSgaInfoCli
4243

4344
[options.packages.find]
4445
where = src

src/relic/sga/core/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
"""
44
from relic.sga.core.definitions import Version, MagicWord, StorageType, VerificationType
55

6-
__version__ = "1.1.3"
6+
__version__ = "1.1.4"

src/relic/sga/core/cli.py

Lines changed: 147 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
from __future__ import annotations
22

33
import argparse
4+
import datetime
45
import os.path
56
from argparse import ArgumentParser, Namespace
6-
from typing import Optional, Callable
7+
from typing import Optional, Callable, Dict, List, Any, Tuple, Set, TextIO
78

89
import fs.copy
910
from fs.base import FS
11+
from fs.multifs import MultiFS
1012
from relic.core.cli import CliPluginGroup, _SubParsersAction, CliPlugin
1113

14+
from relic.sga.core.definitions import StorageType
15+
from relic.sga.core.filesystem import EssenceFS, _EssenceDriveFS
16+
1217

1318
class RelicSgaCli(CliPluginGroup):
1419
GROUP = "relic.cli.sga"
@@ -130,3 +135,144 @@ def _create_parser(
130135
# pack further delegates to version plugins
131136

132137
return parser
138+
139+
140+
class RelicSgaInfoCli(CliPlugin):
141+
def _create_parser(
142+
self, command_group: Optional[_SubParsersAction] = None
143+
) -> ArgumentParser:
144+
parser: ArgumentParser
145+
description = "Dumps metadata packed into an SGA file."
146+
if command_group is None:
147+
parser = ArgumentParser("info", description=description)
148+
else:
149+
parser = command_group.add_parser("info", description=description)
150+
151+
parser.add_argument(
152+
"sga",
153+
type=_get_file_type_validator(exists=True),
154+
help="SGA File to inspect",
155+
)
156+
parser.add_argument(
157+
"log_file",
158+
nargs="?",
159+
type=_get_file_type_validator(exists=False),
160+
help="Optional file to write messages to, required if `-q/--quiet` is used",
161+
default=None,
162+
)
163+
parser.add_argument(
164+
"-q",
165+
"--quiet",
166+
action="store_true",
167+
default=False,
168+
help="When specified, SGA info is not printed to the console",
169+
)
170+
return parser
171+
172+
def command(self, ns: Namespace) -> Optional[int]:
173+
sga: str = ns.sga
174+
log_file: str = ns.log_file
175+
quiet: bool = ns.quiet
176+
177+
logger: Optional[TextIO] = None
178+
try:
179+
if log_file is not None:
180+
logger = open(log_file, "w")
181+
182+
outputs: List[Optional[TextIO]] = []
183+
if quiet is False:
184+
outputs.append(None) # None is a sentinel for stdout
185+
if logger is not None:
186+
outputs.append(logger)
187+
188+
if len(outputs) == 0:
189+
print(
190+
"Please specify a `log_file` if using the `-q` or `--quiet` command"
191+
)
192+
return 1
193+
194+
def _print(
195+
*msg: str, sep: Optional[str] = None, end: Optional[str] = None
196+
) -> None:
197+
for output in outputs:
198+
print(*msg, sep=sep, end=end, file=output)
199+
200+
def _is_container(d: Any) -> bool:
201+
return isinstance(d, (Dict, List, Tuple, Set)) # type: ignore
202+
203+
def _stringify(d: Any, indent: int = 0) -> None:
204+
_TAB = "\t"
205+
if isinstance(d, Dict):
206+
for k, v in d.items():
207+
if _is_container(v):
208+
_print(f"{_TAB * indent}{k}:")
209+
_stringify(v, indent + 1)
210+
else:
211+
_print(f"{_TAB * indent}{k}: {v}")
212+
elif isinstance(d, (List, Tuple, Set)): # type: ignore
213+
_print(f"{_TAB * indent}{', '.join(*d)}")
214+
else:
215+
_print(f"{_TAB * indent}{d}")
216+
217+
def _getessence(fs: FS, path: str = "/") -> Dict[str, Any]:
218+
return fs.getinfo(path, "essence").raw.get("essence", {}) # type: ignore
219+
220+
_print(f"File: `{sga}`")
221+
sgafs: EssenceFS
222+
with fs.open_fs(f"sga://{sga}") as sgafs: # type: ignore
223+
_print("Archive Metadata:")
224+
_stringify(sgafs.getmeta("essence"), indent=1)
225+
226+
drive: _EssenceDriveFS
227+
for alias, drive in sgafs.iterate_fs(): # type: ignore
228+
_print(f"Drive: `{drive.name}` (`{drive.alias}`)")
229+
_print("\tDrive Metadata:")
230+
info = _getessence(drive)
231+
if len(info) > 0:
232+
_stringify(info, indent=2)
233+
else:
234+
_print(f"\t\tNo Metadata")
235+
236+
_print("\tDrive Files Metadata:")
237+
for f in drive.walk.files():
238+
_print(f"\t\t`{f}`:")
239+
finfo: Dict[str, Any] = _getessence(drive, f)
240+
finfo = finfo.copy()
241+
# We alter storage_type cause it *should* always be present, if its not, we dont do anything
242+
key = "storage_type"
243+
if key in finfo:
244+
stv: int = finfo[key]
245+
st: StorageType = StorageType(stv)
246+
finfo[key] = f"{stv} ({st.name})"
247+
248+
# We alter modified too, cause when it is present, its garbage
249+
key = "modified"
250+
if key in finfo:
251+
mtv: int = finfo[key]
252+
mt = datetime.datetime.fromtimestamp(
253+
mtv, datetime.timezone.utc
254+
)
255+
finfo[key] = str(mt)
256+
257+
# And CRC32 if it's in bytes; this should be removed ASAP tho # I only put this in because its such a minor patch to V2
258+
key = "crc32"
259+
if key in finfo:
260+
crcv: bytes = finfo[key]
261+
if isinstance(crcv, bytes):
262+
crc32 = int.from_bytes(crcv, "little", signed=False)
263+
finfo[key] = crc32
264+
265+
if len(finfo) > 0:
266+
_stringify(finfo, indent=3)
267+
else:
268+
_print(f"\t\t\tNo Metadata")
269+
270+
finally:
271+
if logger is not None:
272+
logger.close()
273+
274+
if log_file is not None:
275+
print(
276+
f"Saved to `{os.path.join(os.getcwd(), log_file)}`"
277+
) # DO NOT USE _PRINT
278+
return None

src/relic/sga/core/filesystem.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -228,11 +228,7 @@ def __init__(self, resource_type: ResourceType, name: Text):
228228
def to_info(self, namespaces=None):
229229
# type: (Optional[Collection[Text]]) -> Info
230230
info = super().to_info(namespaces)
231-
if (
232-
namespaces is not None
233-
and not self.is_dir
234-
and ESSENCE_NAMESPACE in namespaces
235-
):
231+
if namespaces is not None and ESSENCE_NAMESPACE in namespaces:
236232
info_dict = dict(info.raw)
237233
info_dict[ESSENCE_NAMESPACE] = self.essence.copy()
238234
info = Info(info_dict)
@@ -302,8 +298,8 @@ def getinfo(
302298
if _path == "/" and (
303299
namespaces is not None and ESSENCE_NAMESPACE in namespaces
304300
):
305-
raw_info = info.raw
306-
essence_ns = dict(raw_info[ESSENCE_NAMESPACE])
301+
raw_info = dict(info.raw)
302+
essence_ns = raw_info[ESSENCE_NAMESPACE] = {}
307303
essence_ns["alias"] = self.alias
308304
essence_ns["name"] = self.name
309305
info = Info(raw_info)

src/relic/sga/core/serialization.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,9 @@ def flatten_file_collection(self, container_fs: FS) -> Tuple[int, int]:
489489
]
490490
self.flat_files.extend(subfile_defs)
491491
subfile_end = len(self.flat_files)
492+
493+
if subfile_start == subfile_end:
494+
subfile_start = subfile_end = 0 #
492495
return subfile_start, subfile_end
493496

494497
def flatten_folder_collection(self, container_fs: FS, path: str) -> Tuple[int, int]:

tests/regressions/test_version_comparisons.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
# Chunky versions start at 1
88
# Max i've seen is probably 16ish?
99
# Minor has a lot mor variety than Chunky; so we test a bit more
10-
_VERSION_MAJORS = range(1,11) # So far we only go up to V10
11-
_VERSION_MINORS = [0,1] # Allegedly CoHO was v4.1 so... we do 0,1
10+
_VERSION_MAJORS = range(1, 11) # So far we only go up to V10
11+
_VERSION_MINORS = [0, 1] # Allegedly CoHO was v4.1 so... we do 0,1
1212

1313

1414
_VERSION_ARGS = list(itertools.product(_VERSION_MAJORS, _VERSION_MINORS))

tests/test_cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def test_run_with(self, args: Sequence[str], output: str, exit_code: int):
3131
assert status == exit_code
3232

3333

34-
_SGA_HELP = ["sga", "-h"], """usage: relic sga [-h] {pack,repack,unpack} ...""", 0
34+
_SGA_HELP = ["sga", "-h"], """usage: relic sga [-h] {info,pack,repack,unpack} ...""", 0
3535
_SGA_PACK_HELP = ["sga", "pack", "-h"], """usage: relic sga pack [-h] {} ...""", 0
3636
_SGA_UNPACK_HELP = ["sga", "unpack", "-h"], """usage: relic sga unpack [-h]""", 0
3737

0 commit comments

Comments
 (0)