~aleteoryx/muditaos

92ec86e818035897b0be322ff77547161af72178 — Marek Niepieklo 4 years ago bff70a4
[CP-610] Add removeFile operation to Filesystem EP

Moved Rename/Remove/ListDir from DeveloperMode to Filesystem EP API
M module-services/service-desktop/endpoints/CMakeLists.txt => module-services/service-desktop/endpoints/CMakeLists.txt +2 -2
@@ 50,11 50,11 @@ target_sources(
        developerMode/Mode/UI_Helper.cpp
        developerMode/event/ATRequest.cpp
        developerMode/event/DomRequest.cpp
        developerMode/fs/FS_Helper.cpp
        factoryReset/FactoryResetEndpoint.cpp
        filesystem/FileContext.cpp
        filesystem/FileOperations.cpp
        filesystem/FilesystemEndpoint.cpp
        filesystem/FS_Helper.cpp
        nullEndpoint/NullEndpoint.cpp
        restore/RestoreEndpoint.cpp
        restore/RestoreHelper.cpp


@@ 74,11 74,11 @@ target_sources(
        include/endpoints/developerMode/Mode/UI_Helper.hpp
        include/endpoints/developerMode/event/ATRequest.hpp
        include/endpoints/developerMode/event/DomRequest.hpp
        include/endpoints/developerMode/fs/FS_Helper.hpp
        include/endpoints/factoryReset/FactoryResetEndpoint.hpp
        include/endpoints/filesystem/FileContext.hpp
        include/endpoints/filesystem/FileOperations.hpp
        include/endpoints/filesystem/FilesystemEndpoint.hpp
        include/endpoints/filesystem/FS_Helper.hpp
        include/endpoints/nullEndpoint/NullEndpoint.hpp
        include/endpoints/restore/RestoreEndpoint.hpp
        include/endpoints/restore/RestoreHelper.hpp

M module-services/service-desktop/endpoints/developerMode/DeveloperModeEndpoint.cpp => module-services/service-desktop/endpoints/developerMode/DeveloperModeEndpoint.cpp +0 -3
@@ 37,9 37,6 @@ namespace sdesktop::endpoints
        if (ctx.getBody()["ui"] == true) {
            return *uiHelper;
        }
        if (ctx.getBody()["fs"] == true) {
            return *fsHelper;
        }
        return *helper;
    }


D module-services/service-desktop/endpoints/developerMode/fs/FS_Helper.cpp => module-services/service-desktop/endpoints/developerMode/fs/FS_Helper.cpp +0 -95
@@ 1,95 0,0 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <endpoints/developerMode/fs/FS_Helper.hpp>

#include <log/log.hpp>
#include <service-desktop/Constants.hpp>
#include <service-desktop/DeveloperModeMessage.hpp>
#include <endpoints/message/Sender.hpp>

#include <filesystem>
#include <system_error>

namespace sdesktop::endpoints
{
    using sender::putToSendQueue;

    void FS_Helper::preProcess(http::Method method, Context &context)
    {
        LOG_INFO("In FS helper - requesting %d", static_cast<int>(method));
    }

    auto FS_Helper::processGet(Context &context) -> ProcessResult
    {
        const auto &body = context.getBody();
        if (body[json::fs::listDir].is_string()) {
            const auto &dir = body[json::fs::listDir].string_value();
            auto response   = requestListDir(dir);
            return {sent::no, std::move(response)};
        }
        else {
            return {sent::no, ResponseContext{.status = http::Code::BadRequest}};
        }
    }

    auto FS_Helper::processPut(Context &context) -> ProcessResult
    {
        auto body = context.getBody();
        auto code = http::Code::BadRequest;
        if (body[json::fs::removeFile].is_string()) {
            const auto &fileName = body[json::fs::removeFile].string_value();
            try {
                code = requestFileRemoval(fileName) ? http::Code::NoContent : http::Code::NotFound;
            }
            catch (const std::filesystem::filesystem_error &) {
                LOG_WARN("Can't remove requested file");
                code = http::Code::InternalServerError;
            }
        }
        else if (body[json::fs::renameFile].is_string()) {
            const auto &fileName = body[json::fs::renameFile].string_value();
            if (body[json::fs::destFileName].is_string()) {
                const auto &destFileName = body[json::fs::destFileName].string_value();
                code = requestFileRename(fileName, destFileName) ? http::Code::NoContent : http::Code::NotFound;
            }
            else {
                code = http::Code::NotFound;
            }
        }
        else {
            context.setResponseStatus(http::Code::BadRequest);
            putToSendQueue(context.createSimpleResponse());
        }

        return {sent::no, ResponseContext{.status = code}};
    }

    bool FS_Helper::requestFileRemoval(const std::string &fileName)
    {
        return std::filesystem::remove(fileName);
    }

    bool FS_Helper::requestFileRename(const std::string &fileName, const std::string &destFileName) noexcept
    {
        std::error_code ec;
        std::filesystem::rename(fileName, destFileName, ec);
        return (!ec) ? true : false;
    }

    ResponseContext FS_Helper::requestListDir(const std::string &directory)
    {
        std::vector<std::string> filesInDir;
        for (const auto &entry : std::filesystem::directory_iterator{directory}) {
            filesInDir.push_back(entry.path());
        }
        json11::Json::array jsonArr;
        jsonArr.reserve(filesInDir.size());
        for (const auto &path : filesInDir) {
            jsonArr.push_back(json11::Json::object{{json::fs::path, path}});
        }
        json11::Json::object response({{directory, jsonArr}});
        return ResponseContext{.status = http::Code::OK, .body = response};
    }

} // namespace sdesktop::endpoints

A module-services/service-desktop/endpoints/filesystem/FS_Helper.cpp => module-services/service-desktop/endpoints/filesystem/FS_Helper.cpp +333 -0
@@ 0,0 1,333 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <endpoints/filesystem/FS_Helper.hpp>
#include <endpoints/Context.hpp>
#include <service-desktop/DesktopMessages.hpp>
#include <service-desktop/ServiceDesktop.hpp>
#include <endpoints/JsonKeyNames.hpp>
#include <endpoints/message/Sender.hpp>
#include <purefs/filesystem_paths.hpp>

#include <filesystem>

namespace sdesktop::endpoints
{
    using sender::putToSendQueue;
    namespace fs = std::filesystem;

    auto FS_Helper::processGet(Context &context) -> ProcessResult
    {
        LOG_DEBUG("Handling GET");
        const auto &body = context.getBody();
        ResponseContext response{};

        if (body[json::fs::fileName].is_string()) {
            response = startGetFile(context);
        }
        else if (body[json::fs::rxID].is_number()) {
            response = getFileChunk(context);
        }
        else if (body[json::fs::listDir].is_string()) {
            const auto &dir = body[json::fs::listDir].string_value();
            response        = requestListDir(dir);
        }
        else {
            LOG_ERROR("unknown request");
            response = {.status = http::Code::BadRequest};
        }

        return {sent::no, std::move(response)};
    }

    auto FS_Helper::processPut(Context &context) -> ProcessResult
    {
        LOG_DEBUG("Handling PUT");
        const auto &body = context.getBody();
        auto code        = http::Code::BadRequest;
        ResponseContext response{};

        if (body[json::fs::fileName].is_string() && body[json::fs::fileSize].is_number() &&
            body[json::fs::fileCrc32].is_string()) {
            response = startSendFile(context);
        }
        else if (body[json::fs::txID].is_number() && body[json::fs::chunkNo].is_number() &&
                 body[json::fs::data].is_string()) {
            response = sendFileChunk(context);
        }
        else if (body[json::fs::renameFile].is_string() && body[json::fs::destFilename].is_string()) {
            const auto &fileName     = body[json::fs::renameFile].string_value();
            const auto &destFilename = body[json::fs::destFilename].string_value();
            code = requestFileRename(fileName, destFilename) ? http::Code::NoContent : http::Code::NotFound;

            response = ResponseContext{.status = code};
        }
        else {
            LOG_ERROR("Bad request, missing or invalid argument");
            response = ResponseContext{.status = http::Code::BadRequest};
        }

        return {sent::no, std::move(response)};
    }

    auto FS_Helper::processDelete(Context &context) -> ProcessResult
    {
        LOG_DEBUG("Handling DEL");
        const auto &body = context.getBody();
        auto code        = http::Code::BadRequest;

        if (body[json::fs::removeFile].is_string()) {
            const auto &fileName = body[json::fs::removeFile].string_value();
            try {
                code = requestFileRemoval(fileName) ? http::Code::NoContent : http::Code::NotFound;
            }
            catch (const std::filesystem::filesystem_error &ex) {
                LOG_ERROR("Can't remove requested file, error: %d", ex.code().value());
                code = http::Code::InternalServerError;
            }
        }
        else {
            LOG_ERROR("Bad request, missing or invalid argument");
        }

        return {sent::no, ResponseContext{.status = code}};
    }

    auto FS_Helper::requestLogsFlush() const -> void
    {
        auto ownerService = dynamic_cast<ServiceDesktop *>(owner);
        if (ownerService) {
            ownerService->requestLogsFlush();
        }
    }

    auto FS_Helper::startGetFile(Context &context) const -> ResponseContext
    {
        const std::filesystem::path filePath = context.getBody()[json::fs::fileName].string_value();

        try {
            requestLogsFlush();
        }
        catch (const std::runtime_error &e) {
            LOG_ERROR("Logs flush exception: %s", e.what());

            json11::Json::object response({{json::reason, e.what()}});
            return ResponseContext{.status = http::Code::InternalServerError, .body = response};
        }

        if (!std::filesystem::exists(filePath)) {
            LOG_ERROR("file not found");

            json11::Json::object response({{json::reason, json::fs::fileDoesNotExist}});
            return ResponseContext{.status = http::Code::NotFound, .body = response};
        }

        json11::Json::object response{};
        auto code = http::Code::BadRequest;

        LOG_DEBUG("Checking file");

        try {
            auto [rxID, fileSize] = fileOps.createReceiveIDForFile(filePath);

            code     = http::Code::OK;
            response = json11::Json::object({{json::fs::rxID, static_cast<int>(rxID)},
                                             {json::fs::fileSize, static_cast<int>(fileSize)},
                                             {json::fs::chunkSize, static_cast<int>(FileOperations::ChunkSize)}});
        }
        catch (std::runtime_error &e) {
            LOG_ERROR("FileOperations exception: %s", e.what());

            code     = http::Code::InternalServerError;
            response = json11::Json::object({{json::reason, std::string(e.what())}});
        }
        catch (std::exception &e) {
            LOG_ERROR("FileOperations exception: %s", e.what());

            code     = http::Code::BadRequest;
            response = json11::Json::object({{json::reason, std::string(e.what())}});
        }

        return ResponseContext{.status = code, .body = response};
    }

    auto FS_Helper::getFileChunk(Context &context) const -> ResponseContext
    {
        const auto rxID    = context.getBody()[json::fs::rxID].int_value();
        const auto chunkNo = context.getBody()[json::fs::chunkNo].int_value();
        FileOperations::DataWithCrc32 dataWithCrc32;

        try {
            dataWithCrc32 = fileOps.getDataForReceiveID(rxID, chunkNo);
        }
        catch (std::exception &e) {
            LOG_ERROR("%s", e.what());

            json11::Json::object response({{json::reason, e.what()}});
            return ResponseContext{.status = http::Code::BadRequest, .body = response};
        }

        json11::Json::object response{};
        auto code = http::Code::BadRequest;

        if (dataWithCrc32.data.length()) {
            code     = http::Code::OK;
            response = json11::Json::object({{json::fs::rxID, static_cast<int>(rxID)},
                                             {json::fs::chunkNo, static_cast<int>(chunkNo)},
                                             {json::fs::data, dataWithCrc32.data}});

            if (dataWithCrc32.crc32.length()) {
                response[json::fs::fileCrc32] = dataWithCrc32.crc32;
            }
        }
        else {
            std::ostringstream errorReason;
            errorReason << "Invalid request rxID: " << std::to_string(rxID) << ", chunkNo: " << std::to_string(chunkNo);
            LOG_ERROR("%s", errorReason.str().c_str());

            code     = http::Code::BadRequest;
            response = json11::Json::object({{json::reason, errorReason.str()}});
        }

        return ResponseContext{.status = code, .body = response};
    }

    auto FS_Helper::startSendFile(Context &context) const -> ResponseContext
    {
        const auto &body               = context.getBody();
        std::filesystem::path filePath = body[json::fs::fileName].string_value();
        const uint32_t fileSize        = body[json::fs::fileSize].int_value();
        const auto fileCrc32           = body[json::fs::fileCrc32].string_value();
        auto code                      = http::Code::BadRequest;

        LOG_DEBUG("Start sending of file: %s", filePath.c_str());

        if (fileSize == 0 || fileCrc32.empty()) {
            LOG_ERROR("File %s corrupted", filePath.c_str());

            return ResponseContext{.status = code};
        }

        if (!std::filesystem::exists(filePath)) {
            LOG_DEBUG("Creating file %s", filePath.c_str());

            code = http::Code::Created;
        }
        else {
            LOG_DEBUG("Overwriting file %s", filePath.c_str());
        }

        json11::Json::object response{};

        try {
            auto txID = fileOps.createTransmitIDForFile(filePath, fileSize, fileCrc32);

            code     = http::Code::OK;
            response = json11::Json::object({{json::fs::txID, static_cast<int>(txID)},
                                             {json::fs::chunkSize, static_cast<int>(FileOperations::ChunkSize)}});
        }
        catch (std::runtime_error &e) {
            LOG_ERROR("FileOperations exception: %s", e.what());

            code     = http::Code::InternalServerError;
            response = json11::Json::object({{json::reason, std::string(e.what())}});
        }
        catch (std::exception &e) {
            LOG_ERROR("FileOperations exception: %s", e.what());

            code     = http::Code::BadRequest;
            response = json11::Json::object({{json::reason, std::string(e.what())}});
        }

        return ResponseContext{.status = code, .body = response};
    }

    auto FS_Helper::sendFileChunk(Context &context) const -> ResponseContext
    {
        const auto &body   = context.getBody();
        const auto txID    = body[json::fs::txID].int_value();
        const auto chunkNo = body[json::fs::chunkNo].int_value();
        const auto data    = body[json::fs::data].string_value();

        if (data.empty()) {
            std::ostringstream errorReason;
            errorReason << "Invalid request txID: " << std::to_string(txID) << ", chunkNo: " << std::to_string(chunkNo);
            LOG_ERROR("%s", errorReason.str().c_str());

            auto code     = http::Code::BadRequest;
            auto response = json11::Json::object({{json::reason, errorReason.str()}});

            return ResponseContext{.status = code, .body = response};
        }

        auto returnCode = sys::ReturnCodes::Success;

        try {
            returnCode = fileOps.sendDataForTransmitID(txID, chunkNo, data);
        }
        catch (std::exception &e) {
            LOG_ERROR("%s", e.what());

            auto code     = http::Code::NotAcceptable;
            auto response = json11::Json::object({{json::reason, e.what()}});

            return ResponseContext{.status = code, .body = response};
        }

        json11::Json::object response{};
        auto code = http::Code::OK;

        if (returnCode == sys::ReturnCodes::Success) {
            LOG_DEBUG("FileOperations::sendDataForTransmitID success");

            response = json11::Json::object(
                {{json::fs::txID, static_cast<int>(txID)}, {json::fs::chunkNo, static_cast<int>(chunkNo)}});
        }
        else {
            LOG_ERROR("FileOperations::sendDataForTransmitID failed");

            code     = http::Code::BadRequest;
            response = json11::Json::object(
                {{json::fs::txID, static_cast<int>(txID)}, {json::fs::chunkNo, static_cast<int>(chunkNo)}});
        }

        return ResponseContext{.status = code, .body = response};
    }

    auto FS_Helper::requestFileRemoval(const std::string &fileName) -> bool
    {
        return std::filesystem::remove(fileName);
    }

    auto FS_Helper::requestFileRename(const std::string &fileName, const std::string &destFilename) noexcept -> bool
    {
        std::error_code ec;
        std::filesystem::rename(fileName, destFilename, ec);
        if (!ec) {
            LOG_ERROR("Failed to rename %s, error: %d", fileName.c_str(), ec.value());
        }
        return !ec;
    }

    auto FS_Helper::requestListDir(const std::string &directory) -> ResponseContext
    {
        if (!std::filesystem::exists(directory)) {
            return ResponseContext{.status = http::Code::NotFound};
        }

        std::vector<std::string> filesInDir;
        for (const auto &entry : std::filesystem::directory_iterator{directory}) {
            filesInDir.push_back(entry.path());
        }

        json11::Json::array jsonArr;
        jsonArr.reserve(filesInDir.size());

        for (const auto &path : filesInDir) {
            jsonArr.push_back(json11::Json::object{{json::fs::path, path}});
        }

        json11::Json::object response({{directory, jsonArr}});
        return ResponseContext{.status = http::Code::OK, .body = response};
    }
} // namespace sdesktop::endpoints

M module-services/service-desktop/endpoints/filesystem/FilesystemEndpoint.cpp => module-services/service-desktop/endpoints/filesystem/FilesystemEndpoint.cpp +14 -300
@@ 2,320 2,34 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <endpoints/filesystem/FilesystemEndpoint.hpp>

#include <service-desktop/DesktopMessages.hpp>
#include <service-desktop/ServiceDesktop.hpp>
#include <endpoints/JsonKeyNames.hpp>
#include <endpoints/filesystem/FS_Helper.hpp>
#include <endpoints/message/Sender.hpp>
#include <purefs/filesystem_paths.hpp>

#include <filesystem>

namespace sdesktop::endpoints
{

    namespace fs = std::filesystem;
    using sender::putToSendQueue;

    auto FilesystemEndpoint::handle(Context &context) -> void
    {
        auto returnCode = sys::ReturnCodes::Success;
        switch (context.getMethod()) {
        case http::Method::get:
            returnCode = runGet(context);
            break;
        case http::Method::post:
            returnCode = runPost(context);
            break;
        case http::Method::put:
            returnCode = runPut(context);
            break;
        default:
            LOG_ERROR("Unhandled method request: %u", static_cast<unsigned>(context.getMethod()));
            returnCode = sys::ReturnCodes::Failure;
            break;
        }
        LOG_DEBUG("returnCode: %u", static_cast<unsigned>(returnCode));
    }

    auto FilesystemEndpoint::requestLogsFlush() const -> void
    {
        auto owner = dynamic_cast<ServiceDesktop *>(ownerServicePtr);
        if (owner) {
            owner->requestLogsFlush();
        }
    }

    auto FilesystemEndpoint::startGetFile(Context &context) const -> sys::ReturnCodes
    {
        std::filesystem::path filePath = context.getBody()[json::fileName].string_value();

        try {
            requestLogsFlush();
        }
        catch (const std::runtime_error &e) {
            LOG_ERROR("Logs flush exception: %s", e.what());
        const auto &[sent, response] = helper->process(context.getMethod(), context);

            context.setResponseStatus(http::Code::InternalServerError);
            context.setResponseBody(json11::Json::object({{json::reason, std::string(e.what())}}));
            return sys::ReturnCodes::Failure;
        if (sent == sent::delayed) {
            LOG_DEBUG("There is no proper delayed serving mechanism - depend on invisible context caching");
        }

        if (!std::filesystem::exists(filePath)) {
            LOG_ERROR("file not found");

            context.setResponseStatus(http::Code::NotFound);
            context.setResponseBody(
                json11::Json::object({{json::reason, json::filesystem::reasons::fileDoesNotExist}}));
            return sys::ReturnCodes::Failure;
        }

        LOG_DEBUG("Checking file");

        try {
            auto [rxID, fileSize] = fileOps.createReceiveIDForFile(filePath);

            context.setResponseStatus(http::Code::OK);
            context.setResponseBody(
                json11::Json::object({{json::filesystem::rxID, static_cast<int>(rxID)},
                                      {json::fileSize, static_cast<int>(fileSize)},
                                      {json::filesystem::chunkSize, static_cast<int>(FileOperations::ChunkSize)}}));
        }
        catch (std::runtime_error &e) {
            LOG_ERROR("FileOperations exception: %s", e.what());

            context.setResponseStatus(http::Code::InternalServerError);
            context.setResponseBody(json11::Json::object({{json::reason, std::string(e.what())}}));
            return sys::ReturnCodes::Failure;
        }
        catch (std::exception &e) {
            LOG_ERROR("FileOperations exception: %s", e.what());

            context.setResponseStatus(http::Code::BadRequest);
            context.setResponseBody(json11::Json::object({{json::reason, std::string(e.what())}}));
            return sys::ReturnCodes::Failure;
        }

        return sys::ReturnCodes::Success;
    }

    auto FilesystemEndpoint::getFileChunk(Context &context) const -> sys::ReturnCodes
    {
        auto returnCode    = sys::ReturnCodes::Failure;
        const auto rxID    = context.getBody()[json::filesystem::rxID].int_value();
        const auto chunkNo = context.getBody()[json::filesystem::chunkNo].int_value();
        FileOperations::DataWithCrc32 dataWithCrc32;

        try {
            dataWithCrc32 = fileOps.getDataForReceiveID(rxID, chunkNo);
        }
        catch (std::exception &e) {
            LOG_ERROR("%s", e.what());
            context.setResponseStatus(http::Code::BadRequest);
            context.setResponseBody(json11::Json::object({{json::reason, e.what()}}));

            return sys::ReturnCodes::Failure;
        }

        if (dataWithCrc32.data.length()) {
            context.setResponseStatus(http::Code::OK);
            json11::Json::object contextJsonObject =
                json11::Json::object({{json::filesystem::rxID, static_cast<int>(rxID)},
                                      {json::filesystem::chunkNo, static_cast<int>(chunkNo)},
                                      {json::filesystem::data, dataWithCrc32.data}});

            if (dataWithCrc32.crc32.length()) {
                contextJsonObject[json::fileCrc32] = dataWithCrc32.crc32;
        if (sent == sent::no) {
            if (not response) {
                LOG_ERROR("Response not sent & response not created : respond with error");
                context.setResponseStatus(http::Code::NotAcceptable);
            }
            else {
                context.setResponse(response.value());
            }
            context.setResponseBody(contextJsonObject);

            returnCode = sys::ReturnCodes::Success;
        }
        else {
            std::ostringstream errorReason;
            errorReason << "Invalid request rxID: " << std::to_string(rxID) << ", chunkNo: " << std::to_string(chunkNo);
            LOG_ERROR("%s", errorReason.str().c_str());

            context.setResponseStatus(http::Code::BadRequest);
            context.setResponseBody(json11::Json::object({{json::reason, errorReason.str()}}));
        }

        return returnCode;
    }

    auto FilesystemEndpoint::runGet(Context &context) -> sys::ReturnCodes
    {
        LOG_DEBUG("Handling GET");
        auto returnCode = sys::ReturnCodes::Failure;

        if (context.getBody()[json::fileName].is_string()) {
            returnCode = startGetFile(context);
        }
        else if (context.getBody()[json::filesystem::rxID].is_number()) {
            returnCode = getFileChunk(context);
        }
        else {
            LOG_ERROR("unknown request");
            context.setResponseStatus(http::Code::BadRequest);
            returnCode = sys::ReturnCodes::Failure;
        }

        putToSendQueue(context.createSimpleResponse());

        return returnCode;
    }

    auto FilesystemEndpoint::runPost(Context &context) -> sys::ReturnCodes
    {
        LOG_DEBUG("Handling POST");
        sys::ReturnCodes returnCode = sys::ReturnCodes::Failure;
        std::string cmd             = context.getBody()[json::filesystem::command].string_value();

        context.setResponseBody(
            json11::Json::object({{json::status, std::to_string(static_cast<int>(sys::ReturnCodes::Failure))}}));

        if (cmd == json::filesystem::commands::checkFile) {
            fs::path filePath = context.getBody()[json::fileName].string_value();
            LOG_DEBUG("Checking file");

            context.setResponseBody(json11::Json::object{{json::fileExists, std::filesystem::exists(filePath)}});
            returnCode = sys::ReturnCodes::Success;
        }
        else {
            LOG_ERROR("unknown command");
            context.setResponseStatus(http::Code::BadRequest);
            returnCode = sys::ReturnCodes::Failure;
        }

        putToSendQueue(context.createSimpleResponse());
        return returnCode;
    }

    auto FilesystemEndpoint::runPut(Context &context) -> sys::ReturnCodes
    {
        LOG_DEBUG("Handling PUT");
        auto returnCode         = sys::ReturnCodes::Failure;
        const auto &requestBody = context.getBody();

        if (requestBody[json::fileName].is_string() && requestBody[json::fileSize].is_number() &&
            requestBody[json::fileCrc32].is_string()) {
            returnCode = startSendFile(context);
        }
        else if (requestBody[json::filesystem::txID].is_number() &&
                 requestBody[json::filesystem::chunkNo].is_number() &&
                 requestBody[json::filesystem::data].is_string()) {
            returnCode = sendFileChunk(context);
        }
        else {
            LOG_ERROR("unknown request");
            context.setResponseStatus(http::Code::BadRequest);
            returnCode = sys::ReturnCodes::Failure;
        }

        putToSendQueue(context.createSimpleResponse());
        return returnCode;
    }

    auto FilesystemEndpoint::startSendFile(Context &context) const -> sys::ReturnCodes
    {
        auto returnCode         = sys::ReturnCodes::Failure;
        const auto &requestBody = context.getBody();

        std::filesystem::path filePath = requestBody[json::fileName].string_value();
        const uint32_t fileSize        = requestBody[json::fileSize].int_value();
        const auto fileCrc32           = requestBody[json::fileCrc32].string_value();

        LOG_DEBUG("Start sending of file: %s", filePath.c_str());

        if (fileSize == 0 || fileCrc32.empty()) {
            LOG_ERROR("File %s corrupted", filePath.c_str());

            context.setResponseStatus(http::Code::BadRequest);
            return returnCode;
        }

        if (!std::filesystem::exists(filePath)) {
            LOG_DEBUG("Creating file %s", filePath.c_str());

            context.setResponseStatus(http::Code::Created);
        }
        else {
            LOG_DEBUG("Overwriting file %s", filePath.c_str());
        }

        try {
            auto txID = fileOps.createTransmitIDForFile(filePath, fileSize, fileCrc32);

            context.setResponseStatus(http::Code::OK);
            context.setResponseBody(
                json11::Json::object({{json::filesystem::txID, static_cast<int>(txID)},
                                      {json::filesystem::chunkSize, static_cast<int>(FileOperations::ChunkSize)}}));
        }
        catch (std::runtime_error &e) {
            LOG_ERROR("FileOperations exception: %s", e.what());

            context.setResponseStatus(http::Code::InternalServerError);
            context.setResponseBody(json11::Json::object({{json::reason, std::string(e.what())}}));
            return sys::ReturnCodes::Failure;
        }
        catch (std::exception &e) {
            LOG_ERROR("FileOperations exception: %s", e.what());

            context.setResponseStatus(http::Code::BadRequest);
            context.setResponseBody(json11::Json::object({{json::reason, std::string(e.what())}}));
            return sys::ReturnCodes::Failure;
        }

        return sys::ReturnCodes::Success;
    }

    auto FilesystemEndpoint::sendFileChunk(Context &context) const -> sys::ReturnCodes
    {
        auto returnCode         = sys::ReturnCodes::Failure;
        const auto &requestBody = context.getBody();
        const auto txID         = requestBody[json::filesystem::txID].int_value();
        const auto chunkNo      = requestBody[json::filesystem::chunkNo].int_value();
        const auto data         = requestBody[json::filesystem::data].string_value();

        if (data.empty()) {
            std::ostringstream errorReason;
            errorReason << "Invalid request txID: " << std::to_string(txID) << ", chunkNo: " << std::to_string(chunkNo);
            LOG_ERROR("%s", errorReason.str().c_str());

            context.setResponseStatus(http::Code::BadRequest);
            context.setResponseBody(json11::Json::object({{json::reason, errorReason.str()}}));
            return returnCode;
        }

        try {
            returnCode = fileOps.sendDataForTransmitID(txID, chunkNo, data);
        }
        catch (std::exception &e) {
            LOG_ERROR("%s", e.what());
            context.setResponseStatus(http::Code::NotAcceptable);
            context.setResponseBody(json11::Json::object({{json::reason, e.what()}}));

            return sys::ReturnCodes::Failure;
        }

        if (returnCode == sys::ReturnCodes::Success) {
            LOG_DEBUG("FileOperations::sendDataForTransmitID success");
            context.setResponseStatus(http::Code::OK);
            context.setResponseBody(json11::Json::object({
                {json::filesystem::txID, static_cast<int>(txID)},
                {json::filesystem::chunkNo, static_cast<int>(chunkNo)},
            }));
            sender::putToSendQueue(context.createSimpleResponse());
        }
        else {
            LOG_ERROR("FileOperations::sendDataForTransmitID failed");
            context.setResponseStatus(http::Code::BadRequest);
            context.setResponseBody(json11::Json::object({
                {json::filesystem::txID, static_cast<int>(txID)},
                {json::filesystem::chunkNo, static_cast<int>(chunkNo)},
            }));
        if (sent == sent::yes and response) {
            LOG_ERROR("Response set when we already handled response in handler");
        }

        return returnCode;
    }

} // namespace sdesktop::endpoints

M module-services/service-desktop/endpoints/include/endpoints/JsonKeyNames.hpp => module-services/service-desktop/endpoints/include/endpoints/JsonKeyNames.hpp +1 -26
@@ 24,9 24,7 @@ namespace sdesktop::endpoints::json
    inline constexpr auto networkStatus       = "networkStatus";
    inline constexpr auto networkOperatorName = "networkOperatorName";
    inline constexpr auto accessTechnology    = "accessTechnology";
    inline constexpr auto fileName            = "fileName";
    inline constexpr auto fileSize            = "fileSize";
    inline constexpr auto fileCrc32           = "fileCrc32";

    inline constexpr auto update              = "update";
    inline constexpr auto updateInfo          = "updateInfo";
    inline constexpr auto updateError         = "updateError";


@@ 52,29 50,6 @@ namespace sdesktop::endpoints::json
    inline constexpr auto files               = "files";
    inline constexpr auto backupLocation      = "backupLocation";

    namespace filesystem
    {
        inline constexpr auto command   = "command";
        inline constexpr auto chunkSize = "chunkSize";
        inline constexpr auto chunkNo   = "chunkNo";
        inline constexpr auto data      = "data";
        inline constexpr auto rxID      = "rxID";
        inline constexpr auto txID      = "txID";

        namespace commands
        {
            inline constexpr auto upload    = "upload";
            inline constexpr auto rm        = "rm";
            inline constexpr auto download  = "download";
            inline constexpr auto checkFile = "checkFile";
        } // namespace commands

        namespace reasons
        {
            inline constexpr auto fileDoesNotExist = "file does not exist";
        }
    } // namespace filesystem

    namespace updateprocess
    {
        inline constexpr auto command       = "command";

M module-services/service-desktop/endpoints/include/endpoints/developerMode/DeveloperModeEndpoint.hpp => module-services/service-desktop/endpoints/include/endpoints/developerMode/DeveloperModeEndpoint.hpp +1 -4
@@ 6,7 6,6 @@
#include <endpoints/Endpoint.hpp>
#include "DeveloperModeHelper.hpp"
#include "Mode/UI_Helper.hpp"
#include "fs/FS_Helper.hpp"

#include <memory>



@@ 18,13 17,11 @@ namespace sdesktop::endpoints
      private:
        const std::unique_ptr<DeveloperModeHelper> helper;
        const std::unique_ptr<UI_Helper> uiHelper;
        const std::unique_ptr<FS_Helper> fsHelper;

      public:
        explicit DeveloperModeEndpoint(sys::Service *_ownerServicePtr)
            : Endpoint(_ownerServicePtr), helper(std::make_unique<DeveloperModeHelper>(ownerServicePtr)),
              uiHelper(std::make_unique<UI_Helper>(ownerServicePtr)),
              fsHelper(std::make_unique<FS_Helper>(ownerServicePtr))
              uiHelper(std::make_unique<UI_Helper>(ownerServicePtr))
        {
            debugName = "DeveloperModeEndpoint";
        }

R module-services/service-desktop/endpoints/include/endpoints/developerMode/fs/FS_Helper.hpp => module-services/service-desktop/endpoints/include/endpoints/filesystem/FS_Helper.hpp +34 -15
@@ 4,33 4,52 @@
#pragma once

#include <endpoints/BaseHelper.hpp>
#include "FileOperations.hpp"

namespace sdesktop::endpoints
{
    namespace json::fs
    {
        inline constexpr auto removeFile   = "removeFile";
        inline constexpr auto renameFile   = "renameFile";
        inline constexpr auto destFilename = "destFilename";
        inline constexpr auto listDir      = "listDir";
        inline constexpr auto path         = "path";
        inline constexpr auto fileName     = "fileName";
        inline constexpr auto fileSize     = "fileSize";
        inline constexpr auto fileCrc32    = "fileCrc32";
        inline constexpr auto chunkSize    = "chunkSize";
        inline constexpr auto chunkNo      = "chunkNo";
        inline constexpr auto data         = "data";
        inline constexpr auto rxID         = "rxID";
        inline constexpr auto txID         = "txID";

        inline constexpr auto fileDoesNotExist = "file does not exist";
    } // namespace json::fs

    class FS_Helper : public BaseHelper
    {
      public:
        explicit FS_Helper(sys::Service *p) : BaseHelper(p)
        explicit FS_Helper(sys::Service *p, FileOperations &fileOps) : BaseHelper(p), fileOps(fileOps)
        {}

      private:
        auto processGet(Context &context) -> ProcessResult final;
        auto processPut(Context &context) -> ProcessResult final;
        void preProcess(http::Method method, Context &context) final;
        auto processDelete(Context &context) -> ProcessResult final;

        bool requestFileRemoval(const std::string &fileName);
        bool requestFileRename(const std::string &fileName, const std::string &destFileName) noexcept;
        ResponseContext requestListDir(const std::string &directory);
    };
      private:
        auto startGetFile(Context &context) const -> ResponseContext;
        auto getFileChunk(Context &context) const -> ResponseContext;

    namespace json::fs
    {
        inline constexpr auto removeFile   = "removeFile";
        inline constexpr auto renameFile   = "renameFile";
        inline constexpr auto destFileName = "destfilename";
        inline constexpr auto listDir      = "listDir";
        inline constexpr auto path         = "path";
    } // namespace json::fs
        auto startSendFile(Context &context) const -> ResponseContext;
        auto sendFileChunk(Context &context) const -> ResponseContext;

        auto requestFileRemoval(const std::string &fileName) -> bool;
        auto requestFileRename(const std::string &fileName, const std::string &destFileName) noexcept -> bool;
        auto requestListDir(const std::string &directory) -> ResponseContext;

        auto requestLogsFlush() const -> void;

        FileOperations &fileOps;
    };
} // namespace sdesktop::endpoints

M module-services/service-desktop/endpoints/include/endpoints/filesystem/FilesystemEndpoint.hpp => module-services/service-desktop/endpoints/include/endpoints/filesystem/FilesystemEndpoint.hpp +5 -16
@@ 4,6 4,7 @@
#pragma once

#include <endpoints/Endpoint.hpp>
#include "FS_Helper.hpp"
#include <Service/Service.hpp>
#include "FileOperations.hpp"



@@ 12,6 13,8 @@ namespace sdesktop::endpoints

    class FilesystemEndpoint : public Endpoint
    {
        const std::unique_ptr<FS_Helper> helper;

      public:
        static auto createInstance(sys::Service *ownerServicePtr) -> std::unique_ptr<Endpoint>
        {


@@ 19,24 22,10 @@ namespace sdesktop::endpoints
        }

        explicit FilesystemEndpoint(sys::Service *ownerServicePtr, FileOperations &fileOps)
            : Endpoint(ownerServicePtr), fileOps(fileOps)
            : Endpoint(ownerServicePtr), helper(std::make_unique<FS_Helper>(ownerServicePtr, fileOps))
        {}
        auto handle(Context &context) -> void override;
        auto runGet(Context &context) -> sys::ReturnCodes;
        auto runPost(Context &context) -> sys::ReturnCodes;
        auto runPut(Context &context) -> sys::ReturnCodes;
        auto getUpdates(Context &context) -> sys::ReturnCodes;

      private:
        auto startGetFile(Context &context) const -> sys::ReturnCodes;
        auto getFileChunk(Context &context) const -> sys::ReturnCodes;

        auto startSendFile(Context &context) const -> sys::ReturnCodes;
        auto sendFileChunk(Context &context) const -> sys::ReturnCodes;

        auto requestLogsFlush() const -> void;

        FileOperations &fileOps;
        auto handle(Context &context) -> void override;
    };

} // namespace sdesktop::endpoints

M test/harness => test/harness +1 -1
@@ 1,1 1,1 @@
Subproject commit 1e3d1ced82e8d94ce89d93f6d03378893c094731
Subproject commit a7aee485bcb04307202a5439748d6cd56b496a2a