# -*- coding: utf-8 -*-

# Licensed under the MIT license
# http://opensource.org/licenses/mit-license.php

# Copyright 2009, Philippe Normand <phil@base-art.net>

# TODO: podcasts

import mimetypes
import os
import re
import time
import urllib.error
import urllib.parse
import urllib.request
from sqlite3 import dbapi2
from urllib.parse import urlsplit
from datetime import datetime

from twisted.internet import defer, task

from coherence.backend import BackendItem, BackendStore
from coherence.extern import db_row
from coherence.log import LogAble
from coherence.upnp.core import DIDLLite

# fallback on pysqlite2.dbapi2

mimetypes.init()
mimetypes.add_type('audio/x-m4a', '.m4a')
mimetypes.add_type('video/mp4', '.mp4')
mimetypes.add_type('video/mpegts', '.ts')
mimetypes.add_type('video/divx', '.divx')
mimetypes.add_type('video/divx', '.avi')
mimetypes.add_type('video/x-matroska', '.mkv')
mimetypes.add_type('audio/x-musepack', '.mpc')
mimetypes.add_type('audio/x-wavpack', '.flac')
mimetypes.add_type('audio/x-wavpack', '.wv')
mimetypes.add_type('audio/mp4', '.m4a')

ROOT_CONTAINER_ID = 0
AUDIO_CONTAINER = 200
VIDEO_CONTAINER = 300
AUDIO_ALL_CONTAINER_ID = 201
AUDIO_ARTIST_CONTAINER_ID = 202
AUDIO_ALBUM_CONTAINER_ID = 203
AUDIO_PLAYLIST_CONTAINER_ID = 204
VIDEO_ALL_CONTAINER_ID = 301
VIDEO_PLAYLIST_CONTAINER_ID = 302


def get_cover_path(artist_name, album_title):
    def _escape_part(part):
        escaped = ''
        if part:
            if part.find('(') > -1:
                part = part[:part.find('(')]
            escaped = re.sub('[^A-Za-z0-9]*', '', part).lower()
        return escaped

    base_dir = os.path.expanduser('~/.cache/album-art')
    return \
        os.path.join(
            base_dir,
            f'{_escape_part(artist_name)}-{_escape_part(album_title)}.jpg')


class SQLiteDB(LogAble):
    '''
    Python DB API 2.0 backend support.
    '''
    logCategory = 'sqlite'

    def __init__(self, database):
        ''' Connect to a db backend hosting the given database.
        '''
        LogAble.__init__(self)
        self._params = {'database': database, 'check_same_thread': True}
        self.connect()

    def disconnect(self):
        self._db.close()

    def connect(self):
        '''
        Connect to the database, set L{_db} instance variable.
        '''
        self._db = dbapi2.connect(**self._params)

    def reconnect(self):
        '''
        Disconnect and reconnect to the database.
        '''
        self.disconnect()
        self.connect()

    def sql_execute(self, request, *params, **kw):
        ''' Execute a SQL query in the db backend
        '''
        t0 = time.time()
        debug_msg = request
        if params:
            debug_msg = f'{request} params={params!r}'
        debug_msg = ''.join(debug_msg.splitlines())
        if debug_msg:
            self.debug(f'QUERY: {debug_msg}')

        cursor = self._db.cursor()
        result = []
        cursor.execute(request, params)
        if cursor.description:
            all_rows = cursor.fetchall()
            result = db_row.getdict(all_rows, cursor.description)
        cursor.close()
        delta = time.time() - t0
        self.log(f'SQL request took {delta} seconds')
        return result


class Container(BackendItem):
    get_path = None

    def __init__(self, id, parent_id, name, children_callback=None, store=None,
                 play_container=False):
        BackendItem.__init__(self)
        self.id = id
        self.parent_id = parent_id
        self.name = name
        self.mimetype = 'directory'
        self.store = store
        self.play_container = play_container
        self.update_id = 0
        if children_callback is not None:
            self.children = children_callback
        else:
            self.children = []

    def add_child(self, child):
        self.children.append(child)

    def get_children(self, start=0, request_count=0):
        def got_children(children):
            if request_count == 0:
                return children[start:]
            else:
                return children[start:request_count]

        if callable(self.children):
            dfr = defer.maybeDeferred(self.children)
        else:
            dfr = defer.succeed(self.children)
        dfr.addCallback(got_children)
        return dfr

    def get_child_count(self):
        count = 0
        if callable(self.children):
            count = defer.maybeDeferred(self.children)
            count.addCallback(lambda children: len(children))
        else:
            count = len(self.children)
        return count

    def get_item(self):
        item = DIDLLite.Container(self.id, self.parent_id, self.name)

        def got_count(count):
            item.childCount = count
            if self.store and self.play_container:
                if item.childCount > 0:
                    dfr = self.get_children(request_count=1)
                    dfr.addCallback(got_child, item)
                    return dfr
            return item

        def got_child(children, item):
            res = DIDLLite.PlayContainerResource(
                self.store.server.uuid,
                cid=self.get_id(),
                fid=children[0].get_id())
            item.res.append(res)
            return item

        dfr = defer.maybeDeferred(self.get_child_count)
        dfr.addCallback(got_count)
        return dfr

    def get_name(self):
        return self.name

    def get_id(self):
        return self.id


class Artist(BackendItem):

    def __init__(self, *args, **kwargs):
        BackendItem.__init__(self, *args, **kwargs)
        self._row = args[0]
        self._db = args[1]
        self._local_music_library_id = args[2]
        self.musicbrainz_id = self._row.MusicBrainzID
        self.itemID = self._row.ArtistID
        self.name = self._row.Name or ''
        if self.name:
            self.name = self.name.encode('utf-8')

    def get_children(self, start=0, end=0):
        albums = []

        def query_db():
            q = 'select * from CoreAlbums where ArtistID=? and AlbumID in ' \
                '(select distinct(AlbumID) from CoreTracks where ' \
                'PrimarySourceID=?) order by Title'
            rows = self._db.sql_execute(q, self.itemID,
                                        self._local_music_library_id)
            for row in rows:
                album = Album(row, self._db, self)
                albums.append(album)
                yield album

        dfr = task.coiterate(query_db())
        dfr.addCallback(lambda gen: albums)
        return dfr

    def get_child_count(self):
        q = 'select count(AlbumID) as c from CoreAlbums ' \
            'where ArtistID=? and AlbumID in (select distinct(AlbumID) ' \
            'from CoreTracks where PrimarySourceID=?) '
        return self._db.sql_execute(q, self.itemID,
                                    self._local_music_library_id)[0].c

    def get_item(self):
        item = DIDLLite.MusicArtist(self.get_id(),
                                    AUDIO_ARTIST_CONTAINER_ID, self.name)
        item.childCount = self.get_child_count()
        return item

    def get_id(self):
        return f'artist.{self.itemID:d}'

    def __repr__(self):
        return \
            f'<Artist {self.itemID:d} name="{self.name}" ' \
            f'musicbrainz="{self.musicbrainz_id}">'


class Album(BackendItem):
    ''' definition for an album '''
    mimetype = 'directory'
    get_path = None

    def __init__(self, *args, **kwargs):
        BackendItem.__init__(self, *args, **kwargs)
        self._row = args[0]
        self._db = args[1]
        self.artist = args[2]
        self.itemID = self._row.AlbumID
        self.title = self._row.Title
        self.cover = get_cover_path(self.artist.name, self.title)
        if self.title:
            self.title = self.title.encode('utf-8')
        self.musicbrainz_id = self._row.MusicBrainzID
        self.cd_count = 1

    def get_children(self, start=0, request_count=0):
        tracks = []

        def query_db():
            q = 'select * from CoreTracks where AlbumID=? order by TrackNumber'
            if request_count:
                q += f' limit {request_count:d}'
            rows = self._db.sql_execute(q, self.itemID)
            for row in rows:
                track = Track(row, self._db, self)
                tracks.append(track)
                yield track

        dfr = task.coiterate(query_db())
        dfr.addCallback(lambda gen: tracks)
        return dfr

    def get_child_count(self):
        q = 'select count(TrackID) as c from CoreTracks where AlbumID=?'
        count = self._db.sql_execute(q, self.itemID)[0].c
        return count

    def get_item(self):
        item = DIDLLite.MusicAlbum(self.get_id(), AUDIO_ALBUM_CONTAINER_ID,
                                   self.title)
        item.artist = self.artist.name
        item.childCount = self.get_child_count()
        if self.cover:
            _, ext = os.path.splitext(self.cover)
            item.albumArtURI = ''.join((self._db.urlbase,
                                        self.get_id(), '?cover', ext))

        def got_tracks(tracks):
            res = DIDLLite.PlayContainerResource(self._db.server.uuid,
                                                 cid=self.get_id(),
                                                 fid=tracks[0].get_id())
            item.res.append(res)
            return item

        if item.childCount > 0:
            dfr = self.get_children(request_count=1)
            dfr.addCallback(got_tracks)
        else:
            dfr = defer.succeed(item)
        return dfr

    def get_id(self):
        return f'album.{self.itemID:d}'

    def get_name(self):
        return self.title

    def get_cover(self):
        return self.cover

    def __repr__(self):
        return \
            f'<Album {self.itemID:d} title="{self.title}" ' \
            f'artist="{self.artist.name}" #cds {self.cd_count:d} ' \
            f'cover="{self.cover}" musicbrainz="{self.musicbrainz_id}">'


class BasePlaylist(BackendItem):
    ''' definition for a playlist '''
    id_type = 'baseplaylist'
    mimetype = 'directory'
    get_path = None

    def __init__(self, *args, **kwargs):
        BackendItem.__init__(self, *args, **kwargs)
        self._row = args[0]
        self._store = args[1]
        self._db = self._store.db
        self.title = self._row.Name
        if self.title:
            self.title = self.title.encode('utf-8')

    @property
    def db_id(self):
        '''Should be implemented in subclass'''
        return ''

    def get_tracks(self, request_count):
        return []

    def db_to_didl(self, row):
        album = self._store.get_album_with_id(row.AlbumID)
        track = Track(row, self._db, album)
        return track

    def get_id(self):
        return f'{self.id_type}.{self.db_id:d}'

    def __repr__(self):
        return \
            f'<{self.__class__.__name__} ' \
            f'{self.db_id:d} title="{self.title}">'

    def get_children(self, start=0, request_count=0):
        tracks = []

        def query_db():
            rows = self.get_tracks(request_count)
            for row in rows:
                track = self.db_to_didl(row)
                tracks.append(track)
                yield track

        dfr = task.coiterate(query_db())
        dfr.addCallback(lambda gen: tracks)
        return dfr

    def get_child_count(self):
        return self._row.CachedCount

    def get_item(self):
        item = DIDLLite.PlaylistContainer(
            self.get_id(),
            AUDIO_PLAYLIST_CONTAINER_ID,
            self.title)
        item.childCount = self.get_child_count()

        def got_tracks(tracks):
            res = DIDLLite.PlayContainerResource(
                self._db.server.uuid,
                cid=self.get_id(),
                fid=tracks[0].get_id())
            item.res.append(res)
            return item

        if item.childCount > 0:
            dfr = self.get_children(request_count=1)
            dfr.addCallback(got_tracks)
        else:
            dfr = defer.succeed(item)
        return dfr

    def get_name(self):
        return self.title


class MusicPlaylist(BasePlaylist):
    id_type = 'musicplaylist'

    @property
    def db_id(self):
        return self._row.PlaylistID

    def get_tracks(self, request_count):
        q = 'select * from CoreTracks where TrackID in (select TrackID ' \
            'from CorePlaylistEntries where PlaylistID=?)'
        if request_count:
            q += f' limit {request_count:d}'
        return self._db.sql_execute(q, self.db_id)


class MusicSmartPlaylist(BasePlaylist):
    id_type = 'musicsmartplaylist'

    @property
    def db_id(self):
        return self._row.SmartPlaylistID

    def get_tracks(self, request_count):
        q = 'select * from CoreTracks where TrackID in (select TrackID ' \
            'from CoreSmartPlaylistEntries where SmartPlaylistID=?)'
        if request_count:
            q += f' limit {request_count:d}'
        return self._db.sql_execute(q, self.db_id)


class VideoPlaylist(MusicPlaylist):
    id_type = 'videoplaylist'

    def db_to_didl(self, row):
        return Video(row, self._db)


class VideoSmartPlaylist(MusicSmartPlaylist):
    id_type = 'videosmartplaylist'

    def db_to_didl(self, row):
        return Video(row, self._db)


class BaseTrack(BackendItem):
    ''' definition for a track '''

    def __init__(self, *args, **kwargs):
        BackendItem.__init__(self, *args, **kwargs)
        self._row = args[0]
        self._db = args[1]
        self.itemID = self._row.TrackID
        self.title = self._row.Title
        self.track_nr = self._row.TrackNumber
        self.location = self._row.Uri
        self.playlist = kwargs.get('playlist')
        self.album = None

    def get_children(self, start=0, request_count=0):
        return []

    def get_child_count(self):
        return 0

    def get_resources(self):
        resources = []
        _, host_port, _, _, _ = urlsplit(self._db.urlbase)
        if host_port.find(':') != -1:
            host, port = tuple(host_port.split(':'))
        else:
            host = host_port

        _, ext = os.path.splitext(self.location)
        ext = ext.lower()

        # FIXME: drop this hack when we switch to tagbin
        mimetype, dummy = mimetypes.guess_type(f'dummy{ext}')
        if not mimetype:
            mimetype = 'audio/mpeg'
            ext = 'mp3'

        statinfo = os.stat(self.get_path())

        res = DIDLLite.Resource(
            self.location, f'internal:{host}:{mimetype}:*')
        try:
            res.size = statinfo.st_size
        except Exception:
            res.size = 0

        resources.append(res)

        url = f'{self._db.urlbase}{self.get_id()}{ext}'

        res = DIDLLite.Resource(url, f'http-get:*:{mimetype}:*')
        try:
            res.size = statinfo.st_size
        except Exception:
            res.size = 0
        resources.append(res)
        return statinfo, resources

    def get_path(self):
        return urllib.parse.unquote(self.location[7:].encode('utf-8'))

    def get_id(self):
        return 'track.%d' % self.itemID

    def get_name(self):
        return self.title

    def get_url(self):
        return self._db.urlbase + str(self.itemID).encode('utf-8')

    def get_cover(self):
        return self.album.cover

    def __repr__(self):
        return \
            f'<Track {self.itemID:d} title="{self.title}" ' \
            f'nr="{self.track_nr:d}" album="{self.album.title}" ' \
            f'artist="{self.album.artist.name}" path="{self.location}">'


class Track(BaseTrack):

    def __init__(self, *args, **kwargs):
        BaseTrack.__init__(self, *args, **kwargs)
        self.album = args[2]

    def get_item(self):
        item = DIDLLite.MusicTrack(
            self.get_id(), self.album.itemID, self.title)
        item.artist = self.album.artist.name
        item.album = self.album.title
        item.playlist = self.playlist

        if self.album.cover != '':
            _, ext = os.path.splitext(self.album.cover)
            ''' add the cover image extension to help clients not reacting on
                the mimetype '''
            item.albumArtURI = ''.join((self._db.urlbase, self.get_id(),
                                        '?cover', ext))
        item.originalTrackNumber = self.track_nr
        item.server_uuid = str(self._db.server.uuid)[5:]

        statinfo, resources = self.get_resources()
        item.res.extend(resources)

        try:
            # FIXME: getmtime is deprecated in Twisted 2.6
            item.date = datetime.fromtimestamp(statinfo.st_mtime)
        except Exception:
            item.date = None

        return item


class Video(BaseTrack):
    def get_item(self):
        item = DIDLLite.VideoItem(self.get_id(), VIDEO_ALL_CONTAINER_ID,
                                  self.title)
        item.server_uuid = str(self._db.server.uuid)[5:]

        statinfo, resources = self.get_resources()
        item.res.extend(resources)

        try:
            # FIXME: getmtime is deprecated in Twisted 2.6
            item.date = datetime.fromtimestamp(statinfo.st_mtime)
        except Exception:
            item.date = None

        return item


class BansheeDB(LogAble):
    logCategory = 'banshee_db'

    def __init__(self, path=None):
        LogAble.__init__(self)
        self._local_music_library_id = None
        self._local_video_library_id = None
        default_db_path = os.path.expanduser('~/.config/banshee-1/banshee.db')
        self._db_path = path or default_db_path

    def open_db(self):
        self.db = SQLiteDB(self._db_path)

    def close(self):
        self.db.disconnect()

    def get_local_music_library_id(self):
        if self._local_music_library_id is None:
            q = 'select PrimarySourceID ' \
                'from CorePrimarySources where StringID=?'
            row = self.db.sql_execute(q, 'MusicLibrarySource-Library')[0]
            self._local_music_library_id = row.PrimarySourceID
        return self._local_music_library_id

    def get_local_video_library_id(self):
        if self._local_video_library_id is None:
            q = 'select PrimarySourceID ' \
                'from CorePrimarySources where StringID=?'
            row = self.db.sql_execute(q, 'VideoLibrarySource-VideoLibrary')[0]
            self._local_video_library_id = row.PrimarySourceID
        return self._local_video_library_id

    def get_artists(self):
        artists = []

        def query_db():
            source_id = self.get_local_music_library_id()
            q = 'select * from CoreArtists where ArtistID in ' \
                '(select distinct(ArtistID) from CoreTracks where ' \
                'PrimarySourceID=?) order by Name'
            for row in self.db.sql_execute(q, source_id):
                artist = Artist(row, self.db, source_id)
                artists.append(artist)
                yield artist

        dfr = task.coiterate(query_db())
        dfr.addCallback(lambda gen: artists)
        return dfr

    def get_albums(self):
        albums = []
        artists = {}

        def query_db():
            q = 'select * from CoreAlbums where AlbumID in ' \
                '(select distinct(AlbumID) from CoreTracks where ' \
                'PrimarySourceID=?) order by Title'
            for row in self.db.sql_execute(q,
                                           self.get_local_music_library_id()):
                try:
                    artist = artists[row.ArtistID]
                except KeyError:
                    artist = self.get_artist_with_id(row.ArtistID)
                    artists[row.ArtistID] = artist
                album = Album(row, self.db, artist)
                albums.append(album)
                yield album

        dfr = task.coiterate(query_db())
        dfr.addCallback(lambda gen: albums)
        return dfr

    def get_music_playlists(self):
        return self.get_playlists(self.get_local_music_library_id(),
                                  MusicPlaylist, MusicSmartPlaylist)

    def get_playlists(self, source_id, PlaylistClass, SmartPlaylistClass):
        playlists = []

        def query_db():
            q = 'select * from CorePlaylists ' \
                'where PrimarySourceID=? order by Name'
            for row in self.db.sql_execute(q, source_id):
                playlist = PlaylistClass(row, self)
                playlists.append(playlist)
                yield playlist

            q = 'select * from CoreSmartPlaylists ' \
                'where PrimarySourceID=? order by Name'
            for row in self.db.sql_execute(q, source_id):
                playlist = SmartPlaylistClass(row, self)
                playlists.append(playlist)
                yield playlist

        dfr = task.coiterate(query_db())
        dfr.addCallback(lambda gen: playlists)
        return dfr

    def get_artist_with_id(self, artist_id):
        q = 'select * from CoreArtists where ArtistID=? limit 1'
        row = self.db.sql_execute(q, artist_id)[0]
        return Artist(row, self.db, self.get_local_music_library_id())

    def get_album_with_id(self, album_id):
        q = 'select * from CoreAlbums where AlbumID=? limit 1'
        row = self.db.sql_execute(q, album_id)[0]
        artist = self.get_artist_with_id(row.ArtistID)
        return Album(row, self.db, artist)

    def get_playlist_with_id(self, playlist_id, PlaylistClass):
        q = 'select * from CorePlaylists where PlaylistID=? limit 1'
        row = self.db.sql_execute(q, playlist_id)[0]
        return PlaylistClass(row, self)

    def get_smart_playlist_with_id(self, playlist_id, PlaylistClass):
        q = 'select * from CoreSmartPlaylists where SmartPlaylistID=? limit 1'
        row = self.db.sql_execute(q, playlist_id)[0]
        return PlaylistClass(row, self)

    def get_music_playlist_with_id(self, playlist_id):
        return self.get_playlist_with_id(playlist_id, MusicPlaylist)

    def get_music_smart_playlist_with_id(self, playlist_id):
        return self.get_smart_playlist_with_id(playlist_id, MusicSmartPlaylist)

    def get_video_playlist_with_id(self, playlist_id):
        return self.get_playlist_with_id(playlist_id, VideoPlaylist)

    def get_video_smart_playlist_with_id(self, playlist_id):
        return self.get_smart_playlist_with_id(playlist_id, VideoSmartPlaylist)

    def get_track_with_id(self, track_id):
        q = 'select * from CoreTracks where TrackID=? limit 1'
        row = self.db.sql_execute(q, track_id)[0]
        album = self.get_album_with_id(row.AlbumID)
        return Track(row, self.db, album)

    def get_track_for_uri(self, track_uri):
        q = 'select * from CoreTracks where Uri=? limit 1'
        try:
            row = self.db.sql_execute(q, track_uri)[0]
        except IndexError:
            # not found
            track = None
        else:
            album = self.get_album_with_id(row.AlbumID)
            track = Track(row, self, album)
        return track

    def get_tracks(self):
        tracks = []
        albums = {}

        def query_db():
            q = 'select * from CoreTracks where TrackID in ' \
                '(select distinct(TrackID) from CoreTracks where ' \
                'PrimarySourceID=?) order by AlbumID,TrackNumber'
            for row in self.db.sql_execute(q,
                                           self.get_local_music_library_id()):
                if row.AlbumID not in albums:
                    album = self.get_album_with_id(row.AlbumID)
                    albums[row.AlbumID] = album
                else:
                    album = albums[row.AlbumID]
                track = Track(row, self.db, album)
                tracks.append(track)
                yield track

        dfr = task.coiterate(query_db())
        dfr.addCallback(lambda gen: tracks)
        return dfr

    def get_video_with_id(self, video_id):
        q = 'select * from CoreTracks where TrackID=? limit 1'
        row = self.db.sql_execute(q, video_id)[0]
        return Video(row, self.db)

    def get_videos(self):
        videos = []

        def query_db():
            source_id = self.get_local_video_library_id()
            q = 'select * from CoreTracks where TrackID in ' \
                '(select distinct(TrackID) from CoreTracks where ' \
                'PrimarySourceID=?)'
            for row in self.db.sql_execute(q, source_id):
                video = Video(row, self.db, source_id)
                videos.append(video)
                yield video

        dfr = task.coiterate(query_db())
        dfr.addCallback(lambda gen: videos)
        return dfr

    def get_video_playlists(self):
        return self.get_playlists(self.get_local_video_library_id(),
                                  VideoPlaylist, VideoSmartPlaylist)


class BansheeStore(BackendStore, BansheeDB):
    '''
    .. versionchanged:: 0.9.0
        Migrated from louie/dispatcher to EventDispatcher
    '''
    logCategory = 'banshee_store'
    implements = ['MediaServer']

    def __init__(self, server, **kwargs):
        BackendStore.__init__(self, server, **kwargs)
        BansheeDB.__init__(self, kwargs.get('db_path'))
        self.update_id = 0
        self.name = kwargs.get('name', 'Banshee')

        self.containers = {}
        self.containers[ROOT_CONTAINER_ID] = Container(
            ROOT_CONTAINER_ID, -1, self.name, store=self)
        self.init_completed = True

    def upnp_init(self):
        self.open_db()

        music = Container(AUDIO_CONTAINER, ROOT_CONTAINER_ID,
                          'Music', store=self)
        self.containers[ROOT_CONTAINER_ID].add_child(music)
        self.containers[AUDIO_CONTAINER] = music

        artists = Container(AUDIO_ARTIST_CONTAINER_ID, AUDIO_CONTAINER,
                            'Artists', children_callback=self.get_artists,
                            store=self)
        self.containers[AUDIO_ARTIST_CONTAINER_ID] = artists
        self.containers[AUDIO_CONTAINER].add_child(artists)

        albums = Container(AUDIO_ALBUM_CONTAINER_ID, AUDIO_CONTAINER,
                           'Albums', children_callback=self.get_albums,
                           store=self)
        self.containers[AUDIO_ALBUM_CONTAINER_ID] = albums
        self.containers[AUDIO_CONTAINER].add_child(albums)

        tracks = Container(AUDIO_ALL_CONTAINER_ID, AUDIO_CONTAINER,
                           'All tracks', children_callback=self.get_tracks,
                           play_container=True, store=self)
        self.containers[AUDIO_ALL_CONTAINER_ID] = tracks
        self.containers[AUDIO_CONTAINER].add_child(tracks)

        playlists = Container(AUDIO_PLAYLIST_CONTAINER_ID, AUDIO_CONTAINER,
                              'Playlists', store=self,
                              children_callback=self.get_music_playlists)
        self.containers[AUDIO_PLAYLIST_CONTAINER_ID] = playlists
        self.containers[AUDIO_CONTAINER].add_child(playlists)

        videos = Container(VIDEO_CONTAINER, ROOT_CONTAINER_ID,
                           'Videos', store=self)
        self.containers[ROOT_CONTAINER_ID].add_child(videos)
        self.containers[VIDEO_CONTAINER] = videos

        all_videos = Container(VIDEO_ALL_CONTAINER_ID, VIDEO_CONTAINER,
                               'All Videos', children_callback=self.get_videos,
                               store=self)
        self.containers[VIDEO_ALL_CONTAINER_ID] = all_videos
        self.containers[VIDEO_CONTAINER].add_child(all_videos)

        playlists = Container(VIDEO_PLAYLIST_CONTAINER_ID, VIDEO_CONTAINER,
                              'Playlists', store=self,
                              children_callback=self.get_video_playlists)
        self.containers[VIDEO_PLAYLIST_CONTAINER_ID] = playlists
        self.containers[VIDEO_CONTAINER].add_child(playlists)

        self.db.server = self.server
        self.db.urlbase = self.urlbase
        self.db.containers = self.containers

        self.current_connection_id = None
        if self.server:
            hostname = self.server.coherence.hostname
            source_protocol_info = [f'internal:{hostname}:audio/mpeg:*',
                                    'http-get:*:audio/mpeg:*',
                                    f'internal:{hostname}:application/ogg:*',
                                    'http-get:*:application/ogg:*']

            self.server.connection_manager_server.set_variable(
                0, 'SourceProtocolInfo', source_protocol_info, default=True)

    def release(self):
        self.db.disconnect()

    def get_by_id(self, item_id):
        self.info(f'get_by_id {item_id}')
        if isinstance(item_id, bytes):
            item_id = item_id.decode('utf-8')
        if isinstance(item_id, str) and item_id.find('.') > 0:
            item_id = item_id.split('@', 1)
            item_type, item_id = item_id[0].split('.')[:2]
            item_id = int(item_id)
            dfr = self._lookup(item_type, item_id)
        else:
            item_id = int(item_id)
            item = self.containers[item_id]
            dfr = defer.succeed(item)
        return dfr

    def _lookup(self, item_type, item_id):
        lookup_mapping = dict(
            artist=self.get_artist_with_id,
            album=self.get_album_with_id,
            musicplaylist=self.get_music_playlist_with_id,
            musicsmartplaylist=self.get_music_smart_playlist_with_id,
            videoplaylist=self.get_video_playlist_with_id,
            videosmartplaylist=self.get_video_smart_playlist_with_id,
            track=self.get_track_with_id,
            video=self.get_video_with_id)
        item = None
        func = lookup_mapping.get(item_type)
        if func:
            item = func(item_id)
        return defer.succeed(item)
