<commit_before>#include "siprouting.h"

#include "settingskeys.h"

#include "common.h"
#include "logger.h"

#include <QSettings>

SIPRouting::SIPRouting(std::shared_ptr<TCPConnection> connection):
  connection_(connection),
  contactAddress_(""),
  contactPort_(0),
  first_(true)
{}

void SIPRouting::processOutgoingRequest(SIPRequest& request, QVariant& content)
{
  Q_UNUSED(content)

  Logger::getLogger()->printNormal(this, "Processing outgoing request");

  if (connection_ == nullptr)
  {
    Logger::getLogger()->printProgramError(this, "No connection set!");
    return;
  }

  // TODO: Handle this better
  if (!connection_->isConnected())
  {
    Logger::getLogger()->printWarning(this, "Socket not connected");
    return;
  }

  if (request.method != SIP_CANCEL)
  {
    addVia(request.method, request.message,
           connection_->localAddress(),
           connection_->localPort());
  }
  else
  {
    request.message->vias.push_front(previousVia_);
  }

  if (request.method == SIP_INVITE || request.method == SIP_REGISTER)
  {
    addContactField(request.message,
                    connection_->localAddress(),
                    connection_->localPort(),
                    DEFAULT_SIP_TYPE);

    if (request.method == SIP_REGISTER)
    {
      addREGISTERContactParameters(request.message);
    }
  }

  addGruuToSupported(request.message);

  emit outgoingRequest(request, content);

}

void SIPRouting::processOutgoingResponse(SIPResponse& response, QVariant& content)
{
  Q_UNUSED(content)

  Logger::getLogger()->printNormal(this, "Processing outgoing response");

  // TODO: Handle this better
  if (!connection_->isConnected())
  {
    Logger::getLogger()->printWarning(this, "Socket not connected");
    return;
  }

  if (response.message->cSeq.method == SIP_INVITE && response.type == SIP_OK)
  {
    addContactField(response.message,
                      connection_->localAddress(),
                      connection_->localPort(),
                      DEFAULT_SIP_TYPE);
  }

  addGruuToSupported(response.message);

  emit outgoingResponse(response, content);
}

void SIPRouting::processIncomingResponse(SIPResponse& response, QVariant& content)
{
  Q_UNUSED(content)

  if (connection_ && connection_->isConnected())
  {
    processResponseViaFields(response.message->vias,
                             connection_->localAddress(),
                             connection_->localPort());
  }
  else
  {
    Logger::getLogger()->printError(this, "Not connected when checking response via field");
  }

  if (response.type == SIP_OK && response.message->cSeq.method == SIP_REGISTER)
  {
    getGruus(response.message);
  }

  emit incomingResponse(response, content);
}

void SIPRouting::processResponseViaFields(QList<ViaField>& vias,
                                          QString localAddress,
                                          uint16_t localPort)
{
  // find the via with our address and port

  for (ViaField& via : vias)
  {
    if (via.sentBy == localAddress && via.port == localPort)
    {
      Logger::getLogger()->printNormal(this, "Found our via. This is meant for us!");

      if (via.rportValue != 0 && via.receivedAddress != "")
      {
        Logger::getLogger()->printNormal(this, "Found our received address and rport",
                                         {"Address"}, {via.receivedAddress + ":" + QString::number(via.rportValue)});

        // we do not update our address, because we want to remove this registration
        // first before updating.
        if (first_)
        {
          first_ = false;
        }
        else
        {
          contactAddress_ = via.receivedAddress;
          contactPort_ = via.rportValue;
        }
      }
      return;
    }
  }
}

void SIPRouting::addVia(SIPRequestMethod type,
                        std::shared_ptr<SIPMessageHeader> message,
                        QString localAddress,
                        uint16_t localPort)
{
  ViaField via = ViaField{SIP_VERSION, DEFAULT_TRANSPORT, localAddress, localPort,
      QString(MAGIC_COOKIE + generateRandomString(BRANCH_TAIL_LENGTH)),
      false, false, 0, "", {}};

  message->vias.push_back(via);

  if (type == SIP_INVITE || type == SIP_ACK)
  {
    message->vias.back().rport = true;
  }

  if (type == SIP_REGISTER)
  {
    message->vias.back().rport = true;
    message->vias.back().alias = true;
  }

  previousVia_ = message->vias.back();
}

void SIPRouting::addContactField(std::shared_ptr<SIPMessageHeader> message,
                                 QString localAddress, uint16_t localPort,
                                 SIPType type)
{
  message->contact.push_back({{"", SIP_URI{type, {getLocalUsername(), ""}, {"", 0}, {}, {}}}, {}});

  if (pubGruu_ != "")
  {
    message->contact.back().address.uri.hostport.host = pubGruu_;
  }
  else if (tempGruu_ != "")
  {
    message->contact.back().address.uri.hostport.host = tempGruu_;
  }
  // use rport address and port if we have them
  else if (contactAddress_ != "" && contactPort_ != 0)
  {
    message->contact.back().address.uri.hostport.host = contactAddress_;
    message->contact.back().address.uri.hostport.port = contactPort_;
  }
  else // otherwise use localaddress
  {
    message->contact.back().address.uri.hostport.host = localAddress;
    message->contact.back().address.uri.hostport.port = localPort;
  }

}

void SIPRouting::addREGISTERContactParameters(std::shared_ptr<SIPMessageHeader> message)
{
  if (message->contact.empty())
  {
    Logger::getLogger()->printProgramWarning(this,
                                             "Please add contact field before adding parameters");
  }

  QSettings settings("kvazzup.ini", QSettings::IniFormat);
  message->contact.back().parameters.push_back({"reg-id", "1"});
  message->contact.back().parameters.push_back({"+sip.instance", "\"<urn:uuid:" +
                                                settings.value(SettingsKey::sipUUID).toString() + ">\""});
}

void SIPRouting::addGruuToSupported(std::shared_ptr<SIPMessageHeader> message)
{
  if (message->supported == nullptr)
  {
    message->supported = std::shared_ptr<QStringList>(new QStringList);
  }

  message->supported->append("gruu");
}

void SIPRouting::getGruus(std::shared_ptr<SIPMessageHeader> message)
{
  QString tempGruu = "";
  QString pubGruu = "";

  for (auto& contact : message->contact)
  {
    for (int i = 0; i < contact.parameters.size() && tempGruu == ""; ++i)
    {
      // TODO: Check that pub-gruu does not change
      if (contact.parameters.at(i).name == "pub-gruu")
      {
        pubGruu = contact.parameters.at(i).value;
        Logger::getLogger()->printNormal(this, "Found public GRUU", "pub-gruu", pubGruu);
      }
    }

    for (int i = 0; i < contact.parameters.size() && tempGruu == ""; ++i)
    {
      if (contact.parameters.at(i).name == "temp-gruu")
      {
        tempGruu = contact.parameters.at(i).value;
        Logger::getLogger()->printNormal(this, "Found temporary GRUU", "temp-gruu", tempGruu);
      }
    }
  }

  tempGruu_ = tempGruu;
  pubGruu_ = pubGruu;
}
<commit_msg>fix(Transport): Parse GRUU to contact field correctly<commit_after>#include "siprouting.h"

#include "settingskeys.h"

#include "common.h"
#include "logger.h"
#include "sipfieldparsinghelper.h" // we need to parse GRUU URI

#include <QSettings>

SIPRouting::SIPRouting(std::shared_ptr<TCPConnection> connection):
  connection_(connection),
  contactAddress_(""),
  contactPort_(0),
  first_(true)
{}

void SIPRouting::processOutgoingRequest(SIPRequest& request, QVariant& content)
{
  Q_UNUSED(content)

  Logger::getLogger()->printNormal(this, "Processing outgoing request");

  if (connection_ == nullptr)
  {
    Logger::getLogger()->printProgramError(this, "No connection set!");
    return;
  }

  // TODO: Handle this better
  if (!connection_->isConnected())
  {
    Logger::getLogger()->printWarning(this, "Socket not connected");
    return;
  }

  if (request.method != SIP_CANCEL)
  {
    addVia(request.method, request.message,
           connection_->localAddress(),
           connection_->localPort());
  }
  else
  {
    request.message->vias.push_front(previousVia_);
  }

  if (request.method == SIP_INVITE || request.method == SIP_REGISTER)
  {
    addContactField(request.message,
                    connection_->localAddress(),
                    connection_->localPort(),
                    DEFAULT_SIP_TYPE);

    if (request.method == SIP_REGISTER)
    {
      addREGISTERContactParameters(request.message);
    }
  }

  addGruuToSupported(request.message);

  emit outgoingRequest(request, content);

}

void SIPRouting::processOutgoingResponse(SIPResponse& response, QVariant& content)
{
  Q_UNUSED(content)

  Logger::getLogger()->printNormal(this, "Processing outgoing response");

  // TODO: Handle this better
  if (!connection_->isConnected())
  {
    Logger::getLogger()->printWarning(this, "Socket not connected");
    return;
  }

  if (response.message->cSeq.method == SIP_INVITE && response.type == SIP_OK)
  {
    addContactField(response.message,
                      connection_->localAddress(),
                      connection_->localPort(),
                      DEFAULT_SIP_TYPE);
  }

  addGruuToSupported(response.message);

  emit outgoingResponse(response, content);
}

void SIPRouting::processIncomingResponse(SIPResponse& response, QVariant& content)
{
  Q_UNUSED(content)

  if (connection_ && connection_->isConnected())
  {
    processResponseViaFields(response.message->vias,
                             connection_->localAddress(),
                             connection_->localPort());
  }
  else
  {
    Logger::getLogger()->printError(this, "Not connected when checking response via field");
  }

  if (response.type == SIP_OK && response.message->cSeq.method == SIP_REGISTER)
  {
    getGruus(response.message);
  }

  emit incomingResponse(response, content);
}

void SIPRouting::processResponseViaFields(QList<ViaField>& vias,
                                          QString localAddress,
                                          uint16_t localPort)
{
  // find the via with our address and port

  for (ViaField& via : vias)
  {
    if (via.sentBy == localAddress && via.port == localPort)
    {
      Logger::getLogger()->printNormal(this, "Found our via. This is meant for us!");

      if (via.rportValue != 0 && via.receivedAddress != "")
      {
        Logger::getLogger()->printNormal(this, "Found our received address and rport",
                                         {"Address"}, {via.receivedAddress + ":" + QString::number(via.rportValue)});

        // we do not update our address, because we want to remove this registration
        // first before updating.
        if (first_)
        {
          first_ = false;
        }
        else
        {
          contactAddress_ = via.receivedAddress;
          contactPort_ = via.rportValue;
        }
      }
      return;
    }
  }
}

void SIPRouting::addVia(SIPRequestMethod type,
                        std::shared_ptr<SIPMessageHeader> message,
                        QString localAddress,
                        uint16_t localPort)
{
  ViaField via = ViaField{SIP_VERSION, DEFAULT_TRANSPORT, localAddress, localPort,
      QString(MAGIC_COOKIE + generateRandomString(BRANCH_TAIL_LENGTH)),
      false, false, 0, "", {}};

  message->vias.push_back(via);

  if (type == SIP_INVITE || type == SIP_ACK)
  {
    message->vias.back().rport = true;
  }

  if (type == SIP_REGISTER)
  {
    message->vias.back().rport = true;
    message->vias.back().alias = true;
  }

  previousVia_ = message->vias.back();
}

void SIPRouting::addContactField(std::shared_ptr<SIPMessageHeader> message,
                                 QString localAddress, uint16_t localPort,
                                 SIPType type)
{
  message->contact.push_back({{"", SIP_URI{type, {getLocalUsername(), ""}, {"", 0}, {}, {}}}, {}});

  // use public GRUU if we have it, otherwise use temporary GRUU or rport if we have those
  if ((pubGruu_ == "" || !parseURI(pubGruu_, message->contact.back().address.uri)) &&
      (tempGruu_ == "" || !parseURI(tempGruu_, message->contact.back().address.uri)) &&
      (contactAddress_ != "" && contactPort_ != 0))
  {
    // use rport address and port if we have them
    message->contact.back().address.uri.hostport.host = contactAddress_;
    message->contact.back().address.uri.hostport.port = contactPort_;
  }
  else // otherwise use localaddress
  {
    message->contact.back().address.uri.hostport.host = localAddress;
    message->contact.back().address.uri.hostport.port = localPort;
  }
}

void SIPRouting::addREGISTERContactParameters(std::shared_ptr<SIPMessageHeader> message)
{
  if (message->contact.empty())
  {
    Logger::getLogger()->printProgramWarning(this,
                                             "Please add contact field before adding parameters");
  }

  QSettings settings("kvazzup.ini", QSettings::IniFormat);
  message->contact.back().parameters.push_back({"reg-id", "1"});
  message->contact.back().parameters.push_back({"+sip.instance", "\"<urn:uuid:" +
                                                settings.value(SettingsKey::sipUUID).toString() + ">\""});
}

void SIPRouting::addGruuToSupported(std::shared_ptr<SIPMessageHeader> message)
{
  if (message->supported == nullptr)
  {
    message->supported = std::shared_ptr<QStringList>(new QStringList);
  }

  message->supported->append("gruu");
}

void SIPRouting::getGruus(std::shared_ptr<SIPMessageHeader> message)
{
  QString tempGruu = "";
  QString pubGruu = "";

  for (auto& contact : message->contact)
  {
    for (int i = 0; i < contact.parameters.size() && tempGruu == ""; ++i)
    {
      // TODO: Check that pub-gruu does not change
      if (contact.parameters.at(i).name == "pub-gruu")
      {
        pubGruu = contact.parameters.at(i).value;
        Logger::getLogger()->printNormal(this, "Found public GRUU", "pub-gruu", pubGruu);
      }
    }

    for (int i = 0; i < contact.parameters.size() && tempGruu == ""; ++i)
    {
      if (contact.parameters.at(i).name == "temp-gruu")
      {
        tempGruu = contact.parameters.at(i).value;
        Logger::getLogger()->printNormal(this, "Found temporary GRUU", "temp-gruu", tempGruu);
      }
    }
  }

  tempGruu_ = tempGruu;
  pubGruu_ = pubGruu;
}
<|endoftext|>


<commit_before>#include "seqgraph.h"

SeqGraph::SeqGraph (const Profile& prof, const string& alphabet, const vguard<LogProb>& logInsProb, double minPostProb) {
  const LogProb minLogPostProb = log (minPostProb);
  vguard<vguard<NodeIndex> > stateNodes (prof.size());
  vguard<LogProb> lp (logInsProb.size());
  for (ProfileStateIndex s = 0; s < prof.size(); ++s)
    if (prof.state[s].isNull()) {
      stateNodes[s].push_back (node.size());
      node.push_back (Node());
    } else {
      const LogProb lpNorm = logInnerProduct (logInsProb, prof.state[s].lpAbsorb);
      AlphTok iMax = 0;
      for (AlphTok i = 0; i < logInsProb.size(); ++i) {
	lp[i] = logInsProb[i] + prof.state[s].lpAbsorb[i] - lpNorm;
	if (i == 0 || lp[i] > lp[iMax])
	  iMax = i;
      }
      for (AlphTok i = 0; i < logInsProb.size(); ++i)
	if (i == iMax || lp[i] > minLogPostProb) {
	  stateNodes[s].push_back (node.size());
	  node.push_back (Node());
	  node.back().seq += alphabet[i];
	}
    }
  for (const auto& trans : prof.trans)
    for (auto s : stateNodes[trans.src])
      for (auto d : stateNodes[trans.dest])
	edge.push_back (Edge (s, d));
}

void SeqGraph::writeDot (ostream& out) const {
  out << "digraph profile {" << endl;
  for (NodeIndex n = 0; n < nodes(); ++n)
    out << "  n" << n+1 << " [ label = \"" << node[n].seq << "\" ];" << endl;
  for (EdgeIndex e = 0; e < edges(); ++e)
    out << "  n" << edge[e].src+1 << " -> n" << edge[e].dest+1 << ";" << endl;
  out << "}" << endl;
}
<commit_msg>Track incoming & outcoming edges in sequence graphs<commit_after>#include "seqgraph.h"

SeqGraph::SeqGraph (const Profile& prof, const string& alphabet, const vguard<LogProb>& logInsProb, double minPostProb) {
  const LogProb minLogPostProb = log (minPostProb);
  vguard<vguard<NodeIndex> > stateNodes (prof.size());
  vguard<LogProb> lp (logInsProb.size());
  for (ProfileStateIndex s = 0; s < prof.size(); ++s)
    if (prof.state[s].isNull()) {
      stateNodes[s].push_back (node.size());
      node.push_back (Node());
    } else {
      const LogProb lpNorm = logInnerProduct (logInsProb, prof.state[s].lpAbsorb);
      AlphTok iMax = 0;
      for (AlphTok i = 0; i < logInsProb.size(); ++i) {
	lp[i] = logInsProb[i] + prof.state[s].lpAbsorb[i] - lpNorm;
	if (i == 0 || lp[i] > lp[iMax])
	  iMax = i;
      }
      for (AlphTok i = 0; i < logInsProb.size(); ++i)
	if (i == iMax || lp[i] > minLogPostProb) {
	  stateNodes[s].push_back (node.size());
	  node.push_back (Node());
	  node.back().seq += alphabet[i];
	}
    }
  for (const auto& trans : prof.trans)
    for (auto s : stateNodes[trans.src])
      for (auto d : stateNodes[trans.dest]) {
	node[s].out.push_back (edge.size());
	node[d].in.push_back (edge.size());
	edge.push_back (Edge (s, d));
      }
}

void SeqGraph::writeDot (ostream& out) const {
  out << "digraph profile {" << endl;
  for (NodeIndex n = 0; n < nodes(); ++n)
    out << "  n" << n+1 << " [ label = \"" << node[n].seq << "\" ];" << endl;
  for (EdgeIndex e = 0; e < edges(); ++e)
    out << "  n" << edge[e].src+1 << " -> n" << edge[e].dest+1 << ";" << endl;
  out << "}" << endl;
}
<|endoftext|>


<commit_before>
#include "test_suite.h"

int main(int argc, char **argv) {
  bool run_benchmark_all = false;
  bool run_test = false;
  bool run_benchmark_bwtree = false;
  bool run_benchmark_bwtree_full = false;
  bool run_stress = false;
  bool run_epoch_test = false;
  bool run_infinite_insert_test = false;
  bool run_email_test = false;
  bool run_mixed_test = false;

  int opt_index = 1;
  while(opt_index < argc) {
    char *opt_p = argv[opt_index];

    if(strcmp(opt_p, "--benchmark-all") == 0) {
      run_benchmark_all = true;
    } else if(strcmp(opt_p, "--test") == 0) {
      run_test = true;
    } else if(strcmp(opt_p, "--benchmark-bwtree") == 0) {
      run_benchmark_bwtree = true;
    } else if(strcmp(opt_p, "--benchmark-bwtree-full") == 0) {
      run_benchmark_bwtree_full = true;
    } else if(strcmp(opt_p, "--stress-test") == 0) {
      run_stress = true;
    } else if(strcmp(opt_p, "--epoch-test") == 0) {
      run_epoch_test = true;
    } else if(strcmp(opt_p, "--infinite-insert-test") == 0) {
      run_infinite_insert_test = true;
    } else if(strcmp(opt_p, "--email-test") == 0) {
      run_email_test = true;
    } else if(strcmp(opt_p, "--mixed-test") == 0) {
      run_mixed_test = true;
    } else {
      printf("ERROR: Unknown option: %s\n", opt_p);

      return 0;
    }

    opt_index++;
  }

  bwt_printf("RUN_BENCHMARK_ALL = %d\n", run_benchmark_all);
  bwt_printf("RUN_BENCHMARK_BWTREE = %d\n", run_benchmark_bwtree);
  bwt_printf("RUN_BENCHMARK_BWTREE_FULL = %d\n", run_benchmark_bwtree_full);
  bwt_printf("RUN_TEST = %d\n", run_test);
  bwt_printf("RUN_STRESS = %d\n", run_stress);
  bwt_printf("RUN_EPOCH_TEST = %d\n", run_epoch_test);
  bwt_printf("RUN_INFINITE_INSERT_TEST = %d\n", run_infinite_insert_test);
  bwt_printf("RUN_EMAIL_TEST = %d\n", run_email_test);
  bwt_printf("RUN_MIXED_TEST = %d\n", run_mixed_test);
  bwt_printf("======================================\n");

  //////////////////////////////////////////////////////
  // Next start running test cases
  //////////////////////////////////////////////////////

  TreeType *t1 = nullptr;

  if(run_mixed_test == true) {
    print_flag = true;
    t1 = new TreeType{KeyComparator{1},
                      KeyEqualityChecker{1}};
    print_flag = false;

    printf("Starting mixed testing...\n");
    LaunchParallelTestID(mixed_thread_num, MixedTest1, t1);
    printf("Finished mixed testing\n");

    PrintStat(t1);

    MixedGetValueTest(t1);

    print_flag = true;
    delete t1;
    print_flag = false;
  }

  if(run_email_test == true) {
    print_flag = true;
    auto t2 = new BwTree<std::string, long int>{};
    print_flag = false;

    TestBwTreeEmailInsertPerformance(t2, "emails_dump.txt");

    // t2 has already been deleted for memory reason
  }

  if(run_infinite_insert_test == true) {
    print_flag = true;
    t1 = new TreeType{KeyComparator{1},
                      KeyEqualityChecker{1}};
    print_flag = false;

    InfiniteRandomInsertTest(t1);

    print_flag = true;
    delete t1;
    print_flag = false;
  }

  if(run_epoch_test == true) {
    print_flag = true;
    t1 = new TreeType{KeyComparator{1},
                      KeyEqualityChecker{1}};
    print_flag = false;

    TestEpochManager(t1);

    print_flag = true;
    delete t1;
    print_flag = false;
  }

  if(run_benchmark_bwtree == true ||
     run_benchmark_bwtree_full == true) {
    print_flag = true;
    t1 = new TreeType{KeyComparator{1},
                      KeyEqualityChecker{1}};

    int key_num = 3 * 1024 * 1024;

    if(run_benchmark_bwtree_full == true) {
      key_num *= 10;
    }

    bwt_printf("Using key size = %d (%f million)\n",
               key_num,
               key_num / (1024.0 * 1024.0));

    print_flag = false;

    if(run_benchmark_bwtree_full == true) {
      // First we rely on this test to fill bwtree with 30 million keys
      TestBwTreeInsertReadPerformance(t1, key_num);

      // And then do a multithreaded read
      TestBwTreeMultiThreadReadPerformance(t1, key_num);
    } else {
      // This function will delete all keys at the end, so the tree
      // is empty after it returns
      TestBwTreeInsertReadDeletePerformance(t1, key_num);

      delete t1;
      t1 = new TreeType{KeyComparator{1},
                        KeyEqualityChecker{1}};

      // Tests random insert using one thread
      RandomInsertSpeedTest(t1, key_num);

      delete t1;
      t1 = new TreeType{KeyComparator{1},
                        KeyEqualityChecker{1}};

      // Test random insert seq read
      RandomInsertSeqReadSpeedTest(t1, key_num);

      delete t1;
      t1 = new TreeType{KeyComparator{1},
                        KeyEqualityChecker{1}};

      // Test seq insert random read
      SeqInsertRandomReadSpeedTest(t1, key_num);

      // Use stree_multimap as a reference
      RandomBtreeMultimapInsertSpeedTest(key_num);
    }

    print_flag = true;
    delete t1;
    print_flag = false;
  }

  if(run_benchmark_all == true) {
    print_flag = true;
    t1 = new TreeType{KeyComparator{1},
                      KeyEqualityChecker{1}};

    int key_num = 1024 * 1024;
    bwt_printf("Using key size = %d (%f million)\n",
               key_num,
               key_num / (1024.0 * 1024.0));

    print_flag = false;

    TestStdMapInsertReadPerformance();
    TestStdUnorderedMapInsertReadPerformance();
    TestBTreeInsertReadPerformance();
    TestBTreeMultimapInsertReadPerformance();
    TestBwTreeInsertReadPerformance(t1, key_num);

    print_flag = true;
    delete t1;
    print_flag = false;
  }

  if(run_test == true) {
    print_flag = true;
    t1 = new TreeType{KeyComparator{1},
                      KeyEqualityChecker{1}};
    print_flag = false;

    //////////////
    // Test iterator
    //////////////

    printf("Testing iterator...\n");

    IteratorTest(t1);
    PrintStat(t1);

    printf("Finised tetsing iterator\n");

    //////////////////////
    // Test random insert
    //////////////////////

    printf("Testing random insert...\n");

    // Do not print here otherwise we could not see result
    delete t1;
    t1 = new TreeType{KeyComparator{1},
                      KeyEqualityChecker{1}};

    LaunchParallelTestID(8, RandomInsertTest, t1);
    RandomInsertVerify(t1);

    printf("Finished random insert testing. Delete the tree.\n");

    delete t1;
    t1 = new TreeType{KeyComparator{1},
                      KeyEqualityChecker{1}};

    ////////////////////////////
    // Test mixed insert/delete
    ////////////////////////////

    LaunchParallelTestID(basic_test_thread_num, MixedTest1, t1);
    printf("Finished mixed testing\n");

    PrintStat(t1);

    MixedGetValueTest(t1);

    LaunchParallelTestID(basic_test_thread_num, InsertTest2, t1);
    printf("Finished inserting all keys\n");

    PrintStat(t1);

    InsertGetValueTest(t1);
    printf("Finished verifying all inserted values\n");

    LaunchParallelTestID(basic_test_thread_num, DeleteTest1, t1);
    printf("Finished deleting all keys\n");

    PrintStat(t1);

    DeleteGetValueTest(t1);
    printf("Finished verifying all deleted values\n");

    LaunchParallelTestID(basic_test_thread_num, InsertTest1, t1);
    printf("Finished inserting all keys\n");

    PrintStat(t1);

    InsertGetValueTest(t1);
    printf("Finished verifying all inserted values\n");

    LaunchParallelTestID(basic_test_thread_num, DeleteTest2, t1);
    printf("Finished deleting all keys\n");

    PrintStat(t1);

    DeleteGetValueTest(t1);
    printf("Finished verifying all deleted values\n");

    LaunchParallelTestID(basic_test_thread_num, InsertTest1, t1);
    printf("Finished inserting all keys\n");

    PrintStat(t1);

    InsertGetValueTest(t1);
    printf("Finished verifying all inserted values\n");

    LaunchParallelTestID(basic_test_thread_num, DeleteTest1, t1);
    printf("Finished deleting all keys\n");

    PrintStat(t1);

    DeleteGetValueTest(t1);
    printf("Finished verifying all deleted values\n");

    LaunchParallelTestID(basic_test_thread_num, InsertTest2, t1);
    printf("Finished inserting all keys\n");

    PrintStat(t1);

    InsertGetValueTest(t1);
    printf("Finished verifying all inserted values\n");

    LaunchParallelTestID(basic_test_thread_num, DeleteTest2, t1);
    printf("Finished deleting all keys\n");

    PrintStat(t1);

    DeleteGetValueTest(t1);
    printf("Finished verifying all deleted values\n");

    print_flag = true;
    delete t1;
    print_flag = false;
  }

  if(run_stress == true) {
    print_flag = true;
    t1 = new TreeType{KeyComparator{1},
                      KeyEqualityChecker{1}};
    print_flag = false;

    LaunchParallelTestID(8, StressTest, t1);

    print_flag = true;
    delete t1;
    print_flag = false;
  }

  return 0;
}

<commit_msg>Fix a type in main.cpp<commit_after>
#include "test_suite.h"

int main(int argc, char **argv) {
  bool run_benchmark_all = false;
  bool run_test = false;
  bool run_benchmark_bwtree = false;
  bool run_benchmark_bwtree_full = false;
  bool run_stress = false;
  bool run_epoch_test = false;
  bool run_infinite_insert_test = false;
  bool run_email_test = false;
  bool run_mixed_test = false;

  int opt_index = 1;
  while(opt_index < argc) {
    char *opt_p = argv[opt_index];

    if(strcmp(opt_p, "--benchmark-all") == 0) {
      run_benchmark_all = true;
    } else if(strcmp(opt_p, "--test") == 0) {
      run_test = true;
    } else if(strcmp(opt_p, "--benchmark-bwtree") == 0) {
      run_benchmark_bwtree = true;
    } else if(strcmp(opt_p, "--benchmark-bwtree-full") == 0) {
      run_benchmark_bwtree_full = true;
    } else if(strcmp(opt_p, "--stress-test") == 0) {
      run_stress = true;
    } else if(strcmp(opt_p, "--epoch-test") == 0) {
      run_epoch_test = true;
    } else if(strcmp(opt_p, "--infinite-insert-test") == 0) {
      run_infinite_insert_test = true;
    } else if(strcmp(opt_p, "--email-test") == 0) {
      run_email_test = true;
    } else if(strcmp(opt_p, "--mixed-test") == 0) {
      run_mixed_test = true;
    } else {
      printf("ERROR: Unknown option: %s\n", opt_p);

      return 0;
    }

    opt_index++;
  }

  bwt_printf("RUN_BENCHMARK_ALL = %d\n", run_benchmark_all);
  bwt_printf("RUN_BENCHMARK_BWTREE = %d\n", run_benchmark_bwtree);
  bwt_printf("RUN_BENCHMARK_BWTREE_FULL = %d\n", run_benchmark_bwtree_full);
  bwt_printf("RUN_TEST = %d\n", run_test);
  bwt_printf("RUN_STRESS = %d\n", run_stress);
  bwt_printf("RUN_EPOCH_TEST = %d\n", run_epoch_test);
  bwt_printf("RUN_INFINITE_INSERT_TEST = %d\n", run_infinite_insert_test);
  bwt_printf("RUN_EMAIL_TEST = %d\n", run_email_test);
  bwt_printf("RUN_MIXED_TEST = %d\n", run_mixed_test);
  bwt_printf("======================================\n");

  //////////////////////////////////////////////////////
  // Next start running test cases
  //////////////////////////////////////////////////////

  TreeType *t1 = nullptr;

  if(run_mixed_test == true) {
    print_flag = true;
    t1 = new TreeType{KeyComparator{1},
                      KeyEqualityChecker{1}};
    print_flag = false;

    printf("Starting mixed testing...\n");
    LaunchParallelTestID(mixed_thread_num, MixedTest1, t1);
    printf("Finished mixed testing\n");

    PrintStat(t1);

    MixedGetValueTest(t1);

    print_flag = true;
    delete t1;
    print_flag = false;
  }

  if(run_email_test == true) {
    print_flag = true;
    auto t2 = new BwTree<std::string, long int>{};
    print_flag = false;

    TestBwTreeEmailInsertPerformance(t2, "emails_dump.txt");

    // t2 has already been deleted for memory reason
  }

  if(run_infinite_insert_test == true) {
    print_flag = true;
    t1 = new TreeType{KeyComparator{1},
                      KeyEqualityChecker{1}};
    print_flag = false;

    InfiniteRandomInsertTest(t1);

    print_flag = true;
    delete t1;
    print_flag = false;
  }

  if(run_epoch_test == true) {
    print_flag = true;
    t1 = new TreeType{KeyComparator{1},
                      KeyEqualityChecker{1}};
    print_flag = false;

    TestEpochManager(t1);

    print_flag = true;
    delete t1;
    print_flag = false;
  }

  if(run_benchmark_bwtree == true ||
     run_benchmark_bwtree_full == true) {
    print_flag = true;
    t1 = new TreeType{KeyComparator{1},
                      KeyEqualityChecker{1}};

    int key_num = 3 * 1024 * 1024;

    if(run_benchmark_bwtree_full == true) {
      key_num *= 10;
    }

    bwt_printf("Using key size = %d (%f million)\n",
               key_num,
               key_num / (1024.0 * 1024.0));

    print_flag = false;

    if(run_benchmark_bwtree_full == true) {
      // First we rely on this test to fill bwtree with 30 million keys
      TestBwTreeInsertReadPerformance(t1, key_num);

      // And then do a multithreaded read
      TestBwTreeMultiThreadReadPerformance(t1, key_num);
    } else {
      // This function will delete all keys at the end, so the tree
      // is empty after it returns
      TestBwTreeInsertReadDeletePerformance(t1, key_num);

      delete t1;
      t1 = new TreeType{KeyComparator{1},
                        KeyEqualityChecker{1}};

      // Tests random insert using one thread
      RandomInsertSpeedTest(t1, key_num);

      delete t1;
      t1 = new TreeType{KeyComparator{1},
                        KeyEqualityChecker{1}};

      // Test random insert seq read
      RandomInsertSeqReadSpeedTest(t1, key_num);

      delete t1;
      t1 = new TreeType{KeyComparator{1},
                        KeyEqualityChecker{1}};

      // Test seq insert random read
      SeqInsertRandomReadSpeedTest(t1, key_num);

      // Use stree_multimap as a reference
      RandomBtreeMultimapInsertSpeedTest(key_num);
    }

    print_flag = true;
    delete t1;
    print_flag = false;
  }

  if(run_benchmark_all == true) {
    print_flag = true;
    t1 = new TreeType{KeyComparator{1},
                      KeyEqualityChecker{1}};

    int key_num = 1024 * 1024;
    bwt_printf("Using key size = %d (%f million)\n",
               key_num,
               key_num / (1024.0 * 1024.0));

    print_flag = false;

    TestStdMapInsertReadPerformance();
    TestStdUnorderedMapInsertReadPerformance();
    TestBTreeInsertReadPerformance();
    TestBTreeMultimapInsertReadPerformance();
    TestBwTreeInsertReadPerformance(t1, key_num);

    print_flag = true;
    delete t1;
    print_flag = false;
  }

  if(run_test == true) {
    print_flag = true;
    t1 = new TreeType{KeyComparator{1},
                      KeyEqualityChecker{1}};
    print_flag = false;

    //////////////
    // Test iterator
    //////////////

    printf("Testing iterator...\n");

    IteratorTest(t1);
    PrintStat(t1);

    printf("Finised testing iterator\n");

    //////////////////////
    // Test random insert
    //////////////////////

    printf("Testing random insert...\n");

    // Do not print here otherwise we could not see result
    delete t1;
    t1 = new TreeType{KeyComparator{1},
                      KeyEqualityChecker{1}};

    LaunchParallelTestID(8, RandomInsertTest, t1);
    RandomInsertVerify(t1);

    printf("Finished random insert testing. Delete the tree.\n");

    delete t1;
    t1 = new TreeType{KeyComparator{1},
                      KeyEqualityChecker{1}};

    ////////////////////////////
    // Test mixed insert/delete
    ////////////////////////////

    LaunchParallelTestID(basic_test_thread_num, MixedTest1, t1);
    printf("Finished mixed testing\n");

    PrintStat(t1);

    MixedGetValueTest(t1);

    LaunchParallelTestID(basic_test_thread_num, InsertTest2, t1);
    printf("Finished inserting all keys\n");

    PrintStat(t1);

    InsertGetValueTest(t1);
    printf("Finished verifying all inserted values\n");

    LaunchParallelTestID(basic_test_thread_num, DeleteTest1, t1);
    printf("Finished deleting all keys\n");

    PrintStat(t1);

    DeleteGetValueTest(t1);
    printf("Finished verifying all deleted values\n");

    LaunchParallelTestID(basic_test_thread_num, InsertTest1, t1);
    printf("Finished inserting all keys\n");

    PrintStat(t1);

    InsertGetValueTest(t1);
    printf("Finished verifying all inserted values\n");

    LaunchParallelTestID(basic_test_thread_num, DeleteTest2, t1);
    printf("Finished deleting all keys\n");

    PrintStat(t1);

    DeleteGetValueTest(t1);
    printf("Finished verifying all deleted values\n");

    LaunchParallelTestID(basic_test_thread_num, InsertTest1, t1);
    printf("Finished inserting all keys\n");

    PrintStat(t1);

    InsertGetValueTest(t1);
    printf("Finished verifying all inserted values\n");

    LaunchParallelTestID(basic_test_thread_num, DeleteTest1, t1);
    printf("Finished deleting all keys\n");

    PrintStat(t1);

    DeleteGetValueTest(t1);
    printf("Finished verifying all deleted values\n");

    LaunchParallelTestID(basic_test_thread_num, InsertTest2, t1);
    printf("Finished inserting all keys\n");

    PrintStat(t1);

    InsertGetValueTest(t1);
    printf("Finished verifying all inserted values\n");

    LaunchParallelTestID(basic_test_thread_num, DeleteTest2, t1);
    printf("Finished deleting all keys\n");

    PrintStat(t1);

    DeleteGetValueTest(t1);
    printf("Finished verifying all deleted values\n");

    print_flag = true;
    delete t1;
    print_flag = false;
  }

  if(run_stress == true) {
    print_flag = true;
    t1 = new TreeType{KeyComparator{1},
                      KeyEqualityChecker{1}};
    print_flag = false;

    LaunchParallelTestID(8, StressTest, t1);

    print_flag = true;
    delete t1;
    print_flag = false;
  }

  return 0;
}

<|endoftext|>


<commit_before>#include "config.h"
#include "global.h"
#include "pangene.h"
#include <algorithm>
#include <cstdio>
#include <fstream>
#include <functional>
#include <iostream>
#include <random>
#include <string>
#include <vector>

extern "C" {
#include <err.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
}

auto create_distribution()
{
	auto distribution = std::vector<double>(NUM_GENOMES + 1);

	auto tail = std::pow(1 - PROBABILITY, NUM_GENOMES);
	auto factor = 1 / (1 - tail);
	std::cout << "factor\t" << factor << "\n";

	for (size_t i = 1; i < NUM_GENOMES + 1; i++) {
		distribution[i] =
			distribution[i - 1] +
			PROBABILITY * std::pow(1 - PROBABILITY, i - 1) * factor;
	}

	return distribution;
}

auto genome_name(size_t genome_number)
{
	auto ret = std::string("genome");
	auto num = std::to_string(genome_number);
	auto max_digits = std::ceil(std::log10(NUM_GENOMES + 1));
	auto zeros = std::string(max_digits - num.size(), '0');

	ret += zeros + num;

	return ret;
}

void mkpath(const std::string &path)
{
	const auto flags = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
	int check = ::mkdir(path.c_str(), flags);
	if (check == 0 || errno == EEXIST) {
		return; // success
	}
	if (errno != ENOENT) {
		err(errno, "%s", path.c_str());
	}
	auto parent = path.substr(0, path.find_last_of('/'));
	mkpath(parent);
	check = ::mkdir(path.c_str(), flags);
	if (check) {
		err(errno, "%s", path.c_str());
	}
}

void simulate()
{
	// setup RNG
	RNG = std::default_random_engine(SEED);
	auto rng_float_help = std::uniform_real_distribution<double>(0, 1);
	auto rng_float = bind(rng_float_help, RNG);

	// setup distribution of pan genome
	auto distribution = create_distribution();
	auto genome_numbers = std::vector<size_t>(NUM_GENOMES);
	std::iota(genome_numbers.begin(), genome_numbers.end(), 0);

	if (VERBOSE) {
		size_t i = 0;
		for (auto d : distribution) {
			std::cout << i++ << "\t" << d << "\n";
		}
	}

	// simulate pan genomes
	auto pan_genes = std::vector<pan_gene>();
	for (size_t ng = 0; ng < NUM_GENES; ng++) {
		auto random_float = rng_float();
		if (VERBOSE) {
			std::cout << random_float << "\n";
		}
		auto it = std::lower_bound(distribution.begin(), distribution.end(),
								   random_float);

		// FIXME: clean up next line
		auto pan_gene_size =
			NUM_GENOMES + 1 - std::distance(distribution.begin(), it);
		if (VERBOSE) {
			std::cout << pan_gene_size << "\n";
		}

		std::shuffle(genome_numbers.begin(), genome_numbers.end(), RNG);

		auto sub_set = std::vector<size_t>(
			genome_numbers.begin(), genome_numbers.begin() + pan_gene_size);

		// simulate genes
		pan_genes.emplace_back(sub_set);
	}

	// create output directory
	auto check =
		mkdir(OUT_DIR.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
	if (check && errno != EEXIST) {
		err(errno, "%s", OUT_DIR.c_str());
	}

	auto check_io = [](const std::ofstream &o, const std::string &n) {
		if (!o) {
			err(errno, "%s: couldn't write to file", n.c_str());
		}
	};

	{ // print reproducible information
		auto rep_file_name = OUT_DIR + "reproducible.seed";
		auto rep_file = std::ofstream(rep_file_name);
		check_io(rep_file, rep_file_name);

		rep_file << "pangenomesim " VERSION << "\n\n";
		rep_file << "full options:\n";
		rep_file << "--evo-distance=" << EVO_DISTANCE << "\n";
		rep_file << "--gene-length=" << GENE_LENGTH << "\n";
		rep_file << "--num-genes=" << NUM_GENES << "\n";
		rep_file << "--num-genomes=" << NUM_GENOMES << "\n";
		rep_file << "--out-dir=" << OUT_DIR << "\n";
		rep_file << "--probability=" << PROBABILITY << "\n";
		rep_file << "--seed=" << SEED << "\n";
		rep_file << "--threshold=" << THRESHOLD << "\n";

		check_io(rep_file, rep_file_name);
	}

	// produce plenty of output
	std::sort(genome_numbers.begin(), genome_numbers.end());

	std::cout << "number of genomes:\t" << genome_numbers.size() << "\n";
	std::cout << "number of genes:\t" << pan_genes.size() << "\n";
	for (const auto &pg : pan_genes) {
		std::cout << "pg size:\t" << pg.size() << "\n";
	}

	{ // print reference, core-, and accessory-genome
		auto ref_file_name = OUT_DIR + "ref.fasta";
		auto cor_file_name = OUT_DIR + "core.fasta";
		auto acc_file_name = OUT_DIR + "accessory.fasta";
		auto ref_fasta_file = std::ofstream(ref_file_name);
		auto cor_fasta_file = std::ofstream(cor_file_name);
		auto acc_fasta_file = std::ofstream(acc_file_name);
		check_io(ref_fasta_file, ref_file_name);
		check_io(cor_fasta_file, cor_file_name);
		check_io(acc_fasta_file, acc_file_name);

		size_t chromosome = 0;
		for (const auto &pg : pan_genes) {
			ref_fasta_file << ">ref.chr" << ++chromosome << "\n"
						   << pg.reference() << std::endl;
			check_io(ref_fasta_file, ref_file_name);

			auto is_core = THRESHOLD * double(NUM_GENOMES) <= double(pg.size());
			auto &file = is_core ? cor_fasta_file : acc_fasta_file;
			auto &name = is_core ? cor_file_name : acc_file_name;

			file << ">ref.chr" << ++chromosome << "\n"
				 << pg.reference() << std::endl;
			check_io(file, name);
		}
	}

	auto genome_sizes = std::vector<size_t>(NUM_GENOMES);

	// print derived genomes
	for (auto gn : genome_numbers) {
		auto file_name = OUT_DIR + genome_name(gn + 1) + ".fasta";
		auto fasta_file = std::ofstream(file_name);
		check_io(fasta_file, file_name);

		size_t chromosome = 0;
		for (const auto &pg : pan_genes) {
			auto idx = pg.find_genome_number(gn);
			if (idx == pg.size()) {
				continue; // not found
			}

			fasta_file << ">" << genome_name(gn + 1) << ".chr" << ++chromosome
					   << "\n"
					   << pg.derived(idx) << std::endl;
			check_io(fasta_file, file_name);
		}

		genome_sizes[gn] = chromosome * GENE_LENGTH;
	}

	{ // print MAF alignment
		auto reference_counter = 0;
		auto chromosome_counters = std::vector<size_t>(NUM_GENOMES);
		auto maf_file_name = OUT_DIR + "alignment.maf";
		auto maf_file = std::ofstream(maf_file_name);
		check_io(maf_file, maf_file_name);

		maf_file << "##maf version=1\n";
		check_io(maf_file, maf_file_name);

		/* MAF format
		 *
		 * a
		 * s hg16.chr17    876234      13    +         6787  acgtagc
		 * s mm.chr12      872432      13    -         2345  TGCATAA
		 * s <name>.<chr>  <start> <size> <strand> <srcSize>  <seq>
		 */

		for (const auto &pg : pan_genes) {
			auto src_size = NUM_GENES * GENE_LENGTH;
			maf_file << "a\n";
			maf_file << "s ref.chr" << reference_counter << "\t" //
					 << reference_counter * GENE_LENGTH			 //
					 << "\t" << GENE_LENGTH						 //
					 << "\t" << src_size						 //
					 << "\t" << pg.reference() << std::endl;

			reference_counter++;

			auto size = pg.size();
			for (size_t k = 0; k < size; k++) {
				auto gn = pg.genome_number(k);
				maf_file << "s " << genome_name(gn + 1) << ".chr"
						 << (chromosome_counters[gn] + 1)				  //
						 << "\t" << chromosome_counters[gn] * GENE_LENGTH //
						 << "\t" << GENE_LENGTH							  //
						 << "\t" << genome_sizes[gn]					  //
						 << "\t" << pg.reference() << std::endl;

				chromosome_counters[gn]++;
			}

			maf_file << std::endl;
			check_io(maf_file, maf_file_name);
		}
	}
}<commit_msg>build outdir recursively<commit_after>#include "config.h"
#include "global.h"
#include "pangene.h"
#include <algorithm>
#include <cstdio>
#include <fstream>
#include <functional>
#include <iostream>
#include <random>
#include <string>
#include <vector>

extern "C" {
#include <err.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
}

auto create_distribution()
{
	auto distribution = std::vector<double>(NUM_GENOMES + 1);

	auto tail = std::pow(1 - PROBABILITY, NUM_GENOMES);
	auto factor = 1 / (1 - tail);
	std::cout << "factor\t" << factor << "\n";

	for (size_t i = 1; i < NUM_GENOMES + 1; i++) {
		distribution[i] =
			distribution[i - 1] +
			PROBABILITY * std::pow(1 - PROBABILITY, i - 1) * factor;
	}

	return distribution;
}

auto genome_name(size_t genome_number)
{
	auto ret = std::string("genome");
	auto num = std::to_string(genome_number);
	auto max_digits = std::ceil(std::log10(NUM_GENOMES + 1));
	auto zeros = std::string(max_digits - num.size(), '0');

	ret += zeros + num;

	return ret;
}

void mkpath(std::string path)
{
	const auto flags = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
	int check = ::mkdir(path.c_str(), flags);
	if (check == 0 || errno == EEXIST) {
		return; // success
	}
	if (errno != ENOENT) {
		err(errno, "%s", path.c_str());
	}

	auto parent = path.substr(0, path.find_last_of('/'));
	mkpath(parent);
	check = ::mkdir(path.c_str(), flags);
	if (check && errno != EEXIST) {
		err(errno, "%s", path.c_str());
	}
}

void simulate()
{
	// setup RNG
	RNG = std::default_random_engine(SEED);
	auto rng_float_help = std::uniform_real_distribution<double>(0, 1);
	auto rng_float = bind(rng_float_help, RNG);

	// setup distribution of pan genome
	auto distribution = create_distribution();
	auto genome_numbers = std::vector<size_t>(NUM_GENOMES);
	std::iota(genome_numbers.begin(), genome_numbers.end(), 0);

	if (VERBOSE) {
		size_t i = 0;
		for (auto d : distribution) {
			std::cout << i++ << "\t" << d << "\n";
		}
	}

	// simulate pan genomes
	auto pan_genes = std::vector<pan_gene>();
	for (size_t ng = 0; ng < NUM_GENES; ng++) {
		auto random_float = rng_float();
		if (VERBOSE) {
			std::cout << random_float << "\n";
		}
		auto it = std::lower_bound(distribution.begin(), distribution.end(),
								   random_float);

		// FIXME: clean up next line
		auto pan_gene_size =
			NUM_GENOMES + 1 - std::distance(distribution.begin(), it);
		if (VERBOSE) {
			std::cout << pan_gene_size << "\n";
		}

		std::shuffle(genome_numbers.begin(), genome_numbers.end(), RNG);

		auto sub_set = std::vector<size_t>(
			genome_numbers.begin(), genome_numbers.begin() + pan_gene_size);

		// simulate genes
		pan_genes.emplace_back(sub_set);
	}

	// create output directory
	mkpath(OUT_DIR);

	auto check_io = [](const std::ofstream &o, const std::string &n) {
		if (!o) {
			err(errno, "%s: couldn't write to file", n.c_str());
		}
	};

	{ // print reproducible information
		auto rep_file_name = OUT_DIR + "reproducible.seed";
		auto rep_file = std::ofstream(rep_file_name);
		check_io(rep_file, rep_file_name);

		rep_file << "pangenomesim " VERSION << "\n\n";
		rep_file << "full options:\n";
		rep_file << "--evo-distance=" << EVO_DISTANCE << "\n";
		rep_file << "--gene-length=" << GENE_LENGTH << "\n";
		rep_file << "--num-genes=" << NUM_GENES << "\n";
		rep_file << "--num-genomes=" << NUM_GENOMES << "\n";
		rep_file << "--out-dir=" << OUT_DIR << "\n";
		rep_file << "--probability=" << PROBABILITY << "\n";
		rep_file << "--seed=" << SEED << "\n";
		rep_file << "--threshold=" << THRESHOLD << "\n";

		check_io(rep_file, rep_file_name);
	}

	// produce plenty of output
	std::sort(genome_numbers.begin(), genome_numbers.end());

	std::cout << "number of genomes:\t" << genome_numbers.size() << "\n";
	std::cout << "number of genes:\t" << pan_genes.size() << "\n";
	for (const auto &pg : pan_genes) {
		std::cout << "pg size:\t" << pg.size() << "\n";
	}

	{ // print reference, core-, and accessory-genome
		auto ref_file_name = OUT_DIR + "ref.fasta";
		auto cor_file_name = OUT_DIR + "core.fasta";
		auto acc_file_name = OUT_DIR + "accessory.fasta";
		auto ref_fasta_file = std::ofstream(ref_file_name);
		auto cor_fasta_file = std::ofstream(cor_file_name);
		auto acc_fasta_file = std::ofstream(acc_file_name);
		check_io(ref_fasta_file, ref_file_name);
		check_io(cor_fasta_file, cor_file_name);
		check_io(acc_fasta_file, acc_file_name);

		size_t chromosome = 0;
		for (const auto &pg : pan_genes) {
			ref_fasta_file << ">ref.chr" << ++chromosome << "\n"
						   << pg.reference() << std::endl;
			check_io(ref_fasta_file, ref_file_name);

			auto is_core = THRESHOLD * double(NUM_GENOMES) <= double(pg.size());
			auto &file = is_core ? cor_fasta_file : acc_fasta_file;
			auto &name = is_core ? cor_file_name : acc_file_name;

			file << ">ref.chr" << ++chromosome << "\n"
				 << pg.reference() << std::endl;
			check_io(file, name);
		}
	}

	auto genome_sizes = std::vector<size_t>(NUM_GENOMES);

	// print derived genomes
	for (auto gn : genome_numbers) {
		auto file_name = OUT_DIR + genome_name(gn + 1) + ".fasta";
		auto fasta_file = std::ofstream(file_name);
		check_io(fasta_file, file_name);

		size_t chromosome = 0;
		for (const auto &pg : pan_genes) {
			auto idx = pg.find_genome_number(gn);
			if (idx == pg.size()) {
				continue; // not found
			}

			fasta_file << ">" << genome_name(gn + 1) << ".chr" << ++chromosome
					   << "\n"
					   << pg.derived(idx) << std::endl;
			check_io(fasta_file, file_name);
		}

		genome_sizes[gn] = chromosome * GENE_LENGTH;
	}

	{ // print MAF alignment
		auto reference_counter = 0;
		auto chromosome_counters = std::vector<size_t>(NUM_GENOMES);
		auto maf_file_name = OUT_DIR + "alignment.maf";
		auto maf_file = std::ofstream(maf_file_name);
		check_io(maf_file, maf_file_name);

		maf_file << "##maf version=1\n";
		check_io(maf_file, maf_file_name);

		/* MAF format
		 *
		 * a
		 * s hg16.chr17    876234      13    +         6787  acgtagc
		 * s mm.chr12      872432      13    -         2345  TGCATAA
		 * s <name>.<chr>  <start> <size> <strand> <srcSize>  <seq>
		 */

		for (const auto &pg : pan_genes) {
			auto src_size = NUM_GENES * GENE_LENGTH;
			maf_file << "a\n";
			maf_file << "s ref.chr" << reference_counter << "\t" //
					 << reference_counter * GENE_LENGTH			 //
					 << "\t" << GENE_LENGTH						 //
					 << "\t" << src_size						 //
					 << "\t" << pg.reference() << std::endl;

			reference_counter++;

			auto size = pg.size();
			for (size_t k = 0; k < size; k++) {
				auto gn = pg.genome_number(k);
				maf_file << "s " << genome_name(gn + 1) << ".chr"
						 << (chromosome_counters[gn] + 1)				  //
						 << "\t" << chromosome_counters[gn] * GENE_LENGTH //
						 << "\t" << GENE_LENGTH							  //
						 << "\t" << genome_sizes[gn]					  //
						 << "\t" << pg.reference() << std::endl;

				chromosome_counters[gn]++;
			}

			maf_file << std::endl;
			check_io(maf_file, maf_file_name);
		}
	}
}
<|endoftext|>


<commit_before>#include <SFML/Graphics.hpp>
#include <SFML/System.hpp>
#include <iostream>
#include <string>
#include <ctime>
#include <cstdlib>
#include "Maze.hpp"
#include "BitArray2D.hpp"
#include "BFS.hpp"

sf::RenderWindow window(sf::VideoMode(256, 256), "Maze");

const int msize = 16;
const int nsize = 16;
Maze<msize, nsize> maze;
Maze<msize, nsize> undoMaze;
Maze<msize, nsize> discoveredMaze;
BitArray2D<msize, nsize> unvisitedNodes;

Node cursor;
Node mark;
bool markSet = false;

NodeStack bfsPath;
bool showBfs = false;
bool runSim = false;
bool mapping = false;
sf::Clock clk;

void update();
void draw();
void bfs();
bool loadMaze(Maze<16, 16>& maze);
void saveMaze(Maze<16, 16> maze);

int coerce(int a, int l, int u)
{
    return a < l ? l : (a > u ? u: a);
}

int main()
{
    std::srand(std::time(0));

    // Load default maze
    maze.load("16:16:28802a48080a1a16645d54fd502a165999055c2e355b156fad1acd82a054:04ff96576e952e4bfc0ac88f804964aaac55848b4c06062a2a554cad4e9a");

    cursor.i = msize - 1;

    while (window.isOpen())
    {
        update();
        draw();

        sf::sleep(sf::milliseconds(10));
    }

    return 0;
}

void update()
{
    sf::Event event;

    // Handle window events
    while (window.pollEvent(event))
    {
        switch (event.type)
        {
        case sf::Event::Closed:
            // Needed for close window button to work
            window.close();
            break;

        case sf::Event::KeyPressed:
            // Handle controls
            switch (event.key.code)
            {

            // Cursor movement
            case sf::Keyboard::Key::Down:
                ++cursor.i;
                cursor.i = coerce(cursor.i, 0, msize - 1);
                break;
            case sf::Keyboard::Key::Up:
                --cursor.i;
                cursor.i = coerce(cursor.i, 0, msize - 1);
                break;
            case sf::Keyboard::Key::Right:
                ++cursor.j;
                cursor.j= coerce(cursor.j, 0, nsize - 1);
                break;
            case sf::Keyboard::Key::Left:
                --cursor.j;
                cursor.j= coerce(cursor.j, 0, nsize - 1);
                break;

            // Set/unset walls around cursor
            case sf::Keyboard::Key::S: // Bottom
                {
                    auto cw = maze.getCellWalls(cursor.i, cursor.j);
                    cw[0] = !cw[0];
                    maze.setCellWalls(cursor.i, cursor.j, cw);
                }
                break;
            case sf::Keyboard::Key::D: // Right
                {
                    auto cw = maze.getCellWalls(cursor.i, cursor.j);
                    cw[1] = !cw[1];
                    maze.setCellWalls(cursor.i, cursor.j, cw);
                }
                break;
            case sf::Keyboard::Key::W: // Top
                {
                    auto cw = maze.getCellWalls(cursor.i, cursor.j);
                    cw[2] = !cw[2];
                    maze.setCellWalls(cursor.i, cursor.j, cw);
                }
                break;
            case sf::Keyboard::Key::A: // Left
                {
                    auto cw = maze.getCellWalls(cursor.i, cursor.j);
                    cw[3] = !cw[3];
                    maze.setCellWalls(cursor.i, cursor.j, cw);
                }
                break;

            // Modify maze globally
            case sf::Keyboard::Key::C:
                undoMaze = maze;
                maze.clear();
                break;
            case sf::Keyboard::Key::F:
                undoMaze = maze;
                maze.fill();
                break;
            case sf::Keyboard::Key::R:
                undoMaze = maze;
                maze.randomize();
                break;

            // Undo
            case sf::Keyboard::Key::U:
                {
                    Maze<msize, nsize> tmp = maze;
                    maze = undoMaze;
                    undoMaze = tmp;
                }
                break;

            // Load/save maze
            case sf::Keyboard::Key::L:
                {
                    Maze<msize, nsize> tmp;
                    if (loadMaze(tmp))
                    {
                        undoMaze = maze;
                        maze = tmp;
                    }
                }
                break;
            case sf::Keyboard::Key::V:
                saveMaze(maze);
                break;

            // Place mark
            case sf::Keyboard::Key::Space:
                if (cursor == mark && markSet)
                {
                    markSet = false;
                }
                else
                {
                    mark = cursor;
                    markSet = true;
                }
                break;

            // Show BFS path
            case sf::Keyboard::Key::B:
                showBfs = !showBfs;
                break;

            // Run simulation
            case sf::Keyboard::Key::X:
                {
                    runSim = !runSim;
                    mapping = false;
                    clk.restart();
                    discoveredMaze.clear();
                    auto cw = maze.getCellWalls(cursor.i, cursor.j);
                    discoveredMaze.setCellWalls(cursor.i, cursor.j, cw);
                    bfs(discoveredMaze, cursor, mark, bfsPath);
                }
                break;

            // Map the maze
            case sf::Keyboard::Key::M:
                {
                    mapping = !mapping;
                    runSim = false;
                    clk.restart();
                    discoveredMaze.clear();
                    auto cw = maze.getCellWalls(cursor.i, cursor.j);
                    discoveredMaze.setCellWalls(cursor.i, cursor.j, cw);
                    unvisitedNodes.setAll(true);
                    unvisitedNodes.set(cursor.i, cursor.j, false);
                    bfs(discoveredMaze, cursor, unvisitedNodes, bfsPath);
                }
                break;

            default:
                break;
            }
            break;
        default:
            break;
        }
    }

    if (showBfs && markSet && !runSim)
    {
        // Run BFS for display using the entire maze
        bfs(maze, cursor, mark, bfsPath);
    }

    if ((runSim && markSet) || mapping)
    {
        // Cursor steps along the BFS path every so often
        if (clk.getElapsedTime().asSeconds() >= 0.5f)
        {
            clk.restart();

            if (bfsPath.size() > 0)
            {
                bfsPath.pop(); // First node is the current node; remove it
                cursor = bfsPath.pop(); // Set cursor position to next node
            }

            // Sense walls in current cell
            auto cw = maze.getCellWalls(cursor.i, cursor.j);
            discoveredMaze.setCellWalls(cursor.i, cursor.j, cw);

            // Run BFS using only the discovered parts of the maze
            if (mapping)
            {
                unvisitedNodes.set(cursor.i, cursor.j, false);
                bfs(discoveredMaze, cursor, unvisitedNodes, bfsPath);
            }
            else
            {
                bfs(discoveredMaze, cursor, mark, bfsPath);
            }

            // Stop when the goal is reached or unreachable
            if ((runSim && cursor == mark) || bfsPath.size() == 0)
            {
                runSim = false;
                mapping = false;
            }
        }
    }
}

void draw()
{
    window.clear();

    if (runSim)
    {
        // Undiscovered parts of the maze are show in gray
        maze.draw(window, 16.f, 2.f, sf::Color(255, 255, 255, 127));
        discoveredMaze.draw(window);
    }
    else if (mapping)
    {
        // Undiscovered parts of the maze are show in gray
        maze.draw(window, 16.f, 2.f, sf::Color(255, 255, 255, 127));
        discoveredMaze.draw(window);

        // Mark visited cells
        sf::CircleShape markshape(2.f);
        markshape.setFillColor(sf::Color::Cyan);
        for (int i = 0; i < msize; ++i)
        {
            for (int j = 0; j < nsize; ++j)
            {
                if (!unvisitedNodes.get(i, j))
                {
                    markshape.setPosition(j * 16.f + 6.f, i * 16.f + 6.f);
                    window.draw(markshape);
                }
            }
        }

    }
    else
    {
        // Draw maze normally
        maze.draw(window);
    }

    if (showBfs)
    {
        // Draw BFS path
        sf::VertexArray path(sf::LinesStrip, bfsPath.size());
        for (int i = 0; i < bfsPath.size(); ++i)
        {
            auto n = bfsPath[i];
            path[i].position = {n.j * 16.f + 8.f, n.i * 16.f + 8.f};
            path[i].color = sf::Color::Green;
        }
        window.draw(path);
    }

    if (markSet)
    {
        // Draw mark
        sf::CircleShape markshape(4.f);
        markshape.setPosition(mark.j * 16.f + 4.f, mark.i * 16.f + 4.f);
        markshape.setFillColor(sf::Color::Blue);
        window.draw(markshape);
    }

    // Draw cursor
    sf::CircleShape cursorshape(4.f);
    cursorshape.setPosition(cursor.j * 16.f + 4.f, cursor.i * 16.f + 4.f);
    cursorshape.setFillColor(sf::Color::Red);
    window.draw(cursorshape);

    window.display();
}

bool loadMaze(Maze<16, 16>& maze)
{
    std::cout << "Enter maze string:" << std::endl;
    std::string mazestr;
    std::getline(std::cin, mazestr);

    if (maze.load(mazestr))
    {
        std::cout << "Loaded maze" << std::endl;
        return true;
    }
    else
    {
        std::cout << "Failed to load maze" << std::endl;
        return false;
    }
}

void saveMaze(Maze<16, 16> maze)
{
    std::cout << "Maze saved:" << std::endl;
    std::cout << maze.save() << std::endl;
}
<commit_msg>Cells are considered visited if all cells around them have been visited<commit_after>#include <SFML/Graphics.hpp>
#include <SFML/System.hpp>
#include <iostream>
#include <string>
#include <ctime>
#include <cstdlib>
#include "Maze.hpp"
#include "BitArray2D.hpp"
#include "BFS.hpp"

sf::RenderWindow window(sf::VideoMode(256, 256), "Maze");

const int msize = 16;
const int nsize = 16;
Maze<msize, nsize> maze;
Maze<msize, nsize> undoMaze;
Maze<msize, nsize> discoveredMaze;
BitArray2D<msize, nsize> unvisitedNodes;
BitArray2D<msize, nsize> inferredNodes;

Node cursor;
Node mark;
bool markSet = false;

NodeStack bfsPath;
bool showBfs = false;
bool runSim = false;
bool mapping = false;
sf::Clock clk;

void update();
void draw();
void bfs();
bool loadMaze(Maze<16, 16>& maze);
void saveMaze(Maze<16, 16> maze);

int coerce(int a, int l, int u)
{
    return a < l ? l : (a > u ? u: a);
}

int main()
{
    std::srand(std::time(0));

    // Load default maze
    maze.load("16:16:28802a48080a1a16645d54fd502a165999055c2e355b156fad1acd82a054:04ff96576e952e4bfc0ac88f804964aaac55848b4c06062a2a554cad4e9a");

    cursor.i = msize - 1;

    while (window.isOpen())
    {
        update();
        draw();

        sf::sleep(sf::milliseconds(10));
    }

    return 0;
}

void update()
{
    sf::Event event;

    // Handle window events
    while (window.pollEvent(event))
    {
        switch (event.type)
        {
        case sf::Event::Closed:
            // Needed for close window button to work
            window.close();
            break;

        case sf::Event::KeyPressed:
            // Handle controls
            switch (event.key.code)
            {

            // Cursor movement
            case sf::Keyboard::Key::Down:
                ++cursor.i;
                cursor.i = coerce(cursor.i, 0, msize - 1);
                break;
            case sf::Keyboard::Key::Up:
                --cursor.i;
                cursor.i = coerce(cursor.i, 0, msize - 1);
                break;
            case sf::Keyboard::Key::Right:
                ++cursor.j;
                cursor.j= coerce(cursor.j, 0, nsize - 1);
                break;
            case sf::Keyboard::Key::Left:
                --cursor.j;
                cursor.j= coerce(cursor.j, 0, nsize - 1);
                break;

            // Set/unset walls around cursor
            case sf::Keyboard::Key::S: // Bottom
                {
                    auto cw = maze.getCellWalls(cursor.i, cursor.j);
                    cw[0] = !cw[0];
                    maze.setCellWalls(cursor.i, cursor.j, cw);
                }
                break;
            case sf::Keyboard::Key::D: // Right
                {
                    auto cw = maze.getCellWalls(cursor.i, cursor.j);
                    cw[1] = !cw[1];
                    maze.setCellWalls(cursor.i, cursor.j, cw);
                }
                break;
            case sf::Keyboard::Key::W: // Top
                {
                    auto cw = maze.getCellWalls(cursor.i, cursor.j);
                    cw[2] = !cw[2];
                    maze.setCellWalls(cursor.i, cursor.j, cw);
                }
                break;
            case sf::Keyboard::Key::A: // Left
                {
                    auto cw = maze.getCellWalls(cursor.i, cursor.j);
                    cw[3] = !cw[3];
                    maze.setCellWalls(cursor.i, cursor.j, cw);
                }
                break;

            // Modify maze globally
            case sf::Keyboard::Key::C:
                undoMaze = maze;
                maze.clear();
                break;
            case sf::Keyboard::Key::F:
                undoMaze = maze;
                maze.fill();
                break;
            case sf::Keyboard::Key::R:
                undoMaze = maze;
                maze.randomize();
                break;

            // Undo
            case sf::Keyboard::Key::U:
                {
                    Maze<msize, nsize> tmp = maze;
                    maze = undoMaze;
                    undoMaze = tmp;
                }
                break;

            // Load/save maze
            case sf::Keyboard::Key::L:
                {
                    Maze<msize, nsize> tmp;
                    if (loadMaze(tmp))
                    {
                        undoMaze = maze;
                        maze = tmp;
                    }
                }
                break;
            case sf::Keyboard::Key::V:
                saveMaze(maze);
                break;

            // Place mark
            case sf::Keyboard::Key::Space:
                if (cursor == mark && markSet)
                {
                    markSet = false;
                }
                else
                {
                    mark = cursor;
                    markSet = true;
                }
                break;

            // Show BFS path
            case sf::Keyboard::Key::B:
                showBfs = !showBfs;
                break;

            // Run simulation
            case sf::Keyboard::Key::X:
                {
                    runSim = !runSim;
                    mapping = false;
                    clk.restart();
                    discoveredMaze.clear();
                    auto cw = maze.getCellWalls(cursor.i, cursor.j);
                    discoveredMaze.setCellWalls(cursor.i, cursor.j, cw);
                    bfs(discoveredMaze, cursor, mark, bfsPath);
                }
                break;

            // Map the maze
            case sf::Keyboard::Key::M:
                {
                    mapping = !mapping;
                    runSim = false;
                    clk.restart();
                    discoveredMaze.clear();
                    auto cw = maze.getCellWalls(cursor.i, cursor.j);
                    discoveredMaze.setCellWalls(cursor.i, cursor.j, cw);
                    unvisitedNodes.setAll(true);
                    unvisitedNodes.set(cursor.i, cursor.j, false);
                    inferredNodes.setAll(false);
                    bfs(discoveredMaze, cursor, unvisitedNodes, bfsPath);
                }
                break;

            default:
                break;
            }
            break;
        default:
            break;
        }
    }

    if (showBfs && markSet && !runSim)
    {
        // Run BFS for display using the entire maze
        bfs(maze, cursor, mark, bfsPath);
    }

    if ((runSim && markSet) || mapping)
    {
        // Cursor steps along the BFS path every so often
        if (clk.getElapsedTime().asSeconds() >= 0.5f)
        {
            clk.restart();

            if (bfsPath.size() > 0)
            {
                bfsPath.pop(); // First node is the current node; remove it
                cursor = bfsPath.pop(); // Set cursor position to next node
            }

            // Sense walls in current cell
            auto cw = maze.getCellWalls(cursor.i, cursor.j);
            discoveredMaze.setCellWalls(cursor.i, cursor.j, cw);

            // Run BFS using only the discovered parts of the maze
            if (mapping)
            {
                unvisitedNodes.set(cursor.i, cursor.j, false);

                // Infer the contents of a node if all surrounding nodes have been visited
                for (int i = 0; i < msize; ++i)
                {
                    for (int j = 0; j < nsize; ++j)
                    {
                        if (!unvisitedNodes.get(i, j))
                            continue;

                        if ((i + 1 >= msize || !unvisitedNodes.get(i + 1, j)) &&
                            (j + 1 >= nsize || !unvisitedNodes.get(i, j + 1)) &&
                            (i - 1 < 0 || !unvisitedNodes.get(i - 1, j)) &&
                            (j - 1 < 0 || !unvisitedNodes.get(i, j - 1)))
                        {
                            unvisitedNodes.set(i, j, false);
                            inferredNodes.set(i, j, true);
                        }
                    }
                }

                bfs(discoveredMaze, cursor, unvisitedNodes, bfsPath);
            }
            else
            {
                bfs(discoveredMaze, cursor, mark, bfsPath);
            }

            // Stop when the goal is reached or unreachable
            if ((runSim && cursor == mark) || bfsPath.size() == 0)
            {
                runSim = false;
                mapping = false;
            }
        }
    }
}

void draw()
{
    window.clear();

    if (runSim)
    {
        // Undiscovered parts of the maze are show in gray
        maze.draw(window, 16.f, 2.f, sf::Color(255, 255, 255, 127));
        discoveredMaze.draw(window);
    }
    else if (mapping)
    {
        // Undiscovered parts of the maze are show in gray
        maze.draw(window, 16.f, 2.f, sf::Color(255, 255, 255, 127));
        discoveredMaze.draw(window);

        // Mark visited cells
        sf::CircleShape visitedshape(2.f);
        sf::CircleShape inferredshape(2.f);
        visitedshape.setFillColor(sf::Color::Cyan);
        inferredshape.setFillColor(sf::Color::Magenta);
        for (int i = 0; i < msize; ++i)
        {
            for (int j = 0; j < nsize; ++j)
            {
                if (!unvisitedNodes.get(i, j))
                {
                    visitedshape.setPosition(j * 16.f + 6.f, i * 16.f + 6.f);
                    window.draw(visitedshape);
                }

                if (inferredNodes.get(i, j))
                {
                    inferredshape.setPosition(j * 16.f + 6.f, i * 16.f + 6.f);
                    window.draw(inferredshape);
                }
            }
        }

    }
    else
    {
        // Draw maze normally
        maze.draw(window);
    }

    if (showBfs)
    {
        // Draw BFS path
        sf::VertexArray path(sf::LinesStrip, bfsPath.size());
        for (int i = 0; i < bfsPath.size(); ++i)
        {
            auto n = bfsPath[i];
            path[i].position = {n.j * 16.f + 8.f, n.i * 16.f + 8.f};
            path[i].color = sf::Color::Green;
        }
        window.draw(path);
    }

    if (markSet)
    {
        // Draw mark
        sf::CircleShape markshape(4.f);
        markshape.setPosition(mark.j * 16.f + 4.f, mark.i * 16.f + 4.f);
        markshape.setFillColor(sf::Color::Blue);
        window.draw(markshape);
    }

    // Draw cursor
    sf::CircleShape cursorshape(4.f);
    cursorshape.setPosition(cursor.j * 16.f + 4.f, cursor.i * 16.f + 4.f);
    cursorshape.setFillColor(sf::Color::Red);
    window.draw(cursorshape);

    window.display();
}

bool loadMaze(Maze<16, 16>& maze)
{
    std::cout << "Enter maze string:" << std::endl;
    std::string mazestr;
    std::getline(std::cin, mazestr);

    if (maze.load(mazestr))
    {
        std::cout << "Loaded maze" << std::endl;
        return true;
    }
    else
    {
        std::cout << "Failed to load maze" << std::endl;
        return false;
    }
}

void saveMaze(Maze<16, 16> maze)
{
    std::cout << "Maze saved:" << std::endl;
    std::cout << maze.save() << std::endl;
}
<|endoftext|>


<commit_before>/*
 * Skaffari - a mail account administration web interface based on Cutelyst
 * Copyright (C) 2017 Matthias Fehring <kontakt@buschmann23.de>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "skaffari.h"

#include <Cutelyst/Application>
#include <Cutelyst/Plugins/StaticSimple/StaticSimple>
#include <Cutelyst/Plugins/View/Grantlee/grantleeview.h>
#include <Cutelyst/Plugins/Session/Session>
#include <Cutelyst/Plugins/Authentication/authentication.h>
#include <Cutelyst/Plugins/Authentication/credentialpassword.h>
#include <Cutelyst/Plugins/Authentication/authenticationrealm.h>
#include <Cutelyst/Plugins/Utils/Sql>
#include <Cutelyst/Plugins/StatusMessage>
#include <Cutelyst/Plugins/Memcached/Memcached>
#include <Cutelyst/Plugins/MemcachedSessionStore/MemcachedSessionStore>
#include <Cutelyst/Engine>
#include <Cutelyst/Plugins/CSRFProtection/CSRFProtection>
#include <Cutelyst/Plugins/Utils/LangSelect>

#include <grantlee5/grantlee/metatype.h>
#include <grantlee5/grantlee/engine.h>

#include <QSqlDatabase>
#include <QSqlError>
#include <QDir>
#include <QMetaType>
#include <QCoreApplication>
#include <QTranslator>
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLoggingCategory>
#include <QMutexLocker>

extern "C"
{
#ifdef WITH_SYSTEMD
#define SD_JOURNAL_SUPPRESS_LOCATION
#include <systemd/sd-journal.h>
#endif

#include <syslog.h>
}

#include "objects/domain.h"
#include "objects/simpleadmin.h"
#include "objects/simpledomain.h"
#include "objects/simpleaccount.h"
#include "objects/adminaccount.h"
#include "objects/account.h"
#include "objects/folder.h"
#include "objects/helpentry.h"
#include "objects/emailaddress.h"
#include "objects/skaffarierror.h"

#include "utils/language.h"
#include "utils/skaffariconfig.h"
#include "utils/qtimezonevariant_p.h"

#include "../common/config.h"
#include "../common/global.h"
#include "root.h"
#include "authstoresql.h"
#include "login.h"
#include "logout.h"
#include "domaineditor.h"
#include "accounteditor.h"
#include "admineditor.h"
#include "myaccount.h"
#include "settingseditor.h"

Q_LOGGING_CATEGORY(SK_CORE, "skaffari.core")

using namespace Cutelyst;

static QMutex mutex;

bool Skaffari::isInitialized = false;
bool Skaffari::messageHandlerInstalled = false;

#ifdef WITH_SYSTEMD
void journaldMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    int prio = LOG_INFO;
    switch (type) {
    case QtDebugMsg:
        prio = LOG_DEBUG;
        break;
    case QtInfoMsg:
        prio = LOG_INFO;
        break;
    case QtWarningMsg:
        prio = LOG_WARNING;
        break;
    case QtCriticalMsg:
        prio = LOG_CRIT;
        break;
    case QtFatalMsg:
        prio = LOG_ALERT;
        break;
    }

#ifdef QT_DEBUG
    sd_journal_send("PRIORITY=%i", prio, "SYSLOG_FACILITY=1", "SYSLOG_IDENTIFIER=%s", context.category, "SYSLOG_PID=%lli", QCoreApplication::applicationPid(), "MESSAGE=%s", qFormatLogMessage(type, context, msg).toUtf8().constData(), "CODE_FILE=%s", context.file, "CODE_LINE=%s", context.line, "CODE_FUNC=%s", context.function, NULL);
#else
    sd_journal_send("PRIORITY=%i", prio, "SYSLOG_FACILITY=1", "SYSLOG_IDENTIFIER=%s", context.category, "SYSLOG_PID=%lli", QCoreApplication::applicationPid(), "MESSAGE=%s", qFormatLogMessage(type, context, msg).toUtf8().constData(), NULL);
#endif

    if (prio == 0) {
        abort();
    }
}
#endif

void syslogMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    int prio = LOG_INFO;
    switch (type) {
    case QtDebugMsg:
        prio = LOG_DEBUG;
        break;
    case QtInfoMsg:
        prio = LOG_INFO;
        break;
    case QtWarningMsg:
        prio = LOG_WARNING;
        break;
    case QtCriticalMsg:
        prio = LOG_CRIT;
        break;
    case QtFatalMsg:
        prio = LOG_ALERT;
        break;
    }

    openlog(context.category, LOG_PID, LOG_USER);
    syslog(prio, "%s", qFormatLogMessage(type, context, msg).toUtf8().constData());
    closelog();

    if (prio == 0) {
        abort();
    }
}

Skaffari::Skaffari(QObject *parent) : Application(parent)
{
}

Skaffari::~Skaffari()
{
}

bool Skaffari::init()
{
    const QVariantMap generalConfig = engine()->config(QStringLiteral("Skaffari"));

    if (!messageHandlerInstalled) {
        const QString backend = generalConfig.value(QStringLiteral("logging_backend")).toString();
        if (backend.compare(QLatin1String("syslog"), Qt::CaseInsensitive) == 0) {
            qSetMessagePattern(QStringLiteral("%{message}"));
            qInstallMessageHandler(syslogMessageOutput);
            qCInfo(SK_CORE, "Logging backend: syslog");
        }
#ifdef WITH_SYSTEMD
        else if (backend.compare(QLatin1String("journald"), Qt::CaseInsensitive) == 0) {
            qSetMessagePattern(QStringLiteral("%{message}"));
            qInstallMessageHandler(journaldMessageOutput);
            qCInfo(SK_CORE, "Logging backend: journald");
        }
#endif
        else {
            qCInfo(SK_CORE, "Logging backend: stdout");
        }
        messageHandlerInstalled = true;
    }

    QCoreApplication::setApplicationName(QStringLiteral("Skaffari"));
    QCoreApplication::setApplicationVersion(QStringLiteral(SKAFFARI_VERSION));

    const auto supportedLocales = loadTranslationsFromDir(QStringLiteral("skaffari"), QStringLiteral(SKAFFARI_L10NDIR), QStringLiteral("_"));
    qCInfo(SK_CORE) << "Loaded locales:" << supportedLocales;

    qCDebug(SK_CORE) << "Registering Qt meta types for typedefs.";
    qRegisterMetaType<quota_size_t>("quota_size_t");
    qRegisterMetaType<dbid_t>("dbid_t");
    qRegisterMetaType<HelpHash>("HelpHash");
    qRegisterMetaTypeStreamOperators<QTimeZone>("QTimeZone");

    qCDebug(SK_CORE) << "Registering Grantlee meta types.";
    Grantlee::registerMetaType<Folder>();
    Grantlee::registerMetaType<Domain>();
    Grantlee::registerMetaType<SimpleAdmin>();
    Grantlee::registerMetaType<SimpleDomain>();
    Grantlee::registerMetaType<SimpleAccount>();
    Grantlee::registerMetaType<AdminAccount>();
    Grantlee::registerMetaType<Language>();
    Grantlee::registerMetaType<Account>();
    Grantlee::registerMetaType<HelpEntry>();
    Grantlee::registerMetaType<EmailAddress>();

    const QString tmplName = generalConfig.value(QStringLiteral("template"), QStringLiteral("default")).toString().trimmed();

    if (!isInitialized) {
        const QString tmplBasePath = QStringLiteral(SKAFFARI_TMPLDIR) + QLatin1Char('/') + tmplName;
        qCInfo(SK_CORE, "Template: %s", qUtf8Printable(tmplBasePath));
        SkaffariConfig::setTmplBasePath(tmplBasePath);

        QVariantMap tmplConfig;
        QFile tmplConfigFile(SkaffariConfig::tmplBasePath() + QLatin1String("/metadata.json"));
        if (tmplConfigFile.exists()) {
            qCDebug(SK_CORE, "Found template metadata file.");
            if (tmplConfigFile.open(QIODevice::ReadOnly|QIODevice::Text)) {
                QJsonParseError jpe;
                QJsonDocument tmplJsonConfig(QJsonDocument::fromJson(tmplConfigFile.readAll(), &jpe));
                if (jpe.error != QJsonParseError::NoError) {
                    qCCritical(SK_CORE, "Failed to parse template metadata file: %s", qUtf8Printable(jpe.errorString()));
                    return false;
                }

                tmplConfig = tmplJsonConfig.object().value(QStringLiteral("config")).toObject().toVariantMap();
            } else {
                qCCritical(SK_CORE, "Failed to open template metadata file %s.", qUtf8Printable(tmplConfigFile.fileName()));
                return false;
            }
        }

        qCDebug(SK_CORE) << "Initializing configuration.";
        SkaffariConfig::load(generalConfig,
                             engine()->config(QStringLiteral("Accounts")),
                             engine()->config(QStringLiteral("Admins")),
                             engine()->config(QStringLiteral("IMAP")),
                             tmplConfig);

        if (SkaffariConfig::imapUser().isEmpty()) {
            qCCritical(SK_CORE) << "No valid IMAP user defined.";
            return false;
        }

        if (SkaffariConfig::imapPassword().isEmpty()) {
            qCCritical(SK_CORE) << "No valid IMAP password defined.";
            return false;
        }

        // initialize DB one time to prevent https://bugreports.qt.io/browse/QTBUG-54872
        if (!initDb()) {
            return false;
        }

        SkaffariConfig::loadSettingsFromDB();

        QSqlDatabase::removeDatabase(Sql::databaseNameThread());

        isInitialized = true;
    }

    const QString sitePath = SkaffariConfig::tmplBasePath() + QLatin1String("/site");

    qCDebug(SK_CORE) << "Registering Grantlee view.";
    auto view = new GrantleeView(this);
    view->setTemplateExtension(QStringLiteral(".html"));
    view->setWrapper(QStringLiteral("wrapper.html"));
    view->setCache(false);
    view->setIncludePaths({sitePath});
    view->engine()->addDefaultLibrary(QStringLiteral("grantlee_i18ntags"));
    view->engine()->addDefaultLibrary(QStringLiteral("grantlee_skaffari"));

    view->loadTranslationsFromDir(tmplName, SkaffariConfig::tmplBasePath() + QLatin1String("/l10n"), QStringLiteral("_"));

    qCDebug(SK_CORE) << "Registering Controllers.";
    new Root(this);
    new Login(this);
    new Logout(this);
    new DomainEditor(this);
    new AccountEditor(this);
    new AdminEditor(this);
    new MyAccount(this);
    new SettingsEditor(this);

    qCDebug(SK_CORE) << "Registering plugins.";

    auto staticSimple = new StaticSimple(this);
    const QString staticPath = SkaffariConfig::tmplBasePath() + QLatin1String("/static");
    staticSimple->setIncludePaths({staticPath, QStringLiteral(SKAFFARI_STATICDIR)});

    if (SkaffariConfig::useMemcached()) {
        auto memc = new Memcached(this);
        memc->setDefaultConfig({
                                   {QStringLiteral("binary_protocol"), true}
                               });
    }

    auto sess = new Session(this);

    if (SkaffariConfig::useMemcachedSession()) {
        sess->setStorage(new MemcachedSessionStore(this, this));
    }

    auto lsp = new LangSelect(this, LangSelect::Session);
    lsp->setFallbackLocale(QLocale(QLocale::English));
    lsp->setSupportedLocales(supportedLocales);
    lsp->setSessionKey(QStringLiteral("lang"));

    auto csrf = new CSRFProtection(this);
    csrf->setDefaultDetachTo(QStringLiteral("/csrfdenied"));

    new StatusMessage(this);

    auto auth = new Authentication(this);
    auto cred = new CredentialPassword;
    cred->setPasswordType(CredentialPassword::Hashed);
    auto store = new AuthStoreSql;
    auth->addRealm(store, cred);

    defaultHeaders().setHeader(QStringLiteral("X-Frame-Options"), QStringLiteral("DENY"));
    defaultHeaders().setHeader(QStringLiteral("X-Content-Type-Options"), QStringLiteral("nosniff"));
    defaultHeaders().setHeader(QStringLiteral("X-XSS-Protection"), QStringLiteral("1; mode=block"));
    defaultHeaders().setHeader(QStringLiteral("Content-Security-Policy"), QStringLiteral("default-src 'none'; script-src 'self'; style-src 'self'; font-src 'self'; img-src 'self' data:; connect-src 'self'; base-uri 'self'; form-action 'self';"));
    defaultHeaders().setHeader(QStringLiteral("X-Robots-Tag"), QStringLiteral("none"));
    defaultHeaders().setHeader(QStringLiteral("Referrer-Policy"), QStringLiteral("no-referrer, strict-origin-when-cross-origin"));

    return true;
}

bool Skaffari::postFork()
{
    QMutexLocker locker(&mutex);

    return initDb();
}

bool Skaffari::initDb() const
{
    const QVariantMap dbconfig = engine()->config(QStringLiteral("Database"));
    const QString dbtype = dbconfig.value(QStringLiteral("type")).toString();
    const QString dbname = dbconfig.value(QStringLiteral("name")).toString();
    const QString dbuser = dbconfig.value(QStringLiteral("user")).toString();
    const QString dbpass = dbconfig.value(QStringLiteral("password")).toString();
    const QString dbhost = dbconfig.value(QStringLiteral("host"), QStringLiteral("localhost")).toString();
    const int dbport = dbconfig.value(QStringLiteral("port"), QStringLiteral("3306")).toInt();

    if (!isInitialized) {
        qCDebug(SK_CORE, "Initializing database connection.");
        qCDebug(SK_CORE, "DB Type: %s", qUtf8Printable(dbtype));
        qCDebug(SK_CORE, "DB Name: %s", qUtf8Printable(dbname));
        qCDebug(SK_CORE, "DB User: %s", qUtf8Printable(dbuser));
        qCDebug(SK_CORE, "DB Host: %s", qUtf8Printable(dbhost));
        qCDebug(SK_CORE, "DB Port: %i", dbport);
    } else {
        qCDebug(SK_CORE) << "Establishing database connection:";
    }

    QSqlDatabase db;
    const QString dbConName = Sql::databaseNameThread();
    if (dbtype == QLatin1String("QMYSQL")) {
        if (dbname.isEmpty()) {
            qCCritical(SK_CORE) << "No database name set!";
            return false;
        }
        if (dbuser.isEmpty()) {
            qCCritical(SK_CORE) << "No database user set!";
            return false;
        }
        if (dbpass.isEmpty()) {
            qCCritical(SK_CORE) << "No database password set!";
            return false;
        }

        db = QSqlDatabase::addDatabase(dbtype, dbConName);
        if (Q_LIKELY(db.isValid())) {
            db.setDatabaseName(dbname);
            db.setUserName(dbuser);
            db.setPassword(dbpass);

            if (dbhost[0] == QLatin1Char('/')) {
                db.setConnectOptions(QStringLiteral("UNIX_SOCKET=%1;MYSQL_OPT_RECONNECT=1;CLIENT_INTERACTIVE=1").arg(dbhost));
            } else {
                db.setConnectOptions(QStringLiteral("MYSQL_OPT_RECONNECT=1;CLIENT_INTERACTIVE=1"));
                db.setHostName(dbhost);
                db.setPort(dbport);
            }
        } else {
            qCCritical(SK_CORE) << "Failed to obtain database object.";
            return false;
        }
    } else {
        qCCritical(SK_CORE) << dbtype << "is not a supported database type.";
        return false;
    }

    if (Q_UNLIKELY(!db.open())) {
        qCCritical(SK_CORE) << "Failed to establish database connection:" << db.lastError().text();
        return false;
    }

    return true;
}

#include "moc_skaffari.cpp"
<commit_msg>Improve Content-Security-Policy header<commit_after>/*
 * Skaffari - a mail account administration web interface based on Cutelyst
 * Copyright (C) 2017 Matthias Fehring <kontakt@buschmann23.de>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "skaffari.h"

#include <Cutelyst/Application>
#include <Cutelyst/Plugins/StaticSimple/StaticSimple>
#include <Cutelyst/Plugins/View/Grantlee/grantleeview.h>
#include <Cutelyst/Plugins/Session/Session>
#include <Cutelyst/Plugins/Authentication/authentication.h>
#include <Cutelyst/Plugins/Authentication/credentialpassword.h>
#include <Cutelyst/Plugins/Authentication/authenticationrealm.h>
#include <Cutelyst/Plugins/Utils/Sql>
#include <Cutelyst/Plugins/StatusMessage>
#include <Cutelyst/Plugins/Memcached/Memcached>
#include <Cutelyst/Plugins/MemcachedSessionStore/MemcachedSessionStore>
#include <Cutelyst/Engine>
#include <Cutelyst/Plugins/CSRFProtection/CSRFProtection>
#include <Cutelyst/Plugins/Utils/LangSelect>

#include <grantlee5/grantlee/metatype.h>
#include <grantlee5/grantlee/engine.h>

#include <QSqlDatabase>
#include <QSqlError>
#include <QDir>
#include <QMetaType>
#include <QCoreApplication>
#include <QTranslator>
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLoggingCategory>
#include <QMutexLocker>

extern "C"
{
#ifdef WITH_SYSTEMD
#define SD_JOURNAL_SUPPRESS_LOCATION
#include <systemd/sd-journal.h>
#endif

#include <syslog.h>
}

#include "objects/domain.h"
#include "objects/simpleadmin.h"
#include "objects/simpledomain.h"
#include "objects/simpleaccount.h"
#include "objects/adminaccount.h"
#include "objects/account.h"
#include "objects/folder.h"
#include "objects/helpentry.h"
#include "objects/emailaddress.h"
#include "objects/skaffarierror.h"

#include "utils/language.h"
#include "utils/skaffariconfig.h"
#include "utils/qtimezonevariant_p.h"

#include "../common/config.h"
#include "../common/global.h"
#include "root.h"
#include "authstoresql.h"
#include "login.h"
#include "logout.h"
#include "domaineditor.h"
#include "accounteditor.h"
#include "admineditor.h"
#include "myaccount.h"
#include "settingseditor.h"

Q_LOGGING_CATEGORY(SK_CORE, "skaffari.core")

using namespace Cutelyst;

static QMutex mutex;

bool Skaffari::isInitialized = false;
bool Skaffari::messageHandlerInstalled = false;

#ifdef WITH_SYSTEMD
void journaldMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    int prio = LOG_INFO;
    switch (type) {
    case QtDebugMsg:
        prio = LOG_DEBUG;
        break;
    case QtInfoMsg:
        prio = LOG_INFO;
        break;
    case QtWarningMsg:
        prio = LOG_WARNING;
        break;
    case QtCriticalMsg:
        prio = LOG_CRIT;
        break;
    case QtFatalMsg:
        prio = LOG_ALERT;
        break;
    }

#ifdef QT_DEBUG
    sd_journal_send("PRIORITY=%i", prio, "SYSLOG_FACILITY=1", "SYSLOG_IDENTIFIER=%s", context.category, "SYSLOG_PID=%lli", QCoreApplication::applicationPid(), "MESSAGE=%s", qFormatLogMessage(type, context, msg).toUtf8().constData(), "CODE_FILE=%s", context.file, "CODE_LINE=%s", context.line, "CODE_FUNC=%s", context.function, NULL);
#else
    sd_journal_send("PRIORITY=%i", prio, "SYSLOG_FACILITY=1", "SYSLOG_IDENTIFIER=%s", context.category, "SYSLOG_PID=%lli", QCoreApplication::applicationPid(), "MESSAGE=%s", qFormatLogMessage(type, context, msg).toUtf8().constData(), NULL);
#endif

    if (prio == 0) {
        abort();
    }
}
#endif

void syslogMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    int prio = LOG_INFO;
    switch (type) {
    case QtDebugMsg:
        prio = LOG_DEBUG;
        break;
    case QtInfoMsg:
        prio = LOG_INFO;
        break;
    case QtWarningMsg:
        prio = LOG_WARNING;
        break;
    case QtCriticalMsg:
        prio = LOG_CRIT;
        break;
    case QtFatalMsg:
        prio = LOG_ALERT;
        break;
    }

    openlog(context.category, LOG_PID, LOG_USER);
    syslog(prio, "%s", qFormatLogMessage(type, context, msg).toUtf8().constData());
    closelog();

    if (prio == 0) {
        abort();
    }
}

Skaffari::Skaffari(QObject *parent) : Application(parent)
{
}

Skaffari::~Skaffari()
{
}

bool Skaffari::init()
{
    const QVariantMap generalConfig = engine()->config(QStringLiteral("Skaffari"));

    if (!messageHandlerInstalled) {
        const QString backend = generalConfig.value(QStringLiteral("logging_backend")).toString();
        if (backend.compare(QLatin1String("syslog"), Qt::CaseInsensitive) == 0) {
            qSetMessagePattern(QStringLiteral("%{message}"));
            qInstallMessageHandler(syslogMessageOutput);
            qCInfo(SK_CORE, "Logging backend: syslog");
        }
#ifdef WITH_SYSTEMD
        else if (backend.compare(QLatin1String("journald"), Qt::CaseInsensitive) == 0) {
            qSetMessagePattern(QStringLiteral("%{message}"));
            qInstallMessageHandler(journaldMessageOutput);
            qCInfo(SK_CORE, "Logging backend: journald");
        }
#endif
        else {
            qCInfo(SK_CORE, "Logging backend: stdout");
        }
        messageHandlerInstalled = true;
    }

    QCoreApplication::setApplicationName(QStringLiteral("Skaffari"));
    QCoreApplication::setApplicationVersion(QStringLiteral(SKAFFARI_VERSION));

    const auto supportedLocales = loadTranslationsFromDir(QStringLiteral("skaffari"), QStringLiteral(SKAFFARI_L10NDIR), QStringLiteral("_"));
    qCInfo(SK_CORE) << "Loaded locales:" << supportedLocales;

    qCDebug(SK_CORE) << "Registering Qt meta types for typedefs.";
    qRegisterMetaType<quota_size_t>("quota_size_t");
    qRegisterMetaType<dbid_t>("dbid_t");
    qRegisterMetaType<HelpHash>("HelpHash");
    qRegisterMetaTypeStreamOperators<QTimeZone>("QTimeZone");

    qCDebug(SK_CORE) << "Registering Grantlee meta types.";
    Grantlee::registerMetaType<Folder>();
    Grantlee::registerMetaType<Domain>();
    Grantlee::registerMetaType<SimpleAdmin>();
    Grantlee::registerMetaType<SimpleDomain>();
    Grantlee::registerMetaType<SimpleAccount>();
    Grantlee::registerMetaType<AdminAccount>();
    Grantlee::registerMetaType<Language>();
    Grantlee::registerMetaType<Account>();
    Grantlee::registerMetaType<HelpEntry>();
    Grantlee::registerMetaType<EmailAddress>();

    const QString tmplName = generalConfig.value(QStringLiteral("template"), QStringLiteral("default")).toString().trimmed();

    if (!isInitialized) {
        const QString tmplBasePath = QStringLiteral(SKAFFARI_TMPLDIR) + QLatin1Char('/') + tmplName;
        qCInfo(SK_CORE, "Template: %s", qUtf8Printable(tmplBasePath));
        SkaffariConfig::setTmplBasePath(tmplBasePath);

        QVariantMap tmplConfig;
        QFile tmplConfigFile(SkaffariConfig::tmplBasePath() + QLatin1String("/metadata.json"));
        if (tmplConfigFile.exists()) {
            qCDebug(SK_CORE, "Found template metadata file.");
            if (tmplConfigFile.open(QIODevice::ReadOnly|QIODevice::Text)) {
                QJsonParseError jpe;
                QJsonDocument tmplJsonConfig(QJsonDocument::fromJson(tmplConfigFile.readAll(), &jpe));
                if (jpe.error != QJsonParseError::NoError) {
                    qCCritical(SK_CORE, "Failed to parse template metadata file: %s", qUtf8Printable(jpe.errorString()));
                    return false;
                }

                tmplConfig = tmplJsonConfig.object().value(QStringLiteral("config")).toObject().toVariantMap();
            } else {
                qCCritical(SK_CORE, "Failed to open template metadata file %s.", qUtf8Printable(tmplConfigFile.fileName()));
                return false;
            }
        }

        qCDebug(SK_CORE) << "Initializing configuration.";
        SkaffariConfig::load(generalConfig,
                             engine()->config(QStringLiteral("Accounts")),
                             engine()->config(QStringLiteral("Admins")),
                             engine()->config(QStringLiteral("IMAP")),
                             tmplConfig);

        if (SkaffariConfig::imapUser().isEmpty()) {
            qCCritical(SK_CORE) << "No valid IMAP user defined.";
            return false;
        }

        if (SkaffariConfig::imapPassword().isEmpty()) {
            qCCritical(SK_CORE) << "No valid IMAP password defined.";
            return false;
        }

        // initialize DB one time to prevent https://bugreports.qt.io/browse/QTBUG-54872
        if (!initDb()) {
            return false;
        }

        SkaffariConfig::loadSettingsFromDB();

        QSqlDatabase::removeDatabase(Sql::databaseNameThread());

        isInitialized = true;
    }

    const QString sitePath = SkaffariConfig::tmplBasePath() + QLatin1String("/site");

    qCDebug(SK_CORE) << "Registering Grantlee view.";
    auto view = new GrantleeView(this);
    view->setTemplateExtension(QStringLiteral(".html"));
    view->setWrapper(QStringLiteral("wrapper.html"));
    view->setCache(false);
    view->setIncludePaths({sitePath});
    view->engine()->addDefaultLibrary(QStringLiteral("grantlee_i18ntags"));
    view->engine()->addDefaultLibrary(QStringLiteral("grantlee_skaffari"));

    view->loadTranslationsFromDir(tmplName, SkaffariConfig::tmplBasePath() + QLatin1String("/l10n"), QStringLiteral("_"));

    qCDebug(SK_CORE) << "Registering Controllers.";
    new Root(this);
    new Login(this);
    new Logout(this);
    new DomainEditor(this);
    new AccountEditor(this);
    new AdminEditor(this);
    new MyAccount(this);
    new SettingsEditor(this);

    qCDebug(SK_CORE) << "Registering plugins.";

    auto staticSimple = new StaticSimple(this);
    const QString staticPath = SkaffariConfig::tmplBasePath() + QLatin1String("/static");
    staticSimple->setIncludePaths({staticPath, QStringLiteral(SKAFFARI_STATICDIR)});

    if (SkaffariConfig::useMemcached()) {
        auto memc = new Memcached(this);
        memc->setDefaultConfig({
                                   {QStringLiteral("binary_protocol"), true}
                               });
    }

    auto sess = new Session(this);

    if (SkaffariConfig::useMemcachedSession()) {
        sess->setStorage(new MemcachedSessionStore(this, this));
    }

    auto lsp = new LangSelect(this, LangSelect::Session);
    lsp->setFallbackLocale(QLocale(QLocale::English));
    lsp->setSupportedLocales(supportedLocales);
    lsp->setSessionKey(QStringLiteral("lang"));

    auto csrf = new CSRFProtection(this);
    csrf->setDefaultDetachTo(QStringLiteral("/csrfdenied"));

    new StatusMessage(this);

    auto auth = new Authentication(this);
    auto cred = new CredentialPassword;
    cred->setPasswordType(CredentialPassword::Hashed);
    auto store = new AuthStoreSql;
    auth->addRealm(store, cred);

    defaultHeaders().setHeader(QStringLiteral("X-Frame-Options"), QStringLiteral("DENY"));
    defaultHeaders().setHeader(QStringLiteral("X-Content-Type-Options"), QStringLiteral("nosniff"));
    defaultHeaders().setHeader(QStringLiteral("X-XSS-Protection"), QStringLiteral("1; mode=block"));
    defaultHeaders().setHeader(QStringLiteral("Content-Security-Policy"), QStringLiteral("default-src 'none'; script-src 'self'; style-src 'self'; font-src 'self'; img-src 'self' data:; connect-src 'self'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; block-all-mixed-content"));
    defaultHeaders().setHeader(QStringLiteral("X-Robots-Tag"), QStringLiteral("none"));
    defaultHeaders().setHeader(QStringLiteral("Referrer-Policy"), QStringLiteral("same-origin"));

    return true;
}

bool Skaffari::postFork()
{
    QMutexLocker locker(&mutex);

    return initDb();
}

bool Skaffari::initDb() const
{
    const QVariantMap dbconfig = engine()->config(QStringLiteral("Database"));
    const QString dbtype = dbconfig.value(QStringLiteral("type")).toString();
    const QString dbname = dbconfig.value(QStringLiteral("name")).toString();
    const QString dbuser = dbconfig.value(QStringLiteral("user")).toString();
    const QString dbpass = dbconfig.value(QStringLiteral("password")).toString();
    const QString dbhost = dbconfig.value(QStringLiteral("host"), QStringLiteral("localhost")).toString();
    const int dbport = dbconfig.value(QStringLiteral("port"), QStringLiteral("3306")).toInt();

    if (!isInitialized) {
        qCDebug(SK_CORE, "Initializing database connection.");
        qCDebug(SK_CORE, "DB Type: %s", qUtf8Printable(dbtype));
        qCDebug(SK_CORE, "DB Name: %s", qUtf8Printable(dbname));
        qCDebug(SK_CORE, "DB User: %s", qUtf8Printable(dbuser));
        qCDebug(SK_CORE, "DB Host: %s", qUtf8Printable(dbhost));
        qCDebug(SK_CORE, "DB Port: %i", dbport);
    } else {
        qCDebug(SK_CORE) << "Establishing database connection:";
    }

    QSqlDatabase db;
    const QString dbConName = Sql::databaseNameThread();
    if (dbtype == QLatin1String("QMYSQL")) {
        if (dbname.isEmpty()) {
            qCCritical(SK_CORE) << "No database name set!";
            return false;
        }
        if (dbuser.isEmpty()) {
            qCCritical(SK_CORE) << "No database user set!";
            return false;
        }
        if (dbpass.isEmpty()) {
            qCCritical(SK_CORE) << "No database password set!";
            return false;
        }

        db = QSqlDatabase::addDatabase(dbtype, dbConName);
        if (Q_LIKELY(db.isValid())) {
            db.setDatabaseName(dbname);
            db.setUserName(dbuser);
            db.setPassword(dbpass);

            if (dbhost[0] == QLatin1Char('/')) {
                db.setConnectOptions(QStringLiteral("UNIX_SOCKET=%1;MYSQL_OPT_RECONNECT=1;CLIENT_INTERACTIVE=1").arg(dbhost));
            } else {
                db.setConnectOptions(QStringLiteral("MYSQL_OPT_RECONNECT=1;CLIENT_INTERACTIVE=1"));
                db.setHostName(dbhost);
                db.setPort(dbport);
            }
        } else {
            qCCritical(SK_CORE) << "Failed to obtain database object.";
            return false;
        }
    } else {
        qCCritical(SK_CORE) << dbtype << "is not a supported database type.";
        return false;
    }

    if (Q_UNLIKELY(!db.open())) {
        qCCritical(SK_CORE) << "Failed to establish database connection:" << db.lastError().text();
        return false;
    }

    return true;
}

#include "moc_skaffari.cpp"
<|endoftext|>