<commit_before>/******************************************************************************
 *                       ____    _    _____                                   *
 *                      / ___|  / \  |  ___|    C++                           *
 *                     | |     / _ \ | |_       Actor                         *
 *                     | |___ / ___ \|  _|      Framework                     *
 *                      \____/_/   \_|_|                                      *
 *                                                                            *
 * Copyright 2011-2018 Dominik Charousset                                     *
 *                                                                            *
 * Distributed under the terms and conditions of the BSD 3-Clause License or  *
 * (at your option) under the terms and conditions of the Boost Software      *
 * License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE.       *
 *                                                                            *
 * If you did not receive a copy of the license files, see                    *
 * http://opensource.org/licenses/BSD-3-Clause and                            *
 * http://www.boost.org/LICENSE_1_0.txt.                                      *
 ******************************************************************************/

#pragma once

#include "caf/stream_deserializer.hpp"
#include "caf/streambuf.hpp"

namespace caf {

/// Implements the deserializer interface with a binary serialization protocol.
class binary_deserializer final : public deserializer {
public:
  // -- member types -----------------------------------------------------------

  using super = deserializer;

  using buffer = std::vector<char>;

  // -- constructors, destructors, and assignment operators --------------------

  binary_deserializer(actor_system& sys, const char* buf, size_t buf_size);

  binary_deserializer(execution_unit* ctx, const char* buf, size_t buf_size);

  binary_deserializer(actor_system& sys, const buffer& buf);

  binary_deserializer(execution_unit* ctx, const buffer& buf);

  // -- overridden member functions --------------------------------------------

  error begin_object(uint16_t& typenr, std::string& name) override;

  error end_object() override;

  error begin_sequence(size_t& list_size) override;

  error end_sequence() override;

  error apply_raw(size_t num_bytes, void* data) override;

protected:
  error apply_impl(int8_t&) override;

  error apply_impl(uint8_t&) override;

  error apply_impl(int16_t&) override;

  error apply_impl(uint16_t&) override;

  error apply_impl(int32_t&) override;

  error apply_impl(uint32_t&) override;

  error apply_impl(int64_t&) override;

  error apply_impl(uint64_t&) override;

  error apply_impl(float&) override;

  error apply_impl(double&) override;

  error apply_impl(long double&) override;

  error apply_impl(std::string&) override;

  error apply_impl(std::u16string&) override;

  error apply_impl(std::u32string&) override;

private:
  bool range_check(size_t read_size) {
    return pos_ + read_size <= end_;
  }

  const char* pos_;
  const char* end_;
};

} // namespace caf

<commit_msg>Clean up includes<commit_after>/******************************************************************************
 *                       ____    _    _____                                   *
 *                      / ___|  / \  |  ___|    C++                           *
 *                     | |     / _ \ | |_       Actor                         *
 *                     | |___ / ___ \|  _|      Framework                     *
 *                      \____/_/   \_|_|                                      *
 *                                                                            *
 * Copyright 2011-2018 Dominik Charousset                                     *
 *                                                                            *
 * Distributed under the terms and conditions of the BSD 3-Clause License or  *
 * (at your option) under the terms and conditions of the Boost Software      *
 * License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE.       *
 *                                                                            *
 * If you did not receive a copy of the license files, see                    *
 * http://opensource.org/licenses/BSD-3-Clause and                            *
 * http://www.boost.org/LICENSE_1_0.txt.                                      *
 ******************************************************************************/

#pragma once

#include <cstddef>
#include <cstdint>
#include <vector>

#include "caf/deserializer.hpp"

namespace caf {

/// Implements the deserializer interface with a binary serialization protocol.
class binary_deserializer final : public deserializer {
public:
  // -- member types -----------------------------------------------------------

  using super = deserializer;

  using buffer = std::vector<char>;

  // -- constructors, destructors, and assignment operators --------------------

  binary_deserializer(actor_system& sys, const char* buf, size_t buf_size);

  binary_deserializer(execution_unit* ctx, const char* buf, size_t buf_size);

  binary_deserializer(actor_system& sys, const buffer& buf);

  binary_deserializer(execution_unit* ctx, const buffer& buf);

  // -- overridden member functions --------------------------------------------

  error begin_object(uint16_t& typenr, std::string& name) override;

  error end_object() override;

  error begin_sequence(size_t& list_size) override;

  error end_sequence() override;

  error apply_raw(size_t num_bytes, void* data) override;

protected:
  error apply_impl(int8_t&) override;

  error apply_impl(uint8_t&) override;

  error apply_impl(int16_t&) override;

  error apply_impl(uint16_t&) override;

  error apply_impl(int32_t&) override;

  error apply_impl(uint32_t&) override;

  error apply_impl(int64_t&) override;

  error apply_impl(uint64_t&) override;

  error apply_impl(float&) override;

  error apply_impl(double&) override;

  error apply_impl(long double&) override;

  error apply_impl(std::string&) override;

  error apply_impl(std::u16string&) override;

  error apply_impl(std::u32string&) override;

private:
  bool range_check(size_t read_size) {
    return pos_ + read_size <= end_;
  }

  const char* pos_;
  const char* end_;
};

} // namespace caf
<|endoftext|>


<commit_before>#include "mainwindow.h"

#include <QDockWidget>
#include <QMenuBar>
#include <QFont>
#include <QMdiArea>
#include <QMdiSubWindow>
#include <QFileDialog>
#include <QTextStream>
#include <QToolBar>
#include <QComboBox>
#include <QTreeWidget>
#include <QLineEdit>
#include <QMessageBox>
#include <QShortcut>

#include "Languages/simplelanguage.h"

#include "webbrowser.hpp"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    mEditorFont("Monospace")
{
    this->mEditorFont.setStyleHint(QFont::TypeWriter);
    this->initMenuBar();
    this->initPanels();
    this->initToolBar();

    this->mMdi = new QMdiArea();
    this->mMdi->setViewMode(QMdiArea::TabbedView);
    //this->mMdi->setDocumentMode(true);
    this->mMdi->setTabsClosable(true);
    this->setCentralWidget(this->mMdi);

	this->mLanguage = new SimpleLanguage();
}

MainWindow::~MainWindow()
{

}

CodeEditor *MainWindow::currentEditor()
{
    auto *window = this->mMdi->currentSubWindow();
    if(window == nullptr)
        return nullptr;
    return qobject_cast<CodeEditor*>(window->widget());
}

CodeEditor *MainWindow::newEditor()
{
    auto *editor = new CodeEditor();
    editor->setFont(this->mEditorFont);
    editor->setTabStopWidth(20);
    editor->setLanguage(this->mLanguage);

    auto *window = this->mMdi->addSubWindow(editor);
    window->setAttribute(Qt::WA_DeleteOnClose);
    window->show();

    return editor;
}

void MainWindow::emptyAction()
{
	QMessageBox(QMessageBox::Information, this->windowTitle(), "Not implemented yet").exec();
}

void MainWindow::newFile()
{
    auto *doc = this->newEditor();
    doc->setPlainText("");
    doc->setWindowTitle("*");
}

void MainWindow::loadFile()
{
    auto fileName = QFileDialog::getOpenFileName(
                this,
                "Open File",
                ".",
                "All Files (*)");
    if(fileName.isNull())
        return;
    QFile file(fileName);
    if(file.exists() == false)
        return;

    auto *editor = this->newEditor();
    editor->open(fileName);
}

void MainWindow::closeFile()
{
    auto *window = this->mMdi->currentSubWindow();
    if(window != nullptr) {
        window->deleteLater();
    }
}

void MainWindow::saveFile()
{
    auto *editor = this->currentEditor();
    if(editor == nullptr)
        return;

    if(editor->fileName().isNull()){
        this->saveFileAs();
    } else {
        editor->save();
    }
}

void MainWindow::saveFileAs()
{
    auto *editor = this->currentEditor();
    if(editor == nullptr)
        return;
    QString fileName = QFileDialog::getSaveFileName(this, "Save file...", ".", "All Files (*)");
    if(fileName.isNull())
        return;
    editor->save(fileName);
}

void MainWindow::copy()
{
    auto *editor = this->currentEditor();
    editor->copy();
}

void MainWindow::cut()
{
    auto *editor = this->currentEditor();
    editor->cut();
}

void MainWindow::paste()
{
    auto *editor = this->currentEditor();
    editor->paste();
}

void MainWindow::selectAll()
{
    auto *editor = this->currentEditor();
    editor->selectAll();
}

void MainWindow::undo()
{
    auto *editor = this->currentEditor();
    editor->undo();
}

void MainWindow::redo()
{
    auto *editor = this->currentEditor();
    editor->redo();
}

void MainWindow::search()
{
	auto *editor = this->currentEditor();
	if(editor == nullptr)
		return;
	editor->search(this->mSearchField->text());
}

void MainWindow::searchWeb()
{
	this->mDockBrowser->setVisible(true);
	this->mBrowser->search(this->mSearchField->text());
}

void MainWindow::toggleOutput()
{
	this->mDockOutput->setVisible(!this->mDockOutput->isVisible());
	this->aOutput->setChecked(this->mDockOutput->isVisible());
}

void MainWindow::toggleWebBrowser()
{
	this->mDockBrowser->setVisible(!this->mDockBrowser->isVisible());
	this->aBrowser->setChecked(this->mDockBrowser->isVisible());
}

void MainWindow::toggleCodeJumper()
{
	this->mDockJumper->setVisible(!this->mDockJumper->isVisible());
	this->aCodeJumper->setChecked(this->mDockJumper->isVisible());
}

void MainWindow::initPanels()
{
    // Code Jumper
    {
		this->mDockJumper  = new QDockWidget();
		this->mDockJumper->setWindowTitle("Code Jumper");

        auto *tree = new QTreeWidget();
        {
            auto *root = new QTreeWidgetItem();
            root->setText(0, "file-a.c");
            tree->addTopLevelItem(root);

            root = new QTreeWidgetItem();
            root->setText(0, "file-b.c");
            tree->addTopLevelItem(root);
        }
		this->mDockJumper->setWidget(tree);

		this->addDockWidget(Qt::LeftDockWidgetArea, this->mDockJumper);
    }

    // Output
    {
		this->mDockOutput = new QDockWidget();
		this->mDockOutput->setWindowTitle("Output");
		this->mDockOutput->setWindowIcon(QIcon("://Icons/appbar.console.svg"));

        auto *output = new QTextEdit();
        output->setReadOnly(true);
        output->setText(">cat data.dat");

		this->mDockOutput->setWidget(output);

		this->addDockWidget(Qt::BottomDockWidgetArea, this->mDockOutput);
    }

    // Browser
	//*
    {
		this->mDockBrowser = new QDockWidget();
		this->mDockBrowser->setWindowTitle("Browser");
		this->mDockBrowser->setWidget(this->mBrowser = new WebBrowser());
		this->mDockBrowser->setVisible(false);
		this->addDockWidget(Qt::RightDockWidgetArea, this->mDockBrowser);
    }
    //*/
}

template<typename T, typename R>
QAction *MainWindow::menuItem(
		T *menu,
        const QString &title,
        const QString &hotkey,
		const QIcon &icon,
		const R &fn)
{
	QAction *a = menu->addAction(icon, title);
	if(hotkey.isEmpty() == false)
        a->setShortcut(QKeySequence(hotkey));
    if(fn != nullptr)
        connect(a, &QAction::triggered, this, fn);
    return a;
}

void MainWindow::initMenuBar()
{
    QMenuBar *menu = this->menuBar();

    QMenu *fileMenu = menu->addMenu("&File");
    connect(
        fileMenu, &QMenu::aboutToShow,
        this, &MainWindow::updateFileMenu);
	menuItem(fileMenu, "New", "Ctrl+N", QIcon("://Icons/appbar.page.new.svg"), &MainWindow::newFile);
	menuItem(fileMenu, "Open...", "Ctrl+O", QIcon("://Icons/appbar.folder.open.svg"), &MainWindow::loadFile);
    fileMenu->addSeparator();
	this->aSave = menuItem(fileMenu, "Save", "Ctrl+S", QIcon("://Icons/appbar.save.svg"), &MainWindow::saveFile);
	this->aSaveAs = menuItem(fileMenu, "Save As...", "Ctrl+Shift+S", QIcon(), &MainWindow::saveFileAs);
	this->aClose = menuItem(fileMenu, "Close", "", QIcon("://Icons/appbar.close.svg"), &MainWindow::closeFile);
    fileMenu->addSeparator();
	menuItem(fileMenu, "Quit", "Alt+F4", QIcon(), &QWidget::close);

    QMenu *editMenu = menu->addMenu("&Edit");
    connect(
        editMenu, &QMenu::aboutToShow,
        this, &MainWindow::updateEditMenu);
	this->aUndo = menuItem(editMenu, "Undo", "Ctrl+Z", QIcon("://Icons/appbar.undo.svg"), &MainWindow::undo);
	this->aRedo = menuItem(editMenu, "Redo", "Ctrl+Y", QIcon("://Icons/appbar.redo.svg"), &MainWindow::redo);
    editMenu->addSeparator();
	this->aCut = menuItem(editMenu, "Cut", "Ctrl+X", QIcon(), &MainWindow::cut);
	this->aCopy = menuItem(editMenu, "Copy", "Ctrl+C", QIcon(), &MainWindow::copy);
	this->aPaste = menuItem(editMenu, "Paste", "Ctrl+V", QIcon(), &MainWindow::paste);
    editMenu->addSeparator();
	this->aSelectAll = menuItem(editMenu, "Select All", "Ctrl+A", QIcon(), &MainWindow::selectAll);

    QMenu *toolsMenu = menu->addMenu("&Tools");

	QMenu *windowMenu = menu->addMenu("&Window");
	connect(
		windowMenu, &QMenu::aboutToShow,
		this, &MainWindow::updateWindowMenu);
	this->aOutput = menuItem(windowMenu, "Output", "", QIcon("://Icons/appbar.console.svg"), &MainWindow::toggleOutput);
	this->aOutput->setCheckable(true);
	this->aBrowser = menuItem(windowMenu, "Web Browser", "", QIcon("://Icons/appbar.globe.wire.svg"), &MainWindow::toggleWebBrowser);
	this->aBrowser->setCheckable(true);
	this->aCodeJumper = menuItem(windowMenu, "Code Jumper", "", QIcon(), &MainWindow::toggleCodeJumper);
	this->aCodeJumper->setCheckable(true);

    QMenu *helpMenu = menu->addMenu("&Help");
}

void MainWindow::updateFileMenu()
{
    auto *editor = this->currentEditor();

    this->aSave->setEnabled(editor != nullptr);
    this->aSaveAs->setEnabled(editor != nullptr);
    this->aClose->setEnabled(editor != nullptr);
}

void MainWindow::updateEditMenu()
{
    auto *editor = this->currentEditor();

    this->aUndo->setEnabled((editor != nullptr) && (editor->document()->isUndoAvailable()));
    this->aRedo->setEnabled((editor != nullptr) && (editor->document()->isRedoAvailable()));
    this->aCut->setEnabled((editor != nullptr) && (editor->textCursor().hasSelection()));
    this->aCopy->setEnabled((editor != nullptr) && (editor->textCursor().hasSelection()));
    this->aPaste->setEnabled((editor != nullptr) && (editor->canPaste()));
    this->aSelectAll->setEnabled(editor != nullptr);
}

void MainWindow::updateWindowMenu()
{
	this->aBrowser->setChecked(this->mDockBrowser->isVisible());
	this->aOutput->setChecked(this->mDockOutput->isVisible());
	this->aCodeJumper->setChecked(this->mDockJumper->isVisible());
}

void MainWindow::initToolBar()
{
    {
        auto *bar = this->addToolBar("File Control");
		menuItem(bar, "New", "", QIcon("://Icons/appbar.page.new.svg"), &MainWindow::newFile);
		menuItem(bar, "Open...", "", QIcon("://Icons/appbar.folder.open.svg"), &MainWindow::loadFile);
		menuItem(bar, "Save", "", QIcon("://Icons/appbar.save.svg"), &MainWindow::saveFile);
        bar->addAction("Save All");
		menuItem(bar, "Close", "", QIcon("://Icons/appbar.close.svg"), &MainWindow::closeFile);
    }
    {
        auto *bar = this->addToolBar("Editor Primitives");
		menuItem(bar, "Undo", "", QIcon("://Icons/appbar.undo.svg"), &MainWindow::undo);
		menuItem(bar, "Redo", "", QIcon("://Icons/appbar.redo.svg"), &MainWindow::redo);
        bar->addSeparator();
        bar->addAction("Cut");
        bar->addAction("Copy");
        bar->addAction("Paste");
    }
    {
        auto *bar = this->addToolBar("Language Options");
        {
            auto *box = new QComboBox();
            box->addItem("C/C++");
            box->addItem("C#");
            box->addItem("Lua");
            box->addItem("Vala");
            box->addItem("SolidMarkup");
            bar->addWidget(box);
        }
		menuItem(bar, "Language Settings", "", QIcon("://Icons/appbar.settings.svg"), &MainWindow::emptyAction);
    }
    {
        auto *bar = this->addToolBar("Search");
        {
			this->mSearchField = new QLineEdit();
			this->mSearchField->setPlaceholderText("Search...");
			this->mSearchField->setMaximumWidth(200);

			auto *shortcut = new QShortcut(QKeySequence("Ctrl+F"), this);
			QObject::connect(
				shortcut, &QShortcut::activated,
				[this]() {
					this->mSearchField->setFocus();
					this->mSearchField->setText("");
				});

			shortcut = new QShortcut(QKeySequence("Alt+Return"), this->mSearchField);
			QObject::connect(
				shortcut, &QShortcut::activated,
				this, &MainWindow::searchWeb);

			shortcut = new QShortcut(QKeySequence("Return"), this->mSearchField);
			QObject::connect(
				shortcut, &QShortcut::activated,
				this, &MainWindow::search);

			shortcut = new QShortcut(QKeySequence("Escape"), this->mSearchField);
			QObject::connect(
				shortcut, &QShortcut::activated,
				[this]() {
					auto editor = this->currentEditor();
					if(editor != nullptr)
						editor->setFocus();
				});

			bar->addWidget(this->mSearchField);
		}
		menuItem(bar, "Find", "", QIcon("://Icons/appbar.page.search.svg"), &MainWindow::search);
		menuItem(bar, "Find in Web", "", QIcon("://Icons/appbar.globe.wire.svg"), &MainWindow::searchWeb);
    }
}
<commit_msg>Adds navigate to site functionality to search box.<commit_after>#include "mainwindow.h"

#include <QDockWidget>
#include <QMenuBar>
#include <QFont>
#include <QMdiArea>
#include <QMdiSubWindow>
#include <QFileDialog>
#include <QTextStream>
#include <QToolBar>
#include <QComboBox>
#include <QTreeWidget>
#include <QLineEdit>
#include <QMessageBox>
#include <QShortcut>

#include "Languages/simplelanguage.h"

#include "webbrowser.hpp"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    mEditorFont("Monospace")
{
    this->mEditorFont.setStyleHint(QFont::TypeWriter);
    this->initMenuBar();
    this->initPanels();
    this->initToolBar();

    this->mMdi = new QMdiArea();
    this->mMdi->setViewMode(QMdiArea::TabbedView);
    //this->mMdi->setDocumentMode(true);
    this->mMdi->setTabsClosable(true);
    this->setCentralWidget(this->mMdi);

	this->mLanguage = new SimpleLanguage();
}

MainWindow::~MainWindow()
{

}

CodeEditor *MainWindow::currentEditor()
{
    auto *window = this->mMdi->currentSubWindow();
    if(window == nullptr)
        return nullptr;
    return qobject_cast<CodeEditor*>(window->widget());
}

CodeEditor *MainWindow::newEditor()
{
    auto *editor = new CodeEditor();
    editor->setFont(this->mEditorFont);
    editor->setTabStopWidth(20);
    editor->setLanguage(this->mLanguage);

    auto *window = this->mMdi->addSubWindow(editor);
    window->setAttribute(Qt::WA_DeleteOnClose);
    window->show();

    return editor;
}

void MainWindow::emptyAction()
{
	QMessageBox(QMessageBox::Information, this->windowTitle(), "Not implemented yet").exec();
}

void MainWindow::newFile()
{
    auto *doc = this->newEditor();
    doc->setPlainText("");
    doc->setWindowTitle("*");
}

void MainWindow::loadFile()
{
    auto fileName = QFileDialog::getOpenFileName(
                this,
                "Open File",
                ".",
                "All Files (*)");
    if(fileName.isNull())
        return;
    QFile file(fileName);
    if(file.exists() == false)
        return;

    auto *editor = this->newEditor();
    editor->open(fileName);
}

void MainWindow::closeFile()
{
    auto *window = this->mMdi->currentSubWindow();
    if(window != nullptr) {
        window->deleteLater();
    }
}

void MainWindow::saveFile()
{
    auto *editor = this->currentEditor();
    if(editor == nullptr)
        return;

    if(editor->fileName().isNull()){
        this->saveFileAs();
    } else {
        editor->save();
    }
}

void MainWindow::saveFileAs()
{
    auto *editor = this->currentEditor();
    if(editor == nullptr)
        return;
    QString fileName = QFileDialog::getSaveFileName(this, "Save file...", ".", "All Files (*)");
    if(fileName.isNull())
        return;
    editor->save(fileName);
}

void MainWindow::copy()
{
    auto *editor = this->currentEditor();
    editor->copy();
}

void MainWindow::cut()
{
    auto *editor = this->currentEditor();
    editor->cut();
}

void MainWindow::paste()
{
    auto *editor = this->currentEditor();
    editor->paste();
}

void MainWindow::selectAll()
{
    auto *editor = this->currentEditor();
    editor->selectAll();
}

void MainWindow::undo()
{
    auto *editor = this->currentEditor();
    editor->undo();
}

void MainWindow::redo()
{
    auto *editor = this->currentEditor();
    editor->redo();
}

void MainWindow::search()
{
	auto *editor = this->currentEditor();
	if(editor == nullptr)
		return;
	if(this->mSearchField->text().length() > 0)
		editor->search(this->mSearchField->text());
}

void MainWindow::searchWeb()
{
	QString text = this->mSearchField->text();
	if(text.length() > 0)
	{
		this->mDockBrowser->setVisible(true);
		if(QUrl(text).scheme().isEmpty()) {
			this->mBrowser->search(this->mSearchField->text());
		} else {
			this->mBrowser->navigateTo(QUrl(text));
		}
	}
}

void MainWindow::toggleOutput()
{
	this->mDockOutput->setVisible(!this->mDockOutput->isVisible());
	this->aOutput->setChecked(this->mDockOutput->isVisible());
}

void MainWindow::toggleWebBrowser()
{
	this->mDockBrowser->setVisible(!this->mDockBrowser->isVisible());
	this->aBrowser->setChecked(this->mDockBrowser->isVisible());
}

void MainWindow::toggleCodeJumper()
{
	this->mDockJumper->setVisible(!this->mDockJumper->isVisible());
	this->aCodeJumper->setChecked(this->mDockJumper->isVisible());
}

void MainWindow::initPanels()
{
    // Code Jumper
    {
		this->mDockJumper  = new QDockWidget();
		this->mDockJumper->setWindowTitle("Code Jumper");

        auto *tree = new QTreeWidget();
        {
            auto *root = new QTreeWidgetItem();
            root->setText(0, "file-a.c");
            tree->addTopLevelItem(root);

            root = new QTreeWidgetItem();
            root->setText(0, "file-b.c");
            tree->addTopLevelItem(root);
        }
		this->mDockJumper->setWidget(tree);

		this->addDockWidget(Qt::LeftDockWidgetArea, this->mDockJumper);
    }

    // Output
    {
		this->mDockOutput = new QDockWidget();
		this->mDockOutput->setWindowTitle("Output");
		this->mDockOutput->setWindowIcon(QIcon("://Icons/appbar.console.svg"));

        auto *output = new QTextEdit();
        output->setReadOnly(true);
        output->setText(">cat data.dat");

		this->mDockOutput->setWidget(output);

		this->addDockWidget(Qt::BottomDockWidgetArea, this->mDockOutput);
    }

    // Browser
	//*
    {
		this->mDockBrowser = new QDockWidget();
		this->mDockBrowser->setWindowTitle("Browser");
		this->mDockBrowser->setWidget(this->mBrowser = new WebBrowser());
		this->mDockBrowser->setVisible(false);
		this->addDockWidget(Qt::RightDockWidgetArea, this->mDockBrowser);
    }
    //*/
}

template<typename T, typename R>
QAction *MainWindow::menuItem(
		T *menu,
        const QString &title,
        const QString &hotkey,
		const QIcon &icon,
		const R &fn)
{
	QAction *a = menu->addAction(icon, title);
	if(hotkey.isEmpty() == false)
        a->setShortcut(QKeySequence(hotkey));
    if(fn != nullptr)
        connect(a, &QAction::triggered, this, fn);
    return a;
}

void MainWindow::initMenuBar()
{
    QMenuBar *menu = this->menuBar();

    QMenu *fileMenu = menu->addMenu("&File");
    connect(
        fileMenu, &QMenu::aboutToShow,
        this, &MainWindow::updateFileMenu);
	menuItem(fileMenu, "New", "Ctrl+N", QIcon("://Icons/appbar.page.new.svg"), &MainWindow::newFile);
	menuItem(fileMenu, "Open...", "Ctrl+O", QIcon("://Icons/appbar.folder.open.svg"), &MainWindow::loadFile);
    fileMenu->addSeparator();
	this->aSave = menuItem(fileMenu, "Save", "Ctrl+S", QIcon("://Icons/appbar.save.svg"), &MainWindow::saveFile);
	this->aSaveAs = menuItem(fileMenu, "Save As...", "Ctrl+Shift+S", QIcon(), &MainWindow::saveFileAs);
	this->aClose = menuItem(fileMenu, "Close", "", QIcon("://Icons/appbar.close.svg"), &MainWindow::closeFile);
    fileMenu->addSeparator();
	menuItem(fileMenu, "Quit", "Alt+F4", QIcon(), &QWidget::close);

    QMenu *editMenu = menu->addMenu("&Edit");
    connect(
        editMenu, &QMenu::aboutToShow,
        this, &MainWindow::updateEditMenu);
	this->aUndo = menuItem(editMenu, "Undo", "Ctrl+Z", QIcon("://Icons/appbar.undo.svg"), &MainWindow::undo);
	this->aRedo = menuItem(editMenu, "Redo", "Ctrl+Y", QIcon("://Icons/appbar.redo.svg"), &MainWindow::redo);
    editMenu->addSeparator();
	this->aCut = menuItem(editMenu, "Cut", "Ctrl+X", QIcon(), &MainWindow::cut);
	this->aCopy = menuItem(editMenu, "Copy", "Ctrl+C", QIcon(), &MainWindow::copy);
	this->aPaste = menuItem(editMenu, "Paste", "Ctrl+V", QIcon(), &MainWindow::paste);
    editMenu->addSeparator();
	this->aSelectAll = menuItem(editMenu, "Select All", "Ctrl+A", QIcon(), &MainWindow::selectAll);

    QMenu *toolsMenu = menu->addMenu("&Tools");

	QMenu *windowMenu = menu->addMenu("&Window");
	connect(
		windowMenu, &QMenu::aboutToShow,
		this, &MainWindow::updateWindowMenu);
	this->aOutput = menuItem(windowMenu, "Output", "", QIcon("://Icons/appbar.console.svg"), &MainWindow::toggleOutput);
	this->aOutput->setCheckable(true);
	this->aBrowser = menuItem(windowMenu, "Web Browser", "", QIcon("://Icons/appbar.globe.wire.svg"), &MainWindow::toggleWebBrowser);
	this->aBrowser->setCheckable(true);
	this->aCodeJumper = menuItem(windowMenu, "Code Jumper", "", QIcon(), &MainWindow::toggleCodeJumper);
	this->aCodeJumper->setCheckable(true);

    QMenu *helpMenu = menu->addMenu("&Help");
}

void MainWindow::updateFileMenu()
{
    auto *editor = this->currentEditor();

    this->aSave->setEnabled(editor != nullptr);
    this->aSaveAs->setEnabled(editor != nullptr);
    this->aClose->setEnabled(editor != nullptr);
}

void MainWindow::updateEditMenu()
{
    auto *editor = this->currentEditor();

    this->aUndo->setEnabled((editor != nullptr) && (editor->document()->isUndoAvailable()));
    this->aRedo->setEnabled((editor != nullptr) && (editor->document()->isRedoAvailable()));
    this->aCut->setEnabled((editor != nullptr) && (editor->textCursor().hasSelection()));
    this->aCopy->setEnabled((editor != nullptr) && (editor->textCursor().hasSelection()));
    this->aPaste->setEnabled((editor != nullptr) && (editor->canPaste()));
    this->aSelectAll->setEnabled(editor != nullptr);
}

void MainWindow::updateWindowMenu()
{
	this->aBrowser->setChecked(this->mDockBrowser->isVisible());
	this->aOutput->setChecked(this->mDockOutput->isVisible());
	this->aCodeJumper->setChecked(this->mDockJumper->isVisible());
}

void MainWindow::initToolBar()
{
    {
        auto *bar = this->addToolBar("File Control");
		menuItem(bar, "New", "", QIcon("://Icons/appbar.page.new.svg"), &MainWindow::newFile);
		menuItem(bar, "Open...", "", QIcon("://Icons/appbar.folder.open.svg"), &MainWindow::loadFile);
		menuItem(bar, "Save", "", QIcon("://Icons/appbar.save.svg"), &MainWindow::saveFile);
        bar->addAction("Save All");
		menuItem(bar, "Close", "", QIcon("://Icons/appbar.close.svg"), &MainWindow::closeFile);
    }
    {
        auto *bar = this->addToolBar("Editor Primitives");
		menuItem(bar, "Undo", "", QIcon("://Icons/appbar.undo.svg"), &MainWindow::undo);
		menuItem(bar, "Redo", "", QIcon("://Icons/appbar.redo.svg"), &MainWindow::redo);
        bar->addSeparator();
        bar->addAction("Cut");
        bar->addAction("Copy");
        bar->addAction("Paste");
    }
    {
        auto *bar = this->addToolBar("Language Options");
        {
            auto *box = new QComboBox();
            box->addItem("C/C++");
            box->addItem("C#");
            box->addItem("Lua");
            box->addItem("Vala");
            box->addItem("SolidMarkup");
            bar->addWidget(box);
        }
		menuItem(bar, "Language Settings", "", QIcon("://Icons/appbar.settings.svg"), &MainWindow::emptyAction);
    }
    {
        auto *bar = this->addToolBar("Search");
        {
			this->mSearchField = new QLineEdit();
			this->mSearchField->setPlaceholderText("Search...");
			this->mSearchField->setMaximumWidth(200);

			auto *shortcut = new QShortcut(QKeySequence("Ctrl+F"), this);
			QObject::connect(
				shortcut, &QShortcut::activated,
				[this]() {
					this->mSearchField->setFocus();
					this->mSearchField->setText("");
				});

			shortcut = new QShortcut(QKeySequence("Alt+Return"), this->mSearchField);
			QObject::connect(
				shortcut, &QShortcut::activated,
				this, &MainWindow::searchWeb);

			shortcut = new QShortcut(QKeySequence("Return"), this->mSearchField);
			QObject::connect(
				shortcut, &QShortcut::activated,
				this, &MainWindow::search);

			shortcut = new QShortcut(QKeySequence("Escape"), this->mSearchField);
			QObject::connect(
				shortcut, &QShortcut::activated,
				[this]() {
					auto editor = this->currentEditor();
					if(editor != nullptr)
						editor->setFocus();
				});

			bar->addWidget(this->mSearchField);
		}
		menuItem(bar, "Find", "", QIcon("://Icons/appbar.page.search.svg"), &MainWindow::search);
		menuItem(bar, "Find in Web", "", QIcon("://Icons/appbar.globe.wire.svg"), &MainWindow::searchWeb);
    }
}
<|endoftext|>


<commit_before>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2017 Nathan Osman
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#include <cstring>

#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QMetaObject>
#include <QMetaProperty>
#include <QtEndian>

#include <nitroshare/application.h>
#include <nitroshare/bundle.h>
#include <nitroshare/handler.h>
#include <nitroshare/handlerregistry.h>
#include <nitroshare/item.h>
#include <nitroshare/logger.h>
#include <nitroshare/message.h>
#include <nitroshare/packet.h>
#include <nitroshare/transfer.h>
#include <nitroshare/transport.h>

#include "transfer_p.h"

const QString MessageTag = "Transfer";

TransferPrivate::TransferPrivate(Transfer *parent, Application *application, Transport *transport,
                                 Bundle *bundle, Transfer::Direction direction)
    : QObject(parent),
      q(parent),
      application(application),
      transport(transport),
      bundle(bundle),
      protocolState(TransferHeader),
      direction(direction),
      state(Transfer::Connecting),
      progress(0),
      itemIndex(0),
      itemCount(bundle ? bundle->rowCount(QModelIndex()) : 0),
      bytesTransferred(0),
      bytesTotal(bundle ? bundle->totalSize() : 0),
      currentItem(nullptr),
      currentItemBytesTransferred(0),
      currentItemBytesTotal(0)
{
    // If sending data, trigger the first packet after connection
    if (direction == Transfer::Send) {
        connect(transport, &Transport::connected, this, &TransferPrivate::onPacketSent);
    }

    connect(transport, &Transport::packetReceived, this, &TransferPrivate::onPacketReceived);
    connect(transport, &Transport::packetSent, this, &TransferPrivate::onPacketSent);
    connect(transport, &Transport::error, this, &TransferPrivate::onError);
}

void TransferPrivate::sendTransferHeader()
{
    QJsonObject object{
        { "name", application->deviceName() },
        { "count", QString::number(bundle->rowCount(QModelIndex())) },
        { "size", QString::number(bundle->totalSize()) }
    };

    Packet packet(Packet::Json, QJsonDocument(object).toJson());
    transport->sendPacket(&packet);

    // The next packet will be an item header
    protocolState = ItemHeader;
}

void TransferPrivate::sendItemHeader()
{
    // Grab the next item and attempt to open it
    currentItem = bundle->index(itemIndex, 0).data(Qt::UserRole).value<Item*>();
    if (!currentItem->open(Item::Read)) {
        setError(tr("unable to open \"%1\" for reading").arg(currentItem->name()), true);
        return;
    }

    // Reset transfer stats
    currentItemBytesTransferred = 0;
    currentItemBytesTotal = currentItem->size();

    // Build a JSON object with all of the properties
    const QMetaObject *metaObj = currentItem->metaObject();
    QJsonObject object;

    // Skip the "name" QObject property and convert all longlongs to strings
    for (int i = 1; i < metaObj->propertyCount(); ++i) {
        auto property = metaObj->property(i);
        auto name = property.name();
        auto value = currentItem->property(name);
        if (property.type() == QVariant::LongLong) {
            value = QString::number(value.toLongLong());
        }
        object.insert(name, QJsonValue::fromVariant(value));
    }

    // Send the item header
    Packet packet(Packet::Json, QJsonDocument(object).toJson());
    transport->sendPacket(&packet);

    // If the item has a size, switch states; otherwise send the next item
    if (currentItemBytesTotal) {
        protocolState = ItemContent;
    } else {
        sendNext();
    }
}

void TransferPrivate::sendItemContent()
{
    QByteArray data = currentItem->read();
    Packet packet(Packet::Binary, data);
    transport->sendPacket(&packet);

    // Increment the number of bytes written to the socket
    bytesTransferred += data.length();
    currentItemBytesTransferred += data.length();

    updateProgress();

    // If the item completed, send the next one
    if (currentItemBytesTransferred >= currentItemBytesTotal) {
        sendNext();
    }
}

void TransferPrivate::sendNext()
{
    // Close the current item and increment the index
    currentItem->close();
    ++itemIndex;

    // If all items have been sent, move to the finished state and wait for
    // the success packet; otherwise, prepare to send the next item
    if (itemIndex == itemCount) {
        protocolState = Finished;
    } else {
        protocolState = ItemHeader;
    }
}

void TransferPrivate::processTransferHeader(Packet *packet)
{
    QJsonParseError error;
    QJsonObject object = QJsonDocument::fromJson(packet->content(), &error).object();
    if (error.error != QJsonParseError::NoError) {
        setError(QString("transfer header: %1").arg(error.errorString()), true);
        return;
    }

    // If the device name was provided, use it
    deviceName = object.value("name").toString();
    if (!deviceName.isEmpty()) {
        emit q->deviceNameChanged(deviceName);
    }

    // Strings must be used for 64-bit numbers
    itemCount = object.value("count").toString().toInt();
    bytesTotal = object.value("size").toString().toLongLong();

    // Prepare to receive the first item
    protocolState = ItemHeader;
}

void TransferPrivate::processItemHeader(Packet *packet)
{
    QJsonParseError error;
    QJsonObject object = QJsonDocument::fromJson(packet->content(), &error).object();
    if (error.error != QJsonParseError::NoError) {
        setError(QString("item header: %1").arg(error.errorString()), true);
        return;
    }

    // In order to maintain compatibility with legacy versions (which is very
    // desirable), if "type" is not in the object, assume "file" unless
    // "directory" is present (in which case, use that)
    QString type;
    if (object.contains("type")) {
        type = object.value("type").toString();
    } else {
        if (object.contains("directory")) {
            type = "directory";
        } else {
            type = "file";
        }
    }

    // Attempt to locate a handler for the type
    Handler *handler = application->handlerRegistry()->find(type);
    if (!handler) {
        setError(tr("unrecognized item type \"%1\"").arg(type), true);
        return;
    }

    // Use the handler to create an item and open it
    currentItem = handler->createItem(type, object.toVariantMap());
    currentItem->setParent(this);
    if (!currentItem->open(Item::Write)) {
        setError(tr("unable to open \"%1\" for writing").arg(currentItem->name()), true);
        return;
    }

    // Reset transfer stats
    currentItemBytesTransferred = 0;
    currentItemBytesTotal = currentItem->size();

    // If the item has a size, switch states; otherwise receive the next item
    if (currentItemBytesTotal) {
        protocolState = ItemContent;
    } else {
        processNext();
    }
}

void TransferPrivate::processItemContent(Packet *packet)
{
    currentItem->write(packet->content());

    // Add the number of bytes to the global & current item totals
    bytesTransferred += packet->content().size();
    currentItemBytesTransferred += packet->content().size();

    updateProgress();

    // If the current item is complete, advance to the next item or finish
    if (currentItemBytesTransferred >= currentItemBytesTotal) {
        processNext();
    }
}

void TransferPrivate::processNext()
{
    // Close & free the current item and increment the index
    currentItem->close();
    delete currentItem;
    ++itemIndex;

    // If there are no more items, send the success packet
    if (itemIndex == itemCount) {
        setSuccess(true);
    } else {
        protocolState = ItemHeader;
    }
}

void TransferPrivate::updateProgress()
{
    int newProgress = 0;
    if (bytesTotal) {
        newProgress = static_cast<int>(100.0 *
            static_cast<double>(bytesTransferred) /
            static_cast<double>(bytesTotal));
    }

    // Only update progress if it has actually changed
    if (newProgress != progress) {
        emit q->progressChanged(progress = newProgress);
    }
}

void TransferPrivate::setSuccess(bool send)
{
    if (send) {
        Packet packet(Packet::Success);
        transport->sendPacket(&packet);
    }

    emit q->stateChanged(state = Transfer::Succeeded);

    // Both peers should be aware that the transfer succeeded at this point
    transport->close();
}

void TransferPrivate::setError(const QString &message, bool send)
{
    application->logger()->log(new Message(
        Message::Error,
        MessageTag,
        message
    ));

    if (send) {
        Packet packet(Packet::Error, message.toUtf8());
        transport->sendPacket(&packet);
    }

    emit q->errorChanged(error = message);
    emit q->stateChanged(state = Transfer::Failed);

    // An error on either end necessitates the transport be closed
    transport->close();

    // The protocol dictates that the transfer is now "finished"
    protocolState = Finished;
}

void TransferPrivate::onPacketReceived(Packet *packet)
{
    // If an error packet is received, set the error and quit
    if (packet->type() == Packet::Error) {
        setError(packet->content());
        return;
    }

    if (direction == Transfer::Send) {

        // The only packet expected when sending items is the success packet
        // which indicates the receiver got all of the files
        if (protocolState == Finished && packet->type() == Packet::Success) {
            setSuccess();
            return;
        }

    } else {

        // Dispatch the packet to the appropriate method based on state
        switch (protocolState) {
        case TransferHeader:
            processTransferHeader(packet);
            return;
        case ItemHeader:
            processItemHeader(packet);
            return;
        case ItemContent:
            processItemContent(packet);
            return;
        }
    }

    // Any other packet was unexpected - assume this is an error
    setError(tr("protocol error - unexpected packet"), true);
}

void TransferPrivate::onPacketSent()
{
    // We don't care about sent packets when receiving data
    if (direction == Transfer::Receive) {
        return;
    }

    switch (protocolState) {
    case TransferHeader:
        sendTransferHeader();
    case ItemHeader:
        sendItemHeader();
    case ItemContent:
        sendItemContent();
    }
}

void TransferPrivate::onError(const QString &message)
{
    setError(message, true);
}

Transfer::Transfer(Application *application, Transport *transport, QObject *parent)
    : QObject(parent),
      d(new TransferPrivate(this, application, transport, nullptr, Transfer::Receive))
{
}

Transfer::Transfer(Application *application, Transport *transport, Bundle *bundle, QObject *parent)
    : QObject(parent),
      d(new TransferPrivate(this, application, transport, bundle, Transfer::Send))
{
}

Transfer::Direction Transfer::direction() const
{
    return d->direction;
}

Transfer::State Transfer::state() const
{
    return d->state;
}

int Transfer::progress() const
{
    return d->progress;
}

QString Transfer::deviceName() const
{
    return d->deviceName;
}

QString Transfer::error() const
{
    return d->error;
}

bool Transfer::isFinished() const
{
    return d->state == Failed || d->state == Succeeded;
}

void Transfer::cancel()
{
    d->setError(tr("transfer cancelled"), true);
}
<commit_msg>Use JsonUtil in Transfer class.<commit_after>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2017 Nathan Osman
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#include <cstring>

#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QtEndian>

#include <nitroshare/application.h>
#include <nitroshare/bundle.h>
#include <nitroshare/handler.h>
#include <nitroshare/handlerregistry.h>
#include <nitroshare/item.h>
#include <nitroshare/jsonutil.h>
#include <nitroshare/logger.h>
#include <nitroshare/message.h>
#include <nitroshare/packet.h>
#include <nitroshare/transfer.h>
#include <nitroshare/transport.h>

#include "transfer_p.h"

const QString MessageTag = "Transfer";

TransferPrivate::TransferPrivate(Transfer *parent, Application *application, Transport *transport,
                                 Bundle *bundle, Transfer::Direction direction)
    : QObject(parent),
      q(parent),
      application(application),
      transport(transport),
      bundle(bundle),
      protocolState(TransferHeader),
      direction(direction),
      state(Transfer::Connecting),
      progress(0),
      itemIndex(0),
      itemCount(bundle ? bundle->rowCount(QModelIndex()) : 0),
      bytesTransferred(0),
      bytesTotal(bundle ? bundle->totalSize() : 0),
      currentItem(nullptr),
      currentItemBytesTransferred(0),
      currentItemBytesTotal(0)
{
    // If sending data, trigger the first packet after connection
    if (direction == Transfer::Send) {
        connect(transport, &Transport::connected, this, &TransferPrivate::onPacketSent);
    }

    connect(transport, &Transport::packetReceived, this, &TransferPrivate::onPacketReceived);
    connect(transport, &Transport::packetSent, this, &TransferPrivate::onPacketSent);
    connect(transport, &Transport::error, this, &TransferPrivate::onError);
}

void TransferPrivate::sendTransferHeader()
{
    QJsonObject object{
        { "name", application->deviceName() },
        { "count", QString::number(bundle->rowCount(QModelIndex())) },
        { "size", QString::number(bundle->totalSize()) }
    };

    Packet packet(Packet::Json, QJsonDocument(object).toJson());
    transport->sendPacket(&packet);

    // The next packet will be an item header
    protocolState = ItemHeader;
}

void TransferPrivate::sendItemHeader()
{
    // Grab the next item and attempt to open it
    currentItem = bundle->index(itemIndex, 0).data(Qt::UserRole).value<Item*>();
    if (!currentItem->open(Item::Read)) {
        setError(tr("unable to open \"%1\" for reading").arg(currentItem->name()), true);
        return;
    }

    // Reset transfer stats
    currentItemBytesTransferred = 0;
    currentItemBytesTotal = currentItem->size();

    // Build a JSON object with all of the properties
    QJsonObject object = JsonUtil::objectToJson(currentItem);

    // Send the item header
    Packet packet(Packet::Json, QJsonDocument(object).toJson());
    transport->sendPacket(&packet);

    // If the item has a size, switch states; otherwise send the next item
    if (currentItemBytesTotal) {
        protocolState = ItemContent;
    } else {
        sendNext();
    }
}

void TransferPrivate::sendItemContent()
{
    QByteArray data = currentItem->read();
    Packet packet(Packet::Binary, data);
    transport->sendPacket(&packet);

    // Increment the number of bytes written to the socket
    bytesTransferred += data.length();
    currentItemBytesTransferred += data.length();

    updateProgress();

    // If the item completed, send the next one
    if (currentItemBytesTransferred >= currentItemBytesTotal) {
        sendNext();
    }
}

void TransferPrivate::sendNext()
{
    // Close the current item and increment the index
    currentItem->close();
    ++itemIndex;

    // If all items have been sent, move to the finished state and wait for
    // the success packet; otherwise, prepare to send the next item
    if (itemIndex == itemCount) {
        protocolState = Finished;
    } else {
        protocolState = ItemHeader;
    }
}

void TransferPrivate::processTransferHeader(Packet *packet)
{
    QJsonParseError error;
    QJsonObject object = QJsonDocument::fromJson(packet->content(), &error).object();
    if (error.error != QJsonParseError::NoError) {
        setError(QString("transfer header: %1").arg(error.errorString()), true);
        return;
    }

    // If the device name was provided, use it
    deviceName = object.value("name").toString();
    if (!deviceName.isEmpty()) {
        emit q->deviceNameChanged(deviceName);
    }

    // Strings must be used for 64-bit numbers
    itemCount = object.value("count").toString().toInt();
    bytesTotal = object.value("size").toString().toLongLong();

    // Prepare to receive the first item
    protocolState = ItemHeader;
}

void TransferPrivate::processItemHeader(Packet *packet)
{
    QJsonParseError error;
    QJsonObject object = QJsonDocument::fromJson(packet->content(), &error).object();
    if (error.error != QJsonParseError::NoError) {
        setError(QString("item header: %1").arg(error.errorString()), true);
        return;
    }

    // In order to maintain compatibility with legacy versions (which is very
    // desirable), if "type" is not in the object, assume "file" unless
    // "directory" is present (in which case, use that)
    QString type;
    if (object.contains("type")) {
        type = object.value("type").toString();
    } else {
        if (object.contains("directory")) {
            type = "directory";
        } else {
            type = "file";
        }
    }

    // Attempt to locate a handler for the type
    Handler *handler = application->handlerRegistry()->find(type);
    if (!handler) {
        setError(tr("unrecognized item type \"%1\"").arg(type), true);
        return;
    }

    // Use the handler to create an item and open it
    currentItem = handler->createItem(type, object.toVariantMap());
    currentItem->setParent(this);
    if (!currentItem->open(Item::Write)) {
        setError(tr("unable to open \"%1\" for writing").arg(currentItem->name()), true);
        return;
    }

    // Reset transfer stats
    currentItemBytesTransferred = 0;
    currentItemBytesTotal = currentItem->size();

    // If the item has a size, switch states; otherwise receive the next item
    if (currentItemBytesTotal) {
        protocolState = ItemContent;
    } else {
        processNext();
    }
}

void TransferPrivate::processItemContent(Packet *packet)
{
    currentItem->write(packet->content());

    // Add the number of bytes to the global & current item totals
    bytesTransferred += packet->content().size();
    currentItemBytesTransferred += packet->content().size();

    updateProgress();

    // If the current item is complete, advance to the next item or finish
    if (currentItemBytesTransferred >= currentItemBytesTotal) {
        processNext();
    }
}

void TransferPrivate::processNext()
{
    // Close & free the current item and increment the index
    currentItem->close();
    delete currentItem;
    ++itemIndex;

    // If there are no more items, send the success packet
    if (itemIndex == itemCount) {
        setSuccess(true);
    } else {
        protocolState = ItemHeader;
    }
}

void TransferPrivate::updateProgress()
{
    int newProgress = 0;
    if (bytesTotal) {
        newProgress = static_cast<int>(100.0 *
            static_cast<double>(bytesTransferred) /
            static_cast<double>(bytesTotal));
    }

    // Only update progress if it has actually changed
    if (newProgress != progress) {
        emit q->progressChanged(progress = newProgress);
    }
}

void TransferPrivate::setSuccess(bool send)
{
    if (send) {
        Packet packet(Packet::Success);
        transport->sendPacket(&packet);
    }

    emit q->stateChanged(state = Transfer::Succeeded);

    // Both peers should be aware that the transfer succeeded at this point
    transport->close();
}

void TransferPrivate::setError(const QString &message, bool send)
{
    application->logger()->log(new Message(
        Message::Error,
        MessageTag,
        message
    ));

    if (send) {
        Packet packet(Packet::Error, message.toUtf8());
        transport->sendPacket(&packet);
    }

    emit q->errorChanged(error = message);
    emit q->stateChanged(state = Transfer::Failed);

    // An error on either end necessitates the transport be closed
    transport->close();

    // The protocol dictates that the transfer is now "finished"
    protocolState = Finished;
}

void TransferPrivate::onPacketReceived(Packet *packet)
{
    // If an error packet is received, set the error and quit
    if (packet->type() == Packet::Error) {
        setError(packet->content());
        return;
    }

    if (direction == Transfer::Send) {

        // The only packet expected when sending items is the success packet
        // which indicates the receiver got all of the files
        if (protocolState == Finished && packet->type() == Packet::Success) {
            setSuccess();
            return;
        }

    } else {

        // Dispatch the packet to the appropriate method based on state
        switch (protocolState) {
        case TransferHeader:
            processTransferHeader(packet);
            return;
        case ItemHeader:
            processItemHeader(packet);
            return;
        case ItemContent:
            processItemContent(packet);
            return;
        }
    }

    // Any other packet was unexpected - assume this is an error
    setError(tr("protocol error - unexpected packet"), true);
}

void TransferPrivate::onPacketSent()
{
    // We don't care about sent packets when receiving data
    if (direction == Transfer::Receive) {
        return;
    }

    switch (protocolState) {
    case TransferHeader:
        sendTransferHeader();
    case ItemHeader:
        sendItemHeader();
    case ItemContent:
        sendItemContent();
    }
}

void TransferPrivate::onError(const QString &message)
{
    setError(message, true);
}

Transfer::Transfer(Application *application, Transport *transport, QObject *parent)
    : QObject(parent),
      d(new TransferPrivate(this, application, transport, nullptr, Transfer::Receive))
{
}

Transfer::Transfer(Application *application, Transport *transport, Bundle *bundle, QObject *parent)
    : QObject(parent),
      d(new TransferPrivate(this, application, transport, bundle, Transfer::Send))
{
}

Transfer::Direction Transfer::direction() const
{
    return d->direction;
}

Transfer::State Transfer::state() const
{
    return d->state;
}

int Transfer::progress() const
{
    return d->progress;
}

QString Transfer::deviceName() const
{
    return d->deviceName;
}

QString Transfer::error() const
{
    return d->error;
}

bool Transfer::isFinished() const
{
    return d->state == Failed || d->state == Succeeded;
}

void Transfer::cancel()
{
    d->setError(tr("transfer cancelled"), true);
}
<|endoftext|>


<commit_before>//===-- Value.cpp - Implement the Value class -----------------------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file was developed by the LLVM research group and is distributed under
// the University of Illinois Open Source License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file implements the Value and User classes.
//
//===----------------------------------------------------------------------===//

#include "llvm/InstrTypes.h"
#include "llvm/SymbolTable.h"
#include "llvm/DerivedTypes.h"
#include "llvm/Constant.h"
#include "Support/LeakDetector.h"
#include <algorithm>
using namespace llvm;

//===----------------------------------------------------------------------===//
//                                Value Class
//===----------------------------------------------------------------------===//

static inline const Type *checkType(const Type *Ty) {
  assert(Ty && "Value defined with a null type: Error!");
  return Ty;
}

Value::Value(const Type *ty, unsigned scid, const std::string &name)
  : SubclassID(scid), Ty(checkType(ty)), Name(name) {
}

Value::~Value() {
#ifndef NDEBUG      // Only in -g mode...
  // Check to make sure that there are no uses of this value that are still
  // around when the value is destroyed.  If there are, then we have a dangling
  // reference and something is wrong.  This code is here to print out what is
  // still being referenced.  The value in question should be printed as
  // a <badref>
  //
  if (Uses.begin() != Uses.end()) {
    std::cerr << "While deleting: " << *Ty << "%" << Name << "\n";
    for (use_const_iterator I = Uses.begin(), E = Uses.end(); I != E; ++I)
      std::cerr << "Use still stuck around after Def is destroyed:"
                << **I << "\n";
  }
#endif
  assert(Uses.begin() == Uses.end() &&"Uses remain when a value is destroyed!");

  // There should be no uses of this object anymore, remove it.
  LeakDetector::removeGarbageObject(this);
}

// uncheckedReplaceAllUsesWith - This is exactly the same as replaceAllUsesWith,
// except that it doesn't have all of the asserts.  The asserts fail because we
// are half-way done resolving types, which causes some types to exist as two
// different Type*'s at the same time.  This is a sledgehammer to work around
// this problem.
//
void Value::uncheckedReplaceAllUsesWith(Value *New) {
  while (!Uses.empty()) {
    Use &U = Uses.back();
    // Must handle Constants specially, we cannot call replaceUsesOfWith on a
    // constant!
    if (Constant *C = dyn_cast<Constant>(U.getUser())) {
      C->replaceUsesOfWithOnConstant(this, New, true);
    } else {
      U.set(New);
    }
  }
}

void Value::replaceAllUsesWith(Value *New) {
  assert(New && "Value::replaceAllUsesWith(<null>) is invalid!");
  assert(New != this && "this->replaceAllUsesWith(this) is NOT valid!");
  assert(New->getType() == getType() &&
         "replaceAllUses of value with new value of different type!");

  uncheckedReplaceAllUsesWith(New);
}

//===----------------------------------------------------------------------===//
//                                 User Class
//===----------------------------------------------------------------------===//

User::User(const Type *Ty, ValueTy vty, const std::string &name)
  : Value(Ty, vty, name) {
}

// replaceUsesOfWith - Replaces all references to the "From" definition with
// references to the "To" definition.
//
void User::replaceUsesOfWith(Value *From, Value *To) {
  if (From == To) return;   // Duh what?

  assert(!isa<Constant>(this) &&
         "Cannot call User::replaceUsesofWith on a constant!");

  for (unsigned i = 0, E = getNumOperands(); i != E; ++i)
    if (getOperand(i) == From) {  // Is This operand is pointing to oldval?
      // The side effects of this setOperand call include linking to
      // "To", adding "this" to the uses list of To, and
      // most importantly, removing "this" from the use list of "From".
      setOperand(i, To); // Fix it now...
    }
}
<commit_msg>User ctor is now inline<commit_after>//===-- Value.cpp - Implement the Value class -----------------------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file was developed by the LLVM research group and is distributed under
// the University of Illinois Open Source License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file implements the Value and User classes.
//
//===----------------------------------------------------------------------===//

#include "llvm/InstrTypes.h"
#include "llvm/SymbolTable.h"
#include "llvm/DerivedTypes.h"
#include "llvm/Constant.h"
#include "Support/LeakDetector.h"
#include <algorithm>
using namespace llvm;

//===----------------------------------------------------------------------===//
//                                Value Class
//===----------------------------------------------------------------------===//

static inline const Type *checkType(const Type *Ty) {
  assert(Ty && "Value defined with a null type: Error!");
  return Ty;
}

Value::Value(const Type *ty, unsigned scid, const std::string &name)
  : SubclassID(scid), Ty(checkType(ty)), Name(name) {
}

Value::~Value() {
#ifndef NDEBUG      // Only in -g mode...
  // Check to make sure that there are no uses of this value that are still
  // around when the value is destroyed.  If there are, then we have a dangling
  // reference and something is wrong.  This code is here to print out what is
  // still being referenced.  The value in question should be printed as
  // a <badref>
  //
  if (Uses.begin() != Uses.end()) {
    std::cerr << "While deleting: " << *Ty << "%" << Name << "\n";
    for (use_const_iterator I = Uses.begin(), E = Uses.end(); I != E; ++I)
      std::cerr << "Use still stuck around after Def is destroyed:"
                << **I << "\n";
  }
#endif
  assert(Uses.begin() == Uses.end() &&"Uses remain when a value is destroyed!");

  // There should be no uses of this object anymore, remove it.
  LeakDetector::removeGarbageObject(this);
}

// uncheckedReplaceAllUsesWith - This is exactly the same as replaceAllUsesWith,
// except that it doesn't have all of the asserts.  The asserts fail because we
// are half-way done resolving types, which causes some types to exist as two
// different Type*'s at the same time.  This is a sledgehammer to work around
// this problem.
//
void Value::uncheckedReplaceAllUsesWith(Value *New) {
  while (!Uses.empty()) {
    Use &U = Uses.back();
    // Must handle Constants specially, we cannot call replaceUsesOfWith on a
    // constant!
    if (Constant *C = dyn_cast<Constant>(U.getUser())) {
      C->replaceUsesOfWithOnConstant(this, New, true);
    } else {
      U.set(New);
    }
  }
}

void Value::replaceAllUsesWith(Value *New) {
  assert(New && "Value::replaceAllUsesWith(<null>) is invalid!");
  assert(New != this && "this->replaceAllUsesWith(this) is NOT valid!");
  assert(New->getType() == getType() &&
         "replaceAllUses of value with new value of different type!");

  uncheckedReplaceAllUsesWith(New);
}

//===----------------------------------------------------------------------===//
//                                 User Class
//===----------------------------------------------------------------------===//

// replaceUsesOfWith - Replaces all references to the "From" definition with
// references to the "To" definition.
//
void User::replaceUsesOfWith(Value *From, Value *To) {
  if (From == To) return;   // Duh what?

  assert(!isa<Constant>(this) &&
         "Cannot call User::replaceUsesofWith on a constant!");

  for (unsigned i = 0, E = getNumOperands(); i != E; ++i)
    if (getOperand(i) == From) {  // Is This operand is pointing to oldval?
      // The side effects of this setOperand call include linking to
      // "To", adding "this" to the uses list of To, and
      // most importantly, removing "this" from the use list of "From".
      setOperand(i, To); // Fix it now...
    }
}
<|endoftext|>


<commit_before><commit_msg>Cleanup in BaseInMemoryCertificateStore dtor<commit_after><|endoftext|>


<commit_before>#line 2 "togo/resource/resource_package.cpp"
/**
@copyright MIT license; see @ref index or the accompanying LICENSE file.
*/

#include <togo/config.hpp>
#include <togo/utility/utility.hpp>
#include <togo/error/assert.hpp>
#include <togo/log/log.hpp>
#include <togo/hash/hash.hpp>
#include <togo/collection/hash_map.hpp>
#include <togo/io/io.hpp>
#include <togo/io/file_stream.hpp>
#include <togo/resource/types.hpp>
#include <togo/resource/resource.hpp>
#include <togo/resource/resource_package.hpp>
#include <togo/resource/resource_manager.hpp>
#include <togo/serialization/serializer.hpp>
#include <togo/serialization/support.hpp>
#include <togo/serialization/binary_serializer.hpp>
#include <togo/serialization/array.hpp>
#include <togo/serialization/resource/resource_metadata.hpp>

namespace togo {

ResourcePackage::ResourcePackage(
	StringRef const& name,
	StringRef const& path,
	Allocator& allocator
)
	: _name_hash(resource::hash_package_name(name))
	, _open_resource_id(0)
	, _stream()
	, _lookup(allocator)
	, _manifest(allocator)
	, _name()
	, _path()
{
	string::copy(_name, name);
	string::copy(_path, path);
}

void resource_package::open(
	ResourcePackage& pkg,
	ResourceManager const& rm
) {
	TOGO_ASSERT(!pkg._stream.is_open(), "package is already open");

	StringRef const name{pkg._name};
	StringRef const path{pkg._path};
	TOGO_ASSERTF(
		pkg._stream.open(path),
		"failed to open package '%.*s' at '%.*s'",
		name.size, name.data,
		path.size, path.data
	);

	BinaryInputSerializer ser{pkg._stream};
	u32 format_version = 0;
	ser % format_version;
	TOGO_ASSERTF(
		format_version == SER_FORMAT_VERSION_PKG_MANIFEST,
		"manifest version %u unsupported from package '%.*s' at '%.*s'",
		format_version,
		name.size, name.data,
		path.size, path.data
	);

	ser % make_ser_collection<u32>(pkg._manifest);
	for (u32 i = 0; i < array::size(pkg._manifest); ++i) {
		auto& metadata = pkg._manifest[i];
		if (metadata.type == RES_TYPE_NULL) {
			metadata.id = 0;
			continue;
		}
		metadata.id = i + 1;
		hash_map::push(pkg._lookup, metadata.name_hash, metadata.id);
		TOGO_ASSERTF(
			resource_manager::has_handler(rm, metadata.type),
			"no handler registered for resource %16lx's type %08x",
			metadata.name_hash, metadata.type
		);
	}
}

void resource_package::close(
	ResourcePackage& pkg
) {
	TOGO_ASSERT(pkg._stream.is_open(), "package is already closed");
	pkg._stream.close();
}

ResourceMetadata const& resource_package::resource_metadata(
	ResourcePackage const& pkg,
	u32 const id
) {
	TOGO_ASSERT(id > 0 && id <= array::size(pkg._manifest), "invalid ID");
	auto const& metadata = pkg._manifest[id - 1];
	TOGO_DEBUG_ASSERT(metadata.id != 0, "null entry");
	return metadata;
}

ResourcePackage::LookupNode* resource_package::find_resource(
	ResourcePackage& pkg,
	ResourceType const type,
	ResourceNameHash const name_hash
) {
	auto* const node = hash_map::get_node(pkg._lookup, name_hash);
	return (node && pkg._manifest[node->value - 1].type == type) ? node : nullptr;
}

IReader* resource_package::open_resource_stream(
	ResourcePackage& pkg,
	u32 const id
) {
	TOGO_ASSERT(pkg._stream.is_open(), "package is not open");
	TOGO_ASSERT(pkg._open_resource_id == 0, "a resource stream is already open");
	TOGO_ASSERT(id > 0 && id <= array::size(pkg._manifest), "invalid ID");
	auto const& metadata = pkg._manifest[id - 1];
	TOGO_ASSERTE(io::seek_to(pkg._stream, metadata.data_offset));
	pkg._open_resource_id = id;
	return &pkg._stream;
}

void resource_package::close_resource_stream(
	ResourcePackage& pkg
) {
	TOGO_ASSERT(pkg._stream.is_open(), "package is not open");
	TOGO_ASSERT(pkg._open_resource_id != 0, "invalid ID");
	#if defined(TOGO_DEBUG)
		auto const& metadata = pkg._manifest[pkg._open_resource_id - 1];
		auto const stream_pos = io::position(pkg._stream);
		TOGO_DEBUG_ASSERTE(
			stream_pos >= metadata.data_offset &&
			stream_pos <= metadata.data_offset + metadata.data_size
		);
	#endif
	pkg._open_resource_id = 0;
}

} // namespace togo
<commit_msg>resource/resource_package: close_resource_stream(): corrected assertion message.<commit_after>#line 2 "togo/resource/resource_package.cpp"
/**
@copyright MIT license; see @ref index or the accompanying LICENSE file.
*/

#include <togo/config.hpp>
#include <togo/utility/utility.hpp>
#include <togo/error/assert.hpp>
#include <togo/log/log.hpp>
#include <togo/hash/hash.hpp>
#include <togo/collection/hash_map.hpp>
#include <togo/io/io.hpp>
#include <togo/io/file_stream.hpp>
#include <togo/resource/types.hpp>
#include <togo/resource/resource.hpp>
#include <togo/resource/resource_package.hpp>
#include <togo/resource/resource_manager.hpp>
#include <togo/serialization/serializer.hpp>
#include <togo/serialization/support.hpp>
#include <togo/serialization/binary_serializer.hpp>
#include <togo/serialization/array.hpp>
#include <togo/serialization/resource/resource_metadata.hpp>

namespace togo {

ResourcePackage::ResourcePackage(
	StringRef const& name,
	StringRef const& path,
	Allocator& allocator
)
	: _name_hash(resource::hash_package_name(name))
	, _open_resource_id(0)
	, _stream()
	, _lookup(allocator)
	, _manifest(allocator)
	, _name()
	, _path()
{
	string::copy(_name, name);
	string::copy(_path, path);
}

void resource_package::open(
	ResourcePackage& pkg,
	ResourceManager const& rm
) {
	TOGO_ASSERT(!pkg._stream.is_open(), "package is already open");

	StringRef const name{pkg._name};
	StringRef const path{pkg._path};
	TOGO_ASSERTF(
		pkg._stream.open(path),
		"failed to open package '%.*s' at '%.*s'",
		name.size, name.data,
		path.size, path.data
	);

	BinaryInputSerializer ser{pkg._stream};
	u32 format_version = 0;
	ser % format_version;
	TOGO_ASSERTF(
		format_version == SER_FORMAT_VERSION_PKG_MANIFEST,
		"manifest version %u unsupported from package '%.*s' at '%.*s'",
		format_version,
		name.size, name.data,
		path.size, path.data
	);

	ser % make_ser_collection<u32>(pkg._manifest);
	for (u32 i = 0; i < array::size(pkg._manifest); ++i) {
		auto& metadata = pkg._manifest[i];
		if (metadata.type == RES_TYPE_NULL) {
			metadata.id = 0;
			continue;
		}
		metadata.id = i + 1;
		hash_map::push(pkg._lookup, metadata.name_hash, metadata.id);
		TOGO_ASSERTF(
			resource_manager::has_handler(rm, metadata.type),
			"no handler registered for resource %16lx's type %08x",
			metadata.name_hash, metadata.type
		);
	}
}

void resource_package::close(
	ResourcePackage& pkg
) {
	TOGO_ASSERT(pkg._stream.is_open(), "package is already closed");
	pkg._stream.close();
}

ResourceMetadata const& resource_package::resource_metadata(
	ResourcePackage const& pkg,
	u32 const id
) {
	TOGO_ASSERT(id > 0 && id <= array::size(pkg._manifest), "invalid ID");
	auto const& metadata = pkg._manifest[id - 1];
	TOGO_DEBUG_ASSERT(metadata.id != 0, "null entry");
	return metadata;
}

ResourcePackage::LookupNode* resource_package::find_resource(
	ResourcePackage& pkg,
	ResourceType const type,
	ResourceNameHash const name_hash
) {
	auto* const node = hash_map::get_node(pkg._lookup, name_hash);
	return (node && pkg._manifest[node->value - 1].type == type) ? node : nullptr;
}

IReader* resource_package::open_resource_stream(
	ResourcePackage& pkg,
	u32 const id
) {
	TOGO_ASSERT(pkg._stream.is_open(), "package is not open");
	TOGO_ASSERT(pkg._open_resource_id == 0, "a resource stream is already open");
	TOGO_ASSERT(id > 0 && id <= array::size(pkg._manifest), "invalid ID");
	auto const& metadata = pkg._manifest[id - 1];
	TOGO_ASSERTE(io::seek_to(pkg._stream, metadata.data_offset));
	pkg._open_resource_id = id;
	return &pkg._stream;
}

void resource_package::close_resource_stream(
	ResourcePackage& pkg
) {
	TOGO_ASSERT(pkg._stream.is_open(), "package is not open");
	TOGO_ASSERT(pkg._open_resource_id != 0, "no resource stream is open");
	#if defined(TOGO_DEBUG)
		auto const& metadata = pkg._manifest[pkg._open_resource_id - 1];
		auto const stream_pos = io::position(pkg._stream);
		TOGO_DEBUG_ASSERTE(
			stream_pos >= metadata.data_offset &&
			stream_pos <= metadata.data_offset + metadata.data_size
		);
	#endif
	pkg._open_resource_id = 0;
}

} // namespace togo
<|endoftext|>


<commit_before>// Copyright 2010-2015 RethinkDB, all rights reserved.
#include "unittest/gtest.hpp"

#include "clustering/administration/metadata.hpp"
#include "clustering/generic/raft_core.hpp"
#include "clustering/generic/raft_core.tcc"
#include "clustering/generic/raft_network.hpp"
#include "clustering/generic/raft_network.tcc"
#include "unittest/clustering_utils.hpp"
#include "unittest/clustering_utils_raft.hpp"
#include "unittest/dummy_metadata_controller.hpp"
#include "unittest/unittest_utils.hpp"

namespace unittest {

void print_config(RAFT_DEBUG_VAR const raft_config_t &config) {
    for (RAFT_DEBUG_VAR auto const &id : config.voting_members) {
        RAFT_DEBUG("  member %s: voting\n", show_member_id(id).c_str());
    }
    for (RAFT_DEBUG_VAR auto const &id : config.non_voting_members) {
        RAFT_DEBUG("  member %s: non-voting\n", show_member_id(id).c_str());
    }
}

void print_config(RAFT_DEBUG_VAR const raft_complex_config_t &config) {
    RAFT_DEBUG("raft_complex_config_t state (%s):\n", config.is_joint_consensus() ? "joint consensus" : "normal");
    print_config(config.config);
    if (config.is_joint_consensus()) {
        RAFT_DEBUG(" new config:\n");
        print_config(config.new_config.get());
    }
}

class raft_fuzzer_t {
public:
    explicit raft_fuzzer_t(size_t initial_member_count) :
            cluster(initial_member_count, dummy_raft_state_t(), &member_ids)
    {
        raft_config_t config;
        for (auto &&id : member_ids) {
            config.voting_members.insert(id);
        }
        active_config.config = config;
    }

    void run(uint64_t timeout_ms) {
        signal_timer_t interruptor;
        interruptor.start(timeout_ms);

        for (RAFT_DEBUG_VAR auto const &id : member_ids) {
            RAFT_DEBUG("initial member: %s\n", show_member_id(id).c_str());
        }

        cond_t states_done, config_done, writes_done;
        coro_t::spawn_sometime(std::bind(&raft_fuzzer_t::fuzz_states,
                                         this, &states_done, &interruptor));
        coro_t::spawn_sometime(std::bind(&raft_fuzzer_t::fuzz_config,
                                         this, &config_done, &interruptor));
        coro_t::spawn_sometime(std::bind(&raft_fuzzer_t::fuzz_writes,
                                         this, &writes_done, &interruptor));
        states_done.wait();
        config_done.wait();
        writes_done.wait();
        RAFT_DEBUG("stopped fuzzer sub-coroutines\n");

        print_config(active_config);
        cluster.print_state();

        // Put the cluster in a good state and check that writes work
        std::set<raft_member_id_t> possible_quorum_members = active_config.config.voting_members;
        if (active_config.is_joint_consensus()) {
            possible_quorum_members.insert(active_config.new_config->voting_members.begin(),
                                           active_config.new_config->voting_members.end());
        }

        std::set<raft_member_id_t> non_alive_voting_members;
        std::set<raft_member_id_t> alive_voting_members;
        for (auto const &id : possible_quorum_members) {
            if (cluster.get_live(id) == dummy_raft_cluster_t::live_t::alive) {
                alive_voting_members.insert(id);
            } else {
                non_alive_voting_members.insert(id);
            }
        }

        while (!active_config.is_quorum(alive_voting_members)) {
            guarantee(!non_alive_voting_members.empty());
            RAFT_DEBUG("reviving member for quorum %s\n", show_member_id(*non_alive_voting_members.begin()).c_str());
            cluster.set_live(*non_alive_voting_members.begin(), dummy_raft_cluster_t::live_t::alive);
            alive_voting_members.insert(*non_alive_voting_members.begin());
            non_alive_voting_members.erase(non_alive_voting_members.begin());
        }

        cluster.print_state();

        /* Give the cluster some time to stabilize */
        RAFT_DEBUG("waiting for cluster to stabilize\n");
        nap(5000);

        RAFT_DEBUG("running final test writes\n");
        do_writes_raft(&cluster, 100, 20000);

        RAFT_DEBUG("running final test config change\n");
        signal_timer_t final_interruptor;
        final_interruptor.start(10000); // Allow up to 10 seconds to finish the final verification
        raft_member_id_t leader = cluster.find_leader(&final_interruptor);
        raft_config_t final_config;
        final_config.voting_members.insert(leader);

        cluster.run_on_member(leader, [&](dummy_raft_member_t *m, signal_t *) {
                m->get_readiness_for_config_change()->run_until_satisfied([&](bool v) {
                    return v;
                }, &final_interruptor);
            });

        bool res = cluster.try_config_change(leader, final_config, &final_interruptor);
        guarantee(res);
    }

private:
    void fuzz_states(cond_t *done, signal_t *interruptor) {
        try {
            while (true) {
                signal_timer_t timeout;
                timeout.start(rng.randint(800));
                wait_interruptible(&timeout, interruptor);

                if (rng.randint(4) == 0) {
                    // We can only add a member if there is at least one alive member
                    for (auto &&id : member_ids) {
                        if (cluster.get_live(id) == dummy_raft_cluster_t::live_t::alive) {
                            raft_member_id_t id2 = cluster.join();
                            member_ids.push_back(id2);
                            break;
                        }
                    }
                } else {
                    raft_member_id_t id = member_ids[rng.randint(member_ids.size())];
                    dummy_raft_cluster_t::live_t new_state =
                        static_cast<dummy_raft_cluster_t::live_t>(rng.randint(3));
                    cluster.set_live(id, new_state);
                }
            }
        } catch (const interrupted_exc_t &) { /* pass */ }
        done->pulse();
    }

    void fuzz_config(cond_t *done, signal_t *interruptor) {
        try {
            while (true) {
                signal_timer_t timeout;
                timeout.start(rng.randint(100));
                wait_interruptible(&timeout, interruptor);

                raft_config_t config;
                for (auto &&id : member_ids) {
                    switch (rng.randint(3)) {
                    case 0:
                        config.voting_members.insert(id);
                        break;
                    case 1:
                        config.non_voting_members.insert(id);
                        break;
                    case 2:
                        /* Make this server not a member at all */
                        break;
                    default: unreachable();
                    }
                }

                if (rng.randint(3) == 0 && config.voting_members.size() > 0) {
                    raft_member_id_t leader = cluster.find_leader(interruptor);
                    // Check if we're in the middle of a joint consensus
                    cluster.run_on_member(leader, [&] (dummy_raft_member_t *m, signal_t *) {
                            scoped_ptr_t<dummy_raft_member_t::change_lock_t> lock;
                            lock.init(new dummy_raft_member_t::change_lock_t(m, interruptor));
                            bool is_joint = (m->get_committed_state()->get().config !=
                                             m->get_latest_state()->get().config) ||
                                            m->get_committed_state()->get().config.is_joint_consensus();
                            if (!is_joint) {
                                RAFT_DEBUG("Pre-config change, not in joint consensus\n");
                                raft_config_t committed = m->get_committed_state()->get().config.config;
                                if (active_config.is_joint_consensus()) {
                                    guarantee(committed == active_config.config ||
                                              committed == active_config.new_config.get());
                                } else {
                                    guarantee(committed == active_config.config);
                                }
                                active_config.config = m->get_committed_state()->get().config.config;
                                active_config.new_config.reset();
                            }

                            RAFT_DEBUG("Performing config change to:\n");
                            print_config(config);

                            scoped_ptr_t<dummy_raft_member_t::change_token_t> tok =
                                m->propose_config_change(lock.get(), config);
                            lock.reset();

                            bool res = false;
                            if (tok.has()) {
                                wait_interruptible(tok->get_ready_signal(), interruptor);
                                res = tok->assert_get_value();
                            }

                            if (is_joint) {
                                guarantee(!res);
                                RAFT_DEBUG("Config change failed - in joint consensus mode\n");
                            } else if (res) {
                                active_config.config = config;
                                active_config.new_config.reset();
                                RAFT_DEBUG("Config change succeeded\n");
                            } else {
                                active_config.new_config = config;
                                RAFT_DEBUG("Config change indeterminate\n");
                            }
                            print_config(active_config);
                        });

                }
            }
        } catch (const interrupted_exc_t &) { /* pass */ }
        done->pulse();
    }

    void fuzz_writes(cond_t *done, signal_t *interruptor) {
        try {
            while (true) {
                signal_timer_t timeout;
                timeout.start(rng.randint(100));
                wait_interruptible(&timeout, interruptor);

                raft_member_id_t leader = cluster.find_leader(interruptor);
                cluster.try_change(leader, generate_uuid(), interruptor);
            }
        } catch (const interrupted_exc_t &) { /* pass */ }
        done->pulse();
    }

    rng_t rng;
    std::vector<raft_member_id_t> member_ids;
    dummy_raft_cluster_t cluster;
    raft_complex_config_t active_config;
};

TPTEST(ClusteringRaft, Fuzzer) {
#ifdef ENABLE_RAFT_DEBUG
    /* If we run multiple concurrent coroutines with raft debugging enabled, the logs
    will get interleaved and the result will be difficult to understand. */
    int num_concurrent = 1;
#else
    int num_concurrent = 10;
#endif
    pmap(num_concurrent, [](int) {
        raft_fuzzer_t fuzzer(randint(7) + 1);
        fuzzer.run(20000);
    });
}

}   /* namespace unittest */

<commit_msg>Upped Raft fuzzer timeouts.<commit_after>// Copyright 2010-2015 RethinkDB, all rights reserved.
#include "unittest/gtest.hpp"

#include "clustering/administration/metadata.hpp"
#include "clustering/generic/raft_core.hpp"
#include "clustering/generic/raft_core.tcc"
#include "clustering/generic/raft_network.hpp"
#include "clustering/generic/raft_network.tcc"
#include "unittest/clustering_utils.hpp"
#include "unittest/clustering_utils_raft.hpp"
#include "unittest/dummy_metadata_controller.hpp"
#include "unittest/unittest_utils.hpp"

namespace unittest {

void print_config(RAFT_DEBUG_VAR const raft_config_t &config) {
    for (RAFT_DEBUG_VAR auto const &id : config.voting_members) {
        RAFT_DEBUG("  member %s: voting\n", show_member_id(id).c_str());
    }
    for (RAFT_DEBUG_VAR auto const &id : config.non_voting_members) {
        RAFT_DEBUG("  member %s: non-voting\n", show_member_id(id).c_str());
    }
}

void print_config(RAFT_DEBUG_VAR const raft_complex_config_t &config) {
    RAFT_DEBUG("raft_complex_config_t state (%s):\n", config.is_joint_consensus() ? "joint consensus" : "normal");
    print_config(config.config);
    if (config.is_joint_consensus()) {
        RAFT_DEBUG(" new config:\n");
        print_config(config.new_config.get());
    }
}

class raft_fuzzer_t {
public:
    explicit raft_fuzzer_t(size_t initial_member_count) :
            cluster(initial_member_count, dummy_raft_state_t(), &member_ids)
    {
        raft_config_t config;
        for (auto &&id : member_ids) {
            config.voting_members.insert(id);
        }
        active_config.config = config;
    }

    void run(uint64_t timeout_ms) {
        signal_timer_t interruptor;
        interruptor.start(timeout_ms);

        for (RAFT_DEBUG_VAR auto const &id : member_ids) {
            RAFT_DEBUG("initial member: %s\n", show_member_id(id).c_str());
        }

        cond_t states_done, config_done, writes_done;
        coro_t::spawn_sometime(std::bind(&raft_fuzzer_t::fuzz_states,
                                         this, &states_done, &interruptor));
        coro_t::spawn_sometime(std::bind(&raft_fuzzer_t::fuzz_config,
                                         this, &config_done, &interruptor));
        coro_t::spawn_sometime(std::bind(&raft_fuzzer_t::fuzz_writes,
                                         this, &writes_done, &interruptor));
        states_done.wait();
        config_done.wait();
        writes_done.wait();
        RAFT_DEBUG("stopped fuzzer sub-coroutines\n");

        print_config(active_config);
        cluster.print_state();

        // Put the cluster in a good state and check that writes work
        std::set<raft_member_id_t> possible_quorum_members = active_config.config.voting_members;
        if (active_config.is_joint_consensus()) {
            possible_quorum_members.insert(active_config.new_config->voting_members.begin(),
                                           active_config.new_config->voting_members.end());
        }

        std::set<raft_member_id_t> non_alive_voting_members;
        std::set<raft_member_id_t> alive_voting_members;
        for (auto const &id : possible_quorum_members) {
            if (cluster.get_live(id) == dummy_raft_cluster_t::live_t::alive) {
                alive_voting_members.insert(id);
            } else {
                non_alive_voting_members.insert(id);
            }
        }

        while (!active_config.is_quorum(alive_voting_members)) {
            guarantee(!non_alive_voting_members.empty());
            RAFT_DEBUG("reviving member for quorum %s\n", show_member_id(*non_alive_voting_members.begin()).c_str());
            cluster.set_live(*non_alive_voting_members.begin(), dummy_raft_cluster_t::live_t::alive);
            alive_voting_members.insert(*non_alive_voting_members.begin());
            non_alive_voting_members.erase(non_alive_voting_members.begin());
        }

        cluster.print_state();

        /* Give the cluster some time to stabilize */
        RAFT_DEBUG("waiting for cluster to stabilize\n");
        nap(5000);

        RAFT_DEBUG("running final test writes\n");
        do_writes_raft(&cluster, 100, 60000);

        RAFT_DEBUG("running final test config change\n");
        signal_timer_t final_interruptor;
        final_interruptor.start(30000); // Allow up to 30 seconds to finish the final verification
        raft_member_id_t leader = cluster.find_leader(&final_interruptor);
        raft_config_t final_config;
        final_config.voting_members.insert(leader);

        cluster.run_on_member(leader, [&](dummy_raft_member_t *m, signal_t *) {
                m->get_readiness_for_config_change()->run_until_satisfied([&](bool v) {
                    return v;
                }, &final_interruptor);
            });

        bool res = cluster.try_config_change(leader, final_config, &final_interruptor);
        guarantee(res);
    }

private:
    void fuzz_states(cond_t *done, signal_t *interruptor) {
        try {
            while (true) {
                signal_timer_t timeout;
                timeout.start(rng.randint(800));
                wait_interruptible(&timeout, interruptor);

                if (rng.randint(4) == 0) {
                    // We can only add a member if there is at least one alive member
                    for (auto &&id : member_ids) {
                        if (cluster.get_live(id) == dummy_raft_cluster_t::live_t::alive) {
                            raft_member_id_t id2 = cluster.join();
                            member_ids.push_back(id2);
                            break;
                        }
                    }
                } else {
                    raft_member_id_t id = member_ids[rng.randint(member_ids.size())];
                    dummy_raft_cluster_t::live_t new_state =
                        static_cast<dummy_raft_cluster_t::live_t>(rng.randint(3));
                    cluster.set_live(id, new_state);
                }
            }
        } catch (const interrupted_exc_t &) { /* pass */ }
        done->pulse();
    }

    void fuzz_config(cond_t *done, signal_t *interruptor) {
        try {
            while (true) {
                signal_timer_t timeout;
                timeout.start(rng.randint(100));
                wait_interruptible(&timeout, interruptor);

                raft_config_t config;
                for (auto &&id : member_ids) {
                    switch (rng.randint(3)) {
                    case 0:
                        config.voting_members.insert(id);
                        break;
                    case 1:
                        config.non_voting_members.insert(id);
                        break;
                    case 2:
                        /* Make this server not a member at all */
                        break;
                    default: unreachable();
                    }
                }

                if (rng.randint(3) == 0 && config.voting_members.size() > 0) {
                    raft_member_id_t leader = cluster.find_leader(interruptor);
                    // Check if we're in the middle of a joint consensus
                    cluster.run_on_member(leader, [&] (dummy_raft_member_t *m, signal_t *) {
                            scoped_ptr_t<dummy_raft_member_t::change_lock_t> lock;
                            lock.init(new dummy_raft_member_t::change_lock_t(m, interruptor));
                            bool is_joint = (m->get_committed_state()->get().config !=
                                             m->get_latest_state()->get().config) ||
                                            m->get_committed_state()->get().config.is_joint_consensus();
                            if (!is_joint) {
                                RAFT_DEBUG("Pre-config change, not in joint consensus\n");
                                raft_config_t committed = m->get_committed_state()->get().config.config;
                                if (active_config.is_joint_consensus()) {
                                    guarantee(committed == active_config.config ||
                                              committed == active_config.new_config.get());
                                } else {
                                    guarantee(committed == active_config.config);
                                }
                                active_config.config = m->get_committed_state()->get().config.config;
                                active_config.new_config.reset();
                            }

                            RAFT_DEBUG("Performing config change to:\n");
                            print_config(config);

                            scoped_ptr_t<dummy_raft_member_t::change_token_t> tok =
                                m->propose_config_change(lock.get(), config);
                            lock.reset();

                            bool res = false;
                            if (tok.has()) {
                                wait_interruptible(tok->get_ready_signal(), interruptor);
                                res = tok->assert_get_value();
                            }

                            if (is_joint) {
                                guarantee(!res);
                                RAFT_DEBUG("Config change failed - in joint consensus mode\n");
                            } else if (res) {
                                active_config.config = config;
                                active_config.new_config.reset();
                                RAFT_DEBUG("Config change succeeded\n");
                            } else {
                                active_config.new_config = config;
                                RAFT_DEBUG("Config change indeterminate\n");
                            }
                            print_config(active_config);
                        });

                }
            }
        } catch (const interrupted_exc_t &) { /* pass */ }
        done->pulse();
    }

    void fuzz_writes(cond_t *done, signal_t *interruptor) {
        try {
            while (true) {
                signal_timer_t timeout;
                timeout.start(rng.randint(100));
                wait_interruptible(&timeout, interruptor);

                raft_member_id_t leader = cluster.find_leader(interruptor);
                cluster.try_change(leader, generate_uuid(), interruptor);
            }
        } catch (const interrupted_exc_t &) { /* pass */ }
        done->pulse();
    }

    rng_t rng;
    std::vector<raft_member_id_t> member_ids;
    dummy_raft_cluster_t cluster;
    raft_complex_config_t active_config;
};

TPTEST(ClusteringRaft, Fuzzer) {
#ifdef ENABLE_RAFT_DEBUG
    /* If we run multiple concurrent coroutines with raft debugging enabled, the logs
    will get interleaved and the result will be difficult to understand. */
    int num_concurrent = 1;
#else
    int num_concurrent = 10;
#endif
    pmap(num_concurrent, [](int) {
        raft_fuzzer_t fuzzer(randint(7) + 1);
        fuzzer.run(20000);
    });
}

}   /* namespace unittest */

<|endoftext|>