Progress: 25%.
This commit is contained in:
commit
2fd0d6d3d4
7 changed files with 917 additions and 0 deletions
84
.gitignore
vendored
Normal file
84
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
# This file is used to ignore files which are generated
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
*~
|
||||
*.autosave
|
||||
*.a
|
||||
*.core
|
||||
*.moc
|
||||
*.o
|
||||
*.obj
|
||||
*.orig
|
||||
*.rej
|
||||
*.so
|
||||
*.so.*
|
||||
*_pch.h.cpp
|
||||
*_resource.rc
|
||||
*.qm
|
||||
.#*
|
||||
*.*#
|
||||
core
|
||||
!core/
|
||||
tags
|
||||
.DS_Store
|
||||
.directory
|
||||
*.debug
|
||||
Makefile*
|
||||
*.prl
|
||||
*.app
|
||||
moc_*.cpp
|
||||
ui_*.h
|
||||
qrc_*.cpp
|
||||
Thumbs.db
|
||||
*.res
|
||||
*.rc
|
||||
/.qmake.cache
|
||||
/.qmake.stash
|
||||
**/.qmlls.ini
|
||||
|
||||
# qtcreator generated files
|
||||
*.pro.user*
|
||||
*.qbs.user*
|
||||
CMakeLists.txt.user*
|
||||
|
||||
# xemacs temporary files
|
||||
*.flc
|
||||
|
||||
# Vim temporary files
|
||||
.*.swp
|
||||
|
||||
# Visual Studio generated files
|
||||
*.ib_pdb_index
|
||||
*.idb
|
||||
*.ilk
|
||||
*.pdb
|
||||
*.sln
|
||||
*.suo
|
||||
*.vcproj
|
||||
*vcproj.*.*.user
|
||||
*.ncb
|
||||
*.sdf
|
||||
*.opensdf
|
||||
*.vcxproj
|
||||
*vcxproj.*
|
||||
|
||||
# MinGW generated files
|
||||
*.Debug
|
||||
*.Release
|
||||
|
||||
# Python byte code
|
||||
*.pyc
|
||||
|
||||
# Binaries
|
||||
# --------
|
||||
*.dll
|
||||
*.exe
|
||||
|
||||
# Directories with generated files
|
||||
.moc/
|
||||
.obj/
|
||||
.pch/
|
||||
.rcc/
|
||||
.uic/
|
||||
/build*/
|
||||
/.qtcreator/
|
||||
53
CMakeLists.txt
Normal file
53
CMakeLists.txt
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
cmake_minimum_required(VERSION 3.19)
|
||||
project(nekodl LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 26)
|
||||
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(ftxui
|
||||
GIT_REPOSITORY https://github.com/ArthurSonzogni/ftxui
|
||||
GIT_TAG v7.0.3
|
||||
)
|
||||
FetchContent_MakeAvailable(ftxui)
|
||||
|
||||
FetchContent_Declare(htmlparser
|
||||
GIT_REPOSITORY https://github.com/mylogin/htmlparser
|
||||
GIT_TAG f468bb3eb6ed2ac38b7c5cb8e6789346f888c69a
|
||||
)
|
||||
FetchContent_MakeAvailable(htmlparser)
|
||||
|
||||
find_package(Qt6 6.5 REQUIRED COMPONENTS Core Network)
|
||||
find_package(Boost CONFIG REQUIRED)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(nekodl
|
||||
main.cpp
|
||||
mainscreen.h mainscreen.cpp
|
||||
torrentsession.h torrentsession.cpp
|
||||
)
|
||||
|
||||
target_include_directories(nekodl
|
||||
PRIVATE
|
||||
${htmlparser_SOURCE_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(nekodl
|
||||
PRIVATE
|
||||
Qt::Core
|
||||
Qt::Network
|
||||
libtorrent.so
|
||||
ftxui::ftxui
|
||||
htmlparser
|
||||
OpenSSL::SSL
|
||||
OpenSSL::Crypto
|
||||
)
|
||||
|
||||
target_compile_definitions(nekodl PRIVATE TORRENT_USE_OPENSSL=1)
|
||||
|
||||
install(TARGETS nekodl
|
||||
BUNDLE DESTINATION .
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
)
|
||||
50
main.cpp
Normal file
50
main.cpp
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#include <QCoreApplication>
|
||||
#include <expected>
|
||||
#include <iostream>
|
||||
#include "mainscreen.h"
|
||||
|
||||
std::expected<std::string, int> parseArgs(int argc, char **argv) {
|
||||
std::vector<std::string> arg_list;
|
||||
for(int i=1;i < argc; i++) {
|
||||
arg_list.push_back(argv[i]);
|
||||
}
|
||||
if (argc == 1) {
|
||||
return "";
|
||||
}
|
||||
if(argc == 2) {
|
||||
return arg_list[0];
|
||||
}
|
||||
else {
|
||||
return std::unexpected(1);
|
||||
}
|
||||
}
|
||||
|
||||
void printHelp(int t) {
|
||||
if(t == 1) {
|
||||
std::cout << "Invalid number of arguments." << std::endl;
|
||||
}
|
||||
std::cout << "USAGE: nekodl [Search Term]" << std::endl;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication a(argc, argv);
|
||||
auto args = parseArgs(argc, argv);
|
||||
if(!args.has_value()) {
|
||||
printHelp(args.error());
|
||||
return -1;
|
||||
}
|
||||
MainScreen screen(args.value());
|
||||
// Set up code that uses the Qt event loop here.
|
||||
// Call QCoreApplication::quit() or QCoreApplication::exit() to quit the application.
|
||||
// A not very useful example would be including
|
||||
// #include <QTimer>
|
||||
// near the top of the file and calling
|
||||
// QTimer::singleShot(5000, &a, &QCoreApplication::quit);
|
||||
// which quits the application after 5 seconds.
|
||||
|
||||
// If you do not need a running Qt event loop, remove the call
|
||||
// to QCoreApplication::exec() or use the Non-Qt Plain C++ Application template.
|
||||
|
||||
return QCoreApplication::exec();
|
||||
}
|
||||
440
mainscreen.cpp
Normal file
440
mainscreen.cpp
Normal file
|
|
@ -0,0 +1,440 @@
|
|||
#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);
|
||||
}
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
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();
|
||||
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.";
|
||||
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;
|
||||
while(true) {
|
||||
std::print("nsh|{}>",fs::current_path());
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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 "";
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
69
mainscreen.h
Normal file
69
mainscreen.h
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#ifndef MAINSCREEN_H
|
||||
#define MAINSCREEN_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonValue>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QUrlQuery>
|
||||
#include <ftxui/ftxui.hpp>
|
||||
#include <expected>
|
||||
#include <filesystem>
|
||||
#include <ftxui/component/component.hpp>
|
||||
#include <ftxui/component/screen_interactive.hpp>
|
||||
#include <ftxui/dom/elements.hpp>
|
||||
#include "torrentsession.h"
|
||||
|
||||
|
||||
|
||||
struct SearchItem {
|
||||
QString title;
|
||||
QUrl url;
|
||||
QString size;
|
||||
QString seeders;
|
||||
QString leechers;
|
||||
QString downloads;
|
||||
QString timestamp;
|
||||
QString magnet;
|
||||
void printData();
|
||||
};
|
||||
|
||||
class MainScreen : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
class Tags {
|
||||
public:
|
||||
Tags() = delete;
|
||||
static std::string getTag(std::string file_name, std::string key);
|
||||
static void setTag(std::string file_name, std::string key, std::string value);
|
||||
static std::unordered_map<std::string, std::string> getAllTags(std::string file_name);
|
||||
|
||||
private:
|
||||
static QByteArray readFile(std::string file_name);
|
||||
static bool writeFile(std::string file_name, QByteArray data);
|
||||
};
|
||||
|
||||
QNetworkAccessManager *manager = nullptr;
|
||||
std::string tags_file_path = "";
|
||||
public:
|
||||
explicit MainScreen(std::string search_term = "", QObject *parent = nullptr);
|
||||
signals:
|
||||
private:
|
||||
void renderSearchPage(std::string search_term);
|
||||
void renderHomePage();
|
||||
std::vector<SearchItem> parseSearchPage(std::string html_page);
|
||||
std::vector<std::string> getStringEntries(std::vector<SearchItem> res);
|
||||
int displaySearchMenu(std::vector<SearchItem> res);
|
||||
int startDownloadIndex(std::vector<SearchItem> res, int index);
|
||||
std::expected<std::string,int> runBasicShell(SearchItem it);
|
||||
std::string stripForArgument(std::string command, int index);
|
||||
private slots:
|
||||
void renderSearchPage_(QNetworkReply *rep);
|
||||
};
|
||||
|
||||
#endif // MAINSCREEN_H
|
||||
152
torrentsession.cpp
Normal file
152
torrentsession.cpp
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
#include "torrentsession.h"
|
||||
#include "mainscreen.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
TorrentSession::TorrentSession(SearchItem it, std::filesystem::path download_path, QObject *parent)
|
||||
{
|
||||
this->item = new SearchItem(it);
|
||||
this->download_path = download_path;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
int TorrentSession::start()
|
||||
{
|
||||
auto pid = fork();
|
||||
if(pid != 0) {
|
||||
qDebug() << "Process id: " << getpid() << "forked.";
|
||||
return pid;
|
||||
}
|
||||
qDebug() << "Process id: " << getpid() <<
|
||||
"created for torrent session with download path: " << download_path.string();
|
||||
|
||||
manager = new SocketManager();
|
||||
socket_manager_thread = new QThread(this);
|
||||
manager->moveToThread(socket_manager_thread);
|
||||
socket_manager_thread->start();
|
||||
connect(this, &TorrentSession::startSocketManager, manager, &SocketManager::start);
|
||||
connect(this, &TorrentSession::sendStatusSignalToSocketManager, manager, &SocketManager::statusSignalFromTorrentSession);
|
||||
connect(manager, &SocketManager::status, this, &TorrentSession::sendStatus);
|
||||
connect(manager, &SocketManager::pause, this, &TorrentSession::pause);
|
||||
connect(manager, &SocketManager::resume, this, &TorrentSession::resume);
|
||||
connect(manager, &SocketManager::remove, this, &TorrentSession::remove);
|
||||
|
||||
emit startSocketManager();
|
||||
connect(&start_timer, &QTimer::timeout, this, &TorrentSession::launchSession);
|
||||
start_timer.setInterval(300);
|
||||
start_timer.setSingleShot(true);
|
||||
start_timer.start();
|
||||
return pid;
|
||||
}
|
||||
|
||||
void TorrentSession::sendStatus()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void TorrentSession::pause()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void TorrentSession::resume()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void TorrentSession::remove()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//libtorrent stuff starts here.
|
||||
void TorrentSession::launchSession()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
SocketManager::SocketManager(QObject *parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SocketManager::start()
|
||||
{
|
||||
server = new QLocalServer(this);
|
||||
connect(server, &QLocalServer::newConnection, this, &SocketManager::addConnection);
|
||||
auto unique_path = testDir("/tmp/nekodl", ".sock");
|
||||
bool f = server->listen(QString::fromStdString(unique_path.string()));
|
||||
if(!f) {
|
||||
qDebug() << "Failed to start server";
|
||||
}
|
||||
}
|
||||
|
||||
void SocketManager::statusSignalFromTorrentSession(QByteArray data)
|
||||
{
|
||||
if(socket && socket->isOpen() && socket->isValid()) {
|
||||
socket->write(data);
|
||||
socket->flush();
|
||||
}
|
||||
}
|
||||
|
||||
void SocketManager::addConnection()
|
||||
{
|
||||
if(server->hasPendingConnections()) {
|
||||
if(socket) {
|
||||
socket->deleteLater();
|
||||
}
|
||||
socket = server->nextPendingConnection();
|
||||
connect(socket, &QLocalSocket::readyRead, this, &SocketManager::commandFromClient);
|
||||
}
|
||||
}
|
||||
|
||||
void SocketManager::commandFromClient()
|
||||
{
|
||||
if(socket) {
|
||||
QByteArray big_data;
|
||||
while(socket->bytesAvailable()) {
|
||||
auto data = socket->readAll();
|
||||
big_data += data;
|
||||
}
|
||||
QString data(big_data);
|
||||
sendSignalToTorrentSession(data);
|
||||
}
|
||||
}
|
||||
|
||||
void SocketManager::sendSignalToTorrentSession(QString command)
|
||||
{
|
||||
if(command == "status") {
|
||||
emit status();
|
||||
}
|
||||
if(command == "pause") {
|
||||
emit pause();
|
||||
}
|
||||
if(command == "resume") {
|
||||
emit resume();
|
||||
}
|
||||
if(command == "remove") {
|
||||
emit remove();
|
||||
}
|
||||
}
|
||||
|
||||
std::string SocketManager::numToStr(int num)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << num;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::filesystem::path SocketManager::testDir(std::string file_name, std::string file_format)
|
||||
{
|
||||
int i = 1;
|
||||
while(true) {
|
||||
std::filesystem::path final_path = std::filesystem::path(file_name + "_" + numToStr(i++) + file_format);
|
||||
qDebug() << "Checking path: " << final_path.string();
|
||||
if(!std::filesystem::exists(final_path)) {
|
||||
return final_path;
|
||||
}
|
||||
}
|
||||
}
|
||||
69
torrentsession.h
Normal file
69
torrentsession.h
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#ifndef TORRENTSESSION_H
|
||||
#define TORRENTSESSION_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QLocalServer>
|
||||
#include <QLocalSocket>
|
||||
#include <QProcess>
|
||||
#include <filesystem>
|
||||
#include <QTimer>
|
||||
#include <unistd.h>
|
||||
|
||||
|
||||
struct SearchItem;
|
||||
|
||||
class SocketManager: public QObject {
|
||||
Q_OBJECT
|
||||
QLocalServer *server = nullptr;
|
||||
QLocalSocket *socket = nullptr;
|
||||
public:
|
||||
SocketManager(QObject *parent = nullptr);
|
||||
signals:
|
||||
void status();
|
||||
void resume();
|
||||
void pause();
|
||||
void remove();
|
||||
|
||||
public slots:
|
||||
void start();
|
||||
void statusSignalFromTorrentSession(QByteArray data);
|
||||
|
||||
private slots:
|
||||
void addConnection();
|
||||
void commandFromClient();
|
||||
private:
|
||||
void sendSignalToTorrentSession(QString command);
|
||||
std::filesystem::path testDir(std::string dir_name, std::string file_format);
|
||||
std::string numToStr(int num);
|
||||
};
|
||||
|
||||
class TorrentSession : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
SearchItem *item = nullptr;
|
||||
std::filesystem::path download_path;
|
||||
enum class Status {
|
||||
Downloading,
|
||||
Paused,
|
||||
};
|
||||
SocketManager *manager = nullptr;
|
||||
QThread *socket_manager_thread = nullptr;
|
||||
QTimer start_timer;
|
||||
public:
|
||||
explicit TorrentSession(SearchItem it, std::filesystem::path download_path, QObject *parent = nullptr);
|
||||
int start();
|
||||
signals:
|
||||
void startSocketManager();
|
||||
void sendStatusSignalToSocketManager(QByteArray data);
|
||||
public slots:
|
||||
void sendStatus();
|
||||
void pause();
|
||||
void resume();
|
||||
void remove();
|
||||
private slots:
|
||||
void launchSession();
|
||||
signals:
|
||||
};
|
||||
|
||||
#endif // TORRENTSESSION_H
|
||||
Loading…
Reference in a new issue