From a61f5640d3f313fcff8c7a02f0fdcd94703b9475 Mon Sep 17 00:00:00 2001 From: libgenapps <33476799+libgenapps@users.noreply.github.com> Date: Thu, 30 May 2019 06:22:26 +0300 Subject: [PATCH] Search results grid context menu for multiple selected items --- LibgenDesktop.Setup/Constants.cs | 4 +- LibgenDesktop/Common/Constants.cs | 7 +- LibgenDesktop/Common/Environment.cs | 30 ++-- LibgenDesktop/LibgenDesktop.csproj | 6 +- .../Models/Download/DownloadItemRequest.cs | 12 ++ LibgenDesktop/Models/Download/Downloader.cs | 45 ++++-- .../DownloadManagerLocalizator.cs | 3 +- .../SearchResultsTabLocalizator.cs | 5 + .../SynchronizationLocalizator.cs | 3 +- .../Models/Localization/Translation.cs | 2 + LibgenDesktop/Models/Utils/ExceptionUtils.cs | 16 ++ LibgenDesktop/Resources/Languages/English.lng | 8 +- LibgenDesktop/Resources/Languages/French.lng | 4 +- .../Resources/Languages/Romanian.lng | 19 ++- LibgenDesktop/Resources/Languages/Russian.lng | 8 +- LibgenDesktop/Resources/Languages/Spanish.lng | 4 +- .../Resources/Languages/Ukrainian.lng | 4 +- .../FictionSearchResultItemViewModel.cs | 4 + .../NonFictionSearchResultItemViewModel.cs | 4 + .../SciMagSearchResultItemViewModel.cs | 4 + .../SearchResultItemViewModel.cs | 6 +- .../ViewModels/Tabs/DetailsTabViewModel.cs | 12 +- .../Tabs/FictionSearchResultsTabViewModel.cs | 37 +---- .../Tabs/ISearchResultsTabViewModel.cs | 8 + .../ViewModels/Tabs/ITabViewModel.cs | 9 ++ .../NonFictionSearchResultsTabViewModel.cs | 37 +---- .../Tabs/SciMagSearchResultsTabViewModel.cs | 37 +---- .../Tabs/SearchResultsTabViewModel.cs | 138 +++++++++++++++--- LibgenDesktop/ViewModels/Tabs/TabViewModel.cs | 2 +- .../ViewModels/Windows/MainWindowViewModel.cs | 16 +- .../Windows/SynchronizationWindowViewModel.cs | 10 +- LibgenDesktop/Views/Controls/BookDataGrid.cs | 29 ++++ .../Controls/BookDataGridContextMenu.xaml | 11 +- .../Views/Controls/ImportLogPanel.xaml | 4 +- .../Views/Styles/ImportLogPanelStyles.xaml | 2 + LibgenDesktop/Views/Styles/TabStyles.xaml | 2 +- .../Views/Tabs/FictionSearchResultsTab.xaml | 13 +- .../Tabs/NonFictionSearchResultsTab.xaml | 11 +- .../Views/Tabs/SciMagSearchResultsTab.xaml | 13 +- .../Views/Utils/DataGridExtensions.cs | 10 +- 40 files changed, 397 insertions(+), 202 deletions(-) create mode 100644 LibgenDesktop/Models/Download/DownloadItemRequest.cs create mode 100644 LibgenDesktop/Models/Utils/ExceptionUtils.cs create mode 100644 LibgenDesktop/ViewModels/Tabs/ISearchResultsTabViewModel.cs create mode 100644 LibgenDesktop/ViewModels/Tabs/ITabViewModel.cs create mode 100644 LibgenDesktop/Views/Controls/BookDataGrid.cs diff --git a/LibgenDesktop.Setup/Constants.cs b/LibgenDesktop.Setup/Constants.cs index f6a81d2..067a23a 100644 --- a/LibgenDesktop.Setup/Constants.cs +++ b/LibgenDesktop.Setup/Constants.cs @@ -2,8 +2,8 @@ { internal static class Constants { - public const string CURRENT_VERSION = "1.3.0"; - public const string TITLE_VERSION = "1.3.0"; + public const string CURRENT_VERSION = "1.3.1"; + public const string TITLE_VERSION = "1.3.1"; public const string PRODUCT_TITLE_FORMAT = "Libgen Desktop " + TITLE_VERSION + " ({0}-bit)"; public const string SHORTCUT_TITLE_FORMAT = "Libgen Desktop ({0}-bit)"; public const string PRODUCT_COMPANY = "Libgen Apps"; diff --git a/LibgenDesktop/Common/Constants.cs b/LibgenDesktop/Common/Constants.cs index 8bd5cce..8597fa0 100644 --- a/LibgenDesktop/Common/Constants.cs +++ b/LibgenDesktop/Common/Constants.cs @@ -5,9 +5,9 @@ namespace LibgenDesktop.Common internal static class Constants { public const string DATABASE_METADATA_APP_NAME = "LibgenDesktop"; - public const string CURRENT_VERSION = "1.3.0"; - public const string CURRENT_GITHUB_RELEASE_NAME = "1.3.0"; - public static readonly DateTime CURRENT_GITHUB_RELEASE_DATE = new DateTime(2019, 5, 22); + public const string CURRENT_VERSION = "1.3.1"; + public const string CURRENT_GITHUB_RELEASE_NAME = "1.3.1"; + public static readonly DateTime CURRENT_GITHUB_RELEASE_DATE = new DateTime(2019, 5, 30); public const string CURRENT_DATABASE_VERSION = "1.2.1"; public const string APP_SETTINGS_FILE_NAME = "libgen.config"; @@ -119,6 +119,7 @@ internal static class Constants public const double SYNCHRONIZATION_PROGRESS_UPDATE_INTERVAL = 0.1; public const int DATABASE_TRANSACTION_BATCH = 500; public const int MAX_EXPORT_ROWS_PER_FILE = 1048575; + public const int LARGE_NUMBER_OF_ITEMS_TO_DOWNLOAD_WARNING_THRESHOLD = 1000; public const int MIN_DOWNLOAD_TIMEOUT = 15; public const int MAX_DOWNLOAD_TIMEOUT = 9999; public const int DEFAULT_DOWNLOAD_TIMEOUT = 120; diff --git a/LibgenDesktop/Common/Environment.cs b/LibgenDesktop/Common/Environment.cs index 6da2674..983fedf 100644 --- a/LibgenDesktop/Common/Environment.cs +++ b/LibgenDesktop/Common/Environment.cs @@ -1,7 +1,5 @@ using System; using System.IO; -using System.Linq; -using System.Management; using System.Reflection; using Microsoft.Win32; using static LibgenDesktop.Common.Constants; @@ -10,6 +8,7 @@ namespace LibgenDesktop.Common { internal static class Environment { + private const string WINDOWS_NT_CURRENT_VERSION_REGISTRY_KEY = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion\"; private const string NET_FRAMEWORK_REGISTRY_KEY = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\"; static Environment() @@ -56,13 +55,16 @@ static Environment() private static string GetOsVersion() { - ManagementObject osInfo = new ManagementObjectSearcher("SELECT * FROM Win32_OperatingSystem").Get().OfType().FirstOrDefault(); - if (osInfo != null) - { - return $"{osInfo.Properties["Caption"].Value.ToString()} {osInfo.Properties["Version"].Value.ToString()} {osInfo.Properties["OSArchitecture"].Value.ToString()}"; - } - else + using (RegistryKey windowsNtCurrentVersionRegistryKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default). + OpenSubKey(WINDOWS_NT_CURRENT_VERSION_REGISTRY_KEY)) { + if (windowsNtCurrentVersionRegistryKey != null) + { + string productName = windowsNtCurrentVersionRegistryKey.GetValue("ProductName")?.ToString() ?? "Unknown"; + string releaseId = windowsNtCurrentVersionRegistryKey.GetValue("ReleaseId")?.ToString() ?? "Unknown"; + string build = windowsNtCurrentVersionRegistryKey.GetValue("CurrentBuildNumber")?.ToString() ?? "Unknown"; + return $"{productName} (release: {releaseId}, build: {build})"; + } return "Unknown"; } } @@ -79,9 +81,17 @@ private static string GetNetFrameworkVersion() { if (Int32.TryParse(releaseValue.ToString(), out int releaseNumber)) { - if (releaseNumber >= 461308) + if (releaseNumber >= 528040) + { + return "4.8 or later"; + } + else if (releaseNumber >= 461808) + { + return "4.7.2"; + } + else if (releaseNumber >= 461308) { - return "4.7.1 or later"; + return "4.7.1"; } else if (releaseNumber >= 460798) { diff --git a/LibgenDesktop/LibgenDesktop.csproj b/LibgenDesktop/LibgenDesktop.csproj index 72698d1..dc78626 100644 --- a/LibgenDesktop/LibgenDesktop.csproj +++ b/LibgenDesktop/LibgenDesktop.csproj @@ -93,7 +93,6 @@ - @@ -150,6 +149,7 @@ + @@ -234,6 +234,7 @@ + @@ -249,9 +250,11 @@ + + @@ -286,6 +289,7 @@ + BookDataGridContextMenu.xaml diff --git a/LibgenDesktop/Models/Download/DownloadItemRequest.cs b/LibgenDesktop/Models/Download/DownloadItemRequest.cs new file mode 100644 index 0000000..58f1191 --- /dev/null +++ b/LibgenDesktop/Models/Download/DownloadItemRequest.cs @@ -0,0 +1,12 @@ +namespace LibgenDesktop.Models.Download +{ + internal class DownloadItemRequest + { + public string DownloadPageUrl { get; set; } + public string FileNameWithoutExtension { get; set; } + public string FileExtension { get; set; } + public string Md5Hash { get; set; } + public string DownloadTransformations { get; set; } + public bool RestartSessionOnTimeout { get; set; } + } +} diff --git a/LibgenDesktop/Models/Download/Downloader.cs b/LibgenDesktop/Models/Download/Downloader.cs index 9bf52d5..84e18f8 100644 --- a/LibgenDesktop/Models/Download/Downloader.cs +++ b/LibgenDesktop/Models/Download/Downloader.cs @@ -93,14 +93,14 @@ public DownloadItem GetDownloadItemByDownloadPageUrl(string downloadPageUrl) } } - public void EnqueueDownloadItem(string downloadPageUrl, string fileNameWithoutExtension, string fileExtension, string md5Hash, - string downloadTransformations, bool restartSessionOnTimeout) + public void EnqueueDownloadItem(DownloadItemRequest downloadItemRequest) { - string fileName = String.Concat(FileUtils.RemoveInvalidFileNameCharacters(fileNameWithoutExtension, md5Hash), ".", fileExtension.ToLower()); + string fileName = String.Concat(FileUtils.RemoveInvalidFileNameCharacters(downloadItemRequest.FileNameWithoutExtension, + downloadItemRequest.Md5Hash), ".", downloadItemRequest.FileExtension.ToLower()); lock (downloadQueueLock) { - DownloadItem newDownloadItem = new DownloadItem(Guid.NewGuid(), downloadPageUrl, downloadSettings.DownloadDirectory, fileName, - downloadTransformations, md5Hash, restartSessionOnTimeout); + DownloadItem newDownloadItem = new DownloadItem(Guid.NewGuid(), downloadItemRequest.DownloadPageUrl, downloadSettings.DownloadDirectory, + fileName, downloadItemRequest.DownloadTransformations, downloadItemRequest.Md5Hash, downloadItemRequest.RestartSessionOnTimeout); downloadQueue.Add(newDownloadItem); eventQueue.Add(new DownloadItemAddedEventArgs(newDownloadItem)); AddLogLine(newDownloadItem, DownloadItemLogLineType.INFORMATIONAL, localization.LogLineQueued); @@ -109,6 +109,25 @@ public void EnqueueDownloadItem(string downloadPageUrl, string fileNameWithoutEx } } + public void EnqueueDownloadItems(List downloadItemRequests) + { + lock (downloadQueueLock) + { + foreach (DownloadItemRequest downloadItemRequest in downloadItemRequests) + { + string fileName = String.Concat(FileUtils.RemoveInvalidFileNameCharacters(downloadItemRequest.FileNameWithoutExtension, + downloadItemRequest.Md5Hash), ".", downloadItemRequest.FileExtension.ToLower()); + DownloadItem newDownloadItem = new DownloadItem(Guid.NewGuid(), downloadItemRequest.DownloadPageUrl, downloadSettings.DownloadDirectory, + fileName, downloadItemRequest.DownloadTransformations, downloadItemRequest.Md5Hash, downloadItemRequest.RestartSessionOnTimeout); + downloadQueue.Add(newDownloadItem); + eventQueue.Add(new DownloadItemAddedEventArgs(newDownloadItem)); + AddLogLine(newDownloadItem, DownloadItemLogLineType.INFORMATIONAL, localization.LogLineQueued); + } + SaveDownloadQueue(); + ResumeDownloadTask(); + } + } + public void StartDownloads(IEnumerable downloadItemIds) { lock (downloadQueueLock) @@ -571,7 +590,7 @@ private async Task DownloadFileAsync(DownloadItem downloadItem) catch (Exception exception) { Logger.Exception(exception); - ReportError(downloadItem, localization.LogLineUnexpectedError); + ReportError(downloadItem, localization.GetLogLineUnexpectedError(exception.GetInnermostException().Message)); } } @@ -727,7 +746,7 @@ private async Task DownloadFileAsync(DownloadItem downloadItem, FileStream desti if (!expectedError) { Logger.Exception(ioException); - ReportError(downloadItem, localization.LogLineUnexpectedError); + ReportError(downloadItem, localization.GetLogLineUnexpectedError(ioException.GetInnermostException().Message)); } break; } @@ -739,7 +758,7 @@ private async Task DownloadFileAsync(DownloadItem downloadItem, FileStream desti break; } Logger.Exception(exception); - ReportError(downloadItem, localization.LogLineUnexpectedError); + ReportError(downloadItem, localization.GetLogLineUnexpectedError(exception.GetInnermostException().Message)); break; } if (bytesRead == 0) @@ -840,14 +859,14 @@ private async Task SendDownloadRequestAsync(DownloadItem do else { Logger.Exception(aggregateException); - ReportError(downloadItem, localization.LogLineUnexpectedError); + ReportError(downloadItem, localization.GetLogLineUnexpectedError(aggregateException.GetInnermostException().Message)); } return null; } catch (Exception exception) { Logger.Exception(exception); - ReportError(downloadItem, localization.LogLineUnexpectedError); + ReportError(downloadItem, localization.GetLogLineUnexpectedError(exception.GetInnermostException().Message)); return null; } Logger.Debug($"Response status code: {(int)response.StatusCode} {response.StatusCode}."); @@ -902,11 +921,7 @@ private Task SendRequestAsync(HttpRequestMessage request, C } catch (Exception exception) { - while (!(exception is SocketException) && exception.InnerException != null) - { - exception = exception.InnerException; - } - if (exception is SocketException socketException && socketException.SocketErrorCode == SocketError.TimedOut) + if (exception.GetInnermostException() is SocketException socketException && socketException.SocketErrorCode == SocketError.TimedOut) { throw new TimeoutException(); } diff --git a/LibgenDesktop/Models/Localization/Localizators/DownloadManagerLocalizator.cs b/LibgenDesktop/Models/Localization/Localizators/DownloadManagerLocalizator.cs index 2b13cbe..6e02908 100644 --- a/LibgenDesktop/Models/Localization/Localizators/DownloadManagerLocalizator.cs +++ b/LibgenDesktop/Models/Localization/Localizators/DownloadManagerLocalizator.cs @@ -41,7 +41,6 @@ public DownloadManagerLocalizator(List prioritizedTranslationList, LogLineServerResponseTimeout = Format(translation => translation?.LogMessages?.ServerResponseTimeout); LogLineDownloadIncompleteError = Format(translation => translation?.LogMessages?.DownloadIncompleteError); LogLineFileWriteError = Format(translation => translation?.LogMessages?.FileWriteError); - LogLineUnexpectedError = Format(translation => translation?.LogMessages?.UnexpectedError); } public string TabTitle { get; } @@ -77,7 +76,6 @@ public DownloadManagerLocalizator(List prioritizedTranslationList, public string LogLineServerResponseTimeout { get; } public string LogLineDownloadIncompleteError { get; } public string LogLineFileWriteError { get; } - public string LogLineUnexpectedError { get; } public string GetDownloadProgressKnownFileSize(long downloaded, long total, int percent) => Format(translation => translation?.DownloadProgressKnownFileSize, @@ -107,6 +105,7 @@ public string GetLogLineCannotCreateOrOpenFile(string file) => public string GetLogLineCannotRenamePartFile(string source, string destination) => Format(translation => translation?.LogMessages?.CannotRenamePartFile, new { source, destination }); public string GetLogLineRequestError(string url) => Format(translation => translation?.LogMessages?.LogLineRequestError, new { url }); + public string GetLogLineUnexpectedError(string error) => Format(translation => translation?.LogMessages?.UnexpectedError, new { error }); private string Format(Func field, object templateArguments = null) { diff --git a/LibgenDesktop/Models/Localization/Localizators/SearchResultsTabLocalizator.cs b/LibgenDesktop/Models/Localization/Localizators/SearchResultsTabLocalizator.cs index a72e216..dcd671e 100644 --- a/LibgenDesktop/Models/Localization/Localizators/SearchResultsTabLocalizator.cs +++ b/LibgenDesktop/Models/Localization/Localizators/SearchResultsTabLocalizator.cs @@ -22,6 +22,7 @@ public SearchResultsTabLocalizator(List prioritizedTranslationList, OfflineModeIsOnMessageTitle = Format(translation => translation?.OfflineModeIsOnMessageTitle); OfflineModeIsOnMessageText = Format(translation => translation?.OfflineModeIsOnMessageText); NoDownloadMirrorError = Format(translation => translation?.NoDownloadMirrorError); + LargeNumberOfItemsToDownloadPromptTitle = Format(translation => translation?.LargeNumberOfItemsToDownloadPromptTitle); } public string SearchPlaceHolder { get; } @@ -38,9 +39,13 @@ public SearchResultsTabLocalizator(List prioritizedTranslationList, public string OfflineModeIsOnMessageTitle { get; } public string OfflineModeIsOnMessageText { get; } public string NoDownloadMirrorError { get; } + public string LargeNumberOfItemsToDownloadPromptTitle { get; } public string GetFileNotFoundErrorText(string file) => Format(translation => translation?.FileNotFoundError, new { file }); + public string GetLargeNumberOfItemsToDownloadPromptText(int number) => + Format(translation => translation?.LargeNumberOfItemsToDownloadPromptText, new { number }); + private string Format(Func field, object templateArguments = null) { return Format(translation => field(translation?.SearchResultsTabs), templateArguments); diff --git a/LibgenDesktop/Models/Localization/Localizators/SynchronizationLocalizator.cs b/LibgenDesktop/Models/Localization/Localizators/SynchronizationLocalizator.cs index 292f811..90814c5 100644 --- a/LibgenDesktop/Models/Localization/Localizators/SynchronizationLocalizator.cs +++ b/LibgenDesktop/Models/Localization/Localizators/SynchronizationLocalizator.cs @@ -30,7 +30,6 @@ public SynchronizationLocalizator(List prioritizedTranslationList, LogLineDownloadingNewBooks = FormatLogLine(translation => translation?.DownloadingNewBooks); LogLineSynchronizationSuccessful = FormatLogLine(translation => translation?.SynchronizationSuccessful); LogLineSynchronizationCancelled = FormatLogLine(translation => translation?.SynchronizationCancelled); - LogLineSynchronizationError = FormatLogLine(translation => translation?.SynchronizationError); } public string WindowTitle { get; } @@ -55,7 +54,6 @@ public SynchronizationLocalizator(List prioritizedTranslationList, public string LogLineDownloadingNewBooks { get; set; } public string LogLineSynchronizationSuccessful { get; set; } public string LogLineSynchronizationCancelled { get; set; } - public string LogLineSynchronizationError { get; set; } public string GetElapsedString(string elapsed) => Format(translation => translation?.Elapsed, new { elapsed }); public string GetStatusStep(int current, int total) => FormatStatus(translation => translation?.Step, new { current, total }); @@ -70,6 +68,7 @@ public string GetLogLineSynchronizationProgressUpdated(int downloaded, int updat FormatLogLine(translation => translation?.SynchronizationProgressUpdated, new { downloaded, updated }); public string GetLogLineSynchronizationProgressAddedAndUpdated(int downloaded, int added, int updated) => FormatLogLine(translation => translation?.SynchronizationProgressAddedAndUpdated, new { downloaded, added, updated }); + public string GetLogLineSynchronizationError(string error) => FormatLogLine(translation => translation?.SynchronizationError, new { error }); private string Format(Func field, object templateArguments = null) { diff --git a/LibgenDesktop/Models/Localization/Translation.cs b/LibgenDesktop/Models/Localization/Translation.cs index 8db262c..1598099 100644 --- a/LibgenDesktop/Models/Localization/Translation.cs +++ b/LibgenDesktop/Models/Localization/Translation.cs @@ -105,6 +105,8 @@ internal class SearchResultsTabsTranslation public string OfflineModeIsOnMessageTitle { get; set; } public string OfflineModeIsOnMessageText { get; set; } public string NoDownloadMirrorError { get; set; } + public string LargeNumberOfItemsToDownloadPromptTitle { get; set; } + public string LargeNumberOfItemsToDownloadPromptText { get; set; } } internal class NonFictionSearchResultsGridColumnsTranslation diff --git a/LibgenDesktop/Models/Utils/ExceptionUtils.cs b/LibgenDesktop/Models/Utils/ExceptionUtils.cs new file mode 100644 index 0000000..767e309 --- /dev/null +++ b/LibgenDesktop/Models/Utils/ExceptionUtils.cs @@ -0,0 +1,16 @@ +using System; + +namespace LibgenDesktop.Models.Utils +{ + internal static class ExceptionUtils + { + public static Exception GetInnermostException(this Exception exception) + { + while (exception.InnerException != null) + { + exception = exception.InnerException; + } + return exception; + } + } +} diff --git a/LibgenDesktop/Resources/Languages/English.lng b/LibgenDesktop/Resources/Languages/English.lng index cb2cf05..d796fb7 100644 --- a/LibgenDesktop/Resources/Languages/English.lng +++ b/LibgenDesktop/Resources/Languages/English.lng @@ -92,7 +92,9 @@ "FileNotFoundError": "File {file} not found.", "OfflineModeIsOnMessageTitle": "Offline mode is on", "OfflineModeIsOnMessageText": "Downloading is disabled while the offline mode is on. You can turn it off in the settings window.", - "NoDownloadMirrorError": "No download mirror has been selected. Please choose one of the mirrors in the settings window." + "NoDownloadMirrorError": "No download mirror has been selected. Please choose one of the mirrors in the settings window.", + "LargeNumberOfItemsToDownloadPromptTitle": "Large number of items to download", + "LargeNumberOfItemsToDownloadPromptText": "You are about to download {number} items. Are you sure?" }, "NonFictionSearchResultsTab": { @@ -506,7 +508,7 @@ "SynchronizationProgressAddedAndUpdated": "Books downloaded: {downloaded}, added: {added}, updated: {updated}.", "SynchronizationSuccessful": "Synchronization completed successfully.", "SynchronizationCancelled": "Synchronization has been cancelled by the user.", - "SynchronizationError": "An error occurred during the synchronization." + "SynchronizationError": "An error occurred during the synchronization: {error}" } }, "Library": @@ -607,7 +609,7 @@ "DownloadIncompleteError": "Server indicates that file download is complete, but it is not.", "FileWriteError": "File write error.", "LogLineRequestError": "Couldn't send request to {url}", - "UnexpectedError": "An unexpected error occurred." + "UnexpectedError": "An unexpected error occurred: {error}" } }, "ApplicationUpdate": diff --git a/LibgenDesktop/Resources/Languages/French.lng b/LibgenDesktop/Resources/Languages/French.lng index 55c6c7d..e78a3d3 100644 --- a/LibgenDesktop/Resources/Languages/French.lng +++ b/LibgenDesktop/Resources/Languages/French.lng @@ -500,7 +500,7 @@ "SynchronizationProgressAddedAndUpdated": "Livres téléchargés : {downloaded}, ajoutés : {added}, actualisés : {updated}.", "SynchronizationSuccessful": "La synchronisation s'est terminée avec succès.", "SynchronizationCancelled": "La synchronisation a été annulée par l'utilisateur.", - "SynchronizationError": "Une erreur est apparue lors de la synchronisation." + "SynchronizationError": "Une erreur est apparue lors de la synchronisation: {error}" } }, "Library": @@ -594,7 +594,7 @@ "ServerResponseTimeout": "Délai de réponse du serveur.", "DownloadIncompleteError": "Le serveur indique que le téléchargement du fichier est terminé, mais il ne l'est pas.", "FileWriteError": "Erreur d'écriture de fichier.", - "UnexpectedError": "Une erreur inattendue est apparue." + "UnexpectedError": "Une erreur inattendue est apparue: {error}" } }, "ApplicationUpdate": diff --git a/LibgenDesktop/Resources/Languages/Romanian.lng b/LibgenDesktop/Resources/Languages/Romanian.lng index 1e80806..1e007fe 100644 --- a/LibgenDesktop/Resources/Languages/Romanian.lng +++ b/LibgenDesktop/Resources/Languages/Romanian.lng @@ -85,9 +85,14 @@ "AddToBookmarksTooltip": "Adaugă la Marcaje", "RemoveFromBookmarksTooltip": "Șterge din Marcaje", "ExportButtonTooltip": "Exportă rezultatele căutării într-un fișier", + "Details": "Detalii", + "Open": "Deschide", + "Download": "Descarcă", "ErrorMessageTitle": "Eroare", - "FileNotFoundError": "fișierul {file} nu a fost găsit.", - "OfflineModeIsOnMessageTitle": "Modul Offline este dezactivat" + "FileNotFoundError": "Fișierul {file} nu a fost găsit.", + "OfflineModeIsOnMessageTitle": "Modul Offline este activat", + "OfflineModeIsOnMessageText": "Descărcarea este dezactivată în modul de lucru Offline. O poți activa în fereastra de setări.", + "NoDownloadMirrorError": "Nu a fost selectat un link de descărcare. Vă rugăm alegeți unul din link-urile de descărcare în fereastra de setări." }, "NonFictionSearchResultsTab": { @@ -501,7 +506,7 @@ "SynchronizationProgressAddedAndUpdated": "Cărți descărcate: {downloaded}, adăugate: {added}, actualizate: {updated}.", "SynchronizationSuccessful": "Sincronizarea s-a încheiat cu succes.", "SynchronizationCancelled": "Sincronizarea a fost oprită de utilizator.", - "SynchronizationError": "S-a produs o eroare în timpul sincronizării." + "SynchronizationError": "S-a produs o eroare în timpul sincronizării: {error}" } }, "Library": @@ -531,6 +536,7 @@ "Database": { "WindowTitle": "Informații despre Baza de date", + "CurrentDatabase": "Baza de date curentă:", "NonFiction": "Cărți non-ficțiune", "Fiction": "Cărți ficțiune", "SciMagArticles": "Articole științifice", @@ -538,9 +544,9 @@ "TotalArticles": "Articole disponibile", "LastUpdate": "Ultima actualizare", "Never": "niciodată", - "CreatingIndexes": "Vă rugăm așteptați. Se creează indecșii lipsă...{new-line}This operation cannot be interrupted.", + "CreatingIndexes": "Vă rugăm așteptați. Se creează indecșii lipsă...{new-line}Această operațiune nu poate fi întreruptă.", "ChangeDatabase": "SCHIMBĂ BAZA DE DATE...", - "BrowseDatabaseDialogTitle": "Locația Bazei e date", + "BrowseDatabaseDialogTitle": "Locația Bazei de date", "Databases": "Baza de date", "AllFiles": "Toate fișierele", "Error": "Eroare", @@ -600,7 +606,8 @@ "ServerResponseTimeout": "Intervalul de răspuns al serverului a expirat.", "DownloadIncompleteError": "Serverul indică faptul că descărcarea fișierelor este completă, dar nu este.", "FileWriteError": "eroare de scriere a fișierului.", - "UnexpectedError": "S-a produs o eroare neașteptată." + "LogLineRequestError": "Nu s-a putut trimte cererea către {url}", + "UnexpectedError": "S-a produs o eroare neașteptată: {error}" } }, "ApplicationUpdate": diff --git a/LibgenDesktop/Resources/Languages/Russian.lng b/LibgenDesktop/Resources/Languages/Russian.lng index 2f374fa..86d289f 100644 --- a/LibgenDesktop/Resources/Languages/Russian.lng +++ b/LibgenDesktop/Resources/Languages/Russian.lng @@ -92,7 +92,9 @@ "FileNotFoundError": "Файл {file} не найден.", "OfflineModeIsOnMessageTitle": "Включен автономный режим", "OfflineModeIsOnMessageText": "Скачивание файлов недоступно в автономном режиме. Вы можете отключить автономный режим в окне настроек.", - "NoDownloadMirrorError": "Не выбрано зеркало для загрузки файлов. Пожалуйста, выберите одно из доступных зеркал в окне настроек." + "NoDownloadMirrorError": "Не выбрано зеркало для загрузки файлов. Пожалуйста, выберите одно из доступных зеркал в окне настроек.", + "LargeNumberOfItemsToDownloadPromptTitle": "Большое количество элементов для загрузки", + "LargeNumberOfItemsToDownloadPromptText": "Вы выбрали большое количество элементов для загрузки ({number}). Продолжить?" }, "NonFictionSearchResultsTab": { @@ -506,7 +508,7 @@ "SynchronizationProgressAddedAndUpdated": "Скачано книг: {downloaded}, добавлено книг: {added}, обновлено книг: {updated}.", "SynchronizationSuccessful": "Синхронизация выполнена успешно.", "SynchronizationCancelled": "Синхронизация была прервана пользователем.", - "SynchronizationError": "Синхронизация завершилась с ошибками." + "SynchronizationError": "Синхронизация завершилась с ошибками: {error}" } }, "Library": @@ -607,7 +609,7 @@ "DownloadIncompleteError": "Загрузка завершена не полностью.", "FileWriteError": "Ошибка записи файла на диск.", "LogLineRequestError": "Не удалось отправить запрос по адресу {url}", - "UnexpectedError": "Возникла непредвиденная ошибка." + "UnexpectedError": "Возникла непредвиденная ошибка: {error}" } }, "ApplicationUpdate": diff --git a/LibgenDesktop/Resources/Languages/Spanish.lng b/LibgenDesktop/Resources/Languages/Spanish.lng index 313e1bb..7341bec 100644 --- a/LibgenDesktop/Resources/Languages/Spanish.lng +++ b/LibgenDesktop/Resources/Languages/Spanish.lng @@ -500,7 +500,7 @@ "SynchronizationProgressAddedAndUpdated": "Libros descargados: {downloaded}, añadidos: {added}, actualizados: {updated}.", "SynchronizationSuccessful": "Sincronización completada satisfactoriamente.", "SynchronizationCancelled": "La sincronización ha sido cancelada por el usuario.", - "SynchronizationError": "Ocurrió un error duante la sincronización." + "SynchronizationError": "Ocurrió un error duante la sincronización: {error}" } }, "Library": @@ -594,7 +594,7 @@ "ServerResponseTimeout": "El servidor respondió con un tiempo de espera agotado.", "DownloadIncompleteError": "El servidor indica que la descarga está completa, pero no es así. Reintente la descarga", "FileWriteError": "Error de escritura del archivo.", - "UnexpectedError": "Ocurrió un error inesperado." + "UnexpectedError": "Ocurrió un error inesperado: {error}" } }, "ApplicationUpdate": diff --git a/LibgenDesktop/Resources/Languages/Ukrainian.lng b/LibgenDesktop/Resources/Languages/Ukrainian.lng index dd4827e..df6699f 100644 --- a/LibgenDesktop/Resources/Languages/Ukrainian.lng +++ b/LibgenDesktop/Resources/Languages/Ukrainian.lng @@ -495,7 +495,7 @@ "SynchronizationProgressAddedAndUpdated": " Завантажено книг: {downloaded}, додано книг: {added}, оновлено книг: {updated}.", "SynchronizationSuccessful": "Синхронізація виконана успішно.", "SynchronizationCancelled": "Синхронізація була перервана користувачем.", - "SynchronizationError": "Синхронізація завершилася з помилками." + "SynchronizationError": "Синхронізація завершилася з помилками: {error}" } }, "Database": @@ -570,7 +570,7 @@ "ServerResponseTimeout": "Ви перевищили час очікування відповіді від сервера.", "DownloadIncompleteError": "Завантаження завершено в повному обсязі.", "FileWriteError": "Не можу записати файлу на диск.", - "UnexpectedError": "Виникла непередбачена помилка." + "UnexpectedError": "Виникла непередбачена помилка: {error}" } }, "ApplicationUpdate": diff --git a/LibgenDesktop/ViewModels/SearchResultItems/FictionSearchResultItemViewModel.cs b/LibgenDesktop/ViewModels/SearchResultItems/FictionSearchResultItemViewModel.cs index 86e3038..eb1fcd6 100644 --- a/LibgenDesktop/ViewModels/SearchResultItems/FictionSearchResultItemViewModel.cs +++ b/LibgenDesktop/ViewModels/SearchResultItems/FictionSearchResultItemViewModel.cs @@ -21,6 +21,10 @@ public FictionSearchResultItemViewModel(FictionBook book, LanguageFormatter form public string FileSize => Formatter.FileSizeToString(Book.SizeInBytes, false); public long SizeInBytes => Book.SizeInBytes; + public override string FileNameWithoutExtension => $"{Authors} - {Title}"; + public override string FileExtension => Format; + public override string Md5Hash => Book.Md5Hash; + protected override void UpdateLocalizableProperties() { NotifyPropertyChanged(nameof(FileSize)); diff --git a/LibgenDesktop/ViewModels/SearchResultItems/NonFictionSearchResultItemViewModel.cs b/LibgenDesktop/ViewModels/SearchResultItems/NonFictionSearchResultItemViewModel.cs index d97c258..791222f 100644 --- a/LibgenDesktop/ViewModels/SearchResultItems/NonFictionSearchResultItemViewModel.cs +++ b/LibgenDesktop/ViewModels/SearchResultItems/NonFictionSearchResultItemViewModel.cs @@ -21,6 +21,10 @@ public NonFictionSearchResultItemViewModel(NonFictionBook book, LanguageFormatte public long SizeInBytes => Book.SizeInBytes; public bool Ocr => Book.Searchable == "1"; + public override string FileNameWithoutExtension => $"{Authors} - {Title}"; + public override string FileExtension => Format; + public override string Md5Hash => Book.Md5Hash; + protected override void UpdateLocalizableProperties() { NotifyPropertyChanged(nameof(FileSize)); diff --git a/LibgenDesktop/ViewModels/SearchResultItems/SciMagSearchResultItemViewModel.cs b/LibgenDesktop/ViewModels/SearchResultItems/SciMagSearchResultItemViewModel.cs index cda2687..4486068 100644 --- a/LibgenDesktop/ViewModels/SearchResultItems/SciMagSearchResultItemViewModel.cs +++ b/LibgenDesktop/ViewModels/SearchResultItems/SciMagSearchResultItemViewModel.cs @@ -19,6 +19,10 @@ public SciMagSearchResultItemViewModel(SciMagArticle article, LanguageFormatter public long SizeInBytes => Article.SizeInBytes; public string Doi => Article.DoiString; + public override string FileNameWithoutExtension => $"{Authors} - {Title}"; + public override string FileExtension => "pdf"; + public override string Md5Hash => Article.Md5Hash; + protected override void UpdateLocalizableProperties() { NotifyPropertyChanged(nameof(FileSize)); diff --git a/LibgenDesktop/ViewModels/SearchResultItems/SearchResultItemViewModel.cs b/LibgenDesktop/ViewModels/SearchResultItems/SearchResultItemViewModel.cs index 447738c..be9537b 100644 --- a/LibgenDesktop/ViewModels/SearchResultItems/SearchResultItemViewModel.cs +++ b/LibgenDesktop/ViewModels/SearchResultItems/SearchResultItemViewModel.cs @@ -11,9 +11,13 @@ public SearchResultItemViewModel(T libgenObject, LanguageFormatter formatter) Formatter = formatter; } + public T LibgenObject { get; } public bool ExistsInLibrary => LibgenObject.FileId.HasValue; - protected T LibgenObject { get; } + public abstract string FileNameWithoutExtension { get; } + public abstract string FileExtension { get; } + public abstract string Md5Hash { get; } + protected LanguageFormatter Formatter { get; private set; } public void UpdateLocalization(LanguageFormatter newFormatter) diff --git a/LibgenDesktop/ViewModels/Tabs/DetailsTabViewModel.cs b/LibgenDesktop/ViewModels/Tabs/DetailsTabViewModel.cs index 5b55780..9f7ada1 100644 --- a/LibgenDesktop/ViewModels/Tabs/DetailsTabViewModel.cs +++ b/LibgenDesktop/ViewModels/Tabs/DetailsTabViewModel.cs @@ -350,8 +350,16 @@ private void MainActionButtonClick() if (MainModel.AppSettings.Download.UseDownloadManager) { Mirrors.MirrorConfiguration mirror = MainModel.Mirrors[downloadMirrorName]; - MainModel.Downloader.EnqueueDownloadItem(downloadUrl, FileNameWithoutExtension, FileExtension.ToLower(), Md5Hash, - GetDownloadTransformations(mirror), mirror.RestartSessionOnTimeout); + DownloadItemRequest downloadItemRequest = new DownloadItemRequest + { + DownloadPageUrl = downloadUrl, + FileNameWithoutExtension = FileNameWithoutExtension, + FileExtension = FileExtension.ToLower(), + Md5Hash = Md5Hash, + DownloadTransformations = GetDownloadTransformations(mirror), + RestartSessionOnTimeout = mirror.RestartSessionOnTimeout + }; + MainModel.Downloader.EnqueueDownloadItem(downloadItemRequest); } else { diff --git a/LibgenDesktop/ViewModels/Tabs/FictionSearchResultsTabViewModel.cs b/LibgenDesktop/ViewModels/Tabs/FictionSearchResultsTabViewModel.cs index d0308f8..c237579 100644 --- a/LibgenDesktop/ViewModels/Tabs/FictionSearchResultsTabViewModel.cs +++ b/LibgenDesktop/ViewModels/Tabs/FictionSearchResultsTabViewModel.cs @@ -18,7 +18,7 @@ namespace LibgenDesktop.ViewModels.Tabs { - internal class FictionSearchResultsTabViewModel : SearchResultsTabViewModel + internal class FictionSearchResultsTabViewModel : SearchResultsTabViewModel { private readonly FictionColumnSettings columnSettings; private ObservableCollection books; @@ -34,8 +34,6 @@ public FictionSearchResultsTabViewModel(MainModel mainModel, IWindowContext pare LanguageFormatter formatter = MainModel.Localization.CurrentLanguage.Formatter; books = new ObservableCollection(searchResults.Select(book => new FictionSearchResultItemViewModel(book, formatter))); - OpenDetailsCommand = new Command(param => OpenDetails((param as FictionSearchResultItemViewModel)?.Book)); - BookDataGridEnterKeyCommand = new Command(BookDataGridEnterKeyPressed); Initialize(); } @@ -187,15 +185,6 @@ public string BookCount } } - public FictionSearchResultItemViewModel SelectedBook { get; set; } - - public Command OpenDetailsCommand { get; } - public Command BookDataGridEnterKeyCommand { get; } - - protected override string FileNameWithoutExtension => $"{SelectedBook.Book.Authors} - {SelectedBook.Book.Title}"; - protected override string FileExtension => SelectedBook.Book.Format; - protected override string Md5Hash => SelectedBook.Book.Md5Hash; - public event EventHandler OpenFictionDetailsRequested; protected override SearchResultsTabLocalizator GetLocalization() @@ -203,11 +192,6 @@ protected override SearchResultsTabLocalizator GetLocalization() return localization; } - protected override LibgenObject GetSelectedLibgenObject() - { - return SelectedBook.Book; - } - protected override async Task SearchAsync(string searchQuery, CancellationToken cancellationToken) { IsSearchResultsGridVisible = false; @@ -243,14 +227,19 @@ protected override void UpdateLocalization(Language newLanguage) } } + protected override void OpenDetails(FictionBook book) + { + OpenFictionDetailsRequested?.Invoke(this, new OpenFictionDetailsEventArgs(book)); + } + protected override string GetDownloadMirrorName() { return MainModel.AppSettings.Mirrors.FictionBooksMirrorName; } - protected override string GenerateDownloadUrl(Mirrors.MirrorConfiguration mirrorConfiguration) + protected override string GenerateDownloadUrl(Mirrors.MirrorConfiguration mirrorConfiguration, FictionBook book) { - return UrlGenerator.GetFictionDownloadUrl(mirrorConfiguration, SelectedBook?.Book); + return UrlGenerator.GetFictionDownloadUrl(mirrorConfiguration, book); } protected override string GetDownloadTransformations(Mirrors.MirrorConfiguration mirrorConfiguration) @@ -269,16 +258,6 @@ private void UpdateBookCount() BookCount = Localization.GetStatusBarText(Books.Count); } - private void BookDataGridEnterKeyPressed() - { - OpenDetails(SelectedBook.Book); - } - - private void OpenDetails(FictionBook book) - { - OpenFictionDetailsRequested?.Invoke(this, new OpenFictionDetailsEventArgs(book)); - } - private void HandleSearchProgress(SearchProgress searchProgress) { UpdateSearchProgressStatus(searchProgress.ItemsFound); diff --git a/LibgenDesktop/ViewModels/Tabs/ISearchResultsTabViewModel.cs b/LibgenDesktop/ViewModels/Tabs/ISearchResultsTabViewModel.cs new file mode 100644 index 0000000..80d44b8 --- /dev/null +++ b/LibgenDesktop/ViewModels/Tabs/ISearchResultsTabViewModel.cs @@ -0,0 +1,8 @@ +namespace LibgenDesktop.ViewModels.Tabs +{ + internal interface ISearchResultsTabViewModel : ITabViewModel + { + void Search(string searchQuery); + void ShowExportPanel(); + } +} diff --git a/LibgenDesktop/ViewModels/Tabs/ITabViewModel.cs b/LibgenDesktop/ViewModels/Tabs/ITabViewModel.cs new file mode 100644 index 0000000..963b3d1 --- /dev/null +++ b/LibgenDesktop/ViewModels/Tabs/ITabViewModel.cs @@ -0,0 +1,9 @@ +namespace LibgenDesktop.ViewModels.Tabs +{ + internal interface ITabViewModel + { + string Title { get; set; } + + void HandleTabClosing(); + } +} \ No newline at end of file diff --git a/LibgenDesktop/ViewModels/Tabs/NonFictionSearchResultsTabViewModel.cs b/LibgenDesktop/ViewModels/Tabs/NonFictionSearchResultsTabViewModel.cs index 3b1aed3..e85010a 100644 --- a/LibgenDesktop/ViewModels/Tabs/NonFictionSearchResultsTabViewModel.cs +++ b/LibgenDesktop/ViewModels/Tabs/NonFictionSearchResultsTabViewModel.cs @@ -18,7 +18,7 @@ namespace LibgenDesktop.ViewModels.Tabs { - internal class NonFictionSearchResultsTabViewModel : SearchResultsTabViewModel + internal class NonFictionSearchResultsTabViewModel : SearchResultsTabViewModel { private readonly NonFictionColumnSettings columnSettings; private ObservableCollection books; @@ -34,8 +34,6 @@ public NonFictionSearchResultsTabViewModel(MainModel mainModel, IWindowContext p LanguageFormatter formatter = MainModel.Localization.CurrentLanguage.Formatter; books = new ObservableCollection(searchResults.Select(book => new NonFictionSearchResultItemViewModel(book, formatter))); - OpenDetailsCommand = new Command(param => OpenDetails((param as NonFictionSearchResultItemViewModel)?.Book)); - BookDataGridEnterKeyCommand = new Command(BookDataGridEnterKeyPressed); Initialize(); } @@ -199,15 +197,6 @@ public string BookCount } } - public NonFictionSearchResultItemViewModel SelectedBook { get; set; } - - public Command OpenDetailsCommand { get; } - public Command BookDataGridEnterKeyCommand { get; } - - protected override string FileNameWithoutExtension => $"{SelectedBook.Book.Authors} - {SelectedBook.Book.Title}"; - protected override string FileExtension => SelectedBook.Book.Format; - protected override string Md5Hash => SelectedBook.Book.Md5Hash; - public event EventHandler OpenNonFictionDetailsRequested; protected override SearchResultsTabLocalizator GetLocalization() @@ -215,11 +204,6 @@ protected override SearchResultsTabLocalizator GetLocalization() return localization; } - protected override LibgenObject GetSelectedLibgenObject() - { - return SelectedBook?.Book; - } - protected override async Task SearchAsync(string searchQuery, CancellationToken cancellationToken) { IsSearchResultsGridVisible = false; @@ -255,14 +239,19 @@ protected override void UpdateLocalization(Language newLanguage) } } + protected override void OpenDetails(NonFictionBook book) + { + OpenNonFictionDetailsRequested?.Invoke(this, new OpenNonFictionDetailsEventArgs(book)); + } + protected override string GetDownloadMirrorName() { return MainModel.AppSettings.Mirrors.NonFictionBooksMirrorName; } - protected override string GenerateDownloadUrl(Mirrors.MirrorConfiguration mirrorConfiguration) + protected override string GenerateDownloadUrl(Mirrors.MirrorConfiguration mirrorConfiguration, NonFictionBook book) { - return UrlGenerator.GetNonFictionDownloadUrl(mirrorConfiguration, SelectedBook?.Book); + return UrlGenerator.GetNonFictionDownloadUrl(mirrorConfiguration, book); } protected override string GetDownloadTransformations(Mirrors.MirrorConfiguration mirrorConfiguration) @@ -281,16 +270,6 @@ private void UpdateBookCount() BookCount = Localization.GetStatusBarText(Books.Count); } - private void BookDataGridEnterKeyPressed() - { - OpenDetails(SelectedBook.Book); - } - - private void OpenDetails(NonFictionBook book) - { - OpenNonFictionDetailsRequested?.Invoke(this, new OpenNonFictionDetailsEventArgs(book)); - } - private void HandleSearchProgress(SearchProgress searchProgress) { UpdateSearchProgressStatus(searchProgress.ItemsFound); diff --git a/LibgenDesktop/ViewModels/Tabs/SciMagSearchResultsTabViewModel.cs b/LibgenDesktop/ViewModels/Tabs/SciMagSearchResultsTabViewModel.cs index 05b4191..8a05958 100644 --- a/LibgenDesktop/ViewModels/Tabs/SciMagSearchResultsTabViewModel.cs +++ b/LibgenDesktop/ViewModels/Tabs/SciMagSearchResultsTabViewModel.cs @@ -18,7 +18,7 @@ namespace LibgenDesktop.ViewModels.Tabs { - internal class SciMagSearchResultsTabViewModel : SearchResultsTabViewModel + internal class SciMagSearchResultsTabViewModel : SearchResultsTabViewModel { private readonly SciMagColumnSettings columnSettings; private ObservableCollection articles; @@ -34,8 +34,6 @@ public SciMagSearchResultsTabViewModel(MainModel mainModel, IWindowContext paren LanguageFormatter formatter = MainModel.Localization.CurrentLanguage.Formatter; articles = new ObservableCollection(searchResults.Select(article => new SciMagSearchResultItemViewModel(article, formatter))); - OpenDetailsCommand = new Command(param => OpenDetails((param as SciMagSearchResultItemViewModel)?.Article)); - ArticleDataGridEnterKeyCommand = new Command(ArticleDataGridEnterKeyPressed); Initialize(); } @@ -175,15 +173,6 @@ public string ArticleCount } } - public SciMagSearchResultItemViewModel SelectedArticle { get; set; } - - public Command OpenDetailsCommand { get; } - public Command ArticleDataGridEnterKeyCommand { get; } - - protected override string FileNameWithoutExtension => $"{SelectedArticle.Article.Authors} - {SelectedArticle.Article.Title}"; - protected override string FileExtension => "pdf"; - protected override string Md5Hash => SelectedArticle.Article.Md5Hash; - public event EventHandler OpenSciMagDetailsRequested; protected override SearchResultsTabLocalizator GetLocalization() @@ -191,11 +180,6 @@ protected override SearchResultsTabLocalizator GetLocalization() return localization; } - protected override LibgenObject GetSelectedLibgenObject() - { - return SelectedArticle.Article; - } - protected override async Task SearchAsync(string searchQuery, CancellationToken cancellationToken) { IsSearchResultsGridVisible = false; @@ -231,14 +215,19 @@ protected override void UpdateLocalization(Language newLanguage) } } + protected override void OpenDetails(SciMagArticle article) + { + OpenSciMagDetailsRequested?.Invoke(this, new OpenSciMagDetailsEventArgs(article)); + } + protected override string GetDownloadMirrorName() { return MainModel.AppSettings.Mirrors.ArticlesMirrorName; } - protected override string GenerateDownloadUrl(Mirrors.MirrorConfiguration mirrorConfiguration) + protected override string GenerateDownloadUrl(Mirrors.MirrorConfiguration mirrorConfiguration, SciMagArticle article) { - return UrlGenerator.GetSciMagDownloadUrl(mirrorConfiguration, SelectedArticle.Article); + return UrlGenerator.GetSciMagDownloadUrl(mirrorConfiguration, article); } protected override string GetDownloadTransformations(Mirrors.MirrorConfiguration mirrorConfiguration) @@ -257,16 +246,6 @@ private void UpdateArticleCount() ArticleCount = Localization.GetStatusBarText(Articles.Count); } - private void ArticleDataGridEnterKeyPressed() - { - OpenDetails(SelectedArticle.Article); - } - - private void OpenDetails(SciMagArticle article) - { - OpenSciMagDetailsRequested?.Invoke(this, new OpenSciMagDetailsEventArgs(article)); - } - private void HandleSearchProgress(SearchProgress searchProgress) { UpdateSearchProgressStatus(searchProgress.ItemsFound); diff --git a/LibgenDesktop/ViewModels/Tabs/SearchResultsTabViewModel.cs b/LibgenDesktop/ViewModels/Tabs/SearchResultsTabViewModel.cs index d386d61..6590f54 100644 --- a/LibgenDesktop/ViewModels/Tabs/SearchResultsTabViewModel.cs +++ b/LibgenDesktop/ViewModels/Tabs/SearchResultsTabViewModel.cs @@ -1,19 +1,25 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; +using LibgenDesktop.Common; using LibgenDesktop.Infrastructure; using LibgenDesktop.Models; +using LibgenDesktop.Models.Download; using LibgenDesktop.Models.Entities; using LibgenDesktop.Models.Localization; using LibgenDesktop.Models.Localization.Localizators; using LibgenDesktop.Models.Settings; using LibgenDesktop.ViewModels.Panels; +using LibgenDesktop.ViewModels.SearchResultItems; namespace LibgenDesktop.ViewModels.Tabs { - internal abstract class SearchResultsTabViewModel : TabViewModel + internal abstract class SearchResultsTabViewModel : TabViewModel, ISearchResultsTabViewModel where T : LibgenObject { private readonly LibgenObjectType libgenObjectType; private string searchQuery; @@ -26,6 +32,7 @@ internal abstract class SearchResultsTabViewModel : TabViewModel private bool isSearchResultsGridVisible; private bool isStatusBarVisible; private CancellationTokenSource searchCancellationTokenSource; + private IList selectedRows; protected SearchResultsTabViewModel(MainModel mainModel, IWindowContext parentWindowContext, LibgenObjectType libgenObjectType, string searchQuery) : base(mainModel, parentWindowContext, searchQuery) @@ -43,6 +50,7 @@ protected SearchResultsTabViewModel(MainModel mainModel, IWindowContext parentWi SearchCommand = new Command(Search); InterruptSearchCommand = new Command(InterruptSearch); ToggleBookmarkCommand = new Command(ToggleBookmark); + OpenDetailsCommand = new Command(OpenDetails); OpenFileCommand = new Command(OpenFile); DownloadCommand = new Command(Download); ExportCommand = new Command(ShowExportPanel); @@ -50,6 +58,8 @@ protected SearchResultsTabViewModel(MainModel mainModel, IWindowContext parentWi Events.RaiseEvent(ViewModelEvent.RegisteredEventId.FOCUS_SEARCH_TEXT_BOX); } + public ExportPanelViewModel ExportPanelViewModel { get; } + public string SearchQuery { get @@ -158,19 +168,58 @@ public bool IsStatusBarVisible } } - public ExportPanelViewModel ExportPanelViewModel { get; } + public IList SelectedRows + { + get + { + return selectedRows; + } + set + { + selectedRows = value; + NotifyPropertyChanged(nameof(ShowOpenDetailsMenuItem)); + NotifyPropertyChanged(nameof(ShowOpenFileMenuItem)); + NotifyPropertyChanged(nameof(ShowDownloadMenuItem)); + } + } + + public bool ShowOpenDetailsMenuItem + { + get + { + return SelectedRows.Count == 1; + } + } + + public bool ShowOpenFileMenuItem + { + get + { + if (SelectedRows.Count != 1) + { + return false; + } + return SelectedItem?.ExistsInLibrary == true; + } + } + + public bool ShowDownloadMenuItem + { + get + { + return (SelectedRows.Count == 1 && SelectedItem?.ExistsInLibrary == false) || SelectedRows.Count > 1; + } + } public Command SearchCommand { get; } public Command InterruptSearchCommand { get; } public Command ToggleBookmarkCommand { get; } public Command ExportCommand { get; } + public Command BookDataGridEnterKeyCommand { get; } + public Command OpenDetailsCommand { get; } public Command OpenFileCommand { get; } public Command DownloadCommand { get; } - protected abstract string FileNameWithoutExtension { get; } - protected abstract string FileExtension { get; } - protected abstract string Md5Hash { get; } - private SearchResultsTabLocalizator Localization { get @@ -179,6 +228,22 @@ private SearchResultsTabLocalizator Localization } } + private IEnumerable> SelectedItems + { + get + { + return SelectedRows.OfType>(); + } + } + + private SearchResultItemViewModel SelectedItem + { + get + { + return SelectedItems.FirstOrDefault(); + } + } + public void Search(string searchQuery) { SearchQuery = searchQuery; @@ -202,10 +267,10 @@ public override void HandleTabClosing() } protected abstract SearchResultsTabLocalizator GetLocalization(); - protected abstract LibgenObject GetSelectedLibgenObject(); protected abstract Task SearchAsync(string searchQuery, CancellationToken cancellationToken); + protected abstract void OpenDetails(T libgenObject); protected abstract string GetDownloadMirrorName(); - protected abstract string GenerateDownloadUrl(Mirrors.MirrorConfiguration mirrorConfiguration); + protected abstract string GenerateDownloadUrl(Mirrors.MirrorConfiguration mirrorConfiguration, T libgenObject); protected abstract string GetDownloadTransformations(Mirrors.MirrorConfiguration mirrorConfiguration); protected virtual void UpdateLocalization(Language newLanguage) @@ -237,9 +302,24 @@ private void InterruptSearch() IsInterruptButtonEnabled = false; } + private void OpenDetails() + { + SearchResultItemViewModel selectedItem = SelectedItem; + if (selectedItem == null) + { + return; + } + OpenDetails(selectedItem.LibgenObject); + } + private async void OpenFile() { - LibgenObject selectedLibgenObject = GetSelectedLibgenObject(); + SearchResultItemViewModel selectedItem = SelectedItem; + if (selectedItem == null) + { + return; + } + LibgenObject selectedLibgenObject = selectedItem.LibgenObject; if (selectedLibgenObject == null || !selectedLibgenObject.FileId.HasValue) { return; @@ -258,11 +338,6 @@ private async void OpenFile() private void Download() { - LibgenObject selectedLibgenObject = GetSelectedLibgenObject(); - if (selectedLibgenObject == null || selectedLibgenObject.FileId.HasValue) - { - return; - } if (MainModel.AppSettings.Network.OfflineMode) { ShowMessage(Localization.OfflineModeIsOnMessageTitle, Localization.OfflineModeIsOnMessageText); @@ -274,16 +349,35 @@ private void Download() ShowMessage(Localization.ErrorMessageTitle, Localization.NoDownloadMirrorError); return; } - string downloadUrl = GenerateDownloadUrl(MainModel.Mirrors[downloadMirrorName]); - if (MainModel.AppSettings.Download.UseDownloadManager) + List> itemsToDownload = SelectedItems.Where(selectedItem => !selectedItem.ExistsInLibrary).ToList(); + if (itemsToDownload.Count < Constants.LARGE_NUMBER_OF_ITEMS_TO_DOWNLOAD_WARNING_THRESHOLD || + ShowPrompt(Localization.LargeNumberOfItemsToDownloadPromptTitle, + Localization.GetLargeNumberOfItemsToDownloadPromptText(itemsToDownload.Count))) { Mirrors.MirrorConfiguration mirror = MainModel.Mirrors[downloadMirrorName]; - MainModel.Downloader.EnqueueDownloadItem(downloadUrl, FileNameWithoutExtension, FileExtension.ToLower(), Md5Hash, - GetDownloadTransformations(mirror), mirror.RestartSessionOnTimeout); - } - else - { - Process.Start(downloadUrl); + if (MainModel.AppSettings.Download.UseDownloadManager) + { + string downloadTransformations = GetDownloadTransformations(mirror); + bool restartSessionOnTimeout = mirror.RestartSessionOnTimeout; + List downloadItemRequests = itemsToDownload.Select(itemToDownload => + new DownloadItemRequest + { + DownloadPageUrl = GenerateDownloadUrl(mirror, itemToDownload.LibgenObject), + FileNameWithoutExtension = itemToDownload.FileNameWithoutExtension, + FileExtension = itemToDownload.FileExtension.ToLower(), + Md5Hash = itemToDownload.Md5Hash, + DownloadTransformations = downloadTransformations, + RestartSessionOnTimeout = restartSessionOnTimeout + }).ToList(); + MainModel.Downloader.EnqueueDownloadItems(downloadItemRequests); + } + else + { + foreach (SearchResultItemViewModel itemToDownload in itemsToDownload) + { + Process.Start(GenerateDownloadUrl(mirror, itemToDownload.LibgenObject)); + } + } } } diff --git a/LibgenDesktop/ViewModels/Tabs/TabViewModel.cs b/LibgenDesktop/ViewModels/Tabs/TabViewModel.cs index 7f1e7e7..ed0345a 100644 --- a/LibgenDesktop/ViewModels/Tabs/TabViewModel.cs +++ b/LibgenDesktop/ViewModels/Tabs/TabViewModel.cs @@ -5,7 +5,7 @@ namespace LibgenDesktop.ViewModels.Tabs { - internal abstract class TabViewModel : ContainerViewModel + internal abstract class TabViewModel : ContainerViewModel, ITabViewModel { private readonly SynchronizationContext synchronizationContext; private string title; diff --git a/LibgenDesktop/ViewModels/Windows/MainWindowViewModel.cs b/LibgenDesktop/ViewModels/Windows/MainWindowViewModel.cs index a614f0b..39aeba4 100644 --- a/LibgenDesktop/ViewModels/Windows/MainWindowViewModel.cs +++ b/LibgenDesktop/ViewModels/Windows/MainWindowViewModel.cs @@ -22,7 +22,7 @@ internal class MainWindowViewModel : LibgenWindowViewModel { private MainWindowLocalizator localization; private SearchTabViewModel defaultSearchTabViewModel; - private TabViewModel selectedTabViewModel; + private ITabViewModel selectedTabViewModel; private bool isDownloadManagerButtonHighlighted; private bool isCompletedDownloadCounterVisible; private int completedDownloadCount; @@ -48,7 +48,7 @@ public MainWindowViewModel(MainModel mainModel) SettingsCommand = new Command(SettingsMenuItemClick); AboutCommand = new Command(AboutMenuItemClick); WindowClosedCommand = new Command(WindowClosed); - TabViewModels = new ObservableCollection(); + TabViewModels = new ObservableCollection(); Initialize(); mainModel.ApplicationUpdateCheckCompleted += ApplicationUpdateCheckCompleted; mainModel.BookmarksChanged += BookmarksChanged; @@ -62,7 +62,7 @@ public MainWindowViewModel(MainModel mainModel) public int WindowTop { get; set; } public bool IsWindowMaximized { get; set; } public EventProvider Events { get; } - public ObservableCollection TabViewModels { get; } + public ObservableCollection TabViewModels { get; } public MainWindowLocalizator Localization { @@ -89,7 +89,7 @@ public SearchTabViewModel DefaultSearchTabViewModel } } - public TabViewModel SelectedTabViewModel + public ITabViewModel SelectedTabViewModel { get { @@ -469,13 +469,13 @@ private void CloseCurrentTab() private void Export() { - if (SelectedTabViewModel is SearchResultsTabViewModel searchResultsTabViewModel) + if (SelectedTabViewModel is ISearchResultsTabViewModel searchResultsTabViewModel) { searchResultsTabViewModel.ShowExportPanel(); } } - private void CloseTab(TabViewModel tabViewModel) + private void CloseTab(ITabViewModel tabViewModel) { tabViewModel.HandleTabClosing(); switch (tabViewModel) @@ -701,11 +701,11 @@ private void BookmarksMenuItemClick(BookmarkViewModel bookmarkViewModel) } if (useCurrentTab) { - (SelectedTabViewModel as SearchResultsTabViewModel).Search(bookmarkViewModel.SearchQuery); + (SelectedTabViewModel as ISearchResultsTabViewModel).Search(bookmarkViewModel.SearchQuery); } else { - SearchResultsTabViewModel newTab; + ISearchResultsTabViewModel newTab; switch (bookmarkViewModel.LibgenObjectType) { case LibgenObjectType.NON_FICTION_BOOK: diff --git a/LibgenDesktop/ViewModels/Windows/SynchronizationWindowViewModel.cs b/LibgenDesktop/ViewModels/Windows/SynchronizationWindowViewModel.cs index 5294e05..ea93ce7 100644 --- a/LibgenDesktop/ViewModels/Windows/SynchronizationWindowViewModel.cs +++ b/LibgenDesktop/ViewModels/Windows/SynchronizationWindowViewModel.cs @@ -1,10 +1,12 @@ using System; +using System.Net.Sockets; using System.Text; using System.Threading; using LibgenDesktop.Infrastructure; using LibgenDesktop.Models; using LibgenDesktop.Models.Localization.Localizators; using LibgenDesktop.Models.ProgressArgs; +using LibgenDesktop.Models.Utils; using LibgenDesktop.ViewModels.Panels; namespace LibgenDesktop.ViewModels.Windows @@ -183,13 +185,17 @@ private async void Syncrhonize() } catch (Exception exception) { + exception = exception.GetInnermostException(); elapsedTimer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); - Logs.ShowErrorLogLine(Localization.LogLineSynchronizationError); + Logs.ShowErrorLogLine(Localization.GetLogLineSynchronizationError(exception.Message)); IsInProgress = false; Status = Localization.StatusSynchronizationError; IsCancelButtonVisible = false; IsCloseButtonVisible = true; - ShowErrorWindow(exception, CurrentWindowContext); + if (!(exception is TimeoutException) && !(exception is SocketException)) + { + ShowErrorWindow(exception, CurrentWindowContext); + } return; } elapsedTimer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); diff --git a/LibgenDesktop/Views/Controls/BookDataGrid.cs b/LibgenDesktop/Views/Controls/BookDataGrid.cs new file mode 100644 index 0000000..b0272c2 --- /dev/null +++ b/LibgenDesktop/Views/Controls/BookDataGrid.cs @@ -0,0 +1,29 @@ +using System.Collections; +using System.Windows; +using System.Windows.Controls; + +namespace LibgenDesktop.Views.Controls +{ + public class BookDataGrid : DataGrid + { + public static readonly DependencyProperty SelectedRowsProperty = DependencyProperty.Register("SelectedRows", typeof(IList), typeof(BookDataGrid)); + + public IList SelectedRows + { + get + { + return (IList)GetValue(SelectedRowsProperty); + } + set + { + SetValue(SelectedRowsProperty, value); + } + } + + protected override void OnSelectionChanged(SelectionChangedEventArgs e) + { + base.OnSelectionChanged(e); + SelectedRows = SelectedItems; + } + } +} diff --git a/LibgenDesktop/Views/Controls/BookDataGridContextMenu.xaml b/LibgenDesktop/Views/Controls/BookDataGridContextMenu.xaml index 5082220..d811492 100644 --- a/LibgenDesktop/Views/Controls/BookDataGridContextMenu.xaml +++ b/LibgenDesktop/Views/Controls/BookDataGridContextMenu.xaml @@ -4,12 +4,15 @@ DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"> + Visibility="{Binding DataContext.ShowOpenDetailsMenuItem, Converter={StaticResource booleanToCollapsedConverter}, + RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}}" + Command="{Binding DataContext.OpenDetailsCommand, RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}}" /> diff --git a/LibgenDesktop/Views/Controls/ImportLogPanel.xaml b/LibgenDesktop/Views/Controls/ImportLogPanel.xaml index ec81349..12331a5 100644 --- a/LibgenDesktop/Views/Controls/ImportLogPanel.xaml +++ b/LibgenDesktop/Views/Controls/ImportLogPanel.xaml @@ -27,9 +27,9 @@ + Style="{StaticResource ResultLogLine}" /> + Style="{StaticResource ErrorLogLine}" /> diff --git a/LibgenDesktop/Views/Styles/ImportLogPanelStyles.xaml b/LibgenDesktop/Views/Styles/ImportLogPanelStyles.xaml index d4afe73..e57b0d2 100644 --- a/LibgenDesktop/Views/Styles/ImportLogPanelStyles.xaml +++ b/LibgenDesktop/Views/Styles/ImportLogPanelStyles.xaml @@ -22,9 +22,11 @@ \ No newline at end of file diff --git a/LibgenDesktop/Views/Styles/TabStyles.xaml b/LibgenDesktop/Views/Styles/TabStyles.xaml index 5356124..50a0aba 100644 --- a/LibgenDesktop/Views/Styles/TabStyles.xaml +++ b/LibgenDesktop/Views/Styles/TabStyles.xaml @@ -113,7 +113,7 @@ - + diff --git a/LibgenDesktop/Views/Tabs/FictionSearchResultsTab.xaml b/LibgenDesktop/Views/Tabs/FictionSearchResultsTab.xaml index 0e21d29..d15d143 100644 --- a/LibgenDesktop/Views/Tabs/FictionSearchResultsTab.xaml +++ b/LibgenDesktop/Views/Tabs/FictionSearchResultsTab.xaml @@ -55,19 +55,22 @@