From e0a213601b3efa24565b41eaf7ed1d4b36aeec9a Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 26 May 2016 00:11:27 -0400 Subject: stub out file refresher --- .../IO/FileRefresher.cs | 241 +++++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 MediaBrowser.Server.Implementations/IO/FileRefresher.cs (limited to 'MediaBrowser.Server.Implementations/IO/FileRefresher.cs') diff --git a/MediaBrowser.Server.Implementations/IO/FileRefresher.cs b/MediaBrowser.Server.Implementations/IO/FileRefresher.cs new file mode 100644 index 000000000..74dfbc679 --- /dev/null +++ b/MediaBrowser.Server.Implementations/IO/FileRefresher.cs @@ -0,0 +1,241 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CommonIO; +using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Logging; +using MediaBrowser.Server.Implementations.ScheduledTasks; + +namespace MediaBrowser.Server.Implementations.IO +{ + public class FileRefresher : IDisposable + { + private ILogger Logger { get; set; } + private ITaskManager TaskManager { get; set; } + private ILibraryManager LibraryManager { get; set; } + private IServerConfigurationManager ConfigurationManager { get; set; } + private readonly IFileSystem _fileSystem; + private readonly List _affectedPaths = new List(); + private Timer _timer; + private readonly object _timerLock = new object(); + + public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger) + { + _affectedPaths.Add(path); + + _fileSystem = fileSystem; + ConfigurationManager = configurationManager; + LibraryManager = libraryManager; + TaskManager = taskManager; + Logger = logger; + } + + private void RestartTimer() + { + lock (_timerLock) + { + if (_timer == null) + { + _timer = new Timer(OnTimerCallback, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1)); + } + else + { + _timer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1)); + } + } + } + + private async void OnTimerCallback(object state) + { + // Extend the timer as long as any of the paths are still being written to. + if (_affectedPaths.Any(IsFileLocked)) + { + Logger.Info("Timer extended."); + RestartTimer(); + return; + } + + Logger.Debug("Timer stopped."); + + DisposeTimer(); + + try + { + await ProcessPathChanges(_affectedPaths).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.ErrorException("Error processing directory changes", ex); + } + } + + private async Task ProcessPathChanges(List paths) + { + var itemsToRefresh = paths + .Select(GetAffectedBaseItem) + .Where(item => item != null) + .Distinct() + .ToList(); + + foreach (var p in paths) + { + Logger.Info(p + " reports change."); + } + + // If the root folder changed, run the library task so the user can see it + if (itemsToRefresh.Any(i => i is AggregateFolder)) + { + TaskManager.CancelIfRunningAndQueue(); + return; + } + + foreach (var item in itemsToRefresh) + { + Logger.Info(item.Name + " (" + item.Path + ") will be refreshed."); + + try + { + await item.ChangedExternally().ConfigureAwait(false); + } + catch (IOException ex) + { + // For now swallow and log. + // Research item: If an IOException occurs, the item may be in a disconnected state (media unavailable) + // Should we remove it from it's parent? + Logger.ErrorException("Error refreshing {0}", ex, item.Name); + } + catch (Exception ex) + { + Logger.ErrorException("Error refreshing {0}", ex, item.Name); + } + } + } + + /// + /// Gets the affected base item. + /// + /// The path. + /// BaseItem. + private BaseItem GetAffectedBaseItem(string path) + { + BaseItem item = null; + + while (item == null && !string.IsNullOrEmpty(path)) + { + item = LibraryManager.FindByPath(path, null); + + path = Path.GetDirectoryName(path); + } + + if (item != null) + { + // If the item has been deleted find the first valid parent that still exists + while (!_fileSystem.DirectoryExists(item.Path) && !_fileSystem.FileExists(item.Path)) + { + item = item.GetParent(); + + if (item == null) + { + break; + } + } + } + + return item; + } + + private bool IsFileLocked(string path) + { + if (Environment.OSVersion.Platform != PlatformID.Win32NT) + { + // Causing lockups on linux + return false; + } + + try + { + var data = _fileSystem.GetFileSystemInfo(path); + + if (!data.Exists + || data.IsDirectory + + // Opening a writable stream will fail with readonly files + || data.Attributes.HasFlag(FileAttributes.ReadOnly)) + { + return false; + } + } + catch (IOException) + { + return false; + } + catch (Exception ex) + { + Logger.ErrorException("Error getting file system info for: {0}", ex, path); + return false; + } + + // In order to determine if the file is being written to, we have to request write access + // But if the server only has readonly access, this is going to cause this entire algorithm to fail + // So we'll take a best guess about our access level + var requestedFileAccess = ConfigurationManager.Configuration.SaveLocalMeta + ? FileAccess.ReadWrite + : FileAccess.Read; + + try + { + using (_fileSystem.GetFileStream(path, FileMode.Open, requestedFileAccess, FileShare.ReadWrite)) + { + //file is not locked + return false; + } + } + catch (DirectoryNotFoundException) + { + // File may have been deleted + return false; + } + catch (FileNotFoundException) + { + // File may have been deleted + return false; + } + catch (IOException) + { + //the file is unavailable because it is: + //still being written to + //or being processed by another thread + //or does not exist (has already been processed) + Logger.Debug("{0} is locked.", path); + return true; + } + catch (Exception ex) + { + Logger.ErrorException("Error determining if file is locked: {0}", ex, path); + return false; + } + } + + public void DisposeTimer() + { + lock (_timerLock) + { + if (_timer != null) + { + _timer.Dispose(); + } + } + } + + public void Dispose() + { + DisposeTimer(); + } + } +} -- cgit v1.2.3 From 6b31083e435b24e7a7accc4ba190940457e18477 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 26 May 2016 16:54:05 -0400 Subject: separate file monitor to avoid paths getting stuck --- .../IO/FileRefresher.cs | 49 +++- .../IO/LibraryMonitor.cs | 276 +++++---------------- 2 files changed, 104 insertions(+), 221 deletions(-) (limited to 'MediaBrowser.Server.Implementations/IO/FileRefresher.cs') diff --git a/MediaBrowser.Server.Implementations/IO/FileRefresher.cs b/MediaBrowser.Server.Implementations/IO/FileRefresher.cs index 74dfbc679..18c52ab29 100644 --- a/MediaBrowser.Server.Implementations/IO/FileRefresher.cs +++ b/MediaBrowser.Server.Implementations/IO/FileRefresher.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using CommonIO; +using MediaBrowser.Common.Events; using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -24,9 +25,14 @@ namespace MediaBrowser.Server.Implementations.IO private readonly List _affectedPaths = new List(); private Timer _timer; private readonly object _timerLock = new object(); + public string Path { get; private set; } + + public event EventHandler Completed; public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger) { + logger.Debug("New file refresher created for {0}", path); + Path = path; _affectedPaths.Add(path); _fileSystem = fileSystem; @@ -36,7 +42,24 @@ namespace MediaBrowser.Server.Implementations.IO Logger = logger; } - private void RestartTimer() + private void AddAffectedPath(string path) + { + if (!_affectedPaths.Contains(path, StringComparer.Ordinal)) + { + _affectedPaths.Add(path); + } + } + + public void AddPath(string path) + { + lock (_timerLock) + { + AddAffectedPath(path); + } + RestartTimer(); + } + + public void RestartTimer() { lock (_timerLock) { @@ -51,6 +74,23 @@ namespace MediaBrowser.Server.Implementations.IO } } + public void ResetPath(string path, string affectedFile) + { + lock (_timerLock) + { + Logger.Debug("Resetting file refresher from {0} to {1}", Path, path); + + Path = path; + AddAffectedPath(path); + + if (!string.IsNullOrWhiteSpace(affectedFile)) + { + AddAffectedPath(affectedFile); + } + } + RestartTimer(); + } + private async void OnTimerCallback(object state) { // Extend the timer as long as any of the paths are still being written to. @@ -64,10 +104,11 @@ namespace MediaBrowser.Server.Implementations.IO Logger.Debug("Timer stopped."); DisposeTimer(); + EventHelper.FireEventIfNotNull(Completed, this, EventArgs.Empty, Logger); try { - await ProcessPathChanges(_affectedPaths).ConfigureAwait(false); + await ProcessPathChanges(_affectedPaths.ToList()).ConfigureAwait(false); } catch (Exception ex) { @@ -130,7 +171,7 @@ namespace MediaBrowser.Server.Implementations.IO { item = LibraryManager.FindByPath(path, null); - path = Path.GetDirectoryName(path); + path = System.IO.Path.GetDirectoryName(path); } if (item != null) @@ -222,7 +263,7 @@ namespace MediaBrowser.Server.Implementations.IO } } - public void DisposeTimer() + private void DisposeTimer() { lock (_timerLock) { diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index 09ca134d1..0de6ff306 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -26,13 +26,9 @@ namespace MediaBrowser.Server.Implementations.IO /// private readonly ConcurrentDictionary _fileSystemWatchers = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); /// - /// The update timer - /// - private Timer _updateTimer; - /// /// The affected paths /// - private readonly ConcurrentDictionary _affectedPaths = new ConcurrentDictionary(); + private readonly List _activeRefreshers = new List(); /// /// A dynamic list of paths that should be ignored. Added to during our own file sytem modifications. @@ -44,8 +40,8 @@ namespace MediaBrowser.Server.Implementations.IO /// private readonly IReadOnlyList _alwaysIgnoreFiles = new List { - "thumbs.db", - "small.jpg", + "thumbs.db", + "small.jpg", "albumart.jpg", // WMC temp recording directories that will constantly be written to @@ -53,11 +49,6 @@ namespace MediaBrowser.Server.Implementations.IO "TempSBE" }; - /// - /// The timer lock - /// - private readonly object _timerLock = new object(); - /// /// Add the path to our temporary ignore list. Use when writing to a path within our listening scope. /// @@ -463,226 +454,58 @@ namespace MediaBrowser.Server.Implementations.IO if (monitorPath) { // Avoid implicitly captured closure - var affectedPath = path; - _affectedPaths.AddOrUpdate(path, path, (key, oldValue) => affectedPath); - } - - RestartTimer(); - } - - private void RestartTimer() - { - lock (_timerLock) - { - if (_updateTimer == null) - { - _updateTimer = new Timer(TimerStopped, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1)); - } - else - { - _updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1)); - } - } - } - - /// - /// Timers the stopped. - /// - /// The state info. - private async void TimerStopped(object stateInfo) - { - // Extend the timer as long as any of the paths are still being written to. - if (_affectedPaths.Any(p => IsFileLocked(p.Key))) - { - Logger.Info("Timer extended."); - RestartTimer(); - return; - } - - Logger.Debug("Timer stopped."); - - DisposeTimer(); - - var paths = _affectedPaths.Keys.ToList(); - _affectedPaths.Clear(); - - try - { - await ProcessPathChanges(paths).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.ErrorException("Error processing directory changes", ex); + CreateRefresher(path); } } - private bool IsFileLocked(string path) + private void CreateRefresher(string path) { - if (Environment.OSVersion.Platform != PlatformID.Win32NT) - { - // Causing lockups on linux - return false; - } + var parentPath = Path.GetDirectoryName(path); - try + lock (_activeRefreshers) { - var data = _fileSystem.GetFileSystemInfo(path); - - if (!data.Exists - || data.IsDirectory - - // Opening a writable stream will fail with readonly files - || data.Attributes.HasFlag(FileAttributes.ReadOnly)) + var refreshers = _activeRefreshers.ToList(); + foreach (var refresher in refreshers) { - return false; - } - } - catch (IOException) - { - return false; - } - catch (Exception ex) - { - Logger.ErrorException("Error getting file system info for: {0}", ex, path); - return false; - } - - // In order to determine if the file is being written to, we have to request write access - // But if the server only has readonly access, this is going to cause this entire algorithm to fail - // So we'll take a best guess about our access level - var requestedFileAccess = ConfigurationManager.Configuration.SaveLocalMeta - ? FileAccess.ReadWrite - : FileAccess.Read; + // Path is already being refreshed + if (string.Equals(path, refresher.Path, StringComparison.Ordinal)) + { + refresher.RestartTimer(); + return; + } - try - { - using (_fileSystem.GetFileStream(path, FileMode.Open, requestedFileAccess, FileShare.ReadWrite)) - { - if (_updateTimer != null) + // Parent folder is already being refreshed + if (_fileSystem.ContainsSubPath(refresher.Path, path)) { - //file is not locked - return false; + refresher.AddPath(path); + return; } - } - } - catch (DirectoryNotFoundException) - { - // File may have been deleted - return false; - } - catch (FileNotFoundException) - { - // File may have been deleted - return false; - } - catch (IOException) - { - //the file is unavailable because it is: - //still being written to - //or being processed by another thread - //or does not exist (has already been processed) - Logger.Debug("{0} is locked.", path); - return true; - } - catch (Exception ex) - { - Logger.ErrorException("Error determining if file is locked: {0}", ex, path); - return false; - } - return false; - } + // New path is a parent + if (_fileSystem.ContainsSubPath(path, refresher.Path)) + { + refresher.ResetPath(path, null); + return; + } - private void DisposeTimer() - { - lock (_timerLock) - { - if (_updateTimer != null) - { - _updateTimer.Dispose(); - _updateTimer = null; + // Siblings + if (string.Equals(parentPath, Path.GetDirectoryName(refresher.Path), StringComparison.Ordinal)) + { + refresher.ResetPath(parentPath, path); + return; + } } - } - } - - /// - /// Processes the path changes. - /// - /// The paths. - /// Task. - private async Task ProcessPathChanges(List paths) - { - var itemsToRefresh = paths - .Select(GetAffectedBaseItem) - .Where(item => item != null) - .Distinct() - .ToList(); - - foreach (var p in paths) - { - Logger.Info(p + " reports change."); - } - - // If the root folder changed, run the library task so the user can see it - if (itemsToRefresh.Any(i => i is AggregateFolder)) - { - TaskManager.CancelIfRunningAndQueue(); - return; - } - - foreach (var item in itemsToRefresh) - { - Logger.Info(item.Name + " (" + item.Path + ") will be refreshed."); - try - { - await item.ChangedExternally().ConfigureAwait(false); - } - catch (IOException ex) - { - // For now swallow and log. - // Research item: If an IOException occurs, the item may be in a disconnected state (media unavailable) - // Should we remove it from it's parent? - Logger.ErrorException("Error refreshing {0}", ex, item.Name); - } - catch (Exception ex) - { - Logger.ErrorException("Error refreshing {0}", ex, item.Name); - } + var newRefresher = new FileRefresher(path, _fileSystem, ConfigurationManager, LibraryManager, TaskManager, Logger); + newRefresher.Completed += NewRefresher_Completed; + _activeRefreshers.Add(newRefresher); } } - /// - /// Gets the affected base item. - /// - /// The path. - /// BaseItem. - private BaseItem GetAffectedBaseItem(string path) + private void NewRefresher_Completed(object sender, EventArgs e) { - BaseItem item = null; - - while (item == null && !string.IsNullOrEmpty(path)) - { - item = LibraryManager.FindByPath(path, null); - - path = Path.GetDirectoryName(path); - } - - if (item != null) - { - // If the item has been deleted find the first valid parent that still exists - while (!_fileSystem.DirectoryExists(item.Path) && !_fileSystem.FileExists(item.Path)) - { - item = item.GetParent(); - - if (item == null) - { - break; - } - } - } - - return item; + var refresher = (FileRefresher)sender; + DisposeRefresher(refresher); } /// @@ -713,10 +536,29 @@ namespace MediaBrowser.Server.Implementations.IO watcher.Dispose(); } - DisposeTimer(); - _fileSystemWatchers.Clear(); - _affectedPaths.Clear(); + DisposeRefreshers(); + } + + private void DisposeRefresher(FileRefresher refresher) + { + lock (_activeRefreshers) + { + refresher.Dispose(); + _activeRefreshers.Remove(refresher); + } + } + + private void DisposeRefreshers() + { + lock (_activeRefreshers) + { + foreach (var refresher in _activeRefreshers.ToList()) + { + refresher.Dispose(); + } + _activeRefreshers.Clear(); + } } /// -- cgit v1.2.3 From dc5c15c60b598a58c924daa350dfaf9f6b7d1c17 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 11 Jun 2016 11:56:15 -0400 Subject: update elements --- MediaBrowser.Dlna/PlayTo/PlayToManager.cs | 2 + .../Activity/ActivityRepository.cs | 248 ++++++++++----------- .../EntryPoints/ExternalPortForwarding.cs | 36 ++- .../IO/FileRefresher.cs | 11 +- MediaBrowser.Server.Mono/Native/DbConnector.cs | 4 +- .../ApplicationHost.cs | 34 +-- .../Native/DbConnector.cs | 13 +- MediaBrowser.WebDashboard/Api/DashboardService.cs | 4 +- 8 files changed, 183 insertions(+), 169 deletions(-) (limited to 'MediaBrowser.Server.Implementations/IO/FileRefresher.cs') diff --git a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs index bbb9bf6de..cd9a7b1f0 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs @@ -101,6 +101,7 @@ namespace MediaBrowser.Dlna.PlayTo } var uri = new Uri(location); + _logger.Debug("Attempting to create PlayToController from location {0}", location); var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger).ConfigureAwait(false); if (device.RendererCommands == null) @@ -112,6 +113,7 @@ namespace MediaBrowser.Dlna.PlayTo } } + _logger.Debug("Logging session activity from location {0}", location); var sessionInfo = await _sessionManager.LogSessionActivity(device.Properties.ClientType, _appHost.ApplicationVersion.ToString(), device.Properties.UUID, device.Properties.Name, uri.OriginalString, null) .ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/Activity/ActivityRepository.cs b/MediaBrowser.Server.Implementations/Activity/ActivityRepository.cs index b0e05a5bc..d6ae381e9 100644 --- a/MediaBrowser.Server.Implementations/Activity/ActivityRepository.cs +++ b/MediaBrowser.Server.Implementations/Activity/ActivityRepository.cs @@ -15,25 +15,19 @@ namespace MediaBrowser.Server.Implementations.Activity { public class ActivityRepository : BaseSqliteRepository, IActivityRepository { - private IDbConnection _connection; - private readonly IServerApplicationPaths _appPaths; private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - private IDbCommand _saveActivityCommand; - - public ActivityRepository(ILogManager logManager, IServerApplicationPaths appPaths) - : base(logManager) + public ActivityRepository(ILogManager logManager, IServerApplicationPaths appPaths, IDbConnector connector) + : base(logManager, connector) { - _appPaths = appPaths; + DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db"); } - public async Task Initialize(IDbConnector dbConnector) + public async Task Initialize() { - var dbFile = Path.Combine(_appPaths.DataPath, "activitylog.db"); - - _connection = await dbConnector.Connect(dbFile).ConfigureAwait(false); - - string[] queries = { + using (var connection = await CreateConnection().ConfigureAwait(false)) + { + string[] queries = { "create table if not exists ActivityLogEntries (Id GUID PRIMARY KEY, Name TEXT, Overview TEXT, ShortOverview TEXT, Type TEXT, ItemId TEXT, UserId TEXT, DateCreated DATETIME, LogSeverity TEXT)", "create index if not exists idx_ActivityLogEntries on ActivityLogEntries(Id)", @@ -44,25 +38,8 @@ namespace MediaBrowser.Server.Implementations.Activity "pragma shrink_memory" }; - _connection.RunQueries(queries, Logger); - - PrepareStatements(); - } - - private void PrepareStatements() - { - _saveActivityCommand = _connection.CreateCommand(); - _saveActivityCommand.CommandText = "replace into ActivityLogEntries (Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Id, @Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)"; - - _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@Id"); - _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@Name"); - _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@Overview"); - _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@ShortOverview"); - _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@Type"); - _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@ItemId"); - _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@UserId"); - _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@DateCreated"); - _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@LogSeverity"); + connection.RunQueries(queries, Logger); + } } private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLogEntries"; @@ -79,128 +56,145 @@ namespace MediaBrowser.Server.Implementations.Activity throw new ArgumentNullException("entry"); } - await WriteLock.WaitAsync().ConfigureAwait(false); + using (var connection = await CreateConnection().ConfigureAwait(false)) + { + using (var saveActivityCommand = connection.CreateCommand()) + { + saveActivityCommand.CommandText = "replace into ActivityLogEntries (Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Id, @Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)"; - IDbTransaction transaction = null; + saveActivityCommand.Parameters.Add(saveActivityCommand, "@Id"); + saveActivityCommand.Parameters.Add(saveActivityCommand, "@Name"); + saveActivityCommand.Parameters.Add(saveActivityCommand, "@Overview"); + saveActivityCommand.Parameters.Add(saveActivityCommand, "@ShortOverview"); + saveActivityCommand.Parameters.Add(saveActivityCommand, "@Type"); + saveActivityCommand.Parameters.Add(saveActivityCommand, "@ItemId"); + saveActivityCommand.Parameters.Add(saveActivityCommand, "@UserId"); + saveActivityCommand.Parameters.Add(saveActivityCommand, "@DateCreated"); + saveActivityCommand.Parameters.Add(saveActivityCommand, "@LogSeverity"); - try - { - transaction = _connection.BeginTransaction(); + IDbTransaction transaction = null; - var index = 0; + try + { + transaction = connection.BeginTransaction(); - _saveActivityCommand.GetParameter(index++).Value = new Guid(entry.Id); - _saveActivityCommand.GetParameter(index++).Value = entry.Name; - _saveActivityCommand.GetParameter(index++).Value = entry.Overview; - _saveActivityCommand.GetParameter(index++).Value = entry.ShortOverview; - _saveActivityCommand.GetParameter(index++).Value = entry.Type; - _saveActivityCommand.GetParameter(index++).Value = entry.ItemId; - _saveActivityCommand.GetParameter(index++).Value = entry.UserId; - _saveActivityCommand.GetParameter(index++).Value = entry.Date; - _saveActivityCommand.GetParameter(index++).Value = entry.Severity.ToString(); + var index = 0; - _saveActivityCommand.Transaction = transaction; + saveActivityCommand.GetParameter(index++).Value = new Guid(entry.Id); + saveActivityCommand.GetParameter(index++).Value = entry.Name; + saveActivityCommand.GetParameter(index++).Value = entry.Overview; + saveActivityCommand.GetParameter(index++).Value = entry.ShortOverview; + saveActivityCommand.GetParameter(index++).Value = entry.Type; + saveActivityCommand.GetParameter(index++).Value = entry.ItemId; + saveActivityCommand.GetParameter(index++).Value = entry.UserId; + saveActivityCommand.GetParameter(index++).Value = entry.Date; + saveActivityCommand.GetParameter(index++).Value = entry.Severity.ToString(); - _saveActivityCommand.ExecuteNonQuery(); + saveActivityCommand.Transaction = transaction; - transaction.Commit(); - } - catch (OperationCanceledException) - { - if (transaction != null) - { - transaction.Rollback(); - } + saveActivityCommand.ExecuteNonQuery(); - throw; - } - catch (Exception e) - { - Logger.ErrorException("Failed to save record:", e); + transaction.Commit(); + } + catch (OperationCanceledException) + { + if (transaction != null) + { + transaction.Rollback(); + } - if (transaction != null) - { - transaction.Rollback(); - } + throw; + } + catch (Exception e) + { + Logger.ErrorException("Failed to save record:", e); - throw; - } - finally - { - if (transaction != null) - { - transaction.Dispose(); - } + if (transaction != null) + { + transaction.Rollback(); + } - WriteLock.Release(); + throw; + } + finally + { + if (transaction != null) + { + transaction.Dispose(); + } + } + } } } public QueryResult GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit) { - using (var cmd = _connection.CreateCommand()) + using (var connection = CreateConnection(true).Result) { - cmd.CommandText = BaseActivitySelectText; - - var whereClauses = new List(); - - if (minDate.HasValue) + using (var cmd = connection.CreateCommand()) { - whereClauses.Add("DateCreated>=@DateCreated"); - cmd.Parameters.Add(cmd, "@DateCreated", DbType.Date).Value = minDate.Value; - } + cmd.CommandText = BaseActivitySelectText; - var whereTextWithoutPaging = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); + var whereClauses = new List(); - if (startIndex.HasValue && startIndex.Value > 0) - { - var pagingWhereText = whereClauses.Count == 0 ? + if (minDate.HasValue) + { + whereClauses.Add("DateCreated>=@DateCreated"); + cmd.Parameters.Add(cmd, "@DateCreated", DbType.Date).Value = minDate.Value; + } + + var whereTextWithoutPaging = whereClauses.Count == 0 ? string.Empty : " where " + string.Join(" AND ", whereClauses.ToArray()); - - whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM ActivityLogEntries {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()); - cmd.CommandText += whereText; + if (startIndex.HasValue && startIndex.Value > 0) + { + var pagingWhereText = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); - cmd.CommandText += " ORDER BY DateCreated DESC"; + whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM ActivityLogEntries {0} ORDER BY DateCreated DESC LIMIT {1})", + pagingWhereText, + startIndex.Value.ToString(_usCulture))); + } - if (limit.HasValue) - { - cmd.CommandText += " LIMIT " + limit.Value.ToString(_usCulture); - } + var whereText = whereClauses.Count == 0 ? + string.Empty : + " where " + string.Join(" AND ", whereClauses.ToArray()); - cmd.CommandText += "; select count (Id) from ActivityLogEntries" + whereTextWithoutPaging; + cmd.CommandText += whereText; - var list = new List(); - var count = 0; + cmd.CommandText += " ORDER BY DateCreated DESC"; - using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess)) - { - while (reader.Read()) + if (limit.HasValue) { - list.Add(GetEntry(reader)); + cmd.CommandText += " LIMIT " + limit.Value.ToString(_usCulture); } - if (reader.NextResult() && reader.Read()) + cmd.CommandText += "; select count (Id) from ActivityLogEntries" + whereTextWithoutPaging; + + var list = new List(); + var count = 0; + + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess)) { - count = reader.GetInt32(0); + while (reader.Read()) + { + list.Add(GetEntry(reader)); + } + + if (reader.NextResult() && reader.Read()) + { + count = reader.GetInt32(0); + } } - } - return new QueryResult() - { - Items = list.ToArray(), - TotalRecordCount = count - }; + return new QueryResult() + { + Items = list.ToArray(), + TotalRecordCount = count + }; + } } } @@ -260,19 +254,5 @@ namespace MediaBrowser.Server.Implementations.Activity return info; } - - protected override void CloseConnection() - { - if (_connection != null) - { - if (_connection.IsOpen()) - { - _connection.Close(); - } - - _connection.Dispose(); - _connection = null; - } - } } } diff --git a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index 5777a0af7..50ad3cfbc 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -93,7 +93,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints NatUtility.UnhandledException += NatUtility_UnhandledException; NatUtility.StartDiscovery(); - _timer = new PeriodicTimer(s => _createdRules = new List(), null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5)); + _timer = new PeriodicTimer(ClearCreatedRules, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5)); _ssdp.MessageReceived += _ssdp_MessageReceived; @@ -102,12 +102,43 @@ namespace MediaBrowser.Server.Implementations.EntryPoints _isStarted = true; } + private void ClearCreatedRules(object state) + { + _createdRules = new List(); + _usnsHandled = new List(); + } + void _ssdp_MessageReceived(object sender, SsdpMessageEventArgs e) { var endpoint = e.EndPoint as IPEndPoint; - if (endpoint != null && e.LocalEndPoint != null) + if (endpoint == null || e.LocalEndPoint == null) { + return; + } + + string usn; + if (!e.Headers.TryGetValue("USN", out usn)) usn = string.Empty; + + string nt; + if (!e.Headers.TryGetValue("NT", out nt)) nt = string.Empty; + + // Filter device type + if (usn.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 && + nt.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 && + usn.IndexOf("WANPPPConnection:", StringComparison.OrdinalIgnoreCase) == -1 && + nt.IndexOf("WANPPPConnection:", StringComparison.OrdinalIgnoreCase) == -1) + { + return; + } + + var identifier = string.IsNullOrWhiteSpace(usn) ? nt : usn; + + if (!_usnsHandled.Contains(identifier)) + { + _usnsHandled.Add(identifier); + + _logger.Debug("Calling Nat.Handle on " + identifier); NatUtility.Handle(e.LocalEndPoint.Address, e.Message, endpoint, NatProtocol.Upnp); } } @@ -151,6 +182,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints } private List _createdRules = new List(); + private List _usnsHandled = new List(); private void CreateRules(INatDevice device) { // On some systems the device discovered event seems to fire repeatedly diff --git a/MediaBrowser.Server.Implementations/IO/FileRefresher.cs b/MediaBrowser.Server.Implementations/IO/FileRefresher.cs index 18c52ab29..4bea6ad34 100644 --- a/MediaBrowser.Server.Implementations/IO/FileRefresher.cs +++ b/MediaBrowser.Server.Implementations/IO/FileRefresher.cs @@ -93,8 +93,15 @@ namespace MediaBrowser.Server.Implementations.IO private async void OnTimerCallback(object state) { + List paths; + + lock (_timerLock) + { + paths = _affectedPaths.ToList(); + } + // Extend the timer as long as any of the paths are still being written to. - if (_affectedPaths.Any(IsFileLocked)) + if (paths.Any(IsFileLocked)) { Logger.Info("Timer extended."); RestartTimer(); @@ -108,7 +115,7 @@ namespace MediaBrowser.Server.Implementations.IO try { - await ProcessPathChanges(_affectedPaths.ToList()).ConfigureAwait(false); + await ProcessPathChanges(paths.ToList()).ConfigureAwait(false); } catch (Exception ex) { diff --git a/MediaBrowser.Server.Mono/Native/DbConnector.cs b/MediaBrowser.Server.Mono/Native/DbConnector.cs index 7553dbe1a..5ad3ecfef 100644 --- a/MediaBrowser.Server.Mono/Native/DbConnector.cs +++ b/MediaBrowser.Server.Mono/Native/DbConnector.cs @@ -16,9 +16,9 @@ namespace MediaBrowser.Server.Mono.Native _logger = logger; } - public Task Connect(string dbPath, int? cacheSize = null) + public Task Connect(string dbPath, bool isReadOnly, bool enablePooling = false, int? cacheSize = null) { - return SqliteExtensions.ConnectToDb(dbPath, cacheSize, _logger); + return SqliteExtensions.ConnectToDb(dbPath, isReadOnly, enablePooling, cacheSize, _logger); } } } \ No newline at end of file diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index f344e6f1c..9e869478c 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -424,11 +424,11 @@ namespace MediaBrowser.Server.Startup.Common UserRepository = await GetUserRepository().ConfigureAwait(false); RegisterSingleInstance(UserRepository); - var displayPreferencesRepo = new SqliteDisplayPreferencesRepository(LogManager, JsonSerializer, ApplicationPaths); + var displayPreferencesRepo = new SqliteDisplayPreferencesRepository(LogManager, JsonSerializer, ApplicationPaths, NativeApp.GetDbConnector()); DisplayPreferencesRepository = displayPreferencesRepo; RegisterSingleInstance(DisplayPreferencesRepository); - var itemRepo = new SqliteItemRepository(ServerConfigurationManager, JsonSerializer, LogManager); + var itemRepo = new SqliteItemRepository(ServerConfigurationManager, JsonSerializer, LogManager, NativeApp.GetDbConnector()); ItemRepository = itemRepo; RegisterSingleInstance(ItemRepository); @@ -553,8 +553,8 @@ namespace MediaBrowser.Server.Startup.Common RegisterSingleInstance(NativeApp.GetPowerManagement()); - var sharingRepo = new SharingRepository(LogManager, ApplicationPaths); - await sharingRepo.Initialize(NativeApp.GetDbConnector()).ConfigureAwait(false); + var sharingRepo = new SharingRepository(LogManager, ApplicationPaths, NativeApp.GetDbConnector()); + await sharingRepo.Initialize().ConfigureAwait(false); RegisterSingleInstance(new SharingManager(sharingRepo, ServerConfigurationManager, LibraryManager, this)); RegisterSingleInstance(new SsdpHandler(LogManager.GetLogger("SsdpHandler"), ServerConfigurationManager, this)); @@ -571,7 +571,7 @@ namespace MediaBrowser.Server.Startup.Common SubtitleEncoder = new SubtitleEncoder(LibraryManager, LogManager.GetLogger("SubtitleEncoder"), ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager); RegisterSingleInstance(SubtitleEncoder); - await displayPreferencesRepo.Initialize(NativeApp.GetDbConnector()).ConfigureAwait(false); + await displayPreferencesRepo.Initialize().ConfigureAwait(false); await ConfigureUserDataRepositories().ConfigureAwait(false); await itemRepo.Initialize(NativeApp.GetDbConnector()).ConfigureAwait(false); ((LibraryManager)LibraryManager).ItemRepository = ItemRepository; @@ -670,9 +670,9 @@ namespace MediaBrowser.Server.Startup.Common { try { - var repo = new SqliteUserRepository(LogManager, ApplicationPaths, JsonSerializer); + var repo = new SqliteUserRepository(LogManager, ApplicationPaths, JsonSerializer, NativeApp.GetDbConnector()); - await repo.Initialize(NativeApp.GetDbConnector()).ConfigureAwait(false); + await repo.Initialize().ConfigureAwait(false); return repo; } @@ -689,7 +689,7 @@ namespace MediaBrowser.Server.Startup.Common /// Task{IUserRepository}. private async Task GetFileOrganizationRepository() { - var repo = new SqliteFileOrganizationRepository(LogManager, ServerConfigurationManager.ApplicationPaths); + var repo = new SqliteFileOrganizationRepository(LogManager, ServerConfigurationManager.ApplicationPaths, NativeApp.GetDbConnector()); await repo.Initialize(NativeApp.GetDbConnector()).ConfigureAwait(false); @@ -698,27 +698,27 @@ namespace MediaBrowser.Server.Startup.Common private async Task GetAuthenticationRepository() { - var repo = new AuthenticationRepository(LogManager, ServerConfigurationManager.ApplicationPaths); + var repo = new AuthenticationRepository(LogManager, ServerConfigurationManager.ApplicationPaths, NativeApp.GetDbConnector()); - await repo.Initialize(NativeApp.GetDbConnector()).ConfigureAwait(false); + await repo.Initialize().ConfigureAwait(false); return repo; } private async Task GetActivityLogRepository() { - var repo = new ActivityRepository(LogManager, ServerConfigurationManager.ApplicationPaths); + var repo = new ActivityRepository(LogManager, ServerConfigurationManager.ApplicationPaths, NativeApp.GetDbConnector()); - await repo.Initialize(NativeApp.GetDbConnector()).ConfigureAwait(false); + await repo.Initialize().ConfigureAwait(false); return repo; } private async Task GetSyncRepository() { - var repo = new SyncRepository(LogManager, JsonSerializer, ServerConfigurationManager.ApplicationPaths); + var repo = new SyncRepository(LogManager, JsonSerializer, ServerConfigurationManager.ApplicationPaths, NativeApp.GetDbConnector()); - await repo.Initialize(NativeApp.GetDbConnector()).ConfigureAwait(false); + await repo.Initialize().ConfigureAwait(false); return repo; } @@ -729,9 +729,9 @@ namespace MediaBrowser.Server.Startup.Common /// Task. private async Task ConfigureNotificationsRepository() { - var repo = new SqliteNotificationsRepository(LogManager, ApplicationPaths); + var repo = new SqliteNotificationsRepository(LogManager, ApplicationPaths, NativeApp.GetDbConnector()); - await repo.Initialize(NativeApp.GetDbConnector()).ConfigureAwait(false); + await repo.Initialize().ConfigureAwait(false); NotificationsRepository = repo; @@ -744,7 +744,7 @@ namespace MediaBrowser.Server.Startup.Common /// Task. private async Task ConfigureUserDataRepositories() { - var repo = new SqliteUserDataRepository(LogManager, ApplicationPaths); + var repo = new SqliteUserDataRepository(LogManager, ApplicationPaths, NativeApp.GetDbConnector()); await repo.Initialize(NativeApp.GetDbConnector()).ConfigureAwait(false); diff --git a/MediaBrowser.ServerApplication/Native/DbConnector.cs b/MediaBrowser.ServerApplication/Native/DbConnector.cs index 6001ac3c0..9aaa96a80 100644 --- a/MediaBrowser.ServerApplication/Native/DbConnector.cs +++ b/MediaBrowser.ServerApplication/Native/DbConnector.cs @@ -16,18 +16,9 @@ namespace MediaBrowser.ServerApplication.Native _logger = logger; } - public async Task Connect(string dbPath, int? cacheSize = null) + public Task Connect(string dbPath, bool isReadOnly, bool enablePooling = false, int? cacheSize = null) { - try - { - return await SqliteExtensions.ConnectToDb(dbPath, cacheSize, _logger).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error opening database {0}", ex, dbPath); - - throw; - } + return SqliteExtensions.ConnectToDb(dbPath, isReadOnly, enablePooling, cacheSize, _logger); } } } \ No newline at end of file diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 904953cfc..809c3f1a5 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -342,7 +342,9 @@ namespace MediaBrowser.WebDashboard.Api if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase)) { - DeleteFoldersByName(Path.Combine(bowerPath, "emby-webcomponents"), "fonts"); + DeleteFoldersByName(Path.Combine(bowerPath, "emby-webcomponents", "fonts"), "montserrat"); + DeleteFoldersByName(Path.Combine(bowerPath, "emby-webcomponents", "fonts"), "opensans"); + DeleteFoldersByName(Path.Combine(bowerPath, "emby-webcomponents", "fonts"), "roboto"); } _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "jquery", "src"), true); -- cgit v1.2.3 From 08e58886f767056c67adb7c1859f864f9a9bffea Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 2 Jul 2016 22:47:39 -0400 Subject: update metadata editor --- .../Encoder/FontConfigLoader.cs | 179 ++++++++++++++++++++ MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 18 ++- .../MediaBrowser.MediaEncoding.csproj | 1 + .../IO/FileRefresher.cs | 2 +- .../ApplicationHost.cs | 4 +- .../FFMpeg/FFMpegLoader.cs | 180 --------------------- 6 files changed, 201 insertions(+), 183 deletions(-) create mode 100644 MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs (limited to 'MediaBrowser.Server.Implementations/IO/FileRefresher.cs') diff --git a/MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs b/MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs new file mode 100644 index 000000000..d7ef493c2 --- /dev/null +++ b/MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs @@ -0,0 +1,179 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using CommonIO; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Net; + +namespace MediaBrowser.MediaEncoding.Encoder +{ + public class FontConfigLoader + { + private readonly IHttpClient _httpClient; + private readonly IApplicationPaths _appPaths; + private readonly ILogger _logger; + private readonly IZipClient _zipClient; + private readonly IFileSystem _fileSystem; + + private readonly string[] _fontUrls = + { + "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/ARIALUNI.7z" + }; + + public FontConfigLoader(IHttpClient httpClient, IApplicationPaths appPaths, ILogger logger, IZipClient zipClient, IFileSystem fileSystem) + { + _httpClient = httpClient; + _appPaths = appPaths; + _logger = logger; + _zipClient = zipClient; + _fileSystem = fileSystem; + } + + /// + /// Extracts the fonts. + /// + /// The target path. + /// Task. + public async Task DownloadFonts(string targetPath) + { + try + { + var fontsDirectory = Path.Combine(targetPath, "fonts"); + + _fileSystem.CreateDirectory(fontsDirectory); + + const string fontFilename = "ARIALUNI.TTF"; + + var fontFile = Path.Combine(fontsDirectory, fontFilename); + + if (_fileSystem.FileExists(fontFile)) + { + await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false); + } + else + { + // Kick this off, but no need to wait on it + Task.Run(async () => + { + await DownloadFontFile(fontsDirectory, fontFilename, new Progress()).ConfigureAwait(false); + + await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false); + }); + } + } + catch (HttpException ex) + { + // Don't let the server crash because of this + _logger.ErrorException("Error downloading ffmpeg font files", ex); + } + catch (Exception ex) + { + // Don't let the server crash because of this + _logger.ErrorException("Error writing ffmpeg font files", ex); + } + } + + /// + /// Downloads the font file. + /// + /// The fonts directory. + /// The font filename. + /// Task. + private async Task DownloadFontFile(string fontsDirectory, string fontFilename, IProgress progress) + { + var existingFile = Directory + .EnumerateFiles(_appPaths.ProgramDataPath, fontFilename, SearchOption.AllDirectories) + .FirstOrDefault(); + + if (existingFile != null) + { + try + { + _fileSystem.CopyFile(existingFile, Path.Combine(fontsDirectory, fontFilename), true); + return; + } + catch (IOException ex) + { + // Log this, but don't let it fail the operation + _logger.ErrorException("Error copying file", ex); + } + } + + string tempFile = null; + + foreach (var url in _fontUrls) + { + progress.Report(0); + + try + { + tempFile = await _httpClient.GetTempFile(new HttpRequestOptions + { + Url = url, + Progress = progress + + }).ConfigureAwait(false); + + break; + } + catch (Exception ex) + { + // The core can function without the font file, so handle this + _logger.ErrorException("Failed to download ffmpeg font file from {0}", ex, url); + } + } + + if (string.IsNullOrEmpty(tempFile)) + { + return; + } + + Extract7zArchive(tempFile, fontsDirectory); + + try + { + _fileSystem.DeleteFile(tempFile); + } + catch (IOException ex) + { + // Log this, but don't let it fail the operation + _logger.ErrorException("Error deleting temp file {0}", ex, tempFile); + } + } + private void Extract7zArchive(string archivePath, string targetPath) + { + _logger.Info("Extracting {0} to {1}", archivePath, targetPath); + + _zipClient.ExtractAllFrom7z(archivePath, targetPath, true); + } + + /// + /// Writes the font config file. + /// + /// The fonts directory. + /// Task. + private async Task WriteFontConfigFile(string fontsDirectory) + { + const string fontConfigFilename = "fonts.conf"; + var fontConfigFile = Path.Combine(fontsDirectory, fontConfigFilename); + + if (!_fileSystem.FileExists(fontConfigFile)) + { + var contents = string.Format("{0}ArialArial Unicode MS", fontsDirectory); + + var bytes = Encoding.UTF8.GetBytes(contents); + + using (var fileStream = _fileSystem.GetFileStream(fontConfigFile, FileMode.Create, FileAccess.Write, + FileShare.Read, true)) + { + await fileStream.WriteAsync(bytes, 0, bytes.Length); + } + } + } + } +} diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index a78e23669..383e93758 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -24,6 +24,7 @@ using CommonIO; using MediaBrowser.Model.Configuration; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; namespace MediaBrowser.MediaEncoding.Encoder { @@ -76,11 +77,13 @@ namespace MediaBrowser.MediaEncoding.Encoder protected readonly ISessionManager SessionManager; protected readonly Func SubtitleEncoder; protected readonly Func MediaSourceManager; + private readonly IHttpClient _httpClient; + private readonly IZipClient _zipClient; private readonly List _runningProcesses = new List(); private readonly bool _hasExternalEncoder; - public MediaEncoder(ILogger logger, IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, bool hasExternalEncoder, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, Func subtitleEncoder, Func mediaSourceManager) + public MediaEncoder(ILogger logger, IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, bool hasExternalEncoder, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, Func subtitleEncoder, Func mediaSourceManager, IHttpClient httpClient, IZipClient zipClient) { _logger = logger; _jsonSerializer = jsonSerializer; @@ -93,6 +96,8 @@ namespace MediaBrowser.MediaEncoding.Encoder SessionManager = sessionManager; SubtitleEncoder = subtitleEncoder; MediaSourceManager = mediaSourceManager; + _httpClient = httpClient; + _zipClient = zipClient; FFProbePath = ffProbePath; FFMpegPath = ffMpegPath; @@ -142,6 +147,17 @@ namespace MediaBrowser.MediaEncoding.Encoder SetAvailableDecoders(result.Item1); SetAvailableEncoders(result.Item2); + + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + var directory = Path.GetDirectoryName(FFMpegPath); + + if (FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.ProgramDataPath, directory)) + { + await new FontConfigLoader(_httpClient, ConfigurationManager.ApplicationPaths, _logger, _zipClient, + FileSystem).DownloadFonts(directory).ConfigureAwait(false); + } + } } } diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index 794353451..1b5599577 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -72,6 +72,7 @@ + diff --git a/MediaBrowser.Server.Implementations/IO/FileRefresher.cs b/MediaBrowser.Server.Implementations/IO/FileRefresher.cs index 4bea6ad34..f48beacb5 100644 --- a/MediaBrowser.Server.Implementations/IO/FileRefresher.cs +++ b/MediaBrowser.Server.Implementations/IO/FileRefresher.cs @@ -33,13 +33,13 @@ namespace MediaBrowser.Server.Implementations.IO { logger.Debug("New file refresher created for {0}", path); Path = path; - _affectedPaths.Add(path); _fileSystem = fileSystem; ConfigurationManager = configurationManager; LibraryManager = libraryManager; TaskManager = taskManager; Logger = logger; + AddPath(path); } private void AddAffectedPath(string path) diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 63bdddac9..bcd2ed8bd 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -660,7 +660,9 @@ namespace MediaBrowser.Server.Startup.Common ChannelManager, SessionManager, () => SubtitleEncoder, - () => MediaSourceManager); + () => MediaSourceManager, + HttpClient, + ZipClient); MediaEncoder = mediaEncoder; RegisterSingleInstance(MediaEncoder); diff --git a/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs b/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs index 4c5759b56..68e2a4927 100644 --- a/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs +++ b/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs @@ -2,14 +2,11 @@ using MediaBrowser.Common.Net; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Net; using Mono.Unix.Native; using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Reflection; -using System.Text; using System.Threading; using System.Threading.Tasks; using CommonIO; @@ -26,11 +23,6 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg private readonly NativeEnvironment _environment; private readonly FFMpegInstallInfo _ffmpegInstallInfo; - private readonly string[] _fontUrls = - { - "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/ARIALUNI.7z" - }; - public FFMpegLoader(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IZipClient zipClient, IFileSystem fileSystem, NativeEnvironment environment, FFMpegInstallInfo ffmpegInstallInfo) { _logger = logger; @@ -112,13 +104,6 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg } } - if (_environment.OperatingSystem == OperatingSystem.Windows) - { - await DownloadFonts(versionedDirectoryPath).ConfigureAwait(false); - } - - DeleteOlderFolders(Path.GetDirectoryName(versionedDirectoryPath), excludeFromDeletions); - // Allow just one of these to be overridden, if desired. if (!string.IsNullOrWhiteSpace(customffMpegPath)) { @@ -132,30 +117,6 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg return info; } - private void DeleteOlderFolders(string path, IEnumerable excludeFolders) - { - var folders = Directory.GetDirectories(path) - .Where(i => !excludeFolders.Contains(i, StringComparer.OrdinalIgnoreCase)) - .ToList(); - - foreach (var folder in folders) - { - DeleteFolder(folder); - } - } - - private void DeleteFolder(string path) - { - try - { - _fileSystem.DeleteDirectory(path, true); - } - catch (Exception ex) - { - _logger.ErrorException("Error deleting {0}", ex, path); - } - } - private FFMpegInfo GetExistingVersion(FFMpegInfo info, string rootEncoderPath) { var encoderFilename = Path.GetFileName(info.EncoderPath); @@ -270,12 +231,6 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg _zipClient.ExtractAllFromTar(archivePath, targetPath, true); } } - private void Extract7zArchive(string archivePath, string targetPath) - { - _logger.Info("Extracting {0} to {1}", archivePath, targetPath); - - _zipClient.ExtractAllFrom7z(archivePath, targetPath, true); - } private void DeleteFile(string path) { @@ -289,140 +244,5 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg } } - /// - /// Extracts the fonts. - /// - /// The target path. - /// Task. - private async Task DownloadFonts(string targetPath) - { - try - { - var fontsDirectory = Path.Combine(targetPath, "fonts"); - - _fileSystem.CreateDirectory(fontsDirectory); - - const string fontFilename = "ARIALUNI.TTF"; - - var fontFile = Path.Combine(fontsDirectory, fontFilename); - - if (_fileSystem.FileExists(fontFile)) - { - await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false); - } - else - { - // Kick this off, but no need to wait on it - Task.Run(async () => - { - await DownloadFontFile(fontsDirectory, fontFilename, new Progress()).ConfigureAwait(false); - - await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false); - }); - } - } - catch (HttpException ex) - { - // Don't let the server crash because of this - _logger.ErrorException("Error downloading ffmpeg font files", ex); - } - catch (Exception ex) - { - // Don't let the server crash because of this - _logger.ErrorException("Error writing ffmpeg font files", ex); - } - } - - /// - /// Downloads the font file. - /// - /// The fonts directory. - /// The font filename. - /// Task. - private async Task DownloadFontFile(string fontsDirectory, string fontFilename, IProgress progress) - { - var existingFile = Directory - .EnumerateFiles(_appPaths.ProgramDataPath, fontFilename, SearchOption.AllDirectories) - .FirstOrDefault(); - - if (existingFile != null) - { - try - { - _fileSystem.CopyFile(existingFile, Path.Combine(fontsDirectory, fontFilename), true); - return; - } - catch (IOException ex) - { - // Log this, but don't let it fail the operation - _logger.ErrorException("Error copying file", ex); - } - } - - string tempFile = null; - - foreach (var url in _fontUrls) - { - progress.Report(0); - - try - { - tempFile = await _httpClient.GetTempFile(new HttpRequestOptions - { - Url = url, - Progress = progress - - }).ConfigureAwait(false); - - break; - } - catch (Exception ex) - { - // The core can function without the font file, so handle this - _logger.ErrorException("Failed to download ffmpeg font file from {0}", ex, url); - } - } - - if (string.IsNullOrEmpty(tempFile)) - { - return; - } - - Extract7zArchive(tempFile, fontsDirectory); - - try - { - _fileSystem.DeleteFile(tempFile); - } - catch (IOException ex) - { - // Log this, but don't let it fail the operation - _logger.ErrorException("Error deleting temp file {0}", ex, tempFile); - } - } - - /// - /// Writes the font config file. - /// - /// The fonts directory. - /// Task. - private async Task WriteFontConfigFile(string fontsDirectory) - { - const string fontConfigFilename = "fonts.conf"; - var fontConfigFile = Path.Combine(fontsDirectory, fontConfigFilename); - - if (!_fileSystem.FileExists(fontConfigFile)) - { - var contents = string.Format("{0}ArialArial Unicode MS", fontsDirectory); - - var bytes = Encoding.UTF8.GetBytes(contents); - - using (var fileStream = _fileSystem.GetFileStream(fontConfigFile, FileMode.Create, FileAccess.Write, - FileShare.Read, true)) - { - await fileStream.WriteAsync(bytes, 0, bytes.Length); - } - } - } } } -- cgit v1.2.3