<commit_before>//=============================================================================================================
/**
* @file     test_fiff_anonymize.cpp
* @author   Lorenz Esch <lorenzesch@hotmail.com>;
* @version  1.0
* @date     September, 2019
*
* @section  LICENSE
*
* Copyright (C) 2019, Lorenz Esch. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that
* the following conditions are met:
*     * Redistributions of source code must retain the above copyright notice, this list of conditions and the
*       following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
*       the following disclaimer in the documentation and/or other materials provided with the distribution.
*     * Neither the name of MNE-CPP authors nor the names of its contributors may be used
*       to endorse or promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*
* @brief    Test for anonymizing a fiff raw file
*
*/

//*************************************************************************************************************
//=============================================================================================================
// INCLUDES
//=============================================================================================================

#include <fiff/fiff.h>

#include <iostream>

//*************************************************************************************************************
//=============================================================================================================
// QT INCLUDES
//=============================================================================================================

#include <QtTest>
#include <QProcess>
#include <QScopedPointer>

//*************************************************************************************************************
//=============================================================================================================
// USED NAMESPACES
//=============================================================================================================

using namespace FIFFLIB;

//=============================================================================================================
/**
* DECLARE CLASS TestFiffAnonymize
*
* @brief The TestFiffAnonymize class provides fiff anonymizing verification tests
*
*/
class TestFiffAnonymize: public QObject
{
    Q_OBJECT

public:
    TestFiffAnonymize();

private slots:
    void initTestCase();
    void compareData();
    void cleanupTestCase();

private:
    double epsilon;
};

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

TestFiffAnonymize::TestFiffAnonymize()
: epsilon(0.000001)
{
}

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

void TestFiffAnonymize::initTestCase()
{
    qInfo() << "TestFiffAnonymize::initTestCase - Epsilon" << epsilon;

    // Init testing arguments
    QString sFileIn("/mne-cpp-test-data/MEG/sample/sample_audvis_raw_short.fif");
    QString sFileOut("/mne-cpp-test-data/MEG/sample/sample_audvis_raw_short_anonymized.fif");

    qInfo() << "TestFiffAnonymize::initTestCase - sFileIn" << sFileIn;
    qInfo() << "TestFiffAnonymize::initTestCase - sFileOut" << sFileOut;

    QString program = "./mne_anonymize";
    QStringList arguments;
    arguments << "--in" << sFileIn;
    arguments << "--out" << sFileOut;

    // Pass arguments to application and anaonyimze the fiff file
    QScopedPointer<QProcess> myProcess (new QProcess);
    myProcess->start(program, arguments);
    myProcess->waitForFinished();
}

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

void TestFiffAnonymize::compareData()
{
    // Open ./mne-cpp-test-data/MEG/sample/sample_audvis_raw_anonymized.fif

    // ToDo: Implement function which reads sensitive tags and checks if they were anaonymized. Use Q_VERIFY().
}

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

void TestFiffAnonymize::cleanupTestCase()
{
}

//*************************************************************************************************************
//=============================================================================================================
// MAIN
//=============================================================================================================

QTEST_APPLESS_MAIN(TestFiffAnonymize)
#include "test_fiff_anonymize.moc"
<commit_msg>[ci skip] Fix relative path spec in the test\<commit_after>//=============================================================================================================
/**
* @file     test_fiff_anonymize.cpp
* @author   Lorenz Esch <lorenzesch@hotmail.com>;
* @version  1.0
* @date     September, 2019
*
* @section  LICENSE
*
* Copyright (C) 2019, Lorenz Esch. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that
* the following conditions are met:
*     * Redistributions of source code must retain the above copyright notice, this list of conditions and the
*       following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
*       the following disclaimer in the documentation and/or other materials provided with the distribution.
*     * Neither the name of MNE-CPP authors nor the names of its contributors may be used
*       to endorse or promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*
* @brief    Test for anonymizing a fiff raw file
*
*/

//*************************************************************************************************************
//=============================================================================================================
// INCLUDES
//=============================================================================================================

#include <fiff/fiff.h>

#include <iostream>

//*************************************************************************************************************
//=============================================================================================================
// QT INCLUDES
//=============================================================================================================

#include <QtTest>
#include <QProcess>
#include <QScopedPointer>

//*************************************************************************************************************
//=============================================================================================================
// USED NAMESPACES
//=============================================================================================================

using namespace FIFFLIB;

//=============================================================================================================
/**
* DECLARE CLASS TestFiffAnonymize
*
* @brief The TestFiffAnonymize class provides fiff anonymizing verification tests
*
*/
class TestFiffAnonymize: public QObject
{
    Q_OBJECT

public:
    TestFiffAnonymize();

private slots:
    void initTestCase();
    void compareData();
    void cleanupTestCase();

private:
    double epsilon;
};

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

TestFiffAnonymize::TestFiffAnonymize()
: epsilon(0.000001)
{
}

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

void TestFiffAnonymize::initTestCase()
{
    qInfo() << "TestFiffAnonymize::initTestCase - Epsilon" << epsilon;

    // Init testing arguments
    QString sFileIn("./mne-cpp-test-data/MEG/sample/sample_audvis_raw_short.fif");
    QString sFileOut("./mne-cpp-test-data/MEG/sample/sample_audvis_raw_short_anonymized.fif");

    qInfo() << "TestFiffAnonymize::initTestCase - sFileIn" << sFileIn;
    qInfo() << "TestFiffAnonymize::initTestCase - sFileOut" << sFileOut;

    QString program = "./mne_anonymize";
    QStringList arguments;
    arguments << "--in" << sFileIn;
    arguments << "--out" << sFileOut;

    // Pass arguments to application and anaonyimze the fiff file
    QScopedPointer<QProcess> myProcess (new QProcess);
    myProcess->start(program, arguments);
    myProcess->waitForFinished();
}

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

void TestFiffAnonymize::compareData()
{
    // Open ./mne-cpp-test-data/MEG/sample/sample_audvis_raw_anonymized.fif

    // ToDo: Implement function which reads sensitive tags and checks if they were anaonymized. Use Q_VERIFY().
}

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

void TestFiffAnonymize::cleanupTestCase()
{
}

//*************************************************************************************************************
//=============================================================================================================
// MAIN
//=============================================================================================================

QTEST_APPLESS_MAIN(TestFiffAnonymize)
#include "test_fiff_anonymize.moc"
<|endoftext|>


<commit_before>/*
 * This file is part of qtagger.
 *
 * © 2010 Fernando Tarlá Cardoso Lemos
 *
 * Refer to the LICENSE file for licensing information.
 *
 */

#include <boost/algorithm/string.hpp>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/optional.hpp>
#include <taglib/fileref.h>
#include <taglib/tag.h>
#include <cassert>
#include <cstdlib>
#include <exception>

#include "InteractiveTagger.h"
#include "validators.h"
#include "wide_string_cast.h"

namespace fs = boost::filesystem;

InteractiveTagger::InteractiveTagger()
    : m_input_filter(NULL), m_output_filter(NULL)
{
}

void InteractiveTagger::tag(int num_paths, const char **paths)
{
    assert(m_terminal);

    // Build a list with the normalized paths
    std::list<fs::path> path_list;
    for (int i = 0; i < num_paths; ++i) {
        char *real_path = realpath(paths[i], NULL);
        if (!real_path) {
            std::string errstr("\"");
            errstr += paths[i];
            errstr += "\" is not a valid path";
            m_terminal->display_warning_message(errstr);
            continue;
        }
        path_list.push_back(fs::path(real_path));
        free(real_path);
    }

    // Sort it and tag the paths
    path_list.sort();
    BOOST_FOREACH(const fs::path &path, path_list) {
        // Check if the path exists
        if (!fs::exists(path)) {
            m_terminal->display_warning_message("Path \"" + path.string() + "\" not found, skipping...");
            continue;
        }

        try {
            // If it's a regular file, just tag it
            if (fs::is_regular_file(path)) {
                if (!is_supported_extension(path)) {
                    m_terminal->display_warning_message("Path \"" + path.string() + "\" has no supported extension, skipping...");
                    continue;
                }
                tag_file(path);
            }

            // If it's a directory, tag its contents
            else if (fs::is_directory(path)) {
                tag_directory(path);
            }

            // It's none of the above, do something about it
            else {
                m_terminal->display_warning_message("Path \"" + path.string() + "\" is not a regular file or directory, skipping...");
            }
        }
        catch (std::exception &e) {
            m_terminal->display_warning_message(e.what());
        }
    }

    // Save the unsaved files
    if (!m_unsaved_files.empty() && m_terminal->ask_yes_no_question(L"=== OK to save the changes to the files?")) {
        BOOST_FOREACH(TagLib::FileRef &f, m_unsaved_files) {
            m_terminal->display_info_message("Saving \"" + std::string(f.file()->name()) + "\"");
            if (!f.save())
                m_terminal->display_warning_message("Unable to save " + std::string(f.file()->name()) + "\"");
        }
    }

    // Perform the pending renames
    if (!m_pending_renames.empty() && m_terminal->ask_yes_no_question(L"=== OK to rename the files?")) {
        std::list<std::pair<fs::path, fs::path> >::const_iterator it;
        for (it = m_pending_renames.begin(); it != m_pending_renames.end(); ++it) {
            const fs::path &from((*it).first);
            const fs::path &to((*it).second);
            m_terminal->display_info_message(from.string());
            m_terminal->display_info_message("-> " + to.string());
            try { fs::rename(from, to); }
            catch (std::exception &e) { m_terminal->display_warning_message(e.what()); }
        }
    }

    m_terminal->display_info_message("=== All done!");
}

bool InteractiveTagger::is_supported_extension(const fs::path &path)
{
    static const char *supported_extensions[] = { ".mp3", ".ogg", ".flac", ".mpc", NULL };

    std::string extension(path.extension());
    boost::to_lower(extension);

    for (int i = 0; supported_extensions[i]; ++i) {
        if (extension == supported_extensions[i])
            return true;
    }

    return false;
}

std::string InteractiveTagger::replace_tokens(const std::string &str,
        const std::map<std::string, std::string> &replacements)
{
    std::string res;
    res.reserve(str.size() * 3);

    bool parsing_token = false;
    std::string current_token;
    BOOST_FOREACH(char c, str) {
        if (c == '%') {
            if (parsing_token) {
                std::map<std::string, std::string>::const_iterator it = replacements.find(current_token);
                res += it == replacements.end() ? current_token : it->second;
                current_token.erase();
            }
            parsing_token = true;
        }
        else if (parsing_token) {
            if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
                current_token += c;
            }
            else {
                std::map<std::string, std::string>::const_iterator it = replacements.find(current_token);
                res += it == replacements.end() ? current_token : it->second;
                res += c;
                parsing_token = false;
                current_token.erase();
            }
        }
        else {
            res += c;
        }
    }
    if (parsing_token) {
        std::map<std::string, std::string>::const_iterator it = replacements.find(current_token);
        res += it == replacements.end() ? current_token : it->second;
    }

    return res;
}

void InteractiveTagger::tag_file(const boost::filesystem::path &path,
        std::wstring *artist, std::wstring *album, int *year, int track)
{
    m_terminal->display_info_message("=== Tagging \"" + path.filename() + "\"");

    // Get a reference to the file
    TagLib::FileRef f(path.string().c_str());

    // Ask for the artist
    boost::optional<std::wstring> default_artist;
    if (artist && !artist->empty())
        default_artist = *artist;
    else if (!f.tag()->artist().isNull())
        default_artist = m_input_filter->filter(f.tag()->artist().toWString());
    std::wstring new_artist = m_terminal->ask_string_question(L"Artist:", default_artist);
    if (m_output_filter) {
        std::wstring filtered_artist = m_output_filter->filter(new_artist);
        if (m_input_filter->requires_confirmation_as_output_filter() && filtered_artist != new_artist)
            filtered_artist = m_terminal->ask_string_question(L"Artist (confirmation):", filtered_artist);
        new_artist = filtered_artist;
    }
    f.tag()->setArtist(new_artist);
    if (artist) *artist = new_artist;

    // Ask for the album
    boost::optional<std::wstring> default_album;
    if (album && !album->empty())
        default_album = *album;
    else if (!f.tag()->album().isNull())
        default_album = m_input_filter->filter(f.tag()->album().toWString());
    std::wstring new_album = m_terminal->ask_string_question(L"Album:", default_album);
    if (m_output_filter) {
        std::wstring filtered_album = m_output_filter->filter(new_album);
        if (m_input_filter->requires_confirmation_as_output_filter() && filtered_album != new_album)
            filtered_album = m_terminal->ask_string_question(L"Album (confirmation):", filtered_album);
        new_album = filtered_album;
    }
    f.tag()->setAlbum(new_album);
    if (album) *album = new_album;

    // Ask for the year
    boost::optional<int> default_year;
    if (year && *year != -1)
        default_year = *year;
    else if (f.tag()->year())
        default_year = f.tag()->year();
    YearValidator year_validator;
    int new_year = m_terminal->ask_number_question(L"Year:", default_year, &year_validator);
    f.tag()->setYear(new_year);
    if (year) *year = new_year;

    // Ask for the track
    if (track == -1)
        track = f.tag()->track();
    TrackValidator track_validator;
    int new_track = m_terminal->ask_number_question(L"Track:",
            track > 0 ? track : boost::optional<int>(), &track_validator);
    f.tag()->setTrack(new_track);

    // Ask for the song title
    boost::optional<std::wstring> default_title;
    if (!f.tag()->title().isNull())
        default_title = f.tag()->title().toWString();
    std::wstring new_title = m_terminal->ask_string_question(L"Title:", default_title);
    if (m_output_filter) {
        std::wstring filtered_title = m_output_filter->filter(new_title);
        if (m_input_filter->requires_confirmation_as_output_filter() && filtered_title != new_title)
            filtered_title = m_terminal->ask_string_question(L"Title (confirmation):", filtered_title);
        new_title = filtered_title;
    }
    f.tag()->setTitle(new_title);

    // Reset the comment and genre fields
    f.tag()->setComment(TagLib::String::null);
    f.tag()->setGenre(TagLib::String::null);

    // Add it to the list of unsaved files
    m_unsaved_files.push_back(f);

    // Add it to the list of pending renames based on the supplied format
    if (m_file_rename_format) {
        std::map<std::string, std::string> tokens;
        tokens["artist"] = boost::lexical_cast<std::string>(new_artist);
        tokens["album"] = boost::lexical_cast<std::string>(new_album);
        tokens["year"] = boost::lexical_cast<std::string>(new_year);
        std::string track_str(boost::lexical_cast<std::string>(new_track));
        if (track_str.size() == 1) track_str = "0" + track_str;
        tokens["track"] = track_str;
        tokens["title"] = boost::lexical_cast<std::string>(new_title);
        fs::path new_path = path.parent_path();
        new_path /= replace_tokens(*m_file_rename_format, tokens) + boost::to_lower_copy(path.extension());
        if (new_path != path)
            m_pending_renames.push_back(std::pair<fs::path, fs::path>(path, new_path));
    }
}

void InteractiveTagger::tag_directory(const fs::path &path)
{
    m_terminal->display_info_message("=== Entering \"" + path.string() + "\"");

    // Create lists of files and subdirectories
    std::list<fs::path> file_list, dir_list;
    fs::directory_iterator end_it;
    for (fs::directory_iterator it(path); it != end_it; ++it) {
        // Normalize the path (needed here so that we follow symlinks)
        char *real_path = realpath(it->string().c_str(), NULL);
        if (!real_path) {
            m_terminal->display_warning_message("\"" + it->filename() + "\" is not a valid path, skipping...");
            continue;
        }
        fs::path boost_path(real_path);
        free(real_path);

        // Add to the right list
        if (fs::is_regular_file(boost_path)) {
            if (is_supported_extension(boost_path))
                file_list.push_back(boost_path);
            else
                m_terminal->display_warning_message("\"" + it->filename() + "\" has no supported extension, skipping...");
        }
        else if (fs::is_directory(boost_path)) {
            dir_list.push_back(boost_path);
        }
        else {
            m_terminal->display_warning_message("\"" + boost_path.filename() + "\" is not a regular file or directory, skipping...");
        }
    }

    // Sort those lists
    file_list.sort();
    dir_list.sort();

    // Tag all individual files
    std::wstring artist, album;
    int year = -1;
    if (!file_list.empty()) {
        int track = 1;
        BOOST_FOREACH(const fs::path &p, file_list)
            tag_file(p, &artist, &album, &year, track++);
    }

    // We'll ask confirmation to descend into the subdirectories only if there are files
    BOOST_FOREACH(const fs::path &p, dir_list) {
        if (file_list.empty() || m_terminal->ask_yes_no_question(L"Descend into subdirectory \""
                    + boost::lexical_cast<std::wstring>(p.filename()) + L"\"?", false))
            tag_directory(p);
    }

    // Add it to the list of pending renames based on the supplied format
    if (!artist.empty() && m_dir_rename_format) {
        std::map<std::string, std::string> tokens;
        tokens["artist"] = boost::lexical_cast<std::string>(artist);
        tokens["album"] = boost::lexical_cast<std::string>(album);
        tokens["year"] = boost::lexical_cast<std::string>(year);
        fs::path new_path = path.parent_path();
        new_path /= replace_tokens(*m_dir_rename_format, tokens);
        if (new_path != path)
            m_pending_renames.push_back(std::pair<fs::path, fs::path>(path, new_path));
    }
}
<commit_msg>Use the input filter for the song title too.<commit_after>/*
 * This file is part of qtagger.
 *
 * © 2010 Fernando Tarlá Cardoso Lemos
 *
 * Refer to the LICENSE file for licensing information.
 *
 */

#include <boost/algorithm/string.hpp>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/optional.hpp>
#include <taglib/fileref.h>
#include <taglib/tag.h>
#include <cassert>
#include <cstdlib>
#include <exception>

#include "InteractiveTagger.h"
#include "validators.h"
#include "wide_string_cast.h"

namespace fs = boost::filesystem;

InteractiveTagger::InteractiveTagger()
    : m_input_filter(NULL), m_output_filter(NULL)
{
}

void InteractiveTagger::tag(int num_paths, const char **paths)
{
    assert(m_terminal);

    // Build a list with the normalized paths
    std::list<fs::path> path_list;
    for (int i = 0; i < num_paths; ++i) {
        char *real_path = realpath(paths[i], NULL);
        if (!real_path) {
            std::string errstr("\"");
            errstr += paths[i];
            errstr += "\" is not a valid path";
            m_terminal->display_warning_message(errstr);
            continue;
        }
        path_list.push_back(fs::path(real_path));
        free(real_path);
    }

    // Sort it and tag the paths
    path_list.sort();
    BOOST_FOREACH(const fs::path &path, path_list) {
        // Check if the path exists
        if (!fs::exists(path)) {
            m_terminal->display_warning_message("Path \"" + path.string() + "\" not found, skipping...");
            continue;
        }

        try {
            // If it's a regular file, just tag it
            if (fs::is_regular_file(path)) {
                if (!is_supported_extension(path)) {
                    m_terminal->display_warning_message("Path \"" + path.string() + "\" has no supported extension, skipping...");
                    continue;
                }
                tag_file(path);
            }

            // If it's a directory, tag its contents
            else if (fs::is_directory(path)) {
                tag_directory(path);
            }

            // It's none of the above, do something about it
            else {
                m_terminal->display_warning_message("Path \"" + path.string() + "\" is not a regular file or directory, skipping...");
            }
        }
        catch (std::exception &e) {
            m_terminal->display_warning_message(e.what());
        }
    }

    // Save the unsaved files
    if (!m_unsaved_files.empty() && m_terminal->ask_yes_no_question(L"=== OK to save the changes to the files?")) {
        BOOST_FOREACH(TagLib::FileRef &f, m_unsaved_files) {
            m_terminal->display_info_message("Saving \"" + std::string(f.file()->name()) + "\"");
            if (!f.save())
                m_terminal->display_warning_message("Unable to save " + std::string(f.file()->name()) + "\"");
        }
    }

    // Perform the pending renames
    if (!m_pending_renames.empty() && m_terminal->ask_yes_no_question(L"=== OK to rename the files?")) {
        std::list<std::pair<fs::path, fs::path> >::const_iterator it;
        for (it = m_pending_renames.begin(); it != m_pending_renames.end(); ++it) {
            const fs::path &from((*it).first);
            const fs::path &to((*it).second);
            m_terminal->display_info_message(from.string());
            m_terminal->display_info_message("-> " + to.string());
            try { fs::rename(from, to); }
            catch (std::exception &e) { m_terminal->display_warning_message(e.what()); }
        }
    }

    m_terminal->display_info_message("=== All done!");
}

bool InteractiveTagger::is_supported_extension(const fs::path &path)
{
    static const char *supported_extensions[] = { ".mp3", ".ogg", ".flac", ".mpc", NULL };

    std::string extension(path.extension());
    boost::to_lower(extension);

    for (int i = 0; supported_extensions[i]; ++i) {
        if (extension == supported_extensions[i])
            return true;
    }

    return false;
}

std::string InteractiveTagger::replace_tokens(const std::string &str,
        const std::map<std::string, std::string> &replacements)
{
    std::string res;
    res.reserve(str.size() * 3);

    bool parsing_token = false;
    std::string current_token;
    BOOST_FOREACH(char c, str) {
        if (c == '%') {
            if (parsing_token) {
                std::map<std::string, std::string>::const_iterator it = replacements.find(current_token);
                res += it == replacements.end() ? current_token : it->second;
                current_token.erase();
            }
            parsing_token = true;
        }
        else if (parsing_token) {
            if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
                current_token += c;
            }
            else {
                std::map<std::string, std::string>::const_iterator it = replacements.find(current_token);
                res += it == replacements.end() ? current_token : it->second;
                res += c;
                parsing_token = false;
                current_token.erase();
            }
        }
        else {
            res += c;
        }
    }
    if (parsing_token) {
        std::map<std::string, std::string>::const_iterator it = replacements.find(current_token);
        res += it == replacements.end() ? current_token : it->second;
    }

    return res;
}

void InteractiveTagger::tag_file(const boost::filesystem::path &path,
        std::wstring *artist, std::wstring *album, int *year, int track)
{
    m_terminal->display_info_message("=== Tagging \"" + path.filename() + "\"");

    // Get a reference to the file
    TagLib::FileRef f(path.string().c_str());

    // Ask for the artist
    boost::optional<std::wstring> default_artist;
    if (artist && !artist->empty())
        default_artist = *artist;
    else if (!f.tag()->artist().isNull())
        default_artist = m_input_filter->filter(f.tag()->artist().toWString());
    std::wstring new_artist = m_terminal->ask_string_question(L"Artist:", default_artist);
    if (m_output_filter) {
        std::wstring filtered_artist = m_output_filter->filter(new_artist);
        if (m_input_filter->requires_confirmation_as_output_filter() && filtered_artist != new_artist)
            filtered_artist = m_terminal->ask_string_question(L"Artist (confirmation):", filtered_artist);
        new_artist = filtered_artist;
    }
    f.tag()->setArtist(new_artist);
    if (artist) *artist = new_artist;

    // Ask for the album
    boost::optional<std::wstring> default_album;
    if (album && !album->empty())
        default_album = *album;
    else if (!f.tag()->album().isNull())
        default_album = m_input_filter->filter(f.tag()->album().toWString());
    std::wstring new_album = m_terminal->ask_string_question(L"Album:", default_album);
    if (m_output_filter) {
        std::wstring filtered_album = m_output_filter->filter(new_album);
        if (m_input_filter->requires_confirmation_as_output_filter() && filtered_album != new_album)
            filtered_album = m_terminal->ask_string_question(L"Album (confirmation):", filtered_album);
        new_album = filtered_album;
    }
    f.tag()->setAlbum(new_album);
    if (album) *album = new_album;

    // Ask for the year
    boost::optional<int> default_year;
    if (year && *year != -1)
        default_year = *year;
    else if (f.tag()->year())
        default_year = f.tag()->year();
    YearValidator year_validator;
    int new_year = m_terminal->ask_number_question(L"Year:", default_year, &year_validator);
    f.tag()->setYear(new_year);
    if (year) *year = new_year;

    // Ask for the track
    if (track == -1)
        track = f.tag()->track();
    TrackValidator track_validator;
    int new_track = m_terminal->ask_number_question(L"Track:",
            track > 0 ? track : boost::optional<int>(), &track_validator);
    f.tag()->setTrack(new_track);

    // Ask for the song title
    boost::optional<std::wstring> default_title;
    if (!f.tag()->title().isNull())
        default_title = m_input_filter->filter(f.tag()->title().toWString());
    std::wstring new_title = m_terminal->ask_string_question(L"Title:", default_title);
    if (m_output_filter) {
        std::wstring filtered_title = m_output_filter->filter(new_title);
        if (m_input_filter->requires_confirmation_as_output_filter() && filtered_title != new_title)
            filtered_title = m_terminal->ask_string_question(L"Title (confirmation):", filtered_title);
        new_title = filtered_title;
    }
    f.tag()->setTitle(new_title);

    // Reset the comment and genre fields
    f.tag()->setComment(TagLib::String::null);
    f.tag()->setGenre(TagLib::String::null);

    // Add it to the list of unsaved files
    m_unsaved_files.push_back(f);

    // Add it to the list of pending renames based on the supplied format
    if (m_file_rename_format) {
        std::map<std::string, std::string> tokens;
        tokens["artist"] = boost::lexical_cast<std::string>(new_artist);
        tokens["album"] = boost::lexical_cast<std::string>(new_album);
        tokens["year"] = boost::lexical_cast<std::string>(new_year);
        std::string track_str(boost::lexical_cast<std::string>(new_track));
        if (track_str.size() == 1) track_str = "0" + track_str;
        tokens["track"] = track_str;
        tokens["title"] = boost::lexical_cast<std::string>(new_title);
        fs::path new_path = path.parent_path();
        new_path /= replace_tokens(*m_file_rename_format, tokens) + boost::to_lower_copy(path.extension());
        if (new_path != path)
            m_pending_renames.push_back(std::pair<fs::path, fs::path>(path, new_path));
    }
}

void InteractiveTagger::tag_directory(const fs::path &path)
{
    m_terminal->display_info_message("=== Entering \"" + path.string() + "\"");

    // Create lists of files and subdirectories
    std::list<fs::path> file_list, dir_list;
    fs::directory_iterator end_it;
    for (fs::directory_iterator it(path); it != end_it; ++it) {
        // Normalize the path (needed here so that we follow symlinks)
        char *real_path = realpath(it->string().c_str(), NULL);
        if (!real_path) {
            m_terminal->display_warning_message("\"" + it->filename() + "\" is not a valid path, skipping...");
            continue;
        }
        fs::path boost_path(real_path);
        free(real_path);

        // Add to the right list
        if (fs::is_regular_file(boost_path)) {
            if (is_supported_extension(boost_path))
                file_list.push_back(boost_path);
            else
                m_terminal->display_warning_message("\"" + it->filename() + "\" has no supported extension, skipping...");
        }
        else if (fs::is_directory(boost_path)) {
            dir_list.push_back(boost_path);
        }
        else {
            m_terminal->display_warning_message("\"" + boost_path.filename() + "\" is not a regular file or directory, skipping...");
        }
    }

    // Sort those lists
    file_list.sort();
    dir_list.sort();

    // Tag all individual files
    std::wstring artist, album;
    int year = -1;
    if (!file_list.empty()) {
        int track = 1;
        BOOST_FOREACH(const fs::path &p, file_list)
            tag_file(p, &artist, &album, &year, track++);
    }

    // We'll ask confirmation to descend into the subdirectories only if there are files
    BOOST_FOREACH(const fs::path &p, dir_list) {
        if (file_list.empty() || m_terminal->ask_yes_no_question(L"Descend into subdirectory \""
                    + boost::lexical_cast<std::wstring>(p.filename()) + L"\"?", false))
            tag_directory(p);
    }

    // Add it to the list of pending renames based on the supplied format
    if (!artist.empty() && m_dir_rename_format) {
        std::map<std::string, std::string> tokens;
        tokens["artist"] = boost::lexical_cast<std::string>(artist);
        tokens["album"] = boost::lexical_cast<std::string>(album);
        tokens["year"] = boost::lexical_cast<std::string>(year);
        fs::path new_path = path.parent_path();
        new_path /= replace_tokens(*m_dir_rename_format, tokens);
        if (new_path != path)
            m_pending_renames.push_back(std::pair<fs::path, fs::path>(path, new_path));
    }
}
<|endoftext|>


<commit_before>#include <coffee/core/CApplication>
#include <coffee/core/CFiles>
#include <coffee/sdl2/CSDL2Dialog>
#include <coffee/sdl2/CSDL2System>
#include <coffee/sdl2/CSDL2SpriteWindow>
#include <coffee/sdl2/CSDL2WindowHost>
#include <coffee/core/input/eventhandlers.h>
#include <coffee/core/CDebug>

#include <coffee/core/types/map.h>
#include <coffee/image/cimage.h>

#include <SDL.h>

#undef main

using namespace Coffee;
using namespace Display;

using RSC = CResources::Resource;
using BasicWindow = SDL2WindowHost;
using Sprites = SDL2SpriteRenderer;

Sprites::Texture sprite_load(Sprites* instance, Sprites::Renderer* renderer,
                             cstring source, Sprites::SpriteSource* size)
{
    Sprites::Texture out;

    RSC analog_rsc(source,
                   ResourceAccess::SpecifyStorage
                   |ResourceAccess::AssetFile);
    if(!CResources::FileMap(analog_rsc))
    {
        SDL2Dialog::ErrorMessage("Failed to load resource","Couldn't find texture");
        return {};
    }
    CStbImageLib::CStbImage img;
    CStbImageLib::LoadData(&img,&analog_rsc,PixCmp::RGBA);

    instance->createTexture(*renderer,1,&out,
                            PixelFormat::RGBA8,
                            ResourceAccess::Streaming,
                            {img.size.w,img.size.h});

    if(size)
    {
        size->w = img.size.w;
        size->h = img.size.h;
    }

    CRGBA* data = C_CAST<CRGBA*>(instance->mapTexture(out));
    MemCpy(data,img.data, C_CAST<szptr>(img.size.area()*img.bpp));
    instance->unmapTexture(out);

    CStbImageLib::ImageFree(&img);
    CResources::FileUnmap(analog_rsc);

    return out;
}

void SDL_Handler(void*, SDL_Event* const& ev, c_cptr)
{
//    cDebug("Event received: {0}", ev->type);
}

int32 coffee_main(int32, cstring_w*)
{
    SubsystemWrapper<SDL2::SDL2> sys1;
    C_UNUSED(sys1);

    CResources::FileResourcePrefix("sample_data/input-sprites/");

    auto visual = GetDefaultVisual();

    CString err;

    BasicWindow winhost;
    if(!winhost.init(visual,&err))
    {
        SDL2Dialog::ErrorMessage("Failed to create window",
                                 err.c_str());
        return 1;
    }

    winhost.installEventHandler({SDL_Handler, nullptr, nullptr});

    /* Attaching the sprite instance to the window */
    Sprites rend(&winhost);
    if(!rend.init(&err))
    {
        SDL2Dialog::ErrorMessage("Failed to initialize renderer",
                                 err.c_str());
        return 1;
    }

    /* Create a rendering instance */
    Sprites::Renderer inst = rend.createRenderer();

    rend.setClearColor(inst,CRGBA(0,0,0,255));
    rend.clearBuffer(inst);

    /* Create sprites */
    Sprites::Texture controller_atlas = sprite_load(&rend,&inst,"atlas.png",nullptr);

    Sprites::Sprite analog_sprite;
    Sprites::Sprite analog_back_sprite;
    Sprites::Sprite trigger_sprite;
    Sprites::Sprite trigger_back_sprite;

    Sprites::Sprite menu_sprite;
    Sprites::Sprite menu_p_sprite;

    Sprites::Sprite shoulderl_sprite;
    Sprites::Sprite shoulderl_p_sprite;

    Sprites::Sprite shoulderr_sprite;
    Sprites::Sprite shoulderr_p_sprite;

    Sprites::Sprite button_a_sprite;
    Sprites::Sprite button_x_sprite;
    Sprites::Sprite button_b_sprite;
    Sprites::Sprite button_y_sprite;

    Sprites::Sprite button_ap_sprite;
    Sprites::Sprite button_xp_sprite;
    Sprites::Sprite button_bp_sprite;
    Sprites::Sprite button_yp_sprite;

    Sprites::Sprite dpad_bg_sprite;
    Sprites::Sprite dpad_up_sprite;
    Sprites::Sprite dpad_dp_sprite;
    Sprites::Sprite dpad_lp_sprite;
    Sprites::Sprite dpad_rp_sprite;

    Sprites::Sprite empty_sprite;

    auto bind_sprite = [&](Sprites::SpriteSource const& src, Sprites::Sprite& sprite)
    {
        rend.createSprite(controller_atlas, src, &sprite);
    };

    _cbasic_static_map<Sprites::SpriteSource,Sprites::Sprite&,23> sprite_map = {
        {{256,0,128,128},analog_sprite},

        {{256,128,96,64},menu_sprite},
        {{256,192,96,64},menu_p_sprite},

        {{128,512-128,128,128},dpad_bg_sprite},

        {{293,384,50,43},dpad_up_sprite},
        {{293,472,50,40},dpad_dp_sprite},
        {{256,427,37,45},dpad_lp_sprite},
        {{343,427,38,45},dpad_rp_sprite},

        {{0,0,256,256},analog_back_sprite},

        {{128,256,96,32},trigger_sprite},
        {{0,256,96,256},trigger_back_sprite},

        {{640,256,128,64},shoulderl_sprite},
        {{768,256,128,64},shoulderl_p_sprite},

        {{384,256,128,64},shoulderr_sprite},
        {{512,256,128,64},shoulderr_p_sprite},

        {{384,0,128,128},button_a_sprite},
        {{384,128,128,128},button_x_sprite},
        {{640,0,128,128},button_b_sprite},
        {{640,128,128,128},button_y_sprite},

        {{512,0,128,128},button_ap_sprite},
        {{512,128,128,128},button_xp_sprite},
        {{768,0,128,128},button_bp_sprite},
        {{768,128,128,128},button_yp_sprite},
    };

    for(size_t i=0;i<23;i++)
        bind_sprite(sprite_map[i].key, sprite_map[i].value);

    /* Install standard event handlers */
    winhost.installEventHandler({EventHandlers::EscapeCloseWindow<BasicWindow>,nullptr,&winhost});
    winhost.installEventHandler({EventHandlers::WindowManagerCloseWindow<BasicWindow>,nullptr,&winhost});

    auto draw_element = [&](
            Sprites::Sprite const& unpressed, Sprites::Sprite const* pressed,
            Sprites::Sprite const* background,
            bool button, Vecf2 const& base, Vecf2 const& size, Vecf2 const& pos)
    {
        auto sp = &unpressed;
        if(button)
            sp = pressed;
        if(background)
            rend.drawSprite(inst,
            {
                                base.x() * size.x(),
                                base.y() * size.y()
                            },
            {size.x(), size.y()}, *background);
        rend.drawSprite(
                    inst,
        {
                        (base.x() + pos.x()) * size.x(),
                        (base.y() + pos.y()) * size.y()
                    },
        {
                        size.x(),
                        size.y()
                    }, *sp);
    };

    auto draw_axis = [&](Vecf2 base, i16 x, i16 y, bool btn, f32 scale)
    {
        draw_element(button_x_sprite, &button_xp_sprite, &analog_back_sprite,
                     btn, base,
                    {scale}, {scalar(scalar(x)/Int16_Max*128.f + 64),
                            scalar(scalar(y)/Int16_Max*128.f + 64)}
                     );
    };

    while(!winhost.closeFlag())
    {
        rend.setClearColor(inst,CRGBA(0,0,0,255));
        rend.clearBuffer(inst);

        {
            CSize win_size = winhost.windowSize();
            f32 scale_factor = .5f;

            if(win_size.w >= 960 && win_size.h >= 640)
                scale_factor = 1.f;

            /* Ensure proper scaling by using half scaling under this size */
            win_size.w = CMath::max(win_size.w, 960);
            win_size.h = CMath::max(win_size.h, 640);

            auto ctl = winhost.getControllerState(0);
            f32 axes_ruler = win_size.h - 256.f;
            draw_axis({0,axes_ruler}, ctl.axes.e.l_x,
                      ctl.axes.e.l_y, ctl.buttons.e.s_l, scale_factor);
            draw_axis({win_size.w-256.f,axes_ruler},
                      ctl.axes.e.r_x, ctl.axes.e.r_y, ctl.buttons.e.s_r,scale_factor);

            Vecf2 dpad_root = {256, axes_ruler + 128};
            draw_element(dpad_bg_sprite, nullptr, nullptr,
                         false, {dpad_root.x(), dpad_root.y()}, {scale_factor}, {});
            draw_element(empty_sprite, &dpad_up_sprite, nullptr,
                         ctl.buttons.e.p_up, dpad_root, {scale_factor}, {295-256,0});
            draw_element(empty_sprite, &dpad_dp_sprite, nullptr,
                         ctl.buttons.e.p_down, dpad_root, {scale_factor}, {295-256,472-384});
            draw_element(empty_sprite, &dpad_lp_sprite, nullptr,
                         ctl.buttons.e.p_left, dpad_root, {scale_factor}, {2,427-384});
            draw_element(empty_sprite, &dpad_rp_sprite, nullptr,
                         ctl.buttons.e.p_right, dpad_root, {scale_factor}, {345-256,427-384});

            Vecf2 back_root = {};
            draw_element(shoulderl_sprite, &shoulderl_p_sprite, nullptr,
                         ctl.buttons.e.b_l, {back_root.x(),back_root.y()}, {scale_factor}, {});
            draw_element(trigger_sprite, nullptr, &trigger_back_sprite, false,
                         {back_root.x(),back_root.y()+64}, {scale_factor},
            {0,scalar(scalar(ctl.axes.e.t_l)/Int16_Max*220.f)});

            Vecf2 right_anchor = {scalar(win_size.w-128) + back_root.x(), back_root.y()};
            draw_element(trigger_sprite, nullptr, &trigger_back_sprite, false,
                         {right_anchor.x(),right_anchor.y()+64}, {scale_factor},
                         {0,scalar(scalar(ctl.axes.e.t_r)/Int16_Max*220.f)});
            draw_element(shoulderr_sprite, &shoulderr_p_sprite, nullptr,
                         ctl.buttons.e.b_r, {right_anchor.x(),right_anchor.y()}, {scale_factor}, {});

            Vecf2 button_root = {right_anchor.x()-320.f, 0.f};
            draw_element(button_a_sprite, &button_ap_sprite, nullptr,
                         ctl.buttons.e.a, {button_root.x() + 96, button_root.y() + 196},
            {scale_factor}, {});
            draw_element(button_x_sprite, &button_xp_sprite, nullptr,
                         ctl.buttons.e.x, {button_root.x(),button_root.y() + 96},
            {scale_factor}, {});
            draw_element(button_b_sprite, &button_bp_sprite, nullptr,
                         ctl.buttons.e.b, {button_root.x() + 192,button_root.y() + 96},
            {scale_factor}, {});
            draw_element(button_y_sprite, &button_yp_sprite, nullptr,
                         ctl.buttons.e.y, {button_root.x() + 96,button_root.y()},
            {scale_factor}, {});

            Vecf2 menu_root = {win_size.w / 2.f, win_size.h - 64.f};
            draw_element(menu_sprite, &menu_p_sprite, nullptr,
                         ctl.buttons.e.back, {menu_root.x() - 96.f,menu_root.y()},
            {scale_factor}, {});
            draw_element(menu_sprite, &menu_p_sprite, nullptr,
                         ctl.buttons.e.start, {menu_root.x(),menu_root.y()},
            {scale_factor}, {});
            draw_element(menu_sprite, &menu_p_sprite, nullptr,
                         ctl.buttons.e.guide, {menu_root.x() - 48.f,menu_root.y()-64.f},
            {scale_factor}, {});
        }

        rend.swapBuffers(inst);
        winhost.pollEvents();
    }

    rend.destroyTexture(1,&controller_atlas);
    rend.destroyRenderer(inst);

    rend.cleanup();
    winhost.cleanup();

    return 0;
}

COFFEE_APPLICATION_MAIN(coffee_main);

<commit_msg> - Fix oopsie with defines for JoystickVisualizer<commit_after>#include <coffee/core/CApplication>
#include <coffee/core/CFiles>
#include <coffee/sdl2/CSDL2Dialog>
#include <coffee/sdl2/CSDL2System>
#include <coffee/sdl2/CSDL2SpriteWindow>
#include <coffee/sdl2/CSDL2WindowHost>
#include <coffee/core/input/eventhandlers.h>
#include <coffee/core/CDebug>

#include <coffee/core/types/map.h>
#include <coffee/image/cimage.h>

//#include <SDL.h>

//#undef main

using namespace Coffee;
using namespace Display;

using RSC = CResources::Resource;
using BasicWindow = SDL2WindowHost;
using Sprites = SDL2SpriteRenderer;

Sprites::Texture sprite_load(Sprites* instance, Sprites::Renderer* renderer,
                             cstring source, Sprites::SpriteSource* size)
{
    Sprites::Texture out;

    RSC analog_rsc(source,
                   ResourceAccess::SpecifyStorage
                   |ResourceAccess::AssetFile);
    if(!CResources::FileMap(analog_rsc))
    {
        SDL2Dialog::ErrorMessage("Failed to load resource","Couldn't find texture");
        return {};
    }
    CStbImageLib::CStbImage img;
    CStbImageLib::LoadData(&img,&analog_rsc,PixCmp::RGBA);

    instance->createTexture(*renderer,1,&out,
                            PixelFormat::RGBA8,
                            ResourceAccess::Streaming,
                            {img.size.w,img.size.h});

    if(size)
    {
        size->w = img.size.w;
        size->h = img.size.h;
    }

    CRGBA* data = C_CAST<CRGBA*>(instance->mapTexture(out));
    MemCpy(data,img.data, C_CAST<szptr>(img.size.area()*img.bpp));
    instance->unmapTexture(out);

    CStbImageLib::ImageFree(&img);
    CResources::FileUnmap(analog_rsc);

    return out;
}

void SDL_Handler(void*, SDL_Event* const& ev, c_cptr)
{
//    cDebug("Event received: {0}", ev->type);
}

int32 coffee_main(int32, cstring_w*)
{
    SubsystemWrapper<SDL2::SDL2> sys1;
    C_UNUSED(sys1);

    CResources::FileResourcePrefix("sample_data/input-sprites/");

    auto visual = GetDefaultVisual();

    CString err;

    BasicWindow winhost;
    if(!winhost.init(visual,&err))
    {
        SDL2Dialog::ErrorMessage("Failed to create window",
                                 err.c_str());
        return 1;
    }

    winhost.installEventHandler({SDL_Handler, nullptr, nullptr});

    /* Attaching the sprite instance to the window */
    Sprites rend(&winhost);
    if(!rend.init(&err))
    {
        SDL2Dialog::ErrorMessage("Failed to initialize renderer",
                                 err.c_str());
        return 1;
    }

    /* Create a rendering instance */
    Sprites::Renderer inst = rend.createRenderer();

    rend.setClearColor(inst,CRGBA(0,0,0,255));
    rend.clearBuffer(inst);

    /* Create sprites */
    Sprites::Texture controller_atlas = sprite_load(&rend,&inst,"atlas.png",nullptr);

    Sprites::Sprite analog_sprite;
    Sprites::Sprite analog_back_sprite;
    Sprites::Sprite trigger_sprite;
    Sprites::Sprite trigger_back_sprite;

    Sprites::Sprite menu_sprite;
    Sprites::Sprite menu_p_sprite;

    Sprites::Sprite shoulderl_sprite;
    Sprites::Sprite shoulderl_p_sprite;

    Sprites::Sprite shoulderr_sprite;
    Sprites::Sprite shoulderr_p_sprite;

    Sprites::Sprite button_a_sprite;
    Sprites::Sprite button_x_sprite;
    Sprites::Sprite button_b_sprite;
    Sprites::Sprite button_y_sprite;

    Sprites::Sprite button_ap_sprite;
    Sprites::Sprite button_xp_sprite;
    Sprites::Sprite button_bp_sprite;
    Sprites::Sprite button_yp_sprite;

    Sprites::Sprite dpad_bg_sprite;
    Sprites::Sprite dpad_up_sprite;
    Sprites::Sprite dpad_dp_sprite;
    Sprites::Sprite dpad_lp_sprite;
    Sprites::Sprite dpad_rp_sprite;

    Sprites::Sprite empty_sprite;

    auto bind_sprite = [&](Sprites::SpriteSource const& src, Sprites::Sprite& sprite)
    {
        rend.createSprite(controller_atlas, src, &sprite);
    };

    _cbasic_static_map<Sprites::SpriteSource,Sprites::Sprite&,23> sprite_map = {
        {{256,0,128,128},analog_sprite},

        {{256,128,96,64},menu_sprite},
        {{256,192,96,64},menu_p_sprite},

        {{128,512-128,128,128},dpad_bg_sprite},

        {{293,384,50,43},dpad_up_sprite},
        {{293,472,50,40},dpad_dp_sprite},
        {{256,427,37,45},dpad_lp_sprite},
        {{343,427,38,45},dpad_rp_sprite},

        {{0,0,256,256},analog_back_sprite},

        {{128,256,96,32},trigger_sprite},
        {{0,256,96,256},trigger_back_sprite},

        {{640,256,128,64},shoulderl_sprite},
        {{768,256,128,64},shoulderl_p_sprite},

        {{384,256,128,64},shoulderr_sprite},
        {{512,256,128,64},shoulderr_p_sprite},

        {{384,0,128,128},button_a_sprite},
        {{384,128,128,128},button_x_sprite},
        {{640,0,128,128},button_b_sprite},
        {{640,128,128,128},button_y_sprite},

        {{512,0,128,128},button_ap_sprite},
        {{512,128,128,128},button_xp_sprite},
        {{768,0,128,128},button_bp_sprite},
        {{768,128,128,128},button_yp_sprite},
    };

    for(size_t i=0;i<23;i++)
        bind_sprite(sprite_map[i].key, sprite_map[i].value);

    /* Install standard event handlers */
    winhost.installEventHandler({EventHandlers::EscapeCloseWindow<BasicWindow>,nullptr,&winhost});
    winhost.installEventHandler({EventHandlers::WindowManagerCloseWindow<BasicWindow>,nullptr,&winhost});

    auto draw_element = [&](
            Sprites::Sprite const& unpressed, Sprites::Sprite const* pressed,
            Sprites::Sprite const* background,
            bool button, Vecf2 const& base, Vecf2 const& size, Vecf2 const& pos)
    {
        auto sp = &unpressed;
        if(button)
            sp = pressed;
        if(background)
            rend.drawSprite(inst,
            {
                                base.x() * size.x(),
                                base.y() * size.y()
                            },
            {size.x(), size.y()}, *background);
        rend.drawSprite(
                    inst,
        {
                        (base.x() + pos.x()) * size.x(),
                        (base.y() + pos.y()) * size.y()
                    },
        {
                        size.x(),
                        size.y()
                    }, *sp);
    };

    auto draw_axis = [&](Vecf2 base, i16 x, i16 y, bool btn, f32 scale)
    {
        draw_element(button_x_sprite, &button_xp_sprite, &analog_back_sprite,
                     btn, base,
                    {scale}, {scalar(scalar(x)/Int16_Max*128.f + 64),
                            scalar(scalar(y)/Int16_Max*128.f + 64)}
                     );
    };

    while(!winhost.closeFlag())
    {
        rend.setClearColor(inst,CRGBA(0,0,0,255));
        rend.clearBuffer(inst);

        {
            CSize win_size = winhost.windowSize();
            f32 scale_factor = .5f;

            if(win_size.w >= 960 && win_size.h >= 640)
                scale_factor = 1.f;

            /* Ensure proper scaling by using half scaling under this size */
            win_size.w = CMath::max(win_size.w, 960);
            win_size.h = CMath::max(win_size.h, 640);

            auto ctl = winhost.getControllerState(0);
            f32 axes_ruler = win_size.h - 256.f;
            draw_axis({0,axes_ruler}, ctl.axes.e.l_x,
                      ctl.axes.e.l_y, ctl.buttons.e.s_l, scale_factor);
            draw_axis({win_size.w-256.f,axes_ruler},
                      ctl.axes.e.r_x, ctl.axes.e.r_y, ctl.buttons.e.s_r,scale_factor);

            Vecf2 dpad_root = {256, axes_ruler + 128};
            draw_element(dpad_bg_sprite, nullptr, nullptr,
                         false, {dpad_root.x(), dpad_root.y()}, {scale_factor}, {});
            draw_element(empty_sprite, &dpad_up_sprite, nullptr,
                         ctl.buttons.e.p_up, dpad_root, {scale_factor}, {295-256,0});
            draw_element(empty_sprite, &dpad_dp_sprite, nullptr,
                         ctl.buttons.e.p_down, dpad_root, {scale_factor}, {295-256,472-384});
            draw_element(empty_sprite, &dpad_lp_sprite, nullptr,
                         ctl.buttons.e.p_left, dpad_root, {scale_factor}, {2,427-384});
            draw_element(empty_sprite, &dpad_rp_sprite, nullptr,
                         ctl.buttons.e.p_right, dpad_root, {scale_factor}, {345-256,427-384});

            Vecf2 back_root = {};
            draw_element(shoulderl_sprite, &shoulderl_p_sprite, nullptr,
                         ctl.buttons.e.b_l, {back_root.x(),back_root.y()}, {scale_factor}, {});
            draw_element(trigger_sprite, nullptr, &trigger_back_sprite, false,
                         {back_root.x(),back_root.y()+64}, {scale_factor},
            {0,scalar(scalar(ctl.axes.e.t_l)/Int16_Max*220.f)});

            Vecf2 right_anchor = {scalar(win_size.w-128) + back_root.x(), back_root.y()};
            draw_element(trigger_sprite, nullptr, &trigger_back_sprite, false,
                         {right_anchor.x(),right_anchor.y()+64}, {scale_factor},
                         {0,scalar(scalar(ctl.axes.e.t_r)/Int16_Max*220.f)});
            draw_element(shoulderr_sprite, &shoulderr_p_sprite, nullptr,
                         ctl.buttons.e.b_r, {right_anchor.x(),right_anchor.y()}, {scale_factor}, {});

            Vecf2 button_root = {right_anchor.x()-320.f, 0.f};
            draw_element(button_a_sprite, &button_ap_sprite, nullptr,
                         ctl.buttons.e.a, {button_root.x() + 96, button_root.y() + 196},
            {scale_factor}, {});
            draw_element(button_x_sprite, &button_xp_sprite, nullptr,
                         ctl.buttons.e.x, {button_root.x(),button_root.y() + 96},
            {scale_factor}, {});
            draw_element(button_b_sprite, &button_bp_sprite, nullptr,
                         ctl.buttons.e.b, {button_root.x() + 192,button_root.y() + 96},
            {scale_factor}, {});
            draw_element(button_y_sprite, &button_yp_sprite, nullptr,
                         ctl.buttons.e.y, {button_root.x() + 96,button_root.y()},
            {scale_factor}, {});

            Vecf2 menu_root = {win_size.w / 2.f, win_size.h - 64.f};
            draw_element(menu_sprite, &menu_p_sprite, nullptr,
                         ctl.buttons.e.back, {menu_root.x() - 96.f,menu_root.y()},
            {scale_factor}, {});
            draw_element(menu_sprite, &menu_p_sprite, nullptr,
                         ctl.buttons.e.start, {menu_root.x(),menu_root.y()},
            {scale_factor}, {});
            draw_element(menu_sprite, &menu_p_sprite, nullptr,
                         ctl.buttons.e.guide, {menu_root.x() - 48.f,menu_root.y()-64.f},
            {scale_factor}, {});
        }

        rend.swapBuffers(inst);
        winhost.pollEvents();
    }

    rend.destroyTexture(1,&controller_atlas);
    rend.destroyRenderer(inst);

    rend.cleanup();
    winhost.cleanup();

    return 0;
}

COFFEE_APPLICATION_MAIN(coffee_main);

<|endoftext|>


<commit_before>/*
 * This file is open source software, licensed to you under the terms
 * of the Apache License, Version 2.0 (the "License").  See the NOTICE file
 * distributed with this work for additional information regarding copyright
 * ownership.  You may not use this file except in compliance with the License.
 *
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/*
 * Copyright (C) 2015 Cloudius Systems, Ltd.
 */

#include <seastar/core/app-template.hh>
#include <seastar/core/distributed.hh>
#include <seastar/core/future-util.hh>
#include <seastar/core/sleep.hh>
#include <seastar/core/thread.hh>
#include <seastar/core/print.hh>
#include <seastar/util/defer.hh>
#include <mutex>

using namespace seastar;
using namespace std::chrono_literals;

struct async_service : public seastar::async_sharded_service<async_service> {
    thread_local static bool deleted;
    ~async_service() {
        deleted = true;
    }
    void run() {
        auto ref = shared_from_this();
        // Wait a while and check.
        (void)sleep(std::chrono::milliseconds(100 + 100 * this_shard_id())).then([this, ref] {
           check();
        });
    }
    virtual void check() {
        assert(!deleted);
    }
    future<> stop() { return make_ready_future<>(); }
};

thread_local bool async_service::deleted = false;

struct X {
    sstring echo(sstring arg) {
        return arg;
    }
    int cpu_id_squared() const {
        auto id = this_shard_id();
        return id * id;
    }
    future<> stop() { return make_ready_future<>(); }
};

template <typename T, typename Func>
future<> do_with_distributed(Func&& func) {
    auto x = make_shared<distributed<T>>();
    return func(*x).finally([x] {
        return x->stop();
    }).finally([x]{});
}

future<> test_that_each_core_gets_the_arguments() {
    return do_with_distributed<X>([] (auto& x) {
        return x.start().then([&x] {
            return x.map_reduce([] (sstring msg){
                if (msg != "hello") {
                    throw std::runtime_error("wrong message");
                }
            }, &X::echo, sstring("hello"));
        });
    });
}

future<> test_functor_version() {
    return do_with_distributed<X>([] (auto& x) {
        return x.start().then([&x] {
            return x.map_reduce([] (sstring msg){
                if (msg != "hello") {
                    throw std::runtime_error("wrong message");
                }
            }, [] (X& x) { return x.echo("hello"); });
        });
    });
}

struct Y {
    sstring s;
    Y(sstring s) : s(std::move(s)) {}
    future<> stop() { return make_ready_future<>(); }
};

future<> test_constructor_argument_is_passed_to_each_core() {
    return do_with_distributed<Y>([] (auto& y) {
        return y.start(sstring("hello")).then([&y] {
            return y.invoke_on_all([] (Y& y) {
                if (y.s != "hello") {
                    throw std::runtime_error(format("expected message mismatch, is \"%s\"", y.s));
                }
            });
        });
    });
}

future<> test_map_reduce() {
    return do_with_distributed<X>([] (distributed<X>& x) {
        return x.start().then([&x] {
            return x.map_reduce0(std::mem_fn(&X::cpu_id_squared),
                                 0,
                                 std::plus<int>()).then([] (int result) {
                int n = smp::count - 1;
                if (result != (n * (n + 1) * (2*n + 1)) / 6) {
                    throw std::runtime_error("map_reduce failed");
                }
            });
        });
    });
}

future<> test_async() {
    return do_with_distributed<async_service>([] (distributed<async_service>& x) {
        return x.start().then([&x] {
            return x.invoke_on_all(&async_service::run);
        });
    }).then([] {
        return sleep(std::chrono::milliseconds(100 * (smp::count + 1)));
    });
}

future<> test_invoke_on_others() {
    return seastar::async([] {
        struct my_service {
            int counter = 0;
            void up() { ++counter; }
            future<> stop() { return make_ready_future<>(); }
        };
        for (unsigned c = 0; c < smp::count; ++c) {
            smp::submit_to(c, [c] {
                return seastar::async([c] {
                    sharded<my_service> s;
                    s.start().get();
                    s.invoke_on_others([](auto& s) { s.up(); }).get();
                    if (s.local().counter != 0) {
                        throw std::runtime_error("local modified");
                    }
                    s.invoke_on_all([c](auto& remote) {
                        if (this_shard_id() != c) {
                            if (remote.counter != 1) {
                                throw std::runtime_error("remote not modified");
                            }
                        }
                    }).get();
                    s.stop().get();
                });
            }).get();
        }
    });
}

struct remote_worker {
    unsigned current = 0;
    unsigned max_concurrent_observed = 0;
    future<> do_work() {
        ++current;
        max_concurrent_observed = std::max(current, max_concurrent_observed);
        return sleep(100ms).then([this] {
            max_concurrent_observed = std::max(current, max_concurrent_observed);
            --current;
        });
    }
    future<> do_remote_work(shard_id t, smp_service_group ssg) {
        return smp::submit_to(t,  ssg, [this] {
            return do_work();
        });
    }
};

future<> test_smp_service_groups() {
    return async([] {
        smp_service_group_config ssgc1;
        ssgc1.max_nonlocal_requests = 1;
        auto ssg1 = create_smp_service_group(ssgc1).get0();
        smp_service_group_config ssgc2;
        ssgc2.max_nonlocal_requests = 1000;
        auto ssg2 = create_smp_service_group(ssgc2).get0();
        shard_id other_shard = smp::count - 1;
        remote_worker rm1;
        remote_worker rm2;
        auto bunch1 = parallel_for_each(boost::irange(0, 20), [&] (int ignore) { return rm1.do_remote_work(other_shard, ssg1); });
        auto bunch2 = parallel_for_each(boost::irange(0, 2000), [&] (int ignore) { return rm2.do_remote_work(other_shard, ssg2); });
        bunch1.get();
        bunch2.get();
        if (smp::count > 1) {
            assert(rm1.max_concurrent_observed == 1);
            assert(rm2.max_concurrent_observed >= 1000 / (smp::count - 1) && rm2.max_concurrent_observed <= 1000);
        }
        destroy_smp_service_group(ssg1).get();
        destroy_smp_service_group(ssg2).get();
    });
}

future<> test_smp_service_groups_re_construction() {
    // During development of the feature, we saw a bug where the vector
    // holding the groups did not expand correctly. This test triggers the
    // bug.
    return async([] {
        auto ssg1 = create_smp_service_group({}).get0();
        auto ssg2 = create_smp_service_group({}).get0();
        destroy_smp_service_group(ssg1).get();
        auto ssg3 = create_smp_service_group({}).get0();
        destroy_smp_service_group(ssg2).get();
        destroy_smp_service_group(ssg3).get();
    });
}

future<> test_smp_timeout() {
    return async([] {
        smp_service_group_config ssgc1;
        ssgc1.max_nonlocal_requests = 1;
        auto ssg1 = create_smp_service_group(ssgc1).get0();

        auto _ = defer([ssg1] {
            destroy_smp_service_group(ssg1).get();
        });

        const shard_id other_shard = smp::count - 1;

        // Ugly but beats using sleeps.
        std::mutex mut;
        std::unique_lock<std::mutex> lk(mut);

        // Submitted to the remote shard.
        auto fut1 = smp::submit_to(other_shard, ssg1, [&mut] {
            std::cout << "Running request no. 1" << std::endl;
            std::unique_lock<std::mutex> lk(mut);
            std::cout << "Request no. 1 done" << std::endl;
        });
        // Consume the only unit from the semaphore.
        auto fut2 = smp::submit_to(other_shard, ssg1, [] {
            std::cout << "Running request no. 2 - done" << std::endl;
        });

        auto fut_timedout = smp::submit_to(other_shard, smp_submit_to_options(ssg1, smp_timeout_clock::now() + 10ms), [] {
            std::cout << "Running timed-out request - done" << std::endl;
        });

        {
            auto notify = defer([lk = std::move(lk)] { });

            try {
                fut_timedout.get();
                throw std::runtime_error("smp::submit_to() didn't timeout as expected");
            } catch (semaphore_timed_out& e) {
                std::cout << "Expected timeout received: " << e.what() << std::endl;
            } catch (...) {
                std::throw_with_nested(std::runtime_error("smp::submit_to() failed with unexpected exception"));
            }
        }

        fut1.get();
        fut2.get();
    });
}

int main(int argc, char** argv) {
    app_template app;
    return app.run(argc, argv, [] {
        return test_that_each_core_gets_the_arguments().then([] {
            return test_functor_version();
        }).then([] {
            return test_constructor_argument_is_passed_to_each_core();
        }).then([] {
            return test_map_reduce();
        }).then([] {
            return test_async();
        }).then([] {
            return test_invoke_on_others();
        }).then([] {
            return test_smp_service_groups();
        }).then([] {
            return test_smp_service_groups_re_construction();
        }).then([] {
            return test_smp_timeout();
        });
    });
}
<commit_msg>distributed_test: Fix it on slow machines<commit_after>/*
 * This file is open source software, licensed to you under the terms
 * of the Apache License, Version 2.0 (the "License").  See the NOTICE file
 * distributed with this work for additional information regarding copyright
 * ownership.  You may not use this file except in compliance with the License.
 *
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/*
 * Copyright (C) 2015 Cloudius Systems, Ltd.
 */

#include <seastar/core/app-template.hh>
#include <seastar/core/distributed.hh>
#include <seastar/core/future-util.hh>
#include <seastar/core/semaphore.hh>
#include <seastar/core/sleep.hh>
#include <seastar/core/thread.hh>
#include <seastar/core/print.hh>
#include <seastar/util/defer.hh>
#include <mutex>

using namespace seastar;
using namespace std::chrono_literals;

struct async_service : public seastar::async_sharded_service<async_service> {
    thread_local static bool deleted;
    ~async_service() {
        deleted = true;
    }
    void run() {
        auto ref = shared_from_this();
        // Wait a while and check.
        (void)sleep(std::chrono::milliseconds(100 + 100 * this_shard_id())).then([this, ref] {
           check();
        });
    }
    virtual void check() {
        assert(!deleted);
    }
    future<> stop() { return make_ready_future<>(); }
};

thread_local bool async_service::deleted = false;

struct X {
    sstring echo(sstring arg) {
        return arg;
    }
    int cpu_id_squared() const {
        auto id = this_shard_id();
        return id * id;
    }
    future<> stop() { return make_ready_future<>(); }
};

template <typename T, typename Func>
future<> do_with_distributed(Func&& func) {
    auto x = make_shared<distributed<T>>();
    return func(*x).finally([x] {
        return x->stop();
    }).finally([x]{});
}

future<> test_that_each_core_gets_the_arguments() {
    return do_with_distributed<X>([] (auto& x) {
        return x.start().then([&x] {
            return x.map_reduce([] (sstring msg){
                if (msg != "hello") {
                    throw std::runtime_error("wrong message");
                }
            }, &X::echo, sstring("hello"));
        });
    });
}

future<> test_functor_version() {
    return do_with_distributed<X>([] (auto& x) {
        return x.start().then([&x] {
            return x.map_reduce([] (sstring msg){
                if (msg != "hello") {
                    throw std::runtime_error("wrong message");
                }
            }, [] (X& x) { return x.echo("hello"); });
        });
    });
}

struct Y {
    sstring s;
    Y(sstring s) : s(std::move(s)) {}
    future<> stop() { return make_ready_future<>(); }
};

future<> test_constructor_argument_is_passed_to_each_core() {
    return do_with_distributed<Y>([] (auto& y) {
        return y.start(sstring("hello")).then([&y] {
            return y.invoke_on_all([] (Y& y) {
                if (y.s != "hello") {
                    throw std::runtime_error(format("expected message mismatch, is \"%s\"", y.s));
                }
            });
        });
    });
}

future<> test_map_reduce() {
    return do_with_distributed<X>([] (distributed<X>& x) {
        return x.start().then([&x] {
            return x.map_reduce0(std::mem_fn(&X::cpu_id_squared),
                                 0,
                                 std::plus<int>()).then([] (int result) {
                int n = smp::count - 1;
                if (result != (n * (n + 1) * (2*n + 1)) / 6) {
                    throw std::runtime_error("map_reduce failed");
                }
            });
        });
    });
}

future<> test_async() {
    return do_with_distributed<async_service>([] (distributed<async_service>& x) {
        return x.start().then([&x] {
            return x.invoke_on_all(&async_service::run);
        });
    }).then([] {
        return sleep(std::chrono::milliseconds(100 * (smp::count + 1)));
    });
}

future<> test_invoke_on_others() {
    return seastar::async([] {
        struct my_service {
            int counter = 0;
            void up() { ++counter; }
            future<> stop() { return make_ready_future<>(); }
        };
        for (unsigned c = 0; c < smp::count; ++c) {
            smp::submit_to(c, [c] {
                return seastar::async([c] {
                    sharded<my_service> s;
                    s.start().get();
                    s.invoke_on_others([](auto& s) { s.up(); }).get();
                    if (s.local().counter != 0) {
                        throw std::runtime_error("local modified");
                    }
                    s.invoke_on_all([c](auto& remote) {
                        if (this_shard_id() != c) {
                            if (remote.counter != 1) {
                                throw std::runtime_error("remote not modified");
                            }
                        }
                    }).get();
                    s.stop().get();
                });
            }).get();
        }
    });
}

struct remote_worker {
    unsigned current = 0;
    unsigned max_concurrent_observed = 0;
    unsigned expected_max;
    semaphore sem{0};
    remote_worker(unsigned expected_max) : expected_max(expected_max) {
    }
    future<> do_work() {
        ++current;
        max_concurrent_observed = std::max(current, max_concurrent_observed);
        if (max_concurrent_observed >= expected_max && sem.current() == 0) {
            sem.signal(semaphore::max_counter());
        }
        return sem.wait().then([this] {
            // Sleep a bit to check if the concurrency goes over the max
            return sleep(100ms).then([this] {
                max_concurrent_observed = std::max(current, max_concurrent_observed);
                --current;
            });
        });
    }
    future<> do_remote_work(shard_id t, smp_service_group ssg) {
        return smp::submit_to(t,  ssg, [this] {
            return do_work();
        });
    }
};

future<> test_smp_service_groups() {
    return async([] {
        smp_service_group_config ssgc1;
        ssgc1.max_nonlocal_requests = 1;
        auto ssg1 = create_smp_service_group(ssgc1).get0();
        smp_service_group_config ssgc2;
        ssgc2.max_nonlocal_requests = 1000;
        auto ssg2 = create_smp_service_group(ssgc2).get0();
        shard_id other_shard = smp::count - 1;
        remote_worker rm1(1);
        remote_worker rm2(1000);
        auto bunch1 = parallel_for_each(boost::irange(0, 20), [&] (int ignore) { return rm1.do_remote_work(other_shard, ssg1); });
        auto bunch2 = parallel_for_each(boost::irange(0, 2000), [&] (int ignore) { return rm2.do_remote_work(other_shard, ssg2); });
        bunch1.get();
        bunch2.get();
        if (smp::count > 1) {
            assert(rm1.max_concurrent_observed == 1);
            assert(rm2.max_concurrent_observed == 1000);
        }
        destroy_smp_service_group(ssg1).get();
        destroy_smp_service_group(ssg2).get();
    });
}

future<> test_smp_service_groups_re_construction() {
    // During development of the feature, we saw a bug where the vector
    // holding the groups did not expand correctly. This test triggers the
    // bug.
    return async([] {
        auto ssg1 = create_smp_service_group({}).get0();
        auto ssg2 = create_smp_service_group({}).get0();
        destroy_smp_service_group(ssg1).get();
        auto ssg3 = create_smp_service_group({}).get0();
        destroy_smp_service_group(ssg2).get();
        destroy_smp_service_group(ssg3).get();
    });
}

future<> test_smp_timeout() {
    return async([] {
        smp_service_group_config ssgc1;
        ssgc1.max_nonlocal_requests = 1;
        auto ssg1 = create_smp_service_group(ssgc1).get0();

        auto _ = defer([ssg1] {
            destroy_smp_service_group(ssg1).get();
        });

        const shard_id other_shard = smp::count - 1;

        // Ugly but beats using sleeps.
        std::mutex mut;
        std::unique_lock<std::mutex> lk(mut);

        // Submitted to the remote shard.
        auto fut1 = smp::submit_to(other_shard, ssg1, [&mut] {
            std::cout << "Running request no. 1" << std::endl;
            std::unique_lock<std::mutex> lk(mut);
            std::cout << "Request no. 1 done" << std::endl;
        });
        // Consume the only unit from the semaphore.
        auto fut2 = smp::submit_to(other_shard, ssg1, [] {
            std::cout << "Running request no. 2 - done" << std::endl;
        });

        auto fut_timedout = smp::submit_to(other_shard, smp_submit_to_options(ssg1, smp_timeout_clock::now() + 10ms), [] {
            std::cout << "Running timed-out request - done" << std::endl;
        });

        {
            auto notify = defer([lk = std::move(lk)] { });

            try {
                fut_timedout.get();
                throw std::runtime_error("smp::submit_to() didn't timeout as expected");
            } catch (semaphore_timed_out& e) {
                std::cout << "Expected timeout received: " << e.what() << std::endl;
            } catch (...) {
                std::throw_with_nested(std::runtime_error("smp::submit_to() failed with unexpected exception"));
            }
        }

        fut1.get();
        fut2.get();
    });
}

int main(int argc, char** argv) {
    app_template app;
    return app.run(argc, argv, [] {
        return test_that_each_core_gets_the_arguments().then([] {
            return test_functor_version();
        }).then([] {
            return test_constructor_argument_is_passed_to_each_core();
        }).then([] {
            return test_map_reduce();
        }).then([] {
            return test_async();
        }).then([] {
            return test_invoke_on_others();
        }).then([] {
            return test_smp_service_groups();
        }).then([] {
            return test_smp_service_groups_re_construction();
        }).then([] {
            return test_smp_timeout();
        });
    });
}
<|endoftext|>


<commit_before>#include <stdio.h>
#include <sys/time.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/resource.h>
#include <unistd.h>
#include <common/cycles.h>
#include <common/dump_utils.h>
#include <exo/rand.h>

#include "nvme_device.h"

//#define COUNT (1000000)
#define COUNT (64)
#define NUM_QUEUES (1)
#define SLAB_SIZE (512)
#define NUM_BLOCKS (8)

static Exokernel::Pagemap pm;

class Read_thread : public Exokernel::Base_thread {
private:
  NVME_device * _dev;
  unsigned _qid;
  Notify_object nobj;

  void read_throughput_test()
  {
    using namespace Exokernel;

    //byte r;

#if 0
    /* set higher priority */
    {
      struct sched_param params;
      params.sched_priority = sched_get_priority_max(SCHED_FIFO);
      pthread_setschedparam(pthread_self(), SCHED_FIFO, &params);
    }
#endif

    /* set up a simple slab */
    addr_t phys_array[SLAB_SIZE];
    void * virt_array[SLAB_SIZE];

    /* allocate slab of memory */
    Memory::huge_system_configure_nrpages(SLAB_SIZE+8);
    int h;
    virt_array[0] = Memory::huge_shmem_alloc(1,PAGE_SIZE * SLAB_SIZE,0,&h);
    assert(virt_array[0]);

    char * p = (char *) virt_array[0];
    for(unsigned i=0;i<SLAB_SIZE;i++) {
      virt_array[i] = p;
      phys_array[i] = pm.virt_to_phys(virt_array[i]);
      p+=PAGE_SIZE;
      assert(virt_array[i]);
      assert(phys_array[i]);
    }
    touch_pages(virt_array[0],PAGE_SIZE*SLAB_SIZE); /* eager map */
    PLOG("Memory allocated OK!!");

    cpu_time_t start_ts = rdtsc();
    NVME_IO_queue * ioq = _dev->io_queue(_qid);
    PLOG("** READ THROUGHPUT AROUND Q:%p (_qid=%u)\n",(void *) ioq, _qid);

    unsigned long long mean_cycles = 0;
    unsigned long long total_cycles = 0;
    unsigned long long sample_count = 0;

    _dev->io_queue(_qid)->callback_manager()->register_callback(&Notify_object::notify_callback,
                                                                (void*)&nobj);

    unsigned long threshold = _dev->io_queue(_qid)->queue_length() * 0.8;
    PLOG("threshold = %lu", threshold);
    atomic_t send_id = 0;
    uint16_t cid;
    for(unsigned long i=0;i<COUNT;i++) {

      PLOG("Start to send (Q:%u) %lu...",_qid,send_id);
      cpu_time_t start = rdtsc();
      cid = _dev->block_async_read(_qid,
                                   phys_array[i%SLAB_SIZE],
                                   (i*PAGE_SIZE)+((_qid-1)*COUNT), /* offset */
                                   NUM_BLOCKS); /* num blocks */

      PLOG("sent (Q:%u) %lu...",_qid,send_id);
      /* collect stats */
      cpu_time_t delta = rdtsc() - start;

      if(sample_count > 0) {
        total_cycles += delta;
        sample_count++;
        mean_cycles = total_cycles/sample_count;
      } else {
        total_cycles = delta;
        mean_cycles = delta;
        sample_count = 1;
      }

#define INTERVAL 100
      if(i % INTERVAL == 0) PLOG("(_QID:%u) i=%lu", _qid, i);
      if(i==COUNT) break;

      /* wait for bursts of IOPs - don't do this too late */
      if((i % threshold == 0) && (i > 0)) {
        atomic_t c;

        nobj.set_when(send_id); /* set wake up at send_id */
        nobj.wait();
      }

      send_id++;
    }
    PLOG("all sends complete.");

    // /* wait for last command */
    // uint16_t c;
    // while((c = nobj.wait()) != cid) {
    // }
    // PLOG("end c = %x",c);

    ioq->dump_stats();

    PLOG("(QID:%u) cycles mean/iteration %llu (%llu IOPS)\n",_qid, mean_cycles, (((unsigned long long)get_tsc_frequency_in_mhz())*1000000)/mean_cycles);
    cpu_time_t end_ts = rdtsc();

        Memory::huge_shmem_free(virt_array[0],h);
  }

public:
  Read_thread(NVME_device * dev, unsigned qid, core_id_t core) :
    _qid(qid), _dev(dev), Exokernel::Base_thread(NULL,core) {
  }

  void* entry(void* param) {
    assert(_dev);

    std::stringstream str;
    str << "rt-client-qt-" << _qid;
    Exokernel::set_thread_name(str.str().c_str());

    read_throughput_test();
  }

};

class mt_tests {

    public:
        static void runTest(NVME_device* dev) {
            NVME_INFO("Issuing test read and write test...");
            {
                Read_thread * thr[NUM_QUEUES];

                for(unsigned i=1;i<=NUM_QUEUES;i++) {
                    thr[i-1] = new Read_thread(dev,
                            i, //(2*(i-1))+1, /* qid */
                            i+3); //*2-1); //1,3,5,7 /* core for read thread */

                }

                struct rusage ru_start, ru_end;
                struct timeval tv_start, tv_end;
                gettimeofday(&tv_start, NULL);
                getrusage(RUSAGE_SELF,&ru_start);

                for(unsigned i=1;i<=NUM_QUEUES;i++)
                    thr[i-1]->start();

                for(unsigned i=1;i<=NUM_QUEUES;i++) {
                    PLOG("!!!!!!!!!!!!!! joined read thread %p",thr[i-1]);
                    thr[i-1]->join();
                }

                PLOG("All read threads joined.");

                getrusage(RUSAGE_SELF,&ru_end);
                gettimeofday(&tv_end, NULL);

                double delta = ((ru_end.ru_utime.tv_sec - ru_start.ru_utime.tv_sec) * 1000000.0) +
                    ((ru_end.ru_utime.tv_usec - ru_start.ru_utime.tv_usec));

                /* add kernel time */
                delta += ((ru_end.ru_stime.tv_sec - ru_start.ru_stime.tv_sec) * 1000000.0) +
                    ((ru_end.ru_stime.tv_usec - ru_start.ru_stime.tv_usec));

                float usec_seconds_for_count = delta/NUM_QUEUES;
                PLOG("Time for 1M 4K reads is: %.5g sec",usec_seconds_for_count / 1000000.0);
                float usec_each = usec_seconds_for_count / ((float)COUNT);
                PLOG("Rate: %.5f IOPS/sec", (1000000.0 / usec_each)/1000.0);

                PLOG("Voluntary ctx switches = %ld",ru_end.ru_nvcsw - ru_start.ru_nvcsw);
                PLOG("Involuntary ctx switches = %ld",ru_end.ru_nivcsw - ru_start.ru_nivcsw);
                PLOG("Page-faults = %ld",ru_end.ru_majflt - ru_start.ru_majflt);

                double gtod_delta = ((tv_end.tv_sec - tv_start.tv_sec) * 1000000.0) +
                    ((tv_end.tv_usec - tv_start.tv_usec));
                double gtod_secs = (gtod_delta/NUM_QUEUES) / 1000000.0;
                PLOG("gettimeofday delta %.2g sec (rate=%.2f KIOPS)",
                        gtod_secs,
                        1000.0 / gtod_secs);

            }
        }
};
<commit_msg>indent formatting<commit_after>#include <stdio.h>
#include <sys/time.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/resource.h>
#include <unistd.h>
#include <common/cycles.h>
#include <common/dump_utils.h>
#include <exo/rand.h>

#include "nvme_device.h"

//#define COUNT (1000000)
#define COUNT (64)
#define NUM_QUEUES (1)
#define SLAB_SIZE (512)
#define NUM_BLOCKS (8)

static Exokernel::Pagemap pm;

class Read_thread : public Exokernel::Base_thread {
  private:
    NVME_device * _dev;
    unsigned _qid;
    Notify_object nobj;

    void read_throughput_test()
    {
      using namespace Exokernel;

      //byte r;

#if 0
      /* set higher priority */
      {
        struct sched_param params;
        params.sched_priority = sched_get_priority_max(SCHED_FIFO);
        pthread_setschedparam(pthread_self(), SCHED_FIFO, &params);
      }
#endif

      /* set up a simple slab */
      addr_t phys_array[SLAB_SIZE];
      void * virt_array[SLAB_SIZE];

      /* allocate slab of memory */
      Memory::huge_system_configure_nrpages(SLAB_SIZE+8);
      int h;
      virt_array[0] = Memory::huge_shmem_alloc(1,PAGE_SIZE * SLAB_SIZE,0,&h);
      assert(virt_array[0]);

      char * p = (char *) virt_array[0];
      for(unsigned i=0;i<SLAB_SIZE;i++) {
        virt_array[i] = p;
        phys_array[i] = pm.virt_to_phys(virt_array[i]);
        p+=PAGE_SIZE;
        assert(virt_array[i]);
        assert(phys_array[i]);
      }
      touch_pages(virt_array[0],PAGE_SIZE*SLAB_SIZE); /* eager map */
      PLOG("Memory allocated OK!!");

      cpu_time_t start_ts = rdtsc();
      NVME_IO_queue * ioq = _dev->io_queue(_qid);
      PLOG("** READ THROUGHPUT AROUND Q:%p (_qid=%u)\n",(void *) ioq, _qid);

      unsigned long long mean_cycles = 0;
      unsigned long long total_cycles = 0;
      unsigned long long sample_count = 0;

      _dev->io_queue(_qid)->callback_manager()->register_callback(&Notify_object::notify_callback,
          (void*)&nobj);

      unsigned long threshold = _dev->io_queue(_qid)->queue_length() * 0.8;
      PLOG("threshold = %lu", threshold);
      atomic_t send_id = 0;
      uint16_t cid;
      for(unsigned long i=0;i<COUNT;i++) {

        PLOG("Start to send (Q:%u) %lu...",_qid,send_id);
        cpu_time_t start = rdtsc();
        cid = _dev->block_async_read(_qid,
            phys_array[i%SLAB_SIZE],
            (i*PAGE_SIZE)+((_qid-1)*COUNT), /* offset */
            NUM_BLOCKS); /* num blocks */

        PLOG("sent (Q:%u) %lu...",_qid,send_id);
        /* collect stats */
        cpu_time_t delta = rdtsc() - start;

        if(sample_count > 0) {
          total_cycles += delta;
          sample_count++;
          mean_cycles = total_cycles/sample_count;
        } else {
          total_cycles = delta;
          mean_cycles = delta;
          sample_count = 1;
        }

#define INTERVAL 100
        if(i % INTERVAL == 0) PLOG("(_QID:%u) i=%lu", _qid, i);
        if(i==COUNT) break;

        /* wait for bursts of IOPs - don't do this too late */
        if((i % threshold == 0) && (i > 0)) {
          atomic_t c;

          nobj.set_when(send_id); /* set wake up at send_id */
          nobj.wait();
        }

        send_id++;
      }
      PLOG("all sends complete.");

      // /* wait for last command */
      // uint16_t c;
      // while((c = nobj.wait()) != cid) {
      // }
      // PLOG("end c = %x",c);

      ioq->dump_stats();

      PLOG("(QID:%u) cycles mean/iteration %llu (%llu IOPS)\n",_qid, mean_cycles, (((unsigned long long)get_tsc_frequency_in_mhz())*1000000)/mean_cycles);
      cpu_time_t end_ts = rdtsc();

      Memory::huge_shmem_free(virt_array[0],h);
    }

  public:
    Read_thread(NVME_device * dev, unsigned qid, core_id_t core) :
      _qid(qid), _dev(dev), Exokernel::Base_thread(NULL,core) {
      }

    void* entry(void* param) {
      assert(_dev);

      std::stringstream str;
      str << "rt-client-qt-" << _qid;
      Exokernel::set_thread_name(str.str().c_str());

      read_throughput_test();
    }

};

class mt_tests {

  public:
    static void runTest(NVME_device* dev) {
      NVME_INFO("Issuing test read and write test...");
      {
        Read_thread * thr[NUM_QUEUES];

        for(unsigned i=1;i<=NUM_QUEUES;i++) {
          thr[i-1] = new Read_thread(dev,
              i, //(2*(i-1))+1, /* qid */
              i+3); //*2-1); //1,3,5,7 /* core for read thread */

        }

        struct rusage ru_start, ru_end;
        struct timeval tv_start, tv_end;
        gettimeofday(&tv_start, NULL);
        getrusage(RUSAGE_SELF,&ru_start);

        for(unsigned i=1;i<=NUM_QUEUES;i++)
          thr[i-1]->start();

        for(unsigned i=1;i<=NUM_QUEUES;i++) {
          PLOG("!!!!!!!!!!!!!! joined read thread %p",thr[i-1]);
          thr[i-1]->join();
        }

        PLOG("All read threads joined.");

        getrusage(RUSAGE_SELF,&ru_end);
        gettimeofday(&tv_end, NULL);

        double delta = ((ru_end.ru_utime.tv_sec - ru_start.ru_utime.tv_sec) * 1000000.0) +
          ((ru_end.ru_utime.tv_usec - ru_start.ru_utime.tv_usec));

        /* add kernel time */
        delta += ((ru_end.ru_stime.tv_sec - ru_start.ru_stime.tv_sec) * 1000000.0) +
          ((ru_end.ru_stime.tv_usec - ru_start.ru_stime.tv_usec));

        float usec_seconds_for_count = delta/NUM_QUEUES;
        PLOG("Time for 1M 4K reads is: %.5g sec",usec_seconds_for_count / 1000000.0);
        float usec_each = usec_seconds_for_count / ((float)COUNT);
        PLOG("Rate: %.5f IOPS/sec", (1000000.0 / usec_each)/1000.0);

        PLOG("Voluntary ctx switches = %ld",ru_end.ru_nvcsw - ru_start.ru_nvcsw);
        PLOG("Involuntary ctx switches = %ld",ru_end.ru_nivcsw - ru_start.ru_nivcsw);
        PLOG("Page-faults = %ld",ru_end.ru_majflt - ru_start.ru_majflt);

        double gtod_delta = ((tv_end.tv_sec - tv_start.tv_sec) * 1000000.0) +
          ((tv_end.tv_usec - tv_start.tv_usec));
        double gtod_secs = (gtod_delta/NUM_QUEUES) / 1000000.0;
        PLOG("gettimeofday delta %.2g sec (rate=%.2f KIOPS)",
            gtod_secs,
            1000.0 / gtod_secs);

      }
    }
};
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2008, 2009 Nokia Corporation.
 *
 * Contact: Marius Vollmer <marius.vollmer@nokia.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

#include <QtTest/QtTest>
#include <QtCore>
#include "contextpropertyinfo.h"
#include "infobackend.h"

/* Mocked infobackend */

InfoBackend* currentBackend = NULL;

InfoBackend* InfoBackend::instance(const QString &backendName)
{
    if (currentBackend)
        return currentBackend;
    else {
        currentBackend = new InfoBackend();
        return currentBackend;
    }
}

QStringList InfoBackend::listKeys() const
{
    QStringList l;
    l << QString("Battery.Charging");
    l << QString("Media.NowPlaying");
    return l;
}

QStringList InfoBackend::listKeysForPlugin(QString plugin) const
{
    if (plugin == "contextkit-dbus") {
        QStringList l;
        l << QString("Battery.Charging");
        l << QString("Media.NowPlaying");
        return l;
    } else
        return QStringList();
}

QStringList InfoBackend::listPlugins() const
{
    QStringList l;
    l << QString("contextkit-dbus");
    return l;
}

QString InfoBackend::constructionStringForKey(QString key) const
{
    if (key == "Battery.Charging")
        return "system:org.freedesktop.ContextKit.contextd";
    else if (key == "Media.NowPlaying")
        return "system:com.nokia.musicplayer";
    return QString();
}

QString InfoBackend::typeForKey(QString key) const
{
    if (key == "Battery.Charging")
        return "TRUTH";
    else if (key == "Media.NowPlaying")
        return "STRING";
    else
        return "";
}

QString InfoBackend::docForKey(QString key) const
{
    if (key == "Battery.Charging")
        return "Battery.Charging doc";
    else if (key == "Media.NowPlaying")
        return "Media.NowPlaying doc";
    else
        return "";
}

QString InfoBackend::pluginForKey(QString key) const
{
    if (key == "Battery.Charging")
        return "contextkit-dbus";
    else if (key == "Media.NowPlaying")
        return "contextkit-dbus";
    else
        return "";
}

bool InfoBackend::keyExists(QString key) const
{
    if (key == "Battery.Charging")
        return true;
    else if (key == "Media.NowPlaying")
        return true;
    else
        return false;
}

bool InfoBackend::keyProvided(QString key) const
{
    if (key == "Battery.Charging")
        return true;
    else if (key == "Media.NowPlaying")
        return false;
    else
        return false;
}

void InfoBackend::connectNotify(const char *signal)
{
}

void InfoBackend::disconnectNotify(const char *signal)
{
}

/*
void InfoBackend::fireKeysChanged(const QStringList& keys)
{
    emit keysChanged(keys);
}

void InfoBackend::fireKeysAdded(const QStringList& keys)
{
    emit keysAdded(keys);
}

void InfoBackend::fireKeysRemoved(const QStringList& keys)
{
    emit keysRemoved(keys);
}
*/

/* ContextRegistryInfoUnitTest */

class ContextPropertyInfoUnitTest : public QObject
{
    Q_OBJECT

private slots:
    void initTestCase();
    void key();
    void doc();
    void type();
};

void ContextPropertyInfoUnitTest::initTestCase()
{
}

void ContextPropertyInfoUnitTest::key()
{
    ContextPropertyInfo p("Battery.Charging");
    QCOMPARE(p.key(), QString("Battery.Charging"));
}

void ContextPropertyInfoUnitTest::doc()
{
    ContextPropertyInfo p1("Battery.Charging");
    ContextPropertyInfo p2("Media.NowPlaying");
    ContextPropertyInfo p3("Does.Not.Exist");
    QCOMPARE(p1.doc(), QString("Battery.Charging doc"));
    QCOMPARE(p2.doc(), QString("Media.NowPlaying doc"));
    QCOMPARE(p3.doc(), QString());
}

void ContextPropertyInfoUnitTest::type()
{
    ContextPropertyInfo p1("Battery.Charging");
    ContextPropertyInfo p2("Media.NowPlaying");
    ContextPropertyInfo p3("Does.Not.Exist");
    QCOMPARE(p1.type(), QString("TRUTH"));
    QCOMPARE(p2.type(), QString("STRING"));
    QCOMPARE(p3.type(), QString());
}

#include "contextpropertyinfounittest.moc"
QTEST_MAIN(ContextPropertyInfoUnitTest);
<commit_msg>exists.<commit_after>/*
 * Copyright (C) 2008, 2009 Nokia Corporation.
 *
 * Contact: Marius Vollmer <marius.vollmer@nokia.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

#include <QtTest/QtTest>
#include <QtCore>
#include "contextpropertyinfo.h"
#include "infobackend.h"

/* Mocked infobackend */

InfoBackend* currentBackend = NULL;

InfoBackend* InfoBackend::instance(const QString &backendName)
{
    if (currentBackend)
        return currentBackend;
    else {
        currentBackend = new InfoBackend();
        return currentBackend;
    }
}

QStringList InfoBackend::listKeys() const
{
    QStringList l;
    l << QString("Battery.Charging");
    l << QString("Media.NowPlaying");
    return l;
}

QStringList InfoBackend::listKeysForPlugin(QString plugin) const
{
    if (plugin == "contextkit-dbus") {
        QStringList l;
        l << QString("Battery.Charging");
        l << QString("Media.NowPlaying");
        return l;
    } else
        return QStringList();
}

QStringList InfoBackend::listPlugins() const
{
    QStringList l;
    l << QString("contextkit-dbus");
    return l;
}

QString InfoBackend::constructionStringForKey(QString key) const
{
    if (key == "Battery.Charging")
        return "system:org.freedesktop.ContextKit.contextd";
    else if (key == "Media.NowPlaying")
        return "system:com.nokia.musicplayer";
    return QString();
}

QString InfoBackend::typeForKey(QString key) const
{
    if (key == "Battery.Charging")
        return "TRUTH";
    else if (key == "Media.NowPlaying")
        return "STRING";
    else
        return "";
}

QString InfoBackend::docForKey(QString key) const
{
    if (key == "Battery.Charging")
        return "Battery.Charging doc";
    else if (key == "Media.NowPlaying")
        return "Media.NowPlaying doc";
    else
        return "";
}

QString InfoBackend::pluginForKey(QString key) const
{
    if (key == "Battery.Charging")
        return "contextkit-dbus";
    else if (key == "Media.NowPlaying")
        return "contextkit-dbus";
    else
        return "";
}

bool InfoBackend::keyExists(QString key) const
{
    if (key == "Battery.Charging")
        return true;
    else if (key == "Media.NowPlaying")
        return true;
    else
        return false;
}

bool InfoBackend::keyProvided(QString key) const
{
    if (key == "Battery.Charging")
        return true;
    else if (key == "Media.NowPlaying")
        return false;
    else
        return false;
}

void InfoBackend::connectNotify(const char *signal)
{
}

void InfoBackend::disconnectNotify(const char *signal)
{
}

/*
void InfoBackend::fireKeysChanged(const QStringList& keys)
{
    emit keysChanged(keys);
}

void InfoBackend::fireKeysAdded(const QStringList& keys)
{
    emit keysAdded(keys);
}

void InfoBackend::fireKeysRemoved(const QStringList& keys)
{
    emit keysRemoved(keys);
}
*/

/* ContextRegistryInfoUnitTest */

class ContextPropertyInfoUnitTest : public QObject
{
    Q_OBJECT

private slots:
    void initTestCase();
    void key();
    void doc();
    void type();
    void exists();
};

void ContextPropertyInfoUnitTest::initTestCase()
{
}

void ContextPropertyInfoUnitTest::key()
{
    ContextPropertyInfo p("Battery.Charging");
    QCOMPARE(p.key(), QString("Battery.Charging"));
}

void ContextPropertyInfoUnitTest::doc()
{
    ContextPropertyInfo p1("Battery.Charging");
    ContextPropertyInfo p2("Media.NowPlaying");
    ContextPropertyInfo p3("Does.Not.Exist");
    QCOMPARE(p1.doc(), QString("Battery.Charging doc"));
    QCOMPARE(p2.doc(), QString("Media.NowPlaying doc"));
    QCOMPARE(p3.doc(), QString());
}

void ContextPropertyInfoUnitTest::type()
{
    ContextPropertyInfo p1("Battery.Charging");
    ContextPropertyInfo p2("Media.NowPlaying");
    ContextPropertyInfo p3("Does.Not.Exist");
    QCOMPARE(p1.type(), QString("TRUTH"));
    QCOMPARE(p2.type(), QString("STRING"));
    QCOMPARE(p3.type(), QString());
}

void ContextPropertyInfoUnitTest::exists()
{
    ContextPropertyInfo p1("Battery.Charging");
    ContextPropertyInfo p2("Media.NowPlaying");
    ContextPropertyInfo p3("Does.Not.Exist");
    QCOMPARE(p1.exists(), true);
    QCOMPARE(p2.exists(), true);
    QCOMPARE(p3.exists(), false);
}

#include "contextpropertyinfounittest.moc"
QTEST_MAIN(ContextPropertyInfoUnitTest);
<|endoftext|>