@ -1,6 +0,0 @@ | |||||
#!/bin/sh | |||||
mkdir -p build/debug && \ | |||||
cd build/debug && \ | |||||
qmake ../../torrent-client-v2.pro -spec linux-g++ CONFIG+=debug && \ | |||||
make |
@ -1,6 +0,0 @@ | |||||
#!/bin/sh | |||||
mkdir -p build/release && \ | |||||
cd build/release && \ | |||||
qmake ../../torrent-client-v2.pro -spec linux-g++ && \ | |||||
make |
@ -0,0 +1,8 @@ | |||||
#!/bin/sh | |||||
# This script was designed to be run from Qt Creator through Docker | |||||
mkdir -p build/debug && \ | |||||
cd build/debug && \ | |||||
qmake ../../torrent-client-v2.pro -spec linux-g++ CONFIG+=debug CONFIG-=qml_debug CONFIG-=qtquickcompiler && \ | |||||
make -j 32 |
@ -0,0 +1,8 @@ | |||||
#!/bin/sh | |||||
# This script was designed to be run from Qt Creator through Docker | |||||
mkdir -p build/release && \ | |||||
cd build/release && \ | |||||
qmake ../../torrent-client-v2.pro -spec linux-g++ CONFIG-=qml_debug CONFIG-=qtquickcompiler && \ | |||||
make -j 32 |
@ -0,0 +1,6 @@ | |||||
#!/bin/sh | |||||
# This script was designed to be run from Qt Creator through Docker | |||||
cd build/debug && \ | |||||
./torrent-client-v2 |
@ -0,0 +1,6 @@ | |||||
#!/bin/sh | |||||
# This script was designed to be run from Qt Creator through Docker | |||||
cd build/debug && \ | |||||
./torrent-client-v2 |
@ -1,7 +1,30 @@ | |||||
#include "application.h" | #include "application.h" | ||||
Application::Application(int argc, char *argv[]) | Application::Application(int argc, char *argv[]) | ||||
: QCoreApplication(argc, argv) | |||||
: QCoreApplication(argc, argv), m_torrentClient(nullptr) | |||||
{ | { | ||||
} | |||||
bool Application::boot() | |||||
{ | |||||
// Boot the torrent client | |||||
// m_torrentClient = new TorrentClient(); | |||||
// if (!m_torrentClient->boot()) { | |||||
// return false; | |||||
// } | |||||
// Boot the IPC socket server interface | |||||
m_ipcServer = new IpcServer(m_torrentClient, "/var/autoplex/ipc/torrent_client.sock"); | |||||
if (!m_ipcServer->boot()) { | |||||
return false; | |||||
} | |||||
return true; | |||||
} | |||||
int Application::exec() | |||||
{ | |||||
if (!boot()) { | |||||
return 1; | |||||
} | |||||
return QCoreApplication::exec(); | |||||
} | } |
@ -0,0 +1,85 @@ | |||||
#include "ipcserver.h" | |||||
#include <QTimer> | |||||
/** | |||||
* Create a new IPC server instance | |||||
* | |||||
* @param client | |||||
* @param socketPath | |||||
*/ | |||||
IpcServer::IpcServer(TorrentClient *client, QString socketPath) : QObject() | |||||
{ | |||||
m_server = nullptr; | |||||
m_socketPath = socketPath; | |||||
m_torrentClient = client; | |||||
} | |||||
/** | |||||
* Boot the IPC server | |||||
* @return | |||||
*/ | |||||
bool IpcServer::boot() | |||||
{ | |||||
m_server = new QLocalServer(); | |||||
m_server->setSocketOptions(QLocalServer::UserAccessOption); | |||||
m_server->setMaxPendingConnections(1); | |||||
if (!m_server->listen(m_socketPath)) { | |||||
return false; | |||||
} | |||||
connect(m_server, &QLocalServer::newConnection, this, &IpcServer::connectIpcSockets); | |||||
return true; | |||||
} | |||||
/** | |||||
* Shutdown the IPC server | |||||
*/ | |||||
void IpcServer::shutdown() | |||||
{ | |||||
if (m_server == nullptr) { | |||||
return; | |||||
} | |||||
m_server->close(); | |||||
disconnectIpcSockets(); | |||||
m_server->deleteLater(); | |||||
m_server = nullptr; | |||||
} | |||||
// ------------------------------------------------------------------------------------------------- | |||||
/** | |||||
* Connect all pending IPC socket connections | |||||
*/ | |||||
void IpcServer::connectIpcSockets() | |||||
{ | |||||
while (m_server->hasPendingConnections()) { | |||||
IpcSocketInterface *socket = new IpcSocketInterface(m_server->nextPendingConnection(), m_torrentClient); | |||||
connect(socket, &IpcSocketInterface::disconnected, this, &IpcServer::removeIpcSocket); | |||||
m_connections.append(socket); | |||||
} | |||||
} | |||||
/** | |||||
* Disconnect all IPC socket clients | |||||
*/ | |||||
void IpcServer::disconnectIpcSockets() | |||||
{ | |||||
qDebug() << "Disconnecting sockets..."; | |||||
foreach (auto socket, m_connections) { | |||||
disconnect(socket, &IpcSocketInterface::disconnected, this, &IpcServer::removeIpcSocket); | |||||
socket->disconnectSocket(); | |||||
socket->deleteLater(); | |||||
} | |||||
m_connections.clear(); | |||||
} | |||||
/** | |||||
* Remove an IPC socket when it has been disconnected | |||||
*/ | |||||
void IpcServer::removeIpcSocket() | |||||
{ | |||||
IpcSocketInterface *socket = qobject_cast<IpcSocketInterface*>(sender()); | |||||
qDebug() << "Socket disconnected"; | |||||
m_connections.removeOne(socket); | |||||
socket->deleteLater(); | |||||
} |
@ -0,0 +1,35 @@ | |||||
#ifndef IPCSERVER_H | |||||
#define IPCSERVER_H | |||||
#include <QList> | |||||
#include <QLocalServer> | |||||
#include <QObject> | |||||
#include <QString> | |||||
#include "./ipcsocketinterface.h" | |||||
#include "../torrent/torrentclient.h" | |||||
class IpcServer : public QObject | |||||
{ | |||||
public: | |||||
explicit IpcServer(TorrentClient *client, QString socketPath); | |||||
bool boot(); | |||||
void shutdown(); | |||||
private: | |||||
QString m_socketPath; | |||||
QLocalServer *m_server; | |||||
TorrentClient *m_torrentClient; | |||||
QList<IpcSocketInterface*> m_connections; | |||||
protected slots: | |||||
void connectIpcSockets(); | |||||
void disconnectIpcSockets(); | |||||
void removeIpcSocket(); | |||||
signals: | |||||
}; | |||||
#endif // IPCSERVER_H |
@ -0,0 +1,55 @@ | |||||
#include "ipcsocketinterface.h" | |||||
/** | |||||
* Create a new IPC socket interface | |||||
* | |||||
* @param socket | |||||
* @param client | |||||
*/ | |||||
IpcSocketInterface::IpcSocketInterface(QLocalSocket *socket, TorrentClient *client) | |||||
: TorrentClientInterface(client), m_socket(socket) | |||||
{ | |||||
connect(m_socket, &QLocalSocket::readyRead, this, &IpcSocketInterface::readBytes); | |||||
connect(m_socket, &QLocalSocket::disconnected, this, &IpcSocketInterface::disconnected); | |||||
qDebug() << "Connection established"; | |||||
} | |||||
/** | |||||
* Disconnect the client | |||||
*/ | |||||
void IpcSocketInterface::disconnectSocket() | |||||
{ | |||||
qDebug() << "Closing socket..."; | |||||
m_socket->close(); | |||||
} | |||||
/** | |||||
* Read the bytes from the socket and execute the request | |||||
*/ | |||||
void IpcSocketInterface::readBytes() | |||||
{ | |||||
int offset; | |||||
while (m_socket->bytesAvailable()) { | |||||
offset = m_buffer.size(); | |||||
m_buffer.append(m_socket->readAll()); | |||||
for (int i = offset; i < m_buffer.size(); i++) { | |||||
// If the end of the request is located, execute the request | |||||
if (m_buffer[i] == '\f') { | |||||
exec(m_buffer.mid(0, i)); | |||||
m_buffer = m_buffer.mid(i); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
/** | |||||
* Write the provided bytes to the socket | |||||
* | |||||
* @param data | |||||
*/ | |||||
void IpcSocketInterface::writeBytes(const QByteArray data) | |||||
{ | |||||
m_socket->write(data); | |||||
m_socket->write("\f"); | |||||
m_socket->flush(); | |||||
} |
@ -0,0 +1,31 @@ | |||||
#ifndef IPCSOCKETINTERFACE_H | |||||
#define IPCSOCKETINTERFACE_H | |||||
#include <QByteArray> | |||||
#include <QDebug> | |||||
#include <QLocalSocket> | |||||
#include <QObject> | |||||
#include "torrentclientinterface.h" | |||||
#include "../torrent/torrentclient.h" | |||||
class IpcSocketInterface : public TorrentClientInterface | |||||
{ | |||||
Q_OBJECT | |||||
public: | |||||
explicit IpcSocketInterface(QLocalSocket *socket, TorrentClient *client); | |||||
private: | |||||
QLocalSocket *m_socket; | |||||
QByteArray m_buffer; | |||||
public slots: | |||||
void disconnectSocket(); | |||||
void readBytes(); | |||||
virtual void writeBytes(const QByteArray data) override; | |||||
signals: | |||||
void disconnected(); | |||||
}; | |||||
#endif // IPCSOCKETINTERFACE_H |
@ -0,0 +1,96 @@ | |||||
#include "torrentclientinterface.h" | |||||
/** | |||||
* Create a new torrent client interface instance | |||||
* | |||||
* @param client | |||||
*/ | |||||
TorrentClientInterface::TorrentClientInterface(TorrentClient *client) | |||||
: QObject(), | |||||
m_torrentClient(client), | |||||
m_metaObject(staticMetaObject), | |||||
m_metaEnum(m_metaObject.enumerator(m_metaObject.indexOfEnumerator("RequestType"))) | |||||
{ | |||||
} | |||||
/** | |||||
* Parse and execute the given request and sending any provided responses | |||||
* | |||||
* @param data | |||||
*/ | |||||
void TorrentClientInterface::exec(QByteArray data) | |||||
{ | |||||
QJsonDocument json = QJsonDocument::fromJson(data); | |||||
if (!json.isObject()) { | |||||
qDebug() << "Unable to parse request"; | |||||
return; | |||||
} | |||||
QJsonObject requestPacket = json.object(); | |||||
if (requestPacket.count() != 2 || !requestPacket.contains("type") || !requestPacket.contains("data")) { | |||||
qDebug() << "Invalid request received"; | |||||
return; | |||||
} | |||||
if (!requestPacket["type"].isString() || !requestPacket["data"].isObject()) { | |||||
qDebug() << "Malformed request received"; | |||||
return; | |||||
} | |||||
// Perform the request | |||||
QString requestType = requestPacket["type"].toString(); | |||||
QJsonValueRef body = requestPacket["body"]; | |||||
QJsonValue *responseData = request(requestType, body); | |||||
if (responseData == nullptr) { | |||||
return; | |||||
} | |||||
// Construct and write the response | |||||
QJsonObject response; | |||||
response["type"] = requestType; | |||||
response["data"] = *responseData; | |||||
writeBytes(QJsonDocument(response).toJson(QJsonDocument::Compact)); | |||||
delete responseData; | |||||
} | |||||
// ------------------------------------------------------------------------------------------------- | |||||
/** | |||||
* Match and perform the provided request | |||||
* | |||||
* @param type | |||||
* @param body | |||||
* @return | |||||
*/ | |||||
QJsonValue* TorrentClientInterface::request(const QString type, const QJsonValueRef &body) | |||||
{ | |||||
return request((RequestType)m_metaEnum.keyToValue(("request_" + type).toLatin1()), body); | |||||
} | |||||
/** | |||||
* Match and perform the provided request | |||||
* | |||||
* @param type | |||||
* @param body | |||||
* @return | |||||
*/ | |||||
QJsonValue* TorrentClientInterface::request(const RequestType type, const QJsonValueRef &body) | |||||
{ | |||||
switch(type) { | |||||
case request_list: return requestList(body); | |||||
default: | |||||
return nullptr; | |||||
} | |||||
} | |||||
// ------------------------------------------------------------------------------------------------- | |||||
/** | |||||
* Request the list of torrents from the client | |||||
* | |||||
* @param body | |||||
* @return | |||||
*/ | |||||
QJsonValue* TorrentClientInterface::requestList(const QJsonValueRef &body) | |||||
{ | |||||
return nullptr; | |||||
} |
@ -0,0 +1,45 @@ | |||||
#ifndef TORRENTCLIENTINTERFACE_H | |||||
#define TORRENTCLIENTINTERFACE_H | |||||
#include <QByteArray> | |||||
#include <QJsonArray> | |||||
#include <QJsonDocument> | |||||
#include <QJsonObject> | |||||
#include <QJsonValue> | |||||
#include <QMetaEnum> | |||||
#include <QObject> | |||||
#include "../torrent/torrentclient.h" | |||||
class TorrentClientInterface : public QObject | |||||
{ | |||||
Q_OBJECT | |||||
Q_ENUMS(RequestType) | |||||
QMetaObject m_metaObject; | |||||
QMetaEnum m_metaEnum; | |||||
public: | |||||
enum RequestType | |||||
{ | |||||
request_list | |||||
}; | |||||
explicit TorrentClientInterface(TorrentClient *client); | |||||
protected: | |||||
virtual void writeBytes(const QByteArray data) = 0; | |||||
QJsonValue* request(const QString type, const QJsonValueRef &body); | |||||
QJsonValue* request(const RequestType type, const QJsonValueRef &body); | |||||
// Interface methods | |||||
QJsonValue* requestList(const QJsonValueRef &body); | |||||
private: | |||||
const TorrentClient *m_torrentClient; | |||||
public slots: | |||||
void exec(QByteArray data); | |||||
}; | |||||
#endif // TORRENTCLIENTINTERFACE_H |
@ -0,0 +1,40 @@ | |||||
#include "alertmanager.h" | |||||
/** | |||||
* A macro to generate the switch case statements to handle specific alert types | |||||
*/ | |||||
#define ALERT_EVENT_CASE(name, signal, alert) \ | |||||
case name: \ | |||||
emit signal(lt::alert_cast<lt::name>(alert)); \ | |||||
break | |||||
AlertManager::AlertManager(QObject *parent) : QObject(parent) | |||||
{ | |||||
connect(this, SIGNAL(alertsReady(lt::session_handle*)), this, SLOT(dispatchAlertsForSession(lt::session_handle*))); | |||||
} | |||||
void AlertManager::addSession(lt::session_handle *session) | |||||
{ | |||||
session->set_alert_notify([this, session]() -> void { | |||||
emit alertsReady(session); | |||||
}); | |||||
} | |||||
void AlertManager::dispatchAlertsForSession(lt::session_handle *session) | |||||
{ | |||||
std::vector<lt::alert*> alerts; | |||||
session->pop_alerts(&alerts); | |||||
for (auto alert : alerts) { | |||||
switch(alert->type()) { | |||||
ALERT_EVENT_CASE(add_torrent_alert, addTorrentAlerted, alert); | |||||
ALERT_EVENT_CASE(file_completed_alert, fileCompletedAlerted, alert); | |||||
ALERT_EVENT_CASE(metadata_received_alert, metadataReceivedAlerted, alert); | |||||
ALERT_EVENT_CASE(save_resume_data_alert, saveResumeDataAlerted, alert); | |||||
ALERT_EVENT_CASE(save_resume_data_failed_alert, saveResumeDataFailedAlerted, alert); | |||||
ALERT_EVENT_CASE(state_changed_alert, stateChangedAlerted, alert); | |||||
ALERT_EVENT_CASE(state_update_alert, stateUpdateAlerted, alert); | |||||
ALERT_EVENT_CASE(torrent_finished_alert, torrentFinishedAlerted, alert); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,36 @@ | |||||
#ifndef ALERTMANAGER_H | |||||
#define ALERTMANAGER_H | |||||
#include <QObject> | |||||
#include <libtorrent/alert_types.hpp> | |||||
#include <libtorrent/session.hpp> | |||||
#include "alerts.h" | |||||
class AlertManager : public QObject | |||||
{ | |||||
Q_OBJECT | |||||
public: | |||||
explicit AlertManager(QObject *parent = nullptr); | |||||
void addSession(lt::session_handle *session); | |||||
protected slots: | |||||
void dispatchAlertsForSession(lt::session_handle *session); | |||||
signals: | |||||
void alertsReady(lt::session_handle *session); | |||||
// Alerts | |||||
void addTorrentAlerted(const lt::add_torrent_alert*); | |||||
void fileCompletedAlerted(const lt::file_completed_alert*); | |||||
void metadataReceivedAlerted(const lt::metadata_received_alert*); | |||||
void saveResumeDataAlerted(const lt::save_resume_data_alert*); | |||||
void saveResumeDataFailedAlerted(const lt::save_resume_data_failed_alert*); | |||||
void stateChangedAlerted(const lt::state_changed_alert*); | |||||
void stateUpdateAlerted(const lt::state_update_alert*); | |||||
void torrentFinishedAlerted(const lt::torrent_finished_alert*); | |||||
}; | |||||
#endif // ALERTMANAGER_H |
@ -0,0 +1,102 @@ | |||||
#ifndef ALERTS_H | |||||
#define ALERTS_H | |||||
/** | |||||
* The alert type numbers weren't defined with constants/macros... | |||||
* Will have to do that here to make the app readable at all | |||||
*/ | |||||
enum { | |||||
torrent_removed_alert = 4, | |||||
read_piece_alert = 5, | |||||
file_completed_alert = 6, | |||||
file_renamed_alert = 7, | |||||
file_rename_failed_alert = 8, | |||||
performance_alert = 9, | |||||
state_changed_alert = 10, | |||||
tracker_error_alert = 11, | |||||
tracker_warning_alert = 12, | |||||
scrape_reply_alert = 13, | |||||
scrape_failed_alert = 14, | |||||
tracker_reply_alert = 15, | |||||
dht_reply_alert = 16, | |||||
tracker_announce_alert = 17, | |||||
hash_failed_alert = 18, | |||||
peer_ban_alert = 19, | |||||
peer_unsnubbed_alert = 20, | |||||
peer_snubbed_alert = 21, | |||||
peer_error_alert = 22, | |||||
peer_connect_alert = 23, | |||||
peer_disconnected_alert = 24, | |||||
invalid_request_alert = 25, | |||||
torrent_finished_alert = 26, | |||||
piece_finished_alert = 27, | |||||
request_dropped_alert = 28, | |||||
block_timeout_alert = 29, | |||||
block_finished_alert = 30, | |||||
block_downloading_alert = 31, | |||||
unwanted_block_alert = 32, | |||||
storage_moved_alert = 33, | |||||
storage_moved_failed_alert = 34, | |||||
torrent_deleted_alert = 35, | |||||
torrent_delete_failed_alert = 36, | |||||
save_resume_data_alert = 37, | |||||
save_resume_data_failed_alert = 38, | |||||
torrent_paused_alert = 39, | |||||
torrent_resumed_alert = 40, | |||||
torrent_checked_alert = 41, | |||||
url_seed_alert = 42, | |||||
file_error_alert = 43, | |||||
metadata_failed_alert = 44, | |||||
metadata_received_alert = 45, | |||||
udp_error_alert = 46, | |||||
external_ip_alert = 47, | |||||
listen_failed_alert = 48, | |||||
listen_succeeded_alert = 49, | |||||
portmap_error_alert = 50, | |||||
portmap_alert = 51, | |||||
portmap_log_alert = 52, | |||||
fastresume_rejected_alert = 53, | |||||
peer_blocked_alert = 54, | |||||
dht_announce_alert = 55, | |||||
dht_get_peers_alert = 56, | |||||
stats_alert = 57, | |||||
cache_flushed_alert = 58, | |||||
anonymous_mode_alert = 59, | |||||
lsd_peer_alert = 60, | |||||
trackerid_alert = 61, | |||||
dht_bootstrap_alert = 62, | |||||
torrent_error_alert = 64, | |||||
torrent_need_cert_alert = 65, | |||||
incoming_connection_alert = 66, | |||||
add_torrent_alert = 67, | |||||
state_update_alert = 68, | |||||
mmap_cache_alert = 69, | |||||
session_stats_alert = 70, | |||||
dht_error_alert = 73, | |||||
dht_immutable_item_alert = 74, | |||||
dht_mutable_item_alert = 75, | |||||
dht_put_alert = 76, | |||||
i2p_alert = 77, | |||||
dht_outgoing_get_peers_alert = 78, | |||||
log_alert = 79, | |||||
torrent_log_alert = 80, | |||||
peer_log_alert = 81, | |||||
lsd_error_alert = 82, | |||||
dht_stats_alert = 83, | |||||
incoming_request_alert = 84, | |||||
dht_log_alert = 85, | |||||
dht_pkt_alert = 86, | |||||
dht_get_peers_reply_alert = 87, | |||||
dht_direct_response_alert = 88, | |||||
picker_log_alert = 89, | |||||
session_error_alert = 90, | |||||
dht_live_nodes_alert = 91, | |||||
session_stats_header_alert = 92, | |||||
dht_sample_infohashes_alert = 93, | |||||
block_uploaded_alert = 94, | |||||
alerts_dropped_alert = 95, | |||||
socks5_alert = 96 | |||||
}; | |||||
#endif // ALERTS_H |
@ -0,0 +1,221 @@ | |||||
#include "torrentclient.h" | |||||
#include <QDebug> | |||||
#include <QDateTime> | |||||
#define CONNECT_ALERT(manager, alert, signal) \ | |||||
connect(manager, SIGNAL(signal ## ed(const lt::alert*)), this, SLOT(signal(const lt::alert*))) | |||||
qint64 totalTime = 0; | |||||
TorrentClient::TorrentClient(QDir dataDir) | |||||
: QObject(), m_dataDir(dataDir), m_sessionDataFile(dataDir.absoluteFilePath(".session").toStdString()) | |||||
{ | |||||
dataDir.mkpath("."); | |||||
m_alertManager = new AlertManager(); | |||||
m_numOutstandingResumeData = 0; | |||||
// Handle torrent events | |||||
CONNECT_ALERT(m_alertManager, add_torrent_alert, addTorrentAlert); | |||||
CONNECT_ALERT(m_alertManager, file_completed_alert, fileCompletedAlert); | |||||
CONNECT_ALERT(m_alertManager, metadata_received_alert, metadataReceivedAlert); | |||||
CONNECT_ALERT(m_alertManager, save_resume_data_alert, saveResumeDataAlert); | |||||
CONNECT_ALERT(m_alertManager, save_resume_data_failed_alert, saveResumeDataFailedAlert); | |||||
CONNECT_ALERT(m_alertManager, torrent_finished_alert, torrentFinishedAlert); | |||||
} | |||||
// Boot ------------------------------------------------------------------------------------------- | |||||
/** | |||||
* Boot the torrent client | |||||
*/ | |||||
int TorrentClient::boot() | |||||
{ | |||||
qDebug() << "Booting session..."; | |||||
createSession(); | |||||
loadTorrentsFromDisk(); | |||||
totalTime = QDateTime::currentMSecsSinceEpoch(); | |||||
lt::sha1_hash hash; | |||||
lt::error_code error; | |||||
return 0; | |||||
} | |||||
/** | |||||
* Create the torrent client session | |||||
*/ | |||||
void TorrentClient::createSession() | |||||
{ | |||||
// First, load the saved session parameters (if any) | |||||
std::vector<char> loadedParams = load_file(m_sessionDataFile); | |||||
lt::session_params sessionParams = loadedParams.empty() ? lt::session_params() : lt::read_session_params(loadedParams); | |||||
// Next, configure the session parameters' settings | |||||
sessionParams.settings.set_int(lt::settings_pack::alert_mask, lt::alert_category::status | |||||
| lt::alert_category::stats | |||||
| lt::alert_category::error | |||||
| lt::alert_category::storage | |||||
| lt::alert_category::file_progress); | |||||
// Create the session using these parameters and settings | |||||
m_session = new lt::session(sessionParams); | |||||
// Finally, listen for when new alerts have been added | |||||
m_alertManager->addSession(m_session); | |||||
} | |||||
/** | |||||
* Load torrents from the disk | |||||
*/ | |||||
void TorrentClient::loadTorrentsFromDisk() | |||||
{ | |||||
QDirIterator it(m_dataDir.absolutePath(), {"*.resume"}, QDir::Files); | |||||
while (it.hasNext()) { | |||||
auto buf = load_file(it.next().toStdString()); | |||||
if (!buf.empty()) { | |||||
lt::add_torrent_params magnet = lt::read_resume_data(buf); | |||||
m_session->async_add_torrent(std::move(magnet)); | |||||
} | |||||
} | |||||
qDebug() << "Total torrents:" << m_session->get_torrents().size(); | |||||
} | |||||
void TorrentClient::setFilePriorities(const lt::torrent_handle &torrent) | |||||
{ | |||||
auto files = torrent.torrent_file()->files(); | |||||
for (int i = 0; i < files.num_files(); i++) { | |||||
if (!should_keep_file(files.file_path(i))) { | |||||
torrent.file_priority(i, lt::dont_download); | |||||
qDebug() << "Ignoring: " << QString::fromStdString(files.file_path(i)); | |||||
} else { | |||||
qDebug() << "Downloading: " << QString::fromStdString(files.file_path(i)); | |||||
} | |||||
} | |||||
} | |||||
void TorrentClient::saveTorrent(const lt::torrent_handle &torrent, lt::resume_data_flags_t flags) | |||||
{ | |||||
torrent.save_resume_data(flags); | |||||
incNumOutstandingResumeData(1); | |||||
qDebug() << "Saving a torrent file..."; | |||||
} | |||||
void TorrentClient::saveTorrents() | |||||
{ | |||||
for (auto torrent : m_session->get_torrents()) { | |||||
saveTorrent(torrent, lt::torrent_handle::save_info_dict); | |||||
} | |||||
} | |||||
void TorrentClient::saveSession() | |||||
{ | |||||
std::ofstream of(m_sessionDataFile, std::ios_base::binary); | |||||
of.unsetf(std::ios_base::skipws); | |||||
auto const buffer = write_session_params_buf(m_session->session_state(), lt::save_state_flags_t::all()); | |||||
of.write(buffer.data(), int(buffer.size())); | |||||
qDebug() << "Saved"; | |||||
} | |||||
void TorrentClient::shutdown() | |||||
{ | |||||
qDebug() << "Shutting down..."; | |||||
QEventLoop loop; | |||||
connect(this, SIGNAL(allTorrentsSaved()), &loop, SLOT(quit())); | |||||
m_session->pause(); | |||||
saveSession(); | |||||
saveTorrents(); | |||||
loop.exec(); | |||||
delete m_session; | |||||
qDebug() << "Shutdown complete!"; | |||||
} | |||||
// Event Handling --------------------------------------------------------------------------------- | |||||
void TorrentClient::addTorrentAlert(const lt::add_torrent_alert *alert) | |||||
{ | |||||
if (alert->error) { | |||||
qDebug() << "Failed to add torrent. " << QString::fromStdString(alert->error.message()); | |||||
return; | |||||
} | |||||
saveTorrent(alert->handle, lt::torrent_handle::save_info_dict | lt::torrent_handle::only_if_modified); | |||||
} | |||||
void TorrentClient::fileCompletedAlert(const lt::file_completed_alert *alert) | |||||
{ | |||||
qDebug() << "File completed: " << QString::fromStdString(alert->message()); | |||||
saveTorrent(alert->handle, lt::torrent_handle::save_info_dict | lt::torrent_handle::only_if_modified); | |||||
} | |||||
void TorrentClient::metadataReceivedAlert(const lt::metadata_received_alert *alert) | |||||
{ | |||||
setFilePriorities(alert->handle); | |||||
saveTorrent(alert->handle, lt::torrent_handle::save_info_dict); | |||||
} | |||||
void TorrentClient::saveResumeDataAlert(const lt::save_resume_data_alert* alert) | |||||
{ | |||||
incNumOutstandingResumeData(-1); | |||||
auto const buf = write_resume_data_buf(alert->params); | |||||
save_file(m_dataDir.absoluteFilePath(resume_file(alert->params.info_hashes)).toStdString(), buf); | |||||
qDebug() << "Torrent file written to disk"; | |||||
if (m_numOutstandingResumeData == 0) { | |||||
qDebug() << "All torrents have been saved successfully!"; | |||||
emit allTorrentsSaved(); | |||||
} | |||||
} | |||||
void TorrentClient::saveResumeDataFailedAlert(const lt::save_resume_data_failed_alert *alert) | |||||
{ | |||||
qDebug() << "Save resume data failed"; | |||||
incNumOutstandingResumeData(-1); | |||||
if (alert->error != lt::errors::resume_data_not_modified) { | |||||
qDebug() << QString::fromStdString(alert->error.message()); | |||||
} | |||||
} | |||||
void TorrentClient::torrentFinishedAlert(const lt::torrent_finished_alert *alert) | |||||
{ | |||||
qDebug() << "The torrent finished!" << (QDateTime::currentMSecsSinceEpoch() - totalTime); | |||||
} | |||||
void TorrentClient::incNumOutstandingResumeData(int value) | |||||
{ | |||||
m_numOutstandingResumeData += value; | |||||
qDebug() << "New number of outstanding torrents is: " << m_numOutstandingResumeData; | |||||
} | |||||
// ------------------------------------------------------------------------------------------------ | |||||
bool TorrentClient::addTorrent(QString magnetLink, lt::sha1_hash &resultHash, lt::error_code &error) | |||||
{ | |||||
lt::torrent_handle handle; | |||||
lt::add_torrent_params magnet = lt::parse_magnet_uri(magnetLink.toStdString(), error); | |||||
magnet.save_path = "/media/david/Storage/TorrentData/TorrentDownloads"; | |||||
if (error) { | |||||
std::cout << "Failed to parse magnet link (" << error.value() << "): " << error.message(); | |||||
return false; | |||||
} | |||||
handle = m_session->find_torrent(magnet.info_hashes.get_best()); | |||||
if (handle.is_valid()) { | |||||
qDebug() << "This torrent has already been added!"; | |||||
return false; | |||||
} | |||||
handle = m_session->add_torrent(magnet); | |||||
qDebug() << "Torrent added; " << QString::fromStdString(to_hex(handle.info_hashes().get_best())); | |||||
qDebug() << "Total torrents:" << m_session->get_torrents().size(); | |||||
resultHash = handle.info_hashes().get_best(); | |||||
return true; | |||||
} | |||||
// ------------------------------------------------------------------------------------------------ | |||||
AlertManager* TorrentClient::alertManager() | |||||
{ | |||||
return m_alertManager; | |||||
} |
@ -0,0 +1,77 @@ | |||||
#ifndef TORRENTCLIENT_H | |||||
#define TORRENTCLIENT_H | |||||
#include <QCoreApplication> | |||||
#include <QDir> | |||||
#include <QDirIterator> | |||||
#include <QEvent> | |||||
#include <QEventLoop> | |||||
#include <QObject> | |||||
#include <QString> | |||||
#include <iomanip> | |||||
#include <iostream> | |||||
#include <string> | |||||
#include <libtorrent/error_code.hpp> | |||||
#include <libtorrent/session.hpp> | |||||
#include <libtorrent/session_params.hpp> | |||||
#include <libtorrent/add_torrent_params.hpp> | |||||
#include <libtorrent/torrent_handle.hpp> | |||||
#include <libtorrent/alert_types.hpp> | |||||
#include <libtorrent/magnet_uri.hpp> | |||||
#include <libtorrent/read_resume_data.hpp> | |||||
#include <libtorrent/write_resume_data.hpp> | |||||
#include "alerts.h" | |||||
#include "alertmanager.h" | |||||
#include "./utils.h" | |||||
class TorrentClient : public QObject | |||||
{ | |||||
Q_OBJECT | |||||
public: | |||||
explicit TorrentClient(QDir dataDir); | |||||
AlertManager *alertManager(); | |||||
public slots: | |||||
int boot(); | |||||
void shutdown(); | |||||
bool addTorrent(QString magnetLink, lt::sha1_hash &resultHash, lt::error_code &error); | |||||
protected: | |||||
void loadTorrentsFromDisk(); | |||||
void setFilePriorities(const lt::torrent_handle &torrent); | |||||
void saveTorrent(const lt::torrent_handle &torrent, lt::resume_data_flags_t flags); | |||||
protected slots: | |||||
void createSession(); | |||||
void saveSession(); | |||||
void saveTorrents(); | |||||
void incNumOutstandingResumeData(int value); | |||||
// Alert handlers | |||||
void addTorrentAlert(const lt::add_torrent_alert *alert); | |||||
void fileCompletedAlert(const lt::file_completed_alert *alert); | |||||
void metadataReceivedAlert(const lt::metadata_received_alert *alert); | |||||
void saveResumeDataAlert(const lt::save_resume_data_alert *alert); | |||||
void saveResumeDataFailedAlert(const lt::save_resume_data_failed_alert *alert); | |||||
void torrentFinishedAlert(const lt::torrent_finished_alert *alert); | |||||
private: | |||||
const QDir m_dataDir; | |||||
const std::string m_sessionDataFile; | |||||
AlertManager *m_alertManager; | |||||
int m_numOutstandingResumeData; | |||||
lt::session *m_session = nullptr; | |||||
signals: | |||||
void allTorrentsSaved(); | |||||
void torrentAdded(lt::sha1_hash); | |||||
}; | |||||
#endif // TORRENTCLIENT_H |
@ -0,0 +1,38 @@ | |||||
#include "utils.h" | |||||
std::vector<char> load_file(const std::string filename) | |||||
{ | |||||
std::ifstream ifs(filename, std::ios_base::binary); | |||||
ifs.unsetf(std::ios_base::skipws); | |||||
return { std::istream_iterator<char>(ifs), std::istream_iterator<char>() }; | |||||
} | |||||
bool save_file(const std::string filename, const std::vector<char> &bytes) | |||||
{ | |||||
std::fstream f(filename, std::ios_base::trunc | std::ios_base::out | std::ios_base::binary); | |||||
f.write(bytes.data(), int(bytes.size())); | |||||
return !f.fail(); | |||||
} | |||||
QString resume_file(const lt::info_hash_t hashes) | |||||
{ | |||||
return QString::fromStdString(to_hex(hashes.get_best())) + ".resume"; | |||||
} | |||||
std::string to_hex(const lt::sha1_hash &s) | |||||
{ | |||||
std::stringstream ret; | |||||
ret << s; | |||||
return ret.str(); | |||||
} | |||||
std::string file_ext(std::string path) | |||||
{ | |||||
return path.substr(path.find_last_of(".")); | |||||
} | |||||
bool should_keep_file(std::string path) | |||||
{ | |||||
static const std::unordered_set<std::string> exts({".mp4", ".avi", ".mkv", ".srt"}); | |||||
return exts.count(file_ext(path)) > 0; | |||||
} |
@ -0,0 +1,32 @@ | |||||
#ifndef UTILS_H | |||||
#define UTILS_H | |||||
#include <iterator> | |||||
#include <iostream> | |||||
#include <fstream> | |||||
#include <sstream> | |||||
#include <string> | |||||
#include <unordered_set> | |||||
#include <vector> | |||||
#include <QByteArray> | |||||
#include <QFile> | |||||
#include <QIODevice> | |||||
#include <QString> | |||||
#include <libtorrent/sha1_hash.hpp> | |||||
#include <libtorrent/info_hash.hpp> | |||||
std::vector<char> load_file(const std::string filename); | |||||
bool save_file(const std::string filename, const std::vector<char> &bytes); | |||||
QString resume_file(const lt::info_hash_t hashes); | |||||
std::string to_hex(const lt::sha1_hash &s); | |||||
std::string file_ext(std::string path); | |||||
bool should_keep_file(std::string path); | |||||
#endif // UTILS_H |
@ -1,21 +1,37 @@ | |||||
QT -= gui | QT -= gui | ||||
QT += network | QT += network | ||||
CONFIG += c++11 console | |||||
CONFIG += c++17 console | |||||
CONFIG -= app_bundle | CONFIG -= app_bundle | ||||
# You can make your code fail to compile if it uses deprecated APIs. | # You can make your code fail to compile if it uses deprecated APIs. | ||||
# In order to do so, uncomment the following line. | # In order to do so, uncomment the following line. | ||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 | ||||
HEADERS += \ | |||||
src/application.h \ | |||||
src/interface/ipcserver.h \ | |||||
src/interface/ipcsocketinterface.h \ | |||||
src/interface/torrentclientinterface.h \ | |||||
src/torrent/alertmanager.h \ | |||||
src/torrent/alerts.h \ | |||||
src/torrent/torrentclient.h \ | |||||
src/torrent/utils.h | |||||
SOURCES += \ | SOURCES += \ | ||||
src/application.cpp \ | |||||
src/main.cpp | |||||
src/interface/ipcserver.cpp \ | |||||
src/interface/ipcsocketinterface.cpp \ | |||||
src/interface/torrentclientinterface.cpp \ | |||||
src/main.cpp \ | |||||
src/application.cpp \ | |||||
src/torrent/alertmanager.cpp \ | |||||
src/torrent/torrentclient.cpp \ | |||||
src/torrent/utils.cpp | |||||
INCLUDEPATH += /usr/local/include | |||||
LIBS += -L/usr/local/lib -ltorrent-rasterbar -lpthread | |||||
# Default rules for deployment. | # Default rules for deployment. | ||||
qnx: target.path = /tmp/$${TARGET}/bin | qnx: target.path = /tmp/$${TARGET}/bin | ||||
else: unix:!android: target.path = /opt/$${TARGET}/bin | else: unix:!android: target.path = /opt/$${TARGET}/bin | ||||
!isEmpty(target.path): INSTALLS += target | !isEmpty(target.path): INSTALLS += target | ||||
HEADERS += \ | |||||
src/application.h |