<commit_before>#include "ActiveNode.h"
#include "util/Logger.h"
using srch2::util::Logger;

namespace srch2
{
namespace instantsearch
{

void PrefixActiveNodeSet::getComputedSimilarPrefixes(const Trie *trie, std::vector<std::string> &similarPrefixes){
	for (std::map<const TrieNode*, PivotalActiveNode >::iterator mapIterator = PANMap.begin();
			mapIterator != PANMap.end(); mapIterator ++) {
		const TrieNode *trieNode = mapIterator->first;
		std::string prefix;
		trie->getPrefixString_NotThreadSafe(trieNode, prefix);
		similarPrefixes.push_back(prefix);
	}
}

PrefixActiveNodeSet *PrefixActiveNodeSet::computeActiveNodeSetIncrementally(const CharType additionalChar) {
	// form the new string. // TODO (OPT): avoid string copy
	std::vector<CharType> newString = this->prefix;
	newString.push_back(additionalChar);

	PrefixActiveNodeSet *newActiveNodeSet = new PrefixActiveNodeSet(newString, this->getEditDistanceThreshold(), this->trieRootNodeSharedPtr);

	// PAN:
	for (std::map<const TrieNode*, PivotalActiveNode >::const_iterator mapIterator = PANMap.begin();
			mapIterator != PANMap.end(); mapIterator ++) {
		// Compute the new active nodes for this trie node
		_addPANSetForOneNode(mapIterator->first, mapIterator->second, additionalChar, newActiveNodeSet);
	}

	return newActiveNodeSet;
}

void PrefixActiveNodeSet::printActiveNodes(const Trie* trie) const// Deprecated due to removal of TrieNode->getParent() pointers.
{
	typedef const TrieNode* trieNodeStar;
	std::map<trieNodeStar,PivotalActiveNode>::const_iterator mapIterater;
	for ( mapIterater  = this->PANMap.begin(); mapIterater != this->PANMap.end(); mapIterater++ )
	{
		trieNodeStar trieNode = mapIterater->first;
		string prefix;
		trie->getPrefixString(this->trieRootNodeSharedPtr->root, trieNode, prefix);
        Logger::debug("%s : %d" , prefix.c_str(),mapIterater->second.transformationdistance );
	}
}

void PrefixActiveNodeSet::_addPANSetForOneNode(const TrieNode *trieNode, PivotalActiveNode pan,
            const CharType additionalChar, PrefixActiveNodeSet *newActiveNodeSet) {
        // deletion
	if(additionalChar < CHARTYPE_FUZZY_UPPERBOUND)
	{
		PivotalActiveNode dpan;
		dpan.transformationdistance = pan.transformationdistance + 1;
		dpan.differ = pan.differ + 1;
		dpan.editdistanceofPrefix = pan.editdistanceofPrefix;
		newActiveNodeSet->_addPAN(trieNode, dpan);
	}

	// go through the children of this treNode
	int depthLimit = this->getEditDistanceThreshold() - pan.editdistanceofPrefix;//transformationdistance; LGL: A bug
	int curDepth = 0;
	addPANUpToDepth(trieNode, pan, curDepth, depthLimit, additionalChar, newActiveNodeSet);

}

void PrefixActiveNodeSet::_addPAN(const TrieNode *trieNode, PivotalActiveNode pan) {
	if (pan.transformationdistance > this->editDistanceThreshold) // do nothing if the new distance is above the threshold
		return;
	//PAN:
	std::map<const TrieNode*, PivotalActiveNode >::iterator mapIterator = PANMap.find(trieNode);
	if (mapIterator != PANMap.end()) { // found one
		if (mapIterator->second.transformationdistance > pan.transformationdistance) // reassign the distance if it's smaller
			mapIterator->second = pan;
		else if (mapIterator->second.transformationdistance == pan.transformationdistance)
		{
			if((mapIterator->second.differ < pan.differ)||(mapIterator->second.editdistanceofPrefix > pan.editdistanceofPrefix))
				mapIterator->second = pan;
		}
		return; // otherwise, do nothing
	}

	// insert the new pair
	PANMap.insert(std::pair<const TrieNode*, PivotalActiveNode >(trieNode, pan));

	// set the flag
	this->trieNodeSetVectorComputed = false;
}

void PrefixActiveNodeSet::addPANUpToDepth(const TrieNode *trieNode, PivotalActiveNode pan, const unsigned curDepth, const unsigned depthLimit, const CharType additionalChar, PrefixActiveNodeSet *newActiveNodeSet) {
	// add children
	int max = curDepth;
	if( max < pan.differ )
		max = pan.differ;
	PivotalActiveNode panlocal;

	// if the character is larger than or equal to the upper bound or the depthLimit == 0, we directly
	// locate the child and add it to the pans
	// When depthLimit == 0 we do an exact search, so we don't need to go in the loop
	if(additionalChar >= CHARTYPE_FUZZY_UPPERBOUND || depthLimit == 0)
	{
		int childPosition = trieNode->findChildNodePosition(additionalChar);
		if(childPosition >= 0)
		{
			const TrieNode *child= trieNode->getChild(childPosition);
			panlocal.transformationdistance = pan.editdistanceofPrefix + max;
			panlocal.differ = 0;
			panlocal.editdistanceofPrefix = pan.editdistanceofPrefix + max;
			newActiveNodeSet->_addPAN(child, panlocal);
		}
		return;
	}
	for (unsigned int childIterator = 0; childIterator < trieNode->getChildrenCount(); childIterator++) {
		const TrieNode *child = trieNode->getChild(childIterator);
		//if the current child's character is larger than the upper bound,
		// we no longer need to visit those later children.
		if(child->getCharacter() >= CHARTYPE_FUZZY_UPPERBOUND) break;
		if (child->getCharacter() == additionalChar) { // match
			panlocal.transformationdistance = pan.editdistanceofPrefix + max;
			panlocal.differ = 0;
			panlocal.editdistanceofPrefix = pan.editdistanceofPrefix + max;
			newActiveNodeSet->_addPAN(child, panlocal);
			//swap operation: if there was a delete operation, and there are prefix string
			if (pan.differ > 0 && this->prefix.size()) {
			    // if the last character of prefix can be found in curent's child, it's swap operation, we will give it the same edit distance as its parent
                int childPosition = child->findChildNodePosition(this->prefix.back());
                if (childPosition >= 0) {
                    child= child->getChild(childPosition);
                    panlocal.transformationdistance = pan.editdistanceofPrefix + max;
                    panlocal.differ = 0;
                    panlocal.editdistanceofPrefix = pan.editdistanceofPrefix + max;
                    newActiveNodeSet->_addPAN(child, panlocal);
                }
            }
		}
		if (curDepth < depthLimit) {// recursive call for each child
			addPANUpToDepth(child, pan, curDepth+1, depthLimit, additionalChar, newActiveNodeSet);
		}
	}
}

}
}
<commit_msg>fix format<commit_after>#include "ActiveNode.h"
#include "util/Logger.h"
using srch2::util::Logger;

namespace srch2
{
namespace instantsearch
{

void PrefixActiveNodeSet::getComputedSimilarPrefixes(const Trie *trie, std::vector<std::string> &similarPrefixes)
{
    for (std::map<const TrieNode*, PivotalActiveNode >::iterator mapIterator = PANMap.begin();
            mapIterator != PANMap.end(); mapIterator ++) {
        const TrieNode *trieNode = mapIterator->first;
        std::string prefix;
        trie->getPrefixString_NotThreadSafe(trieNode, prefix);
        similarPrefixes.push_back(prefix);
    }
}

PrefixActiveNodeSet *PrefixActiveNodeSet::computeActiveNodeSetIncrementally(const CharType additionalChar)
{
    // form the new string. // TODO (OPT): avoid string copy
    std::vector<CharType> newString = this->prefix;
    newString.push_back(additionalChar);

    PrefixActiveNodeSet *newActiveNodeSet = new PrefixActiveNodeSet(newString, this->getEditDistanceThreshold(), this->trieRootNodeSharedPtr);

    // PAN:
    for (std::map<const TrieNode*, PivotalActiveNode >::const_iterator mapIterator = PANMap.begin();
            mapIterator != PANMap.end(); mapIterator ++) {
        // Compute the new active nodes for this trie node
        _addPANSetForOneNode(mapIterator->first, mapIterator->second, additionalChar, newActiveNodeSet);
    }

    return newActiveNodeSet;
}

void PrefixActiveNodeSet::printActiveNodes(const Trie* trie) const// Deprecated due to removal of TrieNode->getParent() pointers.
{
    typedef const TrieNode* trieNodeStar;
    std::map<trieNodeStar,PivotalActiveNode>::const_iterator mapIterater;
    for ( mapIterater  = this->PANMap.begin(); mapIterater != this->PANMap.end(); mapIterater++ ) {
        trieNodeStar trieNode = mapIterater->first;
        string prefix;
        trie->getPrefixString(this->trieRootNodeSharedPtr->root, trieNode, prefix);
        Logger::debug("%s : %d" , prefix.c_str(),mapIterater->second.transformationdistance );
    }
}

void PrefixActiveNodeSet::_addPANSetForOneNode(const TrieNode *trieNode, PivotalActiveNode pan,
        const CharType additionalChar, PrefixActiveNodeSet *newActiveNodeSet)
{
    // deletion
    if (additionalChar < CHARTYPE_FUZZY_UPPERBOUND) {
        PivotalActiveNode dpan;
        dpan.transformationdistance = pan.transformationdistance + 1;
        dpan.differ = pan.differ + 1;
        dpan.editdistanceofPrefix = pan.editdistanceofPrefix;
        newActiveNodeSet->_addPAN(trieNode, dpan);
    }

    // go through the children of this treNode
    int depthLimit = this->getEditDistanceThreshold() - pan.editdistanceofPrefix;//transformationdistance; LGL: A bug
    int curDepth = 0;
    addPANUpToDepth(trieNode, pan, curDepth, depthLimit, additionalChar, newActiveNodeSet);

}

void PrefixActiveNodeSet::_addPAN(const TrieNode *trieNode, PivotalActiveNode pan)
{
    if (pan.transformationdistance > this->editDistanceThreshold) // do nothing if the new distance is above the threshold
        return;
    //PAN:
    std::map<const TrieNode*, PivotalActiveNode >::iterator mapIterator = PANMap.find(trieNode);
    if (mapIterator != PANMap.end()) { // found one
        if (mapIterator->second.transformationdistance > pan.transformationdistance) // reassign the distance if it's smaller
            mapIterator->second = pan;
        else if (mapIterator->second.transformationdistance == pan.transformationdistance) {
            if ((mapIterator->second.differ < pan.differ)||(mapIterator->second.editdistanceofPrefix > pan.editdistanceofPrefix))
                mapIterator->second = pan;
        }
        return; // otherwise, do nothing
    }

    // insert the new pair
    PANMap.insert(std::pair<const TrieNode*, PivotalActiveNode >(trieNode, pan));

    // set the flag
    this->trieNodeSetVectorComputed = false;
}

void PrefixActiveNodeSet::addPANUpToDepth(const TrieNode *trieNode, PivotalActiveNode pan, const unsigned curDepth, const unsigned depthLimit, const CharType additionalChar, PrefixActiveNodeSet *newActiveNodeSet)
{
    // add children
    int max = curDepth;
    if ( max < pan.differ )
        max = pan.differ;
    PivotalActiveNode panlocal;

    // if the character is larger than or equal to the upper bound or the depthLimit == 0, we directly
    // locate the child and add it to the pans
    // When depthLimit == 0 we do an exact search, so we don't need to go in the loop
    if (additionalChar >= CHARTYPE_FUZZY_UPPERBOUND || depthLimit == 0) {
        int childPosition = trieNode->findChildNodePosition(additionalChar);
        if (childPosition >= 0) {
            const TrieNode *child= trieNode->getChild(childPosition);
            panlocal.transformationdistance = pan.editdistanceofPrefix + max;
            panlocal.differ = 0;
            panlocal.editdistanceofPrefix = pan.editdistanceofPrefix + max;
            newActiveNodeSet->_addPAN(child, panlocal);
        }
        return;
    }
    for (unsigned int childIterator = 0; childIterator < trieNode->getChildrenCount(); childIterator++) {
        const TrieNode *child = trieNode->getChild(childIterator);
        //if the current child's character is larger than the upper bound,
        // we no longer need to visit those later children.
        if (child->getCharacter() >= CHARTYPE_FUZZY_UPPERBOUND) break;
        if (child->getCharacter() == additionalChar) { // match
            panlocal.transformationdistance = pan.editdistanceofPrefix + max;
            panlocal.differ = 0;
            panlocal.editdistanceofPrefix = pan.editdistanceofPrefix + max;
            newActiveNodeSet->_addPAN(child, panlocal);
            //swap operation: if there was a delete operation, and there are prefix string
            if (pan.differ > 0 && this->prefix.size()) {
                // if the last character of prefix can be found in curent's child, it's swap operation, we will give it the same edit distance as its parent
                int childPosition = child->findChildNodePosition(this->prefix.back());
                if (childPosition >= 0) {
                    child= child->getChild(childPosition);
                    panlocal.transformationdistance = pan.editdistanceofPrefix + max;
                    panlocal.differ = 0;
                    panlocal.editdistanceofPrefix = pan.editdistanceofPrefix + max;
                    newActiveNodeSet->_addPAN(child, panlocal);
                }
            }
        }
        if (curDepth < depthLimit) {// recursive call for each child
            addPANUpToDepth(child, pan, curDepth+1, depthLimit, additionalChar, newActiveNodeSet);
        }
    }
}

}
}
<|endoftext|>


<commit_before>/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the Qt Mobility Components.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

//TESTED_COMPONENT=src/location

#include "../qlocationtestutils_p.h"

#include <qgeocoordinate.h>
#include <qgraphicsgeomap.h>
#include <qgeomappixmapobject.h>
#include <qgeomapdata.h>
#include <qgeomappingmanager.h>
#include <qtest.h>

#include <QtGui/QApplication>
#include <QDebug>
#include <QtGui>
#include <QPixmap>

#include "../qgeotiledmappingmanagerengine/pixelindexengine.h"

QTM_USE_NAMESPACE
Q_DECLARE_METATYPE(QGeoCoordinate)
Q_DECLARE_METATYPE(QGeoCoordinate::CoordinateFormat)
Q_DECLARE_METATYPE(QGeoCoordinate::CoordinateType)
Q_DECLARE_METATYPE(QGeoMappingManagerEngine*)
Q_DECLARE_METATYPE(QGeoMapData*)
Q_DECLARE_METATYPE(QGeoMapPixmapObject*)

class tst_QGeoTiledMapData : public QObject
{
    Q_OBJECT

private slots:
    void initTestCase();

    void pixmapDraw_data();
    void pixmapDraw();

    void objectsAtPoint_data();
    void objectsAtPoint();

    void pixmapAtDateline_data();
    void pixmapAtDateline();

private:
    void makeFixtures(QGeoMapData *&gmd, QGeoMapPixmapObject *&obj,
                      const QGeoCoordinate &center, const QGeoCoordinate &pixmap,
                      const qreal &zoom, const QPixmap &targetPixmap,
                      QGeoMappingManagerEngine *mgr, const QSize &window,
                      const QSize &target);

};

void tst_QGeoTiledMapData::initTestCase()
{
    qRegisterMetaType<QGeoCoordinate>("QGeoCoordinate");
    qRegisterMetaType<QGeoMappingManagerEngine*>("QGeoMappingManagerEngine*");
    qRegisterMetaType<QGeoMapData*>("QGeoMapData*");
    qRegisterMetaType<QGeoMapPixmapObject*>("QGeoMapPixmapObject*");
    qRegisterMetaType<QPixmap>("QPixmap");
}

void tst_QGeoTiledMapData::makeFixtures(QGeoMapData *&gmd,
                                        QGeoMapPixmapObject *&obj,
                                        const QGeoCoordinate &center,
                                        const QGeoCoordinate &pixmap,
                                        const qreal &zoom,
                                        const QPixmap &targetPixmap,
                                        QGeoMappingManagerEngine *mgr,
                                        const QSize &window,
                                        const QSize &target)
{
    gmd = mgr->createMapData();
    gmd->init();
    gmd->setWindowSize(window);
    gmd->setZoomLevel(zoom);
    gmd->setCenter(center);
    obj = new QGeoMapPixmapObject();
    obj->setPixmap(targetPixmap);
    obj->setCoordinate(pixmap);
    gmd->addMapObject(obj);
    obj->setVisible(true);
}

void tst_QGeoTiledMapData::pixmapDraw_data()
{
    QTest::addColumn<QGeoMappingManagerEngine*>("mgr");
    QTest::addColumn<QGeoMapData*>("gmd");
    QTest::addColumn<QGeoMapPixmapObject*>("obj");
    QTest::addColumn<QSize>("windowSize");
    QTest::addColumn<QSize>("targetSize");
    QTest::addColumn<QPoint>("offset");

    QMap<QString, QVariant> params;
    QGeoMappingManagerEngine *mgr = new WhiteTileEngine(params, this);

    QSize window = QSize(500, 500);
    QSize target = QSize(50, 50);
    QPoint offset = QPoint(0, 0);
    QPixmap targetPixmap = indexedPixmap(target.width(), target.height());

    QGeoMapData *gmd;
    QGeoCoordinate center;
    QGeoMapPixmapObject *obj;

    center = QGeoCoordinate(-27.58, 153.10);
    makeFixtures(gmd, obj, center, center, 3.0, targetPixmap, mgr, window, target);
    QTest::newRow("Brisbane @z=3") << mgr << gmd << obj << window << target << offset;

    center = QGeoCoordinate(45.6, -160.2);
    makeFixtures(gmd, obj, center, center, 2.0, targetPixmap, mgr, window, target);
    QTest::newRow("Somewhere up north") << mgr << gmd << obj << window << target << offset;

    center = QGeoCoordinate(0.0, 0.0);
    makeFixtures(gmd, obj, center, center, 1.0, targetPixmap, mgr, window, target);
    QTest::newRow("At 0,0") << mgr << gmd << obj << window << target << offset;

    center = QGeoCoordinate(0.0, 179.9);
    makeFixtures(gmd, obj, center, center, 2.0, targetPixmap, mgr, window, target);
    QTest::newRow("Positive dateline") << mgr << gmd << obj << window << target << offset;

    center = QGeoCoordinate(0.0, -180.0);
    makeFixtures(gmd, obj, center, center, 2.0, targetPixmap, mgr, window, target);
    QTest::newRow("Negative dateline") << mgr << gmd << obj << window << target << offset;

    center = QGeoCoordinate(-27.58, 153.10);
    makeFixtures(gmd, obj, center, center, 3.0, targetPixmap, mgr, window, target);
    offset = QPoint(5,5);
    obj->setOffset(offset);
    QTest::newRow("Brisbane with offset") << mgr << gmd << obj << window << target << offset;

    center = QGeoCoordinate(-27.58, 153.10);
    makeFixtures(gmd, obj, center, center, 3.0, targetPixmap, mgr, window, target);
    offset = QPoint(-5,-5);
    obj->setOffset(offset);
    QTest::newRow("Brisbane with -ve offset") << mgr << gmd << obj << window << target << offset;

    center = QGeoCoordinate(0.0, -180.0);
    makeFixtures(gmd, obj, center, center, 2.0, targetPixmap, mgr, window, target);
    offset = QPoint(-20, 0);
    obj->setOffset(offset);
    QTest::newRow("Negative dateline with offset") << mgr << gmd << obj << window << target << offset;
}

void tst_QGeoTiledMapData::objectsAtPoint_data()
{
    QTest::addColumn<QGeoMappingManagerEngine*>("mgr");
    QTest::addColumn<QGeoMapData*>("gmd");
    QTest::addColumn<QGeoMapPixmapObject*>("obj");
    QTest::addColumn<QGeoCoordinate>("center");

    QMap<QString, QVariant> params;
    QGeoMappingManagerEngine *mgr = new WhiteTileEngine(params, this);

    QSize window = QSize(500, 500);
    QSize target = QSize(50, 50);
    QPixmap targetPixmap = indexedPixmap(target.width(), target.height());

    QGeoMapData *gmd;
    QGeoCoordinate center;
    QGeoCoordinate pixmap;
    QGeoMapPixmapObject *obj;

    center = QGeoCoordinate(-27.58, 153.10);
    makeFixtures(gmd, obj, center, center, 3.0, targetPixmap, mgr, window, target);
    QTest::newRow("Brisbane @z=3") << mgr << gmd << obj << center;

    center = QGeoCoordinate(0.0, 0.0);
    makeFixtures(gmd, obj, center, center, 1.0, targetPixmap, mgr, window, target);
    QTest::newRow("At 0,0") << mgr << gmd << obj << center;

    center = QGeoCoordinate(0.0, 179.9);
    makeFixtures(gmd, obj, center, center, 2.0, targetPixmap, mgr, window, target);
    QTest::newRow("Positive dateline") << mgr << gmd << obj << center;

    center = QGeoCoordinate(0.0, -179.9);
    pixmap = QGeoCoordinate(3.0, 175.0);
    makeFixtures(gmd, obj, center, pixmap, 3.0, targetPixmap, mgr, window, target);
    QTest::newRow("Crossing dateline") << mgr << gmd << obj << center;
}

void tst_QGeoTiledMapData::objectsAtPoint()
{
    QFETCH(QGeoMappingManagerEngine*, mgr);
    QFETCH(QGeoMapData*, gmd);
    QFETCH(QGeoMapPixmapObject*, obj);
    QFETCH(QGeoCoordinate, center);

    QVERIFY(gmd->mapObjectsInViewport().contains(obj));
    QPointF centerPt = gmd->coordinateToScreenPosition(center);
    QVERIFY(gmd->mapObjectsAtScreenPosition(centerPt).contains(obj));
}

void tst_QGeoTiledMapData::pixmapDraw()
{
    QFETCH(QGeoMappingManagerEngine*, mgr);
    QFETCH(QGeoMapData*, gmd);
    QFETCH(QGeoMapPixmapObject*, obj);
    QFETCH(QSize, windowSize);
    QFETCH(QSize, targetSize);
    QFETCH(QPoint, offset);

    QPixmap pm(windowSize);
    QPainter *painter = new QPainter(&pm);
    pm.fill(Qt::black);

    QApplication::processEvents();

    gmd->paint(painter, NULL);
    painter->end();

    QImage im = pm.toImage();

    uint px = 0;
    uint x = windowSize.width()/2 + offset.x();
    for (; px < targetSize.width(); px++, x++) {
        uint py = 0;
        uint y = windowSize.height()/2 + offset.y();
        for (; py < targetSize.height(); py++, y++) {
            TilePixelValue tpv(im.pixel(x, y));
            QCOMPARE(tpv.px(), px);
            QCOMPARE(tpv.py(), py);
            QCOMPARE(tpv.zoom(), 1u);
        }
    }
}

void tst_QGeoTiledMapData::pixmapAtDateline_data()
{
    QTest::addColumn<QGeoMappingManagerEngine*>("mgr");
    QTest::addColumn<QGeoMapData*>("gmd");
    QTest::addColumn<QGeoMapPixmapObject*>("obj");
    QTest::addColumn<QSize>("windowSize");
    QTest::addColumn<QSize>("targetSize");

    QMap<QString, QVariant> params;
    QGeoMappingManagerEngine *mgr = new WhiteTileEngine(params, this);

    QSize window = QSize(500, 500);
    QSize target = QSize(50, 50);
    QPixmap targetPixmap = indexedPixmap(target.width(), target.height());

    QGeoMapData *gmd;
    QGeoCoordinate center, pixmap;
    QGeoMapPixmapObject *obj;
    QPoint offset;

    center = QGeoCoordinate(0.0, -179.9);
    pixmap = QGeoCoordinate(3.0, 175.0);
    makeFixtures(gmd, obj, center, pixmap, 3.0, targetPixmap, mgr, window, target);
    QTest::newRow("east to west") << mgr << gmd << obj << window << target;

    center = QGeoCoordinate(0.0, 179.0);
    pixmap = QGeoCoordinate(0.0, -179.0);
    makeFixtures(gmd, obj, center, pixmap, 3.0, targetPixmap, mgr, window, target);
    offset = QPoint(-20, -5);
    obj->setOffset(offset);
    QTest::newRow("west to east") << mgr << gmd << obj << window << target;
}

void tst_QGeoTiledMapData::pixmapAtDateline()
{
    QFETCH(QGeoMappingManagerEngine*, mgr);
    QFETCH(QGeoMapData*, gmd);
    QFETCH(QGeoMapPixmapObject*, obj);
    QFETCH(QSize, windowSize);
    QFETCH(QSize, targetSize);

    QPixmap pm(windowSize);
    QPainter *painter = new QPainter(&pm);
    pm.fill(Qt::black);

    QApplication::processEvents();

    gmd->paint(painter, NULL);
    painter->end();

    QImage im = pm.toImage();
    TilePixelValue tpv(im.pixel(windowSize.width()/2, windowSize.height()/2));
    QCOMPARE(tpv.zoom(), 1u);
    QVERIFY(tpv.px() < targetSize.width());
    QVERIFY(tpv.py() < targetSize.height());
    QVERIFY(tpv.px() > 1);
    QVERIFY(tpv.py() > 1);
}

QTEST_MAIN(tst_QGeoTiledMapData)
#include "tst_qgeotiledmapdata.moc"
<commit_msg>Adding autotest for QGeoTiledMapData::pan()<commit_after>/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the Qt Mobility Components.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

//TESTED_COMPONENT=src/location

#include "../qlocationtestutils_p.h"

#include <qgeocoordinate.h>
#include <qgraphicsgeomap.h>
#include <qgeomappixmapobject.h>
#include <qgeomapdata.h>
#include <qgeomappingmanager.h>
#include <qtest.h>

#include <QtGui/QApplication>
#include <QDebug>
#include <QtGui>
#include <QPixmap>

#include "../qgeotiledmappingmanagerengine/pixelindexengine.h"

QTM_USE_NAMESPACE
Q_DECLARE_METATYPE(QGeoCoordinate)
Q_DECLARE_METATYPE(QGeoCoordinate::CoordinateFormat)
Q_DECLARE_METATYPE(QGeoCoordinate::CoordinateType)
Q_DECLARE_METATYPE(QGeoMappingManagerEngine*)
Q_DECLARE_METATYPE(QGeoMapData*)
Q_DECLARE_METATYPE(QGeoMapPixmapObject*)

class tst_QGeoTiledMapData : public QObject
{
    Q_OBJECT

private slots:
    void initTestCase();

    void pixmapDraw_data();
    void pixmapDraw();

    void objectsAtPoint_data();
    void objectsAtPoint();

    void pixmapAtDateline_data();
    void pixmapAtDateline();

    void panTest_data();
    void panTest();

private:
    void makeFixtures(QGeoMapData *&gmd, QGeoMapPixmapObject *&obj,
                      const QGeoCoordinate &center, const QGeoCoordinate &pixmap,
                      const qreal &zoom, const QPixmap &targetPixmap,
                      QGeoMappingManagerEngine *mgr, const QSize &window,
                      const QSize &target);

};

void tst_QGeoTiledMapData::initTestCase()
{
    qRegisterMetaType<QGeoCoordinate>("QGeoCoordinate");
    qRegisterMetaType<QGeoMappingManagerEngine*>("QGeoMappingManagerEngine*");
    qRegisterMetaType<QGeoMapData*>("QGeoMapData*");
    qRegisterMetaType<QGeoMapPixmapObject*>("QGeoMapPixmapObject*");
    qRegisterMetaType<QPixmap>("QPixmap");
}

void tst_QGeoTiledMapData::makeFixtures(QGeoMapData *&gmd,
                                        QGeoMapPixmapObject *&obj,
                                        const QGeoCoordinate &center,
                                        const QGeoCoordinate &pixmap,
                                        const qreal &zoom,
                                        const QPixmap &targetPixmap,
                                        QGeoMappingManagerEngine *mgr,
                                        const QSize &window,
                                        const QSize &target)
{
    gmd = mgr->createMapData();
    // this call shouldn't be necessary?
    // but you get a nice happy sigsegv without it
    gmd->init();
    gmd->setWindowSize(window);
    gmd->setZoomLevel(zoom);
    gmd->setCenter(center);
    obj = new QGeoMapPixmapObject();
    obj->setPixmap(targetPixmap);
    obj->setCoordinate(pixmap);
    gmd->addMapObject(obj);
    obj->setVisible(true);
}

void tst_QGeoTiledMapData::pixmapDraw_data()
{
    QTest::addColumn<QGeoMappingManagerEngine*>("mgr");
    QTest::addColumn<QGeoMapData*>("gmd");
    QTest::addColumn<QGeoMapPixmapObject*>("obj");
    QTest::addColumn<QSize>("windowSize");
    QTest::addColumn<QSize>("targetSize");
    QTest::addColumn<QPoint>("offset");

    QMap<QString, QVariant> params;
    QGeoMappingManagerEngine *mgr = new WhiteTileEngine(params, this);

    QSize window = QSize(500, 500);
    QSize target = QSize(50, 50);
    QPoint offset = QPoint(0, 0);
    QPixmap targetPixmap = indexedPixmap(target.width(), target.height());

    QGeoMapData *gmd;
    QGeoCoordinate center;
    QGeoMapPixmapObject *obj;

    center = QGeoCoordinate(-27.58, 153.10);
    makeFixtures(gmd, obj, center, center, 3.0, targetPixmap, mgr, window, target);
    QTest::newRow("Brisbane @z=3") << mgr << gmd << obj << window << target << offset;

    center = QGeoCoordinate(45.6, -160.2);
    makeFixtures(gmd, obj, center, center, 2.0, targetPixmap, mgr, window, target);
    QTest::newRow("Somewhere up north") << mgr << gmd << obj << window << target << offset;

    center = QGeoCoordinate(0.0, 0.0);
    makeFixtures(gmd, obj, center, center, 1.0, targetPixmap, mgr, window, target);
    QTest::newRow("At 0,0") << mgr << gmd << obj << window << target << offset;

    center = QGeoCoordinate(0.0, 179.9);
    makeFixtures(gmd, obj, center, center, 2.0, targetPixmap, mgr, window, target);
    QTest::newRow("Positive dateline") << mgr << gmd << obj << window << target << offset;

    center = QGeoCoordinate(0.0, -180.0);
    makeFixtures(gmd, obj, center, center, 2.0, targetPixmap, mgr, window, target);
    QTest::newRow("Negative dateline") << mgr << gmd << obj << window << target << offset;

    center = QGeoCoordinate(-27.58, 153.10);
    makeFixtures(gmd, obj, center, center, 3.0, targetPixmap, mgr, window, target);
    offset = QPoint(5,5);
    obj->setOffset(offset);
    QTest::newRow("Brisbane with offset") << mgr << gmd << obj << window << target << offset;

    center = QGeoCoordinate(-27.58, 153.10);
    makeFixtures(gmd, obj, center, center, 3.0, targetPixmap, mgr, window, target);
    offset = QPoint(-5,-5);
    obj->setOffset(offset);
    QTest::newRow("Brisbane with -ve offset") << mgr << gmd << obj << window << target << offset;

    center = QGeoCoordinate(0.0, -180.0);
    makeFixtures(gmd, obj, center, center, 2.0, targetPixmap, mgr, window, target);
    offset = QPoint(-20, 0);
    obj->setOffset(offset);
    QTest::newRow("Negative dateline with offset") << mgr << gmd << obj << window << target << offset;
}

void tst_QGeoTiledMapData::objectsAtPoint_data()
{
    QTest::addColumn<QGeoMappingManagerEngine*>("mgr");
    QTest::addColumn<QGeoMapData*>("gmd");
    QTest::addColumn<QGeoMapPixmapObject*>("obj");
    QTest::addColumn<QGeoCoordinate>("center");

    QMap<QString, QVariant> params;
    QGeoMappingManagerEngine *mgr = new WhiteTileEngine(params, this);

    QSize window = QSize(500, 500);
    QSize target = QSize(50, 50);
    QPixmap targetPixmap = indexedPixmap(target.width(), target.height());

    QGeoMapData *gmd;
    QGeoCoordinate center;
    QGeoCoordinate pixmap;
    QGeoMapPixmapObject *obj;

    center = QGeoCoordinate(-27.58, 153.10);
    makeFixtures(gmd, obj, center, center, 3.0, targetPixmap, mgr, window, target);
    QTest::newRow("Brisbane @z=3") << mgr << gmd << obj << center;

    center = QGeoCoordinate(0.0, 0.0);
    makeFixtures(gmd, obj, center, center, 1.0, targetPixmap, mgr, window, target);
    QTest::newRow("At 0,0") << mgr << gmd << obj << center;

    center = QGeoCoordinate(0.0, 179.9);
    makeFixtures(gmd, obj, center, center, 2.0, targetPixmap, mgr, window, target);
    QTest::newRow("Positive dateline") << mgr << gmd << obj << center;

    center = QGeoCoordinate(0.0, -179.9);
    pixmap = QGeoCoordinate(3.0, 175.0);
    makeFixtures(gmd, obj, center, pixmap, 3.0, targetPixmap, mgr, window, target);
    QTest::newRow("Crossing dateline") << mgr << gmd << obj << center;
}

void tst_QGeoTiledMapData::panTest_data()
{
    QTest::addColumn<QGeoMappingManagerEngine*>("mgr");
    QTest::addColumn<QGeoMapData*>("gmd");
    QTest::addColumn<QGeoMapPixmapObject*>("obj");
    QTest::addColumn<QGeoCoordinate>("center");
    QTest::addColumn<QPoint>("pxCenter");
    QTest::addColumn<QPoint>("pan");
    QTest::addColumn<qreal>("dist");

    QMap<QString, QVariant> params;
    QGeoMappingManagerEngine *mgr = new WhiteTileEngine(params, this);

    QSize window = QSize(500, 500);
    QSize target = QSize(50, 50);
    QPoint pxCenter = QPoint(250, 250);
    QPixmap targetPixmap = indexedPixmap(target.width(), target.height());

    QGeoMapData *gmd;
    QGeoCoordinate center;
    QPoint pan;
    qreal dist;
    QGeoMapPixmapObject *obj;

    center = QGeoCoordinate(-27.58, 153.10);
    pan = QPoint(30, 50);
    dist = 1800e3;
    makeFixtures(gmd, obj, center, center, 3.0, targetPixmap, mgr, window, target);
    QTest::newRow("Brisbane, pan +ve") << mgr << gmd << obj <<
                                          center << pxCenter << pan << dist;

    center = QGeoCoordinate(0.0, 0.0);
    pan = QPoint(-30, 0);
    dist = 4700e3;
    makeFixtures(gmd, obj, center, center, 1.0, targetPixmap, mgr, window, target);
    QTest::newRow("At 0,0, pan -ve x") << mgr << gmd << obj <<
                                          center << pxCenter << pan << dist;

    center = QGeoCoordinate(0.0, 179.9);
    pan = QPoint(50, -10);
    dist = 3900e3;
    makeFixtures(gmd, obj, center, center, 2.0, targetPixmap, mgr, window, target);
    QTest::newRow("Positive dateline, pan +ve") << mgr << gmd << obj << center
                                                << pxCenter << pan << dist;
}

void tst_QGeoTiledMapData::panTest()
{
    QFETCH(QGeoMappingManagerEngine*, mgr);
    QFETCH(QGeoMapData*, gmd);
    QFETCH(QGeoMapPixmapObject*, obj);
    QFETCH(QGeoCoordinate, center);
    QFETCH(QPoint, pxCenter);
    QFETCH(QPoint, pan);
    QFETCH(qreal, dist);

    QPointF c = gmd->coordinateToScreenPosition(center);
    QCOMPARE(int(c.x()), pxCenter.x());
    QCOMPARE(int(c.y()), pxCenter.y());

    gmd->pan(pan.x(), pan.y());

    QPointF c2 = gmd->coordinateToScreenPosition(center);
    QCOMPARE(int(c2.x()), pxCenter.x() - pan.x());
    QCOMPARE(int(c2.y()), pxCenter.y() - pan.y());

    QGeoCoordinate nc = gmd->screenPositionToCoordinate(pxCenter);
    qreal d = nc.distanceTo(center);
    QVERIFY(d > 0.9*dist && d < 1.1*dist);
}

void tst_QGeoTiledMapData::objectsAtPoint()
{
    QFETCH(QGeoMappingManagerEngine*, mgr);
    QFETCH(QGeoMapData*, gmd);
    QFETCH(QGeoMapPixmapObject*, obj);
    QFETCH(QGeoCoordinate, center);

    QVERIFY(gmd->mapObjects().contains(obj));
    QVERIFY(gmd->mapObjectsInViewport().contains(obj));
    QPointF centerPt = gmd->coordinateToScreenPosition(center);
    QVERIFY(gmd->mapObjectsAtScreenPosition(centerPt).contains(obj));
}

void tst_QGeoTiledMapData::pixmapDraw()
{
    QFETCH(QGeoMappingManagerEngine*, mgr);
    QFETCH(QGeoMapData*, gmd);
    QFETCH(QGeoMapPixmapObject*, obj);
    QFETCH(QSize, windowSize);
    QFETCH(QSize, targetSize);
    QFETCH(QPoint, offset);

    QPixmap pm(windowSize);
    QPainter *painter = new QPainter(&pm);
    pm.fill(Qt::black);

    QApplication::processEvents();

    gmd->paint(painter, NULL);
    painter->end();

    QImage im = pm.toImage();

    uint px = 0;
    uint x = windowSize.width()/2 + offset.x();
    for (; px < targetSize.width(); px++, x++) {
        uint py = 0;
        uint y = windowSize.height()/2 + offset.y();
        for (; py < targetSize.height(); py++, y++) {
            TilePixelValue tpv(im.pixel(x, y));
            QCOMPARE(tpv.px(), px);
            QCOMPARE(tpv.py(), py);
            QCOMPARE(tpv.zoom(), 1u);
        }
    }
}

void tst_QGeoTiledMapData::pixmapAtDateline_data()
{
    QTest::addColumn<QGeoMappingManagerEngine*>("mgr");
    QTest::addColumn<QGeoMapData*>("gmd");
    QTest::addColumn<QGeoMapPixmapObject*>("obj");
    QTest::addColumn<QSize>("windowSize");
    QTest::addColumn<QSize>("targetSize");

    QMap<QString, QVariant> params;
    QGeoMappingManagerEngine *mgr = new WhiteTileEngine(params, this);

    QSize window = QSize(500, 500);
    QSize target = QSize(50, 50);
    QPixmap targetPixmap = indexedPixmap(target.width(), target.height());

    QGeoMapData *gmd;
    QGeoCoordinate center, pixmap;
    QGeoMapPixmapObject *obj;
    QPoint offset;

    center = QGeoCoordinate(0.0, -179.9);
    pixmap = QGeoCoordinate(3.0, 175.0);
    makeFixtures(gmd, obj, center, pixmap, 3.0, targetPixmap, mgr, window, target);
    QTest::newRow("east to west") << mgr << gmd << obj << window << target;

    center = QGeoCoordinate(0.0, 179.0);
    pixmap = QGeoCoordinate(0.0, -179.0);
    makeFixtures(gmd, obj, center, pixmap, 3.0, targetPixmap, mgr, window, target);
    offset = QPoint(-20, -5);
    obj->setOffset(offset);
    QTest::newRow("west to east") << mgr << gmd << obj << window << target;
}

void tst_QGeoTiledMapData::pixmapAtDateline()
{
    QFETCH(QGeoMappingManagerEngine*, mgr);
    QFETCH(QGeoMapData*, gmd);
    QFETCH(QGeoMapPixmapObject*, obj);
    QFETCH(QSize, windowSize);
    QFETCH(QSize, targetSize);

    QPixmap pm(windowSize);
    QPainter *painter = new QPainter(&pm);
    pm.fill(Qt::black);

    QApplication::processEvents();

    gmd->paint(painter, NULL);
    painter->end();

    QImage im = pm.toImage();
    TilePixelValue tpv(im.pixel(windowSize.width()/2, windowSize.height()/2));
    QCOMPARE(tpv.zoom(), 1u);
    QVERIFY(tpv.px() < targetSize.width());
    QVERIFY(tpv.py() < targetSize.height());
    QVERIFY(tpv.px() > 1);
    QVERIFY(tpv.py() > 1);
}

QTEST_MAIN(tst_QGeoTiledMapData)
#include "tst_qgeotiledmapdata.moc"
<|endoftext|>


<commit_before>/*****************************************************************************
 * Media Library
 *****************************************************************************
 * Copyright (C) 2015-2019 Hugo Beauzée-Luyssen, Videolabs, VideoLAN
 *
 * Authors: Hugo Beauzée-Luyssen <hugo@beauzee.fr>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#if HAVE_CONFIG_H
# include "config.h"
#endif

#include "SqliteConnection.h"

#include "database/SqliteTools.h"

#include <cstring>

#define DEBUG_SQLITE_TRIGGERS 0

namespace medialibrary
{
namespace sqlite
{

Connection::Connection( const std::string& dbPath )
    : m_dbPath( dbPath )
    , m_readLock( m_contextLock )
    , m_writeLock( m_contextLock )
{
    if ( sqlite3_threadsafe() == 0 )
        throw std::runtime_error( "SQLite isn't built with threadsafe mode" );
    if ( sqlite3_config( SQLITE_CONFIG_MULTITHREAD ) == SQLITE_ERROR )
        throw std::runtime_error( "Failed to enable sqlite multithreaded mode" );
    sqlite3_config( SQLITE_CONFIG_LOG, &logCallback, nullptr );
}

Connection::~Connection()
{
    sqlite::Statement::FlushStatementCache();
}

Connection::Handle Connection::handle()
{
    /**
     * We need to have a single sqlite connection per thread, but we also need
     * to be able to destroy those when the SqliteConnection wrapper instance
     * gets destroyed (otherwise we can't re-instantiate the media library during
     * the application runtime. That can't work for unit test and can be quite
     * a limitation for the application using the medialib)
     * Being able to refer to all per-thread connection means we can't use
     * thread local directly, however, we need to know if a thread goes away, to
     * avoid re-using an old connection on a new thread with the same id, as this
     * would result in a "Database is locked error"
     * In order to solve this, we use a single map to store all connection,
     * indexed by std::thread::id (well, compat::thread::id). Additionaly, in
     * order to know when a thread gets terminated, we store a thread_local
     * object to signal back when a thread returns, and to remove the now
     * unusable connection.
     * Also note that we need to flush any potentially cached compiled statement
     * when a thread gets terminated, as it would become unusable as well.
     *
     * \sa sqlite::Connection::ThreadSpecificConnection::~ThreadSpecificConnection
     * \sa sqlite::Statement::StatementsCache
     */
    std::unique_lock<compat::Mutex> lock( m_connMutex );
    auto it = m_conns.find( compat::this_thread::get_id() );
    if ( it == end( m_conns ) )
    {
        sqlite3* dbConnection;
        auto flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_NOMUTEX;
        if ( m_conns.empty() == true )
            flags |= SQLITE_OPEN_CREATE;
        auto res = sqlite3_open_v2( m_dbPath.c_str(), &dbConnection, flags, nullptr );
        ConnPtr dbConn( dbConnection, &sqlite3_close );
        if ( res != SQLITE_OK )
        {
            int err = sqlite3_system_errno(dbConnection);
            LOG_ERROR( "Failed to connect to database. OS error: ", err );
            errors::mapToException( "<connecting to db>", "", res );
        }
        res = sqlite3_extended_result_codes( dbConnection, 1 );
        if ( res != SQLITE_OK )
            errors::mapToException( "<enabling extended errors>", "", res );
        sqlite3_busy_timeout( dbConnection, 500 );
        // Don't use public wrapper, they need to be able to call getConn, which
        // would result from a recursive call and a deadlock from here.
        setPragma( dbConnection, "foreign_keys", "1" );
        setPragma( dbConnection, "recursive_triggers", "1" );
#ifdef __ANDROID__
        // https://github.com/mozilla/mentat/issues/505
        // Should solve `Failed to run request <DELETE FROM File WHERE id_file = ?>: disk I/O error(6410)`
        setPragma( dbConnection, "temp_store", "2" );
#endif
#if DEBUG_SQLITE_TRIGGERS
        sqlite3_trace_v2( dbConnection, SQLITE_TRACE_STMT | SQLITE_TRACE_CLOSE,
                          [](unsigned int t, void* , void* p, void* x) {
            if ( t == SQLITE_TRACE_STMT )
            {
                const char* str = static_cast<const char*>( x );
                LOG_VERBOSE( "Executed: ", str );
            }
            else if ( t == SQLITE_TRACE_CLOSE )
            {
                LOG_VERBOSE( "Connection ", p, " was closed" );
            }
            return 0;
        }, nullptr );
#endif
        m_conns.emplace( compat::this_thread::get_id(), std::move( dbConn ) );
        sqlite3_update_hook( dbConnection, &updateHook, this );
        static thread_local ThreadSpecificConnection tsc( shared_from_this() );
        return dbConnection;
    }
    return it->second.get();
}

std::unique_ptr<sqlite::Transaction> Connection::newTransaction()
{
    return std::unique_ptr<sqlite::Transaction>{ new sqlite::Transaction( this ) };
}

Connection::ReadContext Connection::acquireReadContext()
{
    return ReadContext{ m_readLock };
}

Connection::WriteContext Connection::acquireWriteContext()
{
    return WriteContext{ m_writeLock };
}

void Connection::setPragma( Connection::Handle conn, const std::string& pragmaName,
                            const std::string& value )

{
    std::string reqBase = std::string{ "PRAGMA " } + pragmaName;
    std::string reqSet = reqBase + " = " + value;

    sqlite::Statement stmt( conn, reqSet );
    stmt.execute();
    if ( stmt.row() != nullptr )
        throw std::runtime_error( "Failed to enable/disable " + pragmaName );

    sqlite::Statement stmtCheck( conn, reqBase );
    stmtCheck.execute();
    auto resultRow = stmtCheck.row();
    std::string resultValue;
    resultRow >> resultValue;
    if( resultValue != value )
        throw std::runtime_error( "PRAGMA " + pragmaName + " value mismatch" );
}

void Connection::setForeignKeyEnabled( bool value )
{
    // Changing this pragma during a transaction is a no-op (silently ignored by
    // sqlite), so ensure we're doing something usefull here:
    assert( sqlite::Transaction::transactionInProgress() == false );
    // Ensure no transaction will be started during the pragma change
    auto ctx = acquireWriteContext();
    setPragma( handle(), "foreign_keys", value ? "1" : "0" );
}

void Connection::setRecursiveTriggersEnabled( bool value )
{
    // Ensure no request will run while we change this setting
    auto ctx = acquireWriteContext();
    setPragma( handle(), "recursive_triggers", value == true ? "1" : "0" );
}

void Connection::registerUpdateHook( const std::string& table, Connection::UpdateHookCb cb )
{
    m_hooks.emplace( table, cb );
}

bool Connection::checkSchemaIntegrity()
{
    auto conn = handle();
    std::string req = std::string{ "PRAGMA integrity_check" };

    sqlite::Statement stmt( conn, req );
    stmt.execute();
    auto row = stmt.row();
    if ( row.load<std::string>( 0 ) == "ok" )
    {
        row = stmt.row();
        assert( row == nullptr );
        return true;
    }
    do
    {
        LOG_ERROR( "Error string from integrity_check: ", row.load<std::string>( 0 ) );
        row = stmt.row();
    }
    while ( row != nullptr );
    return false;
}

bool Connection::checkForeignKeysIntegrity()
{
    auto conn = handle();
    std::string req = std::string{ "PRAGMA foreign_key_check" };

    sqlite::Statement stmt( conn, req );
    stmt.execute();
    auto row = stmt.row();
    if ( row == nullptr )
        return true;
    do
    {
        auto table = row.extract<std::string>();
        auto rowid = row.extract<int64_t>();
        auto targetTable = row.extract<std::string>();
        auto idx = row.extract<int64_t>();
        LOG_ERROR( "Foreign Key error: In table ", table, " rowid: ", rowid,
                   " referring to table ", targetTable, " at index ", idx );
        row = stmt.row();
    }
    while ( row != nullptr );
    return false;
}

const std::string&Connection::dbPath() const
{
    return m_dbPath;
}

std::shared_ptr<Connection> Connection::connect( const std::string& dbPath )
{
    // Use a wrapper to allow make_shared to use the private Connection ctor
    struct SqliteConnectionWrapper : public Connection
    {
        explicit SqliteConnectionWrapper( const std::string& p ) : Connection( p ) {}
    };
    return std::make_shared<SqliteConnectionWrapper>( dbPath );
}

void Connection::updateHook( void* data, int reason, const char*,
                                   const char* table, sqlite_int64 rowId )
{
    const auto self = reinterpret_cast<Connection*>( data );
    auto it = self->m_hooks.find( table );
    if ( it == end( self->m_hooks ) )
        return;
    switch ( reason )
    {
    case SQLITE_INSERT:
        it->second( HookReason::Insert, rowId );
        break;
    case SQLITE_UPDATE:
        it->second( HookReason::Update, rowId );
        break;
    case SQLITE_DELETE:
        it->second( HookReason::Delete, rowId );
        break;
    }
}

void Connection::logCallback( void*, int c, const char *str )
{
    LOG_DEBUG( "Sqlite error; code: ", c, " msg: ", str);
}

Connection::WeakDbContext::WeakDbContext( Connection* conn )
    : m_conn( conn )
{
    m_conn->setForeignKeyEnabled( false );
    m_conn->setRecursiveTriggersEnabled( false );
}

Connection::WeakDbContext::~WeakDbContext()
{
    m_conn->setForeignKeyEnabled( true );
    m_conn->setRecursiveTriggersEnabled( true );
}

Connection::ThreadSpecificConnection::ThreadSpecificConnection(
        std::shared_ptr<Connection> conn )
    : m_weakConnection( conn )
{
}

Connection::ThreadSpecificConnection::~ThreadSpecificConnection()
{
    auto conn =  m_weakConnection.lock();
    if ( conn == nullptr )
        return;
    std::unique_lock<compat::Mutex> lock( conn->m_connMutex );
    auto it = conn->m_conns.find( compat::this_thread::get_id() );
    if ( it != end( conn->m_conns ) )
    {
        // Ensure those cached statements will not be used if another connection
        // with the same pointer gets created
        sqlite::Statement::FlushConnectionStatementCache( it->second.get() );
        // And ensure we won't use the same connection if a thread with the same
        // ID gets used in the future.
        conn->m_conns.erase( it );
    }
}

}

}
<commit_msg>sqlite: Connection: Get rid of the busy timeout<commit_after>/*****************************************************************************
 * Media Library
 *****************************************************************************
 * Copyright (C) 2015-2019 Hugo Beauzée-Luyssen, Videolabs, VideoLAN
 *
 * Authors: Hugo Beauzée-Luyssen <hugo@beauzee.fr>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#if HAVE_CONFIG_H
# include "config.h"
#endif

#include "SqliteConnection.h"

#include "database/SqliteTools.h"

#include <cstring>

#define DEBUG_SQLITE_TRIGGERS 0

namespace medialibrary
{
namespace sqlite
{

Connection::Connection( const std::string& dbPath )
    : m_dbPath( dbPath )
    , m_readLock( m_contextLock )
    , m_writeLock( m_contextLock )
{
    if ( sqlite3_threadsafe() == 0 )
        throw std::runtime_error( "SQLite isn't built with threadsafe mode" );
    if ( sqlite3_config( SQLITE_CONFIG_MULTITHREAD ) == SQLITE_ERROR )
        throw std::runtime_error( "Failed to enable sqlite multithreaded mode" );
    sqlite3_config( SQLITE_CONFIG_LOG, &logCallback, nullptr );
}

Connection::~Connection()
{
    sqlite::Statement::FlushStatementCache();
}

Connection::Handle Connection::handle()
{
    /**
     * We need to have a single sqlite connection per thread, but we also need
     * to be able to destroy those when the SqliteConnection wrapper instance
     * gets destroyed (otherwise we can't re-instantiate the media library during
     * the application runtime. That can't work for unit test and can be quite
     * a limitation for the application using the medialib)
     * Being able to refer to all per-thread connection means we can't use
     * thread local directly, however, we need to know if a thread goes away, to
     * avoid re-using an old connection on a new thread with the same id, as this
     * would result in a "Database is locked error"
     * In order to solve this, we use a single map to store all connection,
     * indexed by std::thread::id (well, compat::thread::id). Additionaly, in
     * order to know when a thread gets terminated, we store a thread_local
     * object to signal back when a thread returns, and to remove the now
     * unusable connection.
     * Also note that we need to flush any potentially cached compiled statement
     * when a thread gets terminated, as it would become unusable as well.
     *
     * \sa sqlite::Connection::ThreadSpecificConnection::~ThreadSpecificConnection
     * \sa sqlite::Statement::StatementsCache
     */
    std::unique_lock<compat::Mutex> lock( m_connMutex );
    auto it = m_conns.find( compat::this_thread::get_id() );
    if ( it == end( m_conns ) )
    {
        sqlite3* dbConnection;
        auto flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_NOMUTEX;
        if ( m_conns.empty() == true )
            flags |= SQLITE_OPEN_CREATE;
        auto res = sqlite3_open_v2( m_dbPath.c_str(), &dbConnection, flags, nullptr );
        ConnPtr dbConn( dbConnection, &sqlite3_close );
        if ( res != SQLITE_OK )
        {
            int err = sqlite3_system_errno(dbConnection);
            LOG_ERROR( "Failed to connect to database. OS error: ", err );
            errors::mapToException( "<connecting to db>", "", res );
        }
        res = sqlite3_extended_result_codes( dbConnection, 1 );
        if ( res != SQLITE_OK )
            errors::mapToException( "<enabling extended errors>", "", res );
        // Don't use public wrapper, they need to be able to call getConn, which
        // would result from a recursive call and a deadlock from here.
        setPragma( dbConnection, "foreign_keys", "1" );
        setPragma( dbConnection, "recursive_triggers", "1" );
#ifdef __ANDROID__
        // https://github.com/mozilla/mentat/issues/505
        // Should solve `Failed to run request <DELETE FROM File WHERE id_file = ?>: disk I/O error(6410)`
        setPragma( dbConnection, "temp_store", "2" );
#endif
#if DEBUG_SQLITE_TRIGGERS
        sqlite3_trace_v2( dbConnection, SQLITE_TRACE_STMT | SQLITE_TRACE_CLOSE,
                          [](unsigned int t, void* , void* p, void* x) {
            if ( t == SQLITE_TRACE_STMT )
            {
                const char* str = static_cast<const char*>( x );
                LOG_VERBOSE( "Executed: ", str );
            }
            else if ( t == SQLITE_TRACE_CLOSE )
            {
                LOG_VERBOSE( "Connection ", p, " was closed" );
            }
            return 0;
        }, nullptr );
#endif
        m_conns.emplace( compat::this_thread::get_id(), std::move( dbConn ) );
        sqlite3_update_hook( dbConnection, &updateHook, this );
        static thread_local ThreadSpecificConnection tsc( shared_from_this() );
        return dbConnection;
    }
    return it->second.get();
}

std::unique_ptr<sqlite::Transaction> Connection::newTransaction()
{
    return std::unique_ptr<sqlite::Transaction>{ new sqlite::Transaction( this ) };
}

Connection::ReadContext Connection::acquireReadContext()
{
    return ReadContext{ m_readLock };
}

Connection::WriteContext Connection::acquireWriteContext()
{
    return WriteContext{ m_writeLock };
}

void Connection::setPragma( Connection::Handle conn, const std::string& pragmaName,
                            const std::string& value )

{
    std::string reqBase = std::string{ "PRAGMA " } + pragmaName;
    std::string reqSet = reqBase + " = " + value;

    sqlite::Statement stmt( conn, reqSet );
    stmt.execute();
    if ( stmt.row() != nullptr )
        throw std::runtime_error( "Failed to enable/disable " + pragmaName );

    sqlite::Statement stmtCheck( conn, reqBase );
    stmtCheck.execute();
    auto resultRow = stmtCheck.row();
    std::string resultValue;
    resultRow >> resultValue;
    if( resultValue != value )
        throw std::runtime_error( "PRAGMA " + pragmaName + " value mismatch" );
}

void Connection::setForeignKeyEnabled( bool value )
{
    // Changing this pragma during a transaction is a no-op (silently ignored by
    // sqlite), so ensure we're doing something usefull here:
    assert( sqlite::Transaction::transactionInProgress() == false );
    // Ensure no transaction will be started during the pragma change
    auto ctx = acquireWriteContext();
    setPragma( handle(), "foreign_keys", value ? "1" : "0" );
}

void Connection::setRecursiveTriggersEnabled( bool value )
{
    // Ensure no request will run while we change this setting
    auto ctx = acquireWriteContext();
    setPragma( handle(), "recursive_triggers", value == true ? "1" : "0" );
}

void Connection::registerUpdateHook( const std::string& table, Connection::UpdateHookCb cb )
{
    m_hooks.emplace( table, cb );
}

bool Connection::checkSchemaIntegrity()
{
    auto conn = handle();
    std::string req = std::string{ "PRAGMA integrity_check" };

    sqlite::Statement stmt( conn, req );
    stmt.execute();
    auto row = stmt.row();
    if ( row.load<std::string>( 0 ) == "ok" )
    {
        row = stmt.row();
        assert( row == nullptr );
        return true;
    }
    do
    {
        LOG_ERROR( "Error string from integrity_check: ", row.load<std::string>( 0 ) );
        row = stmt.row();
    }
    while ( row != nullptr );
    return false;
}

bool Connection::checkForeignKeysIntegrity()
{
    auto conn = handle();
    std::string req = std::string{ "PRAGMA foreign_key_check" };

    sqlite::Statement stmt( conn, req );
    stmt.execute();
    auto row = stmt.row();
    if ( row == nullptr )
        return true;
    do
    {
        auto table = row.extract<std::string>();
        auto rowid = row.extract<int64_t>();
        auto targetTable = row.extract<std::string>();
        auto idx = row.extract<int64_t>();
        LOG_ERROR( "Foreign Key error: In table ", table, " rowid: ", rowid,
                   " referring to table ", targetTable, " at index ", idx );
        row = stmt.row();
    }
    while ( row != nullptr );
    return false;
}

const std::string&Connection::dbPath() const
{
    return m_dbPath;
}

std::shared_ptr<Connection> Connection::connect( const std::string& dbPath )
{
    // Use a wrapper to allow make_shared to use the private Connection ctor
    struct SqliteConnectionWrapper : public Connection
    {
        explicit SqliteConnectionWrapper( const std::string& p ) : Connection( p ) {}
    };
    return std::make_shared<SqliteConnectionWrapper>( dbPath );
}

void Connection::updateHook( void* data, int reason, const char*,
                                   const char* table, sqlite_int64 rowId )
{
    const auto self = reinterpret_cast<Connection*>( data );
    auto it = self->m_hooks.find( table );
    if ( it == end( self->m_hooks ) )
        return;
    switch ( reason )
    {
    case SQLITE_INSERT:
        it->second( HookReason::Insert, rowId );
        break;
    case SQLITE_UPDATE:
        it->second( HookReason::Update, rowId );
        break;
    case SQLITE_DELETE:
        it->second( HookReason::Delete, rowId );
        break;
    }
}

void Connection::logCallback( void*, int c, const char *str )
{
    LOG_DEBUG( "Sqlite error; code: ", c, " msg: ", str);
}

Connection::WeakDbContext::WeakDbContext( Connection* conn )
    : m_conn( conn )
{
    m_conn->setForeignKeyEnabled( false );
    m_conn->setRecursiveTriggersEnabled( false );
}

Connection::WeakDbContext::~WeakDbContext()
{
    m_conn->setForeignKeyEnabled( true );
    m_conn->setRecursiveTriggersEnabled( true );
}

Connection::ThreadSpecificConnection::ThreadSpecificConnection(
        std::shared_ptr<Connection> conn )
    : m_weakConnection( conn )
{
}

Connection::ThreadSpecificConnection::~ThreadSpecificConnection()
{
    auto conn =  m_weakConnection.lock();
    if ( conn == nullptr )
        return;
    std::unique_lock<compat::Mutex> lock( conn->m_connMutex );
    auto it = conn->m_conns.find( compat::this_thread::get_id() );
    if ( it != end( conn->m_conns ) )
    {
        // Ensure those cached statements will not be used if another connection
        // with the same pointer gets created
        sqlite::Statement::FlushConnectionStatementCache( it->second.get() );
        // And ensure we won't use the same connection if a thread with the same
        // ID gets used in the future.
        conn->m_conns.erase( it );
    }
}

}

}
<|endoftext|>


<commit_before>// Copyright (c) 2012 Timur Iskhodzhanov and MIPT students. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/file_utils.h"

#include <string>

#include "base/common.h"

bool ReadFileToString(const std::string &filename, std::string *contents) {
  CHECK(contents != NULL);

  FILE *fp = fopen(filename.c_str(), "r");
  if (fp == NULL)
    return false;

  const int BUFFER_SIZE = 50;
  char buf[BUFFER_SIZE];
  while (!feof(fp) && !ferror(fp)) {
    if (fgets(buf, BUFFER_SIZE, fp)) {
      // fgets necessarily wrote '\0' at the end.
      *contents += buf;
    }
  }

  if (ferror(fp) != 0)
    return false;

  return (fclose(fp) == 0);
}

bool WriteStringAs(const std::string &filename, const std::string &contents) {
  FILE *fp = fopen(filename.c_str(), "w");
  if (fp == NULL)
    return false;

  unsigned int bytes_to_write = contents.length();
  if (fwrite(contents.c_str(), 1, bytes_to_write, fp) != bytes_to_write) {
    fclose(fp);
    return false;
  }

  if (ferror(fp) != 0)
    return false;

  return (fclose(fp) == 0);
}

bool PathExists(const std::string &path) {
  return (access(path.c_str(), F_OK));
}

bool PathAccessPermitted(const std::string &path, int mode) {
  return (access(path.c_str(), mode));
}
<commit_msg>Add a missing #include<commit_after>// Copyright (c) 2012 Timur Iskhodzhanov and MIPT students. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/file_utils.h"

#include <unistd.h>

#include <string>

#include "base/common.h"

bool ReadFileToString(const std::string &filename, std::string *contents) {
  CHECK(contents != NULL);

  FILE *fp = fopen(filename.c_str(), "r");
  if (fp == NULL)
    return false;

  const int BUFFER_SIZE = 50;
  char buf[BUFFER_SIZE];
  while (!feof(fp) && !ferror(fp)) {
    if (fgets(buf, BUFFER_SIZE, fp)) {
      // fgets necessarily wrote '\0' at the end.
      *contents += buf;
    }
  }

  if (ferror(fp) != 0)
    return false;

  return (fclose(fp) == 0);
}

bool WriteStringAs(const std::string &filename, const std::string &contents) {
  FILE *fp = fopen(filename.c_str(), "w");
  if (fp == NULL)
    return false;

  unsigned int bytes_to_write = contents.length();
  if (fwrite(contents.c_str(), 1, bytes_to_write, fp) != bytes_to_write) {
    fclose(fp);
    return false;
  }

  if (ferror(fp) != 0)
    return false;

  return (fclose(fp) == 0);
}

bool PathExists(const std::string &path) {
  return (access(path.c_str(), F_OK));
}

bool PathAccessPermitted(const std::string &path, int mode) {
  return (access(path.c_str(), mode));
}
<|endoftext|>


<commit_before>#include "deviceselectiontablemodel.h"
#include "config/configuration.h"

#include <QStandardItem>
#include <QString>
#include <QtGui>

DeviceSelectionTableModel::DeviceSelectionTableModel(Devices *devices) {
  for (Devices::iterator it = devices->begin(); it != devices->end(); it++)
    tableData.push_back(it->second);
}

int DeviceSelectionTableModel::rowCount(const QModelIndex &parent
                                        __attribute__((unused))) const {
  return tableData.size();
}

int DeviceSelectionTableModel::columnCount(const QModelIndex &parent
                                           __attribute__((unused))) const {
  return 3;
}

QVariant DeviceSelectionTableModel::data(const QModelIndex &index,
                                         int role) const {
  if (role == Qt::DisplayRole) {
    Device *current = tableData.at(index.row());
    switch (index.column()) {
    case 0:
      return QString(current->getDeviceName().c_str());
    case 1:
      return QString(current->getModelName().c_str());
    case 2:
      return current->getSerialNumberString() != ""
                 ? QString(current->getSerialNumberString().c_str())
                 : QString::number(current->getSerialNumber()->getLongValue());
    default:
      return QVariant::Invalid;
      break;
    }
  }
  if ((role == Qt::CheckStateRole) && (index.column() == 0)) {
    Device *current = tableData.at(index.row());
    return current->getDefault() ? Qt::Checked : Qt::Unchecked;
  }
  return QVariant::Invalid;
}

bool DeviceSelectionTableModel::setData(const QModelIndex &index,
                                        const QVariant &value, int role) {
  bool success = true;

  if (index.isValid()) {
    Device *current = tableData.at(index.row());
    if (role == Qt::CheckStateRole) {
      if (index.column() == 0) {
        if (current->getDefault()) {
          success = false;
        } else {
          current->setDefault(value.toBool());
          Configuration::getInstance().setDefaultDevice(
              current->getSerialNumber()->getLongValue());
          for (int i = 0; i < tableData.size(); i++) {
            if (i != index.row()) {
              if (tableData.at(i)->getDefault()) {
                tableData.at(i)->setDefault(false);
                QModelIndex upd = createIndex(i, 0);
                emit dataChanged(upd, upd);
              }
            }
          }
          QModelIndex topLeft = index;
          QModelIndex bottomRight = index;
          emit dataChanged(topLeft, bottomRight);
          success = true;
        }
      } else
        success = false;
    }
  }
  return success;
}

QVariant DeviceSelectionTableModel::headerData(int section,
                                               Qt::Orientation orientation,
                                               int role) const {
  if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
    switch (section) {
    case 0:
      return QString("Default");
      break;
    case 1:
      return QString("Device Name");
    case 2:
      return QString("Device Model");
    case 3:
      return QString("Serial Number");
    default:
      return QVariant::Invalid;
    }
  }
  return QVariant::Invalid;
}

Qt::ItemFlags DeviceSelectionTableModel::flags(const QModelIndex &index) const {
  if (index.column() == 0) {
    return QAbstractTableModel::flags(index) | Qt::ItemIsUserCheckable |
           Qt::ItemIsEditable;
  }
  return QAbstractTableModel::flags(index);
}
<commit_msg>++it instead of it++<commit_after>#include "deviceselectiontablemodel.h"
#include "config/configuration.h"

#include <QStandardItem>
#include <QString>
#include <QtGui>

DeviceSelectionTableModel::DeviceSelectionTableModel(Devices *devices) {
	for (Devices::iterator it = devices->begin(); it != devices->end(); ++it)
    tableData.push_back(it->second);
}

int DeviceSelectionTableModel::rowCount(const QModelIndex &parent
                                        __attribute__((unused))) const {
  return tableData.size();
}

int DeviceSelectionTableModel::columnCount(const QModelIndex &parent
                                           __attribute__((unused))) const {
  return 3;
}

QVariant DeviceSelectionTableModel::data(const QModelIndex &index,
                                         int role) const {
  if (role == Qt::DisplayRole) {
    Device *current = tableData.at(index.row());
    switch (index.column()) {
    case 0:
      return QString(current->getDeviceName().c_str());
    case 1:
      return QString(current->getModelName().c_str());
    case 2:
      return current->getSerialNumberString() != ""
                 ? QString(current->getSerialNumberString().c_str())
                 : QString::number(current->getSerialNumber()->getLongValue());
    default:
      return QVariant::Invalid;
      break;
    }
  }
  if ((role == Qt::CheckStateRole) && (index.column() == 0)) {
    Device *current = tableData.at(index.row());
    return current->getDefault() ? Qt::Checked : Qt::Unchecked;
  }
  return QVariant::Invalid;
}

bool DeviceSelectionTableModel::setData(const QModelIndex &index,
                                        const QVariant &value, int role) {
  bool success = true;

  if (index.isValid()) {
    Device *current = tableData.at(index.row());
    if (role == Qt::CheckStateRole) {
      if (index.column() == 0) {
        if (current->getDefault()) {
          success = false;
        } else {
          current->setDefault(value.toBool());
          Configuration::getInstance().setDefaultDevice(
              current->getSerialNumber()->getLongValue());
          for (int i = 0; i < tableData.size(); i++) {
            if (i != index.row()) {
              if (tableData.at(i)->getDefault()) {
                tableData.at(i)->setDefault(false);
                QModelIndex upd = createIndex(i, 0);
                emit dataChanged(upd, upd);
              }
            }
          }
          QModelIndex topLeft = index;
          QModelIndex bottomRight = index;
          emit dataChanged(topLeft, bottomRight);
          success = true;
        }
      } else
        success = false;
    }
  }
  return success;
}

QVariant DeviceSelectionTableModel::headerData(int section,
                                               Qt::Orientation orientation,
                                               int role) const {
  if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
    switch (section) {
    case 0:
      return QString("Default");
      break;
    case 1:
      return QString("Device Name");
    case 2:
      return QString("Device Model");
    case 3:
      return QString("Serial Number");
    default:
      return QVariant::Invalid;
    }
  }
  return QVariant::Invalid;
}

Qt::ItemFlags DeviceSelectionTableModel::flags(const QModelIndex &index) const {
  if (index.column() == 0) {
    return QAbstractTableModel::flags(index) | Qt::ItemIsUserCheckable |
           Qt::ItemIsEditable;
  }
  return QAbstractTableModel::flags(index);
}
<|endoftext|>


<commit_before>/*
 *  Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "modules/desktop_capture/cropping_window_capturer.h"

#include "modules/desktop_capture/win/screen_capture_utils.h"
#include "modules/desktop_capture/win/window_capture_utils.h"
#include "rtc_base/logging.h"
#include "rtc_base/trace_event.h"
#include "rtc_base/win32.h"

namespace webrtc {

namespace {

// Used to pass input/output data during the EnumWindow call for verifying if
// the selected window is on top.
struct TopWindowVerifierContext {
  TopWindowVerifierContext(HWND selected_window,
                           HWND excluded_window,
                           DesktopRect selected_window_rect,
                           WindowCaptureHelperWin* window_capture_helper)
      : selected_window(selected_window),
        excluded_window(excluded_window),
        selected_window_rect(selected_window_rect),
        window_capture_helper(window_capture_helper),
        is_top_window(false) {
    RTC_DCHECK_NE(selected_window, excluded_window);
  }

  const HWND selected_window;
  const HWND excluded_window;
  const DesktopRect selected_window_rect;
  WindowCaptureHelperWin* window_capture_helper;
  bool is_top_window;
};

// The function is called during EnumWindow for every window enumerated and is
// responsible for verifying if the selected window is on top.
// Return TRUE to continue enumerating if the current window belongs to the
// selected window or is to be ignored.
// Return FALSE to stop enumerating if the selected window is found or decided
// if it's on top most.
BOOL CALLBACK TopWindowVerifier(HWND hwnd, LPARAM param) {
  TopWindowVerifierContext* context =
      reinterpret_cast<TopWindowVerifierContext*>(param);

  if (hwnd == context->selected_window) {
    context->is_top_window = true;
    return FALSE;
  }

  // Ignore the excluded window.
  if (hwnd == context->excluded_window) {
    return TRUE;
  }

  // Ignore invisible window on current desktop.
  if (!context->window_capture_helper->IsWindowVisibleOnCurrentDesktop(hwnd)) {
    return TRUE;
  }

  // Ignore Chrome notification windows, especially the notification for the
  // ongoing window sharing.
  // Notes:
  // - This only works with notifications from Chrome, not other Apps.
  // - All notifications from Chrome will be ignored.
  // - This may cause part or whole of notification window being cropped into
  // the capturing of the target window if there is overlapping.
  if (context->window_capture_helper->IsWindowChromeNotification(hwnd)) {
    return TRUE;
  }

  // Ignore descendant windows since we want to capture them.
  // This check does not work for tooltips and context menus. Drop down menus
  // and popup windows are fine.
  //
  // GA_ROOT returns the root window instead of the owner. I.e. for a dialog
  // window, GA_ROOT returns the dialog window itself. GA_ROOTOWNER returns the
  // application main window which opens the dialog window. Since we are sharing
  // the application main window, GA_ROOT should be used here.
  if (GetAncestor(hwnd, GA_ROOT) == context->selected_window) {
    return TRUE;
  }

  // If |hwnd| has no title and belongs to the same process, assume it's a
  // tooltip or context menu from the selected window and ignore it.
  // TODO(zijiehe): This check cannot cover the case where tooltip or context
  // menu of the child-window is covering the main window. See
  // https://bugs.chromium.org/p/webrtc/issues/detail?id=8062 for details.
  const size_t kTitleLength = 32;
  WCHAR window_title[kTitleLength];
  GetWindowText(hwnd, window_title, kTitleLength);
  if (wcsnlen_s(window_title, kTitleLength) == 0) {
    DWORD enumerated_window_process_id;
    DWORD selected_window_process_id;
    GetWindowThreadProcessId(hwnd, &enumerated_window_process_id);
    GetWindowThreadProcessId(context->selected_window,
                             &selected_window_process_id);
    if (selected_window_process_id == enumerated_window_process_id) {
      return TRUE;
    }
  }

  // Checks whether current window |hwnd| intersects with
  // |context|->selected_window.
  // |content_rect| is preferred because,
  // 1. WindowCapturerWin is using GDI capturer, which cannot capture DX output.
  //    So ScreenCapturer should be used as much as possible to avoid
  //    uncapturable cases. Note: lots of new applications are using DX output
  //    (hardware acceleration) to improve the performance which cannot be
  //    captured by WindowCapturerWin. See bug http://crbug.com/741770.
  // 2. WindowCapturerWin is still useful because we do not want to expose the
  //    content on other windows if the target window is covered by them.
  // 3. Shadow and borders should not be considered as "content" on other
  //    windows because they do not expose any useful information.
  //
  // So we can bear the false-negative cases (target window is covered by the
  // borders or shadow of other windows, but we have not detected it) in favor
  // of using ScreenCapturer, rather than let the false-positive cases (target
  // windows is only covered by borders or shadow of other windows, but we treat
  // it as overlapping) impact the user experience.
  DesktopRect content_rect;
  if (!GetWindowContentRect(hwnd, &content_rect)) {
    // Bail out if failed to get the window area.
    context->is_top_window = false;
    return FALSE;
  }

  content_rect.IntersectWith(context->selected_window_rect);

  // If intersection is not empty, the selected window is not on top.
  if (!content_rect.is_empty()) {
    context->is_top_window = false;
    return FALSE;
  }
  // Otherwise, keep enumerating.
  return TRUE;
}

class CroppingWindowCapturerWin : public CroppingWindowCapturer {
 public:
  CroppingWindowCapturerWin(const DesktopCaptureOptions& options)
      : CroppingWindowCapturer(options) {}

 private:
  bool ShouldUseScreenCapturer() override;
  DesktopRect GetWindowRectInVirtualScreen() override;

  // The region from GetWindowRgn in the desktop coordinate if the region is
  // rectangular, or the rect from GetWindowRect if the region is not set.
  DesktopRect window_region_rect_;

  WindowCaptureHelperWin window_capture_helper_;
};

bool CroppingWindowCapturerWin::ShouldUseScreenCapturer() {
  if (!rtc::IsWindows8OrLater() && window_capture_helper_.IsAeroEnabled()) {
    return false;
  }

  const HWND selected = reinterpret_cast<HWND>(selected_window());
  // Check if the window is visible on current desktop.
  if (!window_capture_helper_.IsWindowVisibleOnCurrentDesktop(selected)) {
    return false;
  }

  // Check if the window is a translucent layered window.
  const LONG window_ex_style = GetWindowLong(selected, GWL_EXSTYLE);
  if (window_ex_style & WS_EX_LAYERED) {
    COLORREF color_ref_key = 0;
    BYTE alpha = 0;
    DWORD flags = 0;

    // GetLayeredWindowAttributes fails if the window was setup with
    // UpdateLayeredWindow. We have no way to know the opacity of the window in
    // that case. This happens for Stiky Note (crbug/412726).
    if (!GetLayeredWindowAttributes(selected, &color_ref_key, &alpha, &flags))
      return false;

    // UpdateLayeredWindow is the only way to set per-pixel alpha and will cause
    // the previous GetLayeredWindowAttributes to fail. So we only need to check
    // the window wide color key or alpha.
    if ((flags & LWA_COLORKEY) || ((flags & LWA_ALPHA) && (alpha < 255))) {
      return false;
    }
  }

  if (!GetWindowRect(selected, &window_region_rect_)) {
    return false;
  }

  DesktopRect content_rect;
  if (!GetWindowContentRect(selected, &content_rect)) {
    return false;
  }

  DesktopRect region_rect;
  // Get the window region and check if it is rectangular.
  const int region_type =
      GetWindowRegionTypeWithBoundary(selected, &region_rect);

  // Do not use the screen capturer if the region is empty or not rectangular.
  if (region_type == COMPLEXREGION || region_type == NULLREGION) {
    return false;
  }

  if (region_type == SIMPLEREGION) {
    // The |region_rect| returned from GetRgnBox() is always in window
    // coordinate.
    region_rect.Translate(window_region_rect_.left(),
                          window_region_rect_.top());
    // MSDN: The window region determines the area *within* the window where the
    // system permits drawing.
    // https://msdn.microsoft.com/en-us/library/windows/desktop/dd144950(v=vs.85).aspx.
    //
    // |region_rect| should always be inside of |window_region_rect_|. So after
    // the intersection, |window_region_rect_| == |region_rect|. If so, what's
    // the point of the intersecting operations? Why cannot we directly retrieve
    // |window_region_rect_| from GetWindowRegionTypeWithBoundary() function?
    // TODO(zijiehe): Figure out the purpose of these intersections.
    window_region_rect_.IntersectWith(region_rect);
    content_rect.IntersectWith(region_rect);
  }

  // Check if the client area is out of the screen area. When the window is
  // maximized, only its client area is visible in the screen, the border will
  // be hidden. So we are using |content_rect| here.
  if (!GetFullscreenRect().ContainsRect(content_rect)) {
    return false;
  }

  // Check if the window is occluded by any other window, excluding the child
  // windows, context menus, and |excluded_window_|.
  // |content_rect| is preferred, see the comments in TopWindowVerifier()
  // function.
  TopWindowVerifierContext context(selected,
                                   reinterpret_cast<HWND>(excluded_window()),
                                   content_rect, &window_capture_helper_);
  const LPARAM enum_param = reinterpret_cast<LPARAM>(&context);
  EnumWindows(&TopWindowVerifier, enum_param);
  if (!context.is_top_window) {
    return false;
  }

  // If |selected| is not covered by other windows, check whether it is
  // covered by its own child windows. Note: EnumChildWindows() enumerates child
  // windows in all generations, but does not include any controls like buttons
  // or textboxes.
  EnumChildWindows(selected, &TopWindowVerifier, enum_param);
  return context.is_top_window;
}

DesktopRect CroppingWindowCapturerWin::GetWindowRectInVirtualScreen() {
  TRACE_EVENT0("webrtc",
               "CroppingWindowCapturerWin::GetWindowRectInVirtualScreen");
  DesktopRect window_rect;
  HWND hwnd = reinterpret_cast<HWND>(selected_window());
  if (!GetCroppedWindowRect(hwnd, &window_rect, /* original_rect */ nullptr)) {
    RTC_LOG(LS_WARNING) << "Failed to get window info: " << GetLastError();
    return window_rect;
  }
  window_rect.IntersectWith(window_region_rect_);

  // Convert |window_rect| to be relative to the top-left of the virtual screen.
  DesktopRect screen_rect(GetFullscreenRect());
  window_rect.IntersectWith(screen_rect);
  window_rect.Translate(-screen_rect.left(), -screen_rect.top());
  return window_rect;
}

}  // namespace

// static
std::unique_ptr<DesktopCapturer> CroppingWindowCapturer::CreateCapturer(
    const DesktopCaptureOptions& options) {
  return std::unique_ptr<DesktopCapturer>(
      new CroppingWindowCapturerWin(options));
}

}  // namespace webrtc
<commit_msg>[Window capture] filter out sibling windows with same title.<commit_after>/*
 *  Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "modules/desktop_capture/cropping_window_capturer.h"

#include "modules/desktop_capture/win/screen_capture_utils.h"
#include "modules/desktop_capture/win/window_capture_utils.h"
#include "rtc_base/logging.h"
#include "rtc_base/trace_event.h"
#include "rtc_base/win32.h"

namespace webrtc {

namespace {

const size_t kTitleLength = 256;

// Used to pass input/output data during the EnumWindow call for verifying if
// the selected window is on top.
struct TopWindowVerifierContext {
  TopWindowVerifierContext(HWND selected_window,
                           HWND excluded_window,
                           DesktopRect selected_window_rect,
                           WindowCaptureHelperWin* window_capture_helper)
      : selected_window(selected_window),
        excluded_window(excluded_window),
        selected_window_rect(selected_window_rect),
        window_capture_helper(window_capture_helper),
        is_top_window(false) {
    RTC_DCHECK_NE(selected_window, excluded_window);

    GetWindowText(selected_window, selected_window_title, kTitleLength);
    GetWindowThreadProcessId(selected_window, &selected_window_process_id);
  }

  const HWND selected_window;
  const HWND excluded_window;
  const DesktopRect selected_window_rect;
  WindowCaptureHelperWin* window_capture_helper;
  WCHAR selected_window_title[kTitleLength];
  DWORD selected_window_process_id;
  bool is_top_window;
};

// The function is called during EnumWindow for every window enumerated and is
// responsible for verifying if the selected window is on top.
// Return TRUE to continue enumerating if the current window belongs to the
// selected window or is to be ignored.
// Return FALSE to stop enumerating if the selected window is found or decided
// if it's on top most.
BOOL CALLBACK TopWindowVerifier(HWND hwnd, LPARAM param) {
  TopWindowVerifierContext* context =
      reinterpret_cast<TopWindowVerifierContext*>(param);

  if (hwnd == context->selected_window) {
    context->is_top_window = true;
    return FALSE;
  }

  // Ignore the excluded window.
  if (hwnd == context->excluded_window) {
    return TRUE;
  }

  // Ignore invisible window on current desktop.
  if (!context->window_capture_helper->IsWindowVisibleOnCurrentDesktop(hwnd)) {
    return TRUE;
  }

  // Ignore Chrome notification windows, especially the notification for the
  // ongoing window sharing.
  // Notes:
  // - This only works with notifications from Chrome, not other Apps.
  // - All notifications from Chrome will be ignored.
  // - This may cause part or whole of notification window being cropped into
  // the capturing of the target window if there is overlapping.
  if (context->window_capture_helper->IsWindowChromeNotification(hwnd)) {
    return TRUE;
  }

  // Ignore descendant windows since we want to capture them.
  // This check does not work for tooltips and context menus. Drop down menus
  // and popup windows are fine.
  //
  // GA_ROOT returns the root window instead of the owner. I.e. for a dialog
  // window, GA_ROOT returns the dialog window itself. GA_ROOTOWNER returns the
  // application main window which opens the dialog window. Since we are sharing
  // the application main window, GA_ROOT should be used here.
  if (GetAncestor(hwnd, GA_ROOT) == context->selected_window) {
    return TRUE;
  }

  // If |hwnd| has no title or has same title as the selected window (i.e.
  // Window Media Player consisting of several sibling windows) and belongs to
  // the same process, assume it's a tooltip or context menu or sibling window
  // from the selected window and ignore it.
  // TODO(zijiehe): This check cannot cover the case where tooltip or context
  // menu of the child-window is covering the main window. See
  // https://bugs.chromium.org/p/webrtc/issues/detail?id=8062 for details.
  WCHAR window_title[kTitleLength];
  GetWindowText(hwnd, window_title, kTitleLength);
  if (wcsnlen_s(window_title, kTitleLength) == 0 ||
      wcscmp(window_title, context->selected_window_title) == 0) {
    DWORD enumerated_window_process_id;
    GetWindowThreadProcessId(hwnd, &enumerated_window_process_id);
    if (context->selected_window_process_id == enumerated_window_process_id) {
      return TRUE;
    }
  }

  // Checks whether current window |hwnd| intersects with
  // |context|->selected_window.
  // |content_rect| is preferred because,
  // 1. WindowCapturerWin is using GDI capturer, which cannot capture DX output.
  //    So ScreenCapturer should be used as much as possible to avoid
  //    uncapturable cases. Note: lots of new applications are using DX output
  //    (hardware acceleration) to improve the performance which cannot be
  //    captured by WindowCapturerWin. See bug http://crbug.com/741770.
  // 2. WindowCapturerWin is still useful because we do not want to expose the
  //    content on other windows if the target window is covered by them.
  // 3. Shadow and borders should not be considered as "content" on other
  //    windows because they do not expose any useful information.
  //
  // So we can bear the false-negative cases (target window is covered by the
  // borders or shadow of other windows, but we have not detected it) in favor
  // of using ScreenCapturer, rather than let the false-positive cases (target
  // windows is only covered by borders or shadow of other windows, but we treat
  // it as overlapping) impact the user experience.
  DesktopRect content_rect;
  if (!GetWindowContentRect(hwnd, &content_rect)) {
    // Bail out if failed to get the window area.
    context->is_top_window = false;
    return FALSE;
  }

  content_rect.IntersectWith(context->selected_window_rect);

  // If intersection is not empty, the selected window is not on top.
  if (!content_rect.is_empty()) {
    context->is_top_window = false;
    return FALSE;
  }
  // Otherwise, keep enumerating.
  return TRUE;
}

class CroppingWindowCapturerWin : public CroppingWindowCapturer {
 public:
  CroppingWindowCapturerWin(const DesktopCaptureOptions& options)
      : CroppingWindowCapturer(options) {}

 private:
  bool ShouldUseScreenCapturer() override;
  DesktopRect GetWindowRectInVirtualScreen() override;

  // The region from GetWindowRgn in the desktop coordinate if the region is
  // rectangular, or the rect from GetWindowRect if the region is not set.
  DesktopRect window_region_rect_;

  WindowCaptureHelperWin window_capture_helper_;
};

bool CroppingWindowCapturerWin::ShouldUseScreenCapturer() {
  if (!rtc::IsWindows8OrLater() && window_capture_helper_.IsAeroEnabled()) {
    return false;
  }

  const HWND selected = reinterpret_cast<HWND>(selected_window());
  // Check if the window is visible on current desktop.
  if (!window_capture_helper_.IsWindowVisibleOnCurrentDesktop(selected)) {
    return false;
  }

  // Check if the window is a translucent layered window.
  const LONG window_ex_style = GetWindowLong(selected, GWL_EXSTYLE);
  if (window_ex_style & WS_EX_LAYERED) {
    COLORREF color_ref_key = 0;
    BYTE alpha = 0;
    DWORD flags = 0;

    // GetLayeredWindowAttributes fails if the window was setup with
    // UpdateLayeredWindow. We have no way to know the opacity of the window in
    // that case. This happens for Stiky Note (crbug/412726).
    if (!GetLayeredWindowAttributes(selected, &color_ref_key, &alpha, &flags))
      return false;

    // UpdateLayeredWindow is the only way to set per-pixel alpha and will cause
    // the previous GetLayeredWindowAttributes to fail. So we only need to check
    // the window wide color key or alpha.
    if ((flags & LWA_COLORKEY) || ((flags & LWA_ALPHA) && (alpha < 255))) {
      return false;
    }
  }

  if (!GetWindowRect(selected, &window_region_rect_)) {
    return false;
  }

  DesktopRect content_rect;
  if (!GetWindowContentRect(selected, &content_rect)) {
    return false;
  }

  DesktopRect region_rect;
  // Get the window region and check if it is rectangular.
  const int region_type =
      GetWindowRegionTypeWithBoundary(selected, &region_rect);

  // Do not use the screen capturer if the region is empty or not rectangular.
  if (region_type == COMPLEXREGION || region_type == NULLREGION) {
    return false;
  }

  if (region_type == SIMPLEREGION) {
    // The |region_rect| returned from GetRgnBox() is always in window
    // coordinate.
    region_rect.Translate(window_region_rect_.left(),
                          window_region_rect_.top());
    // MSDN: The window region determines the area *within* the window where the
    // system permits drawing.
    // https://msdn.microsoft.com/en-us/library/windows/desktop/dd144950(v=vs.85).aspx.
    //
    // |region_rect| should always be inside of |window_region_rect_|. So after
    // the intersection, |window_region_rect_| == |region_rect|. If so, what's
    // the point of the intersecting operations? Why cannot we directly retrieve
    // |window_region_rect_| from GetWindowRegionTypeWithBoundary() function?
    // TODO(zijiehe): Figure out the purpose of these intersections.
    window_region_rect_.IntersectWith(region_rect);
    content_rect.IntersectWith(region_rect);
  }

  // Check if the client area is out of the screen area. When the window is
  // maximized, only its client area is visible in the screen, the border will
  // be hidden. So we are using |content_rect| here.
  if (!GetFullscreenRect().ContainsRect(content_rect)) {
    return false;
  }

  // Check if the window is occluded by any other window, excluding the child
  // windows, context menus, and |excluded_window_|.
  // |content_rect| is preferred, see the comments in TopWindowVerifier()
  // function.
  TopWindowVerifierContext context(selected,
                                   reinterpret_cast<HWND>(excluded_window()),
                                   content_rect, &window_capture_helper_);
  const LPARAM enum_param = reinterpret_cast<LPARAM>(&context);
  EnumWindows(&TopWindowVerifier, enum_param);
  if (!context.is_top_window) {
    return false;
  }

  // If |selected| is not covered by other windows, check whether it is
  // covered by its own child windows. Note: EnumChildWindows() enumerates child
  // windows in all generations, but does not include any controls like buttons
  // or textboxes.
  EnumChildWindows(selected, &TopWindowVerifier, enum_param);
  return context.is_top_window;
}

DesktopRect CroppingWindowCapturerWin::GetWindowRectInVirtualScreen() {
  TRACE_EVENT0("webrtc",
               "CroppingWindowCapturerWin::GetWindowRectInVirtualScreen");
  DesktopRect window_rect;
  HWND hwnd = reinterpret_cast<HWND>(selected_window());
  if (!GetCroppedWindowRect(hwnd, &window_rect, /* original_rect */ nullptr)) {
    RTC_LOG(LS_WARNING) << "Failed to get window info: " << GetLastError();
    return window_rect;
  }
  window_rect.IntersectWith(window_region_rect_);

  // Convert |window_rect| to be relative to the top-left of the virtual screen.
  DesktopRect screen_rect(GetFullscreenRect());
  window_rect.IntersectWith(screen_rect);
  window_rect.Translate(-screen_rect.left(), -screen_rect.top());
  return window_rect;
}

}  // namespace

// static
std::unique_ptr<DesktopCapturer> CroppingWindowCapturer::CreateCapturer(
    const DesktopCaptureOptions& options) {
  return std::unique_ptr<DesktopCapturer>(
      new CroppingWindowCapturerWin(options));
}

}  // namespace webrtc
<|endoftext|>