#include "WedgeNetwork.h"
#include <QByteArray>
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include "WedgeConfig.h"
NetworkManager::NetworkManager(QObject *parent)
: QObject(parent), socket(nullptr), server(nullptr), m_connected(false),
adbTracker(nullptr), m_metaSeen(false), m_syncPending(false), m_adbForwarded(false),
m_autoMerge(ConfigManager::loadNote().autoMerge), m_peerAutoMerge(false),
m_peerLastModified(0) {
dialTimer = new QTimer(this);
dialTimer->setSingleShot(true);
dialTimer->setInterval(5000);
connect(dialTimer, &QTimer::timeout, this, &NetworkManager::handleDialTimeout);
idleTimer = new QTimer(this);
idleTimer->setSingleShot(true);
idleTimer->setInterval(3000);
connect(idleTimer, &QTimer::timeout, this, &NetworkManager::handleIdleTimeout);
}
NetworkManager::~NetworkManager() {
disconnectFromPeer();
teardownAdb();
}
void NetworkManager::startListener() {
if (server) return;
server = new QTcpServer(this);
connect(server, &QTcpServer::newConnection, this, &NetworkManager::handleNewConnection);
server->listen(QHostAddress::Any, PORT_HOST);
startAdbTracker();
}
// adb tunnel on connect
void NetworkManager::startAdbTracker() {
adbTracker = new QProcess(this);
connect(adbTracker, &QProcess::readyReadStandardOutput,
this, &NetworkManager::handleAdbTrackerOutput);
connect(adbTracker, &QProcess::finished, this, [this]() {
QTimer::singleShot(2000, this, [this]() {
adbTracker->start("adb", QStringList() << "track-devices");
});
});
adbTracker->start("adb", QStringList() << "track-devices");
}
void NetworkManager::handleAdbTrackerOutput() {
const QString chunk = QString::fromUtf8(adbTracker->readAllStandardOutput());
const bool devicePresent = chunk.contains("\tdevice");
if (devicePresent && !m_adbForwarded) {
runAdbTunnels();
emit statusChanged("Idle", false);
if (m_autoMerge && !m_connected &&
ConfigManager::loadNote().networkRoute == 0) { dial(Route::USB); }
} else if (!devicePresent) {
m_adbForwarded = false;
}
}
void NetworkManager::requestSync(Route route) {
if (m_metaSeen && socket && socket->state() == QAbstractSocket::ConnectedState) {
emit linkReady();
return;
}
m_syncPending = true;
if (route == Route::USB && !m_adbForwarded) { runAdbTunnels(); }
dial(route);
}
void NetworkManager::runAdbTunnels() {
emit statusChanged("ADB Tunnel", false);
QString phonePort = QString("tcp:%1").arg(PORT_PHONE);
QString hostPort = QString("tcp:%1").arg(PORT_HOST);
QProcess::execute("adb", QStringList() << "forward" << phonePort << phonePort);
QProcess::execute("adb", QStringList() << "reverse" << hostPort << hostPort);
m_adbForwarded = true;
}
void NetworkManager::teardownAdb() {
if (!m_adbForwarded) return;
m_adbForwarded = false;
QProcess::execute("adb", QStringList() << "forward" << "--remove-all");
QProcess::execute("adb", QStringList() << "reverse" << "--remove-all");
}
void NetworkManager::dial(Route route) {
if (socket) {
socket->disconnect(this);
socket->abort();
socket->deleteLater();
socket = nullptr;
}
m_metaSeen = false;
socket = new QTcpSocket(this);
attachSocket(socket);
emit statusChanged("Connecting", false);
dialTimer->start();
QString targetHost = (route == Route::USB) ? "127.0.0.1" : ConfigManager::loadNote().ipAddress;
socket->connectToHost(targetHost, PORT_PHONE);
}
void NetworkManager::attachSocket(QTcpSocket *s) {
connect(s, &QTcpSocket::connected, this, &NetworkManager::handleConnected);
connect(s, &QTcpSocket::disconnected, this, &NetworkManager::handleDisconnected);
connect(s, &QTcpSocket::readyRead, this, &NetworkManager::handleReadyRead);
connect(s, &QTcpSocket::errorOccurred, this, &NetworkManager::handleSocketError);
}
void NetworkManager::handleNewConnection() {
if (!server) return;
QTcpSocket *incoming = server->nextPendingConnection();
if (socket) { incoming->abort(); incoming->deleteLater(); return; }
socket = incoming;
m_metaSeen = false;
attachSocket(socket);
handleConnected();
}
void NetworkManager::handleConnected() {
m_connected = true;
emit statusChanged("Connected", true);
sendMeta();
}
void NetworkManager::handleDisconnected() {
m_connected = false;
m_metaSeen = false;
m_syncPending = false;
m_peerAutoMerge = false;
dialTimer->stop();
idleTimer->stop();
emit statusChanged("Idle", false);
if (socket) {
socket->disconnect();
socket->deleteLater();
socket = nullptr;
}
}
void NetworkManager::handleDialTimeout() {
if (m_metaSeen) return;
if (socket && socket->state() != QAbstractSocket::UnconnectedState) {
socket->abort();
}
handleDisconnected();
emit statusChanged("No Peer", false);
}
void NetworkManager::handleSocketError(QAbstractSocket::SocketError) {
if (socket) {
emit statusChanged(socket->errorString(), false);
handleDisconnected();
}
}
void NetworkManager::handleReadyRead() {
if (!socket) return;
while (socket->canReadLine()) {
QByteArray line = socket->readLine().trimmed();
if (!line.isEmpty()) {
QByteArray decodedBytes = QByteArray::fromBase64(line);
QJsonDocument doc = QJsonDocument::fromJson(decodedBytes);
if (doc.isObject()) {
QJsonObject json = doc.object();
QString receivedKey = json["key"].toString();
QString localKey = ConfigManager::loadNote().passKey;
if (receivedKey != localKey) {
qDebug() << "Key Fail";
emit statusChanged("Pass Key Incorrect", false);
socket->abort();
return;
}
const QString type = json.contains("type") ? json["type"].toString() : "data";
if (type == "meta") {
m_peerLastModified = json["date"].toVariant().toLongLong();
m_peerAutoMerge = json["auto"].toBool();
emit statusChanged("Connected", true);
m_metaSeen = true;
dialTimer->stop();
if (m_syncPending) { m_syncPending = false; emit linkReady(); }
if (autoMergeReady()) emit autoSyncDue();
} else {
m_peerLastModified = json["date"].toVariant().toLongLong();
m_peerAutoMerge = json["auto"].toBool();
QString cleanMessage = json["data"].toString();
emit contentReceived(cleanMessage);
emit statusChanged("Received", true);
m_metaSeen = true;
dialTimer->stop();
if (m_syncPending) { m_syncPending = false; emit linkReady(); }
noteActivity();
}
}
}
}
}
void NetworkManager::sendNoteContent(const QString &content, const QString &passKey, qint64 lastModified) {
if (!m_connected || !socket) return;
QJsonObject json;
json["type"] = "data";
json["key"] = passKey;
json["date"] = lastModified;
json["auto"] = m_autoMerge;
json["data"] = content;
QByteArray encodedPayload = QJsonDocument(json).toJson(QJsonDocument::Compact).toBase64();
encodedPayload.append('\n');
if (socket->write(encodedPayload) != -1) {
socket->flush();
m_peerLastModified = lastModified;
emit statusChanged("Sent", true);
}
}
bool NetworkManager::isConnected() const {
return m_connected;
}
qint64 NetworkManager::peerLastModified() const {
return m_peerLastModified;
}
void NetworkManager::sendMeta() {
if (!socket) return;
NoteData local = ConfigManager::loadNote();
QJsonObject json;
json["type"] = "meta";
json["key"] = local.passKey;
json["date"] = local.lastModified;
json["auto"] = m_autoMerge;
QByteArray payload = QJsonDocument(json).toJson(QJsonDocument::Compact).toBase64();
payload.append('\n');
if (socket->write(payload) != -1) {
socket->flush();
}
}
void NetworkManager::disconnectFromPeer() {
if (socket && socket->state() != QAbstractSocket::UnconnectedState) {
socket->abort();
} else {
handleDisconnected();
}
}
bool NetworkManager::autoMergeReady() const {
return m_connected && m_autoMerge && m_peerAutoMerge;
}
void NetworkManager::setAutoMerge(bool on) {
if (m_autoMerge == on) return;
m_autoMerge = on;
if (m_connected) sendMeta();
if (autoMergeReady()) emit autoSyncDue();
else idleTimer->stop();
}
void NetworkManager::noteActivity() {
if (!autoMergeReady()) { idleTimer->stop(); return; }
idleTimer->start();
}
void NetworkManager::handleIdleTimeout() {
if (autoMergeReady()) emit autoSyncDue();
}