Browse Source

Added system notifications and block notifications

* Only libnotify (Linux) notifications are supported so far
* A notification will be triggered every N minutes to study a block
master
Macoy Madson 7 months ago
parent
commit
39bcf2e6c8
8 changed files with 162 additions and 29 deletions
  1. +3
    -0
      Build_WithLibNotify_Debug.sh
  2. +8
    -1
      Jamfile
  3. +23
    -4
      Jamrules
  4. +11
    -0
      ReadMe.org
  5. +75
    -24
      src/Main.cpp
  6. +10
    -0
      src/Notifications.hpp
  7. +20
    -0
      src/Notifications_LibNotify.cpp
  8. +12
    -0
      src/Notifications_Stub.cpp

+ 3
- 0
Build_WithLibNotify_Debug.sh View File

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

jam -j4 -sDEBUG_BUILD=true -sLIBNOTIFY_AVAILABLE=true

+ 8
- 1
Jamfile View File

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

Main japanese_for_me : src/Main.cpp
;
;

LinkLibraries japanese_for_me : libJFMNotify ;

Library libJFMNotify : $(NOTIFY_IMPLEMENTATION_FILES) ;
# Library libJFMNotify : src/Notifications_Stub.cpp ;

ObjectHdrs src/Notifications_LibNotify.cpp : $(NOTIFY_HEADERS) ;

+ 23
- 4
Jamrules View File

@@ -47,10 +47,33 @@ OPTIM = -O0 ;
## Linking
##

if $(LIBNOTIFY_AVAILABLE)
{
# From pkg-config --libs libnotify
NOTIFY_LINKLIBS = -lnotify -lgdk_pixbuf-2.0 -lgio-2.0 -lgobject-2.0 -lglib-2.0 ;
# From `pkg-config --cflags libnotify`
NOTIFY_HEADERS = /usr/include/gdk-pixbuf-2.0 /usr/include/libpng16
/usr/include/glib-2.0 /usr/lib/x86_64-linux-gnu/glib-2.0/include ;

NOTIFY_IMPLEMENTATION_FILES = src/Notifications_LibNotify.cpp ;
}
else
{
NOTIFY_LINKLIBS = ;
NOTIFY_HEADERS = ;
NOTIFY_IMPLEMENTATION_FILES = src/Notifications_Stub.cpp ;
}

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

$(NOTIFY_LINKLIBS)

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

# SFML
# -LDependencies/base2.0/dependencies/SFML/lib
# $(SFML_LINKLIBS)
@@ -62,10 +85,6 @@ LINKLIBS =

# Base
# -LDependencies/base2.0 -lBase20

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

# LINKFLAGS = -Wl,-rpath,. ;


+ 11
- 0
ReadMe.org View File

@@ -15,6 +15,12 @@ Jam is used to build Japanese for Me. You will need a C++ toolchain (clang or gc
#+BEGIN_SRC sh
sudo apt install jam
#+END_SRC

*** For system notifications
Install LibNotify development headers:
#+BEGIN_SRC sh
sudo apt install libnotify-dev
#+END_SRC
** Prepare Anki
Anki is used to manage spaced repitition.

@@ -30,6 +36,11 @@ Build Japanese for Me:
#+BEGIN_SRC sh
./Build_Debug.sh
#+END_SRC

If you want system-wide notifications, run this instead:
#+BEGIN_SRC sh
./Build_WithLibNotify_Debug.sh
#+END_SRC
* License
The repository itself is under the MIT license.



+ 75
- 24
src/Main.cpp View File

@@ -11,6 +11,8 @@
#include "rapidjson/document.h"
#include "rapidjson/writer.h"

#include "Notifications.hpp"

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

// Assumptions made
@@ -19,9 +21,22 @@ static const char* ankiConnectURL = "http://localhost:8765";
// - Advanced cards use field "Lemma" for the thing in the language you're learning and "English
// Gloss" for the first language field

//
// Study settings
//

// 7PM is when I want to be done studying to focus on winding down
int hourStudyTimeEnds = 7 + 12;

// I find I'm most happy with studying in 2-minute bursts. On average, I get about ten cards done.
// This setting will determine how often I will be prompted to study in a burst of 10.
int numCardsInStudyBlock = 10;
// Never remind to study more than once every ten minutes (else, remind at optimal rate)
float reasonableStudyIntervalSeconds = 60.f * 10.f;

//
// Curl configuration
//
CURL* curl_handle = nullptr;
bool curlVerbose = false;
bool curlRequestVerbose = false;
@@ -104,12 +119,20 @@ void listDecks()
std::cout << "[" << i << "] " << resultValue[i].GetString() << "\n";
}

void syncAnkiToAnkiWeb()
bool syncAnkiToAnkiWeb()
{
std::cout << "Synchronizing Collection..." << std::flush;
const char* jsonRequest = "{\"action\": \"sync\", \"version\": 6}";
std::string result = ankiConnectRequest(curl_handle, jsonRequest);
// if (result.empty())
// return;
std::cout << "done\n";

if (result.empty())
{
// Likely a failed connection
return false;
}

return true;
}

void getDueCards(rapidjson::Document& jsonResult)
@@ -250,16 +273,25 @@ int main()
std::cout << "Japanese For Me\nA vocabulary learning app by Macoy Madson.\n\n";

std::chrono::steady_clock::time_point programStartTime = std::chrono::steady_clock::now();
std::cout << "Syncing to AnkiWeb...";
curl_global_init(CURL_GLOBAL_ALL);
curl_handle = curl_easy_init();

NotificationsHandler notifications;

// Make sure we are working with up-to-date data
syncAnkiToAnkiWeb();
std::cout << "done\n";
if (syncAnkiToAnkiWeb())
notifications.sendNotification("Collection synced");
else
{
notifications.sendNotification(
"Failed to sync Collection. Please make sure Anki is running, and AnkiConnect is "
"installed.");
return 1;
}

// listDecks();

rapidjson::Document dueCardIds;
getDueCards(dueCardIds);
if (dueCardIds.HasMember("result"))
@@ -274,6 +306,10 @@ int main()
int numCards = static_cast<int>(dueCardsArray.Size());
if (numCards)
{
std::ostringstream outStr;
outStr << numCards << " remaining";
notifications.sendNotification(outStr.str().c_str());

// The time math is gonna be a bit loosey goosey here, but that's fine in our case
std::time_t currentTime;
std::tm* currentTimeInfo;
@@ -305,8 +341,11 @@ int main()
static_cast<float>(numCards))
<< " cards per hour\n";

// TODO: Eventually this needs to update after syncing throughout the day
float timeToNextCard = std::max(0.1f, secondsTimeLeft / (numCards * 1.2f));

// Drip feed cards over the maximal time
for (int i = 0; i < std::min(5, numCards); ++i)
for (int i = 0; i < numCards; ++i)
{
// Present card
const RapidJsonObject& currentCard = dueCardsArray[i].GetObject();
@@ -314,25 +353,37 @@ int main()
std::cout << "[" << i + 1 << "/" << numCards << "]\n\t" << quizWord << "\n";

// Wait to present next card
std::chrono::steady_clock::time_point startTime =
std::chrono::steady_clock::now();
// This won't be super accurate, but give or take a couple seconds even is fine
// in our case. Keep it positive so things don't get too wacky if weird math is
// above
float timeToNextCard = std::max(0.1f, secondsTimeLeft / (numCards * 1.2f));
std::this_thread::sleep_for(
std::chrono::duration<float, std::ratio<1, 1>>(timeToNextCard));
std::chrono::steady_clock::time_point endTime =
std::chrono::steady_clock::now();

std::chrono::duration<float> timeSlept =
std::chrono::duration_cast<std::chrono::duration<float>>(endTime -
startTime);
std::cout << "\n\n" << timeSlept.count() << " seconds slept\n";
{
std::chrono::steady_clock::time_point startTime =
std::chrono::steady_clock::now();
// This won't be super accurate, but give or take a couple seconds even is
// fine in our case. Keep it positive so things don't get too wacky if weird
// math is above
std::this_thread::sleep_for(
std::chrono::duration<float, std::ratio<1, 1>>(timeToNextCard));
std::chrono::steady_clock::time_point endTime =
std::chrono::steady_clock::now();

std::chrono::duration<float> timeSlept =
std::chrono::duration_cast<std::chrono::duration<float>>(endTime -
startTime);
std::cout << "\n\n" << timeSlept.count() << " seconds slept\n";
}

// Trigger a notification to prompt studying
// Throttle notifications to not happen too often (this will occur if the
// learner is running out of time and has a lot of cards)
if (timeToNextCard * numCardsInStudyBlock > reasonableStudyIntervalSeconds &&
i % numCardsInStudyBlock == 0)
notifications.sendNotification("Time to study!");
}
}
else
std::cout << "Study time over. " << numCards << " cards remaining.\n";
}
else
{
std::cout << "Study time over. " << numCards << " cards remaining.\n";

notifications.sendNotification("No more cards! Good work.");
}
}



+ 10
- 0
src/Notifications.hpp View File

@@ -0,0 +1,10 @@
#pragma once

class NotificationsHandler
{
public:
NotificationsHandler();
~NotificationsHandler();

void sendNotification(const char* text);
};

+ 20
- 0
src/Notifications_LibNotify.cpp View File

@@ -0,0 +1,20 @@
#include "Notifications.hpp"

#include <libnotify/notify.h>

NotificationsHandler::NotificationsHandler()
{
notify_init("Japanese for Me");
}
NotificationsHandler::~NotificationsHandler()
{
notify_uninit();
}

void NotificationsHandler::sendNotification(const char* text)
{
NotifyNotification* notification =
notify_notification_new("Japanese for Me", text, "dialog-information");
notify_notification_show(notification, nullptr);
g_object_unref(G_OBJECT(notification));
}

+ 12
- 0
src/Notifications_Stub.cpp View File

@@ -0,0 +1,12 @@
#include "Notifications.hpp"

#include <iostream>

NotificationsHandler::NotificationsHandler(){};
NotificationsHandler::~NotificationsHandler(){};

void NotificationsHandler::sendNotification(const char* text)
{
std::cout << "Notifications not supported. Check ReadMe for build instructions\n";
std::cout << text << "\n";
}

Loading…
Cancel
Save