diff --git a/Doxyfile b/Doxyfile
index 623f7c9..5cedfec 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -1,4 +1,4 @@
-# Doxyfile 1.9.2
+# Doxyfile 1.9.3
# This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project.
@@ -38,7 +38,7 @@ PROJECT_NAME = "libscry"
# could be handy for archiving the generated documentation or if some version
# control system is used.
-PROJECT_NUMBER = 0.2
+PROJECT_NUMBER =
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
@@ -58,7 +58,7 @@ PROJECT_LOGO =
# entered, it will be relative to the location where doxygen was started. If
# left blank the current directory will be used.
-OUTPUT_DIRECTORY = docs
+OUTPUT_DIRECTORY = "docs"
# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
# directories (in 2 levels) under the output directory of each output format and
@@ -217,7 +217,7 @@ QT_AUTOBRIEF = NO
# not recognized any more.
# The default value is: NO.
-MULTILINE_CPP_IS_BRIEF = YES
+MULTILINE_CPP_IS_BRIEF = NO
# By default Python docstrings are displayed as preformatted text and doxygen's
# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the
@@ -857,7 +857,10 @@ WARN_FORMAT = "$file:$line: $text"
# The WARN_LOGFILE tag can be used to specify a file to which warning and error
# messages should be written. If left blank the output is written to standard
-# error (stderr).
+# error (stderr). In case the file specified cannot be opened for writing the
+# warning and error messages are written to standard error. When as file - is
+# specified the warning and error messages are written to standard output
+# (stdout).
WARN_LOGFILE =
@@ -982,7 +985,7 @@ EXCLUDE_PATTERNS =
# (namespaces, classes, functions, etc.) that should be excluded from the
# output. The symbol name can be a fully qualified name, a word, or if the
# wildcard * is used, a substring. Examples: ANamespace, AClass,
-# AClass::ANamespace, ANamespace::*Test
+# ANamespace::AClass, ANamespace::*Test
#
# Note that the wildcards are matched against the file with absolute path, so to
# exclude all test directories use the pattern */test/*
@@ -1359,6 +1362,13 @@ GENERATE_DOCSET = NO
DOCSET_FEEDNAME = "Doxygen generated docs"
+# This tag determines the URL of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDURL =
+
# This tag specifies a string that should uniquely identify the documentation
# set bundle. This should be a reverse domain-name style string, e.g.
# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
@@ -1563,7 +1573,7 @@ GENERATE_TREEVIEW = NO
# area (value NO) or if it should extend to the full height of the window (value
# YES). Setting this to YES gives a layout similar to
# https://docs.readthedocs.io with more room for contents, but less room for the
-# project logo, title, and description. If either GENERATOR_TREEVIEW or
+# project logo, title, and description. If either GENERATE_TREEVIEW or
# DISABLE_INDEX is set to NO, this option has no effect.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
@@ -1594,6 +1604,13 @@ TREEVIEW_WIDTH = 250
EXT_LINKS_IN_WINDOW = NO
+# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email
+# addresses.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+OBFUSCATE_EMAILS = YES
+
# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
@@ -2307,15 +2324,6 @@ EXTERNAL_PAGES = YES
# Configuration options related to the dot tool
#---------------------------------------------------------------------------
-# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
-# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
-# NO turns the diagrams off. Note that this option also works with HAVE_DOT
-# disabled, but it is recommended to install and use dot, since it yields more
-# powerful graphs.
-# The default value is: YES.
-
-CLASS_DIAGRAMS = YES
-
# You can include diagrams made with dia in doxygen documentation. Doxygen will
# then run dia to produce the diagram and insert it in the documentation. The
# DIA_PATH tag allows you to specify the directory where the dia binary resides.
@@ -2372,11 +2380,14 @@ DOT_FONTSIZE = 10
DOT_FONTPATH =
-# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
-# each documented class showing the direct and indirect inheritance relations.
-# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a
+# graph for each documented class showing the direct and indirect inheritance
+# relations. In case HAVE_DOT is set as well dot will be used to draw the graph,
+# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set
+# to TEXT the direct and indirect inheritance relations will be shown as texts /
+# links.
+# Possible values are: NO, YES, TEXT and GRAPH.
# The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
CLASS_GRAPH = YES
@@ -2505,6 +2516,13 @@ GRAPHICAL_HIERARCHY = YES
DIRECTORY_GRAPH = YES
+# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels
+# of child directories generated in directory dependency graphs by dot.
+# Minimum value: 1, maximum value: 25, default value: 1.
+# This tag requires that the tag DIRECTORY_GRAPH is set to YES.
+
+DIR_GRAPH_MAX_DEPTH = 1
+
# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
# generated by dot. For an explanation of the image formats see the section
# output formats in the documentation of the dot tool (Graphviz (see:
@@ -2558,10 +2576,10 @@ MSCFILE_DIRS =
DIAFILE_DIRS =
# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
-# path where java can find the plantuml.jar file. If left blank, it is assumed
-# PlantUML is not used or called during a preprocessing step. Doxygen will
-# generate a warning when it encounters a \startuml command in this case and
-# will not generate output for the diagram.
+# path where java can find the plantuml.jar file or to the filename of jar file
+# to be used. If left blank, it is assumed PlantUML is not used or called during
+# a preprocessing step. Doxygen will generate a warning when it encounters a
+# \startuml command in this case and will not generate output for the diagram.
PLANTUML_JAR_PATH =
@@ -2623,6 +2641,8 @@ DOT_MULTI_TARGETS = NO
# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
# explaining the meaning of the various boxes and arrows in the dot generated
# graphs.
+# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal
+# graphical representation for inheritance and collaboration diagrams is used.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.
diff --git a/PKGBUILD b/PKGBUILD
index 40b4c04..b722b56 100644
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -13,7 +13,7 @@ sha256sums=('3c35bee0e7383f704f2ff59ae100a708c0c36772d0b1b375c481587a6cea3622')
build () {
cd "$srcdir/$pkgname-$pkgver"
- g++ -std=c++20 -lcurl -lsqlite3 -fPIC -shared libscry.cc -o libscry.so
+ g++ -std=c++20 -fPIC -shared src/*.cc -o libscry.so
}
package () {
diff --git a/README.md b/README.md
index 5fd93ce..8f627ae 100644
--- a/README.md
+++ b/README.md
@@ -21,9 +21,11 @@ make
make install #as root
```
## Documentation
-Available in [docs](docs/)
+Available in [docs](docs/) and [examples](examples/)
+
## Projects using libscry
[scrycli](https://github.com/EmperorPenguin18/scrycli)
+
## Legal
From the API Docs:
diff --git a/docs/html/doxygen.css b/docs/html/doxygen.css
index 8e9cca3..9036737 100644
--- a/docs/html/doxygen.css
+++ b/docs/html/doxygen.css
@@ -1,4 +1,4 @@
-/* The standard CSS for doxygen 1.9.2 */
+/* The standard CSS for doxygen 1.9.3 */
body, table, div, p, dl {
font: 400 14px/22px Roboto,sans-serif;
@@ -262,7 +262,7 @@ dl.el {
}
ul {
- overflow: hidden; /*Fixed: list item bullets overlap floating elements*/
+ overflow: visible;
}
#side-nav ul {
@@ -1356,6 +1356,11 @@ dl.section dd {
}
+#projectrow
+{
+ height: 56px;
+}
+
#projectlogo
{
text-align: center;
@@ -1371,6 +1376,7 @@ dl.section dd {
#projectalign
{
vertical-align: middle;
+ padding-left: 0.5em;
}
#projectname
@@ -1521,6 +1527,10 @@ span.emoji {
*/
}
+span.obfuscator {
+ display: none;
+}
+
.PageDocRTL-title div.toc li.level1 {
margin-left: 0 !important;
margin-right: 0;
@@ -1575,7 +1585,7 @@ tr.heading h2 {
#powerTip {
cursor: default;
- white-space: nowrap;
+ /*white-space: nowrap;*/
background-color: white;
border: 1px solid gray;
border-radius: 4px 4px 4px 4px;
@@ -1814,6 +1824,10 @@ table.DocNodeLTR {
margin-left: 0;
}
+code.JavaDocCode
+ direction:ltr;
+}
+
tt, code, kbd, samp
{
display: inline-block;
diff --git a/docs/html/index.html b/docs/html/index.html
index ccc9501..de7c95e 100644
--- a/docs/html/index.html
+++ b/docs/html/index.html
@@ -3,7 +3,7 @@
-
+
libscry: Main Page
@@ -19,9 +19,9 @@
-
-
- libscry
0.2
+
+
+ libscry
@@ -29,7 +29,7 @@
-
+
diff --git a/docs/html/search/all_0.js b/docs/html/search/all_0.js
index 1fb36f4..9872c3c 100644
--- a/docs/html/search/all_0.js
+++ b/docs/html/search/all_0.js
@@ -1,4 +1,4 @@
var searchData=
[
- ['allcards_0',['allcards',['../classList.html#a87c53df0e4968d8c6311f224a697b470',1,'List']]]
+ ['libscry_0',['libscry',['../md_README.html',1,'']]]
];
diff --git a/docs/html/search/pages_0.html b/docs/html/search/pages_0.html
index 34ca499..e53c280 100644
--- a/docs/html/search/pages_0.html
+++ b/docs/html/search/pages_0.html
@@ -2,7 +2,7 @@
-
+
diff --git a/docs/html/search/searchdata.js b/docs/html/search/searchdata.js
index 97ad9e2..9048815 100644
--- a/docs/html/search/searchdata.js
+++ b/docs/html/search/searchdata.js
@@ -1,24 +1,18 @@
var indexSectionsWithContent =
{
- 0: "acdjlmnopst",
- 1: "clms",
- 2: "acdjmnopst",
- 3: "l"
+ 0: "l",
+ 1: "l"
};
var indexSectionNames =
{
0: "all",
- 1: "classes",
- 2: "functions",
- 3: "pages"
+ 1: "pages"
};
var indexSectionLabels =
{
0: "All",
- 1: "Classes",
- 2: "Functions",
- 3: "Pages"
+ 1: "Pages"
};
diff --git a/docs/latex/doxygen.sty b/docs/latex/doxygen.sty
index 8d9535a..7e01991 100644
--- a/docs/latex/doxygen.sty
+++ b/docs/latex/doxygen.sty
@@ -23,6 +23,7 @@
\RequirePackage{enumitem}
\RequirePackage{alphalph}
\RequirePackage[normalem]{ulem} % for strikeout, but don't modify emphasis
+\RequirePackage{enumitem}
%---------- Internal commands used in this style file ----------------
@@ -165,18 +166,30 @@
}
% Used by numbered lists (using '-#' or ... )
-\newenvironment{DoxyEnumerate}{%
- \enumerate%
-}{%
- \endenumerate%
-}
+\setlistdepth{12}
+\newlist{DoxyEnumerate}{enumerate}{12}
+\setlist[DoxyEnumerate,1]{label=\arabic*.}
+\setlist[DoxyEnumerate,2]{label=(\enumalphalphcnt*)}
+\setlist[DoxyEnumerate,3]{label=\roman*.}
+\setlist[DoxyEnumerate,4]{label=\enumAlphAlphcnt*.}
+\setlist[DoxyEnumerate,5]{label=\arabic*.}
+\setlist[DoxyEnumerate,6]{label=(\enumalphalphcnt*)}
+\setlist[DoxyEnumerate,7]{label=\roman*.}
+\setlist[DoxyEnumerate,8]{label=\enumAlphAlphcnt*.}
+\setlist[DoxyEnumerate,9]{label=\arabic*.}
+\setlist[DoxyEnumerate,10]{label=(\enumalphalphcnt*)}
+\setlist[DoxyEnumerate,11]{label=\roman*.}
+\setlist[DoxyEnumerate,12]{label=\enumAlphAlphcnt*.}
% Used by bullet lists (using '-', @li, @arg, or )
-\newenvironment{DoxyItemize}{%
- \itemize%
-}{%
- \enditemize%
-}
+\setlistdepth{12}
+\newlist{DoxyItemize}{itemize}{12}
+\setlist[DoxyItemize]{label=\textperiodcentered}
+
+\setlist[DoxyItemize,1]{label=\textbullet}
+\setlist[DoxyItemize,2]{label=\normalfont\bfseries \textendash}
+\setlist[DoxyItemize,3]{label=\textasteriskcentered}
+\setlist[DoxyItemize,4]{label=\textperiodcentered}
% Used by description lists (using ... )
\newenvironment{DoxyDescription}{%
diff --git a/docs/latex/md_README.tex b/docs/latex/md_README.tex
index 239e0fe..8a6b386 100644
--- a/docs/latex/md_README.tex
+++ b/docs/latex/md_README.tex
@@ -38,7 +38,7 @@
$\ast$$\ast$\+\_\+\+Image Guidelines\+\_\+$\ast$$\ast$
-{\itshape \mbox{\hyperlink{classCard}{Card}} images on Scryfall are copyright Wizards of the Coast (and/or their artist, for very old sets) and they are provided for the purpose of creating additional Magic software, or creating community content (such as videos, set reviews, etc) about Magic and related products.}
+{\itshape Card images on Scryfall are copyright Wizards of the Coast (and/or their artist, for very old sets) and they are provided for the purpose of creating additional Magic software, or creating community content (such as videos, set reviews, etc) about Magic and related products.}
{\itshape When using images from Scryfall, please adhere to the following guidelines\+:}
@@ -51,4 +51,4 @@
{\itshape In particular, when using the art\+\_\+crop\+:}
-{\itshape \mbox{\hyperlink{classList}{List}} the artist name and copyright elsewhere in the same interface presenting the art crop, or use the full card image elsewhere in the same interface. Users should be able to identify the artist and source of the image somehow.}
\ No newline at end of file
+{\itshape List the artist name and copyright elsewhere in the same interface presenting the art crop, or use the full card image elsewhere in the same interface. Users should be able to identify the artist and source of the image somehow.}
\ No newline at end of file
diff --git a/docs/latex/refman.tex b/docs/latex/refman.tex
index 2e91d87..2df3ce3 100644
--- a/docs/latex/refman.tex
+++ b/docs/latex/refman.tex
@@ -130,7 +130,9 @@
colorlinks=true,%
linkcolor=blue,%
citecolor=blue,%
- unicode%
+ unicode,%
+ pdftitle=libscry,%
+ pdfsubject=%
}
% Custom commands used by the header
% Custom commands
@@ -162,7 +164,7 @@
\begin{center}%
{\Large libscry}\\
\vspace*{1cm}
- {\large Generated by Doxygen 1.9.2}\\
+ {\large Generated by Doxygen 1.9.3}\\
\end{center}
\end{titlepage}
\clearemptydoublepage
@@ -177,17 +179,6 @@ \chapter{libscry}
\label{md_README}
\Hypertarget{md_README}
\input{md_README}
-\chapter{Class Index}
-\input{annotated}
-\chapter{File Index}
-\input{files}
-\chapter{Class Documentation}
-\input{classCard}
-\input{classList}
-\input{structmemory}
-\input{classScry}
-\chapter{File Documentation}
-\input{libscry_8h_source}
%--- End generated contents ---
% Index
\backmatter
diff --git a/examples/simple.cc b/examples/simple.cc
index c4099f6..067fc09 100644
--- a/examples/simple.cc
+++ b/examples/simple.cc
@@ -4,14 +4,14 @@
#include
#include
-#include
+#include
using namespace std;
int main(int argc, char **argv)
{
/* This block is what allows the library to be dynamic. */
- void* handle = dlopen("/usr/lib/libscry.so", RTLD_LAZY);
+ void* handle = dlopen("libscry.so", RTLD_LAZY);
Scry* (*create)();
void (*destroy)(Scry*);
create = (Scry* (*)())dlsym(handle, "create_object");
@@ -30,4 +30,4 @@ int main(int argc, char **argv)
destroy(scry);
}
-/* Compiled with `g++ -std=c++20 simple.cc -ldl -o simple`. */
+/* Compiled with `g++ simple.cc -o simple`. */
diff --git a/examples/test.cc b/examples/test.cc
index 55dcbbd..aafcb4b 100644
--- a/examples/test.cc
+++ b/examples/test.cc
@@ -1,2 +1,159 @@
-//Test no results
-//Test not found
+//Test all functions example
+//License is available at
+//https://github.com/EmperorPenguin18/libscry/blob/main/LICENSE
+
+#include
+#include
+#include
+#include
+
+using namespace std;
+
+int main(int argc, char **argv)
+{
+ char fname[50] = "";
+ strcat(fname, getenv("HOME"));
+ strcat(fname, "/.local/share/libscry.db");
+ remove(fname);
+
+ void* handle = dlopen("libscry.so", RTLD_LAZY);
+ Scry* (*create)();
+ void (*destroy)(Scry*);
+ create = (Scry* (*)())dlsym(handle, "create_object");
+ destroy = (void (*)(Scry*))dlsym(handle, "destroy_object");
+
+ Scry* scry = (Scry*)create();
+
+ cout << "cards_search(\"cmc>3\")" << endl;
+ List* stuff = scry->cards_search("cmc>3");
+ vector cards = stuff->cards();
+ size_t i;
+ for (i = 0; i < 10; i++)
+ cout << cards[i]->name() << endl;
+ cout << ".." << endl;
+ for (i = cards.size()-10; i < cards.size(); i++)
+ cout << cards[i]->name() << endl;
+ cout << endl;
+
+ cout << "cards_search_cache(\"commander:wubrg\")" << endl;
+ clock_t start = clock();
+ stuff = scry->cards_search_cache("commander:wubrg");
+ cout << "Time taken: " << ((double) (clock() - start)) / CLOCKS_PER_SEC << endl;
+ cards = stuff->cards();
+ for (i = 0; i < 10; i++)
+ cout << cards[i]->name() << endl;
+ cout << ".." << endl;
+ for (i = cards.size()-10; i < cards.size(); i++)
+ cout << cards[i]->name() << endl;
+ cout << endl;
+
+ cout << "cards_search_cache(\"commander:wubrg\")" << endl;
+ start = clock();
+ stuff = scry->cards_search_cache("commander:wubrg");
+ cout << "Time taken: " << ((double) (clock() - start)) / CLOCKS_PER_SEC << endl;
+ cards = stuff->cards();
+ for (i = 0; i < 10; i++)
+ cout << cards[i]->name() << endl;
+ cout << ".." << endl;
+ for (i = cards.size()-10; i < cards.size(); i++)
+ cout << cards[i]->name() << endl;
+ cout << endl;
+
+ cout << "cards_named(\"vannifar\")" << endl;
+ Card* card = scry->cards_named("vannifar");
+ cout << card->name() << endl;
+ cout << endl;
+
+ cout << "cards_named_cache(\"agadeem's awakening\")" << endl;
+ start = clock();
+ card = scry->cards_named_cache("agadeem's awakening");
+ cout << "Time taken: " << ((double) (clock() - start)) / CLOCKS_PER_SEC << endl;
+ cout << "split(card)" << endl;
+ cards = scry->split(card);
+ cout << cards[1]->name() << endl;
+ cout << endl;
+
+ cout << "cards_named_cache(\"agadeem's awakening\")" << endl;
+ start = clock();
+ card = scry->cards_named_cache("agadeem's awakening");
+ cout << "Time taken: " << ((double) (clock() - start)) / CLOCKS_PER_SEC << endl;
+ cout << card->name() << endl;
+ cout << card->mana_cost() << endl;
+ cout << card->type_line() << endl;
+ cout << card->oracle_text() << endl;
+ cout << card->power() << endl;
+ cout << card->toughness() << endl;
+ cout << card->dual_sided() << endl;
+ cout << card->json() << endl;
+ cout << card->loyalty() << endl;
+ cout << endl;
+
+ cout << "cards_named(\"vannifar\", &img_size)" << endl;
+ size_t img_size = 0;
+ byte* image = scry->cards_named("vannifar", &img_size);
+ FILE* fp = fopen("test0.jpg", "wb");
+ fwrite((FILE*)image, sizeof(byte), img_size/sizeof(byte), fp);
+ fclose(fp);
+ free(image);
+ cout << endl;
+
+ cout << "cards_named_cache(\"agadeem's awakening\", &img_size)" << endl;
+ start = clock();
+ image = scry->cards_named_cache("agadeem's awakening", &img_size);
+ cout << "Time taken: " << ((double) (clock() - start)) / CLOCKS_PER_SEC << endl;
+ fp = fopen("test1.jpg", "wb");
+ fwrite((FILE*)image, sizeof(byte), img_size/sizeof(byte), fp);
+ fclose(fp);
+ free(image);
+ cout << endl;
+
+ cout << "cards_named_cache(\"agadeem's awakening\", &img_size)" << endl;
+ start = clock();
+ image = scry->cards_named_cache("agadeem's awakening", &img_size);
+ cout << "Time taken: " << ((double) (clock() - start)) / CLOCKS_PER_SEC << endl;
+ fp = fopen("test2.jpg", "wb");
+ fwrite((FILE*)image, sizeof(byte), img_size/sizeof(byte), fp);
+ fclose(fp);
+ cout << endl;
+
+ cout << "cards_autocomplete(\"oko\")" << endl;
+ vector complete = scry->cards_autocomplete("oko");
+ for (i = 0; i < complete.size(); i++)
+ cout << complete[i] << endl;
+ cout << endl;
+
+ cout << "cards_autocomplete_cache(\"rain\")" << endl;
+ start = clock();
+ complete = scry->cards_autocomplete_cache("rain");
+ cout << "Time taken: " << ((double) (clock() - start)) / CLOCKS_PER_SEC << endl;
+ for (i = 0; i < complete.size(); i++)
+ cout << complete[i] << endl;
+ cout << endl;
+
+ cout << "cards_autocomplete_cache(\"rain\")" << endl;
+ start = clock();
+ complete = scry->cards_autocomplete_cache("rain");
+ cout << "Time taken: " << ((double) (clock() - start)) / CLOCKS_PER_SEC << endl;
+ for (i = 0; i < complete.size(); i++)
+ cout << complete[i] << endl;
+ cout << endl;
+
+ cout << "cards_random()" << endl;
+ card = scry->cards_random();
+ cout << card->name() << endl;
+ cout << endl;
+
+ cout << "Test no card" << endl;
+ card = scry->cards_named("");
+ if (!card) cout << "Worked" << endl;
+ cout << endl;
+
+ cout << "Test no results" << endl;
+ stuff = scry->cards_search("");
+ if (!stuff) cout << "Worked" << endl;
+ cout << endl;
+
+ destroy(scry);
+}
+
+/* Compiled with `g++ test.cc -o test`. */
diff --git a/libscry.cc b/libscry.cc
deleted file mode 100644
index d82faa3..0000000
--- a/libscry.cc
+++ /dev/null
@@ -1,443 +0,0 @@
-//libscry by Sebastien MacDougall-Landry
-//License is available at
-//https://github.com/EmperorPenguin18/libscry/blob/main/LICENSE
-
-#include "libscry.h"
-
-using namespace std;
-using namespace rapidjson;
-using namespace std::chrono;
-
-extern "C" Scry* create_object() {
- return new Scry;
-}
-
-extern "C" void destroy_object( Scry* object ) {
- delete object;
-}
-
-struct memory {
- char *response;
- size_t size;
-};
-
-static size_t cb(void *data, size_t size, size_t nmemb, void *userp) {
- size_t realsize = size * nmemb;
- struct memory *mem = (struct memory *)userp;
-
- char *ptr = (char *)realloc(mem->response, mem->size + realsize + 1);
- if (ptr == NULL) return 0; // out of memory!
-
- mem->response = ptr;
- memcpy(&(mem->response[mem->size]), data, realsize);
- mem->size += realsize;
- mem->response[mem->size] = 0;
-
- return realsize;
-}
-
-Scry::Scry() {
- curl_global_init(CURL_GLOBAL_ALL);
- easyhandle = curl_easy_init();
- curl_easy_setopt(easyhandle, CURLOPT_WRITEFUNCTION, cb);
- curl_easy_setopt(easyhandle, CURLOPT_FOLLOWLOCATION, 1);
- curl_easy_setopt(easyhandle, CURLOPT_HTTPGET, 1);
- char * cachedir = getenv("XDG_CACHE_HOME");
- int rc;
- if (cachedir != NULL) rc = sqlite3_open(strcat(cachedir, "/libscry.db"), &db);
- else rc = sqlite3_open(strcat(getenv("HOME"), "/.cache/libscry.db"), &db);
- if (rc) {
- fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
- sqlite3_close(db);
- exit(1);
- }
- db_init("Cards");
- db_init("Lists");
- db_init("Autocompletes");
-}
-
-Scry::~Scry() {
- curl_easy_cleanup(easyhandle);
- curl_global_cleanup();
- sqlite3_close(db);
- while (!cards.empty()) {
- delete cards.back();
- cards.pop_back();
- }
- while (!lists.empty()) {
- delete lists.back();
- lists.pop_back();
- }
-}
-
-char * Scry::api_call(string url) {
- curl_easy_setopt(easyhandle, CURLOPT_URL, url.c_str());
- struct memory chunk = {0};
- curl_easy_setopt(easyhandle, CURLOPT_WRITEDATA, (void *)&chunk);
- CURLcode success = curl_easy_perform(easyhandle);
- if (success != 0) {
- fprintf(stderr, "Errored with CURLcode %i\n", success);
- exit(success);
- }
- return chunk.response;
-}
-
-string Scry::db_exec(string in) {
- const char * cmd = in.c_str();
- sqlite3_stmt *stmt;
- int rc = sqlite3_prepare_v2(db, cmd, -1, &stmt, NULL);
- if (rc != SQLITE_OK) {
- fprintf(stderr, "DB error: %s\n", sqlite3_errmsg(db));
- exit(1);
- }
- rc = sqlite3_step(stmt);
- if (rc != SQLITE_ROW && rc != SQLITE_DONE) {
- fprintf(stderr, "DB error: %s\n", sqlite3_errmsg(db));
- sqlite3_finalize(stmt);
- exit(1);
- }
- string output;
- string temp = in.substr(0, 6);
- if (temp == "SELECT") output = string((char*)sqlite3_column_text(stmt, 0));
- sqlite3_finalize(stmt);
- return output;
-}
-
-void Scry::db_init(string table) {
- db_exec("CREATE TABLE IF NOT EXISTS " + table + "(Key TEXT, Updated DATETIME, Value TEXT);");
-}
-
-bool Scry::db_check(string table, string search) {
- string cmd = "SELECT COUNT(1) FROM " + table + " WHERE Key='" + search + "';";
- if (db_exec(cmd).compare("1") == 0) return true;
- return false;
-}
-
-string Scry::db_read(string table, string search, string column) {
- string cmd = "SELECT " + column + " FROM " + table + " WHERE Key='" + search + "';";
- return db_exec(cmd);
-}
-
-void Scry::db_write(string table, string key, string value) {
- string cmd = "UPDATE " + table + " SET Updated=datetime(), Value='" + value + "' WHERE Key='" + key + "';";
- db_exec(cmd);
-}
-
-void Scry::db_new(string table, string key, string value) {
- string cmd = "INSERT INTO " + table + " VALUES ('" + key + "', datetime(), '" + value + "');";
- db_exec(cmd);
-}
-
-const year_month_day Scry::parse(string datetime) {
- string ys = datetime.substr(0,4);
- string ms = datetime.substr(5,2);
- string ds = datetime.substr(8,2);
- year y(stoi(ys));
- month m (stoi(ms));
- day d (stoi(ds));
- const year_month_day output(y, m, d);
- return output;
-}
-
-int Scry::datecheck(string datetime) {
- const time_point now{system_clock::now()};
- const year_month_day ymd{floor(now)};
- const year_month_day old = parse(datetime);
-
- int output;
- if (static_cast(ymd.year()) > static_cast(old.year())) {
- output = 1;
- } else {
- if (static_cast(ymd.month()) > static_cast(old.month())) {
- output = 1;
- } else {
- if (static_cast(ymd.day()) > static_cast(old.day()) + 7) {
- output = 1;
- } else if (static_cast(ymd.day()) == static_cast(old.day()) + 7) {
- output = 0;
- } else {
- output = -1;
- }
- }
- }
-
- return output;
-}
-
-vector Scry::explode(const string& str, const char& ch) {
- string next;
- vector result;
- for (string::const_iterator it = str.begin(); it != str.end(); it++) {
- if (*it == ch) {
- if (!next.empty()) {
- result.push_back(next);
- next.clear();
- }
- } else next += *it;
- }
- if (!next.empty()) result.push_back(next);
- return result;
-}
-
-string Scry::implode(const vector& strs, const char& ch) {
- string result = "";
- for (auto it = strs.begin(); it != strs.end(); it++) {
- result += (*it) + ch;
- }
- return result;
-}
-
-string Scry::urlformat(string str) {
- regex space(" ");
- regex colon(":");
- str = regex_replace(str, space, "%20");
- str = regex_replace(str, colon, "%3A");
- return str;
-}
-
-string Scry::nameformat(string str) {
- regex apos("'");
- str = regex_replace(str, apos, "''");
- return str;
-}
-
-List * Scry::cards_search(string query) {
- query = urlformat(query);
- string url = "https://api.scryfall.com/cards/search?q=" + query;
- List * list = new List(this, api_call(url), false);
- lists.push_back(list);
- return list;
-}
-
-string Scry::cachecard(List * list, bool recursive) {
- string names = "";
- if (recursive) {
- for (int i = 0; i < list->allcards().size(); i++) {
- string name = nameformat(list->allcards()[i]->name());
- names += name + "\n";
- string temp = nameformat(list->allcards()[i]->json());
- if (i < list->cards().size()) {
- if (db_check("Cards", name)) {
- db_write("Cards", name, temp);
- } else db_new("Cards", name, temp);
- }
- }
- } else {
- for (int i = 0; i < list->cards().size(); i++) {
- string name = nameformat(list->cards()[i]->name());
- names += name + "\n";
- string temp = nameformat(list->cards()[i]->json());
- if (db_check("Cards", name)) {
- db_write("Cards", name, temp);
- } else db_new("Cards", name, temp);
- }
- }
- names.pop_back();
- return names;
-}
-
-List * Scry::cards_search_cache(string query) {
- query = urlformat(query);
- List * list;
-
- regex pages(".*&p"); smatch sm; regex_search(query, sm, pages);
- string search = string(sm[0]).substr(0, sm[0].length()-2);
- if (size(search) < 1) search = query;
- if (db_check("Lists", search)) {
- if (datecheck( db_read("Lists", search, "Updated") ) == 1) {
- string url = "https://api.scryfall.com/cards/search?q=" + query;
- list = new List(this, api_call(url), true);
- lists.push_back(list);
- db_write("Lists", search, cachecard(list, true));
- } else {
- vector strvec = explode(db_read("Lists", search, "Value"), '\n');
- vector content;
- for (int i = 0; i < strvec.size(); i++)
- content.push_back( new Card( db_read("Cards", nameformat(strvec[i]), "Value").c_str() ) );
- list = new List( content );
- lists.push_back(list);
- }
- } else {
- string url = "https://api.scryfall.com/cards/search?q=" + query;
- list = new List(this, api_call(url), true);
- lists.push_back(list);
- string names = cachecard(list, false);
- if (db_check("Lists", search)) {
- string temp = nameformat( db_read("Lists", search, "Value") );
- db_write("Lists", search, names + "\n" + temp);
- } else db_new("Lists", search, names);
- }
-
- return list;
-}
-
-Card * Scry::cards_named(string query) {
- query = urlformat(query);
- string url = "https://api.scryfall.com/cards/named?fuzzy=" + query;
- Card * card = new Card(api_call(url));
- cards.push_back(card);
- return card;
-}
-
-Card * Scry::cards_named_cache(string query) {
- query = urlformat(query);
- Card * card;
- query[0] = toupper(query[0]);
- string name = nameformat(query);
-
- if (db_check("Cards", name)) {
- if (datecheck( db_read("Cards", name, "Updated") ) == 1) {
- card = cards_named(query);
- db_write("Cards", name, nameformat(card->json()));
- } else {
- card = new Card( db_read("Cards", name, "Value").c_str() );
- cards.push_back(card);
- }
- } else {
- card = cards_named(query);
- db_new("Cards", name, nameformat(card->json()));
- }
-
- return card;
-}
-
-vector Scry::cards_autocomplete(string query) {
- query = urlformat(query);
- string url = "https://api.scryfall.com/cards/autocomplete?q=" + query;
- Document doc;
- doc.Parse(api_call(url));
- const Value& a = doc["data"];
- vector output;
- for (auto& v : a.GetArray()) output.push_back(v.GetString());
- return output;
-}
-
-vector Scry::cards_autocomplete_cache(string query) {
- query = urlformat(query);
- vector names;
-
- if (db_check("Autocompletes", query)) {
- if (datecheck( db_read("Autocompletes", query, "Updated") ) == 1) {
- names = cards_autocomplete(query);
- string namestr = implode(names, '\n');
- db_write("Autocompletes", query, nameformat(namestr));
- } else {
- names = explode(db_read("Autocompletes", query, "Value"), '\n');
- }
- } else {
- names = cards_autocomplete(query);
- string namestr = implode(names, '\n');
- db_new("Autocompletes", query, nameformat(namestr));
- }
-
- return names;
-}
-
-Card * Scry::cards_random() {
- string url = "https://api.scryfall.com/cards/random";
- Card * card = new Card(api_call(url));
- cards.push_back(card);
- return card;
-}
-
-vector Scry::split(Card * card) {
- Document doc; doc.Parse(card->json().c_str());
- vector output;
- for (int i = 0; i < doc["card_faces"].Size(); i++) {
- StringBuffer buffer;
- Writer writer(buffer);
- doc["card_faces"][i].Accept(writer);
- Card * card = new Card(buffer.GetString());
- output.push_back(card); cards.push_back(card);
- }
- return output;
-}
-
-Card::Card(const char * rawjson) {
- data.Parse(rawjson);
-}
-
-string Card::name() {
- return data["name"].GetString();
-}
-
-string Card::mana_cost() {
- if (!data.HasMember("mana_cost")) return "";
- return data["mana_cost"].GetString();
-}
-
-string Card::type_line() {
- return data["type_line"].GetString();
-}
-
-string Card::oracle_text() {
- if (!data.HasMember("oracle_text")) return "";
- return data["oracle_text"].GetString();
-}
-
-string Card::power() {
- if (!data.HasMember("power")) return "";
- return data["power"].GetString();
-}
-
-string Card::toughness() {
- if (!data.HasMember("toughness")) return "";
- return data["toughness"].GetString();
-}
-
-bool Card::dual_sided() {
- return data.HasMember("card_faces");
-}
-
-string Card::json() {
- StringBuffer buffer;
- Writer writer(buffer);
- data.Accept(writer);
- return buffer.GetString();
-}
-
-List::List(Scry * scry, const char * rawjson, bool cache) {
- Document doc;
- doc.Parse(rawjson);
- for (int i = 0; i < doc["data"].Size(); i++) {
- StringBuffer buffer;
- Writer writer(buffer);
- doc["data"][i].Accept(writer);
- content.push_back(new Card(buffer.GetString()));
- }
- if (doc["has_more"].GetBool()) {
- string url = doc["next_page"].GetString();
- regex q("q=.*&");
- regex page("page=.*&q");
- smatch sm1; regex_search(url, sm1, q);
- smatch sm2; regex_search(url, sm2, page);
- string query = string(sm1[0]).substr(2, sm1[0].length()-2) + string(sm2[0]).substr(0, sm2[0].length()-2);
- if (cache) nextpage = scry->cards_search_cache(query);
- else nextpage = scry->cards_search(query);
- } else nextpage = nullptr;
-}
-
-List::List(vector input) {
- content = input;
- nextpage = nullptr;
-}
-
-List::~List() {
- while (!content.empty()) {
- delete content.back();
- content.pop_back();
- }
-}
-
-vector List::cards() {
- return content;
-}
-
-vector List::allcards() {
- vector output = cards();
- if (nextpage != nullptr) {
- vector append = nextpage->allcards();
- output.insert(output.end(), append.begin(), append.end());
- }
- return output;
-}
diff --git a/libscry.h b/libscry.h
deleted file mode 100644
index e3c1b91..0000000
--- a/libscry.h
+++ /dev/null
@@ -1,108 +0,0 @@
-//libscry by Sebastien MacDougall-Landry
-//License is available at
-//https://github.com/EmperorPenguin18/libscry/blob/main/LICENSE
-
-#pragma once
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-using namespace std;
-using namespace rapidjson;
-using namespace std::chrono;
-
-class Card;
-class List;
-class Scry;
-
-///This class is used to represent a single card
-class Card {
- public:
- Card(const char * rawjson);
-
- ///Get the name (Storm Crow) of the card.
- virtual string name();
- ///Get the mana cost ({1}{U}) of the card.
- virtual string mana_cost();
- ///Get the type line (Creature - Bird) of the card.
- virtual string type_line();
- ///Get the oracle text (Flying) of the card.
- virtual string oracle_text();
- ///Get the power (1) of the card.
- virtual string power();
- ///Get the toughness (2) of the card.
- virtual string toughness();
- ///Returns true if the card has two faces, false otherwise.
- virtual bool dual_sided();
- ///Get the raw json text of the card provided by Scryfall.
- virtual string json();
- private:
- Document data;
-};
-
-///This class is used to represent a list of cards (returned from a search for example)
-class List {
- public:
- List(Scry * scry, const char * rawjson, bool cache);
- List(vector input);
- ~List();
-
- ///Returns a vector with all the cards on this page of the list. For cards on all pages see allcards().
- virtual vector cards();
- ///Cumulates cards from all pages of the list and returns them as a vector.
- virtual vector allcards();
- private:
- vector content;
- List * nextpage;
-};
-
-///This class is the main interface to Scryfall. It handles all the API requests and caching.
-class Scry {
- public:
- Scry();
- ~Scry();
-
- ///Put in a query using Scryfall syntax (commander:g+type:legendary) as the argument.
- virtual List * cards_search(string query);
- ///Cached version of cards_search.
- virtual List * cards_search_cache(string query);
- ///Put in the name of a card as the argument.
- virtual Card * cards_named(string query);
- ///Cached version of cards_named.
- virtual Card * cards_named_cache(string query);
- ///Put in part of a card's name as the argument.
- virtual vector cards_autocomplete(string query);
- ///Cached version of cards_autocomplete.
- virtual vector cards_autocomplete_cache(string query);
- ///Returns a randomly selected card.
- virtual Card * cards_random();
- ///Splits a card into it's multiple faces. Useful because some cards won't have certain data in the conglomerate.
- virtual vector split(Card * card);
- private:
- CURL *easyhandle;
- sqlite3 *db;
- vector cards;
- vector lists;
- virtual char * api_call(string url);
- virtual string db_exec(string in);
- virtual void db_init(string table);
- virtual bool db_check(string table, string search);
- virtual string db_read(string table, string search, string column);
- virtual void db_write(string table, string key, string value);
- virtual void db_new(string table, string key, string value);
- virtual int datecheck(string datetime);
- virtual const year_month_day parse(string datetime);
- virtual vector explode(const string& str, const char& ch);
- virtual string implode(const vector& strs, const char& ch);
- virtual string urlformat(string str);
- virtual string nameformat(string str);
- virtual string cachecard(List * list, bool recursive);
-};
diff --git a/makefile b/makefile
index ff2412f..a8f136a 100644
--- a/makefile
+++ b/makefile
@@ -1,8 +1,16 @@
-build: libscry.h libscry.cc
- g++ -std=c++20 -lcurl -lsqlite3 -fPIC -shared libscry.cc -o libscry.so
+build: src/*.cc
+ g++ -g -Og -D DEBUG -rdynamic -std=c++20 -pthread -fPIC -shared src/*.cc -o libscry.so
-install: libscry.h libscry.so
- @mkdir -p /usr/include/
- @mkdir -p /usr/lib/
- @cp libscry.h /usr/include/libscry.h
- @mv libscry.so /usr/lib/libscry.so
+release: src/*.cc
+ g++ -O3 -std=c++20 -pthread -fPIC -shared src/*.cc -o libscry.so
+
+install: src/*.h libscry.so
+ mkdir -p /usr/include/scry
+ mkdir -p /usr/lib/
+ cp src/*.h /usr/include/scry/
+ mv libscry.so /usr/lib/libscry.so
+
+clean:
+ rm -f libscry.so
+ rm -f examples/simple
+ rm -f examples/test
diff --git a/src/card.cc b/src/card.cc
new file mode 100644
index 0000000..f517e38
--- /dev/null
+++ b/src/card.cc
@@ -0,0 +1,56 @@
+//libscry by Sebastien MacDougall-Landry
+//License is available at
+//https://github.com/EmperorPenguin18/libscry/blob/main/LICENSE
+
+#include "card.h"
+
+using namespace std;
+
+Card::Card(const char * rawjson) {
+ data.Parse(rawjson);
+ if (strcmp(data["object"].GetString(), "error") == 0) throw "Invalid Card";
+}
+
+string Card::name() {
+ return data["name"].GetString();
+}
+
+string Card::mana_cost() {
+ if (!data.HasMember("mana_cost")) return "";
+ return data["mana_cost"].GetString();
+}
+
+string Card::type_line() {
+ return data["type_line"].GetString();
+}
+
+string Card::oracle_text() {
+ if (!data.HasMember("oracle_text")) return "";
+ return data["oracle_text"].GetString();
+}
+
+string Card::power() {
+ if (!data.HasMember("power")) return "";
+ return data["power"].GetString();
+}
+
+string Card::toughness() {
+ if (!data.HasMember("toughness")) return "";
+ return data["toughness"].GetString();
+}
+
+bool Card::dual_sided() {
+ return data.HasMember("card_faces");
+}
+
+string Card::json() {
+ StringBuffer buffer;
+ Writer writer(buffer);
+ data.Accept(writer);
+ return buffer.GetString();
+}
+
+string Card::loyalty() {
+ if (!data.HasMember("loyalty")) return "";
+ return data["loyalty"].GetString();
+}
diff --git a/src/card.h b/src/card.h
new file mode 100644
index 0000000..77fb1b9
--- /dev/null
+++ b/src/card.h
@@ -0,0 +1,39 @@
+//libscry by Sebastien MacDougall-Landry
+//License is available at
+//https://github.com/EmperorPenguin18/libscry/blob/main/LICENSE
+
+#pragma once
+#include
+#include
+#include
+#include
+
+using namespace std;
+using namespace rapidjson;
+
+///This class is used to represent a single card
+class Card {
+ public:
+ Card(const char*);
+
+ ///Get the name (Storm Crow) of the card.
+ virtual string name();
+ ///Get the mana cost ({1}{U}) of the card.
+ virtual string mana_cost();
+ ///Get the type line (Creature - Bird) of the card.
+ virtual string type_line();
+ ///Get the oracle text (Flying) of the card.
+ virtual string oracle_text();
+ ///Get the power (1) of the card.
+ virtual string power();
+ ///Get the toughness (2) of the card.
+ virtual string toughness();
+ ///Returns true if the card has two faces, false otherwise.
+ virtual bool dual_sided();
+ ///Get the raw json text of the card provided by Scryfall.
+ virtual string json();
+ ///Get the starting loyalty of the card
+ virtual string loyalty();
+ private:
+ Document data;
+};
diff --git a/src/data.cc b/src/data.cc
new file mode 100644
index 0000000..16005b2
--- /dev/null
+++ b/src/data.cc
@@ -0,0 +1,272 @@
+//libscry by Sebastien MacDougall-Landry
+//License is available at
+//https://github.com/EmperorPenguin18/libscry/blob/main/LICENSE
+
+#include "data.h"
+
+using namespace std;
+using namespace std::chrono;
+
+DataAccess::DataAccess(const char *name) {
+ sqlite3_lib = dlopen("libsqlite3.so", RTLD_LAZY | RTLD_DEEPBIND);
+ if (!sqlite3_lib) {
+ fprintf(stderr, "%s\n", dlerror());
+ exit(EXIT_FAILURE);
+ }
+ sqlite3_open = reinterpret_cast(dlsym(sqlite3_lib, "sqlite3_open"));
+ sqlite3_errmsg = reinterpret_cast(dlsym(sqlite3_lib, "sqlite3_errmsg"));
+ sqlite3_close = reinterpret_cast(dlsym(sqlite3_lib, "sqlite3_close"));
+ sqlite3_prepare_v2 = reinterpret_cast(dlsym(sqlite3_lib, "sqlite3_prepare_v2"));
+ sqlite3_step = reinterpret_cast(dlsym(sqlite3_lib, "sqlite3_step"));
+ sqlite3_finalize = reinterpret_cast(dlsym(sqlite3_lib, "sqlite3_finalize"));
+ sqlite3_column_blob = reinterpret_cast(dlsym(sqlite3_lib, "sqlite3_column_blob"));
+ sqlite3_column_bytes = reinterpret_cast(dlsym(sqlite3_lib, "sqlite3_column_bytes"));
+ sqlite3_exec = reinterpret_cast(dlsym(sqlite3_lib, "sqlite3_exec"));
+ sqlite3_backup_init = reinterpret_cast(dlsym(sqlite3_lib, "sqlite3_backup_init"));
+ sqlite3_backup_step = reinterpret_cast(dlsym(sqlite3_lib, "sqlite3_backup_step"));
+ sqlite3_backup_finish = reinterpret_cast(dlsym(sqlite3_lib, "sqlite3_backup_finish"));
+ sqlite3_errcode = reinterpret_cast(dlsym(sqlite3_lib, "sqlite3_errcode"));
+ sqlite3_bind_blob = reinterpret_cast(dlsym(sqlite3_lib, "sqlite3_bind_blob"));
+
+ char * cachedir = getenv("XDG_DATA_HOME");
+ fname = (char*)calloc(100, sizeof(char)*100);
+ int rc;
+ if (cachedir != NULL) {
+ strcat(fname, cachedir);
+ strcat(fname, "/");
+ } else {
+ strcat(fname, getenv("HOME"));
+ strcat(fname, "/.local/share/");
+ }
+ strcat(fname, name);
+ strcat(fname, ".db");
+#ifdef DEBUG
+ fprintf(stderr, "Database filepath: %s\n", fname);
+#endif
+ rc = sqlite3_open(":memory:", &db);
+ if (rc) {
+ fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
+ sqlite3_close(db);
+ exit(1);
+ }
+ db_copy(0);
+ sql_exec(db, "pragma journal_mode = WAL;");
+ sql_exec(db, "pragma synchronous = normal;");
+ sql_exec(db, "pragma temp_store = memory;");
+ sql_exec(db, "pragma mmap_size = 30000000000;");
+ sql_exec(db, "pragma locking_mode = exclusive;");
+}
+
+DataAccess::~DataAccess() {
+ sql_exec(db, "pragma optimize;");
+ db_copy(1);
+ sqlite3_close(db);
+ dlclose(sqlite3_lib);
+ free(fname);
+ while (!bytes.empty()) {
+ free(bytes.back());
+ bytes.pop_back();
+ }
+}
+
+void DataAccess::db_copy(int isSave) {
+#ifdef DEBUG
+ cerr << "DB Copy:" << endl;
+#endif
+ sqlite3 *pFile;
+ int rc = sqlite3_open(fname, &pFile);
+ if (rc == SQLITE_OK) {
+ /*size_t size = 0;
+ char* page_size = (isSave ? (char*)sql_read(db, "PRAGMA PAGE_SIZE;", &size) : (char*)sql_read(pFile, "PRAGMA PAGE_SIZE;", &size) );
+#ifdef DEBUG
+ cerr << "Page size: " << (char*)page_size << endl;
+#endif
+ char cmd[30] = "pragma page_size = ";
+ strcat(cmd, page_size);
+ strcat(cmd, ";");
+ if (isSave) sql_write(pFile, cmd, NULL, size);
+ else sql_write(db, cmd, NULL, size);*/
+ sqlite3 *pFrom = (isSave ? db : pFile);
+ sqlite3 *pTo = (isSave ? pFile : db);
+ sqlite3_backup *pBackup = sqlite3_backup_init(pTo, "main", pFrom, "main");
+ if (pBackup) {
+ (void)sqlite3_backup_step(pBackup, -1);
+ (void)sqlite3_backup_finish(pBackup);
+ }
+ rc = sqlite3_errcode(pTo);
+ if (rc != SQLITE_OK) {
+ fprintf(stderr, "Copy error: %s\n", sqlite3_errmsg(db));
+ exit(1);
+ }
+ }
+ (void)sqlite3_close(pFile);
+}
+
+byte* DataAccess::sql_read(sqlite3 *pDb, const char* cmd, size_t* size) {
+#ifdef DEBUG
+ cerr << "Sql call: " << cmd << endl;
+#endif
+ sqlite3_stmt *stmt;
+ int rc = sqlite3_prepare_v2(pDb, cmd, -1, &stmt, NULL);
+ if (rc != SQLITE_OK) {
+ fprintf(stderr, "SQL compile error: %s\n", sqlite3_errmsg(pDb));
+ exit(1);
+ }
+ rc = sqlite3_step(stmt);
+ if (rc != SQLITE_ROW && rc != SQLITE_DONE) {
+ fprintf(stderr, "Read error: %s\n", sqlite3_errmsg(pDb));
+ sqlite3_finalize(stmt);
+ exit(1);
+ }
+ byte *rawoutput = (byte*)sqlite3_column_blob(stmt, 0);
+ if (!rawoutput) {
+ sqlite3_finalize(stmt);
+ return nullptr;
+ }
+ *size = sqlite3_column_bytes(stmt, 0);
+#ifdef DEBUG
+ cerr << "Database returned (250 chars): ";
+ for (size_t i = 0; i < min((size_t)250, *size); i++) cerr << (char)rawoutput[i];
+ cerr << endl;
+#endif
+ byte* output = (byte*)malloc(sizeof(byte)*(*size) + 1);
+ if (!output) {
+ fprintf(stderr, "not enough memory: malloc returned null");
+ exit(1);
+ }
+ memcpy(output, rawoutput, *size);
+ bytes.push_back(output);
+ sqlite3_finalize(stmt);
+ return output;
+}
+
+void DataAccess::sql_write(sqlite3 *pDb, const char* cmd, byte* data, const size_t& size) {
+#ifdef DEBUG
+ cerr << "Sql call: " << cmd << endl;
+#endif
+ sqlite3_stmt *stmt;
+ int rc = sqlite3_prepare_v2(pDb, cmd, -1, &stmt, NULL);
+ if (rc != SQLITE_OK) {
+ fprintf(stderr, "SQL compile error: %s\n", sqlite3_errmsg(pDb));
+ exit(1);
+ }
+#ifdef DEBUG
+ cerr << "Data input (250 chars): ";
+ for (size_t i = 0; i < min((size_t)250, size); i++) cerr << (char)data[i];
+ cerr << endl;
+#endif
+ rc = sqlite3_bind_blob(stmt, 1, data, size, SQLITE_STATIC);
+ rc = sqlite3_step(stmt);
+ if (rc != SQLITE_ROW && rc != SQLITE_DONE) {
+ fprintf(stderr, "Write error: %s\n", sqlite3_errmsg(pDb));
+ sqlite3_finalize(stmt);
+ exit(1);
+ }
+#ifdef DEBUG
+ if (rc == SQLITE_ROW)
+ cerr << "Write returned: " << (char*)sqlite3_column_blob(stmt, 0) << endl;
+#endif
+ sqlite3_finalize(stmt);
+}
+
+void DataAccess::sql_exec(sqlite3 *pDb, const char* cmd) {
+#ifdef DEBUG
+ cerr << "Sql call: " << cmd << endl;
+#endif
+ int rc = sqlite3_exec(pDb, cmd, NULL, NULL, NULL);
+ if (rc != SQLITE_OK) {
+ fprintf(stderr, "SQL exec error: %s\n", sqlite3_errmsg(pDb));
+ exit(1);
+ }
+}
+
+int DataAccess::datecheck(const char* table, const char* search) {
+ char cmd[200] = "";
+ strcat(cmd, "SELECT Updated FROM ");
+ strcat(cmd, table);
+ strcat(cmd, " WHERE Key='");
+ strcat(cmd, search);
+ strcat(cmd, "';");
+ size_t size = 0;
+ char* rawdatetime = (char*)sql_read(db, cmd, &size);
+ if (!rawdatetime) return 1;
+ rawdatetime[size] = '\0';
+ string datetime = string(rawdatetime);
+
+ const time_point now{system_clock::now()};
+ const year_month_day ymd{floor(now)};
+ const year_month_day old(
+ static_cast(stoi(datetime.substr(0,4))),
+ static_cast(stoi(datetime.substr(5,2))),
+ static_cast(stoi(datetime.substr(8,2))));
+
+ int output;
+ if (static_cast(ymd.year()) > static_cast(old.year())) {
+ output = 1;
+ } else {
+ if (static_cast(ymd.month()) > static_cast(old.month())) {
+ output = 1;
+ } else {
+ if (static_cast(ymd.day()) > static_cast(old.day()) + 7) {
+ output = 1;
+ } else if (static_cast(ymd.day()) == static_cast(old.day()) + 7) {
+ output = 0;
+ } else {
+ output = -1;
+ }
+ }
+ }
+
+ return output;
+}
+
+void DataAccess::db_exec(const char* table) {
+#ifdef DEBUG
+ cerr << "Creating table: " << table << endl;
+#endif
+ string cmd = "CREATE TABLE IF NOT EXISTS " + string(table) + "(Key TEXT NOT NULL, Updated DATETIME NOT NULL, Value BLOB NOT NULL, PRIMARY KEY(Key));";
+ sql_exec(db, cmd.c_str());
+}
+
+byte* DataAccess::db_exec(const char* table, const char* key, size_t* size) {
+ char cmd[200] = "";
+ strcat(cmd, "SELECT Value FROM ");
+ strcat(cmd, table);
+ strcat(cmd, " WHERE Key='");
+ strcat(cmd, key);
+ strcat(cmd, "';");
+ return sql_read(db, cmd, size);
+}
+
+char* DataAccess::db_exec(const char* table, const char* key) {
+ size_t size = 0;
+ char* output = (char*)db_exec(table, key, &size);
+ output[size] = '\0';
+ return output;
+}
+
+void DataAccess::db_exec(const char* table, const char* key, byte* value, const size_t& size) {
+ char cmd[200] = "";
+ strcat(cmd, "REPLACE INTO ");
+ strcat(cmd, table);
+ strcat(cmd, " VALUES ('");
+ strcat(cmd, key);
+ strcat(cmd, "', datetime(), ?);");
+ sql_write(db, cmd, value, size);
+}
+
+void DataAccess::db_exec(const char* table, vector keys, vector values) {
+ sql_exec(db, "BEGIN TRANSACTION;");
+ for (int i = 0; i < keys.size(); i++) {
+ char cmd[200] = "";
+ strcat(cmd, "REPLACE INTO ");
+ strcat(cmd, table);
+ strcat(cmd, " VALUES ('");
+ strcat(cmd, keys[i]);
+ strcat(cmd, "', datetime(), ?);");
+ sql_write(db, cmd, (byte*)values[i], strlen(values[i]));
+ free(keys[i]);
+ delete[] values[i];
+ }
+ sql_exec(db, "END TRANSACTION;");
+}
diff --git a/src/data.h b/src/data.h
new file mode 100644
index 0000000..2d650f6
--- /dev/null
+++ b/src/data.h
@@ -0,0 +1,72 @@
+//libscry by Sebastien MacDougall-Landry
+//License is available at
+//https://github.com/EmperorPenguin18/libscry/blob/main/LICENSE
+
+#pragma once
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef DEBUG
+#include
+#include
+#endif
+
+using namespace std;
+
+///This class is used to access the database
+class DataAccess {
+ public:
+ DataAccess(const char*);
+ ~DataAccess();
+
+ virtual int datecheck(const char*, const char*);
+ virtual void db_exec(const char*);
+ virtual byte* db_exec(const char*, const char*, size_t*);
+ virtual char* db_exec(const char*, const char*);
+ virtual void db_exec(const char*, const char*, byte*, const size_t&);
+ virtual void db_exec(const char*, vector, vector);
+ private:
+ void* sqlite3_lib;
+ typedef int (*o_handle)(const char*, sqlite3**);
+ o_handle sqlite3_open;
+ typedef const char* (*e_handle)(sqlite3*);
+ e_handle sqlite3_errmsg;
+ typedef int (*c_handle)(sqlite3*);
+ c_handle sqlite3_close;
+ typedef int (*p_handle)(sqlite3*, const char*, int, sqlite3_stmt**, const char**);
+ p_handle sqlite3_prepare_v2;
+ typedef int (*s_handle)(sqlite3_stmt*);
+ s_handle sqlite3_step;
+ typedef int (*f_handle)(sqlite3_stmt*);
+ f_handle sqlite3_finalize;
+ typedef const void* (*t_handle)(sqlite3_stmt*, int);
+ t_handle sqlite3_column_blob;
+ typedef int (*y_handle)(sqlite3_stmt*, int);
+ y_handle sqlite3_column_bytes;
+ typedef int (*x_handle)(sqlite3*, const char*, int(*)(void*, int, char**, char**), void*, char**);
+ x_handle sqlite3_exec;
+ typedef sqlite3_backup* (*i_handle)(sqlite3*, const char*, sqlite3*, const char*);
+ i_handle sqlite3_backup_init;
+ typedef int (*b_handle)(sqlite3_backup*, int);
+ b_handle sqlite3_backup_step;
+ typedef int (*n_handle)(sqlite3_backup*);
+ n_handle sqlite3_backup_finish;
+ typedef int (*r_handle)(sqlite3*);
+ r_handle sqlite3_errcode;
+ typedef int (*d_handle)(sqlite3_stmt*, int, const void*, int, void(*)(void*));
+ d_handle sqlite3_bind_blob;
+
+ sqlite3 *db;
+ char *fname;
+ vector bytes;
+ virtual void db_copy(int);
+ virtual byte* sql_read(sqlite3*, const char*, size_t*);
+ virtual void sql_write(sqlite3*, const char*, byte*, const size_t&);
+ virtual void sql_exec(sqlite3*, const char*);
+};
+
diff --git a/src/list.cc b/src/list.cc
new file mode 100644
index 0000000..5dae06a
--- /dev/null
+++ b/src/list.cc
@@ -0,0 +1,74 @@
+//libscry by Sebastien MacDougall-Landry
+//License is available at
+//https://github.com/EmperorPenguin18/libscry/blob/main/LICENSE
+
+#include "list.h"
+
+using namespace std;
+using namespace rapidjson;
+
+List::List(const char * rawjson) {
+ construct(rawjson);
+}
+
+List::List(vector rawjsons) {
+ string str = "";
+ for (int i = 0; i < rawjsons.size(); i++) {
+ str += rawjsons[i] + '\n';
+#ifdef DEBUG
+ cerr << "Last ten chars: " << rawjsons[i].substr(rawjsons[i].length()-10, 10) << endl;
+ cerr << "Size: " << rawjsons[i].size() << endl << "Capacity: " << rawjsons[i].capacity() << endl;
+#endif
+ construct(rawjsons[i].c_str());
+ }
+ data.Parse(str.c_str());
+}
+
+void List::construct(const char * rawjson) {
+ data.Parse(rawjson);
+ if (strcmp(data["object"].GetString(), "error") == 0) throw "Invalid List";
+#ifdef DEBUG
+ if (data.IsObject()) cerr << "JSON is valid" << endl;
+#endif
+ for (int i = 0; i < data["data"].Size(); i++) {
+ StringBuffer buffer;
+ Writer writer(buffer);
+ data["data"][i].Accept(writer);
+ content.push_back(new Card(buffer.GetString()));
+ }
+ if (data["has_more"].GetBool()) {
+ string url = data["next_page"].GetString();
+ regex dmn(".*\\?");
+ regex q("q=.*&");
+ regex page("page=.*&q");
+ smatch sm1; regex_search(url, sm1, dmn);
+ smatch sm2; regex_search(url, sm2, q);
+ smatch sm3; regex_search(url, sm3, page);
+#ifdef DEBUG
+ cerr << "Regex 1: " << sm1[0] << endl << "Regex 2: " << sm2[0] << endl << "Regex 3: " << sm3[0] << endl;
+#endif
+ nextpage = string(sm1[0]) + string(sm2[0]) + string(sm3[0]).substr(0, 5);
+ } else nextpage = "";
+}
+
+List::~List() {
+ while (!content.empty()) {
+ delete content.back();
+ content.pop_back();
+ }
+}
+
+vector List::cards() {
+ return content;
+}
+
+string List::nextPage() {
+ return nextpage;
+}
+
+string List::json() {
+ StringBuffer buffer;
+ Writer writer(buffer);
+ data.Accept(writer);
+ return buffer.GetString();
+}
diff --git a/src/list.h b/src/list.h
new file mode 100644
index 0000000..3056a3c
--- /dev/null
+++ b/src/list.h
@@ -0,0 +1,36 @@
+//libscry by Sebastien MacDougall-Landry
+//License is available at
+//https://github.com/EmperorPenguin18/libscry/blob/main/LICENSE
+
+#pragma once
+#include
+#include
+#include
+#include
+#include
+#include "card.h"
+
+#ifdef DEBUG
+#include
+#endif
+
+///This class is used to represent a list of cards (returned from a search for example)
+class List {
+ public:
+ List(const char*);
+ List(vector);
+ ~List();
+
+ ///Returns a vector with all the cards on this page of the list. For cards on all pages see allcards().
+ virtual vector cards();
+ ///Returns the string of the url for the next page of a search result
+ virtual string nextPage();
+ ///Gets the raw json text of the card provided by Scryfall.
+ virtual string json();
+ private:
+ Document data;
+ vector content;
+ string nextpage;
+ void construct(const char*);
+};
+
diff --git a/src/scry-private.cc b/src/scry-private.cc
new file mode 100644
index 0000000..f7dcd60
--- /dev/null
+++ b/src/scry-private.cc
@@ -0,0 +1,207 @@
+//libscry by Sebastien MacDougall-Landry
+//License is available at
+//https://github.com/EmperorPenguin18/libscry/blob/main/LICENSE
+
+#include "scry.h"
+
+using namespace std;
+
+vector Scry::explode(const char* str, const char& ch) {
+#ifdef DEBUG
+ cerr << "Exploding: " << str << endl;
+#endif
+ cstring_t next; next.len = 0; next.max = 0; next.str = NULL;
+ vector result;
+ for (size_t i = 0; i < strlen(str); i++) {
+ if (str[i] == ch) {
+ if (next.str) {
+ string_cat(&next, '\0');
+#ifdef DEBUG
+ cerr << "Line " << result.size() << ": " << next.str << endl;
+#endif
+ result.push_back(next.str);
+ next.len = 0; next.max = 0; next.str = NULL;
+ }
+ } else string_cat(&next, str[i]);
+ }
+ if (next.str) result.push_back(next.str);
+ return result;
+}
+
+string Scry::implode(const vector& strs, const char& ch) {
+ string result = "";
+ for (auto it = strs.begin(); it != strs.end(); it++) {
+ result += (*it) + ch;
+ }
+ return result;
+}
+
+void Scry::replace(char* str, char c, const char* s) {
+#ifdef DEBUG
+ cerr << "Replacing: " << str << endl;
+#endif
+ size_t length = strlen(str);
+ for (size_t i = 0; i < length; i++) {
+ if (str[i] == c) {
+ size_t length_s = strlen(s);
+ char temp[length_s] = "";
+ char prev[length_s] = "";
+ strcat(prev, s);
+ length += length_s-1;
+ if (!str) {
+ fprintf(stderr, "not enough memory: realloc returned null");
+ exit(1);
+ }
+ str[i] = prev[0];
+ for (size_t j = i; j < length; j+=length_s-1) {
+ for (size_t k = 1; k < length_s; k++) {
+ temp[k] = str[j+k];
+ str[j+k] = prev[k];
+ prev[k] = temp[k];
+ }
+ }
+ i += length_s-1;
+ }
+ }
+#ifdef DEBUG
+ cerr << "Replaced with: " << str << endl;
+#endif
+}
+
+void Scry::firstupper(char* str) {
+#ifdef DEBUG
+ cerr << "First upper input: " << str << endl;
+#endif
+ for(int i = 0; i < strlen(str); i++) {
+ if (i == 0 || str[i-1] == ' ' || str[i-1] == '-') {
+ if(str[i] >= 'a' && str[i] <= 'z') {
+ str[i] = (char)(('A'-'a') + str[i] );
+ }
+ }
+ }
+#ifdef DEBUG
+ cerr << "First upper output: " << str << endl;
+#endif
+}
+
+char* Scry::urlformat(const string& str) {
+ char* output = (char*)malloc(sizeof(char)*str.length()*3+1);
+ if (!output) {
+ fprintf(stderr, "not enough memory: malloc returned null");
+ exit(1);
+ }
+ strcpy(output, str.c_str());
+ replace(output, ' ', "%20");
+ replace(output, ':', "%3A");
+ replace(output, '<', "%3C");
+ replace(output, '>', "%3E");
+#ifdef DEBUG
+ cerr << "URL formatted to: " << output << endl;
+#endif
+ return output;
+}
+
+char* Scry::nameformat(const string& str) {
+ char* output = (char*)malloc(sizeof(char)*str.length()*2+1);
+ if (!output) {
+ fprintf(stderr, "not enough memory: malloc returned null");
+ exit(1);
+ }
+ strcpy(output, str.c_str());
+ replace(output, '\'', "''");
+ firstupper(output);
+#ifdef DEBUG
+ cerr << "Name formatted to: " << output << endl;
+#endif
+ return output;
+}
+
+char* Scry::cachecard(List* list) {
+ cstring_t output; output.len = 0; output.max = 0; output.str = NULL;
+ vector cards = list->cards();
+ vector names;
+ vector data;
+ for (int i = 0; i < cards.size(); i++) {
+ char* name = nameformat(cards[i]->name());
+ string_cat(&output, name);
+ string_cat(&output, "\n");
+ names.push_back(name);
+ string json = cards[i]->json();
+ char* cstr = new char[json.length()+1];
+ strcpy(cstr, json.c_str());
+ data.push_back(cstr);
+ }
+ da->db_exec("Cards", names, data);
+ output.len--;
+ return output.str;
+}
+
+List * Scry::allcards(List * list) {
+ List * newlist;
+ if (list->nextPage() != "") {
+ Document doc; doc.Parse(list->json().c_str());
+ unsigned int pages = static_cast(
+ ceil(
+ doc["total_cards"].GetInt() / (list->cards().size())
+ )
+ );
+#ifdef DEBUG
+ cerr << "# of pages: " << to_string(pages) << endl;
+#endif
+ vector urls;
+ int i;
+ for (i = 2; i <= pages; i++) urls.push_back(list->nextPage() + to_string(i));
+ vector one; one.push_back(list->json());
+ vector two = wa->api_call(urls);
+ two.insert(two.begin(), one.begin(), one.end());
+ newlist = new List(two);
+ lists.push_back(newlist);
+ while (newlist->nextPage() != "") {
+ string extrapage = wa->api_call(newlist->nextPage() + to_string(i));
+ two.push_back(extrapage);
+ newlist = new List(two);
+ lists.push_back(newlist);
+ i++;
+ }
+ } else newlist = list;
+ return newlist;
+}
+
+void Scry::string_cat(cstring_t* dest, const char* src) {
+ size_t newlen = strlen(src);
+#ifdef DEBUG
+ cerr << "String cat: " << dest->len << ", " << dest->max << ", " << newlen << endl;
+#endif
+ if (dest->max < newlen+dest->len) {
+ dest->max = max(dest->max * 2, newlen+dest->len+1);
+#ifdef DEBUG
+ cerr << "Realloc to size: " << dest->max << endl;
+#endif
+ dest->str = (char*)realloc(dest->str, dest->max);
+ if (!dest->str) {
+ fprintf(stderr, "not enough memory: realloc returned null");
+ exit(1);
+ }
+ }
+ strcpy(dest->str + dest->len, src);
+ dest->len += newlen;
+}
+
+void Scry::string_cat(cstring_t* dest, char c) {
+#ifdef DEBUG
+ cerr << "String cat: " << dest->len << ", " << dest->max << ", 1" << endl;
+#endif
+ if (dest->max < 1+dest->len) {
+ dest->max = max(dest->max * 2, dest->len+2);
+#ifdef DEBUG
+ cerr << "Realloc to size: " << dest->max << endl;
+#endif
+ dest->str = (char*)realloc(dest->str, dest->max);
+ if (!dest->str) {
+ fprintf(stderr, "not enough memory: realloc returned null");
+ exit(1);
+ }
+ }
+ dest->str[dest->len] = c;
+ dest->len += 1;
+}
diff --git a/src/scry-public.cc b/src/scry-public.cc
new file mode 100644
index 0000000..7d59e44
--- /dev/null
+++ b/src/scry-public.cc
@@ -0,0 +1,232 @@
+//libscry by Sebastien MacDougall-Landry
+//License is available at
+//https://github.com/EmperorPenguin18/libscry/blob/main/LICENSE
+
+#include "scry.h"
+
+using namespace std;
+using namespace rapidjson;
+
+extern "C" Scry* create_object() {
+ return new Scry;
+}
+
+extern "C" void destroy_object( Scry* object ) {
+ delete object;
+}
+
+Scry::Scry() {
+#ifdef DEBUG
+ signal(SIGSEGV, print_stacktrace);
+ signal(SIGABRT, print_stacktrace);
+#endif
+ vector temp;
+ temp.push_back("api.scryfall.com");
+ wa = new WebAccess(temp, 50, 20);
+ da = new DataAccess("libscry");
+ da->db_exec("Cards");
+ da->db_exec("Lists");
+ da->db_exec("Autocompletes");
+ da->db_exec("Images");
+}
+
+Scry::~Scry() {
+ delete(wa);
+ delete(da);
+ while (!cards.empty()) {
+ delete cards.back();
+ cards.pop_back();
+ }
+ while (!lists.empty()) {
+ delete lists.back();
+ lists.pop_back();
+ }
+}
+
+#ifdef DEBUG
+void print_stacktrace(int signum) {
+ const char * signals[31] = {"SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP", "SIGABR", "SIGBUS", "SIGFPE", "SIGKILL", "SIGUSR1", "SIGSEGV", "SIGUSR2", "SIGPIPE", "SIGALRM", "SIGTERM", "SIGSTKFLT", "SIGCHLD", "SIGCONT", "SIGSTOP", "SIGTSTP", "SIGTTIN", "SIGTTOU", "SIGURG", "SIGXCPU", "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGIO", "SIGPWR", "SIGUNUSED"};
+ printf("\nReceived signal %d: %s\n", signum, signals[signum-1]);
+ int nptrs;
+ void *buffer[BT_BUF_SIZE];
+ char **strings;
+
+ nptrs = backtrace(buffer, BT_BUF_SIZE);
+ printf("backtrace() returned %d addresses\n", nptrs);
+
+ /* The call backtrace_symbols_fd(buffer, nptrs, STDOUT_FILENO)
+ would produce similar output to the following: */
+
+ strings = backtrace_symbols(buffer, nptrs);
+ if (strings == NULL) {
+ perror("backtrace_symbols");
+ exit(signum);
+ }
+
+ for (int j = 0; j < nptrs; j++)
+ printf("%s\n", strings[j]);
+
+ free(strings);
+ exit(signum);
+}
+#endif
+
+List* Scry::cards_search(string in) {
+ string query = string(urlformat(in));
+ string url = "https://api.scryfall.com/cards/search?q=" + query;
+#ifdef DEBUG
+ cerr << "URL: " << url << endl;
+#endif
+ size_t size = 0;
+ List* list;
+ try {
+ list = new List(wa->api_call(url));
+ } catch(const char* e) {
+ return nullptr;
+ }
+#ifdef DEBUG
+ cerr << "First card: " << list->cards()[0]->name() << endl;
+#endif
+ lists.push_back(list);
+ List* full_list = allcards(list);
+#ifdef DEBUG
+ cerr << "Full first card: " << full_list->cards()[0]->name() << endl;
+#endif
+ return full_list;
+}
+
+List* Scry::cards_search_cache(string in) {
+ char* query = urlformat(in);
+ List* list;
+
+ if (da->datecheck("Lists", query) == 1) {
+ list = cards_search(in);
+ if (!list) return list;
+ char* str = cachecard(list);
+ da->db_exec("Lists", query, (byte*)str, strlen(str));
+ free(str);
+ } else {
+ cstring_t data; data.len = 0; data.max = 0; data.str = NULL;
+ vector names = explode(da->db_exec("Lists", query), '\n');
+ string_cat(&data, "{\"object\":\"list\",\"data\":[");
+ for (size_t i = 0; i < names.size(); i++) {
+ string_cat(&data, da->db_exec("Cards", names[i]));
+ string_cat(&data, ",");
+ }
+ data.len--;
+ string_cat(&data, "],\"has_more\":false}");
+#ifdef DEBUG
+ cerr << "New list (last 50 chars): " << &(data.str[strlen(data.str)-50]) << endl;
+#endif
+ list = new List(data.str);
+ lists.push_back(list);
+ free(data.str);
+ }
+
+ free(query);
+ return list;
+}
+
+Card* Scry::cards_named(string in) {
+ string query = string(urlformat(in));
+ string url = "https://api.scryfall.com/cards/named?fuzzy=" + query;
+ Card* card;
+ try {
+ card = new Card(wa->api_call(url));
+ } catch (const char* e) {
+ return nullptr;
+ }
+ cards.push_back(card);
+ return card;
+}
+
+Card* Scry::cards_named_cache(string in) {
+ Card* card;
+ char* name = nameformat(in);
+
+ if (da->datecheck("Cards", name) == 1) {
+ card = cards_named(in);
+ if (!card) return card;
+ string str = card->json();
+ char* cstr = new char[str.length()+1];
+ strcpy(cstr, str.c_str());
+ da->db_exec("Cards", name, (byte*)cstr, str.length());
+ } else {
+ card = new Card( da->db_exec("Cards", name) );
+ cards.push_back(card);
+ }
+
+ free(name);
+ return card;
+}
+
+byte* Scry::cards_named(string in, size_t *size) {
+ string query = string(urlformat(in));
+ string url = "https://api.scryfall.com/cards/named?fuzzy=" + query + "&format=image&version=border_crop";
+ byte* image = wa->api_call(url, size);
+ return image;
+}
+
+byte* Scry::cards_named_cache(string in, size_t *size) {
+ byte* image;
+ char* name = nameformat(in);
+
+ if (da->datecheck("Images", name) == 1) {
+ image = cards_named(in, size);
+ da->db_exec("Images", name, image, *size);
+ } else {
+ image = da->db_exec("Images", name, size);
+ }
+
+ free(name);
+ return image;
+}
+
+vector Scry::cards_autocomplete(string in) {
+ string query = string(urlformat(in));
+ string url = "https://api.scryfall.com/cards/autocomplete?q=" + query;
+ Document doc;
+ doc.Parse(wa->api_call(url));
+ const Value& a = doc["data"];
+ vector output;
+ for (auto& v : a.GetArray()) output.push_back(v.GetString());
+ return output;
+}
+
+vector Scry::cards_autocomplete_cache(string in) {
+ char* query = urlformat(in);
+ vector names;
+
+ if (da->datecheck("Autocompletes", query) == 1) {
+ names = cards_autocomplete(in);
+ string namestr = implode(names, '\n');
+ const char* str = namestr.c_str();
+ da->db_exec("Autocompletes", query, (byte*)str, strlen(str));
+ } else {
+ vector rawnames = explode(da->db_exec("Autocompletes", query), '\n');
+ for (size_t i = 0; i < rawnames.size(); i++) names.push_back(string(rawnames[i]));
+ }
+
+ free(query);
+ return names;
+}
+
+Card* Scry::cards_random() {
+ string url = "https://api.scryfall.com/cards/random";
+ Card* card = new Card(wa->api_call(url));
+ cards.push_back(card);
+ return card;
+}
+
+vector Scry::split(Card* card) {
+ Document doc; doc.Parse(card->json().c_str());
+ vector output;
+ for (int i = 0; i < doc["card_faces"].Size(); i++) {
+ StringBuffer buffer;
+ Writer writer(buffer);
+ doc["card_faces"][i].Accept(writer);
+ Card * card = new Card(buffer.GetString());
+ output.push_back(card); cards.push_back(card);
+ }
+ return output;
+}
diff --git a/src/scry.h b/src/scry.h
new file mode 100644
index 0000000..a20b9f8
--- /dev/null
+++ b/src/scry.h
@@ -0,0 +1,63 @@
+//libscry by Sebastien MacDougall-Landry
+//License is available at
+//https://github.com/EmperorPenguin18/libscry/blob/main/LICENSE
+
+#pragma once
+#include
+#include "web.h"
+#include "data.h"
+#include "list.h"
+#include "card.h"
+
+#ifdef DEBUG
+ #include
+ #include
+ #define BT_BUF_SIZE 100
+ void print_stacktrace(int);
+#endif
+
+using namespace std;
+
+///This class is the main interface to Scryfall. It handles all the API requests and caching.
+class Scry {
+ public:
+ Scry();
+ ~Scry();
+
+ ///Put in a query using Scryfall syntax (commander:g+type:legendary) as the argument.
+ virtual List* cards_search(string);
+ ///Cached version of cards_search.
+ virtual List* cards_search_cache(string);
+ ///Put in the name of a card as the argument.
+ virtual Card* cards_named(string);
+ ///Cached version of cards_named.
+ virtual Card* cards_named_cache(string);
+ ///Put in the name of a card and get its image
+ virtual byte* cards_named(string, size_t*);
+ ///Cached version of cards_named (image)
+ virtual byte* cards_named_cache(string, size_t*);
+ ///Put in part of a card's name as the argument.
+ virtual vector cards_autocomplete(string);
+ ///Cached version of cards_autocomplete.
+ virtual vector cards_autocomplete_cache(string);
+ ///Returns a randomly selected card.
+ virtual Card* cards_random();
+ ///Splits a card into it's multiple faces. Useful because some cards won't have certain data in the conglomerate.
+ virtual vector split(Card*);
+ private:
+ typedef struct { size_t len, max; char* str; } cstring_t;
+ WebAccess* wa;
+ DataAccess* da;
+ vector cards;
+ vector lists;
+ virtual vector explode(const char*, const char&);
+ virtual string implode(const vector&, const char&);
+ virtual void firstupper(char*);
+ virtual char* urlformat(const string&);
+ virtual char* nameformat(const string&);
+ virtual char* cachecard(List*);
+ virtual List* allcards(List*);
+ virtual void string_cat(cstring_t*, const char*);
+ virtual void string_cat(cstring_t*, char);
+ virtual void replace(char*, char, const char*);
+};
diff --git a/src/web.cc b/src/web.cc
new file mode 100644
index 0000000..a131501
--- /dev/null
+++ b/src/web.cc
@@ -0,0 +1,262 @@
+//libscry by Sebastien MacDougall-Landry
+//License is available at
+//https://github.com/EmperorPenguin18/libscry/blob/main/LICENSE
+
+#include "web.h"
+
+using namespace std;
+using namespace std::chrono;
+
+WebAccess::WebAccess() {
+ delay = duration>(50);
+ conn_per_thread = 10;
+ construct();
+}
+
+WebAccess::WebAccess(vector approved_urls) : approved_urls(approved_urls) {
+ delay = duration>(50);
+ conn_per_thread = 10;
+ construct();
+}
+
+WebAccess::WebAccess(vector approved_urls, long delay) : approved_urls(approved_urls), delay(delay) {
+ conn_per_thread = 10;
+ construct();
+}
+
+WebAccess::WebAccess(vector approved_urls, long delay, size_t conn_per_thread) : approved_urls(approved_urls), delay(delay), conn_per_thread(conn_per_thread) {
+ construct();
+}
+
+WebAccess::WebAccess(long delay) : delay(delay) {
+ conn_per_thread = 10;
+ construct();
+}
+
+WebAccess::WebAccess(long delay, size_t conn_per_thread) : delay(delay), conn_per_thread(conn_per_thread) {
+ construct();
+}
+
+void WebAccess::construct() {
+ curl_lib = dlopen("libcurl.so", RTLD_LAZY | RTLD_DEEPBIND);
+ if (!curl_lib) {
+ fprintf(stderr, "%s\n", dlerror());
+ exit(EXIT_FAILURE);
+ }
+ curl_global_init = reinterpret_cast(dlsym(curl_lib, "curl_global_init"));
+ curl_easy_init = reinterpret_cast(dlsym(curl_lib, "curl_easy_init"));
+ curl_easy_setopt = reinterpret_cast(dlsym(curl_lib, "curl_easy_setopt"));
+ curl_easy_perform = reinterpret_cast(dlsym(curl_lib, "curl_easy_perform"));
+ curl_easy_cleanup = reinterpret_cast(dlsym(curl_lib, "curl_easy_cleanup"));
+ curl_global_cleanup = reinterpret_cast(dlsym(curl_lib, "curl_global_cleanup"));
+ curl_easy_strerror = reinterpret_cast(dlsym(curl_lib, "curl_easy_strerror"));
+ curl_multi_add_handle = reinterpret_cast(dlsym(curl_lib, "curl_multi_add_handle"));
+ curl_multi_init = reinterpret_cast(dlsym(curl_lib, "curl_multi_init"));
+ curl_multi_setopt = reinterpret_cast(dlsym(curl_lib, "curl_multi_setopt"));
+ curl_multi_perform = reinterpret_cast(dlsym(curl_lib, "curl_multi_perform"));
+ curl_multi_info_read = reinterpret_cast(dlsym(curl_lib, "curl_multi_info_read"));
+ curl_easy_getinfo = reinterpret_cast(dlsym(curl_lib, "curl_easy_getinfo"));
+ curl_multi_remove_handle = reinterpret_cast(dlsym(curl_lib, "curl_multi_remove_handle"));
+ curl_multi_wait = reinterpret_cast(dlsym(curl_lib, "curl_multi_wait"));
+ curl_multi_cleanup = reinterpret_cast(dlsym(curl_lib, "curl_multi_cleanup"));
+
+ curl_global_init(CURL_GLOBAL_ALL);
+}
+
+WebAccess::~WebAccess() {
+ curl_global_cleanup();
+ dlclose(curl_lib);
+}
+
+size_t WebAccess::cb(void *data, size_t size, size_t nmemb, void *userp) {
+ size_t realsize = size * nmemb;
+ struct memory *mem = (struct memory *)userp;
+ byte *ptr = (byte *)realloc(mem->response, *(mem->size) + realsize + 1);
+ if (!ptr) {
+ /* out of memory */
+ fprintf(stderr, "not enough memory (realloc returned NULL)\n");
+ return 0;
+ }
+ mem->response = ptr;
+ memcpy(&(mem->response[*(mem->size)]), data, realsize);
+ *(mem->size) += realsize;
+ //mem->response[mem->size] = (byte)0;
+ return realsize;
+}
+
+CURL * WebAccess::add_transfer(string url, struct memory* chunk, int num) {
+#ifdef DEBUG
+ cerr << "Adding url: " << url << endl;
+#endif
+ *(chunk->size) = 0;
+ CURL *eh = curl_easy_init();
+ curl_easy_setopt(eh, CURLOPT_FOLLOWLOCATION, 1);
+ curl_easy_setopt(eh, CURLOPT_HTTPGET, 1);
+ curl_easy_setopt(eh, CURLOPT_USERAGENT, "libcurl-agent/1.0");
+ curl_easy_setopt(eh, CURLOPT_WRITEFUNCTION, cb);
+ curl_easy_setopt(eh, CURLOPT_WRITEDATA, (void *)chunk);
+ curl_easy_setopt(eh, CURLOPT_URL, url.c_str());
+ curl_easy_setopt(eh, CURLOPT_PRIVATE, num);
+ return eh;
+}
+
+void WebAccess::checkurl(string url) {
+ regex first(".*//");
+ regex last("/.*");
+ url = regex_replace(url, first, "");
+ url = regex_replace(url, last, "");
+ if ( std::find(approved_urls.begin(), approved_urls.end(), url) == approved_urls.end() ) {
+ fprintf(stderr, "Attempted to use unapproved url: %s\n", url.c_str());
+ exit(EXIT_FAILURE);
+ }
+}
+
+byte* WebAccess::api_call(string url, size_t* size) {
+ checkurl(url);
+
+ struct memory chunk = {0};
+ chunk.size = size;
+ CURL *eh = add_transfer(url, &chunk, 0);
+
+ CURLcode res = curl_easy_perform(eh);
+ if (res != CURLE_OK) {
+ fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
+ exit(res);
+ }
+#ifdef DEBUG
+ cerr << "First 250 chars of response: ";
+ for (size_t i = 0; i < min((size_t)250, *(chunk.size)); i++) cerr << (char)chunk.response[i];
+ cerr << endl;
+#endif
+
+ curl_easy_cleanup(eh);
+ return chunk.response;
+}
+
+char* WebAccess::api_call(string url) {
+ size_t size = 0;
+ char* output = (char*)api_call(url, &size);
+ output[size] = '\0';
+ return output;
+}
+
+vector WebAccess::start_multi(vector urls) {
+#ifdef DEBUG
+ mtx.lock();
+ cerr << "All urls: " << endl;
+ for (int i = 0; i < urls.size(); i++) cerr << urls[i] << endl;
+ mtx.unlock();
+#endif
+ CURLM *cm;
+ CURLMsg *msg;
+ unsigned int transfers = 0;
+ int msgs_left = -1;
+ int still_alive = 1;
+ unsigned int conns = min(urls.size(), conn_per_thread);
+ vector output(conns);
+ struct memory chunks[conns];
+ size_t sizes[conns];
+
+ cm = curl_multi_init();
+ curl_multi_setopt(cm, CURLMOPT_MAXCONNECTS, conn_per_thread);
+
+ for (transfers = 0; transfers < conns; transfers++) {
+ checkurl(urls[transfers]);
+ chunks[transfers] = {0};
+ sizes[transfers] = 0;
+ chunks[transfers].size = &sizes[transfers];
+ curl_multi_add_handle(cm, add_transfer(urls[transfers], &chunks[transfers], transfers));
+ }
+#ifdef DEBUG
+ mtx.lock();
+ cerr << "Handles added" << endl;
+ mtx.unlock();
+#endif
+
+ do {
+ duration> time_span = duration_cast>>(steady_clock::now() - prev_time);
+ if (time_span > delay) {
+ curl_multi_perform(cm, &still_alive);
+ mtx.lock();
+ prev_time = steady_clock::now();
+ mtx.unlock();
+ }
+
+ while ((msg = curl_multi_info_read(cm, &msgs_left))) {
+#ifdef DEBUG
+ mtx.lock();
+ cerr << "Msg ready" << endl;
+ mtx.unlock();
+#endif
+ if (msg->msg == CURLMSG_DONE) {
+ int num;
+ CURL *e = msg->easy_handle;
+ curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &num);
+#ifdef DEBUG
+ mtx.lock();
+ cerr << "Transfer num: " << to_string(num) << endl;
+ mtx.unlock();
+#endif
+ chunks[num].response[*(chunks[num].size)] = (byte)'\0';
+ output[num].reserve(strlen((char*)chunks[num].response));
+ output[num].assign((char*)chunks[num].response);
+#ifdef DEBUG
+ mtx.lock();
+ cerr << "First 250 chars of response: " << output[num].substr(0, 250) << endl;
+ mtx.unlock();
+#endif
+ curl_multi_remove_handle(cm, e);
+ curl_easy_cleanup(e);
+ }
+ else {
+ fprintf(stderr, "E: CURLMsg (%d)\n", msg->msg);
+ }
+ }
+ if (still_alive)
+ curl_multi_wait(cm, NULL, 0, 1000, NULL);
+
+ } while (still_alive || (transfers < urls.size()));
+
+ curl_multi_cleanup(cm);
+ return output;
+}
+
+vector WebAccess::api_call(vector urls) {
+ unsigned available_threads = thread::hardware_concurrency()-1; //Not actual max, but keeps things reasonable
+ unsigned used_threads = static_cast(ceil(
+ static_cast(urls.size()) / static_cast(conn_per_thread)
+ ));
+#ifdef DEBUG
+ cerr << "Available threads: " << to_string(available_threads) << endl
+ << "Used threads: " << to_string(used_threads) << endl;
+#endif
+ if ( (available_threads > 0) && (available_threads >= used_threads) ) {
+ vector output;
+ vector>> threads(used_threads);
+ for (int i = 0; i < used_threads; i++) {
+ vector data( min(conn_per_thread, urls.size()-i*conn_per_thread) );
+ for (int j = 0; j < data.size(); j++) data.at(j) = urls.at(i*conn_per_thread+j);
+#ifdef DEBUG
+ mtx.lock();
+ cerr << "Thread " << to_string(i) << " is getting: " << endl;
+ for (int j = 0; j < data.size(); j++) cerr << data.at(j) << endl;
+ mtx.unlock();
+#endif
+ threads.at(i) = async(&WebAccess::start_multi, this, data);
+ }
+ for (int i = 0; i < used_threads; i++) {
+ vector newdata = threads[i].get();
+ for (int j = 0; j < newdata.size(); j++) {
+#ifdef DEBUG
+ mtx.lock();
+ cerr << "Adding to output: " << newdata[j].substr(0, 10) << endl;
+ mtx.unlock();
+#endif
+ output.push_back(newdata[j]);
+ }
+ }
+ return output;
+ }
+ start_multi(urls);
+ return urls;
+}
diff --git a/src/web.h b/src/web.h
new file mode 100644
index 0000000..cefb984
--- /dev/null
+++ b/src/web.h
@@ -0,0 +1,92 @@
+//libscry by Sebastien MacDougall-Landry
+//License is available at
+//https://github.com/EmperorPenguin18/libscry/blob/main/LICENSE
+
+#pragma once
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef DEBUG
+#include
+#endif
+
+using namespace std;
+using namespace std::chrono;
+
+class WebAccess {
+ public:
+ WebAccess();
+ WebAccess(vector);
+ WebAccess(vector, long);
+ WebAccess(vector, long, size_t);
+ WebAccess(long);
+ WebAccess(long, size_t);
+ ~WebAccess();
+
+ virtual byte* api_call(string, size_t*);
+ virtual char* api_call(string);
+ virtual vector api_call(vector);
+ private:
+ void construct();
+ void* curl_lib;
+ typedef CURL* (*cgi_handle)(int);
+ cgi_handle curl_global_init;
+ typedef CURL* (*cei_handle)();
+ cei_handle curl_easy_init;
+ typedef CURLcode (*ces_handle)(CURL*, CURLoption, ...);
+ ces_handle curl_easy_setopt;
+ typedef CURLcode (*cep_handle)(CURL*);
+ cep_handle curl_easy_perform;
+ typedef void (*cec_handle)(CURL*);
+ cec_handle curl_easy_cleanup;
+ typedef void (*cgc_handle)();
+ cgc_handle curl_global_cleanup;
+ typedef const char* (*cee_handle)(CURLcode);
+ cee_handle curl_easy_strerror;
+ typedef CURLMcode (*cma_handle)(CURLM*, CURL*);
+ cma_handle curl_multi_add_handle;
+ typedef CURLM* (*cmi_handle)();
+ cmi_handle curl_multi_init;
+ typedef CURLMcode (*cms_handle)(CURLM*, CURLMoption, ...);
+ cms_handle curl_multi_setopt;
+ typedef CURLMcode (*cmp_handle)(CURLM*, int*);
+ cmp_handle curl_multi_perform;
+ typedef CURLMsg* (*cmn_handle)(CURLM*, int*);
+ cmn_handle curl_multi_info_read;
+ typedef CURLcode (*ceg_handle)(CURL*, CURLINFO, ...);
+ ceg_handle curl_easy_getinfo;
+ typedef CURLMcode (*cmr_handle)(CURLM*, CURL*);
+ cmr_handle curl_multi_remove_handle;
+ typedef CURLMcode (*cmw_handle)(CURLM*, struct curl_waitfd[], unsigned int, int, int*);
+ cmw_handle curl_multi_wait;
+ typedef CURLMcode (*cmc_handle)(CURLM*);
+ cmc_handle curl_multi_cleanup;
+
+ static size_t cb(void*, size_t, size_t, void*);
+ struct memory {
+ byte* response;
+ size_t* size;
+ };
+
+ vector approved_urls;
+ void checkurl(string);
+
+ duration> delay;
+ steady_clock::time_point prev_time;
+ size_t conn_per_thread;
+ CURL* add_transfer(string, struct memory*, int);
+ mutex mtx;
+ vector start_multi(vector);
+};