Implement TA->JF synchronization
This commit is contained in:
@@ -1,9 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Plugin.TubeArchivistMetadata.Configuration;
|
using Jellyfin.Plugin.TubeArchivistMetadata.Configuration;
|
||||||
|
using Jellyfin.Plugin.TubeArchivistMetadata.Tasks;
|
||||||
using Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist;
|
using Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist;
|
||||||
using Jellyfin.Plugin.TubeArchivistMetadata.Utilities;
|
using Jellyfin.Plugin.TubeArchivistMetadata.Utilities;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
@@ -13,7 +15,7 @@ using MediaBrowser.Controller.Library;
|
|||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
using MediaBrowser.Model.Plugins;
|
using MediaBrowser.Model.Plugins;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
using MediaBrowser.Model.Session;
|
using MediaBrowser.Model.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Jellyfin.Plugin.TubeArchivistMetadata
|
namespace Jellyfin.Plugin.TubeArchivistMetadata
|
||||||
@@ -31,7 +33,18 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata
|
|||||||
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
|
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
|
||||||
/// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
|
/// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
|
||||||
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
|
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
|
||||||
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer, ILogger<Plugin> logger, ISessionManager sessionManager, ILibraryManager libraryManager)
|
/// <param name="taskManager">Instance of the <see cref="ITaskManager"/> interface.</param>
|
||||||
|
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
|
||||||
|
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
|
||||||
|
public Plugin(
|
||||||
|
IApplicationPaths applicationPaths,
|
||||||
|
IXmlSerializer xmlSerializer,
|
||||||
|
ILogger<Plugin> logger,
|
||||||
|
ISessionManager sessionManager,
|
||||||
|
ILibraryManager libraryManager,
|
||||||
|
ITaskManager taskManager,
|
||||||
|
IUserManager userManager,
|
||||||
|
IUserDataManager userDataManager)
|
||||||
: base(applicationPaths, xmlSerializer)
|
: base(applicationPaths, xmlSerializer)
|
||||||
{
|
{
|
||||||
Instance = this;
|
Instance = this;
|
||||||
@@ -44,6 +57,10 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata
|
|||||||
SessionManager = sessionManager;
|
SessionManager = sessionManager;
|
||||||
sessionManager.PlaybackProgress += OnPlaybackProgress;
|
sessionManager.PlaybackProgress += OnPlaybackProgress;
|
||||||
LibraryManager = libraryManager;
|
LibraryManager = libraryManager;
|
||||||
|
taskManager.AddTasks([new TAToJellyfinProgressSyncTask(logger, libraryManager, userManager, userDataManager)]);
|
||||||
|
taskManager.Execute<TAToJellyfinProgressSyncTask>();
|
||||||
|
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}", "Collection display name: " + Instance?.Configuration.CollectionTitle);
|
||||||
logger.LogInformation("{Message}", "TubeArchivist API URL: " + Instance?.Configuration.TubeArchivistUrl);
|
logger.LogInformation("{Message}", "TubeArchivist API URL: " + Instance?.Configuration.TubeArchivistUrl);
|
||||||
logger.LogInformation("{Message}", "Pinging TubeArchivist API...");
|
logger.LogInformation("{Message}", "Pinging TubeArchivist API...");
|
||||||
|
|||||||
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Task to sync TubeArchivist playback progresses to Jellyfin.
|
||||||
|
/// </summary>
|
||||||
|
public class TAToJellyfinProgressSyncTask : IScheduledTask
|
||||||
|
{
|
||||||
|
private readonly ILogger<Plugin> _logger;
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
private readonly IUserManager _userManager;
|
||||||
|
private readonly IUserDataManager _userDataManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="TAToJellyfinProgressSyncTask"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger">Logger.</param>
|
||||||
|
/// <param name="libraryManager">Library manager.</param>
|
||||||
|
/// <param name="userManager">User manager.</param>
|
||||||
|
/// <param name="userDataManager">User data manager.</param>
|
||||||
|
public TAToJellyfinProgressSyncTask(ILogger<Plugin> logger, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
_userManager = userManager;
|
||||||
|
_userDataManager = userDataManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string Name => "TAToJellyfinProgressSyncTask";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string Description => "This tasks syncs TubeArchivist playback progresses to Jellyfin";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string Category => "TubeArchivistMetadata";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string Key => "TAToJellyfinProgressSyncTask";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public async Task ExecuteAsync(IProgress<double> 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}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||||
|
{
|
||||||
|
return
|
||||||
|
[
|
||||||
|
new TaskTriggerInfo
|
||||||
|
{
|
||||||
|
Type = TaskTriggerInfo.TriggerInterval,
|
||||||
|
// TODO: Use configuration defined interval
|
||||||
|
IntervalTicks = TimeSpan.FromSeconds(5).Ticks
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -154,7 +154,6 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
|
|||||||
/// <param name="videoId">Video id.</param>
|
/// <param name="videoId">Video id.</param>
|
||||||
/// <param name="progress">Progress in seconds.</param>
|
/// <param name="progress">Progress in seconds.</param>
|
||||||
/// <returns>Nothing if successful.</returns>
|
/// <returns>Nothing if successful.</returns>
|
||||||
/// <exception cref="HttpRequestException">Throws this exception if the request was not successful.</exception>
|
|
||||||
public async Task<HttpStatusCode> SetProgress(string videoId, long progress)
|
public async Task<HttpStatusCode> SetProgress(string videoId, long progress)
|
||||||
{
|
{
|
||||||
var progressEndpoint = $"/api/video/{videoId}/progress/";
|
var progressEndpoint = $"/api/video/{videoId}/progress/";
|
||||||
@@ -165,5 +164,28 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
|
|||||||
|
|
||||||
return response.StatusCode;
|
return response.StatusCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send video playback progress to TubeArchivist.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="videoId">Video id.</param>
|
||||||
|
/// <returns>Video playback progress data.</returns>
|
||||||
|
public async Task<Progress?> GetProgress(string videoId)
|
||||||
|
{
|
||||||
|
Progress? progress = null;
|
||||||
|
|
||||||
|
var progressEndpoint = $"/api/video/{videoId}/progress/";
|
||||||
|
var url = new Uri(Utils.SanitizeUrl(Plugin.Instance!.Configuration.TubeArchivistUrl + progressEndpoint));
|
||||||
|
|
||||||
|
var response = await client.GetAsync(url).ConfigureAwait(true);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
string rawData = await response.Content.ReadAsStringAsync().ConfigureAwait(true);
|
||||||
|
progress = JsonConvert.DeserializeObject<Progress>(rawData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user