bug fixes (#757)
* fix docker blur hash generation * scanner async cleanup * catch and log some unauthorized exception errors
This commit is contained in:
@@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
- Cleanly stop Plex library scan when service termination is requested
|
- Cleanly stop Plex library scan when service termination is requested
|
||||||
- Fix bug introduced with 0.5.2-beta that prevented some Plex content from being played
|
- Fix bug introduced with 0.5.2-beta that prevented some Plex content from being played
|
||||||
- Fix spammy subtitle error message
|
- Fix spammy subtitle error message
|
||||||
|
- Fix generating blur hashes for song backgrounds in Docker
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- No longer remove Plex movies and episodes from ErsatzTV when they do not exist on disk
|
- No longer remove Plex movies and episodes from ErsatzTV when they do not exist on disk
|
||||||
|
|||||||
@@ -70,43 +70,41 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner
|
|||||||
apiKey,
|
apiKey,
|
||||||
library.ItemId);
|
library.ItemId);
|
||||||
|
|
||||||
await maybeShows.Match(
|
foreach (BaseError error in maybeShows.LeftToSeq())
|
||||||
async shows =>
|
{
|
||||||
{
|
_logger.LogWarning(
|
||||||
await ProcessShows(
|
"Error synchronizing emby library {Path}: {Error}",
|
||||||
address,
|
library.Name,
|
||||||
apiKey,
|
error.Value);
|
||||||
library,
|
}
|
||||||
ffmpegPath,
|
|
||||||
ffprobePath,
|
|
||||||
pathReplacements,
|
|
||||||
existingShows,
|
|
||||||
shows);
|
|
||||||
|
|
||||||
var incomingShowIds = shows.Map(s => s.ItemId).ToList();
|
foreach (List<EmbyShow> shows in maybeShows.RightToSeq())
|
||||||
var showIds = existingShows
|
{
|
||||||
.Filter(i => !incomingShowIds.Contains(i.ItemId))
|
await ProcessShows(
|
||||||
.Map(m => m.ItemId)
|
address,
|
||||||
.ToList();
|
apiKey,
|
||||||
List<int> missingShowIds = await _televisionRepository.RemoveMissingShows(library, showIds);
|
library,
|
||||||
await _searchIndex.RemoveItems(missingShowIds);
|
ffmpegPath,
|
||||||
|
ffprobePath,
|
||||||
|
pathReplacements,
|
||||||
|
existingShows,
|
||||||
|
shows);
|
||||||
|
|
||||||
await _televisionRepository.DeleteEmptySeasons(library);
|
var incomingShowIds = shows.Map(s => s.ItemId).ToList();
|
||||||
List<int> emptyShowIds = await _televisionRepository.DeleteEmptyShows(library);
|
var showIds = existingShows
|
||||||
await _searchIndex.RemoveItems(emptyShowIds);
|
.Filter(i => !incomingShowIds.Contains(i.ItemId))
|
||||||
|
.Map(m => m.ItemId)
|
||||||
|
.ToList();
|
||||||
|
List<int> missingShowIds = await _televisionRepository.RemoveMissingShows(library, showIds);
|
||||||
|
await _searchIndex.RemoveItems(missingShowIds);
|
||||||
|
|
||||||
await _mediator.Publish(new LibraryScanProgress(library.Id, 0));
|
await _televisionRepository.DeleteEmptySeasons(library);
|
||||||
_searchIndex.Commit();
|
List<int> emptyShowIds = await _televisionRepository.DeleteEmptyShows(library);
|
||||||
},
|
await _searchIndex.RemoveItems(emptyShowIds);
|
||||||
error =>
|
|
||||||
{
|
|
||||||
_logger.LogWarning(
|
|
||||||
"Error synchronizing emby library {Path}: {Error}",
|
|
||||||
library.Name,
|
|
||||||
error.Value);
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
await _mediator.Publish(new LibraryScanProgress(library.Id, 0));
|
||||||
});
|
_searchIndex.Commit();
|
||||||
|
}
|
||||||
|
|
||||||
return Unit.Default;
|
return Unit.Default;
|
||||||
}
|
}
|
||||||
@@ -128,39 +126,35 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner
|
|||||||
await _mediator.Publish(new LibraryScanProgress(library.Id, percentCompletion));
|
await _mediator.Publish(new LibraryScanProgress(library.Id, percentCompletion));
|
||||||
|
|
||||||
Option<EmbyItemEtag> maybeExisting = existingShows.Find(ie => ie.ItemId == incoming.ItemId);
|
Option<EmbyItemEtag> maybeExisting = existingShows.Find(ie => ie.ItemId == incoming.ItemId);
|
||||||
await maybeExisting.Match(
|
if (maybeExisting.IsNone)
|
||||||
async existing =>
|
{
|
||||||
|
incoming.LibraryPathId = library.Paths.Head().Id;
|
||||||
|
|
||||||
|
// _logger.LogDebug("INSERT: Item id is new for show {Show}", incoming.ShowMetadata.Head().Title);
|
||||||
|
|
||||||
|
if (await _televisionRepository.AddShow(incoming))
|
||||||
{
|
{
|
||||||
if (existing.Etag == incoming.Etag)
|
await _searchIndex.AddItems(_searchRepository, new List<MediaItem> { incoming });
|
||||||
{
|
}
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogDebug(
|
foreach (EmbyItemEtag existing in maybeExisting)
|
||||||
"UPDATE: Etag has changed for show {Show}",
|
{
|
||||||
incoming.ShowMetadata.Head().Title);
|
if (existing.Etag == incoming.Etag)
|
||||||
|
|
||||||
incoming.LibraryPathId = library.Paths.Head().Id;
|
|
||||||
|
|
||||||
Option<EmbyShow> updated = await _televisionRepository.Update(incoming);
|
|
||||||
if (updated.IsSome)
|
|
||||||
{
|
|
||||||
await _searchIndex.UpdateItems(
|
|
||||||
_searchRepository,
|
|
||||||
new List<MediaItem> { updated.ValueUnsafe() });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async () =>
|
|
||||||
{
|
{
|
||||||
incoming.LibraryPathId = library.Paths.Head().Id;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// _logger.LogDebug("INSERT: Item id is new for show {Show}", incoming.ShowMetadata.Head().Title);
|
_logger.LogDebug("UPDATE: Etag has changed for show {Show}", incoming.ShowMetadata.Head().Title);
|
||||||
|
|
||||||
if (await _televisionRepository.AddShow(incoming))
|
incoming.LibraryPathId = library.Paths.Head().Id;
|
||||||
{
|
|
||||||
await _searchIndex.AddItems(_searchRepository, new List<MediaItem> { incoming });
|
Option<EmbyShow> updated = await _televisionRepository.Update(incoming);
|
||||||
}
|
if (updated.IsSome)
|
||||||
});
|
{
|
||||||
|
await _searchIndex.UpdateItems(_searchRepository, new List<MediaItem> { updated.ValueUnsafe() });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<EmbyItemEtag> existingSeasons =
|
List<EmbyItemEtag> existingSeasons =
|
||||||
await _televisionRepository.GetExistingSeasons(library, incoming.ItemId);
|
await _televisionRepository.GetExistingSeasons(library, incoming.ItemId);
|
||||||
@@ -168,36 +162,34 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner
|
|||||||
Either<BaseError, List<EmbySeason>> maybeSeasons =
|
Either<BaseError, List<EmbySeason>> maybeSeasons =
|
||||||
await _embyApiClient.GetSeasonLibraryItems(address, apiKey, incoming.ItemId);
|
await _embyApiClient.GetSeasonLibraryItems(address, apiKey, incoming.ItemId);
|
||||||
|
|
||||||
await maybeSeasons.Match(
|
foreach (BaseError error in maybeSeasons.LeftToSeq())
|
||||||
async seasons =>
|
{
|
||||||
{
|
_logger.LogWarning(
|
||||||
await ProcessSeasons(
|
"Error synchronizing emby library {Path}: {Error}",
|
||||||
address,
|
library.Name,
|
||||||
apiKey,
|
error.Value);
|
||||||
library,
|
}
|
||||||
ffmpegPath,
|
|
||||||
ffprobePath,
|
|
||||||
pathReplacements,
|
|
||||||
incoming,
|
|
||||||
existingSeasons,
|
|
||||||
seasons);
|
|
||||||
|
|
||||||
var incomingSeasonIds = seasons.Map(s => s.ItemId).ToList();
|
foreach (List<EmbySeason> seasons in maybeSeasons.RightToSeq())
|
||||||
var seasonIds = existingSeasons
|
{
|
||||||
.Filter(i => !incomingSeasonIds.Contains(i.ItemId))
|
await ProcessSeasons(
|
||||||
.Map(m => m.ItemId)
|
address,
|
||||||
.ToList();
|
apiKey,
|
||||||
await _televisionRepository.RemoveMissingSeasons(library, seasonIds);
|
library,
|
||||||
},
|
ffmpegPath,
|
||||||
error =>
|
ffprobePath,
|
||||||
{
|
pathReplacements,
|
||||||
_logger.LogWarning(
|
incoming,
|
||||||
"Error synchronizing emby library {Path}: {Error}",
|
existingSeasons,
|
||||||
library.Name,
|
seasons);
|
||||||
error.Value);
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
var incomingSeasonIds = seasons.Map(s => s.ItemId).ToList();
|
||||||
});
|
var seasonIds = existingSeasons
|
||||||
|
.Filter(i => !incomingSeasonIds.Contains(i.ItemId))
|
||||||
|
.Map(m => m.ItemId)
|
||||||
|
.ToList();
|
||||||
|
await _televisionRepository.RemoveMissingSeasons(library, seasonIds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,9 +229,7 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner
|
|||||||
|
|
||||||
foreach (MediaItem toIndex in await _searchRepository.GetItemToIndex(updated.Id))
|
foreach (MediaItem toIndex in await _searchRepository.GetItemToIndex(updated.Id))
|
||||||
{
|
{
|
||||||
await _searchIndex.UpdateItems(
|
await _searchIndex.UpdateItems(_searchRepository, new List<MediaItem> { toIndex });
|
||||||
_searchRepository,
|
|
||||||
new List<MediaItem> { toIndex });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -361,9 +351,7 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner
|
|||||||
Option<EmbyEpisode> maybeUpdated = await _televisionRepository.Update(incoming);
|
Option<EmbyEpisode> maybeUpdated = await _televisionRepository.Update(incoming);
|
||||||
foreach (EmbyEpisode updated in maybeUpdated)
|
foreach (EmbyEpisode updated in maybeUpdated)
|
||||||
{
|
{
|
||||||
await _searchIndex.UpdateItems(
|
await _searchIndex.UpdateItems(_searchRepository, new List<MediaItem> { updated });
|
||||||
_searchRepository,
|
|
||||||
new List<MediaItem> { updated });
|
|
||||||
|
|
||||||
incomingEpisode = updated;
|
incomingEpisode = updated;
|
||||||
}
|
}
|
||||||
@@ -425,13 +413,14 @@ public class EmbyTelevisionLibraryScanner : IEmbyTelevisionLibraryScanner
|
|||||||
refreshResult = await UpdateSubtitles(incomingEpisode, localPath);
|
refreshResult = await UpdateSubtitles(incomingEpisode, localPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshResult.Match(
|
foreach (BaseError error in refreshResult.LeftToSeq())
|
||||||
_ => { },
|
{
|
||||||
error => _logger.LogWarning(
|
_logger.LogWarning(
|
||||||
"Unable to refresh {Attribute} for media item {Path}. Error: {Error}",
|
"Unable to refresh {Attribute} for media item {Path}. Error: {Error}",
|
||||||
"Statistics",
|
"Statistics",
|
||||||
localPath,
|
localPath,
|
||||||
error.Value));
|
error.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ public class LocalFileSystem : ILocalFileSystem
|
|||||||
{
|
{
|
||||||
return Directory.EnumerateDirectories(folder);
|
return Directory.EnumerateDirectories(folder);
|
||||||
}
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Unauthorized access exception listing subdirectories of folder {Folder}", folder);
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// do nothing
|
// do nothing
|
||||||
@@ -65,6 +69,10 @@ public class LocalFileSystem : ILocalFileSystem
|
|||||||
{
|
{
|
||||||
return Directory.EnumerateFiles(folder, "*", SearchOption.TopDirectoryOnly);
|
return Directory.EnumerateFiles(folder, "*", SearchOption.TopDirectoryOnly);
|
||||||
}
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Unauthorized access exception listing files in folder {Folder}", folder);
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// do nothing
|
// do nothing
|
||||||
@@ -83,6 +91,10 @@ public class LocalFileSystem : ILocalFileSystem
|
|||||||
{
|
{
|
||||||
return Directory.EnumerateFiles(folder, searchPattern, SearchOption.TopDirectoryOnly);
|
return Directory.EnumerateFiles(folder, searchPattern, SearchOption.TopDirectoryOnly);
|
||||||
}
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Unauthorized access exception listing files in folder {Folder}", folder);
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// do nothing
|
// do nothing
|
||||||
|
|||||||
@@ -88,7 +88,8 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner
|
|||||||
{
|
{
|
||||||
decimal percentCompletion = (decimal)foldersCompleted / (foldersCompleted + folderQueue.Count);
|
decimal percentCompletion = (decimal)foldersCompleted / (foldersCompleted + folderQueue.Count);
|
||||||
await _mediator.Publish(
|
await _mediator.Publish(
|
||||||
new LibraryScanProgress(libraryPath.LibraryId, progressMin + percentCompletion * progressSpread));
|
new LibraryScanProgress(libraryPath.LibraryId, progressMin + percentCompletion * progressSpread),
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
string movieFolder = folderQueue.Dequeue();
|
string movieFolder = folderQueue.Dequeue();
|
||||||
foldersCompleted++;
|
foldersCompleted++;
|
||||||
@@ -142,25 +143,24 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner
|
|||||||
.BindT(UpdateSubtitles)
|
.BindT(UpdateSubtitles)
|
||||||
.BindT(FlagNormal);
|
.BindT(FlagNormal);
|
||||||
|
|
||||||
await maybeMovie.Match(
|
foreach (BaseError error in maybeMovie.LeftToSeq())
|
||||||
async result =>
|
{
|
||||||
{
|
_logger.LogWarning("Error processing movie at {Path}: {Error}", file, error.Value);
|
||||||
if (result.IsAdded)
|
}
|
||||||
{
|
|
||||||
await _searchIndex.AddItems(_searchRepository, new List<MediaItem> { result.Item });
|
|
||||||
}
|
|
||||||
else if (result.IsUpdated)
|
|
||||||
{
|
|
||||||
await _searchIndex.UpdateItems(_searchRepository, new List<MediaItem> { result.Item });
|
|
||||||
}
|
|
||||||
|
|
||||||
await _libraryRepository.SetEtag(libraryPath, knownFolder, movieFolder, etag);
|
foreach (MediaItemScanResult<Movie> result in maybeMovie.RightToSeq())
|
||||||
},
|
{
|
||||||
error =>
|
if (result.IsAdded)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Error processing movie at {Path}: {Error}", file, error.Value);
|
await _searchIndex.AddItems(_searchRepository, new List<MediaItem> { result.Item });
|
||||||
return Task.CompletedTask;
|
}
|
||||||
});
|
else if (result.IsUpdated)
|
||||||
|
{
|
||||||
|
await _searchIndex.UpdateItems(_searchRepository, new List<MediaItem> { result.Item });
|
||||||
|
}
|
||||||
|
|
||||||
|
await _libraryRepository.SetEtag(libraryPath, knownFolder, movieFolder, etag);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,35 +192,37 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Movie movie = result.Item;
|
Movie movie = result.Item;
|
||||||
await LocateNfoFile(movie).Match(
|
|
||||||
async nfoFile =>
|
|
||||||
{
|
|
||||||
bool shouldUpdate = Optional(movie.MovieMetadata).Flatten().HeadOrNone().Match(
|
|
||||||
m => m.MetadataKind == MetadataKind.Fallback ||
|
|
||||||
m.DateUpdated != _localFileSystem.GetLastWriteTime(nfoFile),
|
|
||||||
true);
|
|
||||||
|
|
||||||
if (shouldUpdate)
|
Option<string> maybeNfoFile = LocateNfoFile(movie);
|
||||||
{
|
if (maybeNfoFile.IsNone)
|
||||||
_logger.LogDebug("Refreshing {Attribute} from {Path}", "Sidecar Metadata", nfoFile);
|
{
|
||||||
if (await _localMetadataProvider.RefreshSidecarMetadata(movie, nfoFile))
|
if (!Optional(movie.MovieMetadata).Flatten().Any())
|
||||||
{
|
|
||||||
result.IsUpdated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async () =>
|
|
||||||
{
|
{
|
||||||
if (!Optional(movie.MovieMetadata).Flatten().Any())
|
string path = movie.MediaVersions.Head().MediaFiles.Head().Path;
|
||||||
|
_logger.LogDebug("Refreshing {Attribute} for {Path}", "Fallback Metadata", path);
|
||||||
|
if (await _localMetadataProvider.RefreshFallbackMetadata(movie))
|
||||||
{
|
{
|
||||||
string path = movie.MediaVersions.Head().MediaFiles.Head().Path;
|
result.IsUpdated = true;
|
||||||
_logger.LogDebug("Refreshing {Attribute} for {Path}", "Fallback Metadata", path);
|
|
||||||
if (await _localMetadataProvider.RefreshFallbackMetadata(movie))
|
|
||||||
{
|
|
||||||
result.IsUpdated = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (string nfoFile in maybeNfoFile)
|
||||||
|
{
|
||||||
|
bool shouldUpdate = Optional(movie.MovieMetadata).Flatten().HeadOrNone().Match(
|
||||||
|
m => m.MetadataKind == MetadataKind.Fallback ||
|
||||||
|
m.DateUpdated != _localFileSystem.GetLastWriteTime(nfoFile),
|
||||||
|
true);
|
||||||
|
|
||||||
|
if (shouldUpdate)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Refreshing {Attribute} from {Path}", "Sidecar Metadata", nfoFile);
|
||||||
|
if (await _localMetadataProvider.RefreshSidecarMetadata(movie, nfoFile))
|
||||||
|
{
|
||||||
|
result.IsUpdated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -239,12 +241,12 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Movie movie = result.Item;
|
Movie movie = result.Item;
|
||||||
await LocateArtwork(movie, artworkKind).IfSomeAsync(
|
Option<string> maybeArtwork = LocateArtwork(movie, artworkKind);
|
||||||
async posterFile =>
|
foreach (string posterFile in maybeArtwork)
|
||||||
{
|
{
|
||||||
MovieMetadata metadata = movie.MovieMetadata.Head();
|
MovieMetadata metadata = movie.MovieMetadata.Head();
|
||||||
await RefreshArtwork(posterFile, metadata, artworkKind, None, None, cancellationToken);
|
await RefreshArtwork(posterFile, metadata, artworkKind, None, None, cancellationToken);
|
||||||
});
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,8 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
|
|||||||
{
|
{
|
||||||
decimal percentCompletion = (decimal)allShowFolders.IndexOf(showFolder) / allShowFolders.Count;
|
decimal percentCompletion = (decimal)allShowFolders.IndexOf(showFolder) / allShowFolders.Count;
|
||||||
await _mediator.Publish(
|
await _mediator.Publish(
|
||||||
new LibraryScanProgress(libraryPath.LibraryId, progressMin + percentCompletion * progressSpread));
|
new LibraryScanProgress(libraryPath.LibraryId, progressMin + percentCompletion * progressSpread),
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
Either<BaseError, MediaItemScanResult<Show>> maybeShow =
|
Either<BaseError, MediaItemScanResult<Show>> maybeShow =
|
||||||
await FindOrCreateShow(libraryPath.Id, showFolder)
|
await FindOrCreateShow(libraryPath.Id, showFolder)
|
||||||
@@ -92,34 +93,27 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
|
|||||||
.BindT(show => UpdateArtworkForShow(show, showFolder, ArtworkKind.FanArt, cancellationToken))
|
.BindT(show => UpdateArtworkForShow(show, showFolder, ArtworkKind.FanArt, cancellationToken))
|
||||||
.BindT(show => UpdateArtworkForShow(show, showFolder, ArtworkKind.Thumbnail, cancellationToken));
|
.BindT(show => UpdateArtworkForShow(show, showFolder, ArtworkKind.Thumbnail, cancellationToken));
|
||||||
|
|
||||||
await maybeShow.Match(
|
foreach (BaseError error in maybeShow.LeftToSeq())
|
||||||
async result =>
|
{
|
||||||
{
|
_logger.LogWarning(
|
||||||
await ScanSeasons(
|
"Error processing show in folder {Folder}: {Error}",
|
||||||
libraryPath,
|
showFolder,
|
||||||
ffmpegPath,
|
error.Value);
|
||||||
ffprobePath,
|
}
|
||||||
result.Item,
|
|
||||||
showFolder,
|
|
||||||
cancellationToken);
|
|
||||||
|
|
||||||
if (result.IsAdded)
|
foreach (MediaItemScanResult<Show> result in maybeShow.RightToSeq())
|
||||||
{
|
{
|
||||||
await _searchIndex.AddItems(_searchRepository, new List<MediaItem> { result.Item });
|
await ScanSeasons(libraryPath, ffmpegPath, ffprobePath, result.Item, showFolder, cancellationToken);
|
||||||
}
|
|
||||||
else if (result.IsUpdated)
|
if (result.IsAdded)
|
||||||
{
|
|
||||||
await _searchIndex.UpdateItems(_searchRepository, new List<MediaItem> { result.Item });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error =>
|
|
||||||
{
|
{
|
||||||
_logger.LogWarning(
|
await _searchIndex.AddItems(_searchRepository, new List<MediaItem> { result.Item });
|
||||||
"Error processing show in folder {Folder}: {Error}",
|
}
|
||||||
showFolder,
|
else if (result.IsUpdated)
|
||||||
error.Value);
|
{
|
||||||
return Task.FromResult(Unit.Default);
|
await _searchIndex.UpdateItems(_searchRepository, new List<MediaItem> { result.Item });
|
||||||
});
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (string path in await _televisionRepository.FindEpisodePaths(libraryPath))
|
foreach (string path in await _televisionRepository.FindEpisodePaths(libraryPath))
|
||||||
@@ -249,16 +243,15 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
|
|||||||
.BindT(e => FlagNormal(new MediaItemScanResult<Episode>(e)))
|
.BindT(e => FlagNormal(new MediaItemScanResult<Episode>(e)))
|
||||||
.MapT(r => r.Item);
|
.MapT(r => r.Item);
|
||||||
|
|
||||||
await maybeEpisode.Match(
|
foreach (BaseError error in maybeEpisode.LeftToSeq())
|
||||||
async episode =>
|
{
|
||||||
{
|
_logger.LogWarning("Error processing episode at {Path}: {Error}", file, error.Value);
|
||||||
await _searchIndex.UpdateItems(_searchRepository, new List<MediaItem> { episode });
|
}
|
||||||
},
|
|
||||||
error =>
|
foreach (Episode episode in maybeEpisode.RightToSeq())
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Error processing episode at {Path}: {Error}", file, error.Value);
|
await _searchIndex.UpdateItems(_searchRepository, new List<MediaItem> { episode });
|
||||||
return Task.CompletedTask;
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove missing episodes?
|
// TODO: remove missing episodes?
|
||||||
@@ -273,34 +266,36 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Show show = result.Item;
|
Show show = result.Item;
|
||||||
await LocateNfoFileForShow(showFolder).Match(
|
|
||||||
async nfoFile =>
|
|
||||||
{
|
|
||||||
bool shouldUpdate = Optional(show.ShowMetadata).Flatten().HeadOrNone().Match(
|
|
||||||
m => m.MetadataKind == MetadataKind.Fallback ||
|
|
||||||
m.DateUpdated != _localFileSystem.GetLastWriteTime(nfoFile),
|
|
||||||
true);
|
|
||||||
|
|
||||||
if (shouldUpdate)
|
Option<string> maybeNfo = LocateNfoFileForShow(showFolder);
|
||||||
{
|
if (maybeNfo.IsNone)
|
||||||
_logger.LogDebug("Refreshing {Attribute} from {Path}", "Sidecar Metadata", nfoFile);
|
{
|
||||||
if (await _localMetadataProvider.RefreshSidecarMetadata(show, nfoFile))
|
if (!Optional(show.ShowMetadata).Flatten().Any())
|
||||||
{
|
|
||||||
result.IsUpdated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async () =>
|
|
||||||
{
|
{
|
||||||
if (!Optional(show.ShowMetadata).Flatten().Any())
|
_logger.LogDebug("Refreshing {Attribute} for {Path}", "Fallback Metadata", showFolder);
|
||||||
|
if (await _localMetadataProvider.RefreshFallbackMetadata(show, showFolder))
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Refreshing {Attribute} for {Path}", "Fallback Metadata", showFolder);
|
result.IsUpdated = true;
|
||||||
if (await _localMetadataProvider.RefreshFallbackMetadata(show, showFolder))
|
|
||||||
{
|
|
||||||
result.IsUpdated = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (string nfoFile in maybeNfo)
|
||||||
|
{
|
||||||
|
bool shouldUpdate = Optional(show.ShowMetadata).Flatten().HeadOrNone().Match(
|
||||||
|
m => m.MetadataKind == MetadataKind.Fallback ||
|
||||||
|
m.DateUpdated != _localFileSystem.GetLastWriteTime(nfoFile),
|
||||||
|
true);
|
||||||
|
|
||||||
|
if (shouldUpdate)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Refreshing {Attribute} from {Path}", "Sidecar Metadata", nfoFile);
|
||||||
|
if (await _localMetadataProvider.RefreshSidecarMetadata(show, nfoFile))
|
||||||
|
{
|
||||||
|
result.IsUpdated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -337,33 +332,34 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await LocateNfoFile(episode).Match(
|
Option<string> maybeNfo = LocateNfoFile(episode);
|
||||||
async nfoFile =>
|
if (maybeNfo.IsNone)
|
||||||
{
|
{
|
||||||
bool shouldUpdate = Optional(episode.EpisodeMetadata).Flatten().HeadOrNone().Match(
|
bool shouldUpdate = Optional(episode.EpisodeMetadata).Flatten().HeadOrNone().Match(
|
||||||
m => m.MetadataKind == MetadataKind.Fallback ||
|
m => m.DateUpdated == SystemTime.MinValueUtc,
|
||||||
m.DateUpdated != _localFileSystem.GetLastWriteTime(nfoFile),
|
true);
|
||||||
true);
|
|
||||||
|
|
||||||
if (shouldUpdate)
|
if (shouldUpdate)
|
||||||
{
|
|
||||||
_logger.LogDebug("Refreshing {Attribute} from {Path}", "Sidecar Metadata", nfoFile);
|
|
||||||
await _localMetadataProvider.RefreshSidecarMetadata(episode, nfoFile);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async () =>
|
|
||||||
{
|
{
|
||||||
bool shouldUpdate = Optional(episode.EpisodeMetadata).Flatten().HeadOrNone().Match(
|
string path = episode.MediaVersions.Head().MediaFiles.Head().Path;
|
||||||
m => m.DateUpdated == SystemTime.MinValueUtc,
|
_logger.LogDebug("Refreshing {Attribute} for {Path}", "Fallback Metadata", path);
|
||||||
true);
|
await _localMetadataProvider.RefreshFallbackMetadata(episode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (shouldUpdate)
|
foreach (string nfoFile in maybeNfo)
|
||||||
{
|
{
|
||||||
string path = episode.MediaVersions.Head().MediaFiles.Head().Path;
|
bool shouldUpdate = Optional(episode.EpisodeMetadata).Flatten().HeadOrNone().Match(
|
||||||
_logger.LogDebug("Refreshing {Attribute} for {Path}", "Fallback Metadata", path);
|
m => m.MetadataKind == MetadataKind.Fallback ||
|
||||||
await _localMetadataProvider.RefreshFallbackMetadata(episode);
|
m.DateUpdated != _localFileSystem.GetLastWriteTime(nfoFile),
|
||||||
}
|
true);
|
||||||
});
|
|
||||||
|
if (shouldUpdate)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Refreshing {Attribute} from {Path}", "Sidecar Metadata", nfoFile);
|
||||||
|
await _localMetadataProvider.RefreshSidecarMetadata(episode, nfoFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return episode;
|
return episode;
|
||||||
}
|
}
|
||||||
@@ -383,12 +379,12 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Show show = result.Item;
|
Show show = result.Item;
|
||||||
await LocateArtworkForShow(showFolder, artworkKind).IfSomeAsync(
|
Option<string> maybeArtwork = LocateArtworkForShow(showFolder, artworkKind);
|
||||||
async artworkFile =>
|
foreach (string artworkFile in maybeArtwork)
|
||||||
{
|
{
|
||||||
ShowMetadata metadata = show.ShowMetadata.Head();
|
ShowMetadata metadata = show.ShowMetadata.Head();
|
||||||
await RefreshArtwork(artworkFile, metadata, artworkKind, None, None, cancellationToken);
|
await RefreshArtwork(artworkFile, metadata, artworkKind, None, None, cancellationToken);
|
||||||
});
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -406,12 +402,12 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await LocatePoster(season, seasonFolder).IfSomeAsync(
|
Option<string> maybePoster = LocatePoster(season, seasonFolder);
|
||||||
async posterFile =>
|
foreach (string posterFile in maybePoster)
|
||||||
{
|
{
|
||||||
SeasonMetadata metadata = season.SeasonMetadata.Head();
|
SeasonMetadata metadata = season.SeasonMetadata.Head();
|
||||||
await RefreshArtwork(posterFile, metadata, ArtworkKind.Poster, None, None, cancellationToken);
|
await RefreshArtwork(posterFile, metadata, ArtworkKind.Poster, None, None, cancellationToken);
|
||||||
});
|
}
|
||||||
|
|
||||||
return season;
|
return season;
|
||||||
}
|
}
|
||||||
@@ -426,20 +422,20 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await LocateThumbnail(episode).IfSomeAsync(
|
Option<string> maybeThumbnail = LocateThumbnail(episode);
|
||||||
async posterFile =>
|
foreach (string thumbnailFile in maybeThumbnail)
|
||||||
|
{
|
||||||
|
foreach (EpisodeMetadata metadata in episode.EpisodeMetadata)
|
||||||
{
|
{
|
||||||
foreach (EpisodeMetadata metadata in episode.EpisodeMetadata)
|
await RefreshArtwork(
|
||||||
{
|
thumbnailFile,
|
||||||
await RefreshArtwork(
|
metadata,
|
||||||
posterFile,
|
ArtworkKind.Thumbnail,
|
||||||
metadata,
|
None,
|
||||||
ArtworkKind.Thumbnail,
|
None,
|
||||||
None,
|
cancellationToken);
|
||||||
None,
|
}
|
||||||
cancellationToken);
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return episode;
|
return episode;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -290,28 +290,26 @@ public class PlexMovieLibraryScanner : PlexLibraryScanner, IPlexMovieLibraryScan
|
|||||||
Either<BaseError, bool> refreshResult =
|
Either<BaseError, bool> refreshResult =
|
||||||
await _localStatisticsProvider.RefreshStatistics(ffmpegPath, ffprobePath, existing, localPath);
|
await _localStatisticsProvider.RefreshStatistics(ffmpegPath, ffprobePath, existing, localPath);
|
||||||
|
|
||||||
await refreshResult.Match(
|
foreach (BaseError error in refreshResult.LeftToSeq())
|
||||||
async _ =>
|
{
|
||||||
{
|
_logger.LogWarning(
|
||||||
foreach (MediaItem updated in await _searchRepository.GetItemToIndex(incoming.Id))
|
"Unable to refresh {Attribute} for media item {Path}. Error: {Error}",
|
||||||
{
|
"Statistics",
|
||||||
await _searchIndex.UpdateItems(
|
localPath,
|
||||||
_searchRepository,
|
error.Value);
|
||||||
new List<MediaItem> { updated });
|
}
|
||||||
}
|
|
||||||
|
|
||||||
await _metadataRepository.UpdatePlexStatistics(existingVersion.Id, incomingVersion);
|
foreach (bool _ in refreshResult.RightToSeq())
|
||||||
},
|
{
|
||||||
error =>
|
foreach (MediaItem updated in await _searchRepository.GetItemToIndex(incoming.Id))
|
||||||
{
|
{
|
||||||
_logger.LogWarning(
|
await _searchIndex.UpdateItems(
|
||||||
"Unable to refresh {Attribute} for media item {Path}. Error: {Error}",
|
_searchRepository,
|
||||||
"Statistics",
|
new List<MediaItem> { updated });
|
||||||
localPath,
|
}
|
||||||
error.Value);
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
await _metadataRepository.UpdatePlexStatistics(existingVersion.Id, incomingVersion);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,193 +338,191 @@ public class PlexMovieLibraryScanner : PlexLibraryScanner, IPlexMovieLibraryScan
|
|||||||
connection,
|
connection,
|
||||||
token);
|
token);
|
||||||
|
|
||||||
await maybeMetadata.Match(
|
foreach (MovieMetadata fullMetadata in maybeMetadata.RightToSeq())
|
||||||
async fullMetadata =>
|
{
|
||||||
|
if (existingMetadata.MetadataKind != MetadataKind.External)
|
||||||
{
|
{
|
||||||
if (existingMetadata.MetadataKind != MetadataKind.External)
|
existingMetadata.MetadataKind = MetadataKind.External;
|
||||||
{
|
await _metadataRepository.MarkAsExternal(existingMetadata);
|
||||||
existingMetadata.MetadataKind = MetadataKind.External;
|
}
|
||||||
await _metadataRepository.MarkAsExternal(existingMetadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingMetadata.ContentRating != fullMetadata.ContentRating)
|
if (existingMetadata.ContentRating != fullMetadata.ContentRating)
|
||||||
|
{
|
||||||
|
existingMetadata.ContentRating = fullMetadata.ContentRating;
|
||||||
|
await _metadataRepository.SetContentRating(existingMetadata, fullMetadata.ContentRating);
|
||||||
|
result.IsUpdated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Genre genre in existingMetadata.Genres
|
||||||
|
.Filter(g => fullMetadata.Genres.All(g2 => g2.Name != g.Name))
|
||||||
|
.ToList())
|
||||||
|
{
|
||||||
|
existingMetadata.Genres.Remove(genre);
|
||||||
|
if (await _metadataRepository.RemoveGenre(genre))
|
||||||
{
|
{
|
||||||
existingMetadata.ContentRating = fullMetadata.ContentRating;
|
|
||||||
await _metadataRepository.SetContentRating(existingMetadata, fullMetadata.ContentRating);
|
|
||||||
result.IsUpdated = true;
|
result.IsUpdated = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (Genre genre in existingMetadata.Genres
|
foreach (Genre genre in fullMetadata.Genres
|
||||||
.Filter(g => fullMetadata.Genres.All(g2 => g2.Name != g.Name))
|
.Filter(g => existingMetadata.Genres.All(g2 => g2.Name != g.Name))
|
||||||
.ToList())
|
.ToList())
|
||||||
|
{
|
||||||
|
existingMetadata.Genres.Add(genre);
|
||||||
|
if (await _movieRepository.AddGenre(existingMetadata, genre))
|
||||||
{
|
{
|
||||||
existingMetadata.Genres.Remove(genre);
|
result.IsUpdated = true;
|
||||||
if (await _metadataRepository.RemoveGenre(genre))
|
|
||||||
{
|
|
||||||
result.IsUpdated = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (Genre genre in fullMetadata.Genres
|
foreach (Studio studio in existingMetadata.Studios
|
||||||
.Filter(g => existingMetadata.Genres.All(g2 => g2.Name != g.Name))
|
.Filter(s => fullMetadata.Studios.All(s2 => s2.Name != s.Name))
|
||||||
.ToList())
|
.ToList())
|
||||||
|
{
|
||||||
|
existingMetadata.Studios.Remove(studio);
|
||||||
|
if (await _metadataRepository.RemoveStudio(studio))
|
||||||
{
|
{
|
||||||
existingMetadata.Genres.Add(genre);
|
result.IsUpdated = true;
|
||||||
if (await _movieRepository.AddGenre(existingMetadata, genre))
|
|
||||||
{
|
|
||||||
result.IsUpdated = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (Studio studio in existingMetadata.Studios
|
foreach (Studio studio in fullMetadata.Studios
|
||||||
.Filter(s => fullMetadata.Studios.All(s2 => s2.Name != s.Name))
|
.Filter(s => existingMetadata.Studios.All(s2 => s2.Name != s.Name))
|
||||||
.ToList())
|
.ToList())
|
||||||
|
{
|
||||||
|
existingMetadata.Studios.Add(studio);
|
||||||
|
if (await _movieRepository.AddStudio(existingMetadata, studio))
|
||||||
{
|
{
|
||||||
existingMetadata.Studios.Remove(studio);
|
result.IsUpdated = true;
|
||||||
if (await _metadataRepository.RemoveStudio(studio))
|
|
||||||
{
|
|
||||||
result.IsUpdated = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (Studio studio in fullMetadata.Studios
|
foreach (Actor actor in existingMetadata.Actors
|
||||||
.Filter(s => existingMetadata.Studios.All(s2 => s2.Name != s.Name))
|
.Filter(
|
||||||
.ToList())
|
a => fullMetadata.Actors.All(
|
||||||
|
a2 => a2.Name != a.Name || a.Artwork == null && a2.Artwork != null))
|
||||||
|
.ToList())
|
||||||
|
{
|
||||||
|
existingMetadata.Actors.Remove(actor);
|
||||||
|
if (await _metadataRepository.RemoveActor(actor))
|
||||||
{
|
{
|
||||||
existingMetadata.Studios.Add(studio);
|
result.IsUpdated = true;
|
||||||
if (await _movieRepository.AddStudio(existingMetadata, studio))
|
|
||||||
{
|
|
||||||
result.IsUpdated = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (Actor actor in existingMetadata.Actors
|
foreach (Actor actor in fullMetadata.Actors
|
||||||
.Filter(
|
.Filter(a => existingMetadata.Actors.All(a2 => a2.Name != a.Name))
|
||||||
a => fullMetadata.Actors.All(
|
.ToList())
|
||||||
a2 => a2.Name != a.Name || a.Artwork == null && a2.Artwork != null))
|
{
|
||||||
.ToList())
|
existingMetadata.Actors.Add(actor);
|
||||||
|
if (await _movieRepository.AddActor(existingMetadata, actor))
|
||||||
{
|
{
|
||||||
existingMetadata.Actors.Remove(actor);
|
result.IsUpdated = true;
|
||||||
if (await _metadataRepository.RemoveActor(actor))
|
|
||||||
{
|
|
||||||
result.IsUpdated = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (Actor actor in fullMetadata.Actors
|
foreach (Director director in existingMetadata.Directors
|
||||||
.Filter(a => existingMetadata.Actors.All(a2 => a2.Name != a.Name))
|
.Filter(g => fullMetadata.Directors.All(g2 => g2.Name != g.Name))
|
||||||
.ToList())
|
.ToList())
|
||||||
|
{
|
||||||
|
existingMetadata.Directors.Remove(director);
|
||||||
|
if (await _metadataRepository.RemoveDirector(director))
|
||||||
{
|
{
|
||||||
existingMetadata.Actors.Add(actor);
|
result.IsUpdated = true;
|
||||||
if (await _movieRepository.AddActor(existingMetadata, actor))
|
|
||||||
{
|
|
||||||
result.IsUpdated = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (Director director in existingMetadata.Directors
|
foreach (Director director in fullMetadata.Directors
|
||||||
.Filter(g => fullMetadata.Directors.All(g2 => g2.Name != g.Name))
|
.Filter(g => existingMetadata.Directors.All(g2 => g2.Name != g.Name))
|
||||||
.ToList())
|
.ToList())
|
||||||
|
{
|
||||||
|
existingMetadata.Directors.Add(director);
|
||||||
|
if (await _movieRepository.AddDirector(existingMetadata, director))
|
||||||
{
|
{
|
||||||
existingMetadata.Directors.Remove(director);
|
result.IsUpdated = true;
|
||||||
if (await _metadataRepository.RemoveDirector(director))
|
|
||||||
{
|
|
||||||
result.IsUpdated = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (Director director in fullMetadata.Directors
|
foreach (Writer writer in existingMetadata.Writers
|
||||||
.Filter(g => existingMetadata.Directors.All(g2 => g2.Name != g.Name))
|
.Filter(g => fullMetadata.Writers.All(g2 => g2.Name != g.Name))
|
||||||
.ToList())
|
.ToList())
|
||||||
|
{
|
||||||
|
existingMetadata.Writers.Remove(writer);
|
||||||
|
if (await _metadataRepository.RemoveWriter(writer))
|
||||||
{
|
{
|
||||||
existingMetadata.Directors.Add(director);
|
result.IsUpdated = true;
|
||||||
if (await _movieRepository.AddDirector(existingMetadata, director))
|
|
||||||
{
|
|
||||||
result.IsUpdated = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (Writer writer in existingMetadata.Writers
|
foreach (Writer writer in fullMetadata.Writers
|
||||||
.Filter(g => fullMetadata.Writers.All(g2 => g2.Name != g.Name))
|
.Filter(g => existingMetadata.Writers.All(g2 => g2.Name != g.Name))
|
||||||
.ToList())
|
.ToList())
|
||||||
|
{
|
||||||
|
existingMetadata.Writers.Add(writer);
|
||||||
|
if (await _movieRepository.AddWriter(existingMetadata, writer))
|
||||||
{
|
{
|
||||||
existingMetadata.Writers.Remove(writer);
|
result.IsUpdated = true;
|
||||||
if (await _metadataRepository.RemoveWriter(writer))
|
|
||||||
{
|
|
||||||
result.IsUpdated = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (Writer writer in fullMetadata.Writers
|
foreach (MetadataGuid guid in existingMetadata.Guids
|
||||||
.Filter(g => existingMetadata.Writers.All(g2 => g2.Name != g.Name))
|
.Filter(g => fullMetadata.Guids.All(g2 => g2.Guid != g.Guid))
|
||||||
.ToList())
|
.ToList())
|
||||||
|
{
|
||||||
|
existingMetadata.Guids.Remove(guid);
|
||||||
|
if (await _metadataRepository.RemoveGuid(guid))
|
||||||
{
|
{
|
||||||
existingMetadata.Writers.Add(writer);
|
result.IsUpdated = true;
|
||||||
if (await _movieRepository.AddWriter(existingMetadata, writer))
|
|
||||||
{
|
|
||||||
result.IsUpdated = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (MetadataGuid guid in existingMetadata.Guids
|
foreach (MetadataGuid guid in fullMetadata.Guids
|
||||||
.Filter(g => fullMetadata.Guids.All(g2 => g2.Guid != g.Guid))
|
.Filter(g => existingMetadata.Guids.All(g2 => g2.Guid != g.Guid))
|
||||||
.ToList())
|
.ToList())
|
||||||
|
{
|
||||||
|
existingMetadata.Guids.Add(guid);
|
||||||
|
if (await _metadataRepository.AddGuid(existingMetadata, guid))
|
||||||
{
|
{
|
||||||
existingMetadata.Guids.Remove(guid);
|
result.IsUpdated = true;
|
||||||
if (await _metadataRepository.RemoveGuid(guid))
|
|
||||||
{
|
|
||||||
result.IsUpdated = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (MetadataGuid guid in fullMetadata.Guids
|
foreach (Tag tag in existingMetadata.Tags
|
||||||
.Filter(g => existingMetadata.Guids.All(g2 => g2.Guid != g.Guid))
|
.Filter(g => fullMetadata.Tags.All(g2 => g2.Name != g.Name))
|
||||||
.ToList())
|
.ToList())
|
||||||
|
{
|
||||||
|
existingMetadata.Tags.Remove(tag);
|
||||||
|
if (await _metadataRepository.RemoveTag(tag))
|
||||||
{
|
{
|
||||||
existingMetadata.Guids.Add(guid);
|
result.IsUpdated = true;
|
||||||
if (await _metadataRepository.AddGuid(existingMetadata, guid))
|
|
||||||
{
|
|
||||||
result.IsUpdated = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (Tag tag in existingMetadata.Tags
|
foreach (Tag tag in fullMetadata.Tags
|
||||||
.Filter(g => fullMetadata.Tags.All(g2 => g2.Name != g.Name))
|
.Filter(g => existingMetadata.Tags.All(g2 => g2.Name != g.Name))
|
||||||
.ToList())
|
.ToList())
|
||||||
|
{
|
||||||
|
existingMetadata.Tags.Add(tag);
|
||||||
|
if (await _movieRepository.AddTag(existingMetadata, tag))
|
||||||
{
|
{
|
||||||
existingMetadata.Tags.Remove(tag);
|
result.IsUpdated = true;
|
||||||
if (await _metadataRepository.RemoveTag(tag))
|
|
||||||
{
|
|
||||||
result.IsUpdated = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (Tag tag in fullMetadata.Tags
|
if (fullMetadata.SortTitle != existingMetadata.SortTitle)
|
||||||
.Filter(g => existingMetadata.Tags.All(g2 => g2.Name != g.Name))
|
{
|
||||||
.ToList())
|
existingMetadata.SortTitle = fullMetadata.SortTitle;
|
||||||
|
if (await _movieRepository.UpdateSortTitle(existingMetadata))
|
||||||
{
|
{
|
||||||
existingMetadata.Tags.Add(tag);
|
result.IsUpdated = true;
|
||||||
if (await _movieRepository.AddTag(existingMetadata, tag))
|
|
||||||
{
|
|
||||||
result.IsUpdated = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (fullMetadata.SortTitle != existingMetadata.SortTitle)
|
if (result.IsUpdated)
|
||||||
{
|
{
|
||||||
existingMetadata.SortTitle = fullMetadata.SortTitle;
|
await _metadataRepository.MarkAsUpdated(existingMetadata, fullMetadata.DateUpdated);
|
||||||
if (await _movieRepository.UpdateSortTitle(existingMetadata))
|
}
|
||||||
{
|
}
|
||||||
result.IsUpdated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.IsUpdated)
|
|
||||||
{
|
|
||||||
await _metadataRepository.MarkAsUpdated(existingMetadata, fullMetadata.DateUpdated);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => Task.CompletedTask);
|
|
||||||
|
|
||||||
// TODO: update other metadata?
|
// TODO: update other metadata?
|
||||||
|
|
||||||
|
|||||||
@@ -747,83 +747,79 @@ public class PlexTelevisionLibraryScanner : PlexLibraryScanner, IPlexTelevisionL
|
|||||||
localPath);
|
localPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
await refreshResult.Match(
|
foreach (BaseError error in refreshResult.LeftToSeq())
|
||||||
async _ =>
|
{
|
||||||
|
_logger.LogWarning(
|
||||||
|
"Unable to refresh {Attribute} for media item {Path}. Error: {Error}",
|
||||||
|
"Statistics",
|
||||||
|
localPath,
|
||||||
|
error.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var _ in refreshResult.RightToSeq())
|
||||||
|
{
|
||||||
|
foreach (MediaItem updated in await _searchRepository.GetItemToIndex(incoming.Id))
|
||||||
{
|
{
|
||||||
foreach (MediaItem updated in await _searchRepository.GetItemToIndex(incoming.Id))
|
await _searchIndex.UpdateItems(
|
||||||
|
_searchRepository,
|
||||||
|
new List<MediaItem> { updated });
|
||||||
|
}
|
||||||
|
|
||||||
|
Either<BaseError, Tuple<EpisodeMetadata, MediaVersion>> maybeStatistics =
|
||||||
|
await _plexServerApiClient.GetEpisodeMetadataAndStatistics(
|
||||||
|
library,
|
||||||
|
incoming.Key.Split("/").Last(),
|
||||||
|
connection,
|
||||||
|
token);
|
||||||
|
|
||||||
|
foreach (Tuple<EpisodeMetadata, MediaVersion> tuple in maybeStatistics.RightToSeq())
|
||||||
|
{
|
||||||
|
(EpisodeMetadata incomingMetadata, MediaVersion mediaVersion) = tuple;
|
||||||
|
|
||||||
|
Option<EpisodeMetadata> maybeExisting = existing.EpisodeMetadata
|
||||||
|
.Find(em => em.EpisodeNumber == incomingMetadata.EpisodeNumber);
|
||||||
|
foreach (EpisodeMetadata existingMetadata in maybeExisting)
|
||||||
{
|
{
|
||||||
await _searchIndex.UpdateItems(
|
foreach (MetadataGuid guid in existingMetadata.Guids
|
||||||
_searchRepository,
|
.Filter(g => incomingMetadata.Guids.All(g2 => g2.Guid != g.Guid))
|
||||||
new List<MediaItem> { updated });
|
.ToList())
|
||||||
|
{
|
||||||
|
existingMetadata.Guids.Remove(guid);
|
||||||
|
await _metadataRepository.RemoveGuid(guid);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (MetadataGuid guid in incomingMetadata.Guids
|
||||||
|
.Filter(g => existingMetadata.Guids.All(g2 => g2.Guid != g.Guid))
|
||||||
|
.ToList())
|
||||||
|
{
|
||||||
|
existingMetadata.Guids.Add(guid);
|
||||||
|
await _metadataRepository.AddGuid(existingMetadata, guid);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Tag tag in existingMetadata.Tags
|
||||||
|
.Filter(g => incomingMetadata.Tags.All(g2 => g2.Name != g.Name))
|
||||||
|
.ToList())
|
||||||
|
{
|
||||||
|
existingMetadata.Tags.Remove(tag);
|
||||||
|
await _metadataRepository.RemoveTag(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Tag tag in incomingMetadata.Tags
|
||||||
|
.Filter(g => existingMetadata.Tags.All(g2 => g2.Name != g.Name))
|
||||||
|
.ToList())
|
||||||
|
{
|
||||||
|
existingMetadata.Tags.Add(tag);
|
||||||
|
await _televisionRepository.AddTag(existingMetadata, tag);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Either<BaseError, Tuple<EpisodeMetadata, MediaVersion>> maybeStatistics =
|
existingVersion.SampleAspectRatio = mediaVersion.SampleAspectRatio;
|
||||||
await _plexServerApiClient.GetEpisodeMetadataAndStatistics(
|
existingVersion.VideoScanKind = mediaVersion.VideoScanKind;
|
||||||
library,
|
existingVersion.DateUpdated = mediaVersion.DateUpdated;
|
||||||
incoming.Key.Split("/").Last(),
|
|
||||||
connection,
|
|
||||||
token);
|
|
||||||
|
|
||||||
await maybeStatistics.Match(
|
await _metadataRepository.UpdatePlexStatistics(existingVersion.Id, mediaVersion);
|
||||||
async tuple =>
|
}
|
||||||
{
|
}
|
||||||
(EpisodeMetadata incomingMetadata, MediaVersion mediaVersion) = tuple;
|
|
||||||
|
|
||||||
Option<EpisodeMetadata> maybeExisting = existing.EpisodeMetadata
|
|
||||||
.Find(em => em.EpisodeNumber == incomingMetadata.EpisodeNumber);
|
|
||||||
foreach (EpisodeMetadata existingMetadata in maybeExisting)
|
|
||||||
{
|
|
||||||
foreach (MetadataGuid guid in existingMetadata.Guids
|
|
||||||
.Filter(g => incomingMetadata.Guids.All(g2 => g2.Guid != g.Guid))
|
|
||||||
.ToList())
|
|
||||||
{
|
|
||||||
existingMetadata.Guids.Remove(guid);
|
|
||||||
await _metadataRepository.RemoveGuid(guid);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (MetadataGuid guid in incomingMetadata.Guids
|
|
||||||
.Filter(g => existingMetadata.Guids.All(g2 => g2.Guid != g.Guid))
|
|
||||||
.ToList())
|
|
||||||
{
|
|
||||||
existingMetadata.Guids.Add(guid);
|
|
||||||
await _metadataRepository.AddGuid(existingMetadata, guid);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (Tag tag in existingMetadata.Tags
|
|
||||||
.Filter(g => incomingMetadata.Tags.All(g2 => g2.Name != g.Name))
|
|
||||||
.ToList())
|
|
||||||
{
|
|
||||||
existingMetadata.Tags.Remove(tag);
|
|
||||||
await _metadataRepository.RemoveTag(tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (Tag tag in incomingMetadata.Tags
|
|
||||||
.Filter(g => existingMetadata.Tags.All(g2 => g2.Name != g.Name))
|
|
||||||
.ToList())
|
|
||||||
{
|
|
||||||
existingMetadata.Tags.Add(tag);
|
|
||||||
await _televisionRepository.AddTag(existingMetadata, tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
existingVersion.SampleAspectRatio = mediaVersion.SampleAspectRatio;
|
|
||||||
existingVersion.VideoScanKind = mediaVersion.VideoScanKind;
|
|
||||||
existingVersion.DateUpdated = mediaVersion.DateUpdated;
|
|
||||||
|
|
||||||
await _metadataRepository.UpdatePlexStatistics(existingVersion.Id, mediaVersion);
|
|
||||||
},
|
|
||||||
_ => Task.CompletedTask);
|
|
||||||
},
|
|
||||||
error =>
|
|
||||||
{
|
|
||||||
_logger.LogWarning(
|
|
||||||
"Unable to refresh {Attribute} for media item {Path}. Error: {Error}",
|
|
||||||
"Statistics",
|
|
||||||
localPath,
|
|
||||||
error.Value);
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
FROM jasongdove/ffmpeg:5.0-ubuntu2004 AS runtime-base
|
FROM jasongdove/ffmpeg:5.0-ubuntu2004 AS runtime-base
|
||||||
COPY --from=dotnet-runtime /usr/share/dotnet /usr/share/dotnet
|
COPY --from=dotnet-runtime /usr/share/dotnet /usr/share/dotnet
|
||||||
RUN apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y libicu-dev tzdata fontconfig fonts-dejavu
|
RUN apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y libicu-dev tzdata fontconfig fonts-dejavu libgdiplus
|
||||||
|
|
||||||
# https://hub.docker.com/_/microsoft-dotnet
|
# https://hub.docker.com/_/microsoft-dotnet
|
||||||
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
|
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
FROM jasongdove/ffmpeg-base:5.0-nvidia2004 AS runtime-base
|
FROM jasongdove/ffmpeg-base:5.0-nvidia2004 AS runtime-base
|
||||||
COPY --from=dotnet-runtime /usr/share/dotnet /usr/share/dotnet
|
COPY --from=dotnet-runtime /usr/share/dotnet /usr/share/dotnet
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& DEBIAN_FRONTEND="noninteractive" apt-get install -y libicu-dev tzdata fontconfig fonts-dejavu \
|
&& DEBIAN_FRONTEND="noninteractive" apt-get install -y libicu-dev tzdata fontconfig fonts-dejavu libgdiplus \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ RUN apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y libicu
|
|||||||
tzdata \
|
tzdata \
|
||||||
fontconfig \
|
fontconfig \
|
||||||
fonts-dejavu \
|
fonts-dejavu \
|
||||||
|
libgdiplus \
|
||||||
autoconf \
|
autoconf \
|
||||||
libtool \
|
libtool \
|
||||||
libdrm-dev \
|
libdrm-dev \
|
||||||
|
|||||||
Reference in New Issue
Block a user