import pathlib
import _nx
from . import users
SAVEDATA_BASE_PATH = 'save:/'
ROMFS_BASE_PATH = 'romfs:/'
mounted_romfs = None
mounted_savedata = None
[docs]class FileSystem:
"""Represents a filesystem.
Attributes
----------
base_path: pathlib.Path
The base path of the filesystem."""
def __init__(self, base_path: str):
self.base_path = pathlib.Path(base_path)
[docs] def open(self, file_path: str, mode='r', buffering=-1, encoding=None,
errors=None, newline=None):
"""Opens a file given a file path and returns a file-like object.
Apart from the ``file_path: str`` parameter, this method works
the same way as ``pathlib.Path.open``, thus it works pretty much
the same way as the ``open`` function.
"""
return self.base_path.joinpath(file_path).open(mode=mode, buffering=buffering,
encoding=encoding, errors=errors,
newline=newline)
[docs]class MountableFileSystem(FileSystem):
"""Represents a filesystem that is able to be mounted.
Attributes
----------
base_path: pathlib.Path
The base path of the filesystem.
"""
def __init__(self, base_path):
super().__init__(base_path)
@property
def is_mounted(self):
"""Whether or not the filesystem is currently mounted."""
raise NotImplementedError
[docs] def open(self, file_path: str, mode='r', buffering=-1, encoding=None,
errors=None, newline=None):
"""Opens a file given a file path and returns a file-like object.
Apart from the ``file_path: str`` parameter, this method works
the same way as ``pathlib.Path.open``, thus it works pretty much
the same way as the ``open`` function.
"""
if not self.is_mounted:
self.mount()
return super().open(file_path, mode=mode, buffering=buffering, encoding=encoding,
errors=errors, newline=newline)
[docs] def mount(self):
"""Mounts the filesystem."""
raise NotImplementedError
[docs] def commit(self):
"""Commits the filesystem."""
raise NotImplementedError
[docs] def unmount(self):
"""Unmounts the filesystem."""
raise NotImplementedError
def __enter__(self):
self.mount()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.commit()
self.unmount()
[docs]class RomFS(MountableFileSystem):
"""Represents the data filesystem of a title.
Do not instantiate this. Rather, get a RomFS object via
``nx.titles[MY_TITLE_ID].romfs``.
Attributes
----------
title: :class:`Title`
The title this RomFS belongs to.
base_path: pathlib.Path
The base path of the RomFS.
"""
def __init__(self, title):
super().__init__(ROMFS_BASE_PATH)
self.title = title
@property
def is_mounted(self):
"""Whether the RomFS is mounted."""
return self is mounted_romfs
[docs] def mount(self):
"""Yet to be implemented. Mounts the RomFS."""
raise NotImplementedError # TODO: implement RomFS.mount
[docs] def unmount(self):
"""Unmounts the mounted RomFS."""
_nx.fsdev_unmount_device('romfs')
def __exit__(self, exc_type, exc_val, exc_tb):
self.unmount()
[docs]class Savedata(MountableFileSystem):
"""Represents the savedata filesystem of a title.
Do not instantiate this. Rather, get a Savedata object via
``nx.titles[MY_TITLE_ID].savedata``.
Attributes
----------
title: :class:`Title`
The title this Savedata belongs to.
base_path: pathlib.Path
The base path of the savedata filesystem.
"""
def __init__(self, title, user=None):
super().__init__(SAVEDATA_BASE_PATH)
self.title = title
self.user = user if user is not None else users.active_user
@property
def is_mounted(self):
"""Whether the savedata filesystem has been mounted."""
return self is mounted_savedata
[docs] def mount(self):
"""Mounts the savedata filesystem."""
if self.is_mounted:
return
if self.user is None:
raise RuntimeError("No active user, you need to launch and "
"close a game prior to launching HBL.")
_nx.fs_mount_savedata('save', self.title.id, self.user.id)
global mounted_savedata # TODO: consider not using globals
mounted_savedata = self
[docs] def commit(self):
"""Commits the savedata filesystem."""
_nx.fsdev_commit_device('save')
[docs] def unmount(self):
"""Unmounts the savedata filesystem."""
_nx.fsdev_unmount_device('save')