Merge branch 'beta'

pull/1154/head
Luke Pulverenti 9 years ago
commit 37352785ac

@ -1,5 +1,4 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
@ -46,6 +45,11 @@ namespace MediaBrowser.Api
/// <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")]
public bool IncludeHidden { get; set; }
public GetDirectoryContents()
{
IncludeHidden = true;
}
}
[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))
{
inputModifier += " -noaccurate_seek";
//inputModifier += " -noaccurate_seek";
}
}

@ -141,10 +141,10 @@ namespace MediaBrowser.Api.Playback
var profile = request.DeviceProfile;
var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
if (caps != null)
if (profile == null)
{
if (profile == null)
var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
if (caps != null)
{
profile = caps.DeviceProfile;
}

@ -66,13 +66,12 @@ namespace MediaBrowser.Api
{
_config.Configuration.IsStartupWizardCompleted = true;
_config.Configuration.EnableLocalizedGuids = true;
_config.Configuration.MergeMetadataAndImagesByName = true;
_config.Configuration.EnableStandaloneMetadata = true;
_config.Configuration.EnableLibraryMetadataSubFolder = true;
_config.Configuration.EnableCustomPathSubFolders = true;
_config.Configuration.DisableStartupScan = true;
_config.Configuration.EnableUserViews = true;
_config.Configuration.EnableDateLastRefresh = true;
_config.Configuration.MergeMetadataAndImagesByName = true;
_config.SaveConfiguration();
}

@ -370,6 +370,8 @@ namespace MediaBrowser.Api.UserLibrary
public void Post(ReportPlaybackStopped request)
{
Logger.Debug("ReportPlaybackStopped PlaySessionId: {0}", request.PlaySessionId ?? string.Empty);
if (!string.IsNullOrWhiteSpace(request.PlaySessionId))
{
ApiEntryPoint.Instance.KillTranscodingJobs(AuthorizationContext.GetAuthorizationInfo(Request).DeviceId, request.PlaySessionId, s => true);

@ -6,7 +6,6 @@ using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading;
using MoreLinq;
namespace MediaBrowser.Common.Implementations.Networking
@ -14,22 +13,11 @@ namespace MediaBrowser.Common.Implementations.Networking
public abstract class BaseNetworkManager
{
protected ILogger Logger { get; private set; }
private Timer _clearCacheTimer;
private DateTime _lastRefresh;
protected BaseNetworkManager(ILogger 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;
@ -41,15 +29,21 @@ namespace MediaBrowser.Common.Implementations.Networking
/// <returns>IPAddress.</returns>
public IEnumerable<IPAddress> GetLocalIpAddresses()
{
if (_localIpAddresses == null)
const int cacheMinutes = 3;
var forceRefresh = (DateTime.UtcNow - _lastRefresh).TotalMinutes >= cacheMinutes;
if (_localIpAddresses == null || forceRefresh)
{
lock (_localIpAddressSyncLock)
{
if (_localIpAddresses == null)
forceRefresh = (DateTime.UtcNow - _lastRefresh).TotalMinutes >= cacheMinutes;
if (_localIpAddresses == null || forceRefresh)
{
var addresses = GetLocalIpAddressesInternal().ToList();
_localIpAddresses = addresses;
_lastRefresh = DateTime.UtcNow;
return addresses;
}

@ -233,7 +233,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// <summary>
/// The _triggers
/// </summary>
private IEnumerable<ITaskTrigger> _triggers;
private volatile List<ITaskTrigger> _triggers;
/// <summary>
/// The _triggers sync lock
/// </summary>
@ -532,7 +532,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// Loads the triggers.
/// </summary>
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
private IEnumerable<ITaskTrigger> LoadTriggers()
private List<ITaskTrigger> LoadTriggers()
{
try
{
@ -543,12 +543,12 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
catch (FileNotFoundException)
{
// File doesn't exist. No biggie. Return defaults.
return ScheduledTask.GetDefaultTriggers();
return ScheduledTask.GetDefaultTriggers().ToList();
}
catch (DirectoryNotFoundException)
{
// 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\ISecurityManager.cs" />
<Compile Include="Security\PaymentRequiredException.cs" />
<Compile Include="Threading\PeriodicTimer.cs" />
<Compile Include="Updates\IInstallationManager.cs" />
<Compile Include="Updates\InstallationEventArgs.cs" />
<Compile Include="Updates\InstallationFailedEventArgs.cs" />

@ -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.Entities;
using MediaBrowser.Model.Users;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Library;
namespace MediaBrowser.Controller.Entities.Audio
{
/// <summary>
/// Class MusicAlbum
/// </summary>
public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo<AlbumInfo>
public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo<AlbumInfo>, IMetadataContainer
{
public MusicAlbum()
{
@ -139,5 +143,58 @@ namespace MediaBrowser.Controller.Entities.Audio
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>
public DateTime? LastActivityDate { get; set; }
private UserConfiguration _config;
private volatile UserConfiguration _config;
private readonly object _configSyncLock = new object();
[IgnoreDataMember]
public UserConfiguration Configuration
@ -132,7 +132,7 @@ namespace MediaBrowser.Controller.Entities
set { _config = value; }
}
private UserPolicy _policy;
private volatile UserPolicy _policy;
private readonly object _policySyncLock = new object();
[IgnoreDataMember]
public UserPolicy Policy

@ -265,6 +265,7 @@
<Compile Include="Playlists\IPlaylistManager.cs" />
<Compile Include="Playlists\Playlist.cs" />
<Compile Include="Plugins\ILocalizablePlugin.cs" />
<Compile Include="Power\IPowerManagement.cs" />
<Compile Include="Providers\AlbumInfo.cs" />
<Compile Include="Providers\ArtistInfo.cs" />
<Compile Include="Providers\BookInfo.cs" />

@ -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
private Timer _timer;
private Timer _volumeTimer;
public DeviceInfo Properties { get; set; }
private int _muteVol;
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; }
@ -93,11 +105,6 @@ namespace MediaBrowser.Dlna.PlayTo
return 1000;
}
private int GetVolumeTimerIntervalMs()
{
return 5000;
}
private int GetInactiveTimerIntervalMs()
{
return 20000;
@ -107,11 +114,37 @@ namespace MediaBrowser.Dlna.PlayTo
{
_timer = new Timer(TimerCallback, null, GetPlaybackTimerIntervalMs(), GetInactiveTimerIntervalMs());
_volumeTimer = new Timer(VolumeTimerCallback, null, Timeout.Infinite, Timeout.Infinite);
_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 bool _timerActive;
private void RestartTimer()
@ -124,7 +157,6 @@ namespace MediaBrowser.Dlna.PlayTo
{
_logger.Debug("RestartTimer");
_timer.Change(10, GetPlaybackTimerIntervalMs());
_volumeTimer.Change(100, GetVolumeTimerIntervalMs());
}
_timerActive = true;
@ -150,10 +182,6 @@ namespace MediaBrowser.Dlna.PlayTo
{
_timer.Change(interval, interval);
}
if (_volumeTimer != null)
{
_volumeTimer.Change(Timeout.Infinite, Timeout.Infinite);
}
}
_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()
{
var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
@ -1012,7 +1027,6 @@ namespace MediaBrowser.Dlna.PlayTo
_disposed = true;
DisposeTimer();
DisposeVolumeTimer();
}
}
@ -1025,15 +1039,6 @@ namespace MediaBrowser.Dlna.PlayTo
}
}
private void DisposeVolumeTimer()
{
if (_volumeTimer != null)
{
_volumeTimer.Dispose();
_volumeTimer = null;
}
}
#endregion
public override string ToString()

@ -37,11 +37,28 @@ namespace MediaBrowser.Dlna.PlayTo
private readonly IDeviceDiscovery _deviceDiscovery;
private readonly string _serverAddress;
private readonly string _accessToken;
private readonly DateTime _creationTime;
public bool IsSessionActive
{
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;
}
}
@ -55,8 +72,6 @@ namespace MediaBrowser.Dlna.PlayTo
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)
{
_session = session;
@ -72,6 +87,7 @@ namespace MediaBrowser.Dlna.PlayTo
_mediaSourceManager = mediaSourceManager;
_accessToken = accessToken;
_logger = logger;
_creationTime = DateTime.UtcNow;
}
public void Init(Device device)
@ -84,8 +100,6 @@ namespace MediaBrowser.Dlna.PlayTo
_device.Start();
_deviceDiscovery.DeviceLeft += _deviceDiscovery_DeviceLeft;
_updateTimer = new Timer(updateTimer_Elapsed, null, 60000, 60000);
}
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)
{
try
@ -634,21 +632,10 @@ namespace MediaBrowser.Dlna.PlayTo
_device.MediaChanged -= _device_MediaChanged;
_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
DisposeUpdateTimer();
_device.Dispose();
}
}
private void DisposeUpdateTimer()
{
if (_updateTimer != null)
{
_updateTimer.Dispose();
_updateTimer = null;
}
}
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)

@ -36,7 +36,7 @@ namespace MediaBrowser.Dlna.PlayTo
private readonly IMediaSourceManager _mediaSourceManager;
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)
{
@ -57,19 +57,9 @@ namespace MediaBrowser.Dlna.PlayTo
public void Start()
{
_clearNonRenderersTimer = new Timer(OnClearUrlTimerCallback, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
}
private void OnClearUrlTimerCallback(object state)
{
lock (_nonRendererUrls)
{
_nonRendererUrls.Clear();
}
}
async void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e)
{
string usn;
@ -99,6 +89,12 @@ namespace MediaBrowser.Dlna.PlayTo
lock (_nonRendererUrls)
{
if ((DateTime.UtcNow - _lastRendererClear).TotalMinutes >= 10)
{
_nonRendererUrls.Clear();
_lastRendererClear = DateTime.UtcNow;
}
if (_nonRendererUrls.Contains(location, StringComparer.OrdinalIgnoreCase))
{
return;
@ -181,12 +177,6 @@ namespace MediaBrowser.Dlna.PlayTo
public void Dispose()
{
_deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered;
if (_clearNonRenderersTimer != null)
{
_clearNonRenderersTimer.Dispose();
_clearNonRenderersTimer = null;
}
}
}
}

@ -58,7 +58,7 @@ namespace MediaBrowser.Dlna.Profiles
Container = "avi",
Type = DlnaProfileType.Video,
VideoCodec = "mpeg1video,mpeg2video,mpeg4,h264,vc1",
AudioCodec = "ac3,dca,mp2,mp3,pcm"
AudioCodec = "ac3,dca,mp2,mp3,pcm,dca"
},
new DirectPlayProfile
@ -66,7 +66,7 @@ namespace MediaBrowser.Dlna.Profiles
Container = "mpeg",
Type = DlnaProfileType.Video,
VideoCodec = "mpeg1video,mpeg2video",
AudioCodec = "ac3,dca,mp2,mp3,pcm"
AudioCodec = "ac3,dca,mp2,mp3,pcm,dca"
},
new DirectPlayProfile
@ -74,7 +74,7 @@ namespace MediaBrowser.Dlna.Profiles
Container = "mkv",
Type = DlnaProfileType.Video,
VideoCodec = "mpeg1video,mpeg2video,mpeg4,h264,vc1",
AudioCodec = "ac3,dca,aac,mp2,mp3,pcm"
AudioCodec = "ac3,dca,aac,mp2,mp3,pcm,dca"
},
new DirectPlayProfile
@ -82,7 +82,7 @@ namespace MediaBrowser.Dlna.Profiles
Container = "ts",
Type = DlnaProfileType.Video,
VideoCodec = "mpeg1video,mpeg2video,h264,vc1",
AudioCodec = "ac3,dca,mp2,mp3,aac"
AudioCodec = "ac3,dca,mp2,mp3,aac,dca"
},
new DirectPlayProfile
@ -90,7 +90,7 @@ namespace MediaBrowser.Dlna.Profiles
Container = "mp4,mov",
Type = DlnaProfileType.Video,
VideoCodec = "h264,mpeg4",
AudioCodec = "ac3,aac,mp2,mp3"
AudioCodec = "ac3,aac,mp2,mp3,dca"
},
new DirectPlayProfile

@ -37,11 +37,11 @@
<IgnoreTranscodeByteRangeRequests>true</IgnoreTranscodeByteRangeRequests>
<XmlRootAttributes />
<DirectPlayProfiles>
<DirectPlayProfile container="avi" audioCodec="ac3,dca,mp2,mp3,pcm" videoCodec="mpeg1video,mpeg2video,mpeg4,h264,vc1" type="Video" />
<DirectPlayProfile container="mpeg" audioCodec="ac3,dca,mp2,mp3,pcm" 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="ts" audioCodec="ac3,dca,mp2,mp3,aac" videoCodec="mpeg1video,mpeg2video,h264,vc1" type="Video" />
<DirectPlayProfile container="mp4,mov" audioCodec="ac3,aac,mp2,mp3" videoCodec="h264,mpeg4" 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,dca" videoCodec="mpeg1video,mpeg2video" 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,dca" videoCodec="mpeg1video,mpeg2video,h264,vc1" 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="mp2,ac3" videoCodec="mpeg2video" type="Video" />
<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 IPEndPoint _ssdpEndp = new IPEndPoint(IPAddress.Parse(SSDPAddr), SSDPPort);
private Timer _queueTimer;
private Timer _notificationTimer;
private readonly AutoResetEvent _datagramPosted = new AutoResetEvent(false);
private readonly ConcurrentQueue<Datagram> _messageQueue = new ConcurrentQueue<Datagram>();
private bool _isDisposed;
private readonly ConcurrentDictionary<Guid, List<UpnpDevice>> _devices = new ConcurrentDictionary<Guid, List<UpnpDevice>>();
@ -121,9 +117,13 @@ namespace MediaBrowser.Dlna.Ssdp
public void Start()
{
RestartSocketListener();
DisposeSocket();
StopAliveNotifier();
RestartSocketListener();
ReloadAliveNotifier();
SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
}
@ -131,7 +131,7 @@ namespace MediaBrowser.Dlna.Ssdp
{
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);
}
public void SendDatagram(string header,
public async void SendDatagram(string header,
Dictionary<string, string> values,
EndPoint endpoint,
EndPoint localAddress,
@ -162,28 +162,18 @@ namespace MediaBrowser.Dlna.Ssdp
int sendCount)
{
var msg = new SsdpMessageBuilder().BuildMessage(header, values);
var queued = false;
var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLogging;
for (var i = 0; i < sendCount; i++)
{
var dgram = new Datagram(endpoint, localAddress, _logger, msg, isBroadcast, enableDebugLogging);
if (_messageQueue.Count == 0)
if (i > 0)
{
dgram.Send();
await Task.Delay(500).ConfigureAwait(false);
}
else
{
_messageQueue.Enqueue(dgram);
queued = true;
}
}
if (queued)
{
StartQueueTimer();
var dgram = new Datagram(endpoint, localAddress, _logger, msg, isBroadcast, enableDebugLogging);
dgram.Send();
}
}
@ -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()
{
if (_isDisposed)
{
StopSocketRetryTimer();
return;
}
@ -304,8 +257,6 @@ namespace MediaBrowser.Dlna.Ssdp
_logger.Info("MultiCast socket created");
StopSocketRetryTimer();
Receive();
}
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()
{
try
@ -448,16 +374,9 @@ namespace MediaBrowser.Dlna.Ssdp
SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
_isDisposed = true;
while (_messageQueue.Count != 0)
{
_datagramPosted.WaitOne();
}
DisposeSocket();
DisposeQueueTimer();
DisposeNotificationTimer();
_datagramPosted.Dispose();
StopAliveNotifier();
}
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()
{
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)
{
List<UpnpDevice> list;
lock (_devices)
{
if (!_devices.TryGetValue(uuid, out list))
{
_devices.TryAdd(uuid, list = new List<UpnpDevice>());
}
}
var list = _devices.GetOrAdd(uuid, new List<UpnpDevice>());
list.AddRange(services.Select(i => new UpnpDevice(uuid, i, descriptionUri, address)));
@ -572,7 +472,7 @@ namespace MediaBrowser.Dlna.Ssdp
if (!config.BlastAliveMessages)
{
DisposeNotificationTimer();
StopAliveNotifier();
return;
}
@ -599,7 +499,7 @@ namespace MediaBrowser.Dlna.Ssdp
}
}
private void DisposeNotificationTimer()
private void StopAliveNotifier()
{
lock (_notificationTimerSyncLock)
{

@ -292,16 +292,25 @@ namespace MediaBrowser.MediaEncoding.Encoder
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 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 it's mpeg based, assume true
if ((videoStream.Codec ?? string.Empty).IndexOf("mpeg", StringComparison.OrdinalIgnoreCase) != -1)
{
var formats = (video.Container ?? string.Empty).Split(',').ToList();
if (!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 (enableInterlacedDection)
{
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;
}

@ -164,7 +164,7 @@ namespace MediaBrowser.Model.Configuration
/// different directories and files.
/// </summary>
/// <value>The file watcher delay.</value>
public int RealtimeLibraryMonitorDelay { get; set; }
public int LibraryMonitorDelay { get; set; }
/// <summary>
/// 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 bool MergeMetadataAndImagesByName { get; set; }
public bool EnableStandaloneMetadata { get; set; }
/// <summary>
/// Gets or sets the image saving convention.
@ -256,7 +255,7 @@ namespace MediaBrowser.Model.Configuration
MinResumeDurationSeconds = 300;
EnableLibraryMonitor = AutoOnOff.Auto;
RealtimeLibraryMonitorDelay = 40;
LibraryMonitorDelay = 60;
EnableInternetProviders = true;
FindInternetTrailers = true;

@ -48,6 +48,7 @@ namespace MediaBrowser.Model.Configuration
public string[] PlainFolderViews { get; set; }
public bool HidePlayedInLatest { get; set; }
public bool DisplayChannelsInline { get; set; }
/// <summary>
/// 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>();
if (video.VideoType == VideoType.BluRay || (video.IsoType.HasValue && video.IsoType.Value == IsoType.BluRay))
if (blurayInfo != null)
{
FetchBdInfo(video, chapters, mediaStreams, blurayInfo);
}
@ -360,7 +360,15 @@ namespace MediaBrowser.Providers.MediaInfo
/// <returns>VideoStream.</returns>
private BlurayDiscInfo GetBDInfo(string path)
{
return _blurayExaminer.GetDiscInfo(path);
try
{
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)
@ -628,7 +636,7 @@ namespace MediaBrowser.Providers.MediaInfo
FetchFromDvdLib(item, mount);
}
if (item.VideoType == VideoType.BluRay || (item.IsoType.HasValue && item.IsoType.Value == IsoType.BluRay))
if (blurayDiscInfo != null)
{
item.PlayableStreamFileNames = blurayDiscInfo.Files.ToList();
}

@ -38,7 +38,7 @@ namespace MediaBrowser.Providers.People
private int _requestCount;
private readonly object _requestCountLock = new object();
private Timer _requestCountReset;
private DateTime _lastRequestCountReset;
public MovieDbPersonProvider(IFileSystem fileSystem, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogger logger)
{
@ -48,16 +48,6 @@ namespace MediaBrowser.Providers.People
_httpClient = httpClient;
_logger = logger;
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
@ -101,6 +91,12 @@ namespace MediaBrowser.Providers.People
{
lock (_requestCountLock)
{
if ((DateTime.UtcNow - _lastRequestCountReset).TotalHours >= 1)
{
_requestCount = 0;
_lastRequestCountReset = DateTime.UtcNow;
}
var requestCount = _requestCount;
if (requestCount >= 5)

@ -238,7 +238,7 @@ namespace MediaBrowser.Providers.TV
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
{
@ -268,7 +268,7 @@ namespace MediaBrowser.Providers.TV
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");
if (!string.Equals(downloadLangaugeXmlFile, saveAsLanguageXmlFile, StringComparison.OrdinalIgnoreCase))

@ -29,7 +29,7 @@ using CommonIO;
namespace MediaBrowser.Server.Implementations.Channels
{
public class ChannelManager : IChannelManager, IDisposable
public class ChannelManager : IChannelManager
{
private IChannel[] _channels;
@ -47,11 +47,6 @@ namespace MediaBrowser.Server.Implementations.Channels
private readonly ILocalizationManager _localization;
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)
{
_userManager = userManager;
@ -65,9 +60,6 @@ namespace MediaBrowser.Server.Implementations.Channels
_localization = localization;
_httpClient = httpClient;
_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
@ -206,6 +198,8 @@ namespace MediaBrowser.Server.Implementations.Channels
public async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
{
_refreshedItems.Clear();
var allChannelsList = GetAllChannels().ToList();
var numComplete = 0;
@ -1471,12 +1465,6 @@ namespace MediaBrowser.Server.Implementations.Channels
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)
{
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))
{
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,20 +121,12 @@ namespace MediaBrowser.Server.Implementations.Configuration
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = metadataPath;
if (Configuration.MergeMetadataAndImagesByName)
{
((ServerApplicationPaths)ApplicationPaths).ItemsByNamePath = ((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath;
}
((ServerApplicationPaths)ApplicationPaths).ItemsByNamePath = ((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath;
}
private string GetInternalMetadataPath()
{
if (Configuration.EnableStandaloneMetadata)
{
return Path.Combine(ApplicationPaths.ProgramDataPath, "metadata");
}
return null;
return Path.Combine(ApplicationPaths.ProgramDataPath, "metadata");
}
/// <summary>

@ -13,12 +13,13 @@ using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Threading;
namespace MediaBrowser.Server.Implementations.Connect
{
public class ConnectEntryPoint : IServerEntryPoint
{
private Timer _timer;
private PeriodicTimer _timer;
private readonly IHttpClient _httpClient;
private readonly IApplicationPaths _appPaths;
private readonly ILogger _logger;
@ -43,7 +44,7 @@ namespace MediaBrowser.Server.Implementations.Connect
{
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" };

@ -25,7 +25,7 @@ namespace MediaBrowser.Server.Implementations.Devices
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private ConcurrentBag<DeviceInfo> _devices;
private List<DeviceInfo> _devices;
public DeviceRepository(IApplicationPaths appPaths, IJsonSerializer json, ILogger logger, IFileSystem fileSystem)
{
@ -93,17 +93,14 @@ namespace MediaBrowser.Server.Implementations.Devices
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()

@ -11,6 +11,7 @@ using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using MediaBrowser.Common.Threading;
namespace MediaBrowser.Server.Implementations.EntryPoints
{
@ -21,7 +22,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
private readonly IServerConfigurationManager _config;
private readonly ISsdpHandler _ssdp;
private Timer _timer;
private PeriodicTimer _timer;
private bool _isStarted;
public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, ISsdpHandler ssdp)
@ -95,7 +96,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
NatUtility.UnhandledException += NatUtility_UnhandledException;
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;

@ -4,6 +4,7 @@ using MediaBrowser.Model.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Threading;
namespace MediaBrowser.Server.Implementations.EntryPoints
{
@ -22,7 +23,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
/// </summary>
private readonly ILogger _logger;
private Timer _timer;
private PeriodicTimer _timer;
/// <summary>
/// Initializes a new instance of the <see cref="LoadRegistrations" /> class.
@ -41,7 +42,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
/// </summary>
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()

@ -9,6 +9,7 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.EntryPoints
{
@ -23,7 +24,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
private readonly ISessionManager _sessionManager;
private readonly IUserManager _userManager;
private Timer _timer;
private readonly TimeSpan _frequency = TimeSpan.FromHours(24);
private readonly ConcurrentDictionary<Guid, ClientInfo> _apps = new ConcurrentDictionary<Guid, ClientInfo>();
@ -95,16 +95,16 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
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>
/// Called when [timer fired].
/// </summary>
/// <param name="state">The state.</param>
private async void OnTimerFired(object state)
private async void OnTimerFired()
{
try
{
@ -121,12 +121,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
public void Dispose()
{
_sessionManager.SessionStarted -= _sessionManager_SessionStarted;
if (_timer != null)
{
_timer.Dispose();
_timer = null;
}
}
}
}

@ -76,50 +76,50 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
{
var seasonNumber = episodeInfo.SeasonNumber;
result.ExtractedSeasonNumber = seasonNumber;
// Passing in true will include a few extra regex's
var episodeNumber = episodeInfo.EpisodeNumber;
result.ExtractedEpisodeNumber = episodeNumber;
var premiereDate = episodeInfo.IsByDate ?
new DateTime(episodeInfo.Year.Value, episodeInfo.Month.Value, episodeInfo.Day.Value) :
(DateTime?)null;
if (episodeInfo.IsByDate || (seasonNumber.HasValue && episodeNumber.HasValue))
{
if (episodeInfo.IsByDate)
{
_logger.Debug("Extracted information from {0}. Series name {1}, Date {2}", path, seriesName, premiereDate.Value);
}
else
{
_logger.Debug("Extracted information from {0}. Series name {1}, Season {2}, Episode {3}", path, seriesName, seasonNumber, episodeNumber);
}
var endingEpisodeNumber = episodeInfo.EndingEpsiodeNumber;
result.ExtractedEndingEpisodeNumber = endingEpisodeNumber;
await OrganizeEpisode(path,
seriesName,
seasonNumber,
episodeNumber,
endingEpisodeNumber,
premiereDate,
options,
overwriteExisting,
result,
cancellationToken).ConfigureAwait(false);
}
else
{
var msg = string.Format("Unable to determine episode number from {0}", path);
result.Status = FileSortingStatus.Failure;
result.StatusMessage = msg;
_logger.Warn(msg);
}
result.ExtractedSeasonNumber = seasonNumber;
// Passing in true will include a few extra regex's
var episodeNumber = episodeInfo.EpisodeNumber;
result.ExtractedEpisodeNumber = episodeNumber;
var premiereDate = episodeInfo.IsByDate ?
new DateTime(episodeInfo.Year.Value, episodeInfo.Month.Value, episodeInfo.Day.Value) :
(DateTime?)null;
if (episodeInfo.IsByDate || (seasonNumber.HasValue && episodeNumber.HasValue))
{
if (episodeInfo.IsByDate)
{
_logger.Debug("Extracted information from {0}. Series name {1}, Date {2}", path, seriesName, premiereDate.Value);
}
else
{
_logger.Debug("Extracted information from {0}. Series name {1}, Season {2}, Episode {3}", path, seriesName, seasonNumber, episodeNumber);
}
var endingEpisodeNumber = episodeInfo.EndingEpsiodeNumber;
result.ExtractedEndingEpisodeNumber = endingEpisodeNumber;
await OrganizeEpisode(path,
seriesName,
seasonNumber,
episodeNumber,
endingEpisodeNumber,
premiereDate,
options,
overwriteExisting,
result,
cancellationToken).ConfigureAwait(false);
}
else
{
var msg = string.Format("Unable to determine episode number from {0}", path);
result.Status = FileSortingStatus.Failure;
result.StatusMessage = msg;
_logger.Warn(msg);
}
}
else
{
@ -151,32 +151,32 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
var series = (Series)_libraryManager.GetItemById(new Guid(request.SeriesId));
await OrganizeEpisode(result.OriginalPath,
series,
request.SeasonNumber,
request.EpisodeNumber,
request.EndingEpisodeNumber,
null,
options,
true,
result,
cancellationToken).ConfigureAwait(false);
await OrganizeEpisode(result.OriginalPath,
series,
request.SeasonNumber,
request.EpisodeNumber,
request.EndingEpisodeNumber,
null,
options,
true,
result,
cancellationToken).ConfigureAwait(false);
await _organizationService.SaveResult(result, CancellationToken.None).ConfigureAwait(false);
return result;
}
private Task OrganizeEpisode(string sourcePath,
string seriesName,
int? seasonNumber,
int? episodeNumber,
int? endingEpiosdeNumber,
DateTime? premiereDate,
TvFileOrganizationOptions options,
bool overwriteExisting,
FileOrganizationResult result,
CancellationToken cancellationToken)
private Task OrganizeEpisode(string sourcePath,
string seriesName,
int? seasonNumber,
int? episodeNumber,
int? endingEpiosdeNumber,
DateTime? premiereDate,
TvFileOrganizationOptions options,
bool overwriteExisting,
FileOrganizationResult result,
CancellationToken cancellationToken)
{
var series = GetMatchingSeries(seriesName, result);
@ -189,33 +189,33 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
return Task.FromResult(true);
}
return OrganizeEpisode(sourcePath,
series,
seasonNumber,
episodeNumber,
endingEpiosdeNumber,
premiereDate,
options,
overwriteExisting,
result,
cancellationToken);
return OrganizeEpisode(sourcePath,
series,
seasonNumber,
episodeNumber,
endingEpiosdeNumber,
premiereDate,
options,
overwriteExisting,
result,
cancellationToken);
}
private async Task OrganizeEpisode(string sourcePath,
Series series,
int? seasonNumber,
int? episodeNumber,
int? endingEpiosdeNumber,
DateTime? premiereDate,
TvFileOrganizationOptions options,
bool overwriteExisting,
FileOrganizationResult result,
CancellationToken cancellationToken)
private async Task OrganizeEpisode(string sourcePath,
Series series,
int? seasonNumber,
int? episodeNumber,
int? endingEpiosdeNumber,
DateTime? premiereDate,
TvFileOrganizationOptions options,
bool overwriteExisting,
FileOrganizationResult result,
CancellationToken cancellationToken)
{
_logger.Info("Sorting file {0} into series {1}", sourcePath, series.Path);
// Proceed to sort the file
var newPath = await GetNewPath(sourcePath, series, seasonNumber, episodeNumber, endingEpiosdeNumber, premiereDate, options, cancellationToken).ConfigureAwait(false);
var newPath = await GetNewPath(sourcePath, series, seasonNumber, episodeNumber, endingEpiosdeNumber, premiereDate, options, cancellationToken).ConfigureAwait(false);
if (string.IsNullOrEmpty(newPath))
{
@ -324,17 +324,17 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
}
}
private List<string> GetOtherDuplicatePaths(string targetPath,
Series series,
int? seasonNumber,
int? episodeNumber,
int? endingEpisodeNumber)
private List<string> GetOtherDuplicatePaths(string targetPath,
Series series,
int? seasonNumber,
int? episodeNumber,
int? endingEpisodeNumber)
{
// TODO: Support date-naming?
if (!seasonNumber.HasValue || episodeNumber.HasValue)
{
return new List<string> ();
}
// TODO: Support date-naming?
if (!seasonNumber.HasValue || episodeNumber.HasValue)
{
return new List<string>();
}
var episodePaths = series.GetRecursiveChildren()
.OfType<Episode>()
@ -462,16 +462,18 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
/// <param name="seasonNumber">The season number.</param>
/// <param name="episodeNumber">The 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="cancellationToken">The cancellation token.</param>
/// <returns>System.String.</returns>
private async Task<string> GetNewPath(string sourcePath,
Series series,
int? seasonNumber,
int? episodeNumber,
int? endingEpisodeNumber,
DateTime? premiereDate,
TvFileOrganizationOptions options,
CancellationToken cancellationToken)
private async Task<string> GetNewPath(string sourcePath,
Series series,
int? seasonNumber,
int? episodeNumber,
int? endingEpisodeNumber,
DateTime? premiereDate,
TvFileOrganizationOptions options,
CancellationToken cancellationToken)
{
var episodeInfo = new EpisodeInfo
{
@ -481,7 +483,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
MetadataLanguage = series.GetPreferredMetadataLanguage(),
ParentIndexNumber = seasonNumber,
SeriesProviderIds = series.ProviderIds,
PremiereDate = premiereDate
PremiereDate = premiereDate
};
var searchResults = await _providerManager.GetRemoteSearchResults<Episode, EpisodeInfo>(new RemoteSearchQuery<EpisodeInfo>
@ -491,22 +493,25 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
}, cancellationToken).ConfigureAwait(false);
var episode = searchResults.FirstOrDefault();
string episodeName = string.Empty;
if (episode == null)
{
var msg = string.Format("No provider metadata found for {0} season {1} episode {2}", series.Name, seasonNumber, episodeNumber);
_logger.Warn(msg);
//throw new Exception(msg);
return null;
}
else
{
episodeName = episode.Name;
}
seasonNumber = seasonNumber ?? episode.ParentIndexNumber;
episodeNumber = episodeNumber ?? episode.IndexNumber;
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;
episodeNumber = episodeNumber ?? episode.IndexNumber;
var newPath = GetSeasonFolderPath(series, seasonNumber.Value, options);
@ -579,7 +584,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
{
seriesName = _fileSystem.GetValidFilename(seriesName).Trim();
if (string.IsNullOrEmpty(episodeTitle))
if (string.IsNullOrWhiteSpace(episodeTitle))
{
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>
/// Overridable method that can be used to implement a custom hnandler
/// </summary>
@ -271,6 +290,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer
var operationName = httpReq.OperationName;
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) ||
string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase))
{
@ -333,15 +360,16 @@ namespace MediaBrowser.Server.Implementations.HttpServer
task.ContinueWith(x => httpRes.Close(), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent);
//Matches Exceptions handled in HttpListenerBase.InitTask()
var urlString = url.ToString();
task.ContinueWith(x =>
{
var statusCode = httpRes.StatusCode;
var duration = DateTime.Now - date;
LoggerUtils.LogResponse(_logger, statusCode, urlString, remoteIp, duration);
if (enableLog)
{
LoggerUtils.LogResponse(_logger, statusCode, urlString, remoteIp, duration);
}
}, TaskContinuationOptions.None);
return task;

@ -1,11 +1,30 @@
using MediaBrowser.Model.Logging;
using System;
using System.Globalization;
using System.IO;
using SocketHttpListener.Net;
namespace MediaBrowser.Server.Implementations.HttpServer
{
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>
/// Logs the response.
/// </summary>

@ -78,10 +78,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
{
var request = context.Request;
LogRequest(_logger, request);
if (request.IsWebSocketRequest)
{
LoggerUtils.LogRequest(_logger, request);
ProcessWebSocketRequest(context);
return Task.FromResult(true);
}
@ -156,44 +156,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
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)
{
var httpReq = GetRequest(context);

@ -471,11 +471,11 @@ namespace MediaBrowser.Server.Implementations.IO
{
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
{
_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)
{
if (Environment.OSVersion.Platform != PlatformID.Win32NT)
{
// Causing lockups on linux
return false;
}
try
{
var data = _fileSystem.GetFileSystemInfo(path);
if (!data.Exists
|| data.Attributes.HasFlag(FileAttributes.Directory)
|| data.IsDirectory
// Opening a writable stream will fail with readonly files
|| data.Attributes.HasFlag(FileAttributes.ReadOnly))

@ -222,7 +222,7 @@ namespace MediaBrowser.Server.Implementations.Library
/// <summary>
/// The _root folder
/// </summary>
private AggregateFolder _rootFolder;
private volatile AggregateFolder _rootFolder;
/// <summary>
/// The _root folder sync lock
/// </summary>
@ -743,7 +743,7 @@ namespace MediaBrowser.Server.Implementations.Library
return rootFolder;
}
private UserRootFolder _userRootFolder;
private volatile UserRootFolder _userRootFolder;
private readonly object _syncLock = new object();
public Folder GetUserRootFolder()
{

@ -51,8 +51,17 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
{
var filename = Path.GetFileNameWithoutExtension(path) ?? string.Empty;
return !IgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase)
&& imageProcessor.SupportedInputFormats.Contains((Path.GetExtension(path) ?? string.Empty).TrimStart('.'), StringComparer.OrdinalIgnoreCase);
if (IgnoreFiles.Contains(filename, 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;
list.AddRange(channels);
if (user.Configuration.DisplayChannelsInline && channels.Length > 0)
{
list.Add(await _channelManager.GetInternalChannelFolder(cancellationToken).ConfigureAwait(false));
}
else
{
list.AddRange(channels);
}
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.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Power;
using Microsoft.Win32;
namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
@ -55,7 +58,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
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;
@ -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));
_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;
}
public void Start()
{
_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;
@ -155,7 +171,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
epgData = GetEpgDataForChannel(timer.ChannelId);
}
await UpdateTimersForSeriesTimer(epgData, timer).ConfigureAwait(false);
await UpdateTimersForSeriesTimer(epgData, timer, false).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)
{
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)
{
CancelTimerInternal(timer.Id);
@ -332,25 +352,44 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
_seriesTimerProvider.Add(info);
await UpdateTimersForSeriesTimer(epgData, info).ConfigureAwait(false);
await UpdateTimersForSeriesTimer(epgData, info, false).ConfigureAwait(false);
}
public async Task UpdateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
{
_seriesTimerProvider.Update(info);
List<ProgramInfo> epgData;
if (info.RecordAnyChannel)
{
var channels = await GetChannelsAsync(true, CancellationToken.None).ConfigureAwait(false);
var channelIds = channels.Select(i => i.Id).ToList();
epgData = GetEpgDataForChannels(channelIds);
}
else
var instance = _seriesTimerProvider.GetAll().FirstOrDefault(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
if (instance != null)
{
epgData = GetEpgDataForChannel(info.ChannelId);
}
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;
await UpdateTimersForSeriesTimer(epgData, info).ConfigureAwait(false);
_seriesTimerProvider.Update(instance);
List<ProgramInfo> epgData;
if (instance.RecordAnyChannel)
{
var channels = await GetChannelsAsync(true, CancellationToken.None).ConfigureAwait(false);
var channelIds = channels.Select(i => i.Id).ToList();
epgData = GetEpgDataForChannels(channelIds);
}
else
{
epgData = GetEpgDataForChannel(instance.ChannelId);
}
await UpdateTimersForSeriesTimer(epgData, instance, true).ConfigureAwait(false);
}
}
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);
}
else
{
_logger.Info("Skipping RecordStream because it's already in progress.");
}
}
catch (OperationCanceledException)
{
@ -721,7 +764,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
recording.DateLastUpdated = DateTime.UtcNow;
_recordingProvider.AddOrUpdate(recording);
_logger.Info("Beginning recording.");
_logger.Info("Beginning recording. Will record for {0} minutes.", duration.TotalMinutes.ToString(CultureInfo.InvariantCulture));
httpRequestOptions.BufferContent = false;
var durationToken = new CancellationTokenSource(duration);
@ -836,7 +879,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
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();
@ -849,12 +892,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_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)
{
// 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);

@ -13,7 +13,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
where T : class
{
private readonly object _fileDataLock = new object();
private List<T> _items;
private volatile List<T> _items;
private readonly IJsonSerializer _jsonSerializer;
protected readonly ILogger Logger;
private readonly string _dataPath;

@ -5,22 +5,27 @@ using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using System;
using System.Collections.Concurrent;
using System.Globalization;
using System.Linq;
using System.Threading;
using CommonIO;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Power;
namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
public class TimerManager : ItemDataProvider<TimerInfo>
{
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 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))
{
_powerManagement = powerManagement;
_logger = logger1;
}
public void RestartTimers()
@ -58,6 +63,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
var timespan = RecordingHelper.GetStartTime(item) - DateTime.UtcNow;
timer.Change(timespan, TimeSpan.Zero);
ScheduleWake(item);
}
else
{
@ -74,6 +80,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
base.Add(item);
AddTimer(item);
ScheduleWake(item);
}
private void AddTimer(TimerInfo item)
@ -91,15 +98,39 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
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)
{
StopTimer(item);
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();
_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);
_lastRecordingRefreshTime = DateTime.MinValue;
_logger.Info("New recording scheduled");
}
public async Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)

@ -1,6 +1,6 @@
{
"AppDeviceValues": "App: {0}, Device: {1}",
"UserDownloadingItemWithValues": "{0} is downloading {1}",
"AppDeviceValues": "App: {0}, Dispositiu: {1}",
"UserDownloadingItemWithValues": "{0} est\u00e0 descarregant {1}",
"FolderTypeMixed": "Mixed content",
"FolderTypeMovies": "Pel\u00b7l\u00edcules",
"FolderTypeMusic": "M\u00fasica",
@ -12,10 +12,10 @@
"FolderTypeBooks": "Llibres",
"FolderTypeTvShows": "TV",
"FolderTypeInherit": "Heretat",
"HeaderCastCrew": "Repartiment i equip",
"HeaderCastCrew": "Repartiment i Equip",
"HeaderPeople": "People",
"ValueSpecialEpisodeName": "Special - {0}",
"LabelChapterName": "Chapter {0}",
"LabelChapterName": "Cap\u00edtol {0}",
"NameSeasonNumber": "Temporada {0}",
"LabelExit": "Sortir",
"LabelVisitCommunity": "Visita la comunitat",
@ -77,7 +77,7 @@
"ViewTypeMovieMovies": "Pel\u00b7l\u00edcules",
"ViewTypeMovieCollections": "Col\u00b7leccions",
"ViewTypeMovieFavorites": "Preferides",
"ViewTypeMovieGenres": "Genres",
"ViewTypeMovieGenres": "G\u00e8neres",
"ViewTypeMusicLatest": "Novetats",
"ViewTypeMusicPlaylists": "Llistes de reproducci\u00f3",
"ViewTypeMusicAlbums": "\u00c0lbums",
@ -89,7 +89,7 @@
"ViewTypeMusicFavoriteArtists": "Artistes Preferits",
"ViewTypeMusicFavoriteSongs": "Can\u00e7ons Preferides",
"ViewTypeFolders": "Directoris",
"ViewTypeLiveTvRecordingGroups": "Recordings",
"ViewTypeLiveTvRecordingGroups": "Enregistraments",
"ViewTypeLiveTvChannels": "Canals",
"ScheduledTaskFailedWithName": "{0} failed",
"LabelRunningTimeValue": "Running time: {0}",
@ -103,7 +103,7 @@
"LabelIpAddressValue": "Ip address: {0}",
"DeviceOnlineWithName": "{0} is connected",
"UserOnlineFromDevice": "{0} is online from {1}",
"ProviderValue": "Provider: {0}",
"ProviderValue": "Prove\u00efdor: {0}",
"SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
"UserConfigurationUpdatedWithName": "User configuration has been updated for {0}",
"UserCreatedWithName": "User {0} has been created",
@ -113,12 +113,12 @@
"MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
"MessageApplicationUpdated": "Emby Server has been updated",
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
"AuthenticationSucceededWithUserName": "{0} successfully authenticated",
"AuthenticationSucceededWithUserName": "{0} autenticat correctament",
"DeviceOfflineWithName": "{0} has disconnected",
"UserLockedOutWithName": "User {0} has been locked out",
"UserOfflineFromDevice": "{0} has disconnected from {1}",
"UserStartedPlayingItemWithValues": "{0} has started playing {1}",
"UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}",
"UserStartedPlayingItemWithValues": "{0} ha comen\u00e7at a reproduir {1}",
"UserStoppedPlayingItemWithValues": "{0} ha parat de reproduir {1}",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"HeaderUnidentified": "Unidentified",
"HeaderImagePrimary": "Primary",
@ -164,7 +164,7 @@
"HeaderTracks": "Tracks",
"HeaderMusicArtist": "M\u00fasic",
"HeaderLocked": "Locked",
"HeaderStudios": "Studios",
"HeaderStudios": "Estudis",
"HeaderActor": "Actors",
"HeaderComposer": "Compositors",
"HeaderDirector": "Directors",

@ -63,7 +63,7 @@
"ViewTypeLatestGames": "Nieuwste games",
"ViewTypeRecentlyPlayedGames": "Recent gespeelt",
"ViewTypeGameFavorites": "Favorieten",
"ViewTypeGameSystems": "Gam systemen",
"ViewTypeGameSystems": "Game systemen",
"ViewTypeGameGenres": "Genres",
"ViewTypeTvResume": "Hervatten",
"ViewTypeTvNextUp": "Volgende",
@ -147,7 +147,7 @@
"HeaderCommunityRating": "Gemeenschap cijfer",
"HeaderTrailers": "Trailers",
"HeaderSpecials": "Specials",
"HeaderGameSystems": "Spel systemen",
"HeaderGameSystems": "Game systemen",
"HeaderPlayers": "Spelers:",
"HeaderAlbumArtists": "Album artiesten",
"HeaderAlbums": "Albums",

@ -48,9 +48,9 @@
<Reference Include="Interfaces.IO">
<HintPath>..\packages\Interfaces.IO.1.0.0.5\lib\portable-net45+sl4+wp71+win8+wpa81\Interfaces.IO.dll</HintPath>
</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>
<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 Include="MoreLinq">
<HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath>

@ -1,6 +1,5 @@
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Notifications;
@ -17,12 +16,13 @@ using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using CommonIO;
using MediaBrowser.Common.Threading;
namespace MediaBrowser.Server.Implementations.News
{
public class NewsEntryPoint : IServerEntryPoint
{
private Timer _timer;
private PeriodicTimer _timer;
private readonly IHttpClient _httpClient;
private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem;
@ -47,7 +47,7 @@ namespace MediaBrowser.Server.Implementations.News
public void Run()
{
_timer = new Timer(OnTimerFired, null, TimeSpan.FromMilliseconds(500), _frequency);
_timer = new PeriodicTimer(OnTimerFired, null, TimeSpan.FromMilliseconds(500), _frequency);
}
/// <summary>

@ -2,7 +2,7 @@
<packages>
<package id="CommonIO" version="1.0.0.7" 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="morelinq" version="1.4.0" 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.Reflection;
using System.Text.RegularExpressions;
using MediaBrowser.Controller.Power;
namespace MediaBrowser.Server.Mono.Native
{
@ -203,5 +204,18 @@ namespace MediaBrowser.Server.Mono.Native
public string sysname = 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;
internal INativeApp NativeApp { get; set; }
private Timer _ipAddressCacheTimer;
/// <summary>
/// Initializes a new instance of the <see cref="ApplicationHost" /> class.
@ -234,8 +233,6 @@ namespace MediaBrowser.Server.Startup.Common
NativeApp = nativeApp;
SetBaseExceptionMessage();
_ipAddressCacheTimer = new Timer(OnCacheClearTimerFired, null, TimeSpan.FromMinutes(3), TimeSpan.FromMinutes(3));
}
private Version _version;
@ -533,6 +530,8 @@ namespace MediaBrowser.Server.Startup.Common
EncodingManager = new EncodingManager(FileSystemManager, Logger, MediaEncoder, ChapterManager);
RegisterSingleInstance(EncodingManager);
RegisterSingleInstance(NativeApp.GetPowerManagement());
var sharingRepo = new SharingRepository(LogManager, ApplicationPaths);
await sharingRepo.Initialize().ConfigureAwait(false);
RegisterSingleInstance<ISharingManager>(new SharingManager(sharingRepo, ServerConfigurationManager, LibraryManager, this));
@ -970,10 +969,10 @@ namespace MediaBrowser.Server.Startup.Common
{
get
{
if (!ServerConfigurationManager.Configuration.EnableAutoUpdate)
{
return false;
}
if (!ServerConfigurationManager.Configuration.EnableAutoUpdate)
{
return false;
}
#if DEBUG
return false;
#endif
@ -1157,7 +1156,12 @@ namespace MediaBrowser.Server.Startup.Common
}
private readonly ConcurrentDictionary<string, bool> _validAddressResults = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
private DateTime _lastAddressCacheClear;
private bool IsIpAddressValid(IPAddress address)
{
return IsIpAddressValidInternal(address).Result;
}
private async Task<bool> IsIpAddressValidInternal(IPAddress address)
{
if (IPAddress.IsLoopback(address))
{
@ -1167,6 +1171,12 @@ namespace MediaBrowser.Server.Startup.Common
var apiUrl = GetLocalApiUrl(address.ToString());
apiUrl += "/system/ping";
if ((DateTime.UtcNow - _lastAddressCacheClear).TotalMinutes >= 5)
{
_lastAddressCacheClear = DateTime.UtcNow;
_validAddressResults.Clear();
}
bool cachedResult;
if (_validAddressResults.TryGetValue(apiUrl, out cachedResult))
{
@ -1175,14 +1185,15 @@ namespace MediaBrowser.Server.Startup.Common
try
{
using (var response = HttpClient.SendAsync(new HttpRequestOptions
using (var response = await HttpClient.SendAsync(new HttpRequestOptions
{
Url = apiUrl,
LogErrorResponseBody = false,
LogErrors = false,
LogRequest = false
LogRequest = false,
TimeoutMs = 30000
}, "POST").Result)
}, "POST").ConfigureAwait(false))
{
using (var reader = new StreamReader(response.Content))
{
@ -1190,25 +1201,20 @@ namespace MediaBrowser.Server.Startup.Common
var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase);
_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;
}
}
}
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);
return false;
}
}
private void OnCacheClearTimerFired(object state)
{
_validAddressResults.Clear();
}
public string FriendlyName
{
get

@ -4,7 +4,7 @@ using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Logging;
using System;
using System.Linq;
using System.Threading;
using MediaBrowser.Common.Threading;
namespace MediaBrowser.Server.Startup.Common.EntryPoints
{
@ -12,7 +12,7 @@ namespace MediaBrowser.Server.Startup.Common.EntryPoints
{
private readonly ISessionManager _sessionManager;
private readonly ILogger _logger;
private Timer _timer;
private PeriodicTimer _timer;
private readonly IServerApplicationHost _appHost;
public KeepServerAwake(ISessionManager sessionManager, ILogger logger, IServerApplicationHost appHost)
@ -24,7 +24,7 @@ namespace MediaBrowser.Server.Startup.Common.EntryPoints
public void Run()
{
_timer = new Timer(obj =>
_timer = new PeriodicTimer(obj =>
{
var now = DateTime.UtcNow;
if (_sessionManager.Sessions.Any(i => (now - i.LastActivityDate).TotalMinutes < 15))

@ -2,6 +2,7 @@
using MediaBrowser.Model.Logging;
using System.Collections.Generic;
using System.Reflection;
using MediaBrowser.Controller.Power;
namespace MediaBrowser.Server.Startup.Common
{
@ -90,5 +91,11 @@ namespace MediaBrowser.Server.Startup.Common
/// Prevents the system stand by.
/// </summary>
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")));
fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
var nativeApp = new WindowsApp(fileSystem)
var nativeApp = new WindowsApp(fileSystem, _logger)
{
IsRunningAsService = runService
};

@ -120,6 +120,7 @@
<Compile Include="Native\Standby.cs" />
<Compile Include="Native\ServerAuthorization.cs" />
<Compile Include="Native\WindowsApp.cs" />
<Compile Include="Native\WindowsPowerManagement.cs" />
<Compile Include="Networking\CertificateGenerator.cs" />
<Compile Include="Networking\NativeMethods.cs" />
<Compile Include="Networking\NetworkManager.cs" />

@ -6,16 +6,19 @@ using MediaBrowser.ServerApplication.Networking;
using System.Collections.Generic;
using System.Reflection;
using CommonIO;
using MediaBrowser.Controller.Power;
namespace MediaBrowser.ServerApplication.Native
{
public class WindowsApp : INativeApp
{
private readonly IFileSystem _fileSystem;
private readonly ILogger _logger;
public WindowsApp(IFileSystem fileSystem)
public WindowsApp(IFileSystem fileSystem, ILogger logger)
{
_fileSystem = fileSystem;
_logger = logger;
}
public List<Assembly> GetAssembliesWithParts()
@ -117,5 +120,10 @@ namespace MediaBrowser.ServerApplication.Native
{
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, "Sortable"), "meteor");
DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "st");
DeleteFoldersByName(Path.Combine(bowerPath, "swipebox"), "lib");
DeleteFoldersByName(Path.Combine(bowerPath, "swipebox"), "scss");
DeleteFoldersByName(Path.Combine(bowerPath, "Swiper"), "src");
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
{

@ -158,9 +158,6 @@
<Content Include="dashboard-ui\components\metadataeditor\metadataeditor.template.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\components\paperdialoghelper.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\components\playlisteditor\playlisteditor.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -311,9 +308,6 @@
<Content Include="dashboard-ui\scripts\supporterkeypage.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\components\testermessage.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\wizardlivetvguide.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

@ -215,7 +215,11 @@ namespace MediaBrowser.XbmcMetadata.Parsers
if (!string.IsNullOrWhiteSpace(val))
{
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();
}
@ -627,7 +631,10 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
var person = GetPersonFromXmlNode(subtree);
itemResult.AddPerson(person);
if (!string.IsNullOrWhiteSpace(person.Name))
{
itemResult.AddPerson(person);
}
}
break;
}
@ -976,11 +983,11 @@ namespace MediaBrowser.XbmcMetadata.Parsers
if (!string.IsNullOrWhiteSpace(val) && !string.IsNullOrWhiteSpace(userDataUserId))
{
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);
userData.LastPlayedDate = parsedValue;
userData.LastPlayedDate = parsedValue.ToUniversalTime();
}
}
break;

@ -416,6 +416,8 @@ namespace MediaBrowser.XbmcMetadata.Savers
writer.WriteEndElement();
}
public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss";
/// <summary>
/// Adds the common nodes.
/// </summary>
@ -472,7 +474,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
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("originaltitle", item.Name ?? string.Empty);
@ -949,7 +951,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
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");

Loading…
Cancel
Save