3. Adapter API
Adapters are an interface between a collection of music data and metadata and the Sublime Music UI. An adapter exposes a Subsonic-like API to the UI layer, but can be backed by a variety of music stores including a Subsonic-compatible server, data on the local filesystem, or even an entirely different service.
This document is designed to help you understand the Adapter API so that you can
create your own custom adapters. This document is best read in conjunction with
the sublime_music.adapters.Adapter
documentation. This document is
meant as a guide to tell you a general order in which to implement things.
3.1. Terms
- Music Metadata
Metadata about a music collection. This includes things like song metadata, playlists, artists, albums, filesystem hierarchy, etc.
- Music Data
The actual data of a music file. This may be accessed in a variety of different ways including via a stream URL, or via the local filesystem.
- Music Source
A source of music metadata and music data. This is the most atomic entity that the user interacts with. It can be composed of one or two Adapters.
- Adapter
A module which exposes the Adapter API.
3.2. Creating Your Adapter Class
An adapter is composed of a single Python module. The adapter module can have
arbitrary code, and as many files/classes/functions/etc. as necessary, however
there must be one and only one class in the module which inherits from the
sublime_music.adapters.Adapter
class. Normally, a single file with a
single class should be enough to implement the entire adapter.
Warning
Your adapter cannot assume that it will be running on a single thread. Due to the nature of the GTK event loop, functions can be called from any thread at any time. It is critical that your adapter is thread-safe. Failure to make your adapter thread-safe will result in massive problems and undefined behavior.
After you’ve created the class, you will want to implement the following functions and properties first:
get_ui_info
: Returns asublime_music.adapters.UIInfo
with the info for the adapter.__init__
: Used to initialize your adapter. See thesublime_music.adapters.Adapter.__init__
documentation for the function signature of the__init__
function.ping_status
: Assuming that your adapter requires connection to the internet, this property needs to be implemented. (If your adapter doesn’t require connection to the internet, setsublime_music.adapters.Adapter.is_networked
toFalse
and ignore the rest of this bullet point.)This property will tell the UI whether or not the underlying server can be pinged.
Warning
This function is called a lot (probably too much?) so it must return a value instantly. Do not perform the actual network request in this function. Instead, use a periodic ping that updates a state variable that this function returns.
get_configuration_form
: This function should return aGtk.Box
that gets any inputs required from the user and uses the givenconfig_store
to store the configuration values.The
Gtk.Box
must expose a signal with the name"config-valid-changed"
which returns a single boolean value indicating whether or not the configuration is valid.If you don’t want to implement all of the GTK logic yourself, and just want a simple form, then you can use the
sublime_music.adapters.ConfigureServerForm
class to generate a form in a declarative manner.
Note
The sublime_music.adapters.Adapter
class is an Abstract Base Class and all required functions are annotated with the
@abstractmethod
decorator. This means that your adapter will fail to
instantiate if the abstract methods are not implemented.
3.2.1. Handling Configuration
For each configuration parameter you want to allow your adapter to accept, you must do the following:
Choose a name for your configuration parameter. The configuration parameter name must be unique within your adapter.
Add a new entry to the return value of your
sublime_music.adapters.Adapter.get_config_parameters
function with the key being the name from (1), and the value being asublime_music.adapters.ConfigParamDescriptor
. The order of the keys in the dictionary matters, since the UI uses that to determine the order in which the configuration parameters will be shown in the UI.Add any verifications that are necessary for your configuration parameter in your
sublime_music.adapters.Adapter.verify_configuration
function. If you parameter descriptor hasrequired = True
, then that parameter is guaranteed to appear in the configuration.The configuration parameter will be passed into your
sublime_music.adapters.Adapter.init
function. It is guaranteed that theverify_configuration
will have been called first, so there is no need to re-verify the config that is passed.
3.2.2. Implementing Data Retrieval Methods
After you’ve done the initial configuration of your adapter class, you will want to implement the actual adapter data retrieval functions.
For each data retrieval function there is a corresponding can_
-prefixed
property (CPP) which will be used by the UI to determine if the data retrieval
function can be called. If the CPP is False
, the UI will never call the
corresponding function (and if it does, it’s a UI bug). The CPP can be dynamic,
for example, if your adapter supports many API versions, some of the CPPs may
depend on the API version. However, CPPs should not be dependent on connection
status (there are times where the user may want to force a connection retry,
even if the most recent ping failed).
Here is an example of what a get_playlists
interface for an external server
might look:
can_get_playlists = True
def get_playlists(self) -> List[Playlist]:
return my_server.get_playlists()
can_get_playlist_details = True
def get_playlist_details(self, playlist_id: str) -> PlaylistDetails:
return my_server.get_playlist(playlist_id)
Tip
By default, all can_
-prefixed properties are False
, which means that
you can implement them one-by-one, testing as you go. The UI should
dynamically enable features as new can_
-prefixed properties become
True
.*
* At the moment, this isn’t really the case and the UI just kinda explodes if it doesn’t have some of the functions available, but in the future, guards will be added around all of the function calls.
3.2.3. Usage Parameters
There are a few special properties dictate how the adapter can be used. You probably do not need to use this except for very specific purposes. Read the “Usage Parameters” section of the source code for details.