瀏覽代碼

update recording saving

Luke Pulverenti 9 年之前
父節點
當前提交
33c002684e

+ 0 - 54
MediaBrowser.Api/Library/LibraryHelpers.cs

@@ -1,54 +0,0 @@
-using MediaBrowser.Controller;
-using System;
-using System.IO;
-using System.Linq;
-using CommonIO;
-
-namespace MediaBrowser.Api.Library
-{
-    /// <summary>
-    /// Class LibraryHelpers
-    /// </summary>
-    public static class LibraryHelpers
-    {
-        /// <summary>
-        /// The shortcut file extension
-        /// </summary>
-        private const string ShortcutFileExtension = ".mblink";
-        /// <summary>
-        /// The shortcut file search
-        /// </summary>
-        private const string ShortcutFileSearch = "*" + ShortcutFileExtension;
-
-        /// <summary>
-        /// Deletes a shortcut from within a virtual folder, within either the default view or a user view
-        /// </summary>
-        /// <param name="fileSystem">The file system.</param>
-        /// <param name="virtualFolderName">Name of the virtual folder.</param>
-        /// <param name="mediaPath">The media path.</param>
-        /// <param name="appPaths">The app paths.</param>
-        /// <exception cref="System.IO.DirectoryNotFoundException">The media folder does not exist</exception>
-        public static void RemoveMediaPath(IFileSystem fileSystem, string virtualFolderName, string mediaPath, IServerApplicationPaths appPaths)
-        {
-            if (string.IsNullOrWhiteSpace(mediaPath))
-            {
-                throw new ArgumentNullException("mediaPath");
-            }
-
-            var rootFolderPath = appPaths.DefaultUserViewsPath;
-            var path = Path.Combine(rootFolderPath, virtualFolderName);
-
-            if (!fileSystem.DirectoryExists(path))
-            {
-                throw new DirectoryNotFoundException(string.Format("The media collection {0} does not exist", virtualFolderName));
-            }
-            
-            var shortcut = Directory.EnumerateFiles(path, ShortcutFileSearch, SearchOption.AllDirectories).FirstOrDefault(f => fileSystem.ResolveShortcut(f).Equals(mediaPath, StringComparison.OrdinalIgnoreCase));
-
-            if (!string.IsNullOrEmpty(shortcut))
-            {
-                fileSystem.DeleteFile(shortcut);
-            }
-        }
-    }
-}

+ 2 - 41
MediaBrowser.Api/Library/LibraryStructureService.cs

@@ -268,46 +268,7 @@ namespace MediaBrowser.Api.Library
         /// <param name="request">The request.</param>
         /// <param name="request">The request.</param>
         public void Delete(RemoveVirtualFolder request)
         public void Delete(RemoveVirtualFolder request)
         {
         {
-            if (string.IsNullOrWhiteSpace(request.Name))
-            {
-                throw new ArgumentNullException("request");
-            }
-
-            var rootFolderPath = _appPaths.DefaultUserViewsPath;
-
-            var path = Path.Combine(rootFolderPath, request.Name);
-
-			if (!_fileSystem.DirectoryExists(path))
-            {
-                throw new DirectoryNotFoundException("The media folder does not exist");
-            }
-
-            _libraryMonitor.Stop();
-
-            try
-            {
-                _fileSystem.DeleteDirectory(path, true);
-            }
-            finally
-            {
-                Task.Run(() =>
-                {
-                    // No need to start if scanning the library because it will handle it
-                    if (request.RefreshLibrary)
-                    {
-                        _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None);
-                    }
-                    else
-                    {
-                        // Need to add a delay here or directory watchers may still pick up the changes
-                        var task = Task.Delay(1000);
-                        // Have to block here to allow exceptions to bubble
-                        Task.WaitAll(task);
-
-                        _libraryMonitor.Start();
-                    }
-                });
-            }
+            _libraryManager.RemoveVirtualFolder(request.Name, request.RefreshLibrary);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -364,7 +325,7 @@ namespace MediaBrowser.Api.Library
 
 
             try
             try
             {
             {
-                LibraryHelpers.RemoveMediaPath(_fileSystem, request.Name, request.Path, _appPaths);
+                _libraryManager.RemoveMediaPath(request.Name, request.Path);
             }
             }
             finally
             finally
             {
             {

+ 0 - 1
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -129,7 +129,6 @@
     <Compile Include="ItemUpdateService.cs" />
     <Compile Include="ItemUpdateService.cs" />
     <Compile Include="Library\LibraryService.cs" />
     <Compile Include="Library\LibraryService.cs" />
     <Compile Include="Library\FileOrganizationService.cs" />
     <Compile Include="Library\FileOrganizationService.cs" />
-    <Compile Include="Library\LibraryHelpers.cs" />
     <Compile Include="Library\LibraryStructureService.cs" />
     <Compile Include="Library\LibraryStructureService.cs" />
     <Compile Include="LiveTv\LiveTvService.cs" />
     <Compile Include="LiveTv\LiveTvService.cs" />
     <Compile Include="LocalizationService.cs" />
     <Compile Include="LocalizationService.cs" />

+ 2 - 0
MediaBrowser.Controller/Library/ILibraryManager.cs

@@ -571,6 +571,8 @@ namespace MediaBrowser.Controller.Library
         bool IgnoreFile(FileSystemMetadata file, BaseItem parent);
         bool IgnoreFile(FileSystemMetadata file, BaseItem parent);
 
 
         void AddVirtualFolder(string name, string collectionType, string[] mediaPaths, bool refreshLibrary);
         void AddVirtualFolder(string name, string collectionType, string[] mediaPaths, bool refreshLibrary);
+        void RemoveVirtualFolder(string name, bool refreshLibrary);
         void AddMediaPath(string virtualFolderName, string path);
         void AddMediaPath(string virtualFolderName, string path);
+        void RemoveMediaPath(string virtualFolderName, string path);
     }
     }
 }
 }

+ 4 - 0
MediaBrowser.Model/LiveTv/LiveTvOptions.cs

@@ -7,8 +7,11 @@ namespace MediaBrowser.Model.LiveTv
         public int? GuideDays { get; set; }
         public int? GuideDays { get; set; }
         public bool EnableMovieProviders { get; set; }
         public bool EnableMovieProviders { get; set; }
         public string RecordingPath { get; set; }
         public string RecordingPath { get; set; }
+        public string MovieRecordingPath { get; set; }
+        public string SeriesRecordingPath { get; set; }
         public bool EnableAutoOrganize { get; set; }
         public bool EnableAutoOrganize { get; set; }
         public bool EnableRecordingEncoding { get; set; }
         public bool EnableRecordingEncoding { get; set; }
+        public bool EnableRecordingSubfolders { get; set; }
         public bool EnableOriginalAudioWithEncodedRecordings { get; set; }
         public bool EnableOriginalAudioWithEncodedRecordings { get; set; }
 
 
         public List<TunerHostInfo> TunerHosts { get; set; }
         public List<TunerHostInfo> TunerHosts { get; set; }
@@ -20,6 +23,7 @@ namespace MediaBrowser.Model.LiveTv
         public LiveTvOptions()
         public LiveTvOptions()
         {
         {
             EnableMovieProviders = true;
             EnableMovieProviders = true;
+            EnableRecordingSubfolders = true;
             TunerHosts = new List<TunerHostInfo>();
             TunerHosts = new List<TunerHostInfo>();
             ListingProviders = new List<ListingsProviderInfo>();
             ListingProviders = new List<ListingsProviderInfo>();
         }
         }

+ 68 - 0
MediaBrowser.Server.Implementations/Library/LibraryManager.cs

@@ -2640,7 +2640,52 @@ namespace MediaBrowser.Server.Implementations.Library
             }
             }
         }
         }
 
 
+        public void RemoveVirtualFolder(string name, bool refreshLibrary)
+        {
+            if (string.IsNullOrWhiteSpace(name))
+            {
+                throw new ArgumentNullException("name");
+            }
+
+            var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+
+            var path = Path.Combine(rootFolderPath, name);
+
+            if (!_fileSystem.DirectoryExists(path))
+            {
+                throw new DirectoryNotFoundException("The media folder does not exist");
+            }
+
+            _libraryMonitorFactory().Stop();
+
+            try
+            {
+                _fileSystem.DeleteDirectory(path, true);
+            }
+            finally
+            {
+                Task.Run(() =>
+                {
+                    // No need to start if scanning the library because it will handle it
+                    if (refreshLibrary)
+                    {
+                        ValidateMediaLibrary(new Progress<double>(), CancellationToken.None);
+                    }
+                    else
+                    {
+                        // Need to add a delay here or directory watchers may still pick up the changes
+                        var task = Task.Delay(1000);
+                        // Have to block here to allow exceptions to bubble
+                        Task.WaitAll(task);
+
+                        _libraryMonitorFactory().Start();
+                    }
+                });
+            }
+        }
+
         private const string ShortcutFileExtension = ".mblink";
         private const string ShortcutFileExtension = ".mblink";
+        private const string ShortcutFileSearch = "*" + ShortcutFileExtension;
         public void AddMediaPath(string virtualFolderName, string path)
         public void AddMediaPath(string virtualFolderName, string path)
         {
         {
             if (string.IsNullOrWhiteSpace(path))
             if (string.IsNullOrWhiteSpace(path))
@@ -2668,5 +2713,28 @@ namespace MediaBrowser.Server.Implementations.Library
 
 
             _fileSystem.CreateShortcut(lnk, path);
             _fileSystem.CreateShortcut(lnk, path);
         }
         }
+
+        public void RemoveMediaPath(string virtualFolderName, string mediaPath)
+        {
+            if (string.IsNullOrWhiteSpace(mediaPath))
+            {
+                throw new ArgumentNullException("mediaPath");
+            }
+
+            var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+            var path = Path.Combine(rootFolderPath, virtualFolderName);
+
+            if (!_fileSystem.DirectoryExists(path))
+            {
+                throw new DirectoryNotFoundException(string.Format("The media collection {0} does not exist", virtualFolderName));
+            }
+
+            var shortcut = Directory.EnumerateFiles(path, ShortcutFileSearch, SearchOption.AllDirectories).FirstOrDefault(f => _fileSystem.ResolveShortcut(f).Equals(mediaPath, StringComparison.OrdinalIgnoreCase));
+
+            if (!string.IsNullOrEmpty(shortcut))
+            {
+                _fileSystem.DeleteFile(shortcut);
+            }
+        }
     }
     }
 }
 }

+ 327 - 185
MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs

@@ -26,7 +26,10 @@ using System.Linq;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using CommonIO;
 using CommonIO;
+using MediaBrowser.Common.Events;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Power;
 using MediaBrowser.Controller.Power;
 using Microsoft.Win32;
 using Microsoft.Win32;
 
 
@@ -40,7 +43,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
         private readonly IServerConfigurationManager _config;
         private readonly IServerConfigurationManager _config;
         private readonly IJsonSerializer _jsonSerializer;
         private readonly IJsonSerializer _jsonSerializer;
 
 
-        private readonly ItemDataProvider<RecordingInfo> _recordingProvider;
         private readonly ItemDataProvider<SeriesTimerInfo> _seriesTimerProvider;
         private readonly ItemDataProvider<SeriesTimerInfo> _seriesTimerProvider;
         private readonly TimerManager _timerProvider;
         private readonly TimerManager _timerProvider;
 
 
@@ -56,6 +58,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
 
         public static EmbyTV Current;
         public static EmbyTV Current;
 
 
+        public event EventHandler DataSourceChanged;
+        public event EventHandler<RecordingStatusChangedEventArgs> RecordingStatusChanged;
+
+        private readonly ConcurrentDictionary<string, ActiveRecordingInfo> _activeRecordings =
+            new ConcurrentDictionary<string, ActiveRecordingInfo>(StringComparer.OrdinalIgnoreCase);
+
         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)
         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;
@@ -74,10 +82,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             _liveTvManager = (LiveTvManager)liveTvManager;
             _liveTvManager = (LiveTvManager)liveTvManager;
             _jsonSerializer = jsonSerializer;
             _jsonSerializer = jsonSerializer;
 
 
-            _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"), powerManagement, _logger);
             _timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), powerManagement, _logger);
             _timerProvider.TimerFired += _timerProvider_TimerFired;
             _timerProvider.TimerFired += _timerProvider_TimerFired;
+
+            _config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated;
+        }
+
+        private void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
+        {
+            if (string.Equals(e.Key, "livetv", StringComparison.OrdinalIgnoreCase))
+            {
+                OnRecordingFoldersChanged();
+            }
         }
         }
 
 
         public void Start()
         public void Start()
@@ -85,6 +102,95 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             _timerProvider.RestartTimers();
             _timerProvider.RestartTimers();
 
 
             SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
             SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
+
+            CreateRecordingFolders();
+        }
+
+        private void OnRecordingFoldersChanged()
+        {
+            CreateRecordingFolders();
+        }
+
+        private void CreateRecordingFolders()
+        {
+            var recordingFolders = GetRecordingFolders();
+
+            var defaultRecordingPath = DefaultRecordingPath;
+            if (!recordingFolders.Any(i => i.Locations.Contains(defaultRecordingPath, StringComparer.OrdinalIgnoreCase)))
+            {
+                RemovePathFromLibrary(defaultRecordingPath);
+            }
+
+            var virtualFolders = _libraryManager.GetVirtualFolders()
+                .ToList();
+
+            var allExistingPaths = virtualFolders.SelectMany(i => i.Locations).ToList();
+
+            foreach (var recordingFolder in recordingFolders)
+            {
+                var pathsToCreate = recordingFolder.Locations
+                    .Where(i => !allExistingPaths.Contains(i, StringComparer.OrdinalIgnoreCase))
+                    .ToList();
+
+                if (pathsToCreate.Count == 0)
+                {
+                    continue;
+                }
+
+                try
+                {
+                    _libraryManager.AddVirtualFolder(recordingFolder.Name, recordingFolder.CollectionType, pathsToCreate.ToArray(), true);
+                }
+                catch (Exception ex)
+                {
+                    _logger.ErrorException("Error creating virtual folder", ex);
+                }
+            }
+        }
+
+        private void RemovePathFromLibrary(string path)
+        {
+            var requiresRefresh = false;
+            var virtualFolders = _libraryManager.GetVirtualFolders()
+               .ToList();
+
+            foreach (var virtualFolder in virtualFolders)
+            {
+                if (!virtualFolder.Locations.Contains(path, StringComparer.OrdinalIgnoreCase))
+                {
+                    continue;
+                }
+
+                if (virtualFolder.Locations.Count == 1)
+                {
+                    // remove entire virtual folder
+                    try
+                    {
+                        _libraryManager.RemoveVirtualFolder(virtualFolder.Name, true);
+                    }
+                    catch (Exception ex)
+                    {
+                        _logger.ErrorException("Error removing virtual folder", ex);
+                    }
+                }
+                else
+                {
+                    try
+                    {
+                        _libraryManager.RemoveMediaPath(virtualFolder.Name, path);
+                        requiresRefresh = true;
+                    }
+                    catch (Exception ex)
+                    {
+                        _logger.ErrorException("Error removing media path", ex);
+                    }
+                }
+            }
+
+            if (requiresRefresh)
+            {
+                _libraryManager.ValidateMediaLibrary(new Progress<Double>(), CancellationToken.None);
+            }
         }
         }
 
 
         void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
         void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
@@ -97,13 +203,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             }
             }
         }
         }
 
 
-        public event EventHandler DataSourceChanged;
-
-        public event EventHandler<RecordingStatusChangedEventArgs> RecordingStatusChanged;
-
-        private readonly ConcurrentDictionary<string, ActiveRecordingInfo> _activeRecordings =
-            new ConcurrentDictionary<string, ActiveRecordingInfo>(StringComparer.OrdinalIgnoreCase);
-
         public string Name
         public string Name
         {
         {
             get { return "Emby"; }
             get { return "Emby"; }
@@ -114,6 +213,26 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             get { return Path.Combine(_config.CommonApplicationPaths.DataPath, "livetv"); }
             get { return Path.Combine(_config.CommonApplicationPaths.DataPath, "livetv"); }
         }
         }
 
 
+        private string DefaultRecordingPath
+        {
+            get
+            {
+                return Path.Combine(DataPath, "recordings");
+            }
+        }
+
+        private string RecordingPath
+        {
+            get
+            {
+                var path = GetConfiguration().RecordingPath;
+
+                return string.IsNullOrWhiteSpace(path)
+                    ? DefaultRecordingPath
+                    : path;
+            }
+        }
+
         public string HomePageUrl
         public string HomePageUrl
         {
         {
             get { return "http://emby.media"; }
             get { return "http://emby.media"; }
@@ -280,49 +399,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             return Task.FromResult(true);
             return Task.FromResult(true);
         }
         }
 
 
-        public async Task DeleteRecordingAsync(string recordingId, CancellationToken cancellationToken)
+        public Task DeleteRecordingAsync(string recordingId, CancellationToken cancellationToken)
         {
         {
-            var remove = _recordingProvider.GetAll().FirstOrDefault(i => string.Equals(i.Id, recordingId, StringComparison.OrdinalIgnoreCase));
-            if (remove != null)
-            {
-                if (!string.IsNullOrWhiteSpace(remove.TimerId))
-                {
-                    var enableDelay = _activeRecordings.ContainsKey(remove.TimerId);
-
-                    CancelTimerInternal(remove.TimerId);
-
-                    if (enableDelay)
-                    {
-                        // A hack yes, but need to make sure the file is closed before attempting to delete it
-                        await Task.Delay(3000, cancellationToken).ConfigureAwait(false);
-                    }
-                }
-
-                if (!string.IsNullOrWhiteSpace(remove.Path))
-                {
-                    try
-                    {
-                        _fileSystem.DeleteFile(remove.Path);
-                    }
-                    catch (DirectoryNotFoundException)
-                    {
-
-                    }
-                    catch (FileNotFoundException)
-                    {
-
-                    }
-                    catch (Exception ex)
-                    {
-                        _logger.ErrorException("Error deleting recording file {0}", ex, remove.Path);
-                    }
-                }
-                _recordingProvider.Delete(remove);
-            }
-            else
-            {
-                throw new ResourceNotFoundException("Recording not found: " + recordingId);
-            }
+            return Task.FromResult(true);
         }
         }
 
 
         public Task CreateTimerAsync(TimerInfo info, CancellationToken cancellationToken)
         public Task CreateTimerAsync(TimerInfo info, CancellationToken cancellationToken)
@@ -424,29 +503,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
 
         public async Task<IEnumerable<RecordingInfo>> GetRecordingsAsync(CancellationToken cancellationToken)
         public async Task<IEnumerable<RecordingInfo>> GetRecordingsAsync(CancellationToken cancellationToken)
         {
         {
-            var recordings = _recordingProvider.GetAll().ToList();
-            var updated = false;
-
-            foreach (var recording in recordings)
-            {
-                if (recording.Status == RecordingStatus.InProgress)
-                {
-                    if (string.IsNullOrWhiteSpace(recording.TimerId) || !_activeRecordings.ContainsKey(recording.TimerId))
-                    {
-                        recording.Status = RecordingStatus.Cancelled;
-                        recording.DateLastUpdated = DateTime.UtcNow;
-                        _recordingProvider.Update(recording);
-                        updated = true;
-                    }
-                }
-            }
-
-            if (updated)
-            {
-                recordings = _recordingProvider.GetAll().ToList();
-            }
-
-            return recordings;
+            return new List<RecordingInfo>();
         }
         }
 
 
         public Task<IEnumerable<TimerInfo>> GetTimersAsync(CancellationToken cancellationToken)
         public Task<IEnumerable<TimerInfo>> GetTimersAsync(CancellationToken cancellationToken)
@@ -695,104 +752,124 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             }
             }
         }
         }
 
 
-        private async Task RecordStream(TimerInfo timer, DateTime recordingEndDate, ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken)
+        private string GetRecordingPath(TimerInfo timer, ProgramInfo info)
         {
         {
-            if (timer == null)
-            {
-                throw new ArgumentNullException("timer");
-            }
-
-            ProgramInfo info = null;
-
-            if (string.IsNullOrWhiteSpace(timer.ProgramId))
-            {
-                _logger.Info("Timer {0} has null programId", timer.Id);
-            }
-            else
-            {
-                info = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId);
-            }
-
-            if (info == null)
-            {
-                _logger.Info("Unable to find program with Id {0}. Will search using start date", timer.ProgramId);
-                info = GetProgramInfoFromCache(timer.ChannelId, timer.StartDate);
-            }
-
-            if (info == null)
-            {
-                throw new InvalidOperationException(string.Format("Program with Id {0} not found", timer.ProgramId));
-            }
-
             var recordPath = RecordingPath;
             var recordPath = RecordingPath;
+            var config = GetConfiguration();
 
 
             if (info.IsMovie)
             if (info.IsMovie)
             {
             {
-                recordPath = Path.Combine(recordPath, "Movies", _fileSystem.GetValidFilename(info.Name).Trim());
+                var customRecordingPath = config.MovieRecordingPath;
+                if ((string.IsNullOrWhiteSpace(customRecordingPath) || string.Equals(customRecordingPath, recordPath, StringComparison.OrdinalIgnoreCase)) && config.EnableRecordingSubfolders)
+                {
+                    recordPath = Path.Combine(recordPath, "Movies");
+                }
+
+                var folderName = _fileSystem.GetValidFilename(info.Name).Trim();
+                if (info.ProductionYear.HasValue)
+                {
+                    folderName += " (" + info.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")";
+                }
+                recordPath = Path.Combine(recordPath, folderName);
             }
             }
             else if (info.IsSeries)
             else if (info.IsSeries)
             {
             {
-                recordPath = Path.Combine(recordPath, "Series", _fileSystem.GetValidFilename(info.Name).Trim());
+                var customRecordingPath = config.SeriesRecordingPath;
+                if ((string.IsNullOrWhiteSpace(customRecordingPath) || string.Equals(customRecordingPath, recordPath, StringComparison.OrdinalIgnoreCase)) && config.EnableRecordingSubfolders)
+                {
+                    recordPath = Path.Combine(recordPath, "Series");
+                }
+
+                var folderName = _fileSystem.GetValidFilename(info.Name).Trim();
+                var folderNameWithYear = folderName;
+                if (info.ProductionYear.HasValue)
+                {
+                    folderNameWithYear += " (" + info.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")";
+                }
+
+                if (Directory.Exists(Path.Combine(recordPath, folderName)))
+                {
+                    recordPath = Path.Combine(recordPath, folderName);
+                }
+                else
+                {
+                    recordPath = Path.Combine(recordPath, folderNameWithYear);
+                }
 
 
                 if (info.SeasonNumber.HasValue)
                 if (info.SeasonNumber.HasValue)
                 {
                 {
-                    var folderName = string.Format("Season {0}", info.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture));
+                    folderName = string.Format("Season {0}", info.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture));
                     recordPath = Path.Combine(recordPath, folderName);
                     recordPath = Path.Combine(recordPath, folderName);
                 }
                 }
             }
             }
             else if (info.IsKids)
             else if (info.IsKids)
             {
             {
-                recordPath = Path.Combine(recordPath, "Kids", _fileSystem.GetValidFilename(info.Name).Trim());
+                if (config.EnableRecordingSubfolders)
+                {
+                    recordPath = Path.Combine(recordPath, "Kids");
+                }
+
+                var folderName = _fileSystem.GetValidFilename(info.Name).Trim();
+                if (info.ProductionYear.HasValue)
+                {
+                    folderName += " (" + info.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")";
+                }
+                recordPath = Path.Combine(recordPath, folderName);
             }
             }
             else if (info.IsSports)
             else if (info.IsSports)
             {
             {
-                recordPath = Path.Combine(recordPath, "Sports", _fileSystem.GetValidFilename(info.Name).Trim());
+                if (config.EnableRecordingSubfolders)
+                {
+                    recordPath = Path.Combine(recordPath, "Sports");
+                }
+                recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(info.Name).Trim());
             }
             }
             else
             else
             {
             {
-                recordPath = Path.Combine(recordPath, "Other", _fileSystem.GetValidFilename(info.Name).Trim());
+                if (config.EnableRecordingSubfolders)
+                {
+                    recordPath = Path.Combine(recordPath, "Other");
+                }
+                recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(info.Name).Trim());
             }
             }
 
 
             var recordingFileName = _fileSystem.GetValidFilename(RecordingHelper.GetRecordingName(timer, info)).Trim() + ".ts";
             var recordingFileName = _fileSystem.GetValidFilename(RecordingHelper.GetRecordingName(timer, info)).Trim() + ".ts";
 
 
-            recordPath = Path.Combine(recordPath, recordingFileName);
+            return Path.Combine(recordPath, recordingFileName);
+        }
 
 
-            var recordingId = info.Id.GetMD5().ToString("N");
-            var recording = _recordingProvider.GetAll().FirstOrDefault(x => string.Equals(x.Id, recordingId, StringComparison.OrdinalIgnoreCase));
+        private async Task RecordStream(TimerInfo timer, DateTime recordingEndDate, ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken)
+        {
+            if (timer == null)
+            {
+                throw new ArgumentNullException("timer");
+            }
 
 
-            if (recording == null)
+            ProgramInfo info = null;
+
+            if (string.IsNullOrWhiteSpace(timer.ProgramId))
             {
             {
-                recording = new RecordingInfo
-                {
-                    ChannelId = info.ChannelId,
-                    Id = recordingId,
-                    StartDate = info.StartDate,
-                    EndDate = info.EndDate,
-                    Genres = info.Genres,
-                    IsKids = info.IsKids,
-                    IsLive = info.IsLive,
-                    IsMovie = info.IsMovie,
-                    IsHD = info.IsHD,
-                    IsNews = info.IsNews,
-                    IsPremiere = info.IsPremiere,
-                    IsSeries = info.IsSeries,
-                    IsSports = info.IsSports,
-                    IsRepeat = !info.IsPremiere,
-                    Name = info.Name,
-                    EpisodeTitle = info.EpisodeTitle,
-                    ProgramId = info.Id,
-                    ImagePath = info.ImagePath,
-                    ImageUrl = info.ImageUrl,
-                    OriginalAirDate = info.OriginalAirDate,
-                    Status = RecordingStatus.Scheduled,
-                    Overview = info.Overview,
-                    SeriesTimerId = timer.SeriesTimerId,
-                    TimerId = timer.Id,
-                    ShowId = info.ShowId
-                };
-                _recordingProvider.AddOrUpdate(recording);
+                _logger.Info("Timer {0} has null programId", timer.Id);
+            }
+            else
+            {
+                info = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId);
+            }
+
+            if (info == null)
+            {
+                _logger.Info("Unable to find program with Id {0}. Will search using start date", timer.ProgramId);
+                info = GetProgramInfoFromCache(timer.ChannelId, timer.StartDate);
             }
             }
 
 
+            if (info == null)
+            {
+                throw new InvalidOperationException(string.Format("Program with Id {0} not found", timer.ProgramId));
+            }
+
+            var recordPath = GetRecordingPath(timer, info);
+            var recordingStatus = RecordingStatus.New;
+
             try
             try
             {
             {
                 var result = await GetChannelStreamInternal(timer.ChannelId, null, CancellationToken.None).ConfigureAwait(false);
                 var result = await GetChannelStreamInternal(timer.ChannelId, null, CancellationToken.None).ConfigureAwait(false);
@@ -817,11 +894,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
 
                     _libraryMonitor.ReportFileSystemChangeBeginning(recordPath);
                     _libraryMonitor.ReportFileSystemChangeBeginning(recordPath);
 
 
-                    recording.Path = recordPath;
-                    recording.Status = RecordingStatus.InProgress;
-                    recording.DateLastUpdated = DateTime.UtcNow;
-                    _recordingProvider.AddOrUpdate(recording);
-
                     var duration = recordingEndDate - DateTime.UtcNow;
                     var duration = recordingEndDate - DateTime.UtcNow;
 
 
                     _logger.Info("Beginning recording. Will record for {0} minutes.", duration.TotalMinutes.ToString(CultureInfo.InvariantCulture));
                     _logger.Info("Beginning recording. Will record for {0} minutes.", duration.TotalMinutes.ToString(CultureInfo.InvariantCulture));
@@ -846,7 +918,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
 
                     await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken).ConfigureAwait(false);
                     await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken).ConfigureAwait(false);
 
 
-                    recording.Status = RecordingStatus.Completed;
+                    recordingStatus = RecordingStatus.Completed;
                     _logger.Info("Recording completed: {0}", recordPath);
                     _logger.Info("Recording completed: {0}", recordPath);
                 }
                 }
                 finally
                 finally
@@ -862,12 +934,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             catch (OperationCanceledException)
             catch (OperationCanceledException)
             {
             {
                 _logger.Info("Recording stopped: {0}", recordPath);
                 _logger.Info("Recording stopped: {0}", recordPath);
-                recording.Status = RecordingStatus.Completed;
+                recordingStatus = RecordingStatus.Completed;
             }
             }
             catch (Exception ex)
             catch (Exception ex)
             {
             {
                 _logger.ErrorException("Error recording to {0}", ex, recordPath);
                 _logger.ErrorException("Error recording to {0}", ex, recordPath);
-                recording.Status = RecordingStatus.Error;
+                recordingStatus = RecordingStatus.Error;
             }
             }
             finally
             finally
             {
             {
@@ -875,12 +947,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
                 _activeRecordings.TryRemove(timer.Id, out removed);
                 _activeRecordings.TryRemove(timer.Id, out removed);
             }
             }
 
 
-            recording.DateLastUpdated = DateTime.UtcNow;
-            _recordingProvider.AddOrUpdate(recording);
-
-            if (recording.Status == RecordingStatus.Completed)
+            if (recordingStatus == RecordingStatus.Completed)
             {
             {
-                OnSuccessfulRecording(recording);
+                OnSuccessfulRecording(info.IsSeries, recordPath);
                 _timerProvider.Delete(timer);
                 _timerProvider.Delete(timer);
             }
             }
             else if (DateTime.UtcNow < timer.EndDate)
             else if (DateTime.UtcNow < timer.EndDate)
@@ -893,7 +962,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             else
             else
             {
             {
                 _timerProvider.Delete(timer);
                 _timerProvider.Delete(timer);
-                _recordingProvider.Delete(recording);
             }
             }
         }
         }
 
 
@@ -948,11 +1016,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             return new DirectRecorder(_logger, _httpClient, _fileSystem);
             return new DirectRecorder(_logger, _httpClient, _fileSystem);
         }
         }
 
 
-        private async void OnSuccessfulRecording(RecordingInfo recording)
+        private async void OnSuccessfulRecording(bool isSeries, string path)
         {
         {
             if (GetConfiguration().EnableAutoOrganize)
             if (GetConfiguration().EnableAutoOrganize)
             {
             {
-                if (recording.IsSeries)
+                if (isSeries)
                 {
                 {
                     try
                     try
                     {
                     {
@@ -962,12 +1030,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
 
                         var organize = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, _libraryMonitor, _providerManager);
                         var organize = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, _libraryMonitor, _providerManager);
 
 
-                        var result = await organize.OrganizeEpisodeFile(recording.Path, CancellationToken.None).ConfigureAwait(false);
-
-                        if (result.Status == FileSortingStatus.Success)
-                        {
-                            _recordingProvider.Delete(recording);
-                        }
+                        var result = await organize.OrganizeEpisodeFile(path, CancellationToken.None).ConfigureAwait(false);
                     }
                     }
                     catch (Exception ex)
                     catch (Exception ex)
                     {
                     {
@@ -991,18 +1054,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             return epgData.FirstOrDefault(p => Math.Abs(startDateTicks - p.StartDate.Ticks) <= TimeSpan.FromMinutes(3).Ticks);
             return epgData.FirstOrDefault(p => Math.Abs(startDateTicks - p.StartDate.Ticks) <= TimeSpan.FromMinutes(3).Ticks);
         }
         }
 
 
-        private string RecordingPath
-        {
-            get
-            {
-                var path = GetConfiguration().RecordingPath;
-
-                return string.IsNullOrWhiteSpace(path)
-                    ? Path.Combine(DataPath, "recordings")
-                    : path;
-            }
-        }
-
         private LiveTvOptions GetConfiguration()
         private LiveTvOptions GetConfiguration()
         {
         {
             return _config.GetConfiguration<LiveTvOptions>("livetv");
             return _config.GetConfiguration<LiveTvOptions>("livetv");
@@ -1010,7 +1061,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
 
         private async Task UpdateTimersForSeriesTimer(List<ProgramInfo> epgData, SeriesTimerInfo seriesTimer, bool deleteInvalidTimers)
         private async Task UpdateTimersForSeriesTimer(List<ProgramInfo> epgData, SeriesTimerInfo seriesTimer, bool deleteInvalidTimers)
         {
         {
-            var newTimers = GetTimersForSeries(seriesTimer, epgData, _recordingProvider.GetAll()).ToList();
+            var newTimers = GetTimersForSeries(seriesTimer, epgData, true).ToList();
 
 
             var registration = await GetRegistrationInfo("seriesrecordings").ConfigureAwait(false);
             var registration = await GetRegistrationInfo("seriesrecordings").ConfigureAwait(false);
 
 
@@ -1024,7 +1075,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
 
             if (deleteInvalidTimers)
             if (deleteInvalidTimers)
             {
             {
-                var allTimers = GetTimersForSeries(seriesTimer, epgData, new List<RecordingInfo>())
+                var allTimers = GetTimersForSeries(seriesTimer, epgData, false)
                     .Select(i => i.Id)
                     .Select(i => i.Id)
                     .ToList();
                     .ToList();
 
 
@@ -1040,7 +1091,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             }
             }
         }
         }
 
 
-        private IEnumerable<TimerInfo> GetTimersForSeries(SeriesTimerInfo seriesTimer, IEnumerable<ProgramInfo> allPrograms, IReadOnlyList<RecordingInfo> currentRecordings)
+        private IEnumerable<TimerInfo> GetTimersForSeries(SeriesTimerInfo seriesTimer,
+            IEnumerable<ProgramInfo> allPrograms,
+            bool filterByCurrentRecordings)
         {
         {
             if (seriesTimer == null)
             if (seriesTimer == null)
             {
             {
@@ -1050,23 +1103,71 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             {
             {
                 throw new ArgumentNullException("allPrograms");
                 throw new ArgumentNullException("allPrograms");
             }
             }
-            if (currentRecordings == null)
-            {
-                throw new ArgumentNullException("currentRecordings");
-            }
 
 
             // Exclude programs that have already ended
             // Exclude programs that have already ended
             allPrograms = allPrograms.Where(i => i.EndDate > DateTime.UtcNow && i.StartDate > DateTime.UtcNow);
             allPrograms = allPrograms.Where(i => i.EndDate > DateTime.UtcNow && i.StartDate > DateTime.UtcNow);
 
 
             allPrograms = GetProgramsForSeries(seriesTimer, allPrograms);
             allPrograms = GetProgramsForSeries(seriesTimer, allPrograms);
 
 
-            var recordingShowIds = currentRecordings.Select(i => i.ProgramId).Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
-
-            allPrograms = allPrograms.Where(i => !recordingShowIds.Contains(i.Id, StringComparer.OrdinalIgnoreCase));
+            if (filterByCurrentRecordings)
+            {
+                allPrograms = allPrograms.Where(i => !IsProgramAlreadyInLibrary(i));
+            }
 
 
             return allPrograms.Select(i => RecordingHelper.CreateTimer(i, seriesTimer));
             return allPrograms.Select(i => RecordingHelper.CreateTimer(i, seriesTimer));
         }
         }
 
 
+        private bool IsProgramAlreadyInLibrary(ProgramInfo program)
+        {
+            if ((program.EpisodeNumber.HasValue && program.SeasonNumber.HasValue) || !string.IsNullOrWhiteSpace(program.EpisodeTitle))
+            {
+                var seriesIds = _libraryManager.GetItemIds(new InternalItemsQuery
+                {
+                    IncludeItemTypes = new[] { typeof(Series).Name },
+                    Name = program.Name
+
+                }).Select(i => i.ToString("N")).ToArray();
+
+                if (seriesIds.Length == 0)
+                {
+                    return false;
+                }
+
+                if (program.EpisodeNumber.HasValue && program.SeasonNumber.HasValue)
+                {
+                    var result = _libraryManager.GetItemsResult(new InternalItemsQuery
+                    {
+                        IncludeItemTypes = new[] { typeof(Episode).Name },
+                        ParentIndexNumber = program.SeasonNumber.Value,
+                        IndexNumber = program.EpisodeNumber.Value,
+                        AncestorIds = seriesIds
+                    });
+
+                    if (result.TotalRecordCount > 0)
+                    {
+                        return true;
+                    }
+                }
+
+                if (!string.IsNullOrWhiteSpace(program.EpisodeTitle))
+                {
+                    var result = _libraryManager.GetItemsResult(new InternalItemsQuery
+                    {
+                        IncludeItemTypes = new[] { typeof(Episode).Name },
+                        Name = program.EpisodeTitle,
+                        AncestorIds = seriesIds
+                    });
+
+                    if (result.TotalRecordCount > 0)
+                    {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
+
         private IEnumerable<ProgramInfo> GetProgramsForSeries(SeriesTimerInfo seriesTimer, IEnumerable<ProgramInfo> allPrograms)
         private IEnumerable<ProgramInfo> GetProgramsForSeries(SeriesTimerInfo seriesTimer, IEnumerable<ProgramInfo> allPrograms)
         {
         {
             if (!seriesTimer.RecordAnyTime)
             if (!seriesTimer.RecordAnyTime)
@@ -1151,6 +1252,47 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             });
             });
         }
         }
 
 
+        public List<VirtualFolderInfo> GetRecordingFolders()
+        {
+            var list = new List<VirtualFolderInfo>();
+
+            var defaultFolder = RecordingPath;
+            var defaultName = "Recordings";
+
+            if (Directory.Exists(defaultFolder))
+            {
+                list.Add(new VirtualFolderInfo
+                {
+                    Locations = new List<string> { defaultFolder },
+                    Name = defaultName
+                });
+            }
+
+            var customPath = GetConfiguration().MovieRecordingPath;
+            if ((!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase)) && Directory.Exists(customPath))
+            {
+                list.Add(new VirtualFolderInfo
+                {
+                    Locations = new List<string> { customPath },
+                    Name = "Recorded Movies",
+                    CollectionType = CollectionType.Movies
+                });
+            }
+
+            customPath = GetConfiguration().SeriesRecordingPath;
+            if ((!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase)) && Directory.Exists(customPath))
+            {
+                list.Add(new VirtualFolderInfo
+                {
+                    Locations = new List<string> { customPath },
+                    Name = "Recorded Series",
+                    CollectionType = CollectionType.TvShows
+                });
+            }
+
+            return list;
+        }
+
         class ActiveRecordingInfo
         class ActiveRecordingInfo
         {
         {
             public string Path { get; set; }
             public string Path { get; set; }