feat: add smart collections to backend.
This commit is contained in:
parent
dcdee2403c
commit
f5448e8a51
8 changed files with 326 additions and 57 deletions
|
@ -1,5 +1,4 @@
|
||||||
using MediaBrowser.Model.Plugins;
|
using MediaBrowser.Model.Plugins;
|
||||||
using MediaBrowser.Controller;
|
|
||||||
|
|
||||||
namespace Jellyfin.Plugin.SmartPlaylist {
|
namespace Jellyfin.Plugin.SmartPlaylist {
|
||||||
public class PluginConfiguration : BasePluginConfiguration {
|
public class PluginConfiguration : BasePluginConfiguration {
|
||||||
|
@ -43,7 +42,7 @@ namespace Jellyfin.Plugin.SmartPlaylist {
|
||||||
(define find-parent (lambda (typename) (invoke-generic *item* "FindParent" nil (list typename))))
|
(define find-parent (lambda (typename) (invoke-generic *item* "FindParent" nil (list typename))))
|
||||||
(define find-artist (lambda nil (find-parent "MediaBrowser.Controller.Entities.Audio.MusicArtist, MediaBrowser.Controller"))))
|
(define find-artist (lambda nil (find-parent "MediaBrowser.Controller.Entities.Audio.MusicArtist, MediaBrowser.Controller"))))
|
||||||
""";
|
""";
|
||||||
store = new Store(new SmartPlaylistFileSystem(Plugin.Instance.ServerApplicationPaths));
|
store = new Store(new SmartFileSystem(Plugin.Instance.ServerApplicationPaths));
|
||||||
}
|
}
|
||||||
private Store store { get; set; }
|
private Store store { get; set; }
|
||||||
public string InitialProgram { get; set; }
|
public string InitialProgram { get; set; }
|
||||||
|
@ -62,6 +61,21 @@ namespace Jellyfin.Plugin.SmartPlaylist {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public SmartCollectionDto[] Collections {
|
||||||
|
get {
|
||||||
|
return store.GetAllSmartCollectionsAsync().GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
var existing = store.GetAllSmartCollectionsAsync().GetAwaiter().GetResult().Select(x => x.Id).ToList();
|
||||||
|
foreach (var p in value) {
|
||||||
|
existing.Remove(p.Id);
|
||||||
|
store.SaveSmartCollectionAsync(p).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
foreach (var p in existing) {
|
||||||
|
store.DeleteSmartCollectionById(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
public object[][] Users {
|
public object[][] Users {
|
||||||
get {
|
get {
|
||||||
return Plugin.Instance.UserManager.Users.Select(x => new object[]{x.Id, x.Username}).ToArray();
|
return Plugin.Instance.UserManager.Users.Select(x => new object[]{x.Id, x.Username}).ToArray();
|
||||||
|
|
84
Jellyfin.Plugin.SmartPlaylist/ScheduledTasks/Common.cs
Normal file
84
Jellyfin.Plugin.SmartPlaylist/ScheduledTasks/Common.cs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Jellyfin.Data.Entities;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
|
using MediaBrowser.Controller.Entities;
|
||||||
|
|
||||||
|
using Jellyfin.Plugin.SmartPlaylist.Lisp;
|
||||||
|
using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler;
|
||||||
|
|
||||||
|
namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
|
||||||
|
|
||||||
|
public class Common {
|
||||||
|
public static readonly BaseItemKind[] AvailableFilterItems = {
|
||||||
|
BaseItemKind.Audio,
|
||||||
|
BaseItemKind.MusicAlbum,
|
||||||
|
BaseItemKind.Playlist,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
public readonly ILogger _logger;
|
||||||
|
public Common(ILogger logger) {
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
public Executor SetupExecutor() {
|
||||||
|
var env = new DefaultEnvironment();
|
||||||
|
var executor = new Executor(env);
|
||||||
|
executor.builtins["logd"] = (x) => {
|
||||||
|
_logger.LogDebug(((Lisp.String)x.First()).Value(), x.Skip(1).ToArray());
|
||||||
|
return Lisp.Boolean.TRUE;
|
||||||
|
};
|
||||||
|
executor.builtins["logi"] = (x) => {
|
||||||
|
_logger.LogInformation(((Lisp.String)x.First()).Value(), x.Skip(1).ToArray());
|
||||||
|
return Lisp.Boolean.TRUE;
|
||||||
|
};
|
||||||
|
executor.builtins["logw"] = (x) => {
|
||||||
|
_logger.LogWarning(((Lisp.String)x.First()).Value(), x.Skip(1).ToArray());
|
||||||
|
return Lisp.Boolean.TRUE;
|
||||||
|
};
|
||||||
|
executor.builtins["loge"] = (x) => {
|
||||||
|
_logger.LogError(((Lisp.String)x.First()).Value(), x.Skip(1).ToArray());
|
||||||
|
return Lisp.Boolean.TRUE;
|
||||||
|
};
|
||||||
|
if (Plugin.Instance is not null) {
|
||||||
|
executor.eval(Plugin.Instance.Configuration.InitialProgram);
|
||||||
|
} else {
|
||||||
|
throw new ApplicationException("Plugin Instance is not yet initialized");
|
||||||
|
}
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
public IEnumerable<Guid> FilterCollectionItems(IEnumerable<BaseItem> items, User? user, string name, string program, string sortProgram) {
|
||||||
|
List<BaseItem> results = new List<BaseItem>();
|
||||||
|
Expression expression = new Parser(StringTokenStream.generate(program)).parse(); // parse here, so that we don't repeat the work for each item
|
||||||
|
Executor executor = SetupExecutor();
|
||||||
|
|
||||||
|
executor.environment.Set("*user*", Lisp.Object.FromBase(user));
|
||||||
|
foreach (var i in items) {
|
||||||
|
executor.environment.Set("*item*", Lisp.Object.FromBase(i));
|
||||||
|
var r = executor.eval(expression);
|
||||||
|
_logger.LogTrace("Item {0} evaluated to {1}", i, r.ToString());
|
||||||
|
if ((r is not Lisp.Boolean r_bool) || (r_bool.Value())) {
|
||||||
|
_logger.LogDebug("Added '{0}' to Smart Collection {1}", i, name);
|
||||||
|
results.Add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
executor = SetupExecutor();
|
||||||
|
executor.environment.Set("*user*", Lisp.Object.FromBase(user));
|
||||||
|
executor.environment.Set("*items*", Lisp.Object.FromBase(results));
|
||||||
|
results = new List<BaseItem>();
|
||||||
|
var sort_result = executor.eval(sortProgram);
|
||||||
|
if (sort_result is Cons sorted_items) {
|
||||||
|
foreach (var i in sorted_items.ToList()) {
|
||||||
|
if (i is Lisp.Object iObject && iObject.Value() is BaseItem iBaseItem) {
|
||||||
|
results.Add(iBaseItem);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw new ApplicationException($"Returned sorted list does contain unexpected items, got {i}");
|
||||||
|
}
|
||||||
|
} else if (sort_result == Lisp.Boolean.FALSE) {
|
||||||
|
} else {
|
||||||
|
throw new ApplicationException($"Did not return a list of items, returned {sort_result}");
|
||||||
|
}
|
||||||
|
return results.Select(x => x.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
using System.Globalization;
|
||||||
|
using MediaBrowser.Model.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using MediaBrowser.Controller;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Collections;
|
||||||
|
using MediaBrowser.Controller.Providers;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using Jellyfin.Data.Entities;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
|
using MediaBrowser.Controller.Entities;
|
||||||
|
using MediaBrowser.Controller.Entities.Movies;
|
||||||
|
using MediaBrowser.Model.Collections;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
|
||||||
|
public class GenerateCollection: Common, IScheduledTask {
|
||||||
|
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
private readonly IUserManager _userManager;
|
||||||
|
private readonly IProviderManager _providerManager;
|
||||||
|
private readonly IFileSystem _fileSystem;
|
||||||
|
private readonly ICollectionManager _collectionManager;
|
||||||
|
|
||||||
|
private readonly IStore _store;
|
||||||
|
|
||||||
|
public GenerateCollection(
|
||||||
|
ILogger<Plugin> logger,
|
||||||
|
ILibraryManager libraryManager,
|
||||||
|
IUserManager userManager,
|
||||||
|
IProviderManager providerManager,
|
||||||
|
IFileSystem fileSystem,
|
||||||
|
ICollectionManager collectionManager,
|
||||||
|
IServerApplicationPaths serverApplicationPaths
|
||||||
|
) : base(logger) {
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
_userManager = userManager;
|
||||||
|
_providerManager = providerManager;
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
_collectionManager = collectionManager;
|
||||||
|
|
||||||
|
_store = new Store(new SmartFileSystem(serverApplicationPaths));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Category => "Library";
|
||||||
|
public string Name => "(re)generate Smart Collections";
|
||||||
|
public string Description => "Generate or regenerate all Smart Collections";
|
||||||
|
public string Key => nameof(GenerateCollection);
|
||||||
|
|
||||||
|
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() {
|
||||||
|
return new[] {
|
||||||
|
new TaskTriggerInfo {
|
||||||
|
IntervalTicks = TimeSpan.FromHours(24).Ticks,
|
||||||
|
Type = TaskTriggerInfo.TriggerInterval,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<CollectionId> CreateNewCollection(string name) {
|
||||||
|
_logger.LogDebug("Creating collection '{0}'", name);
|
||||||
|
return (await _collectionManager.CreateCollectionAsync(
|
||||||
|
new CollectionCreationOptions {
|
||||||
|
Name = name,
|
||||||
|
}
|
||||||
|
)).Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<BaseItem> GetAllMedia() {
|
||||||
|
var req = new InternalItemsQuery() {
|
||||||
|
IncludeItemTypes = AvailableFilterItems,
|
||||||
|
Recursive = true,
|
||||||
|
};
|
||||||
|
return _libraryManager.GetItemsResult(req).Items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) {
|
||||||
|
_logger.LogInformation("Started regenerate Smart Collections");
|
||||||
|
_logger.LogDebug("Loaded Assemblies:");
|
||||||
|
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) {
|
||||||
|
_logger.LogDebug("- {0}", asm);
|
||||||
|
}
|
||||||
|
var i = 0;
|
||||||
|
var smartCollections = await _store.GetAllSmartCollectionsAsync();
|
||||||
|
foreach (SmartCollectionDto dto in smartCollections) {
|
||||||
|
if (!dto.Enabled) {
|
||||||
|
progress.Report(100 * ((double)i)/smartCollections.Count());
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (dto.CollectionId == Guid.Empty) {
|
||||||
|
dto.CollectionId = await CreateNewCollection(dto.Name);
|
||||||
|
_store.DeleteSmartCollection(dto); // delete in case the file was not the canonical one.
|
||||||
|
await _store.SaveSmartCollectionAsync(dto);
|
||||||
|
}
|
||||||
|
var insertItems = FilterCollectionItems(GetAllMedia(), null, dto.Name, dto.Program, dto.SortProgram).ToArray();
|
||||||
|
await ClearCollection(dto.CollectionId);
|
||||||
|
await _collectionManager.AddToCollectionAsync(dto.CollectionId, insertItems);
|
||||||
|
i += 1;
|
||||||
|
progress.Report(100 * ((double)i)/smartCollections.Count());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ClearCollection(CollectionId collectionId) {
|
||||||
|
// fuck if I know
|
||||||
|
if (_libraryManager.GetItemById(collectionId) is not BoxSet collection) {
|
||||||
|
throw new ArgumentException("");
|
||||||
|
}
|
||||||
|
var existingItems = collection.Children;
|
||||||
|
await _collectionManager.RemoveFromCollectionAsync(collectionId, existingItems.Select(x => x.Id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,15 +19,8 @@ using Lisp_Boolean = Jellyfin.Plugin.SmartPlaylist.Lisp.Boolean;
|
||||||
|
|
||||||
|
|
||||||
namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
|
namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
|
||||||
public class GeneratePlaylist : IScheduledTask {
|
public class GeneratePlaylist : Common, IScheduledTask {
|
||||||
|
|
||||||
public static readonly BaseItemKind[] AvailableFilterItems = {
|
|
||||||
BaseItemKind.Audio,
|
|
||||||
BaseItemKind.MusicAlbum,
|
|
||||||
BaseItemKind.Playlist,
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
private readonly IProviderManager _providerManager;
|
private readonly IProviderManager _providerManager;
|
||||||
|
@ -44,15 +37,14 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IPlaylistManager playlistManager,
|
IPlaylistManager playlistManager,
|
||||||
IServerApplicationPaths serverApplicationPaths
|
IServerApplicationPaths serverApplicationPaths
|
||||||
) {
|
) : base(logger) {
|
||||||
_logger = logger;
|
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_providerManager = providerManager;
|
_providerManager = providerManager;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_playlistManager = playlistManager;
|
_playlistManager = playlistManager;
|
||||||
|
|
||||||
_store = new Store(new SmartPlaylistFileSystem(serverApplicationPaths));
|
_store = new Store(new SmartFileSystem(serverApplicationPaths));
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Category => "Library";
|
public string Category => "Library";
|
||||||
|
@ -81,33 +73,6 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
|
||||||
return playlistGuid;
|
return playlistGuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Executor SetupExecutor() {
|
|
||||||
var env = new DefaultEnvironment();
|
|
||||||
var executor = new Executor(env);
|
|
||||||
executor.builtins["logd"] = (x) => {
|
|
||||||
_logger.LogDebug(((Lisp.String)x.First()).Value(), x.Skip(1).ToArray());
|
|
||||||
return Lisp_Boolean.TRUE;
|
|
||||||
};
|
|
||||||
executor.builtins["logi"] = (x) => {
|
|
||||||
_logger.LogInformation(((Lisp.String)x.First()).Value(), x.Skip(1).ToArray());
|
|
||||||
return Lisp_Boolean.TRUE;
|
|
||||||
};
|
|
||||||
executor.builtins["logw"] = (x) => {
|
|
||||||
_logger.LogWarning(((Lisp.String)x.First()).Value(), x.Skip(1).ToArray());
|
|
||||||
return Lisp_Boolean.TRUE;
|
|
||||||
};
|
|
||||||
executor.builtins["loge"] = (x) => {
|
|
||||||
_logger.LogError(((Lisp.String)x.First()).Value(), x.Skip(1).ToArray());
|
|
||||||
return Lisp_Boolean.TRUE;
|
|
||||||
};
|
|
||||||
if (Plugin.Instance is not null) {
|
|
||||||
executor.eval(Plugin.Instance.Configuration.InitialProgram);
|
|
||||||
} else {
|
|
||||||
throw new ApplicationException("Plugin Instance is not yet initialized");
|
|
||||||
}
|
|
||||||
return executor;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<Guid> FilterPlaylistItems(IEnumerable<BaseItem> items, User user, SmartPlaylistDto smartPlaylist) {
|
private IEnumerable<Guid> FilterPlaylistItems(IEnumerable<BaseItem> items, User user, SmartPlaylistDto smartPlaylist) {
|
||||||
List<BaseItem> results = new List<BaseItem>();
|
List<BaseItem> results = new List<BaseItem>();
|
||||||
Expression expression = new Parser(StringTokenStream.generate(smartPlaylist.Program)).parse(); // parse here, so that we don't repeat the work for each item
|
Expression expression = new Parser(StringTokenStream.generate(smartPlaylist.Program)).parse(); // parse here, so that we don't repeat the work for each item
|
||||||
|
@ -162,6 +127,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
|
||||||
foreach (SmartPlaylistDto dto in all_playlists) {
|
foreach (SmartPlaylistDto dto in all_playlists) {
|
||||||
if (!dto.Enabled) {
|
if (!dto.Enabled) {
|
||||||
i += 1;
|
i += 1;
|
||||||
|
progress.Report(100 * ((double)i)/all_playlists.Count());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var changedDto = false;
|
var changedDto = false;
|
||||||
|
|
|
@ -108,4 +108,15 @@ namespace Jellyfin.Plugin.SmartPlaylist {
|
||||||
info.AddValue("Enabled", Enabled);
|
info.AddValue("Enabled", Enabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class SmartCollectionDto {
|
||||||
|
public SmartCollectionId Id { get; set; }
|
||||||
|
public CollectionId CollectionId { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Program { get; set; }
|
||||||
|
public string SortProgram { get; set; }
|
||||||
|
public string? Filename { get; set; }
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -3,34 +3,61 @@ using MediaBrowser.Controller;
|
||||||
namespace Jellyfin.Plugin.SmartPlaylist {
|
namespace Jellyfin.Plugin.SmartPlaylist {
|
||||||
|
|
||||||
public interface ISmartPlaylistFileSystem {
|
public interface ISmartPlaylistFileSystem {
|
||||||
public string StoragePath { get; }
|
public string PlaylistStoragePath { get; }
|
||||||
public string GetSmartPlaylistFilePath(SmartPlaylistId smartPlaylistId);
|
public string CollectionStoragePath { get; }
|
||||||
public string FindSmartPlaylistFilePath(SmartPlaylistId smartPlaylistId);
|
|
||||||
|
public string GetSmartPlaylistFilePath(string id);
|
||||||
|
public string FindSmartPlaylistFilePath(string id);
|
||||||
public string[] FindAllSmartPlaylistFilePaths();
|
public string[] FindAllSmartPlaylistFilePaths();
|
||||||
|
|
||||||
|
public string GetSmartCollectionFilePath(string id);
|
||||||
|
public string FindSmartCollectionFilePath(string id);
|
||||||
|
public string[] FindAllSmartCollectionFilePaths();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SmartPlaylistFileSystem : ISmartPlaylistFileSystem {
|
public class SmartFileSystem : ISmartPlaylistFileSystem {
|
||||||
public SmartPlaylistFileSystem(IServerApplicationPaths serverApplicationPaths) {
|
public SmartFileSystem(IServerApplicationPaths serverApplicationPaths) {
|
||||||
StoragePath = Path.Combine(serverApplicationPaths.DataPath, "smartplaylists");
|
PlaylistStoragePath = Path.Combine(serverApplicationPaths.DataPath, "smartplaylists");
|
||||||
if (!Directory.Exists(StoragePath)) { Directory.CreateDirectory(StoragePath); }
|
CollectionStoragePath = Path.Combine(serverApplicationPaths.DataPath, "smartcollections");
|
||||||
|
if (!Directory.Exists(PlaylistStoragePath)) { Directory.CreateDirectory(PlaylistStoragePath); }
|
||||||
|
if (!Directory.Exists(CollectionStoragePath)) { Directory.CreateDirectory(CollectionStoragePath); }
|
||||||
}
|
}
|
||||||
public string StoragePath { get; }
|
public string PlaylistStoragePath { get; }
|
||||||
public string GetSmartPlaylistFilePath(SmartPlaylistId smartPlaylistId) {
|
public string CollectionStoragePath { get; }
|
||||||
return Path.Combine(StoragePath, $"{smartPlaylistId}.yaml");
|
public string GetSmartPlaylistFilePath(string id) {
|
||||||
|
return Path.Combine(PlaylistStoragePath, $"{id}.yaml");
|
||||||
}
|
}
|
||||||
public string FindSmartPlaylistFilePath(SmartPlaylistId smartPlaylistId) {
|
public string FindSmartPlaylistFilePath(string id) {
|
||||||
return Directory.GetFiles(StoragePath, $"{smartPlaylistId}.yaml", SearchOption.AllDirectories).Concat(
|
return Directory.GetFiles(PlaylistStoragePath, $"{id}.yaml", SearchOption.AllDirectories).Concat(
|
||||||
Directory.GetFiles(StoragePath, $"{smartPlaylistId}.yml", SearchOption.AllDirectories)
|
Directory.GetFiles(PlaylistStoragePath, $"{id}.yml", SearchOption.AllDirectories)
|
||||||
).Concat(
|
).Concat(
|
||||||
Directory.GetFiles(StoragePath, $"{smartPlaylistId}.json", SearchOption.AllDirectories)
|
Directory.GetFiles(PlaylistStoragePath, $"{id}.json", SearchOption.AllDirectories)
|
||||||
).First();
|
).First();
|
||||||
}
|
}
|
||||||
public string[] FindAllSmartPlaylistFilePaths() {
|
public string[] FindAllSmartPlaylistFilePaths() {
|
||||||
return Directory.GetFiles(StoragePath, "*.yaml", SearchOption.AllDirectories).Concat(
|
return Directory.GetFiles(PlaylistStoragePath, "*.yaml", SearchOption.AllDirectories).Concat(
|
||||||
Directory.GetFiles(StoragePath, "*.yml", SearchOption.AllDirectories)
|
Directory.GetFiles(PlaylistStoragePath, "*.yml", SearchOption.AllDirectories)
|
||||||
).Concat(
|
).Concat(
|
||||||
Directory.GetFiles(StoragePath, "*.json", SearchOption.AllDirectories)
|
Directory.GetFiles(PlaylistStoragePath, "*.json", SearchOption.AllDirectories)
|
||||||
).ToArray();
|
).ToArray();
|
||||||
}
|
}
|
||||||
|
public string GetSmartCollectionFilePath(string id) {
|
||||||
|
return Path.Combine(CollectionStoragePath, $"{id}.yaml");
|
||||||
|
}
|
||||||
|
public string FindSmartCollectionFilePath(string id) {
|
||||||
|
return Directory.GetFiles(CollectionStoragePath, $"{id}.yaml", SearchOption.AllDirectories).Concat(
|
||||||
|
Directory.GetFiles(CollectionStoragePath, $"{id}.yml", SearchOption.AllDirectories)
|
||||||
|
).Concat(
|
||||||
|
Directory.GetFiles(CollectionStoragePath, $"{id}.json", SearchOption.AllDirectories)
|
||||||
|
).First();
|
||||||
|
}
|
||||||
|
public string[] FindAllSmartCollectionFilePaths() {
|
||||||
|
return Directory.GetFiles(CollectionStoragePath, "*.yaml", SearchOption.AllDirectories).Concat(
|
||||||
|
Directory.GetFiles(CollectionStoragePath, "*.yml", SearchOption.AllDirectories)
|
||||||
|
).Concat(
|
||||||
|
Directory.GetFiles(CollectionStoragePath, "*.json", SearchOption.AllDirectories)
|
||||||
|
).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,12 @@ namespace Jellyfin.Plugin.SmartPlaylist {
|
||||||
Task SaveSmartPlaylistAsync(SmartPlaylistDto smartPlaylist);
|
Task SaveSmartPlaylistAsync(SmartPlaylistDto smartPlaylist);
|
||||||
void DeleteSmartPlaylistById(SmartPlaylistId smartPlaylistId);
|
void DeleteSmartPlaylistById(SmartPlaylistId smartPlaylistId);
|
||||||
void DeleteSmartPlaylist(SmartPlaylistDto smartPlaylist);
|
void DeleteSmartPlaylist(SmartPlaylistDto smartPlaylist);
|
||||||
|
|
||||||
|
Task<SmartCollectionDto> GetSmartCollectionAsync(SmartCollectionId smartCollectionId);
|
||||||
|
Task<SmartCollectionDto[]> GetAllSmartCollectionsAsync();
|
||||||
|
Task SaveSmartCollectionAsync(SmartCollectionDto smartCollection);
|
||||||
|
void DeleteSmartCollectionById(SmartCollectionId smartCollectionId);
|
||||||
|
void DeleteSmartCollection(SmartCollectionDto smartCollection);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Store : IStore {
|
public class Store : IStore {
|
||||||
|
@ -59,5 +65,51 @@ namespace Jellyfin.Plugin.SmartPlaylist {
|
||||||
if (File.Exists(smartPlaylist.Filename)) { File.Delete(smartPlaylist.Filename); }
|
if (File.Exists(smartPlaylist.Filename)) { File.Delete(smartPlaylist.Filename); }
|
||||||
DeleteSmartPlaylistById(smartPlaylist.Id);
|
DeleteSmartPlaylistById(smartPlaylist.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<SmartCollectionDto> LoadCollectionAsync(string filename) {
|
||||||
|
var r = await File.ReadAllTextAsync(filename);
|
||||||
|
if (r.Equals("")) {
|
||||||
|
r = "{}";
|
||||||
|
}
|
||||||
|
var dto = new DeserializerBuilder().Build().Deserialize<SmartCollectionDto>(r);
|
||||||
|
if (dto == null)
|
||||||
|
{
|
||||||
|
throw new ApplicationException("");
|
||||||
|
}
|
||||||
|
if (dto.Id != Path.GetFileNameWithoutExtension(filename)) {
|
||||||
|
dto.Id = Path.GetFileNameWithoutExtension(filename);
|
||||||
|
}
|
||||||
|
if (dto.Name != Path.GetFileNameWithoutExtension(filename)) {
|
||||||
|
dto.Name = Path.GetFileNameWithoutExtension(filename);
|
||||||
|
}
|
||||||
|
if (dto.Filename != filename) {
|
||||||
|
dto.Filename = filename;
|
||||||
|
}
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
public async Task<SmartCollectionDto> GetSmartCollectionAsync(SmartCollectionId smartCollectionId) {
|
||||||
|
string filename = _fileSystem.FindSmartCollectionFilePath(smartCollectionId);
|
||||||
|
return await LoadCollectionAsync(filename).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
public async Task<SmartCollectionDto[]> GetAllSmartCollectionsAsync() {
|
||||||
|
var t = _fileSystem.FindAllSmartCollectionFilePaths().Select(LoadCollectionAsync).ToArray();
|
||||||
|
await Task.WhenAll(t).ConfigureAwait(false);
|
||||||
|
return t.Where(x => x != null).Select(x => x.Result).ToArray();
|
||||||
|
}
|
||||||
|
public async Task SaveSmartCollectionAsync(SmartCollectionDto smartCollection) {
|
||||||
|
string filename = _fileSystem.GetSmartCollectionFilePath(smartCollection.Id);
|
||||||
|
var text = new SerializerBuilder().Build().Serialize(smartCollection);
|
||||||
|
await File.WriteAllTextAsync(filename, text);
|
||||||
|
}
|
||||||
|
public void DeleteSmartCollectionById(SmartCollectionId smartCollectionId) {
|
||||||
|
try {
|
||||||
|
string filename = _fileSystem.FindSmartCollectionFilePath(smartCollectionId);
|
||||||
|
if (File.Exists(filename)) { File.Delete(filename); }
|
||||||
|
} catch (System.InvalidOperationException) {}
|
||||||
|
}
|
||||||
|
public void DeleteSmartCollection(SmartCollectionDto smartCollection) {
|
||||||
|
if (File.Exists(smartCollection.Filename)) { File.Delete(smartCollection.Filename); }
|
||||||
|
DeleteSmartCollectionById(smartCollection.Id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,4 +2,6 @@ global using System;
|
||||||
|
|
||||||
global using UserId = System.Guid;
|
global using UserId = System.Guid;
|
||||||
global using PlaylistId = System.Guid;
|
global using PlaylistId = System.Guid;
|
||||||
|
global using CollectionId = System.Guid;
|
||||||
global using SmartPlaylistId = string;
|
global using SmartPlaylistId = string;
|
||||||
|
global using SmartCollectionId = string;
|
||||||
|
|
Loading…
Add table
Reference in a new issue