blob: be7d3c49017a6c0a3796eba16b237271f5360444 [file] [log] [blame]
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/**
* Copyright (c) 2013-2017, Regents of the University of California.
*
* This file is part of ChronoShare, a decentralized file sharing application over NDN.
*
* ChronoShare is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Foundation, either
* version 3 of the License, or (at your option) any later version.
*
* ChronoShare is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received copies of the GNU General Public License along with
* ChronoShare, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
*
* See AUTHORS.md for complete list of ChronoShare authors and contributors.
*/
#include "dispatcher.hpp"
#include "ccnx-discovery.hpp"
#include "fetch-task-db.hpp"
#include "logging.hpp"
#include <boost/lexical_cast.hpp>
#include <boost/make_shared.hpp>
using namespace Ndnx;
using namespace std;
using namespace boost;
_LOG_INIT(Dispatcher);
static const string CHRONOSHARE_APP = "chronoshare";
static const string BROADCAST_DOMAIN = "/ndn/broadcast";
static const int CONTENT_FRESHNESS = 1800; // seconds
const static double DEFAULT_SYNC_INTEREST_INTERVAL = 10.0; // seconds;
Dispatcher::Dispatcher(const std::string& localUserName, const std::string& sharedFolder,
const filesystem::path& rootDir, Ccnx::CcnxWrapperPtr ccnx,
bool enablePrefixDiscovery)
: m_ccnx(ccnx)
, m_core(NULL)
, m_rootDir(rootDir)
, m_executor(
1) // creates problems with file assembly. need to ensure somehow that FinishExectute is called after all Segment_Execute finished
, m_objectManager(ccnx, rootDir, CHRONOSHARE_APP)
, m_localUserName(localUserName)
, m_sharedFolder(sharedFolder)
, m_server(NULL)
, m_enablePrefixDiscovery(enablePrefixDiscovery)
{
m_syncLog = make_shared<SyncLog>(m_rootDir, localUserName);
m_actionLog =
make_shared<ActionLog>(m_ccnx, m_rootDir, m_syncLog, sharedFolder, CHRONOSHARE_APP,
// bind (&Dispatcher::Did_ActionLog_ActionApply_AddOrModify, this, _1, _2, _3, _4, _5, _6, _7),
ActionLog::OnFileAddedOrChangedCallback(), // don't really need this callback
bind(&Dispatcher::Did_ActionLog_ActionApply_Delete, this, _1));
m_fileState = m_actionLog->GetFileState();
Name syncPrefix = Name(BROADCAST_DOMAIN)(CHRONOSHARE_APP)(sharedFolder);
// m_server needs a different ccnx face
m_server = new ContentServer(make_shared<CcnxWrapper>(), m_actionLog, rootDir, m_localUserName,
m_sharedFolder, CHRONOSHARE_APP, CONTENT_FRESHNESS);
m_server->registerPrefix(Name("/"));
m_server->registerPrefix(Name(BROADCAST_DOMAIN));
m_stateServer =
new StateServer(make_shared<CcnxWrapper>(), m_actionLog, rootDir, m_localUserName,
m_sharedFolder, CHRONOSHARE_APP, m_objectManager, CONTENT_FRESHNESS);
// no need to register, right now only listening on localhost prefix
m_core = new SyncCore(m_syncLog, localUserName, Name("/"), syncPrefix,
bind(&Dispatcher::Did_SyncLog_StateChange, this, _1), ccnx,
DEFAULT_SYNC_INTEREST_INTERVAL);
FetchTaskDbPtr actionTaskDb = make_shared<FetchTaskDb>(m_rootDir, "action");
m_actionFetcher =
make_shared<FetchManager>(m_ccnx, bind(&SyncLog::LookupLocator, &*m_syncLog, _1),
Name(BROADCAST_DOMAIN), // no appname suffix now
3,
bind(&Dispatcher::Did_FetchManager_ActionFetch, this, _1, _2, _3, _4),
FetchManager::FinishCallback(), actionTaskDb);
FetchTaskDbPtr fileTaskDb = make_shared<FetchTaskDb>(m_rootDir, "file");
m_fileFetcher =
make_shared<FetchManager>(m_ccnx, bind(&SyncLog::LookupLocator, &*m_syncLog, _1),
Name(BROADCAST_DOMAIN), // no appname suffix now
3, bind(&Dispatcher::Did_FetchManager_FileSegmentFetch, this, _1, _2,
_3, _4),
bind(&Dispatcher::Did_FetchManager_FileFetchComplete, this, _1, _2),
fileTaskDb);
if (m_enablePrefixDiscovery) {
_LOG_DEBUG("registering prefix discovery in Dispatcher");
string tag = "dispatcher" + m_localUserName.toString();
Ccnx::CcnxDiscovery::registerCallback(
TaggedFunction(bind(&Dispatcher::Did_LocalPrefix_Updated, this, _1), tag));
}
m_executor.start();
}
Dispatcher::~Dispatcher()
{
_LOG_DEBUG("Enter destructor of dispatcher");
m_executor.shutdown();
// _LOG_DEBUG (">>");
if (m_enablePrefixDiscovery) {
_LOG_DEBUG("deregistering prefix discovery in Dispatcher");
string tag = "dispatcher" + m_localUserName.toString();
Ccnx::CcnxDiscovery::deregisterCallback(
TaggedFunction(bind(&Dispatcher::Did_LocalPrefix_Updated, this, _1), tag));
}
if (m_core != NULL) {
delete m_core;
m_core = NULL;
}
if (m_server != NULL) {
delete m_server;
m_server = NULL;
}
if (m_stateServer != NULL) {
delete m_stateServer;
m_stateServer = NULL;
}
}
void
Dispatcher::Did_LocalPrefix_Updated(const Ccnx::Name& forwardingHint)
{
Name effectiveForwardingHint;
if (m_localUserName.size() >= forwardingHint.size() &&
m_localUserName.getPartialName(0, forwardingHint.size()) == forwardingHint) {
effectiveForwardingHint = Name("/"); // "directly" accesible
}
else {
effectiveForwardingHint = forwardingHint;
}
Name oldLocalPrefix = m_syncLog->LookupLocalLocator();
if (oldLocalPrefix == effectiveForwardingHint) {
_LOG_DEBUG(
"Got notification about prefix change from " << oldLocalPrefix << " to: " << forwardingHint
<< ", but effective prefix didn't change");
return;
}
if (effectiveForwardingHint == Name("/") || effectiveForwardingHint == Name(BROADCAST_DOMAIN)) {
_LOG_DEBUG("Basic effective prefix [" << effectiveForwardingHint
<< "]. Updating local prefix, but don't reregister");
m_syncLog->UpdateLocalLocator(effectiveForwardingHint);
return;
}
_LOG_DEBUG("LocalPrefix changed from: " << oldLocalPrefix << " to: " << effectiveForwardingHint);
m_server->registerPrefix(effectiveForwardingHint);
m_syncLog->UpdateLocalLocator(effectiveForwardingHint);
if (oldLocalPrefix == Name("/") || oldLocalPrefix == Name(BROADCAST_DOMAIN)) {
_LOG_DEBUG("Don't deregister basic prefix: " << oldLocalPrefix);
return;
}
m_server->deregisterPrefix(oldLocalPrefix);
}
// moved to state-server
// void
// Dispatcher::Restore_LocalFile (FileItemPtr file)
// {
// m_executor.execute (bind (&Dispatcher::Restore_LocalFile_Execute, this, file));
// }
/////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////
void
Dispatcher::Did_LocalFile_AddOrModify(const filesystem::path& relativeFilePath)
{
m_executor.execute(bind(&Dispatcher::Did_LocalFile_AddOrModify_Execute, this, relativeFilePath));
}
void
Dispatcher::Did_LocalFile_AddOrModify_Execute(filesystem::path relativeFilePath)
{
_LOG_DEBUG(m_localUserName << " calls LocalFile_AddOrModify_Execute");
filesystem::path absolutePath = m_rootDir / relativeFilePath;
if (!filesystem::exists(absolutePath)) {
//BOOST_THROW_EXCEPTION (Error::Dispatcher() << error_info_str("Update non exist file: " + absolutePath.string() ));
_LOG_DEBUG("Update non exist file: " << absolutePath.string());
return;
}
FileItemPtr currentFile = m_fileState->LookupFile(relativeFilePath.generic_string());
if (currentFile &&
*Hash::FromFileContent(absolutePath) ==
Hash(currentFile->file_hash().c_str(), currentFile->file_hash().size())
// The following two are commented out to prevent front end from reporting intermediate files
// should enable it if there is other way to prevent this
// && last_write_time (absolutePath) == currentFile->mtime ()
// && status (absolutePath).permissions () == static_cast<filesystem::perms> (currentFile->mode ())
) {
_LOG_ERROR("Got notification about the same file [" << relativeFilePath << "]");
return;
}
if (currentFile && !currentFile->is_complete()) {
_LOG_ERROR("Got notification about incomplete file [" << relativeFilePath << "]");
return;
}
int seg_num;
HashPtr hash;
tie(hash, seg_num) = m_objectManager.localFileToObjects(absolutePath, m_localUserName);
try {
m_actionLog->AddLocalActionUpdate(relativeFilePath.generic_string(),
*hash,
last_write_time(absolutePath),
#if BOOST_VERSION >= 104900
status(absolutePath).permissions(),
#else
0,
#endif
seg_num);
// notify SyncCore to propagate the change
m_core->localStateChangedDelayed();
}
catch (filesystem::filesystem_error& error) {
_LOG_ERROR("File operations failed on [" << relativeFilePath << "] (ignoring)");
}
}
void
Dispatcher::Did_LocalFile_Delete(const filesystem::path& relativeFilePath)
{
m_executor.execute(bind(&Dispatcher::Did_LocalFile_Delete_Execute, this, relativeFilePath));
}
void
Dispatcher::Did_LocalFile_Delete_Execute(filesystem::path relativeFilePath)
{
filesystem::path absolutePath = m_rootDir / relativeFilePath;
if (filesystem::exists(absolutePath)) {
//BOOST_THROW_EXCEPTION (Error::Dispatcher() << error_info_str("Delete notification but file exists: " + absolutePath.string() ));
_LOG_ERROR("DELETE command, but file still exists: " << absolutePath.string());
return;
}
m_actionLog->AddLocalActionDelete(relativeFilePath.generic_string());
// notify SyncCore to propagate the change
m_core->localStateChangedDelayed();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Callbacks:
*
* - from SyncLog: when state changes -> to fetch missing actions
*
* - from FetchManager/Actions: when action is fetched -> to request a file, specified by the action
* -> to add action to the action log
*
* - from ActionLog/Delete: when action applied (file state changed, file deleted) -> to delete local file
*
* - from ActionLog/AddOrUpdate: when action applied (file state changes, file added or modified) -> do nothing?
*
* - from FetchManager/Files: when file segment is retrieved -> save it in ObjectDb
* when file fetch is completed -> if file belongs to FileState, then assemble it to filesystem. Don't do anything otherwise
*/
void
Dispatcher::Did_SyncLog_StateChange(SyncStateMsgPtr stateMsg)
{
m_executor.execute(bind(&Dispatcher::Did_SyncLog_StateChange_Execute, this, stateMsg));
}
void
Dispatcher::Did_SyncLog_StateChange_Execute(SyncStateMsgPtr stateMsg)
{
int size = stateMsg->state_size();
int index = 0;
// iterate and fetch the actions
for (; index < size; index++) {
SyncState state = stateMsg->state(index);
if (state.has_old_seq() && state.has_seq()) {
uint64_t oldSeq = state.old_seq();
uint64_t newSeq = state.seq();
Name userName(reinterpret_cast<const unsigned char*>(state.name().c_str()),
state.name().size());
// fetch actions with oldSeq + 1 to newSeq (inclusive)
Name actionNameBase = Name("/")(userName)(CHRONOSHARE_APP)("action")(m_sharedFolder);
m_actionFetcher->Enqueue(userName, actionNameBase, std::max<uint64_t>(oldSeq + 1, 1), newSeq,
FetchManager::PRIORITY_HIGH);
}
}
}
void
Dispatcher::Did_FetchManager_ActionFetch(const Ccnx::Name& deviceName,
const Ccnx::Name& actionBaseName, uint32_t seqno,
Ccnx::PcoPtr actionPco)
{
/// @todo Errors and exception checking
_LOG_DEBUG("Received action deviceName: " << deviceName << ", actionBaseName: " << actionBaseName
<< ", seqno: "
<< seqno);
ActionItemPtr action = m_actionLog->AddRemoteAction(deviceName, seqno, actionPco);
if (!action) {
_LOG_ERROR("AddRemoteAction did not insert action, ignoring");
return;
}
// trigger may invoke Did_ActionLog_ActionApply_Delete or Did_ActionLog_ActionApply_AddOrModify callbacks
if (action->action() == ActionItem::UPDATE) {
Hash hash(action->file_hash().c_str(), action->file_hash().size());
Name fileNameBase =
Name("/")(deviceName)(CHRONOSHARE_APP)("file")(hash.GetHash(), hash.GetHashBytes());
string hashStr = lexical_cast<string>(hash);
if (ObjectDb::DoesExist(m_rootDir / ".chronoshare", deviceName, hashStr)) {
_LOG_DEBUG(
"File already exists in the database. No need to refetch, just directly applying the action");
Did_FetchManager_FileFetchComplete(deviceName, fileNameBase);
}
else {
if (m_objectDbMap.find(hash) == m_objectDbMap.end()) {
_LOG_DEBUG("create ObjectDb for " << hash);
m_objectDbMap[hash] = make_shared<ObjectDb>(m_rootDir / ".chronoshare", hashStr);
}
m_fileFetcher->Enqueue(deviceName, fileNameBase, 0, action->seg_num() - 1,
FetchManager::PRIORITY_NORMAL);
}
}
// if necessary (when version number is the highest) delete will be applied through the trigger in m_actionLog->AddRemoteAction call
}
void
Dispatcher::Did_ActionLog_ActionApply_Delete(const std::string& filename)
{
m_executor.execute(bind(&Dispatcher::Did_ActionLog_ActionApply_Delete_Execute, this, filename));
}
void
Dispatcher::Did_ActionLog_ActionApply_Delete_Execute(std::string filename)
{
_LOG_DEBUG("Action to delete " << filename);
filesystem::path absolutePath = m_rootDir / filename;
try {
if (filesystem::exists(absolutePath)) {
// need some protection from local detection of removal
remove(absolutePath);
// hack to remove empty parent dirs
filesystem::path parentPath = absolutePath.parent_path();
while (parentPath > m_rootDir) {
if (filesystem::is_empty(parentPath)) {
filesystem::remove(parentPath);
parentPath = parentPath.parent_path();
}
else {
break;
}
}
}
// don't exist
}
catch (filesystem::filesystem_error& error) {
_LOG_ERROR("File operations failed when removing [" << absolutePath << "] (ignoring)");
}
}
void
Dispatcher::Did_FetchManager_FileSegmentFetch(const Ccnx::Name& deviceName,
const Ccnx::Name& fileSegmentBaseName,
uint32_t segment, Ccnx::PcoPtr fileSegmentPco)
{
m_executor.execute(bind(&Dispatcher::Did_FetchManager_FileSegmentFetch_Execute, this, deviceName,
fileSegmentBaseName, segment, fileSegmentPco));
}
void
Dispatcher::Did_FetchManager_FileSegmentFetch_Execute(Ccnx::Name deviceName,
Ccnx::Name fileSegmentBaseName,
uint32_t segment, Ccnx::PcoPtr fileSegmentPco)
{
// fileSegmentBaseName: /<device_name>/<appname>/file/<hash>
const Bytes& hashBytes = fileSegmentBaseName.getCompFromBack(0);
Hash hash(head(hashBytes), hashBytes.size());
_LOG_DEBUG("Received segment deviceName: " << deviceName << ", segmentBaseName: " << fileSegmentBaseName
<< ", segment: "
<< segment);
// _LOG_DEBUG ("Looking up objectdb for " << hash);
map<Hash, ObjectDbPtr>::iterator db = m_objectDbMap.find(hash);
if (db != m_objectDbMap.end()) {
db->second->saveContentObject(deviceName, segment, fileSegmentPco->buf());
}
else {
_LOG_ERROR("no db available for this content object: " << fileSegmentBaseName << ", size: "
<< fileSegmentPco->buf().size());
}
// ObjectDb objectDb (m_rootDir / ".chronoshare", lexical_cast<string> (hash));
// objectDb.saveContentObject(deviceName, segment, fileSegmentPco->buf ());
}
void
Dispatcher::Did_FetchManager_FileFetchComplete(const Ccnx::Name& deviceName,
const Ccnx::Name& fileBaseName)
{
m_executor.execute(
bind(&Dispatcher::Did_FetchManager_FileFetchComplete_Execute, this, deviceName, fileBaseName));
}
void
Dispatcher::Did_FetchManager_FileFetchComplete_Execute(Ccnx::Name deviceName, Ccnx::Name fileBaseName)
{
// fileBaseName: /<device_name>/<appname>/file/<hash>
_LOG_DEBUG("Finished fetching " << deviceName << ", fileBaseName: " << fileBaseName);
const Bytes& hashBytes = fileBaseName.getCompFromBack(0);
Hash hash(head(hashBytes), hashBytes.size());
_LOG_DEBUG("Extracted hash: " << hash.shortHash());
if (m_objectDbMap.find(hash) != m_objectDbMap.end()) {
// remove the db handle
m_objectDbMap.erase(hash); // to commit write
}
else {
_LOG_ERROR("no db available for this file: " << hash);
}
FileItemsPtr filesToAssemble = m_fileState->LookupFilesForHash(hash);
for (FileItems::iterator file = filesToAssemble->begin(); file != filesToAssemble->end(); file++) {
boost::filesystem::path filePath = m_rootDir / file->filename();
try {
if (filesystem::exists(filePath) && filesystem::last_write_time(filePath) == file->mtime() &&
#if BOOST_VERSION >= 104900
filesystem::status(filePath).permissions() == static_cast<filesystem::perms>(file->mode()) &&
#endif
*Hash::FromFileContent(filePath) == hash) {
_LOG_DEBUG("Asking to assemble a file, but file already exists on a filesystem");
continue;
}
}
catch (filesystem::filesystem_error& error) {
_LOG_ERROR("File operations failed on [" << filePath << "] (ignoring)");
}
if (ObjectDb::DoesExist(m_rootDir / ".chronoshare", deviceName,
boost::lexical_cast<string>(hash))) {
bool ok = m_objectManager.objectsToLocalFile(deviceName, hash, filePath);
if (ok) {
last_write_time(filePath, file->mtime());
#if BOOST_VERSION >= 104900
permissions(filePath, static_cast<filesystem::perms>(file->mode()));
#endif
m_fileState->SetFileComplete(file->filename());
}
else {
_LOG_ERROR("Notified about complete fetch, but file cannot be restored from the database: ["
<< filePath
<< "]");
}
}
else {
_LOG_ERROR(filePath << " supposed to have all segments, but not");
// should abort for debugging
}
}
}
// moved to state-server
// void
// Dispatcher::Restore_LocalFile_Execute (FileItemPtr file)
// {
// _LOG_DEBUG ("Got request to restore local file [" << file->filename () << "]");
// // the rest will gracefully fail if object-db is missing or incomplete
// boost::filesystem::path filePath = m_rootDir / file->filename ();
// Name deviceName (file->device_name ().c_str (), file->device_name ().size ());
// Hash hash (file->file_hash ().c_str (), file->file_hash ().size ());
// try
// {
// if (filesystem::exists (filePath) &&
// filesystem::last_write_time (filePath) == file->mtime () &&
// filesystem::status (filePath).permissions () == static_cast<filesystem::perms> (file->mode ()) &&
// *Hash::FromFileContent (filePath) == hash)
// {
// _LOG_DEBUG ("Asking to assemble a file, but file already exists on a filesystem");
// return;
// }
// }
// catch (filesystem::filesystem_error &error)
// {
// _LOG_ERROR ("File operations failed on [" << filePath << "] (ignoring)");
// }
// m_objectManager.objectsToLocalFile (deviceName, hash, filePath);
// last_write_time (filePath, file->mtime ());
// permissions (filePath, static_cast<filesystem::perms> (file->mode ()));
// }