diff --git a/PcgsInvUi/App.axaml.cs b/PcgsInvUi/App.axaml.cs
index ed78335..e71a99c 100644
--- a/PcgsInvUi/App.axaml.cs
+++ b/PcgsInvUi/App.axaml.cs
@@ -1,3 +1,5 @@
+using System;
+using System.Data.Entity.Migrations.Model;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
@@ -15,15 +17,20 @@ public partial class App : Application {
public override void OnFrameworkInitializationCompleted() {
base.OnFrameworkInitializationCompleted();
-
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
var coins = new CoinDatabase();
-
desktop.MainWindow = new MainWindow {
DataContext = new MainWindowViewModel(coins),
};
+
+ desktop.Exit += (sender, args) => {
+ coins.UpdateCollection("CollectionTable");
+ };
}
-
base.OnFrameworkInitializationCompleted();
}
+
+ public void OnExit(object sender, ControlledApplicationLifetimeExitEventArgs e) {
+ // Save the database?
+ }
}
\ No newline at end of file
diff --git a/PcgsInvUi/Models/Coin.cs b/PcgsInvUi/Models/Coin.cs
index ee6ebe6..2d9e8bf 100644
--- a/PcgsInvUi/Models/Coin.cs
+++ b/PcgsInvUi/Models/Coin.cs
@@ -1,5 +1,4 @@
-using System;
-using ReactiveUI;
+using ReactiveUI;
namespace PcgsInvUi.Models;
diff --git a/PcgsInvUi/PcgsInvUi.csproj b/PcgsInvUi/PcgsInvUi.csproj
index a1ce9f5..8a56561 100644
--- a/PcgsInvUi/PcgsInvUi.csproj
+++ b/PcgsInvUi/PcgsInvUi.csproj
@@ -1,7 +1,7 @@
WinExe
- net6.0
+ net7.0
enable
true
app.manifest
@@ -15,13 +15,13 @@
-
-
-
-
+
+
+
+
-
-
+
+
@@ -83,4 +83,9 @@
+
+
+
+
+
diff --git a/PcgsInvUi/Services/CoinDatabase.cs b/PcgsInvUi/Services/CoinDatabase.cs
index cdee8f5..a5ec1ac 100644
--- a/PcgsInvUi/Services/CoinDatabase.cs
+++ b/PcgsInvUi/Services/CoinDatabase.cs
@@ -1,5 +1,5 @@
using System;
-using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Data.SQLite;
using PcgsInvUi.Models;
@@ -8,13 +8,30 @@ namespace PcgsInvUi.Services;
public class CoinDatabase {
public SQLiteConnection Connection { get; set; }
private Boolean IsConnected { get; set; }
-
+ public ObservableCollection Collection { get; set; }
+
+ private PcgsClient _pcgsClient;
+
public CoinDatabase() {
// Create a connection to SQLite
Connection = CreateConnection();
+ // Create a client to access the API.
+ _pcgsClient = new PcgsClient(
+ "eAb8gS0I2XAvT_5gJeiGJaglMia1Tk-oB4kJUK6kuafyrny_S61vIJY-Ikl4nCQM67wrdxzUqLVWTV2kBSxD3d5XNBHxHnYBhcSS6dOPug0hZaF3qAv56df3gYSzOGh9Tif5y0eP3Iw0LrqKDr1Hj-dk6SV6GKog2IIqCPQhhHH8FMTWBTYO-_O8cx7qLdM5GM8KlTsic6g3VRUhM8EA_4OO04dCfmNLGhqINRl3jGZ0Q4ziI8fng2bVWsIyteqiPzUn10rIQ3-OPpqVZG_DxeOmOejj4GzbUNyqUOajy-nr5rYY");
// Create a base table.
CreateCollection("CollectionTable");
Console.WriteLine("Connected to database.");
+ Collection = new ObservableCollection();
+ GetCollection("CollectionTable");
+ Console.WriteLine("Got collection.");
+ }
+
+ ~CoinDatabase() {
+ Console.WriteLine("Destructor called, saving collection.");
+ // Save collection.
+ UpdateCollection("CollectionTable");
+ // Close the connection.
+ Connection.Close();
}
private SQLiteConnection CreateConnection() {
@@ -62,13 +79,15 @@ public class CoinDatabase {
"DESIGNATION TEXT," +
"CERT_NUM INTEGER," +
"QUANTITY INTEGER," +
+ "NOTES TEXT," +
"PRIMARY KEY(PCGS_NUMBER, GRADE))";
// Execute the command.
cmd.ExecuteNonQuery();
}
- public void InsertCoin(Coin newCoin) {
+ public void InsertCoin(Coin coin) {
+ Console.WriteLine("Inserting coin...");
// Use our existing connection to insert a coin to the database.
if (!IsConnected) {
// TODO Create a NotConnectedToDatabaseException
@@ -82,44 +101,63 @@ public class CoinDatabase {
"INSERT INTO CollectionTable (PCGS_NUMBER, GRADE, YEAR," +
"DENOMINATION, MINT_MARK, PRICE_GUIDE_VALUE, COIN_FACTS_LINK," +
"MAJOR_VARIETY, MINOR_VARIETY, DIE_VARIETY, SERIES_NAME, CATEGORY, DESIGNATION," +
- "CERT_NUM, QUANTITY) " +
- "VALUES (@PcgsNumber, @Grade, @Year, @Denomination, @MintMark, @PriceGuideValue," +
- "@CoinFactsLink, @MajorVariety, @MinorVariety, @DieVariety, @SeriesName, @Category, @Designation," +
- "@CertificateNumber, @Quantity)";
- cmd.Parameters.AddWithValue("@PcgsNumber", newCoin.PCGSNo);
- cmd.Parameters.AddWithValue("@Grade", newCoin.Grade);
- cmd.Parameters.AddWithValue("@Year", newCoin.Year);
- cmd.Parameters.AddWithValue("@Denomination", newCoin.Denomination);
- cmd.Parameters.AddWithValue("@MintMark", newCoin.MintMark);
- cmd.Parameters.AddWithValue("@PriceGuideValue", newCoin.PriceGuideValue);
- cmd.Parameters.AddWithValue("@CoinFactsLink", newCoin.CoinFactsLink);
- cmd.Parameters.AddWithValue("@MajorVariety", newCoin.MajorVariety);
- cmd.Parameters.AddWithValue("@MinorVariety", newCoin.MinorVariety);
- cmd.Parameters.AddWithValue("@DieVariety", newCoin.DieVariety);
- cmd.Parameters.AddWithValue("@SeriesName", newCoin.SeriesName);
- cmd.Parameters.AddWithValue("@Category", newCoin.Category);
- cmd.Parameters.AddWithValue("@Designation", newCoin.Designation);
- cmd.Parameters.AddWithValue("@CertificateNumber", newCoin.CertificateNumber);
- cmd.Parameters.AddWithValue("@Quantity", newCoin.Quantity);
+ "CERT_NUM, QUANTITY, NOTES) " +
+ $"VALUES ('{coin.PCGSNo}', '{coin.Grade}', {coin.Year}, '{coin.Denomination}', '{coin.MintMark}', {coin.PriceGuideValue}," +
+ $"'{coin.CoinFactsLink}', '{coin.MajorVariety}', '{coin.MinorVariety}', '{coin.DieVariety}', '{coin.SeriesName}', " +
+ $"'{coin.Category}', '{coin.Designation}', {coin.CertificateNumber}, {coin.Quantity}, @Notes)";
+ cmd.Parameters.AddWithValue("@Notes", coin.Notes);
// Execute the command.
cmd.ExecuteNonQuery();
}
- public List GetCollection(string collectionName) {
+ public async void CreateCoin(int pcgsNumber, string grade, int quantity) {
+ Console.WriteLine("Creating coin...");
+ // Create a coin by calling the API, then insert it into the database.
+ // First, check if the coin already exists - if it does, just update the quantity by adding the amount entered.
+ var coin = GetCoin(pcgsNumber, grade);
+ if (coin is not null) {
+ coin.Quantity += quantity;
+ SaveCoin("CollectionTable", coin);
+ }
+ else {
+ // Build a new coin with the API and insert it.
+ var newCoin = await _pcgsClient.GetCoinFactsByGrade(pcgsNumber, grade);
+ newCoin.Quantity = quantity;
+ InsertCoin(newCoin);
+ }
+ }
+
+ public Coin? GetCoin(int pcgsNumber, string grade) {
+ Console.WriteLine($"Getting coin {pcgsNumber}, {grade}...");
+ int gradeInt = GetGradeFromString(grade);
+
+ // Check the collection to see if the coin exists.
+ // We can't make a more efficient algorithm since there's not really an ordering to the collection.
+ foreach (var coin in Collection) {
+ if (coin.PCGSNo == pcgsNumber.ToString() && coin.Grade.Contains(gradeInt.ToString())) { // This has the possibility of a false positive for single digit grades.
+ if (gradeInt < 10)
+ if (char.IsDigit(coin.Grade[3])) continue; // TODO For right now, we're going to assume that the first 2 characters are ciruclation strings, and the next 2 are grade numbers.
+ Console.WriteLine("Found coin.");
+ return coin;
+ }
+ }
+
+ Console.WriteLine("Couldn't find coin.");
+ return null;
+ }
+
+ public void GetCollection(string collectionName) {
+ Console.WriteLine("Getting collection...");
SQLiteCommand cmd = Connection.CreateCommand();
cmd.CommandText = $"SELECT * FROM {collectionName}";
-
- var coins = new List();
using (var reader = cmd.ExecuteReader()) {
while (reader.Read()) {
// Create coin from reader then add to list.
- coins.Add(CoinFromReader(reader));
+ Collection.Add(CoinFromReader(reader));
}
}
-
- return coins;
}
public String CollectionToCSV(string collectionName) {
@@ -163,54 +201,70 @@ public class CoinDatabase {
resultString += "NULL" + ",";
}
}
-
resultString += "\n";
}
}
return resultString;
}
-
- public Coin? GetCoin(int pcgsNumber, string grade) {
- // Get the coin based off of the pcgs number.
+
+ public void SaveCoin(string collectionName, Coin coin) {
+ // Get and replace the coin from the database.
+ Console.WriteLine("Saving coin...");
SQLiteCommand cmd = Connection.CreateCommand();
- cmd.CommandText = "SELECT * FROM CollectionTable WHERE Id = @Id AND Grade = @Grade";
- cmd.Parameters.AddWithValue("@Id", pcgsNumber);
- cmd.Parameters.AddWithValue("@Grade", grade);
-
- Coin? returnCoin = null;
-
+ cmd.CommandText = $"UPDATE {collectionName} " +
+ $"SET YEAR = {coin.Year}, " +
+ $"DENOMINATION = \'{coin.Denomination}\', " +
+ $"MINT_MARK = \'{coin.MintMark}\', " +
+ $"PRICE_GUIDE_VALUE = \'{coin.PriceGuideValue}\', " +
+ $"COIN_FACTS_LINK = \'{coin.CoinFactsLink}\', " +
+ $"MAJOR_VARIETY = \'{coin.MajorVariety}\', " +
+ $"MINOR_VARIETY = \'{coin.MinorVariety}\', " +
+ $"DIE_VARIETY = \'{coin.DieVariety}\', " +
+ $"SERIES_NAME = \'{coin.SeriesName}\', " +
+ $"CATEGORY = \'{coin.Category}\', " +
+ $"DESIGNATION = \'{coin.Designation}\', " +
+ $"CERT_NUM = \'{coin.CertificateNumber}\', " +
+ $"QUANTITY = {coin.Quantity}, " +
+ $"NOTES = @Notes " +
+ $"WHERE PCGS_NUMBER = {coin.PCGSNo} AND GRADE = \'{coin.Grade}\'";
+ cmd.Parameters.AddWithValue("@Notes", coin.Notes);
+ Console.WriteLine(cmd.CommandText);
// Execute the command.
- using (var reader = cmd.ExecuteReader()) {
- while (reader.Read()) {
- returnCoin = CoinFromReader(reader);
- }
- }
+ cmd.ExecuteNonQuery();
+ }
- return returnCoin;
+ 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.
+ foreach (var coin in Collection) {
+ SaveCoin(collectionName, coin); // CAREFUL! This will not create a coin if it doesn't currently exist.
+ }
}
private Coin CoinFromReader(SQLiteDataReader reader) {
+ // Create a coin while filtering out any possible null values - they throw exceptions.
Coin coin = new Coin();
- coin.PCGSNo = reader.GetString(0);
- coin.Grade = reader.GetString(1);
- coin.Year = reader.GetInt32(2);
- coin.Denomination = reader.GetString(3);
- coin.MintMark = reader.GetString(4);
- coin.PriceGuideValue = reader.GetDouble(5);
- coin.CoinFactsLink = reader.GetString(6);
- coin.MajorVariety = reader.GetString(7);
- coin.MinorVariety = reader.GetString(8);
- coin.DieVariety = reader.GetString(9);
- coin.SeriesName = reader.GetString(10);
- coin.Category = reader.GetString(11);
- coin.Designation = reader.GetString(12);
- coin.CertificateNumber = reader.GetInt32(13);
- coin.Quantity = reader.GetInt32(14);
+ if (!reader.IsDBNull(0)) coin.PCGSNo = reader.GetString(0);
+ if (!reader.IsDBNull(1)) coin.Grade = reader.GetString(1);
+ if (!reader.IsDBNull(2)) coin.Year = reader.GetInt32(2);
+ if (!reader.IsDBNull(3)) coin.Denomination = reader.GetString(3);
+ if (!reader.IsDBNull(4)) coin.MintMark = reader.GetString(4);
+ if (!reader.IsDBNull(5)) coin.PriceGuideValue = reader.GetDouble(5);
+ if (!reader.IsDBNull(6)) coin.CoinFactsLink = reader.GetString(6);
+ if (!reader.IsDBNull(7)) coin.MajorVariety = reader.GetString(7);
+ if (!reader.IsDBNull(8)) coin.MinorVariety = reader.GetString(8);
+ if (!reader.IsDBNull(9)) coin.DieVariety = reader.GetString(9);
+ if (!reader.IsDBNull(10)) coin.SeriesName = reader.GetString(10);
+ if (!reader.IsDBNull(11)) coin.Category = reader.GetString(11);
+ if (!reader.IsDBNull(12)) coin.Designation = reader.GetString(12);
+ if (!reader.IsDBNull(13)) coin.CertificateNumber = reader.GetInt32(13);
+ if (!reader.IsDBNull(14)) coin.Quantity = reader.GetInt32(14);
+ if (!reader.IsDBNull(15)) coin.Notes = reader.GetString(15);
coin.TotalPrice = coin.Quantity * coin.PriceGuideValue;
return coin;
}
-
+
public void DeleteCoin(Coin delCoin) {
SQLiteCommand cmd = Connection.CreateCommand();
cmd.CommandText = "DELETE FROM CollectionTable WHERE PCGS_NUMBER = @PcgsNumber AND GRADE = @Grade";
@@ -219,4 +273,10 @@ public class CoinDatabase {
cmd.ExecuteNonQuery();
}
+
+ private int GetGradeFromString(string grade) {
+ var actualGrade = int.Parse(grade.Substring(grade.Length - 2));
+ Console.WriteLine($"Grade is {actualGrade}");
+ return actualGrade;
+ }
}
\ No newline at end of file
diff --git a/PcgsInvUi/Services/PcgsClient.cs b/PcgsInvUi/Services/PcgsClient.cs
index 791f3fe..3b39b49 100644
--- a/PcgsInvUi/Services/PcgsClient.cs
+++ b/PcgsInvUi/Services/PcgsClient.cs
@@ -2,16 +2,22 @@
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
-// using Newtonsoft.Json;
using PcgsInvUi.Models;
using System.Text.Json;
-using System.Text.Json.Serialization;
+using ReactiveUI;
+
namespace PcgsInvUi.Services;
-public class PcgsClient {
+public class PcgsClient : ReactiveObject {
+ public int NumApiRequests {
+ get => _numApiRequests;
+ set => this.RaiseAndSetIfChanged(ref _numApiRequests, value);
+ }
+
private string _apiUri = "https://api.pcgs.com/publicapi";
private readonly string _apiKey;
+ private int _numApiRequests;
public PcgsClient(string apiKey) {
// _apiKey = apiKey;
diff --git a/PcgsInvUi/ViewModels/MainWindowViewModel.cs b/PcgsInvUi/ViewModels/MainWindowViewModel.cs
index ad046d2..4bc71af 100644
--- a/PcgsInvUi/ViewModels/MainWindowViewModel.cs
+++ b/PcgsInvUi/ViewModels/MainWindowViewModel.cs
@@ -10,7 +10,7 @@ using ReactiveUI;
namespace PcgsInvUi.ViewModels;
-// TODO Error handling.
+// TODO Error handling for API calls.
// TODO Manual handling of API key.
// TODO Use CoinFacts link.
// TODO Splash screen during startup.
@@ -26,7 +26,6 @@ public class MainWindowViewModel : ViewModelBase {
get => _sideContent;
private set => this.RaiseAndSetIfChanged(ref _sideContent, value);
}
- public ObservableCollection CoinCollection { get; set; }
public ObservableCollection DisplayedList { get; set; }
public CoinDatabase ConnectedCoinDatabase { get; set; }
public Coin? SelectedCoin {
@@ -53,12 +52,9 @@ public class MainWindowViewModel : ViewModelBase {
var newViewModel = new NewViewModel();
_sideContent = newViewModel;
// DisplayedList = CoinCollection = new ObservableCollection(coins.GetItems());
- DisplayedList = CoinCollection =
- new ObservableCollection(coins.GetCollection("CollectionTable"));
+ DisplayedList = ConnectedCoinDatabase.Collection;
TotalValue = DisplayedList.Sum(x => x.TotalPrice);
- _pcgsClient = new PcgsClient(
- "eAb8gS0I2XAvT_5gJeiGJaglMia1Tk-oB4kJUK6kuafyrny_S61vIJY-Ikl4nCQM67wrdxzUqLVWTV2kBSxD3d5XNBHxHnYBhcSS6dOPug0hZaF3qAv56df3gYSzOGh9Tif5y0eP3Iw0LrqKDr1Hj-dk6SV6GKog2IIqCPQhhHH8FMTWBTYO-_O8cx7qLdM5GM8KlTsic6g3VRUhM8EA_4OO04dCfmNLGhqINRl3jGZ0Q4ziI8fng2bVWsIyteqiPzUn10rIQ3-OPpqVZG_DxeOmOejj4GzbUNyqUOajy-nr5rYY");
-
+
// Set up the ability to open the delete window.
ShowDeleteWindow = new Interaction();
var deleteEnabled = this.WhenAnyValue(x => x.SelectedCoin)
@@ -68,7 +64,7 @@ public class MainWindowViewModel : ViewModelBase {
var result = await ShowDeleteWindow.Handle(deleteViewModel);
if (result && SelectedCoin is not null) {
ConnectedCoinDatabase.DeleteCoin(SelectedCoin);
- CoinCollection.Remove(SelectedCoin); // possible null deref - ignored due to button being disabled.
+ ConnectedCoinDatabase.Collection.Remove(SelectedCoin); // possible null deref - ignored due to button being disabled.
}
}, deleteEnabled);
@@ -93,16 +89,12 @@ public class MainWindowViewModel : ViewModelBase {
// This is the same as the above, but with a different syntax.
newViewModel.OkCommand
.Subscribe(async requestStructure => {
- var newCoin = await _pcgsClient.GetCoinFactsByGrade(requestStructure.Item1, requestStructure.Item2);
- newCoin.Quantity = requestStructure.Item3;
- CoinCollection.Add(newCoin);
- coins.InsertCoin(newCoin);
- TotalValue += newCoin.Quantity * newCoin.PriceGuideValue;
+ ConnectedCoinDatabase.CreateCoin(requestStructure.Item1, requestStructure.Item2, requestStructure.Item3);
});
// Change TotalValue anytime SelectedCoin.TotalPrice changes.
// Possible null deref - ignored due to null values summing to 0.
this.WhenAnyValue(x => x.SelectedCoin.TotalPrice)
- .Subscribe(_ => TotalValue = CoinCollection.Sum(x => x.TotalPrice));
+ .Subscribe(_ => TotalValue = ConnectedCoinDatabase.Collection.Sum(x => x.TotalPrice));
}
}
\ No newline at end of file
diff --git a/PcgsInvUi/Views/MainWindow.axaml b/PcgsInvUi/Views/MainWindow.axaml
index fafc7dd..a351c12 100644
--- a/PcgsInvUi/Views/MainWindow.axaml
+++ b/PcgsInvUi/Views/MainWindow.axaml
@@ -14,7 +14,9 @@
-
+