Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change SMB Provider to use OS-level mounts #603

Merged
merged 7 commits into from
Apr 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ RUN set -x \
libsox-fmt-all \
libsox3 \
sox \
cifs-utils \
# cleanup
&& rm -rf /tmp/* \
&& rm -rf /var/lib/apt/lists/*
Expand Down
5 changes: 5 additions & 0 deletions docker-compose.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@ services:
network_mode: host
volumes:
- ${USERDIR:-$HOME}/docker/music-assistant-server/data:/data/
# privileged caps needed to mount smb folders within the container
cap_add:
- SYS_ADMIN
- DAC_READ_SEARCH
privileged: true
27 changes: 16 additions & 11 deletions music_assistant/server/providers/filesystem_local/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,16 @@
isdir = wrap(os.path.isdir)
isfile = wrap(os.path.isfile)
exists = wrap(os.path.exists)
makedirs = wrap(os.makedirs)


async def setup(
mass: MusicAssistant, manifest: ProviderManifest, config: ProviderConfig
) -> ProviderInstanceType:
"""Initialize provider(instance) with given configuration."""
conf_path = config.get_value(CONF_PATH)
if not await isdir(conf_path):
raise SetupFailedError(f"Music Directory {conf_path} does not exist")
prov = LocalFileSystemProvider(mass, manifest, config)
await prov.handle_setup()
return prov
Expand Down Expand Up @@ -80,11 +84,11 @@ def _create_item():
class LocalFileSystemProvider(FileSystemProviderBase):
"""Implementation of a musicprovider for local files."""

base_path: str

async def handle_setup(self) -> None:
"""Handle async initialization of the provider."""
conf_path = self.config.get_value(CONF_PATH)
if not await isdir(conf_path):
raise SetupFailedError(f"Music Directory {conf_path} does not exist")
self.base_path = self.config.get_value(CONF_PATH)

async def listdir(
self, path: str, recursive: bool = False
Expand All @@ -102,14 +106,15 @@ async def listdir(
AsyncGenerator yielding FileSystemItem objects.

"""
abs_path = get_absolute_path(self.config.get_value(CONF_PATH), path)
self.logger.debug("Processing: %s", abs_path)
abs_path = get_absolute_path(self.base_path, path)
rel_path = get_relative_path(self.base_path, path)
self.logger.debug("Processing: %s", rel_path)
entries = await asyncio.to_thread(os.scandir, abs_path)
for entry in entries:
if entry.name.startswith(".") or any(x in entry.name for x in IGNORE_DIRS):
# skip invalid/system files and dirs
continue
item = await create_item(self.config.get_value(CONF_PATH), entry)
item = await create_item(self.base_path, entry)
if recursive and item.is_dir:
try:
async for subitem in self.listdir(item.absolute_path, True):
Expand All @@ -127,13 +132,13 @@ async def resolve(
If require_local is True, we prefer to have the `local_path` attribute filled
(e.g. with a tempfile), if supported by the provider/item.
"""
absolute_path = get_absolute_path(self.config.get_value(CONF_PATH), file_path)
absolute_path = get_absolute_path(self.base_path, file_path)

def _create_item():
stat = os.stat(absolute_path, follow_symlinks=False)
return FileSystemItem(
name=os.path.basename(file_path),
path=get_relative_path(self.config.get_value(CONF_PATH), file_path),
path=get_relative_path(self.base_path, file_path),
absolute_path=absolute_path,
is_dir=os.path.isdir(absolute_path),
is_file=os.path.isfile(absolute_path),
Expand All @@ -150,12 +155,12 @@ async def exists(self, file_path: str) -> bool:
"""Return bool is this FileSystem musicprovider has given file/dir."""
if not file_path:
return False # guard
abs_path = get_absolute_path(self.config.get_value(CONF_PATH), file_path)
abs_path = get_absolute_path(self.base_path, file_path)
return await exists(abs_path)

async def read_file_content(self, file_path: str, seek: int = 0) -> AsyncGenerator[bytes, None]:
"""Yield (binary) contents of file in chunks of bytes."""
abs_path = get_absolute_path(self.config.get_value(CONF_PATH), file_path)
abs_path = get_absolute_path(self.base_path, file_path)
chunk_size = 512000
async with aiofiles.open(abs_path, "rb") as _file:
if seek:
Expand All @@ -169,6 +174,6 @@ async def read_file_content(self, file_path: str, seek: int = 0) -> AsyncGenerat

async def write_file_content(self, file_path: str, data: bytes) -> None:
"""Write entire file content as bytes (e.g. for playlists)."""
abs_path = get_absolute_path(self.config.get_value(CONF_PATH), file_path)
abs_path = get_absolute_path(self.base_path, file_path)
async with aiofiles.open(abs_path, "wb") as _file:
await _file.write(data)
Loading