Skip to content

Commit

Permalink
(archives) added filesystem utility methods
Browse files Browse the repository at this point in the history
  • Loading branch information
snake-biscuits committed Nov 5, 2024
1 parent 5d3ba54 commit 7a22fab
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 16 deletions.
54 changes: 45 additions & 9 deletions bsp_tool/archives/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,17 @@
from typing import List, Tuple


def path_tuple(path: str) -> Tuple[str]:
out = tuple(path.replace("\\", "/").strip("/").split("/"))
if len(out) > 1 and out[0] == ".":
return out[1:]
else:
return out


class Archive:
ext = None
# NOTE: we assume namelist only contains filenames, no folders

def extract(self, filename, to_path=None):
if filename not in self.namelist():
Expand All @@ -25,29 +34,56 @@ def extract_all_matching(self, pattern="*.bsp", to_path=None, case_sensitive=Fal
for filename in self.search(pattern, case_sensitive):
self.extract(filename, to_path)

def is_dir(self, filename: str) -> bool:
all_dirs = {path_tuple(fn)[:-1] for fn in self.namelist()}
all_dirs.update({tuple_[:i] for tuple_ in all_dirs for i in range(1, len(tuple_))})
all_dirs.update({path_tuple(root) for root in (".", "./", "/")})
return path_tuple(filename) in all_dirs

def is_file(self, filename: str) -> bool:
return filename in self.namelist()

def listdir(self, folder: str) -> List[str]:
def path_tuple(path: str) -> Tuple[str]:
return tuple(path.replace("\\", "/").strip("/").split("/"))
if folder.replace("\\", "/") in "./":
return self.namelist()
if not self.is_dir(folder):
raise FileNotFoundError(f"no such directory: {folder}")
folder_tuple = path_tuple(folder)
if folder_tuple in {path_tuple(root) for root in (".", "./", "/")}:
folder_tuple = tuple() # empty
namelist_tuples = map(path_tuple, self.namelist())
return [tuple_[-1] for tuple_ in namelist_tuples if tuple_[:-1] == folder_tuple]

# TODO: isdir, isfile & tree methods for exploring files
folder_contents = list()
for tuple_ in namelist_tuples:
if tuple_[:-1] == folder_tuple:
folder_contents.append(tuple_[-1]) # file
elif tuple_[:len(folder_tuple)] == folder_tuple:
subfolder = tuple_[len(folder_tuple)] + "/"
if subfolder not in folder_contents:
folder_contents.append(subfolder)
return folder_contents

def namelist(self) -> List[str]:
raise NotImplementedError("ArchiveClass has not defined .namelist()")

def read(self) -> bytes:
def path_exists(self, filename: str) -> bool:
return self.is_file(filename) or self.is_dir(filename)

def read(self, filename: str) -> bytes:
raise NotImplementedError("ArchiveClass has not defined .read()")

def search(self, pattern="*.bsp", case_sensitive=False):
pattern = pattern if case_sensitive else pattern.lower()
namelist = self.namelist() if case_sensitive else [fn.lower() for fn in self.namelist()]
return fnmatch.filter(namelist, pattern)

# TODO: mount & unmount methods for attaching "external files" to ArchiveClass
def tree(self, folder: str = ".", depth: int = 0):
"""namelist pretty printer"""
for filename in self.listdir(folder):
print(f"{' ' * depth}{filename}")
full_filename = os.path.join(folder, filename)
if self.is_dir(full_filename):
self.tree(full_filename, depth + 1)

# TODO: mount_file & unmount_file for external files
# -- .gdi tracks, pak_000.vpk etc.

@classmethod
def from_archive(cls, parent_archive: Archive, filename: str) -> Archive:
Expand Down
7 changes: 0 additions & 7 deletions bsp_tool/archives/cdrom.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,13 +425,6 @@ def seek(self, lba: int) -> int:
true_lba = lba + self.lba_offset
return self.disc.seek(true_lba * self.pvd.block_size)

def tree(self, head=1, depth=0):
# NOTE: folders only
for path in self.path_table[1:]:
if path.parent_index == head:
print(f"{' ' * depth}{path.name}/")
self.tree(self.path_table.index(path) + 1, depth + 1)

@classmethod
def from_bytes(cls, raw_iso: bytes, pvd_address: int = None, lba_offset: int = 0) -> Iso:
# NOTE: only from_bytes will search for PVDs
Expand Down
13 changes: 13 additions & 0 deletions tests/archives/id_software/test_Pak.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,16 @@ def test_from_file(filename: str):
if len(namelist) != 0:
first_file = pak.read(namelist[0])
assert isinstance(first_file, bytes), ".read() failed"


# TODO: filesystem utility tests on "Steam | Quake | PAK0.PAK"
# -- pak.is_dir("sound/")
# -- pak.is_dir("sound/ambience/")

# -- pak.is_dir(".")
# -- pak.is_dir("./")
# -- pak.is_dir("./sound/")
# -- pak.is_dir("./sound/ambience/")
# TODO: is_file
# TODO: path_exists
# TODO: tree

0 comments on commit 7a22fab

Please sign in to comment.