diff --git a/beets/dbcore/db.py b/beets/dbcore/db.py index 43c044572..97a4a7ce3 100755 --- a/beets/dbcore/db.py +++ b/beets/dbcore/db.py @@ -850,16 +850,21 @@ class Database(object): """A container for Model objects that wraps an SQLite database as the backend. """ + _models = () """The Model subclasses representing tables in this database. """ + supports_extensions = hasattr(sqlite3.Connection, 'enable_load_extension') + """Whether or not the current version of SQLite supports extensions""" + def __init__(self, path, timeout=5.0): self.path = path self.timeout = timeout self._connections = {} self._tx_stacks = defaultdict(list) + self._extensions = [] # A lock to protect the _connections and _tx_stacks maps, which # both map thread IDs to private resources. @@ -909,6 +914,13 @@ class Database(object): py3_path(self.path), timeout=self.timeout ) + if self.supports_extensions: + conn.enable_load_extension(True) + + # Load any extension that are already loaded for other connections. + for path in self._extensions: + conn.load_extension(path) + # Access SELECT results like dictionaries. conn.row_factory = sqlite3.Row return conn @@ -937,6 +949,18 @@ class Database(object): """ return Transaction(self) + def load_extension(self, path): + """Load an SQLite extension into all open connections.""" + if not self.supports_extensions: + raise ValueError( + 'this sqlite3 installation does not support extensions') + + self._extensions.append(path) + + # Load the extension into every open connection. + for conn in self._connections.values(): + conn.load_extension(path) + # Schema setup and migration. def _make_table(self, table, fields): diff --git a/beetsplug/loadext.py b/beetsplug/loadext.py new file mode 100644 index 000000000..5ab98bd59 --- /dev/null +++ b/beetsplug/loadext.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# This file is part of beets. +# Copyright 2019, Jack Wilsdon +# +# 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. + +"""Load SQLite extensions. +""" + +from __future__ import division, absolute_import, print_function + +from beets.dbcore import Database +from beets.plugins import BeetsPlugin +import sqlite3 + + +class LoadExtPlugin(BeetsPlugin): + def __init__(self): + super(LoadExtPlugin, self).__init__() + + if not Database.supports_extensions: + self._log.warn('loadext is enabled but the current SQLite ' + 'installation does not support extensions') + return + + self.register_listener('library_opened', self.library_opened) + + def library_opened(self, lib): + for v in self.config: + ext = v.as_filename() + + self._log.debug(u'loading extension {}', ext) + + try: + lib.load_extension(ext) + except sqlite3.OperationalError as e: + self._log.error(u'failed to load extension {}: {}', ext, e) diff --git a/docs/changelog.rst b/docs/changelog.rst index ffe8bdcac..2ccfbadb5 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -94,6 +94,9 @@ New features: :bug:`3205` :bug:`800` * :doc:`/plugins/bpd`: MPD protocol command ``decoders`` is now supported. :bug:`3222` +* The new :doc:`/plugins/loadext` allows loading of SQLite extensions, primarily + for use with the ICU SQLite extension for internationalization. + :bug:`3160` :bug:`3226` Changes: diff --git a/docs/plugins/index.rst b/docs/plugins/index.rst index 173aab5db..e885db39b 100644 --- a/docs/plugins/index.rst +++ b/docs/plugins/index.rst @@ -71,6 +71,7 @@ like this:: kodiupdate lastgenre lastimport + loadext lyrics mbcollection mbsubmit @@ -189,6 +190,7 @@ Miscellaneous * :doc:`hook`: Run a command when an event is emitted by beets. * :doc:`ihate`: Automatically skip albums and tracks during the import process. * :doc:`info`: Print music files' tags to the console. +* :doc:`loadext`: Load SQLite extensions. * :doc:`mbcollection`: Maintain your MusicBrainz collection list. * :doc:`mbsubmit`: Print an album's tracks in a MusicBrainz-friendly format. * :doc:`missing`: List missing tracks. diff --git a/docs/plugins/loadext.rst b/docs/plugins/loadext.rst new file mode 100644 index 000000000..5acd10ec7 --- /dev/null +++ b/docs/plugins/loadext.rst @@ -0,0 +1,53 @@ +Load Extension Plugin +===================== + +Beets uses an SQLite database to store and query library information, which +has support for extensions to extend its functionality. The ``loadext`` plugin +lets you enable these SQLite extensions within beets. + +One of the primary uses of this within beets is with the `"ICU" extension`_, +which adds support for case insensitive querying of non-ASCII characters. + +.. _"ICU" extension: https://www.sqlite.org/src/dir?ci=7461d2e120f21493&name=ext/icu + +Configuration +------------- + +To configure the plugin, make a ``loadext`` section in your configuration +file. The section must consist of a list of paths to extensions to load, which +looks like this: + +.. code-block:: yaml + + loadext: + - libicu + +If a relative path is specified, it is resolved relative to the beets +configuration directory. + +If no file extension is specified, the default dynamic library extension for +the current platform will be used. + +Building the ICU extension +-------------------------- +This section is for **advanced** users only, and is not an in-depth guide on +building the extension. + +To compile the ICU extension, you will need a few dependencies: + + - gcc + - icu-devtools + - libicu + - libicu-dev + - libsqlite3-dev + +Here's roughly how to download, build and install the extension (although the +specifics may vary from system to system): + +.. code-block:: shell + + $ wget https://sqlite.org/2019/sqlite-src-3280000.zip + $ unzip sqlite-src-3280000.zip + $ cd sqlite-src-3280000/ext/icu + $ gcc -shared -fPIC icu.c `icu-config --ldflags` -o libicu.so + $ cp libicu.so ~/.config/beets