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

Modernize the code base #75

Merged
merged 5 commits into from
Dec 19, 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
7 changes: 4 additions & 3 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ jobs:
matrix:
python-version:
- "3.8" # minimum required
- "3.11" # latest
- "3.12-dev" # next
- "3.12" # latest
- "3.13-dev" # next

runs-on: ubuntu-latest
continue-on-error: ${{ matrix.python-version == '3.12-dev' }}
continue-on-error: ${{ matrix.python-version == '3.13-dev' }}

steps:
- uses: actions/checkout@v3
Expand All @@ -26,6 +26,7 @@ jobs:
- run: poetry run black --check .
- run: poetry run isort --check .
- run: poetry run flake8
- run: poetry run pyright --warnings
- run: poetry run pytest
- uses: coverallsapp/github-action@v2
if: github.event_name == 'pull_request' && matrix.python-version == '3.8'
Expand Down
77 changes: 43 additions & 34 deletions beetsplug/alternatives.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@
import threading
import traceback
from concurrent import futures
from enum import Enum
from typing import Iterator, List, Optional, Tuple

import beets
import six
from beets import art, util
from beets.library import Item, parse_query_string
from beets.plugins import BeetsPlugin
from beets.ui import Subcommand, UserError, decargs, get_path_formats, input_yn, print_
from beets.util import FilesystemError, bytestring_path, displayable_path, syspath

from beetsplug import convert
import beetsplug.convert as convert


def _remove(path, soft=True):
Expand Down Expand Up @@ -61,7 +62,7 @@ def update(self, lib, options):
def list_tracks(self, lib, options):
if options.format is not None:
(fmt,) = decargs([options.format])
beets.config[beets.library.Item._format_config_key].set(fmt)
beets.config[Item._format_config_key].set(fmt)

alt = self.alternative(options.name, lib)

Expand All @@ -78,6 +79,7 @@ def alternative(self, name, lib):

if conf["formats"].exists():
fmt = conf["formats"].as_str()
assert isinstance(fmt, str)
if fmt == "link":
return SymlinkView(self._log, name, lib, conf)
else:
Expand Down Expand Up @@ -144,13 +146,15 @@ def _get_all_options(self):
return []


class External(object):
class Action(Enum):
ADD = 1
REMOVE = 2
WRITE = 3
MOVE = 4
SYNC_ART = 5


class External(object):
def __init__(self, log, name, lib, config):
self._log = log
self.name = name
Expand Down Expand Up @@ -186,11 +190,11 @@ def item_change_actions(self, item, path, dest):
actions = []

if not util.samefile(path, dest):
actions.append(self.MOVE)
actions.append(Action.MOVE)

item_mtime_alt = os.path.getmtime(syspath(path))
if item_mtime_alt < os.path.getmtime(syspath(item.path)):
actions.append(self.WRITE)
actions.append(Action.WRITE)
album = item.get_album()

if album:
Expand All @@ -199,7 +203,7 @@ def item_change_actions(self, item, path, dest):
and os.path.isfile(syspath(album.artpath))
and (item_mtime_alt < os.path.getmtime(syspath(album.artpath)))
):
actions.append(self.SYNC_ART)
actions.append(Action.SYNC_ART)

return actions

Expand All @@ -211,13 +215,13 @@ def matched_item_action(self, item):
_, dest_ext = os.path.splitext(dest)
if not path_ext == dest_ext:
# formats config option changed
return (item, [self.REMOVE, self.ADD])
return (item, [Action.REMOVE, Action.ADD])
else:
return (item, self.item_change_actions(item, path, dest))
else:
return (item, [self.ADD])
return (item, [Action.ADD])

def items_actions(self):
def items_actions(self) -> Iterator[Tuple[Item, List[Action]]]:
matched_ids = set()
for album in self.lib.albums():
if self.query.match(album):
Expand All @@ -228,7 +232,7 @@ def items_actions(self):
if item.id in matched_ids or self.query.match(item):
yield self.matched_item_action(item)
elif self.get_path(item):
yield (item, [self.REMOVE])
yield (item, [Action.REMOVE])

def ask_create(self, create=None):
if not self.removable:
Expand All @@ -255,28 +259,29 @@ def update(self, create=None):
dest = self.destination(item)
path = self.get_path(item)
for action in actions:
if action == self.MOVE:
if action == Action.MOVE:
print_(
">{0} -> {1}".format(
displayable_path(path), displayable_path(dest)
)
)
util.mkdirall(dest)
util.move(path, dest)
assert path is not None
util.prune_dirs(os.path.dirname(path), root=self.directory)
self.set_path(item, dest)
item.store()
path = dest
elif action == self.WRITE:
elif action == Action.WRITE:
print_("*{0}".format(displayable_path(path)))
item.write(path=path)
elif action == self.SYNC_ART:
elif action == Action.SYNC_ART:
print_("~{0}".format(displayable_path(path)))
self.sync_art(item, path)
elif action == self.ADD:
elif action == Action.ADD:
print_("+{0}".format(displayable_path(dest)))
converter.submit(item)
elif action == self.REMOVE:
elif action == Action.REMOVE:
print_("-{0}".format(displayable_path(path)))
self.remove_item(item)
item.store()
Expand All @@ -286,20 +291,22 @@ def update(self, create=None):
item.store()
converter.shutdown()

def destination(self, item):
return item.destination(basedir=self.directory, path_formats=self.path_formats)
def destination(self, item: Item) -> bytes:
path = item.destination(basedir=self.directory, path_formats=self.path_formats)
assert isinstance(path, bytes)
return path

def set_path(self, item, path):
item[self.path_key] = six.text_type(path, "utf8")
def set_path(self, item, path: bytes):
item[self.path_key] = str(path, "utf8")

@staticmethod
def _get_path(item, path_key):
def _get_path(item, path_key) -> Optional[bytes]:
try:
return item[path_key].encode("utf8")
except KeyError:
return None

def get_path(self, item):
def get_path(self, item) -> Optional[bytes]:
return self._get_path(item, self.path_key)

def remove_item(self, item):
Expand Down Expand Up @@ -361,7 +368,7 @@ def _convert(item):

return Worker(_convert, self.max_workers)

def destination(self, item):
def destination(self, item: Item):
dest = super(ExternalConvert, self).destination(item)
if self.should_transcode(item):
return os.path.splitext(dest)[0] + b"." + self.ext
Expand All @@ -372,10 +379,12 @@ def should_transcode(self, item):
return item.format.lower() not in self.formats


class SymlinkView(External):
LINK_ABSOLUTE = 0
LINK_RELATIVE = 1
class SymlinkType(Enum):
ABSOLUTE = 0
RELATIVE = 1


class SymlinkView(External):
def parse_config(self, config):
if "query" not in config:
config["query"] = "" # This is a TrueQuery()
Expand All @@ -384,7 +393,7 @@ def parse_config(self, config):
config["link_type"] = "absolute"

self.relativelinks = config["link_type"].as_choice(
{"relative": self.LINK_RELATIVE, "absolute": self.LINK_ABSOLUTE}
{"relative": SymlinkType.RELATIVE, "absolute": SymlinkType.ABSOLUTE}
)

super(SymlinkView, self).parse_config(config)
Expand All @@ -397,10 +406,10 @@ def item_change_actions(self, item, path, dest):

if not path == dest:
# The path of the link itself changed
actions.append(self.MOVE)
actions.append(Action.MOVE)
elif not util.samefile(path, item.path):
# link target changed
actions.append(self.MOVE)
actions.append(Action.MOVE)

return actions

Expand All @@ -409,7 +418,7 @@ def update(self, create=None):
dest = self.destination(item)
path = self.get_path(item)
for action in actions:
if action == self.MOVE:
if action == Action.MOVE:
print_(
">{0} -> {1}".format(
displayable_path(path), displayable_path(dest)
Expand All @@ -418,11 +427,11 @@ def update(self, create=None):
self.remove_item(item)
self.create_symlink(item)
self.set_path(item, dest)
elif action == self.ADD:
elif action == Action.ADD:
print_("+{0}".format(displayable_path(dest)))
self.create_symlink(item)
self.set_path(item, dest)
elif action == self.REMOVE:
elif action == Action.REMOVE:
print_("-{0}".format(displayable_path(path)))
self.remove_item(item)
else:
Expand All @@ -434,7 +443,7 @@ def create_symlink(self, item):
util.mkdirall(dest)
link = (
os.path.relpath(item.path, os.path.dirname(dest))
if self.relativelinks == self.LINK_RELATIVE
if self.relativelinks == SymlinkType.RELATIVE
else item.path
)
util.link(link, dest)
Expand All @@ -445,7 +454,7 @@ def sync_art(self, item, path):


class Worker(futures.ThreadPoolExecutor):
def __init__(self, fn, max_workers):
def __init__(self, fn, max_workers: Optional[int]):
super(Worker, self).__init__(max_workers)
self._tasks = set()
self._fn = fn
Expand Down
Loading
Loading