Merge branch 'beta'
This commit is contained in:
commit
37352785ac
|
@ -1,5 +1,4 @@
|
||||||
using MediaBrowser.Common.IO;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Common.Net;
|
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
|
@ -46,6 +45,11 @@ namespace MediaBrowser.Api
|
||||||
/// <value><c>true</c> if [include hidden]; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if [include hidden]; otherwise, <c>false</c>.</value>
|
||||||
[ApiMember(Name = "IncludeHidden", Description = "An optional filter to include or exclude hidden files and folders. true/false", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
|
[ApiMember(Name = "IncludeHidden", Description = "An optional filter to include or exclude hidden files and folders. true/false", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
|
||||||
public bool IncludeHidden { get; set; }
|
public bool IncludeHidden { get; set; }
|
||||||
|
|
||||||
|
public GetDirectoryContents()
|
||||||
|
{
|
||||||
|
IncludeHidden = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Environment/NetworkShares", "GET", Summary = "Gets shares from a network device")]
|
[Route("/Environment/NetworkShares", "GET", Summary = "Gets shares from a network device")]
|
||||||
|
|
|
@ -2192,7 +2192,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
{
|
{
|
||||||
if (string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
inputModifier += " -noaccurate_seek";
|
//inputModifier += " -noaccurate_seek";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -141,10 +141,10 @@ namespace MediaBrowser.Api.Playback
|
||||||
|
|
||||||
var profile = request.DeviceProfile;
|
var profile = request.DeviceProfile;
|
||||||
|
|
||||||
|
if (profile == null)
|
||||||
|
{
|
||||||
var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
|
var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
|
||||||
if (caps != null)
|
if (caps != null)
|
||||||
{
|
|
||||||
if (profile == null)
|
|
||||||
{
|
{
|
||||||
profile = caps.DeviceProfile;
|
profile = caps.DeviceProfile;
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,13 +66,12 @@ namespace MediaBrowser.Api
|
||||||
{
|
{
|
||||||
_config.Configuration.IsStartupWizardCompleted = true;
|
_config.Configuration.IsStartupWizardCompleted = true;
|
||||||
_config.Configuration.EnableLocalizedGuids = true;
|
_config.Configuration.EnableLocalizedGuids = true;
|
||||||
_config.Configuration.MergeMetadataAndImagesByName = true;
|
|
||||||
_config.Configuration.EnableStandaloneMetadata = true;
|
|
||||||
_config.Configuration.EnableLibraryMetadataSubFolder = true;
|
_config.Configuration.EnableLibraryMetadataSubFolder = true;
|
||||||
_config.Configuration.EnableCustomPathSubFolders = true;
|
_config.Configuration.EnableCustomPathSubFolders = true;
|
||||||
_config.Configuration.DisableStartupScan = true;
|
_config.Configuration.DisableStartupScan = true;
|
||||||
_config.Configuration.EnableUserViews = true;
|
_config.Configuration.EnableUserViews = true;
|
||||||
_config.Configuration.EnableDateLastRefresh = true;
|
_config.Configuration.EnableDateLastRefresh = true;
|
||||||
|
_config.Configuration.MergeMetadataAndImagesByName = true;
|
||||||
_config.SaveConfiguration();
|
_config.SaveConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -370,6 +370,8 @@ namespace MediaBrowser.Api.UserLibrary
|
||||||
|
|
||||||
public void Post(ReportPlaybackStopped request)
|
public void Post(ReportPlaybackStopped request)
|
||||||
{
|
{
|
||||||
|
Logger.Debug("ReportPlaybackStopped PlaySessionId: {0}", request.PlaySessionId ?? string.Empty);
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(request.PlaySessionId))
|
if (!string.IsNullOrWhiteSpace(request.PlaySessionId))
|
||||||
{
|
{
|
||||||
ApiEntryPoint.Instance.KillTranscodingJobs(AuthorizationContext.GetAuthorizationInfo(Request).DeviceId, request.PlaySessionId, s => true);
|
ApiEntryPoint.Instance.KillTranscodingJobs(AuthorizationContext.GetAuthorizationInfo(Request).DeviceId, request.PlaySessionId, s => true);
|
||||||
|
|
|
@ -6,7 +6,6 @@ using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.NetworkInformation;
|
using System.Net.NetworkInformation;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Threading;
|
|
||||||
using MoreLinq;
|
using MoreLinq;
|
||||||
|
|
||||||
namespace MediaBrowser.Common.Implementations.Networking
|
namespace MediaBrowser.Common.Implementations.Networking
|
||||||
|
@ -14,22 +13,11 @@ namespace MediaBrowser.Common.Implementations.Networking
|
||||||
public abstract class BaseNetworkManager
|
public abstract class BaseNetworkManager
|
||||||
{
|
{
|
||||||
protected ILogger Logger { get; private set; }
|
protected ILogger Logger { get; private set; }
|
||||||
private Timer _clearCacheTimer;
|
private DateTime _lastRefresh;
|
||||||
|
|
||||||
protected BaseNetworkManager(ILogger logger)
|
protected BaseNetworkManager(ILogger logger)
|
||||||
{
|
{
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
|
|
||||||
// Can't use network change events due to a crash in Linux
|
|
||||||
_clearCacheTimer = new Timer(ClearCacheTimerCallback, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ClearCacheTimerCallback(object state)
|
|
||||||
{
|
|
||||||
lock (_localIpAddressSyncLock)
|
|
||||||
{
|
|
||||||
_localIpAddresses = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private volatile List<IPAddress> _localIpAddresses;
|
private volatile List<IPAddress> _localIpAddresses;
|
||||||
|
@ -41,15 +29,21 @@ namespace MediaBrowser.Common.Implementations.Networking
|
||||||
/// <returns>IPAddress.</returns>
|
/// <returns>IPAddress.</returns>
|
||||||
public IEnumerable<IPAddress> GetLocalIpAddresses()
|
public IEnumerable<IPAddress> GetLocalIpAddresses()
|
||||||
{
|
{
|
||||||
if (_localIpAddresses == null)
|
const int cacheMinutes = 3;
|
||||||
|
var forceRefresh = (DateTime.UtcNow - _lastRefresh).TotalMinutes >= cacheMinutes;
|
||||||
|
|
||||||
|
if (_localIpAddresses == null || forceRefresh)
|
||||||
{
|
{
|
||||||
lock (_localIpAddressSyncLock)
|
lock (_localIpAddressSyncLock)
|
||||||
{
|
{
|
||||||
if (_localIpAddresses == null)
|
forceRefresh = (DateTime.UtcNow - _lastRefresh).TotalMinutes >= cacheMinutes;
|
||||||
|
|
||||||
|
if (_localIpAddresses == null || forceRefresh)
|
||||||
{
|
{
|
||||||
var addresses = GetLocalIpAddressesInternal().ToList();
|
var addresses = GetLocalIpAddressesInternal().ToList();
|
||||||
|
|
||||||
_localIpAddresses = addresses;
|
_localIpAddresses = addresses;
|
||||||
|
_lastRefresh = DateTime.UtcNow;
|
||||||
|
|
||||||
return addresses;
|
return addresses;
|
||||||
}
|
}
|
||||||
|
|
|
@ -233,7 +233,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _triggers
|
/// The _triggers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private IEnumerable<ITaskTrigger> _triggers;
|
private volatile List<ITaskTrigger> _triggers;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _triggers sync lock
|
/// The _triggers sync lock
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -532,7 +532,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
||||||
/// Loads the triggers.
|
/// Loads the triggers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
|
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
|
||||||
private IEnumerable<ITaskTrigger> LoadTriggers()
|
private List<ITaskTrigger> LoadTriggers()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -543,12 +543,12 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
||||||
catch (FileNotFoundException)
|
catch (FileNotFoundException)
|
||||||
{
|
{
|
||||||
// File doesn't exist. No biggie. Return defaults.
|
// File doesn't exist. No biggie. Return defaults.
|
||||||
return ScheduledTask.GetDefaultTriggers();
|
return ScheduledTask.GetDefaultTriggers().ToList();
|
||||||
}
|
}
|
||||||
catch (DirectoryNotFoundException)
|
catch (DirectoryNotFoundException)
|
||||||
{
|
{
|
||||||
// File doesn't exist. No biggie. Return defaults.
|
// File doesn't exist. No biggie. Return defaults.
|
||||||
return ScheduledTask.GetDefaultTriggers();
|
return ScheduledTask.GetDefaultTriggers().ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -90,6 +90,7 @@
|
||||||
<Compile Include="Security\IRequiresRegistration.cs" />
|
<Compile Include="Security\IRequiresRegistration.cs" />
|
||||||
<Compile Include="Security\ISecurityManager.cs" />
|
<Compile Include="Security\ISecurityManager.cs" />
|
||||||
<Compile Include="Security\PaymentRequiredException.cs" />
|
<Compile Include="Security\PaymentRequiredException.cs" />
|
||||||
|
<Compile Include="Threading\PeriodicTimer.cs" />
|
||||||
<Compile Include="Updates\IInstallationManager.cs" />
|
<Compile Include="Updates\IInstallationManager.cs" />
|
||||||
<Compile Include="Updates\InstallationEventArgs.cs" />
|
<Compile Include="Updates\InstallationEventArgs.cs" />
|
||||||
<Compile Include="Updates\InstallationFailedEventArgs.cs" />
|
<Compile Include="Updates\InstallationFailedEventArgs.cs" />
|
||||||
|
|
72
MediaBrowser.Common/Threading/PeriodicTimer.cs
Normal file
72
MediaBrowser.Common/Threading/PeriodicTimer.cs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Common.Threading
|
||||||
|
{
|
||||||
|
public class PeriodicTimer : IDisposable
|
||||||
|
{
|
||||||
|
public Action<object> Callback { get; set; }
|
||||||
|
private Timer _timer;
|
||||||
|
private readonly object _state;
|
||||||
|
private readonly object _timerLock = new object();
|
||||||
|
private readonly TimeSpan _period;
|
||||||
|
|
||||||
|
public PeriodicTimer(Action<object> callback, object state, TimeSpan dueTime, TimeSpan period)
|
||||||
|
{
|
||||||
|
if (callback == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("callback");
|
||||||
|
}
|
||||||
|
|
||||||
|
Callback = callback;
|
||||||
|
_period = period;
|
||||||
|
_state = state;
|
||||||
|
|
||||||
|
StartTimer(dueTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Mode == PowerModes.Resume)
|
||||||
|
{
|
||||||
|
DisposeTimer();
|
||||||
|
StartTimer(Timeout.InfiniteTimeSpan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TimerCallback(object state)
|
||||||
|
{
|
||||||
|
Callback(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartTimer(TimeSpan dueTime)
|
||||||
|
{
|
||||||
|
lock (_timerLock)
|
||||||
|
{
|
||||||
|
_timer = new Timer(TimerCallback, _state, dueTime, _period);
|
||||||
|
|
||||||
|
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisposeTimer()
|
||||||
|
{
|
||||||
|
SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
|
||||||
|
|
||||||
|
lock (_timerLock)
|
||||||
|
{
|
||||||
|
if (_timer != null)
|
||||||
|
{
|
||||||
|
_timer.Dispose();
|
||||||
|
_timer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
DisposeTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,17 +1,21 @@
|
||||||
using MediaBrowser.Controller.Providers;
|
using System;
|
||||||
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Users;
|
using MediaBrowser.Model.Users;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.Entities.Audio
|
namespace MediaBrowser.Controller.Entities.Audio
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class MusicAlbum
|
/// Class MusicAlbum
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo<AlbumInfo>
|
public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo<AlbumInfo>, IMetadataContainer
|
||||||
{
|
{
|
||||||
public MusicAlbum()
|
public MusicAlbum()
|
||||||
{
|
{
|
||||||
|
@ -139,5 +143,58 @@ namespace MediaBrowser.Controller.Entities.Audio
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var items = GetRecursiveChildren().ToList();
|
||||||
|
|
||||||
|
var songs = items.OfType<Audio>().ToList();
|
||||||
|
|
||||||
|
var others = items.Except(songs).ToList();
|
||||||
|
|
||||||
|
var totalItems = songs.Count + others.Count;
|
||||||
|
var numComplete = 0;
|
||||||
|
|
||||||
|
var childUpdateType = ItemUpdateType.None;
|
||||||
|
|
||||||
|
// Refresh songs
|
||||||
|
foreach (var item in songs)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var updateType = await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
|
||||||
|
childUpdateType = childUpdateType | updateType;
|
||||||
|
|
||||||
|
numComplete++;
|
||||||
|
double percent = numComplete;
|
||||||
|
percent /= totalItems;
|
||||||
|
progress.Report(percent * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
var parentRefreshOptions = refreshOptions;
|
||||||
|
if (childUpdateType > ItemUpdateType.None)
|
||||||
|
{
|
||||||
|
parentRefreshOptions = new MetadataRefreshOptions(refreshOptions);
|
||||||
|
parentRefreshOptions.MetadataRefreshMode = MetadataRefreshMode.FullRefresh;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh current item
|
||||||
|
await RefreshMetadata(parentRefreshOptions, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
// Refresh all non-songs
|
||||||
|
foreach (var item in others)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var updateType = await item.RefreshMetadata(parentRefreshOptions, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
numComplete++;
|
||||||
|
double percent = numComplete;
|
||||||
|
percent /= totalItems;
|
||||||
|
progress.Report(percent * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.Report(100);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,7 +109,7 @@ namespace MediaBrowser.Controller.Entities
|
||||||
/// <value>The last activity date.</value>
|
/// <value>The last activity date.</value>
|
||||||
public DateTime? LastActivityDate { get; set; }
|
public DateTime? LastActivityDate { get; set; }
|
||||||
|
|
||||||
private UserConfiguration _config;
|
private volatile UserConfiguration _config;
|
||||||
private readonly object _configSyncLock = new object();
|
private readonly object _configSyncLock = new object();
|
||||||
[IgnoreDataMember]
|
[IgnoreDataMember]
|
||||||
public UserConfiguration Configuration
|
public UserConfiguration Configuration
|
||||||
|
@ -132,7 +132,7 @@ namespace MediaBrowser.Controller.Entities
|
||||||
set { _config = value; }
|
set { _config = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserPolicy _policy;
|
private volatile UserPolicy _policy;
|
||||||
private readonly object _policySyncLock = new object();
|
private readonly object _policySyncLock = new object();
|
||||||
[IgnoreDataMember]
|
[IgnoreDataMember]
|
||||||
public UserPolicy Policy
|
public UserPolicy Policy
|
||||||
|
|
|
@ -265,6 +265,7 @@
|
||||||
<Compile Include="Playlists\IPlaylistManager.cs" />
|
<Compile Include="Playlists\IPlaylistManager.cs" />
|
||||||
<Compile Include="Playlists\Playlist.cs" />
|
<Compile Include="Playlists\Playlist.cs" />
|
||||||
<Compile Include="Plugins\ILocalizablePlugin.cs" />
|
<Compile Include="Plugins\ILocalizablePlugin.cs" />
|
||||||
|
<Compile Include="Power\IPowerManagement.cs" />
|
||||||
<Compile Include="Providers\AlbumInfo.cs" />
|
<Compile Include="Providers\AlbumInfo.cs" />
|
||||||
<Compile Include="Providers\ArtistInfo.cs" />
|
<Compile Include="Providers\ArtistInfo.cs" />
|
||||||
<Compile Include="Providers\BookInfo.cs" />
|
<Compile Include="Providers\BookInfo.cs" />
|
||||||
|
|
13
MediaBrowser.Controller/Power/IPowerManagement.cs
Normal file
13
MediaBrowser.Controller/Power/IPowerManagement.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Controller.Power
|
||||||
|
{
|
||||||
|
public interface IPowerManagement
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Schedules the wake.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="utcTime">The UTC time.</param>
|
||||||
|
void ScheduleWake(DateTime utcTime);
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,14 +22,26 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
#region Fields & Properties
|
#region Fields & Properties
|
||||||
|
|
||||||
private Timer _timer;
|
private Timer _timer;
|
||||||
private Timer _volumeTimer;
|
|
||||||
|
|
||||||
public DeviceInfo Properties { get; set; }
|
public DeviceInfo Properties { get; set; }
|
||||||
|
|
||||||
private int _muteVol;
|
private int _muteVol;
|
||||||
public bool IsMuted { get; set; }
|
public bool IsMuted { get; set; }
|
||||||
|
|
||||||
public int Volume { get; set; }
|
private int _volume;
|
||||||
|
|
||||||
|
public int Volume
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
RefreshVolumeIfNeeded();
|
||||||
|
return _volume;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_volume = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public TimeSpan? Duration { get; set; }
|
public TimeSpan? Duration { get; set; }
|
||||||
|
|
||||||
|
@ -93,11 +105,6 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
return 1000;
|
return 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetVolumeTimerIntervalMs()
|
|
||||||
{
|
|
||||||
return 5000;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int GetInactiveTimerIntervalMs()
|
private int GetInactiveTimerIntervalMs()
|
||||||
{
|
{
|
||||||
return 20000;
|
return 20000;
|
||||||
|
@ -107,11 +114,37 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
_timer = new Timer(TimerCallback, null, GetPlaybackTimerIntervalMs(), GetInactiveTimerIntervalMs());
|
_timer = new Timer(TimerCallback, null, GetPlaybackTimerIntervalMs(), GetInactiveTimerIntervalMs());
|
||||||
|
|
||||||
_volumeTimer = new Timer(VolumeTimerCallback, null, Timeout.Infinite, Timeout.Infinite);
|
|
||||||
|
|
||||||
_timerActive = false;
|
_timerActive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DateTime _lastVolumeRefresh;
|
||||||
|
private void RefreshVolumeIfNeeded()
|
||||||
|
{
|
||||||
|
if (!_timerActive)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DateTime.UtcNow >= _lastVolumeRefresh.AddSeconds(5))
|
||||||
|
{
|
||||||
|
_lastVolumeRefresh = DateTime.UtcNow;
|
||||||
|
RefreshVolume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void RefreshVolume()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await GetVolume().ConfigureAwait(false);
|
||||||
|
await GetMute().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error updating device volume info for {0}", ex, Properties.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private readonly object _timerLock = new object();
|
private readonly object _timerLock = new object();
|
||||||
private bool _timerActive;
|
private bool _timerActive;
|
||||||
private void RestartTimer()
|
private void RestartTimer()
|
||||||
|
@ -124,7 +157,6 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
_logger.Debug("RestartTimer");
|
_logger.Debug("RestartTimer");
|
||||||
_timer.Change(10, GetPlaybackTimerIntervalMs());
|
_timer.Change(10, GetPlaybackTimerIntervalMs());
|
||||||
_volumeTimer.Change(100, GetVolumeTimerIntervalMs());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_timerActive = true;
|
_timerActive = true;
|
||||||
|
@ -150,10 +182,6 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
_timer.Change(interval, interval);
|
_timer.Change(interval, interval);
|
||||||
}
|
}
|
||||||
if (_volumeTimer != null)
|
|
||||||
{
|
|
||||||
_volumeTimer.Change(Timeout.Infinite, Timeout.Infinite);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_timerActive = false;
|
_timerActive = false;
|
||||||
|
@ -440,19 +468,6 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void VolumeTimerCallback(object sender)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await GetVolume().ConfigureAwait(false);
|
|
||||||
await GetMute().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.ErrorException("Error updating device volume info for {0}", ex, Properties.Name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task GetVolume()
|
private async Task GetVolume()
|
||||||
{
|
{
|
||||||
var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
|
var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
|
||||||
|
@ -1012,7 +1027,6 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
|
|
||||||
DisposeTimer();
|
DisposeTimer();
|
||||||
DisposeVolumeTimer();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1025,15 +1039,6 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisposeVolumeTimer()
|
|
||||||
{
|
|
||||||
if (_volumeTimer != null)
|
|
||||||
{
|
|
||||||
_volumeTimer.Dispose();
|
|
||||||
_volumeTimer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
|
|
@ -37,11 +37,28 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
private readonly IDeviceDiscovery _deviceDiscovery;
|
private readonly IDeviceDiscovery _deviceDiscovery;
|
||||||
private readonly string _serverAddress;
|
private readonly string _serverAddress;
|
||||||
private readonly string _accessToken;
|
private readonly string _accessToken;
|
||||||
|
private readonly DateTime _creationTime;
|
||||||
|
|
||||||
public bool IsSessionActive
|
public bool IsSessionActive
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
var lastDateKnownActivity = new[] { _creationTime, _device.DateLastActivity }.Max();
|
||||||
|
|
||||||
|
if (DateTime.UtcNow >= lastDateKnownActivity.AddSeconds(120))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Session is inactive, mark it for Disposal and don't start the elapsed timer.
|
||||||
|
_sessionManager.ReportSessionEnded(_session.Id);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error in ReportSessionEnded", ex);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return _device != null;
|
return _device != null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,8 +72,6 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
get { return IsSessionActive; }
|
get { return IsSessionActive; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private Timer _updateTimer;
|
|
||||||
|
|
||||||
public PlayToController(SessionInfo session, ISessionManager sessionManager, ILibraryManager libraryManager, ILogger logger, IDlnaManager dlnaManager, IUserManager userManager, IImageProcessor imageProcessor, string serverAddress, string accessToken, IDeviceDiscovery deviceDiscovery, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager)
|
public PlayToController(SessionInfo session, ISessionManager sessionManager, ILibraryManager libraryManager, ILogger logger, IDlnaManager dlnaManager, IUserManager userManager, IImageProcessor imageProcessor, string serverAddress, string accessToken, IDeviceDiscovery deviceDiscovery, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager)
|
||||||
{
|
{
|
||||||
_session = session;
|
_session = session;
|
||||||
|
@ -72,6 +87,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
_accessToken = accessToken;
|
_accessToken = accessToken;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_creationTime = DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Init(Device device)
|
public void Init(Device device)
|
||||||
|
@ -84,8 +100,6 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
_device.Start();
|
_device.Start();
|
||||||
|
|
||||||
_deviceDiscovery.DeviceLeft += _deviceDiscovery_DeviceLeft;
|
_deviceDiscovery.DeviceLeft += _deviceDiscovery_DeviceLeft;
|
||||||
|
|
||||||
_updateTimer = new Timer(updateTimer_Elapsed, null, 60000, 60000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _deviceDiscovery_DeviceLeft(object sender, SsdpMessageEventArgs e)
|
void _deviceDiscovery_DeviceLeft(object sender, SsdpMessageEventArgs e)
|
||||||
|
@ -117,22 +131,6 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTimer_Elapsed(object state)
|
|
||||||
{
|
|
||||||
if (DateTime.UtcNow >= _device.DateLastActivity.AddSeconds(120))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Session is inactive, mark it for Disposal and don't start the elapsed timer.
|
|
||||||
_sessionManager.ReportSessionEnded(_session.Id);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.ErrorException("Error in ReportSessionEnded", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async void _device_MediaChanged(object sender, MediaChangedEventArgs e)
|
async void _device_MediaChanged(object sender, MediaChangedEventArgs e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -634,21 +632,10 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
_device.MediaChanged -= _device_MediaChanged;
|
_device.MediaChanged -= _device_MediaChanged;
|
||||||
_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
|
_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
|
||||||
|
|
||||||
DisposeUpdateTimer();
|
|
||||||
|
|
||||||
_device.Dispose();
|
_device.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisposeUpdateTimer()
|
|
||||||
{
|
|
||||||
if (_updateTimer != null)
|
|
||||||
{
|
|
||||||
_updateTimer.Dispose();
|
|
||||||
_updateTimer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||||
|
|
||||||
public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
|
public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
|
||||||
|
|
|
@ -36,7 +36,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
private readonly IMediaSourceManager _mediaSourceManager;
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
|
|
||||||
private readonly List<string> _nonRendererUrls = new List<string>();
|
private readonly List<string> _nonRendererUrls = new List<string>();
|
||||||
private Timer _clearNonRenderersTimer;
|
private DateTime _lastRendererClear;
|
||||||
|
|
||||||
public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager)
|
public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager)
|
||||||
{
|
{
|
||||||
|
@ -57,19 +57,9 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
_clearNonRenderersTimer = new Timer(OnClearUrlTimerCallback, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
|
|
||||||
|
|
||||||
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
|
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnClearUrlTimerCallback(object state)
|
|
||||||
{
|
|
||||||
lock (_nonRendererUrls)
|
|
||||||
{
|
|
||||||
_nonRendererUrls.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e)
|
async void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e)
|
||||||
{
|
{
|
||||||
string usn;
|
string usn;
|
||||||
|
@ -99,6 +89,12 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
|
|
||||||
lock (_nonRendererUrls)
|
lock (_nonRendererUrls)
|
||||||
{
|
{
|
||||||
|
if ((DateTime.UtcNow - _lastRendererClear).TotalMinutes >= 10)
|
||||||
|
{
|
||||||
|
_nonRendererUrls.Clear();
|
||||||
|
_lastRendererClear = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
if (_nonRendererUrls.Contains(location, StringComparer.OrdinalIgnoreCase))
|
if (_nonRendererUrls.Contains(location, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
@ -181,12 +177,6 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered;
|
_deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered;
|
||||||
|
|
||||||
if (_clearNonRenderersTimer != null)
|
|
||||||
{
|
|
||||||
_clearNonRenderersTimer.Dispose();
|
|
||||||
_clearNonRenderersTimer = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ namespace MediaBrowser.Dlna.Profiles
|
||||||
Container = "avi",
|
Container = "avi",
|
||||||
Type = DlnaProfileType.Video,
|
Type = DlnaProfileType.Video,
|
||||||
VideoCodec = "mpeg1video,mpeg2video,mpeg4,h264,vc1",
|
VideoCodec = "mpeg1video,mpeg2video,mpeg4,h264,vc1",
|
||||||
AudioCodec = "ac3,dca,mp2,mp3,pcm"
|
AudioCodec = "ac3,dca,mp2,mp3,pcm,dca"
|
||||||
},
|
},
|
||||||
|
|
||||||
new DirectPlayProfile
|
new DirectPlayProfile
|
||||||
|
@ -66,7 +66,7 @@ namespace MediaBrowser.Dlna.Profiles
|
||||||
Container = "mpeg",
|
Container = "mpeg",
|
||||||
Type = DlnaProfileType.Video,
|
Type = DlnaProfileType.Video,
|
||||||
VideoCodec = "mpeg1video,mpeg2video",
|
VideoCodec = "mpeg1video,mpeg2video",
|
||||||
AudioCodec = "ac3,dca,mp2,mp3,pcm"
|
AudioCodec = "ac3,dca,mp2,mp3,pcm,dca"
|
||||||
},
|
},
|
||||||
|
|
||||||
new DirectPlayProfile
|
new DirectPlayProfile
|
||||||
|
@ -74,7 +74,7 @@ namespace MediaBrowser.Dlna.Profiles
|
||||||
Container = "mkv",
|
Container = "mkv",
|
||||||
Type = DlnaProfileType.Video,
|
Type = DlnaProfileType.Video,
|
||||||
VideoCodec = "mpeg1video,mpeg2video,mpeg4,h264,vc1",
|
VideoCodec = "mpeg1video,mpeg2video,mpeg4,h264,vc1",
|
||||||
AudioCodec = "ac3,dca,aac,mp2,mp3,pcm"
|
AudioCodec = "ac3,dca,aac,mp2,mp3,pcm,dca"
|
||||||
},
|
},
|
||||||
|
|
||||||
new DirectPlayProfile
|
new DirectPlayProfile
|
||||||
|
@ -82,7 +82,7 @@ namespace MediaBrowser.Dlna.Profiles
|
||||||
Container = "ts",
|
Container = "ts",
|
||||||
Type = DlnaProfileType.Video,
|
Type = DlnaProfileType.Video,
|
||||||
VideoCodec = "mpeg1video,mpeg2video,h264,vc1",
|
VideoCodec = "mpeg1video,mpeg2video,h264,vc1",
|
||||||
AudioCodec = "ac3,dca,mp2,mp3,aac"
|
AudioCodec = "ac3,dca,mp2,mp3,aac,dca"
|
||||||
},
|
},
|
||||||
|
|
||||||
new DirectPlayProfile
|
new DirectPlayProfile
|
||||||
|
@ -90,7 +90,7 @@ namespace MediaBrowser.Dlna.Profiles
|
||||||
Container = "mp4,mov",
|
Container = "mp4,mov",
|
||||||
Type = DlnaProfileType.Video,
|
Type = DlnaProfileType.Video,
|
||||||
VideoCodec = "h264,mpeg4",
|
VideoCodec = "h264,mpeg4",
|
||||||
AudioCodec = "ac3,aac,mp2,mp3"
|
AudioCodec = "ac3,aac,mp2,mp3,dca"
|
||||||
},
|
},
|
||||||
|
|
||||||
new DirectPlayProfile
|
new DirectPlayProfile
|
||||||
|
|
|
@ -37,11 +37,11 @@
|
||||||
<IgnoreTranscodeByteRangeRequests>true</IgnoreTranscodeByteRangeRequests>
|
<IgnoreTranscodeByteRangeRequests>true</IgnoreTranscodeByteRangeRequests>
|
||||||
<XmlRootAttributes />
|
<XmlRootAttributes />
|
||||||
<DirectPlayProfiles>
|
<DirectPlayProfiles>
|
||||||
<DirectPlayProfile container="avi" audioCodec="ac3,dca,mp2,mp3,pcm" videoCodec="mpeg1video,mpeg2video,mpeg4,h264,vc1" type="Video" />
|
<DirectPlayProfile container="avi" audioCodec="ac3,dca,mp2,mp3,pcm,dca" videoCodec="mpeg1video,mpeg2video,mpeg4,h264,vc1" type="Video" />
|
||||||
<DirectPlayProfile container="mpeg" audioCodec="ac3,dca,mp2,mp3,pcm" videoCodec="mpeg1video,mpeg2video" type="Video" />
|
<DirectPlayProfile container="mpeg" audioCodec="ac3,dca,mp2,mp3,pcm,dca" videoCodec="mpeg1video,mpeg2video" type="Video" />
|
||||||
<DirectPlayProfile container="mkv" audioCodec="ac3,dca,aac,mp2,mp3,pcm" videoCodec="mpeg1video,mpeg2video,mpeg4,h264,vc1" type="Video" />
|
<DirectPlayProfile container="mkv" audioCodec="ac3,dca,aac,mp2,mp3,pcm,dca" videoCodec="mpeg1video,mpeg2video,mpeg4,h264,vc1" type="Video" />
|
||||||
<DirectPlayProfile container="ts" audioCodec="ac3,dca,mp2,mp3,aac" videoCodec="mpeg1video,mpeg2video,h264,vc1" type="Video" />
|
<DirectPlayProfile container="ts" audioCodec="ac3,dca,mp2,mp3,aac,dca" videoCodec="mpeg1video,mpeg2video,h264,vc1" type="Video" />
|
||||||
<DirectPlayProfile container="mp4,mov" audioCodec="ac3,aac,mp2,mp3" videoCodec="h264,mpeg4" type="Video" />
|
<DirectPlayProfile container="mp4,mov" audioCodec="ac3,aac,mp2,mp3,dca" videoCodec="h264,mpeg4" type="Video" />
|
||||||
<DirectPlayProfile container="asf" audioCodec="wmav2,wmapro" videoCodec="vc1" type="Video" />
|
<DirectPlayProfile container="asf" audioCodec="wmav2,wmapro" videoCodec="vc1" type="Video" />
|
||||||
<DirectPlayProfile container="asf" audioCodec="mp2,ac3" videoCodec="mpeg2video" type="Video" />
|
<DirectPlayProfile container="asf" audioCodec="mp2,ac3" videoCodec="mpeg2video" type="Video" />
|
||||||
<DirectPlayProfile container="mp3" audioCodec="mp2,mp3" type="Audio" />
|
<DirectPlayProfile container="mp3" audioCodec="mp2,mp3" type="Audio" />
|
||||||
|
|
|
@ -33,12 +33,8 @@ namespace MediaBrowser.Dlna.Ssdp
|
||||||
private readonly IPAddress _ssdpIp = IPAddress.Parse(SSDPAddr);
|
private readonly IPAddress _ssdpIp = IPAddress.Parse(SSDPAddr);
|
||||||
private readonly IPEndPoint _ssdpEndp = new IPEndPoint(IPAddress.Parse(SSDPAddr), SSDPPort);
|
private readonly IPEndPoint _ssdpEndp = new IPEndPoint(IPAddress.Parse(SSDPAddr), SSDPPort);
|
||||||
|
|
||||||
private Timer _queueTimer;
|
|
||||||
private Timer _notificationTimer;
|
private Timer _notificationTimer;
|
||||||
|
|
||||||
private readonly AutoResetEvent _datagramPosted = new AutoResetEvent(false);
|
|
||||||
private readonly ConcurrentQueue<Datagram> _messageQueue = new ConcurrentQueue<Datagram>();
|
|
||||||
|
|
||||||
private bool _isDisposed;
|
private bool _isDisposed;
|
||||||
private readonly ConcurrentDictionary<Guid, List<UpnpDevice>> _devices = new ConcurrentDictionary<Guid, List<UpnpDevice>>();
|
private readonly ConcurrentDictionary<Guid, List<UpnpDevice>> _devices = new ConcurrentDictionary<Guid, List<UpnpDevice>>();
|
||||||
|
|
||||||
|
@ -121,9 +117,13 @@ namespace MediaBrowser.Dlna.Ssdp
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
RestartSocketListener();
|
DisposeSocket();
|
||||||
|
StopAliveNotifier();
|
||||||
|
|
||||||
|
RestartSocketListener();
|
||||||
ReloadAliveNotifier();
|
ReloadAliveNotifier();
|
||||||
|
|
||||||
|
SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
|
||||||
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
|
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +131,7 @@ namespace MediaBrowser.Dlna.Ssdp
|
||||||
{
|
{
|
||||||
if (e.Mode == PowerModes.Resume)
|
if (e.Mode == PowerModes.Resume)
|
||||||
{
|
{
|
||||||
NotifyAll();
|
Start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +154,7 @@ namespace MediaBrowser.Dlna.Ssdp
|
||||||
SendDatagram("M-SEARCH * HTTP/1.1", values, _ssdpEndp, localIp, true, 2);
|
SendDatagram("M-SEARCH * HTTP/1.1", values, _ssdpEndp, localIp, true, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendDatagram(string header,
|
public async void SendDatagram(string header,
|
||||||
Dictionary<string, string> values,
|
Dictionary<string, string> values,
|
||||||
EndPoint endpoint,
|
EndPoint endpoint,
|
||||||
EndPoint localAddress,
|
EndPoint localAddress,
|
||||||
|
@ -162,29 +162,19 @@ namespace MediaBrowser.Dlna.Ssdp
|
||||||
int sendCount)
|
int sendCount)
|
||||||
{
|
{
|
||||||
var msg = new SsdpMessageBuilder().BuildMessage(header, values);
|
var msg = new SsdpMessageBuilder().BuildMessage(header, values);
|
||||||
var queued = false;
|
|
||||||
|
|
||||||
var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLogging;
|
var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLogging;
|
||||||
|
|
||||||
for (var i = 0; i < sendCount; i++)
|
for (var i = 0; i < sendCount; i++)
|
||||||
{
|
{
|
||||||
|
if (i > 0)
|
||||||
|
{
|
||||||
|
await Task.Delay(500).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
var dgram = new Datagram(endpoint, localAddress, _logger, msg, isBroadcast, enableDebugLogging);
|
var dgram = new Datagram(endpoint, localAddress, _logger, msg, isBroadcast, enableDebugLogging);
|
||||||
|
|
||||||
if (_messageQueue.Count == 0)
|
|
||||||
{
|
|
||||||
dgram.Send();
|
dgram.Send();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
_messageQueue.Enqueue(dgram);
|
|
||||||
queued = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (queued)
|
|
||||||
{
|
|
||||||
StartQueueTimer();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -254,47 +244,10 @@ namespace MediaBrowser.Dlna.Ssdp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly object _queueTimerSyncLock = new object();
|
|
||||||
private void StartQueueTimer()
|
|
||||||
{
|
|
||||||
lock (_queueTimerSyncLock)
|
|
||||||
{
|
|
||||||
if (_queueTimer == null)
|
|
||||||
{
|
|
||||||
_queueTimer = new Timer(QueueTimerCallback, null, 500, Timeout.Infinite);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_queueTimer.Change(500, Timeout.Infinite);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void QueueTimerCallback(object state)
|
|
||||||
{
|
|
||||||
Datagram msg;
|
|
||||||
while (_messageQueue.TryDequeue(out msg))
|
|
||||||
{
|
|
||||||
msg.Send();
|
|
||||||
}
|
|
||||||
|
|
||||||
_datagramPosted.Set();
|
|
||||||
|
|
||||||
if (_messageQueue.Count > 0)
|
|
||||||
{
|
|
||||||
StartQueueTimer();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
DisposeQueueTimer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RestartSocketListener()
|
private void RestartSocketListener()
|
||||||
{
|
{
|
||||||
if (_isDisposed)
|
if (_isDisposed)
|
||||||
{
|
{
|
||||||
StopSocketRetryTimer();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,8 +257,6 @@ namespace MediaBrowser.Dlna.Ssdp
|
||||||
|
|
||||||
_logger.Info("MultiCast socket created");
|
_logger.Info("MultiCast socket created");
|
||||||
|
|
||||||
StopSocketRetryTimer();
|
|
||||||
|
|
||||||
Receive();
|
Receive();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -315,31 +266,6 @@ namespace MediaBrowser.Dlna.Ssdp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Timer _socketRetryTimer;
|
|
||||||
private readonly object _socketRetryLock = new object();
|
|
||||||
private void StartSocketRetryTimer()
|
|
||||||
{
|
|
||||||
lock (_socketRetryLock)
|
|
||||||
{
|
|
||||||
if (_socketRetryTimer == null)
|
|
||||||
{
|
|
||||||
_socketRetryTimer = new Timer(s => RestartSocketListener(), null, TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StopSocketRetryTimer()
|
|
||||||
{
|
|
||||||
lock (_socketRetryLock)
|
|
||||||
{
|
|
||||||
if (_socketRetryTimer != null)
|
|
||||||
{
|
|
||||||
_socketRetryTimer.Dispose();
|
|
||||||
_socketRetryTimer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Receive()
|
private void Receive()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -448,16 +374,9 @@ namespace MediaBrowser.Dlna.Ssdp
|
||||||
SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
|
SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
|
||||||
|
|
||||||
_isDisposed = true;
|
_isDisposed = true;
|
||||||
while (_messageQueue.Count != 0)
|
|
||||||
{
|
|
||||||
_datagramPosted.WaitOne();
|
|
||||||
}
|
|
||||||
|
|
||||||
DisposeSocket();
|
DisposeSocket();
|
||||||
DisposeQueueTimer();
|
StopAliveNotifier();
|
||||||
DisposeNotificationTimer();
|
|
||||||
|
|
||||||
_datagramPosted.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisposeSocket()
|
private void DisposeSocket()
|
||||||
|
@ -470,18 +389,6 @@ namespace MediaBrowser.Dlna.Ssdp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisposeQueueTimer()
|
|
||||||
{
|
|
||||||
lock (_queueTimerSyncLock)
|
|
||||||
{
|
|
||||||
if (_queueTimer != null)
|
|
||||||
{
|
|
||||||
_queueTimer.Dispose();
|
|
||||||
_queueTimer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Socket CreateMulticastSocket()
|
private Socket CreateMulticastSocket()
|
||||||
{
|
{
|
||||||
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||||
|
@ -534,14 +441,7 @@ namespace MediaBrowser.Dlna.Ssdp
|
||||||
|
|
||||||
public void RegisterNotification(Guid uuid, Uri descriptionUri, IPAddress address, IEnumerable<string> services)
|
public void RegisterNotification(Guid uuid, Uri descriptionUri, IPAddress address, IEnumerable<string> services)
|
||||||
{
|
{
|
||||||
List<UpnpDevice> list;
|
var list = _devices.GetOrAdd(uuid, new List<UpnpDevice>());
|
||||||
lock (_devices)
|
|
||||||
{
|
|
||||||
if (!_devices.TryGetValue(uuid, out list))
|
|
||||||
{
|
|
||||||
_devices.TryAdd(uuid, list = new List<UpnpDevice>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
list.AddRange(services.Select(i => new UpnpDevice(uuid, i, descriptionUri, address)));
|
list.AddRange(services.Select(i => new UpnpDevice(uuid, i, descriptionUri, address)));
|
||||||
|
|
||||||
|
@ -572,7 +472,7 @@ namespace MediaBrowser.Dlna.Ssdp
|
||||||
|
|
||||||
if (!config.BlastAliveMessages)
|
if (!config.BlastAliveMessages)
|
||||||
{
|
{
|
||||||
DisposeNotificationTimer();
|
StopAliveNotifier();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -599,7 +499,7 @@ namespace MediaBrowser.Dlna.Ssdp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisposeNotificationTimer()
|
private void StopAliveNotifier()
|
||||||
{
|
{
|
||||||
lock (_notificationTimerSyncLock)
|
lock (_notificationTimerSyncLock)
|
||||||
{
|
{
|
||||||
|
|
|
@ -292,16 +292,25 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the video codec is not some form of mpeg, then take a shortcut and limit this to containers that are likely to have interlaced content
|
|
||||||
if ((videoStream.Codec ?? string.Empty).IndexOf("mpeg", StringComparison.OrdinalIgnoreCase) == -1)
|
|
||||||
{
|
|
||||||
var formats = (video.Container ?? string.Empty).Split(',').ToList();
|
var formats = (video.Container ?? string.Empty).Split(',').ToList();
|
||||||
|
var enableInterlacedDection = formats.Contains("vob", StringComparer.OrdinalIgnoreCase) &&
|
||||||
|
formats.Contains("m2ts", StringComparer.OrdinalIgnoreCase) &&
|
||||||
|
formats.Contains("ts", StringComparer.OrdinalIgnoreCase) &&
|
||||||
|
formats.Contains("mpegts", StringComparer.OrdinalIgnoreCase) &&
|
||||||
|
formats.Contains("wtv", StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
if (!formats.Contains("vob", StringComparer.OrdinalIgnoreCase) &&
|
// If it's mpeg based, assume true
|
||||||
!formats.Contains("m2ts", StringComparer.OrdinalIgnoreCase) &&
|
if ((videoStream.Codec ?? string.Empty).IndexOf("mpeg", StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
!formats.Contains("ts", StringComparer.OrdinalIgnoreCase) &&
|
{
|
||||||
!formats.Contains("mpegts", StringComparer.OrdinalIgnoreCase) &&
|
if (enableInterlacedDection)
|
||||||
!formats.Contains("wtv", StringComparer.OrdinalIgnoreCase))
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If the video codec is not some form of mpeg, then take a shortcut and limit this to containers that are likely to have interlaced content
|
||||||
|
if (!enableInterlacedDection)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -164,7 +164,7 @@ namespace MediaBrowser.Model.Configuration
|
||||||
/// different directories and files.
|
/// different directories and files.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The file watcher delay.</value>
|
/// <value>The file watcher delay.</value>
|
||||||
public int RealtimeLibraryMonitorDelay { get; set; }
|
public int LibraryMonitorDelay { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether [enable dashboard response caching].
|
/// Gets or sets a value indicating whether [enable dashboard response caching].
|
||||||
|
@ -181,7 +181,6 @@ namespace MediaBrowser.Model.Configuration
|
||||||
public string DashboardSourcePath { get; set; }
|
public string DashboardSourcePath { get; set; }
|
||||||
|
|
||||||
public bool MergeMetadataAndImagesByName { get; set; }
|
public bool MergeMetadataAndImagesByName { get; set; }
|
||||||
public bool EnableStandaloneMetadata { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the image saving convention.
|
/// Gets or sets the image saving convention.
|
||||||
|
@ -256,7 +255,7 @@ namespace MediaBrowser.Model.Configuration
|
||||||
MinResumeDurationSeconds = 300;
|
MinResumeDurationSeconds = 300;
|
||||||
|
|
||||||
EnableLibraryMonitor = AutoOnOff.Auto;
|
EnableLibraryMonitor = AutoOnOff.Auto;
|
||||||
RealtimeLibraryMonitorDelay = 40;
|
LibraryMonitorDelay = 60;
|
||||||
|
|
||||||
EnableInternetProviders = true;
|
EnableInternetProviders = true;
|
||||||
FindInternetTrailers = true;
|
FindInternetTrailers = true;
|
||||||
|
|
|
@ -48,6 +48,7 @@ namespace MediaBrowser.Model.Configuration
|
||||||
public string[] PlainFolderViews { get; set; }
|
public string[] PlainFolderViews { get; set; }
|
||||||
|
|
||||||
public bool HidePlayedInLatest { get; set; }
|
public bool HidePlayedInLatest { get; set; }
|
||||||
|
public bool DisplayChannelsInline { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="UserConfiguration" /> class.
|
/// Initializes a new instance of the <see cref="UserConfiguration" /> class.
|
||||||
|
|
|
@ -213,7 +213,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
var chapters = mediaInfo.Chapters ?? new List<ChapterInfo>();
|
var chapters = mediaInfo.Chapters ?? new List<ChapterInfo>();
|
||||||
if (video.VideoType == VideoType.BluRay || (video.IsoType.HasValue && video.IsoType.Value == IsoType.BluRay))
|
if (blurayInfo != null)
|
||||||
{
|
{
|
||||||
FetchBdInfo(video, chapters, mediaStreams, blurayInfo);
|
FetchBdInfo(video, chapters, mediaStreams, blurayInfo);
|
||||||
}
|
}
|
||||||
|
@ -359,9 +359,17 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
/// <param name="path">The path.</param>
|
/// <param name="path">The path.</param>
|
||||||
/// <returns>VideoStream.</returns>
|
/// <returns>VideoStream.</returns>
|
||||||
private BlurayDiscInfo GetBDInfo(string path)
|
private BlurayDiscInfo GetBDInfo(string path)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
return _blurayExaminer.GetDiscInfo(path);
|
return _blurayExaminer.GetDiscInfo(path);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error getting BDInfo", ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void FetchEmbeddedInfo(Video video, Model.MediaInfo.MediaInfo data, MetadataRefreshOptions options)
|
private void FetchEmbeddedInfo(Video video, Model.MediaInfo.MediaInfo data, MetadataRefreshOptions options)
|
||||||
{
|
{
|
||||||
|
@ -628,7 +636,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
FetchFromDvdLib(item, mount);
|
FetchFromDvdLib(item, mount);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.VideoType == VideoType.BluRay || (item.IsoType.HasValue && item.IsoType.Value == IsoType.BluRay))
|
if (blurayDiscInfo != null)
|
||||||
{
|
{
|
||||||
item.PlayableStreamFileNames = blurayDiscInfo.Files.ToList();
|
item.PlayableStreamFileNames = blurayDiscInfo.Files.ToList();
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ namespace MediaBrowser.Providers.People
|
||||||
|
|
||||||
private int _requestCount;
|
private int _requestCount;
|
||||||
private readonly object _requestCountLock = new object();
|
private readonly object _requestCountLock = new object();
|
||||||
private Timer _requestCountReset;
|
private DateTime _lastRequestCountReset;
|
||||||
|
|
||||||
public MovieDbPersonProvider(IFileSystem fileSystem, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogger logger)
|
public MovieDbPersonProvider(IFileSystem fileSystem, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogger logger)
|
||||||
{
|
{
|
||||||
|
@ -48,16 +48,6 @@ namespace MediaBrowser.Providers.People
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
Current = this;
|
Current = this;
|
||||||
|
|
||||||
_requestCountReset = new Timer(OnRequestThrottleTimerFired, null, TimeSpan.FromHours(1), TimeSpan.FromHours(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnRequestThrottleTimerFired(object state)
|
|
||||||
{
|
|
||||||
lock (_requestCountLock)
|
|
||||||
{
|
|
||||||
_requestCount = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name
|
public string Name
|
||||||
|
@ -101,6 +91,12 @@ namespace MediaBrowser.Providers.People
|
||||||
{
|
{
|
||||||
lock (_requestCountLock)
|
lock (_requestCountLock)
|
||||||
{
|
{
|
||||||
|
if ((DateTime.UtcNow - _lastRequestCountReset).TotalHours >= 1)
|
||||||
|
{
|
||||||
|
_requestCount = 0;
|
||||||
|
_lastRequestCountReset = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
var requestCount = _requestCount;
|
var requestCount = _requestCount;
|
||||||
|
|
||||||
if (requestCount >= 5)
|
if (requestCount >= 5)
|
||||||
|
|
|
@ -238,7 +238,7 @@ namespace MediaBrowser.Providers.TV
|
||||||
throw new ArgumentNullException("seriesId");
|
throw new ArgumentNullException("seriesId");
|
||||||
}
|
}
|
||||||
|
|
||||||
var url = string.Format(SeriesGetZip, TVUtils.TvdbApiKey, seriesId, preferredMetadataLanguage);
|
var url = string.Format(SeriesGetZip, TVUtils.TvdbApiKey, seriesId, NormalizeLanguage(preferredMetadataLanguage));
|
||||||
|
|
||||||
using (var zipStream = await _httpClient.Get(new HttpRequestOptions
|
using (var zipStream = await _httpClient.Get(new HttpRequestOptions
|
||||||
{
|
{
|
||||||
|
@ -268,7 +268,7 @@ namespace MediaBrowser.Providers.TV
|
||||||
await SanitizeXmlFile(file).ConfigureAwait(false);
|
await SanitizeXmlFile(file).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
var downloadLangaugeXmlFile = Path.Combine(seriesDataPath, preferredMetadataLanguage + ".xml");
|
var downloadLangaugeXmlFile = Path.Combine(seriesDataPath, NormalizeLanguage(preferredMetadataLanguage) + ".xml");
|
||||||
var saveAsLanguageXmlFile = Path.Combine(seriesDataPath, saveAsMetadataLanguage + ".xml");
|
var saveAsLanguageXmlFile = Path.Combine(seriesDataPath, saveAsMetadataLanguage + ".xml");
|
||||||
|
|
||||||
if (!string.Equals(downloadLangaugeXmlFile, saveAsLanguageXmlFile, StringComparison.OrdinalIgnoreCase))
|
if (!string.Equals(downloadLangaugeXmlFile, saveAsLanguageXmlFile, StringComparison.OrdinalIgnoreCase))
|
||||||
|
|
|
@ -29,7 +29,7 @@ using CommonIO;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.Channels
|
namespace MediaBrowser.Server.Implementations.Channels
|
||||||
{
|
{
|
||||||
public class ChannelManager : IChannelManager, IDisposable
|
public class ChannelManager : IChannelManager
|
||||||
{
|
{
|
||||||
private IChannel[] _channels;
|
private IChannel[] _channels;
|
||||||
|
|
||||||
|
@ -47,11 +47,6 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||||
private readonly ILocalizationManager _localization;
|
private readonly ILocalizationManager _localization;
|
||||||
private readonly ConcurrentDictionary<Guid, bool> _refreshedItems = new ConcurrentDictionary<Guid, bool>();
|
private readonly ConcurrentDictionary<Guid, bool> _refreshedItems = new ConcurrentDictionary<Guid, bool>();
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, int> _downloadCounts = new ConcurrentDictionary<string, int>();
|
|
||||||
|
|
||||||
private Timer _refreshTimer;
|
|
||||||
private Timer _clearDownloadCountsTimer;
|
|
||||||
|
|
||||||
public ChannelManager(IUserManager userManager, IDtoService dtoService, ILibraryManager libraryManager, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem, IUserDataManager userDataManager, IJsonSerializer jsonSerializer, ILocalizationManager localization, IHttpClient httpClient, IProviderManager providerManager)
|
public ChannelManager(IUserManager userManager, IDtoService dtoService, ILibraryManager libraryManager, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem, IUserDataManager userDataManager, IJsonSerializer jsonSerializer, ILocalizationManager localization, IHttpClient httpClient, IProviderManager providerManager)
|
||||||
{
|
{
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
|
@ -65,9 +60,6 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||||
_localization = localization;
|
_localization = localization;
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
_providerManager = providerManager;
|
_providerManager = providerManager;
|
||||||
|
|
||||||
_refreshTimer = new Timer(s => _refreshedItems.Clear(), null, TimeSpan.FromHours(3), TimeSpan.FromHours(3));
|
|
||||||
_clearDownloadCountsTimer = new Timer(s => _downloadCounts.Clear(), null, TimeSpan.FromHours(24), TimeSpan.FromHours(24));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TimeSpan CacheLength
|
private TimeSpan CacheLength
|
||||||
|
@ -206,6 +198,8 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||||
|
|
||||||
public async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
|
public async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
_refreshedItems.Clear();
|
||||||
|
|
||||||
var allChannelsList = GetAllChannels().ToList();
|
var allChannelsList = GetAllChannels().ToList();
|
||||||
|
|
||||||
var numComplete = 0;
|
var numComplete = 0;
|
||||||
|
@ -1471,12 +1465,6 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||||
|
|
||||||
var limit = features.DailyDownloadLimit;
|
var limit = features.DailyDownloadLimit;
|
||||||
|
|
||||||
if (!ValidateDownloadLimit(host, limit))
|
|
||||||
{
|
|
||||||
_logger.Error(string.Format("Download limit has been reached for {0}", channel.Name));
|
|
||||||
throw new ChannelDownloadException(string.Format("Download limit has been reached for {0}", channel.Name));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var header in source.RequiredHttpHeaders)
|
foreach (var header in source.RequiredHttpHeaders)
|
||||||
{
|
{
|
||||||
options.RequestHeaders[header.Key] = header.Value;
|
options.RequestHeaders[header.Key] = header.Value;
|
||||||
|
@ -1495,8 +1483,6 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
IncrementDownloadCount(host, limit);
|
|
||||||
|
|
||||||
if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase) && response.ContentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase) && response.ContentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var extension = response.ContentType.Split('/')
|
var extension = response.ContentType.Split('/')
|
||||||
|
@ -1531,46 +1517,5 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void IncrementDownloadCount(string key, int? limit)
|
|
||||||
{
|
|
||||||
if (!limit.HasValue)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int current;
|
|
||||||
_downloadCounts.TryGetValue(key, out current);
|
|
||||||
|
|
||||||
current++;
|
|
||||||
_downloadCounts.AddOrUpdate(key, current, (k, v) => current);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool ValidateDownloadLimit(string key, int? limit)
|
|
||||||
{
|
|
||||||
if (!limit.HasValue)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int current;
|
|
||||||
_downloadCounts.TryGetValue(key, out current);
|
|
||||||
|
|
||||||
return current < limit.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (_clearDownloadCountsTimer != null)
|
|
||||||
{
|
|
||||||
_clearDownloadCountsTimer.Dispose();
|
|
||||||
_clearDownloadCountsTimer = null;
|
|
||||||
}
|
|
||||||
if (_refreshTimer != null)
|
|
||||||
{
|
|
||||||
_refreshTimer.Dispose();
|
|
||||||
_refreshTimer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,22 +121,14 @@ namespace MediaBrowser.Server.Implementations.Configuration
|
||||||
|
|
||||||
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = metadataPath;
|
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = metadataPath;
|
||||||
|
|
||||||
if (Configuration.MergeMetadataAndImagesByName)
|
|
||||||
{
|
|
||||||
((ServerApplicationPaths)ApplicationPaths).ItemsByNamePath = ((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath;
|
((ServerApplicationPaths)ApplicationPaths).ItemsByNamePath = ((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private string GetInternalMetadataPath()
|
private string GetInternalMetadataPath()
|
||||||
{
|
|
||||||
if (Configuration.EnableStandaloneMetadata)
|
|
||||||
{
|
{
|
||||||
return Path.Combine(ApplicationPaths.ProgramDataPath, "metadata");
|
return Path.Combine(ApplicationPaths.ProgramDataPath, "metadata");
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the transcoding temporary path.
|
/// Updates the transcoding temporary path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -13,12 +13,13 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommonIO;
|
using CommonIO;
|
||||||
using MediaBrowser.Common.IO;
|
using MediaBrowser.Common.IO;
|
||||||
|
using MediaBrowser.Common.Threading;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.Connect
|
namespace MediaBrowser.Server.Implementations.Connect
|
||||||
{
|
{
|
||||||
public class ConnectEntryPoint : IServerEntryPoint
|
public class ConnectEntryPoint : IServerEntryPoint
|
||||||
{
|
{
|
||||||
private Timer _timer;
|
private PeriodicTimer _timer;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
private readonly IApplicationPaths _appPaths;
|
private readonly IApplicationPaths _appPaths;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
@ -43,7 +44,7 @@ namespace MediaBrowser.Server.Implementations.Connect
|
||||||
{
|
{
|
||||||
Task.Run(() => LoadCachedAddress());
|
Task.Run(() => LoadCachedAddress());
|
||||||
|
|
||||||
_timer = new Timer(TimerCallback, null, TimeSpan.FromSeconds(5), TimeSpan.FromHours(3));
|
_timer = new PeriodicTimer(TimerCallback, null, TimeSpan.FromSeconds(5), TimeSpan.FromHours(3));
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly string[] _ipLookups = { "http://bot.whatismyipaddress.com", "https://connect.emby.media/service/ip" };
|
private readonly string[] _ipLookups = { "http://bot.whatismyipaddress.com", "https://connect.emby.media/service/ip" };
|
||||||
|
|
|
@ -25,7 +25,7 @@ namespace MediaBrowser.Server.Implementations.Devices
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
|
|
||||||
private ConcurrentBag<DeviceInfo> _devices;
|
private List<DeviceInfo> _devices;
|
||||||
|
|
||||||
public DeviceRepository(IApplicationPaths appPaths, IJsonSerializer json, ILogger logger, IFileSystem fileSystem)
|
public DeviceRepository(IApplicationPaths appPaths, IJsonSerializer json, ILogger logger, IFileSystem fileSystem)
|
||||||
{
|
{
|
||||||
|
@ -92,19 +92,16 @@ namespace MediaBrowser.Server.Implementations.Devices
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<DeviceInfo> GetDevices()
|
public IEnumerable<DeviceInfo> GetDevices()
|
||||||
{
|
|
||||||
if (_devices == null)
|
|
||||||
{
|
{
|
||||||
lock (_syncLock)
|
lock (_syncLock)
|
||||||
{
|
{
|
||||||
if (_devices == null)
|
if (_devices == null)
|
||||||
{
|
{
|
||||||
_devices = new ConcurrentBag<DeviceInfo>(LoadDevices());
|
_devices = LoadDevices().ToList();
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return _devices.ToList();
|
return _devices.ToList();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private IEnumerable<DeviceInfo> LoadDevices()
|
private IEnumerable<DeviceInfo> LoadDevices()
|
||||||
{
|
{
|
||||||
|
|
|
@ -11,6 +11,7 @@ using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using MediaBrowser.Common.Threading;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.EntryPoints
|
namespace MediaBrowser.Server.Implementations.EntryPoints
|
||||||
{
|
{
|
||||||
|
@ -21,7 +22,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly ISsdpHandler _ssdp;
|
private readonly ISsdpHandler _ssdp;
|
||||||
|
|
||||||
private Timer _timer;
|
private PeriodicTimer _timer;
|
||||||
private bool _isStarted;
|
private bool _isStarted;
|
||||||
|
|
||||||
public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, ISsdpHandler ssdp)
|
public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, ISsdpHandler ssdp)
|
||||||
|
@ -95,7 +96,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
|
||||||
NatUtility.UnhandledException += NatUtility_UnhandledException;
|
NatUtility.UnhandledException += NatUtility_UnhandledException;
|
||||||
NatUtility.StartDiscovery();
|
NatUtility.StartDiscovery();
|
||||||
|
|
||||||
_timer = new Timer(s => _createdRules = new List<string>(), null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
|
_timer = new PeriodicTimer(s => _createdRules = new List<string>(), null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
|
||||||
|
|
||||||
_ssdp.MessageReceived += _ssdp_MessageReceived;
|
_ssdp.MessageReceived += _ssdp_MessageReceived;
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ using MediaBrowser.Model.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Common.Threading;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.EntryPoints
|
namespace MediaBrowser.Server.Implementations.EntryPoints
|
||||||
{
|
{
|
||||||
|
@ -22,7 +23,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
private Timer _timer;
|
private PeriodicTimer _timer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="LoadRegistrations" /> class.
|
/// Initializes a new instance of the <see cref="LoadRegistrations" /> class.
|
||||||
|
@ -41,7 +42,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Run()
|
public void Run()
|
||||||
{
|
{
|
||||||
_timer = new Timer(s => LoadAllRegistrations(), null, TimeSpan.FromMilliseconds(100), TimeSpan.FromHours(12));
|
_timer = new PeriodicTimer(s => LoadAllRegistrations(), null, TimeSpan.FromMilliseconds(100), TimeSpan.FromHours(12));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadAllRegistrations()
|
private async Task LoadAllRegistrations()
|
||||||
|
|
|
@ -9,6 +9,7 @@ using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.EntryPoints
|
namespace MediaBrowser.Server.Implementations.EntryPoints
|
||||||
{
|
{
|
||||||
|
@ -23,7 +24,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
|
||||||
private readonly ISessionManager _sessionManager;
|
private readonly ISessionManager _sessionManager;
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
|
|
||||||
private Timer _timer;
|
|
||||||
private readonly TimeSpan _frequency = TimeSpan.FromHours(24);
|
private readonly TimeSpan _frequency = TimeSpan.FromHours(24);
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<Guid, ClientInfo> _apps = new ConcurrentDictionary<Guid, ClientInfo>();
|
private readonly ConcurrentDictionary<Guid, ClientInfo> _apps = new ConcurrentDictionary<Guid, ClientInfo>();
|
||||||
|
@ -95,16 +95,16 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Run()
|
public async void Run()
|
||||||
{
|
{
|
||||||
_timer = new Timer(OnTimerFired, null, TimeSpan.FromMilliseconds(5000), _frequency);
|
await Task.Delay(5000).ConfigureAwait(false);
|
||||||
|
OnTimerFired();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when [timer fired].
|
/// Called when [timer fired].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state">The state.</param>
|
private async void OnTimerFired()
|
||||||
private async void OnTimerFired(object state)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -121,12 +121,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_sessionManager.SessionStarted -= _sessionManager_SessionStarted;
|
_sessionManager.SessionStarted -= _sessionManager_SessionStarted;
|
||||||
|
|
||||||
if (_timer != null)
|
|
||||||
{
|
|
||||||
_timer.Dispose();
|
|
||||||
_timer = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -462,7 +462,9 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
|
||||||
/// <param name="seasonNumber">The season number.</param>
|
/// <param name="seasonNumber">The season number.</param>
|
||||||
/// <param name="episodeNumber">The episode number.</param>
|
/// <param name="episodeNumber">The episode number.</param>
|
||||||
/// <param name="endingEpisodeNumber">The ending episode number.</param>
|
/// <param name="endingEpisodeNumber">The ending episode number.</param>
|
||||||
|
/// <param name="premiereDate">The premiere date.</param>
|
||||||
/// <param name="options">The options.</param>
|
/// <param name="options">The options.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
private async Task<string> GetNewPath(string sourcePath,
|
private async Task<string> GetNewPath(string sourcePath,
|
||||||
Series series,
|
Series series,
|
||||||
|
@ -492,19 +494,22 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
|
||||||
|
|
||||||
var episode = searchResults.FirstOrDefault();
|
var episode = searchResults.FirstOrDefault();
|
||||||
|
|
||||||
string episodeName = string.Empty;
|
|
||||||
|
|
||||||
if (episode == null)
|
if (episode == null)
|
||||||
{
|
{
|
||||||
var msg = string.Format("No provider metadata found for {0} season {1} episode {2}", series.Name, seasonNumber, episodeNumber);
|
var msg = string.Format("No provider metadata found for {0} season {1} episode {2}", series.Name, seasonNumber, episodeNumber);
|
||||||
_logger.Warn(msg);
|
_logger.Warn(msg);
|
||||||
//throw new Exception(msg);
|
return null;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
episodeName = episode.Name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var episodeName = episode.Name;
|
||||||
|
|
||||||
|
//if (string.IsNullOrWhiteSpace(episodeName))
|
||||||
|
//{
|
||||||
|
// var msg = string.Format("No provider metadata found for {0} season {1} episode {2}", series.Name, seasonNumber, episodeNumber);
|
||||||
|
// _logger.Warn(msg);
|
||||||
|
// return null;
|
||||||
|
//}
|
||||||
|
|
||||||
seasonNumber = seasonNumber ?? episode.ParentIndexNumber;
|
seasonNumber = seasonNumber ?? episode.ParentIndexNumber;
|
||||||
episodeNumber = episodeNumber ?? episode.IndexNumber;
|
episodeNumber = episodeNumber ?? episode.IndexNumber;
|
||||||
|
|
||||||
|
@ -579,7 +584,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
|
||||||
{
|
{
|
||||||
seriesName = _fileSystem.GetValidFilename(seriesName).Trim();
|
seriesName = _fileSystem.GetValidFilename(seriesName).Trim();
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(episodeTitle))
|
if (string.IsNullOrWhiteSpace(episodeTitle))
|
||||||
{
|
{
|
||||||
episodeTitle = string.Empty;
|
episodeTitle = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
|
@ -256,6 +256,25 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly Dictionary<string, int> _skipLogExtensions = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
{".js", 0},
|
||||||
|
{".css", 0},
|
||||||
|
{".woff", 0},
|
||||||
|
{".woff2", 0},
|
||||||
|
{".ttf", 0},
|
||||||
|
{".html", 0}
|
||||||
|
};
|
||||||
|
|
||||||
|
private bool EnableLogging(string url)
|
||||||
|
{
|
||||||
|
var parts = url.Split(new[] { '?' }, 2);
|
||||||
|
|
||||||
|
var extension = Path.GetExtension(parts[0]);
|
||||||
|
|
||||||
|
return string.IsNullOrWhiteSpace(extension) || !_skipLogExtensions.ContainsKey(extension);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Overridable method that can be used to implement a custom hnandler
|
/// Overridable method that can be used to implement a custom hnandler
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -271,6 +290,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
var operationName = httpReq.OperationName;
|
var operationName = httpReq.OperationName;
|
||||||
var localPath = url.LocalPath;
|
var localPath = url.LocalPath;
|
||||||
|
|
||||||
|
var urlString = url.OriginalString;
|
||||||
|
var enableLog = EnableLogging(urlString);
|
||||||
|
|
||||||
|
if (enableLog)
|
||||||
|
{
|
||||||
|
LoggerUtils.LogRequest(_logger, urlString, httpReq.HttpMethod, httpReq.UserAgent);
|
||||||
|
}
|
||||||
|
|
||||||
if (string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase) ||
|
if (string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase))
|
string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
@ -333,15 +360,16 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
task.ContinueWith(x => httpRes.Close(), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent);
|
task.ContinueWith(x => httpRes.Close(), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent);
|
||||||
//Matches Exceptions handled in HttpListenerBase.InitTask()
|
//Matches Exceptions handled in HttpListenerBase.InitTask()
|
||||||
|
|
||||||
var urlString = url.ToString();
|
|
||||||
|
|
||||||
task.ContinueWith(x =>
|
task.ContinueWith(x =>
|
||||||
{
|
{
|
||||||
var statusCode = httpRes.StatusCode;
|
var statusCode = httpRes.StatusCode;
|
||||||
|
|
||||||
var duration = DateTime.Now - date;
|
var duration = DateTime.Now - date;
|
||||||
|
|
||||||
|
if (enableLog)
|
||||||
|
{
|
||||||
LoggerUtils.LogResponse(_logger, statusCode, urlString, remoteIp, duration);
|
LoggerUtils.LogResponse(_logger, statusCode, urlString, remoteIp, duration);
|
||||||
|
}
|
||||||
|
|
||||||
}, TaskContinuationOptions.None);
|
}, TaskContinuationOptions.None);
|
||||||
return task;
|
return task;
|
||||||
|
|
|
@ -1,11 +1,30 @@
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using SocketHttpListener.Net;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.HttpServer
|
namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
{
|
{
|
||||||
public static class LoggerUtils
|
public static class LoggerUtils
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Logs the request.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger">The logger.</param>
|
||||||
|
/// <param name="request">The request.</param>
|
||||||
|
public static void LogRequest(ILogger logger, HttpListenerRequest request)
|
||||||
|
{
|
||||||
|
var url = request.Url.ToString();
|
||||||
|
|
||||||
|
logger.Info("{0} {1}. UserAgent: {2}", (request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod), url, request.UserAgent ?? string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LogRequest(ILogger logger, string url, string method, string userAgent)
|
||||||
|
{
|
||||||
|
logger.Info("{0} {1}. UserAgent: {2}", ("HTTP " + method), url, userAgent ?? string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Logs the response.
|
/// Logs the response.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -78,10 +78,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
|
||||||
{
|
{
|
||||||
var request = context.Request;
|
var request = context.Request;
|
||||||
|
|
||||||
LogRequest(_logger, request);
|
|
||||||
|
|
||||||
if (request.IsWebSocketRequest)
|
if (request.IsWebSocketRequest)
|
||||||
{
|
{
|
||||||
|
LoggerUtils.LogRequest(_logger, request);
|
||||||
|
|
||||||
ProcessWebSocketRequest(context);
|
ProcessWebSocketRequest(context);
|
||||||
return Task.FromResult(true);
|
return Task.FromResult(true);
|
||||||
}
|
}
|
||||||
|
@ -156,44 +156,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Logs the request.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="logger">The logger.</param>
|
|
||||||
/// <param name="request">The request.</param>
|
|
||||||
private static void LogRequest(ILogger logger, HttpListenerRequest request)
|
|
||||||
{
|
|
||||||
var url = request.Url.ToString();
|
|
||||||
var extension = Path.GetExtension(url);
|
|
||||||
|
|
||||||
if (string.Equals(extension, ".js", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (string.Equals(extension, ".css", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (string.Equals(extension, ".woff", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (string.Equals(extension, ".woff2", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (string.Equals(extension, ".ttf", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (string.Equals(extension, ".html", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("{0} {1}. UserAgent: {2}", (request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod), url, request.UserAgent ?? string.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleError(Exception ex, HttpListenerContext context)
|
private void HandleError(Exception ex, HttpListenerContext context)
|
||||||
{
|
{
|
||||||
var httpReq = GetRequest(context);
|
var httpReq = GetRequest(context);
|
||||||
|
|
|
@ -471,11 +471,11 @@ namespace MediaBrowser.Server.Implementations.IO
|
||||||
{
|
{
|
||||||
if (_updateTimer == null)
|
if (_updateTimer == null)
|
||||||
{
|
{
|
||||||
_updateTimer = new Timer(TimerStopped, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.RealtimeLibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
|
_updateTimer = new Timer(TimerStopped, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.RealtimeLibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
|
_updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -513,12 +513,18 @@ namespace MediaBrowser.Server.Implementations.IO
|
||||||
|
|
||||||
private bool IsFileLocked(string path)
|
private bool IsFileLocked(string path)
|
||||||
{
|
{
|
||||||
|
if (Environment.OSVersion.Platform != PlatformID.Win32NT)
|
||||||
|
{
|
||||||
|
// Causing lockups on linux
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var data = _fileSystem.GetFileSystemInfo(path);
|
var data = _fileSystem.GetFileSystemInfo(path);
|
||||||
|
|
||||||
if (!data.Exists
|
if (!data.Exists
|
||||||
|| data.Attributes.HasFlag(FileAttributes.Directory)
|
|| data.IsDirectory
|
||||||
|
|
||||||
// Opening a writable stream will fail with readonly files
|
// Opening a writable stream will fail with readonly files
|
||||||
|| data.Attributes.HasFlag(FileAttributes.ReadOnly))
|
|| data.Attributes.HasFlag(FileAttributes.ReadOnly))
|
||||||
|
|
|
@ -222,7 +222,7 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _root folder
|
/// The _root folder
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private AggregateFolder _rootFolder;
|
private volatile AggregateFolder _rootFolder;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _root folder sync lock
|
/// The _root folder sync lock
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -743,7 +743,7 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
return rootFolder;
|
return rootFolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserRootFolder _userRootFolder;
|
private volatile UserRootFolder _userRootFolder;
|
||||||
private readonly object _syncLock = new object();
|
private readonly object _syncLock = new object();
|
||||||
public Folder GetUserRootFolder()
|
public Folder GetUserRootFolder()
|
||||||
{
|
{
|
||||||
|
|
|
@ -51,8 +51,17 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
|
||||||
{
|
{
|
||||||
var filename = Path.GetFileNameWithoutExtension(path) ?? string.Empty;
|
var filename = Path.GetFileNameWithoutExtension(path) ?? string.Empty;
|
||||||
|
|
||||||
return !IgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase)
|
if (IgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase))
|
||||||
&& imageProcessor.SupportedInputFormats.Contains((Path.GetExtension(path) ?? string.Empty).TrimStart('.'), StringComparer.OrdinalIgnoreCase);
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IgnoreFiles.Any(i => filename.IndexOf("-" + i, StringComparison.OrdinalIgnoreCase) != -1))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return imageProcessor.SupportedInputFormats.Contains((Path.GetExtension(path) ?? string.Empty).TrimStart('.'), StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -163,7 +163,14 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
|
|
||||||
var channels = channelResult.Items;
|
var channels = channelResult.Items;
|
||||||
|
|
||||||
|
if (user.Configuration.DisplayChannelsInline && channels.Length > 0)
|
||||||
|
{
|
||||||
|
list.Add(await _channelManager.GetInternalChannelFolder(cancellationToken).ConfigureAwait(false));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
list.AddRange(channels);
|
list.AddRange(channels);
|
||||||
|
}
|
||||||
|
|
||||||
if (_liveTvManager.GetEnabledUsers().Select(i => i.Id.ToString("N")).Contains(query.UserId))
|
if (_liveTvManager.GetEnabledUsers().Select(i => i.Id.ToString("N")).Contains(query.UserId))
|
||||||
{
|
{
|
||||||
|
|
|
@ -22,12 +22,15 @@ using MediaBrowser.Server.Implementations.FileOrganization;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommonIO;
|
using CommonIO;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
|
using MediaBrowser.Controller.Power;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
||||||
{
|
{
|
||||||
|
@ -55,7 +58,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
||||||
|
|
||||||
public static EmbyTV Current;
|
public static EmbyTV Current;
|
||||||
|
|
||||||
public EmbyTV(IApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ISecurityManager security, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IFileOrganizationService organizationService, IMediaEncoder mediaEncoder)
|
public EmbyTV(IApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ISecurityManager security, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IFileOrganizationService organizationService, IMediaEncoder mediaEncoder, IPowerManagement powerManagement)
|
||||||
{
|
{
|
||||||
Current = this;
|
Current = this;
|
||||||
|
|
||||||
|
@ -75,13 +78,26 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
||||||
|
|
||||||
_recordingProvider = new ItemDataProvider<RecordingInfo>(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "recordings"), (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase));
|
_recordingProvider = new ItemDataProvider<RecordingInfo>(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "recordings"), (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase));
|
||||||
_seriesTimerProvider = new SeriesTimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers"));
|
_seriesTimerProvider = new SeriesTimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers"));
|
||||||
_timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"));
|
_timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), powerManagement, _logger);
|
||||||
_timerProvider.TimerFired += _timerProvider_TimerFired;
|
_timerProvider.TimerFired += _timerProvider_TimerFired;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
_timerProvider.RestartTimers();
|
_timerProvider.RestartTimers();
|
||||||
|
|
||||||
|
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
|
||||||
|
{
|
||||||
|
_logger.Info("Power mode changed to {0}", e.Mode);
|
||||||
|
|
||||||
|
if (e.Mode == PowerModes.Resume)
|
||||||
|
{
|
||||||
|
_timerProvider.RestartTimers();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public event EventHandler DataSourceChanged;
|
public event EventHandler DataSourceChanged;
|
||||||
|
@ -155,7 +171,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
||||||
{
|
{
|
||||||
epgData = GetEpgDataForChannel(timer.ChannelId);
|
epgData = GetEpgDataForChannel(timer.ChannelId);
|
||||||
}
|
}
|
||||||
await UpdateTimersForSeriesTimer(epgData, timer).ConfigureAwait(false);
|
await UpdateTimersForSeriesTimer(epgData, timer, false).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
var timers = await GetTimersAsync(cancellationToken).ConfigureAwait(false);
|
var timers = await GetTimersAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
@ -223,7 +239,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
||||||
|
|
||||||
public Task CancelSeriesTimerAsync(string timerId, CancellationToken cancellationToken)
|
public Task CancelSeriesTimerAsync(string timerId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var timers = _timerProvider.GetAll().Where(i => string.Equals(i.SeriesTimerId, timerId, StringComparison.OrdinalIgnoreCase));
|
var timers = _timerProvider
|
||||||
|
.GetAll()
|
||||||
|
.Where(i => string.Equals(i.SeriesTimerId, timerId, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
foreach (var timer in timers)
|
foreach (var timer in timers)
|
||||||
{
|
{
|
||||||
CancelTimerInternal(timer.Id);
|
CancelTimerInternal(timer.Id);
|
||||||
|
@ -332,14 +352,32 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
||||||
}
|
}
|
||||||
|
|
||||||
_seriesTimerProvider.Add(info);
|
_seriesTimerProvider.Add(info);
|
||||||
await UpdateTimersForSeriesTimer(epgData, info).ConfigureAwait(false);
|
await UpdateTimersForSeriesTimer(epgData, info, false).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
|
public async Task UpdateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_seriesTimerProvider.Update(info);
|
var instance = _seriesTimerProvider.GetAll().FirstOrDefault(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
if (instance != null)
|
||||||
|
{
|
||||||
|
instance.ChannelId = info.ChannelId;
|
||||||
|
instance.Days = info.Days;
|
||||||
|
instance.EndDate = info.EndDate;
|
||||||
|
instance.IsPostPaddingRequired = info.IsPostPaddingRequired;
|
||||||
|
instance.IsPrePaddingRequired = info.IsPrePaddingRequired;
|
||||||
|
instance.PostPaddingSeconds = info.PostPaddingSeconds;
|
||||||
|
instance.PrePaddingSeconds = info.PrePaddingSeconds;
|
||||||
|
instance.Priority = info.Priority;
|
||||||
|
instance.RecordAnyChannel = info.RecordAnyChannel;
|
||||||
|
instance.RecordAnyTime = info.RecordAnyTime;
|
||||||
|
instance.RecordNewOnly = info.RecordNewOnly;
|
||||||
|
instance.StartDate = info.StartDate;
|
||||||
|
|
||||||
|
_seriesTimerProvider.Update(instance);
|
||||||
|
|
||||||
List<ProgramInfo> epgData;
|
List<ProgramInfo> epgData;
|
||||||
if (info.RecordAnyChannel)
|
if (instance.RecordAnyChannel)
|
||||||
{
|
{
|
||||||
var channels = await GetChannelsAsync(true, CancellationToken.None).ConfigureAwait(false);
|
var channels = await GetChannelsAsync(true, CancellationToken.None).ConfigureAwait(false);
|
||||||
var channelIds = channels.Select(i => i.Id).ToList();
|
var channelIds = channels.Select(i => i.Id).ToList();
|
||||||
|
@ -347,10 +385,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
epgData = GetEpgDataForChannel(info.ChannelId);
|
epgData = GetEpgDataForChannel(instance.ChannelId);
|
||||||
}
|
}
|
||||||
|
|
||||||
await UpdateTimersForSeriesTimer(epgData, info).ConfigureAwait(false);
|
await UpdateTimersForSeriesTimer(epgData, instance, true).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task UpdateTimerAsync(TimerInfo info, CancellationToken cancellationToken)
|
public Task UpdateTimerAsync(TimerInfo info, CancellationToken cancellationToken)
|
||||||
|
@ -603,6 +642,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
||||||
{
|
{
|
||||||
await RecordStream(timer, recordingEndDate, cancellationTokenSource.Token).ConfigureAwait(false);
|
await RecordStream(timer, recordingEndDate, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Info("Skipping RecordStream because it's already in progress.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
|
@ -721,7 +764,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
||||||
recording.DateLastUpdated = DateTime.UtcNow;
|
recording.DateLastUpdated = DateTime.UtcNow;
|
||||||
_recordingProvider.AddOrUpdate(recording);
|
_recordingProvider.AddOrUpdate(recording);
|
||||||
|
|
||||||
_logger.Info("Beginning recording.");
|
_logger.Info("Beginning recording. Will record for {0} minutes.", duration.TotalMinutes.ToString(CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
httpRequestOptions.BufferContent = false;
|
httpRequestOptions.BufferContent = false;
|
||||||
var durationToken = new CancellationTokenSource(duration);
|
var durationToken = new CancellationTokenSource(duration);
|
||||||
|
@ -836,7 +879,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
||||||
return _config.GetConfiguration<LiveTvOptions>("livetv");
|
return _config.GetConfiguration<LiveTvOptions>("livetv");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateTimersForSeriesTimer(List<ProgramInfo> epgData, SeriesTimerInfo seriesTimer)
|
private async Task UpdateTimersForSeriesTimer(List<ProgramInfo> epgData, SeriesTimerInfo seriesTimer, bool deleteInvalidTimers)
|
||||||
{
|
{
|
||||||
var newTimers = GetTimersForSeries(seriesTimer, epgData, _recordingProvider.GetAll()).ToList();
|
var newTimers = GetTimersForSeries(seriesTimer, epgData, _recordingProvider.GetAll()).ToList();
|
||||||
|
|
||||||
|
@ -849,12 +892,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
||||||
_timerProvider.AddOrUpdate(timer);
|
_timerProvider.AddOrUpdate(timer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (deleteInvalidTimers)
|
||||||
|
{
|
||||||
|
var allTimers = GetTimersForSeries(seriesTimer, epgData, new List<RecordingInfo>())
|
||||||
|
.Select(i => i.Id)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var deletes = _timerProvider.GetAll()
|
||||||
|
.Where(i => string.Equals(i.SeriesTimerId, seriesTimer.Id, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.Where(i => !allTimers.Contains(i.Id, StringComparer.OrdinalIgnoreCase) && i.StartDate > DateTime.UtcNow)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var timer in deletes)
|
||||||
|
{
|
||||||
|
await CancelTimerAsync(timer.Id, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<TimerInfo> GetTimersForSeries(SeriesTimerInfo seriesTimer, IEnumerable<ProgramInfo> allPrograms, IReadOnlyList<RecordingInfo> currentRecordings)
|
private IEnumerable<TimerInfo> GetTimersForSeries(SeriesTimerInfo seriesTimer, IEnumerable<ProgramInfo> allPrograms, IReadOnlyList<RecordingInfo> currentRecordings)
|
||||||
{
|
{
|
||||||
// Exclude programs that have already ended
|
// Exclude programs that have already ended
|
||||||
allPrograms = allPrograms.Where(i => i.EndDate > DateTime.UtcNow);
|
allPrograms = allPrograms.Where(i => i.EndDate > DateTime.UtcNow && i.StartDate > DateTime.UtcNow);
|
||||||
|
|
||||||
allPrograms = GetProgramsForSeries(seriesTimer, allPrograms);
|
allPrograms = GetProgramsForSeries(seriesTimer, allPrograms);
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
||||||
where T : class
|
where T : class
|
||||||
{
|
{
|
||||||
private readonly object _fileDataLock = new object();
|
private readonly object _fileDataLock = new object();
|
||||||
private List<T> _items;
|
private volatile List<T> _items;
|
||||||
private readonly IJsonSerializer _jsonSerializer;
|
private readonly IJsonSerializer _jsonSerializer;
|
||||||
protected readonly ILogger Logger;
|
protected readonly ILogger Logger;
|
||||||
private readonly string _dataPath;
|
private readonly string _dataPath;
|
||||||
|
|
|
@ -5,22 +5,27 @@ using MediaBrowser.Model.Logging;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using CommonIO;
|
using CommonIO;
|
||||||
using MediaBrowser.Common.IO;
|
using MediaBrowser.Controller.Power;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
||||||
{
|
{
|
||||||
public class TimerManager : ItemDataProvider<TimerInfo>
|
public class TimerManager : ItemDataProvider<TimerInfo>
|
||||||
{
|
{
|
||||||
private readonly ConcurrentDictionary<string, Timer> _timers = new ConcurrentDictionary<string, Timer>(StringComparer.OrdinalIgnoreCase);
|
private readonly ConcurrentDictionary<string, Timer> _timers = new ConcurrentDictionary<string, Timer>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
private readonly IPowerManagement _powerManagement;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public event EventHandler<GenericEventArgs<TimerInfo>> TimerFired;
|
public event EventHandler<GenericEventArgs<TimerInfo>> TimerFired;
|
||||||
|
|
||||||
public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath)
|
public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, IPowerManagement powerManagement, ILogger logger1)
|
||||||
: base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
|
: base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
_powerManagement = powerManagement;
|
||||||
|
_logger = logger1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RestartTimers()
|
public void RestartTimers()
|
||||||
|
@ -58,6 +63,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
||||||
{
|
{
|
||||||
var timespan = RecordingHelper.GetStartTime(item) - DateTime.UtcNow;
|
var timespan = RecordingHelper.GetStartTime(item) - DateTime.UtcNow;
|
||||||
timer.Change(timespan, TimeSpan.Zero);
|
timer.Change(timespan, TimeSpan.Zero);
|
||||||
|
ScheduleWake(item);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -74,6 +80,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
||||||
|
|
||||||
base.Add(item);
|
base.Add(item);
|
||||||
AddTimer(item);
|
AddTimer(item);
|
||||||
|
ScheduleWake(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddTimer(TimerInfo item)
|
private void AddTimer(TimerInfo item)
|
||||||
|
@ -91,15 +98,39 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
||||||
StartTimer(item, timerLength);
|
StartTimer(item, timerLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ScheduleWake(TimerInfo info)
|
||||||
|
{
|
||||||
|
var startDate = RecordingHelper.GetStartTime(info).AddMinutes(-5);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_powerManagement.ScheduleWake(startDate);
|
||||||
|
_logger.Info("Scheduled system wake timer at {0} (UTC)", startDate);
|
||||||
|
}
|
||||||
|
catch (NotImplementedException)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error scheduling wake timer", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void StartTimer(TimerInfo item, TimeSpan length)
|
public void StartTimer(TimerInfo item, TimeSpan length)
|
||||||
{
|
{
|
||||||
StopTimer(item);
|
StopTimer(item);
|
||||||
|
|
||||||
var timer = new Timer(TimerCallback, item.Id, length, TimeSpan.Zero);
|
var timer = new Timer(TimerCallback, item.Id, length, TimeSpan.Zero);
|
||||||
|
|
||||||
if (!_timers.TryAdd(item.Id, timer))
|
if (_timers.TryAdd(item.Id, timer))
|
||||||
|
{
|
||||||
|
_logger.Info("Creating recording timer for {0}, {1}. Timer will fire in {2} minutes", item.Id, item.Name, length.TotalMinutes.ToString(CultureInfo.InvariantCulture));
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
timer.Dispose();
|
timer.Dispose();
|
||||||
|
_logger.Warn("Timer already exists for item {0}", item.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1954,6 +1954,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||||
|
|
||||||
await service.CreateTimerAsync(info, cancellationToken).ConfigureAwait(false);
|
await service.CreateTimerAsync(info, cancellationToken).ConfigureAwait(false);
|
||||||
_lastRecordingRefreshTime = DateTime.MinValue;
|
_lastRecordingRefreshTime = DateTime.MinValue;
|
||||||
|
_logger.Info("New recording scheduled");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
|
public async Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"AppDeviceValues": "App: {0}, Device: {1}",
|
"AppDeviceValues": "App: {0}, Dispositiu: {1}",
|
||||||
"UserDownloadingItemWithValues": "{0} is downloading {1}",
|
"UserDownloadingItemWithValues": "{0} est\u00e0 descarregant {1}",
|
||||||
"FolderTypeMixed": "Mixed content",
|
"FolderTypeMixed": "Mixed content",
|
||||||
"FolderTypeMovies": "Pel\u00b7l\u00edcules",
|
"FolderTypeMovies": "Pel\u00b7l\u00edcules",
|
||||||
"FolderTypeMusic": "M\u00fasica",
|
"FolderTypeMusic": "M\u00fasica",
|
||||||
|
@ -12,10 +12,10 @@
|
||||||
"FolderTypeBooks": "Llibres",
|
"FolderTypeBooks": "Llibres",
|
||||||
"FolderTypeTvShows": "TV",
|
"FolderTypeTvShows": "TV",
|
||||||
"FolderTypeInherit": "Heretat",
|
"FolderTypeInherit": "Heretat",
|
||||||
"HeaderCastCrew": "Repartiment i equip",
|
"HeaderCastCrew": "Repartiment i Equip",
|
||||||
"HeaderPeople": "People",
|
"HeaderPeople": "People",
|
||||||
"ValueSpecialEpisodeName": "Special - {0}",
|
"ValueSpecialEpisodeName": "Special - {0}",
|
||||||
"LabelChapterName": "Chapter {0}",
|
"LabelChapterName": "Cap\u00edtol {0}",
|
||||||
"NameSeasonNumber": "Temporada {0}",
|
"NameSeasonNumber": "Temporada {0}",
|
||||||
"LabelExit": "Sortir",
|
"LabelExit": "Sortir",
|
||||||
"LabelVisitCommunity": "Visita la comunitat",
|
"LabelVisitCommunity": "Visita la comunitat",
|
||||||
|
@ -77,7 +77,7 @@
|
||||||
"ViewTypeMovieMovies": "Pel\u00b7l\u00edcules",
|
"ViewTypeMovieMovies": "Pel\u00b7l\u00edcules",
|
||||||
"ViewTypeMovieCollections": "Col\u00b7leccions",
|
"ViewTypeMovieCollections": "Col\u00b7leccions",
|
||||||
"ViewTypeMovieFavorites": "Preferides",
|
"ViewTypeMovieFavorites": "Preferides",
|
||||||
"ViewTypeMovieGenres": "Genres",
|
"ViewTypeMovieGenres": "G\u00e8neres",
|
||||||
"ViewTypeMusicLatest": "Novetats",
|
"ViewTypeMusicLatest": "Novetats",
|
||||||
"ViewTypeMusicPlaylists": "Llistes de reproducci\u00f3",
|
"ViewTypeMusicPlaylists": "Llistes de reproducci\u00f3",
|
||||||
"ViewTypeMusicAlbums": "\u00c0lbums",
|
"ViewTypeMusicAlbums": "\u00c0lbums",
|
||||||
|
@ -89,7 +89,7 @@
|
||||||
"ViewTypeMusicFavoriteArtists": "Artistes Preferits",
|
"ViewTypeMusicFavoriteArtists": "Artistes Preferits",
|
||||||
"ViewTypeMusicFavoriteSongs": "Can\u00e7ons Preferides",
|
"ViewTypeMusicFavoriteSongs": "Can\u00e7ons Preferides",
|
||||||
"ViewTypeFolders": "Directoris",
|
"ViewTypeFolders": "Directoris",
|
||||||
"ViewTypeLiveTvRecordingGroups": "Recordings",
|
"ViewTypeLiveTvRecordingGroups": "Enregistraments",
|
||||||
"ViewTypeLiveTvChannels": "Canals",
|
"ViewTypeLiveTvChannels": "Canals",
|
||||||
"ScheduledTaskFailedWithName": "{0} failed",
|
"ScheduledTaskFailedWithName": "{0} failed",
|
||||||
"LabelRunningTimeValue": "Running time: {0}",
|
"LabelRunningTimeValue": "Running time: {0}",
|
||||||
|
@ -103,7 +103,7 @@
|
||||||
"LabelIpAddressValue": "Ip address: {0}",
|
"LabelIpAddressValue": "Ip address: {0}",
|
||||||
"DeviceOnlineWithName": "{0} is connected",
|
"DeviceOnlineWithName": "{0} is connected",
|
||||||
"UserOnlineFromDevice": "{0} is online from {1}",
|
"UserOnlineFromDevice": "{0} is online from {1}",
|
||||||
"ProviderValue": "Provider: {0}",
|
"ProviderValue": "Prove\u00efdor: {0}",
|
||||||
"SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
|
"SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
|
||||||
"UserConfigurationUpdatedWithName": "User configuration has been updated for {0}",
|
"UserConfigurationUpdatedWithName": "User configuration has been updated for {0}",
|
||||||
"UserCreatedWithName": "User {0} has been created",
|
"UserCreatedWithName": "User {0} has been created",
|
||||||
|
@ -113,12 +113,12 @@
|
||||||
"MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
|
"MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
|
||||||
"MessageApplicationUpdated": "Emby Server has been updated",
|
"MessageApplicationUpdated": "Emby Server has been updated",
|
||||||
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
|
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
|
||||||
"AuthenticationSucceededWithUserName": "{0} successfully authenticated",
|
"AuthenticationSucceededWithUserName": "{0} autenticat correctament",
|
||||||
"DeviceOfflineWithName": "{0} has disconnected",
|
"DeviceOfflineWithName": "{0} has disconnected",
|
||||||
"UserLockedOutWithName": "User {0} has been locked out",
|
"UserLockedOutWithName": "User {0} has been locked out",
|
||||||
"UserOfflineFromDevice": "{0} has disconnected from {1}",
|
"UserOfflineFromDevice": "{0} has disconnected from {1}",
|
||||||
"UserStartedPlayingItemWithValues": "{0} has started playing {1}",
|
"UserStartedPlayingItemWithValues": "{0} ha comen\u00e7at a reproduir {1}",
|
||||||
"UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}",
|
"UserStoppedPlayingItemWithValues": "{0} ha parat de reproduir {1}",
|
||||||
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
||||||
"HeaderUnidentified": "Unidentified",
|
"HeaderUnidentified": "Unidentified",
|
||||||
"HeaderImagePrimary": "Primary",
|
"HeaderImagePrimary": "Primary",
|
||||||
|
@ -164,7 +164,7 @@
|
||||||
"HeaderTracks": "Tracks",
|
"HeaderTracks": "Tracks",
|
||||||
"HeaderMusicArtist": "M\u00fasic",
|
"HeaderMusicArtist": "M\u00fasic",
|
||||||
"HeaderLocked": "Locked",
|
"HeaderLocked": "Locked",
|
||||||
"HeaderStudios": "Studios",
|
"HeaderStudios": "Estudis",
|
||||||
"HeaderActor": "Actors",
|
"HeaderActor": "Actors",
|
||||||
"HeaderComposer": "Compositors",
|
"HeaderComposer": "Compositors",
|
||||||
"HeaderDirector": "Directors",
|
"HeaderDirector": "Directors",
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
"ViewTypeLatestGames": "Nieuwste games",
|
"ViewTypeLatestGames": "Nieuwste games",
|
||||||
"ViewTypeRecentlyPlayedGames": "Recent gespeelt",
|
"ViewTypeRecentlyPlayedGames": "Recent gespeelt",
|
||||||
"ViewTypeGameFavorites": "Favorieten",
|
"ViewTypeGameFavorites": "Favorieten",
|
||||||
"ViewTypeGameSystems": "Gam systemen",
|
"ViewTypeGameSystems": "Game systemen",
|
||||||
"ViewTypeGameGenres": "Genres",
|
"ViewTypeGameGenres": "Genres",
|
||||||
"ViewTypeTvResume": "Hervatten",
|
"ViewTypeTvResume": "Hervatten",
|
||||||
"ViewTypeTvNextUp": "Volgende",
|
"ViewTypeTvNextUp": "Volgende",
|
||||||
|
@ -147,7 +147,7 @@
|
||||||
"HeaderCommunityRating": "Gemeenschap cijfer",
|
"HeaderCommunityRating": "Gemeenschap cijfer",
|
||||||
"HeaderTrailers": "Trailers",
|
"HeaderTrailers": "Trailers",
|
||||||
"HeaderSpecials": "Specials",
|
"HeaderSpecials": "Specials",
|
||||||
"HeaderGameSystems": "Spel systemen",
|
"HeaderGameSystems": "Game systemen",
|
||||||
"HeaderPlayers": "Spelers:",
|
"HeaderPlayers": "Spelers:",
|
||||||
"HeaderAlbumArtists": "Album artiesten",
|
"HeaderAlbumArtists": "Album artiesten",
|
||||||
"HeaderAlbums": "Albums",
|
"HeaderAlbums": "Albums",
|
||||||
|
|
|
@ -48,9 +48,9 @@
|
||||||
<Reference Include="Interfaces.IO">
|
<Reference Include="Interfaces.IO">
|
||||||
<HintPath>..\packages\Interfaces.IO.1.0.0.5\lib\portable-net45+sl4+wp71+win8+wpa81\Interfaces.IO.dll</HintPath>
|
<HintPath>..\packages\Interfaces.IO.1.0.0.5\lib\portable-net45+sl4+wp71+win8+wpa81\Interfaces.IO.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="MediaBrowser.Naming, Version=1.0.5818.23111, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="MediaBrowser.Naming, Version=1.0.5869.26812, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
<SpecificVersion>False</SpecificVersion>
|
||||||
<HintPath>..\packages\MediaBrowser.Naming.1.0.0.41\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
|
<HintPath>..\packages\MediaBrowser.Naming.1.0.0.44\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="MoreLinq">
|
<Reference Include="MoreLinq">
|
||||||
<HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath>
|
<HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Common.IO;
|
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Notifications;
|
using MediaBrowser.Controller.Notifications;
|
||||||
|
@ -17,12 +16,13 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using CommonIO;
|
using CommonIO;
|
||||||
|
using MediaBrowser.Common.Threading;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.News
|
namespace MediaBrowser.Server.Implementations.News
|
||||||
{
|
{
|
||||||
public class NewsEntryPoint : IServerEntryPoint
|
public class NewsEntryPoint : IServerEntryPoint
|
||||||
{
|
{
|
||||||
private Timer _timer;
|
private PeriodicTimer _timer;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
private readonly IApplicationPaths _appPaths;
|
private readonly IApplicationPaths _appPaths;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
|
@ -47,7 +47,7 @@ namespace MediaBrowser.Server.Implementations.News
|
||||||
|
|
||||||
public void Run()
|
public void Run()
|
||||||
{
|
{
|
||||||
_timer = new Timer(OnTimerFired, null, TimeSpan.FromMilliseconds(500), _frequency);
|
_timer = new PeriodicTimer(OnTimerFired, null, TimeSpan.FromMilliseconds(500), _frequency);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<packages>
|
<packages>
|
||||||
<package id="CommonIO" version="1.0.0.7" targetFramework="net45" />
|
<package id="CommonIO" version="1.0.0.7" targetFramework="net45" />
|
||||||
<package id="Interfaces.IO" version="1.0.0.5" targetFramework="net45" />
|
<package id="Interfaces.IO" version="1.0.0.5" targetFramework="net45" />
|
||||||
<package id="MediaBrowser.Naming" version="1.0.0.41" targetFramework="net45" />
|
<package id="MediaBrowser.Naming" version="1.0.0.44" targetFramework="net45" />
|
||||||
<package id="Mono.Nat" version="1.2.24.0" targetFramework="net45" />
|
<package id="Mono.Nat" version="1.2.24.0" targetFramework="net45" />
|
||||||
<package id="morelinq" version="1.4.0" targetFramework="net45" />
|
<package id="morelinq" version="1.4.0" targetFramework="net45" />
|
||||||
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
|
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
|
||||||
|
|
|
@ -8,6 +8,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using MediaBrowser.Controller.Power;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Mono.Native
|
namespace MediaBrowser.Server.Mono.Native
|
||||||
{
|
{
|
||||||
|
@ -203,5 +204,18 @@ namespace MediaBrowser.Server.Mono.Native
|
||||||
public string sysname = string.Empty;
|
public string sysname = string.Empty;
|
||||||
public string machine = string.Empty;
|
public string machine = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IPowerManagement GetPowerManagement()
|
||||||
|
{
|
||||||
|
return new NullPowerManagement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NullPowerManagement : IPowerManagement
|
||||||
|
{
|
||||||
|
public void ScheduleWake(DateTime utcTime)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -210,7 +210,6 @@ namespace MediaBrowser.Server.Startup.Common
|
||||||
private readonly string _releaseAssetFilename;
|
private readonly string _releaseAssetFilename;
|
||||||
|
|
||||||
internal INativeApp NativeApp { get; set; }
|
internal INativeApp NativeApp { get; set; }
|
||||||
private Timer _ipAddressCacheTimer;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ApplicationHost" /> class.
|
/// Initializes a new instance of the <see cref="ApplicationHost" /> class.
|
||||||
|
@ -234,8 +233,6 @@ namespace MediaBrowser.Server.Startup.Common
|
||||||
NativeApp = nativeApp;
|
NativeApp = nativeApp;
|
||||||
|
|
||||||
SetBaseExceptionMessage();
|
SetBaseExceptionMessage();
|
||||||
|
|
||||||
_ipAddressCacheTimer = new Timer(OnCacheClearTimerFired, null, TimeSpan.FromMinutes(3), TimeSpan.FromMinutes(3));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Version _version;
|
private Version _version;
|
||||||
|
@ -533,6 +530,8 @@ namespace MediaBrowser.Server.Startup.Common
|
||||||
EncodingManager = new EncodingManager(FileSystemManager, Logger, MediaEncoder, ChapterManager);
|
EncodingManager = new EncodingManager(FileSystemManager, Logger, MediaEncoder, ChapterManager);
|
||||||
RegisterSingleInstance(EncodingManager);
|
RegisterSingleInstance(EncodingManager);
|
||||||
|
|
||||||
|
RegisterSingleInstance(NativeApp.GetPowerManagement());
|
||||||
|
|
||||||
var sharingRepo = new SharingRepository(LogManager, ApplicationPaths);
|
var sharingRepo = new SharingRepository(LogManager, ApplicationPaths);
|
||||||
await sharingRepo.Initialize().ConfigureAwait(false);
|
await sharingRepo.Initialize().ConfigureAwait(false);
|
||||||
RegisterSingleInstance<ISharingManager>(new SharingManager(sharingRepo, ServerConfigurationManager, LibraryManager, this));
|
RegisterSingleInstance<ISharingManager>(new SharingManager(sharingRepo, ServerConfigurationManager, LibraryManager, this));
|
||||||
|
@ -1157,7 +1156,12 @@ namespace MediaBrowser.Server.Startup.Common
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, bool> _validAddressResults = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
|
private readonly ConcurrentDictionary<string, bool> _validAddressResults = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
private DateTime _lastAddressCacheClear;
|
||||||
private bool IsIpAddressValid(IPAddress address)
|
private bool IsIpAddressValid(IPAddress address)
|
||||||
|
{
|
||||||
|
return IsIpAddressValidInternal(address).Result;
|
||||||
|
}
|
||||||
|
private async Task<bool> IsIpAddressValidInternal(IPAddress address)
|
||||||
{
|
{
|
||||||
if (IPAddress.IsLoopback(address))
|
if (IPAddress.IsLoopback(address))
|
||||||
{
|
{
|
||||||
|
@ -1167,6 +1171,12 @@ namespace MediaBrowser.Server.Startup.Common
|
||||||
var apiUrl = GetLocalApiUrl(address.ToString());
|
var apiUrl = GetLocalApiUrl(address.ToString());
|
||||||
apiUrl += "/system/ping";
|
apiUrl += "/system/ping";
|
||||||
|
|
||||||
|
if ((DateTime.UtcNow - _lastAddressCacheClear).TotalMinutes >= 5)
|
||||||
|
{
|
||||||
|
_lastAddressCacheClear = DateTime.UtcNow;
|
||||||
|
_validAddressResults.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
bool cachedResult;
|
bool cachedResult;
|
||||||
if (_validAddressResults.TryGetValue(apiUrl, out cachedResult))
|
if (_validAddressResults.TryGetValue(apiUrl, out cachedResult))
|
||||||
{
|
{
|
||||||
|
@ -1175,14 +1185,15 @@ namespace MediaBrowser.Server.Startup.Common
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var response = HttpClient.SendAsync(new HttpRequestOptions
|
using (var response = await HttpClient.SendAsync(new HttpRequestOptions
|
||||||
{
|
{
|
||||||
Url = apiUrl,
|
Url = apiUrl,
|
||||||
LogErrorResponseBody = false,
|
LogErrorResponseBody = false,
|
||||||
LogErrors = false,
|
LogErrors = false,
|
||||||
LogRequest = false
|
LogRequest = false,
|
||||||
|
TimeoutMs = 30000
|
||||||
|
|
||||||
}, "POST").Result)
|
}, "POST").ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
using (var reader = new StreamReader(response.Content))
|
using (var reader = new StreamReader(response.Content))
|
||||||
{
|
{
|
||||||
|
@ -1190,25 +1201,20 @@ namespace MediaBrowser.Server.Startup.Common
|
||||||
var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase);
|
var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
_validAddressResults.AddOrUpdate(apiUrl, valid, (k, v) => valid);
|
_validAddressResults.AddOrUpdate(apiUrl, valid, (k, v) => valid);
|
||||||
Logger.Debug("Ping test result to {0}. Success: {1}", apiUrl, valid);
|
//Logger.Debug("Ping test result to {0}. Success: {1}", apiUrl, valid);
|
||||||
return valid;
|
return valid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
Logger.Debug("Ping test result to {0}. Success: {1}", apiUrl, false);
|
//Logger.Debug("Ping test result to {0}. Success: {1}", apiUrl, false);
|
||||||
|
|
||||||
_validAddressResults.AddOrUpdate(apiUrl, false, (k, v) => false);
|
_validAddressResults.AddOrUpdate(apiUrl, false, (k, v) => false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnCacheClearTimerFired(object state)
|
|
||||||
{
|
|
||||||
_validAddressResults.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public string FriendlyName
|
public string FriendlyName
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
|
@ -4,7 +4,7 @@ using MediaBrowser.Controller.Session;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using MediaBrowser.Common.Threading;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Startup.Common.EntryPoints
|
namespace MediaBrowser.Server.Startup.Common.EntryPoints
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,7 @@ namespace MediaBrowser.Server.Startup.Common.EntryPoints
|
||||||
{
|
{
|
||||||
private readonly ISessionManager _sessionManager;
|
private readonly ISessionManager _sessionManager;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private Timer _timer;
|
private PeriodicTimer _timer;
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
|
|
||||||
public KeepServerAwake(ISessionManager sessionManager, ILogger logger, IServerApplicationHost appHost)
|
public KeepServerAwake(ISessionManager sessionManager, ILogger logger, IServerApplicationHost appHost)
|
||||||
|
@ -24,7 +24,7 @@ namespace MediaBrowser.Server.Startup.Common.EntryPoints
|
||||||
|
|
||||||
public void Run()
|
public void Run()
|
||||||
{
|
{
|
||||||
_timer = new Timer(obj =>
|
_timer = new PeriodicTimer(obj =>
|
||||||
{
|
{
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
if (_sessionManager.Sessions.Any(i => (now - i.LastActivityDate).TotalMinutes < 15))
|
if (_sessionManager.Sessions.Any(i => (now - i.LastActivityDate).TotalMinutes < 15))
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using MediaBrowser.Controller.Power;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Startup.Common
|
namespace MediaBrowser.Server.Startup.Common
|
||||||
{
|
{
|
||||||
|
@ -90,5 +91,11 @@ namespace MediaBrowser.Server.Startup.Common
|
||||||
/// Prevents the system stand by.
|
/// Prevents the system stand by.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void PreventSystemStandby();
|
void PreventSystemStandby();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the power management.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>IPowerManagement.</returns>
|
||||||
|
IPowerManagement GetPowerManagement();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -218,7 +218,7 @@ namespace MediaBrowser.ServerApplication
|
||||||
var fileSystem = new WindowsFileSystem(new PatternsLogger(logManager.GetLogger("FileSystem")));
|
var fileSystem = new WindowsFileSystem(new PatternsLogger(logManager.GetLogger("FileSystem")));
|
||||||
fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
|
fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
|
||||||
|
|
||||||
var nativeApp = new WindowsApp(fileSystem)
|
var nativeApp = new WindowsApp(fileSystem, _logger)
|
||||||
{
|
{
|
||||||
IsRunningAsService = runService
|
IsRunningAsService = runService
|
||||||
};
|
};
|
||||||
|
|
|
@ -120,6 +120,7 @@
|
||||||
<Compile Include="Native\Standby.cs" />
|
<Compile Include="Native\Standby.cs" />
|
||||||
<Compile Include="Native\ServerAuthorization.cs" />
|
<Compile Include="Native\ServerAuthorization.cs" />
|
||||||
<Compile Include="Native\WindowsApp.cs" />
|
<Compile Include="Native\WindowsApp.cs" />
|
||||||
|
<Compile Include="Native\WindowsPowerManagement.cs" />
|
||||||
<Compile Include="Networking\CertificateGenerator.cs" />
|
<Compile Include="Networking\CertificateGenerator.cs" />
|
||||||
<Compile Include="Networking\NativeMethods.cs" />
|
<Compile Include="Networking\NativeMethods.cs" />
|
||||||
<Compile Include="Networking\NetworkManager.cs" />
|
<Compile Include="Networking\NetworkManager.cs" />
|
||||||
|
|
|
@ -6,16 +6,19 @@ using MediaBrowser.ServerApplication.Networking;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using CommonIO;
|
using CommonIO;
|
||||||
|
using MediaBrowser.Controller.Power;
|
||||||
|
|
||||||
namespace MediaBrowser.ServerApplication.Native
|
namespace MediaBrowser.ServerApplication.Native
|
||||||
{
|
{
|
||||||
public class WindowsApp : INativeApp
|
public class WindowsApp : INativeApp
|
||||||
{
|
{
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public WindowsApp(IFileSystem fileSystem)
|
public WindowsApp(IFileSystem fileSystem, ILogger logger)
|
||||||
{
|
{
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Assembly> GetAssembliesWithParts()
|
public List<Assembly> GetAssembliesWithParts()
|
||||||
|
@ -117,5 +120,10 @@ namespace MediaBrowser.ServerApplication.Native
|
||||||
{
|
{
|
||||||
Standby.PreventSystemStandby();
|
Standby.PreventSystemStandby();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IPowerManagement GetPowerManagement()
|
||||||
|
{
|
||||||
|
return new WindowsPowerManagement(_logger);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
using MediaBrowser.Controller.Power;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using Microsoft.Win32.SafeHandles;
|
||||||
|
|
||||||
|
namespace MediaBrowser.ServerApplication.Native
|
||||||
|
{
|
||||||
|
public class WindowsPowerManagement : IPowerManagement
|
||||||
|
{
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
public static extern SafeWaitHandle CreateWaitableTimer(IntPtr lpTimerAttributes,
|
||||||
|
bool bManualReset,
|
||||||
|
string lpTimerName);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public static extern bool SetWaitableTimer(SafeWaitHandle hTimer,
|
||||||
|
[In] ref long pDueTime,
|
||||||
|
int lPeriod,
|
||||||
|
IntPtr pfnCompletionRoutine,
|
||||||
|
IntPtr lpArgToCompletionRoutine,
|
||||||
|
bool fResume);
|
||||||
|
|
||||||
|
private BackgroundWorker _bgWorker;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly object _initLock = new object();
|
||||||
|
|
||||||
|
public WindowsPowerManagement(ILogger logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ScheduleWake(DateTime utcTime)
|
||||||
|
{
|
||||||
|
//Initialize();
|
||||||
|
//_bgWorker.RunWorkerAsync(utcTime.ToFileTime());
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Initialize()
|
||||||
|
{
|
||||||
|
lock (_initLock)
|
||||||
|
{
|
||||||
|
if (_bgWorker == null)
|
||||||
|
{
|
||||||
|
_bgWorker = new BackgroundWorker();
|
||||||
|
|
||||||
|
_bgWorker.DoWork += bgWorker_DoWork;
|
||||||
|
_bgWorker.RunWorkerCompleted += bgWorker_RunWorkerCompleted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
|
||||||
|
{
|
||||||
|
//if (Woken != null)
|
||||||
|
//{
|
||||||
|
// Woken(this, new EventArgs());
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
long waketime = (long)e.Argument;
|
||||||
|
|
||||||
|
using (SafeWaitHandle handle = CreateWaitableTimer(IntPtr.Zero, true, GetType().Assembly.GetName().Name + "Timer"))
|
||||||
|
{
|
||||||
|
if (SetWaitableTimer(handle, ref waketime, 0, IntPtr.Zero, IntPtr.Zero, true))
|
||||||
|
{
|
||||||
|
using (EventWaitHandle wh = new EventWaitHandle(false,
|
||||||
|
EventResetMode.AutoReset))
|
||||||
|
{
|
||||||
|
wh.SafeWaitHandle = handle;
|
||||||
|
wh.WaitOne();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error scheduling wake timer", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -354,8 +354,7 @@ namespace MediaBrowser.WebDashboard.Api
|
||||||
DeleteFoldersByName(Path.Combine(bowerPath, "jstree"), "src");
|
DeleteFoldersByName(Path.Combine(bowerPath, "jstree"), "src");
|
||||||
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, "swipebox"), "lib");
|
DeleteFoldersByName(Path.Combine(bowerPath, "Swiper"), "src");
|
||||||
DeleteFoldersByName(Path.Combine(bowerPath, "swipebox"), "scss");
|
|
||||||
|
|
||||||
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
|
|
@ -158,9 +158,6 @@
|
||||||
<Content Include="dashboard-ui\components\metadataeditor\metadataeditor.template.html">
|
<Content Include="dashboard-ui\components\metadataeditor\metadataeditor.template.html">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Include="dashboard-ui\components\paperdialoghelper.js">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="dashboard-ui\components\playlisteditor\playlisteditor.js">
|
<Content Include="dashboard-ui\components\playlisteditor\playlisteditor.js">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
@ -311,9 +308,6 @@
|
||||||
<Content Include="dashboard-ui\scripts\supporterkeypage.js">
|
<Content Include="dashboard-ui\scripts\supporterkeypage.js">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Include="dashboard-ui\components\testermessage.js">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="dashboard-ui\scripts\wizardlivetvguide.js">
|
<Content Include="dashboard-ui\scripts\wizardlivetvguide.js">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
|
|
@ -215,7 +215,11 @@ namespace MediaBrowser.XbmcMetadata.Parsers
|
||||||
if (!string.IsNullOrWhiteSpace(val))
|
if (!string.IsNullOrWhiteSpace(val))
|
||||||
{
|
{
|
||||||
DateTime added;
|
DateTime added;
|
||||||
if (DateTime.TryParse(val, out added))
|
if (DateTime.TryParseExact(val, BaseNfoSaver.DateAddedFormat, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out added))
|
||||||
|
{
|
||||||
|
item.EndDate = added.ToUniversalTime();
|
||||||
|
}
|
||||||
|
else if (DateTime.TryParse(val, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out added))
|
||||||
{
|
{
|
||||||
item.DateCreated = added.ToUniversalTime();
|
item.DateCreated = added.ToUniversalTime();
|
||||||
}
|
}
|
||||||
|
@ -627,8 +631,11 @@ namespace MediaBrowser.XbmcMetadata.Parsers
|
||||||
{
|
{
|
||||||
var person = GetPersonFromXmlNode(subtree);
|
var person = GetPersonFromXmlNode(subtree);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(person.Name))
|
||||||
|
{
|
||||||
itemResult.AddPerson(person);
|
itemResult.AddPerson(person);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -976,11 +983,11 @@ namespace MediaBrowser.XbmcMetadata.Parsers
|
||||||
if (!string.IsNullOrWhiteSpace(val) && !string.IsNullOrWhiteSpace(userDataUserId))
|
if (!string.IsNullOrWhiteSpace(val) && !string.IsNullOrWhiteSpace(userDataUserId))
|
||||||
{
|
{
|
||||||
DateTime parsedValue;
|
DateTime parsedValue;
|
||||||
if (DateTime.TryParseExact(val, "yyyy-MM-dd HH:mm:ss", _usCulture, DateTimeStyles.None, out parsedValue))
|
if (DateTime.TryParseExact(val, "yyyy-MM-dd HH:mm:ss", _usCulture, DateTimeStyles.AssumeLocal, out parsedValue))
|
||||||
{
|
{
|
||||||
var userData = GetOrAdd(itemResult, userDataUserId);
|
var userData = GetOrAdd(itemResult, userDataUserId);
|
||||||
|
|
||||||
userData.LastPlayedDate = parsedValue;
|
userData.LastPlayedDate = parsedValue.ToUniversalTime();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -416,6 +416,8 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
||||||
writer.WriteEndElement();
|
writer.WriteEndElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the common nodes.
|
/// Adds the common nodes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -472,7 +474,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
||||||
writer.WriteElementString("type", item.DisplayMediaType);
|
writer.WriteElementString("type", item.DisplayMediaType);
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.WriteElementString("dateadded", item.DateCreated.ToString("yyyy-MM-dd HH:mm:ss"));
|
writer.WriteElementString("dateadded", item.DateCreated.ToLocalTime().ToString(DateAddedFormat));
|
||||||
|
|
||||||
writer.WriteElementString("title", item.Name ?? string.Empty);
|
writer.WriteElementString("title", item.Name ?? string.Empty);
|
||||||
writer.WriteElementString("originaltitle", item.Name ?? string.Empty);
|
writer.WriteElementString("originaltitle", item.Name ?? string.Empty);
|
||||||
|
@ -949,7 +951,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
||||||
|
|
||||||
if (userdata.LastPlayedDate.HasValue)
|
if (userdata.LastPlayedDate.HasValue)
|
||||||
{
|
{
|
||||||
writer.WriteElementString("lastplayed", userdata.LastPlayedDate.Value.ToString("yyyy-MM-dd HH:mm:ss").ToLower());
|
writer.WriteElementString("lastplayed", userdata.LastPlayedDate.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss").ToLower());
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.WriteStartElement("resume");
|
writer.WriteStartElement("resume");
|
||||||
|
|
Loading…
Reference in New Issue
Block a user