Merge pull request #73 from jellyfin/master

Updating from upstream
pull/4186/head
BaronGreenback 4 years ago committed by GitHub
commit 4c6b60d69d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -198,3 +198,4 @@
- [tikuf](https://github.com/tikuf/)
- [Tim Hobbs](https://github.com/timhobbs)
- [SvenVandenbrande](https://github.com/SvenVandenbrande)
- [olsh](https://github.com/olsh)

@ -455,7 +455,7 @@ namespace Emby.Drawing
throw new ArgumentException("Path can't be empty.", nameof(path));
}
if (path.IsEmpty)
if (filename.IsEmpty)
{
throw new ArgumentException("Filename can't be empty.", nameof(filename));
}

@ -50,27 +50,14 @@ namespace Emby.Naming.AudioBook
{
if (int.TryParse(value.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue))
{
result.ChapterNumber = intValue;
result.PartNumber = intValue;
}
}
}
}
}
/*var matches = _iRegexProvider.GetRegex("\\d+", RegexOptions.IgnoreCase).Matches(fileName);
if (matches.Count > 0)
{
if (!result.ChapterNumber.HasValue)
{
result.ChapterNumber = int.Parse(matches[0].Groups[0].Value);
}
if (matches.Count > 1)
{
result.PartNumber = int.Parse(matches[matches.Count - 1].Groups[0].Value);
}
}*/
result.Success = result.PartNumber.HasValue || result.ChapterNumber.HasValue;
result.Success = result.ChapterNumber.HasValue || result.PartNumber.HasValue;
return result;
}

@ -55,8 +55,8 @@ namespace Emby.Naming.AudioBook
{
Path = path,
Container = container,
PartNumber = parsingResult.PartNumber,
ChapterNumber = parsingResult.ChapterNumber,
PartNumber = parsingResult.PartNumber,
IsDirectory = isDirectory
};
}

@ -4,6 +4,7 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
@ -37,6 +38,7 @@ using Emby.Server.Implementations.LiveTv;
using Emby.Server.Implementations.Localization;
using Emby.Server.Implementations.Net;
using Emby.Server.Implementations.Playlists;
using Emby.Server.Implementations.Plugins;
using Emby.Server.Implementations.QuickConnect;
using Emby.Server.Implementations.ScheduledTasks;
using Emby.Server.Implementations.Security;
@ -119,6 +121,7 @@ namespace Emby.Server.Implementations
private readonly IFileSystem _fileSystemManager;
private readonly INetworkManager _networkManager;
private readonly IXmlSerializer _xmlSerializer;
private readonly IJsonSerializer _jsonSerializer;
private readonly IStartupOptions _startupOptions;
private IMediaEncoder _mediaEncoder;
@ -255,6 +258,8 @@ namespace Emby.Server.Implementations
IServiceCollection serviceCollection)
{
_xmlSerializer = new MyXmlSerializer();
_jsonSerializer = new JsonSerializer();
ServiceCollection = serviceCollection;
_networkManager = networkManager;
@ -1021,6 +1026,108 @@ namespace Emby.Server.Implementations
protected abstract void RestartInternal();
/// <summary>
/// Comparison function used in <see cref="GetPlugins" />.
/// </summary>
/// <param name="a">Item to compare.</param>
/// <param name="b">Item to compare with.</param>
/// <returns>Boolean result of the operation.</returns>
private static int VersionCompare(
(Version PluginVersion, string Name, string Path) a,
(Version PluginVersion, string Name, string Path) b)
{
int compare = string.Compare(a.Name, b.Name, true, CultureInfo.InvariantCulture);
if (compare == 0)
{
return a.PluginVersion.CompareTo(b.PluginVersion);
}
return compare;
}
/// <summary>
/// Returns a list of plugins to install.
/// </summary>
/// <param name="path">Path to check.</param>
/// <param name="cleanup">True if an attempt should be made to delete old plugs.</param>
/// <returns>Enumerable list of dlls to load.</returns>
private IEnumerable<string> GetPlugins(string path, bool cleanup = true)
{
var dllList = new List<string>();
var versions = new List<(Version PluginVersion, string Name, string Path)>();
var directories = Directory.EnumerateDirectories(path, "*.*", SearchOption.TopDirectoryOnly);
string metafile;
foreach (var dir in directories)
{
try
{
metafile = Path.Combine(dir, "meta.json");
if (File.Exists(metafile))
{
var manifest = _jsonSerializer.DeserializeFromFile<PluginManifest>(metafile);
if (!Version.TryParse(manifest.TargetAbi, out var targetAbi))
{
targetAbi = new Version(0, 0, 0, 1);
}
if (!Version.TryParse(manifest.Version, out var version))
{
version = new Version(0, 0, 0, 1);
}
if (ApplicationVersion >= targetAbi)
{
// Only load Plugins if the plugin is built for this version or below.
versions.Add((version, manifest.Name, dir));
}
}
else
{
metafile = dir.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries)[^1];
// Add it under the path name and version 0.0.0.1.
versions.Add((new Version(0, 0, 0, 1), metafile, dir));
}
}
catch
{
continue;
}
}
string lastName = string.Empty;
versions.Sort(VersionCompare);
// Traverse backwards through the list.
// The first item will be the latest version.
for (int x = versions.Count - 1; x >= 0; x--)
{
if (!string.Equals(lastName, versions[x].Name, StringComparison.OrdinalIgnoreCase))
{
dllList.AddRange(Directory.EnumerateFiles(versions[x].Path, "*.dll", SearchOption.AllDirectories));
lastName = versions[x].Name;
continue;
}
if (!string.IsNullOrEmpty(lastName) && cleanup)
{
// Attempt a cleanup of old folders.
try
{
Logger.LogDebug("Deleting {Path}", versions[x].Path);
Directory.Delete(versions[x].Path, true);
}
catch (Exception e)
{
Logger.LogWarning(e, "Unable to delete {Path}", versions[x].Path);
}
}
}
return dllList;
}
/// <summary>
/// Gets the composable part assemblies.
/// </summary>
@ -1029,7 +1136,7 @@ namespace Emby.Server.Implementations
{
if (Directory.Exists(ApplicationPaths.PluginsPath))
{
foreach (var file in Directory.EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.AllDirectories))
foreach (var file in GetPlugins(ApplicationPaths.PluginsPath))
{
Assembly plugAss;
try

@ -0,0 +1,117 @@
{
"MessageApplicationUpdatedTo": "Serveri Jellyfin u përditesua në versionin {0}",
"Inherit": "Trashgimi",
"TaskDownloadMissingSubtitlesDescription": "Kërkon në internet për titra që mungojnë bazuar tek konfigurimi i metadata-ve.",
"TaskDownloadMissingSubtitles": "Shkarko titra që mungojnë",
"TaskRefreshChannelsDescription": "Rifreskon informacionin e kanaleve të internetit.",
"TaskRefreshChannels": "Rifresko Kanalet",
"TaskCleanTranscodeDescription": "Fshin skedarët e transkodimit që janë më të vjetër se një ditë.",
"TaskCleanTranscode": "Fshi dosjen e transkodimit",
"TaskUpdatePluginsDescription": "Shkarkon dhe instalon përditësimi për plugin që janë konfiguruar të përditësohen automatikisht.",
"TaskUpdatePlugins": "Përditëso Plugin",
"TaskRefreshPeopleDescription": "Përditëson metadata të aktorëve dhe regjizorëve në librarinë tuaj.",
"TaskRefreshPeople": "Rifresko aktorët",
"TaskCleanLogsDescription": "Fshin skëdarët log që janë më të vjetër se {0} ditë.",
"TaskCleanLogs": "Fshi dosjen Log",
"TaskRefreshLibraryDescription": "Skanon librarinë media për skedarë të rinj dhe rifreskon metadata.",
"TaskRefreshLibrary": "Skano librarinë media",
"TaskRefreshChapterImagesDescription": "Krijon imazh për videot që kanë kapituj.",
"TaskRefreshChapterImages": "Ekstrakto Imazhet e Kapitullit",
"TaskCleanCacheDescription": "Fshi skedarët e cache-s që nuk i duhen më sistemit.",
"TaskCleanCache": "Pastro memorjen cache",
"TasksChannelsCategory": "Kanalet nga interneti",
"TasksApplicationCategory": "Aplikacioni",
"TasksLibraryCategory": "Libraria",
"TasksMaintenanceCategory": "Mirëmbajtje",
"VersionNumber": "Versioni {0}",
"ValueSpecialEpisodeName": "Speciale - {0}",
"ValueHasBeenAddedToLibrary": "{0} u shtua tek libraria juaj",
"UserStoppedPlayingItemWithValues": "{0} mbaroi së shikuari {1} tek {2}",
"UserStartedPlayingItemWithValues": "{0} po shikon {1} tek {2}",
"UserPolicyUpdatedWithName": "Politika e përdoruesit u përditësua për {0}",
"UserPasswordChangedWithName": "Fjalëkalimi u ndryshua për përdoruesin {0}",
"UserOnlineFromDevice": "{0} është në linjë nga {1}",
"UserOfflineFromDevice": "{0} u shkëput nga {1}",
"UserLockedOutWithName": "Përdoruesi {0} u përjashtua",
"UserDownloadingItemWithValues": "{0} po shkarkon {1}",
"UserDeletedWithName": "Përdoruesi {0} u fshi",
"UserCreatedWithName": "Përdoruesi {0} u krijua",
"User": "Përdoruesi",
"TvShows": "Seriale TV",
"System": "Sistemi",
"Sync": "Sinkronizo",
"SubtitleDownloadFailureFromForItem": "Titrat deshtuan të shkarkohen nga {0} për {1}",
"StartupEmbyServerIsLoading": "Serveri Jellyfin po ngarkohet. Ju lutemi provoni përseri pas pak.",
"Songs": "Këngë",
"Shows": "Seriale",
"ServerNameNeedsToBeRestarted": "{0} duhet të ristartoj",
"ScheduledTaskStartedWithName": "{0} filloi",
"ScheduledTaskFailedWithName": "{0} dështoi",
"ProviderValue": "Ofruesi: {0}",
"PluginUpdatedWithName": "{0} u përditësua",
"PluginUninstalledWithName": "{0} u çinstalua",
"PluginInstalledWithName": "{0} u instalua",
"Plugin": "Plugin",
"Playlists": "Listat për luajtje",
"Photos": "Fotografitë",
"NotificationOptionVideoPlaybackStopped": "Luajtja e videos ndaloi",
"NotificationOptionVideoPlayback": "Luajtja e videos filloi",
"NotificationOptionUserLockedOut": "Përdoruesi u përjashtua",
"NotificationOptionTaskFailed": "Ushtrimi i planifikuar dështoi",
"NotificationOptionServerRestartRequired": "Kërkohet ristartim i serverit",
"NotificationOptionPluginUpdateInstalled": "Përditësimi i plugin u instalua",
"NotificationOptionPluginUninstalled": "Plugin u çinstalua",
"NotificationOptionPluginInstalled": "Plugin u instalua",
"NotificationOptionPluginError": "Plugin dështoi",
"NotificationOptionNewLibraryContent": "Një përmbajtje e re u shtua",
"NotificationOptionInstallationFailed": "Instalimi dështoi",
"NotificationOptionCameraImageUploaded": "Fotoja nga kamera u ngarkua",
"NotificationOptionAudioPlaybackStopped": "Luajtja e audios ndaloi",
"NotificationOptionAudioPlayback": "Luajtja e audios filloi",
"NotificationOptionApplicationUpdateInstalled": "Përditësimi i aplikacionit u instalua",
"NotificationOptionApplicationUpdateAvailable": "Një perditësim i aplikacionit është gati",
"NewVersionIsAvailable": "Një version i ri i Jellyfin është gati për tu shkarkuar.",
"NameSeasonUnknown": "Sezon i panjohur",
"NameSeasonNumber": "Sezoni {0}",
"NameInstallFailed": "Instalimi i {0} dështoi",
"MusicVideos": "Video muzikore",
"Music": "Muzikë",
"Movies": "Filma",
"MixedContent": "Përmbajtje e përzier",
"MessageServerConfigurationUpdated": "Konfigurimet e serverit u përditësuan",
"MessageNamedServerConfigurationUpdatedWithValue": "Seksioni i konfigurimit të serverit {0} u përditësua",
"MessageApplicationUpdated": "Serveri Jellyfin u përditësua",
"Latest": "Të fundit",
"LabelRunningTimeValue": "Kohëzgjatja: {0}",
"LabelIpAddressValue": "Adresa IP: {0}",
"ItemRemovedWithName": "{0} u fshi nga libraria",
"ItemAddedWithName": "{0} u shtua tek libraria",
"HomeVideos": "Video personale",
"HeaderRecordingGroups": "Grupet e regjistrimit",
"HeaderNextUp": "Në vazhdim",
"HeaderLiveTV": "TV Live",
"HeaderFavoriteSongs": "Kënget e preferuara",
"HeaderFavoriteShows": "Serialet e preferuar",
"HeaderFavoriteEpisodes": "Episodet e preferuar",
"HeaderFavoriteArtists": "Artistët e preferuar",
"HeaderFavoriteAlbums": "Albumet e preferuar",
"HeaderContinueWatching": "Vazhdo të shikosh",
"HeaderCameraUploads": "Ngarkimet nga Kamera",
"HeaderAlbumArtists": "Artistët e albumeve",
"Genres": "Zhanre",
"Folders": "Dosje",
"Favorites": "Të preferuara",
"FailedLoginAttemptWithUserName": "Përpjekja për hyrje dështoi nga {0}",
"DeviceOnlineWithName": "{0} u lidh",
"DeviceOfflineWithName": "{0} u shkëput",
"Collections": "Koleksione",
"ChapterNameValue": "Kapituj",
"Channels": "Kanale",
"CameraImageUploadedFrom": "Një foto e re nga kamera u ngarkua nga {0}",
"Books": "Libra",
"AuthenticationSucceededWithUserName": "{0} u identifikua me sukses",
"Artists": "Artistë",
"Application": "Aplikacioni",
"AppDeviceValues": "Aplikacioni: {0}, Pajisja: {1}",
"Albums": "Albumet"
}

@ -0,0 +1,117 @@
{
"Collections": "Bộ Sưu Tập",
"Favorites": "Sở Thích",
"Folders": "Thư Mục",
"Genres": "Thể Loại",
"HeaderAlbumArtists": "Bộ Sưu Tập Nghệ sĩ",
"HeaderContinueWatching": "Tiếp Tục Xem Tiếp",
"HeaderLiveTV": "TV Trực Tiếp",
"Movies": "Phim",
"Photos": "Ảnh",
"Playlists": "Danh Sách Chơi",
"Shows": "Các Chương Trình",
"Songs": "Các Bài Hát",
"Sync": "Đồng Bộ",
"ValueSpecialEpisodeName": "Đặc Biệt - {0}",
"Albums": "Bộ Sưu Tập",
"Artists": "Các Nghệ Sĩ",
"TaskDownloadMissingSubtitlesDescription": "Tìm kiếm phụ đề bị thiếu trên Internet dựa trên cấu hình thông tin chi tiết.",
"TaskDownloadMissingSubtitles": "Tải xuống phụ đề bị thiếu",
"TaskRefreshChannelsDescription": "Làm mới thông tin kênh internet.",
"TaskRefreshChannels": "Làm Mới Kênh",
"TaskCleanTranscodeDescription": "Xóa các tệp chuyển mã cũ hơn một ngày.",
"TaskCleanTranscode": "Làm Sạch Thư Mục Chuyển Mã",
"TaskUpdatePluginsDescription": "Tải xuống và cài đặt các bản cập nhật cho các plugin được định cấu hình để cập nhật tự động.",
"TaskUpdatePlugins": "Cập Nhật Plugins",
"TaskRefreshPeopleDescription": "Cập nhật thông tin chi tiết cho diễn viên và đạo diễn trong thư viện phương tiện của bạn.",
"TaskRefreshPeople": "Làm mới Người dùng",
"TaskCleanLogsDescription": "Xóa tập tin nhật ký cũ hơn {0} ngày.",
"TaskCleanLogs": "Làm sạch nhật ký",
"TaskRefreshLibraryDescription": "Quét thư viện phương tiện của bạn để tìm các tệp mới và làm mới thông tin chi tiết.",
"TaskRefreshLibrary": "Quét Thư viện Phương tiện",
"TaskRefreshChapterImagesDescription": "Tạo hình thu nhỏ cho các video có chương.",
"TaskRefreshChapterImages": "Trích xuất hình ảnh chương",
"TaskCleanCacheDescription": "Xóa các tệp cache không còn cần thiết của hệ thống.",
"TaskCleanCache": "Làm Sạch Thư Mục Cache",
"TasksChannelsCategory": "Kênh Internet",
"TasksApplicationCategory": "Ứng Dụng",
"TasksLibraryCategory": "Thư Viện",
"TasksMaintenanceCategory": "Bảo Trì",
"VersionNumber": "Phiên Bản {0}",
"ValueHasBeenAddedToLibrary": "{0} đã được thêm vào thư viện của bạn",
"UserStoppedPlayingItemWithValues": "{0} đã phát xong {1} trên {2}",
"UserStartedPlayingItemWithValues": "{0} đang phát {1} trên {2}",
"UserPolicyUpdatedWithName": "Chính sách người dùng đã được cập nhật cho {0}",
"UserPasswordChangedWithName": "Mật khẩu đã được thay đổi cho người dùng {0}",
"UserOnlineFromDevice": "{0} trực tuyến từ {1}",
"UserOfflineFromDevice": "{0} đã ngắt kết nối từ {1}",
"UserLockedOutWithName": "User {0} đã bị khóa",
"UserDownloadingItemWithValues": "{0} đang tải xuống {1}",
"UserDeletedWithName": "Người Dùng {0} đã được xóa",
"UserCreatedWithName": "Người Dùng {0} đã được tạo",
"User": "Người Dùng",
"TvShows": "Chương Trình TV",
"System": "Hệ Thống",
"SubtitleDownloadFailureFromForItem": "Không thể tải xuống phụ đề từ {0} cho {1}",
"StartupEmbyServerIsLoading": "Jellyfin Server đang tải. Vui lòng thử lại trong thời gian ngắn.",
"ServerNameNeedsToBeRestarted": "{0} cần được khởi động lại",
"ScheduledTaskStartedWithName": "{0} đã bắt đầu",
"ScheduledTaskFailedWithName": "{0} đã thất bại",
"ProviderValue": "Provider: {0}",
"PluginUpdatedWithName": "{0} đã cập nhật",
"PluginUninstalledWithName": "{0} đã được gỡ bỏ",
"PluginInstalledWithName": "{0} đã được cài đặt",
"Plugin": "Plugin",
"NotificationOptionVideoPlaybackStopped": "Phát lại video đã dừng",
"NotificationOptionVideoPlayback": "Đã bắt đầu phát lại video",
"NotificationOptionUserLockedOut": "Người dùng bị khóa",
"NotificationOptionTaskFailed": "Lỗi tác vụ đã lên lịch",
"NotificationOptionServerRestartRequired": "Yêu cầu khởi động lại Server",
"NotificationOptionPluginUpdateInstalled": "Cập nhật Plugin đã được cài đặt",
"NotificationOptionPluginUninstalled": "Đã gỡ bỏ Plugin",
"NotificationOptionPluginInstalled": "Đã cài đặt Plugin",
"NotificationOptionPluginError": "Thất bại Plugin",
"NotificationOptionNewLibraryContent": "Nội dung mới được thêm vào",
"NotificationOptionInstallationFailed": "Cài đặt thất bại",
"NotificationOptionCameraImageUploaded": "Đã tải lên hình ảnh máy ảnh",
"NotificationOptionAudioPlaybackStopped": "Phát lại âm thanh đã dừng",
"NotificationOptionAudioPlayback": "Phát lại âm thanh đã bắt đầu",
"NotificationOptionApplicationUpdateInstalled": "Bản cập nhật ứng dụng đã được cài đặt",
"NotificationOptionApplicationUpdateAvailable": "Bản cập nhật ứng dụng hiện sẵn có",
"NewVersionIsAvailable": "Một phiên bản mới của Jellyfin Server sẵn có để tải.",
"NameSeasonUnknown": "Không Rõ Mùa",
"NameSeasonNumber": "Mùa {0}",
"NameInstallFailed": "{0} cài đặt thất bại",
"MusicVideos": "Video Nhạc",
"Music": "Nhạc",
"MixedContent": "Nội dung hỗn hợp",
"MessageServerConfigurationUpdated": "Cấu hình máy chủ đã được cập nhật",
"MessageNamedServerConfigurationUpdatedWithValue": "Phần cấu hình máy chủ {0} đã được cập nhật",
"MessageApplicationUpdatedTo": "Jellyfin Server đã được cập nhật lên {0}",
"MessageApplicationUpdated": "Jellyfin Server đã được cập nhật",
"Latest": "Gần Nhất",
"LabelRunningTimeValue": "Thời Gian Chạy: {0}",
"LabelIpAddressValue": "Địa Chỉ IP: {0}",
"ItemRemovedWithName": "{0} đã xóa khỏi thư viện",
"ItemAddedWithName": "{0} được thêm vào thư viện",
"Inherit": "Thừa hưởng",
"HomeVideos": "Video nhà",
"HeaderRecordingGroups": "Nhóm Ghi Video",
"HeaderNextUp": "Tiếp Theo",
"HeaderFavoriteSongs": "Bài Hát Yêu Thích",
"HeaderFavoriteShows": "Chương Trình Yêu Thích",
"HeaderFavoriteEpisodes": "Tập Phim Yêu Thích",
"HeaderFavoriteArtists": "Nghệ Sĩ Yêu Thích",
"HeaderFavoriteAlbums": "Album Ưa Thích",
"HeaderCameraUploads": "Máy Ảnh Tải Lên",
"FailedLoginAttemptWithUserName": "Nỗ lực đăng nhập thất bại từ {0}",
"DeviceOnlineWithName": "{0} đã kết nối",
"DeviceOfflineWithName": "{0} đã ngắt kết nối",
"ChapterNameValue": "Chương {0}",
"Channels": "Các Kênh",
"CameraImageUploadedFrom": "Một hình ảnh máy ảnh mới đã được tải lên từ {0}",
"Books": "Các Quyển Sách",
"AuthenticationSucceededWithUserName": "{0} xác thực thành công",
"Application": "Ứng Dụng",
"AppDeviceValues": "Ứng Dụng: {0}, Thiết Bị: {1}"
}

@ -0,0 +1,60 @@
using System;
namespace Emby.Server.Implementations.Plugins
{
/// <summary>
/// Defines a Plugin manifest file.
/// </summary>
public class PluginManifest
{
/// <summary>
/// Gets or sets the category of the plugin.
/// </summary>
public string Category { get; set; }
/// <summary>
/// Gets or sets the changelog information.
/// </summary>
public string Changelog { get; set; }
/// <summary>
/// Gets or sets the description of the plugin.
/// </summary>
public string Description { get; set; }
/// <summary>
/// Gets or sets the Global Unique Identifier for the plugin.
/// </summary>
public Guid Guid { get; set; }
/// <summary>
/// Gets or sets the Name of the plugin.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets an overview of the plugin.
/// </summary>
public string Overview { get; set; }
/// <summary>
/// Gets or sets the owner of the plugin.
/// </summary>
public string Owner { get; set; }
/// <summary>
/// Gets or sets the compatibility version for the plugin.
/// </summary>
public string TargetAbi { get; set; }
/// <summary>
/// Gets or sets the timestamp of the plugin.
/// </summary>
public DateTime Timestamp { get; set; }
/// <summary>
/// Gets or sets the Version number of the plugin.
/// </summary>
public string Version { get; set; }
}
}

@ -15,12 +15,14 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates;
using MediaBrowser.Common.System;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Updates;
using Microsoft.Extensions.Logging;
using MediaBrowser.Model.System;
namespace Emby.Server.Implementations.Updates
{
@ -377,11 +379,20 @@ namespace Emby.Server.Implementations.Updates
throw new InvalidDataException("The checksum of the received data doesn't match.");
}
// Version folder as they cannot be overwritten in Windows.
targetDir += "_" + package.Version;
if (Directory.Exists(targetDir))
{
Directory.Delete(targetDir, true);
try
{
Directory.Delete(targetDir, true);
}
catch
{
// Ignore any exceptions.
}
}
stream.Position = 0;
_zipClient.ExtractAllFromZip(stream, targetDir, true);
@ -423,15 +434,22 @@ namespace Emby.Server.Implementations.Updates
path = file;
}
if (isDirectory)
try
{
_logger.LogInformation("Deleting plugin directory {0}", path);
Directory.Delete(path, true);
if (isDirectory)
{
_logger.LogInformation("Deleting plugin directory {0}", path);
Directory.Delete(path, true);
}
else
{
_logger.LogInformation("Deleting plugin file {0}", path);
_fileSystem.DeleteFile(path);
}
}
else
catch
{
_logger.LogInformation("Deleting plugin file {0}", path);
_fileSystem.DeleteFile(path);
// Ignore file errors.
}
var list = _config.Configuration.UninstalledPlugins.ToList();

@ -20,7 +20,7 @@
<ItemGroup>
<PackageReference Include="BlurHashSharp" Version="1.1.0" />
<PackageReference Include="BlurHashSharp.SkiaSharp" Version="1.1.0" />
<PackageReference Include="SkiaSharp" Version="2.80.1" />
<PackageReference Include="SkiaSharp" Version="2.80.2" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.80.1" />
</ItemGroup>

@ -141,6 +141,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
Name = episode.EpisodeName,
Overview = episode.Overview,
CommunityRating = (float?)episode.SiteRating,
OfficialRating = episode.ContentRating,
}
};
result.ResetPeople();

@ -124,7 +124,7 @@ To run the project with Visual Studio Code you will first need to open the repos
Second, you need to [install the recommended extensions for the workspace](https://code.visualstudio.com/docs/editor/extension-gallery#_recommended-extensions). Note that extension recommendations are classified as either "Workspace Recommendations" or "Other Recommendations", but only the "Workspace Recommendations" are required.
After the required extensions are installed, you can can run the server by pressing `F5`.
After the required extensions are installed, you can run the server by pressing `F5`.
#### Running From The Command Line

@ -0,0 +1,90 @@
using System.Linq;
using Emby.Naming.AudioBook;
using Emby.Naming.Common;
using MediaBrowser.Model.IO;
using Xunit;
namespace Jellyfin.Naming.Tests.AudioBook
{
public class AudioBookListResolverTests
{
private readonly NamingOptions _namingOptions = new NamingOptions();
[Fact]
public void TestStackAndExtras()
{
// No stacking here because there is no part/disc/etc
var files = new[]
{
"Harry Potter and the Deathly Hallows/Part 1.mp3",
"Harry Potter and the Deathly Hallows/Part 2.mp3",
"Harry Potter and the Deathly Hallows/book.nfo",
"Batman/Chapter 1.mp3",
"Batman/Chapter 2.mp3",
"Batman/Chapter 3.mp3",
};
var resolver = GetResolver();
var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
{
IsDirectory = false,
FullName = i
})).ToList();
Assert.Equal(2, result[0].Files.Count);
// Assert.Empty(result[0].Extras); FIXME: AudioBookListResolver should resolve extra files properly
Assert.Equal("Harry Potter and the Deathly Hallows", result[0].Name);
Assert.Equal(3, result[1].Files.Count);
Assert.Empty(result[1].Extras);
Assert.Equal("Batman", result[1].Name);
}
[Fact]
public void TestWithMetadata()
{
var files = new[]
{
"Harry Potter and the Deathly Hallows/Chapter 1.ogg",
"Harry Potter and the Deathly Hallows/Harry Potter and the Deathly Hallows.nfo"
};
var resolver = GetResolver();
var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
{
IsDirectory = false,
FullName = i
}));
Assert.Single(result);
}
[Fact]
public void TestWithExtra()
{
var files = new[]
{
"Harry Potter and the Deathly Hallows/Chapter 1.mp3",
"Harry Potter and the Deathly Hallows/Harry Potter and the Deathly Hallows trailer.mp3"
};
var resolver = GetResolver();
var result = resolver.Resolve(files.Select(i => new FileSystemMetadata
{
IsDirectory = false,
FullName = i
})).ToList();
Assert.Single(result);
}
private AudioBookListResolver GetResolver()
{
return new AudioBookListResolver(_namingOptions);
}
}
}

@ -0,0 +1,57 @@
using System.Collections.Generic;
using Emby.Naming.AudioBook;
using Emby.Naming.Common;
using Xunit;
namespace Jellyfin.Naming.Tests.AudioBook
{
public class AudioBookResolverTests
{
private readonly NamingOptions _namingOptions = new NamingOptions();
public static IEnumerable<object[]> GetResolveFileTestData()
{
yield return new object[]
{
new AudioBookFileInfo()
{
Path = @"/server/AudioBooks/Larry Potter/Larry Potter.mp3",
Container = "mp3",
}
};
yield return new object[]
{
new AudioBookFileInfo()
{
Path = @"/server/AudioBooks/Berry Potter/Chapter 1 .ogg",
Container = "ogg",
ChapterNumber = 1
}
};
yield return new object[]
{
new AudioBookFileInfo()
{
Path = @"/server/AudioBooks/Nerry Potter/Part 3 - Chapter 2.mp3",
Container = "mp3",
ChapterNumber = 2,
PartNumber = 3
}
};
}
[Theory]
[MemberData(nameof(GetResolveFileTestData))]
public void ResolveFile_ValidFileName_Success(AudioBookFileInfo expectedResult)
{
var result = new AudioBookResolver(_namingOptions).Resolve(expectedResult.Path);
Assert.NotNull(result);
Assert.Equal(result.Path, expectedResult.Path);
Assert.Equal(result.Container, expectedResult.Container);
Assert.Equal(result.ChapterNumber, expectedResult.ChapterNumber);
Assert.Equal(result.PartNumber, expectedResult.PartNumber);
Assert.Equal(result.IsDirectory, expectedResult.IsDirectory);
}
}
}
Loading…
Cancel
Save