from __future__ import unicode_literals
import spotify
from spotify import ffi, utils
from spotify.session import _SessionCallbacks
__all__ = ["Config"]
[docs]class Config(object):
"""The session config.
Create an instance and assign to its attributes to configure. Then use the
config object to create a session::
>>> config = spotify.Config()
>>> config.user_agent = 'My awesome Spotify client'
>>> # Etc ...
>>> session = spotify.Session(config=config)
"""
def __init__(self):
self._sp_session_callbacks = _SessionCallbacks.get_struct()
self._sp_session_config = ffi.new(
"sp_session_config *", {"callbacks": self._sp_session_callbacks}
)
# Defaults
self.api_version = spotify.get_libspotify_api_version()
self.cache_location = b"tmp"
self.settings_location = b"tmp"
self.user_agent = "pyspotify %s" % spotify.__version__
self.compress_playlists = False
self.dont_save_metadata_for_playlists = False
self.initially_unload_playlists = False
self.device_id = None
self.proxy = None
self.proxy_username = None
self.proxy_password = None
self.ca_certs_filename = None
self.tracefile = None
@property
def api_version(self):
"""The API version of the libspotify we're using.
You should not need to change this. It defaults to the value provided
by libspotify through :func:`spotify.get_libspotify_api_version`.
"""
return self._sp_session_config.api_version
@api_version.setter
def api_version(self, value):
self._sp_session_config.api_version = value
@property
def cache_location(self):
"""A location for libspotify to cache files.
Defaults to ``tmp`` in the current working directory.
Must be a bytestring. Cannot be shared with other Spotify apps. Can
only be used by one session at the time. Optimally, you should use a
lock file or similar to ensure this.
"""
return utils.to_bytes(self._sp_session_config.cache_location)
@cache_location.setter
def cache_location(self, value):
# NOTE libspotify segfaults if cache_location is set to NULL, thus we
# convert None to empty string.
self._cache_location = utils.to_char("" if value is None else value)
self._sp_session_config.cache_location = self._cache_location
@property
def settings_location(self):
"""A location for libspotify to save settings.
Defaults to ``tmp`` in the current working directory.
Must be a bytestring. Cannot be shared with other Spotify apps. Can
only be used by one session at the time. Optimally, you should use a
lock file or similar to ensure this.
"""
return utils.to_bytes(self._sp_session_config.settings_location)
@settings_location.setter
def settings_location(self, value):
self._settings_location = utils.to_char("" if value is None else value)
self._sp_session_config.settings_location = self._settings_location
@property
def application_key(self):
"""Your libspotify application key.
Must be a bytestring. Alternatively, you can call
:meth:`load_application_key_file`, and pyspotify will correctly read
the file into :attr:`application_key`.
"""
return utils.to_bytes_or_none(
ffi.cast("char *", self._sp_session_config.application_key)
)
@application_key.setter
def application_key(self, value):
if value is None:
size = 0
else:
size = len(value)
assert size in (0, 321), (
"Invalid application key; expected 321 bytes, got %d bytes" % size
)
self._application_key = utils.to_char_or_null(value)
self._sp_session_config.application_key = ffi.cast(
"void *", self._application_key
)
self._sp_session_config.application_key_size = size
def load_application_key_file(self, filename=b"spotify_appkey.key"):
"""Load your libspotify application key file.
If called without arguments, it tries to read ``spotify_appkey.key``
from the current working directory.
This is an alternative to setting :attr:`application_key` yourself. The
file must be a binary key file, not the C code key file that can be
compiled into an application.
"""
with open(filename, "rb") as fh:
self.application_key = fh.read()
@property
def user_agent(self):
"""A string with the name of your client.
Defaults to ``pyspotify 2.x.y``.
"""
return utils.to_unicode(self._sp_session_config.user_agent)
@user_agent.setter
def user_agent(self, value):
self._user_agent = utils.to_char("" if value is None else value)
self._sp_session_config.user_agent = self._user_agent
@property
def compress_playlists(self):
"""Compress local copy of playlists, reduces disk space usage.
Defaults to :class:`False`.
"""
return bool(self._sp_session_config.compress_playlists)
@compress_playlists.setter
def compress_playlists(self, value):
self._sp_session_config.compress_playlists = bool(value)
@property
def dont_save_metadata_for_playlists(self):
"""Don't save metadata for local copies of playlists.
Defaults to :class:`False`.
Reduces disk space usage at the expense of needing to request metadata
from Spotify backend when loading list.
"""
return bool(self._sp_session_config.dont_save_metadata_for_playlists)
@dont_save_metadata_for_playlists.setter
def dont_save_metadata_for_playlists(self, value):
self._sp_session_config.dont_save_metadata_for_playlists = bool(value)
@property
def initially_unload_playlists(self):
"""Avoid loading playlists into RAM on startup.
Defaults to :class:`False`.
See :meth:`Playlist.in_ram` for more details.
"""
return bool(self._sp_session_config.initially_unload_playlists)
@initially_unload_playlists.setter
def initially_unload_playlists(self, value):
self._sp_session_config.initially_unload_playlists = bool(value)
@property
def device_id(self):
"""Device ID for offline synchronization and logging purposes.
Defaults to :class:`None`.
The Device ID must be unique to the particular device instance, i.e. no
two units must supply the same Device ID. The Device ID must not change
between sessions or power cycles. Good examples is the device's MAC
address or unique serial number.
Setting the device ID to an empty string has the same effect as setting
it to :class:`None`.
"""
return utils.to_unicode_or_none(self._sp_session_config.device_id)
@device_id.setter
def device_id(self, value):
# NOTE libspotify segfaults if device_id is set to an empty string,
# thus we convert empty strings to NULL.
self._device_id = utils.to_char_or_null(value or None)
self._sp_session_config.device_id = self._device_id
@property
def proxy(self):
"""URL to the proxy server that should be used.
Defaults to :class:`None`.
The format is protocol://host:port where protocol is
http/https/socks4/socks5.
"""
return utils.to_unicode(self._sp_session_config.proxy) or None
@proxy.setter
def proxy(self, value):
# NOTE libspotify reuses cached values from previous sessions if this
# is set to NULL, thus we convert None to empty string.
self._proxy = utils.to_char_or_null("" if value is None else value)
self._sp_session_config.proxy = self._proxy
@property
def proxy_username(self):
"""Username to authenticate with proxy server.
Defaults to :class:`None`.
"""
return utils.to_unicode(self._sp_session_config.proxy_username) or None
@proxy_username.setter
def proxy_username(self, value):
# NOTE libspotify reuses cached values from previous sessions if this
# is set to NULL, thus we convert None to empty string.
self._proxy_username = utils.to_char("" if value is None else value)
self._sp_session_config.proxy_username = self._proxy_username
@property
def proxy_password(self):
"""Password to authenticate with proxy server.
Defaults to :class:`None`.
"""
return utils.to_unicode(self._sp_session_config.proxy_password) or None
@proxy_password.setter
def proxy_password(self, value):
# NOTE libspotify reuses cached values from previous sessions if this
# is set to NULL, thus we convert None to empty string.
self._proxy_password = utils.to_char("" if value is None else value)
self._sp_session_config.proxy_password = self._proxy_password
@property
def ca_certs_filename(self):
"""Path to a file containing the root CA certificates that HTTPS
servers should be verified with.
Defaults to :class:`None`. Must be a bytestring file path otherwise.
This is not used for verifying Spotify's servers, but may be
used for verifying third parties' HTTPS servers, like the Last.fm
servers if you scrobbling the music you listen to through libspotify.
libspotify for macOS use other means for communicating with HTTPS
servers and ignores this configuration.
The file must be a concatenation of all certificates in PEM format.
Provided with libspotify is a sample PEM file in the ``examples/``
dir. It is recommended that the application export a similar file
from the local certificate store. On Linux systems, the certificate
store is often found at :file:`/etc/ssl/certs/ca-certificates.crt` or
:file:`/etc/ssl/certs/ca-bundle.crt`
"""
ptr = self._get_ca_certs_filename_ptr()
if ptr is not None:
return utils.to_bytes_or_none(ptr[0])
else:
return None
@ca_certs_filename.setter
def ca_certs_filename(self, value):
ptr = self._get_ca_certs_filename_ptr()
if ptr is not None:
self._ca_certs_filename = utils.to_char_or_null(value)
ptr[0] = self._ca_certs_filename
def _get_ca_certs_filename_ptr(self):
# XXX This function does pointer arithmetic based on the assumption
# that if the ca_certs_filename field exists in sp_session_config on
# the current platform, it will reside between the proxy_password and
# tracefile fields.
#
# If CFFI supported #ifdef we could make this exact
# science by including ca_certs_filename field in the sp_session_config
# struct only if the SP_WITH_CURL macro is defined, ref. the
# sp_session_create example in the libspotify docs.
proxy_password_ptr = spotify.ffi.addressof(
self._sp_session_config, "proxy_password"
)
tracefile_ptr = spotify.ffi.addressof(self._sp_session_config, "tracefile")
if tracefile_ptr - proxy_password_ptr != 2:
return None
return proxy_password_ptr + 1
@property
def tracefile(self):
"""Path to API trace file.
Defaults to :class:`None`. Must be a bytestring otherwise.
"""
return utils.to_bytes_or_none(self._sp_session_config.tracefile)
@tracefile.setter
def tracefile(self, value):
# NOTE libspotify does not consider empty string as unset, and will try
# to open the file "" and fail with a "LibError: Unable to open trace
# file", thus we convert empty string to NULL.
self._tracefile = utils.to_char_or_null(value or None)
self._sp_session_config.tracefile = self._tracefile