Browse Source

IPC socket interface created. Base torrent client code transferred from tests, but needs refactoring

master
David Ludwig 4 years ago
parent
commit
11df663d33
22 changed files with 976 additions and 19 deletions
  1. +0
    -6
      build_debug.sh
  2. +0
    -6
      build_release.sh
  3. +8
    -0
      scripts/build_debug.sh
  4. +8
    -0
      scripts/build_release.sh
  5. +6
    -0
      scripts/run_debug.sh
  6. +6
    -0
      scripts/run_release.sh
  7. +24
    -1
      src/application.cpp
  8. +9
    -0
      src/application.h
  9. +85
    -0
      src/interface/ipcserver.cpp
  10. +35
    -0
      src/interface/ipcserver.h
  11. +55
    -0
      src/interface/ipcsocketinterface.cpp
  12. +31
    -0
      src/interface/ipcsocketinterface.h
  13. +96
    -0
      src/interface/torrentclientinterface.cpp
  14. +45
    -0
      src/interface/torrentclientinterface.h
  15. +40
    -0
      src/torrent/alertmanager.cpp
  16. +36
    -0
      src/torrent/alertmanager.h
  17. +102
    -0
      src/torrent/alerts.h
  18. +221
    -0
      src/torrent/torrentclient.cpp
  19. +77
    -0
      src/torrent/torrentclient.h
  20. +38
    -0
      src/torrent/utils.cpp
  21. +32
    -0
      src/torrent/utils.h
  22. +22
    -6
      torrent-client-v2.pro

+ 0
- 6
build_debug.sh View File

@ -1,6 +0,0 @@
#!/bin/sh
mkdir -p build/debug && \
cd build/debug && \
qmake ../../torrent-client-v2.pro -spec linux-g++ CONFIG+=debug && \
make

+ 0
- 6
build_release.sh View File

@ -1,6 +0,0 @@
#!/bin/sh
mkdir -p build/release && \
cd build/release && \
qmake ../../torrent-client-v2.pro -spec linux-g++ && \
make

+ 8
- 0
scripts/build_debug.sh View File

@ -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

+ 8
- 0
scripts/build_release.sh View File

@ -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

+ 6
- 0
scripts/run_debug.sh View File

@ -0,0 +1,6 @@
#!/bin/sh
# This script was designed to be run from Qt Creator through Docker
cd build/debug && \
./torrent-client-v2

+ 6
- 0
scripts/run_release.sh View File

@ -0,0 +1,6 @@
#!/bin/sh
# This script was designed to be run from Qt Creator through Docker
cd build/debug && \
./torrent-client-v2

+ 24
- 1
src/application.cpp View File

@ -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();
}

+ 9
- 0
src/application.h View File

@ -3,6 +3,8 @@
#include <QCoreApplication>
#include <QObject>
#include "./interface/ipcserver.h"
#include "./torrent/torrentclient.h"
class Application : public QCoreApplication
{
@ -10,6 +12,13 @@ class Application : public QCoreApplication
public:
Application(int argc, char *argv[]);
bool boot();
virtual int exec();
private:
IpcServer *m_ipcServer;
TorrentClient *m_torrentClient;
signals:
};


+ 85
- 0
src/interface/ipcserver.cpp View File

@ -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();
}

+ 35
- 0
src/interface/ipcserver.h View File

@ -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

+ 55
- 0
src/interface/ipcsocketinterface.cpp View File

@ -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();
}

+ 31
- 0
src/interface/ipcsocketinterface.h View File

@ -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

+ 96
- 0
src/interface/torrentclientinterface.cpp View File

@ -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;
}

+ 45
- 0
src/interface/torrentclientinterface.h View File

@ -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

+ 40
- 0
src/torrent/alertmanager.cpp View File

@ -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);
}
}
}

+ 36
- 0
src/torrent/alertmanager.h View File

@ -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

+ 102
- 0
src/torrent/alerts.h View File

@ -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

+ 221
- 0
src/torrent/torrentclient.cpp View File

@ -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;
}

+ 77
- 0
src/torrent/torrentclient.h View File

@ -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

+ 38
- 0
src/torrent/utils.cpp View File

@ -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;
}

+ 32
- 0
src/torrent/utils.h View File

@ -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

+ 22
- 6
torrent-client-v2.pro View File

@ -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

Loading…
Cancel
Save