diff --git a/PcgsInvUi/App.axaml.cs b/PcgsInvUi/App.axaml.cs index c5de0f4..8501681 100644 --- a/PcgsInvUi/App.axaml.cs +++ b/PcgsInvUi/App.axaml.cs @@ -17,11 +17,10 @@ public partial class App : Application { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { var coins = new CoinDatabase(); desktop.MainWindow = new MainWindow { - DataContext = new MainWindowViewModel(coins, !coins.TryInitApiClient()), + DataContext = new MainWindowViewModel(coins), }; // Allow the application to save the database when it exits. - // The issue is that this will break if the database is not initialized here. desktop.Exit += (sender, args) => { coins.UpdateCollection("CollectionTable"); }; diff --git a/PcgsInvUi/Services/CoinDatabase.cs b/PcgsInvUi/Services/CoinDatabase.cs index decc7b6..194fbf4 100644 --- a/PcgsInvUi/Services/CoinDatabase.cs +++ b/PcgsInvUi/Services/CoinDatabase.cs @@ -30,6 +30,9 @@ public class CoinDatabase { Console.WriteLine("Got collection."); } + /// + /// Destructor. Currently not used, due to the strange nature of desktop applications. + /// ~CoinDatabase() { Console.WriteLine("Destructor called, saving collection."); // Save collection. @@ -42,7 +45,7 @@ public class CoinDatabase { /// Attempts to intitialize the API client by selecting the API key from the database. /// /// True if the API key was found, false otherwise. - public bool TryInitApiClient() { + public async Task TryInitApiClient() { Console.Write("Attempting to initialize API client... "); SQLiteCommand cmd = Connection.CreateCommand(); cmd.CommandText = "SELECT API_KEY FROM ApiTable"; @@ -50,7 +53,7 @@ public class CoinDatabase { using (var reader = cmd.ExecuteReader()) { if (reader.Read()) { var apiKey = reader.GetString(0); - _pcgsClient = PcgsClient.CreateClient(apiKey); + _pcgsClient = await PcgsClient.CreateClient(apiKey); // Check that the client is valid. if (_pcgsClient is null) { @@ -59,14 +62,8 @@ public class CoinDatabase { return IsClientConnected; } - // This only runs if the key is valid. Insert the key into the database. - // TODO Create checks to ensure that the database doesn't already contain a key. - var insertCmd = Connection.CreateCommand(); - insertCmd.CommandText = $"INSERT INTO ApiTable (API_KEY, REQUESTS_REMAINING) VALUE ({apiKey}, 1000)"; - insertCmd.ExecuteNonQuery(); - IsClientConnected = true; - Console.WriteLine("Done!"); + Console.WriteLine("Done! API key was found from database."); } else { IsClientConnected = false; @@ -76,9 +73,13 @@ public class CoinDatabase { return IsClientConnected; } - public bool TryInitApiClient(string apiKey) { + /// + /// Atttempts to initialize the PCGS API client. + /// + /// A Task with a return value that returns true if the client intializes properly. + public async Task TryInitApiClient(string apiKey) { Console.Write("Attempting to initialize API client with key param... "); - _pcgsClient = PcgsClient.CreateClient(apiKey); + _pcgsClient = await PcgsClient.CreateClient(apiKey); // TODO Check if the API key is valid and if it is, then add to the database. if (_pcgsClient is null) { IsClientConnected = false; @@ -87,13 +88,19 @@ public class CoinDatabase { } var cmd = Connection.CreateCommand(); - cmd.CommandText = $"INSERT INTO ApiTable (API_KEY, REQUESTS_REMAINING) VALUES ({apiKey}, 1000)"; - // cmd.ExecuteNonQuery(); + cmd.CommandText = $"INSERT INTO ApiTable (API_KEY, REQUESTS_REMAINING) VALUES (\"{apiKey}\", 1000)"; + await cmd.ExecuteNonQueryAsync(); + IsClientConnected = true; - Console.WriteLine("Done!"); + Console.WriteLine("Done! API key was added to database."); return IsClientConnected; } + /// + /// Gets the collection from the database and stores it in the Collection property. + /// Creates the collection table if it does not exist already. + /// + /// The SQLiteConnection that corresponds to the connected database. private SQLiteConnection CreateConnection() { // Check if database exists. if (!System.IO.File.Exists("coin_collection")) { @@ -119,6 +126,9 @@ public class CoinDatabase { return connection; } + /// + /// Creates the table that stores the API key and the number of requests remaining. + /// public void CreateApiTable() { // Create a table to store the API key and the number of requests remaining. if (!IsConnected) { @@ -176,6 +186,9 @@ public class CoinDatabase { Console.WriteLine("Collection table initialized."); } + /// + /// Inserts a Coin object into the database. + /// public void InsertCoin(Coin coin) { Console.WriteLine("Inserting coin..."); // Use our existing connection to insert a coin to the database. @@ -202,6 +215,13 @@ public class CoinDatabase { Console.WriteLine("Coin inserted."); } + /// + /// Creates a Coin object and saves it to the database. + /// + /// The PCGS number of the coin. + /// The grade of the coin. + /// The quantity of the coin. + /// The error type returned by the API. public async Task CreateCoin(int pcgsNumber, string grade, int quantity) { Console.WriteLine("Creating coin..."); // Create a coin by calling the API, then insert it into the database. @@ -227,6 +247,12 @@ public class CoinDatabase { return apiResult.Item1; } + /// + /// Gets a coin from the database. + /// + /// The PCGS number of the coin. + /// The grade of the coin. + /// The coin if it exists, null otherwise. public Coin? GetCoin(int pcgsNumber, string grade) { Console.WriteLine($"Getting coin {pcgsNumber}, {grade}..."); int gradeInt = GetGradeFromString(grade); @@ -246,6 +272,10 @@ public class CoinDatabase { return null; } + /// + /// Used to get all Coin objects within the database. + /// + /// The name of the collection table. public void GetCollection(string collectionName) { Console.WriteLine("Getting collection..."); SQLiteCommand cmd = Connection.CreateCommand(); @@ -259,6 +289,11 @@ public class CoinDatabase { } } + /// + /// Creates a CSV formatted string from a collection. + /// + /// The name of the collection table. + /// A CSV formatted string. public String CollectionToCSV(string collectionName) { var cmd = Connection.CreateCommand(); cmd.CommandText = $"SELECT * FROM {collectionName}"; @@ -307,6 +342,11 @@ public class CoinDatabase { return resultString; } + /// + /// Saves a Coin object to the database. + /// + /// The name of the collection table. + /// The coin to save. public void SaveCoin(string collectionName, Coin coin) { // Get and replace the coin from the database. Console.WriteLine("Saving coin..."); @@ -333,6 +373,10 @@ public class CoinDatabase { cmd.ExecuteNonQuery(); } + /// + /// Synchronizes the database with the current contents of the specified collection. + /// + /// The name of the collection table. public void UpdateCollection(string collectionName) { // Update the database with the contents of the collection. // TODO This is a very inefficient way of doing this. I should be able to do this in one command. @@ -341,6 +385,10 @@ public class CoinDatabase { } } + /// + /// Creates a Coin from the information in a reader specifically from this database. + /// + /// The reader to get the information from. private Coin CoinFromReader(SQLiteDataReader reader) { // Create a coin while filtering out any possible null values - they throw exceptions. Coin coin = new Coin(); @@ -364,6 +412,10 @@ public class CoinDatabase { return coin; } + /// + /// Deletes a Coin object from the database. + /// + /// The coin to delete. public void DeleteCoin(Coin delCoin) { SQLiteCommand cmd = Connection.CreateCommand(); cmd.CommandText = "DELETE FROM CollectionTable WHERE PCGS_NUMBER = @PcgsNumber AND GRADE = @Grade"; @@ -373,6 +425,11 @@ public class CoinDatabase { cmd.ExecuteNonQuery(); } + /// + /// Gets the grade from a string returned from the main window. + /// These are always in a format similar to "MS-65", so we can just get the last two characters. + /// + /// The grade string. private int GetGradeFromString(string grade) { var actualGrade = int.Parse(grade.Substring(grade.Length - 2)); Console.WriteLine($"Grade is {actualGrade}"); diff --git a/PcgsInvUi/Services/PcgsClient.cs b/PcgsInvUi/Services/PcgsClient.cs index 21752dd..9602343 100644 --- a/PcgsInvUi/Services/PcgsClient.cs +++ b/PcgsInvUi/Services/PcgsClient.cs @@ -39,18 +39,19 @@ public class PcgsClient : ReactiveObject { /// /// The api key to use for requests. /// A new instance of the PcgsClient class, or null if the key is invalid. - public static PcgsClient? CreateClient(string apiKey) { + public static async Task CreateClient(string apiKey) { // Check if the api key is valid. var testClient = new PcgsClient(apiKey); - if (!testClient.VerifyCredentials()) return null; + var validCreds = await testClient.VerifyCredentials(); + if (!validCreds) return null; return testClient; } - public bool VerifyCredentials() { + public async Task VerifyCredentials() { // Create a way to check if the api key given is valid // I think the best way to do this is to make a request to the api and see if it returns the expected response. - var result = this.GetCoinFactsByGrade(1, "1"); // This is a known invalid coin id - this is because the api is very fast with invalid requests. - if (result.Result.Item1 == ErrorType.ApiKeyInvalid) return false; + var result = await this.GetCoinFactsByGrade(1, "AG-03"); // This is a known invalid coin id - this is because the api is very fast with invalid requests. + if (result.Item1 == ErrorType.ApiKeyInvalid) return false; return true; } @@ -66,6 +67,7 @@ public class PcgsClient : ReactiveObject { Console.WriteLine("Got response."); if (response.IsSuccessStatusCode) { + // Check response for errors. string jsonResponse = await response.Content.ReadAsStringAsync(); var parsedResponse = ParseJsonResponse(jsonResponse); return parsedResponse; @@ -76,13 +78,11 @@ public class PcgsClient : ReactiveObject { } private int GetGradeFromString(string grade) { - var actualGrade = int.Parse(grade.Substring(grade.Length - 2)); + var actualGrade = int.Parse(grade.Substring(grade.Length - 2)); // Throws an exception if the grade doesn't exist. Console.WriteLine($"Grade is {actualGrade}"); return actualGrade; } - // TODO Will eventually create the other methods that are included in the Public API. - private (ErrorType, Coin?) ParseJsonResponse(string json) { Console.WriteLine(json); // TODO Check for errors in the response diff --git a/PcgsInvUi/ViewModels/MainWindowViewModel.cs b/PcgsInvUi/ViewModels/MainWindowViewModel.cs index 4464f05..2291d54 100644 --- a/PcgsInvUi/ViewModels/MainWindowViewModel.cs +++ b/PcgsInvUi/ViewModels/MainWindowViewModel.cs @@ -7,6 +7,7 @@ using System.Reactive.Linq; using PcgsInvUi.Models; using PcgsInvUi.Services; using ReactiveUI; +using System.Threading.Tasks; namespace PcgsInvUi.ViewModels; @@ -35,9 +36,11 @@ public class MainWindowViewModel : ViewModelBase { private ViewModelBase _sideContent; private Coin? _selectedCoin; private double _totalValue; - - public MainWindowViewModel(CoinDatabase coins, bool askForApiKey) { + + public MainWindowViewModel(CoinDatabase coins) { ConnectedCoinDatabase = coins; + var keyFound = Task.Run(() => coins.TryInitApiClient()).Result; + Console.WriteLine("Function completed."); var newViewModel = new NewViewModel(); var apiKeyViewModel = new ApiKeyViewModel(); // DisplayedList = CoinCollection = new ObservableCollection(coins.GetItems()); @@ -45,13 +48,13 @@ public class MainWindowViewModel : ViewModelBase { TotalValue = DisplayedList.Sum(x => x.TotalPrice); // Setup interaction to show ApiKeyWindow - apiKeyViewModel.OkCommand.Subscribe(apiKey => { - if (ConnectedCoinDatabase.TryInitApiClient(apiKey)) - SidebarContent = newViewModel; // Must assign this to the observable property. + apiKeyViewModel.OkCommand.Subscribe(async apiKey => { + if (await ConnectedCoinDatabase.TryInitApiClient(apiKey)) + SidebarContent = newViewModel; // Must assign this to the observable property rather than private variable. }); // Check if API key must be entered. - if (askForApiKey) { + if (!keyFound) { Console.WriteLine("API key must be entered by user."); // Show the API key input view. _sideContent = apiKeyViewModel;