@ -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" | |||
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 += network | |||
CONFIG += c++11 console | |||
CONFIG += c++17 console | |||
CONFIG -= app_bundle | |||
# You can make your code fail to compile if it uses deprecated APIs. | |||
# 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 | |||
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 += \ | |||
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. | |||
qnx: target.path = /tmp/$${TARGET}/bin | |||
else: unix:!android: target.path = /opt/$${TARGET}/bin | |||
!isEmpty(target.path): INSTALLS += target | |||
HEADERS += \ | |||
src/application.h |