From 058daf2932a16396fe228be05b5790ad2197179e Mon Sep 17 00:00:00 2001 From: ryuuzera Date: Fri, 20 Sep 2024 23:35:50 -0300 Subject: [PATCH 1/3] add download feature --- TrimuiSmartHub.Application/App.xaml | 4 +- .../Frames/GameDownloader.xaml | 34 ++++ .../Frames/GameDownloader.xaml.cs | 166 ++++++++++++++++++ .../Frames/GameDownloaderSearch.xaml | 99 +++++++++++ .../Frames/GameDownloaderSearch.xaml.cs | 121 +++++++++++++ .../Frames/GameImageScrapper.xaml.cs | 29 ++- .../Model/GameInfoRetrostic.cs | 16 ++ .../Pages/SmartProHub.xaml | 4 +- .../Pages/SmartProHub.xaml.cs | 5 + .../Repository/EmulatorDictionary.cs | 64 +++++++ .../Services/Megathread/MegathreadService.cs | 45 +++++ .../Services/RetrosticService.cs | 137 +++++++++++++++ 12 files changed, 703 insertions(+), 21 deletions(-) create mode 100644 TrimuiSmartHub.Application/Frames/GameDownloader.xaml create mode 100644 TrimuiSmartHub.Application/Frames/GameDownloader.xaml.cs create mode 100644 TrimuiSmartHub.Application/Frames/GameDownloaderSearch.xaml create mode 100644 TrimuiSmartHub.Application/Frames/GameDownloaderSearch.xaml.cs create mode 100644 TrimuiSmartHub.Application/Model/GameInfoRetrostic.cs create mode 100644 TrimuiSmartHub.Application/Services/Megathread/MegathreadService.cs create mode 100644 TrimuiSmartHub.Application/Services/RetrosticService.cs diff --git a/TrimuiSmartHub.Application/App.xaml b/TrimuiSmartHub.Application/App.xaml index d97905f..5b7a837 100644 --- a/TrimuiSmartHub.Application/App.xaml +++ b/TrimuiSmartHub.Application/App.xaml @@ -49,8 +49,8 @@ - - + + diff --git a/TrimuiSmartHub.Application/Frames/GameDownloader.xaml b/TrimuiSmartHub.Application/Frames/GameDownloader.xaml new file mode 100644 index 0000000..ae8a87c --- /dev/null +++ b/TrimuiSmartHub.Application/Frames/GameDownloader.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TrimuiSmartHub.Application/Frames/GameDownloader.xaml.cs b/TrimuiSmartHub.Application/Frames/GameDownloader.xaml.cs new file mode 100644 index 0000000..dfd6df3 --- /dev/null +++ b/TrimuiSmartHub.Application/Frames/GameDownloader.xaml.cs @@ -0,0 +1,166 @@ +using System.IO; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using TrimuiSmartHub.Application.Repository; +using TrimuiSmartHub.Application.Services.LibRetro; +using TrimuiSmartHub.Application.Services.Trimui; +using TrimuiSmartHub.Application.Helpers; +using TrimuiSmartHub.Application.Services; +using CsQuery.Implementation; +using MahApps.Metro.Controls.Dialogs; +using MahApps.Metro.Controls; +using System.Linq; +using TrimuiSmartHub.Application.Pages; +using CsQuery.Engine.PseudoClassSelectors; + +namespace TrimuiSmartHub.Application.Frames +{ + public partial class GameDownloader : Page + { + private SmartProHub Parent { get; set; } + public GameDownloader(SmartProHub parent) + { + InitializeComponent(); + + Parent = parent; + + PopulateEmulators(); + } + + private void PopulateEmulators() + { + Task.Run(() => + { + var emulatorList = TrimuiService.New().GetEmulators(); + + foreach (var emulator in emulatorList) + { + + Dispatcher.Invoke(() => + { + var emulatorCard = CreateGameComponent(emulator); + + if (emulatorCard != null) + { + RomsContainer.Children.Add(emulatorCard); + } + }); + } + + Dispatcher.Invoke(() => + { + RomsPanel.Visibility = Visibility.Visible; + LoadingIndicator.Visibility = Visibility.Collapsed; + }); + }); + } + private Button? CreateGameComponent(string emulator) + { + string emulatorDescription; + + EmulatorDictionary.EmulatorMapRetrostic.TryGetValue(emulator, out emulatorDescription); + + if (emulatorDescription.IsNullOrEmpty()) return null; + + Border border = new Border + { + Background = new LinearGradientBrush + { + StartPoint = new Point(0.5, 0), + EndPoint = new Point(0.5, 1), + GradientStops = new GradientStopCollection + { + new GradientStop(Color.FromArgb(35, 25, 84, 112), 1), + new GradientStop(Color.FromArgb(75, 45, 84, 112), 1) + } + }, + CornerRadius = new CornerRadius(10), + Width = 120, + Height = 120, + Margin = new Thickness(5), + BorderThickness = new Thickness(1), + BorderBrush = new SolidColorBrush(Color.FromArgb(75, 45, 84, 112)), + }; + + StackPanel stackPanel = new StackPanel + { + Orientation = Orientation.Vertical, + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center + }; + + try + { + var imgSource = new BitmapImage(new Uri($"pack://application:,,,/Resources/Images/Emulators/{emulator}.png")); + Image image = new Image + { + Source = imgSource, + Width = 70, + Height = 60, + VerticalAlignment = VerticalAlignment.Center, + Margin = new Thickness(0, 0, 0, 0) + }; + + RenderOptions.SetBitmapScalingMode(image, BitmapScalingMode.HighQuality); + + stackPanel.Children.Add(image); + } + catch (Exception) + { + // + } + + + TextBlock titleBlock = new TextBlock + { + Text = emulator, + FontSize = 14, + FontWeight = FontWeights.Bold, + Foreground = Brushes.LightGray, + HorizontalAlignment = HorizontalAlignment.Center + }; + + stackPanel.Children.Add(titleBlock); + + border.Child = stackPanel; + + Button button = new Button + { + Content = border, + Style = (Style)System.Windows.Application.Current.FindResource("GameButtonStyle"), + Background = Brushes.Transparent, + BorderThickness = new Thickness(0), + }; + + button.Click += async (sender, e) => + { + //await Loading(true, $"Downloading games boxart..."); + + var count = 0; + + await Task.Run(async () => + { + //var download = RetrosticService.New().DownloadGame(emulator, "Street Fighter"); + + var gameList = await RetrosticService.New().ListGamesByEmulator(emulator); + + if (gameList == null) return; + + Dispatcher.Invoke(() => + { + Parent.NavigationFrame.Navigate(new GameDownloaderSearch(gameList)); + }); + + }); + + //await Loading(false); + + //await ShowMessageAsync("Download Completed!", (count > 0) ? $"{count} Files was updated!" : "The boxarts already updated!"); + }; + + return button; + } + } +} diff --git a/TrimuiSmartHub.Application/Frames/GameDownloaderSearch.xaml b/TrimuiSmartHub.Application/Frames/GameDownloaderSearch.xaml new file mode 100644 index 0000000..0490487 --- /dev/null +++ b/TrimuiSmartHub.Application/Frames/GameDownloaderSearch.xaml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - @@ -65,8 +100,9 @@ + - + + - - - - + + + + diff --git a/TrimuiSmartHub.Application/Frames/GameDownloaderSearch.xaml.cs b/TrimuiSmartHub.Application/Frames/GameDownloaderSearch.xaml.cs index 4c70abd..d8e719e 100644 --- a/TrimuiSmartHub.Application/Frames/GameDownloaderSearch.xaml.cs +++ b/TrimuiSmartHub.Application/Frames/GameDownloaderSearch.xaml.cs @@ -6,6 +6,13 @@ using System.Windows.Data; using System.Globalization; using TrimuiSmartHub.Application.Services; +using System.Windows.Input; +using TrimuiSmartHub.Application.Repository; +using TrimuiSmartHub.Application.Helpers; +using System.Diagnostics; +using MahApps.Metro.Controls.Dialogs; +using MahApps.Metro.Controls; +using System.Runtime.CompilerServices; namespace TrimuiSmartHub.Application.Frames { @@ -28,25 +35,50 @@ public object ConvertBack(object value, Type targetType, object parameter, Cultu } public partial class GameDownloaderSearch : Page { + private ProgressDialogController Controller { get; set; } private List GameList { get; set; } - public GameDownloaderSearch(List gameList) + private string Emulator { get; set; } + public GameDownloaderSearch(string emulator) { InitializeComponent(); - GameList = gameList; + string emulatorDescription; - PopulateGameList(); + EmulatorDictionary.EmulatorMapSystem.TryGetValue(emulator, out emulatorDescription); + if (emulatorDescription.IsNotNullOrEmpty()) ConsoleName.Text = $"{emulatorDescription} Games"; + + Emulator = emulator; + + Task.Run(() => PopulateGameList(emulator)); } - private void PopulateGameList() + private void PopulateGameList(string emulator) { - foreach (var item in GameList) + Task.Run(async () => { - var button = CreateGameButton(item); + var gameList = await RetrosticService.New().ListGamesByEmulator(emulator); - Container.Children.Add(button); - } + if (gameList == null) return; + + GameList = gameList; + + foreach (var item in GameList) + { + Dispatcher.Invoke(() => + { + var button = CreateGameButton(item); + + Container.Children.Add(button); + }); + } + + Dispatcher.Invoke(() => + { + LoadingComponent.Visibility = Visibility.Collapsed; + Container.Visibility = Visibility.Visible; + }); + }); } public Button CreateGameButton(GameInfoRetrostic gameInfo) @@ -54,7 +86,8 @@ public Button CreateGameButton(GameInfoRetrostic gameInfo) Button button = new Button { Margin = new Thickness(0, 10, 0, 0), - Style = (Style)System.Windows.Application.Current.FindResource("TransparentButton") + Style = (Style)System.Windows.Application.Current.FindResource("TransparentButton"), + Cursor = Cursors.Hand }; Border border = new Border @@ -68,8 +101,10 @@ public Button CreateGameButton(GameInfoRetrostic gameInfo) Grid grid = new Grid(); grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); + grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(3, GridUnitType.Star) }); grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); + try { var imgSrc = new BitmapImage(gameInfo.BoxArt); @@ -88,11 +123,10 @@ public Button CreateGameButton(GameInfoRetrostic gameInfo) { // } - TextBlock textBlock = new TextBlock { - Text = gameInfo.Title.Replace("Roms", string.Empty), + Text = gameInfo.Title, Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#AAB8C2")), FontWeight = FontWeights.SemiBold, FontSize = 18, @@ -103,10 +137,23 @@ public Button CreateGameButton(GameInfoRetrostic gameInfo) grid.Children.Add(textBlock); + TextBlock textRegion = new TextBlock + { + Text = gameInfo.Region, + Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#AAB8C2")), + FontWeight = FontWeights.SemiBold, + FontSize = 18, + VerticalAlignment = VerticalAlignment.Center, + Margin = new Thickness(50, 0, 0, 0) + }; + Grid.SetColumn(textRegion, 2); + + grid.Children.Add(textRegion); + Binding binding = new Binding("ActualWidth") { RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(WrapPanel), 1), - Converter = new WidthAdjustmentConverter() // Aplicar o conversor que diminui a largura + Converter = new WidthAdjustmentConverter() }; border.SetBinding(Border.WidthProperty, binding); @@ -116,24 +163,146 @@ public Button CreateGameButton(GameInfoRetrostic gameInfo) button.Click += async (sender, e) => { - //await Loading(true, $"Downloading games boxart..."); - - var count = 0; - + await Loading(true, $"Downloading {gameInfo.Title}"); + await Task.Run(async () => { - var download = RetrosticService.New().DownloadGame(gameInfo); + var (contentStream, fileStream, totalBytes) = await RetrosticService.New().DownloadGame(gameInfo); - }); + var buffer = new byte[8192]; + long totalBytesRead = 0; + int bytesRead; + var stopwatch = Stopwatch.StartNew(); + + Controller.Maximum = 100; + + while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length)) > 0) + { + await fileStream.WriteAsync(buffer, 0, bytesRead); + totalBytesRead += bytesRead; + + double elapsedTime = stopwatch.Elapsed.TotalSeconds; + + double downloadSpeed = totalBytesRead / elapsedTime; + + double downloadSpeedMbps = downloadSpeed / (1024 * 1024); + + double downloadSpeedKbps = downloadSpeed / 1024; + + Debug.WriteLine($"Downloaded {(double)totalBytesRead * 100 / totalBytes:0.00}%."); + + double progress = (totalBytesRead * 100 / totalBytes); + + progress = Math.Min(progress, 100); - //await Loading(false); + await Dispatcher.InvokeAsync(() => { + Controller.SetProgress(progress); + Controller.SetMessage($"Progress: {progress:0.00}% | Download Speed: {downloadSpeedMbps:0.00} MB/s"); + }); + } - //await ShowMessageAsync("Download Completed!", (count > 0) ? $"{count} Files was updated!" : "The boxarts already updated!"); + fileStream.Close(); + contentStream.Close(); + }); + + await Loading(false); }; return button; } + private async Task Loading(bool isLoading, string controllerTitle = null, Action cancelAction = null) + { + try + { + await CloseController(); + + if (!isLoading) return; + + var metroDialogSettings = new MetroDialogSettings + { + DialogTitleFontSize = 30, + DialogMessageFontSize = 20, + AnimateShow = true, + AnimateHide = true, + ColorScheme = MetroDialogColorScheme.Inverted + }; + + var metroWindow = (MetroWindow)System.Windows.Application.Current.MainWindow; + + controllerTitle = controllerTitle.IsNullOrEmpty() ? "Downloading box arts..." : controllerTitle; + + Controller = await metroWindow.ShowProgressAsync(controllerTitle, "Please don't disconnect the device.", cancelAction != null, metroDialogSettings); + + //Controller.SetIndeterminate(); + + Controller.Canceled += (sender, args) => + { + cancelAction?.Invoke(); + }; + } + catch (Exception) + { + await CloseController(); + } + } + + private async Task CloseController() + { + try + { + if (Controller != null) + { + if (Controller.IsOpen) await Controller.CloseAsync(); + + Controller = null; + } + } + catch (Exception) + { + // ignored + } + } + + private void Search_Button_Click(object sender, RoutedEventArgs e) + { + try + { + Container.Children.Clear(); + + LoadingComponent.Visibility = Visibility.Visible; + + Task.Run(async () => + { + var searchTerm = Dispatcher.Invoke(() => SearchTermTextBox.Text); + var gameList = await RetrosticService.New().SearchGamesByName(searchTerm, Emulator); + + if (gameList == null) return; + + GameList = gameList; + + foreach (var item in GameList) + { + Dispatcher.Invoke(() => + { + var button = CreateGameButton(item); + + Container.Children.Add(button); + }); + } + + Dispatcher.Invoke(() => + { + LoadingComponent.Visibility = Visibility.Collapsed; + Container.Visibility = Visibility.Visible; + }); + }); + } + catch (Exception) + { + // + } + } } } diff --git a/TrimuiSmartHub.Application/Helpers/ValidationHelper.cs b/TrimuiSmartHub.Application/Helpers/ValidationHelper.cs index 0d6b95f..37cf53f 100644 --- a/TrimuiSmartHub.Application/Helpers/ValidationHelper.cs +++ b/TrimuiSmartHub.Application/Helpers/ValidationHelper.cs @@ -215,6 +215,11 @@ public static string Clear(this string stringToClean) return stringToClean == null ? null : Regex.Replace(stringToClean, @"[^\d]", string.Empty); } + public static string ClearSpecial(this string stringToClean) + { + return stringToClean == null ? null : Regex.Replace(stringToClean, @"[^a-zA-Z0-9]", string.Empty); + } + public static bool IsNullOrEmpty(this string stringToVerify) { return string.IsNullOrEmpty(stringToVerify); diff --git a/TrimuiSmartHub.Application/MainWindow.xaml.cs b/TrimuiSmartHub.Application/MainWindow.xaml.cs index b8f8760..f75aa47 100644 --- a/TrimuiSmartHub.Application/MainWindow.xaml.cs +++ b/TrimuiSmartHub.Application/MainWindow.xaml.cs @@ -16,7 +16,7 @@ public MainWindow() { InitializeComponent(); - ThemeManager.Current.ChangeTheme(this, ThemeManager.Current.GetTheme("Light.Blue")); + ThemeManager.Current.ChangeTheme(this, ThemeManager.Current.GetTheme("Light.Steel")); trimuiService = TrimuiService.New(); diff --git a/TrimuiSmartHub.Application/Model/GameInfoRetrostic.cs b/TrimuiSmartHub.Application/Model/GameInfoRetrostic.cs index 03e4f7a..0be11a2 100644 --- a/TrimuiSmartHub.Application/Model/GameInfoRetrostic.cs +++ b/TrimuiSmartHub.Application/Model/GameInfoRetrostic.cs @@ -8,6 +8,7 @@ namespace TrimuiSmartHub.Application.Model { public class GameInfoRetrostic { + public string? Emulator { get; set; } public string Title { get; set; } public Uri? BoxArt { get; set; } public string? Region { get; set; } diff --git a/TrimuiSmartHub.Application/Pages/Home.xaml b/TrimuiSmartHub.Application/Pages/Home.xaml index b152fb5..03a8c15 100644 --- a/TrimuiSmartHub.Application/Pages/Home.xaml +++ b/TrimuiSmartHub.Application/Pages/Home.xaml @@ -92,7 +92,7 @@ - + diff --git a/TrimuiSmartHub.Application/Pages/SmartProHub.xaml.cs b/TrimuiSmartHub.Application/Pages/SmartProHub.xaml.cs index f383f7d..77029a6 100644 --- a/TrimuiSmartHub.Application/Pages/SmartProHub.xaml.cs +++ b/TrimuiSmartHub.Application/Pages/SmartProHub.xaml.cs @@ -7,28 +7,55 @@ namespace TrimuiSmartHub.Application.Pages public partial class SmartProHub : Page { private readonly MainWindow _mainWindow; + + private readonly Stack _navigationHistory = new Stack(); + public SmartProHub(MainWindow mainWindow) { InitializeComponent(); _mainWindow = mainWindow; - NavigationFrame.Navigate(new TrimuiSmartHubLogo()); + var initialPage = new TrimuiSmartHubLogo(); + + NavigationFrame.Navigate(initialPage); + + _navigationHistory.Push(initialPage); } private void BackHome_Click(object sender, RoutedEventArgs e) { - _mainWindow.MainFrame.Navigate(new Home(_mainWindow)); + if (NavigationFrame.Content is TrimuiSmartHubLogo) + { + _mainWindow.MainFrame.Navigate(new Home(_mainWindow)); + return; + } + + if (_navigationHistory.Count > 1) + { + _navigationHistory.Pop(); + var previousPage = _navigationHistory.Peek(); + NavigationFrame.Navigate(previousPage); + } } private void GetImages_Click(object sender, RoutedEventArgs e) { - NavigationFrame.Navigate(new GameImageScrapper()); + NavigateToPage(new GameImageScrapper()); } private void Roms_Click(object sender, RoutedEventArgs e) { - NavigationFrame.Navigate(new GameDownloader(this)); + NavigateToPage(new GameDownloader(this)); + } + + private void NavigateToPage(Page newPage) + { + if (NavigationFrame.Content is Page currentPage && currentPage != newPage) + { + _navigationHistory.Push(newPage); + NavigationFrame.Navigate(newPage); + } } } } diff --git a/TrimuiSmartHub.Application/Repository/EmulatorDictionary.cs b/TrimuiSmartHub.Application/Repository/EmulatorDictionary.cs index 4c5f461..6f8ec0b 100644 --- a/TrimuiSmartHub.Application/Repository/EmulatorDictionary.cs +++ b/TrimuiSmartHub.Application/Repository/EmulatorDictionary.cs @@ -40,38 +40,6 @@ internal static class EmulatorDictionary { "MAME", "MAME" } }; - public static Dictionary EmulatorMapMegathread = new() - { - { "ARCADE", "MAME" }, - { "ATARI2600", "Atari - 2600" }, - { "ATARI7800", "Atari - 7800" }, - { "CPS1", "https://archive.org/download/fbnarcade-fullnonmerged/arcade/" }, - { "CPS2", "https://archive.org/download/fbnarcade-fullnonmerged/arcade/" }, - { "CPS3", "https://archive.org/download/fbnarcade-fullnonmerged/arcade/" }, - { "DC", "Sega - Dreamcast" }, - { "FBNEO", "https://archive.org/download/fbnarcade-fullnonmerged/arcade/" }, - { "FC", "Nintendo - Nintendo Entertainment System" }, - { "GB", "Nintendo - Game Boy" }, - { "GBA", "Nintendo - Game Boy Advance" }, - { "GBC", "Nintendo - Game Boy Color" }, - { "MAME2003PLUS", "MAME" }, - { "MD", "Sega - Mega Drive - Genesis" }, - { "MS", "Sega - Master System - Mark III" }, - { "N64", "Nintendo - Nintendo 64" }, - { "NDS", "Nintendo - Nintendo DS" }, - { "NEOGEO", "SNK - Neo Geo" }, - { "NGP", "SNK - Neo Geo Pocket" }, - { "PCE", "NEC - PC Engine - TurboGrafx 16" }, - { "PGM", "IGS - PolyGameMaster" }, - { "PPSSPP", "Sony - PlayStation Portable" }, - { "PS", "Sony - PlayStation" }, - { "SFC", "Nintendo - Super Nintendo Entertainment System" }, - { "WSC", "Bandai - WonderSwan Color" }, - { "GG", "Sega - Game Gear" }, - { "SS", "Sega - Saturn" }, - { "MAME", "MAME" } - }; - public static Dictionary EmulatorMapRetrostic = new() { { "ARCADE", "https://www.retrostic.com/roms/mame" }, @@ -103,5 +71,37 @@ internal static class EmulatorDictionary { "SS", "https://www.retrostic.com/roms/saturn" }, { "MAME", "https://www.retrostic.com/roms/mame" } }; + + public static Dictionary EmulatorMapSystem = new() + { + { "ARCADE", "Multiple Arcade Machine Emulator" }, + { "ATARI2600", "Atari 2600" }, + { "ATARI7800", "Atari 7800" }, + { "CPS1", "Capcom Play System 1" }, + { "CPS2", "Capcom Play System 2" }, + { "CPS3", "Capcom Play System 3" }, + { "DC", "Sega Dreamcast" }, + //{ "FBNEO", "https://archive.org/download/fbnarcade-fullnonmerged/arcade/" }, + { "FC", "Nintendo Entertainment System" }, + { "GB", "Nintendo Game Boy" }, + { "GBA", "Nintendo Game Boy Advance" }, + { "GBC", "Nintendo Game Boy Color" }, + { "MAME2003PLUS", "Multiple Arcade Machine Emulator" }, + { "MD", "Sega Mega Drive" }, + { "MS", "Sega Master System" }, + { "N64", "Nintendo 64" }, + { "NDS", "Nintendo DS" }, + { "NEOGEO", "Neo Geo" }, + { "NGP", "Neo Geo Pocket" }, + { "PCE", "PC Engine - TurboGrafx16" }, + //{ "PGM", "IGS - PolyGameMaster" }, + { "PPSSPP", "Playstation Portable" }, + { "PS", "Sony PSX/PlayStation 1" }, + { "SFC", "Super Nintendo Entertainment System" }, + { "WSC", "Bandai Wonderswan Color" }, + { "GG", "Sega Game Gear" }, + { "SS", "Sega Saturn" }, + { "MAME", "Multiple Arcade Machine Emulator" } + }; } } diff --git a/TrimuiSmartHub.Application/Services/RetrosticService.cs b/TrimuiSmartHub.Application/Services/RetrosticService.cs index 0fa0e2f..222130d 100644 --- a/TrimuiSmartHub.Application/Services/RetrosticService.cs +++ b/TrimuiSmartHub.Application/Services/RetrosticService.cs @@ -1,4 +1,5 @@ -using CsQuery; +using ControlzEx.Standard; +using CsQuery; using CsQuery.ExtensionMethods; using System.Diagnostics; using System.IO; @@ -8,6 +9,7 @@ using TrimuiSmartHub.Application.Helpers; using TrimuiSmartHub.Application.Model; using TrimuiSmartHub.Application.Repository; +using TrimuiSmartHub.Application.Services.Trimui; namespace TrimuiSmartHub.Application.Services @@ -18,7 +20,6 @@ class RetrosticService : IDisposable private readonly HttpClient httpClient; private CookieContainer cookieContainer; - private Uri baseUri { get; set; } private readonly GameRepository gameRepository; @@ -49,6 +50,70 @@ public void Dispose() { httpClient.Dispose(); } + public async Task> SearchGamesByName(string gameName, string emulator) + { + var result = new List(); + + var firstLetter = gameName.ToLower()[0]; + var emulatorUri = EmulatorDictionary.EmulatorMapRetrostic.FirstOrDefault(x => x.Key.Equals(emulator)).Value.ToUri(); + var sortingUri = emulatorUri.AddQuery("sorting", firstLetter); + + string currentUri = sortingUri.ToString(); + + while (!string.IsNullOrEmpty(currentUri)) + { + CQ lastDom = httpClient.GetStringAsync(currentUri).Result; + + var tableRows = lastDom["tbody tr[itemtype*='VideoGame']"]; + + var gameList = tableRows["td[class*='d-sm-table-cell'] a[href*='/roms/']"] + .Where(x => x.HasAttribute("title") && x.GetAttribute("title").ClearSpecial().ToLower().Contains(gameName?.ClearSpecial().ToLower())); + + var gameImgs = gameList.Select(x => x.ChildNodes[0].GetAttribute("data-src")).ToList(); + + var gameRegions = tableRows["td"] + .Where(x => + { + var parentHtml = CQ.Create(x.ParentNode.InnerHTML); + + var hasMatchingTitle = parentHtml["td[class*='d-sm-table-cell'] a[href*='/roms/']"] + .Any(a => a.HasAttribute("title") && a.GetAttribute("title").ClearSpecial().ToLower().Contains(gameName?.ClearSpecial().ToLower())); + + return hasMatchingTitle && x.InnerHTML.Contains("/flags") && !x.InnerHTML.Contains("\n"); + }) + .Select(x => x.InnerText) + .ToList(); + + foreach (var item in gameList) + { + var gameInfo = new GameInfoRetrostic + { + Emulator = emulator, + Title = Regex.Replace(item.GetAttribute("title").ToString() + .Replace("Roms", string.Empty) + .Replace("ISO", string.Empty), + @"\(\s*[^)]+\s*\)|\[\s*[^\]]+\s*\]", string.Empty).Trim(), + BoxArt = baseUri.Append(gameImgs[gameList.IndexOf(item)] ?? ""), + DownloadPage = baseUri.Append(item.GetAttribute("href") ?? ""), + Region = gameRegions[gameList.IndexOf(item)] ?? "" + }; + + result.Add(gameInfo); + } + + var nextPageElement = lastDom["a[class*='page-link']"].FirstOrDefault(x => x.InnerText.Equals(">")); + + if (nextPageElement != null) + { + currentUri = baseUri.Append(nextPageElement.GetAttribute("href")).ToString(); + continue; + } + + currentUri = null; + } + + return result; + } public async Task> ListGamesByEmulator(string emulator) { @@ -58,78 +123,61 @@ public async Task> ListGamesByEmulator(string emulator) CQ lastDom = httpClient.GetStringAsync(emulatorUri).Result; - var gameList = lastDom["td[class*='d-sm-table-cell'] a[href*='/roms/']"].Where(x => x.HasAttribute("title")); + var tableRows = lastDom["tbody tr[itemtype*='VideoGame']"]; + + var gameList = tableRows["td[class*='d-sm-table-cell'] a[href*='/roms/']"].Where(x => x.HasAttribute("title")); var gameImgs = gameList.Select(x => x.ChildNodes[0].GetAttribute("data-src")).ToList(); + var gameRegions = tableRows["td"].Where(x => x.InnerHTML.Contains("/flags") && !x.InnerHTML.Contains("\n")).Select(x => x.InnerText).ToList(); + foreach (var item in gameList) { var gameInfo = new GameInfoRetrostic { - Title = item.GetAttribute("title"), + Emulator = emulator, + Title = Regex.Replace(item.GetAttribute("title").ToString() + .Replace("Roms", string.Empty) + .Replace("ISO", string.Empty), + @"\(\s*[^)]+\s*\)|\[\s*[^\]]+\s*\]", string.Empty).Trim(), BoxArt = baseUri.Append(gameImgs[gameList.IndexOf(item)] ?? ""), - DownloadPage = baseUri.Append(item.GetAttribute("href") ?? "") + DownloadPage = baseUri.Append(item.GetAttribute("href") ?? ""), + Region = gameRegions[gameList.IndexOf(item)] ?? "" }; result.Add(gameInfo); } - //var findGame = gameLinks.Select(x => x.GetAttribute("title")).Where(x => x.IsNotNullOrEmpty()).Where(x => x.Contains(romName)).First(); - - //var findGameLink = gameLinks.FirstOrDefault(x => x.GetAttribute("title").IsNotNullOrEmpty() && x.GetAttribute("title").Contains(romName)).GetAttribute("href"); - - //var downloadGamePageLink = baseUri.Append(findGameLink.ToString()); - return result; } - public async Task DownloadGame(GameInfoRetrostic gameInfo) + public async Task<(Stream contentStream, FileStream fileStream, long totalBytes)> DownloadGame(GameInfoRetrostic gameInfo) { CQ downloadGamePage = httpClient.GetStringAsync(gameInfo.DownloadPage).Result; var session = downloadGamePage["input[name*='session']"]?.FirstOrDefault()?.GetAttribute("value"); - var gameinfo = gameInfo.DownloadPage?.ToString().Split('/'); var formContent = KeyValueHelper.AddValue("session", session) - .AddValue("rom_url", gameinfo[5]) - .AddValue("console_url", gameinfo[4]); + .AddValue("rom_url", gameinfo[5]) + .AddValue("console_url", gameinfo[4]); var content = new FormUrlEncodedContent(formContent); - downloadGamePage = httpClient.PostAsync(gameInfo.DownloadPage?.Append("download"), content).Result.Content.ReadAsStringAsync().Result; var downloadLink = Regex.Match(downloadGamePage.RenderSelection(), @"window\.location\.href\s*=\s*""(https:\/\/[^\s""]+)""").Groups[1].Value; - var response = await httpClient.GetAsync(downloadLink, HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); var totalBytes = response.Content.Headers.ContentLength ?? -1L; - var canReportProgress = totalBytes != -1; - using (var contentStream = await response.Content.ReadAsStreamAsync()) - using (var fileStream = new FileStream($"{gameInfo.Title}." + downloadLink.Split('.').Last(), FileMode.Create, FileAccess.Write, FileShare.None, 8192, true)) - { - var buffer = new byte[8192]; - long totalBytesRead = 0; - int bytesRead; - while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length)) > 0) - { - await fileStream.WriteAsync(buffer, 0, bytesRead); - totalBytesRead += bytesRead; + var contentStream = await response.Content.ReadAsStreamAsync(); - if (canReportProgress) - { - Debug.WriteLine($"Downloaded {totalBytesRead} of {totalBytes} bytes. {(totalBytesRead * 100 / totalBytes):0.00}% complete."); - } - else - { - Debug.WriteLine($"Downloaded {totalBytesRead} bytes."); - } - } - } + var romsFolder = TrimuiService.New().GetRomsFolder(gameInfo.Emulator!); + + var fileStream = new FileStream(Path.Combine(romsFolder, $"{gameInfo.Title}.{downloadLink.Split('.').Last()}"), FileMode.Create, FileAccess.Write, FileShare.None, 8192, true); - return [0]; + return (contentStream, fileStream, totalBytes); } diff --git a/TrimuiSmartHub.Application/Services/Trimui/TrimuiService.cs b/TrimuiSmartHub.Application/Services/Trimui/TrimuiService.cs index 8d03821..ca6f16a 100644 --- a/TrimuiSmartHub.Application/Services/Trimui/TrimuiService.cs +++ b/TrimuiSmartHub.Application/Services/Trimui/TrimuiService.cs @@ -262,6 +262,17 @@ public string GetImageFolder(string emulator) return emulatorImagesPath; } + public string GetRomsFolder(string emulator) + { + string romPath = EmulatorConfig(emulator, "rompath"); + + if (string.IsNullOrEmpty(romPath)) return string.Empty; + + string emulatorImagesPath = Path.GetFullPath(Path.Combine(Path.Combine(EmulatorPath, emulator), romPath)); + + return emulatorImagesPath; + } + public List GetEmulators() { var result = new List();