Merge pull request #1710 from MediaBrowser/dev

Dev
pull/702/head
Luke 9 years ago
commit ab2476b9e0

@ -79,7 +79,7 @@ namespace MediaBrowser.Api
} }
} }
} }
/// <summary> /// <summary>
/// To the optimized serialized result using cache. /// To the optimized serialized result using cache.
/// </summary> /// </summary>
@ -118,9 +118,6 @@ namespace MediaBrowser.Api
return ResultFactory.GetStaticFileResult(Request, path); return ResultFactory.GetStaticFileResult(Request, path);
} }
private readonly char[] _dashReplaceChars = { '?', '/', '&' };
private const char SlugChar = '-';
protected DtoOptions GetDtoOptions(object request) protected DtoOptions GetDtoOptions(object request)
{ {
var options = new DtoOptions(); var options = new DtoOptions();
@ -154,152 +151,122 @@ namespace MediaBrowser.Api
protected MusicArtist GetArtist(string name, ILibraryManager libraryManager) protected MusicArtist GetArtist(string name, ILibraryManager libraryManager)
{ {
return libraryManager.GetArtist(DeSlugArtistName(name, libraryManager)); if (name.IndexOf(BaseItem.SlugChar) != -1)
} {
var result = libraryManager.GetItemList(new InternalItemsQuery
protected Studio GetStudio(string name, ILibraryManager libraryManager) {
{ SlugName = name,
return libraryManager.GetStudio(DeSlugStudioName(name, libraryManager)); IncludeItemTypes = new[] { typeof(MusicArtist).Name }
}
protected Genre GetGenre(string name, ILibraryManager libraryManager)
{
return libraryManager.GetGenre(DeSlugGenreName(name, libraryManager));
}
protected MusicGenre GetMusicGenre(string name, ILibraryManager libraryManager) }).OfType<MusicArtist>().FirstOrDefault();
{
return libraryManager.GetMusicGenre(DeSlugGenreName(name, libraryManager));
}
protected GameGenre GetGameGenre(string name, ILibraryManager libraryManager) if (result != null)
{ {
return libraryManager.GetGameGenre(DeSlugGameGenreName(name, libraryManager)); return result;
} }
}
protected Person GetPerson(string name, ILibraryManager libraryManager) return libraryManager.GetArtist(name);
{
return libraryManager.GetPerson(DeSlugPersonName(name, libraryManager));
} }
/// <summary> protected Studio GetStudio(string name, ILibraryManager libraryManager)
/// Deslugs an artist name by finding the correct entry in the library
/// </summary>
/// <param name="name"></param>
/// <param name="libraryManager"></param>
/// <returns></returns>
protected string DeSlugArtistName(string name, ILibraryManager libraryManager)
{ {
if (name.IndexOf(SlugChar) == -1) if (name.IndexOf(BaseItem.SlugChar) != -1)
{
return name;
}
var items = libraryManager.GetItemList(new InternalItemsQuery
{ {
IncludeItemTypes = new[] { typeof(Audio).Name, typeof(MusicVideo).Name, typeof(MusicAlbum).Name } var result = libraryManager.GetItemList(new InternalItemsQuery
});
return items
.OfType<IHasArtist>()
.SelectMany(i => i.AllArtists)
.DistinctNames()
.FirstOrDefault(i =>
{ {
i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar)); SlugName = name,
IncludeItemTypes = new[] { typeof(Studio).Name }
return string.Equals(i, name, StringComparison.OrdinalIgnoreCase); }).OfType<Studio>().FirstOrDefault();
if (result != null)
{
return result;
}
}
}) ?? name; return libraryManager.GetStudio(name);
} }
/// <summary> protected Genre GetGenre(string name, ILibraryManager libraryManager)
/// Deslugs a genre name by finding the correct entry in the library
/// </summary>
protected string DeSlugGenreName(string name, ILibraryManager libraryManager)
{ {
if (name.IndexOf(SlugChar) == -1) if (name.IndexOf(BaseItem.SlugChar) != -1)
{ {
return name; var result = libraryManager.GetItemList(new InternalItemsQuery
}
return libraryManager.RootFolder.GetRecursiveChildren()
.SelectMany(i => i.Genres)
.DistinctNames()
.FirstOrDefault(i =>
{ {
i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar)); SlugName = name,
IncludeItemTypes = new[] { typeof(Genre).Name }
return string.Equals(i, name, StringComparison.OrdinalIgnoreCase); }).OfType<Genre>().FirstOrDefault();
if (result != null)
{
return result;
}
}
}) ?? name; return libraryManager.GetGenre(name);
} }
protected string DeSlugGameGenreName(string name, ILibraryManager libraryManager) protected MusicGenre GetMusicGenre(string name, ILibraryManager libraryManager)
{ {
if (name.IndexOf(SlugChar) == -1) if (name.IndexOf(BaseItem.SlugChar) != -1)
{ {
return name; var result = libraryManager.GetItemList(new InternalItemsQuery
} {
SlugName = name,
IncludeItemTypes = new[] { typeof(MusicGenre).Name }
var items = libraryManager.GetItemList(new InternalItemsQuery }).OfType<MusicGenre>().FirstOrDefault();
{
IncludeItemTypes = new[] { typeof(Game).Name }
});
return items if (result != null)
.SelectMany(i => i.Genres)
.DistinctNames()
.FirstOrDefault(i =>
{ {
i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar)); return result;
}
return string.Equals(i, name, StringComparison.OrdinalIgnoreCase); }
}) ?? name; return libraryManager.GetMusicGenre(name);
} }
/// <summary> protected GameGenre GetGameGenre(string name, ILibraryManager libraryManager)
/// Deslugs a studio name by finding the correct entry in the library
/// </summary>
protected string DeSlugStudioName(string name, ILibraryManager libraryManager)
{ {
if (name.IndexOf(SlugChar) == -1) if (name.IndexOf(BaseItem.SlugChar) != -1)
{ {
return name; var result = libraryManager.GetItemList(new InternalItemsQuery
}
return libraryManager.RootFolder
.GetRecursiveChildren()
.SelectMany(i => i.Studios)
.DistinctNames()
.FirstOrDefault(i =>
{ {
i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar)); SlugName = name,
IncludeItemTypes = new[] { typeof(GameGenre).Name }
return string.Equals(i, name, StringComparison.OrdinalIgnoreCase); }).OfType<GameGenre>().FirstOrDefault();
}) ?? name; if (result != null)
{
return result;
}
}
return libraryManager.GetGameGenre(name);
} }
/// <summary> protected Person GetPerson(string name, ILibraryManager libraryManager)
/// Deslugs a person name by finding the correct entry in the library
/// </summary>
protected string DeSlugPersonName(string name, ILibraryManager libraryManager)
{ {
if (name.IndexOf(SlugChar) == -1) if (name.IndexOf(BaseItem.SlugChar) != -1)
{ {
return name; var result = libraryManager.GetItemList(new InternalItemsQuery
}
return libraryManager.GetPeopleNames(new InternalPeopleQuery())
.FirstOrDefault(i =>
{ {
i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar)); SlugName = name,
IncludeItemTypes = new[] { typeof(Person).Name }
return string.Equals(i, name, StringComparison.OrdinalIgnoreCase); }).OfType<Person>().FirstOrDefault();
if (result != null)
{
return result;
}
}
}) ?? name; return libraryManager.GetPerson(name);
} }
protected string GetPathValue(int index) protected string GetPathValue(int index)

@ -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);
}
}
}
}

@ -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)) _libraryManager.RemoveVirtualFolder(request.Name, request.RefreshLibrary);
{
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();
}
});
}
} }
/// <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
{ {

@ -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" />

@ -175,7 +175,7 @@ namespace MediaBrowser.Api
foreach (var item in items.Where(i => i.Id != primaryVersion.Id)) foreach (var item in items.Where(i => i.Id != primaryVersion.Id))
{ {
item.PrimaryVersionId = primaryVersion.Id; item.PrimaryVersionId = primaryVersion.Id.ToString("N");
await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);

@ -44,6 +44,9 @@ namespace MediaBrowser.Controller.Entities
ImageInfos = new List<ItemImageInfo>(); ImageInfos = new List<ItemImageInfo>();
} }
public static readonly char[] SlugReplaceChars = { '?', '/', '&' };
public static char SlugChar = '-';
/// <summary> /// <summary>
/// The supported image extensions /// The supported image extensions
/// </summary> /// </summary>
@ -125,6 +128,21 @@ namespace MediaBrowser.Controller.Entities
} }
} }
[IgnoreDataMember]
public string SlugName
{
get
{
var name = Name;
if (string.IsNullOrWhiteSpace(name))
{
return string.Empty;
}
return SlugReplaceChars.Aggregate(name, (current, c) => current.Replace(c, SlugChar));
}
}
public string OriginalTitle { get; set; } public string OriginalTitle { get; set; }
/// <summary> /// <summary>
@ -728,12 +746,14 @@ namespace MediaBrowser.Controller.Entities
/// Gets or sets the critic rating. /// Gets or sets the critic rating.
/// </summary> /// </summary>
/// <value>The critic rating.</value> /// <value>The critic rating.</value>
[IgnoreDataMember]
public float? CriticRating { get; set; } public float? CriticRating { get; set; }
/// <summary> /// <summary>
/// Gets or sets the critic rating summary. /// Gets or sets the critic rating summary.
/// </summary> /// </summary>
/// <value>The critic rating summary.</value> /// <value>The critic rating summary.</value>
[IgnoreDataMember]
public string CriticRatingSummary { get; set; } public string CriticRatingSummary { get; set; }
/// <summary> /// <summary>

@ -707,8 +707,8 @@ namespace MediaBrowser.Controller.Entities
{ {
return ItemRepository.GetItemIdsList(new InternalItemsQuery return ItemRepository.GetItemIdsList(new InternalItemsQuery
{ {
ParentId = Id ParentId = Id,
GroupByPresentationUniqueKey = false
}); });
} }

@ -49,6 +49,7 @@ namespace MediaBrowser.Controller.Entities
public string PresentationUniqueKey { get; set; } public string PresentationUniqueKey { get; set; }
public string Path { get; set; } public string Path { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string SlugName { get; set; }
public string Person { get; set; } public string Person { get; set; }
public string[] PersonIds { get; set; } public string[] PersonIds { get; set; }
@ -133,9 +134,13 @@ namespace MediaBrowser.Controller.Entities
public string[] AlbumNames { get; set; } public string[] AlbumNames { get; set; }
public string[] ArtistNames { get; set; } public string[] ArtistNames { get; set; }
public bool GroupByPresentationUniqueKey { get; set; }
public InternalItemsQuery() public InternalItemsQuery()
{ {
GroupByPresentationUniqueKey = true;
AlbumNames = new string[] { }; AlbumNames = new string[] { };
ArtistNames = new string[] { }; ArtistNames = new string[] { };

@ -8,6 +8,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using MediaBrowser.Controller.Entities.Audio;
namespace MediaBrowser.Controller.Entities.Movies namespace MediaBrowser.Controller.Entities.Movies
{ {
@ -118,7 +119,7 @@ namespace MediaBrowser.Controller.Entities.Movies
// Gather all possible ratings // Gather all possible ratings
var ratings = GetRecursiveChildren() var ratings = GetRecursiveChildren()
.Concat(GetLinkedChildren()) .Concat(GetLinkedChildren())
.Where(i => i is Movie || i is Series) .Where(i => i is Movie || i is Series || i is MusicAlbum || i is Game)
.Select(i => i.OfficialRating) .Select(i => i.OfficialRating)
.Where(i => !string.IsNullOrEmpty(i)) .Where(i => !string.IsNullOrEmpty(i))
.Distinct(StringComparer.OrdinalIgnoreCase) .Distinct(StringComparer.OrdinalIgnoreCase)

@ -28,7 +28,8 @@ namespace MediaBrowser.Controller.Entities
IThemeMedia, IThemeMedia,
IArchivable IArchivable
{ {
public Guid? PrimaryVersionId { get; set; } [IgnoreDataMember]
public string PrimaryVersionId { get; set; }
public List<string> AdditionalParts { get; set; } public List<string> AdditionalParts { get; set; }
public List<string> LocalAlternateVersions { get; set; } public List<string> LocalAlternateVersions { get; set; }
@ -49,9 +50,9 @@ namespace MediaBrowser.Controller.Entities
{ {
get get
{ {
if (PrimaryVersionId.HasValue) if (!string.IsNullOrWhiteSpace(PrimaryVersionId))
{ {
return PrimaryVersionId.Value.ToString("N"); return PrimaryVersionId;
} }
return base.PresentationUniqueKey; return base.PresentationUniqueKey;
@ -70,6 +71,72 @@ namespace MediaBrowser.Controller.Entities
/// <value>The timestamp.</value> /// <value>The timestamp.</value>
public TransportStreamTimestamp? Timestamp { get; set; } public TransportStreamTimestamp? Timestamp { get; set; }
/// <summary>
/// Gets or sets the subtitle paths.
/// </summary>
/// <value>The subtitle paths.</value>
public List<string> SubtitleFiles { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance has subtitles.
/// </summary>
/// <value><c>true</c> if this instance has subtitles; otherwise, <c>false</c>.</value>
public bool HasSubtitles { get; set; }
public bool IsPlaceHolder { get; set; }
public bool IsShortcut { get; set; }
public string ShortcutPath { get; set; }
/// <summary>
/// Gets or sets the video bit rate.
/// </summary>
/// <value>The video bit rate.</value>
public int? VideoBitRate { get; set; }
/// <summary>
/// Gets or sets the default index of the video stream.
/// </summary>
/// <value>The default index of the video stream.</value>
public int? DefaultVideoStreamIndex { get; set; }
/// <summary>
/// Gets or sets the type of the video.
/// </summary>
/// <value>The type of the video.</value>
public VideoType VideoType { get; set; }
/// <summary>
/// Gets or sets the type of the iso.
/// </summary>
/// <value>The type of the iso.</value>
public IsoType? IsoType { get; set; }
/// <summary>
/// Gets or sets the video3 D format.
/// </summary>
/// <value>The video3 D format.</value>
public Video3DFormat? Video3DFormat { get; set; }
/// <summary>
/// If the video is a folder-rip, this will hold the file list for the largest playlist
/// </summary>
public List<string> PlayableStreamFileNames { get; set; }
/// <summary>
/// Gets the playable stream files.
/// </summary>
/// <returns>List{System.String}.</returns>
public List<string> GetPlayableStreamFiles()
{
return GetPlayableStreamFiles(Path);
}
/// <summary>
/// Gets or sets the aspect ratio.
/// </summary>
/// <value>The aspect ratio.</value>
public string AspectRatio { get; set; }
public Video() public Video()
{ {
PlayableStreamFileNames = new List<string>(); PlayableStreamFileNames = new List<string>();
@ -104,9 +171,9 @@ namespace MediaBrowser.Controller.Entities
{ {
get get
{ {
if (PrimaryVersionId.HasValue) if (!string.IsNullOrWhiteSpace(PrimaryVersionId))
{ {
var item = LibraryManager.GetItemById(PrimaryVersionId.Value) as Video; var item = LibraryManager.GetItemById(PrimaryVersionId) as Video;
if (item != null) if (item != null)
{ {
return item.MediaSourceCount; return item.MediaSourceCount;
@ -238,72 +305,6 @@ namespace MediaBrowser.Controller.Entities
.OrderBy(i => i.SortName); .OrderBy(i => i.SortName);
} }
/// <summary>
/// Gets or sets the subtitle paths.
/// </summary>
/// <value>The subtitle paths.</value>
public List<string> SubtitleFiles { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance has subtitles.
/// </summary>
/// <value><c>true</c> if this instance has subtitles; otherwise, <c>false</c>.</value>
public bool HasSubtitles { get; set; }
public bool IsPlaceHolder { get; set; }
public bool IsShortcut { get; set; }
public string ShortcutPath { get; set; }
/// <summary>
/// Gets or sets the video bit rate.
/// </summary>
/// <value>The video bit rate.</value>
public int? VideoBitRate { get; set; }
/// <summary>
/// Gets or sets the default index of the video stream.
/// </summary>
/// <value>The default index of the video stream.</value>
public int? DefaultVideoStreamIndex { get; set; }
/// <summary>
/// Gets or sets the type of the video.
/// </summary>
/// <value>The type of the video.</value>
public VideoType VideoType { get; set; }
/// <summary>
/// Gets or sets the type of the iso.
/// </summary>
/// <value>The type of the iso.</value>
public IsoType? IsoType { get; set; }
/// <summary>
/// Gets or sets the video3 D format.
/// </summary>
/// <value>The video3 D format.</value>
public Video3DFormat? Video3DFormat { get; set; }
/// <summary>
/// If the video is a folder-rip, this will hold the file list for the largest playlist
/// </summary>
public List<string> PlayableStreamFileNames { get; set; }
/// <summary>
/// Gets the playable stream files.
/// </summary>
/// <returns>List{System.String}.</returns>
public List<string> GetPlayableStreamFiles()
{
return GetPlayableStreamFiles(Path);
}
/// <summary>
/// Gets or sets the aspect ratio.
/// </summary>
/// <value>The aspect ratio.</value>
public string AspectRatio { get; set; }
[IgnoreDataMember] [IgnoreDataMember]
public override string ContainingFolderPath public override string ContainingFolderPath
{ {
@ -520,9 +521,9 @@ namespace MediaBrowser.Controller.Entities
list.Add(new Tuple<Video, MediaSourceType>(this, MediaSourceType.Default)); list.Add(new Tuple<Video, MediaSourceType>(this, MediaSourceType.Default));
list.AddRange(GetLinkedAlternateVersions().Select(i => new Tuple<Video, MediaSourceType>(i, MediaSourceType.Grouping))); list.AddRange(GetLinkedAlternateVersions().Select(i => new Tuple<Video, MediaSourceType>(i, MediaSourceType.Grouping)));
if (PrimaryVersionId.HasValue) if (!string.IsNullOrWhiteSpace(PrimaryVersionId))
{ {
var primary = LibraryManager.GetItemById(PrimaryVersionId.Value) as Video; var primary = LibraryManager.GetItemById(PrimaryVersionId) as Video;
if (primary != null) if (primary != null)
{ {
var existingIds = list.Select(i => i.Item1.Id).ToList(); var existingIds = list.Select(i => i.Item1.Id).ToList();

@ -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);
} }
} }

@ -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>();
} }

@ -397,12 +397,6 @@ namespace MediaBrowser.Server.Implementations.Dto
collectionFolder.GetViewType(user); collectionFolder.GetViewType(user);
} }
var playlist = item as Playlist;
if (playlist != null)
{
AttachLinkedChildImages(dto, playlist, user, options);
}
if (fields.Contains(ItemFields.CanDelete)) if (fields.Contains(ItemFields.CanDelete))
{ {
dto.CanDelete = user == null dto.CanDelete = user == null
@ -1564,45 +1558,6 @@ namespace MediaBrowser.Server.Implementations.Dto
} }
} }
private void AttachLinkedChildImages(BaseItemDto dto, Folder folder, User user, DtoOptions options)
{
List<BaseItem> linkedChildren = null;
var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
if (backdropLimit > 0 && dto.BackdropImageTags.Count == 0)
{
linkedChildren = user == null
? folder.GetRecursiveChildren().ToList()
: folder.GetRecursiveChildren(user).ToList();
var parentWithBackdrop = linkedChildren.FirstOrDefault(i => i.GetImages(ImageType.Backdrop).Any());
if (parentWithBackdrop != null)
{
dto.ParentBackdropItemId = GetDtoId(parentWithBackdrop);
dto.ParentBackdropImageTags = GetBackdropImageTags(parentWithBackdrop, backdropLimit);
}
}
if (!dto.ImageTags.ContainsKey(ImageType.Primary) && options.GetImageLimit(ImageType.Primary) > 0)
{
if (linkedChildren == null)
{
linkedChildren = user == null
? folder.GetRecursiveChildren().ToList()
: folder.GetRecursiveChildren(user).ToList();
}
var parentWithImage = linkedChildren.FirstOrDefault(i => i.GetImages(ImageType.Primary).Any());
if (parentWithImage != null)
{
dto.ParentPrimaryImageItemId = GetDtoId(parentWithImage);
dto.ParentPrimaryImageTag = GetImageCacheTag(parentWithImage, ImageType.Primary);
}
}
}
private string GetMappedPath(IHasMetadata item) private string GetMappedPath(IHasMetadata item)
{ {
var path = item.Path; var path = item.Path;

@ -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);
}
}
} }
} }

@ -2,7 +2,6 @@
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -35,25 +34,20 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
/// <returns>Task.</returns> /// <returns>Task.</returns>
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{ {
var items = _libraryManager.RootFolder.GetRecursiveChildren() var items = _libraryManager.GetItemList(new InternalItemsQuery
.SelectMany(i => i.Studios) {
.DistinctNames() IncludeItemTypes = new[] { typeof(Studio).Name }
.ToList();
}).ToList();
var numComplete = 0; var numComplete = 0;
var count = items.Count; var count = items.Count;
var validIds = new List<Guid>(); foreach (var item in items)
foreach (var name in items)
{ {
try try
{ {
var itemByName = _libraryManager.GetStudio(name); await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
validIds.Add(itemByName.Id);
await itemByName.RefreshMetadata(cancellationToken).ConfigureAwait(false);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@ -62,7 +56,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.ErrorException("Error refreshing {0}", ex, name); _logger.ErrorException("Error refreshing {0}", ex, item.Name);
} }
numComplete++; numComplete++;
@ -73,28 +67,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
progress.Report(percent); progress.Report(percent);
} }
var allIds = _libraryManager.GetItemIds(new InternalItemsQuery
{
IncludeItemTypes = new[] { typeof(Studio).Name }
});
var invalidIds = allIds
.Except(validIds)
.ToList();
foreach (var id in invalidIds)
{
cancellationToken.ThrowIfCancellationRequested();
var item = _libraryManager.GetItemById(id);
await _libraryManager.DeleteItem(item, new DeleteOptions
{
DeleteFileLocation = false
}).ConfigureAwait(false);
}
progress.Report(100); progress.Report(100);
} }
} }

@ -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)); return Task.FromResult(true);
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);
}
} }
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(); return new List<RecordingInfo>();
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;
} }
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"); private async Task RecordStream(TimerInfo timer, DateTime recordingEndDate, ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken)
var recording = _recordingProvider.GetAll().FirstOrDefault(x => string.Equals(x.Id, recordingId, StringComparison.OrdinalIgnoreCase)); {
if (timer == null)
{
throw new ArgumentNullException("timer");
}
if (recording == null) ProgramInfo info = null;
if (string.IsNullOrWhiteSpace(timer.ProgramId))
{ {
recording = new RecordingInfo _logger.Info("Timer {0} has null programId", timer.Id);
{ }
ChannelId = info.ChannelId, else
Id = recordingId, {
StartDate = info.StartDate, info = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId);
EndDate = info.EndDate, }
Genres = info.Genres,
IsKids = info.IsKids, if (info == null)
IsLive = info.IsLive, {
IsMovie = info.IsMovie, _logger.Info("Unable to find program with Id {0}. Will search using start date", timer.ProgramId);
IsHD = info.IsHD, info = GetProgramInfoFromCache(timer.ChannelId, timer.StartDate);
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);
} }
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; if (recordingStatus == RecordingStatus.Completed)
_recordingProvider.AddOrUpdate(recording);
if (recording.Status == 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); var result = await organize.OrganizeEpisodeFile(path, CancellationToken.None).ConfigureAwait(false);
if (result.Status == FileSortingStatus.Success)
{
_recordingProvider.Delete(recording);
}
} }
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(); if (filterByCurrentRecordings)
{
allPrograms = allPrograms.Where(i => !recordingShowIds.Contains(i.Id, StringComparer.OrdinalIgnoreCase)); 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; }

@ -82,7 +82,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
private IDbCommand _updateInheritedRatingCommand; private IDbCommand _updateInheritedRatingCommand;
private IDbCommand _updateInheritedTagsCommand; private IDbCommand _updateInheritedTagsCommand;
public const int LatestSchemaVersion = 69; public const int LatestSchemaVersion = 71;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SqliteItemRepository"/> class. /// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.
@ -226,6 +226,9 @@ namespace MediaBrowser.Server.Implementations.Persistence
_connection.AddColumn(Logger, "TypedBaseItems", "InheritedTags", "Text"); _connection.AddColumn(Logger, "TypedBaseItems", "InheritedTags", "Text");
_connection.AddColumn(Logger, "TypedBaseItems", "CleanName", "Text"); _connection.AddColumn(Logger, "TypedBaseItems", "CleanName", "Text");
_connection.AddColumn(Logger, "TypedBaseItems", "PresentationUniqueKey", "Text"); _connection.AddColumn(Logger, "TypedBaseItems", "PresentationUniqueKey", "Text");
_connection.AddColumn(Logger, "TypedBaseItems", "SlugName", "Text");
_connection.AddColumn(Logger, "TypedBaseItems", "OriginalTitle", "Text");
_connection.AddColumn(Logger, "TypedBaseItems", "PrimaryVersionId", "Text");
string[] postQueries = string[] postQueries =
{ {
@ -367,7 +370,9 @@ namespace MediaBrowser.Server.Implementations.Persistence
"Tags", "Tags",
"SourceType", "SourceType",
"TrailerTypes", "TrailerTypes",
"DateModifiedDuringLastRefresh" "DateModifiedDuringLastRefresh",
"OriginalTitle",
"PrimaryVersionId"
}; };
private readonly string[] _mediaStreamSaveColumns = private readonly string[] _mediaStreamSaveColumns =
@ -476,7 +481,10 @@ namespace MediaBrowser.Server.Implementations.Persistence
"DateModifiedDuringLastRefresh", "DateModifiedDuringLastRefresh",
"InheritedTags", "InheritedTags",
"CleanName", "CleanName",
"PresentationUniqueKey" "PresentationUniqueKey",
"SlugName",
"OriginalTitle",
"PrimaryVersionId"
}; };
_saveItemCommand = _connection.CreateCommand(); _saveItemCommand = _connection.CreateCommand();
_saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values ("; _saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values (";
@ -810,7 +818,20 @@ namespace MediaBrowser.Server.Implementations.Persistence
{ {
_saveItemCommand.GetParameter(index++).Value = item.Name.RemoveDiacritics(); _saveItemCommand.GetParameter(index++).Value = item.Name.RemoveDiacritics();
} }
_saveItemCommand.GetParameter(index++).Value = item.PresentationUniqueKey; _saveItemCommand.GetParameter(index++).Value = item.PresentationUniqueKey;
_saveItemCommand.GetParameter(index++).Value = item.SlugName;
_saveItemCommand.GetParameter(index++).Value = item.OriginalTitle;
var video = item as Video;
if (video != null)
{
_saveItemCommand.GetParameter(index++).Value = video.PrimaryVersionId;
}
else
{
_saveItemCommand.GetParameter(index++).Value = null;
}
_saveItemCommand.Transaction = transaction; _saveItemCommand.Transaction = transaction;
@ -1189,6 +1210,20 @@ namespace MediaBrowser.Server.Implementations.Persistence
item.DateModifiedDuringLastRefresh = reader.GetDateTime(51).ToUniversalTime(); item.DateModifiedDuringLastRefresh = reader.GetDateTime(51).ToUniversalTime();
} }
if (!reader.IsDBNull(52))
{
item.OriginalTitle = reader.GetString(52);
}
var video = item as Video;
if (video != null)
{
if (!reader.IsDBNull(53))
{
video.PrimaryVersionId = reader.GetString(53);
}
}
return item; return item;
} }
@ -2070,6 +2105,19 @@ namespace MediaBrowser.Server.Implementations.Persistence
cmd.Parameters.Add(cmd, "@PersonName", DbType.String).Value = query.Person; cmd.Parameters.Add(cmd, "@PersonName", DbType.String).Value = query.Person;
} }
if (!string.IsNullOrWhiteSpace(query.SlugName))
{
if (_config.Configuration.SchemaVersion >= 70)
{
whereClauses.Add("SlugName=@SlugName");
}
else
{
whereClauses.Add("Name=@SlugName");
}
cmd.Parameters.Add(cmd, "@SlugName", DbType.String).Value = query.SlugName;
}
if (!string.IsNullOrWhiteSpace(query.Name)) if (!string.IsNullOrWhiteSpace(query.Name))
{ {
if (_config.Configuration.SchemaVersion >= 66) if (_config.Configuration.SchemaVersion >= 66)
@ -2097,14 +2145,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
} }
if (!string.IsNullOrWhiteSpace(query.NameStartsWith)) if (!string.IsNullOrWhiteSpace(query.NameStartsWith))
{ {
if (_config.Configuration.SchemaVersion >= 66) whereClauses.Add("SortName like @NameStartsWith");
{
whereClauses.Add("CleanName like @NameStartsWith");
}
else
{
whereClauses.Add("Name like @NameStartsWith");
}
cmd.Parameters.Add(cmd, "@NameStartsWith", DbType.String).Value = query.NameStartsWith + "%"; cmd.Parameters.Add(cmd, "@NameStartsWith", DbType.String).Value = query.NameStartsWith + "%";
} }
if (!string.IsNullOrWhiteSpace(query.NameStartsWithOrGreater)) if (!string.IsNullOrWhiteSpace(query.NameStartsWithOrGreater))
@ -2347,6 +2388,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
private bool EnableGroupByPresentationUniqueKey(InternalItemsQuery query) private bool EnableGroupByPresentationUniqueKey(InternalItemsQuery query)
{ {
if (!query.GroupByPresentationUniqueKey)
{
return false;
}
if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey)) if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey))
{ {
return false; return false;

@ -384,7 +384,7 @@ namespace MediaBrowser.WebDashboard.Api
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase)) if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
{ {
sb.Append("<meta http-equiv=\"Content-Security-Policy\" content=\"default-src * 'unsafe-inline' 'unsafe-eval' data:;\">"); sb.Append("<meta http-equiv=\"Content-Security-Policy\" content=\"default-src * 'unsafe-inline' 'unsafe-eval' data: filesystem:;\">");
} }
sb.Append("<link rel=\"manifest\" href=\"manifest.json\">"); sb.Append("<link rel=\"manifest\" href=\"manifest.json\">");

Loading…
Cancel
Save