aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Body.cpp10
-rw-r--r--src/DownloadUtils.cpp3
-rw-r--r--src/FileAnalyzer.cpp2
-rw-r--r--src/Notification.cpp2
-rw-r--r--src/Program.cpp (renamed from src/Program.c)47
-rw-r--r--src/QuickMedia.cpp222
-rw-r--r--src/VideoPlayer.cpp2
-rw-r--r--src/plugins/Manga.cpp5
-rw-r--r--src/plugins/Matrix.cpp11
-rw-r--r--src/plugins/NyaaSi.cpp2
10 files changed, 197 insertions, 109 deletions
diff --git a/src/Body.cpp b/src/Body.cpp
index 7f96263..3f5c755 100644
--- a/src/Body.cpp
+++ b/src/Body.cpp
@@ -186,10 +186,11 @@ namespace QuickMedia {
return true;
}
- void Body::set_selected_item(int item) {
+ void Body::set_selected_item(int item, bool reset_prev_selected_item) {
//assert(item >= 0 && item < (int)items.size());
selected_item = item;
- prev_selected_item = selected_item;
+ if(reset_prev_selected_item)
+ prev_selected_item = selected_item;
clamp_selection();
//page_scroll = 0.0f;
}
@@ -216,10 +217,6 @@ namespace QuickMedia {
clamp_selection();
}
- void Body::reset_selected() {
- select_first_item();
- }
-
void Body::clear_items() {
items.clear();
selected_item = 0;
@@ -235,6 +232,7 @@ namespace QuickMedia {
items.insert(items.end(), std::make_move_iterator(new_items.begin()), std::make_move_iterator(new_items.end()));
}
+ // TODO: Binary search and use hint to start search from start or end (for example when adding "previous" items or "next" items)
void Body::insert_item_by_timestamp(std::shared_ptr<BodyItem> body_item) {
for(size_t i = 0; i < items.size(); ++i) {
if(body_item->get_timestamp() < items[i]->get_timestamp()) {
diff --git a/src/DownloadUtils.cpp b/src/DownloadUtils.cpp
index fd7e7d1..cbdded6 100644
--- a/src/DownloadUtils.cpp
+++ b/src/DownloadUtils.cpp
@@ -1,5 +1,5 @@
#include "../include/DownloadUtils.hpp"
-#include "../include/Program.h"
+#include "../include/Program.hpp"
#include "../include/Storage.hpp"
#include "../include/base64_url.hpp"
#include <SFML/System/Clock.hpp>
@@ -141,6 +141,7 @@ namespace QuickMedia {
char read_buffer[8192];
rapidjson::FileReadStream is(file, read_buffer, sizeof(read_buffer));
rapidjson::ParseResult parse_result = result.ParseStream(is);
+ program_clear_current_thread();
fclose(file);
wait_program(read_program.pid);
diff --git a/src/FileAnalyzer.cpp b/src/FileAnalyzer.cpp
index 82d2c06..8d5eae8 100644
--- a/src/FileAnalyzer.cpp
+++ b/src/FileAnalyzer.cpp
@@ -1,5 +1,5 @@
#include "../include/FileAnalyzer.hpp"
-#include "../include/Program.h"
+#include "../include/Program.hpp"
#include <sys/stat.h>
#include <stdio.h>
#include <array>
diff --git a/src/Notification.cpp b/src/Notification.cpp
index 1201557..9ffbaac 100644
--- a/src/Notification.cpp
+++ b/src/Notification.cpp
@@ -1,5 +1,5 @@
#include "../include/Notification.hpp"
-#include "../include/Program.h"
+#include "../include/Program.hpp"
#include <assert.h>
#include <stdio.h>
diff --git a/src/Program.c b/src/Program.cpp
index a82bcd2..136a494 100644
--- a/src/Program.c
+++ b/src/Program.cpp
@@ -1,4 +1,4 @@
-#include "../include/Program.h"
+#include "../include/Program.hpp"
#include <unistd.h>
#include <sys/wait.h>
#include <sys/prctl.h>
@@ -7,10 +7,40 @@
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
+#include <unordered_map>
+#include <mutex>
+#include <signal.h>
#define READ_END 0
#define WRITE_END 1
+class CurrentThreadProgram {
+public:
+ void set(ReadProgram read_program) {
+ std::lock_guard<std::mutex> lock(mutex);
+ thread_current_program[std::this_thread::get_id()] = read_program;
+ }
+
+ void clear() {
+ std::lock_guard<std::mutex> lock(mutex);
+ thread_current_program.erase(std::this_thread::get_id());
+ }
+
+ void kill_in_thread(const std::thread::id &thread_id) {
+ std::lock_guard<std::mutex> lock(mutex);
+ auto it = thread_current_program.find(thread_id);
+ if(it != thread_current_program.end()) {
+ close(it->second.read_fd);
+ kill(it->second.pid, SIGTERM);
+ }
+ }
+private:
+ std::unordered_map<std::thread::id, ReadProgram> thread_current_program;
+ std::mutex mutex;
+};
+
+static CurrentThreadProgram current_thread_program;
+
int exec_program_pipe(const char **args, ReadProgram *read_program) {
/* 1 arguments */
if(args[0] == NULL)
@@ -51,6 +81,7 @@ int exec_program_pipe(const char **args, ReadProgram *read_program) {
close(fd[WRITE_END]);
read_program->pid = pid;
read_program->read_fd = fd[READ_END];
+ current_thread_program.set(*read_program);
return 0;
}
}
@@ -63,6 +94,7 @@ int exec_program(const char **args, ProgramOutputCallback output_callback, void
int result = 0;
int status;
+ int exit_status;
char buffer[4097];
@@ -77,6 +109,8 @@ int exec_program(const char **args, ProgramOutputCallback output_callback, void
break;
}
+ //check if running. Also do the same in download_to_json
+
buffer[bytes_read] = '\0';
if(output_callback) {
result = output_callback(buffer, bytes_read, userdata);
@@ -84,6 +118,7 @@ int exec_program(const char **args, ProgramOutputCallback output_callback, void
break;
}
}
+ program_clear_current_thread();
if(result != 0)
kill(read_program.pid, SIGTERM);
@@ -99,7 +134,7 @@ int exec_program(const char **args, ProgramOutputCallback output_callback, void
goto cleanup;
}
- int exit_status = WEXITSTATUS(status);
+ exit_status = WEXITSTATUS(status);
if(exit_status != 0) {
fprintf(stderr, "Failed to execute program (");
const char **arg = args;
@@ -201,3 +236,11 @@ int exec_program_async(const char **args, pid_t *result_process_id) {
}
return 0;
}
+
+void program_clear_current_thread() {
+ current_thread_program.clear();
+}
+
+void program_kill_in_thread(const std::thread::id &thread_id) {
+ current_thread_program.kill_in_thread(thread_id);
+}
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index ba9a680..3b99d55 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -9,7 +9,7 @@
#include "../plugins/Matrix.hpp"
#include "../plugins/FileManager.hpp"
#include "../include/Scale.hpp"
-#include "../include/Program.h"
+#include "../include/Program.hpp"
#include "../include/VideoPlayer.hpp"
#include "../include/StringUtils.hpp"
#include "../include/GoogleCaptcha.hpp"
@@ -641,51 +641,7 @@ namespace QuickMedia {
chat_login_page();
}
- if(!window.isOpen())
- return exit_code;
-
- auto rooms_body = create_body();
- rooms_body->thumbnail_mask_shader = &circle_mask_shader;
- auto matrix_rooms_page = std::make_unique<MatrixRoomsPage>(this, rooms_body.get(), "All rooms");
-
- auto rooms_tags_body = create_body();
- rooms_tags_body->thumbnail_mask_shader = &circle_mask_shader;
- auto matrix_rooms_tag_page = std::make_unique<MatrixRoomTagsPage>(this, rooms_tags_body.get());
-
- MatrixQuickMedia matrix_handler(this, matrix, matrix_rooms_page.get(), matrix_rooms_tag_page.get());
- matrix->start_sync(&matrix_handler);
-
- tabs.push_back(Tab{std::move(rooms_body), std::move(matrix_rooms_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
- tabs.push_back(Tab{std::move(rooms_tags_body), std::move(matrix_rooms_tag_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
-
- sf::Sprite load_sprite(loading_icon);
- sf::Vector2u loading_icon_size = loading_icon.getSize();
- load_sprite.setOrigin(loading_icon_size.x * 0.5f, loading_icon_size.y * 0.5f);
-
- sf::Clock timer;
- sf::Event event;
- while(window.isOpen() && !matrix->is_initial_sync_finished()) {
- while(window.pollEvent(event)) {
- if(event.type == sf::Event::Closed)
- window.close();
- else if(event.type == sf::Event::Resized) {
- window_size.x = event.size.width;
- window_size.y = event.size.height;
- sf::FloatRect visible_area(0, 0, window_size.x, window_size.y);
- window.setView(sf::View(visible_area));
- }
- }
- window.clear(back_color);
- load_sprite.setPosition(window_size.x * 0.5f, window_size.y * 0.5f);
- load_sprite.setRotation(timer.getElapsedTime().asSeconds() * 400.0);
- window.draw(load_sprite);
- window.display();
- }
-
- while(window.isOpen()) {
- page_loop(tabs);
- }
-
+ after_matrix_login_page();
exit(exit_code); // Exit immediately without waiting for anything to finish
//matrix->stop_sync();
}
@@ -975,6 +931,16 @@ namespace QuickMedia {
return current_chat_room;
}
+ static void select_body_item_by_room(Body *body, RoomData *room) {
+ for(size_t i = 0; i < body->items.size(); ++i) {
+ auto &body_item = body->items[i];
+ if(body_item->userdata == room) {
+ body->set_selected_item(i, false);
+ return;
+ }
+ }
+ }
+
void Program::page_loop(std::vector<Tab> &tabs) {
if(tabs.empty()) {
show_notification("QuickMedia", "No tabs provided!", Urgency::CRITICAL);
@@ -983,7 +949,7 @@ namespace QuickMedia {
for(Tab &tab : tabs) {
tab.body->thumbnail_max_size = tab.page->get_thumbnail_max_size();
- tab.page->on_navigate_to_page();
+ tab.page->on_navigate_to_page(tab.body->items);
}
const Json::Value *json_chapters = &Json::Value::nullSingleton();
@@ -1106,11 +1072,12 @@ namespace QuickMedia {
current_page = PageType::CHAT;
current_chat_room = matrix->get_room_by_id(selected_item->url);
chat_page(static_cast<MatrixChatPage*>(new_tabs[0].page.get()), current_chat_room);
+ select_body_item_by_room(tabs[selected_tab].body.get(), current_chat_room);
current_chat_room = nullptr;
} else {
page_loop(new_tabs);
}
- tabs[selected_tab].page->on_navigate_to_page();
+ tabs[selected_tab].page->on_navigate_to_page(tabs[selected_tab].body->items);
if(content_storage_json.isObject()) {
const Json::Value &chapters_json = content_storage_json["chapters"];
if(chapters_json.isObject())
@@ -1614,9 +1581,11 @@ namespace QuickMedia {
video_url = std::move(video_path.data);
video_url_is_local = true;
} else {
- std::future<DownloadResult> download_future = std::async(std::launch::async, [this, &video_path, video_url]() {
- return download_to_file(video_url, video_path.data, {}, use_tor, true);
- });
+ std::promise<DownloadResult> download_result_promise;
+ std::future<DownloadResult> download_future = download_result_promise.get_future();
+ std::thread download_thread([this, &video_path, video_url](std::promise<DownloadResult> &&promise) {
+ promise.set_value(download_to_file(video_url, video_path.data, {}, use_tor, true));
+ }, std::move(download_result_promise));
window_size.x = window.getSize().x;
window_size.y = window.getSize().y;
@@ -1625,9 +1594,8 @@ namespace QuickMedia {
while(window.isOpen()) {
while(window.pollEvent(event)) {
if(event.type == sf::Event::Closed) {
- // TODO: Remove this
- if(download_future.valid())
- download_future.get();
+ program_kill_in_thread(download_thread.get_id());
+ download_thread.join();
current_page = previous_page;
window.close();
return;
@@ -1637,16 +1605,17 @@ namespace QuickMedia {
sf::FloatRect visible_area(0, 0, window_size.x, window_size.y);
window.setView(sf::View(visible_area));
} else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape) {
- // TODO: Remove this
- if(download_future.valid())
- download_future.get();
+ program_kill_in_thread(download_thread.get_id());
+ download_thread.join();
current_page = previous_page;
return;
}
}
if(is_future_ready(download_future)) {
- if(download_future.get() != DownloadResult::OK) {
+ DownloadResult download_result = download_future.get();
+ download_thread.join();
+ if(download_result != DownloadResult::OK) {
show_notification("QuickMedia", "Failed to download " + video_url, Urgency::CRITICAL);
current_page = previous_page;
return;
@@ -3307,7 +3276,9 @@ namespace QuickMedia {
chat_state = ChatState::NAVIGATING;
return true;
} else if(text == "/logout") {
- show_notification("QuickMedia", "/logout command is temporary disabled. Delete " + get_storage_dir().join("matrix").join("session.json").data + " and restart QuickMedia to logout", Urgency::CRITICAL);
+ new_page = PageType::CHAT_LOGIN;
+ chat_input.set_editable(false);
+ chat_state = ChatState::NAVIGATING;
return true;
} else if(strncmp(text.c_str(), "/me ", 4) == 0) {
msgtype = "m.emote";
@@ -3460,14 +3431,20 @@ namespace QuickMedia {
float tab_vertical_offset = 0.0f;
- auto typing_async_func = [this](bool new_state, RoomData *room) {
- if(new_state) {
- matrix->on_start_typing(room);
- } else {
- matrix->on_stop_typing(room);
+ MessageQueue<bool> typing_state_queue;
+ std::thread typing_state_thread([this, &current_room, &typing_state_queue]() {
+ while(true) {
+ std::optional<bool> state_opt = typing_state_queue.pop_wait();
+ if(!state_opt)
+ break;
+
+ bool state = state_opt.value();
+ if(state)
+ matrix->on_start_typing(current_room);
+ else
+ matrix->on_stop_typing(current_room);
}
- };
- std::vector<std::future<void>> typing_futures;
+ });
sf::Clock frame_timer;
@@ -3638,7 +3615,7 @@ namespace QuickMedia {
if(typing && current_room) {
fprintf(stderr, "Stopped typing\n");
typing = false;
- typing_futures.push_back(std::async(typing_async_func, false, current_room));
+ typing_state_queue.push(false);
}
} else if((event.key.code == sf::Keyboard::Right) && selected_tab < (int)tabs.size() - 1) {
tabs[selected_tab].body->clear_cache();
@@ -3648,7 +3625,7 @@ namespace QuickMedia {
if(typing && current_room) {
fprintf(stderr, "Stopped typing\n");
typing = false;
- typing_futures.push_back(std::async(typing_async_func, false, current_room));
+ typing_state_queue.push(false);
}
} else if(event.key.code == sf::Keyboard::Escape) {
goto chat_page_end;
@@ -3662,6 +3639,14 @@ namespace QuickMedia {
}
}
+ if(current_room) {
+ if(event.key.control && event.key.code == sf::Keyboard::C) {
+ BodyItem *selected = tabs[selected_tab].body->get_selected();
+ if(selected)
+ sf::Clipboard::setString(selected->get_description());
+ }
+ }
+
if(selected_tab == MESSAGES_TAB_INDEX && current_room) {
if(event.key.code == sf::Keyboard::U) {
frame_skip_text_entry = true;
@@ -3783,7 +3768,7 @@ namespace QuickMedia {
start_typing_timer.restart();
if(!typing && current_room) {
fprintf(stderr, "Started typing\n");
- typing_futures.push_back(std::async(typing_async_func, true, current_room));
+ typing_state_queue.push(true);
}
typing = true;
}
@@ -3795,7 +3780,7 @@ namespace QuickMedia {
if(typing && current_room) {
fprintf(stderr, "Stopped typing\n");
typing = false;
- typing_futures.push_back(std::async(typing_async_func, false, current_room));
+ typing_state_queue.push(false);
}
}
//chat_input.on_event(event);
@@ -3840,7 +3825,30 @@ namespace QuickMedia {
break;
}
case PageType::CHAT_LOGIN: {
- abort();
+ // TODO: Cancel these instead
+ if(set_read_marker_future.valid())
+ set_read_marker_future.get();
+ if(previous_messages_future.valid())
+ previous_messages_future.get();
+ if(fetch_message_future.valid())
+ fetch_message_future.get();
+ typing_state_queue.close();
+ if(typing_state_thread.joinable())
+ typing_state_thread.join();
+ new_page = PageType::CHAT;
+ matrix->stop_sync();
+ matrix->logout();
+ tabs[MESSAGES_TAB_INDEX].body->clear_cache();
+ // TODO: Instead of doing this, exit this current function and navigate to chat login page instead.
+ // This doesn't currently work because at the end of this function there are futures that need to wait
+ // and one of them is /sync, which has a timeout of 30 seconds. That timeout has to be killed somehow.
+ //delete current_plugin;
+ //current_plugin = new Matrix();
+ current_page = PageType::CHAT_LOGIN;
+ chat_login_page();
+ if(current_page == PageType::CHAT)
+ after_matrix_login_page();
+ exit(0);
break;
}
default:
@@ -3850,22 +3858,7 @@ namespace QuickMedia {
if(typing && start_typing_timer.getElapsedTime().asSeconds() >= typing_timeout_seconds && current_room) {
fprintf(stderr, "Stopped typing\n");
typing = false;
- typing_futures.push_back(std::async(typing_async_func, false, current_room));
- }
-
- for(auto it = typing_futures.begin(); it != typing_futures.end(); ) {
- if(!it->valid()) {
- it = typing_futures.erase(it);
- continue;
- }
-
- if(it->wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
- it->get();
- it = typing_futures.erase(it);
- continue;
- }
-
- ++it;
+ typing_state_queue.push(false);
}
if(current_room && current_room->userdata && room_avatar_thumbnail_data->loading_state == LoadingState::NOT_LOADED)
@@ -4146,13 +4139,60 @@ namespace QuickMedia {
previous_messages_future.get();
if(fetch_message_future.valid())
fetch_message_future.get();
- for(auto &typing_future : typing_futures) {
- if(typing_future.valid())
- typing_future.get();
- }
+ typing_state_queue.close();
+ if(typing_state_thread.joinable())
+ typing_state_thread.join();
for(auto &body_item : tabs[PINNED_TAB_INDEX].body->items) {
delete (PinnedEventData*)body_item->userdata;
}
}
+
+ void Program::after_matrix_login_page() {
+ if(!window.isOpen())
+ exit(exit_code);
+
+ auto rooms_body = create_body();
+ rooms_body->thumbnail_mask_shader = &circle_mask_shader;
+ auto matrix_rooms_page = std::make_unique<MatrixRoomsPage>(this, rooms_body.get(), "All rooms");
+
+ auto rooms_tags_body = create_body();
+ rooms_tags_body->thumbnail_mask_shader = &circle_mask_shader;
+ auto matrix_rooms_tag_page = std::make_unique<MatrixRoomTagsPage>(this, rooms_tags_body.get());
+
+ MatrixQuickMedia matrix_handler(this, matrix, matrix_rooms_page.get(), matrix_rooms_tag_page.get());
+ matrix->start_sync(&matrix_handler);
+
+ std::vector<Tab> tabs;
+ tabs.push_back(Tab{std::move(rooms_body), std::move(matrix_rooms_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
+ tabs.push_back(Tab{std::move(rooms_tags_body), std::move(matrix_rooms_tag_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
+
+ sf::Sprite load_sprite(loading_icon);
+ sf::Vector2u loading_icon_size = loading_icon.getSize();
+ load_sprite.setOrigin(loading_icon_size.x * 0.5f, loading_icon_size.y * 0.5f);
+
+ sf::Clock timer;
+ sf::Event event;
+ while(window.isOpen() && !matrix->is_initial_sync_finished()) {
+ while(window.pollEvent(event)) {
+ if(event.type == sf::Event::Closed)
+ window.close();
+ else if(event.type == sf::Event::Resized) {
+ window_size.x = event.size.width;
+ window_size.y = event.size.height;
+ sf::FloatRect visible_area(0, 0, window_size.x, window_size.y);
+ window.setView(sf::View(visible_area));
+ }
+ }
+ window.clear(back_color);
+ load_sprite.setPosition(window_size.x * 0.5f, window_size.y * 0.5f);
+ load_sprite.setRotation(timer.getElapsedTime().asSeconds() * 400.0);
+ window.draw(load_sprite);
+ window.display();
+ }
+
+ while(window.isOpen()) {
+ page_loop(tabs);
+ }
+ }
}
diff --git a/src/VideoPlayer.cpp b/src/VideoPlayer.cpp
index cbb9634..c7d2697 100644
--- a/src/VideoPlayer.cpp
+++ b/src/VideoPlayer.cpp
@@ -1,6 +1,6 @@
#include "../include/VideoPlayer.hpp"
#include "../include/Storage.hpp"
-#include "../include/Program.h"
+#include "../include/Program.hpp"
#include <string>
#include <json/reader.h>
#include <json/writer.h>
diff --git a/src/plugins/Manga.cpp b/src/plugins/Manga.cpp
index 323794c..4685cec 100644
--- a/src/plugins/Manga.cpp
+++ b/src/plugins/Manga.cpp
@@ -1,5 +1,5 @@
#include "../../plugins/Manga.hpp"
-#include "../../include/Program.h"
+#include "../../include/Program.hpp"
namespace QuickMedia {
TrackResult MangaChaptersPage::track(const std::string &str) {
@@ -10,7 +10,8 @@ namespace QuickMedia {
return TrackResult::ERR;
}
- void MangaChaptersPage::on_navigate_to_page() {
+ void MangaChaptersPage::on_navigate_to_page(BodyItems &body_items) {
+ (void)body_items;
std::string manga_id;
if(extract_id_from_url(content_url, manga_id))
load_manga_content_storage(get_service_name(), content_title, manga_id);
diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp
index a702841..93eb1d6 100644
--- a/src/plugins/Matrix.cpp
+++ b/src/plugins/Matrix.cpp
@@ -3,6 +3,7 @@
#include "../../include/StringUtils.hpp"
#include "../../include/NetUtils.hpp"
#include "../../include/Notification.hpp"
+#include "../../include/Program.hpp"
#include <rapidjson/document.h>
#include <rapidjson/writer.h>
#include <rapidjson/stringbuffer.h>
@@ -521,8 +522,8 @@ namespace QuickMedia {
}
void Matrix::stop_sync() {
- // TODO: Kill the running download in |sync_thread| instead of waiting until sync returns (which can be up to 30 seconds)
sync_running = false;
+ program_kill_in_thread(sync_thread.get_id());
if(sync_thread.joinable())
sync_thread.join();
}
@@ -534,8 +535,12 @@ namespace QuickMedia {
void Matrix::get_room_sync_data(RoomData *room, SyncData &sync_data) {
room->acquire_room_lock();
auto &room_messages = room->get_messages_thread_unsafe();
- sync_data.messages.insert(sync_data.messages.end(), room_messages.begin() + room->messages_read_index, room_messages.end());
- room->messages_read_index = room_messages.size();
+ if(room->messages_read_index <= room_messages.size()) {
+ sync_data.messages.insert(sync_data.messages.end(), room_messages.begin() + room->messages_read_index, room_messages.end());
+ room->messages_read_index = room_messages.size();
+ } else {
+ fprintf(stderr, "Unexpected behavior!!!! get_room_sync_data said read index is %zu but we only have %zu messages\n", room->messages_read_index, room_messages.size());
+ }
if(room->pinned_events_updated) {
sync_data.pinned_events = room->get_pinned_events_unsafe();
room->pinned_events_updated = false;
diff --git a/src/plugins/NyaaSi.cpp b/src/plugins/NyaaSi.cpp
index 8b1efc7..082aaa8 100644
--- a/src/plugins/NyaaSi.cpp
+++ b/src/plugins/NyaaSi.cpp
@@ -1,5 +1,5 @@
#include "../../plugins/NyaaSi.hpp"
-#include "../../include/Program.h"
+#include "../../include/Program.hpp"
#include "../../include/Storage.hpp"
#include "../../include/Notification.hpp"
#include "../../include/StringUtils.hpp"