diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/Plugin.cs b/Jellyfin.Plugin.TubeArchivistMetadata/Plugin.cs
index 9affaad..4a52a6d 100644
--- a/Jellyfin.Plugin.TubeArchivistMetadata/Plugin.cs
+++ b/Jellyfin.Plugin.TubeArchivistMetadata/Plugin.cs
@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.Globalization;
+using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Jellyfin.Plugin.TubeArchivistMetadata.Configuration;
+using Jellyfin.Plugin.TubeArchivistMetadata.Tasks;
using Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist;
using Jellyfin.Plugin.TubeArchivistMetadata.Utilities;
using MediaBrowser.Common.Configuration;
@@ -13,7 +15,7 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Serialization;
-using MediaBrowser.Model.Session;
+using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.TubeArchivistMetadata
@@ -31,7 +33,18 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata
/// Instance of the interface.
/// Instance of the interface.
/// Instance of the interface.
- public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer, ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager)
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ public Plugin(
+ IApplicationPaths applicationPaths,
+ IXmlSerializer xmlSerializer,
+ ILogger logger,
+ ISessionManager sessionManager,
+ ILibraryManager libraryManager,
+ ITaskManager taskManager,
+ IUserManager userManager,
+ IUserDataManager userDataManager)
: base(applicationPaths, xmlSerializer)
{
Instance = this;
@@ -44,6 +57,10 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata
SessionManager = sessionManager;
sessionManager.PlaybackProgress += OnPlaybackProgress;
LibraryManager = libraryManager;
+ taskManager.AddTasks([new TAToJellyfinProgressSyncTask(logger, libraryManager, userManager, userDataManager)]);
+ taskManager.Execute();
+ logger.LogInformation("Enabled tasks: {Tasks}", string.Join(", ", taskManager.ScheduledTasks.ToList().Select(t => t.Name)));
+
logger.LogInformation("{Message}", "Collection display name: " + Instance?.Configuration.CollectionTitle);
logger.LogInformation("{Message}", "TubeArchivist API URL: " + Instance?.Configuration.TubeArchivistUrl);
logger.LogInformation("{Message}", "Pinging TubeArchivist API...");
diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/Tasks/TAToJellyfinProgressSyncTask.cs b/Jellyfin.Plugin.TubeArchivistMetadata/Tasks/TAToJellyfinProgressSyncTask.cs
new file mode 100644
index 0000000..3c36ef5
--- /dev/null
+++ b/Jellyfin.Plugin.TubeArchivistMetadata/Tasks/TAToJellyfinProgressSyncTask.cs
@@ -0,0 +1,153 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Jellyfin.Data.Enums;
+using Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist;
+using Jellyfin.Plugin.TubeArchivistMetadata.Utilities;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Tasks;
+using Microsoft.Extensions.Logging;
+
+namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
+{
+ ///
+ /// Task to sync TubeArchivist playback progresses to Jellyfin.
+ ///
+ public class TAToJellyfinProgressSyncTask : IScheduledTask
+ {
+ private readonly ILogger _logger;
+ private readonly ILibraryManager _libraryManager;
+ private readonly IUserManager _userManager;
+ private readonly IUserDataManager _userDataManager;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Logger.
+ /// Library manager.
+ /// User manager.
+ /// User data manager.
+ public TAToJellyfinProgressSyncTask(ILogger logger, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager)
+ {
+ _logger = logger;
+ _libraryManager = libraryManager;
+ _userManager = userManager;
+ _userDataManager = userDataManager;
+ }
+
+ ///
+ public string Name => "TAToJellyfinProgressSyncTask";
+
+ ///
+ public string Description => "This tasks syncs TubeArchivist playback progresses to Jellyfin";
+
+ ///
+ public string Category => "TubeArchivistMetadata";
+
+ ///
+ public string Key => "TAToJellyfinProgressSyncTask";
+
+ ///
+ public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken)
+ {
+ // TODO: Check if the TA->JF synchronization is enabled from the configuration before proceeding
+ _logger.LogInformation("Starting TubeArchivist playback progresses synchronization.");
+ // TODO: Replace with the lists from configuration
+ var jfUsernames = new[] { "c", "c" };
+ var taUsernames = new[] { "c", "c" };
+ var taApi = TubeArchivistApi.GetInstance();
+ foreach (var jfUsername in jfUsernames)
+ {
+ var user = _userManager.GetUserByName(jfUsername);
+ if (user == null)
+ {
+ _logger.LogInformation("{Message}", $"Jellyfin user with username {jfUsername} not found");
+ continue;
+ }
+
+ var collectionItem = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ Name = Plugin.Instance?.Configuration.CollectionTitle,
+ IncludeItemTypes = new[] { BaseItemKind.CollectionFolder }
+ }).FirstOrDefault();
+
+ if (collectionItem == null)
+ {
+ var message = $"Collection '{Plugin.Instance?.Configuration.CollectionTitle}' not found.";
+ _logger.LogCritical("{Message}", message);
+ }
+ else
+ {
+ var channels = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ ParentId = collectionItem.Id,
+ IncludeItemTypes = new[] { BaseItemKind.Series }
+ });
+
+ foreach (var channel in channels)
+ {
+ var years = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ ParentId = channel.Id,
+ IncludeItemTypes = new[] { BaseItemKind.Season }
+ });
+
+ foreach (var year in years)
+ {
+ var videos = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ ParentId = year.Id,
+ IncludeItemTypes = new[] { BaseItemKind.Episode }
+ });
+
+ foreach (var video in videos)
+ {
+ var playbackProgress = await taApi.GetProgress(Utils.GetVideoNameFromPath(video.Path)).ConfigureAwait(true);
+ if (playbackProgress != null)
+ {
+ var userItemData = _userDataManager.GetUserData(user, video);
+ var userUpdateData = new UpdateUserItemDataDto
+ {
+ PlaybackPositionTicks = playbackProgress.Position * TimeSpan.TicksPerSecond
+ };
+
+ // TODO: Also last played datetime should be updated once TA will return it
+ if (userItemData.PlaybackPositionTicks >= video.RunTimeTicks)
+ {
+ userUpdateData.Played = true;
+ }
+ else
+ {
+ userUpdateData.Played = false;
+ }
+
+ _userDataManager.SaveUserData(user, video, userUpdateData, UserDataSaveReason.UpdateUserData);
+ _logger.LogInformation("{Message}", $"Playback progress for video {video.Name} set to {userItemData.PlaybackPositionTicks / TimeSpan.TicksPerSecond} seconds for user {jfUsername}.");
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ public IEnumerable GetDefaultTriggers()
+ {
+ return
+ [
+ new TaskTriggerInfo
+ {
+ Type = TaskTriggerInfo.TriggerInterval,
+ // TODO: Use configuration defined interval
+ IntervalTicks = TimeSpan.FromSeconds(5).Ticks
+ },
+ ];
+ }
+ }
+}
diff --git a/Jellyfin.Plugin.TubeArchivistMetadata/TubeArchivist/TubeArchivistApi.cs b/Jellyfin.Plugin.TubeArchivistMetadata/TubeArchivist/TubeArchivistApi.cs
index 294bacf..13e1826 100644
--- a/Jellyfin.Plugin.TubeArchivistMetadata/TubeArchivist/TubeArchivistApi.cs
+++ b/Jellyfin.Plugin.TubeArchivistMetadata/TubeArchivist/TubeArchivistApi.cs
@@ -154,7 +154,6 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
/// Video id.
/// Progress in seconds.
/// Nothing if successful.
- /// Throws this exception if the request was not successful.
public async Task SetProgress(string videoId, long progress)
{
var progressEndpoint = $"/api/video/{videoId}/progress/";
@@ -165,5 +164,28 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
return response.StatusCode;
}
+
+ ///
+ /// Send video playback progress to TubeArchivist.
+ ///
+ /// Video id.
+ /// Video playback progress data.
+ public async Task