Compare commits
22 Commits
0e3ee54e1c
...
d2bc986506
Author | SHA1 | Date |
---|---|---|
The Magician | d2bc986506 | |
The Magician | 78e1c865c7 | |
The Magician | e6466cc01a | |
The Magician | 2fdbb0d467 | |
The Magician | 9d6d39d75f | |
The Magician | 2ab69dce8e | |
The Magician | 7fd6ed07f3 | |
The Magician | 93184d4c52 | |
The Magician | 1b579a28da | |
The Magician | b30525c2d4 | |
The Magician | 0dd67f3bb8 | |
The Magician | 2ef339bbce | |
The Magician | 6a4ff5dffd | |
The Magician | ec5328fed1 | |
The Magician | b90a570d29 | |
The Magician | 0e1647bb2d | |
The Magician | a028126fba | |
The Magician | 5e271c4132 | |
The Magician | 03e8dc8c03 | |
The Magician | c1f581f312 | |
The Magician | f7e0a713f6 | |
The Magician | c0c290916a |
|
@ -1,5 +0,0 @@
|
|||
cmake_install.cmake
|
||||
CMakeCache.txt
|
||||
CMakeFiles/*
|
||||
Makefile
|
||||
AScannerDarkly
|
|
@ -1,7 +0,0 @@
|
|||
cmake_minimum_required(VERSION 3.2)
|
||||
project( AScannerDarkly )
|
||||
find_package( OpenCV REQUIRED )
|
||||
include_directories( ${OpenCV_INCLUDE_DIRS} )
|
||||
add_executable( AScannerDarkly main.cpp )
|
||||
target_link_libraries( AScannerDarkly ${OpenCV_LIBS} )
|
||||
target_link_libraries( AScannerDarkly -llept -ltesseract )
|
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 124 KiB |
Before Width: | Height: | Size: 126 KiB |
Before Width: | Height: | Size: 115 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 5.0 KiB |
|
@ -1,161 +0,0 @@
|
|||
#include <opencv2/opencv.hpp>
|
||||
#include <tesseract/baseapi.h>
|
||||
#include <leptonica/allheaders.h>
|
||||
#include <string>
|
||||
|
||||
const std::string SCANNER_WINDOW_NAME = "A Scanner Darkly";
|
||||
const std::string CARD_WINDOW_NAME = "Detected Card";
|
||||
|
||||
const std::string CANNY_LOWER_THRESHOLD_TRACKBAR_NAME = "Canny: Lower Threshold";
|
||||
|
||||
const float CARD_ASPECT_RATIO = 88.0f / 63.0f;
|
||||
const std::string ASPECT_RATIO_TOLERANCE_TRACKBAR_NAME = "Aspect Ratio Tolerance";
|
||||
int g_aspect_ratio_tolerance = 1;
|
||||
const int MAX_ASPECT_RATIO_TOLERANCE = 100;
|
||||
|
||||
int g_Canny_lower_threshold = 195;
|
||||
const int CANNY_UPPER_THRESHOLD = 255;
|
||||
|
||||
cv::Mat cropImageToRoi(cv::Mat img, cv::Rect roi) {
|
||||
return img(roi);
|
||||
}
|
||||
|
||||
cv::Mat detectCardInFrame(cv::Mat frame) {
|
||||
cv::Mat grayscaleFrame, blurFrame, cannyFrame, cardFrame;
|
||||
|
||||
// Perform edge detection
|
||||
cv::cvtColor(frame, grayscaleFrame, cv::COLOR_BGR2GRAY);
|
||||
cv::GaussianBlur(grayscaleFrame, blurFrame, cv::Size(5, 5), 2, 2);
|
||||
cv::Canny(blurFrame, cannyFrame, g_Canny_lower_threshold, CANNY_UPPER_THRESHOLD);
|
||||
|
||||
// Perform contour detection
|
||||
std::vector<std::vector<cv::Point>> contours;
|
||||
std::vector<cv::Vec4i> hierarchy;
|
||||
cv::findContours(cannyFrame, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
|
||||
|
||||
// Detect convex hulls
|
||||
std::vector<std::vector<cv::Point>> hull(contours.size());
|
||||
for(size_t i = 0; i < contours.size(); i++) {
|
||||
cv::convexHull(contours[i], hull[i]);
|
||||
}
|
||||
|
||||
// Detect RotatedRects
|
||||
std::vector<cv::RotatedRect> minRect(contours.size());
|
||||
for(size_t i = 0; i < contours.size(); i++) {
|
||||
minRect[i] = cv::minAreaRect(contours[i]);
|
||||
}
|
||||
|
||||
// Draw lines around RotatedRects
|
||||
/*
|
||||
cv::Scalar rectangleColor(255, 0, 0, 0);
|
||||
cv::Point2f rect_points[4];
|
||||
minRect[i].points(rect_points);
|
||||
for (int j = 0; j < 4; j++) {
|
||||
cv::line(frame, rect_points[j], rect_points[(j + 1) % 4], rectangleColor);
|
||||
}
|
||||
*/
|
||||
|
||||
// Find possible cards
|
||||
std::vector<cv::RotatedRect> possibleCards(contours.size());
|
||||
int possibleCount = 0;
|
||||
for (size_t i = 0; i < minRect.size(); i++) {
|
||||
float aspectRatio = minRect[i].size.height / minRect[i].size.width;
|
||||
float aspectRatioTolerance = g_aspect_ratio_tolerance / 100.0f;
|
||||
if (aspectRatio < (CARD_ASPECT_RATIO - aspectRatioTolerance) || aspectRatio > (CARD_ASPECT_RATIO + aspectRatioTolerance)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cv::putText(frame, std::to_string(minRect[i].angle), minRect[i].center, cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(255, 0, 0, 0));
|
||||
|
||||
possibleCards[possibleCount] = minRect[i];
|
||||
possibleCount++;
|
||||
}
|
||||
|
||||
if (possibleCount == 1) {
|
||||
return cropImageToRoi(frame, possibleCards[0].boundingRect());
|
||||
} else if (possibleCount > 1) {
|
||||
// If we have more than one possible match, take the largest one
|
||||
int largestCardIndex = 0;
|
||||
int largestCardSize = 0;
|
||||
|
||||
for (size_t i = 0; i < possibleCards.size(); i++) {
|
||||
float size = possibleCards[i].size.width * possibleCards[i].size.height;
|
||||
if (size >= largestCardSize) {
|
||||
largestCardIndex = i;
|
||||
largestCardSize = size;
|
||||
}
|
||||
}
|
||||
|
||||
// Some basic error checking to ensure the RotatedRect is roughly card-sized
|
||||
if (largestCardSize >= 14400 && largestCardSize <= 200000) {
|
||||
return cropImageToRoi(frame, possibleCards[largestCardIndex].boundingRect());
|
||||
}
|
||||
}
|
||||
|
||||
return cv::Mat();
|
||||
}
|
||||
|
||||
const char* readTextFromImage(cv::InputArray img) {
|
||||
const char* tempFilename = "temp.bmp";
|
||||
|
||||
// Initialize Tesseract api
|
||||
tesseract::TessBaseAPI* tess = new tesseract::TessBaseAPI();
|
||||
tess->Init(NULL, "eng");
|
||||
tess->SetPageSegMode(tesseract::PSM_SPARSE_TEXT);
|
||||
|
||||
// Load image into Tesseract
|
||||
cv::imwrite(tempFilename, img);
|
||||
Pix* pixd = pixRead(tempFilename);
|
||||
tess->SetImage(pixd);
|
||||
tess->Recognize(0);
|
||||
|
||||
// Perform OCR
|
||||
const char* out = tess->GetUTF8Text();
|
||||
|
||||
// Cleanup
|
||||
pixDestroy(&pixd);
|
||||
std::remove(tempFilename);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv ) {
|
||||
cv::VideoCapture cap;
|
||||
cap.open(0);
|
||||
|
||||
if (!cap.isOpened()) {
|
||||
std::cerr << "Couldn't open capture" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
cv::namedWindow(SCANNER_WINDOW_NAME);
|
||||
|
||||
cv::createTrackbar(CANNY_LOWER_THRESHOLD_TRACKBAR_NAME, SCANNER_WINDOW_NAME, &g_Canny_lower_threshold, CANNY_UPPER_THRESHOLD, NULL);
|
||||
cv::createTrackbar(ASPECT_RATIO_TOLERANCE_TRACKBAR_NAME, SCANNER_WINDOW_NAME, &g_aspect_ratio_tolerance, MAX_ASPECT_RATIO_TOLERANCE, NULL);
|
||||
|
||||
cv::Mat frame, cardFrame;
|
||||
while (true) {
|
||||
cap >> frame;
|
||||
//cardFrame = detectCardInFrame(frame);
|
||||
cv::imshow(SCANNER_WINDOW_NAME, frame);
|
||||
|
||||
/*
|
||||
if (cardFrame.size().width > 0) {
|
||||
printf("Card detected\n");
|
||||
while (true) {
|
||||
cv::imshow(CARD_WINDOW_NAME, cardFrame);
|
||||
char c = (char)cv::waitKey(33);
|
||||
if (c == 27) {
|
||||
cv::destroyWindow(CARD_WINDOW_NAME);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
char c = (char)cv::waitKey(33);
|
||||
if (c == 27) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 301 KiB |
|
@ -1,24 +0,0 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
mouse_raw = (0, 0)
|
||||
|
||||
def clickEvent(event, x, y, flags, params):
|
||||
global mouse_raw
|
||||
|
||||
if event == cv2.EVENT_LBUTTONDOWN:
|
||||
mouse_raw = (x, y)
|
||||
print(mouse_raw)
|
||||
|
||||
cap = cv2.VideoCapture(0)
|
||||
cv2.namedWindow("image")
|
||||
cv2.setMouseCallback("image", clickEvent)
|
||||
|
||||
while True:
|
||||
success, img = cap.read()
|
||||
cv2.imshow("image", img)
|
||||
cv2.waitKey(0)
|
||||
|
||||
cv2.destroyAllWindows()
|
|
@ -0,0 +1,59 @@
|
|||
#!/bin/bash
|
||||
|
||||
check_error() {
|
||||
if test $? -ne 0; then
|
||||
echo "card_scanner: $1 exited with unexpected error"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
STORAGE_DIR="$HOME/.local/share/sevenkeys/scanimages/"
|
||||
mkdir -p "$STORAGE_DIR"
|
||||
|
||||
printf "Database profile: "
|
||||
read profile
|
||||
|
||||
printf "Storage area name: "
|
||||
read storageAreaName
|
||||
|
||||
ADD_CARDS=0
|
||||
echo "scantap: Beginning scan loop"
|
||||
while true; do
|
||||
rng="$(cat /dev/random | tr -cd 'a-f0-9' | head -c 32)"
|
||||
filename="$STORAGE_DIR/$rng.png"
|
||||
|
||||
scanimage --output-file="$filename" --source "ADF Front" --mode Color --page-width 63mm --page-height 88mm 2>/dev/null
|
||||
|
||||
# scanimage exits with code 7 if no documents are available in scanner
|
||||
if test $? -eq 7; then
|
||||
if test $ADD_CARDS -eq 0; then
|
||||
ADD_CARDS=1
|
||||
echo "scantap: No more cards in feeder" >&2
|
||||
fi
|
||||
|
||||
# If we have generated a zero-length file, then delete it
|
||||
if ! test -s "$filename"; then
|
||||
rm "$filename"
|
||||
fi
|
||||
|
||||
continue
|
||||
fi
|
||||
ADD_CARDS=0
|
||||
check_error "scanimage"
|
||||
|
||||
convert -rotate 180 "$filename" "$filename"
|
||||
check_error "convert"
|
||||
|
||||
cardLocationId="$(./sevenkeys --profile="$profile" store --storagearea="$storageAreaName" --id="00000000-0000-0000-0000-0000000000000")"
|
||||
check_error "sevenkeys"
|
||||
|
||||
if test "$profile" == "development"; then
|
||||
databaseName="sevenkeys_development"
|
||||
else
|
||||
databaseName="sevenkeys"
|
||||
fi
|
||||
|
||||
mysql --silent --silent --user=root --password="$(pass show sevenkeys/mysql)" \
|
||||
-e "USE $databaseName; INSERT INTO CardScan (CardLocationId, Filename) VALUES ('$cardLocationId', '$rng.png');"
|
||||
check_error "mysql"
|
||||
done
|
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
crop() {
|
||||
convert "$1" -crop 1200x120+90+100 "$2"
|
||||
}
|
||||
|
||||
crop old_border.png name_old.png
|
||||
crop new_border.png name_new.png
|
|
@ -0,0 +1,49 @@
|
|||
#!/bin/bash
|
||||
|
||||
CLASSNAME="svnProfiler"
|
||||
TITLE_FILENAME="/tmp/title.png"
|
||||
touch "$TITLE_FILENAME"
|
||||
|
||||
get_window_id() {
|
||||
while true; do
|
||||
id="$(xdotool search --classname "$CLASSNAME")"
|
||||
if test "$?" -ne 0; then
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "$id"
|
||||
break
|
||||
done
|
||||
}
|
||||
|
||||
printf "Database profile: "
|
||||
read profile
|
||||
if test "$profile" == "development"; then
|
||||
databaseName="sevenkeys_development"
|
||||
else
|
||||
databaseName="sevenkeys"
|
||||
fi
|
||||
|
||||
STORAGE_DIR="$HOME/.local/share/sevenkeys/scanimages/"
|
||||
cd "$STORAGE_DIR"
|
||||
|
||||
files="$(find *.png)"
|
||||
|
||||
nsxiv -N "$CLASSNAME" $files &
|
||||
nsxivWindowId="$(get_window_id)"
|
||||
nsxiv "$TITLE_FILENAME" &
|
||||
|
||||
for file in $files; do
|
||||
cardLocationId="$(mysql --silent --silent --user=root --password="$(pass show sevenkeys/mysql)" -e "USE $databaseName; SELECT CardLocationId FROM CardScan WHERE Filename = '$file';")"
|
||||
|
||||
# TODO: Detect features of the card automatically
|
||||
#convert "$file" -crop 1200x120+90+100 "$TITLE_FILENAME"
|
||||
#title="$(tesseract --psm 9 "$TITLE_FILENAME" stdout)"
|
||||
#echo "$title"
|
||||
|
||||
cardPrintingId="$(sevenkeys --profile="$profile" search-printings)"
|
||||
|
||||
sevenkeys --profile="$profile" replace --card-location-id="$cardLocationId" --card-printing-id="$cardPrintingId"
|
||||
|
||||
xdotool key --window "$nsxivWindowId" 'n'
|
||||
done
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/bash
|
||||
for i in $(seq 1 13); do
|
||||
echo "PSM $i:" >>output
|
||||
echo "Old card:" >>output
|
||||
tesseract name_old.png stdout --psm "$i" >>output
|
||||
echo "New card:" >>output
|
||||
tesseract name_new.png stdout --psm "$i" >>output
|
||||
echo >>output
|
||||
done
|
|
@ -0,0 +1,34 @@
|
|||
#!/bin/bash
|
||||
|
||||
if test -z "$1"; then
|
||||
echo "usage: test_card_scanner_count <count>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
STORAGE_DIR="$HOME/.local/share/sevenkeys/testimages/"
|
||||
mkdir -p "$STORAGE_DIR"
|
||||
|
||||
starting_file_count="$(find $STORAGE_DIR/*.png | wc -l)"
|
||||
|
||||
echo "test_card_scanner_count: Beginning test"
|
||||
while true; do
|
||||
rng="$(cat /dev/random | tr -cd 'a-f0-9' | head -c 32)"
|
||||
filename="$STORAGE_DIR/$rng.png"
|
||||
|
||||
scanimage --output-file="$filename" --source "ADF Front" --mode Color --page-width 63mm --page-height 88mm 2>/dev/null
|
||||
|
||||
# scanimage exits with code 7 if no documents are available in scanner
|
||||
if test $? -eq 7; then
|
||||
echo "test_card_scanner_count: No more cards in feeder" >&2
|
||||
rm "$filename"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
ending_file_count="$(find $STORAGE_DIR/*.png | wc -l)"
|
||||
|
||||
if test $(( ending_file_count - starting_file_count )) -ne "$1"; then
|
||||
echo "FAILED: Start: $starting_file_count, End: $ending_file_count"
|
||||
else
|
||||
echo "SUCCESS"
|
||||
fi
|
|
@ -0,0 +1,12 @@
|
|||
#!/bin/bash
|
||||
|
||||
STORAGE_DIR="$HOME/.local/share/sevenkeys/testimages/"
|
||||
mkdir -p "$STORAGE_DIR"
|
||||
|
||||
echo "test_card_scanner_count: Beginning test"
|
||||
while true; do
|
||||
rng="$(cat /dev/random | tr -cd 'a-f0-9' | head -c 32)"
|
||||
filename="$STORAGE_DIR/$rng.png"
|
||||
|
||||
scanimage --output-file="$filename" --source "ADF Front" --mode Color --page-width 63mm --page-height 88mm 2>/dev/null
|
||||
done
|
|
@ -1,8 +0,0 @@
|
|||
USE sevenkeys;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS CardScan (
|
||||
Id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
StorageAreaId INT NOT NULL,
|
||||
FOREIGN KEY (StorageAreaId) REFERENCES StorageArea(Id),
|
||||
Position INT NOT NULL
|
||||
);
|
|
@ -1,59 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
mysql --user=root --password="$(pass show sevenkeys/mysql)" <createtable.sql
|
||||
|
||||
printf "scantap: Enter name of storage box: "
|
||||
read storageAreaName
|
||||
|
||||
storageAreaId="$(mysql --silent --silent --user=root --password="$(pass show sevenkeys/mysql)" -e "USE sevenkeys; SELECT Id FROM StorageArea WHERE Name = '$storageAreaName';")"
|
||||
if test -z "$storageAreaId"; then
|
||||
echo "scantap: No storage area named \"$storageAreaName\"" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
startPosition="$(mysql --silent --silent --user=root --password="$(pass show sevenkeys/mysql)" -e "USE sevenkeys; SELECT Position FROM CardScan WHERE StorageAreaId = '$storageAreaId' ORDER BY Position DESC LIMIT 1;")"
|
||||
if test "$?" -ne "0"; then
|
||||
echo "scantap: Failed to establish starting position" >&2
|
||||
exit 1
|
||||
fi
|
||||
position=$((startPosition + 1))
|
||||
|
||||
printf "scantap: Enter mode (foil/nonfoil): "
|
||||
read foilMode
|
||||
outputDir="images/$foilMode"
|
||||
mkdir -p "$outputDir"
|
||||
|
||||
ADD_CARDS=0
|
||||
echo "scantap: Beginning scan loop"
|
||||
while true; do
|
||||
scanimage --format=png --batch=tmp%d.png --batch-count=2 --source "ADF Duplex" --mode Color --page-width 63mm --page-height 88mm 2>/dev/null
|
||||
|
||||
# scanimage exits with code 7 if no documents are available in scanner
|
||||
if test $? -eq 7; then
|
||||
if test $ADD_CARDS -eq 0; then
|
||||
ADD_CARDS=1
|
||||
echo "scantap: No more cards in feeder" >&2
|
||||
fi
|
||||
|
||||
continue
|
||||
fi
|
||||
ADD_CARDS=0
|
||||
|
||||
if ! test -e tmp1.png || ! test -e tmp2.png; then
|
||||
echo "scantap: Failed to create temporary image files" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
key="$(mysql --silent --silent --user=root --password="$(pass show sevenkeys/mysql)" -e "USE sevenkeys; INSERT INTO CardScan (StorageAreaId, Position) VALUES ('$storageAreaId', '$position'); SELECT Id FROM CardScan ORDER BY Id DESC LIMIT 1;")"
|
||||
if test "$?" -ne "0"; then
|
||||
echo "scantap: Failed to get key from database" >&2
|
||||
exit 1
|
||||
fi
|
||||
position=$((position + 1))
|
||||
|
||||
convert -rotate 180 tmp1.png tmp1.png
|
||||
convert -rotate 180 tmp2.png tmp2.png
|
||||
|
||||
mv tmp1.png "$outputDir/${key}_back.png"
|
||||
mv tmp2.png "$outputDir/${key}_front.png"
|
||||
done
|
|
@ -1,9 +1,17 @@
|
|||
createdb:
|
||||
mysql --user=root --password=$(shell pass show sevenkeys/mysql) <database/sql/createdb.sql
|
||||
removedb:
|
||||
rm -rf cache/
|
||||
mysql --user=root --password=$(shell pass show sevenkeys/mysql) <database/sql/removedb.sql
|
||||
connect:
|
||||
mysql --user=root --password=$(shell pass show sevenkeys/mysql)
|
||||
|
||||
dump:
|
||||
mysqldump --user=root --password=$(shell pass show sevenkeys/mysql) sevenkeys >sevenkeys.sql
|
||||
|
||||
dev_create:
|
||||
goose mysql "root:$(shell pass show sevenkeys/mysql)@/sevenkeys_development?parseTime=true" up
|
||||
dev_rollback:
|
||||
rm -rf cache/
|
||||
goose mysql "root:$(shell pass show sevenkeys/mysql)@/sevenkeys_development?parseTime=true" reset
|
||||
|
||||
prod_create:
|
||||
goose mysql "root:$(shell pass show sevenkeys/mysql)@/sevenkeys?parseTime=true" up
|
||||
prod_rollback:
|
||||
rm -rf cache/
|
||||
goose mysql "root:$(shell pass show sevenkeys/mysql)@/sevenkeys?parseTime=true" reset
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
sevenkeys --profile=production add --card-printing-id="$(sevenkeys --profile=production search-printings)" --storagearea="$1"
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Note: Make sure to export BEARER before using
|
||||
|
||||
URL="https://api.cardtrader.com/api/v2"
|
||||
|
||||
curl --silent "$URL/$1" -H "Authorization: Bearer $BEARER" | jq .
|
|
@ -1,149 +0,0 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"sevenkeys/database"
|
||||
"sevenkeys/delverlens"
|
||||
"sevenkeys/logic"
|
||||
)
|
||||
|
||||
var output string
|
||||
|
||||
func MainCliLoop(db *sql.DB) {
|
||||
var command string
|
||||
|
||||
//var selectedStorageAreaName string
|
||||
var cardLocation database.CardLocation
|
||||
|
||||
var insertSearchCriteria logic.SearchCriteria = logic.SearchCriteria{
|
||||
SetCode: "",
|
||||
Foil: logic.Either,
|
||||
Promo: logic.Either,
|
||||
Language: "en",
|
||||
}
|
||||
var insertSearchOptions logic.InsertSearchOptions
|
||||
|
||||
// TODO: Add the ability to modify this
|
||||
var locateSearchCriteria logic.SearchCriteria = logic.SearchCriteria{
|
||||
SetCode: "",
|
||||
Foil: logic.True,
|
||||
Promo: logic.Either,
|
||||
Language: "en",
|
||||
}
|
||||
|
||||
for {
|
||||
ShowSplashScreen()
|
||||
// TODO: Fix this
|
||||
//showStorageInfo(os.Stdout, selectedStorageAreaName)
|
||||
showInsertSearchCriteria(insertSearchCriteria)
|
||||
showSelectedCard()
|
||||
showCopiesInserted()
|
||||
|
||||
command = GetStringResponse("SEVENKEYS $")
|
||||
|
||||
var err error
|
||||
|
||||
switch command {
|
||||
case "q", "quit":
|
||||
return
|
||||
case "n", "newstorage":
|
||||
var storageArea database.StorageArea
|
||||
storageArea.Name = GetStringResponse("Storage area name:")
|
||||
storageArea.StorageType = GetStringResponse("Storage area type (Binder/Box):")
|
||||
err = logic.CreateStorageArea(db, storageArea)
|
||||
logic.Check(err)
|
||||
break
|
||||
case "a", "area":
|
||||
options, err := logic.GetStorageAreaSearchOptions(db)
|
||||
logic.Check(err)
|
||||
|
||||
id, _, err := logic.GenericSearch(options)
|
||||
logic.Check(err)
|
||||
|
||||
//selectedStorageAreaName = name
|
||||
// TODO: Make db call to cache StorageArea once we have the ID
|
||||
cardLocation.StorageAreaId = id
|
||||
break
|
||||
case "c", "criteria":
|
||||
insertSearchCriteria = getSearchCriteria()
|
||||
break
|
||||
case "s", "search":
|
||||
if shouldRefreshSearch {
|
||||
insertSearchOptions = getSearchOptions(db, insertSearchCriteria)
|
||||
}
|
||||
|
||||
var previousCardPrintingId = cardLocation.CardPrintingId
|
||||
|
||||
pk, searchLine, err := logic.GenericSearch(insertSearchOptions)
|
||||
cardLocation.CardPrintingId = pk
|
||||
selectedCardPrintingSearchLine = searchLine
|
||||
|
||||
var exitError *exec.ExitError
|
||||
if errors.As(err, &exitError) {
|
||||
break
|
||||
}
|
||||
logic.Check(err)
|
||||
|
||||
output = ""
|
||||
if cardLocation.CardPrintingId != previousCardPrintingId {
|
||||
copiesInserted = 0
|
||||
}
|
||||
break
|
||||
case "i", "insert":
|
||||
err := logic.StoreCard(db, cardLocation)
|
||||
logic.Check(err)
|
||||
copiesInserted++
|
||||
break
|
||||
case "d", "delverlens":
|
||||
filename := GetStringResponse("Filename:")
|
||||
|
||||
cards, err := delverlens.ParseExportFile(filename)
|
||||
logic.Check(err)
|
||||
|
||||
err = logic.ImportDelverLensCards(db, cards, cardLocation.StorageAreaId)
|
||||
logic.Check(err)
|
||||
break
|
||||
case "l", "locate":
|
||||
filename := GetStringResponse("Filename:")
|
||||
cardNames, err := logic.GetCardNamesFromFile(filename)
|
||||
logic.Check(err)
|
||||
|
||||
locations, err := logic.LocateCards(db, cardNames, locateSearchCriteria)
|
||||
logic.Check(err)
|
||||
|
||||
if len(locations) == 0 {
|
||||
fmt.Println("No results found")
|
||||
fmt.Scanln()
|
||||
break
|
||||
}
|
||||
|
||||
for _, location := range locations {
|
||||
description := logic.GetLocationDescription(location)
|
||||
fmt.Println(description)
|
||||
|
||||
for true {
|
||||
todo := GetStringResponse("TODO:")
|
||||
|
||||
if todo == "r" {
|
||||
logic.RemoveFromStorage(db, location)
|
||||
break
|
||||
} else if todo == "n" {
|
||||
break
|
||||
} else {
|
||||
fmt.Printf("Unrecognized option: %s\n", todo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("Though this query has ended, its relics still slumber in New Argive.")
|
||||
fmt.Scanln()
|
||||
break
|
||||
default:
|
||||
fmt.Println("Unrecognized command:", command)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"sevenkeys/logic"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetStringResponse(prompt string) string {
|
||||
fmt.Print(prompt + " ")
|
||||
|
||||
var response string
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
scanner.Scan()
|
||||
response = scanner.Text()
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
func GetYesNoResponse(prompt string) bool {
|
||||
response := GetStringResponse(prompt)
|
||||
return strings.ToUpper(response) == "Y"
|
||||
}
|
||||
|
||||
func GetTriadicResponse(prompt string) logic.Triadic {
|
||||
response := GetStringResponse(prompt)
|
||||
|
||||
switch strings.ToUpper(response) {
|
||||
case "Y":
|
||||
return logic.True
|
||||
case "N":
|
||||
return logic.False
|
||||
default:
|
||||
return logic.Either
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"sevenkeys/logic"
|
||||
)
|
||||
|
||||
var shouldRefreshSearch bool = true
|
||||
|
||||
func getSearchCriteria() logic.SearchCriteria {
|
||||
var searchCriteria logic.SearchCriteria
|
||||
|
||||
searchCriteria.SetCode = GetStringResponse("Set code:")
|
||||
searchCriteria.Foil = GetTriadicResponse("Foil (y/n/E):")
|
||||
searchCriteria.Promo = GetTriadicResponse("Promo (y/n/E):")
|
||||
searchCriteria.Language = GetStringResponse("Language:")
|
||||
shouldRefreshSearch = true
|
||||
|
||||
return searchCriteria
|
||||
}
|
||||
|
||||
func getTriadicDisplay(triadic logic.Triadic) string {
|
||||
if triadic == logic.True {
|
||||
return "True"
|
||||
}
|
||||
|
||||
if triadic == logic.False {
|
||||
return "False"
|
||||
}
|
||||
|
||||
return "Either"
|
||||
}
|
||||
|
||||
func showInsertSearchCriteria(insertSearchCriteria logic.SearchCriteria) {
|
||||
fmt.Println("SEARCH CRITERIA")
|
||||
|
||||
setCodeDisplay := getInfoDisplay(insertSearchCriteria.SetCode)
|
||||
foilDisplay := getTriadicDisplay(insertSearchCriteria.Foil)
|
||||
promoDisplay := getTriadicDisplay(insertSearchCriteria.Promo)
|
||||
languageDisplay := getInfoDisplay(insertSearchCriteria.Language)
|
||||
|
||||
fmt.Println("Set code:", setCodeDisplay)
|
||||
fmt.Println("Foil:", foilDisplay)
|
||||
fmt.Println("Promo:", promoDisplay)
|
||||
fmt.Println("Language:", languageDisplay)
|
||||
|
||||
fmt.Print("\n")
|
||||
}
|
||||
|
||||
func showSelectedCard() {
|
||||
selectedCardDisplay := getInfoDisplay(selectedCardPrintingSearchLine)
|
||||
fmt.Println("Selected card:", selectedCardDisplay)
|
||||
}
|
||||
|
||||
func getSearchOptions(db *sql.DB, insertSearchCriteria logic.SearchCriteria) logic.InsertSearchOptions {
|
||||
options, err := logic.GetAllSearchOptions(db, insertSearchCriteria)
|
||||
logic.Check(err)
|
||||
shouldRefreshSearch = false
|
||||
return options
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"sevenkeys/figlet"
|
||||
|
||||
"github.com/inancgumus/screen"
|
||||
)
|
||||
|
||||
func ClearScreen() {
|
||||
screen.Clear()
|
||||
screen.MoveTopLeft()
|
||||
}
|
||||
|
||||
func ShowSplashScreen() {
|
||||
ClearScreen()
|
||||
|
||||
figlet.PrintMsgSlant("SEVENKEYS", "center")
|
||||
figlet.PrintMsgTerm("the ultimate Magic: the Gathering trading card storage system", "center")
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sevenkeys/database"
|
||||
)
|
||||
|
||||
var (
|
||||
selectedCardPrintingSearchLine string
|
||||
|
||||
copiesInserted int
|
||||
)
|
||||
|
||||
func getInfoDisplay(info string) string {
|
||||
if info == "" {
|
||||
return "[EMPTY]"
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
func showStorageInfo(w io.Writer, area database.StorageArea) {
|
||||
fmt.Fprint(w, "Selected Storage Area: ")
|
||||
if area.Name == "" { // TODO: Add a struct method to determine whether it is unset?
|
||||
fmt.Fprint(w, "[None]\n")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%s (%s)\n", area.Name, area.StorageType)
|
||||
return
|
||||
}
|
||||
|
||||
func showCopiesInserted() {
|
||||
fmt.Println("Copies inserted:", copiesInserted)
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sevenkeys/database"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_showStorageInfo_DisplaysNone_IfSelectedStorageAreaIsUnset(t *testing.T) {
|
||||
expected := "Selected Storage Area: [None]\n"
|
||||
|
||||
var output bytes.Buffer
|
||||
|
||||
var area database.StorageArea
|
||||
showStorageInfo(&output, area)
|
||||
|
||||
result := output.String()
|
||||
if result != expected {
|
||||
t.Errorf("expected %s, got %s", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_showStorageInfo_DisplaysStorageAreaNameAndType_IfSelectedStorageAreaIsSet(t *testing.T) {
|
||||
expected := "Selected Storage Area: Test A (Box)\n"
|
||||
|
||||
var output bytes.Buffer
|
||||
|
||||
area := database.StorageArea{
|
||||
Id: 1,
|
||||
Name: "Test A",
|
||||
Type: "Box",
|
||||
}
|
||||
showStorageInfo(&output, area)
|
||||
|
||||
result := output.String()
|
||||
if result != expected {
|
||||
t.Errorf("expected %s, got %s", expected, result)
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"sevenkeys/logic"
|
||||
"sevenkeys/logic/scryfall"
|
||||
)
|
||||
|
||||
func RunUpdateCheck(db *sql.DB) {
|
||||
fmt.Println("Checking for updates...")
|
||||
bulkData, err := scryfall.GetBulkDataByType(scryfall.BulkDataTypeAllCards)
|
||||
logic.Check(err)
|
||||
|
||||
needsUpdate, err := logic.CheckForUpdates(db, bulkData)
|
||||
logic.Check(err)
|
||||
|
||||
if !needsUpdate {
|
||||
fmt.Println("No update required.")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Update required.")
|
||||
if GetYesNoResponse("Run update? (y/N)") {
|
||||
fmt.Println("Running update...")
|
||||
|
||||
logic.CreateCacheDirectories()
|
||||
|
||||
err = logic.UpdateSets(db)
|
||||
logic.Check(err)
|
||||
|
||||
err = logic.UpdateCards(db, bulkData)
|
||||
logic.Check(err)
|
||||
|
||||
fmt.Println("Update finished.")
|
||||
}
|
||||
}
|
|
@ -26,22 +26,27 @@ func GetLastPositionInStorageArea(db *sql.DB, storageAreaId int) (int, error) {
|
|||
return lastPosition, nil
|
||||
}
|
||||
|
||||
func InsertCardLocation(db *sql.DB, storageLocation CardLocation) error {
|
||||
func InsertCardLocation(db *sql.DB, storageLocation CardLocation) (int64, error) {
|
||||
query := `INSERT INTO CardLocation
|
||||
(CardPrintingId, StorageAreaId, Position)
|
||||
VALUES (?, ?, ?);`
|
||||
|
||||
insert, err := db.Prepare(query)
|
||||
if err != nil {
|
||||
return err
|
||||
return -1, err
|
||||
}
|
||||
|
||||
_, err = insert.Exec(storageLocation.CardPrintingId, storageLocation.StorageAreaId, storageLocation.Position)
|
||||
result, err := insert.Exec(storageLocation.CardPrintingId, storageLocation.StorageAreaId, storageLocation.Position)
|
||||
if err != nil {
|
||||
return err
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return nil
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func InsertCardInExistingLocation(db *sql.DB, cardLocation CardLocation) error {
|
||||
|
|
|
@ -20,6 +20,33 @@ type LocateCardResult struct {
|
|||
Position int
|
||||
}
|
||||
|
||||
func GetLocateResultByCardLocationId(db *sql.DB, cardLocationId int) (LocateCardResult, error) {
|
||||
var result LocateCardResult
|
||||
|
||||
query := `SELECT CardLocation.Id,
|
||||
CardPrinting.Name,
|
||||
CardPrinting.SetCode,
|
||||
CardPrinting.IsFoil,
|
||||
CardPrinting.IsPromo,
|
||||
CardPrinting.CollectorNumber,
|
||||
CardPrinting.Language,
|
||||
StorageArea.Id,
|
||||
StorageArea.StorageType,
|
||||
StorageArea.Name,
|
||||
CardLocation.Position
|
||||
FROM CardLocation
|
||||
JOIN CardPrinting ON CardLocation.CardPrintingId = CardPrinting.Id
|
||||
JOIN StorageArea ON CardLocation.StorageAreaId = StorageArea.Id
|
||||
WHERE CardLocation.Id = ?;`
|
||||
|
||||
err := db.QueryRow(query, cardLocationId).Scan(&result.CardLocationId, &result.CardName, &result.SetCode, &result.IsFoil, &result.IsPromo, &result.CollectorNumber, &result.Language, &result.StorageAreaId, &result.StorageAreaType, &result.StorageAreaName, &result.Position)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func GetLocateResults(db *sql.DB, cardNames []string) ([]LocateCardResult, error) {
|
||||
var results []LocateCardResult
|
||||
|
||||
|
@ -61,3 +88,39 @@ func GetLocateResults(db *sql.DB, cardNames []string) ([]LocateCardResult, error
|
|||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func GetAllStoredCards(db *sql.DB) ([]LocateCardResult, error) {
|
||||
var results []LocateCardResult
|
||||
|
||||
query := `SELECT CardLocation.Id,
|
||||
CardPrinting.Name,
|
||||
CardPrinting.SetCode,
|
||||
CardPrinting.IsFoil,
|
||||
CardPrinting.IsPromo,
|
||||
CardPrinting.CollectorNumber,
|
||||
CardPrinting.Language,
|
||||
StorageArea.Id,
|
||||
StorageArea.StorageType,
|
||||
StorageArea.Name,
|
||||
CardLocation.Position
|
||||
FROM CardLocation
|
||||
JOIN CardPrinting ON CardLocation.CardPrintingId = CardPrinting.Id
|
||||
JOIN StorageArea ON CardLocation.StorageAreaId = StorageArea.Id`
|
||||
rows, err := db.Query(query)
|
||||
defer rows.Close()
|
||||
if err != nil {
|
||||
return results, err
|
||||
}
|
||||
|
||||
var result LocateCardResult
|
||||
for rows.Next() {
|
||||
err := rows.Scan(&result.CardLocationId, &result.CardName, &result.SetCode, &result.IsFoil, &result.IsPromo, &result.CollectorNumber, &result.Language, &result.StorageAreaId, &result.StorageAreaType, &result.StorageAreaName, &result.Position)
|
||||
if err != nil {
|
||||
return results, err
|
||||
}
|
||||
|
||||
results = append(results, result)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
|
|
@ -26,6 +26,28 @@ CREATE TABLE IF NOT EXISTS CardPrinting (
|
|||
Language VARCHAR(3) NOT NULL
|
||||
);
|
||||
|
||||
/*
|
||||
INSERT INTO CardPrinting (
|
||||
Id,
|
||||
Name,
|
||||
SetCode,
|
||||
IsFoil,
|
||||
IsPromo,
|
||||
CollectorNumber,
|
||||
ImageUrl,
|
||||
Language
|
||||
) VALUES (
|
||||
'00000000-0000-0000-0000-0000000000000',
|
||||
'Scanned Card Placeholder',
|
||||
'lea',
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
'',
|
||||
'en'
|
||||
);
|
||||
*/
|
||||
|
||||
CREATE TABLE IF NOT EXISTS StorageArea (
|
||||
Id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
Name VARCHAR(100) NOT NULL,
|
||||
|
@ -40,3 +62,11 @@ CREATE TABLE IF NOT EXISTS CardLocation (
|
|||
FOREIGN KEY (StorageAreaId) REFERENCES StorageArea(Id),
|
||||
Position INT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE CardLocation ADD CardtraderProductId INT NULL;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS CardScan (
|
||||
Id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
CardLocationId INT NOT NULL,
|
||||
Filename VARCHAR(100) NOT NULL
|
||||
);
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
filename="$(adb shell 'find /sdcard/Download/ | grep Temporary')"
|
||||
adb pull "$filename" /home/viciouscirce/dox/sevenkeys_imports/import.csv
|
||||
adb shell "rm $filename"
|
||||
./sevenkeys --profile=production import import.csv
|
||||
rm /home/viciouscirce/dox/sevenkeys_imports/import.csv
|
|
@ -11,24 +11,33 @@ require (
|
|||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/PuerkitoBio/goquery v1.8.1 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.2 // indirect
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/charmbracelet/lipgloss v0.9.1 // indirect
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
|
||||
github.com/inancgumus/screen v0.0.0-20190314163918-06e984b86ed3 // indirect
|
||||
github.com/jmoiron/sqlx v1.4.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/lukesampson/figlet v0.0.0-20190211215653-8a3ef4a6ac42 // indirect
|
||||
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mroth/weightedrand/v2 v2.1.0 // indirect
|
||||
github.com/mtgban/go-mtgban v0.0.0-20241115160804-6b7158d200d7 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/rivo/uniseg v0.4.6 // indirect
|
||||
golang.org/x/crypto v0.24.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
golang.org/x/term v0.21.0 // indirect
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
|
||||
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
|
||||
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
||||
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
|
@ -18,6 +23,12 @@ github.com/go-mysql/errors v0.0.0-20180603193453-03314bea68e0 h1:meiLwrW6ukHHehy
|
|||
github.com/go-mysql/errors v0.0.0-20180603193453-03314bea68e0/go.mod h1:ZH8V0509n2OSZLMYTMHzcy4hqUB+rG8ghK1zsP4i5gE=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
|
||||
github.com/inancgumus/screen v0.0.0-20190314163918-06e984b86ed3 h1:fO9A67/izFYFYky7l1pDP5Dr0BTCRkaQJUG6Jm5ehsk=
|
||||
github.com/inancgumus/screen v0.0.0-20190314163918-06e984b86ed3/go.mod h1:Ey4uAp+LvIl+s5jRbOHLcZpUDnkjLBROl15fZLwPlTM=
|
||||
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||
|
@ -29,12 +40,17 @@ github.com/lukesampson/figlet v0.0.0-20190211215653-8a3ef4a6ac42 h1:UtyD+eBVdLYS
|
|||
github.com/lukesampson/figlet v0.0.0-20190211215653-8a3ef4a6ac42/go.mod h1:/peI0OaxVYh7fzA72CD7rUsyGVdF7sCiFw7GcYqOcCw=
|
||||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
|
||||
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
|
||||
github.com/mtgban/go-mtgban v0.0.0-20241115160804-6b7158d200d7 h1:AKVe40N5i3GhO2sOq8Uipqr6/g5Ihln8AUcdReN8IVI=
|
||||
github.com/mtgban/go-mtgban v0.0.0-20241115160804-6b7158d200d7/go.mod h1:0YMKwBLKOogXJdaTXsfr9sqHr0O0XJgDyGujjcQHbo4=
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
|
@ -47,23 +63,64 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
|
|||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg=
|
||||
github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
package logic
|
||||
|
||||
import "sevenkeys/database"
|
||||
import (
|
||||
"sevenkeys/database"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func filterPrinting(printing database.CardPrinting, searchCriteria SearchCriteria) bool {
|
||||
if searchCriteria.SetCode != "" && printing.SetCode != searchCriteria.SetCode {
|
||||
if searchCriteria.SetCode != "" && !strings.Contains(printing.SetCode, searchCriteria.SetCode) {
|
||||
return true
|
||||
}
|
||||
|
||||
if searchCriteria.CollectorNumber != "" && !strings.Contains(printing.CollectorNumber, searchCriteria.CollectorNumber) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
@ -400,3 +400,27 @@ func TestFilterPrinting_ReturnsFalse_IfLanguageNotSet(t *testing.T) {
|
|||
t.Errorf("filter was true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterPrinting_ReturnsTrue_IfSetCodeMatchesButCollectorNumberDoesnt(t *testing.T) {
|
||||
printing := database.CardPrinting{
|
||||
SetCode: "rtr",
|
||||
CollectorNumber: "301",
|
||||
IsFoil: false,
|
||||
IsPromo: false,
|
||||
Language: "en",
|
||||
}
|
||||
|
||||
searchCriteria := SearchCriteria{
|
||||
SetCode: "rtr",
|
||||
CollectorNumber: "299",
|
||||
Foil: False,
|
||||
Promo: False,
|
||||
Language: "",
|
||||
}
|
||||
|
||||
filter := filterPrinting(printing, searchCriteria)
|
||||
|
||||
if filter != true {
|
||||
t.Errorf("filter was false")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,17 @@ func GetBinderLocationDescription(position int) string {
|
|||
return fmt.Sprintf(" on page %d in %s slot %d", page, frontOrBack, slot)
|
||||
}
|
||||
|
||||
func GetCardAtLocation(db *sql.DB, cardLocationId int) (database.LocateCardResult, error) {
|
||||
var result database.LocateCardResult
|
||||
|
||||
result, err := database.GetLocateResultByCardLocationId(db, cardLocationId)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func LocateCards(db *sql.DB, cardNames []string, criteria SearchCriteria) ([]database.LocateCardResult, error) {
|
||||
results, err := database.GetLocateResults(db, cardNames)
|
||||
if err != nil {
|
||||
|
|
|
@ -20,17 +20,16 @@ const (
|
|||
|
||||
type SearchCriteria struct {
|
||||
SetCode string
|
||||
CollectorNumber string
|
||||
Foil Triadic
|
||||
Promo Triadic
|
||||
Language string
|
||||
}
|
||||
|
||||
// The InsertSearchOptions type is a map of `string` keys (which can be searched using
|
||||
// fzf) to `string` values (which represent a primary key in the CardPrintings table)
|
||||
type InsertSearchOptions map[string]string
|
||||
type CardPrintingSearchOptions map[string]string
|
||||
|
||||
func GetAllSearchOptions(db *sql.DB, searchCriteria SearchCriteria) (InsertSearchOptions, error) {
|
||||
var searchOptions InsertSearchOptions = make(map[string]string)
|
||||
func GetAllCardPrintingSearchOptions(db *sql.DB, searchCriteria SearchCriteria) (CardPrintingSearchOptions, error) {
|
||||
var searchOptions CardPrintingSearchOptions = make(CardPrintingSearchOptions)
|
||||
|
||||
cardPrintings, err := database.GetAllCardPrintings(db)
|
||||
if err != nil {
|
||||
|
@ -61,6 +60,24 @@ func GetAllSearchOptions(db *sql.DB, searchCriteria SearchCriteria) (InsertSearc
|
|||
return searchOptions, err
|
||||
}
|
||||
|
||||
type StorageSearchOptions map[string]int
|
||||
|
||||
func GetAllStorageSearchOptions(db *sql.DB) (StorageSearchOptions, error) {
|
||||
var searchOptions StorageSearchOptions = make(StorageSearchOptions)
|
||||
|
||||
storedCards, err := database.GetAllStoredCards(db)
|
||||
if err != nil {
|
||||
return searchOptions, nil
|
||||
}
|
||||
|
||||
for _, storedCard := range storedCards {
|
||||
searchString := GetLocationDescription(storedCard)
|
||||
searchOptions[searchString] = storedCard.CardLocationId
|
||||
}
|
||||
|
||||
return searchOptions, nil
|
||||
}
|
||||
|
||||
func GenericSearch[pk string | int](options map[string]pk) (pk, string, error) {
|
||||
var value pk
|
||||
|
||||
|
|
|
@ -59,20 +59,20 @@ func GetStorageAreaSearchOptions(db *sql.DB) (StorageAreaSearchOptions, error) {
|
|||
return options, nil
|
||||
}
|
||||
|
||||
func storeAfterLastCard(db *sql.DB, cardLocation database.CardLocation) error {
|
||||
func storeAfterLastCard(db *sql.DB, cardLocation database.CardLocation) (int64, error) {
|
||||
lastPosition, err := database.GetLastPositionInStorageArea(db, cardLocation.StorageAreaId)
|
||||
if err != nil {
|
||||
return err
|
||||
return -1, err
|
||||
}
|
||||
|
||||
cardLocation.Position = lastPosition + 1
|
||||
|
||||
err = database.InsertCardLocation(db, cardLocation)
|
||||
id, err := database.InsertCardLocation(db, cardLocation)
|
||||
if err != nil {
|
||||
return err
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return nil
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func storeInEmptySlot(db *sql.DB, cardLocation database.CardLocation, cardLocationId int) error {
|
||||
|
@ -85,36 +85,47 @@ func storeInEmptySlot(db *sql.DB, cardLocation database.CardLocation, cardLocati
|
|||
return nil
|
||||
}
|
||||
|
||||
func StoreCard(db *sql.DB, cardLocation database.CardLocation) error {
|
||||
func StoreCard(db *sql.DB, cardLocation database.CardLocation) (int64, error) {
|
||||
storageAreaType, err := database.GetStorageAreaTypeById(db, cardLocation.StorageAreaId)
|
||||
if err != nil {
|
||||
return err
|
||||
return -1, err
|
||||
}
|
||||
|
||||
if storageAreaType == database.StorageAreaTypeBinder {
|
||||
nextEmptySlotId, err := database.GetNextEmptySlotInBinder(db, cardLocation.StorageAreaId)
|
||||
|
||||
if err == database.ErrNoEmptySlotsInBinder {
|
||||
err = storeAfterLastCard(db, cardLocation)
|
||||
id, err := storeAfterLastCard(db, cardLocation)
|
||||
if err != nil {
|
||||
return err
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return id, nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
return -1, err
|
||||
} else {
|
||||
err = storeInEmptySlot(db, cardLocation, nextEmptySlotId)
|
||||
if err != nil {
|
||||
return err
|
||||
return -1, err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return int64(nextEmptySlotId), nil
|
||||
}
|
||||
|
||||
err = storeAfterLastCard(db, cardLocation)
|
||||
id, err := storeAfterLastCard(db, cardLocation)
|
||||
if err != nil {
|
||||
return err
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return nil
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func Replace(db *sql.DB, cardLocationId int, cardPrintingId string) error {
|
||||
cardLocation := database.CardLocation{
|
||||
Id: cardLocationId,
|
||||
CardPrintingId: cardPrintingId,
|
||||
}
|
||||
|
||||
return database.InsertCardInExistingLocation(db, cardLocation)
|
||||
}
|
||||
|
|
|
@ -1,18 +1,52 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"sevenkeys/cli"
|
||||
"sevenkeys/database"
|
||||
"sevenkeys/delverlens"
|
||||
"sevenkeys/figlet"
|
||||
"sevenkeys/logic"
|
||||
"sevenkeys/logic/scryfall"
|
||||
"strings"
|
||||
|
||||
"github.com/mtgban/go-mtgban/cardtrader"
|
||||
)
|
||||
|
||||
func GetStringResponse(prompt string) string {
|
||||
fmt.Print(prompt + " ")
|
||||
|
||||
var response string
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
scanner.Scan()
|
||||
response = scanner.Text()
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
func GetYesNoResponse(prompt string) bool {
|
||||
response := GetStringResponse(prompt)
|
||||
return strings.ToUpper(response) == "Y"
|
||||
}
|
||||
|
||||
const (
|
||||
UpdateSubcommand string = "update"
|
||||
CreateStorageAreaSubcommand string = "createstorage"
|
||||
StoreSubcommand string = "store"
|
||||
ImportSubcommand string = "import"
|
||||
SearchPrintingsSubcommand string = "search-printings"
|
||||
SearchStorageSubcommand string = "search-storage"
|
||||
AddSubcommand string = "add"
|
||||
RemoveSubcommand string = "remove"
|
||||
ReplaceSubcommand string = "replace"
|
||||
DeckSubcommand string = "deck"
|
||||
|
||||
GetProductIdSubcommand string = "products"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -22,15 +56,16 @@ func main() {
|
|||
flag.Parse()
|
||||
|
||||
db := database.GetDatabaseFromConfig("config." + profile + ".json")
|
||||
/* Sad.
|
||||
figlet.ReadFigletFonts()
|
||||
cli.ShowSplashScreen()
|
||||
cli.RunUpdateCheck(db)
|
||||
*/
|
||||
|
||||
// TODO: Decide in what form we need to retain this functionality if any
|
||||
//cli.MainCliLoop(db)
|
||||
|
||||
importCmd := flag.NewFlagSet("import", flag.ExitOnError)
|
||||
storageArea := importCmd.String("storagearea", "", "The name of the StorageArea where cards should be imported.")
|
||||
//searchCmd := flag.NewFlagSet(SearchSubcommand, flag.ExitOnError)
|
||||
//name := searchCmd.String("name", "", "The card name to search for.")
|
||||
|
||||
if len(flag.Args()) == 0 {
|
||||
fmt.Fprintln(os.Stderr, "Please specify a subcommand.")
|
||||
|
@ -38,7 +73,77 @@ func main() {
|
|||
}
|
||||
|
||||
switch flag.Args()[0] {
|
||||
case UpdateSubcommand:
|
||||
fmt.Println("Checking for updates...")
|
||||
bulkData, err := scryfall.GetBulkDataByType(scryfall.BulkDataTypeAllCards)
|
||||
logic.Check(err)
|
||||
|
||||
needsUpdate, err := logic.CheckForUpdates(db, bulkData)
|
||||
logic.Check(err)
|
||||
|
||||
if !needsUpdate {
|
||||
fmt.Println("No update required.")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Update required.")
|
||||
|
||||
if GetYesNoResponse("Run update? (y/N)") {
|
||||
fmt.Println("Running update...")
|
||||
|
||||
logic.CreateCacheDirectories()
|
||||
|
||||
err = logic.UpdateSets(db)
|
||||
logic.Check(err)
|
||||
|
||||
err = logic.UpdateCards(db, bulkData)
|
||||
logic.Check(err)
|
||||
|
||||
fmt.Println("Update finished.")
|
||||
}
|
||||
break
|
||||
case CreateStorageAreaSubcommand:
|
||||
createStorageCmd := flag.NewFlagSet(CreateStorageAreaSubcommand, flag.ExitOnError)
|
||||
storageAreaName := createStorageCmd.String("name", "",
|
||||
"The name of the StorageArea to create.")
|
||||
storageAreaType := createStorageCmd.String("type", "",
|
||||
"The name of the StorageArea to create.")
|
||||
|
||||
createStorageCmd.Parse(flag.Args()[1:])
|
||||
|
||||
storageArea := database.StorageArea{Name: *storageAreaName, StorageType: *storageAreaType}
|
||||
err := logic.CreateStorageArea(db, storageArea)
|
||||
logic.Check(err)
|
||||
|
||||
break
|
||||
case StoreSubcommand:
|
||||
storeCmd := flag.NewFlagSet(StoreSubcommand, flag.ExitOnError)
|
||||
storageArea := storeCmd.String("storagearea", "",
|
||||
"The name of the StorageArea the card should be inserted to.")
|
||||
id := storeCmd.String("id", "", "The CardPrintingId of the card to store.")
|
||||
|
||||
storeCmd.Parse(flag.Args()[1:])
|
||||
|
||||
storageAreaId, err := logic.GetStorageAreaId(db, *storageArea)
|
||||
if err == logic.ErrCouldNotGetStorageAreaId {
|
||||
fmt.Fprintf(os.Stderr, "[sevenkeys] No storage area was selected, exiting.\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cardLocation := database.CardLocation{
|
||||
CardPrintingId: *id,
|
||||
StorageAreaId: storageAreaId,
|
||||
}
|
||||
cardLocationId, err := logic.StoreCard(db, cardLocation)
|
||||
logic.Check(err)
|
||||
|
||||
fmt.Printf("%d\n", cardLocationId)
|
||||
break
|
||||
case ImportSubcommand:
|
||||
importCmd := flag.NewFlagSet(ImportSubcommand, flag.ExitOnError)
|
||||
storageArea := importCmd.String("storagearea", "",
|
||||
"The name of the StorageArea where cards should be imported.")
|
||||
|
||||
importCmd.Parse(flag.Args()[1:])
|
||||
|
||||
storageAreaId, err := logic.GetStorageAreaId(db, *storageArea)
|
||||
|
@ -54,6 +159,146 @@ func main() {
|
|||
err = logic.ImportDelverLensCards(db, delverLensCards, storageAreaId)
|
||||
logic.Check(err)
|
||||
break
|
||||
case SearchPrintingsSubcommand:
|
||||
searchPrintingsCmd := flag.NewFlagSet(SearchPrintingsSubcommand, flag.ExitOnError)
|
||||
|
||||
setCode := searchPrintingsCmd.String("set-code", "", "The code for the set the card we're searching for belongs to.")
|
||||
collectorNumber := searchPrintingsCmd.String("collector-number", "", "The collector number of the card we're searching for.")
|
||||
foil := searchPrintingsCmd.String("foil", "E", "Whether the card we're searching for is foil.")
|
||||
|
||||
searchPrintingsCmd.Parse(flag.Args()[1:])
|
||||
|
||||
searchCriteria := logic.SearchCriteria{
|
||||
SetCode: *setCode,
|
||||
CollectorNumber: *collectorNumber,
|
||||
Promo: logic.Either,
|
||||
Language: "en",
|
||||
}
|
||||
|
||||
if *foil == "Y" {
|
||||
searchCriteria.Foil = logic.True
|
||||
} else if *foil == "N" {
|
||||
searchCriteria.Foil = logic.False
|
||||
} else {
|
||||
searchCriteria.Foil = logic.Either
|
||||
}
|
||||
|
||||
searchOptions, err := logic.GetAllCardPrintingSearchOptions(db, searchCriteria)
|
||||
logic.Check(err)
|
||||
id, _, err := logic.GenericSearch(searchOptions)
|
||||
logic.Check(err)
|
||||
fmt.Println(id)
|
||||
break
|
||||
case SearchStorageSubcommand:
|
||||
searchOptions, err := logic.GetAllStorageSearchOptions(db)
|
||||
logic.Check(err)
|
||||
id, _, err := logic.GenericSearch(searchOptions)
|
||||
logic.Check(err)
|
||||
fmt.Println(id)
|
||||
break
|
||||
case AddSubcommand:
|
||||
addCmd := flag.NewFlagSet(AddSubcommand, flag.ExitOnError)
|
||||
|
||||
cardPrintingId := addCmd.String("card-printing-id", "", "The ID of the card printing to add to storage.")
|
||||
storageArea := addCmd.String("storagearea", "",
|
||||
"The name of the StorageArea where cards should be imported.")
|
||||
|
||||
addCmd.Parse(flag.Args()[1:])
|
||||
|
||||
storageAreaId, err := logic.GetStorageAreaId(db, *storageArea)
|
||||
if err == logic.ErrCouldNotGetStorageAreaId {
|
||||
fmt.Fprintf(os.Stderr, "[sevenkeys] No storage area was selected, exiting.\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
logic.Check(err)
|
||||
|
||||
cardLocation := database.CardLocation{
|
||||
CardPrintingId: *cardPrintingId,
|
||||
StorageAreaId: storageAreaId,
|
||||
}
|
||||
logic.StoreCard(db, cardLocation)
|
||||
break
|
||||
case RemoveSubcommand:
|
||||
removeCmd := flag.NewFlagSet(RemoveSubcommand, flag.ExitOnError)
|
||||
|
||||
cardLocationId := removeCmd.Int("card-location-id", -1, "The card location to remove the card at.")
|
||||
|
||||
removeCmd.Parse(flag.Args()[1:])
|
||||
|
||||
if *cardLocationId == -1 {
|
||||
log.Fatal(errors.New("No CardLocationId given."))
|
||||
}
|
||||
|
||||
location, err := logic.GetCardAtLocation(db, *cardLocationId)
|
||||
logic.Check(err)
|
||||
|
||||
err = logic.RemoveFromStorage(db, location)
|
||||
logic.Check(err)
|
||||
|
||||
break
|
||||
case ReplaceSubcommand:
|
||||
replaceCmd := flag.NewFlagSet(ReplaceSubcommand, flag.ExitOnError)
|
||||
|
||||
cardLocationId := replaceCmd.Int("card-location-id", -1, "The card location to replace the card at.")
|
||||
cardPrintingId := replaceCmd.String("card-printing-id", "", "The card printing to put at the specified location.")
|
||||
|
||||
replaceCmd.Parse(flag.Args()[1:])
|
||||
|
||||
if *cardLocationId == -1 {
|
||||
log.Fatal(errors.New("No CardLocationId given."))
|
||||
}
|
||||
if *cardPrintingId == "" {
|
||||
log.Fatal(errors.New("No CardPrintingId given."))
|
||||
}
|
||||
|
||||
err := logic.Replace(db, *cardLocationId, *cardPrintingId)
|
||||
logic.Check(err)
|
||||
break
|
||||
case DeckSubcommand:
|
||||
deckCmd := flag.NewFlagSet(DeckSubcommand, flag.ExitOnError)
|
||||
deckCmd.Parse(flag.Args()[1:])
|
||||
|
||||
//filename := deckCmd.Args()[0]
|
||||
break
|
||||
case GetProductIdSubcommand:
|
||||
blbBlueprintsBytes, err := ioutil.ReadFile("blb_blueprints.json")
|
||||
logic.Check(err)
|
||||
|
||||
var blbBlueprints []cardtrader.Blueprint
|
||||
err = json.Unmarshal(blbBlueprintsBytes, &blbBlueprints)
|
||||
logic.Check(err)
|
||||
|
||||
productsBytes, err := ioutil.ReadFile("products.json")
|
||||
logic.Check(err)
|
||||
|
||||
var products []cardtrader.Product
|
||||
err = json.Unmarshal(productsBytes, &products)
|
||||
logic.Check(err)
|
||||
|
||||
for _, product := range products {
|
||||
var productBlueprint cardtrader.Blueprint
|
||||
for _, blueprint := range blbBlueprints {
|
||||
if blueprint.Id == product.BlueprintId {
|
||||
productBlueprint = blueprint
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("%s %s %d ",
|
||||
productBlueprint.Name,
|
||||
product.Properties.Number,
|
||||
product.Id,
|
||||
)
|
||||
|
||||
if product.Properties.MTGFoil {
|
||||
fmt.Printf("FOIL ")
|
||||
} else {
|
||||
fmt.Printf("NONFOIL ")
|
||||
}
|
||||
|
||||
fmt.Printf("x%d\n", product.Quantity)
|
||||
}
|
||||
break
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Unrecognized subcommand: %s\n", os.Args[1])
|
||||
break
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
goose -s -dir database/migrations/ create "$1" sql
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
sevenkeys --profile=production remove --card-location-id="$(sevenkeys --profile=production search-storage)"
|