From 85175972624a1c883b1a330dfb1d3752c75be3f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= 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?= 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?= 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?= 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?= 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):