2nd half of changes

This commit is contained in:
Patrick Barron 2020-05-12 22:25:45 -04:00
commit b7621d762c
14 changed files with 211 additions and 37 deletions

View File

@ -596,6 +596,7 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IBlurayExaminer, BdInfoExaminer>(); serviceCollection.AddSingleton<IBlurayExaminer, BdInfoExaminer>();
serviceCollection.AddSingleton<IUserDataRepository, SqliteUserDataRepository>();
serviceCollection.AddSingleton<IUserDataManager, UserDataManager>(); serviceCollection.AddSingleton<IUserDataManager, UserDataManager>();
serviceCollection.AddSingleton<IItemRepository, SqliteItemRepository>(); serviceCollection.AddSingleton<IItemRepository, SqliteItemRepository>();
@ -700,7 +701,8 @@ namespace Emby.Server.Implementations
SetStaticProperties(); SetStaticProperties();
((SqliteItemRepository)Resolve<IItemRepository>()).Initialize(); var userDataRepo = (SqliteUserDataRepository)Resolve<IUserDataRepository>();
((SqliteItemRepository)Resolve<IItemRepository>()).Initialize(userDataRepo, Resolve<IUserManager>());
FindParts(); FindParts();
} }

View File

@ -102,7 +102,7 @@ namespace Emby.Server.Implementations.Data
/// <summary> /// <summary>
/// Opens the connection to the database /// Opens the connection to the database
/// </summary> /// </summary>
public void Initialize() public void Initialize(SqliteUserDataRepository userDataRepo, IUserManager userManager)
{ {
const string CreateMediaStreamsTableCommand const string CreateMediaStreamsTableCommand
= "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, ColorPrimaries TEXT NULL, ColorSpace TEXT NULL, ColorTransfer TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))"; = "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, ColorPrimaries TEXT NULL, ColorSpace TEXT NULL, ColorTransfer TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))";
@ -324,6 +324,8 @@ namespace Emby.Server.Implementations.Data
connection.RunQueries(postQueries); connection.RunQueries(postQueries);
} }
userDataRepo.Initialize(userManager, WriteLock, WriteConnection);
} }
private static readonly string[] _retriveItemColumns = private static readonly string[] _retriveItemColumns =

View File

@ -4,6 +4,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;

View File

@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Text.Json.Serialization;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
namespace Jellyfin.Data.Entities namespace Jellyfin.Data.Entities
@ -121,7 +122,8 @@ namespace Jellyfin.Data.Entities
/// </summary> /// </summary>
[Key] [Key]
[Required] [Required]
public Guid Id { get; protected set; } [JsonIgnore]
public Guid Id { get; set; }
/// <summary> /// <summary>
/// Required, Max length = 255 /// Required, Max length = 255
@ -129,6 +131,7 @@ namespace Jellyfin.Data.Entities
[Required] [Required]
[MaxLength(255)] [MaxLength(255)]
[StringLength(255)] [StringLength(255)]
[JsonPropertyName("Name")]
public string Username { get; set; } public string Username { get; set; }
/// <summary> /// <summary>

View File

@ -16,10 +16,6 @@ namespace Jellyfin.Server.Implementations
public partial class JellyfinDb : DbContext public partial class JellyfinDb : DbContext
{ {
public virtual DbSet<ActivityLog> ActivityLogs { get; set; } public virtual DbSet<ActivityLog> ActivityLogs { get; set; }
public virtual DbSet<Group> Groups { get; set; }
public virtual DbSet<Permission> Permissions { get; set; }
public virtual DbSet<Preference> Preferences { get; set; }
public virtual DbSet<ProviderMapping> ProviderMappings { get; set; }
public virtual DbSet<Data.Entities.User> Users { get; set; } public virtual DbSet<Data.Entities.User> Users { get; set; }
/*public virtual DbSet<Artwork> Artwork { get; set; } /*public virtual DbSet<Artwork> Artwork { get; set; }
public virtual DbSet<Book> Books { get; set; } public virtual DbSet<Book> Books { get; set; }
@ -34,6 +30,7 @@ namespace Jellyfin.Server.Implementations
public virtual DbSet<Episode> Episodes { get; set; } public virtual DbSet<Episode> Episodes { get; set; }
public virtual DbSet<EpisodeMetadata> EpisodeMetadata { get; set; } public virtual DbSet<EpisodeMetadata> EpisodeMetadata { get; set; }
public virtual DbSet<Genre> Genres { get; set; } public virtual DbSet<Genre> Genres { get; set; }
public virtual DbSet<Group> Groups { get; set; }
public virtual DbSet<Library> Libraries { get; set; } public virtual DbSet<Library> Libraries { get; set; }
public virtual DbSet<LibraryItem> LibraryItems { get; set; } public virtual DbSet<LibraryItem> LibraryItems { get; set; }
public virtual DbSet<LibraryRoot> LibraryRoot { get; set; } public virtual DbSet<LibraryRoot> LibraryRoot { get; set; }
@ -46,10 +43,13 @@ namespace Jellyfin.Server.Implementations
public virtual DbSet<MovieMetadata> MovieMetadata { get; set; } public virtual DbSet<MovieMetadata> MovieMetadata { get; set; }
public virtual DbSet<MusicAlbum> MusicAlbums { get; set; } public virtual DbSet<MusicAlbum> MusicAlbums { get; set; }
public virtual DbSet<MusicAlbumMetadata> MusicAlbumMetadata { get; set; } public virtual DbSet<MusicAlbumMetadata> MusicAlbumMetadata { get; set; }
public virtual DbSet<Permission> Permissions { get; set; }
public virtual DbSet<Person> People { get; set; } public virtual DbSet<Person> People { get; set; }
public virtual DbSet<PersonRole> PersonRoles { get; set; } public virtual DbSet<PersonRole> PersonRoles { get; set; }
public virtual DbSet<Photo> Photo { get; set; } public virtual DbSet<Photo> Photo { get; set; }
public virtual DbSet<PhotoMetadata> PhotoMetadata { get; set; } public virtual DbSet<PhotoMetadata> PhotoMetadata { get; set; }
public virtual DbSet<Preference> Preferences { get; set; }
public virtual DbSet<ProviderMapping> ProviderMappings { get; set; }
public virtual DbSet<Rating> Ratings { get; set; } public virtual DbSet<Rating> Ratings { get; set; }
/// <summary> /// <summary>

View File

@ -306,6 +306,28 @@ namespace Jellyfin.Server.Implementations.User
}; };
} }
public PublicUserDto GetPublicUserDto(Data.Entities.User user, string remoteEndPoint = null)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user);
bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetAuthenticationProvider(user).GetEasyPasswordHash(user));
bool hasPassword = user.EnableLocalPassword &&
!string.IsNullOrEmpty(remoteEndPoint) &&
_networkManager.IsInLocalNetwork(remoteEndPoint) ? hasConfiguredEasyPassword : hasConfiguredPassword;
return new PublicUserDto
{
Name = user.Username,
HasPassword = hasPassword,
HasConfiguredPassword = hasConfiguredPassword
};
}
public async Task<Data.Entities.User> AuthenticateUser( public async Task<Data.Entities.User> AuthenticateUser(
string username, string username,
string password, string password,

View File

@ -44,6 +44,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.7.82" /> <PackageReference Include="CommandLineParser" Version="2.7.82" />
<PackageReference Include="Json.Net" Version="1.0.22" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.3"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.3">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@ -17,7 +17,9 @@ namespace Jellyfin.Server.Migrations
private static readonly Type[] _migrationTypes = private static readonly Type[] _migrationTypes =
{ {
typeof(Routines.DisableTranscodingThrottling), typeof(Routines.DisableTranscodingThrottling),
typeof(Routines.CreateUserLoggingConfigFile) typeof(Routines.CreateUserLoggingConfigFile),
typeof(Routines.MigrateActivityLogDb),
typeof(Routines.MigrateUserDb)
}; };
/// <summary> /// <summary>

View File

@ -5,8 +5,8 @@ using System.IO;
using Emby.Server.Implementations.Data; using Emby.Server.Implementations.Data;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations;
using MediaBrowser.Controller;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using SQLitePCL.pretty; using SQLitePCL.pretty;
@ -16,20 +16,31 @@ namespace Jellyfin.Server.Migrations.Routines
{ {
private const string DbFilename = "activitylog.db"; private const string DbFilename = "activitylog.db";
private readonly ILogger<MigrateActivityLogDb> _logger;
private readonly JellyfinDbProvider _provider;
private readonly IServerApplicationPaths _paths;
public MigrateActivityLogDb(ILogger<MigrateActivityLogDb> logger, IServerApplicationPaths paths, JellyfinDbProvider provider)
{
_logger = logger;
_provider = provider;
_paths = paths;
}
public Guid Id => Guid.Parse("3793eb59-bc8c-456c-8b9f-bd5a62a42978"); public Guid Id => Guid.Parse("3793eb59-bc8c-456c-8b9f-bd5a62a42978");
public string Name => "MigrateActivityLogDatabase"; public string Name => "MigrateActivityLogDatabase";
public void Perform(CoreAppHost host, ILogger logger) public void Perform()
{ {
var dataPath = host.ServerConfigurationManager.ApplicationPaths.DataPath; var dataPath = _paths.DataPath;
using (var connection = SQLite3.Open( using (var connection = SQLite3.Open(
Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename),
ConnectionFlags.ReadOnly, ConnectionFlags.ReadOnly,
null)) null))
{ {
logger.LogInformation("Migrating the database may take a while, do not stop Jellyfin."); _logger.LogInformation("Migrating the database may take a while, do not stop Jellyfin.");
using var dbContext = host.ServiceProvider.GetService<JellyfinDb>(); using var dbContext = _provider.CreateContext();
var queryResult = connection.Query("SELECT * FROM ActivityLog ORDER BY Id ASC"); var queryResult = connection.Query("SELECT * FROM ActivityLog ORDER BY Id ASC");
@ -75,7 +86,7 @@ namespace Jellyfin.Server.Migrations.Routines
} }
catch (IOException e) catch (IOException e)
{ {
logger.LogError(e, "Error renaming legacy activity log database to 'activitylog.db.old'"); _logger.LogError(e, "Error renaming legacy activity log database to 'activitylog.db.old'");
} }
} }

View File

@ -1,8 +1,144 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.IO;
using Emby.Server.Implementations.Data;
using Emby.Server.Implementations.Serialization;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Server.Implementations;
using MediaBrowser.Controller;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Users;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using SQLitePCL.pretty;
namespace Jellyfin.Server.Migrations.Routines namespace Jellyfin.Server.Migrations.Routines
{ {
public class MigrateUserDb public class MigrateUserDb : IMigrationRoutine
{ {
private readonly ILogger<MigrateUserDb> _logger;
private readonly IServerApplicationPaths _paths;
private readonly JellyfinDbProvider _provider;
private readonly MyXmlSerializer _xmlSerializer;
public MigrateUserDb(ILogger<MigrateUserDb> logger, IServerApplicationPaths paths, JellyfinDbProvider provider, MyXmlSerializer xmlSerializer)
{
_logger = logger;
_paths = paths;
_provider = provider;
_xmlSerializer = xmlSerializer;
}
public Guid Id => Guid.Parse("5C4B82A2-F053-4009-BD05-B6FCAD82F14C");
public string Name => "MigrateUserDb";
public void Perform()
{
var dataPath = _paths.DataPath;
_logger.LogInformation("Migrating the user database may take a while, do not stop Jellyfin.");
using (var connection = SQLite3.Open(Path.Combine(dataPath, "users.db"), ConnectionFlags.ReadOnly, null))
{
using var dbContext = _provider.CreateContext();
var queryResult = connection.Query("SELECT * FROM LocalUsersv2");
dbContext.RemoveRange(dbContext.Users);
dbContext.SaveChanges();
foreach (var entry in queryResult)
{
var json = JsonConvert.DeserializeObject<Dictionary<string, string>>(entry[2].ToString());
var userDataDir = Path.Combine(_paths.UserConfigurationDirectoryPath, json["Name"]);
var config = (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), Path.Combine(userDataDir, "config.xml"));
var policy = (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), Path.Combine(userDataDir, "policy.xml"));
var user = new User(
json["Name"],
false,
policy.AuthenticatioIsnProviderId,
policy.InvalidLoginAttemptCount,
config.SubtitleMode,
config.PlayDefaultAudioTrack)
{
Id = entry[1].ReadGuidFromBlob(),
InternalId = entry[0].ToInt64(),
MaxParentalAgeRating = policy.MaxParentalRating,
EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess,
RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit,
AuthenticationProviderId = policy.AuthenticatioIsnProviderId,
PasswordResetProviderId = policy.PasswordResetProviderId,
InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount,
LoginAttemptsBeforeLockout = policy.LoginAttemptsBeforeLockout == -1 ? null : new int?(policy.LoginAttemptsBeforeLockout),
SubtitleMode = config.SubtitleMode,
HidePlayedInLatest = config.HidePlayedInLatest,
EnableLocalPassword = config.EnableLocalPassword,
PlayDefaultAudioTrack = config.PlayDefaultAudioTrack,
DisplayCollectionsView = config.DisplayCollectionsView,
DisplayMissingEpisodes = config.DisplayMissingEpisodes,
AudioLanguagePreference = config.AudioLanguagePreference,
RememberAudioSelections = config.RememberAudioSelections,
EnableNextEpisodeAutoPlay = config.EnableNextEpisodeAutoPlay,
RememberSubtitleSelections = config.RememberSubtitleSelections,
SubtitleLanguagePreference = config.SubtitleLanguagePreference,
};
user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator);
user.SetPermission(PermissionKind.IsHidden, policy.IsHidden);
user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled);
user.SetPermission(PermissionKind.EnableSharedDeviceControl, policy.EnableSharedDeviceControl);
user.SetPermission(PermissionKind.EnableRemoteAccess, policy.EnableRemoteAccess);
user.SetPermission(PermissionKind.EnableLiveTvManagement, policy.EnableLiveTvManagement);
user.SetPermission(PermissionKind.EnableLiveTvAccess, policy.EnableLiveTvAccess);
user.SetPermission(PermissionKind.EnableMediaPlayback, policy.EnableMediaPlayback);
user.SetPermission(PermissionKind.EnableAudioPlaybackTranscoding, policy.EnableAudioPlaybackTranscoding);
user.SetPermission(PermissionKind.EnableVideoPlaybackTranscoding, policy.EnableVideoPlaybackTranscoding);
user.SetPermission(PermissionKind.EnableContentDeletion, policy.EnableContentDeletion);
user.SetPermission(PermissionKind.EnableContentDownloading, policy.EnableContentDownloading);
user.SetPermission(PermissionKind.EnableSyncTranscoding, policy.EnableSyncTranscoding);
user.SetPermission(PermissionKind.EnableMediaConversion, policy.EnableMediaConversion);
user.SetPermission(PermissionKind.EnableAllChannels, policy.EnableAllChannels);
user.SetPermission(PermissionKind.EnableAllDevices, policy.EnableAllDevices);
user.SetPermission(PermissionKind.EnableAllFolders, policy.EnableAllFolders);
user.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, policy.EnableRemoteControlOfOtherUsers);
user.SetPermission(PermissionKind.EnablePlaybackRemuxing, policy.EnablePlaybackRemuxing);
user.SetPermission(PermissionKind.ForceRemoteSourceTranscoding, policy.ForceRemoteSourceTranscoding);
user.SetPermission(PermissionKind.EnablePublicSharing, policy.EnablePublicSharing);
foreach (var policyAccessSchedule in policy.AccessSchedules)
{
user.AccessSchedules.Add(policyAccessSchedule);
}
user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags);
user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels);
user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices);
user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders);
user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders);
user.SetPreference(PreferenceKind.OrderedViews, config.OrderedViews);
user.SetPreference(PreferenceKind.GroupedFolders, config.GroupedFolders);
user.SetPreference(PreferenceKind.MyMediaExcludes, config.MyMediaExcludes);
user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes);
}
dbContext.SaveChanges();
}
try
{
File.Move(Path.Combine(dataPath, "users.db"), Path.Combine(dataPath, "users.db" + ".old"));
}
catch (IOException e)
{
_logger.LogError(e, "Error renaming legacy user database to 'users.db.old'");
}
}
} }
} }

View File

@ -884,9 +884,10 @@ namespace MediaBrowser.Api.Images
// Handle image/png; charset=utf-8 // Handle image/png; charset=utf-8
mimeType = mimeType.Split(';').FirstOrDefault(); mimeType = mimeType.Split(';').FirstOrDefault();
var userDataPath = Path.Combine(ServerConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath, user.Username);
await _providerManager await _providerManager
.SaveImage(user, memoryStream, mimeType, Path.Combine(ServerConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath, _imageProcessor.GetImageCacheTag(user))) .SaveImage(user, memoryStream, mimeType, Path.Combine(userDataPath, _imageProcessor.GetImageCacheTag(user)))
.ConfigureAwait(false); .ConfigureAwait(false);
await _userManager.UpdateUserAsync(user); await _userManager.UpdateUserAsync(user);
} }

View File

@ -276,12 +276,12 @@ namespace MediaBrowser.Api
{ {
var result = _userManager var result = _userManager
.Users .Users
.Where(item => !item.Policy.IsDisabled); .Where(item => !item.HasPermission(PermissionKind.IsDisabled));
if (ServerConfigurationManager.Configuration.IsStartupWizardCompleted) if (ServerConfigurationManager.Configuration.IsStartupWizardCompleted)
{ {
var deviceId = _authContext.GetAuthorizationInfo(Request).DeviceId; var deviceId = _authContext.GetAuthorizationInfo(Request).DeviceId;
result = result.Where(item => !item.Policy.IsHidden); result = result.Where(item => !item.HasPermission(PermissionKind.IsHidden));
if (!string.IsNullOrWhiteSpace(deviceId)) if (!string.IsNullOrWhiteSpace(deviceId))
{ {
@ -290,12 +290,12 @@ namespace MediaBrowser.Api
if (!_networkManager.IsInLocalNetwork(Request.RemoteIp)) if (!_networkManager.IsInLocalNetwork(Request.RemoteIp))
{ {
result = result.Where(i => i.Policy.EnableRemoteAccess); result = result.Where(i => i.HasPermission(PermissionKind.EnableRemoteAccess));
} }
} }
return ToOptimizedResult(result return ToOptimizedResult(result
.OrderBy(u => u.Name) .OrderBy(u => u.Username)
.Select(i => _userManager.GetPublicUserDto(i, Request.RemoteIp)) .Select(i => _userManager.GetPublicUserDto(i, Request.RemoteIp))
.ToArray() .ToArray()
); );

View File

@ -144,7 +144,7 @@ namespace MediaBrowser.Controller.Library
/// <summary> /// <summary>
/// Gets the user public dto. /// Gets the user public dto.
/// </summary> /// </summary>
/// <param name="user">Ther user.</param>\ /// <param name="user">The user.</param>\
/// <param name="remoteEndPoint">The remote end point.</param> /// <param name="remoteEndPoint">The remote end point.</param>
/// <returns>A public UserDto, aka a UserDto stripped of personal data.</returns> /// <returns>A public UserDto, aka a UserDto stripped of personal data.</returns>
PublicUserDto GetPublicUserDto(User user, string remoteEndPoint = null); PublicUserDto GetPublicUserDto(User user, string remoteEndPoint = null);

View File

@ -61,11 +61,13 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Naming.Tests", "tests\Jellyfin.Naming.Tests\Jellyfin.Naming.Tests.csproj", "{3998657B-1CCC-49DD-A19F-275DC8495F57}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Naming.Tests", "tests\Jellyfin.Naming.Tests\Jellyfin.Naming.Tests.csproj", "{3998657B-1CCC-49DD-A19F-275DC8495F57}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Api.Tests", "tests\Jellyfin.Api.Tests\Jellyfin.Api.Tests.csproj", "{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Api.Tests", "tests\Jellyfin.Api.Tests\Jellyfin.Api.Tests.csproj", "{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}"
("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Implementations.Tests", "tests\Jellyfin.Server.Implementations.Tests\Jellyfin.Server.Implementations.Tests.csproj", "{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}" EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Implementations.Tests", "tests\Jellyfin.Server.Implementations.Tests\Jellyfin.Server.Implementations.Tests.csproj", "{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Implementations.Tests", "tests\Jellyfin.Server.Implementations.Tests\Jellyfin.Server.Implementations.Tests.csproj", "{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Controller.Tests", "tests\Jellyfin.Controller.Tests\Jellyfin.Controller.Tests.csproj", "{462584F7-5023-4019-9EAC-B98CA458C0A0}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Controller.Tests", "tests\Jellyfin.Controller.Tests\Jellyfin.Controller.Tests.csproj", "{462584F7-5023-4019-9EAC-B98CA458C0A0}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Data", "Jellyfin.Data\Jellyfin.Data.csproj", "{F03299F2-469F-40EF-A655-3766F97A5702}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Implementations", "Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj", "{DAE48069-6D86-4BA6-B148-D1D49B6DDA52}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Implementations", "Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj", "{DAE48069-6D86-4BA6-B148-D1D49B6DDA52}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Api.Tests", "tests\MediaBrowser.Api.Tests\MediaBrowser.Api.Tests.csproj", "{7C93C84F-105C-48E5-A878-406FA0A5B296}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Api.Tests", "tests\MediaBrowser.Api.Tests\MediaBrowser.Api.Tests.csproj", "{7C93C84F-105C-48E5-A878-406FA0A5B296}"
@ -75,8 +77,7 @@ Global
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSectEndProject GlobalSection(ProjectConfigurationPlatforms) = postSolution
Projection(ProjectConfigurationPlatforms) = postSolution
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.Build.0 = Debug|Any CPU {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -185,26 +186,18 @@ Global
{F03299F2-469F-40EF-A655-3766F97A5702}.Debug|Any CPU.Build.0 = Debug|Any CPU {F03299F2-469F-40EF-A655-3766F97A5702}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F03299F2-469F-40EF-A655-3766F97A5702}.Release|Any CPU.ActiveCfg = Release|Any CPU {F03299F2-469F-40EF-A655-3766F97A5702}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F03299F2-469F-40EF-A655-3766F97A5702}.Release|Any CPU.Build.0 = Release|Any CPU {F03299F2-469F-40EF-A655-3766F97A5702}.Release|Any CPU.Build.0 = Release|Any CPU
{22C7DA3A-94F2-4E86-9CE6-86AB02B4F843}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{22C7DA3A-94F2-4E86-9CE6-86AB02B4F843}.Debug|Any CPU.Build.0 = Debug|Any CPU
{22C7DA3A-94F2-4E86-9CE6-86AB02B4F843}.Release|Any CPU.ActiveCfg = Release|Any CPU
{22C7DA3A-94F2-4E86-9CE6-86AB02B4F843}.Release|Any CPU.Build.0 = Release|Any CPU
{DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Debug|Any CPU.Build.0 = Debug|Any CPU {DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Release|Any CPU.ActiveCfg = Release|Any CPU {DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Release|Any CPU.Build.0 = Release|Any CPU {DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Release|Any CPU.Build.0 = Release|Any CPU
{7C93C84F-105C-48E5-A878-406FA0A5B296}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7C93C84F-105C-48E5-A878-406FA0A5B296}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7C93C84F-105C-48E5-A878-406FA0A5B296}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7C93C84F-105C-48E5-A878-406FA0A5B296}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{DF194677-DFD3-42AF-9F75-D44D5A416478} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{28464062-0939-4AA7-9F7B-24DDDA61A7C0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{3998657B-1CCC-49DD-A19F-275DC8495F57} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{462584F7-5023-4019-9EAC-B98CA458C0A0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE}
EndGlobalSection EndGlobalSection