nzbToMedia/libs/common/beetsplug/ipfs.py
Labrys of Knossos 56c6773c6b Update vendored beets to 1.6.0
Updates colorama to 0.4.6
Adds confuse version 1.7.0
Updates jellyfish to 0.9.0
Adds mediafile 0.10.1
Updates munkres to 1.1.4
Updates musicbrainzngs to 0.7.1
Updates mutagen to 1.46.0
Updates pyyaml to 6.0
Updates unidecode to 1.3.6
2022-11-29 00:44:48 -05:00

296 lines
10 KiB
Python

# This file is part of beets.
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
"""Adds support for ipfs. Requires go-ipfs and a running ipfs daemon
"""
from beets import ui, util, library, config
from beets.plugins import BeetsPlugin
import subprocess
import shutil
import os
import tempfile
class IPFSPlugin(BeetsPlugin):
def __init__(self):
super().__init__()
self.config.add({
'auto': True,
'nocopy': False,
})
if self.config['auto']:
self.import_stages = [self.auto_add]
def commands(self):
cmd = ui.Subcommand('ipfs',
help='interact with ipfs')
cmd.parser.add_option('-a', '--add', dest='add',
action='store_true',
help='Add to ipfs')
cmd.parser.add_option('-g', '--get', dest='get',
action='store_true',
help='Get from ipfs')
cmd.parser.add_option('-p', '--publish', dest='publish',
action='store_true',
help='Publish local library to ipfs')
cmd.parser.add_option('-i', '--import', dest='_import',
action='store_true',
help='Import remote library from ipfs')
cmd.parser.add_option('-l', '--list', dest='_list',
action='store_true',
help='Query imported libraries')
cmd.parser.add_option('-m', '--play', dest='play',
action='store_true',
help='Play music from remote libraries')
def func(lib, opts, args):
if opts.add:
for album in lib.albums(ui.decargs(args)):
if len(album.items()) == 0:
self._log.info('{0} does not contain items, aborting',
album)
self.ipfs_add(album)
album.store()
if opts.get:
self.ipfs_get(lib, ui.decargs(args))
if opts.publish:
self.ipfs_publish(lib)
if opts._import:
self.ipfs_import(lib, ui.decargs(args))
if opts._list:
self.ipfs_list(lib, ui.decargs(args))
if opts.play:
self.ipfs_play(lib, opts, ui.decargs(args))
cmd.func = func
return [cmd]
def auto_add(self, session, task):
if task.is_album:
if self.ipfs_add(task.album):
task.album.store()
def ipfs_play(self, lib, opts, args):
from beetsplug.play import PlayPlugin
jlib = self.get_remote_lib(lib)
player = PlayPlugin()
config['play']['relative_to'] = None
player.album = True
player.play_music(jlib, player, args)
def ipfs_add(self, album):
try:
album_dir = album.item_dir()
except AttributeError:
return False
try:
if album.ipfs:
self._log.debug('{0} already added', album_dir)
# Already added to ipfs
return False
except AttributeError:
pass
self._log.info('Adding {0} to ipfs', album_dir)
if self.config['nocopy']:
cmd = "ipfs add --nocopy -q -r".split()
else:
cmd = "ipfs add -q -r".split()
cmd.append(album_dir)
try:
output = util.command_output(cmd).stdout.split()
except (OSError, subprocess.CalledProcessError) as exc:
self._log.error('Failed to add {0}, error: {1}', album_dir, exc)
return False
length = len(output)
for linenr, line in enumerate(output):
line = line.strip()
if linenr == length - 1:
# last printed line is the album hash
self._log.info("album: {0}", line)
album.ipfs = line
else:
try:
item = album.items()[linenr]
self._log.info("item: {0}", line)
item.ipfs = line
item.store()
except IndexError:
# if there's non music files in the to-add folder they'll
# get ignored here
pass
return True
def ipfs_get(self, lib, query):
query = query[0]
# Check if query is a hash
# TODO: generalize to other hashes; probably use a multihash
# implementation
if query.startswith("Qm") and len(query) == 46:
self.ipfs_get_from_hash(lib, query)
else:
albums = self.query(lib, query)
for album in albums:
self.ipfs_get_from_hash(lib, album.ipfs)
def ipfs_get_from_hash(self, lib, _hash):
try:
cmd = "ipfs get".split()
cmd.append(_hash)
util.command_output(cmd)
except (OSError, subprocess.CalledProcessError) as err:
self._log.error('Failed to get {0} from ipfs.\n{1}',
_hash, err.output)
return False
self._log.info('Getting {0} from ipfs', _hash)
imp = ui.commands.TerminalImportSession(lib, loghandler=None,
query=None, paths=[_hash])
imp.run()
shutil.rmtree(_hash)
def ipfs_publish(self, lib):
with tempfile.NamedTemporaryFile() as tmp:
self.ipfs_added_albums(lib, tmp.name)
try:
if self.config['nocopy']:
cmd = "ipfs add --nocopy -q ".split()
else:
cmd = "ipfs add -q ".split()
cmd.append(tmp.name)
output = util.command_output(cmd).stdout
except (OSError, subprocess.CalledProcessError) as err:
msg = f"Failed to publish library. Error: {err}"
self._log.error(msg)
return False
self._log.info("hash of library: {0}", output)
def ipfs_import(self, lib, args):
_hash = args[0]
if len(args) > 1:
lib_name = args[1]
else:
lib_name = _hash
lib_root = os.path.dirname(lib.path)
remote_libs = os.path.join(lib_root, b"remotes")
if not os.path.exists(remote_libs):
try:
os.makedirs(remote_libs)
except OSError as e:
msg = f"Could not create {remote_libs}. Error: {e}"
self._log.error(msg)
return False
path = os.path.join(remote_libs, lib_name.encode() + b".db")
if not os.path.exists(path):
cmd = f"ipfs get {_hash} -o".split()
cmd.append(path)
try:
util.command_output(cmd)
except (OSError, subprocess.CalledProcessError):
self._log.error(f"Could not import {_hash}")
return False
# add all albums from remotes into a combined library
jpath = os.path.join(remote_libs, b"joined.db")
jlib = library.Library(jpath)
nlib = library.Library(path)
for album in nlib.albums():
if not self.already_added(album, jlib):
new_album = []
for item in album.items():
item.id = None
new_album.append(item)
added_album = jlib.add_album(new_album)
added_album.ipfs = album.ipfs
added_album.store()
def already_added(self, check, jlib):
for jalbum in jlib.albums():
if jalbum.mb_albumid == check.mb_albumid:
return True
return False
def ipfs_list(self, lib, args):
fmt = config['format_album'].get()
try:
albums = self.query(lib, args)
except OSError:
ui.print_("No imported libraries yet.")
return
for album in albums:
ui.print_(format(album, fmt), " : ", album.ipfs.decode())
def query(self, lib, args):
rlib = self.get_remote_lib(lib)
albums = rlib.albums(args)
return albums
def get_remote_lib(self, lib):
lib_root = os.path.dirname(lib.path)
remote_libs = os.path.join(lib_root, b"remotes")
path = os.path.join(remote_libs, b"joined.db")
if not os.path.isfile(path):
raise OSError
return library.Library(path)
def ipfs_added_albums(self, rlib, tmpname):
""" Returns a new library with only albums/items added to ipfs
"""
tmplib = library.Library(tmpname)
for album in rlib.albums():
try:
if album.ipfs:
self.create_new_album(album, tmplib)
except AttributeError:
pass
return tmplib
def create_new_album(self, album, tmplib):
items = []
for item in album.items():
try:
if not item.ipfs:
break
except AttributeError:
pass
item_path = os.path.basename(item.path).decode(
util._fsencoding(), 'ignore'
)
# Clear current path from item
item.path = f'/ipfs/{album.ipfs}/{item_path}'
item.id = None
items.append(item)
if len(items) < 1:
return False
self._log.info("Adding '{0}' to temporary library", album)
new_album = tmplib.add_album(items)
new_album.ipfs = album.ipfs
new_album.store()