fixes #1001 - Support downloading

pull/702/head
Luke Pulverenti 9 years ago
parent 4ae6b5f675
commit b6d59c7688

@ -226,6 +226,18 @@ namespace MediaBrowser.Api.Library
public string TvdbId { get; set; }
}
[Route("/Items/{Id}/Download", "GET", Summary = "Downloads item media")]
[Authenticated(Roles = "download")]
public class GetDownload
{
/// <summary>
/// Gets or sets the id.
/// </summary>
/// <value>The id.</value>
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Id { get; set; }
}
/// <summary>
/// Class LibraryService
/// </summary>
@ -273,7 +285,7 @@ namespace MediaBrowser.Api.Library
}
var dtoOptions = GetDtoOptions(request);
var result = new ItemsResult
{
TotalRecordCount = items.Count,
@ -289,6 +301,28 @@ namespace MediaBrowser.Api.Library
Task.Run(() => _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None));
}
public object Get(GetDownload request)
{
var item = _libraryManager.GetItemById(request.Id);
if (!item.CanDelete())
{
throw new ArgumentException("Item does not support downloading");
}
var headers = new Dictionary<string, string>();
// Quotes are valid in linux. They'll possibly cause issues here
var filename = Path.GetFileName(item.Path).Replace("\"", string.Empty);
headers["Content-Disposition"] = string.Format("inline; filename=\"{0}\"", filename);
return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
{
Path = item.Path,
ResponseHeaders = headers
});
}
public object Get(GetFile request)
{
var item = _libraryManager.GetItemById(request.Id);
@ -347,7 +381,7 @@ namespace MediaBrowser.Api.Library
var dtoOptions = GetDtoOptions(request);
BaseItem parent = item.Parent;
while (parent != null)
{
if (user != null)
@ -458,23 +492,9 @@ namespace MediaBrowser.Api.Library
var auth = _authContext.GetAuthorizationInfo(Request);
var user = _userManager.GetUserById(auth.UserId);
if (item is Playlist || item is BoxSet)
if (!item.CanDelete(user))
{
// For now this is allowed if user can see the playlist
}
else if (item is ILiveTvRecording)
{
if (!user.Policy.EnableLiveTvManagement)
{
throw new UnauthorizedAccessException();
}
}
else
{
if (!user.Policy.EnableContentDeletion)
{
throw new UnauthorizedAccessException();
}
throw new UnauthorizedAccessException();
}
var task = _libraryManager.DeleteItem(item);

@ -123,7 +123,7 @@ namespace MediaBrowser.Api.Playback
private string GetOutputFilePath(StreamState state)
{
var folder = ServerConfigurationManager.ApplicationPaths.TranscodingTempPath;
var outputFileExtension = GetOutputFileExtension(state);
var data = GetCommandLineArguments("dummy\\dummy", "dummyTranscodingId", state, false);
@ -323,13 +323,13 @@ namespace MediaBrowser.Api.Playback
switch (qualitySetting)
{
case EncodingQuality.HighSpeed:
param += " -crf 23";
param += " -subq 0 -crf 23";
break;
case EncodingQuality.HighQuality:
param += " -crf 20";
param += " -subq 3 -crf 20";
break;
case EncodingQuality.MaxQuality:
param += " -crf 18";
param += " -subq 6 -crf 18";
break;
}
}
@ -507,7 +507,7 @@ namespace MediaBrowser.Api.Playback
}
}
return param;
return "-pix_fmt yuv420p " + param;
}
protected string GetAudioFilterParam(StreamState state, bool isHls)
@ -918,7 +918,7 @@ namespace MediaBrowser.Api.Playback
if (SupportsThrottleWithStream)
{
var url = "http://localhost:" + ServerConfigurationManager.Configuration.HttpServerPortNumber.ToString(UsCulture) + "/videos/" + state.Request.Id + "/stream?static=true&Throttle=true&mediaSourceId=" + state.Request.MediaSourceId;
url += "&transcodingJobId=" + transcodingJobId;
return string.Format("\"{0}\"", url);

@ -97,6 +97,7 @@ namespace MediaBrowser.Api.Playback.Progressive
if (string.Equals(Path.GetExtension(outputPath), ".mp4", StringComparison.OrdinalIgnoreCase))
{
// Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
format = " -f mp4 -movflags frag_keyframe+empty_moov";
}

@ -67,5 +67,10 @@ namespace MediaBrowser.Controller.Channels
{
return System.IO.Path.Combine(basePath, "channels", id.ToString("N"), "metadata");
}
public override bool CanDelete()
{
return false;
}
}
}

@ -3,10 +3,10 @@ using MediaBrowser.Model.Channels;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Users;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.Channels
{
@ -89,5 +89,10 @@ namespace MediaBrowser.Controller.Channels
return list;
}
public override bool CanDelete()
{
return false;
}
}
}

@ -1,11 +1,10 @@
using System;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Channels;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Users;
using System;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.Channels
{
@ -76,5 +75,10 @@ namespace MediaBrowser.Controller.Channels
{
return System.IO.Path.Combine(basePath, "channels", ChannelId, Id.ToString("N"));
}
public override bool CanDelete()
{
return false;
}
}
}

@ -4,11 +4,11 @@ using MediaBrowser.Model.Channels;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Users;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.Channels
{
@ -119,5 +119,10 @@ namespace MediaBrowser.Controller.Channels
{
return System.IO.Path.Combine(basePath, "channels", ChannelId, Id.ToString("N"));
}
public override bool CanDelete()
{
return false;
}
}
}

@ -32,6 +32,11 @@ namespace MediaBrowser.Controller.Entities
}
}
public override bool CanDelete()
{
return false;
}
/// <summary>
/// The _virtual children
/// </summary>

@ -113,6 +113,13 @@ namespace MediaBrowser.Controller.Entities.Audio
}
}
public override bool CanDownload()
{
var locationType = LocationType;
return locationType != LocationType.Remote &&
locationType != LocationType.Virtual;
}
/// <summary>
/// Gets or sets the artist.
/// </summary>

@ -1,14 +1,13 @@
using System.Runtime.Serialization;
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Users;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.Entities.Audio
{
@ -35,6 +34,11 @@ namespace MediaBrowser.Controller.Entities.Audio
get { return true; }
}
public override bool CanDelete()
{
return !IsAccessedByName;
}
protected override IEnumerable<BaseItem> ActualChildren
{
get

@ -39,6 +39,11 @@ namespace MediaBrowser.Controller.Entities.Audio
}
}
public override bool CanDelete()
{
return false;
}
/// <summary>
/// Gets a value indicating whether this instance is owned item.
/// </summary>

@ -239,6 +239,38 @@ namespace MediaBrowser.Controller.Entities
get { return this.GetImagePath(ImageType.Primary); }
}
public virtual bool CanDelete()
{
var locationType = LocationType;
return locationType != LocationType.Remote &&
locationType != LocationType.Virtual;
}
public virtual bool IsAuthorizedToDelete(User user)
{
return user.Policy.EnableContentDeletion;
}
public bool CanDelete(User user)
{
return CanDelete() && IsAuthorizedToDelete(user);
}
public virtual bool CanDownload()
{
return false;
}
public virtual bool IsAuthorizedToDownload(User user)
{
return user.Policy.EnableContentDownloading;
}
public bool CanDownload(User user)
{
return CanDownload() && IsAuthorizedToDownload(user);
}
/// <summary>
/// Gets or sets the date created.
/// </summary>

@ -11,5 +11,10 @@ namespace MediaBrowser.Controller.Entities
{
get { return null; }
}
public override bool CanDelete()
{
return false;
}
}
}

@ -2,6 +2,7 @@
using MediaBrowser.Model.Configuration;
using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.Entities
@ -37,6 +38,13 @@ namespace MediaBrowser.Controller.Entities
Tags = new List<string>();
}
public override bool CanDownload()
{
var locationType = LocationType;
return locationType != LocationType.Remote &&
locationType != LocationType.Virtual;
}
protected override bool GetBlockUnratedValue(UserPolicy config)
{
return config.BlockUnratedItems.Contains(UnratedItem.Book);

@ -35,6 +35,11 @@ namespace MediaBrowser.Controller.Entities
}
}
public override bool CanDelete()
{
return false;
}
public string CollectionType { get; set; }
/// <summary>

@ -1,10 +1,10 @@
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Users;
using System;
using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.Entities
{
@ -38,6 +38,13 @@ namespace MediaBrowser.Controller.Entities
public List<Guid> LocalTrailerIds { get; set; }
public List<Guid> RemoteTrailerIds { get; set; }
public override bool CanDownload()
{
var locationType = LocationType;
return locationType != LocationType.Remote &&
locationType != LocationType.Virtual;
}
/// <summary>
/// Gets or sets the tags.
/// </summary>

@ -43,6 +43,11 @@ namespace MediaBrowser.Controller.Entities
}
}
public override bool CanDelete()
{
return false;
}
public IEnumerable<BaseItem> GetTaggedItems(IEnumerable<BaseItem> inputItems)
{
return inputItems.Where(GetItemFilter());

@ -34,6 +34,11 @@ namespace MediaBrowser.Controller.Entities
}
}
public override bool CanDelete()
{
return false;
}
/// <summary>
/// Gets a value indicating whether this instance is owned item.
/// </summary>

@ -15,6 +15,10 @@ namespace MediaBrowser.Controller.Entities
/// <returns>IEnumerable{BaseItem}.</returns>
IEnumerable<BaseItem> GetTaggedItems(IEnumerable<BaseItem> inputItems);
/// <summary>
/// Gets the item filter.
/// </summary>
/// <returns>Func&lt;BaseItem, System.Boolean&gt;.</returns>
Func<BaseItem, bool> GetItemFilter();
}

@ -74,6 +74,11 @@ namespace MediaBrowser.Controller.Entities.Movies
}
}
public override bool IsAuthorizedToDelete(User user)
{
return true;
}
/// <summary>
/// Gets the trailer ids.
/// </summary>

@ -45,6 +45,11 @@ namespace MediaBrowser.Controller.Entities
}
}
public override bool CanDelete()
{
return false;
}
/// <summary>
/// Gets a value indicating whether this instance is owned item.
/// </summary>

@ -40,6 +40,11 @@ namespace MediaBrowser.Controller.Entities
}
}
public override bool CanDelete()
{
return false;
}
/// <summary>
/// Gets a value indicating whether this instance is owned item.
/// </summary>

@ -40,6 +40,11 @@ namespace MediaBrowser.Controller.Entities
return result.Items;
}
public override bool CanDelete()
{
return false;
}
public override IEnumerable<BaseItem> GetRecursiveChildren(User user, Func<BaseItem, bool> filter)
{
var result = GetItems(new InternalItemsQuery

@ -64,6 +64,19 @@ namespace MediaBrowser.Controller.Entities
LinkedAlternateVersions = new List<LinkedChild>();
}
public override bool CanDownload()
{
if (VideoType == VideoType.HdDvd || VideoType == VideoType.Dvd ||
VideoType == VideoType.BluRay)
{
return false;
}
var locationType = LocationType;
return locationType != LocationType.Remote &&
locationType != LocationType.Virtual;
}
[IgnoreDataMember]
public override bool SupportsAddingToPlaylist
{

@ -34,6 +34,11 @@ namespace MediaBrowser.Controller.Entities
}
}
public override bool CanDelete()
{
return false;
}
/// <summary>
/// Gets a value indicating whether this instance is owned item.
/// </summary>

@ -25,5 +25,9 @@ namespace MediaBrowser.Controller.LiveTv
Task RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken);
PlayAccess GetPlayAccess(User user);
bool CanDelete();
bool CanDelete(User user);
}
}

@ -1,4 +1,5 @@
using System.Runtime.Serialization;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
@ -93,5 +94,10 @@ namespace MediaBrowser.Controller.LiveTv
{
return System.IO.Path.Combine(basePath, "livetv", Id.ToString("N"));
}
public override bool IsAuthorizedToDelete(User user)
{
return user.Policy.EnableLiveTvManagement;
}
}
}

@ -1,5 +1,4 @@
using System.Runtime.Serialization;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
@ -8,6 +7,7 @@ using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Users;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
namespace MediaBrowser.Controller.LiveTv
{
@ -135,5 +135,10 @@ namespace MediaBrowser.Controller.LiveTv
{
return System.IO.Path.Combine(basePath, "livetv", Id.ToString("N"), "metadata");
}
public override bool CanDelete()
{
return false;
}
}
}

@ -1,13 +1,13 @@
using System.Runtime.Serialization;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Users;
using System;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.LiveTv
{
@ -215,5 +215,10 @@ namespace MediaBrowser.Controller.LiveTv
{
return System.IO.Path.Combine(basePath, "livetv", Id.ToString("N"));
}
public override bool CanDelete()
{
return false;
}
}
}

@ -92,5 +92,10 @@ namespace MediaBrowser.Controller.LiveTv
{
return System.IO.Path.Combine(basePath, "livetv", Id.ToString("N"));
}
public override bool IsAuthorizedToDelete(User user)
{
return user.Policy.EnableLiveTvManagement;
}
}
}

@ -1,7 +1,5 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using System;
@ -40,6 +38,11 @@ namespace MediaBrowser.Controller.Playlists
}
}
public override bool IsAuthorizedToDelete(User user)
{
return true;
}
public override bool IsSaveLocalMetadataEnabled()
{
return true;

@ -631,13 +631,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
switch (qualitySetting)
{
case EncodingQuality.HighSpeed:
param += " -crf 23";
param += " -subq 0 -crf 23";
break;
case EncodingQuality.HighQuality:
param += " -crf 20";
param += " -subq 3 -crf 20";
break;
case EncodingQuality.MaxQuality:
param += " -crf 18";
param += " -subq 6 -crf 18";
break;
}
}
@ -740,7 +740,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
param += " -level " + state.Options.Level.Value.ToString(UsCulture);
}
return param;
return "-pix_fmt yuv420p " + param;
}
protected string GetVideoBitrateParam(EncodingJob state, string videoCodec, bool isHls)

@ -56,6 +56,8 @@ namespace MediaBrowser.Model.Dto
public int? AirsBeforeEpisodeNumber { get; set; }
public int? AbsoluteEpisodeNumber { get; set; }
public bool? DisplaySpecialsWithSeasons { get; set; }
public bool? CanDelete { get; set; }
public bool? CanDownload { get; set; }
public string PreferredMetadataLanguage { get; set; }
public string PreferredMetadataCountryCode { get; set; }

@ -99,6 +99,12 @@ namespace MediaBrowser.Model.LiveTv
/// <value>The path.</value>
public string Path { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance can delete.
/// </summary>
/// <value><c>null</c> if [can delete] contains no value, <c>true</c> if [can delete]; otherwise, <c>false</c>.</value>
public bool? CanDelete { get; set; }
/// <summary>
/// Overview of the recording.
/// </summary>

@ -1,5 +1,4 @@

namespace MediaBrowser.Model.Querying
namespace MediaBrowser.Model.Querying
{
/// <summary>
/// Used to control the data that gets attached to DtoBaseItems
@ -26,6 +25,16 @@ namespace MediaBrowser.Model.Querying
/// </summary>
Budget,
/// <summary>
/// The can delete
/// </summary>
CanDelete,
/// <summary>
/// The can download
/// </summary>
CanDownload,
/// <summary>
/// The chapters
/// </summary>

@ -42,7 +42,8 @@ namespace MediaBrowser.Model.Users
public bool EnableMediaPlayback { get; set; }
public bool EnableContentDeletion { get; set; }
public bool EnableContentDownloading { get; set; }
/// <summary>
/// Gets or sets a value indicating whether [enable synchronize].
/// </summary>
@ -80,6 +81,8 @@ namespace MediaBrowser.Model.Users
EnabledDevices = new string[] { };
EnableAllDevices = true;
EnableContentDownloading = true;
}
}
}

@ -284,6 +284,20 @@ namespace MediaBrowser.Server.Implementations.Dto
AttachLinkedChildImages(dto, playlist, user, options);
}
if (fields.Contains(ItemFields.CanDelete))
{
dto.CanDelete = user == null
? item.CanDelete()
: item.CanDelete(user);
}
if (fields.Contains(ItemFields.CanDownload))
{
dto.CanDownload = user == null
? item.CanDownload()
: item.CanDownload(user);
}
return dto;
}

@ -74,7 +74,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
ValidateUserAccess(user, request, authAttribtues, auth);
}
if (!IsExemptFromRoles(auth, authAttribtues))
var info = (AuthenticationInfo)request.Items["OriginalAuthenticationInfo"];
if (!IsExemptFromRoles(auth, authAttribtues, info))
{
var roles = authAttribtues.GetRoles().ToList();
@ -142,7 +144,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
StringComparer.OrdinalIgnoreCase);
}
private bool IsExemptFromRoles(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues)
private bool IsExemptFromRoles(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues, AuthenticationInfo tokenInfo)
{
if (!_config.Configuration.IsStartupWizardCompleted &&
authAttribtues.AllowBeforeStartupWizard)
@ -150,6 +152,16 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
return true;
}
if (string.IsNullOrWhiteSpace(auth.Token))
{
return true;
}
if (tokenInfo != null && string.IsNullOrWhiteSpace(tokenInfo.UserId))
{
return true;
}
return false;
}
@ -175,6 +187,16 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
};
}
}
if (roles.Contains("download", StringComparer.OrdinalIgnoreCase))
{
if (user == null || !user.Policy.EnableContentDownloading)
{
throw new SecurityException("User does not have download access.")
{
SecurityExceptionType = SecurityExceptionType.Unauthenticated
};
}
}
}
private bool IsValidConnectKey(string token)

@ -229,6 +229,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv
ServerId = _appHost.SystemId
};
dto.CanDelete = user == null
? recording.CanDelete()
: recording.CanDelete(user);
dto.MediaStreams = dto.MediaSources.SelectMany(i => i.MediaStreams).ToList();
if (info.Status == RecordingStatus.InProgress)

@ -48,6 +48,7 @@
"LabelFailed": "(failed)",
"ButtonHelp": "Help",
"ButtonSave": "Save",
"ButtonDownload": "Download",
"SyncJobStatusQueued": "Queued",
"SyncJobStatusConverting": "Converting",
"SyncJobStatusFailed": "Failed",
@ -56,6 +57,7 @@
"SyncJobStatusReadyToTransfer": "Ready to Transfer",
"SyncJobStatusTransferring": "Transferring",
"SyncJobStatusCompletedWithError": "Synced with errors",
"SyncJobItemStatusReadyToTransfer": "Ready to Transfer",
"LabelCollection": "Collection",
"HeaderAddToCollection": "Add to Collection",
"NewCollectionNameExample": "Example: Star Wars Collection",

@ -285,10 +285,10 @@
"ButtonHelp": "Help",
"OptionAllowUserToManageServer": "Allow this user to manage the server",
"HeaderFeatureAccess": "Feature Access",
"OptionAllowMediaPlayback": "Allow media playback",
"OptionAllowBrowsingLiveTv": "Allow browsing of live tv",
"OptionAllowDeleteLibraryContent": "Allow deletion of library content",
"OptionAllowManageLiveTv": "Allow management of live tv recordings",
"OptionAllowMediaPlayback": "Media playback",
"OptionAllowBrowsingLiveTv": "Live TV",
"OptionAllowDeleteLibraryContent": "Media deletion",
"OptionAllowManageLiveTv": "Live TV recording management",
"OptionAllowRemoteControlOthers": "Allow remote control of other users",
"OptionAllowRemoteSharedDevices": "Allow remote control of shared devices",
"OptionAllowRemoteSharedDevicesHelp": "Dlna devices are considered shared until a user begins controlling it.",
@ -1133,7 +1133,7 @@
"ViewTypeLiveTvRecordingGroups": "Recordings",
"ViewTypeLiveTvChannels": "Channels",
"LabelEasyPinCode": "Easy pin code:",
"EasyPasswordHelp": "Your easy pin code is used for offline access with supported Media Browser apps, and can also be used for easy in-network sign in.",
"EasyPasswordHelp": "Your easy pin code is used for offline access with supported Media Browser apps, and can also be used for easy in-network sign in.",
"LabelInNetworkSignInWithEasyPassword": "Enable in-network sign in with my easy pin code",
"LabelInNetworkSignInWithEasyPasswordHelp": "If enabled, you'll be able to use your easy pin code to sign in to Media Browser apps from inside your home network. Your regular password will only be needed away from home. If the pin code is left blank, you won't need a password within your home network.",
"HeaderPassword": "Password",
@ -1362,7 +1362,8 @@
"LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within Didl.",
"TabActivity": "Activity",
"TitleSync": "Sync",
"OptionAllowSyncContent": "Allow syncing media to devices",
"OptionAllowSyncContent": "Sync",
"OptionAllowContentDownloading": "Media downloading",
"NameSeasonUnknown": "Season Unknown",
"NameSeasonNumber": "Season {0}",
"LabelNewUserNameHelp": "Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)",

@ -311,8 +311,10 @@ namespace MediaBrowser.Server.Implementations.Sync
var itemByName = item as IItemByName;
if (itemByName != null)
{
var itemByNameFilter = itemByName.GetItemFilter();
return user.RootFolder
.GetRecursiveChildren(user, itemByName.GetItemFilter());
.GetRecursiveChildren(user, i => !i.IsFolder && itemByNameFilter(i));
}
if (item.IsFolder)

@ -414,7 +414,6 @@ namespace MediaBrowser.WebDashboard.Api
"indexpage.js",
"itembynamedetailpage.js",
"itemdetailpage.js",
"itemgallery.js",
"itemlistpage.js",
"librarypathmapping.js",
"reports.js",

@ -1656,9 +1656,6 @@
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="dashboard-ui\itemgallery.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\librarysettings.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -1707,9 +1704,6 @@
<Content Include="dashboard-ui\scripts\edititemmetadata.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\itemgallery.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\librarysettings.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

Loading…
Cancel
Save