LibraryExplorer.xaml.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  1. using MediaBrowser.Common;
  2. using MediaBrowser.Controller.Entities;
  3. using MediaBrowser.Controller.Entities.Movies;
  4. using MediaBrowser.Controller.Entities.TV;
  5. using MediaBrowser.Controller.Library;
  6. using MediaBrowser.Controller.Localization;
  7. using MediaBrowser.Model.Entities;
  8. using MediaBrowser.Model.Logging;
  9. using MediaBrowser.Model.Serialization;
  10. using System;
  11. using System.Collections.Generic;
  12. using System.Globalization;
  13. using System.Linq;
  14. using System.Text;
  15. using System.Threading;
  16. using System.Threading.Tasks;
  17. using System.Windows;
  18. using System.Windows.Controls;
  19. using System.Windows.Data;
  20. using System.Windows.Input;
  21. using System.Windows.Media.Imaging;
  22. namespace MediaBrowser.ServerApplication
  23. {
  24. /// <summary>
  25. /// Interaction logic for LibraryExplorer.xaml
  26. /// </summary>
  27. public partial class LibraryExplorer : Window
  28. {
  29. private readonly ILogger _logger;
  30. private readonly IJsonSerializer _jsonSerializer;
  31. private readonly ILibraryManager _libraryManager;
  32. /// <summary>
  33. /// The current user
  34. /// </summary>
  35. private User CurrentUser;
  36. /// <summary>
  37. /// Initializes a new instance of the <see cref="LibraryExplorer" /> class.
  38. /// </summary>
  39. public LibraryExplorer(IJsonSerializer jsonSerializer, ILogger logger, IApplicationHost appHost, IUserManager userManager, ILibraryManager libraryManager)
  40. {
  41. _logger = logger;
  42. _jsonSerializer = jsonSerializer;
  43. _libraryManager = libraryManager;
  44. InitializeComponent();
  45. lblVersion.Content = "Version: " + appHost.ApplicationVersion;
  46. foreach (var user in userManager.Users)
  47. ddlProfile.Items.Add(user);
  48. ddlProfile.Items.Insert(0,new User {Name = "Physical"});
  49. ddlProfile.SelectedIndex = 0;
  50. ddlIndexBy.Visibility = ddlSortBy.Visibility = lblIndexBy.Visibility = lblSortBy.Visibility = Visibility.Hidden;
  51. }
  52. /// <summary>
  53. /// Handles the Click event of the btnLoad control.
  54. /// </summary>
  55. /// <param name="sender">The source of the event.</param>
  56. /// <param name="e">The <see cref="RoutedEventArgs" /> instance containing the event data.</param>
  57. private void btnLoad_Click(object sender, RoutedEventArgs e)
  58. {
  59. }
  60. /// <summary>
  61. /// Loads the tree.
  62. /// </summary>
  63. /// <returns>Task.</returns>
  64. private async Task LoadTree()
  65. {
  66. tvwLibrary.Items.Clear();
  67. lblLoading.Visibility = Visibility.Visible;
  68. //grab UI context so we can update within the below task
  69. var ui = TaskScheduler.FromCurrentSynchronizationContext();
  70. //this whole async thing doesn't really work in this instance since all my work pretty much needs to be on the UI thread...
  71. Cursor = Cursors.Wait;
  72. await Task.Run(() =>
  73. {
  74. IEnumerable<BaseItem> children = CurrentUser.Name == "Physical" ? _libraryManager.RootFolder.Children.OrderBy(i => i.SortName) : _libraryManager.RootFolder.GetChildren(CurrentUser, sortBy: LocalizedStrings.Instance.GetString("NameDispPref"));
  75. foreach (Folder folder in children)
  76. {
  77. var currentFolder = folder;
  78. Task.Factory.StartNew(() =>
  79. {
  80. var prefs = ddlProfile.SelectedItem != null ? currentFolder.GetDisplayPrefs(ddlProfile.SelectedItem as User, false) ?? new DisplayPreferences {SortBy = LocalizedStrings.Instance.GetString("NameDispPref")} : new DisplayPreferences {SortBy = LocalizedStrings.Instance.GetString("NameDispPref")};
  81. var node = new TreeViewItem { Tag = currentFolder };
  82. AddChildren(node, currentFolder.GetChildren(CurrentUser, prefs.IndexBy, prefs.SortBy ?? LocalizedStrings.Instance.GetString("NameDispPref")), CurrentUser);
  83. node.Header = currentFolder.Name + " (" +
  84. node.Items.Count + ")";
  85. tvwLibrary.Items.Add(node);
  86. }, CancellationToken.None, TaskCreationOptions.None, ui);
  87. }
  88. });
  89. lblLoading.Visibility = Visibility.Hidden;
  90. Cursor = Cursors.Arrow;
  91. }
  92. /// <summary>
  93. /// Adds the children.
  94. /// </summary>
  95. /// <param name="parent">The parent.</param>
  96. /// <param name="children">The children.</param>
  97. /// <param name="user">The user.</param>
  98. private void AddChildren(TreeViewItem parent, IEnumerable<BaseItem> children, User user)
  99. {
  100. foreach (var item in children)
  101. {
  102. var node = new TreeViewItem { Tag = item };
  103. var subFolder = item as Folder;
  104. if (subFolder != null)
  105. {
  106. var prefs = subFolder.GetDisplayPrefs(user, false) ?? new DisplayPreferences {SortBy = LocalizedStrings.Instance.GetString("NameDispPref")};
  107. AddChildren(node, subFolder.GetChildren(user, sortBy: prefs.SortBy), user);
  108. node.Header = item.Name + " (" + node.Items.Count + ")";
  109. }
  110. else
  111. {
  112. node.Header = item.Name;
  113. }
  114. parent.Items.Add(node);
  115. }
  116. }
  117. /// <summary>
  118. /// TVWs the library_ selected item changed.
  119. /// </summary>
  120. /// <param name="sender">The sender.</param>
  121. /// <param name="e">The e.</param>
  122. private async void tvwLibrary_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
  123. {
  124. if (tvwLibrary.SelectedItem != null)
  125. {
  126. var item = (BaseItem)(tvwLibrary.SelectedItem as TreeViewItem).Tag;
  127. lblObjType.Content = "Type: " + item.GetType().Name;
  128. var trailers = item.LocalTrailers != null && item.LocalTrailers.Count > 0
  129. ? "\nTrailers: " +
  130. String.Join(Environment.NewLine, item.LocalTrailers.Select(t => t.Path))
  131. : "";
  132. var movie = item as Movie;
  133. var features = movie != null && movie.SpecialFeatures != null
  134. ? "\nSpecial Features: " +
  135. string.Join(Environment.NewLine,
  136. movie.SpecialFeatures.Select(f => f.Path))
  137. : "";
  138. var folder = item as Folder;
  139. if (folder != null)
  140. {
  141. lblIndexBy.Visibility = ddlIndexBy.Visibility = ddlSortBy.Visibility = lblSortBy.Visibility = Visibility.Visible;
  142. ddlIndexBy.ItemsSource = folder.IndexByOptionStrings;
  143. ddlSortBy.ItemsSource = folder.SortByOptionStrings;
  144. var prefs = folder.GetDisplayPrefs(ddlProfile.SelectedItem as User, false);
  145. ddlIndexBy.SelectedItem = prefs != null
  146. ? prefs.IndexBy ?? LocalizedStrings.Instance.GetString("NoneDispPref")
  147. : LocalizedStrings.Instance.GetString("NoneDispPref");
  148. ddlSortBy.SelectedItem = prefs != null
  149. ? prefs.SortBy ?? LocalizedStrings.Instance.GetString("NameDispPref")
  150. : LocalizedStrings.Instance.GetString("NameDispPref");
  151. }
  152. else
  153. {
  154. lblIndexBy.Visibility = ddlIndexBy.Visibility = ddlSortBy.Visibility = lblSortBy.Visibility = Visibility.Hidden;
  155. }
  156. txtData.Text = FormatJson(_jsonSerializer.SerializeToString(item)) + trailers + features;
  157. var previews = new List<PreviewItem>();
  158. await Task.Run(() =>
  159. {
  160. if (!string.IsNullOrEmpty(item.PrimaryImagePath))
  161. {
  162. previews.Add(new PreviewItem(item.PrimaryImagePath, "Primary"));
  163. }
  164. if (item.HasImage(ImageType.Banner))
  165. {
  166. previews.Add(new PreviewItem(item.GetImage(ImageType.Banner), "Banner"));
  167. }
  168. if (item.HasImage(ImageType.Logo))
  169. {
  170. previews.Add(new PreviewItem(item.GetImage(ImageType.Logo), "Logo"));
  171. }
  172. if (item.HasImage(ImageType.Art))
  173. {
  174. previews.Add(new PreviewItem(item.GetImage(ImageType.Art), "Art"));
  175. }
  176. if (item.HasImage(ImageType.Thumb))
  177. {
  178. previews.Add(new PreviewItem(item.GetImage(ImageType.Thumb), "Thumb"));
  179. }
  180. if (item.BackdropImagePaths != null)
  181. previews.AddRange(
  182. item.BackdropImagePaths.Select(
  183. image => new PreviewItem(image, "Backdrop")));
  184. });
  185. lstPreviews.ItemsSource = previews;
  186. lstPreviews.Items.Refresh();
  187. }
  188. }
  189. /// <summary>
  190. /// The INDEN t_ STRING
  191. /// </summary>
  192. private const string INDENT_STRING = " ";
  193. /// <summary>
  194. /// Formats the json.
  195. /// </summary>
  196. /// <param name="str">The STR.</param>
  197. /// <returns>System.String.</returns>
  198. private static string FormatJson(string str)
  199. {
  200. var indent = 0;
  201. var quoted = false;
  202. var sb = new StringBuilder();
  203. for (var i = 0; i < str.Length; i++)
  204. {
  205. var ch = str[i];
  206. switch (ch)
  207. {
  208. case '{':
  209. case '[':
  210. sb.Append(ch);
  211. if (!quoted)
  212. {
  213. sb.AppendLine();
  214. Enumerable.Range(0, ++indent).ForEach(item => sb.Append(INDENT_STRING));
  215. }
  216. break;
  217. case '}':
  218. case ']':
  219. if (!quoted)
  220. {
  221. sb.AppendLine();
  222. Enumerable.Range(0, --indent).ForEach(item => sb.Append(INDENT_STRING));
  223. }
  224. sb.Append(ch);
  225. break;
  226. case '"':
  227. sb.Append(ch);
  228. bool escaped = false;
  229. var index = i;
  230. while (index > 0 && str[--index] == '\\')
  231. escaped = !escaped;
  232. if (!escaped)
  233. quoted = !quoted;
  234. break;
  235. case ',':
  236. sb.Append(ch);
  237. if (!quoted)
  238. {
  239. sb.AppendLine();
  240. Enumerable.Range(0, indent).ForEach(item => sb.Append(INDENT_STRING));
  241. }
  242. break;
  243. case ':':
  244. sb.Append(ch);
  245. if (!quoted)
  246. sb.Append(" ");
  247. break;
  248. default:
  249. sb.Append(ch);
  250. break;
  251. }
  252. }
  253. return sb.ToString();
  254. }
  255. /// <summary>
  256. /// Handles the SelectionChanged event of the ddlProfile control.
  257. /// </summary>
  258. /// <param name="sender">The source of the event.</param>
  259. /// <param name="e">The <see cref="SelectionChangedEventArgs" /> instance containing the event data.</param>
  260. private void ddlProfile_SelectionChanged(object sender, SelectionChangedEventArgs e)
  261. {
  262. CurrentUser = ddlProfile.SelectedItem as User;
  263. if (CurrentUser != null)
  264. LoadTree().ConfigureAwait(false);
  265. }
  266. /// <summary>
  267. /// Handles the Click event of the btnRefresh control.
  268. /// </summary>
  269. /// <param name="sender">The source of the event.</param>
  270. /// <param name="e">The <see cref="RoutedEventArgs" /> instance containing the event data.</param>
  271. private void btnRefresh_Click(object sender, RoutedEventArgs e)
  272. {
  273. if (tvwLibrary.SelectedItem != null)
  274. {
  275. var item = ((TreeViewItem)tvwLibrary.SelectedItem).Tag as BaseItem;
  276. if (item != null)
  277. {
  278. item.RefreshMetadata(CancellationToken.None, forceRefresh: cbxForce.IsChecked.Value);
  279. tvwLibrary_SelectedItemChanged(this, null);
  280. }
  281. }
  282. }
  283. /// <summary>
  284. /// Handles the SelectionChanged event of the ddlIndexBy control.
  285. /// </summary>
  286. /// <param name="sender">The source of the event.</param>
  287. /// <param name="e">The <see cref="SelectionChangedEventArgs" /> instance containing the event data.</param>
  288. private async void ddlIndexBy_SelectionChanged(object sender, SelectionChangedEventArgs e)
  289. {
  290. if (ddlIndexBy.SelectedItem != null)
  291. {
  292. var treeItem = tvwLibrary.SelectedItem as TreeViewItem;
  293. var folder = treeItem != null
  294. ? treeItem.Tag as Folder
  295. : null;
  296. var prefs = folder != null ? folder.GetDisplayPrefs(CurrentUser, true) : new DisplayPreferences {SortBy = LocalizedStrings.Instance.GetString("NameDispPref")};
  297. if (folder != null && prefs.IndexBy != ddlIndexBy.SelectedItem as string)
  298. {
  299. //grab UI context so we can update within the below task
  300. var ui = TaskScheduler.FromCurrentSynchronizationContext();
  301. Cursor = Cursors.Wait;
  302. await Task.Factory.StartNew(() =>
  303. {
  304. using (
  305. new Profiler("Explorer full index expansion for " +
  306. folder.Name, _logger))
  307. {
  308. //re-build the current item's children as an index
  309. prefs.IndexBy = ddlIndexBy.SelectedItem as string;
  310. treeItem.Items.Clear();
  311. AddChildren(treeItem,folder.GetChildren(CurrentUser, prefs.IndexBy, prefs.SortBy), CurrentUser);
  312. treeItem.Header = folder.Name + "(" +
  313. treeItem.Items.Count + ")";
  314. Cursor = Cursors.Arrow;
  315. }
  316. }, CancellationToken.None, TaskCreationOptions.None,
  317. ui);
  318. }
  319. }
  320. }
  321. /// <summary>
  322. /// Handles the SelectionChanged event of the ddlSortBy control.
  323. /// </summary>
  324. /// <param name="sender">The source of the event.</param>
  325. /// <param name="e">The <see cref="SelectionChangedEventArgs" /> instance containing the event data.</param>
  326. private async void ddlSortBy_SelectionChanged(object sender, SelectionChangedEventArgs e)
  327. {
  328. if (ddlSortBy.SelectedItem != null)
  329. {
  330. var treeItem = tvwLibrary.SelectedItem as TreeViewItem;
  331. var folder = treeItem != null
  332. ? treeItem.Tag as Folder
  333. : null;
  334. var prefs = folder != null ? folder.GetDisplayPrefs(CurrentUser, true) : new DisplayPreferences();
  335. if (folder != null && prefs.SortBy != ddlSortBy.SelectedItem as string)
  336. {
  337. //grab UI context so we can update within the below task
  338. var ui = TaskScheduler.FromCurrentSynchronizationContext();
  339. Cursor = Cursors.Wait;
  340. await Task.Factory.StartNew(() =>
  341. {
  342. using (
  343. new Profiler("Explorer sorting by " + ddlSortBy.SelectedItem + " for " +
  344. folder.Name, _logger))
  345. {
  346. //re-sort
  347. prefs.SortBy = ddlSortBy.SelectedItem as string;
  348. treeItem.Items.Clear();
  349. AddChildren(treeItem,folder.GetChildren(CurrentUser,prefs.IndexBy, prefs.SortBy ?? LocalizedStrings.Instance.GetString("NameDispPref")), CurrentUser);
  350. treeItem.Header = folder.Name + "(" +
  351. treeItem.Items.Count + ")";
  352. Cursor = Cursors.Arrow;
  353. }
  354. }, CancellationToken.None, TaskCreationOptions.None,
  355. ui);
  356. }
  357. }
  358. }
  359. }
  360. /// <summary>
  361. /// Class PreviewItem
  362. /// </summary>
  363. public class PreviewItem
  364. {
  365. /// <summary>
  366. /// The preview
  367. /// </summary>
  368. private readonly string preview;
  369. /// <summary>
  370. /// The name
  371. /// </summary>
  372. private readonly string name;
  373. /// <summary>
  374. /// Gets the preview.
  375. /// </summary>
  376. /// <value>The preview.</value>
  377. public string Preview
  378. {
  379. get { return preview; }
  380. }
  381. /// <summary>
  382. /// Gets the name.
  383. /// </summary>
  384. /// <value>The name.</value>
  385. public string Name
  386. {
  387. get { return name; }
  388. }
  389. /// <summary>
  390. /// Initializes a new instance of the <see cref="PreviewItem" /> class.
  391. /// </summary>
  392. /// <param name="p">The p.</param>
  393. /// <param name="n">The n.</param>
  394. public PreviewItem(string p, string n)
  395. {
  396. preview = p;
  397. name = n;
  398. }
  399. }
  400. /// <summary>
  401. /// Class Extensions
  402. /// </summary>
  403. static class Extensions
  404. {
  405. /// <summary>
  406. /// Fors the each.
  407. /// </summary>
  408. /// <typeparam name="T"></typeparam>
  409. /// <param name="ie">The ie.</param>
  410. /// <param name="action">The action.</param>
  411. public static void ForEach<T>(this IEnumerable<T> ie, Action<T> action)
  412. {
  413. foreach (var i in ie)
  414. {
  415. action(i);
  416. }
  417. }
  418. }
  419. #region ItemToImageConverter
  420. /// <summary>
  421. /// Class ItemToImageConverter
  422. /// </summary>
  423. [ValueConversion(typeof(string), typeof(bool))]
  424. public class ItemToImageConverter : IValueConverter
  425. {
  426. /// <summary>
  427. /// The instance
  428. /// </summary>
  429. public static ItemToImageConverter Instance =
  430. new ItemToImageConverter();
  431. /// <summary>
  432. /// Converts a value.
  433. /// </summary>
  434. /// <param name="value">The value produced by the binding source.</param>
  435. /// <param name="targetType">The type of the binding target property.</param>
  436. /// <param name="parameter">The converter parameter to use.</param>
  437. /// <param name="culture">The culture to use in the converter.</param>
  438. /// <returns>A converted value. If the method returns null, the valid null value is used.</returns>
  439. public object Convert(object value, Type targetType,
  440. object parameter, CultureInfo culture)
  441. {
  442. var item = value as BaseItem ?? new Folder();
  443. switch (item.DisplayMediaType)
  444. {
  445. case "DVD":
  446. case "HD DVD":
  447. case "Blu-ray":
  448. case "Blu-Ray":
  449. case "Movie":
  450. {
  451. var uri = new Uri
  452. ("pack://application:,,,/Resources/Images/movie.png");
  453. var source = new BitmapImage(uri);
  454. return source;
  455. }
  456. case "Series":
  457. {
  458. var uri = new Uri
  459. ("pack://application:,,,/Resources/Images/series.png");
  460. var source = new BitmapImage(uri);
  461. return source;
  462. }
  463. case "Season":
  464. {
  465. var uri = new Uri
  466. ("pack://application:,,,/Resources/Images/season.png");
  467. var source = new BitmapImage(uri);
  468. return source;
  469. }
  470. case "Episode":
  471. {
  472. var uri = new Uri
  473. ("pack://application:,,,/Resources/Images/episode.png");
  474. var source = new BitmapImage(uri);
  475. return source;
  476. }
  477. case "BoxSet":
  478. {
  479. var uri = new Uri
  480. ("pack://application:,,,/Resources/Images/boxset.png");
  481. var source = new BitmapImage(uri);
  482. return source;
  483. }
  484. case "Audio":
  485. {
  486. var uri = new Uri
  487. ("pack://application:,,,/Resources/Images/audio.png");
  488. var source = new BitmapImage(uri);
  489. return source;
  490. }
  491. case "Person":
  492. {
  493. var uri = new Uri
  494. ("pack://application:,,,/Resources/Images/persons.png");
  495. var source = new BitmapImage(uri);
  496. return source;
  497. }
  498. case "MusicArtist":
  499. {
  500. var uri = new Uri
  501. ("pack://application:,,,/Resources/Images/artist.png");
  502. var source = new BitmapImage(uri);
  503. return source;
  504. }
  505. case "MusicAlbum":
  506. {
  507. var uri = new Uri
  508. ("pack://application:,,,/Resources/Images/album.png");
  509. var source = new BitmapImage(uri);
  510. return source;
  511. }
  512. case "Trailer":
  513. {
  514. var uri = new Uri
  515. ("pack://application:,,,/Resources/Images/trailer.png");
  516. var source = new BitmapImage(uri);
  517. return source;
  518. }
  519. case "None":
  520. {
  521. Uri uri;
  522. if (item is Movie)
  523. uri = new Uri("pack://application:,,,/Resources/Images/movie.png");
  524. else if (item is Series)
  525. uri = new Uri("pack://application:,,,/Resources/Images/series.png");
  526. else if (item is BoxSet)
  527. uri = new Uri("pack://application:,,,/Resources/Images/boxset.png");
  528. else
  529. uri = new Uri("pack://application:,,,/Resources/Images/folder.png");
  530. return new BitmapImage(uri);
  531. }
  532. default:
  533. {
  534. var uri = new Uri("pack://application:,,,/Resources/Images/folder.png");
  535. var source = new BitmapImage(uri);
  536. return source;
  537. }
  538. }
  539. }
  540. /// <summary>
  541. /// Converts a value.
  542. /// </summary>
  543. /// <param name="value">The value that is produced by the binding target.</param>
  544. /// <param name="targetType">The type to convert to.</param>
  545. /// <param name="parameter">The converter parameter to use.</param>
  546. /// <param name="culture">The culture to use in the converter.</param>
  547. /// <returns>A converted value. If the method returns null, the valid null value is used.</returns>
  548. /// <exception cref="System.NotSupportedException">Cannot convert back</exception>
  549. public object ConvertBack(object value, Type targetType,
  550. object parameter, CultureInfo culture)
  551. {
  552. throw new NotSupportedException("Cannot convert back");
  553. }
  554. }
  555. #endregion // ItemToImageConverter
  556. }