changeset 77:6f7385bd334c

*: update formatted all source files, no more subclassing QThread... many other changes :)
author Paper <mrpapersonic@gmail.com>
date Fri, 06 Oct 2023 06:18:53 -0400
parents 3364fadc8a36
children 1ce00c1c8ddc
files .clang-format include/core/http.h include/gui/pages/anime_list.h src/core/config.cpp src/core/date.cpp src/core/http.cpp src/core/strings.cpp src/gui/dialog/about.cpp src/gui/dialog/information.cpp src/gui/dialog/settings.cpp src/gui/pages/anime_list.cpp src/gui/widgets/poster.cpp src/gui/widgets/sidebar.cpp src/gui/widgets/text.cpp src/gui/window.cpp src/services/anilist.cpp
diffstat 16 files changed, 125 insertions(+), 150 deletions(-) [+]
line wrap: on
line diff
--- a/.clang-format	Wed Oct 04 01:46:33 2023 -0400
+++ b/.clang-format	Fri Oct 06 06:18:53 2023 -0400
@@ -11,6 +11,7 @@
 IndentAccessModifiers: true
 IndentPPDirectives: AfterHash
 
+BreakArrays: true
 BreakBeforeBraces: Attach
 BreakStringLiterals: true
 
@@ -25,6 +26,8 @@
 AllowShortFunctionsOnASingleLine: InlineOnly
 AllowShortCaseLabelsOnASingleLine: true
 
+Cpp11BracedListStyle: true
+
 ---
 Language: Cpp
 Standard: Cpp11
--- a/include/core/http.h	Wed Oct 04 01:46:33 2023 -0400
+++ b/include/core/http.h	Fri Oct 06 06:18:53 2023 -0400
@@ -1,32 +1,14 @@
 #ifndef __core__http_h
 #define __core__http_h
 
-#include <QThread>
+#include <QByteArray>
 #include <string>
 #include <vector>
 
-class QObject;
-
 namespace HTTP {
 
-class HttpGetThread : public QThread {
-		Q_OBJECT
-
-	public:
-		HttpGetThread(std::string url, std::vector<std::string> headers = {}, QObject* parent = nullptr);
-
-	private:
-		static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userdata);
-		void run() override;
-		std::string _url;
-		std::vector<std::string> _headers;
-
-	signals:
-		void resultReady(const QByteArray& arr);
-};
-
-/* Performs a basic (blocking) post request */
-std::string PerformBasicPostRequest(std::string url, std::string data, std::vector<std::string> headers = {});
+QByteArray Get(std::string url, std::vector<std::string> headers = {});
+QByteArray Post(std::string url, std::string data, std::vector<std::string> headers = {});
 
 } // namespace HTTP
 
--- a/include/gui/pages/anime_list.h	Wed Oct 04 01:46:33 2023 -0400
+++ b/include/gui/pages/anime_list.h	Fri Oct 06 06:18:53 2023 -0400
@@ -54,7 +54,6 @@
 		int columnCount(const QModelIndex& parent = QModelIndex()) const override;
 		QVariant data(const QModelIndex& index, int role) const override;
 		QVariant headerData(const int section, const Qt::Orientation orientation, const int role) const override;
-		void UpdateAnime(int id);
 		void RefreshList();
 		Anime::Anime* GetAnimeFromIndex(QModelIndex index);
 
@@ -82,6 +81,8 @@
 		void resizeEvent(QResizeEvent* e) override;
 		void RefreshList();
 		void RefreshTabs();
+		void UpdateAnime(int id);
+		void RemoveAnime(int id);
 
 	private slots:
 		void DisplayColumnHeaderMenu();
--- a/src/core/config.cpp	Wed Oct 04 01:46:33 2023 -0400
+++ b/src/core/config.cpp	Fri Oct 06 06:18:53 2023 -0400
@@ -53,8 +53,8 @@
 	std::ifstream config(cfg_path.GetPath(), std::ifstream::in);
 	auto config_js = nlohmann::json::parse(config);
 	service = StringToService[JSON::GetString(config_js, "/General/Service"_json_pointer)];
-	anime_list.language = StringToAnimeTitleMap[JSON::GetString(
-	    config_js, "/Anime List/Display only aired episodes"_json_pointer, "Romaji")];
+	anime_list.language =
+	    StringToAnimeTitleMap[JSON::GetString(config_js, "/Anime List/Title language"_json_pointer, "Romaji")];
 	anime_list.display_aired_episodes =
 	    JSON::GetBoolean(config_js, "/Anime List/Display only aired episodes"_json_pointer, true);
 	anime_list.display_available_episodes =
@@ -76,7 +76,7 @@
 	if (!cfg_path.GetParent().Exists())
 		cfg_path.GetParent().CreateDirectories();
 	std::ofstream config(cfg_path.GetPath(), std::ofstream::out | std::ofstream::trunc);
-	// clang-format off
+	/* clang-format off */
 	nlohmann::json config_js = {
 		{"General",	{
 			{"Service", ServiceToString[service]}
@@ -99,7 +99,7 @@
 			{"Theme", ThemeToString[theme]}
 		}}
 	};
-	// clang-format on
+	/* clang-format on */
 	config << std::setw(4) << config_js << std::endl;
 	config.close();
 	return 0;
--- a/src/core/date.cpp	Wed Oct 04 01:46:33 2023 -0400
+++ b/src/core/date.cpp	Fri Oct 06 06:18:53 2023 -0400
@@ -105,18 +105,18 @@
 }
 
 nlohmann::json Date::GetAsAniListJson() const {
-	nlohmann::json result;
+	nlohmann::json result = {};
 	if (year.get())
-		result.insert(result.end(), {"year", *year});
+		result["year"] = *year;
 	else
-		result.insert(result.end(), {"year", nullptr});
+		result["year"] = nullptr;
 	if (month.get())
-		result.insert(result.end(), {"month", *month});
+		result["month"] = *month;
 	else
-		result.insert(result.end(), {"month", nullptr});
+		result["month"] = nullptr;
 	if (day.get())
-		result.insert(result.end(), {"day", *day});
+		result["day"] = *day;
 	else
-		result.insert(result.end(), {"day", nullptr});
+		result["day"] = nullptr;
 	return result;
 }
--- a/src/core/http.cpp	Wed Oct 04 01:46:33 2023 -0400
+++ b/src/core/http.cpp	Fri Oct 06 06:18:53 2023 -0400
@@ -2,33 +2,28 @@
 #include "core/session.h"
 #include <QByteArray>
 #include <QMessageBox>
-#include <QThread>
 #include <curl/curl.h>
 #include <string>
 #include <vector>
 
 namespace HTTP {
 
-HttpGetThread::HttpGetThread(std::string url, std::vector<std::string> headers, QObject* parent) : QThread(parent) {
-	_url = url;
-	_headers = headers;
-}
-
-size_t HttpGetThread::WriteCallback(void* contents, size_t size, size_t nmemb, void* userdata) {
+static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userdata) {
 	reinterpret_cast<QByteArray*>(userdata)->append(reinterpret_cast<char*>(contents), size * nmemb);
 	return size * nmemb;
 }
 
-void HttpGetThread::run() {
+QByteArray Get(std::string url, std::vector<std::string> headers) {
 	struct curl_slist* list = NULL;
 	QByteArray userdata;
 
 	CURL* curl = curl_easy_init();
 	if (curl) {
-		for (const std::string& h : _headers) {
+		for (const std::string& h : headers) {
 			list = curl_slist_append(list, h.c_str());
 		}
-		curl_easy_setopt(curl, CURLOPT_URL, _url.c_str());
+		curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
+		curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
 		curl_easy_setopt(curl, CURLOPT_WRITEDATA, &userdata);
 		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback);
 		/* Use system certs... useful on Windows. */
@@ -42,18 +37,13 @@
 			box.exec();
 		}
 	}
-	emit resultReady(userdata);
+	return userdata;
 }
 
-static size_t CurlWriteStringCallback(void* contents, size_t size, size_t nmemb, void* userdata) {
-	reinterpret_cast<std::string*>(userdata)->append(reinterpret_cast<char*>(contents), size * nmemb);
-	return size * nmemb;
-}
+QByteArray Post(std::string url, std::string data, std::vector<std::string> headers) {
+	struct curl_slist* list = NULL;
+	QByteArray userdata;
 
-/* Performs a basic (blocking) post request */
-std::string PerformBasicPostRequest(std::string url, std::string data, std::vector<std::string> headers) {
-	struct curl_slist* list = NULL;
-	std::string userdata;
 	CURL* curl = curl_easy_init();
 	if (curl) {
 		for (const std::string& h : headers) {
@@ -63,24 +53,19 @@
 		curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
 		curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
 		curl_easy_setopt(curl, CURLOPT_WRITEDATA, &userdata);
-		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &CurlWriteStringCallback);
+		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback);
 		/* Use system certs... useful on Windows. */
 		curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
 		CURLcode res = curl_easy_perform(curl);
 		session.IncrementRequests();
-		curl_slist_free_all(list);
 		curl_easy_cleanup(curl);
 		if (res != CURLE_OK) {
 			QMessageBox box(QMessageBox::Icon::Critical, "",
 			                QString("curl_easy_perform(curl) failed!: ") + QString(curl_easy_strerror(res)));
 			box.exec();
-			return "";
 		}
-		return userdata;
 	}
-	return "";
+	return userdata;
 }
 
 } // namespace HTTP
-
-#include "core/moc_http.cpp"
--- a/src/core/strings.cpp	Wed Oct 04 01:46:33 2023 -0400
+++ b/src/core/strings.cpp	Fri Oct 06 06:18:53 2023 -0400
@@ -96,7 +96,11 @@
 
 std::string ToUtf8String(const QString& string) {
 	QByteArray ba = string.toUtf8();
-	return std::string(ba.data(), ba.size());
+	return std::string(ba.constData(), ba.size());
+}
+
+std::string ToUtf8String(const QByteArray& ba) {
+	return std::string(ba.constData(), ba.size());
 }
 
 QString ToQString(const std::string& string) {
--- a/src/gui/dialog/about.cpp	Wed Oct 04 01:46:33 2023 -0400
+++ b/src/gui/dialog/about.cpp	Fri Oct 06 06:18:53 2023 -0400
@@ -16,17 +16,17 @@
 
 #define SET_TITLE_FONT(font, format, cursor) \
 	{ \
-		font = cursor.charFormat().font(); \
-		font.setPixelSize(16); \
-		format.setFont(font); \
+		QFont fnt; \
+		fnt.setPixelSize(16); \
+		format.setFont(fnt); \
 		cursor.setCharFormat(format); \
 	}
 
 #define SET_PARAGRAPH_FONT(font, format, cursor) \
 	{ \
-		font = cursor.charFormat().font(); \
-		font.setPixelSize(12); \
-		format.setFont(font); \
+		QFont fnt; \
+		fnt.setPixelSize(12); \
+		format.setFont(fnt); \
 		cursor.setCharFormat(format); \
 	}
 
@@ -48,12 +48,18 @@
 
 #define SET_FORMAT_HYPERLINK(format, cursor, link) \
 	{ \
+		font = cursor.charFormat().font(); \
+		font.setUnderline(true); \
+		format.setFont(font); \
 		format.setAnchor(true); \
 		format.setAnchorHref(link); \
 		cursor.setCharFormat(format); \
 	}
 #define UNSET_FORMAT_HYPERLINK(format, cursor) \
 	{ \
+		font = cursor.charFormat().font(); \
+		font.setUnderline(false); \
+		format.setFont(font); \
 		format.setAnchor(false); \
 		format.setAnchorHref(""); \
 		cursor.setCharFormat(format); \
--- a/src/gui/dialog/information.cpp	Wed Oct 04 01:46:33 2023 -0400
+++ b/src/gui/dialog/information.cpp	Fri Oct 06 06:18:53 2023 -0400
@@ -235,7 +235,7 @@
 		CREATE_FULL_WIDTH_SUBSECTION({
 			subsection_layout->addWidget(new QLabel(tr("Alternative titles:"), subsection));
 
-			QLineEdit* line_edit = new QLineEdit(Strings::ToQString(anime.GetUserNotes()), subsection);
+			QLineEdit* line_edit = new QLineEdit("", subsection);
 			line_edit->setPlaceholderText(
 			    tr("Enter alternative titles here, separated by a semicolon (i.e. Title 1; Title 2)"));
 			subsection_layout->addWidget(line_edit);
--- a/src/gui/dialog/settings.cpp	Wed Oct 04 01:46:33 2023 -0400
+++ b/src/gui/dialog/settings.cpp	Fri Oct 06 06:18:53 2023 -0400
@@ -16,7 +16,7 @@
 	page_title->setFrameShadow(QFrame::Sunken);
 
 	QFont font(page_title->font());
-	font.setPixelSize(13);
+	font.setPixelSize(12);
 	font.setWeight(QFont::Bold);
 	page_title->setFont(font);
 
@@ -75,10 +75,6 @@
 	sidebar->setIconSize(QSize(24, 24));
 	sidebar->setFrameShape(QFrame::Box);
 
-	QFont font(sidebar->font());
-	font.setPixelSize(12);
-	sidebar->setFont(font);
-
 	QPalette pal(sidebar->palette());
 	sidebar->SetBackgroundColor(pal.color(QPalette::Base));
 
--- a/src/gui/pages/anime_list.cpp	Wed Oct 04 01:46:33 2023 -0400
+++ b/src/gui/pages/anime_list.cpp	Fri Oct 06 06:18:53 2023 -0400
@@ -26,7 +26,8 @@
 #include <QShortcut>
 #include <QStylePainter>
 #include <QStyledItemDelegate>
-#include <cmath>
+#include <QThreadPool>
+#include <set>
 
 AnimeListPageDelegate::AnimeListPageDelegate(QObject* parent) : QStyledItemDelegate(parent) {
 }
@@ -191,33 +192,19 @@
 	return QVariant();
 }
 
-void AnimeListPageModel::UpdateAnime(int id) {
-	/* meh... it might be better to just reinit the entire list */
-	int i = 0;
-	for (const auto& a : Anime::db.items) {
-		if (a.second.IsInUserList() && a.first == id && a.second.GetUserStatus() == status) {
-			emit dataChanged(index(i), index(i));
-		}
-		i++;
-	}
-}
-
 Anime::Anime* AnimeListPageModel::GetAnimeFromIndex(QModelIndex index) {
 	return &list.at(index.row());
 }
 
 void AnimeListPageModel::RefreshList() {
 	bool has_children = !!rowCount(index(0));
-	if (has_children)
-		beginResetModel();
-	else {
-		int count = 0;
-		for (const auto& a : Anime::db.items)
-			if (a.second.IsInUserList() && a.second.GetUserStatus() == status)
-				count++;
-		beginInsertRows(index(0), 0, count - 1);
+	if (!has_children) {
+		beginInsertRows(QModelIndex(), 0, 0);
+		endInsertRows();
 	}
 
+	beginResetModel();
+
 	list.clear();
 
 	for (const auto& a : Anime::db.items) {
@@ -226,10 +213,7 @@
 		}
 	}
 
-	if (has_children)
-		endResetModel();
-	else
-		endInsertRows();
+	endResetModel();
 }
 
 int AnimeListPage::VisibleColumnsCount() const {
@@ -258,6 +242,19 @@
 	tree_view->setColumnHidden(AnimeListPageModel::AL_NOTES, true);
 }
 
+void AnimeListPage::UpdateAnime(int id) {
+	QThreadPool::globalInstance()->start([this, id] {
+		Services::UpdateAnimeEntry(id);
+		Refresh();
+	});
+}
+
+void AnimeListPage::RemoveAnime(int id) {
+	Anime::Anime& anime = Anime::db.items[id];
+	anime.RemoveFromUserList();
+	Refresh();
+}
+
 void AnimeListPage::DisplayColumnHeaderMenu() {
 	QMenu* menu = new QMenu(this);
 	menu->setAttribute(Qt::WA_DeleteOnClose);
@@ -301,32 +298,35 @@
 	menu->setTitle(tr("Column visibility"));
 	menu->setToolTipsVisible(true);
 
+	AnimeListPageModel* source_model =
+	    reinterpret_cast<AnimeListPageModel*>(sort_models[tab_bar->currentIndex()]->sourceModel());
 	const QItemSelection selection =
 	    sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection());
-	if (!selection.indexes().first().isValid()) {
-		return;
+
+	std::set<Anime::Anime*> animes;
+	for (const auto& index : selection.indexes()) {
+		if (!index.isValid())
+			continue;
+		Anime::Anime* anime = source_model->GetAnimeFromIndex(index);
+		if (anime)
+			animes.insert(anime);
 	}
 
-	QAction* action = menu->addAction(tr("Information"), [this, selection] {
-		AnimeListPageModel* source_model =
-		    reinterpret_cast<AnimeListPageModel*>(sort_models[tab_bar->currentIndex()]->sourceModel());
-		const QModelIndex index = source_model->index(selection.indexes().first().row());
-		Anime::Anime* anime = source_model->GetAnimeFromIndex(index);
-		if (!anime) {
-			return;
-		}
+	QAction* action = menu->addAction(tr("Information"), [this, animes] {
+		for (auto& anime : animes) {
+			InformationDialog* dialog = new InformationDialog(
+			    *anime, [this, anime] { UpdateAnime(anime->GetId()); }, this);
 
-		InformationDialog* dialog = new InformationDialog(
-		    *anime,
-		    [this, anime] {
-			    Services::UpdateAnimeEntry(anime->GetId());
-			    Refresh();
-		    },
-		    this);
-
-		dialog->show();
-		dialog->raise();
-		dialog->activateWindow();
+			dialog->show();
+			dialog->raise();
+			dialog->activateWindow();
+		}
+	});
+	menu->addSeparator();
+	action = menu->addAction(tr("Delete from list..."), [this, animes] {
+		for (auto& anime : animes) {
+			RemoveAnime(anime->GetId());
+		}
 	});
 	menu->popup(QCursor::pos());
 }
@@ -346,12 +346,7 @@
 	Anime::Anime* anime = source_model->GetAnimeFromIndex(index);
 
 	InformationDialog* dialog = new InformationDialog(
-	    *anime,
-	    [this, anime] {
-		    Services::UpdateAnimeEntry(anime->GetId());
-		    Refresh();
-	    },
-	    this);
+	    *anime, [this, anime] { UpdateAnime(anime->GetId()); }, this);
 
 	dialog->show();
 	dialog->raise();
--- a/src/gui/widgets/poster.cpp	Wed Oct 04 01:46:33 2023 -0400
+++ b/src/gui/widgets/poster.cpp	Fri Oct 06 06:18:53 2023 -0400
@@ -12,6 +12,7 @@
 #include <QLabel>
 #include <QMessageBox>
 #include <QPixmap>
+#include <QThreadPool>
 #include <QUrl>
 #include <curl/curl.h>
 
@@ -26,10 +27,10 @@
 
 	const Anime::Anime& anime = Anime::db.items[id];
 
-	HTTP::HttpGetThread* image_thread = new HTTP::HttpGetThread(anime.GetPosterUrl(), {}, this);
-	connect(image_thread, &HTTP::HttpGetThread::resultReady, this, &Poster::ImageDownloadFinished);
-	connect(image_thread, &HTTP::HttpGetThread::finished, image_thread, &QObject::deleteLater);
-	image_thread->start();
+	QThreadPool::globalInstance()->start([this, anime] {
+		QByteArray ba = HTTP::Get(anime.GetPosterUrl(), {});
+		ImageDownloadFinished(ba);
+	});
 
 	QPixmap pixmap = QPixmap::fromImage(img);
 
--- a/src/gui/widgets/sidebar.cpp	Wed Oct 04 01:46:33 2023 -0400
+++ b/src/gui/widgets/sidebar.cpp	Fri Oct 06 06:18:53 2023 -0400
@@ -16,10 +16,6 @@
 
 	SetBackgroundColor(Qt::transparent);
 
-	QFont font;
-	font.setPixelSize(12);
-	setFont(font);
-
 	connect(this, &QListWidget::currentRowChanged, this,
 	        [this](int index) { emit CurrentItemChanged(RemoveSeparatorsFromIndex(index)); });
 }
--- a/src/gui/widgets/text.cpp	Wed Oct 04 01:46:33 2023 -0400
+++ b/src/gui/widgets/text.cpp	Fri Oct 06 06:18:53 2023 -0400
@@ -1,5 +1,6 @@
 #include "gui/widgets/text.h"
 #include "core/session.h"
+#include <QDebug>
 #include <QFrame>
 #include <QLabel>
 #include <QTextBlock>
@@ -16,8 +17,6 @@
 	QFont font = static_text_title->font();
 	font.setWeight(QFont::Bold);
 	static_text_title->setFont(font);
-	/* FIXME: is this needed? */
-	static_text_title->setFixedHeight(16);
 
 	static_text_line = new QFrame(this);
 	static_text_line->setFrameShape(QFrame::HLine);
@@ -76,6 +75,7 @@
 Line::Line(QString text, QWidget* parent) : QLineEdit(text, parent) {
 	setFrame(false);
 	setReadOnly(true);
+	setCursorPosition(0); /* displays left text first */
 
 	QPalette pal;
 	pal.setColor(QPalette::Window, Qt::transparent);
--- a/src/gui/window.cpp	Wed Oct 04 01:46:33 2023 -0400
+++ b/src/gui/window.cpp	Fri Oct 06 06:18:53 2023 -0400
@@ -27,6 +27,7 @@
 #include <QPlainTextEdit>
 #include <QStackedWidget>
 #include <QTextStream>
+#include <QThreadPool>
 #include <QTimer>
 #include <QToolBar>
 #include <QToolButton>
@@ -48,6 +49,13 @@
 	TORRENTS
 };
 
+static void AsyncSynchronize(QStackedWidget* stack) {
+	QThreadPool::globalInstance()->start([stack] {
+		Services::Synchronize();
+		reinterpret_cast<AnimeListPage*>(stack->widget(static_cast<int>(Pages::ANIME_LIST)))->Refresh();
+	});
+}
+
 MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
 	main_widget = new QWidget(parent);
 
@@ -98,10 +106,7 @@
 	action = menu->addAction(tr("E&xit"), qApp, &QApplication::quit);
 
 	menu = menubar->addMenu(tr("&Services"));
-	action = menu->addAction(tr("Synchronize &list"), [stack] {
-		Services::Synchronize();
-		reinterpret_cast<AnimeListPage*>(stack->widget(static_cast<int>(Pages::ANIME_LIST)))->Refresh();
-	});
+	action = menu->addAction(tr("Synchronize &list"), [stack] { AsyncSynchronize(stack); });
 	action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_S));
 
 	menu->addSeparator();
@@ -199,10 +204,8 @@
 
 	/* Toolbar */
 	QToolBar* toolbar = new QToolBar(this);
-	toolbar->addAction(QIcon(":/icons/24x24/arrow-circle-double-135.png"), tr("&Synchronize"), [stack] {
-		Services::Synchronize();
-		reinterpret_cast<AnimeListPage*>(stack->widget(static_cast<int>(Pages::ANIME_LIST)))->Refresh();
-	});
+	toolbar->addAction(QIcon(":/icons/24x24/arrow-circle-double-135.png"), tr("&Synchronize"),
+	                   [stack] { AsyncSynchronize(stack); });
 	toolbar->addSeparator();
 
 	QToolButton* button = new QToolButton(toolbar);
--- a/src/services/anilist.cpp	Wed Oct 04 01:46:33 2023 -0400
+++ b/src/services/anilist.cpp	Fri Oct 06 06:18:53 2023 -0400
@@ -7,6 +7,7 @@
 #include "core/session.h"
 #include "core/strings.h"
 #include "gui/translate/anilist.h"
+#include <QByteArray>
 #include <QDesktopServices>
 #include <QInputDialog>
 #include <QLineEdit>
@@ -40,7 +41,7 @@
 std::string SendRequest(std::string data) {
 	std::vector<std::string> headers = {"Authorization: Bearer " + account.AuthToken(), "Accept: application/json",
 	                                    "Content-Type: application/json"};
-	return HTTP::PerformBasicPostRequest("https://graphql.anilist.co", data, headers);
+	return Strings::ToUtf8String(HTTP::Post("https://graphql.anilist.co", data, headers));
 }
 
 void ParseListStatus(std::string status, Anime::Anime& anime) {
@@ -255,13 +256,13 @@
 	 * Date completedAt
 	 **/
 	Anime::Anime& anime = Anime::db.items[id];
-	const std::string query =
-	    "mutation ($media_id: Int, $progress: Int, $status: MediaListStatus, $score: Int, $notes: String) {\n"
-	    "  SaveMediaListEntry (mediaId: $media_id, progress: $progress, status: $status, scoreRaw: $score, notes: "
-	    "$notes) {\n"
-	    "    id\n"
-	    "  }\n"
-	    "}\n";
+	const std::string query = "mutation ($media_id: Int, $progress: Int, $status: MediaListStatus, $score: Int, "
+	                          "$notes: String, $start: FuzzyDateInput, $comp: FuzzyDateInput) {\n"
+	                          "  SaveMediaListEntry (mediaId: $media_id, progress: $progress, status: $status, "
+	                          "scoreRaw: $score, notes: $notes, startedAt: $start, completedAt: $comp) {\n"
+	                          "    id\n"
+	                          "  }\n"
+	                          "}\n";
 	// clang-format off
 	nlohmann::json json = {
 		{"query", query},
@@ -270,7 +271,9 @@
 			{"progress", anime.GetUserProgress()},
 			{"status",   ListStatusToString(anime)},
 			{"score",    anime.GetUserScore()},
-			{"notes",    anime.GetUserNotes()}
+			{"notes",    anime.GetUserNotes()},
+			{"start",    anime.GetUserDateStarted().GetAsAniListJson()},
+			{"comp",     anime.GetUserDateCompleted().GetAsAniListJson()}
 		}}
 	};
 	// clang-format on