This commit is contained in:
Odd Stråbø 2024-06-10 06:09:04 +02:00 committed by GitHub
commit aca89a9974
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 134 additions and 137 deletions

View file

@ -41,8 +41,11 @@ def create_addon_xml(config: dict, source: str, py_version: str) -> None:
# Populate dependencies in template # Populate dependencies in template
dependencies = config['dependencies'].get(py_version) dependencies = config['dependencies'].get(py_version)
requires_el = root.find('requires')
assert isinstance(requires_el, ET.Element), "Unable to find requires element in template"
for dep in dependencies: for dep in dependencies:
ET.SubElement(root.find('requires'), 'import', attrib=dep) ET.SubElement(requires_el, 'import', attrib=dep)
# Populate version string # Populate version string
addon_version = config.get('version') addon_version = config.get('version')
@ -110,7 +113,6 @@ def folder_filter(folder_name: str) -> bool:
return True return True
if __name__ == '__main__': if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Build flags:') parser = argparse.ArgumentParser(description='Build flags:')
parser.add_argument( parser.add_argument(

View file

@ -7,7 +7,7 @@ import os
from kodi_six import xbmc, xbmcaddon, xbmcvfs from kodi_six import xbmc, xbmcaddon, xbmcvfs
from .helper import translate, window, settings, addon_id, dialog, LazyLogger from .helper import translate, window, settings, ADDON_ID, dialog, LazyLogger
from .helper.utils import create_id, translate_path from .helper.utils import create_id, translate_path
################################################################################################## ##################################################################################################
@ -21,11 +21,11 @@ def get_addon_name():
''' Used for logging. ''' Used for logging.
''' '''
return xbmcaddon.Addon(addon_id()).getAddonInfo('name').upper() return xbmcaddon.Addon(ADDON_ID).getAddonInfo('name').upper()
def get_version(): def get_version():
return xbmcaddon.Addon(addon_id()).getAddonInfo('version') return xbmcaddon.Addon(ADDON_ID).getAddonInfo('version')
def get_platform(): def get_platform():

View file

@ -8,7 +8,7 @@ from kodi_six import xbmc, xbmcaddon
from . import client from . import client
from .database import get_credentials, save_credentials from .database import get_credentials, save_credentials
from .dialogs import ServerConnect, UsersConnect, LoginManual, ServerManual from .dialogs import ServerConnect, UsersConnect, LoginManual, ServerManual
from .helper import settings, addon_id, event, api, window, LazyLogger from .helper import settings, ADDON_ID, event, api, window, LazyLogger
from .jellyfin import Jellyfin from .jellyfin import Jellyfin
from .jellyfin.connection_manager import CONNECTION_STATE from .jellyfin.connection_manager import CONNECTION_STATE
from .helper.exceptions import HTTPException from .helper.exceptions import HTTPException
@ -16,7 +16,7 @@ from .helper.exceptions import HTTPException
################################################################################################## ##################################################################################################
LOG = LazyLogger(__name__) LOG = LazyLogger(__name__)
XML_PATH = (xbmcaddon.Addon(addon_id()).getAddonInfo('path'), "default", "1080i") XML_PATH = (xbmcaddon.Addon(ADDON_ID).getAddonInfo('path'), "default", "1080i")
################################################################################################## ##################################################################################################
@ -129,7 +129,7 @@ class Connect(object):
except RuntimeError as error: except RuntimeError as error:
LOG.exception(error) LOG.exception(error)
xbmc.executebuiltin('Addon.OpenSettings(%s)' % addon_id()) xbmc.executebuiltin('Addon.OpenSettings(%s)' % ADDON_ID)
raise Exception('User sign in interrupted') raise Exception('User sign in interrupted')
@ -144,17 +144,17 @@ class Connect(object):
''' Save user info. ''' Save user info.
''' '''
self.user = client.jellyfin.get_user() user = client.jellyfin.get_user()
settings('username', self.user['Name']) settings('username', user['Name'])
if 'PrimaryImageTag' in self.user: if 'PrimaryImageTag' in user:
server_address = client.auth.get_server_info(client.auth.server_id)['address'] server_address = client.auth.get_server_info(client.auth.server_id)['address']
window('JellyfinUserImage', api.API(self.user, server_address).get_user_artwork(self.user['Id'])) window('JellyfinUserImage', api.API(user, server_address).get_user_artwork(user['Id']))
def select_servers(self, state=None): def select_servers(self, state=None):
state = state or self.connect_manager.connect({'enableAutoLogin': False}) state = state or self.connect_manager.connect({'enableAutoLogin': False})
user = {} user = {} # TODO: Fixme: content of this dict is used below, but can never contain anything
dialog = ServerConnect("script-jellyfin-connect-server.xml", *XML_PATH) dialog = ServerConnect("script-jellyfin-connect-server.xml", *XML_PATH)
dialog.set_args( dialog.set_args(

View file

@ -8,6 +8,7 @@ import os
import sqlite3 import sqlite3
import sys import sys
import re import re
from typing import Any, Dict
from kodi_six import xbmc, xbmcvfs from kodi_six import xbmc, xbmcvfs
from six import text_type from six import text_type
@ -36,7 +37,7 @@ class Database(object):
''' '''
timeout = 120 timeout = 120
discovered = False discovered = False
discovered_file = None discovered_file: str = None
def __init__(self, db_file=None, commit_close=True): def __init__(self, db_file=None, commit_close=True):
@ -319,7 +320,7 @@ def reset_artwork():
LOG.info("[ reset artwork ]") LOG.info("[ reset artwork ]")
def get_sync(): def get_sync() -> dict:
if (3, 0) <= sys.version_info < (3, 6): if (3, 0) <= sys.version_info < (3, 6):
LOG.error("Python versions 3.0-3.5 are NOT supported.") LOG.error("Python versions 3.0-3.5 are NOT supported.")
@ -343,7 +344,7 @@ def get_sync():
return sync return sync
def save_sync(sync): def save_sync(sync: Dict[str, Any]):
if not xbmcvfs.exists(ADDON_DATA): if not xbmcvfs.exists(ADDON_DATA):
xbmcvfs.mkdirs(ADDON_DATA) xbmcvfs.mkdirs(ADDON_DATA)

View file

@ -1,5 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals from __future__ import division, absolute_import, print_function, unicode_literals
from sqlite3 import Cursor
################################################################################################# #################################################################################################
from . import queries as QU from . import queries as QU
@ -14,9 +17,10 @@ LOG = LazyLogger(__name__)
################################################################################################## ##################################################################################################
class JellyfinDatabase(): class JellyfinDatabase:
cursor: Cursor
def __init__(self, cursor): def __init__(self, cursor: Cursor) -> None:
self.cursor = cursor self.cursor = cursor
cursor.row_factory = sqlite_namedtuple_factory cursor.row_factory = sqlite_namedtuple_factory

View file

@ -4,11 +4,12 @@ from __future__ import division, absolute_import, print_function, unicode_litera
################################################################################################## ##################################################################################################
import os import os
from typing import List, cast
from kodi_six import xbmcgui, xbmcaddon from kodi_six import xbmcgui, xbmcaddon
from six import ensure_text from six import ensure_text
from ..helper import window, addon_id from ..helper import window, ADDON_ID
from ..helper import LazyLogger from ..helper import LazyLogger
################################################################################################## ##################################################################################################
@ -27,7 +28,7 @@ USER_IMAGE = 150
class ContextMenu(xbmcgui.WindowXMLDialog): class ContextMenu(xbmcgui.WindowXMLDialog):
_options = [] _options: List[str] = []
selected_option = None selected_option = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -51,7 +52,7 @@ class ContextMenu(xbmcgui.WindowXMLDialog):
self.getControl(USER_IMAGE).setImage(window('JellyfinUserImage')) self.getControl(USER_IMAGE).setImage(window('JellyfinUserImage'))
LOG.info("options: %s", self._options) LOG.info("options: %s", self._options)
self.list_ = self.getControl(LIST) self.list_ = cast(xbmcgui.ControlList, self.getControl(LIST))
for option in self._options: for option in self._options:
self.list_.addItem(self._add_listitem(option)) self.list_.addItem(self._add_listitem(option))
@ -73,7 +74,7 @@ class ContextMenu(xbmcgui.WindowXMLDialog):
def _add_editcontrol(self, x, y, height, width, password=0): def _add_editcontrol(self, x, y, height, width, password=0):
media = os.path.join(xbmcaddon.Addon(addon_id()).getAddonInfo('path'), 'resources', 'skins', 'default', 'media') media = os.path.join(xbmcaddon.Addon(ADDON_ID).getAddonInfo('path'), 'resources', 'skins', 'default', 'media')
control = xbmcgui.ControlImage(0, 0, 0, 0, control = xbmcgui.ControlImage(0, 0, 0, 0,
filename=os.path.join(media, "white.png"), filename=os.path.join(media, "white.png"),
aspectRatio=0, aspectRatio=0,

View file

@ -1,6 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals from __future__ import division, absolute_import, print_function, unicode_literals
from typing import Optional
################################################################################################## ##################################################################################################
from kodi_six import xbmc, xbmcgui from kodi_six import xbmc, xbmcgui
@ -22,7 +24,7 @@ START_BEGINNING = 3011
class ResumeDialog(xbmcgui.WindowXMLDialog): class ResumeDialog(xbmcgui.WindowXMLDialog):
_resume_point = None _resume_point = None
selected_option = None selected_option: Optional[int] = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs)

View file

@ -1,6 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals from __future__ import division, absolute_import, print_function, unicode_literals
from typing import Any, Dict, List
################################################################################################## ##################################################################################################
from six import iteritems from six import iteritems
@ -32,7 +34,7 @@ MANUAL_SERVER = 206
class ServerConnect(xbmcgui.WindowXMLDialog): class ServerConnect(xbmcgui.WindowXMLDialog):
user_image = None user_image = None
servers = [] servers: List[Dict[str, Any]] = []
_selected_server = None _selected_server = None
_connect_login = False _connect_login = False

View file

@ -345,7 +345,7 @@ def browse(media, view_id=None, folder=None, server_id=None, api_client=None):
actions = Actions(server_id, api_client) actions = Actions(server_id, api_client)
list_li = [] list_li = []
listing = listing if type(listing) == list else listing.get('Items', []) listing = listing if isinstance(listing, list) else listing.get('Items', [])
for item in listing: for item in listing:
@ -885,7 +885,7 @@ def backup():
if xbmcvfs.exists(backup + '/'): if xbmcvfs.exists(backup + '/'):
if not dialog("yesno", "{jellyfin}", translate(33090)): if not dialog("yesno", "{jellyfin}", translate(33090)):
return backup() return backup
delete_folder(backup) delete_folder(backup)

View file

@ -6,6 +6,7 @@ from __future__ import division, absolute_import, print_function, unicode_litera
import json import json
import sys import sys
from datetime import datetime from datetime import datetime
from typing import Any, Dict
# Workaround for threads using datetime: _striptime is locked # Workaround for threads using datetime: _striptime is locked
import _strptime # noqa:F401 import _strptime # noqa:F401
@ -37,7 +38,7 @@ class Service(xbmc.Monitor):
monitor = None monitor = None
play_event = None play_event = None
warn = True warn = True
settings = {'last_progress': datetime.today(), 'last_progress_report': datetime.today()} settings: Dict[str, Any] = {'last_progress': datetime.today(), 'last_progress_report': datetime.today()}
def __init__(self): def __init__(self):
@ -264,17 +265,17 @@ class Service(xbmc.Monitor):
window('jellyfin_should_stop.bool', True) window('jellyfin_should_stop.bool', True)
self.running = False self.running = False
elif method in ('SyncLibrarySelection', 'RepairLibrarySelection', 'AddLibrarySelection', 'RemoveLibrarySelection'): elif self.library_thread is not None and method in ('SyncLibrarySelection', 'RepairLibrarySelection', 'AddLibrarySelection', 'RemoveLibrarySelection'):
self.library_thread.select_libraries(method) self.library_thread.select_libraries(method)
elif method == 'SyncLibrary': elif self.library_thread is not None and method == 'SyncLibrary':
if not data.get('Id'): if not data.get('Id'):
return return
self.library_thread.add_library(data['Id'], data.get('Update', False)) self.library_thread.add_library(data['Id'], data.get('Update', False))
xbmc.executebuiltin("Container.Refresh") xbmc.executebuiltin("Container.Refresh")
elif method == 'RepairLibrary': elif self.library_thread is not None and method == 'RepairLibrary':
if not data.get('Id'): if not data.get('Id'):
return return
@ -288,7 +289,7 @@ class Service(xbmc.Monitor):
self.library_thread.add_library(data['Id']) self.library_thread.add_library(data['Id'])
xbmc.executebuiltin("Container.Refresh") xbmc.executebuiltin("Container.Refresh")
elif method == 'RemoveLibrary': elif self.library_thread is not None and method == 'RemoveLibrary':
libraries = data['Id'].split(',') libraries = data['Id'].split(',')
for lib in libraries: for lib in libraries:
@ -309,10 +310,11 @@ class Service(xbmc.Monitor):
self.library_thread = None self.library_thread = None
Jellyfin.close_all() Jellyfin.close_all()
self.monitor.server = [] if self.monitor is not None:
self.monitor.sleep = True self.monitor.server = []
self.monitor.sleep = True
elif method == 'System.OnWake': elif self.monitor is not None and method == 'System.OnWake':
if not self.monitor.sleep: if not self.monitor.sleep:
LOG.warning("System.OnSleep was never called, skip System.OnWake") LOG.warning("System.OnSleep was never called, skip System.OnWake")
@ -365,7 +367,7 @@ class Service(xbmc.Monitor):
self.settings['enable_context_transcode'] = settings('enableContextTranscode.bool') self.settings['enable_context_transcode'] = settings('enableContextTranscode.bool')
LOG.info("New context transcode setting: %s", self.settings['enable_context_transcode']) LOG.info("New context transcode setting: %s", self.settings['enable_context_transcode'])
if settings('useDirectPaths') != self.settings['mode'] and self.library_thread.started: if self.library_thread is not None and settings('useDirectPaths') != self.settings['mode'] and self.library_thread.started:
self.settings['mode'] = settings('useDirectPaths') self.settings['mode'] = settings('useDirectPaths')
LOG.info("New playback mode setting: %s", self.settings['mode']) LOG.info("New playback mode setting: %s", self.settings['mode'])

View file

@ -5,6 +5,7 @@ from __future__ import division, absolute_import, print_function, unicode_litera
from contextlib import contextmanager from contextlib import contextmanager
import datetime import datetime
from typing import Any, Dict
from kodi_six import xbmc from kodi_six import xbmc
@ -29,8 +30,8 @@ class FullSync(object):
sync.libraries() sync.libraries()
''' '''
# Borg - multiple instances, shared state # Borg - multiple instances, shared state
_shared_state = {} _shared_state: Dict[str, Any] = {}
sync = None sync = get_sync()
running = False running = False
screensaver = None screensaver = None

View file

@ -4,7 +4,7 @@ from .lazylogger import LazyLogger
from .translate import translate from .translate import translate
from .utils import addon_id from .utils import ADDON_ID
from .utils import window from .utils import window
from .utils import settings from .utils import settings
from .utils import kodi_version from .utils import kodi_version

View file

@ -1,6 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals from __future__ import division, absolute_import, print_function, unicode_literals
from typing import Any, Dict, List
################################################################################################## ##################################################################################################
from . import settings, LazyLogger from . import settings, LazyLogger
@ -42,7 +44,7 @@ class API(object):
return self.item['Name'] return self.item['Name']
def get_actors(self): def get_actors(self):
cast = [] cast: List[Dict[str, Any]] = []
if 'People' in self.item: if 'People' in self.item:
self.get_people_artwork(self.item['People']) self.get_people_artwork(self.item['People'])

View file

@ -6,6 +6,7 @@ from __future__ import division, absolute_import, print_function, unicode_litera
import os import os
import logging import logging
import traceback import traceback
from typing import Dict, List
from six import ensure_text from six import ensure_text
from kodi_six import xbmc, xbmcaddon from kodi_six import xbmc, xbmcaddon
@ -36,7 +37,7 @@ class LogHandler(logging.StreamHandler):
logging.StreamHandler.__init__(self) logging.StreamHandler.__init__(self)
self.setFormatter(MyFormatter()) self.setFormatter(MyFormatter())
self.sensitive = {'Token': [], 'Server': []} self.sensitive: Dict[str, List[str]] = {'Token': [], 'Server': []}
for server in database.get_credentials()['Servers']: for server in database.get_credentials()['Servers']:

View file

@ -18,7 +18,7 @@ def translate(string):
''' Get add-on string. Returns in unicode. ''' Get add-on string. Returns in unicode.
''' '''
if type(string) != int: if not isinstance(string, int):
string = STRINGS[string] string = STRINGS[string]
result = xbmcaddon.Addon('plugin.video.jellyfin').getLocalizedString(string) result = xbmcaddon.Addon('plugin.video.jellyfin').getLocalizedString(string)

View file

@ -29,11 +29,10 @@ LOG = LazyLogger(__name__)
################################################################################################# #################################################################################################
def addon_id(): ADDON_ID = "plugin.video.jellyfin"
return "plugin.video.jellyfin"
def kodi_version(): def kodi_version() -> int:
# Kodistubs returns empty string, causing Python 3 tests to choke on int() # Kodistubs returns empty string, causing Python 3 tests to choke on int()
# TODO: Make Kodistubs version configurable for testing purposes # TODO: Make Kodistubs version configurable for testing purposes
if sys.version_info.major == 2: if sys.version_info.major == 2:
@ -84,7 +83,7 @@ def settings(setting, value=None):
''' Get or add add-on settings. ''' Get or add add-on settings.
getSetting returns unicode object. getSetting returns unicode object.
''' '''
addon = xbmcaddon.Addon(addon_id()) addon = xbmcaddon.Addon(ADDON_ID)
if value is not None: if value is not None:
if setting.endswith('.bool'): if setting.endswith('.bool'):
@ -154,7 +153,8 @@ def event(method, data=None, sender=None, hexlify=False):
xbmc.executebuiltin('NotifyAll(%s, %s, %s)' % (sender, method, data)) xbmc.executebuiltin('NotifyAll(%s, %s, %s)' % (sender, method, data))
def dialog(dialog_type, *args, **kwargs): def dialog(dialog_type: str, *args: str, **kwargs):
arg_list = list(args)
d = xbmcgui.Dialog() d = xbmcgui.Dialog()
@ -166,9 +166,8 @@ def dialog(dialog_type, *args, **kwargs):
if "heading" in kwargs: if "heading" in kwargs:
kwargs['heading'] = kwargs['heading'].replace("{jellyfin}", translate('addon_name')) kwargs['heading'] = kwargs['heading'].replace("{jellyfin}", translate('addon_name'))
if args: if arg_list:
args = list(args) arg_list[0] = arg_list[0].replace("{jellyfin}", translate('addon_name'))
args[0] = args[0].replace("{jellyfin}", translate('addon_name'))
types = { types = {
'yesno': d.yesno, 'yesno': d.yesno,
@ -179,7 +178,7 @@ def dialog(dialog_type, *args, **kwargs):
'numeric': d.numeric, 'numeric': d.numeric,
'multi': d.multiselect 'multi': d.multiselect
} }
return types[dialog_type](*args, **kwargs) return types[dialog_type](*arg_list, **kwargs)
def should_stop(): def should_stop():

View file

@ -27,7 +27,7 @@ def progress(message=None):
dialog = xbmcgui.DialogProgressBG() dialog = xbmcgui.DialogProgressBG()
if item and type(item) == dict: if item and isinstance(item, dict):
dialog.create(translate('addon_name'), "%s %s" % (translate('gathering'), item['Name'])) dialog.create(translate('addon_name'), "%s %s" % (translate('gathering'), item['Name']))
LOG.info("Processing %s: %s", item['Name'], item['Id']) LOG.info("Processing %s: %s", item['Name'], item['Id'])
@ -72,7 +72,7 @@ def jellyfin_item(func):
''' Wrapper to retrieve the jellyfin_db item. ''' Wrapper to retrieve the jellyfin_db item.
''' '''
def wrapper(self, item, *args, **kwargs): def wrapper(self, item, *args, **kwargs):
e_item = self.jellyfin_db.get_item_by_id(item['Id'] if type(item) == dict else item) e_item = self.jellyfin_db.get_item_by_id(item['Id'] if isinstance(item, dict) else item)
return func(self, item, e_item=e_item, *args, **kwargs) return func(self, item, e_item=e_item, *args, **kwargs)

View file

@ -27,7 +27,7 @@ def tvtunes_nfo(path, urls):
except Exception: except Exception:
xml = etree.Element('tvtunes') xml = etree.Element('tvtunes')
for elem in xml.getiterator('tvtunes'): for elem in xml.iter('tvtunes'):
for file in list(elem): for file in list(elem):
elem.remove(file) elem.remove(file)
@ -72,6 +72,7 @@ def advanced_settings():
return True return True
def verify_kodi_defaults(): def verify_kodi_defaults():
''' Make sure we have the kodi default folder in place. ''' Make sure we have the kodi default folder in place.
''' '''

View file

@ -1,6 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals from __future__ import division, absolute_import, print_function, unicode_literals
from typing import Any, Dict
################################################################################################# #################################################################################################
from ..helper import has_attribute, LazyLogger from ..helper import has_attribute, LazyLogger
@ -43,16 +45,15 @@ class Jellyfin(object):
''' '''
# Borg - multiple instances, shared state # Borg - multiple instances, shared state
_shared_state = {} _shared_state: Dict[str, Any] = {}
client = {} client: Dict[str, JellyfinClient] = {}
server_id = "default" server_id: str = "default"
def __init__(self, server_id=None): def __init__(self, server_id=None):
self.__dict__ = self._shared_state self.__dict__ = self._shared_state
self.server_id = server_id or "default" self.server_id = server_id or "default"
def get_client(self): def get_client(self) -> JellyfinClient:
# type: () -> JellyfinClient
return self.client[self.server_id] return self.client[self.server_id]
def close(self): def close(self):
@ -71,7 +72,7 @@ class Jellyfin(object):
for client in cls.client: for client in cls.client:
cls.client[client].stop() cls.client[client].stop()
cls.client = {} cls.client.clear()
LOG.info("---[ STOPPED ALL JELLYFINCLIENTS ]---") LOG.info("---[ STOPPED ALL JELLYFINCLIENTS ]---")
@classmethod @classmethod

View file

@ -8,6 +8,7 @@ import socket
from datetime import datetime from datetime import datetime
from operator import itemgetter from operator import itemgetter
import traceback import traceback
from typing import Optional
import urllib3 import urllib3
@ -31,8 +32,7 @@ CONNECTION_STATE = {
class ConnectionManager(object): class ConnectionManager(object):
user = {} server_id: Optional[str] = None
server_id = None
def __init__(self, client): def __init__(self, client):

View file

@ -33,7 +33,7 @@ def clean_none_dict_values(obj):
if mutable: if mutable:
# Remove keys with None value # Remove keys with None value
for key in remove: for key in remove:
item.pop(key) item.pop(key) # typing: ignore [attr-defined]
elif isinstance(item, collections_abc.Iterable): elif isinstance(item, collections_abc.Iterable):
for value in item: for value in item:

View file

@ -14,7 +14,7 @@ from ..helper import LazyLogger, settings
# If numpy is installed, the websockets library tries to use it, and then # If numpy is installed, the websockets library tries to use it, and then
# kodi hard crashes for reasons I don't even want to pretend to understand # kodi hard crashes for reasons I don't even want to pretend to understand
import sys # noqa: E402,I100 import sys # noqa: E402,I100
sys.modules['numpy'] = None sys.modules['numpy'] = None # type: ignore [assignment]
import websocket # noqa: E402,I201 import websocket # noqa: E402,I201
################################################################################################## ##################################################################################################

View file

@ -5,6 +5,7 @@ from __future__ import division, absolute_import, print_function, unicode_litera
import threading import threading
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Dict, List, Tuple
from six.moves import queue as Queue from six.moves import queue as Queue
@ -48,18 +49,18 @@ class Library(threading.Thread):
self.monitor = monitor self.monitor = monitor
self.player = monitor.monitor.player self.player = monitor.monitor.player
self.server = Jellyfin().get_client() self.server = Jellyfin().get_client()
self.updated_queue = Queue.Queue() self.updated_queue: Queue.Queue[str] = Queue.Queue()
self.userdata_queue = Queue.Queue() self.userdata_queue: Queue.Queue[str] = Queue.Queue()
self.removed_queue = Queue.Queue() self.removed_queue: Queue.Queue[str] = Queue.Queue()
self.updated_output = self.__new_queues__() self.updated_output = self.__new_queues__()
self.userdata_output = self.__new_queues__() self.userdata_output = self.__new_queues__()
self.removed_output = self.__new_queues__() self.removed_output = self.__new_queues__()
self.notify_output = Queue.Queue() self.notify_output: Queue.Queue[Tuple[str, str]] = Queue.Queue()
self.jellyfin_threads = [] self.jellyfin_threads = []
self.download_threads = [] self.download_threads = []
self.notify_threads = [] self.notify_threads = []
self.writer_threads = {'updated': [], 'userdata': [], 'removed': []} self.writer_threads: Dict[str, List[BaseWorker]] = {'updated': [], 'userdata': [], 'removed': []}
self.database_lock = threading.Lock() self.database_lock = threading.Lock()
self.music_database_lock = threading.Lock() self.music_database_lock = threading.Lock()
@ -231,10 +232,10 @@ class Library(threading.Thread):
''' Get items from jellyfin and place them in the appropriate queues. ''' Get items from jellyfin and place them in the appropriate queues.
''' '''
for queue in ((self.updated_queue, self.updated_output), (self.userdata_queue, self.userdata_output)): for input_queue, output_queue in ((self.updated_queue, self.updated_output), (self.userdata_queue, self.userdata_output)):
if queue[0].qsize() and len(self.download_threads) < DTHREADS: if input_queue.qsize() and len(self.download_threads) < DTHREADS:
new_thread = GetItemWorker(self.server, queue[0], queue[1]) new_thread = GetItemWorker(self.server, input_queue, output_queue)
new_thread.start() new_thread.start()
LOG.info("-->[ q:download/%s ]", id(new_thread)) LOG.info("-->[ q:download/%s ]", id(new_thread))
self.download_threads.append(new_thread) self.download_threads.append(new_thread)
@ -600,7 +601,11 @@ class Library(threading.Thread):
LOG.info("---[ removed:%s ]", len(data)) LOG.info("---[ removed:%s ]", len(data))
class UpdateWorker(threading.Thread): class BaseWorker(threading.Thread):
is_done: bool
class UpdateWorker(BaseWorker):
is_done = False is_done = False
@ -676,7 +681,7 @@ class UpdateWorker(threading.Thread):
self.is_done = True self.is_done = True
class UserDataWorker(threading.Thread): class UserDataWorker(BaseWorker):
is_done = False is_done = False
@ -739,7 +744,7 @@ class UserDataWorker(threading.Thread):
self.is_done = True self.is_done = True
class SortWorker(threading.Thread): class SortWorker(BaseWorker):
is_done = False is_done = False
@ -786,7 +791,7 @@ class SortWorker(threading.Thread):
self.is_done = True self.is_done = True
class RemovedWorker(threading.Thread): class RemovedWorker(BaseWorker):
is_done = False is_done = False
@ -847,7 +852,7 @@ class RemovedWorker(threading.Thread):
self.is_done = True self.is_done = True
class NotifyWorker(threading.Thread): class NotifyWorker(BaseWorker):
is_done = False is_done = False

View file

@ -6,6 +6,7 @@ from __future__ import division, absolute_import, print_function, unicode_litera
import binascii import binascii
import json import json
import threading import threading
from typing import List
from kodi_six import xbmc from kodi_six import xbmc
@ -27,7 +28,7 @@ LOG = LazyLogger(__name__)
class Monitor(xbmc.Monitor): class Monitor(xbmc.Monitor):
servers = [] servers: List[str] = []
sleep = False sleep = False
def __init__(self): def __init__(self):
@ -116,11 +117,11 @@ class Monitor(xbmc.Monitor):
LOG.exception(error) LOG.exception(error)
server = Jellyfin() server = Jellyfin()
server = server.get_client() server_client = server.get_client()
if method == 'Play': if method == 'Play':
items = server.jellyfin.get_items(data['ItemIds']) items = server_client.jellyfin.get_items(data['ItemIds'])
PlaylistWorker(data.get('ServerId'), items, data['PlayCommand'] == 'PlayNow', PlaylistWorker(data.get('ServerId'), items, data['PlayCommand'] == 'PlayNow',
data.get('StartPositionTicks', 0), data.get('AudioStreamIndex'), data.get('StartPositionTicks', 0), data.get('AudioStreamIndex'),
@ -129,7 +130,7 @@ class Monitor(xbmc.Monitor):
# TODO no clue if this is called by anything # TODO no clue if this is called by anything
elif method == 'PlayPlaylist': elif method == 'PlayPlaylist':
server.jellyfin.post_session(server.config.data['app.session'], "Playing", { server_client.jellyfin.post_session(server_client.config.data['app.session'], "Playing", {
'PlayCommand': "PlayNow", 'PlayCommand': "PlayNow",
'ItemIds': data['Id'], 'ItemIds': data['Id'],
'StartPositionTicks': 0 'StartPositionTicks': 0
@ -148,14 +149,14 @@ class Monitor(xbmc.Monitor):
self.server_instance(data['ServerId']) self.server_instance(data['ServerId'])
elif method == 'AddUser': elif method == 'AddUser':
server.jellyfin.session_add_user(server.config.data['app.session'], data['Id'], data['Add']) server_client.jellyfin.session_add_user(server_client.config.data['app.session'], data['Id'], data['Add'])
self.additional_users(server) self.additional_users(server_client)
elif method == 'Player.OnPlay': elif method == 'Player.OnPlay':
on_play(data, server) on_play(data, server_client)
elif method == 'VideoLibrary.OnUpdate': elif method == 'VideoLibrary.OnUpdate':
on_update(data, server) on_update(data, server_client)
def server_instance(self, server_id=None): def server_instance(self, server_id=None):
@ -178,7 +179,6 @@ class Monitor(xbmc.Monitor):
self.additional_users(server) self.additional_users(server)
def additional_users(self, server): def additional_users(self, server):
''' Setup additional users images. ''' Setup additional users images.

View file

@ -1,6 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals from __future__ import division, absolute_import, print_function, unicode_literals
from abc import ABCMeta
from sqlite3 import Cursor
################################################################################################## ##################################################################################################
from ...helper import values, LazyLogger, kodi_version from ...helper import values, LazyLogger, kodi_version
@ -15,7 +18,8 @@ LOG = LazyLogger(__name__)
################################################################################################## ##################################################################################################
class Kodi(object): class Kodi(metaclass=ABCMeta):
cursor: Cursor
def __init__(self): def __init__(self):
self.artwork = artwork.Artwork(self.cursor) self.artwork = artwork.Artwork(self.cursor)

View file

@ -4,6 +4,7 @@ from __future__ import division, absolute_import, print_function, unicode_litera
################################################################################################## ##################################################################################################
import datetime import datetime
from typing import List
from ..database import jellyfin_db, queries as QUEM from ..database import jellyfin_db, queries as QUEM
from ..helper import api, stop, validate, jellyfin_item, values, Local, LazyLogger from ..helper import api, stop, validate, jellyfin_item, values, Local, LazyLogger
@ -551,7 +552,7 @@ class Music(KodiDb):
''' Get all child elements from tv show jellyfin id. ''' Get all child elements from tv show jellyfin id.
''' '''
obj = {'Id': item_id} obj = {'Id': item_id}
child = [] child: List[str] = []
try: try:
obj['KodiId'] = e_item[0] obj['KodiId'] = e_item[0]

View file

@ -5,6 +5,7 @@ from __future__ import division, absolute_import, print_function, unicode_litera
import datetime import datetime
import re import re
from typing import List
from six.moves.urllib.parse import urlencode from six.moves.urllib.parse import urlencode
from kodi_six.utils import py2_encode from kodi_six.utils import py2_encode
@ -122,7 +123,7 @@ class MusicVideos(KodiDb):
if search: if search:
obj['Index'] = search.group() obj['Index'] = search.group()
tags = [] tags: List[str] = []
tags.extend(obj['Tags'] or []) tags.extend(obj['Tags'] or [])
tags.append(obj['LibraryName']) tags.append(obj['LibraryName'])

View file

@ -5,6 +5,7 @@ from __future__ import division, absolute_import, print_function, unicode_litera
import json import json
import os import os
from typing import Any, Dict
from six import iteritems, ensure_text from six import iteritems, ensure_text
@ -20,7 +21,7 @@ LOG = LazyLogger(__name__)
class Objects(object): class Objects(object):
# Borg - multiple instances, shared state # Borg - multiple instances, shared state
_shared_state = {} _shared_state: Dict[str, Any] = {}
def __init__(self): def __init__(self):
@ -107,7 +108,7 @@ class Objects(object):
continue continue
if obj_key: if obj_key:
obj = [d[obj_key] for d in obj if d.get(obj_key)] if type(obj) == list else obj.get(obj_key) obj = [d[obj_key] for d in obj if d.get(obj_key)] if isinstance(obj, list) else obj.get(obj_key)
self.mapped_item[key] = obj self.mapped_item[key] = obj
break break

View file

@ -5,6 +5,7 @@ from __future__ import division, absolute_import, print_function, unicode_litera
import sqlite3 import sqlite3
from ntpath import dirname from ntpath import dirname
from typing import List
from six.moves.urllib.parse import urlencode from six.moves.urllib.parse import urlencode
from kodi_six.utils import py2_encode from kodi_six.utils import py2_encode
@ -101,7 +102,7 @@ class TVShows(KodiDb):
if obj['Premiere']: if obj['Premiere']:
obj['Premiere'] = str(Local(obj['Premiere'])).split('.')[0].replace('T', " ") obj['Premiere'] = str(Local(obj['Premiere'])).split('.')[0].replace('T', " ")
tags = [] tags: List[str] = []
tags.extend(obj['Tags'] or []) tags.extend(obj['Tags'] or [])
tags.append(obj['LibraryName']) tags.append(obj['LibraryName'])
@ -656,7 +657,7 @@ class TVShows(KodiDb):
''' Get all child elements from tv show jellyfin id. ''' Get all child elements from tv show jellyfin id.
''' '''
obj = {'Id': item_id} obj = {'Id': item_id}
child = [] child: List[str] = []
try: try:
obj['KodiId'] = e_item[0] obj['KodiId'] = e_item[0]

View file

@ -4,6 +4,7 @@ from __future__ import division, absolute_import, print_function, unicode_litera
################################################################################################# #################################################################################################
import os import os
from typing import Dict
from kodi_six import xbmc, xbmcvfs from kodi_six import xbmc, xbmcvfs
@ -22,7 +23,7 @@ LOG = LazyLogger(__name__)
class Player(xbmc.Player): class Player(xbmc.Player):
played = {} played: Dict[str, dict] = {}
up_next = False up_next = False
def __init__(self): def __init__(self):

View file

@ -411,7 +411,7 @@ class Views(object):
etree.SubElement(xml, 'content') etree.SubElement(xml, 'content')
label = xml.find('label') label = xml.find('label')
label.text = str(name) if type(name) == int else name label.text = str(name) if isinstance(name, int) else name
content = xml.find('content') content = xml.find('content')
content.text = view['Media'] content.text = view['Media']
@ -772,7 +772,7 @@ class Views(object):
else: else:
window_path = "ActivateWindow(Videos,%s,return)" % path window_path = "ActivateWindow(Videos,%s,return)" % path
node_label = translate(node_label) if type(node_label) == int else node_label node_label = translate(node_label) if isinstance(node_label, int) else node_label
node_label = node_label or view['Name'] node_label = node_label or view['Name']
if node in ('all', 'music'): if node in ('all', 'music'):
@ -824,7 +824,7 @@ class Views(object):
else: else:
window_path = "ActivateWindow(Videos,%s,return)" % path window_path = "ActivateWindow(Videos,%s,return)" % path
node_label = translate(node_label) if type(node_label) == int else node_label node_label = translate(node_label) if isinstance(node_label, int) else node_label
node_label = node_label or view['Name'] node_label = node_label or view['Name']
if node == 'all': if node == 'all':

4
mypy.ini Normal file
View file

@ -0,0 +1,4 @@
[mypy]
check_untyped_defs = True
warn_unused_configs = True
files = .

View file

@ -1,7 +1,7 @@
[flake8] [flake8]
max-line-length = 9999 max-line-length = 9999
import-order-style = pep8 import-order-style = pep8
exclude = .git,.vscode,libraries,build.py,.github exclude = .git,.vscode,libraries,.github
extend-ignore = extend-ignore =
I202 I202
per-file-ignores = per-file-ignores =

View file

@ -1,39 +0,0 @@
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: ...

View file

@ -1 +0,0 @@
def kodi_version(self) -> int: ...