2026-08-22 22:43:53 +05:30
|
|
|
#include "mainscreen.h"
|
|
|
|
|
#include "html.hpp"
|
|
|
|
|
#include <libtorrent/session.hpp>
|
|
|
|
|
#include <libtorrent/add_torrent_params.hpp>
|
|
|
|
|
#include <libtorrent/torrent_handle.hpp>
|
|
|
|
|
#include <libtorrent/magnet_uri.hpp>
|
|
|
|
|
#include <print>
|
|
|
|
|
#include <iostream>
|
|
|
|
|
#include <fstream>
|
|
|
|
|
#include <QStandardPaths>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
|
|
MainScreen::MainScreen(std::string search_term, QObject *parent)
|
|
|
|
|
: QObject{parent}
|
|
|
|
|
{
|
|
|
|
|
tags_file_path = std::filesystem::path(
|
|
|
|
|
QStandardPaths::writableLocation
|
|
|
|
|
(QStandardPaths::AppLocalDataLocation).toStdString());
|
|
|
|
|
if(!std::filesystem::exists(tags_file_path)) {
|
|
|
|
|
qDebug() << "Created dir.";
|
|
|
|
|
std::filesystem::create_directories(tags_file_path);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
qDebug() << "It already exists.";
|
|
|
|
|
}
|
|
|
|
|
tags_file_path = std::filesystem::path(tags_file_path) / "tags.json";
|
|
|
|
|
qDebug() << "Using tags file location: " << tags_file_path;
|
|
|
|
|
if(search_term == "") {
|
|
|
|
|
renderHomePage();
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
renderSearchPage(search_term);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-25 17:26:04 +05:30
|
|
|
void MainScreen::resumeOldTorrentSession(SearchItem it)
|
|
|
|
|
{
|
|
|
|
|
qDebug() << "Resuming session: " << it.title;
|
|
|
|
|
qDebug() << "Old download path: " << it.download_path.string();
|
|
|
|
|
TorrentSession *session = new TorrentSession(it, it.download_path);
|
|
|
|
|
auto pid = session->start();
|
|
|
|
|
if(pid != 0) {
|
|
|
|
|
session->deleteLater();
|
|
|
|
|
qDebug() << "Parent done.";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-22 22:43:53 +05:30
|
|
|
void MainScreen::renderSearchPage(std::string search_term)
|
|
|
|
|
{
|
|
|
|
|
if(!manager) {
|
|
|
|
|
manager = new QNetworkAccessManager(this);
|
|
|
|
|
}
|
|
|
|
|
disconnect(manager, nullptr, nullptr, nullptr);
|
|
|
|
|
QNetworkRequest req;
|
|
|
|
|
QUrl req_url;
|
|
|
|
|
req_url.setUrl("https://nyaa.si/");
|
|
|
|
|
QUrlQuery Q{{"f","0"},{"c","0_0"},{"s", "seeders"},{"o","desc"}};
|
|
|
|
|
Q.addQueryItem("q", QString::fromStdString(search_term));
|
|
|
|
|
req_url.setQuery(Q);
|
|
|
|
|
req.setUrl(req_url);
|
|
|
|
|
connect(manager, &QNetworkAccessManager::finished, this, &MainScreen::renderSearchPage_);
|
|
|
|
|
manager->get(req);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainScreen::renderHomePage()
|
|
|
|
|
{
|
|
|
|
|
qDebug() << "Render Home Page";
|
2026-08-25 17:26:04 +05:30
|
|
|
if(!socket) {
|
|
|
|
|
socket = new Socket();
|
|
|
|
|
socket_thread = new QThread(this);
|
|
|
|
|
socket->moveToThread(socket_thread);
|
|
|
|
|
connect(this, &MainScreen::startSocket, socket, &Socket::start);
|
|
|
|
|
connect(socket, &Socket::launchTorrentSession, this, &MainScreen::resumeOldTorrentSession);
|
|
|
|
|
socket_thread->start();
|
|
|
|
|
emit startSocket();
|
|
|
|
|
}
|
2026-08-22 22:43:53 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<SearchItem> MainScreen::parseSearchPage(std::string html_page)
|
|
|
|
|
{
|
|
|
|
|
html::parser p;
|
|
|
|
|
auto root = p.parse(html_page);
|
|
|
|
|
qDebug() << root->size() << " elements.";
|
|
|
|
|
auto titles = root->select("div>table>tbody>tr");
|
|
|
|
|
qDebug() << "Found rows: " << titles.size();
|
|
|
|
|
std::vector<SearchItem> results;
|
|
|
|
|
for(auto i: titles) {
|
|
|
|
|
/*
|
|
|
|
|
* title -> name I see in the selection.
|
|
|
|
|
* url -> url for it's nyaa page.
|
|
|
|
|
* seeders
|
|
|
|
|
* leachers
|
|
|
|
|
* downloads
|
|
|
|
|
* timestamp
|
|
|
|
|
* magnet
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
auto g = i->select("td>a[title]");
|
|
|
|
|
SearchItem result;
|
|
|
|
|
for(auto j: g) {
|
|
|
|
|
if(j->has_attr("class") || j->select("img").size() > 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
result.title = QString::fromStdString(j->get_attr("title"));
|
|
|
|
|
result.url = QUrl(QString::fromStdString(j->get_attr("href")));
|
|
|
|
|
}
|
|
|
|
|
g = i->select("a[href*='magnet:?']");
|
|
|
|
|
if(g.size() == 0 || g.size() > 1) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
result.magnet = QString::fromStdString(g[0]->get_attr("href"));
|
|
|
|
|
// Asuming 4th column is size
|
|
|
|
|
// 5th is timestamp
|
|
|
|
|
// 6th is seeders
|
|
|
|
|
// 7th is leachers
|
|
|
|
|
// 8th is total downloads
|
|
|
|
|
g = i->select("td[data-timestamp]");
|
|
|
|
|
if(g.size() > 0)
|
|
|
|
|
result.timestamp = QString::fromStdString(g[0]->get_attr("data-timestamp"));
|
|
|
|
|
g = i->select("td");
|
|
|
|
|
if(g.size() >= 8) {
|
|
|
|
|
result.size = QString::fromStdString(g[3]->to_text());
|
|
|
|
|
result.seeders = QString::fromStdString(g[5]->to_text());
|
|
|
|
|
result.leechers = QString::fromStdString(g[6]->to_text());
|
|
|
|
|
result.downloads = QString::fromStdString(g[7]->to_text());
|
|
|
|
|
}
|
|
|
|
|
results.push_back(result);
|
|
|
|
|
}
|
|
|
|
|
return results;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> MainScreen::getStringEntries(std::vector<SearchItem> res)
|
|
|
|
|
{
|
|
|
|
|
std::vector<std::string> dat;
|
|
|
|
|
for(auto &i: res) {
|
|
|
|
|
dat.push_back(i.title.toStdString());
|
|
|
|
|
}
|
|
|
|
|
return dat;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//SearchMenu is rendered here.
|
|
|
|
|
int MainScreen::displaySearchMenu(std::vector<SearchItem> res)
|
|
|
|
|
{
|
|
|
|
|
using namespace ftxui;
|
|
|
|
|
std::vector<std::string> entries;
|
|
|
|
|
for (const auto& f : res) {
|
|
|
|
|
entries.push_back(f.title.toStdString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int selected = 0;
|
|
|
|
|
bool exit_flag = false;
|
|
|
|
|
auto screen = ScreenInteractive::TerminalOutput();
|
|
|
|
|
MenuOption option;
|
|
|
|
|
std::string status_message = "Use arrows to navigate. Press Enter to select, 'q' to quit.";
|
|
|
|
|
option.on_enter = [&] {
|
|
|
|
|
screen.Exit();
|
|
|
|
|
status_message = "You selected: " + res[selected].title.toStdString();
|
|
|
|
|
};
|
|
|
|
|
option.entries_option.transform = [&res](const EntryState& state) {
|
|
|
|
|
const auto& file = res[state.index];
|
|
|
|
|
auto row = hbox({
|
|
|
|
|
text(file.title.toStdString()) | flex,
|
|
|
|
|
text(file.size.toStdString()) | size(WIDTH, EQUAL, 10),
|
|
|
|
|
text(file.timestamp.toStdString()) | size(WIDTH, EQUAL, 15),
|
|
|
|
|
text(file.seeders.toStdString()) | size(WIDTH, EQUAL, 10),
|
|
|
|
|
text(file.leechers.toStdString()) | size(WIDTH, EQUAL, 10),
|
|
|
|
|
text(file.downloads.toStdString()) | size(WIDTH, EQUAL, 15),
|
|
|
|
|
});
|
|
|
|
|
if (state.active) {
|
|
|
|
|
row = row | inverted | bold;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return row;
|
|
|
|
|
};
|
|
|
|
|
auto menu = Menu(&entries, &selected, option);
|
|
|
|
|
auto renderer = Renderer(menu, [&] {
|
|
|
|
|
Element header = hbox({
|
|
|
|
|
text(" TITLE") | flex,
|
|
|
|
|
text("SIZE") | size(WIDTH, EQUAL, 12),
|
|
|
|
|
text("DATE") | size(WIDTH, EQUAL, 12),
|
|
|
|
|
text("SEEDERS") | size(WIDTH, EQUAL, 12),
|
|
|
|
|
text("LEECHERS") | size(WIDTH, EQUAL, 12),
|
|
|
|
|
text("DOWNLOADS") | size(WIDTH, EQUAL, 12),
|
|
|
|
|
}) | underlined;
|
|
|
|
|
|
|
|
|
|
return window(text(" Search Results "),
|
|
|
|
|
vbox({
|
|
|
|
|
header,
|
|
|
|
|
separatorEmpty(),
|
|
|
|
|
menu->Render()
|
|
|
|
|
})
|
|
|
|
|
) | size(WIDTH, GREATER_THAN, 50);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto container = CatchEvent(renderer, [&](Event event) {
|
|
|
|
|
// Checking for 'q', 'Q', or Escape
|
|
|
|
|
if (event == Event::Character("q") || event == Event::Character("Q") || event == Event::Escape) {
|
|
|
|
|
screen.Exit();
|
|
|
|
|
exit_flag = true;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
});
|
|
|
|
|
screen.Loop(container);
|
|
|
|
|
if(exit_flag)
|
|
|
|
|
selected = -1;
|
|
|
|
|
qDebug() << "Selected element = " << selected;
|
|
|
|
|
return selected;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//TorrentSession is created here.
|
|
|
|
|
int MainScreen::startDownloadIndex(std::vector<SearchItem> res, int index)
|
|
|
|
|
{
|
|
|
|
|
qDebug() << "Enter Download Path: ";
|
|
|
|
|
auto expected_path = runBasicShell(res[index]);
|
|
|
|
|
if(!expected_path.has_value()) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
auto download_path = expected_path.value();
|
2026-08-25 17:26:04 +05:30
|
|
|
res[index].download_path = download_path;
|
2026-08-22 22:43:53 +05:30
|
|
|
TorrentSession *session = new TorrentSession(res[index], download_path);
|
|
|
|
|
auto pid = session->start();
|
|
|
|
|
if(pid != 0) {
|
|
|
|
|
session->deleteLater();
|
|
|
|
|
qDebug() << "Parent done.";
|
|
|
|
|
}
|
|
|
|
|
return pid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::expected<std::string,int> MainScreen::runBasicShell(SearchItem it)
|
|
|
|
|
{
|
|
|
|
|
qDebug() << "Running Basic Shell";
|
|
|
|
|
qDebug() << "Valid commands: ls, cd, select, pwd, rmdir, mkdir, set, lstags and exit.";
|
2026-08-25 17:26:04 +05:30
|
|
|
qDebug() << "mkdir: Use $title to create a folder with name of title.";
|
2026-08-22 22:43:53 +05:30
|
|
|
qDebug() << "select: Selects the present working dir"
|
|
|
|
|
" as download directory if no arguments are provided."
|
|
|
|
|
"Else uses the provided argument as a tag or a path.";
|
|
|
|
|
qDebug() << "As a select argument the tag name be prefixed by '$' like '$TAG_NAME'.";
|
|
|
|
|
qDebug() << "set: used for setting a tag. set [TAG_NAME] [FOLDER PATH]";
|
|
|
|
|
qDebug() << "lstags: Lists all tags stored.";
|
|
|
|
|
namespace fs = std::filesystem;
|
2026-08-25 17:26:04 +05:30
|
|
|
std::string extras = "";
|
2026-08-22 22:43:53 +05:30
|
|
|
while(true) {
|
2026-08-25 17:26:04 +05:30
|
|
|
std::print("nsh|{}>{}",fs::current_path(), extras);
|
|
|
|
|
extras = "";
|
2026-08-22 22:43:53 +05:30
|
|
|
std::string command;
|
|
|
|
|
std::getline(std::cin, command);
|
|
|
|
|
if(command == "ls") {
|
|
|
|
|
system("ls");
|
|
|
|
|
}
|
|
|
|
|
if(command.starts_with("cd")) {
|
|
|
|
|
auto arg = stripForArgument(command, 1);
|
|
|
|
|
auto abs = fs::absolute(arg);
|
|
|
|
|
if(fs::is_directory(abs))
|
|
|
|
|
fs::current_path(abs);
|
|
|
|
|
else {
|
|
|
|
|
if(arg.starts_with('$')) {
|
|
|
|
|
arg = arg.erase(0, 1);
|
|
|
|
|
auto dir = Tags::getTag(tags_file_path, arg);
|
|
|
|
|
if(fs::exists(fs::absolute(dir))) {
|
|
|
|
|
fs::current_path(fs::absolute(dir));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
qDebug() << "No such tag: " << arg;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
qDebug() << "No such directory: " << abs.string();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(command == "pwd") {
|
|
|
|
|
qDebug() << QString::fromStdString(fs::current_path());
|
|
|
|
|
}
|
|
|
|
|
if(command == "exit") {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if(command.starts_with("select")) {
|
|
|
|
|
if(command == "select") {
|
|
|
|
|
Tags::setTag(tags_file_path, "last", fs::current_path().string());
|
|
|
|
|
return fs::current_path();
|
|
|
|
|
}
|
|
|
|
|
auto arg = stripForArgument(command, 1);
|
|
|
|
|
if(arg.starts_with("$")) {
|
|
|
|
|
arg = arg.erase(0,1);
|
|
|
|
|
auto string_path = Tags::getTag(tags_file_path, arg);
|
|
|
|
|
if(string_path == "<NONE>") {
|
|
|
|
|
qDebug() << arg << " tag not found in tags file.";
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
Tags::setTag(tags_file_path, "last", fs::current_path().string());
|
|
|
|
|
return fs::path(string_path);
|
|
|
|
|
}
|
|
|
|
|
else if(fs::is_directory(fs::absolute(arg))) {
|
|
|
|
|
auto path = fs::absolute(arg);
|
|
|
|
|
Tags::setTag(tags_file_path, "last", path.string());
|
|
|
|
|
return path;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
qDebug() << arg << " is not a valid path or tag.";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(command.starts_with("set")) {
|
|
|
|
|
auto arg1 = stripForArgument(command, 1);
|
|
|
|
|
auto arg2 = stripForArgument(command, 2);
|
|
|
|
|
if(arg1 == "" || arg2 == "") {
|
|
|
|
|
qDebug() << "Invalid arguments for set.";
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if(!fs::is_directory(fs::canonical(arg2))) {
|
|
|
|
|
qDebug() << arg2 << " is not a valid path.";
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if(arg1.contains(' ') || arg1.contains('\n') || arg1.contains('\t') || arg1.contains('\r') || arg1.contains('\v')) {
|
|
|
|
|
qDebug() << arg1 << " can not have a whitespace char.";
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
arg2 = fs::canonical(fs::path(arg2));
|
|
|
|
|
qDebug() << "Setting tag:" << arg1 << ":" << arg2;
|
|
|
|
|
Tags::setTag(tags_file_path, arg1, arg2);
|
|
|
|
|
}
|
|
|
|
|
if(command == "lstags") {
|
|
|
|
|
auto tags = Tags::getAllTags(tags_file_path);
|
|
|
|
|
for(auto &[key, value]: tags) {
|
|
|
|
|
qDebug() << key << ": " << value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(command.starts_with("rmdir")) {
|
|
|
|
|
auto arg = stripForArgument(command, 1);
|
|
|
|
|
fs::path path_to_remove = fs::current_path() / fs::path(arg);
|
|
|
|
|
std::string t;
|
|
|
|
|
if(!fs::is_directory(path_to_remove)) {
|
|
|
|
|
qDebug() << QString::fromStdString(path_to_remove) << " is not a directory.";
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
qDebug() << "Are you sure you want to remove: " << QString::fromStdString(path_to_remove) << "(y/n)?";
|
|
|
|
|
std::cin >> t;
|
|
|
|
|
std::cin.clear();
|
|
|
|
|
if(t.size() >= 1) {
|
|
|
|
|
if(t[0] == 'y' || t[0] == 'Y')
|
|
|
|
|
fs::remove(path_to_remove);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(command.starts_with("mkdir")) {
|
|
|
|
|
auto arg = stripForArgument(command, 1);
|
2026-08-25 17:26:04 +05:30
|
|
|
if(!arg.starts_with('$')) {
|
|
|
|
|
fs::path path_to_create = fs::current_path() / fs::path(arg);
|
|
|
|
|
bool t = fs::create_directory(path_to_create);
|
|
|
|
|
if(!t) {
|
|
|
|
|
qDebug() << "Failed to create path: " << QString::fromStdString(path_to_create);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if(arg == "$title") {
|
|
|
|
|
extras = "mkdir " + it.title.toStdString();
|
|
|
|
|
continue;
|
2026-08-22 22:43:53 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return std::unexpected(-1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string MainScreen::stripForArgument(std::string command, int index)
|
|
|
|
|
{
|
|
|
|
|
std::istringstream iss(command);
|
|
|
|
|
std::string word;
|
|
|
|
|
std::vector<std::string> tokens;
|
|
|
|
|
while (iss >> word) {
|
|
|
|
|
tokens.push_back(word);
|
|
|
|
|
}
|
|
|
|
|
if (index > 0 && index < tokens.size()) {
|
|
|
|
|
return tokens[index];
|
|
|
|
|
}
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-25 17:26:04 +05:30
|
|
|
void MainScreen::statusFromSocket(Status st)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-22 22:43:53 +05:30
|
|
|
void MainScreen::renderSearchPage_(QNetworkReply *rep)
|
|
|
|
|
{
|
|
|
|
|
QByteArray data = rep->readAll();
|
|
|
|
|
qDebug() << "Render Search Page";
|
|
|
|
|
std::string page = data.toStdString();
|
|
|
|
|
if(data.size() == 0) {
|
|
|
|
|
qDebug() << "Invalid reply; Retrying...";
|
|
|
|
|
auto req = rep->request();
|
|
|
|
|
manager->get(req);
|
|
|
|
|
rep->deleteLater();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
rep->deleteLater();
|
|
|
|
|
auto res = parseSearchPage(page);
|
|
|
|
|
for(auto i: res) {
|
|
|
|
|
i.printData();
|
|
|
|
|
qDebug() << "================================";
|
|
|
|
|
}
|
|
|
|
|
qDebug() << "Total got from response: " << res.size();
|
|
|
|
|
int index = displaySearchMenu(res);
|
|
|
|
|
if(index >= 0) {
|
|
|
|
|
auto pid = startDownloadIndex(res, index);
|
|
|
|
|
if(pid == 0)
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
renderHomePage();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SearchItem::printData()
|
|
|
|
|
{
|
|
|
|
|
qDebug() << "Title: " << title;
|
|
|
|
|
qDebug() << "Url: " << url;
|
|
|
|
|
qDebug() << "Size: " << size;
|
|
|
|
|
qDebug() << "Seeders: " << seeders;
|
|
|
|
|
qDebug() << "Leechers: " << leechers;
|
|
|
|
|
qDebug() << "Downloads: " << downloads;
|
|
|
|
|
qDebug() << "Timestamp: " << timestamp;
|
|
|
|
|
qDebug() << "Magnet: " << magnet;
|
2026-08-25 17:26:04 +05:30
|
|
|
qDebug() << "Download path: " << download_path.string();
|
2026-08-22 22:43:53 +05:30
|
|
|
}
|
|
|
|
|
|
2026-08-24 15:23:46 +05:30
|
|
|
void SearchItem::writeData(std::string file_path)
|
|
|
|
|
{
|
|
|
|
|
std::fstream out(file_path, std::fstream::out);
|
|
|
|
|
if(!out.is_open()) {
|
|
|
|
|
qDebug() << "Failed to write to file: " << file_path;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-08-25 17:26:04 +05:30
|
|
|
qDebug() << "Writing title: " << title;
|
|
|
|
|
qDebug() << "Writing url: " << url;
|
|
|
|
|
qDebug() << "Writing magnet: " << magnet;
|
|
|
|
|
qDebug() << "Writing download_path: " << download_path.string();
|
2026-08-24 15:23:46 +05:30
|
|
|
out << title.toStdString() << std::endl;
|
|
|
|
|
out << url.toDisplayString().toStdString() << std::endl;
|
|
|
|
|
out << magnet.toStdString() << std::endl;
|
2026-08-25 17:26:04 +05:30
|
|
|
out << download_path.string() << std::endl;
|
2026-08-24 15:23:46 +05:30
|
|
|
out.flush();
|
|
|
|
|
out.close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SearchItem::readFromData(std::string file_path)
|
|
|
|
|
{
|
|
|
|
|
std::fstream in(file_path, std::fstream::in);
|
|
|
|
|
if(!in.is_open()) {
|
|
|
|
|
qDebug() << "Failed to read from file: " << file_path;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-08-25 17:26:04 +05:30
|
|
|
std::string stitle, surl, smagnet, spath;
|
|
|
|
|
std::getline(in, stitle);
|
|
|
|
|
std::getline(in, surl);
|
|
|
|
|
std::getline(in, smagnet);
|
|
|
|
|
std::getline(in, spath);
|
2026-08-24 15:23:46 +05:30
|
|
|
in.close();
|
|
|
|
|
title = QString::fromStdString(stitle);
|
|
|
|
|
url = QUrl(QString::fromStdString(surl));
|
|
|
|
|
magnet = QString::fromStdString(smagnet);
|
2026-08-25 17:26:04 +05:30
|
|
|
download_path = std::filesystem::canonical(spath);
|
2026-08-24 15:23:46 +05:30
|
|
|
in.close();
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-22 22:43:53 +05:30
|
|
|
std::string MainScreen::Tags::getTag(std::string file_name, std::string key)
|
|
|
|
|
{
|
|
|
|
|
QJsonDocument doc = QJsonDocument::fromJson(readFile(file_name));
|
|
|
|
|
QJsonObject obj = doc.object();
|
|
|
|
|
QString value = obj[QString::fromStdString(key)].toString("<NONE>");
|
|
|
|
|
return value.toStdString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainScreen::Tags::setTag(std::string file_name, std::string key, std::string value)
|
|
|
|
|
{
|
|
|
|
|
QJsonDocument doc = QJsonDocument::fromJson(readFile(file_name));
|
|
|
|
|
QJsonObject obj = doc.object();
|
|
|
|
|
obj[QString::fromStdString(key)] = QString::fromStdString(value);
|
|
|
|
|
doc.setObject(obj);
|
|
|
|
|
bool write = writeFile(file_name, doc.toJson());
|
|
|
|
|
if(!write)
|
|
|
|
|
qDebug() << "Failed to write to file: " << file_name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QByteArray MainScreen::Tags::readFile(std::string file_name)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
std::fstream fin(file_name, std::fstream::in);
|
|
|
|
|
if(!fin.is_open())
|
|
|
|
|
return QByteArray();
|
|
|
|
|
std::string data, fdata;
|
|
|
|
|
while(std::getline(fin, data)) {
|
|
|
|
|
fdata += data;
|
|
|
|
|
}
|
|
|
|
|
fin.flush();
|
|
|
|
|
fin.close();
|
|
|
|
|
return QByteArray::fromStdString(fdata);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MainScreen::Tags::writeFile(std::string file_name, QByteArray data)
|
|
|
|
|
{
|
|
|
|
|
std::fstream fout(file_name, std::fstream::out);
|
|
|
|
|
if(!fout.is_open())
|
|
|
|
|
return false;
|
|
|
|
|
fout << data.toStdString();
|
|
|
|
|
fout.flush();
|
|
|
|
|
fout.close();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::unordered_map<std::string, std::string> MainScreen::Tags::getAllTags(std::string file_name)
|
|
|
|
|
{
|
|
|
|
|
QJsonDocument doc = QJsonDocument::fromJson(readFile(file_name));
|
|
|
|
|
QJsonObject obj = doc.object();
|
|
|
|
|
std::unordered_map<std::string, std::string> t;
|
|
|
|
|
for(auto &key: obj.keys()) {
|
|
|
|
|
t[key.toStdString()] = obj[key].toString().toStdString();
|
|
|
|
|
}
|
|
|
|
|
return t;
|
|
|
|
|
}
|
2026-08-25 17:26:04 +05:30
|
|
|
|
|
|
|
|
Socket::Socket(QObject *parent)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//For each resume file found either start the libtorrent session if it was not running or
|
|
|
|
|
//Ask each session their status.
|
|
|
|
|
void Socket::start()
|
|
|
|
|
{
|
|
|
|
|
// Generate a list of all active libtorrent sessions.
|
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
|
auto temp_dir = QStandardPaths::writableLocation(QStandardPaths::TempLocation).toStdString();
|
|
|
|
|
auto temp_dir_path = fs::path(temp_dir);
|
|
|
|
|
fs::directory_iterator itr(temp_dir_path);
|
|
|
|
|
for(auto &file_folder: itr) {
|
|
|
|
|
if(file_folder.is_regular_file() || file_folder.is_socket()) {
|
|
|
|
|
auto file_name = file_folder.path().string();
|
|
|
|
|
if(file_name.contains("nekodl")
|
|
|
|
|
&& file_name.ends_with(".sock")) {
|
|
|
|
|
QLocalSocket *socket = new QLocalSocket(this);
|
|
|
|
|
connect(socket, &QLocalSocket::readyRead, this, &Socket::socketReadyRead);
|
|
|
|
|
socket->connectToServer(QString::fromStdString(file_name));
|
|
|
|
|
qDebug() << "Opening file: " << file_name;
|
|
|
|
|
if(socket->isOpen()) {
|
|
|
|
|
qDebug() << "Open socket: " << file_name;
|
|
|
|
|
QString data = "<STATUS>";
|
|
|
|
|
socket->write(data.toUtf8());
|
|
|
|
|
socket->flush();
|
|
|
|
|
active_sockets[file_name] = socket;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
qDebug() << "Stale socket: " << file_name;
|
|
|
|
|
fs::remove(file_name);
|
|
|
|
|
disconnect(socket);
|
|
|
|
|
socket->deleteLater();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
qDebug() << "Checking temp dir.";
|
|
|
|
|
if(!recheck) {
|
|
|
|
|
if(!timer) {
|
|
|
|
|
timer = new QTimer(this);
|
|
|
|
|
timer->setInterval(5000);
|
|
|
|
|
timer->setSingleShot(true);
|
|
|
|
|
connect(timer, &QTimer::timeout, this, [&](){
|
|
|
|
|
startNotDownloadingSessions();
|
|
|
|
|
});
|
|
|
|
|
timer->start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if(checked_resume_files) {
|
|
|
|
|
if(timer) {
|
|
|
|
|
timer->deleteLater();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
recheck = new QTimer(this);
|
|
|
|
|
recheck->setInterval(3000);
|
|
|
|
|
recheck->setSingleShot(false);
|
|
|
|
|
connect(recheck, &QTimer::timeout, this, [this](){
|
|
|
|
|
start();
|
|
|
|
|
});
|
|
|
|
|
recheck->start();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Start all sessions with a .resume_file but
|
|
|
|
|
// are not in list of active libtorrent session.
|
|
|
|
|
void Socket::startNotDownloadingSessions()
|
|
|
|
|
{
|
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
|
fs::path resume_file_location = fs::path(
|
|
|
|
|
QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation).toStdString()
|
|
|
|
|
);
|
|
|
|
|
resume_file_location = resume_file_location / "resumes";
|
|
|
|
|
if(!fs::is_directory(resume_file_location)) {
|
|
|
|
|
fs::create_directories(resume_file_location);
|
|
|
|
|
}
|
|
|
|
|
fs::directory_iterator itr(resume_file_location);
|
|
|
|
|
for(auto &file_folder: itr) {
|
|
|
|
|
|
|
|
|
|
qDebug() << "Checking: " << file_folder.path().string();
|
|
|
|
|
if(fs::is_regular_file(file_folder)) {
|
|
|
|
|
if(file_folder.path().extension() == ".resume_file") {
|
|
|
|
|
//Load resume file.
|
|
|
|
|
qDebug() << "Resume file found: " << file_folder.path().string();
|
|
|
|
|
auto parent_path = file_folder.path().parent_path();
|
|
|
|
|
auto file_name = parent_path / file_folder.path().stem();
|
|
|
|
|
auto searchitem_file_path = file_name.string() + ".searchitem";
|
|
|
|
|
qDebug() << parent_path.string() << ": " << file_name.string();
|
|
|
|
|
qDebug() << "Search item path: " << searchitem_file_path;
|
|
|
|
|
if(fs::exists(searchitem_file_path))
|
|
|
|
|
{
|
|
|
|
|
SearchItem it;
|
|
|
|
|
it.readFromData(searchitem_file_path);
|
|
|
|
|
qDebug() << "Recovering from: " << it.title;
|
|
|
|
|
if(!isTitleInActiveSession(it.title)) {
|
|
|
|
|
qDebug() << "Title not in active session list.";
|
|
|
|
|
it.printData();
|
|
|
|
|
emit launchTorrentSession(it);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
qDebug() << "Failed to recover: " << searchitem_file_path;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
checked_resume_files = true;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Socket::isTitleInActiveSession(QString title)
|
|
|
|
|
{
|
|
|
|
|
return active_sockets.find(title.toStdString()) != active_sockets.end();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void Socket::socketReadyRead()
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|