[OpenDocString] kdeconnect-kde (cpp)
filetransferjob.cpp
FileTransferJob::FileTransferJob(const NetworkPacket *np, const QUrl &destination)
    : KJob()
    , m_origin(np->payload())
    , m_reply(nullptr)
    , m_from(QStringLiteral("KDE Connect"))
    , m_destination(destination)
    , m_speedBytes(0)
    , m_written(0)
    , m_size(np->payloadSize())
    , m_np(np)
{
    Q_ASSERT(m_origin);
    // Disabled this assert: QBluetoothSocket doesn't report "->isReadable() == true" until it's connected
    // Q_ASSERT(m_origin->isReadable());
    if (m_destination.scheme().isEmpty()) {
        qCWarning(KDECONNECT_CORE) << "Destination QUrl" << m_destination << "lacks a scheme. Setting its scheme to 'file'.";
        m_destination.setScheme(QStringLiteral("file"));
    }

    setCapabilities(Killable);
    qCDebug(KDECONNECT_CORE) << "FileTransferJob Downloading payload to" << destination << "size:" << m_size;
}
This creates a file transfer job from a network package and a destination url. It takes the payload of the network package and its payload size, and creates a transfer context. It makes sure the payload is readable and is writable.
void FileTransferJob::start()
{
    QMetaObject::invokeMethod(this, "doStart", Qt::QueuedConnection);
    // qCDebug(KDECONNECT_CORE) << "FileTransferJob start";
}
This method is invoked to start the FileTransferJob object. It is called by the doStart method of the base class and sets the connection to the queued connection.
void FileTransferJob::doStart()
{
    if (m_destination.isLocalFile() && QFile::exists(m_destination.toLocalFile())) {
        setError(2);
        setErrorText(i18n("Filename already present"));
        emitResult();
        return;
    }

    if (m_origin->bytesAvailable())
        startTransfer();
    connect(m_origin.data(), &QIODevice::readyRead, this, &FileTransferJob::startTransfer);
}
This starts the transfer process. It checks if the destination file exists, if it doesn't, it creates it. Then it connects the origin data with the file transfer job and starts the transfer.
void FileTransferJob::startTransfer()
{
    // Don't put each ready read
    if (m_reply)
        return;

    setProcessedAmount(Bytes, 0);

    QNetworkRequest req(m_destination);
    if (m_size >= 0) {
        setTotalAmount(Bytes, m_size);
        req.setHeader(QNetworkRequest::ContentLengthHeader, m_size);
    }
    m_reply = Daemon::instance()->networkAccessManager()->put(req, m_origin.data());

    connect(m_reply, &QNetworkReply::uploadProgress, this, [this](qint64 bytesSent, qint64 /*bytesTotal*/) {
        if (!m_timer.isValid())
            m_timer.start();
        setProcessedAmount(Bytes, bytesSent);

        const auto elapsed = m_timer.elapsed();
        if (elapsed > 0) {
            emitSpeed((1000 * bytesSent) / elapsed);
        }

        m_written = bytesSent;
    });
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
    connect(m_reply, static_cast(&QNetworkReply::error), this, &FileTransferJob::transferFailed);
#else
    connect(m_reply, &QNetworkReply::errorOccurred, this, &FileTransferJob::transferFailed);
#endif
    connect(m_reply, &QNetworkReply::finished, this, &FileTransferJob::transferFinished);
}
This method starts the transfer process, which creates a request object, sets the processed amount and total amount of bytes, and starts the transfer timer. It also emits the transfer progress signal if the transfer is complete.
void FileTransferJob::transferFailed(QNetworkReply::NetworkError error)
{
    qCDebug(KDECONNECT_CORE) << "Couldn't transfer the file successfully" << error << m_reply->errorString();
    setError(error);
    setErrorText(i18n("Received incomplete file: %1", m_reply->errorString()));
    emitResult();

    m_reply->close();
}
This sends a network error message to the daemon and emits a result.
void FileTransferJob::transferFinished()
{
    // TODO: MD5-check the file
    if (m_size == m_written) {
        qCDebug(KDECONNECT_CORE) << "Finished transfer" << m_destination;
        emitResult();
    } else {
        qCDebug(KDECONNECT_CORE) << "Received incomplete file (" << m_written << "/" << m_size << "bytes ), deleting";

        deleteDestinationFile();

        setError(3);
        setErrorText(i18n("Received incomplete file from: %1", m_from));
        emitResult();
    }
}
This method is called when the transfer has finished. It checks if the file size is the same as the number of bytes written to the destination file, and if it is incomplete, it deletes the destination file. It also emits a debug message if the file size is not the number of bytes written, and emits a result.
void FileTransferJob::deleteDestinationFile()
{
    if (m_destination.isLocalFile() && QFile::exists(m_destination.toLocalFile())) {
        QFile::remove(m_destination.toLocalFile());
    }
}
Deletes the destination file if it exists.
bool FileTransferJob::doKill()
{
    if (m_reply) {
        m_reply->close();
    }
    if (m_origin) {
        m_origin->close();
    }

    deleteDestinationFile();

    return true;
}
This cleans up the destination file after the kill. It closes the reply and origin files, and deletes the destination file. It returns true on the first successful attempt.