<commit_before>/* ***** BEGIN LICENSE BLOCK *****
 *
 *   Copyright (C) 2014, Peter Hatina <phatina@redhat.com>
 *
 *   This library is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU Lesser General Public License as
 *   published by the Free Software Foundation, either version 2.1 of the
 *   License, or (at your option) any later version.
 *
 *   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 program; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *   MA 02110-1301 USA
 *
 * ***** END LICENSE BLOCK ***** */

#include <sstream>
#include <boost/python/class.hpp>
#include <boost/python/dict.hpp>
#include <boost/python/list.hpp>
#include <boost/python/str.hpp>
#include "lmiwbem_extract.h"
#include "lmiwbem_instance.h"
#include "lmiwbem_instance_name.h"
#include "lmiwbem_nocasedict.h"
#include "lmiwbem_property.h"
#include "lmiwbem_qualifier.h"
#include "lmiwbem_util.h"

namespace bp = boost::python;

CIMInstance::CIMInstance()
    : m_classname()
    , m_path()
    , m_properties()
    , m_qualifiers()
    , m_property_list()
    , m_rc_inst_path()
    , m_rc_inst_properties()
    , m_rc_inst_qualifiers()
{
}

CIMInstance::CIMInstance(
    const bp::object &classname,
    const bp::object &properties,
    const bp::object &qualifiers,
    const bp::object &path,
    const bp::object &property_list)
{
    m_classname = lmi::extract_or_throw<std::string>(classname, "classname");

    // We store properties in NocaseDict. Convert python's dict, if necessary.
    lmi::extract<bp::dict> ext_properties(properties);
    if (ext_properties.check())
        m_properties = NocaseDict::create(properties);
    else
        m_properties = lmi::get_or_throw<NocaseDict>(properties, "properties");

    // We store qualifiers in NocaseDict. Convert python's dict, if necessary.
    lmi::extract<bp::dict> ext_qualifiers(qualifiers);
    if (ext_qualifiers.check())
        m_qualifiers = NocaseDict::create(qualifiers);
    else
        m_qualifiers = lmi::get_or_throw<NocaseDict>(qualifiers, "qualifiers");

    m_path = lmi::get_or_throw<CIMInstanceName, bp::object>(path, "path");
    m_property_list = lmi::get_or_throw<bp::list, bp::object>(property_list, "property_list");
}

void CIMInstance::init_type()
{
    CIMBase::s_class = bp::class_<CIMInstance>("CIMInstance", bp::init<>())
        .def(bp::init<
            const bp::object &,
            const bp::object &,
            const bp::object &,
            const bp::object &,
            const bp::object &>((
                bp::arg("classname"),
                bp::arg("properties") = NocaseDict::create(),
                bp::arg("qualifiers") = NocaseDict::create(),
                bp::arg("path") = bp::object(),
                bp::arg("property_list") = bp::object()),
                "Constructs a :py:class:`CIMInstance`.\n\n"
                ":param str classname: String containing a class name\n"
                ":param NocaseDict properties: Dictionary containing :py:class:`CIMProperty`\n"
                ":param NocaseDict qualifiers: Dictionary containing :py:class:`CIMQualifier`\n"
                ":param CIMInstanceName path: Object path\n"
                ":param list property_list: List containing strings of properties"))
        .def("__repr__", &CIMInstance::repr,
            ":returns: pretty string of the object")
        .add_property("classname",
            &CIMInstance::getClassname,
            &CIMInstance::setClassname,
            "Property storing class name\n\n"
            ":returns: class name\n"
            ":rtype: string")
        .add_property("path",
            &CIMInstance::getPath,
            &CIMInstance::setPath,
            "Property storing object path\n\n"
            ":returns: object path\n"
            ":rtype: :py:class:`CIMInstanceName`")
        .add_property("properties",
            &CIMInstance::getProperties,
            &CIMInstance::setProperties,
            "Property storing instance properties\n\n"
            ":returns: dictionary containing instance properties\n"
            ":rtype: :py:class:`NocaseDict`")
        .add_property("qualifiers",
            &CIMInstance::getQualifiers,
            &CIMInstance::setQualifiers,
            "Property storing instance qualifiers\n\n"
            ":returns: dictionary containing instance qualifiers\n"
            ":rtype: :py:class:`NocaseDict`")
        .add_property("property_list",
            &CIMInstance::getPropertyList,
            &CIMInstance::setPropertyList,
            "Property storing instance properties\n\n"
            ":returns: list of instance properties\n")
        ;
}

bp::object CIMInstance::create(const Pegasus::CIMInstance &instance)
{
    bp::object inst = CIMBase::s_class();
    CIMInstance &fake_this = lmi::extract<CIMInstance&>(inst);
    fake_this.m_classname = instance.getClassName().getString().getCString();

    // Store path for lazy evaluation
    fake_this.m_rc_inst_path.set(instance.getPath());

    // Store list of properties for lazy evaluation
    fake_this.m_rc_inst_properties.set(std::list<Pegasus::CIMConstProperty>());
    Pegasus::Uint32 cnt = instance.getPropertyCount();
    for (Pegasus::Uint32 i = 0; i < cnt; ++i)
        fake_this.m_rc_inst_properties.get()->push_back(instance.getProperty(i));

    // Store list of qualifiers for lazy evaluation
    fake_this.m_rc_inst_qualifiers.set(std::list<Pegasus::CIMConstQualifier>());
    cnt = instance.getQualifierCount();
    for (Pegasus::Uint32 i = 0; i < cnt; ++i)
        fake_this.m_rc_inst_qualifiers.get()->push_back(instance.getQualifier(i));

    return inst;
}

bp::object CIMInstance::create(const Pegasus::CIMObject &object)
{
    return create(Pegasus::CIMInstance(object));
}

Pegasus::CIMInstance CIMInstance::asPegasusCIMInstance()
{
    Pegasus::CIMInstance instance(Pegasus::CIMName(m_classname.c_str()));
    const CIMInstanceName &path = lmi::extract<CIMInstanceName&>(getPath());

    // Set CIMObjectPath
    instance.setPath(path.asPegasusCIMObjectPath());

    // Add all the properties
    const NocaseDict &properties = lmi::extract<NocaseDict&>(getProperties());
    nocase_map_t::const_iterator it;
    for (it = properties.begin(); it != properties.end(); ++it) {
        CIMProperty &property = lmi::extract<CIMProperty&>(it->second);
        instance.addProperty(property.asPegasusCIMProperty());
    }

    // Add all the qualifiers
    const NocaseDict &qualifiers = lmi::extract<NocaseDict&>(getQualifiers());
    for (it = qualifiers.begin(); it != qualifiers.end(); ++it) {
        CIMQualifier &qualifier = lmi::extract<CIMQualifier&>(it->second);
        instance.addQualifier(qualifier.asPegasusCIMQualifier());
    }

    return instance;
}

std::string CIMInstance::repr()
{
    std::stringstream ss;
    ss << "CIMInstance(classname='" << m_classname << "', ...)";
    return ss.str();
}

bp::object CIMInstance::getClassname()
{
    return std_string_as_pyunicode(m_classname);
}

bp::object CIMInstance::getPath()
{
    if (!m_rc_inst_path.empty()) {
        m_path = CIMInstanceName::create(*m_rc_inst_path.get());
        m_rc_inst_path.unref();
    }

    return m_path;
}

bp::object CIMInstance::getProperties()
{
    evalProperties();
    return m_properties;
}

bp::object CIMInstance::getQualifiers()
{
    if (!m_rc_inst_qualifiers.empty()) {
        m_qualifiers = NocaseDict::create();
        std::list<Pegasus::CIMConstQualifier> &qualifiers = *m_rc_inst_qualifiers.get();
        std::list<Pegasus::CIMConstQualifier>::const_iterator it;
        for (it = qualifiers.begin(); it != qualifiers.end(); ++it) {
            bp::object qualif_name(it->getName());
            bp::setitem(m_qualifiers, qualif_name, CIMQualifier::create(*it));
        }
        m_rc_inst_qualifiers.unref();
    }

    return m_qualifiers;
}

bp::object CIMInstance::getPropertyList()
{
    evalProperties();
    return m_property_list;
}

void CIMInstance::setClassname(const bp::object &classname)
{
    m_classname = lmi::extract_or_throw<std::string>(classname, "classname");
}

void CIMInstance::setPath(const bp::object &path)
{
    m_path = lmi::get_or_throw<CIMInstanceName>(path);

    // Unref cached resource, it will never be used
    m_rc_inst_path.unref();
}

void CIMInstance::setProperties(const bp::object &properties)
{
    if (!m_rc_inst_properties.empty())
        evalProperties();

    m_properties = lmi::get_or_throw<NocaseDict, bp::dict>(properties, "properties");

    // Unref cached resource, it will never be used
    m_rc_inst_properties.unref();
}

void CIMInstance::setQualifiers(const bp::object &qualifiers)
{
    m_qualifiers = lmi::get_or_throw<NocaseDict, bp::dict>(qualifiers, "qualifiers");

    // Unref cached resource, it will never be used
    m_rc_inst_qualifiers.unref();
}

void CIMInstance::setPropertyList(const bp::object &property_list)
{
    if (!m_rc_inst_properties.empty())
        evalProperties();

    m_property_list = lmi::get_or_throw<bp::list>(property_list);

    // Unref cached resource, it will never be used
    m_rc_inst_properties.unref();
}

void CIMInstance::evalProperties()
{
    if (m_rc_inst_properties.empty())
        return;

    m_properties = NocaseDict::create();
    bp::list property_list;
    std::list<Pegasus::CIMConstProperty>::const_iterator it;
    std::list<Pegasus::CIMConstProperty> &properties = *m_rc_inst_properties.get();
    for (it = properties.begin(); it != properties.end(); ++it) {
        bp::object prop_name(it->getName());
        bp::setitem(m_properties, prop_name, CIMProperty::create(*it));
        property_list.append(prop_name);
    }

    m_property_list = property_list;
    m_rc_inst_properties.unref();
}
<commit_msg>introduce CIMInstance auto-conversion of plain values into CIMProperty<commit_after>/* ***** BEGIN LICENSE BLOCK *****
 *
 *   Copyright (C) 2014, Peter Hatina <phatina@redhat.com>
 *
 *   This library is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU Lesser General Public License as
 *   published by the Free Software Foundation, either version 2.1 of the
 *   License, or (at your option) any later version.
 *
 *   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 program; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *   MA 02110-1301 USA
 *
 * ***** END LICENSE BLOCK ***** */

#include <sstream>
#include <boost/python/class.hpp>
#include <boost/python/dict.hpp>
#include <boost/python/list.hpp>
#include <boost/python/str.hpp>
#include "lmiwbem_extract.h"
#include "lmiwbem_instance.h"
#include "lmiwbem_instance_name.h"
#include "lmiwbem_nocasedict.h"
#include "lmiwbem_property.h"
#include "lmiwbem_qualifier.h"
#include "lmiwbem_util.h"

namespace bp = boost::python;

CIMInstance::CIMInstance()
    : m_classname()
    , m_path()
    , m_properties()
    , m_qualifiers()
    , m_property_list()
    , m_rc_inst_path()
    , m_rc_inst_properties()
    , m_rc_inst_qualifiers()
{
}

CIMInstance::CIMInstance(
    const bp::object &classname,
    const bp::object &properties,
    const bp::object &qualifiers,
    const bp::object &path,
    const bp::object &property_list)
{
    m_classname = lmi::extract_or_throw<std::string>(classname, "classname");

    // We store properties in NocaseDict. Convert python's dict, if necessary.
    lmi::extract<bp::dict> ext_properties(properties);
    if (ext_properties.check())
        m_properties = NocaseDict::create(properties);
    else
        m_properties = lmi::get_or_throw<NocaseDict>(properties, "properties");

    // Convert plain values
    NocaseDict &prop_dict = lmi::extract<NocaseDict&>(m_properties);
    if (!prop_dict.empty() && !isinstance(prop_dict.begin()->second, CIMProperty::type())) {
        nocase_map_t::iterator it;
        for (it = prop_dict.begin(); it != prop_dict.end(); ++it) {
            it->second = CIMProperty::create(bp::object(it->first), it->second);
        }
    }

    // We store qualifiers in NocaseDict. Convert python's dict, if necessary.
    lmi::extract<bp::dict> ext_qualifiers(qualifiers);
    if (ext_qualifiers.check())
        m_qualifiers = NocaseDict::create(qualifiers);
    else
        m_qualifiers = lmi::get_or_throw<NocaseDict>(qualifiers, "qualifiers");

    m_path = lmi::get_or_throw<CIMInstanceName, bp::object>(path, "path");
    m_property_list = lmi::get_or_throw<bp::list, bp::object>(property_list, "property_list");
}

void CIMInstance::init_type()
{
    CIMBase::s_class = bp::class_<CIMInstance>("CIMInstance", bp::init<>())
        .def(bp::init<
            const bp::object &,
            const bp::object &,
            const bp::object &,
            const bp::object &,
            const bp::object &>((
                bp::arg("classname"),
                bp::arg("properties") = NocaseDict::create(),
                bp::arg("qualifiers") = NocaseDict::create(),
                bp::arg("path") = bp::object(),
                bp::arg("property_list") = bp::object()),
                "Constructs a :py:class:`CIMInstance`.\n\n"
                ":param str classname: String containing a class name\n"
                ":param NocaseDict properties: Dictionary containing :py:class:`CIMProperty`\n"
                ":param NocaseDict qualifiers: Dictionary containing :py:class:`CIMQualifier`\n"
                ":param CIMInstanceName path: Object path\n"
                ":param list property_list: List containing strings of properties"))
        .def("__repr__", &CIMInstance::repr,
            ":returns: pretty string of the object")
        .add_property("classname",
            &CIMInstance::getClassname,
            &CIMInstance::setClassname,
            "Property storing class name\n\n"
            ":returns: class name\n"
            ":rtype: string")
        .add_property("path",
            &CIMInstance::getPath,
            &CIMInstance::setPath,
            "Property storing object path\n\n"
            ":returns: object path\n"
            ":rtype: :py:class:`CIMInstanceName`")
        .add_property("properties",
            &CIMInstance::getProperties,
            &CIMInstance::setProperties,
            "Property storing instance properties\n\n"
            ":returns: dictionary containing instance properties\n"
            ":rtype: :py:class:`NocaseDict`")
        .add_property("qualifiers",
            &CIMInstance::getQualifiers,
            &CIMInstance::setQualifiers,
            "Property storing instance qualifiers\n\n"
            ":returns: dictionary containing instance qualifiers\n"
            ":rtype: :py:class:`NocaseDict`")
        .add_property("property_list",
            &CIMInstance::getPropertyList,
            &CIMInstance::setPropertyList,
            "Property storing instance properties\n\n"
            ":returns: list of instance properties\n")
        ;
}

bp::object CIMInstance::create(const Pegasus::CIMInstance &instance)
{
    bp::object inst = CIMBase::s_class();
    CIMInstance &fake_this = lmi::extract<CIMInstance&>(inst);
    fake_this.m_classname = instance.getClassName().getString().getCString();

    // Store path for lazy evaluation
    fake_this.m_rc_inst_path.set(instance.getPath());

    // Store list of properties for lazy evaluation
    fake_this.m_rc_inst_properties.set(std::list<Pegasus::CIMConstProperty>());
    Pegasus::Uint32 cnt = instance.getPropertyCount();
    for (Pegasus::Uint32 i = 0; i < cnt; ++i)
        fake_this.m_rc_inst_properties.get()->push_back(instance.getProperty(i));

    // Store list of qualifiers for lazy evaluation
    fake_this.m_rc_inst_qualifiers.set(std::list<Pegasus::CIMConstQualifier>());
    cnt = instance.getQualifierCount();
    for (Pegasus::Uint32 i = 0; i < cnt; ++i)
        fake_this.m_rc_inst_qualifiers.get()->push_back(instance.getQualifier(i));

    return inst;
}

bp::object CIMInstance::create(const Pegasus::CIMObject &object)
{
    return create(Pegasus::CIMInstance(object));
}

Pegasus::CIMInstance CIMInstance::asPegasusCIMInstance()
{
    Pegasus::CIMInstance instance(Pegasus::CIMName(m_classname.c_str()));
    const CIMInstanceName &path = lmi::extract<CIMInstanceName&>(getPath());

    // Set CIMObjectPath
    instance.setPath(path.asPegasusCIMObjectPath());

    // Add all the properties
    const NocaseDict &properties = lmi::extract<NocaseDict&>(getProperties());
    nocase_map_t::const_iterator it;
    for (it = properties.begin(); it != properties.end(); ++it) {
        CIMProperty &property = lmi::extract<CIMProperty&>(it->second);
        instance.addProperty(property.asPegasusCIMProperty());
    }

    // Add all the qualifiers
    const NocaseDict &qualifiers = lmi::extract<NocaseDict&>(getQualifiers());
    for (it = qualifiers.begin(); it != qualifiers.end(); ++it) {
        CIMQualifier &qualifier = lmi::extract<CIMQualifier&>(it->second);
        instance.addQualifier(qualifier.asPegasusCIMQualifier());
    }

    return instance;
}

std::string CIMInstance::repr()
{
    std::stringstream ss;
    ss << "CIMInstance(classname='" << m_classname << "', ...)";
    return ss.str();
}

bp::object CIMInstance::getClassname()
{
    return std_string_as_pyunicode(m_classname);
}

bp::object CIMInstance::getPath()
{
    if (!m_rc_inst_path.empty()) {
        m_path = CIMInstanceName::create(*m_rc_inst_path.get());
        m_rc_inst_path.unref();
    }

    return m_path;
}

bp::object CIMInstance::getProperties()
{
    evalProperties();
    return m_properties;
}

bp::object CIMInstance::getQualifiers()
{
    if (!m_rc_inst_qualifiers.empty()) {
        m_qualifiers = NocaseDict::create();
        std::list<Pegasus::CIMConstQualifier> &qualifiers = *m_rc_inst_qualifiers.get();
        std::list<Pegasus::CIMConstQualifier>::const_iterator it;
        for (it = qualifiers.begin(); it != qualifiers.end(); ++it) {
            bp::object qualif_name(it->getName());
            bp::setitem(m_qualifiers, qualif_name, CIMQualifier::create(*it));
        }
        m_rc_inst_qualifiers.unref();
    }

    return m_qualifiers;
}

bp::object CIMInstance::getPropertyList()
{
    evalProperties();
    return m_property_list;
}

void CIMInstance::setClassname(const bp::object &classname)
{
    m_classname = lmi::extract_or_throw<std::string>(classname, "classname");
}

void CIMInstance::setPath(const bp::object &path)
{
    m_path = lmi::get_or_throw<CIMInstanceName>(path);

    // Unref cached resource, it will never be used
    m_rc_inst_path.unref();
}

void CIMInstance::setProperties(const bp::object &properties)
{
    if (!m_rc_inst_properties.empty())
        evalProperties();

    m_properties = lmi::get_or_throw<NocaseDict, bp::dict>(properties, "properties");

    // Unref cached resource, it will never be used
    m_rc_inst_properties.unref();
}

void CIMInstance::setQualifiers(const bp::object &qualifiers)
{
    m_qualifiers = lmi::get_or_throw<NocaseDict, bp::dict>(qualifiers, "qualifiers");

    // Unref cached resource, it will never be used
    m_rc_inst_qualifiers.unref();
}

void CIMInstance::setPropertyList(const bp::object &property_list)
{
    if (!m_rc_inst_properties.empty())
        evalProperties();

    m_property_list = lmi::get_or_throw<bp::list>(property_list);

    // Unref cached resource, it will never be used
    m_rc_inst_properties.unref();
}

void CIMInstance::evalProperties()
{
    if (m_rc_inst_properties.empty())
        return;

    m_properties = NocaseDict::create();
    bp::list property_list;
    std::list<Pegasus::CIMConstProperty>::const_iterator it;
    std::list<Pegasus::CIMConstProperty> &properties = *m_rc_inst_properties.get();
    for (it = properties.begin(); it != properties.end(); ++it) {
        bp::object prop_name(it->getName());
        bp::setitem(m_properties, prop_name, CIMProperty::create(*it));
        property_list.append(prop_name);
    }

    m_property_list = property_list;
    m_rc_inst_properties.unref();
}
<|endoftext|>


<commit_before>/****************************************************************************

This file is part of the QtMediaHub project on http://www.gitorious.org.

Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).*
All rights reserved.

Contact:  Nokia Corporation (qt-info@nokia.com)**

You may use this file under the terms of the BSD license as follows:

"Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."

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

#include "mediamodel.h"
#include "mediascanner.h"
#include "dbreader.h"
#include "backend.h"
#include "qmh-config.h"

#define DEBUG if (0) qDebug() << this << __PRETTY_FUNCTION__

MediaModel::MediaModel(QObject *parent)
    : QAbstractItemModel(parent), m_loading(false), m_loaded(false), m_reader(0), m_readerThread(0)
{
    m_refreshTimer.setInterval(Config::value("mediamodel-refresh-interval", 10000));
    connect(&m_refreshTimer, SIGNAL(timeout()), this, SLOT(refresh()));

    MediaScanner *scanner = Backend::instance()->mediaScanner();
    connect(scanner, SIGNAL(scanStarted(QString)), this, SLOT(handleScanStarted(QString)));
    connect(scanner, SIGNAL(scanFinished(QString)), this, SLOT(handleScanFinished(QString)));
    connect(scanner, SIGNAL(searchPathRemoved(QString, QString)), this, SLOT(handleScanFinished(QString)));

    m_readerThread = new QThread(this);
    m_readerThread->start();
}

MediaModel::~MediaModel()
{
    if (m_reader) {
        m_reader->stop();
        m_reader->deleteLater();
        m_readerThread->quit();
        m_readerThread->wait();
    }
}

QString MediaModel::part() const
{
    return m_structure.split("|").value(m_cursor.length());
}

QString MediaModel::mediaType() const
{
    return m_mediaType;
}

void MediaModel::setMediaType(const QString &type)
{
    if (type == m_mediaType)
        return;

    DEBUG << type;

    m_mediaType = type;
    emit mediaTypeChanged();

    // Add the fields of the table as role names
    QSqlDriver *driver = Backend::instance()->mediaDatabase().driver();
    QSqlRecord record = driver->record(m_mediaType);
    if (record.isEmpty())
        qWarning() << "Table " << type << " is not valid it seems";

    QHash<int, QByteArray> hash = roleNames();
    hash.insert(DotDotRole, "dotdot");
    hash.insert(IsLeafRole, "isLeaf");
    hash.insert(ModelIndexRole,"modelIndex");
    hash.insert(PreviewUrlRole, "previewUrl");

    for (int i = 0; i < record.count(); i++) {
        hash.insert(FieldRolesBegin + i, record.fieldName(i).toUtf8());
        m_fieldToRole.insert(record.fieldName(i), FieldRolesBegin + i);
    }

    setRoleNames(hash);

    reload();
}

QString MediaModel::structure() const
{
    return m_structure;
}

void MediaModel::setStructure(const QString &str)
{
    if (str == m_structure)
        return;
    DEBUG << str;
    m_structure = str;
    m_layoutInfo.clear();
    foreach(const QString &part, m_structure.split("|"))
        m_layoutInfo.append(part.split(","));

    reload();

    emit structureChanged();
}

void MediaModel::enter(int index)
{
    if (m_cursor.count() + 1 == m_layoutInfo.count() && !m_data.at(index)[DotDotRole].toBool() /* up on leaf node is OK */) {
        DEBUG << "Refusing to enter leaf node";
        return;
    }

    if (m_data.at(index)[DotDotRole].toBool() && !m_cursor.isEmpty()) {
        back();
        return;
    }

    DEBUG << "Entering " << index;
    m_cursor.append(m_data[index]);
    reload();
    emit partChanged();
}

void MediaModel::back(int count)
{
    if (count == 0 || m_cursor.count() < 1)
        return;

    if (m_cursor.count() > count) {
        for (int i = 0; i < count; ++i)
            m_cursor.removeLast();
    } else {
        m_cursor.clear();
    }
    reload();
    emit partChanged();
}

QVariant MediaModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (role == ModelIndexRole)
        return qVariantFromValue(index);

    return m_data.value(index.row()).value(role);
}

QModelIndex MediaModel::index(int row, int col, const QModelIndex &parent) const
{
    if (parent.isValid())
        return QModelIndex();

    return createIndex(row, col);
}

QModelIndex MediaModel::parent(const QModelIndex &idx) const
{
    Q_UNUSED(idx);
    return QModelIndex();
}

int MediaModel::rowCount(const QModelIndex &parent) const
{
    if (parent.isValid())
        return 0;
    return m_data.count();
}

int MediaModel::columnCount(const QModelIndex &idx) const
{
    Q_UNUSED(idx);
    return 1;
}

bool MediaModel::hasChildren(const QModelIndex &parent) const
{
    return !parent.isValid();
}

bool MediaModel::canFetchMore(const QModelIndex &parent) const
{
    if (parent.isValid() || m_mediaType.isEmpty() || m_layoutInfo.isEmpty()) {
        DEBUG << "false " << parent.isValid() << m_mediaType.isEmpty() << m_layoutInfo.isEmpty();
        return false;
    }
    DEBUG << (!m_loading && !m_loaded);
    return !m_loading && !m_loaded;
}

void MediaModel::fetchMore(const QModelIndex &parent)
{
    if (!canFetchMore(parent))
        return;

    DEBUG << "";

    m_loading = true;

    QSqlQuery q = buildQuery();
    DEBUG << q.lastQuery();
    QMetaObject::invokeMethod(m_reader, "execute", Qt::QueuedConnection, Q_ARG(QSqlQuery, q));
}

void MediaModel::createNewDbReader()
{
    DEBUG << "";

    if (m_reader) {
        disconnect(m_reader, 0, this, 0);
        m_reader->stop();
        m_reader->deleteLater();
    }
    m_reader = new DbReader;
    m_reader->moveToThread(m_readerThread);

    QMetaObject::invokeMethod(m_reader, "initialize", Q_ARG(QSqlDatabase, Backend::instance()->mediaDatabase()));
    connect(m_reader, SIGNAL(dataReady(DbReader *, QList<QSqlRecord>, void *)),
            this, SLOT(handleDataReady(DbReader *, QList<QSqlRecord>, void *)));
}

void MediaModel::reload()
{
    createNewDbReader();

    beginResetModel();
    m_loading = m_loaded = false;
    m_data.clear();
    endResetModel();
}

QHash<int, QVariant> MediaModel::dataFromRecord(const QSqlRecord &record) const
{
    QHash<int, QVariant> data;
    for (int j = 0; j < record.count(); j++) {
        int role = m_fieldToRole.value(record.fieldName(j));
        if (record.fieldName(j) == "uri")
            data.insert(role, QUrl::fromEncoded(record.value(j).toByteArray()));
        else
            data.insert(role, record.value(j));
    }

    // Provide 'display' role as , separated values
    QStringList cols = m_layoutInfo.value(m_cursor.count());
    QStringList displayString;
    for (int j = 0; j < cols.count(); j++) {
        displayString << record.value(cols[j]).toString();
    }
    data.insert(Qt::DisplayRole, displayString.join(", "));
    data.insert(DotDotRole, false);
    data.insert(IsLeafRole, m_cursor.count() + 1 == m_layoutInfo.count());

    data.insert(PreviewUrlRole, QUrl::fromEncoded(record.value("thumbnail").toByteArray()));
    return data;
}

void MediaModel::handleDataReady(DbReader *reader, const QList<QSqlRecord> &records, void *node)
{
    Q_ASSERT(reader == m_reader);
    Q_UNUSED(reader);
    Q_UNUSED(node);

    DEBUG << "Received response from db of size " << records.size();

    if (m_data.isEmpty()) {
        insertAll(records);
    } else {
        update(records);
    }

    m_loading = false;
    m_loaded = true;
}

void MediaModel::insertAll(const QList<QSqlRecord> &records)
{
    if (records.isEmpty())
        return;

    if (!m_cursor.isEmpty()) {
        beginInsertRows(QModelIndex(), 0, records.count());
    } else {
        beginInsertRows(QModelIndex(), 0, records.count() - 1);
    }

    for (int i = 0; i < records.count(); i++) {
        QHash<int, QVariant> data = dataFromRecord(records[i]);
        m_data.append(data);
    }

    if (!m_cursor.isEmpty()) {
        QHash<int, QVariant> data;
        data.insert(Qt::DisplayRole, tr(".."));
        data.insert(DotDotRole, true);
        data.insert(IsLeafRole, false);
        m_data.append(data);
    }

    endInsertRows();
}

int MediaModel::compareData(int idx, const QSqlRecord &record) const
{
    const QHash<int, QVariant> &curData = m_data[idx];
    QStringList cols = m_layoutInfo.value(m_cursor.count());
    foreach(const QString &col, cols) {
        const int role = m_fieldToRole.value(col);
        int cmp = QString::compare(curData.value(role).toString(), record.value(col).toString(), Qt::CaseInsensitive); // ## must use sqlite's compare
        if (cmp != 0)
            return cmp;
    }
    return 0;
}

void MediaModel::update(const QList<QSqlRecord> &records)
{
    int old = 0, shiny = 0;

    while (shiny < records.length()) {
        const QSqlRecord &record = records[shiny];
        int cmp = old == m_data.count() ? 1 : compareData(old, record);

        if (cmp == 0) {
            ++old;
            ++shiny;
        } else if (cmp < 0) {
            beginRemoveRows(QModelIndex(), old, old);
            m_data.removeAt(old);
            endRemoveRows();
        } else {
            beginInsertRows(QModelIndex(), old, old);
            m_data.insert(old, dataFromRecord(record));
            endInsertRows();
            ++old;
            ++shiny;
        }
    }

    if (old != m_data.count()) {
        beginRemoveRows(QModelIndex(), old, m_data.count()-1);
        m_data = m_data.mid(0, old);
        endRemoveRows();
    }
}

QSqlQuery MediaModel::buildQuery() const
{
    QSqlDriver *driver = Backend::instance()->mediaDatabase().driver();

    QStringList escapedCurParts;
    QStringList curParts = m_layoutInfo[m_cursor.count()];
    for (int i = 0; i < curParts.count(); i++)
        escapedCurParts.append(driver->escapeIdentifier(curParts[i], QSqlDriver::FieldName));
    QString escapedCurPart = escapedCurParts.join(",");

    QSqlQuery query(Backend::instance()->mediaDatabase());
    query.setForwardOnly(true);

    QStringList placeHolders;
    const bool lastPart = m_cursor.count() == m_layoutInfo.count()-1;
    QString queryString;
    // SQLite allows us to select columns that are not present in the GROUP BY. We use this feature
    // to select thumbnails for non-leaf nodes
    queryString.append("SELECT *");
    queryString.append(" FROM " + driver->escapeIdentifier(m_mediaType, QSqlDriver::TableName));

    if (!m_cursor.isEmpty()) {
        QStringList where;
        for (int i = 0; i < m_cursor.count(); i++) {
            QStringList subParts = m_layoutInfo[i];
            for (int j = 0; j < subParts.count(); j++) {
                where.append(subParts[j] + " = ?");
                const int role = m_fieldToRole.value(subParts[j]);
                placeHolders << m_cursor[i].value(role).toString();
            }
        }

        queryString.append(" WHERE " + where.join(" AND "));
    }

    if (!lastPart)
        queryString.append(" GROUP BY " + escapedCurPart);

    queryString.append(" ORDER BY " + escapedCurPart + " COLLATE NOCASE");

    query.prepare(queryString);

    foreach(const QString &placeHolder, placeHolders)
        query.addBindValue(placeHolder);

    return query;
}

void MediaModel::handleScanStarted(const QString &type)
{
    if (type != m_mediaType)
        return;

    m_refreshTimer.start();
}

void MediaModel::handleScanFinished(const QString &type)
{
    if (type != m_mediaType)
        return;
    m_refreshTimer.stop();
    refresh();
}

void MediaModel::refresh()
{
    createNewDbReader();

    m_loading = true;
    m_loaded = false;

    QSqlQuery q = buildQuery();
    DEBUG << m_mediaType << q.lastQuery();
    QMetaObject::invokeMethod(m_reader, "execute", Qt::QueuedConnection, Q_ARG(QSqlQuery, q));
}

<commit_msg>Make sure .. is never removed and always added<commit_after>/****************************************************************************

This file is part of the QtMediaHub project on http://www.gitorious.org.

Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).*
All rights reserved.

Contact:  Nokia Corporation (qt-info@nokia.com)**

You may use this file under the terms of the BSD license as follows:

"Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."

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

#include "mediamodel.h"
#include "mediascanner.h"
#include "dbreader.h"
#include "backend.h"
#include "qmh-config.h"

#define DEBUG if (0) qDebug() << this << __PRETTY_FUNCTION__

MediaModel::MediaModel(QObject *parent)
    : QAbstractItemModel(parent), m_loading(false), m_loaded(false), m_reader(0), m_readerThread(0)
{
    m_refreshTimer.setInterval(Config::value("mediamodel-refresh-interval", 10000));
    connect(&m_refreshTimer, SIGNAL(timeout()), this, SLOT(refresh()));

    MediaScanner *scanner = Backend::instance()->mediaScanner();
    connect(scanner, SIGNAL(scanStarted(QString)), this, SLOT(handleScanStarted(QString)));
    connect(scanner, SIGNAL(scanFinished(QString)), this, SLOT(handleScanFinished(QString)));
    connect(scanner, SIGNAL(searchPathRemoved(QString, QString)), this, SLOT(handleScanFinished(QString)));

    m_readerThread = new QThread(this);
    m_readerThread->start();
}

MediaModel::~MediaModel()
{
    if (m_reader) {
        m_reader->stop();
        m_reader->deleteLater();
        m_readerThread->quit();
        m_readerThread->wait();
    }
}

QString MediaModel::part() const
{
    return m_structure.split("|").value(m_cursor.length());
}

QString MediaModel::mediaType() const
{
    return m_mediaType;
}

void MediaModel::setMediaType(const QString &type)
{
    if (type == m_mediaType)
        return;

    DEBUG << type;

    m_mediaType = type;
    emit mediaTypeChanged();

    // Add the fields of the table as role names
    QSqlDriver *driver = Backend::instance()->mediaDatabase().driver();
    QSqlRecord record = driver->record(m_mediaType);
    if (record.isEmpty())
        qWarning() << "Table " << type << " is not valid it seems";

    QHash<int, QByteArray> hash = roleNames();
    hash.insert(DotDotRole, "dotdot");
    hash.insert(IsLeafRole, "isLeaf");
    hash.insert(ModelIndexRole,"modelIndex");
    hash.insert(PreviewUrlRole, "previewUrl");

    for (int i = 0; i < record.count(); i++) {
        hash.insert(FieldRolesBegin + i, record.fieldName(i).toUtf8());
        m_fieldToRole.insert(record.fieldName(i), FieldRolesBegin + i);
    }

    setRoleNames(hash);

    reload();
}

QString MediaModel::structure() const
{
    return m_structure;
}

void MediaModel::setStructure(const QString &str)
{
    if (str == m_structure)
        return;
    DEBUG << str;
    m_structure = str;
    m_layoutInfo.clear();
    foreach(const QString &part, m_structure.split("|"))
        m_layoutInfo.append(part.split(","));

    reload();

    emit structureChanged();
}

void MediaModel::enter(int index)
{
    if (m_cursor.count() + 1 == m_layoutInfo.count() && !m_data.at(index)[DotDotRole].toBool() /* up on leaf node is OK */) {
        DEBUG << "Refusing to enter leaf node";
        return;
    }

    if (m_data.at(index)[DotDotRole].toBool() && !m_cursor.isEmpty()) {
        back();
        return;
    }

    DEBUG << "Entering " << index;
    m_cursor.append(m_data[index]);
    reload();
    emit partChanged();
}

void MediaModel::back(int count)
{
    if (count == 0 || m_cursor.count() < 1)
        return;

    if (m_cursor.count() > count) {
        for (int i = 0; i < count; ++i)
            m_cursor.removeLast();
    } else {
        m_cursor.clear();
    }
    reload();
    emit partChanged();
}

QVariant MediaModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (role == ModelIndexRole)
        return qVariantFromValue(index);

    return m_data.value(index.row()).value(role);
}

QModelIndex MediaModel::index(int row, int col, const QModelIndex &parent) const
{
    if (parent.isValid())
        return QModelIndex();

    return createIndex(row, col);
}

QModelIndex MediaModel::parent(const QModelIndex &idx) const
{
    Q_UNUSED(idx);
    return QModelIndex();
}

int MediaModel::rowCount(const QModelIndex &parent) const
{
    if (parent.isValid())
        return 0;
    return m_data.count();
}

int MediaModel::columnCount(const QModelIndex &idx) const
{
    Q_UNUSED(idx);
    return 1;
}

bool MediaModel::hasChildren(const QModelIndex &parent) const
{
    return !parent.isValid();
}

bool MediaModel::canFetchMore(const QModelIndex &parent) const
{
    if (parent.isValid() || m_mediaType.isEmpty() || m_layoutInfo.isEmpty()) {
        DEBUG << "false " << parent.isValid() << m_mediaType.isEmpty() << m_layoutInfo.isEmpty();
        return false;
    }
    DEBUG << (!m_loading && !m_loaded);
    return !m_loading && !m_loaded;
}

void MediaModel::fetchMore(const QModelIndex &parent)
{
    if (!canFetchMore(parent))
        return;

    DEBUG << "";

    m_loading = true;

    QSqlQuery q = buildQuery();
    DEBUG << q.lastQuery();
    QMetaObject::invokeMethod(m_reader, "execute", Qt::QueuedConnection, Q_ARG(QSqlQuery, q));
}

void MediaModel::createNewDbReader()
{
    DEBUG << "";

    if (m_reader) {
        disconnect(m_reader, 0, this, 0);
        m_reader->stop();
        m_reader->deleteLater();
    }
    m_reader = new DbReader;
    m_reader->moveToThread(m_readerThread);

    QMetaObject::invokeMethod(m_reader, "initialize", Q_ARG(QSqlDatabase, Backend::instance()->mediaDatabase()));
    connect(m_reader, SIGNAL(dataReady(DbReader *, QList<QSqlRecord>, void *)),
            this, SLOT(handleDataReady(DbReader *, QList<QSqlRecord>, void *)));
}

void MediaModel::reload()
{
    createNewDbReader();

    beginResetModel();
    m_loading = m_loaded = false;
    m_data.clear();
    endResetModel();
}

QHash<int, QVariant> MediaModel::dataFromRecord(const QSqlRecord &record) const
{
    QHash<int, QVariant> data;
    for (int j = 0; j < record.count(); j++) {
        int role = m_fieldToRole.value(record.fieldName(j));
        if (record.fieldName(j) == "uri")
            data.insert(role, QUrl::fromEncoded(record.value(j).toByteArray()));
        else
            data.insert(role, record.value(j));
    }

    // Provide 'display' role as , separated values
    QStringList cols = m_layoutInfo.value(m_cursor.count());
    QStringList displayString;
    for (int j = 0; j < cols.count(); j++) {
        displayString << record.value(cols[j]).toString();
    }
    data.insert(Qt::DisplayRole, displayString.join(", "));
    data.insert(DotDotRole, false);
    data.insert(IsLeafRole, m_cursor.count() + 1 == m_layoutInfo.count());

    data.insert(PreviewUrlRole, QUrl::fromEncoded(record.value("thumbnail").toByteArray()));
    return data;
}

void MediaModel::handleDataReady(DbReader *reader, const QList<QSqlRecord> &records, void *node)
{
    Q_ASSERT(reader == m_reader);
    Q_UNUSED(reader);
    Q_UNUSED(node);

    DEBUG << "Received response from db of size " << records.size();

    if (m_data.isEmpty()) {
        insertAll(records);
    } else {
        update(records);
    }

    m_loading = false;
    m_loaded = true;
}

void MediaModel::insertAll(const QList<QSqlRecord> &records)
{
    const bool addDotDot = !m_cursor.isEmpty();

    if (records.isEmpty() && !addDotDot)
        return;

    beginInsertRows(QModelIndex(), 0, records.count() + (addDotDot ? 0 : -1));

    for (int i = 0; i < records.count(); i++) {
        QHash<int, QVariant> data = dataFromRecord(records[i]);
        m_data.append(data);
    }

    if (addDotDot) {
        QHash<int, QVariant> data;
        data.insert(Qt::DisplayRole, tr(".."));
        data.insert(DotDotRole, true);
        data.insert(IsLeafRole, false);
        m_data.prepend(data);
    }

    endInsertRows();
}

int MediaModel::compareData(int idx, const QSqlRecord &record) const
{
    const QHash<int, QVariant> &curData = m_data[idx];
    QStringList cols = m_layoutInfo.value(m_cursor.count());
    foreach(const QString &col, cols) {
        const int role = m_fieldToRole.value(col);
        int cmp = QString::compare(curData.value(role).toString(), record.value(col).toString(), Qt::CaseInsensitive); // ## must use sqlite's compare
        if (cmp != 0)
            return cmp;
    }
    return 0;
}

void MediaModel::update(const QList<QSqlRecord> &records)
{
    const bool hasDotDot = !m_cursor.isEmpty();
    int old = (int)hasDotDot, shiny = 0;

    while (shiny < records.length()) {
        const QSqlRecord &record = records[shiny];
        int cmp = old == m_data.count() ? 1 : compareData(old, record);

        if (cmp == 0) {
            ++old;
            ++shiny;
        } else if (cmp < 0) {
            beginRemoveRows(QModelIndex(), old, old);
            m_data.removeAt(old);
            endRemoveRows();
        } else {
            beginInsertRows(QModelIndex(), old, old);
            m_data.insert(old, dataFromRecord(record));
            endInsertRows();
            ++old;
            ++shiny;
        }
    }

    if (old != m_data.count()) {
        beginRemoveRows(QModelIndex(), old, m_data.count()-1);
        m_data = m_data.mid(0, old);
        endRemoveRows();
    }
}

QSqlQuery MediaModel::buildQuery() const
{
    QSqlDriver *driver = Backend::instance()->mediaDatabase().driver();

    QStringList escapedCurParts;
    QStringList curParts = m_layoutInfo[m_cursor.count()];
    for (int i = 0; i < curParts.count(); i++)
        escapedCurParts.append(driver->escapeIdentifier(curParts[i], QSqlDriver::FieldName));
    QString escapedCurPart = escapedCurParts.join(",");

    QSqlQuery query(Backend::instance()->mediaDatabase());
    query.setForwardOnly(true);

    QStringList placeHolders;
    const bool lastPart = m_cursor.count() == m_layoutInfo.count()-1;
    QString queryString;
    // SQLite allows us to select columns that are not present in the GROUP BY. We use this feature
    // to select thumbnails for non-leaf nodes
    queryString.append("SELECT *");
    queryString.append(" FROM " + driver->escapeIdentifier(m_mediaType, QSqlDriver::TableName));

    if (!m_cursor.isEmpty()) {
        QStringList where;
        for (int i = 0; i < m_cursor.count(); i++) {
            QStringList subParts = m_layoutInfo[i];
            for (int j = 0; j < subParts.count(); j++) {
                where.append(subParts[j] + " = ?");
                const int role = m_fieldToRole.value(subParts[j]);
                placeHolders << m_cursor[i].value(role).toString();
            }
        }

        queryString.append(" WHERE " + where.join(" AND "));
    }

    if (!lastPart)
        queryString.append(" GROUP BY " + escapedCurPart);

    queryString.append(" ORDER BY " + escapedCurPart + " COLLATE NOCASE");

    query.prepare(queryString);

    foreach(const QString &placeHolder, placeHolders)
        query.addBindValue(placeHolder);

    return query;
}

void MediaModel::handleScanStarted(const QString &type)
{
    if (type != m_mediaType)
        return;

    m_refreshTimer.start();
}

void MediaModel::handleScanFinished(const QString &type)
{
    if (type != m_mediaType)
        return;
    m_refreshTimer.stop();
    refresh();
}

void MediaModel::refresh()
{
    createNewDbReader();

    m_loading = true;
    m_loaded = false;

    QSqlQuery q = buildQuery();
    DEBUG << m_mediaType << q.lastQuery();
    QMetaObject::invokeMethod(m_reader, "execute", Qt::QueuedConnection, Q_ARG(QSqlQuery, q));
}

<|endoftext|>


<commit_before>/*
 * Copyright (c) 2015, Christian Menard
 * Copyright (c) 2015, Nils Asmussen
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are
 * those of the authors and should not be interpreted as representing official
 * policies, either expressed or implied, of the FreeBSD Project.
 */

#include "debug/Dtu.hh"
#include "debug/DtuBuf.hh"
#include "debug/DtuPackets.hh"
#include "debug/DtuSysCalls.hh"
#include "debug/DtuPower.hh"
#include "debug/DtuXfers.hh"
#include "debug/DtuTlb.hh"
#include "mem/dtu/xfer_unit.hh"

XferUnit::XferUnit(Dtu &_dtu,
                   size_t _blockSize,
                   size_t _bufCount,
                   size_t _bufSize)
    : dtu(_dtu),
      blockSize(_blockSize),
      bufCount(_bufCount),
      bufSize(_bufSize),
      bufs(new Buffer*[bufCount]),
      queue()
{
    for (size_t i = 0; i < bufCount; ++i)
        bufs[i] = new Buffer(i, bufSize);
}

XferUnit::~XferUnit()
{
    for (size_t i = 0; i < bufCount; ++i)
        delete bufs[i];
    delete[] bufs;
}

void
XferUnit::regStats()
{
    reads
        .init(8)
        .name(dtu.name() + ".xfer.reads")
        .desc("Read times (in Cycles)")
        .flags(Stats::nozero);
    writes
        .init(8)
        .name(dtu.name() + ".xfer.writes")
        .desc("Write times (in Cycles)")
        .flags(Stats::nozero);
    bytesRead
        .init(8)
        .name(dtu.name() + ".xfer.bytesRead")
        .desc("Read bytes (from internal memory)")
        .flags(Stats::nozero);
    bytesWritten
        .init(8)
        .name(dtu.name() + ".xfer.bytesWritten")
        .desc("Written bytes (to internal memory)")
        .flags(Stats::nozero);
    delays
        .name(dtu.name() + ".xfer.delays")
        .desc("Number of delays due to occupied buffers");
    pagefaults
        .name(dtu.name() + ".xfer.pagefaults")
        .desc("Number of pagefaults during transfers");
    pfAborts
        .name(dtu.name() + ".xfer.pfAborts")
        .desc("Number of aborts due to pagefaults");
}

void
XferUnit::TransferEvent::tryStart()
{
    assert(buf == NULL);

    buf = xfer.allocateBuf(this, flags);

    // try again later, if there is no free buffer
    if (!buf)
    {
        DPRINTFS(DtuXfers, (&xfer.dtu),
            "Delaying %s transfer of %lu bytes @ %p [flags=%#x]\n",
            isWrite() ? "mem-write" : "mem-read",
            size,
            localAddr,
            flags);

        xfer.delays++;
        xfer.queue.push_back(this);
        return;
    }

    // if there is data to put into the buffer, do that now
    if (header)
    {
        // note that this causes no additional delay because we assume that we
        // create the header directly in the buffer (and if there is no one
        // free we just wait until there is)
        memcpy(buf->bytes, header, sizeof(Dtu::MessageHeader));
        buf->event->flags |= XferFlags::MESSAGE;

        // for the header
        buf->offset += sizeof(Dtu::MessageHeader);
        delete header;
        header = nullptr;
    }
    else if (pkt)
    {
        // here is also no additional delay, because we are doing that in
        // parallel and are already paying for it at other places
        memcpy(buf->bytes, pkt->getPtr<uint8_t>(), pkt->getSize());
    }

    DPRINTFS(DtuXfers, (&xfer.dtu),
        "buf%d: Starting %s transfer of %lu bytes @ %p [flags=%#x]\n",
        buf->id,
        isWrite() ? "mem-write" : "mem-read",
        size,
        localAddr,
        buf->event->flags);

    xfer.dtu.schedule(this, xfer.dtu.clockEdge(Cycles(1)));
}

void
XferUnit::TransferEvent::process()
{
    if (!buf)
    {
        tryStart();
        return;
    }

    NocAddr phys(localAddr);
    if (xfer.dtu.tlb())
    {
        uint access = isWrite() ? DtuTlb::WRITE : DtuTlb::READ;
        access |= isRemote() ? 0 : DtuTlb::INTERN;

        DtuTlb::Result res = xfer.dtu.tlb()->lookup(localAddr, access, &phys);
        if (res != DtuTlb::HIT)
        {
            if (res == DtuTlb::PAGEFAULT)
                xfer.pagefaults++;

            // if this is a pagefault and we are not allowed to cause one,
            // report an error
            if (res == DtuTlb::PAGEFAULT && (flags & XferFlags::NOPF))
            {
                pagefault();
                return;
            }

            assert(res != DtuTlb::NOMAP);
            DPRINTFS(DtuTlb, (&xfer.dtu),
                "%s for %s access to %p\n",
                res != DtuTlb::MISS ? "Pagefault" : "TLB-miss",
                access == DtuTlb::READ ? "read" : "write",
                localAddr);

            Translation *trans = new Translation(*this);
            xfer.dtu.startTranslate(localAddr, access, trans);
            return;
        }
    }

    translateDone(true, phys);
}

void
XferUnit::TransferEvent::translateDone(bool success, const NocAddr &phys)
{
    if (!success)
    {
        pagefault();
        return;
    }

    assert(size > 0);

    Addr localOff = localAddr & (xfer.blockSize - 1);
    Addr reqSize = std::min(size, xfer.blockSize - localOff);

    auto cmd = isWrite() ? MemCmd::WriteReq : MemCmd::ReadReq;
    auto pkt = xfer.dtu.generateRequest(phys.getAddr(), reqSize, cmd);

    if (isWrite())
    {
        assert(buf->offset + reqSize <= xfer.bufSize);

        memcpy(pkt->getPtr<uint8_t>(), buf->bytes + buf->offset, reqSize);

        buf->offset += reqSize;
    }

    DPRINTFS(DtuXfers, (&xfer.dtu),
        "buf%d: %s %lu bytes @ %p->%p in local memory\n",
        buf->id,
        isWrite() ? "Writing" : "Reading",
        reqSize,
        localAddr,
        phys.getAddr());

    xfer.dtu.sendMemRequest(pkt,
                            localAddr,
                            buf->id,
                            Dtu::MemReqType::TRANSFER,
                            xfer.dtu.transferToMemRequestLatency);

    // to next block
    localAddr += reqSize;
    size -= reqSize;
}

void
XferUnit::TransferEvent::pagefault()
{
    DPRINTFS(DtuXfers, (&xfer.dtu),
        "buf%d: Translation failed; aborting transfer",
        buf->id);

    xfer.pfAborts++;

    buf->event->size = 0;
    buf->event->result = Dtu::Error::PAGEFAULT;
    xfer.recvMemResponse(buf->id,
                         NULL,
                         0,
                         buf->event->pkt->headerDelay,
                         buf->event->pkt->payloadDelay);
}

void
XferUnit::startTransfer(Dtu::TransferType type,
                        NocAddr remoteAddr,
                        Addr localAddr,
                        Addr size,
                        PacketPtr pkt,
                        uint vpeId,
                        Dtu::MessageHeader* header,
                        Cycles delay,
                        uint flags)
{
    TransferEvent *event = new TransferEvent(*this);

    event->type = type;
    event->remoteAddr = remoteAddr;
    event->localAddr = localAddr;
    event->size = size;
    event->pkt = pkt;
    event->vpeId = vpeId;
    event->flags = flags;
    event->header = header;
    event->startCycle = dtu.curCycle();

    if (event->isRead())
        bytesRead.sample(size);
    else
        bytesWritten.sample(size);

    dtu.schedule(event, dtu.clockEdge(Cycles(delay + 1)));

    // finish the noc request now to make the port unbusy
    if (event->isRemote())
        dtu.schedNocRequestFinished(dtu.clockEdge(Cycles(1)));
}

void
XferUnit::recvMemResponse(size_t bufId,
                          const void* data,
                          Addr size,
                          Tick headerDelay,
                          Tick payloadDelay)
{
    Buffer *buf = bufs[bufId];

    assert(buf->event);

    if (data && buf->event->isRead())
    {
        assert(buf->offset + size <= bufSize);

        memcpy(buf->bytes + buf->offset, data, size);

        buf->offset += size;
    }

    // nothing more to copy?
    if (buf->event->size == 0)
    {
        if (buf->event->type == Dtu::TransferType::LOCAL_READ)
        {
            DPRINTFS(DtuXfers, (&dtu),
                "buf%d: Sending NoC request of %lu bytes @ %p\n",
                buf->id,
                buf->offset,
                buf->event->remoteAddr.offset);

            auto pkt = dtu.generateRequest(buf->event->remoteAddr.getAddr(),
                                           buf->offset,
                                           MemCmd::WriteReq);
            memcpy(pkt->getPtr<uint8_t>(),
                   buf->bytes,
                   buf->offset);

            /*
             * See sendNocMessage() for an explanation of delay handling.
             */
            Cycles delay = dtu.transferToNocLatency;
            delay += dtu.ticksToCycles(headerDelay);
            pkt->payloadDelay = payloadDelay;
            dtu.printPacket(pkt);

            Dtu::NocPacketType pktType;
            if (buf->event->flags & MESSAGE)
                pktType = Dtu::NocPacketType::MESSAGE;
            else
                pktType = Dtu::NocPacketType::WRITE_REQ;
            uint cmdflags = (buf->event->flags & NOPF) ? Dtu::Command::NOPF : 0;
            dtu.sendNocRequest(pktType, pkt, buf->event->vpeId, cmdflags, delay);
        }
        else if (buf->event->type == Dtu::TransferType::LOCAL_WRITE)
        {
            if (buf->event->flags & LAST)
                dtu.scheduleFinishOp(Cycles(1));

            dtu.freeRequest(buf->event->pkt);
        }
        else
        {
            if (buf->event->flags & XferFlags::MSGRECV)
            {
                NocAddr addr(buf->event->pkt->getAddr());
                dtu.finishMsgReceive(addr.offset);
            }

            // TODO should we respond earlier for remote reads? i.e. as soon
            // as its in the buffer
            assert(buf->event->pkt != NULL);

            // some requests from the cache (e.g. cleanEvict) do not need a
            // response
            if (buf->event->pkt->needsResponse())
            {
                DPRINTFS(DtuXfers, (&dtu),
                    "buf%d: Sending NoC response of %lu bytes\n",
                    buf->id,
                    buf->offset);

                buf->event->pkt->makeResponse();

                if (buf->event->type == Dtu::TransferType::REMOTE_READ)
                {
                    memcpy(buf->event->pkt->getPtr<uint8_t>(),
                           buf->bytes,
                           buf->offset);
                }

                Cycles delay = dtu.transferToNocLatency;
                dtu.schedNocResponse(buf->event->pkt, dtu.clockEdge(delay));
            }
        }

        DPRINTFS(DtuXfers, (&dtu), "buf%d: Transfer done\n",
                 buf->id);

        // we're done with this buffer now
        if (buf->event->isRead())
            reads.sample(dtu.curCycle() - buf->event->startCycle);
        else
            writes.sample(dtu.curCycle() - buf->event->startCycle);
        buf->event->finish();
        buf->event = NULL;

        // start the next one, if there is any
        if (!queue.empty())
        {
            TransferEvent *ev = queue.front();
            queue.pop_front();
            dtu.schedule(ev, dtu.clockEdge(Cycles(1)));
        }
    }
    else
        buf->event->process();
}

XferUnit::Buffer*
XferUnit::allocateBuf(TransferEvent *event, uint flags)
{
    // don't allow message receives in parallel. because otherwise we run into race conditions.
    // e.g., we could overwrite unread messages because we can't increase the message counter when
    // the receive starts (to not notify SW) and thus might start receiving without having space
    // another problem is that we might finish receiving the second message before the first and
    // then increase the message counter, so that the SW looks at the first message, which is not
    // ready yet.
    if (flags & XferFlags::MSGRECV)
    {
        for (size_t i = 0; i < bufCount; ++i)
        {
            if (bufs[i]->event && (bufs[i]->event->flags & XferFlags::MSGRECV))
                return NULL;
        }
    }

    // the first buffer cannot cause pagefaults; thus we can only use it if for
    // transfers which abort if a pagefault is caused
    // this is required to resolve a deadlock due to additional transfers that
    // handle a already running pagefault transfer.
    size_t i = !dtu.tlb() || (flags & XferFlags::NOPF) ? 0 : 1;
    for (; i < bufCount; ++i)
    {
        if (!bufs[i]->event)
        {
            bufs[i]->event = event;
            bufs[i]->offset = 0;
            return bufs[i];
        }
    }

    return NULL;
}
<commit_msg>DTU: ensure that we have >= 2 xfer buffers if paging is enabled.<commit_after>/*
 * Copyright (c) 2015, Christian Menard
 * Copyright (c) 2015, Nils Asmussen
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are
 * those of the authors and should not be interpreted as representing official
 * policies, either expressed or implied, of the FreeBSD Project.
 */

#include "debug/Dtu.hh"
#include "debug/DtuBuf.hh"
#include "debug/DtuPackets.hh"
#include "debug/DtuSysCalls.hh"
#include "debug/DtuPower.hh"
#include "debug/DtuXfers.hh"
#include "debug/DtuTlb.hh"
#include "mem/dtu/xfer_unit.hh"

XferUnit::XferUnit(Dtu &_dtu,
                   size_t _blockSize,
                   size_t _bufCount,
                   size_t _bufSize)
    : dtu(_dtu),
      blockSize(_blockSize),
      bufCount(_bufCount),
      bufSize(_bufSize),
      bufs(new Buffer*[bufCount]),
      queue()
{
    panic_if(dtu.tlb() && bufCount < 2,
        "With paging enabled, at least 2 buffers are required");

    for (size_t i = 0; i < bufCount; ++i)
        bufs[i] = new Buffer(i, bufSize);
}

XferUnit::~XferUnit()
{
    for (size_t i = 0; i < bufCount; ++i)
        delete bufs[i];
    delete[] bufs;
}

void
XferUnit::regStats()
{
    reads
        .init(8)
        .name(dtu.name() + ".xfer.reads")
        .desc("Read times (in Cycles)")
        .flags(Stats::nozero);
    writes
        .init(8)
        .name(dtu.name() + ".xfer.writes")
        .desc("Write times (in Cycles)")
        .flags(Stats::nozero);
    bytesRead
        .init(8)
        .name(dtu.name() + ".xfer.bytesRead")
        .desc("Read bytes (from internal memory)")
        .flags(Stats::nozero);
    bytesWritten
        .init(8)
        .name(dtu.name() + ".xfer.bytesWritten")
        .desc("Written bytes (to internal memory)")
        .flags(Stats::nozero);
    delays
        .name(dtu.name() + ".xfer.delays")
        .desc("Number of delays due to occupied buffers");
    pagefaults
        .name(dtu.name() + ".xfer.pagefaults")
        .desc("Number of pagefaults during transfers");
    pfAborts
        .name(dtu.name() + ".xfer.pfAborts")
        .desc("Number of aborts due to pagefaults");
}

void
XferUnit::TransferEvent::tryStart()
{
    assert(buf == NULL);

    buf = xfer.allocateBuf(this, flags);

    // try again later, if there is no free buffer
    if (!buf)
    {
        DPRINTFS(DtuXfers, (&xfer.dtu),
            "Delaying %s transfer of %lu bytes @ %p [flags=%#x]\n",
            isWrite() ? "mem-write" : "mem-read",
            size,
            localAddr,
            flags);

        xfer.delays++;
        xfer.queue.push_back(this);
        return;
    }

    // if there is data to put into the buffer, do that now
    if (header)
    {
        // note that this causes no additional delay because we assume that we
        // create the header directly in the buffer (and if there is no one
        // free we just wait until there is)
        memcpy(buf->bytes, header, sizeof(Dtu::MessageHeader));
        buf->event->flags |= XferFlags::MESSAGE;

        // for the header
        buf->offset += sizeof(Dtu::MessageHeader);
        delete header;
        header = nullptr;
    }
    else if (pkt)
    {
        // here is also no additional delay, because we are doing that in
        // parallel and are already paying for it at other places
        memcpy(buf->bytes, pkt->getPtr<uint8_t>(), pkt->getSize());
    }

    DPRINTFS(DtuXfers, (&xfer.dtu),
        "buf%d: Starting %s transfer of %lu bytes @ %p [flags=%#x]\n",
        buf->id,
        isWrite() ? "mem-write" : "mem-read",
        size,
        localAddr,
        buf->event->flags);

    xfer.dtu.schedule(this, xfer.dtu.clockEdge(Cycles(1)));
}

void
XferUnit::TransferEvent::process()
{
    if (!buf)
    {
        tryStart();
        return;
    }

    NocAddr phys(localAddr);
    if (xfer.dtu.tlb())
    {
        uint access = isWrite() ? DtuTlb::WRITE : DtuTlb::READ;
        access |= isRemote() ? 0 : DtuTlb::INTERN;

        DtuTlb::Result res = xfer.dtu.tlb()->lookup(localAddr, access, &phys);
        if (res != DtuTlb::HIT)
        {
            if (res == DtuTlb::PAGEFAULT)
                xfer.pagefaults++;

            // if this is a pagefault and we are not allowed to cause one,
            // report an error
            if (res == DtuTlb::PAGEFAULT && (flags & XferFlags::NOPF))
            {
                pagefault();
                return;
            }

            assert(res != DtuTlb::NOMAP);
            DPRINTFS(DtuTlb, (&xfer.dtu),
                "%s for %s access to %p\n",
                res != DtuTlb::MISS ? "Pagefault" : "TLB-miss",
                access == DtuTlb::READ ? "read" : "write",
                localAddr);

            Translation *trans = new Translation(*this);
            xfer.dtu.startTranslate(localAddr, access, trans);
            return;
        }
    }

    translateDone(true, phys);
}

void
XferUnit::TransferEvent::translateDone(bool success, const NocAddr &phys)
{
    if (!success)
    {
        pagefault();
        return;
    }

    assert(size > 0);

    Addr localOff = localAddr & (xfer.blockSize - 1);
    Addr reqSize = std::min(size, xfer.blockSize - localOff);

    auto cmd = isWrite() ? MemCmd::WriteReq : MemCmd::ReadReq;
    auto pkt = xfer.dtu.generateRequest(phys.getAddr(), reqSize, cmd);

    if (isWrite())
    {
        assert(buf->offset + reqSize <= xfer.bufSize);

        memcpy(pkt->getPtr<uint8_t>(), buf->bytes + buf->offset, reqSize);

        buf->offset += reqSize;
    }

    DPRINTFS(DtuXfers, (&xfer.dtu),
        "buf%d: %s %lu bytes @ %p->%p in local memory\n",
        buf->id,
        isWrite() ? "Writing" : "Reading",
        reqSize,
        localAddr,
        phys.getAddr());

    xfer.dtu.sendMemRequest(pkt,
                            localAddr,
                            buf->id,
                            Dtu::MemReqType::TRANSFER,
                            xfer.dtu.transferToMemRequestLatency);

    // to next block
    localAddr += reqSize;
    size -= reqSize;
}

void
XferUnit::TransferEvent::pagefault()
{
    DPRINTFS(DtuXfers, (&xfer.dtu),
        "buf%d: Translation failed; aborting transfer",
        buf->id);

    xfer.pfAborts++;

    buf->event->size = 0;
    buf->event->result = Dtu::Error::PAGEFAULT;
    xfer.recvMemResponse(buf->id,
                         NULL,
                         0,
                         buf->event->pkt->headerDelay,
                         buf->event->pkt->payloadDelay);
}

void
XferUnit::startTransfer(Dtu::TransferType type,
                        NocAddr remoteAddr,
                        Addr localAddr,
                        Addr size,
                        PacketPtr pkt,
                        uint vpeId,
                        Dtu::MessageHeader* header,
                        Cycles delay,
                        uint flags)
{
    TransferEvent *event = new TransferEvent(*this);

    event->type = type;
    event->remoteAddr = remoteAddr;
    event->localAddr = localAddr;
    event->size = size;
    event->pkt = pkt;
    event->vpeId = vpeId;
    event->flags = flags;
    event->header = header;
    event->startCycle = dtu.curCycle();

    if (event->isRead())
        bytesRead.sample(size);
    else
        bytesWritten.sample(size);

    dtu.schedule(event, dtu.clockEdge(Cycles(delay + 1)));

    // finish the noc request now to make the port unbusy
    if (event->isRemote())
        dtu.schedNocRequestFinished(dtu.clockEdge(Cycles(1)));
}

void
XferUnit::recvMemResponse(size_t bufId,
                          const void* data,
                          Addr size,
                          Tick headerDelay,
                          Tick payloadDelay)
{
    Buffer *buf = bufs[bufId];

    assert(buf->event);

    if (data && buf->event->isRead())
    {
        assert(buf->offset + size <= bufSize);

        memcpy(buf->bytes + buf->offset, data, size);

        buf->offset += size;
    }

    // nothing more to copy?
    if (buf->event->size == 0)
    {
        if (buf->event->type == Dtu::TransferType::LOCAL_READ)
        {
            DPRINTFS(DtuXfers, (&dtu),
                "buf%d: Sending NoC request of %lu bytes @ %p\n",
                buf->id,
                buf->offset,
                buf->event->remoteAddr.offset);

            auto pkt = dtu.generateRequest(buf->event->remoteAddr.getAddr(),
                                           buf->offset,
                                           MemCmd::WriteReq);
            memcpy(pkt->getPtr<uint8_t>(),
                   buf->bytes,
                   buf->offset);

            /*
             * See sendNocMessage() for an explanation of delay handling.
             */
            Cycles delay = dtu.transferToNocLatency;
            delay += dtu.ticksToCycles(headerDelay);
            pkt->payloadDelay = payloadDelay;
            dtu.printPacket(pkt);

            Dtu::NocPacketType pktType;
            if (buf->event->flags & MESSAGE)
                pktType = Dtu::NocPacketType::MESSAGE;
            else
                pktType = Dtu::NocPacketType::WRITE_REQ;
            uint cmdflags = (buf->event->flags & NOPF) ? Dtu::Command::NOPF : 0;
            dtu.sendNocRequest(pktType, pkt, buf->event->vpeId, cmdflags, delay);
        }
        else if (buf->event->type == Dtu::TransferType::LOCAL_WRITE)
        {
            if (buf->event->flags & LAST)
                dtu.scheduleFinishOp(Cycles(1));

            dtu.freeRequest(buf->event->pkt);
        }
        else
        {
            if (buf->event->flags & XferFlags::MSGRECV)
            {
                NocAddr addr(buf->event->pkt->getAddr());
                dtu.finishMsgReceive(addr.offset);
            }

            // TODO should we respond earlier for remote reads? i.e. as soon
            // as its in the buffer
            assert(buf->event->pkt != NULL);

            // some requests from the cache (e.g. cleanEvict) do not need a
            // response
            if (buf->event->pkt->needsResponse())
            {
                DPRINTFS(DtuXfers, (&dtu),
                    "buf%d: Sending NoC response of %lu bytes\n",
                    buf->id,
                    buf->offset);

                buf->event->pkt->makeResponse();

                if (buf->event->type == Dtu::TransferType::REMOTE_READ)
                {
                    memcpy(buf->event->pkt->getPtr<uint8_t>(),
                           buf->bytes,
                           buf->offset);
                }

                Cycles delay = dtu.transferToNocLatency;
                dtu.schedNocResponse(buf->event->pkt, dtu.clockEdge(delay));
            }
        }

        DPRINTFS(DtuXfers, (&dtu), "buf%d: Transfer done\n",
                 buf->id);

        // we're done with this buffer now
        if (buf->event->isRead())
            reads.sample(dtu.curCycle() - buf->event->startCycle);
        else
            writes.sample(dtu.curCycle() - buf->event->startCycle);
        buf->event->finish();
        buf->event = NULL;

        // start the next one, if there is any
        if (!queue.empty())
        {
            TransferEvent *ev = queue.front();
            queue.pop_front();
            dtu.schedule(ev, dtu.clockEdge(Cycles(1)));
        }
    }
    else
        buf->event->process();
}

XferUnit::Buffer*
XferUnit::allocateBuf(TransferEvent *event, uint flags)
{
    // don't allow message receives in parallel. because otherwise we run into race conditions.
    // e.g., we could overwrite unread messages because we can't increase the message counter when
    // the receive starts (to not notify SW) and thus might start receiving without having space
    // another problem is that we might finish receiving the second message before the first and
    // then increase the message counter, so that the SW looks at the first message, which is not
    // ready yet.
    if (flags & XferFlags::MSGRECV)
    {
        for (size_t i = 0; i < bufCount; ++i)
        {
            if (bufs[i]->event && (bufs[i]->event->flags & XferFlags::MSGRECV))
                return NULL;
        }
    }

    // the first buffer cannot cause pagefaults; thus we can only use it if for
    // transfers which abort if a pagefault is caused
    // this is required to resolve a deadlock due to additional transfers that
    // handle a already running pagefault transfer.
    size_t i = !dtu.tlb() || (flags & XferFlags::NOPF) ? 0 : 1;
    for (; i < bufCount; ++i)
    {
        if (!bufs[i]->event)
        {
            bufs[i]->event = event;
            bufs[i]->offset = 0;
            return bufs[i];
        }
    }

    return NULL;
}
<|endoftext|>