commit
e205caee03
|
@ -550,9 +550,7 @@ namespace MediaBrowser.Api.LiveTv
|
||||||
|
|
||||||
var response = await _httpClient.Get(new HttpRequestOptions
|
var response = await _httpClient.Get(new HttpRequestOptions
|
||||||
{
|
{
|
||||||
Url = "https://json.schedulesdirect.org/20141201/available/countries",
|
Url = "https://json.schedulesdirect.org/20141201/available/countries"
|
||||||
CacheLength = TimeSpan.FromDays(1),
|
|
||||||
CacheMode = CacheMode.Unconditional
|
|
||||||
|
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
|
|
||||||
|
|
|
@ -1518,6 +1518,13 @@ namespace MediaBrowser.Api.Playback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (i == 25)
|
else if (i == 25)
|
||||||
|
{
|
||||||
|
if (videoRequest != null)
|
||||||
|
{
|
||||||
|
videoRequest.ForceLiveStream = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (i == 26)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
|
if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
|
||||||
{
|
{
|
||||||
|
@ -1528,7 +1535,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (i == 26)
|
else if (i == 27)
|
||||||
{
|
{
|
||||||
request.TranscodingMaxAudioChannels = int.Parse(val, UsCulture);
|
request.TranscodingMaxAudioChannels = int.Parse(val, UsCulture);
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,6 +98,9 @@ namespace MediaBrowser.Api.Subtitles
|
||||||
|
|
||||||
[ApiMember(Name = "EndPositionTicks", Description = "EndPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
[ApiMember(Name = "EndPositionTicks", Description = "EndPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||||
public long? EndPositionTicks { get; set; }
|
public long? EndPositionTicks { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "CopyTimestamps", Description = "CopyTimestamps", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
|
||||||
|
public bool CopyTimestamps { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/subtitles.m3u8", "GET", Summary = "Gets an HLS subtitle playlist.")]
|
[Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/subtitles.m3u8", "GET", Summary = "Gets an HLS subtitle playlist.")]
|
||||||
|
@ -175,7 +178,7 @@ namespace MediaBrowser.Api.Subtitles
|
||||||
|
|
||||||
var endPositionTicks = Math.Min(runtime, positionTicks + segmentLengthTicks);
|
var endPositionTicks = Math.Min(runtime, positionTicks + segmentLengthTicks);
|
||||||
|
|
||||||
var url = string.Format("stream.vtt?StartPositionTicks={0}&EndPositionTicks={1}&api_key={2}",
|
var url = string.Format("stream.vtt?CopyTimestamps=true,StartPositionTicks={0}&EndPositionTicks={1}&api_key={2}",
|
||||||
positionTicks.ToString(CultureInfo.InvariantCulture),
|
positionTicks.ToString(CultureInfo.InvariantCulture),
|
||||||
endPositionTicks.ToString(CultureInfo.InvariantCulture),
|
endPositionTicks.ToString(CultureInfo.InvariantCulture),
|
||||||
accessToken);
|
accessToken);
|
||||||
|
@ -222,6 +225,7 @@ namespace MediaBrowser.Api.Subtitles
|
||||||
request.Format,
|
request.Format,
|
||||||
request.StartPositionTicks,
|
request.StartPositionTicks,
|
||||||
request.EndPositionTicks,
|
request.EndPositionTicks,
|
||||||
|
request.CopyTimestamps,
|
||||||
CancellationToken.None).ConfigureAwait(false);
|
CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -332,13 +332,14 @@ namespace MediaBrowser.Controller.Entities
|
||||||
|
|
||||||
private QueryResult<BaseItem> GetMusicAlbumArtists(Folder parent, User user, InternalItemsQuery query)
|
private QueryResult<BaseItem> GetMusicAlbumArtists(Folder parent, User user, InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
|
var items = parent.QueryRecursive(new InternalItemsQuery(user)
|
||||||
{
|
{
|
||||||
Recursive = true,
|
Recursive = true,
|
||||||
ParentId = parent.Id,
|
ParentId = parent.Id,
|
||||||
IncludeItemTypes = new[] { typeof(Audio.Audio).Name }
|
IncludeItemTypes = new[] { typeof(Audio.Audio).Name },
|
||||||
|
EnableTotalRecordCount = false
|
||||||
|
|
||||||
}).Cast<IHasAlbumArtist>();
|
}).Items.Cast<IHasAlbumArtist>();
|
||||||
|
|
||||||
var artists = _libraryManager.GetAlbumArtists(items);
|
var artists = _libraryManager.GetAlbumArtists(items);
|
||||||
|
|
||||||
|
@ -347,13 +348,14 @@ namespace MediaBrowser.Controller.Entities
|
||||||
|
|
||||||
private QueryResult<BaseItem> GetMusicArtists(Folder parent, User user, InternalItemsQuery query)
|
private QueryResult<BaseItem> GetMusicArtists(Folder parent, User user, InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
|
var items = parent.QueryRecursive(new InternalItemsQuery(user)
|
||||||
{
|
{
|
||||||
Recursive = true,
|
Recursive = true,
|
||||||
ParentId = parent.Id,
|
ParentId = parent.Id,
|
||||||
IncludeItemTypes = new[] { typeof(Audio.Audio).Name, typeof(MusicVideo).Name }
|
IncludeItemTypes = new[] { typeof(Audio.Audio).Name, typeof(MusicVideo).Name },
|
||||||
|
EnableTotalRecordCount = false
|
||||||
|
|
||||||
}).Cast<IHasArtist>();
|
}).Items.Cast<IHasArtist>();
|
||||||
|
|
||||||
var artists = _libraryManager.GetArtists(items);
|
var artists = _libraryManager.GetArtists(items);
|
||||||
|
|
||||||
|
@ -362,13 +364,14 @@ namespace MediaBrowser.Controller.Entities
|
||||||
|
|
||||||
private QueryResult<BaseItem> GetFavoriteArtists(Folder parent, User user, InternalItemsQuery query)
|
private QueryResult<BaseItem> GetFavoriteArtists(Folder parent, User user, InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
|
var items = parent.QueryRecursive(new InternalItemsQuery(user)
|
||||||
{
|
{
|
||||||
Recursive = true,
|
Recursive = true,
|
||||||
ParentId = parent.Id,
|
ParentId = parent.Id,
|
||||||
IncludeItemTypes = new[] { typeof(Audio.Audio).Name }
|
IncludeItemTypes = new[] { typeof(Audio.Audio).Name },
|
||||||
|
EnableTotalRecordCount = false
|
||||||
|
|
||||||
}).Cast<IHasAlbumArtist>();
|
}).Items.Cast<IHasAlbumArtist>();
|
||||||
|
|
||||||
var artists = _libraryManager.GetAlbumArtists(items).Where(i => _userDataManager.GetUserData(user, i).IsFavorite);
|
var artists = _libraryManager.GetAlbumArtists(items).Where(i => _userDataManager.GetUserData(user, i).IsFavorite);
|
||||||
|
|
||||||
|
|
|
@ -10,13 +10,6 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the subtitles.
|
/// Gets the subtitles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="itemId">The item identifier.</param>
|
|
||||||
/// <param name="mediaSourceId">The media source identifier.</param>
|
|
||||||
/// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
|
|
||||||
/// <param name="outputFormat">The output format.</param>
|
|
||||||
/// <param name="startTimeTicks">The start time ticks.</param>
|
|
||||||
/// <param name="endTimeTicks">The end time ticks.</param>
|
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
|
||||||
/// <returns>Task{Stream}.</returns>
|
/// <returns>Task{Stream}.</returns>
|
||||||
Task<Stream> GetSubtitles(string itemId,
|
Task<Stream> GetSubtitles(string itemId,
|
||||||
string mediaSourceId,
|
string mediaSourceId,
|
||||||
|
@ -24,6 +17,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
string outputFormat,
|
string outputFormat,
|
||||||
long startTimeTicks,
|
long startTimeTicks,
|
||||||
long? endTimeTicks,
|
long? endTimeTicks,
|
||||||
|
bool preserveOriginalTimestamps,
|
||||||
CancellationToken cancellationToken);
|
CancellationToken cancellationToken);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -58,6 +58,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
string outputFormat,
|
string outputFormat,
|
||||||
long startTimeTicks,
|
long startTimeTicks,
|
||||||
long? endTimeTicks,
|
long? endTimeTicks,
|
||||||
|
bool preserveOriginalTimestamps,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var ms = new MemoryStream();
|
var ms = new MemoryStream();
|
||||||
|
@ -68,7 +69,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
|
|
||||||
var trackInfo = reader.Parse(stream, cancellationToken);
|
var trackInfo = reader.Parse(stream, cancellationToken);
|
||||||
|
|
||||||
FilterEvents(trackInfo, startTimeTicks, endTimeTicks, false);
|
FilterEvents(trackInfo, startTimeTicks, endTimeTicks, preserveOriginalTimestamps);
|
||||||
|
|
||||||
var writer = GetWriter(outputFormat);
|
var writer = GetWriter(outputFormat);
|
||||||
|
|
||||||
|
@ -116,6 +117,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
string outputFormat,
|
string outputFormat,
|
||||||
long startTimeTicks,
|
long startTimeTicks,
|
||||||
long? endTimeTicks,
|
long? endTimeTicks,
|
||||||
|
bool preserveOriginalTimestamps,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var subtitle = await GetSubtitleStream(itemId, mediaSourceId, subtitleStreamIndex, cancellationToken)
|
var subtitle = await GetSubtitleStream(itemId, mediaSourceId, subtitleStreamIndex, cancellationToken)
|
||||||
|
@ -130,7 +132,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
|
|
||||||
using (var stream = subtitle.Item1)
|
using (var stream = subtitle.Item1)
|
||||||
{
|
{
|
||||||
return await ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, endTimeTicks, cancellationToken).ConfigureAwait(false);
|
return await ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, endTimeTicks, preserveOriginalTimestamps, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommonIO;
|
using CommonIO;
|
||||||
|
using MediaBrowser.Common.Events;
|
||||||
using MediaBrowser.Common.ScheduledTasks;
|
using MediaBrowser.Common.ScheduledTasks;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
|
@ -24,9 +25,14 @@ namespace MediaBrowser.Server.Implementations.IO
|
||||||
private readonly List<string> _affectedPaths = new List<string>();
|
private readonly List<string> _affectedPaths = new List<string>();
|
||||||
private Timer _timer;
|
private Timer _timer;
|
||||||
private readonly object _timerLock = new object();
|
private readonly object _timerLock = new object();
|
||||||
|
public string Path { get; private set; }
|
||||||
|
|
||||||
|
public event EventHandler<EventArgs> Completed;
|
||||||
|
|
||||||
public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger)
|
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);
|
_affectedPaths.Add(path);
|
||||||
|
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
|
@ -36,7 +42,24 @@ namespace MediaBrowser.Server.Implementations.IO
|
||||||
Logger = logger;
|
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)
|
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)
|
private async void OnTimerCallback(object state)
|
||||||
{
|
{
|
||||||
// Extend the timer as long as any of the paths are still being written to.
|
// 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.");
|
Logger.Debug("Timer stopped.");
|
||||||
|
|
||||||
DisposeTimer();
|
DisposeTimer();
|
||||||
|
EventHelper.FireEventIfNotNull(Completed, this, EventArgs.Empty, Logger);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ProcessPathChanges(_affectedPaths).ConfigureAwait(false);
|
await ProcessPathChanges(_affectedPaths.ToList()).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -130,7 +171,7 @@ namespace MediaBrowser.Server.Implementations.IO
|
||||||
{
|
{
|
||||||
item = LibraryManager.FindByPath(path, null);
|
item = LibraryManager.FindByPath(path, null);
|
||||||
|
|
||||||
path = Path.GetDirectoryName(path);
|
path = System.IO.Path.GetDirectoryName(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item != null)
|
if (item != null)
|
||||||
|
@ -222,7 +263,7 @@ namespace MediaBrowser.Server.Implementations.IO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DisposeTimer()
|
private void DisposeTimer()
|
||||||
{
|
{
|
||||||
lock (_timerLock)
|
lock (_timerLock)
|
||||||
{
|
{
|
||||||
|
|
|
@ -26,13 +26,9 @@ namespace MediaBrowser.Server.Implementations.IO
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ConcurrentDictionary<string, FileSystemWatcher> _fileSystemWatchers = new ConcurrentDictionary<string, FileSystemWatcher>(StringComparer.OrdinalIgnoreCase);
|
private readonly ConcurrentDictionary<string, FileSystemWatcher> _fileSystemWatchers = new ConcurrentDictionary<string, FileSystemWatcher>(StringComparer.OrdinalIgnoreCase);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The update timer
|
|
||||||
/// </summary>
|
|
||||||
private Timer _updateTimer;
|
|
||||||
/// <summary>
|
|
||||||
/// The affected paths
|
/// The affected paths
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ConcurrentDictionary<string, string> _affectedPaths = new ConcurrentDictionary<string, string>();
|
private readonly List<FileRefresher> _activeRefreshers = new List<FileRefresher>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A dynamic list of paths that should be ignored. Added to during our own file sytem modifications.
|
/// A dynamic list of paths that should be ignored. Added to during our own file sytem modifications.
|
||||||
|
@ -53,11 +49,6 @@ namespace MediaBrowser.Server.Implementations.IO
|
||||||
"TempSBE"
|
"TempSBE"
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The timer lock
|
|
||||||
/// </summary>
|
|
||||||
private readonly object _timerLock = new object();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add the path to our temporary ignore list. Use when writing to a path within our listening scope.
|
/// Add the path to our temporary ignore list. Use when writing to a path within our listening scope.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -463,226 +454,58 @@ namespace MediaBrowser.Server.Implementations.IO
|
||||||
if (monitorPath)
|
if (monitorPath)
|
||||||
{
|
{
|
||||||
// Avoid implicitly captured closure
|
// Avoid implicitly captured closure
|
||||||
var affectedPath = path;
|
CreateRefresher(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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private void CreateRefresher(string path)
|
||||||
/// Timers the stopped.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="stateInfo">The state info.</param>
|
|
||||||
private async void TimerStopped(object stateInfo)
|
|
||||||
{
|
{
|
||||||
// Extend the timer as long as any of the paths are still being written to.
|
var parentPath = Path.GetDirectoryName(path);
|
||||||
if (_affectedPaths.Any(p => IsFileLocked(p.Key)))
|
|
||||||
|
lock (_activeRefreshers)
|
||||||
{
|
{
|
||||||
Logger.Info("Timer extended.");
|
var refreshers = _activeRefreshers.ToList();
|
||||||
RestartTimer();
|
foreach (var refresher in refreshers)
|
||||||
|
{
|
||||||
|
// Path is already being refreshed
|
||||||
|
if (string.Equals(path, refresher.Path, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
refresher.RestartTimer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Debug("Timer stopped.");
|
// Parent folder is already being refreshed
|
||||||
|
if (_fileSystem.ContainsSubPath(refresher.Path, path))
|
||||||
DisposeTimer();
|
|
||||||
|
|
||||||
var paths = _affectedPaths.Keys.ToList();
|
|
||||||
_affectedPaths.Clear();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
await ProcessPathChanges(paths).ConfigureAwait(false);
|
refresher.AddPath(path);
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.ErrorException("Error processing directory changes", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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))
|
|
||||||
{
|
|
||||||
if (_updateTimer != null)
|
|
||||||
{
|
|
||||||
//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;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DisposeTimer()
|
|
||||||
{
|
|
||||||
lock (_timerLock)
|
|
||||||
{
|
|
||||||
if (_updateTimer != null)
|
|
||||||
{
|
|
||||||
_updateTimer.Dispose();
|
|
||||||
_updateTimer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Processes the path changes.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="paths">The paths.</param>
|
|
||||||
/// <returns>Task.</returns>
|
|
||||||
private async Task ProcessPathChanges(List<string> 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<RefreshMediaLibraryTask>();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var item in itemsToRefresh)
|
// New path is a parent
|
||||||
|
if (_fileSystem.ContainsSubPath(path, refresher.Path))
|
||||||
{
|
{
|
||||||
Logger.Info(item.Name + " (" + item.Path + ") will be refreshed.");
|
refresher.ResetPath(path, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
// They are siblings. Rebase the refresher to the parent folder.
|
||||||
|
if (string.Equals(parentPath, Path.GetDirectoryName(refresher.Path), StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
await item.ChangedExternally().ConfigureAwait(false);
|
refresher.ResetPath(parentPath, path);
|
||||||
}
|
return;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
var newRefresher = new FileRefresher(path, _fileSystem, ConfigurationManager, LibraryManager, TaskManager, Logger);
|
||||||
/// Gets the affected base item.
|
newRefresher.Completed += NewRefresher_Completed;
|
||||||
/// </summary>
|
_activeRefreshers.Add(newRefresher);
|
||||||
/// <param name="path">The path.</param>
|
|
||||||
/// <returns>BaseItem.</returns>
|
|
||||||
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 void NewRefresher_Completed(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
var refresher = (FileRefresher)sender;
|
||||||
|
DisposeRefresher(refresher);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -713,10 +536,29 @@ namespace MediaBrowser.Server.Implementations.IO
|
||||||
watcher.Dispose();
|
watcher.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
DisposeTimer();
|
|
||||||
|
|
||||||
_fileSystemWatchers.Clear();
|
_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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -1959,10 +1959,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||||
var defaults = await GetNewTimerDefaultsInternal(cancellationToken, program).ConfigureAwait(false);
|
var defaults = await GetNewTimerDefaultsInternal(cancellationToken, program).ConfigureAwait(false);
|
||||||
var info = _tvDtoService.GetSeriesTimerInfoDto(defaults.Item1, defaults.Item2, null);
|
var info = _tvDtoService.GetSeriesTimerInfoDto(defaults.Item1, defaults.Item2, null);
|
||||||
|
|
||||||
info.Days = new List<DayOfWeek>
|
info.Days = defaults.Item1.Days;
|
||||||
{
|
|
||||||
program.StartDate.ToLocalTime().DayOfWeek
|
|
||||||
};
|
|
||||||
|
|
||||||
info.DayPattern = _tvDtoService.GetDayPattern(info.Days);
|
info.DayPattern = _tvDtoService.GetDayPattern(info.Days);
|
||||||
|
|
||||||
|
|
|
@ -748,7 +748,7 @@ namespace MediaBrowser.Server.Implementations.Sync
|
||||||
|
|
||||||
_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
|
_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
|
||||||
|
|
||||||
using (var stream = await _subtitleEncoder.GetSubtitles(streamInfo.ItemId, streamInfo.MediaSourceId, subtitleStreamIndex, subtitleStreamInfo.Format, 0, null, cancellationToken).ConfigureAwait(false))
|
using (var stream = await _subtitleEncoder.GetSubtitles(streamInfo.ItemId, streamInfo.MediaSourceId, subtitleStreamIndex, subtitleStreamInfo.Format, 0, null, false, cancellationToken).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
using (var fs = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, true))
|
using (var fs = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, true))
|
||||||
{
|
{
|
||||||
|
|
|
@ -356,9 +356,6 @@ namespace MediaBrowser.WebDashboard.Api
|
||||||
DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "meteor");
|
DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "meteor");
|
||||||
DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "st");
|
DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "st");
|
||||||
DeleteFoldersByName(Path.Combine(bowerPath, "Swiper"), "src");
|
DeleteFoldersByName(Path.Combine(bowerPath, "Swiper"), "src");
|
||||||
DeleteFoldersByName(Path.Combine(bowerPath, "material-design-lite"), "src");
|
|
||||||
DeleteFoldersByName(Path.Combine(bowerPath, "material-design-lite"), "utils");
|
|
||||||
_fileSystem.DeleteFile(Path.Combine(bowerPath, "material-design-lite", "gulpfile.babel.js"));
|
|
||||||
|
|
||||||
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "marked"), true);
|
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "marked"), true);
|
||||||
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "marked-element"), true);
|
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "marked-element"), true);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user