~aleteoryx/muditaos

df12fbaa95eb446fe3df67782f369457e13ef9dc — Maciej Gibowicz 8 months ago 6b45d3b
[BH-2099] Add synchronization of custom quotes with Center

Added support for sending a quotes database file and sending feedback
about custom quotes settings
M module-services/service-db/DBServiceAPI.cpp => module-services/service-db/DBServiceAPI.cpp +16 -0
@@ 362,6 362,22 @@ void DBServiceAPI::QuotesIntervalChanged(sys::Service *serv, const std::string &
    DBServiceAPI::GetQuery(serv, db::Interface::Name::Quotes, std::move(query));
}

bool DBServiceAPI::QuotesGetGroup(sys::Service *serv, std::unique_ptr<db::QueryListener> &&listener)
{
    auto query = std::make_unique<Quotes::Messages::GetGroup>();
    query->setQueryListener(std::move(listener));
    const auto [result, _] = DBServiceAPI::GetQuery(serv, db::Interface::Name::Quotes, std::move(query));
    return result;
}

bool DBServiceAPI::QuotesGetInterval(sys::Service *serv, std::unique_ptr<db::QueryListener> &&listener)
{
    auto query = std::make_unique<Quotes::Messages::GetInterval>();
    query->setQueryListener(std::move(listener));
    const auto [result, _] = DBServiceAPI::GetQuery(serv, db::Interface::Name::Quotes, std::move(query));
    return result;
}

auto DBServiceAPI::hasContactSameNumbers(sys::Service *serv, const ContactRecord &rec) -> bool
{
    std::shared_ptr<DBContactMessage> msg =

M module-services/service-db/include/service-db/DBServiceAPI.hpp => module-services/service-db/include/service-db/DBServiceAPI.hpp +2 -0
@@ 140,4 140,6 @@ class DBServiceAPI
    static bool QuotesDeleteEntry(sys::Service *serv, std::uint32_t id, std::unique_ptr<db::QueryListener> &&listener);
    static void QuotesGroupChanged(sys::Service *serv, const std::string &group);
    static void QuotesIntervalChanged(sys::Service *serv, const std::string &interval);
    static bool QuotesGetGroup(sys::Service *serv, std::unique_ptr<db::QueryListener> &&listener);
    static bool QuotesGetInterval(sys::Service *serv, std::unique_ptr<db::QueryListener> &&listener);
};

M module-services/service-db/include/service-db/QuotesMessages.hpp => module-services/service-db/include/service-db/QuotesMessages.hpp +50 -0
@@ 432,6 432,31 @@ namespace Quotes
            }
        };

        class GetGroup : public db::Query
        {
          public:
            explicit GetGroup() : Query(Query::Type::Create)
            {}

            auto debugInfo() const -> std::string
            {
                return "GetGroup";
            }
        };

        class GetGroupResponse : public db::QueryResult
        {
          public:
            explicit GetGroupResponse(const std::string &group) : group(group)
            {}
            const std::string group;

            auto debugInfo() const -> std::string
            {
                return "GetGroupResponse";
            }
        };

        class InformIntervalChanged : public db::Query
        {
          public:


@@ 445,6 470,31 @@ namespace Quotes
            }
        };

        class GetInterval : public db::Query
        {
          public:
            explicit GetInterval() : Query(Query::Type::Create)
            {}

            auto debugInfo() const -> std::string
            {
                return "GetInterval";
            }
        };

        class GetIntervalResponse : public db::QueryResult
        {
          public:
            explicit GetIntervalResponse(const std::string &interval) : interval(interval)
            {}
            const std::string interval;

            auto debugInfo() const -> std::string
            {
                return "GetIntervalResponse";
            }
        };

        class WriteQuoteRequest : public db::Query
        {
          public:

M module-services/service-desktop/endpoints/include/endpoints/JsonKeyNames.hpp => module-services/service-desktop/endpoints/include/endpoints/JsonKeyNames.hpp +22 -21
@@ 67,26 67,26 @@ namespace sdesktop::endpoints::json
        inline constexpr auto categoryBackup   = "backup";
        inline constexpr auto categorySync     = "sync";

        inline constexpr auto limit              = "limit";
        inline constexpr auto offset             = "offset";
        inline constexpr auto order              = "order";
        inline constexpr auto entries            = "entries";
        inline constexpr auto messageBody        = "messageBody";
        inline constexpr auto messageCount       = "messageCount";
        inline constexpr auto messageID          = "messageID";
        inline constexpr auto messageType        = "messageType";
        inline constexpr auto phoneNumber        = "phoneNumber";
        inline constexpr auto createdAt          = "createdAt";
        inline constexpr auto lastUsedAt         = "lastUsedAt";
        inline constexpr auto lastUpdatedAt      = "lastUpdatedAt";
        inline constexpr auto isUnread           = "isUnread";
        inline constexpr auto contactID          = "contactID";
        inline constexpr auto number             = "number";
        inline constexpr auto numberID           = "numberID";
        inline constexpr auto threadID           = "threadID";
        inline constexpr auto messageSnippet     = "messageSnippet";
        inline constexpr auto templateBody       = "templateBody";
        inline constexpr auto templateID         = "templateID";
        inline constexpr auto limit          = "limit";
        inline constexpr auto offset         = "offset";
        inline constexpr auto order          = "order";
        inline constexpr auto entries        = "entries";
        inline constexpr auto messageBody    = "messageBody";
        inline constexpr auto messageCount   = "messageCount";
        inline constexpr auto messageID      = "messageID";
        inline constexpr auto messageType    = "messageType";
        inline constexpr auto phoneNumber    = "phoneNumber";
        inline constexpr auto createdAt      = "createdAt";
        inline constexpr auto lastUsedAt     = "lastUsedAt";
        inline constexpr auto lastUpdatedAt  = "lastUpdatedAt";
        inline constexpr auto isUnread       = "isUnread";
        inline constexpr auto contactID      = "contactID";
        inline constexpr auto number         = "number";
        inline constexpr auto numberID       = "numberID";
        inline constexpr auto threadID       = "threadID";
        inline constexpr auto messageSnippet = "messageSnippet";
        inline constexpr auto templateBody   = "templateBody";
        inline constexpr auto templateID     = "templateID";
    } // namespace messages

    namespace outbox


@@ 122,7 122,7 @@ namespace sdesktop::endpoints::json
    {
        inline constexpr auto value     = "value";
        inline constexpr auto timestamp = "timestamp";
    }
    } // namespace timeSync

    namespace quotes
    {


@@ 131,6 131,7 @@ namespace sdesktop::endpoints::json
        inline constexpr auto group    = "group";
        inline constexpr auto interval = "interval";
        inline constexpr auto quoteID  = "quoteID";
        inline constexpr auto settings = "settings";
    } // namespace quotes

} // namespace sdesktop::endpoints::json

M products/BellHybrid/services/db/ServiceDB.cpp => products/BellHybrid/services/db/ServiceDB.cpp +40 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2025, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/blob/master/LICENSE.md

#include "include/db/ServiceDB.hpp"


@@ 56,6 56,35 @@ db::Interface *ServiceDB::getInterface(db::Interface::Name interface)
    return nullptr;
}

sys::MessagePointer ServiceDB::DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp)
{
    const auto message = ServiceDBCommon::DataReceivedHandler(msgl, resp);
    auto responseMsg   = std::static_pointer_cast<sys::ResponseMessage>(message);
    if (responseMsg) {
        return responseMsg;
    }

    auto type = static_cast<MessageType>(msgl->messageType);
    switch (type) {
    case MessageType::DBSyncPackage: {
        auto time   = utils::time::Scoped("DBSyncPackage");
        auto msg    = static_cast<DBServiceMessageSyncPackage *>(msgl);
        auto ret    = StoreIntoSyncPackage({msg->syncPackagePath});
        responseMsg = std::make_shared<DBServiceResponseMessage>(ret);
    } break;

    default:
        break;
    }

    if (responseMsg == nullptr) {
        return std::make_shared<sys::ResponseMessage>();
    }

    responseMsg->responseTo = msgl->messageType;
    return responseMsg;
}

sys::ReturnCodes ServiceDB::InitHandler()
{
    if (const auto returnCode = ServiceDBCommon::InitHandler(); returnCode != sys::ReturnCodes::Success) {


@@ 98,3 127,13 @@ sys::ReturnCodes ServiceDB::InitHandler()

    return sys::ReturnCodes::Success;
}

bool ServiceDB::StoreIntoSyncPackage(const std::filesystem::path &syncPackagePath)
{
    const auto &path = syncPackagePath / std::filesystem::path(quotesDB->getName()).filename();
    if (!quotesDB->storeIntoFile(path)) {
        LOG_ERROR("Store quotesDB in sync package failed");
        return false;
    }
    return true;
}

M products/BellHybrid/services/db/agents/QuotesAgent.cpp => products/BellHybrid/services/db/agents/QuotesAgent.cpp +23 -0
@@ 36,9 36,15 @@ namespace Quotes
        else if (typeid(*query) == typeid(Messages::InformGroupChanged)) {
            return handleGroupChanged(query);
        }
        else if (typeid(*query) == typeid(Messages::GetGroup)) {
            return handleGetGroup(query);
        }
        else if (typeid(*query) == typeid(Messages::InformIntervalChanged)) {
            return handleIntervalChanged(query);
        }
        else if (typeid(*query) == typeid(Messages::GetInterval)) {
            return handleGetInterval(query);
        }
        return nullptr;
    }



@@ 160,6 166,14 @@ namespace Quotes
        return std::make_unique<Messages::NotificationResult>(true);
    }

    auto QuotesAgent::handleGetGroup(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        const auto &quotesGroup = settings->getValue(settings::Quotes::selectedGroup, settings::SettingsScope::Global);
        auto response          = std::make_unique<Messages::GetGroupResponse>(quotesGroup);
        response->setRequestQuery(query);
        return response;
    }

    auto QuotesAgent::handleIntervalChanged(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        const auto request = std::dynamic_pointer_cast<Messages::InformIntervalChanged>(query);


@@ 174,4 188,13 @@ namespace Quotes
        shuffleQuoteModel.updateList(ListUpdateMode::Forced);
        return std::make_unique<Messages::NotificationResult>(true);
    }

    auto QuotesAgent::handleGetInterval(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        const auto &quotesInterval =
            settings->getValue(settings::Quotes::selectedInterval, settings::SettingsScope::Global);
        auto response = std::make_unique<Messages::GetIntervalResponse>(quotesInterval);
        response->setRequestQuery(query);
        return response;
    }
} // namespace Quotes

M products/BellHybrid/services/db/agents/QuotesAgent.hpp => products/BellHybrid/services/db/agents/QuotesAgent.hpp +2 -0
@@ 35,6 35,8 @@ namespace Quotes
        auto handleEditEntry(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleDeleteEntry(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleGroupChanged(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleGetGroup(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleIntervalChanged(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleGetInterval(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
    };
} // namespace Quotes

M products/BellHybrid/services/db/include/db/ServiceDB.hpp => products/BellHybrid/services/db/include/db/ServiceDB.hpp +4 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2025, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/blob/master/LICENSE.md

#pragma once


@@ 34,7 34,10 @@ class ServiceDB : public ServiceDBCommon
    std::unique_ptr<db::multimedia_files::MultimediaFilesRecordInterface> multimediaFilesRecordInterface;

    db::Interface *getInterface(db::Interface::Name interface) override;
    sys::MessagePointer DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp) override;
    sys::ReturnCodes InitHandler() override;

    bool StoreIntoSyncPackage(const std::filesystem::path &syncPackagePath);
};

namespace sys

M products/BellHybrid/services/desktop/endpoints/quotes/QuotesHelper.cpp => products/BellHybrid/services/desktop/endpoints/quotes/QuotesHelper.cpp +44 -1
@@ 14,7 14,50 @@ namespace sdesktop::endpoints
{
    auto QuotesHelper::processGet(Context &context) -> ProcessResult
    {
        // TODO: https://appnroll.atlassian.net/browse/BH-2099
        const auto &body = context.getBody();
        if (const auto settings = body[json::quotes::settings].string_value(); !settings.empty()) {
            if (settings == json::quotes::group) {
                auto listener = std::make_unique<db::EndpointListener>(
                    [=](db::QueryResult *result, Context &context) {
                        const auto getGroupResult = dynamic_cast<Quotes::Messages::GetGroupResponse *>(result);
                        if (getGroupResult == nullptr) {
                            context.setResponseStatus(http::Code::InternalServerError);
                            sender::putToSendQueue(context.createSimpleResponse());
                            return false;
                        }

                        context.setResponseBody(json11::Json::object{{json::quotes::group, getGroupResult->group}});
                        context.setResponseStatus(http::Code::OK);
                        sender::putToSendQueue(context.createSimpleResponse());
                        return true;
                    },
                    context);

                DBServiceAPI::QuotesGetGroup(owner, std::move(listener));
                return {Sent::Yes, std::nullopt};
            }
            else if (settings == json::quotes::interval) {
                auto listener = std::make_unique<db::EndpointListener>(
                    [=](db::QueryResult *result, Context &context) {
                        const auto getIntervalResult = dynamic_cast<Quotes::Messages::GetIntervalResponse *>(result);
                        if (getIntervalResult == nullptr) {
                            context.setResponseStatus(http::Code::InternalServerError);
                            sender::putToSendQueue(context.createSimpleResponse());
                            return false;
                        }

                        context.setResponseBody(
                            json11::Json::object{{json::quotes::interval, getIntervalResult->interval}});
                        context.setResponseStatus(http::Code::OK);
                        sender::putToSendQueue(context.createSimpleResponse());
                        return true;
                    },
                    context);

                DBServiceAPI::QuotesGetInterval(owner, std::move(listener));
                return {Sent::Yes, std::nullopt};
            }
        }
        return {Sent::No, ResponseContext{.status = http::Code::BadRequest}};
    }


M test/custom_quotes.py => test/custom_quotes.py +221 -58
@@ 39,13 39,63 @@ endpoint_types = {
payload_marker = '#'
payload_size_len = 9
port_timeout_short_s = 1
port_timeout_medium_s = 2
port_timeout_long_s = 5
port_baudrate = 115200

default_rx_data_size = 1024
chunk_rx_data_size = 1024 * 200

sync_filename = 'sync.tar'
sync_path = '/user/temp/' + sync_filename

def get_new_uuid() -> int:
    return random.randint(1, 10000)

def send_data(port_path: str, payload_str: str, expected_response: int) -> bool:
    payload_len_str = str(len(payload_str)).rjust(payload_size_len, '0')
    request_str = payload_marker + payload_len_str + payload_str

    with serial.Serial(port_path, port_baudrate, timeout=port_timeout_short_s) as port:
        port.write(request_str.encode('ascii'))
        response = port.read(2048)
        resp_json = json.loads(response[10:])
        status = resp_json['status']
        if status == expected_response:
            print(f'Request success')
            return True
        print(f'Request failed, status {status}')
        return False
    
def get_request_response(port_path: str, request: str, expected_size: int) -> str:
    payload_len_str = str(len(request)).rjust(payload_size_len, '0')
    request_str = payload_marker + payload_len_str + request

    response = ''
    with serial.Serial(port_path, port_baudrate, timeout=port_timeout_medium_s) as port:
        port.write(request_str.encode('ascii'))
        response = port.read(expected_size)
    return response
    
def get_str_data(port_path: str, payload_str: str, expected_responses: int, body_str: str) -> str:
    payload_len_str = str(len(payload_str)).rjust(payload_size_len, '0')
    request_str = payload_marker + payload_len_str + payload_str

    with serial.Serial(port_path, port_baudrate, timeout=port_timeout_short_s) as port:
        port.write(request_str.encode('ascii'))
        response = port.read(2048)
        resp_json = json.loads(response[10:])
        status = resp_json['status']

        for expected_status in expected_responses:
            if status == expected_status:
                print(f'Request success')
                body = resp_json['body']
                return body[body_str]
            
        print(f'Request failed, status {status}')
        return ""

def add_quote(port_path: str, quote_str: str, author_str: str) -> bool:
    uuid = get_new_uuid()



@@ 76,7 126,6 @@ def add_quote(port_path: str, quote_str: str, author_str: str) -> bool:
    
def edit_quote(port_path: str, quote_id: int,  quote_str: str, author_str: str) -> bool:
    uuid = get_new_uuid()

    payload = {
        'endpoint': endpoint_types['Quotes'],
        'method': http_methods['PUT'],


@@ 87,25 136,11 @@ def edit_quote(port_path: str, quote_id: int,  quote_str: str, author_str: str) 
            'author': author_str
        }
    }

    payload_str = json.dumps(payload)
    payload_len_str = str(len(payload_str)).rjust(payload_size_len, '0')
    request_str = payload_marker + payload_len_str + payload_str

    with serial.Serial(port_path, port_baudrate, timeout=port_timeout_short_s) as port:
        port.write(request_str.encode('ascii'))
        response = port.read(2048)
        resp_json = json.loads(response[10:])
        status = resp_json['status']
        if status == 200:
            print(f'Request success')
            return True
        print(f'Request failed, status {status}')
        return False
    return send_data(port_path, payload_str, 200)

def delete_quote(port_path: str, quote_id: int) -> bool:
    uuid = get_new_uuid()

    payload = {
        'endpoint': endpoint_types['Quotes'],
        'method': http_methods['DELETE'],


@@ 114,25 149,11 @@ def delete_quote(port_path: str, quote_id: int) -> bool:
            'quoteID': quote_id
        }
    }

    payload_str = json.dumps(payload)
    payload_len_str = str(len(payload_str)).rjust(payload_size_len, '0')
    request_str = payload_marker + payload_len_str + payload_str

    with serial.Serial(port_path, port_baudrate, timeout=port_timeout_short_s) as port:
        port.write(request_str.encode('ascii'))
        response = port.read(2048)
        resp_json = json.loads(response[10:])
        status = resp_json['status']
        if status == 204:
            print(f'Request success')
            return True
        print(f'Request failed, status {status}')
        return False
    return send_data(port_path, payload_str, 204)

def change_group(port_path: str, quote_group: str) -> bool:
    uuid = get_new_uuid()

    payload = {
        'endpoint': endpoint_types['Quotes'],
        'method': http_methods['PUT'],


@@ 141,25 162,11 @@ def change_group(port_path: str, quote_group: str) -> bool:
            'group': quote_group
        }
    }

    payload_str = json.dumps(payload)
    payload_len_str = str(len(payload_str)).rjust(payload_size_len, '0')
    request_str = payload_marker + payload_len_str + payload_str

    with serial.Serial(port_path, port_baudrate, timeout=port_timeout_short_s) as port:
        port.write(request_str.encode('ascii'))
        response = port.read(2048)
        resp_json = json.loads(response[10:])
        status = resp_json['status']
        if status == 200:
            print(f'Request success')
            return True
        print(f'Request failed, status {status}')
        return False
    return send_data(port_path, payload_str, 200)

def change_interval(port_path: str, quote_interval: str) -> bool:
    uuid = get_new_uuid()

    payload = {
        'endpoint': endpoint_types['Quotes'],
        'method': http_methods['PUT'],


@@ 168,22 175,162 @@ def change_interval(port_path: str, quote_interval: str) -> bool:
            'interval': quote_interval
        }
    }
    payload_str = json.dumps(payload)
    return send_data(port_path, payload_str, 200)
    
def get_settings(port_path: str, settings: str) -> str:
    uuid = get_new_uuid()
    payload = {
        'endpoint': endpoint_types['Quotes'],
        'method': http_methods['GET'],
        'uuid': uuid,
        'body': {
            'settings': settings
        }
    }
    payload_str = json.dumps(payload)
    return get_str_data(port_path, payload_str, [200], settings)

def start_sync(port_path: str) -> bool:
    uuid = get_new_uuid()
    payload = {
        'endpoint': endpoint_types['Backup'],
        'method': http_methods['POST'],
        'uuid': uuid,
        'body': {
            "category" : "sync"
        }
    }
    payload_str = json.dumps(payload)
    payload_len_str = str(len(payload_str)).rjust(payload_size_len, '0')
    request_str = payload_marker + payload_len_str + payload_str
    return send_data(port_path, payload_str, 202)

    with serial.Serial(port_path, port_baudrate, timeout=port_timeout_short_s) as port:
        port.write(request_str.encode('ascii'))
        response = port.read(2048)
        resp_json = json.loads(response[10:])
        status = resp_json['status']
        if status == 200:
            print(f'Request success')
def wait_for_sync_end(port_path: str, timeout: int) -> bool:
    uuid = get_new_uuid()
    payload = {
        'endpoint': endpoint_types['Backup'],
        'method': http_methods['GET'],
        'uuid': uuid,
        'body': {
            "category" : "sync"
        }
    }
    payload_str = json.dumps(payload)

    start_timestamp = time.time()
    while time.time() < start_timestamp + timeout:
        status = get_str_data(port_path, payload_str, [204, 303], 'state')
        print("sync status: " + status)
        if status == "finished":
            return True
        print(f'Request failed, status {status}')
        time.sleep(1)

    return False

def download_file(port_path: str, path: str) -> bool:
    uuid = get_new_uuid()
    chunk_size = 0
    file_size = 0

    payload = {
        'endpoint': endpoint_types['FilesystemUpload'],
        'method': http_methods['GET'],
        'uuid': uuid,
        'body': {
            'fileName': path
        }
    }
    payload_str = json.dumps(payload)

    requestID = {
        'endpoint': endpoint_types['FilesystemUpload'],
        'method': http_methods['GET'],
        'uuid': uuid,
        'body': {
            'rxID': 0,
            'chunkNo': 1
        }
    }

    response = get_request_response(port_path, payload_str, default_rx_data_size)
    if not response:
        print("Request GET file " + path + " info failed!")
        return False
    
    resp_json = json.loads(response[10:])
    status = resp_json['status']
    if status == 200:
        requestID['body']['rxID'] = resp_json['body']['rxID']
        chunk_size = resp_json['body']['chunkSize']
        file_size = resp_json['body']['fileSize']
    else:
        print(f'Request failed, status: {status}')
        return False

    chunks = int((file_size / chunk_size) + 1)
    print(f'File size: {file_size} chunk size: {chunk_size} chunks: {chunks}')

    filename = "temp/" + sync_filename
    os.makedirs(os.path.dirname(filename), exist_ok=True)
    progress_bar = tqdm(desc=f'Downloading \'{path}\'', total=file_size, unit='bytes', unit_scale=True, unit_divisor=1024)
    with open(filename, 'wb') as file:
        for i in range(1, chunks + 1):
            requestID_str = json.dumps(requestID)
            response = get_request_response(port_path, requestID_str, chunk_rx_data_size)
            if not response:
                print("Request GET chunkNo: " + requestID['body']['chunkNo'] + " failed!")
                return False

            resp_json = json.loads(response[10:])
            status = resp_json['status']
            if status == 200:
                data = base64.b64decode(resp_json['body']['data'])
                file.write(data)
                requestID['body']['chunkNo'] = i + 1
                progress_bar.update(len(data))
            else:
                print(f'Request failed, status {status}')
                progress_bar.close()
                return False

    progress_bar.close()
    return True

def delete_sync(port_path: str, path: str) -> bool:
    uuid = get_new_uuid()
    payload = {
        'endpoint': endpoint_types['FilesystemUpload'],
        'method': http_methods['DELETE'],
        'uuid': uuid,
        'body': {
            "removeFile" : path
        }
    }
    payload_str = json.dumps(payload)
    return send_data(port_path, payload_str, 204)

def get_quotes(port_path: str) -> bool:
    if start_sync(port_path) == False:
        print("Error! Synchronization has not been started.")
        return False
    print("Synchronization has started...")

    if wait_for_sync_end(port_path, 20) == False:
        print("Error! Synchronization fail.")
        return False
    print("Synchronization completed successfully.")

    if download_file(port_path, sync_path) == False:
        print("Error! Sync file download fail.")
        return False
    print("Sync file download completed.")

    if delete_sync(port_path, sync_path) == False:
        print("Error! Failed to delete sync file.")
        return False
    print("Sync file deletion completed successfully.")

    return True

def main():
    parser = argparse.ArgumentParser(
        prog='custom_quotes',


@@ 215,14 362,17 @@ def main():
    parser.add_argument('-i', '--interval',
                        metavar='quotes_display_interval',
                        help='quotes display interval [\'x\' minutes or \'AtMidnight\']')
    parser.add_argument('-s', '--synchro',
                        metavar='settings_to_get',
                        help='get quotes file or settings [\'quotes\' or \'group\' or \'interval\']')

    args = parser.parse_args()
    if not args.port:
        print('Invalid usage: please specify device port')
        print('Run with -h to see help')
        return
    if not args.add and not args.delete and not args.edit and not args.group and not args.interval:
        print('Invalid usage: please specify add, delete, edit, group or interval argument')
    if not args.add and not args.delete and not args.edit and not args.group and not args.interval and not args.synchro:
        print('Invalid usage: please specify add, delete, edit, synchro, group or interval argument')
        print('Run with -h to see help')
        return
    if args.add:


@@ 250,6 400,19 @@ def main():
    elif args.interval:
        print("quotes interval: " + args.interval)
        status = change_interval(args.port, args.interval)
    elif args.synchro:
        if args.synchro == "quotes":
            print("downloading quotes file")
            status = get_quotes(args.port)
        elif args.synchro == "group":
            group = get_settings(args.port, args.synchro)
            print("quotes group: " + group)
        elif args.synchro == "interval":
            interval = get_settings(args.port, args.synchro)
            print("quotes interval: " + interval)
        else:
            print('Invalid usage: please choose one option: \'quotes\' or \'group\' or \'interval\'')
            print('Run with -h to see help')

if __name__ == '__main__':
    main()