Skip to content
Snippets Groups Projects
ProjectRecoveryThread.cpp 5.34 KiB
Newer Older
#include "ProjectRecoveryThread.h"
#include "ApplicationWindow.h"
#include "Folder.h"
#include "ProjectSerialiser.h"
#include "MantidAPI/FileProperty.h"
#include "MantidKernel/ConfigService.h"
#include "MantidKernel/Logger.h"

#include "Poco/NObserver.h"
#include "Poco/Path.h"
#include "qmetaobject.h"
#include <condition_variable>
#include <mutex>
#include <string>
#include <thread>

namespace {
Mantid::Kernel::Logger g_log("Project Recovery Thread");
const std::chrono::seconds TIME_BETWEEN_SAVING = std::chrono::seconds(30);
const std::string SAVING_ENABLED_CONFIG_KEY = "projectRecovery.enabled";

bool isRecoveryEnabled() {
  std::string isEnabled;
  int valueIsGood = Mantid::Kernel::ConfigService::Instance().getValue<std::string>(
      SAVING_ENABLED_CONFIG_KEY, isEnabled);
  
  return (valueIsGood == 1) && isEnabled.find("true") != std::string::npos;
}

std::string getOutputPath() {
  static bool isInitalised = false;
  static std::string recoverFolder;

  if (!isInitalised) {
    recoverFolder = Mantid::Kernel::ConfigService::Instance().getAppDataDir();
    recoverFolder.append("/recovery");
    isInitalised = true;
  }

  return recoverFolder;
std::string getOutputProjectName() { return "recovery.project"; }

} // namespace

namespace MantidQt {
namespace API {

ProjectRecoveryThread::ProjectRecoveryThread(ApplicationWindow *windowHandle)
    : m_backgroundSavingThread(), m_stopBackgroundThread(true),
      m_configKeyObserver(*this, &ProjectRecoveryThread::configKeyChanged),
      m_windowPtr(windowHandle) {

  if (isRecoveryEnabled()) {
    startProjectSaving();
  }
ProjectRecoveryThread::~ProjectRecoveryThread() { stopProjectSaving(); }

std::thread ProjectRecoveryThread::createBackgroundThread() {
  // Using a lambda helps the compiler deduce the this pointer
  // otherwise the resolution is ambiguous
  return std::thread([this] { projectSavingThreadWrapper(); });
}

void ProjectRecoveryThread::configKeyChanged(
    Mantid::Kernel::ConfigValChangeNotification_ptr notif) {
  if (notif->key() != (SAVING_ENABLED_CONFIG_KEY)) {
    return;
  }

  if (notif->curValue() == "True") {
    startProjectSaving();
  } else {
    stopProjectSaving();
  }
}

void ProjectRecoveryThread::startProjectSaving() {
  // Close the existing thread first
  stopProjectSaving();
  // Spin up a new thread
  {
    std::lock_guard<std::mutex> lock(m_notifierMutex);
    m_stopBackgroundThread = false;
  }
  m_backgroundSavingThread = createBackgroundThread();
}

void ProjectRecoveryThread::stopProjectSaving() {
  {
    std::lock_guard<std::mutex> lock(m_notifierMutex);
    m_stopBackgroundThread = true;
    m_threadNotifier.notify_all();
  }

  if (m_backgroundSavingThread.joinable()) {
    m_backgroundSavingThread.join();
  }
void ProjectRecoveryThread::projectSavingThreadWrapper() {
  try {
    projectSavingThread();
  } catch (std::exception const &e) {
    std::string preamble("Project recovery has stopped. Please report"
                         " this to the development team.\nException:\n");
    g_log.warning(preamble + e.what());
  } catch (...) {
    g_log.warning("Project recovery has stopped. Please report"
                  " this to the development team.");
  }
}

void ProjectRecoveryThread::projectSavingThread() {
  while (!m_stopBackgroundThread) {
    std::unique_lock<std::mutex> lock(m_notifierMutex);
    // The condition variable releases the lock until the var changes
    if (m_threadNotifier.wait_for(lock, TIME_BETWEEN_SAVING, [this]() {
          return m_stopBackgroundThread;
        })) {
      // Exit thread
      g_log.information("Project Recovery: Stopping background saving thread");
      return;
    }

    g_log.information("Project Recovery: Saving started");
    // "Timeout" - Save out again
    // Generate output paths
    const auto basePath = getOutputPath();
    auto projectFile = Poco::Path(basePath).append(getOutputProjectName());

    // Python's OS module cannot handle Poco's parsed paths
    // so use std::string instead and let OS parse the '/' char on Win
    saveWsHistories(basePath);
    saveOpenWindows(projectFile.toString());
    g_log.information("Project Recovery: Saving finished");
  }
void ProjectRecoveryThread::saveOpenWindows(
    const std::string &projectDestFile) {
  if (!QMetaObject::invokeMethod(m_windowPtr, "saveProjectRecovery",
                                 Qt::QueuedConnection,
                                 Q_ARG(const std::string, projectDestFile))) {
    g_log.warning(
        "Project Recovery: Failed to save project windows - Qt binding failed");
  }
void ProjectRecoveryThread::saveWsHistories(
    const std::string &historyDestFolder) {
  QString projectSavingCode =
      "from mantid.simpleapi import write_all_workspaces_histories\n"
      "write_all_workspaces_histories(\"" +
      QString::fromStdString(historyDestFolder) + "\")\n";

  m_windowPtr->runPythonScript(projectSavingCode);
void ProjectRecoveryThread::loadOpenWindows(const std::string &projectFolder) {
  const bool isRecovery = true;
  ProjectSerialiser projectWriter(m_windowPtr, isRecovery);
  // Use this version of Mantid as the current version field - as recovery
  // across major versions is not an intended use case
  const int fileVersion = 100 * maj_version + 10 * min_version + patch_version;
  projectWriter.load(projectFolder, fileVersion);
} // namespace API