<commit_before>/**
 * This file is part of the Marble Desktop Globe.
 *
 * Copyright 2005-2007 Torsten Rahn <tackat@kde.org>"
 * Copyright 2007      Inge Wallin  <ingwa@kde.org>"
 * Copyright 2008,2009 Jens-Michael Hoffmann <jensmh@gmx.de>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "TileLoader.h"

#include "global.h"
#include "GeoSceneLayer.h"
#include "GeoSceneTexture.h"
#include "HttpDownloadManager.h"
#include "DatasetProvider.h"
#include "TextureTile.h"
#include "MarbleDirs.h"
#include "MarbleModel.h"
#include "TileLoaderHelper.h"

#include <QtCore/QCache>
#include <QtCore/QDebug>
#include <QtCore/QHash>
#include <QtGui/QImage>

#ifdef Q_CC_MSVC
# ifndef KDEWIN_MATH_H
   long double log(int i) { return log((long double)i); }
# endif
#endif

namespace Marble
{

class TileLoaderPrivate
{
    public:
        TileLoaderPrivate()
            : m_datasetProvider( 0 ),
              m_downloadManager( 0 ),
              m_layer( 0 ),
              m_tileWidth( 0 ),
              m_tileHeight( 0 )
        {
            m_tileCache.setMaxCost( 20000 * 1024 ); // Cache size measured in bytes
        }

        DatasetProvider *m_datasetProvider;
        HttpDownloadManager *m_downloadManager;
        GeoSceneLayer *m_layer;
        QHash <TileId, TextureTile*>  m_tileHash;
        int           m_tileWidth;
        int           m_tileHeight;
        QCache <TileId, TextureTile>  m_tileCache;
};

TileLoader::TileLoader( HttpDownloadManager *downloadManager, MarbleModel* parent)
    : d( new TileLoaderPrivate() ),
      m_parent(parent)
{
    setDownloadManager( downloadManager );
}

TileLoader::~TileLoader()
{
    flush();
    d->m_tileCache.clear();
    if ( d->m_downloadManager != 0 )
        d->m_downloadManager->disconnect( this );

    delete d;
}

void TileLoader::setDownloadManager( HttpDownloadManager *downloadManager )
{
    if ( d->m_downloadManager != 0 ) {
        d->m_downloadManager->disconnect( this );
        d->m_downloadManager = 0;
    }

    d->m_downloadManager = downloadManager;
    if ( d->m_downloadManager != 0 ) {
        connect( d->m_downloadManager, SIGNAL( downloadComplete( QString, QString ) ),
                 this,              SLOT( reloadTile( QString, QString ) ) );
    }
}

void TileLoader::setLayer( GeoSceneLayer * layer )
{
    // Initialize map theme.
    flush();
    d->m_tileCache.clear();

    if ( !layer ) {
        qDebug() << "No layer specified! (GeoSceneLayer * layer = 0)";
        return;
    }

    d->m_layer = layer;

    TileId id;
    TextureTile tile( id );

    GeoSceneTexture * texture = static_cast<GeoSceneTexture *>( d->m_layer->groundDataset() );

    tile.loadDataset( texture, 0, 0, 0 );

    // We assume that all tiles have the same size. TODO: check to be safe
    d->m_tileWidth  = tile.rawtile().width();
    d->m_tileHeight = tile.rawtile().height();
}

void TileLoader::resetTilehash()
{
    QHash<TileId, TextureTile*>::const_iterator it = d->m_tileHash.constBegin();
    while ( it != d->m_tileHash.constEnd() ) {
        it.value()->setUsed( false );
        ++it;
    }
}

void TileLoader::cleanupTilehash()
{
    // Make sure that tiles which haven't been used during the last
    // rendering of the map at all get removed from the tile hash.

    QHashIterator<TileId, TextureTile*> it( d->m_tileHash );
    while ( it.hasNext() ) {
        it.next();
        if ( !it.value()->used() ) {
            // If insert call result is false then the cache is too small to store the tile
            // but the item will get deleted nevertheless and the pointer we have
            // doesn't get set to zero (so don't delete it in this case or it will crash!)
            d->m_tileCache.insert( it.key(), it.value(), it.value()->numBytes() );
            d->m_tileHash.remove( it.key() );
        }
    }
}

void TileLoader::flush()
{
    // Remove all tiles from m_tileHash
    QHashIterator<TileId, TextureTile*> it( d->m_tileHash );
    while ( it.hasNext() ) {
        it.next();
        // If insert call result is false then the cache is too small to store the tile
        // but the item will get deleted nevertheless and the pointer we have
        // doesn't get set to zero (so don't delete it in this case or it will crash!)
        d->m_tileCache.insert( it.key(), it.value(), it.value()->numBytes() );
        d->m_tileHash.remove( it.key() );
    }

    d->m_tileHash.clear();
}

int TileLoader::tileWidth() const
{
    return d->m_tileWidth;
}

int TileLoader::tileHeight() const
{
    return d->m_tileHeight;
}

int TileLoader::globalWidth( int level ) const
{
    if ( !d->m_layer ) return 0;

    GeoSceneTexture * texture = static_cast<GeoSceneTexture *>( d->m_layer->groundDataset() );

    return d->m_tileWidth * TileLoaderHelper::levelToColumn(
                                texture->levelZeroColumns(), level );
}

int TileLoader::globalHeight( int level ) const
{
    if ( !d->m_layer ) return 0;

    GeoSceneTexture * texture = static_cast<GeoSceneTexture *>( d->m_layer->groundDataset() );

    return d->m_tileHeight * TileLoaderHelper::levelToRow(
                                texture->levelZeroRows(), level );
}

TextureTile* TileLoader::loadTile( int tilx, int tily, int tileLevel )
{
    if ( !d->m_layer ) return 0;

    TileId tileId( tileLevel, tilx, tily );

    // check if the tile is in the hash
    TextureTile * tile = d->m_tileHash.value( tileId, 0 );
    if ( tile ) {
        tile->setUsed( true );
        return tile;
    }
    // here ends the performance critical section of this method

    // the tile was not in the hash or has been removed because of expiration
    // so check if it is in the cache
    tile = d->m_tileCache.take( tileId );

    GeoSceneTexture * texture = static_cast<GeoSceneTexture *>( d->m_layer->groundDataset() );

    if ( tile ) {
        // the tile was in the cache, but is it up to date?
        const QDateTime now = QDateTime::currentDateTime();

        if ( tile->created().secsTo( now ) < texture->expire()) {
            d->m_tileHash[tileId] = tile;
            tile->setUsed( true );
            return tile;
        } else {
            delete tile;
            tile = 0;
        }
    }

    // tile (valid) has not been found in hash or cache, so load it from disk
    // and place it in the hash from where it will get transfered to the cache

    // qDebug() << "load Tile from Disk: " << tileId.toString();
    tile = new TextureTile( tileId );
    d->m_tileHash[tileId] = tile;

    // FIXME: Implement asynchronous tile loading
    // d->m_datasetProvider->loadDatasets( tile );

    if ( d->m_downloadManager != 0 ) {
        connect( tile, SIGNAL( downloadTile( QUrl, QString, QString ) ),
                 d->m_downloadManager, SLOT( addJob( QUrl, QString, QString ) ) );
    }
    connect( tile, SIGNAL( tileUpdateDone() ),
             this, SIGNAL( tileUpdateAvailable() ) );

    tile->loadDataset( texture, tileLevel, tilx, tily, &( d->m_tileCache ) );
    tile->initJumpTables( false );

    // TODO should emit signal rather than directly calling paintTile
    // emit paintTile( tile, tilx, tily, tileLevel, d->m_theme, false );
    m_parent->paintTile( tile, tilx, tily, tileLevel, texture, false );

    return tile;
}

GeoSceneLayer * TileLoader::layer() const
{
    return d->m_layer;
}

quint64 TileLoader::volatileCacheLimit() const
{
    return d->m_tileCache.maxCost() / 1024;
}

QList<TileId> TileLoader::tilesOnDisplay() const
{
    QList<TileId> result;
    QHash<TileId, TextureTile*>::const_iterator pos = d->m_tileHash.constBegin();
    QHash<TileId, TextureTile*>::const_iterator const end = d->m_tileHash.constEnd();
    for (; pos != end; ++pos ) {
        if ( pos.value()->used() ) {
            result.append( pos.key() );
        }
    }
    return result;
}

int TileLoader::maxPartialTileLevel( GeoSceneLayer * layer )
{
    int maxtilelevel = -1;

    if ( !layer ) return maxtilelevel;

    GeoSceneTexture * texture = static_cast<GeoSceneTexture *>( layer->groundDataset() );

    if ( !texture ) return maxtilelevel;

    QString tilepath = MarbleDirs::path( TileLoaderHelper::themeStr( texture ) );
//    qDebug() << "TileLoader::maxPartialTileLevel tilepath" << tilepath;
    QStringList leveldirs = ( QDir( tilepath ) ).entryList( QDir::AllDirs | QDir::NoSymLinks | QDir::NoDotAndDotDot );

    bool ok = true;

    QStringList::const_iterator constIterator = leveldirs.constBegin();
    for (; constIterator != leveldirs.constEnd(); ++constIterator )
    {
        int value = (*constIterator).toInt( &ok, 10 );
        if ( ok && value > maxtilelevel )
            maxtilelevel = value;
    }

//    qDebug() << "Detected maximum tile level that contains data: "
//             << maxtilelevel;

    return maxtilelevel;
}

bool TileLoader::baseTilesAvailable( GeoSceneLayer * layer )
{
    if ( !layer ) return false;

    GeoSceneTexture * texture = static_cast<GeoSceneTexture *>( layer->groundDataset() );

    const int  levelZeroColumns = texture->levelZeroColumns();
    const int  levelZeroRows    = texture->levelZeroRows();

    bool noerr = true;

    // Check whether the tiles from the lowest texture level are available
    //
    for ( int column = 0; noerr && column < levelZeroColumns; ++column ) {
        for ( int row = 0; noerr && row < levelZeroRows; ++row ) {

            const QString tilepath = MarbleDirs::path( TileLoaderHelper::relativeTileFileName(
                texture, 0, column, row ));
            noerr = QFile::exists( tilepath );
        }
    }

    return noerr;
}

void TileLoader::setVolatileCacheLimit( quint64 kiloBytes )
{
    qDebug() << QString("Setting tile cache to %1 kilobytes.").arg( kiloBytes );
    d->m_tileCache.setMaxCost( kiloBytes * 1024 );
}

void TileLoader::reloadTile( const QString &idStr )
{
    if ( !d->m_layer ) return;

//    qDebug() << "TileLoader::reloadTile:" << idStr;

    const TileId id = TileId::fromString( idStr );
    if ( d->m_tileHash.contains( id ) ) {
        int  level = id.zoomLevel();
        int  y     = id.y();
        int  x     = id.x();

        // TODO should emit signal rather than directly calling paintTile
//         emit paintTile( d->m_tileHash[id], x, y, level, d->m_theme, true );
        GeoSceneTexture * texture = static_cast<GeoSceneTexture *>( d->m_layer->groundDataset() );

        (d->m_tileHash[id])->loadDataset( texture, level, x, y, &( d->m_tileCache ) );
        m_parent->paintTile( d->m_tileHash[id], x, y, level, texture, true );
//         (d->m_tileHash[id])->reloadTile( x, y, level, d->m_theme );
    } else {
      // Remove "false" tile from cache so it doesn't get loaded anymore
      d->m_tileCache.remove( id );
      qDebug() << "No such ID:" << idStr;
    }
}

void TileLoader::reloadTile( const QString &relativeUrlString, const QString &_id )
{
    Q_UNUSED( relativeUrlString );
    // qDebug() << "Reloading Tile" << relativeUrlString << "id:" << _id;

    reloadTile( _id );
}

void TileLoader::reloadTile( const QString& serverUrlString, const QString &relativeUrlString,
                             const QString &_id )
{
    Q_UNUSED( serverUrlString );
    Q_UNUSED( relativeUrlString );
    // qDebug() << "Reloading Tile" << serverUrlString << relativeUrlString << "id:" << _id;

    reloadTile( _id );
}

void TileLoader::update()
{
    qDebug() << "TileLoader::update()";
    flush(); // trigger a reload of all tiles that are currently in use
    d->m_tileCache.clear(); // clear the tile cache in physical memory
    emit tileUpdateAvailable();
}

}

#include "TileLoader.moc"
<commit_msg>Rename member variable: m_tileHash -> m_tilesOnDisplay.<commit_after>/**
 * This file is part of the Marble Desktop Globe.
 *
 * Copyright 2005-2007 Torsten Rahn <tackat@kde.org>"
 * Copyright 2007      Inge Wallin  <ingwa@kde.org>"
 * Copyright 2008,2009 Jens-Michael Hoffmann <jensmh@gmx.de>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "TileLoader.h"

#include "global.h"
#include "GeoSceneLayer.h"
#include "GeoSceneTexture.h"
#include "HttpDownloadManager.h"
#include "DatasetProvider.h"
#include "TextureTile.h"
#include "MarbleDirs.h"
#include "MarbleModel.h"
#include "TileLoaderHelper.h"

#include <QtCore/QCache>
#include <QtCore/QDebug>
#include <QtCore/QHash>
#include <QtGui/QImage>

#ifdef Q_CC_MSVC
# ifndef KDEWIN_MATH_H
   long double log(int i) { return log((long double)i); }
# endif
#endif

namespace Marble
{

class TileLoaderPrivate
{
    public:
        TileLoaderPrivate()
            : m_datasetProvider( 0 ),
              m_downloadManager( 0 ),
              m_layer( 0 ),
              m_tileWidth( 0 ),
              m_tileHeight( 0 )
        {
            m_tileCache.setMaxCost( 20000 * 1024 ); // Cache size measured in bytes
        }

        DatasetProvider *m_datasetProvider;
        HttpDownloadManager *m_downloadManager;
        GeoSceneLayer *m_layer;
        QHash <TileId, TextureTile*>  m_tilesOnDisplay;
        int           m_tileWidth;
        int           m_tileHeight;
        QCache <TileId, TextureTile>  m_tileCache;
};

TileLoader::TileLoader( HttpDownloadManager *downloadManager, MarbleModel* parent)
    : d( new TileLoaderPrivate() ),
      m_parent(parent)
{
    setDownloadManager( downloadManager );
}

TileLoader::~TileLoader()
{
    flush();
    d->m_tileCache.clear();
    if ( d->m_downloadManager != 0 )
        d->m_downloadManager->disconnect( this );

    delete d;
}

void TileLoader::setDownloadManager( HttpDownloadManager *downloadManager )
{
    if ( d->m_downloadManager != 0 ) {
        d->m_downloadManager->disconnect( this );
        d->m_downloadManager = 0;
    }

    d->m_downloadManager = downloadManager;
    if ( d->m_downloadManager != 0 ) {
        connect( d->m_downloadManager, SIGNAL( downloadComplete( QString, QString ) ),
                 this,              SLOT( reloadTile( QString, QString ) ) );
    }
}

void TileLoader::setLayer( GeoSceneLayer * layer )
{
    // Initialize map theme.
    flush();
    d->m_tileCache.clear();

    if ( !layer ) {
        qDebug() << "No layer specified! (GeoSceneLayer * layer = 0)";
        return;
    }

    d->m_layer = layer;

    TileId id;
    TextureTile tile( id );

    GeoSceneTexture * texture = static_cast<GeoSceneTexture *>( d->m_layer->groundDataset() );

    tile.loadDataset( texture, 0, 0, 0 );

    // We assume that all tiles have the same size. TODO: check to be safe
    d->m_tileWidth  = tile.rawtile().width();
    d->m_tileHeight = tile.rawtile().height();
}

void TileLoader::resetTilehash()
{
    QHash<TileId, TextureTile*>::const_iterator it = d->m_tilesOnDisplay.constBegin();
    while ( it != d->m_tilesOnDisplay.constEnd() ) {
        it.value()->setUsed( false );
        ++it;
    }
}

void TileLoader::cleanupTilehash()
{
    // Make sure that tiles which haven't been used during the last
    // rendering of the map at all get removed from the tile hash.

    QHashIterator<TileId, TextureTile*> it( d->m_tilesOnDisplay );
    while ( it.hasNext() ) {
        it.next();
        if ( !it.value()->used() ) {
            // If insert call result is false then the cache is too small to store the tile
            // but the item will get deleted nevertheless and the pointer we have
            // doesn't get set to zero (so don't delete it in this case or it will crash!)
            d->m_tileCache.insert( it.key(), it.value(), it.value()->numBytes() );
            d->m_tilesOnDisplay.remove( it.key() );
        }
    }
}

void TileLoader::flush()
{
    // Remove all tiles from m_tilesOnDisplay
    QHashIterator<TileId, TextureTile*> it( d->m_tilesOnDisplay );
    while ( it.hasNext() ) {
        it.next();
        // If insert call result is false then the cache is too small to store the tile
        // but the item will get deleted nevertheless and the pointer we have
        // doesn't get set to zero (so don't delete it in this case or it will crash!)
        d->m_tileCache.insert( it.key(), it.value(), it.value()->numBytes() );
        d->m_tilesOnDisplay.remove( it.key() );
    }

    d->m_tilesOnDisplay.clear();
}

int TileLoader::tileWidth() const
{
    return d->m_tileWidth;
}

int TileLoader::tileHeight() const
{
    return d->m_tileHeight;
}

int TileLoader::globalWidth( int level ) const
{
    if ( !d->m_layer ) return 0;

    GeoSceneTexture * texture = static_cast<GeoSceneTexture *>( d->m_layer->groundDataset() );

    return d->m_tileWidth * TileLoaderHelper::levelToColumn(
                                texture->levelZeroColumns(), level );
}

int TileLoader::globalHeight( int level ) const
{
    if ( !d->m_layer ) return 0;

    GeoSceneTexture * texture = static_cast<GeoSceneTexture *>( d->m_layer->groundDataset() );

    return d->m_tileHeight * TileLoaderHelper::levelToRow(
                                texture->levelZeroRows(), level );
}

TextureTile* TileLoader::loadTile( int tilx, int tily, int tileLevel )
{
    if ( !d->m_layer ) return 0;

    TileId tileId( tileLevel, tilx, tily );

    // check if the tile is in the hash
    TextureTile * tile = d->m_tilesOnDisplay.value( tileId, 0 );
    if ( tile ) {
        tile->setUsed( true );
        return tile;
    }
    // here ends the performance critical section of this method

    // the tile was not in the hash or has been removed because of expiration
    // so check if it is in the cache
    tile = d->m_tileCache.take( tileId );

    GeoSceneTexture * texture = static_cast<GeoSceneTexture *>( d->m_layer->groundDataset() );

    if ( tile ) {
        // the tile was in the cache, but is it up to date?
        const QDateTime now = QDateTime::currentDateTime();

        if ( tile->created().secsTo( now ) < texture->expire()) {
            d->m_tilesOnDisplay[tileId] = tile;
            tile->setUsed( true );
            return tile;
        } else {
            delete tile;
            tile = 0;
        }
    }

    // tile (valid) has not been found in hash or cache, so load it from disk
    // and place it in the hash from where it will get transfered to the cache

    // qDebug() << "load Tile from Disk: " << tileId.toString();
    tile = new TextureTile( tileId );
    d->m_tilesOnDisplay[tileId] = tile;

    // FIXME: Implement asynchronous tile loading
    // d->m_datasetProvider->loadDatasets( tile );

    if ( d->m_downloadManager != 0 ) {
        connect( tile, SIGNAL( downloadTile( QUrl, QString, QString ) ),
                 d->m_downloadManager, SLOT( addJob( QUrl, QString, QString ) ) );
    }
    connect( tile, SIGNAL( tileUpdateDone() ),
             this, SIGNAL( tileUpdateAvailable() ) );

    tile->loadDataset( texture, tileLevel, tilx, tily, &( d->m_tileCache ) );
    tile->initJumpTables( false );

    // TODO should emit signal rather than directly calling paintTile
    // emit paintTile( tile, tilx, tily, tileLevel, d->m_theme, false );
    m_parent->paintTile( tile, tilx, tily, tileLevel, texture, false );

    return tile;
}

GeoSceneLayer * TileLoader::layer() const
{
    return d->m_layer;
}

quint64 TileLoader::volatileCacheLimit() const
{
    return d->m_tileCache.maxCost() / 1024;
}

QList<TileId> TileLoader::tilesOnDisplay() const
{
    QList<TileId> result;
    QHash<TileId, TextureTile*>::const_iterator pos = d->m_tilesOnDisplay.constBegin();
    QHash<TileId, TextureTile*>::const_iterator const end = d->m_tilesOnDisplay.constEnd();
    for (; pos != end; ++pos ) {
        if ( pos.value()->used() ) {
            result.append( pos.key() );
        }
    }
    return result;
}

int TileLoader::maxPartialTileLevel( GeoSceneLayer * layer )
{
    int maxtilelevel = -1;

    if ( !layer ) return maxtilelevel;

    GeoSceneTexture * texture = static_cast<GeoSceneTexture *>( layer->groundDataset() );

    if ( !texture ) return maxtilelevel;

    QString tilepath = MarbleDirs::path( TileLoaderHelper::themeStr( texture ) );
//    qDebug() << "TileLoader::maxPartialTileLevel tilepath" << tilepath;
    QStringList leveldirs = ( QDir( tilepath ) ).entryList( QDir::AllDirs | QDir::NoSymLinks | QDir::NoDotAndDotDot );

    bool ok = true;

    QStringList::const_iterator constIterator = leveldirs.constBegin();
    for (; constIterator != leveldirs.constEnd(); ++constIterator )
    {
        int value = (*constIterator).toInt( &ok, 10 );
        if ( ok && value > maxtilelevel )
            maxtilelevel = value;
    }

//    qDebug() << "Detected maximum tile level that contains data: "
//             << maxtilelevel;

    return maxtilelevel;
}

bool TileLoader::baseTilesAvailable( GeoSceneLayer * layer )
{
    if ( !layer ) return false;

    GeoSceneTexture * texture = static_cast<GeoSceneTexture *>( layer->groundDataset() );

    const int  levelZeroColumns = texture->levelZeroColumns();
    const int  levelZeroRows    = texture->levelZeroRows();

    bool noerr = true;

    // Check whether the tiles from the lowest texture level are available
    //
    for ( int column = 0; noerr && column < levelZeroColumns; ++column ) {
        for ( int row = 0; noerr && row < levelZeroRows; ++row ) {

            const QString tilepath = MarbleDirs::path( TileLoaderHelper::relativeTileFileName(
                texture, 0, column, row ));
            noerr = QFile::exists( tilepath );
        }
    }

    return noerr;
}

void TileLoader::setVolatileCacheLimit( quint64 kiloBytes )
{
    qDebug() << QString("Setting tile cache to %1 kilobytes.").arg( kiloBytes );
    d->m_tileCache.setMaxCost( kiloBytes * 1024 );
}

void TileLoader::reloadTile( const QString &idStr )
{
    if ( !d->m_layer ) return;

//    qDebug() << "TileLoader::reloadTile:" << idStr;

    const TileId id = TileId::fromString( idStr );
    if ( d->m_tilesOnDisplay.contains( id ) ) {
        int  level = id.zoomLevel();
        int  y     = id.y();
        int  x     = id.x();

        // TODO should emit signal rather than directly calling paintTile
//         emit paintTile( d->m_tilesOnDisplay[id], x, y, level, d->m_theme, true );
        GeoSceneTexture * texture = static_cast<GeoSceneTexture *>( d->m_layer->groundDataset() );

        (d->m_tilesOnDisplay[id])->loadDataset( texture, level, x, y, &( d->m_tileCache ) );
        m_parent->paintTile( d->m_tilesOnDisplay[id], x, y, level, texture, true );
//         (d->m_tilesOnDisplay[id])->reloadTile( x, y, level, d->m_theme );
    } else {
      // Remove "false" tile from cache so it doesn't get loaded anymore
      d->m_tileCache.remove( id );
      qDebug() << "No such ID:" << idStr;
    }
}

void TileLoader::reloadTile( const QString &relativeUrlString, const QString &_id )
{
    Q_UNUSED( relativeUrlString );
    // qDebug() << "Reloading Tile" << relativeUrlString << "id:" << _id;

    reloadTile( _id );
}

void TileLoader::reloadTile( const QString& serverUrlString, const QString &relativeUrlString,
                             const QString &_id )
{
    Q_UNUSED( serverUrlString );
    Q_UNUSED( relativeUrlString );
    // qDebug() << "Reloading Tile" << serverUrlString << relativeUrlString << "id:" << _id;

    reloadTile( _id );
}

void TileLoader::update()
{
    qDebug() << "TileLoader::update()";
    flush(); // trigger a reload of all tiles that are currently in use
    d->m_tileCache.clear(); // clear the tile cache in physical memory
    emit tileUpdateAvailable();
}

}

#include "TileLoader.moc"
<|endoftext|>


<commit_before>//
// This file is part of the Marble Desktop Globe.
//
// This program is free software licensed under the GNU LGPL. You can
// find a copy of this license in LICENSE.txt in the top directory of
// the source code.
//
// Copyright 2007      Inge Wallin  <ingwa@kde.org>
// Copyright 2008      Jens-Michael Hoffmann <jensmh@gmx.de>
//

#include "ViewParams.h"

#include <QtGui/QImage>

#include "MarbleDebug.h"
#include "AbstractProjection.h"
#include "GeoSceneDocument.h"
#include "GeoSceneGroup.h"
#include "GeoSceneProperty.h"
#include "GeoSceneSettings.h"
#include "MapThemeManager.h"
#include "ViewportParams.h"

namespace Marble
{
static QString const showCloudsPropertyName = "showClouds";
static QString const cloudsLayerName = "clouds_data";

class ViewParamsPrivate
{
public:
    ViewParamsPrivate();
    ~ViewParamsPrivate();

    GeoSceneDocument *m_mapTheme;

    ViewportParams  m_viewport;

    // The quality that we are painting right now.
    MapQuality           m_mapQuality;

    // FIXME: We should try to get rid of these two:
    Quaternion  m_planetAxisUpdated;
    int         m_radiusUpdated;

    // Parameters that determine the painting
    // Show/don't show options

    // FIXME: All of these parameters should get stored as a GeoSceneSettings
    //        property object in the future instead ...
    bool        m_showAtmosphere;
    bool        m_showCrosshairs;

    bool        m_showElevationModel;

    bool        m_showGps; //for gps layer

    // here "global" settings are stored, which are used for every map theme
    // where they are applicable. For example, clouds visibility is stored here,
    // but this property is only used for some map themes.
    GeoSceneSettings m_globalSettings;

    // Cached data that will make painting faster.
    QImage  *m_canvasImage;     // Base image with space and atmosphere
    QImage  *m_coastImage;      // A slightly higher level image.

    void initGlobalSettings();
};

ViewParamsPrivate::ViewParamsPrivate()
    : m_mapTheme( 0 ),
      m_viewport(),
      m_mapQuality( NormalQuality ),
      m_planetAxisUpdated(),
      m_radiusUpdated( 0 ),
      // Show / don't show parameters
      m_showAtmosphere( true ),
      m_showElevationModel( false ),
      // Other layers
      m_showGps( false ),
      // Just to have something.  These will be resized anyway.
      m_canvasImage( new QImage( 10, 10, QImage::Format_RGB32 )),
      m_coastImage( new QImage( 10, 10, QImage::Format_RGB32 ))
{
    initGlobalSettings();
}

ViewParamsPrivate::~ViewParamsPrivate()
{
    delete m_canvasImage;
    delete m_coastImage;
}

void ViewParamsPrivate::initGlobalSettings()
{
    GeoSceneProperty * const showClouds = new GeoSceneProperty( showCloudsPropertyName );
    m_globalSettings.addProperty( showClouds );
}

ViewParams::ViewParams()
    : d( new ViewParamsPrivate )
{
}

ViewParams::~ViewParams()
{
    delete d;
}

ViewportParams *ViewParams::viewport()
{
    return &d->m_viewport;
}

Projection ViewParams::projection() const
{
    return d->m_viewport.projection();
}

MapQuality ViewParams::mapQuality() const
{
    return d->m_mapQuality;
}

void ViewParams::setMapQuality( MapQuality mapQuality )
{
    d->m_mapQuality = mapQuality;
}

AbstractProjection *ViewParams::currentProjection() const
{
    return d->m_viewport.currentProjection();
}

void ViewParams::setProjection(Projection newProjection)
{
    d->m_viewport.setProjection( newProjection );

    // Repaint the background if necessary
    if ( !currentProjection()->mapCoversViewport( viewport() ) ) {
        canvasImage()->fill(0); // Using Qt::transparent is wrong here (equals "18")!
    }
}

void ViewParams::setMapThemeId( const QString& mapThemeId )
{
    GeoSceneDocument* mapTheme = MapThemeManager::loadMapTheme( mapThemeId );

    // Check whether the selected theme got parsed well
    if ( !mapTheme ) {

        // Check whether the previous theme works
        if ( !d->m_mapTheme ){
            // Fall back to default theme
            QString defaultTheme = "earth/srtm/srtm.dgml";
            qWarning() << "Falling back to default theme " << defaultTheme;
            mapTheme = MapThemeManager::loadMapTheme(defaultTheme);

            // If this last resort doesn't work either shed a tear and exit
            if ( !mapTheme ) {
                qWarning() << "Couldn't find a valid DGML map.";
                return;
            }
        }
        else {
            qWarning() << "Selected theme doesn't work, so we stick to the previous one";
            return;
        }
    }

    d->m_mapTheme = mapTheme;
}

GeoSceneDocument *ViewParams::mapTheme()
{
    return d->m_mapTheme;
}

void ViewParams::setPropertyValue( const QString &name, bool value )
{
    if ( d->m_mapTheme ) {
        d->m_mapTheme->settings()->setPropertyValue( name, value );
    }
    else {
        mDebug() << "WARNING: Failed to access a map theme! Property: " << name;
    }
}

void ViewParams::propertyValue( const QString &name, bool &value )
{
    if ( d->m_mapTheme ) {
        d->m_mapTheme->settings()->propertyValue( name, value );
    }
    else {
        value = false;
        mDebug() << "WARNING: Failed to access a map theme! Property: " << name;
    }
}

void ViewParams::propertyAvailable( const QString &name, bool &value )
{
    if ( d->m_mapTheme ) {
        d->m_mapTheme->settings()->propertyAvailable( name, value );
    }
    else {
        value = false;
        mDebug() << "WARNING: Failed to access a map theme! Property: " << name;
    }
}

int ViewParams::radius() const
{
    return d->m_viewport.radius();
}

void ViewParams::setRadius(int newRadius)
{
    d->m_viewport.setRadius( newRadius );

    // Repaint the background if necessary
    if ( !currentProjection()->mapCoversViewport( viewport() ) ) {
        canvasImage()->fill(0); // Using Qt::transparent is wrong here (equals "18")!
    }

}

Quaternion ViewParams::planetAxis() const
{
    return d->m_viewport.planetAxis();
}

void ViewParams::setPlanetAxis(const Quaternion &newAxis)
{
    d->m_viewport.setPlanetAxis( newAxis );
/*
    // Repaint the background if necessary
    if ( projection() != Spherical && !currentProjection()->mapCoversViewport( viewport() ) ) {
        canvasImage()->fill(0); // Using Qt::transparent is wrong here (equals "18")!
    }
*/
}

void ViewParams::centerCoordinates( qreal &centerLon, qreal &centerLat )
{
    d->m_viewport.centerCoordinates( centerLon, centerLat );
}

QImage * ViewParams::canvasImage() const
{
    return d->m_canvasImage;
}

void ViewParams::setCanvasImage( QImage * const image )
{
    delete d->m_canvasImage;
    d->m_canvasImage = image;

    // Repaint the background if necessary
    if ( !currentProjection()->mapCoversViewport( viewport() ) ) {
        d->m_canvasImage->fill(0); // Using Qt::transparent is wrong here (equals "18")!
    }
}

QImage * ViewParams::coastImage() const
{
    return d->m_coastImage;
}

void ViewParams::setCoastImage( QImage * const image )
{
    delete d->m_coastImage;
    d->m_coastImage = image;
}

int ViewParams::radiusUpdated() const
{
    return d->m_radiusUpdated;
}

void ViewParams::setRadiusUpdated( const int radiusUpdated )
{
    d->m_radiusUpdated = radiusUpdated;
}

bool ViewParams::showGps() const
{
    return d->m_showGps;
}

void ViewParams::setShowGps( bool showGps )
{
    d->m_showGps = showGps;
}

bool ViewParams::showElevationModel() const
{
    return d->m_showElevationModel;
}

void ViewParams::setShowElevationModel( bool showElevationModel )
{
    d->m_showElevationModel = showElevationModel;
}

bool ViewParams::showAtmosphere() const
{
    return d->m_showAtmosphere;
}

void ViewParams::setShowAtmosphere( bool showAtmosphere )
{
    d->m_showAtmosphere = showAtmosphere;
}

bool ViewParams::showClouds() const
{
    bool showClouds = false;
    bool const propertyFound = d->m_globalSettings.propertyValue( showCloudsPropertyName,
                                                                  showClouds );
    return propertyFound && showClouds;
}

void ViewParams::setShowClouds( bool const showClouds )
{
    d->m_globalSettings.setPropertyValue( showCloudsPropertyName, showClouds );
    if ( !d->m_mapTheme )
        return;

    GeoSceneSettings * const settings = d->m_mapTheme->settings();
    if ( !settings )
        return;

    GeoSceneGroup * const textureLayerSettings = settings->group( "Texture Layers" );
    if ( !textureLayerSettings )
        return;
    textureLayerSettings->setPropertyValue( cloudsLayerName, showClouds );
}

Quaternion ViewParams::planetAxisUpdated() const
{
    return d->m_planetAxisUpdated;
}

void ViewParams::setPlanetAxisUpdated( const Quaternion & planetAxisUpdated )
{
    d->m_planetAxisUpdated = planetAxisUpdated;
}

}
<commit_msg>ViewParams: propagate global settings to local settings on map theme change.<commit_after>//
// This file is part of the Marble Desktop Globe.
//
// This program is free software licensed under the GNU LGPL. You can
// find a copy of this license in LICENSE.txt in the top directory of
// the source code.
//
// Copyright 2007      Inge Wallin  <ingwa@kde.org>
// Copyright 2008      Jens-Michael Hoffmann <jensmh@gmx.de>
//

#include "ViewParams.h"

#include <QtGui/QImage>

#include "MarbleDebug.h"
#include "AbstractProjection.h"
#include "GeoSceneDocument.h"
#include "GeoSceneGroup.h"
#include "GeoSceneProperty.h"
#include "GeoSceneSettings.h"
#include "MapThemeManager.h"
#include "ViewportParams.h"

namespace Marble
{
static QString const showCloudsPropertyName = "showClouds";
static QString const cloudsLayerName = "clouds_data";

class ViewParamsPrivate
{
public:
    ViewParamsPrivate();
    ~ViewParamsPrivate();

    GeoSceneDocument *m_mapTheme;

    ViewportParams  m_viewport;

    // The quality that we are painting right now.
    MapQuality           m_mapQuality;

    // FIXME: We should try to get rid of these two:
    Quaternion  m_planetAxisUpdated;
    int         m_radiusUpdated;

    // Parameters that determine the painting
    // Show/don't show options

    // FIXME: All of these parameters should get stored as a GeoSceneSettings
    //        property object in the future instead ...
    bool        m_showAtmosphere;
    bool        m_showCrosshairs;

    bool        m_showElevationModel;

    bool        m_showGps; //for gps layer

    // here "global" settings are stored, which are used for every map theme
    // where they are applicable. For example, clouds visibility is stored here,
    // but this property is only used for some map themes.
    GeoSceneSettings m_globalSettings;

    // Cached data that will make painting faster.
    QImage  *m_canvasImage;     // Base image with space and atmosphere
    QImage  *m_coastImage;      // A slightly higher level image.

    void initGlobalSettings();
    void propagateGlobalToLocalSettings();
};

ViewParamsPrivate::ViewParamsPrivate()
    : m_mapTheme( 0 ),
      m_viewport(),
      m_mapQuality( NormalQuality ),
      m_planetAxisUpdated(),
      m_radiusUpdated( 0 ),
      // Show / don't show parameters
      m_showAtmosphere( true ),
      m_showElevationModel( false ),
      // Other layers
      m_showGps( false ),
      // Just to have something.  These will be resized anyway.
      m_canvasImage( new QImage( 10, 10, QImage::Format_RGB32 )),
      m_coastImage( new QImage( 10, 10, QImage::Format_RGB32 ))
{
    initGlobalSettings();
}

ViewParamsPrivate::~ViewParamsPrivate()
{
    delete m_canvasImage;
    delete m_coastImage;
}

void ViewParamsPrivate::initGlobalSettings()
{
    GeoSceneProperty * const showClouds = new GeoSceneProperty( showCloudsPropertyName );
    m_globalSettings.addProperty( showClouds );
}

void ViewParamsPrivate::propagateGlobalToLocalSettings()
{
    bool showClouds = false;
    bool const propertyFound = m_globalSettings.propertyValue( showCloudsPropertyName, showClouds );
    if ( propertyFound ) {
        if ( !m_mapTheme )
            return;

        GeoSceneSettings * const settings = m_mapTheme->settings();
        if ( !settings )
            return;

        GeoSceneGroup * const textureLayerSettings = settings->group( "Texture Layers" );
        if ( !textureLayerSettings )
            return;
        textureLayerSettings->setPropertyValue( cloudsLayerName, showClouds );
    }
}

ViewParams::ViewParams()
    : d( new ViewParamsPrivate )
{
}

ViewParams::~ViewParams()
{
    delete d;
}

ViewportParams *ViewParams::viewport()
{
    return &d->m_viewport;
}

Projection ViewParams::projection() const
{
    return d->m_viewport.projection();
}

MapQuality ViewParams::mapQuality() const
{
    return d->m_mapQuality;
}

void ViewParams::setMapQuality( MapQuality mapQuality )
{
    d->m_mapQuality = mapQuality;
}

AbstractProjection *ViewParams::currentProjection() const
{
    return d->m_viewport.currentProjection();
}

void ViewParams::setProjection(Projection newProjection)
{
    d->m_viewport.setProjection( newProjection );

    // Repaint the background if necessary
    if ( !currentProjection()->mapCoversViewport( viewport() ) ) {
        canvasImage()->fill(0); // Using Qt::transparent is wrong here (equals "18")!
    }
}

void ViewParams::setMapThemeId( const QString& mapThemeId )
{
    GeoSceneDocument* mapTheme = MapThemeManager::loadMapTheme( mapThemeId );

    // Check whether the selected theme got parsed well
    if ( !mapTheme ) {

        // Check whether the previous theme works
        if ( !d->m_mapTheme ){
            // Fall back to default theme
            QString defaultTheme = "earth/srtm/srtm.dgml";
            qWarning() << "Falling back to default theme " << defaultTheme;
            mapTheme = MapThemeManager::loadMapTheme(defaultTheme);

            // If this last resort doesn't work either shed a tear and exit
            if ( !mapTheme ) {
                qWarning() << "Couldn't find a valid DGML map.";
                return;
            }
        }
        else {
            qWarning() << "Selected theme doesn't work, so we stick to the previous one";
            return;
        }
    }

    d->m_mapTheme = mapTheme;
    d->propagateGlobalToLocalSettings();
}

GeoSceneDocument *ViewParams::mapTheme()
{
    return d->m_mapTheme;
}

void ViewParams::setPropertyValue( const QString &name, bool value )
{
    if ( d->m_mapTheme ) {
        d->m_mapTheme->settings()->setPropertyValue( name, value );
    }
    else {
        mDebug() << "WARNING: Failed to access a map theme! Property: " << name;
    }
}

void ViewParams::propertyValue( const QString &name, bool &value )
{
    if ( d->m_mapTheme ) {
        d->m_mapTheme->settings()->propertyValue( name, value );
    }
    else {
        value = false;
        mDebug() << "WARNING: Failed to access a map theme! Property: " << name;
    }
}

void ViewParams::propertyAvailable( const QString &name, bool &value )
{
    if ( d->m_mapTheme ) {
        d->m_mapTheme->settings()->propertyAvailable( name, value );
    }
    else {
        value = false;
        mDebug() << "WARNING: Failed to access a map theme! Property: " << name;
    }
}

int ViewParams::radius() const
{
    return d->m_viewport.radius();
}

void ViewParams::setRadius(int newRadius)
{
    d->m_viewport.setRadius( newRadius );

    // Repaint the background if necessary
    if ( !currentProjection()->mapCoversViewport( viewport() ) ) {
        canvasImage()->fill(0); // Using Qt::transparent is wrong here (equals "18")!
    }

}

Quaternion ViewParams::planetAxis() const
{
    return d->m_viewport.planetAxis();
}

void ViewParams::setPlanetAxis(const Quaternion &newAxis)
{
    d->m_viewport.setPlanetAxis( newAxis );
/*
    // Repaint the background if necessary
    if ( projection() != Spherical && !currentProjection()->mapCoversViewport( viewport() ) ) {
        canvasImage()->fill(0); // Using Qt::transparent is wrong here (equals "18")!
    }
*/
}

void ViewParams::centerCoordinates( qreal &centerLon, qreal &centerLat )
{
    d->m_viewport.centerCoordinates( centerLon, centerLat );
}

QImage * ViewParams::canvasImage() const
{
    return d->m_canvasImage;
}

void ViewParams::setCanvasImage( QImage * const image )
{
    delete d->m_canvasImage;
    d->m_canvasImage = image;

    // Repaint the background if necessary
    if ( !currentProjection()->mapCoversViewport( viewport() ) ) {
        d->m_canvasImage->fill(0); // Using Qt::transparent is wrong here (equals "18")!
    }
}

QImage * ViewParams::coastImage() const
{
    return d->m_coastImage;
}

void ViewParams::setCoastImage( QImage * const image )
{
    delete d->m_coastImage;
    d->m_coastImage = image;
}

int ViewParams::radiusUpdated() const
{
    return d->m_radiusUpdated;
}

void ViewParams::setRadiusUpdated( const int radiusUpdated )
{
    d->m_radiusUpdated = radiusUpdated;
}

bool ViewParams::showGps() const
{
    return d->m_showGps;
}

void ViewParams::setShowGps( bool showGps )
{
    d->m_showGps = showGps;
}

bool ViewParams::showElevationModel() const
{
    return d->m_showElevationModel;
}

void ViewParams::setShowElevationModel( bool showElevationModel )
{
    d->m_showElevationModel = showElevationModel;
}

bool ViewParams::showAtmosphere() const
{
    return d->m_showAtmosphere;
}

void ViewParams::setShowAtmosphere( bool showAtmosphere )
{
    d->m_showAtmosphere = showAtmosphere;
}

bool ViewParams::showClouds() const
{
    bool showClouds = false;
    bool const propertyFound = d->m_globalSettings.propertyValue( showCloudsPropertyName,
                                                                  showClouds );
    return propertyFound && showClouds;
}

void ViewParams::setShowClouds( bool const showClouds )
{
    d->m_globalSettings.setPropertyValue( showCloudsPropertyName, showClouds );
    if ( !d->m_mapTheme )
        return;

    GeoSceneSettings * const settings = d->m_mapTheme->settings();
    if ( !settings )
        return;

    GeoSceneGroup * const textureLayerSettings = settings->group( "Texture Layers" );
    if ( !textureLayerSettings )
        return;
    textureLayerSettings->setPropertyValue( cloudsLayerName, showClouds );
}

Quaternion ViewParams::planetAxisUpdated() const
{
    return d->m_planetAxisUpdated;
}

void ViewParams::setPlanetAxisUpdated( const Quaternion & planetAxisUpdated )
{
    d->m_planetAxisUpdated = planetAxisUpdated;
}

}
<|endoftext|>


<commit_before>/* <x0/Process.cpp>
 *
 * This file is part of the x0 web server project and is released under LGPL-3.
 * http://www.xzero.ws/
 *
 * (c) 2009-2010 Christian Parpart <trapni@gentoo.org>
 */

#include <x0/Process.h>
#include <string>
#include <cstdlib>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>

namespace x0 {

/** invokes \p cmd until its not early aborted with EINTR. */
#define EINTR_LOOP(cmd) {									\
	int _rv;												\
	do {													\
		_rv = cmd;											\
	} while (_rv == -1 && errno == EINTR);					\
	if (_rv < 0) {											\
		fprintf(stderr, "EINTR_LOOP(%s): failed with: %s\n",\
				#cmd, strerror(errno));						\
	}														\
}

Process::Process(struct ev_loop *loop) :
	loop_(loop),
	input_(),
	output_(),
	error_(),
	pid_(-1),
	status_(0)
{
}

Process::Process(struct ev_loop *loop, const std::string& exe, const ArgumentList& args, const Environment& env, const std::string& workdir) :
	loop_(loop),
	input_(),
	output_(),
	error_(),
	pid_(-1),
	status_(0)
{
	start(exe, args, env, workdir);
}

Process::~Process()
{
	if (pid_ > 0)
		EINTR_LOOP(::waitpid(pid_, &status_, 0));

	//fprintf(stderr, "~Process(): rv=%d, errno=%s\n", rv, strerror(errno));
}

int Process::start(const std::string& exe, const ArgumentList& args, const Environment& env, const std::string& workdir)
{
#if !defined(NDEBUG)
	//::fprintf(stderr, "proc[%d] start(exe=%s, args=[...], workdir=%s)\n", getpid(), exe.c_str(), workdir.c_str());
	for (int i = 3; i < 32; ++i)
		if (!(fcntl(i, F_GETFD) & FD_CLOEXEC))
			fprintf(stderr, "Process: fd %d still open\n", i);
#endif

	switch (pid_ = vfork())
	{
		case -1: // error
			fprintf(stderr, "Process: error starting process: %s\n", strerror(errno));
			return -1;
		case 0: // child
			setupChild(exe, args, env, workdir);
			break;
		default: // parent
			setupParent();
			break;
	}
	return 0;
}

/** sends SIGTERM (terminate signal) to the child Process.
 */
void Process::terminate()
{
	fprintf(stderr, "Process(%ld).terminate()\n", pid_);
	if (pid_ > 0) {
		if (::kill(pid_, SIGTERM) < 0) {
			fprintf(stderr, "error sending SIGTERM to child %ld: %s\n", pid_, strerror(errno));
		}
	}
}

void Process::setStatus(int status)
{
	status_ = status;

	if (WIFEXITED(status_) || WIFSIGNALED(status_))
		// process terminated (normally or by signal).
		// so mark process as exited by resetting the PID
		pid_ = -1;
}

/** tests whether child Process has exited already.
 */
bool Process::expired()
{
	if (pid_ <= 0)
		return true;

	int rv;
	EINTR_LOOP(rv = ::waitpid(pid_, &status_, WNOHANG));

	if (rv == 0)
		// child not exited yet
		return false;

	if (rv < 0)
		// error
		return false;

	pid_ = -1;
	return true;
}

void Process::setupParent()
{
	// setup I/O
	input_.closeRemote();
	output_.closeRemote();
	error_.closeRemote();
}

void Process::setupChild(const std::string& _exe, const ArgumentList& _args, const Environment& _env, const std::string& _workdir)
{
	// restore signal handler(s)
	::signal(SIGPIPE, SIG_DFL);

	// setup environment
	int k = 0;
	std::vector<char *> env(_env.size() + 1);

	for (Environment::const_iterator i = _env.cbegin(), e = _env.cend(); i != e; ++i)
	{
		char *buf = new char[i->first.size() + i->second.size() + 2];
		::memcpy(buf, i->first.c_str(), i->first.size());
		buf[i->first.size()] = '=';
		::memcpy(buf + i->first.size() + 1, i->second.c_str(), i->second.size() + 1);

		//::fprintf(stderr, "proc[%d]: setting env[%d]: %s\n", getpid(), k, buf);
		//::fflush(stderr);
		env[k++] = buf;
	}
	env[_env.size()] = 0;

	// setup args
	std::vector<char *> args(_args.size() + 2);
	args[0] = const_cast<char *>(_exe.c_str());
	//::fprintf(stderr, "args[%d] = %s\n", 0, args[0]);
	for (int i = 0, e = _args.size(); i != e; ++i)
	{
		args[i + 1] = const_cast<char *>(_args[i].c_str());
		//::fprintf(stderr, "args[%d] = %s\n", i + 1, args[i + 1]);
	}
	args[args.size() - 1] = 0;

	// chdir
	if (!_workdir.empty())
	{
		::chdir(_workdir.c_str());
	}

	// setup I/O
	EINTR_LOOP(::close(STDIN_FILENO));
	EINTR_LOOP(::close(STDOUT_FILENO));
	EINTR_LOOP(::close(STDERR_FILENO));

	EINTR_LOOP(::dup2(input_.remote(), STDIN_FILENO));
	EINTR_LOOP(::dup2(output_.remote(), STDOUT_FILENO));
	EINTR_LOOP(::dup2(error_.remote(), STDERR_FILENO));

#if 0 // this is basically working but a very bad idea for high performance (XXX better get O_CLOEXEC working)
	for (int i = 3; i < 1024; ++i)
		::close(i);
#endif

//	input_.close();
//	output_.close();
//	error_.close();

	// finally execute
	::execve(args[0], &args[0], &env[0]);

	// OOPS
	::fprintf(stderr, "proc[%d]: execve(%s) error: %s\n", getpid(), args[0], strerror(errno));
	::fflush(stderr);
	::_exit(1);
}

} // namespace x0
<commit_msg>[base] Process: fixes compiler warnings on fprintf()'s<commit_after>/* <x0/Process.cpp>
 *
 * This file is part of the x0 web server project and is released under LGPL-3.
 * http://www.xzero.ws/
 *
 * (c) 2009-2010 Christian Parpart <trapni@gentoo.org>
 */

#include <x0/Process.h>
#include <string>
#include <cstdlib>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>

namespace x0 {

/** invokes \p cmd until its not early aborted with EINTR. */
#define EINTR_LOOP(cmd) {									\
	int _rv;												\
	do {													\
		_rv = cmd;											\
	} while (_rv == -1 && errno == EINTR);					\
	if (_rv < 0) {											\
		fprintf(stderr, "EINTR_LOOP(%s): failed with: %s\n",\
				#cmd, strerror(errno));						\
	}														\
}

Process::Process(struct ev_loop *loop) :
	loop_(loop),
	input_(),
	output_(),
	error_(),
	pid_(-1),
	status_(0)
{
}

Process::Process(struct ev_loop *loop, const std::string& exe, const ArgumentList& args, const Environment& env, const std::string& workdir) :
	loop_(loop),
	input_(),
	output_(),
	error_(),
	pid_(-1),
	status_(0)
{
	start(exe, args, env, workdir);
}

Process::~Process()
{
	if (pid_ > 0)
		EINTR_LOOP(::waitpid(pid_, &status_, 0));

	//fprintf(stderr, "~Process(): rv=%d, errno=%s\n", rv, strerror(errno));
}

int Process::start(const std::string& exe, const ArgumentList& args, const Environment& env, const std::string& workdir)
{
#if !defined(NDEBUG)
	//::fprintf(stderr, "proc[%d] start(exe=%s, args=[...], workdir=%s)\n", getpid(), exe.c_str(), workdir.c_str());
	for (int i = 3; i < 32; ++i)
		if (!(fcntl(i, F_GETFD) & FD_CLOEXEC))
			fprintf(stderr, "Process: fd %d still open\n", i);
#endif

	switch (pid_ = vfork())
	{
		case -1: // error
			fprintf(stderr, "Process: error starting process: %s\n", strerror(errno));
			return -1;
		case 0: // child
			setupChild(exe, args, env, workdir);
			break;
		default: // parent
			setupParent();
			break;
	}
	return 0;
}

/** sends SIGTERM (terminate signal) to the child Process.
 */
void Process::terminate()
{
	fprintf(stderr, "Process(%d).terminate()\n", pid_);
	if (pid_ > 0) {
		if (::kill(pid_, SIGTERM) < 0) {
			fprintf(stderr, "error sending SIGTERM to child %d: %s\n", pid_, strerror(errno));
		}
	}
}

void Process::setStatus(int status)
{
	status_ = status;

	if (WIFEXITED(status_) || WIFSIGNALED(status_))
		// process terminated (normally or by signal).
		// so mark process as exited by resetting the PID
		pid_ = -1;
}

/** tests whether child Process has exited already.
 */
bool Process::expired()
{
	if (pid_ <= 0)
		return true;

	int rv;
	EINTR_LOOP(rv = ::waitpid(pid_, &status_, WNOHANG));

	if (rv == 0)
		// child not exited yet
		return false;

	if (rv < 0)
		// error
		return false;

	pid_ = -1;
	return true;
}

void Process::setupParent()
{
	// setup I/O
	input_.closeRemote();
	output_.closeRemote();
	error_.closeRemote();
}

void Process::setupChild(const std::string& _exe, const ArgumentList& _args, const Environment& _env, const std::string& _workdir)
{
	// restore signal handler(s)
	::signal(SIGPIPE, SIG_DFL);

	// setup environment
	int k = 0;
	std::vector<char *> env(_env.size() + 1);

	for (Environment::const_iterator i = _env.cbegin(), e = _env.cend(); i != e; ++i)
	{
		char *buf = new char[i->first.size() + i->second.size() + 2];
		::memcpy(buf, i->first.c_str(), i->first.size());
		buf[i->first.size()] = '=';
		::memcpy(buf + i->first.size() + 1, i->second.c_str(), i->second.size() + 1);

		//::fprintf(stderr, "proc[%d]: setting env[%d]: %s\n", getpid(), k, buf);
		//::fflush(stderr);
		env[k++] = buf;
	}
	env[_env.size()] = 0;

	// setup args
	std::vector<char *> args(_args.size() + 2);
	args[0] = const_cast<char *>(_exe.c_str());
	//::fprintf(stderr, "args[%d] = %s\n", 0, args[0]);
	for (int i = 0, e = _args.size(); i != e; ++i)
	{
		args[i + 1] = const_cast<char *>(_args[i].c_str());
		//::fprintf(stderr, "args[%d] = %s\n", i + 1, args[i + 1]);
	}
	args[args.size() - 1] = 0;

	// chdir
	if (!_workdir.empty())
	{
		::chdir(_workdir.c_str());
	}

	// setup I/O
	EINTR_LOOP(::close(STDIN_FILENO));
	EINTR_LOOP(::close(STDOUT_FILENO));
	EINTR_LOOP(::close(STDERR_FILENO));

	EINTR_LOOP(::dup2(input_.remote(), STDIN_FILENO));
	EINTR_LOOP(::dup2(output_.remote(), STDOUT_FILENO));
	EINTR_LOOP(::dup2(error_.remote(), STDERR_FILENO));

#if 0 // this is basically working but a very bad idea for high performance (XXX better get O_CLOEXEC working)
	for (int i = 3; i < 1024; ++i)
		::close(i);
#endif

//	input_.close();
//	output_.close();
//	error_.close();

	// finally execute
	::execve(args[0], &args[0], &env[0]);

	// OOPS
	::fprintf(stderr, "proc[%d]: execve(%s) error: %s\n", getpid(), args[0], strerror(errno));
	::fflush(stderr);
	::_exit(1);
}

} // namespace x0
<|endoftext|>


<commit_before>/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qbs.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "transformer.h"

#include "artifact.h"
#include <jsextensions/moduleproperties.h>
#include <language/language.h>
#include <language/preparescriptobserver.h>
#include <language/scriptengine.h>
#include <logging/translator.h>
#include <tools/error.h>
#include <tools/fileinfo.h>
#include <tools/scripttools.h>
#include <tools/qbsassert.h>
#include <tools/stringconstants.h>
#include <tools/stlutils.h>

#include <QtCore/qdir.h>

#include <algorithm>

namespace qbs {
namespace Internal {

Transformer::Transformer() : alwaysRun(false)
{
}

Transformer::~Transformer()
{
}

static QScriptValue js_baseName(QScriptContext *ctx, QScriptEngine *engine,
                                const Artifact *artifact)
{
    Q_UNUSED(ctx);
    Q_UNUSED(engine);
    return {FileInfo::baseName(artifact->filePath())};
}

static QScriptValue js_completeBaseName(QScriptContext *ctx, QScriptEngine *engine,
                                        const Artifact *artifact)
{
    Q_UNUSED(ctx);
    Q_UNUSED(engine);
    return {FileInfo::completeBaseName(artifact->filePath())};
}

static QScriptValue js_baseDir(QScriptContext *ctx, QScriptEngine *engine,
                               const Artifact *artifact)
{
    Q_UNUSED(ctx);
    Q_UNUSED(engine);
    QString basedir;
    if (artifact->artifactType == Artifact::SourceFile) {
        QDir sourceDir(artifact->product->sourceDirectory);
        basedir = FileInfo::path(sourceDir.relativeFilePath(artifact->filePath()));
    } else {
        QDir buildDir(artifact->product->buildDirectory());
        basedir = FileInfo::path(buildDir.relativeFilePath(artifact->filePath()));
    }
    return basedir;
}

static QScriptValue js_children(QScriptContext *ctx, QScriptEngine *engine, const Artifact *artifact)
{
    Q_UNUSED(ctx);
    QScriptValue sv = engine->newArray();
    uint idx = 0;
    for (const Artifact *parent : artifact->childArtifacts()) {
        sv.setProperty(idx++, Transformer::translateFileConfig(static_cast<ScriptEngine *>(engine),
                                                               parent, QString()));
    }
    return sv;
}

static void setArtifactProperty(QScriptValue &obj, const QString &name,
        QScriptValue (*func)(QScriptContext *, QScriptEngine *, const Artifact *),
        const Artifact *artifact)
{
    obj.setProperty(name, static_cast<ScriptEngine *>(obj.engine())->newFunction(func, artifact),
                    QScriptValue::PropertyGetter);
}

QScriptValue Transformer::translateFileConfig(ScriptEngine *scriptEngine, const Artifact *artifact,
                                              const QString &defaultModuleName)
{
    QScriptValue obj = scriptEngine->newObject();
    attachPointerTo(obj, artifact);
    ModuleProperties::init(obj, artifact);
    obj.setProperty(StringConstants::fileNameProperty(), artifact->fileName());
    obj.setProperty(StringConstants::filePathProperty(), artifact->filePath());
    setArtifactProperty(obj, StringConstants::baseNameProperty(), js_baseName, artifact);
    setArtifactProperty(obj, StringConstants::completeBaseNameProperty(), js_completeBaseName,
                        artifact);
    setArtifactProperty(obj, QStringLiteral("baseDir"), js_baseDir, artifact);
    setArtifactProperty(obj, QStringLiteral("children"), js_children, artifact);
    const QStringList fileTags = sorted(artifact->fileTags().toStringList());
    scriptEngine->setObservedProperty(obj, StringConstants::fileTagsProperty(),
                                      scriptEngine->toScriptValue(fileTags));
    scriptEngine->observer()->addArtifactId(obj.objectId());
    if (!defaultModuleName.isEmpty())
        obj.setProperty(StringConstants::moduleNameProperty(), defaultModuleName);
    return obj;
}

static bool compareByFilePath(const Artifact *a1, const Artifact *a2)
{
    return a1->filePath() < a2->filePath();
}

QScriptValue Transformer::translateInOutputs(ScriptEngine *scriptEngine,
                                             const ArtifactSet &artifacts,
                                             const QString &defaultModuleName)
{
    using TagArtifactsMap = QMap<QString, QList<Artifact*>>;
    TagArtifactsMap tagArtifactsMap;
    for (Artifact *artifact : artifacts)
        for (const FileTag &fileTag : artifact->fileTags())
            tagArtifactsMap[fileTag.toString()].push_back(artifact);
    for (TagArtifactsMap::Iterator it = tagArtifactsMap.begin(); it != tagArtifactsMap.end(); ++it)
        std::sort(it.value().begin(), it.value().end(), compareByFilePath);

    QScriptValue jsTagFiles = scriptEngine->newObject();
    for (TagArtifactsMap::const_iterator tag = tagArtifactsMap.constBegin(); tag != tagArtifactsMap.constEnd(); ++tag) {
        const QList<Artifact*> &artifacts = tag.value();
        QScriptValue jsFileConfig = scriptEngine->newArray(artifacts.size());
        int i = 0;
        for (Artifact * const artifact : artifacts) {
            jsFileConfig.setProperty(i++, translateFileConfig(scriptEngine, artifact,
                                                              defaultModuleName));
        }
        jsTagFiles.setProperty(tag.key(), jsFileConfig);
    }

    return jsTagFiles;
}

ResolvedProductPtr Transformer::product() const
{
    if (outputs.empty())
        return {};
    return (*outputs.cbegin())->product.lock();
}

void Transformer::setupInputs(QScriptValue targetScriptValue, const ArtifactSet &inputs,
        const QString &defaultModuleName)
{
    const auto scriptEngine = static_cast<ScriptEngine *>(targetScriptValue.engine());
    QScriptValue scriptValue = translateInOutputs(scriptEngine, inputs, defaultModuleName);
    targetScriptValue.setProperty(StringConstants::inputsVar(), scriptValue);
    QScriptValue inputScriptValue;
    if (inputs.size() == 1) {
        Artifact *input = *inputs.cbegin();
        const FileTags &fileTags = input->fileTags();
        QBS_ASSERT(!fileTags.empty(), return);
        QScriptValue inputsForFileTag = scriptValue.property(fileTags.cbegin()->toString());
        inputScriptValue = inputsForFileTag.property(0);
    }
    targetScriptValue.setProperty(StringConstants::inputVar(), inputScriptValue);
}

void Transformer::setupInputs(QScriptValue targetScriptValue)
{
    setupInputs(targetScriptValue, inputs, rule->module->name);
}

void Transformer::setupOutputs(QScriptValue targetScriptValue)
{
    const auto scriptEngine = static_cast<ScriptEngine *>(targetScriptValue.engine());
    const QString &defaultModuleName = rule->module->name;
    QScriptValue scriptValue = translateInOutputs(scriptEngine, outputs, defaultModuleName);
    targetScriptValue.setProperty(StringConstants::outputsVar(), scriptValue);
    QScriptValue outputScriptValue;
    if (outputs.size() == 1) {
        Artifact *output = *outputs.cbegin();
        const FileTags &fileTags = output->fileTags();
        QBS_ASSERT(!fileTags.empty(), return);
        QScriptValue outputsForFileTag = scriptValue.property(fileTags.cbegin()->toString());
        outputScriptValue = outputsForFileTag.property(0);
    }
    targetScriptValue.setProperty(StringConstants::outputVar(), outputScriptValue);
}

void Transformer::setupExplicitlyDependsOn(QScriptValue targetScriptValue)
{
    const auto scriptEngine = static_cast<ScriptEngine *>(targetScriptValue.engine());
    QScriptValue scriptValue = translateInOutputs(scriptEngine, explicitlyDependsOn,
                                                  rule->module->name);
    targetScriptValue.setProperty(StringConstants::explicitlyDependsOnVar(), scriptValue);
}

AbstractCommandPtr Transformer::createCommandFromScriptValue(const QScriptValue &scriptValue,
                                                             const CodeLocation &codeLocation)
{
    AbstractCommandPtr cmdBase;
    if (scriptValue.isUndefined() || !scriptValue.isValid())
        return cmdBase;
    QString className = scriptValue.property(StringConstants::classNameProperty()).toString();
    if (className == StringConstants::commandType())
        cmdBase = ProcessCommand::create();
    else if (className == StringConstants::javaScriptCommandType())
        cmdBase = JavaScriptCommand::create();
    if (cmdBase)
        cmdBase->fillFromScriptValue(&scriptValue, codeLocation);
    if (className == StringConstants::commandType()) {
        auto procCmd = static_cast<ProcessCommand *>(cmdBase.get());
        procCmd->clearRelevantEnvValues();
        for (const QString &key : procCmd->relevantEnvVars())
            procCmd->addRelevantEnvValue(key, product()->buildEnvironment.value(key));
    }
    return cmdBase;
}

void Transformer::createCommands(ScriptEngine *engine, const PrivateScriptFunction &script,
                                 const QScriptValueList &args)
{
    if (!script.scriptFunction.isValid() || script.scriptFunction.engine() != engine) {
        script.scriptFunction = engine->evaluate(script.sourceCode(),
                                                  script.location().filePath(),
                                                  script.location().line());
        if (Q_UNLIKELY(!script.scriptFunction.isFunction()))
            throw ErrorInfo(Tr::tr("Invalid prepare script."), script.location());
    }

    QScriptValue scriptValue = script.scriptFunction.call(QScriptValue(), args);
    engine->releaseResourcesOfScriptObjects();
    propertiesRequestedInPrepareScript = engine->propertiesRequestedInScript();
    propertiesRequestedFromArtifactInPrepareScript = engine->propertiesRequestedFromArtifact();
    importedFilesUsedInPrepareScript = engine->importedFilesUsedInScript();
    depsRequestedInPrepareScript = engine->requestedDependencies();
    artifactsMapRequestedInPrepareScript = engine->requestedArtifacts();
    lastPrepareScriptExecutionTime = FileTime::currentTime();
    for (const ResolvedProduct * const p : engine->requestedExports()) {
        exportedModulesAccessedInPrepareScript.insert(std::make_pair(p->uniqueName(),
                                                                     p->exportedModule));
    }
    engine->clearRequestedProperties();
    if (Q_UNLIKELY(engine->hasErrorOrException(scriptValue)))
        throw engine->lastError(scriptValue, script.location());
    commands.clear();
    if (scriptValue.isArray()) {
        const int count = scriptValue.property(StringConstants::lengthProperty()).toInt32();
        for (qint32 i = 0; i < count; ++i) {
            QScriptValue item = scriptValue.property(i);
            if (item.isValid() && !item.isUndefined()) {
                const AbstractCommandPtr cmd
                        = createCommandFromScriptValue(item, script.location());
                if (cmd)
                    commands.addCommand(cmd);
            }
        }
    } else {
        const AbstractCommandPtr cmd = createCommandFromScriptValue(scriptValue,
                                                                    script.location());
        if (cmd)
            commands.addCommand(cmd);
    }
}

void Transformer::rescueChangeTrackingData(const TransformerConstPtr &other)
{
    if (!other)
        return;
    propertiesRequestedInPrepareScript = other->propertiesRequestedInPrepareScript;
    propertiesRequestedInCommands = other->propertiesRequestedInCommands;
    propertiesRequestedFromArtifactInPrepareScript
            = other->propertiesRequestedFromArtifactInPrepareScript;
    propertiesRequestedFromArtifactInCommands = other->propertiesRequestedFromArtifactInCommands;
    importedFilesUsedInPrepareScript = other->importedFilesUsedInPrepareScript;
    importedFilesUsedInCommands = other->importedFilesUsedInCommands;
    depsRequestedInPrepareScript = other->depsRequestedInPrepareScript;
    depsRequestedInCommands = other->depsRequestedInCommands;
    artifactsMapRequestedInPrepareScript = other->artifactsMapRequestedInPrepareScript;
    artifactsMapRequestedInCommands = other->artifactsMapRequestedInCommands;
    lastCommandExecutionTime = other->lastCommandExecutionTime;
    lastPrepareScriptExecutionTime = other->lastPrepareScriptExecutionTime;
    prepareScriptNeedsChangeTracking = other->prepareScriptNeedsChangeTracking;
    commandsNeedChangeTracking = other->commandsNeedChangeTracking;
    markedForRerun = other->markedForRerun;
    exportedModulesAccessedInPrepareScript = other->exportedModulesAccessedInPrepareScript;
    exportedModulesAccessedInCommands = other->exportedModulesAccessedInCommands;
}

Set<QString> Transformer::jobPools() const
{
    Set<QString> pools;
    for (const AbstractCommandPtr &c : commands.commands()) {
        if (!c->jobPool().isEmpty())
            pools.insert(c->jobPool());
    }
    return pools;
}

} // namespace Internal
} // namespace qbs
<commit_msg>Transformer: Fix wrong variable name<commit_after>/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qbs.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "transformer.h"

#include "artifact.h"
#include <jsextensions/moduleproperties.h>
#include <language/language.h>
#include <language/preparescriptobserver.h>
#include <language/scriptengine.h>
#include <logging/translator.h>
#include <tools/error.h>
#include <tools/fileinfo.h>
#include <tools/scripttools.h>
#include <tools/qbsassert.h>
#include <tools/stringconstants.h>
#include <tools/stlutils.h>

#include <QtCore/qdir.h>

#include <algorithm>

namespace qbs {
namespace Internal {

Transformer::Transformer() : alwaysRun(false)
{
}

Transformer::~Transformer()
{
}

static QScriptValue js_baseName(QScriptContext *ctx, QScriptEngine *engine,
                                const Artifact *artifact)
{
    Q_UNUSED(ctx);
    Q_UNUSED(engine);
    return {FileInfo::baseName(artifact->filePath())};
}

static QScriptValue js_completeBaseName(QScriptContext *ctx, QScriptEngine *engine,
                                        const Artifact *artifact)
{
    Q_UNUSED(ctx);
    Q_UNUSED(engine);
    return {FileInfo::completeBaseName(artifact->filePath())};
}

static QScriptValue js_baseDir(QScriptContext *ctx, QScriptEngine *engine,
                               const Artifact *artifact)
{
    Q_UNUSED(ctx);
    Q_UNUSED(engine);
    QString basedir;
    if (artifact->artifactType == Artifact::SourceFile) {
        QDir sourceDir(artifact->product->sourceDirectory);
        basedir = FileInfo::path(sourceDir.relativeFilePath(artifact->filePath()));
    } else {
        QDir buildDir(artifact->product->buildDirectory());
        basedir = FileInfo::path(buildDir.relativeFilePath(artifact->filePath()));
    }
    return basedir;
}

static QScriptValue js_children(QScriptContext *ctx, QScriptEngine *engine, const Artifact *artifact)
{
    Q_UNUSED(ctx);
    QScriptValue sv = engine->newArray();
    uint idx = 0;
    for (const Artifact *child : artifact->childArtifacts()) {
        sv.setProperty(idx++, Transformer::translateFileConfig(static_cast<ScriptEngine *>(engine),
                                                               child, QString()));
    }
    return sv;
}

static void setArtifactProperty(QScriptValue &obj, const QString &name,
        QScriptValue (*func)(QScriptContext *, QScriptEngine *, const Artifact *),
        const Artifact *artifact)
{
    obj.setProperty(name, static_cast<ScriptEngine *>(obj.engine())->newFunction(func, artifact),
                    QScriptValue::PropertyGetter);
}

QScriptValue Transformer::translateFileConfig(ScriptEngine *scriptEngine, const Artifact *artifact,
                                              const QString &defaultModuleName)
{
    QScriptValue obj = scriptEngine->newObject();
    attachPointerTo(obj, artifact);
    ModuleProperties::init(obj, artifact);
    obj.setProperty(StringConstants::fileNameProperty(), artifact->fileName());
    obj.setProperty(StringConstants::filePathProperty(), artifact->filePath());
    setArtifactProperty(obj, StringConstants::baseNameProperty(), js_baseName, artifact);
    setArtifactProperty(obj, StringConstants::completeBaseNameProperty(), js_completeBaseName,
                        artifact);
    setArtifactProperty(obj, QStringLiteral("baseDir"), js_baseDir, artifact);
    setArtifactProperty(obj, QStringLiteral("children"), js_children, artifact);
    const QStringList fileTags = sorted(artifact->fileTags().toStringList());
    scriptEngine->setObservedProperty(obj, StringConstants::fileTagsProperty(),
                                      scriptEngine->toScriptValue(fileTags));
    scriptEngine->observer()->addArtifactId(obj.objectId());
    if (!defaultModuleName.isEmpty())
        obj.setProperty(StringConstants::moduleNameProperty(), defaultModuleName);
    return obj;
}

static bool compareByFilePath(const Artifact *a1, const Artifact *a2)
{
    return a1->filePath() < a2->filePath();
}

QScriptValue Transformer::translateInOutputs(ScriptEngine *scriptEngine,
                                             const ArtifactSet &artifacts,
                                             const QString &defaultModuleName)
{
    using TagArtifactsMap = QMap<QString, QList<Artifact*>>;
    TagArtifactsMap tagArtifactsMap;
    for (Artifact *artifact : artifacts)
        for (const FileTag &fileTag : artifact->fileTags())
            tagArtifactsMap[fileTag.toString()].push_back(artifact);
    for (TagArtifactsMap::Iterator it = tagArtifactsMap.begin(); it != tagArtifactsMap.end(); ++it)
        std::sort(it.value().begin(), it.value().end(), compareByFilePath);

    QScriptValue jsTagFiles = scriptEngine->newObject();
    for (TagArtifactsMap::const_iterator tag = tagArtifactsMap.constBegin(); tag != tagArtifactsMap.constEnd(); ++tag) {
        const QList<Artifact*> &artifacts = tag.value();
        QScriptValue jsFileConfig = scriptEngine->newArray(artifacts.size());
        int i = 0;
        for (Artifact * const artifact : artifacts) {
            jsFileConfig.setProperty(i++, translateFileConfig(scriptEngine, artifact,
                                                              defaultModuleName));
        }
        jsTagFiles.setProperty(tag.key(), jsFileConfig);
    }

    return jsTagFiles;
}

ResolvedProductPtr Transformer::product() const
{
    if (outputs.empty())
        return {};
    return (*outputs.cbegin())->product.lock();
}

void Transformer::setupInputs(QScriptValue targetScriptValue, const ArtifactSet &inputs,
        const QString &defaultModuleName)
{
    const auto scriptEngine = static_cast<ScriptEngine *>(targetScriptValue.engine());
    QScriptValue scriptValue = translateInOutputs(scriptEngine, inputs, defaultModuleName);
    targetScriptValue.setProperty(StringConstants::inputsVar(), scriptValue);
    QScriptValue inputScriptValue;
    if (inputs.size() == 1) {
        Artifact *input = *inputs.cbegin();
        const FileTags &fileTags = input->fileTags();
        QBS_ASSERT(!fileTags.empty(), return);
        QScriptValue inputsForFileTag = scriptValue.property(fileTags.cbegin()->toString());
        inputScriptValue = inputsForFileTag.property(0);
    }
    targetScriptValue.setProperty(StringConstants::inputVar(), inputScriptValue);
}

void Transformer::setupInputs(QScriptValue targetScriptValue)
{
    setupInputs(targetScriptValue, inputs, rule->module->name);
}

void Transformer::setupOutputs(QScriptValue targetScriptValue)
{
    const auto scriptEngine = static_cast<ScriptEngine *>(targetScriptValue.engine());
    const QString &defaultModuleName = rule->module->name;
    QScriptValue scriptValue = translateInOutputs(scriptEngine, outputs, defaultModuleName);
    targetScriptValue.setProperty(StringConstants::outputsVar(), scriptValue);
    QScriptValue outputScriptValue;
    if (outputs.size() == 1) {
        Artifact *output = *outputs.cbegin();
        const FileTags &fileTags = output->fileTags();
        QBS_ASSERT(!fileTags.empty(), return);
        QScriptValue outputsForFileTag = scriptValue.property(fileTags.cbegin()->toString());
        outputScriptValue = outputsForFileTag.property(0);
    }
    targetScriptValue.setProperty(StringConstants::outputVar(), outputScriptValue);
}

void Transformer::setupExplicitlyDependsOn(QScriptValue targetScriptValue)
{
    const auto scriptEngine = static_cast<ScriptEngine *>(targetScriptValue.engine());
    QScriptValue scriptValue = translateInOutputs(scriptEngine, explicitlyDependsOn,
                                                  rule->module->name);
    targetScriptValue.setProperty(StringConstants::explicitlyDependsOnVar(), scriptValue);
}

AbstractCommandPtr Transformer::createCommandFromScriptValue(const QScriptValue &scriptValue,
                                                             const CodeLocation &codeLocation)
{
    AbstractCommandPtr cmdBase;
    if (scriptValue.isUndefined() || !scriptValue.isValid())
        return cmdBase;
    QString className = scriptValue.property(StringConstants::classNameProperty()).toString();
    if (className == StringConstants::commandType())
        cmdBase = ProcessCommand::create();
    else if (className == StringConstants::javaScriptCommandType())
        cmdBase = JavaScriptCommand::create();
    if (cmdBase)
        cmdBase->fillFromScriptValue(&scriptValue, codeLocation);
    if (className == StringConstants::commandType()) {
        auto procCmd = static_cast<ProcessCommand *>(cmdBase.get());
        procCmd->clearRelevantEnvValues();
        for (const QString &key : procCmd->relevantEnvVars())
            procCmd->addRelevantEnvValue(key, product()->buildEnvironment.value(key));
    }
    return cmdBase;
}

void Transformer::createCommands(ScriptEngine *engine, const PrivateScriptFunction &script,
                                 const QScriptValueList &args)
{
    if (!script.scriptFunction.isValid() || script.scriptFunction.engine() != engine) {
        script.scriptFunction = engine->evaluate(script.sourceCode(),
                                                  script.location().filePath(),
                                                  script.location().line());
        if (Q_UNLIKELY(!script.scriptFunction.isFunction()))
            throw ErrorInfo(Tr::tr("Invalid prepare script."), script.location());
    }

    QScriptValue scriptValue = script.scriptFunction.call(QScriptValue(), args);
    engine->releaseResourcesOfScriptObjects();
    propertiesRequestedInPrepareScript = engine->propertiesRequestedInScript();
    propertiesRequestedFromArtifactInPrepareScript = engine->propertiesRequestedFromArtifact();
    importedFilesUsedInPrepareScript = engine->importedFilesUsedInScript();
    depsRequestedInPrepareScript = engine->requestedDependencies();
    artifactsMapRequestedInPrepareScript = engine->requestedArtifacts();
    lastPrepareScriptExecutionTime = FileTime::currentTime();
    for (const ResolvedProduct * const p : engine->requestedExports()) {
        exportedModulesAccessedInPrepareScript.insert(std::make_pair(p->uniqueName(),
                                                                     p->exportedModule));
    }
    engine->clearRequestedProperties();
    if (Q_UNLIKELY(engine->hasErrorOrException(scriptValue)))
        throw engine->lastError(scriptValue, script.location());
    commands.clear();
    if (scriptValue.isArray()) {
        const int count = scriptValue.property(StringConstants::lengthProperty()).toInt32();
        for (qint32 i = 0; i < count; ++i) {
            QScriptValue item = scriptValue.property(i);
            if (item.isValid() && !item.isUndefined()) {
                const AbstractCommandPtr cmd
                        = createCommandFromScriptValue(item, script.location());
                if (cmd)
                    commands.addCommand(cmd);
            }
        }
    } else {
        const AbstractCommandPtr cmd = createCommandFromScriptValue(scriptValue,
                                                                    script.location());
        if (cmd)
            commands.addCommand(cmd);
    }
}

void Transformer::rescueChangeTrackingData(const TransformerConstPtr &other)
{
    if (!other)
        return;
    propertiesRequestedInPrepareScript = other->propertiesRequestedInPrepareScript;
    propertiesRequestedInCommands = other->propertiesRequestedInCommands;
    propertiesRequestedFromArtifactInPrepareScript
            = other->propertiesRequestedFromArtifactInPrepareScript;
    propertiesRequestedFromArtifactInCommands = other->propertiesRequestedFromArtifactInCommands;
    importedFilesUsedInPrepareScript = other->importedFilesUsedInPrepareScript;
    importedFilesUsedInCommands = other->importedFilesUsedInCommands;
    depsRequestedInPrepareScript = other->depsRequestedInPrepareScript;
    depsRequestedInCommands = other->depsRequestedInCommands;
    artifactsMapRequestedInPrepareScript = other->artifactsMapRequestedInPrepareScript;
    artifactsMapRequestedInCommands = other->artifactsMapRequestedInCommands;
    lastCommandExecutionTime = other->lastCommandExecutionTime;
    lastPrepareScriptExecutionTime = other->lastPrepareScriptExecutionTime;
    prepareScriptNeedsChangeTracking = other->prepareScriptNeedsChangeTracking;
    commandsNeedChangeTracking = other->commandsNeedChangeTracking;
    markedForRerun = other->markedForRerun;
    exportedModulesAccessedInPrepareScript = other->exportedModulesAccessedInPrepareScript;
    exportedModulesAccessedInCommands = other->exportedModulesAccessedInCommands;
}

Set<QString> Transformer::jobPools() const
{
    Set<QString> pools;
    for (const AbstractCommandPtr &c : commands.commands()) {
        if (!c->jobPool().isEmpty())
            pools.insert(c->jobPool());
    }
    return pools;
}

} // namespace Internal
} // namespace qbs
<|endoftext|>