You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
jellyfin/MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs

648 lines
29 KiB

using MahApps.Metro.Controls;
using MediaBrowser.Common;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Localization;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Serialization;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media.Imaging;
namespace MediaBrowser.ServerApplication
{
/// <summary>
/// Interaction logic for LibraryExplorer.xaml
/// </summary>
public partial class LibraryExplorer : MetroWindow
{
private readonly ILogger _logger;
private readonly IJsonSerializer _jsonSerializer;
private readonly ILibraryManager _libraryManager;
private readonly IDisplayPreferencesManager _displayPreferencesManager;
/// <summary>
/// The current user
/// </summary>
private User CurrentUser;
/// <summary>
/// Initializes a new instance of the <see cref="LibraryExplorer" /> class.
/// </summary>
/// <param name="jsonSerializer">The json serializer.</param>
/// <param name="logger">The logger.</param>
/// <param name="appHost">The app host.</param>
/// <param name="userManager">The user manager.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="displayPreferencesManager">The display preferences manager.</param>
public LibraryExplorer(IJsonSerializer jsonSerializer, ILogger logger, IApplicationHost appHost, IUserManager userManager, ILibraryManager libraryManager, IDisplayPreferencesManager displayPreferencesManager)
{
_logger = logger;
_jsonSerializer = jsonSerializer;
_libraryManager = libraryManager;
_displayPreferencesManager = displayPreferencesManager;
InitializeComponent();
lblVersion.Content = "Version: " + appHost.ApplicationVersion;
foreach (var user in userManager.Users)
ddlProfile.Items.Add(user);
ddlProfile.Items.Insert(0,new User {Name = "Physical"});
ddlProfile.SelectedIndex = 0;
ddlIndexBy.Visibility = ddlSortBy.Visibility = lblIndexBy.Visibility = lblSortBy.Visibility = Visibility.Hidden;
}
/// <summary>
/// Handles the Click event of the btnLoad control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="RoutedEventArgs" /> instance containing the event data.</param>
private void btnLoad_Click(object sender, RoutedEventArgs e)
{
}
/// <summary>
/// Loads the tree.
/// </summary>
/// <returns>Task.</returns>
private async Task LoadTree()
{
tvwLibrary.Items.Clear();
lblLoading.Visibility = Visibility.Visible;
//grab UI context so we can update within the below task
var ui = TaskScheduler.FromCurrentSynchronizationContext();
//this whole async thing doesn't really work in this instance since all my work pretty much needs to be on the UI thread...
Cursor = Cursors.Wait;
await Task.Run(() =>
{
IEnumerable<BaseItem> children = CurrentUser.Name == "Physical" ? _libraryManager.RootFolder.Children : _libraryManager.RootFolder.GetChildren(CurrentUser);
children = OrderByName(children, CurrentUser);
foreach (Folder folder in children)
{
var currentFolder = folder;
Task.Factory.StartNew(() =>
{
var prefs = ddlProfile.SelectedItem != null ? _displayPreferencesManager.GetDisplayPreferences(currentFolder.GetDisplayPreferencesId((ddlProfile.SelectedItem as User).Id)).Result ?? new DisplayPreferences { SortBy = ItemSortBy.SortName } : new DisplayPreferences { SortBy = ItemSortBy.SortName };
var node = new TreeViewItem { Tag = currentFolder };
var subChildren = currentFolder.GetChildren(CurrentUser, prefs.IndexBy);
subChildren = OrderByName(subChildren, CurrentUser);
AddChildren(node, subChildren, CurrentUser);
node.Header = currentFolder.Name + " (" +
node.Items.Count + ")";
tvwLibrary.Items.Add(node);
}, CancellationToken.None, TaskCreationOptions.None, ui);
}
});
lblLoading.Visibility = Visibility.Hidden;
Cursor = Cursors.Arrow;
}
/// <summary>
/// Orders the name of the by.
/// </summary>
/// <param name="items">The items.</param>
/// <param name="user">The user.</param>
/// <returns>IEnumerable{BaseItem}.</returns>
private IEnumerable<BaseItem> OrderByName(IEnumerable<BaseItem> items, User user)
{
return OrderBy(items, user, ItemSortBy.SortName);
}
/// <summary>
/// Orders the name of the by.
/// </summary>
/// <param name="items">The items.</param>
/// <param name="user">The user.</param>
/// <returns>IEnumerable{BaseItem}.</returns>
private IEnumerable<BaseItem> OrderBy(IEnumerable<BaseItem> items, User user, string order)
{
return _libraryManager.Sort(items, user, new[] { order }, SortOrder.Ascending);
}
/// <summary>
/// Adds the children.
/// </summary>
/// <param name="parent">The parent.</param>
/// <param name="children">The children.</param>
/// <param name="user">The user.</param>
private void AddChildren(TreeViewItem parent, IEnumerable<BaseItem> children, User user)
{
foreach (var item in children)
{
var node = new TreeViewItem { Tag = item };
var subFolder = item as Folder;
if (subFolder != null)
{
var prefs = _displayPreferencesManager.GetDisplayPreferences(subFolder.GetDisplayPreferencesId(user.Id)).Result;
AddChildren(node, OrderBy(subFolder.GetChildren(user), user, prefs.SortBy), user);
node.Header = item.Name + " (" + node.Items.Count + ")";
}
else
{
node.Header = item.Name;
}
parent.Items.Add(node);
}
}
/// <summary>
/// TVWs the library_ selected item changed.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The e.</param>
private async void tvwLibrary_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (tvwLibrary.SelectedItem != null)
{
var item = (BaseItem)(tvwLibrary.SelectedItem as TreeViewItem).Tag;
lblObjType.Content = "Type: " + item.GetType().Name;
var trailers = item.LocalTrailers != null && item.LocalTrailers.Count > 0
? "\nTrailers: " +
String.Join(Environment.NewLine, item.LocalTrailers.Select(t => t.Path))
: "";
var movie = item as Movie;
var features = movie != null && movie.SpecialFeatures != null
? "\nSpecial Features: " +
string.Join(Environment.NewLine,
movie.SpecialFeatures.Select(f => f.Path))
: "";
var folder = item as Folder;
if (folder != null)
{
lblIndexBy.Visibility = ddlIndexBy.Visibility = ddlSortBy.Visibility = lblSortBy.Visibility = Visibility.Visible;
ddlIndexBy.ItemsSource = folder.IndexByOptionStrings;
ddlSortBy.ItemsSource = new []
{
ItemSortBy.SortName,
ItemSortBy.Album,
ItemSortBy.AlbumArtist,
ItemSortBy.Artist,
ItemSortBy.CommunityRating,
ItemSortBy.DateCreated,
ItemSortBy.DatePlayed,
ItemSortBy.PremiereDate,
ItemSortBy.ProductionYear,
ItemSortBy.Random,
ItemSortBy.Runtime
};
var prefs =
await
_displayPreferencesManager.GetDisplayPreferences(folder.GetDisplayPreferencesId((ddlProfile.SelectedItem as User).Id));
ddlIndexBy.SelectedItem = prefs != null
? prefs.IndexBy ?? LocalizedStrings.Instance.GetString("NoneDispPref")
: LocalizedStrings.Instance.GetString("NoneDispPref");
ddlSortBy.SelectedItem = prefs != null
? prefs.SortBy ?? ItemSortBy.SortName
: ItemSortBy.SortName;
}
else
{
lblIndexBy.Visibility = ddlIndexBy.Visibility = ddlSortBy.Visibility = lblSortBy.Visibility = Visibility.Hidden;
}
txtData.Text = FormatJson(_jsonSerializer.SerializeToString(item)) + trailers + features;
var previews = new List<PreviewItem>();
await Task.Run(() =>
{
if (!string.IsNullOrEmpty(item.PrimaryImagePath))
{
previews.Add(new PreviewItem(item.PrimaryImagePath, "Primary"));
}
if (item.HasImage(ImageType.Banner))
{
previews.Add(new PreviewItem(item.GetImage(ImageType.Banner), "Banner"));
}
if (item.HasImage(ImageType.Logo))
{
previews.Add(new PreviewItem(item.GetImage(ImageType.Logo), "Logo"));
}
if (item.HasImage(ImageType.Art))
{
previews.Add(new PreviewItem(item.GetImage(ImageType.Art), "Art"));
}
if (item.HasImage(ImageType.Thumb))
{
previews.Add(new PreviewItem(item.GetImage(ImageType.Thumb), "Thumb"));
}
if (item.BackdropImagePaths != null)
previews.AddRange(
item.BackdropImagePaths.Select(
image => new PreviewItem(image, "Backdrop")));
});
lstPreviews.ItemsSource = previews;
lstPreviews.Items.Refresh();
}
}
/// <summary>
/// The INDEN t_ STRING
/// </summary>
private const string INDENT_STRING = " ";
/// <summary>
/// Formats the json.
/// </summary>
/// <param name="str">The STR.</param>
/// <returns>System.String.</returns>
private static string FormatJson(string str)
{
var indent = 0;
var quoted = false;
var sb = new StringBuilder();
for (var i = 0; i < str.Length; i++)
{
var ch = str[i];
switch (ch)
{
case '{':
case '[':
sb.Append(ch);
if (!quoted)
{
sb.AppendLine();
Enumerable.Range(0, ++indent).ForEach(item => sb.Append(INDENT_STRING));
}
break;
case '}':
case ']':
if (!quoted)
{
sb.AppendLine();
Enumerable.Range(0, --indent).ForEach(item => sb.Append(INDENT_STRING));
}
sb.Append(ch);
break;
case '"':
sb.Append(ch);
bool escaped = false;
var index = i;
while (index > 0 && str[--index] == '\\')
escaped = !escaped;
if (!escaped)
quoted = !quoted;
break;
case ',':
sb.Append(ch);
if (!quoted)
{
sb.AppendLine();
Enumerable.Range(0, indent).ForEach(item => sb.Append(INDENT_STRING));
}
break;
case ':':
sb.Append(ch);
if (!quoted)
sb.Append(" ");
break;
default:
sb.Append(ch);
break;
}
}
return sb.ToString();
}
/// <summary>
/// Handles the SelectionChanged event of the ddlProfile control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="SelectionChangedEventArgs" /> instance containing the event data.</param>
private void ddlProfile_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
CurrentUser = ddlProfile.SelectedItem as User;
if (CurrentUser != null)
LoadTree().ConfigureAwait(false);
}
/// <summary>
/// Handles the Click event of the btnRefresh control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="RoutedEventArgs" /> instance containing the event data.</param>
private void btnRefresh_Click(object sender, RoutedEventArgs e)
{
if (tvwLibrary.SelectedItem != null)
{
var item = ((TreeViewItem)tvwLibrary.SelectedItem).Tag as BaseItem;
if (item != null)
{
item.RefreshMetadata(CancellationToken.None, forceRefresh: cbxForce.IsChecked.Value);
tvwLibrary_SelectedItemChanged(this, null);
}
}
}
/// <summary>
/// Handles the SelectionChanged event of the ddlIndexBy control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="SelectionChangedEventArgs" /> instance containing the event data.</param>
private async void ddlIndexBy_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ddlIndexBy.SelectedItem != null)
{
var treeItem = tvwLibrary.SelectedItem as TreeViewItem;
var folder = treeItem != null
? treeItem.Tag as Folder
: null;
var prefs = folder != null ? _displayPreferencesManager.GetDisplayPreferences(folder.GetDisplayPreferencesId(CurrentUser.Id)).Result : new DisplayPreferences { SortBy = ItemSortBy.SortName };
if (folder != null && prefs.IndexBy != ddlIndexBy.SelectedItem as string)
{
//grab UI context so we can update within the below task
var ui = TaskScheduler.FromCurrentSynchronizationContext();
Cursor = Cursors.Wait;
await Task.Factory.StartNew(() =>
{
using (
new Profiler("Explorer full index expansion for " +
folder.Name, _logger))
{
//re-build the current item's children as an index
prefs.IndexBy = ddlIndexBy.SelectedItem as string;
treeItem.Items.Clear();
AddChildren(treeItem, OrderBy(folder.GetChildren(CurrentUser, prefs.IndexBy), CurrentUser, prefs.SortBy), CurrentUser);
treeItem.Header = folder.Name + "(" +
treeItem.Items.Count + ")";
Cursor = Cursors.Arrow;
}
}, CancellationToken.None, TaskCreationOptions.None,
ui);
}
}
}
/// <summary>
/// Handles the SelectionChanged event of the ddlSortBy control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="SelectionChangedEventArgs" /> instance containing the event data.</param>
private async void ddlSortBy_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ddlSortBy.SelectedItem != null)
{
var treeItem = tvwLibrary.SelectedItem as TreeViewItem;
var folder = treeItem != null
? treeItem.Tag as Folder
: null;
var prefs = folder != null ? _displayPreferencesManager.GetDisplayPreferences(folder.GetDisplayPreferencesId(CurrentUser.Id)).Result : new DisplayPreferences();
if (folder != null && prefs.SortBy != ddlSortBy.SelectedItem as string)
{
//grab UI context so we can update within the below task
var ui = TaskScheduler.FromCurrentSynchronizationContext();
Cursor = Cursors.Wait;
await Task.Factory.StartNew(() =>
{
using (
new Profiler("Explorer sorting by " + ddlSortBy.SelectedItem + " for " +
folder.Name, _logger))
{
//re-sort
prefs.SortBy = ddlSortBy.SelectedItem as string;
treeItem.Items.Clear();
AddChildren(treeItem, OrderBy(folder.GetChildren(CurrentUser, prefs.IndexBy), CurrentUser, prefs.SortBy ?? ItemSortBy.SortName), CurrentUser);
treeItem.Header = folder.Name + "(" +
treeItem.Items.Count + ")";
Cursor = Cursors.Arrow;
}
}, CancellationToken.None, TaskCreationOptions.None,
ui);
}
}
}
}
/// <summary>
/// Class PreviewItem
/// </summary>
public class PreviewItem
{
/// <summary>
/// The preview
/// </summary>
private readonly string preview;
/// <summary>
/// The name
/// </summary>
private readonly string name;
/// <summary>
/// Gets the preview.
/// </summary>
/// <value>The preview.</value>
public string Preview
{
get { return preview; }
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return name; }
}
/// <summary>
/// Initializes a new instance of the <see cref="PreviewItem" /> class.
/// </summary>
/// <param name="p">The p.</param>
/// <param name="n">The n.</param>
public PreviewItem(string p, string n)
{
preview = p;
name = n;
}
}
/// <summary>
/// Class Extensions
/// </summary>
static class Extensions
{
/// <summary>
/// Fors the each.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="ie">The ie.</param>
/// <param name="action">The action.</param>
public static void ForEach<T>(this IEnumerable<T> ie, Action<T> action)
{
foreach (var i in ie)
{
action(i);
}
}
}
#region ItemToImageConverter
/// <summary>
/// Class ItemToImageConverter
/// </summary>
[ValueConversion(typeof(string), typeof(bool))]
public class ItemToImageConverter : IValueConverter
{
/// <summary>
/// The instance
/// </summary>
public static ItemToImageConverter Instance =
new ItemToImageConverter();
/// <summary>
/// Converts a value.
/// </summary>
/// <param name="value">The value produced by the binding source.</param>
/// <param name="targetType">The type of the binding target property.</param>
/// <param name="parameter">The converter parameter to use.</param>
/// <param name="culture">The culture to use in the converter.</param>
/// <returns>A converted value. If the method returns null, the valid null value is used.</returns>
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
var item = value as BaseItem ?? new Folder();
switch (item.DisplayMediaType)
{
case "DVD":
case "HD DVD":
case "Blu-ray":
case "Blu-Ray":
case "Movie":
{
var uri = new Uri
("pack://application:,,,/Resources/Images/movie.png");
var source = new BitmapImage(uri);
return source;
}
case "Series":
{
var uri = new Uri
("pack://application:,,,/Resources/Images/series.png");
var source = new BitmapImage(uri);
return source;
}
case "Season":
{
var uri = new Uri
("pack://application:,,,/Resources/Images/season.png");
var source = new BitmapImage(uri);
return source;
}
case "Episode":
{
var uri = new Uri
("pack://application:,,,/Resources/Images/episode.png");
var source = new BitmapImage(uri);
return source;
}
case "BoxSet":
{
var uri = new Uri
("pack://application:,,,/Resources/Images/boxset.png");
var source = new BitmapImage(uri);
return source;
}
case "Audio":
{
var uri = new Uri
("pack://application:,,,/Resources/Images/audio.png");
var source = new BitmapImage(uri);
return source;
}
case "Person":
{
var uri = new Uri
("pack://application:,,,/Resources/Images/persons.png");
var source = new BitmapImage(uri);
return source;
}
case "MusicArtist":
{
var uri = new Uri
("pack://application:,,,/Resources/Images/artist.png");
var source = new BitmapImage(uri);
return source;
}
case "MusicAlbum":
{
var uri = new Uri
("pack://application:,,,/Resources/Images/album.png");
var source = new BitmapImage(uri);
return source;
}
case "Trailer":
{
var uri = new Uri
("pack://application:,,,/Resources/Images/trailer.png");
var source = new BitmapImage(uri);
return source;
}
case "None":
{
Uri uri;
if (item is Movie)
uri = new Uri("pack://application:,,,/Resources/Images/movie.png");
else if (item is Series)
uri = new Uri("pack://application:,,,/Resources/Images/series.png");
else if (item is BoxSet)
uri = new Uri("pack://application:,,,/Resources/Images/boxset.png");
else
uri = new Uri("pack://application:,,,/Resources/Images/folder.png");
return new BitmapImage(uri);
}
default:
{
var uri = new Uri("pack://application:,,,/Resources/Images/folder.png");
var source = new BitmapImage(uri);
return source;
}
}
}
/// <summary>
/// Converts a value.
/// </summary>
/// <param name="value">The value that is produced by the binding target.</param>
/// <param name="targetType">The type to convert to.</param>
/// <param name="parameter">The converter parameter to use.</param>
/// <param name="culture">The culture to use in the converter.</param>
/// <returns>A converted value. If the method returns null, the valid null value is used.</returns>
/// <exception cref="System.NotSupportedException">Cannot convert back</exception>
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotSupportedException("Cannot convert back");
}
}
#endregion // ItemToImageConverter
}