diff --git a/App.xaml b/App.xaml index 9afe297..fe7f884 100644 --- a/App.xaml +++ b/App.xaml @@ -9,7 +9,9 @@ + + diff --git a/App.xaml.cs b/App.xaml.cs index 06fc145..0c57ac0 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -1,5 +1,7 @@ using System.Windows; using LibgenDesktop.Infrastructure; +using LibgenDesktop.Models; +using LibgenDesktop.ViewModels; namespace LibgenDesktop { @@ -8,9 +10,38 @@ public partial class App : Application protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); - IWindowContext mainWindowContext = WindowManager.CreateWindow(RegisteredWindows.WindowKey.MAIN_WINDOW); - mainWindowContext.Closed += (sender, args) => Shutdown(); - mainWindowContext.Show(); + MainModel mainModel = new MainModel(); + if (mainModel.LocalDatabaseStatus == MainModel.DatabaseStatus.OPENED) + { + ShowMainWindow(mainModel); + } + else + { + ShowCreateDatabaseWindow(mainModel); + } + } + + private void ShowMainWindow(MainModel mainModel) + { + MainWindowViewModel mainWindowViewModel = new MainWindowViewModel(mainModel); + IWindowContext windowContext = WindowManager.CreateWindow(RegisteredWindows.WindowKey.MAIN_WINDOW, mainWindowViewModel); + windowContext.Closed += (sender, args) => Shutdown(); + windowContext.Show(); + } + + private void ShowCreateDatabaseWindow(MainModel mainModel) + { + CreateDatabaseViewModel createDatabaseViewModel = new CreateDatabaseViewModel(mainModel); + IWindowContext windowContext = WindowManager.CreateWindow(RegisteredWindows.WindowKey.CREATE_DATABASE_WINDOW, createDatabaseViewModel); + bool? result = windowContext.ShowDialog(); + if (result == true) + { + ShowMainWindow(mainModel); + } + else + { + Shutdown(); + } } } } diff --git a/Common/Constants.cs b/Common/Constants.cs index cfe6484..60e2168 100644 --- a/Common/Constants.cs +++ b/Common/Constants.cs @@ -2,39 +2,88 @@ { internal static class Constants { + public const string CURRENT_DATABASE_VERSION = "0.7"; + public const int MAIN_WINDOW_MIN_WIDTH = 1000; public const int MAIN_WINDOW_MIN_HEIGHT = 500; - public const int BOOK_WINDOW_MIN_WIDTH = 1000; - public const int BOOK_WINDOW_MIN_HEIGHT = 500; + public const int NON_FICTION_DETAILS_WINDOW_MIN_WIDTH = 1000; + public const int NON_FICTION_DETAILS_WINDOW_MIN_HEIGHT = 500; + public const int NON_FICTION_GRID_TITLE_COLUMN_MIN_WIDTH = 150; + public const int NON_FICTION_GRID_AUTHORS_COLUMN_MIN_WIDTH = 150; + public const int NON_FICTION_GRID_SERIES_COLUMN_MIN_WIDTH = 150; + public const int NON_FICTION_GRID_YEAR_COLUMN_MIN_WIDTH = 60; + public const int NON_FICTION_GRID_PUBLISHER_COLUMN_MIN_WIDTH = 150; + public const int NON_FICTION_GRID_FORMAT_COLUMN_MIN_WIDTH = 80; + public const int NON_FICTION_GRID_FILESIZE_COLUMN_MIN_WIDTH = 130; + public const int NON_FICTION_GRID_OCR_COLUMN_MIN_WIDTH = 55; + public const int FICTION_DETAILS_WINDOW_MIN_WIDTH = 600; + public const int FICTION_DETAILS_WINDOW_MIN_HEIGHT = 500; + public const int FICTION_GRID_TITLE_COLUMN_MIN_WIDTH = 150; + public const int FICTION_GRID_AUTHORS_COLUMN_MIN_WIDTH = 150; + public const int FICTION_GRID_SERIES_COLUMN_MIN_WIDTH = 150; + public const int FICTION_GRID_YEAR_COLUMN_MIN_WIDTH = 60; + public const int FICTION_GRID_PUBLISHER_COLUMN_MIN_WIDTH = 150; + public const int FICTION_GRID_FORMAT_COLUMN_MIN_WIDTH = 80; + public const int FICTION_GRID_FILESIZE_COLUMN_MIN_WIDTH = 130; + public const int SCI_MAG_DETAILS_WINDOW_MIN_WIDTH = 600; + public const int SCI_MAG_DETAILS_WINDOW_MIN_HEIGHT = 500; + public const int SCI_MAG_GRID_TITLE_COLUMN_MIN_WIDTH = 150; + public const int SCI_MAG_GRID_AUTHORS_COLUMN_MIN_WIDTH = 150; + public const int SCI_MAG_GRID_JOURNAL_COLUMN_MIN_WIDTH = 100; + public const int SCI_MAG_GRID_YEAR_COLUMN_MIN_WIDTH = 60; + public const int SCI_MAG_GRID_FILESIZE_COLUMN_MIN_WIDTH = 130; + public const int SCI_MAG_GRID_DOI_COLUMN_MIN_WIDTH = 150; + public const int ERROR_WINDOW_DEFAULT_WIDTH = 620; + public const int ERROR_WINDOW_DEFAULT_HEIGHT = 450; public const int ERROR_WINDOW_MIN_WIDTH = 400; public const int ERROR_WINDOW_MIN_HEIGHT = 300; - public const int TITLE_COLUMN_MIN_WIDTH = 150; - public const int AUTHORS_COLUMN_MIN_WIDTH = 150; - public const int SERIES_COLUMN_MIN_WIDTH = 150; - public const int YEAR_COLUMN_MIN_WIDTH = 60; - public const int PUBLISHER_COLUMN_MIN_WIDTH = 150; - public const int FORMAT_COLUMN_MIN_WIDTH = 80; - public const int FILESIZE_COLUMN_MIN_WIDTH = 130; - public const int OCR_COLUMN_MIN_WIDTH = 55; + public const int IMPORT_WINDOW_MIN_WIDTH = 500; + public const int IMPORT_WINDOW_MIN_HEIGHT = 400; + public const int CREATE_DATABASE_WINDOW_WIDTH = 500; + public const int SETTINGS_WINDOW_DEFAULT_WIDTH = 700; + public const int SETTINGS_WINDOW_DEFAULT_HEIGHT = 450; + public const int SETTINGS_WINDOW_MIN_WIDTH = 650; + public const int SETTINGS_WINDOW_MIN_HEIGHT = 450; public const string DEFAULT_DATABASE_FILE_NAME = "libgen.db"; - public const int DEFAULT_MAIN_WINDOW_WIDTH = 1200; + public const int DEFAULT_MAIN_WINDOW_WIDTH = 1000; public const int DEFAULT_MAIN_WINDOW_HEIGHT = 650; - public const int DEFAULT_BOOK_WINDOW_WIDTH = 1200; - public const int DEFAULT_BOOK_WINDOW_HEIGHT = 618; - public const int DEFAULT_TITLE_COLUMN_WIDTH = 200; - public const int DEFAULT_AUTHORS_COLUMN_WIDTH = 200; - public const int DEFAULT_SERIES_COLUMN_WIDTH = 180; - public const int DEFAULT_YEAR_COLUMN_WIDTH = 60; - public const int DEFAULT_PUBLISHER_COLUMN_WIDTH = 180; - public const int DEFAULT_FORMAT_COLUMN_WIDTH = 100; - public const int DEFAULT_FILESIZE_COLUMN_WIDTH = 150; - public const int DEFAULT_OCR_COLUMN_WIDTH = 55; + public const int DEFAULT_NON_FICTION_DETAILS_WINDOW_WIDTH = 1200; + public const int DEFAULT_NON_FICTION_DETAILS_WINDOW_HEIGHT = 618; + public const int DEFAULT_NON_FICTION_GRID_TITLE_COLUMN_WIDTH = 200; + public const int DEFAULT_NON_FICTION_GRID_AUTHORS_COLUMN_WIDTH = 200; + public const int DEFAULT_NON_FICTION_GRID_SERIES_COLUMN_WIDTH = 180; + public const int DEFAULT_NON_FICTION_GRID_YEAR_COLUMN_WIDTH = 60; + public const int DEFAULT_NON_FICTION_GRID_PUBLISHER_COLUMN_WIDTH = 180; + public const int DEFAULT_NON_FICTION_GRID_FORMAT_COLUMN_WIDTH = 100; + public const int DEFAULT_NON_FICTION_GRID_FILESIZE_COLUMN_WIDTH = 150; + public const int DEFAULT_NON_FICTION_GRID_OCR_COLUMN_WIDTH = 55; + public const int DEFAULT_FICTION_DETAILS_WINDOW_WIDTH = 850; + public const int DEFAULT_FICTION_DETAILS_WINDOW_HEIGHT = 650; + public const int DEFAULT_FICTION_GRID_TITLE_COLUMN_WIDTH = 200; + public const int DEFAULT_FICTION_GRID_AUTHORS_COLUMN_WIDTH = 200; + public const int DEFAULT_FICTION_GRID_SERIES_COLUMN_WIDTH = 180; + public const int DEFAULT_FICTION_GRID_YEAR_COLUMN_WIDTH = 60; + public const int DEFAULT_FICTION_GRID_PUBLISHER_COLUMN_WIDTH = 180; + public const int DEFAULT_FICTION_GRID_FORMAT_COLUMN_WIDTH = 100; + public const int DEFAULT_FICTION_GRID_FILESIZE_COLUMN_WIDTH = 150; + public const int DEFAULT_SCI_MAG_DETAILS_WINDOW_WIDTH = 910; + public const int DEFAULT_SCI_MAG_DETAILS_WINDOW_HEIGHT = 680; + public const int DEFAULT_SCI_MAG_GRID_TITLE_COLUMN_WIDTH = 270; + public const int DEFAULT_SCI_MAG_GRID_AUTHORS_COLUMN_WIDTH = 200; + public const int DEFAULT_SCI_MAG_GRID_JOURNAL_COLUMN_WIDTH = 120; + public const int DEFAULT_SCI_MAG_GRID_YEAR_COLUMN_WIDTH = 60; + public const int DEFAULT_SCI_MAG_GRID_FILESIZE_COLUMN_WIDTH = 150; + public const int DEFAULT_SCI_MAG_GRID_DOI_COLUMN_WIDTH = 290; + public const int DEFAULT_MAXIMUM_SEARCH_RESULT_COUNT = 50000; public const int SEARCH_REPORT_PROGRESS_BATCH_SIZE = 2000; public const int INSERT_TRANSACTION_BATCH = 500; - public const string BOOK_COVER_URL_PREFIX = "http://libgen.io/covers/"; - public const string BOOK_DOWNLOAD_URL_PREFIX = "http://libgen.io/book/index.php?md5="; + public const string NON_FICTION_COVER_URL_PREFIX = "http://libgen.io/covers/"; + public const string NON_FICTION_DOWNLOAD_URL_PREFIX = "http://libgen.io/book/index.php?md5="; + public const string FICTION_COVER_URL_PREFIX = "http://libgen.io/fictioncovers/"; + public const string FICTION_DOWNLOAD_URL_PREFIX = "http://libgen.io/foreignfiction/ads.php?md5="; + public const string SCI_MAG_DOWNLOAD_URL_PREFIX = "http://libgen.io/scimag/ads.php?doi="; } } diff --git a/Infrastructure/EventProvider.cs b/Infrastructure/EventProvider.cs new file mode 100644 index 0000000..ae3decb --- /dev/null +++ b/Infrastructure/EventProvider.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace LibgenDesktop.Infrastructure +{ + public class EventProvider + { + private readonly Queue eventQueue; + private IEventListener eventListener; + + public EventProvider() + { + eventQueue = new Queue(); + eventListener = null; + } + + public void SetEventListener(IEventListener eventListener) + { + this.eventListener = eventListener; + PassEvents(); + } + + public void RaiseEvent(ViewModelEvent viewModelEvent) + { + eventQueue.Enqueue(viewModelEvent); + PassEvents(); + } + + public void RaiseEvent(ViewModelEvent.RegisteredEventId eventId) + { + RaiseEvent(new ViewModelEvent(eventId)); + } + + private void PassEvents() + { + if (eventListener != null) + { + while (eventQueue.Any()) + { + ViewModelEvent viewModelEvent = eventQueue.Dequeue(); + eventListener.OnViewModelEvent(viewModelEvent); + } + } + } + } +} diff --git a/Infrastructure/IEventListener.cs b/Infrastructure/IEventListener.cs new file mode 100644 index 0000000..66a3488 --- /dev/null +++ b/Infrastructure/IEventListener.cs @@ -0,0 +1,7 @@ +namespace LibgenDesktop.Infrastructure +{ + public interface IEventListener + { + void OnViewModelEvent(ViewModelEvent viewModelEvent); + } +} diff --git a/Infrastructure/OpenFileDialogResult.cs b/Infrastructure/OpenFileDialogResult.cs index 849f3b7..dd67356 100644 --- a/Infrastructure/OpenFileDialogResult.cs +++ b/Infrastructure/OpenFileDialogResult.cs @@ -2,7 +2,7 @@ namespace LibgenDesktop.Infrastructure { - public class OpenFileDialogResult + internal class OpenFileDialogResult { public bool DialogResult { get; set; } public List SelectedFilePaths { get; set; } diff --git a/Infrastructure/RegisteredWindows.cs b/Infrastructure/RegisteredWindows.cs index 5090bf1..6292e8d 100644 --- a/Infrastructure/RegisteredWindows.cs +++ b/Infrastructure/RegisteredWindows.cs @@ -10,9 +10,13 @@ internal static class RegisteredWindows internal enum WindowKey { MAIN_WINDOW = 1, - BOOK_DETAILS_WINDOW, + NON_FICTION_DETAILS_WINDOW, + FICTION_DETAILS_WINDOW, + SCI_MAG_DETAILS_WINDOW, ERROR_WINDOW, - SQL_DUMP_IMPORT_WINDOW + IMPORT_WINDOW, + CREATE_DATABASE_WINDOW, + SETTINGS_WINDOW } internal class RegisteredWindow @@ -31,15 +35,22 @@ public RegisteredWindow(WindowKey windowKey, Type windowType, Type viewModelType static RegisteredWindows() { - AllWindows = new Dictionary - { - { WindowKey.MAIN_WINDOW, new RegisteredWindow(WindowKey.MAIN_WINDOW, typeof(MainWindow), typeof(MainWindowViewModel)) }, - { WindowKey.BOOK_DETAILS_WINDOW, new RegisteredWindow(WindowKey.BOOK_DETAILS_WINDOW, typeof(BookDetailsWindow), typeof(BookDetailsWindowViewModel)) }, - { WindowKey.ERROR_WINDOW, new RegisteredWindow(WindowKey.ERROR_WINDOW, typeof(ErrorWindow), typeof(ErrorWindowViewModel)) }, - { WindowKey.SQL_DUMP_IMPORT_WINDOW, new RegisteredWindow(WindowKey.SQL_DUMP_IMPORT_WINDOW, typeof(SqlDumpImportWindow), typeof(SqlDumpImportWindowViewModel)) } - }; + AllWindows = new Dictionary(); + RegisterWindow(WindowKey.MAIN_WINDOW, typeof(MainWindow), typeof(MainWindowViewModel)); + RegisterWindow(WindowKey.NON_FICTION_DETAILS_WINDOW, typeof(NonFictionDetailsWindow), typeof(NonFictionDetailsWindowViewModel)); + RegisterWindow(WindowKey.FICTION_DETAILS_WINDOW, typeof(FictionDetailsWindow), typeof(FictionDetailsWindowViewModel)); + RegisterWindow(WindowKey.SCI_MAG_DETAILS_WINDOW, typeof(SciMagDetailsWindow), typeof(SciMagDetailsWindowViewModel)); + RegisterWindow(WindowKey.ERROR_WINDOW, typeof(ErrorWindow), typeof(ErrorWindowViewModel)); + RegisterWindow(WindowKey.IMPORT_WINDOW, typeof(ImportWindow), typeof(ImportWindowViewModel)); + RegisterWindow(WindowKey.CREATE_DATABASE_WINDOW, typeof(CreateDatabaseWindow), typeof(CreateDatabaseViewModel)); + RegisterWindow(WindowKey.SETTINGS_WINDOW, typeof(SettingsWindow), typeof(SettingsWindowViewModel)); } public static Dictionary AllWindows { get; } + + private static void RegisterWindow(WindowKey windowKey, Type windowType, Type viewModelType) + { + AllWindows.Add(windowKey, new RegisteredWindow(windowKey, windowType, viewModelType)); + } } } diff --git a/Infrastructure/SaveFileDialogParameters.cs b/Infrastructure/SaveFileDialogParameters.cs new file mode 100644 index 0000000..591a6c5 --- /dev/null +++ b/Infrastructure/SaveFileDialogParameters.cs @@ -0,0 +1,11 @@ +namespace LibgenDesktop.Infrastructure +{ + internal class SaveFileDialogParameters + { + public string DialogTitle { get; set; } + public string Filter { get; set; } + public bool OverwritePrompt { get; set; } + public string InitialDirectory { get; set; } + public string InitialFileName { get; set; } + } +} diff --git a/Infrastructure/SaveFileDialogResult.cs b/Infrastructure/SaveFileDialogResult.cs new file mode 100644 index 0000000..d86733d --- /dev/null +++ b/Infrastructure/SaveFileDialogResult.cs @@ -0,0 +1,8 @@ +namespace LibgenDesktop.Infrastructure +{ + internal class SaveFileDialogResult + { + public bool DialogResult { get; set; } + public string SelectedFilePath { get; set; } + } +} diff --git a/Infrastructure/ViewModelEvent.cs b/Infrastructure/ViewModelEvent.cs new file mode 100644 index 0000000..3d62a1d --- /dev/null +++ b/Infrastructure/ViewModelEvent.cs @@ -0,0 +1,17 @@ +namespace LibgenDesktop.Infrastructure +{ + public class ViewModelEvent + { + public enum RegisteredEventId + { + FOCUS_SEARCH_TEXT_BOX = 1 + } + + public ViewModelEvent(RegisteredEventId eventId) + { + EventId = eventId; + } + + public RegisteredEventId EventId { get; } + } +} diff --git a/Infrastructure/WindowContext.cs b/Infrastructure/WindowContext.cs index 16c56e6..326e0db 100644 --- a/Infrastructure/WindowContext.cs +++ b/Infrastructure/WindowContext.cs @@ -64,7 +64,11 @@ public void Show(bool showMaximized = false) Window.Owner = parentWindow; Window.WindowStartupLocation = WindowStartupLocation.CenterOwner; } - return ShowDialog(showMaximized, true); + else + { + Window.WindowStartupLocation = WindowStartupLocation.CenterScreen; + } + return ShowDialog(showMaximized, parentWindow == null); } public void Close() @@ -102,10 +106,9 @@ protected virtual void OnClosed() Closed?.Invoke(this, EventArgs.Empty); } - private bool? ShowDialog(bool showMaximized, bool hasOwner) + private bool? ShowDialog(bool showMaximized, bool showInTaskbar) { - Window.WindowStartupLocation = hasOwner ? WindowStartupLocation.CenterOwner : WindowStartupLocation.CenterScreen; - Window.ShowInTaskbar = false; + Window.ShowInTaskbar = showInTaskbar; Window.WindowState = GetWindowState(showMaximized); return Window.ShowDialog(); } diff --git a/Infrastructure/WindowManager.cs b/Infrastructure/WindowManager.cs index 19ea27a..258060a 100644 --- a/Infrastructure/WindowManager.cs +++ b/Infrastructure/WindowManager.cs @@ -80,16 +80,21 @@ public static IWindowContext CreateWindow(RegisteredWindows.WindowKey windowKey, return result; } - public static IWindowContext GetCreatedWindowContext(object viewModel) + public static IWindowContext GetWindowContext(object viewModel) { return createdWindowContexts.FirstOrDefault(windowContext => ReferenceEquals(windowContext.DataContext, viewModel)); } + public static void ShowMessageBox(string title, string text) + { + MessageBox.Show(text, title); + } + public static OpenFileDialogResult ShowOpenFileDialog(OpenFileDialogParameters openFileDialogParameters) { if (openFileDialogParameters == null) { - throw new ArgumentNullException("openFileDialogParameters"); + throw new ArgumentNullException(nameof(openFileDialogParameters)); } OpenFileDialog openFileDialog = new OpenFileDialog(); if (!String.IsNullOrEmpty(openFileDialogParameters.DialogTitle)) @@ -111,6 +116,36 @@ public static OpenFileDialogResult ShowOpenFileDialog(OpenFileDialogParameters o return result; } + public static SaveFileDialogResult ShowSaveFileDialog(SaveFileDialogParameters saveFileDialogParameters) + { + if (saveFileDialogParameters == null) + { + throw new ArgumentNullException(nameof(saveFileDialogParameters)); + } + SaveFileDialog saveFileDialog = new SaveFileDialog(); + if (!String.IsNullOrEmpty(saveFileDialogParameters.DialogTitle)) + { + saveFileDialog.Title = saveFileDialogParameters.DialogTitle; + } + if (!String.IsNullOrEmpty(saveFileDialogParameters.Filter)) + { + saveFileDialog.Filter = saveFileDialogParameters.Filter; + } + saveFileDialog.OverwritePrompt = saveFileDialogParameters.OverwritePrompt; + if (!String.IsNullOrEmpty(saveFileDialogParameters.InitialDirectory)) + { + saveFileDialog.InitialDirectory = saveFileDialogParameters.InitialDirectory; + } + if (!String.IsNullOrEmpty(saveFileDialogParameters.InitialFileName)) + { + saveFileDialog.FileName = saveFileDialogParameters.InitialFileName; + } + SaveFileDialogResult result = new SaveFileDialogResult(); + result.DialogResult = saveFileDialog.ShowDialog() == true; + result.SelectedFilePath = result.DialogResult ? saveFileDialog.FileName : null; + return result; + } + public static void RemoveWindowMaximizeButton(Window window) { RemoveWindowStyle(window, WS_MAXIMIZEBOX); @@ -131,18 +166,18 @@ public static void RemoveWindowIcon(Window window) SetWindowPos(windowHandle, IntPtr.Zero, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); } - private static void WindowContext_Closed(object sender, EventArgs e) - { - WindowContext closedWindowContext = (WindowContext)sender; - createdWindowContexts.Remove(closedWindowContext); - closedWindowContext.Closed -= WindowContext_Closed; - } - private static void RemoveWindowStyle(Window window, int styleAttribute) { IntPtr windowHandle = new WindowInteropHelper(window).Handle; int windowStyle = GetWindowLong(windowHandle, GWL_STYLE); SetWindowLong(windowHandle, GWL_STYLE, windowStyle & ~styleAttribute); } + + private static void WindowContext_Closed(object sender, EventArgs e) + { + WindowContext closedWindowContext = (WindowContext)sender; + createdWindowContexts.Remove(closedWindowContext); + closedWindowContext.Closed -= WindowContext_Closed; + } } } diff --git a/LibgenDesktop.csproj b/LibgenDesktop.csproj index b611561..9be2ad1 100644 --- a/LibgenDesktop.csproj +++ b/LibgenDesktop.csproj @@ -42,6 +42,9 @@ packages\Costura.Fody.1.6.2\lib\dotnet\Costura.dll False + + packages\Dragablz.0.0.3.197\lib\net45\Dragablz.dll + packages\MaterialDesignColors.1.1.2\lib\net45\MaterialDesignColors.dll @@ -51,14 +54,16 @@ packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll - - packages\SharpCompress.0.18.2\lib\net45\SharpCompress.dll + + packages\SharpCompress.0.19.2\lib\net45\SharpCompress.dll packages\System.Data.SQLite.Core.1.0.106.0\lib\net45\System.Data.SQLite.dll + + @@ -78,39 +83,126 @@ Designer + + + + + - - + + + + + + + + + + - + + + + + + + + - - - BookDetailsWindow.xaml + + + + + + + + + AddTabButton.xaml + + + CloseTabButton.xaml + + + + SciMagDetailsWindow.xaml + + + FictionDetailsWindow.xaml + + + NonFictionDetailsWindow.xaml BookAttributeValueLabel.xaml + + + PressedSwitch.xaml + + + SettingsTab.xaml + + + Toolbar.xaml + + + CreateDatabaseWindow.xaml + ErrorWindow.xaml - - SqlDumpImportWindow.xaml + + ImportWindow.xaml + + + SciMagSearchResultsTab.xaml + + + FictionSearchResultsTab.xaml + + + NonFictionSearchResultsTab.xaml + + + DownloadManagerTab.xaml + + + SearchTab.xaml + + + SettingsWindow.xaml - + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + Designer MSBuild:Compile @@ -118,10 +210,30 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + Designer MSBuild:Compile + + Designer + MSBuild:Compile + MSBuild:Compile Designer @@ -132,9 +244,8 @@ - + - @@ -146,11 +257,63 @@ MainWindow.xaml Code - + + MSBuild:Compile Designer + + MSBuild:Compile + Designer - + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + Designer MSBuild:Compile @@ -162,11 +325,27 @@ Designer MSBuild:Compile + + MSBuild:Compile + Designer + Designer MSBuild:Compile - + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + Designer MSBuild:Compile @@ -212,7 +391,9 @@ - + + + diff --git a/Models/Database/LocalDatabase.cs b/Models/Database/LocalDatabase.cs index 353fa38..0f9f7de 100644 --- a/Models/Database/LocalDatabase.cs +++ b/Models/Database/LocalDatabase.cs @@ -4,195 +4,150 @@ using System.Data.SQLite; using System.Globalization; using System.IO; +using System.Runtime.CompilerServices; using LibgenDesktop.Models.Entities; namespace LibgenDesktop.Models.Database { - internal class LocalDatabase + internal class LocalDatabase : IDisposable { - private readonly SQLiteConnection connection; + private SQLiteConnection connection; + private bool disposed; - public LocalDatabase(string databaseFileName) + private LocalDatabase(string databaseFilePath) { - if (!File.Exists(databaseFileName)) - { - SQLiteConnection.CreateFile(databaseFileName); - } - connection = new SQLiteConnection("Data Source = " + databaseFileName); + disposed = false; + connection = new SQLiteConnection("Data Source = " + databaseFilePath); connection.Open(); connection.EnableExtensions(true); connection.LoadExtension("SQLite.Interop.dll", "sqlite3_fts5_init"); - using (SQLiteCommand command = connection.CreateCommand()) - { - command.CommandText = SqlScripts.CREATE_BOOKS_TABLE; - command.ExecuteNonQuery(); - } - using (SQLiteCommand command = connection.CreateCommand()) + ExecuteCommands("PRAGMA LOCKING_MODE = EXCLUSIVE", "PRAGMA TEMP_STORE = MEMORY", "PRAGMA JOURNAL_MODE = OFF", + "PRAGMA SYNCHRONOUS = OFF"); + } + + public static LocalDatabase CreateDatabase(string databaseFilePath) + { + SQLiteConnection.CreateFile(databaseFilePath); + return new LocalDatabase(databaseFilePath); + } + + public static LocalDatabase OpenDatabase(string databaseFilePath) + { + if (!File.Exists(databaseFilePath)) { - command.CommandText = SqlScripts.CREATE_BOOKS_FTS_TABLE; - command.ExecuteNonQuery(); + throw new FileNotFoundException("Database file not found", databaseFilePath); } - ExecuteCommands("PRAGMA LOCKING_MODE = EXCLUSIVE"); + return new LocalDatabase(databaseFilePath); } - public int CountBooks() + public void Dispose() { - using (SQLiteCommand command = connection.CreateCommand()) + if (!disposed && connection != null) { - command.CommandText = SqlScripts.COUNT_BOOKS; - object objectResult = command.ExecuteScalar(); - return objectResult != DBNull.Value ? (int)(long)objectResult : 0; + connection.Dispose(); + connection = null; } + disposed = true; } - public IEnumerable GetAllBooks() + public void CreateMetadataTable() { + ExecuteCommands(SqlScripts.CREATE_METADATA_TABLE); + } + + public bool CheckIfMetadataExists() + { + return ExecuteScalarCommand(SqlScripts.CHECK_IF_METADATA_TABLE_EXIST) == 1; + } + + public DatabaseMetadata GetMetadata() + { + DatabaseMetadata result = new DatabaseMetadata(); using (SQLiteCommand command = connection.CreateCommand()) { - command.CommandText = SqlScripts.GET_ALL_BOOKS; + command.CommandText = SqlScripts.GET_ALL_METADATA_ITEMS; using (SQLiteDataReader dataReader = command.ExecuteReader()) { while (dataReader.Read()) { - Book book = new Book(); - book.Id = dataReader.GetInt32(0); - book.Title = dataReader.GetString(1); - book.Authors = dataReader.GetString(2); - book.Series = dataReader.GetString(3); - book.Year = dataReader.GetString(4); - book.Publisher = dataReader.GetString(5); - book.Format = dataReader.GetString(6); - book.SizeInBytes = dataReader.GetInt64(7); - book.Searchable = dataReader.GetString(8); - yield return book; + string key = dataReader.GetString(0); + string value = dataReader.GetString(1); + if (DatabaseMetadata.FieldDefinitions.TryGetValue(key.ToLower(), out DatabaseMetadata.FieldDefinition field)) + { + field.Setter(result, value); + } } } } + return result; } - public Book GetBookById(int id) + public void AddMetadata(DatabaseMetadata databaseMetadata) + { + foreach (DatabaseMetadata.FieldDefinition fieldDefinition in DatabaseMetadata.FieldDefinitions.Values) + { + using (SQLiteCommand command = connection.CreateCommand()) + { + command.CommandText = SqlScripts.INSERT_METADATA_ITEM; + command.Parameters.AddWithValue("Key", fieldDefinition.FieldName); + command.Parameters.AddWithValue("Value", fieldDefinition.Getter(databaseMetadata)); + command.ExecuteNonQuery(); + } + } + } + + public void CreateNonFictionTables() + { + ExecuteCommands(SqlScripts.CREATE_NON_FICTION_TABLE); + ExecuteCommands(SqlScripts.CREATE_NON_FICTION_FTS_TABLE); + } + + public int CountNonFictionBooks() + { + return ExecuteScalarCommand(SqlScripts.COUNT_NON_FICTION); + } + + public NonFictionBook GetNonFictionBookById(int id) { using (SQLiteCommand command = connection.CreateCommand()) { - command.CommandText = SqlScripts.GET_BOOK_BY_ID; + command.CommandText = SqlScripts.GET_NON_FICTION_BY_ID; command.Parameters.AddWithValue("@Id", id); using (SQLiteDataReader dataReader = command.ExecuteReader()) { dataReader.Read(); - Book book = new Book(); - book.ExtendedProperties = new Book.BookExtendedProperties(); - book.Id = dataReader.GetInt32(0); - book.Title = dataReader.GetString(1); - book.ExtendedProperties.VolumeInfo = dataReader.GetString(2); - book.Series = dataReader.GetString(3); - book.ExtendedProperties.Periodical = dataReader.GetString(4); - book.Authors = dataReader.GetString(5); - book.Year = dataReader.GetString(6); - book.ExtendedProperties.Edition = dataReader.GetString(7); - book.Publisher = dataReader.GetString(8); - book.ExtendedProperties.City = dataReader.GetString(9); - book.ExtendedProperties.Pages = dataReader.GetString(10); - book.ExtendedProperties.PagesInFile = dataReader.GetInt32(11); - book.ExtendedProperties.Language = dataReader.GetString(12); - book.ExtendedProperties.Topic = dataReader.GetString(13); - book.ExtendedProperties.Library = dataReader.GetString(14); - book.ExtendedProperties.Issue = dataReader.GetString(15); - book.ExtendedProperties.Identifier = dataReader.GetString(16); - book.ExtendedProperties.Issn = dataReader.GetString(17); - book.ExtendedProperties.Asin = dataReader.GetString(18); - book.ExtendedProperties.Udc = dataReader.GetString(19); - book.ExtendedProperties.Lbc = dataReader.GetString(20); - book.ExtendedProperties.Ddc = dataReader.GetString(21); - book.ExtendedProperties.Lcc = dataReader.GetString(22); - book.ExtendedProperties.Doi = dataReader.GetString(23); - book.ExtendedProperties.GoogleBookid = dataReader.GetString(24); - book.ExtendedProperties.OpenLibraryId = dataReader.GetString(25); - book.ExtendedProperties.Commentary = dataReader.GetString(26); - book.ExtendedProperties.Dpi = dataReader.GetInt32(27); - book.ExtendedProperties.Color = dataReader.GetString(28); - book.ExtendedProperties.Cleaned = dataReader.GetString(29); - book.ExtendedProperties.Orientation = dataReader.GetString(30); - book.ExtendedProperties.Paginated = dataReader.GetString(31); - book.ExtendedProperties.Scanned = dataReader.GetString(32); - book.ExtendedProperties.Bookmarked = dataReader.GetString(33); - book.Searchable = dataReader.GetString(34); - book.SizeInBytes = dataReader.GetInt64(35); - book.Format = dataReader.GetString(36); - book.ExtendedProperties.Md5Hash = dataReader.GetString(37); - book.ExtendedProperties.Generic = dataReader.GetString(38); - book.ExtendedProperties.Visible = dataReader.GetString(39); - book.ExtendedProperties.Locator = dataReader.GetString(40); - book.ExtendedProperties.Local = dataReader.GetInt32(41); - book.ExtendedProperties.AddedDateTime = DateTime.ParseExact(dataReader.GetString(42), "s", CultureInfo.InvariantCulture); - book.ExtendedProperties.LastModifiedDateTime = DateTime.ParseExact(dataReader.GetString(43), "s", CultureInfo.InvariantCulture); - book.ExtendedProperties.CoverUrl = dataReader.GetString(44); - book.ExtendedProperties.Tags = dataReader.GetString(45); - book.ExtendedProperties.IdentifierPlain = dataReader.GetString(46); - book.ExtendedProperties.LibgenId = dataReader.GetInt32(47); + NonFictionBook book = ReadNonFictionBook(dataReader); return book; } } } - public IEnumerable SearchBooks(string searchQuery) + public IEnumerable SearchNonFictionBooks(string searchQuery, int? resultLimit) { - List searchQueryBuilder = new List(); - foreach (string searchQueryPart in searchQuery.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)) - { - switch (searchQueryPart) - { - case "AND": - case "OR": - case "NOT": - searchQueryBuilder.Add(searchQueryPart); - continue; - default: - if (searchQueryPart.Length > 1 && searchQueryPart.EndsWith("*")) - { - searchQueryBuilder.Add($"\"{ searchQueryPart.Substring(0, searchQueryPart.Length - 1) }\"*"); - } - else - { - searchQueryBuilder.Add($"\"{ searchQueryPart }\""); - } - break; - } - } - searchQuery = String.Join(" ", searchQueryBuilder); + searchQuery = EscapeSearchQuery(searchQuery); using (SQLiteCommand command = connection.CreateCommand()) { - command.CommandText = SqlScripts.SEARCH_BOOKS; + command.CommandText = GetSearchCommandWithLimit(SqlScripts.SEARCH_NON_FICTION, resultLimit); command.Parameters.AddWithValue("@SearchQuery", searchQuery); using (SQLiteDataReader dataReader = command.ExecuteReader()) { while (dataReader.Read()) { - Book book = new Book(); - book.Id = dataReader.GetInt32(0); - book.Title = dataReader.GetString(1); - book.Authors = dataReader.GetString(2); - book.Series = dataReader.GetString(3); - book.Year = dataReader.GetString(4); - book.Publisher = dataReader.GetString(5); - book.Format = dataReader.GetString(6); - book.SizeInBytes = dataReader.GetInt64(7); - book.Searchable = dataReader.GetString(8); + NonFictionBook book = ReadNonFictionBook(dataReader); yield return book; } } } } - public void AddBooks(List books) + public void AddNonFictionBooks(List books) { - ExecuteCommands("PRAGMA TEMP_STORE = MEMORY", "PRAGMA JOURNAL_MODE = OFF", - "PRAGMA SYNCHRONOUS = OFF"); using (SQLiteTransaction transaction = connection.BeginTransaction()) { using (SQLiteCommand insertCommand = connection.CreateCommand()) using (SQLiteCommand insertFtsCommand = connection.CreateCommand()) { - insertCommand.CommandText = SqlScripts.INSERT_BOOK; + insertCommand.CommandText = SqlScripts.INSERT_NON_FICTION; insertCommand.Parameters.AddWithValue("@Id", null); SQLiteParameter titleParameter = insertCommand.Parameters.Add("@Title", DbType.String); SQLiteParameter volumeInfoParameter = insertCommand.Parameters.Add("@VolumeInfo", DbType.String); @@ -217,7 +172,7 @@ public void AddBooks(List books) SQLiteParameter ddcParameter = insertCommand.Parameters.Add("@Ddc", DbType.String); SQLiteParameter lccParameter = insertCommand.Parameters.Add("@Lcc", DbType.String); SQLiteParameter doiParameter = insertCommand.Parameters.Add("@Doi", DbType.String); - SQLiteParameter googleBookidParameter = insertCommand.Parameters.Add("@GoogleBookid", DbType.String); + SQLiteParameter googleBookIdParameter = insertCommand.Parameters.Add("@GoogleBookId", DbType.String); SQLiteParameter openLibraryIdParameter = insertCommand.Parameters.Add("@OpenLibraryId", DbType.String); SQLiteParameter commentaryParameter = insertCommand.Parameters.Add("@Commentary", DbType.String); SQLiteParameter dpiParameter = insertCommand.Parameters.Add("@Dpi", DbType.Int32); @@ -241,74 +196,611 @@ public void AddBooks(List books) SQLiteParameter tagsParameter = insertCommand.Parameters.Add("@Tags", DbType.String); SQLiteParameter identifierPlainParameter = insertCommand.Parameters.Add("@IdentifierPlain", DbType.String); SQLiteParameter libgenIdParameter = insertCommand.Parameters.Add("@LibgenId", DbType.Int32); - insertFtsCommand.CommandText = SqlScripts.INSERT_BOOK_FTS; + insertFtsCommand.CommandText = SqlScripts.INSERT_NON_FICTION_FTS; SQLiteParameter titleFtsParameter = insertFtsCommand.Parameters.Add("@Title", DbType.String); SQLiteParameter seriesFtsParameter = insertFtsCommand.Parameters.Add("@Series", DbType.String); SQLiteParameter authorsFtsParameter = insertFtsCommand.Parameters.Add("@Authors", DbType.String); SQLiteParameter publisherFtsParameter = insertFtsCommand.Parameters.Add("@Publisher", DbType.String); SQLiteParameter identifierPlainFtsParameter = insertFtsCommand.Parameters.Add("@IdentifierPlain", DbType.String); - foreach (Book book in books) + foreach (NonFictionBook book in books) { titleParameter.Value = book.Title; - volumeInfoParameter.Value = book.ExtendedProperties.VolumeInfo; + volumeInfoParameter.Value = book.VolumeInfo; seriesParameter.Value = book.Series; - periodicalParameter.Value = book.ExtendedProperties.Periodical; + periodicalParameter.Value = book.Periodical; authorsParameter.Value = book.Authors; yearParameter.Value = book.Year; - editionParameter.Value = book.ExtendedProperties.Edition; + editionParameter.Value = book.Edition; publisherParameter.Value = book.Publisher; - cityParameter.Value = book.ExtendedProperties.City; - pagesParameter.Value = book.ExtendedProperties.Pages; - pagesInFileParameter.Value = book.ExtendedProperties.PagesInFile; - languageParameter.Value = book.ExtendedProperties.Language; - topicParameter.Value = book.ExtendedProperties.Topic; - libraryParameter.Value = book.ExtendedProperties.Library; - issueParameter.Value = book.ExtendedProperties.Issue; - identifierParameter.Value = book.ExtendedProperties.Identifier; - issnParameter.Value = book.ExtendedProperties.Issn; - asinParameter.Value = book.ExtendedProperties.Asin; - udcParameter.Value = book.ExtendedProperties.Udc; - lbcParameter.Value = book.ExtendedProperties.Lbc; - ddcParameter.Value = book.ExtendedProperties.Ddc; - lccParameter.Value = book.ExtendedProperties.Lcc; - doiParameter.Value = book.ExtendedProperties.Doi; - googleBookidParameter.Value = book.ExtendedProperties.GoogleBookid; - openLibraryIdParameter.Value = book.ExtendedProperties.OpenLibraryId; - commentaryParameter.Value = book.ExtendedProperties.Commentary; - dpiParameter.Value = book.ExtendedProperties.Dpi; - colorParameter.Value = book.ExtendedProperties.Color; - cleanedParameter.Value = book.ExtendedProperties.Cleaned; - orientationParameter.Value = book.ExtendedProperties.Orientation; - paginatedParameter.Value = book.ExtendedProperties.Paginated; - scannedParameter.Value = book.ExtendedProperties.Scanned; - bookmarkedParameter.Value = book.ExtendedProperties.Bookmarked; + cityParameter.Value = book.City; + pagesParameter.Value = book.Pages; + pagesInFileParameter.Value = book.PagesInFile; + languageParameter.Value = book.Language; + topicParameter.Value = book.Topic; + libraryParameter.Value = book.Library; + issueParameter.Value = book.Issue; + identifierParameter.Value = book.Identifier; + issnParameter.Value = book.Issn; + asinParameter.Value = book.Asin; + udcParameter.Value = book.Udc; + lbcParameter.Value = book.Lbc; + ddcParameter.Value = book.Ddc; + lccParameter.Value = book.Lcc; + doiParameter.Value = book.Doi; + googleBookIdParameter.Value = book.GoogleBookId; + openLibraryIdParameter.Value = book.OpenLibraryId; + commentaryParameter.Value = book.Commentary; + dpiParameter.Value = book.Dpi; + colorParameter.Value = book.Color; + cleanedParameter.Value = book.Cleaned; + orientationParameter.Value = book.Orientation; + paginatedParameter.Value = book.Paginated; + scannedParameter.Value = book.Scanned; + bookmarkedParameter.Value = book.Bookmarked; searchableParameter.Value = book.Searchable; sizeInBytesParameter.Value = book.SizeInBytes; formatParameter.Value = book.Format; - md5HashParameter.Value = book.ExtendedProperties.Md5Hash; - genericParameter.Value = book.ExtendedProperties.Generic; - visibleParameter.Value = book.ExtendedProperties.Visible; - locatorParameter.Value = book.ExtendedProperties.Locator; - localParameter.Value = book.ExtendedProperties.Local; - addedDateTimeParameter.Value = book.ExtendedProperties.AddedDateTime.ToString("s"); - lastModifiedDateTimeParameter.Value = book.ExtendedProperties.LastModifiedDateTime.ToString("s"); - coverUrlParameter.Value = book.ExtendedProperties.CoverUrl; - tagsParameter.Value = book.ExtendedProperties.Tags; - identifierPlainParameter.Value = book.ExtendedProperties.IdentifierPlain; - libgenIdParameter.Value = book.ExtendedProperties.LibgenId; + md5HashParameter.Value = book.Md5Hash; + genericParameter.Value = book.Generic; + visibleParameter.Value = book.Visible; + locatorParameter.Value = book.Locator; + localParameter.Value = book.Local; + addedDateTimeParameter.Value = book.AddedDateTime.ToString("s"); + lastModifiedDateTimeParameter.Value = book.LastModifiedDateTime.ToString("s"); + coverUrlParameter.Value = book.CoverUrl; + tagsParameter.Value = book.Tags; + identifierPlainParameter.Value = book.IdentifierPlain; + libgenIdParameter.Value = book.LibgenId; insertCommand.ExecuteNonQuery(); titleFtsParameter.Value = book.Title; seriesFtsParameter.Value = book.Series; authorsFtsParameter.Value = book.Authors; publisherFtsParameter.Value = book.Publisher; - identifierPlainFtsParameter.Value = book.ExtendedProperties.IdentifierPlain; + identifierPlainFtsParameter.Value = book.IdentifierPlain; + insertFtsCommand.ExecuteNonQuery(); + } + } + transaction.Commit(); + } + } + + public void CreateFictionTables() + { + ExecuteCommands(SqlScripts.CREATE_FICTION_TABLE); + ExecuteCommands(SqlScripts.CREATE_FICTION_FTS_TABLE); + } + + public int CountFictionBooks() + { + return ExecuteScalarCommand(SqlScripts.COUNT_FICTION); + } + + public FictionBook GetFictionBookById(int id) + { + using (SQLiteCommand command = connection.CreateCommand()) + { + command.CommandText = SqlScripts.GET_FICTION_BY_ID; + command.Parameters.AddWithValue("@Id", id); + using (SQLiteDataReader dataReader = command.ExecuteReader()) + { + dataReader.Read(); + FictionBook book = ReadFictionBook(dataReader); + return book; + } + } + } + + public IEnumerable SearchFictionBooks(string searchQuery, int? resultLimit) + { + searchQuery = EscapeSearchQuery(searchQuery); + using (SQLiteCommand command = connection.CreateCommand()) + { + command.CommandText = GetSearchCommandWithLimit(SqlScripts.SEARCH_FICTION, resultLimit); + command.Parameters.AddWithValue("@SearchQuery", searchQuery); + using (SQLiteDataReader dataReader = command.ExecuteReader()) + { + while (dataReader.Read()) + { + FictionBook book = ReadFictionBook(dataReader); + yield return book; + } + } + } + } + + public void AddFictionBooks(List books) + { + using (SQLiteTransaction transaction = connection.BeginTransaction()) + { + using (SQLiteCommand insertCommand = connection.CreateCommand()) + using (SQLiteCommand insertFtsCommand = connection.CreateCommand()) + { + insertCommand.CommandText = SqlScripts.INSERT_FICTION; + insertCommand.Parameters.AddWithValue("@Id", null); + SQLiteParameter authorFamily1Parameter = insertCommand.Parameters.Add("@AuthorFamily1", DbType.String); + SQLiteParameter authorName1Parameter = insertCommand.Parameters.Add("@AuthorName1", DbType.String); + SQLiteParameter authorSurname1Parameter = insertCommand.Parameters.Add("@AuthorSurname1", DbType.String); + SQLiteParameter role1Parameter = insertCommand.Parameters.Add("@Role1", DbType.String); + SQLiteParameter pseudonim1Parameter = insertCommand.Parameters.Add("@Pseudonim1", DbType.String); + SQLiteParameter authorFamily2Parameter = insertCommand.Parameters.Add("@AuthorFamily2", DbType.String); + SQLiteParameter authorName2Parameter = insertCommand.Parameters.Add("@AuthorName2", DbType.String); + SQLiteParameter authorSurname2Parameter = insertCommand.Parameters.Add("@AuthorSurname2", DbType.String); + SQLiteParameter role2Parameter = insertCommand.Parameters.Add("@Role2", DbType.String); + SQLiteParameter pseudonim2Parameter = insertCommand.Parameters.Add("@Pseudonim2", DbType.String); + SQLiteParameter authorFamily3Parameter = insertCommand.Parameters.Add("@AuthorFamily3", DbType.String); + SQLiteParameter authorName3Parameter = insertCommand.Parameters.Add("@AuthorName3", DbType.String); + SQLiteParameter authorSurname3Parameter = insertCommand.Parameters.Add("@AuthorSurname3", DbType.String); + SQLiteParameter role3Parameter = insertCommand.Parameters.Add("@Role3", DbType.String); + SQLiteParameter pseudonim3Parameter = insertCommand.Parameters.Add("@Pseudonim3", DbType.String); + SQLiteParameter authorFamily4Parameter = insertCommand.Parameters.Add("@AuthorFamily4", DbType.String); + SQLiteParameter authorName4Parameter = insertCommand.Parameters.Add("@AuthorName4", DbType.String); + SQLiteParameter authorSurname4Parameter = insertCommand.Parameters.Add("@AuthorSurname4", DbType.String); + SQLiteParameter role4Parameter = insertCommand.Parameters.Add("@Role4", DbType.String); + SQLiteParameter pseudonim4Parameter = insertCommand.Parameters.Add("@Pseudonim4", DbType.String); + SQLiteParameter series1Parameter = insertCommand.Parameters.Add("@Series1", DbType.String); + SQLiteParameter series2Parameter = insertCommand.Parameters.Add("@Series2", DbType.String); + SQLiteParameter series3Parameter = insertCommand.Parameters.Add("@Series3", DbType.String); + SQLiteParameter series4Parameter = insertCommand.Parameters.Add("@Series4", DbType.String); + SQLiteParameter titleParameter = insertCommand.Parameters.Add("@Title", DbType.String); + SQLiteParameter formatParameter = insertCommand.Parameters.Add("@Format", DbType.String); + SQLiteParameter versionParameter = insertCommand.Parameters.Add("@Version", DbType.String); + SQLiteParameter sizeInBytesParameter = insertCommand.Parameters.Add("@SizeInBytes", DbType.Int64); + SQLiteParameter md5HashParameter = insertCommand.Parameters.Add("@Md5Hash", DbType.String); + SQLiteParameter pathParameter = insertCommand.Parameters.Add("@Path", DbType.String); + SQLiteParameter languageParameter = insertCommand.Parameters.Add("@Language", DbType.String); + SQLiteParameter pagesParameter = insertCommand.Parameters.Add("@Pages", DbType.String); + SQLiteParameter identifierParameter = insertCommand.Parameters.Add("@Identifier", DbType.String); + SQLiteParameter yearParameter = insertCommand.Parameters.Add("@Year", DbType.String); + SQLiteParameter publisherParameter = insertCommand.Parameters.Add("@Publisher", DbType.String); + SQLiteParameter editionParameter = insertCommand.Parameters.Add("@Edition", DbType.String); + SQLiteParameter commentaryParameter = insertCommand.Parameters.Add("@Commentary", DbType.String); + SQLiteParameter addedDateTimeParameter = insertCommand.Parameters.Add("@AddedDateTime", DbType.String); + SQLiteParameter lastModifiedDateTimeParameter = insertCommand.Parameters.Add("@LastModifiedDateTime", DbType.String); + SQLiteParameter russianAuthorFamilyParameter = insertCommand.Parameters.Add("@RussianAuthorFamily", DbType.String); + SQLiteParameter russianAuthorNameParameter = insertCommand.Parameters.Add("@RussianAuthorName", DbType.String); + SQLiteParameter russianAuthorSurnameParameter = insertCommand.Parameters.Add("@RussianAuthorSurname", DbType.String); + SQLiteParameter coverParameter = insertCommand.Parameters.Add("@Cover", DbType.String); + SQLiteParameter googleBookIdParameter = insertCommand.Parameters.Add("@GoogleBookId", DbType.String); + SQLiteParameter asinParameter = insertCommand.Parameters.Add("@Asin", DbType.String); + SQLiteParameter authorHashParameter = insertCommand.Parameters.Add("@AuthorHash", DbType.String); + SQLiteParameter titleHashParameter = insertCommand.Parameters.Add("@TitleHash", DbType.String); + SQLiteParameter visibleParameter = insertCommand.Parameters.Add("@Visible", DbType.String); + SQLiteParameter libgenIdParameter = insertCommand.Parameters.Add("@LibgenId", DbType.Int32); + insertFtsCommand.CommandText = SqlScripts.INSERT_FICTION_FTS; + SQLiteParameter titleFtsParameter = insertFtsCommand.Parameters.Add("@Title", DbType.String); + SQLiteParameter authorFamily1FtsParameter = insertFtsCommand.Parameters.Add("@AuthorFamily1", DbType.String); + SQLiteParameter authorName1FtsParameter = insertFtsCommand.Parameters.Add("@AuthorName1", DbType.String); + SQLiteParameter authorSurname1FtsParameter = insertFtsCommand.Parameters.Add("@AuthorSurname1", DbType.String); + SQLiteParameter pseudonim1FtsParameter = insertFtsCommand.Parameters.Add("@Pseudonim1", DbType.String); + SQLiteParameter authorFamily2FtsParameter = insertFtsCommand.Parameters.Add("@AuthorFamily2", DbType.String); + SQLiteParameter authorName2FtsParameter = insertFtsCommand.Parameters.Add("@AuthorName2", DbType.String); + SQLiteParameter authorSurname2FtsParameter = insertFtsCommand.Parameters.Add("@AuthorSurname2", DbType.String); + SQLiteParameter pseudonim2FtsParameter = insertFtsCommand.Parameters.Add("@Pseudonim2", DbType.String); + SQLiteParameter authorFamily3FtsParameter = insertFtsCommand.Parameters.Add("@AuthorFamily3", DbType.String); + SQLiteParameter authorName3FtsParameter = insertFtsCommand.Parameters.Add("@AuthorName3", DbType.String); + SQLiteParameter authorSurname3FtsParameter = insertFtsCommand.Parameters.Add("@AuthorSurname3", DbType.String); + SQLiteParameter pseudonim3FtsParameter = insertFtsCommand.Parameters.Add("@Pseudonim3", DbType.String); + SQLiteParameter authorFamily4FtsParameter = insertFtsCommand.Parameters.Add("@AuthorFamily4", DbType.String); + SQLiteParameter authorName4FtsParameter = insertFtsCommand.Parameters.Add("@AuthorName4", DbType.String); + SQLiteParameter authorSurname4FtsParameter = insertFtsCommand.Parameters.Add("@AuthorSurname4", DbType.String); + SQLiteParameter pseudonim4FtsParameter = insertFtsCommand.Parameters.Add("@Pseudonim4", DbType.String); + SQLiteParameter russianAuthorFamilyFtsParameter = insertFtsCommand.Parameters.Add("@RussianAuthorFamily", DbType.String); + SQLiteParameter russianAuthorNameFtsParameter = insertFtsCommand.Parameters.Add("@RussianAuthorName", DbType.String); + SQLiteParameter russianAuthorSurnameFtsParameter = insertFtsCommand.Parameters.Add("@RussianAuthorSurname", DbType.String); + SQLiteParameter series1FtsParameter = insertFtsCommand.Parameters.Add("@Series1", DbType.String); + SQLiteParameter series2FtsParameter = insertFtsCommand.Parameters.Add("@Series2", DbType.String); + SQLiteParameter series3FtsParameter = insertFtsCommand.Parameters.Add("@Series3", DbType.String); + SQLiteParameter series4FtsParameter = insertFtsCommand.Parameters.Add("@Series4", DbType.String); + SQLiteParameter publisherFtsParameter = insertFtsCommand.Parameters.Add("@Publisher", DbType.String); + SQLiteParameter identifierFtsParameter = insertFtsCommand.Parameters.Add("@Identifier", DbType.String); + foreach (FictionBook book in books) + { + authorFamily1Parameter.Value = book.AuthorFamily1; + authorName1Parameter.Value = book.AuthorName1; + authorSurname1Parameter.Value = book.AuthorSurname1; + role1Parameter.Value = book.Role1; + pseudonim1Parameter.Value = book.Pseudonim1; + authorFamily2Parameter.Value = book.AuthorFamily2; + authorName2Parameter.Value = book.AuthorName2; + authorSurname2Parameter.Value = book.AuthorSurname2; + role2Parameter.Value = book.Role2; + pseudonim2Parameter.Value = book.Pseudonim2; + authorFamily3Parameter.Value = book.AuthorFamily3; + authorName3Parameter.Value = book.AuthorName3; + authorSurname3Parameter.Value = book.AuthorSurname3; + role3Parameter.Value = book.Role3; + pseudonim3Parameter.Value = book.Pseudonim3; + authorFamily4Parameter.Value = book.AuthorFamily4; + authorName4Parameter.Value = book.AuthorName4; + authorSurname4Parameter.Value = book.AuthorSurname4; + role4Parameter.Value = book.Role4; + pseudonim4Parameter.Value = book.Pseudonim4; + series1Parameter.Value = book.Series1; + series2Parameter.Value = book.Series2; + series3Parameter.Value = book.Series3; + series4Parameter.Value = book.Series4; + titleParameter.Value = book.Title; + formatParameter.Value = book.Format; + versionParameter.Value = book.Version; + sizeInBytesParameter.Value = book.SizeInBytes; + md5HashParameter.Value = book.Md5Hash; + pathParameter.Value = book.Path; + languageParameter.Value = book.Language; + pagesParameter.Value = book.Pages; + identifierParameter.Value = book.Identifier; + yearParameter.Value = book.Year; + publisherParameter.Value = book.Publisher; + editionParameter.Value = book.Edition; + commentaryParameter.Value = book.Commentary; + addedDateTimeParameter.Value = book.AddedDateTime?.ToString("s"); + lastModifiedDateTimeParameter.Value = book.LastModifiedDateTime.ToString("s"); + russianAuthorFamilyParameter.Value = book.RussianAuthorFamily; + russianAuthorNameParameter.Value = book.RussianAuthorName; + russianAuthorSurnameParameter.Value = book.RussianAuthorSurname; + coverParameter.Value = book.Cover; + googleBookIdParameter.Value = book.GoogleBookId; + asinParameter.Value = book.Asin; + authorHashParameter.Value = book.AuthorHash; + titleHashParameter.Value = book.TitleHash; + visibleParameter.Value = book.Visible; + libgenIdParameter.Value = book.LibgenId; + insertCommand.ExecuteNonQuery(); + titleFtsParameter.Value = book.Title; + authorFamily1FtsParameter.Value = book.AuthorFamily1; + authorName1FtsParameter.Value = book.AuthorName1; + authorSurname1FtsParameter.Value = book.AuthorSurname1; + pseudonim1FtsParameter.Value = book.Pseudonim1; + authorFamily2FtsParameter.Value = book.AuthorFamily2; + authorName2FtsParameter.Value = book.AuthorName2; + authorSurname2FtsParameter.Value = book.AuthorSurname2; + pseudonim2FtsParameter.Value = book.Pseudonim2; + authorFamily3FtsParameter.Value = book.AuthorFamily3; + authorName3FtsParameter.Value = book.AuthorName3; + authorSurname3FtsParameter.Value = book.AuthorSurname3; + pseudonim3FtsParameter.Value = book.Pseudonim3; + authorFamily4FtsParameter.Value = book.AuthorFamily4; + authorName4FtsParameter.Value = book.AuthorName4; + authorSurname4FtsParameter.Value = book.AuthorSurname4; + pseudonim4FtsParameter.Value = book.Pseudonim4; + russianAuthorFamilyFtsParameter.Value = book.RussianAuthorFamily; + russianAuthorNameFtsParameter.Value = book.RussianAuthorName; + russianAuthorSurnameFtsParameter.Value = book.RussianAuthorSurname; + series1FtsParameter.Value = book.Series1; + series2FtsParameter.Value = book.Series2; + series3FtsParameter.Value = book.Series3; + series4FtsParameter.Value = book.Series4; + publisherFtsParameter.Value = book.Publisher; + identifierFtsParameter.Value = book.Identifier; insertFtsCommand.ExecuteNonQuery(); } } transaction.Commit(); } - ExecuteCommands("PRAGMA TEMP_STORE = DEFAULT", "PRAGMA JOURNAL_MODE = DELETE", - "PRAGMA SYNCHRONOUS = FULL"); + } + + public void CreateSciMagTables() + { + ExecuteCommands(SqlScripts.CREATE_SCIMAG_TABLE); + ExecuteCommands(SqlScripts.CREATE_SCIMAG_FTS_TABLE); + } + + public int CountSciMagArticles() + { + return ExecuteScalarCommand(SqlScripts.COUNT_SCIMAG); + } + + public SciMagArticle GetSciMagArticleById(int id) + { + using (SQLiteCommand command = connection.CreateCommand()) + { + command.CommandText = SqlScripts.GET_SCIMAG_BY_ID; + command.Parameters.AddWithValue("@Id", id); + using (SQLiteDataReader dataReader = command.ExecuteReader()) + { + dataReader.Read(); + SciMagArticle article = ReadSciMagArticle(dataReader); + return article; + } + } + } + + public IEnumerable SearchSciMagArticles(string searchQuery, int? resultLimit) + { + searchQuery = EscapeSearchQuery(searchQuery); + using (SQLiteCommand command = connection.CreateCommand()) + { + command.CommandText = GetSearchCommandWithLimit(SqlScripts.SEARCH_SCIMAG, resultLimit); + command.Parameters.AddWithValue("@SearchQuery", searchQuery); + using (SQLiteDataReader dataReader = command.ExecuteReader()) + { + while (dataReader.Read()) + { + SciMagArticle article = ReadSciMagArticle(dataReader); + yield return article; + } + } + } + } + + public void AddSciMagArticles(List articles) + { + using (SQLiteTransaction transaction = connection.BeginTransaction()) + { + using (SQLiteCommand insertCommand = connection.CreateCommand()) + using (SQLiteCommand insertFtsCommand = connection.CreateCommand()) + { + insertCommand.CommandText = SqlScripts.INSERT_SCIMAG; + insertCommand.Parameters.AddWithValue("@Id", null); + SQLiteParameter doiParameter = insertCommand.Parameters.Add("@Doi", DbType.String); + SQLiteParameter doi2Parameter = insertCommand.Parameters.Add("@Doi2", DbType.String); + SQLiteParameter titleParameter = insertCommand.Parameters.Add("@Title", DbType.String); + SQLiteParameter authorsParameter = insertCommand.Parameters.Add("@Authors", DbType.String); + SQLiteParameter yearParameter = insertCommand.Parameters.Add("@Year", DbType.String); + SQLiteParameter monthParameter = insertCommand.Parameters.Add("@Month", DbType.String); + SQLiteParameter dayParameter = insertCommand.Parameters.Add("@Day", DbType.String); + SQLiteParameter volumeParameter = insertCommand.Parameters.Add("@Volume", DbType.String); + SQLiteParameter issueParameter = insertCommand.Parameters.Add("@Issue", DbType.String); + SQLiteParameter firstPageParameter = insertCommand.Parameters.Add("@FirstPage", DbType.String); + SQLiteParameter lastPageParameter = insertCommand.Parameters.Add("@LastPage", DbType.String); + SQLiteParameter journalParameter = insertCommand.Parameters.Add("@Journal", DbType.String); + SQLiteParameter isbnParameter = insertCommand.Parameters.Add("@Isbn", DbType.String); + SQLiteParameter issnpParameter = insertCommand.Parameters.Add("@Issnp", DbType.String); + SQLiteParameter issneParameter = insertCommand.Parameters.Add("@Issne", DbType.String); + SQLiteParameter md5HashParameter = insertCommand.Parameters.Add("@Md5Hash", DbType.String); + SQLiteParameter sizeInBytesParameter = insertCommand.Parameters.Add("@SizeInBytes", DbType.Int64); + SQLiteParameter addedDateTimeParameter = insertCommand.Parameters.Add("@AddedDateTime", DbType.String); + SQLiteParameter journalIdParameter = insertCommand.Parameters.Add("@JournalId", DbType.String); + SQLiteParameter abstractUrlParameter = insertCommand.Parameters.Add("@AbstractUrl", DbType.String); + SQLiteParameter attribute1Parameter = insertCommand.Parameters.Add("@Attribute1", DbType.String); + SQLiteParameter attribute2Parameter = insertCommand.Parameters.Add("@Attribute2", DbType.String); + SQLiteParameter attribute3Parameter = insertCommand.Parameters.Add("@Attribute3", DbType.String); + SQLiteParameter attribute4Parameter = insertCommand.Parameters.Add("@Attribute4", DbType.String); + SQLiteParameter attribute5Parameter = insertCommand.Parameters.Add("@Attribute5", DbType.String); + SQLiteParameter attribute6Parameter = insertCommand.Parameters.Add("@Attribute6", DbType.String); + SQLiteParameter visibleParameter = insertCommand.Parameters.Add("@Visible", DbType.String); + SQLiteParameter pubmedIdParameter = insertCommand.Parameters.Add("@PubmedId", DbType.String); + SQLiteParameter pmcParameter = insertCommand.Parameters.Add("@Pmc", DbType.String); + SQLiteParameter piiParameter = insertCommand.Parameters.Add("@Pii", DbType.String); + SQLiteParameter libgenIdParameter = insertCommand.Parameters.Add("@LibgenId", DbType.Int32); + insertFtsCommand.CommandText = SqlScripts.INSERT_SCIMAG_FTS; + SQLiteParameter titleFtsParameter = insertFtsCommand.Parameters.Add("@Title", DbType.String); + SQLiteParameter authorsFtsParameter = insertFtsCommand.Parameters.Add("@Authors", DbType.String); + SQLiteParameter doiFtsParameter = insertFtsCommand.Parameters.Add("@Doi", DbType.String); + SQLiteParameter doi2FtsParameter = insertFtsCommand.Parameters.Add("@Doi2", DbType.String); + SQLiteParameter pubmedIdFtsParameter = insertFtsCommand.Parameters.Add("@PubmedId", DbType.String); + SQLiteParameter journalFtsParameter = insertFtsCommand.Parameters.Add("@Journal", DbType.String); + SQLiteParameter issnpFtsParameter = insertFtsCommand.Parameters.Add("@Issnp", DbType.String); + SQLiteParameter issneFtsParameter = insertFtsCommand.Parameters.Add("@Issne", DbType.String); + foreach (SciMagArticle article in articles) + { + doiParameter.Value = article.Doi; + doi2Parameter.Value = article.Doi2; + titleParameter.Value = article.Title; + authorsParameter.Value = article.Authors; + yearParameter.Value = article.Year; + monthParameter.Value = article.Month; + dayParameter.Value = article.Day; + volumeParameter.Value = article.Volume; + issueParameter.Value = article.Issue; + firstPageParameter.Value = article.FirstPage; + lastPageParameter.Value = article.LastPage; + journalParameter.Value = article.Journal; + isbnParameter.Value = article.Isbn; + issnpParameter.Value = article.Issnp; + issneParameter.Value = article.Issne; + md5HashParameter.Value = article.Md5Hash; + sizeInBytesParameter.Value = article.SizeInBytes; + addedDateTimeParameter.Value = article.AddedDateTime?.ToString("s"); + journalIdParameter.Value = article.JournalId; + abstractUrlParameter.Value = article.AbstractUrl; + attribute1Parameter.Value = article.Attribute1; + attribute2Parameter.Value = article.Attribute2; + attribute3Parameter.Value = article.Attribute3; + attribute4Parameter.Value = article.Attribute4; + attribute5Parameter.Value = article.Attribute5; + attribute6Parameter.Value = article.Attribute6; + visibleParameter.Value = article.Visible; + pubmedIdParameter.Value = article.PubmedId; + pmcParameter.Value = article.Pmc; + piiParameter.Value = article.Pii; + libgenIdParameter.Value = article.LibgenId; + insertCommand.ExecuteNonQuery(); + titleFtsParameter.Value = article.Title; + authorsFtsParameter.Value = article.Authors; + doiFtsParameter.Value = article.Doi; + doi2FtsParameter.Value = article.Doi2; + pubmedIdFtsParameter.Value = article.PubmedId; + journalFtsParameter.Value = article.Journal; + issnpFtsParameter.Value = article.Issnp; + issneFtsParameter.Value = article.Issne; + insertFtsCommand.ExecuteNonQuery(); + } + } + transaction.Commit(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private NonFictionBook ReadNonFictionBook(SQLiteDataReader dataReader) + { + NonFictionBook book = new NonFictionBook(); + book.Id = dataReader.GetInt32(0); + book.Title = dataReader.GetString(1); + book.VolumeInfo = dataReader.GetString(2); + book.Series = dataReader.GetString(3); + book.Periodical = dataReader.GetString(4); + book.Authors = dataReader.GetString(5); + book.Year = dataReader.GetString(6); + book.Edition = dataReader.GetString(7); + book.Publisher = dataReader.GetString(8); + book.City = dataReader.GetString(9); + book.Pages = dataReader.GetString(10); + book.PagesInFile = dataReader.GetInt32(11); + book.Language = dataReader.GetString(12); + book.Topic = dataReader.GetString(13); + book.Library = dataReader.GetString(14); + book.Issue = dataReader.GetString(15); + book.Identifier = dataReader.GetString(16); + book.Issn = dataReader.GetString(17); + book.Asin = dataReader.GetString(18); + book.Udc = dataReader.GetString(19); + book.Lbc = dataReader.GetString(20); + book.Ddc = dataReader.GetString(21); + book.Lcc = dataReader.GetString(22); + book.Doi = dataReader.GetString(23); + book.GoogleBookId = dataReader.GetString(24); + book.OpenLibraryId = dataReader.GetString(25); + book.Commentary = dataReader.GetString(26); + book.Dpi = dataReader.GetInt32(27); + book.Color = dataReader.GetString(28); + book.Cleaned = dataReader.GetString(29); + book.Orientation = dataReader.GetString(30); + book.Paginated = dataReader.GetString(31); + book.Scanned = dataReader.GetString(32); + book.Bookmarked = dataReader.GetString(33); + book.Searchable = dataReader.GetString(34); + book.SizeInBytes = dataReader.GetInt64(35); + book.Format = dataReader.GetString(36); + book.Md5Hash = dataReader.GetString(37); + book.Generic = dataReader.GetString(38); + book.Visible = dataReader.GetString(39); + book.Locator = dataReader.GetString(40); + book.Local = dataReader.GetInt32(41); + book.AddedDateTime = ParseDbDate(dataReader.GetString(42)); + book.LastModifiedDateTime = ParseDbDate(dataReader.GetString(43)); + book.CoverUrl = dataReader.GetString(44); + book.Tags = dataReader.GetString(45); + book.IdentifierPlain = dataReader.GetString(46); + book.LibgenId = dataReader.GetInt32(47); + return book; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private FictionBook ReadFictionBook(SQLiteDataReader dataReader) + { + FictionBook book = new FictionBook(); + book.Id = dataReader.GetInt32(0); + book.AuthorFamily1 = dataReader.GetString(1); + book.AuthorName1 = dataReader.GetString(2); + book.AuthorSurname1 = dataReader.GetString(3); + book.Role1 = dataReader.GetString(4); + book.Pseudonim1 = dataReader.GetString(5); + book.AuthorFamily2 = dataReader.GetString(6); + book.AuthorName2 = dataReader.GetString(7); + book.AuthorSurname2 = dataReader.GetString(8); + book.Role2 = dataReader.GetString(9); + book.Pseudonim2 = dataReader.GetString(10); + book.AuthorFamily3 = dataReader.GetString(11); + book.AuthorName3 = dataReader.GetString(12); + book.AuthorSurname3 = dataReader.GetString(13); + book.Role3 = dataReader.GetString(14); + book.Pseudonim3 = dataReader.GetString(15); + book.AuthorFamily4 = dataReader.GetString(16); + book.AuthorName4 = dataReader.GetString(17); + book.AuthorSurname4 = dataReader.GetString(18); + book.Role4 = dataReader.GetString(19); + book.Pseudonim4 = dataReader.GetString(20); + book.Series1 = dataReader.GetString(21); + book.Series2 = dataReader.GetString(22); + book.Series3 = dataReader.GetString(23); + book.Series4 = dataReader.GetString(24); + book.Title = dataReader.GetString(25); + book.Format = dataReader.GetString(26); + book.Version = dataReader.GetString(27); + book.SizeInBytes = dataReader.GetInt64(28); + book.Md5Hash = dataReader.GetString(29); + book.Path = dataReader.GetString(30); + book.Language = dataReader.GetString(31); + book.Pages = dataReader.GetString(32); + book.Identifier = dataReader.GetString(33); + book.Year = dataReader.GetString(34); + book.Publisher = dataReader.GetString(35); + book.Edition = dataReader.GetString(36); + book.Commentary = dataReader.GetString(37); + book.AddedDateTime = ParseNullableDbDate(dataReader.GetValue(38)); + book.LastModifiedDateTime = ParseDbDate(dataReader.GetString(39)); + book.RussianAuthorFamily = dataReader.GetString(40); + book.RussianAuthorName = dataReader.GetString(41); + book.RussianAuthorSurname = dataReader.GetString(42); + book.Cover = dataReader.GetString(43); + book.GoogleBookId = dataReader.GetString(44); + book.Asin = dataReader.GetString(45); + book.AuthorHash = dataReader.GetString(46); + book.TitleHash = dataReader.GetString(47); + book.Visible = dataReader.GetString(48); + book.LibgenId = dataReader.GetInt32(49); + return book; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private SciMagArticle ReadSciMagArticle(SQLiteDataReader dataReader) + { + SciMagArticle article = new SciMagArticle(); + article.Id = dataReader.GetInt32(0); + article.Doi = dataReader.GetString(1); + article.Doi2 = dataReader.GetString(2); + article.Title = dataReader.GetString(3); + article.Authors = dataReader.GetString(4); + article.Year = dataReader.GetString(5); + article.Month = dataReader.GetString(6); + article.Day = dataReader.GetString(7); + article.Volume = dataReader.GetString(8); + article.Issue = dataReader.GetString(9); + article.FirstPage = dataReader.GetString(10); + article.LastPage = dataReader.GetString(11); + article.Journal = dataReader.GetString(12); + article.Isbn = dataReader.GetString(13); + article.Issnp = dataReader.GetString(14); + article.Issne = dataReader.GetString(15); + article.Md5Hash = dataReader.GetString(16); + article.SizeInBytes = dataReader.GetInt64(17); + article.AddedDateTime = ParseNullableDbDate(dataReader.GetValue(18)); + article.JournalId = dataReader.GetString(19); + article.AbstractUrl = dataReader.GetString(20); + article.Attribute1 = dataReader.GetString(21); + article.Attribute2 = dataReader.GetString(22); + article.Attribute3 = dataReader.GetString(23); + article.Attribute4 = dataReader.GetString(24); + article.Attribute5 = dataReader.GetString(25); + article.Attribute6 = dataReader.GetString(26); + article.Visible = dataReader.GetString(27); + article.PubmedId = dataReader.GetString(28); + article.Pmc = dataReader.GetString(29); + article.Pii = dataReader.GetString(30); + article.LibgenId = dataReader.GetInt32(31); + return article; + } + + private string EscapeSearchQuery(string originalSearchQuery) + { + List searchQueryBuilder = new List(); + foreach (string searchQueryPart in originalSearchQuery.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)) + { + switch (searchQueryPart) + { + case "AND": + case "OR": + case "NOT": + searchQueryBuilder.Add(searchQueryPart); + continue; + default: + if (searchQueryPart.Length > 1 && searchQueryPart.EndsWith("*")) + { + searchQueryBuilder.Add($"\"{searchQueryPart.Substring(0, searchQueryPart.Length - 1)}\"*"); + } + else + { + searchQueryBuilder.Add($"\"{searchQueryPart}\""); + } + break; + } + } + return String.Join(" ", searchQueryBuilder); + } + + private string GetSearchCommandWithLimit(string searchCommand, int? resultLimit) + { + return searchCommand + (resultLimit.HasValue ? " LIMIT " + resultLimit.Value : String.Empty); } private void ExecuteCommands(params string[] commands) @@ -322,5 +814,35 @@ private void ExecuteCommands(params string[] commands) } } } + + private int ExecuteScalarCommand(string commandText) + { + using (SQLiteCommand command = connection.CreateCommand()) + { + command.CommandText = commandText; + object objectResult = command.ExecuteScalar(); + return objectResult != DBNull.Value ? (int)(long)objectResult : 0; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private DateTime ParseDbDate(string input) + { + return DateTime.ParseExact(input, "s", CultureInfo.InvariantCulture); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private DateTime? ParseNullableDbDate(object input) + { + if (input is string inputString) + { + if (String.IsNullOrEmpty(inputString)) + { + return null; + } + return ParseDbDate(inputString); + } + return null; + } } } diff --git a/Models/Database/SqlScripts.cs b/Models/Database/SqlScripts.cs index 0427bff..0c7dc0b 100644 --- a/Models/Database/SqlScripts.cs +++ b/Models/Database/SqlScripts.cs @@ -1,15 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LibgenDesktop.Models.Database +namespace LibgenDesktop.Models.Database { internal static class SqlScripts { - public const string CREATE_BOOKS_TABLE = - "CREATE TABLE IF NOT EXISTS books (" + + public const string CHECK_IF_METADATA_TABLE_EXIST = @"SELECT COUNT(name) FROM sqlite_master WHERE type=""table"" AND name=""metadata"""; + + public const string CREATE_METADATA_TABLE = + "CREATE TABLE IF NOT EXISTS metadata (" + + "Key TEXT PRIMARY KEY NOT NULL," + + "Value TEXT" + + ")"; + + public const string GET_ALL_METADATA_ITEMS = "SELECT Key,Value FROM metadata"; + + public const string INSERT_METADATA_ITEM = "INSERT INTO metadata VALUES (@Key,@Value)"; + + public const string UPDATE_METADATA_ITEM = "UPDATE metadata SET Value = @Value WHERE Key = @Key"; + + public const string CREATE_NON_FICTION_TABLE = + "CREATE TABLE IF NOT EXISTS non_fiction (" + "Id INTEGER PRIMARY KEY NOT NULL," + "Title TEXT," + "VolumeInfo TEXT," + @@ -34,7 +42,7 @@ internal static class SqlScripts "Ddc TEXT," + "Lcc TEXT," + "Doi TEXT," + - "GoogleBookid TEXT," + + "GoogleBookId TEXT," + "OpenLibraryId TEXT," + "Commentary TEXT," + "Dpi INTEGER," + @@ -60,31 +68,156 @@ internal static class SqlScripts "LibgenId INTEGER NOT NULL" + ")"; - public const string CREATE_BOOKS_FTS_TABLE = - "CREATE VIRTUAL TABLE IF NOT EXISTS books_fts USING fts5 (Title, Series, Authors, Publisher, IdentifierPlain, " + - "content=books, content_rowid=Id)"; + public const string CREATE_NON_FICTION_FTS_TABLE = + "CREATE VIRTUAL TABLE IF NOT EXISTS non_fiction_fts USING fts5 (Title, Series, Authors, Publisher, IdentifierPlain, " + + "content=non_fiction, content_rowid=Id)"; - public const string COUNT_BOOKS = "SELECT MAX(Id) FROM books LIMIT 1"; + public const string COUNT_NON_FICTION = "SELECT MAX(Id) FROM non_fiction LIMIT 1"; - public const string GET_ALL_BOOKS = "SELECT Id,Title,Authors,Series,Year,Publisher,Format,SizeInBytes,Searchable FROM books ORDER BY Id"; + public const string GET_NON_FICTION_BY_ID = "SELECT * FROM non_fiction WHERE Id = @Id"; - public const string GET_BOOK_BY_ID = "SELECT Id,Title,VolumeInfo,Series,Periodical,Authors,Year,Edition,Publisher,City," + - "Pages,PagesInFile,Language,Topic,Library,Issue,Identifier,Issn,Asin,Udc,Lbc,Ddc,Lcc,Doi," + - "GoogleBookid,OpenLibraryId,Commentary,Dpi,Color,Cleaned,Orientation,Paginated,Scanned,Bookmarked," + - "Searchable,SizeInBytes,Format,Md5Hash,Generic,Visible,Locator,Local,AddedDateTime," + - "LastModifiedDateTime,CoverUrl,Tags,IdentifierPlain,LibgenId FROM books WHERE Id = @Id"; + public const string SEARCH_NON_FICTION = "SELECT * FROM non_fiction " + + "WHERE Id IN (SELECT rowid FROM non_fiction_fts WHERE non_fiction_fts MATCH @SearchQuery) ORDER BY Id"; - public const string SEARCH_BOOKS = "SELECT Id,Title,Authors,Series,Year,Publisher,Format,SizeInBytes,Searchable FROM books " + - "WHERE Id IN (SELECT rowid FROM books_fts WHERE books_fts MATCH @SearchQuery) ORDER BY Id"; - - public const string INSERT_BOOK = - "INSERT INTO books VALUES (@Id,@Title,@VolumeInfo,@Series,@Periodical,@Authors,@Year,@Edition,@Publisher,@City," + + public const string INSERT_NON_FICTION = + "INSERT INTO non_fiction VALUES (@Id,@Title,@VolumeInfo,@Series,@Periodical,@Authors,@Year,@Edition,@Publisher,@City," + "@Pages,@PagesInFile,@Language,@Topic,@Library,@Issue,@Identifier,@Issn,@Asin,@Udc,@Lbc,@Ddc,@Lcc,@Doi," + - "@GoogleBookid,@OpenLibraryId,@Commentary,@Dpi,@Color,@Cleaned,@Orientation,@Paginated,@Scanned,@Bookmarked," + + "@GoogleBookId,@OpenLibraryId,@Commentary,@Dpi,@Color,@Cleaned,@Orientation,@Paginated,@Scanned,@Bookmarked," + "@Searchable,@SizeInBytes,@Format,@Md5Hash,@Generic,@Visible,@Locator,@Local,@AddedDateTime," + "@LastModifiedDateTime,@CoverUrl,@Tags,@IdentifierPlain,@LibgenId)"; - public const string INSERT_BOOK_FTS = - "INSERT INTO books_fts VALUES (@Title,@Series,@Authors,@Publisher,@IdentifierPlain)"; + public const string INSERT_NON_FICTION_FTS = + "INSERT INTO non_fiction_fts VALUES (@Title,@Series,@Authors,@Publisher,@IdentifierPlain)"; + + public const string CREATE_FICTION_TABLE = + "CREATE TABLE IF NOT EXISTS fiction (" + + "Id INTEGER PRIMARY KEY NOT NULL," + + "AuthorFamily1 TEXT," + + "AuthorName1 TEXT," + + "AuthorSurname1 TEXT," + + "Role1 TEXT," + + "Pseudonim1 TEXT," + + "AuthorFamily2 TEXT," + + "AuthorName2 TEXT," + + "AuthorSurname2 TEXT," + + "Role2 TEXT," + + "Pseudonim2 TEXT," + + "AuthorFamily3 TEXT," + + "AuthorName3 TEXT," + + "AuthorSurname3 TEXT," + + "Role3 TEXT," + + "Pseudonim3 TEXT," + + "AuthorFamily4 TEXT," + + "AuthorName4 TEXT," + + "AuthorSurname4 TEXT," + + "Role4 TEXT," + + "Pseudonim4 TEXT," + + "Series1 TEXT," + + "Series2 TEXT," + + "Series3 TEXT," + + "Series4 TEXT," + + "Title TEXT," + + "Format TEXT," + + "Version TEXT," + + "SizeInBytes INTEGER NOT NULL," + + "Md5Hash TEXT," + + "Path TEXT," + + "Language TEXT," + + "Pages TEXT," + + "Identifier TEXT," + + "Year TEXT," + + "Publisher TEXT," + + "Edition TEXT," + + "Commentary TEXT," + + "AddedDateTime TEXT," + + "LastModifiedDateTime TEXT NOT NULL," + + "RussianAuthorFamily TEXT," + + "RussianAuthorName TEXT," + + "RussianAuthorSurname TEXT," + + "Cover TEXT," + + "GoogleBookId TEXT," + + "Asin TEXT," + + "AuthorHash TEXT," + + "TitleHash TEXT," + + "Visible TEXT," + + "LibgenId INTEGER NOT NULL" + + ")"; + + public const string CREATE_FICTION_FTS_TABLE = + "CREATE VIRTUAL TABLE IF NOT EXISTS fiction_fts USING fts5 (Title, AuthorFamily1, AuthorName1, AuthorSurname1, Pseudonim1, " + + "AuthorFamily2, AuthorName2, AuthorSurname2, Pseudonim2, AuthorFamily3, AuthorName3, AuthorSurname3, Pseudonim3, " + + "AuthorFamily4, AuthorName4, AuthorSurname4, Pseudonim4, RussianAuthorFamily, RussianAuthorName, RussianAuthorSurname, " + + "Series1, Series2, Series3, Series4, Publisher, Identifier, content=fiction, content_rowid=Id)"; + + public const string COUNT_FICTION = "SELECT MAX(Id) FROM fiction LIMIT 1"; + + public const string GET_FICTION_BY_ID = "SELECT * FROM fiction WHERE Id = @Id"; + + public const string SEARCH_FICTION = "SELECT * FROM fiction WHERE Id IN (SELECT rowid FROM fiction_fts WHERE fiction_fts MATCH @SearchQuery) ORDER BY Id"; + + public const string INSERT_FICTION = + "INSERT INTO fiction VALUES (@Id,@AuthorFamily1,@AuthorName1,@AuthorSurname1,@Role1,@Pseudonim1,@AuthorFamily2,@AuthorName2,@AuthorSurname2,@Role2,@Pseudonim2," + + "@AuthorFamily3,@AuthorName3,@AuthorSurname3,@Role3,@Pseudonim3,@AuthorFamily4,@AuthorName4,@AuthorSurname4,@Role4,@Pseudonim4,@Series1,@Series2,@Series3,@Series4," + + "@Title,@Format,@Version,@SizeInBytes,@Md5Hash,@Path,@Language,@Pages,@Identifier,@Year,@Publisher,@Edition,@Commentary,@AddedDateTime,@LastModifiedDateTime," + + "@RussianAuthorFamily,@RussianAuthorName,@RussianAuthorSurname,@Cover,@GoogleBookId,@Asin,@AuthorHash,@TitleHash,@Visible,@LibgenId)"; + + public const string INSERT_FICTION_FTS = + "INSERT INTO fiction_fts VALUES (@Title,@AuthorFamily1,@AuthorName1,@AuthorSurname1,@Pseudonim1," + + "@AuthorFamily2, @AuthorName2, @AuthorSurname2, @Pseudonim2, @AuthorFamily3, @AuthorName3, @AuthorSurname3, @Pseudonim3, " + + "@AuthorFamily4, @AuthorName4, @AuthorSurname4, @Pseudonim4, @RussianAuthorFamily, @RussianAuthorName, @RussianAuthorSurname, " + + "@Series1, @Series2, @Series3, @Series4, @Publisher, @Identifier)"; + + public const string CREATE_SCIMAG_TABLE = + "CREATE TABLE IF NOT EXISTS scimag (" + + "Id INTEGER PRIMARY KEY NOT NULL," + + "Doi TEXT," + + "Doi2 TEXT," + + "Title TEXT," + + "Authors TEXT," + + "Year TEXT," + + "Month TEXT," + + "Day TEXT," + + "Volume TEXT," + + "Issue TEXT," + + "FirstPage TEXT," + + "LastPage TEXT," + + "Journal TEXT," + + "Isbn TEXT," + + "Issnp TEXT," + + "Issne TEXT," + + "Md5Hash TEXT," + + "SizeInBytes INTEGER NOT NULL," + + "AddedDateTime TEXT," + + "JournalId TEXT," + + "AbstractUrl TEXT," + + "Attribute1 TEXT," + + "Attribute2 TEXT," + + "Attribute3 TEXT," + + "Attribute4 TEXT," + + "Attribute5 TEXT," + + "Attribute6 TEXT," + + "Visible TEXT," + + "PubmedId TEXT," + + "Pmc TEXT," + + "Pii TEXT," + + "LibgenId INTEGER NOT NULL" + + ")"; + + public const string CREATE_SCIMAG_FTS_TABLE = + "CREATE VIRTUAL TABLE IF NOT EXISTS scimag_fts USING fts5 (Title, Authors, Doi, Doi2, PubmedId, Journal, Issnp, Issne, content=scimag, content_rowid=Id)"; + + public const string COUNT_SCIMAG = "SELECT MAX(Id) FROM scimag LIMIT 1"; + + public const string GET_SCIMAG_BY_ID = "SELECT * FROM scimag WHERE Id = @Id"; + + public const string SEARCH_SCIMAG = "SELECT * FROM scimag WHERE Id IN (SELECT rowid FROM scimag_fts WHERE scimag_fts MATCH @SearchQuery) ORDER BY Id"; + + public const string INSERT_SCIMAG = + "INSERT INTO scimag VALUES (@Id,@Doi,@Doi2,@Title,@Authors,@Year,@Month,@Day,@Volume,@Issue,@FirstPage,@LastPage,@Journal,@Isbn,@Issnp,@Issne," + + "@Md5Hash,@SizeInBytes,@AddedDateTime,@JournalId,@AbstractUrl,@Attribute1,@Attribute2,@Attribute3,@Attribute4,@Attribute5,@Attribute6," + + "@Visible,@PubmedId,@Pmc,@Pii,@LibgenId)"; + + public const string INSERT_SCIMAG_FTS = + "INSERT INTO scimag_fts VALUES (@Title,@Authors,@Doi,@Doi2,@PubmedId,@Journal,@Issnp,@Issne)"; } } diff --git a/Models/Entities/Book.cs b/Models/Entities/Book.cs deleted file mode 100644 index ab797fb..0000000 --- a/Models/Entities/Book.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System; -using System.Text; -using LibgenDesktop.Models.Utils; - -namespace LibgenDesktop.Models.Entities -{ - internal class Book - { - internal class BookExtendedProperties - { - public string VolumeInfo { get; set; } - public string Periodical { get; set; } - public string Edition { get; set; } - public string City { get; set; } - public string Pages { get; set; } - public int PagesInFile { get; set; } - public string Language { get; set; } - public string Topic { get; set; } - public string Library { get; set; } - public string Issue { get; set; } - public string Identifier { get; set; } - public string Issn { get; set; } - public string Asin { get; set; } - public string Udc { get; set; } - public string Lbc { get; set; } - public string Ddc { get; set; } - public string Lcc { get; set; } - public string Doi { get; set; } - public string GoogleBookid { get; set; } - public string OpenLibraryId { get; set; } - public string Commentary { get; set; } - public int Dpi { get; set; } - public string Color { get; set; } - public string Cleaned { get; set; } - public string Orientation { get; set; } - public string Paginated { get; set; } - public string Scanned { get; set; } - public string Bookmarked { get; set; } - public string Md5Hash { get; set; } - public string Generic { get; set; } - public string Visible { get; set; } - public string Locator { get; set; } - public int Local { get; set; } - public DateTime AddedDateTime { get; set; } - public DateTime LastModifiedDateTime { get; set; } - public string CoverUrl { get; set; } - public string Tags { get; set; } - public string IdentifierPlain { get; set; } - public int LibgenId { get; set; } - - public string AddedDateTimeString => AddedDateTime.ToString("dd.MM.yyyy HH:mm:ss"); - public string LastModifiedDateTimeString => LastModifiedDateTime.ToString("dd.MM.yyyy HH:mm:ss"); - public string BookmarkedString => StringBooleanToLabelString(Bookmarked, "есть", "нет", "неизвестно"); - public string ScannedString => StringBooleanToLabelString(Scanned, "да", "нет", "неизвестно"); - public string OrientationString => StringBooleanToLabelString(Orientation, "портретная", "альбомная", "неизвестно"); - public string PaginatedString => StringBooleanToLabelString(Paginated, "да", "нет", "неизвестно"); - public string ColorString => StringBooleanToLabelString(Color, "да", "нет", "неизвестно"); - public string CleanedString => StringBooleanToLabelString(Cleaned, "да", "нет", "неизвестно"); - - public string PagesString - { - get - { - StringBuilder resultBuilder = new StringBuilder(); - if (!String.IsNullOrWhiteSpace(Pages)) - { - resultBuilder.Append(Pages); - } - else - { - resultBuilder.Append("неизвестно"); - } - resultBuilder.Append(" (содержательная часть) / "); - resultBuilder.Append(PagesInFile.ToString()); - resultBuilder.Append(" (всего в файле)"); - return resultBuilder.ToString(); - } - } - } - - public int Id { get; set; } - public string Title { get; set; } - public string Series { get; set; } - public string Authors { get; set; } - public string Year { get; set; } - public string Publisher { get; set; } - public long SizeInBytes { get; set; } - public string Format { get; set; } - public string Searchable { get; set; } - public BookExtendedProperties ExtendedProperties { get; set; } - - public string FileSizeString => Formatters.FileSizeToString(SizeInBytes, false); - public string FileSizeWithBytesString => Formatters.FileSizeToString(SizeInBytes, true); - public bool Ocr => Searchable == "1"; - public string SearchableString => StringBooleanToLabelString(Searchable, "да", "нет", "неизвестно"); - - private static string StringBooleanToLabelString(string value, string value1Label, string value0Label, string valueUnknownLabel) - { - switch (value) - { - case "0": - return value0Label; - case "1": - return value1Label; - default: - return valueUnknownLabel; - } - } - } -} diff --git a/Models/Entities/DatabaseMetadata.cs b/Models/Entities/DatabaseMetadata.cs new file mode 100644 index 0000000..58b66ec --- /dev/null +++ b/Models/Entities/DatabaseMetadata.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; + +namespace LibgenDesktop.Models.Entities +{ + internal class DatabaseMetadata + { + internal class FieldDefinition + { + public FieldDefinition(string fieldName, Func getter, Action setter) + { + FieldName = fieldName; + Getter = getter; + Setter = setter; + } + + public string FieldName { get; } + public Func Getter { get; } + public Action Setter { get; } + } + + static DatabaseMetadata() + { + FieldDefinitions = new Dictionary(); + AddField("Version", metadata => metadata.Version, (metadata, value) => metadata.Version = value); + } + + public static Dictionary FieldDefinitions { get; } + + public string Version { get; set; } + + private static void AddField(string fieldName, Func getter, Action setter) + { + FieldDefinitions.Add(fieldName.ToLower(), new FieldDefinition(fieldName, getter, setter)); + } + } +} diff --git a/Models/Entities/FictionBook.cs b/Models/Entities/FictionBook.cs new file mode 100644 index 0000000..bb7c3ff --- /dev/null +++ b/Models/Entities/FictionBook.cs @@ -0,0 +1,198 @@ +using System; +using System.Runtime.CompilerServices; +using System.Text; +using LibgenDesktop.Models.Utils; + +namespace LibgenDesktop.Models.Entities +{ + internal class FictionBook + { + private string authors; + private string series; + private string russianAuthor; + + public FictionBook() + { + authors = null; + series = null; + russianAuthor = null; + } + + public int Id { get; set; } + public string AuthorFamily1 { get; set; } + public string AuthorName1 { get; set; } + public string AuthorSurname1 { get; set; } + public string Role1 { get; set; } + public string Pseudonim1 { get; set; } + public string AuthorFamily2 { get; set; } + public string AuthorName2 { get; set; } + public string AuthorSurname2 { get; set; } + public string Role2 { get; set; } + public string Pseudonim2 { get; set; } + public string AuthorFamily3 { get; set; } + public string AuthorName3 { get; set; } + public string AuthorSurname3 { get; set; } + public string Role3 { get; set; } + public string Pseudonim3 { get; set; } + public string AuthorFamily4 { get; set; } + public string AuthorName4 { get; set; } + public string AuthorSurname4 { get; set; } + public string Role4 { get; set; } + public string Pseudonim4 { get; set; } + public string Series1 { get; set; } + public string Series2 { get; set; } + public string Series3 { get; set; } + public string Series4 { get; set; } + public string Title { get; set; } + public string Format { get; set; } + public string Version { get; set; } + public long SizeInBytes { get; set; } + public string Md5Hash { get; set; } + public string Path { get; set; } + public string Language { get; set; } + public string Pages { get; set; } + public string Identifier { get; set; } + public string Year { get; set; } + public string Publisher { get; set; } + public string Edition { get; set; } + public string Commentary { get; set; } + public DateTime? AddedDateTime { get; set; } + public DateTime LastModifiedDateTime { get; set; } + public string RussianAuthorFamily { get; set; } + public string RussianAuthorName { get; set; } + public string RussianAuthorSurname { get; set; } + public string Cover { get; set; } + public string GoogleBookId { get; set; } + public string Asin { get; set; } + public string AuthorHash { get; set; } + public string TitleHash { get; set; } + public string Visible { get; set; } + public int LibgenId { get; set; } + + public string YearString => Year != "0" ? Year : String.Empty; + public string PagesString => Pages != "0" ? Pages : "неизвестно"; + public string FileSizeString => Formatters.FileSizeToString(SizeInBytes, false); + public string FileSizeWithBytesString => Formatters.FileSizeToString(SizeInBytes, true); + public string AddedDateTimeString => AddedDateTime != null ? AddedDateTime.Value.ToString("dd.MM.yyyy HH:mm:ss") : "неизвестно"; + public string LastModifiedDateTimeString => LastModifiedDateTime.ToString("dd.MM.yyyy HH:mm:ss"); + + public string Authors + { + get + { + if (authors == null) + { + StringBuilder authorsStringBuilder = new StringBuilder(); + AppendAuthorString(authorsStringBuilder, AuthorName1, AuthorSurname1, AuthorFamily1, Pseudonim1, Role1); + AppendAuthorString(authorsStringBuilder, AuthorName2, AuthorSurname2, AuthorFamily2, Pseudonim2, Role2); + AppendAuthorString(authorsStringBuilder, AuthorName3, AuthorSurname3, AuthorFamily3, Pseudonim3, Role3); + AppendAuthorString(authorsStringBuilder, AuthorName4, AuthorSurname4, AuthorFamily4, Pseudonim4, Role4); + authors = authorsStringBuilder.ToString(); + } + return authors; + } + } + + public string Series + { + get + { + if (series == null) + { + StringBuilder seriesStringBuilder = new StringBuilder(); + foreach (string seriesItem in new[] { Series1, Series2, Series3, Series4 }) + { + if (!String.IsNullOrWhiteSpace(seriesItem)) + { + if (seriesStringBuilder.Length > 0) + { + seriesStringBuilder.Append("; "); + } + seriesStringBuilder.Append(seriesItem); + } + } + series = seriesStringBuilder.ToString(); + } + return series; + } + } + + public string RussianAuthor + { + get + { + if (russianAuthor == null) + { + StringBuilder russianAuthorStringBuilder = new StringBuilder(); + AppendAuthorString(russianAuthorStringBuilder, RussianAuthorName, RussianAuthorSurname, RussianAuthorFamily, null, null); + russianAuthor = russianAuthorStringBuilder.ToString(); + } + return russianAuthor; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AppendAuthorString(StringBuilder stringBuilder, string name, string surname, string familyName, string pseudonim, string role) + { + bool hasName = !String.IsNullOrWhiteSpace(name); + bool hasSurname = !String.IsNullOrWhiteSpace(surname); + bool hasFamilyName = !String.IsNullOrWhiteSpace(familyName); + bool hasPseudonim = !String.IsNullOrWhiteSpace(pseudonim); + bool hasRole = !String.IsNullOrWhiteSpace(role); + if (hasName || hasSurname || hasFamilyName || hasPseudonim || hasRole) + { + if (stringBuilder.Length > 0) + { + stringBuilder.Append("; "); + } + bool firstPart = true; + if (hasName) + { + stringBuilder.Append(name); + firstPart = false; + } + if (hasSurname) + { + if (!firstPart) + { + stringBuilder.Append(" "); + } + stringBuilder.Append(surname); + firstPart = false; + } + if (hasFamilyName) + { + if (!firstPart) + { + stringBuilder.Append(" "); + } + stringBuilder.Append(familyName); + firstPart = false; + } + if (hasPseudonim) + { + if (!firstPart) + { + stringBuilder.Append(" ("); + } + stringBuilder.Append(pseudonim); + if (!firstPart) + { + stringBuilder.Append(")"); + } + firstPart = false; + } + if (hasRole) + { + if (!firstPart) + { + stringBuilder.Append(" "); + } + stringBuilder.Append("("); + stringBuilder.Append(role); + stringBuilder.Append(")"); + } + } + } + } +} diff --git a/Models/Entities/NonFictionBook.cs b/Models/Entities/NonFictionBook.cs new file mode 100644 index 0000000..40181ed --- /dev/null +++ b/Models/Entities/NonFictionBook.cs @@ -0,0 +1,104 @@ +using System; +using System.Text; +using LibgenDesktop.Models.Utils; + +namespace LibgenDesktop.Models.Entities +{ + internal class NonFictionBook + { + public int Id { get; set; } + public string Title { get; set; } + public string VolumeInfo { get; set; } + public string Series { get; set; } + public string Periodical { get; set; } + public string Authors { get; set; } + public string Year { get; set; } + public string Edition { get; set; } + public string Publisher { get; set; } + public string City { get; set; } + public string Pages { get; set; } + public int PagesInFile { get; set; } + public string Language { get; set; } + public string Topic { get; set; } + public string Library { get; set; } + public string Issue { get; set; } + public string Identifier { get; set; } + public string Issn { get; set; } + public string Asin { get; set; } + public string Udc { get; set; } + public string Lbc { get; set; } + public string Ddc { get; set; } + public string Lcc { get; set; } + public string Doi { get; set; } + public string GoogleBookId { get; set; } + public string OpenLibraryId { get; set; } + public string Commentary { get; set; } + public int Dpi { get; set; } + public string Color { get; set; } + public string Cleaned { get; set; } + public string Orientation { get; set; } + public string Paginated { get; set; } + public string Scanned { get; set; } + public string Bookmarked { get; set; } + public string Searchable { get; set; } + public long SizeInBytes { get; set; } + public string Format { get; set; } + public string Md5Hash { get; set; } + public string Generic { get; set; } + public string Visible { get; set; } + public string Locator { get; set; } + public int Local { get; set; } + public DateTime AddedDateTime { get; set; } + public DateTime LastModifiedDateTime { get; set; } + public string CoverUrl { get; set; } + public string Tags { get; set; } + public string IdentifierPlain { get; set; } + public int LibgenId { get; set; } + + public string FileSizeString => Formatters.FileSizeToString(SizeInBytes, false); + public string FileSizeWithBytesString => Formatters.FileSizeToString(SizeInBytes, true); + public bool Ocr => Searchable == "1"; + public string SearchableString => StringBooleanToLabelString(Searchable, "да", "нет", "неизвестно"); + public string AddedDateTimeString => AddedDateTime.ToString("dd.MM.yyyy HH:mm:ss"); + public string LastModifiedDateTimeString => LastModifiedDateTime.ToString("dd.MM.yyyy HH:mm:ss"); + public string BookmarkedString => StringBooleanToLabelString(Bookmarked, "есть", "нет", "неизвестно"); + public string ScannedString => StringBooleanToLabelString(Scanned, "да", "нет", "неизвестно"); + public string OrientationString => StringBooleanToLabelString(Orientation, "портретная", "альбомная", "неизвестно"); + public string PaginatedString => StringBooleanToLabelString(Paginated, "да", "нет", "неизвестно"); + public string ColorString => StringBooleanToLabelString(Color, "да", "нет", "неизвестно"); + public string CleanedString => StringBooleanToLabelString(Cleaned, "да", "нет", "неизвестно"); + + public string PagesString + { + get + { + StringBuilder resultBuilder = new StringBuilder(); + if (!String.IsNullOrWhiteSpace(Pages)) + { + resultBuilder.Append(Pages); + } + else + { + resultBuilder.Append("неизвестно"); + } + resultBuilder.Append(" (содержательная часть) / "); + resultBuilder.Append(PagesInFile.ToString()); + resultBuilder.Append(" (всего в файле)"); + return resultBuilder.ToString(); + } + } + + private static string StringBooleanToLabelString(string value, string value1Label, string value0Label, string valueUnknownLabel) + { + switch (value) + { + case "0": + return value0Label; + case "1": + return value1Label; + default: + return valueUnknownLabel; + } + } + } +} diff --git a/Models/Entities/SciMagArticle.cs b/Models/Entities/SciMagArticle.cs new file mode 100644 index 0000000..b2060bd --- /dev/null +++ b/Models/Entities/SciMagArticle.cs @@ -0,0 +1,92 @@ +using System; +using LibgenDesktop.Models.Utils; + +namespace LibgenDesktop.Models.Entities +{ + internal class SciMagArticle + { + private string doiString; + + public SciMagArticle() + { + doiString = null; + } + + public int Id { get; set; } + public string Doi { get; set; } + public string Doi2 { get; set; } + public string Title { get; set; } + public string Authors { get; set; } + public string Year { get; set; } + public string Month { get; set; } + public string Day { get; set; } + public string Volume { get; set; } + public string Issue { get; set; } + public string FirstPage { get; set; } + public string LastPage { get; set; } + public string Journal { get; set; } + public string Isbn { get; set; } + public string Issnp { get; set; } + public string Issne { get; set; } + public string Md5Hash { get; set; } + public long SizeInBytes { get; set; } + public DateTime? AddedDateTime { get; set; } + public string JournalId { get; set; } + public string AbstractUrl { get; set; } + public string Attribute1 { get; set; } + public string Attribute2 { get; set; } + public string Attribute3 { get; set; } + public string Attribute4 { get; set; } + public string Attribute5 { get; set; } + public string Attribute6 { get; set; } + public string Visible { get; set; } + public string PubmedId { get; set; } + public string Pmc { get; set; } + public string Pii { get; set; } + public int LibgenId { get; set; } + + public string FileSizeString => Formatters.FileSizeToString(SizeInBytes, false); + public string FileSizeWithBytesString => Formatters.FileSizeToString(SizeInBytes, true); + public string AddedDateTimeString => AddedDateTime != null ? AddedDateTime.Value.ToString("dd.MM.yyyy HH:mm:ss") : "неизвестно"; + + public string DoiString + { + get + { + if (doiString == null) + { + if (!String.IsNullOrWhiteSpace(Doi)) + { + doiString = Doi; + } + if (!String.IsNullOrWhiteSpace(Doi2)) + { + if (doiString.Length > 0) + { + doiString += "; "; + } + doiString += Doi2; + } + } + return doiString; + } + } + + public string PagesString + { + get + { + if ((!String.IsNullOrWhiteSpace(FirstPage) && FirstPage != "0") || (!String.IsNullOrWhiteSpace(LastPage) && LastPage != "0")) + { + string firstPage = FirstPage != "0" ? FirstPage.Trim() + " " : String.Empty; + string lastPage = LastPage != "0" ? " " + LastPage.Trim() : String.Empty; + return firstPage + "–" + lastPage; + } + else + { + return "неизвестно"; + } + } + } + } +} diff --git a/Models/MainModel.cs b/Models/MainModel.cs index f0eb2f3..e2695c7 100644 --- a/Models/MainModel.cs +++ b/Models/MainModel.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -8,157 +10,391 @@ using LibgenDesktop.Models.ProgressArgs; using LibgenDesktop.Models.Settings; using LibgenDesktop.Models.SqlDump; -using LibgenDesktop.Models.Utils; using static LibgenDesktop.Common.Constants; namespace LibgenDesktop.Models { internal class MainModel { - private readonly LocalDatabase localDatabase; + internal enum DatabaseStatus + { + OPENED = 1, + NOT_FOUND, + NOT_SET, + CORRUPTED + } + + internal enum ImportSqlDumpResult + { + COMPLETED = 1, + CANCELLED, + DATA_NOT_FOUND, + EXCEPTION + } + + private const double IMPORT_PROGRESS_UPDATE_INTERVAL = 0.5; + private const double SEARCH_PROGRESS_UPDATE_INTERVAL = 0.1; + + private LocalDatabase localDatabase; public MainModel() { AppSettings = SettingsStorage.LoadSettings(); - localDatabase = new LocalDatabase(AppSettings.DatabaseFileName); - AllBooks = new AsyncBookCollection(); - SearchResults = new AsyncBookCollection(); + OpenDatabase(AppSettings.DatabaseFileName); } public AppSettings AppSettings { get; } - public AsyncBookCollection AllBooks { get; } - public AsyncBookCollection SearchResults { get; } + public DatabaseStatus LocalDatabaseStatus { get; private set; } + public DatabaseMetadata DatabaseMetadata { get; private set; } + public int NonFictionBookCount { get; private set; } + public int FictionBookCount { get; private set; } + public int SciMagArticleCount { get; private set; } + + public Task> SearchNonFictionAsync(string searchQuery, IProgress progressHandler, + CancellationToken cancellationToken) + { + return SearchItemsAsync(localDatabase.SearchNonFictionBooks, searchQuery, progressHandler, cancellationToken); + } + + public Task LoadNonFictionBookAsync(int bookId) + { + return LoadItemAsync(localDatabase.GetNonFictionBookById, bookId); + } + + public Task> SearchFictionAsync(string searchQuery, IProgress progressHandler, + CancellationToken cancellationToken) + { + return SearchItemsAsync(localDatabase.SearchFictionBooks, searchQuery, progressHandler, cancellationToken); + } - public Task LoadAllBooksAsync(IProgress progressHandler, CancellationToken cancellationToken) + public Task LoadFictionBookAsync(int bookId) + { + return LoadItemAsync(localDatabase.GetFictionBookById, bookId); + } + + public Task> SearchSciMagAsync(string searchQuery, IProgress progressHandler, + CancellationToken cancellationToken) + { + return SearchItemsAsync(localDatabase.SearchSciMagArticles, searchQuery, progressHandler, cancellationToken); + } + + public Task LoadSciMagArticleAsync(int articleId) + { + return LoadItemAsync(localDatabase.GetSciMagArticleById, articleId); + } + + public Task ImportSqlDumpAsync(string sqlDumpFilePath, IProgress progressHandler, CancellationToken cancellationToken) { return Task.Run(() => { - int totalBookCount = localDatabase.CountBooks(); - AllBooks.SetCapacity(totalBookCount); - int currentBatchBookNumber = 0; - int reportProgressBatchSize = totalBookCount / 1000; - foreach (Book book in localDatabase.GetAllBooks()) + using (SqlDumpReader sqlDumpReader = new SqlDumpReader(sqlDumpFilePath)) { - if (cancellationToken.IsCancellationRequested) + while (true) { - return; + bool tableFound = false; + while (sqlDumpReader.ReadLine()) + { + if (cancellationToken.IsCancellationRequested) + { + return ImportSqlDumpResult.CANCELLED; + } + if (sqlDumpReader.CurrentLineCommand == SqlDumpReader.LineCommand.CREATE_TABLE) + { + tableFound = true; + break; + } + progressHandler.Report(new ImportSearchTableDefinitionProgress(sqlDumpReader.CurrentFilePosition, sqlDumpReader.FileSize)); + } + if (!tableFound) + { + return ImportSqlDumpResult.DATA_NOT_FOUND; + } + SqlDumpReader.ParsedTableDefinition parsedTableDefinition = sqlDumpReader.ParseTableDefinition(); + TableType tableType = DetectImportTableType(parsedTableDefinition); + if (tableType == TableType.UNKNOWN) + { + continue; + } + progressHandler.Report(new ImportTableDefinitionFoundProgress(tableType)); + bool insertFound = false; + while (sqlDumpReader.ReadLine()) + { + if (cancellationToken.IsCancellationRequested) + { + return ImportSqlDumpResult.CANCELLED; + } + if (sqlDumpReader.CurrentLineCommand == SqlDumpReader.LineCommand.INSERT) + { + insertFound = true; + break; + } + } + if (!insertFound) + { + return ImportSqlDumpResult.DATA_NOT_FOUND; + } + switch (tableType) + { + case TableType.NON_FICTION: + if (NonFictionBookCount != 0) + { + throw new Exception("Update-импорт пока не поддерживается."); + } + ImportObjects(sqlDumpReader, progressHandler, cancellationToken, TableDefinitions.NonFiction, + parsedTableDefinition, localDatabase.AddNonFictionBooks); + UpdateNonFictionBookCount(); + break; + case TableType.FICTION: + if (FictionBookCount != 0) + { + throw new Exception("Update-импорт пока не поддерживается."); + } + ImportObjects(sqlDumpReader, progressHandler, cancellationToken, TableDefinitions.Fiction, + parsedTableDefinition, localDatabase.AddFictionBooks); + UpdateFictionBookCount(); + break; + case TableType.SCI_MAG: + if (SciMagArticleCount != 0) + { + throw new Exception("Update-импорт пока не поддерживается."); + } + ImportObjects(sqlDumpReader, progressHandler, cancellationToken, TableDefinitions.SciMag, + parsedTableDefinition, localDatabase.AddSciMagArticles); + UpdateSciMagArticleCount(); + break; + } + if (cancellationToken.IsCancellationRequested) + { + return ImportSqlDumpResult.CANCELLED; + } + return ImportSqlDumpResult.COMPLETED; } - AllBooks.AddBook(book); - currentBatchBookNumber++; - if (currentBatchBookNumber == reportProgressBatchSize) + } + }); + } + + public void SaveSettings() + { + SettingsStorage.SaveSettings(AppSettings); + } + + public string GetDatabaseNormalizedPath(string databaseFullPath) + { + string currentDirectory = Directory.GetCurrentDirectory(); + if (databaseFullPath.ToLower().StartsWith(currentDirectory.ToLower())) + { + return databaseFullPath.Substring(currentDirectory.Length + 1); + } + else + { + return databaseFullPath; + } + } + + public string GetDatabaseFullPath(string databaseNormalizedPath) + { + if (Path.IsPathRooted(databaseNormalizedPath)) + { + return databaseNormalizedPath; + } + else + { + return Path.Combine(Directory.GetCurrentDirectory(), databaseNormalizedPath); + } + } + + public string GetCurrentDirectory() + { + return Directory.GetCurrentDirectory(); + } + + public bool OpenDatabase(string databaseFilePath) + { + if (localDatabase != null) + { + localDatabase.Dispose(); + localDatabase = null; + } + if (!String.IsNullOrWhiteSpace(databaseFilePath)) + { + if (File.Exists(databaseFilePath)) + { + try { - progressHandler.Report(new LoadAllBooksProgress(AllBooks.AddedBookCount, totalBookCount)); - currentBatchBookNumber = 0; - if (AllBooks.AddedBookCount - AllBooks.Count > AllBooks.Count) + localDatabase = LocalDatabase.OpenDatabase(databaseFilePath); + if (!localDatabase.CheckIfMetadataExists()) + { + LocalDatabaseStatus = DatabaseStatus.CORRUPTED; + return false; + } + DatabaseMetadata = localDatabase.GetMetadata(); + if (DatabaseMetadata.Version != CURRENT_DATABASE_VERSION) { - AllBooks.UpdateReportedBookCount(); + LocalDatabaseStatus = DatabaseStatus.CORRUPTED; + return false; } + UpdateNonFictionBookCount(); + UpdateFictionBookCount(); + UpdateSciMagArticleCount(); } + catch + { + LocalDatabaseStatus = DatabaseStatus.CORRUPTED; + return false; + } + LocalDatabaseStatus = DatabaseStatus.OPENED; + return true; } - if (currentBatchBookNumber > 0) + else { - progressHandler.Report(new LoadAllBooksProgress(AllBooks.AddedBookCount, totalBookCount, isFinished: true)); + LocalDatabaseStatus = DatabaseStatus.NOT_FOUND; } - AllBooks.UpdateReportedBookCount(); - }); + } + else + { + LocalDatabaseStatus = DatabaseStatus.NOT_SET; + } + return false; } - public void ClearSearchResults() + public bool CreateDatabase(string databaseFilePath) { - SearchResults.Clear(); + if (localDatabase != null) + { + localDatabase.Dispose(); + localDatabase = null; + } + try + { + localDatabase = LocalDatabase.CreateDatabase(databaseFilePath); + localDatabase.CreateMetadataTable(); + localDatabase.CreateNonFictionTables(); + localDatabase.CreateFictionTables(); + localDatabase.CreateSciMagTables(); + DatabaseMetadata databaseMetadata = new DatabaseMetadata + { + Version = CURRENT_DATABASE_VERSION + }; + localDatabase.AddMetadata(databaseMetadata); + NonFictionBookCount = 0; + FictionBookCount = 0; + SciMagArticleCount = 0; + return true; + } + catch + { + LocalDatabaseStatus = DatabaseStatus.CORRUPTED; + return false; + } } - public Task SearchBooksAsync(string searchQuery, IProgress progressHandler, CancellationToken cancellationToken) + private Task> SearchItemsAsync(Func> searchFunction, string searchQuery, + IProgress progressHandler, CancellationToken cancellationToken) { return Task.Run(() => { - int currentBatchBookNumber = 0; - foreach (Book book in localDatabase.SearchBooks(searchQuery)) + int? resultLimit = null; + if (AppSettings.Search.LimitResults) + { + resultLimit = AppSettings.Search.MaximumResultCount; + } + ObservableCollection result = new ObservableCollection(); + DateTime lastUpdateDateTime = DateTime.Now; + foreach (T item in searchFunction(searchQuery, resultLimit)) { if (cancellationToken.IsCancellationRequested) { - return; + return null; } - SearchResults.AddBook(book); - currentBatchBookNumber++; - if (currentBatchBookNumber == SEARCH_REPORT_PROGRESS_BATCH_SIZE) + result.Add(item); + DateTime now = DateTime.Now; + if ((now - lastUpdateDateTime).TotalSeconds > SEARCH_PROGRESS_UPDATE_INTERVAL) { - progressHandler.Report(new SearchBooksProgress(SearchResults.AddedBookCount)); - currentBatchBookNumber = 0; - if (SearchResults.AddedBookCount - SearchResults.Count > SearchResults.Count) - { - SearchResults.UpdateReportedBookCount(); - } + progressHandler.Report(new SearchProgress(result.Count)); + lastUpdateDateTime = now; } } - if (currentBatchBookNumber > 0) - { - progressHandler.Report(new SearchBooksProgress(SearchResults.AddedBookCount, isFinished: true)); - } - SearchResults.UpdateReportedBookCount(); + progressHandler.Report(new SearchProgress(result.Count)); + return result; }); } - public Task LoadBookAsync(int bookId) + private Task LoadItemAsync(Func loadFunction, int itemId) { - return Task.Run(() => + return Task.Run(() => { - return localDatabase.GetBookById(bookId); + return loadFunction(itemId); }); } - public Task ImportSqlDumpAsync(string sqlDumpFilePath, IProgress progressHandler, CancellationToken cancellationToken) + private TableType DetectImportTableType(SqlDumpReader.ParsedTableDefinition parsedTableDefinition) { - return Task.Run(() => + if (TableDefinitions.AllTables.TryGetValue(parsedTableDefinition.TableName, out TableDefinition tableDefinition)) { - using (SqlDumpReader sqlDumpReader = new SqlDumpReader(sqlDumpFilePath)) + foreach (SqlDumpReader.ParsedColumnDefinition parsedColumnDefinition in parsedTableDefinition.Columns) { - EventHandler readRowsProgressHandler = (sender, e) => + if (tableDefinition.Columns.TryGetValue(parsedColumnDefinition.ColumnName.ToLower(), out ColumnDefinition columnDefinition)) { - progressHandler.Report(new ImportSqlDumpProgress(e.RowsParsed, e.CurrentPosition, e.TotalLength)); - }; - sqlDumpReader.ReadRowsProgress += readRowsProgressHandler; - List currentBatchBooks = new List(INSERT_TRANSACTION_BATCH); - foreach (Book book in sqlDumpReader.ReadRows()) - { - if (cancellationToken.IsCancellationRequested) + if (columnDefinition.ColumnType == parsedColumnDefinition.ColumnType) { - break; - } - currentBatchBooks.Add(book); - if (currentBatchBooks.Count == INSERT_TRANSACTION_BATCH) - { - localDatabase.AddBooks(currentBatchBooks); - foreach (Book currentBatchBook in currentBatchBooks) - { - currentBatchBook.ExtendedProperties = null; - } - AllBooks.AddBooks(currentBatchBooks); - if (AllBooks.Count == 0) - { - AllBooks.UpdateReportedBookCount(); - } - currentBatchBooks.Clear(); + continue; } } - if (currentBatchBooks.Any()) + return TableType.UNKNOWN; + } + return tableDefinition.TableType; + } + return TableType.UNKNOWN; + } + + private void ImportObjects(SqlDumpReader sqlDumpReader, IProgress progressHandler, CancellationToken cancellationToken, + TableDefinition tableDefinition, SqlDumpReader.ParsedTableDefinition parsedTableDefinition, Action> databaseBatchImportAction) + where T : new() + { + DateTime lastUpdateDateTime = DateTime.Now; + int importedObjectCount = 0; + List currentBatchObjects = new List(INSERT_TRANSACTION_BATCH); + List> sortedColumnSetters = tableDefinition.GetSortedColumnSetters(parsedTableDefinition.Columns.Select(column => column.ColumnName)); + foreach (T importingObject in sqlDumpReader.ParseImportObjects(sortedColumnSetters)) + { + if (cancellationToken.IsCancellationRequested) + { + return; + } + currentBatchObjects.Add(importingObject); + importedObjectCount++; + if (currentBatchObjects.Count == INSERT_TRANSACTION_BATCH) + { + databaseBatchImportAction(currentBatchObjects); + currentBatchObjects.Clear(); + DateTime now = DateTime.Now; + if ((now - lastUpdateDateTime).TotalSeconds > IMPORT_PROGRESS_UPDATE_INTERVAL) { - localDatabase.AddBooks(currentBatchBooks); - foreach (Book currentBatchBook in currentBatchBooks) - { - currentBatchBook.ExtendedProperties = null; - } - AllBooks.AddBooks(currentBatchBooks); + progressHandler.Report(new ImportObjectsProgress(importedObjectCount)); + lastUpdateDateTime = now; } - sqlDumpReader.ReadRowsProgress -= readRowsProgressHandler; } - AllBooks.UpdateReportedBookCount(); - }); + } + if (currentBatchObjects.Any()) + { + databaseBatchImportAction(currentBatchObjects); + } + progressHandler.Report(new ImportObjectsProgress(importedObjectCount)); } - public void SaveSettings() + private void UpdateNonFictionBookCount() { - SettingsStorage.SaveSettings(AppSettings); + NonFictionBookCount = localDatabase.CountNonFictionBooks(); + } + + private void UpdateFictionBookCount() + { + FictionBookCount = localDatabase.CountFictionBooks(); + } + + private void UpdateSciMagArticleCount() + { + SciMagArticleCount = localDatabase.CountSciMagArticles(); } } } diff --git a/Models/ProgressArgs/ImportObjectsProgress.cs b/Models/ProgressArgs/ImportObjectsProgress.cs new file mode 100644 index 0000000..cf20e6c --- /dev/null +++ b/Models/ProgressArgs/ImportObjectsProgress.cs @@ -0,0 +1,12 @@ +namespace LibgenDesktop.Models.ProgressArgs +{ + internal class ImportObjectsProgress + { + public ImportObjectsProgress(int objectsImported) + { + ObjectsImported = objectsImported; + } + + public int ObjectsImported { get; } + } +} diff --git a/Models/ProgressArgs/ImportSqlDumpProgress.cs b/Models/ProgressArgs/ImportSearchTableDefinitionProgress.cs similarity index 52% rename from Models/ProgressArgs/ImportSqlDumpProgress.cs rename to Models/ProgressArgs/ImportSearchTableDefinitionProgress.cs index c16369c..79d53b0 100644 --- a/Models/ProgressArgs/ImportSqlDumpProgress.cs +++ b/Models/ProgressArgs/ImportSearchTableDefinitionProgress.cs @@ -1,15 +1,13 @@ namespace LibgenDesktop.Models.ProgressArgs { - internal class ImportSqlDumpProgress + internal class ImportSearchTableDefinitionProgress { - public ImportSqlDumpProgress(int booksImported, long bytesParsed, long totalBytes) + public ImportSearchTableDefinitionProgress(long bytesParsed, long totalBytes) { - BooksImported = booksImported; BytesParsed = bytesParsed; TotalBytes = totalBytes; } - public int BooksImported { get; } public long BytesParsed { get; } public long TotalBytes { get; } } diff --git a/Models/ProgressArgs/ImportTableDefinitionFoundProgress.cs b/Models/ProgressArgs/ImportTableDefinitionFoundProgress.cs new file mode 100644 index 0000000..c19800f --- /dev/null +++ b/Models/ProgressArgs/ImportTableDefinitionFoundProgress.cs @@ -0,0 +1,14 @@ +using LibgenDesktop.Models.SqlDump; + +namespace LibgenDesktop.Models.ProgressArgs +{ + internal class ImportTableDefinitionFoundProgress + { + public ImportTableDefinitionFoundProgress(TableType tableFound) + { + TableFound = tableFound; + } + + public TableType TableFound { get; } + } +} diff --git a/Models/ProgressArgs/LoadAllBooksProgress.cs b/Models/ProgressArgs/LoadAllBooksProgress.cs deleted file mode 100644 index bc0dfbb..0000000 --- a/Models/ProgressArgs/LoadAllBooksProgress.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace LibgenDesktop.Models.ProgressArgs -{ - internal class LoadAllBooksProgress - { - public LoadAllBooksProgress(int booksLoaded, int totalBookCount, bool isFinished = false) - { - BooksLoaded = booksLoaded; - TotalBookCount = totalBookCount; - IsFinished = isFinished; - } - - public int BooksLoaded { get; } - public int TotalBookCount { get; } - public bool IsFinished { get; } - } -} diff --git a/Models/ProgressArgs/SearchBooksProgress.cs b/Models/ProgressArgs/SearchBooksProgress.cs deleted file mode 100644 index 72568e2..0000000 --- a/Models/ProgressArgs/SearchBooksProgress.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace LibgenDesktop.Models.ProgressArgs -{ - internal class SearchBooksProgress - { - public SearchBooksProgress(int booksFound, bool isFinished = false) - { - BooksFound = booksFound; - IsFinished = isFinished; - } - - public int BooksFound { get; } - public bool IsFinished { get; } - } -} diff --git a/Models/ProgressArgs/SearchProgress.cs b/Models/ProgressArgs/SearchProgress.cs new file mode 100644 index 0000000..542a429 --- /dev/null +++ b/Models/ProgressArgs/SearchProgress.cs @@ -0,0 +1,12 @@ +namespace LibgenDesktop.Models.ProgressArgs +{ + internal class SearchProgress + { + public SearchProgress(int itemsFound) + { + ItemsFound = itemsFound; + } + + public int ItemsFound { get; } + } +} diff --git a/Models/Settings/AppSettings.cs b/Models/Settings/AppSettings.cs index 410fe4c..cc16b69 100644 --- a/Models/Settings/AppSettings.cs +++ b/Models/Settings/AppSettings.cs @@ -1,4 +1,5 @@ -using LibgenDesktop.Infrastructure; +using System; +using LibgenDesktop.Infrastructure; using static LibgenDesktop.Common.Constants; namespace LibgenDesktop.Models.Settings @@ -7,6 +8,21 @@ internal class AppSettings { internal class MainWindowSettings { + public static MainWindowSettings Default + { + get + { + return new MainWindowSettings + { + Maximized = false, + Left = (WindowManager.ScreenWidth - DEFAULT_MAIN_WINDOW_WIDTH) / 2, + Top = (WindowManager.ScreenHeight - DEFAULT_MAIN_WINDOW_HEIGHT) / 2, + Width = DEFAULT_MAIN_WINDOW_WIDTH, + Height = DEFAULT_MAIN_WINDOW_HEIGHT + }; + } + } + public bool Maximized { get; set; } public int Left { get; set; } public int Top { get; set; } @@ -14,14 +30,47 @@ internal class MainWindowSettings public int Height { get; set; } } - internal class BookWindowSettings + internal abstract class DetailsWindowSettings { public int Width { get; set; } public int Height { get; set; } } - internal class ColumnSettings + internal class NonFictionDetailsWindowSettings : DetailsWindowSettings { + public static NonFictionDetailsWindowSettings Default + { + get + { + return new NonFictionDetailsWindowSettings + { + Width = DEFAULT_NON_FICTION_DETAILS_WINDOW_WIDTH, + Height = DEFAULT_NON_FICTION_DETAILS_WINDOW_HEIGHT + }; + } + } + } + + internal class NonFictionColumnSettings + { + public static NonFictionColumnSettings Default + { + get + { + return new NonFictionColumnSettings + { + TitleColumnWidth = DEFAULT_NON_FICTION_GRID_TITLE_COLUMN_WIDTH, + AuthorsColumnWidth = DEFAULT_NON_FICTION_GRID_AUTHORS_COLUMN_WIDTH, + SeriesColumnWidth = DEFAULT_NON_FICTION_GRID_SERIES_COLUMN_WIDTH, + YearColumnWidth = DEFAULT_NON_FICTION_GRID_YEAR_COLUMN_WIDTH, + PublisherColumnWidth = DEFAULT_NON_FICTION_GRID_PUBLISHER_COLUMN_WIDTH, + FormatColumnWidth = DEFAULT_NON_FICTION_GRID_FORMAT_COLUMN_WIDTH, + FileSizeColumnWidth = DEFAULT_NON_FICTION_GRID_FILESIZE_COLUMN_WIDTH, + OcrColumnWidth = DEFAULT_NON_FICTION_GRID_OCR_COLUMN_WIDTH + }; + } + } + public int TitleColumnWidth { get; set; } public int AuthorsColumnWidth { get; set; } public int SeriesColumnWidth { get; set; } @@ -32,163 +81,471 @@ internal class ColumnSettings public int OcrColumnWidth { get; set; } } - public static AppSettings Default + internal class NonFictionSettings { - get + public static NonFictionSettings Default { - return new AppSettings + get { - DatabaseFileName = DEFAULT_DATABASE_FILE_NAME, - OfflineMode = true, - // ResultLimit = 0, - MainWindow = DefaultMainWindowSettings, - BookWindow = DefaultBookWindowSettings, - Columns = DefaultColumnSettings - }; + return new NonFictionSettings + { + DetailsWindow = NonFictionDetailsWindowSettings.Default, + Columns = NonFictionColumnSettings.Default + }; + } } + + public NonFictionDetailsWindowSettings DetailsWindow { get; set; } + public NonFictionColumnSettings Columns { get; set; } } - private static MainWindowSettings DefaultMainWindowSettings + internal class FictionDetailsWindowSettings : DetailsWindowSettings { - get + public static FictionDetailsWindowSettings Default { - return new MainWindowSettings + get { - Maximized = false, - Left = (WindowManager.ScreenWidth - DEFAULT_MAIN_WINDOW_WIDTH) / 2, - Top = (WindowManager.ScreenHeight - DEFAULT_MAIN_WINDOW_HEIGHT) / 2, - Width = DEFAULT_MAIN_WINDOW_WIDTH, - Height = DEFAULT_MAIN_WINDOW_HEIGHT - }; + return new FictionDetailsWindowSettings + { + Width = DEFAULT_FICTION_DETAILS_WINDOW_WIDTH, + Height = DEFAULT_FICTION_DETAILS_WINDOW_HEIGHT + }; + } } } - private static BookWindowSettings DefaultBookWindowSettings + internal class FictionColumnSettings { - get + public static FictionColumnSettings Default { - return new BookWindowSettings + get { - Width = DEFAULT_BOOK_WINDOW_WIDTH, - Height = DEFAULT_BOOK_WINDOW_HEIGHT - }; + return new FictionColumnSettings + { + TitleColumnWidth = DEFAULT_FICTION_GRID_TITLE_COLUMN_WIDTH, + AuthorsColumnWidth = DEFAULT_FICTION_GRID_AUTHORS_COLUMN_WIDTH, + SeriesColumnWidth = DEFAULT_FICTION_GRID_SERIES_COLUMN_WIDTH, + YearColumnWidth = DEFAULT_FICTION_GRID_YEAR_COLUMN_WIDTH, + PublisherColumnWidth = DEFAULT_FICTION_GRID_PUBLISHER_COLUMN_WIDTH, + FormatColumnWidth = DEFAULT_FICTION_GRID_FORMAT_COLUMN_WIDTH, + FileSizeColumnWidth = DEFAULT_FICTION_GRID_FILESIZE_COLUMN_WIDTH + }; + } + } + + public int TitleColumnWidth { get; set; } + public int AuthorsColumnWidth { get; set; } + public int SeriesColumnWidth { get; set; } + public int YearColumnWidth { get; set; } + public int PublisherColumnWidth { get; set; } + public int FormatColumnWidth { get; set; } + public int FileSizeColumnWidth { get; set; } + } + + internal class FictionSettings + { + public static FictionSettings Default + { + get + { + return new FictionSettings + { + DetailsWindow = FictionDetailsWindowSettings.Default, + Columns = FictionColumnSettings.Default + }; + } + } + + public FictionDetailsWindowSettings DetailsWindow { get; set; } + public FictionColumnSettings Columns { get; set; } + } + + internal class SciMagDetailsWindowSettings : DetailsWindowSettings + { + public static SciMagDetailsWindowSettings Default + { + get + { + return new SciMagDetailsWindowSettings + { + Width = DEFAULT_SCI_MAG_DETAILS_WINDOW_WIDTH, + Height = DEFAULT_SCI_MAG_DETAILS_WINDOW_HEIGHT + }; + } + } + } + + internal class SciMagColumnSettings + { + public static SciMagColumnSettings Default + { + get + { + return new SciMagColumnSettings + { + TitleColumnWidth = DEFAULT_SCI_MAG_GRID_TITLE_COLUMN_WIDTH, + AuthorsColumnWidth = DEFAULT_SCI_MAG_GRID_AUTHORS_COLUMN_WIDTH, + JournalColumnWidth = DEFAULT_SCI_MAG_GRID_JOURNAL_COLUMN_WIDTH, + YearColumnWidth = DEFAULT_SCI_MAG_GRID_YEAR_COLUMN_WIDTH, + FileSizeColumnWidth = DEFAULT_SCI_MAG_GRID_FILESIZE_COLUMN_WIDTH, + DoiColumnWidth = DEFAULT_SCI_MAG_GRID_DOI_COLUMN_WIDTH + }; + } + } + + public int TitleColumnWidth { get; set; } + public int AuthorsColumnWidth { get; set; } + public int JournalColumnWidth { get; set; } + public int YearColumnWidth { get; set; } + public int FileSizeColumnWidth { get; set; } + public int DoiColumnWidth { get; set; } + } + + internal class SciMagSettings + { + public static SciMagSettings Default + { + get + { + return new SciMagSettings + { + DetailsWindow = SciMagDetailsWindowSettings.Default, + Columns = SciMagColumnSettings.Default + }; + } + } + + public SciMagDetailsWindowSettings DetailsWindow { get; set; } + public SciMagColumnSettings Columns { get; set; } + } + + internal class NetworkSettings + { + public static NetworkSettings Default + { + get + { + return new NetworkSettings + { + OfflineMode = true + }; + } + } + + public bool OfflineMode { get; set; } + } + + internal class SearchSettings + { + public static SearchSettings Default + { + get + { + return new SearchSettings + { + LimitResults = true, + MaximumResultCount = DEFAULT_MAXIMUM_SEARCH_RESULT_COUNT + }; + } } + + public bool LimitResults { get; set; } + public int MaximumResultCount { get; set; } } - private static ColumnSettings DefaultColumnSettings + public static AppSettings Default { get { - return new ColumnSettings + return new AppSettings { - TitleColumnWidth = DEFAULT_TITLE_COLUMN_WIDTH, - AuthorsColumnWidth = DEFAULT_AUTHORS_COLUMN_WIDTH, - SeriesColumnWidth = DEFAULT_SERIES_COLUMN_WIDTH, - YearColumnWidth = DEFAULT_YEAR_COLUMN_WIDTH, - PublisherColumnWidth = DEFAULT_PUBLISHER_COLUMN_WIDTH, - FormatColumnWidth = DEFAULT_FORMAT_COLUMN_WIDTH, - FileSizeColumnWidth = DEFAULT_FILESIZE_COLUMN_WIDTH, - OcrColumnWidth = DEFAULT_OCR_COLUMN_WIDTH + DatabaseFileName = String.Empty, + MainWindow = MainWindowSettings.Default, + NonFiction = NonFictionSettings.Default, + Network = NetworkSettings.Default, + Search = SearchSettings.Default }; } } public string DatabaseFileName { get; set; } - public bool OfflineMode { get; set; } - // public int ResultLimit { get; set; } public MainWindowSettings MainWindow { get; set; } - public BookWindowSettings BookWindow { get; set; } - public ColumnSettings Columns { get; set; } + public NonFictionSettings NonFiction { get; set; } + public FictionSettings Fiction { get; set; } + public SciMagSettings SciMag { get; set; } + public NetworkSettings Network { get; set; } + public SearchSettings Search { get; set; } - public static void ValidateAndCorrect(AppSettings appSettings) + public static AppSettings ValidateAndCorrect(AppSettings appSettings) { - if (string.IsNullOrEmpty(appSettings.DatabaseFileName)) + if (appSettings == null) + { + return Default; + } + else { - appSettings.DatabaseFileName = DEFAULT_DATABASE_FILE_NAME; + appSettings.ValidateAndCorrectDatabaseFileName(); + appSettings.ValidateAndCorrectMainWindowSettings(); + appSettings.ValidateAndCorrectNonFictionSettings(); + appSettings.ValidateAndCorrectFictionSettings(); + appSettings.ValidateAndCorrectNetworkSettings(); + appSettings.ValidateAndCorrectSearchSettings(); + appSettings.ValidateAndCorrectSciMagSettings(); + return appSettings; } - if (appSettings.MainWindow == null) + } + + private void ValidateAndCorrectDatabaseFileName() + { + if (DatabaseFileName == null) + { + DatabaseFileName = String.Empty; + } + } + + private void ValidateAndCorrectMainWindowSettings() + { + if (MainWindow == null) { - appSettings.MainWindow = DefaultMainWindowSettings; + MainWindow = MainWindowSettings.Default; } else { - if (appSettings.MainWindow.Width < MAIN_WINDOW_MIN_WIDTH) + if (MainWindow.Width < MAIN_WINDOW_MIN_WIDTH) { - appSettings.MainWindow.Width = MAIN_WINDOW_MIN_WIDTH; + MainWindow.Width = MAIN_WINDOW_MIN_WIDTH; } - if (appSettings.MainWindow.Height < MAIN_WINDOW_MIN_HEIGHT) + if (MainWindow.Height < MAIN_WINDOW_MIN_HEIGHT) { - appSettings.MainWindow.Height = MAIN_WINDOW_MIN_HEIGHT; + MainWindow.Height = MAIN_WINDOW_MIN_HEIGHT; } - if (appSettings.MainWindow.Left >= WindowManager.ScreenWidth) + if (MainWindow.Left >= WindowManager.ScreenWidth) { - appSettings.MainWindow.Left = WindowManager.ScreenWidth - appSettings.MainWindow.Width; + MainWindow.Left = WindowManager.ScreenWidth - MainWindow.Width; } - if (appSettings.MainWindow.Top >= WindowManager.ScreenHeight) + if (MainWindow.Top >= WindowManager.ScreenHeight) { - appSettings.MainWindow.Top = WindowManager.ScreenHeight - appSettings.MainWindow.Height; + MainWindow.Top = WindowManager.ScreenHeight - MainWindow.Height; } - if (appSettings.MainWindow.Left < 0) + if (MainWindow.Left < 0) { - appSettings.MainWindow.Left = 0; + MainWindow.Left = 0; } - if (appSettings.MainWindow.Top < 0) + if (MainWindow.Top < 0) { - appSettings.MainWindow.Top = 0; + MainWindow.Top = 0; } } - if (appSettings.BookWindow == null) + } + + private void ValidateAndCorrectNonFictionSettings() + { + if (NonFiction == null) { - appSettings.BookWindow = DefaultBookWindowSettings; + NonFiction = NonFictionSettings.Default; } else { - if (appSettings.BookWindow.Width < BOOK_WINDOW_MIN_WIDTH) + if (NonFiction.DetailsWindow == null) + { + NonFiction.DetailsWindow = NonFictionDetailsWindowSettings.Default; + } + else { - appSettings.BookWindow.Width = BOOK_WINDOW_MIN_WIDTH; + NonFictionDetailsWindowSettings nonFictionDetailsWindow = NonFiction.DetailsWindow; + if (nonFictionDetailsWindow.Width < NON_FICTION_DETAILS_WINDOW_MIN_WIDTH) + { + nonFictionDetailsWindow.Width = NON_FICTION_DETAILS_WINDOW_MIN_WIDTH; + } + if (nonFictionDetailsWindow.Height < NON_FICTION_DETAILS_WINDOW_MIN_HEIGHT) + { + nonFictionDetailsWindow.Height = NON_FICTION_DETAILS_WINDOW_MIN_HEIGHT; + } } - if (appSettings.BookWindow.Height < BOOK_WINDOW_MIN_HEIGHT) + if (NonFiction.Columns == null) { - appSettings.BookWindow.Height = BOOK_WINDOW_MIN_HEIGHT; + NonFiction.Columns = NonFictionColumnSettings.Default; + } + else + { + NonFictionColumnSettings nonFictionColumns = NonFiction.Columns; + if (nonFictionColumns.TitleColumnWidth < NON_FICTION_GRID_TITLE_COLUMN_MIN_WIDTH) + { + nonFictionColumns.TitleColumnWidth = NON_FICTION_GRID_TITLE_COLUMN_MIN_WIDTH; + } + if (nonFictionColumns.AuthorsColumnWidth < NON_FICTION_GRID_AUTHORS_COLUMN_MIN_WIDTH) + { + nonFictionColumns.AuthorsColumnWidth = NON_FICTION_GRID_AUTHORS_COLUMN_MIN_WIDTH; + } + if (nonFictionColumns.SeriesColumnWidth < NON_FICTION_GRID_SERIES_COLUMN_MIN_WIDTH) + { + nonFictionColumns.SeriesColumnWidth = NON_FICTION_GRID_SERIES_COLUMN_MIN_WIDTH; + } + if (nonFictionColumns.YearColumnWidth < NON_FICTION_GRID_YEAR_COLUMN_MIN_WIDTH) + { + nonFictionColumns.YearColumnWidth = NON_FICTION_GRID_YEAR_COLUMN_MIN_WIDTH; + } + if (nonFictionColumns.PublisherColumnWidth < NON_FICTION_GRID_PUBLISHER_COLUMN_MIN_WIDTH) + { + nonFictionColumns.PublisherColumnWidth = NON_FICTION_GRID_PUBLISHER_COLUMN_MIN_WIDTH; + } + if (nonFictionColumns.FormatColumnWidth < NON_FICTION_GRID_FORMAT_COLUMN_MIN_WIDTH) + { + nonFictionColumns.FormatColumnWidth = NON_FICTION_GRID_FORMAT_COLUMN_MIN_WIDTH; + } + if (nonFictionColumns.FileSizeColumnWidth < NON_FICTION_GRID_FILESIZE_COLUMN_MIN_WIDTH) + { + nonFictionColumns.FileSizeColumnWidth = NON_FICTION_GRID_FILESIZE_COLUMN_MIN_WIDTH; + } + if (nonFictionColumns.OcrColumnWidth < NON_FICTION_GRID_OCR_COLUMN_MIN_WIDTH) + { + nonFictionColumns.OcrColumnWidth = NON_FICTION_GRID_OCR_COLUMN_MIN_WIDTH; + } } } - if (appSettings.Columns == null) + } + + private void ValidateAndCorrectFictionSettings() + { + if (Fiction == null) { - appSettings.Columns = DefaultColumnSettings; + Fiction = FictionSettings.Default; } else { - if (appSettings.Columns.TitleColumnWidth < TITLE_COLUMN_MIN_WIDTH) + if (Fiction.DetailsWindow == null) { - appSettings.Columns.TitleColumnWidth = TITLE_COLUMN_MIN_WIDTH; + Fiction.DetailsWindow = FictionDetailsWindowSettings.Default; } - if (appSettings.Columns.AuthorsColumnWidth < AUTHORS_COLUMN_MIN_WIDTH) + else { - appSettings.Columns.AuthorsColumnWidth = AUTHORS_COLUMN_MIN_WIDTH; + FictionDetailsWindowSettings fictionDetailsWindow = Fiction.DetailsWindow; + if (fictionDetailsWindow.Width < FICTION_DETAILS_WINDOW_MIN_WIDTH) + { + fictionDetailsWindow.Width = FICTION_DETAILS_WINDOW_MIN_WIDTH; + } + if (fictionDetailsWindow.Height < FICTION_DETAILS_WINDOW_MIN_HEIGHT) + { + fictionDetailsWindow.Height = FICTION_DETAILS_WINDOW_MIN_HEIGHT; + } } - if (appSettings.Columns.SeriesColumnWidth < SERIES_COLUMN_MIN_WIDTH) + if (Fiction.Columns == null) { - appSettings.Columns.SeriesColumnWidth = SERIES_COLUMN_MIN_WIDTH; + Fiction.Columns = FictionColumnSettings.Default; } - if (appSettings.Columns.YearColumnWidth < YEAR_COLUMN_MIN_WIDTH) + else { - appSettings.Columns.YearColumnWidth = YEAR_COLUMN_MIN_WIDTH; + FictionColumnSettings fictionColumns = Fiction.Columns; + if (fictionColumns.TitleColumnWidth < FICTION_GRID_TITLE_COLUMN_MIN_WIDTH) + { + fictionColumns.TitleColumnWidth = FICTION_GRID_TITLE_COLUMN_MIN_WIDTH; + } + if (fictionColumns.AuthorsColumnWidth < FICTION_GRID_AUTHORS_COLUMN_MIN_WIDTH) + { + fictionColumns.AuthorsColumnWidth = FICTION_GRID_AUTHORS_COLUMN_MIN_WIDTH; + } + if (fictionColumns.SeriesColumnWidth < FICTION_GRID_SERIES_COLUMN_MIN_WIDTH) + { + fictionColumns.SeriesColumnWidth = FICTION_GRID_SERIES_COLUMN_MIN_WIDTH; + } + if (fictionColumns.YearColumnWidth < FICTION_GRID_YEAR_COLUMN_MIN_WIDTH) + { + fictionColumns.YearColumnWidth = FICTION_GRID_YEAR_COLUMN_MIN_WIDTH; + } + if (fictionColumns.PublisherColumnWidth < FICTION_GRID_PUBLISHER_COLUMN_MIN_WIDTH) + { + fictionColumns.PublisherColumnWidth = FICTION_GRID_PUBLISHER_COLUMN_MIN_WIDTH; + } + if (fictionColumns.FormatColumnWidth < FICTION_GRID_FORMAT_COLUMN_MIN_WIDTH) + { + fictionColumns.FormatColumnWidth = FICTION_GRID_FORMAT_COLUMN_MIN_WIDTH; + } + if (fictionColumns.FileSizeColumnWidth < FICTION_GRID_FILESIZE_COLUMN_MIN_WIDTH) + { + fictionColumns.FileSizeColumnWidth = FICTION_GRID_FILESIZE_COLUMN_MIN_WIDTH; + } } - if (appSettings.Columns.PublisherColumnWidth < PUBLISHER_COLUMN_MIN_WIDTH) + } + } + + private void ValidateAndCorrectSciMagSettings() + { + if (SciMag == null) + { + SciMag = SciMagSettings.Default; + } + else + { + if (SciMag.DetailsWindow == null) + { + SciMag.DetailsWindow = SciMagDetailsWindowSettings.Default; + } + else { - appSettings.Columns.PublisherColumnWidth = PUBLISHER_COLUMN_MIN_WIDTH; + SciMagDetailsWindowSettings sciMagDetailsWindow = SciMag.DetailsWindow; + if (sciMagDetailsWindow.Width < SCI_MAG_DETAILS_WINDOW_MIN_WIDTH) + { + sciMagDetailsWindow.Width = SCI_MAG_DETAILS_WINDOW_MIN_WIDTH; + } + if (sciMagDetailsWindow.Height < SCI_MAG_DETAILS_WINDOW_MIN_HEIGHT) + { + sciMagDetailsWindow.Height = SCI_MAG_DETAILS_WINDOW_MIN_HEIGHT; + } } - if (appSettings.Columns.FormatColumnWidth < FORMAT_COLUMN_MIN_WIDTH) + if (SciMag.Columns == null) { - appSettings.Columns.FormatColumnWidth = FORMAT_COLUMN_MIN_WIDTH; + SciMag.Columns = SciMagColumnSettings.Default; } - if (appSettings.Columns.FileSizeColumnWidth < FILESIZE_COLUMN_MIN_WIDTH) + else { - appSettings.Columns.FileSizeColumnWidth = FILESIZE_COLUMN_MIN_WIDTH; + SciMagColumnSettings sciMagColumns = SciMag.Columns; + if (sciMagColumns.TitleColumnWidth < SCI_MAG_GRID_TITLE_COLUMN_MIN_WIDTH) + { + sciMagColumns.TitleColumnWidth = SCI_MAG_GRID_TITLE_COLUMN_MIN_WIDTH; + } + if (sciMagColumns.AuthorsColumnWidth < SCI_MAG_GRID_AUTHORS_COLUMN_MIN_WIDTH) + { + sciMagColumns.AuthorsColumnWidth = SCI_MAG_GRID_AUTHORS_COLUMN_MIN_WIDTH; + } + if (sciMagColumns.JournalColumnWidth < SCI_MAG_GRID_JOURNAL_COLUMN_MIN_WIDTH) + { + sciMagColumns.JournalColumnWidth = SCI_MAG_GRID_JOURNAL_COLUMN_MIN_WIDTH; + } + if (sciMagColumns.YearColumnWidth < SCI_MAG_GRID_YEAR_COLUMN_MIN_WIDTH) + { + sciMagColumns.YearColumnWidth = SCI_MAG_GRID_YEAR_COLUMN_MIN_WIDTH; + } + if (sciMagColumns.FileSizeColumnWidth < SCI_MAG_GRID_FILESIZE_COLUMN_MIN_WIDTH) + { + sciMagColumns.FileSizeColumnWidth = SCI_MAG_GRID_FILESIZE_COLUMN_MIN_WIDTH; + } + if (sciMagColumns.DoiColumnWidth < SCI_MAG_GRID_DOI_COLUMN_MIN_WIDTH) + { + sciMagColumns.DoiColumnWidth = SCI_MAG_GRID_DOI_COLUMN_MIN_WIDTH; + } } - if (appSettings.Columns.OcrColumnWidth < OCR_COLUMN_MIN_WIDTH) + } + } + + private void ValidateAndCorrectNetworkSettings() + { + if (Network == null) + { + Network = NetworkSettings.Default; + } + } + + private void ValidateAndCorrectSearchSettings() + { + if (Search == null) + { + Search = SearchSettings.Default; + } + else + { + if (Search.MaximumResultCount <= 0) { - appSettings.Columns.OcrColumnWidth = OCR_COLUMN_MIN_WIDTH; + Search.MaximumResultCount = DEFAULT_MAXIMUM_SEARCH_RESULT_COUNT; } } } diff --git a/Models/Settings/SettingsStorage.cs b/Models/Settings/SettingsStorage.cs index 86823fd..a752642 100644 --- a/Models/Settings/SettingsStorage.cs +++ b/Models/Settings/SettingsStorage.cs @@ -21,7 +21,7 @@ public static AppSettings LoadSettings() result = jsonSerializer.Deserialize(jsonTextReader); } } - AppSettings.ValidateAndCorrect(result); + result = AppSettings.ValidateAndCorrect(result); } catch { diff --git a/Models/SqlDump/ColumnDefinition.cs b/Models/SqlDump/ColumnDefinition.cs new file mode 100644 index 0000000..c6c7daa --- /dev/null +++ b/Models/SqlDump/ColumnDefinition.cs @@ -0,0 +1,14 @@ +namespace LibgenDesktop.Models.SqlDump +{ + internal class ColumnDefinition + { + public ColumnDefinition(string columnName, ColumnType columnType) + { + ColumnName = columnName.ToLower(); + ColumnType = columnType; + } + + public string ColumnName { get; } + public ColumnType ColumnType { get; } + } +} \ No newline at end of file diff --git a/Models/SqlDump/ColumnType.cs b/Models/SqlDump/ColumnType.cs new file mode 100644 index 0000000..852d7dd --- /dev/null +++ b/Models/SqlDump/ColumnType.cs @@ -0,0 +1,10 @@ +namespace LibgenDesktop.Models.SqlDump +{ + internal enum ColumnType + { + INT = 1, + BIGINT, + CHAR_OR_VARCHAR, + TIMESTAMP + } +} diff --git a/Models/SqlDump/ParsedTableDefinition.cs b/Models/SqlDump/ParsedTableDefinition.cs new file mode 100644 index 0000000..303c4ea --- /dev/null +++ b/Models/SqlDump/ParsedTableDefinition.cs @@ -0,0 +1,9 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LibgenDesktop.Models.SqlDump +{ +} diff --git a/Models/SqlDump/SqlDumpReader.cs b/Models/SqlDump/SqlDumpReader.cs index 50589be..19b9fef 100644 --- a/Models/SqlDump/SqlDumpReader.cs +++ b/Models/SqlDump/SqlDumpReader.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using LibgenDesktop.Models.Entities; +using SharpCompress.Archives.GZip; using SharpCompress.Archives.Rar; using SharpCompress.Archives.Zip; @@ -11,20 +11,37 @@ namespace LibgenDesktop.Models.SqlDump { internal class SqlDumpReader : IDisposable { - public class ReadRowsProgressEventArgs : EventArgs + internal enum LineCommand { - public long CurrentPosition { get; set; } - public long TotalLength { get; set; } - public int RowsParsed { get; set; } + CREATE_TABLE = 1, + INSERT, + OTHER + } + + internal class ParsedTableDefinition + { + public string TableName { get; set; } + public List Columns { get; set; } + } + + internal class ParsedColumnDefinition + { + public ParsedColumnDefinition(string columnName, ColumnType columnType) + { + ColumnName = columnName; + ColumnType = columnType; + } + + public string ColumnName { get; set; } + public ColumnType ColumnType { get; set; } } - private readonly long fileSize; private readonly StreamReader streamReader; private readonly ZipArchive zipArchive; private readonly RarArchive rarArchive; + private readonly GZipArchive gZipArchive; private bool disposed = false; - private long currentFilePosition; public SqlDumpReader(string filePath) { @@ -34,141 +51,143 @@ public SqlDumpReader(string filePath) case ".zip": zipArchive = ZipArchive.Open(filePath); ZipArchiveEntry firstZipArchiveEntry = zipArchive.Entries.First(); - fileSize = firstZipArchiveEntry.Size; + FileSize = firstZipArchiveEntry.Size; streamReader = new StreamReader(firstZipArchiveEntry.OpenEntryStream()); break; case ".rar": rarArchive = RarArchive.Open(filePath); RarArchiveEntry firstRarArchiveEntry = rarArchive.Entries.First(); - fileSize = firstRarArchiveEntry.Size; + FileSize = firstRarArchiveEntry.Size; streamReader = new StreamReader(firstRarArchiveEntry.OpenEntryStream()); break; + case ".gz": + gZipArchive = GZipArchive.Open(filePath); + GZipArchiveEntry firstGZipArchiveEntry = gZipArchive.Entries.First(); + FileSize = firstGZipArchiveEntry.Size; + streamReader = new StreamReader(firstGZipArchiveEntry.OpenEntryStream()); + break; default: - fileSize = new FileInfo(filePath).Length; + FileSize = new FileInfo(filePath).Length; streamReader = new StreamReader(filePath); break; } + CurrentFilePosition = 0; } - public event EventHandler ReadRowsProgress; + public long CurrentFilePosition { get; private set; } + public long FileSize { get; } + public LineCommand CurrentLineCommand { get; private set; } + private string CurrentLine { get; set; } + + public void Dispose() + { + Dispose(true); + } + + public bool ReadLine() + { + if (streamReader.EndOfStream) + { + CurrentLineCommand = LineCommand.OTHER; + return false; + } + CurrentLine = streamReader.ReadLine(); + CurrentFilePosition = streamReader.BaseStream.Position; + if (CurrentLine.StartsWith("CREATE TABLE")) + { + CurrentLineCommand = LineCommand.CREATE_TABLE; + } + else if (CurrentLine.StartsWith("INSERT INTO")) + { + CurrentLineCommand = LineCommand.INSERT; + } + else + { + CurrentLineCommand = LineCommand.OTHER; + } + return true; + } - public IEnumerable ReadRows() + public ParsedTableDefinition ParseTableDefinition() { - currentFilePosition = 0; - int rowsParsed = 0; - bool updateTableParsed = false; - RaiseReadRowsProgressEvent(0); - while (!streamReader.EndOfStream) + ParsedTableDefinition result = new ParsedTableDefinition(); + result.TableName = ParseTableName(CurrentLine); + result.Columns = new List(); + bool tableParsed = false; + while (ReadLine()) { - string line = streamReader.ReadLine(); - currentFilePosition += line.Length + 1; - if (line.StartsWith("CREATE TABLE")) + string tableLine = CurrentLine.Trim(); + if (tableLine.StartsWith("`")) { - string tableName = ParseTableName(line); - if (tableName == "updated") + string[] tableLineParts = tableLine.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + string columnName = tableLineParts[0].Trim('`'); + string columnTypeString = tableLineParts[1].ToLower(); + ColumnType? columnType; + if (columnTypeString.StartsWith("char") || columnTypeString.StartsWith("varchar")) { - while (!streamReader.EndOfStream) - { - line = streamReader.ReadLine(); - currentFilePosition += line.Length + 1; - if (line.StartsWith("CREATE TABLE")) - { - break; - } - if (line.StartsWith("INSERT INTO `updated`")) - { - if (line.StartsWith("INSERT INTO `updated` (`ID`, `Title`, `VolumeInfo`, `Series`, `Periodical`, `Author`, `Year`, `Edition`, `Publisher`, `City`, `Pages`, `PagesInFile`, `Language`, `Topic`, `Library`, `Issue`, `Identifier`, `ISSN`, `ASIN`, `UDC`, `LBC`, `DDC`, `LCC`, `Doi`, `Googlebookid`, `OpenLibraryID`, `Commentary`, `DPI`, `Color`, `Cleaned`, `Orientation`, `Paginated`, `Scanned`, `Bookmarked`, `Searchable`, `Filesize`, `Extension`, `MD5`, `Generic`, `Visible`, `Locator`, `Local`, `TimeAdded`, `TimeLastModified`, `Coverurl`, `Tags`, `IdentifierWODash`) VALUES (")) - { - int position = line.IndexOf("VALUES (") + 8; - bool endOfBatchFound = false; - while (!endOfBatchFound) - { - rowsParsed++; - Book book = new Book(); - book.Id = rowsParsed; - book.ExtendedProperties = new Book.BookExtendedProperties(); - book.ExtendedProperties.LibgenId = ParseInt32(line, ref position); - book.Title = ParseString(line, ref position); - book.ExtendedProperties.VolumeInfo = ParseString(line, ref position); - book.Series = ParseString(line, ref position); - book.ExtendedProperties.Periodical = ParseString(line, ref position); - book.Authors = ParseString(line, ref position); - book.Year = ParseString(line, ref position); - book.ExtendedProperties.Edition = ParseString(line, ref position); - book.Publisher = ParseString(line, ref position); - book.ExtendedProperties.City = ParseString(line, ref position); - book.ExtendedProperties.Pages = ParseString(line, ref position); - book.ExtendedProperties.PagesInFile = ParseInt32(line, ref position); - book.ExtendedProperties.Language = ParseString(line, ref position); - book.ExtendedProperties.Topic = ParseString(line, ref position); - book.ExtendedProperties.Library = ParseString(line, ref position); - book.ExtendedProperties.Issue = ParseString(line, ref position); - book.ExtendedProperties.Identifier = ParseString(line, ref position); - book.ExtendedProperties.Issn = ParseString(line, ref position); - book.ExtendedProperties.Asin = ParseString(line, ref position); - book.ExtendedProperties.Udc = ParseString(line, ref position); - book.ExtendedProperties.Lbc = ParseString(line, ref position); - book.ExtendedProperties.Ddc = ParseString(line, ref position); - book.ExtendedProperties.Lcc = ParseString(line, ref position); - book.ExtendedProperties.Doi = ParseString(line, ref position); - book.ExtendedProperties.GoogleBookid = ParseString(line, ref position); - book.ExtendedProperties.OpenLibraryId = ParseString(line, ref position); - book.ExtendedProperties.Commentary = ParseString(line, ref position); - book.ExtendedProperties.Dpi = ParseInt32(line, ref position); - book.ExtendedProperties.Color = ParseString(line, ref position); - book.ExtendedProperties.Cleaned = ParseString(line, ref position); - book.ExtendedProperties.Orientation = ParseString(line, ref position); - book.ExtendedProperties.Paginated = ParseString(line, ref position); - book.ExtendedProperties.Scanned = ParseString(line, ref position); - book.ExtendedProperties.Bookmarked = ParseString(line, ref position); - book.Searchable = ParseString(line, ref position); - book.SizeInBytes = ParseInt64(line, ref position); - book.Format = ParseString(line, ref position); - book.ExtendedProperties.Md5Hash = ParseString(line, ref position); - book.ExtendedProperties.Generic = ParseString(line, ref position); - book.ExtendedProperties.Visible = ParseString(line, ref position); - book.ExtendedProperties.Locator = ParseString(line, ref position); - book.ExtendedProperties.Local = ParseInt32(line, ref position); - book.ExtendedProperties.AddedDateTime = ParseDateTime(line, ref position); - book.ExtendedProperties.LastModifiedDateTime = ParseDateTime(line, ref position); - book.ExtendedProperties.CoverUrl = ParseString(line, ref position); - book.ExtendedProperties.Tags = ParseString(line, ref position); - book.ExtendedProperties.IdentifierPlain = ParseString(line, ref position); - yield return book; - if (line[position] == ';') - { - endOfBatchFound = true; - } - else - { - position += 2; - } - } - } - else - { - throw new Exception($"Unexpected set of columns in the line:\r\n{line}"); - } - } - RaiseReadRowsProgressEvent(rowsParsed); - } - updateTableParsed = true; + columnType = ColumnType.CHAR_OR_VARCHAR; + } + else if (columnTypeString.StartsWith("int")) + { + columnType = ColumnType.INT; + } + else if (columnTypeString.StartsWith("bigint")) + { + columnType = ColumnType.BIGINT; + } + else if (columnTypeString.StartsWith("timestamp")) + { + columnType = ColumnType.TIMESTAMP; + } + else + { + columnType = null; + } + if (columnType.HasValue) + { + result.Columns.Add(new ParsedColumnDefinition(columnName, columnType.Value)); } } - else + if (tableLine.EndsWith(";")) { - RaiseReadRowsProgressEvent(rowsParsed); + tableParsed = true; + break; } } - if (!updateTableParsed) + if (!tableParsed) { - throw new Exception("Couldn't find \"updated\" table rows in the dump file."); + throw new Exception($"Couldn't parse definition of the table \"{result.TableName}\"."); } + return result; } - public void Dispose() + public IEnumerable ParseImportObjects(List> objectSetters) where T : new() { - Dispose(true); + do + { + int position = CurrentLine.IndexOf("VALUES (") + 8; + bool endOfBatchFound = false; + while (!endOfBatchFound) + { + T newObject = new T(); + foreach (Action objectSetter in objectSetters) + { + string stringValue = ParseString(CurrentLine, ref position); + objectSetter?.Invoke(newObject, stringValue); + } + yield return newObject; + if (CurrentLine[position] == ';') + { + endOfBatchFound = true; + } + else + { + position += 2; + } + } + ReadLine(); + } + while (CurrentLineCommand == LineCommand.INSERT); } protected virtual void Dispose(bool disposing) @@ -185,37 +204,40 @@ protected virtual void Dispose(bool disposing) { rarArchive.Dispose(); } + if (gZipArchive != null) + { + gZipArchive.Dispose(); + } streamReader.Dispose(); } disposed = true; } } - protected virtual void RaiseReadRowsProgressEvent(int rowsParsed) - { - ReadRowsProgress?.Invoke(this, new ReadRowsProgressEventArgs - { - CurrentPosition = streamReader.BaseStream.Position, - TotalLength = fileSize, - RowsParsed = rowsParsed - }); - } - private string ParseTableName(string line) { - int position = 0; + int position = "CREATE TABLE".Length; int tableNameStartPosition = 0; while (position < line.Length) { - if (line[position] == '`') + if (line[position] != ' ') { if (tableNameStartPosition == 0) { - tableNameStartPosition = position + 1; + tableNameStartPosition = position; } - else + } + else + { + if (tableNameStartPosition != 0) { - return line.Substring(tableNameStartPosition, position - tableNameStartPosition); + string tableName = line.Substring(tableNameStartPosition, position - tableNameStartPosition); + int lastDotPosition = tableName.LastIndexOf('.'); + if (lastDotPosition != -1) + { + tableName = tableName.Substring(lastDotPosition + 1); + } + return tableName.Trim('`'); } } position++; @@ -223,57 +245,61 @@ private string ParseTableName(string line) throw new Exception($"Couldn't parse table name from the line:\r\n{line}"); } - private int ParseInt32(string line, ref int position) + private void PopulateBookField(NonFictionBook book, string fieldName, string line, ref int position) { - int startPosition = position; - while (Char.IsDigit(line[position])) - { - position++; - } - int result = Int32.Parse(line.Substring(startPosition, position - startPosition)); - position++; - return result; } - private long ParseInt64(string line, ref int position) + private string ParseString(string line, ref int position) { - int startPosition = position; - while (Char.IsDigit(line[position])) + bool openQuote = false; + bool closingQuote = false; + if (line[position] == '\'') { + openQuote = true; position++; } - long result = Int64.Parse(line.Substring(startPosition, position - startPosition)); - position++; - return result; - } - - private string ParseString(string line, ref int position) - { - position++; int startPosition = position; - while (position < line.Length && line[position] != '\'') + while (position < line.Length) { - if (line[position] == '\\' && line[position + 1] == '\'') + if ((line[position] == ',' || line[position] == ')') && !openQuote) + { + break; + } + if (line[position] == '\'') + { + if (openQuote) + { + closingQuote = true; + break; + } + else + { + throw new Exception($"Matching opening quote was not found for a closing quote at position {startPosition} in the line:\r\n{line}"); + } + } + if (line[position] == '\\' && (line[position + 1] == '\'' || line[position + 1] == '\\')) { position++; } position++; } - if (position == line.Length) + if (openQuote && !closingQuote) { throw new Exception($"Closing quote is missing after position {startPosition} in the line:\r\n{line}"); } else { - string result = line.Substring(startPosition, position - startPosition).Replace("\\'", "'"); - position += 2; + string result = line.Substring(startPosition, position - startPosition).Replace(@"\\", @"\").Replace(@"\'", "'"); + if (closingQuote) + { + position += 2; + } + else + { + position++; + } return result; } } - - private DateTime ParseDateTime(string line, ref int position) - { - return DateTime.ParseExact(ParseString(line, ref position), "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture); - } } } diff --git a/Models/SqlDump/TableDefinition.cs b/Models/SqlDump/TableDefinition.cs new file mode 100644 index 0000000..758f71d --- /dev/null +++ b/Models/SqlDump/TableDefinition.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Globalization; + +namespace LibgenDesktop.Models.SqlDump +{ + internal abstract class TableDefinition + { + public TableDefinition(string tableName, TableType tableType) + { + TableName = tableName; + TableType = tableType; + Columns = new Dictionary(); + } + + public string TableName { get; } + public TableType TableType { get; } + public Dictionary Columns { get; } + + protected void AddColumn(string columnName, ColumnType columnType) + { + Columns.Add(columnName.ToLower(), new ColumnDefinition(columnName, columnType)); + } + + protected int ParseInt(string value) + { + return Int32.Parse(value); + } + + protected long ParseLong(string value) + { + return Int64.Parse(value); + } + + protected DateTime ParseDateTime(string value) + { + return DateTime.ParseExact(value, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture); + } + + protected DateTime? ParseNullableDateTime(string value) + { + if (DateTime.TryParseExact(value, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime result)) + { + return result; + } + return null; + } + } + + internal abstract class TableDefinition : TableDefinition + { + public TableDefinition(string tableName, TableType tableType) + : base(tableName, tableType) + { + ColumnSetters = new Dictionary>(); + } + + public Dictionary> ColumnSetters { get; } + + public void AddColumn(string columnName, ColumnType columnType, Action setter) + { + columnName = columnName.ToLower(); + AddColumn(columnName, columnType); + ColumnSetters.Add(columnName, setter); + } + + public List> GetSortedColumnSetters(IEnumerable columnNames) + { + List> result = new List>(); + foreach (string columnName in columnNames) + { + if (ColumnSetters.TryGetValue(columnName.ToLower(), out Action columnSetter)) + { + result.Add(columnSetter); + } + else + { + result.Add(null); + } + } + return result; + } + } +} diff --git a/Models/SqlDump/TableDefinitions.cs b/Models/SqlDump/TableDefinitions.cs new file mode 100644 index 0000000..a0fb11d --- /dev/null +++ b/Models/SqlDump/TableDefinitions.cs @@ -0,0 +1,177 @@ +using System.Collections.Generic; +using LibgenDesktop.Models.Entities; + +namespace LibgenDesktop.Models.SqlDump +{ + internal static class TableDefinitions + { + internal class NonFictionTableDefinition : TableDefinition + { + public NonFictionTableDefinition() + : base("updated", TableType.NON_FICTION) + { + AddColumn("ID", ColumnType.INT, (book, value) => book.LibgenId = ParseInt(value)); + AddColumn("Title", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Title = value); + AddColumn("VolumeInfo", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.VolumeInfo = value); + AddColumn("Series", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Series = value); + AddColumn("Periodical", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Periodical = value); + AddColumn("Author", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Authors = value); + AddColumn("Year", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Year = value); + AddColumn("Edition", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Edition = value); + AddColumn("Publisher", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Publisher = value); + AddColumn("City", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.City = value); + AddColumn("Pages", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Pages = value); + AddColumn("PagesInFile", ColumnType.INT, (book, value) => book.PagesInFile = ParseInt(value)); + AddColumn("Language", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Language = value); + AddColumn("Topic", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Topic = value); + AddColumn("Library", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Library = value); + AddColumn("Issue", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Issue = value); + AddColumn("Identifier", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Identifier = value); + AddColumn("ISSN", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Issn = value); + AddColumn("ASIN", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Asin = value); + AddColumn("UDC", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Udc = value); + AddColumn("LBC", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Lbc = value); + AddColumn("DDC", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Ddc = value); + AddColumn("LCC", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Lcc = value); + AddColumn("Doi", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Doi = value); + AddColumn("Googlebookid", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.GoogleBookId = value); + AddColumn("OpenLibraryID", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.OpenLibraryId = value); + AddColumn("Commentary", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Commentary = value); + AddColumn("DPI", ColumnType.INT, (book, value) => book.Dpi = ParseInt(value)); + AddColumn("Color", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Color = value); + AddColumn("Cleaned", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Cleaned = value); + AddColumn("Orientation", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Orientation = value); + AddColumn("Paginated", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Paginated = value); + AddColumn("Scanned", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Scanned = value); + AddColumn("Bookmarked", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Bookmarked = value); + AddColumn("Searchable", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Searchable = value); + AddColumn("Filesize", ColumnType.BIGINT, (book, value) => book.SizeInBytes = ParseLong(value)); + AddColumn("Extension", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Format = value); + AddColumn("MD5", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Md5Hash = value); + AddColumn("Generic", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Generic = value); + AddColumn("Visible", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Visible = value); + AddColumn("Locator", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Locator = value); + AddColumn("Local", ColumnType.INT, (book, value) => book.Local = ParseInt(value)); + AddColumn("TimeAdded", ColumnType.TIMESTAMP, (book, value) => book.AddedDateTime = ParseDateTime(value)); + AddColumn("TimeLastModified", ColumnType.TIMESTAMP, (book, value) => book.LastModifiedDateTime = ParseDateTime(value)); + AddColumn("Coverurl", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.CoverUrl = value); + AddColumn("Tags", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Tags = value); + AddColumn("IdentifierWODash", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.IdentifierPlain = value); + } + } + + internal class FictionTableDefinition : TableDefinition + { + public FictionTableDefinition() + : base("main", TableType.FICTION) + { + AddColumn("ID", ColumnType.INT, (book, value) => book.LibgenId = ParseInt(value)); + AddColumn("AuthorFamily1", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.AuthorFamily1 = value); + AddColumn("AuthorName1", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.AuthorName1 = value); + AddColumn("AuthorSurname1", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.AuthorSurname1 = value); + AddColumn("Role1", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Role1 = value); + AddColumn("Pseudonim1", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Pseudonim1 = value); + AddColumn("AuthorFamily2", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.AuthorFamily2 = value); + AddColumn("AuthorName2", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.AuthorName2 = value); + AddColumn("AuthorSurname2", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.AuthorSurname2 = value); + AddColumn("Role2", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Role2 = value); + AddColumn("Pseudonim2", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Pseudonim2 = value); + AddColumn("AuthorFamily3", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.AuthorFamily3 = value); + AddColumn("AuthorName3", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.AuthorName3 = value); + AddColumn("AuthorSurname3", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.AuthorSurname3 = value); + AddColumn("Role3", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Role3 = value); + AddColumn("Pseudonim3", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Pseudonim3 = value); + AddColumn("AuthorFamily4", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.AuthorFamily4 = value); + AddColumn("AuthorName4", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.AuthorName4 = value); + AddColumn("AuthorSurname4", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.AuthorSurname4 = value); + AddColumn("Role4", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Role4 = value); + AddColumn("Pseudonim4", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Pseudonim4 = value); + AddColumn("Series1", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Series1 = value); + AddColumn("Series2", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Series2 = value); + AddColumn("Series3", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Series3 = value); + AddColumn("Series4", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Series4 = value); + AddColumn("Title", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Title = value); + AddColumn("Extension", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Format = value); + AddColumn("Version", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Version = value); + AddColumn("Filesize", ColumnType.INT, (book, value) => book.SizeInBytes = ParseLong(value)); + AddColumn("MD5", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Md5Hash = value); + AddColumn("Path", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Path = value); + AddColumn("Language", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Language = value); + AddColumn("Pages", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Pages = value); + AddColumn("Identifier", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Identifier = value); + AddColumn("Year", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Year = value); + AddColumn("Publisher", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Publisher = value); + AddColumn("Edition", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Edition = value); + AddColumn("Commentary", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Commentary = value); + AddColumn("TimeAdded", ColumnType.TIMESTAMP, (book, value) => book.AddedDateTime = ParseNullableDateTime(value)); + AddColumn("TimeLastModified", ColumnType.TIMESTAMP, (book, value) => book.LastModifiedDateTime = ParseDateTime(value)); + AddColumn("RussianAuthorFamily", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.RussianAuthorFamily = value); + AddColumn("RussianAuthorName", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.RussianAuthorName = value); + AddColumn("RussianAuthorSurname", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.RussianAuthorSurname = value); + AddColumn("Cover", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Cover = value); + AddColumn("GooglebookID", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.GoogleBookId = value); + AddColumn("ASIN", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Asin = value); + AddColumn("AuthorHash", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.AuthorHash = value); + AddColumn("TitleHash", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.TitleHash = value); + AddColumn("Visible", ColumnType.CHAR_OR_VARCHAR, (book, value) => book.Visible = value); + } + } + + internal class SciMagTableDefinition : TableDefinition + { + public SciMagTableDefinition() + : base("scimag", TableType.SCI_MAG) + { + AddColumn("ID", ColumnType.INT, (article, value) => article.LibgenId = ParseInt(value)); + AddColumn("DOI", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.Doi = value); + AddColumn("DOI2", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.Doi2 = value); + AddColumn("Title", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.Title = value); + AddColumn("Author", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.Authors = value); + AddColumn("Year", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.Year = value); + AddColumn("Month", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.Month = value); + AddColumn("Day", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.Day = value); + AddColumn("Volume", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.Volume = value); + AddColumn("Issue", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.Issue = value); + AddColumn("First_page", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.FirstPage = value); + AddColumn("Last_page", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.LastPage = value); + AddColumn("Journal", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.Journal = value); + AddColumn("ISBN", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.Isbn = value); + AddColumn("ISSNP", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.Issnp = value); + AddColumn("ISSNE", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.Issne = value); + AddColumn("MD5", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.Md5Hash = value); + AddColumn("Filesize", ColumnType.INT, (article, value) => article.SizeInBytes = ParseLong(value)); + AddColumn("TimeAdded", ColumnType.TIMESTAMP, (article, value) => article.AddedDateTime = ParseNullableDateTime(value)); + AddColumn("JOURNALID", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.JournalId = value); + AddColumn("AbstractURL", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.AbstractUrl = value); + AddColumn("Attribute1", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.Attribute1 = value); + AddColumn("Attribute2", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.Attribute2 = value); + AddColumn("Attribute3", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.Attribute3 = value); + AddColumn("Attribute4", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.Attribute4 = value); + AddColumn("Attribute5", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.Attribute5 = value); + AddColumn("Attribute6", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.Attribute6 = value); + AddColumn("visible", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.Visible = value); + AddColumn("PubmedID", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.PubmedId = value); + AddColumn("PMC", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.Pmc = value); + AddColumn("PII", ColumnType.CHAR_OR_VARCHAR, (article, value) => article.Pii = value); + } + } + + static TableDefinitions() + { + NonFiction = new NonFictionTableDefinition(); + Fiction = new FictionTableDefinition(); + SciMag = new SciMagTableDefinition(); + AllTables = new Dictionary + { + { NonFiction.TableName, NonFiction }, + { Fiction.TableName, Fiction }, + { SciMag.TableName, SciMag } + }; + } + + public static Dictionary AllTables { get; } + public static TableDefinition NonFiction { get; } + public static TableDefinition Fiction { get; } + public static TableDefinition SciMag { get; } + } +} diff --git a/Models/SqlDump/TableType.cs b/Models/SqlDump/TableType.cs new file mode 100644 index 0000000..3979153 --- /dev/null +++ b/Models/SqlDump/TableType.cs @@ -0,0 +1,10 @@ +namespace LibgenDesktop.Models.SqlDump +{ + internal enum TableType + { + NON_FICTION = 1, + FICTION, + SCI_MAG, + UNKNOWN + } +} diff --git a/Models/Utils/AsyncBookCollection.cs b/Models/Utils/AsyncBookCollection.cs index 8d1c53e..879cd02 100644 --- a/Models/Utils/AsyncBookCollection.cs +++ b/Models/Utils/AsyncBookCollection.cs @@ -8,22 +8,22 @@ namespace LibgenDesktop.Models.Utils { - internal class AsyncBookCollection : IReadOnlyList, IList, INotifyCollectionChanged, INotifyPropertyChanged + internal class AsyncBookCollection : IReadOnlyList, IList, INotifyCollectionChanged, INotifyPropertyChanged { - internal class BookEnumerator : IEnumerator, IEnumerator + internal class BookEnumerator : IEnumerator, IEnumerator { - private readonly List sourceList; + private readonly List sourceList; private readonly int sourceListItemLimit; private int currentIndex; - public BookEnumerator(List sourceList, int sourceListItemLimit) + public BookEnumerator(List sourceList, int sourceListItemLimit) { this.sourceList = sourceList; this.sourceListItemLimit = sourceListItemLimit; currentIndex = -1; } - public Book Current => sourceList[currentIndex]; + public NonFictionBook Current => sourceList[currentIndex]; object IEnumerator.Current => Current; @@ -34,7 +34,6 @@ public void Dispose() public bool MoveNext() { currentIndex++; - System.Diagnostics.Debug.WriteLine("MoveNext " + currentIndex.ToString()); return currentIndex < sourceListItemLimit; } @@ -44,19 +43,19 @@ public void Reset() } } - private readonly List internalList; + private readonly List internalList; private readonly SynchronizationContext synchronizationContext; private int reportedBookCount; public AsyncBookCollection() { - internalList = new List(); + internalList = new List(); synchronizationContext = SynchronizationContext.Current; reportedBookCount = 0; } - public Book this[int index] => internalList[index]; + public NonFictionBook this[int index] => internalList[index]; public int Count => reportedBookCount; public int AddedBookCount => internalList.Count; @@ -79,12 +78,12 @@ public void SetCapacity(int capacity) internalList.Capacity = capacity; } - public void AddBook(Book book) + public void AddBook(NonFictionBook book) { internalList.Add(book); } - public void AddBooks(IEnumerable books) + public void AddBooks(IEnumerable books) { internalList.AddRange(books); } @@ -95,7 +94,7 @@ public void UpdateReportedBookCount() NotifyReset(); } - public IEnumerator GetEnumerator() + public IEnumerator GetEnumerator() { return new BookEnumerator(internalList, reportedBookCount); } @@ -112,7 +111,7 @@ public int Add(object value) public bool Contains(object value) { - return internalList.Contains((Book)value); + return internalList.Contains((NonFictionBook)value); } public void Clear() @@ -124,7 +123,7 @@ public void Clear() public int IndexOf(object value) { - return internalList.IndexOf((Book)value); + return internalList.IndexOf((NonFictionBook)value); } public void Insert(int index, object value) diff --git a/Models/Utils/Formatters.cs b/Models/Utils/Formatters.cs index 00a407f..bf3330f 100644 --- a/Models/Utils/Formatters.cs +++ b/Models/Utils/Formatters.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using System.Runtime.CompilerServices; using System.Text; namespace LibgenDesktop.Models.Utils @@ -18,6 +19,13 @@ static Formatters() public static NumberFormatInfo ThousandsSeparatedNumberFormat => thousandsSeparatedNumberFormat; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ToFormattedString(this int value) + { + return value.ToString("N0", ThousandsSeparatedNumberFormat); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string FileSizeToString(long fileSize, bool showBytes) { int postfixIndex = fileSize != 0 ? (int)Math.Floor(Math.Log(fileSize) / Math.Log(1024)) : 0; diff --git a/Resources/LibgenDesktop_logo.png b/Resources/LibgenDesktop_logo.png new file mode 100644 index 0000000..d01ce4d Binary files /dev/null and b/Resources/LibgenDesktop_logo.png differ diff --git a/ViewModels/CreateDatabaseViewModel.cs b/ViewModels/CreateDatabaseViewModel.cs new file mode 100644 index 0000000..1a8bfe8 --- /dev/null +++ b/ViewModels/CreateDatabaseViewModel.cs @@ -0,0 +1,177 @@ +using System; +using System.IO; +using System.Linq; +using LibgenDesktop.Common; +using LibgenDesktop.Infrastructure; +using LibgenDesktop.Models; + +namespace LibgenDesktop.ViewModels +{ + internal class CreateDatabaseViewModel : ViewModel + { + internal enum EventType + { + DATABASE_NOT_FOUND = 1, + DATABASE_CORRUPTED, + FIRST_RUN + } + + private readonly MainModel mainModel; + private EventType eventType; + private string header; + private bool isCreateDatabaseSelected; + private bool isOpenDatabaseSelected; + + public CreateDatabaseViewModel(MainModel mainModel) + { + this.mainModel = mainModel; + OkButtonCommand = new Command(OkButtonClick); + CancelButtonCommand = new Command(CancelButtonClick); + Initialize(); + } + + public string Header + { + get + { + return header; + } + set + { + header = value; + NotifyPropertyChanged(); + } + } + + public bool IsCreateDatabaseSelected + { + get + { + return isCreateDatabaseSelected; + } + set + { + isCreateDatabaseSelected = value; + NotifyPropertyChanged(); + } + } + + public bool IsOpenDatabaseSelected + { + get + { + return isOpenDatabaseSelected; + } + set + { + isOpenDatabaseSelected = value; + NotifyPropertyChanged(); + } + } + + public Command OkButtonCommand { get; } + public Command CancelButtonCommand { get; } + + private void Initialize() + { + UpdateHeaderAndEventType(); + isCreateDatabaseSelected = true; + isOpenDatabaseSelected = false; + } + + private void UpdateHeaderAndEventType(string databaseFileName = null) + { + switch (mainModel.LocalDatabaseStatus) + { + case MainModel.DatabaseStatus.NOT_SET: + eventType = EventType.FIRST_RUN; + header = "Добро пожаловать в Libgen Desktop"; + break; + case MainModel.DatabaseStatus.NOT_FOUND: + eventType = EventType.DATABASE_NOT_FOUND; + header = $"База данных {databaseFileName ?? GetDatabaseFileName()} не найдена"; + break; + case MainModel.DatabaseStatus.CORRUPTED: + eventType = EventType.DATABASE_CORRUPTED; + header = $"База данных {databaseFileName ?? GetDatabaseFileName()} повреждена"; + break; + default: + throw new Exception($"Unexpected database status: {mainModel.LocalDatabaseStatus}."); + } + header += ". Выберите действие:"; + } + + private string GetDatabaseFileName() + { + return Path.GetFileName(mainModel.AppSettings.DatabaseFileName); + } + + private void OkButtonClick() + { + if (IsCreateDatabaseSelected) + { + SaveFileDialogParameters saveFileDialogParameters = new SaveFileDialogParameters + { + DialogTitle = "Сохранение новой базы данных", + Filter = "Базы данных (*.db)|*.db|Все файлы (*.*)|*.*", + OverwritePrompt = true + }; + if (eventType == EventType.DATABASE_CORRUPTED) + { + string databaseFilePath = mainModel.GetDatabaseFullPath(mainModel.AppSettings.DatabaseFileName); + saveFileDialogParameters.InitialDirectory = Path.GetDirectoryName(databaseFilePath); + saveFileDialogParameters.InitialFileName = Path.GetFileName(databaseFilePath); + } + else + { + saveFileDialogParameters.InitialDirectory = mainModel.GetCurrentDirectory(); + saveFileDialogParameters.InitialFileName = Constants.DEFAULT_DATABASE_FILE_NAME; + } + SaveFileDialogResult saveFileDialogResult = WindowManager.ShowSaveFileDialog(saveFileDialogParameters); + if (saveFileDialogResult.DialogResult) + { + if (mainModel.CreateDatabase(saveFileDialogResult.SelectedFilePath)) + { + mainModel.AppSettings.DatabaseFileName = mainModel.GetDatabaseNormalizedPath(saveFileDialogResult.SelectedFilePath); + mainModel.SaveSettings(); + CurrentWindowContext.CloseDialog(true); + } + else + { + WindowManager.ShowMessageBox("Ошибка", "Не удалось создать базу данных."); + } + } + } + else + { + OpenFileDialogParameters openFileDialogParameters = new OpenFileDialogParameters + { + DialogTitle = "Выбор базы данных", + Filter = "Базы данных (*.db)|*.db|Все файлы (*.*)|*.*", + Multiselect = false + }; + OpenFileDialogResult openFileDialogResult = WindowManager.ShowOpenFileDialog(openFileDialogParameters); + if (openFileDialogResult.DialogResult) + { + string databaseFilePath = openFileDialogResult.SelectedFilePaths.First(); + if (mainModel.OpenDatabase(databaseFilePath)) + { + mainModel.AppSettings.DatabaseFileName = mainModel.GetDatabaseNormalizedPath(databaseFilePath); + mainModel.SaveSettings(); + CurrentWindowContext.CloseDialog(true); + } + else + { + UpdateHeaderAndEventType(Path.GetFileName(databaseFilePath)); + NotifyPropertyChanged(nameof(Header)); + } + } + } + } + + private void CancelButtonClick() + { + CurrentWindowContext.CloseDialog(false); + } + } +} diff --git a/ViewModels/DownloadManagerTabViewModel.cs b/ViewModels/DownloadManagerTabViewModel.cs new file mode 100644 index 0000000..a589d99 --- /dev/null +++ b/ViewModels/DownloadManagerTabViewModel.cs @@ -0,0 +1,13 @@ +using LibgenDesktop.Infrastructure; +using LibgenDesktop.Models; + +namespace LibgenDesktop.ViewModels +{ + internal class DownloadManagerTabViewModel : TabViewModel + { + public DownloadManagerTabViewModel(MainModel mainModel, IWindowContext mainWindowContext) + : base(mainModel, mainWindowContext, "Загрузки") + { + } + } +} diff --git a/ViewModels/FictionDetailsWindowViewModel.cs b/ViewModels/FictionDetailsWindowViewModel.cs new file mode 100644 index 0000000..26ab7e3 --- /dev/null +++ b/ViewModels/FictionDetailsWindowViewModel.cs @@ -0,0 +1,213 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Windows.Media.Imaging; +using LibgenDesktop.Common; +using LibgenDesktop.Infrastructure; +using LibgenDesktop.Models; +using LibgenDesktop.Models.Entities; +using LibgenDesktop.Models.Settings; + +namespace LibgenDesktop.ViewModels +{ + internal class FictionDetailsWindowViewModel : ViewModel + { + private readonly MainModel mainModel; + private FictionBook book; + private bool bookCoverNotLoadedDueToOfflineMode; + private bool bookCoverLoading; + private bool bookCoverLoadingFailed; + private bool noCover; + private bool bookCoverVisible; + private BitmapImage bookCover; + + public FictionDetailsWindowViewModel(MainModel mainModel, FictionBook book) + { + this.mainModel = mainModel; + this.book = book; + DownloadBookCommand = new Command(DownloadBook); + CloseCommand = new Command(CloseWindow); + WindowClosedCommand = new Command(WindowClosed); + Initialize(); + } + + public string WindowTitle { get; private set; } + public int WindowWidth { get; set; } + public int WindowHeight { get; set; } + public bool IsInOfflineMode { get; private set; } + + public FictionBook Book + { + get + { + return book; + } + private set + { + book = value; + NotifyPropertyChanged(); + } + } + + public bool BookCoverNotLoadedDueToOfflineMode + { + get + { + return bookCoverNotLoadedDueToOfflineMode; + } + private set + { + bookCoverNotLoadedDueToOfflineMode = value; + NotifyPropertyChanged(); + } + } + + public bool BookCoverLoading + { + get + { + return bookCoverLoading; + } + private set + { + bookCoverLoading = value; + NotifyPropertyChanged(); + } + } + + public bool BookCoverLoadingFailed + { + get + { + return bookCoverLoadingFailed; + } + private set + { + bookCoverLoadingFailed = value; + NotifyPropertyChanged(); + } + } + + public bool NoCover + { + get + { + return noCover; + } + private set + { + noCover = value; + NotifyPropertyChanged(); + } + } + + public bool BookCoverVisible + { + get + { + return bookCoverVisible; + } + private set + { + bookCoverVisible = value; + NotifyPropertyChanged(); + } + } + + public BitmapImage BookCover + { + get + { + return bookCover; + } + private set + { + bookCover = value; + NotifyPropertyChanged(); + } + } + + public Command DownloadBookCommand { get; } + public Command CloseCommand { get; } + public Command WindowClosedCommand { get; } + + private async void Initialize() + { + WindowTitle = Book.Title; + WindowWidth = mainModel.AppSettings.Fiction.DetailsWindow.Width; + WindowHeight = mainModel.AppSettings.Fiction.DetailsWindow.Height; + if (mainModel.AppSettings.Network.OfflineMode) + { + IsInOfflineMode = true; + BookCoverNotLoadedDueToOfflineMode = true; + BookCoverLoading = false; + } + else + { + IsInOfflineMode = false; + BookCoverNotLoadedDueToOfflineMode = false; + BookCoverLoading = true; + } + BookCoverLoadingFailed = false; + NoCover = false; + BookCoverVisible = false; + BookCover = null; + if (Book.Cover != "1") + { + BookCoverNotLoadedDueToOfflineMode = false; + BookCoverLoading = false; + NoCover = true; + } + else + { + if (!IsInOfflineMode) + { + try + { + string bookCoverUrl = String.Concat(Book.LibgenId / 1000 * 1000, "/", Book.Md5Hash, ".jpg"); + WebClient webClient = new WebClient(); + byte[] imageData = await webClient.DownloadDataTaskAsync(new Uri(Constants.FICTION_COVER_URL_PREFIX + bookCoverUrl)); + BitmapImage bitmapImage = new BitmapImage(); + using (MemoryStream memoryStream = new MemoryStream(imageData)) + { + bitmapImage.BeginInit(); + bitmapImage.CacheOption = BitmapCacheOption.OnLoad; + bitmapImage.StreamSource = memoryStream; + bitmapImage.EndInit(); + bitmapImage.Freeze(); + } + BookCover = bitmapImage; + BookCoverVisible = true; + } + catch + { + BookCoverLoadingFailed = true; + } + BookCoverLoading = false; + } + } + } + + private void DownloadBook() + { + Process.Start(Constants.FICTION_DOWNLOAD_URL_PREFIX + Book.Md5Hash); + } + + private void CloseWindow() + { + IWindowContext currentWindowContext = WindowManager.GetWindowContext(this); + currentWindowContext.CloseDialog(false); + } + + private void WindowClosed() + { + mainModel.AppSettings.Fiction.DetailsWindow = new AppSettings.FictionDetailsWindowSettings + { + Width = WindowWidth, + Height = WindowHeight + }; + mainModel.SaveSettings(); + } + } +} diff --git a/ViewModels/FictionSearchResultsTabViewModel.cs b/ViewModels/FictionSearchResultsTabViewModel.cs new file mode 100644 index 0000000..a4dd754 --- /dev/null +++ b/ViewModels/FictionSearchResultsTabViewModel.cs @@ -0,0 +1,282 @@ +using System; +using System.Collections.ObjectModel; +using System.Threading; +using LibgenDesktop.Infrastructure; +using LibgenDesktop.Models; +using LibgenDesktop.Models.Entities; +using LibgenDesktop.Models.ProgressArgs; +using LibgenDesktop.Models.Utils; +using static LibgenDesktop.Models.Settings.AppSettings; + +namespace LibgenDesktop.ViewModels +{ + internal class FictionSearchResultsTabViewModel : TabViewModel + { + private readonly FictionColumnSettings columnSettings; + private ObservableCollection books; + private string searchQuery; + private string bookCount; + private bool isBookGridVisible; + private bool isSearchProgressPanelVisible; + private string searchProgressStatus; + private bool isStatusBarVisible; + + public FictionSearchResultsTabViewModel(MainModel mainModel, IWindowContext mainWindowContext, string searchQuery, ObservableCollection searchResults) + : base(mainModel, mainWindowContext, searchQuery) + { + columnSettings = mainModel.AppSettings.Fiction.Columns; + this.searchQuery = searchQuery; + books = searchResults; + OpenDetailsCommand = new Command(param => OpenDetails(param as FictionBook)); + SearchCommand = new Command(Search); + BookDataGridEnterKeyCommand = new Command(BookDataGridEnterKeyPressed); + Initialize(); + } + + public string SearchQuery + { + get + { + return searchQuery; + } + set + { + searchQuery = value; + NotifyPropertyChanged(); + } + } + + public ObservableCollection Books + { + get + { + return books; + } + set + { + books = value; + NotifyPropertyChanged(); + } + } + + public bool IsBookGridVisible + { + get + { + return isBookGridVisible; + } + set + { + isBookGridVisible = value; + NotifyPropertyChanged(); + } + } + + public int TitleColumnWidth + { + get + { + return columnSettings.TitleColumnWidth; + } + set + { + columnSettings.TitleColumnWidth = value; + } + } + + public int AuthorsColumnWidth + { + get + { + return columnSettings.AuthorsColumnWidth; + } + set + { + columnSettings.AuthorsColumnWidth = value; + } + } + + public int SeriesColumnWidth + { + get + { + return columnSettings.SeriesColumnWidth; + } + set + { + columnSettings.SeriesColumnWidth = value; + } + } + + public int YearColumnWidth + { + get + { + return columnSettings.YearColumnWidth; + } + set + { + columnSettings.YearColumnWidth = value; + } + } + + public int PublisherColumnWidth + { + get + { + return columnSettings.PublisherColumnWidth; + } + set + { + columnSettings.PublisherColumnWidth = value; + } + } + + public int FormatColumnWidth + { + get + { + return columnSettings.FormatColumnWidth; + } + set + { + columnSettings.FormatColumnWidth = value; + } + } + + public int FileSizeColumnWidth + { + get + { + return columnSettings.FileSizeColumnWidth; + } + set + { + columnSettings.FileSizeColumnWidth = value; + } + } + + public bool IsSearchProgressPanelVisible + { + get + { + return isSearchProgressPanelVisible; + } + set + { + isSearchProgressPanelVisible = value; + NotifyPropertyChanged(); + } + } + + public string SearchProgressStatus + { + get + { + return searchProgressStatus; + } + set + { + searchProgressStatus = value; + NotifyPropertyChanged(); + } + } + + public bool IsStatusBarVisible + { + get + { + return isStatusBarVisible; + } + set + { + isStatusBarVisible = value; + NotifyPropertyChanged(); + } + } + + public string BookCount + { + get + { + return bookCount; + } + set + { + bookCount = value; + NotifyPropertyChanged(); + } + } + + public FictionBook SelectedBook { get; set; } + + public Command OpenDetailsCommand { get; } + public Command SearchCommand { get; } + public Command BookDataGridEnterKeyCommand { get; } + + private void Initialize() + { + isBookGridVisible = true; + isStatusBarVisible = true; + isSearchProgressPanelVisible = false; + UpdateBookCount(); + Events.RaiseEvent(ViewModelEvent.RegisteredEventId.FOCUS_SEARCH_TEXT_BOX); + } + + private void UpdateBookCount() + { + BookCount = $"Найдено книг: {Books.Count.ToFormattedString()}"; + } + + private void BookDataGridEnterKeyPressed() + { + OpenDetails(SelectedBook); + } + + private void OpenDetails(FictionBook book) + { + FictionDetailsWindowViewModel detailsWindowViewModel = new FictionDetailsWindowViewModel(MainModel, book); + IWindowContext detailsWindowContext = WindowManager.CreateWindow(RegisteredWindows.WindowKey.FICTION_DETAILS_WINDOW, detailsWindowViewModel, MainWindowContext); + FictionDetailsWindowSettings detailsWindowSettings = MainModel.AppSettings.Fiction.DetailsWindow; + detailsWindowContext.ShowDialog(detailsWindowSettings.Width, detailsWindowSettings.Height); + } + + private async void Search() + { + if (!String.IsNullOrWhiteSpace(SearchQuery) && !IsSearchProgressPanelVisible) + { + Title = SearchQuery; + IsBookGridVisible = false; + IsStatusBarVisible = false; + IsSearchProgressPanelVisible = true; + UpdateSearchProgressStatus(0); + Progress searchProgressHandler = new Progress(HandleSearchProgress); + CancellationToken cancellationToken = new CancellationToken(); + ObservableCollection result = new ObservableCollection(); + try + { + result = await MainModel.SearchFictionAsync(SearchQuery, searchProgressHandler, cancellationToken); + } + catch (Exception exception) + { + ShowErrorWindow(exception); + } + Books = result; + UpdateBookCount(); + IsSearchProgressPanelVisible = false; + IsBookGridVisible = true; + IsStatusBarVisible = true; + } + } + + private void HandleSearchProgress(SearchProgress searchProgress) + { + UpdateSearchProgressStatus(searchProgress.ItemsFound); + } + + private void UpdateSearchProgressStatus(int booksFound) + { + SearchProgressStatus = $"Найдено книг: {booksFound.ToFormattedString()}"; + } + } +} diff --git a/ViewModels/ImportWindowViewModel.cs b/ViewModels/ImportWindowViewModel.cs new file mode 100644 index 0000000..214f550 --- /dev/null +++ b/ViewModels/ImportWindowViewModel.cs @@ -0,0 +1,396 @@ +using System; +using System.Collections.ObjectModel; +using System.Threading; +using LibgenDesktop.Infrastructure; +using LibgenDesktop.Models; +using LibgenDesktop.Models.ProgressArgs; +using LibgenDesktop.Models.SqlDump; +using LibgenDesktop.Models.Utils; + +namespace LibgenDesktop.ViewModels +{ + internal class ImportWindowViewModel : ViewModel + { + internal class LogLine : ViewModel + { + private string step; + private string header; + private string status; + + public LogLine(string step, string header, string status) + { + this.step = step; + this.header = header; + this.status = status; + } + + public string Step + { + get + { + return step; + } + set + { + step = value; + NotifyPropertyChanged(); + } + } + + public string Header + { + get + { + return header; + } + set + { + header = value; + NotifyPropertyChanged(); + } + } + + public string Status + { + get + { + return status; + } + set + { + status = value; + NotifyPropertyChanged(); + } + } + } + + private readonly MainModel mainModel; + private readonly string dumpFilePath; + private readonly CancellationTokenSource cancellationTokenSource; + private bool isInProgress; + private string status; + private ObservableCollection logs; + private string resultLogLine; + private bool isResultLogLineVisible; + private string errorLogLine; + private bool isErrorLogLineVisible; + private string elapsed; + private bool isCancellationEnabled; + private bool isCancelButtonVisible; + private bool isCloseButtonVisible; + private decimal lastScannedPercentage; + private TableType tableType; + private DateTime startDateTime; + private TimeSpan lastElapsedTime; + + public ImportWindowViewModel(MainModel mainModel, string dumpFilePath) + { + this.mainModel = mainModel; + this.dumpFilePath = dumpFilePath; + cancellationTokenSource = new CancellationTokenSource(); + CancelCommand = new Command(Cancel); + CloseCommand = new Command(Close); + Initialize(); + } + + public bool IsInProgress + { + get + { + return isInProgress; + } + set + { + isInProgress = value; + NotifyPropertyChanged(); + } + } + + public string Status + { + get + { + return status; + } + set + { + status = value; + NotifyPropertyChanged(); + } + } + + public ObservableCollection Logs + { + get + { + return logs; + } + set + { + logs = value; + NotifyPropertyChanged(); + } + } + + public string ResultLogLine + { + get + { + return resultLogLine; + } + set + { + resultLogLine = value; + NotifyPropertyChanged(); + } + } + + public bool IsResultLogLineVisible + { + get + { + return isResultLogLineVisible; + } + set + { + isResultLogLineVisible = value; + NotifyPropertyChanged(); + } + } + + public string ErrorLogLine + { + get + { + return errorLogLine; + } + set + { + errorLogLine = value; + NotifyPropertyChanged(); + } + } + + public bool IsErrorLogLineVisible + { + get + { + return isErrorLogLineVisible; + } + set + { + isErrorLogLineVisible = value; + NotifyPropertyChanged(); + } + } + + public string Elapsed + { + get + { + return elapsed; + } + set + { + elapsed = value; + NotifyPropertyChanged(); + } + } + + public bool IsCancellationEnabled + { + get + { + return isCancellationEnabled; + } + set + { + isCancellationEnabled = value; + NotifyPropertyChanged(); + } + } + + public bool IsCancelButtonVisible + { + get + { + return isCancelButtonVisible; + } + set + { + isCancelButtonVisible = value; + NotifyPropertyChanged(); + } + } + + public bool IsCloseButtonVisible + { + get + { + return isCloseButtonVisible; + } + set + { + isCloseButtonVisible = value; + NotifyPropertyChanged(); + } + } + + public Command CancelCommand { get; } + public Command CloseCommand { get; } + + private void Initialize() + { + isInProgress = true; + status = "Шаг 1 из 2. Поиск данных для импорта..."; + logs = new ObservableCollection(); + isResultLogLineVisible = false; + isErrorLogLineVisible = false; + startDateTime = DateTime.Now; + lastElapsedTime = TimeSpan.Zero; + elapsed = GetElapsedString(lastElapsedTime); + isCancellationEnabled = true; + isCancelButtonVisible = true; + isCloseButtonVisible = false; + lastScannedPercentage = 0; + LogLine searchHeaderLogLine = new LogLine("Шаг 1", "Поиск данных для импорта в файле", "Идет сканирование файла..."); + logs.Add(searchHeaderLogLine); + Import(); + } + + private async void Import() + { + Progress importProgressHandler = new Progress(HandleImportProgress); + CancellationToken cancellationToken = cancellationTokenSource.Token; + MainModel.ImportSqlDumpResult importResult; + try + { + importResult = await mainModel.ImportSqlDumpAsync(dumpFilePath, importProgressHandler, cancellationToken); + } + catch (Exception exception) + { + ErrorLogLine = "Импорт завершился с ошибками."; + IsErrorLogLineVisible = true; + IsInProgress = false; + Status = "Импорт завершен с ошибками"; + IsCancelButtonVisible = false; + IsCloseButtonVisible = true; + ShowErrorWindow(exception); + return; + } + switch (importResult) + { + case MainModel.ImportSqlDumpResult.COMPLETED: + ResultLogLine = "Импорт выполнен успешно."; + IsResultLogLineVisible = true; + Status = "Импорт завершен"; + break; + case MainModel.ImportSqlDumpResult.CANCELLED: + ErrorLogLine = "Импорт был прерван пользователем."; + IsErrorLogLineVisible = true; + Status = "Импорт прерван"; + break; + case MainModel.ImportSqlDumpResult.DATA_NOT_FOUND: + ErrorLogLine = "Не найдены данные для импорта."; + IsErrorLogLineVisible = true; + Status = "Данные не найдены"; + break; + } + IsInProgress = false; + IsCancelButtonVisible = false; + IsCloseButtonVisible = true; + } + + private void HandleImportProgress(object progress) + { + DateTime now = DateTime.Now; + switch (progress) + { + case ImportSearchTableDefinitionProgress searchTableDefinitionProgress: + if (searchTableDefinitionProgress.TotalBytes != 0) + { + decimal scannedPercentage = Math.Round((decimal)searchTableDefinitionProgress.BytesParsed * 100 / searchTableDefinitionProgress.TotalBytes, 1); + if (scannedPercentage != lastScannedPercentage) + { + logs[0].Status = GetScannedPercentageStatusString(scannedPercentage); + lastScannedPercentage = scannedPercentage; + } + } + break; + case ImportTableDefinitionFoundProgress tableDefinitionFoundProgress: + tableType = tableDefinitionFoundProgress.TableFound; + string tableFoundStatusString = "Найдена таблица с "; + switch (tableDefinitionFoundProgress.TableFound) + { + case TableType.NON_FICTION: + tableFoundStatusString += "нехудожественными книгами"; + break; + case TableType.FICTION: + tableFoundStatusString += "художественными книгами"; + break; + case TableType.SCI_MAG: + tableFoundStatusString += "научными статьями"; + break; + } + logs[0].Status = tableFoundStatusString + "."; + Status = "Шаг 2 из 2. Импорт данных..."; + logs.Add(new LogLine("Шаг 2", "Импорт данных", GetImportedObjectCountStatusString(0))); + break; + case ImportObjectsProgress importObjectsProgress: + logs[1].Status = GetImportedObjectCountStatusString(importObjectsProgress.ObjectsImported); + break; + } + TimeSpan elapsedTime = now - startDateTime; + if (elapsedTime.Seconds != lastElapsedTime.Seconds) + { + lastElapsedTime = elapsedTime; + Elapsed = GetElapsedString(elapsedTime); + } + } + + private void Cancel() + { + cancellationTokenSource.Cancel(); + } + + private void Close() + { + CurrentWindowContext.Close(); + } + + private string GetScannedPercentageStatusString(decimal scannedPercentage) + { + return $"Просканировано {scannedPercentage}% файла..."; + } + + private string GetImportedObjectCountStatusString(int importedObjectCount) + { + string result = "Импортировано "; + switch (tableType) + { + case TableType.NON_FICTION: + case TableType.FICTION: + result += "книг"; + break; + case TableType.SCI_MAG: + result += "статей"; + break; + default: + throw new Exception($"Unknown table type: {tableType}."); + } + return $"{result}: {importedObjectCount.ToFormattedString()}."; + } + + private string GetElapsedString(TimeSpan elapsedTime) + { + if (elapsedTime.Hours > 0) + { + return $"Прошло {Math.Truncate(elapsedTime.TotalHours)}:{elapsedTime:mm\\:ss}"; + } + else + { + return $"Прошло {elapsedTime:mm\\:ss}"; + } + } + } +} diff --git a/ViewModels/MainWindowViewModel.cs b/ViewModels/MainWindowViewModel.cs index ce181ec..5349ec9 100644 --- a/ViewModels/MainWindowViewModel.cs +++ b/ViewModels/MainWindowViewModel.cs @@ -1,37 +1,31 @@ using System; +using System.Collections.ObjectModel; using System.Linq; -using System.Threading; using LibgenDesktop.Infrastructure; using LibgenDesktop.Models; using LibgenDesktop.Models.Entities; -using LibgenDesktop.Models.ProgressArgs; using LibgenDesktop.Models.Settings; -using LibgenDesktop.Models.Utils; namespace LibgenDesktop.ViewModels { internal class MainWindowViewModel : ViewModel { private readonly MainModel mainModel; - private AsyncBookCollection books; - private bool allowSqlDumpImport; - private bool isInOfflineMode; - private bool isInSearchMode; - private double progressValue; - private bool isProgressVisible; - private bool isProgressIndeterminate; - private bool isSearchindDisabled; - private string statusText; + private SearchTabViewModel defaultSearchTabViewModel; + private TabViewModel selectedTabViewModel; - public MainWindowViewModel() + public MainWindowViewModel(MainModel mainModel) { - mainModel = new MainModel(); - OpenBookDetailsCommand = new Command(param => OpenBookDetails(param as Book)); - ImportSqlDumpCommand = new Command(ImportSqlDump); - ExitCommand = new Command(Exit); - SearchCommand = new Command(Search); - BookDataGridEnterKeyCommand = new Command(BookDataGridEnterKeyPressed); + this.mainModel = mainModel; + NewTabCommand = new Command(NewTab); + CloseTabCommand = new Command(param => CloseTab(param as TabViewModel)); + CloseCurrentTabCommand = new Command(CloseCurrentTab); + DownloadManagerCommand = new Command(ShowDownloadManager); + ImportCommand = new Command(Import); + SettingsCommand = new Command(SettingsMenuItemClick); WindowClosedCommand = new Command(WindowClosed); + DefaultSearchTabViewModel = new SearchTabViewModel(mainModel, null); + TabViewModels = new ObservableCollection(); Initialize(); } @@ -40,131 +34,68 @@ public MainWindowViewModel() public int WindowLeft { get; set; } public int WindowTop { get; set; } public bool IsWindowMaximized { get; set; } - public int TitleColumnWidth { get; set; } - public int AuthorsColumnWidth { get; set; } - public int SeriesColumnWidth { get; set; } - public int YearColumnWidth { get; set; } - public int PublisherColumnWidth { get; set; } - public int FormatColumnWidth { get; set; } - public int FileSizeColumnWidth { get; set; } - public int OcrColumnWidth { get; set; } - public string SearchQuery { get; set; } - public Book SelectedBook { get; set; } - public AsyncBookCollection Books - { - get - { - return books; - } - private set - { - books = value; - NotifyPropertyChanged(); - } - } + public ObservableCollection TabViewModels { get; } - public bool AllowSqlDumpImport + public SearchTabViewModel DefaultSearchTabViewModel { get { - return allowSqlDumpImport; + return defaultSearchTabViewModel; } set { - allowSqlDumpImport = value; + defaultSearchTabViewModel = value; NotifyPropertyChanged(); } } - public bool IsInOfflineMode + public TabViewModel SelectedTabViewModel { get { - return isInOfflineMode; + return selectedTabViewModel; } set { - isInOfflineMode = value; + selectedTabViewModel = value; NotifyPropertyChanged(); - mainModel.AppSettings.OfflineMode = value; - mainModel.SaveSettings(); } } - public double ProgressValue + public bool IsDefaultSearchTabVisible { get { - return progressValue; - } - private set - { - progressValue = value; - NotifyPropertyChanged(); + return TabViewModels == null || !TabViewModels.Any(); } } - public bool IsProgressVisible + public bool AreTabsVisible { get { - return isProgressVisible; - } - private set - { - isProgressVisible = value; - NotifyPropertyChanged(); + return !IsDefaultSearchTabVisible; } } - public bool IsProgressIndeterminate + public bool IsNewTabButtonVisible { get { - return isProgressIndeterminate; - } - set - { - isProgressIndeterminate = value; - NotifyPropertyChanged(); + return TabViewModels.Any() && !TabViewModels.Any(tabViewModel => tabViewModel is SearchTabViewModel); } } - public bool IsSearchindDisabled - { - get - { - return isSearchindDisabled; - } - private set - { - isSearchindDisabled = value; - NotifyPropertyChanged(); - } - } - - public string StatusText - { - get - { - return statusText; - } - private set - { - statusText = value; - NotifyPropertyChanged(); - } - } - - public Command OpenBookDetailsCommand { get; } - public Command ImportSqlDumpCommand { get; } - public Command ExitCommand { get; } - public Command SearchCommand { get; } - public Command BookDataGridEnterKeyCommand { get; } + public Command NewTabCommand { get; } + public Command CloseTabCommand { get; } + public Command CloseCurrentTabCommand { get; } + public Command DownloadManagerCommand { get; } + public Command ImportCommand { get; } + public Command SettingsCommand { get; } public Command WindowClosedCommand { get; } - private async void Initialize() + private void Initialize() { AppSettings appSettings = mainModel.AppSettings; AppSettings.MainWindowSettings mainWindowSettings = appSettings.MainWindow; @@ -173,163 +104,153 @@ private async void Initialize() WindowLeft = mainWindowSettings.Left; WindowTop = mainWindowSettings.Top; IsWindowMaximized = mainWindowSettings.Maximized; - AppSettings.ColumnSettings columnSettings = appSettings.Columns; - TitleColumnWidth = columnSettings.TitleColumnWidth; - AuthorsColumnWidth = columnSettings.AuthorsColumnWidth; - SeriesColumnWidth = columnSettings.SeriesColumnWidth; - YearColumnWidth = columnSettings.YearColumnWidth; - PublisherColumnWidth = columnSettings.PublisherColumnWidth; - FormatColumnWidth = columnSettings.FormatColumnWidth; - FileSizeColumnWidth = columnSettings.FileSizeColumnWidth; - OcrColumnWidth = columnSettings.OcrColumnWidth; - isInOfflineMode = appSettings.OfflineMode; - allowSqlDumpImport = false; - SearchQuery = String.Empty; - IsSearchindDisabled = false; - books = mainModel.AllBooks; - isInSearchMode = false; - progressValue = 0; - isProgressVisible = true; - isProgressIndeterminate = false; - statusText = "Загрузка книг..."; - Progress loadAllBooksProgressHandler = new Progress(HandleLoadAllBooksProgress); - CancellationToken cancellationToken = new CancellationToken(); - try - { - await mainModel.LoadAllBooksAsync(loadAllBooksProgressHandler, cancellationToken); - } - catch (Exception exception) - { - ShowErrorWindow(exception); - } - InitialLoadCompleted(); + DefaultSearchTabViewModel.ImportRequested += DefaultSearchTabViewModel_ImportRequested; + DefaultSearchTabViewModel.NonFictionSearchComplete += SearchTabNonFictionSearchComplete; + DefaultSearchTabViewModel.FictionSearchComplete += SearchTabFictionSearchComplete; + DefaultSearchTabViewModel.SciMagSearchComplete += SearchTabSciMagSearchComplete; + selectedTabViewModel = null; } - private void HandleLoadAllBooksProgress(LoadAllBooksProgress loadAllBooksProgress) + private void DefaultSearchTabViewModel_ImportRequested(object sender, EventArgs e) { - if (!isInSearchMode) - { - IsProgressIndeterminate = false; - if (!loadAllBooksProgress.IsFinished) - { - IsProgressVisible = true; - StatusText = $"Загрузка книг (загружено { loadAllBooksProgress.BooksLoaded.ToString("N0", Formatters.ThousandsSeparatedNumberFormat)} из { loadAllBooksProgress.TotalBookCount.ToString("N0", Formatters.ThousandsSeparatedNumberFormat)})..."; - ProgressValue = (double)loadAllBooksProgress.BooksLoaded / loadAllBooksProgress.TotalBookCount; - } - } + Import(); } - private void BookDataGridEnterKeyPressed() + private void SearchTabNonFictionSearchComplete(object sender, SearchTabViewModel.SearchCompleteEventArgs e) { - OpenBookDetails(SelectedBook); + NonFictionSearchResultsTabViewModel nonFictionSearchResultsTabViewModel = + new NonFictionSearchResultsTabViewModel(mainModel, CurrentWindowContext, e.SearchQuery, e.SearchResult); + ShowSearchResults(sender as SearchTabViewModel, nonFictionSearchResultsTabViewModel); } - private void OpenBookDetails(Book book) + private void SearchTabFictionSearchComplete(object sender, SearchTabViewModel.SearchCompleteEventArgs e) { - IWindowContext currentWindowContext = WindowManager.GetCreatedWindowContext(this); - BookDetailsWindowViewModel bookDetailsWindowViewModel = new BookDetailsWindowViewModel(mainModel, book); - IWindowContext bookDetailsWindowContext = WindowManager.CreateWindow(RegisteredWindows.WindowKey.BOOK_DETAILS_WINDOW, bookDetailsWindowViewModel, currentWindowContext); - AppSettings.BookWindowSettings bookWindowSettings = mainModel.AppSettings.BookWindow; - bookDetailsWindowContext.ShowDialog(bookWindowSettings.Width, bookWindowSettings.Height); + FictionSearchResultsTabViewModel fictionSearchResultsTabViewModel = + new FictionSearchResultsTabViewModel(mainModel, CurrentWindowContext, e.SearchQuery, e.SearchResult); + ShowSearchResults(sender as SearchTabViewModel, fictionSearchResultsTabViewModel); } - private void ImportSqlDump() + private void SearchTabSciMagSearchComplete(object sender, SearchTabViewModel.SearchCompleteEventArgs e) { - OpenFileDialogParameters selectSqlDumpFileDialogParameters = new OpenFileDialogParameters - { - DialogTitle = "Выбор SQL-дампа", - Filter = "SQL-дампы (*.sql, *.zip, *.rar)|*.sql;*zip;*.rar|Все файлы (*.*)|*.*", - Multiselect = false - }; - OpenFileDialogResult selectSqlDumpFileDialogResult = WindowManager.ShowOpenFileDialog(selectSqlDumpFileDialogParameters); - if (selectSqlDumpFileDialogResult.DialogResult) - { - StatusText = "Импорт из SQL-дампа..."; - IWindowContext currentWindowContext = WindowManager.GetCreatedWindowContext(this); - SqlDumpImportWindowViewModel sqlDumpImportWindowViewModel = new SqlDumpImportWindowViewModel(mainModel, selectSqlDumpFileDialogResult.SelectedFilePaths.First()); - IWindowContext sqlDumpImportWindowContext = WindowManager.CreateWindow(RegisteredWindows.WindowKey.SQL_DUMP_IMPORT_WINDOW, sqlDumpImportWindowViewModel, currentWindowContext); - sqlDumpImportWindowContext.ShowDialog(); - SetTotalBookNumberStatus(); - } + SciMagSearchResultsTabViewModel sciMagSearchResultsTabViewModel = + new SciMagSearchResultsTabViewModel(mainModel, CurrentWindowContext, e.SearchQuery, e.SearchResult); + ShowSearchResults(sender as SearchTabViewModel, sciMagSearchResultsTabViewModel); } - private async void Search() + private void ShowSearchResults(SearchTabViewModel searchTabViewModel, TabViewModel searchResultsTabViewModel) { - string searchQuery = SearchQuery.Trim(); - if (String.IsNullOrEmpty(searchQuery)) - { - Books = mainModel.AllBooks; - SetTotalBookNumberStatus(); - IsSearchindDisabled = false; - isInSearchMode = false; - } - else + if (searchTabViewModel != DefaultSearchTabViewModel) { - isInSearchMode = true; - IsProgressIndeterminate = true; - IsProgressVisible = true; - IsSearchindDisabled = true; - statusText = "Поиск книг..."; - mainModel.ClearSearchResults(); - Books = mainModel.SearchResults; - Progress searchBooksProgressHandler = new Progress(HandleSearchBooksProgress); - CancellationToken cancellationToken = new CancellationToken(); - try - { - await mainModel.SearchBooksAsync(searchQuery, searchBooksProgressHandler, cancellationToken); - } - catch (Exception exception) - { - ShowErrorWindow(exception); - } - IsProgressVisible = false; - IsSearchindDisabled = false; - SetFoundBooksStatus(); + searchTabViewModel.NonFictionSearchComplete -= SearchTabNonFictionSearchComplete; + searchTabViewModel.FictionSearchComplete -= SearchTabFictionSearchComplete; + searchTabViewModel.SciMagSearchComplete -= SearchTabSciMagSearchComplete; + TabViewModels.Remove(searchTabViewModel); } + TabViewModels.Add(searchResultsTabViewModel); + SelectedTabViewModel = searchResultsTabViewModel; + NotifyPropertyChanged(nameof(IsDefaultSearchTabVisible)); + NotifyPropertyChanged(nameof(AreTabsVisible)); + NotifyPropertyChanged(nameof(IsNewTabButtonVisible)); } - private void HandleSearchBooksProgress(SearchBooksProgress searchBooksProgress) + private void NewTab() { - if (isInSearchMode) + if (IsNewTabButtonVisible) { - if (!searchBooksProgress.IsFinished) - { - IsProgressVisible = true; - IsProgressIndeterminate = true; - StatusText = $"Поиск книг (найдено { searchBooksProgress.BooksFound.ToString("N0", Formatters.ThousandsSeparatedNumberFormat)})..."; - } + SearchTabViewModel searchTabViewModel = new SearchTabViewModel(mainModel, CurrentWindowContext); + searchTabViewModel.NonFictionSearchComplete += SearchTabNonFictionSearchComplete; + searchTabViewModel.FictionSearchComplete += SearchTabFictionSearchComplete; + searchTabViewModel.SciMagSearchComplete += SearchTabSciMagSearchComplete; + TabViewModels.Add(searchTabViewModel); + SelectedTabViewModel = searchTabViewModel; + NotifyPropertyChanged(nameof(IsNewTabButtonVisible)); } } - private void SetTotalBookNumberStatus() + private void CloseCurrentTab() { - StatusText = $"Всего книг: {Books.Count.ToString("N0", Formatters.ThousandsSeparatedNumberFormat)}"; + if (TabViewModels.Any()) + { + CloseTab(SelectedTabViewModel); + } } - private void SetFoundBooksStatus() + private void CloseTab(TabViewModel tabViewModel) { - StatusText = $"Найдено книг: {Books.Count.ToString("N0", Formatters.ThousandsSeparatedNumberFormat)}"; + if (tabViewModel is SearchTabViewModel searchTabViewModel) + { + searchTabViewModel.NonFictionSearchComplete -= SearchTabNonFictionSearchComplete; + searchTabViewModel.FictionSearchComplete -= SearchTabFictionSearchComplete; + searchTabViewModel.SciMagSearchComplete -= SearchTabSciMagSearchComplete; + } + int removingTabIndex = TabViewModels.IndexOf(tabViewModel); + TabViewModels.Remove(tabViewModel); + NotifyPropertyChanged(nameof(IsDefaultSearchTabVisible)); + NotifyPropertyChanged(nameof(AreTabsVisible)); + NotifyPropertyChanged(nameof(IsNewTabButtonVisible)); + if (!TabViewModels.Any()) + { + SelectedTabViewModel = null; + DefaultSearchTabViewModel.Refresh(setFocus: true); + } + else + { + int newSelectedTabIndex = TabViewModels.Count > removingTabIndex ? removingTabIndex : TabViewModels.Count - 1; + SelectedTabViewModel = TabViewModels[newSelectedTabIndex]; + } } - private void InitialLoadCompleted() + private void ShowDownloadManager() { - UpdateAllowSqlDumpImport(); - if (!isInSearchMode) + DownloadManagerTabViewModel downloadManagerTabViewModel = TabViewModels.OfType().FirstOrDefault(); + if (downloadManagerTabViewModel == null) { - IsProgressVisible = false; - SetTotalBookNumberStatus(); + downloadManagerTabViewModel = new DownloadManagerTabViewModel(mainModel, CurrentWindowContext); + TabViewModels.Add(downloadManagerTabViewModel); + SelectedTabViewModel = downloadManagerTabViewModel; + NotifyPropertyChanged(nameof(IsDefaultSearchTabVisible)); + NotifyPropertyChanged(nameof(AreTabsVisible)); + NotifyPropertyChanged(nameof(IsNewTabButtonVisible)); + } + else + { + SelectedTabViewModel = downloadManagerTabViewModel; } } - private void UpdateAllowSqlDumpImport() + private void Import() { - AllowSqlDumpImport = mainModel.AllBooks.Count == 0; + OpenFileDialogParameters selectSqlDumpFileDialogParameters = new OpenFileDialogParameters + { + DialogTitle = "Выбор SQL-дампа", + Filter = "Все поддерживаемые файлы|*.sql;*zip;*.rar;*.gz|SQL -дампы (*.sql)|*.sql|Архивы (*.zip, *.rar, *.gz)|*zip;*.rar;*.gz|Все файлы (*.*)|*.*", + Multiselect = false + }; + OpenFileDialogResult selectSqlDumpFileDialogResult = WindowManager.ShowOpenFileDialog(selectSqlDumpFileDialogParameters); + if (selectSqlDumpFileDialogResult.DialogResult) + { + ImportWindowViewModel importWindowViewModel = new ImportWindowViewModel(mainModel, selectSqlDumpFileDialogResult.SelectedFilePaths.First()); + IWindowContext importWindowContext = WindowManager.CreateWindow(RegisteredWindows.WindowKey.IMPORT_WINDOW, importWindowViewModel, CurrentWindowContext); + importWindowContext.ShowDialog(); + if (IsDefaultSearchTabVisible) + { + DefaultSearchTabViewModel.Refresh(setFocus: true); + } + else + { + foreach (SearchTabViewModel searchTabViewModel in TabViewModels.OfType()) + { + searchTabViewModel.Refresh(setFocus: searchTabViewModel == SelectedTabViewModel); + } + } + } } - private void Exit() + private void SettingsMenuItemClick() { - IWindowContext currentWindowContext = WindowManager.GetCreatedWindowContext(this); - currentWindowContext.Close(); + SettingsWindowViewModel settingsWindowViewModel = new SettingsWindowViewModel(mainModel); + IWindowContext settingsWindowContext = WindowManager.CreateWindow(RegisteredWindows.WindowKey.SETTINGS_WINDOW, settingsWindowViewModel, CurrentWindowContext); + settingsWindowContext.ShowDialog(); } private void WindowClosed() @@ -342,17 +263,6 @@ private void WindowClosed() Top = WindowTop, Maximized = IsWindowMaximized }; - mainModel.AppSettings.Columns = new AppSettings.ColumnSettings - { - TitleColumnWidth = TitleColumnWidth, - AuthorsColumnWidth = AuthorsColumnWidth, - SeriesColumnWidth = SeriesColumnWidth, - YearColumnWidth = YearColumnWidth, - PublisherColumnWidth = PublisherColumnWidth, - FormatColumnWidth = FormatColumnWidth, - FileSizeColumnWidth = FileSizeColumnWidth, - OcrColumnWidth = OcrColumnWidth - }; mainModel.SaveSettings(); } } diff --git a/ViewModels/BookDetailsWindowViewModel.cs b/ViewModels/NonFictionDetailsWindowViewModel.cs similarity index 81% rename from ViewModels/BookDetailsWindowViewModel.cs rename to ViewModels/NonFictionDetailsWindowViewModel.cs index de8e91e..b347e94 100644 --- a/ViewModels/BookDetailsWindowViewModel.cs +++ b/ViewModels/NonFictionDetailsWindowViewModel.cs @@ -1,12 +1,7 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Linq; using System.Net; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Input; using System.Windows.Media.Imaging; using LibgenDesktop.Common; using LibgenDesktop.Infrastructure; @@ -16,9 +11,10 @@ namespace LibgenDesktop.ViewModels { - internal class BookDetailsWindowViewModel : ViewModel + internal class NonFictionDetailsWindowViewModel : ViewModel { private readonly MainModel mainModel; + private NonFictionBook book; private bool bookCoverNotLoadedDueToOfflineMode; private bool bookCoverLoading; private bool bookCoverLoadingFailed; @@ -26,21 +22,34 @@ internal class BookDetailsWindowViewModel : ViewModel private bool bookCoverVisible; private BitmapImage bookCover; - public BookDetailsWindowViewModel(MainModel mainModel, Book mainBookInfo) + public NonFictionDetailsWindowViewModel(MainModel mainModel, NonFictionBook book) { this.mainModel = mainModel; + this.book = book; DownloadBookCommand = new Command(DownloadBook); CloseCommand = new Command(CloseWindow); WindowClosedCommand = new Command(WindowClosed); - Initialize(mainBookInfo); + Initialize(); } - public Book Book { get; private set; } public string WindowTitle { get; private set; } public int WindowWidth { get; set; } public int WindowHeight { get; set; } public bool IsInOfflineMode { get; private set; } + public NonFictionBook Book + { + get + { + return book; + } + private set + { + book = value; + NotifyPropertyChanged(); + } + } + public bool BookCoverNotLoadedDueToOfflineMode { get @@ -123,12 +132,12 @@ private set public Command CloseCommand { get; } public Command WindowClosedCommand { get; } - private async void Initialize(Book mainBookInfo) + private async void Initialize() { - WindowTitle = mainBookInfo.Title; - WindowWidth = mainModel.AppSettings.BookWindow.Width; - WindowHeight = mainModel.AppSettings.BookWindow.Height; - if (mainModel.AppSettings.OfflineMode) + WindowTitle = Book.Title; + WindowWidth = mainModel.AppSettings.NonFiction.DetailsWindow.Width; + WindowHeight = mainModel.AppSettings.NonFiction.DetailsWindow.Height; + if (mainModel.AppSettings.Network.OfflineMode) { IsInOfflineMode = true; BookCoverNotLoadedDueToOfflineMode = true; @@ -144,9 +153,7 @@ private async void Initialize(Book mainBookInfo) NoCover = false; BookCoverVisible = false; BookCover = null; - Book = await mainModel.LoadBookAsync(mainBookInfo.Id); - NotifyPropertyChanged(nameof(Book)); - if (String.IsNullOrWhiteSpace(Book.ExtendedProperties.CoverUrl)) + if (String.IsNullOrWhiteSpace(Book.CoverUrl)) { BookCoverNotLoadedDueToOfflineMode = false; BookCoverLoading = false; @@ -159,7 +166,7 @@ private async void Initialize(Book mainBookInfo) try { WebClient webClient = new WebClient(); - byte[] imageData = await webClient.DownloadDataTaskAsync(new Uri(Constants.BOOK_COVER_URL_PREFIX + Book.ExtendedProperties.CoverUrl)); + byte[] imageData = await webClient.DownloadDataTaskAsync(new Uri(Constants.NON_FICTION_COVER_URL_PREFIX + Book.CoverUrl)); BitmapImage bitmapImage = new BitmapImage(); using (MemoryStream memoryStream = new MemoryStream(imageData)) { @@ -183,18 +190,18 @@ private async void Initialize(Book mainBookInfo) private void DownloadBook() { - Process.Start(Constants.BOOK_DOWNLOAD_URL_PREFIX + Book.ExtendedProperties.Md5Hash); + Process.Start(Constants.NON_FICTION_DOWNLOAD_URL_PREFIX + Book.Md5Hash); } private void CloseWindow() { - IWindowContext currentWindowContext = WindowManager.GetCreatedWindowContext(this); + IWindowContext currentWindowContext = WindowManager.GetWindowContext(this); currentWindowContext.CloseDialog(false); } private void WindowClosed() { - mainModel.AppSettings.BookWindow = new AppSettings.BookWindowSettings + mainModel.AppSettings.NonFiction.DetailsWindow = new AppSettings.NonFictionDetailsWindowSettings { Width = WindowWidth, Height = WindowHeight diff --git a/ViewModels/NonFictionSearchResultsTabViewModel.cs b/ViewModels/NonFictionSearchResultsTabViewModel.cs new file mode 100644 index 0000000..f7c8b02 --- /dev/null +++ b/ViewModels/NonFictionSearchResultsTabViewModel.cs @@ -0,0 +1,294 @@ +using System; +using System.Collections.ObjectModel; +using System.Threading; +using LibgenDesktop.Infrastructure; +using LibgenDesktop.Models; +using LibgenDesktop.Models.Entities; +using LibgenDesktop.Models.ProgressArgs; +using LibgenDesktop.Models.Utils; +using static LibgenDesktop.Models.Settings.AppSettings; + +namespace LibgenDesktop.ViewModels +{ + internal class NonFictionSearchResultsTabViewModel : TabViewModel + { + private readonly NonFictionColumnSettings columnSettings; + private ObservableCollection books; + private string searchQuery; + private string bookCount; + private bool isBookGridVisible; + private bool isSearchProgressPanelVisible; + private string searchProgressStatus; + private bool isStatusBarVisible; + + public NonFictionSearchResultsTabViewModel(MainModel mainModel, IWindowContext mainWindowContext, string searchQuery, ObservableCollection searchResults) + : base(mainModel, mainWindowContext, searchQuery) + { + columnSettings = mainModel.AppSettings.NonFiction.Columns; + this.searchQuery = searchQuery; + books = searchResults; + OpenDetailsCommand = new Command(param => OpenDetails(param as NonFictionBook)); + SearchCommand = new Command(Search); + BookDataGridEnterKeyCommand = new Command(BookDataGridEnterKeyPressed); + Initialize(); + } + + public string SearchQuery + { + get + { + return searchQuery; + } + set + { + searchQuery = value; + NotifyPropertyChanged(); + } + } + + public ObservableCollection Books + { + get + { + return books; + } + set + { + books = value; + NotifyPropertyChanged(); + } + } + + public bool IsBookGridVisible + { + get + { + return isBookGridVisible; + } + set + { + isBookGridVisible = value; + NotifyPropertyChanged(); + } + } + + public int TitleColumnWidth + { + get + { + return columnSettings.TitleColumnWidth; + } + set + { + columnSettings.TitleColumnWidth = value; + } + } + + public int AuthorsColumnWidth + { + get + { + return columnSettings.AuthorsColumnWidth; + } + set + { + columnSettings.AuthorsColumnWidth = value; + } + } + + public int SeriesColumnWidth + { + get + { + return columnSettings.SeriesColumnWidth; + } + set + { + columnSettings.SeriesColumnWidth = value; + } + } + + public int YearColumnWidth + { + get + { + return columnSettings.YearColumnWidth; + } + set + { + columnSettings.YearColumnWidth = value; + } + } + + public int PublisherColumnWidth + { + get + { + return columnSettings.PublisherColumnWidth; + } + set + { + columnSettings.PublisherColumnWidth = value; + } + } + + public int FormatColumnWidth + { + get + { + return columnSettings.FormatColumnWidth; + } + set + { + columnSettings.FormatColumnWidth = value; + } + } + + public int FileSizeColumnWidth + { + get + { + return columnSettings.FileSizeColumnWidth; + } + set + { + columnSettings.FileSizeColumnWidth = value; + } + } + + public int OcrColumnWidth + { + get + { + return columnSettings.OcrColumnWidth; + } + set + { + columnSettings.OcrColumnWidth = value; + } + } + + public bool IsSearchProgressPanelVisible + { + get + { + return isSearchProgressPanelVisible; + } + set + { + isSearchProgressPanelVisible = value; + NotifyPropertyChanged(); + } + } + + public string SearchProgressStatus + { + get + { + return searchProgressStatus; + } + set + { + searchProgressStatus = value; + NotifyPropertyChanged(); + } + } + + public bool IsStatusBarVisible + { + get + { + return isStatusBarVisible; + } + set + { + isStatusBarVisible = value; + NotifyPropertyChanged(); + } + } + + public string BookCount + { + get + { + return bookCount; + } + set + { + bookCount = value; + NotifyPropertyChanged(); + } + } + + public NonFictionBook SelectedBook { get; set; } + + public Command OpenDetailsCommand { get; } + public Command SearchCommand { get; } + public Command BookDataGridEnterKeyCommand { get; } + + private void Initialize() + { + isBookGridVisible = true; + isStatusBarVisible = true; + isSearchProgressPanelVisible = false; + UpdateBookCount(); + Events.RaiseEvent(ViewModelEvent.RegisteredEventId.FOCUS_SEARCH_TEXT_BOX); + } + + private void UpdateBookCount() + { + BookCount = $"Найдено книг: {Books.Count.ToFormattedString()}"; + } + + private void BookDataGridEnterKeyPressed() + { + OpenDetails(SelectedBook); + } + + private void OpenDetails(NonFictionBook book) + { + NonFictionDetailsWindowViewModel detailsWindowViewModel = new NonFictionDetailsWindowViewModel(MainModel, book); + IWindowContext detailsWindowContext = WindowManager.CreateWindow(RegisteredWindows.WindowKey.NON_FICTION_DETAILS_WINDOW, detailsWindowViewModel, MainWindowContext); + NonFictionDetailsWindowSettings detailsWindowSettings = MainModel.AppSettings.NonFiction.DetailsWindow; + detailsWindowContext.ShowDialog(detailsWindowSettings.Width, detailsWindowSettings.Height); + } + + private async void Search() + { + if (!String.IsNullOrWhiteSpace(SearchQuery) && !IsSearchProgressPanelVisible) + { + Title = SearchQuery; + IsBookGridVisible = false; + IsStatusBarVisible = false; + IsSearchProgressPanelVisible = true; + UpdateSearchProgressStatus(0); + Progress searchProgressHandler = new Progress(HandleSearchProgress); + CancellationToken cancellationToken = new CancellationToken(); + ObservableCollection result = new ObservableCollection(); + try + { + result = await MainModel.SearchNonFictionAsync(SearchQuery, searchProgressHandler, cancellationToken); + } + catch (Exception exception) + { + ShowErrorWindow(exception); + } + Books = result; + UpdateBookCount(); + IsSearchProgressPanelVisible = false; + IsBookGridVisible = true; + IsStatusBarVisible = true; + } + } + + private void HandleSearchProgress(SearchProgress searchProgress) + { + UpdateSearchProgressStatus(searchProgress.ItemsFound); + } + + private void UpdateSearchProgressStatus(int booksFound) + { + SearchProgressStatus = $"Найдено книг: {booksFound.ToFormattedString()}"; + } + } +} diff --git a/ViewModels/SciMagDetailsWindowViewModel.cs b/ViewModels/SciMagDetailsWindowViewModel.cs new file mode 100644 index 0000000..c9847c1 --- /dev/null +++ b/ViewModels/SciMagDetailsWindowViewModel.cs @@ -0,0 +1,76 @@ +using System.Diagnostics; +using LibgenDesktop.Common; +using LibgenDesktop.Infrastructure; +using LibgenDesktop.Models; +using LibgenDesktop.Models.Entities; +using LibgenDesktop.Models.Settings; + +namespace LibgenDesktop.ViewModels +{ + internal class SciMagDetailsWindowViewModel : ViewModel + { + private readonly MainModel mainModel; + private SciMagArticle article; + + public SciMagDetailsWindowViewModel(MainModel mainModel, SciMagArticle article) + { + this.mainModel = mainModel; + this.article = article; + DownloadArticleCommand = new Command(DownloadArticle); + CloseCommand = new Command(CloseWindow); + WindowClosedCommand = new Command(WindowClosed); + Initialize(); + } + + public string WindowTitle { get; private set; } + public int WindowWidth { get; set; } + public int WindowHeight { get; set; } + public bool IsInOfflineMode { get; private set; } + + public SciMagArticle Article + { + get + { + return article; + } + private set + { + article = value; + NotifyPropertyChanged(); + } + } + + public Command DownloadArticleCommand { get; } + public Command CloseCommand { get; } + public Command WindowClosedCommand { get; } + + private void Initialize() + { + WindowTitle = Article.Title; + WindowWidth = mainModel.AppSettings.SciMag.DetailsWindow.Width; + WindowHeight = mainModel.AppSettings.SciMag.DetailsWindow.Height; + IsInOfflineMode = mainModel.AppSettings.Network.OfflineMode; + } + + private void DownloadArticle() + { + Process.Start(Constants.SCI_MAG_DOWNLOAD_URL_PREFIX + Article.Doi); + } + + private void CloseWindow() + { + IWindowContext currentWindowContext = WindowManager.GetWindowContext(this); + currentWindowContext.CloseDialog(false); + } + + private void WindowClosed() + { + mainModel.AppSettings.SciMag.DetailsWindow = new AppSettings.SciMagDetailsWindowSettings + { + Width = WindowWidth, + Height = WindowHeight + }; + mainModel.SaveSettings(); + } + } +} diff --git a/ViewModels/SciMagSearchResultsTabViewModel.cs b/ViewModels/SciMagSearchResultsTabViewModel.cs new file mode 100644 index 0000000..1ec17d0 --- /dev/null +++ b/ViewModels/SciMagSearchResultsTabViewModel.cs @@ -0,0 +1,270 @@ +using System; +using System.Collections.ObjectModel; +using System.Threading; +using LibgenDesktop.Infrastructure; +using LibgenDesktop.Models; +using LibgenDesktop.Models.Entities; +using LibgenDesktop.Models.ProgressArgs; +using LibgenDesktop.Models.Utils; +using static LibgenDesktop.Models.Settings.AppSettings; + +namespace LibgenDesktop.ViewModels +{ + internal class SciMagSearchResultsTabViewModel : TabViewModel + { + private readonly SciMagColumnSettings columnSettings; + private ObservableCollection articles; + private string searchQuery; + private string articleCount; + private bool isArticleGridVisible; + private bool isSearchProgressPanelVisible; + private string searchProgressStatus; + private bool isStatusBarVisible; + + public SciMagSearchResultsTabViewModel(MainModel mainModel, IWindowContext mainWindowContext, string searchQuery, ObservableCollection searchResults) + : base(mainModel, mainWindowContext, searchQuery) + { + columnSettings = mainModel.AppSettings.SciMag.Columns; + this.searchQuery = searchQuery; + articles = searchResults; + OpenDetailsCommand = new Command(param => OpenDetails(param as SciMagArticle)); + SearchCommand = new Command(Search); + ArticleDataGridEnterKeyCommand = new Command(ArticleDataGridEnterKeyPressed); + Initialize(); + } + + public string SearchQuery + { + get + { + return searchQuery; + } + set + { + searchQuery = value; + NotifyPropertyChanged(); + } + } + + public ObservableCollection Articles + { + get + { + return articles; + } + set + { + articles = value; + NotifyPropertyChanged(); + } + } + + public bool IsArticleGridVisible + { + get + { + return isArticleGridVisible; + } + set + { + isArticleGridVisible = value; + NotifyPropertyChanged(); + } + } + + public int TitleColumnWidth + { + get + { + return columnSettings.TitleColumnWidth; + } + set + { + columnSettings.TitleColumnWidth = value; + } + } + + public int AuthorsColumnWidth + { + get + { + return columnSettings.AuthorsColumnWidth; + } + set + { + columnSettings.AuthorsColumnWidth = value; + } + } + + public int JournalColumnWidth + { + get + { + return columnSettings.JournalColumnWidth; + } + set + { + columnSettings.JournalColumnWidth = value; + } + } + + public int YearColumnWidth + { + get + { + return columnSettings.YearColumnWidth; + } + set + { + columnSettings.YearColumnWidth = value; + } + } + + public int FileSizeColumnWidth + { + get + { + return columnSettings.FileSizeColumnWidth; + } + set + { + columnSettings.FileSizeColumnWidth = value; + } + } + + public int DoiColumnWidth + { + get + { + return columnSettings.DoiColumnWidth; + } + set + { + columnSettings.DoiColumnWidth = value; + } + } + + public bool IsSearchProgressPanelVisible + { + get + { + return isSearchProgressPanelVisible; + } + set + { + isSearchProgressPanelVisible = value; + NotifyPropertyChanged(); + } + } + + public string SearchProgressStatus + { + get + { + return searchProgressStatus; + } + set + { + searchProgressStatus = value; + NotifyPropertyChanged(); + } + } + + public bool IsStatusBarVisible + { + get + { + return isStatusBarVisible; + } + set + { + isStatusBarVisible = value; + NotifyPropertyChanged(); + } + } + + public string ArticleCount + { + get + { + return articleCount; + } + set + { + articleCount = value; + NotifyPropertyChanged(); + } + } + + public SciMagArticle SelectedArticle { get; set; } + + public Command OpenDetailsCommand { get; } + public Command SearchCommand { get; } + public Command ArticleDataGridEnterKeyCommand { get; } + + private void Initialize() + { + isArticleGridVisible = true; + isStatusBarVisible = true; + isSearchProgressPanelVisible = false; + UpdateArticleCount(); + Events.RaiseEvent(ViewModelEvent.RegisteredEventId.FOCUS_SEARCH_TEXT_BOX); + } + + private void UpdateArticleCount() + { + ArticleCount = $"Найдено статей: {Articles.Count.ToFormattedString()}"; + } + + private void ArticleDataGridEnterKeyPressed() + { + OpenDetails(SelectedArticle); + } + + private void OpenDetails(SciMagArticle article) + { + SciMagDetailsWindowViewModel detailsWindowViewModel = new SciMagDetailsWindowViewModel(MainModel, article); + IWindowContext detailsWindowContext = WindowManager.CreateWindow(RegisteredWindows.WindowKey.SCI_MAG_DETAILS_WINDOW, detailsWindowViewModel, MainWindowContext); + SciMagDetailsWindowSettings detailsWindowSettings = MainModel.AppSettings.SciMag.DetailsWindow; + detailsWindowContext.ShowDialog(detailsWindowSettings.Width, detailsWindowSettings.Height); + } + + private async void Search() + { + if (!String.IsNullOrWhiteSpace(SearchQuery) && !IsSearchProgressPanelVisible) + { + Title = SearchQuery; + IsArticleGridVisible = false; + IsStatusBarVisible = false; + IsSearchProgressPanelVisible = true; + UpdateSearchProgressStatus(0); + Progress searchProgressHandler = new Progress(HandleSearchProgress); + CancellationToken cancellationToken = new CancellationToken(); + ObservableCollection result = new ObservableCollection(); + try + { + result = await MainModel.SearchSciMagAsync(SearchQuery, searchProgressHandler, cancellationToken); + } + catch (Exception exception) + { + ShowErrorWindow(exception); + } + Articles = result; + UpdateArticleCount(); + IsSearchProgressPanelVisible = false; + IsArticleGridVisible = true; + IsStatusBarVisible = true; + } + } + + private void HandleSearchProgress(SearchProgress searchProgress) + { + UpdateSearchProgressStatus(searchProgress.ItemsFound); + } + + private void UpdateSearchProgressStatus(int articlesFound) + { + SearchProgressStatus = $"Найдено статей: {articlesFound.ToFormattedString()}"; + } + } +} diff --git a/ViewModels/SearchTabViewModel.cs b/ViewModels/SearchTabViewModel.cs new file mode 100644 index 0000000..1d3d425 --- /dev/null +++ b/ViewModels/SearchTabViewModel.cs @@ -0,0 +1,403 @@ +using System; +using System.Collections.ObjectModel; +using System.Threading; +using System.Threading.Tasks; +using LibgenDesktop.Infrastructure; +using LibgenDesktop.Models; +using LibgenDesktop.Models.Entities; +using LibgenDesktop.Models.ProgressArgs; +using LibgenDesktop.Models.Utils; + +namespace LibgenDesktop.ViewModels +{ + internal class SearchTabViewModel : TabViewModel + { + internal class SearchCompleteEventArgs : EventArgs + { + public SearchCompleteEventArgs(string searchQuery, ObservableCollection searchResult) + { + SearchQuery = searchQuery; + SearchResult = searchResult; + } + + public string SearchQuery { get; } + public ObservableCollection SearchResult { get; } + } + + private bool isSearchBlockVisible; + private bool isSearchParamsPanelVisible; + private string searchQuery; + private string searchBoxHint; + private bool isLibrarySelectorVisible; + private bool isNonFictionLibraryAvailable; + private bool isFictionLibraryAvailable; + private bool isSciMagLibraryAvailable; + private bool isNonFictionLibrarySelected; + private bool isFictionLibrarySelected; + private bool isSciMagLibrarySelected; + private bool isSearchProgressPanelVisible; + private string searchProgressStatus; + private bool isImportBlockVisible; + + public SearchTabViewModel(MainModel mainModel, IWindowContext mainWindowContext) + : base(mainModel, mainWindowContext, "Поиск") + { + ImportCommand = new Command(Import); + SearchCommand = new Command(Search); + Initialize(); + } + + public bool IsSearchBlockVisible + { + get + { + return isSearchBlockVisible; + } + set + { + isSearchBlockVisible = value; + NotifyPropertyChanged(); + } + } + + public bool IsSearchParamsPanelVisible + { + get + { + return isSearchParamsPanelVisible; + } + set + { + isSearchParamsPanelVisible = value; + NotifyPropertyChanged(); + } + } + + public string SearchQuery + { + get + { + return searchQuery; + } + set + { + searchQuery = value; + NotifyPropertyChanged(); + } + } + + public string SearchBoxHint + { + get + { + return searchBoxHint; + } + set + { + searchBoxHint = value; + NotifyPropertyChanged(); + } + } + + public bool IsLibrarySelectorVisible + { + get + { + return isLibrarySelectorVisible; + } + set + { + isLibrarySelectorVisible = value; + NotifyPropertyChanged(); + } + } + + public bool IsNonFictionLibraryAvailable + { + get + { + return isNonFictionLibraryAvailable; + } + set + { + isNonFictionLibraryAvailable = value; + NotifyPropertyChanged(); + } + } + + public bool IsFictionLibraryAvailable + { + get + { + return isFictionLibraryAvailable; + } + set + { + isFictionLibraryAvailable = value; + NotifyPropertyChanged(); + } + } + + public bool IsSciMagLibraryAvailable + { + get + { + return isSciMagLibraryAvailable; + } + set + { + isSciMagLibraryAvailable = value; + NotifyPropertyChanged(); + } + } + + public bool IsNonFictionLibrarySelected + { + get + { + return isNonFictionLibrarySelected; + } + set + { + isNonFictionLibrarySelected = value; + NotifyPropertyChanged(); + UpdateSearchBoxHint(); + } + } + + public bool IsFictionLibrarySelected + { + get + { + return isFictionLibrarySelected; + } + set + { + isFictionLibrarySelected = value; + NotifyPropertyChanged(); + UpdateSearchBoxHint(); + } + } + + public bool IsSciMagLibrarySelected + { + get + { + return isSciMagLibrarySelected; + } + set + { + isSciMagLibrarySelected = value; + NotifyPropertyChanged(); + UpdateSearchBoxHint(); + } + } + + public bool IsSearchProgressPanelVisible + { + get + { + return isSearchProgressPanelVisible; + } + set + { + isSearchProgressPanelVisible = value; + NotifyPropertyChanged(); + } + } + + public string SearchProgressStatus + { + get + { + return searchProgressStatus; + } + set + { + searchProgressStatus = value; + NotifyPropertyChanged(); + } + } + + public bool IsImportBlockVisible + { + get + { + return isImportBlockVisible; + } + set + { + isImportBlockVisible = value; + NotifyPropertyChanged(); + } + } + + public Command ImportCommand { get; } + public Command SearchCommand { get; } + + public event EventHandler ImportRequested; + public event EventHandler> NonFictionSearchComplete; + public event EventHandler> FictionSearchComplete; + public event EventHandler> SciMagSearchComplete; + + public void Refresh(bool setFocus) + { + int nonFictionBookCount = MainModel.NonFictionBookCount; + int fictionBookCount = MainModel.FictionBookCount; + int sciMagArticleCount = MainModel.SciMagArticleCount; + int availableLibraryCount = 0; + if (nonFictionBookCount > 0) + { + availableLibraryCount++; + } + if (fictionBookCount > 0) + { + availableLibraryCount++; + } + if (sciMagArticleCount > 0) + { + availableLibraryCount++; + } + if (availableLibraryCount > 0) + { + IsImportBlockVisible = false; + IsSearchBlockVisible = true; + IsSearchParamsPanelVisible = true; + IsNonFictionLibraryAvailable = nonFictionBookCount > 0; + IsFictionLibraryAvailable = fictionBookCount > 0; + IsSciMagLibraryAvailable = sciMagArticleCount > 0; + if (!IsNonFictionLibrarySelected && !IsFictionLibrarySelected && !IsSciMagLibrarySelected) + { + if (IsNonFictionLibraryAvailable) + { + IsNonFictionLibrarySelected = true; + } + else if (IsFictionLibraryAvailable) + { + IsFictionLibrarySelected = true; + } + else + { + IsSciMagLibrarySelected = true; + } + } + IsLibrarySelectorVisible = availableLibraryCount > 1; + if (setFocus) + { + Events.RaiseEvent(ViewModelEvent.RegisteredEventId.FOCUS_SEARCH_TEXT_BOX); + } + } + else + { + IsSearchBlockVisible = false; + IsImportBlockVisible = true; + } + } + + private void Initialize() + { + isSearchBlockVisible = false; + isSearchParamsPanelVisible = false; + isLibrarySelectorVisible = false; + isNonFictionLibraryAvailable = false; + isFictionLibraryAvailable = false; + isSciMagLibraryAvailable = false; + isNonFictionLibrarySelected = false; + isFictionLibrarySelected = false; + isSciMagLibrarySelected = false; + isSearchProgressPanelVisible = false; + isImportBlockVisible = false; + Refresh(setFocus: true); + } + + private void Import() + { + ImportRequested?.Invoke(this, EventArgs.Empty); + } + + private void UpdateSearchBoxHint() + { + if (IsNonFictionLibrarySelected) + { + SearchBoxHint = "Поиск по наименованию, авторам, серии, издателю и ISBN без дефисов"; + } + else if (IsFictionLibrarySelected) + { + SearchBoxHint = "Поиск по наименованию, авторам, серии, издателю и ISBN с дефисами"; + } + else if (IsSciMagLibrarySelected) + { + SearchBoxHint = "Поиск по наименованию, авторам, журналу, DOI, Pubmed ID и ISSN (p/e)"; + } + } + + private void Search() + { + if (!String.IsNullOrWhiteSpace(SearchQuery)) + { + IsSearchParamsPanelVisible = false; + IsSearchProgressPanelVisible = true; + UpdateSearchProgressStatus(0); + if (IsNonFictionLibrarySelected) + { + SearchItemsAsync(MainModel.SearchNonFictionAsync, SearchQuery, NonFictionSearchComplete); + } + else if (IsFictionLibrarySelected) + { + SearchItemsAsync(MainModel.SearchFictionAsync, SearchQuery, FictionSearchComplete); + } + else if (IsSciMagLibrarySelected) + { + SearchItemsAsync(MainModel.SearchSciMagAsync, SearchQuery, SciMagSearchComplete); + } + } + } + + private async void SearchItemsAsync(Func, CancellationToken, Task>> searchFunction, + string searchQuery, EventHandler> searchCompleteEventHandler) + { + Progress searchProgressHandler = new Progress(HandleSearchProgress); + CancellationToken cancellationToken = new CancellationToken(); + ObservableCollection result = null; + bool error = false; + try + { + result = await searchFunction(searchQuery, searchProgressHandler, cancellationToken); + } + catch (Exception exception) + { + ShowErrorWindow(exception); + error = true; + } + IsSearchProgressPanelVisible = false; + IsSearchParamsPanelVisible = true; + if (error) + { + Events.RaiseEvent(ViewModelEvent.RegisteredEventId.FOCUS_SEARCH_TEXT_BOX); + } + else + { + searchCompleteEventHandler?.Invoke(this, new SearchCompleteEventArgs(searchQuery, result)); + SearchQuery = String.Empty; + } + } + + private void HandleSearchProgress(SearchProgress searchProgress) + { + UpdateSearchProgressStatus(searchProgress.ItemsFound); + } + + private void UpdateSearchProgressStatus(int itemsFound) + { + string itemsFoundString = itemsFound.ToFormattedString(); + if (IsNonFictionLibrarySelected || IsFictionLibrarySelected) + { + SearchProgressStatus = $"Найдено книг: {itemsFoundString}"; + } + else if (IsSciMagLibrarySelected) + { + SearchProgressStatus = $"Найдено статей: {itemsFoundString}"; + } + } + } +} diff --git a/ViewModels/SettingsWindowViewModel.cs b/ViewModels/SettingsWindowViewModel.cs new file mode 100644 index 0000000..b75ed7a --- /dev/null +++ b/ViewModels/SettingsWindowViewModel.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using LibgenDesktop.Infrastructure; +using LibgenDesktop.Models; +using LibgenDesktop.Models.Settings; + +namespace LibgenDesktop.ViewModels +{ + internal class SettingsWindowViewModel : ViewModel, INotifyDataErrorInfo + { + private readonly MainModel mainModel; + private readonly Dictionary errors; + private bool isNetworkTabSelected; + private bool isSearchTabSelected; + private bool networkIsOfflineModeOn; + private bool searchIsLimitResultsOn; + private ObservableCollection searchMaximumResultCountDefaultValues; + private string searchMaximumResultCount; + private bool isOkButtonEnabled; + + public SettingsWindowViewModel(MainModel mainModel) + { + this.mainModel = mainModel; + errors = new Dictionary(); + OkCommand = new Command(OkButtonClick); + CancelCommand = new Command(CancelButtonClick); + Initialize(); + } + + public bool IsNetworkTabSelected + { + get + { + return isNetworkTabSelected; + } + set + { + isNetworkTabSelected = value; + NotifyPropertyChanged(); + } + } + + public bool IsSearchTabSelected + { + get + { + return isSearchTabSelected; + } + set + { + isSearchTabSelected = value; + NotifyPropertyChanged(); + } + } + + public bool NetworkIsOfflineModeOn + { + get + { + return networkIsOfflineModeOn; + } + set + { + networkIsOfflineModeOn = value; + NotifyPropertyChanged(); + } + } + + public bool SearchIsLimitResultsOn + { + get + { + return searchIsLimitResultsOn; + } + set + { + searchIsLimitResultsOn = value; + NotifyPropertyChanged(); + Validate(); + } + } + + public ObservableCollection SearchMaximumResultCountDefaultValues + { + get + { + return searchMaximumResultCountDefaultValues; + } + set + { + searchMaximumResultCountDefaultValues = value; + NotifyPropertyChanged(); + } + } + + public string SearchMaximumResultCount + { + get + { + return searchMaximumResultCount; + } + set + { + searchMaximumResultCount = value; + NotifyPropertyChanged(); + Validate(); + } + } + + public bool IsOkButtonEnabled + { + get + { + return isOkButtonEnabled; + } + set + { + isOkButtonEnabled = value; + NotifyPropertyChanged(); + } + } + + public bool HasErrors => errors.Any(); + + public Command OkCommand { get; } + public Command CancelCommand { get; } + + private int? SearchMaximumResultCountValue + { + get + { + if (Int32.TryParse(SearchMaximumResultCount, out int value)) + { + if (value > 0) + { + return value; + } + } + return null; + } + } + + public event EventHandler ErrorsChanged; + + public IEnumerable GetErrors(string propertyName) + { + if (errors.TryGetValue(propertyName, out string error)) + { + yield return error; + } + } + + private void Initialize() + { + isNetworkTabSelected = true; + isSearchTabSelected = false; + searchMaximumResultCountDefaultValues = new ObservableCollection { "100", "250", "500", "1000", "2500", "5000", "10000", "25000", "50000", "100000", "250000", "500000", "1000000" }; + AppSettings appSettings = mainModel.AppSettings; + networkIsOfflineModeOn = appSettings.Network.OfflineMode; + searchIsLimitResultsOn = appSettings.Search.LimitResults; + searchMaximumResultCount = appSettings.Search.MaximumResultCount.ToString(); + Validate(); + } + + private void Validate() + { + bool isValid = true; + if (SearchIsLimitResultsOn && SearchMaximumResultCountValue == null) + { + isValid = false; + } + string propertyName = nameof(SearchMaximumResultCount); + if (isValid && errors.Any()) + { + errors.Clear(); + ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName)); + } + else if (!isValid && !errors.Any()) + { + errors.Add(propertyName, "Только положительные числа"); + ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName)); + } + IsOkButtonEnabled = isValid; + } + + private void OkButtonClick() + { + mainModel.AppSettings.Network = new AppSettings.NetworkSettings + { + OfflineMode = NetworkIsOfflineModeOn + }; + mainModel.AppSettings.Search = new AppSettings.SearchSettings + { + LimitResults = SearchIsLimitResultsOn, + MaximumResultCount = SearchMaximumResultCountValue.Value + }; + mainModel.SaveSettings(); + CurrentWindowContext.CloseDialog(true); + } + + private void CancelButtonClick() + { + CurrentWindowContext.CloseDialog(false); + } + } +} diff --git a/ViewModels/SqlDumpImportWindowViewModel.cs b/ViewModels/SqlDumpImportWindowViewModel.cs deleted file mode 100644 index 9e8d8e5..0000000 --- a/ViewModels/SqlDumpImportWindowViewModel.cs +++ /dev/null @@ -1,148 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using LibgenDesktop.Infrastructure; -using LibgenDesktop.Models; -using LibgenDesktop.Models.ProgressArgs; -using LibgenDesktop.Models.Utils; - -namespace LibgenDesktop.ViewModels -{ - internal class SqlDumpImportWindowViewModel : ViewModel - { - private readonly MainModel mainModel; - private readonly string sqlDumpFilePath; - private DateTime operationStartTime; - private CancellationTokenSource cancellationTokenSource; - private bool allowWindowClose; - private string progressDescription; - private double progressValue; - private string status; - private bool cancellationEnabled; - - public SqlDumpImportWindowViewModel(MainModel mainModel, string sqlDumpFilePath) - { - this.mainModel = mainModel; - this.sqlDumpFilePath = sqlDumpFilePath; - CancelCommand = new Command(CancelImport); - WindowClosingCommand = new FuncCommand(WindowClosing); - Initialize(); - } - - public string ProgressDescription - { - get - { - return progressDescription; - } - private set - { - progressDescription = value; - NotifyPropertyChanged(); - } - } - - public double ProgressValue - { - get - { - return progressValue; - } - private set - { - progressValue = value; - NotifyPropertyChanged(); - } - } - - public string Status - { - get - { - return status; - } - private set - { - status = value; - NotifyPropertyChanged(); - } - } - - public bool CancellationEnabled - { - get - { - return cancellationEnabled; - } - private set - { - cancellationEnabled = value; - NotifyPropertyChanged(); - } - } - - public Command CancelCommand { get; } - public FuncCommand WindowClosingCommand { get; } - - private async void Initialize() - { - operationStartTime = DateTime.Now; - ProgressDescription = "Импорт из SQL-дампа..."; - UpdateStatus(0); - cancellationEnabled = true; - allowWindowClose = false; - Progress importSqlDumpProgressHandler = new Progress(HandleImportSqlDumpProgress); - cancellationTokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = cancellationTokenSource.Token; - try - { - await mainModel.ImportSqlDumpAsync(sqlDumpFilePath, importSqlDumpProgressHandler, cancellationToken); - } - catch (Exception exception) - { - ShowErrorWindow(exception); - } - allowWindowClose = true; - IWindowContext currentWindowContext = WindowManager.GetCreatedWindowContext(this); - currentWindowContext.Close(); - } - - private void UpdateStatus(double completed) - { - DateTime now = DateTime.Now; - TimeSpan elapsed = now - operationStartTime; - string newStatus = $"Прошло {elapsed:mm\\:ss}"; - if (completed > 0.05) - { - TimeSpan remaining = TimeSpan.FromSeconds(elapsed.TotalSeconds / completed * (1 - completed)); - newStatus += $", осталось {remaining:mm\\:ss}"; - } - Status = newStatus; - ProgressValue = completed; - } - - private void HandleImportSqlDumpProgress(ImportSqlDumpProgress importSqlDumpProgress) - { - ProgressDescription = $"Импорт из SQL-дампа... (импортировано {importSqlDumpProgress.BooksImported.ToString("N0", Formatters.ThousandsSeparatedNumberFormat)} книг)"; - UpdateStatus((double)importSqlDumpProgress.BytesParsed / importSqlDumpProgress.TotalBytes); - } - - private void CancelImport() - { - CancellationEnabled = false; - cancellationTokenSource.Cancel(); - } - - private bool WindowClosing() - { - if (!allowWindowClose) - { - CancelImport(); - } - return allowWindowClose; - } - } -} diff --git a/ViewModels/TabViewModel.cs b/ViewModels/TabViewModel.cs new file mode 100644 index 0000000..a35db18 --- /dev/null +++ b/ViewModels/TabViewModel.cs @@ -0,0 +1,36 @@ +using LibgenDesktop.Infrastructure; +using LibgenDesktop.Models; + +namespace LibgenDesktop.ViewModels +{ + internal abstract class TabViewModel : ViewModel + { + private string title; + + protected TabViewModel(MainModel mainModel, IWindowContext mainWindowContext, string title) + { + MainModel = mainModel; + MainWindowContext = mainWindowContext; + this.title = title; + Events = new EventProvider(); + } + + public string Title + { + get + { + return title; + } + set + { + title = value; + NotifyPropertyChanged(); + } + } + + public EventProvider Events { get; } + + protected MainModel MainModel { get; } + protected IWindowContext MainWindowContext { get; } + } +} diff --git a/ViewModels/ViewModel.cs b/ViewModels/ViewModel.cs index fc068dc..c2ec55d 100644 --- a/ViewModels/ViewModel.cs +++ b/ViewModels/ViewModel.cs @@ -7,6 +7,14 @@ namespace LibgenDesktop.ViewModels { internal abstract class ViewModel : INotifyPropertyChanged { + protected IWindowContext CurrentWindowContext + { + get + { + return WindowManager.GetWindowContext(this); + } + } + public event PropertyChangedEventHandler PropertyChanged; protected void NotifyPropertyChanged([CallerMemberName]string propertyName = "") @@ -16,10 +24,10 @@ protected void NotifyPropertyChanged([CallerMemberName]string propertyName = "") protected void ShowErrorWindow(Exception exception) { - IWindowContext currentWindowContext = WindowManager.GetCreatedWindowContext(this); + IWindowContext currentWindowContext = WindowManager.GetWindowContext(this); ErrorWindowViewModel errorWindowViewModel = new ErrorWindowViewModel(exception.ToString()); - IWindowContext bookDetailsWindowContext = WindowManager.CreateWindow(RegisteredWindows.WindowKey.ERROR_WINDOW, errorWindowViewModel, currentWindowContext); - bookDetailsWindowContext.ShowDialog(); + IWindowContext errorWindowContext = WindowManager.CreateWindow(RegisteredWindows.WindowKey.ERROR_WINDOW, errorWindowViewModel, currentWindowContext); + errorWindowContext.ShowDialog(); } } } diff --git a/Views/Controls/AddTabButton.xaml b/Views/Controls/AddTabButton.xaml new file mode 100644 index 0000000..36f9758 --- /dev/null +++ b/Views/Controls/AddTabButton.xaml @@ -0,0 +1,23 @@ + diff --git a/Views/Controls/AddTabButton.xaml.cs b/Views/Controls/AddTabButton.xaml.cs new file mode 100644 index 0000000..81aed2a --- /dev/null +++ b/Views/Controls/AddTabButton.xaml.cs @@ -0,0 +1,11 @@ +namespace LibgenDesktop.Views.Controls +{ + public partial class AddTabButton + { + public AddTabButton() + { + InitializeComponent(); + } + + } +} diff --git a/Views/Controls/BookAttributeValueLabel.xaml.cs b/Views/Controls/BookAttributeValueLabel.xaml.cs index b1c5206..f77ae87 100644 --- a/Views/Controls/BookAttributeValueLabel.xaml.cs +++ b/Views/Controls/BookAttributeValueLabel.xaml.cs @@ -25,7 +25,7 @@ private void TextChanged(object sender, EventArgs e) { ContextMenu labelContextMenu = Resources["labelContextMenu"] as ContextMenu; MenuItem copyMenuItem = labelContextMenu.Items[0] as MenuItem; - copyMenuItem.Header = $"Копировать \"{ Text }\""; + copyMenuItem.Header = $"Копировать \"{Text}\""; ContextMenu = labelContextMenu; } } diff --git a/Views/Controls/CloseTabButton.xaml b/Views/Controls/CloseTabButton.xaml new file mode 100644 index 0000000..479ca42 --- /dev/null +++ b/Views/Controls/CloseTabButton.xaml @@ -0,0 +1,23 @@ + diff --git a/Views/Controls/CloseTabButton.xaml.cs b/Views/Controls/CloseTabButton.xaml.cs new file mode 100644 index 0000000..de10d36 --- /dev/null +++ b/Views/Controls/CloseTabButton.xaml.cs @@ -0,0 +1,11 @@ +namespace LibgenDesktop.Views.Controls +{ + public partial class CloseTabButton + { + public CloseTabButton() + { + InitializeComponent(); + } + + } +} diff --git a/Views/Controls/ControlExtensions.cs b/Views/Controls/ControlExtensions.cs new file mode 100644 index 0000000..d920d2a --- /dev/null +++ b/Views/Controls/ControlExtensions.cs @@ -0,0 +1,34 @@ +using System.Windows; +using System.Windows.Controls; + +namespace LibgenDesktop.Views.Controls +{ + internal static class ControlExtensions + { + public static readonly DependencyProperty MaxLengthProperty = DependencyProperty.RegisterAttached("MaxLength", typeof(int), typeof(ControlExtensions), new UIPropertyMetadata(OnMaxLengthChanged)); + + public static int GetMaxLength(DependencyObject dependencyObject) + { + return (int)dependencyObject.GetValue(MaxLengthProperty); + } + + public static void SetMaxLength(DependencyObject dependencyObject, int value) + { + dependencyObject.SetValue(MaxLengthProperty, value); + } + + private static void OnMaxLengthChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) + { + if (dependencyObject is ComboBox comboBox) + { + comboBox.Loaded += (sender, args) => + { + if (comboBox.Template.FindName("PART_EditableTextBox", comboBox) is TextBox textBox) + { + textBox.SetValue(TextBox.MaxLengthProperty, e.NewValue); + } + }; + } + } + } +} diff --git a/Views/Controls/PressedSwitch.xaml b/Views/Controls/PressedSwitch.xaml new file mode 100644 index 0000000..6131b1e --- /dev/null +++ b/Views/Controls/PressedSwitch.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Views/Controls/PressedSwitch.xaml.cs b/Views/Controls/PressedSwitch.xaml.cs new file mode 100644 index 0000000..e654b05 --- /dev/null +++ b/Views/Controls/PressedSwitch.xaml.cs @@ -0,0 +1,10 @@ +namespace LibgenDesktop.Views.Controls +{ + public partial class PressedSwitch + { + public PressedSwitch() + { + InitializeComponent(); + } + } +} diff --git a/Views/Controls/SettingsTab.xaml b/Views/Controls/SettingsTab.xaml new file mode 100644 index 0000000..6ac26f4 --- /dev/null +++ b/Views/Controls/SettingsTab.xaml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + diff --git a/Views/Controls/SettingsTab.xaml.cs b/Views/Controls/SettingsTab.xaml.cs new file mode 100644 index 0000000..bf7a39f --- /dev/null +++ b/Views/Controls/SettingsTab.xaml.cs @@ -0,0 +1,10 @@ +namespace LibgenDesktop.Views.Controls +{ + public partial class SettingsTab + { + public SettingsTab() + { + InitializeComponent(); + } + } +} diff --git a/Views/Controls/TabControl.cs b/Views/Controls/TabControl.cs new file mode 100644 index 0000000..118b724 --- /dev/null +++ b/Views/Controls/TabControl.cs @@ -0,0 +1,23 @@ +using System.Windows; +using System.Windows.Input; +using Dragablz; + +namespace LibgenDesktop.Views.Controls +{ + public class TabControl : TabablzControl + { + public static readonly DependencyProperty CloseTabCommandProperty = DependencyProperty.Register("CloseTabCommand", typeof(ICommand), typeof(TabControl)); + + public ICommand CloseTabCommand + { + get + { + return (ICommand)GetValue(CloseTabCommandProperty); + } + set + { + SetValue(CloseTabCommandProperty, value); + } + } + } +} diff --git a/Views/Controls/Toolbar.xaml b/Views/Controls/Toolbar.xaml new file mode 100644 index 0000000..3bb9654 --- /dev/null +++ b/Views/Controls/Toolbar.xaml @@ -0,0 +1,18 @@ + + + + + + + +