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 { /// /// Interaction logic for LibraryExplorer.xaml /// public partial class LibraryExplorer : MetroWindow { private readonly ILogger _logger; private readonly IJsonSerializer _jsonSerializer; private readonly ILibraryManager _libraryManager; private readonly IDisplayPreferencesManager _displayPreferencesManager; /// /// The current user /// private User CurrentUser; /// /// Initializes a new instance of the class. /// /// The json serializer. /// The logger. /// The app host. /// The user manager. /// The library manager. /// The display preferences manager. 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; } /// /// Handles the Click event of the btnLoad control. /// /// The source of the event. /// The instance containing the event data. private void btnLoad_Click(object sender, RoutedEventArgs e) { } /// /// Loads the tree. /// /// Task. 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 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; } /// /// Orders the name of the by. /// /// The items. /// The user. /// IEnumerable{BaseItem}. private IEnumerable OrderByName(IEnumerable items, User user) { return OrderBy(items, user, ItemSortBy.SortName); } /// /// Orders the name of the by. /// /// The items. /// The user. /// IEnumerable{BaseItem}. private IEnumerable OrderBy(IEnumerable items, User user, string order) { return _libraryManager.Sort(items, user, new[] { order }, SortOrder.Ascending); } /// /// Adds the children. /// /// The parent. /// The children. /// The user. private void AddChildren(TreeViewItem parent, IEnumerable 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); } } /// /// TVWs the library_ selected item changed. /// /// The sender. /// The e. private async void tvwLibrary_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs 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(); 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(); } } /// /// The INDEN t_ STRING /// private const string INDENT_STRING = " "; /// /// Formats the json. /// /// The STR. /// System.String. 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(); } /// /// Handles the SelectionChanged event of the ddlProfile control. /// /// The source of the event. /// The instance containing the event data. private void ddlProfile_SelectionChanged(object sender, SelectionChangedEventArgs e) { CurrentUser = ddlProfile.SelectedItem as User; if (CurrentUser != null) LoadTree().ConfigureAwait(false); } /// /// Handles the Click event of the btnRefresh control. /// /// The source of the event. /// The instance containing the event data. 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); } } } /// /// Handles the SelectionChanged event of the ddlIndexBy control. /// /// The source of the event. /// The instance containing the event data. 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); } } } /// /// Handles the SelectionChanged event of the ddlSortBy control. /// /// The source of the event. /// The instance containing the event data. 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); } } } } /// /// Class PreviewItem /// public class PreviewItem { /// /// The preview /// private readonly string preview; /// /// The name /// private readonly string name; /// /// Gets the preview. /// /// The preview. public string Preview { get { return preview; } } /// /// Gets the name. /// /// The name. public string Name { get { return name; } } /// /// Initializes a new instance of the class. /// /// The p. /// The n. public PreviewItem(string p, string n) { preview = p; name = n; } } /// /// Class Extensions /// static class Extensions { /// /// Fors the each. /// /// /// The ie. /// The action. public static void ForEach(this IEnumerable ie, Action action) { foreach (var i in ie) { action(i); } } } #region ItemToImageConverter /// /// Class ItemToImageConverter /// [ValueConversion(typeof(string), typeof(bool))] public class ItemToImageConverter : IValueConverter { /// /// The instance /// public static ItemToImageConverter Instance = new ItemToImageConverter(); /// /// Converts a value. /// /// The value produced by the binding source. /// The type of the binding target property. /// The converter parameter to use. /// The culture to use in the converter. /// A converted value. If the method returns null, the valid null value is used. 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; } } } /// /// Converts a value. /// /// The value that is produced by the binding target. /// The type to convert to. /// The converter parameter to use. /// The culture to use in the converter. /// A converted value. If the method returns null, the valid null value is used. /// Cannot convert back public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotSupportedException("Cannot convert back"); } } #endregion // ItemToImageConverter }