From 85175972624a1c883b1a330dfb1d3752c75be3f5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= <oddstr13@openshell.no>
Date: Mon, 22 Feb 2021 04:42:51 +0100
Subject: [PATCH 1/5] Get specific library vs list of libraries

---
 jellyfin_kodi/full_sync.py | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/jellyfin_kodi/full_sync.py b/jellyfin_kodi/full_sync.py
index c82c137c..9c671cb3 100644
--- a/jellyfin_kodi/full_sync.py
+++ b/jellyfin_kodi/full_sync.py
@@ -82,7 +82,7 @@ class FullSync(object):
             for selected in libraries:
 
                 if selected not in [x.replace('Mixed:', "") for x in self.sync['Libraries']]:
-                    library = self.get_libraries(selected)
+                    library = self.get_library(selected)
 
                     if library:
 
@@ -100,13 +100,13 @@ class FullSync(object):
         if not xmls.advanced_settings() and self.sync['Libraries']:
             self.start()
 
-    def get_libraries(self, library_id=None):
-
+    def get_libraries(self):
         with Database('jellyfin') as jellyfindb:
-            if library_id is None:
-                return jellyfin_db.JellyfinDatabase(jellyfindb.cursor).get_views()
-            else:
-                return jellyfin_db.JellyfinDatabase(jellyfindb.cursor).get_view(library_id)
+            return jellyfin_db.JellyfinDatabase(jellyfindb.cursor).get_views()
+
+    def get_library(self, library_id):
+        with Database('jellyfin') as jellyfindb:
+            return jellyfin_db.JellyfinDatabase(jellyfindb.cursor).get_view(library_id)
 
     def mapping(self):
 
@@ -223,7 +223,7 @@ class FullSync(object):
                 if not sync_id or sync_id == 'Refresh':
                     libraries = self.get_libraries()
                 else:
-                    libraries = self.get_libraries(sync_id)
+                    libraries = [self.get_library(sync_id)]
 
                 for entry in libraries:
                     if entry[2] == 'boxsets':

From 2bade17e7f6943d406d8e2c3696b2cfd747ec4ee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= <oddstr13@openshell.no>
Date: Mon, 22 Feb 2021 05:31:25 +0100
Subject: [PATCH 2/5] Why ever would I want the sync_id back when I query with
 it?

---
 jellyfin_kodi/full_sync.py | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/jellyfin_kodi/full_sync.py b/jellyfin_kodi/full_sync.py
index 9c671cb3..bd00ec1e 100644
--- a/jellyfin_kodi/full_sync.py
+++ b/jellyfin_kodi/full_sync.py
@@ -223,7 +223,12 @@ class FullSync(object):
                 if not sync_id or sync_id == 'Refresh':
                     libraries = self.get_libraries()
                 else:
-                    libraries = [self.get_library(sync_id)]
+                    res = self.get_library(sync_id)
+                    if res is not None:
+                        # FIXME: This is a hack. Fix plz.
+                        libraries = [(sync_id,), res]
+                    else:
+                        libraries = []
 
                 for entry in libraries:
                     if entry[2] == 'boxsets':

From 61fcfe3b5e8f38c687f4dc888bd859fb8ea3375f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= <oddstr13@openshell.no>
Date: Tue, 23 Feb 2021 00:18:02 +0100
Subject: [PATCH 3/5] Fix tuple prepend

---
 jellyfin_kodi/full_sync.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/jellyfin_kodi/full_sync.py b/jellyfin_kodi/full_sync.py
index bd00ec1e..b28ade1c 100644
--- a/jellyfin_kodi/full_sync.py
+++ b/jellyfin_kodi/full_sync.py
@@ -226,7 +226,7 @@ class FullSync(object):
                     res = self.get_library(sync_id)
                     if res is not None:
                         # FIXME: This is a hack. Fix plz.
-                        libraries = [(sync_id,), res]
+                        libraries = [(sync_id,) + res]
                     else:
                         libraries = []
 

From 164fc50d9817c56ff58f1cd6cd434d3dc66c85d3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= <oddstr13@openshell.no>
Date: Tue, 23 Feb 2021 02:42:26 +0100
Subject: [PATCH 4/5] Named tuples and type hints? What is this magic?

---
 jellyfin_kodi/database/jellyfin_db.py |  7 +++--
 jellyfin_kodi/database/queries.py     |  2 +-
 jellyfin_kodi/entrypoint/default.py   |  2 +-
 jellyfin_kodi/full_sync.py            | 29 +++++++++-----------
 jellyfin_kodi/jellyfin/utils.py       | 13 +++++++++
 jellyfin_kodi/library.py              |  6 ++---
 jellyfin_kodi/views.py                | 13 +++++----
 typings/database/jellyfin_db.pyi      | 39 +++++++++++++++++++++++++++
 8 files changed, 80 insertions(+), 31 deletions(-)
 create mode 100644 typings/database/jellyfin_db.pyi

diff --git a/jellyfin_kodi/database/jellyfin_db.py b/jellyfin_kodi/database/jellyfin_db.py
index 92b6d69f..2036a25d 100644
--- a/jellyfin_kodi/database/jellyfin_db.py
+++ b/jellyfin_kodi/database/jellyfin_db.py
@@ -5,6 +5,8 @@ from __future__ import division, absolute_import, print_function, unicode_litera
 from database import queries as QU
 from helper import LazyLogger
 
+from jellyfin.utils import sqlite_namedtuple_factory
+
 ##################################################################################################
 
 LOG = LazyLogger(__name__)
@@ -16,6 +18,7 @@ class JellyfinDatabase():
 
     def __init__(self, cursor):
         self.cursor = cursor
+        cursor.row_factory = sqlite_namedtuple_factory
 
     def get_item_by_id(self, *args):
         self.cursor.execute(QU.get_item, args)
@@ -124,8 +127,8 @@ class JellyfinDatabase():
     def remove_view(self, *args):
         self.cursor.execute(QU.delete_view, args)
 
-    def get_views(self, *args):
-        self.cursor.execute(QU.get_views, args)
+    def get_views(self):
+        self.cursor.execute(QU.get_views)
 
         return self.cursor.fetchall()
 
diff --git a/jellyfin_kodi/database/queries.py b/jellyfin_kodi/database/queries.py
index b80b792b..e1c3eb69 100644
--- a/jellyfin_kodi/database/queries.py
+++ b/jellyfin_kodi/database/queries.py
@@ -66,7 +66,7 @@ FROM        jellyfin
 WHERE       jellyfin_parent_id = ?
 """
 get_view = """
-SELECT      view_name, media_type
+SELECT      *
 FROM        view
 WHERE       view_id = ?
 """
diff --git a/jellyfin_kodi/entrypoint/default.py b/jellyfin_kodi/entrypoint/default.py
index 2e002d06..cbede539 100644
--- a/jellyfin_kodi/entrypoint/default.py
+++ b/jellyfin_kodi/entrypoint/default.py
@@ -811,7 +811,7 @@ def get_themes(api_client):
 
     with Database('jellyfin') as jellyfindb:
         all_views = jellyfin_db.JellyfinDatabase(jellyfindb.cursor).get_views()
-        views = [x[0] for x in all_views if x[2] in ('movies', 'tvshows', 'mixed')]
+        views = [x.view_id for x in all_views if x.media_type in ('movies', 'tvshows', 'mixed')]
 
     items = {}
     server = api_client.config.data['auth.server']
diff --git a/jellyfin_kodi/full_sync.py b/jellyfin_kodi/full_sync.py
index b28ade1c..36ceb64b 100644
--- a/jellyfin_kodi/full_sync.py
+++ b/jellyfin_kodi/full_sync.py
@@ -86,9 +86,9 @@ class FullSync(object):
 
                     if library:
 
-                        self.sync['Libraries'].append("Mixed:%s" % selected if library[1] == 'mixed' else selected)
+                        self.sync['Libraries'].append("Mixed:%s" % selected)
 
-                        if library[1] in ('mixed', 'movies'):
+                        if library.media_type in ('mixed', 'movies'):
                             self.sync['Libraries'].append('Boxsets:%s' % selected)
                     else:
                         self.sync['Libraries'].append(selected)
@@ -129,9 +129,8 @@ class FullSync(object):
             libraries = []
 
             for library in self.get_libraries():
-
-                if library[2] in ('movies', 'tvshows', 'musicvideos', 'music', 'mixed'):
-                    libraries.append({'Id': library[0], 'Name': library[1], 'Media': library[2]})
+                if library.media_type in ('movies', 'tvshows', 'musicvideos', 'music', 'mixed'):
+                    libraries.append({'Id': library.view_id, 'Name': library.view_name, 'Media': library.media_type})
 
             libraries = self.select_libraries(libraries)
 
@@ -223,16 +222,12 @@ class FullSync(object):
                 if not sync_id or sync_id == 'Refresh':
                     libraries = self.get_libraries()
                 else:
-                    res = self.get_library(sync_id)
-                    if res is not None:
-                        # FIXME: This is a hack. Fix plz.
-                        libraries = [(sync_id,) + res]
-                    else:
-                        libraries = []
+                    _lib = self.get_library(sync_id)
+                    libraries = [_lib] if _lib else []
 
                 for entry in libraries:
-                    if entry[2] == 'boxsets':
-                        boxset_library = {'Id': entry[0], 'Name': entry[1]}
+                    if entry.media_type == 'boxsets':
+                        boxset_library = {'Id': entry.view_id, 'Name': entry.view_name}
                         break
 
                 if boxset_library:
@@ -529,7 +524,7 @@ class FullSync(object):
             db = jellyfin_db.JellyfinDatabase(jellyfindb.cursor)
             library = db.get_view(library_id.replace('Mixed:', ""))
             items = db.get_item_by_media_folder(library_id.replace('Mixed:', ""))
-            media = 'music' if library[1] == 'music' else 'video'
+            media = 'music' if library.media_type == 'music' else 'video'
 
             if media == 'music':
                 settings('MusicRescan.bool', False)
@@ -540,7 +535,7 @@ class FullSync(object):
                 with self.library.music_database_lock if media == 'music' else self.library.database_lock:
                     with Database(media) as kodidb:
 
-                        if library[1] == 'mixed':
+                        if library.media_type == 'mixed':
 
                             movies = [x for x in items if x[1] == 'Movie']
                             tvshows = [x for x in items if x[1] == 'Series']
@@ -550,7 +545,7 @@ class FullSync(object):
                             for item in movies:
 
                                 obj(item[0])
-                                dialog.update(int((float(count) / float(len(items)) * 100)), heading="%s: %s" % (translate('addon_name'), library[0]))
+                                dialog.update(int((float(count) / float(len(items)) * 100)), heading="%s: %s" % (translate('addon_name'), library.view_name))
                                 count += 1
 
                             obj = TVShows(self.server, jellyfindb, kodidb, direct_path, library).remove
@@ -558,7 +553,7 @@ class FullSync(object):
                             for item in tvshows:
 
                                 obj(item[0])
-                                dialog.update(int((float(count) / float(len(items)) * 100)), heading="%s: %s" % (translate('addon_name'), library[0]))
+                                dialog.update(int((float(count) / float(len(items)) * 100)), heading="%s: %s" % (translate('addon_name'), library.view_name))
                                 count += 1
                         else:
                             default_args = (self.server, jellyfindb, kodidb, direct_path)
diff --git a/jellyfin_kodi/jellyfin/utils.py b/jellyfin_kodi/jellyfin/utils.py
index a2b8f572..ad976c78 100644
--- a/jellyfin_kodi/jellyfin/utils.py
+++ b/jellyfin_kodi/jellyfin/utils.py
@@ -1,5 +1,6 @@
 from six import string_types
 from six.moves import collections_abc
+from collections import namedtuple
 
 
 def clean_none_dict_values(obj):
@@ -41,3 +42,15 @@ def clean_none_dict_values(obj):
                     queue.append(value)
 
     return obj
+
+
+def sqlite_namedtuple_factory(cursor, row):
+    """
+    Usage:
+    con.row_factory = namedtuple_factory
+
+    http://peter-hoffmann.com/2010/python-sqlite-namedtuple-factory.html
+    """
+    fields = [col[0] for col in cursor.description]
+    Row = namedtuple("Row", fields)
+    return Row(*row)
diff --git a/jellyfin_kodi/library.py b/jellyfin_kodi/library.py
index edd0d112..c55d4256 100644
--- a/jellyfin_kodi/library.py
+++ b/jellyfin_kodi/library.py
@@ -471,10 +471,10 @@ class Library(threading.Thread):
                 available = [x for x in sync['SortedViews'] if x not in whitelist]
 
                 for library in available:
-                    name, media = db.get_view(library)
+                    view = db.get_view(library)
 
-                    if media in ('movies', 'tvshows', 'musicvideos', 'mixed', 'music'):
-                        libraries.append({'Id': library, 'Name': name})
+                    if view.media_type in ('movies', 'tvshows', 'musicvideos', 'mixed', 'music'):
+                        libraries.append({'Id': view.view_id, 'Name': view.view_name})
 
         choices = [x['Name'] for x in libraries]
         choices.insert(0, translate(33121))
diff --git a/jellyfin_kodi/views.py b/jellyfin_kodi/views.py
index 0070fe16..aa07aca7 100644
--- a/jellyfin_kodi/views.py
+++ b/jellyfin_kodi/views.py
@@ -174,9 +174,8 @@ class Views(object):
             removed = []
 
             for view in views:
-
-                if view[0] not in self.sync['SortedViews']:
-                    removed.append(view[0])
+                if view.view_id not in self.sync['SortedViews']:
+                    removed.append(view.view_id)
 
             if removed:
                 event('RemoveLibrary', {'Id': ','.join(removed)})
@@ -204,7 +203,7 @@ class Views(object):
                 view = db.get_view(library)
 
                 if view:
-                    view = {'Id': library, 'Name': view[0], 'Tag': view[0], 'Media': view[1]}
+                    view = {'Id': library, 'Name': view.view_name, 'Tag': view.view_name, 'Media': view.media_type}
 
                     if view['Media'] == 'mixed':
                         for media in ('movies', 'tvshows'):
@@ -697,10 +696,10 @@ class Views(object):
         except IndexError as error:
             LOG.exception(error)
 
-        for library in (libraries or []):
-            view = {'Id': library[0], 'Name': library[1], 'Tag': library[1], 'Media': library[2]}
+        for library in libraries:
+            view = {'Id': library.view_id, 'Name': library.view_name, 'Tag': library.view_name, 'Media': library.media_type}
 
-            if library[0] in [x.replace('Mixed:', "") for x in self.sync['Whitelist']]:  # Synced libraries
+            if library.view_id in [x.replace('Mixed:', "") for x in self.sync['Whitelist']]:  # Synced libraries
 
                 if view['Media'] in ('movies', 'tvshows', 'musicvideos', 'mixed'):
 
diff --git a/typings/database/jellyfin_db.pyi b/typings/database/jellyfin_db.pyi
new file mode 100644
index 00000000..2cfc2a3d
--- /dev/null
+++ b/typings/database/jellyfin_db.pyi
@@ -0,0 +1,39 @@
+from sqlite3 import Cursor
+from typing import Any, List, Optional, NamedTuple
+
+
+class ViewRow(NamedTuple):
+    view_id: str
+    view_name: str
+    media_type: str
+
+
+class JellyfinDatabase:
+    cursor: Cursor = ...
+    def __init__(self, cursor: Cursor) -> None: ...
+    def get_view(self, *args: Any) -> Optional[ViewRow]: ...
+    def get_views(self) -> List[ViewRow]: ...
+
+    # def get_item_by_id(self, *args: Any): ...
+    # def add_reference(self, *args: Any) -> None: ...
+    # def update_reference(self, *args: Any) -> None: ...
+    # def update_parent_id(self, *args: Any) -> None: ...
+    # def get_item_id_by_parent_id(self, *args: Any): ...
+    # def get_item_by_parent_id(self, *args: Any): ...
+    # def get_item_by_media_folder(self, *args: Any): ...
+    # def get_item_by_wild_id(self, item_id: Any): ...
+    # def get_checksum(self, *args: Any): ...
+    # def get_item_by_kodi_id(self, *args: Any): ...
+    # def get_full_item_by_kodi_id(self, *args: Any): ...
+    # def get_media_by_id(self, *args: Any): ...
+    # def get_media_by_parent_id(self, *args: Any): ...
+    # def remove_item(self, *args: Any) -> None: ...
+    # def remove_items_by_parent_id(self, *args: Any) -> None: ...
+    # def remove_item_by_kodi_id(self, *args: Any) -> None: ...
+    # def remove_wild_item(self, item_id: Any) -> None: ...
+    # def get_view_name(self, item_id: Any): ...
+    # def add_view(self, *args: Any) -> None: ...
+    # def remove_view(self, *args: Any) -> None: ...
+    # def get_views_by_media(self, *args: Any): ...
+    # def get_items_by_media(self, *args: Any): ...
+    # def remove_media_by_parent_id(self, *args: Any) -> None: ...

From 9b30c883ea45a44b2ec398473c8d0dede73f5a9a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= <oddstr13@openshell.no>
Date: Tue, 23 Feb 2021 02:53:10 +0100
Subject: [PATCH 5/5] Fix import order

---
 jellyfin_kodi/jellyfin/utils.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/jellyfin_kodi/jellyfin/utils.py b/jellyfin_kodi/jellyfin/utils.py
index ad976c78..af5cab59 100644
--- a/jellyfin_kodi/jellyfin/utils.py
+++ b/jellyfin_kodi/jellyfin/utils.py
@@ -1,6 +1,7 @@
+from collections import namedtuple
+
 from six import string_types
 from six.moves import collections_abc
-from collections import namedtuple
 
 
 def clean_none_dict_values(obj):