<commit_before>/*
 * SessionSourceDatabaseSupervisor.cpp
 *
 * Copyright (C) 2009-12 by RStudio, Inc.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#include "SessionSourceDatabaseSupervisor.hpp"

#include <vector>

#include <boost/foreach.hpp>
#include <boost/scope_exit.hpp>
#include <boost/algorithm/string/predicate.hpp>

#include <core/Error.hpp>
#include <core/FilePath.hpp>
#include <core/FileSerializer.hpp>
#include <core/FileLock.hpp>
#include <core/FileUtils.hpp>
#include <core/BoostErrors.hpp>

#include <core/system/System.hpp>

#include <session/SessionModuleContext.hpp>
#include "session/SessionSourceDatabase.hpp"

using namespace rstudio::core;

namespace rstudio {
namespace session {
namespace source_database {
namespace supervisor {

namespace {

const char * const kSessionDirPrefix = "s-";

FilePath oldSourceDatabaseRoot()
{
   return
      module_context::scopedScratchPath().complete("source_database");
}

FilePath sourceDatabaseRoot()
{
   return module_context::scopedScratchPath().complete("sdb");
}

FilePath mostRecentTitledDir()
{
   return module_context::scopedScratchPath().complete("sdb/mt");
}

FilePath mostRecentUntitledDir()
{
   return module_context::scopedScratchPath().complete("sdb/mu");
}

FilePath persistentTitledDir(bool multiSession = true)
{
   if (multiSession && !module_context::activeSession().empty())
   {
      std::string id = module_context::activeSession().id();
      return sourceDatabaseRoot().complete("per/t/" + id);
   }
   else
   {
      return sourceDatabaseRoot().complete("per/t");
   }
}

FilePath oldPersistentTitledDir()
{
   FilePath oldPath = module_context::oldScopedScratchPath();
   if (oldPath.exists())
      return oldPath.complete("source_database_v2/persistent/titled");
   else
      return FilePath();
}

FilePath persistentUntitledDir(bool multiSession = true)
{
   if (multiSession && !module_context::activeSession().empty())
   {
      std::string id = module_context::activeSession().id();
      return sourceDatabaseRoot().complete("per/u/" + id);
   }
   else
   {
      return sourceDatabaseRoot().complete("per/u");
   }
}

FilePath oldPersistentUntitledDir()
{
   FilePath oldPath = module_context::oldScopedScratchPath();
   if (oldPath.exists())
      return oldPath.complete("source_database_v2/persistent/untitled");
   else
      return FilePath();
}

FilePath sessionLockFilePath(const FilePath& sessionDir)
{
   return sessionDir.complete("lock_file");
}

// session dir lock (lock is acquired within 'attachToSourceDatabase()')
boost::shared_ptr<FileLock> createSessionDirLock()
{
   return FileLock::createDefault();
}

boost::shared_ptr<FileLock>& sessionDirLock()
{
   static boost::shared_ptr<FileLock> instance = createSessionDirLock();
   return instance;
}

Error removeSessionDir(const FilePath& sessionDir)
{
   // first remove children
   std::vector<FilePath> children;
   Error error = sessionDir.children(&children);
   if (error)
      LOG_ERROR(error);
   BOOST_FOREACH(const FilePath& filePath, children)
   {
      error = filePath.remove();
      if (error)
         LOG_ERROR(error);
   }

   // then remove dir
   return sessionDir.remove();
}

FilePath generateSessionDirPath()
{
   return file_utils::uniqueFilePath(sourceDatabaseRoot(),
                                     kSessionDirPrefix);
}

bool isNotSessionDir(const FilePath& filePath)
{
   return !filePath.isDirectory() || !boost::algorithm::starts_with(
                                                filePath.filename(),
                                                kSessionDirPrefix);
}

Error enumerateSessionDirs(std::vector<FilePath>* pSessionDirs)
{
   // get the directories
   Error error = sourceDatabaseRoot().children(pSessionDirs);
   if (error)
      return error;

   // clean out non session dirs
   pSessionDirs->erase(std::remove_if(pSessionDirs->begin(),
                                      pSessionDirs->end(),
                                      isNotSessionDir),
                       pSessionDirs->end());

   // return success
   return Success();
}

void attemptToMoveSourceDbFiles(const FilePath& fromPath,
                                const FilePath& toPath)
{
   // enumerate the from path
   std::vector<FilePath> children;
   Error error = fromPath.children(&children);
   if (error)
      LOG_ERROR(error);

   // move the files
   BOOST_FOREACH(const FilePath& filePath, children)
   {
      // skip directories (directories can exist because multi-session
      // mode writes top level directories into the /sdb/t and /sdb/u
      // stores -- these directories correspond to the persistent docs
      // of particular long-running sessions)
      if (filePath.isDirectory())
         continue;

      // if the target path already exists then skip it and log
      // (we used to generate a new uniqueFilePath however this
      // caused the filename and id (stored in the source doc)
      // to get out of sync, making documents unclosable. The
      // chance of file with the same name already existing is
      // close to zero (collision probability of uniqueFilePath)
      // so it's no big deal to punt here.
      FilePath targetPath = toPath.complete(filePath.filename());
      if (targetPath.exists())
      {
         LOG_WARNING_MESSAGE("Skipping source db move from: " +
                             filePath.absolutePath() + " to " +
                             targetPath.absolutePath());

         Error error = filePath.remove();
         if (error)
            LOG_ERROR(error);

         continue;
      }

      Error error = filePath.move(targetPath);
      if (error)
         LOG_ERROR(error);
   }
}

// NOTE: the supervisor needs to return a session dir in order for the process
// to start. therefore, in the createSessionDir family of functions below
// once we successfully create and lock the session dir other errors (such
// as trying to move files into the session dir) are simply logged

Error createSessionDir(FilePath* pSessionDir)
{
   *pSessionDir = generateSessionDirPath();

   Error error = pSessionDir->ensureDirectory();
   if (error)
      return error;

   // attempt to acquire the lock. if we can't then we still continue
   // so we can support filesystems that don't have file locks.
   error = sessionDirLock()->acquire(sessionLockFilePath(*pSessionDir));
   if (error)
      LOG_ERROR(error);

   return Success();
}

Error createSessionDirFromOldSourceDatabase(FilePath* pSessionDir)
{
   // move properties (if any) into new source database root
   FilePath propsPath = oldSourceDatabaseRoot().complete("properties");
   if (propsPath.exists())
   {
      FilePath newPropsPath = sourceDatabaseRoot().complete("prop");
      Error error = propsPath.move(newPropsPath);
      if (error)
         LOG_ERROR(error);
   }

   // move the old source database into a new dir
   *pSessionDir = generateSessionDirPath();
   Error error = oldSourceDatabaseRoot().move(*pSessionDir);
   if (error)
      LOG_ERROR(error);

   // if that failed we might still need to call ensureDirectory
   error = pSessionDir->ensureDirectory();
   if (error)
      return error;

   // attempt to acquire the lock. if we can't then we still continue
   // so we can support filesystems that don't have file locks.
   error = sessionDirLock()->acquire(sessionLockFilePath(*pSessionDir));
   if (error)
      LOG_ERROR(error);

   return Success();
}

Error createSessionDirFromPersistent(FilePath* pSessionDir)
{
   // note whether we are in multi-session mode
   bool multiSession = !module_context::activeSession().empty();

   // create new session dir
   Error error = createSessionDir(pSessionDir);
   if (error)
      return error;

   // move persistent titled files. if we don't have any this is a
   // brand new instantiation of this project within this session
   // so we try to grab the MRU list
   if (persistentTitledDir().exists())
   {
      attemptToMoveSourceDbFiles(persistentTitledDir(), *pSessionDir);
   }
   // check for the most recent titled directory
   else if (multiSession && mostRecentTitledDir().exists())
   {
      attemptToMoveSourceDbFiles(mostRecentTitledDir(), *pSessionDir);
   }
   // last resort: if we are in multi-session mode see if there is a set of
   // mono-session source documents that we can migrate
   else if (multiSession && persistentTitledDir(false).exists())
   {
      attemptToMoveSourceDbFiles(persistentTitledDir(false), *pSessionDir);
   }

   // get legacy titled docs if they exist
   if (oldPersistentTitledDir().exists())
      attemptToMoveSourceDbFiles(oldPersistentTitledDir(), *pSessionDir);

   // move persistent untitled files
   if (persistentUntitledDir().exists())
   {
      attemptToMoveSourceDbFiles(persistentUntitledDir(), *pSessionDir);
   }
   // check for the most recent untitled directory
   else if (multiSession && mostRecentUntitledDir().exists())
   {
      attemptToMoveSourceDbFiles(mostRecentUntitledDir(), *pSessionDir);
   }
   // last resort: if we are in multi-session mode see if there is a set of
   // mono-session source documents that we can migrate
   else if (multiSession && persistentUntitledDir(false).exists())
   {
       attemptToMoveSourceDbFiles(persistentUntitledDir(false), *pSessionDir);
   }

   // get legacy untitled docs if they exist
   if (oldPersistentUntitledDir().exists())
      attemptToMoveSourceDbFiles(oldPersistentUntitledDir(), *pSessionDir);

   // return success
   return Success();
}

bool reclaimOrphanedSession(FilePath* pSessionDir)
{
   // check for existing sessions
   std::vector<FilePath> sessionDirs;
   Error error = enumerateSessionDirs(&sessionDirs);
   if (error)
   {
      LOG_ERROR(error);
      return false;
   }

   BOOST_FOREACH(const FilePath& sessionDir, sessionDirs)
   {
      FilePath lockFilePath = sessionLockFilePath(sessionDir);
      if (!sessionDirLock()->isLocked(lockFilePath))
      {
         Error error = sessionDirLock()->acquire(lockFilePath);
         if (!error)
         {
            *pSessionDir = sessionDir;
            return true;
         }
         else
         {
            LOG_ERROR(error);
         }
      }
   }

   return false;
}

Error removeAndRecreate(const FilePath& dir)
{
   // blow it away if it exists then recreate it
   Error error = dir.removeIfExists();
   if (error)
      LOG_ERROR(error);
   return dir.ensureDirectory();
}

} // anonymous namespace

// NOTE: we attempt to use file locks to coordinate between disperate
// processes all attempting to open a session in the same context (project
// or global). Locks are used to implement recovery of crashed sessions
// as follows: if there is an existing source-db directory on disk that
// is NOT locked then it's presumed to be an orphan (resulting from a crash)
// and we should initialize with this directory to "recover" it
//
// Unfortunately, some file systems (mostly remote network volumes) don't
// support file-locking. In these cases we need to gracefully fall back
// to some sane behavior. To implement this we use the following scheme:
//
//  (1) Always attempt to call FileLock::acquire to create an advisory lock
//      but if it fails we still allow the process to start up.
//
//  (2) When checking for "orphan" source-db directories we try to acquire
//      a lock on them -- for volumes that don't support locks this will
//      always be an error so we'll never be able to recover an orphan dir
//
// In some multi-machine cases it's actually possible for two proccesses
// to both get a lock on the same file. For this reason if we are running
// multi-machine (i.e. load balancing enabled) we don't attempt orphan
// recovery because it could result in one session stealing the other's
// source database out from under it.
//

Error attachToSourceDatabase(FilePath* pSessionDir)
{
   // check whether we will need to migrate -- ensure we do this only
   // one time so that if for whatever reason we can't migrate the
   // old source database we don't get stuck trying to do it every
   // time we start up
   bool needToMigrate = !sourceDatabaseRoot().exists() &&
                        oldSourceDatabaseRoot().exists();

   // ensure the root path exists
   Error error = sourceDatabaseRoot().ensureDirectory();
   if (error)
      return error;

   // attempt to migrate if necessary
   if (needToMigrate)
      return createSessionDirFromOldSourceDatabase(pSessionDir);

   // if there is an orphan (crash) then reclaim it.
   else if (reclaimOrphanedSession(pSessionDir))
      return Success();

   // attempt to create from persistent
   else
      return createSessionDirFromPersistent(pSessionDir);
}

// preserve documents for re-opening in a future session
Error saveMostRecentDocuments()
{
   // only do this for multi-session contexts
   if (!module_context::activeSession().empty())
   {
      // most recent docs is last one wins so we remove and recreate
      FilePath mostRecentDir = mostRecentTitledDir();
      Error error = removeAndRecreate(mostRecentDir);
      if (error)
         return error;

      // untitled are aggregated (so we never lose unsaved docs)
      // so we just ensure the directory exists)
      FilePath mostRecentDirUntitled = mostRecentUntitledDir();
      error = mostRecentDirUntitled.ensureDirectory();
      if (error)
         return error;

      // list all current source docs
      std::vector<boost::shared_ptr<SourceDocument> > sourceDocs;
      error = source_database::list(&sourceDocs);
      if (error)
         return error;

      // write the docs into the mru directories
      BOOST_FOREACH(boost::shared_ptr<SourceDocument> pDoc, sourceDocs)
      {
         FilePath targetDir = pDoc->isUntitled() ? mostRecentDirUntitled :
                                                   mostRecentDir;

         Error error = pDoc->writeToFile(targetDir.childPath(pDoc->id()));
         if (error)
            LOG_ERROR(error);
      }
   }

   return Success();
}

Error detachFromSourceDatabase()
{
   // list all current source docs
   std::vector<boost::shared_ptr<SourceDocument> > sourceDocs;
   Error error = source_database::list(&sourceDocs);
   if (error)
      return error;

   // get references to persistent subdirs
   FilePath titledDir = persistentTitledDir();
   FilePath untitledDir = persistentUntitledDir();

   // first blow away the existing persistent titled dir
   error = titledDir.removeIfExists();
   if (error)
      LOG_ERROR(error);

   // ensure both directories exist -- if they don't it is a fatal error
   error = titledDir.ensureDirectory();
   if (error)
      return error;
   error = untitledDir.ensureDirectory();
   if (error)
      return error;

   // now write the source database entries to the appropriate places
   BOOST_FOREACH(boost::shared_ptr<SourceDocument> pDoc, sourceDocs)
   {
      if (pDoc->isUntitled())
      {
         // compute the target path (manage uniqueness since this
         // directory is appended to from multiple processes who
         // could have created docs with the same id)
         FilePath targetPath = untitledDir.complete(pDoc->id());
         if (targetPath.exists())
            targetPath = file_utils::uniqueFilePath(untitledDir);

         error = pDoc->writeToFile(targetPath);
         if (error)
            LOG_ERROR(error);
      }
      else
      {
         error = pDoc->writeToFile(titledDir.complete(pDoc->id()));
         if (error)
            LOG_ERROR(error);
      }
   }

   // record session dir (parent of lock file)
   FilePath sessionDir = sessionDirLock()->lockFilePath().parent();

   // give up our lock
   error = sessionDirLock()->release();
   if (error)
      LOG_ERROR(error);

   // remove the session directory
   return removeSessionDir(sessionDir);
}

} // namespace supervisor
} // namespace source_database
} // namespace session
} // namespace rstudio

<commit_msg>use linkbased locks on server<commit_after>/*
 * SessionSourceDatabaseSupervisor.cpp
 *
 * Copyright (C) 2009-12 by RStudio, Inc.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#include "SessionSourceDatabaseSupervisor.hpp"

#include <vector>

#include <boost/foreach.hpp>
#include <boost/scope_exit.hpp>
#include <boost/algorithm/string/predicate.hpp>

#include <core/Error.hpp>
#include <core/FilePath.hpp>
#include <core/FileSerializer.hpp>
#include <core/FileLock.hpp>
#include <core/FileUtils.hpp>
#include <core/BoostErrors.hpp>

#include <core/system/System.hpp>

#include <session/SessionModuleContext.hpp>
#include "session/SessionSourceDatabase.hpp"

using namespace rstudio::core;

namespace rstudio {
namespace session {
namespace source_database {
namespace supervisor {

namespace {

const char * const kSessionDirPrefix = "s-";

FilePath oldSourceDatabaseRoot()
{
   return
      module_context::scopedScratchPath().complete("source_database");
}

FilePath sourceDatabaseRoot()
{
   return module_context::scopedScratchPath().complete("sdb");
}

FilePath mostRecentTitledDir()
{
   return module_context::scopedScratchPath().complete("sdb/mt");
}

FilePath mostRecentUntitledDir()
{
   return module_context::scopedScratchPath().complete("sdb/mu");
}

FilePath persistentTitledDir(bool multiSession = true)
{
   if (multiSession && !module_context::activeSession().empty())
   {
      std::string id = module_context::activeSession().id();
      return sourceDatabaseRoot().complete("per/t/" + id);
   }
   else
   {
      return sourceDatabaseRoot().complete("per/t");
   }
}

FilePath oldPersistentTitledDir()
{
   FilePath oldPath = module_context::oldScopedScratchPath();
   if (oldPath.exists())
      return oldPath.complete("source_database_v2/persistent/titled");
   else
      return FilePath();
}

FilePath persistentUntitledDir(bool multiSession = true)
{
   if (multiSession && !module_context::activeSession().empty())
   {
      std::string id = module_context::activeSession().id();
      return sourceDatabaseRoot().complete("per/u/" + id);
   }
   else
   {
      return sourceDatabaseRoot().complete("per/u");
   }
}

FilePath oldPersistentUntitledDir()
{
   FilePath oldPath = module_context::oldScopedScratchPath();
   if (oldPath.exists())
      return oldPath.complete("source_database_v2/persistent/untitled");
   else
      return FilePath();
}

FilePath sessionLockFilePath(const FilePath& sessionDir)
{
   return sessionDir.complete("lock_file");
}

// session dir lock (lock is acquired within 'attachToSourceDatabase()')
boost::shared_ptr<FileLock> createSessionDirLock()
{
#ifdef RSTUDIO_SERVER
   return FileLock::create(FileLock::LOCKTYPE_LINKBASED);
#else
   return FileLock::createDefault();
#endif
}

boost::shared_ptr<FileLock>& sessionDirLock()
{
   static boost::shared_ptr<FileLock> instance = createSessionDirLock();
   return instance;
}

Error removeSessionDir(const FilePath& sessionDir)
{
   // first remove children
   std::vector<FilePath> children;
   Error error = sessionDir.children(&children);
   if (error)
      LOG_ERROR(error);
   BOOST_FOREACH(const FilePath& filePath, children)
   {
      error = filePath.remove();
      if (error)
         LOG_ERROR(error);
   }

   // then remove dir
   return sessionDir.remove();
}

FilePath generateSessionDirPath()
{
   return file_utils::uniqueFilePath(sourceDatabaseRoot(),
                                     kSessionDirPrefix);
}

bool isNotSessionDir(const FilePath& filePath)
{
   return !filePath.isDirectory() || !boost::algorithm::starts_with(
                                                filePath.filename(),
                                                kSessionDirPrefix);
}

Error enumerateSessionDirs(std::vector<FilePath>* pSessionDirs)
{
   // get the directories
   Error error = sourceDatabaseRoot().children(pSessionDirs);
   if (error)
      return error;

   // clean out non session dirs
   pSessionDirs->erase(std::remove_if(pSessionDirs->begin(),
                                      pSessionDirs->end(),
                                      isNotSessionDir),
                       pSessionDirs->end());

   // return success
   return Success();
}

void attemptToMoveSourceDbFiles(const FilePath& fromPath,
                                const FilePath& toPath)
{
   // enumerate the from path
   std::vector<FilePath> children;
   Error error = fromPath.children(&children);
   if (error)
      LOG_ERROR(error);

   // move the files
   BOOST_FOREACH(const FilePath& filePath, children)
   {
      // skip directories (directories can exist because multi-session
      // mode writes top level directories into the /sdb/t and /sdb/u
      // stores -- these directories correspond to the persistent docs
      // of particular long-running sessions)
      if (filePath.isDirectory())
         continue;

      // if the target path already exists then skip it and log
      // (we used to generate a new uniqueFilePath however this
      // caused the filename and id (stored in the source doc)
      // to get out of sync, making documents unclosable. The
      // chance of file with the same name already existing is
      // close to zero (collision probability of uniqueFilePath)
      // so it's no big deal to punt here.
      FilePath targetPath = toPath.complete(filePath.filename());
      if (targetPath.exists())
      {
         LOG_WARNING_MESSAGE("Skipping source db move from: " +
                             filePath.absolutePath() + " to " +
                             targetPath.absolutePath());

         Error error = filePath.remove();
         if (error)
            LOG_ERROR(error);

         continue;
      }

      Error error = filePath.move(targetPath);
      if (error)
         LOG_ERROR(error);
   }
}

// NOTE: the supervisor needs to return a session dir in order for the process
// to start. therefore, in the createSessionDir family of functions below
// once we successfully create and lock the session dir other errors (such
// as trying to move files into the session dir) are simply logged

Error createSessionDir(FilePath* pSessionDir)
{
   *pSessionDir = generateSessionDirPath();

   Error error = pSessionDir->ensureDirectory();
   if (error)
      return error;

   // attempt to acquire the lock. if we can't then we still continue
   // so we can support filesystems that don't have file locks.
   error = sessionDirLock()->acquire(sessionLockFilePath(*pSessionDir));
   if (error)
      LOG_ERROR(error);

   return Success();
}

Error createSessionDirFromOldSourceDatabase(FilePath* pSessionDir)
{
   // move properties (if any) into new source database root
   FilePath propsPath = oldSourceDatabaseRoot().complete("properties");
   if (propsPath.exists())
   {
      FilePath newPropsPath = sourceDatabaseRoot().complete("prop");
      Error error = propsPath.move(newPropsPath);
      if (error)
         LOG_ERROR(error);
   }

   // move the old source database into a new dir
   *pSessionDir = generateSessionDirPath();
   Error error = oldSourceDatabaseRoot().move(*pSessionDir);
   if (error)
      LOG_ERROR(error);

   // if that failed we might still need to call ensureDirectory
   error = pSessionDir->ensureDirectory();
   if (error)
      return error;

   // attempt to acquire the lock. if we can't then we still continue
   // so we can support filesystems that don't have file locks.
   error = sessionDirLock()->acquire(sessionLockFilePath(*pSessionDir));
   if (error)
      LOG_ERROR(error);

   return Success();
}

Error createSessionDirFromPersistent(FilePath* pSessionDir)
{
   // note whether we are in multi-session mode
   bool multiSession = !module_context::activeSession().empty();

   // create new session dir
   Error error = createSessionDir(pSessionDir);
   if (error)
      return error;

   // move persistent titled files. if we don't have any this is a
   // brand new instantiation of this project within this session
   // so we try to grab the MRU list
   if (persistentTitledDir().exists())
   {
      attemptToMoveSourceDbFiles(persistentTitledDir(), *pSessionDir);
   }
   // check for the most recent titled directory
   else if (multiSession && mostRecentTitledDir().exists())
   {
      attemptToMoveSourceDbFiles(mostRecentTitledDir(), *pSessionDir);
   }
   // last resort: if we are in multi-session mode see if there is a set of
   // mono-session source documents that we can migrate
   else if (multiSession && persistentTitledDir(false).exists())
   {
      attemptToMoveSourceDbFiles(persistentTitledDir(false), *pSessionDir);
   }

   // get legacy titled docs if they exist
   if (oldPersistentTitledDir().exists())
      attemptToMoveSourceDbFiles(oldPersistentTitledDir(), *pSessionDir);

   // move persistent untitled files
   if (persistentUntitledDir().exists())
   {
      attemptToMoveSourceDbFiles(persistentUntitledDir(), *pSessionDir);
   }
   // check for the most recent untitled directory
   else if (multiSession && mostRecentUntitledDir().exists())
   {
      attemptToMoveSourceDbFiles(mostRecentUntitledDir(), *pSessionDir);
   }
   // last resort: if we are in multi-session mode see if there is a set of
   // mono-session source documents that we can migrate
   else if (multiSession && persistentUntitledDir(false).exists())
   {
       attemptToMoveSourceDbFiles(persistentUntitledDir(false), *pSessionDir);
   }

   // get legacy untitled docs if they exist
   if (oldPersistentUntitledDir().exists())
      attemptToMoveSourceDbFiles(oldPersistentUntitledDir(), *pSessionDir);

   // return success
   return Success();
}

bool reclaimOrphanedSession(FilePath* pSessionDir)
{
   // check for existing sessions
   std::vector<FilePath> sessionDirs;
   Error error = enumerateSessionDirs(&sessionDirs);
   if (error)
   {
      LOG_ERROR(error);
      return false;
   }

   BOOST_FOREACH(const FilePath& sessionDir, sessionDirs)
   {
      FilePath lockFilePath = sessionLockFilePath(sessionDir);
      if (!sessionDirLock()->isLocked(lockFilePath))
      {
         Error error = sessionDirLock()->acquire(lockFilePath);
         if (!error)
         {
            *pSessionDir = sessionDir;
            return true;
         }
         else
         {
            LOG_ERROR(error);
         }
      }
   }

   return false;
}

Error removeAndRecreate(const FilePath& dir)
{
   // blow it away if it exists then recreate it
   Error error = dir.removeIfExists();
   if (error)
      LOG_ERROR(error);
   return dir.ensureDirectory();
}

} // anonymous namespace

// NOTE: we attempt to use file locks to coordinate between disperate
// processes all attempting to open a session in the same context (project
// or global). Locks are used to implement recovery of crashed sessions
// as follows: if there is an existing source-db directory on disk that
// is NOT locked then it's presumed to be an orphan (resulting from a crash)
// and we should initialize with this directory to "recover" it
//
// Unfortunately, some file systems (mostly remote network volumes) don't
// support file-locking. In these cases we need to gracefully fall back
// to some sane behavior. To implement this we use the following scheme:
//
//  (1) Always attempt to call FileLock::acquire to create an advisory lock
//      but if it fails we still allow the process to start up.
//
//  (2) When checking for "orphan" source-db directories we try to acquire
//      a lock on them -- for volumes that don't support locks this will
//      always be an error so we'll never be able to recover an orphan dir
//
// In some multi-machine cases it's actually possible for two proccesses
// to both get a lock on the same file. For this reason if we are running
// multi-machine (i.e. load balancing enabled) we don't attempt orphan
// recovery because it could result in one session stealing the other's
// source database out from under it.
//

Error attachToSourceDatabase(FilePath* pSessionDir)
{
   // check whether we will need to migrate -- ensure we do this only
   // one time so that if for whatever reason we can't migrate the
   // old source database we don't get stuck trying to do it every
   // time we start up
   bool needToMigrate = !sourceDatabaseRoot().exists() &&
                        oldSourceDatabaseRoot().exists();

   // ensure the root path exists
   Error error = sourceDatabaseRoot().ensureDirectory();
   if (error)
      return error;

   // attempt to migrate if necessary
   if (needToMigrate)
      return createSessionDirFromOldSourceDatabase(pSessionDir);

   // if there is an orphan (crash) then reclaim it.
   else if (reclaimOrphanedSession(pSessionDir))
      return Success();

   // attempt to create from persistent
   else
      return createSessionDirFromPersistent(pSessionDir);
}

// preserve documents for re-opening in a future session
Error saveMostRecentDocuments()
{
   // only do this for multi-session contexts
   if (!module_context::activeSession().empty())
   {
      // most recent docs is last one wins so we remove and recreate
      FilePath mostRecentDir = mostRecentTitledDir();
      Error error = removeAndRecreate(mostRecentDir);
      if (error)
         return error;

      // untitled are aggregated (so we never lose unsaved docs)
      // so we just ensure the directory exists)
      FilePath mostRecentDirUntitled = mostRecentUntitledDir();
      error = mostRecentDirUntitled.ensureDirectory();
      if (error)
         return error;

      // list all current source docs
      std::vector<boost::shared_ptr<SourceDocument> > sourceDocs;
      error = source_database::list(&sourceDocs);
      if (error)
         return error;

      // write the docs into the mru directories
      BOOST_FOREACH(boost::shared_ptr<SourceDocument> pDoc, sourceDocs)
      {
         FilePath targetDir = pDoc->isUntitled() ? mostRecentDirUntitled :
                                                   mostRecentDir;

         Error error = pDoc->writeToFile(targetDir.childPath(pDoc->id()));
         if (error)
            LOG_ERROR(error);
      }
   }

   return Success();
}

Error detachFromSourceDatabase()
{
   // list all current source docs
   std::vector<boost::shared_ptr<SourceDocument> > sourceDocs;
   Error error = source_database::list(&sourceDocs);
   if (error)
      return error;

   // get references to persistent subdirs
   FilePath titledDir = persistentTitledDir();
   FilePath untitledDir = persistentUntitledDir();

   // first blow away the existing persistent titled dir
   error = titledDir.removeIfExists();
   if (error)
      LOG_ERROR(error);

   // ensure both directories exist -- if they don't it is a fatal error
   error = titledDir.ensureDirectory();
   if (error)
      return error;
   error = untitledDir.ensureDirectory();
   if (error)
      return error;

   // now write the source database entries to the appropriate places
   BOOST_FOREACH(boost::shared_ptr<SourceDocument> pDoc, sourceDocs)
   {
      if (pDoc->isUntitled())
      {
         // compute the target path (manage uniqueness since this
         // directory is appended to from multiple processes who
         // could have created docs with the same id)
         FilePath targetPath = untitledDir.complete(pDoc->id());
         if (targetPath.exists())
            targetPath = file_utils::uniqueFilePath(untitledDir);

         error = pDoc->writeToFile(targetPath);
         if (error)
            LOG_ERROR(error);
      }
      else
      {
         error = pDoc->writeToFile(titledDir.complete(pDoc->id()));
         if (error)
            LOG_ERROR(error);
      }
   }

   // record session dir (parent of lock file)
   FilePath sessionDir = sessionDirLock()->lockFilePath().parent();

   // give up our lock
   error = sessionDirLock()->release();
   if (error)
      LOG_ERROR(error);

   // remove the session directory
   return removeSessionDir(sessionDir);
}

} // namespace supervisor
} // namespace source_database
} // namespace session
} // namespace rstudio

<|endoftext|>


<commit_before>/*
    This file is part of Magnum.

    Original authors — credit is appreciated but not required:

        2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 —
            Vladimír Vondruš <mosra@centrum.cz>
        2015, 2016 — Jonathan Hale <squareys@googlemail.com>

    This is free and unencumbered software released into the public domain.

    Anyone is free to copy, modify, publish, use, compile, sell, or distribute
    this software, either in source code form or as a compiled binary, for any
    purpose, commercial or non-commercial, and by any means.

    In jurisdictions that recognize copyright laws, the author or authors of
    this software dedicate any and all copyright interest in the software to
    the public domain. We make this dedication for the benefit of the public
    at large and to the detriment of our heirs and successors. We intend this
    dedication to be an overt act of relinquishment in perpetuity of all
    present and future rights to this software under copyright law.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
    THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
    IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
    CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#include <memory>

#include <Magnum/Buffer.h>
#include <Magnum/Context.h>
#include <Magnum/DefaultFramebuffer.h>
#include <Magnum/Framebuffer.h>
#include <Magnum/Magnum.h>
#include <Magnum/Mesh.h>
#include <Magnum/Renderer.h>
#include <Magnum/Renderbuffer.h>
#include <Magnum/Texture.h>
#include <Magnum/TextureFormat.h>
#include <Magnum/MeshTools/CompressIndices.h>
#include <Magnum/MeshTools/Interleave.h>
#include <Magnum/Platform/Sdl2Application.h>
#include <Magnum/Primitives/Cube.h>
#include <Magnum/Shaders/Phong.h>
#include <Magnum/Trade/MeshData3D.h>
#include <Magnum/OvrIntegration/OvrIntegration.h>
#include <Magnum/OvrIntegration/Context.h>
#include <Magnum/OvrIntegration/Session.h>
#include <Magnum/OvrIntegration/Enums.h>

namespace Magnum { namespace Examples {

using namespace Math::Literals;

class OvrExample: public Platform::Application {
    public:
        explicit OvrExample(const Arguments& arguments);

    private:
        void drawEvent() override;
        void keyPressEvent(KeyEvent& event) override;

        OvrIntegration::Context _ovrContext;
        std::unique_ptr<OvrIntegration::Session> _session;

        Buffer _indexBuffer{NoCreate}, _vertexBuffer{NoCreate};
        Mesh _mesh{NoCreate};
        Shaders::Phong _shader{NoCreate};

        enum: std::size_t { CubeCount = 4 };
        Matrix4 _cubeTransforms[CubeCount]{
            Matrix4::rotationY(45.0_degf)*Matrix4::translation({0.0f, 0.0f, -3.0f}),
            Matrix4::rotationY(45.0_degf)*Matrix4::translation({5.0f, 0.0f, 0.0f}),
            Matrix4::rotationY(45.0_degf)*Matrix4::translation({-10.0f, 0.0f, 0.0f}),
            Matrix4::rotationY(45.0_degf)*Matrix4::translation({0.0f, 0.0f, 7.0f})};
        Color3 _cubeColors[CubeCount]{0xffff00_rgbf, 0xff0000_rgbf, 0x0000ff_rgbf, 0x00ffff_rgbf};

        Framebuffer _mirrorFramebuffer{NoCreate};
        Texture2D* _mirrorTexture;

        OvrIntegration::LayerEyeFov* _layer;
        OvrIntegration::PerformanceHudMode _curPerfHudMode;
        OvrIntegration::DebugHudStereoMode _curDebugHudStereoMode;

        /* Whether to show contents in the window or just on the VR HMD */
        bool _enableMirroring;

        /* Per eye view members */
        Texture2D _depth[2]{Texture2D{NoCreate}, Texture2D{NoCreate}};
        std::unique_ptr<OvrIntegration::TextureSwapChain> _textureSwapChain[2];
        Framebuffer _framebuffer[2]{Framebuffer{NoCreate}, Framebuffer{NoCreate}};
        Matrix4 _projectionMatrix[2];
        Deg _cameraRotation = 0.0_degf;
};

OvrExample::OvrExample(const Arguments& arguments) : Platform::Application(arguments, NoCreate),
    _curPerfHudMode(OvrIntegration::PerformanceHudMode::Off),
    _curDebugHudStereoMode(OvrIntegration::DebugHudStereoMode::Off),
    _enableMirroring(true)
{
    /* Connect to an active Oculus session */
    _session = _ovrContext.createSession();

    if(!_session) {
        Error() << "No HMD connected.";
        exit();
        return;
    }

    /* Get the HMD display resolution */
    const Vector2i resolution = _session->resolution()/2;

    /* Create a context with the HMD display resolution */
    Configuration conf;
    conf.setTitle("Magnum OculusVR Example")
        .setSize(resolution)
        .setSampleCount(16)
        .setSRGBCapable(true);
    if(!tryCreateContext(conf))
        createContext(conf.setSampleCount(0));

    /* The oculus sdk compositor does some "magic" to reduce latency. For
       that to work, VSync needs to be turned off. */
    if(!setSwapInterval(0))
        Error() << "Could not turn off VSync.";

    Renderer::enable(Renderer::Feature::DepthTest);
    Renderer::enable(Renderer::Feature::FramebufferSRGB);

    _session->configureRendering();

    /* Setup mirroring of oculus sdk compositor results to a texture which can
       later be blitted onto the default framebuffer */
    _mirrorTexture = &_session->createMirrorTexture(resolution);
    _mirrorFramebuffer = Framebuffer(Range2Di::fromSize({}, resolution));
    _mirrorFramebuffer.attachTexture(Framebuffer::ColorAttachment(0), *_mirrorTexture, 0)
                      .mapForRead(Framebuffer::ColorAttachment(0));

    /* Setup cube mesh */
    const Trade::MeshData3D cube = Primitives::cubeSolid();
    _vertexBuffer = Buffer();
    _vertexBuffer.setData(MeshTools::interleave(cube.positions(0), cube.normals(0)),
                          BufferUsage::StaticDraw);

    Containers::Array<char> indexData;
    Mesh::IndexType indexType;
    UnsignedInt indexStart, indexEnd;
    std::tie(indexData, indexType, indexStart, indexEnd) =
            MeshTools::compressIndices(cube.indices());

    _indexBuffer = Buffer();
    _indexBuffer.setData(indexData, BufferUsage::StaticDraw);

    _mesh = Mesh(cube.primitive());
    _mesh.setCount(cube.indices().size())
         .addVertexBuffer(_vertexBuffer, 0, Shaders::Phong::Position{}, Shaders::Phong::Normal{})
         .setIndexBuffer(_indexBuffer, 0, indexType, indexStart, indexEnd);

    /* Setup shader */
    _shader = Shaders::Phong();
    _shader.setSpecularColor(Color3(1.0f))
           .setShininess(20)
           .setLightPosition({3.0f, 3.0f, 3.0f});

    /* Setup compositor layers */
    _layer = &_ovrContext.compositor().addLayerEyeFov();
    _layer->setFov(*_session.get())
           .setHighQuality(true);

    /* Setup per-eye views */
    for(int eye: {0, 1}) {
        _projectionMatrix[eye] = _session->projectionMatrix(eye, 0.001f, 100.0f);

        const Vector2i textureSize = _session->fovTextureSize(eye);
        _textureSwapChain[eye] =_session->createTextureSwapChain(textureSize);

        /* Create the framebuffer which will be used to render to the current
        texture of the texture set later. */
        _framebuffer[eye] = Framebuffer({{}, textureSize});
        _framebuffer[eye].mapForDraw(Framebuffer::ColorAttachment(0));

        /* Setup depth attachment */
        _depth[eye] = Texture2D();
        _depth[eye].setMinificationFilter(Sampler::Filter::Linear)
                   .setWrapping(Sampler::Wrapping::ClampToEdge)
                   .setStorage(1, TextureFormat::DepthComponent32F, textureSize);

        _layer->setColorTexture(eye, *_textureSwapChain[eye])
               .setViewport(eye, {{}, textureSize});
    }
}

void OvrExample::drawEvent() {
    /* Get orientation and position of the hmd. */
    const std::array<DualQuaternion, 2> poses = _session->pollEyePoses().eyePoses();

    /* Draw the scene for both eyes */
    for(int eye: {0, 1}) {
        /* Switch to eye render target and bind render textures */
        _framebuffer[eye].bind();
        _framebuffer[eye].attachTexture(Framebuffer::ColorAttachment(0), _textureSwapChain[eye]->activeTexture(), 0)
            .attachTexture(Framebuffer::BufferAttachment::Depth, _depth[eye], 0)
            /* Clear with the standard grey so that at least that will be visible in
            case the scene is not correctly set up */
            .clear(FramebufferClear::Color|FramebufferClear::Depth);

        /* Render scene */
        const Matrix4 viewProjMatrix = _projectionMatrix[eye]*poses[eye].inverted().toMatrix()*Matrix4::rotationY(_cameraRotation);
        for(int cubeIndex = 0; cubeIndex < CubeCount; ++cubeIndex) {
            _shader.setDiffuseColor(_cubeColors[cubeIndex])
                .setTransformationMatrix(_cubeTransforms[cubeIndex])
                .setNormalMatrix(_cubeTransforms[cubeIndex].rotationScaling())
                .setProjectionMatrix(viewProjMatrix);
            _mesh.draw(_shader);
        }

        /* Commit changes and use next texture in chain */
        _textureSwapChain[eye]->commit();

        /* Reasoning for the next two lines, taken from the Oculus SDK examples
           code: Without this, [during the next frame, this method] would bind a
           framebuffer with an invalid COLOR_ATTACHMENT0 because the texture ID
           associated with COLOR_ATTACHMENT0 had been unlocked by calling
           wglDXUnlockObjectsNV(). */
        _framebuffer[eye].detach(Framebuffer::ColorAttachment(0))
                         .detach(Framebuffer::BufferAttachment::Depth);
    }

    /* Set the layers eye poses to the poses chached in the _hmd. */
    _layer->setRenderPoses(*_session.get());

    /* Let the libOVR sdk compositor do its magic! */
    _ovrContext.compositor().submitFrame(*_session.get());

    if(_enableMirroring) {
        /* Blit mirror texture to default framebuffer */
        const Vector2i size = _mirrorTexture->imageSize(0);
        Framebuffer::blit(_mirrorFramebuffer,
                          defaultFramebuffer,
                          {{0, size.y()}, {size.x(), 0}},
                          {{}, size},
                          FramebufferBlit::Color, FramebufferBlitFilter::Nearest);

        swapBuffers();
    }

    /* Provide some rotation, but only without real devices to avoid VR sickness ;) */
    if(_session->isDebugHmd()) _cameraRotation += 0.1_degf;

    redraw();
}

void OvrExample::keyPressEvent(KeyEvent& event) {
    /* Toggle through the performance hud modes */
    if(event.key() == KeyEvent::Key::F11) {
        switch(_curPerfHudMode) {
            case OvrIntegration::PerformanceHudMode::Off:
                _curPerfHudMode = OvrIntegration::PerformanceHudMode::LatencyTiming;
                break;
            case OvrIntegration::PerformanceHudMode::LatencyTiming:
                _curPerfHudMode = OvrIntegration::PerformanceHudMode::AppRenderTiming;
                break;
            case OvrIntegration::PerformanceHudMode::AppRenderTiming:
                _curPerfHudMode = OvrIntegration::PerformanceHudMode::CompRenderTiming;
                break;
            case OvrIntegration::PerformanceHudMode::CompRenderTiming:
                _curPerfHudMode = OvrIntegration::PerformanceHudMode::PerfSummary;
                break;
            case OvrIntegration::PerformanceHudMode::PerfSummary:
                _curPerfHudMode = OvrIntegration::PerformanceHudMode::VersionInfo;
                break;
            case OvrIntegration::PerformanceHudMode::VersionInfo:
                _curPerfHudMode = OvrIntegration::PerformanceHudMode::Off;
                break;
        }

        _session->setPerformanceHudMode(_curPerfHudMode);

    /* Toggle through the debug hud stereo modes */
    } else if(event.key() == KeyEvent::Key::F12) {
        switch(_curDebugHudStereoMode) {
            case OvrIntegration::DebugHudStereoMode::Off:
                _curDebugHudStereoMode = OvrIntegration::DebugHudStereoMode::Quad;
                break;
            case OvrIntegration::DebugHudStereoMode::Quad:
                _curDebugHudStereoMode = OvrIntegration::DebugHudStereoMode::QuadWithCrosshair;
                break;
            case OvrIntegration::DebugHudStereoMode::QuadWithCrosshair:
                _curDebugHudStereoMode = OvrIntegration::DebugHudStereoMode::CrosshairAtInfinity;
                break;
            case OvrIntegration::DebugHudStereoMode::CrosshairAtInfinity:
                _curDebugHudStereoMode = OvrIntegration::DebugHudStereoMode::Off;
                break;
        }

        _session->setDebugHudStereoMode(_curDebugHudStereoMode);

    /* Toggle mirroring */
    } else if(event.key() == KeyEvent::Key::M) {
        _enableMirroring = !_enableMirroring;

    /* Exit */
    } else if(event.key() == KeyEvent::Key::Esc) {
        exit();
    }
}

}}

MAGNUM_APPLICATION_MAIN(Magnum::Examples::OvrExample)
<commit_msg>ovr: further simplification.<commit_after>/*
    This file is part of Magnum.

    Original authors — credit is appreciated but not required:

        2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 —
            Vladimír Vondruš <mosra@centrum.cz>
        2015, 2016, 2018 — Jonathan Hale <squareys@googlemail.com>

    This is free and unencumbered software released into the public domain.

    Anyone is free to copy, modify, publish, use, compile, sell, or distribute
    this software, either in source code form or as a compiled binary, for any
    purpose, commercial or non-commercial, and by any means.

    In jurisdictions that recognize copyright laws, the author or authors of
    this software dedicate any and all copyright interest in the software to
    the public domain. We make this dedication for the benefit of the public
    at large and to the detriment of our heirs and successors. We intend this
    dedication to be an overt act of relinquishment in perpetuity of all
    present and future rights to this software under copyright law.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
    THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
    IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
    CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#include <memory>
#include <Magnum/Buffer.h>
#include <Magnum/Context.h>
#include <Magnum/DefaultFramebuffer.h>
#include <Magnum/Framebuffer.h>
#include <Magnum/Magnum.h>
#include <Magnum/Mesh.h>
#include <Magnum/Renderer.h>
#include <Magnum/Renderbuffer.h>
#include <Magnum/Texture.h>
#include <Magnum/TextureFormat.h>
#include <Magnum/MeshTools/CompressIndices.h>
#include <Magnum/MeshTools/Interleave.h>
#include <Magnum/Platform/Sdl2Application.h>
#include <Magnum/Primitives/Cube.h>
#include <Magnum/Shaders/Phong.h>
#include <Magnum/Trade/MeshData3D.h>
#include <Magnum/OvrIntegration/OvrIntegration.h>
#include <Magnum/OvrIntegration/Context.h>
#include <Magnum/OvrIntegration/Session.h>
#include <Magnum/OvrIntegration/Enums.h>

namespace Magnum { namespace Examples {

using namespace Math::Literals;

class OvrExample: public Platform::Application {
    public:
        explicit OvrExample(const Arguments& arguments);

    private:
        void drawEvent() override;
        void keyPressEvent(KeyEvent& event) override;

        OvrIntegration::Context _ovrContext;
        std::unique_ptr<OvrIntegration::Session> _session;

        Buffer _indexBuffer{NoCreate}, _vertexBuffer{NoCreate};
        Mesh _mesh{NoCreate};
        Shaders::Phong _shader{NoCreate};

        enum: std::size_t { CubeCount = 4 };
        Matrix4 _cubeTransforms[CubeCount]{
            Matrix4::rotationY(45.0_degf)*Matrix4::translation({0.0f, 0.0f, -3.0f}),
            Matrix4::rotationY(45.0_degf)*Matrix4::translation({5.0f, 0.0f, 0.0f}),
            Matrix4::rotationY(45.0_degf)*Matrix4::translation({-10.0f, 0.0f, 0.0f}),
            Matrix4::rotationY(45.0_degf)*Matrix4::translation({0.0f, 0.0f, 7.0f})};
        Color3 _cubeColors[CubeCount]{
            0xffff00_rgbf, 0xff0000_rgbf, 0x0000ff_rgbf, 0x00ffff_rgbf};

        Framebuffer _mirrorFramebuffer{NoCreate};
        Texture2D* _mirrorTexture;

        OvrIntegration::LayerEyeFov* _layer;
        OvrIntegration::PerformanceHudMode _curPerfHudMode{
            OvrIntegration::PerformanceHudMode::Off};
        OvrIntegration::DebugHudStereoMode _curDebugHudStereoMode{
            OvrIntegration::DebugHudStereoMode::Off};

        /* Whether to show contents in the window or just on the VR HMD */
        bool _enableMirroring{true};

        /* Per eye view members */
        Texture2D _depth[2]{Texture2D{NoCreate}, Texture2D{NoCreate}};
        std::unique_ptr<OvrIntegration::TextureSwapChain> _textureSwapChain[2];
        Framebuffer _framebuffer[2]{Framebuffer{NoCreate}, Framebuffer{NoCreate}};
        Matrix4 _projectionMatrix[2];
        Deg _cameraRotation = 0.0_degf;
};

OvrExample::OvrExample(const Arguments& arguments): Platform::Application(arguments, NoCreate) {
    /* Connect to an active Oculus session */
    _session = _ovrContext.createSession();

    if(!_session) {
        Error() << "No HMD connected.";
        exit();
        return;
    }

    /* Get the HMD display resolution */
    const Vector2i resolution = _session->resolution()/2;

    /* Create a context with the HMD display resolution */
    Configuration conf;
    conf.setTitle("Magnum OculusVR Example")
        .setSize(resolution)
        .setSampleCount(16)
        .setSRGBCapable(true);
    if(!tryCreateContext(conf))
        createContext(conf.setSampleCount(0));

    /* The oculus sdk compositor does some "magic" to reduce latency. For
       that to work, VSync needs to be turned off. */
    if(!setSwapInterval(0))
        Error() << "Could not turn off VSync.";

    Renderer::enable(Renderer::Feature::DepthTest);
    Renderer::enable(Renderer::Feature::FramebufferSRGB);

    _session->configureRendering();

    /* Setup mirroring of oculus sdk compositor results to a texture which can
       later be blitted onto the default framebuffer */
    _mirrorTexture = &_session->createMirrorTexture(resolution);
    _mirrorFramebuffer = Framebuffer(Range2Di::fromSize({}, resolution));
    _mirrorFramebuffer.attachTexture(Framebuffer::ColorAttachment(0), *_mirrorTexture, 0)
                      .mapForRead(Framebuffer::ColorAttachment(0));

    /* Setup cube mesh */
    const Trade::MeshData3D cube = Primitives::cubeSolid();
    _vertexBuffer = Buffer();
    _vertexBuffer.setData(MeshTools::interleave(cube.positions(0), cube.normals(0)),
                          BufferUsage::StaticDraw);

    Containers::Array<char> indexData;
    Mesh::IndexType indexType;
    UnsignedInt indexStart, indexEnd;
    std::tie(indexData, indexType, indexStart, indexEnd) =
            MeshTools::compressIndices(cube.indices());

    _indexBuffer = Buffer();
    _indexBuffer.setData(indexData, BufferUsage::StaticDraw);

    _mesh = Mesh(cube.primitive());
    _mesh.setCount(cube.indices().size())
         .addVertexBuffer(_vertexBuffer, 0, Shaders::Phong::Position{}, Shaders::Phong::Normal{})
         .setIndexBuffer(_indexBuffer, 0, indexType, indexStart, indexEnd);

    /* Setup shader */
    _shader = Shaders::Phong();
    _shader.setShininess(20)
           .setLightPosition({3.0f, 3.0f, 3.0f});

    /* Setup compositor layers */
    _layer = &_ovrContext.compositor().addLayerEyeFov();
    _layer->setFov(*_session.get())
           .setHighQuality(true);

    /* Setup per-eye views */
    for(Int eye: {0, 1}) {
        _projectionMatrix[eye] = _session->projectionMatrix(eye, 0.001f, 100.0f);

        const Vector2i textureSize = _session->fovTextureSize(eye);
        _textureSwapChain[eye] =_session->createTextureSwapChain(textureSize);

        /* Create the framebuffer which will be used to render to the current
        texture of the texture set later. */
        _framebuffer[eye] = Framebuffer({{}, textureSize});
        _framebuffer[eye].mapForDraw(Framebuffer::ColorAttachment(0));

        /* Setup depth attachment */
        _depth[eye] = Texture2D();
        _depth[eye].setMinificationFilter(Sampler::Filter::Linear)
                   .setWrapping(Sampler::Wrapping::ClampToEdge)
                   .setStorage(1, TextureFormat::DepthComponent32F, textureSize);

        _layer->setColorTexture(eye, *_textureSwapChain[eye])
               .setViewport(eye, {{}, textureSize});
    }
}

void OvrExample::drawEvent() {
    /* Get orientation and position of the hmd. */
    const std::array<DualQuaternion, 2> poses = _session->pollEyePoses().eyePoses();

    /* Draw the scene for both eyes */
    for(Int eye: {0, 1}) {
        /* Switch to eye render target and bind render textures */
        _framebuffer[eye]
            .attachTexture(Framebuffer::ColorAttachment(0), _textureSwapChain[eye]->activeTexture(), 0)
            .attachTexture(Framebuffer::BufferAttachment::Depth, _depth[eye], 0)
            /* Clear with the standard grey so that at least that will be visible in
            case the scene is not correctly set up */
            .clear(FramebufferClear::Color|FramebufferClear::Depth)
            .bind();

        /* Render scene */
        const Matrix4 viewProjMatrix = _projectionMatrix[eye]*poses[eye].inverted().toMatrix()*Matrix4::rotationY(_cameraRotation);
        for(Int cubeIndex = 0; cubeIndex < CubeCount; ++cubeIndex) {
            _shader.setDiffuseColor(_cubeColors[cubeIndex])
                .setTransformationMatrix(_cubeTransforms[cubeIndex])
                .setNormalMatrix(_cubeTransforms[cubeIndex].rotationScaling())
                .setProjectionMatrix(viewProjMatrix);
            _mesh.draw(_shader);
        }

        /* Commit changes and use next texture in chain */
        _textureSwapChain[eye]->commit();

        /* Reasoning for the next two lines, taken from the Oculus SDK examples
           code: Without this, [during the next frame, this method] would bind a
           framebuffer with an invalid COLOR_ATTACHMENT0 because the texture ID
           associated with COLOR_ATTACHMENT0 had been unlocked by calling
           wglDXUnlockObjectsNV(). */
        _framebuffer[eye].detach(Framebuffer::ColorAttachment(0))
                         .detach(Framebuffer::BufferAttachment::Depth);
    }

    /* Set the layers eye poses to the poses chached in the _hmd. */
    _layer->setRenderPoses(*_session.get());

    /* Let the libOVR sdk compositor do its magic! */
    _ovrContext.compositor().submitFrame(*_session.get());

    if(_enableMirroring) {
        /* Blit mirror texture to default framebuffer */
        const Vector2i size = _mirrorTexture->imageSize(0);
        Framebuffer::blit(_mirrorFramebuffer,
                          defaultFramebuffer,
                          {{0, size.y()}, {size.x(), 0}},
                          {{}, size},
                          FramebufferBlit::Color, FramebufferBlitFilter::Nearest);

        swapBuffers();
    }

    /* Provide some rotation, but only without real devices to avoid VR sickness ;) */
    if(_session->isDebugHmd()) _cameraRotation += 0.1_degf;

    redraw();
}

void OvrExample::keyPressEvent(KeyEvent& event) {
    /* Toggle through the performance hud modes */
    if(event.key() == KeyEvent::Key::F11) {
        switch(_curPerfHudMode) {
            case OvrIntegration::PerformanceHudMode::Off:
                _curPerfHudMode = OvrIntegration::PerformanceHudMode::LatencyTiming;
                break;
            case OvrIntegration::PerformanceHudMode::LatencyTiming:
                _curPerfHudMode = OvrIntegration::PerformanceHudMode::AppRenderTiming;
                break;
            case OvrIntegration::PerformanceHudMode::AppRenderTiming:
                _curPerfHudMode = OvrIntegration::PerformanceHudMode::CompRenderTiming;
                break;
            case OvrIntegration::PerformanceHudMode::CompRenderTiming:
                _curPerfHudMode = OvrIntegration::PerformanceHudMode::PerfSummary;
                break;
            case OvrIntegration::PerformanceHudMode::PerfSummary:
                _curPerfHudMode = OvrIntegration::PerformanceHudMode::VersionInfo;
                break;
            case OvrIntegration::PerformanceHudMode::VersionInfo:
                _curPerfHudMode = OvrIntegration::PerformanceHudMode::Off;
                break;
        }

        _session->setPerformanceHudMode(_curPerfHudMode);

    /* Toggle through the debug hud stereo modes */
    } else if(event.key() == KeyEvent::Key::F12) {
        switch(_curDebugHudStereoMode) {
            case OvrIntegration::DebugHudStereoMode::Off:
                _curDebugHudStereoMode = OvrIntegration::DebugHudStereoMode::Quad;
                break;
            case OvrIntegration::DebugHudStereoMode::Quad:
                _curDebugHudStereoMode = OvrIntegration::DebugHudStereoMode::QuadWithCrosshair;
                break;
            case OvrIntegration::DebugHudStereoMode::QuadWithCrosshair:
                _curDebugHudStereoMode = OvrIntegration::DebugHudStereoMode::CrosshairAtInfinity;
                break;
            case OvrIntegration::DebugHudStereoMode::CrosshairAtInfinity:
                _curDebugHudStereoMode = OvrIntegration::DebugHudStereoMode::Off;
                break;
        }

        _session->setDebugHudStereoMode(_curDebugHudStereoMode);

    /* Toggle mirroring */
    } else if(event.key() == KeyEvent::Key::M) {
        _enableMirroring = !_enableMirroring;

    /* Exit */
    } else if(event.key() == KeyEvent::Key::Esc) {
        exit();
    }
}

}}

MAGNUM_APPLICATION_MAIN(Magnum::Examples::OvrExample)
<|endoftext|>


<commit_before>#include "malloc_allocator.h"
#include "memory_types.h"
#include <cstdlib>
#include <cassert>
#include "callstack_capturer.h"
#include <cstring>
#include "allocator_helpers.h"

namespace bowtie
{

namespace internal
{

using namespace bowtie;
const unsigned HEADER_PAD_VALUE = 0xffffffffu;

#if defined(TRACING)
	const unsigned TRACING_MARKER = 0xfffffffeu;
#endif

// Header stored at the beginning of a memory allocation to indicate the
// size of the allocated data.
struct Header {
	#if defined(TRACING)
		unsigned tracing_marker;
	#endif
	unsigned size;
};

// Given a pointer to the header, returns a pointer to the data that follows it.
inline void *data_pointer(Header *header, unsigned align) {
	auto p = header + 1;
	return memory::align_forward(p, align);
}

// Given a pointer to the data, returns a pointer to the header before it.
inline Header *header(void *data)
{
	auto p = (unsigned *)data;
	while (p[-1] == HEADER_PAD_VALUE)
		--p;
	return (Header *)memory::pointer_sub(p, sizeof(Header));
}

// Stores the size in the header and pads with HEADER_PAD_VALUE up to the
// data pointer.
inline void fill(Header *header, void *data, unsigned size)
{
	header->size = size;
	#if defined(TRACING)
		header->tracing_marker = TRACING_MARKER;
	#endif

	auto p = (unsigned *)memory::pointer_add(header, sizeof(Header));
	while (p < data)
		*p++ = HEADER_PAD_VALUE;
}

inline unsigned size_with_padding(unsigned size, unsigned align) {
	return size + align * 2 + sizeof(Header);
}

inline void* alloc(Allocator& a, unsigned size, unsigned align)
{
	const unsigned ts = size_with_padding(size, align);
	auto h = (Header *)memory::align_forward(malloc(ts), memory::default_align);
	void *p = data_pointer(h, align);
	fill(h, p, ts);
	++a.total_allocations;
	a.total_allocated += ts;

	#if defined(TRACING)
		allocator_helpers::add_captured_callstack(a._captured_callstacks, a.callstack_capturer->capture(1, p));
	#endif

	return p;
}

inline void dealloc(Allocator& a, void* p)
{
	if (!p)
		return;

	auto h = header(p);
	assert(a.total_allocated >= h->size);

	#if defined(TRACING)
		assert(h->tracing_marker == TRACING_MARKER);
		h->tracing_marker = 0;
	#endif

	--a.total_allocations;
	assert(a.total_allocations >= 0);
	a.total_allocated -= h->size;

	#if defined(TRACING)
		allocator_helpers::remove_captured_callstack(a._captured_callstacks, p);
	#endif

	free(h);
}

} // namespace internal

void* MallocAllocator::alloc(unsigned size, unsigned align)
{
	auto p = internal::alloc(*this, size, align);
	memset(p, 0, size);
	return p;
}

void* MallocAllocator::alloc_raw(uint32_t size, uint32_t align)
{
	return internal::alloc(*this, size, align);
}

void MallocAllocator::dealloc(void *p)
{
	internal::dealloc(*this, p);
}

} // namespace bowtie
<commit_msg>Mooore.<commit_after>#include "malloc_allocator.h"
#include "memory_types.h"
#include <cstdlib>
#include <cassert>
#include "callstack_capturer.h"
#include <cstring>
#include "allocator_helpers.h"

namespace bowtie
{

namespace internal
{

using namespace bowtie;
const unsigned HEADER_PAD_VALUE = 0xffffffffu;

#if defined(TRACING)
	const unsigned TRACING_MARKER = 0xfffffffeu;
#endif

// Header stored at the beginning of a memory allocation to indicate the
// size of the allocated data.
struct Header {
	#if defined(TRACING)
		unsigned tracing_marker;
	#endif
	unsigned size;
};

// Given a pointer to the header, returns a pointer to the data that follows it.
inline void *data_pointer(Header *header, unsigned align) {
	auto p = header + 1;
	return memory::align_forward(p, align);
}

// Given a pointer to the data, returns a pointer to the header before it.
inline Header *header(void *data)
{
	auto p = (unsigned *)data;
	while (p[-1] == HEADER_PAD_VALUE)
		--p;
	return (Header *)memory::pointer_sub(p, sizeof(Header));
}

// Stores the size in the header and pads with HEADER_PAD_VALUE up to the
// data pointer.
inline void fill(Header *header, void *data, unsigned size)
{
	header->size = size;
	#if defined(TRACING)
		header->tracing_marker = TRACING_MARKER;
	#endif

	auto p = (unsigned *)memory::pointer_add(header, sizeof(Header));
	while (p < data)
		*p++ = HEADER_PAD_VALUE;
}

inline unsigned size_with_padding(unsigned size, unsigned align) {
	return size + align * 2 + sizeof(Header);
}

inline void* alloc(Allocator& a, unsigned size, unsigned align)
{
	const unsigned ts = size_with_padding(size, align);
	auto h = (Header *)memory::align_forward(malloc(ts), memory::default_align);
	void *p = data_pointer(h, align);
	fill(h, p, ts);
	++a.total_allocations;
	a.total_allocated += ts;

	#if defined(TRACING)
		allocator_helpers::add_captured_callstack(a._captured_callstacks, a.callstack_capturer->capture(1, p));
	#endif

	return p;
}

inline void dealloc(Allocator& a, void* p)
{
	if (!p)
		return;

	auto h = header(p);
	assert(a.total_allocated >= h->size);

	#if defined(TRACING)
		assert(h->tracing_marker == TRACING_MARKER);
		h->tracing_marker = 0;
	#endif

	--a.total_allocations;
	assert(a.total_allocations >= 0);
	a.total_allocated -= h->size;

	#if defined(TRACING)
		allocator_helpers::remove_captured_callstack(a._captured_callstacks, p);
	#endif

	free(h);
}

} // namespace internal

void* MallocAllocator::alloc(unsigned size, unsigned align)
{
	auto p = internal::alloc(*this, size, align);
	memset(p, 0, size);
	return p;
}

void* MallocAllocator::alloc_raw(unsigned size, unsigned align)
{
	return internal::alloc(*this, size, align);
}

void MallocAllocator::dealloc(void *p)
{
	internal::dealloc(*this, p);
}

} // namespace bowtie
<|endoftext|>


<commit_before>#include "particlewidget.h"

ParticleWidget::ParticleWidget(QWidget *parent) : QOpenGLWidget(parent) {
    initializeGL();
}

void ParticleWidget::setBoxGeometry(const SGeometry &newGeometry) {
    this->mBoxGeometry = newGeometry;
    paintGL();
}

void ParticleWidget::setParticles(const QVector<SParticle> &newParticles) {
    this->mParticles = newParticles;
    paintGL();
}

void ParticleWidget::initializeWorld(SGeometry geometry, QVector<SParticle> particles) {
    this->mBoxGeometry = geometry;
    this->mParticles = particles;
    paintGL();
}

void ParticleWidget::clearGL() {
    glClearColor(0.05, 0.05, 0.05, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

void ParticleWidget::paintGL() {
    clearGL();
    glLoadIdentity();

    const double sin45 = 0.70710678118654752440084436210485d;
    const double cos45 = sin45;
    SGeometry *pGeometry = &mBoxGeometry;
    double R = pGeometry->rParticle;
    double Rsin45 = R * sin45;
    double Rcos45 = R * cos45;

    for (int i = 0; i < mParticles.size(); i++)
    {
        SParticle *pParticle = &mParticles[i];
        if (pParticle->color)
            glColor3ub(255, 0, 0);
        else
            glColor3ub(0, 255, 255);

        // TODO: use display  lists here for speed
        glBegin(GL_TRIANGLE_FAN);
            glVertex2d(pParticle->x+0, pParticle->y+0);
            glVertex2d(pParticle->x+R, pParticle->y+0);
            glVertex2d(pParticle->x+Rcos45, pParticle->y+Rsin45);
            glVertex2d(pParticle->x+0, pParticle->y+R);
            glVertex2d(pParticle->x-Rcos45, pParticle->y+Rsin45);
            glVertex2d(pParticle->x-R, pParticle->y+0);
            glVertex2d(pParticle->x-Rcos45, pParticle->y-Rsin45);
            glVertex2d(pParticle->x+0, pParticle->y-R);
            glVertex2d(pParticle->x+Rcos45, pParticle->y-Rsin45);
            glVertex2d(pParticle->x+R, pParticle->y+0);
        glEnd();
    }
    glColor3f(1,1,1);
    glLineWidth(2);
    glBegin(GL_LINE_STRIP);
        glVertex2d(pGeometry->wLeft, pGeometry->yMax);
        glVertex2d(pGeometry->wLeft, pGeometry->yGapTop);
        glVertex2d(pGeometry->wRight, pGeometry->yGapTop);
        glVertex2d(pGeometry->wRight, pGeometry->yMax);
    glEnd();
    glBegin(GL_LINE_STRIP);
        glVertex2d(pGeometry->wLeft, 0.0);
        glVertex2d(pGeometry->wLeft, pGeometry->yGapBottom);
        glVertex2d(pGeometry->wRight, pGeometry->yGapBottom);
        glVertex2d(pGeometry->wRight, 0.0);
    glEnd();
    doneCurrent();
}

void ParticleWidget::initializeGL() {
    glShadeModel(GL_SMOOTH);
    glClearColor(0.05, 0.05, 0.05, 1.0);
    glClearDepth(1.0);

    glEnable(GL_DEPTH_TEST);
    glMatrixMode(GL_PROJECTION);
    glDepthFunc(GL_LEQUAL);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
}

void ParticleWidget::resizeGL(int width, int height) {
    glViewport(0, 0, width, height);

    glOrtho(0.0, this->mBoxGeometry.xRight,
            0.0, this->mBoxGeometry.yMax,
            0.0, 1.0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}
<commit_msg>Добавлено обновление QOpenGLWidget<commit_after>#include "particlewidget.h"

ParticleWidget::ParticleWidget(QWidget *parent) : QOpenGLWidget(parent) {
    initializeGL();
}

ParticleWidget::~ParticleWidget() {
}

void ParticleWidget::setBoxGeometry(const SGeometry &newGeometry) {
    this->mBoxGeometry = newGeometry;
    paintGL();
    update();
}

void ParticleWidget::setParticles(const QVector<SParticle> &newParticles) {
    this->mParticles = newParticles;
    paintGL();
    update();
}

void ParticleWidget::initializeWorld(SGeometry geometry, QVector<SParticle> particles) {
    this->mBoxGeometry = geometry;
    this->mParticles = particles;
    paintGL();
    update();
}

void ParticleWidget::clearGL() {
    glClearColor(0.05, 0.05, 0.05, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

void ParticleWidget::paintGL() {
    clearGL();
    glLoadIdentity();

    const double sin45 = 0.70710678118654752440084436210485d;
    const double cos45 = sin45;
    SGeometry *pGeometry = &mBoxGeometry;
    double R = pGeometry->rParticle;
    double Rsin45 = R * sin45;
    double Rcos45 = R * cos45;

    for (int i = 0; i < mParticles.size(); i++)
    {
        SParticle *pParticle = &mParticles[i];
        if (pParticle->color)
            glColor3ub(255, 0, 0);
        else
            glColor3ub(0, 255, 255);

        // TODO: use display  lists here for speed
        glBegin(GL_TRIANGLE_FAN);
            glVertex2d(pParticle->x+0, pParticle->y+0);
            glVertex2d(pParticle->x+R, pParticle->y+0);
            glVertex2d(pParticle->x+Rcos45, pParticle->y+Rsin45);
            glVertex2d(pParticle->x+0, pParticle->y+R);
            glVertex2d(pParticle->x-Rcos45, pParticle->y+Rsin45);
            glVertex2d(pParticle->x-R, pParticle->y+0);
            glVertex2d(pParticle->x-Rcos45, pParticle->y-Rsin45);
            glVertex2d(pParticle->x+0, pParticle->y-R);
            glVertex2d(pParticle->x+Rcos45, pParticle->y-Rsin45);
            glVertex2d(pParticle->x+R, pParticle->y+0);
        glEnd();
    }
    glColor3f(1,1,1);
    glLineWidth(2);
    glBegin(GL_LINE_STRIP);
        glVertex2d(pGeometry->wLeft, pGeometry->yMax);
        glVertex2d(pGeometry->wLeft, pGeometry->yGapTop);
        glVertex2d(pGeometry->wRight, pGeometry->yGapTop);
        glVertex2d(pGeometry->wRight, pGeometry->yMax);
    glEnd();
    glBegin(GL_LINE_STRIP);
        glVertex2d(pGeometry->wLeft, 0.0);
        glVertex2d(pGeometry->wLeft, pGeometry->yGapBottom);
        glVertex2d(pGeometry->wRight, pGeometry->yGapBottom);
        glVertex2d(pGeometry->wRight, 0.0);
    glEnd();
    doneCurrent();
}

void ParticleWidget::initializeGL() {
    glShadeModel(GL_SMOOTH);
    glClearColor(0.05, 0.05, 0.05, 1.0);
    glClearDepth(1.0);

    glEnable(GL_DEPTH_TEST);
    glMatrixMode(GL_PROJECTION);
    glDepthFunc(GL_LEQUAL);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
}

void ParticleWidget::resizeGL(int width, int height) {
    glViewport(0, 0, width, height);

    glOrtho(0.0, this->mBoxGeometry.xRight,
            0.0, this->mBoxGeometry.yMax,
            0.0, 1.0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}
<|endoftext|>


<commit_before>//  Copyright 2010 Gregory Szorc
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.

#include <pblog/envelope.hpp>
#include <pblog/message_registrar.hpp>

#include <string>

namespace pblog {

using ::std::string;

Envelope::Envelope()
{
    ;
}

Envelope::Envelope(message_t *msg)
{
    this->envelope.ParseFromArray(msg->data(), msg->size());
}

bool Envelope::add_message(Message *m, uint32 ns, uint32 enumeration)
{
    string buffer;
    if (!m->SerializeToString(&buffer)) return false;

    this->envelope.add_message_namespace(ns);
    this->envelope.add_message_type(enumeration);
    this->envelope.add_message(buffer);

    return true;
}

message_t * Envelope::to_zmq_message()
{
    string buffer;
    this->envelope.SerializeToString(&buffer);

    message_t *msg = new message_t(buffer.length());
    memcpy(msg->data(), (void *)buffer.c_str(), buffer.length());

    return msg;
}

Message * Envelope::get_message(int index)
{
    Message *msg = NULL;

    if (this->envelope.message_size() < index - 1) {
        return NULL;
    }

    if (this->envelope.message_namespace_size() < index - 1) {
        return NULL;
    }

    if (this->envelope.message_type_size() < index -1) {
        return NULL;
    }

    uint32 ns = this->envelope.message_namespace(index);
    uint32 enumeration = this->envelope.message_type(index);

    msg = MessageRegistrar::instance()->get_message(ns, enumeration);
    if (!msg) return msg;

    string buffer = envelope.message(index);
    msg->ParseFromString(buffer);

    return msg;
}

} // namespace<commit_msg>fix logic error for bounds checking<commit_after>//  Copyright 2010 Gregory Szorc
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.

#include <pblog/envelope.hpp>
#include <pblog/message_registrar.hpp>

#include <string>

namespace pblog {

using ::std::string;

Envelope::Envelope()
{
    ;
}

Envelope::Envelope(message_t *msg)
{
    this->envelope.ParseFromArray(msg->data(), msg->size());
}

bool Envelope::add_message(Message *m, uint32 ns, uint32 enumeration)
{
    string buffer;
    if (!m->SerializeToString(&buffer)) return false;

    this->envelope.add_message_namespace(ns);
    this->envelope.add_message_type(enumeration);
    this->envelope.add_message(buffer);

    return true;
}

message_t * Envelope::to_zmq_message()
{
    string buffer;
    this->envelope.SerializeToString(&buffer);

    message_t *msg = new message_t(buffer.length());
    memcpy(msg->data(), (void *)buffer.c_str(), buffer.length());

    return msg;
}

Message * Envelope::get_message(int index)
{
    Message *msg = NULL;

    if (this->envelope.message_size() < index + 1) {
        return NULL;
    }

    if (this->envelope.message_namespace_size() < index + 1) {
        return NULL;
    }

    if (this->envelope.message_type_size() < index + 1) {
        return NULL;
    }

    uint32 ns = this->envelope.message_namespace(index);
    uint32 enumeration = this->envelope.message_type(index);

    msg = MessageRegistrar::instance()->get_message(ns, enumeration);
    if (!msg) return msg;

    string buffer = envelope.message(index);
    msg->ParseFromString(buffer);

    return msg;
}

} // namespace<|endoftext|>


<commit_before><commit_msg>pdn: ensure repair straps snaps to grid and correct end point checks<commit_after><|endoftext|>


<commit_before>#ifndef AB_LINEARMEMORY_HPP_
#define AB_LINEARMEMORY_HPP_

#include <Ab/Address.hpp>
#include <Ab/Assert.hpp>
#include <Ab/Bytes.hpp>
#include <Ab/Page.hpp>
#include <Ab/Result.hpp>
#include <Ab/Span.hpp>
#include <cstdint>

namespace Ab {

class LinearMemoryError : public std::runtime_error {
	using std::runtime_error::runtime_error;
};

struct LinearMemoryConfig {
	Address address            = nullptr;
	std::size_t page_count_min = 1;
	std::size_t page_count_max = 4;

	void verify() const {
		if (page_count_min > page_count_max) {
			throw LinearMemoryError(
				"LinearMemoryConfig validation error: minPageCount greater than "
				"max");
		}
	}
};

/// The WASM contiguous raw memory subsystem.
/// web assembly gives programs low level access to a contiguous region of memory.
/// The LinearMemory class manages that giant blob of memory. Per the spec, LinearMemory can be
/// grown, but does not shrink. Each call
class LinearMemory {
public:
	/// The size of a memory page.
	static std::size_t page_size() { return Page::size(); }

	/// Bring up the memory subsytem.
	///
	LinearMemory(const LinearMemoryConfig& config)
		: address_(nullptr), page_count_(0), config_(config) {
		config_.verify();
		address_ = reserve(config.address, config.page_count_max);
		grow(config.page_count_min);
	}

	/// Bring up the memory subsystem with the default config.
	///
	LinearMemory() : LinearMemory(LinearMemoryConfig()) {}

	~LinearMemory() { release(address_, max_size()); }

	/// The address.
	///
	Address addresss() const noexcept { return address_; }

	/// The size of the currently allocated memory.
	///
	std::size_t size() const noexcept { return page_count_ * page_size(); }

	/// The maximum size the memory can grow to.
	///
	std::size_t max_size() const noexcept { return config_.page_count_max; }

	/// Grow the memory by n pages.
	void grow(std::size_t n = 1) {
		// TODO: worry about overflow / underflow
		if (config_.page_count_max - page_count_ < n) {
			throw LinearMemoryError("Failed to grow, not enough reserved pages.");
		}
		// TODO: Activate new region
	}

	/// Shrink the memory by n pages.
	void shrink(std::size_t n = 1) {
		if (page_count_ < n) {
			throw LinearMemoryError("Failed to shrink: not enough active pages.");
		}
		/// TODO: Deactivate region
	}

	const LinearMemoryConfig& config() const noexcept { return config_; }

private:
	Address reserve(const Address address, std::size_t n) { return Page::map(address, n); }

	void activate(const Address address, const std::size_t n) {
		// activate the memory region by requesting read/write permissions.
		auto permissions = PagePermission::READ | PagePermission::WRITE;
		Page::set_permissions(address, n * Page::size(), permissions);
	}

	void deactivate(const Address address, const std::size_t n) {
		// deactivate the memory region by disabling all permissions. this should hopefully
		// cause the OS to unmap the memory.
		auto permissions = PagePermission::NONE;
		Page::set_permissions(address, n * Page::size(), permissions);
	}

	void release(const Address address, const std::size_t n) {
		Page::unmap(address, n * Page::size());
	}

	Address address_;
	std::size_t page_count_;
	const LinearMemoryConfig config_;
};

}  // namespace Ab

#endif  // AB_LINEARMEMORY_HPP_
<commit_msg>Clean up linear memory header<commit_after>#ifndef AB_LINEARMEMORY_HPP_
#define AB_LINEARMEMORY_HPP_

#include <Ab/Config.hpp>
#include <Ab/Address.hpp>
#include <Ab/Assert.hpp>
#include <Ab/Bytes.hpp>
#include <Ab/Page.hpp>
#include <Ab/Result.hpp>
#include <Ab/Span.hpp>
#include <cstdint>

namespace Ab {

class LinearMemoryError : public std::runtime_error {
	using std::runtime_error::runtime_error;
};

struct LinearMemoryConfig {
	Address address            = nullptr;
	std::size_t page_count_min = 1;
	std::size_t page_count_max = 4;

	void verify() const {
		if (page_count_max < page_count_min) {
			throw LinearMemoryError(
				"LinearMemoryConfig validation error: minPageCount greater than "
				"max");
		}
	}
};

/// The WASM contiguous raw memory subsystem.
/// web assembly gives programs low level access to a contiguous region of memory.
/// The LinearMemory class manages that giant blob of memory. Per the spec, LinearMemory can be
/// grown, but does not shrink. Each call
class LinearMemory {
public:
	/// The size of a memory page.
	static std::size_t page_size() { return Page::size(); }

	/// Bring up the memory subsytem.
	///
	LinearMemory(const LinearMemoryConfig& config)
		: address_(nullptr), page_count_(0), config_(config) {
		config_.verify();
		address_ = reserve(config.address, config.page_count_max);
		grow(config.page_count_min);
	}

	/// Bring up the memory subsystem with the default config.
	///
	LinearMemory() : LinearMemory(LinearMemoryConfig()) {}

	~LinearMemory() { release(address_, max_size()); }

	/// The address.
	///
	Address address() const noexcept { return address_; }

	/// The size of the currently allocated memory.
	///
	std::size_t size() const noexcept { return page_count_ * page_size(); }

	/// The maximum size the memory can grow to.
	///
	std::size_t max_size() const noexcept { return config_.page_count_max; }

	/// Grow the memory by n pages.
	///
	void grow(std::size_t n = 1) {
		// TODO: worry about overflow / underflow
		if (config_.page_count_max - page_count_ < n) {
			throw LinearMemoryError("Failed to grow, not enough reserved pages.");
		}
		// TODO: Activate new region
	}

	/// Shrink the memory by n pages.
	///
	void shrink(std::size_t n = 1) {
		if (page_count_ < n) {
			throw LinearMemoryError("Failed to shrink: not enough active pages.");
		}
		/// TODO: Deactivate region
	}

	const LinearMemoryConfig& config() const noexcept { return config_; }

private:
	Address reserve(const Address address, std::size_t n) { return Page::map(address, n); }

	void activate(const Address address, const std::size_t n) {
		// activate the memory region by requesting read/write permissions.
		auto permissions = PagePermission::READ | PagePermission::WRITE;
		Page::set_permissions(address, n * Page::size(), permissions);
	}

	void deactivate(const Address address, const std::size_t n) {
		// deactivate the memory region by disabling all permissions. this should hopefully
		// cause the OS to unmap the memory.
		auto permissions = PagePermission::NONE;
		Page::set_permissions(address, n * Page::size(), permissions);
	}

	void release(const Address address, const std::size_t n) {
		Page::unmap(address, n * Page::size());
	}

	Address address_;
	std::size_t page_count_;
	const LinearMemoryConfig config_;
};

}  // namespace Ab

#endif  // AB_LINEARMEMORY_HPP_
<|endoftext|>


<commit_before><commit_msg>pdn: use extents of channels instead of rects to avoid splitting channels into different rects<commit_after><|endoftext|>


<commit_before>// [WriteFile Name=EditFeatureAttachments, Category=EditData]
// [Legal]
// Copyright 2016 Esri.

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// [Legal]

#include "EditFeatureAttachments.h"

#include "Map.h"
#include "MapQuickView.h"
#include "Basemap.h"
#include "Viewpoint.h"
#include "Point.h"
#include "SpatialReference.h"
#include "ServiceFeatureTable.h"
#include "FeatureLayer.h"
#include "Feature.h"
#include "ArcGISFeature.h"
#include "FeatureEditResult.h"
#include "FeatureQueryResult.h"
#include "AttachmentListModel.h"
#include <QUrl>
#include <QUuid>
#include <QSharedPointer>
#include <QMouseEvent>
#include <QFile>

using namespace Esri::ArcGISRuntime;

EditFeatureAttachments::EditFeatureAttachments(QQuickItem* parent) :
    QQuickItem(parent),
    m_map(nullptr),
    m_mapView(nullptr),
    m_featureLayer(nullptr),
    m_featureTable(nullptr),
    m_selectedFeature(nullptr),
    m_screenX(0),
    m_screenY(0),
    m_attachmentCount(0),
    m_featureType("")
{
}

EditFeatureAttachments::~EditFeatureAttachments()
{
}

void EditFeatureAttachments::componentComplete()
{
    QQuickItem::componentComplete();

    // find QML MapView component
    m_mapView = findChild<MapQuickView*>("mapView");
    m_mapView->setWrapAroundMode(WrapAroundMode::Disabled);

    // create a Viewpoint
    Viewpoint vp(Point(14902768,4050890,SpatialReference(3857)), 2e6);

    // create a Map by passing in the Basemap
    m_map = new Map(Basemap::streets(this), this);
    m_map->setInitialViewpoint(vp);

    // set map on the map view
    m_mapView->setMap(m_map);

    // create the ServiceFeatureTable
    m_featureTable = new ServiceFeatureTable(QUrl("http://sampleserver6.arcgisonline.com/arcgis/rest/services/DamageAssessment/FeatureServer/0"), this);

    // create the FeatureLayer with the ServiceFeatureTable and add it to the Map
    m_featureLayer = new FeatureLayer(m_featureTable, this);
    m_featureLayer->setSelectionWidth(3);
    m_map->operationalLayers()->append(m_featureLayer);

    connectSignals();
}

void EditFeatureAttachments::connectSignals()
{
    // connect to the mouse press release signal on the MapQuickView
    connect(m_mapView, &MapQuickView::mouseClick, this, [this](QMouseEvent& mouseEvent)
    {
        // first clear the selection
        m_featureLayer->clearSelection();

        // set the properties for qml
        m_screenX = mouseEvent.x();
        emit screenXChanged();
        m_screenY = mouseEvent.y();
        emit screenYChanged();
        emit hideWindow();

        // call identify on the map view
        m_mapView->identifyLayer(m_featureLayer, mouseEvent.x(), mouseEvent.y(), 5, 1);
    });

    // connect to the viewpoint changed signal on the MapQuickView
    connect(m_mapView, &MapQuickView::viewpointChanged, this, [this]()
    {
        m_featureLayer->clearSelection();
        emit hideWindow();
    });

    // connect to the identifyLayerCompleted signal on the map view
    connect(m_mapView, &MapQuickView::identifyLayerCompleted,
            this, [this](QUuid, IdentifyLayerResult* identifyResult)
    {
        if(!identifyResult)
          return;

        if (!identifyResult->geoElements().empty())
        {
            // select the item in the result
            m_featureLayer->selectFeature(static_cast<Feature*>(identifyResult->geoElements().at(0)));

            // obtain the selected feature with attributes
            QueryParameters queryParams;
            m_whereClause = "objectid=" + identifyResult->geoElements().at(0)->attributes()->attributeValue("objectid").toString();
            queryParams.setWhereClause(m_whereClause);
            m_featureTable->queryFeatures(queryParams);
        }
    });

    // connect to the queryFeaturesCompleted signal on the feature table
    connect(m_featureTable, &FeatureTable::queryFeaturesCompleted,
            this, [this](QUuid, QSharedPointer<FeatureQueryResult> featureQueryResult)
    {
        if (featureQueryResult->iterator().hasNext())
        {
            // first delete if not nullptr
            if (m_selectedFeature != nullptr)
                delete m_selectedFeature;

            // set selected feature and attachment model members
            m_selectedFeature = static_cast<ArcGISFeature*>(featureQueryResult->iterator().next(this));
            m_featureType = m_selectedFeature->attributes()->attributeValue("typdamage").toString();
            emit featureTypeChanged();
            emit featureSelected();
            emit attachmentModelChanged();

            // get the number of attachments
            connect(m_selectedFeature->attachments(), &AttachmentListModel::fetchAttachmentsCompleted,
                    this, [this](QUuid, const QList<QSharedPointer<Esri::ArcGISRuntime::Attachment>>)
            {
                m_attachmentCount = m_selectedFeature->attachments()->rowCount();
                emit attachmentCountChanged();
            });
        }
    });

    // connect to the applyEditsCompleted signal from the ServiceFeatureTable
    connect(m_featureTable, &ServiceFeatureTable::applyEditsCompleted,
            this, [this](QUuid, QList<QSharedPointer<FeatureEditResult>> featureEditResults)
    {
        if (featureEditResults.length() > 0)
        {
            // obtain the first item in the list
            QSharedPointer<FeatureEditResult> featureEditResult = featureEditResults.first();
            // check if there were errors
            if (!featureEditResult->isCompletedWithErrors())
            {
                qDebug() << "Successfully edited feature attachments";

                // update the selected feature with attributes
                QueryParameters queryParams;
                queryParams.setWhereClause(m_whereClause);
                m_featureTable->queryFeatures(queryParams);
            }
            else
            {
                qDebug() << "Apply edits error:" << featureEditResult->error().code() << featureEditResult->error().message();
            }
        }
    });
}

int EditFeatureAttachments::screenX() const
{
    return m_screenX;
}

int EditFeatureAttachments::screenY() const
{
    return m_screenY;
}

QString EditFeatureAttachments::featureType() const
{
    return m_featureType;
}

AttachmentListModel* EditFeatureAttachments::attachmentModel() const
{
    return m_selectedFeature ? m_selectedFeature->attachments() : nullptr;
}

void EditFeatureAttachments::addAttachment(QUrl fileUrl, QString contentType, QString fileName)
{
    if (QFile::exists(fileUrl.toLocalFile()))
    {
        disconnect(m_attachmentConnection); // disconnect previous connection if necessary

        if (m_selectedFeature->loadStatus() == LoadStatus::Loaded)
        {
            QFile file(fileUrl.toLocalFile());
            m_selectedFeature->attachments()->addAttachment(&file, contentType, fileName);
        }
        else
        {
            m_attachmentConnection = connect(m_selectedFeature, &ArcGISFeature::loadStatusChanged,
                    this, [this, fileUrl, contentType, fileName](Esri::ArcGISRuntime::LoadStatus)
            {
                if (m_selectedFeature->loadStatus() == LoadStatus::Loaded)
                {
                    QFile file(fileUrl.toLocalFile());
                    disconnect(m_attachmentConnection);
                    m_selectedFeature->attachments()->addAttachment(&file, contentType, fileName);
                }
            });
            m_selectedFeature->load();
        }
    }
}

void EditFeatureAttachments::deleteAttachment(int index)
{
    disconnect(m_attachmentConnection); // disconnect previous connection if necessary

    if (m_selectedFeature->loadStatus() == LoadStatus::Loaded)
    {
        m_selectedFeature->attachments()->deleteAttachment(index);
    }
    else
    {
        m_attachmentConnection = connect(m_selectedFeature, &ArcGISFeature::loadStatusChanged,
                this, [this, index](Esri::ArcGISRuntime::LoadStatus)
        {
            if (m_selectedFeature->loadStatus() == LoadStatus::Loaded)
            {
                disconnect(m_attachmentConnection);
                m_selectedFeature->attachments()->deleteAttachment(index);
            }
        });
        m_selectedFeature->load();
    }
}

int EditFeatureAttachments::attachmentCount() const
{
    return m_attachmentCount;
}
<commit_msg>Use the same logic as the matching Qml sample.<commit_after>// [WriteFile Name=EditFeatureAttachments, Category=EditData]
// [Legal]
// Copyright 2016 Esri.

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// [Legal]

#include "EditFeatureAttachments.h"

#include "Map.h"
#include "MapQuickView.h"
#include "Basemap.h"
#include "Viewpoint.h"
#include "Point.h"
#include "SpatialReference.h"
#include "ServiceFeatureTable.h"
#include "FeatureLayer.h"
#include "Feature.h"
#include "ArcGISFeature.h"
#include "FeatureEditResult.h"
#include "FeatureQueryResult.h"
#include "AttachmentListModel.h"
#include <QUrl>
#include <QUuid>
#include <QSharedPointer>
#include <QMouseEvent>
#include <QFile>

using namespace Esri::ArcGISRuntime;

EditFeatureAttachments::EditFeatureAttachments(QQuickItem* parent) :
    QQuickItem(parent),
    m_map(nullptr),
    m_mapView(nullptr),
    m_featureLayer(nullptr),
    m_featureTable(nullptr),
    m_selectedFeature(nullptr),
    m_screenX(0),
    m_screenY(0),
    m_attachmentCount(0),
    m_featureType("")
{
}

EditFeatureAttachments::~EditFeatureAttachments()
{
}

void EditFeatureAttachments::componentComplete()
{
    QQuickItem::componentComplete();

    // find QML MapView component
    m_mapView = findChild<MapQuickView*>("mapView");
    m_mapView->setWrapAroundMode(WrapAroundMode::Disabled);

    // create a Viewpoint
    Viewpoint vp(Point(14902768,4050890,SpatialReference(3857)), 2e6);

    // create a Map by passing in the Basemap
    m_map = new Map(Basemap::streets(this), this);
    m_map->setInitialViewpoint(vp);

    // set map on the map view
    m_mapView->setMap(m_map);

    // create the ServiceFeatureTable
    m_featureTable = new ServiceFeatureTable(QUrl("http://sampleserver6.arcgisonline.com/arcgis/rest/services/DamageAssessment/FeatureServer/0"), this);

    // create the FeatureLayer with the ServiceFeatureTable and add it to the Map
    m_featureLayer = new FeatureLayer(m_featureTable, this);
    m_featureLayer->setSelectionWidth(3);
    m_map->operationalLayers()->append(m_featureLayer);

    connectSignals();
}

void EditFeatureAttachments::connectSignals()
{
    // connect to the mouse press release signal on the MapQuickView
    connect(m_mapView, &MapQuickView::mouseClick, this, [this](QMouseEvent& mouseEvent)
    {
        // first clear the selection
        m_featureLayer->clearSelection();

        // set the properties for qml
        m_screenX = mouseEvent.x();
        emit screenXChanged();
        m_screenY = mouseEvent.y();
        emit screenYChanged();
        emit hideWindow();

        // call identify on the map view
        m_mapView->identifyLayer(m_featureLayer, mouseEvent.x(), mouseEvent.y(), 5, 1);
    });

    // connect to the viewpoint changed signal on the MapQuickView
    connect(m_mapView, &MapQuickView::viewpointChanged, this, [this]()
    {
        m_featureLayer->clearSelection();
        emit hideWindow();
    });

    // connect to the identifyLayerCompleted signal on the map view
    connect(m_mapView, &MapQuickView::identifyLayerCompleted,
            this, [this](QUuid, IdentifyLayerResult* identifyResult)
    {
        if(!identifyResult)
          return;

        if (!identifyResult->geoElements().empty())
        {
            // select the item in the result
            m_featureLayer->selectFeature(static_cast<Feature*>(identifyResult->geoElements().at(0)));

            // obtain the selected feature with attributes
            QueryParameters queryParams;
            m_whereClause = "objectid=" + identifyResult->geoElements().at(0)->attributes()->attributeValue("objectid").toString();
            queryParams.setWhereClause(m_whereClause);
            m_featureLayer->selectFeatures(queryParams, SelectionMode::New);
        }
    });

    // connect to the queryFeaturesCompleted signal on the feature table
    connect(m_featureLayer, &FeatureLayer::selectFeaturesCompleted,
            this, [this](QUuid, QSharedPointer<FeatureQueryResult> featureQueryResult)
    {
        if (featureQueryResult->iterator().hasNext())
        {
            // first delete if not nullptr
            if (m_selectedFeature != nullptr)
                delete m_selectedFeature;

            // set selected feature and attachment model members
            m_selectedFeature = static_cast<ArcGISFeature*>(featureQueryResult->iterator().next(this));
            m_featureType = m_selectedFeature->attributes()->attributeValue("typdamage").toString();
            emit featureTypeChanged();
            emit featureSelected();
            emit attachmentModelChanged();

            // get the number of attachments
            connect(m_selectedFeature->attachments(), &AttachmentListModel::fetchAttachmentsCompleted,
                    this, [this](QUuid, const QList<QSharedPointer<Esri::ArcGISRuntime::Attachment>>)
            {
                m_attachmentCount = m_selectedFeature->attachments()->rowCount();
                emit attachmentCountChanged();
            });
        }
    });

    // connect to the applyEditsCompleted signal from the ServiceFeatureTable
    connect(m_featureTable, &ServiceFeatureTable::applyEditsCompleted,
            this, [this](QUuid, QList<QSharedPointer<FeatureEditResult>> featureEditResults)
    {
        if (featureEditResults.length() > 0)
        {
            // obtain the first item in the list
            QSharedPointer<FeatureEditResult> featureEditResult = featureEditResults.first();
            // check if there were errors
            if (!featureEditResult->isCompletedWithErrors())
            {
                qDebug() << "Successfully edited feature attachments";

                // update the selected feature with attributes
                QueryParameters queryParams;
                queryParams.setWhereClause(m_whereClause);
                m_featureTable->queryFeatures(queryParams);
            }
            else
            {
                qDebug() << "Apply edits error:" << featureEditResult->error().code() << featureEditResult->error().message();
            }
        }
    });
}

int EditFeatureAttachments::screenX() const
{
    return m_screenX;
}

int EditFeatureAttachments::screenY() const
{
    return m_screenY;
}

QString EditFeatureAttachments::featureType() const
{
    return m_featureType;
}

AttachmentListModel* EditFeatureAttachments::attachmentModel() const
{
    return m_selectedFeature ? m_selectedFeature->attachments() : nullptr;
}

void EditFeatureAttachments::addAttachment(QUrl fileUrl, QString contentType, QString fileName)
{
    if (QFile::exists(fileUrl.toLocalFile()))
    {
        disconnect(m_attachmentConnection); // disconnect previous connection if necessary

        if (m_selectedFeature->loadStatus() == LoadStatus::Loaded)
        {
            QFile file(fileUrl.toLocalFile());
            m_selectedFeature->attachments()->addAttachment(&file, contentType, fileName);
        }
        else
        {
            m_attachmentConnection = connect(m_selectedFeature, &ArcGISFeature::loadStatusChanged,
                    this, [this, fileUrl, contentType, fileName](Esri::ArcGISRuntime::LoadStatus)
            {
                if (m_selectedFeature->loadStatus() == LoadStatus::Loaded)
                {
                    QFile file(fileUrl.toLocalFile());
                    disconnect(m_attachmentConnection);
                    m_selectedFeature->attachments()->addAttachment(&file, contentType, fileName);
                }
            });
            m_selectedFeature->load();
        }
    }
}

void EditFeatureAttachments::deleteAttachment(int index)
{
    disconnect(m_attachmentConnection); // disconnect previous connection if necessary

    if (m_selectedFeature->loadStatus() == LoadStatus::Loaded)
    {
        m_selectedFeature->attachments()->deleteAttachment(index);
    }
    else
    {
        m_attachmentConnection = connect(m_selectedFeature, &ArcGISFeature::loadStatusChanged,
                this, [this, index](Esri::ArcGISRuntime::LoadStatus)
        {
            if (m_selectedFeature->loadStatus() == LoadStatus::Loaded)
            {
                disconnect(m_attachmentConnection);
                m_selectedFeature->attachments()->deleteAttachment(index);
            }
        });
        m_selectedFeature->load();
    }
}

int EditFeatureAttachments::attachmentCount() const
{
    return m_attachmentCount;
}
<|endoftext|>