<commit_before>/*******************************************************************************

Licensed to the OpenCOR team under one or more contributor license agreements.
See the NOTICE.txt file distributed with this work for additional information
regarding copyright ownership. The OpenCOR team licenses this file to you under
the Apache License, Version 2.0 (the "License"); you may not use this file
except in compliance with the License. You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

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

*******************************************************************************/

//==============================================================================
// Raw CellML view widget
//==============================================================================

#include "corecellmleditingwidget.h"
#include "filemanager.h"
#include "qscintillawidget.h"
#include "rawcellmlviewwidget.h"
#include "viewerwidget.h"

//==============================================================================

#include "ui_rawcellmlviewwidget.h"

//==============================================================================

#include <QDesktopWidget>
#include <QLabel>
#include <QLayout>
#include <QMetaType>
#include <QSettings>
#include <QVariant>

//==============================================================================

#include "Qsci/qscilexerxml.h"

//==============================================================================

namespace OpenCOR {
namespace RawCellMLView {

//==============================================================================

RawCellmlViewWidget::RawCellmlViewWidget(QWidget *pParent) :
    ViewWidget(pParent),
    mGui(new Ui::RawCellmlViewWidget),
    mEditingWidget(0),
    mEditingWidgets(QMap<QString, CoreCellMLEditing::CoreCellmlEditingWidget *>()),
    mEditingWidgetSizes(QIntList()),
    mEditorZoomLevel(0)
{
    // Set up the GUI

    mGui->setupUi(this);
}

//==============================================================================

RawCellmlViewWidget::~RawCellmlViewWidget()
{
    // Delete the GUI

    delete mGui;
}

//==============================================================================

static const auto SettingsEditingWidgetSizes = QStringLiteral("EditingWidgetSizes");
static const auto SettingsEditorZoomLevel    = QStringLiteral("EditorZoomLevel");

//==============================================================================

void RawCellmlViewWidget::loadSettings(QSettings *pSettings)
{
    // Retrieve the editing widget's sizes and the editor's zoom level
    // Note #1: the viewer's default height is 19% of the desktop's height while
    //          that of the editor is as big as it can be...
    // Note #2: because the editor's default height is much bigger than that of
    //          our raw CellML view widget, the viewer's default height will
    //          effectively be less than 19% of the desktop's height, but that
    //          doesn't matter at all...

    QVariantList defaultEditingWidgetSizes = QVariantList() << 0.19*qApp->desktop()->screenGeometry().height()
                                                            << qApp->desktop()->screenGeometry().height();

    mEditingWidgetSizes = qVariantListToIntList(pSettings->value(SettingsEditingWidgetSizes, defaultEditingWidgetSizes).toList());
    mEditorZoomLevel = pSettings->value(SettingsEditorZoomLevel, 0).toInt();
}

//==============================================================================

void RawCellmlViewWidget::saveSettings(QSettings *pSettings) const
{
    // Keep track of the editing widget's sizes and the editor's zoom level

    pSettings->setValue(SettingsEditingWidgetSizes, qIntListToVariantList(mEditingWidgetSizes));
    pSettings->setValue(SettingsEditorZoomLevel, mEditorZoomLevel);
}

//==============================================================================

bool RawCellmlViewWidget::contains(const QString &pFileName) const
{
    // Return whether we know about the given file, i.e. whether we have an
    // editing widget for it

    return mEditingWidgets.value(pFileName);
}

//==============================================================================

void RawCellmlViewWidget::initialize(const QString &pFileName)
{
    // Retrieve the editing widget associated with the given file, if any

    mEditingWidget = mEditingWidgets.value(pFileName);

    if (!mEditingWidget) {
        // No editing widget exists for the given file, so create one

        QString fileContents;

        Core::readTextFromFile(pFileName, fileContents);

        mEditingWidget = new CoreCellMLEditing::CoreCellmlEditingWidget(fileContents,
                                                                        !Core::FileManager::instance()->isReadableAndWritable(pFileName),
                                                                        new QsciLexerXML(this),
                                                                        parentWidget());

        // Keep track of our editing widget's sizes when moving the splitter and
        // of changes to our editor's zoom level

        connect(mEditingWidget, SIGNAL(splitterMoved(int, int)),
                this, SLOT(splitterMoved()));

        connect(mEditingWidget->editor(), SIGNAL(SCN_ZOOM()),
                this, SLOT(editorZoomLevelChanged()));

        connect(mEditingWidget->editor(), SIGNAL(cursorPositionChanged(int, int)),
                this, SLOT(cursorPositionChanged()));

        // Keep track of our editing widget and add it to ourselves

        mEditingWidgets.insert(pFileName, mEditingWidget);

        layout()->addWidget(mEditingWidget);
    }

    // Show/hide our editing widgets and adjust our sizes

    foreach (CoreCellMLEditing::CoreCellmlEditingWidget *editingWidget, mEditingWidgets)
        if (editingWidget == mEditingWidget) {
            // This is the editing widget we are after, so show it and update
            // its size and zoom level

            editingWidget->setSizes(mEditingWidgetSizes);
            editingWidget->editor()->zoomTo(mEditorZoomLevel);

            editingWidget->show();
        } else {
            // Not the editing widget we are after, so hide it

            editingWidget->hide();
        }

    // Set our focus proxy to our 'new' editing widget and make sure that the
    // latter immediately gets the focus
    // Note: if we were not to immediately give our 'new' editing widget the
    //       focus, then the central widget would give the focus to our 'old'
    //       editing widget (see CentralWidget::updateGui()), so...

    setFocusProxy(mEditingWidget->editor());

    mEditingWidget->editor()->setFocus();
}

//==============================================================================

void RawCellmlViewWidget::finalize(const QString &pFileName)
{
    // Remove the editing widget, should there be one for the given file

    CoreCellMLEditing::CoreCellmlEditingWidget *editingWidget  = mEditingWidgets.value(pFileName);

    if (editingWidget) {
        // There is an editor for the given file name, so delete it and remove
        // it from our list

        delete editingWidget;

        mEditingWidgets.remove(pFileName);

        // Reset our memory of the current editor

        mEditingWidget = 0;
    }
}

//==============================================================================

void RawCellmlViewWidget::fileReloaded(const QString &pFileName)
{
    // The given file has been reloaded, so reload it, should it be managed

    if (contains(pFileName)) {
        finalize(pFileName);
        initialize(pFileName);
    }
}

//==============================================================================

void RawCellmlViewWidget::fileRenamed(const QString &pOldFileName,
                                      const QString &pNewFileName)
{
    // The given file has been renamed, so update our editing widgets mapping

    CoreCellMLEditing::CoreCellmlEditingWidget *editingWidget = mEditingWidgets.value(pOldFileName);

    if (editingWidget) {
        mEditingWidgets.insert(pNewFileName, editingWidget);
        mEditingWidgets.remove(pOldFileName);
    }
}

//==============================================================================

QScintillaSupport::QScintillaWidget * RawCellmlViewWidget::editor(const QString &pFileName) const
{
    // Return the requested editor

    CoreCellMLEditing::CoreCellmlEditingWidget *editingWidget = mEditingWidgets.value(pFileName);

    return editingWidget?editingWidget->editor():0;
}

//==============================================================================

QList<QWidget *> RawCellmlViewWidget::statusBarWidgets() const
{
    // Return our status bar widgets

    if (mEditingWidget)
        return QList<QWidget *>() << mEditingWidget->editor()->cursorPositionWidget()
                                  << mEditingWidget->editor()->editingModeWidget();
    else
        return QList<QWidget *>();
}

//==============================================================================

void RawCellmlViewWidget::splitterMoved()
{
    // The splitter has moved, so keep track of the editing widget's sizes

    mEditingWidgetSizes = qobject_cast<CoreCellMLEditing::CoreCellmlEditingWidget *>(sender())->sizes();
}

//==============================================================================

void RawCellmlViewWidget::editorZoomLevelChanged()
{
    // One of our editors had its zoom level changed, so keep track of the new
    // zoom level

    mEditorZoomLevel = qobject_cast<QScintillaSupport::QScintillaWidget *>(sender())->SendScintilla(QsciScintillaBase::SCI_GETZOOM);
}

//==============================================================================

void RawCellmlViewWidget::cursorPositionChanged()
{
    // The cursor has moved, so retrieve the new mathematical equation, if any,
    // around our current position
qDebug("---------");

    static const QString StartMathTag = "<math ";
    static const QByteArray EndMathTag = "</math>";

    QScintillaSupport::QScintillaWidget *editor = mEditingWidget->editor();
    int crtPos = editor->currentPosition();

    int crtStartMathTagPos = editor->findTextInRange(crtPos+StartMathTag.length(), 0, StartMathTag);
    int prevEndMathTagPos = editor->findTextInRange(crtPos, 0, EndMathTag);
    int crtEndMathTagPos = editor->findTextInRange(crtPos-EndMathTag.length()+1, editor->contentsSize(), EndMathTag);

    bool foundMathBlock = true;

    if (   (crtStartMathTagPos >= 0) && (crtEndMathTagPos >= 0)
        && (crtStartMathTagPos <= crtPos)
        && (crtPos <= crtEndMathTagPos+EndMathTag.length()-1)) {
        if (   (prevEndMathTagPos >= 0)
            && (prevEndMathTagPos > crtStartMathTagPos)
            && (prevEndMathTagPos < crtPos))
            foundMathBlock = false;
    } else {
        foundMathBlock = false;
    }

    QString mathBlock = QString();

    if (foundMathBlock)
        mathBlock = editor->textInRange(crtStartMathTagPos, crtEndMathTagPos+EndMathTag.length());

    if (mathBlock.length())
        qDebug(">>> Math block found:\n%s", qPrintable(mathBlock));
    else
        qDebug(">>> No math block found...");

    mEditingWidget->viewer()->setContent(mathBlock);
}

//==============================================================================

}   // namespace RawCellMLView
}   // namespace OpenCOR

//==============================================================================
// End of file
//==============================================================================
<commit_msg>Some minor cleaning up.<commit_after>/*******************************************************************************

Licensed to the OpenCOR team under one or more contributor license agreements.
See the NOTICE.txt file distributed with this work for additional information
regarding copyright ownership. The OpenCOR team licenses this file to you under
the Apache License, Version 2.0 (the "License"); you may not use this file
except in compliance with the License. You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

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

*******************************************************************************/

//==============================================================================
// Raw CellML view widget
//==============================================================================

#include "corecellmleditingwidget.h"
#include "filemanager.h"
#include "qscintillawidget.h"
#include "rawcellmlviewwidget.h"
#include "viewerwidget.h"

//==============================================================================

#include "ui_rawcellmlviewwidget.h"

//==============================================================================

#include <QDesktopWidget>
#include <QLabel>
#include <QLayout>
#include <QMetaType>
#include <QSettings>
#include <QVariant>

//==============================================================================

#include "Qsci/qscilexerxml.h"

//==============================================================================

namespace OpenCOR {
namespace RawCellMLView {

//==============================================================================

RawCellmlViewWidget::RawCellmlViewWidget(QWidget *pParent) :
    ViewWidget(pParent),
    mGui(new Ui::RawCellmlViewWidget),
    mEditingWidget(0),
    mEditingWidgets(QMap<QString, CoreCellMLEditing::CoreCellmlEditingWidget *>()),
    mEditingWidgetSizes(QIntList()),
    mEditorZoomLevel(0)
{
    // Set up the GUI

    mGui->setupUi(this);
}

//==============================================================================

RawCellmlViewWidget::~RawCellmlViewWidget()
{
    // Delete the GUI

    delete mGui;
}

//==============================================================================

static const auto SettingsEditingWidgetSizes = QStringLiteral("EditingWidgetSizes");
static const auto SettingsEditorZoomLevel    = QStringLiteral("EditorZoomLevel");

//==============================================================================

void RawCellmlViewWidget::loadSettings(QSettings *pSettings)
{
    // Retrieve the editing widget's sizes and the editor's zoom level
    // Note #1: the viewer's default height is 19% of the desktop's height while
    //          that of the editor is as big as it can be...
    // Note #2: because the editor's default height is much bigger than that of
    //          our raw CellML view widget, the viewer's default height will
    //          effectively be less than 19% of the desktop's height, but that
    //          doesn't matter at all...

    QVariantList defaultEditingWidgetSizes = QVariantList() << 0.19*qApp->desktop()->screenGeometry().height()
                                                            << qApp->desktop()->screenGeometry().height();

    mEditingWidgetSizes = qVariantListToIntList(pSettings->value(SettingsEditingWidgetSizes, defaultEditingWidgetSizes).toList());
    mEditorZoomLevel = pSettings->value(SettingsEditorZoomLevel, 0).toInt();
}

//==============================================================================

void RawCellmlViewWidget::saveSettings(QSettings *pSettings) const
{
    // Keep track of the editing widget's sizes and the editor's zoom level

    pSettings->setValue(SettingsEditingWidgetSizes, qIntListToVariantList(mEditingWidgetSizes));
    pSettings->setValue(SettingsEditorZoomLevel, mEditorZoomLevel);
}

//==============================================================================

bool RawCellmlViewWidget::contains(const QString &pFileName) const
{
    // Return whether we know about the given file, i.e. whether we have an
    // editing widget for it

    return mEditingWidgets.value(pFileName);
}

//==============================================================================

void RawCellmlViewWidget::initialize(const QString &pFileName)
{
    // Retrieve the editing widget associated with the given file, if any

    mEditingWidget = mEditingWidgets.value(pFileName);

    if (!mEditingWidget) {
        // No editing widget exists for the given file, so create one

        QString fileContents;

        Core::readTextFromFile(pFileName, fileContents);

        mEditingWidget = new CoreCellMLEditing::CoreCellmlEditingWidget(fileContents,
                                                                        !Core::FileManager::instance()->isReadableAndWritable(pFileName),
                                                                        new QsciLexerXML(this),
                                                                        parentWidget());

        // Keep track of our editing widget's sizes when moving the splitter and
        // of changes to our editor's zoom level

        connect(mEditingWidget, SIGNAL(splitterMoved(int, int)),
                this, SLOT(splitterMoved()));

        connect(mEditingWidget->editor(), SIGNAL(SCN_ZOOM()),
                this, SLOT(editorZoomLevelChanged()));

        connect(mEditingWidget->editor(), SIGNAL(cursorPositionChanged(int, int)),
                this, SLOT(cursorPositionChanged()));

        // Keep track of our editing widget and add it to ourselves

        mEditingWidgets.insert(pFileName, mEditingWidget);

        layout()->addWidget(mEditingWidget);
    }

    // Show/hide our editing widgets and adjust our sizes

    foreach (CoreCellMLEditing::CoreCellmlEditingWidget *editingWidget, mEditingWidgets)
        if (editingWidget == mEditingWidget) {
            // This is the editing widget we are after, so show it and update
            // its size and zoom level

            editingWidget->setSizes(mEditingWidgetSizes);
            editingWidget->editor()->zoomTo(mEditorZoomLevel);

            editingWidget->show();
        } else {
            // Not the editing widget we are after, so hide it

            editingWidget->hide();
        }

    // Set our focus proxy to our 'new' editing widget and make sure that the
    // latter immediately gets the focus
    // Note: if we were not to immediately give our 'new' editing widget the
    //       focus, then the central widget would give the focus to our 'old'
    //       editing widget (see CentralWidget::updateGui()), so...

    setFocusProxy(mEditingWidget->editor());

    mEditingWidget->editor()->setFocus();
}

//==============================================================================

void RawCellmlViewWidget::finalize(const QString &pFileName)
{
    // Remove the editing widget, should there be one for the given file

    CoreCellMLEditing::CoreCellmlEditingWidget *editingWidget  = mEditingWidgets.value(pFileName);

    if (editingWidget) {
        // There is an editor for the given file name, so delete it and remove
        // it from our list

        delete editingWidget;

        mEditingWidgets.remove(pFileName);

        // Reset our memory of the current editor

        mEditingWidget = 0;
    }
}

//==============================================================================

void RawCellmlViewWidget::fileReloaded(const QString &pFileName)
{
    // The given file has been reloaded, so reload it, should it be managed

    if (contains(pFileName)) {
        finalize(pFileName);
        initialize(pFileName);
    }
}

//==============================================================================

void RawCellmlViewWidget::fileRenamed(const QString &pOldFileName,
                                      const QString &pNewFileName)
{
    // The given file has been renamed, so update our editing widgets mapping

    CoreCellMLEditing::CoreCellmlEditingWidget *editingWidget = mEditingWidgets.value(pOldFileName);

    if (editingWidget) {
        mEditingWidgets.insert(pNewFileName, editingWidget);
        mEditingWidgets.remove(pOldFileName);
    }
}

//==============================================================================

QScintillaSupport::QScintillaWidget * RawCellmlViewWidget::editor(const QString &pFileName) const
{
    // Return the requested editor

    CoreCellMLEditing::CoreCellmlEditingWidget *editingWidget = mEditingWidgets.value(pFileName);

    return editingWidget?editingWidget->editor():0;
}

//==============================================================================

QList<QWidget *> RawCellmlViewWidget::statusBarWidgets() const
{
    // Return our status bar widgets

    if (mEditingWidget)
        return QList<QWidget *>() << mEditingWidget->editor()->cursorPositionWidget()
                                  << mEditingWidget->editor()->editingModeWidget();
    else
        return QList<QWidget *>();
}

//==============================================================================

void RawCellmlViewWidget::splitterMoved()
{
    // The splitter has moved, so keep track of the editing widget's sizes

    mEditingWidgetSizes = qobject_cast<CoreCellMLEditing::CoreCellmlEditingWidget *>(sender())->sizes();
}

//==============================================================================

void RawCellmlViewWidget::editorZoomLevelChanged()
{
    // One of our editors had its zoom level changed, so keep track of the new
    // zoom level

    mEditorZoomLevel = qobject_cast<QScintillaSupport::QScintillaWidget *>(sender())->SendScintilla(QsciScintillaBase::SCI_GETZOOM);
}

//==============================================================================

void RawCellmlViewWidget::cursorPositionChanged()
{
    // The cursor has moved, so retrieve the new mathematical equation, if any,
    // around our current position
qDebug("---------");

    static const QString StartMathTag = "<math ";
    static const QByteArray EndMathTag = "</math>";

    QScintillaSupport::QScintillaWidget *editor = mEditingWidget->editor();
    int crtPos = editor->currentPosition();

    int crtStartMathTagPos = editor->findTextInRange(crtPos+StartMathTag.length(), 0, StartMathTag);
    int prevEndMathTagPos = editor->findTextInRange(crtPos, 0, EndMathTag);
    int crtEndMathTagPos = editor->findTextInRange(crtPos-EndMathTag.length()+1, editor->contentsSize(), EndMathTag);

    bool foundMathBlock = true;

    if (   (crtStartMathTagPos >= 0) && (crtEndMathTagPos >= 0)
        && (crtStartMathTagPos <= crtPos)
        && (crtPos <= crtEndMathTagPos+EndMathTag.length()-1)) {
        if (   (prevEndMathTagPos >= 0)
            && (prevEndMathTagPos > crtStartMathTagPos)
            && (prevEndMathTagPos < crtPos))
            foundMathBlock = false;
    } else {
        foundMathBlock = false;
    }

    QString mathBlock = QString();

    if (foundMathBlock)
        mathBlock = editor->textInRange(crtStartMathTagPos, crtEndMathTagPos+EndMathTag.length());

    if (mathBlock.length())
        qDebug(">>> Math block found:\n%s", qPrintable(mathBlock));
    else
        qDebug(">>> No math block found...");

    mEditingWidget->viewer()->setContents(mathBlock);
}

//==============================================================================

}   // namespace RawCellMLView
}   // namespace OpenCOR

//==============================================================================
// End of file
//==============================================================================
<|endoftext|>


<commit_before>//===- unittest/Support/ProgramTest.cpp -----------------------------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Program.h"
#include "gtest/gtest.h"

#include <stdlib.h>

namespace {

using namespace llvm;
using namespace sys;

static cl::opt<std::string>
ProgramTestStringArg1("program-test-string-arg1");
static cl::opt<std::string>
ProgramTestStringArg2("program-test-string-arg2");

TEST(ProgramTest, CreateProcessTrailingSlash) {
  if (getenv("LLVM_PROGRAM_TEST_CHILD")) {
    if (ProgramTestStringArg1 == "has\\\\ trailing\\" &&
        ProgramTestStringArg2 == "has\\\\ trailing\\") {
      exit(0);  // Success!  The arguments were passed and parsed.
    }
    exit(1);
  }

  // FIXME: Hardcoding argv0 here since I don't know a good cross-platform way
  // to get it.  Maybe ParseCommandLineOptions() should save it?
  Path my_exe = Path::GetMainExecutable("SupportTests", &ProgramTestStringArg1);
  const char *argv[] = {
    my_exe.c_str(),
    "--gtest_filter=ProgramTest.CreateProcessTrailingSlashChild",
    "-program-test-string-arg1", "has\\\\ trailing\\",
    "-program-test-string-arg2", "has\\\\ trailing\\",
    0
  };
  const char *envp[] = { "LLVM_PROGRAM_TEST_CHILD=1", 0 };
  std::string error;
  bool ExecutionFailed;
  // Redirect stdout and stdin to NUL, but let stderr through.
#ifdef LLVM_ON_WIN32
  Path nul("NUL");
#else
  Path nul("/dev/null");
#endif
  const Path *redirects[] = { &nul, &nul, 0 };
  int rc = Program::ExecuteAndWait(my_exe, argv, envp, redirects,
                                   /*secondsToWait=*/10, /*memoryLimit=*/0,
                                   &error, &ExecutionFailed);
  EXPECT_FALSE(ExecutionFailed) << error;
  EXPECT_EQ(0, rc);
}

} // end anonymous namespace
<commit_msg>[Support] Propagate the environment into the test child process<commit_after>//===- unittest/Support/ProgramTest.cpp -----------------------------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Program.h"
#include "gtest/gtest.h"

#include <stdlib.h>

namespace {

using namespace llvm;
using namespace sys;

static cl::opt<std::string>
ProgramTestStringArg1("program-test-string-arg1");
static cl::opt<std::string>
ProgramTestStringArg2("program-test-string-arg2");

static void CopyEnvironment(std::vector<const char *> out) {
  // environ appears to be pretty portable.
  char **envp = environ;
  while (*envp != 0) {
    out.push_back(*envp);
    ++envp;
  }
}

TEST(ProgramTest, CreateProcessTrailingSlash) {
  if (getenv("LLVM_PROGRAM_TEST_CHILD")) {
    if (ProgramTestStringArg1 == "has\\\\ trailing\\" &&
        ProgramTestStringArg2 == "has\\\\ trailing\\") {
      exit(0);  // Success!  The arguments were passed and parsed.
    }
    exit(1);
  }

  // FIXME: Hardcoding argv0 here since I don't know a good cross-platform way
  // to get it.  Maybe ParseCommandLineOptions() should save it?
  Path my_exe = Path::GetMainExecutable("SupportTests", &ProgramTestStringArg1);
  const char *argv[] = {
    my_exe.c_str(),
    "--gtest_filter=ProgramTest.CreateProcessTrailingSlashChild",
    "-program-test-string-arg1", "has\\\\ trailing\\",
    "-program-test-string-arg2", "has\\\\ trailing\\",
    0
  };

  // Add LLVM_PROGRAM_TEST_CHILD to the environment of the child.
  std::vector<const char *> envp;
  CopyEnvironment(envp);
  envp.push_back("LLVM_PROGRAM_TEST_CHILD=1");
  envp.push_back(0);

  std::string error;
  bool ExecutionFailed;
  // Redirect stdout and stdin to NUL, but let stderr through.
#ifdef LLVM_ON_WIN32
  Path nul("NUL");
#else
  Path nul("/dev/null");
#endif
  const Path *redirects[] = { &nul, &nul, 0 };
  int rc = Program::ExecuteAndWait(my_exe, argv, &envp[0], redirects,
                                   /*secondsToWait=*/10, /*memoryLimit=*/0,
                                   &error, &ExecutionFailed);
  EXPECT_FALSE(ExecutionFailed) << error;
  EXPECT_EQ(0, rc);
}

} // end anonymous namespace
<|endoftext|>


<commit_before>// https://core.telegram.org/bots/api#making-requests

#include "../Logger.h"
#include "../Network.h"
#include "../User.h"
#include "../device/Switch.h"
#include "../device/Text.h"

#include "Telegram.h"

namespace micasa {

	extern std::shared_ptr<Logger> g_logger;
	extern std::shared_ptr<Network> g_network;

	using namespace nlohmann;

	void Telegram::start() {
		g_logger->log( Logger::LogLevel::VERBOSE, this, "Starting..." );
		Hardware::start();

		// Add the devices that will initiate controller actions and the broadcast device.
		this->declareDevice<Switch>( "accept", "Enable Accept Mode", {
			{ DEVICE_SETTING_ALLOWED_UPDATE_SOURCES, Device::resolveUpdateSource( Device::UpdateSource::CONTROLLER | Device::UpdateSource::API ) },
			{ DEVICE_SETTING_DEFAULT_SUBTYPE,        Switch::resolveSubType( Switch::SubType::ACTION ) },
			{ DEVICE_SETTING_MINIMUM_USER_RIGHTS,    User::resolveRights( User::Rights::INSTALLER ) }
		} )->updateValue( Device::UpdateSource::INIT | Device::UpdateSource::HARDWARE, Switch::Option::IDLE );
		this->declareDevice<Text>( "broadcast", "Broadcast", {
			{ DEVICE_SETTING_ALLOWED_UPDATE_SOURCES, Device::resolveUpdateSource( Device::UpdateSource::ANY ) },
			{ DEVICE_SETTING_DEFAULT_SUBTYPE,        Text::resolveSubType( Text::SubType::NOTIFICATION ) }
		} );
	};

	void Telegram::stop() {
		g_logger->log( Logger::LogLevel::VERBOSE, this, "Stopping..." );
		Hardware::stop();
	};

	std::chrono::milliseconds Telegram::_work( const unsigned long int& iteration_ ) {
		if ( ! this->m_settings->contains( { "username", "token" } ) ) {
			g_logger->log( Logger::LogLevel::ERROR, this, "Missing settings." );
			this->setState( Hardware::State::FAILED );
			return std::chrono::milliseconds( 60 * 1000 );
		}

		std::stringstream url;
		url << "https://api.telegram.org/bot" << this->m_settings->get( "token" ) << "/getUpdates";

		json params = {
			{ "timeout", 60 }
		};
		if ( this->m_lastUpdateId > -1 ) {
			params["offset"] = this->m_lastUpdateId + 1;
		}

		this->setState( Hardware::State::READY );
		g_network->connect( url.str(), Network::t_callback( [this]( mg_connection* connection_, int event_, void* data_ ) {
			if ( event_ == MG_EV_HTTP_REPLY ) {

				// See if a valid response was received from the Telegram API. It should be json encoded data with an
				// "ok" property set to a boolean true.
				std::string body;
				body.assign( ((http_message*)data_)->body.p, ((http_message*)data_)->body.len );
				try {
					json data = json::parse( body );
					bool success = data["ok"].get<bool>();
					if ( ! success ) {
						throw std::runtime_error( "Telegram API reported a failure" );
					}

					auto find = data.find( "result" );
					if (
						find != data.end()
						&& (*find).is_array()
					) {
						for ( auto updateIt = (*find).begin(); updateIt != (*find).end(); updateIt++ ) {
							int lastUpdateId = jsonGet<int>( *updateIt, "update_id" );
							if ( lastUpdateId > this->m_lastUpdateId ) {
								this->m_lastUpdateId = lastUpdateId;
							}

							if ( (*updateIt).find( "message" ) != (*updateIt).end() ) {
								this->_processIncomingMessage( (*updateIt)["message"] );
							}
						}
					}

				} catch( std::invalid_argument ex_ ) {
					g_logger->log( Logger::LogLevel::ERROR, this, "Invalid data received from the Telegram API." );
					this->setState( Hardware::State::FAILED );
				} catch( std::runtime_error ex_ ) {
					g_logger->log( Logger::LogLevel::ERROR, this, ex_.what() );
					this->setState( Hardware::State::FAILED );
				}

				connection_->flags |= MG_F_CLOSE_IMMEDIATELY;

			}  else if ( event_ == MG_EV_CLOSE ) {

				// If a successfull response was received, a new connection needs to be made immediately (long poll),
				// if not, try the Telegram API again after the default of 5 minutes.
				if ( this->getState() == Hardware::State::READY ) {
					this->wakeUp();
				}
			}
		} ), params );

		return std::chrono::milliseconds( 1000 * 60 * 5 );
	};

	bool Telegram::updateDevice( const Device::UpdateSource& source_, std::shared_ptr<Device> device_, bool& apply_ ) {
		if ( this->getState() == Hardware::State::READY ) {
			if ( device_->getType() == Device::Type::SWITCH ) {
				std::shared_ptr<Switch> device = std::static_pointer_cast<Switch>( device_ );
				if (
					device->getReference() == "accept"
					&& device->getValueOption() == Switch::Option::ACTIVATE
				) {
					if ( this->m_acceptMode ) {
						g_logger->log( Logger::LogLevel::ERROR, this, "Accept Mode already enabled." );
					} else {
						g_logger->log( Logger::LogLevel::NORMAL, this, "Accept Mode enabled." );
						this->m_acceptMode = true;
						std::thread( [this, device]{
							std::this_thread::sleep_for( std::chrono::minutes( 60 * 5 ) );
							this->m_acceptMode = false;
							device->updateValue( Device::UpdateSource::HARDWARE, Switch::Option::IDLE );
							g_logger->log( Logger::LogLevel::VERBOSE, this, "Accept Mode disabled." );
						} ).detach();
					}
					return true;
				}

			} else if ( device_->getType() == Device::Type::TEXT ) {
				std::shared_ptr<Text> device = std::static_pointer_cast<Text>( device_ );

				std::stringstream url;
				url << "https://api.telegram.org/bot" << this->m_settings->get( "token" ) << "/sendMessage";

				json params = {
					{ "chat_id", std::stoi( device->getReference() ) },
					{ "text", device->getValue() },
					{ "parse_mode", "Markdown" }
				};
				g_network->connect( url.str(), Network::t_callback( [this]( mg_connection* connection_, int event_, void* data_ ) {
					if ( event_ == MG_EV_HTTP_REPLY ) {

						// See if a valid response was received from the Telegram API.
						std::string body;
						body.assign( ((http_message*)data_)->body.p, ((http_message*)data_)->body.len );
						try {
							json data = json::parse( body );
							bool success = data["ok"].get<bool>();
							if ( ! success ) {
								throw std::runtime_error( "Telegram API reported a failure" );
							}
						} catch( std::invalid_argument ex_ ) {
							g_logger->log( Logger::LogLevel::ERROR, this, "Invalid data received from the Telegram API." );
						} catch( std::runtime_error ex_ ) {
							g_logger->log( Logger::LogLevel::ERROR, this, ex_.what() );
						}

						connection_->flags |= MG_F_CLOSE_IMMEDIATELY;
					}
				} ), params );

				return true;
			}
		}
		return false;
	};

	json Telegram::getJson( bool full_ ) const {
		json result = Hardware::getJson( full_ );
		result["username"] = this->m_settings->get( "username", "" );
		result["token"] = this->m_settings->get( "token", "" );
		if ( full_ ) {
			result["settings"] = this->getSettingsJson();
		}
		return result;
	};

	json Telegram::getSettingsJson() const {
		json result = Hardware::getSettingsJson();
		result += {
			{ "name", "username" },
			{ "label", "Username" },
			{ "type", "string" },
			{ "class", this->m_settings->contains( "username" ) ? "advanced" : "normal" },
			{ "mandatory", true },
			{ "sort", 97 }
		};
		result += {
			{ "name", "token" },
			{ "label", "Token" },
			{ "type", "string" },
			{ "class", this->m_settings->contains( "token" ) ? "advanced" : "normal" },
			{ "mandatory", true },
			{ "sort", 98 }
		};
		return result;
	};

	void Telegram::_processIncomingMessage( const json& message_ ) {
		auto find = message_.find( "chat" );
		if ( find != message_.end() ) {
			std::string reference = jsonGet<std::string>( *find, "id" );
			auto device = std::static_pointer_cast<Text>( this->getDevice( reference ) );
			if (
				device == nullptr
				&& this->m_acceptMode
			) {
				std::stringstream label;
				if ( (*find).find( "first_name" ) != (*find).end() ) {
					label << (*find)["first_name"].get<std::string>();
				}
				if ( (*find).find( "last_name" ) != (*find).end() ) {
					if ( label.str().size() > 0 ) {
						label << " ";
					}
					label << (*find)["last_name"].get<std::string>();
				}
				if ( (*find).find( "username" ) != (*find).end() ) {
					if ( label.str().size() > 0 ) {
						label << " (" << (*find)["username"].get<std::string>() << ")";
					} else {
						label << (*find)["username"].get<std::string>();
					}
				}
				if ( label.str().size() == 0 ) {
					label << reference;
				}
				device = this->declareDevice<Text>( reference, label.str(), {
					{ DEVICE_SETTING_ALLOWED_UPDATE_SOURCES, Device::resolveUpdateSource( Device::UpdateSource::ANY ) },
					{ DEVICE_SETTING_DEFAULT_SUBTYPE,        Text::resolveSubType( Text::SubType::NOTIFICATION ) }
				} );
			}
			if (
				device != nullptr
				&& message_.find( "text" ) != message_.end()
			) {
				device->updateValue( Device::UpdateSource::HARDWARE, message_["text"].get<std::string>() );
			}
		}
	};

}; // namespace micasa
<commit_msg>fixed broadcasting<commit_after>// https://core.telegram.org/bots/api#making-requests

#include "../Logger.h"
#include "../Network.h"
#include "../User.h"
#include "../device/Switch.h"
#include "../device/Text.h"

#include "Telegram.h"

namespace micasa {

	extern std::shared_ptr<Logger> g_logger;
	extern std::shared_ptr<Network> g_network;

	using namespace nlohmann;

	void Telegram::start() {
		g_logger->log( Logger::LogLevel::VERBOSE, this, "Starting..." );
		Hardware::start();

		// Add the devices that will initiate controller actions and the broadcast device.
		this->declareDevice<Switch>( "accept", "Enable Accept Mode", {
			{ DEVICE_SETTING_ALLOWED_UPDATE_SOURCES, Device::resolveUpdateSource( Device::UpdateSource::CONTROLLER | Device::UpdateSource::API ) },
			{ DEVICE_SETTING_DEFAULT_SUBTYPE,        Switch::resolveSubType( Switch::SubType::ACTION ) },
			{ DEVICE_SETTING_MINIMUM_USER_RIGHTS,    User::resolveRights( User::Rights::INSTALLER ) }
		} )->updateValue( Device::UpdateSource::INIT | Device::UpdateSource::HARDWARE, Switch::Option::IDLE );
		this->declareDevice<Text>( "broadcast", "Broadcast", {
			{ DEVICE_SETTING_ALLOWED_UPDATE_SOURCES, Device::resolveUpdateSource( Device::UpdateSource::ANY ) },
			{ DEVICE_SETTING_DEFAULT_SUBTYPE,        Text::resolveSubType( Text::SubType::NOTIFICATION ) }
		} );
	};

	void Telegram::stop() {
		g_logger->log( Logger::LogLevel::VERBOSE, this, "Stopping..." );
		Hardware::stop();
	};

	std::chrono::milliseconds Telegram::_work( const unsigned long int& iteration_ ) {
		if ( ! this->m_settings->contains( { "username", "token" } ) ) {
			g_logger->log( Logger::LogLevel::ERROR, this, "Missing settings." );
			this->setState( Hardware::State::FAILED );
			return std::chrono::milliseconds( 60 * 1000 );
		}

		std::stringstream url;
		url << "https://api.telegram.org/bot" << this->m_settings->get( "token" ) << "/getUpdates";

		json params = {
			{ "timeout", 60 }
		};
		if ( this->m_lastUpdateId > -1 ) {
			params["offset"] = this->m_lastUpdateId + 1;
		}

		this->setState( Hardware::State::READY );
		g_network->connect( url.str(), Network::t_callback( [this]( mg_connection* connection_, int event_, void* data_ ) {
			if ( event_ == MG_EV_HTTP_REPLY ) {

				// See if a valid response was received from the Telegram API. It should be json encoded data with an
				// "ok" property set to a boolean true.
				std::string body;
				body.assign( ((http_message*)data_)->body.p, ((http_message*)data_)->body.len );
				try {
					json data = json::parse( body );
					bool success = data["ok"].get<bool>();
					if ( ! success ) {
						throw std::runtime_error( "Telegram API reported a failure" );
					}

					auto find = data.find( "result" );
					if (
						find != data.end()
						&& (*find).is_array()
					) {
						for ( auto updateIt = (*find).begin(); updateIt != (*find).end(); updateIt++ ) {
							int lastUpdateId = jsonGet<int>( *updateIt, "update_id" );
							if ( lastUpdateId > this->m_lastUpdateId ) {
								this->m_lastUpdateId = lastUpdateId;
							}

							if ( (*updateIt).find( "message" ) != (*updateIt).end() ) {
								this->_processIncomingMessage( (*updateIt)["message"] );
							}
						}
					}

				} catch( std::invalid_argument ex_ ) {
					g_logger->log( Logger::LogLevel::ERROR, this, "Invalid data received from the Telegram API." );
					this->setState( Hardware::State::FAILED );
				} catch( std::runtime_error ex_ ) {
					g_logger->log( Logger::LogLevel::ERROR, this, ex_.what() );
					this->setState( Hardware::State::FAILED );
				}

				connection_->flags |= MG_F_CLOSE_IMMEDIATELY;

			}  else if ( event_ == MG_EV_CLOSE ) {

				// If a successfull response was received, a new connection needs to be made immediately (long poll),
				// if not, try the Telegram API again after the default of 5 minutes.
				if ( this->getState() == Hardware::State::READY ) {
					this->wakeUp();
				}
			}
		} ), params );

		return std::chrono::milliseconds( 1000 * 60 * 5 );
	};

	bool Telegram::updateDevice( const Device::UpdateSource& source_, std::shared_ptr<Device> device_, bool& apply_ ) {
		if ( this->getState() == Hardware::State::READY ) {
			if ( device_->getType() == Device::Type::SWITCH ) {

				// This part takes care of temprarely enabling the accept new chats mode.
				std::shared_ptr<Switch> device = std::static_pointer_cast<Switch>( device_ );
				if (
					device->getReference() == "accept"
					&& device->getValueOption() == Switch::Option::ACTIVATE
				) {
					if ( this->m_acceptMode ) {
						g_logger->log( Logger::LogLevel::ERROR, this, "Accept Mode already enabled." );
					} else {
						g_logger->log( Logger::LogLevel::NORMAL, this, "Accept Mode enabled." );
						this->m_acceptMode = true;
						std::thread( [this, device]{
							std::this_thread::sleep_for( std::chrono::minutes( 60 * 5 ) );
							this->m_acceptMode = false;
							device->updateValue( Device::UpdateSource::HARDWARE, Switch::Option::IDLE );
							g_logger->log( Logger::LogLevel::VERBOSE, this, "Accept Mode disabled." );
						} ).detach();
					}
					return true;
				}

			} else if ( device_->getType() == Device::Type::TEXT ) {

				// A message can be sent to a single device (=chat) or to the broadcast device, in which case it is send
				// to all chat devices.
				std::vector<std::shared_ptr<Device> > devices;
				if ( device_->getReference() == "broadcast" ) {
					auto allDevices = this->getAllDevices();
					for ( auto deviceIt = allDevices.begin(); deviceIt != allDevices.end(); deviceIt++ ) {
						if (
							(*deviceIt)->getType() == Device::Type::TEXT
							&& (*deviceIt)->getReference() != "broadcast"
						) {
							devices.push_back( *deviceIt );
						}
					}
				} else {
					devices.push_back( device_ );
				}
				for ( auto deviceIt = devices.begin(); deviceIt != devices.end(); deviceIt++ ) {
					std::shared_ptr<Text> device = std::static_pointer_cast<Text>( *deviceIt );

					std::stringstream url;
					url << "https://api.telegram.org/bot" << this->m_settings->get( "token" ) << "/sendMessage";

					json params = {
						{ "chat_id", std::stoi( device->getReference() ) },
						{ "text", std::static_pointer_cast<Text>( device_ )->getValue() },
						{ "parse_mode", "Markdown" }
					};
					g_network->connect( url.str(), Network::t_callback( [this]( mg_connection* connection_, int event_, void* data_ ) {
						if ( event_ == MG_EV_HTTP_REPLY ) {

							// See if a valid response was received from the Telegram API.
							std::string body;
							body.assign( ((http_message*)data_)->body.p, ((http_message*)data_)->body.len );
							try {
								json data = json::parse( body );
								bool success = data["ok"].get<bool>();
								if ( ! success ) {
									throw std::runtime_error( "Telegram API reported a failure" );
								}
							} catch( std::invalid_argument ex_ ) {
								g_logger->log( Logger::LogLevel::ERROR, this, "Invalid data received from the Telegram API." );
							} catch( std::runtime_error ex_ ) {
								g_logger->log( Logger::LogLevel::ERROR, this, ex_.what() );
							}

							connection_->flags |= MG_F_CLOSE_IMMEDIATELY;
						}
					} ), params );
				}
				return true;
			}
		}
		return false;
	};

	json Telegram::getJson( bool full_ ) const {
		json result = Hardware::getJson( full_ );
		result["username"] = this->m_settings->get( "username", "" );
		result["token"] = this->m_settings->get( "token", "" );
		if ( full_ ) {
			result["settings"] = this->getSettingsJson();
		}
		return result;
	};

	json Telegram::getSettingsJson() const {
		json result = Hardware::getSettingsJson();
		result += {
			{ "name", "username" },
			{ "label", "Username" },
			{ "type", "string" },
			{ "class", this->m_settings->contains( "username" ) ? "advanced" : "normal" },
			{ "mandatory", true },
			{ "sort", 97 }
		};
		result += {
			{ "name", "token" },
			{ "label", "Token" },
			{ "type", "string" },
			{ "class", this->m_settings->contains( "token" ) ? "advanced" : "normal" },
			{ "mandatory", true },
			{ "sort", 98 }
		};
		return result;
	};

	void Telegram::_processIncomingMessage( const json& message_ ) {
		auto find = message_.find( "chat" );
		if ( find != message_.end() ) {
			std::string reference = jsonGet<std::string>( *find, "id" );
			auto device = std::static_pointer_cast<Text>( this->getDevice( reference ) );
			if (
				device == nullptr
				&& this->m_acceptMode
			) {
				std::stringstream label;
				if ( (*find).find( "first_name" ) != (*find).end() ) {
					label << (*find)["first_name"].get<std::string>();
				}
				if ( (*find).find( "last_name" ) != (*find).end() ) {
					if ( label.str().size() > 0 ) {
						label << " ";
					}
					label << (*find)["last_name"].get<std::string>();
				}
				if ( (*find).find( "username" ) != (*find).end() ) {
					if ( label.str().size() > 0 ) {
						label << " (" << (*find)["username"].get<std::string>() << ")";
					} else {
						label << (*find)["username"].get<std::string>();
					}
				}
				if ( label.str().size() == 0 ) {
					label << reference;
				}
				device = this->declareDevice<Text>( reference, label.str(), {
					{ DEVICE_SETTING_ALLOWED_UPDATE_SOURCES, Device::resolveUpdateSource( Device::UpdateSource::ANY ) },
					{ DEVICE_SETTING_DEFAULT_SUBTYPE,        Text::resolveSubType( Text::SubType::NOTIFICATION ) }
				} );
			}
			if (
				device != nullptr
				&& message_.find( "text" ) != message_.end()
			) {
				device->updateValue( Device::UpdateSource::HARDWARE, message_["text"].get<std::string>() );
			}
		}
	};

}; // namespace micasa
<|endoftext|>


<commit_before>
#include <memory>
#include <jsapi.h>
#ifndef MOZJS_MAJOR_VERSION
#include <jsversion.h>
#endif
#include <QtCore>

#include "webqq_calljs.hpp"

static JSClass global_class = {
	"global",
	JSCLASS_GLOBAL_FLAGS|JSCLASS_NEW_RESOLVE,
	JS_PropertyStub,
#ifdef MOZJS_MAJOR_VERSION
	JS_DeletePropertyStub,
#else
	JS_PropertyStub,
#endif
	JS_PropertyStub,
	JS_StrictPropertyStub,
	JS_EnumerateStub,
	JS_ResolveStub,
	JS_ConvertStub,
	NULL,
	JSCLASS_NO_OPTIONAL_MEMBERS
};

std::string webqq::qqimpl::call_js_helper_function_in_buffer(const char* js_content, int js_content_length, std::string helper_file, std::string function, std::vector< std::string > args)
{
#if MOZJS_MAJOR_VERSION >= 24
	std::shared_ptr<JSRuntime> jsrt{ JS_NewRuntime(1024*1024*10, JS_NO_HELPER_THREADS) , JS_DestroyRuntime};
#else
	std::shared_ptr<JSRuntime> jsrt{ JS_NewRuntime(1024*1024*10) , JS_DestroyRuntime};
#endif
	std::shared_ptr<JSContext> jsctx{ JS_NewContext(jsrt.get(), 16*1024) , JS_DestroyContext};

	JS_SetOptions(jsctx.get(), JSOPTION_VAROBJFIX|JSOPTION_COMPILE_N_GO|JSOPTION_NO_SCRIPT_RVAL);

#if JS_VERSION == 185
	JSObject* global_object = JS_NewCompartmentAndGlobalObject(jsctx.get(), &global_class, NULL);
#else
	JSObject* global_object = JS_NewGlobalObject(jsctx.get(), &global_class, NULL);
#endif
	JS_InitStandardClasses(jsctx.get(), global_object);

#if JS_VERSION == 185
	JSObject * script
#else
	JSScript * script
#endif
	= JS_CompileScript(jsctx.get(), global_object, js_content, js_content_length,  helper_file.c_str(), 0);

	JS_ExecuteScript(jsctx.get(), global_object, script, NULL);

	jsval res;
	std::vector<jsval> argv;
	char* res_;

	for (auto _a : args)
	{
		JSString* js_arg = JS_NewStringCopyN(jsctx.get(), _a.c_str(), _a.length());
		argv.push_back(STRING_TO_JSVAL(js_arg));
	}

	JS_CallFunctionName(jsctx.get(), global_object, function.c_str(), argv.size(), argv.data(), &res);

	res_ = JS_EncodeString(jsctx.get(),JSVAL_TO_STRING(res));
	std::string ret = res_;
	JS_free(jsctx.get(),res_);
	return ret;

}

std::string webqq::qqimpl::call_js_helper_function(std::string helper_file, std::string function, std::vector< std::string > args)
{
	QFile helperfile(helper_file.c_str());

	helperfile.open(QIODevice::ReadOnly);

	auto js_content = helperfile.readAll();

	return call_js_helper_function_in_buffer(js_content.constData(), js_content.length(), helper_file, function, args);

}

<commit_msg>fix compatibility with mozjs-24<commit_after>
#include <memory>
#include <jsapi.h>
#ifndef MOZJS_MAJOR_VERSION
#include <jsversion.h>
#endif
#include <QtCore>

#include "webqq_calljs.hpp"

static JSClass global_class = {
	"global",
	JSCLASS_GLOBAL_FLAGS|JSCLASS_NEW_RESOLVE,
	JS_PropertyStub,
#ifdef MOZJS_MAJOR_VERSION
	JS_DeletePropertyStub,
#else
	JS_PropertyStub,
#endif
	JS_PropertyStub,
	JS_StrictPropertyStub,
	JS_EnumerateStub,
	JS_ResolveStub,
	JS_ConvertStub,
	NULL,
	JSCLASS_NO_OPTIONAL_MEMBERS
};

std::string webqq::qqimpl::call_js_helper_function_in_buffer(const char* js_content, int js_content_length, std::string helper_file, std::string function, std::vector< std::string > args)
{
#if MOZJS_MAJOR_VERSION >= 24
	std::shared_ptr<JSRuntime> jsrt{ JS_NewRuntime(1024*1024*10, JS_NO_HELPER_THREADS) , JS_DestroyRuntime};
#else
	std::shared_ptr<JSRuntime> jsrt{ JS_NewRuntime(1024*1024*10) , JS_DestroyRuntime};
#endif
	std::shared_ptr<JSContext> jsctx{ JS_NewContext(jsrt.get(), 16*1024) , JS_DestroyContext};

	JS_SetOptions(jsctx.get(), JS_GetOptions(jsctx.get()) |JSOPTION_VAROBJFIX|JSOPTION_COMPILE_N_GO|JSOPTION_NO_SCRIPT_RVAL);

#if JS_VERSION == 185
	JSObject* global_object = JS_NewCompartmentAndGlobalObject(jsctx.get(), &global_class, NULL);
#else
    JS::CompartmentOptions options;
    options.setVersion(JSVERSION_LATEST);

	JSObject* global_object = JS_NewGlobalObject(jsctx.get(), &global_class, NULL, options);
    JSAutoCompartment ac(jsctx.get(), global_object);
#endif
    JS_SetGlobalObject(jsctx.get(), global_object);

	JS_InitStandardClasses(jsctx.get(), global_object);

#if JS_VERSION == 185
	JSObject * script
#else
	JSScript * script
#endif
	= JS_CompileScript(jsctx.get(), global_object, js_content, js_content_length,  helper_file.c_str(), 0);

	JS_ExecuteScript(jsctx.get(), global_object, script, NULL);

	jsval res;
	std::vector<jsval> argv;
	char* res_;

	for (auto _a : args)
	{
		JSString* js_arg = JS_NewStringCopyN(jsctx.get(), _a.c_str(), _a.length());
		argv.push_back(STRING_TO_JSVAL(js_arg));
	}

	JS_CallFunctionName(jsctx.get(), global_object, function.c_str(), argv.size(), argv.data(), &res);

	res_ = JS_EncodeString(jsctx.get(),JSVAL_TO_STRING(res));
	std::string ret = res_;
	JS_free(jsctx.get(),res_);
	return ret;

}

std::string webqq::qqimpl::call_js_helper_function(std::string helper_file, std::string function, std::vector< std::string > args)
{
	QFile helperfile(helper_file.c_str());

	helperfile.open(QIODevice::ReadOnly);

	auto js_content = helperfile.readAll();

	return call_js_helper_function_in_buffer(js_content.constData(), js_content.length(), helper_file, function, args);

}

<|endoftext|>


<commit_before>#include <sstream>

#include <rclcpp/rclcpp.hpp>

#include <std_msgs/String.h>
#include "std_msgs/dds_impl/String_convert.h"

#include <rclcpp/publisher/publisher.hpp>

int main(int argc, char** argv)
{
    rclcpp::init(argc, argv);

    auto node = rclcpp::create_node("talker");

    auto publisher = node->create_publisher<std_msgs::String>("chatter", 0);

    std_msgs::String msg;
    int count = 0;
    while(node->is_running())
    {
        std::stringstream ss;
        ss << "[" << count++ << "]: Hello World!";
        msg.data = ss.str();
        publisher->publish(msg);
        sleep(1);
    }
}
<commit_msg>Removed unused header<commit_after>#include <sstream>

#include <rclcpp/rclcpp.hpp>

#include <std_msgs/String.h>
#include "std_msgs/dds_impl/String_convert.h"

int main(int argc, char** argv)
{
    rclcpp::init(argc, argv);

    auto node = rclcpp::create_node("talker");

    auto publisher = node->create_publisher<std_msgs::String>("chatter", 0);

    std_msgs::String msg;
    int count = 0;
    while(node->is_running())
    {
        std::stringstream ss;
        ss << "[" << count++ << "]: Hello World!";
        msg.data = ss.str();
        publisher->publish(msg);
        sleep(1);
    }
}
<|endoftext|>


<commit_before>/** @file

  A brief file description

  @section license License

  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you under the Apache License, Version 2.0 (the
  "License"); you may not use this file except in compliance
  with the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

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

#include <yaml-cpp/yaml.h>
#include "I_Machine.h"
#include "NextHopSelectionStrategy.h"

// ring mode strings
constexpr std::string_view alternate_rings = "alternate_ring";
constexpr std::string_view exhaust_rings   = "exhaust_ring";

// health check strings
constexpr std::string_view active_health_check  = "active";
constexpr std::string_view passive_health_check = "passive";

constexpr const char *policy_strings[] = {"NH_UNDEFINED", "NH_FIRST_LIVE", "NH_RR_STRICT",
                                          "NH_RR_IP",     "NH_RR_LATCHED", "NH_CONSISTENT_HASH"};

NextHopSelectionStrategy::NextHopSelectionStrategy(const std::string_view &name, const NHPolicyType &policy)
{
  strategy_name = name;
  policy_type   = policy;
  NH_Debug(NH_DEBUG_TAG, "Using a selection strategy of type %s", policy_strings[policy]);
}

//
// parse out the data for this strategy.
//
bool
NextHopSelectionStrategy::Init(const YAML::Node &n)
{
  NH_Debug(NH_DEBUG_TAG, "calling Init()");

  try {
    if (n["scheme"]) {
      auto scheme_val = n["scheme"].Scalar();
      if (scheme_val == "http") {
        scheme = NH_SCHEME_HTTP;
      } else if (scheme_val == "https") {
        scheme = NH_SCHEME_HTTPS;
      } else {
        scheme = NH_SCHEME_NONE;
        NH_Note("Invalid 'scheme' value, '%s', for the strategy named '%s', setting to NH_SCHEME_NONE", scheme_val.c_str(),
                strategy_name.c_str());
      }
    }

    // go_direct config.
    if (n["go_direct"]) {
      go_direct = n["go_direct"].as<bool>();
    }

    // parent_is_proxy config.
    if (n["parent_is_proxy"]) {
      parent_is_proxy = n["parent_is_proxy"].as<bool>();
    }

    // ignore_self_detect
    if (n["ignore_self_detect"]) {
      ignore_self_detect = n["ignore_self_detect"].as<bool>();
    }

    // failover node.
    YAML::Node failover_node;
    if (n["failover"]) {
      failover_node = n["failover"];
      if (failover_node["ring_mode"]) {
        auto ring_mode_val = failover_node["ring_mode"].Scalar();
        if (ring_mode_val == alternate_rings) {
          ring_mode = NH_ALTERNATE_RING;
        } else if (ring_mode_val == exhaust_rings) {
          ring_mode = NH_EXHAUST_RING;
        } else {
          ring_mode = NH_ALTERNATE_RING;
          NH_Note("Invalid 'ring_mode' value, '%s', for the strategy named '%s', using default '%s'.", ring_mode_val.c_str(),
                  strategy_name.c_str(), alternate_rings.data());
        }
      }
      if (failover_node["max_simple_retries"]) {
        max_simple_retries = failover_node["max_simple_retries"].as<int>();
      }

      YAML::Node resp_codes_node;
      if (failover_node["response_codes"]) {
        resp_codes_node = failover_node["response_codes"];
        if (resp_codes_node.Type() != YAML::NodeType::Sequence) {
          NH_Error("Error in the response_codes definition for the strategy named '%s', skipping response_codes.",
                   strategy_name.c_str());
        } else {
          for (unsigned int k = 0; k < resp_codes_node.size(); ++k) {
            auto code = resp_codes_node[k].as<int>();
            if (code > 300 && code < 599) {
              resp_codes.add(code);
            } else {
              NH_Note("Skipping invalid response code '%d' for the strategy named '%s'.", code, strategy_name.c_str());
            }
          }
          resp_codes.sort();
        }
      }
      YAML::Node health_check_node;
      if (failover_node["health_check"]) {
        health_check_node = failover_node["health_check"];
        if (health_check_node.Type() != YAML::NodeType::Sequence) {
          NH_Error("Error in the health_check definition for the strategy named '%s', skipping health_checks.",
                   strategy_name.c_str());
        } else {
          for (auto it = health_check_node.begin(); it != health_check_node.end(); ++it) {
            auto health_check = it->as<std::string>();
            if (health_check.compare(active_health_check) == 0) {
              health_checks.active = true;
            }
            if (health_check.compare(passive_health_check) == 0) {
              health_checks.passive = true;
            }
          }
        }
      }
    }

    // parse and load the host data
    YAML::Node groups_node;
    if (n["groups"]) {
      groups_node = n["groups"];
      // a groups list is required.
      if (groups_node.Type() != YAML::NodeType::Sequence) {
        throw std::invalid_argument("Invalid groups definition, expected a sequence, '" + strategy_name + "' cannot be loaded.");
      } else {
        Machine *mach      = Machine::instance();
        HostStatus &h_stat = HostStatus::instance();
        uint32_t grp_size  = groups_node.size();
        if (grp_size > MAX_GROUP_RINGS) {
          NH_Note("the groups list exceeds the maximum of %d for the strategy '%s'. Only the first %d groups will be configured.",
                  MAX_GROUP_RINGS, strategy_name.c_str(), MAX_GROUP_RINGS);
          groups = MAX_GROUP_RINGS;
        } else {
          groups = groups_node.size();
        }
        // resize the hosts vector.
        host_groups.reserve(groups);
        // loop through the groups
        for (unsigned int grp = 0; grp < groups; ++grp) {
          YAML::Node hosts_list = groups_node[grp];

          // a list of hosts is required.
          if (hosts_list.Type() != YAML::NodeType::Sequence) {
            throw std::invalid_argument("Invalid hosts definition, expected a sequence, '" + strategy_name + "' cannot be loaded.");
          } else {
            // loop through the hosts list.
            std::vector<std::shared_ptr<HostRecord>> hosts_inner;

            for (unsigned int hst = 0; hst < hosts_list.size(); ++hst) {
              std::shared_ptr<HostRecord> host_rec = std::make_shared<HostRecord>(hosts_list[hst].as<HostRecord>());
              host_rec->group_index                = grp;
              host_rec->host_index                 = hst;
              if (mach->is_self(host_rec->hostname.c_str())) {
                h_stat.setHostStatus(host_rec->hostname.c_str(), HostStatus_t::HOST_STATUS_DOWN, 0, Reason::SELF_DETECT);
              }
              hosts_inner.push_back(std::move(host_rec));
              num_parents++;
            }
            host_groups.push_back(std::move(hosts_inner));
          }
        }
      }
    }
  } catch (std::exception &ex) {
    NH_Note("Error parsing the strategy named '%s' due to '%s', this strategy will be ignored.", strategy_name.c_str(), ex.what());
    return false;
  }

  return true;
}

void
NextHopSelectionStrategy::markNextHopDown(const uint64_t sm_id, ParentResult &result, const uint64_t fail_threshold,
                                          const uint64_t retry_time, time_t now)
{
  time_t _now;
  now == 0 ? _now = time(nullptr) : _now = now;
  uint32_t new_fail_count                = 0;

  //  Make sure that we are being called back with with a
  //  result structure with a selected parent.
  if (result.result != PARENT_SPECIFIED) {
    return;
  }
  // If we were set through the API we currently have not failover
  //   so just return fail
  if (result.is_api_result()) {
    ink_assert(0);
    return;
  }
  uint32_t hst_size = host_groups[result.last_group].size();
  ink_assert(result.last_parent < hst_size);
  std::shared_ptr<HostRecord> h = host_groups[result.last_group][result.last_parent];

  // If the parent has already been marked down, just increment
  //   the failure count.  If this is the first mark down on a
  //   parent we need to both set the failure time and set
  //   count to one. If this was the result of a retry, we
  //   must update move the failedAt timestamp to now so that we
  //   continue negative cache the parent
  if (h->failedAt == 0 || result.retry == true) {
    { // start of lock_guard scope.
      std::lock_guard<std::mutex> lock(h->_mutex);
      if (h->failedAt == 0) {
        // Mark the parent failure time.
        h->failedAt = _now;
        if (result.retry == false) {
          new_fail_count = h->failCount = 1;
        }
      } else if (result.retry == true) {
        h->failedAt = _now;
      }
    } // end of lock_guard scope
    NH_Note("[%" PRIu64 "] NextHop %s marked as down %s:%d", sm_id, (result.retry) ? "retry" : "initially", h->hostname.c_str(),
            h->getPort(scheme));

  } else {
    int old_count = 0;

    // if the last failure was outside the retry window, set the failcount to 1 and failedAt to now.
    { // start of lock_guard_scope
      std::lock_guard<std::mutex> lock(h->_mutex);
      if ((h->failedAt + retry_time) < static_cast<unsigned>(_now)) {
        h->failCount = 1;
        h->failedAt  = _now;
      } else {
        old_count = h->failCount = 1;
      }
      new_fail_count = old_count + 1;
    } // end of lock_guard
    NH_Debug("parent_select", "[%" PRIu64 "] Parent fail count increased to %d for %s:%d", sm_id, new_fail_count,
             h->hostname.c_str(), h->getPort(scheme));
  }

  if (new_fail_count >= fail_threshold) {
    h->set_unavailable();
    NH_Note("[%" PRIu64 "] Failure threshold met failcount:%d >= threshold:%" PRIu64 ", http parent proxy %s:%d marked down", sm_id,
            new_fail_count, fail_threshold, h->hostname.c_str(), h->getPort(scheme));
    NH_Debug("parent_select", "[%" PRIu64 "] NextHop %s:%d marked unavailable, h->available=%s", sm_id, h->hostname.c_str(),
             h->getPort(scheme), (h->available) ? "true" : "false");
  }
}

void
NextHopSelectionStrategy::markNextHopUp(const uint64_t sm_id, ParentResult &result)
{
  uint32_t old_count = 0;
  //  Make sure that we are being called back with with a
  //   result structure with a parent that is being retried
  ink_assert(result.retry == true);
  if (result.result != PARENT_SPECIFIED) {
    return;
  }
  // If we were set through the API we currently have not failover
  //   so just return fail
  if (result.is_api_result()) {
    ink_assert(0);
    return;
  }
  uint32_t hst_size = host_groups[result.last_group].size();
  ink_assert(result.last_parent < hst_size);
  std::shared_ptr<HostRecord> h = host_groups[result.last_group][result.last_parent];

  if (!h->available) {
    h->set_available();
  }
  if (h->available && old_count > 0) {
    NH_Note("[%" PRIu64 "] http parent proxy %s:%d restored", sm_id, h->hostname.c_str(), h->getPort(scheme));
  }
}

namespace YAML
{
template <> struct convert<HostRecord> {
  static bool
  decode(const Node &node, HostRecord &nh)
  {
    YAML::Node nd;
    bool merge_tag_used = false;

    // check for YAML merge tag.
    if (node["<<"]) {
      nd             = node["<<"];
      merge_tag_used = true;
    } else {
      nd = node;
    }

    // lookup the hostname
    if (nd["host"]) {
      nh.hostname = nd["host"].Scalar();
    } else {
      throw std::invalid_argument("Invalid host definition, missing host name.");
    }

    // lookup the port numbers supported by this host.
    YAML::Node proto = nd["protocol"];

    if (proto.Type() != YAML::NodeType::Sequence) {
      throw std::invalid_argument("Invalid host protocol definition, expected a sequence.");
    } else {
      for (unsigned int ii = 0; ii < proto.size(); ii++) {
        YAML::Node protocol_node       = proto[ii];
        std::shared_ptr<NHProtocol> pr = std::make_shared<NHProtocol>(protocol_node.as<NHProtocol>());
        nh.protocols.push_back(std::move(pr));
      }
    }

    // get the host's weight
    YAML::Node weight;
    if (merge_tag_used) {
      weight    = node["weight"];
      nh.weight = weight.as<float>();
    } else if ((weight = nd["weight"])) {
      nh.weight = weight.as<float>();
    } else {
      NH_Note("No weight is defined for the host '%s', using default 1.0", nh.hostname.data());
      nh.weight = 1.0;
    }

    // get the host's optional hash_string
    YAML::Node hash;
    if ((hash = nd["hash_string"])) {
      nh.hash_string = hash.Scalar();
    }

    return true;
  }
};

template <> struct convert<NHProtocol> {
  static bool
  decode(const Node &node, NHProtocol &nh)
  {
    if (node["scheme"]) {
      if (node["scheme"].Scalar() == "http") {
        nh.scheme = NH_SCHEME_HTTP;
      } else if (node["scheme"].Scalar() == "https") {
        nh.scheme = NH_SCHEME_HTTPS;
      } else {
        nh.scheme = NH_SCHEME_NONE;
      }
    }
    if (node["port"]) {
      nh.port = node["port"].as<int>();
    }
    if (node["health_check_url"]) {
      nh.health_check_url = node["health_check_url"].Scalar();
    }
    return true;
  }
};
}; // namespace YAML
// namespace YAML
<commit_msg>LGTM: fix a comparison that is always false.<commit_after>/** @file

  A brief file description

  @section license License

  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you under the Apache License, Version 2.0 (the
  "License"); you may not use this file except in compliance
  with the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

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

#include <yaml-cpp/yaml.h>
#include "I_Machine.h"
#include "NextHopSelectionStrategy.h"

// ring mode strings
constexpr std::string_view alternate_rings = "alternate_ring";
constexpr std::string_view exhaust_rings   = "exhaust_ring";

// health check strings
constexpr std::string_view active_health_check  = "active";
constexpr std::string_view passive_health_check = "passive";

constexpr const char *policy_strings[] = {"NH_UNDEFINED", "NH_FIRST_LIVE", "NH_RR_STRICT",
                                          "NH_RR_IP",     "NH_RR_LATCHED", "NH_CONSISTENT_HASH"};

NextHopSelectionStrategy::NextHopSelectionStrategy(const std::string_view &name, const NHPolicyType &policy)
{
  strategy_name = name;
  policy_type   = policy;
  NH_Debug(NH_DEBUG_TAG, "Using a selection strategy of type %s", policy_strings[policy]);
}

//
// parse out the data for this strategy.
//
bool
NextHopSelectionStrategy::Init(const YAML::Node &n)
{
  NH_Debug(NH_DEBUG_TAG, "calling Init()");

  try {
    if (n["scheme"]) {
      auto scheme_val = n["scheme"].Scalar();
      if (scheme_val == "http") {
        scheme = NH_SCHEME_HTTP;
      } else if (scheme_val == "https") {
        scheme = NH_SCHEME_HTTPS;
      } else {
        scheme = NH_SCHEME_NONE;
        NH_Note("Invalid 'scheme' value, '%s', for the strategy named '%s', setting to NH_SCHEME_NONE", scheme_val.c_str(),
                strategy_name.c_str());
      }
    }

    // go_direct config.
    if (n["go_direct"]) {
      go_direct = n["go_direct"].as<bool>();
    }

    // parent_is_proxy config.
    if (n["parent_is_proxy"]) {
      parent_is_proxy = n["parent_is_proxy"].as<bool>();
    }

    // ignore_self_detect
    if (n["ignore_self_detect"]) {
      ignore_self_detect = n["ignore_self_detect"].as<bool>();
    }

    // failover node.
    YAML::Node failover_node;
    if (n["failover"]) {
      failover_node = n["failover"];
      if (failover_node["ring_mode"]) {
        auto ring_mode_val = failover_node["ring_mode"].Scalar();
        if (ring_mode_val == alternate_rings) {
          ring_mode = NH_ALTERNATE_RING;
        } else if (ring_mode_val == exhaust_rings) {
          ring_mode = NH_EXHAUST_RING;
        } else {
          ring_mode = NH_ALTERNATE_RING;
          NH_Note("Invalid 'ring_mode' value, '%s', for the strategy named '%s', using default '%s'.", ring_mode_val.c_str(),
                  strategy_name.c_str(), alternate_rings.data());
        }
      }
      if (failover_node["max_simple_retries"]) {
        max_simple_retries = failover_node["max_simple_retries"].as<int>();
      }

      YAML::Node resp_codes_node;
      if (failover_node["response_codes"]) {
        resp_codes_node = failover_node["response_codes"];
        if (resp_codes_node.Type() != YAML::NodeType::Sequence) {
          NH_Error("Error in the response_codes definition for the strategy named '%s', skipping response_codes.",
                   strategy_name.c_str());
        } else {
          for (unsigned int k = 0; k < resp_codes_node.size(); ++k) {
            auto code = resp_codes_node[k].as<int>();
            if (code > 300 && code < 599) {
              resp_codes.add(code);
            } else {
              NH_Note("Skipping invalid response code '%d' for the strategy named '%s'.", code, strategy_name.c_str());
            }
          }
          resp_codes.sort();
        }
      }
      YAML::Node health_check_node;
      if (failover_node["health_check"]) {
        health_check_node = failover_node["health_check"];
        if (health_check_node.Type() != YAML::NodeType::Sequence) {
          NH_Error("Error in the health_check definition for the strategy named '%s', skipping health_checks.",
                   strategy_name.c_str());
        } else {
          for (auto it = health_check_node.begin(); it != health_check_node.end(); ++it) {
            auto health_check = it->as<std::string>();
            if (health_check.compare(active_health_check) == 0) {
              health_checks.active = true;
            }
            if (health_check.compare(passive_health_check) == 0) {
              health_checks.passive = true;
            }
          }
        }
      }
    }

    // parse and load the host data
    YAML::Node groups_node;
    if (n["groups"]) {
      groups_node = n["groups"];
      // a groups list is required.
      if (groups_node.Type() != YAML::NodeType::Sequence) {
        throw std::invalid_argument("Invalid groups definition, expected a sequence, '" + strategy_name + "' cannot be loaded.");
      } else {
        Machine *mach      = Machine::instance();
        HostStatus &h_stat = HostStatus::instance();
        uint32_t grp_size  = groups_node.size();
        if (grp_size > MAX_GROUP_RINGS) {
          NH_Note("the groups list exceeds the maximum of %d for the strategy '%s'. Only the first %d groups will be configured.",
                  MAX_GROUP_RINGS, strategy_name.c_str(), MAX_GROUP_RINGS);
          groups = MAX_GROUP_RINGS;
        } else {
          groups = groups_node.size();
        }
        // resize the hosts vector.
        host_groups.reserve(groups);
        // loop through the groups
        for (unsigned int grp = 0; grp < groups; ++grp) {
          YAML::Node hosts_list = groups_node[grp];

          // a list of hosts is required.
          if (hosts_list.Type() != YAML::NodeType::Sequence) {
            throw std::invalid_argument("Invalid hosts definition, expected a sequence, '" + strategy_name + "' cannot be loaded.");
          } else {
            // loop through the hosts list.
            std::vector<std::shared_ptr<HostRecord>> hosts_inner;

            for (unsigned int hst = 0; hst < hosts_list.size(); ++hst) {
              std::shared_ptr<HostRecord> host_rec = std::make_shared<HostRecord>(hosts_list[hst].as<HostRecord>());
              host_rec->group_index                = grp;
              host_rec->host_index                 = hst;
              if (mach->is_self(host_rec->hostname.c_str())) {
                h_stat.setHostStatus(host_rec->hostname.c_str(), HostStatus_t::HOST_STATUS_DOWN, 0, Reason::SELF_DETECT);
              }
              hosts_inner.push_back(std::move(host_rec));
              num_parents++;
            }
            host_groups.push_back(std::move(hosts_inner));
          }
        }
      }
    }
  } catch (std::exception &ex) {
    NH_Note("Error parsing the strategy named '%s' due to '%s', this strategy will be ignored.", strategy_name.c_str(), ex.what());
    return false;
  }

  return true;
}

void
NextHopSelectionStrategy::markNextHopDown(const uint64_t sm_id, ParentResult &result, const uint64_t fail_threshold,
                                          const uint64_t retry_time, time_t now)
{
  time_t _now;
  now == 0 ? _now = time(nullptr) : _now = now;
  uint32_t new_fail_count                = 0;

  //  Make sure that we are being called back with with a
  //  result structure with a selected parent.
  if (result.result != PARENT_SPECIFIED) {
    return;
  }
  // If we were set through the API we currently have not failover
  //   so just return fail
  if (result.is_api_result()) {
    ink_assert(0);
    return;
  }
  uint32_t hst_size = host_groups[result.last_group].size();
  ink_assert(result.last_parent < hst_size);
  std::shared_ptr<HostRecord> h = host_groups[result.last_group][result.last_parent];

  // If the parent has already been marked down, just increment
  //   the failure count.  If this is the first mark down on a
  //   parent we need to both set the failure time and set
  //   count to one. If this was the result of a retry, we
  //   must update move the failedAt timestamp to now so that we
  //   continue negative cache the parent
  if (h->failedAt == 0 || result.retry == true) {
    { // start of lock_guard scope.
      std::lock_guard<std::mutex> lock(h->_mutex);
      if (h->failedAt == 0) {
        // Mark the parent failure time.
        h->failedAt = _now;
        if (result.retry == false) {
          new_fail_count = h->failCount = 1;
        }
      } else if (result.retry == true) {
        h->failedAt = _now;
      }
    } // end of lock_guard scope
    NH_Note("[%" PRIu64 "] NextHop %s marked as down %s:%d", sm_id, (result.retry) ? "retry" : "initially", h->hostname.c_str(),
            h->getPort(scheme));

  } else {
    int old_count = 0;

    // if the last failure was outside the retry window, set the failcount to 1 and failedAt to now.
    { // start of lock_guard_scope
      std::lock_guard<std::mutex> lock(h->_mutex);
      if ((h->failedAt + retry_time) < static_cast<unsigned>(_now)) {
        h->failCount = 1;
        h->failedAt  = _now;
      } else {
        old_count = h->failCount = 1;
      }
      new_fail_count = old_count + 1;
    } // end of lock_guard
    NH_Debug("parent_select", "[%" PRIu64 "] Parent fail count increased to %d for %s:%d", sm_id, new_fail_count,
             h->hostname.c_str(), h->getPort(scheme));
  }

  if (new_fail_count >= fail_threshold) {
    h->set_unavailable();
    NH_Note("[%" PRIu64 "] Failure threshold met failcount:%d >= threshold:%" PRIu64 ", http parent proxy %s:%d marked down", sm_id,
            new_fail_count, fail_threshold, h->hostname.c_str(), h->getPort(scheme));
    NH_Debug("parent_select", "[%" PRIu64 "] NextHop %s:%d marked unavailable, h->available=%s", sm_id, h->hostname.c_str(),
             h->getPort(scheme), (h->available) ? "true" : "false");
  }
}

void
NextHopSelectionStrategy::markNextHopUp(const uint64_t sm_id, ParentResult &result)
{
  //  Make sure that we are being called back with with a
  //   result structure with a parent that is being retried
  ink_assert(result.retry == true);
  if (result.result != PARENT_SPECIFIED) {
    return;
  }
  // If we were set through the API we currently have not failover
  //   so just return fail
  if (result.is_api_result()) {
    ink_assert(0);
    return;
  }
  uint32_t hst_size = host_groups[result.last_group].size();
  ink_assert(result.last_parent < hst_size);
  std::shared_ptr<HostRecord> h = host_groups[result.last_group][result.last_parent];

  if (!h->available) {
    h->set_available();
    NH_Note("[%" PRIu64 "] http parent proxy %s:%d restored", sm_id, h->hostname.c_str(), h->getPort(scheme));
  }
}

namespace YAML
{
template <> struct convert<HostRecord> {
  static bool
  decode(const Node &node, HostRecord &nh)
  {
    YAML::Node nd;
    bool merge_tag_used = false;

    // check for YAML merge tag.
    if (node["<<"]) {
      nd             = node["<<"];
      merge_tag_used = true;
    } else {
      nd = node;
    }

    // lookup the hostname
    if (nd["host"]) {
      nh.hostname = nd["host"].Scalar();
    } else {
      throw std::invalid_argument("Invalid host definition, missing host name.");
    }

    // lookup the port numbers supported by this host.
    YAML::Node proto = nd["protocol"];

    if (proto.Type() != YAML::NodeType::Sequence) {
      throw std::invalid_argument("Invalid host protocol definition, expected a sequence.");
    } else {
      for (unsigned int ii = 0; ii < proto.size(); ii++) {
        YAML::Node protocol_node       = proto[ii];
        std::shared_ptr<NHProtocol> pr = std::make_shared<NHProtocol>(protocol_node.as<NHProtocol>());
        nh.protocols.push_back(std::move(pr));
      }
    }

    // get the host's weight
    YAML::Node weight;
    if (merge_tag_used) {
      weight    = node["weight"];
      nh.weight = weight.as<float>();
    } else if ((weight = nd["weight"])) {
      nh.weight = weight.as<float>();
    } else {
      NH_Note("No weight is defined for the host '%s', using default 1.0", nh.hostname.data());
      nh.weight = 1.0;
    }

    // get the host's optional hash_string
    YAML::Node hash;
    if ((hash = nd["hash_string"])) {
      nh.hash_string = hash.Scalar();
    }

    return true;
  }
};

template <> struct convert<NHProtocol> {
  static bool
  decode(const Node &node, NHProtocol &nh)
  {
    if (node["scheme"]) {
      if (node["scheme"].Scalar() == "http") {
        nh.scheme = NH_SCHEME_HTTP;
      } else if (node["scheme"].Scalar() == "https") {
        nh.scheme = NH_SCHEME_HTTPS;
      } else {
        nh.scheme = NH_SCHEME_NONE;
      }
    }
    if (node["port"]) {
      nh.port = node["port"].as<int>();
    }
    if (node["health_check_url"]) {
      nh.health_check_url = node["health_check_url"].Scalar();
    }
    return true;
  }
};
}; // namespace YAML
// namespace YAML
<|endoftext|>