M changelog.md => changelog.md +1 -0
@@ 3,6 3,7 @@
### Added
* `[cellular]` Integration with basic flow for SIM card (cellular<>GUI)
+* `[file indexer db]` Added UT fot File Indexer DB
### Fixed
M module-services/service-db/agents/file_indexer/FileIndexerAgent.cpp => module-services/service-db/agents/file_indexer/FileIndexerAgent.cpp +43 -19
@@ 19,6 19,19 @@ void FileIndexerAgent::initDb()
{
LOG_INFO("[ServiceDB][File Indexer] Initialized");
database->execute(getDbInitString().c_str());
+
+ auto notifications = database->query(FileIndexer::Statements::getAllNotifications);
+ if (nullptr == notifications || FileIndexer::ONE_ROW_FOUND == notifications->getRowCount()) {
+ return;
+ }
+ if (notifications->getFieldCount() == FileIndexer::NOTIFICATION_RECORD_COLUMN_COUNT) {
+ fileChangeRecipents.clear();
+ do {
+ std::string directory = (*notifications)[1].getString();
+ std::string service = (*notifications)[2].getString();
+ fileChangeRecipents[directory].insert(service);
+ } while (notifications->nextRow());
+ }
}
void FileIndexerAgent::deinitDb()
@@ 91,30 104,40 @@ auto FileIndexerAgent::dbUnregisterFileChange(std::string dir, std::string servi
auto FileIndexerAgent::handleRegisterOnFileChange(sys::Message *req) -> sys::MessagePointer
{
+ std::string directory;
if (auto msg = dynamic_cast<FileIndexer::Messages::RegisterOnFileChange *>(req)) {
- if (dbRegisterFileChange(msg->directory, msg->sender)) {
- auto it = fileChangeRecipents.find(msg->directory);
- if (fileChangeRecipents.end() == it) {
- fileChangeRecipents[msg->directory] = {msg->sender};
- }
- else {
- it->second.insert(msg->sender);
+ if (msg->directory != nullptr) {
+ directory = *(msg->directory);
+ if (dbRegisterFileChange(directory, msg->sender)) {
+ auto it = fileChangeRecipents.find(directory);
+ if (fileChangeRecipents.end() == it) {
+ fileChangeRecipents[directory] = {msg->sender};
+ }
+ else {
+ it->second.insert(msg->sender);
+ }
}
}
}
+ LOG_INFO("RegisterOnFileChange from %s", req->sender.c_str());
return std::make_shared<sys::ResponseMessage>();
}
auto FileIndexerAgent::handleUnregisterOnFileChange(sys::Message *req) -> sys::MessagePointer
{
+ std::string directory;
if (auto msg = dynamic_cast<FileIndexer::Messages::UnregisterOnFileChange *>(req)) {
- if (dbUnregisterFileChange(msg->directory, msg->sender)) {
- auto it = fileChangeRecipents.find(msg->directory);
- if (fileChangeRecipents.end() != it) {
- it->second.erase(msg->sender);
+ if (msg->directory != nullptr) {
+ directory = *(msg->directory);
+ if (dbUnregisterFileChange(directory, msg->sender)) {
+ auto it = fileChangeRecipents.find(directory);
+ if (fileChangeRecipents.end() != it) {
+ it->second.erase(msg->sender);
+ }
}
}
}
+ LOG_INFO("UnRegisterOnFileChange from %s", req->sender.c_str());
return std::make_shared<sys::ResponseMessage>();
}
@@ 200,7 223,6 @@ auto FileIndexerAgent::dbGetRecord(std::unique_ptr<FileIndexer::FileRecord> reco
auto FileIndexerAgent::dbSetRecord(std::unique_ptr<FileIndexer::FileRecord> record) -> bool
{
-
auto retQuery = database->query(FileIndexer::Statements::getFileInfoByPath, record->path.c_str());
FileIndexer::FileRecord retRecord(retQuery.get());
@@ 262,11 284,12 @@ auto FileIndexerAgent::handleSetRecord(sys::Message *req) -> sys::MessagePointer
sys::Bus::SendUnicast(std::move(updateMsg), msg->sender, parentService);
dbSetRecord(std::make_unique<FileIndexer::FileRecord>(record));
- }
- for (auto recipient : fileChangeRecipents[record.directory]) {
- auto notifeMsg = std::make_shared<FileIndexer::Messages::FileChanged>(record.directory);
- sys::Bus::SendUnicast(std::move(notifeMsg), recipient, parentService);
+ for (auto recipient : fileChangeRecipents[record.directory]) {
+ auto notifeMsg = std::make_shared<FileIndexer::Messages::FileChanged>(
+ std::make_unique<std::string>(record.directory));
+ sys::Bus::SendUnicast(std::move(notifeMsg), recipient, parentService);
+ }
}
}
return std::make_shared<sys::ResponseMessage>();
@@ 402,7 425,7 @@ auto FileIndexerAgent::handleGetProperty(sys::Message *req) -> sys::MessagePoint
auto FileIndexerAgent::handleGetAllProperties(sys::Message *req) -> sys::MessagePointer
{
- if (auto msg = dynamic_cast<FileIndexer::Messages::GetPropertyMessage *>(req)) {
+ if (auto msg = dynamic_cast<FileIndexer::Messages::GetAllPropertiesMessage *>(req)) {
auto metaDataPtr = std::move(msg->metaData);
FileIndexer::FileMetadata metaData = *metaDataPtr;
msg->dbMetaData = dbGetAllProperties(std::make_unique<FileIndexer::FileMetadata>(metaData));
@@ 437,7 460,7 @@ auto FileIndexerAgent::handleSetProperty(sys::Message *req) -> sys::MessagePoint
auto FileIndexerAgent::handleSetProperties(sys::Message *req) -> sys::MessagePointer
{
- if (auto msg = dynamic_cast<FileIndexer::Messages::SetPropertyMessage *>(req)) {
+ if (auto msg = dynamic_cast<FileIndexer::Messages::SetPropertiesMessage *>(req)) {
auto metaDataPtr = std::move(msg->metaData);
FileIndexer::FileMetadata metaData = *metaDataPtr;
msg->dbMetaData = dbGetAllProperties(std::make_unique<FileIndexer::FileMetadata>(metaData));
@@ 447,7 470,8 @@ auto FileIndexerAgent::handleSetProperties(sys::Message *req) -> sys::MessagePoi
dbSetProperties(std::make_unique<FileIndexer::FileMetadata>(metaData));
for (auto recipient : fileChangeRecipents[metaData.directory]) {
- auto notifyMsg = std::make_shared<FileIndexer::Messages::FileChanged>(metaData.directory);
+ auto notifyMsg =
+ std::make_shared<FileIndexer::Messages::FileChanged>(std::make_unique<std::string>(metaData.directory));
sys::Bus::SendUnicast(std::move(notifyMsg), recipient, parentService);
}
}
M module-services/service-db/agents/file_indexer/FileIndexerAgent.hpp => module-services/service-db/agents/file_indexer/FileIndexerAgent.hpp +15 -11
@@ 16,13 16,24 @@ class FileIndexerAgent : public DatabaseAgent
void deinitDb() override;
void registerMessages() override;
auto getAgentName() -> const std::string override;
- void dbTest();
- private:
- using MapOfRecipentsToBeNotified = std::map<std::string, std::set<std::string>>;
- MapOfRecipentsToBeNotified fileChangeRecipents;
+ protected:
+ // msg handlers
auto handleRegisterOnFileChange(sys::Message *req) -> sys::MessagePointer;
auto handleUnregisterOnFileChange(sys::Message *req) -> sys::MessagePointer;
+ auto handleListDir(sys::Message *req) -> sys::MessagePointer;
+ auto handleGetProperty(sys::Message *req) -> sys::MessagePointer;
+ auto handleGetAllProperties(sys::Message *req) -> sys::MessagePointer;
+ auto handleSetProperty(sys::Message *req) -> sys::MessagePointer;
+ auto handleSetProperties(sys::Message *req) -> sys::MessagePointer;
+ auto handleGetRecord(sys::Message *req) -> sys::MessagePointer;
+ auto handleSetRecord(sys::Message *req) -> sys::MessagePointer;
+
+ using MapOfRecipentsToBeNotified = std::map<std::string, std::set<std::string>>;
+ MapOfRecipentsToBeNotified fileChangeRecipents;
+
+ private:
+ // db operations
auto dbRegisterFileChange(std::string dir, std::string service) -> bool;
auto dbUnregisterFileChange(std::string dir, std::string service) -> bool;
auto dbListDir(std::unique_ptr<FileIndexer::ListDirData> listDir) -> std::unique_ptr<FileIndexer::ListDirData>;
@@ 37,11 48,4 @@ class FileIndexerAgent : public DatabaseAgent
auto getDbInitString() -> const std::string override;
auto getDbFilePath() -> const std::string override;
auto dbGetFilesCount() -> unsigned int;
- auto handleListDir(sys::Message *req) -> sys::MessagePointer;
- auto handleGetProperty(sys::Message *req) -> sys::MessagePointer;
- auto handleGetAllProperties(sys::Message *req) -> sys::MessagePointer;
- auto handleSetProperty(sys::Message *req) -> sys::MessagePointer;
- auto handleSetProperties(sys::Message *req) -> sys::MessagePointer;
- auto handleGetRecord(sys::Message *req) -> sys::MessagePointer;
- auto handleSetRecord(sys::Message *req) -> sys::MessagePointer;
};
M module-services/service-db/service-db/FileIndexerMessages.hpp => module-services/service-db/service-db/FileIndexerMessages.hpp +7 -6
@@ 15,6 15,7 @@ namespace FileIndexer
{
constexpr unsigned int FILE_ID_NOT_EXISTS = 0;
constexpr unsigned int FILE_RECORD_COLUMN_COUNT = 7;
+ constexpr unsigned int NOTIFICATION_RECORD_COLUMN_COUNT = 3;
constexpr unsigned int ONE_ROW_FOUND = 1;
constexpr unsigned int ZERO_ROWS_FOUND = 0;
@@ 94,28 95,28 @@ namespace FileIndexer
{
public:
RegisterOnFileChange() = default;
- explicit RegisterOnFileChange(std::string dir) : directory(std::move(dir))
+ explicit RegisterOnFileChange(std::unique_ptr<std::string> dir) : directory(std::move(dir))
{}
- std::string directory;
+ std::unique_ptr<std::string> directory;
};
class UnregisterOnFileChange : public FileIndexerMessage
{
public:
UnregisterOnFileChange() = default;
- explicit UnregisterOnFileChange(std::string dir) : directory(std::move(dir))
+ explicit UnregisterOnFileChange(std::unique_ptr<std::string> dir) : directory(std::move(dir))
{}
- std::string directory;
+ std::unique_ptr<std::string> directory;
};
class FileChanged : public FileIndexerMessage
{
public:
FileChanged() = default;
- explicit FileChanged(std::string dir) : directory(dir)
+ explicit FileChanged(std::unique_ptr<std::string> dir) : directory(std::move(dir))
{}
- std::string directory;
+ std::unique_ptr<std::string> directory;
};
class GetListDirMessage : public FileIndexerMessage
M module-services/service-db/test/CMakeLists.txt => module-services/service-db/test/CMakeLists.txt +1 -0
@@ 7,6 7,7 @@ add_catch2_executable(
test-service-db-api.cpp
test-service-db-settings-messages.cpp
test-service-db-settings-api.cpp
+ test-service-db-file_indexer.cpp
LIBS
service-audio
module-vfs
A module-services/service-db/test/test-service-db-file_indexer.cpp => module-services/service-db/test/test-service-db-file_indexer.cpp +226 -0
@@ 0,0 1,226 @@
+// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include <catch2/catch.hpp>
+
+#include <Service/Service.hpp>
+#include <functional>
+#include <thread> // for Message_t, ResponseMessage, DataMessage, Message
+#include <service-db/ServiceDB.hpp>
+
+#include <module-services/service-db/test/test-service-db-file_indexer.hpp>
+#include <service-db/FileIndexerMessages.hpp>
+
+class ClientService : public sys::Service
+{
+ public:
+ using sys::Service::Service;
+
+ explicit ClientService(std::string name) : Service(name)
+ {}
+
+ sys::MessagePointer DataReceivedHandler(sys::DataMessage *msg, sys::ResponseMessage *resp)
+ {
+ return std::make_shared<sys::ResponseMessage>();
+ }
+
+ sys::ReturnCodes InitHandler()
+ {
+ return sys::ReturnCodes::Success;
+ }
+
+ sys::ReturnCodes DeinitHandler()
+ {
+ return sys::ReturnCodes::Success;
+ }
+
+ sys::ReturnCodes SwitchPowerModeHandler(const sys::ServicePowerMode mode)
+ {
+ return sys::ReturnCodes::Success;
+ }
+};
+
+TEST_CASE("FileIndexer")
+{
+ std::string serviceName = "file_indexer_client";
+ ClientService file_indexer_client_service(serviceName);
+ file_indexer_client_service.InitHandler();
+
+ SECTION("init db, register /unregister on file change")
+ {
+ std::cout << "Init File Indexer DB" << std::endl << std::flush;
+ std::unique_ptr<FileIndexerTest> fiAgentTest{new FileIndexerTest(&file_indexer_client_service)};
+ fiAgentTest->initDb();
+ fiAgentTest->registerMessages();
+
+ std::cout << "Register on file change" << std::endl << std::flush;
+ std::string inputDirReg = "mp3";
+ auto recordPtr = std::make_unique<std::string>(inputDirReg);
+ auto regMsg = std::make_shared<FileIndexer::Messages::RegisterOnFileChange>(std::move(recordPtr));
+ regMsg->sender = serviceName;
+ sys::MessagePointer recMsg = fiAgentTest->handleRegisterOnFileChangeTest(regMsg.get());
+ fiAgentTest->getNumberOfRecipients(inputDirReg);
+
+ regMsg->sender = "serv1";
+ recMsg = fiAgentTest->handleRegisterOnFileChangeTest(regMsg.get());
+ auto recipientsCount1 = fiAgentTest->getNumberOfRecipients(inputDirReg);
+
+ std::cout << "Unregister on file change" << std::endl << std::flush;
+ std::string inputDirUnReg = "mp3";
+ auto strPtr = std::make_unique<std::string>(inputDirUnReg);
+ auto unregMsg = std::make_shared<FileIndexer::Messages::UnregisterOnFileChange>(std::move(strPtr));
+ unregMsg->sender = serviceName;
+ recMsg = fiAgentTest->handleUnregisterOnFileChangeTest(unregMsg.get());
+ auto recipientsCount2 = fiAgentTest->getNumberOfRecipients(inputDirUnReg);
+
+ REQUIRE(recipientsCount2 == recipientsCount1 - 1);
+ REQUIRE(fiAgentTest->getAgentName() == "fileIndexerAgent");
+ }
+
+ SECTION("SetRecord & GetRecord")
+ {
+ FileIndexer::FileRecord record{};
+ std::cout << "Test File indexer Get/Set/Get Record sequence:" << std::endl << std::flush;
+
+ // GetRecord
+ record.file_id = 1;
+ auto recordPtr = std::make_unique<FileIndexer::FileRecord>(record);
+ auto getMsg = std::make_shared<FileIndexer::Messages::GetRecordMessage>(std::move(recordPtr));
+ std::unique_ptr<FileIndexerTest> fiAgentTest{new FileIndexerTest(&file_indexer_client_service)};
+ sys::MessagePointer recMsg = fiAgentTest->handleGetRecordTest(getMsg.get());
+ auto msg = dynamic_cast<FileIndexer::Messages::GetRecordResponseMessage *>(recMsg.get());
+ auto fileRec = msg->record.get();
+
+ LOG_INFO("file id= %d file path= %s size = %d mime= %d mtime = %d directory= %s ftype= %d",
+ fileRec->file_id,
+ fileRec->path.c_str(),
+ fileRec->size,
+ fileRec->mime_type,
+ fileRec->mtime,
+ fileRec->directory.c_str(),
+ fileRec->file_type);
+
+ REQUIRE(fileRec->file_id == record.file_id);
+
+ record = *fileRec; // fill record by data recived from db
+
+ // SetRecord - modify mime_type
+ record.mime_type = 2;
+ recordPtr = std::make_unique<FileIndexer::FileRecord>(record);
+ auto setMsg = std::make_shared<FileIndexer::Messages::SetRecordMessage>(std::move(recordPtr));
+ setMsg->sender = serviceName;
+ recMsg = fiAgentTest->handleSetRecordTest(setMsg.get());
+
+ // GetRecord to confirm changes in mime_type
+ recordPtr = std::make_unique<FileIndexer::FileRecord>(record);
+ getMsg = std::make_shared<FileIndexer::Messages::GetRecordMessage>(std::move(recordPtr));
+ recMsg = fiAgentTest->handleGetRecordTest(getMsg.get());
+ msg = dynamic_cast<FileIndexer::Messages::GetRecordResponseMessage *>(recMsg.get());
+ fileRec = msg->record.get();
+
+ LOG_INFO("file id= %d file path= %s size = %d mime= %d mtime = %d directory= %s ftype= %d",
+ fileRec->file_id,
+ fileRec->path.c_str(),
+ fileRec->size,
+ fileRec->mime_type,
+ fileRec->mtime,
+ fileRec->directory.c_str(),
+ fileRec->file_type);
+ // check if new value was set
+ REQUIRE(fileRec->mime_type == record.mime_type);
+ // check if the rest of the record did not change
+ REQUIRE(fileRec->file_id == record.file_id);
+ REQUIRE(fileRec->path == record.path);
+ REQUIRE(fileRec->size == record.size);
+ REQUIRE(fileRec->mtime == record.mtime);
+ REQUIRE(fileRec->directory == record.directory);
+ REQUIRE(fileRec->file_type == record.file_type);
+ }
+
+ SECTION("List dir - support for listview")
+ {
+ FileIndexer::ListDirData inputData{};
+ std::cout << "List dir - support for listview" << std::endl << std::flush;
+
+ // GetListDir
+ inputData.directory = "mp3";
+ inputData.list_offset = 1;
+ inputData.list_limit = 2;
+
+ auto recordPtr = std::make_unique<FileIndexer::ListDirData>(inputData);
+ auto getMsg = std::make_shared<FileIndexer::Messages::GetListDirMessage>(std::move(recordPtr));
+ std::unique_ptr<FileIndexerTest> fiAgentTest{new FileIndexerTest(&file_indexer_client_service)};
+ sys::MessagePointer recMsg = fiAgentTest->handleListDirTest(getMsg.get());
+ auto msg = dynamic_cast<FileIndexer::Messages::GetListDirResponseMessage *>(recMsg.get());
+ auto fileCount = msg->getCount();
+ auto fileList = msg->getResults();
+ LOG_INFO("Total number of files in %s = %d", inputData.directory.c_str(), fileCount);
+ LOG_INFO("Limit= %d Offset= %d", inputData.list_limit, inputData.list_offset);
+ for (auto fileRec : fileList) {
+ LOG_INFO("file id= %d file path= %s size = %d mime= %d mtime = %d directory= %s ftype= %d",
+ fileRec.file_id,
+ fileRec.path.c_str(),
+ fileRec.size,
+ fileRec.mime_type,
+ fileRec.mtime,
+ fileRec.directory.c_str(),
+ fileRec.file_type);
+ }
+ REQUIRE(fileList.size() == inputData.list_limit);
+ REQUIRE(fileList[0].directory == inputData.directory);
+ }
+
+ SECTION("get/set Properties")
+ {
+ FileIndexer::FileMetadata inputMetaData{};
+ std::cout << "get/set Properties" << std::endl << std::flush;
+
+ // Get Properties
+ inputMetaData.path = "mp3/track3.mp3";
+ LOG_INFO("File path= %s", inputMetaData.path.c_str());
+
+ auto recordPtr = std::make_unique<FileIndexer::FileMetadata>(inputMetaData);
+ auto getMsg = std::make_shared<FileIndexer::Messages::GetAllPropertiesMessage>(std::move(recordPtr));
+ std::unique_ptr<FileIndexerTest> fiAgentTest{new FileIndexerTest(&file_indexer_client_service)};
+ sys::MessagePointer recMsg = fiAgentTest->handleGetAllPropertiesTest(getMsg.get());
+ auto msg = dynamic_cast<FileIndexer::Messages::GetPropertyResponseMessage *>(recMsg.get());
+ for (auto prop : msg->metaData->properties) {
+ LOG_INFO("file id= %d property= %s value = %s",
+ msg->metaData->file_id,
+ prop.first.c_str(),
+ prop.second.c_str());
+ }
+ REQUIRE(msg->metaData->properties.size() > 0);
+
+ // set Properties
+ inputMetaData.properties.clear();
+ inputMetaData.properties.emplace("year", "2015");
+
+ std::string newProperty = "time";
+ std::string newPropertyValue = "5min";
+ inputMetaData.properties.emplace(newProperty, newPropertyValue);
+ recordPtr = std::make_unique<FileIndexer::FileMetadata>(inputMetaData);
+ auto setMsg = std::make_shared<FileIndexer::Messages::SetPropertiesMessage>(std::move(recordPtr));
+ setMsg->sender = serviceName;
+ recMsg = fiAgentTest->handleSetPropertiesTest(setMsg.get());
+
+ // Get Properties
+ recordPtr = std::make_unique<FileIndexer::FileMetadata>(inputMetaData);
+ getMsg = std::make_shared<FileIndexer::Messages::GetAllPropertiesMessage>(std::move(recordPtr));
+ recMsg = fiAgentTest->handleGetAllPropertiesTest(getMsg.get());
+ msg = dynamic_cast<FileIndexer::Messages::GetPropertyResponseMessage *>(recMsg.get());
+ for (auto prop : msg->metaData->properties) {
+ LOG_INFO("file id= %d property= %s value = %s",
+ msg->metaData->file_id,
+ prop.first.c_str(),
+ prop.second.c_str());
+ }
+ // serch if a new property was set correctly
+ std::string newPropertyValueFound;
+ auto it = msg->metaData->properties.find(newProperty);
+ if (msg->metaData->properties.end() != it) {
+ newPropertyValueFound = it->second;
+ }
+ REQUIRE(newPropertyValueFound == newPropertyValue);
+ }
+}
A module-services/service-db/test/test-service-db-file_indexer.hpp => module-services/service-db/test/test-service-db-file_indexer.hpp +60 -0
@@ 0,0 1,60 @@
+// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include <service-db/DatabaseAgent.hpp>
+#include <service-db/FileIndexerMessages.hpp>
+#include <module-services/service-db/agents/file_indexer/FileIndexerAgent.hpp>
+
+class FileIndexerTest : public FileIndexerAgent
+{
+ public:
+ FileIndexerTest(sys::Service *parentService) : FileIndexerAgent(parentService){};
+ ~FileIndexerTest() = default;
+
+ auto handleGetRecordTest(FileIndexer::Messages::GetRecordMessage *req) -> sys::MessagePointer
+ {
+ return FileIndexerAgent::handleGetRecord(req);
+ }
+
+ auto handleSetRecordTest(FileIndexer::Messages::SetRecordMessage *req) -> sys::MessagePointer
+ {
+ return FileIndexerAgent::handleSetRecord(req);
+ }
+
+ auto handleListDirTest(FileIndexer::Messages::GetListDirMessage *req) -> sys::MessagePointer
+ {
+ return FileIndexerAgent::handleListDir(req);
+ }
+
+ auto handleGetAllPropertiesTest(FileIndexer::Messages::GetAllPropertiesMessage *req) -> sys::MessagePointer
+ {
+ return FileIndexerAgent::handleGetAllProperties(req);
+ }
+
+ auto handleSetPropertiesTest(FileIndexer::Messages::SetPropertiesMessage *req) -> sys::MessagePointer
+ {
+ return FileIndexerAgent::handleSetProperties(req);
+ }
+
+ auto handleRegisterOnFileChangeTest(FileIndexer::Messages::RegisterOnFileChange *req) -> sys::MessagePointer
+ {
+ return FileIndexerAgent::handleRegisterOnFileChange(req);
+ }
+
+ auto handleUnregisterOnFileChangeTest(FileIndexer::Messages::UnregisterOnFileChange *req) -> sys::MessagePointer
+ {
+ return FileIndexerAgent::handleUnregisterOnFileChange(req);
+ }
+
+ unsigned int getNumberOfRecipients(std::string directory)
+ {
+ unsigned int recipientCount = 0;
+ for (auto recipient : fileChangeRecipents[directory]) {
+ LOG_INFO("Notifications: directory= %s service= %s", directory.c_str(), recipient.c_str());
+ recipientCount++;
+ }
+ return recipientCount++;
+ }
+};