Skip to content

Commit

Permalink
Change SMB Provider to use OS-level mounts (#603)
Browse files Browse the repository at this point in the history
wrap os-level mount commands for the smb provider instead of native python

---------

Co-authored-by: Marvin Schenkel <[email protected]>
  • Loading branch information
marcelveldt and MarvinSchenkel authored Apr 1, 2023
1 parent b93b179 commit 939c653
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 220 deletions.
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

0 comments on commit 939c653

Please sign in to comment.