Merge remote-tracking branch 'upstream/master' into no-more-doppelgangers

This commit is contained in:
Mark Monteiro 2020-05-15 15:23:44 -04:00
commit 757643e326
49 changed files with 932 additions and 969 deletions

View File

@ -4,11 +4,10 @@ using System.Globalization;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates; using MediaBrowser.Common.Updates;
using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Session;
@ -30,7 +29,7 @@ namespace Emby.Server.Implementations.Activity
/// </summary> /// </summary>
public sealed class ActivityLogEntryPoint : IServerEntryPoint public sealed class ActivityLogEntryPoint : IServerEntryPoint
{ {
private readonly ILogger _logger; private readonly ILogger<ActivityLogEntryPoint> _logger;
private readonly IInstallationManager _installationManager; private readonly IInstallationManager _installationManager;
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
private readonly ITaskManager _taskManager; private readonly ITaskManager _taskManager;
@ -38,14 +37,12 @@ namespace Emby.Server.Implementations.Activity
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;
private readonly ISubtitleManager _subManager; private readonly ISubtitleManager _subManager;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly IDeviceManager _deviceManager;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ActivityLogEntryPoint"/> class. /// Initializes a new instance of the <see cref="ActivityLogEntryPoint"/> class.
/// </summary> /// </summary>
/// <param name="logger">The logger.</param> /// <param name="logger">The logger.</param>
/// <param name="sessionManager">The session manager.</param> /// <param name="sessionManager">The session manager.</param>
/// <param name="deviceManager">The device manager.</param>
/// <param name="taskManager">The task manager.</param> /// <param name="taskManager">The task manager.</param>
/// <param name="activityManager">The activity manager.</param> /// <param name="activityManager">The activity manager.</param>
/// <param name="localization">The localization manager.</param> /// <param name="localization">The localization manager.</param>
@ -55,7 +52,6 @@ namespace Emby.Server.Implementations.Activity
public ActivityLogEntryPoint( public ActivityLogEntryPoint(
ILogger<ActivityLogEntryPoint> logger, ILogger<ActivityLogEntryPoint> logger,
ISessionManager sessionManager, ISessionManager sessionManager,
IDeviceManager deviceManager,
ITaskManager taskManager, ITaskManager taskManager,
IActivityManager activityManager, IActivityManager activityManager,
ILocalizationManager localization, ILocalizationManager localization,
@ -65,7 +61,6 @@ namespace Emby.Server.Implementations.Activity
{ {
_logger = logger; _logger = logger;
_sessionManager = sessionManager; _sessionManager = sessionManager;
_deviceManager = deviceManager;
_taskManager = taskManager; _taskManager = taskManager;
_activityManager = activityManager; _activityManager = activityManager;
_localization = localization; _localization = localization;
@ -99,52 +94,38 @@ namespace Emby.Server.Implementations.Activity
_userManager.UserPolicyUpdated += OnUserPolicyUpdated; _userManager.UserPolicyUpdated += OnUserPolicyUpdated;
_userManager.UserLockedOut += OnUserLockedOut; _userManager.UserLockedOut += OnUserLockedOut;
_deviceManager.CameraImageUploaded += OnCameraImageUploaded;
return Task.CompletedTask; return Task.CompletedTask;
} }
private void OnCameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e) private async void OnUserLockedOut(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
{ {
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format( CultureInfo.InvariantCulture,
CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserLockedOutWithName"),
_localization.GetLocalizedString("CameraImageUploadedFrom"), e.Argument.Name),
e.Argument.Device.Name), NotificationType.UserLockedOut.ToString(),
Type = NotificationType.CameraImageUploaded.ToString() e.Argument.Id))
}); .ConfigureAwait(false);
} }
private void OnUserLockedOut(object sender, GenericEventArgs<User> e) private async void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
{ {
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserLockedOutWithName"),
e.Argument.Name),
Type = NotificationType.UserLockedOut.ToString(),
UserId = e.Argument.Id
});
}
private void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
{
CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"), _localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"),
e.Provider, e.Provider,
Notifications.NotificationEntryPoint.GetItemName(e.Item)), Notifications.NotificationEntryPoint.GetItemName(e.Item)),
Type = "SubtitleDownloadFailure", "SubtitleDownloadFailure",
Guid.Empty)
{
ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture), ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture),
ShortOverview = e.Exception.Message ShortOverview = e.Exception.Message
}); }).ConfigureAwait(false);
} }
private void OnPlaybackStopped(object sender, PlaybackStopEventArgs e) private async void OnPlaybackStopped(object sender, PlaybackStopEventArgs e)
{ {
var item = e.MediaInfo; var item = e.MediaInfo;
@ -167,20 +148,19 @@ namespace Emby.Server.Implementations.Activity
var user = e.Users[0]; var user = e.Users[0];
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), _localization.GetLocalizedString("UserStoppedPlayingItemWithValues"),
user.Name, user.Name,
GetItemName(item), GetItemName(item),
e.DeviceName), e.DeviceName),
Type = GetPlaybackStoppedNotificationType(item.MediaType), GetPlaybackStoppedNotificationType(item.MediaType),
UserId = user.Id user.Id))
}); .ConfigureAwait(false);
} }
private void OnPlaybackStart(object sender, PlaybackProgressEventArgs e) private async void OnPlaybackStart(object sender, PlaybackProgressEventArgs e)
{ {
var item = e.MediaInfo; var item = e.MediaInfo;
@ -203,17 +183,16 @@ namespace Emby.Server.Implementations.Activity
var user = e.Users.First(); var user = e.Users.First();
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserStartedPlayingItemWithValues"), _localization.GetLocalizedString("UserStartedPlayingItemWithValues"),
user.Name, user.Name,
GetItemName(item), GetItemName(item),
e.DeviceName), e.DeviceName),
Type = GetPlaybackNotificationType(item.MediaType), GetPlaybackNotificationType(item.MediaType),
UserId = user.Id user.Id))
}); .ConfigureAwait(false);
} }
private static string GetItemName(BaseItemDto item) private static string GetItemName(BaseItemDto item)
@ -263,7 +242,7 @@ namespace Emby.Server.Implementations.Activity
return null; return null;
} }
private void OnSessionEnded(object sender, SessionEventArgs e) private async void OnSessionEnded(object sender, SessionEventArgs e)
{ {
var session = e.SessionInfo; var session = e.SessionInfo;
@ -272,110 +251,108 @@ namespace Emby.Server.Implementations.Activity
return; return;
} }
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserOfflineFromDevice"), _localization.GetLocalizedString("UserOfflineFromDevice"),
session.UserName, session.UserName,
session.DeviceName), session.DeviceName),
Type = "SessionEnded", "SessionEnded",
session.UserId)
{
ShortOverview = string.Format( ShortOverview = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("LabelIpAddressValue"), _localization.GetLocalizedString("LabelIpAddressValue"),
session.RemoteEndPoint), session.RemoteEndPoint),
UserId = session.UserId }).ConfigureAwait(false);
});
} }
private void OnAuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e) private async void OnAuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
{ {
var user = e.Argument.User; var user = e.Argument.User;
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("AuthenticationSucceededWithUserName"), _localization.GetLocalizedString("AuthenticationSucceededWithUserName"),
user.Name), user.Name),
Type = "AuthenticationSucceeded", "AuthenticationSucceeded",
user.Id)
{
ShortOverview = string.Format( ShortOverview = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("LabelIpAddressValue"), _localization.GetLocalizedString("LabelIpAddressValue"),
e.Argument.SessionInfo.RemoteEndPoint), e.Argument.SessionInfo.RemoteEndPoint),
UserId = user.Id }).ConfigureAwait(false);
});
} }
private void OnAuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e) private async void OnAuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
{ {
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("FailedLoginAttemptWithUserName"), _localization.GetLocalizedString("FailedLoginAttemptWithUserName"),
e.Argument.Username), e.Argument.Username),
Type = "AuthenticationFailed", "AuthenticationFailed",
Guid.Empty)
{
LogSeverity = LogLevel.Error,
ShortOverview = string.Format( ShortOverview = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("LabelIpAddressValue"), _localization.GetLocalizedString("LabelIpAddressValue"),
e.Argument.RemoteEndPoint), e.Argument.RemoteEndPoint),
Severity = LogLevel.Error }).ConfigureAwait(false);
});
} }
private void OnUserPolicyUpdated(object sender, GenericEventArgs<User> e) private async void OnUserPolicyUpdated(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
{ {
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserPolicyUpdatedWithName"), _localization.GetLocalizedString("UserPolicyUpdatedWithName"),
e.Argument.Name), e.Argument.Name),
Type = "UserPolicyUpdated", "UserPolicyUpdated",
UserId = e.Argument.Id e.Argument.Id))
}); .ConfigureAwait(false);
} }
private void OnUserDeleted(object sender, GenericEventArgs<User> e) private async void OnUserDeleted(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
{ {
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserDeletedWithName"), _localization.GetLocalizedString("UserDeletedWithName"),
e.Argument.Name), e.Argument.Name),
Type = "UserDeleted" "UserDeleted",
}); Guid.Empty))
.ConfigureAwait(false);
} }
private void OnUserPasswordChanged(object sender, GenericEventArgs<User> e) private async void OnUserPasswordChanged(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
{ {
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserPasswordChangedWithName"), _localization.GetLocalizedString("UserPasswordChangedWithName"),
e.Argument.Name), e.Argument.Name),
Type = "UserPasswordChanged", "UserPasswordChanged",
UserId = e.Argument.Id e.Argument.Id))
}); .ConfigureAwait(false);
} }
private void OnUserCreated(object sender, GenericEventArgs<User> e) private async void OnUserCreated(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
{ {
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserCreatedWithName"), _localization.GetLocalizedString("UserCreatedWithName"),
e.Argument.Name), e.Argument.Name),
Type = "UserCreated", "UserCreated",
UserId = e.Argument.Id e.Argument.Id))
}); .ConfigureAwait(false);
} }
private void OnSessionStarted(object sender, SessionEventArgs e) private async void OnSessionStarted(object sender, SessionEventArgs e)
{ {
var session = e.SessionInfo; var session = e.SessionInfo;
@ -384,87 +361,90 @@ namespace Emby.Server.Implementations.Activity
return; return;
} }
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserOnlineFromDevice"), _localization.GetLocalizedString("UserOnlineFromDevice"),
session.UserName, session.UserName,
session.DeviceName), session.DeviceName),
Type = "SessionStarted", "SessionStarted",
session.UserId)
{
ShortOverview = string.Format( ShortOverview = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("LabelIpAddressValue"), _localization.GetLocalizedString("LabelIpAddressValue"),
session.RemoteEndPoint), session.RemoteEndPoint)
UserId = session.UserId }).ConfigureAwait(false);
});
} }
private void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, VersionInfo)> e) private async void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, VersionInfo)> e)
{ {
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("PluginUpdatedWithName"), _localization.GetLocalizedString("PluginUpdatedWithName"),
e.Argument.Item1.Name), e.Argument.Item1.Name),
Type = NotificationType.PluginUpdateInstalled.ToString(), NotificationType.PluginUpdateInstalled.ToString(),
Guid.Empty)
{
ShortOverview = string.Format( ShortOverview = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("VersionNumber"), _localization.GetLocalizedString("VersionNumber"),
e.Argument.Item2.version), e.Argument.Item2.version),
Overview = e.Argument.Item2.changelog Overview = e.Argument.Item2.changelog
}); }).ConfigureAwait(false);
} }
private void OnPluginUninstalled(object sender, GenericEventArgs<IPlugin> e) private async void OnPluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
{ {
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("PluginUninstalledWithName"), _localization.GetLocalizedString("PluginUninstalledWithName"),
e.Argument.Name), e.Argument.Name),
Type = NotificationType.PluginUninstalled.ToString() NotificationType.PluginUninstalled.ToString(),
}); Guid.Empty))
.ConfigureAwait(false);
} }
private void OnPluginInstalled(object sender, GenericEventArgs<VersionInfo> e) private async void OnPluginInstalled(object sender, GenericEventArgs<VersionInfo> e)
{ {
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("PluginInstalledWithName"), _localization.GetLocalizedString("PluginInstalledWithName"),
e.Argument.name), e.Argument.name),
Type = NotificationType.PluginInstalled.ToString(), NotificationType.PluginInstalled.ToString(),
Guid.Empty)
{
ShortOverview = string.Format( ShortOverview = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("VersionNumber"), _localization.GetLocalizedString("VersionNumber"),
e.Argument.version) e.Argument.version)
}); }).ConfigureAwait(false);
} }
private void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e) private async void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e)
{ {
var installationInfo = e.InstallationInfo; var installationInfo = e.InstallationInfo;
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
{ string.Format(
Name = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("NameInstallFailed"), _localization.GetLocalizedString("NameInstallFailed"),
installationInfo.Name), installationInfo.Name),
Type = NotificationType.InstallationFailed.ToString(), NotificationType.InstallationFailed.ToString(),
Guid.Empty)
{
ShortOverview = string.Format( ShortOverview = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("VersionNumber"), _localization.GetLocalizedString("VersionNumber"),
installationInfo.Version), installationInfo.Version),
Overview = e.Exception.Message Overview = e.Exception.Message
}); }).ConfigureAwait(false);
} }
private void OnTaskCompleted(object sender, TaskCompletionEventArgs e) private async void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
{ {
var result = e.Result; var result = e.Result;
var task = e.Task; var task = e.Task;
@ -495,22 +475,20 @@ namespace Emby.Server.Implementations.Activity
vals.Add(e.Result.LongErrorMessage); vals.Add(e.Result.LongErrorMessage);
} }
CreateLogEntry(new ActivityLogEntry await CreateLogEntry(new ActivityLog(
string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name),
NotificationType.TaskFailed.ToString(),
Guid.Empty)
{ {
Name = string.Format( LogSeverity = LogLevel.Error,
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("ScheduledTaskFailedWithName"),
task.Name),
Type = NotificationType.TaskFailed.ToString(),
Overview = string.Join(Environment.NewLine, vals), Overview = string.Join(Environment.NewLine, vals),
ShortOverview = runningTime, ShortOverview = runningTime
Severity = LogLevel.Error }).ConfigureAwait(false);
});
} }
} }
private void CreateLogEntry(ActivityLogEntry entry) private async Task CreateLogEntry(ActivityLog entry)
=> _activityManager.Create(entry); => await _activityManager.CreateAsync(entry).ConfigureAwait(false);
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
@ -537,8 +515,6 @@ namespace Emby.Server.Implementations.Activity
_userManager.UserDeleted -= OnUserDeleted; _userManager.UserDeleted -= OnUserDeleted;
_userManager.UserPolicyUpdated -= OnUserPolicyUpdated; _userManager.UserPolicyUpdated -= OnUserPolicyUpdated;
_userManager.UserLockedOut -= OnUserLockedOut; _userManager.UserLockedOut -= OnUserLockedOut;
_deviceManager.CameraImageUploaded -= OnCameraImageUploaded;
} }
/// <summary> /// <summary>
@ -566,7 +542,7 @@ namespace Emby.Server.Implementations.Activity
{ {
int months = days / DaysInMonth; int months = days / DaysInMonth;
values.Add(CreateValueString(months, "month")); values.Add(CreateValueString(months, "month"));
days %= DaysInMonth; days = days % DaysInMonth;
} }
// Number of days // Number of days

View File

@ -1,70 +0,0 @@
using System;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Activity
{
/// <summary>
/// The activity log manager.
/// </summary>
public class ActivityManager : IActivityManager
{
private readonly IActivityRepository _repo;
private readonly IUserManager _userManager;
/// <summary>
/// Initializes a new instance of the <see cref="ActivityManager"/> class.
/// </summary>
/// <param name="repo">The activity repository.</param>
/// <param name="userManager">The user manager.</param>
public ActivityManager(IActivityRepository repo, IUserManager userManager)
{
_repo = repo;
_userManager = userManager;
}
/// <inheritdoc />
public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
public void Create(ActivityLogEntry entry)
{
entry.Date = DateTime.UtcNow;
_repo.Create(entry);
EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(entry));
}
/// <inheritdoc />
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
{
var result = _repo.GetActivityLogEntries(minDate, hasUserId, startIndex, limit);
foreach (var item in result.Items)
{
if (item.UserId == Guid.Empty)
{
continue;
}
var user = _userManager.GetUserById(item.UserId);
if (user != null)
{
var dto = _userManager.GetUserDto(user);
item.UserPrimaryImageTag = dto.PrimaryImageTag;
}
}
return result;
}
/// <inheritdoc />
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit)
{
return GetActivityLogEntries(minDate, null, startIndex, limit);
}
}
}

View File

@ -1,308 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Emby.Server.Implementations.Data;
using MediaBrowser.Controller;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging;
using SQLitePCL.pretty;
namespace Emby.Server.Implementations.Activity
{
/// <summary>
/// The activity log repository.
/// </summary>
public class ActivityRepository : BaseSqliteRepository, IActivityRepository
{
private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog";
private readonly IFileSystem _fileSystem;
/// <summary>
/// Initializes a new instance of the <see cref="ActivityRepository"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="appPaths">The server application paths.</param>
/// <param name="fileSystem">The filesystem.</param>
public ActivityRepository(ILogger<ActivityRepository> logger, IServerApplicationPaths appPaths, IFileSystem fileSystem)
: base(logger)
{
DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db");
_fileSystem = fileSystem;
}
/// <summary>
/// Initializes the <see cref="ActivityRepository"/>.
/// </summary>
public void Initialize()
{
try
{
InitializeInternal();
}
catch (Exception ex)
{
Logger.LogError(ex, "Error loading database file. Will reset and retry.");
_fileSystem.DeleteFile(DbFilePath);
InitializeInternal();
}
}
private void InitializeInternal()
{
using var connection = GetConnection();
connection.RunQueries(new[]
{
"create table if not exists ActivityLog (Id INTEGER PRIMARY KEY, Name TEXT NOT NULL, Overview TEXT, ShortOverview TEXT, Type TEXT NOT NULL, ItemId TEXT, UserId TEXT, DateCreated DATETIME NOT NULL, LogSeverity TEXT NOT NULL)",
"drop index if exists idx_ActivityLogEntries"
});
TryMigrate(connection);
}
private void TryMigrate(ManagedConnection connection)
{
try
{
if (TableExists(connection, "ActivityLogEntries"))
{
connection.RunQueries(new[]
{
"INSERT INTO ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) SELECT Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity FROM ActivityLogEntries",
"drop table if exists ActivityLogEntries"
});
}
}
catch (Exception ex)
{
Logger.LogError(ex, "Error migrating activity log database");
}
}
/// <inheritdoc />
public void Create(ActivityLogEntry entry)
{
if (entry == null)
{
throw new ArgumentNullException(nameof(entry));
}
using var connection = GetConnection();
connection.RunInTransaction(db =>
{
using var statement = db.PrepareStatement("insert into ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)");
statement.TryBind("@Name", entry.Name);
statement.TryBind("@Overview", entry.Overview);
statement.TryBind("@ShortOverview", entry.ShortOverview);
statement.TryBind("@Type", entry.Type);
statement.TryBind("@ItemId", entry.ItemId);
if (entry.UserId.Equals(Guid.Empty))
{
statement.TryBindNull("@UserId");
}
else
{
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
}
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
statement.TryBind("@LogSeverity", entry.Severity.ToString());
statement.MoveNext();
}, TransactionMode);
}
/// <summary>
/// Adds the provided <see cref="ActivityLogEntry"/> to this repository.
/// </summary>
/// <param name="entry">The activity log entry.</param>
/// <exception cref="ArgumentNullException">If entry is null.</exception>
public void Update(ActivityLogEntry entry)
{
if (entry == null)
{
throw new ArgumentNullException(nameof(entry));
}
using var connection = GetConnection();
connection.RunInTransaction(db =>
{
using var statement = db.PrepareStatement("Update ActivityLog set Name=@Name,Overview=@Overview,ShortOverview=@ShortOverview,Type=@Type,ItemId=@ItemId,UserId=@UserId,DateCreated=@DateCreated,LogSeverity=@LogSeverity where Id=@Id");
statement.TryBind("@Id", entry.Id);
statement.TryBind("@Name", entry.Name);
statement.TryBind("@Overview", entry.Overview);
statement.TryBind("@ShortOverview", entry.ShortOverview);
statement.TryBind("@Type", entry.Type);
statement.TryBind("@ItemId", entry.ItemId);
if (entry.UserId.Equals(Guid.Empty))
{
statement.TryBindNull("@UserId");
}
else
{
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
}
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
statement.TryBind("@LogSeverity", entry.Severity.ToString());
statement.MoveNext();
}, TransactionMode);
}
/// <inheritdoc />
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
{
var commandText = BaseActivitySelectText;
var whereClauses = new List<string>();
if (minDate.HasValue)
{
whereClauses.Add("DateCreated>=@DateCreated");
}
if (hasUserId.HasValue)
{
whereClauses.Add(hasUserId.Value ? "UserId not null" : "UserId is null");
}
var whereTextWithoutPaging = whereClauses.Count == 0 ?
string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray());
if (startIndex.HasValue && startIndex.Value > 0)
{
var pagingWhereText = whereClauses.Count == 0 ?
string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray());
whereClauses.Add(
string.Format(
CultureInfo.InvariantCulture,
"Id NOT IN (SELECT Id FROM ActivityLog {0} ORDER BY DateCreated DESC LIMIT {1})",
pagingWhereText,
startIndex.Value));
}
var whereText = whereClauses.Count == 0 ?
string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray());
commandText += whereText;
commandText += " ORDER BY DateCreated DESC";
if (limit.HasValue)
{
commandText += " LIMIT " + limit.Value.ToString(CultureInfo.InvariantCulture);
}
var statementTexts = new[]
{
commandText,
"select count (Id) from ActivityLog" + whereTextWithoutPaging
};
var list = new List<ActivityLogEntry>();
var result = new QueryResult<ActivityLogEntry>();
using var connection = GetConnection(true);
connection.RunInTransaction(
db =>
{
var statements = PrepareAll(db, statementTexts).ToList();
using (var statement = statements[0])
{
if (minDate.HasValue)
{
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
}
list.AddRange(statement.ExecuteQuery().Select(GetEntry));
}
using (var statement = statements[1])
{
if (minDate.HasValue)
{
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
}
result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
}
},
ReadTransactionMode);
result.Items = list;
return result;
}
private static ActivityLogEntry GetEntry(IReadOnlyList<IResultSetValue> reader)
{
var index = 0;
var info = new ActivityLogEntry
{
Id = reader[index].ToInt64()
};
index++;
if (reader[index].SQLiteType != SQLiteType.Null)
{
info.Name = reader[index].ToString();
}
index++;
if (reader[index].SQLiteType != SQLiteType.Null)
{
info.Overview = reader[index].ToString();
}
index++;
if (reader[index].SQLiteType != SQLiteType.Null)
{
info.ShortOverview = reader[index].ToString();
}
index++;
if (reader[index].SQLiteType != SQLiteType.Null)
{
info.Type = reader[index].ToString();
}
index++;
if (reader[index].SQLiteType != SQLiteType.Null)
{
info.ItemId = reader[index].ToString();
}
index++;
if (reader[index].SQLiteType != SQLiteType.Null)
{
info.UserId = new Guid(reader[index].ToString());
}
index++;
info.Date = reader[index].ReadDateTime();
index++;
if (reader[index].SQLiteType != SQLiteType.Null)
{
info.Severity = Enum.Parse<LogLevel>(reader[index].ToString(), true);
}
return info;
}
}
}

View File

@ -22,7 +22,6 @@ using Emby.Dlna.Ssdp;
using Emby.Drawing; using Emby.Drawing;
using Emby.Notifications; using Emby.Notifications;
using Emby.Photos; using Emby.Photos;
using Emby.Server.Implementations.Activity;
using Emby.Server.Implementations.Archiving; using Emby.Server.Implementations.Archiving;
using Emby.Server.Implementations.Channels; using Emby.Server.Implementations.Channels;
using Emby.Server.Implementations.Collections; using Emby.Server.Implementations.Collections;
@ -81,7 +80,6 @@ using MediaBrowser.Controller.Subtitles;
using MediaBrowser.Controller.TV; using MediaBrowser.Controller.TV;
using MediaBrowser.LocalMetadata.Savers; using MediaBrowser.LocalMetadata.Savers;
using MediaBrowser.MediaEncoding.BdInfo; using MediaBrowser.MediaEncoding.BdInfo;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
@ -628,9 +626,6 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>(); serviceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
serviceCollection.AddSingleton<IActivityRepository, ActivityRepository>();
serviceCollection.AddSingleton<IActivityManager, ActivityManager>();
serviceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>(); serviceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>();
serviceCollection.AddSingleton<ISessionContext, SessionContext>(); serviceCollection.AddSingleton<ISessionContext, SessionContext>();
@ -661,7 +656,6 @@ namespace Emby.Server.Implementations
((SqliteDisplayPreferencesRepository)Resolve<IDisplayPreferencesRepository>()).Initialize(); ((SqliteDisplayPreferencesRepository)Resolve<IDisplayPreferencesRepository>()).Initialize();
((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize(); ((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
((SqliteUserRepository)Resolve<IUserRepository>()).Initialize(); ((SqliteUserRepository)Resolve<IUserRepository>()).Initialize();
((ActivityRepository)Resolve<IActivityRepository>()).Initialize();
SetStaticProperties(); SetStaticProperties();

View File

@ -193,12 +193,6 @@ namespace Emby.Server.Implementations.Configuration
changed = true; changed = true;
} }
if (!config.CameraUploadUpgraded)
{
config.CameraUploadUpgraded = true;
changed = true;
}
if (!config.CollectionsUpgraded) if (!config.CollectionsUpgraded)
{ {
config.CollectionsUpgraded = true; config.CollectionsUpgraded = true;

View File

@ -5,27 +5,18 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Security;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Devices; using MediaBrowser.Model.Devices;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Events; using MediaBrowser.Model.Events;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Session; using MediaBrowser.Model.Session;
using MediaBrowser.Model.Users; using MediaBrowser.Model.Users;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Devices namespace Emby.Server.Implementations.Devices
{ {
@ -33,38 +24,23 @@ namespace Emby.Server.Implementations.Devices
{ {
private readonly IJsonSerializer _json; private readonly IJsonSerializer _json;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly IFileSystem _fileSystem;
private readonly ILibraryMonitor _libraryMonitor;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localizationManager;
private readonly IAuthenticationRepository _authRepo; private readonly IAuthenticationRepository _authRepo;
private readonly Dictionary<string, ClientCapabilities> _capabilitiesCache; private readonly Dictionary<string, ClientCapabilities> _capabilitiesCache;
public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated; public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
public event EventHandler<GenericEventArgs<CameraImageUploadInfo>> CameraImageUploaded;
private readonly object _cameraUploadSyncLock = new object();
private readonly object _capabilitiesSyncLock = new object(); private readonly object _capabilitiesSyncLock = new object();
public DeviceManager( public DeviceManager(
IAuthenticationRepository authRepo, IAuthenticationRepository authRepo,
IJsonSerializer json, IJsonSerializer json,
ILibraryManager libraryManager,
ILocalizationManager localizationManager,
IUserManager userManager, IUserManager userManager,
IFileSystem fileSystem,
ILibraryMonitor libraryMonitor,
IServerConfigurationManager config) IServerConfigurationManager config)
{ {
_json = json; _json = json;
_userManager = userManager; _userManager = userManager;
_fileSystem = fileSystem;
_libraryMonitor = libraryMonitor;
_config = config; _config = config;
_libraryManager = libraryManager;
_localizationManager = localizationManager;
_authRepo = authRepo; _authRepo = authRepo;
_capabilitiesCache = new Dictionary<string, ClientCapabilities>(StringComparer.OrdinalIgnoreCase); _capabilitiesCache = new Dictionary<string, ClientCapabilities>(StringComparer.OrdinalIgnoreCase);
} }
@ -194,172 +170,6 @@ namespace Emby.Server.Implementations.Devices
return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N", CultureInfo.InvariantCulture)); return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N", CultureInfo.InvariantCulture));
} }
public ContentUploadHistory GetCameraUploadHistory(string deviceId)
{
var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json");
lock (_cameraUploadSyncLock)
{
try
{
return _json.DeserializeFromFile<ContentUploadHistory>(path);
}
catch (IOException)
{
return new ContentUploadHistory
{
DeviceId = deviceId
};
}
}
}
public async Task AcceptCameraUpload(string deviceId, Stream stream, LocalFileInfo file)
{
var device = GetDevice(deviceId, false);
var uploadPathInfo = GetUploadPath(device);
var path = uploadPathInfo.Item1;
if (!string.IsNullOrWhiteSpace(file.Album))
{
path = Path.Combine(path, _fileSystem.GetValidFilename(file.Album));
}
path = Path.Combine(path, file.Name);
path = Path.ChangeExtension(path, MimeTypes.ToExtension(file.MimeType) ?? "jpg");
Directory.CreateDirectory(Path.GetDirectoryName(path));
await EnsureLibraryFolder(uploadPathInfo.Item2, uploadPathInfo.Item3).ConfigureAwait(false);
_libraryMonitor.ReportFileSystemChangeBeginning(path);
try
{
using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
{
await stream.CopyToAsync(fs).ConfigureAwait(false);
}
AddCameraUpload(deviceId, file);
}
finally
{
_libraryMonitor.ReportFileSystemChangeComplete(path, true);
}
if (CameraImageUploaded != null)
{
CameraImageUploaded?.Invoke(this, new GenericEventArgs<CameraImageUploadInfo>
{
Argument = new CameraImageUploadInfo
{
Device = device,
FileInfo = file
}
});
}
}
private void AddCameraUpload(string deviceId, LocalFileInfo file)
{
var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json");
Directory.CreateDirectory(Path.GetDirectoryName(path));
lock (_cameraUploadSyncLock)
{
ContentUploadHistory history;
try
{
history = _json.DeserializeFromFile<ContentUploadHistory>(path);
}
catch (IOException)
{
history = new ContentUploadHistory
{
DeviceId = deviceId
};
}
history.DeviceId = deviceId;
var list = history.FilesUploaded.ToList();
list.Add(file);
history.FilesUploaded = list.ToArray();
_json.SerializeToFile(history, path);
}
}
internal Task EnsureLibraryFolder(string path, string name)
{
var existingFolders = _libraryManager
.RootFolder
.Children
.OfType<Folder>()
.Where(i => _fileSystem.AreEqual(path, i.Path) || _fileSystem.ContainsSubPath(i.Path, path))
.ToList();
if (existingFolders.Count > 0)
{
return Task.CompletedTask;
}
Directory.CreateDirectory(path);
var libraryOptions = new LibraryOptions
{
PathInfos = new[] { new MediaPathInfo { Path = path } },
EnablePhotos = true,
EnableRealtimeMonitor = false,
SaveLocalMetadata = true
};
if (string.IsNullOrWhiteSpace(name))
{
name = _localizationManager.GetLocalizedString("HeaderCameraUploads");
}
return _libraryManager.AddVirtualFolder(name, CollectionType.HomeVideos, libraryOptions, true);
}
private Tuple<string, string, string> GetUploadPath(DeviceInfo device)
{
var config = _config.GetUploadOptions();
var path = config.CameraUploadPath;
if (string.IsNullOrWhiteSpace(path))
{
path = DefaultCameraUploadsPath;
}
var topLibraryPath = path;
if (config.EnableCameraUploadSubfolders)
{
path = Path.Combine(path, _fileSystem.GetValidFilename(device.Name));
}
return new Tuple<string, string, string>(path, topLibraryPath, null);
}
internal string GetUploadsPath()
{
var config = _config.GetUploadOptions();
var path = config.CameraUploadPath;
if (string.IsNullOrWhiteSpace(path))
{
path = DefaultCameraUploadsPath;
}
return path;
}
private string DefaultCameraUploadsPath => Path.Combine(_config.CommonApplicationPaths.DataPath, "camerauploads");
public bool CanAccessDevice(User user, string deviceId) public bool CanAccessDevice(User user, string deviceId)
{ {
if (user == null) if (user == null)
@ -399,102 +209,4 @@ namespace Emby.Server.Implementations.Devices
return policy.EnabledDevices.Contains(id, StringComparer.OrdinalIgnoreCase); return policy.EnabledDevices.Contains(id, StringComparer.OrdinalIgnoreCase);
} }
} }
public class DeviceManagerEntryPoint : IServerEntryPoint
{
private readonly DeviceManager _deviceManager;
private readonly IServerConfigurationManager _config;
private ILogger _logger;
public DeviceManagerEntryPoint(
IDeviceManager deviceManager,
IServerConfigurationManager config,
ILogger<DeviceManagerEntryPoint> logger)
{
_deviceManager = (DeviceManager)deviceManager;
_config = config;
_logger = logger;
}
public async Task RunAsync()
{
if (!_config.Configuration.CameraUploadUpgraded && _config.Configuration.IsStartupWizardCompleted)
{
var path = _deviceManager.GetUploadsPath();
if (Directory.Exists(path))
{
try
{
await _deviceManager.EnsureLibraryFolder(path, null).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating camera uploads library");
}
_config.Configuration.CameraUploadUpgraded = true;
_config.SaveConfiguration();
}
}
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: dispose managed state (managed objects).
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
disposedValue = true;
}
}
// TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
// ~DeviceManagerEntryPoint() {
// // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
// Dispose(false);
// }
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
// TODO: uncomment the following line if the finalizer is overridden above.
// GC.SuppressFinalize(this);
}
#endregion
}
public class DevicesConfigStore : IConfigurationFactory
{
public IEnumerable<ConfigurationStore> GetConfigurations()
{
return new ConfigurationStore[]
{
new ConfigurationStore
{
Key = "devices",
ConfigurationType = typeof(DevicesOptions)
}
};
}
}
public static class UploadConfigExtension
{
public static DevicesOptions GetUploadOptions(this IConfigurationManager config)
{
return config.GetConfiguration<DevicesOptions>("devices");
}
}
} }

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<!-- ProjectGuid is only included as a requirement for SonarQube analysis --> <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
<PropertyGroup> <PropertyGroup>

View File

@ -28,6 +28,7 @@ using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using ServiceStack.Text.Jsv; using ServiceStack.Text.Jsv;
namespace Emby.Server.Implementations.HttpServer namespace Emby.Server.Implementations.HttpServer
@ -454,9 +455,10 @@ namespace Emby.Server.Implementations.HttpServer
if (string.Equals(httpReq.Verb, "OPTIONS", StringComparison.OrdinalIgnoreCase)) if (string.Equals(httpReq.Verb, "OPTIONS", StringComparison.OrdinalIgnoreCase))
{ {
httpRes.StatusCode = 200; httpRes.StatusCode = 200;
httpRes.Headers.Add("Access-Control-Allow-Origin", "*"); foreach(var (key, value) in GetDefaultCorsHeaders(httpReq))
httpRes.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); {
httpRes.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization"); httpRes.Headers.Add(key, value);
}
httpRes.ContentType = "text/plain"; httpRes.ContentType = "text/plain";
await httpRes.WriteAsync(string.Empty, cancellationToken).ConfigureAwait(false); await httpRes.WriteAsync(string.Empty, cancellationToken).ConfigureAwait(false);
return; return;
@ -576,6 +578,31 @@ namespace Emby.Server.Implementations.HttpServer
} }
} }
/// <summary>
/// Get the default CORS headers
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
public IDictionary<string, string> GetDefaultCorsHeaders(IRequest req)
{
var origin = req.Headers["Origin"];
if (origin == StringValues.Empty)
{
origin = req.Headers["Host"];
if (origin == StringValues.Empty)
{
origin = "*";
}
}
var headers = new Dictionary<string, string>();
headers.Add("Access-Control-Allow-Origin", origin);
headers.Add("Access-Control-Allow-Credentials", "true");
headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization, Cookie");
return headers;
}
// Entry point for HttpListener // Entry point for HttpListener
public ServiceHandler GetServiceHandler(IHttpRequest httpReq) public ServiceHandler GetServiceHandler(IHttpRequest httpReq)
{ {
@ -622,7 +649,7 @@ namespace Emby.Server.Implementations.HttpServer
ResponseFilters = new Action<IRequest, HttpResponse, object>[] ResponseFilters = new Action<IRequest, HttpResponse, object>[]
{ {
new ResponseFilter(_logger).FilterResponse new ResponseFilter(this, _logger).FilterResponse
}; };
} }

View File

@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Text; using System.Text;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Services; using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -13,14 +15,17 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary> /// </summary>
public class ResponseFilter public class ResponseFilter
{ {
private readonly IHttpServer _server;
private readonly ILogger _logger; private readonly ILogger _logger;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ResponseFilter"/> class. /// Initializes a new instance of the <see cref="ResponseFilter"/> class.
/// </summary> /// </summary>
/// <param name="server">The HTTP server.</param>
/// <param name="logger">The logger.</param> /// <param name="logger">The logger.</param>
public ResponseFilter(ILogger logger) public ResponseFilter(IHttpServer server, ILogger logger)
{ {
_server = server;
_logger = logger; _logger = logger;
} }
@ -32,10 +37,16 @@ namespace Emby.Server.Implementations.HttpServer
/// <param name="dto">The dto.</param> /// <param name="dto">The dto.</param>
public void FilterResponse(IRequest req, HttpResponse res, object dto) public void FilterResponse(IRequest req, HttpResponse res, object dto)
{ {
foreach(var (key, value) in _server.GetDefaultCorsHeaders(req))
{
res.Headers.Add(key, value);
}
// Try to prevent compatibility view // Try to prevent compatibility view
res.Headers.Add("Access-Control-Allow-Headers", "Accept, Accept-Language, Authorization, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, Content-Type, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, X-Emby-Authorization"); res.Headers["Access-Control-Allow-Headers"] = ("Accept, Accept-Language, Authorization, Cache-Control, " +
res.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); "Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, " +
res.Headers.Add("Access-Control-Allow-Origin", "*"); "Content-Type, Cookie, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, " +
"Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, " +
"X-Emby-Authorization");
if (dto is Exception exception) if (dto is Exception exception)
{ {

View File

@ -97,5 +97,9 @@
"TasksApplicationCategory": "Applikasjon", "TasksApplicationCategory": "Applikasjon",
"TasksLibraryCategory": "Bibliotek", "TasksLibraryCategory": "Bibliotek",
"TasksMaintenanceCategory": "Vedlikehold", "TasksMaintenanceCategory": "Vedlikehold",
"TaskCleanCache": "Tøm buffer katalog" "TaskCleanCache": "Tøm buffer katalog",
"TaskRefreshLibrary": "Skann mediebibliotek",
"TaskRefreshChapterImagesDescription": "Lager forhåndsvisningsbilder for videoer som har kapitler.",
"TaskRefreshChapterImages": "Trekk ut Kapittelbilder",
"TaskCleanCacheDescription": "Sletter mellomlagrede filer som ikke lengre trengs av systemet."
} }

View File

@ -0,0 +1,154 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Data.Entities
{
/// <summary>
/// An entity referencing an activity log entry.
/// </summary>
public partial class ActivityLog : ISavingChanges
{
/// <summary>
/// Initializes a new instance of the <see cref="ActivityLog"/> class.
/// Public constructor with required data.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="type">The type.</param>
/// <param name="userId">The user id.</param>
public ActivityLog(string name, string type, Guid userId)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentNullException(nameof(name));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentNullException(nameof(type));
}
this.Name = name;
this.Type = type;
this.UserId = userId;
this.DateCreated = DateTime.UtcNow;
this.LogSeverity = LogLevel.Trace;
Init();
}
/// <summary>
/// Initializes a new instance of the <see cref="ActivityLog"/> class.
/// Default constructor. Protected due to required properties, but present because EF needs it.
/// </summary>
protected ActivityLog()
{
Init();
}
/// <summary>
/// Static create function (for use in LINQ queries, etc.)
/// </summary>
/// <param name="name">The name.</param>
/// <param name="type">The type.</param>
/// <param name="userId">The user's id.</param>
/// <returns>The new <see cref="ActivityLog"/> instance.</returns>
public static ActivityLog Create(string name, string type, Guid userId)
{
return new ActivityLog(name, type, userId);
}
/*************************************************************************
* Properties
*************************************************************************/
/// <summary>
/// Gets or sets the identity of this instance.
/// This is the key in the backing database.
/// </summary>
[Key]
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; }
/// <summary>
/// Gets or sets the name.
/// Required, Max length = 512.
/// </summary>
[Required]
[MaxLength(512)]
[StringLength(512)]
public string Name { get; set; }
/// <summary>
/// Gets or sets the overview.
/// Max length = 512.
/// </summary>
[MaxLength(512)]
[StringLength(512)]
public string Overview { get; set; }
/// <summary>
/// Gets or sets the short overview.
/// Max length = 512.
/// </summary>
[MaxLength(512)]
[StringLength(512)]
public string ShortOverview { get; set; }
/// <summary>
/// Gets or sets the type.
/// Required, Max length = 256.
/// </summary>
[Required]
[MaxLength(256)]
[StringLength(256)]
public string Type { get; set; }
/// <summary>
/// Gets or sets the user id.
/// Required.
/// </summary>
[Required]
public Guid UserId { get; set; }
/// <summary>
/// Gets or sets the item id.
/// Max length = 256.
/// </summary>
[MaxLength(256)]
[StringLength(256)]
public string ItemId { get; set; }
/// <summary>
/// Gets or sets the date created. This should be in UTC.
/// Required.
/// </summary>
[Required]
public DateTime DateCreated { get; set; }
/// <summary>
/// Gets or sets the log severity. Default is <see cref="LogLevel.Trace"/>.
/// Required.
/// </summary>
[Required]
public LogLevel LogSeverity { get; set; }
/// <summary>
/// Gets or sets the row version.
/// Required, ConcurrencyToken.
/// </summary>
[ConcurrencyCheck]
[Required]
public uint RowVersion { get; set; }
partial void Init();
/// <inheritdoc />
public void OnSavingChanges()
{
RowVersion++;
}
}
}

View File

@ -1,12 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<!-- Code analysers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="2.2.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.3" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,102 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Querying;
namespace Jellyfin.Server.Implementations.Activity
{
/// <summary>
/// Manages the storage and retrieval of <see cref="ActivityLog"/> instances.
/// </summary>
public class ActivityManager : IActivityManager
{
private readonly JellyfinDbProvider _provider;
/// <summary>
/// Initializes a new instance of the <see cref="ActivityManager"/> class.
/// </summary>
/// <param name="provider">The Jellyfin database provider.</param>
public ActivityManager(JellyfinDbProvider provider)
{
_provider = provider;
}
/// <inheritdoc/>
public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
/// <inheritdoc/>
public void Create(ActivityLog entry)
{
using var dbContext = _provider.CreateContext();
dbContext.ActivityLogs.Add(entry);
dbContext.SaveChanges();
EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(ConvertToOldModel(entry)));
}
/// <inheritdoc/>
public async Task CreateAsync(ActivityLog entry)
{
using var dbContext = _provider.CreateContext();
await dbContext.ActivityLogs.AddAsync(entry);
await dbContext.SaveChangesAsync().ConfigureAwait(false);
EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(ConvertToOldModel(entry)));
}
/// <inheritdoc/>
public QueryResult<ActivityLogEntry> GetPagedResult(
Func<IQueryable<ActivityLog>, IQueryable<ActivityLog>> func,
int? startIndex,
int? limit)
{
using var dbContext = _provider.CreateContext();
var query = func(dbContext.ActivityLogs.OrderByDescending(entry => entry.DateCreated));
if (startIndex.HasValue)
{
query = query.Skip(startIndex.Value);
}
if (limit.HasValue)
{
query = query.Take(limit.Value);
}
// This converts the objects from the new database model to the old for compatibility with the existing API.
var list = query.Select(ConvertToOldModel).ToList();
return new QueryResult<ActivityLogEntry>
{
Items = list,
TotalRecordCount = func(dbContext.ActivityLogs).Count()
};
}
/// <inheritdoc/>
public QueryResult<ActivityLogEntry> GetPagedResult(int? startIndex, int? limit)
{
return GetPagedResult(logs => logs, startIndex, limit);
}
private static ActivityLogEntry ConvertToOldModel(ActivityLog entry)
{
return new ActivityLogEntry
{
Id = entry.Id,
Name = entry.Name,
Overview = entry.Overview,
ShortOverview = entry.ShortOverview,
Type = entry.Type,
ItemId = entry.ItemId,
UserId = entry.UserId,
Date = entry.DateCreated,
Severity = entry.LogSeverity
};
}
}
}

View File

@ -25,6 +25,14 @@
<Compile Remove="Migrations\20200430214405_InitialSchema.Designer.cs" /> <Compile Remove="Migrations\20200430214405_InitialSchema.Designer.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Jellyfin.Data\Jellyfin.Data.csproj" /> <ProjectReference Include="..\Jellyfin.Data\Jellyfin.Data.csproj" />
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" /> <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />

View File

@ -15,6 +15,7 @@ namespace Jellyfin.Server.Implementations
/// <inheritdoc/> /// <inheritdoc/>
public partial class JellyfinDb : DbContext public partial class JellyfinDb : DbContext
{ {
public virtual DbSet<ActivityLog> ActivityLogs { 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; }
public virtual DbSet<BookMetadata> BookMetadata { get; set; } public virtual DbSet<BookMetadata> BookMetadata { get; set; }
@ -49,6 +50,7 @@ namespace Jellyfin.Server.Implementations
public virtual DbSet<Preference> Preferences { get; set; } public virtual DbSet<Preference> Preferences { get; set; }
public virtual DbSet<ProviderMapping> ProviderMappings { 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>
/// Repository for global::Jellyfin.Data.Entities.RatingSource - This is the entity to /// Repository for global::Jellyfin.Data.Entities.RatingSource - This is the entity to
/// store review ratings, not age ratings /// store review ratings, not age ratings
@ -93,8 +95,10 @@ namespace Jellyfin.Server.Implementations
modelBuilder.HasDefaultSchema("jellyfin"); modelBuilder.HasDefaultSchema("jellyfin");
/*modelBuilder.Entity<Artwork>().HasIndex(t => t.Kind); /*modelBuilder.Entity<Artwork>().HasIndex(t => t.Kind);
modelBuilder.Entity<Genre>().HasIndex(t => t.Name) modelBuilder.Entity<Genre>().HasIndex(t => t.Name)
.IsUnique(); .IsUnique();
modelBuilder.Entity<LibraryItem>().HasIndex(t => t.UrlId) modelBuilder.Entity<LibraryItem>().HasIndex(t => t.UrlId)
.IsUnique();*/ .IsUnique();*/
@ -103,9 +107,10 @@ namespace Jellyfin.Server.Implementations
public override int SaveChanges() public override int SaveChanges()
{ {
foreach (var entity in ChangeTracker.Entries().Where(e => e.State == EntityState.Modified)) foreach (var saveEntity in ChangeTracker.Entries()
.Where(e => e.State == EntityState.Modified)
.OfType<ISavingChanges>())
{ {
var saveEntity = entity.Entity as ISavingChanges;
saveEntity.OnSavingChanges(); saveEntity.OnSavingChanges();
} }

View File

@ -0,0 +1,33 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace Jellyfin.Server.Implementations
{
/// <summary>
/// Factory class for generating new <see cref="JellyfinDb"/> instances.
/// </summary>
public class JellyfinDbProvider
{
private readonly IServiceProvider _serviceProvider;
/// <summary>
/// Initializes a new instance of the <see cref="JellyfinDbProvider"/> class.
/// </summary>
/// <param name="serviceProvider">The application's service provider.</param>
public JellyfinDbProvider(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
serviceProvider.GetService<JellyfinDb>().Database.Migrate();
}
/// <summary>
/// Creates a new <see cref="JellyfinDb"/> context.
/// </summary>
/// <returns>The newly created context.</returns>
public JellyfinDb CreateContext()
{
return _serviceProvider.GetRequiredService<JellyfinDb>();
}
}
}

View File

@ -0,0 +1,72 @@
#pragma warning disable CS1591
// <auto-generated />
using System;
using Jellyfin.Server.Implementations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Jellyfin.Server.Implementations.Migrations
{
[DbContext(typeof(JellyfinDb))]
[Migration("20200514181226_AddActivityLog")]
partial class AddActivityLog
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("jellyfin")
.HasAnnotation("ProductVersion", "3.1.3");
modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("DateCreated")
.HasColumnType("TEXT");
b.Property<string>("ItemId")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<int>("LogSeverity")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(512);
b.Property<string>("Overview")
.HasColumnType("TEXT")
.HasMaxLength(512);
b.Property<uint>("RowVersion")
.IsConcurrencyToken()
.HasColumnType("INTEGER");
b.Property<string>("ShortOverview")
.HasColumnType("TEXT")
.HasMaxLength(512);
b.Property<string>("Type")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("ActivityLogs");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,46 @@
#pragma warning disable CS1591
#pragma warning disable SA1601
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Jellyfin.Server.Implementations.Migrations
{
public partial class AddActivityLog : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.EnsureSchema(
name: "jellyfin");
migrationBuilder.CreateTable(
name: "ActivityLogs",
schema: "jellyfin",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Name = table.Column<string>(maxLength: 512, nullable: false),
Overview = table.Column<string>(maxLength: 512, nullable: true),
ShortOverview = table.Column<string>(maxLength: 512, nullable: true),
Type = table.Column<string>(maxLength: 256, nullable: false),
UserId = table.Column<Guid>(nullable: false),
ItemId = table.Column<string>(maxLength: 256, nullable: true),
DateCreated = table.Column<DateTime>(nullable: false),
LogSeverity = table.Column<int>(nullable: false),
RowVersion = table.Column<uint>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ActivityLogs", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ActivityLogs",
schema: "jellyfin");
}
}
}

View File

@ -0,0 +1,20 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
namespace Jellyfin.Server.Implementations.Migrations
{
/// <summary>
/// The design time factory for <see cref="JellyfinDb"/>.
/// This is only used for the creation of migrations and not during runtime.
/// </summary>
internal class DesignTimeJellyfinDbFactory : IDesignTimeDbContextFactory<JellyfinDb>
{
public JellyfinDb CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<JellyfinDb>();
optionsBuilder.UseSqlite("Data Source=jellyfin.db");
return new JellyfinDb(optionsBuilder.Options);
}
}
}

View File

@ -0,0 +1,66 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace Jellyfin.Server.Implementations.Migrations
{
[DbContext(typeof(JellyfinDb))]
partial class JellyfinDbModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("jellyfin")
.HasAnnotation("ProductVersion", "3.1.3");
modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("DateCreated")
.HasColumnType("TEXT");
b.Property<string>("ItemId")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<int>("LogSeverity")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(512);
b.Property<string>("Overview")
.HasColumnType("TEXT")
.HasMaxLength(512);
b.Property<uint>("RowVersion")
.IsConcurrencyToken()
.HasColumnType("INTEGER");
b.Property<string>("ShortOverview")
.HasColumnType("TEXT")
.HasMaxLength(512);
b.Property<string>("Type")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("ActivityLogs");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -1,12 +1,17 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Reflection; using System.Reflection;
using Emby.Drawing; using Emby.Drawing;
using Emby.Server.Implementations; using Emby.Server.Implementations;
using Jellyfin.Drawing.Skia; using Jellyfin.Drawing.Skia;
using Jellyfin.Server.Implementations;
using Jellyfin.Server.Implementations.Activity;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -56,6 +61,15 @@ namespace Jellyfin.Server
Logger.LogWarning($"Skia not available. Will fallback to {nameof(NullImageEncoder)}."); Logger.LogWarning($"Skia not available. Will fallback to {nameof(NullImageEncoder)}.");
} }
// TODO: Set up scoping and use AddDbContextPool
serviceCollection.AddDbContext<JellyfinDb>(
options => options.UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"),
ServiceLifetime.Transient);
serviceCollection.AddSingleton<JellyfinDbProvider>();
serviceCollection.AddSingleton<IActivityManager, ActivityManager>();
base.RegisterServices(serviceCollection); base.RegisterServices(serviceCollection);
} }

View File

@ -60,6 +60,7 @@
<ProjectReference Include="..\Emby.Drawing\Emby.Drawing.csproj" /> <ProjectReference Include="..\Emby.Drawing\Emby.Drawing.csproj" />
<ProjectReference Include="..\Emby.Server.Implementations\Emby.Server.Implementations.csproj" /> <ProjectReference Include="..\Emby.Server.Implementations\Emby.Server.Implementations.csproj" />
<ProjectReference Include="..\Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj" /> <ProjectReference Include="..\Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj" />
<ProjectReference Include="..\Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -18,6 +18,7 @@ namespace Jellyfin.Server.Migrations
{ {
typeof(Routines.DisableTranscodingThrottling), typeof(Routines.DisableTranscodingThrottling),
typeof(Routines.CreateUserLoggingConfigFile), typeof(Routines.CreateUserLoggingConfigFile),
typeof(Routines.MigrateActivityLogDb),
typeof(Routines.RemoveDuplicateExtras) typeof(Routines.RemoveDuplicateExtras)
}; };

View File

@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Emby.Server.Implementations.Data;
using Jellyfin.Data.Entities;
using Jellyfin.Server.Implementations;
using MediaBrowser.Controller;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using SQLitePCL.pretty;
namespace Jellyfin.Server.Migrations.Routines
{
/// <summary>
/// The migration routine for migrating the activity log database to EF Core.
/// </summary>
public class MigrateActivityLogDb : IMigrationRoutine
{
private const string DbFilename = "activitylog.db";
private readonly ILogger<MigrateActivityLogDb> _logger;
private readonly JellyfinDbProvider _provider;
private readonly IServerApplicationPaths _paths;
/// <summary>
/// Initializes a new instance of the <see cref="MigrateActivityLogDb"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="paths">The server application paths.</param>
/// <param name="provider">The database provider.</param>
public MigrateActivityLogDb(ILogger<MigrateActivityLogDb> logger, IServerApplicationPaths paths, JellyfinDbProvider provider)
{
_logger = logger;
_provider = provider;
_paths = paths;
}
/// <inheritdoc/>
public Guid Id => Guid.Parse("3793eb59-bc8c-456c-8b9f-bd5a62a42978");
/// <inheritdoc/>
public string Name => "MigrateActivityLogDatabase";
/// <inheritdoc/>
public void Perform()
{
var logLevelDictionary = new Dictionary<string, LogLevel>(StringComparer.OrdinalIgnoreCase)
{
{ "None", LogLevel.None },
{ "Trace", LogLevel.Trace },
{ "Debug", LogLevel.Debug },
{ "Information", LogLevel.Information },
{ "Info", LogLevel.Information },
{ "Warn", LogLevel.Warning },
{ "Warning", LogLevel.Warning },
{ "Error", LogLevel.Error },
{ "Critical", LogLevel.Critical }
};
var dataPath = _paths.DataPath;
using (var connection = SQLite3.Open(
Path.Combine(dataPath, DbFilename),
ConnectionFlags.ReadOnly,
null))
{
_logger.LogWarning("Migrating the activity database may take a while, do not stop Jellyfin.");
using var dbContext = _provider.CreateContext();
var queryResult = connection.Query("SELECT * FROM ActivityLog ORDER BY Id ASC");
// Make sure that the database is empty in case of failed migration due to power outages, etc.
dbContext.ActivityLogs.RemoveRange(dbContext.ActivityLogs);
dbContext.SaveChanges();
// Reset the autoincrement counter
dbContext.Database.ExecuteSqlRaw("UPDATE sqlite_sequence SET seq = 0 WHERE name = 'ActivityLog';");
dbContext.SaveChanges();
var newEntries = queryResult.Select(entry =>
{
if (!logLevelDictionary.TryGetValue(entry[8].ToString(), out var severity))
{
severity = LogLevel.Trace;
}
var newEntry = new ActivityLog(
entry[1].ToString(),
entry[4].ToString(),
entry[6].SQLiteType == SQLiteType.Null ? Guid.Empty : Guid.Parse(entry[6].ToString()))
{
DateCreated = entry[7].ReadDateTime(),
LogSeverity = severity
};
if (entry[2].SQLiteType != SQLiteType.Null)
{
newEntry.Overview = entry[2].ToString();
}
if (entry[3].SQLiteType != SQLiteType.Null)
{
newEntry.ShortOverview = entry[3].ToString();
}
if (entry[5].SQLiteType != SQLiteType.Null)
{
newEntry.ItemId = entry[5].ToString();
}
return newEntry;
});
dbContext.ActivityLogs.AddRange(newEntries);
dbContext.SaveChanges();
}
try
{
File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old"));
var journalPath = Path.Combine(dataPath, DbFilename + "-journal");
if (File.Exists(journalPath))
{
File.Move(journalPath, Path.Combine(dataPath, DbFilename + ".old-journal"));
}
}
catch (IOException e)
{
_logger.LogError(e, "Error renaming legacy activity log database to 'activitylog.db.old'");
}
}
}
}

View File

@ -21,7 +21,7 @@ namespace MediaBrowser.Api
public abstract class BaseApiService : IService, IRequiresRequest public abstract class BaseApiService : IService, IRequiresRequest
{ {
public BaseApiService( public BaseApiService(
ILogger logger, ILogger<BaseApiService> logger,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory) IHttpResultFactory httpResultFactory)
{ {
@ -34,7 +34,7 @@ namespace MediaBrowser.Api
/// Gets the logger. /// Gets the logger.
/// </summary> /// </summary>
/// <value>The logger.</value> /// <value>The logger.</value>
protected ILogger Logger { get; } protected ILogger<BaseApiService> Logger { get; }
/// <summary> /// <summary>
/// Gets or sets the server configuration manager. /// Gets or sets the server configuration manager.

View File

@ -1,5 +1,4 @@
using System.IO; using System.IO;
using System.Threading.Tasks;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
@ -116,11 +115,6 @@ namespace MediaBrowser.Api.Devices
return _deviceManager.GetDeviceOptions(request.Id); return _deviceManager.GetDeviceOptions(request.Id);
} }
public object Get(GetCameraUploads request)
{
return ToOptimizedResult(_deviceManager.GetCameraUploadHistory(request.DeviceId));
}
public void Delete(DeleteDevice request) public void Delete(DeleteDevice request)
{ {
var sessions = _authRepo.Get(new AuthenticationInfoQuery var sessions = _authRepo.Get(new AuthenticationInfoQuery
@ -134,35 +128,5 @@ namespace MediaBrowser.Api.Devices
_sessionManager.Logout(session); _sessionManager.Logout(session);
} }
} }
public Task Post(PostCameraUpload request)
{
var deviceId = Request.QueryString["DeviceId"];
var album = Request.QueryString["Album"];
var id = Request.QueryString["Id"];
var name = Request.QueryString["Name"];
var req = Request.Response.HttpContext.Request;
if (req.HasFormContentType)
{
var file = req.Form.Files.Count == 0 ? null : req.Form.Files[0];
return _deviceManager.AcceptCameraUpload(deviceId, file.OpenReadStream(), new LocalFileInfo
{
MimeType = file.ContentType,
Album = album,
Name = name,
Id = id
});
}
return _deviceManager.AcceptCameraUpload(deviceId, request.RequestStream, new LocalFileInfo
{
MimeType = Request.ContentType,
Album = album,
Name = name,
Id = id
});
}
} }
} }

View File

@ -319,11 +319,14 @@ namespace MediaBrowser.Api.Library
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;
private readonly ILibraryMonitor _libraryMonitor; private readonly ILibraryMonitor _libraryMonitor;
private readonly ILogger<MoviesService> _moviesServiceLogger;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LibraryService" /> class. /// Initializes a new instance of the <see cref="LibraryService" /> class.
/// </summary> /// </summary>
public LibraryService( public LibraryService(
ILogger<LibraryService> logger, ILogger<LibraryService> logger,
ILogger<MoviesService> moviesServiceLogger,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory, IHttpResultFactory httpResultFactory,
IProviderManager providerManager, IProviderManager providerManager,
@ -344,6 +347,7 @@ namespace MediaBrowser.Api.Library
_activityManager = activityManager; _activityManager = activityManager;
_localization = localization; _localization = localization;
_libraryMonitor = libraryMonitor; _libraryMonitor = libraryMonitor;
_moviesServiceLogger = moviesServiceLogger;
} }
private string[] GetRepresentativeItemTypes(string contentType) private string[] GetRepresentativeItemTypes(string contentType)
@ -543,7 +547,7 @@ namespace MediaBrowser.Api.Library
if (item is Movie || (program != null && program.IsMovie) || item is Trailer) if (item is Movie || (program != null && program.IsMovie) || item is Trailer)
{ {
return new MoviesService( return new MoviesService(
Logger, _moviesServiceLogger,
ServerConfigurationManager, ServerConfigurationManager,
ResultFactory, ResultFactory,
_userManager, _userManager,
@ -759,13 +763,12 @@ namespace MediaBrowser.Api.Library
{ {
try try
{ {
_activityManager.Create(new ActivityLogEntry _activityManager.Create(new Jellyfin.Data.Entities.ActivityLog(
string.Format(_localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Name, item.Name),
"UserDownloadingContent",
auth.UserId)
{ {
Name = string.Format(_localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Name, item.Name),
Type = "UserDownloadingContent",
ShortOverview = string.Format(_localization.GetLocalizedString("AppDeviceValues"), auth.Client, auth.Device), ShortOverview = string.Format(_localization.GetLocalizedString("AppDeviceValues"), auth.Client, auth.Device),
UserId = auth.UserId
}); });
} }
catch catch

View File

@ -82,7 +82,7 @@ namespace MediaBrowser.Api.Movies
/// Initializes a new instance of the <see cref="MoviesService" /> class. /// Initializes a new instance of the <see cref="MoviesService" /> class.
/// </summary> /// </summary>
public MoviesService( public MoviesService(
ILogger logger, ILogger<MoviesService> logger,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory, IHttpResultFactory httpResultFactory,
IUserManager userManager, IUserManager userManager,

View File

@ -81,7 +81,7 @@ namespace MediaBrowser.Api.Playback
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class. /// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
/// </summary> /// </summary>
protected BaseStreamingService( protected BaseStreamingService(
ILogger logger, ILogger<BaseStreamingService> logger,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory, IHttpResultFactory httpResultFactory,
IUserManager userManager, IUserManager userManager,

View File

@ -25,7 +25,7 @@ namespace MediaBrowser.Api.Playback.Hls
public abstract class BaseHlsService : BaseStreamingService public abstract class BaseHlsService : BaseStreamingService
{ {
public BaseHlsService( public BaseHlsService(
ILogger logger, ILogger<BaseHlsService> logger,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory, IHttpResultFactory httpResultFactory,
IUserManager userManager, IUserManager userManager,

View File

@ -94,7 +94,7 @@ namespace MediaBrowser.Api.Playback.Hls
public class DynamicHlsService : BaseHlsService public class DynamicHlsService : BaseHlsService
{ {
public DynamicHlsService( public DynamicHlsService(
ILogger logger, ILogger<DynamicHlsService> logger,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory, IHttpResultFactory httpResultFactory,
IUserManager userManager, IUserManager userManager,

View File

@ -79,7 +79,7 @@ namespace MediaBrowser.Api.Playback
private readonly IAuthorizationContext _authContext; private readonly IAuthorizationContext _authContext;
public MediaInfoService( public MediaInfoService(
ILogger logger, ILogger<MediaInfoService> logger,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory, IHttpResultFactory httpResultFactory,
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,

View File

@ -33,7 +33,7 @@ namespace MediaBrowser.Api.Playback.Progressive
public class AudioService : BaseProgressiveStreamingService public class AudioService : BaseProgressiveStreamingService
{ {
public AudioService( public AudioService(
ILogger logger, ILogger<AudioService> logger,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory, IHttpResultFactory httpResultFactory,
IHttpClient httpClient, IHttpClient httpClient,

View File

@ -28,7 +28,7 @@ namespace MediaBrowser.Api.Playback.Progressive
protected IHttpClient HttpClient { get; private set; } protected IHttpClient HttpClient { get; private set; }
public BaseProgressiveStreamingService( public BaseProgressiveStreamingService(
ILogger logger, ILogger<BaseProgressiveStreamingService> logger,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory, IHttpResultFactory httpResultFactory,
IHttpClient httpClient, IHttpClient httpClient,

View File

@ -75,9 +75,11 @@ namespace MediaBrowser.Api.Playback
public class UniversalAudioService : BaseApiService public class UniversalAudioService : BaseApiService
{ {
private readonly EncodingHelper _encodingHelper; private readonly EncodingHelper _encodingHelper;
private readonly ILoggerFactory _loggerFactory;
public UniversalAudioService( public UniversalAudioService(
ILogger<UniversalAudioService> logger, ILogger<UniversalAudioService> logger,
ILoggerFactory loggerFactory,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory, IHttpResultFactory httpResultFactory,
IHttpClient httpClient, IHttpClient httpClient,
@ -108,6 +110,7 @@ namespace MediaBrowser.Api.Playback
AuthorizationContext = authorizationContext; AuthorizationContext = authorizationContext;
NetworkManager = networkManager; NetworkManager = networkManager;
_encodingHelper = encodingHelper; _encodingHelper = encodingHelper;
_loggerFactory = loggerFactory;
} }
protected IHttpClient HttpClient { get; private set; } protected IHttpClient HttpClient { get; private set; }
@ -233,7 +236,7 @@ namespace MediaBrowser.Api.Playback
AuthorizationContext.GetAuthorizationInfo(Request).DeviceId = request.DeviceId; AuthorizationContext.GetAuthorizationInfo(Request).DeviceId = request.DeviceId;
var mediaInfoService = new MediaInfoService( var mediaInfoService = new MediaInfoService(
Logger, _loggerFactory.CreateLogger<MediaInfoService>(),
ServerConfigurationManager, ServerConfigurationManager,
ResultFactory, ResultFactory,
MediaSourceManager, MediaSourceManager,
@ -277,7 +280,7 @@ namespace MediaBrowser.Api.Playback
if (!isStatic && string.Equals(mediaSource.TranscodingSubProtocol, "hls", StringComparison.OrdinalIgnoreCase)) if (!isStatic && string.Equals(mediaSource.TranscodingSubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
{ {
var service = new DynamicHlsService( var service = new DynamicHlsService(
Logger, _loggerFactory.CreateLogger<DynamicHlsService>(),
ServerConfigurationManager, ServerConfigurationManager,
ResultFactory, ResultFactory,
UserManager, UserManager,
@ -331,7 +334,7 @@ namespace MediaBrowser.Api.Playback
else else
{ {
var service = new AudioService( var service = new AudioService(
Logger, _loggerFactory.CreateLogger<AudioService>(),
ServerConfigurationManager, ServerConfigurationManager,
ResultFactory, ResultFactory,
HttpClient, HttpClient,

View File

@ -1,5 +1,7 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.Linq;
using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Activity; using MediaBrowser.Model.Activity;
@ -53,7 +55,10 @@ namespace MediaBrowser.Api.System
(DateTime?)null : (DateTime?)null :
DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime(); DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
var result = _activityManager.GetActivityLogEntries(minDate, request.HasUserId, request.StartIndex, request.Limit); var filterFunc = new Func<IQueryable<ActivityLog>, IQueryable<ActivityLog>>(
entries => entries.Where(entry => entry.DateCreated >= minDate));
var result = _activityManager.GetPagedResult(filterFunc, request.StartIndex, request.Limit);
return ToOptimizedResult(result); return ToOptimizedResult(result);
} }

View File

@ -51,7 +51,7 @@ namespace MediaBrowser.Api.UserLibrary
public class ArtistsService : BaseItemsByNameService<MusicArtist> public class ArtistsService : BaseItemsByNameService<MusicArtist>
{ {
public ArtistsService( public ArtistsService(
ILogger<GenresService> logger, ILogger<ArtistsService> logger,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory, IHttpResultFactory httpResultFactory,
IUserManager userManager, IUserManager userManager,

View File

@ -28,7 +28,7 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="userDataRepository">The user data repository.</param> /// <param name="userDataRepository">The user data repository.</param>
/// <param name="dtoService">The dto service.</param> /// <param name="dtoService">The dto service.</param>
protected BaseItemsByNameService( protected BaseItemsByNameService(
ILogger logger, ILogger<BaseItemsByNameService<TItemType>> logger,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory, IHttpResultFactory httpResultFactory,
IUserManager userManager, IUserManager userManager,

View File

@ -1,6 +1,4 @@
using System; using System;
using System.IO;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Devices; using MediaBrowser.Model.Devices;
using MediaBrowser.Model.Events; using MediaBrowser.Model.Events;
@ -11,11 +9,6 @@ namespace MediaBrowser.Controller.Devices
{ {
public interface IDeviceManager public interface IDeviceManager
{ {
/// <summary>
/// Occurs when [camera image uploaded].
/// </summary>
event EventHandler<GenericEventArgs<CameraImageUploadInfo>> CameraImageUploaded;
/// <summary> /// <summary>
/// Saves the capabilities. /// Saves the capabilities.
/// </summary> /// </summary>
@ -45,22 +38,6 @@ namespace MediaBrowser.Controller.Devices
/// <returns>IEnumerable&lt;DeviceInfo&gt;.</returns> /// <returns>IEnumerable&lt;DeviceInfo&gt;.</returns>
QueryResult<DeviceInfo> GetDevices(DeviceQuery query); QueryResult<DeviceInfo> GetDevices(DeviceQuery query);
/// <summary>
/// Gets the upload history.
/// </summary>
/// <param name="deviceId">The device identifier.</param>
/// <returns>ContentUploadHistory.</returns>
ContentUploadHistory GetCameraUploadHistory(string deviceId);
/// <summary>
/// Accepts the upload.
/// </summary>
/// <param name="deviceId">The device identifier.</param>
/// <param name="stream">The stream.</param>
/// <param name="file">The file.</param>
/// <returns>Task.</returns>
Task AcceptCameraUpload(string deviceId, Stream stream, LocalFileInfo file);
/// <summary> /// <summary>
/// Determines whether this instance [can access device] the specified user identifier. /// Determines whether this instance [can access device] the specified user identifier.
/// </summary> /// </summary>

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Model.Events; using MediaBrowser.Model.Events;
using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
namespace MediaBrowser.Controller.Net namespace MediaBrowser.Controller.Net
@ -38,5 +39,12 @@ namespace MediaBrowser.Controller.Net
/// <param name="context"></param> /// <param name="context"></param>
/// <returns></returns> /// <returns></returns>
Task RequestHandler(HttpContext context); Task RequestHandler(HttpContext context);
/// <summary>
/// Get the default CORS headers
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
IDictionary<string, string> GetDefaultCorsHeaders(IRequest req);
} }
} }

View File

@ -7,14 +7,25 @@ using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.MediaEncoding.Subtitles namespace MediaBrowser.MediaEncoding.Subtitles
{ {
/// <summary>
/// Subtitle writer for the WebVTT format.
/// </summary>
public class VttWriter : ISubtitleWriter public class VttWriter : ISubtitleWriter
{ {
/// <inheritdoc />
public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken) public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken)
{ {
using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
{ {
writer.WriteLine("WEBVTT"); writer.WriteLine("WEBVTT");
writer.WriteLine(string.Empty); writer.WriteLine(string.Empty);
writer.WriteLine("REGION");
writer.WriteLine("id:subtitle");
writer.WriteLine("width:80%");
writer.WriteLine("lines:3");
writer.WriteLine("regionanchor:50%,100%");
writer.WriteLine("viewportanchor:50%,90%");
writer.WriteLine(string.Empty);
foreach (var trackEvent in info.TrackEvents) foreach (var trackEvent in info.TrackEvents)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
@ -22,13 +33,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var startTime = TimeSpan.FromTicks(trackEvent.StartPositionTicks); var startTime = TimeSpan.FromTicks(trackEvent.StartPositionTicks);
var endTime = TimeSpan.FromTicks(trackEvent.EndPositionTicks); var endTime = TimeSpan.FromTicks(trackEvent.EndPositionTicks);
// make sure the start and end times are different and seqential // make sure the start and end times are different and sequential
if (endTime.TotalMilliseconds <= startTime.TotalMilliseconds) if (endTime.TotalMilliseconds <= startTime.TotalMilliseconds)
{ {
endTime = startTime.Add(TimeSpan.FromMilliseconds(1)); endTime = startTime.Add(TimeSpan.FromMilliseconds(1));
} }
writer.WriteLine(@"{0:hh\:mm\:ss\.fff} --> {1:hh\:mm\:ss\.fff}", startTime, endTime); writer.WriteLine(@"{0:hh\:mm\:ss\.fff} --> {1:hh\:mm\:ss\.fff} region:subtitle", startTime, endTime);
var text = trackEvent.Text; var text = trackEvent.Text;

View File

@ -59,6 +59,7 @@ namespace MediaBrowser.Model.Activity
/// Gets or sets the user primary image tag. /// Gets or sets the user primary image tag.
/// </summary> /// </summary>
/// <value>The user primary image tag.</value> /// <value>The user primary image tag.</value>
[Obsolete("UserPrimaryImageTag is not used.")]
public string UserPrimaryImageTag { get; set; } public string UserPrimaryImageTag { get; set; }
/// <summary> /// <summary>

View File

@ -1,6 +1,9 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using MediaBrowser.Model.Events; using MediaBrowser.Model.Events;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
@ -10,10 +13,15 @@ namespace MediaBrowser.Model.Activity
{ {
event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated; event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
void Create(ActivityLogEntry entry); void Create(ActivityLog entry);
QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit); Task CreateAsync(ActivityLog entry);
QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? x, int? y); QueryResult<ActivityLogEntry> GetPagedResult(int? startIndex, int? limit);
QueryResult<ActivityLogEntry> GetPagedResult(
Func<IQueryable<ActivityLog>, IQueryable<ActivityLog>> func,
int? startIndex,
int? limit);
} }
} }

View File

@ -1,14 +0,0 @@
#pragma warning disable CS1591
using System;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Model.Activity
{
public interface IActivityRepository
{
void Create(ActivityLogEntry entry);
QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? z, int? startIndex, int? limit);
}
}

View File

@ -79,8 +79,6 @@ namespace MediaBrowser.Model.Configuration
public bool EnableRemoteAccess { get; set; } public bool EnableRemoteAccess { get; set; }
public bool CameraUploadUpgraded { get; set; }
public bool CollectionsUpgraded { get; set; } public bool CollectionsUpgraded { get; set; }
/// <summary> /// <summary>

View File

@ -0,0 +1,9 @@
#pragma warning disable CS1591
namespace MediaBrowser.Model.Devices
{
public class DeviceOptions
{
public string CustomName { get; set; }
}
}

View File

@ -1,23 +0,0 @@
#pragma warning disable CS1591
using System;
namespace MediaBrowser.Model.Devices
{
public class DevicesOptions
{
public string[] EnabledCameraUploadDevices { get; set; }
public string CameraUploadPath { get; set; }
public bool EnableCameraUploadSubfolders { get; set; }
public DevicesOptions()
{
EnabledCameraUploadDevices = Array.Empty<string>();
}
}
public class DeviceOptions
{
public string CustomName { get; set; }
}
}

View File

@ -37,6 +37,9 @@
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Jellyfin.Data\Jellyfin.Data.csproj" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>

View File

@ -66,9 +66,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Implementat
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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Data", "Jellyfin.Data\Jellyfin.Data.csproj", "{F03299F2-469F-40EF-A655-3766F97A5702}" 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}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Implementations", "Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj", "{22C7DA3A-94F2-4E86-9CE6-86AB02B4F843}"
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}"
EndProject EndProject
Global Global
@ -185,10 +186,10 @@ 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 {DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{22C7DA3A-94F2-4E86-9CE6-86AB02B4F843}.Debug|Any CPU.Build.0 = Debug|Any CPU {DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Debug|Any CPU.Build.0 = Debug|Any CPU
{22C7DA3A-94F2-4E86-9CE6-86AB02B4F843}.Release|Any CPU.ActiveCfg = Release|Any CPU {DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.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}.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.ActiveCfg = Debug|Any CPU
{7C93C84F-105C-48E5-A878-406FA0A5B296}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU