<commit_before>/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/

#include "cmakevalidator.h"

#include <QProcess>
#include <QFileInfo>
#include <QTextDocument>

using namespace CMakeProjectManager::Internal;

///////////////////////////
// CMakeValidator
///////////////////////////
CMakeValidator::CMakeValidator()
    : m_state(Invalid), m_process(0), m_hasCodeBlocksMsvcGenerator(false), m_hasCodeBlocksNinjaGenerator(false)
{

}

CMakeValidator::~CMakeValidator()
{
    cancel();
}

void CMakeValidator::cancel()
{
    if (m_process) {
        disconnect(m_process, SIGNAL(finished(int)));
        m_process->waitForFinished();
        delete m_process;
        m_process = 0;
    }
}

void CMakeValidator::setCMakeExecutable(const QString &executable)
{
    cancel();
    m_process = new QProcess();
    connect(m_process, SIGNAL(finished(int)),
            this, SLOT(finished(int)));

    m_executable = executable;
    QFileInfo fi(m_executable);
    if (fi.exists() && fi.isExecutable()) {
        // Run it to find out more
        m_state = CMakeValidator::RunningBasic;
        if (!startProcess(QStringList(QLatin1String("--help"))))
            m_state = CMakeValidator::Invalid;
    } else {
        m_state = CMakeValidator::Invalid;
    }
}

void CMakeValidator::finished(int exitCode)
{
    if (exitCode) {
        m_state = CMakeValidator::Invalid;
        return;
    }
    if (m_state == CMakeValidator::RunningBasic) {
        QByteArray response = m_process->readAll();
        QRegExp versionRegexp(QLatin1String("^cmake version ([\\d\\.]*)"));
        versionRegexp.indexIn(QString::fromLocal8Bit(response));

        m_hasCodeBlocksMsvcGenerator = response.contains("CodeBlocks - NMake Makefiles");
        m_hasCodeBlocksNinjaGenerator = response.contains("CodeBlocks - Ninja");
        m_version = versionRegexp.cap(1);
        if (versionRegexp.capturedTexts().size() > 3)
            m_version += QLatin1Char('.') + versionRegexp.cap(3);

        if (m_version.isEmpty()) {
            m_state = CMakeValidator::Invalid;
        } else {
            m_state = CMakeValidator::RunningFunctionList;
            if (!startProcess(QStringList(QLatin1String("--help-command-list"))))
                finished(0); // should never happen, just continue
        }
    } else if (m_state == CMakeValidator::RunningFunctionList) {
        parseFunctionOutput(m_process->readAll());
        m_state = CMakeValidator::RunningFunctionDetails;
        if (!startProcess(QStringList(QLatin1String("--help-commands"))))
            finished(0); // should never happen, just continue
    } else if (m_state == CMakeValidator::RunningFunctionDetails) {
        parseFunctionDetailsOutput(m_process->readAll());
        m_state = CMakeValidator::RunningPropertyList;
        if (!startProcess(QStringList(QLatin1String("--help-property-list"))))
            finished(0); // should never happen, just continue
    } else if (m_state == CMakeValidator::RunningPropertyList) {
        parseVariableOutput(m_process->readAll());
        m_state = CMakeValidator::RunningVariableList;
        if (!startProcess(QStringList(QLatin1String("--help-variable-list"))))
            finished(0); // should never happen, just continue
    } else if (m_state == CMakeValidator::RunningVariableList) {
        parseVariableOutput(m_process->readAll());
        parseDone();
        m_state = CMakeValidator::RunningDone;
    }
}

bool CMakeValidator::isValid() const
{
    if (m_state == CMakeValidator::Invalid)
        return false;
    if (m_state == CMakeValidator::RunningBasic)
        m_process->waitForFinished();
    return (m_state != CMakeValidator::Invalid);
}

bool CMakeValidator::startProcess(const QStringList &args)
{
    m_process->start(m_executable, args);
    return m_process->waitForStarted(2000);
}

QString CMakeValidator::cmakeExecutable() const
{
    return m_executable;
}

bool CMakeValidator::hasCodeBlocksMsvcGenerator() const
{
    if (!isValid())
        return false;
    return m_hasCodeBlocksMsvcGenerator;
}

bool CMakeValidator::hasCodeBlocksNinjaGenerator() const
{
    if (!isValid())
        return false;
    return m_hasCodeBlocksNinjaGenerator;
}

TextEditor::Keywords CMakeValidator::keywords()
{
    while (m_state != RunningDone && m_state != CMakeValidator::Invalid) {
        m_process->waitForFinished();
    }

    if (m_state == CMakeValidator::Invalid)
        return TextEditor::Keywords(QStringList(), QStringList(), QMap<QString, QStringList>());

    return TextEditor::Keywords(m_variables, m_functions, m_functionArgs);
}

static void extractKeywords(const QByteArray &input, QStringList *destination)
{
    if (!destination)
        return;

    QString keyword;
    int ignoreZone = 0;
    for (int i = 0; i < input.count(); ++i) {
        const QChar chr = QLatin1Char(input.at(i));
        if (chr == QLatin1Char('{'))
            ++ignoreZone;
        if (chr == QLatin1Char('}'))
            --ignoreZone;
        if (ignoreZone == 0) {
            if ((chr.isLetterOrNumber() && chr.isUpper())
                || chr == QLatin1Char('_')) {
                keyword += chr;
            } else {
                if (!keyword.isEmpty()) {
                    if (keyword.size() > 1)
                        *destination << keyword;
                    keyword.clear();
                }
            }
        }
    }
    if (keyword.size() > 1)
        *destination << keyword;
}

void CMakeValidator::parseFunctionOutput(const QByteArray &output)
{
    QList<QByteArray> cmakeFunctionsList = output.split('\n');
    m_functions.clear();
    if (!cmakeFunctionsList.isEmpty()) {
        cmakeFunctionsList.removeFirst(); //remove version string
        foreach (const QByteArray &function, cmakeFunctionsList)
            m_functions << QString::fromLocal8Bit(function.trimmed());
    }
}

QString CMakeValidator::formatFunctionDetails(const QString &command, const QString &args)
{
    return QString::fromLatin1("

%1	%2
")
            .arg(Qt::escape(command)).arg(Qt::escape(args));
}

void CMakeValidator::parseFunctionDetailsOutput(const QByteArray &output)
{
    QStringList cmakeFunctionsList = m_functions;
    QList<QByteArray> cmakeCommandsHelp = output.split('\n');
    for (int i = 0; i < cmakeCommandsHelp.count(); ++i) {
        QByteArray lineTrimmed = cmakeCommandsHelp.at(i).trimmed();
        if (cmakeFunctionsList.first().toLatin1() == lineTrimmed) {
            QStringList commandSyntaxes;
            QString currentCommandSyntax;
            QString currentCommand = cmakeFunctionsList.takeFirst();
            ++i;
            for (; i < cmakeCommandsHelp.count(); ++i) {
                lineTrimmed = cmakeCommandsHelp.at(i).trimmed();

                if (cmakeFunctionsList.first().toLatin1() == lineTrimmed) {
                    //start of next function in output
                    if (!currentCommandSyntax.isEmpty())
                        commandSyntaxes << currentCommandSyntax.append(QLatin1String("
"));
                    --i;
                    break;
                }
                if (lineTrimmed.startsWith(currentCommand.toLatin1() + "(")) {
                    if (!currentCommandSyntax.isEmpty())
                        commandSyntaxes << currentCommandSyntax.append(QLatin1String("
"));

                    QByteArray argLine = lineTrimmed.mid(currentCommand.length());
                    extractKeywords(argLine, &m_variables);
                    currentCommandSyntax = formatFunctionDetails(currentCommand, QString::fromUtf8(argLine));
                } else {
                    if (!currentCommandSyntax.isEmpty()) {
                        if (lineTrimmed.isEmpty()) {
                            commandSyntaxes << currentCommandSyntax.append(QLatin1String("
"));
                            currentCommandSyntax.clear();
                        } else {
                            extractKeywords(lineTrimmed, &m_variables);
                            currentCommandSyntax += QString::fromLatin1("
 	%1
")
                                    .arg(Qt::escape(QString::fromLocal8Bit(lineTrimmed)));
                        }
                    }
                }
            }
            m_functionArgs[currentCommand] = commandSyntaxes;
        }
    }
    m_functions = m_functionArgs.keys();
}

void CMakeValidator::parseVariableOutput(const QByteArray &output)
{
    QList<QByteArray> variableList = output.split('\n');
    if (!variableList.isEmpty()) {
        variableList.removeFirst(); //remove version string
        foreach (const QByteArray &variable, variableList) {
            if (variable.contains("_<CONFIG>")) {
                m_variables << QString::fromLocal8Bit(variable).replace(QLatin1String("_<CONFIG>"), QLatin1String("_DEBUG"));
                m_variables << QString::fromLocal8Bit(variable).replace(QLatin1String("_<CONFIG>"), QLatin1String("_RELEASE"));
                m_variables << QString::fromLocal8Bit(variable).replace(QLatin1String("_<CONFIG>"), QLatin1String("_MINSIZEREL"));
                m_variables << QString::fromLocal8Bit(variable).replace(QLatin1String("_<CONFIG>"), QLatin1String("_RELWITHDEBINFO"));
            } else if (variable.contains("_<LANG>")) {
                m_variables << QString::fromLocal8Bit(variable).replace(QLatin1String("_<LANG>"), QLatin1String("_C"));
                m_variables << QString::fromLocal8Bit(variable).replace(QLatin1String("_<LANG>"), QLatin1String("_CXX"));
            } else if (!variable.contains("_<") && !variable.contains('[')) {
                m_variables << QString::fromLocal8Bit(variable);
            }
        }
    }
}

void CMakeValidator::parseDone()
{
    m_variables.sort();
    m_variables.removeDuplicates();
}
<commit_msg>CMakeValidator: Make code more robust<commit_after>/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/

#include "cmakevalidator.h"

#include <QProcess>
#include <QFileInfo>
#include <QTextDocument>

using namespace CMakeProjectManager::Internal;

///////////////////////////
// CMakeValidator
///////////////////////////
CMakeValidator::CMakeValidator()
    : m_state(Invalid), m_process(0), m_hasCodeBlocksMsvcGenerator(false), m_hasCodeBlocksNinjaGenerator(false)
{

}

CMakeValidator::~CMakeValidator()
{
    cancel();
}

void CMakeValidator::cancel()
{
    if (m_process) {
        disconnect(m_process, SIGNAL(finished(int)));
        m_process->waitForFinished();
        delete m_process;
        m_process = 0;
    }
}

void CMakeValidator::setCMakeExecutable(const QString &executable)
{
    cancel();
    m_process = new QProcess();
    connect(m_process, SIGNAL(finished(int)),
            this, SLOT(finished(int)));

    m_executable = executable;
    QFileInfo fi(m_executable);
    if (fi.exists() && fi.isExecutable()) {
        // Run it to find out more
        m_state = CMakeValidator::RunningBasic;
        if (!startProcess(QStringList(QLatin1String("--help"))))
            m_state = CMakeValidator::Invalid;
    } else {
        m_state = CMakeValidator::Invalid;
    }
}

void CMakeValidator::finished(int exitCode)
{
    if (exitCode) {
        m_state = CMakeValidator::Invalid;
        return;
    }
    if (m_state == CMakeValidator::RunningBasic) {
        QByteArray response = m_process->readAll();
        QRegExp versionRegexp(QLatin1String("^cmake version ([\\d\\.]*)"));
        versionRegexp.indexIn(QString::fromLocal8Bit(response));

        m_hasCodeBlocksMsvcGenerator = response.contains("CodeBlocks - NMake Makefiles");
        m_hasCodeBlocksNinjaGenerator = response.contains("CodeBlocks - Ninja");
        m_version = versionRegexp.cap(1);
        if (versionRegexp.capturedTexts().size() > 3)
            m_version += QLatin1Char('.') + versionRegexp.cap(3);

        if (m_version.isEmpty()) {
            m_state = CMakeValidator::Invalid;
        } else {
            m_state = CMakeValidator::RunningFunctionList;
            if (!startProcess(QStringList(QLatin1String("--help-command-list"))))
                finished(0); // should never happen, just continue
        }
    } else if (m_state == CMakeValidator::RunningFunctionList) {
        parseFunctionOutput(m_process->readAll());
        m_state = CMakeValidator::RunningFunctionDetails;
        if (!startProcess(QStringList(QLatin1String("--help-commands"))))
            finished(0); // should never happen, just continue
    } else if (m_state == CMakeValidator::RunningFunctionDetails) {
        parseFunctionDetailsOutput(m_process->readAll());
        m_state = CMakeValidator::RunningPropertyList;
        if (!startProcess(QStringList(QLatin1String("--help-property-list"))))
            finished(0); // should never happen, just continue
    } else if (m_state == CMakeValidator::RunningPropertyList) {
        parseVariableOutput(m_process->readAll());
        m_state = CMakeValidator::RunningVariableList;
        if (!startProcess(QStringList(QLatin1String("--help-variable-list"))))
            finished(0); // should never happen, just continue
    } else if (m_state == CMakeValidator::RunningVariableList) {
        parseVariableOutput(m_process->readAll());
        parseDone();
        m_state = CMakeValidator::RunningDone;
    }
}

bool CMakeValidator::isValid() const
{
    if (m_state == CMakeValidator::Invalid)
        return false;
    if (m_state == CMakeValidator::RunningBasic)
        m_process->waitForFinished();
    return (m_state != CMakeValidator::Invalid);
}

bool CMakeValidator::startProcess(const QStringList &args)
{
    m_process->start(m_executable, args);
    return m_process->waitForStarted(2000);
}

QString CMakeValidator::cmakeExecutable() const
{
    return m_executable;
}

bool CMakeValidator::hasCodeBlocksMsvcGenerator() const
{
    if (!isValid())
        return false;
    return m_hasCodeBlocksMsvcGenerator;
}

bool CMakeValidator::hasCodeBlocksNinjaGenerator() const
{
    if (!isValid())
        return false;
    return m_hasCodeBlocksNinjaGenerator;
}

TextEditor::Keywords CMakeValidator::keywords()
{
    while (m_state != RunningDone && m_state != CMakeValidator::Invalid) {
        m_process->waitForFinished();
    }

    if (m_state == CMakeValidator::Invalid)
        return TextEditor::Keywords(QStringList(), QStringList(), QMap<QString, QStringList>());

    return TextEditor::Keywords(m_variables, m_functions, m_functionArgs);
}

static void extractKeywords(const QByteArray &input, QStringList *destination)
{
    if (!destination)
        return;

    QString keyword;
    int ignoreZone = 0;
    for (int i = 0; i < input.count(); ++i) {
        const QChar chr = QLatin1Char(input.at(i));
        if (chr == QLatin1Char('{'))
            ++ignoreZone;
        if (chr == QLatin1Char('}'))
            --ignoreZone;
        if (ignoreZone == 0) {
            if ((chr.isLetterOrNumber() && chr.isUpper())
                || chr == QLatin1Char('_')) {
                keyword += chr;
            } else {
                if (!keyword.isEmpty()) {
                    if (keyword.size() > 1)
                        *destination << keyword;
                    keyword.clear();
                }
            }
        }
    }
    if (keyword.size() > 1)
        *destination << keyword;
}

void CMakeValidator::parseFunctionOutput(const QByteArray &output)
{
    QList<QByteArray> cmakeFunctionsList = output.split('\n');
    m_functions.clear();
    if (!cmakeFunctionsList.isEmpty()) {
        cmakeFunctionsList.removeFirst(); //remove version string
        foreach (const QByteArray &function, cmakeFunctionsList)
            m_functions << QString::fromLocal8Bit(function.trimmed());
    }
}

QString CMakeValidator::formatFunctionDetails(const QString &command, const QString &args)
{
    return QString::fromLatin1("

%1	%2
")
            .arg(Qt::escape(command)).arg(Qt::escape(args));
}

void CMakeValidator::parseFunctionDetailsOutput(const QByteArray &output)
{
    QStringList cmakeFunctionsList = m_functions;
    QList<QByteArray> cmakeCommandsHelp = output.split('\n');
    for (int i = 0; i < cmakeCommandsHelp.count(); ++i) {
        QByteArray lineTrimmed = cmakeCommandsHelp.at(i).trimmed();
        if (cmakeFunctionsList.isEmpty())
            break;
        if (cmakeFunctionsList.first().toLatin1() == lineTrimmed) {
            QStringList commandSyntaxes;
            QString currentCommandSyntax;
            QString currentCommand = cmakeFunctionsList.takeFirst();
            ++i;
            for (; i < cmakeCommandsHelp.count(); ++i) {
                lineTrimmed = cmakeCommandsHelp.at(i).trimmed();

                if (!cmakeFunctionsList.isEmpty() && cmakeFunctionsList.first().toLatin1() == lineTrimmed) {
                    //start of next function in output
                    if (!currentCommandSyntax.isEmpty())
                        commandSyntaxes << currentCommandSyntax.append(QLatin1String("
"));
                    --i;
                    break;
                }
                if (lineTrimmed.startsWith(currentCommand.toLatin1() + "(")) {
                    if (!currentCommandSyntax.isEmpty())
                        commandSyntaxes << currentCommandSyntax.append(QLatin1String("
"));

                    QByteArray argLine = lineTrimmed.mid(currentCommand.length());
                    extractKeywords(argLine, &m_variables);
                    currentCommandSyntax = formatFunctionDetails(currentCommand, QString::fromUtf8(argLine));
                } else {
                    if (!currentCommandSyntax.isEmpty()) {
                        if (lineTrimmed.isEmpty()) {
                            commandSyntaxes << currentCommandSyntax.append(QLatin1String("
"));
                            currentCommandSyntax.clear();
                        } else {
                            extractKeywords(lineTrimmed, &m_variables);
                            currentCommandSyntax += QString::fromLatin1("
 	%1
")
                                    .arg(Qt::escape(QString::fromLocal8Bit(lineTrimmed)));
                        }
                    }
                }
            }
            m_functionArgs[currentCommand] = commandSyntaxes;
        }
    }
    m_functions = m_functionArgs.keys();
}

void CMakeValidator::parseVariableOutput(const QByteArray &output)
{
    QList<QByteArray> variableList = output.split('\n');
    if (!variableList.isEmpty()) {
        variableList.removeFirst(); //remove version string
        foreach (const QByteArray &variable, variableList) {
            if (variable.contains("_<CONFIG>")) {
                m_variables << QString::fromLocal8Bit(variable).replace(QLatin1String("_<CONFIG>"), QLatin1String("_DEBUG"));
                m_variables << QString::fromLocal8Bit(variable).replace(QLatin1String("_<CONFIG>"), QLatin1String("_RELEASE"));
                m_variables << QString::fromLocal8Bit(variable).replace(QLatin1String("_<CONFIG>"), QLatin1String("_MINSIZEREL"));
                m_variables << QString::fromLocal8Bit(variable).replace(QLatin1String("_<CONFIG>"), QLatin1String("_RELWITHDEBINFO"));
            } else if (variable.contains("_<LANG>")) {
                m_variables << QString::fromLocal8Bit(variable).replace(QLatin1String("_<LANG>"), QLatin1String("_C"));
                m_variables << QString::fromLocal8Bit(variable).replace(QLatin1String("_<LANG>"), QLatin1String("_CXX"));
            } else if (!variable.contains("_<") && !variable.contains('[')) {
                m_variables << QString::fromLocal8Bit(variable);
            }
        }
    }
}

void CMakeValidator::parseDone()
{
    m_variables.sort();
    m_variables.removeDuplicates();
}
<|endoftext|>


<commit_before>#include "fileio.h"

// traverse directory and call Callback() for each file
#ifdef _WIN32
void TraverseDirectory(const wchar_t Dir[], int Callback(const wchar_t file_path[]))
{
    WIN32_FIND_DATA FindFileData;
    wchar_t DirSpec[MAX_PATH];
    //DWORD dwError;
    lstrcpy(DirSpec, Dir);
    lstrcat(DirSpec, L"\\*");

    HANDLE hFind = FindFirstFile(DirSpec, &FindFileData);

    if (hFind == INVALID_HANDLE_VALUE)
    {
        FindClose(hFind);
        return;
    }

    while (FindNextFile(hFind, &FindFileData) != 0)
    {
        if (FindFileData.cFileName[0] == '.')
        {
            if (FindFileData.cFileName[1] == 0 ||               // "."
                lstrcmp(FindFileData.cFileName + 1, L".") == 0) // ".."
            {
                continue;
            }
        }

        wchar_t DirAdd[MAX_PATH];
        lstrcpy(DirAdd, Dir);
        lstrcat(DirAdd, L"\\");
        lstrcat(DirAdd, FindFileData.cFileName);

        if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
        {
            // directory
            TraverseDirectory(DirAdd, Callback);
        }
        else
        {
            // file
            Callback(DirAdd);
        }
    }

    FindClose(hFind);
}
#else
void TraverseDirectory(const char Dir[], int Callback(const char file_path[], const struct stat *sb, int typeflag))
{
    if (ftw(Dir, Callback, 16))
    {
        perror("ftw");
    }
}
#endif // _WIN32

#ifdef _WIN32
bool IsDirectory(const wchar_t path[])
{
    DWORD fa = GetFileAttributes(path);
    if (fa == INVALID_FILE_ATTRIBUTES)
    {
        return false;
    }
    return (fa & FILE_ATTRIBUTE_DIRECTORY) != 0;
}
#else
bool IsDirectory(const char path[])
{
    struct stat sb;
    if (!stat(path, &sb))
    {
        return (sb.st_mode & S_IFDIR) != 0;
    }
    return false;
}
#endif // _WIN32

#ifdef _WIN32
File::File(const wchar_t *filepath)
{
    fp = nullptr;
    hFile = CreateFile(filepath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_WRITE_THROUGH, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {
        PrintErrorMessage("Open file error!");
        size = 0;
        return;
    }
    size = GetFileSize(hFile, NULL);
    hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);
    if (hMap == INVALID_HANDLE_VALUE)
    {
        PrintErrorMessage("Map file error!");
        return;
    }
    fp = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
}

void File::UnMapFile(size_t new_size)
{
    if (new_size < size)
    {
        if (!FlushViewOfFile(fp, 0))
        {
            PrintErrorMessage("Write file error!");
            return;
        }
    }
    UnmapViewOfFile(fp);
    CloseHandle(hMap);
    if (new_size)
    {
        SetFilePointer(hFile, new_size, NULL, FILE_BEGIN);
        SetEndOfFile(hFile);
    }
    CloseHandle(hFile);
}
void File::PrintErrorMessage(const char *msg)
{
    char *error_msg = NULL;
    FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, GetLastError(), 0, (char *)&error_msg, 0, NULL);
    std::cerr << msg << std::endl;
    if (error_msg)
    {
        std::cerr << error_msg << std::endl;
        LocalFree(error_msg);
    }
}
#else
File::File(const char *filepath)
{
    fp = nullptr;
    fd = open(filepath, O_RDWR);

    if(fd == -1)
    {
        perror("Open file error");
        return;
    }

    struct stat sb;
    if (fstat(fd, &sb) == -1)
    {
        perror("fstat");
        return;
    }
    size = sb.st_size;

    // map the file into memory
    fp = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (fp == MAP_FAILED)
    {
        perror("Map file error");
        fp = nullptr;
    }
}

void File::UnMapFile(size_t new_size)
{
    if (munmap(fp, size) == -1)
    {
        perror("munmap");
    }
    if (new_size)
    {
        if (ftruncate(fd, new_size) == -1)
        {
            perror("ftruncate");
        }
    }

    close(fd);
}

#endif // _WIN32<commit_msg>Win: more check on return value<commit_after>#include "fileio.h"

// traverse directory and call Callback() for each file
#ifdef _WIN32
void TraverseDirectory(const wchar_t Dir[], int Callback(const wchar_t file_path[]))
{
    WIN32_FIND_DATA FindFileData;
    wchar_t DirSpec[MAX_PATH];
    //DWORD dwError;
    lstrcpy(DirSpec, Dir);
    lstrcat(DirSpec, L"\\*");

    HANDLE hFind = FindFirstFile(DirSpec, &FindFileData);

    if (hFind == INVALID_HANDLE_VALUE)
    {
        FindClose(hFind);
        return;
    }

    while (FindNextFile(hFind, &FindFileData) != 0)
    {
        if (FindFileData.cFileName[0] == '.')
        {
            if (FindFileData.cFileName[1] == 0 ||               // "."
                lstrcmp(FindFileData.cFileName + 1, L".") == 0) // ".."
            {
                continue;
            }
        }

        wchar_t DirAdd[MAX_PATH];
        lstrcpy(DirAdd, Dir);
        lstrcat(DirAdd, L"\\");
        lstrcat(DirAdd, FindFileData.cFileName);

        if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
        {
            // directory
            TraverseDirectory(DirAdd, Callback);
        }
        else
        {
            // file
            Callback(DirAdd);
        }
    }

    FindClose(hFind);
}
#else
void TraverseDirectory(const char Dir[], int Callback(const char file_path[], const struct stat *sb, int typeflag))
{
    if (ftw(Dir, Callback, 16))
    {
        perror("ftw");
    }
}
#endif // _WIN32

#ifdef _WIN32
bool IsDirectory(const wchar_t path[])
{
    DWORD fa = GetFileAttributes(path);
    if (fa == INVALID_FILE_ATTRIBUTES)
    {
        return false;
    }
    return (fa & FILE_ATTRIBUTE_DIRECTORY) != 0;
}
#else
bool IsDirectory(const char path[])
{
    struct stat sb;
    if (!stat(path, &sb))
    {
        return (sb.st_mode & S_IFDIR) != 0;
    }
    return false;
}
#endif // _WIN32

#ifdef _WIN32
File::File(const wchar_t *filepath)
{
    fp = nullptr;
    hFile = CreateFile(filepath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_WRITE_THROUGH, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {
        PrintErrorMessage("Open file error!");
        size = 0;
        return;
    }
    size = GetFileSize(hFile, NULL);
    hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);
    if (hMap == INVALID_HANDLE_VALUE)
    {
        PrintErrorMessage("Map file error!");
        return;
    }
    fp = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
}

void File::UnMapFile(size_t new_size)
{
    if (new_size < size)
    {
        if (!FlushViewOfFile(fp, 0))
        {
            PrintErrorMessage("Write file error!");
        }
    }
    if (!UnmapViewOfFile(fp))
    {
        PrintErrorMessage("UnmapViewOfFile error!");
    }
    CloseHandle(hMap);
    if (new_size)
    {
        SetFilePointer(hFile, new_size, NULL, FILE_BEGIN);
        if (!SetEndOfFile(hFile))
        {
            PrintErrorMessage("SetEndOfFile error!");
        }
    }
    CloseHandle(hFile);
}
void File::PrintErrorMessage(const char *msg)
{
    char *error_msg = NULL;
    FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, GetLastError(), 0, (char *)&error_msg, 0, NULL);
    std::cerr << msg << std::endl;
    if (error_msg)
    {
        std::cerr << error_msg << std::endl;
        LocalFree(error_msg);
    }
}
#else
File::File(const char *filepath)
{
    fp = nullptr;
    fd = open(filepath, O_RDWR);

    if(fd == -1)
    {
        perror("Open file error");
        return;
    }

    struct stat sb;
    if (fstat(fd, &sb) == -1)
    {
        perror("fstat");
        return;
    }
    size = sb.st_size;

    // map the file into memory
    fp = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (fp == MAP_FAILED)
    {
        perror("Map file error");
        fp = nullptr;
    }
}

void File::UnMapFile(size_t new_size)
{
    if (munmap(fp, size) == -1)
    {
        perror("munmap");
    }
    if (new_size)
    {
        if (ftruncate(fd, new_size) == -1)
        {
            perror("ftruncate");
        }
    }

    close(fd);
}

#endif // _WIN32<|endoftext|>


<commit_before>/** \file   FullTextCache.cc
 *  \brief  Implementation of functions relating to our full-text cache.
 *  \author Dr. Johannes Ruscheinski (johannes.ruscheinski@uni-tuebingen.de)
 *
 *  \copyright 2017-2019 Universitätsbibliothek Tübingen.  All rights reserved.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "FullTextCache.h"
#include <algorithm>
#include <tuple>
#include <ctime>
#include "Compiler.h"
#include "DbRow.h"
#include "Random.h"
#include "StringUtil.h"
#include "TimeUtil.h"
#include "UrlUtil.h"
#include "util.h"
#include "VuFind.h"

constexpr unsigned MIN_CACHE_EXPIRE_TIME_ON_ERROR(42300 * 60); // About 1 month in seconds.
constexpr unsigned MAX_CACHE_EXPIRE_TIME_ON_ERROR(42300 * 60 * 2); // About 2 months in seconds.

bool FullTextCache::getDomainFromUrl(const std::string &url, std::string * const domain) const {
    std::string scheme, username_password, authority, port, path, params, query, fragment, relative_url;
    const bool result(UrlUtil::ParseUrl(url, &scheme, &username_password, &authority, &port, &path, &params, &query,
                                        &fragment, &relative_url));
    if (result)
        *domain = authority;

    return result;
}

bool FullTextCache::entryExpired(const std::string &id, std::vector<std::string> urls) {
    Entry entry;
    if (not getEntry(id, &entry))
        return true;
    const time_t now(std::time(nullptr));
    if (entry.expiration_ == TimeUtil::BAD_TIME_T or now < entry.expiration_) {
        std::vector<std::string> existing_urls(FullTextCache::getEntryUrlsAsStrings(id));

        std::sort(existing_urls.begin(), existing_urls.end());
        std::sort(urls.begin(), urls.end());
        if (urls == existing_urls)
            return false;
    }

    return full_text_cache_urls_.deleteDocument(id);
}

void FullTextCache::expireEntries() {
    full_text_cache_urls_.deleteRange("expiration", Elasticsearch::RO_LTE, "now");
}

bool FullTextCache::getEntry(const std::string &id, Entry * const entry) const {
    const auto results(full_text_cache_.simpleSelect({ "expiration" }, "id", id));
    if (results.empty())
        return false;

    entry->id_ = id;
    if (results.front().find("expiration") == results.front().cend())
        entry->expiration_ = TimeUtil::BAD_TIME_T;
    else
        entry->expiration_ = TimeUtil::Iso8601StringToTimeT(results.front().find("expiration")->second);
    return true;
}

inline std::string GetValueOrEmptyString(const std::map<std::string, std::string> &map, const std::string &key) {
    const auto pair(map.find(key));
    return pair == map.cend() ? "" : pair->second;
}

std::vector<FullTextCache::EntryUrl> FullTextCache::getEntryUrls(const std::string &id) const {
    const auto results(full_text_cache_urls_.simpleSelect({ "url", "domain", "error_message" }, "id", id));

    std::vector<EntryUrl> entry_urls;
    entry_urls.reserve(results.size());
    for (const auto &map : results) {
        EntryUrl entry_url;
        entry_url.id_            = id;
        entry_url.url_           = GetValueOrEmptyString(map, "url");
        entry_url.domain_        = GetValueOrEmptyString(map, "domain");
        entry_url.error_message_ = GetValueOrEmptyString(map, "error_message");
        entry_urls.emplace_back(entry_url);
    }
    return entry_urls;
}

std::vector<std::string> FullTextCache::getEntryUrlsAsStrings(const std::string &id) const {
    const auto results(full_text_cache_urls_.simpleSelect({ "url" }, "id", id));

    std::vector<std::string> urls;
    for (const auto &map : results) {

        const auto url(GetValueOrEmptyString(map, "url"));
        if (not url.empty())
            urls.emplace_back(url);
    }
    return urls;
}

unsigned FullTextCache::getErrorCount() const {
    return full_text_cache_urls_.count({ { "error_message", "*" } });
}

bool FullTextCache::getFullText(const std::string &id, std::string * const full_text) const {
    const auto results(full_text_cache_.simpleSelect({ "full_text" }, "id", id));

    if (results.empty())
        return false;

    *full_text = GetValueOrEmptyString(results.front(), "full_text");
    return true;
}

const std::string US("\x1F"); // ASCII unit separator

std::vector<FullTextCache::EntryGroup> FullTextCache::getEntryGroupsByDomainAndErrorMessage() const {
    const auto results(full_text_cache_urls_.simpleSelect({ "url", "domain", "error_message", "id" }));

    std::unordered_map<std::string, std::tuple<std::string, std::string, unsigned>> domains_and_errors_to_ids_and_urls_and_counts_map;
    for (const auto &map : results) {
        const auto url_pair(map.find("url"));
        const auto domain_pair(map.find("domain"));
        const auto error_message_pair(map.find("error_message"));
        if (url_pair == map.cend() or domain_pair == map.cend() or error_message_pair == map.cend())
            continue;

        const auto id_pair(map.find("id"));
        const auto key(domain_pair->second + US + error_message_pair->second);
        auto id_and_url_and_count(domains_and_errors_to_ids_and_urls_and_counts_map.find(key));
        if (id_and_url_and_count != domains_and_errors_to_ids_and_urls_and_counts_map.end())
            ++std::get<2>(id_and_url_and_count->second);
        else
            domains_and_errors_to_ids_and_urls_and_counts_map[key] = std::make_tuple(id_pair->second, url_pair->second, 1);
    }

    std::vector<EntryGroup> groups;
    groups.reserve(domains_and_errors_to_ids_and_urls_and_counts_map.size());
    for (const auto &domain_and_error_to_id_and_url_and_count : domains_and_errors_to_ids_and_urls_and_counts_map) {
        std::vector<std::string> parts;
        if (unlikely(StringUtil::Split(domain_and_error_to_id_and_url_and_count.first, US, &parts) != 2))
            LOG_ERROR("This should never happen (" + std::to_string(parts.size()) + "): "
                      + StringUtil::CStyleEscape(domain_and_error_to_id_and_url_and_count.first));

        groups.emplace_back(EntryGroup(std::get<2>(domain_and_error_to_id_and_url_and_count.second),
                                       parts[0], parts[1],
                                       std::get<0>(domain_and_error_to_id_and_url_and_count.second),
                                       std::get<1>(domain_and_error_to_id_and_url_and_count.second)));
    }

    std::sort(groups.begin(), groups.end(), [](const EntryGroup &eg1, const EntryGroup &eg2){ return eg1.count_ > eg2.count_;});
    return groups;
}

std::vector<FullTextCache::EntryUrl> FullTextCache::getJoinedEntriesByDomainAndErrorMessage(const std::string &domain_,
                                                                                const std::string &error_message_) const
{
    const auto results(full_text_cache_urls_.simpleSelect({ "url", "domain", "error_message", "id" },
                                                          { { "domain", domain_ }, { "error_message", error_message_ } }));

    std::vector<EntryUrl> entries;
    for (const auto &map : results) {
        const std::string url(GetValueOrEmptyString(map, "url"));
        const std::string domain(GetValueOrEmptyString(map, "domain"));
        const std::string error_message(GetValueOrEmptyString(map, "error_message"));
        const std::string id(GetValueOrEmptyString(map, "id"));

        entries.emplace_back(EntryUrl(id, url, domain, error_message));
    }

    return entries;
}

FullTextCache::EntryUrl FullTextCache::getJoinedEntryByDomainAndErrorMessage(const std::string &domain_, const std::string &error_message_)
    const
{
    const auto results(full_text_cache_urls_.simpleSelect({ "url", "domain", "error_message", "id" },
                                                          { { "domain", domain_ }, { "error_message", error_message_ } },
                                                          /* max_count = */1));
    if (unlikely(results.size() != 1))
        LOG_ERROR("failed to get one entry!");

    const std::string url(GetValueOrEmptyString(results.front(), "url"));
    const std::string domain(GetValueOrEmptyString(results.front(), "domain"));
    const std::string error_message(GetValueOrEmptyString(results.front(), "error_message"));
    const std::string id(GetValueOrEmptyString(results.front(), "id"));

    return EntryUrl(id, url, domain, error_message);
}

unsigned FullTextCache::getSize() const {
    return full_text_cache_.size();
}

void FullTextCache::insertEntry(const std::string &id, const std::string &full_text, const std::vector<EntryUrl> &entry_urls) {
    const time_t now(std::time(nullptr));
    Random::Rand rand(now);
    time_t expiration(TimeUtil::BAD_TIME_T);
    for (const auto &entry_url : entry_urls) {
        if (full_text.empty() and entry_url.error_message_.empty())
            LOG_ERROR("you must provide either data to be cached or a non-empty error message! (id " + id + ")");

        if (not entry_url.error_message_.empty())
            expiration = now + MIN_CACHE_EXPIRE_TIME_ON_ERROR + rand(MAX_CACHE_EXPIRE_TIME_ON_ERROR - MIN_CACHE_EXPIRE_TIME_ON_ERROR);
    }

    std::string expiration_string;
    if (expiration == TimeUtil::BAD_TIME_T) {
        full_text_cache_.simpleInsert({ { "id", id }, { "full_text", full_text } });
    }
    else {
        expiration_string = TimeUtil::TimeTToString(expiration, TimeUtil::ISO_8601_FORMAT);
        full_text_cache_.simpleInsert({ { "id", id }, { "expiration", expiration_string }, { "full_text", full_text } });
    }

    for (const auto &entry_url : entry_urls) {
        if (entry_url.error_message_.empty())
            full_text_cache_urls_.simpleInsert({ { "id", id }, { "url", entry_url.url_ }, { "domain", entry_url.domain_ } });
        else
            full_text_cache_urls_.simpleInsert({ { "id", id }, { "url", entry_url.url_ }, { "domain", entry_url.domain_ },
                                                 { "error_message", entry_url.error_message_ } });
    }
}
<commit_msg>FullTextCache::getJoinedEntriesByDomainAndErrorMessage => fixed sort order<commit_after>/** \file   FullTextCache.cc
 *  \brief  Implementation of functions relating to our full-text cache.
 *  \author Dr. Johannes Ruscheinski (johannes.ruscheinski@uni-tuebingen.de)
 *
 *  \copyright 2017-2019 Universitätsbibliothek Tübingen.  All rights reserved.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "FullTextCache.h"
#include <algorithm>
#include <tuple>
#include <ctime>
#include "Compiler.h"
#include "DbRow.h"
#include "Random.h"
#include "StringUtil.h"
#include "TimeUtil.h"
#include "UrlUtil.h"
#include "util.h"
#include "VuFind.h"

constexpr unsigned MIN_CACHE_EXPIRE_TIME_ON_ERROR(42300 * 60); // About 1 month in seconds.
constexpr unsigned MAX_CACHE_EXPIRE_TIME_ON_ERROR(42300 * 60 * 2); // About 2 months in seconds.

bool FullTextCache::getDomainFromUrl(const std::string &url, std::string * const domain) const {
    std::string scheme, username_password, authority, port, path, params, query, fragment, relative_url;
    const bool result(UrlUtil::ParseUrl(url, &scheme, &username_password, &authority, &port, &path, &params, &query,
                                        &fragment, &relative_url));
    if (result)
        *domain = authority;

    return result;
}

bool FullTextCache::entryExpired(const std::string &id, std::vector<std::string> urls) {
    Entry entry;
    if (not getEntry(id, &entry))
        return true;
    const time_t now(std::time(nullptr));
    if (entry.expiration_ == TimeUtil::BAD_TIME_T or now < entry.expiration_) {
        std::vector<std::string> existing_urls(FullTextCache::getEntryUrlsAsStrings(id));

        std::sort(existing_urls.begin(), existing_urls.end());
        std::sort(urls.begin(), urls.end());
        if (urls == existing_urls)
            return false;
    }

    return full_text_cache_urls_.deleteDocument(id);
}

void FullTextCache::expireEntries() {
    full_text_cache_urls_.deleteRange("expiration", Elasticsearch::RO_LTE, "now");
}

bool FullTextCache::getEntry(const std::string &id, Entry * const entry) const {
    const auto results(full_text_cache_.simpleSelect({ "expiration" }, "id", id));
    if (results.empty())
        return false;

    entry->id_ = id;
    if (results.front().find("expiration") == results.front().cend())
        entry->expiration_ = TimeUtil::BAD_TIME_T;
    else
        entry->expiration_ = TimeUtil::Iso8601StringToTimeT(results.front().find("expiration")->second);
    return true;
}

inline std::string GetValueOrEmptyString(const std::map<std::string, std::string> &map, const std::string &key) {
    const auto pair(map.find(key));
    return pair == map.cend() ? "" : pair->second;
}

std::vector<FullTextCache::EntryUrl> FullTextCache::getEntryUrls(const std::string &id) const {
    const auto results(full_text_cache_urls_.simpleSelect({ "url", "domain", "error_message" }, "id", id));

    std::vector<EntryUrl> entry_urls;
    entry_urls.reserve(results.size());
    for (const auto &map : results) {
        EntryUrl entry_url;
        entry_url.id_            = id;
        entry_url.url_           = GetValueOrEmptyString(map, "url");
        entry_url.domain_        = GetValueOrEmptyString(map, "domain");
        entry_url.error_message_ = GetValueOrEmptyString(map, "error_message");
        entry_urls.emplace_back(entry_url);
    }
    return entry_urls;
}

std::vector<std::string> FullTextCache::getEntryUrlsAsStrings(const std::string &id) const {
    const auto results(full_text_cache_urls_.simpleSelect({ "url" }, "id", id));

    std::vector<std::string> urls;
    for (const auto &map : results) {

        const auto url(GetValueOrEmptyString(map, "url"));
        if (not url.empty())
            urls.emplace_back(url);
    }
    return urls;
}

unsigned FullTextCache::getErrorCount() const {
    return full_text_cache_urls_.count({ { "error_message", "*" } });
}

bool FullTextCache::getFullText(const std::string &id, std::string * const full_text) const {
    const auto results(full_text_cache_.simpleSelect({ "full_text" }, "id", id));

    if (results.empty())
        return false;

    *full_text = GetValueOrEmptyString(results.front(), "full_text");
    return true;
}

const std::string US("\x1F"); // ASCII unit separator

std::vector<FullTextCache::EntryGroup> FullTextCache::getEntryGroupsByDomainAndErrorMessage() const {
    const auto results(full_text_cache_urls_.simpleSelect({ "url", "domain", "error_message", "id" }));

    std::unordered_map<std::string, std::tuple<std::string, std::string, unsigned>> domains_and_errors_to_ids_and_urls_and_counts_map;
    for (const auto &map : results) {
        const auto url_pair(map.find("url"));
        const auto domain_pair(map.find("domain"));
        const auto error_message_pair(map.find("error_message"));
        if (url_pair == map.cend() or domain_pair == map.cend() or error_message_pair == map.cend())
            continue;

        const auto id_pair(map.find("id"));
        const auto key(domain_pair->second + US + error_message_pair->second);
        auto id_and_url_and_count(domains_and_errors_to_ids_and_urls_and_counts_map.find(key));
        if (id_and_url_and_count != domains_and_errors_to_ids_and_urls_and_counts_map.end())
            ++std::get<2>(id_and_url_and_count->second);
        else
            domains_and_errors_to_ids_and_urls_and_counts_map[key] = std::make_tuple(id_pair->second, url_pair->second, 1);
    }

    std::vector<EntryGroup> groups;
    groups.reserve(domains_and_errors_to_ids_and_urls_and_counts_map.size());
    for (const auto &domain_and_error_to_id_and_url_and_count : domains_and_errors_to_ids_and_urls_and_counts_map) {
        std::vector<std::string> parts;
        if (unlikely(StringUtil::Split(domain_and_error_to_id_and_url_and_count.first, US, &parts) != 2))
            LOG_ERROR("This should never happen (" + std::to_string(parts.size()) + "): "
                      + StringUtil::CStyleEscape(domain_and_error_to_id_and_url_and_count.first));

        groups.emplace_back(EntryGroup(std::get<2>(domain_and_error_to_id_and_url_and_count.second),
                                       parts[0], parts[1],
                                       std::get<0>(domain_and_error_to_id_and_url_and_count.second),
                                       std::get<1>(domain_and_error_to_id_and_url_and_count.second)));
    }

    std::sort(groups.begin(), groups.end(), [](const EntryGroup &eg1, const EntryGroup &eg2){ return eg1.count_ > eg2.count_;});
    return groups;
}

std::vector<FullTextCache::EntryUrl> FullTextCache::getJoinedEntriesByDomainAndErrorMessage(const std::string &domain_,
                                                                                const std::string &error_message_) const
{
    const auto results(full_text_cache_urls_.simpleSelect({ "url", "domain", "error_message", "id" },
                                                          { { "domain", domain_ }, { "error_message", error_message_ } }));

    std::vector<EntryUrl> entries;
    for (const auto &map : results) {
        const std::string url(GetValueOrEmptyString(map, "url"));
        const std::string domain(GetValueOrEmptyString(map, "domain"));
        const std::string error_message(GetValueOrEmptyString(map, "error_message"));
        const std::string id(GetValueOrEmptyString(map, "id"));

        entries.emplace_back(EntryUrl(id, url, domain, error_message));
    }

    std::sort(entries.begin(), entries.end(), [](const EntryUrl &eu1, const EntryUrl &eu2){ return eu1.id_ < eu2.id_; });
    return entries;
}

FullTextCache::EntryUrl FullTextCache::getJoinedEntryByDomainAndErrorMessage(const std::string &domain_, const std::string &error_message_)
    const
{
    const auto results(full_text_cache_urls_.simpleSelect({ "url", "domain", "error_message", "id" },
                                                          { { "domain", domain_ }, { "error_message", error_message_ } },
                                                          /* max_count = */1));
    if (unlikely(results.size() != 1))
        LOG_ERROR("failed to get one entry!");

    const std::string url(GetValueOrEmptyString(results.front(), "url"));
    const std::string domain(GetValueOrEmptyString(results.front(), "domain"));
    const std::string error_message(GetValueOrEmptyString(results.front(), "error_message"));
    const std::string id(GetValueOrEmptyString(results.front(), "id"));

    return EntryUrl(id, url, domain, error_message);
}

unsigned FullTextCache::getSize() const {
    return full_text_cache_.size();
}

void FullTextCache::insertEntry(const std::string &id, const std::string &full_text, const std::vector<EntryUrl> &entry_urls) {
    const time_t now(std::time(nullptr));
    Random::Rand rand(now);
    time_t expiration(TimeUtil::BAD_TIME_T);
    for (const auto &entry_url : entry_urls) {
        if (full_text.empty() and entry_url.error_message_.empty())
            LOG_ERROR("you must provide either data to be cached or a non-empty error message! (id " + id + ")");

        if (not entry_url.error_message_.empty())
            expiration = now + MIN_CACHE_EXPIRE_TIME_ON_ERROR + rand(MAX_CACHE_EXPIRE_TIME_ON_ERROR - MIN_CACHE_EXPIRE_TIME_ON_ERROR);
    }

    std::string expiration_string;
    if (expiration == TimeUtil::BAD_TIME_T) {
        full_text_cache_.simpleInsert({ { "id", id }, { "full_text", full_text } });
    }
    else {
        expiration_string = TimeUtil::TimeTToString(expiration, TimeUtil::ISO_8601_FORMAT);
        full_text_cache_.simpleInsert({ { "id", id }, { "expiration", expiration_string }, { "full_text", full_text } });
    }

    for (const auto &entry_url : entry_urls) {
        if (entry_url.error_message_.empty())
            full_text_cache_urls_.simpleInsert({ { "id", id }, { "url", entry_url.url_ }, { "domain", entry_url.domain_ } });
        else
            full_text_cache_urls_.simpleInsert({ { "id", id }, { "url", entry_url.url_ }, { "domain", entry_url.domain_ },
                                                 { "error_message", entry_url.error_message_ } });
    }
}
<|endoftext|>


<commit_before>#include <cassert>
#include "ch_frb_io_internals.hpp"

using namespace std;
using namespace ch_frb_io;

inline bool array_contains(const int *arr, int len, int x)
{
    for (int i = 0; i < len; i++)
	if (arr[i] == x)
	    return true;
    return false;
}

inline double intval(int beam_id, int ifreq, int it)
{
    return 0.823*beam_id + 1.319*ifreq + 0.2139*it;
}

// weights are between 0.2 and 1
inline double wtval(int beam_id, int ifreq, int it)
{
    return 0.6 + 0.4 * sin(1.328*beam_id + 2.382*ifreq + 0.883*it);
}

// -------------------------------------------------------------------------------------------------

struct unit_test_instance {
    static constexpr int maxbeams = 8;

    int nbeams = 0;
    int nupfreq = 0;
    int nfreq_coarse_per_packet = 0;
    int nt_per_packet = 0;
    int nt_per_chunk = 0;
    int nt_tot = 0;
    int fpga_counts_per_sample = 0;
    uint64_t initial_fpga_count = 0;
    float wt_cutoff = 0.0;

    vector<int> recv_beam_ids;
    vector<int> send_beam_ids;
    vector<int> send_freq_ids;
    int send_stride;

    // not protected by lock
    pthread_t consumer_threads[maxbeams];
    shared_ptr<ch_frb_io::intensity_network_stream> istream;

    pthread_mutex_t lock;

    unit_test_instance(std::mt19937 &rng);
    ~unit_test_instance();
};

unit_test_instance::unit_test_instance(std::mt19937 &rng)
{
    const int nfreq_coarse = ch_frb_io::constants::nfreq_coarse;

    this->nbeams = randint(rng, 1, maxbeams+1);
    this->nupfreq = randint(rng, 1, 17);
    this->nfreq_coarse_per_packet = 1 << randint(rng,0,5);

    // nt_max = max possible nt_per_packet (before max packet size is exceeded)
    int header_nbytes = 24 + 2*nbeams + 2*nfreq_coarse_per_packet + 8*nbeams*nfreq_coarse_per_packet;
    int nbytes_per_nt = nbeams * nfreq_coarse_per_packet * nupfreq;
    int nt_max = (ch_frb_io::constants::max_output_udp_packet_size - header_nbytes) / nbytes_per_nt;

    assert(nt_max >= 3);
    this->nt_per_packet = randint(rng, nt_max/2+1, nt_max+1);

    // FIXME increase nt_tot
    this->nt_per_chunk = nt_per_packet * randint(rng,1,5);
    this->nt_tot = nt_per_chunk * randint(rng,1,5);

    this->fpga_counts_per_sample = randint(rng, 1, 1025);
    this->initial_fpga_count = fpga_counts_per_sample * randint(rng, 0, 4097);
    this->wt_cutoff = uniform_rand(rng, 0.3, 0.7);

    // Clunky way of generating random beam_ids
    this->recv_beam_ids.resize(nbeams);
    for (int i = 0; i < nbeams; i++) {
	do {
	    recv_beam_ids[i] = randint(rng, 0, ch_frb_io::constants::max_allowed_beam_id);
	} while (array_contains(&recv_beam_ids[0], i, recv_beam_ids[i]));
    }

    // To slightly strengthen the test, we permute the receiver beams relative to sender
    this->send_beam_ids = recv_beam_ids;
    std::shuffle(send_beam_ids.begin(), send_beam_ids.end(), rng);

    // Randomly permute frequencies, just to strengthen the test
    this->send_freq_ids.resize(nfreq_coarse);
    for (int i = 0; i < nfreq_coarse; i++)
	send_freq_ids[i] = i;
    std::shuffle(send_freq_ids.begin(), send_freq_ids.end(), rng);

    this->send_stride = randint(rng, nt_per_chunk, 2*nt_per_chunk+1);

    // Will be needed shortly for synchronization
    xpthread_mutex_init(&this->lock);
}

unit_test_instance::~unit_test_instance()
{
    pthread_mutex_destroy(&lock);
}

// -------------------------------------------------------------------------------------------------

struct consumer_thread_context {
    shared_ptr<ch_frb_io::intensity_beam_assembler> assembler;
    shared_ptr<unit_test_instance> tp;

    pthread_mutex_t lock;
    pthread_cond_t cond_running;
    bool is_running = false;

    consumer_thread_context(const shared_ptr<ch_frb_io::intensity_beam_assembler> &assembler_, const shared_ptr<unit_test_instance> &tp_)
	: assembler(assembler_), tp(tp_)
    {
	xpthread_mutex_init(&lock);
	xpthread_cond_init(&cond_running);
    }
};

static void *consumer_thread_main(void *opaque_arg)
{
    consumer_thread_context *context = reinterpret_cast<consumer_thread_context *> (opaque_arg);
    shared_ptr<ch_frb_io::intensity_beam_assembler> assembler = context->assembler;
    shared_ptr<unit_test_instance> tp = context->tp;

    pthread_mutex_lock(&context->lock);
    context->is_running = true;
    pthread_cond_broadcast(&context->cond_running);
    pthread_mutex_unlock(&context->lock);

    // actual logic of the consumer thread goes here
    cerr << "consumer_thread: artificially exiting\n";
    return NULL;
}

static void spawn_consumer_thread(const shared_ptr<ch_frb_io::intensity_beam_assembler> &assembler, const shared_ptr<unit_test_instance> &tp, int ibeam)
{
    consumer_thread_context context(assembler, tp);

    int err = pthread_create(&tp->consumer_threads[ibeam], NULL, consumer_thread_main, &context);
    if (err)
	throw runtime_error(string("pthread_create() failed to create consumer thread: ") + strerror(errno));

    pthread_mutex_lock(&context.lock);
    while (!context.is_running)
	pthread_cond_wait(&context.cond_running, &context.lock);
    pthread_mutex_unlock(&context.lock);
}

static void spawn_all_receive_threads(const shared_ptr<unit_test_instance> &tp)
{
    vector<shared_ptr<ch_frb_io::intensity_beam_assembler> > assemblers;

    for (int ibeam = 0; ibeam < tp->nbeams; ibeam++) {
	auto assembler = ch_frb_io::intensity_beam_assembler::make(tp->recv_beam_ids[ibeam]);
	spawn_consumer_thread(assembler, tp, ibeam);
	assemblers.push_back(assembler);
    }

    tp->istream = intensity_network_stream::make(assemblers, ch_frb_io::constants::default_udp_port);
}

// -------------------------------------------------------------------------------------------------

static void send_data(const shared_ptr<unit_test_instance> &tp)
{
    const int nbeams = tp->nbeams;
    const int nupfreq = tp->nupfreq;
    const int nfreq_coarse = ch_frb_io::constants::nfreq_coarse;
    const int nt_chunk = tp->nt_per_chunk;
    const int nchunks = tp->nt_tot / tp->nt_per_chunk;
    const int stride = tp->send_stride;
    const int s2 = nupfreq * stride;
    const int s3 = nfreq_coarse * s2;

    string dstname = "127.0.0.1:" + to_string(ch_frb_io::constants::default_udp_port);

    // spawns network thread
    auto ostream = intensity_network_ostream::make(dstname, tp->send_beam_ids, tp->send_freq_ids, tp->nupfreq,
						   tp->nt_per_chunk, tp->nfreq_coarse_per_packet,
						   tp->nt_per_packet, tp->fpga_counts_per_sample,
						   tp->wt_cutoff, 0.0);

    vector<float> intensity(nbeams * s3, 0.0);
    vector<float> weights(nbeams * s3, 0.0);

    for (int ichunk = 0; ichunk < nchunks; ichunk++) {
	int it0 = ichunk * nt_chunk;   // start of chunk

	for (int ibeam = 0; ibeam < nbeams; ibeam++) {
	    int beam_id = tp->send_beam_ids[ibeam];

	    for (int ifreq_coarse = 0; ifreq_coarse < nfreq_coarse; ifreq_coarse++) {
		int coarse_freq_id = tp->send_freq_ids[ifreq_coarse];

		for (int iupfreq = 0; iupfreq < nupfreq; iupfreq++) {
		    int ifreq_logical = coarse_freq_id * nupfreq + iupfreq;

		    int row_start = ibeam*s3 + ifreq_coarse*s2 + iupfreq*stride;
		    float *i_row = &intensity[row_start];
		    float *w_row = &weights[row_start];

		    for (int it = 0; it < nt_chunk; it++) {
			i_row[it] = intval(beam_id, ifreq_logical, it+it0);
			w_row[it] = wtval(beam_id, ifreq_logical, it+it0);
		    }
		}
	    }
	}

	uint64_t fpga_count = tp->initial_fpga_count + ichunk * nt_chunk * tp->fpga_counts_per_sample;
	ostream->send_chunk(&intensity[0], &weights[0], stride, fpga_count, true);
    }

    // joins network thread
    ostream->end_stream(true);
}

// -------------------------------------------------------------------------------------------------

int main(int argc, char **argv)
{
    std::random_device rd;
    std::mt19937 rng(rd());

    for (int iouter = 0; iouter < 100; iouter++) {
	auto tp = make_shared<unit_test_instance> (rng);
	spawn_all_receive_threads(tp);

	tp->istream->start_stream();
	send_data(tp);
	tp->istream->end_stream(true);  // join_threads=true

	for (int ibeam = 0; ibeam < tp->nbeams; ibeam++) {
	    int err = pthread_join(tp->consumer_threads[ibeam], NULL);
	    if (err)
		throw runtime_error("pthread_join() failed");
	}
    }

    return 0;
}
<commit_msg>unit_test_instance::show()<commit_after>#include <cassert>
#include "ch_frb_io_internals.hpp"

using namespace std;
using namespace ch_frb_io;

inline bool array_contains(const int *arr, int len, int x)
{
    for (int i = 0; i < len; i++)
	if (arr[i] == x)
	    return true;
    return false;
}

inline double intval(int beam_id, int ifreq, int it)
{
    return 0.823*beam_id + 1.319*ifreq + 0.2139*it;
}

// weights are between 0.2 and 1
inline double wtval(int beam_id, int ifreq, int it)
{
    return 0.6 + 0.4 * sin(1.328*beam_id + 2.382*ifreq + 0.883*it);
}

// -------------------------------------------------------------------------------------------------

struct unit_test_instance {
    static constexpr int maxbeams = 8;

    int nbeams = 0;
    int nupfreq = 0;
    int nfreq_coarse_per_packet = 0;
    int nt_per_packet = 0;
    int nt_per_chunk = 0;
    int nt_tot = 0;
    int fpga_counts_per_sample = 0;
    uint64_t initial_fpga_count = 0;
    float wt_cutoff = 0.0;

    vector<int> recv_beam_ids;
    vector<int> send_beam_ids;
    vector<int> send_freq_ids;
    int send_stride;

    // not protected by lock
    pthread_t consumer_threads[maxbeams];
    shared_ptr<ch_frb_io::intensity_network_stream> istream;

    pthread_mutex_t lock;

    unit_test_instance(std::mt19937 &rng);
    ~unit_test_instance();

    void show() const;
};

unit_test_instance::unit_test_instance(std::mt19937 &rng)
{
    const int nfreq_coarse = ch_frb_io::constants::nfreq_coarse;

    this->nbeams = randint(rng, 1, maxbeams+1);
    this->nupfreq = randint(rng, 1, 17);
    this->nfreq_coarse_per_packet = 1 << randint(rng,0,5);

    // nt_max = max possible nt_per_packet (before max packet size is exceeded)
    int header_nbytes = 24 + 2*nbeams + 2*nfreq_coarse_per_packet + 8*nbeams*nfreq_coarse_per_packet;
    int nbytes_per_nt = nbeams * nfreq_coarse_per_packet * nupfreq;
    int nt_max = (ch_frb_io::constants::max_output_udp_packet_size - header_nbytes) / nbytes_per_nt;

    assert(nt_max >= 3);
    this->nt_per_packet = randint(rng, nt_max/2+1, nt_max+1);

    // FIXME increase nt_tot
    this->nt_per_chunk = nt_per_packet * randint(rng,1,5);
    this->nt_tot = nt_per_chunk * randint(rng,1,5);

    this->fpga_counts_per_sample = randint(rng, 1, 1025);
    this->initial_fpga_count = fpga_counts_per_sample * randint(rng, 0, 4097);
    this->wt_cutoff = uniform_rand(rng, 0.3, 0.7);

    // Clunky way of generating random beam_ids
    this->recv_beam_ids.resize(nbeams);
    for (int i = 0; i < nbeams; i++) {
	do {
	    recv_beam_ids[i] = randint(rng, 0, ch_frb_io::constants::max_allowed_beam_id);
	} while (array_contains(&recv_beam_ids[0], i, recv_beam_ids[i]));
    }

    // To slightly strengthen the test, we permute the receiver beams relative to sender
    this->send_beam_ids = recv_beam_ids;
    std::shuffle(send_beam_ids.begin(), send_beam_ids.end(), rng);

    // Randomly permute frequencies, just to strengthen the test
    this->send_freq_ids.resize(nfreq_coarse);
    for (int i = 0; i < nfreq_coarse; i++)
	send_freq_ids[i] = i;
    std::shuffle(send_freq_ids.begin(), send_freq_ids.end(), rng);

    this->send_stride = randint(rng, nt_per_chunk, 2*nt_per_chunk+1);

    // Will be needed shortly for synchronization
    xpthread_mutex_init(&this->lock);
}

unit_test_instance::~unit_test_instance()
{
    pthread_mutex_destroy(&lock);
}

void unit_test_instance::show() const
{
    cout << "nbeams=" << nbeams << endl
	 << "nupfreq=" << nupfreq << endl
	 << "nfreq_coarse_per_packet=" << nfreq_coarse_per_packet << endl
	 << "nt_per_packet=" << nt_per_packet << endl
	 << "nt_per_chunk=" << nt_per_chunk << endl
	 << "nt_tot=" << nt_tot << endl;
}

// -------------------------------------------------------------------------------------------------

struct consumer_thread_context {
    shared_ptr<ch_frb_io::intensity_beam_assembler> assembler;
    shared_ptr<unit_test_instance> tp;

    pthread_mutex_t lock;
    pthread_cond_t cond_running;
    bool is_running = false;

    consumer_thread_context(const shared_ptr<ch_frb_io::intensity_beam_assembler> &assembler_, const shared_ptr<unit_test_instance> &tp_)
	: assembler(assembler_), tp(tp_)
    {
	xpthread_mutex_init(&lock);
	xpthread_cond_init(&cond_running);
    }
};

static void *consumer_thread_main(void *opaque_arg)
{
    consumer_thread_context *context = reinterpret_cast<consumer_thread_context *> (opaque_arg);
    shared_ptr<ch_frb_io::intensity_beam_assembler> assembler = context->assembler;
    shared_ptr<unit_test_instance> tp = context->tp;

    pthread_mutex_lock(&context->lock);
    context->is_running = true;
    pthread_cond_broadcast(&context->cond_running);
    pthread_mutex_unlock(&context->lock);

    // actual logic of the consumer thread goes here
    cerr << "consumer_thread: artificially exiting\n";
    return NULL;
}

static void spawn_consumer_thread(const shared_ptr<ch_frb_io::intensity_beam_assembler> &assembler, const shared_ptr<unit_test_instance> &tp, int ibeam)
{
    consumer_thread_context context(assembler, tp);

    int err = pthread_create(&tp->consumer_threads[ibeam], NULL, consumer_thread_main, &context);
    if (err)
	throw runtime_error(string("pthread_create() failed to create consumer thread: ") + strerror(errno));

    pthread_mutex_lock(&context.lock);
    while (!context.is_running)
	pthread_cond_wait(&context.cond_running, &context.lock);
    pthread_mutex_unlock(&context.lock);
}

static void spawn_all_receive_threads(const shared_ptr<unit_test_instance> &tp)
{
    vector<shared_ptr<ch_frb_io::intensity_beam_assembler> > assemblers;

    for (int ibeam = 0; ibeam < tp->nbeams; ibeam++) {
	auto assembler = ch_frb_io::intensity_beam_assembler::make(tp->recv_beam_ids[ibeam]);
	spawn_consumer_thread(assembler, tp, ibeam);
	assemblers.push_back(assembler);
    }

    tp->istream = intensity_network_stream::make(assemblers, ch_frb_io::constants::default_udp_port);
}

// -------------------------------------------------------------------------------------------------

static void send_data(const shared_ptr<unit_test_instance> &tp)
{
    const int nbeams = tp->nbeams;
    const int nupfreq = tp->nupfreq;
    const int nfreq_coarse = ch_frb_io::constants::nfreq_coarse;
    const int nt_chunk = tp->nt_per_chunk;
    const int nchunks = tp->nt_tot / tp->nt_per_chunk;
    const int stride = tp->send_stride;
    const int s2 = nupfreq * stride;
    const int s3 = nfreq_coarse * s2;

    string dstname = "127.0.0.1:" + to_string(ch_frb_io::constants::default_udp_port);

    // spawns network thread
    auto ostream = intensity_network_ostream::make(dstname, tp->send_beam_ids, tp->send_freq_ids, tp->nupfreq,
						   tp->nt_per_chunk, tp->nfreq_coarse_per_packet,
						   tp->nt_per_packet, tp->fpga_counts_per_sample,
						   tp->wt_cutoff, 0.0);

    vector<float> intensity(nbeams * s3, 0.0);
    vector<float> weights(nbeams * s3, 0.0);

    for (int ichunk = 0; ichunk < nchunks; ichunk++) {
	int it0 = ichunk * nt_chunk;   // start of chunk

	for (int ibeam = 0; ibeam < nbeams; ibeam++) {
	    int beam_id = tp->send_beam_ids[ibeam];

	    for (int ifreq_coarse = 0; ifreq_coarse < nfreq_coarse; ifreq_coarse++) {
		int coarse_freq_id = tp->send_freq_ids[ifreq_coarse];

		for (int iupfreq = 0; iupfreq < nupfreq; iupfreq++) {
		    int ifreq_logical = coarse_freq_id * nupfreq + iupfreq;

		    int row_start = ibeam*s3 + ifreq_coarse*s2 + iupfreq*stride;
		    float *i_row = &intensity[row_start];
		    float *w_row = &weights[row_start];

		    for (int it = 0; it < nt_chunk; it++) {
			i_row[it] = intval(beam_id, ifreq_logical, it+it0);
			w_row[it] = wtval(beam_id, ifreq_logical, it+it0);
		    }
		}
	    }
	}

	uint64_t fpga_count = tp->initial_fpga_count + ichunk * nt_chunk * tp->fpga_counts_per_sample;
	ostream->send_chunk(&intensity[0], &weights[0], stride, fpga_count, true);
    }

    // joins network thread
    ostream->end_stream(true);
}

// -------------------------------------------------------------------------------------------------

int main(int argc, char **argv)
{
    std::random_device rd;
    std::mt19937 rng(rd());

    for (int iouter = 0; iouter < 100; iouter++) {
	auto tp = make_shared<unit_test_instance> (rng);
	tp->show();

	spawn_all_receive_threads(tp);
	tp->istream->start_stream();

	send_data(tp);
	tp->istream->end_stream(true);  // join_threads=true

	for (int ibeam = 0; ibeam < tp->nbeams; ibeam++) {
	    int err = pthread_join(tp->consumer_threads[ibeam], NULL);
	    if (err)
		throw runtime_error("pthread_join() failed");
	}
    }

    return 0;
}
<|endoftext|>


<commit_before>//------------------------------------------------------------------------
// csved_command.cpp
//
// Base class for CSVED commands
//
// Copyright (C) 2008 Neil Butterworth
//------------------------------------------------------------------------

#include "a_str.h"
#include "a_csv.h"
#include "csved_command.h"
#include "csved_except.h"
#include "csved_strings.h"

using std::string;
using std::vector;

namespace CSVED {

//----------------------------------------------------------------------------
// This is the help for the generic flags
//----------------------------------------------------------------------------

const char * const GEN_SEED = "  -seed n\tseed to be used by random() function\n";
const char * const GEN_SEP = "  -sep s\tspecify CSV field separator character\n";
const char * const GEN_RSEP = "  -rsep s\tas for -sep but retain separator on output\n";
const char * const GEN_OSEP = "  -osep s\tspecifies output separator\n";
const char * const GEN_IBL = "  -ibl\t\tignore blank input lines\n";
const char * const GEN_IFN = "  -ifn\t\tignore field name record\n";
const char * const GEN_SMQ = "  -smq\t\tuse smart quotes on output\n";
const char * const GEN_SQF = "  -sqf fields\tspecify fields that must be quoted\n";
const char * const GEN_OFL	= "  -o file\twrite output to file "
									"rather than standard output\n";

const char * const GEN_SKIP = "  -skip t\tif test t is true, do not process or output record\n";
const char * const GEN_PASS = "  -pass t\tif test t is true, output CSV record as is\n";

//------------------------------------------------------------------------
// Construct from command name, short description and list of flags
// that can be used with this command.
//---------------------------------------------------------------------------

Command :: Command( const string & name,
						const string & desc)
	: mName( name ), mDesc( desc )   {
}

Command :: Command( const string & name,
						const string & desc,
						const string & help )
	: mName( name ), mDesc( desc ), mHelp( help ){
}

//---------------------------------------------------------------------------
// Do-nothing dtor
//---------------------------------------------------------------------------

Command :: ~Command() {
	// nothing
}

//---------------------------------------------------------------------------
// accessors
//---------------------------------------------------------------------------

string Command :: Name() const {
	return mName;
}

string Command :: Desc() const {
	return mDesc;
}

void Command :: AddHelp( const string & help ) {
	mHelp = help;
}

//----------------------------------------------------------------------------
// Expressions to test whether an input row should be skipped or passed
// through as is.
//----------------------------------------------------------------------------

void Command :: GetSkipOptions( const ALib::CommandLine & cl ) {

	mSkipExpr = ALib::Expression();
	mPassExpr = ALib::Expression();

	if ( cl.HasFlag( FLAG_SKIP ) ) {
		string expr = cl.GetValue( FLAG_SKIP );
		mSkipExpr.Compile( expr );
	}
	if ( cl.HasFlag( FLAG_PASS ) ) {
		string expr = cl.GetValue( FLAG_PASS );
		mPassExpr.Compile( expr );
	}
}

static void SetPosParams( ALib::Expression & e, const CSVRow & r ) {
	e.ClearPosParams();
	for( unsigned int i = 0; i < r.size(); i++ ) {
		e.AddPosParam( r[i] );
	}
}

static bool EvalSkipPass( ALib::Expression & e, const CSVRow & r ) {
	if ( e.IsCompiled() ) {
		SetPosParams( e, r );
		string ev = e.Evaluate();
		return ev == "0" ? false : true;
	}
	else {
		return false;
	}
}

bool Command :: Skip( const CSVRow & r  )  {
	return EvalSkipPass( mSkipExpr, r );
}

bool Command :: Pass( const CSVRow & r )  {
	return EvalSkipPass( mPassExpr, r );
}

//----------------------------------------------------------------------------
// Process help text. May contain a terminal section preceded by a # char
// containing names of the generic flags applicable to this command. The
// name "ALL" means the command can support all generic flags.
//
// ToDo: This is a mess and needs rethinking. But it does "work".
//----------------------------------------------------------------------------

const char GFL_DELIM = '#';

string Command :: Help() const {

	if ( mHelp == "" ) {	// no help written yet
		return "";
	}

	vector <string> tmp;
	ALib::Split( mHelp, GFL_DELIM, tmp );
	if ( tmp.size() > 1 ) {
		tmp[0] += "\n";
		if ( tmp[1] == "ALL" ) {
			tmp[1] ="IBL,IFN,SMQ,OFL,SEP";
		}
		if ( tmp[1].find( "IBL" ) != string::npos ) {
			tmp[0] += GEN_IBL;
		}
		if ( tmp[1].find( "SEP" ) != string::npos ) {
			// if you can specify a CSV separator you can specify & retain
			tmp[0] += GEN_SEP;
			tmp[0] += GEN_RSEP;
			tmp[0] += GEN_OSEP;
		}
		if ( tmp[1].find( "IFN" ) != string::npos ) {
			tmp[0] += GEN_IFN;
		}
		if ( tmp[1].find( "SMQ" ) != string::npos ) {
			// if you can specify smart quotes, you can specify a quote list
			tmp[0] += GEN_SMQ;
			tmp[0] += GEN_SQF;
		}
		if ( tmp[1].find( "OFL" ) != string::npos ) {
			tmp[0] += GEN_OFL;
		}
		if ( tmp[1].find( "SKIP" ) != string::npos ) {
			tmp[0] += GEN_SKIP;
		}
		if ( tmp[1].find( "PASS" ) != string::npos ) {
			tmp[0] += GEN_PASS;
		}
	}
	return tmp[0];
}

//---------------------------------------------------------------------------
// Give my flags to the command line & then get it to check them
//---------------------------------------------------------------------------

void Command :: CheckFlags( ALib::CommandLine & cmd ) {

	for ( unsigned int i = 0; i < mFlags.size(); i++ ) {
		cmd.AddFlag( mFlags[i] );
	}

	// all commands can have output flag and can ignore blank lines
	// also now can have smart quotes and ignore col header
	cmd.AddFlag( ALib::CommandLineFlag( FLAG_OUT, false, 1 ) );
	cmd.AddFlag( ALib::CommandLineFlag( FLAG_IGNBL, false, 0 ) );
	cmd.AddFlag( ALib::CommandLineFlag( FLAG_SMARTQ, false, 0 ) );
	cmd.AddFlag( ALib::CommandLineFlag( FLAG_ICNAMES, false, 0 ) );
	cmd.AddFlag( ALib::CommandLineFlag( FLAG_CSVSEP, false, 1 ) );
	cmd.AddFlag( ALib::CommandLineFlag( FLAG_CSVSEPR, false, 1 ) );
	cmd.AddFlag( ALib::CommandLineFlag( FLAG_OUTSEP, false, 1 ) );
	cmd.AddFlag( ALib::CommandLineFlag( FLAG_QLIST, false, 1 ) );
	cmd.AddFlag( ALib::CommandLineFlag( FLAG_RFSEED, false, 1 ) );

	vector <string> tmp;
	ALib::Split( mHelp, GFL_DELIM, tmp );
	if ( tmp.size() > 1 && tmp[1].find( "SKIP" ) != string::npos ) {
		cmd.AddFlag( ALib::CommandLineFlag( FLAG_SKIP, false, 1 ) );
		cmd.AddFlag( ALib::CommandLineFlag( FLAG_PASS, false, 1 ) );
	}

	cmd.CheckFlags( 2 );		// don't check the command name

}

//----------------------------------------------------------------------------
// Provide count of flags excluding the generic ones
//----------------------------------------------------------------------------

int Command :: CountNonGeneric( const ALib::CommandLine & cmd ) const  {

	int n = 0;

	for ( int i = 0; i < cmd.Argc(); i++ ) {
		if ( ALib::StrFirst( cmd.Argv(i) ) == '-' ) {
			string arg = cmd.Argv( i );
			if ( arg != FLAG_OUT
				&& arg != FLAG_IGNBL
				&& arg != FLAG_SMARTQ
				&& arg != FLAG_CSVSEP
				&& arg != FLAG_OUTSEP
				&& arg != FLAG_PASS
				&& arg != FLAG_ICNAMES ) {
					n++;
			}
		}
	}

	return n;
}

//---------------------------------------------------------------------------
// Add flag to allowed flags for a command
//---------------------------------------------------------------------------

void Command :: AddFlag( const ALib::CommandLineFlag & f ) {
	mFlags.push_back( f );
}

//------------------------------------------------------------------------

}	// end namespace

// end
<commit_msg>fixed problem with built-in help<commit_after>//------------------------------------------------------------------------
// csved_command.cpp
//
// Base class for CSVED commands
//
// Copyright (C) 2008 Neil Butterworth
//------------------------------------------------------------------------

#include "a_str.h"
#include "a_csv.h"
#include "csved_command.h"
#include "csved_except.h"
#include "csved_strings.h"

using std::string;
using std::vector;

namespace CSVED {

//----------------------------------------------------------------------------
// This is the help for the generic flags
//----------------------------------------------------------------------------

const char * const GEN_SEED = "  -seed n\tseed to be used by random() function\n";
const char * const GEN_SEP = "  -sep s\tspecify CSV field separator character\n";
const char * const GEN_RSEP = "  -rsep s\tas for -sep but retain separator on output\n";
const char * const GEN_OSEP = "  -osep s\tspecifies output separator\n";
const char * const GEN_IBL = "  -ibl\t\tignore blank input lines\n";
const char * const GEN_IFN = "  -ifn\t\tignore field name record\n";
const char * const GEN_SMQ = "  -smq\t\tuse smart quotes on output\n";
const char * const GEN_SQF = "  -sqf fields\tspecify fields that must be quoted\n";
const char * const GEN_OFL	= "  -o file\twrite output to file "
									"rather than standard output\n";

const char * const GEN_SKIP = "  -skip t\tif test t is true, do not process or output record\n";
const char * const GEN_PASS = "  -pass t\tif test t is true, output CSV record as is\n";

//------------------------------------------------------------------------
// Construct from command name, short description and list of flags
// that can be used with this command.
//---------------------------------------------------------------------------

Command :: Command( const string & name,
						const string & desc)
	: mName( name ), mDesc( desc )   {
}

Command :: Command( const string & name,
						const string & desc,
						const string & help )
	: mName( name ), mDesc( desc ), mHelp( help ){
}

//---------------------------------------------------------------------------
// Do-nothing dtor
//---------------------------------------------------------------------------

Command :: ~Command() {
	// nothing
}

//---------------------------------------------------------------------------
// accessors
//---------------------------------------------------------------------------

string Command :: Name() const {
	return mName;
}

string Command :: Desc() const {
	return mDesc;
}

void Command :: AddHelp( const string & help ) {
	mHelp = help;
}

//----------------------------------------------------------------------------
// Expressions to test whether an input row should be skipped or passed
// through as is.
//----------------------------------------------------------------------------

void Command :: GetSkipOptions( const ALib::CommandLine & cl ) {

	mSkipExpr = ALib::Expression();
	mPassExpr = ALib::Expression();

	if ( cl.HasFlag( FLAG_SKIP ) ) {
		string expr = cl.GetValue( FLAG_SKIP );
		mSkipExpr.Compile( expr );
	}
	if ( cl.HasFlag( FLAG_PASS ) ) {
		string expr = cl.GetValue( FLAG_PASS );
		mPassExpr.Compile( expr );
	}
}

static void SetPosParams( ALib::Expression & e, const CSVRow & r ) {
	e.ClearPosParams();
	for( unsigned int i = 0; i < r.size(); i++ ) {
		e.AddPosParam( r[i] );
	}
}

static bool EvalSkipPass( ALib::Expression & e, const CSVRow & r ) {
	if ( e.IsCompiled() ) {
		SetPosParams( e, r );
		string ev = e.Evaluate();
		return ev == "0" ? false : true;
	}
	else {
		return false;
	}
}

bool Command :: Skip( const CSVRow & r  )  {
	return EvalSkipPass( mSkipExpr, r );
}

bool Command :: Pass( const CSVRow & r )  {
	return EvalSkipPass( mPassExpr, r );
}

//----------------------------------------------------------------------------
// Process help text. May contain a terminal section preceded by a # char
// containing names of the generic flags applicable to this command. The
// name "ALL" means the command can support all generic flags.
//
// ToDo: This is a mess and needs rethinking. But it does "work".
//----------------------------------------------------------------------------

const char GFL_DELIM = '#';

string Command :: Help() const {

	if ( mHelp == "" ) {	// no help written yet
		return "";
	}

	vector <string> tmp;
	ALib::Split( mHelp, GFL_DELIM, tmp );
	if ( tmp.size() > 1 ) {
		tmp[0] += "\n";
		if ( tmp[1].find( "ALL" ) != string::npos ) {
			tmp[1] +="IBL,IFN,SMQ,OFL,SEP";
		}
		if ( tmp[1].find( "IBL" ) != string::npos ) {
			tmp[0] += GEN_IBL;
		}
		if ( tmp[1].find( "SEP" ) != string::npos ) {
			// if you can specify a CSV separator you can specify & retain
			tmp[0] += GEN_SEP;
			tmp[0] += GEN_RSEP;
			tmp[0] += GEN_OSEP;
		}
		if ( tmp[1].find( "IFN" ) != string::npos ) {
			tmp[0] += GEN_IFN;
		}
		if ( tmp[1].find( "SMQ" ) != string::npos ) {
			// if you can specify smart quotes, you can specify a quote list
			tmp[0] += GEN_SMQ;
			tmp[0] += GEN_SQF;
		}
		if ( tmp[1].find( "OFL" ) != string::npos ) {
			tmp[0] += GEN_OFL;
		}
		if ( tmp[1].find( "SKIP" ) != string::npos ) {
			tmp[0] += GEN_SKIP;
		}
		if ( tmp[1].find( "PASS" ) != string::npos ) {
			tmp[0] += GEN_PASS;
		}
	}
	return tmp[0];
}

//---------------------------------------------------------------------------
// Give my flags to the command line & then get it to check them
//---------------------------------------------------------------------------

void Command :: CheckFlags( ALib::CommandLine & cmd ) {

	for ( unsigned int i = 0; i < mFlags.size(); i++ ) {
		cmd.AddFlag( mFlags[i] );
	}

	// all commands can have output flag and can ignore blank lines
	// also now can have smart quotes and ignore col header
	cmd.AddFlag( ALib::CommandLineFlag( FLAG_OUT, false, 1 ) );
	cmd.AddFlag( ALib::CommandLineFlag( FLAG_IGNBL, false, 0 ) );
	cmd.AddFlag( ALib::CommandLineFlag( FLAG_SMARTQ, false, 0 ) );
	cmd.AddFlag( ALib::CommandLineFlag( FLAG_ICNAMES, false, 0 ) );
	cmd.AddFlag( ALib::CommandLineFlag( FLAG_CSVSEP, false, 1 ) );
	cmd.AddFlag( ALib::CommandLineFlag( FLAG_CSVSEPR, false, 1 ) );
	cmd.AddFlag( ALib::CommandLineFlag( FLAG_OUTSEP, false, 1 ) );
	cmd.AddFlag( ALib::CommandLineFlag( FLAG_QLIST, false, 1 ) );
	cmd.AddFlag( ALib::CommandLineFlag( FLAG_RFSEED, false, 1 ) );

	vector <string> tmp;
	ALib::Split( mHelp, GFL_DELIM, tmp );
	if ( tmp.size() > 1 && tmp[1].find( "SKIP" ) != string::npos ) {
		cmd.AddFlag( ALib::CommandLineFlag( FLAG_SKIP, false, 1 ) );
		cmd.AddFlag( ALib::CommandLineFlag( FLAG_PASS, false, 1 ) );
	}

	cmd.CheckFlags( 2 );		// don't check the command name

}

//----------------------------------------------------------------------------
// Provide count of flags excluding the generic ones
//----------------------------------------------------------------------------

int Command :: CountNonGeneric( const ALib::CommandLine & cmd ) const  {

	int n = 0;

	for ( int i = 0; i < cmd.Argc(); i++ ) {
		if ( ALib::StrFirst( cmd.Argv(i) ) == '-' ) {
			string arg = cmd.Argv( i );
			if ( arg != FLAG_OUT
				&& arg != FLAG_IGNBL
				&& arg != FLAG_SMARTQ
				&& arg != FLAG_CSVSEP
				&& arg != FLAG_OUTSEP
				&& arg != FLAG_PASS
				&& arg != FLAG_ICNAMES ) {
					n++;
			}
		}
	}

	return n;
}

//---------------------------------------------------------------------------
// Add flag to allowed flags for a command
//---------------------------------------------------------------------------

void Command :: AddFlag( const ALib::CommandLineFlag & f ) {
	mFlags.push_back( f );
}

//------------------------------------------------------------------------

}	// end namespace

// end
<|endoftext|>


<commit_before>#include "era.hpp"
#include <unistd.h>  // getcwd
#include <sys/stat.h>

namespace era01 {

File::File() {
  sFQName.assign(home("default_era.log"));
}

File::File(string fname) {
  if (this->pdir.find("\\") == -1) {
    if (this->pdir.find("/") == -1) {
      fname = home(fname);
    }
  }
  sFQName.assign(fname);
}

File::File(const File& file) {
  this->sFQName = string(file.sFQName);
  this->type = file.type;
}

File::~File() {
  close();
}

const char* File::name() {
  return this->sFQName.c_str();
}

FileType File::mode() {
  return this->type;
}

void File::assign(const File& file) {
  close();
  sFQName.assign(file.sFQName);
  pdir.assign(file.pdir);
  type = file.type;
}

string File::home(string node) {
  const char* cwd = getcwd(NULL, 0);
  stringstream srm;
  srm << cwd;
  if (this->pdir.find("\\") != -1) {
    srm << "\\";
  } else {
    srm << "/";
  }
  srm << node << ends;
  delete cwd;  // stop a leak
  return srm.str();
}

istream& File::openRead(FileType at) {
  close();
  pIOStream = new fstream;

  if (at == AT_BINARY)
    pIOStream->open(sFQName.c_str(), ios::in | ios::binary);
  else
    pIOStream->open(sFQName.c_str(), ios::in);
  return *pIOStream;
}

ostream& File::openWrite(FileType at) {
  close();
  remove();  // For the benefit of Microsoft's legacy.
  pIOStream = new fstream;

  if (at == AT_BINARY)
    pIOStream->open(sFQName.c_str(), ios::out | ios::binary);
  else
    pIOStream->open(sFQName.c_str(), ios::out);
  return *pIOStream;
}

ostream& File::openAppend(FileType at) {
  close();
  delete pIOStream;
  pIOStream = NULL;
  pIOStream = new fstream;

  if (at == AT_BINARY)
    pIOStream->open(sFQName.c_str(), ios::out | ios::app | ios::binary);
  else
    pIOStream->open(sFQName.c_str(), ios::out | ios::app);
  return *pIOStream;
}

iostream& File::openReadWrite(FileType at) {
  close();
  delete pIOStream;
  pIOStream = NULL;
  pIOStream = new fstream;

  if (at == AT_BINARY)
    pIOStream->open(sFQName.c_str(), ios::in | ios::out | ios::binary);
  else
    pIOStream->open(sFQName.c_str(), ios::in | ios::out);
  return *pIOStream;
}

void File::close(void) {
  if (pIOStream != NULL)
    pIOStream->close();
  pIOStream = NULL;
}

bool File::remove(void) {
  close();
  if (sFQName.size() == 0)
    return true;
  if (::remove(sFQName.c_str()))
    return false;
  return true;
}

bool File::exists() {
  struct stat info;
  if (::stat(sFQName.c_str(), &info) != 0)
    return false;
  return true;
}

}  // namespace
<commit_msg>Modernized<commit_after>#include "era.hpp"
#include <unistd.h>  // getcwd
#include <sys/stat.h>

namespace era01 {

File::File() {
  sFQName.assign(home("default_era.log"));
}

File::File(string fname) {
  if (this->pdir.find("\\") == -1) {
    if (this->pdir.find("/") == -1) {
      fname = home(fname);
    }
  }
  sFQName.assign(fname);
}

File::File(const File& file) {
  this->sFQName = string(file.sFQName);
  this->type = file.type;
}

File::~File() {
  close();
}

const char* File::name() {
  return this->sFQName.c_str();
}

FileType File::mode() {
  return this->type;
}

void File::assign(const File& file) {
  close();
  sFQName.assign(file.sFQName);
  pdir.assign(file.pdir);
  type = file.type;
}

string File::home(string node) {
  const char* cwd = getcwd(nullptr, 0);
  stringstream srm;
  srm << cwd;
  if (this->pdir.find("\\") != -1) {
    srm << "\\";
  } else {
    srm << "/";
  }
  srm << node << ends;
  delete cwd;  // stop a leak
  return srm.str();
}

istream& File::openRead(FileType at) {
  close();
  pIOStream = new fstream;

  if (at == AT_BINARY)
    pIOStream->open(sFQName.c_str(), ios::in | ios::binary);
  else
    pIOStream->open(sFQName.c_str(), ios::in);
  return *pIOStream;
}

ostream& File::openWrite(FileType at) {
  close();
  remove();  // For the benefit of Microsoft's legacy.
  pIOStream = new fstream;

  if (at == AT_BINARY)
    pIOStream->open(sFQName.c_str(), ios::out | ios::binary);
  else
    pIOStream->open(sFQName.c_str(), ios::out);
  return *pIOStream;
}

ostream& File::openAppend(FileType at) {
  close();
  delete pIOStream;
  pIOStream = nullptr;
  pIOStream = new fstream;

  if (at == AT_BINARY)
    pIOStream->open(sFQName.c_str(), ios::out | ios::app | ios::binary);
  else
    pIOStream->open(sFQName.c_str(), ios::out | ios::app);
  return *pIOStream;
}

iostream& File::openReadWrite(FileType at) {
  close();
  delete pIOStream;
  pIOStream = nullptr;
  pIOStream = new fstream;

  if (at == AT_BINARY)
    pIOStream->open(sFQName.c_str(), ios::in | ios::out | ios::binary);
  else
    pIOStream->open(sFQName.c_str(), ios::in | ios::out);
  return *pIOStream;
}

void File::close(void) {
  if (pIOStream != nullptr)
    pIOStream->close();
  pIOStream = nullptr;
}

bool File::remove(void) {
  close();
  if (sFQName.size() == 0)
    return true;
  if (::remove(sFQName.c_str()))
    return false;
  return true;
}

bool File::exists() {
  struct stat info;
  if (::stat(sFQName.c_str(), &info) != 0)
    return false;
  return true;
}

}  // namespace
<|endoftext|>


<commit_before>/*
 *
 * Copyright (C) 2009, Joacim Jacobsson ( j dot jacobsson at gmail dot com )
 * All rights reserved.
 *
 * See LICENSE file for details
 *
 */

#include "BehaviorTreeNode.h"
#include "../NodeToNodeArrow.h"
#include <btree/btree.h>

#include <QtGui/QtGui>
#include <QtSvg/QSvgRenderer>

const char* const g_NodeResourcePaths[_E_MAX_GRIST_TYPES_] = {
	":/nodes/unknown.svg",
	":/nodes/sequence.svg",
	":/nodes/selector.svg",
	":/nodes/parallel.svg",
	":/nodes/dyn_selector.svg",
	":/nodes/decorator.svg",
	":/nodes/action.svg"
};

BehaviorTreeNode::BehaviorTreeNode( Node* n, BehaviorTreeNode* parent )
	: QGraphicsSvgItem( g_NodeResourcePaths[n->m_Grist.m_Type] )
	, m_Node( n )
	, m_MouseState( E_MS_NONE )
	, m_DraggingArrow( 0x0 )
{
	setFlag( QGraphicsItem::ItemIsMovable, true );
	setFlag( QGraphicsItem::ItemIsSelectable, true );
	setFlag( QGraphicsItem::ItemStacksBehindParent, false );

	if( parent )
		setParentItem( parent );
	setZValue( 0.0 );
}

void BehaviorTreeNode::removeArrow(NodeToNodeArrow *arrow)
{
	int index = m_Arrows.indexOf(arrow);

	if (index != -1)
		m_Arrows.removeAt(index);
}

void BehaviorTreeNode::removeArrows()
{
	foreach( NodeToNodeArrow *arrow, m_Arrows )
	{
		arrow->startItem()->removeArrow(arrow);
		arrow->endItem()->removeArrow(arrow);
		scene()->removeItem(arrow);
		delete arrow;
	}
}

void BehaviorTreeNode::addArrow(NodeToNodeArrow *arrow)
{
	m_Arrows.append(arrow);
}

NodeToNodeArrow* BehaviorTreeNode::findArrowTo( BehaviorTreeNode* other )
{
	foreach( NodeToNodeArrow *arrow, m_Arrows )
	{
		if( arrow->startItem() == this && arrow->endItem() == other )
			return arrow;
		if( arrow->startItem() == other && arrow->endItem() == this )
			return arrow;
	}
	return 0x0;
}

QVariant BehaviorTreeNode::itemChange( GraphicsItemChange change, const QVariant &value )
{
	switch( change )
	{
	case ItemSelectedChange:
		update();
		break;
	case ItemPositionChange:
		break;
	}
	return QGraphicsSvgItem::itemChange( change, value );
}

void BehaviorTreeNode::mousePressEvent( QGraphicsSceneMouseEvent* event )
{
	if( event->button() == Qt::LeftButton )
	{
		m_MouseState = E_MS_LB_DOWN;
		m_StartPos = event->screenPos();
	}
	QGraphicsSvgItem::mousePressEvent( event );
}

void BehaviorTreeNode::mouseReleaseEvent( QGraphicsSceneMouseEvent* event )
{
	if( event->button() == Qt::LeftButton )
	{
		if( m_MouseState == E_MS_DRAGGING )
			draggingEnded();

		m_MouseState = E_MS_NONE;
		setZValue( 0.0 );
	}
	QGraphicsSvgItem::mouseReleaseEvent( event );
}

void BehaviorTreeNode::mouseMoveEvent( QGraphicsSceneMouseEvent* event )
{
	if( m_MouseState == E_MS_LB_DOWN )
	{
		if( m_StartPos != event->screenPos() )
		{

			m_MouseState = E_MS_DRAGGING;
			setZValue( 1.0 );
		}
	}
	QGraphicsSvgItem::mouseMoveEvent( event );
}

void BehaviorTreeNode::draggingStarted()
{
	if( parentItem() )
	{
		BehaviorTreeNode* p = (BehaviorTreeNode*)parentItem();
		m_DraggingArrow = findArrowTo( p );
		p->removeArrow( m_DraggingArrow );
		m_DraggingArrow->setDashed( true );
		m_DraggingArrow->setStartAndEnd( this, p );

		QPointF position( scenePos() );
		setParentItem( 0x0 );
		setPos( position );
	}
	else
	{
		m_DraggingArrow = new NodeToNodeArrow( this, 0x0, scene() );
		m_DraggingArrow->setDashed( true );
		addArrow( m_DraggingArrow );
	}

	setupRelinkage();
}

void BehaviorTreeNode::draggingEnded()
{
	if( m_Relinkage.m_Parent )
	{
		BehaviorTreeNode* p = (BehaviorTreeNode*)m_Relinkage.m_Parent->m_UserData;
		setPos( p->mapFromScene( scenePos() ) );
		setParentItem( p );

		p->addArrow( m_DraggingArrow );
		m_DraggingArrow->setStartAndEnd( p, this );
		m_DraggingArrow->setDashed( false );
		m_DraggingArrow = 0x0;
	}
	else
	{
		removeArrow( m_DraggingArrow );
		delete m_DraggingArrow;
		m_DraggingArrow = 0x0;
	}

	executeRelinkage();

	emit nodeDragged();
}

void BehaviorTreeNode::setupRelinkage()
{
	m_Relinkage.m_Parent = m_Node->m_Pare;
	if( m_Node->m_Prev )
	{
		m_Relinkage.m_Sibling		= m_Node->m_Prev;
		m_Relinkage.m_BeforeSibling	= false;
	}
	else if( m_Node->m_Next )
	{
		m_Relinkage.m_Sibling		= m_Node->m_Next;
		m_Relinkage.m_BeforeSibling	= true;
	}
	else
	{
		m_Relinkage.m_Sibling		= 0x0;
		m_Relinkage.m_BeforeSibling = false;
	}
	UnlinkNodeFromParentAndSiblings( m_Node );
}

void BehaviorTreeNode::executeRelinkage()
{
	if( m_Relinkage.m_Parent )
		m_Node->m_Pare = m_Relinkage.m_Parent;

	if( m_Relinkage.m_Sibling )
	{
		if( m_Relinkage.m_BeforeSibling )
		{
			if( m_Relinkage.m_Sibling->m_Prev )
				m_Relinkage.m_Sibling->m_Prev->m_Next = m_Node;
			else
				SetFirstChild( m_Node->m_Pare, m_Node );

			m_Node->m_Prev = m_Relinkage.m_Sibling->m_Prev;

			m_Relinkage.m_Sibling->m_Prev = m_Node;
			m_Node->m_Next = m_Relinkage.m_Sibling;
		}
		else
		{
			if( m_Relinkage.m_Sibling->m_Next )
				m_Relinkage.m_Sibling->m_Next->m_Prev = m_Node;
			m_Node->m_Next = m_Relinkage.m_Sibling->m_Next;
			m_Relinkage.m_Sibling->m_Next = m_Node;
			m_Node->m_Prev = m_Relinkage.m_Sibling;
		}
	}
	else
	{
		m_Node->m_Next = 0x0;
		m_Node->m_Prev = 0x0;
	}
}
<commit_msg>Fixed crash. And a bugg. yay.<commit_after>/*
 *
 * Copyright (C) 2009, Joacim Jacobsson ( j dot jacobsson at gmail dot com )
 * All rights reserved.
 *
 * See LICENSE file for details
 *
 */

#include "BehaviorTreeNode.h"
#include "../NodeToNodeArrow.h"
#include <btree/btree.h>

#include <QtGui/QtGui>
#include <QtSvg/QSvgRenderer>

const char* const g_NodeResourcePaths[_E_MAX_GRIST_TYPES_] = {
	":/nodes/unknown.svg",
	":/nodes/sequence.svg",
	":/nodes/selector.svg",
	":/nodes/parallel.svg",
	":/nodes/dyn_selector.svg",
	":/nodes/decorator.svg",
	":/nodes/action.svg"
};

BehaviorTreeNode::BehaviorTreeNode( Node* n, BehaviorTreeNode* parent )
	: QGraphicsSvgItem( g_NodeResourcePaths[n->m_Grist.m_Type] )
	, m_Node( n )
	, m_MouseState( E_MS_NONE )
	, m_DraggingArrow( 0x0 )
{
	setFlag( QGraphicsItem::ItemIsMovable, true );
	setFlag( QGraphicsItem::ItemIsSelectable, true );
	setFlag( QGraphicsItem::ItemStacksBehindParent, false );

	if( parent )
		setParentItem( parent );
	setZValue( 0.0 );
}

void BehaviorTreeNode::removeArrow(NodeToNodeArrow *arrow)
{
	int index = m_Arrows.indexOf(arrow);

	if (index != -1)
		m_Arrows.removeAt(index);
}

void BehaviorTreeNode::removeArrows()
{
	foreach( NodeToNodeArrow *arrow, m_Arrows )
	{
		arrow->startItem()->removeArrow(arrow);
		arrow->endItem()->removeArrow(arrow);
		scene()->removeItem(arrow);
		delete arrow;
	}
}

void BehaviorTreeNode::addArrow(NodeToNodeArrow *arrow)
{
	m_Arrows.append(arrow);
}

NodeToNodeArrow* BehaviorTreeNode::findArrowTo( BehaviorTreeNode* other )
{
	foreach( NodeToNodeArrow *arrow, m_Arrows )
	{
		if( arrow->startItem() == this && arrow->endItem() == other )
			return arrow;
		if( arrow->startItem() == other && arrow->endItem() == this )
			return arrow;
	}
	return 0x0;
}

QVariant BehaviorTreeNode::itemChange( GraphicsItemChange change, const QVariant &value )
{
	switch( change )
	{
	case ItemSelectedChange:
		update();
		break;
	case ItemPositionChange:
		break;
	}
	return QGraphicsSvgItem::itemChange( change, value );
}

void BehaviorTreeNode::mousePressEvent( QGraphicsSceneMouseEvent* event )
{
	if( event->button() == Qt::LeftButton )
	{
		m_MouseState = E_MS_LB_DOWN;
		m_StartPos = event->screenPos();
	}
	QGraphicsSvgItem::mousePressEvent( event );
}

void BehaviorTreeNode::mouseReleaseEvent( QGraphicsSceneMouseEvent* event )
{
	if( event->button() == Qt::LeftButton )
	{
		if( m_MouseState == E_MS_DRAGGING )
			draggingEnded();

		m_MouseState = E_MS_NONE;
	}
	QGraphicsSvgItem::mouseReleaseEvent( event );
}

void BehaviorTreeNode::mouseMoveEvent( QGraphicsSceneMouseEvent* event )
{
	if( m_MouseState == E_MS_LB_DOWN )
	{
		if( m_StartPos != event->screenPos() )
		{
			draggingStarted();
			m_MouseState = E_MS_DRAGGING;
		}
	}
	QGraphicsSvgItem::mouseMoveEvent( event );
}

void BehaviorTreeNode::draggingStarted()
{
	if( parentItem() )
	{
		BehaviorTreeNode* p = (BehaviorTreeNode*)parentItem();
		m_DraggingArrow = findArrowTo( p );
		p->removeArrow( m_DraggingArrow );
		m_DraggingArrow->setDashed( true );
		m_DraggingArrow->setStartAndEnd( this, p );

		QPointF position( scenePos() );
		setParentItem( 0x0 );
		setPos( position );
	}
	else
	{
		m_DraggingArrow = new NodeToNodeArrow( this, 0x0, scene() );
		m_DraggingArrow->setDashed( true );
		addArrow( m_DraggingArrow );
	}

	setupRelinkage();

	setZValue( 1.0 );
}

void BehaviorTreeNode::draggingEnded()
{
	if( m_Relinkage.m_Parent )
	{
		BehaviorTreeNode* p = (BehaviorTreeNode*)m_Relinkage.m_Parent->m_UserData;
		setPos( p->mapFromScene( scenePos() ) );
		setParentItem( p );

		p->addArrow( m_DraggingArrow );
		m_DraggingArrow->setStartAndEnd( p, this );
		m_DraggingArrow->setDashed( false );
		m_DraggingArrow = 0x0;
	}
	else
	{
		removeArrow( m_DraggingArrow );
		delete m_DraggingArrow;
		m_DraggingArrow = 0x0;
	}

	executeRelinkage();

	setZValue( 0.0 );

	emit nodeDragged();
}

void BehaviorTreeNode::setupRelinkage()
{
	m_Relinkage.m_Parent = m_Node->m_Pare;
	if( m_Node->m_Prev )
	{
		m_Relinkage.m_Sibling		= m_Node->m_Prev;
		m_Relinkage.m_BeforeSibling	= false;
	}
	else if( m_Node->m_Next )
	{
		m_Relinkage.m_Sibling		= m_Node->m_Next;
		m_Relinkage.m_BeforeSibling	= true;
	}
	else
	{
		m_Relinkage.m_Sibling		= 0x0;
		m_Relinkage.m_BeforeSibling = false;
	}
	UnlinkNodeFromParentAndSiblings( m_Node );
}

void BehaviorTreeNode::executeRelinkage()
{
	if( m_Relinkage.m_Parent )
		m_Node->m_Pare = m_Relinkage.m_Parent;

	if( m_Relinkage.m_Sibling )
	{
		if( m_Relinkage.m_BeforeSibling )
		{
			if( m_Relinkage.m_Sibling->m_Prev )
				m_Relinkage.m_Sibling->m_Prev->m_Next = m_Node;
			else
				SetFirstChild( m_Node->m_Pare, m_Node );

			m_Node->m_Prev = m_Relinkage.m_Sibling->m_Prev;

			m_Relinkage.m_Sibling->m_Prev = m_Node;
			m_Node->m_Next = m_Relinkage.m_Sibling;
		}
		else
		{
			if( m_Relinkage.m_Sibling->m_Next )
				m_Relinkage.m_Sibling->m_Next->m_Prev = m_Node;
			m_Node->m_Next = m_Relinkage.m_Sibling->m_Next;
			m_Relinkage.m_Sibling->m_Next = m_Node;
			m_Node->m_Prev = m_Relinkage.m_Sibling;
		}
	}
	else
	{
		m_Node->m_Next = 0x0;
		m_Node->m_Prev = 0x0;
		SetFirstChild( m_Node->m_Pare, m_Node );
	}
}
<|endoftext|>