using MediaBrowser.ApiInteraction; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Common.Kernel; using MediaBrowser.Common.Logging; using MediaBrowser.Common.UI; using MediaBrowser.IsoMounter; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Net; using MediaBrowser.Model.Weather; using MediaBrowser.UI.Controller; using MediaBrowser.UI.Controls; using MediaBrowser.UI.Pages; using MediaBrowser.UI.Uninstall; using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Media.Imaging; namespace MediaBrowser.UI { /// /// Interaction logic for App.xaml /// public partial class App : BaseApplication, IApplication { /// /// Gets or sets the clock timer. /// /// The clock timer. private Timer ClockTimer { get; set; } /// /// Gets or sets the server configuration timer. /// /// The server configuration timer. private Timer ServerConfigurationTimer { get; set; } /// /// Gets the name of the product. /// /// The name of the product. protected override string ProductName { get { return Globals.ProductName; } } /// /// Gets the name of the publisher. /// /// The name of the publisher. protected override string PublisherName { get { return Globals.PublisherName; } } /// /// Gets the name of the suite. /// /// The name of the suite. protected override string SuiteName { get { return Globals.SuiteName; } } /// /// Gets the name of the uninstaller file. /// /// The name of the uninstaller file. protected override string UninstallerFileName { get { return "MediaBrowser.UI.Uninstall.exe"; } } /// /// Gets the instance. /// /// The instance. public static App Instance { get { return Current as App; } } /// /// Gets the API client. /// /// The API client. public ApiClient ApiClient { get { return UIKernel.Instance.ApiClient; } } /// /// Gets the application window. /// /// The application window. public MainWindow ApplicationWindow { get; private set; } /// /// Gets the hidden window. /// /// The hidden window. public HiddenWindow HiddenWindow { get; private set; } /// /// The _current user /// private UserDto _currentUser; /// /// Gets or sets the current user. /// /// The current user. public UserDto CurrentUser { get { return _currentUser; } set { _currentUser = value; if (UIKernel.Instance.ApiClient != null) { if (value == null) { UIKernel.Instance.ApiClient.CurrentUserId = null; } else { UIKernel.Instance.ApiClient.CurrentUserId = value.Id; } } OnPropertyChanged("CurrentUser"); } } /// /// The _server configuration /// private ServerConfiguration _serverConfiguration; /// /// Gets or sets the server configuration. /// /// The server configuration. public ServerConfiguration ServerConfiguration { get { return _serverConfiguration; } set { _serverConfiguration = value; OnPropertyChanged("ServerConfiguration"); } } /// /// The _current time /// private DateTime _currentTime = DateTime.Now; /// /// Gets the current time. /// /// The current time. public DateTime CurrentTime { get { return _currentTime; } private set { _currentTime = value; OnPropertyChanged("CurrentTime"); } } /// /// The _current weather /// private WeatherInfo _currentWeather; /// /// Gets the current weather. /// /// The current weather. public WeatherInfo CurrentWeather { get { return _currentWeather; } private set { _currentWeather = value; OnPropertyChanged("CurrentWeather"); } } /// /// The _current theme /// private BaseTheme _currentTheme; /// /// Gets the current theme. /// /// The current theme. public BaseTheme CurrentTheme { get { return _currentTheme; } private set { _currentTheme = value; OnPropertyChanged("CurrentTheme"); } } /// /// Defines the entry point of the application. /// [STAThread] public static void Main() { RunApplication("MediaBrowserUI"); } /// /// Instantiates the kernel. /// /// IKernel. protected override IKernel InstantiateKernel() { return new UIKernel(new PismoIsoManager(LogManager.GetLogger("PismoIsoManager"))); } /// /// Instantiates the main window. /// /// Window. protected override Window InstantiateMainWindow() { HiddenWindow = new HiddenWindow { }; return HiddenWindow; } /// /// Shows the application window. /// private void ShowApplicationWindow() { var win = new MainWindow(Logger); var config = UIKernel.Instance.Configuration; // Restore window position/size if (config.WindowState.HasValue) { // Set window state win.WindowState = config.WindowState.Value; // Set position if not maximized if (config.WindowState.Value != WindowState.Maximized) { double left = 0; double top = 0; // Set left if (config.WindowLeft.HasValue) { win.WindowStartupLocation = WindowStartupLocation.Manual; win.Left = left = Math.Max(config.WindowLeft.Value, 0); } // Set top if (config.WindowTop.HasValue) { win.WindowStartupLocation = WindowStartupLocation.Manual; win.Top = top = Math.Max(config.WindowTop.Value, 0); } // Set width if (config.WindowWidth.HasValue) { win.Width = Math.Min(config.WindowWidth.Value, SystemParameters.VirtualScreenWidth - left); } // Set height if (config.WindowHeight.HasValue) { win.Height = Math.Min(config.WindowHeight.Value, SystemParameters.VirtualScreenHeight - top); } } } win.LocationChanged += ApplicationWindow_LocationChanged; win.StateChanged += ApplicationWindow_LocationChanged; win.SizeChanged += ApplicationWindow_LocationChanged; ApplicationWindow = win; ApplicationWindow.Show(); ApplicationWindow.Owner = HiddenWindow; SyncHiddenWindowLocation(); } /// /// Handles the LocationChanged event of the ApplicationWindow control. /// /// The source of the event. /// The instance containing the event data. void ApplicationWindow_LocationChanged(object sender, EventArgs e) { SyncHiddenWindowLocation(); } /// /// Syncs the hidden window location. /// public void SyncHiddenWindowLocation() { HiddenWindow.Width = ApplicationWindow.Width; HiddenWindow.Height = ApplicationWindow.Height; HiddenWindow.Top = ApplicationWindow.Top; HiddenWindow.Left = ApplicationWindow.Left; HiddenWindow.WindowState = ApplicationWindow.WindowState; ApplicationWindow.Activate(); } /// /// Loads the kernel. /// protected override async void LoadKernel() { // Without this the app will shutdown after the splash screen closes ShutdownMode = ShutdownMode.OnExplicitShutdown; Kernel = InstantiateKernel(); try { var now = DateTime.UtcNow; await Kernel.Init(); Logger.Info("Kernel.Init completed in {0} seconds.", (DateTime.UtcNow - now).TotalSeconds); ShutdownMode = System.Windows.ShutdownMode.OnLastWindowClose; await OnKernelLoaded(); InstantiateMainWindow().Show(); ShowApplicationWindow(); await ApplicationWindow.LoadInitialUI().ConfigureAwait(false); } catch (Exception ex) { Logger.ErrorException("Error launching application", ex); MessageBox.Show("There was an error launching Media Browser: " + ex.Message); // Shutdown the app with an error code Shutdown(1); } } /// /// Called when [kernel loaded]. /// /// Task. protected override async Task OnKernelLoaded() { await base.OnKernelLoaded().ConfigureAwait(false); PropertyChanged += AppPropertyChanged; // Update every 10 seconds ClockTimer = new Timer(ClockTimerCallback, null, 0, 10000); // Update every 30 minutes ServerConfigurationTimer = new Timer(ServerConfigurationTimerCallback, null, 0, 1800000); CurrentTheme = UIKernel.Instance.Themes.First(); foreach (var resource in CurrentTheme.GetGlobalResources()) { Resources.MergedDictionaries.Add(resource); } } /// /// Raises the event. /// /// An that contains the event data. protected override void OnExit(ExitEventArgs e) { var win = ApplicationWindow; if (win != null) { // Save window position var config = UIKernel.Instance.Configuration; config.WindowState = win.WindowState; config.WindowTop = win.Top; config.WindowLeft = win.Left; config.WindowWidth = win.Width; config.WindowHeight = win.Height; UIKernel.Instance.SaveConfiguration(); } base.OnExit(e); } /// /// Apps the property changed. /// /// The sender. /// The instance containing the event data. async void AppPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName.Equals("ServerConfiguration")) { if (string.IsNullOrEmpty(ServerConfiguration.WeatherLocation)) { CurrentWeather = null; } else { try { CurrentWeather = await ApiClient.GetWeatherInfoAsync(ServerConfiguration.WeatherLocation); } catch (HttpException ex) { Logger.ErrorException("Error downloading weather information", ex); } } } } /// /// Clocks the timer callback. /// /// The state info. private void ClockTimerCallback(object stateInfo) { CurrentTime = DateTime.Now; } /// /// Servers the configuration timer callback. /// /// The state info. private async void ServerConfigurationTimerCallback(object stateInfo) { try { ServerConfiguration = await ApiClient.GetServerConfigurationAsync(); } catch (HttpException ex) { Logger.ErrorException("Error refreshing server configuration", ex); } } /// /// Logouts the user. /// /// Task. public async Task LogoutUser() { CurrentUser = null; await Dispatcher.InvokeAsync(() => Navigate(CurrentTheme.GetLoginPage())); } /// /// Navigates the specified page. /// /// The page. public void Navigate(Page page) { _remoteImageCache = new FileSystemRepository(UIKernel.Instance.ApplicationPaths.RemoteImageCachePath); ApplicationWindow.Navigate(page); } /// /// Navigates to settings page. /// public void NavigateToSettingsPage() { Navigate(new SettingsPage()); } /// /// Navigates to internal player page. /// public void NavigateToInternalPlayerPage() { Navigate(CurrentTheme.GetInternalPlayerPage()); } /// /// Navigates to image viewer. /// /// The image URL. /// The caption. public void OpenImageViewer(Uri imageUrl, string caption) { var tuple = new Tuple(imageUrl, caption); OpenImageViewer(new[] { tuple }); } /// /// Navigates to image viewer. /// /// The images. public void OpenImageViewer(IEnumerable> images) { new ImageViewerWindow(images).ShowModal(ApplicationWindow); } /// /// Navigates to item. /// /// The item. public void NavigateToItem(BaseItemDto item) { if (item.IsRoot.HasValue && item.IsRoot.Value) { NavigateToHomePage(); } else if (item.IsFolder) { Navigate(CurrentTheme.GetListPage(item)); } else { Navigate(CurrentTheme.GetDetailPage(item)); } } /// /// Displays the weather. /// public void DisplayWeather() { CurrentTheme.DisplayWeather(); } /// /// Navigates to home page. /// public void NavigateToHomePage() { Navigate(CurrentTheme.GetHomePage()); } /// /// Shows a notification message that will disappear on it's own /// /// The text. /// The caption. /// The icon. public void ShowNotificationMessage(string text, string caption = null, MessageBoxIcon icon = MessageBoxIcon.None) { ApplicationWindow.ShowModalMessage(text, caption: caption, icon: icon); } /// /// Shows a notification message that will disappear on it's own /// /// The text. /// The caption. /// The icon. public void ShowNotificationMessage(UIElement text, string caption = null, MessageBoxIcon icon = MessageBoxIcon.None) { ApplicationWindow.ShowModalMessage(text, caption: caption, icon: icon); } /// /// Shows a modal message box and asynchronously returns a MessageBoxResult /// /// The text. /// The caption. /// The button. /// The icon. /// MessageBoxResult. public MessageBoxResult ShowModalMessage(string text, string caption = null, MessageBoxButton button = MessageBoxButton.OK, MessageBoxIcon icon = MessageBoxIcon.None) { return ApplicationWindow.ShowModalMessage(text, caption: caption, button: button, icon: icon); } /// /// Shows a modal message box and asynchronously returns a MessageBoxResult /// /// The text. /// The caption. /// The button. /// The icon. /// MessageBoxResult. public MessageBoxResult ShowModalMessage(UIElement text, string caption = null, MessageBoxButton button = MessageBoxButton.OK, MessageBoxIcon icon = MessageBoxIcon.None) { return ApplicationWindow.ShowModalMessage(text, caption: caption, button: button, icon: icon); } /// /// Shows the error message. /// /// The message. /// The caption. public void ShowErrorMessage(string message, string caption = null) { caption = caption ?? "Error"; ShowModalMessage(message, caption: caption, button: MessageBoxButton.OK, icon: MessageBoxIcon.Error); } /// /// Shows the default error message. /// public void ShowDefaultErrorMessage() { ShowErrorMessage("There was an error processing the request", "Error"); } /// /// The _remote image cache /// private FileSystemRepository _remoteImageCache; /// /// Gets the remote image async. /// /// The URL. /// Task{Image}. public async Task GetRemoteImageAsync(string url) { var bitmap = await GetRemoteBitmapAsync(url); return new Image { Source = bitmap }; } /// /// Gets the remote image async. /// /// The URL. /// Task{BitmapImage}. /// url public Task GetRemoteBitmapAsync(string url) { if (string.IsNullOrEmpty(url)) { throw new ArgumentNullException("url"); } Logger.Info("Image url: " + url); return Task.Run(async () => { var cachePath = _remoteImageCache.GetResourcePath(url.GetMD5().ToString()); await _remoteImageCache.WaitForLockAsync(cachePath).ConfigureAwait(false); var releaseLock = true; try { using (var stream = new FileStream(cachePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, true)) { return await GetRemoteBitmapAsync(stream).ConfigureAwait(false); } } catch (FileNotFoundException) { // Doesn't exist. No biggie releaseLock = false; } finally { if (releaseLock) { _remoteImageCache.ReleaseLock(cachePath); } } try { using (var httpStream = await UIKernel.Instance.ApiClient.GetImageStreamAsync(url + "&x=1")) { return await GetRemoteBitmapAsync(httpStream, cachePath); } } finally { _remoteImageCache.ReleaseLock(cachePath); } }); } /// /// Gets the image async. /// /// The source stream. /// The cache path. /// Task{BitmapImage}. private async Task GetRemoteBitmapAsync(Stream sourceStream, string cachePath = null) { byte[] bytes; using (var ms = new MemoryStream()) { await sourceStream.CopyToAsync(ms).ConfigureAwait(false); bytes = ms.ToArray(); } if (!string.IsNullOrEmpty(cachePath)) { using (var fileStream = new FileStream(cachePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, true)) { await fileStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); } } using (Stream stream = new MemoryStream(bytes)) { var bitmapImage = new BitmapImage { CreateOptions = BitmapCreateOptions.DelayCreation }; bitmapImage.BeginInit(); bitmapImage.CacheOption = BitmapCacheOption.OnLoad; bitmapImage.StreamSource = stream; bitmapImage.EndInit(); bitmapImage.Freeze(); return bitmapImage; } } } }