<commit_before>/*!
@file

@copyright Edouard Alligand and Joel Falcou 2015-2017
(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
#ifndef BOOST_BRIGAND_ALGORITHMS_HPP
#define BOOST_BRIGAND_ALGORITHMS_HPP

#include <brigand/algorithms/all.hpp>
#include <brigand/algorithms/any.hpp>
#include <brigand/algorithms/count.hpp>
#include <brigand/algorithms/find.hpp>
#include <brigand/algorithms/flatten.hpp>
#include <brigand/algorithms/fold.hpp>
#include <brigand/algorithms/for_each.hpp>
#include <brigand/algorithms/for_each_args.hpp>
#include <brigand/algorithms/index_of.hpp>
#include <brigand/algorithms/is_set.hpp>
#include <brigand/algorithms/none.hpp>
#include <brigand/algorithms/partition.hpp>
#include <brigand/algorithms/remove.hpp>
#include <brigand/algorithms/replace.hpp>
#include <brigand/algorithms/reverse.hpp>
#include <brigand/algorithms/select.hpp>
#include <brigand/algorithms/split.hpp>
#include <brigand/algorithms/split_at.hpp>
#include <brigand/algorithms/sort.hpp>
#include <brigand/algorithms/transform.hpp>
#include <brigand/algorithms/wrap.hpp>
#endif
<commit_msg>Delete algorithms.hpp<commit_after><|endoftext|>


<commit_before>/** \file    patch_up_ppns_for_k10plus.cc
 *  \brief   Swaps out all persistent old PPN's with new PPN's.
 *  \author  Dr. Johannes Ruscheinski
 */

/*
    Copyright (C) 2019, Library of the University of Tübingen

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as
    published by the Free Software Foundation, either version 3 of the
    License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <iostream>
#include <memory>
#include <unordered_map>
#include <vector>
#include <cstdlib>
#include <cstring>
#include <kchashdb.h>
#include "BSZUtil.h"
#include "Compiler.h"
#include "DbConnection.h"
#include "DbResultSet.h"
#include "DbRow.h"
#include "FileUtil.h"
#include "MapUtil.h"
#include "MARC.h"
#include "RegexMatcher.h"
#include "StringUtil.h"
#include "UBTools.h"
#include "util.h"
#include "VuFind.h"

namespace {

[[noreturn]] void Usage() {
    ::Usage("[--store-only] marc_input1 [marc_input2 .. marc_inputN] [-- deletion_list1 deletion_list2 .. deletion_listN]\n"
            "If --store-only has been specified, no swapping will be performed and only the persistent map file will be overwritten.\n"
            "If deletion lists should be processed, they need to be spcified after a double-hyhen to indicate the end of the MARC files.");
}

struct PPNsAndSigil {
    std::string old_ppn_, old_sigil_, new_ppn_;
public:
    PPNsAndSigil(const std::string &old_ppn, const std::string &old_sigil, const std::string &new_ppn)
        : old_ppn_(old_ppn), old_sigil_(old_sigil), new_ppn_(new_ppn) { }
    PPNsAndSigil() = default;
    PPNsAndSigil(const PPNsAndSigil &other) = default;
};

void LoadMapping(MARC::Reader * const marc_reader,
                 const std::unordered_multimap<std::string, std::string> &already_processed_ppns_and_sigils,
                 std::vector<PPNsAndSigil> * const old_ppns_sigils_and_new_ppns)
{
    // We need to consider the sigil of the old BSZ as well as the K10+ sigil because in future there may also be merges of
    // K10+ entities which will lead to old PPN's being K10+ PPN's.
    auto matcher(RegexMatcher::RegexMatcherFactoryOrDie("^\\((DE-576|DE-627)\\)(.+)"));
    while (const auto record = marc_reader->read()) {
        for (const auto &field : record.getTagRange("035")) {
            const auto subfield_a(field.getFirstSubfieldWithCode('a'));
            if (matcher->matched(subfield_a)) {
                const std::string old_sigil((*matcher)[1]);
                const std::string old_ppn((*matcher)[2]);
                if (not MapUtil::Contains(already_processed_ppns_and_sigils, old_ppn, old_sigil))
                    old_ppns_sigils_and_new_ppns->emplace_back(old_ppn, old_sigil, record.getControlNumber());
            }
        }
    }

    LOG_INFO("Found " + std::to_string(old_ppns_sigils_and_new_ppns->size()) + " new mappings of old PPN's to new PPN's in \""
             + marc_reader->getPath() + "\".\n");
}

void PatchTable(DbConnection * const db_connection, const std::string &table, const std::string &column,
                const std::vector<PPNsAndSigil> &old_ppns_sigils_and_new_ppns)
{
    const unsigned MAX_BATCH_SIZE(100);

    db_connection->queryOrDie("BEGIN");

    unsigned replacement_count(0), batch_size(0);
    for (const auto &old_ppn_sigil_and_new_ppn : old_ppns_sigils_and_new_ppns) {
        ++batch_size;
        db_connection->queryOrDie("UPDATE IGNORE " + table + " SET " + column + "='" + old_ppn_sigil_and_new_ppn.new_ppn_
                                  + "' WHERE " + column + "='" + old_ppn_sigil_and_new_ppn.old_ppn_ + "'");
        replacement_count += db_connection->getNoOfAffectedRows();
        if (batch_size >= MAX_BATCH_SIZE) {
            db_connection->queryOrDie("COMMIT");
            db_connection->queryOrDie("BEGIN");
        }
    }

    db_connection->queryOrDie("COMMIT");

    LOG_INFO("Replaced " + std::to_string(replacement_count) + " rows in " + table + ".");
}

void DeleteFromTable(DbConnection * const db_connection, const std::string &table, const std::string &column,
                     const std::unordered_set<std::string> &deletion_ppns)
{
    const unsigned MAX_BATCH_SIZE(100);

    db_connection->queryOrDie("BEGIN");

    unsigned deletion_count(0), batch_size(0);
    for (const auto &deletion_ppn : deletion_ppns) {
        ++batch_size;
        db_connection->queryOrDie("DELETE FROM " + table + + "' WHERE " + column + "='" + deletion_ppn + "'");
        deletion_count += db_connection->getNoOfAffectedRows();
        if (batch_size >= MAX_BATCH_SIZE) {
            db_connection->queryOrDie("COMMIT");
            db_connection->queryOrDie("BEGIN");
        }
    }

    db_connection->queryOrDie("COMMIT");

    LOG_INFO("Deleted " + std::to_string(deletion_count) + " rows from " + table + ".");
}

void PatchNotifiedDB(const std::string &user_type, const std::vector<PPNsAndSigil> &old_ppns_sigils_and_new_ppns) {
    const std::string DB_FILENAME(UBTools::GetTuelibPath() + user_type + "_notified.db");
    std::unique_ptr<kyotocabinet::HashDB> db(new kyotocabinet::HashDB());
    if (not (db->open(DB_FILENAME, kyotocabinet::HashDB::OWRITER | kyotocabinet::HashDB::OREADER))) {
        LOG_INFO("\"" + DB_FILENAME + "\" not found!");
        return;
    }

    unsigned updated_count(0);
    for (const auto &ppns_and_sigil : old_ppns_sigils_and_new_ppns) {
        std::string value;
        if (db->get(ppns_and_sigil.old_ppn_, &value)) {
            if (unlikely(not db->remove(ppns_and_sigil.old_ppn_)))
                LOG_ERROR("failed to remove key \"" + ppns_and_sigil.old_ppn_ + "\" from \"" + DB_FILENAME + "\"!");
            if (unlikely(not db->add(ppns_and_sigil.old_ppn_, value)))
                LOG_ERROR("failed to add key \"" + ppns_and_sigil.old_ppn_ + "\" from \"" + DB_FILENAME + "\"!");
            ++updated_count;
        }
    }

    LOG_INFO("Updated " + std::to_string(updated_count) + " entries in \"" + DB_FILENAME + "\".");
}

void DeleteFromNotifiedDB(const std::string &user_type, const std::unordered_set<std::string> &deletion_ppns) {
    const std::string DB_FILENAME(UBTools::GetTuelibPath() + user_type + "_notified.db");
    std::unique_ptr<kyotocabinet::HashDB> db(new kyotocabinet::HashDB());
    if (not (db->open(DB_FILENAME, kyotocabinet::HashDB::OWRITER | kyotocabinet::HashDB::OREADER))) {
        LOG_INFO("\"" + DB_FILENAME + "\" not found!");
        return;
    }

    unsigned deletion_count(0);
    for (const auto &deletion_ppn : deletion_ppns) {
        if (db->remove(deletion_ppn))
            ++deletion_count;
    }

    LOG_INFO("Deleted " + std::to_string(deletion_count) + " entries from \"" + DB_FILENAME + "\".");
}

bool HaveAllPermissions(DbConnection * const db_connection, const std::string &database) {
    const std::string QUERY("SHOW GRANTS FOR '" + db_connection->getUser() + "'@'" + db_connection->getHost() + "'");
    if (not db_connection->query(QUERY)) {
        if (db_connection->getLastErrorCode() == 1141)
            return false;
        LOG_ERROR(QUERY + " failed: " + db_connection->getLastErrorMessage());
    }

    DbResultSet result_set(db_connection->getLastResultSet());
    while (const auto row = result_set.getNextRow()) {
        if (row[0]
            == "GRANT ALL PRIVILEGES ON `" + database + "`.* TO '" + db_connection->getUser() + "'@'" + db_connection->getHost() + "'")
            return true;
    }
    return false;
}

void CheckMySQLPermissions(DbConnection * const db_connection) {
    if (not HaveAllPermissions(db_connection, "vufind"))
        LOG_ERROR("'" + db_connection->getUser() + "'@'" + db_connection->getHost() + "' needs all permissions on the vufind database!");
    if (VuFind::GetTueFindFlavour() == "ixtheo") {
        if (not HaveAllPermissions(db_connection, "ixtheo"))
            LOG_ERROR("'" + db_connection->getUser() + "'@' " + db_connection->getHost()
                      + "' needs all permissions on the ixtheo database!");
    }
}

void AddPPNsAndSigilsToMultiMap(const std::vector<PPNsAndSigil> &old_ppns_sigils_and_new_ppns,
                                std::unordered_multimap<std::string, std::string> * const already_processed_ppns_and_sigils)
{
    for (const auto &old_ppn_sigil_and_new_ppn : old_ppns_sigils_and_new_ppns)
        already_processed_ppns_and_sigils->emplace(std::make_pair(old_ppn_sigil_and_new_ppn.old_ppn_, old_ppn_sigil_and_new_ppn.old_sigil_));
}

template<class SetOrMap, typename ProcessNotifieldDBFunc, typename ProcessTableFunc>
void ProcessAllDatabases(DbConnection * const db_connection, const SetOrMap &set_or_map, const ProcessNotifieldDBFunc notified_db_func,
                         const ProcessTableFunc table_func)
{
    notified_db_func("ixtheo", set_or_map);
    notified_db_func("relbib", set_or_map);

    table_func(db_connection, "vufind.resource", "record_id", set_or_map);
    table_func(db_connection, "vufind.record", "record_id", set_or_map);
    table_func(db_connection, "vufind.change_tracker", "id", set_or_map);
    if (VuFind::GetTueFindFlavour() == "ixtheo") {
        table_func(db_connection, "ixtheo.keyword_translations", "ppn", set_or_map);
        table_func(db_connection, "vufind.ixtheo_journal_subscriptions", "journal_control_number_or_bundle_name",
                   set_or_map);
        table_func(db_connection, "vufind.ixtheo_pda_subscriptions", "book_ppn", set_or_map);
        table_func(db_connection, "vufind.relbib_ids", "record_id", set_or_map);
        table_func(db_connection, "vufind.bibstudies_ids", "record_id", set_or_map);
    }
}

} // unnamed namespace

static const std::string ALREADY_SWAPPED_PPNS_MAP_FILE(UBTools::GetTuelibPath() + "k10+_ppn_map.map");

int Main(int argc, char **argv) {
    if (argc < 2)
        Usage();

    bool store_only(false);
    if (std::strcmp(argv[1], "--store-only") == 0) {
        store_only = true;
        --argc, ++argv;
        if (argc < 2)
            Usage();
    }

    DbConnection db_connection; // ub_tools user

    CheckMySQLPermissions(&db_connection);

    std::unordered_multimap<std::string, std::string> already_processed_ppns_and_sigils;
    if (not FileUtil::Exists(ALREADY_SWAPPED_PPNS_MAP_FILE))
        FileUtil::WriteStringOrDie(ALREADY_SWAPPED_PPNS_MAP_FILE, "");
    if (not store_only)
        MapUtil::DeserialiseMap(ALREADY_SWAPPED_PPNS_MAP_FILE, &already_processed_ppns_and_sigils);

    std::vector<PPNsAndSigil> old_ppns_sigils_and_new_ppns;
    int arg_no(1);
    for (/* Intentionally empty! */; arg_no < argc; ++arg_no) {
        if (__builtin_strcmp(argv[arg_no], "--"))
            break;
        const auto marc_reader(MARC::Reader::Factory(argv[arg_no]));
        LoadMapping(marc_reader.get(), already_processed_ppns_and_sigils, &old_ppns_sigils_and_new_ppns);
    }

    std::unordered_set <std::string> title_deletion_ppns;
    for (/* Intentionally empty! */; arg_no < argc; ++arg_no) {
        const auto input(FileUtil::OpenInputFileOrDie(argv[arg_no]));
        std::unordered_set <std::string> local_deletion_ids;
        BSZUtil::ExtractDeletionIds(input.get(), &title_deletion_ppns, &local_deletion_ids);
    }

    if (old_ppns_sigils_and_new_ppns.empty() and title_deletion_ppns.empty()) {
        LOG_INFO("nothing to do!");
        return EXIT_SUCCESS;
    }
    if (old_ppns_sigils_and_new_ppns.empty())
        goto clean_up_deleted_ppns;

    if (store_only) {
        AddPPNsAndSigilsToMultiMap(old_ppns_sigils_and_new_ppns, &already_processed_ppns_and_sigils);
        MapUtil::SerialiseMap(ALREADY_SWAPPED_PPNS_MAP_FILE, already_processed_ppns_and_sigils);
        return EXIT_SUCCESS;
    }

    ProcessAllDatabases(&db_connection, old_ppns_sigils_and_new_ppns, PatchNotifiedDB, PatchTable);
    AddPPNsAndSigilsToMultiMap(old_ppns_sigils_and_new_ppns, &already_processed_ppns_and_sigils);
    MapUtil::SerialiseMap(ALREADY_SWAPPED_PPNS_MAP_FILE, already_processed_ppns_and_sigils);

clean_up_deleted_ppns:
    ProcessAllDatabases(&db_connection, title_deletion_ppns, DeleteFromNotifiedDB, DeleteFromTable);

    return EXIT_SUCCESS;
}
<commit_msg>Fixed a couple of typos.<commit_after>/** \file    patch_up_ppns_for_k10plus.cc
 *  \brief   Swaps out all persistent old PPN's with new PPN's.
 *  \author  Dr. Johannes Ruscheinski
 */

/*
    Copyright (C) 2019, Library of the University of Tübingen

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as
    published by the Free Software Foundation, either version 3 of the
    License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <iostream>
#include <memory>
#include <unordered_map>
#include <vector>
#include <cstdlib>
#include <cstring>
#include <kchashdb.h>
#include "BSZUtil.h"
#include "Compiler.h"
#include "DbConnection.h"
#include "DbResultSet.h"
#include "DbRow.h"
#include "FileUtil.h"
#include "MapUtil.h"
#include "MARC.h"
#include "RegexMatcher.h"
#include "StringUtil.h"
#include "UBTools.h"
#include "util.h"
#include "VuFind.h"

namespace {

[[noreturn]] void Usage() {
    ::Usage("[--store-only] marc_input1 [marc_input2 .. marc_inputN] [-- deletion_list1 deletion_list2 .. deletion_listN]\n"
            "If --store-only has been specified, no swapping will be performed and only the persistent map file will be overwritten.\n"
            "If deletion lists should be processed, they need to be specified after a double-hyphen to indicate the end of the MARC files.");
}

struct PPNsAndSigil {
    std::string old_ppn_, old_sigil_, new_ppn_;
public:
    PPNsAndSigil(const std::string &old_ppn, const std::string &old_sigil, const std::string &new_ppn)
        : old_ppn_(old_ppn), old_sigil_(old_sigil), new_ppn_(new_ppn) { }
    PPNsAndSigil() = default;
    PPNsAndSigil(const PPNsAndSigil &other) = default;
};

void LoadMapping(MARC::Reader * const marc_reader,
                 const std::unordered_multimap<std::string, std::string> &already_processed_ppns_and_sigils,
                 std::vector<PPNsAndSigil> * const old_ppns_sigils_and_new_ppns)
{
    // We need to consider the sigil of the old BSZ as well as the K10+ sigil because in future there may also be merges of
    // K10+ entities which will lead to old PPN's being K10+ PPN's.
    auto matcher(RegexMatcher::RegexMatcherFactoryOrDie("^\\((DE-576|DE-627)\\)(.+)"));
    while (const auto record = marc_reader->read()) {
        for (const auto &field : record.getTagRange("035")) {
            const auto subfield_a(field.getFirstSubfieldWithCode('a'));
            if (matcher->matched(subfield_a)) {
                const std::string old_sigil((*matcher)[1]);
                const std::string old_ppn((*matcher)[2]);
                if (not MapUtil::Contains(already_processed_ppns_and_sigils, old_ppn, old_sigil))
                    old_ppns_sigils_and_new_ppns->emplace_back(old_ppn, old_sigil, record.getControlNumber());
            }
        }
    }

    LOG_INFO("Found " + std::to_string(old_ppns_sigils_and_new_ppns->size()) + " new mappings of old PPN's to new PPN's in \""
             + marc_reader->getPath() + "\".\n");
}

void PatchTable(DbConnection * const db_connection, const std::string &table, const std::string &column,
                const std::vector<PPNsAndSigil> &old_ppns_sigils_and_new_ppns)
{
    const unsigned MAX_BATCH_SIZE(100);

    db_connection->queryOrDie("BEGIN");

    unsigned replacement_count(0), batch_size(0);
    for (const auto &old_ppn_sigil_and_new_ppn : old_ppns_sigils_and_new_ppns) {
        ++batch_size;
        db_connection->queryOrDie("UPDATE IGNORE " + table + " SET " + column + "='" + old_ppn_sigil_and_new_ppn.new_ppn_
                                  + "' WHERE " + column + "='" + old_ppn_sigil_and_new_ppn.old_ppn_ + "'");
        replacement_count += db_connection->getNoOfAffectedRows();
        if (batch_size >= MAX_BATCH_SIZE) {
            db_connection->queryOrDie("COMMIT");
            db_connection->queryOrDie("BEGIN");
        }
    }

    db_connection->queryOrDie("COMMIT");

    LOG_INFO("Replaced " + std::to_string(replacement_count) + " rows in " + table + ".");
}

void DeleteFromTable(DbConnection * const db_connection, const std::string &table, const std::string &column,
                     const std::unordered_set<std::string> &deletion_ppns)
{
    const unsigned MAX_BATCH_SIZE(100);

    db_connection->queryOrDie("BEGIN");

    unsigned deletion_count(0), batch_size(0);
    for (const auto &deletion_ppn : deletion_ppns) {
        ++batch_size;
        db_connection->queryOrDie("DELETE FROM " + table + + "' WHERE " + column + "='" + deletion_ppn + "'");
        deletion_count += db_connection->getNoOfAffectedRows();
        if (batch_size >= MAX_BATCH_SIZE) {
            db_connection->queryOrDie("COMMIT");
            db_connection->queryOrDie("BEGIN");
        }
    }

    db_connection->queryOrDie("COMMIT");

    LOG_INFO("Deleted " + std::to_string(deletion_count) + " rows from " + table + ".");
}

void PatchNotifiedDB(const std::string &user_type, const std::vector<PPNsAndSigil> &old_ppns_sigils_and_new_ppns) {
    const std::string DB_FILENAME(UBTools::GetTuelibPath() + user_type + "_notified.db");
    std::unique_ptr<kyotocabinet::HashDB> db(new kyotocabinet::HashDB());
    if (not (db->open(DB_FILENAME, kyotocabinet::HashDB::OWRITER | kyotocabinet::HashDB::OREADER))) {
        LOG_INFO("\"" + DB_FILENAME + "\" not found!");
        return;
    }

    unsigned updated_count(0);
    for (const auto &ppns_and_sigil : old_ppns_sigils_and_new_ppns) {
        std::string value;
        if (db->get(ppns_and_sigil.old_ppn_, &value)) {
            if (unlikely(not db->remove(ppns_and_sigil.old_ppn_)))
                LOG_ERROR("failed to remove key \"" + ppns_and_sigil.old_ppn_ + "\" from \"" + DB_FILENAME + "\"!");
            if (unlikely(not db->add(ppns_and_sigil.old_ppn_, value)))
                LOG_ERROR("failed to add key \"" + ppns_and_sigil.old_ppn_ + "\" from \"" + DB_FILENAME + "\"!");
            ++updated_count;
        }
    }

    LOG_INFO("Updated " + std::to_string(updated_count) + " entries in \"" + DB_FILENAME + "\".");
}

void DeleteFromNotifiedDB(const std::string &user_type, const std::unordered_set<std::string> &deletion_ppns) {
    const std::string DB_FILENAME(UBTools::GetTuelibPath() + user_type + "_notified.db");
    std::unique_ptr<kyotocabinet::HashDB> db(new kyotocabinet::HashDB());
    if (not (db->open(DB_FILENAME, kyotocabinet::HashDB::OWRITER | kyotocabinet::HashDB::OREADER))) {
        LOG_INFO("\"" + DB_FILENAME + "\" not found!");
        return;
    }

    unsigned deletion_count(0);
    for (const auto &deletion_ppn : deletion_ppns) {
        if (db->remove(deletion_ppn))
            ++deletion_count;
    }

    LOG_INFO("Deleted " + std::to_string(deletion_count) + " entries from \"" + DB_FILENAME + "\".");
}

bool HaveAllPermissions(DbConnection * const db_connection, const std::string &database) {
    const std::string QUERY("SHOW GRANTS FOR '" + db_connection->getUser() + "'@'" + db_connection->getHost() + "'");
    if (not db_connection->query(QUERY)) {
        if (db_connection->getLastErrorCode() == 1141)
            return false;
        LOG_ERROR(QUERY + " failed: " + db_connection->getLastErrorMessage());
    }

    DbResultSet result_set(db_connection->getLastResultSet());
    while (const auto row = result_set.getNextRow()) {
        if (row[0]
            == "GRANT ALL PRIVILEGES ON `" + database + "`.* TO '" + db_connection->getUser() + "'@'" + db_connection->getHost() + "'")
            return true;
    }
    return false;
}

void CheckMySQLPermissions(DbConnection * const db_connection) {
    if (not HaveAllPermissions(db_connection, "vufind"))
        LOG_ERROR("'" + db_connection->getUser() + "'@'" + db_connection->getHost() + "' needs all permissions on the vufind database!");
    if (VuFind::GetTueFindFlavour() == "ixtheo") {
        if (not HaveAllPermissions(db_connection, "ixtheo"))
            LOG_ERROR("'" + db_connection->getUser() + "'@' " + db_connection->getHost()
                      + "' needs all permissions on the ixtheo database!");
    }
}

void AddPPNsAndSigilsToMultiMap(const std::vector<PPNsAndSigil> &old_ppns_sigils_and_new_ppns,
                                std::unordered_multimap<std::string, std::string> * const already_processed_ppns_and_sigils)
{
    for (const auto &old_ppn_sigil_and_new_ppn : old_ppns_sigils_and_new_ppns)
        already_processed_ppns_and_sigils->emplace(std::make_pair(old_ppn_sigil_and_new_ppn.old_ppn_, old_ppn_sigil_and_new_ppn.old_sigil_));
}

template<class SetOrMap, typename ProcessNotifieldDBFunc, typename ProcessTableFunc>
void ProcessAllDatabases(DbConnection * const db_connection, const SetOrMap &set_or_map, const ProcessNotifieldDBFunc notified_db_func,
                         const ProcessTableFunc table_func)
{
    notified_db_func("ixtheo", set_or_map);
    notified_db_func("relbib", set_or_map);

    table_func(db_connection, "vufind.resource", "record_id", set_or_map);
    table_func(db_connection, "vufind.record", "record_id", set_or_map);
    table_func(db_connection, "vufind.change_tracker", "id", set_or_map);
    if (VuFind::GetTueFindFlavour() == "ixtheo") {
        table_func(db_connection, "ixtheo.keyword_translations", "ppn", set_or_map);
        table_func(db_connection, "vufind.ixtheo_journal_subscriptions", "journal_control_number_or_bundle_name",
                   set_or_map);
        table_func(db_connection, "vufind.ixtheo_pda_subscriptions", "book_ppn", set_or_map);
        table_func(db_connection, "vufind.relbib_ids", "record_id", set_or_map);
        table_func(db_connection, "vufind.bibstudies_ids", "record_id", set_or_map);
    }
}

} // unnamed namespace

static const std::string ALREADY_SWAPPED_PPNS_MAP_FILE(UBTools::GetTuelibPath() + "k10+_ppn_map.map");

int Main(int argc, char **argv) {
    if (argc < 2)
        Usage();

    bool store_only(false);
    if (std::strcmp(argv[1], "--store-only") == 0) {
        store_only = true;
        --argc, ++argv;
        if (argc < 2)
            Usage();
    }

    DbConnection db_connection; // ub_tools user

    CheckMySQLPermissions(&db_connection);

    std::unordered_multimap<std::string, std::string> already_processed_ppns_and_sigils;
    if (not FileUtil::Exists(ALREADY_SWAPPED_PPNS_MAP_FILE))
        FileUtil::WriteStringOrDie(ALREADY_SWAPPED_PPNS_MAP_FILE, "");
    if (not store_only)
        MapUtil::DeserialiseMap(ALREADY_SWAPPED_PPNS_MAP_FILE, &already_processed_ppns_and_sigils);

    std::vector<PPNsAndSigil> old_ppns_sigils_and_new_ppns;
    int arg_no(1);
    for (/* Intentionally empty! */; arg_no < argc; ++arg_no) {
        if (__builtin_strcmp(argv[arg_no], "--"))
            break;
        const auto marc_reader(MARC::Reader::Factory(argv[arg_no]));
        LoadMapping(marc_reader.get(), already_processed_ppns_and_sigils, &old_ppns_sigils_and_new_ppns);
    }

    std::unordered_set <std::string> title_deletion_ppns;
    for (/* Intentionally empty! */; arg_no < argc; ++arg_no) {
        const auto input(FileUtil::OpenInputFileOrDie(argv[arg_no]));
        std::unordered_set <std::string> local_deletion_ids;
        BSZUtil::ExtractDeletionIds(input.get(), &title_deletion_ppns, &local_deletion_ids);
    }

    if (old_ppns_sigils_and_new_ppns.empty() and title_deletion_ppns.empty()) {
        LOG_INFO("nothing to do!");
        return EXIT_SUCCESS;
    }
    if (old_ppns_sigils_and_new_ppns.empty())
        goto clean_up_deleted_ppns;

    if (store_only) {
        AddPPNsAndSigilsToMultiMap(old_ppns_sigils_and_new_ppns, &already_processed_ppns_and_sigils);
        MapUtil::SerialiseMap(ALREADY_SWAPPED_PPNS_MAP_FILE, already_processed_ppns_and_sigils);
        return EXIT_SUCCESS;
    }

    ProcessAllDatabases(&db_connection, old_ppns_sigils_and_new_ppns, PatchNotifiedDB, PatchTable);
    AddPPNsAndSigilsToMultiMap(old_ppns_sigils_and_new_ppns, &already_processed_ppns_and_sigils);
    MapUtil::SerialiseMap(ALREADY_SWAPPED_PPNS_MAP_FILE, already_processed_ppns_and_sigils);

clean_up_deleted_ppns:
    ProcessAllDatabases(&db_connection, title_deletion_ppns, DeleteFromNotifiedDB, DeleteFromTable);

    return EXIT_SUCCESS;
}
<|endoftext|>


<commit_before>#include <stdio.h>

#include "main.h"

class Elf32 : public Elf {
public:
	Elf32(const char *data, size_t size);
private:
	Elf32_Ehdr *hdr;
};

class Elf64 : public Elf {
public:
	Elf64(const char *data, size_t size);
private:
	Elf64_Ehdr *hdr;
};

Elf* Elf::open(const char *data, size_t size)
{
	unsigned char *elf_ident = (unsigned char*)data;
	if (elf_ident[EI_MAG0] != ELFMAG0 ||
	    elf_ident[EI_MAG1] != ELFMAG1 ||
	    elf_ident[EI_MAG2] != ELFMAG2 ||
	    elf_ident[EI_MAG3] != ELFMAG3)
	{
		log(Debug, "not an ELF file\n");
		return 0;
	}

	unsigned char ei_class   = elf_ident[EI_CLASS];
	unsigned char ei_version = elf_ident[EI_VERSION];
	unsigned char ei_osabi   = elf_ident[EI_OSABI];

	if (ei_version != EV_CURRENT) {
		log(Error, "invalid ELF version: %u\n", (unsigned)ei_version);
		return 0;
	}

	if (ei_osabi != ELFOSABI_FREEBSD &&
	    ei_osabi != ELFOSABI_LINUX   &&
	    ei_osabi != ELFOSABI_NONE)
	{
		log(Warn, "osabi not recognized: %u\n", (unsigned)ei_osabi);
	}

	if (ei_class == ELFCLASS32)
		return new Elf32(data, size);
	else if (ei_class == ELFCLASS64)
		return new Elf64(data, size);
	else {
		log(Error, "unrecognized ELF class: %u\n", (unsigned)ei_class);
		return 0;
	}
}

Elf::Elf(const char *data_, size_t size_)
: error(false), data(data_), size(size_)
{
	elf_ident = (unsigned char*)data;
	if (elf_ident[EI_MAG0] != ELFMAG0 ||
	    elf_ident[EI_MAG1] != ELFMAG1 ||
	    elf_ident[EI_MAG2] != ELFMAG2 ||
	    elf_ident[EI_MAG3] != ELFMAG3)
	{
		error = true;
		return;
	}

	ei_class      = elf_ident[EI_CLASS];
	ei_data       = elf_ident[EI_DATA];
	ei_version    = elf_ident[EI_VERSION];
	ei_osabi      = elf_ident[EI_OSABI];
	ei_abiversion = elf_ident[EI_ABIVERSION];
}

Elf32::Elf32(const char *data, size_t size)
: Elf(data, size)
{
	hdr = (decltype(hdr))(data);
	db_ident = (ei_class << 16) | (ei_osabi << 8) | 32;
}

Elf64::Elf64(const char *data, size_t size)
: Elf(data, size)
{
	hdr = (decltype(hdr))(data);
	db_ident = (ei_class << 16) | (ei_osabi << 8) | 64;
}
<commit_msg>no need to implement twice<commit_after>#include <stdio.h>

#include "main.h"

template<typename HDR, typename SecHDR, typename Dyn>
class ElfImpl : public Elf {
private:
	HDR *hdr;
	SecHDR *strhdr;
	SecHDR *dynhdr;
	size_t  dyncount;

public:
	ElfImpl(const char *data, size_t size)
	: Elf(data, size)
	{
		strhdr   = 0;
		dynhdr   = 0;
		dyncount = 0;

		hdr = (decltype(hdr))(data);
		db_ident = (ei_class << 16) | (ei_osabi << 8) | 32;

		SecHDR *shdr = (SecHDR*)(data + hdr->e_shoff);
		for (size_t i = 0; i != hdr->e_shnum; ++i) {
			if (shdr[i].sh_type == SHT_DYNAMIC)
				dynhdr = shdr+i;
			else if (shdr[i].sh_type == SHT_STRTAB)
				strhdr = shdr+i;
		}

		if (!dynhdr) {
			log(Debug, "no dynamic section");
			return;
		}
		if (!strhdr) {
			log(Debug, "no string table section");
			return;
		}

		if (dynhdr->sh_entsize != sizeof(Dyn)) {
			log(Error, "invalid entsize for dynamic section\n");
			error = true;
			return;
		}

		dyncount = dynhdr->sh_size / sizeof(Dyn);
	}

	// can now read the dynamic section, and the string table
};

using Elf32 = ElfImpl<Elf32_Ehdr, Elf32_Shdr, Elf32_Dyn>;
using Elf64 = ElfImpl<Elf64_Ehdr, Elf64_Shdr, Elf64_Dyn>;

Elf* Elf::open(const char *data, size_t size)
{
	unsigned char *elf_ident = (unsigned char*)data;
	if (elf_ident[EI_MAG0] != ELFMAG0 ||
	    elf_ident[EI_MAG1] != ELFMAG1 ||
	    elf_ident[EI_MAG2] != ELFMAG2 ||
	    elf_ident[EI_MAG3] != ELFMAG3)
	{
		log(Debug, "not an ELF file\n");
		return 0;
	}

	unsigned char ei_class   = elf_ident[EI_CLASS];
	unsigned char ei_version = elf_ident[EI_VERSION];
	unsigned char ei_osabi   = elf_ident[EI_OSABI];

	if (ei_version != EV_CURRENT) {
		log(Error, "invalid ELF version: %u\n", (unsigned)ei_version);
		return 0;
	}

	if (ei_osabi != ELFOSABI_FREEBSD &&
	    ei_osabi != ELFOSABI_LINUX   &&
	    ei_osabi != ELFOSABI_NONE)
	{
		log(Warn, "osabi not recognized: %u\n", (unsigned)ei_osabi);
	}

	if (ei_class == ELFCLASS32)
		return new Elf32(data, size);
	else if (ei_class == ELFCLASS64)
		return new Elf64(data, size);
	else {
		log(Error, "unrecognized ELF class: %u\n", (unsigned)ei_class);
		return 0;
	}
}

Elf::Elf(const char *data_, size_t size_)
: error(false), data(data_), size(size_)
{
	elf_ident = (unsigned char*)data;
	if (elf_ident[EI_MAG0] != ELFMAG0 ||
	    elf_ident[EI_MAG1] != ELFMAG1 ||
	    elf_ident[EI_MAG2] != ELFMAG2 ||
	    elf_ident[EI_MAG3] != ELFMAG3)
	{
		error = true;
		return;
	}

	ei_class      = elf_ident[EI_CLASS];
	ei_data       = elf_ident[EI_DATA];
	ei_version    = elf_ident[EI_VERSION];
	ei_osabi      = elf_ident[EI_OSABI];
	ei_abiversion = elf_ident[EI_ABIVERSION];
}
<|endoftext|>


<commit_before>/*
 * fIcy - HTTP/1.0-ICY stream extractor/separator - implementation
 * Copyright(c) 2003-2004 of wave++ (Yuri D'Elia) <wavexx@users.sf.net>
 * Distributed under GNU LGPL without ANY warranty.
 */

// local headers
#include "fIcy.hh"
#include "icy.hh"
#include "http.hh"
#include "hdrparse.hh"
#include "sanitize.hh"
#include "urlparse.hh"
#include "rewrite.hh"
#include "match.hh"
#include "msg.hh"
using std::string;
using std::map;

// system headers
#include <iostream>
using std::cout;
using std::cerr;

#include <fstream>
using std::ofstream;

#include <stdexcept>
using std::runtime_error;

#include <limits>
#include <memory>
using std::auto_ptr;

// c system headers
#include <cstdlib>
#include <sys/types.h>
#include <stdarg.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <time.h>

// constants (urgh)
bool dupStdout(true);
char* lastFName(NULL);

void
shwIcyHdr(const map<string, string>& headers, const char* search,
    const char* name)
{
  map<string, string>::const_iterator it;
  if((it = headers.find(search)) != headers.end())
    cerr << name << it->second << std::endl;
}

// this helper function returns a new file opened for writing. errors during
// open or creat are (eventually) thrown as runtime_errors.
ofstream*
newFWrap(const char* file, const bool clobber, const bool ign)
{
  if(!clobber && !access(file, F_OK))
  {
    if(ign)
      return NULL;
    else
      throw runtime_error(string("cannot clobber existing file: ") + file);
  }

  ofstream* out(new ofstream(file, std::ios_base::out));
  if(!*out)
  {
    delete out;
    throw runtime_error(string("cannot write `") + file + "'");
  }

  return out;
}

// a wrapper to the wrapper: if a file exists retry with a new file with
// an incremental number appended. changes file to the real name of the file
ofstream*
newNFWrap(string& file, const bool ign)
{
  ofstream* out(newFWrap(file.c_str(), false, true));
  if(!out)
  {
    char buf[16];
    for(size_t n = 0; n != std::numeric_limits<size_t>::max(); ++n)
    {
      snprintf(buf, sizeof(buf), ".%lu", n);
      if((out = newFWrap((file + buf).c_str(), false, true)))
      {
        file += buf;
        break;
      }
    }

    if(!(out || ign))
      throw runtime_error(string("no free files for `") + file + "'");
  }

  return out;
}

// SIGPIPE handler
void
sigPipe(const int)
{
  msg("broken pipe: disabling duping.");
  dupStdout = false;

  // close the descriptor: this is needed to release the resource
  close(STDOUT_FILENO);
}

// termination handler
void
sigTerm(const int)
{
  if(lastFName)
  {
    msg("removing incomplete last file: %s", lastFName);
    unlink(lastFName);
  }
  exit(Exit::success);
}

// signal installers
void
sigPipeInst()
{
  signal(SIGPIPE, SIG_IGN);
}

void
sigTermInst(const bool handlePipe)
{
  signal(SIGTERM, sigTerm);
  signal(SIGINT, sigTerm);
  signal(SIGHUP, sigTerm);
  if(handlePipe)
    signal(SIGPIPE, sigTerm);
}

// current song status
std::ostream&
hour(std::ostream& fd)
{
  fd.setf(std::ios_base::right);
  fd.width(2);
  fd.fill('0');

  return fd;
}

/*
 * function naming scheme: yep. I'm again in a new coding style internal fight.
 * But this time, I've been seriously tainted by functional/logical programming
 * so be patient. When I'll come up with clear ideas, I will uniform the code.
 */
time_t
display_status(const string& title, const size_t num, const time_t last)
{
  time_t now(time(NULL));

  if(last)
  {
    // show the song duration
    cerr << " [" << hour << ((now - last) / 60) << ":" <<
      hour << ((now - last) % 60) << "]\n";
  }

  cerr << "playing #" << num << ": " << title << std::flush;
  return now;
}

// update the sequence file
void
write_seq(ofstream& out, const char* file)
{
  if(out.rdbuf()->is_open())
  {
    out << file << std::endl;
    if(!out)
      throw runtime_error("cannot append to sequence file");
  }
}

// implementation
int
main(int argc, char* const argv[]) try
{
  // option defaults
  prg = argv[0];

  char* outFile(NULL);
  char* suffix(NULL);
  char* sedFile(NULL);
  ofstream seq;
  bool enuFiles(false);
  bool useMeta(false);
  bool showMeta(false);
  bool clobber(true);
  bool ignFErr(true);
  bool numEFiles(false);
  bool instSignal(false);
  bool rmPartial(false);
  BMatch match;

  int arg;
  while((arg = getopt(argc, argv, "do:emvtcs:inprhq:x:X:f:")) != -1)
    switch(arg)
    {
    case 'd':
      dupStdout = false;
      break;

    case 'o':
      outFile = optarg;
      break;

    case 'e':
      enuFiles = true;
      break;

    case 'm':
      useMeta = true;
      break;

    case 'v':
      verbose = true;
      break;

    case 't':
      showMeta = true;
      break;

    case 'c':
      clobber = false;
      break;

    case 's':
      suffix = optarg;
      break;

    case 'i':
      clobber = false;
      ignFErr = false;
      break;

    case 'n':
      numEFiles = true;
      break;

    case 'p':
      instSignal = true;
      break;

    case 'r':
      rmPartial = true;
      break;

    case 'q':
      seq.open(optarg, std::ios_base::out | std::ios_base::app);
      if(!seq)
      {
	err("cannot open `%s' for appending", optarg);
	return Exit::fail;
      }
      break;

    case 'x':
      match.include(optarg);
      break;

    case 'X':
      match.exclude(optarg);
      break;

    case 'f':
      sedFile = optarg;
      break;

    case 'h':
      cout << prg << fIcy::fIcyHelp << prg << " v" << fIcy::version <<
        " is\n" << fIcy::copyright;

    default:
      return Exit::args;
    }

  argc -= optind;
  if(argc < 1 || argc > 3)
  {
    err("bad parameters; see %s -h", prg);
    return Exit::args;
  }

  // connection parameters
  string proto;
  string server;
  int port;
  string path;
  if(argc > 1)
  {
    server = argv[optind++];
    port = atoi(argv[optind++]);
    path = (argc == 3? argv[optind++]: "/");
  }
  else
  {
    urlParse(proto, server, port, path, argv[optind++]);
    if(proto.size() && proto != "http" && proto != "icy")
    {
      err("unknown protocol \"%s\"", proto.c_str());
      return Exit::args;
    }
  }

  // check for parameters consistency
  // enuFiles and useMeta requires a prefix
  bool reqMeta(enuFiles || useMeta || showMeta);
  if((useMeta || enuFiles) && !outFile)
  {
    err("a prefix is required (see -o) when writing multiple files");
    return Exit::args;
  }

  // you cannot disable duping if you don't write anything!
  if(!(outFile || dupStdout))
  {
    err("trying to perform a do-nothing download");
    return Exit::args;
  }

  // spawn the rewrite coprocess before installing signals
  // TODO: we should probably mask the signal now
  Rewrite rewrite(sedFile);

  // install the signals
  instSignal = instSignal && dupStdout;
  if(instSignal)
    sigPipeInst();
  if(rmPartial && (enuFiles || useMeta))
    sigTermInst(!instSignal);

  // resolve the hostname
  msg("connecting to (%s %d)", server.c_str(), port);
  Http::Http httpc(server.c_str(), port);

  // setup headers
  Http::Header qHeaders;
  Http::Header aHeaders;
  Http::Reply reply(&aHeaders);

  // query
  qHeaders.push_back(fIcy::userAgent);
  if(reqMeta)
    qHeaders.push_back(ICY::Proto::reqMeta);

  msg("requesting stream on (%s)", path.c_str());
  auto_ptr<Socket> s(httpc.get(path.c_str(), reply, &qHeaders));

  // validate the reply
  if(reply.code != Http::Proto::ok)
  {
    err("unexpected reply: %d %s", reply.code,
	sanitize_esc(
	    (reply.description.size()? reply.description: reply.proto)
	    ).c_str());
    return Exit::fail;
  }

  map<string, string> pReply(Http::hdrParse(aHeaders));
  if(reqMeta && pReply.find(ICY::Proto::metaint) == pReply.end())
  {
    err("requested metadata, but got nothing.");
    return Exit::fail;
  }

  // show some headers
  if(verbose)
  {
    shwIcyHdr(pReply, ICY::Proto::notice1, "Notice: ");
    shwIcyHdr(pReply, ICY::Proto::notice2, "Notice: ");
    shwIcyHdr(pReply, ICY::Proto::title, "Title: ");
    shwIcyHdr(pReply, ICY::Proto::genre, "Genre: ");
    shwIcyHdr(pReply, ICY::Proto::url, "URL: ");
    shwIcyHdr(pReply, ICY::Proto::br, "Bit Rate: ");
  }

  // start reading
  ssize_t metaInt(reqMeta?
      atol(pReply.find(ICY::Proto::metaint)->second.c_str()): fIcy::bufSz);
  ICY::Reader reader(*s, fIcy::bufSz);
  size_t enu(0);
  time_t tStamp(0);
  string lastTitle;

  // initial file
  auto_ptr<std::ostream> out;
  if(outFile && !(enuFiles || useMeta))
    out.reset(newFWrap(outFile, clobber, false));
  else
    // the first filename is unknown as the metadata block will
    // arrive in the next metaInt bytes
    out.reset();

  for(;;)
  {
    if(dupStdout && !cout)
    {
      // try an empty write to determine if the file is ready
      if(!write(STDOUT_FILENO, NULL, 0))
	cout.clear();
    }

    // read the stream
    if(reader.dup(out.get(), metaInt, dupStdout) != metaInt)
    {
      msg("connection terminated");
      break;
    }
    if(!(outFile || dupStdout))
      break;

    // read metadata
    if(reqMeta)
    {
      map<string, string> data;
      if(reader.readMeta(data))
      {
	map<string, string>::const_iterator it(
	    data.find(ICY::Proto::mTitle));

	// de-uglify
	string title;
	if(it != data.end())
	{
	  // sanitize immediately, we don't want \n for sed
	  title = sanitize_esc(it->second);
	  rewrite(title);
	}

	if(title.size() && (title != lastTitle))
	{
	  string newFName;

	  if(showMeta)
	    tStamp = display_status(title, enu, tStamp);

	  // skip the first filename generation when discarding partials
	  // or when the title doesn't match
	  if((enuFiles || useMeta) && (enu || !rmPartial) &&
	      match(title.c_str()))
	  {
	    newFName = outFile;

	    if(enuFiles)
	    {
	      char buf[16];
	      snprintf(buf, sizeof(buf), (useMeta? "[%lu] ": "%lu"), enu);
	      newFName += buf;
	    }
	    if(useMeta)
	      newFName += sanitize_file(title);
	    if(suffix)
	      newFName += suffix;

	    // open the new file
	    if(numEFiles)
	      out.reset(newNFWrap(newFName, ignFErr));
	    else
	      out.reset(newFWrap(newFName.c_str(), clobber, ignFErr));
	  }
	  else
	    out.reset();

	  // update the last filename pointer
	  if(lastFName)
	    free(lastFName);
	  if(out.get())
	  {
	    lastFName = strdup(newFName.c_str());
	    write_seq(seq, lastFName);
	    msg("file changed to: %s", lastFName);
	  }
	  else if(lastFName)
	  {
	    lastFName = NULL;
	    msg("file reset");
	  }

	  // update stream number
	  lastTitle = title;
	  ++enu;
	}
      }
    }
  }

  return Exit::success;
}
catch(runtime_error& err)
{
  ::err("%s", err.what());
  return Exit::fail;
}
catch(...)
{
  err("unknown error, aborting");
  abort();
}
<commit_msg>Do not bind SIGPIPE to sigTerm when there's a coprocess, otherwise a fail would result in a 0 exit status. This is still not perfect.<commit_after>/*
 * fIcy - HTTP/1.0-ICY stream extractor/separator - implementation
 * Copyright(c) 2003-2004 of wave++ (Yuri D'Elia) <wavexx@users.sf.net>
 * Distributed under GNU LGPL without ANY warranty.
 */

// local headers
#include "fIcy.hh"
#include "icy.hh"
#include "http.hh"
#include "hdrparse.hh"
#include "sanitize.hh"
#include "urlparse.hh"
#include "rewrite.hh"
#include "match.hh"
#include "msg.hh"
using std::string;
using std::map;

// system headers
#include <iostream>
using std::cout;
using std::cerr;

#include <fstream>
using std::ofstream;

#include <stdexcept>
using std::runtime_error;

#include <limits>
#include <memory>
using std::auto_ptr;

// c system headers
#include <cstdlib>
#include <sys/types.h>
#include <stdarg.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <time.h>

// constants (urgh)
bool dupStdout(true);
char* lastFName(NULL);

void
shwIcyHdr(const map<string, string>& headers, const char* search,
    const char* name)
{
  map<string, string>::const_iterator it;
  if((it = headers.find(search)) != headers.end())
    cerr << name << it->second << std::endl;
}

// this helper function returns a new file opened for writing. errors during
// open or creat are (eventually) thrown as runtime_errors.
ofstream*
newFWrap(const char* file, const bool clobber, const bool ign)
{
  if(!clobber && !access(file, F_OK))
  {
    if(ign)
      return NULL;
    else
      throw runtime_error(string("cannot clobber existing file: ") + file);
  }

  ofstream* out(new ofstream(file, std::ios_base::out));
  if(!*out)
  {
    delete out;
    throw runtime_error(string("cannot write `") + file + "'");
  }

  return out;
}

// a wrapper to the wrapper: if a file exists retry with a new file with
// an incremental number appended. changes file to the real name of the file
ofstream*
newNFWrap(string& file, const bool ign)
{
  ofstream* out(newFWrap(file.c_str(), false, true));
  if(!out)
  {
    char buf[16];
    for(size_t n = 0; n != std::numeric_limits<size_t>::max(); ++n)
    {
      snprintf(buf, sizeof(buf), ".%lu", n);
      if((out = newFWrap((file + buf).c_str(), false, true)))
      {
        file += buf;
        break;
      }
    }

    if(!(out || ign))
      throw runtime_error(string("no free files for `") + file + "'");
  }

  return out;
}

// SIGPIPE handler
void
sigPipe(const int)
{
  msg("broken pipe: disabling duping.");
  dupStdout = false;

  // close the descriptor: this is needed to release the resource
  close(STDOUT_FILENO);
}

// termination handler
void
sigTerm(const int)
{
  if(lastFName)
  {
    msg("removing incomplete last file: %s", lastFName);
    unlink(lastFName);
  }
  exit(Exit::success);
}

// signal installers
void
sigPipeInst()
{
  signal(SIGPIPE, SIG_IGN);
}

void
sigTermInst(const bool handlePipe)
{
  signal(SIGTERM, sigTerm);
  signal(SIGINT, sigTerm);
  signal(SIGHUP, sigTerm);
  if(handlePipe)
    signal(SIGPIPE, sigTerm);
}

// current song status
std::ostream&
hour(std::ostream& fd)
{
  fd.setf(std::ios_base::right);
  fd.width(2);
  fd.fill('0');

  return fd;
}

/*
 * function naming scheme: yep. I'm again in a new coding style internal fight.
 * But this time, I've been seriously tainted by functional/logical programming
 * so be patient. When I'll come up with clear ideas, I will uniform the code.
 */
time_t
display_status(const string& title, const size_t num, const time_t last)
{
  time_t now(time(NULL));

  if(last)
  {
    // show the song duration
    cerr << " [" << hour << ((now - last) / 60) << ":" <<
      hour << ((now - last) % 60) << "]\n";
  }

  cerr << "playing #" << num << ": " << title << std::flush;
  return now;
}

// update the sequence file
void
write_seq(ofstream& out, const char* file)
{
  if(out.rdbuf()->is_open())
  {
    out << file << std::endl;
    if(!out)
      throw runtime_error("cannot append to sequence file");
  }
}

// implementation
int
main(int argc, char* const argv[]) try
{
  // option defaults
  prg = argv[0];

  char* outFile(NULL);
  char* suffix(NULL);
  char* sedFile(NULL);
  ofstream seq;
  bool enuFiles(false);
  bool useMeta(false);
  bool showMeta(false);
  bool clobber(true);
  bool ignFErr(true);
  bool numEFiles(false);
  bool instSignal(false);
  bool rmPartial(false);
  BMatch match;

  int arg;
  while((arg = getopt(argc, argv, "do:emvtcs:inprhq:x:X:f:")) != -1)
    switch(arg)
    {
    case 'd':
      dupStdout = false;
      break;

    case 'o':
      outFile = optarg;
      break;

    case 'e':
      enuFiles = true;
      break;

    case 'm':
      useMeta = true;
      break;

    case 'v':
      verbose = true;
      break;

    case 't':
      showMeta = true;
      break;

    case 'c':
      clobber = false;
      break;

    case 's':
      suffix = optarg;
      break;

    case 'i':
      clobber = false;
      ignFErr = false;
      break;

    case 'n':
      numEFiles = true;
      break;

    case 'p':
      instSignal = true;
      break;

    case 'r':
      rmPartial = true;
      break;

    case 'q':
      seq.open(optarg, std::ios_base::out | std::ios_base::app);
      if(!seq)
      {
	err("cannot open `%s' for appending", optarg);
	return Exit::fail;
      }
      break;

    case 'x':
      match.include(optarg);
      break;

    case 'X':
      match.exclude(optarg);
      break;

    case 'f':
      sedFile = optarg;
      break;

    case 'h':
      cout << prg << fIcy::fIcyHelp << prg << " v" << fIcy::version <<
        " is\n" << fIcy::copyright;

    default:
      return Exit::args;
    }

  argc -= optind;
  if(argc < 1 || argc > 3)
  {
    err("bad parameters; see %s -h", prg);
    return Exit::args;
  }

  // connection parameters
  string proto;
  string server;
  int port;
  string path;
  if(argc > 1)
  {
    server = argv[optind++];
    port = atoi(argv[optind++]);
    path = (argc == 3? argv[optind++]: "/");
  }
  else
  {
    urlParse(proto, server, port, path, argv[optind++]);
    if(proto.size() && proto != "http" && proto != "icy")
    {
      err("unknown protocol \"%s\"", proto.c_str());
      return Exit::args;
    }
  }

  // check for parameters consistency
  // enuFiles and useMeta requires a prefix
  bool reqMeta(enuFiles || useMeta || showMeta);
  if((useMeta || enuFiles) && !outFile)
  {
    err("a prefix is required (see -o) when writing multiple files");
    return Exit::args;
  }

  // you cannot disable duping if you don't write anything!
  if(!(outFile || dupStdout))
  {
    err("trying to perform a do-nothing download");
    return Exit::args;
  }

  // spawn the rewrite coprocess before installing signals
  // TODO: we should probably mask the signal now instead!
  Rewrite rewrite(sedFile);

  // install the signals
  instSignal = (instSignal && dupStdout);
  if(instSignal)
    sigPipeInst();
  if(rmPartial && (enuFiles || useMeta))
    sigTermInst(!(instSignal || sedFile));

  // resolve the hostname
  msg("connecting to (%s %d)", server.c_str(), port);
  Http::Http httpc(server.c_str(), port);

  // setup headers
  Http::Header qHeaders;
  Http::Header aHeaders;
  Http::Reply reply(&aHeaders);

  // query
  qHeaders.push_back(fIcy::userAgent);
  if(reqMeta)
    qHeaders.push_back(ICY::Proto::reqMeta);

  msg("requesting stream on (%s)", path.c_str());
  auto_ptr<Socket> s(httpc.get(path.c_str(), reply, &qHeaders));

  // validate the reply
  if(reply.code != Http::Proto::ok)
  {
    err("unexpected reply: %d %s", reply.code,
	sanitize_esc(
	    (reply.description.size()? reply.description: reply.proto)
	    ).c_str());
    return Exit::fail;
  }

  map<string, string> pReply(Http::hdrParse(aHeaders));
  if(reqMeta && pReply.find(ICY::Proto::metaint) == pReply.end())
  {
    err("requested metadata, but got nothing.");
    return Exit::fail;
  }

  // show some headers
  if(verbose)
  {
    shwIcyHdr(pReply, ICY::Proto::notice1, "Notice: ");
    shwIcyHdr(pReply, ICY::Proto::notice2, "Notice: ");
    shwIcyHdr(pReply, ICY::Proto::title, "Title: ");
    shwIcyHdr(pReply, ICY::Proto::genre, "Genre: ");
    shwIcyHdr(pReply, ICY::Proto::url, "URL: ");
    shwIcyHdr(pReply, ICY::Proto::br, "Bit Rate: ");
  }

  // start reading
  ssize_t metaInt(reqMeta?
      atol(pReply.find(ICY::Proto::metaint)->second.c_str()): fIcy::bufSz);
  ICY::Reader reader(*s, fIcy::bufSz);
  size_t enu(0);
  time_t tStamp(0);
  string lastTitle;

  // initial file
  auto_ptr<std::ostream> out;
  if(outFile && !(enuFiles || useMeta))
    out.reset(newFWrap(outFile, clobber, false));
  else
    // the first filename is unknown as the metadata block will
    // arrive in the next metaInt bytes
    out.reset();

  for(;;)
  {
    if(dupStdout && !cout)
    {
      // try an empty write to determine if the file is ready
      if(!write(STDOUT_FILENO, NULL, 0))
	cout.clear();
    }

    // read the stream
    if(reader.dup(out.get(), metaInt, dupStdout) != metaInt)
    {
      msg("connection terminated");
      break;
    }
    if(!(outFile || dupStdout))
      break;

    // read metadata
    if(reqMeta)
    {
      map<string, string> data;
      if(reader.readMeta(data))
      {
	map<string, string>::const_iterator it(
	    data.find(ICY::Proto::mTitle));

	// de-uglify
	string title;
	if(it != data.end())
	{
	  // sanitize immediately, we don't want \n for sed
	  title = sanitize_esc(it->second);
	  rewrite(title);
	}

	if(title.size() && (title != lastTitle))
	{
	  string newFName;

	  if(showMeta)
	    tStamp = display_status(title, enu, tStamp);

	  // skip the first filename generation when discarding partials
	  // or when the title doesn't match
	  if((enuFiles || useMeta) && (enu || !rmPartial) &&
	      match(title.c_str()))
	  {
	    newFName = outFile;

	    if(enuFiles)
	    {
	      char buf[16];
	      snprintf(buf, sizeof(buf), (useMeta? "[%lu] ": "%lu"), enu);
	      newFName += buf;
	    }
	    if(useMeta)
	      newFName += sanitize_file(title);
	    if(suffix)
	      newFName += suffix;

	    // open the new file
	    if(numEFiles)
	      out.reset(newNFWrap(newFName, ignFErr));
	    else
	      out.reset(newFWrap(newFName.c_str(), clobber, ignFErr));
	  }
	  else
	    out.reset();

	  // update the last filename pointer
	  if(lastFName)
	    free(lastFName);
	  if(out.get())
	  {
	    lastFName = strdup(newFName.c_str());
	    write_seq(seq, lastFName);
	    msg("file changed to: %s", lastFName);
	  }
	  else if(lastFName)
	  {
	    lastFName = NULL;
	    msg("file reset");
	  }

	  // update stream number
	  lastTitle = title;
	  ++enu;
	}
      }
    }
  }

  return Exit::success;
}
catch(runtime_error& err)
{
  ::err("%s", err.what());
  return Exit::fail;
}
catch(...)
{
  err("unknown error, aborting");
  abort();
}
<|endoftext|>


<commit_before>/*
-----------------------------------------------------------------------------
This source file is part of OGRE
    (Object-oriented Graphics Rendering Engine)
For the latest info, see http://www.ogre3d.org/

Copyright (c) 2000-2006 Torus Knot Software Ltd
Also see acknowledgements in Readme.html

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA, or go to
http://www.gnu.org/copyleft/lesser.txt.

You may alternatively use this source under the terms of a specific version of
the OGRE Unrestricted License provided you have obtained such a license from
Torus Knot Software Ltd.
-----------------------------------------------------------------------------
*/
#include "OgreStableHeaders.h"
#include "OgreCompositorChain.h"
#include "OgreCompositionTechnique.h"
#include "OgreCompositorInstance.h"
#include "OgreCompositionTargetPass.h"
#include "OgreCompositionPass.h"
#include "OgreViewport.h"
#include "OgreCamera.h"
#include "OgreRenderTarget.h"
#include "OgreLogManager.h"
#include "OgreCompositorManager.h"
#include "OgreSceneManager.h"
#include "OgreRenderQueueInvocation.h"
namespace Ogre {
CompositorChain::CompositorChain(Viewport *vp):
    mViewport(vp),
	mOriginalScene(0),
    mDirty(true),
	mAnyCompositorsEnabled(false)
{
	mOldClearEveryFrameBuffers = mViewport->getClearBuffers();
    assert(mViewport);
}
//-----------------------------------------------------------------------
CompositorChain::~CompositorChain()
{
	destroyResources();
}
//-----------------------------------------------------------------------
void CompositorChain::destroyResources(void)
{
	clearCompiledState();

	if (mViewport)
	{
		removeAllCompositors();
		mViewport->getTarget()->removeListener(this);
		/// Destroy "original scene" compositor instance
		if (mOriginalScene)
		{
			mOriginalScene->getTechnique()->destroyInstance(mOriginalScene);
			mOriginalScene = 0;
		}
		mViewport = 0;
	}
}
//-----------------------------------------------------------------------
CompositorInstance* CompositorChain::addCompositor(CompositorPtr filter, size_t addPosition, size_t technique)
{
	// Init on demand
	if (!mOriginalScene)
	{
		mViewport->getTarget()->addListener(this);

		/// Create base "original scene" compositor
		CompositorPtr base = CompositorManager::getSingleton().load("Ogre/Scene",
			ResourceGroupManager::INTERNAL_RESOURCE_GROUP_NAME);
		mOriginalScene = base->getSupportedTechnique(0)->createInstance(this);
	}

	filter->touch();
    if(technique >= filter->getNumSupportedTechniques())
    {
        /// Warn user
        LogManager::getSingleton().logMessage(
            "CompositorChain: Compositor " + filter->getName() + " has no supported techniques.", LML_CRITICAL
        );
        return 0;
    }
    CompositionTechnique *tech = filter->getSupportedTechnique(technique);
    CompositorInstance *t = tech->createInstance(this);

    if(addPosition == LAST)
        addPosition = mInstances.size();
    else
        assert(addPosition <= mInstances.size() && "Index out of bounds.");
    mInstances.insert(mInstances.begin()+addPosition, t);

    mDirty = true;
	mAnyCompositorsEnabled = true;
    return t;
}
//-----------------------------------------------------------------------
void CompositorChain::removeCompositor(size_t index)
{
    assert (index < mInstances.size() && "Index out of bounds.");
    Instances::iterator i = mInstances.begin() + index;
    (*i)->getTechnique()->destroyInstance(*i);
    mInstances.erase(i);

    mDirty = true;
}
//-----------------------------------------------------------------------
size_t CompositorChain::getNumCompositors()
{
    return mInstances.size();
}
//-----------------------------------------------------------------------
void CompositorChain::removeAllCompositors()
{
    Instances::iterator i, iend;
    iend = mInstances.end();
    for (i = mInstances.begin(); i != iend; ++i)
    {
        (*i)->getTechnique()->destroyInstance(*i);
    }
    mInstances.clear();

    mDirty = true;
}
//-----------------------------------------------------------------------
void CompositorChain::_removeInstance(CompositorInstance *i)
{
	mInstances.erase(std::find(mInstances.begin(), mInstances.end(), i));
	i->getTechnique()->destroyInstance(i);
}
//-----------------------------------------------------------------------
void CompositorChain::_queuedOperation(CompositorInstance::RenderSystemOperation* op)
{
	mRenderSystemOperations.push_back(op);

}
//-----------------------------------------------------------------------
CompositorInstance *CompositorChain::getCompositor(size_t index)
{
    assert (index < mInstances.size() && "Index out of bounds.");
    return mInstances[index];
}

//-----------------------------------------------------------------------
CompositorChain::InstanceIterator CompositorChain::getCompositors()
{
    return InstanceIterator(mInstances.begin(), mInstances.end());
}
//-----------------------------------------------------------------------
void CompositorChain::setCompositorEnabled(size_t position, bool state)
{
    getCompositor(position)->setEnabled(state);
}
//-----------------------------------------------------------------------
void CompositorChain::preRenderTargetUpdate(const RenderTargetEvent& evt)
{
	/// Compile if state is dirty
	if(mDirty)
		_compile();

	// Do nothing if no compositors enabled
	if (!mAnyCompositorsEnabled)
	{
		return;
	}

	/// Update dependent render targets; this is done in the preRenderTarget
	/// and not the preViewportUpdate for a reason: at this time, the
	/// target Rendertarget will not yet have been set as current.
	/// ( RenderSystem::setViewport(...) ) if it would have been, the rendering
	/// order would be screwed up and problems would arise with copying rendertextures.
    Camera *cam = mViewport->getCamera();
    /// Iterate over compiled state
    CompositorInstance::CompiledState::iterator i;
    for(i=mCompiledState.begin(); i!=mCompiledState.end(); ++i)
    {
		/// Skip if this is a target that should only be initialised initially
		if(i->onlyInitial && i->hasBeenRendered)
			continue;
		i->hasBeenRendered = true;
		/// Setup and render
        preTargetOperation(*i, i->target->getViewport(0), cam);
        i->target->update();
        postTargetOperation(*i, i->target->getViewport(0), cam);
    }
}
//-----------------------------------------------------------------------
void CompositorChain::preViewportUpdate(const RenderTargetViewportEvent& evt)
{
	// Only set up if there is at least one compositor enabled, and it's this viewport
    if(evt.source != mViewport || !mAnyCompositorsEnabled)
        return;

	// set original scene details from viewport
	CompositionPass* pass = mOriginalScene->getTechnique()->getOutputTargetPass()->getPass(0);
	CompositionTargetPass* passParent = pass->getParent();
	if (pass->getClearBuffers() != mViewport->getClearBuffers() ||
		pass->getClearColour() != mViewport->getBackgroundColour() ||
		passParent->getVisibilityMask() != mViewport->getVisibilityMask() ||
		passParent->getMaterialScheme() != mViewport->getMaterialScheme() ||
		passParent->getShadowsEnabled() != mViewport->getShadowsEnabled())
	{
		// recompile if viewport settings are different
		pass->setClearBuffers(mViewport->getClearBuffers());
		pass->setClearColour(mViewport->getBackgroundColour());
		passParent->setVisibilityMask(mViewport->getVisibilityMask());
		passParent->setMaterialScheme(mViewport->getMaterialScheme());
		passParent->setShadowsEnabled(mViewport->getShadowsEnabled());
		_compile();
	}

	Camera *cam = mViewport->getCamera();
	/// Prepare for output operation
	preTargetOperation(mOutputOperation, mViewport, cam);
}
//-----------------------------------------------------------------------
void CompositorChain::preTargetOperation(CompositorInstance::TargetOperation &op, Viewport *vp, Camera *cam)
{
    SceneManager *sm = cam->getSceneManager();
	/// Set up render target listener
	mOurListener.setOperation(&op, sm, sm->getDestinationRenderSystem());
	mOurListener.notifyViewport(vp);
	/// Register it
	sm->addRenderQueueListener(&mOurListener);
	/// Set visiblity mask
	mOldVisibilityMask = sm->getVisibilityMask();
	sm->setVisibilityMask(op.visibilityMask);
	/// Set whether we find visibles
	mOldFindVisibleObjects = sm->getFindVisibleObjects();
	sm->setFindVisibleObjects(op.findVisibleObjects);
    /// Set LOD bias level
    mOldLodBias = cam->getLodBias();
    cam->setLodBias(cam->getLodBias() * op.lodBias);
	/// Set material scheme
	mOldMaterialScheme = vp->getMaterialScheme();
	vp->setMaterialScheme(op.materialScheme);
	/// Set shadows enabled
	mOldShadowsEnabled = vp->getShadowsEnabled();
	vp->setShadowsEnabled(op.shadowsEnabled);
    /// XXX TODO
    //vp->setClearEveryFrame( true );
    //vp->setOverlaysEnabled( false );
    //vp->setBackgroundColour( op.clearColour );
}
//-----------------------------------------------------------------------
void CompositorChain::postTargetOperation(CompositorInstance::TargetOperation &op, Viewport *vp, Camera *cam)
{
    SceneManager *sm = cam->getSceneManager();
	/// Unregister our listener
	sm->removeRenderQueueListener(&mOurListener);
	/// Restore default scene and camera settings
	sm->setVisibilityMask(mOldVisibilityMask);
	sm->setFindVisibleObjects(mOldFindVisibleObjects);
    cam->setLodBias(mOldLodBias);
	vp->setMaterialScheme(mOldMaterialScheme);
	vp->setShadowsEnabled(mOldShadowsEnabled);
}
//-----------------------------------------------------------------------
void CompositorChain::postViewportUpdate(const RenderTargetViewportEvent& evt)
{
	// Only tidy up if there is at least one compositor enabled, and it's this viewport
    if(evt.source != mViewport || !mAnyCompositorsEnabled)
        return;

	postTargetOperation(mOutputOperation, mViewport, mViewport->getCamera());
}
//-----------------------------------------------------------------------
void CompositorChain::viewportRemoved(const RenderTargetViewportEvent& evt)
{
	// check this is the viewport we're attached to (multi-viewport targets)
	if (evt.source == mViewport)
	{
		// this chain is now orphaned
		// can't delete it since held from outside, but release all resources being used
		destroyResources();
	}

}
//-----------------------------------------------------------------------
void CompositorChain::clearCompiledState()
{
	for (RenderSystemOperations::iterator i = mRenderSystemOperations.begin();
		i != mRenderSystemOperations.end(); ++i)
	{
		OGRE_DELETE *i;
	}
	mRenderSystemOperations.clear();

	/// Clear compiled state
	mCompiledState.clear();
	mOutputOperation = CompositorInstance::TargetOperation(0);

}
//-----------------------------------------------------------------------
void CompositorChain::_compile()
{
	clearCompiledState();

	bool compositorsEnabled = false;

    /// Set previous CompositorInstance for each compositor in the list
    CompositorInstance *lastComposition = mOriginalScene;
	mOriginalScene->mPreviousInstance = 0;
	CompositionPass* pass = mOriginalScene->getTechnique()->getOutputTargetPass()->getPass(0);
	pass->setClearBuffers(mViewport->getClearBuffers());
	pass->setClearColour(mViewport->getBackgroundColour());
    for(Instances::iterator i=mInstances.begin(); i!=mInstances.end(); ++i)
    {
        if((*i)->getEnabled())
        {
			compositorsEnabled = true;
            (*i)->mPreviousInstance = lastComposition;
            lastComposition = (*i);
        }
    }

    /// Compile misc targets
    lastComposition->_compileTargetOperations(mCompiledState);

    /// Final target viewport (0)
	mOutputOperation.renderSystemOperations.clear();
    lastComposition->_compileOutputOperation(mOutputOperation);

	// Deal with viewport settings
	if (compositorsEnabled != mAnyCompositorsEnabled)
	{
		mAnyCompositorsEnabled = compositorsEnabled;
		if (mAnyCompositorsEnabled)
		{
			// Save old viewport clearing options
			mOldClearEveryFrameBuffers = mViewport->getClearBuffers();
			// Don't clear anything every frame since we have our own clear ops
			mViewport->setClearEveryFrame(false);
		}
		else
		{
			// Reset clearing options
			mViewport->setClearEveryFrame(mOldClearEveryFrameBuffers > 0,
				mOldClearEveryFrameBuffers);
		}
	}

    mDirty = false;
}
//-----------------------------------------------------------------------
void CompositorChain::_markDirty()
{
    mDirty = true;
}
//-----------------------------------------------------------------------
Viewport *CompositorChain::getViewport()
{
    return mViewport;
}
//---------------------------------------------------------------------
void CompositorChain::_notifyViewport(Viewport* vp)
{
	mViewport = vp;
}
//-----------------------------------------------------------------------
void CompositorChain::RQListener::renderQueueStarted(uint8 id,
	const String& invocation, bool& skipThisQueue)
{
	// Skip when not matching viewport
	// shadows update is nested within main viewport update
	if (mSceneManager->getCurrentViewport() != mViewport)
		return;

	flushUpTo(id);
	/// If noone wants to render this queue, skip it
	/// Don't skip the OVERLAY queue because that's handled seperately
	if(!mOperation->renderQueues.test(id) && id!=RENDER_QUEUE_OVERLAY)
	{
		skipThisQueue = true;
	}
}
//-----------------------------------------------------------------------
void CompositorChain::RQListener::renderQueueEnded(uint8 id,
	const String& invocation, bool& repeatThisQueue)
{
}
//-----------------------------------------------------------------------
void CompositorChain::RQListener::setOperation(CompositorInstance::TargetOperation *op,SceneManager *sm,RenderSystem *rs)
{
	mOperation = op;
	mSceneManager = sm;
	mRenderSystem = rs;
	currentOp = op->renderSystemOperations.begin();
	lastOp = op->renderSystemOperations.end();
}
//-----------------------------------------------------------------------
void CompositorChain::RQListener::flushUpTo(uint8 id)
{
	/// Process all RenderSystemOperations up to and including render queue id.
    /// Including, because the operations for RenderQueueGroup x should be executed
	/// at the beginning of the RenderQueueGroup render for x.
	while(currentOp != lastOp && currentOp->first <= id)
	{
		currentOp->second->execute(mSceneManager, mRenderSystem);
		++currentOp;
	}
}
//-----------------------------------------------------------------------
}
<commit_msg>Patch 2633429: fix a crash when a compositor chain doesn't have a compositor attached to it<commit_after>/*
-----------------------------------------------------------------------------
This source file is part of OGRE
    (Object-oriented Graphics Rendering Engine)
For the latest info, see http://www.ogre3d.org/

Copyright (c) 2000-2006 Torus Knot Software Ltd
Also see acknowledgements in Readme.html

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA, or go to
http://www.gnu.org/copyleft/lesser.txt.

You may alternatively use this source under the terms of a specific version of
the OGRE Unrestricted License provided you have obtained such a license from
Torus Knot Software Ltd.
-----------------------------------------------------------------------------
*/
#include "OgreStableHeaders.h"
#include "OgreCompositorChain.h"
#include "OgreCompositionTechnique.h"
#include "OgreCompositorInstance.h"
#include "OgreCompositionTargetPass.h"
#include "OgreCompositionPass.h"
#include "OgreViewport.h"
#include "OgreCamera.h"
#include "OgreRenderTarget.h"
#include "OgreLogManager.h"
#include "OgreCompositorManager.h"
#include "OgreSceneManager.h"
#include "OgreRenderQueueInvocation.h"
namespace Ogre {
CompositorChain::CompositorChain(Viewport *vp):
    mViewport(vp),
	mOriginalScene(0),
    mDirty(true),
	mAnyCompositorsEnabled(false)
{
	mOldClearEveryFrameBuffers = mViewport->getClearBuffers();
    assert(mViewport);
}
//-----------------------------------------------------------------------
CompositorChain::~CompositorChain()
{
	destroyResources();
}
//-----------------------------------------------------------------------
void CompositorChain::destroyResources(void)
{
	clearCompiledState();

	if (mViewport)
	{
		removeAllCompositors();
		/// Destroy "original scene" compositor instance
		if (mOriginalScene)
		{
			mViewport->getTarget()->removeListener(this);
			mOriginalScene->getTechnique()->destroyInstance(mOriginalScene);
			mOriginalScene = 0;
		}
		mViewport = 0;
	}
}
//-----------------------------------------------------------------------
CompositorInstance* CompositorChain::addCompositor(CompositorPtr filter, size_t addPosition, size_t technique)
{
	// Init on demand
	if (!mOriginalScene)
	{
		mViewport->getTarget()->addListener(this);

		/// Create base "original scene" compositor
		CompositorPtr base = CompositorManager::getSingleton().load("Ogre/Scene",
			ResourceGroupManager::INTERNAL_RESOURCE_GROUP_NAME);
		mOriginalScene = base->getSupportedTechnique(0)->createInstance(this);
	}

	filter->touch();
    if(technique >= filter->getNumSupportedTechniques())
    {
        /// Warn user
        LogManager::getSingleton().logMessage(
            "CompositorChain: Compositor " + filter->getName() + " has no supported techniques.", LML_CRITICAL
        );
        return 0;
    }
    CompositionTechnique *tech = filter->getSupportedTechnique(technique);
    CompositorInstance *t = tech->createInstance(this);

    if(addPosition == LAST)
        addPosition = mInstances.size();
    else
        assert(addPosition <= mInstances.size() && "Index out of bounds.");
    mInstances.insert(mInstances.begin()+addPosition, t);

    mDirty = true;
	mAnyCompositorsEnabled = true;
    return t;
}
//-----------------------------------------------------------------------
void CompositorChain::removeCompositor(size_t index)
{
    assert (index < mInstances.size() && "Index out of bounds.");
    Instances::iterator i = mInstances.begin() + index;
    (*i)->getTechnique()->destroyInstance(*i);
    mInstances.erase(i);

    mDirty = true;
}
//-----------------------------------------------------------------------
size_t CompositorChain::getNumCompositors()
{
    return mInstances.size();
}
//-----------------------------------------------------------------------
void CompositorChain::removeAllCompositors()
{
    Instances::iterator i, iend;
    iend = mInstances.end();
    for (i = mInstances.begin(); i != iend; ++i)
    {
        (*i)->getTechnique()->destroyInstance(*i);
    }
    mInstances.clear();

    mDirty = true;
}
//-----------------------------------------------------------------------
void CompositorChain::_removeInstance(CompositorInstance *i)
{
	mInstances.erase(std::find(mInstances.begin(), mInstances.end(), i));
	i->getTechnique()->destroyInstance(i);
}
//-----------------------------------------------------------------------
void CompositorChain::_queuedOperation(CompositorInstance::RenderSystemOperation* op)
{
	mRenderSystemOperations.push_back(op);

}
//-----------------------------------------------------------------------
CompositorInstance *CompositorChain::getCompositor(size_t index)
{
    assert (index < mInstances.size() && "Index out of bounds.");
    return mInstances[index];
}

//-----------------------------------------------------------------------
CompositorChain::InstanceIterator CompositorChain::getCompositors()
{
    return InstanceIterator(mInstances.begin(), mInstances.end());
}
//-----------------------------------------------------------------------
void CompositorChain::setCompositorEnabled(size_t position, bool state)
{
    getCompositor(position)->setEnabled(state);
}
//-----------------------------------------------------------------------
void CompositorChain::preRenderTargetUpdate(const RenderTargetEvent& evt)
{
	/// Compile if state is dirty
	if(mDirty)
		_compile();

	// Do nothing if no compositors enabled
	if (!mAnyCompositorsEnabled)
	{
		return;
	}

	/// Update dependent render targets; this is done in the preRenderTarget
	/// and not the preViewportUpdate for a reason: at this time, the
	/// target Rendertarget will not yet have been set as current.
	/// ( RenderSystem::setViewport(...) ) if it would have been, the rendering
	/// order would be screwed up and problems would arise with copying rendertextures.
    Camera *cam = mViewport->getCamera();
    /// Iterate over compiled state
    CompositorInstance::CompiledState::iterator i;
    for(i=mCompiledState.begin(); i!=mCompiledState.end(); ++i)
    {
		/// Skip if this is a target that should only be initialised initially
		if(i->onlyInitial && i->hasBeenRendered)
			continue;
		i->hasBeenRendered = true;
		/// Setup and render
        preTargetOperation(*i, i->target->getViewport(0), cam);
        i->target->update();
        postTargetOperation(*i, i->target->getViewport(0), cam);
    }
}
//-----------------------------------------------------------------------
void CompositorChain::preViewportUpdate(const RenderTargetViewportEvent& evt)
{
	// Only set up if there is at least one compositor enabled, and it's this viewport
    if(evt.source != mViewport || !mAnyCompositorsEnabled)
        return;

	// set original scene details from viewport
	CompositionPass* pass = mOriginalScene->getTechnique()->getOutputTargetPass()->getPass(0);
	CompositionTargetPass* passParent = pass->getParent();
	if (pass->getClearBuffers() != mViewport->getClearBuffers() ||
		pass->getClearColour() != mViewport->getBackgroundColour() ||
		passParent->getVisibilityMask() != mViewport->getVisibilityMask() ||
		passParent->getMaterialScheme() != mViewport->getMaterialScheme() ||
		passParent->getShadowsEnabled() != mViewport->getShadowsEnabled())
	{
		// recompile if viewport settings are different
		pass->setClearBuffers(mViewport->getClearBuffers());
		pass->setClearColour(mViewport->getBackgroundColour());
		passParent->setVisibilityMask(mViewport->getVisibilityMask());
		passParent->setMaterialScheme(mViewport->getMaterialScheme());
		passParent->setShadowsEnabled(mViewport->getShadowsEnabled());
		_compile();
	}

	Camera *cam = mViewport->getCamera();
	/// Prepare for output operation
	preTargetOperation(mOutputOperation, mViewport, cam);
}
//-----------------------------------------------------------------------
void CompositorChain::preTargetOperation(CompositorInstance::TargetOperation &op, Viewport *vp, Camera *cam)
{
    SceneManager *sm = cam->getSceneManager();
	/// Set up render target listener
	mOurListener.setOperation(&op, sm, sm->getDestinationRenderSystem());
	mOurListener.notifyViewport(vp);
	/// Register it
	sm->addRenderQueueListener(&mOurListener);
	/// Set visiblity mask
	mOldVisibilityMask = sm->getVisibilityMask();
	sm->setVisibilityMask(op.visibilityMask);
	/// Set whether we find visibles
	mOldFindVisibleObjects = sm->getFindVisibleObjects();
	sm->setFindVisibleObjects(op.findVisibleObjects);
    /// Set LOD bias level
    mOldLodBias = cam->getLodBias();
    cam->setLodBias(cam->getLodBias() * op.lodBias);
	/// Set material scheme
	mOldMaterialScheme = vp->getMaterialScheme();
	vp->setMaterialScheme(op.materialScheme);
	/// Set shadows enabled
	mOldShadowsEnabled = vp->getShadowsEnabled();
	vp->setShadowsEnabled(op.shadowsEnabled);
    /// XXX TODO
    //vp->setClearEveryFrame( true );
    //vp->setOverlaysEnabled( false );
    //vp->setBackgroundColour( op.clearColour );
}
//-----------------------------------------------------------------------
void CompositorChain::postTargetOperation(CompositorInstance::TargetOperation &op, Viewport *vp, Camera *cam)
{
    SceneManager *sm = cam->getSceneManager();
	/// Unregister our listener
	sm->removeRenderQueueListener(&mOurListener);
	/// Restore default scene and camera settings
	sm->setVisibilityMask(mOldVisibilityMask);
	sm->setFindVisibleObjects(mOldFindVisibleObjects);
    cam->setLodBias(mOldLodBias);
	vp->setMaterialScheme(mOldMaterialScheme);
	vp->setShadowsEnabled(mOldShadowsEnabled);
}
//-----------------------------------------------------------------------
void CompositorChain::postViewportUpdate(const RenderTargetViewportEvent& evt)
{
	// Only tidy up if there is at least one compositor enabled, and it's this viewport
    if(evt.source != mViewport || !mAnyCompositorsEnabled)
        return;

	postTargetOperation(mOutputOperation, mViewport, mViewport->getCamera());
}
//-----------------------------------------------------------------------
void CompositorChain::viewportRemoved(const RenderTargetViewportEvent& evt)
{
	// check this is the viewport we're attached to (multi-viewport targets)
	if (evt.source == mViewport)
	{
		// this chain is now orphaned
		// can't delete it since held from outside, but release all resources being used
		destroyResources();
	}

}
//-----------------------------------------------------------------------
void CompositorChain::clearCompiledState()
{
	for (RenderSystemOperations::iterator i = mRenderSystemOperations.begin();
		i != mRenderSystemOperations.end(); ++i)
	{
		OGRE_DELETE *i;
	}
	mRenderSystemOperations.clear();

	/// Clear compiled state
	mCompiledState.clear();
	mOutputOperation = CompositorInstance::TargetOperation(0);

}
//-----------------------------------------------------------------------
void CompositorChain::_compile()
{
	clearCompiledState();

	bool compositorsEnabled = false;

    /// Set previous CompositorInstance for each compositor in the list
    CompositorInstance *lastComposition = mOriginalScene;
	mOriginalScene->mPreviousInstance = 0;
	CompositionPass* pass = mOriginalScene->getTechnique()->getOutputTargetPass()->getPass(0);
	pass->setClearBuffers(mViewport->getClearBuffers());
	pass->setClearColour(mViewport->getBackgroundColour());
    for(Instances::iterator i=mInstances.begin(); i!=mInstances.end(); ++i)
    {
        if((*i)->getEnabled())
        {
			compositorsEnabled = true;
            (*i)->mPreviousInstance = lastComposition;
            lastComposition = (*i);
        }
    }

    /// Compile misc targets
    lastComposition->_compileTargetOperations(mCompiledState);

    /// Final target viewport (0)
	mOutputOperation.renderSystemOperations.clear();
    lastComposition->_compileOutputOperation(mOutputOperation);

	// Deal with viewport settings
	if (compositorsEnabled != mAnyCompositorsEnabled)
	{
		mAnyCompositorsEnabled = compositorsEnabled;
		if (mAnyCompositorsEnabled)
		{
			// Save old viewport clearing options
			mOldClearEveryFrameBuffers = mViewport->getClearBuffers();
			// Don't clear anything every frame since we have our own clear ops
			mViewport->setClearEveryFrame(false);
		}
		else
		{
			// Reset clearing options
			mViewport->setClearEveryFrame(mOldClearEveryFrameBuffers > 0,
				mOldClearEveryFrameBuffers);
		}
	}

    mDirty = false;
}
//-----------------------------------------------------------------------
void CompositorChain::_markDirty()
{
    mDirty = true;
}
//-----------------------------------------------------------------------
Viewport *CompositorChain::getViewport()
{
    return mViewport;
}
//---------------------------------------------------------------------
void CompositorChain::_notifyViewport(Viewport* vp)
{
	mViewport = vp;
}
//-----------------------------------------------------------------------
void CompositorChain::RQListener::renderQueueStarted(uint8 id,
	const String& invocation, bool& skipThisQueue)
{
	// Skip when not matching viewport
	// shadows update is nested within main viewport update
	if (mSceneManager->getCurrentViewport() != mViewport)
		return;

	flushUpTo(id);
	/// If noone wants to render this queue, skip it
	/// Don't skip the OVERLAY queue because that's handled seperately
	if(!mOperation->renderQueues.test(id) && id!=RENDER_QUEUE_OVERLAY)
	{
		skipThisQueue = true;
	}
}
//-----------------------------------------------------------------------
void CompositorChain::RQListener::renderQueueEnded(uint8 id,
	const String& invocation, bool& repeatThisQueue)
{
}
//-----------------------------------------------------------------------
void CompositorChain::RQListener::setOperation(CompositorInstance::TargetOperation *op,SceneManager *sm,RenderSystem *rs)
{
	mOperation = op;
	mSceneManager = sm;
	mRenderSystem = rs;
	currentOp = op->renderSystemOperations.begin();
	lastOp = op->renderSystemOperations.end();
}
//-----------------------------------------------------------------------
void CompositorChain::RQListener::flushUpTo(uint8 id)
{
	/// Process all RenderSystemOperations up to and including render queue id.
    /// Including, because the operations for RenderQueueGroup x should be executed
	/// at the beginning of the RenderQueueGroup render for x.
	while(currentOp != lastOp && currentOp->first <= id)
	{
		currentOp->second->execute(mSceneManager, mRenderSystem);
		++currentOp;
	}
}
//-----------------------------------------------------------------------
}
<|endoftext|>