Browse Source

Added C++ version using RapidJSON and CURL

It runs many times faster, though admittedly was more painful to
write.
I made a C++ version because I might want to have a full SFML
graphical frontend.
example-sentences
Macoy Madson 7 months ago
parent
commit
0e19718623
9 changed files with 387 additions and 0 deletions
  1. +22
    -0
      .clang-format
  2. +2
    -0
      .gitignore
  3. +6
    -0
      .gitmodules
  4. +10
    -0
      BuildDependencies_Debug.sh
  5. +1
    -0
      Dependencies/curl
  6. +1
    -0
      Dependencies/rapidjson
  7. +4
    -0
      Jamfile
  8. +105
    -0
      Jamrules
  9. +236
    -0
      src/Main.cpp

+ 22
- 0
.clang-format View File

@@ -0,0 +1,22 @@
# http://releases.llvm.org/6.0.0/tools/clang/docs/ClangFormatStyleOptions.html
BasedOnStyle: Google
AccessModifierOffset: -4
AllowShortBlocksOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
BreakBeforeBraces: Allman
BraceWrapping:
AfterNamespace: false
BreakBeforeTernaryOperators: false
ColumnLimit: 100
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
IndentWidth: 4
Standard: Cpp11
TabWidth: 4
UseTab: ForIndentation
DerivePointerAlignment: false
PointerAlignment: Left
NamespaceIndentation: None
IndentCaseLabels: true

+ 2
- 0
.gitignore View File

@@ -127,3 +127,5 @@ dmypy.json

# Pyre type checker
.pyre/

*.o

+ 6
- 0
.gitmodules View File

@@ -0,0 +1,6 @@
[submodule "Dependencies/curl"]
path = Dependencies/curl
url = https://github.com/curl/curl
[submodule "Dependencies/rapidjson"]
path = Dependencies/rapidjson
url = https://github.com/Tencent/rapidjson

+ 10
- 0
BuildDependencies_Debug.sh View File

@@ -0,0 +1,10 @@
#!/bin/sh

echo "Building curl..."
cd Dependencies/curl
./buildconf
curl_dir=$(echo $PWD/local_install)
./configure --prefix=$curl_dir # --enable-debug
make -j4
make install
cd ../../

+ 1
- 0
Dependencies/curl

@@ -0,0 +1 @@
Subproject commit 1369b8ad31d5fff44af1a0f2b5fa8b24b4c24b09

+ 1
- 0
Dependencies/rapidjson

@@ -0,0 +1 @@
Subproject commit 563fe5bbbea3505c930f233955bb420b799e86da

+ 4
- 0
Jamfile View File

@@ -0,0 +1,4 @@
SubDir . ;

Main japanese_for_me : src/Main.cpp
;

+ 105
- 0
Jamrules View File

@@ -0,0 +1,105 @@
##
## Compilation
##

C++ = clang++ ;
LINK = clang++ ;
# C++ = g++ ;
# LINK = g++ ;

# If I was building a library, these would be useful
# LINKFLAGS = -shared ;
# if $(UNIX) { SUFSHR = .so ; }
# else if $(NT) { SUFSHR = .dll ; }

## Compiler arguments

# Arguments used on all projects, regardless of any variables
C++FLAGS = -std=c++11 -Wall -Wextra -Wno-unused-parameter
# Only for profiling, i.e. not release builds
# -DTRACY_ENABLE
# BT_USE_DOUBLE_PRECISION solves the Dantzig LCP missing definition
# Disabled now that I'm compiling Bullet in single-precision
# -DBT_USE_DOUBLE_PRECISION
-g ;

HDRS = src
# Dependencies/base2.0
# Dependencies/glm
# Dependencies/tracy
Dependencies/curl/include
Dependencies/rapidjson/include
;

# TODO: Make base hold all this weirdness?
# if $(DEBUG_BUILD)
# {
# SFML_LINKLIBS = -lsfml-audio-d -lsfml-graphics-d -lsfml-window-d -lsfml-system-d ;
# }
# else
# {
# SFML_LINKLIBS = -lsfml-audio -lsfml-graphics -lsfml-window -lsfml-system ;
# }

OPTIM = -O0 ;

##
## Linking
##

LINKLIBS =
# Standard (e.g. for Tracy)
-lpthread -ldl

# SFML
# -LDependencies/base2.0/dependencies/SFML/lib
# $(SFML_LINKLIBS)

# OpenGL
# -lGL
# -lGLU
# -lGLEW

# Base
# -LDependencies/base2.0 -lBase20

# curl
-LDependencies/curl/local_install/lib
-lcurl
;

# LINKFLAGS = -Wl,-rpath,. ;

# TODO: Copy libs to better directory, or static link?
LINKFLAGS = -g
-Wl,-rpath,.:Dependencies/curl/local_install/lib ;

##
## Jam stuff
##

# Fix for unnecessary rebuilding any Jam project
KEEPOBJS = true ; # This doesn't actually fix anything, though it seems like it should
NOARSCAN = true ; # This actually fixes the problem
#AR = ar rUu ; # I was thinking maybe the AR command was wrong (always outputting deterministically)
# It doesn't seem like this is the problem though
AR = ar cr ;

# Cross compilation
# E.g.
# jam -j4 -q -sCROSS_COMPILE_WINDOWS=true
# if $(CROSS_COMPILE_WINDOWS)
# {
# CC = x86_64-w64-mingw32-gcc ;
# LINK = x86_64-w64-mingw32-gcc ;
# AR = x86_64-w64-mingw32-ar ;
# SUFSHR = .dll ;
# }

# Some helpful Jam commands
# -q : stop on failed target
# -jN : use N cores
# -sVAR=VAL : Set VAR to VAL. Note that setting WINDOWS=false is the same as setting UNREAL=true,
# frustratingly
# -dx : print commands being used
# -n : don't actually run commands

+ 236
- 0
src/Main.cpp View File

@@ -0,0 +1,236 @@
#include <assert.h>
#include <string.h>
#include <iostream>
#include <sstream>

#include "curl/curl.h"
#include "rapidjson/document.h"
#include "rapidjson/writer.h"

static const char* ankiConnectURL = "http://localhost:8765";

CURL* curl_handle = nullptr;
bool curlVerbose = false;

// I hate auto
typedef rapidjson::GenericObject<
true, rapidjson::GenericValue<rapidjson::UTF8<char>,
rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>>>
RapidJsonObject;

// Note that this can be called many times for a single request, if the packets are split
static size_t CurlReceive(char* ptr, size_t size, size_t nmemb, void* userdata)
{
std::string* stringOut = static_cast<std::string*>(userdata);
stringOut->append(ptr);
return (size_t)(size * nmemb);
}

std::string ankiConnectRequest(CURL* curl_handle, const char* jsonRequest)
{
std::string receivedString = "";

std::cout << "Request: '" << jsonRequest << "'\n";

curl_easy_setopt(curl_handle, CURLOPT_URL, ankiConnectURL);
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, CurlReceive);
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void*)&receivedString);
curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "Japanese-for-me/1.0");
if (curlVerbose)
curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L);
// curl_easy_setopt(curl_handle, CURLOPT_RETURNTRANSFER, true);

curl_slist* httpHeaders = nullptr;
httpHeaders = curl_slist_append(httpHeaders, "Expect:");
httpHeaders = curl_slist_append(httpHeaders, "Content-Type: application/json");
httpHeaders = curl_slist_append(httpHeaders, "Accept: text/json");
httpHeaders = curl_slist_append(httpHeaders, "charset: utf-8");
curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, httpHeaders);

curl_easy_setopt(curl_handle, CURLOPT_POST, 1L);
curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDSIZE, strlen(jsonRequest));
curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, jsonRequest);

CURLcode resultCode = curl_easy_perform(curl_handle);
if (resultCode == CURLE_OK)
{
char* contentType;
resultCode = curl_easy_getinfo(curl_handle, CURLINFO_CONTENT_TYPE, &contentType);
if (resultCode == CURLE_OK && contentType)
std::cout << "Received type " << contentType << "\n";

std::cout << receivedString.size() << " characters received\n";
// std::cout << receivedString << "\n";
}
else
{
std::cerr << "Error: " << curl_easy_strerror(resultCode) << "\n";
}

return receivedString;
}

void listDecks()
{
const char* jsonRequest = "{\"action\": \"deckNames\", \"version\": 6}";
std::string result = ankiConnectRequest(curl_handle, jsonRequest);
if (result.empty())
return;
rapidjson::Document jsonResult;
jsonResult.Parse(result.c_str());
const rapidjson::Value& resultValue = jsonResult["result"];
assert(resultValue.IsArray());
for (rapidjson::SizeType i = 0; i < resultValue.Size(); ++i)
std::cout << "[" << i << "] " << resultValue[i].GetString() << "\n";
}

void listDueCards(rapidjson::Document& jsonResult)
{
// Build the request
rapidjson::StringBuffer jsonString;
rapidjson::Writer<rapidjson::StringBuffer> writer(jsonString);
writer.StartObject();
writer.Key("action");
writer.String("findCards");
writer.Key("version");
writer.Int(6);

writer.Key("params");
{
writer.StartObject();
writer.Key("query");
std::ostringstream query;
query << "is:due";
writer.String(query.str().c_str());
writer.EndObject();
}
writer.EndObject();

const char* jsonRequest = jsonString.GetString();
std::string result = ankiConnectRequest(curl_handle, jsonRequest);
if (result.empty())
return;
jsonResult.Parse(result.c_str());
const rapidjson::Value& resultValue = jsonResult["result"];
assert(resultValue.IsArray());
for (rapidjson::SizeType i = 0; i < resultValue.Size(); ++i)
std::cout << "[" << i << "] " << resultValue[i].GetInt64() << "\n";
}

void getCardInfo(rapidjson::Document& jsonResult, const rapidjson::Value& cardsIds)
{
rapidjson::StringBuffer jsonString;
rapidjson::Writer<rapidjson::StringBuffer> writer(jsonString);
writer.StartObject();
writer.Key("action");
writer.String("cardsInfo");
writer.Key("version");
writer.Int(6);

writer.Key("params");
{
writer.StartObject();
writer.Key("cards");
writer.StartArray();
for (rapidjson::SizeType i = 0; i < cardsIds.Size(); ++i)
writer.Int64(cardsIds[i].GetInt64());
writer.EndArray();
writer.EndObject();
}
writer.EndObject();

const char* jsonRequest = jsonString.GetString();
// std::cout << jsonRequest << "\n";
std::string result = ankiConnectRequest(curl_handle, jsonRequest);
if (result.empty())
return;

jsonResult.Parse(result.c_str());
}

void printObjectMembers(const rapidjson::Value& objectValue)
{
static const char* kTypeNames[] = {"Null", "False", "True", "Object",
"Array", "String", "Number"};
for (rapidjson::Value::ConstMemberIterator itr = objectValue.MemberBegin();
itr != objectValue.MemberEnd(); ++itr)
{
std::cout << itr->name.GetString() << " is " << kTypeNames[itr->value.GetType()] << "\n";
}
}

int main()
{
std::cout << "Japanese For Me\n";

curl_global_init(CURL_GLOBAL_ALL);
curl_handle = curl_easy_init();

listDecks();
rapidjson::Document dueCardIds;
listDueCards(dueCardIds);
if (dueCardIds.HasMember("result"))
{
rapidjson::Document dueCards;
getCardInfo(dueCards, dueCardIds["result"]);

assert(dueCards.HasMember("result"));
const rapidjson::Value& resultValue = dueCards["result"];
assert(resultValue.IsArray());
for (rapidjson::SizeType i = 0; i < resultValue.Size(); ++i)
{
const RapidJsonObject& currentCard = resultValue[i].GetObject();
int fieldOrder = currentCard["fieldOrder"].GetInt();

// Get quiz word
assert(currentCard["fields"].IsObject());
const rapidjson::Value& fieldsValue = currentCard["fields"];
assert(fieldsValue.IsObject());
std::string quizWord = "";
// Only "A Frequency Dictionary of Japanese Words" has this
if (fieldsValue.HasMember("Lemma"))
{
if (fieldOrder == 1)
{
// English -> Japanese
quizWord = fieldsValue["English Gloss"]["value"].GetString();
}
else
{
// Japanese -> English
quizWord = fieldsValue["Lemma"]["value"].GetString();
}
}
else if (fieldsValue.HasMember("Front"))
{
if (fieldOrder == 1)
quizWord = fieldsValue["Front"]["value"].GetString();
else
quizWord = fieldsValue["Back"]["value"].GetString();
}
else
{
std::cerr << "Card has unrecognized fields\n";
}

// const rapidjson::Value& lemmaValue = currentCard["fields"]["Lemma"];
// assert(lemmaValue.IsObject());
// std::string lemmaString = resultValue[i]
// .GetObject()["fields"]
// .GetObject()["Lemma"]
// .GetObject()["value"]
// .GetString();

std::cout << "[" << i << "] " << currentCard["note"].GetInt64() << "\n"
<< "\tField order: " << fieldOrder << "\n"
<< "\tModel: " << currentCard["modelName"].GetString() << "\n"
<< "\tInterval: " << currentCard["interval"].GetInt64() << "\n"
<< "\tQuiz word: " << quizWord << "\n"
<< "\n\n";
}
}

curl_easy_cleanup(curl_handle);
curl_global_cleanup();
return 0;
}

Loading…
Cancel
Save