Skip to content

File uploader.cpp

File List > demo-projets > jaculus > main > util > uploader.cpp

Go to the documentation of this file.

#include "uploader.h"

#include <fstream>
#include <memory>
#include <string>
#include <filesystem>
#include <dirent.h>
#include <optional>


std::optional<std::pair<std::vector<std::string>, size_t>> listDir(std::string path) {
    size_t dataSize = 0;
    std::vector<std::string> files;
    DIR *dir;
    struct dirent *ent;
    dir = opendir(path.c_str());
    if (dir == NULL) {
        return std::nullopt;
    }
    while ((ent = readdir(dir)) != NULL) {
        files.push_back(ent->d_name);
        dataSize += files.back().size() + 1;
    }
    closedir(dir);
    return std::make_pair(files, dataSize);
}

bool deleteDir(std::string path) {
    auto list = listDir(path);
    if (!list) {
        return false;
    }

    for (auto& file : list->first) {
        std::string fullPath = path + "/" + file;
        if (std::filesystem::is_directory(fullPath)) {
            if (!deleteDir(fullPath)) {
                return false;
            }
        }
        else if (!std::filesystem::remove(fullPath)) {
            return false;
        }
    }
    if (path == "/" || path == "/data" || path == "/data/") {
        return true;
    }
    return std::filesystem::remove(path);
}

bool Uploader::processPacket(int sender, std::span<const uint8_t> data) {
    if (data.size() < 1) {
        return false;
    }
    auto begin = data.begin();
    Command cmd = static_cast<Command>(*begin);
    ++begin;

    if (state == State::WAITING_FOR_DATA) {
        bool success = false;
        switch (cmd) {
            case Command::HAS_MORE_DATA:
                success = onData(std::span<const uint8_t>(begin, data.end()));
                break;
            case Command::LAST_DATA:
                success = onData(std::span<const uint8_t>(begin, data.end()));
                if (success) {
                    success = (!onDataComplete) || onDataComplete();
                    state = State::NONE;
                    file.close();
                    onData = nullptr;
                    onDataComplete = nullptr;
                }
                break;
            default:
                // TODO: ignore/reset state
                return false;
        }

        if (!success) {
            state = State::NONE;
            file.close();
            onData = nullptr;
            onDataComplete = nullptr;
        }
        return success;
    }

    switch (cmd) {
        case Command::READ_FILE:
            return processReadFile(sender, std::span<const uint8_t>(begin, data.end()));
        case Command::WRITE_FILE:
            return processWriteFile(sender, std::span<const uint8_t>(begin, data.end()));
        case Command::DELETE_FILE:
            return processDeleteFile(sender, std::span<const uint8_t>(begin, data.end()));
        case Command::LIST_DIR:
            return processListDir(sender, std::span<const uint8_t>(begin, data.end()));
        case Command::CREATE_DIR:
            return processCreateDir(sender, std::span<const uint8_t>(begin, data.end()));
        case Command::DELETE_DIR:
            return processDeleteDir(sender, std::span<const uint8_t>(begin, data.end()));
        default:
            auto response = output->buildPacket({sender});
            response->put(static_cast<uint8_t>(Command::ERROR));
            response->put(static_cast<uint8_t>(Error::UNKNOWN_COMMAND));
            response->put(static_cast<uint8_t>(cmd));
            response->send();
            return false;
    }
}

bool Uploader::processReadFile(int sender, std::span<const uint8_t> data) {
    // TODO: check if file is open for writing
    auto begin = data.begin();
    std::string filename(begin, data.end());

    file = std::fstream(filename, std::ios::in | std::ios::binary);
    if (!file.is_open()) {
        auto response = output->buildPacket({sender});
        response->put(static_cast<uint8_t>(Command::NOT_FOUND));
        response->send();
        return false;
    }

    std::vector<uint8_t> buff(output->maxPacketSize({sender}) - 1);

    Command prefix = Command::HAS_MORE_DATA;
    size_t read = 1;
    while (read > 0) {
        file.read(reinterpret_cast<char*>(buff.data()), buff.size());
        read = file.gcount();

        if (read < buff.size()) {
            prefix = Command::LAST_DATA;
        }
        auto response = output->buildPacket({sender});
        response->put(static_cast<uint8_t>(prefix));
        size_t sent = response->put(std::span(buff.data(), read));
        (void)sent;
        response->send();
    }
    return true;
}

bool Uploader::processWriteFile(int sender, std::span<const uint8_t> data) {
    // TODO: check if file is available for writing
    auto filenameEnd = std::find(data.begin(), data.end(), '\0');
    if (filenameEnd == data.end()) {
        auto response = output->buildPacket({sender});
        response->put(static_cast<uint8_t>(Command::ERROR));
        response->put(static_cast<uint8_t>(Error::INVALID_FILENAME));

        response->send();
        return false;
    }
    std::string filename(data.begin(), filenameEnd);
    auto begin = ++filenameEnd;
    state = State::WAITING_FOR_DATA;
    // TODO: delete file
    file = std::fstream(filename, std::ios::out | std::ios::binary);
    if (!file.is_open()) {
        auto response = output->buildPacket({sender});
        response->put(static_cast<uint8_t>(Command::ERROR));
        response->put(static_cast<uint8_t>(Error::FILE_OPEN_FAILED));
        response->send();
        return false;
    }
    onData = [this, sender](std::span<const uint8_t> data) {
        file.write(reinterpret_cast<const char*>(data.data()), data.size());
        file.sync();
        auto response = output->buildPacket({sender});
        response->put(static_cast<uint8_t>(Command::CONTINUE));
        response->send();
        return true;
    };
    onDataComplete = [this, sender]() {
        file.close();
        auto response = output->buildPacket({sender});
        response->put(static_cast<uint8_t>(Command::OK));
        response->send();
        return true;
    };

    if (begin != data.end()) {
        processPacket(sender, std::span<const uint8_t>(begin, data.end()));
    }

    return true;
}

bool Uploader::processDeleteFile(int sender, std::span<const uint8_t> data) {
    // TODO: check if file is available for writing
    auto begin = data.begin();
    std::string filename(begin, data.end());

    if (!std::filesystem::is_directory(filename) && std::filesystem::remove(filename)) {
        auto response = output->buildPacket({sender});
        response->put(static_cast<uint8_t>(Command::OK));
        response->send();
        return true;
    }
    else {
        auto response = output->buildPacket({sender});
        response->put(static_cast<uint8_t>(Command::ERROR));
        response->put(static_cast<uint8_t>(Error::FILE_DELETE_FAILED));
        response->send();
        return false;
    }
}

bool Uploader::processListDir(int sender, std::span<const uint8_t> data) {
    // TODO: check if dir is available for reading
    auto begin = data.begin();
    std::string filename(begin, data.end());

    std::filesystem::path path(filename);
    if (!std::filesystem::is_directory(path)) {
        auto response = output->buildPacket({sender});
        response->put(static_cast<uint8_t>(Command::NOT_FOUND));
        response->send();
        return false;
    }

    std::vector<std::string> files;
    size_t dataSize = 0;
    // for (const auto& entry : std::filesystem::directory_iterator(path)) {
    //     files.push_back(entry.path().filename().string());
    //     dataSize += files.back().size() + 1;
    // }
    // TODO: check std::filesystem::directory_iterator
    auto result = listDir(path);

    if (!result) {
        auto response = output->buildPacket({sender});
        response->put(static_cast<uint8_t>(Command::ERROR));
        response->put(static_cast<uint8_t>(Error::DIR_OPEN_FAILED));
        response->send();
        return false;
    }

    std::tie(files, dataSize) = *result;

    auto it = files.begin();
    Command prefix = Command::HAS_MORE_DATA;
    do {
        if (dataSize <= output->maxPacketSize({sender}) - 1) {
            prefix = Command::LAST_DATA;
        }
        auto response = output->buildPacket({sender});
        response->put(static_cast<uint8_t>(prefix));
        while (it != files.end() && it->size() + 1 <= response->space()) {
            response->put(std::span<const uint8_t>(reinterpret_cast<const uint8_t*>(it->data()), it->size()));
            response->put(static_cast<uint8_t>('\0'));
            dataSize -= it->size() + 1;
            ++it;
        }
        response->send();
    } while (it != files.end());
    return true;
}

bool Uploader::processCreateDir(int sender, std::span<const uint8_t> data) {
    // TODO: check if dir exists
    auto begin = data.begin();
    std::string filename(begin, data.end());

    if (std::filesystem::create_directory(filename)) {
        auto response = output->buildPacket({sender});
        response->put(static_cast<uint8_t>(Command::OK));
        response->send();
        return true;
    }
    else {
        auto response = output->buildPacket({sender});
        response->put(static_cast<uint8_t>(Command::ERROR));
        response->put(static_cast<uint8_t>(Error::DIR_CREATE_FAILED));
        response->send();
        return false;
    }
}

bool Uploader::processDeleteDir(int sender, std::span<const uint8_t> data) {
    // TODO: check if is available for writing
    auto begin = data.begin();
    std::string filename(begin, data.end());

    if (deleteDir(filename)) {
        auto response = output->buildPacket({sender});
        response->put(static_cast<uint8_t>(Command::OK));
        response->send();
        return true;
    }
    else {
        auto response = output->buildPacket({sender});
        response->put(static_cast<uint8_t>(Command::ERROR));
        response->put(static_cast<uint8_t>(Error::DIR_DELETE_FAILED));
        response->send();
        return false;
    }
}