aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Server.Implementations
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Server.Implementations')
-rw-r--r--MediaBrowser.Server.Implementations/Activity/ActivityRepository.cs4
-rw-r--r--MediaBrowser.Server.Implementations/Channels/ChannelManager.cs12
-rw-r--r--MediaBrowser.Server.Implementations/Dto/DtoService.cs131
-rw-r--r--MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs12
-rw-r--r--MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs28
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs17
-rw-r--r--MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs7
-rw-r--r--MediaBrowser.Server.Implementations/Library/LibraryManager.cs289
-rw-r--r--MediaBrowser.Server.Implementations/Library/LocalTrailerPostScanTask.cs24
-rw-r--r--MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs16
-rw-r--r--MediaBrowser.Server.Implementations/Library/MusicManager.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Library/SearchEngine.cs7
-rw-r--r--MediaBrowser.Server.Implementations/Library/UserDataManager.cs129
-rw-r--r--MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs24
-rw-r--r--MediaBrowser.Server.Implementations/Library/Validators/YearsPostScanTask.cs13
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs22
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs556
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs38
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs2
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs27
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs30
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs8
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs9
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs5
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Core/en-US.json353
-rw-r--r--MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj16
-rw-r--r--MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs4
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs6
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/DataExtensions.cs (renamed from MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs)46
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/IDbConnector.cs10
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/MediaStreamColumns.cs32
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteDisplayPreferencesRepository.cs4
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs4
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs825
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs4
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs11
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteUserRepository.cs6
-rw-r--r--MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs19
-rw-r--r--MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs4
-rw-r--r--MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs12
-rw-r--r--MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs4
-rw-r--r--MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs19
-rw-r--r--MediaBrowser.Server.Implementations/Session/SessionManager.cs50
-rw-r--r--MediaBrowser.Server.Implementations/Social/SharingRepository.cs4
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs10
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs10
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/PlayCountComparer.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs46
-rw-r--r--MediaBrowser.Server.Implementations/Sync/SyncManager.cs18
-rw-r--r--MediaBrowser.Server.Implementations/Sync/SyncRepository.cs21
-rw-r--r--MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs84
-rw-r--r--MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs3
-rw-r--r--MediaBrowser.Server.Implementations/packages.config3
55 files changed, 2059 insertions, 987 deletions
diff --git a/MediaBrowser.Server.Implementations/Activity/ActivityRepository.cs b/MediaBrowser.Server.Implementations/Activity/ActivityRepository.cs
index 85ab76182..b0e05a5bc 100644
--- a/MediaBrowser.Server.Implementations/Activity/ActivityRepository.cs
+++ b/MediaBrowser.Server.Implementations/Activity/ActivityRepository.cs
@@ -27,11 +27,11 @@ namespace MediaBrowser.Server.Implementations.Activity
_appPaths = appPaths;
}
- public async Task Initialize()
+ public async Task Initialize(IDbConnector dbConnector)
{
var dbFile = Path.Combine(_appPaths.DataPath, "activitylog.db");
- _connection = await SqliteExtensions.ConnectToDb(dbFile, Logger).ConfigureAwait(false);
+ _connection = await dbConnector.Connect(dbFile).ConfigureAwait(false);
string[] queries = {
diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
index c9956c68a..6a9842cb2 100644
--- a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
+++ b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
@@ -133,7 +133,7 @@ namespace MediaBrowser.Server.Implementations.Channels
if (query.IsFavorite.HasValue)
{
var val = query.IsFavorite.Value;
- channels = channels.Where(i => _userDataManager.GetUserData(user.Id, i.GetUserDataKey()).IsFavorite == val)
+ channels = channels.Where(i => _userDataManager.GetUserData(user, i).IsFavorite == val)
.ToList();
}
@@ -1437,7 +1437,7 @@ namespace MediaBrowser.Server.Implementations.Channels
case ItemFilter.IsFavoriteOrLikes:
return items.Where(item =>
{
- var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
+ var userdata = _userDataManager.GetUserData(user, item);
if (userdata == null)
{
@@ -1453,7 +1453,7 @@ namespace MediaBrowser.Server.Implementations.Channels
case ItemFilter.Likes:
return items.Where(item =>
{
- var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
+ var userdata = _userDataManager.GetUserData(user, item);
return userdata != null && userdata.Likes.HasValue && userdata.Likes.Value;
});
@@ -1461,7 +1461,7 @@ namespace MediaBrowser.Server.Implementations.Channels
case ItemFilter.Dislikes:
return items.Where(item =>
{
- var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
+ var userdata = _userDataManager.GetUserData(user, item);
return userdata != null && userdata.Likes.HasValue && !userdata.Likes.Value;
});
@@ -1469,7 +1469,7 @@ namespace MediaBrowser.Server.Implementations.Channels
case ItemFilter.IsFavorite:
return items.Where(item =>
{
- var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
+ var userdata = _userDataManager.GetUserData(user, item);
return userdata != null && userdata.IsFavorite;
});
@@ -1477,7 +1477,7 @@ namespace MediaBrowser.Server.Implementations.Channels
case ItemFilter.IsResumable:
return items.Where(item =>
{
- var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
+ var userdata = _userDataManager.GetUserData(user, item);
return userdata != null && userdata.PlaybackPositionTicks > 0;
});
diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
index 2ee55edb9..dfbac47d5 100644
--- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs
+++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
@@ -115,11 +115,10 @@ namespace MediaBrowser.Server.Implementations.Dto
{
if (options.Fields.Contains(ItemFields.ItemCounts))
{
- var itemFilter = byName.GetItemFilter();
-
- var libraryItems = user != null ?
- user.RootFolder.GetRecursiveChildren(user, itemFilter) :
- _libraryManager.RootFolder.GetRecursiveChildren(itemFilter);
+ var libraryItems = byName.GetTaggedItems(new InternalItemsQuery(user)
+ {
+ Recursive = true
+ });
SetItemByNameInfo(item, dto, libraryItems.ToList(), user);
}
@@ -194,24 +193,13 @@ namespace MediaBrowser.Server.Implementations.Dto
private List<BaseItem> GetTaggedItems(IItemByName byName, User user)
{
- var person = byName as Person;
-
- if (person != null)
+ var items = byName.GetTaggedItems(new InternalItemsQuery(user)
{
- var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
- {
- Person = byName.Name
-
- }, new string[] { });
-
- return items.ToList();
- }
+ Recursive = true
- var itemFilter = byName.GetItemFilter();
+ }).ToList();
- return user != null ?
- user.RootFolder.GetRecursiveChildren(user, itemFilter).ToList() :
- _libraryManager.RootFolder.GetRecursiveChildren(itemFilter).ToList();
+ return items;
}
private SyncedItemProgress[] GetSyncedItemProgress(DtoOptions options)
@@ -282,7 +270,7 @@ namespace MediaBrowser.Server.Implementations.Dto
else if (dto.HasSyncJob.Value)
{
- dto.SyncStatus = SyncJobItemStatus.Queued;
+ dto.SyncStatus = syncProgress.Where(i => string.Equals(i.ItemId, dto.Id, StringComparison.OrdinalIgnoreCase)).Select(i => i.Status).Max();
}
}
}
@@ -307,7 +295,7 @@ namespace MediaBrowser.Server.Implementations.Dto
else if (dto.HasSyncJob.Value)
{
- dto.SyncStatus = SyncJobItemStatus.Queued;
+ dto.SyncStatus = syncProgress.Where(i => string.Equals(i.ItemId, dto.Id, StringComparison.OrdinalIgnoreCase)).Select(i => i.Status).Max();
}
}
}
@@ -397,12 +385,6 @@ namespace MediaBrowser.Server.Implementations.Dto
collectionFolder.GetViewType(user);
}
- var playlist = item as Playlist;
- if (playlist != null)
- {
- AttachLinkedChildImages(dto, playlist, user, options);
- }
-
if (fields.Contains(ItemFields.CanDelete))
{
dto.CanDelete = user == null
@@ -487,7 +469,7 @@ namespace MediaBrowser.Server.Implementations.Dto
{
if (item.IsFolder)
{
- var userData = _userDataRepository.GetUserData(user.Id, item.GetUserDataKey());
+ var userData = _userDataRepository.GetUserData(user, item);
// Skip the user data manager because we've already looped through the recursive tree and don't want to do it twice
// TODO: Improve in future
@@ -499,13 +481,22 @@ namespace MediaBrowser.Server.Implementations.Dto
{
dto.ChildCount = GetChildCount(folder, user);
- // These are just far too slow.
- if (!(folder is UserRootFolder) && !(folder is UserView) && !(folder is ICollectionFolder))
+ if (folder.SupportsUserDataFromChildren)
{
SetSpecialCounts(folder, user, dto, fields, syncProgress);
}
}
+ if (fields.Contains(ItemFields.CumulativeRunTimeTicks))
+ {
+ dto.CumulativeRunTimeTicks = item.RunTimeTicks;
+ }
+
+ if (fields.Contains(ItemFields.DateLastMediaAdded))
+ {
+ dto.DateLastMediaAdded = folder.DateLastMediaAdded;
+ }
+
dto.UserData.Played = dto.UserData.PlayedPercentage.HasValue && dto.UserData.PlayedPercentage.Value >= 100;
}
@@ -626,9 +617,12 @@ namespace MediaBrowser.Server.Implementations.Dto
{
if (!string.IsNullOrEmpty(item.Album))
{
- var parentAlbum = _libraryManager.RootFolder
- .GetRecursiveChildren(i => i is MusicAlbum && string.Equals(i.Name, item.Album, StringComparison.OrdinalIgnoreCase))
- .FirstOrDefault();
+ var parentAlbum = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ IncludeItemTypes = new[] { typeof(MusicAlbum).Name },
+ Name = item.Album
+
+ }).FirstOrDefault();
if (parentAlbum != null)
{
@@ -1363,9 +1357,10 @@ namespace MediaBrowser.Server.Implementations.Dto
if (fields.Contains(ItemFields.MediaSourceCount))
{
- if (video.MediaSourceCount != 1)
+ var mediaSourceCount = video.MediaSourceCount;
+ if (mediaSourceCount != 1)
{
- dto.MediaSourceCount = video.MediaSourceCount;
+ dto.MediaSourceCount = mediaSourceCount;
}
}
@@ -1564,45 +1559,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)
{
var path = item.Path;
@@ -1658,9 +1614,7 @@ namespace MediaBrowser.Server.Implementations.Dto
{
var recursiveItemCount = 0;
var unplayed = 0;
- long runtime = 0;
- DateTime? dateLastMediaAdded = null;
double totalPercentPlayed = 0;
double totalSyncPercent = 0;
var addSyncInfo = fields.Contains(ItemFields.SyncInfo);
@@ -1677,16 +1631,7 @@ namespace MediaBrowser.Server.Implementations.Dto
// Loop through each recursive child
foreach (var child in children)
{
- if (!dateLastMediaAdded.HasValue)
- {
- dateLastMediaAdded = child.DateCreated;
- }
- else
- {
- dateLastMediaAdded = new[] { dateLastMediaAdded.Value, child.DateCreated }.Max();
- }
-
- var userdata = _userDataRepository.GetUserData(user.Id, child.GetUserDataKey());
+ var userdata = _userDataRepository.GetUserData(user, child);
recursiveItemCount++;
@@ -1714,8 +1659,6 @@ namespace MediaBrowser.Server.Implementations.Dto
unplayed++;
}
- runtime += child.RunTimeTicks ?? 0;
-
if (addSyncInfo)
{
double percent = 0;
@@ -1754,16 +1697,6 @@ namespace MediaBrowser.Server.Implementations.Dto
}
}
}
-
- if (runtime > 0 && fields.Contains(ItemFields.CumulativeRunTimeTicks))
- {
- dto.CumulativeRunTimeTicks = runtime;
- }
-
- if (fields.Contains(ItemFields.DateLastMediaAdded))
- {
- dto.DateLastMediaAdded = dateLastMediaAdded;
- }
}
/// <summary>
diff --git a/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs b/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs
index 71019e0ad..e84b66c5a 100644
--- a/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs
+++ b/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs
@@ -215,6 +215,12 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
return;
}
+ var video = e.Item as Video;
+ if (video != null && video.IsThemeMedia)
+ {
+ return;
+ }
+
var type = GetPlaybackNotificationType(item.MediaType);
SendPlaybackNotification(type, e);
@@ -230,6 +236,12 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
return;
}
+ var video = e.Item as Video;
+ if (video != null && video.IsThemeMedia)
+ {
+ return;
+ }
+
var type = GetPlaybackStoppedNotificationType(item.MediaType);
SendPlaybackNotification(type, e);
diff --git a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
index e45df3f4a..f402a0a33 100644
--- a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
+++ b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
@@ -116,7 +116,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
premiereDate,
options,
overwriteExisting,
- false,
+ false,
result,
cancellationToken).ConfigureAwait(false);
}
@@ -202,7 +202,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
null,
options,
true,
- request.RememberCorrection,
+ request.RememberCorrection,
result,
cancellationToken).ConfigureAwait(false);
@@ -219,7 +219,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
DateTime? premiereDate,
AutoOrganizeOptions options,
bool overwriteExisting,
- bool rememberCorrection,
+ bool rememberCorrection,
FileOrganizationResult result,
CancellationToken cancellationToken)
{
@@ -242,7 +242,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
premiereDate,
options,
overwriteExisting,
- rememberCorrection,
+ rememberCorrection,
result,
cancellationToken);
}
@@ -255,7 +255,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
DateTime? premiereDate,
AutoOrganizeOptions options,
bool overwriteExisting,
- bool rememberCorrection,
+ bool rememberCorrection,
FileOrganizationResult result,
CancellationToken cancellationToken)
{
@@ -304,7 +304,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
if (otherDuplicatePaths.Count > 0)
{
- var msg = string.Format("File '{0}' already exists as '{1}', stopping organization", sourcePath, otherDuplicatePaths);
+ var msg = string.Format("File '{0}' already exists as these:'{1}'. Stopping organization", sourcePath, string.Join("', '", otherDuplicatePaths));
_logger.Info(msg);
result.Status = FileSortingStatus.SkippedExisting;
result.StatusMessage = msg;
@@ -536,7 +536,11 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
result.ExtractedName = nameWithoutYear;
result.ExtractedYear = yearInName;
- var series = _libraryManager.RootFolder.GetRecursiveChildren(i => i is Series)
+ var series = _libraryManager.GetItemList(new Controller.Entities.InternalItemsQuery
+ {
+ IncludeItemTypes = new[] { typeof(Series).Name },
+ Recursive = true
+ })
.Cast<Series>()
.Select(i => NameUtils.GetMatchScore(nameWithoutYear, yearInName, i))
.Where(i => i.Item2 > 0)
@@ -550,10 +554,12 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
if (info != null)
{
- series = _libraryManager.RootFolder
- .GetRecursiveChildren(i => i is Series)
- .Cast<Series>()
- .FirstOrDefault(i => string.Equals(i.Name, info.ItemName, StringComparison.OrdinalIgnoreCase));
+ series = _libraryManager.GetItemList(new Controller.Entities.InternalItemsQuery
+ {
+ IncludeItemTypes = new[] { typeof(Series).Name },
+ Recursive = true
+ }).Cast<Series>()
+ .FirstOrDefault(i => string.Equals(i.Name, info.ItemName, StringComparison.OrdinalIgnoreCase));
}
}
diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
index 25463b660..c5cb810e5 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
@@ -179,6 +179,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer
private void OnWebSocketConnecting(WebSocketConnectingEventArgs args)
{
+ if (_disposed)
+ {
+ return;
+ }
+
if (WebSocketConnecting != null)
{
WebSocketConnecting(this, args);
@@ -187,6 +192,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer
private void OnWebSocketConnected(WebSocketConnectEventArgs args)
{
+ if (_disposed)
+ {
+ return;
+ }
+
if (WebSocketConnected != null)
{
WebSocketConnected(this, args);
@@ -331,6 +341,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer
var httpRes = httpReq.Response;
+ if (_disposed)
+ {
+ httpRes.StatusCode = 503;
+ httpRes.Close();
+ return Task.FromResult(true);
+ }
+
var operationName = httpReq.OperationName;
var localPath = url.LocalPath;
diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
index 13a06afc2..09ca134d1 100644
--- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
+++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
@@ -663,7 +663,7 @@ namespace MediaBrowser.Server.Implementations.IO
while (item == null && !string.IsNullOrEmpty(path))
{
- item = LibraryManager.FindByPath(path);
+ item = LibraryManager.FindByPath(path, null);
path = Path.GetDirectoryName(path);
}
diff --git a/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs b/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs
index 9ebae5d91..49012c65a 100644
--- a/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs
+++ b/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs
@@ -102,15 +102,10 @@ namespace MediaBrowser.Server.Implementations.Intros
if (trailerTypes.Count > 0)
{
- var excludeTrailerTypes = Enum.GetNames(typeof(TrailerType))
- .Select(i => (TrailerType)Enum.Parse(typeof(TrailerType), i, true))
- .Except(trailerTypes)
- .ToArray();
-
var trailerResult = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new[] { typeof(Trailer).Name },
- ExcludeTrailerTypes = excludeTrailerTypes
+ TrailerTypes = trailerTypes.ToArray()
});
candidates.AddRange(trailerResult.Select(i => new ItemWithTrailer
diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
index ccba293a3..1407cdce3 100644
--- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
@@ -143,6 +143,7 @@ namespace MediaBrowser.Server.Implementations.Library
private readonly Func<ILibraryMonitor> _libraryMonitorFactory;
private readonly Func<IProviderManager> _providerManagerFactory;
private readonly Func<IUserViewManager> _userviewManager;
+ public bool IsScanRunning { get; private set; }
/// <summary>
/// The _library items cache
@@ -305,9 +306,14 @@ namespace MediaBrowser.Server.Implementations.Library
/// <returns>Task.</returns>
private async Task UpdateSeasonZeroNames(string newName, CancellationToken cancellationToken)
{
- var seasons = RootFolder.GetRecursiveChildren(i => i is Season)
- .Cast<Season>()
- .Where(i => i.IndexNumber.HasValue && i.IndexNumber.Value == 0 && !string.Equals(i.Name, newName, StringComparison.Ordinal))
+ var seasons = GetItemList(new InternalItemsQuery
+ {
+ IncludeItemTypes = new[] { typeof(Season).Name },
+ Recursive = true,
+ IndexNumber = 0
+
+ }).Cast<Season>()
+ .Where(i => !string.Equals(i.Name, newName, StringComparison.Ordinal))
.ToList();
foreach (var season in seasons)
@@ -521,29 +527,7 @@ namespace MediaBrowser.Server.Implementations.Library
throw new ArgumentNullException("items");
}
- var dict = new Dictionary<Guid, BaseItem>();
-
- foreach (var item in items)
- {
- var video = item as Video;
-
- if (video != null)
- {
- if (video.PrimaryVersionId.HasValue)
- {
- var primary = GetItemById(video.PrimaryVersionId.Value) as Video;
-
- if (primary != null)
- {
- dict[primary.Id] = primary;
- continue;
- }
- }
- }
- dict[item.Id] = item;
- }
-
- return dict.Values;
+ return items.DistinctBy(i => i.PresentationUniqueKey, StringComparer.OrdinalIgnoreCase);
}
/// <summary>
@@ -800,27 +784,22 @@ namespace MediaBrowser.Server.Implementations.Library
return _userRootFolder;
}
- public BaseItem FindByPath(string path)
+ public BaseItem FindByPath(string path, bool? isFolder)
{
var query = new InternalItemsQuery
{
- Path = path
+ Path = path,
+ IsFolder = isFolder
};
- // Only use the database result if there's exactly one item, otherwise we run the risk of returning old data that hasn't been cleaned yet.
- var items = GetItemIds(query).Select(GetItemById).Where(i => i != null).ToArray();
+ // If this returns multiple items it could be tricky figuring out which one is correct.
+ // In most cases, the newest one will be and the others obsolete but not yet cleaned up
- if (items.Length == 1)
- {
- return items[0];
- }
-
- if (items.Length == 0)
- {
- return null;
- }
-
- return RootFolder.FindByPath(path);
+ return GetItemIds(query)
+ .Select(GetItemById)
+ .Where(i => i != null)
+ .OrderByDescending(i => i.DateCreated)
+ .FirstOrDefault();
}
/// <summary>
@@ -929,7 +908,10 @@ namespace MediaBrowser.Server.Implementations.Library
throw new ArgumentNullException("name");
}
- var validFilename = _fileSystem.GetValidFilename(name).Trim();
+ // Trim the period at the end because windows will have a hard time with that
+ var validFilename = _fileSystem.GetValidFilename(name)
+ .Trim()
+ .TrimEnd('.');
string subFolderPrefix = null;
@@ -951,31 +933,25 @@ namespace MediaBrowser.Server.Implementations.Library
Path.Combine(path, validFilename) :
Path.Combine(path, subFolderPrefix, validFilename);
- var id = GetNewItemId(fullPath, type);
-
- BaseItem obj;
-
- if (!_libraryItemsCache.TryGetValue(id, out obj))
- {
- obj = CreateItemByName<T>(fullPath, name, id);
-
- RegisterItem(id, obj);
- }
-
- return obj as T;
+ return CreateItemByName<T>(fullPath, name);
}
- private T CreateItemByName<T>(string path, string name, Guid id)
+ private T CreateItemByName<T>(string path, string name)
where T : BaseItem, new()
{
var isArtist = typeof(T) == typeof(MusicArtist);
if (isArtist)
{
- var existing = RootFolder
- .GetRecursiveChildren(i => i is T && NameExtensions.AreEqual(i.Name, name))
- .Cast<T>()
- .FirstOrDefault();
+ var existing = GetItemList(new InternalItemsQuery
+ {
+ IncludeItemTypes = new[] { typeof(T).Name },
+ Name = name
+
+ }).Cast<MusicArtist>()
+ .OrderBy(i => i.IsAccessedByName ? 1 : 0)
+ .Cast<T>()
+ .FirstOrDefault();
if (existing != null)
{
@@ -983,6 +959,8 @@ namespace MediaBrowser.Server.Implementations.Library
}
}
+ var id = GetNewItemId(path, typeof(T));
+
var item = GetItemById(id) as T;
if (item == null)
@@ -996,11 +974,6 @@ namespace MediaBrowser.Server.Implementations.Library
Path = path
};
- if (isArtist)
- {
- (item as MusicArtist).IsAccessedByName = true;
- }
-
var task = CreateItem(item, CancellationToken.None);
Task.WaitAll(task);
}
@@ -1102,6 +1075,7 @@ namespace MediaBrowser.Server.Implementations.Library
/// <returns>Task.</returns>
public async Task ValidateMediaLibraryInternal(IProgress<double> progress, CancellationToken cancellationToken)
{
+ IsScanRunning = true;
_libraryMonitorFactory().Stop();
try
@@ -1111,6 +1085,7 @@ namespace MediaBrowser.Server.Implementations.Library
finally
{
_libraryMonitorFactory().Start();
+ IsScanRunning = false;
}
}
@@ -1369,12 +1344,20 @@ namespace MediaBrowser.Server.Implementations.Library
AddUserToQuery(query, query.User);
}
- var initialResult = ItemRepository.GetItemIds(query);
+ if (query.EnableTotalRecordCount)
+ {
+ var initialResult = ItemRepository.GetItemIds(query);
+
+ return new QueryResult<BaseItem>
+ {
+ TotalRecordCount = initialResult.TotalRecordCount,
+ Items = initialResult.Items.Select(GetItemById).Where(i => i != null).ToArray()
+ };
+ }
return new QueryResult<BaseItem>
{
- TotalRecordCount = initialResult.TotalRecordCount,
- Items = initialResult.Items.Select(GetItemById).Where(i => i != null).ToArray()
+ Items = ItemRepository.GetItemIdsList(query).Select(GetItemById).Where(i => i != null).ToArray()
};
}
@@ -1413,7 +1396,7 @@ namespace MediaBrowser.Server.Implementations.Library
private void AddUserToQuery(InternalItemsQuery query, User user)
{
- if (query.AncestorIds.Length == 0 && !query.ParentId.HasValue && query.ChannelIds.Length == 0 && query.TopParentIds.Length == 0)
+ if (query.AncestorIds.Length == 0 && !query.ParentId.HasValue && query.ChannelIds.Length == 0 && query.TopParentIds.Length == 0 && string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey))
{
var userViews = _userviewManager().GetUserViews(new UserViewQuery
{
@@ -2576,5 +2559,175 @@ namespace MediaBrowser.Server.Implementations.Library
throw new InvalidOperationException();
}
+
+ public void AddVirtualFolder(string name, string collectionType, string[] mediaPaths, bool refreshLibrary)
+ {
+ if (string.IsNullOrWhiteSpace(name))
+ {
+ throw new ArgumentNullException("name");
+ }
+
+ name = _fileSystem.GetValidFilename(name);
+
+ var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+
+ var virtualFolderPath = Path.Combine(rootFolderPath, name);
+ while (_fileSystem.DirectoryExists(virtualFolderPath))
+ {
+ name += "1";
+ virtualFolderPath = Path.Combine(rootFolderPath, name);
+ }
+
+ if (mediaPaths != null)
+ {
+ var invalidpath = mediaPaths.FirstOrDefault(i => !_fileSystem.DirectoryExists(i));
+ if (invalidpath != null)
+ {
+ throw new ArgumentException("The specified path does not exist: " + invalidpath + ".");
+ }
+ }
+
+ _libraryMonitorFactory().Stop();
+
+ try
+ {
+ _fileSystem.CreateDirectory(virtualFolderPath);
+
+ if (!string.IsNullOrEmpty(collectionType))
+ {
+ var path = Path.Combine(virtualFolderPath, collectionType + ".collection");
+
+ using (File.Create(path))
+ {
+
+ }
+ }
+
+ if (mediaPaths != null)
+ {
+ foreach (var path in mediaPaths)
+ {
+ AddMediaPath(name, path);
+ }
+ }
+ }
+ 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();
+ }
+ });
+ }
+ }
+
+ 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))
+ {
+ throw new ArgumentNullException("path");
+ }
+
+ if (!_fileSystem.DirectoryExists(path))
+ {
+ throw new DirectoryNotFoundException("The path does not exist.");
+ }
+
+ var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+ var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
+
+ var shortcutFilename = _fileSystem.GetFileNameWithoutExtension(path);
+
+ var lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
+
+ while (_fileSystem.FileExists(lnk))
+ {
+ shortcutFilename += "1";
+ lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
+ }
+
+ _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);
+ }
+ }
}
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Library/LocalTrailerPostScanTask.cs b/MediaBrowser.Server.Implementations/Library/LocalTrailerPostScanTask.cs
index 96d570ef9..78107b82d 100644
--- a/MediaBrowser.Server.Implementations/Library/LocalTrailerPostScanTask.cs
+++ b/MediaBrowser.Server.Implementations/Library/LocalTrailerPostScanTask.cs
@@ -6,6 +6,8 @@ using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
namespace MediaBrowser.Server.Implementations.Library
{
@@ -22,18 +24,24 @@ namespace MediaBrowser.Server.Implementations.Library
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
- var items = _libraryManager.RootFolder
- .GetRecursiveChildren(i => i is IHasTrailers)
- .Cast<IHasTrailers>()
- .ToList();
+ var items = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ IncludeItemTypes = new[] { typeof(BoxSet).Name, typeof(Game).Name, typeof(Movie).Name, typeof(Series).Name },
+ Recursive = true
+
+ }).OfType<IHasTrailers>().ToList();
+
+ var trailerTypes = Enum.GetNames(typeof(TrailerType))
+ .Select(i => (TrailerType)Enum.Parse(typeof(TrailerType), i, true))
+ .Except(new[] { TrailerType.LocalTrailer })
+ .ToArray();
var trailers = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new[] { typeof(Trailer).Name },
- ExcludeTrailerTypes = new[]
- {
- TrailerType.LocalTrailer
- }
+ TrailerTypes = trailerTypes,
+ Recursive = true
+
}).ToArray();
var numComplete = 0;
diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs
index 95f5cb0e1..a47fcdf4f 100644
--- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs
@@ -267,15 +267,17 @@ namespace MediaBrowser.Server.Implementations.Library
private void SetUserProperties(IHasUserData item, MediaSourceInfo source, User user)
{
- var userData = item == null ? new UserItemData() : _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
+ var userData = item == null ? new UserItemData() : _userDataManager.GetUserData(user, item);
- SetDefaultAudioStreamIndex(source, userData, user);
- SetDefaultSubtitleStreamIndex(source, userData, user);
+ var allowRememberingSelection = item == null || item.EnableRememberingTrackSelections;
+
+ SetDefaultAudioStreamIndex(source, userData, user, allowRememberingSelection);
+ SetDefaultSubtitleStreamIndex(source, userData, user, allowRememberingSelection);
}
- private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user)
+ private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)
{
- if (userData.SubtitleStreamIndex.HasValue && user.Configuration.RememberSubtitleSelections && user.Configuration.SubtitleMode != SubtitlePlaybackMode.None)
+ if (userData.SubtitleStreamIndex.HasValue && user.Configuration.RememberSubtitleSelections && user.Configuration.SubtitleMode != SubtitlePlaybackMode.None && allowRememberingSelection)
{
var index = userData.SubtitleStreamIndex.Value;
// Make sure the saved index is still valid
@@ -304,9 +306,9 @@ namespace MediaBrowser.Server.Implementations.Library
user.Configuration.SubtitleMode, audioLangage);
}
- private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user)
+ private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)
{
- if (userData.AudioStreamIndex.HasValue && user.Configuration.RememberAudioSelections)
+ if (userData.AudioStreamIndex.HasValue && user.Configuration.RememberAudioSelections && allowRememberingSelection)
{
var index = userData.AudioStreamIndex.Value;
// Make sure the saved index is still valid
diff --git a/MediaBrowser.Server.Implementations/Library/MusicManager.cs b/MediaBrowser.Server.Implementations/Library/MusicManager.cs
index aad7c112b..c82c4cdf7 100644
--- a/MediaBrowser.Server.Implementations/Library/MusicManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/MusicManager.cs
@@ -55,7 +55,7 @@ namespace MediaBrowser.Server.Implementations.Library
public IEnumerable<Audio> GetInstantMixFromFolder(Folder item, User user)
{
var genres = item
- .GetRecursiveChildren(user, i => i is Audio)
+ .GetRecursiveChildren(user, i => i is Audio)
.Cast<Audio>()
.SelectMany(i => i.Genres)
.Concat(item.Genres)
diff --git a/MediaBrowser.Server.Implementations/Library/SearchEngine.cs b/MediaBrowser.Server.Implementations/Library/SearchEngine.cs
index 61a5d98a3..e271fbcb2 100644
--- a/MediaBrowser.Server.Implementations/Library/SearchEngine.cs
+++ b/MediaBrowser.Server.Implementations/Library/SearchEngine.cs
@@ -87,13 +87,16 @@ namespace MediaBrowser.Server.Implementations.Library
{
var searchTerm = query.SearchTerm;
+ if (searchTerm != null)
+ {
+ searchTerm = searchTerm.Trim().RemoveDiacritics();
+ }
+
if (string.IsNullOrWhiteSpace(searchTerm))
{
throw new ArgumentNullException("searchTerm");
}
- searchTerm = searchTerm.Trim().RemoveDiacritics();
-
var terms = GetWords(searchTerm);
var hints = new List<Tuple<BaseItem, string, int>>();
diff --git a/MediaBrowser.Server.Implementations/Library/UserDataManager.cs b/MediaBrowser.Server.Implementations/Library/UserDataManager.cs
index ae737d244..0e211937f 100644
--- a/MediaBrowser.Server.Implementations/Library/UserDataManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/UserDataManager.cs
@@ -10,6 +10,7 @@ using MediaBrowser.Model.Logging;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -22,7 +23,7 @@ namespace MediaBrowser.Server.Implementations.Library
{
public event EventHandler<UserDataSaveEventArgs> UserDataSaved;
- private readonly ConcurrentDictionary<string, UserItemData> _userData = new ConcurrentDictionary<string, UserItemData>();
+ private readonly Dictionary<string, UserItemData> _userData = new Dictionary<string, UserItemData>(StringComparer.OrdinalIgnoreCase);
private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
@@ -56,27 +57,32 @@ namespace MediaBrowser.Server.Implementations.Library
cancellationToken.ThrowIfCancellationRequested();
- var key = item.GetUserDataKey();
+ var keys = item.GetUserDataKeys();
- try
+ foreach (var key in keys)
{
- await Repository.SaveUserData(userId, key, userData, cancellationToken).ConfigureAwait(false);
+ try
+ {
+ await Repository.SaveUserData(userId, key, userData, cancellationToken).ConfigureAwait(false);
- var newValue = userData;
+ var newValue = userData;
- // Once it succeeds, put it into the dictionary to make it available to everyone else
- _userData.AddOrUpdate(GetCacheKey(userId, key), newValue, delegate { return newValue; });
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error saving user data", ex);
+ lock (_userData)
+ {
+ _userData[GetCacheKey(userId, key)] = newValue;
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error saving user data", ex);
- throw;
+ throw;
+ }
}
EventHelper.FireEventIfNotNull(UserDataSaved, this, new UserDataSaveEventArgs
{
- Key = key,
+ Keys = keys,
UserData = userData,
SaveReason = reason,
UserId = userId,
@@ -134,6 +140,54 @@ namespace MediaBrowser.Server.Implementations.Library
return Repository.GetAllUserData(userId);
}
+ public UserItemData GetUserData(Guid userId, List<string> keys)
+ {
+ if (userId == Guid.Empty)
+ {
+ throw new ArgumentNullException("userId");
+ }
+ if (keys == null)
+ {
+ throw new ArgumentNullException("keys");
+ }
+
+ lock (_userData)
+ {
+ foreach (var key in keys)
+ {
+ var cacheKey = GetCacheKey(userId, key);
+ UserItemData value;
+ if (_userData.TryGetValue(cacheKey, out value))
+ {
+ return value;
+ }
+
+ value = Repository.GetUserData(userId, key);
+
+ if (value != null)
+ {
+ _userData[cacheKey] = value;
+ return value;
+ }
+ }
+
+ if (keys.Count > 0)
+ {
+ var key = keys[0];
+ var cacheKey = GetCacheKey(userId, key);
+ var userdata = new UserItemData
+ {
+ UserId = userId,
+ Key = key
+ };
+ _userData[cacheKey] = userdata;
+ return userdata;
+ }
+
+ return null;
+ }
+ }
+
/// <summary>
/// Gets the user data.
/// </summary>
@@ -151,14 +205,29 @@ namespace MediaBrowser.Server.Implementations.Library
throw new ArgumentNullException("key");
}
- return _userData.GetOrAdd(GetCacheKey(userId, key), keyName => GetUserDataFromRepository(userId, key));
- }
+ lock (_userData)
+ {
+ var cacheKey = GetCacheKey(userId, key);
+ UserItemData value;
+ if (_userData.TryGetValue(cacheKey, out value))
+ {
+ return value;
+ }
- public UserItemData GetUserDataFromRepository(Guid userId, string key)
- {
- var data = Repository.GetUserData(userId, key);
+ value = Repository.GetUserData(userId, key);
- return data;
+ if (value == null)
+ {
+ value = new UserItemData
+ {
+ UserId = userId,
+ Key = key
+ };
+ }
+
+ _userData[cacheKey] = value;
+ return value;
+ }
}
/// <summary>
@@ -172,9 +241,24 @@ namespace MediaBrowser.Server.Implementations.Library
return userId + key;
}
+ public UserItemData GetUserData(IHasUserData user, IHasUserData item)
+ {
+ return GetUserData(user.Id, item);
+ }
+
+ public UserItemData GetUserData(string userId, IHasUserData item)
+ {
+ return GetUserData(new Guid(userId), item);
+ }
+
+ public UserItemData GetUserData(Guid userId, IHasUserData item)
+ {
+ return GetUserData(userId, item.GetUserDataKeys());
+ }
+
public UserItemDataDto GetUserDataDto(IHasUserData item, User user)
{
- var userData = GetUserData(user.Id, item.GetUserDataKey());
+ var userData = GetUserData(user.Id, item);
var dto = GetUserItemDataDto(userData);
item.FillUserDataDtoValues(dto, userData, user);
@@ -261,10 +345,5 @@ namespace MediaBrowser.Server.Implementations.Library
return playedToCompletion;
}
-
- public UserItemData GetUserData(string userId, string key)
- {
- return GetUserData(new Guid(userId), key);
- }
}
}
diff --git a/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs
index c122d64d3..c1803b5e4 100644
--- a/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs
+++ b/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs
@@ -35,7 +35,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
/// <returns>Task.</returns>
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
- var items = _libraryManager.RootFolder.GetRecursiveChildren()
+ var items = _libraryManager.RootFolder.GetRecursiveChildren(i => true)
.SelectMany(i => i.Studios)
.DistinctNames()
.ToList();
@@ -73,28 +73,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
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);
}
}
diff --git a/MediaBrowser.Server.Implementations/Library/Validators/YearsPostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/YearsPostScanTask.cs
index 5ea5fb254..7f52a4506 100644
--- a/MediaBrowser.Server.Implementations/Library/Validators/YearsPostScanTask.cs
+++ b/MediaBrowser.Server.Implementations/Library/Validators/YearsPostScanTask.cs
@@ -20,16 +20,12 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
- var allYears = _libraryManager.RootFolder.GetRecursiveChildren(i => i.ProductionYear.HasValue)
- .Select(i => i.ProductionYear ?? -1)
- .Where(i => i > 0)
- .Distinct()
- .ToList();
-
- var count = allYears.Count;
+ var yearNumber = 1900;
+ var maxYear = DateTime.UtcNow.Year + 3;
+ var count = maxYear - yearNumber + 1;
var numComplete = 0;
- foreach (var yearNumber in allYears)
+ while (yearNumber < maxYear)
{
try
{
@@ -53,6 +49,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
percent *= 100;
progress.Report(percent);
+ yearNumber++;
}
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
index 3c7ee55b7..b21aa904b 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
@@ -23,6 +23,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_fileSystem = fileSystem;
}
+ public string GetOutputPath(MediaSourceInfo mediaSource, string targetFile)
+ {
+ return targetFile;
+ }
+
public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
var httpRequestOptions = new HttpRequestOptions()
@@ -42,10 +47,21 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_logger.Info("Copying recording stream to file {0}", targetFile);
- var durationToken = new CancellationTokenSource(duration);
- var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
+ if (mediaSource.RunTimeTicks.HasValue)
+ {
+ // The media source already has a fixed duration
+ // But add another stop 1 minute later just in case the recording gets stuck for any reason
+ var durationToken = new CancellationTokenSource(duration.Add(TimeSpan.FromMinutes(1)));
+ cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
+ }
+ else
+ {
+ // The media source if infinite so we need to handle stopping ourselves
+ var durationToken = new CancellationTokenSource(duration);
+ cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
+ }
- await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, linkedToken).ConfigureAwait(false);
+ await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 60ff23b04..2de51479f 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/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)
@@ -591,7 +648,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
throw new ApplicationException("Tuner not found.");
}
- private async Task<Tuple<MediaSourceInfo, SemaphoreSlim>> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken)
+ private async Task<Tuple<MediaSourceInfo, ITunerHost, SemaphoreSlim>> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken)
{
_logger.Info("Streaming Channel " + channelId);
@@ -599,7 +656,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
try
{
- return await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false);
+ var result = await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false);
+
+ return new Tuple<MediaSourceInfo, ITunerHost, SemaphoreSlim>(result.Item1, hostInstance, result.Item2);
}
catch (Exception e)
{
@@ -693,6 +752,106 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
}
+ private string GetRecordingPath(TimerInfo timer, ProgramInfo info)
+ {
+ var recordPath = RecordingPath;
+ var config = GetConfiguration();
+
+ if (info.IsMovie)
+ {
+ var customRecordingPath = config.MovieRecordingPath;
+ var allowSubfolder = true;
+ if (!string.IsNullOrWhiteSpace(customRecordingPath))
+ {
+ allowSubfolder = string.Equals(customRecordingPath, recordPath, StringComparison.OrdinalIgnoreCase);
+ recordPath = customRecordingPath;
+ }
+
+ if (allowSubfolder && 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)
+ {
+ var customRecordingPath = config.SeriesRecordingPath;
+ var allowSubfolder = true;
+ if (!string.IsNullOrWhiteSpace(customRecordingPath))
+ {
+ allowSubfolder = string.Equals(customRecordingPath, recordPath, StringComparison.OrdinalIgnoreCase);
+ recordPath = customRecordingPath;
+ }
+
+ if (allowSubfolder && 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)
+ {
+ folderName = string.Format("Season {0}", info.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture));
+ recordPath = Path.Combine(recordPath, folderName);
+ }
+ }
+ else if (info.IsKids)
+ {
+ 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)
+ {
+ if (config.EnableRecordingSubfolders)
+ {
+ recordPath = Path.Combine(recordPath, "Sports");
+ }
+ recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(info.Name).Trim());
+ }
+ else
+ {
+ 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";
+
+ return Path.Combine(recordPath, recordingFileName);
+ }
+
private async Task RecordStream(TimerInfo timer, DateTime recordingEndDate, ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken)
{
if (timer == null)
@@ -722,68 +881,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
throw new InvalidOperationException(string.Format("Program with Id {0} not found", timer.ProgramId));
}
- var recordPath = RecordingPath;
-
- if (info.IsMovie)
- {
- recordPath = Path.Combine(recordPath, "Movies", _fileSystem.GetValidFilename(info.Name).Trim());
- }
- else if (info.IsSeries)
- {
- recordPath = Path.Combine(recordPath, "Series", _fileSystem.GetValidFilename(info.Name).Trim());
- }
- else if (info.IsKids)
- {
- recordPath = Path.Combine(recordPath, "Kids", _fileSystem.GetValidFilename(info.Name).Trim());
- }
- else if (info.IsSports)
- {
- recordPath = Path.Combine(recordPath, "Sports", _fileSystem.GetValidFilename(info.Name).Trim());
- }
- else
- {
- recordPath = Path.Combine(recordPath, "Other", _fileSystem.GetValidFilename(info.Name).Trim());
- }
-
- var recordingFileName = _fileSystem.GetValidFilename(RecordingHelper.GetRecordingName(timer, info)).Trim() + ".ts";
-
- recordPath = Path.Combine(recordPath, recordingFileName);
-
- var recordingId = info.Id.GetMD5().ToString("N");
- var recording = _recordingProvider.GetAll().FirstOrDefault(x => string.Equals(x.Id, recordingId, StringComparison.OrdinalIgnoreCase));
-
- if (recording == null)
- {
- 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);
- }
+ var recordPath = GetRecordingPath(timer, info);
+ var recordingStatus = RecordingStatus.New;
try
{
@@ -797,24 +896,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
// HDHR doesn't seem to release the tuner right away after first probing with ffmpeg
//await Task.Delay(3000, cancellationToken).ConfigureAwait(false);
- var duration = recordingEndDate - DateTime.UtcNow;
-
var recorder = await GetRecorder().ConfigureAwait(false);
- if (recorder is EncodedRecorder)
- {
- recordPath = Path.ChangeExtension(recordPath, ".mp4");
- }
+ recordPath = recorder.GetOutputPath(mediaStreamInfo, recordPath);
recordPath = EnsureFileUnique(recordPath, timer.Id);
_fileSystem.CreateDirectory(Path.GetDirectoryName(recordPath));
activeRecordingInfo.Path = recordPath;
_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));
@@ -823,20 +914,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
Action onStarted = () =>
{
- result.Item2.Release();
+ result.Item3.Release();
isResourceOpen = false;
};
+ var pathWithDuration = result.Item2.ApplyDuration(mediaStreamInfo.Path, duration);
+
+ // If it supports supplying duration via url
+ if (!string.Equals(pathWithDuration, mediaStreamInfo.Path, StringComparison.OrdinalIgnoreCase))
+ {
+ mediaStreamInfo.Path = pathWithDuration;
+ mediaStreamInfo.RunTimeTicks = duration.Ticks;
+ }
+
await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken).ConfigureAwait(false);
- recording.Status = RecordingStatus.Completed;
+ recordingStatus = RecordingStatus.Completed;
_logger.Info("Recording completed: {0}", recordPath);
}
finally
{
if (isResourceOpen)
{
- result.Item2.Release();
+ result.Item3.Release();
}
_libraryMonitor.ReportFileSystemChangeComplete(recordPath, false);
@@ -845,12 +945,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
{
@@ -858,12 +958,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)
@@ -876,7 +973,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
else
{
_timerProvider.Delete(timer);
- _recordingProvider.Delete(recording);
}
}
@@ -916,24 +1012,26 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
private async Task<IRecorder> GetRecorder()
{
- if (GetConfiguration().EnableRecordingEncoding)
+ var config = GetConfiguration();
+
+ if (config.EnableRecordingEncoding)
{
var regInfo = await _security.GetRegistrationStatus("embytvrecordingconversion").ConfigureAwait(false);
if (regInfo.IsValid)
{
- return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer);
+ return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, config);
}
}
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
{
@@ -943,12 +1041,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)
{
@@ -972,18 +1065,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");
@@ -991,7 +1072,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);
@@ -1005,7 +1086,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();
@@ -1021,7 +1102,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)
{
@@ -1031,23 +1114,73 @@ 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,
+ ExcludeLocationTypes = new[] { LocationType.Virtual }
+ });
+
+ 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,
+ ExcludeLocationTypes = new[] { LocationType.Virtual }
+ });
+
+ if (result.TotalRecordCount > 0)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
private IEnumerable<ProgramInfo> GetProgramsForSeries(SeriesTimerInfo seriesTimer, IEnumerable<ProgramInfo> allPrograms)
{
if (!seriesTimer.RecordAnyTime)
@@ -1132,6 +1265,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; }
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index 442f151dd..e9ea49fa3 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -12,6 +12,7 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
@@ -23,6 +24,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
private readonly IFileSystem _fileSystem;
private readonly IMediaEncoder _mediaEncoder;
private readonly IApplicationPaths _appPaths;
+ private readonly LiveTvOptions _liveTvOptions;
private bool _hasExited;
private Stream _logFileStream;
private string _targetPath;
@@ -30,17 +32,47 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
private readonly IJsonSerializer _json;
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
- public EncodedRecorder(ILogger logger, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IApplicationPaths appPaths, IJsonSerializer json)
+ public EncodedRecorder(ILogger logger, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IApplicationPaths appPaths, IJsonSerializer json, LiveTvOptions liveTvOptions)
{
_logger = logger;
_fileSystem = fileSystem;
_mediaEncoder = mediaEncoder;
_appPaths = appPaths;
_json = json;
+ _liveTvOptions = liveTvOptions;
+ }
+
+ public string GetOutputPath(MediaSourceInfo mediaSource, string targetFile)
+ {
+ if (_liveTvOptions.EnableOriginalAudioWithEncodedRecordings)
+ {
+ // if the audio is aac_latm, stream copying to mp4 will fail
+ var streams = mediaSource.MediaStreams ?? new List<MediaStream>();
+ if (streams.Any(i => i.Type == MediaStreamType.Audio && (i.Codec ?? string.Empty).IndexOf("aac", StringComparison.OrdinalIgnoreCase) != -1))
+ {
+ return Path.ChangeExtension(targetFile, ".mkv");
+ }
+ }
+
+ return Path.ChangeExtension(targetFile, ".mp4");
}
public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
+ if (mediaSource.RunTimeTicks.HasValue)
+ {
+ // The media source already has a fixed duration
+ // But add another stop 1 minute later just in case the recording gets stuck for any reason
+ var durationToken = new CancellationTokenSource(duration.Add(TimeSpan.FromMinutes(1)));
+ cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
+ }
+ else
+ {
+ // The media source if infinite so we need to handle stopping ourselves
+ var durationToken = new CancellationTokenSource(duration);
+ cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
+ }
+
_targetPath = targetFile;
_fileSystem.CreateDirectory(Path.GetDirectoryName(targetFile));
@@ -104,7 +136,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
var maxBitrate = 25000000;
videoArgs = string.Format(
- "-codec:v:0 libx264 -force_key_frames expr:gte(t,n_forced*5) {0} -pix_fmt yuv420p -preset superfast -crf 23 -b:v {1} -maxrate {1} -bufsize ({1}*2) -vsync vfr -profile:v high -level 41",
+ "-codec:v:0 libx264 -force_key_frames expr:gte(t,n_forced*5) {0} -pix_fmt yuv420p -preset superfast -crf 23 -b:v {1} -maxrate {1} -bufsize ({1}*2) -vsync -1 -profile:v high -level 41",
GetOutputSizeParam(),
maxBitrate.ToString(CultureInfo.InvariantCulture));
}
@@ -129,7 +161,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
var copyAudio = new[] { "aac", "mp3" };
var mediaStreams = mediaSource.MediaStreams ?? new List<MediaStream>();
- if (mediaStreams.Any(i => i.Type == MediaStreamType.Audio && copyAudio.Contains(i.Codec, StringComparer.OrdinalIgnoreCase)))
+ if (_liveTvOptions.EnableOriginalAudioWithEncodedRecordings || mediaStreams.Any(i => i.Type == MediaStreamType.Audio && copyAudio.Contains(i.Codec, StringComparer.OrdinalIgnoreCase)))
{
return "-codec:a:0 copy";
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs
index 268a4f751..5706b6ae9 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs
@@ -17,5 +17,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken);
+
+ string GetOutputPath(MediaSourceInfo mediaSource, string targetFile);
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
index ab2b59d48..f85be5100 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -135,7 +135,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var channels = _libraryManager.GetItemList(new InternalItemsQuery
{
- IncludeItemTypes = new[] { typeof(LiveTvChannel).Name }
+ IncludeItemTypes = new[] { typeof(LiveTvChannel).Name },
+ SortBy = new[] { ItemSortBy.SortName }
}).Cast<LiveTvChannel>();
@@ -164,7 +165,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var val = query.IsFavorite.Value;
channels = channels
- .Where(i => _userDataManager.GetUserData(user.Id, i.GetUserDataKey()).IsFavorite == val);
+ .Where(i => _userDataManager.GetUserData(user, i).IsFavorite == val);
}
if (query.IsLiked.HasValue)
@@ -174,7 +175,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
channels = channels
.Where(i =>
{
- var likes = _userDataManager.GetUserData(user.Id, i.GetUserDataKey()).Likes;
+ var likes = _userDataManager.GetUserData(user, i).Likes;
return likes.HasValue && likes.Value == val;
});
@@ -187,7 +188,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
channels = channels
.Where(i =>
{
- var likes = _userDataManager.GetUserData(user.Id, i.GetUserDataKey()).Likes;
+ var likes = _userDataManager.GetUserData(user, i).Likes;
return likes.HasValue && likes.Value != val;
});
@@ -200,7 +201,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
if (enableFavoriteSorting)
{
- var userData = _userDataManager.GetUserData(user.Id, i.GetUserDataKey());
+ var userData = _userDataManager.GetUserData(user, i);
if (userData.IsFavorite)
{
@@ -886,7 +887,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
StartIndex = query.StartIndex,
Limit = query.Limit,
SortBy = query.SortBy,
- SortOrder = query.SortOrder ?? SortOrder.Ascending
+ SortOrder = query.SortOrder ?? SortOrder.Ascending,
+ EnableTotalRecordCount = query.EnableTotalRecordCount
};
if (query.HasAired.HasValue)
@@ -924,7 +926,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
IsAiring = query.IsAiring,
IsMovie = query.IsMovie,
IsSports = query.IsSports,
- IsKids = query.IsKids
+ IsKids = query.IsKids,
+ EnableTotalRecordCount = query.EnableTotalRecordCount
};
if (query.HasAired.HasValue)
@@ -1005,7 +1008,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var channel = GetInternalChannel(program.ChannelId);
- var channelUserdata = _userDataManager.GetUserData(userId, channel.GetUserDataKey());
+ var channelUserdata = _userDataManager.GetUserData(userId, channel);
if (channelUserdata.Likes ?? false)
{
@@ -1036,7 +1039,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
if (genres.TryGetValue(i, out genre))
{
- var genreUserdata = _userDataManager.GetUserData(userId, genre.GetUserDataKey());
+ var genreUserdata = _userDataManager.GetUserData(userId, genre);
if (genreUserdata.Likes ?? false)
{
@@ -1263,11 +1266,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
private async Task CleanDatabaseInternal(List<Guid> currentIdList, string[] validTypes, IProgress<double> progress, CancellationToken cancellationToken)
{
- var list = _itemRepo.GetItemIds(new InternalItemsQuery
+ var list = _itemRepo.GetItemIdsList(new InternalItemsQuery
{
IncludeItemTypes = validTypes
- }).Items.ToList();
+ }).ToList();
var numComplete = 0;
@@ -1513,6 +1516,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
dto.ChannelName = channel.Name;
dto.MediaType = channel.MediaType;
+ dto.ChannelNumber = channel.Number;
if (channel.HasImage(ImageType.Primary))
{
@@ -1852,6 +1856,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var channel = tuple.Item2;
dto.Number = channel.Number;
+ dto.ChannelNumber = channel.Number;
dto.ChannelType = channel.ChannelType;
dto.ServiceName = GetService(channel).Name;
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
index 02a8d6938..9bb5b4fd7 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
@@ -224,7 +224,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
var stream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false);
- //await AddMediaInfo(stream, false, resourcePool, cancellationToken).ConfigureAwait(false);
+ if (EnableMediaProbing)
+ {
+ await AddMediaInfo(stream, false, resourcePool, cancellationToken).ConfigureAwait(false);
+ }
+
return new Tuple<MediaSourceInfo, SemaphoreSlim>(stream, resourcePool);
}
catch (Exception ex)
@@ -239,6 +243,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
throw new LiveTvConflictException();
}
+ protected virtual bool EnableMediaProbing
+ {
+ get { return false; }
+ }
+
protected async Task<bool> IsAvailable(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken)
{
try
@@ -268,6 +277,25 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
return _semaphoreLocks.GetOrAdd(url, key => new SemaphoreSlim(1, 1));
}
+ private async Task AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
+ {
+ await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+ try
+ {
+ await AddMediaInfoInternal(mediaSource, isAudio, cancellationToken).ConfigureAwait(false);
+
+ // Leave the resource locked. it will be released upstream
+ }
+ catch (Exception)
+ {
+ // Release the resource if there's some kind of failure.
+ resourcePool.Release();
+
+ throw;
+ }
+ }
+
private async Task AddMediaInfoInternal(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken)
{
var originalRuntime = mediaSource.RunTimeTicks;
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index 469767c65..a3e5589e8 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -59,6 +59,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return id;
}
+ public string ApplyDuration(string streamPath, TimeSpan duration)
+ {
+ streamPath += streamPath.IndexOf('?') == -1 ? "?" : "&";
+ streamPath += "duration=" + Convert.ToInt32(duration.TotalSeconds).ToString(CultureInfo.InvariantCulture);
+
+ return streamPath;
+ }
+
private async Task<IEnumerable<Channels>> GetLineup(TunerHostInfo info, CancellationToken cancellationToken)
{
var options = new HttpRequestOptions
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index 523f14dfc..2a974b545 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -134,7 +134,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
}
},
RequiresOpening = false,
- RequiresClosing = false
+ RequiresClosing = false,
+
+ ReadAtNativeFramerate = true
};
return new List<MediaSourceInfo> { mediaSource };
@@ -146,5 +148,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
{
return Task.FromResult(true);
}
+
+ public string ApplyDuration(string streamPath, TimeSpan duration)
+ {
+ return streamPath;
+ }
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs
index ffd85fd18..1e571c84f 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs
@@ -164,5 +164,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
return list;
}
+
+ public string ApplyDuration(string streamPath, TimeSpan duration)
+ {
+ return streamPath;
+ }
}
}
diff --git a/MediaBrowser.Server.Implementations/Localization/Core/en-US.json b/MediaBrowser.Server.Implementations/Localization/Core/en-US.json
index 0d5b5c4aa..5e2f98c09 100644
--- a/MediaBrowser.Server.Implementations/Localization/Core/en-US.json
+++ b/MediaBrowser.Server.Implementations/Localization/Core/en-US.json
@@ -1,178 +1,179 @@
{
- "DbUpgradeMessage": "Please wait while your Emby Server database is upgraded. {0}% complete.",
- "AppDeviceValues": "App: {0}, Device: {1}",
- "UserDownloadingItemWithValues": "{0} is downloading {1}",
- "FolderTypeMixed": "Mixed content",
- "FolderTypeMovies": "Movies",
- "FolderTypeMusic": "Music",
- "FolderTypeAdultVideos": "Adult videos",
- "FolderTypePhotos": "Photos",
- "FolderTypeMusicVideos": "Music videos",
- "FolderTypeHomeVideos": "Home videos",
- "FolderTypeGames": "Games",
- "FolderTypeBooks": "Books",
- "FolderTypeTvShows": "TV",
- "FolderTypeInherit": "Inherit",
- "HeaderCastCrew": "Cast & Crew",
- "HeaderPeople": "People",
- "ValueSpecialEpisodeName": "Special - {0}",
- "LabelChapterName": "Chapter {0}",
- "NameSeasonNumber": "Season {0}",
- "LabelExit": "Exit",
- "LabelVisitCommunity": "Visit Community",
- "LabelGithub": "Github",
- "LabelApiDocumentation": "Api Documentation",
- "LabelDeveloperResources": "Developer Resources",
- "LabelBrowseLibrary": "Browse Library",
- "LabelConfigureServer": "Configure Emby",
- "LabelRestartServer": "Restart Server",
- "CategorySync": "Sync",
- "CategoryUser": "User",
- "CategorySystem": "System",
- "CategoryApplication": "Application",
- "CategoryPlugin": "Plugin",
- "NotificationOptionPluginError": "Plugin failure",
- "NotificationOptionApplicationUpdateAvailable": "Application update available",
- "NotificationOptionApplicationUpdateInstalled": "Application update installed",
- "NotificationOptionPluginUpdateInstalled": "Plugin update installed",
- "NotificationOptionPluginInstalled": "Plugin installed",
- "NotificationOptionPluginUninstalled": "Plugin uninstalled",
- "NotificationOptionVideoPlayback": "Video playback started",
- "NotificationOptionAudioPlayback": "Audio playback started",
- "NotificationOptionGamePlayback": "Game playback started",
- "NotificationOptionVideoPlaybackStopped": "Video playback stopped",
- "NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
- "NotificationOptionGamePlaybackStopped": "Game playback stopped",
- "NotificationOptionTaskFailed": "Scheduled task failure",
- "NotificationOptionInstallationFailed": "Installation failure",
- "NotificationOptionNewLibraryContent": "New content added",
- "NotificationOptionNewLibraryContentMultiple": "New content added (multiple)",
- "NotificationOptionCameraImageUploaded": "Camera image uploaded",
- "NotificationOptionUserLockedOut": "User locked out",
- "NotificationOptionServerRestartRequired": "Server restart required",
- "ViewTypePlaylists": "Playlists",
- "ViewTypeMovies": "Movies",
- "ViewTypeTvShows": "TV",
- "ViewTypeGames": "Games",
- "ViewTypeMusic": "Music",
- "ViewTypeMusicGenres": "Genres",
- "ViewTypeMusicArtists": "Artists",
- "ViewTypeBoxSets": "Collections",
- "ViewTypeChannels": "Channels",
- "ViewTypeLiveTV": "Live TV",
- "ViewTypeLiveTvNowPlaying": "Now Airing",
- "ViewTypeLatestGames": "Latest Games",
- "ViewTypeRecentlyPlayedGames": "Recently Played",
- "ViewTypeGameFavorites": "Favorites",
- "ViewTypeGameSystems": "Game Systems",
- "ViewTypeGameGenres": "Genres",
- "ViewTypeTvResume": "Resume",
- "ViewTypeTvNextUp": "Next Up",
- "ViewTypeTvLatest": "Latest",
- "ViewTypeTvShowSeries": "Series",
- "ViewTypeTvGenres": "Genres",
- "ViewTypeTvFavoriteSeries": "Favorite Series",
- "ViewTypeTvFavoriteEpisodes": "Favorite Episodes",
- "ViewTypeMovieResume": "Resume",
- "ViewTypeMovieLatest": "Latest",
- "ViewTypeMovieMovies": "Movies",
- "ViewTypeMovieCollections": "Collections",
- "ViewTypeMovieFavorites": "Favorites",
- "ViewTypeMovieGenres": "Genres",
- "ViewTypeMusicLatest": "Latest",
- "ViewTypeMusicPlaylists": "Playlists",
- "ViewTypeMusicAlbums": "Albums",
- "ViewTypeMusicAlbumArtists": "Album Artists",
- "HeaderOtherDisplaySettings": "Display Settings",
- "ViewTypeMusicSongs": "Songs",
- "ViewTypeMusicFavorites": "Favorites",
- "ViewTypeMusicFavoriteAlbums": "Favorite Albums",
- "ViewTypeMusicFavoriteArtists": "Favorite Artists",
- "ViewTypeMusicFavoriteSongs": "Favorite Songs",
- "ViewTypeFolders": "Folders",
- "ViewTypeLiveTvRecordingGroups": "Recordings",
- "ViewTypeLiveTvChannels": "Channels",
- "ScheduledTaskFailedWithName": "{0} failed",
- "LabelRunningTimeValue": "Running time: {0}",
- "ScheduledTaskStartedWithName": "{0} started",
- "VersionNumber": "Version {0}",
- "PluginInstalledWithName": "{0} was installed",
- "PluginUpdatedWithName": "{0} was updated",
- "PluginUninstalledWithName": "{0} was uninstalled",
- "ItemAddedWithName": "{0} was added to the library",
- "ItemRemovedWithName": "{0} was removed from the library",
- "LabelIpAddressValue": "Ip address: {0}",
- "DeviceOnlineWithName": "{0} is connected",
- "UserOnlineFromDevice": "{0} is online from {1}",
- "ProviderValue": "Provider: {0}",
- "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
- "UserConfigurationUpdatedWithName": "User configuration has been updated for {0}",
- "UserCreatedWithName": "User {0} has been created",
- "UserPasswordChangedWithName": "Password has been changed for user {0}",
- "UserDeletedWithName": "User {0} has been deleted",
- "MessageServerConfigurationUpdated": "Server configuration has been updated",
- "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
- "MessageApplicationUpdated": "Emby Server has been updated",
- "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
- "AuthenticationSucceededWithUserName": "{0} successfully authenticated",
- "DeviceOfflineWithName": "{0} has disconnected",
- "UserLockedOutWithName": "User {0} has been locked out",
- "UserOfflineFromDevice": "{0} has disconnected from {1}",
- "UserStartedPlayingItemWithValues": "{0} has started playing {1}",
- "UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}",
- "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
- "HeaderUnidentified": "Unidentified",
- "HeaderImagePrimary": "Primary",
- "HeaderImageBackdrop": "Backdrop",
- "HeaderImageLogo": "Logo",
- "HeaderUserPrimaryImage": "User Image",
- "HeaderOverview": "Overview",
- "HeaderShortOverview": "Short Overview",
- "HeaderType": "Type",
- "HeaderSeverity": "Severity",
- "HeaderUser": "User",
- "HeaderName": "Name",
- "HeaderDate": "Date",
- "HeaderPremiereDate": "Premiere Date",
- "HeaderDateAdded": "Date Added",
- "HeaderReleaseDate": "Release date",
- "HeaderRuntime": "Runtime",
- "HeaderPlayCount": "Play Count",
- "HeaderSeason": "Season",
- "HeaderSeasonNumber": "Season number",
- "HeaderSeries": "Series:",
- "HeaderNetwork": "Network",
- "HeaderYear": "Year:",
- "HeaderYears": "Years:",
- "HeaderParentalRating": "Parental Rating",
- "HeaderCommunityRating": "Community rating",
- "HeaderTrailers": "Trailers",
- "HeaderSpecials": "Specials",
- "HeaderGameSystems": "Game Systems",
- "HeaderPlayers": "Players:",
- "HeaderAlbumArtists": "Album Artists",
- "HeaderAlbums": "Albums",
- "HeaderDisc": "Disc",
- "HeaderTrack": "Track",
- "HeaderAudio": "Audio",
- "HeaderVideo": "Video",
- "HeaderEmbeddedImage": "Embedded image",
- "HeaderResolution": "Resolution",
- "HeaderSubtitles": "Subtitles",
- "HeaderGenres": "Genres",
- "HeaderCountries": "Countries",
- "HeaderStatus": "Status",
- "HeaderTracks": "Tracks",
- "HeaderMusicArtist": "Music artist",
- "HeaderLocked": "Locked",
- "HeaderStudios": "Studios",
- "HeaderActor": "Actors",
- "HeaderComposer": "Composers",
- "HeaderDirector": "Directors",
- "HeaderGuestStar": "Guest star",
- "HeaderProducer": "Producers",
- "HeaderWriter": "Writers",
- "HeaderParentalRatings": "Parental Ratings",
- "HeaderCommunityRatings": "Community ratings",
- "StartupEmbyServerIsLoading": "Emby Server is loading. Please try again shortly."
+ "DbUpgradeMessage": "Please wait while your Emby Server database is upgraded. {0}% complete.",
+ "AppDeviceValues": "App: {0}, Device: {1}",
+ "UserDownloadingItemWithValues": "{0} is downloading {1}",
+ "FolderTypeMixed": "Mixed content",
+ "FolderTypeMovies": "Movies",
+ "FolderTypeMusic": "Music",
+ "FolderTypeAdultVideos": "Adult videos",
+ "FolderTypePhotos": "Photos",
+ "FolderTypeMusicVideos": "Music videos",
+ "FolderTypeHomeVideos": "Home videos",
+ "FolderTypeGames": "Games",
+ "FolderTypeBooks": "Books",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "Inherit",
+ "HeaderCastCrew": "Cast & Crew",
+ "HeaderPeople": "People",
+ "ValueSpecialEpisodeName": "Special - {0}",
+ "LabelChapterName": "Chapter {0}",
+ "NameSeasonUnknown": "Season Unknown",
+ "NameSeasonNumber": "Season {0}",
+ "LabelExit": "Exit",
+ "LabelVisitCommunity": "Visit Community",
+ "LabelGithub": "Github",
+ "LabelApiDocumentation": "Api Documentation",
+ "LabelDeveloperResources": "Developer Resources",
+ "LabelBrowseLibrary": "Browse Library",
+ "LabelConfigureServer": "Configure Emby",
+ "LabelRestartServer": "Restart Server",
+ "CategorySync": "Sync",
+ "CategoryUser": "User",
+ "CategorySystem": "System",
+ "CategoryApplication": "Application",
+ "CategoryPlugin": "Plugin",
+ "NotificationOptionPluginError": "Plugin failure",
+ "NotificationOptionApplicationUpdateAvailable": "Application update available",
+ "NotificationOptionApplicationUpdateInstalled": "Application update installed",
+ "NotificationOptionPluginUpdateInstalled": "Plugin update installed",
+ "NotificationOptionPluginInstalled": "Plugin installed",
+ "NotificationOptionPluginUninstalled": "Plugin uninstalled",
+ "NotificationOptionVideoPlayback": "Video playback started",
+ "NotificationOptionAudioPlayback": "Audio playback started",
+ "NotificationOptionGamePlayback": "Game playback started",
+ "NotificationOptionVideoPlaybackStopped": "Video playback stopped",
+ "NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
+ "NotificationOptionGamePlaybackStopped": "Game playback stopped",
+ "NotificationOptionTaskFailed": "Scheduled task failure",
+ "NotificationOptionInstallationFailed": "Installation failure",
+ "NotificationOptionNewLibraryContent": "New content added",
+ "NotificationOptionNewLibraryContentMultiple": "New content added (multiple)",
+ "NotificationOptionCameraImageUploaded": "Camera image uploaded",
+ "NotificationOptionUserLockedOut": "User locked out",
+ "NotificationOptionServerRestartRequired": "Server restart required",
+ "ViewTypePlaylists": "Playlists",
+ "ViewTypeMovies": "Movies",
+ "ViewTypeTvShows": "TV",
+ "ViewTypeGames": "Games",
+ "ViewTypeMusic": "Music",
+ "ViewTypeMusicGenres": "Genres",
+ "ViewTypeMusicArtists": "Artists",
+ "ViewTypeBoxSets": "Collections",
+ "ViewTypeChannels": "Channels",
+ "ViewTypeLiveTV": "Live TV",
+ "ViewTypeLiveTvNowPlaying": "Now Airing",
+ "ViewTypeLatestGames": "Latest Games",
+ "ViewTypeRecentlyPlayedGames": "Recently Played",
+ "ViewTypeGameFavorites": "Favorites",
+ "ViewTypeGameSystems": "Game Systems",
+ "ViewTypeGameGenres": "Genres",
+ "ViewTypeTvResume": "Resume",
+ "ViewTypeTvNextUp": "Next Up",
+ "ViewTypeTvLatest": "Latest",
+ "ViewTypeTvShowSeries": "Series",
+ "ViewTypeTvGenres": "Genres",
+ "ViewTypeTvFavoriteSeries": "Favorite Series",
+ "ViewTypeTvFavoriteEpisodes": "Favorite Episodes",
+ "ViewTypeMovieResume": "Resume",
+ "ViewTypeMovieLatest": "Latest",
+ "ViewTypeMovieMovies": "Movies",
+ "ViewTypeMovieCollections": "Collections",
+ "ViewTypeMovieFavorites": "Favorites",
+ "ViewTypeMovieGenres": "Genres",
+ "ViewTypeMusicLatest": "Latest",
+ "ViewTypeMusicPlaylists": "Playlists",
+ "ViewTypeMusicAlbums": "Albums",
+ "ViewTypeMusicAlbumArtists": "Album Artists",
+ "HeaderOtherDisplaySettings": "Display Settings",
+ "ViewTypeMusicSongs": "Songs",
+ "ViewTypeMusicFavorites": "Favorites",
+ "ViewTypeMusicFavoriteAlbums": "Favorite Albums",
+ "ViewTypeMusicFavoriteArtists": "Favorite Artists",
+ "ViewTypeMusicFavoriteSongs": "Favorite Songs",
+ "ViewTypeFolders": "Folders",
+ "ViewTypeLiveTvRecordingGroups": "Recordings",
+ "ViewTypeLiveTvChannels": "Channels",
+ "ScheduledTaskFailedWithName": "{0} failed",
+ "LabelRunningTimeValue": "Running time: {0}",
+ "ScheduledTaskStartedWithName": "{0} started",
+ "VersionNumber": "Version {0}",
+ "PluginInstalledWithName": "{0} was installed",
+ "PluginUpdatedWithName": "{0} was updated",
+ "PluginUninstalledWithName": "{0} was uninstalled",
+ "ItemAddedWithName": "{0} was added to the library",
+ "ItemRemovedWithName": "{0} was removed from the library",
+ "LabelIpAddressValue": "Ip address: {0}",
+ "DeviceOnlineWithName": "{0} is connected",
+ "UserOnlineFromDevice": "{0} is online from {1}",
+ "ProviderValue": "Provider: {0}",
+ "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
+ "UserConfigurationUpdatedWithName": "User configuration has been updated for {0}",
+ "UserCreatedWithName": "User {0} has been created",
+ "UserPasswordChangedWithName": "Password has been changed for user {0}",
+ "UserDeletedWithName": "User {0} has been deleted",
+ "MessageServerConfigurationUpdated": "Server configuration has been updated",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
+ "MessageApplicationUpdated": "Emby Server has been updated",
+ "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
+ "AuthenticationSucceededWithUserName": "{0} successfully authenticated",
+ "DeviceOfflineWithName": "{0} has disconnected",
+ "UserLockedOutWithName": "User {0} has been locked out",
+ "UserOfflineFromDevice": "{0} has disconnected from {1}",
+ "UserStartedPlayingItemWithValues": "{0} has started playing {1}",
+ "UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}",
+ "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
+ "HeaderUnidentified": "Unidentified",
+ "HeaderImagePrimary": "Primary",
+ "HeaderImageBackdrop": "Backdrop",
+ "HeaderImageLogo": "Logo",
+ "HeaderUserPrimaryImage": "User Image",
+ "HeaderOverview": "Overview",
+ "HeaderShortOverview": "Short Overview",
+ "HeaderType": "Type",
+ "HeaderSeverity": "Severity",
+ "HeaderUser": "User",
+ "HeaderName": "Name",
+ "HeaderDate": "Date",
+ "HeaderPremiereDate": "Premiere Date",
+ "HeaderDateAdded": "Date Added",
+ "HeaderReleaseDate": "Release date",
+ "HeaderRuntime": "Runtime",
+ "HeaderPlayCount": "Play Count",
+ "HeaderSeason": "Season",
+ "HeaderSeasonNumber": "Season number",
+ "HeaderSeries": "Series:",
+ "HeaderNetwork": "Network",
+ "HeaderYear": "Year:",
+ "HeaderYears": "Years:",
+ "HeaderParentalRating": "Parental Rating",
+ "HeaderCommunityRating": "Community rating",
+ "HeaderTrailers": "Trailers",
+ "HeaderSpecials": "Specials",
+ "HeaderGameSystems": "Game Systems",
+ "HeaderPlayers": "Players:",
+ "HeaderAlbumArtists": "Album Artists",
+ "HeaderAlbums": "Albums",
+ "HeaderDisc": "Disc",
+ "HeaderTrack": "Track",
+ "HeaderAudio": "Audio",
+ "HeaderVideo": "Video",
+ "HeaderEmbeddedImage": "Embedded image",
+ "HeaderResolution": "Resolution",
+ "HeaderSubtitles": "Subtitles",
+ "HeaderGenres": "Genres",
+ "HeaderCountries": "Countries",
+ "HeaderStatus": "Status",
+ "HeaderTracks": "Tracks",
+ "HeaderMusicArtist": "Music artist",
+ "HeaderLocked": "Locked",
+ "HeaderStudios": "Studios",
+ "HeaderActor": "Actors",
+ "HeaderComposer": "Composers",
+ "HeaderDirector": "Directors",
+ "HeaderGuestStar": "Guest star",
+ "HeaderProducer": "Producers",
+ "HeaderWriter": "Writers",
+ "HeaderParentalRatings": "Parental Ratings",
+ "HeaderCommunityRatings": "Community ratings",
+ "StartupEmbyServerIsLoading": "Emby Server is loading. Please try again shortly."
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
index 60d8f737f..aff3a5e16 100644
--- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
+++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
@@ -68,15 +68,16 @@
<Reference Include="ServiceStack.Api.Swagger">
<HintPath>..\ThirdParty\ServiceStack\ServiceStack.Api.Swagger.dll</HintPath>
</Reference>
- <Reference Include="SocketHttpListener, Version=1.0.5908.28560, Culture=neutral, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
- <HintPath>..\packages\SocketHttpListener.1.0.0.29\lib\net45\SocketHttpListener.dll</HintPath>
+ <Reference Include="SimpleInjector, Version=3.1.3.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
+ <HintPath>..\packages\SimpleInjector.3.1.3\lib\net45\SimpleInjector.dll</HintPath>
+ <Private>True</Private>
+ </Reference>
+ <Reference Include="SocketHttpListener, Version=1.0.5955.1537, Culture=neutral, processorArchitecture=MSIL">
+ <HintPath>..\packages\SocketHttpListener.1.0.0.30\lib\net45\SocketHttpListener.dll</HintPath>
+ <Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
- <Reference Include="System.Data.SQLite">
- <HintPath>..\ThirdParty\System.Data.SQLite.ManagedOnly\1.0.94.0\System.Data.SQLite.dll</HintPath>
- </Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net" />
@@ -257,6 +258,8 @@
<Compile Include="Notifications\IConfigurableNotificationService.cs" />
<Compile Include="Persistence\BaseSqliteRepository.cs" />
<Compile Include="Persistence\CleanDatabaseScheduledTask.cs" />
+ <Compile Include="Persistence\DataExtensions.cs" />
+ <Compile Include="Persistence\IDbConnector.cs" />
<Compile Include="Persistence\MediaStreamColumns.cs" />
<Compile Include="Social\SharingManager.cs" />
<Compile Include="Social\SharingRepository.cs" />
@@ -271,7 +274,6 @@
<Compile Include="Notifications\InternalNotificationService.cs" />
<Compile Include="Notifications\NotificationConfigurationFactory.cs" />
<Compile Include="Notifications\NotificationManager.cs" />
- <Compile Include="Persistence\SqliteExtensions.cs" />
<Compile Include="Persistence\SqliteFileOrganizationRepository.cs" />
<Compile Include="Notifications\SqliteNotificationsRepository.cs" />
<Compile Include="Persistence\SqliteProviderInfoRepository.cs" />
diff --git a/MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs b/MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs
index 7302431e1..cecf03ddf 100644
--- a/MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs
+++ b/MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs
@@ -32,11 +32,11 @@ namespace MediaBrowser.Server.Implementations.Notifications
_appPaths = appPaths;
}
- public async Task Initialize()
+ public async Task Initialize(IDbConnector dbConnector)
{
var dbFile = Path.Combine(_appPaths.DataPath, "notifications.db");
- _connection = await SqliteExtensions.ConnectToDb(dbFile, Logger).ConfigureAwait(false);
+ _connection = await dbConnector.Connect(dbFile).ConfigureAwait(false);
string[] queries = {
diff --git a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs
index 031333f2c..2a2f9a09d 100644
--- a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs
@@ -110,6 +110,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
_config.SaveConfiguration();
}
+ if (_config.Configuration.SchemaVersion < SqliteItemRepository.LatestSchemaVersion)
+ {
+ _config.Configuration.SchemaVersion = SqliteItemRepository.LatestSchemaVersion;
+ _config.SaveConfiguration();
+ }
+
if (EnableUnavailableMessage)
{
EnableUnavailableMessage = false;
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs b/MediaBrowser.Server.Implementations/Persistence/DataExtensions.cs
index 4fb1e07dd..103b75f84 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/DataExtensions.cs
@@ -3,16 +3,12 @@ using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using System;
using System.Data;
-using System.Data.SQLite;
using System.IO;
using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.Persistence
{
- /// <summary>
- /// Class SQLiteExtensions
- /// </summary>
- static class SqliteExtensions
+ static class DataExtensions
{
/// <summary>
/// Determines whether the specified conn is open.
@@ -28,11 +24,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
return (IDataParameter)cmd.Parameters[index];
}
-
+
public static IDataParameter Add(this IDataParameterCollection paramCollection, IDbCommand cmd, string name, DbType type)
{
var param = cmd.CreateParameter();
-
+
param.ParameterName = name;
param.DbType = type;
@@ -48,11 +44,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
param.ParameterName = name;
paramCollection.Add(param);
-
+
return param;
}
-
+
/// <summary>
/// Gets a stream from a DataReader at a given ordinal
/// </summary>
@@ -122,38 +118,6 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
}
- /// <summary>
- /// Connects to db.
- /// </summary>
- /// <param name="dbPath">The db path.</param>
- /// <param name="logger">The logger.</param>
- /// <returns>Task{IDbConnection}.</returns>
- /// <exception cref="System.ArgumentNullException">dbPath</exception>
- public static async Task<IDbConnection> ConnectToDb(string dbPath, ILogger logger)
- {
- if (string.IsNullOrEmpty(dbPath))
- {
- throw new ArgumentNullException("dbPath");
- }
-
- logger.Info("Sqlite {0} opening {1}", SQLiteConnection.SQLiteVersion, dbPath);
-
- var connectionstr = new SQLiteConnectionStringBuilder
- {
- PageSize = 4096,
- CacheSize = 2000,
- SyncMode = SynchronizationModes.Full,
- DataSource = dbPath,
- JournalMode = SQLiteJournalModeEnum.Wal
- };
-
- var connection = new SQLiteConnection(connectionstr.ConnectionString);
-
- await connection.OpenAsync().ConfigureAwait(false);
-
- return connection;
- }
-
public static void Attach(IDbConnection db, string path, string alias)
{
using (var cmd = db.CreateCommand())
diff --git a/MediaBrowser.Server.Implementations/Persistence/IDbConnector.cs b/MediaBrowser.Server.Implementations/Persistence/IDbConnector.cs
new file mode 100644
index 000000000..cac9fe983
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/Persistence/IDbConnector.cs
@@ -0,0 +1,10 @@
+using System.Data;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.Persistence
+{
+ public interface IDbConnector
+ {
+ Task<IDbConnection> Connect(string dbPath);
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/Persistence/MediaStreamColumns.cs b/MediaBrowser.Server.Implementations/Persistence/MediaStreamColumns.cs
index debcd054f..76682c63b 100644
--- a/MediaBrowser.Server.Implementations/Persistence/MediaStreamColumns.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/MediaStreamColumns.cs
@@ -26,6 +26,38 @@ namespace MediaBrowser.Server.Implementations.Persistence
AddCodecTagColumn();
AddCommentColumn();
AddNalColumn();
+ AddIsAvcColumn();
+ }
+
+ private void AddIsAvcColumn()
+ {
+ using (var cmd = _connection.CreateCommand())
+ {
+ cmd.CommandText = "PRAGMA table_info(mediastreams)";
+
+ using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
+ {
+ while (reader.Read())
+ {
+ if (!reader.IsDBNull(1))
+ {
+ var name = reader.GetString(1);
+
+ if (string.Equals(name, "IsAvc", StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ var builder = new StringBuilder();
+
+ builder.AppendLine("alter table mediastreams");
+ builder.AppendLine("add column IsAvc BIT NULL");
+
+ _connection.RunQueries(new[] { builder.ToString() }, _logger);
}
private void AddNalColumn()
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteDisplayPreferencesRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteDisplayPreferencesRepository.cs
index 45e0304c1..6077cfdba 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteDisplayPreferencesRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteDisplayPreferencesRepository.cs
@@ -52,11 +52,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
/// Opens the connection to the database
/// </summary>
/// <returns>Task.</returns>
- public async Task Initialize()
+ public async Task Initialize(IDbConnector dbConnector)
{
var dbFile = Path.Combine(_appPaths.DataPath, "displaypreferences.db");
- _connection = await SqliteExtensions.ConnectToDb(dbFile, Logger).ConfigureAwait(false);
+ _connection = await dbConnector.Connect(dbFile).ConfigureAwait(false);
string[] queries = {
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs
index 2d5aad04d..037776997 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs
@@ -35,11 +35,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
/// Opens the connection to the database
/// </summary>
/// <returns>Task.</returns>
- public async Task Initialize()
+ public async Task Initialize(IDbConnector dbConnector)
{
var dbFile = Path.Combine(_appPaths.DataPath, "fileorganization.db");
- _connection = await SqliteExtensions.ConnectToDb(dbFile, Logger).ConfigureAwait(false);
+ _connection = await dbConnector.Connect(dbFile).ConfigureAwait(false);
string[] queries = {
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
index fb655c9cb..308ca90e0 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
@@ -1,4 +1,3 @@
-using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
@@ -18,7 +17,9 @@ using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Channels;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Model.LiveTv;
@@ -54,7 +55,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
/// <summary>
/// The _app paths
/// </summary>
- private readonly IApplicationPaths _appPaths;
+ private readonly IServerConfigurationManager _config;
/// <summary>
/// The _save item command
@@ -77,38 +78,33 @@ namespace MediaBrowser.Server.Implementations.Persistence
private IDbCommand _deleteAncestorsCommand;
private IDbCommand _saveAncestorCommand;
+ private IDbCommand _deleteUserDataKeysCommand;
+ private IDbCommand _saveUserDataKeysCommand;
+
private IDbCommand _updateInheritedRatingCommand;
private IDbCommand _updateInheritedTagsCommand;
- private const int LatestSchemaVersion = 63;
+ public const int LatestSchemaVersion = 78;
/// <summary>
/// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.
/// </summary>
- /// <param name="appPaths">The app paths.</param>
- /// <param name="jsonSerializer">The json serializer.</param>
- /// <param name="logManager">The log manager.</param>
- /// <exception cref="System.ArgumentNullException">
- /// appPaths
- /// or
- /// jsonSerializer
- /// </exception>
- public SqliteItemRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager)
+ public SqliteItemRepository(IServerConfigurationManager config, IJsonSerializer jsonSerializer, ILogManager logManager)
: base(logManager)
{
- if (appPaths == null)
+ if (config == null)
{
- throw new ArgumentNullException("appPaths");
+ throw new ArgumentNullException("config");
}
if (jsonSerializer == null)
{
throw new ArgumentNullException("jsonSerializer");
}
- _appPaths = appPaths;
+ _config = config;
_jsonSerializer = jsonSerializer;
- _criticReviewsPath = Path.Combine(_appPaths.DataPath, "critic-reviews");
+ _criticReviewsPath = Path.Combine(_config.ApplicationPaths.DataPath, "critic-reviews");
}
private const string ChaptersTableName = "Chapters2";
@@ -117,14 +113,14 @@ namespace MediaBrowser.Server.Implementations.Persistence
/// Opens the connection to the database
/// </summary>
/// <returns>Task.</returns>
- public async Task Initialize()
+ public async Task Initialize(IDbConnector dbConnector)
{
- var dbFile = Path.Combine(_appPaths.DataPath, "library.db");
+ var dbFile = Path.Combine(_config.ApplicationPaths.DataPath, "library.db");
- _connection = await SqliteExtensions.ConnectToDb(dbFile, Logger).ConfigureAwait(false);
+ _connection = await dbConnector.Connect(dbFile).ConfigureAwait(false);
var createMediaStreamsTableCommand
- = "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))";
+ = "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, PRIMARY KEY (ItemId, StreamIndex))";
string[] queries = {
@@ -137,15 +133,18 @@ namespace MediaBrowser.Server.Implementations.Persistence
"create index if not exists idx_AncestorIds1 on AncestorIds(AncestorId)",
"create index if not exists idx_AncestorIds2 on AncestorIds(AncestorIdText)",
+ "create table if not exists UserDataKeys (ItemId GUID, UserDataKey TEXT, PRIMARY KEY (ItemId, UserDataKey))",
+ "create index if not exists idx_UserDataKeys1 on UserDataKeys(ItemId)",
+
"create table if not exists People (ItemId GUID, Name TEXT NOT NULL, Role TEXT, PersonType TEXT, SortOrder int, ListOrder int)",
"create index if not exists idxPeopleItemId on People(ItemId)",
"create index if not exists idxPeopleName on People(Name)",
"create table if not exists "+ChaptersTableName+" (ItemId GUID, ChapterIndex INT, StartPositionTicks BIGINT, Name TEXT, ImagePath TEXT, PRIMARY KEY (ItemId, ChapterIndex))",
- "create index if not exists idx_"+ChaptersTableName+" on "+ChaptersTableName+"(ItemId, ChapterIndex)",
+ "create index if not exists idx_"+ChaptersTableName+"1 on "+ChaptersTableName+"(ItemId)",
createMediaStreamsTableCommand,
- "create index if not exists idx_mediastreams on mediastreams(ItemId, StreamIndex)",
+ "create index if not exists idx_mediastreams1 on mediastreams(ItemId)",
//pragmas
"pragma temp_store = memory",
@@ -226,22 +225,35 @@ namespace MediaBrowser.Server.Implementations.Persistence
_connection.AddColumn(Logger, "TypedBaseItems", "CriticRatingSummary", "Text");
_connection.AddColumn(Logger, "TypedBaseItems", "DateModifiedDuringLastRefresh", "DATETIME");
_connection.AddColumn(Logger, "TypedBaseItems", "InheritedTags", "Text");
+ _connection.AddColumn(Logger, "TypedBaseItems", "CleanName", "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");
+ _connection.AddColumn(Logger, "TypedBaseItems", "DateLastMediaAdded", "DATETIME");
+ _connection.AddColumn(Logger, "TypedBaseItems", "Album", "Text");
+
+ _connection.AddColumn(Logger, "UserDataKeys", "Priority", "INT");
+
+ string[] postQueries =
+ {
+ "create index if not exists idx_PresentationUniqueKey on TypedBaseItems(PresentationUniqueKey)",
+ "create index if not exists idx_Type on TypedBaseItems(Type)"
+ };
+
+ _connection.RunQueries(postQueries, Logger);
PrepareStatements();
new MediaStreamColumns(_connection, Logger).AddColumns();
- var chapterDbFile = Path.Combine(_appPaths.DataPath, "chapters.db");
- if (File.Exists(chapterDbFile))
- {
- MigrateChapters(chapterDbFile);
- }
-
- var mediaStreamsDbFile = Path.Combine(_appPaths.DataPath, "mediainfo.db");
+ var mediaStreamsDbFile = Path.Combine(_config.ApplicationPaths.DataPath, "mediainfo.db");
if (File.Exists(mediaStreamsDbFile))
{
MigrateMediaStreams(mediaStreamsDbFile);
}
+
+ DataExtensions.Attach(_connection, Path.Combine(_config.ApplicationPaths.DataPath, "userdata_v2.db"), "UserDataDb");
}
private void MigrateMediaStreams(string file)
@@ -250,7 +262,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
var backupFile = file + ".bak";
File.Copy(file, backupFile, true);
- SqliteExtensions.Attach(_connection, backupFile, "MediaInfoOld");
+ DataExtensions.Attach(_connection, backupFile, "MediaInfoOld");
var columns = string.Join(",", _mediaStreamSaveColumns);
@@ -270,30 +282,6 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
}
- private void MigrateChapters(string file)
- {
- try
- {
- var backupFile = file + ".bak";
- File.Copy(file, backupFile, true);
- SqliteExtensions.Attach(_connection, backupFile, "ChaptersOld");
-
- string[] queries = {
- "REPLACE INTO "+ChaptersTableName+"(ItemId, ChapterIndex, StartPositionTicks, Name, ImagePath) SELECT ItemId, ChapterIndex, StartPositionTicks, Name, ImagePath FROM ChaptersOld.Chapters;"
- };
-
- _connection.RunQueries(queries, Logger);
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error migrating chapter database", ex);
- }
- finally
- {
- TryDeleteFile(file);
- }
- }
-
private void TryDeleteFile(string file)
{
try
@@ -359,7 +347,13 @@ namespace MediaBrowser.Server.Implementations.Persistence
"Tags",
"SourceType",
"TrailerTypes",
- "DateModifiedDuringLastRefresh"
+ "DateModifiedDuringLastRefresh",
+ "OriginalTitle",
+ "PrimaryVersionId",
+ "DateLastMediaAdded",
+ "Album",
+ "CriticRating",
+ "CriticRatingSummary"
};
private readonly string[] _mediaStreamSaveColumns =
@@ -391,7 +385,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
"RefFrames",
"CodecTag",
"Comment",
- "NalLengthSize"
+ "NalLengthSize",
+ "IsAvc"
};
/// <summary>
@@ -465,7 +460,14 @@ namespace MediaBrowser.Server.Implementations.Persistence
"CriticRating",
"CriticRatingSummary",
"DateModifiedDuringLastRefresh",
- "InheritedTags"
+ "InheritedTags",
+ "CleanName",
+ "PresentationUniqueKey",
+ "SlugName",
+ "OriginalTitle",
+ "PrimaryVersionId",
+ "DateLastMediaAdded",
+ "Album"
};
_saveItemCommand = _connection.CreateCommand();
_saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values (";
@@ -550,6 +552,18 @@ namespace MediaBrowser.Server.Implementations.Persistence
_updateInheritedTagsCommand.CommandText = "Update TypedBaseItems set InheritedTags=@InheritedTags where Guid=@Guid";
_updateInheritedTagsCommand.Parameters.Add(_updateInheritedTagsCommand, "@Guid");
_updateInheritedTagsCommand.Parameters.Add(_updateInheritedTagsCommand, "@InheritedTags");
+
+ // user data
+ _deleteUserDataKeysCommand = _connection.CreateCommand();
+ _deleteUserDataKeysCommand.CommandText = "delete from UserDataKeys where ItemId=@Id";
+ _deleteUserDataKeysCommand.Parameters.Add(_deleteUserDataKeysCommand, "@Id");
+
+ _saveUserDataKeysCommand = _connection.CreateCommand();
+ _saveUserDataKeysCommand.CommandText = "insert into UserDataKeys (ItemId, UserDataKey, Priority) values (@ItemId, @UserDataKey, @Priority)";
+ _saveUserDataKeysCommand.Parameters.Add(_saveUserDataKeysCommand, "@ItemId");
+ _saveUserDataKeysCommand.Parameters.Add(_saveUserDataKeysCommand, "@UserDataKey");
+ _saveUserDataKeysCommand.Parameters.Add(_saveUserDataKeysCommand, "@Priority");
+
}
/// <summary>
@@ -791,6 +805,41 @@ namespace MediaBrowser.Server.Implementations.Persistence
_saveItemCommand.GetParameter(index++).Value = null;
}
+ if (string.IsNullOrWhiteSpace(item.Name))
+ {
+ _saveItemCommand.GetParameter(index++).Value = null;
+ }
+ else
+ {
+ _saveItemCommand.GetParameter(index++).Value = item.Name.RemoveDiacritics();
+ }
+
+ _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;
+ }
+
+ var folder = item as Folder;
+ if (folder != null && folder.DateLastMediaAdded.HasValue)
+ {
+ _saveItemCommand.GetParameter(index++).Value = folder.DateLastMediaAdded.Value;
+ }
+ else
+ {
+ _saveItemCommand.GetParameter(index++).Value = null;
+ }
+
+ _saveItemCommand.GetParameter(index++).Value = item.Album;
+
_saveItemCommand.Transaction = transaction;
_saveItemCommand.ExecuteNonQuery();
@@ -799,6 +848,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
UpdateAncestors(item.Id, item.GetAncestorIds().Distinct().ToList(), transaction);
}
+
+ UpdateUserDataKeys(item.Id, item.GetUserDataKeys().Distinct(StringComparer.OrdinalIgnoreCase).ToList(), transaction);
}
transaction.Commit();
@@ -1168,6 +1219,41 @@ namespace MediaBrowser.Server.Implementations.Persistence
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);
+ }
+ }
+
+ var folder = item as Folder;
+ if (folder != null && !reader.IsDBNull(54))
+ {
+ folder.DateLastMediaAdded = reader.GetDateTime(54).ToUniversalTime();
+ }
+
+ if (!reader.IsDBNull(55))
+ {
+ item.Album = reader.GetString(55);
+ }
+
+ if (!reader.IsDBNull(56))
+ {
+ item.CriticRating = reader.GetFloat(56);
+ }
+
+ if (!reader.IsDBNull(57))
+ {
+ item.CriticRatingSummary = reader.GetString(57);
+ }
+
return item;
}
@@ -1225,6 +1311,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
throw new ArgumentNullException("id");
}
+ var list = new List<ChapterInfo>();
using (var cmd = _connection.CreateCommand())
{
@@ -1236,10 +1323,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
while (reader.Read())
{
- yield return GetChapter(reader);
+ list.Add(GetChapter(reader));
}
}
}
+
+ return list;
}
/// <summary>
@@ -1411,34 +1500,96 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
}
- public IEnumerable<BaseItem> GetItemsOfType(Type type)
+ private bool EnableJoinUserData(InternalItemsQuery query)
{
- if (type == null)
+ if (_config.Configuration.SchemaVersion < 76)
{
- throw new ArgumentNullException("type");
+ return false;
}
- CheckDisposed();
+ if (query.User == null)
+ {
+ return false;
+ }
- using (var cmd = _connection.CreateCommand())
+ if (query.SortBy != null && query.SortBy.Length > 0)
+ {
+ if (query.SortBy.Contains(ItemSortBy.IsFavoriteOrLiked, StringComparer.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ if (query.SortBy.Contains(ItemSortBy.IsPlayed, StringComparer.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ if (query.SortBy.Contains(ItemSortBy.IsUnplayed, StringComparer.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ if (query.SortBy.Contains(ItemSortBy.PlayCount, StringComparer.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ if (query.SortBy.Contains(ItemSortBy.DatePlayed, StringComparer.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ }
+
+ if (query.IsFavoriteOrLiked.HasValue)
{
- cmd.CommandText = "select " + string.Join(",", _retriveItemColumns) + " from TypedBaseItems where type = @type";
+ return true;
+ }
- cmd.Parameters.Add(cmd, "@type", DbType.String).Value = type.FullName;
+ if (query.IsFavorite.HasValue)
+ {
+ return true;
+ }
- using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
- {
- while (reader.Read())
- {
- var item = GetItem(reader);
+ if (query.IsResumable.HasValue)
+ {
+ return true;
+ }
- if (item != null)
- {
- yield return item;
- }
- }
- }
+ if (query.IsPlayed.HasValue)
+ {
+ return true;
}
+
+ if (query.IsLiked.HasValue)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ private string[] GetFinalColumnsToSelect(InternalItemsQuery query, string[] startColumns)
+ {
+ var list = startColumns.ToList();
+
+ if (EnableJoinUserData(query))
+ {
+ list.Add("UserDataDb.UserData.UserId");
+ list.Add("UserDataDb.UserData.lastPlayedDate");
+ list.Add("UserDataDb.UserData.playbackPositionTicks");
+ list.Add("UserDataDb.UserData.playcount");
+ list.Add("UserDataDb.UserData.isFavorite");
+ list.Add("UserDataDb.UserData.played");
+ list.Add("UserDataDb.UserData.rating");
+ }
+
+ return list.ToArray();
+ }
+
+ private string GetJoinUserDataText(InternalItemsQuery query)
+ {
+ if (!EnableJoinUserData(query))
+ {
+ return string.Empty;
+ }
+
+ return " left join UserDataDb.UserData on (select UserDataKey from UserDataKeys where ItemId=Guid order by Priority LIMIT 1)=UserDataDb.UserData.Key";
}
public IEnumerable<BaseItem> GetItemList(InternalItemsQuery query)
@@ -1450,11 +1601,19 @@ namespace MediaBrowser.Server.Implementations.Persistence
CheckDisposed();
+ var now = DateTime.UtcNow;
+
using (var cmd = _connection.CreateCommand())
{
- cmd.CommandText = "select " + string.Join(",", _retriveItemColumns) + " from TypedBaseItems";
+ cmd.CommandText = "select " + string.Join(",", GetFinalColumnsToSelect(query, _retriveItemColumns)) + " from TypedBaseItems";
+ cmd.CommandText += GetJoinUserDataText(query);
- var whereClauses = GetWhereClauses(query, cmd, true);
+ if (EnableJoinUserData(query))
+ {
+ cmd.Parameters.Add(cmd, "@UserId", DbType.Guid).Value = query.User.Id;
+ }
+
+ var whereClauses = GetWhereClauses(query, cmd);
var whereText = whereClauses.Count == 0 ?
string.Empty :
@@ -1462,17 +1621,31 @@ namespace MediaBrowser.Server.Implementations.Persistence
cmd.CommandText += whereText;
+ if (EnableGroupByPresentationUniqueKey(query) && _config.Configuration.SchemaVersion >= 66)
+ {
+ cmd.CommandText += " Group by PresentationUniqueKey";
+ }
+
cmd.CommandText += GetOrderByText(query);
- if (query.Limit.HasValue)
+ if (query.Limit.HasValue || query.StartIndex.HasValue)
{
- cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(CultureInfo.InvariantCulture);
- }
+ var limit = query.Limit ?? int.MaxValue;
+
+ cmd.CommandText += " LIMIT " + limit.ToString(CultureInfo.InvariantCulture);
- //Logger.Debug(cmd.CommandText);
+ if (query.StartIndex.HasValue)
+ {
+ cmd.CommandText += " OFFSET " + query.StartIndex.Value.ToString(CultureInfo.InvariantCulture);
+ }
+ }
using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
{
+ //Logger.Debug("GetItemList query time: {0}ms. Query: {1}",
+ // Convert.ToInt32((DateTime.UtcNow - now).TotalMilliseconds),
+ // cmd.CommandText);
+
while (reader.Read())
{
var item = GetItem(reader);
@@ -1494,40 +1667,70 @@ namespace MediaBrowser.Server.Implementations.Persistence
CheckDisposed();
+ var now = DateTime.UtcNow;
+
using (var cmd = _connection.CreateCommand())
{
- cmd.CommandText = "select " + string.Join(",", _retriveItemColumns) + " from TypedBaseItems";
+ cmd.CommandText = "select " + string.Join(",", GetFinalColumnsToSelect(query, _retriveItemColumns)) + " from TypedBaseItems";
+ cmd.CommandText += GetJoinUserDataText(query);
+
+ if (EnableJoinUserData(query))
+ {
+ cmd.Parameters.Add(cmd, "@UserId", DbType.Guid).Value = query.User.Id;
+ }
- var whereClauses = GetWhereClauses(query, cmd, false);
+ var whereClauses = GetWhereClauses(query, cmd);
var whereTextWithoutPaging = whereClauses.Count == 0 ?
string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray());
- whereClauses = GetWhereClauses(query, cmd, true);
-
var whereText = whereClauses.Count == 0 ?
string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray());
cmd.CommandText += whereText;
+ if (EnableGroupByPresentationUniqueKey(query) && _config.Configuration.SchemaVersion >= 66)
+ {
+ cmd.CommandText += " Group by PresentationUniqueKey";
+ }
+
cmd.CommandText += GetOrderByText(query);
- if (query.Limit.HasValue)
+ if (query.Limit.HasValue || query.StartIndex.HasValue)
{
- cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(CultureInfo.InvariantCulture);
+ var limit = query.Limit ?? int.MaxValue;
+
+ cmd.CommandText += " LIMIT " + limit.ToString(CultureInfo.InvariantCulture);
+
+ if (query.StartIndex.HasValue)
+ {
+ cmd.CommandText += " OFFSET " + query.StartIndex.Value.ToString(CultureInfo.InvariantCulture);
+ }
}
- cmd.CommandText += "; select count (guid) from TypedBaseItems" + whereTextWithoutPaging;
+ if (EnableGroupByPresentationUniqueKey(query) && _config.Configuration.SchemaVersion >= 66)
+ {
+ cmd.CommandText += "; select count (distinct PresentationUniqueKey) from TypedBaseItems";
+ }
+ else
+ {
+ cmd.CommandText += "; select count (guid) from TypedBaseItems";
+ }
- //Logger.Debug(cmd.CommandText);
+ cmd.CommandText += GetJoinUserDataText(query);
+ cmd.CommandText += whereTextWithoutPaging;
var list = new List<BaseItem>();
var count = 0;
using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
{
+ //Logger.Debug("GetItems query time: {0}ms. Query: {1}",
+ // Convert.ToInt32((DateTime.UtcNow - now).TotalMilliseconds),
+ // cmd.CommandText);
+
while (reader.Read())
{
var item = GetItem(reader);
@@ -1558,28 +1761,68 @@ namespace MediaBrowser.Server.Implementations.Persistence
return string.Empty;
}
- var sortOrder = query.SortOrder == SortOrder.Descending ? "DESC" : "ASC";
+ var isAscending = query.SortOrder != SortOrder.Descending;
+
+ return " ORDER BY " + string.Join(",", query.SortBy.Select(i =>
+ {
+ var columnMap = MapOrderByField(i);
+ var columnAscending = isAscending;
+ if (columnMap.Item2)
+ {
+ columnAscending = !columnAscending;
+ }
+
+ var sortOrder = columnAscending ? "ASC" : "DESC";
- return " ORDER BY " + string.Join(",", query.SortBy.Select(i => MapOrderByField(i) + " " + sortOrder).ToArray());
+ return columnMap.Item1 + " " + sortOrder;
+ }).ToArray());
}
- private string MapOrderByField(string name)
+ private Tuple<string,bool> MapOrderByField(string name)
{
if (string.Equals(name, ItemSortBy.AirTime, StringComparison.OrdinalIgnoreCase))
{
// TODO
- return "SortName";
+ return new Tuple<string, bool>("SortName", false);
}
if (string.Equals(name, ItemSortBy.Runtime, StringComparison.OrdinalIgnoreCase))
{
- return "RuntimeTicks";
+ return new Tuple<string, bool>("RuntimeTicks", false);
}
if (string.Equals(name, ItemSortBy.Random, StringComparison.OrdinalIgnoreCase))
{
- return "RANDOM()";
+ return new Tuple<string, bool>("RANDOM()", false);
+ }
+ if (string.Equals(name, ItemSortBy.DatePlayed, StringComparison.OrdinalIgnoreCase))
+ {
+ return new Tuple<string, bool>("LastPlayedDate", false);
+ }
+ if (string.Equals(name, ItemSortBy.PlayCount, StringComparison.OrdinalIgnoreCase))
+ {
+ return new Tuple<string, bool>("PlayCount", false);
+ }
+ if (string.Equals(name, ItemSortBy.IsFavoriteOrLiked, StringComparison.OrdinalIgnoreCase))
+ {
+ return new Tuple<string, bool>("IsFavorite", true);
+ }
+ if (string.Equals(name, ItemSortBy.IsFolder, StringComparison.OrdinalIgnoreCase))
+ {
+ return new Tuple<string, bool>("IsFolder", true);
+ }
+ if (string.Equals(name, ItemSortBy.IsPlayed, StringComparison.OrdinalIgnoreCase))
+ {
+ return new Tuple<string, bool>("played", true);
+ }
+ if (string.Equals(name, ItemSortBy.IsUnplayed, StringComparison.OrdinalIgnoreCase))
+ {
+ return new Tuple<string, bool>("played", false);
+ }
+ if (string.Equals(name, ItemSortBy.DateLastContentAdded, StringComparison.OrdinalIgnoreCase))
+ {
+ return new Tuple<string, bool>("DateLastMediaAdded", false);
}
- return name;
+ return new Tuple<string, bool>(name, false);
}
public List<Guid> GetItemIdsList(InternalItemsQuery query)
@@ -1591,11 +1834,19 @@ namespace MediaBrowser.Server.Implementations.Persistence
CheckDisposed();
+ var now = DateTime.UtcNow;
+
using (var cmd = _connection.CreateCommand())
{
- cmd.CommandText = "select guid from TypedBaseItems";
+ cmd.CommandText = "select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "guid" })) + " from TypedBaseItems";
+ cmd.CommandText += GetJoinUserDataText(query);
+
+ if (EnableJoinUserData(query))
+ {
+ cmd.Parameters.Add(cmd, "@UserId", DbType.Guid).Value = query.User.Id;
+ }
- var whereClauses = GetWhereClauses(query, cmd, true);
+ var whereClauses = GetWhereClauses(query, cmd);
var whereText = whereClauses.Count == 0 ?
string.Empty :
@@ -1603,19 +1854,33 @@ namespace MediaBrowser.Server.Implementations.Persistence
cmd.CommandText += whereText;
+ if (EnableGroupByPresentationUniqueKey(query) && _config.Configuration.SchemaVersion >= 66)
+ {
+ cmd.CommandText += " Group by PresentationUniqueKey";
+ }
+
cmd.CommandText += GetOrderByText(query);
- if (query.Limit.HasValue)
+ if (query.Limit.HasValue || query.StartIndex.HasValue)
{
- cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(CultureInfo.InvariantCulture);
+ var limit = query.Limit ?? int.MaxValue;
+
+ cmd.CommandText += " LIMIT " + limit.ToString(CultureInfo.InvariantCulture);
+
+ if (query.StartIndex.HasValue)
+ {
+ cmd.CommandText += " OFFSET " + query.StartIndex.Value.ToString(CultureInfo.InvariantCulture);
+ }
}
var list = new List<Guid>();
- //Logger.Debug(cmd.CommandText);
-
using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
{
+ //Logger.Debug("GetItemIdsList query time: {0}ms. Query: {1}",
+ // Convert.ToInt32((DateTime.UtcNow - now).TotalMilliseconds),
+ // cmd.CommandText);
+
while (reader.Read())
{
list.Add(reader.GetGuid(0));
@@ -1639,25 +1904,35 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
cmd.CommandText = "select guid,path from TypedBaseItems";
- var whereClauses = GetWhereClauses(query, cmd, false);
+ var whereClauses = GetWhereClauses(query, cmd);
var whereTextWithoutPaging = whereClauses.Count == 0 ?
string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray());
- whereClauses = GetWhereClauses(query, cmd, true);
-
var whereText = whereClauses.Count == 0 ?
string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray());
cmd.CommandText += whereText;
+ if (EnableGroupByPresentationUniqueKey(query) && _config.Configuration.SchemaVersion >= 66)
+ {
+ cmd.CommandText += " Group by PresentationUniqueKey";
+ }
+
cmd.CommandText += GetOrderByText(query);
- if (query.Limit.HasValue)
+ if (query.Limit.HasValue || query.StartIndex.HasValue)
{
- cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(CultureInfo.InvariantCulture);
+ var limit = query.Limit ?? int.MaxValue;
+
+ cmd.CommandText += " LIMIT " + limit.ToString(CultureInfo.InvariantCulture);
+
+ if (query.StartIndex.HasValue)
+ {
+ cmd.CommandText += " OFFSET " + query.StartIndex.Value.ToString(CultureInfo.InvariantCulture);
+ }
}
cmd.CommandText += "; select count (guid) from TypedBaseItems" + whereTextWithoutPaging;
@@ -1704,17 +1979,19 @@ namespace MediaBrowser.Server.Implementations.Persistence
CheckDisposed();
+ var now = DateTime.UtcNow;
+
using (var cmd = _connection.CreateCommand())
{
- cmd.CommandText = "select guid from TypedBaseItems";
-
- var whereClauses = GetWhereClauses(query, cmd, false);
+ cmd.CommandText = "select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "guid" })) + " from TypedBaseItems";
- var whereTextWithoutPaging = whereClauses.Count == 0 ?
- string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
+ var whereClauses = GetWhereClauses(query, cmd);
+ cmd.CommandText += GetJoinUserDataText(query);
- whereClauses = GetWhereClauses(query, cmd, true);
+ if (EnableJoinUserData(query))
+ {
+ cmd.Parameters.Add(cmd, "@UserId", DbType.Guid).Value = query.User.Id;
+ }
var whereText = whereClauses.Count == 0 ?
string.Empty :
@@ -1722,22 +1999,46 @@ namespace MediaBrowser.Server.Implementations.Persistence
cmd.CommandText += whereText;
+ if (EnableGroupByPresentationUniqueKey(query) && _config.Configuration.SchemaVersion >= 66)
+ {
+ cmd.CommandText += " Group by PresentationUniqueKey";
+ }
+
cmd.CommandText += GetOrderByText(query);
- if (query.Limit.HasValue)
+ if (query.Limit.HasValue || query.StartIndex.HasValue)
{
- cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(CultureInfo.InvariantCulture);
+ var limit = query.Limit ?? int.MaxValue;
+
+ cmd.CommandText += " LIMIT " + limit.ToString(CultureInfo.InvariantCulture);
+
+ if (query.StartIndex.HasValue)
+ {
+ cmd.CommandText += " OFFSET " + query.StartIndex.Value.ToString(CultureInfo.InvariantCulture);
+ }
}
- cmd.CommandText += "; select count (guid) from TypedBaseItems" + whereTextWithoutPaging;
+ if (EnableGroupByPresentationUniqueKey(query) && _config.Configuration.SchemaVersion >= 66)
+ {
+ cmd.CommandText += "; select count (distinct PresentationUniqueKey) from TypedBaseItems";
+ }
+ else
+ {
+ cmd.CommandText += "; select count (guid) from TypedBaseItems";
+ }
+
+ cmd.CommandText += GetJoinUserDataText(query);
+ cmd.CommandText += whereText;
var list = new List<Guid>();
var count = 0;
- //Logger.Debug(cmd.CommandText);
-
using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
{
+ //Logger.Debug("GetItemIds query time: {0}ms. Query: {1}",
+ // Convert.ToInt32((DateTime.UtcNow - now).TotalMilliseconds),
+ // cmd.CommandText);
+
while (reader.Read())
{
list.Add(reader.GetGuid(0));
@@ -1757,10 +2058,14 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
}
- private List<string> GetWhereClauses(InternalItemsQuery query, IDbCommand cmd, bool addPaging)
+ private List<string> GetWhereClauses(InternalItemsQuery query, IDbCommand cmd)
{
var whereClauses = new List<string>();
+ if (EnableJoinUserData(query))
+ {
+ whereClauses.Add("(UserId is null or UserId=@UserId)");
+ }
if (query.IsCurrentSchema.HasValue)
{
if (query.IsCurrentSchema.Value)
@@ -1856,6 +2161,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
cmd.Parameters.Add(cmd, "@Path", DbType.String).Value = query.Path;
}
+ if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey))
+ {
+ whereClauses.Add("PresentationUniqueKey=@PresentationUniqueKey");
+ cmd.Parameters.Add(cmd, "@PresentationUniqueKey", DbType.String).Value = query.PresentationUniqueKey;
+ }
+
if (query.MinCommunityRating.HasValue)
{
whereClauses.Add("CommunityRating>=@MinCommunityRating");
@@ -1880,9 +2191,14 @@ namespace MediaBrowser.Server.Implementations.Persistence
// cmd.Parameters.Add(cmd, "@MaxPlayers", DbType.Int32).Value = query.MaxPlayers.Value;
//}
+ if (query.IndexNumber.HasValue)
+ {
+ whereClauses.Add("IndexNumber=@IndexNumber");
+ cmd.Parameters.Add(cmd, "@IndexNumber", DbType.Int32).Value = query.IndexNumber.Value;
+ }
if (query.ParentIndexNumber.HasValue)
{
- whereClauses.Add("ParentIndexNumber=@MinEndDate");
+ whereClauses.Add("ParentIndexNumber=@ParentIndexNumber");
cmd.Parameters.Add(cmd, "@ParentIndexNumber", DbType.Int32).Value = query.ParentIndexNumber.Value;
}
if (query.MinEndDate.HasValue)
@@ -1956,20 +2272,6 @@ namespace MediaBrowser.Server.Implementations.Persistence
whereClauses.Add(clause);
}
- if (query.ExcludeTrailerTypes.Length > 0)
- {
- var clauses = new List<string>();
- var index = 0;
- foreach (var type in query.ExcludeTrailerTypes)
- {
- clauses.Add("(TrailerTypes is null OR TrailerTypes not like @TrailerTypes" + index + ")");
- cmd.Parameters.Add(cmd, "@TrailerTypes" + index, DbType.String).Value = "%" + type + "%";
- index++;
- }
- var clause = "(" + string.Join(" AND ", clauses.ToArray()) + ")";
- whereClauses.Add(clause);
- }
-
if (query.IsAiring.HasValue)
{
if (query.IsAiring.Value)
@@ -1993,10 +2295,126 @@ namespace MediaBrowser.Server.Implementations.Persistence
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 (_config.Configuration.SchemaVersion >= 66)
+ {
+ whereClauses.Add("CleanName=@Name");
+ cmd.Parameters.Add(cmd, "@Name", DbType.String).Value = query.Name.RemoveDiacritics();
+ }
+ else
+ {
+ whereClauses.Add("Name=@Name");
+ cmd.Parameters.Add(cmd, "@Name", DbType.String).Value = query.Name;
+ }
+ }
+
if (!string.IsNullOrWhiteSpace(query.NameContains))
{
- whereClauses.Add("Name like @NameContains");
- cmd.Parameters.Add(cmd, "@NameContains", DbType.String).Value = "%" + query.NameContains + "%";
+ if (_config.Configuration.SchemaVersion >= 66)
+ {
+ whereClauses.Add("CleanName like @NameContains");
+ }
+ else
+ {
+ whereClauses.Add("Name like @NameContains");
+ }
+ cmd.Parameters.Add(cmd, "@NameContains", DbType.String).Value = "%" + query.NameContains.RemoveDiacritics() + "%";
+ }
+ if (!string.IsNullOrWhiteSpace(query.NameStartsWith))
+ {
+ whereClauses.Add("SortName like @NameStartsWith");
+ cmd.Parameters.Add(cmd, "@NameStartsWith", DbType.String).Value = query.NameStartsWith + "%";
+ }
+ if (!string.IsNullOrWhiteSpace(query.NameStartsWithOrGreater))
+ {
+ whereClauses.Add("SortName >= @NameStartsWithOrGreater");
+ // lowercase this because SortName is stored as lowercase
+ cmd.Parameters.Add(cmd, "@NameStartsWithOrGreater", DbType.String).Value = query.NameStartsWithOrGreater.ToLower();
+ }
+ if (!string.IsNullOrWhiteSpace(query.NameLessThan))
+ {
+ whereClauses.Add("SortName < @NameLessThan");
+ // lowercase this because SortName is stored as lowercase
+ cmd.Parameters.Add(cmd, "@NameLessThan", DbType.String).Value = query.NameLessThan.ToLower();
+ }
+
+ if (query.IsLiked.HasValue)
+ {
+ if (query.IsLiked.Value)
+ {
+ whereClauses.Add("rating>=@UserRating");
+ cmd.Parameters.Add(cmd, "@UserRating", DbType.Double).Value = UserItemData.MinLikeValue;
+ }
+ else
+ {
+ whereClauses.Add("(rating is null or rating<@UserRating)");
+ cmd.Parameters.Add(cmd, "@UserRating", DbType.Double).Value = UserItemData.MinLikeValue;
+ }
+ }
+
+ if (query.IsFavoriteOrLiked.HasValue)
+ {
+ if (query.IsFavoriteOrLiked.Value)
+ {
+ whereClauses.Add("IsFavorite=@IsFavoriteOrLiked");
+ }
+ else
+ {
+ whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavoriteOrLiked)");
+ }
+ cmd.Parameters.Add(cmd, "@IsFavoriteOrLiked", DbType.Boolean).Value = query.IsFavoriteOrLiked.Value;
+ }
+
+ if (query.IsFavorite.HasValue)
+ {
+ if (query.IsFavorite.Value)
+ {
+ whereClauses.Add("IsFavorite=@IsFavorite");
+ }
+ else
+ {
+ whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavorite)");
+ }
+ cmd.Parameters.Add(cmd, "@IsFavorite", DbType.Boolean).Value = query.IsFavorite.Value;
+ }
+
+ if (query.IsPlayed.HasValue)
+ {
+ if (query.IsPlayed.Value)
+ {
+ whereClauses.Add("(played=@IsPlayed)");
+ }
+ else
+ {
+ whereClauses.Add("(played is null or played=@IsPlayed)");
+ }
+ cmd.Parameters.Add(cmd, "@IsPlayed", DbType.Boolean).Value = query.IsPlayed.Value;
+ }
+
+ if (query.IsResumable.HasValue)
+ {
+ if (query.IsResumable.Value)
+ {
+ whereClauses.Add("playbackPositionTicks > 0");
+ }
+ else
+ {
+ whereClauses.Add("(playbackPositionTicks is null or playbackPositionTicks = 0)");
+ }
}
if (query.Genres.Length > 0)
@@ -2122,7 +2540,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
if (query.MediaTypes.Length == 1)
{
whereClauses.Add("MediaType=@MediaTypes");
- cmd.Parameters.Add(cmd, "@MediaTypes", DbType.String).Value = query.MediaTypes[0].ToString();
+ cmd.Parameters.Add(cmd, "@MediaTypes", DbType.String).Value = query.MediaTypes[0];
}
if (query.MediaTypes.Length > 1)
{
@@ -2131,7 +2549,28 @@ namespace MediaBrowser.Server.Implementations.Persistence
whereClauses.Add("MediaType in (" + val + ")");
}
- var enableItemsByName = query.IncludeItemsByName ?? query.IncludeItemTypes.Length > 0;
+ if (query.AlbumNames.Length > 0)
+ {
+ var clause = "(";
+
+ var index = 0;
+ foreach (var name in query.AlbumNames)
+ {
+ if (index > 0)
+ {
+ clause += " OR ";
+ }
+ clause += "Album=@AlbumName" + index;
+ index++;
+ cmd.Parameters.Add(cmd, "@AlbumName" + index, DbType.String).Value = name;
+ }
+
+ clause += ")";
+ whereClauses.Add(clause);
+ }
+
+ //var enableItemsByName = query.IncludeItemsByName ?? query.IncludeItemTypes.Length > 0;
+ var enableItemsByName = query.IncludeItemsByName ?? false;
if (query.TopParentIds.Length == 1)
{
@@ -2171,6 +2610,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
var inClause = string.Join(",", query.AncestorIds.Select(i => "'" + new Guid(i).ToString("N") + "'").ToArray());
whereClauses.Add(string.Format("Guid in (select itemId from AncestorIds where AncestorIdText in ({0}))", inClause));
}
+ if (!string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey))
+ {
+ var inClause = "select guid from TypedBaseItems where PresentationUniqueKey=@AncestorWithPresentationUniqueKey";
+ whereClauses.Add(string.Format("Guid in (select itemId from AncestorIds where AncestorId in ({0}))", inClause));
+ cmd.Parameters.Add(cmd, "@AncestorWithPresentationUniqueKey", DbType.String).Value = query.AncestorWithPresentationUniqueKey;
+ }
if (query.BlockUnratedItems.Length == 1)
{
@@ -2194,28 +2639,50 @@ namespace MediaBrowser.Server.Implementations.Persistence
excludeTagIndex = 0;
foreach (var excludeTag in query.ExcludeInheritedTags)
{
- whereClauses.Add("(InheritedTags is null OR InheritedTags not like @excludeInheritedTag" + excludeTagIndex +")");
+ whereClauses.Add("(InheritedTags is null OR InheritedTags not like @excludeInheritedTag" + excludeTagIndex + ")");
cmd.Parameters.Add(cmd, "@excludeInheritedTag" + excludeTagIndex, DbType.String).Value = "%" + excludeTag + "%";
excludeTagIndex++;
}
- if (addPaging)
+ return whereClauses;
+ }
+
+ private bool EnableGroupByPresentationUniqueKey(InternalItemsQuery query)
+ {
+ if (!query.GroupByPresentationUniqueKey)
{
- if (query.StartIndex.HasValue && query.StartIndex.Value > 0)
- {
- var pagingWhereText = whereClauses.Count == 0 ?
- string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
+ return false;
+ }
- var orderBy = GetOrderByText(query);
+ if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey))
+ {
+ return false;
+ }
- whereClauses.Add(string.Format("guid NOT IN (SELECT guid FROM TypedBaseItems {0}" + orderBy + " LIMIT {1})",
- pagingWhereText,
- query.StartIndex.Value.ToString(CultureInfo.InvariantCulture)));
- }
+ if (query.User == null)
+ {
+ return false;
}
- return whereClauses;
+ if (query.IncludeItemTypes.Length == 0)
+ {
+ return true;
+ }
+
+ var types = new[] {
+ typeof(Episode).Name,
+ typeof(Video).Name ,
+ typeof(Movie).Name ,
+ typeof(MusicVideo).Name ,
+ typeof(Series).Name ,
+ typeof(Season).Name };
+
+ if (types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase)))
+ {
+ return true;
+ }
+
+ return false;
}
private static readonly Type[] KnownTypes =
@@ -2296,7 +2763,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
try
{
transaction = _connection.BeginTransaction();
-
+
foreach (var item in newValues)
{
_updateInheritedTagsCommand.GetParameter(0).Value = item.Item1;
@@ -2482,6 +2949,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
_deleteAncestorsCommand.Transaction = transaction;
_deleteAncestorsCommand.ExecuteNonQuery();
+ // Delete user data keys
+ _deleteUserDataKeysCommand.GetParameter(0).Value = id;
+ _deleteUserDataKeysCommand.Transaction = transaction;
+ _deleteUserDataKeysCommand.ExecuteNonQuery();
+
// Delete the item
_deleteItemCommand.GetParameter(0).Value = id;
_deleteItemCommand.Transaction = transaction;
@@ -2674,6 +3146,39 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
}
+ private void UpdateUserDataKeys(Guid itemId, List<string> keys, IDbTransaction transaction)
+ {
+ if (itemId == Guid.Empty)
+ {
+ throw new ArgumentNullException("itemId");
+ }
+
+ if (keys == null)
+ {
+ throw new ArgumentNullException("keys");
+ }
+
+ CheckDisposed();
+
+ // First delete
+ _deleteUserDataKeysCommand.GetParameter(0).Value = itemId;
+ _deleteUserDataKeysCommand.Transaction = transaction;
+
+ _deleteUserDataKeysCommand.ExecuteNonQuery();
+ var index = 0;
+
+ foreach (var key in keys)
+ {
+ _saveUserDataKeysCommand.GetParameter(0).Value = itemId;
+ _saveUserDataKeysCommand.GetParameter(1).Value = key;
+ _saveUserDataKeysCommand.GetParameter(2).Value = index;
+ index++;
+ _saveUserDataKeysCommand.Transaction = transaction;
+
+ _saveUserDataKeysCommand.ExecuteNonQuery();
+ }
+ }
+
public async Task UpdatePeople(Guid itemId, List<PersonInfo> people)
{
if (itemId == Guid.Empty)
@@ -2790,6 +3295,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
throw new ArgumentNullException("query");
}
+ var list = new List<MediaStream>();
+
using (var cmd = _connection.CreateCommand())
{
var cmdText = "select " + string.Join(",", _mediaStreamSaveColumns) + " from mediastreams where";
@@ -2817,10 +3324,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
while (reader.Read())
{
- yield return GetMediaStream(reader);
+ list.Add(GetMediaStream(reader));
}
}
}
+
+ return list;
}
public async Task SaveMediaStreams(Guid id, IEnumerable<MediaStream> streams, CancellationToken cancellationToken)
@@ -2893,6 +3402,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
_saveStreamCommand.GetParameter(index++).Value = stream.CodecTag;
_saveStreamCommand.GetParameter(index++).Value = stream.Comment;
_saveStreamCommand.GetParameter(index++).Value = stream.NalLengthSize;
+ _saveStreamCommand.GetParameter(index++).Value = stream.IsAVC;
_saveStreamCommand.Transaction = transaction;
_saveStreamCommand.ExecuteNonQuery();
@@ -3056,6 +3566,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
item.NalLengthSize = reader.GetString(27);
}
+ if (!reader.IsDBNull(28))
+ {
+ item.IsAVC = reader.GetBoolean(28);
+ }
+
return item;
}
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs
index dbceda727..40d5c9586 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs
@@ -39,11 +39,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
/// Opens the connection to the database
/// </summary>
/// <returns>Task.</returns>
- public async Task Initialize()
+ public async Task Initialize(IDbConnector dbConnector)
{
var dbFile = Path.Combine(_appPaths.DataPath, "refreshinfo.db");
- _connection = await SqliteExtensions.ConnectToDb(dbFile, Logger).ConfigureAwait(false);
+ _connection = await dbConnector.Connect(dbFile).ConfigureAwait(false);
string[] queries = {
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs
index 63c41c71f..7f3b32e06 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs
@@ -37,16 +37,17 @@ namespace MediaBrowser.Server.Implementations.Persistence
/// Opens the connection to the database
/// </summary>
/// <returns>Task.</returns>
- public async Task Initialize()
+ public async Task Initialize(IDbConnector dbConnector)
{
var dbFile = Path.Combine(_appPaths.DataPath, "userdata_v2.db");
- _connection = await SqliteExtensions.ConnectToDb(dbFile, Logger).ConfigureAwait(false);
+ _connection = await dbConnector.Connect(dbFile).ConfigureAwait(false);
string[] queries = {
"create table if not exists userdata (key nvarchar, userId GUID, rating float null, played bit, playCount int, isFavorite bit, playbackPositionTicks bigint, lastPlayedDate datetime null)",
+ "create index if not exists idx_userdata on userdata(key)",
"create unique index if not exists userdataindex on userdata (key, userId)",
//pragmas
@@ -295,11 +296,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
}
- return new UserItemData
- {
- UserId = userId,
- Key = key
- };
+ return null;
}
}
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteUserRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteUserRepository.cs
index 9bd7e47f3..f7ca39a54 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteUserRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteUserRepository.cs
@@ -43,12 +43,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
/// Opens the connection to the database
/// </summary>
/// <returns>Task.</returns>
- public async Task Initialize()
+ public async Task Initialize(IDbConnector dbConnector)
{
var dbFile = Path.Combine(_appPaths.DataPath, "users.db");
- _connection = await SqliteExtensions.ConnectToDb(dbFile, Logger).ConfigureAwait(false);
-
+ _connection = await dbConnector.Connect(dbFile).ConfigureAwait(false);
+
string[] queries = {
"create table if not exists users (guid GUID primary key, data BLOB)",
diff --git a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs
index 4a69646f8..ea2460719 100644
--- a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs
+++ b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs
@@ -17,7 +17,7 @@ using MediaBrowser.Model.Configuration;
namespace MediaBrowser.Server.Implementations.Photos
{
- public abstract class BaseDynamicImageProvider<T> : IHasChangeMonitor, IForcedProvider, ICustomMetadataProvider<T>, IHasOrder
+ public abstract class BaseDynamicImageProvider<T> : IHasItemChangeMonitor, IForcedProvider, ICustomMetadataProvider<T>, IHasOrder
where T : IHasMetadata
{
protected IFileSystem FileSystem { get; private set; }
@@ -109,6 +109,21 @@ namespace MediaBrowser.Server.Implementations.Photos
protected async Task<ItemUpdateType> FetchAsync(IHasImages item, ImageType imageType, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
+ var image = item.GetImageInfo(imageType, 0);
+
+ if (image != null)
+ {
+ if (!image.IsLocalFile)
+ {
+ return ItemUpdateType.None;
+ }
+
+ if (!FileSystem.ContainsSubPath(item.GetInternalMetadataPath(), image.Path))
+ {
+ return ItemUpdateType.None;
+ }
+ }
+
var items = await GetItemsWithImages(item).ConfigureAwait(false);
return await FetchToFileInternal(item, items, imageType, cancellationToken).ConfigureAwait(false);
@@ -247,7 +262,7 @@ namespace MediaBrowser.Server.Implementations.Photos
get { return 7; }
}
- public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
+ public bool HasChanged(IHasMetadata item, IDirectoryService directoryServicee)
{
if (!Supports(item))
{
diff --git a/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs b/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs
index 06ef05951..53c03b91c 100644
--- a/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs
+++ b/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs
@@ -158,7 +158,7 @@ namespace MediaBrowser.Server.Implementations.Playlists
return path;
}
- private IEnumerable<BaseItem> GetPlaylistItems(IEnumerable<string> itemIds, string playlistMediaType, User user)
+ private Task<IEnumerable<BaseItem>> GetPlaylistItems(IEnumerable<string> itemIds, string playlistMediaType, User user)
{
var items = itemIds.Select(i => _libraryManager.GetItemById(i)).Where(i => i != null);
@@ -183,7 +183,7 @@ namespace MediaBrowser.Server.Implementations.Playlists
var list = new List<LinkedChild>();
- var items = GetPlaylistItems(itemIds, playlist.MediaType, user)
+ var items = (await GetPlaylistItems(itemIds, playlist.MediaType, user).ConfigureAwait(false))
.Where(i => i.SupportsAddingToPlaylist)
.ToList();
diff --git a/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs b/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs
index e50de7bac..607a043a6 100644
--- a/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs
+++ b/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs
@@ -12,6 +12,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Model.Entities;
namespace MediaBrowser.Server.Implementations.ScheduledTasks
{
@@ -85,8 +86,13 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
/// <returns>Task.</returns>
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
{
- var videos = _libraryManager.RootFolder.GetRecursiveChildren(i => i is Video)
- .Cast<Video>()
+ var videos = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ MediaTypes = new[] { MediaType.Video },
+ IsFolder = false,
+ Recursive = true
+ })
+ .OfType<Video>()
.ToList();
var numComplete = 0;
@@ -97,7 +103,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
try
{
- previouslyFailedImages = _fileSystem.ReadAllText(failHistoryPath)
+ previouslyFailedImages = _fileSystem.ReadAllText(failHistoryPath)
.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
.ToList();
}
diff --git a/MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs b/MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs
index b932f0cac..e8d9814ec 100644
--- a/MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs
+++ b/MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs
@@ -27,11 +27,11 @@ namespace MediaBrowser.Server.Implementations.Security
_appPaths = appPaths;
}
- public async Task Initialize()
+ public async Task Initialize(IDbConnector dbConnector)
{
var dbFile = Path.Combine(_appPaths.DataPath, "authentication.db");
- _connection = await SqliteExtensions.ConnectToDb(dbFile, Logger).ConfigureAwait(false);
+ _connection = await dbConnector.Connect(dbFile).ConfigureAwait(false);
string[] queries = {
diff --git a/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs b/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs
index 8719f5448..33d106916 100644
--- a/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs
+++ b/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs
@@ -71,6 +71,8 @@ namespace MediaBrowser.Server.Implementations.ServerManager
/// <value>The web socket listeners.</value>
private readonly List<IWebSocketListener> _webSocketListeners = new List<IWebSocketListener>();
+ private bool _disposed;
+
/// <summary>
/// Initializes a new instance of the <see cref="ServerManager" /> class.
/// </summary>
@@ -143,6 +145,11 @@ namespace MediaBrowser.Server.Implementations.ServerManager
/// <param name="e">The <see cref="WebSocketConnectEventArgs" /> instance containing the event data.</param>
void HttpServer_WebSocketConnected(object sender, WebSocketConnectEventArgs e)
{
+ if (_disposed)
+ {
+ return;
+ }
+
var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger)
{
OnReceive = ProcessWebSocketMessageReceived,
@@ -164,6 +171,11 @@ namespace MediaBrowser.Server.Implementations.ServerManager
/// <param name="result">The result.</param>
private async void ProcessWebSocketMessageReceived(WebSocketMessageInfo result)
{
+ if (_disposed)
+ {
+ return;
+ }
+
//_logger.Debug("Websocket message received: {0}", result.MessageType);
var tasks = _webSocketListeners.Select(i => Task.Run(async () =>
@@ -244,6 +256,11 @@ namespace MediaBrowser.Server.Implementations.ServerManager
throw new ArgumentNullException("dataFunction");
}
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(GetType().Name);
+ }
+
cancellationToken.ThrowIfCancellationRequested();
var connectionsList = connections.Where(s => s.State == WebSocketState.Open).ToList();
@@ -301,6 +318,8 @@ namespace MediaBrowser.Server.Implementations.ServerManager
/// </summary>
public void Dispose()
{
+ _disposed = true;
+
Dispose(true);
GC.SuppressFinalize(this);
}
diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs
index 88f11c368..77843ef6b 100644
--- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs
+++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs
@@ -601,11 +601,9 @@ namespace MediaBrowser.Server.Implementations.Session
if (libraryItem != null)
{
- var key = libraryItem.GetUserDataKey();
-
foreach (var user in users)
{
- await OnPlaybackStart(user.Id, key, libraryItem).ConfigureAwait(false);
+ await OnPlaybackStart(user.Id, libraryItem).ConfigureAwait(false);
}
}
@@ -632,12 +630,11 @@ namespace MediaBrowser.Server.Implementations.Session
/// Called when [playback start].
/// </summary>
/// <param name="userId">The user identifier.</param>
- /// <param name="userDataKey">The user data key.</param>
/// <param name="item">The item.</param>
/// <returns>Task.</returns>
- private async Task OnPlaybackStart(Guid userId, string userDataKey, IHasUserData item)
+ private async Task OnPlaybackStart(Guid userId, IHasUserData item)
{
- var data = _userDataRepository.GetUserData(userId, userDataKey);
+ var data = _userDataRepository.GetUserData(userId, item);
data.PlayCount++;
data.LastPlayedDate = DateTime.UtcNow;
@@ -676,11 +673,9 @@ namespace MediaBrowser.Server.Implementations.Session
if (libraryItem != null)
{
- var key = libraryItem.GetUserDataKey();
-
foreach (var user in users)
{
- await OnPlaybackProgress(user, key, libraryItem, info).ConfigureAwait(false);
+ await OnPlaybackProgress(user, libraryItem, info).ConfigureAwait(false);
}
}
@@ -714,9 +709,9 @@ namespace MediaBrowser.Server.Implementations.Session
StartIdleCheckTimer();
}
- private async Task OnPlaybackProgress(User user, string userDataKey, BaseItem item, PlaybackProgressInfo info)
+ private async Task OnPlaybackProgress(User user, BaseItem item, PlaybackProgressInfo info)
{
- var data = _userDataRepository.GetUserData(user.Id, userDataKey);
+ var data = _userDataRepository.GetUserData(user.Id, item);
var positionTicks = info.PositionTicks;
@@ -811,11 +806,9 @@ namespace MediaBrowser.Server.Implementations.Session
if (libraryItem != null)
{
- var key = libraryItem.GetUserDataKey();
-
foreach (var user in users)
{
- playedToCompletion = await OnPlaybackStopped(user.Id, key, libraryItem, info.PositionTicks, info.Failed).ConfigureAwait(false);
+ playedToCompletion = await OnPlaybackStopped(user.Id, libraryItem, info.PositionTicks, info.Failed).ConfigureAwait(false);
}
}
@@ -848,14 +841,14 @@ namespace MediaBrowser.Server.Implementations.Session
await SendPlaybackStoppedNotification(session, CancellationToken.None).ConfigureAwait(false);
}
- private async Task<bool> OnPlaybackStopped(Guid userId, string userDataKey, BaseItem item, long? positionTicks, bool playbackFailed)
+ private async Task<bool> OnPlaybackStopped(Guid userId, BaseItem item, long? positionTicks, bool playbackFailed)
{
bool playedToCompletion = false;
if (!playbackFailed)
{
- var data = _userDataRepository.GetUserData(userId, userDataKey);
-
+ var data = _userDataRepository.GetUserData(userId, item);
+
if (positionTicks.HasValue)
{
playedToCompletion = _userDataRepository.UpdatePlayState(item, data, positionTicks.Value);
@@ -1033,11 +1026,11 @@ namespace MediaBrowser.Server.Implementations.Session
if (byName != null)
{
- var itemFilter = byName.GetItemFilter();
-
- var items = user == null ?
- _libraryManager.RootFolder.GetRecursiveChildren(i => !i.IsFolder && itemFilter(i)) :
- user.RootFolder.GetRecursiveChildren(user, i => !i.IsFolder && itemFilter(i));
+ var items = byName.GetTaggedItems(new InternalItemsQuery(user)
+ {
+ IsFolder = false,
+ Recursive = true
+ });
return FilterToSingleMediaType(items)
.OrderBy(i => i.SortName);
@@ -1047,9 +1040,12 @@ namespace MediaBrowser.Server.Implementations.Session
{
var folder = (Folder)item;
- var items = user == null ?
- folder.GetRecursiveChildren(i => !i.IsFolder) :
- folder.GetRecursiveChildren(user, i => !i.IsFolder);
+ var items = folder.GetItems(new InternalItemsQuery(user)
+ {
+ Recursive = true,
+ IsFolder = false
+
+ }).Result.Items;
return FilterToSingleMediaType(items)
.OrderBy(i => i.SortName);
@@ -1374,8 +1370,8 @@ namespace MediaBrowser.Server.Implementations.Session
ServerId = _appHost.SystemId
};
}
-
-
+
+
private async Task<string> GetAuthorizationToken(string userId, string deviceId, string app, string appVersion, string deviceName)
{
var existing = _authRepo.Get(new AuthenticationInfoQuery
diff --git a/MediaBrowser.Server.Implementations/Social/SharingRepository.cs b/MediaBrowser.Server.Implementations/Social/SharingRepository.cs
index d6d7f021a..317743eb1 100644
--- a/MediaBrowser.Server.Implementations/Social/SharingRepository.cs
+++ b/MediaBrowser.Server.Implementations/Social/SharingRepository.cs
@@ -26,11 +26,11 @@ namespace MediaBrowser.Server.Implementations.Social
/// Opens the connection to the database
/// </summary>
/// <returns>Task.</returns>
- public async Task Initialize()
+ public async Task Initialize(IDbConnector dbConnector)
{
var dbFile = Path.Combine(_appPaths.DataPath, "shares.db");
- _connection = await SqliteExtensions.ConnectToDb(dbFile, Logger).ConfigureAwait(false);
+ _connection = await dbConnector.Connect(dbFile).ConfigureAwait(false);
string[] queries = {
diff --git a/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
index 70cf805cf..91abbe34c 100644
--- a/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
+++ b/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
@@ -49,8 +49,8 @@ namespace MediaBrowser.Server.Implementations.Sorting
private int Compare(Episode x, Episode y)
{
- var isXSpecial = (x.PhysicalSeasonNumber ?? -1) == 0;
- var isYSpecial = (y.PhysicalSeasonNumber ?? -1) == 0;
+ var isXSpecial = (x.ParentIndexNumber ?? -1) == 0;
+ var isYSpecial = (y.ParentIndexNumber ?? -1) == 0;
if (isXSpecial && isYSpecial)
{
@@ -74,7 +74,7 @@ namespace MediaBrowser.Server.Implementations.Sorting
{
// http://thetvdb.com/wiki/index.php?title=Special_Episodes
- var xSeason = x.PhysicalSeasonNumber ?? -1;
+ var xSeason = x.ParentIndexNumber ?? -1;
var ySeason = y.AirsAfterSeasonNumber ?? y.AirsBeforeSeasonNumber ?? -1;
if (xSeason != ySeason)
@@ -142,8 +142,8 @@ namespace MediaBrowser.Server.Implementations.Sorting
private int CompareEpisodes(Episode x, Episode y)
{
- var xValue = (x.PhysicalSeasonNumber ?? -1) * 1000 + (x.IndexNumber ?? -1);
- var yValue = (y.PhysicalSeasonNumber ?? -1) * 1000 + (y.IndexNumber ?? -1);
+ var xValue = (x.ParentIndexNumber ?? -1) * 1000 + (x.IndexNumber ?? -1);
+ var yValue = (y.ParentIndexNumber ?? -1) * 1000 + (y.IndexNumber ?? -1);
return xValue.CompareTo(yValue);
}
diff --git a/MediaBrowser.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs b/MediaBrowser.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs
index 68cd44ec9..e8c78b8e7 100644
--- a/MediaBrowser.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs
+++ b/MediaBrowser.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs
@@ -49,13 +49,13 @@ namespace MediaBrowser.Server.Implementations.Sorting
if (folder != null)
{
- return folder.GetRecursiveChildren(User, i => !i.IsFolder)
- .Select(i => i.DateCreated)
- .OrderByDescending(i => i)
- .FirstOrDefault();
+ if (folder.DateLastMediaAdded.HasValue)
+ {
+ return folder.DateLastMediaAdded.Value;
+ }
}
- return x.DateCreated;
+ return DateTime.MinValue;
}
/// <summary>
diff --git a/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs b/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs
index c881591be..3edf23020 100644
--- a/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs
+++ b/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs
@@ -47,7 +47,7 @@ namespace MediaBrowser.Server.Implementations.Sorting
/// <returns>DateTime.</returns>
private DateTime GetDate(BaseItem x)
{
- var userdata = UserDataRepository.GetUserData(User.Id, x.GetUserDataKey());
+ var userdata = UserDataRepository.GetUserData(User, x);
if (userdata != null && userdata.LastPlayedDate.HasValue)
{
diff --git a/MediaBrowser.Server.Implementations/Sorting/PlayCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/PlayCountComparer.cs
index 1bc5261b4..8b14efffc 100644
--- a/MediaBrowser.Server.Implementations/Sorting/PlayCountComparer.cs
+++ b/MediaBrowser.Server.Implementations/Sorting/PlayCountComparer.cs
@@ -34,7 +34,7 @@ namespace MediaBrowser.Server.Implementations.Sorting
/// <returns>DateTime.</returns>
private int GetValue(BaseItem x)
{
- var userdata = UserDataRepository.GetUserData(User.Id, x.GetUserDataKey());
+ var userdata = UserDataRepository.GetUserData(User, x);
return userdata == null ? 0 : userdata.PlayCount;
}
diff --git a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs
index 33874b4d4..bbba06870 100644
--- a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs
+++ b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs
@@ -149,6 +149,11 @@ namespace MediaBrowser.Server.Implementations.Sync
{
var job = _syncRepo.GetJob(id);
+ if (job == null)
+ {
+ return Task.FromResult(true);
+ }
+
var result = _syncManager.GetJobItems(new SyncJobItemQuery
{
JobId = job.Id,
@@ -321,32 +326,27 @@ namespace MediaBrowser.Server.Implementations.Sync
var itemByName = item as IItemByName;
if (itemByName != null)
{
- var itemByNameFilter = itemByName.GetItemFilter();
-
- return user.RootFolder
- .GetRecursiveChildren(user, i => !i.IsFolder && itemByNameFilter(i));
- }
-
- var series = item as Series;
- if (series != null)
- {
- return series.GetEpisodes(user, false, false);
- }
-
- var season = item as Season;
- if (season != null)
- {
- return season.GetEpisodes(user, false, false);
+ return itemByName.GetTaggedItems(new InternalItemsQuery(user)
+ {
+ IsFolder = false,
+ Recursive = true
+ });
}
if (item.IsFolder)
{
var folder = (Folder)item;
- var items = folder.GetRecursiveChildren(user, i => !i.IsFolder);
+ var items = folder.GetItems(new InternalItemsQuery(user)
+ {
+ Recursive = true,
+ IsFolder = false
+
+ }).Result.Items;
if (!folder.IsPreSorted)
{
- items = items.OrderBy(i => i.SortName);
+ items = _libraryManager.Sort(items, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending)
+ .ToArray();
}
return items;
@@ -577,7 +577,7 @@ namespace MediaBrowser.Server.Implementations.Sync
conversionOptions.ItemId = item.Id.ToString("N");
conversionOptions.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, false, user).ToList();
- var streamInfo = new StreamBuilder(_logger).BuildVideoItem(conversionOptions);
+ var streamInfo = new StreamBuilder(_mediaEncoder, _logger).BuildVideoItem(conversionOptions);
var mediaSource = streamInfo.MediaSource;
// No sense creating external subs if we're already burning one into the video
@@ -632,6 +632,7 @@ namespace MediaBrowser.Server.Implementations.Sync
}, innerProgress, cancellationToken);
+ jobItem.ItemDateModifiedTicks = item.DateModified.Ticks;
_syncManager.OnConversionComplete(jobItem);
}
catch (OperationCanceledException)
@@ -668,6 +669,7 @@ namespace MediaBrowser.Server.Implementations.Sync
throw new InvalidOperationException(string.Format("Cannot direct stream {0} protocol", mediaSource.Protocol));
}
+ jobItem.ItemDateModifiedTicks = item.DateModified.Ticks;
jobItem.MediaSource = mediaSource;
}
@@ -779,7 +781,7 @@ namespace MediaBrowser.Server.Implementations.Sync
conversionOptions.ItemId = item.Id.ToString("N");
conversionOptions.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, false, user).ToList();
- var streamInfo = new StreamBuilder(_logger).BuildAudioItem(conversionOptions);
+ var streamInfo = new StreamBuilder(_mediaEncoder, _logger).BuildAudioItem(conversionOptions);
var mediaSource = streamInfo.MediaSource;
jobItem.MediaSourceId = streamInfo.MediaSourceId;
@@ -819,6 +821,7 @@ namespace MediaBrowser.Server.Implementations.Sync
}, innerProgress, cancellationToken);
+ jobItem.ItemDateModifiedTicks = item.DateModified.Ticks;
_syncManager.OnConversionComplete(jobItem);
}
catch (OperationCanceledException)
@@ -855,6 +858,7 @@ namespace MediaBrowser.Server.Implementations.Sync
throw new InvalidOperationException(string.Format("Cannot direct stream {0} protocol", mediaSource.Protocol));
}
+ jobItem.ItemDateModifiedTicks = item.DateModified.Ticks;
jobItem.MediaSource = mediaSource;
}
@@ -871,6 +875,7 @@ namespace MediaBrowser.Server.Implementations.Sync
jobItem.Progress = 50;
jobItem.Status = SyncJobItemStatus.ReadyToTransfer;
+ jobItem.ItemDateModifiedTicks = item.DateModified.Ticks;
await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
}
@@ -880,6 +885,7 @@ namespace MediaBrowser.Server.Implementations.Sync
jobItem.Progress = 50;
jobItem.Status = SyncJobItemStatus.ReadyToTransfer;
+ jobItem.ItemDateModifiedTicks = item.DateModified.Ticks;
await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs
index 044c8b93a..38edc3024 100644
--- a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs
+++ b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs
@@ -687,7 +687,7 @@ namespace MediaBrowser.Server.Implementations.Sync
private Task ReportOfflinePlayedItem(UserAction action)
{
var item = _libraryManager.GetItemById(action.ItemId);
- var userData = _userDataManager.GetUserData(new Guid(action.UserId), item.GetUserDataKey());
+ var userData = _userDataManager.GetUserData(action.UserId, item);
userData.LastPlayedDate = action.Date;
_userDataManager.UpdatePlayState(item, userData, action.PositionTicks);
@@ -775,6 +775,13 @@ namespace MediaBrowser.Server.Implementations.Sync
removeFromDevice = true;
}
}
+ else if (libraryItem != null && libraryItem.DateModified.Ticks != jobItem.ItemDateModifiedTicks && jobItem.ItemDateModifiedTicks > 0)
+ {
+ _logger.Info("Setting status to Queued for {0} because the media has been modified since the original sync.", jobItem.ItemId);
+ jobItem.Status = SyncJobItemStatus.Queued;
+ jobItem.Progress = 0;
+ requiresSaving = true;
+ }
}
else
{
@@ -881,6 +888,13 @@ namespace MediaBrowser.Server.Implementations.Sync
removeFromDevice = true;
}
}
+ else if (libraryItem != null && libraryItem.DateModified.Ticks != jobItem.ItemDateModifiedTicks && jobItem.ItemDateModifiedTicks > 0)
+ {
+ _logger.Info("Setting status to Queued for {0} because the media has been modified since the original sync.", jobItem.ItemId);
+ jobItem.Status = SyncJobItemStatus.Queued;
+ jobItem.Progress = 0;
+ requiresSaving = true;
+ }
}
else
{
@@ -1117,7 +1131,7 @@ namespace MediaBrowser.Server.Implementations.Sync
public SyncJobOptions GetAudioOptions(SyncJobItem jobItem, SyncJob job)
{
var options = GetSyncJobOptions(jobItem.TargetId, null, null);
-
+
if (job.Bitrate.HasValue)
{
options.DeviceProfile.MaxStaticBitrate = job.Bitrate.Value;
diff --git a/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs b/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs
index 464e8aa58..6d31663b9 100644
--- a/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs
+++ b/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs
@@ -39,18 +39,18 @@ namespace MediaBrowser.Server.Implementations.Sync
_appPaths = appPaths;
}
- public async Task Initialize()
+ public async Task Initialize(IDbConnector dbConnector)
{
var dbFile = Path.Combine(_appPaths.DataPath, "sync14.db");
- _connection = await SqliteExtensions.ConnectToDb(dbFile, Logger).ConfigureAwait(false);
+ _connection = await dbConnector.Connect(dbFile).ConfigureAwait(false);
string[] queries = {
"create table if not exists SyncJobs (Id GUID PRIMARY KEY, TargetId TEXT NOT NULL, Name TEXT NOT NULL, Profile TEXT, Quality TEXT, Bitrate INT, Status TEXT NOT NULL, Progress FLOAT, UserId TEXT NOT NULL, ItemIds TEXT NOT NULL, Category TEXT, ParentId TEXT, UnwatchedOnly BIT, ItemLimit INT, SyncNewContent BIT, DateCreated DateTime, DateLastModified DateTime, ItemCount int)",
"create index if not exists idx_SyncJobs on SyncJobs(Id)",
- "create table if not exists SyncJobItems (Id GUID PRIMARY KEY, ItemId TEXT, ItemName TEXT, MediaSourceId TEXT, JobId TEXT, TemporaryPath TEXT, OutputPath TEXT, Status TEXT, TargetId TEXT, DateCreated DateTime, Progress FLOAT, AdditionalFiles TEXT, MediaSource TEXT, IsMarkedForRemoval BIT, JobItemIndex INT)",
+ "create table if not exists SyncJobItems (Id GUID PRIMARY KEY, ItemId TEXT, ItemName TEXT, MediaSourceId TEXT, JobId TEXT, TemporaryPath TEXT, OutputPath TEXT, Status TEXT, TargetId TEXT, DateCreated DateTime, Progress FLOAT, AdditionalFiles TEXT, MediaSource TEXT, IsMarkedForRemoval BIT, JobItemIndex INT, ItemDateModifiedTicks BIGINT)",
"create index if not exists idx_SyncJobItems on SyncJobs(Id)",
//pragmas
@@ -63,6 +63,7 @@ namespace MediaBrowser.Server.Implementations.Sync
_connection.AddColumn(Logger, "SyncJobs", "Profile", "TEXT");
_connection.AddColumn(Logger, "SyncJobs", "Bitrate", "INT");
+ _connection.AddColumn(Logger, "SyncJobItems", "ItemDateModifiedTicks", "BIGINT");
PrepareStatements();
}
@@ -127,7 +128,7 @@ namespace MediaBrowser.Server.Implementations.Sync
// _insertJobItemCommand
_insertJobItemCommand = _connection.CreateCommand();
- _insertJobItemCommand.CommandText = "insert into SyncJobItems (Id, ItemId, ItemName, MediaSourceId, JobId, TemporaryPath, OutputPath, Status, TargetId, DateCreated, Progress, AdditionalFiles, MediaSource, IsMarkedForRemoval, JobItemIndex) values (@Id, @ItemId, @ItemName, @MediaSourceId, @JobId, @TemporaryPath, @OutputPath, @Status, @TargetId, @DateCreated, @Progress, @AdditionalFiles, @MediaSource, @IsMarkedForRemoval, @JobItemIndex)";
+ _insertJobItemCommand.CommandText = "insert into SyncJobItems (Id, ItemId, ItemName, MediaSourceId, JobId, TemporaryPath, OutputPath, Status, TargetId, DateCreated, Progress, AdditionalFiles, MediaSource, IsMarkedForRemoval, JobItemIndex, ItemDateModifiedTicks) values (@Id, @ItemId, @ItemName, @MediaSourceId, @JobId, @TemporaryPath, @OutputPath, @Status, @TargetId, @DateCreated, @Progress, @AdditionalFiles, @MediaSource, @IsMarkedForRemoval, @JobItemIndex, @ItemDateModifiedTicks)";
_insertJobItemCommand.Parameters.Add(_insertJobItemCommand, "@Id");
_insertJobItemCommand.Parameters.Add(_insertJobItemCommand, "@ItemId");
@@ -144,10 +145,11 @@ namespace MediaBrowser.Server.Implementations.Sync
_insertJobItemCommand.Parameters.Add(_insertJobItemCommand, "@MediaSource");
_insertJobItemCommand.Parameters.Add(_insertJobItemCommand, "@IsMarkedForRemoval");
_insertJobItemCommand.Parameters.Add(_insertJobItemCommand, "@JobItemIndex");
+ _insertJobItemCommand.Parameters.Add(_insertJobItemCommand, "@ItemDateModifiedTicks");
// _updateJobItemCommand
_updateJobItemCommand = _connection.CreateCommand();
- _updateJobItemCommand.CommandText = "update SyncJobItems set ItemId=@ItemId,ItemName=@ItemName,MediaSourceId=@MediaSourceId,JobId=@JobId,TemporaryPath=@TemporaryPath,OutputPath=@OutputPath,Status=@Status,TargetId=@TargetId,DateCreated=@DateCreated,Progress=@Progress,AdditionalFiles=@AdditionalFiles,MediaSource=@MediaSource,IsMarkedForRemoval=@IsMarkedForRemoval,JobItemIndex=@JobItemIndex where Id=@Id";
+ _updateJobItemCommand.CommandText = "update SyncJobItems set ItemId=@ItemId,ItemName=@ItemName,MediaSourceId=@MediaSourceId,JobId=@JobId,TemporaryPath=@TemporaryPath,OutputPath=@OutputPath,Status=@Status,TargetId=@TargetId,DateCreated=@DateCreated,Progress=@Progress,AdditionalFiles=@AdditionalFiles,MediaSource=@MediaSource,IsMarkedForRemoval=@IsMarkedForRemoval,JobItemIndex=@JobItemIndex,ItemDateModifiedTicks=@ItemDateModifiedTicks where Id=@Id";
_updateJobItemCommand.Parameters.Add(_updateJobItemCommand, "@Id");
_updateJobItemCommand.Parameters.Add(_updateJobItemCommand, "@ItemId");
@@ -164,10 +166,11 @@ namespace MediaBrowser.Server.Implementations.Sync
_updateJobItemCommand.Parameters.Add(_updateJobItemCommand, "@MediaSource");
_updateJobItemCommand.Parameters.Add(_updateJobItemCommand, "@IsMarkedForRemoval");
_updateJobItemCommand.Parameters.Add(_updateJobItemCommand, "@JobItemIndex");
+ _updateJobItemCommand.Parameters.Add(_updateJobItemCommand, "@ItemDateModifiedTicks");
}
private const string BaseJobSelectText = "select Id, TargetId, Name, Profile, Quality, Bitrate, Status, Progress, UserId, ItemIds, Category, ParentId, UnwatchedOnly, ItemLimit, SyncNewContent, DateCreated, DateLastModified, ItemCount from SyncJobs";
- private const string BaseJobItemSelectText = "select Id, ItemId, ItemName, MediaSourceId, JobId, TemporaryPath, OutputPath, Status, TargetId, DateCreated, Progress, AdditionalFiles, MediaSource, IsMarkedForRemoval, JobItemIndex from SyncJobItems";
+ private const string BaseJobItemSelectText = "select Id, ItemId, ItemName, MediaSourceId, JobId, TemporaryPath, OutputPath, Status, TargetId, DateCreated, Progress, AdditionalFiles, MediaSource, IsMarkedForRemoval, JobItemIndex, ItemDateModifiedTicks from SyncJobItems";
public SyncJob GetJob(string id)
{
@@ -678,6 +681,7 @@ namespace MediaBrowser.Server.Implementations.Sync
cmd.GetParameter(index++).Value = jobItem.MediaSource == null ? null : _json.SerializeToString(jobItem.MediaSource);
cmd.GetParameter(index++).Value = jobItem.IsMarkedForRemoval;
cmd.GetParameter(index++).Value = jobItem.JobItemIndex;
+ cmd.GetParameter(index++).Value = jobItem.ItemDateModifiedTicks;
cmd.Transaction = transaction;
@@ -782,6 +786,11 @@ namespace MediaBrowser.Server.Implementations.Sync
info.IsMarkedForRemoval = reader.GetBoolean(13);
info.JobItemIndex = reader.GetInt32(14);
+ if (!reader.IsDBNull(15))
+ {
+ info.ItemDateModifiedTicks = reader.GetInt64(15);
+ }
+
return info;
}
diff --git a/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs b/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs
index 3e43ebe9b..ec91dc1b7 100644
--- a/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs
+++ b/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs
@@ -36,10 +36,25 @@ namespace MediaBrowser.Server.Implementations.TV
? new string[] { }
: new[] { request.ParentId };
+ string presentationUniqueKey = null;
+ int? limit = null;
+ if (!string.IsNullOrWhiteSpace(request.SeriesId))
+ {
+ var series = _libraryManager.GetItemById(request.SeriesId);
+
+ if (series != null)
+ {
+ presentationUniqueKey = series.PresentationUniqueKey;
+ limit = 1;
+ }
+ }
+
var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
IncludeItemTypes = new[] { typeof(Series).Name },
- SortOrder = SortOrder.Ascending
+ SortOrder = SortOrder.Ascending,
+ PresentationUniqueKey = presentationUniqueKey,
+ Limit = limit
}, parentIds).Cast<Series>();
@@ -58,10 +73,25 @@ namespace MediaBrowser.Server.Implementations.TV
throw new ArgumentException("User not found");
}
+ string presentationUniqueKey = null;
+ int? limit = null;
+ if (!string.IsNullOrWhiteSpace(request.SeriesId))
+ {
+ var series = _libraryManager.GetItemById(request.SeriesId);
+
+ if (series != null)
+ {
+ presentationUniqueKey = series.PresentationUniqueKey;
+ limit = 1;
+ }
+ }
+
var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
IncludeItemTypes = new[] { typeof(Series).Name },
- SortOrder = SortOrder.Ascending
+ SortOrder = SortOrder.Ascending,
+ PresentationUniqueKey = presentationUniqueKey,
+ Limit = limit
}, parentsFolders.Select(i => i.Id.ToString("N"))).Cast<Series>();
@@ -76,30 +106,30 @@ namespace MediaBrowser.Server.Implementations.TV
// Avoid implicitly captured closure
var currentUser = user;
- return FilterSeries(request, series)
+ return series
.AsParallel()
.Select(i => GetNextUp(i, currentUser))
// Include if an episode was found, and either the series is not unwatched or the specific series was requested
.Where(i => i.Item1 != null && (!i.Item3 || !string.IsNullOrWhiteSpace(request.SeriesId)))
- .OrderByDescending(i =>
- {
- var episode = i.Item1;
+ //.OrderByDescending(i =>
+ //{
+ // var episode = i.Item1;
- var seriesUserData = _userDataManager.GetUserData(user.Id, episode.Series.GetUserDataKey());
+ // var seriesUserData = _userDataManager.GetUserData(user, episode.Series);
- if (seriesUserData.IsFavorite)
- {
- return 2;
- }
+ // if (seriesUserData.IsFavorite)
+ // {
+ // return 2;
+ // }
- if (seriesUserData.Likes.HasValue)
- {
- return seriesUserData.Likes.Value ? 1 : -1;
- }
+ // if (seriesUserData.Likes.HasValue)
+ // {
+ // return seriesUserData.Likes.Value ? 1 : -1;
+ // }
- return 0;
- })
- .ThenByDescending(i => i.Item2)
+ // return 0;
+ //})
+ .OrderByDescending(i => i.Item2)
.ThenByDescending(i => i.Item1.PremiereDate ?? DateTime.MinValue)
.Select(i => i.Item1);
}
@@ -128,7 +158,7 @@ namespace MediaBrowser.Server.Implementations.TV
// Go back starting with the most recent episodes
foreach (var episode in allEpisodes)
{
- var userData = _userDataManager.GetUserData(user.Id, episode.GetUserDataKey());
+ var userData = _userDataManager.GetUserData(user, episode);
if (userData.Played)
{
@@ -142,7 +172,7 @@ namespace MediaBrowser.Server.Implementations.TV
}
else
{
- if (!episode.IsVirtualUnaired && (!episode.IsMissingEpisode || includeMissing))
+ if (!episode.IsVirtualUnaired && (includeMissing || !episode.IsMissingEpisode))
{
nextUp = episode;
}
@@ -154,24 +184,12 @@ namespace MediaBrowser.Server.Implementations.TV
return new Tuple<Episode, DateTime, bool>(nextUp, lastWatchedDate, false);
}
- var firstEpisode = allEpisodes.LastOrDefault(i => !i.IsVirtualUnaired && (!i.IsMissingEpisode || includeMissing) && !i.IsPlayed(user));
+ var firstEpisode = allEpisodes.LastOrDefault(i => !i.IsVirtualUnaired && (includeMissing || !i.IsMissingEpisode) && !i.IsPlayed(user));
// Return the first episode
return new Tuple<Episode, DateTime, bool>(firstEpisode, DateTime.MinValue, true);
}
- private IEnumerable<Series> FilterSeries(NextUpQuery request, IEnumerable<Series> items)
- {
- if (!string.IsNullOrWhiteSpace(request.SeriesId))
- {
- var id = new Guid(request.SeriesId);
-
- items = items.Where(i => i.Id == id);
- }
-
- return items;
- }
-
private QueryResult<BaseItem> GetResult(IEnumerable<BaseItem> items, int? totalRecordLimit, NextUpQuery query)
{
var itemsArray = totalRecordLimit.HasValue ? items.Take(totalRecordLimit.Value).ToArray() : items.ToArray();
diff --git a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs
index 911dbb0cb..161f771a9 100644
--- a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs
+++ b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs
@@ -153,7 +153,8 @@ namespace MediaBrowser.Server.Implementations.UserViews
CollectionType.HomeVideos,
CollectionType.BoxSets,
CollectionType.Playlists,
- CollectionType.Photos
+ CollectionType.Photos,
+ string.Empty
};
return collectionStripViewTypes.Contains(view.ViewType ?? string.Empty);
diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config
index 66aede029..dce839b57 100644
--- a/MediaBrowser.Server.Implementations/packages.config
+++ b/MediaBrowser.Server.Implementations/packages.config
@@ -8,5 +8,6 @@
<package id="Mono.Nat" version="1.2.24.0" targetFramework="net45" />
<package id="morelinq" version="1.4.0" targetFramework="net45" />
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
- <package id="SocketHttpListener" version="1.0.0.29" targetFramework="net45" />
+ <package id="SimpleInjector" version="3.1.3" targetFramework="net45" />
+ <package id="SocketHttpListener" version="1.0.0.30" targetFramework="net45" />
</packages> \ No newline at end of file