Clean stratup through IHostedService, debugging plugin dir, root folder shows up and basic search works
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
@@ -1,111 +0,0 @@
|
||||
using System;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Channels;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Plugin.StreamCinema;
|
||||
|
||||
/// <summary>
|
||||
/// Triggers start-up initialization for the entire Stream Cinema plugin.
|
||||
/// </summary>
|
||||
public sealed class Startup : IChannel
|
||||
{
|
||||
public Startup(ILibraryManager libraryManager, IServerConfigurationManager config, IFileSystem fileSystem, ILogger<Startup> logger)
|
||||
{
|
||||
// Make sure the Stream Cinema root folders are created
|
||||
CreateRoot<StreamCinemaMoviesFolder>("movies", "Movies", libraryManager, config, fileSystem);
|
||||
|
||||
/*
|
||||
pluginItems.Add(CreateMenuItem("movies", Resources.Movies, GetResourceUrl("movies.png")));
|
||||
pluginItems.Add(CreateMenuItem("shows", Resources.Shows, GetResourceUrl("tvshows.png")));
|
||||
pluginItems.Add(CreateMenuItem("tv", Resources.TvProgram, GetResourceUrl("tv-program.png")));
|
||||
pluginItems.Add(CreateMenuItem("anime", Resources.Anime, GetResourceUrl("anime.png")));
|
||||
pluginItems.Add(CreateMenuItem("concerts", Resources.Concerts, GetResourceUrl("music.png")));*/
|
||||
}
|
||||
|
||||
private static void CreateRoot<T>(string idPrefix, string localizedName, ILibraryManager libraryManager, IServerConfigurationManager config, IFileSystem fileSystem) where T : StreamCinemaRootFolder, new()
|
||||
{
|
||||
string internalMetadataPath = config.ApplicationPaths.InternalMetadataPath;
|
||||
|
||||
Guid rootFolderId = libraryManager.GetNewItemId(StreamCinemaRootFolder.GetIdToHash("", idPrefix), typeof(StreamCinemaRootFolder));
|
||||
string rootFolderPath = StreamCinemaRootFolder.GetInternalMetadataPath(internalMetadataPath, rootFolderId);
|
||||
Directory.CreateDirectory(rootFolderPath);
|
||||
StreamCinemaRootFolder? rootFolder = libraryManager.GetItemById(rootFolderId) as StreamCinemaRootFolder;
|
||||
bool isNew;
|
||||
bool forceUpdate = false;
|
||||
if (isNew = rootFolder == null)
|
||||
{
|
||||
rootFolder = new T
|
||||
{
|
||||
Name = localizedName,
|
||||
Id = rootFolderId,
|
||||
DateCreated = fileSystem.GetCreationTimeUtc(rootFolderPath),
|
||||
DateModified = fileSystem.GetLastWriteTimeUtc(rootFolderPath)
|
||||
};
|
||||
rootFolder.Initialize(libraryManager, idPrefix, internalMetadataPath);
|
||||
}
|
||||
|
||||
rootFolder.Path = rootFolderPath;
|
||||
rootFolder.ParentId = Guid.Empty;
|
||||
|
||||
if (isNew)
|
||||
{
|
||||
rootFolder.OnMetadataChanged();
|
||||
libraryManager.CreateItem(rootFolder, null);
|
||||
}
|
||||
|
||||
// We do not bother waiting for the task to finish
|
||||
rootFolder.RefreshMetadata(
|
||||
new MetadataRefreshOptions(new DirectoryService(fileSystem))
|
||||
{
|
||||
ForceSave = !isNew && forceUpdate
|
||||
},
|
||||
default).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public string Name => "Stream Cinema startup";
|
||||
|
||||
public string Description => "";
|
||||
|
||||
public string DataVersion => "1";
|
||||
|
||||
public string HomePageUrl => "";
|
||||
|
||||
public ChannelParentalRating ParentalRating => ChannelParentalRating.GeneralAudience;
|
||||
|
||||
public InternalChannelFeatures GetChannelFeatures()
|
||||
{
|
||||
return new InternalChannelFeatures
|
||||
{
|
||||
// Anything that does not crash Jellyfin
|
||||
ContentTypes = [ChannelMediaContentType.Movie],
|
||||
MediaTypes = [ChannelMediaType.Video]
|
||||
};
|
||||
}
|
||||
|
||||
public Task<DynamicImageResponse> GetChannelImage(ImageType type, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(new DynamicImageResponse { HasImage = false });
|
||||
}
|
||||
|
||||
public Task<ChannelItemResult> GetChannelItems(InternalChannelItemQuery query, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(new ChannelItemResult() { Items = new List<ChannelItemInfo>() });
|
||||
}
|
||||
|
||||
public IEnumerable<ImageType> GetSupportedChannelImages()
|
||||
{
|
||||
return new List<ImageType> { ImageType.Primary };
|
||||
}
|
||||
|
||||
public bool IsEnabledFor(string userId)
|
||||
{
|
||||
// Always hide us from user's view as there is no content here
|
||||
return false;
|
||||
}
|
||||
}
|
||||
93
StreamCinemaJellyfin/StreamCinemaHost.cs
Normal file
93
StreamCinemaJellyfin/StreamCinemaHost.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Plugin.StreamCinema;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IHostedService"/> responsible for Live TV recordings.
|
||||
/// </summary>
|
||||
public sealed class StreamCinemaHost : IHostedService
|
||||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ILogger<StreamCinemaHost> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a the Stream Cinema plugin.
|
||||
/// </summary>
|
||||
public StreamCinemaHost(ILibraryManager libraryManager, IServerConfigurationManager config, IFileSystem fileSystem, ILogger<StreamCinemaHost> logger)
|
||||
{
|
||||
this._libraryManager = libraryManager;
|
||||
this._config = config;
|
||||
this._fileSystem = fileSystem;
|
||||
this._logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// Make sure the Stream Cinema root folders are created
|
||||
CreateRoot<StreamCinemaMoviesFolder>("movies", "Movies", _libraryManager, _config, _fileSystem);
|
||||
|
||||
/*
|
||||
pluginItems.Add(CreateMenuItem("movies", Resources.Movies, GetResourceUrl("movies.png")));
|
||||
pluginItems.Add(CreateMenuItem("shows", Resources.Shows, GetResourceUrl("tvshows.png")));
|
||||
pluginItems.Add(CreateMenuItem("tv", Resources.TvProgram, GetResourceUrl("tv-program.png")));
|
||||
pluginItems.Add(CreateMenuItem("anime", Resources.Anime, GetResourceUrl("anime.png")));
|
||||
pluginItems.Add(CreateMenuItem("concerts", Resources.Concerts, GetResourceUrl("music.png")));*/
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
private static void CreateRoot<T>(string idPrefix, string localizedName, ILibraryManager libraryManager, IServerConfigurationManager config, IFileSystem fileSystem) where T : StreamCinemaRootFolder, new()
|
||||
{
|
||||
string internalMetadataPath = config.ApplicationPaths.InternalMetadataPath;
|
||||
|
||||
Guid rootFolderId = libraryManager.GetNewItemId(StreamCinemaRootFolder.GetIdToHash("", idPrefix), typeof(StreamCinemaRootFolder));
|
||||
string rootFolderPath = StreamCinemaRootFolder.GetInternalMetadataPath(internalMetadataPath, rootFolderId);
|
||||
Directory.CreateDirectory(rootFolderPath);
|
||||
StreamCinemaRootFolder? rootFolder = libraryManager.GetItemById(rootFolderId) as StreamCinemaRootFolder;
|
||||
bool isNew;
|
||||
bool forceUpdate = false;
|
||||
if (isNew = rootFolder == null)
|
||||
{
|
||||
rootFolder = new T
|
||||
{
|
||||
Name = localizedName,
|
||||
Id = rootFolderId,
|
||||
DateCreated = fileSystem.GetCreationTimeUtc(rootFolderPath),
|
||||
DateModified = fileSystem.GetLastWriteTimeUtc(rootFolderPath)
|
||||
};
|
||||
}
|
||||
|
||||
rootFolder.Initialize(libraryManager, idPrefix, internalMetadataPath);
|
||||
rootFolder.Id = rootFolderId; // seems to get lost on existing item
|
||||
rootFolder.Path = rootFolderPath;
|
||||
rootFolder.ParentId = Guid.Empty;
|
||||
rootFolder.IsRoot = true;
|
||||
|
||||
if (isNew)
|
||||
{
|
||||
rootFolder.OnMetadataChanged();
|
||||
libraryManager.CreateItem(rootFolder, null);
|
||||
}
|
||||
|
||||
// We do not bother waiting for the task to finish
|
||||
rootFolder.RefreshMetadata(
|
||||
new MetadataRefreshOptions(new DirectoryService(fileSystem))
|
||||
{
|
||||
ForceSave = !isNew && forceUpdate
|
||||
},
|
||||
default).ConfigureAwait(false);
|
||||
|
||||
libraryManager.RootFolder.AddVirtualChild(rootFolder);
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,18 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../StreamCinemaLib/StreamCinemaLib.csproj" />
|
||||
<PackageReference Include="Jellyfin.Controller" Version="10.10.3" />
|
||||
<!--ProjectReference Include="../../../jellyfin/MediaBrowser.Controller/MediaBrowser.Controller.csproj" /-->
|
||||
<PackageReference Include="Jellyfin.Model" Version="10.10.3" />
|
||||
<!--ProjectReference Include="../../../jellyfin/MediaBrowser.Model/MediaBrowser.Model.csproj" /-->
|
||||
<ProjectReference Include="..\StreamCinemaLib/StreamCinemaLib.csproj" />
|
||||
<!--PackageReference Include="Jellyfin.Controller" Version="10.10.3" /-->
|
||||
<ProjectReference Include="..\..\..\jellyfin/MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
|
||||
<!--PackageReference Include="Jellyfin.Model" Version="10.10.3" /-->
|
||||
<ProjectReference Include="..\..\..\jellyfin\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="'$(Configuration)' == 'Debug'">
|
||||
<ItemGroup>
|
||||
<DebugCopyFiles Include="$(TargetDir)\StreamCinema*.dll" />
|
||||
</ItemGroup>
|
||||
<MakeDir Directories="\home\code\.local\share\jellyfin\plugins\StreamCinema" />
|
||||
<Copy SourceFiles="@(DebugCopyFiles)" DestinationFolder="\home\code\.local\share\jellyfin\plugins\StreamCinema" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
||||
196
StreamCinemaJellyfin/StreamCinemaMetadataService.cs
Normal file
196
StreamCinemaJellyfin/StreamCinemaMetadataService.cs
Normal file
@@ -0,0 +1,196 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Plugin.StreamCinema;
|
||||
|
||||
/// <summary>
|
||||
/// Metadata service that recognizes the StreamCinemaFilterFolder folders.
|
||||
/// </summary>
|
||||
public class StreamCinemaMetadataService : IMetadataService
|
||||
{
|
||||
public StreamCinemaMetadataService(
|
||||
IServerConfigurationManager serverConfigurationManager,
|
||||
ILogger<StreamCinemaMetadataService> logger,
|
||||
IProviderManager providerManager,
|
||||
IFileSystem fileSystem,
|
||||
ILibraryManager libraryManager)
|
||||
{
|
||||
}
|
||||
|
||||
public int Order => 0;
|
||||
|
||||
public bool CanRefresh(BaseItem item)
|
||||
{
|
||||
return item is StreamCinemaFilterFolder;
|
||||
}
|
||||
|
||||
public bool CanRefreshPrimary(Type type)
|
||||
{
|
||||
return typeof(StreamCinemaFilterFolder).IsAssignableFrom(type);
|
||||
}
|
||||
|
||||
public Task<ItemUpdateType> RefreshMetadata(BaseItem item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
var itemOfType = (StreamCinemaFilterFolder)item;
|
||||
|
||||
var updateType = ItemUpdateType.None;
|
||||
|
||||
/*
|
||||
var libraryOptions = LibraryManager.GetLibraryOptions(item);
|
||||
|
||||
var requiresRefresh = libraryOptions.AutomaticRefreshIntervalDays > 0 && (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= libraryOptions.AutomaticRefreshIntervalDays;
|
||||
|
||||
if (!requiresRefresh && refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
|
||||
{
|
||||
// TODO: If this returns true, should we instead just change metadata refresh mode to Full?
|
||||
requiresRefresh = item.RequiresRefresh();
|
||||
|
||||
if (requiresRefresh)
|
||||
{
|
||||
Logger.LogDebug("Refreshing {Type} {Item} because item.RequiresRefresh() returned true", typeof(TItemType).Name, item.Path ?? item.Name);
|
||||
}
|
||||
}
|
||||
|
||||
if (refreshOptions.RemoveOldMetadata && refreshOptions.ReplaceAllImages)
|
||||
{
|
||||
if (ImageProvider.RemoveImages(item))
|
||||
{
|
||||
updateType |= ItemUpdateType.ImageUpdate;
|
||||
}
|
||||
}
|
||||
|
||||
var localImagesFailed = false;
|
||||
var allImageProviders = ProviderManager.GetImageProviders(item, refreshOptions).ToList();
|
||||
|
||||
// Only validate already registered images if we are replacing and saving locally
|
||||
if (item.IsSaveLocalMetadataEnabled() && refreshOptions.ReplaceAllImages)
|
||||
{
|
||||
item.ValidateImages();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Run full image validation and register new local images
|
||||
try
|
||||
{
|
||||
if (ImageProvider.ValidateImages(item, allImageProviders.OfType<ILocalImageProvider>(), refreshOptions))
|
||||
{
|
||||
updateType |= ItemUpdateType.ImageUpdate;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
localImagesFailed = true;
|
||||
Logger.LogError(ex, "Error validating images for {Item}", item.Path ?? item.Name ?? "Unknown name");
|
||||
}
|
||||
}
|
||||
|
||||
var metadataResult = new MetadataResult<StreamCinemaFilterFolder>
|
||||
{
|
||||
Item = itemOfType,
|
||||
//People = LibraryManager.GetPeople(item)
|
||||
};
|
||||
|
||||
bool hasRefreshedMetadata = true;
|
||||
bool hasRefreshedImages = true;
|
||||
var isFirstRefresh = item.DateLastRefreshed == default;
|
||||
|
||||
// Next run metadata providers
|
||||
if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
|
||||
{
|
||||
var providers = GetProviders(item, libraryOptions, refreshOptions, isFirstRefresh, requiresRefresh)
|
||||
.ToList();
|
||||
|
||||
if (providers.Count > 0 || isFirstRefresh || requiresRefresh)
|
||||
{
|
||||
if (item.BeforeMetadataRefresh(refreshOptions.ReplaceAllMetadata))
|
||||
{
|
||||
updateType |= ItemUpdateType.MetadataImport;
|
||||
}
|
||||
}
|
||||
|
||||
if (providers.Count > 0)
|
||||
{
|
||||
var id = itemOfType.GetLookupInfo();
|
||||
|
||||
if (refreshOptions.SearchResult is not null)
|
||||
{
|
||||
ApplySearchResult(id, refreshOptions.SearchResult);
|
||||
}
|
||||
|
||||
id.IsAutomated = refreshOptions.IsAutomated;
|
||||
|
||||
var hasMetadataSavers = ProviderManager.GetMetadataSavers(item, libraryOptions).Any();
|
||||
var result = await RefreshWithProviders(metadataResult, id, refreshOptions, providers, ImageProvider, hasMetadataSavers, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
updateType |= result.UpdateType;
|
||||
if (result.Failures > 0)
|
||||
{
|
||||
hasRefreshedMetadata = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Next run remote image providers, but only if local image providers didn't throw an exception
|
||||
if (!localImagesFailed && refreshOptions.ImageRefreshMode > MetadataRefreshMode.ValidationOnly)
|
||||
{
|
||||
var providers = GetNonLocalImageProviders(item, allImageProviders, refreshOptions).ToList();
|
||||
|
||||
if (providers.Count > 0)
|
||||
{
|
||||
var result = await ImageProvider.RefreshImages(itemOfType, libraryOptions, providers, refreshOptions, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
updateType |= result.UpdateType;
|
||||
if (result.Failures > 0)
|
||||
{
|
||||
hasRefreshedImages = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var beforeSaveResult = BeforeSave(itemOfType, isFirstRefresh || refreshOptions.ReplaceAllMetadata || refreshOptions.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || requiresRefresh || refreshOptions.ForceSave, updateType);
|
||||
updateType |= beforeSaveResult;
|
||||
|
||||
// Save if changes were made, or it's never been saved before
|
||||
if (refreshOptions.ForceSave || updateType > ItemUpdateType.None || isFirstRefresh || refreshOptions.ReplaceAllMetadata || requiresRefresh)
|
||||
{
|
||||
if (item.IsFileProtocol)
|
||||
{
|
||||
var file = TryGetFile(item.Path, refreshOptions.DirectoryService);
|
||||
if (file is not null)
|
||||
{
|
||||
item.DateModified = file.LastWriteTimeUtc;
|
||||
}
|
||||
}
|
||||
|
||||
// If any of these properties are set then make sure the updateType is not None, just to force everything to save
|
||||
if (refreshOptions.ForceSave || refreshOptions.ReplaceAllMetadata)
|
||||
{
|
||||
updateType |= ItemUpdateType.MetadataDownload;
|
||||
}
|
||||
|
||||
if (hasRefreshedMetadata && hasRefreshedImages)
|
||||
{
|
||||
item.DateLastRefreshed = DateTime.UtcNow;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.DateLastRefreshed = default;
|
||||
}
|
||||
|
||||
// Save to database
|
||||
await SaveItemAsync(metadataResult, updateType, cancellationToken).ConfigureAwait(false);
|
||||
}*/
|
||||
|
||||
itemOfType.AfterMetadataRefresh();
|
||||
|
||||
updateType |= ItemUpdateType.MetadataImport | ItemUpdateType.MetadataDownload;
|
||||
return Task.FromResult(updateType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ sealed class StreamCinemaMoviesFolder : StreamCinemaRootFolder
|
||||
|
||||
public override CollectionType? CollectionType => Data.Enums.CollectionType.movies;
|
||||
|
||||
public override BaseItemKind ClientType => BaseItemKind.Movie;
|
||||
|
||||
internal override string ImageName => "movies.png";
|
||||
|
||||
protected override IEnumerable<BaseItem> GetFilterItems(string path)
|
||||
|
||||
@@ -32,12 +32,19 @@ public abstract class StreamCinemaRootFolder : StreamCinemaFilterFolder, ICollec
|
||||
|
||||
public abstract CollectionType? CollectionType { get; }
|
||||
|
||||
public abstract BaseItemKind ClientType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the static filter folders either directly from the root or nested filter folder.
|
||||
/// </summary>
|
||||
/// <param name="path">Path to folder whose filter items to get (empty string for root).</param>
|
||||
protected abstract IEnumerable<BaseItem> GetFilterItems(string path);
|
||||
|
||||
public sealed override string GetClientTypeName()
|
||||
{
|
||||
return ClientType.ToString();
|
||||
}
|
||||
|
||||
protected sealed override QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query)
|
||||
{
|
||||
if (_libraryManager == null)
|
||||
@@ -65,13 +72,14 @@ public abstract class StreamCinemaRootFolder : StreamCinemaFilterFolder, ICollec
|
||||
{
|
||||
items.RemoveRange(0, offset);
|
||||
limit -= items.Count;
|
||||
offset = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
items.Clear();
|
||||
}
|
||||
offset -= staticCount;
|
||||
}
|
||||
}
|
||||
|
||||
// Filtered content items
|
||||
FilterResponse? filterRes = Metadata.SearchAsync(query.SearchTerm ?? "", offset: offset, limit: limit).GetAwaiter().GetResult();
|
||||
|
||||
@@ -15,7 +15,7 @@ public class StreamCinemaServiceRegistrator : IPluginServiceRegistrator
|
||||
/// <inheritdoc />
|
||||
public void RegisterServices(IServiceCollection serviceCollection, IServerApplicationHost applicationHost)
|
||||
{
|
||||
serviceCollection.AddSingleton<IChannel, Startup>();
|
||||
serviceCollection.AddSingleton<IImageProvider, StreamCinemaImageProvider>();
|
||||
serviceCollection.AddHostedService<StreamCinemaHost>();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user