New: Improved Plex library updating

This commit is contained in:
Mark McDowall 2023-03-16 20:25:31 -07:00
parent f59cc99733
commit 98308737cf
2 changed files with 39 additions and 140 deletions

View File

@ -2,6 +2,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Web;
using NLog; using NLog;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
@ -14,11 +15,8 @@ namespace NzbDrone.Core.Notifications.Plex.Server
public interface IPlexServerProxy public interface IPlexServerProxy
{ {
List<PlexSection> GetTvSections(PlexServerSettings settings); List<PlexSection> GetTvSections(PlexServerSettings settings);
void Update(int sectionId, PlexServerSettings settings);
void UpdateSeries(string metadataId, PlexServerSettings settings);
string Version(PlexServerSettings settings); string Version(PlexServerSettings settings);
List<PlexPreference> Preferences(PlexServerSettings settings); void Update(int sectionId, string path, PlexServerSettings settings);
string GetMetadataId(int sectionId, int tvdbId, string language, PlexServerSettings settings);
} }
public class PlexServerProxy : IPlexServerProxy public class PlexServerProxy : IPlexServerProxy
@ -63,19 +61,13 @@ namespace NzbDrone.Core.Notifications.Plex.Server
.ToList(); .ToList();
} }
public void Update(int sectionId, PlexServerSettings settings) public void Update(int sectionId, string path, PlexServerSettings settings)
{ {
var resource = $"library/sections/{sectionId}/refresh"; var resource = $"library/sections/{sectionId}/refresh";
var request = BuildRequest(resource, HttpMethod.Get, settings); var request = BuildRequest(resource, HttpMethod.Get, settings);
var response = ProcessRequest(request);
CheckForError(response); request.AddQueryParam("path", path);
}
public void UpdateSeries(string metadataId, PlexServerSettings settings)
{
var resource = $"library/metadata/{metadataId}/refresh";
var request = BuildRequest(resource, HttpMethod.Put, settings);
var response = ProcessRequest(request); var response = ProcessRequest(request);
CheckForError(response); CheckForError(response);
@ -99,55 +91,6 @@ namespace NzbDrone.Core.Notifications.Plex.Server
.Version; .Version;
} }
public List<PlexPreference> Preferences(PlexServerSettings settings)
{
var request = BuildRequest(":/prefs", HttpMethod.Get, settings);
var response = ProcessRequest(request);
CheckForError(response);
if (response.Contains("_children"))
{
return Json.Deserialize<PlexPreferencesLegacy>(response)
.Preferences;
}
return Json.Deserialize<PlexResponse<PlexPreferences>>(response)
.MediaContainer
.Preferences;
}
public string GetMetadataId(int sectionId, int tvdbId, string language, PlexServerSettings settings)
{
var guid = $"com.plexapp.agents.thetvdb://{tvdbId}?lang={language}";
var resource = $"library/sections/{sectionId}/all?guid={System.Web.HttpUtility.UrlEncode(guid)}";
var request = BuildRequest(resource, HttpMethod.Get, settings);
var response = ProcessRequest(request);
CheckForError(response);
List<PlexSectionItem> items;
if (response.Contains("_children"))
{
items = Json.Deserialize<PlexSectionResponseLegacy>(response)
.Items;
}
else
{
items = Json.Deserialize<PlexResponse<PlexSectionResponse>>(response)
.MediaContainer
.Items;
}
if (items == null || items.Empty())
{
return null;
}
return items.First().Id;
}
private HttpRequestBuilder BuildRequest(string resource, HttpMethod method, PlexServerSettings settings) private HttpRequestBuilder BuildRequest(string resource, HttpMethod method, PlexServerSettings settings)
{ {
var scheme = settings.UseSsl ? "https" : "http"; var scheme = settings.UseSsl ? "https" : "http";

View File

@ -1,12 +1,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using FluentValidation.Results; using FluentValidation.Results;
using NLog; using NLog;
using NzbDrone.Common.Cache; using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.Validation; using NzbDrone.Core.Validation;
@ -22,15 +24,15 @@ namespace NzbDrone.Core.Notifications.Plex.Server
public class PlexServerService : IPlexServerService public class PlexServerService : IPlexServerService
{ {
private readonly ICached<Version> _versionCache; private readonly ICached<Version> _versionCache;
private readonly ICached<bool> _partialUpdateCache;
private readonly IPlexServerProxy _plexServerProxy; private readonly IPlexServerProxy _plexServerProxy;
private readonly IRootFolderService _rootFolderService;
private readonly Logger _logger; private readonly Logger _logger;
public PlexServerService(ICacheManager cacheManager, IPlexServerProxy plexServerProxy, Logger logger) public PlexServerService(ICacheManager cacheManager, IPlexServerProxy plexServerProxy, IRootFolderService rootFolderService, Logger logger)
{ {
_versionCache = cacheManager.GetCache<Version>(GetType(), "versionCache"); _versionCache = cacheManager.GetCache<Version>(GetType(), "versionCache");
_partialUpdateCache = cacheManager.GetCache<bool>(GetType(), "partialUpdateCache");
_plexServerProxy = plexServerProxy; _plexServerProxy = plexServerProxy;
_rootFolderService = rootFolderService;
_logger = logger; _logger = logger;
} }
@ -50,32 +52,10 @@ namespace NzbDrone.Core.Notifications.Plex.Server
ValidateVersion(version); ValidateVersion(version);
var sections = GetSections(settings); var sections = GetSections(settings);
var partialUpdates = _partialUpdateCache.Get(settings.Host, () => PartialUpdatesAllowed(settings, version), TimeSpan.FromHours(2));
if (partialUpdates) foreach (var series in multipleSeries)
{ {
var partiallyUpdated = true; UpdateSections(series, sections, settings);
foreach (var series in multipleSeries)
{
partiallyUpdated &= UpdatePartialSection(series, sections, settings);
if (!partiallyUpdated)
{
break;
}
}
// Only update complete sections if all partial updates failed
if (!partiallyUpdated)
{
_logger.Debug("Unable to update partial section, updating all TV sections");
sections.ForEach(s => UpdateSection(s.Id, settings));
}
}
else
{
sections.ForEach(s => UpdateSection(s.Id, settings));
} }
_logger.Debug("Finished sending Update Request to Plex Server (took {0} ms)", watch.ElapsedMilliseconds); _logger.Debug("Finished sending Update Request to Plex Server (took {0} ms)", watch.ElapsedMilliseconds);
@ -94,31 +74,6 @@ namespace NzbDrone.Core.Notifications.Plex.Server
return _plexServerProxy.GetTvSections(settings).ToList(); return _plexServerProxy.GetTvSections(settings).ToList();
} }
private bool PartialUpdatesAllowed(PlexServerSettings settings, Version version)
{
try
{
if (version >= new Version(0, 9, 12, 0))
{
var preferences = GetPreferences(settings);
var partialScanPreference = preferences.SingleOrDefault(p => p.Id.Equals("FSEventLibraryPartialScanEnabled"));
if (partialScanPreference == null)
{
return false;
}
return Convert.ToBoolean(partialScanPreference.Value);
}
}
catch (Exception ex)
{
_logger.Warn(ex, "Unable to check if partial updates are allowed");
}
return false;
}
private void ValidateVersion(Version version) private void ValidateVersion(Version version)
{ {
if (version >= new Version(1, 3, 0) && version < new Version(1, 3, 1)) if (version >= new Version(1, 3, 0) && version < new Version(1, 3, 1))
@ -137,45 +92,47 @@ namespace NzbDrone.Core.Notifications.Plex.Server
return version; return version;
} }
private List<PlexPreference> GetPreferences(PlexServerSettings settings) private void UpdateSections(Series series, List<PlexSection> sections, PlexServerSettings settings)
{ {
_logger.Debug("Getting preferences from Plex host: {0}", settings.Host); var rootFolderPath = _rootFolderService.GetBestRootFolderPath(series.Path);
var seriesRelativePath = rootFolderPath.GetRelativePath(series.Path);
return _plexServerProxy.Preferences(settings);
}
private void UpdateSection(int sectionId, PlexServerSettings settings)
{
_logger.Debug("Updating Plex host: {0}, Section: {1}", settings.Host, sectionId);
_plexServerProxy.Update(sectionId, settings);
}
private bool UpdatePartialSection(Series series, List<PlexSection> sections, PlexServerSettings settings)
{
var partiallyUpdated = false;
// Try to update a matching section location before falling back to updating all section locations.
foreach (var section in sections) foreach (var section in sections)
{ {
var metadataId = GetMetadataId(section.Id, series, section.Language, settings); foreach (var location in section.Locations)
if (metadataId.IsNotNullOrWhiteSpace())
{ {
_logger.Debug("Updating Plex host: {0}, Section: {1}, Series: {2}", settings.Host, section.Id, series); if (location.Path.PathEquals(rootFolderPath))
_plexServerProxy.UpdateSeries(metadataId, settings); {
_logger.Debug("Updating matching section location, {0}", location.Path);
UpdateSectionPath(seriesRelativePath, section, location, settings);
partiallyUpdated = true; return;
}
} }
} }
return partiallyUpdated; _logger.Debug("Unable to find matching section location, updating all TV sections");
foreach (var section in sections)
{
foreach (var location in section.Locations)
{
UpdateSectionPath(seriesRelativePath, section, location, settings);
}
}
} }
private string GetMetadataId(int sectionId, Series series, string language, PlexServerSettings settings) private void UpdateSectionPath(string seriesRelativePath, PlexSection section, PlexSectionLocation location, PlexServerSettings settings)
{ {
_logger.Debug("Getting metadata from Plex host: {0} for series: {1}", settings.Host, series); var separator = location.Path.Contains('\\') ? "\\" : "/";
var locationRelativePath = seriesRelativePath.Replace("\\", separator).Replace("/", separator);
return _plexServerProxy.GetMetadataId(sectionId, series.TvdbId, language, settings); // Plex location paths trim trailing extraneous separator characters, so it doesn't need to be trimmed
var pathToUpdate = $"{location.Path}{separator}{locationRelativePath}";
_logger.Debug("Updating section location, {0}", location.Path);
_plexServerProxy.Update(section.Id, pathToUpdate, settings);
} }
public ValidationFailure Test(PlexServerSettings settings) public ValidationFailure Test(PlexServerSettings settings)
@ -183,7 +140,6 @@ namespace NzbDrone.Core.Notifications.Plex.Server
try try
{ {
_versionCache.Remove(settings.Host); _versionCache.Remove(settings.Host);
_partialUpdateCache.Remove(settings.Host);
var sections = GetSections(settings); var sections = GetSections(settings);
if (sections.Empty()) if (sections.Empty())