Files
pcgs-inv/PcgsInvUi/Services/CoinDatabase.cs
T

352 lines
14 KiB
C#

using System;
using System.Collections.ObjectModel;
using System.Data.SQLite;
using System.Threading.Tasks;
using PcgsInvUi.Models;
namespace PcgsInvUi.Services;
public class CoinDatabase {
public SQLiteConnection Connection { get; set; }
private Boolean IsConnected { get; set; }
public ObservableCollection<Coin> Collection { get; set; }
public Boolean IsClientConnected { get; set; }
private PcgsClient? _pcgsClient;
/// <summary>
/// Creates and initilizes a new instance of the CoinDatabase.
/// Must call TryInitApiClient() after creating a new instance.
/// </summary>
public CoinDatabase() {
// Create a connection to SQLite
Connection = CreateConnection();
// Create a base table.
CreateCollection("CollectionTable");
CreateApiTable();
Console.WriteLine("Connected to database.");
Collection = new ObservableCollection<Coin>();
GetCollection("CollectionTable");
Console.WriteLine("Got collection.");
}
~CoinDatabase() {
Console.WriteLine("Destructor called, saving collection.");
// Save collection.
UpdateCollection("CollectionTable");
// Close the connection.
Connection.Close();
}
/// <summary>
/// Attempts to intitialize the API client by selecting the API key from the database.
/// </summary>
/// <returns>True if the API key was found, false otherwise.</returns>
public bool TryInitApiClient() {
SQLiteCommand cmd = Connection.CreateCommand();
cmd.CommandText = "SELECT API_KEY FROM ApiTable";
using (var reader = cmd.ExecuteReader()) {
if (reader.Read()) {
var apiKey = reader.GetString(0);
_pcgsClient = new PcgsClient(apiKey);
var insertCmd = Connection.CreateCommand();
insertCmd.CommandText = $"INSERT INTO ApiTable (API_KEY, REQUESTS_REMAINING) VALUE ({apiKey}, 1000)";
insertCmd.ExecuteNonQuery();
IsClientConnected = true;
}
else {
IsClientConnected = false;
}
}
return IsClientConnected;
}
private SQLiteConnection CreateConnection() {
// Check if database exists.
if (!System.IO.File.Exists("coin_collection")) {
// If not, create the database.
Console.Write("Creating database... ");
SQLiteConnection.CreateFile("coin_collection.sqlite");
Console.WriteLine("Done.");
}
// Create a connection to the database.
SQLiteConnection connection =
new SQLiteConnection("DataSource=coin_collection");
// Open the connection.
try {
connection.Open();
IsConnected = true;
}
catch (Exception e) {
Console.WriteLine(e.Message);
}
return connection;
}
public void CreateApiTable() {
// Create a table to store the API key and the number of requests remaining.
if (!IsConnected) {
// TODO Create a NotConnectedToDatabaseException
Console.WriteLine("Not connected to database.");
return;
}
// Create a SQL command.
SQLiteCommand cmd = Connection.CreateCommand();
cmd.CommandText =
"CREATE TABLE IF NOT EXISTS ApiTable (" +
"API_KEY TEXT, " +
"REQUESTS_REMAINING INTEGER, " +
"PRIMARY KEY(API_KEY))";
// Execute the command
cmd.ExecuteNonQuery();
Console.WriteLine("API table initialized.");
}
public void CreateCollection(string collectionName) {
// Create a table to store the collection.
if (!IsConnected) {
// TODO Create a NotConnectedToDatabaseException
Console.WriteLine("Not connected to database.");
return;
}
// Create a SQL command.
SQLiteCommand cmd = Connection.CreateCommand();
cmd.CommandText =
$"CREATE TABLE IF NOT EXISTS {collectionName} (" +
"PCGS_NUMBER TEXT," +
"GRADE TEXT" +
"NAME TEXT," +
"YEAR INTEGER," +
"DENOMINATION TEXT," +
"MINT_MARK TEXT," +
"PRICE_GUIDE_VALUE REAL," +
"COIN_FACTS_LINK TEXT," +
"MAJOR_VARIETY TEXT," +
"MINOR_VARIETY TEXT," +
"DIE_VARIETY TEXT," +
"SERIES_NAME TEXT," +
"CATEGORY TEXT," +
"DESIGNATION TEXT," +
"CERT_NUM INTEGER," +
"QUANTITY INTEGER," +
"NOTES TEXT," +
"PRIMARY KEY(PCGS_NUMBER, GRADE))";
// Execute the command.
cmd.ExecuteNonQuery();
Console.WriteLine("Collection table initialized.");
}
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
Console.WriteLine("Not connected to database.");
return;
}
// Create a SQL command.
SQLiteCommand cmd = Connection.CreateCommand();
cmd.CommandText =
"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, 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();
Console.WriteLine("Coin inserted.");
}
public async Task<PcgsClient.ErrorType> 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);
return PcgsClient.ErrorType.None;
}
// Otherwise, build a new coin with the API and insert it.
var apiResult = await _pcgsClient.GetCoinFactsByGrade(pcgsNumber, grade);
if (apiResult.Item1 == PcgsClient.ErrorType.None) {
var newCoin = apiResult.Item2;
newCoin.Quantity = quantity;
InsertCoin(newCoin);
Collection.Add(newCoin);
return PcgsClient.ErrorType.None;
}
Console.WriteLine("Error getting coin from API - {0}", apiResult.Item1);
return apiResult.Item1;
}
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}";
using (var reader = cmd.ExecuteReader()) {
while (reader.Read()) {
// Create coin from reader then add to list.
Collection.Add(CoinFromReader(reader));
}
}
}
public String CollectionToCSV(string collectionName) {
var cmd = Connection.CreateCommand();
cmd.CommandText = $"SELECT * FROM {collectionName}";
var headersCommand = Connection.CreateCommand();
headersCommand.CommandText = $"PRAGMA table_info({collectionName})";
var resultString = "";
var numHeaders = 0;
// Get the headers.
using (var reader = headersCommand.ExecuteReader()) {
while (reader.Read()) {
resultString += reader.GetString(1) + ",";
numHeaders++;
}
}
resultString += "\n";
// Is there a more efficient way of doing this? This is running at O(n^2) time.
using (var reader = cmd.ExecuteReader()) {
while (reader.Read()) {
// Read information from the reader, then add to CSV string.
for (int i = 0; i < numHeaders; i++) {
// Get the type of each column.
var type = reader.GetFieldType(i);
Console.WriteLine("Type: " + type);
if (type == typeof(Int64)) {
resultString += reader.GetInt32(i) + ",";
}
else if (type == typeof(Double)) {
resultString += reader.GetDouble(i) + ",";
}
else if (type == typeof(String)) {
resultString += reader.GetString(i) + ",";
}
else {
resultString += "NULL" + ",";
}
}
resultString += "\n";
}
}
return resultString;
}
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 = $"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.
cmd.ExecuteNonQuery();
}
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();
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";
cmd.Parameters.AddWithValue("@PcgsNumber", delCoin.PCGSNo);
cmd.Parameters.AddWithValue("@Grade", delCoin.Grade);
cmd.ExecuteNonQuery();
}
private int GetGradeFromString(string grade) {
var actualGrade = int.Parse(grade.Substring(grade.Length - 2));
Console.WriteLine($"Grade is {actualGrade}");
return actualGrade;
}
}