<commit_before>/** \brief  A MARC-21 filter uliity that replace URNs in 856u-fields with URLs
 *  \author Oliver Obenland (oliver.obenland@uni-tuebingen.de)
 *  \author Dr. Johannes Ruscheinski
 *
 *  \copyright 2016-2018 Universitätsbibliothek Tübingen.  All rights reserved.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <iostream>
#include <string>
#include <vector>
#include "MARC.h"
#include "StringUtil.h"
#include "util.h"

namespace {

[[noreturn]] void Usage() {
    std::cerr << "Usage: " << ::progname << " [-v|--verbose] marc_input marc_output\n";
    std::exit(EXIT_FAILURE);
}

inline bool IsHttpOrHttpsURL(const std::string &url_candidate) {
    return StringUtil::StartsWith(url_candidate, "http://") or StringUtil::StartsWith(url_candidate, "https://");
}

// Returns the number of extracted 856u subfields.
size_t ExtractAllHttpOrHttps856uSubfields(const MARC::Record &record, std::vector<std::string> * const _856u_urls) {
    for (const auto &_856_field : record.getTagRange("856")) {
        const std::string _856u_subfield_value(_856_field.getSubfields().getFirstSubfieldWithCode('u'));
        if (IsHttpOrHttpsURL(_856u_subfield_value))
            _856u_urls->emplace_back(_856u_subfield_value);
    }

    return _856u_urls->size();
}

// Returns true if "test_string" is the suffix of "url" after stripping off the schema and domain name as well as
// a single slash after the domain name.
bool IsSuffixOfURL(const std::string &url, const std::string &test_string) {
    const bool starts_with_http(StringUtil::StartsWith(url, "http://"));
    if (not starts_with_http and not StringUtil::StartsWith(url, "https://"))
        return false;

    const size_t next_slash_pos(url.find('/', starts_with_http ? std::strlen("http://") : std::strlen("https://")));
    if (unlikely(next_slash_pos == std::string::npos))
        return false;
    if (unlikely(next_slash_pos + 1 >= url.size()))
        return false; // We have no path component in our URL.

    return url.substr(next_slash_pos + 1) == test_string;
}

// Returns true if "test_string" is a proper suffix of any of the URLs contained in "urls".
bool IsSuffixOfAnyURL(const std::vector<std::string> &urls, const std::string &test_string) {
    for (const auto &url : urls) {
        if (IsSuffixOfURL(url, test_string))
            return true;
    }

    return false;
}

void CreateUrlsFrom024(MARC::Record * const record) {
    for (const auto &_024_field : record->getTagRange("024")) {
        if (_024_field.getFirstSubfieldWithCode('2') == "doi") {
            const std::string doi(_024_field.getFirstSubfieldWithCode('a'));
            if (not doi.empty())
                record->insertField("856", { { 'u', "https://doi.org/" + doi }, { 'x', "doi" } });
        }
    }
}

void NormaliseURLs(const bool verbose, MARC::Reader * const reader, MARC::Writer * const writer) {
    unsigned count(0), modified_count(0), duplicate_skip_count(0);
    while (MARC::Record record = reader->read()) {
        ++count;

        CreateUrlsFrom024(&record);

        std::vector<std::string> _856u_urls;
        ExtractAllHttpOrHttps856uSubfields(record, &_856u_urls);

        bool modified_record(false);
        std::unordered_set<std::string> already_seen_links;

        auto _856_field(record.findTag("856"));
        while (_856_field != record.end() and _856_field->getTag() == "856") {
            MARC::Subfields _856_subfields(_856_field->getSubfields());
            bool duplicate_link(false);
            if (_856_subfields.hasSubfield('u')) {
                const std::string u_subfield(StringUtil::Trim(_856_subfields.getFirstSubfieldWithCode('u')));

                if (IsHttpOrHttpsURL(u_subfield)) {
                    if (already_seen_links.find(u_subfield) == already_seen_links.cend())
                        already_seen_links.insert(u_subfield);
                    else
                        duplicate_link = true;
                } else if (IsSuffixOfAnyURL(_856u_urls, u_subfield)) {
                    if (verbose)
                        std::cout << "Dropped field w/ duplicate URL suffix. (" << u_subfield << ")";
                    _856_field = record.erase(_856_field);
                    continue;
                } else {
                    std::string new_http_replacement_link;
                    if (StringUtil::StartsWith(u_subfield, "urn:"))
                        new_http_replacement_link = "https://nbn-resolving.org/" + u_subfield;
                    else if (StringUtil::StartsWith(u_subfield, "10900/"))
                        new_http_replacement_link = "https://publikationen.uni-tuebingen.de/xmlui/handle/"
                                                    + u_subfield;
                    else
                        new_http_replacement_link = "http://" + u_subfield;
                    if (already_seen_links.find(new_http_replacement_link) == already_seen_links.cend()) {
                        _856_subfields.replaceFirstSubfield('u', new_http_replacement_link);
                        if (verbose)
                            std::cout << "Replaced \"" << u_subfield << "\" with \"" << new_http_replacement_link
                                      << "\". (PPN: " << record.getControlNumber() << ")\n";
                        already_seen_links.insert(new_http_replacement_link);
                        modified_record = true;
                    } else
                        duplicate_link = true;
                }
            }

            if (not duplicate_link)
                ++_856_field;
            else {
                ++duplicate_skip_count;
                if (verbose)
                    std::cout << "Skipping duplicate, control numbers is " << record.getControlNumber() << ".\n";
                _856_field = record.erase(_856_field);
                modified_record = true;
            }
        }

        if (modified_record)
            ++modified_count;

        writer->write(record);
    }

    std::cerr << "Read " << count << " records.\n";
    std::cerr << "Modified " << modified_count << " record(s).\n";
    std::cerr << "Skipped " << duplicate_skip_count << " duplicate links.\n";
}

} // unnamed namespace

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

    const bool verbose(std::strcmp("-v", argv[1]) == 0 or std::strcmp("--verbose", argv[1]) == 0);
    if (verbose)
        --argc, ++argv;

    if (argc < 2)
        Usage();

    if (argc != 3)
        Usage();

    auto marc_reader(MARC::Reader::Factory(argv[1]));
    auto marc_writer(MARC::Writer::Factory(argv[2]));
    NormaliseURLs(verbose, marc_reader.get(), marc_writer.get());

    return EXIT_SUCCESS;
}
<commit_msg>Fixed reporting.<commit_after>/** \brief  A MARC-21 filter uliity that replace URNs in 856u-fields with URLs
 *  \author Oliver Obenland (oliver.obenland@uni-tuebingen.de)
 *  \author Dr. Johannes Ruscheinski
 *
 *  \copyright 2016-2018 Universitätsbibliothek Tübingen.  All rights reserved.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <iostream>
#include <string>
#include <vector>
#include "MARC.h"
#include "StringUtil.h"
#include "util.h"

namespace {

[[noreturn]] void Usage() {
    std::cerr << "Usage: " << ::progname << " [-v|--verbose] marc_input marc_output\n";
    std::exit(EXIT_FAILURE);
}

inline bool IsHttpOrHttpsURL(const std::string &url_candidate) {
    return StringUtil::StartsWith(url_candidate, "http://") or StringUtil::StartsWith(url_candidate, "https://");
}

// Returns the number of extracted 856u subfields.
size_t ExtractAllHttpOrHttps856uSubfields(const MARC::Record &record, std::vector<std::string> * const _856u_urls) {
    for (const auto &_856_field : record.getTagRange("856")) {
        const std::string _856u_subfield_value(_856_field.getSubfields().getFirstSubfieldWithCode('u'));
        if (IsHttpOrHttpsURL(_856u_subfield_value))
            _856u_urls->emplace_back(_856u_subfield_value);
    }

    return _856u_urls->size();
}

// Returns true if "test_string" is the suffix of "url" after stripping off the schema and domain name as well as
// a single slash after the domain name.
bool IsSuffixOfURL(const std::string &url, const std::string &test_string) {
    const bool starts_with_http(StringUtil::StartsWith(url, "http://"));
    if (not starts_with_http and not StringUtil::StartsWith(url, "https://"))
        return false;

    const size_t next_slash_pos(url.find('/', starts_with_http ? std::strlen("http://") : std::strlen("https://")));
    if (unlikely(next_slash_pos == std::string::npos))
        return false;
    if (unlikely(next_slash_pos + 1 >= url.size()))
        return false; // We have no path component in our URL.

    return url.substr(next_slash_pos + 1) == test_string;
}

// Returns true if "test_string" is a proper suffix of any of the URLs contained in "urls".
bool IsSuffixOfAnyURL(const std::vector<std::string> &urls, const std::string &test_string) {
    for (const auto &url : urls) {
        if (IsSuffixOfURL(url, test_string))
            return true;
    }

    return false;
}

bool CreateUrlsFrom024(MARC::Record * const record) {
    bool added_at_least_one_url(false);

    for (const auto &_024_field : record->getTagRange("024")) {
        if (_024_field.getFirstSubfieldWithCode('2') == "doi") {
            const std::string doi(_024_field.getFirstSubfieldWithCode('a'));
            if (not doi.empty()) {
                record->insertField("856", { { 'u', "https://doi.org/" + doi }, { 'x', "doi" } });
                added_at_least_one_url = true;
            }
        }
    }

    return added_at_least_one_url;
}

void NormaliseURLs(const bool verbose, MARC::Reader * const reader, MARC::Writer * const writer) {
    unsigned count(0), modified_count(0), duplicate_skip_count(0);
    while (MARC::Record record = reader->read()) {
        ++count;

        bool modified_record(false);
        if (CreateUrlsFrom024(&record))
            modified_record = true;

        std::vector<std::string> _856u_urls;
        ExtractAllHttpOrHttps856uSubfields(record, &_856u_urls);

        std::unordered_set<std::string> already_seen_links;

        auto _856_field(record.findTag("856"));
        while (_856_field != record.end() and _856_field->getTag() == "856") {
            MARC::Subfields _856_subfields(_856_field->getSubfields());
            bool duplicate_link(false);
            if (_856_subfields.hasSubfield('u')) {
                const std::string u_subfield(StringUtil::Trim(_856_subfields.getFirstSubfieldWithCode('u')));

                if (IsHttpOrHttpsURL(u_subfield)) {
                    if (already_seen_links.find(u_subfield) == already_seen_links.cend())
                        already_seen_links.insert(u_subfield);
                    else
                        duplicate_link = true;
                } else if (IsSuffixOfAnyURL(_856u_urls, u_subfield)) {
                    if (verbose)
                        std::cout << "Dropped field w/ duplicate URL suffix. (" << u_subfield << ")";
                    _856_field = record.erase(_856_field);
                    continue;
                } else {
                    std::string new_http_replacement_link;
                    if (StringUtil::StartsWith(u_subfield, "urn:"))
                        new_http_replacement_link = "https://nbn-resolving.org/" + u_subfield;
                    else if (StringUtil::StartsWith(u_subfield, "10900/"))
                        new_http_replacement_link = "https://publikationen.uni-tuebingen.de/xmlui/handle/"
                                                    + u_subfield;
                    else
                        new_http_replacement_link = "http://" + u_subfield;
                    if (already_seen_links.find(new_http_replacement_link) == already_seen_links.cend()) {
                        _856_subfields.replaceFirstSubfield('u', new_http_replacement_link);
                        if (verbose)
                            std::cout << "Replaced \"" << u_subfield << "\" with \"" << new_http_replacement_link
                                      << "\". (PPN: " << record.getControlNumber() << ")\n";
                        already_seen_links.insert(new_http_replacement_link);
                        modified_record = true;
                    } else
                        duplicate_link = true;
                }
            }

            if (not duplicate_link)
                ++_856_field;
            else {
                ++duplicate_skip_count;
                if (verbose)
                    std::cout << "Skipping duplicate, control numbers is " << record.getControlNumber() << ".\n";
                _856_field = record.erase(_856_field);
                modified_record = true;
            }
        }

        if (modified_record)
            ++modified_count;

        writer->write(record);
    }

    std::cerr << "Read " << count << " records.\n";
    std::cerr << "Modified " << modified_count << " record(s).\n";
    std::cerr << "Skipped " << duplicate_skip_count << " duplicate links.\n";
}

} // unnamed namespace

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

    const bool verbose(std::strcmp("-v", argv[1]) == 0 or std::strcmp("--verbose", argv[1]) == 0);
    if (verbose)
        --argc, ++argv;

    if (argc < 2)
        Usage();

    if (argc != 3)
        Usage();

    auto marc_reader(MARC::Reader::Factory(argv[1]));
    auto marc_writer(MARC::Writer::Factory(argv[2]));
    NormaliseURLs(verbose, marc_reader.get(), marc_writer.get());

    return EXIT_SUCCESS;
}
<|endoftext|>


<commit_before>/** \file   rss_aggregator.cc
 *  \brief  Downloads and aggregates RSS feeds.
 *  \author Dr. Johannes Ruscheinski (johannes.ruscheinski@uni-tuebingen.de)
 *
 *  \copyright 2018 Universitätsbibliothek Tübingen.  All rights reserved.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <iostream>
#include <unordered_map>
#include <unordered_set>
#include <cinttypes>
#include <csignal>
#include <cstring>
#include <unistd.h>
#include "Compiler.h"
#include "DbConnection.h"
#include "DbResultSet.h"
#include "Downloader.h"
#include "IniFile.h"
#include "SignalUtil.h"
#include "StringUtil.h"
#include "SyndicationFormat.h"
#include "util.h"

namespace {

volatile sig_atomic_t sigterm_seen = false;

void SigTermHandler(int /* signum */) {
    sigterm_seen = true;
}

volatile sig_atomic_t sighup_seen = false;

void SigHupHandler(int /* signum */) {
    sighup_seen = true;
}

[[noreturn]] void Usage() {
    std::cerr << "Usage: " << ::progname
              << " [--verbosity=min_verbosity] [--test] xml_output_path\n"
              << "       When --test has been specified no data will be stored.\n";
    std::exit(EXIT_FAILURE);
}

// These must be in sync with the sizes in data/ub_tools.sql (rss_aggregator table)
const size_t MAX_ITEM_ID_LENGTH(100);
const size_t MAX_ITEM_URL_LENGTH(512);
const size_t MAX_SERIAL_NAME_LENGTH(200);

// \return true if the item was new, else false.
bool ProcessRSSItem(const SyndicationFormat::Item &item, const std::string &section_name, DbConnection * const db_connection) {
    const std::string item_id(item.getId());
    db_connection->queryOrDie("SELECT insertion_time FROM rss_aggregator WHERE item_id='" + db_connection->escapeString(item_id) + "'");
    const DbResultSet result_set(db_connection->getLastResultSet());
    if (not result_set.empty())
        return false;

    const std::string item_url(item.getLink());
    if (item_url.empty()) {
        LOG_WARNING("got an item w/o a URL, ID is \"" + item.getId());
        return false;
    }

    std::string title_and_or_description(item.getTitle());
    if (title_and_or_description.empty())
        title_and_or_description = item.getDescription();
    else {
        const std::string description(item.getDescription());
        if (not description.empty())
            title_and_or_description += " (" + description + ")";
    }

    db_connection->queryOrDie("INSERT INTO rss_aggregator SET item_id='"
                              + db_connection->escapeString(StringUtil::Truncate(MAX_ITEM_ID_LENGTH, item_id)) + "'," + "item_url='"
                              + db_connection->escapeString(StringUtil::Truncate(MAX_ITEM_URL_LENGTH, item_url))
                              + "',title_and_or_description='" + db_connection->escapeString(title_and_or_description) + ", serial_name='"
                              + db_connection->escapeString(StringUtil::Truncate(MAX_SERIAL_NAME_LENGTH, section_name)) + "'");

    return true;
}

void CheckForSigTermAndExitIfSeen() {
    if (sigterm_seen) {
        LOG_WARNING("caught SIGTERM, existing...");
        std::exit(EXIT_SUCCESS);
    }
}

void CheckForSigHupAndReloadIniFileIfSeen(IniFile * const ini_file) {
    if (sighup_seen) {
        LOG_WARNING("caught SIGHUP, reloading config file...");
        ini_file->reload();
        sighup_seen = false;
    }
}

std::unordered_map<std::string, uint64_t> section_name_to_ticks_map;

// \return the number of new items.
unsigned ProcessSection(const IniFile::Section &section, Downloader * const downloader, DbConnection * const db_connection,
                        const unsigned default_downloader_time_limit, const unsigned default_poll_interval, const uint64_t now)
{
    const std::string feed_url(section.getString("feed_url"));
    const unsigned poll_interval(section.getUnsigned("poll_interval", default_poll_interval));
    const unsigned downloader_time_limit(section.getUnsigned("downloader_time_limit", default_downloader_time_limit) * 1000);

    const std::string &section_name(section.getSectionName());
    const auto section_name_and_ticks(section_name_to_ticks_map.find(section_name));
    if (section_name_and_ticks != section_name_to_ticks_map.end()) {
        if (section_name_and_ticks->second + poll_interval < now) {
            LOG_DEBUG(section_name + ": not yet time to do work, last work was done at " + std::to_string(section_name_and_ticks->second)
                      + ".");
            return 0;
        }
    }

    unsigned new_item_count(0);
    SignalUtil::SignalBlocker sigterm_blocker(SIGTERM);
    if (not downloader->newUrl(feed_url, downloader_time_limit))
        LOG_WARNING(section_name + ": failed to download the feed: " + downloader->getLastErrorMessage());
    else {
        sigterm_blocker.unblock();
        CheckForSigTermAndExitIfSeen();

        std::string error_message;
        std::unique_ptr<SyndicationFormat> syndication_format(SyndicationFormat::Factory(downloader->getMessageBody(), &error_message));
        if (unlikely(syndication_format == nullptr))
            LOG_WARNING("failed to parse feed: " + error_message);
        else {
            for (const auto &item : *syndication_format) {
                CheckForSigTermAndExitIfSeen();
                SignalUtil::SignalBlocker sigterm_blocker2(SIGTERM);

                if (ProcessRSSItem(item, section_name,  db_connection))
                    ++new_item_count;
            }
        }
    }

    section_name_to_ticks_map[section_name] = now;
    return new_item_count;
}

const std::string CONF_FILE_PATH("/usr/local/var/lib/tuelib/rss_aggregator.conf");

} // unnamed namespace

int Main(int argc, char *argv[]) {
    ::progname = argv[0];

    if (argc != 2 and argc != 3)
        Usage();

    bool test(false);
    if (argc == 3) {
        if (std::strcmp(argv[1], "--test") == 0) {
            test = true;
            --argc, ++argv;
        } else
            Usage();
    }

    IniFile ini_file(CONF_FILE_PATH);
    DbConnection db_connection(ini_file);

    const unsigned DEFAULT_POLL_INTERVAL(ini_file.getUnsigned("", "default_poll_interval"));
    const unsigned DEFAULT_DOWNLOADER_TIME_LIMIT(ini_file.getUnsigned("", "default_downloader_time_limit"));
    const unsigned UPDATE_INTERVAL(ini_file.getUnsigned("", "update_interval"));

    if (not test) {
        SignalUtil::InstallHandler(SIGTERM, SigTermHandler);
        SignalUtil::InstallHandler(SIGHUP, SigHupHandler);

        if (::daemon(0, 1 /* do not close file descriptors and redirect to /dev/null */) != 0)
            LOG_ERROR("we failed to deamonize our process!");
    }

    uint64_t ticks(0);
    Downloader downloader;
    for (;;) {
        LOG_DEBUG("now we're at " + std::to_string(ticks) + ".");

        CheckForSigHupAndReloadIniFileIfSeen(&ini_file);

        const time_t before(std::time(nullptr));

        std::unordered_set<std::string> already_seen_sections;
        for (const auto &section : ini_file) {
            if (sigterm_seen) {
                LOG_INFO("caught SIGTERM, shutting down...");
                return EXIT_SUCCESS;
            }

            SignalUtil::SignalBlocker sighup_blocker(SIGHUP);

            const std::string &section_name(section.first);
            if (not section_name.empty() and section_name != "CGI Params") {
                if (unlikely(already_seen_sections.find(section_name) != already_seen_sections.end()))
                    LOG_ERROR("duplicate section: \"" + section_name + "\"!");
                already_seen_sections.emplace(section_name);

                LOG_INFO("Processing section \"" + section_name + "\".");
                const unsigned new_item_count(ProcessSection(section.second, &downloader, &db_connection, DEFAULT_DOWNLOADER_TIME_LIMIT,
                                                             DEFAULT_POLL_INTERVAL, ticks));
                LOG_INFO("found " + std::to_string(new_item_count) + " new items.");
            }
        }

        if (test) // -> only run through our loop once
            return EXIT_SUCCESS;

        const time_t after(std::time(nullptr));

        uint64_t sleep_interval;
        if (after - before > UPDATE_INTERVAL * 60)
            sleep_interval = 0;
        else
            sleep_interval = (UPDATE_INTERVAL * 60 - (after - before));

        unsigned total_time_slept(0);
        do {
            const unsigned actual_time_slept(::sleep(static_cast<unsigned>(sleep_interval - total_time_slept)));
            CheckForSigTermAndExitIfSeen();
            CheckForSigHupAndReloadIniFileIfSeen(&ini_file);

            total_time_slept += actual_time_slept;
        } while (total_time_slept < sleep_interval);
        ticks += UPDATE_INTERVAL;
    }
}
<commit_msg>Update rss_aggregator.cc<commit_after>/** \file   rss_aggregator.cc
 *  \brief  Downloads and aggregates RSS feeds.
 *  \author Dr. Johannes Ruscheinski (johannes.ruscheinski@uni-tuebingen.de)
 *
 *  \copyright 2018 Universitätsbibliothek Tübingen.  All rights reserved.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <iostream>
#include <unordered_map>
#include <unordered_set>
#include <cinttypes>
#include <csignal>
#include <cstring>
#include <unistd.h>
#include "Compiler.h"
#include "DbConnection.h"
#include "DbResultSet.h"
#include "Downloader.h"
#include "IniFile.h"
#include "SignalUtil.h"
#include "StringUtil.h"
#include "SyndicationFormat.h"
#include "util.h"

namespace {

volatile sig_atomic_t sigterm_seen = false;

void SigTermHandler(int /* signum */) {
    sigterm_seen = true;
}

volatile sig_atomic_t sighup_seen = false;

void SigHupHandler(int /* signum */) {
    sighup_seen = true;
}

[[noreturn]] void Usage() {
    std::cerr << "Usage: " << ::progname
              << " [--verbosity=min_verbosity] [--test] xml_output_path\n"
              << "       When --test has been specified no data will be stored.\n";
    std::exit(EXIT_FAILURE);
}

// These must be in sync with the sizes in data/ub_tools.sql (rss_aggregator table)
const size_t MAX_ITEM_ID_LENGTH(100);
const size_t MAX_ITEM_URL_LENGTH(512);
const size_t MAX_SERIAL_NAME_LENGTH(200);

// \return true if the item was new, else false.
bool ProcessRSSItem(const SyndicationFormat::Item &item, const std::string &section_name, DbConnection * const db_connection) {
    const std::string item_id(item.getId());
    db_connection->queryOrDie("SELECT insertion_time FROM rss_aggregator WHERE item_id='" + db_connection->escapeString(item_id) + "'");
    const DbResultSet result_set(db_connection->getLastResultSet());
    if (not result_set.empty())
        return false;

    const std::string item_url(item.getLink());
    if (item_url.empty()) {
        LOG_WARNING("got an item w/o a URL, ID is \"" + item.getId());
        return false;
    }

    std::string title_and_or_description(item.getTitle());
    if (title_and_or_description.empty())
        title_and_or_description = item.getDescription();
    else {
        const std::string description(item.getDescription());
        if (not description.empty())
            title_and_or_description += " (" + description + ")";
    }

    db_connection->queryOrDie("INSERT INTO rss_aggregator SET item_id='"
                              + db_connection->escapeString(StringUtil::Truncate(MAX_ITEM_ID_LENGTH, item_id)) + "'," + "item_url='"
                              + db_connection->escapeString(StringUtil::Truncate(MAX_ITEM_URL_LENGTH, item_url))
                              + "',title_and_or_description='" + db_connection->escapeString(title_and_or_description) + ", serial_name='"
                              + db_connection->escapeString(StringUtil::Truncate(MAX_SERIAL_NAME_LENGTH, section_name)) + "'");

    return true;
}

void CheckForSigTermAndExitIfSeen() {
    if (sigterm_seen) {
        LOG_WARNING("caught SIGTERM, exiting...");
        std::exit(EXIT_SUCCESS);
    }
}

void CheckForSigHupAndReloadIniFileIfSeen(IniFile * const ini_file) {
    if (sighup_seen) {
        LOG_INFO("caught SIGHUP, reloading config file...");
        ini_file->reload();
        sighup_seen = false;
    }
}

std::unordered_map<std::string, uint64_t> section_name_to_ticks_map;

// \return the number of new items.
unsigned ProcessSection(const IniFile::Section &section, Downloader * const downloader, DbConnection * const db_connection,
                        const unsigned default_downloader_time_limit, const unsigned default_poll_interval, const uint64_t now)
{
    const std::string feed_url(section.getString("feed_url"));
    const unsigned poll_interval(section.getUnsigned("poll_interval", default_poll_interval));
    const unsigned downloader_time_limit(section.getUnsigned("downloader_time_limit", default_downloader_time_limit) * 1000);

    const std::string &section_name(section.getSectionName());
    const auto section_name_and_ticks(section_name_to_ticks_map.find(section_name));
    if (section_name_and_ticks != section_name_to_ticks_map.end()) {
        if (section_name_and_ticks->second + poll_interval < now) {
            LOG_DEBUG(section_name + ": not yet time to do work, last work was done at " + std::to_string(section_name_and_ticks->second)
                      + ".");
            return 0;
        }
    }

    unsigned new_item_count(0);
    SignalUtil::SignalBlocker sigterm_blocker(SIGTERM);
    if (not downloader->newUrl(feed_url, downloader_time_limit))
        LOG_WARNING(section_name + ": failed to download the feed: " + downloader->getLastErrorMessage());
    else {
        sigterm_blocker.unblock();
        CheckForSigTermAndExitIfSeen();

        std::string error_message;
        std::unique_ptr<SyndicationFormat> syndication_format(SyndicationFormat::Factory(downloader->getMessageBody(), &error_message));
        if (unlikely(syndication_format == nullptr))
            LOG_WARNING("failed to parse feed: " + error_message);
        else {
            for (const auto &item : *syndication_format) {
                CheckForSigTermAndExitIfSeen();
                SignalUtil::SignalBlocker sigterm_blocker2(SIGTERM);

                if (ProcessRSSItem(item, section_name,  db_connection))
                    ++new_item_count;
            }
        }
    }

    section_name_to_ticks_map[section_name] = now;
    return new_item_count;
}

const std::string CONF_FILE_PATH("/usr/local/var/lib/tuelib/rss_aggregator.conf");

} // unnamed namespace

int Main(int argc, char *argv[]) {
    ::progname = argv[0];

    if (argc != 2 and argc != 3)
        Usage();

    bool test(false);
    if (argc == 3) {
        if (std::strcmp(argv[1], "--test") == 0) {
            test = true;
            --argc, ++argv;
        } else
            Usage();
    }

    IniFile ini_file(CONF_FILE_PATH);
    DbConnection db_connection(ini_file);

    const unsigned DEFAULT_POLL_INTERVAL(ini_file.getUnsigned("", "default_poll_interval"));
    const unsigned DEFAULT_DOWNLOADER_TIME_LIMIT(ini_file.getUnsigned("", "default_downloader_time_limit"));
    const unsigned UPDATE_INTERVAL(ini_file.getUnsigned("", "update_interval"));

    if (not test) {
        SignalUtil::InstallHandler(SIGTERM, SigTermHandler);
        SignalUtil::InstallHandler(SIGHUP, SigHupHandler);

        if (::daemon(0, 1 /* do not close file descriptors and redirect to /dev/null */) != 0)
            LOG_ERROR("we failed to deamonize our process!");
    }

    uint64_t ticks(0);
    Downloader downloader;
    for (;;) {
        LOG_DEBUG("now we're at " + std::to_string(ticks) + ".");

        CheckForSigHupAndReloadIniFileIfSeen(&ini_file);

        const time_t before(std::time(nullptr));

        std::unordered_set<std::string> already_seen_sections;
        for (const auto &section : ini_file) {
            if (sigterm_seen) {
                LOG_INFO("caught SIGTERM, shutting down...");
                return EXIT_SUCCESS;
            }

            SignalUtil::SignalBlocker sighup_blocker(SIGHUP);

            const std::string &section_name(section.first);
            if (not section_name.empty() and section_name != "CGI Params") {
                if (unlikely(already_seen_sections.find(section_name) != already_seen_sections.end()))
                    LOG_ERROR("duplicate section: \"" + section_name + "\"!");
                already_seen_sections.emplace(section_name);

                LOG_INFO("Processing section \"" + section_name + "\".");
                const unsigned new_item_count(ProcessSection(section.second, &downloader, &db_connection, DEFAULT_DOWNLOADER_TIME_LIMIT,
                                                             DEFAULT_POLL_INTERVAL, ticks));
                LOG_INFO("found " + std::to_string(new_item_count) + " new items.");
            }
        }

        if (test) // -> only run through our loop once
            return EXIT_SUCCESS;

        const time_t after(std::time(nullptr));

        uint64_t sleep_interval;
        if (after - before > UPDATE_INTERVAL * 60)
            sleep_interval = 0;
        else
            sleep_interval = (UPDATE_INTERVAL * 60 - (after - before));

        unsigned total_time_slept(0);
        do {
            const unsigned actual_time_slept(::sleep(static_cast<unsigned>(sleep_interval - total_time_slept)));
            CheckForSigTermAndExitIfSeen();
            CheckForSigHupAndReloadIniFileIfSeen(&ini_file);

            total_time_slept += actual_time_slept;
        } while (total_time_slept < sleep_interval);
        ticks += UPDATE_INTERVAL;
    }
}
<|endoftext|>


<commit_before>/** \file   system_updater.cc
 *  \brief  Runs scripts from /usr/local/ub_tools/data/system_updates
 *  \author Dr. Johannes Ruscheinski (johannes.ruscheinski@uni-tuebingen.de)
 *
 *  \copyright 2017-2020 Universitätsbibliothek Tübingen.  All rights reserved.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <algorithm>
#include <cstdlib>
#include <iostream>
#include "DbConnection.h"
#include "ExecUtil.h"
#include "FileUtil.h"
#include "StringUtil.h"
#include "UBTools.h"
#include "util.h"

namespace {

const std::string VERSION_PATH(UBTools::GetTuelibPath() + "system_version");

unsigned GetCurrentVersion() {
    if (not FileUtil::Exists(VERSION_PATH))
        return 0;

    const auto version_number_string(StringUtil::TrimWhite(FileUtil::ReadStringOrDie(VERSION_PATH)));

    unsigned version;
    if (not StringUtil::ToUnsigned(version_number_string, &version))
        LOG_ERROR("can't convert the contents of \"" + VERSION_PATH + "\" to an unsigned number! (\"" + version_number_string + "\")");

    return version;
}

unsigned GetVersionFromScriptName(const std::string &script_name) {
    const auto basename(FileUtil::GetBasename(script_name));
    if (StringUtil::EndsWith(script_name, ".sh", true) or StringUtil::EndsWith(script_name, ".sql", true))
        return StringUtil::ToUnsignedOrDie(script_name.substr(0, script_name.find('.')));
    else
        LOG_ERROR("unexpected script name: \"" + script_name + "\"!");
}

// \return True if the version number of "script_name1" is smaller then the version number
//         of "script_name2".
inline bool ScriptLessThan(const std::string &script_name1, const std::string &script_name2) {
    return GetVersionFromScriptName(script_name1) < GetVersionFromScriptName(script_name2);
}

void SplitIntoDatabaseAndVersion(const std::string &update_filename, std::string * const database, unsigned * const version) {
    const auto first_dot_pos(update_filename.find('.'));
    if (first_dot_pos == std::string::npos or first_dot_pos == 0 or first_dot_pos == update_filename.length() - 1)
        LOG_ERROR("invalid update filename \"" + update_filename + "\"!");

    if (not StringUtil::ToUnsigned(update_filename.substr(0, first_dot_pos), version))
        LOG_ERROR("bad or missing version in update filename \"" + update_filename + "\"!");
    *database = update_filename.substr(first_dot_pos + 1, update_filename.find_last_of('.') - (first_dot_pos + 1));
}

void ApplyDatabaseUpdate(DbConnection * const db_connection, const std::string &update_directory_path,
                 const std::string &update_filename, std::string * const current_schema, const std::string &last_schema)
{
    std::string database;
    unsigned update_version;
    SplitIntoDatabaseAndVersion(update_filename, &database, &update_version);
    *current_schema = database;

    if (not db_connection->mySQLDatabaseExists(database)) {
        LOG_WARNING("database \"" + database + "\" does not exist, skipping file " + update_filename);
        return;
    }

    if (last_schema.empty() or database != last_schema) {
        LOG_INFO("switching to database: " + database);
        db_connection->queryOrDie("USE " + database);
    }

    LOG_INFO("applying update " + std::to_string(update_version) + " to database \"" + database + "\".");
    db_connection->queryFileOrDie(update_directory_path + "/" + update_filename);
}

} // unnamed namespace

int Main(int argc, char *argv[]) {
    if (argc != 2)
        ::Usage("path_to_update_scripts");

    bool dry_run = false;

    DbConnection db_connection(DbConnection::UBToolsFactory());
    const auto current_version(GetCurrentVersion());

    std::vector<std::string> script_names;
    const std::string SYSTEM_UPDATES_DIR(argv[1]);
    FileUtil::Directory system_updates_dir(SYSTEM_UPDATES_DIR, "(^\\d+\\.sh$|\\d+\\.(?:.*)\\.sql)");
    for (const auto &entry : system_updates_dir) {
        const unsigned script_version = GetVersionFromScriptName(entry.getName());
        if (script_version < 100)
            LOG_WARNING("script version " + std::to_string(script_version) + " might belong to the old update schema and is no longer supported");
        else if (script_version > current_version)
            script_names.emplace_back(entry.getName());
    }
    if (script_names.empty()) {
        if (current_version < 100) {
            LOG_INFO("Setting current version to 99");
            FileUtil::WriteStringOrDie(VERSION_PATH, "99");
        }
        LOG_INFO("nothing to be done!");
        return EXIT_SUCCESS;
    }

    std::sort(script_names.begin(), script_names.end(), ScriptLessThan);
    std::string last_schema, current_schema;
    int last_version_number = -1;

    for (const auto &script_name : script_names) {
        LOG_INFO("Running " + script_name);
        const unsigned version_number(GetVersionFromScriptName(script_name));

        if (last_version_number == (int) version_number)
            LOG_ERROR("the number " + std::to_string(version_number) + " was used multiple times.");
        else
            last_version_number = version_number;

        if (StringUtil::EndsWith(script_name, ".sh", true)) {
            if (dry_run)
                std::cout << SYSTEM_UPDATES_DIR << "---" << script_name << std::endl;
            else
                ExecUtil::ExecOrDie(SYSTEM_UPDATES_DIR + "/" + script_name);
        }
        else if (StringUtil::EndsWith(script_name, ".sql", true)) {
            if (dry_run)
                std::cout << SYSTEM_UPDATES_DIR << " " << script_name << std::endl;
            else
                ApplyDatabaseUpdate(&db_connection, SYSTEM_UPDATES_DIR, script_name, &current_schema, last_schema);
            last_schema = current_schema;
        }
        else {
            LOG_WARNING("unable to process file: \"" + script_name + "\" - skipping file");
            continue;
        }

        // We want to write the version number after each script
        // in case anything goes wrong, to avoid double execution
        // of successfully run scripts
        FileUtil::WriteStringOrDie(VERSION_PATH, std::to_string(version_number));
    }

    return EXIT_SUCCESS;
}
<commit_msg>changes after code review<commit_after>/** \file   system_updater.cc
 *  \brief  Runs scripts from /usr/local/ub_tools/data/system_updates
 *  \author Dr. Johannes Ruscheinski (johannes.ruscheinski@uni-tuebingen.de)
 *
 *  \copyright 2017-2020 Universitätsbibliothek Tübingen.  All rights reserved.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <algorithm>
#include <cstdlib>
#include <iostream>
#include "DbConnection.h"
#include "ExecUtil.h"
#include "FileUtil.h"
#include "StringUtil.h"
#include "UBTools.h"
#include "util.h"

namespace {

const std::string VERSION_PATH(UBTools::GetTuelibPath() + "system_version");

unsigned GetCurrentVersion() {
    if (not FileUtil::Exists(VERSION_PATH))
        return 0;

    const auto version_number_string(StringUtil::TrimWhite(FileUtil::ReadStringOrDie(VERSION_PATH)));

    unsigned version;
    if (not StringUtil::ToUnsigned(version_number_string, &version))
        LOG_ERROR("can't convert the contents of \"" + VERSION_PATH + "\" to an unsigned number! (\"" + version_number_string + "\")");

    return version;
}

unsigned GetVersionFromScriptName(const std::string &script_name) {
    const auto basename(FileUtil::GetBasename(script_name));
    if (StringUtil::EndsWith(script_name, ".sh", true) or StringUtil::EndsWith(script_name, ".sql", true))
        return StringUtil::ToUnsignedOrDie(script_name.substr(0, script_name.find('.')));
    else
        LOG_ERROR("unexpected script name: \"" + script_name + "\"!");
}

// \return True if the version number of "script_name1" is smaller then the version number
//         of "script_name2".
inline bool ScriptLessThan(const std::string &script_name1, const std::string &script_name2) {
    return GetVersionFromScriptName(script_name1) < GetVersionFromScriptName(script_name2);
}

void SplitIntoDatabaseAndVersion(const std::string &update_filename, std::string * const database, unsigned * const version) {
    const auto first_dot_pos(update_filename.find('.'));
    if (first_dot_pos == std::string::npos or first_dot_pos == 0 or first_dot_pos == update_filename.length() - 1)
        LOG_ERROR("invalid update filename \"" + update_filename + "\"!");

    if (not StringUtil::ToUnsigned(update_filename.substr(0, first_dot_pos), version))
        LOG_ERROR("bad or missing version in update filename \"" + update_filename + "\"!");
    *database = update_filename.substr(first_dot_pos + 1, update_filename.find_last_of('.') - (first_dot_pos + 1));
}

void ApplyDatabaseUpdate(DbConnection * const db_connection, const std::string &update_directory_path,
                 const std::string &update_filename, std::string * const current_schema, const std::string &last_schema)
{
    std::string database;
    unsigned update_version;
    SplitIntoDatabaseAndVersion(update_filename, &database, &update_version);
    *current_schema = database;

    if (not db_connection->mySQLDatabaseExists(database)) {
        LOG_WARNING("database \"" + database + "\" does not exist, skipping file " + update_filename);
        return;
    }

    if (last_schema.empty() or database != last_schema) {
        LOG_INFO("switching to database: " + database);
        db_connection->queryOrDie("USE " + database);
    }

    LOG_INFO("applying update " + std::to_string(update_version) + " to database \"" + database + "\".");
    db_connection->queryFileOrDie(update_directory_path + "/" + update_filename);
}

} // unnamed namespace

int Main(int argc, char *argv[]) {
    if (argc != 2)
        ::Usage("path_to_update_scripts");

    bool dry_run = false;

    DbConnection db_connection(DbConnection::UBToolsFactory());
    const auto current_version(GetCurrentVersion());

    std::vector<std::string> script_names;
    const std::string SYSTEM_UPDATES_DIR(argv[1]);
    FileUtil::Directory system_updates_dir(SYSTEM_UPDATES_DIR, "(^\\d+\\.sh$|\\d+\\.(?:.*)\\.sql)");
    for (const auto &entry : system_updates_dir) {
        const unsigned script_version = GetVersionFromScriptName(entry.getName());
        if (script_version < 100)
            LOG_WARNING("script version " + std::to_string(script_version) + " might belong to the old update schema and is no longer supported");
        else if (script_version > current_version)
            script_names.emplace_back(entry.getName());
    }
    if (script_names.empty()) {
        if (current_version < 100) {
            LOG_INFO("Setting current version to 99");
            FileUtil::WriteStringOrDie(VERSION_PATH, "99");
        }
        LOG_INFO("nothing to be done!");
        return EXIT_SUCCESS;
    }

    std::sort(script_names.begin(), script_names.end(), ScriptLessThan);
    std::string last_schema, current_schema;
    int previous_version_number = -1;

    for (const auto &script_name : script_names) {
        LOG_INFO("Running " + script_name);
        const unsigned version_number(GetVersionFromScriptName(script_name));

        if (previous_version_number == (int) version_number)
            LOG_ERROR("the number " + std::to_string(version_number) + " was used multiple times.");
        else
            previous_version_number = version_number;

        if (StringUtil::EndsWith(script_name, ".sh", true)) {
            if (dry_run)
                std::cout << SYSTEM_UPDATES_DIR << "---" << script_name << std::endl;
            else
                ExecUtil::ExecOrDie(SYSTEM_UPDATES_DIR + "/" + script_name);
        }
        else if (StringUtil::EndsWith(script_name, ".sql", true)) {
            if (dry_run)
                std::cout << SYSTEM_UPDATES_DIR << " " << script_name << std::endl;
            else
                ApplyDatabaseUpdate(&db_connection, SYSTEM_UPDATES_DIR, script_name, &current_schema, last_schema);
            last_schema = current_schema;
        }
        else {
            LOG_WARNING("unable to process file: \"" + script_name + "\" - skipping file");
            continue;
        }

        // We want to write the version number after each script
        // in case anything goes wrong, to avoid double execution
        // of successfully run scripts
        FileUtil::WriteStringOrDie(VERSION_PATH, std::to_string(version_number));
    }

    return EXIT_SUCCESS;
}
<|endoftext|>


<commit_before>#include "Exception.h"

using namespace CppUnit;

const std::string
CppUnit::Exception::UNKNOWNFILENAME =
  "<unknown>";
const int CppUnit::Exception::UNKNOWNLINENUMBER = -1;

/// Construct the exception
CppUnit::Exception::Exception (const Exception& other)
  : exception (other)
{
  m_message       = other.m_message;
  m_lineNumber    = other.m_lineNumber;
  m_fileName      = other.m_fileName;
}

CppUnit::Exception::Exception (std::string message, long lineNumber, std::string fileName)
  : m_message (message), m_lineNumber (lineNumber), m_fileName (fileName)
{}

/// Destruct the exception
CppUnit::Exception::~Exception ()
{}

/// Perform an assignment
Exception&
CppUnit::Exception::operator= (const Exception& other)
{
  exception::operator= (other);

  if (&other != this) {
    m_message       = other.m_message;
    m_lineNumber    = other.m_lineNumber;
    m_fileName      = other.m_fileName;
  }

  return *this;
}

/// Return descriptive message
const char*
CppUnit::Exception::what() const throw ()
{ return m_message.c_str (); }

/// The line on which the error occurred
long
CppUnit::Exception::lineNumber ()
{ return m_lineNumber; }

/// The file in which the error occurred
std::string
CppUnit::Exception::fileName ()
{ return m_fileName; }

<commit_msg>code beautification.<commit_after>#include "Exception.h"

using namespace CppUnit;

const std::string
CppUnit::Exception::UNKNOWNFILENAME =
  "<unknown>";
const int CppUnit::Exception::UNKNOWNLINENUMBER = -1;

/// Construct the exception
CppUnit::Exception::Exception (const Exception& other)
  : exception (other)
{
  m_message       = other.m_message;
  m_lineNumber    = other.m_lineNumber;
  m_fileName      = other.m_fileName;
}

CppUnit::Exception::Exception (std::string message, long lineNumber, std::string fileName)
  : m_message (message), m_lineNumber (lineNumber), m_fileName (fileName)
{
}

/// Destruct the exception
CppUnit::Exception::~Exception ()
{}

/// Perform an assignment
Exception&
CppUnit::Exception::operator= (const Exception& other)
{
  exception::operator= (other);

  if (&other != this) {
    m_message       = other.m_message;
    m_lineNumber    = other.m_lineNumber;
    m_fileName      = other.m_fileName;
  }

  return *this;
}

/// Return descriptive message
const char*
CppUnit::Exception::what() const throw ()
{ return m_message.c_str (); }

/// The line on which the error occurred
long
CppUnit::Exception::lineNumber ()
{ return m_lineNumber; }

/// The file in which the error occurred
std::string
CppUnit::Exception::fileName ()
{ return m_fileName; }

<|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 "contextpropertyinfo.h"
#include "infobackend.h"
#include "sconnect.h"
#include "logging.h"
#include "loggingfeatures.h"
#include <QMutex>
#include <QMutexLocker>

/*!
    \page Introspection

    \brief The Context Framework maintains a registry defining which
    context properties are currently provided and by whom. The
    introspection API of libcontextsubscriber allows you to inspect
    the current state of the registry and observe its changes.

    \section Overview

    The introspection is provided via two classes: ContextRegistryInfo and
    ContextPropertyInfo.

    ContextRegistryInfo provides a high-level view to the registry
    contents.  You can use it to obtain info about the list of
    currently available keys or e.g. get a list of keys for one
    particular provider.  ContextRegistryInfo is a singleton instance
    which is created on the first access.

    ContextPropertyInfo is used to obtain metadata about one
    particular key. Once created, it can be used to retrieve the type
    and provider information (DBus bus type and name) of the
    introspected key. It also provides a signal to listen for changes
    happening to a key.

    \section Usage

    \code
    // To get a list of all keys in the registry
    ContextRegistryInfo *context = ContextRegistryInfo::instance();
    QStringList currentKeys = context->listKeys();
    \endcode

    Using the ContextPropertyInfo is even more straight-forward.

    \code
    // To check the type of a particular key
    ContextPropertyInfo propInfo("Battery.ChargeLevel");
    QString propType = propInfo.type();
    \endcode

    The introspection API in general never asserts (never fails). It'll return empty strings
    on errors or if data is missing. For example:

    \code
    ContextPropertyInfo propInfo("Something.That.Doesnt.Exist");
    propInfo.type();     //  ...returns empty string
    propInfo.doc();      //  ...returns empty string
    \endcode

    You can use this functionality to wait for keys to become available in the registry.
    Just create a ContextPropertyInfo for a key that you're expecting to become present
    and connect to the \c changed signal.

    \code
    ContextPropertyInfo propInfo("Something.That.Doesnt.Exist");
    propInfo.declared(); // false
    // Connect something to the changed signal, keep checking it
    \endcode

    \section xmlvscdb XML vs.CDB

    When the introspection API is first used, a backend choice is being made. \b CDB backend
    (reading data from \c 'cache.cdb' ) is used if the tiny database cache file exists
    in the registry. The standard (slower) \b XML backend is used in other cases.

    It's possible to force a usage of a particular backend.
    This can be done by calling the \c instance method with a string name of the backend:

    \code
    ContextRegistryInfo::instance("cdb"); // or "xml"
    \endcode

    This needs to be done early enough before the introspection API is first used.
    For more information about the \b xml and \cdb backends read the \ref UpdatingContextProviders page.

*/

/*!
    \page MigratingFromDuiValueSpace

    \brief libcontextsubscriber is a replacement library for DuiValueSpace which is deprecated.

    DuiValueSpace, the old subscription library providing the keys, is deprecated. This library
    is a replacement for it, providing better API and better implementation while maintaining the
    same core ideas and structure.

    \section quicklook A quick look

    The following code for creating a handle for a context property:

    \code
    DuiValueSpaceItem topEdge("Context.Screen.TopEdge");
    QObject::connect(&topEdge, SIGNAL(valueChanged()),
                     this, SLOT(topEdgeChanged()));
    \endcode

    becomes:

    \code
    ContextProperty topEdge("Screen.TopEdge");
    QObject::connect(&topEdge, SIGNAL(valueChanged()),
                     this, SLOT(topEdgeChanged()));
    \endcode

    The following code for listing the available context keys:

    \code
    DuiValueSpaceItem::listKeys();
    \endcode

    becomes:

    \code
    ContextRegistryInfo::instance()->listKeys();
    \endcode

    \section prefix The Context. prefix

    In \b DuiValueSpace and accompanying packages, the properties used to
    have a "Context." prefix. For example:

    \code
    Context.Screen.TopEdge
    Context.Screen.IsCovered
    \endcode

    This 'Context.' has been dropped now from \b libcontextsubscriber and
    all the provider packages. Providers now explicitly provide properties
    with keys like:

    \code
    Screen.TopEdge
    Screen.IsCovered
    \endcode

    For compatibility reasons the 'Context.' prefix is still supported in
    newer releases of \b DuiValueSpace. The \b DuiValueSpace library transparently
    adds the 'Context.' prefix to all access functions.

    A call to:

    \code
    DuiValueSpaceItem topEdge("Context.Screen.TopEdge");
    \endcode

    ...is internally in \b DuiValueSpace converted to actual \c Screen.TopEdge
    wire access. This mechanism has been introduced to make the
    \b DuiValueSpace and \b libcontextsubscriber libraries parallel-installable.

    It's expected that all \b DuiValueSpace clients migrate to \b libcontextsubscriber
    eventually and \b DuiValueSpace library will be removed.

    \warning When migrating to \b libcontextsubscriber make sure to remove the 'Context.'
    from you key access paths.
*/

/*!
    \class ContextPropertyInfo

    \brief A class to introspect a context property details.

    This class is used to obtain information about a given key in the context registry.
    The information can be provided either from xml files or from a cdb database.
    It's possible to query the type, the provider and the documentation of the given
    key/property.
*/

/// Constructs a new ContextPropertyInfo for \a key with the given \a parent.
/// The object can be used to perform introspection on the given \a key.
/// \param key The full name of the key.
ContextPropertyInfo::ContextPropertyInfo(const QString &key, QObject *parent)
    : QObject(parent)
{
    keyName = key;

    if (key != "") {
        InfoBackend* infoBackend = InfoBackend::instance();
        sconnect(infoBackend, SIGNAL(keyChanged(QString)),
                 this, SLOT(onKeyChanged(QString)));

        cachedTypeInfo = infoBackend->typeInfoForKey(keyName);
        cachedDoc = infoBackend->docForKey(keyName);
        cachedDeclared = infoBackend->keyDeclared(keyName);
        cachedProviders = infoBackend->providersForKey(keyName);
    }
}

/// Returns the full name of the introspected key.
QString ContextPropertyInfo::key() const
{
    QMutexLocker lock(&cacheLock);
    return keyName;
}

/// Returns the doc (documentation) for the introspected key.
QString ContextPropertyInfo::doc() const
{
    QMutexLocker lock(&cacheLock);
    return cachedDoc;
}

/// Returns the old-style type name for the introspected key. To be deprecated soon.
QString ContextPropertyInfo::type() const
{
    QMutexLocker lock(&cacheLock);

    QString typeInfoName = cachedTypeInfo.name();
    if (typeInfoName == "int64")
        return "INT";
    else if (typeInfoName == "int32")
        return "INT";
    else if (typeInfoName == "integer")
        return "INT";
    else if (typeInfoName == "uint32")
        return "INT";
    else if (typeInfoName == "uint64")
        return "INT";
    else if (typeInfoName == "bool")
        return "TRUTH";
    else if (typeInfoName == "string")
        return "STRING";
    else if (typeInfoName == "number")
        return "DOUBLE";
    else
        return "";
}

/// Returns the advanced type info for the introspected key.
ContextTypeInfo ContextPropertyInfo::typeInfo() const
{
    QMutexLocker lock(&cacheLock);
    return cachedTypeInfo;
}

/// DEPRECATED Returns true if the key exists in the registry.
/// This function is deprecated, use declared() instead.
bool ContextPropertyInfo::exists() const
{
    contextWarning() << F_DEPRECATION << "ContextPropertyInfo::exists() is deprecated.";
    QMutexLocker lock(&cacheLock);
    return cachedDeclared;
}

/// Returns true if the key is declared in the registry
/// (it "exists").
bool ContextPropertyInfo::declared() const
{
    QMutexLocker lock(&cacheLock);
    return cachedDeclared;
}

/// Returns true if the key is provided by someone.
bool ContextPropertyInfo::provided() const
{
    QMutexLocker lock(&cacheLock);
    return (cachedProviders.size() > 0);
}

/// DEPRECATED Returns the name of the plugin supplying this property.
/// This function is deprecated, use providers() instead.
QString ContextPropertyInfo::plugin() const
{
    contextWarning() << F_DEPRECATION << "ContextPropertyInfo::plugin() is deprecated.";
    return plugin_i();
}

QString ContextPropertyInfo::plugin_i() const
{
    QMutexLocker lock(&cacheLock);
    if (cachedProviders.size() == 0)
        return "";
    else
        return cachedProviders.at(0).plugin;
}

/// DEPRECATED Returns the construction parameter for the Provider supplying this property
/// This function is deprecated, use providers() instead.
QString ContextPropertyInfo::constructionString() const
{
    contextWarning() << F_DEPRECATION << "ContextPropertyInfo::constructionString() is deprecated.";
    return constructionString_i();
}

QString ContextPropertyInfo::constructionString_i() const
{
    QMutexLocker lock(&cacheLock);
    if (cachedProviders.size() == 0)
        return "";
    else
        return cachedProviders.at(0).constructionString;
}

/// DEPRECATED Returns the dbus name of the provider supplying this
/// property/key. This function is maintained for backwards
/// compatibility. Use providers() instead.
QString ContextPropertyInfo::providerDBusName_i() const
{
    QMutexLocker lock(&cacheLock);
    if (cachedProviders.size() == 0)
        return "";
    else {
        QString plg = cachedProviders.at(0).plugin;
        QString constructionString = cachedProviders.at(0).constructionString;
        if (plg == "contextkit-dbus")
            return constructionString.split(":").last();
        else
            return "";
    }
}

/// DEPRECATED Returns the dbus name of the provider supplying this
/// property/key. This function is maintained for backwards
/// compatibility. Use listProviders() instead.
QString ContextPropertyInfo::providerDBusName() const
{
    contextWarning() << F_DEPRECATION << "ContextPropertyInfo::providerDBusName() is deprecated.";
    return providerDBusName_i();
}
QDBusConnection::BusType ContextPropertyInfo::providerDBusType_i() const
{
    QMutexLocker lock(&cacheLock);
    QString busType = "";

    if (cachedProviders.size() > 0) {
        QString plg = cachedProviders.at(0).plugin;
        QString constructionString = cachedProviders.at(0).constructionString;
        if (plg == "contextkit-dbus")
            busType = constructionString.split(":").first();
    }

    if (busType == "system")
        return QDBusConnection::SystemBus;
    else /* if (busType == "session") */
        return QDBusConnection::SessionBus;
}

/// DEPRECATED Returns the bus type of the provider supplying this property/key.
/// Ie. if it's a session bus or a system bus. This function is
/// maintained for backwards compatibility. Use listProviders() instead.
QDBusConnection::BusType ContextPropertyInfo::providerDBusType() const
{
    contextWarning() << F_DEPRECATION << "ContextPropertyInfo::providerDBusType() is deprecated.";
    return providerDBusType_i();
}

/* Slots */

/// This slot is connected to the \a keyChanged signal of the
/// actual infobackend instance. It's executed on every change to any
/// of the keys. We first check if the data concerns us. Next we
/// update the cached values and fire the actual signals.
void ContextPropertyInfo::onKeyChanged(const QString& key)
{
    QMutexLocker lock(&cacheLock);

    if (key != keyName)
        return;

    // Update caches
    cachedTypeInfo = InfoBackend::instance()->typeInfoForKey(keyName);
    cachedDoc = InfoBackend::instance()->docForKey(keyName);
    cachedDeclared = InfoBackend::instance()->keyDeclared(keyName);
    cachedProviders = InfoBackend::instance()->providersForKey(keyName);

    // Release the lock before emitting the signals; otherwise
    // listeners trying to access cached values would create a
    // deadlock.
    lock.unlock();

    // Emit the needed signals
    emit changed(keyName);
    emit typeChanged(type());
    emit existsChanged(cachedDeclared);
    emit providedChanged((cachedProviders.size() > 0));

    emit providerChanged(providerDBusName_i());
    emit providerDBusTypeChanged(providerDBusType_i());
    emit pluginChanged(plugin_i(), constructionString_i());
}

/// Returns a list of providers that provide this key.
const QList<ContextProviderInfo> ContextPropertyInfo::providers() const
{
    QMutexLocker lock(&cacheLock);
    return cachedProviders;
}

/// Called when people connect to signals. Used to emit deprecation warnings
/// when people connect to deprecated signals.
void ContextPropertyInfo::connectNotify(const char *_signal)
{
    QObject::connectNotify(_signal);
    QLatin1String signal(_signal);

    if (signal == SIGNAL(providerChanged(QString)))
        contextWarning() << F_DEPRECATION << "ContextPropertyInfo::providerChanged signal is deprecated.";
    else if (signal == SIGNAL(providerDBusTypeChanged(QDBusConnection::BusType)))
        contextWarning() << F_DEPRECATION << "ContextPropertyInfo::providerDBusTypeChanged signal is deprecated.";
    else if (signal == SIGNAL(typeChanged(QString)))
        contextWarning() << F_DEPRECATION << "ContextPropertyInfo::typeChanged signal is deprecated.";
    else if (signal == SIGNAL(existsChanged(bool)))
        contextWarning() << F_DEPRECATION << "ContextPropertyInfo::existsChanged signal is deprecated.";
    else if (signal == SIGNAL(providedChanged(bool)))
        contextWarning() << F_DEPRECATION << "ContextPropertyInfo::providedChanged signal is deprecated.";
    else if (signal == SIGNAL(pluginChanged(QString,QString)))
        contextWarning() << F_DEPRECATION << "ContextPropertyInfo::pluginChanged signal is deprecated.";
}

/// Returns resolution strategy for this property. Resolution strategy defines how values
/// are computed in relation to multiple providers being present for one property.
ContextPropertyInfo::ResolutionStrategy ContextPropertyInfo::resolutionStrategy() const
{
    return ContextPropertyInfo::LastValue;
}
<commit_msg>Do not support "int32", "int64", "uint32", "uint64" intentional type names in legacy API.<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 "contextpropertyinfo.h"
#include "infobackend.h"
#include "sconnect.h"
#include "logging.h"
#include "loggingfeatures.h"
#include <QMutex>
#include <QMutexLocker>

/*!
    \page Introspection

    \brief The Context Framework maintains a registry defining which
    context properties are currently provided and by whom. The
    introspection API of libcontextsubscriber allows you to inspect
    the current state of the registry and observe its changes.

    \section Overview

    The introspection is provided via two classes: ContextRegistryInfo and
    ContextPropertyInfo.

    ContextRegistryInfo provides a high-level view to the registry
    contents.  You can use it to obtain info about the list of
    currently available keys or e.g. get a list of keys for one
    particular provider.  ContextRegistryInfo is a singleton instance
    which is created on the first access.

    ContextPropertyInfo is used to obtain metadata about one
    particular key. Once created, it can be used to retrieve the type
    and provider information (DBus bus type and name) of the
    introspected key. It also provides a signal to listen for changes
    happening to a key.

    \section Usage

    \code
    // To get a list of all keys in the registry
    ContextRegistryInfo *context = ContextRegistryInfo::instance();
    QStringList currentKeys = context->listKeys();
    \endcode

    Using the ContextPropertyInfo is even more straight-forward.

    \code
    // To check the type of a particular key
    ContextPropertyInfo propInfo("Battery.ChargeLevel");
    QString propType = propInfo.type();
    \endcode

    The introspection API in general never asserts (never fails). It'll return empty strings
    on errors or if data is missing. For example:

    \code
    ContextPropertyInfo propInfo("Something.That.Doesnt.Exist");
    propInfo.type();     //  ...returns empty string
    propInfo.doc();      //  ...returns empty string
    \endcode

    You can use this functionality to wait for keys to become available in the registry.
    Just create a ContextPropertyInfo for a key that you're expecting to become present
    and connect to the \c changed signal.

    \code
    ContextPropertyInfo propInfo("Something.That.Doesnt.Exist");
    propInfo.declared(); // false
    // Connect something to the changed signal, keep checking it
    \endcode

    \section xmlvscdb XML vs.CDB

    When the introspection API is first used, a backend choice is being made. \b CDB backend
    (reading data from \c 'cache.cdb' ) is used if the tiny database cache file exists
    in the registry. The standard (slower) \b XML backend is used in other cases.

    It's possible to force a usage of a particular backend.
    This can be done by calling the \c instance method with a string name of the backend:

    \code
    ContextRegistryInfo::instance("cdb"); // or "xml"
    \endcode

    This needs to be done early enough before the introspection API is first used.
    For more information about the \b xml and \cdb backends read the \ref UpdatingContextProviders page.

*/

/*!
    \page MigratingFromDuiValueSpace

    \brief libcontextsubscriber is a replacement library for DuiValueSpace which is deprecated.

    DuiValueSpace, the old subscription library providing the keys, is deprecated. This library
    is a replacement for it, providing better API and better implementation while maintaining the
    same core ideas and structure.

    \section quicklook A quick look

    The following code for creating a handle for a context property:

    \code
    DuiValueSpaceItem topEdge("Context.Screen.TopEdge");
    QObject::connect(&topEdge, SIGNAL(valueChanged()),
                     this, SLOT(topEdgeChanged()));
    \endcode

    becomes:

    \code
    ContextProperty topEdge("Screen.TopEdge");
    QObject::connect(&topEdge, SIGNAL(valueChanged()),
                     this, SLOT(topEdgeChanged()));
    \endcode

    The following code for listing the available context keys:

    \code
    DuiValueSpaceItem::listKeys();
    \endcode

    becomes:

    \code
    ContextRegistryInfo::instance()->listKeys();
    \endcode

    \section prefix The Context. prefix

    In \b DuiValueSpace and accompanying packages, the properties used to
    have a "Context." prefix. For example:

    \code
    Context.Screen.TopEdge
    Context.Screen.IsCovered
    \endcode

    This 'Context.' has been dropped now from \b libcontextsubscriber and
    all the provider packages. Providers now explicitly provide properties
    with keys like:

    \code
    Screen.TopEdge
    Screen.IsCovered
    \endcode

    For compatibility reasons the 'Context.' prefix is still supported in
    newer releases of \b DuiValueSpace. The \b DuiValueSpace library transparently
    adds the 'Context.' prefix to all access functions.

    A call to:

    \code
    DuiValueSpaceItem topEdge("Context.Screen.TopEdge");
    \endcode

    ...is internally in \b DuiValueSpace converted to actual \c Screen.TopEdge
    wire access. This mechanism has been introduced to make the
    \b DuiValueSpace and \b libcontextsubscriber libraries parallel-installable.

    It's expected that all \b DuiValueSpace clients migrate to \b libcontextsubscriber
    eventually and \b DuiValueSpace library will be removed.

    \warning When migrating to \b libcontextsubscriber make sure to remove the 'Context.'
    from you key access paths.
*/

/*!
    \class ContextPropertyInfo

    \brief A class to introspect a context property details.

    This class is used to obtain information about a given key in the context registry.
    The information can be provided either from xml files or from a cdb database.
    It's possible to query the type, the provider and the documentation of the given
    key/property.
*/

/// Constructs a new ContextPropertyInfo for \a key with the given \a parent.
/// The object can be used to perform introspection on the given \a key.
/// \param key The full name of the key.
ContextPropertyInfo::ContextPropertyInfo(const QString &key, QObject *parent)
    : QObject(parent)
{
    keyName = key;

    if (key != "") {
        InfoBackend* infoBackend = InfoBackend::instance();
        sconnect(infoBackend, SIGNAL(keyChanged(QString)),
                 this, SLOT(onKeyChanged(QString)));

        cachedTypeInfo = infoBackend->typeInfoForKey(keyName);
        cachedDoc = infoBackend->docForKey(keyName);
        cachedDeclared = infoBackend->keyDeclared(keyName);
        cachedProviders = infoBackend->providersForKey(keyName);
    }
}

/// Returns the full name of the introspected key.
QString ContextPropertyInfo::key() const
{
    QMutexLocker lock(&cacheLock);
    return keyName;
}

/// Returns the doc (documentation) for the introspected key.
QString ContextPropertyInfo::doc() const
{
    QMutexLocker lock(&cacheLock);
    return cachedDoc;
}

/// Returns the old-style type name for the introspected key. To be deprecated soon.
QString ContextPropertyInfo::type() const
{
    QMutexLocker lock(&cacheLock);

    QString typeInfoName = cachedTypeInfo.name();
    if (typeInfoName == "integer")
        return "INT";
    else if (typeInfoName == "bool")
        return "TRUTH";
    else if (typeInfoName == "string")
        return "STRING";
    else if (typeInfoName == "number")
        return "DOUBLE";
    else
        return "";
}

/// Returns the advanced type info for the introspected key.
ContextTypeInfo ContextPropertyInfo::typeInfo() const
{
    QMutexLocker lock(&cacheLock);
    return cachedTypeInfo;
}

/// DEPRECATED Returns true if the key exists in the registry.
/// This function is deprecated, use declared() instead.
bool ContextPropertyInfo::exists() const
{
    contextWarning() << F_DEPRECATION << "ContextPropertyInfo::exists() is deprecated.";
    QMutexLocker lock(&cacheLock);
    return cachedDeclared;
}

/// Returns true if the key is declared in the registry
/// (it "exists").
bool ContextPropertyInfo::declared() const
{
    QMutexLocker lock(&cacheLock);
    return cachedDeclared;
}

/// Returns true if the key is provided by someone.
bool ContextPropertyInfo::provided() const
{
    QMutexLocker lock(&cacheLock);
    return (cachedProviders.size() > 0);
}

/// DEPRECATED Returns the name of the plugin supplying this property.
/// This function is deprecated, use providers() instead.
QString ContextPropertyInfo::plugin() const
{
    contextWarning() << F_DEPRECATION << "ContextPropertyInfo::plugin() is deprecated.";
    return plugin_i();
}

QString ContextPropertyInfo::plugin_i() const
{
    QMutexLocker lock(&cacheLock);
    if (cachedProviders.size() == 0)
        return "";
    else
        return cachedProviders.at(0).plugin;
}

/// DEPRECATED Returns the construction parameter for the Provider supplying this property
/// This function is deprecated, use providers() instead.
QString ContextPropertyInfo::constructionString() const
{
    contextWarning() << F_DEPRECATION << "ContextPropertyInfo::constructionString() is deprecated.";
    return constructionString_i();
}

QString ContextPropertyInfo::constructionString_i() const
{
    QMutexLocker lock(&cacheLock);
    if (cachedProviders.size() == 0)
        return "";
    else
        return cachedProviders.at(0).constructionString;
}

/// DEPRECATED Returns the dbus name of the provider supplying this
/// property/key. This function is maintained for backwards
/// compatibility. Use providers() instead.
QString ContextPropertyInfo::providerDBusName_i() const
{
    QMutexLocker lock(&cacheLock);
    if (cachedProviders.size() == 0)
        return "";
    else {
        QString plg = cachedProviders.at(0).plugin;
        QString constructionString = cachedProviders.at(0).constructionString;
        if (plg == "contextkit-dbus")
            return constructionString.split(":").last();
        else
            return "";
    }
}

/// DEPRECATED Returns the dbus name of the provider supplying this
/// property/key. This function is maintained for backwards
/// compatibility. Use listProviders() instead.
QString ContextPropertyInfo::providerDBusName() const
{
    contextWarning() << F_DEPRECATION << "ContextPropertyInfo::providerDBusName() is deprecated.";
    return providerDBusName_i();
}
QDBusConnection::BusType ContextPropertyInfo::providerDBusType_i() const
{
    QMutexLocker lock(&cacheLock);
    QString busType = "";

    if (cachedProviders.size() > 0) {
        QString plg = cachedProviders.at(0).plugin;
        QString constructionString = cachedProviders.at(0).constructionString;
        if (plg == "contextkit-dbus")
            busType = constructionString.split(":").first();
    }

    if (busType == "system")
        return QDBusConnection::SystemBus;
    else /* if (busType == "session") */
        return QDBusConnection::SessionBus;
}

/// DEPRECATED Returns the bus type of the provider supplying this property/key.
/// Ie. if it's a session bus or a system bus. This function is
/// maintained for backwards compatibility. Use listProviders() instead.
QDBusConnection::BusType ContextPropertyInfo::providerDBusType() const
{
    contextWarning() << F_DEPRECATION << "ContextPropertyInfo::providerDBusType() is deprecated.";
    return providerDBusType_i();
}

/* Slots */

/// This slot is connected to the \a keyChanged signal of the
/// actual infobackend instance. It's executed on every change to any
/// of the keys. We first check if the data concerns us. Next we
/// update the cached values and fire the actual signals.
void ContextPropertyInfo::onKeyChanged(const QString& key)
{
    QMutexLocker lock(&cacheLock);

    if (key != keyName)
        return;

    // Update caches
    cachedTypeInfo = InfoBackend::instance()->typeInfoForKey(keyName);
    cachedDoc = InfoBackend::instance()->docForKey(keyName);
    cachedDeclared = InfoBackend::instance()->keyDeclared(keyName);
    cachedProviders = InfoBackend::instance()->providersForKey(keyName);

    // Release the lock before emitting the signals; otherwise
    // listeners trying to access cached values would create a
    // deadlock.
    lock.unlock();

    // Emit the needed signals
    emit changed(keyName);
    emit typeChanged(type());
    emit existsChanged(cachedDeclared);
    emit providedChanged((cachedProviders.size() > 0));

    emit providerChanged(providerDBusName_i());
    emit providerDBusTypeChanged(providerDBusType_i());
    emit pluginChanged(plugin_i(), constructionString_i());
}

/// Returns a list of providers that provide this key.
const QList<ContextProviderInfo> ContextPropertyInfo::providers() const
{
    QMutexLocker lock(&cacheLock);
    return cachedProviders;
}

/// Called when people connect to signals. Used to emit deprecation warnings
/// when people connect to deprecated signals.
void ContextPropertyInfo::connectNotify(const char *_signal)
{
    QObject::connectNotify(_signal);
    QLatin1String signal(_signal);

    if (signal == SIGNAL(providerChanged(QString)))
        contextWarning() << F_DEPRECATION << "ContextPropertyInfo::providerChanged signal is deprecated.";
    else if (signal == SIGNAL(providerDBusTypeChanged(QDBusConnection::BusType)))
        contextWarning() << F_DEPRECATION << "ContextPropertyInfo::providerDBusTypeChanged signal is deprecated.";
    else if (signal == SIGNAL(typeChanged(QString)))
        contextWarning() << F_DEPRECATION << "ContextPropertyInfo::typeChanged signal is deprecated.";
    else if (signal == SIGNAL(existsChanged(bool)))
        contextWarning() << F_DEPRECATION << "ContextPropertyInfo::existsChanged signal is deprecated.";
    else if (signal == SIGNAL(providedChanged(bool)))
        contextWarning() << F_DEPRECATION << "ContextPropertyInfo::providedChanged signal is deprecated.";
    else if (signal == SIGNAL(pluginChanged(QString,QString)))
        contextWarning() << F_DEPRECATION << "ContextPropertyInfo::pluginChanged signal is deprecated.";
}

/// Returns resolution strategy for this property. Resolution strategy defines how values
/// are computed in relation to multiple providers being present for one property.
ContextPropertyInfo::ResolutionStrategy ContextPropertyInfo::resolutionStrategy() const
{
    return ContextPropertyInfo::LastValue;
}
<|endoftext|>


<commit_before>#include "bookmarkmodel.h"
#include <QPainter>

void BookmarkModel::addNode(LiteApi::IEditorMark *mark, LiteApi::IEditorMarkNode *node)
{
    beginInsertRows(QModelIndex(), m_nodeList.size(), m_nodeList.size());

    BookmarkNode *bn = createBookmarkNode(mark,node);
    m_nodeList.append(bn);
    m_nodeMap.insert(node,bn);

    endInsertRows();
    //selectionModel()->setCurrentIndex(index(m_bookmarksList.size()-1 , 0, QModelIndex()), QItemSelectionModel::Select | QItemSelectionModel::Clear);
}

void BookmarkModel::removeNode(LiteApi::IEditorMark *mark, LiteApi::IEditorMarkNode *node)
{
    BookmarkNode *bn = findBookmarkNode(mark,node);
    if (!bn) {
        return;
    }
    int idx = m_nodeList.indexOf(bn);
    beginRemoveRows(QModelIndex(), idx, idx);

    m_nodeMap.remove(node);

    delete bn;

    m_nodeList.removeAt(idx);
    endRemoveRows();
 //   if (selectionModel()->currentIndex().isValid())
    //       selectionModel()->setCurrentIndex(selectionModel()->currentIndex(), QItemSelectionModel::Select | QItemSelectionModel::Clear);
}

void BookmarkModel::updateNode(LiteApi::IEditorMark *mark, LiteApi::IEditorMarkNode *node)
{
    BookmarkNode *bn = findBookmarkNode(mark,node);
    if (!bn) {
        return;
    }
    bn->setLineNumber(node->blockNumber()+1);
    bn->setLineText(node->block().text());
    int idx = m_nodeList.indexOf(bn);

    emit dataChanged(index(idx, 0, QModelIndex()), index(idx, 2, QModelIndex()));
}

BookmarkNode *BookmarkModel::createBookmarkNode(LiteApi::IEditorMark *mark, LiteApi::IEditorMarkNode *node) const
{
    BookmarkNode *n = new BookmarkNode();
    n->setFilePath(mark->filePath());
    n->setLineNumber(node->blockNumber()+1);
    n->setLineText(node->block().text());
    return n;
}

BookmarkNode *BookmarkModel::bookmarkNodeForIndex(const QModelIndex &index) const
{
    if (!index.isValid() || index.row() >= m_nodeList.size())
        return 0;
    return m_nodeList.at(index.row());
}

BookmarkNode *BookmarkModel::findBookmarkNode(LiteApi::IEditorMark */*mark*/, LiteApi::IEditorMarkNode *node) const
{
    return m_nodeMap.value(node);
}

BookmarkModel::BookmarkModel(QObject *parent)
    : QAbstractItemModel(parent)
{
}

QModelIndex BookmarkModel::index(int row, int column, const QModelIndex &parent) const
{
    if (parent.isValid())
        return QModelIndex();
    else
        return createIndex(row, column);
}

QModelIndex BookmarkModel::parent(const QModelIndex &index) const
{
    return QModelIndex();
}

int BookmarkModel::rowCount(const QModelIndex &parent) const
{
    if (parent.isValid())
        return 0;

    return m_nodeList.size();
}

int BookmarkModel::columnCount(const QModelIndex &parent) const
{
    if (parent.isValid())
        return 0;

    return 1;
}

QVariant BookmarkModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid() || index.column() !=0 || index.row() < 0 || index.row() >= m_nodeList.count())
        return QVariant();

    BookmarkNode *node = m_nodeList.at(index.row());
    if (role == BookmarkModel::FileName)
        return node->fileName();
    if (role == BookmarkModel::LineNumber)
        return node->lineNumber();
    if (role == BookmarkModel::FilePath)
        return node->filePath();
    if (role == BookmarkModel::LineText)
        return node->lineText();
    if (role == BookmarkModel::Note)
        return node->noteText();
    if (role == Qt::ToolTipRole)
        return QDir::toNativeSeparators(node->filePath());
    return QVariant();
}

BookmarkDelegate::BookmarkDelegate(QObject *parent)
    : QStyledItemDelegate(parent)
{
}

QSize BookmarkDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QStyleOptionViewItem opt = option;
    initStyleOption(&opt, index);

    QFontMetrics fm(option.font);
    QSize s;
    s.setWidth(option.rect.width());
    s.setHeight(fm.height() * 2 + 10);
    return s;
}

void BookmarkDelegate::generateGradientPixmap(int width, int height, const QColor &color, bool selected) const
{
    QColor c = color;
    c.setAlpha(0);

    QPixmap pixmap(width+1, height);
    pixmap.fill(c);

    QPainter painter(&pixmap);
    painter.setPen(Qt::NoPen);

    QLinearGradient lg;
    lg.setCoordinateMode(QGradient::ObjectBoundingMode);
    lg.setFinalStop(1,0);

    lg.setColorAt(0, c);
    lg.setColorAt(0.4, color);

    painter.setBrush(lg);
    painter.drawRect(0, 0, width+1, height);

    if (selected)
        m_selectedPixmap = pixmap;
    else
        m_normalPixmap = pixmap;
}

void BookmarkDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QStyleOptionViewItem opt = option;
    initStyleOption(&opt, index);
    painter->save();

    QFontMetrics fm(opt.font);
    static int lwidth = fm.width(QLatin1String("8888")) + 18;

    QColor backgroundColor;
    QColor textColor;

    bool selected = opt.state & QStyle::State_Selected;

    if (selected) {
        painter->setBrush(opt.palette.highlight().color());
        backgroundColor = opt.palette.highlight().color();
        if (!m_selectedPixmap)
            generateGradientPixmap(lwidth, fm.height()+1, backgroundColor, selected);
    } else {
        painter->setBrush(opt.palette.background().color());
        backgroundColor = opt.palette.background().color();
        if (!m_normalPixmap)
            generateGradientPixmap(lwidth, fm.height(), backgroundColor, selected);
    }
    painter->setPen(Qt::NoPen);
    painter->drawRect(opt.rect);

    // Set Text Color
    if (opt.state & QStyle::State_Selected)
        textColor = opt.palette.highlightedText().color();
    else
        textColor = opt.palette.text().color();

    painter->setPen(textColor);

    // TopLeft
    QString topLeft = index.data(BookmarkModel::FileName).toString();
    painter->drawText(6, 2 + opt.rect.top() + fm.ascent(), topLeft);

    QString topRight = index.data(BookmarkModel::LineNumber).toString();
    // Check whether we need to be fancy and paint some background
    int fwidth = fm.width(topLeft);
    if (fwidth + lwidth > opt.rect.width()) {
        int left = opt.rect.right() - lwidth;
        painter->drawPixmap(left, opt.rect.top(), selected ? m_selectedPixmap : m_normalPixmap);
    }
    // topRight
    painter->drawText(opt.rect.right() - fm.width(topRight) - 6 , 2 + opt.rect.top() + fm.ascent(), topRight);

    // Directory
    QColor mix;
    mix.setRgbF(0.7 * textColor.redF()   + 0.3 * backgroundColor.redF(),
                0.7 * textColor.greenF() + 0.3 * backgroundColor.greenF(),
                0.7 * textColor.blueF()  + 0.3 * backgroundColor.blueF());
    painter->setPen(mix);
//
//    QString directory = index.data(BookmarkManager::Directory).toString();
//    int availableSpace = opt.rect.width() - 12;
//    if (fm.width(directory) > availableSpace) {
//        // We need a shorter directory
//        availableSpace -= fm.width("...");
//
//        int pos = directory.size();
//        int idx;
//        forever {
//            idx = directory.lastIndexOf("/", pos-1);
//            if (idx == -1) {
//                // Can't happen, this means the string did fit after all?
//                break;
//            }
//            int width = fm.width(directory.mid(idx, pos-idx));
//            if (width > availableSpace) {
//                directory = "..." + directory.mid(pos);
//                break;
//            } else {
//                pos = idx;
//                availableSpace -= width;
//            }
//        }
//    }
//
//    painter->drawText(3, opt.rect.top() + fm.ascent() + fm.height() + 6, directory);

    QString lineText = index.data(BookmarkModel::Note).toString().trimmed();
    if (lineText.isEmpty())
        lineText = index.data(BookmarkModel::LineText).toString().trimmed();

    painter->drawText(6, opt.rect.top() + fm.ascent() + fm.height() + 6, lineText);

    // Separator lines
    const QRectF innerRect = QRectF(opt.rect).adjusted(0.5, 0.5, -0.5, -0.5);
    painter->setPen(QColor::fromRgb(150,150,150));
    painter->drawLine(innerRect.bottomLeft(), innerRect.bottomRight());
    painter->restore();
}
<commit_msg>bookmakrs: draw full path<commit_after>#include "bookmarkmodel.h"
#include <QPainter>

void BookmarkModel::addNode(LiteApi::IEditorMark *mark, LiteApi::IEditorMarkNode *node)
{
    beginInsertRows(QModelIndex(), m_nodeList.size(), m_nodeList.size());

    BookmarkNode *bn = createBookmarkNode(mark,node);
    m_nodeList.append(bn);
    m_nodeMap.insert(node,bn);

    endInsertRows();
    //selectionModel()->setCurrentIndex(index(m_bookmarksList.size()-1 , 0, QModelIndex()), QItemSelectionModel::Select | QItemSelectionModel::Clear);
}

void BookmarkModel::removeNode(LiteApi::IEditorMark *mark, LiteApi::IEditorMarkNode *node)
{
    BookmarkNode *bn = findBookmarkNode(mark,node);
    if (!bn) {
        return;
    }
    int idx = m_nodeList.indexOf(bn);
    beginRemoveRows(QModelIndex(), idx, idx);

    m_nodeMap.remove(node);

    delete bn;

    m_nodeList.removeAt(idx);
    endRemoveRows();
 //   if (selectionModel()->currentIndex().isValid())
    //       selectionModel()->setCurrentIndex(selectionModel()->currentIndex(), QItemSelectionModel::Select | QItemSelectionModel::Clear);
}

void BookmarkModel::updateNode(LiteApi::IEditorMark *mark, LiteApi::IEditorMarkNode *node)
{
    BookmarkNode *bn = findBookmarkNode(mark,node);
    if (!bn) {
        return;
    }
    bn->setLineNumber(node->blockNumber()+1);
    bn->setLineText(node->block().text());
    int idx = m_nodeList.indexOf(bn);

    emit dataChanged(index(idx, 0, QModelIndex()), index(idx, 2, QModelIndex()));
}

BookmarkNode *BookmarkModel::createBookmarkNode(LiteApi::IEditorMark *mark, LiteApi::IEditorMarkNode *node) const
{
    BookmarkNode *n = new BookmarkNode();
    n->setFilePath(mark->filePath());
    n->setLineNumber(node->blockNumber()+1);
    n->setLineText(node->block().text());
    return n;
}

BookmarkNode *BookmarkModel::bookmarkNodeForIndex(const QModelIndex &index) const
{
    if (!index.isValid() || index.row() >= m_nodeList.size())
        return 0;
    return m_nodeList.at(index.row());
}

BookmarkNode *BookmarkModel::findBookmarkNode(LiteApi::IEditorMark */*mark*/, LiteApi::IEditorMarkNode *node) const
{
    return m_nodeMap.value(node);
}

BookmarkModel::BookmarkModel(QObject *parent)
    : QAbstractItemModel(parent)
{
}

QModelIndex BookmarkModel::index(int row, int column, const QModelIndex &parent) const
{
    if (parent.isValid())
        return QModelIndex();
    else
        return createIndex(row, column);
}

QModelIndex BookmarkModel::parent(const QModelIndex &index) const
{
    return QModelIndex();
}

int BookmarkModel::rowCount(const QModelIndex &parent) const
{
    if (parent.isValid())
        return 0;

    return m_nodeList.size();
}

int BookmarkModel::columnCount(const QModelIndex &parent) const
{
    if (parent.isValid())
        return 0;

    return 1;
}

QVariant BookmarkModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid() || index.column() !=0 || index.row() < 0 || index.row() >= m_nodeList.count())
        return QVariant();

    BookmarkNode *node = m_nodeList.at(index.row());
    if (role == BookmarkModel::FileName)
        return node->fileName();
    if (role == BookmarkModel::LineNumber)
        return node->lineNumber();
    if (role == BookmarkModel::FilePath)
        return node->filePath();
    if (role == BookmarkModel::LineText)
        return node->lineText();
    if (role == BookmarkModel::Note)
        return node->noteText();
    if (role == Qt::ToolTipRole)
        return QDir::toNativeSeparators(node->filePath());
    return QVariant();
}

BookmarkDelegate::BookmarkDelegate(QObject *parent)
    : QStyledItemDelegate(parent)
{
}

QSize BookmarkDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QStyleOptionViewItem opt = option;
    initStyleOption(&opt, index);

    QFontMetrics fm(option.font);
    QSize s;
    s.setWidth(option.rect.width());
    s.setHeight(fm.height() * 2 + 10);
    return s;
}

void BookmarkDelegate::generateGradientPixmap(int width, int height, const QColor &color, bool selected) const
{
    QColor c = color;
    c.setAlpha(0);

    QPixmap pixmap(width+1, height);
    pixmap.fill(c);

    QPainter painter(&pixmap);
    painter.setPen(Qt::NoPen);

    QLinearGradient lg;
    lg.setCoordinateMode(QGradient::ObjectBoundingMode);
    lg.setFinalStop(1,0);

    lg.setColorAt(0, c);
    lg.setColorAt(0.4, color);

    painter.setBrush(lg);
    painter.drawRect(0, 0, width+1, height);

    if (selected)
        m_selectedPixmap = pixmap;
    else
        m_normalPixmap = pixmap;
}

void BookmarkDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QStyleOptionViewItem opt = option;
    initStyleOption(&opt, index);
    painter->save();

    QFontMetrics fm(opt.font);
    static int lwidth = fm.width(QLatin1String("8888")) + 18;

    QColor backgroundColor;
    QColor textColor;

    bool selected = opt.state & QStyle::State_Selected;

    if (selected) {
        painter->setBrush(opt.palette.highlight().color());
        backgroundColor = opt.palette.highlight().color();
        if (!m_selectedPixmap)
            generateGradientPixmap(lwidth, fm.height()+1, backgroundColor, selected);
    } else {
        painter->setBrush(opt.palette.background().color());
        backgroundColor = opt.palette.background().color();
        if (!m_normalPixmap)
            generateGradientPixmap(lwidth, fm.height(), backgroundColor, selected);
    }
    painter->setPen(Qt::NoPen);
    painter->drawRect(opt.rect);

    // Set Text Color
    if (opt.state & QStyle::State_Selected)
        textColor = opt.palette.highlightedText().color();
    else
        textColor = opt.palette.text().color();

    painter->setPen(textColor);

    // TopLeft
    QString topLeft = index.data(BookmarkModel::FileName).toString();
    //painter->drawText(6, 2 + opt.rect.top() + fm.ascent(), topLeft);

    QString topRight = index.data(BookmarkModel::LineNumber).toString();
    // Check whether we need to be fancy and paint some background
    int fwidth = fm.width(topLeft);
    if (fwidth + lwidth > opt.rect.width()) {
        int left = opt.rect.right() - lwidth;
        painter->drawPixmap(left, opt.rect.top(), selected ? m_selectedPixmap : m_normalPixmap);
    }
    // topRight
    painter->drawText(opt.rect.right() - fm.width(topRight) - 6 , 2 + opt.rect.top() + fm.ascent(), topRight);

    // Directory
    QColor mix;
    mix.setRgbF(0.7 * textColor.redF()   + 0.3 * backgroundColor.redF(),
                0.7 * textColor.greenF() + 0.3 * backgroundColor.greenF(),
                0.7 * textColor.blueF()  + 0.3 * backgroundColor.blueF());
    painter->setPen(mix);

    QString directory = index.data(BookmarkModel::FilePath).toString();
    int availableSpace = opt.rect.width() - fm.width("888");
    if (fm.width(directory) > availableSpace) {
        // We need a shorter directory
        availableSpace -= fm.width("...");

        int pos = directory.size();
        int idx;
        forever {
            idx = directory.lastIndexOf("/", pos-1);
            if (idx == -1) {
                // Can't happen, this means the string did fit after all?
                break;
            }
            int width = fm.width(directory.mid(idx, pos-idx));
            if (width > availableSpace) {
                directory = "..." + directory.mid(pos);
                break;
            } else {
                pos = idx;
                availableSpace -= width;
            }
        }
    }

    //painter->drawText(3, opt.rect.top() + fm.ascent() + fm.height() + 6, directory);
    painter->drawText(6, 2 + opt.rect.top() + fm.ascent(), directory);

    QString lineText = index.data(BookmarkModel::Note).toString().trimmed();
    if (lineText.isEmpty())
        lineText = index.data(BookmarkModel::LineText).toString().trimmed();

    painter->drawText(6, opt.rect.top() + fm.ascent() + fm.height() + 6, lineText);

    // Separator lines
    const QRectF innerRect = QRectF(opt.rect).adjusted(0.5, 0.5, -0.5, -0.5);
    painter->setPen(QColor::fromRgb(150,150,150));
    painter->drawLine(innerRect.bottomLeft(), innerRect.bottomRight());
    painter->restore();
}
<|endoftext|>