/** @file * @author Edouard DUPIN * @copyright 2014, Edouard DUPIN, all right reserved * @license MPL v2.0 (see license file) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static ethread::Mutex g_mutex; static etk::String g_basePath; static etk::String g_basePathCover; static etk::String g_basePathCoverGroup; static etk::String g_baseDBName = etk::String(SERVICE_NAME) + "-database.json"; static etk::Vector> m_listFile; static uint64_t m_lastMaxId = 0; static uint64_t m_lastMaxImportId = 0; static bool g_needToStore = false; static uint64_t createUniqueID() { m_lastMaxId++; return m_lastMaxId; } static uint64_t createUniqueImportID() { m_lastMaxImportId++; return m_lastMaxImportId; } static etk::String removeSpaceOutQuote(const etk::String& _in) { etk::String out; bool insideQuote = false; for (auto &it : _in) { if (it == '\'') { if (insideQuote == false) { insideQuote = true; } else { insideQuote = false; } out += it; } else if ( it == ' ' && insideQuote == false) { // nothing to add ... } else { out += it; } } return out; } static etk::Vector splitAction(const etk::String& _in) { etk::Vector out; bool insideQuote = false; etk::String value; for (auto &it : _in) { if (it == '\'') { if (insideQuote == false) { insideQuote = true; } else { insideQuote = false; } if (value != "") { out.pushBack(value); value.clear(); } } else { value += it; } } if (value != "") { out.pushBack(value); } return out; } static void metadataChange(zeus::MediaImpl* _element, const etk::String& _key) { g_needToStore = true; // meta_data have chage ==> we need to upfdate the path of the file where the data is stored ... if (_element == null) { return; } _element->forceUpdateDecoratedName(); etk::String current = _element->getFileName(); etk::String next = _element->getDecoratedName() + "_" + _element->getSha512(); if (next == current) { return; } _element->move(next); } namespace appl { class VideoService : public zeus::service::Video { private: //ememory::SharedPtr& m_client; zeus::ProxyClientProperty m_client; etk::String m_userName; public: /* VideoService(ememory::SharedPtr& _client, const etk::String& _userName) : m_client(_client), m_userName(_userName) { APPL_WARNING("New VideoService ... for user: "); } */ VideoService(uint16_t _clientId) { APPL_VERBOSE("New VideoService ... for user: " << _clientId); } ~VideoService() { APPL_VERBOSE("delete VideoService ..."); } public: uint32_t count() override { ethread::UniqueLock lock(g_mutex); // TODO : Check right ... return m_listFile.size(); } etk::Vector getIds(uint32_t _start, uint32_t _stop) override { ethread::UniqueLock lock(g_mutex); // TODO : Check right ... etk::Vector out; for (size_t iii=_start; iiigetUniqueId()); } return out; } uint32_t getId(etk::String _sha512) override { ethread::UniqueLock lock(g_mutex); // TODO : Check right ... uint32_t out; for (size_t iii=0; iiigetSha512() == _sha512) { return m_listFile[iii]->getUniqueId(); } } throw etk::exception::InvalidArgument("sha512 not find..."); } // Return a File Data (might be a video .tiff/.png/.jpg) ememory::SharedPtr get(uint32_t _mediaId) override { ethread::UniqueLock lock(g_mutex); // TODO : Check right ... ememory::SharedPtr property; for (auto &it : m_listFile) { if (it == null) { continue; } if (it->getUniqueId() == _mediaId) { return it; } } throw etk::exception::InvalidArgument("Wrong file ID ..."); // } uint32_t add(zeus::ActionNotification& _notifs, zeus::ProxyFile _dataFile) override { //_action.setProgress("{\"pourcent\":" + etk::toString(23.54) + ", \"comment\":\"Start loading file\""); //_action.setProgress("{\"pourcent\":" + etk::toString(23.54) + ", \"comment\":\"transfering file\"");; //_action.setProgress("{\"pourcent\":" + etk::toString(23.54) + ", \"comment\":\"synchronize meta-data\""); uint64_t id = 0; uint64_t importId = 0; { ethread::UniqueLock lock(g_mutex); // TODO : Check right ... id = createUniqueID(); importId = createUniqueImportID(); } auto futRemoteSha512 = _dataFile.getSha512(); auto futType = _dataFile.getMineType(); auto futName = _dataFile.getName(); // wait the sha1 to check his existance: futRemoteSha512.wait(); etk::String sha512StringRemote = futRemoteSha512.get(); { ethread::UniqueLock lock(g_mutex); for (auto &it : m_listFile) { if (it == null) { continue; } if (it->getSha512() == sha512StringRemote) { APPL_INFO("File already registered at ");// << it.m_creationData); // simply send the Id of the file // TODO : Check right of this file ... return it->getUniqueId(); } } } etk::String tmpFileName = g_basePath + "tmpImport_" + etk::toString(importId); etk::String sha512String = zeus::storeInFile(_dataFile, tmpFileName); futType.wait(); futName.wait(); // move the file at the good position: APPL_DEBUG("move temporay file in : " << g_basePath << sha512String); if (etk::FSNodeGetSize(tmpFileName) == 0) { APPL_ERROR("try to store an empty file"); throw etk::exception::RuntimeError("file size == 0"); } if (zeus::getExtention(futType.get()) != "") { ethread::UniqueLock lock(g_mutex); etk::FSNodeMove(tmpFileName, g_basePath + sha512String + "." + zeus::getExtention(futType.get())); ememory::SharedPtr property = ememory::makeShared(id, sha512String + "." + zeus::getExtention(futType.get()), g_basePath); property->setMetadata("sha512", sha512String); property->setMetadata("mime-type", futType.get()); property->setCallbackMetadataChange(&metadataChange); m_listFile.pushBack(property); g_needToStore = true; } else { ethread::UniqueLock lock(g_mutex); etk::FSNodeMove(tmpFileName, g_basePath + sha512String); ememory::SharedPtr property = ememory::makeShared(id, sha512String, g_basePath); property->setMetadata("sha512", sha512String); property->setCallbackMetadataChange(&metadataChange); m_listFile.pushBack(property); g_needToStore = true; } APPL_DEBUG(" filename : " << sha512String); return id; } void remove(uint32_t _mediaId) override { ethread::UniqueLock lock(g_mutex); // TODO : Check right ... //Check if the file exist: bool find = false; ememory::SharedPtr property; for (auto it = m_listFile.begin(); it != m_listFile.end(); /* No increment */) { if (*it == null) { it = m_listFile.erase(it); continue; } if ((*it)->getUniqueId() == _mediaId) { property = *it; it = m_listFile.erase(it); find = true; } else { ++it; } g_needToStore = true; } if (find == false) { throw etk::exception::InvalidArgument("Wrong file name ..."); } // Real Remove definitly the file // TODO : Set it in a trash ... For a while ... if (property->erase() == false) { throw etk::exception::RuntimeError("Can not remove file ..."); } } etk::Vector> interpreteSQLRequest(const etk::String& _sqlLikeRequest) { etk::Vector> out; if (_sqlLikeRequest != "*") { etk::Vector listAnd = _sqlLikeRequest.split("AND"); APPL_INFO("Find list AND : "); for (auto &it : listAnd) { it = removeSpaceOutQuote(it); etk::Vector elements = splitAction(it); if (elements.size() != 3) { APPL_ERROR("element : '" + it + "' have wrong spliting " << elements); throw etk::exception::InvalidArgument("element : \"" + it + "\" have wrong spliting"); } if ( elements[1] != "==" && elements[1] != "!=" && elements[1] != ">=" && elements[1] != "<=" && elements[1] != ">" && elements[1] != "<") { throw etk::exception::InvalidArgument("action invalid : '" + elements[1] + "' only availlable : [==,!=,<=,>=,<,>]"); } APPL_INFO(" - '" << elements[0] << "' action='" << elements[1] << "' with='" << elements[2] << "'"); out.pushBack(elements); } } return out; } bool isValid(const etk::Vector>& _listElement, const etk::Map& _metadata) { for (auto &itCheck : _listElement) { // find matadataValue: auto itM = _metadata.find(itCheck[0]); if (itM == _metadata.end()) { // not find key ==> no check to do ... return false; } if (itCheck[1] == "==") { if (itM->second != itCheck[2]) { return false; } } else if (itCheck[1] == "!=") { if (itM->second == itCheck[2]) { return false; } } else if (itCheck[1] == "<=") { if (itM->second < itCheck[2]) { return false; } } else if (itCheck[1] == ">=") { if (itM->second > itCheck[2]) { return false; } } else if (itCheck[1] == "<") { if (itM->second <= itCheck[2]) { return false; } } else if (itCheck[1] == ">") { if (itM->second >= itCheck[2]) { return false; } } } return true; } etk::String mapToString(const etk::Map& _metadata) { etk::String out = "{"; for (auto &it : _metadata) { out += it.first + ":" + it.second + ","; } out += "}"; return out; } etk::Vector getSQL(etk::String _sqlLikeRequest) override { etk::Vector out; if (_sqlLikeRequest == "") { throw etk::exception::InvalidArgument("empty request"); } APPL_DEBUG("check : " << _sqlLikeRequest); etk::Vector> listAndParsed = interpreteSQLRequest(_sqlLikeRequest); ethread::UniqueLock lock(g_mutex); for (auto &it : m_listFile) { if (it == null) { continue; } APPL_DEBUG(" [" << it->getUniqueId() << " list=" << mapToString(it->getMetadataDirect())); bool isCorrectElement = isValid(listAndParsed, it->getMetadataDirect()); if (isCorrectElement == true) { APPL_DEBUG(" select"); out.pushBack(it->getUniqueId()); } } return out; } etk::Vector getMetadataValuesWhere(etk::String _keyName, etk::String _sqlLikeRequest) override { etk::Vector out; if (_sqlLikeRequest == "") { throw etk::exception::InvalidArgument("empty request"); } etk::Vector> listAndParsed = interpreteSQLRequest(_sqlLikeRequest); ethread::UniqueLock lock(g_mutex); for (auto &it : m_listFile) { if (it == null) { continue; } bool isCorrectElement = isValid(listAndParsed, it->getMetadataDirect()); if (isCorrectElement == false) { continue; } auto it2 = it->getMetadataDirect().find(_keyName); if (it2 == it->getMetadataDirect().end()) { continue; } etk::String value = it2->second; isCorrectElement = false; for (auto &it2: out) { if (it2 == value) { isCorrectElement = true; break; } } if (isCorrectElement == false) { out.pushBack(value); } } return out; } ememory::SharedPtr internalGetCover(const etk::String& _baseName, const etk::String& _mediaString, uint32_t _maxSize) { if (etk::FSNodeExist(_baseName + _mediaString + ".jpg") == true) { return zeus::File::create(_baseName + _mediaString + ".jpg"); } if (etk::FSNodeExist(_baseName + _mediaString + ".png") == true) { return zeus::File::create(_baseName + _mediaString + ".png"); } throw etk::exception::RuntimeError("No cover availlable"); } void internalSetCover(const etk::String& _baseName, zeus::ActionNotification& _notifs, zeus::ProxyFile _cover, etk::String _mediaString) { uint64_t importId = 0; { ethread::UniqueLock lock(g_mutex); importId = createUniqueImportID(); } auto futType = _cover.getMineType(); etk::String tmpFileName = g_basePath + "tmpImport_" + etk::toString(importId); etk::String sha512String = zeus::storeInFile(_cover, tmpFileName); futType.wait(); if (etk::FSNodeGetSize(tmpFileName) == 0) { APPL_ERROR("try to store an empty file"); throw etk::exception::RuntimeError("file size == 0"); } if (etk::FSNodeGetSize(tmpFileName) > 1024*1024) { APPL_ERROR("try to store a Bigger file"); throw etk::exception::RuntimeError("file size > 1Mo"); } if (futType.get() == "image/png") { ethread::UniqueLock lock(g_mutex); etk::FSNodeRemove(_baseName + _mediaString + ".jpg"); etk::FSNodeMove(tmpFileName, _baseName + _mediaString + ".png"); } else if (futType.get() == "image/jpeg") { ethread::UniqueLock lock(g_mutex); etk::FSNodeRemove(_baseName + _mediaString + ".png"); etk::FSNodeMove(tmpFileName, _baseName + _mediaString + ".jpg"); } else { APPL_ERROR("try to store a file with the wrong format"); throw etk::exception::RuntimeError("wrong foramt :" + futType.get() + " support only image/jpeg, image/png"); } } ememory::SharedPtr getCover(uint32_t _mediaId, uint32_t _maxSize) override { return internalGetCover(g_basePathCover, etk::toString(_mediaId), _maxSize); } void setCover(zeus::ActionNotification& _notifs, zeus::ProxyFile _cover, uint32_t _mediaId) override { return internalSetCover(g_basePathCover, _notifs, _cover, etk::toString(_mediaId)); } ememory::SharedPtr getGroupCover(etk::String _groupName, uint32_t _maxSize) override { return internalGetCover(g_basePathCoverGroup, _groupName, _maxSize); } void setGroupCover(zeus::ActionNotification& _notifs, zeus::ProxyFile _cover, etk::String _groupName) override { return internalSetCover(g_basePathCoverGroup, _notifs, _cover, _groupName); } }; } static void store_db() { APPL_ERROR("Store database [START]"); ejson::Document database; ejson::Array listFilesArray; database.add("list-files", listFilesArray); for (auto &it : m_listFile) { if (it != null) { listFilesArray.add(it->getJson()); } } bool retGenerate = database.storeSafe(g_basePath + g_baseDBName); APPL_ERROR("Store database [STOP] : " << (g_basePath + g_baseDBName) << " ret = " << retGenerate); g_needToStore = false; } static void load_db() { ejson::Document database; bool ret = database.load(g_basePath + g_baseDBName); if (ret == false) { APPL_WARNING(" ==> LOAD error"); } ejson::Array listFilesArray = database["list-files"].toArray(); for (const auto itArray: listFilesArray) { auto property = ememory::makeShared(g_basePath, itArray.toObject()); if (property == null) { APPL_ERROR("can not allocate element ..."); continue; } if (m_lastMaxId < property->getUniqueId()) { m_lastMaxId = property->getUniqueId()+1; } if (property->getFileName() == "") { APPL_ERROR("Can not access on the file : ... No name "); } else { property->setCallbackMetadataChange(&metadataChange); m_listFile.pushBack(property); } } g_needToStore = false; } ETK_EXPORT_API bool SERVICE_IO_init(int _argc, const char *_argv[], etk::String _basePath) { g_basePath = _basePath; g_basePathCover = _basePath + "/AAAASDGDFGQN4352SCVdfgBSXDFGFCVQDSGFQSfd_cover/"; g_basePathCoverGroup = _basePath + "/AAAASDGDFGQN4352SCVdfgBSXDFGFCVQDSGFQSfd_cover_group/"; ethread::UniqueLock lock(g_mutex); APPL_WARNING("Load USER: " << g_basePath); load_db(); APPL_WARNING("new USER: [STOP]"); return true; } ETK_EXPORT_API bool SERVICE_IO_uninit() { ethread::UniqueLock lock(g_mutex); store_db(); APPL_WARNING("delete USER [STOP]"); return true; } ETK_EXPORT_API void SERVICE_IO_peridic_call() { if (g_needToStore == false) { return; } // try lock mutex: if (g_mutex.tryLock() == false) { return; } store_db(); g_mutex.unLock(); } ZEUS_SERVICE_VIDEO_DECLARE(appl::VideoService);