async fixes (#128)
* refactor local metadata provider * resolve async warnings * more async fixes
This commit is contained in:
@@ -82,7 +82,7 @@ namespace ErsatzTV.Application.Channels.Commands
|
||||
private async Task<Validation<BaseError, string>> ValidateNumber(UpdateChannel updateChannel)
|
||||
{
|
||||
Option<Channel> match = await _channelRepository.GetByNumber(updateChannel.Number);
|
||||
int matchId = match.Map(c => c.Id).IfNone(updateChannel.ChannelId);
|
||||
int matchId = await match.Map(c => c.Id).IfNoneAsync(updateChannel.ChannelId);
|
||||
if (matchId == updateChannel.ChannelId)
|
||||
{
|
||||
if (Regex.IsMatch(updateChannel.Number, Channel.NumberValidator))
|
||||
|
||||
@@ -2,11 +2,20 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<NoWarn>VSTHRD200</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AsyncFixer" Version="1.5.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MediatR" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="16.9.60">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="Winista.MimeDetect" Version="1.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -71,87 +71,32 @@ namespace ErsatzTV.Application.FFmpegProfiles.Commands
|
||||
|
||||
private async Task<Unit> ApplyUpdate(UpdateFFmpegSettings request)
|
||||
{
|
||||
await _configElementRepository.Get(ConfigElementKey.FFmpegPath).Match(
|
||||
ce =>
|
||||
{
|
||||
ce.Value = request.Settings.FFmpegPath;
|
||||
_configElementRepository.Update(ce);
|
||||
},
|
||||
() =>
|
||||
{
|
||||
var ce = new ConfigElement
|
||||
{ Key = ConfigElementKey.FFmpegPath.Key, Value = request.Settings.FFmpegPath };
|
||||
_configElementRepository.Add(ce);
|
||||
});
|
||||
|
||||
await _configElementRepository.Get(ConfigElementKey.FFprobePath).Match(
|
||||
ce =>
|
||||
{
|
||||
ce.Value = request.Settings.FFprobePath;
|
||||
_configElementRepository.Update(ce);
|
||||
},
|
||||
() =>
|
||||
{
|
||||
var ce = new ConfigElement
|
||||
{ Key = ConfigElementKey.FFprobePath.Key, Value = request.Settings.FFprobePath };
|
||||
_configElementRepository.Add(ce);
|
||||
});
|
||||
|
||||
await _configElementRepository.Get(ConfigElementKey.FFmpegDefaultProfileId).Match(
|
||||
ce =>
|
||||
{
|
||||
ce.Value = request.Settings.DefaultFFmpegProfileId.ToString();
|
||||
_configElementRepository.Update(ce);
|
||||
},
|
||||
() =>
|
||||
{
|
||||
var ce = new ConfigElement
|
||||
{
|
||||
Key = ConfigElementKey.FFmpegDefaultProfileId.Key,
|
||||
Value = request.Settings.DefaultFFmpegProfileId.ToString()
|
||||
};
|
||||
_configElementRepository.Add(ce);
|
||||
});
|
||||
|
||||
await _configElementRepository.Get(ConfigElementKey.FFmpegSaveReports).Match(
|
||||
ce =>
|
||||
{
|
||||
ce.Value = request.Settings.SaveReports.ToString();
|
||||
_configElementRepository.Update(ce);
|
||||
},
|
||||
() =>
|
||||
{
|
||||
var ce = new ConfigElement
|
||||
{
|
||||
Key = ConfigElementKey.FFmpegSaveReports.Key,
|
||||
Value = request.Settings.SaveReports.ToString()
|
||||
};
|
||||
_configElementRepository.Add(ce);
|
||||
});
|
||||
await Upsert(ConfigElementKey.FFmpegPath, request.Settings.FFmpegPath);
|
||||
await Upsert(ConfigElementKey.FFprobePath, request.Settings.FFprobePath);
|
||||
await Upsert(ConfigElementKey.FFmpegDefaultProfileId, request.Settings.DefaultFFmpegProfileId.ToString());
|
||||
await Upsert(ConfigElementKey.FFmpegSaveReports, request.Settings.SaveReports.ToString());
|
||||
|
||||
if (request.Settings.SaveReports && !Directory.Exists(FileSystemLayout.FFmpegReportsFolder))
|
||||
{
|
||||
Directory.CreateDirectory(FileSystemLayout.FFmpegReportsFolder);
|
||||
}
|
||||
|
||||
await _configElementRepository.Get(ConfigElementKey.FFmpegPreferredLanguageCode).Match(
|
||||
ce =>
|
||||
{
|
||||
ce.Value = request.Settings.PreferredLanguageCode;
|
||||
_configElementRepository.Update(ce);
|
||||
},
|
||||
() =>
|
||||
{
|
||||
var ce = new ConfigElement
|
||||
{
|
||||
Key = ConfigElementKey.FFmpegPreferredLanguageCode.Key,
|
||||
Value = request.Settings.PreferredLanguageCode
|
||||
};
|
||||
_configElementRepository.Add(ce);
|
||||
});
|
||||
|
||||
await Upsert(ConfigElementKey.FFmpegPreferredLanguageCode, request.Settings.PreferredLanguageCode);
|
||||
|
||||
return Unit.Default;
|
||||
}
|
||||
|
||||
private Task Upsert(ConfigElementKey key, string value) =>
|
||||
_configElementRepository.Get(key).Match(
|
||||
ce =>
|
||||
{
|
||||
ce.Value = value;
|
||||
return _configElementRepository.Update(ce);
|
||||
},
|
||||
() =>
|
||||
{
|
||||
var ce = new ConfigElement { Key = key.Key, Value = value };
|
||||
return _configElementRepository.Add(ce);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,11 +29,11 @@ namespace ErsatzTV.Application.FFmpegProfiles.Queries
|
||||
|
||||
return new FFmpegSettingsViewModel
|
||||
{
|
||||
FFmpegPath = ffmpegPath.IfNone(string.Empty),
|
||||
FFprobePath = ffprobePath.IfNone(string.Empty),
|
||||
DefaultFFmpegProfileId = defaultFFmpegProfileId.IfNone(0),
|
||||
SaveReports = saveReports.IfNone(false),
|
||||
PreferredLanguageCode = preferredLanguageCode.IfNone("eng")
|
||||
FFmpegPath = await ffmpegPath.IfNoneAsync(string.Empty),
|
||||
FFprobePath = await ffprobePath.IfNoneAsync(string.Empty),
|
||||
DefaultFFmpegProfileId = await defaultFFmpegProfileId.IfNoneAsync(0),
|
||||
SaveReports = await saveReports.IfNoneAsync(false),
|
||||
PreferredLanguageCode = await preferredLanguageCode.IfNoneAsync("eng")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ namespace ErsatzTV.Application.MediaCollections.Commands
|
||||
Option<CollectionItem> maybeCollectionItem =
|
||||
c.CollectionItems.FirstOrDefault(ci => ci.MediaItemId == updateItem.MediaItemId);
|
||||
|
||||
maybeCollectionItem.IfSome(ci => ci.CustomIndex = updateItem.CustomIndex);
|
||||
await maybeCollectionItem.IfSomeAsync(ci => ci.CustomIndex = updateItem.CustomIndex);
|
||||
}
|
||||
|
||||
if (await _mediaCollectionRepository.Update(c))
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace ErsatzTV.Application.MediaCollections.Commands
|
||||
private async Task<Unit> ApplyUpdateRequest(Collection c, UpdateCollection request)
|
||||
{
|
||||
c.Name = request.Name;
|
||||
request.UseCustomPlaybackOrder.IfSome(
|
||||
await request.UseCustomPlaybackOrder.IfSomeAsync(
|
||||
useCustomPlaybackOrder => c.UseCustomPlaybackOrder = useCustomPlaybackOrder);
|
||||
if (await _mediaCollectionRepository.Update(c) && request.UseCustomPlaybackOrder.IsSome)
|
||||
{
|
||||
|
||||
@@ -57,11 +57,11 @@ namespace ErsatzTV.Application.Plex.Commands
|
||||
return allExisting;
|
||||
}
|
||||
|
||||
private async Task SynchronizeServer(List<PlexMediaSource> allExisting, PlexMediaSource server)
|
||||
private Task SynchronizeServer(List<PlexMediaSource> allExisting, PlexMediaSource server)
|
||||
{
|
||||
Option<PlexMediaSource> maybeExisting =
|
||||
allExisting.Find(s => s.ClientIdentifier == server.ClientIdentifier);
|
||||
await maybeExisting.Match(
|
||||
return maybeExisting.Match(
|
||||
existing =>
|
||||
{
|
||||
existing.Platform = server.Platform;
|
||||
@@ -84,15 +84,5 @@ namespace ErsatzTV.Application.Plex.Commands
|
||||
await _mediaSourceRepository.Add(server);
|
||||
});
|
||||
}
|
||||
|
||||
private void MergeConnections(
|
||||
List<PlexConnection> existing,
|
||||
List<PlexConnection> incoming)
|
||||
{
|
||||
var toAdd = incoming.Filter(connection => existing.All(c => c.Uri != connection.Uri)).ToList();
|
||||
var toRemove = existing.Filter(connection => incoming.All(c => c.Uri != connection.Uri)).ToList();
|
||||
existing.AddRange(toAdd);
|
||||
toRemove.ForEach(c => existing.Remove(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,22 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<NoWarn>VSTHRD200</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AsyncFixer" Version="1.5.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="FluentAssertions" Version="5.10.3" />
|
||||
<PackageReference Include="LanguageExt.Core" Version="3.4.15" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="16.9.60">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Moq" Version="4.16.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.1" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace ErsatzTV.Core.Tests.Metadata
|
||||
.Returns<string, MediaItem>((_, _) => Right<BaseError, bool>(true).AsTask());
|
||||
|
||||
// fallback metadata adds metadata to a movie, so we need to replicate that here
|
||||
_localMetadataProvider.Setup(x => x.RefreshFallbackMetadata(It.IsAny<MediaItem>()))
|
||||
_localMetadataProvider.Setup(x => x.RefreshFallbackMetadata(It.IsAny<Movie>()))
|
||||
.Returns(
|
||||
(MediaItem mediaItem) =>
|
||||
{
|
||||
|
||||
@@ -2,12 +2,21 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<NoWarn>VSTHRD200</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AsyncFixer" Version="1.5.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="LanguageExt.Core" Version="3.4.15" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="16.9.60">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="Serilog" Version="2.10.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
|
||||
|
||||
@@ -8,9 +8,12 @@ namespace ErsatzTV.Core.Interfaces.Metadata
|
||||
{
|
||||
Task<ShowMetadata> GetMetadataForShow(string showFolder);
|
||||
Task<Option<MusicVideoMetadata>> GetMetadataForMusicVideo(string filePath);
|
||||
Task<bool> RefreshSidecarMetadata(MediaItem mediaItem, string path);
|
||||
Task<bool> RefreshSidecarMetadata(Show televisionShow, string showFolder);
|
||||
Task<bool> RefreshFallbackMetadata(MediaItem mediaItem);
|
||||
Task<bool> RefreshSidecarMetadata(Movie movie, string nfoFileName);
|
||||
Task<bool> RefreshSidecarMetadata(Show televisionShow, string nfoFileName);
|
||||
Task<bool> RefreshSidecarMetadata(Episode episode, string nfoFileName);
|
||||
Task<bool> RefreshSidecarMetadata(MusicVideo musicVideo, string nfoFileName);
|
||||
Task<bool> RefreshFallbackMetadata(Movie movie);
|
||||
Task<bool> RefreshFallbackMetadata(Episode episode);
|
||||
Task<bool> RefreshFallbackMetadata(Show televisionShow, string showFolder);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using LanguageExt;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ErsatzTV.Core
|
||||
{
|
||||
[SuppressMessage("ReSharper", "VSTHRD003")]
|
||||
public static class LanguageExtensions
|
||||
{
|
||||
public static Either<BaseError, TR> ToEither<TR>(this Validation<BaseError, TR> validation) =>
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Xml.Serialization;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Metadata.Nfo;
|
||||
using LanguageExt;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static LanguageExt.Prelude;
|
||||
@@ -86,40 +87,35 @@ namespace ErsatzTV.Core.Metadata
|
||||
});
|
||||
}
|
||||
|
||||
public Task<bool> RefreshSidecarMetadata(MediaItem mediaItem, string path) =>
|
||||
mediaItem switch
|
||||
{
|
||||
Episode e => LoadMetadata(e, path)
|
||||
.Bind(
|
||||
maybeMetadata => maybeMetadata.Match(
|
||||
metadata => ApplyMetadataUpdate(e, metadata),
|
||||
() => Task.FromResult(false))),
|
||||
Movie m => LoadMetadata(m, path)
|
||||
.Bind(
|
||||
maybeMetadata => maybeMetadata.Match(
|
||||
metadata => ApplyMetadataUpdate(m, metadata),
|
||||
() => Task.FromResult(false))),
|
||||
MusicVideo mv => LoadMetadata(mv, path)
|
||||
.Bind(
|
||||
maybeMetadata => maybeMetadata.Match(
|
||||
metadata => ApplyMetadataUpdate(mv, metadata),
|
||||
() => Task.FromResult(false))),
|
||||
_ => Task.FromResult(false)
|
||||
};
|
||||
public Task<bool> RefreshSidecarMetadata(Movie movie, string nfoFileName) =>
|
||||
LoadMovieMetadata(movie, nfoFileName).Bind(
|
||||
maybeMetadata => maybeMetadata.Match(
|
||||
metadata => ApplyMetadataUpdate(movie, metadata),
|
||||
() => Task.FromResult(false)));
|
||||
|
||||
public Task<bool> RefreshSidecarMetadata(Show televisionShow, string showFolder) =>
|
||||
LoadMetadata(televisionShow, showFolder).Bind(
|
||||
public Task<bool> RefreshSidecarMetadata(Show televisionShow, string nfoFileName) =>
|
||||
LoadTelevisionShowMetadata(nfoFileName).Bind(
|
||||
maybeMetadata => maybeMetadata.Match(
|
||||
metadata => ApplyMetadataUpdate(televisionShow, metadata),
|
||||
() => Task.FromResult(false)));
|
||||
|
||||
public Task<bool> RefreshFallbackMetadata(MediaItem mediaItem) =>
|
||||
mediaItem switch
|
||||
{
|
||||
Episode e => ApplyMetadataUpdate(e, _fallbackMetadataProvider.GetFallbackMetadata(e)),
|
||||
Movie m => ApplyMetadataUpdate(m, _fallbackMetadataProvider.GetFallbackMetadata(m)),
|
||||
_ => Task.FromResult(false)
|
||||
};
|
||||
public Task<bool> RefreshSidecarMetadata(Episode episode, string nfoFileName) =>
|
||||
LoadEpisodeMetadata(episode, nfoFileName).Bind(
|
||||
maybeMetadata => maybeMetadata.Match(
|
||||
metadata => ApplyMetadataUpdate(episode, metadata),
|
||||
() => Task.FromResult(false)));
|
||||
|
||||
public Task<bool> RefreshSidecarMetadata(MusicVideo musicVideo, string nfoFileName) =>
|
||||
LoadMusicVideoMetadata(nfoFileName).Bind(
|
||||
maybeMetadata => maybeMetadata.Match(
|
||||
metadata => ApplyMetadataUpdate(musicVideo, metadata),
|
||||
() => Task.FromResult(false)));
|
||||
|
||||
public Task<bool> RefreshFallbackMetadata(Movie movie) =>
|
||||
ApplyMetadataUpdate(movie, _fallbackMetadataProvider.GetFallbackMetadata(movie));
|
||||
|
||||
public Task<bool> RefreshFallbackMetadata(Episode episode) =>
|
||||
ApplyMetadataUpdate(episode, _fallbackMetadataProvider.GetFallbackMetadata(episode));
|
||||
|
||||
public Task<bool> RefreshFallbackMetadata(Show televisionShow, string showFolder) =>
|
||||
ApplyMetadataUpdate(televisionShow, _fallbackMetadataProvider.GetFallbackMetadataForShow(showFolder));
|
||||
@@ -205,8 +201,6 @@ namespace ErsatzTV.Core.Metadata
|
||||
Optional(movie.MovieMetadata).Flatten().HeadOrNone().Match(
|
||||
async existing =>
|
||||
{
|
||||
var updated = false;
|
||||
|
||||
existing.Outline = metadata.Outline;
|
||||
existing.Plot = metadata.Plot;
|
||||
existing.Tagline = metadata.Tagline;
|
||||
@@ -226,67 +220,12 @@ namespace ErsatzTV.Core.Metadata
|
||||
? _fallbackMetadataProvider.GetSortTitle(metadata.Title)
|
||||
: metadata.SortTitle;
|
||||
|
||||
foreach (Genre genre in existing.Genres.Filter(g => metadata.Genres.All(g2 => g2.Name != g.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Genres.Remove(genre);
|
||||
if (await _metadataRepository.RemoveGenre(genre))
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Genre genre in metadata.Genres.Filter(g => existing.Genres.All(g2 => g2.Name != g.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Genres.Add(genre);
|
||||
if (await _movieRepository.AddGenre(existing, genre))
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Tag tag in existing.Tags.Filter(t => metadata.Tags.All(t2 => t2.Name != t.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Tags.Remove(tag);
|
||||
if (await _metadataRepository.RemoveTag(tag))
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Tag tag in metadata.Tags.Filter(t => existing.Tags.All(t2 => t2.Name != t.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Tags.Add(tag);
|
||||
if (await _movieRepository.AddTag(existing, tag))
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Studio studio in existing.Studios
|
||||
.Filter(s => metadata.Studios.All(s2 => s2.Name != s.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Studios.Remove(studio);
|
||||
if (await _metadataRepository.RemoveStudio(studio))
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Studio studio in metadata.Studios
|
||||
.Filter(s => existing.Studios.All(s2 => s2.Name != s.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Studios.Add(studio);
|
||||
if (await _movieRepository.AddStudio(existing, studio))
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
bool updated = await UpdateMetadataCollections(
|
||||
existing,
|
||||
metadata,
|
||||
_movieRepository.AddGenre,
|
||||
_movieRepository.AddTag,
|
||||
_movieRepository.AddStudio);
|
||||
|
||||
return await _metadataRepository.Update(existing) || updated;
|
||||
},
|
||||
@@ -305,8 +244,6 @@ namespace ErsatzTV.Core.Metadata
|
||||
Optional(show.ShowMetadata).Flatten().HeadOrNone().Match(
|
||||
async existing =>
|
||||
{
|
||||
var updated = false;
|
||||
|
||||
existing.Outline = metadata.Outline;
|
||||
existing.Plot = metadata.Plot;
|
||||
existing.Tagline = metadata.Tagline;
|
||||
@@ -326,67 +263,12 @@ namespace ErsatzTV.Core.Metadata
|
||||
? _fallbackMetadataProvider.GetSortTitle(metadata.Title)
|
||||
: metadata.SortTitle;
|
||||
|
||||
foreach (Genre genre in existing.Genres.Filter(g => metadata.Genres.All(g2 => g2.Name != g.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Genres.Remove(genre);
|
||||
if (await _metadataRepository.RemoveGenre(genre))
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Genre genre in metadata.Genres.Filter(g => existing.Genres.All(g2 => g2.Name != g.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Genres.Add(genre);
|
||||
if (await _televisionRepository.AddGenre(existing, genre))
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Tag tag in existing.Tags.Filter(t => metadata.Tags.All(t2 => t2.Name != t.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Tags.Remove(tag);
|
||||
if (await _metadataRepository.RemoveTag(tag))
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Tag tag in metadata.Tags.Filter(t => existing.Tags.All(t2 => t2.Name != t.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Tags.Add(tag);
|
||||
if (await _televisionRepository.AddTag(existing, tag))
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Studio studio in existing.Studios
|
||||
.Filter(s => metadata.Studios.All(s2 => s2.Name != s.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Studios.Remove(studio);
|
||||
if (await _metadataRepository.RemoveStudio(studio))
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Studio studio in metadata.Studios
|
||||
.Filter(s => existing.Studios.All(s2 => s2.Name != s.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Studios.Add(studio);
|
||||
if (await _televisionRepository.AddStudio(existing, studio))
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
bool updated = await UpdateMetadataCollections(
|
||||
existing,
|
||||
metadata,
|
||||
_televisionRepository.AddGenre,
|
||||
_televisionRepository.AddTag,
|
||||
_televisionRepository.AddStudio);
|
||||
|
||||
return await _metadataRepository.Update(existing) || updated;
|
||||
},
|
||||
@@ -405,8 +287,6 @@ namespace ErsatzTV.Core.Metadata
|
||||
Optional(musicVideo.MusicVideoMetadata).Flatten().HeadOrNone().Match(
|
||||
async existing =>
|
||||
{
|
||||
var updated = false;
|
||||
|
||||
existing.Artist = metadata.Artist;
|
||||
existing.Title = metadata.Title;
|
||||
existing.Year = metadata.Year;
|
||||
@@ -426,67 +306,12 @@ namespace ErsatzTV.Core.Metadata
|
||||
? _fallbackMetadataProvider.GetSortTitle(metadata.Title)
|
||||
: metadata.SortTitle;
|
||||
|
||||
foreach (Genre genre in existing.Genres.Filter(g => metadata.Genres.All(g2 => g2.Name != g.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Genres.Remove(genre);
|
||||
if (await _metadataRepository.RemoveGenre(genre))
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Genre genre in metadata.Genres.Filter(g => existing.Genres.All(g2 => g2.Name != g.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Genres.Add(genre);
|
||||
if (await _musicVideoRepository.AddGenre(existing, genre))
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Tag tag in existing.Tags.Filter(t => metadata.Tags.All(t2 => t2.Name != t.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Tags.Remove(tag);
|
||||
if (await _metadataRepository.RemoveTag(tag))
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Tag tag in metadata.Tags.Filter(t => existing.Tags.All(t2 => t2.Name != t.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Tags.Add(tag);
|
||||
if (await _musicVideoRepository.AddTag(existing, tag))
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Studio studio in existing.Studios
|
||||
.Filter(s => metadata.Studios.All(s2 => s2.Name != s.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Studios.Remove(studio);
|
||||
if (await _metadataRepository.RemoveStudio(studio))
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Studio studio in metadata.Studios
|
||||
.Filter(s => existing.Studios.All(s2 => s2.Name != s.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Studios.Add(studio);
|
||||
if (await _musicVideoRepository.AddStudio(existing, studio))
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
bool updated = await UpdateMetadataCollections(
|
||||
existing,
|
||||
metadata,
|
||||
_musicVideoRepository.AddGenre,
|
||||
_musicVideoRepository.AddTag,
|
||||
_musicVideoRepository.AddStudio);
|
||||
|
||||
return await _metadataRepository.Update(existing) || updated;
|
||||
},
|
||||
@@ -501,50 +326,6 @@ namespace ErsatzTV.Core.Metadata
|
||||
return await _metadataRepository.Add(metadata);
|
||||
});
|
||||
|
||||
private async Task<Option<MovieMetadata>> LoadMetadata(Movie mediaItem, string nfoFileName)
|
||||
{
|
||||
if (nfoFileName == null || !File.Exists(nfoFileName))
|
||||
{
|
||||
_logger.LogDebug("NFO file does not exist at {Path}", nfoFileName);
|
||||
return None;
|
||||
}
|
||||
|
||||
return await LoadMovieMetadata(mediaItem, nfoFileName);
|
||||
}
|
||||
|
||||
private async Task<Option<Tuple<EpisodeMetadata, int>>> LoadMetadata(Episode mediaItem, string nfoFileName)
|
||||
{
|
||||
if (nfoFileName == null || !File.Exists(nfoFileName))
|
||||
{
|
||||
_logger.LogDebug("NFO file does not exist at {Path}", nfoFileName);
|
||||
return None;
|
||||
}
|
||||
|
||||
return await LoadEpisodeMetadata(mediaItem, nfoFileName);
|
||||
}
|
||||
|
||||
private async Task<Option<ShowMetadata>> LoadMetadata(Show televisionShow, string nfoFileName)
|
||||
{
|
||||
if (nfoFileName == null || !File.Exists(nfoFileName))
|
||||
{
|
||||
_logger.LogDebug("NFO file does not exist at {Path}", nfoFileName);
|
||||
return None;
|
||||
}
|
||||
|
||||
return await LoadTelevisionShowMetadata(nfoFileName);
|
||||
}
|
||||
|
||||
private async Task<Option<MusicVideoMetadata>> LoadMetadata(MusicVideo musicVideo, string nfoFileName)
|
||||
{
|
||||
if (nfoFileName == null || !File.Exists(nfoFileName))
|
||||
{
|
||||
_logger.LogDebug("NFO file does not exist at {Path}", nfoFileName);
|
||||
return None;
|
||||
}
|
||||
|
||||
return await LoadMusicVideoMetadata(nfoFileName);
|
||||
}
|
||||
|
||||
private async Task<Option<ShowMetadata>> LoadTelevisionShowMetadata(string nfoFileName)
|
||||
{
|
||||
try
|
||||
@@ -605,7 +386,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Option<MovieMetadata>> LoadMovieMetadata(Movie mediaItem, string nfoFileName)
|
||||
private async Task<Option<MovieMetadata>> LoadMovieMetadata(Movie movie, string nfoFileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -632,7 +413,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogInformation(ex, "Failed to read Movie nfo metadata from {Path}", nfoFileName);
|
||||
return _fallbackMetadataProvider.GetFallbackMetadata(mediaItem);
|
||||
return _fallbackMetadataProvider.GetFallbackMetadata(movie);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -668,125 +449,79 @@ namespace ErsatzTV.Core.Metadata
|
||||
return DateTime.TryParse(aired, out DateTime parsed) ? parsed : fallback;
|
||||
}
|
||||
|
||||
[XmlRoot("movie")]
|
||||
public class MovieNfo
|
||||
private async Task<bool> UpdateMetadataCollections<T>(
|
||||
T existing,
|
||||
T incoming,
|
||||
Func<T, Genre, Task<bool>> addGenre,
|
||||
Func<T, Tag, Task<bool>> addTag,
|
||||
Func<T, Studio, Task<bool>> addStudio)
|
||||
where T : Domain.Metadata
|
||||
{
|
||||
[XmlElement("title")]
|
||||
public string Title { get; set; }
|
||||
var updated = false;
|
||||
|
||||
[XmlElement("outline")]
|
||||
public string Outline { get; set; }
|
||||
foreach (Genre genre in existing.Genres.Filter(g => incoming.Genres.All(g2 => g2.Name != g.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Genres.Remove(genre);
|
||||
if (await _metadataRepository.RemoveGenre(genre))
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
[XmlElement("year")]
|
||||
public int Year { get; set; }
|
||||
foreach (Genre genre in incoming.Genres.Filter(g => existing.Genres.All(g2 => g2.Name != g.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Genres.Add(genre);
|
||||
if (await addGenre(existing, genre))
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
[XmlElement("mpaa")]
|
||||
public string ContentRating { get; set; }
|
||||
foreach (Tag tag in existing.Tags.Filter(t => incoming.Tags.All(t2 => t2.Name != t.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Tags.Remove(tag);
|
||||
if (await _metadataRepository.RemoveTag(tag))
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
[XmlElement("premiered")]
|
||||
public DateTime Premiered { get; set; }
|
||||
foreach (Tag tag in incoming.Tags.Filter(t => existing.Tags.All(t2 => t2.Name != t.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Tags.Add(tag);
|
||||
if (await addTag(existing, tag))
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
[XmlElement("plot")]
|
||||
public string Plot { get; set; }
|
||||
foreach (Studio studio in existing.Studios
|
||||
.Filter(s => incoming.Studios.All(s2 => s2.Name != s.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Studios.Remove(studio);
|
||||
if (await _metadataRepository.RemoveStudio(studio))
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
[XmlElement("tagline")]
|
||||
public string Tagline { get; set; }
|
||||
foreach (Studio studio in incoming.Studios
|
||||
.Filter(s => existing.Studios.All(s2 => s2.Name != s.Name))
|
||||
.ToList())
|
||||
{
|
||||
existing.Studios.Add(studio);
|
||||
if (await addStudio(existing, studio))
|
||||
{
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
[XmlElement("genre")]
|
||||
public List<string> Genres { get; set; }
|
||||
|
||||
[XmlElement("tag")]
|
||||
public List<string> Tags { get; set; }
|
||||
|
||||
[XmlElement("studio")]
|
||||
public List<string> Studios { get; set; }
|
||||
}
|
||||
|
||||
[XmlRoot("tvshow")]
|
||||
public class TvShowNfo
|
||||
{
|
||||
[XmlElement("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[XmlElement("year")]
|
||||
public int Year { get; set; }
|
||||
|
||||
[XmlElement("plot")]
|
||||
public string Plot { get; set; }
|
||||
|
||||
[XmlElement("outline")]
|
||||
public string Outline { get; set; }
|
||||
|
||||
[XmlElement("tagline")]
|
||||
public string Tagline { get; set; }
|
||||
|
||||
[XmlElement("premiered")]
|
||||
public string Premiered { get; set; }
|
||||
|
||||
[XmlElement("genre")]
|
||||
public List<string> Genres { get; set; }
|
||||
|
||||
[XmlElement("tag")]
|
||||
public List<string> Tags { get; set; }
|
||||
|
||||
[XmlElement("studio")]
|
||||
public List<string> Studios { get; set; }
|
||||
}
|
||||
|
||||
[XmlRoot("episodedetails")]
|
||||
public class TvShowEpisodeNfo
|
||||
{
|
||||
[XmlElement("showtitle")]
|
||||
public string ShowTitle { get; set; }
|
||||
|
||||
[XmlElement("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[XmlElement("episode")]
|
||||
public int Episode { get; set; }
|
||||
|
||||
[XmlElement("season")]
|
||||
public int Season { get; set; }
|
||||
|
||||
[XmlElement("mpaa")]
|
||||
public string ContentRating { get; set; }
|
||||
|
||||
[XmlElement("aired")]
|
||||
public string Aired { get; set; }
|
||||
|
||||
[XmlElement("plot")]
|
||||
public string Plot { get; set; }
|
||||
}
|
||||
|
||||
[XmlRoot("musicvideo")]
|
||||
public class MusicVideoNfo
|
||||
{
|
||||
[XmlElement("artist")]
|
||||
public string Artist { get; set; }
|
||||
|
||||
[XmlElement("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[XmlElement("album")]
|
||||
public string Album { get; set; }
|
||||
|
||||
[XmlElement("plot")]
|
||||
public string Plot { get; set; }
|
||||
|
||||
[XmlElement("premiered")]
|
||||
public string Premiered { get; set; }
|
||||
|
||||
[XmlElement("year")]
|
||||
public int Year { get; set; }
|
||||
|
||||
[XmlElement("genre")]
|
||||
public List<string> Genres { get; set; }
|
||||
|
||||
[XmlElement("tag")]
|
||||
public List<string> Tags { get; set; }
|
||||
|
||||
[XmlElement("studio")]
|
||||
public List<string> Studios { get; set; }
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace ErsatzTV.Core.Metadata.Nfo
|
||||
{
|
||||
[XmlRoot("movie")]
|
||||
public class MovieNfo
|
||||
{
|
||||
[XmlElement("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[XmlElement("outline")]
|
||||
public string Outline { get; set; }
|
||||
|
||||
[XmlElement("year")]
|
||||
public int Year { get; set; }
|
||||
|
||||
[XmlElement("mpaa")]
|
||||
public string ContentRating { get; set; }
|
||||
|
||||
[XmlElement("premiered")]
|
||||
public DateTime Premiered { get; set; }
|
||||
|
||||
[XmlElement("plot")]
|
||||
public string Plot { get; set; }
|
||||
|
||||
[XmlElement("tagline")]
|
||||
public string Tagline { get; set; }
|
||||
|
||||
[XmlElement("genre")]
|
||||
public List<string> Genres { get; set; }
|
||||
|
||||
[XmlElement("tag")]
|
||||
public List<string> Tags { get; set; }
|
||||
|
||||
[XmlElement("studio")]
|
||||
public List<string> Studios { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace ErsatzTV.Core.Metadata.Nfo
|
||||
{
|
||||
[XmlRoot("musicvideo")]
|
||||
public class MusicVideoNfo
|
||||
{
|
||||
[XmlElement("artist")]
|
||||
public string Artist { get; set; }
|
||||
|
||||
[XmlElement("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[XmlElement("album")]
|
||||
public string Album { get; set; }
|
||||
|
||||
[XmlElement("plot")]
|
||||
public string Plot { get; set; }
|
||||
|
||||
[XmlElement("premiered")]
|
||||
public string Premiered { get; set; }
|
||||
|
||||
[XmlElement("year")]
|
||||
public int Year { get; set; }
|
||||
|
||||
[XmlElement("genre")]
|
||||
public List<string> Genres { get; set; }
|
||||
|
||||
[XmlElement("tag")]
|
||||
public List<string> Tags { get; set; }
|
||||
|
||||
[XmlElement("studio")]
|
||||
public List<string> Studios { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace ErsatzTV.Core.Metadata.Nfo
|
||||
{
|
||||
[XmlRoot("episodedetails")]
|
||||
public class TvShowEpisodeNfo
|
||||
{
|
||||
[XmlElement("showtitle")]
|
||||
public string ShowTitle { get; set; }
|
||||
|
||||
[XmlElement("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[XmlElement("episode")]
|
||||
public int Episode { get; set; }
|
||||
|
||||
[XmlElement("season")]
|
||||
public int Season { get; set; }
|
||||
|
||||
[XmlElement("mpaa")]
|
||||
public string ContentRating { get; set; }
|
||||
|
||||
[XmlElement("aired")]
|
||||
public string Aired { get; set; }
|
||||
|
||||
[XmlElement("plot")]
|
||||
public string Plot { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace ErsatzTV.Core.Metadata.Nfo
|
||||
{
|
||||
[XmlRoot("tvshow")]
|
||||
public class TvShowNfo
|
||||
{
|
||||
[XmlElement("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
[XmlElement("year")]
|
||||
public int Year { get; set; }
|
||||
|
||||
[XmlElement("plot")]
|
||||
public string Plot { get; set; }
|
||||
|
||||
[XmlElement("outline")]
|
||||
public string Outline { get; set; }
|
||||
|
||||
[XmlElement("tagline")]
|
||||
public string Tagline { get; set; }
|
||||
|
||||
[XmlElement("premiered")]
|
||||
public string Premiered { get; set; }
|
||||
|
||||
[XmlElement("genre")]
|
||||
public List<string> Genres { get; set; }
|
||||
|
||||
[XmlElement("tag")]
|
||||
public List<string> Tags { get; set; }
|
||||
|
||||
[XmlElement("studio")]
|
||||
public List<string> Studios { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@ namespace ErsatzTV.Core.Scheduling
|
||||
case ProgramScheduleItemCollectionType.Collection:
|
||||
Option<List<MediaItem>> maybeItems =
|
||||
await _mediaCollectionRepository.GetItems(collectionKey.CollectionId ?? 0);
|
||||
return Tuple(collectionKey, maybeItems.IfNone(new List<MediaItem>()));
|
||||
return Tuple(collectionKey, await maybeItems.IfNoneAsync(new List<MediaItem>()));
|
||||
case ProgramScheduleItemCollectionType.TelevisionShow:
|
||||
List<Episode> showItems =
|
||||
await _televisionRepository.GetShowItems(collectionKey.MediaItemId ?? 0);
|
||||
@@ -167,7 +167,7 @@ namespace ErsatzTV.Core.Scheduling
|
||||
durationFinish.IsSome);
|
||||
|
||||
IMediaCollectionEnumerator enumerator = collectionEnumerators[CollectionKeyForItem(scheduleItem)];
|
||||
enumerator.Current.IfSome(
|
||||
await enumerator.Current.IfSomeAsync(
|
||||
mediaItem =>
|
||||
{
|
||||
_logger.LogDebug(
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using LanguageExt;
|
||||
|
||||
@@ -8,7 +10,7 @@ namespace ErsatzTV.Infrastructure.Data
|
||||
{
|
||||
public static class DbInitializer
|
||||
{
|
||||
public static Unit Initialize(TvContext context)
|
||||
public static async Task<Unit> Initialize(TvContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
if (context.Resolutions.Any())
|
||||
{
|
||||
@@ -22,28 +24,28 @@ namespace ErsatzTV.Infrastructure.Data
|
||||
new() { Id = 3, Name = "1920x1080", Width = 1920, Height = 1080 },
|
||||
new() { Id = 4, Name = "3840x2160", Width = 3840, Height = 2160 }
|
||||
};
|
||||
context.Resolutions.AddRange(resolutions);
|
||||
context.SaveChanges();
|
||||
await context.Resolutions.AddRangeAsync(resolutions, cancellationToken);
|
||||
await context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
var resolutionConfig = new ConfigElement
|
||||
{
|
||||
Key = ConfigElementKey.FFmpegDefaultResolutionId.Key,
|
||||
Value = "3" // 1920x1080
|
||||
};
|
||||
context.ConfigElements.Add(resolutionConfig);
|
||||
context.SaveChanges();
|
||||
await context.ConfigElements.AddAsync(resolutionConfig, cancellationToken);
|
||||
await context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
var defaultProfile = FFmpegProfile.New("1920x1080 x264 ac3", resolutions[2]);
|
||||
context.FFmpegProfiles.Add(defaultProfile);
|
||||
context.SaveChanges();
|
||||
await context.FFmpegProfiles.AddAsync(defaultProfile, cancellationToken);
|
||||
await context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
var profileConfig = new ConfigElement
|
||||
{
|
||||
Key = ConfigElementKey.FFmpegDefaultProfileId.Key,
|
||||
Value = defaultProfile.Id.ToString()
|
||||
};
|
||||
context.ConfigElements.Add(profileConfig);
|
||||
context.SaveChanges();
|
||||
await context.ConfigElements.AddAsync(profileConfig, cancellationToken);
|
||||
await context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
var defaultChannel = new Channel(Guid.NewGuid())
|
||||
{
|
||||
@@ -52,8 +54,8 @@ namespace ErsatzTV.Infrastructure.Data
|
||||
FFmpegProfile = defaultProfile,
|
||||
StreamingMode = StreamingMode.TransportStream
|
||||
};
|
||||
context.Channels.Add(defaultChannel);
|
||||
context.SaveChanges();
|
||||
await context.Channels.AddAsync(defaultChannel, cancellationToken);
|
||||
await context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// TODO: create looping static image that mentions configuring via web
|
||||
return Unit.Default;
|
||||
|
||||
@@ -64,10 +64,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
.ThenInclude(mm => mm.Artwork)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task Update(Channel channel)
|
||||
public Task Update(Channel channel)
|
||||
{
|
||||
_dbContext.Channels.Update(channel);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
return _dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task Delete(int channelId)
|
||||
|
||||
@@ -31,16 +31,16 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
public Task<Option<T>> GetValue<T>(ConfigElementKey key) =>
|
||||
Get(key).MapT(ce => (T) Convert.ChangeType(ce.Value, typeof(T)));
|
||||
|
||||
public async Task Update(ConfigElement configElement)
|
||||
public Task Update(ConfigElement configElement)
|
||||
{
|
||||
_dbContext.ConfigElements.Update(configElement);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
return _dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task Delete(ConfigElement configElement)
|
||||
public Task Delete(ConfigElement configElement)
|
||||
{
|
||||
_dbContext.ConfigElements.Remove(configElement);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
return _dbContext.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,10 +32,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
.Include(p => p.Resolution)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task Update(FFmpegProfile ffmpegProfile)
|
||||
public Task Update(FFmpegProfile ffmpegProfile)
|
||||
{
|
||||
_dbContext.FFmpegProfiles.Update(ffmpegProfile);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
return _dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task Delete(int ffmpegProfileId)
|
||||
|
||||
@@ -124,10 +124,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task Update(Playout playout)
|
||||
public Task Update(Playout playout)
|
||||
{
|
||||
_dbContext.Playouts.Update(playout);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
return _dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task Delete(int playoutId)
|
||||
|
||||
@@ -3,9 +3,14 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
|
||||
<NoWarn>VSTHRD200</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AsyncFixer" Version="1.5.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Dapper" Version="2.0.78" />
|
||||
<PackageReference Include="Lucene.Net" Version="4.8.0-beta00013" />
|
||||
<PackageReference Include="Lucene.Net.Analysis.Common" Version="4.8.0-beta00013" />
|
||||
@@ -16,6 +21,10 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.4" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="16.9.60">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Refit" Version="6.0.38" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -8,7 +8,6 @@ using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Images;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using LanguageExt;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
@@ -19,15 +18,10 @@ namespace ErsatzTV.Infrastructure.Images
|
||||
{
|
||||
private static readonly SHA1CryptoServiceProvider Crypto;
|
||||
private readonly ILocalFileSystem _localFileSystem;
|
||||
private readonly ILogger<ImageCache> _logger;
|
||||
|
||||
static ImageCache() => Crypto = new SHA1CryptoServiceProvider();
|
||||
|
||||
public ImageCache(ILocalFileSystem localFileSystem, ILogger<ImageCache> logger)
|
||||
{
|
||||
_localFileSystem = localFileSystem;
|
||||
_logger = logger;
|
||||
}
|
||||
public ImageCache(ILocalFileSystem localFileSystem) => _localFileSystem = localFileSystem;
|
||||
|
||||
public async Task<Either<BaseError, byte[]>> ResizeImage(byte[] imageBuffer, int height)
|
||||
{
|
||||
|
||||
@@ -41,4 +41,5 @@
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=setsar/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=tvshow/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Vaapi/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=xmltv/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=yadif/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<NoWarn>VSTHRD200</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -11,6 +12,10 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Accelist.FluentValidation.Blazor" Version="4.0.0" />
|
||||
<PackageReference Include="AsyncFixer" Version="1.5.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="FluentValidation" Version="9.5.3" />
|
||||
<PackageReference Include="FluentValidation.AspNetCore" Version="9.5.3" />
|
||||
<PackageReference Include="LanguageExt.Core" Version="3.4.15" />
|
||||
@@ -20,6 +25,10 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="16.9.60">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MudBlazor" Version="5.0.6" />
|
||||
<PackageReference Include="Refit.HttpClientFactory" Version="6.0.24" />
|
||||
<PackageReference Include="Serilog" Version="2.10.0" />
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using LanguageExt;
|
||||
using LanguageExt.Common;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace ErsatzTV.Extensions
|
||||
{
|
||||
[SuppressMessage("ReSharper", "VSTHRD003")]
|
||||
public static class EitherToActionResult
|
||||
{
|
||||
public static Task<IActionResult> ToActionResult<TL, TR>(this Task<Either<TL, TR>> either) => either.Map(Match);
|
||||
@@ -16,13 +18,13 @@ namespace ErsatzTV.Extensions
|
||||
Left: l => new BadRequestObjectResult(l),
|
||||
Right: r => new OkObjectResult(r));
|
||||
|
||||
private static async Task<IActionResult> Match(Either<Error, Task> either) =>
|
||||
await either.MatchAsync<IActionResult>(
|
||||
private static Task<IActionResult> Match(Either<Error, Task> either) =>
|
||||
either.Match<Task<IActionResult>>(
|
||||
async t =>
|
||||
{
|
||||
await t;
|
||||
return new OkResult();
|
||||
},
|
||||
e => new BadRequestObjectResult(e));
|
||||
e => Task.FromResult((IActionResult) new BadRequestObjectResult(e)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Infrastructure.Data;
|
||||
using LanguageExt;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static LanguageExt.Prelude;
|
||||
|
||||
namespace ErsatzTV.Extensions
|
||||
{
|
||||
public static class HostExtensions
|
||||
{
|
||||
public static IHost SeedDatabase(this IHost host)
|
||||
{
|
||||
Unit _ = use(() => host.Services.CreateScope(), Seed);
|
||||
return host;
|
||||
}
|
||||
|
||||
public static IHost CleanCacheFolder(this IHost host)
|
||||
{
|
||||
Unit _ = use(() => host.Services.CreateScope(), CleanCache);
|
||||
return host;
|
||||
}
|
||||
|
||||
private static Unit Seed(IServiceScope scope) =>
|
||||
Try(() => scope.ServiceProvider)
|
||||
.Bind(services => Try(GetDbContext(services)))
|
||||
.Bind(ctx => Try(Migrate(ctx, scope.ServiceProvider)))
|
||||
.Bind(ctx => Try(InitializeDb(ctx)))
|
||||
.IfFail(
|
||||
ex =>
|
||||
{
|
||||
LogException(
|
||||
ex,
|
||||
"Error occured while migrating database; shutting down.",
|
||||
scope.ServiceProvider);
|
||||
Environment.Exit(13);
|
||||
return unit;
|
||||
});
|
||||
|
||||
private static Unit CleanCache(IServiceScope scope) =>
|
||||
Try(() => scope.ServiceProvider)
|
||||
.Bind(services => Try(GetDbContext(services)))
|
||||
.Bind(ctx => Try(CleanCache(ctx, scope.ServiceProvider)))
|
||||
.IfFail(ex => LogException(ex, "Error occured while cleaning cache", scope.ServiceProvider));
|
||||
|
||||
private static TvContext GetDbContext(IServiceProvider provider) =>
|
||||
provider.GetRequiredService<TvContext>();
|
||||
|
||||
private static TvContext Migrate(TvContext context, IServiceProvider provider)
|
||||
{
|
||||
ILogger<Program> logger = provider.GetRequiredService<ILogger<Program>>();
|
||||
logger.LogInformation("Applying database migrations");
|
||||
context.Database.Migrate();
|
||||
logger.LogInformation("Done applying database migrations");
|
||||
return context;
|
||||
}
|
||||
|
||||
private static Unit InitializeDb(TvContext context) =>
|
||||
DbInitializer.Initialize(context);
|
||||
|
||||
private static Unit CleanCache(TvContext context, IServiceProvider provider)
|
||||
{
|
||||
if (Directory.Exists(FileSystemLayout.LegacyImageCacheFolder))
|
||||
{
|
||||
ILogger<Program> logger = provider.GetRequiredService<ILogger<Program>>();
|
||||
logger.LogInformation("Migrating channel logos from legacy image cache folder");
|
||||
|
||||
var logos = context.Channels
|
||||
.SelectMany(c => c.Artwork)
|
||||
.Where(a => a.ArtworkKind == ArtworkKind.Logo)
|
||||
.Map(a => a.Path)
|
||||
.ToList();
|
||||
|
||||
ILocalFileSystem localFileSystem = provider.GetRequiredService<ILocalFileSystem>();
|
||||
foreach (string logo in logos)
|
||||
{
|
||||
string legacyPath = Path.Combine(FileSystemLayout.LegacyImageCacheFolder, logo);
|
||||
if (File.Exists(legacyPath))
|
||||
{
|
||||
string subfolder = logo.Substring(0, 2);
|
||||
string newPath = Path.Combine(FileSystemLayout.LogoCacheFolder, subfolder, logo);
|
||||
localFileSystem.CopyFile(legacyPath, newPath);
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogInformation("Deleting legacy image cache folder");
|
||||
Directory.Delete(FileSystemLayout.LegacyImageCacheFolder, true);
|
||||
}
|
||||
|
||||
return Unit.Default;
|
||||
}
|
||||
|
||||
private static Unit LogException(Exception ex, string message, IServiceProvider provider)
|
||||
{
|
||||
provider.GetRequiredService<ILogger<Program>>().LogError(ex, message);
|
||||
return unit;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using LanguageExt;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace ErsatzTV.Extensions
|
||||
{
|
||||
[SuppressMessage("ReSharper", "VSTHRD003")]
|
||||
public static class ListToActionResult
|
||||
{
|
||||
public static Task<IActionResult> ToActionResult<T>(this Task<List<T>> list) =>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using LanguageExt;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace ErsatzTV.Extensions
|
||||
{
|
||||
[SuppressMessage("ReSharper", "VSTHRD003")]
|
||||
public static class OptionToActionResult
|
||||
{
|
||||
public static IActionResult ToActionResult<T>(this Option<T> option) =>
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core;
|
||||
using LanguageExt;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace ErsatzTV.Extensions
|
||||
{
|
||||
[SuppressMessage("ReSharper", "VSTHRD003")]
|
||||
public static class ValidationToActionResult
|
||||
{
|
||||
public static IActionResult ToActionResult<T>(this Validation<BaseError, T> validation) =>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Extensions;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
@@ -27,7 +28,17 @@ namespace ErsatzTV.Pages
|
||||
}
|
||||
}
|
||||
|
||||
private async void TryFragmentNavigation(object sender, LocationChangedEventArgs args) =>
|
||||
await NavManager.NavigateToFragmentAsync(JsRuntime);
|
||||
[SuppressMessage("ReSharper", "VSTHRD100")]
|
||||
private async void TryFragmentNavigation(object sender, LocationChangedEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
await NavManager.NavigateToFragmentAsync(JsRuntime);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-8
@@ -2,7 +2,6 @@ using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Extensions;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
@@ -12,7 +11,7 @@ namespace ErsatzTV
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static IConfiguration Configuration { get; } = new ConfigurationBuilder()
|
||||
private static IConfiguration Configuration { get; } = new ConfigurationBuilder()
|
||||
.SetBasePath(Directory.GetCurrentDirectory())
|
||||
.AddJsonFile("appsettings.json", false, true)
|
||||
.AddJsonFile(
|
||||
@@ -31,11 +30,7 @@ namespace ErsatzTV
|
||||
|
||||
try
|
||||
{
|
||||
await CreateHostBuilder(args)
|
||||
.Build()
|
||||
.SeedDatabase()
|
||||
.CleanCacheFolder()
|
||||
.RunAsync();
|
||||
await CreateHostBuilder(args).Build().RunAsync();
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -49,7 +44,7 @@ namespace ErsatzTV
|
||||
}
|
||||
}
|
||||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
private static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureWebHostDefaults(
|
||||
webBuilder => webBuilder.UseStartup<Startup>()
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Infrastructure.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ErsatzTV.Services.RunOnce
|
||||
{
|
||||
public class CacheCleanerService : IHostedService
|
||||
{
|
||||
private readonly ILogger<CacheCleanerService> _logger;
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
|
||||
public CacheCleanerService(
|
||||
IServiceScopeFactory serviceScopeFactory,
|
||||
ILogger<CacheCleanerService> logger)
|
||||
{
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
using IServiceScope scope = _serviceScopeFactory.CreateScope();
|
||||
await using TvContext dbContext = scope.ServiceProvider.GetRequiredService<TvContext>();
|
||||
|
||||
if (Directory.Exists(FileSystemLayout.LegacyImageCacheFolder))
|
||||
{
|
||||
_logger.LogInformation("Migrating channel logos from legacy image cache folder");
|
||||
|
||||
List<string> logos = await dbContext.Channels
|
||||
.SelectMany(c => c.Artwork)
|
||||
.Where(a => a.ArtworkKind == ArtworkKind.Logo)
|
||||
.Map(a => a.Path)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
ILocalFileSystem localFileSystem = scope.ServiceProvider.GetRequiredService<ILocalFileSystem>();
|
||||
foreach (string logo in logos)
|
||||
{
|
||||
string legacyPath = Path.Combine(FileSystemLayout.LegacyImageCacheFolder, logo);
|
||||
if (File.Exists(legacyPath))
|
||||
{
|
||||
string subfolder = logo.Substring(0, 2);
|
||||
string newPath = Path.Combine(FileSystemLayout.LogoCacheFolder, subfolder, logo);
|
||||
await localFileSystem.CopyFile(legacyPath, newPath);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("Deleting legacy image cache folder");
|
||||
Directory.Delete(FileSystemLayout.LegacyImageCacheFolder, true);
|
||||
}
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Infrastructure.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ErsatzTV.Services.RunOnce
|
||||
{
|
||||
public class DatabaseMigratorService : IHostedService
|
||||
{
|
||||
private readonly ILogger<DatabaseMigratorService> _logger;
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
|
||||
public DatabaseMigratorService(
|
||||
IServiceScopeFactory serviceScopeFactory,
|
||||
ILogger<DatabaseMigratorService> logger)
|
||||
{
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Applying database migrations");
|
||||
|
||||
using IServiceScope scope = _serviceScopeFactory.CreateScope();
|
||||
await using TvContext dbContext = scope.ServiceProvider.GetRequiredService<TvContext>();
|
||||
await dbContext.Database.MigrateAsync(cancellationToken);
|
||||
await DbInitializer.Initialize(dbContext, cancellationToken);
|
||||
|
||||
_logger.LogInformation("Done applying database migrations");
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -15,50 +15,55 @@ using ErsatzTV.Infrastructure.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ErsatzTV.Services
|
||||
{
|
||||
public class SchedulerService : IHostedService
|
||||
public class SchedulerService : BackgroundService
|
||||
{
|
||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||
private readonly IEntityLocker _entityLocker;
|
||||
private readonly ILogger<SchedulerService> _logger;
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
private Timer _timer;
|
||||
|
||||
public SchedulerService(
|
||||
IServiceScopeFactory serviceScopeFactory,
|
||||
ChannelWriter<IBackgroundServiceRequest> channel,
|
||||
IEntityLocker entityLocker)
|
||||
IEntityLocker entityLocker,
|
||||
ILogger<SchedulerService> logger)
|
||||
{
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
_channel = channel;
|
||||
_entityLocker = entityLocker;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_timer = new Timer(
|
||||
async _ => await DoWork(cancellationToken),
|
||||
null,
|
||||
TimeSpan.FromSeconds(0), // fire immediately
|
||||
TimeSpan.FromHours(1)); // repeat every hour
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
await DoWork(cancellationToken);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_timer?.Change(Timeout.Infinite, 0);
|
||||
|
||||
return Task.CompletedTask;
|
||||
await Task.Delay(TimeSpan.FromHours(1), cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DoWork(CancellationToken cancellationToken)
|
||||
{
|
||||
await RebuildSearchIndex(cancellationToken);
|
||||
await BuildPlayouts(cancellationToken);
|
||||
await ScanLocalMediaSources(cancellationToken);
|
||||
await ScanPlexMediaSources(cancellationToken);
|
||||
try
|
||||
{
|
||||
await RebuildSearchIndex(cancellationToken);
|
||||
await BuildPlayouts(cancellationToken);
|
||||
await ScanLocalMediaSources(cancellationToken);
|
||||
await ScanPlexMediaSources(cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Error during scheduler run");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task BuildPlayouts(CancellationToken cancellationToken)
|
||||
@@ -114,7 +119,7 @@ namespace ErsatzTV.Services
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RebuildSearchIndex(CancellationToken cancellationToken) =>
|
||||
await _channel.WriteAsync(new RebuildSearchIndex(), cancellationToken);
|
||||
private ValueTask RebuildSearchIndex(CancellationToken cancellationToken) =>
|
||||
_channel.WriteAsync(new RebuildSearchIndex(), cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -30,6 +30,7 @@ using ErsatzTV.Infrastructure.Runtime;
|
||||
using ErsatzTV.Infrastructure.Search;
|
||||
using ErsatzTV.Serialization;
|
||||
using ErsatzTV.Services;
|
||||
using ErsatzTV.Services.RunOnce;
|
||||
using FluentValidation.AspNetCore;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
@@ -90,7 +91,7 @@ namespace ErsatzTV
|
||||
|
||||
Log.Logger.Information(
|
||||
"ErsatzTV version {Version}",
|
||||
Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>()
|
||||
Assembly.GetEntryAssembly()?.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
|
||||
?.InformationalVersion ?? "unknown");
|
||||
|
||||
Log.Logger.Warning("This is pre-alpha software and is likely to be unstable");
|
||||
@@ -220,6 +221,8 @@ namespace ErsatzTV
|
||||
services.AddScoped<IFFmpegStreamSelector, FFmpegStreamSelector>();
|
||||
services.AddScoped<FFmpegProcessService>();
|
||||
|
||||
services.AddHostedService<DatabaseMigratorService>();
|
||||
services.AddHostedService<CacheCleanerService>();
|
||||
services.AddHostedService<PlexService>();
|
||||
services.AddHostedService<FFmpegLocatorService>();
|
||||
services.AddHostedService<WorkerService>();
|
||||
|
||||
Reference in New Issue
Block a user