diff --git a/.env b/.env deleted file mode 100644 index e69de29..0000000 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..cc0531c --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +# The directory where data is stored +DATA_PATH = /var/autoplex/torrent + +# The full path to the IPC socket file to save +IPC_SOCKET_FILE = /var/autoplex/ipc/torrent_client.sock diff --git a/.gitignore b/.gitignore index 9682dab..3649b08 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # ---------------------------------------------------------------------------- *~ +.env *.autosave *.a *.core diff --git a/src/application.cpp b/src/application.cpp index d912b69..5670c60 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -7,15 +7,23 @@ Application::Application(int argc, char *argv[]) bool Application::boot() { + auto env = QProcessEnvironment::systemEnvironment(); + if (!env.contains("DATA_PATH") || !env.contains("IPC_SOCKET_FILE")) { + qDebug() << "Missing DATA_PATH and IPC_SOCKET_FILE environment variables"; + return false; + } + // Boot the torrent client -// m_torrentClient = new TorrentClient(); -// if (!m_torrentClient->boot()) { -// return false; -// } + m_torrentClient = new TorrentClient(QDir("/var/autoplex/torrent")); + if (!m_torrentClient->boot()) { + qDebug() << "Torrent client failed to boot"; + return false; + } // Boot the IPC socket server interface - m_ipcServer = new IpcServer(m_torrentClient, "/var/autoplex/ipc/torrent_client.sock"); + m_ipcServer = new IpcServer(m_torrentClient, env.value("IPC_SOCKET_FILE")); if (!m_ipcServer->boot()) { + qDebug() << "IPC server failed to boot"; return false; } return true; diff --git a/src/application.h b/src/application.h index 2936391..a36da97 100644 --- a/src/application.h +++ b/src/application.h @@ -3,6 +3,7 @@ #include #include +#include #include "./interface/ipcserver.h" #include "./torrent/torrentclient.h" diff --git a/src/interface/ipcserver.cpp b/src/interface/ipcserver.cpp index 62aff51..a74cd50 100644 --- a/src/interface/ipcserver.cpp +++ b/src/interface/ipcserver.cpp @@ -20,10 +20,18 @@ IpcServer::IpcServer(TorrentClient *client, QString socketPath) : QObject() */ bool IpcServer::boot() { + // Create the path to the IPC socket file + if (!QDir().mkpath(QFileInfo(m_socketPath).absolutePath())) { + qDebug() << "Failed to create IPC path"; + return false; + } + + // Create the IPC socket server m_server = new QLocalServer(); m_server->setSocketOptions(QLocalServer::UserAccessOption); m_server->setMaxPendingConnections(1); if (!m_server->listen(m_socketPath)) { + qDebug() << "Failed to create IPC socket file"; return false; } diff --git a/src/interface/ipcsocketinterface.cpp b/src/interface/ipcsocketinterface.cpp index da7e943..7b9296a 100644 --- a/src/interface/ipcsocketinterface.cpp +++ b/src/interface/ipcsocketinterface.cpp @@ -49,6 +49,7 @@ void IpcSocketInterface::readBytes() */ void IpcSocketInterface::writeBytes(const QByteArray data) { + qDebug() << data; m_socket->write(data); m_socket->write("\f"); m_socket->flush(); diff --git a/src/interface/torrentclientinterface.cpp b/src/interface/torrentclientinterface.cpp index 1ea1a0e..76c60ae 100644 --- a/src/interface/torrentclientinterface.cpp +++ b/src/interface/torrentclientinterface.cpp @@ -7,9 +7,9 @@ */ TorrentClientInterface::TorrentClientInterface(TorrentClient *client) : QObject(), - m_torrentClient(client), m_metaObject(staticMetaObject), - m_metaEnum(m_metaObject.enumerator(m_metaObject.indexOfEnumerator("RequestType"))) + m_metaEnum(m_metaObject.enumerator(m_metaObject.indexOfEnumerator("RequestType"))), + m_torrentClient(client) { } @@ -31,14 +31,15 @@ void TorrentClientInterface::exec(QByteArray data) qDebug() << "Invalid request received"; return; } - if (!requestPacket["type"].isString() || !requestPacket["data"].isObject()) { + if (!requestPacket["type"].isString()) { qDebug() << "Malformed request received"; return; } + // Perform the request QString requestType = requestPacket["type"].toString(); - QJsonValueRef body = requestPacket["body"]; + QJsonValueRef body = requestPacket["data"]; QJsonValue *responseData = request(requestType, body); if (responseData == nullptr) { return; @@ -52,7 +53,7 @@ void TorrentClientInterface::exec(QByteArray data) delete responseData; } -// ------------------------------------------------------------------------------------------------- +// Request Handling -------------------------------------------------------------------------------- /** * Match and perform the provided request @@ -75,14 +76,99 @@ QJsonValue* TorrentClientInterface::request(const QString type, const QJsonValue */ QJsonValue* TorrentClientInterface::request(const RequestType type, const QJsonValueRef &body) { + qDebug() << "Requesting:" << type << body; switch(type) { + case request_add : return requestAdd(body); case request_list: return requestList(body); default: return nullptr; } } -// ------------------------------------------------------------------------------------------------- +// Response Generation ----------------------------------------------------------------------------- + +/** + * Create an error response + * + * @param message + * @return + */ +QJsonValue* TorrentClientInterface::createErrorResponse(const int code, const char* message) +{ + return createErrorResponse(code, QString::fromStdString(message)); +} + +/** + * Create an error response + * + * @param message + * @return + */ +QJsonValue* TorrentClientInterface::createErrorResponse(const int code, std::string message) +{ + return createErrorResponse(code, QString::fromStdString(message)); +} + +/** + * Create an error response + * + * @param message + * @return + */ +QJsonValue* TorrentClientInterface::createErrorResponse(const int code, const QString message) +{ + QJsonObject response; + response["status"] = "error"; + response["message"] = message; + response["error_code"] = code; + return new QJsonValue(response); +} + +/** + * Create a successful response + * + * @param data + * @return + */ +QJsonValue* TorrentClientInterface::createResponse(QJsonValue data) +{ + QJsonObject response; + response["status"] = "success"; + response["data"] = data; + return new QJsonValue(response); +} + +// Request Implementations ------------------------------------------------------------------------- + +/** + * Add a torrent to the client + * + * @param body + * @return + */ +QJsonValue* TorrentClientInterface::requestAdd(const QJsonValueRef &body) +{ + qDebug() << "Adding torrent"; + + if (!body.isString()) { + return createErrorResponse(-1, "Value is not a string"); + } + + // Add the torrent + QString magnetLink = body.toString(); + lt::sha1_hash infoHash; + lt::error_code error; + m_torrentClient->addTorrent(magnetLink, infoHash, error); + + // Check for an error + if (error.value() != lt::errors::no_error) { + return createErrorResponse(error.value(), error.message()); + } + + QJsonObject response; + response["info_hash"] = QString::fromStdString(infoHash.to_string()); + return createResponse(QJsonValue(response)); +} /** * Request the list of torrents from the client diff --git a/src/interface/torrentclientinterface.h b/src/interface/torrentclientinterface.h index ba1058d..7099e2a 100644 --- a/src/interface/torrentclientinterface.h +++ b/src/interface/torrentclientinterface.h @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include "../torrent/torrentclient.h" class TorrentClientInterface : public QObject @@ -21,6 +23,7 @@ class TorrentClientInterface : public QObject public: enum RequestType { + request_add, request_list }; @@ -29,14 +32,22 @@ public: protected: virtual void writeBytes(const QByteArray data) = 0; + // Request handling QJsonValue* request(const QString type, const QJsonValueRef &body); QJsonValue* request(const RequestType type, const QJsonValueRef &body); + // Response handling + QJsonValue* createErrorResponse(const int code, const char*); + QJsonValue* createErrorResponse(const int code, std::string); + QJsonValue* createErrorResponse(const int code, QString message); + QJsonValue* createResponse(QJsonValue data); + // Interface methods + QJsonValue* requestAdd(const QJsonValueRef &body); QJsonValue* requestList(const QJsonValueRef &body); private: - const TorrentClient *m_torrentClient; + TorrentClient *m_torrentClient; public slots: void exec(QByteArray data); diff --git a/src/main.cpp b/src/main.cpp index d8ee122..a579b76 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,43 @@ +#include #include "application.h" +void ignoreUnixSignals(std::initializer_list ignoreSignals) { + // all these signals will be ignored. + for (int sig : ignoreSignals) { + signal(sig, SIG_IGN); + } +} + +void catchUnixSignals(std::initializer_list quitSignals) { + auto handler = [](int) -> void { + QCoreApplication::quit(); + }; + + sigset_t blocking_mask; + sigemptyset(&blocking_mask); + for (auto sig : quitSignals) { + sigaddset(&blocking_mask, sig); + } + + struct sigaction sa; + sa.sa_handler = handler; + sa.sa_mask = blocking_mask; + sa.sa_flags = 0; + + for (auto sig : quitSignals) { + sigaction(sig, &sa, nullptr); + } +} + int main(int argc, char *argv[]) { Application a(argc, argv); + // Catch signals to allow proper shutdown of the application + catchUnixSignals({ SIGQUIT, SIGINT, SIGTERM, SIGHUP }); + + // Allow cout to automatically flush + std::cout << std::unitbuf; + return a.exec(); } diff --git a/src/torrent/alertmanager.cpp b/src/torrent/alertmanager.cpp index 58243d3..8cf4b48 100644 --- a/src/torrent/alertmanager.cpp +++ b/src/torrent/alertmanager.cpp @@ -10,7 +10,7 @@ AlertManager::AlertManager(QObject *parent) : QObject(parent) { - connect(this, SIGNAL(alertsReady(lt::session_handle*)), this, SLOT(dispatchAlertsForSession(lt::session_handle*))); + connect(this, &AlertManager::alertsReady, this, &AlertManager::dispatchAlertsForSession); } void AlertManager::addSession(lt::session_handle *session) diff --git a/src/torrent/torrentclient.cpp b/src/torrent/torrentclient.cpp index b265cb9..c41125d 100644 --- a/src/torrent/torrentclient.cpp +++ b/src/torrent/torrentclient.cpp @@ -11,8 +11,6 @@ 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; @@ -30,8 +28,13 @@ TorrentClient::TorrentClient(QDir dataDir) /** * Boot the torrent client */ -int TorrentClient::boot() +bool TorrentClient::boot() { + if (!m_dataDir.mkpath(".")) { + qDebug() << "Failed to create data directory"; + return false; + } + qDebug() << "Booting session..."; createSession(); loadTorrentsFromDisk(); @@ -40,7 +43,20 @@ int TorrentClient::boot() lt::sha1_hash hash; lt::error_code error; - return 0; + return true; +} + +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!"; } /** @@ -99,7 +115,7 @@ void TorrentClient::saveTorrent(const lt::torrent_handle &torrent, lt::resume_da { torrent.save_resume_data(flags); incNumOutstandingResumeData(1); - qDebug() << "Saving a torrent file..."; + std::cout << "Saving torrent: " << torrent.name() << std::endl; } void TorrentClient::saveTorrents() @@ -115,20 +131,7 @@ void TorrentClient::saveSession() 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!"; + std::cout << "Session saved"; } // Event Handling --------------------------------------------------------------------------------- @@ -136,7 +139,7 @@ void TorrentClient::shutdown() void TorrentClient::addTorrentAlert(const lt::add_torrent_alert *alert) { if (alert->error) { - qDebug() << "Failed to add torrent. " << QString::fromStdString(alert->error.message()); + std::cout << "Failed to add torrent. " << alert->error.message() << std::endl; return; } saveTorrent(alert->handle, lt::torrent_handle::save_info_dict | lt::torrent_handle::only_if_modified); @@ -177,7 +180,7 @@ void TorrentClient::saveResumeDataFailedAlert(const lt::save_resume_data_failed_ void TorrentClient::torrentFinishedAlert(const lt::torrent_finished_alert *alert) { - qDebug() << "The torrent finished!" << (QDateTime::currentMSecsSinceEpoch() - totalTime); + std::cout << "Torrent finished: '" << alert->torrent_name() << "'" << std::endl; } void TorrentClient::incNumOutstandingResumeData(int value) diff --git a/src/torrent/torrentclient.h b/src/torrent/torrentclient.h index c1cc134..e758005 100644 --- a/src/torrent/torrentclient.h +++ b/src/torrent/torrentclient.h @@ -36,7 +36,7 @@ public: AlertManager *alertManager(); public slots: - int boot(); + bool boot(); void shutdown(); bool addTorrent(QString magnetLink, lt::sha1_hash &resultHash, lt::error_code &error);