jellyfin-kodi/jellyfin_kodi/library.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1003 lines
32 KiB
Python
Raw Normal View History

2018-09-06 08:36:32 +00:00
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, print_function, unicode_literals
2018-09-06 08:36:32 +00:00
##################################################################################################
import threading
from datetime import datetime, timedelta
2024-06-11 02:56:52 +00:00
import queue
2024-06-11 05:10:57 +00:00
import xbmc
import xbmcgui
2018-09-06 08:36:32 +00:00
2021-10-10 18:38:25 +00:00
from .objects import Movies, TVShows, MusicVideos, Music
from .objects.kodi import Movies as KodiDb
2021-10-10 18:38:25 +00:00
from .database import Database, jellyfin_db, get_sync, save_sync
from .full_sync import FullSync
from .views import Views
from .downloader import GetItemWorker
from .helper import translate, api, stop, settings, window, dialog, event, LazyLogger
from .helper.utils import split_list, set_screensaver, get_screensaver
from .helper.exceptions import LibraryException
from .jellyfin import Jellyfin
2018-09-06 08:36:32 +00:00
##################################################################################################
2020-04-19 01:05:59 +00:00
LOG = LazyLogger(__name__)
LIMIT = int(settings("limitIndex") or 15)
2018-10-01 00:35:31 +00:00
DTHREADS = int(settings("limitThreads") or 3)
TARGET_DB_VERSION = 1
2018-09-06 08:36:32 +00:00
##################################################################################################
class Library(threading.Thread):
started = False
stop_thread = False
suspend = False
pending_refresh = False
2018-10-04 03:36:57 +00:00
screensaver = None
progress_updates = None
total_updates = 0
2018-09-06 08:36:32 +00:00
def __init__(self, monitor):
self.direct_path = settings("useDirectPaths") == "1"
2018-10-04 03:36:57 +00:00
self.progress_display = int(settings("syncProgress") or 50)
2018-09-06 08:36:32 +00:00
self.monitor = monitor
2018-10-04 05:41:31 +00:00
self.player = monitor.monitor.player
self.server = Jellyfin().get_client()
2024-06-11 02:56:52 +00:00
self.updated_queue = queue.Queue()
self.userdata_queue = queue.Queue()
self.removed_queue = queue.Queue()
2018-09-06 08:36:32 +00:00
self.updated_output = self.__new_queues__()
self.userdata_output = self.__new_queues__()
self.removed_output = self.__new_queues__()
2024-06-11 02:56:52 +00:00
self.notify_output = queue.Queue()
2018-09-06 08:36:32 +00:00
self.jellyfin_threads = []
2018-09-06 08:36:32 +00:00
self.download_threads = []
2018-10-04 05:41:31 +00:00
self.notify_threads = []
2018-09-06 08:36:32 +00:00
self.writer_threads = {"updated": [], "userdata": [], "removed": []}
self.database_lock = threading.Lock()
self.music_database_lock = threading.Lock()
threading.Thread.__init__(self)
def __new_queues__(self):
return {
2024-06-11 02:56:52 +00:00
"Movie": queue.Queue(),
"BoxSet": queue.Queue(),
"MusicVideo": queue.Queue(),
"Series": queue.Queue(),
"Season": queue.Queue(),
"Episode": queue.Queue(),
"MusicAlbum": queue.Queue(),
"MusicArtist": queue.Queue(),
"AlbumArtist": queue.Queue(),
"Audio": queue.Queue(),
2018-09-06 08:36:32 +00:00
}
def run(self):
LOG.info("--->[ library ]")
2018-09-06 08:36:32 +00:00
if not self.startup():
self.stop_client()
window("jellyfin_startup.bool", True)
2018-09-06 08:36:32 +00:00
while not self.stop_thread:
try:
self.service()
except LibraryException:
2018-09-06 08:36:32 +00:00
break
except Exception as error:
LOG.exception(error)
break
if self.monitor.waitForAbort(2):
break
LOG.info("---<[ library ]")
2018-09-06 08:36:32 +00:00
2019-01-10 23:38:57 +00:00
def test_databases(self):
"""Open the databases to test if the file exists."""
with Database("video"), Database("music"):
pass
2019-01-10 23:38:57 +00:00
def check_version(self):
2024-06-10 09:19:47 +00:00
"""
Checks database version and triggers any required data migrations
2024-06-10 09:19:47 +00:00
"""
with Database("jellyfin") as jellyfindb:
db = jellyfin_db.JellyfinDatabase(jellyfindb.cursor)
db_version = db.get_version()
if not db_version:
# Make sure we always have a version in the database
db.add_version((TARGET_DB_VERSION))
# Video Database Migrations
with Database("video") as videodb:
vid_db = KodiDb(videodb.cursor)
if vid_db.migrations():
LOG.info("changes detected, reloading skin")
xbmc.executebuiltin("UpdateLibrary(video)")
xbmc.executebuiltin("ReloadSkin()")
2020-07-31 21:53:48 +00:00
@stop
2018-09-06 08:36:32 +00:00
def service(self):
"""If error is encountered, it will rerun this function.
Start new "daemon threads" to process library updates.
(actual daemon thread is not supported in Kodi)
2024-06-10 09:19:47 +00:00
"""
self.download_threads = [
thread for thread in self.download_threads if not thread.is_done
]
self.writer_threads["updated"] = [
thread for thread in self.writer_threads["updated"] if not thread.is_done
]
self.writer_threads["userdata"] = [
thread for thread in self.writer_threads["userdata"] if not thread.is_done
]
self.writer_threads["removed"] = [
thread for thread in self.writer_threads["removed"] if not thread.is_done
]
2024-06-10 09:19:47 +00:00
2018-11-19 06:12:46 +00:00
if (
not self.player.isPlayingVideo()
or settings("syncDuringPlay.bool")
or xbmc.getCondVisibility("VideoPlayer.Content(livetv)")
):
2018-10-04 03:36:57 +00:00
self.worker_downloads()
2018-11-17 09:46:22 +00:00
self.worker_sort()
self.worker_updates()
self.worker_userdata()
self.worker_remove()
self.worker_notify()
2018-10-04 03:36:57 +00:00
if self.pending_refresh:
window("jellyfin_sync.bool", True)
2018-10-04 03:36:57 +00:00
2018-11-17 09:46:22 +00:00
if self.total_updates > self.progress_display:
queue_size = self.worker_queue_size()
2018-10-04 03:36:57 +00:00
if self.progress_updates is None:
2018-10-04 03:36:57 +00:00
self.progress_updates = xbmcgui.DialogProgressBG()
self.progress_updates.create(
translate("addon_name"), translate(33178)
)
self.progress_updates.update(
int(
2024-06-10 09:19:47 +00:00
(
float(self.total_updates - queue_size)
/ float(self.total_updates)
2024-06-10 09:19:47 +00:00
)
* 100
2024-06-10 09:19:47 +00:00
),
message="%s: %s" % (translate(33178), queue_size),
2024-06-10 09:19:47 +00:00
)
elif queue_size:
self.progress_updates.update(
int(
2024-06-10 09:19:47 +00:00
(
float(self.total_updates - queue_size)
/ float(self.total_updates)
)
* 100
2024-06-10 09:19:47 +00:00
),
message="%s: %s" % (translate(33178), queue_size),
2024-06-10 09:19:47 +00:00
)
2018-10-04 03:36:57 +00:00
else:
self.progress_updates.update(
int(
2024-06-10 09:19:47 +00:00
(
float(self.total_updates - queue_size)
/ float(self.total_updates)
)
* 100
2024-06-10 09:19:47 +00:00
),
message=translate(33178),
2024-06-10 09:19:47 +00:00
)
2018-10-04 03:36:57 +00:00
if not settings("dbSyncScreensaver.bool") and self.screensaver is None:
2024-06-10 09:19:47 +00:00
2018-10-04 03:36:57 +00:00
xbmc.executebuiltin("InhibitIdleShutdown(true)")
self.screensaver = get_screensaver()
set_screensaver(value="")
2019-07-09 20:05:28 +00:00
2019-10-03 02:14:54 +00:00
if (
self.pending_refresh
and not self.download_threads
and not self.writer_threads["updated"]
and not self.writer_threads["userdata"]
and not self.writer_threads["removed"]
):
2018-10-04 03:36:57 +00:00
self.pending_refresh = False
self.save_last_sync()
self.total_updates = 0
window("jellyfin_sync", clear=True)
2018-10-04 03:36:57 +00:00
if self.progress_updates:
self.progress_updates.close()
self.progress_updates = None
if not settings("dbSyncScreensaver.bool") and self.screensaver is not None:
xbmc.executebuiltin("InhibitIdleShutdown(false)")
set_screensaver(value=self.screensaver)
self.screensaver = None
2019-10-03 02:14:54 +00:00
if xbmc.getCondVisibility(
"Container.Content(musicvideos)"
): # Prevent cursor from moving
2018-10-04 03:36:57 +00:00
xbmc.executebuiltin("Container.Refresh")
2019-10-03 02:14:54 +00:00
else: # Update widgets
2018-10-04 03:36:57 +00:00
xbmc.executebuiltin("UpdateLibrary(video)")
if xbmc.getCondVisibility("Window.IsMedia"):
xbmc.executebuiltin("Container.Refresh")
2018-10-04 03:36:57 +00:00
def stop_client(self):
self.stop_thread = True
2019-01-25 11:32:29 +00:00
def enable_pending_refresh(self):
"""When there's an active thread. Let the main thread know."""
self.pending_refresh = True
window("jellyfin_sync.bool", True)
2019-01-25 11:32:29 +00:00
2018-10-04 03:36:57 +00:00
def worker_queue_size(self):
"""Get how many items are queued up for worker threads."""
2018-10-04 03:36:57 +00:00
total = 0
for queues in self.updated_output:
total += self.updated_output[queues].qsize()
for queues in self.userdata_output:
total += self.userdata_output[queues].qsize()
for queues in self.removed_output:
total += self.removed_output[queues].qsize()
return total
def worker_downloads(self):
"""Get items from jellyfin and place them in the appropriate queues."""
2024-06-11 02:56:52 +00:00
for work_queue in (
2018-09-06 08:36:32 +00:00
(self.updated_queue, self.updated_output),
(self.userdata_queue, self.userdata_output),
):
2024-06-11 02:56:52 +00:00
if work_queue[0].qsize() and len(self.download_threads) < DTHREADS:
2019-07-09 20:05:28 +00:00
2024-06-11 02:56:52 +00:00
new_thread = GetItemWorker(self.server, work_queue[0], work_queue[1])
2018-09-06 08:36:32 +00:00
new_thread.start()
LOG.info("-->[ q:download/%s ]", id(new_thread))
self.download_threads.append(new_thread)
2018-09-06 08:36:32 +00:00
def worker_sort(self):
"""Get items based on the local jellyfin database and place item in appropriate queues."""
if self.removed_queue.qsize() and len(self.jellyfin_threads) < 2:
2018-09-06 08:36:32 +00:00
new_thread = SortWorker(self.removed_queue, self.removed_output)
new_thread.start()
LOG.info("-->[ q:sort/%s ]", id(new_thread))
2018-10-04 03:36:57 +00:00
def worker_updates(self):
"""Update items in the Kodi database."""
2018-09-06 08:36:32 +00:00
for queues in self.updated_output:
queue = self.updated_output[queues]
2018-10-07 23:19:06 +00:00
if queue.qsize() and not len(self.writer_threads["updated"]):
2024-06-10 09:19:47 +00:00
2018-09-06 08:36:32 +00:00
if queues in ("Audio", "MusicArtist", "AlbumArtist", "MusicAlbum"):
new_thread = UpdateWorker(
queue,
self.notify_output,
self.music_database_lock,
"music",
self.server,
self.direct_path,
)
2018-09-06 08:36:32 +00:00
else:
new_thread = UpdateWorker(
queue,
self.notify_output,
self.database_lock,
"video",
self.server,
self.direct_path,
)
2018-09-06 08:36:32 +00:00
new_thread.start()
LOG.info("-->[ q:updated/%s/%s ]", queues, id(new_thread))
self.writer_threads["updated"].append(new_thread)
2019-01-25 11:32:29 +00:00
self.enable_pending_refresh()
2018-09-06 08:36:32 +00:00
2018-10-04 03:36:57 +00:00
def worker_userdata(self):
"""Update userdata in the Kodi database."""
2018-09-06 08:36:32 +00:00
for queues in self.userdata_output:
queue = self.userdata_output[queues]
2018-10-07 23:19:06 +00:00
if queue.qsize() and not len(self.writer_threads["userdata"]):
2018-09-06 08:36:32 +00:00
if queues in ("Audio", "MusicArtist", "AlbumArtist", "MusicAlbum"):
new_thread = UserDataWorker(
queue,
self.music_database_lock,
"music",
self.server,
self.direct_path,
)
else:
new_thread = UserDataWorker(
queue,
self.database_lock,
"video",
self.server,
self.direct_path,
)
new_thread.start()
LOG.info("-->[ q:userdata/%s/%s ]", queues, id(new_thread))
self.writer_threads["userdata"].append(new_thread)
2019-01-25 11:32:29 +00:00
self.enable_pending_refresh()
2018-09-06 08:36:32 +00:00
2018-10-04 03:36:57 +00:00
def worker_remove(self):
"""Remove items from the Kodi database."""
2018-09-06 08:36:32 +00:00
for queues in self.removed_output:
queue = self.removed_output[queues]
2018-10-07 23:19:06 +00:00
if queue.qsize() and not len(self.writer_threads["removed"]):
2018-09-06 08:36:32 +00:00
if queues in ("Audio", "MusicArtist", "AlbumArtist", "MusicAlbum"):
new_thread = RemovedWorker(
queue,
self.music_database_lock,
"music",
self.server,
self.direct_path,
)
else:
new_thread = RemovedWorker(
queue,
self.database_lock,
"video",
self.server,
self.direct_path,
)
2019-07-09 20:05:28 +00:00
2018-09-06 08:36:32 +00:00
new_thread.start()
LOG.info("-->[ q:removed/%s/%s ]", queues, id(new_thread))
self.writer_threads["removed"].append(new_thread)
2019-01-25 11:32:29 +00:00
self.enable_pending_refresh()
2018-10-04 05:41:31 +00:00
def worker_notify(self):
"""Notify the user of new additions."""
2018-10-07 23:19:06 +00:00
if self.notify_output.qsize() and not len(self.notify_threads):
2018-10-04 05:41:31 +00:00
new_thread = NotifyWorker(self.notify_output, self.player)
new_thread.start()
LOG.info("-->[ q:notify/%s ]", id(new_thread))
self.notify_threads.append(new_thread)
2018-09-06 08:36:32 +00:00
def startup(self):
2019-07-09 20:05:28 +00:00
"""Run at startup.
Check databases.
2019-01-10 23:38:57 +00:00
Check for the server plugin.
2024-06-10 09:19:47 +00:00
"""
2019-01-21 13:01:34 +00:00
self.test_databases()
self.check_version()
2019-01-10 23:38:57 +00:00
2018-09-06 08:36:32 +00:00
Views().get_views()
Views().get_nodes()
try:
if get_sync()["Libraries"]:
try:
2019-01-25 15:30:30 +00:00
with FullSync(self, self.server) as sync:
sync.libraries()
Views().get_nodes()
except Exception as error:
2019-07-09 20:05:28 +00:00
LOG.exception(error)
elif not settings("SyncInstallRunDone.bool"):
2019-07-09 20:05:28 +00:00
2019-01-25 15:30:30 +00:00
with FullSync(self, self.server) as sync:
sync.libraries()
Views().get_nodes()
2018-10-12 05:26:44 +00:00
return True
2020-09-27 15:16:48 +00:00
if settings("SyncInstallRunDone.bool") and settings("kodiCompanion.bool"):
# None == Unknown
if self.server.jellyfin.check_companion_enabled() is not False:
2019-07-09 20:05:28 +00:00
2020-09-27 15:16:48 +00:00
if not self.fast_sync():
dialog("ok", "{jellyfin}", translate(33128))
2018-09-06 08:36:32 +00:00
2020-09-27 15:16:48 +00:00
raise Exception("Failed to retrieve latest updates")
2020-09-27 15:16:48 +00:00
LOG.info("--<[ retrieve changes ]")
2018-09-06 08:36:32 +00:00
# is False
2020-09-27 15:16:48 +00:00
else:
dialog("ok", "{jellyfin}", translate(33099))
settings("kodiCompanion.bool", False)
return True
2018-09-06 08:36:32 +00:00
return True
2018-09-06 08:36:32 +00:00
except LibraryException as error:
LOG.error(error.status)
if error.status in "SyncLibraryLater":
dialog("ok", "{jellyfin}", translate(33129))
2018-09-06 08:36:32 +00:00
settings("SyncInstallRunDone.bool", True)
sync = get_sync()
sync["Libraries"] = []
save_sync(sync)
2018-09-06 08:36:32 +00:00
return True
except Exception as error:
LOG.exception(error)
return False
def fast_sync(self):
"""Movie and userdata not provided by server yet."""
last_sync = settings("LastIncrementalSync")
include = []
2018-09-06 08:36:32 +00:00
filters = ["tvshows", "boxsets", "musicvideos", "music", "movies"]
sync = get_sync()
2020-08-21 12:56:15 +00:00
whitelist = [x.replace("Mixed:", "") for x in sync["Whitelist"]]
2018-09-06 08:36:32 +00:00
LOG.info("--[ retrieve changes ] %s", last_sync)
# Get the item type of each synced library and build list of types to request
for item_id in whitelist:
library = self.server.jellyfin.get_item(item_id)
library_type = library.get("CollectionType")
if library_type in filters:
include.append(library_type)
# Include boxsets if movies are synced
if "movies" in include:
include.append("boxsets")
# Filter down to the list of library types we want to exclude
query_filter = list(set(filters) - set(include))
2018-09-06 08:36:32 +00:00
try:
# Get list of updates from server for synced library types and populate work queues
2020-08-21 12:56:15 +00:00
result = self.server.jellyfin.get_sync_queue(
last_sync, ",".join([x for x in query_filter])
2024-06-10 09:19:47 +00:00
)
2020-07-31 21:45:54 +00:00
2020-07-23 21:48:54 +00:00
if result is None:
return True
2020-07-31 21:45:54 +00:00
updated = []
userdata = []
removed = []
2020-07-31 21:45:54 +00:00
updated.extend(result["ItemsAdded"])
updated.extend(result["ItemsUpdated"])
userdata.extend(result["UserDataChanged"])
removed.extend(result["ItemsRemoved"])
total = len(updated) + len(userdata)
if total > int(settings("syncIndicator") or 99):
"""Inverse yes no, in case the dialog is forced closed by Kodi."""
if dialog(
"yesno",
"{jellyfin}",
translate(33172).replace("{number}", str(total)),
nolabel=translate(107),
yeslabel=translate(106),
):
LOG.warning("Large updates skipped.")
return True
self.updated(updated)
self.userdata(userdata)
self.removed(removed)
2018-09-06 08:36:32 +00:00
except Exception as error:
LOG.exception(error)
return False
return True
def save_last_sync(self):
_raw_time = self.server.config.data["server-time"]
2019-07-09 20:05:28 +00:00
2024-12-21 01:59:03 +00:00
try:
import email.utils
2024-12-21 01:59:03 +00:00
time_now = email.utils.parsedate_to_datetime(_raw_time)
except Exception as error:
LOG.warning(error)
LOG.warning("Failed to parse server time, falling back to client time.")
time_now = datetime.utcnow()
# Add some tolerance in case time is out of sync with server
time_now -= timedelta(minutes=2)
last_sync = time_now.strftime("%Y-%m-%dT%H:%M:%SZ")
2018-09-06 08:36:32 +00:00
settings("LastIncrementalSync", value=last_sync)
LOG.info("--[ sync/%s ]", last_sync)
def select_libraries(self, mode=None):
"""Select from libraries synced. Either update or repair libraries.
Send event back to service.py
2024-06-10 09:19:47 +00:00
"""
modes = {
"SyncLibrarySelection": "SyncLibrary",
"RepairLibrarySelection": "RepairLibrary",
"AddLibrarySelection": "SyncLibrary",
"RemoveLibrarySelection": "RemoveLibrary",
}
2018-09-06 08:36:32 +00:00
sync = get_sync()
whitelist = [x.replace("Mixed:", "") for x in sync["Whitelist"]]
2018-09-06 08:36:32 +00:00
libraries = []
with Database("jellyfin") as jellyfindb:
db = jellyfin_db.JellyfinDatabase(jellyfindb.cursor)
2018-09-06 08:36:32 +00:00
if mode in (
"SyncLibrarySelection",
"RepairLibrarySelection",
"RemoveLibrarySelection",
):
for library in sync["Whitelist"]:
name = db.get_view_name(library.replace("Mixed:", ""))
libraries.append({"Id": library, "Name": name})
else:
available = [x for x in sync["SortedViews"] if x not in whitelist]
for library in available:
view = db.get_view(library)
2018-09-06 08:36:32 +00:00
if view.media_type in (
"movies",
"tvshows",
"musicvideos",
"mixed",
"music",
):
libraries.append({"Id": view.view_id, "Name": view.view_name})
2018-09-06 08:36:32 +00:00
choices = [x["Name"] for x in libraries]
choices.insert(0, translate(33121))
titles = {
"RepairLibrarySelection": 33199,
"SyncLibrarySelection": 33198,
"RemoveLibrarySelection": 33200,
"AddLibrarySelection": 33120,
}
title = titles.get(mode, "Failed to get title {}".format(mode))
selection = dialog("multi", translate(title), choices)
2018-09-06 08:36:32 +00:00
if selection is None:
return
if 0 in selection:
selection = list(range(1, len(libraries) + 1))
selected_libraries = []
for x in selection:
library = libraries[x - 1]
selected_libraries.append(library["Id"])
event(
modes[mode],
{
"Id": ",".join([libraries[x - 1]["Id"] for x in selection]),
"Update": mode == "SyncLibrarySelection",
2024-06-10 09:19:47 +00:00
},
)
2018-09-06 08:36:32 +00:00
def add_library(self, library_id, update=False):
2018-09-06 08:36:32 +00:00
try:
2019-01-25 15:30:30 +00:00
with FullSync(self, server=self.server) as sync:
sync.libraries(library_id, update)
2018-09-06 08:36:32 +00:00
except Exception as error:
LOG.exception(error)
return False
Views().get_nodes()
return True
2019-01-25 15:30:30 +00:00
def remove_library(self, library_id):
2019-01-25 11:32:29 +00:00
2018-09-06 08:36:32 +00:00
try:
2019-01-25 15:30:30 +00:00
with FullSync(self, self.server) as sync:
sync.remove_library(library_id)
2018-09-06 08:36:32 +00:00
Views().remove_library(library_id)
except Exception as error:
LOG.exception(error)
return False
Views().get_views()
2018-09-06 08:36:32 +00:00
Views().get_nodes()
return True
def userdata(self, data):
"""Add item_id to userdata queue."""
if not data:
return
2018-10-01 00:35:31 +00:00
items = [x["ItemId"] for x in data]
2018-09-06 08:36:32 +00:00
2018-10-01 00:35:31 +00:00
for item in split_list(items, LIMIT):
self.userdata_queue.put(item)
2018-09-06 08:36:32 +00:00
2018-10-04 03:36:57 +00:00
self.total_updates += len(items)
2018-10-01 00:35:31 +00:00
LOG.info("---[ userdata:%s ]", len(items))
2018-09-06 08:36:32 +00:00
def updated(self, data):
"""Add item_id to updated queue."""
if not data:
return
2018-10-01 00:35:31 +00:00
for item in split_list(data, LIMIT):
2018-09-06 08:36:32 +00:00
self.updated_queue.put(item)
2018-10-04 03:36:57 +00:00
self.total_updates += len(data)
2018-10-01 00:35:31 +00:00
LOG.info("---[ updated:%s ]", len(data))
2018-09-06 08:36:32 +00:00
def removed(self, data):
"""Add item_id to removed queue."""
if not data:
return
for item in data:
if item in list(self.removed_queue.queue):
continue
self.removed_queue.put(item)
2018-10-04 03:36:57 +00:00
self.total_updates += len(data)
2018-10-01 00:35:31 +00:00
LOG.info("---[ removed:%s ]", len(data))
2018-09-06 08:36:32 +00:00
class UpdateWorker(threading.Thread):
2018-09-06 08:36:32 +00:00
is_done = False
def __init__(
self, queue, notify, lock, database, server=None, direct_path=None, *args
):
2018-09-06 08:36:32 +00:00
self.queue = queue
2018-10-04 05:41:31 +00:00
self.notify_output = notify
self.notify = settings("newContent.bool")
2018-09-06 08:36:32 +00:00
self.lock = lock
self.database = Database(database)
self.args = args
self.server = server
self.direct_path = direct_path
2018-09-06 08:36:32 +00:00
threading.Thread.__init__(self)
def run(self):
with self.lock, self.database as kodidb, Database("jellyfin") as jellyfindb:
default_args = (self.server, jellyfindb, kodidb, self.direct_path)
if kodidb.db_file == "video":
movies = Movies(*default_args)
tvshows = TVShows(*default_args)
musicvideos = MusicVideos(*default_args)
elif kodidb.db_file == "music":
music = Music(*default_args)
else:
# this should not happen
LOG.error(
'"{}" is not a valid Kodi library type.'.format(kodidb.db_file)
2024-06-10 09:19:47 +00:00
)
return
while True:
2018-09-06 08:36:32 +00:00
try:
item = self.queue.get(timeout=1)
2024-06-11 02:56:52 +00:00
except queue.Empty:
break
2018-09-06 08:36:32 +00:00
try:
LOG.debug("{} - {}".format(item["Type"], item["Name"]))
if item["Type"] == "Movie":
movies.movie(item)
elif item["Type"] == "BoxSet":
movies.boxset(item)
elif item["Type"] == "Series":
tvshows.tvshow(item)
elif item["Type"] == "Season":
tvshows.season(item)
elif item["Type"] == "Episode":
tvshows.episode(item)
elif item["Type"] == "MusicVideo":
musicvideos.musicvideo(item)
elif item["Type"] == "MusicAlbum":
music.album(item)
elif item["Type"] == "MusicArtist":
music.artist(item)
elif item["Type"] == "AlbumArtist":
music.albumartist(item)
elif item["Type"] == "Audio":
music.song(item)
if self.notify:
self.notify_output.put(
(item["Type"], api.API(item).get_naming())
2024-06-10 09:19:47 +00:00
)
except LibraryException as error:
if error.status == "StopCalled":
break
except Exception as error:
LOG.exception(error)
2018-09-06 08:36:32 +00:00
self.queue.task_done()
if window("jellyfin_should_stop.bool"):
break
2018-09-06 08:36:32 +00:00
LOG.info("--<[ q:updated/%s ]", id(self))
self.is_done = True
2019-10-03 02:14:54 +00:00
2018-09-06 08:36:32 +00:00
class UserDataWorker(threading.Thread):
is_done = False
2020-08-06 00:31:38 +00:00
def __init__(self, queue, lock, database, server, direct_path):
2018-09-06 08:36:32 +00:00
self.queue = queue
self.lock = lock
self.database = Database(database)
2020-08-06 00:31:38 +00:00
self.server = server
self.direct_path = direct_path
2018-09-06 08:36:32 +00:00
threading.Thread.__init__(self)
def run(self):
with self.lock, self.database as kodidb, Database("jellyfin") as jellyfindb:
default_args = (self.server, jellyfindb, kodidb, self.direct_path)
if kodidb.db_file == "video":
movies = Movies(*default_args)
tvshows = TVShows(*default_args)
elif kodidb.db_file == "music":
music = Music(*default_args)
else:
# this should not happen
LOG.error(
'"{}" is not a valid Kodi library type.'.format(kodidb.db_file)
2024-06-10 09:19:47 +00:00
)
return
while True:
2018-09-06 08:36:32 +00:00
try:
item = self.queue.get(timeout=1)
2024-06-11 02:56:52 +00:00
except queue.Empty:
break
2018-09-06 08:36:32 +00:00
try:
if item["Type"] == "Movie":
movies.userdata(item)
elif item["Type"] in ["Series", "Season", "Episode"]:
tvshows.userdata(item)
elif item["Type"] == "MusicAlbum":
music.album(item)
elif item["Type"] == "MusicArtist":
music.artist(item)
elif item["Type"] == "AlbumArtist":
music.albumartist(item)
elif item["Type"] == "Audio":
music.userdata(item)
except LibraryException as error:
if error.status == "StopCalled":
break
except Exception as error:
LOG.exception(error)
2018-09-06 08:36:32 +00:00
self.queue.task_done()
if window("jellyfin_should_stop.bool"):
break
2018-09-06 08:36:32 +00:00
LOG.info("--<[ q:userdata/%s ]", id(self))
self.is_done = True
2019-10-03 02:14:54 +00:00
2018-09-06 08:36:32 +00:00
class SortWorker(threading.Thread):
is_done = False
def __init__(self, queue, output, *args):
self.queue = queue
self.output = output
self.args = args
threading.Thread.__init__(self)
def run(self):
with Database("jellyfin") as jellyfindb:
database = jellyfin_db.JellyfinDatabase(jellyfindb.cursor)
2018-09-06 08:36:32 +00:00
while True:
try:
item_id = self.queue.get(timeout=1)
2024-06-11 02:56:52 +00:00
except queue.Empty:
break
2018-09-06 08:36:32 +00:00
2018-09-13 00:43:51 +00:00
try:
media = database.get_media_by_id(item_id)
if media:
self.output[media].put({"Id": item_id, "Type": media})
else:
items = database.get_media_by_parent_id(item_id)
if not items:
LOG.info(
"Could not find media %s in the jellyfin database.",
item_id,
)
else:
for item in items:
self.output[item[1]].put(
{"Id": item[0], "Type": item[1]}
)
2019-07-09 20:05:28 +00:00
except Exception as error:
LOG.exception(error)
2018-09-06 08:36:32 +00:00
self.queue.task_done()
if window("jellyfin_should_stop.bool"):
2018-09-06 08:36:32 +00:00
break
LOG.info("--<[ q:sort/%s ]", id(self))
self.is_done = True
2019-10-03 02:14:54 +00:00
2018-09-06 08:36:32 +00:00
class RemovedWorker(threading.Thread):
is_done = False
def __init__(self, queue, lock, database, server, direct_path):
2018-09-06 08:36:32 +00:00
self.queue = queue
self.lock = lock
self.database = Database(database)
self.server = server
self.direct_path = direct_path
2018-09-06 08:36:32 +00:00
threading.Thread.__init__(self)
def run(self):
with self.lock, self.database as kodidb, Database("jellyfin") as jellyfindb:
default_args = (self.server, jellyfindb, kodidb, self.direct_path)
if kodidb.db_file == "video":
movies = Movies(*default_args)
tvshows = TVShows(*default_args)
musicvideos = MusicVideos(*default_args)
elif kodidb.db_file == "music":
music = Music(*default_args)
else:
# this should not happen
LOG.error(
'"{}" is not a valid Kodi library type.'.format(kodidb.db_file)
2024-06-10 09:19:47 +00:00
)
return
while True:
2018-09-06 08:36:32 +00:00
try:
item = self.queue.get(timeout=1)
2024-06-11 02:56:52 +00:00
except queue.Empty:
break
2018-09-06 08:36:32 +00:00
if item["Type"] == "Movie":
obj = movies.remove
elif item["Type"] in ["Series", "Season", "Episode"]:
obj = tvshows.remove
elif item["Type"] in [
"MusicAlbum",
"MusicArtist",
"AlbumArtist",
"Audio",
]:
obj = music.remove
elif item["Type"] == "MusicVideo":
obj = musicvideos.remove
2018-09-06 08:36:32 +00:00
try:
obj(item["Id"])
except LibraryException as error:
if error.status == "StopCalled":
break
except Exception as error:
LOG.exception(error)
finally:
self.queue.task_done()
if window("jellyfin_should_stop.bool"):
break
2018-09-06 08:36:32 +00:00
LOG.info("--<[ q:removed/%s ]", id(self))
self.is_done = True
2019-10-03 02:14:54 +00:00
2018-09-06 08:36:32 +00:00
class NotifyWorker(threading.Thread):
is_done = False
2018-10-04 05:41:31 +00:00
def __init__(self, queue, player):
2018-09-06 08:36:32 +00:00
self.queue = queue
2018-10-04 05:41:31 +00:00
self.video_time = int(settings("newvideotime")) * 1000
self.music_time = int(settings("newmusictime")) * 1000
self.player = player
threading.Thread.__init__(self)
2018-09-06 08:36:32 +00:00
def run(self):
while True:
try:
item = self.queue.get(timeout=3)
2024-06-11 02:56:52 +00:00
except queue.Empty:
2018-09-06 08:36:32 +00:00
break
2018-10-04 05:41:31 +00:00
time = self.music_time if item[0] == "Audio" else self.video_time
if time and (
not self.player.isPlayingVideo()
or xbmc.getCondVisibility("VideoPlayer.Content(livetv)")
):
dialog(
"notification",
heading="%s %s" % (translate(33049), item[0]),
message=item[1],
icon="{jellyfin}",
time=time,
sound=False,
)
2018-10-04 05:41:31 +00:00
2018-09-06 08:36:32 +00:00
self.queue.task_done()
if window("jellyfin_should_stop.bool"):
2018-09-06 08:36:32 +00:00
break
LOG.info("--<[ q:notify/%s ]", id(self))
self.is_done = True