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 : Window
{
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
}