Merge pull request #700 from jellyfin/dev

Dev sync
This commit is contained in:
Andrew Rabert 2019-01-24 23:03:54 -05:00 committed by GitHub
commit b4fdfb562d
30 changed files with 414 additions and 579 deletions

View file

@ -932,13 +932,7 @@ namespace Emby.Dlna.Didl
private void AddCover(BaseItem item, BaseItem context, StubType? stubType, XmlWriter writer)
{
ImageDownloadInfo imageInfo = null;
// Finally, just use the image from the item
if (imageInfo == null)
{
imageInfo = GetImageInfo(item);
}
ImageDownloadInfo imageInfo = GetImageInfo(item);;
if (imageInfo == null)
{

View file

@ -270,17 +270,10 @@ namespace Emby.Drawing
// create the bitmap
var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack);
if (bitmap != null)
{
// decode
codec.GetPixels(bitmap.Info, bitmap.GetPixels());
// decode
codec.GetPixels(bitmap.Info, bitmap.GetPixels());
origin = codec.EncodedOrigin;
}
else
{
origin = GetSKEncodedOrigin(orientation);
}
origin = codec.EncodedOrigin;
return bitmap;
}

View file

@ -30,8 +30,11 @@ namespace Emby.Naming.AudioBook
{
throw new ArgumentNullException(nameof(path));
}
if (IsDirectory)
if (IsDirectory) // TODO
{
return null;
}
var extension = Path.GetExtension(path) ?? string.Empty;
// Check supported extensions

View file

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates;
@ -92,18 +93,18 @@ namespace Emby.Server.Implementations.Activity
_appHost.ApplicationUpdated += _appHost_ApplicationUpdated;
}
void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e)
async void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("CameraImageUploadedFrom"), e.Argument.Device.Name),
Type = NotificationType.CameraImageUploaded.ToString()
});
}
void _userManager_UserLockedOut(object sender, GenericEventArgs<User> e)
async void _userManager_UserLockedOut(object sender, GenericEventArgs<User> e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("UserLockedOutWithName"), e.Argument.Name),
Type = NotificationType.UserLockedOut.ToString(),
@ -111,9 +112,9 @@ namespace Emby.Server.Implementations.Activity
});
}
void _subManager_SubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
async void _subManager_SubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"), e.Provider, Notifications.Notifications.GetItemName(e.Item)),
Type = "SubtitleDownloadFailure",
@ -122,7 +123,7 @@ namespace Emby.Server.Implementations.Activity
});
}
void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e)
async void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e)
{
var item = e.MediaInfo;
@ -145,7 +146,7 @@ namespace Emby.Server.Implementations.Activity
var user = e.Users.First();
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName),
Type = GetPlaybackStoppedNotificationType(item.MediaType),
@ -153,7 +154,7 @@ namespace Emby.Server.Implementations.Activity
});
}
void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e)
async void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e)
{
var item = e.MediaInfo;
@ -176,7 +177,7 @@ namespace Emby.Server.Implementations.Activity
var user = e.Users.First();
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("UserStartedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName),
Type = GetPlaybackNotificationType(item.MediaType),
@ -237,7 +238,7 @@ namespace Emby.Server.Implementations.Activity
return null;
}
void _sessionManager_SessionEnded(object sender, SessionEventArgs e)
async void _sessionManager_SessionEnded(object sender, SessionEventArgs e)
{
string name;
var session = e.SessionInfo;
@ -254,7 +255,7 @@ namespace Emby.Server.Implementations.Activity
name = string.Format(_localization.GetLocalizedString("UserOfflineFromDevice"), session.UserName, session.DeviceName);
}
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = name,
Type = "SessionEnded",
@ -263,11 +264,11 @@ namespace Emby.Server.Implementations.Activity
});
}
void _sessionManager_AuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
async void _sessionManager_AuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
{
var user = e.Argument.User;
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("AuthenticationSucceededWithUserName"), user.Name),
Type = "AuthenticationSucceeded",
@ -276,9 +277,9 @@ namespace Emby.Server.Implementations.Activity
});
}
void _sessionManager_AuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
async void _sessionManager_AuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("FailedLoginAttemptWithUserName"), e.Argument.Username),
Type = "AuthenticationFailed",
@ -287,9 +288,9 @@ namespace Emby.Server.Implementations.Activity
});
}
void _appHost_ApplicationUpdated(object sender, GenericEventArgs<PackageVersionInfo> e)
async void _appHost_ApplicationUpdated(object sender, GenericEventArgs<PackageVersionInfo> e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("MessageApplicationUpdatedTo"), e.Argument.versionStr),
Type = NotificationType.ApplicationUpdateInstalled.ToString(),
@ -297,27 +298,27 @@ namespace Emby.Server.Implementations.Activity
});
}
void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
async void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("MessageNamedServerConfigurationUpdatedWithValue"), e.Key),
Type = "NamedConfigurationUpdated"
});
}
void _config_ConfigurationUpdated(object sender, EventArgs e)
async void _config_ConfigurationUpdated(object sender, EventArgs e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = _localization.GetLocalizedString("MessageServerConfigurationUpdated"),
Type = "ServerConfigurationUpdated"
});
}
void _userManager_UserPolicyUpdated(object sender, GenericEventArgs<User> e)
async void _userManager_UserPolicyUpdated(object sender, GenericEventArgs<User> e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("UserPolicyUpdatedWithName"), e.Argument.Name),
Type = "UserPolicyUpdated",
@ -325,18 +326,18 @@ namespace Emby.Server.Implementations.Activity
});
}
void _userManager_UserDeleted(object sender, GenericEventArgs<User> e)
async void _userManager_UserDeleted(object sender, GenericEventArgs<User> e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("UserDeletedWithName"), e.Argument.Name),
Type = "UserDeleted"
});
}
void _userManager_UserPasswordChanged(object sender, GenericEventArgs<User> e)
async void _userManager_UserPasswordChanged(object sender, GenericEventArgs<User> e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("UserPasswordChangedWithName"), e.Argument.Name),
Type = "UserPasswordChanged",
@ -344,9 +345,9 @@ namespace Emby.Server.Implementations.Activity
});
}
void _userManager_UserCreated(object sender, GenericEventArgs<User> e)
async void _userManager_UserCreated(object sender, GenericEventArgs<User> e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("UserCreatedWithName"), e.Argument.Name),
Type = "UserCreated",
@ -354,9 +355,9 @@ namespace Emby.Server.Implementations.Activity
});
}
void _subManager_SubtitlesDownloaded(object sender, SubtitleDownloadEventArgs e)
async void _subManager_SubtitlesDownloaded(object sender, SubtitleDownloadEventArgs e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("SubtitlesDownloadedForItem"), Notifications.Notifications.GetItemName(e.Item)),
Type = "SubtitlesDownloaded",
@ -365,7 +366,7 @@ namespace Emby.Server.Implementations.Activity
});
}
void _sessionManager_SessionStarted(object sender, SessionEventArgs e)
async void _sessionManager_SessionStarted(object sender, SessionEventArgs e)
{
string name;
var session = e.SessionInfo;
@ -382,7 +383,7 @@ namespace Emby.Server.Implementations.Activity
name = string.Format(_localization.GetLocalizedString("UserOnlineFromDevice"), session.UserName, session.DeviceName);
}
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = name,
Type = "SessionStarted",
@ -391,9 +392,9 @@ namespace Emby.Server.Implementations.Activity
});
}
void _installationManager_PluginUpdated(object sender, GenericEventArgs<Tuple<IPlugin, PackageVersionInfo>> e)
async void _installationManager_PluginUpdated(object sender, GenericEventArgs<Tuple<IPlugin, PackageVersionInfo>> e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("PluginUpdatedWithName"), e.Argument.Item1.Name),
Type = NotificationType.PluginUpdateInstalled.ToString(),
@ -402,18 +403,18 @@ namespace Emby.Server.Implementations.Activity
});
}
void _installationManager_PluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
async void _installationManager_PluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("PluginUninstalledWithName"), e.Argument.Name),
Type = NotificationType.PluginUninstalled.ToString()
});
}
void _installationManager_PluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e)
async void _installationManager_PluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e)
{
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("PluginInstalledWithName"), e.Argument.name),
Type = NotificationType.PluginInstalled.ToString(),
@ -421,11 +422,11 @@ namespace Emby.Server.Implementations.Activity
});
}
void _installationManager_PackageInstallationFailed(object sender, InstallationFailedEventArgs e)
async void _installationManager_PackageInstallationFailed(object sender, InstallationFailedEventArgs e)
{
var installationInfo = e.InstallationInfo;
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("NameInstallFailed"), installationInfo.Name),
Type = NotificationType.InstallationFailed.ToString(),
@ -434,7 +435,7 @@ namespace Emby.Server.Implementations.Activity
});
}
void _taskManager_TaskCompleted(object sender, TaskCompletionEventArgs e)
async void _taskManager_TaskCompleted(object sender, TaskCompletionEventArgs e)
{
var result = e.Result;
var task = e.Task;
@ -461,7 +462,7 @@ namespace Emby.Server.Implementations.Activity
vals.Add(e.Result.LongErrorMessage);
}
CreateLogEntry(new ActivityLogEntry
await CreateLogEntry(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name),
Type = NotificationType.TaskFailed.ToString(),
@ -472,11 +473,11 @@ namespace Emby.Server.Implementations.Activity
}
}
private void CreateLogEntry(ActivityLogEntry entry)
private async Task CreateLogEntry(ActivityLogEntry entry)
{
try
{
_activityManager.Create(entry);
await _activityManager.CreateAsync(entry);
}
catch
{

View file

@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Activity
@ -26,20 +27,38 @@ namespace Emby.Server.Implementations.Activity
_userManager = userManager;
}
public void Create(ActivityLogEntry entry)
public async Task CreateAsync(ActivityLogEntry entry)
{
entry.Date = DateTime.UtcNow;
_repo.Create(entry);
await _repo.CreateAsync(entry);
EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(entry));
}
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
public IEnumerable<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
{
var result = _repo.GetActivityLogEntries(minDate, hasUserId, startIndex, limit);
var result = _repo.GetActivityLogEntries();
foreach (var item in result.Items.Where(i => !i.UserId.Equals(Guid.Empty)))
if (minDate.HasValue)
{
result = result.Where(x => x.Date >= minDate.Value);
}
if (hasUserId.HasValue)
{
result = result.Where(x => x.UserId != null && x.UserId != Guid.Empty);
}
if (startIndex.HasValue)
{
result = result.Where(x => x.Id >= startIndex.Value);
}
if (limit.HasValue)
{
result = result.Take(limit.Value);
}
// Add images for each user
foreach (var item in result)
{
var user = _userManager.GetUserById(item.UserId);
@ -50,12 +69,7 @@ namespace Emby.Server.Implementations.Activity
}
}
return result;
}
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit)
{
return GetActivityLogEntries(minDate, null, startIndex, limit);
return result.AsEnumerable();
}
}
}

View file

@ -1,310 +1,37 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Emby.Server.Implementations.Data;
using MediaBrowser.Controller;
using System.Threading.Tasks;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging;
using SQLitePCL.pretty;
using Microsoft.EntityFrameworkCore;
namespace Emby.Server.Implementations.Activity
{
public class ActivityRepository : BaseSqliteRepository, IActivityRepository
public class ActivityRepository : DbContext, IActivityRepository
{
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
protected IFileSystem FileSystem { get; private set; }
protected string _dataDirPath;
public ActivityRepository(ILoggerFactory loggerFactory, IServerApplicationPaths appPaths, IFileSystem fileSystem)
: base(loggerFactory.CreateLogger(nameof(ActivityRepository)))
public DbSet<ActivityLogEntry> ActivityLogs { get; set; }
public ActivityRepository(string dataDirPath)
{
DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db");
FileSystem = fileSystem;
_dataDirPath = dataDirPath;
}
public void Initialize()
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
try
{
InitializeInternal();
}
catch (Exception ex)
{
Logger.LogError(ex, "Error loading database file. Will reset and retry.");
// Ensure the dir exists
Directory.CreateDirectory(_dataDirPath);
FileSystem.DeleteFile(DbFilePath);
InitializeInternal();
}
optionsBuilder.UseSqlite($"Filename={Path.Combine(_dataDirPath, "activitylog.sqlite.db")}");
}
private void InitializeInternal()
public async Task CreateAsync(ActivityLogEntry entry)
{
using (var connection = CreateConnection())
{
RunDefaultInitialization(connection);
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);
}
await ActivityLogs.AddAsync(entry);
await SaveChangesAsync();
}
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");
}
}
private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog";
public void Create(ActivityLogEntry entry)
{
if (entry == null)
{
throw new ArgumentNullException(nameof(entry));
}
using (WriteLock.Write())
using (var connection = CreateConnection())
{
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"));
}
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
statement.TryBind("@LogSeverity", entry.Severity.ToString());
statement.MoveNext();
}
}, TransactionMode);
}
}
public void Update(ActivityLogEntry entry)
{
if (entry == null)
{
throw new ArgumentNullException(nameof(entry));
}
using (WriteLock.Write())
using (var connection = CreateConnection())
{
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"));
}
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
statement.TryBind("@LogSeverity", entry.Severity.ToString());
statement.MoveNext();
}
}, TransactionMode);
}
}
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
{
using (WriteLock.Read())
using (var connection = CreateConnection(true))
{
var commandText = BaseActivitySelectText;
var whereClauses = new List<string>();
if (minDate.HasValue)
{
whereClauses.Add("DateCreated>=@DateCreated");
}
if (hasUserId.HasValue)
{
if (hasUserId.Value)
{
whereClauses.Add("UserId not null");
}
else
{
whereClauses.Add("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("Id NOT IN (SELECT Id FROM ActivityLog {0} ORDER BY DateCreated DESC LIMIT {1})",
pagingWhereText,
startIndex.Value.ToString(_usCulture)));
}
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(_usCulture);
}
var statementTexts = new List<string>();
statementTexts.Add(commandText);
statementTexts.Add("select count (Id) from ActivityLog" + whereTextWithoutPaging);
return connection.RunInTransaction(db =>
{
var list = new List<ActivityLogEntry>();
var result = new QueryResult<ActivityLogEntry>();
var statements = PrepareAllSafe(db, statementTexts).ToList();
using (var statement = statements[0])
{
if (minDate.HasValue)
{
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
}
foreach (var row in statement.ExecuteQuery())
{
list.Add(GetEntry(row));
}
}
using (var statement = statements[1])
{
if (minDate.HasValue)
{
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
}
result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
}
result.Items = list.ToArray();
return result;
}, ReadTransactionMode);
}
}
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 = (LogLevel)Enum.Parse(typeof(LogLevel), reader[index].ToString(), true);
}
return info;
}
public IQueryable<ActivityLogEntry> GetActivityLogEntries()
=> ActivityLogs;
}
}

View file

@ -709,7 +709,7 @@ namespace Emby.Server.Implementations
}
}
public void Init()
public async Task InitAsync()
{
HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber;
@ -739,7 +739,7 @@ namespace Emby.Server.Implementations
SetHttpLimit();
RegisterResources();
await RegisterResourcesAsync();
FindParts();
}
@ -754,7 +754,7 @@ namespace Emby.Server.Implementations
/// <summary>
/// Registers resources that classes will depend on
/// </summary>
protected void RegisterResources()
protected async Task RegisterResourcesAsync()
{
RegisterSingleInstance(ConfigurationManager);
RegisterSingleInstance<IApplicationHost>(this);
@ -931,7 +931,7 @@ namespace Emby.Server.Implementations
EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager);
RegisterSingleInstance(EncodingManager);
var activityLogRepo = GetActivityLogRepository();
var activityLogRepo = await GetActivityLogRepositoryAsync();
RegisterSingleInstance(activityLogRepo);
RegisterSingleInstance<IActivityManager>(new ActivityManager(LoggerFactory, activityLogRepo, UserManager));
@ -1146,11 +1146,11 @@ namespace Emby.Server.Implementations
return repo;
}
private IActivityRepository GetActivityLogRepository()
private async Task<IActivityRepository> GetActivityLogRepositoryAsync()
{
var repo = new ActivityRepository(LoggerFactory, ServerConfigurationManager.ApplicationPaths, FileSystemManager);
var repo = new ActivityRepository(ServerConfigurationManager.ApplicationPaths.DataPath);
repo.Initialize();
await repo.Database.EnsureCreatedAsync();
return repo;
}

View file

@ -681,22 +681,17 @@ namespace Emby.Server.Implementations.Channels
// Find the corresponding channel provider plugin
var channelProvider = GetChannelProvider(channel);
var user = query.User;
ChannelItemSortField? sortField = null;
var sortDescending = false;
var parentItem = !query.ParentId.Equals(Guid.Empty) ? _libraryManager.GetItemById(query.ParentId) : channel;
var parentItem = query.ParentId == Guid.Empty ? channel : _libraryManager.GetItemById(query.ParentId);
var itemsResult = await GetChannelItems(channelProvider,
user,
query.User,
parentItem is Channel ? null : parentItem.ExternalId,
sortField,
sortDescending,
null,
false,
cancellationToken)
.ConfigureAwait(false);
if (query.ParentId.Equals(Guid.Empty))
if (query.ParentId == Guid.Empty)
{
query.Parent = channel;
}

View file

@ -22,6 +22,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.0" />
<PackageReference Include="ServiceStack.Text.Core" Version="5.4.0" />
<PackageReference Include="sharpcompress" Version="0.22.0" />
<PackageReference Include="SimpleInjector" Version="4.4.2" />

View file

@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.HttpServer
private long RangeStart { get; set; }
private long RangeEnd { get; set; }
private long RangeLength { get; set; }
private long TotalContentLength { get; set; }
public long TotalContentLength { get; set; }
public Action OnComplete { get; set; }
public Action OnError { get; set; }

View file

@ -5,6 +5,7 @@ using System.IO;
using System.IO.Compression;
using System.Net;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
@ -423,13 +424,11 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary>
private object GetCachedResult(IRequest requestContext, IDictionary<string, string> responseHeaders, Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType)
{
responseHeaders["ETag"] = string.Format("\"{0}\"", cacheKeyString);
bool noCache = (requestContext.Headers.Get("Cache-Control") ?? string.Empty).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1;
if (!noCache)
{
if (IsNotModified(requestContext, cacheKey, lastDateModified, cacheDuration))
if (IsNotModified(requestContext, cacheKey))
{
AddAgeHeader(responseHeaders, lastDateModified);
AddExpiresHeader(responseHeaders, cacheKeyString, cacheDuration);
@ -442,7 +441,7 @@ namespace Emby.Server.Implementations.HttpServer
}
}
AddCachingHeaders(responseHeaders, cacheKeyString, lastDateModified, cacheDuration);
AddCachingHeaders(responseHeaders, cacheKeyString, cacheDuration);
return null;
}
@ -532,10 +531,11 @@ namespace Emby.Server.Implementations.HttpServer
public async Task<object> GetStaticResult(IRequest requestContext, StaticResultOptions options)
{
var cacheKey = options.CacheKey;
options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var contentType = options.ContentType;
var contentType = options.ContentType;
var etag = requestContext.Headers.Get("If-None-Match");
var cacheKey = etag != null ? new Guid(etag.Trim('\"')) : Guid.Empty;
if (!cacheKey.Equals(Guid.Empty))
{
var key = cacheKey.ToString("N");
@ -554,8 +554,6 @@ namespace Emby.Server.Implementations.HttpServer
var factoryFn = options.ContentFactory;
var responseHeaders = options.ResponseHeaders;
//var requestedCompressionType = GetCompressionType(requestContext);
var rangeHeader = requestContext.Headers.Get("Range");
if (!isHeadRequest && !string.IsNullOrEmpty(options.Path))
@ -568,10 +566,21 @@ namespace Emby.Server.Implementations.HttpServer
};
AddResponseHeaders(hasHeaders, options.ResponseHeaders);
// Generate an ETag based on identifying information - TODO read contents from filesystem instead?
var responseId = $"{hasHeaders.ContentType}{options.Path}{hasHeaders.TotalContentLength}";
var hashedId = MD5.Create().ComputeHash(Encoding.Default.GetBytes(responseId));
hasHeaders.Headers["ETag"] = new Guid(hashedId).ToString("N");
return hasHeaders;
}
var stream = await factoryFn().ConfigureAwait(false);
// Generate an etag based on stream content
var streamHash = MD5.Create().ComputeHash(stream);
var newEtag = new Guid(streamHash).ToString("N");
// reset position so the response can re-use it -- TODO is this ok?
stream.Position = 0;
var totalContentLength = options.ContentLength;
if (!totalContentLength.HasValue)
@ -594,6 +603,7 @@ namespace Emby.Server.Implementations.HttpServer
};
AddResponseHeaders(hasHeaders, options.ResponseHeaders);
hasHeaders.Headers["ETag"] = newEtag;
return hasHeaders;
}
else
@ -618,6 +628,7 @@ namespace Emby.Server.Implementations.HttpServer
};
AddResponseHeaders(hasHeaders, options.ResponseHeaders);
hasHeaders.Headers["ETag"] = newEtag;
return hasHeaders;
}
}
@ -630,16 +641,8 @@ namespace Emby.Server.Implementations.HttpServer
/// <summary>
/// Adds the caching responseHeaders.
/// </summary>
private void AddCachingHeaders(IDictionary<string, string> responseHeaders, string cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration)
private void AddCachingHeaders(IDictionary<string, string> responseHeaders, string cacheKey, TimeSpan? cacheDuration)
{
// Don't specify both last modified and Etag, unless caching unconditionally. They are redundant
// https://developers.google.com/speed/docs/best-practices/caching#LeverageBrowserCaching
if (lastDateModified.HasValue && (string.IsNullOrEmpty(cacheKey) || cacheDuration.HasValue))
{
AddAgeHeader(responseHeaders, lastDateModified);
responseHeaders["Last-Modified"] = lastDateModified.Value.ToString("r");
}
if (cacheDuration.HasValue)
{
responseHeaders["Cache-Control"] = "public, max-age=" + Convert.ToInt32(cacheDuration.Value.TotalSeconds);
@ -692,28 +695,15 @@ namespace Emby.Server.Implementations.HttpServer
/// <param name="lastDateModified">The last date modified.</param>
/// <param name="cacheDuration">Duration of the cache.</param>
/// <returns><c>true</c> if [is not modified] [the specified cache key]; otherwise, <c>false</c>.</returns>
private bool IsNotModified(IRequest requestContext, Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration)
private bool IsNotModified(IRequest requestContext, Guid cacheKey)
{
//var isNotModified = true;
var ifModifiedSinceHeader = requestContext.Headers.Get("If-Modified-Since");
if (!string.IsNullOrEmpty(ifModifiedSinceHeader)
&& DateTime.TryParse(ifModifiedSinceHeader, out DateTime ifModifiedSince)
&& IsNotModified(ifModifiedSince.ToUniversalTime(), cacheDuration, lastDateModified))
{
return true;
}
var ifNoneMatchHeader = requestContext.Headers.Get("If-None-Match");
bool hasCacheKey = !cacheKey.Equals(Guid.Empty);
// Validate If-None-Match
if ((hasCacheKey && !string.IsNullOrEmpty(ifNoneMatchHeader)))
if (hasCacheKey && !string.IsNullOrEmpty(ifNoneMatchHeader))
{
ifNoneMatchHeader = (ifNoneMatchHeader ?? string.Empty).Trim('\"');
if (Guid.TryParse(ifNoneMatchHeader, out var ifNoneMatch)
&& cacheKey.Equals(ifNoneMatch))
{

View file

@ -469,7 +469,7 @@ namespace Emby.Server.Implementations.Library
}
// TODO: Don't hardcode this
var isAudio = false;
const bool isAudio = false;
try
{
@ -480,9 +480,11 @@ namespace Emby.Server.Implementations.Library
else
{
// hack - these two values were taken from LiveTVMediaSourceProvider
var cacheKey = request.OpenToken;
string cacheKey = request.OpenToken;
await new LiveStreamHelper(_mediaEncoder(), _logger, _jsonSerializer, _appPaths).AddMediaInfoWithProbe(mediaSource, isAudio, cacheKey, true, cancellationToken).ConfigureAwait(false);
await new LiveStreamHelper(_mediaEncoder(), _logger, _jsonSerializer, _appPaths)
.AddMediaInfoWithProbe(mediaSource, isAudio, cacheKey, true, cancellationToken)
.ConfigureAwait(false);
}
}
catch (Exception ex)
@ -491,6 +493,7 @@ namespace Emby.Server.Implementations.Library
AddMediaInfo(mediaSource, isAudio);
}
// TODO: @bond Fix
var json = _jsonSerializer.SerializeToString(mediaSource);
_logger.LogInformation("Live stream opened: " + json);
var clone = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json);

View file

@ -86,12 +86,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
.Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
.ToList();
if (isBooksCollectionType)
{
return FindAudio<AudioBook>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
}
return null;
return FindAudio<AudioBook>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
}
if (LibraryManager.IsAudioFile(args.Path, libraryOptions))
@ -145,36 +140,19 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
private T FindAudio<T>(ItemResolveArgs args, string path, Folder parent, List<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, string collectionType, bool parseName)
where T : MediaBrowser.Controller.Entities.Audio.Audio, new()
{
var multiDiscFolders = new List<FileSystemMetadata>();
var libraryOptions = args.GetLibraryOptions();
var filesFromOtherItems = new List<FileSystemMetadata>();
// TODO: Allow GetMultiDiscMovie in here
var supportsMultiVersion = false;
const bool supportsMultiVersion = false;
var result = ResolveMultipleAudio<T>(parent, fileSystemEntries, directoryService, supportsMultiVersion, collectionType, parseName) ??
new MultiItemResolverResult();
if (result.Items.Count == 1)
{
var videoPath = result.Items[0].Path;
// If we were supporting this we'd be checking filesFromOtherItems
var hasOtherItems = false;
if (!hasOtherItems)
{
var item = (T)result.Items[0];
item.IsInMixedFolder = false;
item.Name = Path.GetFileName(item.ContainingFolderPath);
return item;
}
}
if (result.Items.Count == 0 && multiDiscFolders.Count > 0)
{
//return GetMultiDiscAudio<T>(multiDiscFolders, directoryService);
var item = (T)result.Items[0];
item.IsInMixedFolder = false;
item.Name = Path.GetFileName(item.ContainingFolderPath);
return item;
}
return null;
@ -194,11 +172,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
{
leftOver.Add(child);
}
else if (IsIgnored(child.Name))
{
}
else
else if (!IsIgnored(child.Name))
{
files.Add(child);
}

View file

@ -410,7 +410,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
}
// TODO: Allow GetMultiDiscMovie in here
var supportsMultiVersion = true;
const bool supportsMultiVersion = true;
var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, supportsMultiVersion, collectionType, parseName) ??
new MultiItemResolverResult();

View file

@ -153,16 +153,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
var namingOptions = ((LibraryManager)libraryManager).GetNamingOptions();
var episodeResolver = new Naming.TV.EpisodeResolver(namingOptions);
bool? isNamed = null;
bool? isOptimistic = null;
if (!isTvContentType)
{
isNamed = true;
isOptimistic = false;
}
var episodeInfo = episodeResolver.Resolve(fullName, false, isNamed, isOptimistic, fillExtendedInfo: false);
var episodeInfo = episodeResolver.Resolve(fullName, false, true, false, fillExtendedInfo: false);
if (episodeInfo != null && episodeInfo.EpisodeNumber.HasValue)
{
return true;

View file

@ -322,17 +322,14 @@ namespace Emby.Server.Implementations.Library
throw new SecurityException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name));
}
if (user != null)
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
{
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
{
throw new SecurityException("Forbidden.");
}
throw new SecurityException("Forbidden.");
}
if (!user.IsParentalScheduleAllowed())
{
throw new SecurityException("User is not allowed access at this time.");
}
if (!user.IsParentalScheduleAllowed())
{
throw new SecurityException("User is not allowed access at this time.");
}
// Update LastActivityDate and LastLoginDate, then save
@ -463,26 +460,26 @@ namespace Emby.Server.Implementations.Library
{
user.Policy.InvalidLoginAttemptCount = newValue;
var maxCount = user.Policy.IsAdministrator ?
3 :
5;
var maxCount = user.Policy.IsAdministrator ? 3 : 5;
// TODO: Fix
/*
var fireLockout = false;
if (newValue >= maxCount)
{
//_logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue.ToString(CultureInfo.InvariantCulture));
//user.Policy.IsDisabled = true;
_logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue.ToString(CultureInfo.InvariantCulture));
user.Policy.IsDisabled = true;
//fireLockout = true;
}
fireLockout = true;
}*/
UpdateUserPolicy(user, user.Policy, false);
if (fireLockout)
/* if (fireLockout)
{
UserLockedOut?.Invoke(this, new GenericEventArgs<User>(user));
}
}*/
}
}

View file

@ -1133,8 +1133,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
IgnoreIndex = true
};
var isAudio = false;
await new LiveStreamHelper(_mediaEncoder, _logger, _jsonSerializer, _config.CommonApplicationPaths).AddMediaInfoWithProbe(stream, isAudio, false, cancellationToken).ConfigureAwait(false);
await new LiveStreamHelper(_mediaEncoder, _logger, _jsonSerializer, _config.CommonApplicationPaths)
.AddMediaInfoWithProbe(stream, false, false, cancellationToken).ConfigureAwait(false);
return new List<MediaSourceInfo>
{
@ -1149,12 +1149,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public Task RecordLiveStream(string id, CancellationToken cancellationToken)
{
return Task.FromResult(0);
return Task.CompletedTask;
}
public Task ResetTuner(string id, CancellationToken cancellationToken)
{
return Task.FromResult(0);
return Task.CompletedTask;
}
async void _timerProvider_TimerFired(object sender, GenericEventArgs<TimerInfo> e)

View file

@ -175,12 +175,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
var videoStream = mediaSource.VideoStream;
string videoDecoder = null;
if (!string.IsNullOrEmpty(videoDecoder))
{
inputModifier += " " + videoDecoder;
}
if (mediaSource.ReadAtNativeFramerate)
{

View file

@ -354,10 +354,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
int? height = null;
bool isInterlaced = true;
string videoCodec = null;
string audioCodec = null;
int? videoBitrate = null;
int? audioBitrate = null;
var isHd = channelInfo.IsHD ?? true;
@ -427,20 +425,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
}
if (channelInfo != null)
if (string.IsNullOrWhiteSpace(videoCodec))
{
if (string.IsNullOrWhiteSpace(videoCodec))
{
videoCodec = channelInfo.VideoCodec;
}
audioCodec = channelInfo.AudioCodec;
if (!videoBitrate.HasValue)
{
videoBitrate = isHd ? 15000000 : 2000000;
}
audioBitrate = isHd ? 448000 : 192000;
videoCodec = channelInfo.VideoCodec;
}
string audioCodec = channelInfo.AudioCodec;
if (!videoBitrate.HasValue)
{
videoBitrate = isHd ? 15000000 : 2000000;
}
int? audioBitrate = isHd ? 448000 : 192000;
// normalize
if (string.Equals(videoCodec, "mpeg2", StringComparison.OrdinalIgnoreCase))

View file

@ -99,7 +99,7 @@ namespace Jellyfin.Server
new SystemEvents(),
new NetworkManager(_loggerFactory, environmentInfo)))
{
appHost.Init();
await appHost.InitAsync();
appHost.ImageProcessor.ImageEncoder = GetImageEncoder(fileSystem, appPaths, appHost.LocalizationManager);
@ -108,7 +108,6 @@ namespace Jellyfin.Server
await appHost.RunStartupTasks();
// TODO: read input for a stop command
try
{
// Block main thread until shutdown
@ -167,7 +166,6 @@ namespace Jellyfin.Server
{
Directory.CreateDirectory(programDataPath);
}
string configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR");
if (string.IsNullOrEmpty(configDir))
{
@ -224,7 +222,7 @@ namespace Jellyfin.Server
.GetManifestResourceStream("Jellyfin.Server.Resources.Configuration.logging.json"))
using (Stream fstr = File.Open(configPath, FileMode.CreateNew))
{
await rscstr.CopyToAsync(fstr);
await rscstr.CopyToAsync(fstr).ConfigureAwait(false);
}
}
var configuration = new ConfigurationBuilder()
@ -334,11 +332,9 @@ namespace Jellyfin.Server
}
else
{
commandLineArgsString = string.Join(" ",
Environment.GetCommandLineArgs()
.Skip(1)
.Select(NormalizeCommandLineArgument)
);
commandLineArgsString = string.Join(
" ",
Environment.GetCommandLineArgs().Skip(1).Select(NormalizeCommandLineArgument));
}
_logger.LogInformation("Executable: {0}", module);

View file

@ -15,19 +15,27 @@ namespace Jellyfin.SocketSharp
{
int ap = header.IndexOf(attr);
if (ap == -1)
{
return null;
}
ap += attr.Length;
if (ap >= header.Length)
{
return null;
}
char ending = header[ap];
if (ending != '"')
{
ending = ' ';
}
int end = header.IndexOf(ending, ap + 1);
if (end == -1)
{
return ending == '"' ? null : header.Substring(ap);
}
return header.Substring(ap + 1, end - ap - 1);
}
@ -36,7 +44,9 @@ namespace Jellyfin.SocketSharp
{
string boundary = GetParameter(ContentType, "; boundary=");
if (boundary == null)
{
return;
}
using (var requestStream = InputStream)
{
@ -124,7 +134,9 @@ namespace Jellyfin.SocketSharp
{
string v = "\"" + value + "\"";
if (v.Length > 20)
{
v = v.Substring(0, 16) + "...\"";
}
string msg = string.Format("A potentially dangerous Request.{0} value was " +
"detected from the client ({1}={2}).", name, key, v);
@ -135,21 +147,23 @@ namespace Jellyfin.SocketSharp
static void ValidateNameValueCollection(string name, QueryParamCollection coll)
{
if (coll == null)
{
return;
}
foreach (var pair in coll)
{
var key = pair.Name;
var val = pair.Value;
if (val != null && val.Length > 0 && IsInvalidString(val))
{
ThrowValidationException(name, key, val);
}
}
}
internal static bool IsInvalidString(string val)
{
return IsInvalidString(val, out var validationFailureIndex);
}
=> IsInvalidString(val, out var validationFailureIndex);
internal static bool IsInvalidString(string val, out int validationFailureIndex)
{
@ -157,7 +171,9 @@ namespace Jellyfin.SocketSharp
int len = val.Length;
if (len < 2)
{
return false;
}
char current = val[0];
for (int idx = 1; idx < len; idx++)
@ -195,10 +211,15 @@ namespace Jellyfin.SocketSharp
bool IsContentType(string ct, bool starts_with)
{
if (ct == null || ContentType == null) return false;
if (ct == null || ContentType == null)
{
return false;
}
if (starts_with)
{
return StrUtils.StartsWith(ContentType, ct, true);
}
return string.Equals(ContentType, ct, StringComparison.OrdinalIgnoreCase);
}
@ -231,7 +252,9 @@ namespace Jellyfin.SocketSharp
break;
}
else
{
value.Append((char)c);
}
}
if (c == -1)
{
@ -240,22 +263,26 @@ namespace Jellyfin.SocketSharp
}
}
else if (c == '&')
{
AddRawKeyValue(form, key, value);
}
else
{
key.Append((char)c);
}
}
if (c == -1)
{
AddRawKeyValue(form, key, value);
}
}
}
}
}
void AddRawKeyValue(WebROCollection form, StringBuilder key, StringBuilder value)
static void AddRawKeyValue(WebROCollection form, StringBuilder key, StringBuilder value)
{
string decodedKey = WebUtility.UrlDecode(key.ToString());
form.Add(decodedKey,
WebUtility.UrlDecode(value.ToString()));
form.Add(WebUtility.UrlDecode(key.ToString()), WebUtility.UrlDecode(value.ToString()));
key.Length = 0;
value.Length = 0;
@ -271,7 +298,9 @@ namespace Jellyfin.SocketSharp
foreach (var pair in this)
{
if (result.Length > 0)
{
result.Append('&');
}
var key = pair.Name;
if (key != null && key.Length > 0)
@ -314,33 +343,52 @@ namespace Jellyfin.SocketSharp
public override int Read(byte[] buffer, int dest_offset, int count)
{
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
if (dest_offset < 0)
{
throw new ArgumentOutOfRangeException(nameof(dest_offset), "< 0");
}
if (count < 0)
{
throw new ArgumentOutOfRangeException(nameof(count), "< 0");
}
int len = buffer.Length;
if (dest_offset > len)
{
throw new ArgumentException("destination offset is beyond array size");
}
// reordered to avoid possible integer overflow
if (dest_offset > len - count)
{
throw new ArgumentException("Reading would overrun buffer");
}
if (count > end - position)
{
count = (int)(end - position);
}
if (count <= 0)
{
return 0;
}
s.Position = position;
int result = s.Read(buffer, dest_offset, count);
if (result > 0)
{
position += result;
}
else
{
position = end;
}
return result;
}
@ -348,14 +396,20 @@ namespace Jellyfin.SocketSharp
public override int ReadByte()
{
if (position >= end)
{
return -1;
}
s.Position = position;
int result = s.ReadByte();
if (result < 0)
{
position = end;
}
else
{
position++;
}
return result;
}
@ -380,7 +434,9 @@ namespace Jellyfin.SocketSharp
long virt = real - offset;
if (virt < 0 || virt > Length)
{
throw new ArgumentException();
}
position = s.Seek(real, SeekOrigin.Begin);
return position;
@ -410,7 +466,9 @@ namespace Jellyfin.SocketSharp
set
{
if (value > Length)
throw new ArgumentOutOfRangeException();
{
throw new ArgumentOutOfRangeException(nameof(value));
}
position = Seek(value, SeekOrigin.Begin);
}
@ -438,7 +496,7 @@ namespace Jellyfin.SocketSharp
public static readonly CultureInfo InvariantCulture = CultureInfo.InvariantCulture;
}
internal sealed class StrUtils
internal static class StrUtils
{
public static bool StartsWith(string str1, string str2, bool ignore_case)
{
@ -455,11 +513,15 @@ namespace Jellyfin.SocketSharp
{
int l2 = str2.Length;
if (l2 == 0)
{
return true;
}
int l1 = str1.Length;
if (l2 > l1)
{
return false;
}
var comparison = ignore_case ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
return str1.IndexOf(str2, comparison) == str1.Length - str2.Length - 1;
@ -493,7 +555,7 @@ namespace Jellyfin.SocketSharp
Encoding encoding;
StringBuilder sb;
const byte HYPHEN = (byte)'-', LF = (byte)'\n', CR = (byte)'\r';
const byte LF = (byte)'\n', CR = (byte)'\r';
// See RFC 2046
// In the case of multipart entities, in which one or more different
@ -520,7 +582,7 @@ namespace Jellyfin.SocketSharp
sb = new StringBuilder();
}
string ReadLine()
private string ReadLine()
{
// CRLF or LF are ok as line endings.
bool got_cr = false;
@ -543,58 +605,86 @@ namespace Jellyfin.SocketSharp
}
if (got_cr)
{
sb.Length--;
}
return sb.ToString();
}
static string GetContentDispositionAttribute(string l, string name)
private static string GetContentDispositionAttribute(string l, string name)
{
int idx = l.IndexOf(name + "=\"");
if (idx < 0)
{
return null;
}
int begin = idx + name.Length + "=\"".Length;
int end = l.IndexOf('"', begin);
if (end < 0)
{
return null;
}
if (begin == end)
return "";
{
return string.Empty;
}
return l.Substring(begin, end - begin);
}
string GetContentDispositionAttributeWithEncoding(string l, string name)
private string GetContentDispositionAttributeWithEncoding(string l, string name)
{
int idx = l.IndexOf(name + "=\"");
if (idx < 0)
{
return null;
}
int begin = idx + name.Length + "=\"".Length;
int end = l.IndexOf('"', begin);
if (end < 0)
{
return null;
}
if (begin == end)
return "";
{
return string.Empty;
}
string temp = l.Substring(begin, end - begin);
byte[] source = new byte[temp.Length];
for (int i = temp.Length - 1; i >= 0; i--)
{
source[i] = (byte)temp[i];
}
return encoding.GetString(source, 0, source.Length);
}
bool ReadBoundary()
private bool ReadBoundary()
{
try
{
string line = ReadLine();
while (line == "")
while (line == string.Empty)
{
line = ReadLine();
}
if (line[0] != '-' || line[1] != '-')
{
return false;
}
if (!StrUtils.EndsWith(line, boundary, false))
{
return true;
}
}
catch
{
@ -603,25 +693,31 @@ namespace Jellyfin.SocketSharp
return false;
}
string ReadHeaders()
private string ReadHeaders()
{
string s = ReadLine();
if (s == "")
if (s.Length == 0)
{
return null;
}
return s;
}
bool CompareBytes(byte[] orig, byte[] other)
private static bool CompareBytes(byte[] orig, byte[] other)
{
for (int i = orig.Length - 1; i >= 0; i--)
{
if (orig[i] != other[i])
{
return false;
}
}
return true;
}
long MoveToNextBoundary()
private long MoveToNextBoundary()
{
long retval = 0;
bool got_cr = false;
@ -631,13 +727,18 @@ namespace Jellyfin.SocketSharp
while (true)
{
if (c == -1)
{
return -1;
}
if (state == 0 && c == LF)
{
retval = data.Position - 1;
if (got_cr)
{
retval--;
}
state = 1;
c = data.ReadByte();
}
@ -650,7 +751,9 @@ namespace Jellyfin.SocketSharp
{
c = data.ReadByte();
if (c == -1)
{
return -1;
}
if (c != '-')
{
@ -662,7 +765,9 @@ namespace Jellyfin.SocketSharp
int nread = data.Read(buffer, 0, buffer.Length);
int bl = buffer.Length;
if (nread != bl)
{
return -1;
}
if (!CompareBytes(boundary_bytes, buffer))
{
@ -673,6 +778,7 @@ namespace Jellyfin.SocketSharp
data.Position++;
got_cr = false;
}
c = data.ReadByte();
continue;
}
@ -690,12 +796,16 @@ namespace Jellyfin.SocketSharp
data.Position++;
got_cr = false;
}
c = data.ReadByte();
continue;
}
data.Position = retval + 2;
if (got_cr)
{
data.Position++;
}
break;
}
else
@ -711,7 +821,9 @@ namespace Jellyfin.SocketSharp
public Element ReadNextElement()
{
if (at_eof || ReadBoundary())
{
return null;
}
var elem = new Element();
string header;
@ -734,19 +846,27 @@ namespace Jellyfin.SocketSharp
elem.Start = start;
long pos = MoveToNextBoundary();
if (pos == -1)
{
return null;
}
elem.Length = pos - start;
return elem;
}
static string StripPath(string path)
private static string StripPath(string path)
{
if (path == null || path.Length == 0)
{
return path;
}
if (path.IndexOf(":\\") != 1 && !path.StartsWith("\\\\"))
if (path.IndexOf(":\\", StringComparison.Ordinal) != 1
&& !path.StartsWith("\\\\", StringComparison.Ordinal))
{
return path;
}
return path.Substring(path.LastIndexOf('\\') + 1);
}
}

View file

@ -83,15 +83,15 @@ namespace Jellyfin.SocketSharp
private void ProcessContext(HttpListenerContext context)
{
//InitTask(context, _disposeCancellationToken);
Task.Run(() => InitTask(context, _disposeCancellationToken));
var _ = Task.Run(async () => await InitTask(context, _disposeCancellationToken));
}
private void LogRequest(ILogger logger, HttpListenerRequest request)
private static void LogRequest(ILogger logger, HttpListenerRequest request)
{
var url = request.Url.ToString();
logger.LogInformation("{0} {1}. UserAgent: {2}", request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, url, request.UserAgent ?? string.Empty);
logger.LogInformation("{0} {1}. UserAgent: {2}",
request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, url, request.UserAgent ?? string.Empty);
}
private Task InitTask(HttpListenerContext context, CancellationToken cancellationToken)
@ -196,7 +196,7 @@ namespace Jellyfin.SocketSharp
{
try
{
ctx.Response.StatusCode = 200;
ctx.Response.StatusCode = statusCode;
ctx.Response.Close();
}
catch (ObjectDisposedException)

View file

@ -242,7 +242,6 @@ namespace Jellyfin.SocketSharp
return request.ContentType.StartsWith(contentType, StringComparison.OrdinalIgnoreCase);
}
public const string Xml = "application/xml";
private static string GetQueryStringContentType(IRequest httpReq)
{
var format = httpReq.QueryString["format"];
@ -250,22 +249,40 @@ namespace Jellyfin.SocketSharp
{
const int formatMaxLength = 4;
var pi = httpReq.PathInfo;
if (pi == null || pi.Length <= formatMaxLength) return null;
if (pi[0] == '/') pi = pi.Substring(1);
if (pi == null || pi.Length <= formatMaxLength)
{
return null;
}
if (pi[0] == '/')
{
pi = pi.Substring(1);
}
format = LeftPart(pi, '/');
if (format.Length > formatMaxLength) return null;
if (format.Length > formatMaxLength)
{
return null;
}
}
format = LeftPart(format, '.').ToLower();
if (format.Contains("json")) return "application/json";
if (format.Contains("xml")) return Xml;
if (format.Contains("json", StringComparison.OrdinalIgnoreCase))
{
return "application/json";
}
if (format.Contains("xml", StringComparison.OrdinalIgnoreCase))
{
return "application/xml";
}
return null;
}
public static string LeftPart(string strVal, char needle)
{
if (strVal == null) return null;
if (strVal == null)
{
return null;
}
var pos = strVal.IndexOf(needle);
return pos == -1
? strVal
@ -283,14 +300,14 @@ namespace Jellyfin.SocketSharp
{
var mode = HandlerFactoryPath;
var pos = request.RawUrl.IndexOf("?");
var pos = request.RawUrl.IndexOf("?", StringComparison.Ordinal);
if (pos != -1)
{
var path = request.RawUrl.Substring(0, pos);
this.pathInfo = GetPathInfo(
path,
mode,
mode ?? "");
mode ?? string.Empty);
}
else
{
@ -307,18 +324,27 @@ namespace Jellyfin.SocketSharp
private static string GetPathInfo(string fullPath, string mode, string appPath)
{
var pathInfo = ResolvePathInfoFromMappedPath(fullPath, mode);
if (!string.IsNullOrEmpty(pathInfo)) return pathInfo;
if (!string.IsNullOrEmpty(pathInfo))
{
return pathInfo;
}
//Wildcard mode relies on this to work out the handlerPath
pathInfo = ResolvePathInfoFromMappedPath(fullPath, appPath);
if (!string.IsNullOrEmpty(pathInfo)) return pathInfo;
if (!string.IsNullOrEmpty(pathInfo))
{
return pathInfo;
}
return fullPath;
}
private static string ResolvePathInfoFromMappedPath(string fullPath, string mappedPathRoot)
{
if (mappedPathRoot == null) return null;
if (mappedPathRoot == null)
{
return null;
}
var sbPathInfo = new StringBuilder();
var fullPathParts = fullPath.Split('/');
@ -345,7 +371,10 @@ namespace Jellyfin.SocketSharp
}
}
}
if (!pathRootFound) return null;
if (!pathRootFound)
{
return null;
}
var path = sbPathInfo.ToString();
return path.Length > 1 ? path.TrimEnd('/') : "/";
@ -400,7 +429,10 @@ namespace Jellyfin.SocketSharp
public static Encoding GetEncoding(string contentTypeHeader)
{
var param = GetParameter(contentTypeHeader, "charset=");
if (param == null) return null;
if (param == null)
{
return null;
}
try
{
return Encoding.GetEncoding(param);
@ -423,7 +455,9 @@ namespace Jellyfin.SocketSharp
if (httpFiles == null)
{
if (files == null)
return httpFiles = new IHttpFile[0];
{
return httpFiles = Array.Empty<IHttpFile>();
}
httpFiles = new IHttpFile[files.Count];
var i = 0;

View file

@ -839,7 +839,7 @@ namespace MediaBrowser.Api.Library
{
try
{
_activityManager.Create(new ActivityLogEntry
_activityManager.CreateAsync(new ActivityLogEntry
{
Name = string.Format(_localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Name, item.Name),
Type = "UserDownloadingContent",

View file

@ -131,7 +131,7 @@ namespace MediaBrowser.Common
/// <summary>
/// Inits this instance.
/// </summary>
void Init();
Task InitAsync();
/// <summary>
/// Creates the instance.

View file

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Model.Activity
{
@ -8,10 +9,8 @@ namespace MediaBrowser.Model.Activity
{
event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
void Create(ActivityLogEntry entry);
Task CreateAsync(ActivityLogEntry entry);
QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit);
QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? x, int? y);
IEnumerable<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? x, int? y);
}
}

View file

@ -1,12 +1,12 @@
using System;
using MediaBrowser.Model.Querying;
using System.Linq;
using System.Threading.Tasks;
namespace MediaBrowser.Model.Activity
{
public interface IActivityRepository
{
void Create(ActivityLogEntry entry);
Task CreateAsync(ActivityLogEntry entry);
QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? z, int? startIndex, int? limit);
IQueryable<ActivityLogEntry> GetActivityLogEntries();
}
}

View file

@ -23,8 +23,7 @@ get_version()
(
local ROOT=${1-$DEFAULT_ROOT}
grep "AssemblyVersion" ${ROOT}/SharedVersion.cs \
| sed -E 's/\[assembly: ?AssemblyVersion\("([0-9\.]+)"\)\]/\1/' \
| sed -E 's/.0$//'
| sed -E 's/\[assembly: ?AssemblyVersion\("([0-9\.]+)"\)\]/\1/'
)
# Run a build

View file

@ -4,4 +4,15 @@ source ../common.build.sh
VERSION=`get_version ../..`
clean_jellyfin ../.. Release `pwd`/dist/jellyfin_${VERSION}
package_temporary_dir="`pwd`/pkg-dist-tmp"
pkg_src_dir="`pwd`/pkg-src"
image_name="jellyfin-rpmbuild"
docker_sudo=""
if ! $(id -Gn | grep -q 'docker') && [ ! ${EUID:-1000} -eq 0 ] && \
[ ! $USER == "root" ] && ! $(echo "$OSTYPE" | grep -q "darwin"); then
docker_sudo=sudo
fi
$docker_sudo docker image rm $image_name --force
rm -rf "$package_temporary_dir"
rm -rf "$pkg_src_dir/jellyfin-${VERSION}.tar.gz"

View file

@ -18,10 +18,15 @@ output_dir="`pwd`/pkg-dist"
pkg_src_dir="`pwd`/pkg-src"
current_user="`whoami`"
image_name="jellyfin-rpmbuild"
docker_sudo=""
if ! $(id -Gn | grep -q 'docker') && [ ! ${EUID:-1000} -eq 0 ] && \
[ ! $USER == "root" ] && ! $(echo "$OSTYPE" | grep -q "darwin"); then
docker_sudo=sudo
fi
cleanup() {
set +o errexit
docker image rm $image_name --force
$docker_sudo docker image rm $image_name --force
rm -rf "$package_temporary_dir"
rm -rf "$pkg_src_dir/jellyfin-${VERSION}.tar.gz"
}
@ -30,7 +35,7 @@ GNU_TAR=1
mkdir -p "$package_temporary_dir"
echo "Bundling all sources for RPM build."
tar \
--transform "s,^\.,jellyfin-${VERSION}" \
--transform "s,^\.,jellyfin-${VERSION}," \
--exclude='.git*' \
--exclude='**/.git' \
--exclude='**/.hg' \
@ -42,10 +47,8 @@ tar \
--exclude='**/.nuget' \
--exclude='*.deb' \
--exclude='*.rpm' \
-Jcvf \
"$package_temporary_dir/jellyfin-${VERSION}.tar.xz" \
-C "../.." \
./ || true && GNU_TAR=0
-zcf "$pkg_src_dir/jellyfin-${VERSION}.tar.gz" \
-C "../.." ./ || GNU_TAR=0
if [ $GNU_TAR -eq 0 ]; then
echo "The installed tar binary did not support --transform. Using workaround."
@ -75,9 +78,9 @@ if [ $GNU_TAR -eq 0 ]; then
tar -zcf "$pkg_src_dir/jellyfin-${VERSION}.tar.gz" -C "$package_temporary_dir" "jellyfin-${VERSION}"
fi
docker build ../.. -t "$image_name" -f ./Dockerfile
$docker_sudo docker build ../.. -t "$image_name" -f ./Dockerfile
mkdir -p "$output_dir"
docker run --rm -v "$package_temporary_dir:/temp" "$image_name" sh -c 'find /build/rpmbuild -maxdepth 3 -type f -name "jellyfin*.rpm" -exec mv {} /temp \;'
$docker_sudo docker run --rm -v "$package_temporary_dir:/temp" "$image_name" sh -c 'find /build/rpmbuild -maxdepth 3 -type f -name "jellyfin*.rpm" -exec mv {} /temp \;'
chown -R "$current_user" "$package_temporary_dir" \
|| sudo chown -R "$current_user" "$package_temporary_dir"
mv "$package_temporary_dir"/*.rpm "$output_dir"