Selaa lähdekoodia

update recording saving

Luke Pulverenti 9 vuotta sitten
vanhempi
sitoutus
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>
         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>
@@ -364,7 +325,7 @@ namespace MediaBrowser.Api.Library
 
             try
             {
-                LibraryHelpers.RemoveMediaPath(_fileSystem, request.Name, request.Path, _appPaths);
+                _libraryManager.RemoveMediaPath(request.Name, request.Path);
             }
             finally
             {

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

@@ -129,7 +129,6 @@
     <Compile Include="ItemUpdateService.cs" />
     <Compile Include="Library\LibraryService.cs" />
     <Compile Include="Library\FileOrganizationService.cs" />
-    <Compile Include="Library\LibraryHelpers.cs" />
     <Compile Include="Library\LibraryStructureService.cs" />
     <Compile Include="LiveTv\LiveTvService.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);
 
         void AddVirtualFolder(string name, string collectionType, string[] mediaPaths, bool refreshLibrary);
+        void RemoveVirtualFolder(string name, bool refreshLibrary);
         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 bool EnableMovieProviders { get; set; }
         public string RecordingPath { get; set; }
+        public string MovieRecordingPath { get; set; }
+        public string SeriesRecordingPath { get; set; }
         public bool EnableAutoOrganize { get; set; }
         public bool EnableRecordingEncoding { get; set; }
+        public bool EnableRecordingSubfolders { get; set; }
         public bool EnableOriginalAudioWithEncodedRecordings { get; set; }
 
         public List<TunerHostInfo> TunerHosts { get; set; }
@@ -20,6 +23,7 @@ namespace MediaBrowser.Model.LiveTv
         public LiveTvOptions()
         {
             EnableMovieProviders = true;
+            EnableRecordingSubfolders = true;
             TunerHosts = new List<TunerHostInfo>();
             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 ShortcutFileSearch = "*" + ShortcutFileExtension;
         public void AddMediaPath(string virtualFolderName, string path)
         {
             if (string.IsNullOrWhiteSpace(path))
@@ -2668,5 +2713,28 @@ namespace MediaBrowser.Server.Implementations.Library
 
             _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.Tasks;
 using CommonIO;
+using MediaBrowser.Common.Events;
 using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Power;
 using Microsoft.Win32;
 
@@ -40,7 +43,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
         private readonly IServerConfigurationManager _config;
         private readonly IJsonSerializer _jsonSerializer;
 
-        private readonly ItemDataProvider<RecordingInfo> _recordingProvider;
         private readonly ItemDataProvider<SeriesTimerInfo> _seriesTimerProvider;
         private readonly TimerManager _timerProvider;
 
@@ -56,6 +58,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
         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)
         {
             Current = this;
@@ -74,10 +82,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             _liveTvManager = (LiveTvManager)liveTvManager;
             _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"));
             _timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), powerManagement, _logger);
             _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()
@@ -85,6 +102,95 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             _timerProvider.RestartTimers();
 
             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)
@@ -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
         {
             get { return "Emby"; }
@@ -114,6 +213,26 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             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
         {
             get { return "http://emby.media"; }
@@ -280,49 +399,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             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)
@@ -424,29 +503,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
         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)
@@ -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 config = GetConfiguration();
 
             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)
             {
-                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)
                 {
-                    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);
                 }
             }
             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)
             {
-                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
             {
-                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";
 
-            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
             {
                 var result = await GetChannelStreamInternal(timer.ChannelId, null, CancellationToken.None).ConfigureAwait(false);
@@ -817,11 +894,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
                     _libraryMonitor.ReportFileSystemChangeBeginning(recordPath);
 
-                    recording.Path = recordPath;
-                    recording.Status = RecordingStatus.InProgress;
-                    recording.DateLastUpdated = DateTime.UtcNow;
-                    _recordingProvider.AddOrUpdate(recording);
-
                     var duration = recordingEndDate - DateTime.UtcNow;
 
                     _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);
 
-                    recording.Status = RecordingStatus.Completed;
+                    recordingStatus = RecordingStatus.Completed;
                     _logger.Info("Recording completed: {0}", recordPath);
                 }
                 finally
@@ -862,12 +934,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             catch (OperationCanceledException)
             {
                 _logger.Info("Recording stopped: {0}", recordPath);
-                recording.Status = RecordingStatus.Completed;
+                recordingStatus = RecordingStatus.Completed;
             }
             catch (Exception ex)
             {
                 _logger.ErrorException("Error recording to {0}", ex, recordPath);
-                recording.Status = RecordingStatus.Error;
+                recordingStatus = RecordingStatus.Error;
             }
             finally
             {
@@ -875,12 +947,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
                 _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);
             }
             else if (DateTime.UtcNow < timer.EndDate)
@@ -893,7 +962,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             else
             {
                 _timerProvider.Delete(timer);
-                _recordingProvider.Delete(recording);
             }
         }
 
@@ -948,11 +1016,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             return new DirectRecorder(_logger, _httpClient, _fileSystem);
         }
 
-        private async void OnSuccessfulRecording(RecordingInfo recording)
+        private async void OnSuccessfulRecording(bool isSeries, string path)
         {
             if (GetConfiguration().EnableAutoOrganize)
             {
-                if (recording.IsSeries)
+                if (isSeries)
                 {
                     try
                     {
@@ -962,12 +1030,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
                         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)
                     {
@@ -991,18 +1054,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             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()
         {
             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)
         {
-            var newTimers = GetTimersForSeries(seriesTimer, epgData, _recordingProvider.GetAll()).ToList();
+            var newTimers = GetTimersForSeries(seriesTimer, epgData, true).ToList();
 
             var registration = await GetRegistrationInfo("seriesrecordings").ConfigureAwait(false);
 
@@ -1024,7 +1075,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
             if (deleteInvalidTimers)
             {
-                var allTimers = GetTimersForSeries(seriesTimer, epgData, new List<RecordingInfo>())
+                var allTimers = GetTimersForSeries(seriesTimer, epgData, false)
                     .Select(i => i.Id)
                     .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)
             {
@@ -1050,23 +1103,71 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             {
                 throw new ArgumentNullException("allPrograms");
             }
-            if (currentRecordings == null)
-            {
-                throw new ArgumentNullException("currentRecordings");
-            }
 
             // Exclude programs that have already ended
             allPrograms = allPrograms.Where(i => i.EndDate > DateTime.UtcNow && i.StartDate > DateTime.UtcNow);
 
             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));
         }
 
+        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)
         {
             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
         {
             public string Path { get; set; }