<commit_before>//
// This file is part of the Marble Virtual 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      Torsten Rahn     <tackat@kde.org>
// Copyright 2011      Bernhard Beschow <bbeschow@cs.tu-berlin.de>
//

#include "SphericalScanlineTextureMapper.h"

#include <cmath>

#include <QtCore/QRunnable>

#include "MarbleGlobal.h"
#include "GeoPainter.h"
#include "GeoDataPolygon.h"
#include "GeoDataDocument.h"
#include "MarbleDebug.h"
#include "Quaternion.h"
#include "ScanlineTextureMapperContext.h"
#include "StackedTileLoader.h"
#include "StackedTile.h"
#include "TextureColorizer.h"
#include "ViewportParams.h"
#include "MathHelper.h"

using namespace Marble;

class SphericalScanlineTextureMapper::RenderJob : public QRunnable
{
public:
    RenderJob( StackedTileLoader *tileLoader, int tileLevel, QImage *canvasImage, const ViewportParams *viewport, MapQuality mapQuality, int yTop, int yBottom );

    virtual void run();

private:
    StackedTileLoader *const m_tileLoader;
    const int m_tileLevel;
    QImage *const m_canvasImage;
    const ViewportParams *const m_viewport;
    const MapQuality m_mapQuality;
    int const m_yTop;
    int const m_yBottom;
};

SphericalScanlineTextureMapper::RenderJob::RenderJob( StackedTileLoader *tileLoader, int tileLevel, QImage *canvasImage, const ViewportParams *viewport, MapQuality mapQuality, int yTop, int yBottom )
    : m_tileLoader( tileLoader ),
      m_tileLevel( tileLevel ),
      m_canvasImage( canvasImage ),
      m_viewport( viewport ),
      m_mapQuality( mapQuality ),
      m_yTop( yTop ),
      m_yBottom( yBottom )
{
}

SphericalScanlineTextureMapper::SphericalScanlineTextureMapper( StackedTileLoader *tileLoader )
    : TextureMapperInterface()
    , m_tileLoader( tileLoader )
    , m_radius( 0 )
    , m_threadPool()
{
}

void SphericalScanlineTextureMapper::mapTexture( GeoPainter *painter,
                                                 const ViewportParams *viewport,
                                                 int tileZoomLevel,
                                                 const QRect &dirtyRect,
                                                 TextureColorizer *texColorizer )
{
    if ( m_canvasImage.size() != viewport->size() || m_radius != viewport->radius() ) {
        const QImage::Format optimalFormat = ScanlineTextureMapperContext::optimalCanvasImageFormat( viewport );

        if ( m_canvasImage.size() != viewport->size() || m_canvasImage.format() != optimalFormat ) {
            m_canvasImage = QImage( viewport->size(), optimalFormat );
        }

        if ( !viewport->mapCoversViewport() ) {
            m_canvasImage.fill( 0 );
        }

        m_radius = viewport->radius();
        m_repaintNeeded = true;
    }

    if ( m_repaintNeeded ) {
        mapTexture( viewport, tileZoomLevel, painter->mapQuality() );

        if ( texColorizer ) {
            texColorizer->colorize( &m_canvasImage, viewport, painter->mapQuality() );
        }

        m_repaintNeeded = false;
    }

    const int radius = viewport->radius();

    QRect rect( viewport->width() / 2 - radius, viewport->height() / 2 - radius,
                2 * radius, 2 * radius);
    rect = rect.intersect( dirtyRect );
    painter->drawImage( rect, m_canvasImage, rect );
}

void SphericalScanlineTextureMapper::mapTexture( const ViewportParams *viewport, int tileZoomLevel, MapQuality mapQuality )
{
    // Reset backend
    m_tileLoader->resetTilehash();

    // Initialize needed constants:

    const int imageHeight = m_canvasImage.height();
    const qint64  radius      = viewport->radius();

    // Calculate the actual y-range of the map on the screen
    const int skip = ( mapQuality == LowQuality ) ? 1 : 0;
    const int yTop = ( ( imageHeight / 2 - radius < 0 )
                       ? 0 : imageHeight / 2 - radius );
    const int yBottom = ( (yTop == 0)
                          ? imageHeight - skip
                          : yTop + radius + radius - skip );

    const int numThreads = m_threadPool.maxThreadCount();
    const int yStep = ( yBottom - yTop ) / numThreads;
    for ( int i = 0; i < numThreads; ++i ) {
        const int yStart = yTop +  i      * yStep;
        const int yEnd   = yTop + (i + 1) * yStep;
        QRunnable *const job = new RenderJob( m_tileLoader, tileZoomLevel, &m_canvasImage, viewport, mapQuality, yStart, yEnd );
        m_threadPool.start( job );
    }

    m_threadPool.waitForDone();

    m_tileLoader->cleanupTilehash();
}

void SphericalScanlineTextureMapper::RenderJob::run()
{
    const int imageHeight = m_canvasImage->height();
    const int imageWidth  = m_canvasImage->width();
    const qint64  radius  = m_viewport->radius();
    const qreal  inverseRadius = 1.0 / (qreal)(radius);

    const bool interlaced   = ( m_mapQuality == LowQuality );
    const bool highQuality  = ( m_mapQuality == HighQuality
                             || m_mapQuality == PrintQuality );
    const bool printQuality = ( m_mapQuality == PrintQuality );

    // Evaluate the degree of interpolation
    const int n = ScanlineTextureMapperContext::interpolationStep( m_viewport, m_mapQuality );

    // Calculate north pole position to decrease pole distortion later on
    Quaternion northPole = Quaternion::fromSpherical( 0.0, M_PI * 0.5 );
    northPole.rotateAroundAxis( m_viewport->planetAxis().inverse() );
    const int northPoleX = imageWidth / 2 + (int)( radius * northPole.v[Q_X] );
    const int northPoleY = imageHeight / 2 - (int)( radius * northPole.v[Q_Y] );

    // Calculate axis matrix to represent the planet's rotation.
    matrix  planetAxisMatrix;
    m_viewport->planetAxis().toMatrix( planetAxisMatrix );

    // initialize needed variables that are modified during texture mapping:

    ScanlineTextureMapperContext context( m_tileLoader, m_tileLevel );
    qreal  lon = 0.0;
    qreal  lat = 0.0;

    // Scanline based algorithm to texture map a sphere
    for ( int y = m_yTop; y < m_yBottom ; ++y ) {

        // Evaluate coordinates for the 3D position vector of the current pixel
        const qreal qy = inverseRadius * (qreal)( imageHeight / 2 - y );
        const qreal qr = 1.0 - qy * qy;

        // rx is the radius component in x direction
        const int rx = (int)sqrt( (qreal)( radius * radius
                                      - ( ( y - imageHeight / 2 )
                                          * ( y - imageHeight / 2 ) ) ) );

        // Calculate the actual x-range of the map within the current scanline.
        //
        // If the circular border of the earth disk is still visible then xLeft
        // equals the scanline position of the most left pixel that gets covered
        // by the earth disk. In terms of math this equals the half image width minus
        // the radius component on the current scanline in x direction ("rx").
        //
        // If the zoom factor is high enough then the whole screen gets covered
        // by the earth and the border of the earth disk isn't visible anymore.
        // In that situation xLeft equals zero.
        // For xRight the situation is similar.

        const int xLeft  = ( ( imageWidth / 2 - rx > 0 )
                             ? imageWidth / 2 - rx : 0 );
        const int xRight = ( ( imageWidth / 2 - rx > 0 )
                             ? xLeft + rx + rx : imageWidth );

        QRgb * scanLine = (QRgb*)( m_canvasImage->scanLine( y ) ) + xLeft;

        const int xIpLeft  = ( imageWidth / 2 - rx > 0 ) ? n * (int)( xLeft / n + 1 )
                                                         : 1;
        const int xIpRight = ( imageWidth / 2 - rx > 0 ) ? n * (int)( xRight / n - 1 )
                                                         : n * (int)( xRight / n - 1 ) + 1;

        // Decrease pole distortion due to linear approximation ( y-axis )
        bool crossingPoleArea = false;
        if ( northPole.v[Q_Z] > 0
             && northPoleY - ( n * 0.75 ) <= y
             && northPoleY + ( n * 0.75 ) >= y )
        {
            crossingPoleArea = true;
        }

        int ncount = 0;

        for ( int x = xLeft; x < xRight; ++x ) {
            // Prepare for interpolation

            const int leftInterval = xIpLeft + ncount * n;

            bool interpolate = false;
            if ( x >= xIpLeft && x <= xIpRight ) {

                // Decrease pole distortion due to linear approximation ( x-axis )
//                mDebug() << QString("NorthPole X: %1, LeftInterval: %2").arg( northPoleX ).arg( leftInterval );
                if ( crossingPoleArea
                     && northPoleX >= leftInterval + n
                     && northPoleX < leftInterval + 2 * n
                     && x < leftInterval + 3 * n )
                {
                    interpolate = false;
                }
                else {
                    x += n - 1;
                    interpolate = !printQuality;
                    ++ncount;
                }
            }
            else
                interpolate = false;

            // Evaluate more coordinates for the 3D position vector of
            // the current pixel.
            const qreal qx = (qreal)( x - imageWidth / 2 ) * inverseRadius;
            const qreal qr2z = qr - qx * qx;
            const qreal qz = ( qr2z > 0.0 ) ? sqrt( qr2z ) : 0.0;

            // Create Quaternion from vector coordinates and rotate it
            // around globe axis
            Quaternion qpos( 0.0, qx, qy, qz );
            qpos.rotateAroundAxis( planetAxisMatrix );

            qpos.getSpherical( lon, lat );
//            mDebug() << QString("lon: %1 lat: %2").arg(lon).arg(lat);
            // Approx for n-1 out of n pixels within the boundary of
            // xIpLeft to xIpRight

            if ( interpolate ) {
                if (highQuality)
                    context.pixelValueApproxF( lon, lat, scanLine, n );
                else
                    context.pixelValueApprox( lon, lat, scanLine, n );

                scanLine += ( n - 1 );
            }

//          Comment out the pixelValue line and run Marble if you want
//          to understand the interpolation:

//          Uncomment the crossingPoleArea line to check precise
//          rendering around north pole:

//            if ( !crossingPoleArea )
            if ( x < imageWidth ) {
                if ( highQuality )
                    context.pixelValueF( lon, lat, scanLine );
                else
                    context.pixelValue( lon, lat, scanLine );
            }

            ++scanLine;
        }

        // copy scanline to improve performance
        if ( interlaced && y + 1 < m_yBottom ) {

            const int pixelByteSize = m_canvasImage->bytesPerLine() / imageWidth;

            memcpy( m_canvasImage->scanLine( y + 1 ) + xLeft * pixelByteSize,
                    m_canvasImage->scanLine( y ) + xLeft * pixelByteSize,
                    ( xRight - xLeft ) * pixelByteSize );
            ++y;
        }
    }
}
<commit_msg>align ternary operators like others<commit_after>//
// This file is part of the Marble Virtual 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      Torsten Rahn     <tackat@kde.org>
// Copyright 2011      Bernhard Beschow <bbeschow@cs.tu-berlin.de>
//

#include "SphericalScanlineTextureMapper.h"

#include <cmath>

#include <QtCore/QRunnable>

#include "MarbleGlobal.h"
#include "GeoPainter.h"
#include "GeoDataPolygon.h"
#include "GeoDataDocument.h"
#include "MarbleDebug.h"
#include "Quaternion.h"
#include "ScanlineTextureMapperContext.h"
#include "StackedTileLoader.h"
#include "StackedTile.h"
#include "TextureColorizer.h"
#include "ViewportParams.h"
#include "MathHelper.h"

using namespace Marble;

class SphericalScanlineTextureMapper::RenderJob : public QRunnable
{
public:
    RenderJob( StackedTileLoader *tileLoader, int tileLevel, QImage *canvasImage, const ViewportParams *viewport, MapQuality mapQuality, int yTop, int yBottom );

    virtual void run();

private:
    StackedTileLoader *const m_tileLoader;
    const int m_tileLevel;
    QImage *const m_canvasImage;
    const ViewportParams *const m_viewport;
    const MapQuality m_mapQuality;
    int const m_yTop;
    int const m_yBottom;
};

SphericalScanlineTextureMapper::RenderJob::RenderJob( StackedTileLoader *tileLoader, int tileLevel, QImage *canvasImage, const ViewportParams *viewport, MapQuality mapQuality, int yTop, int yBottom )
    : m_tileLoader( tileLoader ),
      m_tileLevel( tileLevel ),
      m_canvasImage( canvasImage ),
      m_viewport( viewport ),
      m_mapQuality( mapQuality ),
      m_yTop( yTop ),
      m_yBottom( yBottom )
{
}

SphericalScanlineTextureMapper::SphericalScanlineTextureMapper( StackedTileLoader *tileLoader )
    : TextureMapperInterface()
    , m_tileLoader( tileLoader )
    , m_radius( 0 )
    , m_threadPool()
{
}

void SphericalScanlineTextureMapper::mapTexture( GeoPainter *painter,
                                                 const ViewportParams *viewport,
                                                 int tileZoomLevel,
                                                 const QRect &dirtyRect,
                                                 TextureColorizer *texColorizer )
{
    if ( m_canvasImage.size() != viewport->size() || m_radius != viewport->radius() ) {
        const QImage::Format optimalFormat = ScanlineTextureMapperContext::optimalCanvasImageFormat( viewport );

        if ( m_canvasImage.size() != viewport->size() || m_canvasImage.format() != optimalFormat ) {
            m_canvasImage = QImage( viewport->size(), optimalFormat );
        }

        if ( !viewport->mapCoversViewport() ) {
            m_canvasImage.fill( 0 );
        }

        m_radius = viewport->radius();
        m_repaintNeeded = true;
    }

    if ( m_repaintNeeded ) {
        mapTexture( viewport, tileZoomLevel, painter->mapQuality() );

        if ( texColorizer ) {
            texColorizer->colorize( &m_canvasImage, viewport, painter->mapQuality() );
        }

        m_repaintNeeded = false;
    }

    const int radius = viewport->radius();

    QRect rect( viewport->width() / 2 - radius, viewport->height() / 2 - radius,
                2 * radius, 2 * radius);
    rect = rect.intersect( dirtyRect );
    painter->drawImage( rect, m_canvasImage, rect );
}

void SphericalScanlineTextureMapper::mapTexture( const ViewportParams *viewport, int tileZoomLevel, MapQuality mapQuality )
{
    // Reset backend
    m_tileLoader->resetTilehash();

    // Initialize needed constants:

    const int imageHeight = m_canvasImage.height();
    const qint64  radius      = viewport->radius();

    // Calculate the actual y-range of the map on the screen
    const int skip = ( mapQuality == LowQuality ) ? 1
                                                  : 0;
    const int yTop = ( imageHeight / 2 - radius >= 0 ) ? imageHeight / 2 - radius
                                                       : 0;
    const int yBottom = ( yTop == 0 ) ? imageHeight - skip
                                      : yTop + radius + radius - skip;

    const int numThreads = m_threadPool.maxThreadCount();
    const int yStep = ( yBottom - yTop ) / numThreads;
    for ( int i = 0; i < numThreads; ++i ) {
        const int yStart = yTop +  i      * yStep;
        const int yEnd   = yTop + (i + 1) * yStep;
        QRunnable *const job = new RenderJob( m_tileLoader, tileZoomLevel, &m_canvasImage, viewport, mapQuality, yStart, yEnd );
        m_threadPool.start( job );
    }

    m_threadPool.waitForDone();

    m_tileLoader->cleanupTilehash();
}

void SphericalScanlineTextureMapper::RenderJob::run()
{
    const int imageHeight = m_canvasImage->height();
    const int imageWidth  = m_canvasImage->width();
    const qint64  radius  = m_viewport->radius();
    const qreal  inverseRadius = 1.0 / (qreal)(radius);

    const bool interlaced   = ( m_mapQuality == LowQuality );
    const bool highQuality  = ( m_mapQuality == HighQuality
                             || m_mapQuality == PrintQuality );
    const bool printQuality = ( m_mapQuality == PrintQuality );

    // Evaluate the degree of interpolation
    const int n = ScanlineTextureMapperContext::interpolationStep( m_viewport, m_mapQuality );

    // Calculate north pole position to decrease pole distortion later on
    Quaternion northPole = Quaternion::fromSpherical( 0.0, M_PI * 0.5 );
    northPole.rotateAroundAxis( m_viewport->planetAxis().inverse() );
    const int northPoleX = imageWidth / 2 + (int)( radius * northPole.v[Q_X] );
    const int northPoleY = imageHeight / 2 - (int)( radius * northPole.v[Q_Y] );

    // Calculate axis matrix to represent the planet's rotation.
    matrix  planetAxisMatrix;
    m_viewport->planetAxis().toMatrix( planetAxisMatrix );

    // initialize needed variables that are modified during texture mapping:

    ScanlineTextureMapperContext context( m_tileLoader, m_tileLevel );
    qreal  lon = 0.0;
    qreal  lat = 0.0;

    // Scanline based algorithm to texture map a sphere
    for ( int y = m_yTop; y < m_yBottom ; ++y ) {

        // Evaluate coordinates for the 3D position vector of the current pixel
        const qreal qy = inverseRadius * (qreal)( imageHeight / 2 - y );
        const qreal qr = 1.0 - qy * qy;

        // rx is the radius component in x direction
        const int rx = (int)sqrt( (qreal)( radius * radius
                                      - ( ( y - imageHeight / 2 )
                                          * ( y - imageHeight / 2 ) ) ) );

        // Calculate the actual x-range of the map within the current scanline.
        //
        // If the circular border of the earth disk is still visible then xLeft
        // equals the scanline position of the most left pixel that gets covered
        // by the earth disk. In terms of math this equals the half image width minus
        // the radius component on the current scanline in x direction ("rx").
        //
        // If the zoom factor is high enough then the whole screen gets covered
        // by the earth and the border of the earth disk isn't visible anymore.
        // In that situation xLeft equals zero.
        // For xRight the situation is similar.

        const int xLeft  = ( imageWidth / 2 - rx > 0 ) ? imageWidth / 2 - rx
                                                       : 0;
        const int xRight = ( imageWidth / 2 - rx > 0 ) ? xLeft + rx + rx
                                                       : imageWidth;

        QRgb * scanLine = (QRgb*)( m_canvasImage->scanLine( y ) ) + xLeft;

        const int xIpLeft  = ( imageWidth / 2 - rx > 0 ) ? n * (int)( xLeft / n + 1 )
                                                         : 1;
        const int xIpRight = ( imageWidth / 2 - rx > 0 ) ? n * (int)( xRight / n - 1 )
                                                         : n * (int)( xRight / n - 1 ) + 1;

        // Decrease pole distortion due to linear approximation ( y-axis )
        bool crossingPoleArea = false;
        if ( northPole.v[Q_Z] > 0
             && northPoleY - ( n * 0.75 ) <= y
             && northPoleY + ( n * 0.75 ) >= y )
        {
            crossingPoleArea = true;
        }

        int ncount = 0;

        for ( int x = xLeft; x < xRight; ++x ) {
            // Prepare for interpolation

            const int leftInterval = xIpLeft + ncount * n;

            bool interpolate = false;
            if ( x >= xIpLeft && x <= xIpRight ) {

                // Decrease pole distortion due to linear approximation ( x-axis )
//                mDebug() << QString("NorthPole X: %1, LeftInterval: %2").arg( northPoleX ).arg( leftInterval );
                if ( crossingPoleArea
                     && northPoleX >= leftInterval + n
                     && northPoleX < leftInterval + 2 * n
                     && x < leftInterval + 3 * n )
                {
                    interpolate = false;
                }
                else {
                    x += n - 1;
                    interpolate = !printQuality;
                    ++ncount;
                }
            }
            else
                interpolate = false;

            // Evaluate more coordinates for the 3D position vector of
            // the current pixel.
            const qreal qx = (qreal)( x - imageWidth / 2 ) * inverseRadius;
            const qreal qr2z = qr - qx * qx;
            const qreal qz = ( qr2z > 0.0 ) ? sqrt( qr2z ) : 0.0;

            // Create Quaternion from vector coordinates and rotate it
            // around globe axis
            Quaternion qpos( 0.0, qx, qy, qz );
            qpos.rotateAroundAxis( planetAxisMatrix );

            qpos.getSpherical( lon, lat );
//            mDebug() << QString("lon: %1 lat: %2").arg(lon).arg(lat);
            // Approx for n-1 out of n pixels within the boundary of
            // xIpLeft to xIpRight

            if ( interpolate ) {
                if (highQuality)
                    context.pixelValueApproxF( lon, lat, scanLine, n );
                else
                    context.pixelValueApprox( lon, lat, scanLine, n );

                scanLine += ( n - 1 );
            }

//          Comment out the pixelValue line and run Marble if you want
//          to understand the interpolation:

//          Uncomment the crossingPoleArea line to check precise
//          rendering around north pole:

//            if ( !crossingPoleArea )
            if ( x < imageWidth ) {
                if ( highQuality )
                    context.pixelValueF( lon, lat, scanLine );
                else
                    context.pixelValue( lon, lat, scanLine );
            }

            ++scanLine;
        }

        // copy scanline to improve performance
        if ( interlaced && y + 1 < m_yBottom ) {

            const int pixelByteSize = m_canvasImage->bytesPerLine() / imageWidth;

            memcpy( m_canvasImage->scanLine( y + 1 ) + xLeft * pixelByteSize,
                    m_canvasImage->scanLine( y ) + xLeft * pixelByteSize,
                    ( xRight - xLeft ) * pixelByteSize );
            ++y;
        }
    }
}
<|endoftext|>


<commit_before>/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
**
** This file is part of Qbs.
**
** 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 http://www.qt.io/terms-conditions. For further information
** use the contact form at http://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 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, The Qt Company gives you certain additional
** rights.  These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/

#include "buildgraphlocker.h"

#include "error.h"
#include "hostosinfo.h"
#include "processutils.h"
#include "progressobserver.h"
#include "version.h"

#include <logging/translator.h>

#include <QDir>
#include <QDirIterator>
#include <QFileInfo>
#include <QString>

namespace qbs {
namespace Internal {

static bool hasQtBug45497()
{
    return Version::fromString(QLatin1String(qVersion())) < Version(5, 5, 1);
}

static bool hasQtBug53392()
{
    if (!HostOsInfo::isWindowsHost())
        return false;
    const Version qtVersion = Version::fromString(QLatin1String(qVersion()));
    // The fix is included in 5.6.2 and 5.7.1, but not in 5.7.0.
    return qtVersion == Version(5, 7, 0) || qtVersion < Version(5, 6, 2);
}

static void tryCreateBuildDirectory(const QString &buildDir, const QString &buildGraphFilePath)
{
    if (!QDir::root().mkpath(buildDir)) {
        throw ErrorInfo(Tr::tr("Cannot lock build graph file '%1': Failed to create directory.")
                        .arg(buildGraphFilePath));
    }
}

BuildGraphLocker::BuildGraphLocker(const QString &buildGraphFilePath, const Logger &logger,
                                   bool waitIndefinitely, ProgressObserver *observer)
    : m_lockFile(buildGraphFilePath + QLatin1String(".lock"))
    , m_logger(logger)
{
    const QString buildDir = QFileInfo(buildGraphFilePath).absolutePath();
    rememberCreatedDirectories(buildDir);
    if (waitIndefinitely)
        m_logger.qbsDebug() << "Waiting to acquire lock file...";
    m_lockFile.setStaleLockTime(0);
    int attemptsToGetInfo = 0;
    do {
        if (observer->canceled())
            break;
        tryCreateBuildDirectory(buildDir, buildGraphFilePath);
        if (m_lockFile.tryLock(250))
            return;
        switch (m_lockFile.error()) {
        case QLockFile::LockFailedError: {
            if (waitIndefinitely)
                continue;
            qint64 pid;
            QString hostName;
            QString appName;
            if (m_lockFile.getLockInfo(&pid, &hostName, &appName)) {
                if ((!hasQtBug45497() && !hasQtBug53392()) || appName == processNameByPid(pid)) {
                    throw ErrorInfo(Tr::tr("Cannot lock build graph file '%1': "
                                           "Already locked by '%2' (PID %3).")
                                    .arg(buildGraphFilePath, appName).arg(pid));
                }

                // The process id was reused by some other process.
                m_logger.qbsInfo() << Tr::tr("Removing stale lock file.");
                m_lockFile.removeStaleLockFile();
            }
            break;
        }
        case QLockFile::PermissionError:
            throw ErrorInfo(Tr::tr("Cannot lock build graph file '%1': Permission denied.")
                            .arg(buildGraphFilePath));
        case QLockFile::UnknownError:
        case QLockFile::NoError:
            throw ErrorInfo(Tr::tr("Cannot lock build graph file '%1' (reason unknown).")
                            .arg(buildGraphFilePath));
        }
    } while (++attemptsToGetInfo < 10);

    // This very unlikely case arises if tryLock() repeatedly returns LockFailedError
    // with the subsequent getLockInfo() failing as well.
    throw ErrorInfo(Tr::tr("Cannot lock build graph file '%1' (reason unknown).")
                    .arg(buildGraphFilePath));
}

BuildGraphLocker::~BuildGraphLocker()
{
    m_lockFile.unlock();
    removeEmptyCreatedDirectories();
}

void BuildGraphLocker::rememberCreatedDirectories(const QString &buildDir)
{
    QString parentDir = buildDir;
    while (!QFileInfo::exists(parentDir)) {
        m_createdParentDirs.enqueue(parentDir);
        parentDir = QDir::cleanPath(parentDir + QLatin1String("/.."));
    }
}

void BuildGraphLocker::removeEmptyCreatedDirectories()
{
    QDir root = QDir::root();
    while (!m_createdParentDirs.isEmpty()) {
        const QString parentDir = m_createdParentDirs.dequeue();
        QDirIterator it(parentDir, QDir::AllEntries | QDir::NoDotAndDotDot | QDir::System,
                        QDirIterator::Subdirectories);
        if (it.hasNext())
            break;
        if (!root.rmdir(parentDir)) {
            m_logger.printWarning(ErrorInfo(Tr::tr("Failed to remove empty directory '%1'.")
                                            .arg(parentDir)));
        }
    }
}

} // namespace Internal
} // namespace qbs
<commit_msg>Fix bug causing --wait-lock to fail if more than 10 tries are attempted<commit_after>/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
**
** This file is part of Qbs.
**
** 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 http://www.qt.io/terms-conditions. For further information
** use the contact form at http://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 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, The Qt Company gives you certain additional
** rights.  These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/

#include "buildgraphlocker.h"

#include "error.h"
#include "hostosinfo.h"
#include "processutils.h"
#include "progressobserver.h"
#include "version.h"

#include <logging/translator.h>

#include <QDir>
#include <QDirIterator>
#include <QFileInfo>
#include <QString>

namespace qbs {
namespace Internal {

static bool hasQtBug45497()
{
    return Version::fromString(QLatin1String(qVersion())) < Version(5, 5, 1);
}

static bool hasQtBug53392()
{
    if (!HostOsInfo::isWindowsHost())
        return false;
    const Version qtVersion = Version::fromString(QLatin1String(qVersion()));
    // The fix is included in 5.6.2 and 5.7.1, but not in 5.7.0.
    return qtVersion == Version(5, 7, 0) || qtVersion < Version(5, 6, 2);
}

static void tryCreateBuildDirectory(const QString &buildDir, const QString &buildGraphFilePath)
{
    if (!QDir::root().mkpath(buildDir)) {
        throw ErrorInfo(Tr::tr("Cannot lock build graph file '%1': Failed to create directory.")
                        .arg(buildGraphFilePath));
    }
}

BuildGraphLocker::BuildGraphLocker(const QString &buildGraphFilePath, const Logger &logger,
                                   bool waitIndefinitely, ProgressObserver *observer)
    : m_lockFile(buildGraphFilePath + QLatin1String(".lock"))
    , m_logger(logger)
{
    const QString buildDir = QFileInfo(buildGraphFilePath).absolutePath();
    rememberCreatedDirectories(buildDir);
    if (waitIndefinitely)
        m_logger.qbsDebug() << "Waiting to acquire lock file...";
    m_lockFile.setStaleLockTime(0);
    int attemptsToGetInfo = 0;
    do {
        if (observer->canceled())
            break;
        tryCreateBuildDirectory(buildDir, buildGraphFilePath);
        if (m_lockFile.tryLock(250))
            return;
        switch (m_lockFile.error()) {
        case QLockFile::LockFailedError: {
            if (waitIndefinitely)
                continue;
            qint64 pid;
            QString hostName;
            QString appName;
            if (m_lockFile.getLockInfo(&pid, &hostName, &appName)) {
                if ((!hasQtBug45497() && !hasQtBug53392()) || appName == processNameByPid(pid)) {
                    throw ErrorInfo(Tr::tr("Cannot lock build graph file '%1': "
                                           "Already locked by '%2' (PID %3).")
                                    .arg(buildGraphFilePath, appName).arg(pid));
                }

                // The process id was reused by some other process.
                m_logger.qbsInfo() << Tr::tr("Removing stale lock file.");
                m_lockFile.removeStaleLockFile();
            }
            break;
        }
        case QLockFile::PermissionError:
            throw ErrorInfo(Tr::tr("Cannot lock build graph file '%1': Permission denied.")
                            .arg(buildGraphFilePath));
        case QLockFile::UnknownError:
        case QLockFile::NoError:
            throw ErrorInfo(Tr::tr("Cannot lock build graph file '%1' (reason unknown).")
                            .arg(buildGraphFilePath));
        }
    } while (++attemptsToGetInfo < 10 || waitIndefinitely);

    // This very unlikely case arises if tryLock() repeatedly returns LockFailedError
    // with the subsequent getLockInfo() failing as well.
    throw ErrorInfo(Tr::tr("Cannot lock build graph file '%1' (reason unknown).")
                    .arg(buildGraphFilePath));
}

BuildGraphLocker::~BuildGraphLocker()
{
    m_lockFile.unlock();
    removeEmptyCreatedDirectories();
}

void BuildGraphLocker::rememberCreatedDirectories(const QString &buildDir)
{
    QString parentDir = buildDir;
    while (!QFileInfo::exists(parentDir)) {
        m_createdParentDirs.enqueue(parentDir);
        parentDir = QDir::cleanPath(parentDir + QLatin1String("/.."));
    }
}

void BuildGraphLocker::removeEmptyCreatedDirectories()
{
    QDir root = QDir::root();
    while (!m_createdParentDirs.isEmpty()) {
        const QString parentDir = m_createdParentDirs.dequeue();
        QDirIterator it(parentDir, QDir::AllEntries | QDir::NoDotAndDotDot | QDir::System,
                        QDirIterator::Subdirectories);
        if (it.hasNext())
            break;
        if (!root.rmdir(parentDir)) {
            m_logger.printWarning(ErrorInfo(Tr::tr("Failed to remove empty directory '%1'.")
                                            .arg(parentDir)));
        }
    }
}

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


<commit_before>#include "downloader/file-downloader.h"
#include <QFile>
#include "functions.h"
#include "logger.h"
#include "network/network-reply.h"

#define WRITE_BUFFER_SIZE (200 * 1024)

FileDownloader::FileDownloader(bool allowHtmlResponses, QObject *parent)
	: QObject(parent), m_allowHtmlResponses(allowHtmlResponses), m_reply(nullptr), m_readSize(0), m_writeError(false)
{}

bool FileDownloader::start(NetworkReply *reply, const QString &path)
{
	m_file.setFileName(path);
	const bool ok = m_file.open(QFile::WriteOnly | QFile::Truncate);

	m_readSize = 0;
	m_writeError = false;
	m_reply = reply;

	if (ok) {
		connect(reply, &NetworkReply::readyRead, this, &FileDownloader::replyReadyRead);
		connect(reply, &NetworkReply::finished, this, &FileDownloader::replyFinished);
	} else {
		log(QStringLiteral("Unable to open file '%1': %2 (%3)").arg(path, m_file.errorString(), QString::number(m_file.error())), Logger::Error);
	}

	return ok;
}

void FileDownloader::replyReadyRead()
{
	if (m_reply->bytesAvailable() < WRITE_BUFFER_SIZE) {
		return;
	}

	const QByteArray data = m_reply->readAll();
	m_readSize += data.size();

	if (m_file.write(data) < 0) {
		m_writeError = true;
		m_reply->abort();
	} else {
		m_file.flush();
	}
}

void FileDownloader::replyFinished()
{
	const QByteArray data = m_reply->readAll();
	m_readSize += data.size();

	const qint64 written = m_file.write(data);
	m_file.close();

	const auto error = m_reply->error();
	const auto msg = m_reply->errorString();
	const bool failedLastWrite = data.length() > 0 && written < 0;
	const bool invalidHtml = !m_allowHtmlResponses && QString(data.left(100)).trimmed().startsWith("<!DOCTYPE", Qt::CaseInsensitive);
	const bool emptyFile = m_readSize == 0;

	if (error != NetworkReply::NetworkError::NoError || failedLastWrite || invalidHtml || emptyFile) {
		// Ignore those errors as they are caused by a bug in Qt
		if (error != NetworkReply::NetworkError::NoError && msg.contains("140E0197")) {
			log(QStringLiteral("Ignored network error '140E0197' for the image: `%1`: %2 (%3)").arg(m_reply->url().toString().toHtmlEscaped()).arg(error).arg(msg), Logger::Info);
			emit success();
			return;
		}

		m_file.remove();
		if (failedLastWrite || m_writeError) {
			emit writeError();
		} else if (invalidHtml) {
			log(QString("Invalid HTML content returned for url '%1'").arg(m_reply->url().toString()), Logger::Info);
			emit networkError(NetworkReply::NetworkError::ContentNotFoundError, "Invalid HTML content returned");
		} else if (emptyFile) {
			log(QString("Empty file returned for url '%1'").arg(m_reply->url().toString()), Logger::Info);
			emit networkError(NetworkReply::NetworkError::ContentNotFoundError, "Empty file returned");
		} else {
			emit networkError(m_reply->error(), m_reply->errorString());
		}
		return;
	}

	emit success();
}
<commit_msg>Fix unit tests<commit_after>#include "downloader/file-downloader.h"
#include <QFile>
#include "functions.h"
#include "logger.h"
#include "network/network-reply.h"

#define WRITE_BUFFER_SIZE (200 * 1024)

FileDownloader::FileDownloader(bool allowHtmlResponses, QObject *parent)
	: QObject(parent), m_allowHtmlResponses(allowHtmlResponses), m_reply(nullptr), m_readSize(0), m_writeError(false)
{}

bool FileDownloader::start(NetworkReply *reply, const QString &path)
{
	m_file.setFileName(path);
	const bool ok = m_file.open(QFile::WriteOnly | QFile::Truncate);

	m_readSize = 0;
	m_writeError = false;
	m_reply = reply;

	if (ok) {
		connect(reply, &NetworkReply::readyRead, this, &FileDownloader::replyReadyRead);
		connect(reply, &NetworkReply::finished, this, &FileDownloader::replyFinished);
	} else {
		log(QStringLiteral("Unable to open file '%1': %2 (%3)").arg(path, m_file.errorString(), QString::number(m_file.error())), Logger::Error);
	}

	return ok;
}

void FileDownloader::replyReadyRead()
{
	if (m_reply->bytesAvailable() < WRITE_BUFFER_SIZE) {
		return;
	}

	const QByteArray data = m_reply->readAll();
	m_readSize += data.size();

	if (m_file.write(data) < 0) {
		m_writeError = true;
		m_reply->abort();
	} else {
		m_file.flush();
	}
}

void FileDownloader::replyFinished()
{
	const QByteArray data = m_reply->readAll();
	m_readSize += data.size();

	const qint64 written = m_file.write(data);
	m_file.close();

	const auto error = m_reply->error();
	const auto msg = m_reply->errorString();
	const bool failedLastWrite = data.length() > 0 && written < 0;
	const bool invalidHtml = !m_allowHtmlResponses && QString(data.left(100)).trimmed().startsWith("<!DOCTYPE", Qt::CaseInsensitive);
	const bool emptyFile = m_readSize == 0;

	if (error != NetworkReply::NetworkError::NoError || failedLastWrite || invalidHtml || emptyFile) {
		// Ignore those errors as they are caused by a bug in Qt
		if (error != NetworkReply::NetworkError::NoError && msg.contains("140E0197")) {
			log(QStringLiteral("Ignored network error '140E0197' for the image: `%1`: %2 (%3)").arg(m_reply->url().toString().toHtmlEscaped()).arg(error).arg(msg), Logger::Info);
			emit success();
			return;
		}

		m_file.remove();
		if (failedLastWrite || m_writeError) {
			emit writeError();
		} else if (invalidHtml) {
			log(QString("Invalid HTML content returned for url '%1'").arg(m_reply->url().toString()), Logger::Info);
			emit networkError(NetworkReply::NetworkError::ContentNotFoundError, "Invalid HTML content returned");
		} else if (emptyFile && error == NetworkReply::NetworkError::NoError) {
			log(QString("Empty file returned for url '%1'").arg(m_reply->url().toString()), Logger::Info);
			emit networkError(NetworkReply::NetworkError::ContentNotFoundError, "Empty file returned");
		} else {
			emit networkError(m_reply->error(), m_reply->errorString());
		}
		return;
	}

	emit success();
}
<|endoftext|>


<commit_before>#include "eval.hh"
#include "command.hh"
#include "common-args.hh"
#include "shared.hh"
#include "store-api.hh"
#include "derivations.hh"
#include "affinity.hh"
#include "progress-bar.hh"

using namespace nix;

struct BuildEnvironment
{
    // FIXME: figure out which vars should be exported.
    std::map<std::string, std::string> env;
    std::map<std::string, std::string> functions;
};

BuildEnvironment readEnvironment(const Path & path)
{
    BuildEnvironment res;

    auto lines = tokenizeString<Strings>(readFile(path), "\n");

    auto getLine =
        [&]() {
            if (lines.empty())
                throw Error("shell environment '%s' ends unexpectedly", path);
            auto line = lines.front();
            lines.pop_front();
            return line;
        };

    while (!lines.empty()) {
        auto line = getLine();

        auto eq = line.find('=');
        if (eq != std::string::npos) {
            std::string name(line, 0, eq);
            std::string value(line, eq + 1);
            // FIXME: parse arrays
            res.env.insert({name, value});
        }

        else if (hasSuffix(line, " () ")) {
            std::string name(line, 0, line.size() - 4);
            // FIXME: validate name
            auto l = getLine();
            if (l != "{ ") throw Error("shell environment '%s' has unexpected line '%s'", path, l);
            std::string body;
            while ((l = getLine()) != "}") {
                body += l;
                body += '\n';
            }
            res.functions.insert({name, body});
        }

        else throw Error("shell environment '%s' has unexpected line '%s'", path, line);
    }

    return res;
}

/* Given an existing derivation, return the shell environment as
   initialised by stdenv's setup script. We do this by building a
   modified derivation with the same dependencies and nearly the same
   initial environment variables, that just writes the resulting
   environment to a file and exits. */
BuildEnvironment getDerivationEnvironment(ref<Store> store, Derivation drv)
{
    auto builder = baseNameOf(drv.builder);
    if (builder != "bash")
        throw Error("'nix shell' only works on derivations that use 'bash' as their builder");

    drv.args = {"-c", "set -e; if [[ -n $stdenv ]]; then source $stdenv/setup; fi; set > $out"};

    /* Remove derivation checks. */
    drv.env.erase("allowedReferences");
    drv.env.erase("allowedRequisites");
    drv.env.erase("disallowedReferences");
    drv.env.erase("disallowedRequisites");

    // FIXME: handle structured attrs

    /* Rehash and write the derivation. FIXME: would be nice to use
       'buildDerivation', but that's privileged. */
    auto drvName = drv.env["name"] + "-env";
    for (auto & output : drv.outputs)
        drv.env.erase(output.first);
    drv.env["out"] = "";
    drv.env["outputs"] = "out";
    drv.outputs["out"] = DerivationOutput("", "", "");
    Hash h = hashDerivationModulo(*store, drv);
    Path shellOutPath = store->makeOutputPath("out", h, drvName);
    drv.outputs["out"].path = shellOutPath;
    drv.env["out"] = shellOutPath;
    Path shellDrvPath2 = writeDerivation(store, drv, drvName);

    /* Build the derivation. */
    store->buildPaths({shellDrvPath2});

    assert(store->isValidPath(shellOutPath));

    return readEnvironment(shellOutPath);
}

struct Common : InstallableCommand
{
    /*
    std::set<string> keepVars{
        "DISPLAY",
        "HOME",
        "IN_NIX_SHELL",
        "LOGNAME",
        "NIX_BUILD_SHELL",
        "PAGER",
        "PATH",
        "TERM",
        "TZ",
        "USER",
    };
    */

    std::set<string> ignoreVars{
        "BASHOPTS",
        "EUID",
        "NIX_BUILD_TOP",
        "NIX_ENFORCE_PURITY",
        "PPID",
        "PWD",
        "SHELLOPTS",
        "SHLVL",
        "TEMP",
        "TEMPDIR",
        "TMP",
        "TMPDIR",
        "UID",
    };

    void makeRcScript(const BuildEnvironment & buildEnvironment, std::ostream & out)
    {
        for (auto & i : buildEnvironment.env) {
            // FIXME: shellEscape
            // FIXME: figure out what to export
            // FIXME: handle arrays
            if (!ignoreVars.count(i.first) && !hasPrefix(i.first, "BASH_"))
                out << fmt("export %s=%s\n", i.first, i.second);
        }

        for (auto & i : buildEnvironment.functions) {
            out << fmt("%s () {\n%s\n}\n", i.first, i.second);
        }

        // FIXME: set outputs

        out << "export NIX_BUILD_TOP=\"$(mktemp -d --tmpdir nix-shell.XXXXXX)\"\n";
        for (auto & i : {"TMP", "TMPDIR", "TEMP", "TEMPDIR"})
            out << fmt("export %s=\"$NIX_BUILD_TOP\"\n", i);
    }

    Strings getDefaultFlakeAttrPaths() override
    {
        return {"devShell", "defaultPackage"};
    }
};

std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix = "nix")
{
    Path tmpl(getEnv("TMPDIR", "/tmp") + "/" + prefix + ".XXXXXX");
    // Strictly speaking, this is UB, but who cares...
    AutoCloseFD fd(mkstemp((char *) tmpl.c_str()));
    if (!fd)
        throw SysError("creating temporary file '%s'", tmpl);
    return {std::move(fd), tmpl};
}

struct CmdDevShell : Common
{

    std::string name() override
    {
        return "dev-shell";
    }

    std::string description() override
    {
        return "run a bash shell that provides the build environment of a derivation";
    }

    Examples examples() override
    {
        return {
            Example{
                "To get the build environment of GNU hello:",
                "nix dev-shell nixpkgs:hello"
            },
            Example{
                "To get the build environment of the default package of flake in the current directory:",
                "nix dev-shell"
            },
        };
    }

    void run(ref<Store> store) override
    {
        auto drvs = toDerivations(store, {installable});

        if (drvs.size() != 1)
            throw Error("'%s' needs to evaluate to a single derivation, but it evaluated to %d derivations",
                installable->what(), drvs.size());

        auto & drvPath = *drvs.begin();

        auto buildEnvironment = getDerivationEnvironment(store, store->derivationFromPath(drvPath));

        auto [rcFileFd, rcFilePath] = createTempFile("nix-shell");

        std::ostringstream ss;
        makeRcScript(buildEnvironment, ss);

        ss << fmt("rm -f '%s'\n", rcFilePath);

        writeFull(rcFileFd.get(), ss.str());

        stopProgressBar();

        auto shell = getEnv("SHELL", "bash");

        auto args = Strings{baseNameOf(shell), "--rcfile", rcFilePath};

        restoreAffinity();
        restoreSignals();

        execvp(shell.c_str(), stringsToCharPtrs(args).data());

        throw SysError("executing shell '%s'", shell);
    }
};

struct CmdPrintDevEnv : Common
{

    std::string name() override
    {
        return "print-dev-env";
    }

    std::string description() override
    {
        return "print shell code that can be sourced by bash to reproduce the build environment of a derivation";
    }

    Examples examples() override
    {
        return {
            Example{
                "To apply the build environment of GNU hello to the current shell:",
                ". <(nix print-dev-env nixpkgs:hello)"
            },
        };
    }

    void run(ref<Store> store) override
    {
        auto drvs = toDerivations(store, {installable});

        if (drvs.size() != 1)
            throw Error("'%s' needs to evaluate to a single derivation, but it evaluated to %d derivations",
                installable->what(), drvs.size());

        auto & drvPath = *drvs.begin();

        auto buildEnvironment = getDerivationEnvironment(store, store->derivationFromPath(drvPath));

        stopProgressBar();

        makeRcScript(buildEnvironment, std::cout);
    }
};

static RegisterCommand r1(make_ref<CmdPrintDevEnv>());
static RegisterCommand r2(make_ref<CmdDevShell>());
<commit_msg>nix dev-shell: Execute shellHook<commit_after>#include "eval.hh"
#include "command.hh"
#include "common-args.hh"
#include "shared.hh"
#include "store-api.hh"
#include "derivations.hh"
#include "affinity.hh"
#include "progress-bar.hh"

using namespace nix;

struct BuildEnvironment
{
    // FIXME: figure out which vars should be exported.
    std::map<std::string, std::string> env;
    std::map<std::string, std::string> functions;
};

BuildEnvironment readEnvironment(const Path & path)
{
    BuildEnvironment res;

    auto lines = tokenizeString<Strings>(readFile(path), "\n");

    auto getLine =
        [&]() {
            if (lines.empty())
                throw Error("shell environment '%s' ends unexpectedly", path);
            auto line = lines.front();
            lines.pop_front();
            return line;
        };

    while (!lines.empty()) {
        auto line = getLine();

        auto eq = line.find('=');
        if (eq != std::string::npos) {
            std::string name(line, 0, eq);
            std::string value(line, eq + 1);
            // FIXME: parse arrays
            res.env.insert({name, value});
        }

        else if (hasSuffix(line, " () ")) {
            std::string name(line, 0, line.size() - 4);
            // FIXME: validate name
            auto l = getLine();
            if (l != "{ ") throw Error("shell environment '%s' has unexpected line '%s'", path, l);
            std::string body;
            while ((l = getLine()) != "}") {
                body += l;
                body += '\n';
            }
            res.functions.insert({name, body});
        }

        else throw Error("shell environment '%s' has unexpected line '%s'", path, line);
    }

    return res;
}

/* Given an existing derivation, return the shell environment as
   initialised by stdenv's setup script. We do this by building a
   modified derivation with the same dependencies and nearly the same
   initial environment variables, that just writes the resulting
   environment to a file and exits. */
BuildEnvironment getDerivationEnvironment(ref<Store> store, Derivation drv)
{
    auto builder = baseNameOf(drv.builder);
    if (builder != "bash")
        throw Error("'nix shell' only works on derivations that use 'bash' as their builder");

    drv.args = {"-c", "set -e; if [[ -n $stdenv ]]; then source $stdenv/setup; fi; set > $out"};

    /* Remove derivation checks. */
    drv.env.erase("allowedReferences");
    drv.env.erase("allowedRequisites");
    drv.env.erase("disallowedReferences");
    drv.env.erase("disallowedRequisites");

    // FIXME: handle structured attrs

    /* Rehash and write the derivation. FIXME: would be nice to use
       'buildDerivation', but that's privileged. */
    auto drvName = drv.env["name"] + "-env";
    for (auto & output : drv.outputs)
        drv.env.erase(output.first);
    drv.env["out"] = "";
    drv.env["outputs"] = "out";
    drv.outputs["out"] = DerivationOutput("", "", "");
    Hash h = hashDerivationModulo(*store, drv);
    Path shellOutPath = store->makeOutputPath("out", h, drvName);
    drv.outputs["out"].path = shellOutPath;
    drv.env["out"] = shellOutPath;
    Path shellDrvPath2 = writeDerivation(store, drv, drvName);

    /* Build the derivation. */
    store->buildPaths({shellDrvPath2});

    assert(store->isValidPath(shellOutPath));

    return readEnvironment(shellOutPath);
}

struct Common : InstallableCommand
{
    /*
    std::set<string> keepVars{
        "DISPLAY",
        "HOME",
        "IN_NIX_SHELL",
        "LOGNAME",
        "NIX_BUILD_SHELL",
        "PAGER",
        "PATH",
        "TERM",
        "TZ",
        "USER",
    };
    */

    std::set<string> ignoreVars{
        "BASHOPTS",
        "EUID",
        "NIX_BUILD_TOP",
        "NIX_ENFORCE_PURITY",
        "PPID",
        "PWD",
        "SHELLOPTS",
        "SHLVL",
        "TEMP",
        "TEMPDIR",
        "TMP",
        "TMPDIR",
        "UID",
    };

    void makeRcScript(const BuildEnvironment & buildEnvironment, std::ostream & out)
    {
        for (auto & i : buildEnvironment.env) {
            // FIXME: shellEscape
            // FIXME: figure out what to export
            // FIXME: handle arrays
            if (!ignoreVars.count(i.first) && !hasPrefix(i.first, "BASH_"))
                out << fmt("export %s=%s\n", i.first, i.second);
        }

        for (auto & i : buildEnvironment.functions) {
            out << fmt("%s () {\n%s\n}\n", i.first, i.second);
        }

        // FIXME: set outputs

        out << "export NIX_BUILD_TOP=\"$(mktemp -d --tmpdir nix-shell.XXXXXX)\"\n";
        for (auto & i : {"TMP", "TMPDIR", "TEMP", "TEMPDIR"})
            out << fmt("export %s=\"$NIX_BUILD_TOP\"\n", i);

        out << "eval \"$shellHook\"\n";
    }

    Strings getDefaultFlakeAttrPaths() override
    {
        return {"devShell", "defaultPackage"};
    }
};

std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix = "nix")
{
    Path tmpl(getEnv("TMPDIR", "/tmp") + "/" + prefix + ".XXXXXX");
    // Strictly speaking, this is UB, but who cares...
    AutoCloseFD fd(mkstemp((char *) tmpl.c_str()));
    if (!fd)
        throw SysError("creating temporary file '%s'", tmpl);
    return {std::move(fd), tmpl};
}

struct CmdDevShell : Common
{

    std::string name() override
    {
        return "dev-shell";
    }

    std::string description() override
    {
        return "run a bash shell that provides the build environment of a derivation";
    }

    Examples examples() override
    {
        return {
            Example{
                "To get the build environment of GNU hello:",
                "nix dev-shell nixpkgs:hello"
            },
            Example{
                "To get the build environment of the default package of flake in the current directory:",
                "nix dev-shell"
            },
        };
    }

    void run(ref<Store> store) override
    {
        auto drvs = toDerivations(store, {installable});

        if (drvs.size() != 1)
            throw Error("'%s' needs to evaluate to a single derivation, but it evaluated to %d derivations",
                installable->what(), drvs.size());

        auto & drvPath = *drvs.begin();

        auto buildEnvironment = getDerivationEnvironment(store, store->derivationFromPath(drvPath));

        auto [rcFileFd, rcFilePath] = createTempFile("nix-shell");

        std::ostringstream ss;
        makeRcScript(buildEnvironment, ss);

        ss << fmt("rm -f '%s'\n", rcFilePath);

        writeFull(rcFileFd.get(), ss.str());

        stopProgressBar();

        auto shell = getEnv("SHELL", "bash");

        auto args = Strings{baseNameOf(shell), "--rcfile", rcFilePath};

        restoreAffinity();
        restoreSignals();

        execvp(shell.c_str(), stringsToCharPtrs(args).data());

        throw SysError("executing shell '%s'", shell);
    }
};

struct CmdPrintDevEnv : Common
{

    std::string name() override
    {
        return "print-dev-env";
    }

    std::string description() override
    {
        return "print shell code that can be sourced by bash to reproduce the build environment of a derivation";
    }

    Examples examples() override
    {
        return {
            Example{
                "To apply the build environment of GNU hello to the current shell:",
                ". <(nix print-dev-env nixpkgs:hello)"
            },
        };
    }

    void run(ref<Store> store) override
    {
        auto drvs = toDerivations(store, {installable});

        if (drvs.size() != 1)
            throw Error("'%s' needs to evaluate to a single derivation, but it evaluated to %d derivations",
                installable->what(), drvs.size());

        auto & drvPath = *drvs.begin();

        auto buildEnvironment = getDerivationEnvironment(store, store->derivationFromPath(drvPath));

        stopProgressBar();

        makeRcScript(buildEnvironment, std::cout);
    }
};

static RegisterCommand r1(make_ref<CmdPrintDevEnv>());
static RegisterCommand r2(make_ref<CmdDevShell>());
<|endoftext|>


<commit_before>/*-------------------------------------------------------------------------
 *
 *   FILE
 *	subtransaction.cxx
 *
 *   DESCRIPTION
 *      implementation of the pqxx::subtransaction class.
 *   pqxx::transaction is a nested transaction, i.e. one within a transaction
 *
 * Copyright (c) 2005-2006, Jeroen T. Vermeulen <jtv@xs4all.nl>
 *
 * See COPYING for copyright license.  If you did not receive a file called
 * COPYING with this source code, please notify the distributor of this mistake,
 * or contact the author.
 *
 *-------------------------------------------------------------------------
 */
#include "pqxx/compiler-internal.hxx"

#include <stdexcept>

#include "pqxx/connection_base"
#include "pqxx/subtransaction"

using namespace PGSTD;

pqxx::subtransaction::subtransaction(dbtransaction &T,
    const string &Name) :
  namedclass("subtransaction", T.conn().adorn_name(Name)),
  transactionfocus(T),
  dbtransaction(T.conn(), false),
  m_parent(T)
{
  check_backendsupport();
}

void pqxx::subtransaction::do_begin()
{
  try
  {
    DirectExec(("SAVEPOINT \"" + name() + "\"").c_str());
  }
  catch (const sql_error &)
  {
    check_backendsupport();
    throw;
  }
}

void pqxx::subtransaction::do_commit()
{
  const int ra = m_reactivation_avoidance.get();
  m_reactivation_avoidance.clear();
  DirectExec(("RELEASE SAVEPOINT \"" + name() + "\"").c_str());
  m_parent.m_reactivation_avoidance.add(ra);
}

void pqxx::subtransaction::do_abort()
{
  DirectExec(("ROLLBACK TO SAVEPOINT \"" + name() + "\"").c_str());
}

void pqxx::subtransaction::check_backendsupport() const
{
#if defined(PQXX_HAVE_PQSERVERVERSION)
  if (!T.conn().supports(connection_base::cap_nested_transactions))
    throw runtime_error("Backend version does not support nested transactions");
#endif
}

<commit_msg>Silly compile fix<commit_after>/*-------------------------------------------------------------------------
 *
 *   FILE
 *	subtransaction.cxx
 *
 *   DESCRIPTION
 *      implementation of the pqxx::subtransaction class.
 *   pqxx::transaction is a nested transaction, i.e. one within a transaction
 *
 * Copyright (c) 2005-2006, Jeroen T. Vermeulen <jtv@xs4all.nl>
 *
 * See COPYING for copyright license.  If you did not receive a file called
 * COPYING with this source code, please notify the distributor of this mistake,
 * or contact the author.
 *
 *-------------------------------------------------------------------------
 */
#include "pqxx/compiler-internal.hxx"

#include <stdexcept>

#include "pqxx/connection_base"
#include "pqxx/subtransaction"

using namespace PGSTD;

pqxx::subtransaction::subtransaction(dbtransaction &T,
    const string &Name) :
  namedclass("subtransaction", T.conn().adorn_name(Name)),
  transactionfocus(T),
  dbtransaction(T.conn(), false),
  m_parent(T)
{
  check_backendsupport();
}

void pqxx::subtransaction::do_begin()
{
  try
  {
    DirectExec(("SAVEPOINT \"" + name() + "\"").c_str());
  }
  catch (const sql_error &)
  {
    check_backendsupport();
    throw;
  }
}

void pqxx::subtransaction::do_commit()
{
  const int ra = m_reactivation_avoidance.get();
  m_reactivation_avoidance.clear();
  DirectExec(("RELEASE SAVEPOINT \"" + name() + "\"").c_str());
  m_parent.m_reactivation_avoidance.add(ra);
}

void pqxx::subtransaction::do_abort()
{
  DirectExec(("ROLLBACK TO SAVEPOINT \"" + name() + "\"").c_str());
}

void pqxx::subtransaction::check_backendsupport() const
{
#if defined(PQXX_HAVE_PQSERVERVERSION)
  if (!m_parent.conn().supports(connection_base::cap_nested_transactions))
    throw runtime_error("Backend version does not support nested transactions");
#endif
}

<|endoftext|>


<commit_before>#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <sstream>
#include <string.h>
#include <queue>

#include "node_ftdi.h"
#include "ftdi_driver.h"
#include <node_buffer.h>

#ifndef WIN32
#include <unistd.h>
#endif

#define EVENT_MASK (FT_EVENT_RXCHAR)

using namespace std;
using namespace v8;
using namespace node;
using namespace node_ftdi;

struct ReadBaton
{
  FT_HANDLE ftHandle;
#ifndef WIN32
  EVENT_HANDLE eh;
#else
  HANDLE hEvent;
#endif
  uint8_t* readData;
  int32_t bufferLength;
  Persistent<Value> callback;
  int result;
};

void PrepareAsyncRead(ReadBaton *baton);
void WaitForReadEvent(ReadBaton *data);

const char* ToCString(Local<String> val)
{
    return *String::Utf8Value(val);
}

Handle<Value> NodeFtdi::ThrowTypeError(std::string message)
{
    return ThrowException(Exception::TypeError(String::New(message.c_str())));
}

Handle<Value> NodeFtdi::ThrowLastError(std::string message)
{
    Local<String> msg = String::New(message.c_str());

    return ThrowException(Exception::Error(msg));
}

NodeFtdi::NodeFtdi() {};
NodeFtdi::~NodeFtdi() {};

void NodeFtdi::Initialize(v8::Handle<v8::Object> target)
{

    printf("Initialize NodeFtdi\r\n");

    // Prepare constructor template
    Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
    tpl->SetClassName(String::NewSymbol("FtdiDevice"));
    tpl->InstanceTemplate()->SetInternalFieldCount(1);
    // Prototype
    tpl->PrototypeTemplate()->Set(String::NewSymbol("write"), FunctionTemplate::New(Write)->GetFunction());
    tpl->PrototypeTemplate()->Set(String::NewSymbol("registerDataCallback"), FunctionTemplate::New(RegisterDataCallback)->GetFunction());
    tpl->PrototypeTemplate()->Set(String::NewSymbol("open"), FunctionTemplate::New(Open)->GetFunction());

    Persistent<Function> constructor = Persistent<Function>::New(tpl->GetFunction());
    target->Set(String::NewSymbol("FtdiDevice"), constructor);
}

Handle<Value> NodeFtdi::New(const Arguments& args)
{
    HandleScope scope;
    Local<String> vid = String::New("vid");
    Local<String> pid = String::New("pid");
    Local<String> description = String::New("description");
    Local<String> serial = String::New("serial");
    Local<String> index = String::New("index");

    NodeFtdi* object = new NodeFtdi();

    object->p.vid = FTDI_VID;
    object->p.pid = FTDI_PID;
    object->p.description = NULL;
    object->p.serial = NULL;
    object->p.index = 0;

    printf("Create New FTDI Object\r\n");

    if(args[0]->IsObject())
    {
        Local<Object> obj = args[0]->ToObject();

        if(obj->Has(vid)) {
            object->p.vid = obj->Get(vid)->Int32Value();
        }
        if(obj->Has(pid)) {
            object->p.pid = obj->Get(pid)->Int32Value();
        }
        if(obj->Has(description)) {
            object->p.description = ToCString(obj->Get(description)->ToString());
        }
        if(obj->Has(serial)) {
            object->p.serial = ToCString(obj->Get(serial)->ToString());
        }
        if(obj->Has(index)) {
            object->p.index = (unsigned int) obj->Get(index)->Int32Value();
        }
    }

    object->Wrap(args.This());

    return args.This();
}

void ReadDataAsync(uv_work_t* req)
{
    ReadBaton* data = static_cast<ReadBaton*>(req->data);

    FT_STATUS ftStatus;
    DWORD RxBytes;
    DWORD BytesReceived;

    printf("Waiting for data\r\n");
    WaitForReadEvent(data);

    FT_GetQueueStatus(data->ftHandle, &RxBytes);
    printf("Status [RX: %d]\r\n", RxBytes);

    if(RxBytes > 0)
    {
        data->readData = (uint8_t *)malloc(RxBytes);

        ftStatus = FT_Read(data->ftHandle, data->readData, RxBytes, &BytesReceived);
        if (ftStatus != FT_OK)
        {
            fprintf(stderr, "Can't read from ftdi device: %d\n", ftStatus);
        }
        data->bufferLength = BytesReceived;
    }
}

void NodeFtdi::ReadCallback(uv_work_t* req)
{
    HandleScope scope;
    ReadBaton* data = static_cast<ReadBaton*>(req->data);

    printf("ReadCallback\r\n");

    if(data->callback->IsFunction())
    {
        Handle<Value> argv[1];

        Buffer *slowBuffer = Buffer::New(data->bufferLength);
        memcpy(Buffer::Data(slowBuffer), data->readData, data->bufferLength);
        Local<Object> globalObj = Context::GetCurrent()->Global();

        Local<Function> bufferConstructor = Local<Function>::Cast(globalObj->Get(String::New("Buffer")));
        Handle<Value> constructorArgs[3] = { slowBuffer->handle_, Integer::New(data->bufferLength), Integer::New(0) };
        Local<Object> actualBuffer = bufferConstructor->NewInstance(3, constructorArgs);
        argv[0] = actualBuffer;

        Function::Cast(*data->callback)->Call(Context::GetCurrent()->Global(), 1, argv);

    }
    else
    {
        printf("Could not call dataCb\r\n");
    }

    free(data->readData);

    uv_queue_work(uv_default_loop(), req, ReadDataAsync, (uv_after_work_cb)NodeFtdi::ReadCallback);
}

Handle<Value> NodeFtdi::Open(const Arguments& args)
{
    HandleScope scope;
    FT_STATUS ftStatus;

    // Get Object
    NodeFtdi* obj = ObjectWrap::Unwrap<NodeFtdi>(args.This());

    if (args.Length() < 1 || !args[0]->IsString())
    {
        return NodeFtdi::ThrowTypeError("open() expects a string as first argument");
    }
    std::string serial(*String::Utf8Value(args[0]));

      // options
    if(!args[1]->IsObject())
    {
        return NodeFtdi::ThrowTypeError("open() expects a object as second argument");
    }
    Local<v8::Object> options = args[1]->ToObject();

    printf("Open Device %s\r\n",  (unsigned char*) serial.c_str());

    ftStatus = FT_OpenEx((unsigned char*) serial.c_str(), FT_OPEN_BY_SERIAL_NUMBER, &(obj->ftHandle));
    if (ftStatus != FT_OK)
    {
        fprintf(stderr, "Can't open ftdi device: %d\n", ftStatus);
    }

    obj->SetDeviceSettings(options);

    ReadBaton* baton = new ReadBaton();
    baton->ftHandle = obj->ftHandle;
    baton->callback = obj->dataCallback;

    PrepareAsyncRead(baton);

    uv_work_t* req = new uv_work_t();
    req->data = baton;
    uv_queue_work(uv_default_loop(), req, ReadDataAsync, (uv_after_work_cb)NodeFtdi::ReadCallback);

    return args.This();
}

void NodeFtdi::SetDeviceSettings(Local<v8::Object> options)
{
    FT_STATUS ftStatus;
    int baudRate = options->Get(v8::String::New("baudrate"))->ToInt32()->Int32Value();

    ftStatus = FT_SetDataCharacteristics(ftHandle, FT_BITS_8, FT_STOP_BITS_1, FT_PARITY_NONE);
    if (ftStatus != FT_OK)
    {
        return;
    }

    ftStatus = FT_SetBaudRate(ftHandle, baudRate);
    if (ftStatus != FT_OK)
    {
        fprintf(stderr, "Can't setBaudRate: %d\n", ftStatus);
    }
    printf("BaudRate set to: %d\r\n", baudRate);
}

Handle<Value> NodeFtdi::Write(const Arguments& args)
{
    HandleScope scope;
    FT_STATUS ftStatus;
    DWORD BytesWritten;

    // Get Object
    NodeFtdi* obj = ObjectWrap::Unwrap<NodeFtdi>(args.This());

     // buffer
    if(!args[0]->IsObject() || !Buffer::HasInstance(args[0]))
    {
        return scope.Close(v8::ThrowException(Exception::TypeError(String::New("First argument must be a buffer"))));
    }
    Persistent<Object> buffer = Persistent<Object>::New(args[0]->ToObject());
    char* bufferData = Buffer::Data(buffer);
    DWORD bufferLength = (DWORD)Buffer::Length(buffer);

    ftStatus = FT_Write(obj->ftHandle, bufferData, bufferLength, &BytesWritten);
    if (ftStatus != FT_OK)
    {
        fprintf(stderr, "Can't write to ftdi device: %d\n", ftStatus);
        return NodeFtdi::ThrowTypeError("Can't write to ftdi device");
    }
    printf("%d bytes written\r\n", BytesWritten);

    return scope.Close(v8::Undefined());
}

Handle<Value> NodeFtdi::RegisterDataCallback(const Arguments& args)
{
    HandleScope scope;

    // Get Object
    NodeFtdi* obj = ObjectWrap::Unwrap<NodeFtdi>(args.This());

    printf("RegisterDataCallback\r\n");

    // callback
    if(!args[0]->IsFunction())
    {
        return scope.Close(v8::ThrowException(v8::Exception::TypeError(v8::String::New("First argument must be a function"))));
    }
    Local<Value> callback = args[0];

    obj->dataCallback = Persistent<Value>::New(callback);

    return scope.Close(v8::Undefined());
}

/**
 * Linux / Mac Functions
 */
#ifndef WIN32

void PrepareAsyncRead(ReadBaton *baton)
{
    FT_STATUS ftStatus;

    pthread_mutex_init(&(baton->eh).eMutex, NULL);
    pthread_cond_init(&(baton->eh).eCondVar, NULL);
    ftStatus = FT_SetEventNotification(baton->ftHandle, EVENT_MASK, (PVOID)&(baton->eh));
}

void WaitForReadEvent(ReadBaton *data)
{
    pthread_mutex_lock(&(data->eh).eMutex);
    pthread_cond_wait(&(data->eh).eCondVar, &(data->eh).eMutex);
    pthread_mutex_unlock(&(data->eh).eMutex);
}

#else
void PrepareAsyncRead(ReadBaton *baton)
{
    FT_STATUS ftStatus;

    baton->hEvent = CreateEvent(NULL, false /* auto-reset event */, false /* non-signalled state */, "");
    ftStatus = FT_SetEventNotification(baton->ftHandle, EVENT_MASK, baton->hEvent);
}

void WaitForReadEvent(ReadBaton *data)
{
    WaitForSingleObject(data->hEvent, INFINITE);
}
#endif

extern "C" {
  void init (v8::Handle<v8::Object> target)
  {
    InitializeList(target);
    NodeFtdi::Initialize(target);
  }
}

NODE_MODULE(ftdi, init)<commit_msg>return type of PrepareAsyncRead() changed<commit_after>#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <sstream>
#include <string.h>
#include <queue>

#include "node_ftdi.h"
#include "ftdi_driver.h"
#include <node_buffer.h>

#ifndef WIN32
#include <unistd.h>
#endif

#define EVENT_MASK (FT_EVENT_RXCHAR)

using namespace std;
using namespace v8;
using namespace node;
using namespace node_ftdi;

struct ReadBaton
{
  FT_HANDLE ftHandle;
#ifndef WIN32
  EVENT_HANDLE eh;
#else
  HANDLE hEvent;
#endif
  uint8_t* readData;
  int32_t bufferLength;
  Persistent<Value> callback;
  int result;
};

FT_STATUS PrepareAsyncRead(ReadBaton *baton);
void WaitForReadEvent(ReadBaton *data);

const char* ToCString(Local<String> val)
{
    return *String::Utf8Value(val);
}

Handle<Value> NodeFtdi::ThrowTypeError(std::string message)
{
    return ThrowException(Exception::TypeError(String::New(message.c_str())));
}

Handle<Value> NodeFtdi::ThrowLastError(std::string message)
{
    Local<String> msg = String::New(message.c_str());

    return ThrowException(Exception::Error(msg));
}

NodeFtdi::NodeFtdi() {};
NodeFtdi::~NodeFtdi() {};

void NodeFtdi::Initialize(v8::Handle<v8::Object> target)
{

    printf("Initialize NodeFtdi\r\n");

    // Prepare constructor template
    Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
    tpl->SetClassName(String::NewSymbol("FtdiDevice"));
    tpl->InstanceTemplate()->SetInternalFieldCount(1);
    // Prototype
    tpl->PrototypeTemplate()->Set(String::NewSymbol("write"), FunctionTemplate::New(Write)->GetFunction());
    tpl->PrototypeTemplate()->Set(String::NewSymbol("registerDataCallback"), FunctionTemplate::New(RegisterDataCallback)->GetFunction());
    tpl->PrototypeTemplate()->Set(String::NewSymbol("open"), FunctionTemplate::New(Open)->GetFunction());

    Persistent<Function> constructor = Persistent<Function>::New(tpl->GetFunction());
    target->Set(String::NewSymbol("FtdiDevice"), constructor);
}

Handle<Value> NodeFtdi::New(const Arguments& args)
{
    HandleScope scope;
    Local<String> vid = String::New("vid");
    Local<String> pid = String::New("pid");
    Local<String> description = String::New("description");
    Local<String> serial = String::New("serial");
    Local<String> index = String::New("index");

    NodeFtdi* object = new NodeFtdi();

    object->p.vid = FTDI_VID;
    object->p.pid = FTDI_PID;
    object->p.description = NULL;
    object->p.serial = NULL;
    object->p.index = 0;

    printf("Create New FTDI Object\r\n");

    if(args[0]->IsObject())
    {
        Local<Object> obj = args[0]->ToObject();

        if(obj->Has(vid)) {
            object->p.vid = obj->Get(vid)->Int32Value();
        }
        if(obj->Has(pid)) {
            object->p.pid = obj->Get(pid)->Int32Value();
        }
        if(obj->Has(description)) {
            object->p.description = ToCString(obj->Get(description)->ToString());
        }
        if(obj->Has(serial)) {
            object->p.serial = ToCString(obj->Get(serial)->ToString());
        }
        if(obj->Has(index)) {
            object->p.index = (unsigned int) obj->Get(index)->Int32Value();
        }
    }

    object->Wrap(args.This());

    return args.This();
}

void ReadDataAsync(uv_work_t* req)
{
    ReadBaton* data = static_cast<ReadBaton*>(req->data);

    FT_STATUS ftStatus;
    DWORD RxBytes;
    DWORD BytesReceived;

    printf("Waiting for data\r\n");
    WaitForReadEvent(data);

    FT_GetQueueStatus(data->ftHandle, &RxBytes);
    printf("Status [RX: %d]\r\n", RxBytes);

    if(RxBytes > 0)
    {
        data->readData = (uint8_t *)malloc(RxBytes);

        ftStatus = FT_Read(data->ftHandle, data->readData, RxBytes, &BytesReceived);
        if (ftStatus != FT_OK)
        {
            fprintf(stderr, "Can't read from ftdi device: %d\n", ftStatus);
        }
        data->bufferLength = BytesReceived;
    }
}

void NodeFtdi::ReadCallback(uv_work_t* req)
{
    HandleScope scope;
    ReadBaton* data = static_cast<ReadBaton*>(req->data);

    printf("ReadCallback\r\n");

    if(data->callback->IsFunction())
    {
        Handle<Value> argv[1];

        Buffer *slowBuffer = Buffer::New(data->bufferLength);
        memcpy(Buffer::Data(slowBuffer), data->readData, data->bufferLength);
        Local<Object> globalObj = Context::GetCurrent()->Global();

        Local<Function> bufferConstructor = Local<Function>::Cast(globalObj->Get(String::New("Buffer")));
        Handle<Value> constructorArgs[3] = { slowBuffer->handle_, Integer::New(data->bufferLength), Integer::New(0) };
        Local<Object> actualBuffer = bufferConstructor->NewInstance(3, constructorArgs);
        argv[0] = actualBuffer;

        Function::Cast(*data->callback)->Call(Context::GetCurrent()->Global(), 1, argv);

    }
    else
    {
        printf("Could not call dataCb\r\n");
    }

    free(data->readData);

    uv_queue_work(uv_default_loop(), req, ReadDataAsync, (uv_after_work_cb)NodeFtdi::ReadCallback);
}

Handle<Value> NodeFtdi::Open(const Arguments& args)
{
    HandleScope scope;
    FT_STATUS ftStatus;

    // Get Object
    NodeFtdi* obj = ObjectWrap::Unwrap<NodeFtdi>(args.This());

    if (args.Length() < 1 || !args[0]->IsString())
    {
        return NodeFtdi::ThrowTypeError("open() expects a string as first argument");
    }
    std::string serial(*String::Utf8Value(args[0]));

      // options
    if(!args[1]->IsObject())
    {
        return NodeFtdi::ThrowTypeError("open() expects a object as second argument");
    }
    Local<v8::Object> options = args[1]->ToObject();

    printf("Open Device %s\r\n",  (unsigned char*) serial.c_str());

    ftStatus = FT_OpenEx((unsigned char*) serial.c_str(), FT_OPEN_BY_SERIAL_NUMBER, &(obj->ftHandle));
    if (ftStatus != FT_OK)
    {
        fprintf(stderr, "Can't open ftdi device: %d\n", ftStatus);
    }

    obj->SetDeviceSettings(options);

    ReadBaton* baton = new ReadBaton();
    baton->ftHandle = obj->ftHandle;
    baton->callback = obj->dataCallback;

    PrepareAsyncRead(baton);

    uv_work_t* req = new uv_work_t();
    req->data = baton;
    uv_queue_work(uv_default_loop(), req, ReadDataAsync, (uv_after_work_cb)NodeFtdi::ReadCallback);

    return args.This();
}

void NodeFtdi::SetDeviceSettings(Local<v8::Object> options)
{
    FT_STATUS ftStatus;
    int baudRate = options->Get(v8::String::New("baudrate"))->ToInt32()->Int32Value();

    ftStatus = FT_SetDataCharacteristics(ftHandle, FT_BITS_8, FT_STOP_BITS_1, FT_PARITY_NONE);
    if (ftStatus != FT_OK)
    {
        return;
    }

    ftStatus = FT_SetBaudRate(ftHandle, baudRate);
    if (ftStatus != FT_OK)
    {
        fprintf(stderr, "Can't setBaudRate: %d\n", ftStatus);
    }
    printf("BaudRate set to: %d\r\n", baudRate);
}

Handle<Value> NodeFtdi::Write(const Arguments& args)
{
    HandleScope scope;
    FT_STATUS ftStatus;
    DWORD BytesWritten;

    // Get Object
    NodeFtdi* obj = ObjectWrap::Unwrap<NodeFtdi>(args.This());

     // buffer
    if(!args[0]->IsObject() || !Buffer::HasInstance(args[0]))
    {
        return scope.Close(v8::ThrowException(Exception::TypeError(String::New("First argument must be a buffer"))));
    }
    Persistent<Object> buffer = Persistent<Object>::New(args[0]->ToObject());
    char* bufferData = Buffer::Data(buffer);
    DWORD bufferLength = (DWORD)Buffer::Length(buffer);

    ftStatus = FT_Write(obj->ftHandle, bufferData, bufferLength, &BytesWritten);
    if (ftStatus != FT_OK)
    {
        fprintf(stderr, "Can't write to ftdi device: %d\n", ftStatus);
        return NodeFtdi::ThrowTypeError("Can't write to ftdi device");
    }
    printf("%d bytes written\r\n", BytesWritten);

    return scope.Close(v8::Undefined());
}

Handle<Value> NodeFtdi::RegisterDataCallback(const Arguments& args)
{
    HandleScope scope;

    // Get Object
    NodeFtdi* obj = ObjectWrap::Unwrap<NodeFtdi>(args.This());

    printf("RegisterDataCallback\r\n");

    // callback
    if(!args[0]->IsFunction())
    {
        return scope.Close(v8::ThrowException(v8::Exception::TypeError(v8::String::New("First argument must be a function"))));
    }
    Local<Value> callback = args[0];

    obj->dataCallback = Persistent<Value>::New(callback);

    return scope.Close(v8::Undefined());
}

/**
 * Linux / Mac Functions
 */
#ifndef WIN32

FT_STATUS PrepareAsyncRead(ReadBaton *baton)
{
    FT_STATUS ftStatus;

    pthread_mutex_init(&(baton->eh).eMutex, NULL);
    pthread_cond_init(&(baton->eh).eCondVar, NULL);
    return FT_SetEventNotification(baton->ftHandle, EVENT_MASK, (PVOID)&(baton->eh));
}

void WaitForReadEvent(ReadBaton *data)
{
    pthread_mutex_lock(&(data->eh).eMutex);
    pthread_cond_wait(&(data->eh).eCondVar, &(data->eh).eMutex);
    pthread_mutex_unlock(&(data->eh).eMutex);
}

#else
FT_STATUS PrepareAsyncRead(ReadBaton *baton)
{
    FT_STATUS ftStatus;

    baton->hEvent = CreateEvent(NULL, false /* auto-reset event */, false /* non-signalled state */, "");
    return FT_SetEventNotification(baton->ftHandle, EVENT_MASK, baton->hEvent);
}

void WaitForReadEvent(ReadBaton *data)
{
    WaitForSingleObject(data->hEvent, INFINITE);
}
#endif

extern "C" {
  void init (v8::Handle<v8::Object> target)
  {
    InitializeList(target);
    NodeFtdi::Initialize(target);
  }
}

NODE_MODULE(ftdi, init)<|endoftext|>