From 9fdaa039c4b1e95842a219499f80ac963bbdb91e Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 13 Oct 2017 15:18:05 -0400 Subject: rework device repository --- .../Devices/DeviceManager.cs | 27 +- .../Devices/DeviceRepository.cs | 212 ---------- .../Devices/SqliteDeviceRepository.cs | 441 +++++++++++++++++++++ 3 files changed, 456 insertions(+), 224 deletions(-) delete mode 100644 Emby.Server.Implementations/Devices/DeviceRepository.cs create mode 100644 Emby.Server.Implementations/Devices/SqliteDeviceRepository.cs (limited to 'Emby.Server.Implementations/Devices') diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index 027a55516c..ee4c4bb265 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.Devices _network = network; } - public async Task RegisterDevice(string reportedId, string name, string appName, string appVersion, string usedByUserId) + public DeviceInfo RegisterDevice(string reportedId, string name, string appName, string appVersion, string usedByUserId) { if (string.IsNullOrWhiteSpace(reportedId)) { @@ -76,14 +76,16 @@ namespace Emby.Server.Implementations.Devices device.DateLastModified = DateTime.UtcNow; - await _repo.SaveDevice(device).ConfigureAwait(false); + device.Name = string.IsNullOrWhiteSpace(device.CustomName) ? device.ReportedName : device.CustomName; + + _repo.SaveDevice(device); return device; } - public Task SaveCapabilities(string reportedId, ClientCapabilities capabilities) + public void SaveCapabilities(string reportedId, ClientCapabilities capabilities) { - return _repo.SaveCapabilities(reportedId, capabilities); + _repo.SaveCapabilities(reportedId, capabilities); } public ClientCapabilities GetCapabilities(string reportedId) @@ -98,13 +100,13 @@ namespace Emby.Server.Implementations.Devices public QueryResult GetDevices(DeviceQuery query) { - IEnumerable devices = _repo.GetDevices().OrderByDescending(i => i.DateLastModified); + IEnumerable devices = _repo.GetDevices(); if (query.SupportsSync.HasValue) { var val = query.SupportsSync.Value; - devices = devices.Where(i => GetCapabilities(i.Id).SupportsSync == val); + devices = devices.Where(i => i.Capabilities.SupportsSync == val); } if (query.SupportsPersistentIdentifier.HasValue) @@ -113,8 +115,7 @@ namespace Emby.Server.Implementations.Devices devices = devices.Where(i => { - var caps = GetCapabilities(i.Id); - var deviceVal = caps.SupportsPersistentIdentifier; + var deviceVal = i.Capabilities.SupportsPersistentIdentifier; return deviceVal == val; }); } @@ -132,9 +133,9 @@ namespace Emby.Server.Implementations.Devices }; } - public Task DeleteDevice(string id) + public void DeleteDevice(string id) { - return _repo.DeleteDevice(id); + _repo.DeleteDevice(id); } public ContentUploadHistory GetCameraUploadHistory(string deviceId) @@ -213,14 +214,16 @@ namespace Emby.Server.Implementations.Devices get { return Path.Combine(_config.CommonApplicationPaths.DataPath, "camerauploads"); } } - public async Task UpdateDeviceInfo(string id, DeviceOptions options) + public void UpdateDeviceInfo(string id, DeviceOptions options) { var device = GetDevice(id); device.CustomName = options.CustomName; device.CameraUploadPath = options.CameraUploadPath; - await _repo.SaveDevice(device).ConfigureAwait(false); + device.Name = string.IsNullOrWhiteSpace(device.CustomName) ? device.ReportedName : device.CustomName; + + _repo.SaveDevice(device); EventHelper.FireEventIfNotNull(DeviceOptionsUpdated, this, new GenericEventArgs(device), _logger); } diff --git a/Emby.Server.Implementations/Devices/DeviceRepository.cs b/Emby.Server.Implementations/Devices/DeviceRepository.cs deleted file mode 100644 index b286a3bb07..0000000000 --- a/Emby.Server.Implementations/Devices/DeviceRepository.cs +++ /dev/null @@ -1,212 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Model.Devices; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Session; -using MediaBrowser.Model.Extensions; - -namespace Emby.Server.Implementations.Devices -{ - public class DeviceRepository : IDeviceRepository - { - private readonly object _syncLock = new object(); - - private readonly IApplicationPaths _appPaths; - private readonly IJsonSerializer _json; - private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; - - private Dictionary _devices; - - public DeviceRepository(IApplicationPaths appPaths, IJsonSerializer json, ILogger logger, IFileSystem fileSystem) - { - _appPaths = appPaths; - _json = json; - _logger = logger; - _fileSystem = fileSystem; - } - - private string GetDevicesPath() - { - return Path.Combine(_appPaths.DataPath, "devices"); - } - - private string GetDevicePath(string id) - { - return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N")); - } - - public Task SaveDevice(DeviceInfo device) - { - var path = Path.Combine(GetDevicePath(device.Id), "device.json"); - _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); - - lock (_syncLock) - { - _json.SerializeToFile(device, path); - _devices[device.Id] = device; - } - return Task.FromResult(true); - } - - public Task SaveCapabilities(string reportedId, ClientCapabilities capabilities) - { - var device = GetDevice(reportedId); - - if (device == null) - { - throw new ArgumentException("No device has been registed with id " + reportedId); - } - - device.Capabilities = capabilities; - SaveDevice(device); - - return Task.FromResult(true); - } - - public ClientCapabilities GetCapabilities(string reportedId) - { - var device = GetDevice(reportedId); - - return device == null ? null : device.Capabilities; - } - - public DeviceInfo GetDevice(string id) - { - if (string.IsNullOrWhiteSpace(id)) - { - throw new ArgumentNullException("id"); - } - - return GetDevices() - .FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)); - } - - public IEnumerable GetDevices() - { - lock (_syncLock) - { - if (_devices == null) - { - _devices = new Dictionary(StringComparer.OrdinalIgnoreCase); - - var devices = LoadDevices().ToList(); - foreach (var device in devices) - { - _devices[device.Id] = device; - } - } - return _devices.Values.ToList(); - } - } - - private IEnumerable LoadDevices() - { - var path = GetDevicesPath(); - - try - { - return _fileSystem - .GetFilePaths(path, true) - .Where(i => string.Equals(Path.GetFileName(i), "device.json", StringComparison.OrdinalIgnoreCase)) - .ToList() - .Select(i => - { - try - { - return _json.DeserializeFromFile(i); - } - catch (Exception ex) - { - _logger.ErrorException("Error reading {0}", ex, i); - return null; - } - }) - .Where(i => i != null); - } - catch (IOException) - { - return new List(); - } - } - - public Task DeleteDevice(string id) - { - var path = GetDevicePath(id); - - lock (_syncLock) - { - try - { - _fileSystem.DeleteDirectory(path, true); - } - catch (IOException) - { - } - - _devices = null; - } - - return Task.FromResult(true); - } - - public ContentUploadHistory GetCameraUploadHistory(string deviceId) - { - var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json"); - - lock (_syncLock) - { - try - { - return _json.DeserializeFromFile(path); - } - catch (IOException) - { - return new ContentUploadHistory - { - DeviceId = deviceId - }; - } - } - } - - public void AddCameraUpload(string deviceId, LocalFileInfo file) - { - var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json"); - _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); - - lock (_syncLock) - { - ContentUploadHistory history; - - try - { - history = _json.DeserializeFromFile(path); - } - catch (IOException) - { - history = new ContentUploadHistory - { - DeviceId = deviceId - }; - } - - history.DeviceId = deviceId; - - var list = history.FilesUploaded.ToList(); - list.Add(file); - history.FilesUploaded = list.ToArray(list.Count); - - _json.SerializeToFile(history, path); - } - } - } -} diff --git a/Emby.Server.Implementations/Devices/SqliteDeviceRepository.cs b/Emby.Server.Implementations/Devices/SqliteDeviceRepository.cs new file mode 100644 index 0000000000..e8b7466a58 --- /dev/null +++ b/Emby.Server.Implementations/Devices/SqliteDeviceRepository.cs @@ -0,0 +1,441 @@ +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.Logging; +using SQLitePCL.pretty; +using MediaBrowser.Model.Extensions; +using MediaBrowser.Model.IO; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Model.Devices; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Session; +using MediaBrowser.Controller.Configuration; + +namespace Emby.Server.Implementations.Devices +{ + public class SqliteDeviceRepository : BaseSqliteRepository, IDeviceRepository + { + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + protected IFileSystem FileSystem { get; private set; } + private readonly object _syncLock = new object(); + private readonly IJsonSerializer _json; + private IServerApplicationPaths _appPaths; + + public SqliteDeviceRepository(ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem, IJsonSerializer json) + : base(logger) + { + var appPaths = config.ApplicationPaths; + + DbFilePath = Path.Combine(appPaths.DataPath, "devices.db"); + FileSystem = fileSystem; + _json = json; + _appPaths = appPaths; + } + + public void Initialize() + { + try + { + InitializeInternal(); + } + catch (Exception ex) + { + Logger.ErrorException("Error loading database file. Will reset and retry.", ex); + + FileSystem.DeleteFile(DbFilePath); + + InitializeInternal(); + } + } + + private void InitializeInternal() + { + using (var connection = CreateConnection()) + { + RunDefaultInitialization(connection); + + string[] queries = { + "create table if not exists Devices (Id TEXT PRIMARY KEY, Name TEXT, ReportedName TEXT, CustomName TEXT, CameraUploadPath TEXT, LastUserName TEXT, AppName TEXT, AppVersion TEXT, LastUserId TEXT, DateLastModified DATETIME, Capabilities TEXT)", + "create index if not exists idx_id on Devices(Id)" + }; + + connection.RunQueries(queries); + + MigrateDevices(); + } + } + + private void MigrateDevices() + { + var files = FileSystem + .GetFilePaths(GetDevicesPath(), true) + .Where(i => string.Equals(Path.GetFileName(i), "device.json", StringComparison.OrdinalIgnoreCase)) + .ToList(); + + foreach (var file in files) + { + try + { + var device = _json.DeserializeFromFile(file); + + SaveDevice(device); + } + catch (Exception ex) + { + Logger.ErrorException("Error reading {0}", ex, file); + } + finally + { + try + { + FileSystem.DeleteFile(file); + } + catch (IOException) + { + try + { + FileSystem.MoveFile(file, Path.ChangeExtension(file, ".old")); + } + catch (IOException) + { + } + } + } + } + } + + private const string BaseSelectText = "select Id, Name, ReportedName, CustomName, CameraUploadPath, LastUserName, AppName, AppVersion, LastUserId, DateLastModified, Capabilities from Devices"; + + public void SaveCapabilities(string deviceId, ClientCapabilities capabilities) + { + using (WriteLock.Write()) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("update devices set Capabilities=@Capabilities where Id=@Id")) + { + statement.TryBind("@Id", deviceId); + + if (capabilities == null) + { + statement.TryBindNull("@Capabilities"); + } + else + { + statement.TryBind("@Capabilities", _json.SerializeToString(capabilities)); + } + + statement.MoveNext(); + } + }, TransactionMode); + } + } + } + + public void SaveDevice(DeviceInfo entry) + { + if (entry == null) + { + throw new ArgumentNullException("entry"); + } + + using (WriteLock.Write()) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("replace into Devices (Id, Name, ReportedName, CustomName, CameraUploadPath, LastUserName, AppName, AppVersion, LastUserId, DateLastModified, Capabilities) values (@Id, @Name, @ReportedName, @CustomName, @CameraUploadPath, @LastUserName, @AppName, @AppVersion, @LastUserId, @DateLastModified, @Capabilities)")) + { + statement.TryBind("@Id", entry.Id); + statement.TryBind("@Name", entry.Name); + statement.TryBind("@ReportedName", entry.ReportedName); + statement.TryBind("@CustomName", entry.CustomName); + statement.TryBind("@CameraUploadPath", entry.CameraUploadPath); + statement.TryBind("@LastUserName", entry.LastUserName); + statement.TryBind("@AppName", entry.AppName); + statement.TryBind("@AppVersion", entry.AppVersion); + statement.TryBind("@DateLastModified", entry.DateLastModified); + + if (entry.Capabilities == null) + { + statement.TryBindNull("@Capabilities"); + } + else + { + statement.TryBind("@Capabilities", _json.SerializeToString(entry.Capabilities)); + } + + statement.MoveNext(); + } + }, TransactionMode); + } + } + } + + public DeviceInfo GetDevice(string id) + { + using (WriteLock.Read()) + { + using (var connection = CreateConnection(true)) + { + var statementTexts = new List(); + statementTexts.Add(BaseSelectText + " where Id=@Id"); + + return connection.RunInTransaction(db => + { + var statements = PrepareAllSafe(db, statementTexts).ToList(); + + using (var statement = statements[0]) + { + statement.TryBind("@Id", id); + + foreach (var row in statement.ExecuteQuery()) + { + return GetEntry(row); + } + } + + return null; + + }, ReadTransactionMode); + } + } + } + + public List GetDevices() + { + using (WriteLock.Read()) + { + using (var connection = CreateConnection(true)) + { + var statementTexts = new List(); + statementTexts.Add(BaseSelectText + " order by DateLastModified desc"); + + return connection.RunInTransaction(db => + { + var list = new List(); + + var statements = PrepareAllSafe(db, statementTexts).ToList(); + + using (var statement = statements[0]) + { + foreach (var row in statement.ExecuteQuery()) + { + list.Add(GetEntry(row)); + } + } + + return list; + + }, ReadTransactionMode); + } + } + } + + public ClientCapabilities GetCapabilities(string id) + { + using (WriteLock.Read()) + { + using (var connection = CreateConnection(true)) + { + var statementTexts = new List(); + statementTexts.Add("Select Capabilities from Devices where Id=@Id"); + + return connection.RunInTransaction(db => + { + var statements = PrepareAllSafe(db, statementTexts).ToList(); + + using (var statement = statements[0]) + { + statement.TryBind("@Id", id); + + foreach (var row in statement.ExecuteQuery()) + { + if (row[0].SQLiteType != SQLiteType.Null) + { + return _json.DeserializeFromString(row.GetString(0)); + } + } + } + + return null; + + }, ReadTransactionMode); + } + } + } + + private DeviceInfo GetEntry(IReadOnlyList reader) + { + var index = 0; + + var info = new DeviceInfo + { + Id = reader.GetString(index) + }; + + index++; + if (reader[index].SQLiteType != SQLiteType.Null) + { + info.Name = reader.GetString(index); + } + + index++; + if (reader[index].SQLiteType != SQLiteType.Null) + { + info.ReportedName = reader.GetString(index); + } + + index++; + if (reader[index].SQLiteType != SQLiteType.Null) + { + info.CustomName = reader.GetString(index); + } + + index++; + if (reader[index].SQLiteType != SQLiteType.Null) + { + info.CameraUploadPath = reader.GetString(index); + } + + index++; + if (reader[index].SQLiteType != SQLiteType.Null) + { + info.LastUserName = reader.GetString(index); + } + + index++; + if (reader[index].SQLiteType != SQLiteType.Null) + { + info.AppName = reader.GetString(index); + } + + index++; + if (reader[index].SQLiteType != SQLiteType.Null) + { + info.AppVersion = reader.GetString(index); + } + + index++; + if (reader[index].SQLiteType != SQLiteType.Null) + { + info.LastUserId = reader.GetString(index); + } + + index++; + if (reader[index].SQLiteType != SQLiteType.Null) + { + info.DateLastModified = reader[index].ReadDateTime(); + } + + index++; + if (reader[index].SQLiteType != SQLiteType.Null) + { + info.Capabilities = _json.DeserializeFromString(reader.GetString(index)); + } + + return info; + } + + private string GetDevicesPath() + { + return Path.Combine(_appPaths.DataPath, "devices"); + } + + private string GetDevicePath(string id) + { + return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N")); + } + + public ContentUploadHistory GetCameraUploadHistory(string deviceId) + { + var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json"); + + lock (_syncLock) + { + try + { + return _json.DeserializeFromFile(path); + } + catch (IOException) + { + return new ContentUploadHistory + { + DeviceId = deviceId + }; + } + } + } + + public void AddCameraUpload(string deviceId, LocalFileInfo file) + { + var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json"); + FileSystem.CreateDirectory(FileSystem.GetDirectoryName(path)); + + lock (_syncLock) + { + ContentUploadHistory history; + + try + { + history = _json.DeserializeFromFile(path); + } + catch (IOException) + { + history = new ContentUploadHistory + { + DeviceId = deviceId + }; + } + + history.DeviceId = deviceId; + + var list = history.FilesUploaded.ToList(); + list.Add(file); + history.FilesUploaded = list.ToArray(list.Count); + + _json.SerializeToFile(history, path); + } + } + + public void DeleteDevice(string id) + { + using (WriteLock.Write()) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("delete from devices where Id=@Id")) + { + statement.TryBind("@Id", id); + + statement.MoveNext(); + } + }, TransactionMode); + } + } + + var path = GetDevicePath(id); + + lock (_syncLock) + { + try + { + FileSystem.DeleteDirectory(path, true); + } + catch (IOException) + { + } + } + } + } +} -- cgit v1.2.3 From 0bc3cdfab7d15a42dadb0101d037cbf12631362b Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 13 Oct 2017 15:21:43 -0400 Subject: update suggestions --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 2 +- Emby.Server.Implementations/Devices/SqliteDeviceRepository.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/Devices') diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index bc4ab8315f..a6c7150daa 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -3038,8 +3038,8 @@ namespace Emby.Server.Implementations.Data { if (orderBy.Count == 0) { - orderBy.Add(new Tuple(ItemSortBy.Random, SortOrder.Ascending)); orderBy.Add(new Tuple("SimilarityScore", SortOrder.Descending)); + orderBy.Add(new Tuple(ItemSortBy.Random, SortOrder.Ascending)); //orderBy.Add(new Tuple(ItemSortBy.Random, SortOrder.Ascending)); } } diff --git a/Emby.Server.Implementations/Devices/SqliteDeviceRepository.cs b/Emby.Server.Implementations/Devices/SqliteDeviceRepository.cs index e8b7466a58..ca0552d98f 100644 --- a/Emby.Server.Implementations/Devices/SqliteDeviceRepository.cs +++ b/Emby.Server.Implementations/Devices/SqliteDeviceRepository.cs @@ -83,6 +83,8 @@ namespace Emby.Server.Implementations.Devices { var device = _json.DeserializeFromFile(file); + device.Name = string.IsNullOrWhiteSpace(device.CustomName) ? device.ReportedName : device.CustomName; + SaveDevice(device); } catch (Exception ex) -- cgit v1.2.3 From 164e7dc896aa71a921f673e2058a2272fc917c4e Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 14 Oct 2017 02:52:56 -0400 Subject: improve live tv direct play --- Emby.Server.Implementations/ApplicationHost.cs | 9 ++- .../Devices/SqliteDeviceRepository.cs | 16 ++++-- .../HttpClientManager/HttpClientInfo.cs | 2 + .../TunerHosts/HdHomerun/HdHomerunHttpStream.cs | 13 ++--- .../TunerHosts/HdHomerun/HdHomerunUdpStream.cs | 7 +-- .../LiveTv/TunerHosts/LiveStream.cs | 34 ++++++++--- MediaBrowser.Api/LiveTv/LiveTvService.cs | 4 +- MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs | 13 ++--- MediaBrowser.Providers/Manager/MetadataService.cs | 66 +++++++++++++++------- .../TV/MissingEpisodeProvider.cs | 4 -- 10 files changed, 110 insertions(+), 58 deletions(-) (limited to 'Emby.Server.Implementations/Devices') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 96c87b6a50..91493cf53c 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -792,6 +792,11 @@ namespace Emby.Server.Implementations protected abstract IConnectManager CreateConnectManager(); protected abstract ISyncManager CreateSyncManager(); + + protected virtual IHttpClient CreateHttpClient() + { + return new HttpClientManager.HttpClientManager(ApplicationPaths, LogManager.GetLogger("HttpClient"), FileSystemManager, MemoryStreamFactory, GetDefaultUserAgent); + } /// /// Registers resources that classes will depend on @@ -814,7 +819,7 @@ namespace Emby.Server.Implementations RegisterSingleInstance(FileSystemManager); - HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, LogManager.GetLogger("HttpClient"), FileSystemManager, MemoryStreamFactory, GetDefaultUserAgent); + HttpClient = CreateHttpClient(); RegisterSingleInstance(HttpClient); RegisterSingleInstance(NetworkManager); @@ -1118,7 +1123,7 @@ namespace Emby.Server.Implementations IsoManager.AddParts(list); } - private string GetDefaultUserAgent() + protected string GetDefaultUserAgent() { var name = FormatAttribute(Name); diff --git a/Emby.Server.Implementations/Devices/SqliteDeviceRepository.cs b/Emby.Server.Implementations/Devices/SqliteDeviceRepository.cs index ca0552d98f..a15eb3558b 100644 --- a/Emby.Server.Implementations/Devices/SqliteDeviceRepository.cs +++ b/Emby.Server.Implementations/Devices/SqliteDeviceRepository.cs @@ -72,10 +72,18 @@ namespace Emby.Server.Implementations.Devices private void MigrateDevices() { - var files = FileSystem - .GetFilePaths(GetDevicesPath(), true) - .Where(i => string.Equals(Path.GetFileName(i), "device.json", StringComparison.OrdinalIgnoreCase)) - .ToList(); + List files; + try + { + files = FileSystem + .GetFilePaths(GetDevicesPath(), true) + .Where(i => string.Equals(Path.GetFileName(i), "device.json", StringComparison.OrdinalIgnoreCase)) + .ToList(); + } + catch (IOException) + { + return; + } foreach (var file in files) { diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientInfo.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientInfo.cs index 6d17bf94de..21cec9d2be 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientInfo.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientInfo.cs @@ -1,4 +1,5 @@ using System; +using System.Net.Http; namespace Emby.Server.Implementations.HttpClientManager { @@ -12,5 +13,6 @@ namespace Emby.Server.Implementations.HttpClientManager /// /// The last timeout. public DateTime LastTimeout { get; set; } + public HttpClient HttpClient { get; set; } } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs index 5e3923972d..ad9c0d8943 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs @@ -22,7 +22,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun private readonly IHttpClient _httpClient; private readonly IServerApplicationHost _appHost; - private readonly CancellationTokenSource _liveStreamCancellationTokenSource = new CancellationTokenSource(); private readonly TaskCompletionSource _liveStreamTaskCompletionSource = new TaskCompletionSource(); public HdHomerunHttpStream(MediaSourceInfo mediaSource, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, IEnvironmentInfo environment) @@ -35,7 +34,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun protected override Task OpenInternal(CancellationToken openCancellationToken) { - _liveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested(); + LiveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested(); var mediaSource = OriginalMediaSource; @@ -45,7 +44,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun var taskCompletionSource = new TaskCompletionSource(); - StartStreaming(url, taskCompletionSource, _liveStreamCancellationTokenSource.Token); + StartStreaming(url, taskCompletionSource, LiveStreamCancellationTokenSource.Token); //OpenedMediaSource.Protocol = MediaProtocol.File; //OpenedMediaSource.Path = tempFile; @@ -65,12 +64,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun //await Task.Delay(5000).ConfigureAwait(false); } - public override Task Close() + public override async Task Close() { Logger.Info("Closing HDHR live stream"); - _liveStreamCancellationTokenSource.Cancel(); + LiveStreamCancellationTokenSource.Cancel(); - return _liveStreamTaskCompletionSource.Task; + await _liveStreamTaskCompletionSource.Task.ConfigureAwait(false); + await DeleteTempFile(TempFilePath).ConfigureAwait(false); } private Task StartStreaming(string url, TaskCompletionSource openTaskCompletionSource, CancellationToken cancellationToken) @@ -112,7 +112,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } _liveStreamTaskCompletionSource.TrySetResult(true); - await DeleteTempFile(TempFilePath).ConfigureAwait(false); }); } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index ff8fd1bc4b..4187fcd8fb 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -26,7 +26,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun private readonly IServerApplicationHost _appHost; private readonly ISocketFactory _socketFactory; - private readonly CancellationTokenSource _liveStreamCancellationTokenSource = new CancellationTokenSource(); private readonly TaskCompletionSource _liveStreamTaskCompletionSource = new TaskCompletionSource(); private readonly IHdHomerunChannelCommands _channelCommands; private readonly int _numTuners; @@ -45,7 +44,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun protected override Task OpenInternal(CancellationToken openCancellationToken) { - _liveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested(); + LiveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested(); var mediaSource = OriginalMediaSource; @@ -56,7 +55,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun var taskCompletionSource = new TaskCompletionSource(); - StartStreaming(uri.Host, localPort, taskCompletionSource, _liveStreamCancellationTokenSource.Token); + StartStreaming(uri.Host, localPort, taskCompletionSource, LiveStreamCancellationTokenSource.Token); //OpenedMediaSource.Protocol = MediaProtocol.File; //OpenedMediaSource.Path = tempFile; @@ -76,7 +75,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public override Task Close() { Logger.Info("Closing HDHR UDP live stream"); - _liveStreamCancellationTokenSource.Cancel(); + LiveStreamCancellationTokenSource.Cancel(); return _liveStreamTaskCompletionSource.Task; } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index 5be91c6c1b..685f794fde 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -32,6 +32,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts protected readonly string TempFilePath; protected readonly ILogger Logger; + protected readonly CancellationTokenSource LiveStreamCancellationTokenSource = new CancellationTokenSource(); public LiveStream(MediaSourceInfo mediaSource, IEnvironmentInfo environment, IFileSystem fileSystem, ILogger logger, IServerApplicationPaths appPaths) { @@ -80,6 +81,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts FileSystem.DeleteFile(path); return; } + catch (DirectoryNotFoundException) + { + return; + } + catch (FileNotFoundException) + { + return; + } catch { @@ -96,6 +105,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public async Task CopyToAsync(Stream stream, CancellationToken cancellationToken) { + cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token).Token; + var allowAsync = false;//Environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows; // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 @@ -110,16 +121,27 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts private static async Task CopyTo(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken) { byte[] buffer = new byte[bufferSize]; - while (true) + + var eofCount = 0; + var emptyReadLimit = 1000; + + while (eofCount < emptyReadLimit) { cancellationToken.ThrowIfCancellationRequested(); - var read = source.Read(buffer, 0, buffer.Length); + var bytesRead = source.Read(buffer, 0, buffer.Length); - if (read > 0) + if (bytesRead == 0) + { + eofCount++; + await Task.Delay(10, cancellationToken).ConfigureAwait(false); + } + else { + eofCount = 0; + //await destination.WriteAsync(buffer, 0, read).ConfigureAwait(false); - destination.Write(buffer, 0, read); + destination.Write(buffer, 0, bytesRead); if (onStarted != null) { @@ -127,10 +149,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts onStarted = null; } } - else - { - await Task.Delay(10).ConfigureAwait(false); - } } } diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 1ae7323dc4..703c96e0c6 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -734,7 +734,7 @@ namespace MediaBrowser.Api.LiveTv outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType(path); - return new ProgressiveFileCopier(_fileSystem, path, outputHeaders, Logger, _environment, CancellationToken.None) + return new ProgressiveFileCopier(_fileSystem, path, outputHeaders, Logger, _environment) { AllowEndOfFile = false }; @@ -753,7 +753,7 @@ namespace MediaBrowser.Api.LiveTv outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType("file." + request.Container); - return new ProgressiveFileCopier(directStreamProvider, outputHeaders, Logger, _environment, CancellationToken.None) + return new ProgressiveFileCopier(directStreamProvider, outputHeaders, Logger, _environment) { AllowEndOfFile = false }; diff --git a/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs b/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs index 9ce109fc40..74293ccd90 100644 --- a/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs +++ b/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs @@ -16,7 +16,6 @@ namespace MediaBrowser.Api.LiveTv private readonly IFileSystem _fileSystem; private readonly ILogger _logger; private readonly string _path; - private readonly CancellationToken _cancellationToken; private readonly Dictionary _outputHeaders; const int StreamCopyToBufferSize = 81920; @@ -28,22 +27,20 @@ namespace MediaBrowser.Api.LiveTv private readonly IDirectStreamProvider _directStreamProvider; private readonly IEnvironmentInfo _environment; - public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary outputHeaders, ILogger logger, IEnvironmentInfo environment, CancellationToken cancellationToken) + public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary outputHeaders, ILogger logger, IEnvironmentInfo environment) { _fileSystem = fileSystem; _path = path; _outputHeaders = outputHeaders; _logger = logger; - _cancellationToken = cancellationToken; _environment = environment; } - public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, Dictionary outputHeaders, ILogger logger, IEnvironmentInfo environment, CancellationToken cancellationToken) + public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, Dictionary outputHeaders, ILogger logger, IEnvironmentInfo environment) { _directStreamProvider = directStreamProvider; _outputHeaders = outputHeaders; _logger = logger; - _cancellationToken = cancellationToken; _environment = environment; } @@ -69,8 +66,6 @@ namespace MediaBrowser.Api.LiveTv public async Task WriteToAsync(Stream outputStream, CancellationToken cancellationToken) { - cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationToken).Token; - if (_directStreamProvider != null) { await _directStreamProvider.CopyToAsync(outputStream, cancellationToken).ConfigureAwait(false); @@ -89,7 +84,9 @@ namespace MediaBrowser.Api.LiveTv inputStream.Position = StartPosition; } - while (eofCount < 20 || !AllowEndOfFile) + var emptyReadLimit = AllowEndOfFile ? 20 : 100; + + while (eofCount < emptyReadLimit) { int bytesRead; if (allowAsyncFileRead) diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index c37e05d957..3759670d4f 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; using MediaBrowser.Model.IO; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Providers; +using MediaBrowser.Providers.MediaInfo; namespace MediaBrowser.Providers.Manager { @@ -37,6 +38,28 @@ namespace MediaBrowser.Providers.Manager LibraryManager = libraryManager; } + private bool RequiresRefresh(IHasMetadata item, IDirectoryService directoryService) + { + if (item.RequiresRefresh()) + { + return true; + } + + if (item.SupportsLocalMetadata) + { + var video = item as Video; + + if (video != null && !video.IsPlaceHolder) + { + return !video.SubtitleFiles + .SequenceEqual(SubtitleResolver.GetSubtitleFiles(video, directoryService, FileSystem, false) + .OrderBy(i => i), StringComparer.OrdinalIgnoreCase); + } + } + + return false; + } + public async Task RefreshMetadata(IHasMetadata item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken) { var itemOfType = (TItemType)item; @@ -47,19 +70,35 @@ namespace MediaBrowser.Providers.Manager var libraryOptions = LibraryManager.GetLibraryOptions((BaseItem)item); - if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None) + DateTime? newDateModified = null; + if (item.LocationType == LocationType.FileSystem) { - // TODO: If this returns true, should we instead just change metadata refresh mode to Full? - requiresRefresh = item.RequiresRefresh(); + var file = refreshOptions.DirectoryService.GetFile(item.Path); + if (file != null) + { + newDateModified = file.LastWriteTimeUtc; + if (item.EnableRefreshOnDateModifiedChange) + { + if (newDateModified != item.DateModified) + { + Logger.Debug("Date modified for {0}. Old date {1} new date {2} Id {3}", item.Path, item.DateModified, newDateModified, item.Id); + requiresRefresh = true; + } + } + } } - if (!requiresRefresh && - libraryOptions.AutomaticRefreshIntervalDays > 0 && - (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= libraryOptions.AutomaticRefreshIntervalDays) + if (!requiresRefresh && libraryOptions.AutomaticRefreshIntervalDays > 0 && (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= libraryOptions.AutomaticRefreshIntervalDays) { requiresRefresh = true; } + if (!requiresRefresh && refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None) + { + // TODO: If this returns true, should we instead just change metadata refresh mode to Full? + requiresRefresh = RequiresRefresh(item, refreshOptions.DirectoryService); + } + var itemImageProvider = new ItemImageProvider(Logger, ProviderManager, ServerConfigurationManager, FileSystem); var localImagesFailed = false; @@ -145,20 +184,9 @@ namespace MediaBrowser.Providers.Manager var beforeSaveResult = BeforeSave(itemOfType, isFirstRefresh || refreshOptions.ReplaceAllMetadata || refreshOptions.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || requiresRefresh, updateType); updateType = updateType | beforeSaveResult; - if (item.LocationType == LocationType.FileSystem) + if (newDateModified.HasValue) { - var file = refreshOptions.DirectoryService.GetFile(item.Path); - if (file != null) - { - var fileLastWriteTime = file.LastWriteTimeUtc; - if (item.EnableRefreshOnDateModifiedChange && fileLastWriteTime != item.DateModified) - { - Logger.Debug("Date modified for {0}. Old date {1} new date {2} Id {3}", item.Path, item.DateModified, fileLastWriteTime, item.Id); - requiresRefresh = true; - } - - item.DateModified = fileLastWriteTime; - } + item.DateModified = newDateModified.Value; } // Save if changes were made, or it's never been saved before diff --git a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs index 44e3cff6a1..b68e6e4a9f 100644 --- a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs @@ -350,8 +350,6 @@ namespace MediaBrowser.Providers.TV foreach (var episodeToRemove in episodesToRemove.Select(e => e.Episode)) { - _logger.Info("Removing missing/unaired episode {0} {1}x{2}", episodeToRemove.Series.Name, episodeToRemove.ParentIndexNumber, episodeToRemove.IndexNumber); - await episodeToRemove.Delete(new DeleteOptions { DeleteFileLocation = true @@ -418,8 +416,6 @@ namespace MediaBrowser.Providers.TV foreach (var seasonToRemove in seasonsToRemove) { - _logger.Info("Removing virtual season {0} {1}", seasonToRemove.Series.Name, seasonToRemove.IndexNumber); - await seasonToRemove.Delete(new DeleteOptions { DeleteFileLocation = true -- cgit v1.2.3 From e7ecd418c43b966bac3c34d09e6f97c1176a749c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 21 Oct 2017 12:39:52 -0400 Subject: update home video library support --- .../Devices/CameraUploadsFolder.cs | 2 +- .../Emby.Server.Implementations.csproj | 2 +- .../Images/BaseDynamicImageProvider.cs | 32 +++---- .../Library/Resolvers/Movies/MovieResolver.cs | 54 ++++++----- .../Library/Resolvers/PhotoAlbumResolver.cs | 46 ++++++++-- .../Library/Resolvers/PhotoResolver.cs | 33 ++++--- .../Localization/Core/ru.json | 6 +- .../Localization/LocalizationManager.cs | 18 +++- .../Photos/PhotoAlbumImageProvider.cs | 34 ------- .../UserViews/CollectionFolderImageProvider.cs | 16 ---- .../UserViews/DynamicImageProvider.cs | 17 ---- .../UserViews/FolderImageProvider.cs | 100 +++++++++++++++++++++ .../MediaBrowser.Providers.csproj | 1 - .../Music/AlbumImageFromSongProvider.cs | 46 ---------- 14 files changed, 234 insertions(+), 173 deletions(-) delete mode 100644 Emby.Server.Implementations/Photos/PhotoAlbumImageProvider.cs create mode 100644 Emby.Server.Implementations/UserViews/FolderImageProvider.cs delete mode 100644 MediaBrowser.Providers/Music/AlbumImageFromSongProvider.cs (limited to 'Emby.Server.Implementations/Devices') diff --git a/Emby.Server.Implementations/Devices/CameraUploadsFolder.cs b/Emby.Server.Implementations/Devices/CameraUploadsFolder.cs index 55063872c1..5c205dd199 100644 --- a/Emby.Server.Implementations/Devices/CameraUploadsFolder.cs +++ b/Emby.Server.Implementations/Devices/CameraUploadsFolder.cs @@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.Devices [IgnoreDataMember] public override string CollectionType { - get { return MediaBrowser.Model.Entities.CollectionType.Photos; } + get { return MediaBrowser.Model.Entities.CollectionType.HomeVideos; } } [IgnoreDataMember] diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 083a147889..1ecbabf2f4 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -435,7 +435,6 @@ - @@ -573,6 +572,7 @@ + diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 3f9ea79c6a..5cd7e4262e 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -42,23 +42,14 @@ namespace Emby.Server.Implementations.Images return true; } - public virtual IEnumerable GetSupportedImages(IHasMetadata item) + public virtual ImageType[] GetSupportedImages(IHasMetadata item) { - return new List + return new ImageType[] { - ImageType.Primary, - ImageType.Thumb + ImageType.Primary }; } - private IEnumerable GetEnabledImages(IHasMetadata item) - { - //var options = ProviderManager.GetMetadataOptions(item); - - return GetSupportedImages(item); - //return GetSupportedImages(item).Where(i => IsEnabled(options, i, item)).ToList(); - } - public async Task FetchAsync(T item, MetadataRefreshOptions options, CancellationToken cancellationToken) { if (!Supports(item)) @@ -67,7 +58,7 @@ namespace Emby.Server.Implementations.Images } var updateType = ItemUpdateType.None; - var supportedImages = GetEnabledImages(item).ToList(); + var supportedImages = GetSupportedImages(item); if (supportedImages.Contains(ImageType.Primary)) { @@ -253,7 +244,7 @@ namespace Emby.Server.Implementations.Images return false; } - var supportedImages = GetEnabledImages(item).ToList(); + var supportedImages = GetSupportedImages(item); if (supportedImages.Contains(ImageType.Primary) && HasChanged(item, ImageType.Primary)) { @@ -283,8 +274,7 @@ namespace Emby.Server.Implementations.Images return false; } - var age = DateTime.UtcNow - image.DateModified; - if (age.TotalDays <= MaxImageAgeDays) + if (!HasChangedByDate(item, image)) { return false; } @@ -293,6 +283,16 @@ namespace Emby.Server.Implementations.Images return true; } + protected virtual bool HasChangedByDate(IHasMetadata item, ItemImageInfo image) + { + var age = DateTime.UtcNow - image.DateModified; + if (age.TotalDays <= MaxImageAgeDays) + { + return false; + } + return true; + } + protected List GetFinalItems(IEnumerable items) { return GetFinalItems(items, 4); diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index fc5cab0a0b..94cafa3e2d 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -11,7 +11,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; - +using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; @@ -216,12 +216,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie(args.Path, args.Parent, files, args.DirectoryService, collectionType, true, false); + return FindMovie(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false); } if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie