mirror of
https://github.com/beetbox/beets.git
synced 2025-12-06 00:24:25 +01:00
167 lines
5.7 KiB
Python
167 lines
5.7 KiB
Python
"""Adds a `source_path` attribute to imported albums indicating from what path
|
|
the album was imported from. Also suggests removing that source path in case
|
|
you've removed the album from the library.
|
|
|
|
"""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
from shutil import rmtree
|
|
|
|
from beets.dbcore.query import PathQuery
|
|
from beets.plugins import BeetsPlugin
|
|
from beets.ui import colorize as colorize_text
|
|
from beets.ui import input_options
|
|
|
|
|
|
class ImportSourcePlugin(BeetsPlugin):
|
|
"""Main plugin class."""
|
|
|
|
def __init__(self):
|
|
"""Initialize the plugin and read configuration."""
|
|
super().__init__()
|
|
self.config.add(
|
|
{
|
|
"suggest_removal": False,
|
|
}
|
|
)
|
|
self.import_stages = [self.import_stage]
|
|
self.register_listener("item_removed", self.suggest_removal)
|
|
# In order to stop future removal suggestions for an album we keep
|
|
# track of `mb_albumid`s in this set.
|
|
self.stop_suggestions_for_albums = set()
|
|
# During reimports (import --library) both the import_task_choice and
|
|
# the item_removed event are triggered. The item_removed event is
|
|
# triggered first. For the import_task_choice event we prevent removal
|
|
# suggestions using the existing stop_suggestions_for_album mechanism.
|
|
self.register_listener(
|
|
"import_task_choice", self.prevent_suggest_removal
|
|
)
|
|
|
|
def prevent_suggest_removal(self, session, task):
|
|
for item in task.imported_items():
|
|
if "mb_albumid" in item:
|
|
self.stop_suggestions_for_albums.add(item.mb_albumid)
|
|
|
|
def import_stage(self, _, task):
|
|
"""Event handler for albums import finished."""
|
|
for item in task.imported_items():
|
|
# During reimports (import --library), we prevent overwriting the
|
|
# source_path attribute with the path from the music library
|
|
if "source_path" in item:
|
|
self._log.info(
|
|
"Preserving source_path of reimported item {}", item.id
|
|
)
|
|
continue
|
|
item["source_path"] = item.path
|
|
item.try_sync(write=False, move=False)
|
|
|
|
def suggest_removal(self, item):
|
|
"""Prompts the user to delete the original path the item was imported from."""
|
|
if (
|
|
not self.config["suggest_removal"]
|
|
or item.mb_albumid in self.stop_suggestions_for_albums
|
|
):
|
|
return
|
|
|
|
if "source_path" not in item:
|
|
self._log.warning(
|
|
"Item without source_path (probably imported before plugin "
|
|
"usage): {}",
|
|
item.filepath,
|
|
)
|
|
return
|
|
|
|
srcpath = Path(os.fsdecode(item.source_path))
|
|
if not srcpath.is_file():
|
|
self._log.warning(
|
|
"Original source file no longer exists or is not accessible: {}",
|
|
srcpath,
|
|
)
|
|
return
|
|
|
|
if not (
|
|
os.access(srcpath, os.W_OK)
|
|
and os.access(srcpath.parent, os.W_OK | os.X_OK)
|
|
):
|
|
self._log.warning(
|
|
"Original source file cannot be deleted (insufficient permissions): {}",
|
|
srcpath,
|
|
)
|
|
return
|
|
|
|
# We ask the user whether they'd like to delete the item's source
|
|
# directory
|
|
item_path = colorize_text("text_warning", item.filepath)
|
|
source_path = colorize_text("text_warning", srcpath)
|
|
|
|
print(
|
|
f"The item:\n{item_path}\nis originated from:\n{source_path}\n"
|
|
"What would you like to do?"
|
|
)
|
|
|
|
resp = input_options(
|
|
[
|
|
"Delete the item's source",
|
|
"Recursively delete the source's directory",
|
|
"do Nothing",
|
|
"do nothing and Stop suggesting to delete items from this album",
|
|
],
|
|
require=True,
|
|
)
|
|
|
|
# Handle user response
|
|
if resp == "d":
|
|
self._log.info(
|
|
"Deleting the item's source file: {}",
|
|
srcpath,
|
|
)
|
|
srcpath.unlink()
|
|
|
|
elif resp == "r":
|
|
self._log.info(
|
|
"Searching for other items with a source_path attr containing: {}",
|
|
srcpath.parent,
|
|
)
|
|
|
|
source_dir_query = PathQuery(
|
|
"source_path",
|
|
srcpath.parent,
|
|
# The "source_path" attribute may not be present in all
|
|
# items of the library, so we avoid errors with this:
|
|
fast=False,
|
|
)
|
|
|
|
print("Doing so will delete the following items' sources as well:")
|
|
for searched_item in item._db.items(source_dir_query):
|
|
print(colorize_text("text_warning", searched_item.filepath))
|
|
|
|
print("Would you like to continue?")
|
|
continue_resp = input_options(
|
|
["Yes", "delete None", "delete just the File"],
|
|
require=False, # Yes is the a default
|
|
)
|
|
|
|
if continue_resp == "y":
|
|
self._log.info(
|
|
"Deleting the item's source directory: {}",
|
|
srcpath.parent,
|
|
)
|
|
rmtree(srcpath.parent)
|
|
|
|
elif continue_resp == "n":
|
|
self._log.info("doing nothing - aborting hook function")
|
|
return
|
|
|
|
elif continue_resp == "f":
|
|
self._log.info(
|
|
"removing just the item's original source: {}",
|
|
srcpath,
|
|
)
|
|
srcpath.unlink()
|
|
|
|
elif resp == "s":
|
|
self.stop_suggestions_for_albums.add(item.mb_albumid)
|
|
|
|
else:
|
|
self._log.info("Doing nothing")
|