Implement first playlist generation, still much to do.
This commit is contained in:
parent
1f022d7f88
commit
6208c9c070
6 changed files with 262 additions and 21 deletions
|
@ -28,10 +28,25 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
|
||||||
interface IComparable<T, E> where T : IComparable<T, E> {
|
interface IComparable<T, E> where T : IComparable<T, E> {
|
||||||
static abstract E operator ==(T left, T right);
|
static abstract E operator ==(T left, T right);
|
||||||
static abstract E operator !=(T left, T right);
|
static abstract E operator !=(T left, T right);
|
||||||
|
E Equals(T other);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class Expression : IFormattable {
|
public abstract class Expression: IFormattable, IComparable<Expression, bool> {
|
||||||
public abstract string ToString(string? format, IFormatProvider? provider);
|
public abstract string ToString(string? format, IFormatProvider? provider);
|
||||||
|
public abstract override int GetHashCode();
|
||||||
|
public abstract bool Equals(Expression other);
|
||||||
|
public override bool Equals(object? other) {
|
||||||
|
if (other is Expression other_e) {
|
||||||
|
return Equals(other_e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public static bool operator ==(Expression left, Expression right) {
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
public static bool operator !=(Expression left, Expression right) {
|
||||||
|
return !left.Equals(right);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public abstract class Atom : Expression {}
|
public abstract class Atom : Expression {}
|
||||||
public class Symbol : Atom {
|
public class Symbol : Atom {
|
||||||
|
@ -40,34 +55,67 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
|
||||||
_name = name;
|
_name = name;
|
||||||
}
|
}
|
||||||
public string name { get => _name; }
|
public string name { get => _name; }
|
||||||
|
public override int GetHashCode() {
|
||||||
|
int hash = 17;
|
||||||
|
hash *= 23;
|
||||||
|
hash += _name.GetHashCode();
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
public override bool Equals(Expression? other) {
|
||||||
|
if (other is Symbol other_s) {
|
||||||
|
return _name == other_s._name;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
public override string ToString(string? format, IFormatProvider? provider) {
|
public override string ToString(string? format, IFormatProvider? provider) {
|
||||||
return _name;
|
return _name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public class Boolean : Atom, IComparable<Boolean, Boolean> {
|
|
||||||
|
public class Boolean : Atom {
|
||||||
private readonly bool _value;
|
private readonly bool _value;
|
||||||
public Boolean(bool value) {
|
public Boolean(bool value) {
|
||||||
_value = value;
|
_value = value;
|
||||||
}
|
}
|
||||||
public bool value { get => _value; }
|
public bool value { get => _value; }
|
||||||
|
public override int GetHashCode() {
|
||||||
|
int hash = 17;
|
||||||
|
hash *= 23;
|
||||||
|
hash += _value.GetHashCode();
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
public override bool Equals(Expression other) {
|
||||||
|
if (other is Boolean other_b) {
|
||||||
|
return _value == other_b.value;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
public override string ToString(string? format, IFormatProvider? provider) {
|
public override string ToString(string? format, IFormatProvider? provider) {
|
||||||
return _value? "t" : "nil";
|
return _value? "t" : "nil";
|
||||||
}
|
}
|
||||||
public static Boolean operator ==(Boolean a, Boolean b) {
|
|
||||||
return new Boolean(a.value == b.value);
|
|
||||||
}
|
}
|
||||||
public static Boolean operator !=(Boolean a, Boolean b) {
|
|
||||||
return new Boolean(a.value != b.value);
|
public class Integer : Atom, IAddable<Integer>, ISubtractable<Integer>, IMultiplicatable<Integer>, IDivisible<Integer>, ISortable<Integer, Boolean> {
|
||||||
}
|
|
||||||
}
|
|
||||||
public class Integer : Atom, IAddable<Integer>, ISubtractable<Integer>, IMultiplicatable<Integer>, IDivisible<Integer>, ISortable<Integer, Boolean>, IComparable<Integer, Boolean> {
|
|
||||||
private readonly int _value;
|
private readonly int _value;
|
||||||
public Integer(int value) {
|
public Integer(int value) {
|
||||||
_value = value;
|
_value = value;
|
||||||
}
|
}
|
||||||
public int value { get => _value; }
|
public int value { get => _value; }
|
||||||
|
public override int GetHashCode() {
|
||||||
|
int hash = 17;
|
||||||
|
hash *= 23;
|
||||||
|
hash += _value.GetHashCode();
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
public override bool Equals(Expression other) {
|
||||||
|
if (other is Integer other_i) {
|
||||||
|
return _value == other_i._value;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
public override string ToString(string? format, IFormatProvider? provider) {
|
public override string ToString(string? format, IFormatProvider? provider) {
|
||||||
return _value.ToString("0", provider);
|
return _value.ToString();
|
||||||
|
//return _value.ToString("0", provider);
|
||||||
}
|
}
|
||||||
public static Integer operator +(Integer a, Integer b) {
|
public static Integer operator +(Integer a, Integer b) {
|
||||||
return new Integer(a.value + b.value);
|
return new Integer(a.value + b.value);
|
||||||
|
@ -103,12 +151,25 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
|
||||||
return new Boolean(a.value != b.value);
|
return new Boolean(a.value != b.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class String : Atom, IAddable<String> {
|
public class String : Atom, IAddable<String> {
|
||||||
private readonly string _value;
|
private readonly string _value;
|
||||||
public String(string value) {
|
public String(string value) {
|
||||||
_value = value;
|
_value = value;
|
||||||
}
|
}
|
||||||
public string value { get => _value; }
|
public string value { get => _value; }
|
||||||
|
public override int GetHashCode() {
|
||||||
|
int hash = 17;
|
||||||
|
hash *= 23;
|
||||||
|
hash += _value.GetHashCode();
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
public override bool Equals(Expression other) {
|
||||||
|
if (other is String other_s) {
|
||||||
|
return _value == other_s._value;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
public override string ToString(string? format, IFormatProvider? provider) {
|
public override string ToString(string? format, IFormatProvider? provider) {
|
||||||
return "\"" + _value + "\"";
|
return "\"" + _value + "\"";
|
||||||
}
|
}
|
||||||
|
@ -116,12 +177,25 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
|
||||||
return new String (a.value + b.value);
|
return new String (a.value + b.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Object : Atom {
|
public class Object : Atom {
|
||||||
private readonly object _value;
|
private readonly object _value;
|
||||||
public Object(object value) {
|
public Object(object value) {
|
||||||
_value = value;
|
_value = value;
|
||||||
}
|
}
|
||||||
public object value { get => _value; }
|
public object value { get => _value; }
|
||||||
|
public override int GetHashCode() {
|
||||||
|
int hash = 17;
|
||||||
|
hash *= 23;
|
||||||
|
hash += _value.GetHashCode();
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
public override bool Equals(Expression other) {
|
||||||
|
if (other is Object other_o) {
|
||||||
|
return _value == other_o._value;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
public override string ToString(string? format, IFormatProvider? provider) {
|
public override string ToString(string? format, IFormatProvider? provider) {
|
||||||
return _value.ToString();
|
return _value.ToString();
|
||||||
}
|
}
|
||||||
|
@ -145,13 +219,22 @@ namespace Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler {
|
||||||
_expressions = expressions;
|
_expressions = expressions;
|
||||||
}
|
}
|
||||||
public IList<Expression> expressions { get => _expressions; }
|
public IList<Expression> expressions { get => _expressions; }
|
||||||
public override string ToString(string? format, IFormatProvider? provider) {
|
public override int GetHashCode() {
|
||||||
string r = "(";
|
int hash = 17;
|
||||||
foreach (var e in _expressions) {
|
foreach (Expression i in _expressions) {
|
||||||
r += " ";
|
hash *= 23;
|
||||||
r += e.ToString("0", provider);
|
hash += i.GetHashCode();
|
||||||
}
|
}
|
||||||
return r + ")";
|
return hash;
|
||||||
|
}
|
||||||
|
public override bool Equals(Expression other) {
|
||||||
|
if (other is List other_l) {
|
||||||
|
return _expressions == other_l._expressions;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public override string ToString(string? format, IFormatProvider? provider) {
|
||||||
|
return "(" + string.Join(" ", _expressions.Select(x => x.ToString("0", provider))) + ")";
|
||||||
}
|
}
|
||||||
public static List operator +(List a, List b) {
|
public static List operator +(List a, List b) {
|
||||||
List<Expression> r = new List<Expression>();
|
List<Expression> r = new List<Expression>();
|
||||||
|
|
|
@ -22,20 +22,39 @@ using MediaBrowser.Model.Playlists;
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
using Jellyfin.Plugin.SmartPlaylist.Lisp;
|
||||||
|
using Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler;
|
||||||
|
using Lisp_Object = Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler.Object;
|
||||||
|
using Lisp_Boolean = Jellyfin.Plugin.SmartPlaylist.Lisp.Compiler.Boolean;
|
||||||
|
|
||||||
|
|
||||||
namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
|
namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
|
||||||
public class GeneratePlaylist : IScheduledTask {
|
public class GeneratePlaylist : IScheduledTask {
|
||||||
|
|
||||||
|
public static readonly BaseItemKind[] AvailableFilterItems = {
|
||||||
|
BaseItemKind.Audio
|
||||||
|
};
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
|
private readonly IPlaylistManager _playlistManager;
|
||||||
|
|
||||||
|
private readonly IStore _store;
|
||||||
|
|
||||||
public GeneratePlaylist(
|
public GeneratePlaylist(
|
||||||
ILogger<Plugin> logger,
|
ILogger<Plugin> logger,
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
IUserManager userManager
|
IUserManager userManager,
|
||||||
|
IPlaylistManager playlistManager,
|
||||||
|
IServerApplicationPaths serverApplicationPaths
|
||||||
) {
|
) {
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
|
_playlistManager = playlistManager;
|
||||||
|
|
||||||
|
_store = new Store(new FileSystem(serverApplicationPaths));
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Category => "Library";
|
public string Category => "Library";
|
||||||
|
@ -46,7 +65,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
|
||||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() {
|
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() {
|
||||||
return new[] {
|
return new[] {
|
||||||
new TaskTriggerInfo {
|
new TaskTriggerInfo {
|
||||||
IntervalTicks = TimeSpan.FromMinutes(1).Ticks,
|
IntervalTicks = TimeSpan.FromHours(24).Ticks,
|
||||||
Type = TaskTriggerInfo.TriggerInterval,
|
Type = TaskTriggerInfo.TriggerInterval,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -56,7 +75,7 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
|
||||||
foreach (var user in _userManager.Users) {
|
foreach (var user in _userManager.Users) {
|
||||||
_logger.LogInformation("User {0}", user);
|
_logger.LogInformation("User {0}", user);
|
||||||
var query = new InternalItemsQuery(user) {
|
var query = new InternalItemsQuery(user) {
|
||||||
IncludeItemTypes = new[] {BaseItemKind.Audio},
|
IncludeItemTypes = AvailableFilterItems,
|
||||||
Recursive = true,
|
Recursive = true,
|
||||||
};
|
};
|
||||||
foreach (BaseItem item in _libraryManager.GetItemsResult(query).Items) {
|
foreach (BaseItem item in _libraryManager.GetItemsResult(query).Items) {
|
||||||
|
@ -65,9 +84,66 @@ namespace Jellyfin.Plugin.SmartPlaylist.ScheduledTasks {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SmartPlaylistId CreateNewPlaylist(SmartPlaylistDto dto, User user) {
|
||||||
|
var req = new PlaylistCreationRequest {
|
||||||
|
Name = dto.Name,
|
||||||
|
UserId = user.Id
|
||||||
|
};
|
||||||
|
return Guid.Parse(_playlistManager.CreatePlaylist(req).Result.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<Guid> FilterPlaylistItems(IEnumerable<BaseItem> items, User user, SmartPlaylistDto smartPlaylist) {
|
||||||
|
List<Guid> results = new List<Guid>();
|
||||||
|
Expression expression = new Parser(StringTokenStream.generate(smartPlaylist.Program)).parse();
|
||||||
|
Executor executor = new Executor();
|
||||||
|
foreach (var i in items) {
|
||||||
|
executor.environment["item"] = new Lisp_Object(i);
|
||||||
|
var r = executor.eval(expression);
|
||||||
|
_logger.LogInformation("Item {0} evaluated to {1}", i, r.ToString());
|
||||||
|
if (r is Lisp_Boolean r_bool) {
|
||||||
|
if (r_bool.value) { results.Add(i.Id); }
|
||||||
|
} else {
|
||||||
|
_logger.LogInformation("Program did not return a boolean, returned {0}", r.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<BaseItem> GetAllUserMedia(User user) {
|
||||||
|
var req = new InternalItemsQuery(user) {
|
||||||
|
IncludeItemTypes = new[] {BaseItemKind.Audio},
|
||||||
|
Recursive = true,
|
||||||
|
};
|
||||||
|
return _libraryManager.GetItemsResult(req).Items;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) {
|
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken) {
|
||||||
_logger.LogInformation("This is a test");
|
_logger.LogInformation("Started regenerate Smart Playlists");
|
||||||
GetUsers();
|
foreach (SmartPlaylistDto dto in await _store.GetAllSmartPlaylistsAsync()) {
|
||||||
|
var user = _userManager.GetUserById(dto.User);
|
||||||
|
List<Playlist> playlists = _playlistManager.GetPlaylists(user.Id).Where(x => x.Id == dto.Id).ToList();
|
||||||
|
if ((dto.Id == null) || !playlists.Any()) {
|
||||||
|
_logger.LogInformation("Generating new smart playlist (dto.Id = {0}, playlists.Any() = {1})", dto.Id, playlists.Any());
|
||||||
|
_store.DeleteSmartPlaylist(dto.Id);
|
||||||
|
dto.Id = CreateNewPlaylist(dto, user);
|
||||||
|
await _store.SaveSmartPlaylistAsync(dto);
|
||||||
|
playlists = _playlistManager.GetPlaylists(user.Id).Where(x => x.Id == dto.Id).ToList();
|
||||||
|
}
|
||||||
|
var insertItems = FilterPlaylistItems(GetAllUserMedia(user), user, dto);
|
||||||
|
Playlist playlist = playlists.First();
|
||||||
|
await ClearPlaylist(dto, playlist, user);
|
||||||
|
await _playlistManager.AddItemToPlaylistAsync(playlist.Id, insertItems.ToArray(), user.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ClearPlaylist(SmartPlaylistDto smartPlaylist, Playlist playlist, User user) {
|
||||||
|
var req = new InternalItemsQuery(user)
|
||||||
|
{
|
||||||
|
IncludeItemTypes = AvailableFilterItems,
|
||||||
|
Recursive = true
|
||||||
|
};
|
||||||
|
var existingItems = playlist.GetChildren(user, false, req).Select(x => x.Id.ToString()).ToList();
|
||||||
|
await _playlistManager.RemoveItemFromPlaylistAsync(playlist.Id.ToString(), existingItems);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
11
Jellyfin.Plugin.SmartPlaylist/SmartPlaylistDto.cs
Normal file
11
Jellyfin.Plugin.SmartPlaylist/SmartPlaylistDto.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
namespace Jellyfin.Plugin.SmartPlaylist {
|
||||||
|
[Serializable]
|
||||||
|
public class SmartPlaylistDto {
|
||||||
|
public SmartPlaylistId Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string FileName { get; set; }
|
||||||
|
public UserId User { get; set; }
|
||||||
|
public string Program { get; set; }
|
||||||
|
public int MaxItems { get; set; }
|
||||||
|
}
|
||||||
|
}
|
28
Jellyfin.Plugin.SmartPlaylist/SmartPlaylistFileSystem.cs
Normal file
28
Jellyfin.Plugin.SmartPlaylist/SmartPlaylistFileSystem.cs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
using MediaBrowser.Controller;
|
||||||
|
|
||||||
|
namespace Jellyfin.Plugin.SmartPlaylist {
|
||||||
|
|
||||||
|
public interface ISmartPlaylistFileSystem {
|
||||||
|
public string StoragePath { get; }
|
||||||
|
public string GetSmartPlaylistFilePath(SmartPlaylistId smartPlaylistId);
|
||||||
|
public string FindSmartPlaylistFilePath(SmartPlaylistId smartPlaylistId);
|
||||||
|
public string[] FindAllSmartPlaylistFilePaths();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FileSystem : ISmartPlaylistFileSystem {
|
||||||
|
public FileSystem(IServerApplicationPaths serverApplicationPaths) {
|
||||||
|
StoragePath = Path.Combine(serverApplicationPaths.DataPath, "smartplaylists");
|
||||||
|
if (!Directory.Exists(StoragePath)) { Directory.CreateDirectory(StoragePath); }
|
||||||
|
}
|
||||||
|
public string StoragePath { get; }
|
||||||
|
public string GetSmartPlaylistFilePath(SmartPlaylistId smartPlaylistId) {
|
||||||
|
return Path.Combine(StoragePath, $"{smartPlaylistId}.json");
|
||||||
|
}
|
||||||
|
public string FindSmartPlaylistFilePath(SmartPlaylistId smartPlaylistId) {
|
||||||
|
return Directory.GetFiles(StoragePath, $"{smartPlaylistId}.json", SearchOption.AllDirectories).First();
|
||||||
|
}
|
||||||
|
public string[] FindAllSmartPlaylistFilePaths() {
|
||||||
|
return Directory.GetFiles(StoragePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
39
Jellyfin.Plugin.SmartPlaylist/Store.cs
Normal file
39
Jellyfin.Plugin.SmartPlaylist/Store.cs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace Jellyfin.Plugin.SmartPlaylist {
|
||||||
|
public interface IStore {
|
||||||
|
Task<SmartPlaylistDto?> GetSmartPlaylistAsync(SmartPlaylistId smartPlaylistId);
|
||||||
|
Task<SmartPlaylistDto[]> GetAllSmartPlaylistsAsync();
|
||||||
|
Task SaveSmartPlaylistAsync(SmartPlaylistDto smartPlaylist);
|
||||||
|
void DeleteSmartPlaylist(SmartPlaylistId smartPlaylistId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Store : IStore {
|
||||||
|
private readonly ISmartPlaylistFileSystem _fileSystem;
|
||||||
|
public Store(ISmartPlaylistFileSystem fileSystem) {
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
}
|
||||||
|
private async Task<SmartPlaylistDto?> LoadPlaylistAsync(string filename) {
|
||||||
|
await using var r = File.OpenRead(filename);
|
||||||
|
return await JsonSerializer.DeserializeAsync<SmartPlaylistDto>(r).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
public async Task<SmartPlaylistDto?> GetSmartPlaylistAsync(SmartPlaylistId smartPlaylistId) {
|
||||||
|
string filename = _fileSystem.FindSmartPlaylistFilePath(smartPlaylistId);
|
||||||
|
return await LoadPlaylistAsync(filename).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
public async Task<SmartPlaylistDto[]> GetAllSmartPlaylistsAsync() {
|
||||||
|
var t = _fileSystem.FindAllSmartPlaylistFilePaths().Select(LoadPlaylistAsync).ToArray();
|
||||||
|
await Task.WhenAll(t).ConfigureAwait(false);
|
||||||
|
return t.Where(x => x != null).Select(x => x.Result).ToArray();
|
||||||
|
}
|
||||||
|
public async Task SaveSmartPlaylistAsync(SmartPlaylistDto smartPlaylist) {
|
||||||
|
string filename = _fileSystem.GetSmartPlaylistFilePath(smartPlaylist.Id);
|
||||||
|
await using var w = File.Create(filename);
|
||||||
|
await JsonSerializer.SerializeAsync(w, smartPlaylist).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
public void DeleteSmartPlaylist(SmartPlaylistId smartPlaylistId) {
|
||||||
|
string filename = _fileSystem.FindSmartPlaylistFilePath(smartPlaylistId);
|
||||||
|
if (File.Exists(filename)) { File.Delete(filename); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
Jellyfin.Plugin.SmartPlaylist/Types.cs
Normal file
4
Jellyfin.Plugin.SmartPlaylist/Types.cs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
global using System;
|
||||||
|
|
||||||
|
global using UserId = System.Guid;
|
||||||
|
global using SmartPlaylistId = System.Guid;
|
Loading…
Reference in a new issue