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

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

#include "SqliteConnection.h"

#include "database/SqliteTools.h"

#include <cstring>

#define DEBUG_SQLITE_TRIGGERS 0

namespace medialibrary
{
namespace sqlite
{

Connection::Connection( const std::string& dbPath )
    : m_dbPath( dbPath )
    , m_readLock( m_contextLock )
    , m_writeLock( m_contextLock )
{
    /* Indirect call to sqlite3_config */
    static SqliteConfigurator config;
}

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

Connection::Handle Connection::handle()
{
    /**
     * We need to have a single sqlite connection per thread, but we also need
     * to be able to destroy those when the SqliteConnection wrapper instance
     * gets destroyed (otherwise we can't re-instantiate the media library during
     * the application runtime. That can't work for unit test and can be quite
     * a limitation for the application using the medialib)
     * Being able to refer to all per-thread connection means we can't use
     * thread local directly, however, we need to know if a thread goes away, to
     * avoid re-using an old connection on a new thread with the same id, as this
     * would result in a "Database is locked error"
     * In order to solve this, we use a single map to store all connection,
     * indexed by std::thread::id (well, compat::thread::id). Additionaly, in
     * order to know when a thread gets terminated, we store a thread_local
     * object to signal back when a thread returns, and to remove the now
     * unusable connection.
     * Also note that we need to flush any potentially cached compiled statement
     * when a thread gets terminated, as it would become unusable as well.
     *
     * \sa sqlite::Connection::ThreadSpecificConnection::~ThreadSpecificConnection
     * \sa sqlite::Statement::StatementsCache
     */
    std::unique_lock<compat::Mutex> lock( m_connMutex );
    auto it = m_conns.find( compat::this_thread::get_id() );
    if ( it == end( m_conns ) )
    {
        sqlite3* dbConnection;
        auto flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_NOMUTEX;
        if ( m_conns.empty() == true )
            flags |= SQLITE_OPEN_CREATE;
        auto res = sqlite3_open_v2( m_dbPath.c_str(), &dbConnection, flags, nullptr );
        ConnPtr dbConn( dbConnection, &sqlite3_close );
        if ( res != SQLITE_OK )
        {
            int err = sqlite3_system_errno(dbConnection);
            LOG_ERROR( "Failed to connect to database. OS error: ", err );
            errors::mapToException( "<connecting to db>", "", res );
        }
        /*
         * Fetch the absolute path to the database. If for whatever reason we were
         * to change directories during the runtime, we'd end up having a connection
         * to a different database in case the provided path is relative.
         * See #262
         */
        if ( m_conns.empty() == true )
        {
            m_dbPath = sqlite3_db_filename( dbConnection, nullptr );
            LOG_DEBUG( "Fetched absolute database path from sqlite: ", m_dbPath );
        }

        res = sqlite3_extended_result_codes( dbConnection, 1 );
        if ( res != SQLITE_OK )
            errors::mapToException( "<enabling extended errors>", "", res );
        // Don't use public wrapper, they need to be able to call getConn, which
        // would result from a recursive call and a deadlock from here.
        setPragma( dbConnection, "foreign_keys", "1" );
        setPragma( dbConnection, "recursive_triggers", "1" );
#ifdef __ANDROID__
        // https://github.com/mozilla/mentat/issues/505
        // Should solve `Failed to run request <DELETE FROM File WHERE id_file = ?>: disk I/O error(6410)`
        setPragma( dbConnection, "temp_store", "2" );
#endif
#if DEBUG_SQLITE_TRIGGERS
        sqlite3_trace_v2( dbConnection, SQLITE_TRACE_STMT | SQLITE_TRACE_CLOSE,
                          [](unsigned int t, void* , void* p, void* x) {
            if ( t == SQLITE_TRACE_STMT )
            {
                const char* str = static_cast<const char*>( x );
                LOG_VERBOSE( "Executed: ", str );
            }
            else if ( t == SQLITE_TRACE_CLOSE )
            {
                LOG_VERBOSE( "Connection ", p, " was closed" );
            }
            return 0;
        }, nullptr );
#endif
        m_conns.emplace( compat::this_thread::get_id(), std::move( dbConn ) );
        sqlite3_update_hook( dbConnection, &updateHook, this );
        static thread_local ThreadSpecificConnection tsc( shared_from_this() );
        return dbConnection;
    }
    return it->second.get();
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

}

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

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

#include "SqliteConnection.h"

#include "database/SqliteTools.h"

#include <cstring>

#define DEBUG_SQLITE_TRIGGERS 0

namespace medialibrary
{
namespace sqlite
{

Connection::Connection( const std::string& dbPath )
    : m_dbPath( dbPath )
    , m_readLock( m_contextLock )
    , m_writeLock( m_contextLock )
{
    /* Indirect call to sqlite3_config */
    static SqliteConfigurator config;
}

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

Connection::Handle Connection::handle()
{
    /**
     * We need to have a single sqlite connection per thread, but we also need
     * to be able to destroy those when the SqliteConnection wrapper instance
     * gets destroyed (otherwise we can't re-instantiate the media library during
     * the application runtime. That can't work for unit test and can be quite
     * a limitation for the application using the medialib)
     * Being able to refer to all per-thread connection means we can't use
     * thread local directly, however, we need to know if a thread goes away, to
     * avoid re-using an old connection on a new thread with the same id, as this
     * would result in a "Database is locked error"
     * In order to solve this, we use a single map to store all connection,
     * indexed by std::thread::id (well, compat::thread::id). Additionaly, in
     * order to know when a thread gets terminated, we store a thread_local
     * object to signal back when a thread returns, and to remove the now
     * unusable connection.
     * Also note that we need to flush any potentially cached compiled statement
     * when a thread gets terminated, as it would become unusable as well.
     *
     * \sa sqlite::Connection::ThreadSpecificConnection::~ThreadSpecificConnection
     * \sa sqlite::Statement::StatementsCache
     */
    std::unique_lock<compat::Mutex> lock( m_connMutex );
    auto it = m_conns.find( compat::this_thread::get_id() );
    if ( it == end( m_conns ) )
    {
        sqlite3* dbConnection;
        auto flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_NOMUTEX;
        if ( m_conns.empty() == true )
            flags |= SQLITE_OPEN_CREATE;
        auto res = sqlite3_open_v2( m_dbPath.c_str(), &dbConnection, flags, nullptr );
        ConnPtr dbConn( dbConnection, &sqlite3_close );
        if ( res != SQLITE_OK )
        {
            int err = sqlite3_system_errno(dbConnection);
            LOG_ERROR( "Failed to connect to database. OS error: ", err );
            errors::mapToException( "<connecting to db>", "", res );
        }
        /*
         * Fetch the absolute path to the database. If for whatever reason we were
         * to change directories during the runtime, we'd end up having a connection
         * to a different database in case the provided path is relative.
         * See #262
         */
        if ( m_conns.empty() == true )
        {
            m_dbPath = sqlite3_db_filename( dbConnection, nullptr );
            LOG_DEBUG( "Fetched absolute database path from sqlite: ", m_dbPath );
        }

        res = sqlite3_extended_result_codes( dbConnection, 1 );
        if ( res != SQLITE_OK )
            errors::mapToException( "<enabling extended errors>", "", res );
        // Don't use public wrapper, they need to be able to call getConn, which
        // would result from a recursive call and a deadlock from here.
        setPragma( dbConnection, "foreign_keys", "1" );
        setPragma( dbConnection, "recursive_triggers", "1" );
#ifdef __ANDROID__
        // https://github.com/mozilla/mentat/issues/505
        // Should solve `Failed to run request <DELETE FROM File WHERE id_file = ?>: disk I/O error(6410)`
        setPragma( dbConnection, "temp_store", "2" );
#endif
#if DEBUG_SQLITE_TRIGGERS
        sqlite3_trace_v2( dbConnection, SQLITE_TRACE_STMT | SQLITE_TRACE_CLOSE,
                          [](unsigned int t, void* , void* p, void* x) {
            if ( t == SQLITE_TRACE_STMT )
            {
                const char* str = static_cast<const char*>( x );
                LOG_VERBOSE( "Executed: ", str );
            }
            else if ( t == SQLITE_TRACE_CLOSE )
            {
                LOG_VERBOSE( "Connection ", p, " was closed" );
            }
            return 0;
        }, nullptr );
#endif
        m_conns.emplace( compat::this_thread::get_id(), std::move( dbConn ) );
        sqlite3_update_hook( dbConnection, &updateHook, this );
        static thread_local ThreadSpecificConnection tsc( shared_from_this() );
        return dbConnection;
    }
    return it->second.get();
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

}

}
<|endoftext|>


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

#include "modules/desktop_capture/mouse_cursor_monitor.h"

#include <assert.h>
#include <stddef.h>

#include <memory>

#include "modules/desktop_capture/desktop_capture_options.h"
#include "modules/desktop_capture/desktop_capturer.h"
#include "modules/desktop_capture/desktop_frame.h"
#include "modules/desktop_capture/mouse_cursor.h"
#include "rtc_base/checks.h"
#include "test/gtest.h"

namespace webrtc {

class MouseCursorMonitorTest : public ::testing::Test,
                               public MouseCursorMonitor::Callback {
 public:
  MouseCursorMonitorTest() : position_received_(false) {}

  // MouseCursorMonitor::Callback interface
  void OnMouseCursor(MouseCursor* cursor_image) override {
    cursor_image_.reset(cursor_image);
  }

  void OnMouseCursorPosition(const DesktopVector& position) override {
    position_ = position;
    position_received_ = true;
  }

 protected:
  std::unique_ptr<MouseCursor> cursor_image_;
  DesktopVector position_;
  bool position_received_;
};

// TODO(sergeyu): On Mac we need to initialize NSApplication before running the
// tests. Figure out how to do that without breaking other tests in
// modules_unittests and enable these tests on Mac.
// https://code.google.com/p/webrtc/issues/detail?id=2532
//
// Disabled on Windows due to flake, see:
// https://code.google.com/p/webrtc/issues/detail?id=3408
// Disabled on Linux due to flake, see:
// https://code.google.com/p/webrtc/issues/detail?id=3245
#if !defined(WEBRTC_MAC) && !defined(WEBRTC_WIN) && !defined(WEBRTC_LINUX)
#define MAYBE(x) x
#else
#define MAYBE(x) DISABLED_##x
#endif

TEST_F(MouseCursorMonitorTest, MAYBE(FromScreen)) {
  std::unique_ptr<MouseCursorMonitor> capturer(
      MouseCursorMonitor::CreateForScreen(
          DesktopCaptureOptions::CreateDefault(),
          webrtc::kFullDesktopScreenId));
  assert(capturer.get());
  capturer->Init(this, MouseCursorMonitor::SHAPE_AND_POSITION);
  capturer->Capture();

  EXPECT_TRUE(cursor_image_.get());
  EXPECT_GE(cursor_image_->hotspot().x(), 0);
  EXPECT_LE(cursor_image_->hotspot().x(),
            cursor_image_->image()->size().width());
  EXPECT_GE(cursor_image_->hotspot().y(), 0);
  EXPECT_LE(cursor_image_->hotspot().y(),
            cursor_image_->image()->size().height());

  EXPECT_TRUE(position_received_);
}

TEST_F(MouseCursorMonitorTest, MAYBE(FromWindow)) {
  DesktopCaptureOptions options = DesktopCaptureOptions::CreateDefault();

  // First get list of windows.
  std::unique_ptr<DesktopCapturer> window_capturer(
      DesktopCapturer::CreateWindowCapturer(options));

  // If window capturing is not supported then skip this test.
  if (!window_capturer.get())
    return;

  DesktopCapturer::SourceList sources;
  EXPECT_TRUE(window_capturer->GetSourceList(&sources));

  // Iterate over all windows and try capturing mouse cursor for each of them.
  for (size_t i = 0; i < sources.size(); ++i) {
    cursor_image_.reset();
    position_received_ = false;

    std::unique_ptr<MouseCursorMonitor> capturer(
        MouseCursorMonitor::CreateForWindow(
            DesktopCaptureOptions::CreateDefault(), sources[i].id));
    assert(capturer.get());

    capturer->Init(this, MouseCursorMonitor::SHAPE_AND_POSITION);
    capturer->Capture();

    EXPECT_TRUE(cursor_image_.get());
    EXPECT_TRUE(position_received_);
  }
}

// Make sure that OnMouseCursorPosition() is not called in the SHAPE_ONLY mode.
TEST_F(MouseCursorMonitorTest, MAYBE(ShapeOnly)) {
  std::unique_ptr<MouseCursorMonitor> capturer(
      MouseCursorMonitor::CreateForScreen(
          DesktopCaptureOptions::CreateDefault(),
          webrtc::kFullDesktopScreenId));
  assert(capturer.get());
  capturer->Init(this, MouseCursorMonitor::SHAPE_ONLY);
  capturer->Capture();

  EXPECT_TRUE(cursor_image_.get());
  EXPECT_FALSE(position_received_);
}

}  // namespace webrtc
<commit_msg>Reland "reenable mouse_cursor_monitor tests on linux"<commit_after>/*
 *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "modules/desktop_capture/mouse_cursor_monitor.h"

#include <assert.h>
#include <stddef.h>

#include <memory>

#include "modules/desktop_capture/desktop_capture_options.h"
#include "modules/desktop_capture/desktop_capturer.h"
#include "modules/desktop_capture/desktop_frame.h"
#include "modules/desktop_capture/mouse_cursor.h"
#include "rtc_base/checks.h"
#include "test/gtest.h"

namespace webrtc {

class MouseCursorMonitorTest : public ::testing::Test,
                               public MouseCursorMonitor::Callback {
 public:
  MouseCursorMonitorTest() : position_received_(false) {}

  // MouseCursorMonitor::Callback interface
  void OnMouseCursor(MouseCursor* cursor_image) override {
    cursor_image_.reset(cursor_image);
  }

  void OnMouseCursorPosition(const DesktopVector& position) override {
    position_ = position;
    position_received_ = true;
  }

 protected:
  std::unique_ptr<MouseCursor> cursor_image_;
  DesktopVector position_;
  bool position_received_;
};

// TODO(sergeyu): On Mac we need to initialize NSApplication before running the
// tests. Figure out how to do that without breaking other tests in
// modules_unittests and enable these tests on Mac.
// https://code.google.com/p/webrtc/issues/detail?id=2532
//
// Disabled on Windows due to flake, see:
// https://code.google.com/p/webrtc/issues/detail?id=3408
//
// Enabled on Linux only when building with X11 support.
#if !defined(WEBRTC_MAC) && !defined(WEBRTC_WIN) && \
    !(defined(WEBRTC_LINUX) && !defined(WEBRTC_USE_X11))
#define MAYBE(x) x
#else
#define MAYBE(x) DISABLED_##x
#endif

TEST_F(MouseCursorMonitorTest, MAYBE(FromScreen)) {
  std::unique_ptr<MouseCursorMonitor> capturer(
      MouseCursorMonitor::CreateForScreen(
          DesktopCaptureOptions::CreateDefault(),
          webrtc::kFullDesktopScreenId));
  assert(capturer.get());
  capturer->Init(this, MouseCursorMonitor::SHAPE_AND_POSITION);
  capturer->Capture();

  EXPECT_TRUE(cursor_image_.get());
  EXPECT_GE(cursor_image_->hotspot().x(), 0);
  EXPECT_LE(cursor_image_->hotspot().x(),
            cursor_image_->image()->size().width());
  EXPECT_GE(cursor_image_->hotspot().y(), 0);
  EXPECT_LE(cursor_image_->hotspot().y(),
            cursor_image_->image()->size().height());

  EXPECT_TRUE(position_received_);
}

TEST_F(MouseCursorMonitorTest, MAYBE(FromWindow)) {
  DesktopCaptureOptions options = DesktopCaptureOptions::CreateDefault();

  // First get list of windows.
  std::unique_ptr<DesktopCapturer> window_capturer(
      DesktopCapturer::CreateWindowCapturer(options));

  // If window capturing is not supported then skip this test.
  if (!window_capturer.get())
    return;

  DesktopCapturer::SourceList sources;
  EXPECT_TRUE(window_capturer->GetSourceList(&sources));

  // Iterate over all windows and try capturing mouse cursor for each of them.
  for (size_t i = 0; i < sources.size(); ++i) {
    cursor_image_.reset();
    position_received_ = false;

    std::unique_ptr<MouseCursorMonitor> capturer(
        MouseCursorMonitor::CreateForWindow(
            DesktopCaptureOptions::CreateDefault(), sources[i].id));
    assert(capturer.get());

    capturer->Init(this, MouseCursorMonitor::SHAPE_AND_POSITION);
    capturer->Capture();

    EXPECT_TRUE(cursor_image_.get());
    EXPECT_TRUE(position_received_);
  }
}

// Make sure that OnMouseCursorPosition() is not called in the SHAPE_ONLY mode.
TEST_F(MouseCursorMonitorTest, MAYBE(ShapeOnly)) {
  std::unique_ptr<MouseCursorMonitor> capturer(
      MouseCursorMonitor::CreateForScreen(
          DesktopCaptureOptions::CreateDefault(),
          webrtc::kFullDesktopScreenId));
  assert(capturer.get());
  capturer->Init(this, MouseCursorMonitor::SHAPE_ONLY);
  capturer->Capture();

  EXPECT_TRUE(cursor_image_.get());
  EXPECT_FALSE(position_received_);
}

}  // namespace webrtc
<|endoftext|>


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

/**
 * @file vdev_posix.cpp
 *
 * POSIX-like API for virtual character device
 */

#include <px4_log.h>
#include <px4_posix.h>
#include <px4_time.h>
#include "device.h"
#include "vfile.h"

#include <hrt_work.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>

using namespace device;

extern "C" {

	static void timer_cb(void *data)
	{
		px4_sem_t *p_sem = (px4_sem_t *)data;
		px4_sem_post(p_sem);
		PX4_DEBUG("timer_handler: Timer expired");
	}

#define PX4_MAX_FD 200
	static device::file_t *filemap[PX4_MAX_FD] = {};

	int px4_errno;

	inline bool valid_fd(int fd)
	{
		return (fd < PX4_MAX_FD && fd >= 0 && filemap[fd] != NULL);
	}

	int px4_open(const char *path, int flags, ...)
	{
		PX4_DEBUG("px4_open");
		VDev *dev = VDev::getDev(path);
		int ret = 0;
		int i;
		mode_t mode;

		if (!dev && (flags & (PX4_F_WRONLY | PX4_F_CREAT)) != 0 &&
		    strncmp(path, "/obj/", 5) != 0 &&
		    strncmp(path, "/dev/", 5) != 0) {
			va_list p;
			va_start(p, flags);
			mode = va_arg(p, mode_t);
			va_end(p);

			// Create the file
			PX4_DEBUG("Creating virtual file %s", path);
			dev = VFile::createFile(path, mode);
		}

		if (dev) {
			for (i = 0; i < PX4_MAX_FD; ++i) {
				if (filemap[i] == 0) {
					filemap[i] = new device::file_t(flags, dev, i);
					break;
				}
			}

			if (i < PX4_MAX_FD) {
				ret = dev->open(filemap[i]);

			} else {
				PX4_WARN("exceeded maximum number of file descriptors!");
				ret = -ENOENT;
			}

		} else {
			ret = -EINVAL;
		}

		if (ret < 0) {
			px4_errno = -ret;
			return -1;
		}

		PX4_DEBUG("px4_open fd = %d", filemap[i]->fd);
		return filemap[i]->fd;
	}

	int px4_close(int fd)
	{
		int ret;

		if (valid_fd(fd)) {
			VDev *dev = (VDev *)(filemap[fd]->vdev);
			PX4_DEBUG("px4_close fd = %d", fd);
			ret = dev->close(filemap[fd]);
			filemap[fd] = NULL;

		} else {
			ret = -EINVAL;
		}

		if (ret < 0) {
			px4_errno = -ret;
			ret = PX4_ERROR;
		}

		return ret;
	}

	ssize_t px4_read(int fd, void *buffer, size_t buflen)
	{
		int ret;

		if (valid_fd(fd)) {
			VDev *dev = (VDev *)(filemap[fd]->vdev);
			PX4_DEBUG("px4_read fd = %d", fd);
			ret = dev->read(filemap[fd], (char *)buffer, buflen);

		} else {
			ret = -EINVAL;
		}

		if (ret < 0) {
			px4_errno = -ret;
			ret = PX4_ERROR;
		}

		return ret;
	}

	ssize_t px4_write(int fd, const void *buffer, size_t buflen)
	{
		int ret;

		if (valid_fd(fd)) {
			VDev *dev = (VDev *)(filemap[fd]->vdev);
			PX4_DEBUG("px4_write fd = %d", fd);
			ret = dev->write(filemap[fd], (const char *)buffer, buflen);

		} else {
			ret = -EINVAL;
		}

		if (ret < 0) {
			px4_errno = -ret;
			ret = PX4_ERROR;
		}

		return ret;
	}

	int px4_ioctl(int fd, int cmd, unsigned long arg)
	{
		PX4_DEBUG("px4_ioctl fd = %d", fd);
		int ret = 0;

		if (valid_fd(fd)) {
			VDev *dev = (VDev *)(filemap[fd]->vdev);
			ret = dev->ioctl(filemap[fd], cmd, arg);

		} else {
			ret = -EINVAL;
		}

		if (ret < 0) {
			px4_errno = -ret;
		}

		return ret;
	}

	int px4_poll(px4_pollfd_struct_t *fds, nfds_t nfds, int timeout)
	{
		px4_sem_t sem;
		int count = 0;
		int ret = -1;
		unsigned int i;

		PX4_DEBUG("Called px4_poll timeout = %d", timeout);
		px4_sem_init(&sem, 0, 0);

		// For each fd
		for (i = 0; i < nfds; ++i) {
			fds[i].sem     = &sem;
			fds[i].revents = 0;
			fds[i].priv    = NULL;

			// If fd is valid
			if (valid_fd(fds[i].fd)) {
				VDev *dev = (VDev *)(filemap[fds[i].fd]->vdev);;
				PX4_DEBUG("px4_poll: VDev->poll(setup) %d", fds[i].fd);
				ret = dev->poll(filemap[fds[i].fd], &fds[i], true);

				if (ret < 0) {
					break;
				}
			}
		}

		if (ret >= 0) {
			if (timeout > 0) {
				// Use a work queue task
				work_s _hpwork;

				hrt_work_queue(&_hpwork, (worker_t)&timer_cb, (void *)&sem, 1000 * timeout);
				px4_sem_wait(&sem);

				// Make sure timer thread is killed before sem goes
				// out of scope
				hrt_work_cancel(&_hpwork);

			} else if (timeout < 0) {
				px4_sem_wait(&sem);
			}

			// For each fd
			for (i = 0; i < nfds; ++i) {
				// If fd is valid
				if (valid_fd(fds[i].fd)) {
					VDev *dev = (VDev *)(filemap[fds[i].fd]->vdev);;
					PX4_DEBUG("px4_poll: VDev->poll(teardown) %d", fds[i].fd);
					ret = dev->poll(filemap[fds[i].fd], &fds[i], false);

					if (ret < 0) {
						break;
					}

					if (fds[i].revents) {
						count += 1;
					}
				}
			}
		}

		px4_sem_destroy(&sem);

		return count;
	}

	int px4_fsync(int fd)
	{
		return 0;
	}

	int px4_access(const char *pathname, int mode)
	{
		if (mode != F_OK) {
			errno = EINVAL;
			return -1;
		}

		VDev *dev = VDev::getDev(pathname);
		return (dev != nullptr) ? 0 : -1;
	}

	void px4_show_devices()
	{
		VDev::showDevices();
	}

	void px4_show_topics()
	{
		VDev::showTopics();
	}

	void px4_show_files()
	{
		VDev::showFiles();
	}

	const char *px4_get_device_names(unsigned int *handle)
	{
		return VDev::devList(handle);
	}

	const char *px4_get_topic_names(unsigned int *handle)
	{
		return VDev::topicList(handle);
	}

}

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

/**
 * @file vdev_posix.cpp
 *
 * POSIX-like API for virtual character device
 */

#include <px4_log.h>
#include <px4_posix.h>
#include <px4_time.h>
#include "device.h"
#include "vfile.h"

#include <hrt_work.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>

using namespace device;

pthread_mutex_t filemutex = PTHREAD_MUTEX_INITIALIZER;

extern "C" {

	static void timer_cb(void *data)
	{
		px4_sem_t *p_sem = (px4_sem_t *)data;
		px4_sem_post(p_sem);
		PX4_DEBUG("timer_handler: Timer expired");
	}

#define PX4_MAX_FD 200
	static device::file_t *filemap[PX4_MAX_FD] = {};

	int px4_errno;

	inline bool valid_fd(int fd)
	{
		pthread_mutex_lock(&filemutex);
		bool ret = (fd < PX4_MAX_FD && fd >= 0 && filemap[fd] != NULL);
		pthread_mutex_unlock(&filemutex);
		return ret;
	}

	inline VDev *get_vdev(int fd)
	{
		pthread_mutex_lock(&filemutex);
		bool valid = (fd < PX4_MAX_FD && fd >= 0 && filemap[fd] != NULL);
		VDev *dev;

		if (valid) {
			dev = (VDev *)(filemap[fd]->vdev);

		} else {
			dev = nullptr;
		}

		pthread_mutex_unlock(&filemutex);
		return dev;
	}

	int px4_open(const char *path, int flags, ...)
	{
		PX4_DEBUG("px4_open");
		VDev *dev = VDev::getDev(path);
		int ret = 0;
		int i;
		mode_t mode;

		if (!dev && (flags & (PX4_F_WRONLY | PX4_F_CREAT)) != 0 &&
		    strncmp(path, "/obj/", 5) != 0 &&
		    strncmp(path, "/dev/", 5) != 0) {
			va_list p;
			va_start(p, flags);
			mode = va_arg(p, mode_t);
			va_end(p);

			// Create the file
			PX4_DEBUG("Creating virtual file %s", path);
			dev = VFile::createFile(path, mode);
		}

		if (dev) {

			pthread_mutex_lock(&filemutex);

			for (i = 0; i < PX4_MAX_FD; ++i) {
				if (filemap[i] == 0) {
					filemap[i] = new device::file_t(flags, dev, i);
					break;
				}
			}

			pthread_mutex_unlock(&filemutex);

			if (i < PX4_MAX_FD) {
				ret = dev->open(filemap[i]);

			} else {
				PX4_WARN("exceeded maximum number of file descriptors!");
				ret = -ENOENT;
			}

		} else {
			ret = -EINVAL;
		}

		if (ret < 0) {
			px4_errno = -ret;
			return -1;
		}

		PX4_DEBUG("px4_open fd = %d", filemap[i]->fd);
		return filemap[i]->fd;
	}

	int px4_close(int fd)
	{
		int ret;

		VDev *dev = get_vdev(fd);

		if (dev) {
			pthread_mutex_lock(&filemutex);
			ret = dev->close(filemap[fd]);
			filemap[fd] = nullptr;
			pthread_mutex_unlock(&filemutex);
			PX4_DEBUG("px4_close fd = %d", fd);

		} else {
			ret = -EINVAL;
		}

		if (ret < 0) {
			px4_errno = -ret;
			ret = PX4_ERROR;
		}

		return ret;
	}

	ssize_t px4_read(int fd, void *buffer, size_t buflen)
	{
		int ret;

		VDev *dev = get_vdev(fd);

		if (dev) {
			PX4_DEBUG("px4_read fd = %d", fd);
			ret = dev->read(filemap[fd], (char *)buffer, buflen);

		} else {
			ret = -EINVAL;
		}

		if (ret < 0) {
			px4_errno = -ret;
			ret = PX4_ERROR;
		}

		return ret;
	}

	ssize_t px4_write(int fd, const void *buffer, size_t buflen)
	{
		int ret;

		VDev *dev = get_vdev(fd);

		if (dev) {
			PX4_DEBUG("px4_write fd = %d", fd);
			ret = dev->write(filemap[fd], (const char *)buffer, buflen);

		} else {
			ret = -EINVAL;
		}

		if (ret < 0) {
			px4_errno = -ret;
			ret = PX4_ERROR;
		}

		return ret;
	}

	int px4_ioctl(int fd, int cmd, unsigned long arg)
	{
		PX4_DEBUG("px4_ioctl fd = %d", fd);
		int ret = 0;

		VDev *dev = get_vdev(fd);

		if (dev) {
			ret = dev->ioctl(filemap[fd], cmd, arg);

		} else {
			ret = -EINVAL;
		}

		if (ret < 0) {
			px4_errno = -ret;
		}

		return ret;
	}

	int px4_poll(px4_pollfd_struct_t *fds, nfds_t nfds, int timeout)
	{
		px4_sem_t sem;
		int count = 0;
		int ret = -1;
		unsigned int i;

		PX4_DEBUG("Called px4_poll timeout = %d", timeout);
		px4_sem_init(&sem, 0, 0);

		// For each fd
		for (i = 0; i < nfds; ++i) {
			fds[i].sem     = &sem;
			fds[i].revents = 0;
			fds[i].priv    = NULL;

			VDev *dev = get_vdev(fds[i].fd);

			// If fd is valid
			if (dev) {
				PX4_DEBUG("px4_poll: VDev->poll(setup) %d", fds[i].fd);
				ret = dev->poll(filemap[fds[i].fd], &fds[i], true);

				if (ret < 0) {
					break;
				}
			}
		}

		if (ret >= 0) {
			if (timeout > 0) {
				// Use a work queue task
				work_s _hpwork;

				hrt_work_queue(&_hpwork, (worker_t)&timer_cb, (void *)&sem, 1000 * timeout);
				px4_sem_wait(&sem);

				// Make sure timer thread is killed before sem goes
				// out of scope
				hrt_work_cancel(&_hpwork);

			} else if (timeout < 0) {
				px4_sem_wait(&sem);
			}

			// For each fd
			for (i = 0; i < nfds; ++i) {

				VDev *dev = get_vdev(fds[i].fd);

				// If fd is valid
				if (dev) {
					PX4_DEBUG("px4_poll: VDev->poll(teardown) %d", fds[i].fd);
					ret = dev->poll(filemap[fds[i].fd], &fds[i], false);

					if (ret < 0) {
						break;
					}

					if (fds[i].revents) {
						count += 1;
					}
				}
			}
		}

		px4_sem_destroy(&sem);

		return count;
	}

	int px4_fsync(int fd)
	{
		return 0;
	}

	int px4_access(const char *pathname, int mode)
	{
		if (mode != F_OK) {
			errno = EINVAL;
			return -1;
		}

		VDev *dev = VDev::getDev(pathname);
		return (dev != nullptr) ? 0 : -1;
	}

	void px4_show_devices()
	{
		VDev::showDevices();
	}

	void px4_show_topics()
	{
		VDev::showTopics();
	}

	void px4_show_files()
	{
		VDev::showFiles();
	}

	const char *px4_get_device_names(unsigned int *handle)
	{
		return VDev::devList(handle);
	}

	const char *px4_get_topic_names(unsigned int *handle)
	{
		return VDev::topicList(handle);
	}

}

<|endoftext|>


<commit_before>#include "widgetcurveeditor.h"

#include <QPainter>
#include <QMouseEvent>
#include <qt4/QtGui/qpaintengine.h>
#include "rendering/tools.h"

WidgetCurveEditor::WidgetCurveEditor(QWidget *parent, double xmin, double xmax, double ymin, double ymax) : QWidget(parent)
{
    _curve = curveCreate();
    _dragged = -1;
    _pen = QColor(0, 0, 0);

    _xmin = xmin;
    _xmax = xmax;
    _ymin = ymin;
    _ymax = ymax;

    setMinimumSize(300, 300);
}

WidgetCurveEditor::~WidgetCurveEditor()
{
    curveDelete(_curve);
}

void WidgetCurveEditor::setAxisLabels(QString xlabel, QString ylabel)
{
    _xlabel = xlabel;
    _ylabel = ylabel;
}

void WidgetCurveEditor::setCurve(Curve* curve)
{
    curveCopy(curve, _curve);
    update();
}

void WidgetCurveEditor::getCurve(Curve* curve)
{
    curveCopy(_curve, curve);
}

void WidgetCurveEditor::setPenColor(QColor color)
{
    _pen = color;
    update();
}

void WidgetCurveEditor::paintEvent(QPaintEvent*)
{
    int i, n;
    int width, height;
    double dwidth, dheight;
    CurvePoint point;
    double position, value, prev_value, next_value;

    width = this->width();
    height = this->height();
    dheight = (double)(height - 1);
    dwidth = (double)(width - 1);

    QPainter painter(this);
    painter.fillRect(rect(), Qt::white);

    // Draw grid
    painter.setPen(QPen(Qt::lightGray));
    for (int x = 0; x <= 10; x++)
    {
        int vx = (x == 10) ? width - 1 : x * width / 10;
        painter.drawLine(vx, 0, vx, height -1);
        vx = (x == 10) ? height - 1 : x * height / 10;
        painter.drawLine(0, vx, width - 1, vx);
    }

    // Draw labels
    painter.setPen(QColor(50, 50, 50));
    if (not _xlabel.isEmpty())
    {
        painter.drawText(rect(), Qt::AlignCenter | Qt::AlignBottom, _xlabel);
    }
    if (not _ylabel.isEmpty())
    {
        painter.rotate(-90.0);
        painter.translate(-height, 0.0);
        painter.drawText(0, 0, height, 20, Qt::AlignCenter | Qt::AlignTop, _ylabel);
        painter.translate(height, 0.0);
        painter.rotate(90.0);
    }

    // Draw curve path
    painter.setPen(_pen);
    for (int x = 0; x < width; x++)
    {
        position = ((double)x / dwidth) * (_xmax - _xmin) + _xmin;

        value = (curveGetValue(_curve, position) - _ymin) / (_ymax - _ymin);
        prev_value = curveGetValue(_curve, position - (_xmax - _xmin) / dwidth);
        next_value = curveGetValue(_curve, position + (_xmax - _xmin) / dwidth);

        painter.drawLine(x, height - 1 - (int)((value + (prev_value - value) / 2.0) * dheight), x, height - 1 - (int)((value + (next_value - value) / 2.0) * dheight));
        painter.drawPoint(x, height - 1 - (int)(value * dheight));
    }

    // Draw handles
    n = curveGetPointCount(_curve);
    painter.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing, true);
    for (i = 0; i < n; i++)
    {
        curveGetPoint(_curve, i, &point);
        painter.drawEllipse(QPointF((int)((point.position - _xmin) / (_xmax - _xmin) * dwidth), height - 1 - (int)((point.value - _ymin) / (_ymax - _ymin) * dheight)), 4.0, 4.0);
    }
}

void WidgetCurveEditor::mousePressEvent(QMouseEvent* event)
{
    if (event->button() == Qt::LeftButton && _dragged < 0)
    {
        _dragged = getPointAt(event->x(), event->y());
    }

    event->accept();
}

void WidgetCurveEditor::mouseMoveEvent(QMouseEvent* event)
{
    CurvePoint point;

    if (_dragged >= 0 && (event->buttons() & Qt::LeftButton))
    {
        screenToCurve(event->x(), event->y(), &point.position, &point.value);

        point.position = (point.position < _xmin) ? _xmin : point.position;
        point.position = (point.position > _xmax) ? _xmax : point.position;
        point.value = (point.value < _ymin) ? _ymin : point.value;
        point.value = (point.value > _ymax) ? _ymax : point.value;

        curveSetPoint(_curve, _dragged, &point);

        update();

        emit liveChanged();
    }

    event->accept();
}

void WidgetCurveEditor::mouseReleaseEvent(QMouseEvent* event)
{
    int clicked;

    if (event->button() == Qt::RightButton)
    {
        clicked = getPointAt(event->x(), event->y());
        if (clicked >= 0)
        {
            curveRemovePoint(_curve, clicked);
            update();
            emit liveChanged();
        }
    }
    else if (event->button() == Qt::LeftButton && _dragged >= 0)
    {
        _dragged = -1;
        curveValidate(_curve);
        update();
    }

    event->accept();
}

void WidgetCurveEditor::mouseDoubleClickEvent(QMouseEvent* event)
{
    CurvePoint point;

    if (event->button() == Qt::LeftButton && _dragged < 0)
    {
        if (getPointAt(event->x(), event->y()) < 0)
        {
            screenToCurve(event->x(), event->y(), &point.position, &point.value);
            curveAddPoint(_curve, &point);
            curveValidate(_curve);
            update();
            emit liveChanged();
        }
    }
}

void WidgetCurveEditor::curveToScreen(double curve_x, double curve_y, int* screen_x, int* screen_y)
{
    *screen_x = (int)((curve_x - _xmin) / (_xmax - _xmin) * (double)(width() - 1));
    *screen_y = (int)((1.0 - (curve_y - _ymin) / (_ymax - _ymin)) * (double)(height() - 1));
}

void WidgetCurveEditor::screenToCurve(int screen_x, int screen_y, double* curve_x, double* curve_y)
{
    *curve_x = ((double)screen_x / (double)(width() - 1)) * (_xmax - _xmin) + _xmin;
    *curve_y = (1.0 - ((double)screen_y / (double)(height() - 1))) * (_ymax - _ymin) + _ymin;
}

int WidgetCurveEditor::getPointAt(int x, int y)
{
    int n;
    int nearest;
    double distance, ndistance;
    CurvePoint point;
    int dx, dy;

    n = curveGetPointCount(_curve);
    if (n < 1)
    {
        return -1;
    }

    // Find nearest point
    nearest = -1;
    distance = 0.0;
    for (int i = 0; i < n; i++)
    {
        curveGetPoint(_curve, i, &point);
        curveToScreen(point.position, point.value, &dx, &dy);
        ndistance = euclidGetDistance2D((double)x, (double)y, (double)dx, (double)dy);
        if (nearest < 0 || ndistance < distance)
        {
            distance = ndistance;
            nearest = i;
        }
    }

    if (nearest >= 0 && distance < 5.0)
    {
        return nearest;
    }
    else
    {
        return -1;
    }
}
<commit_msg>Fixed a wrong qt4 import<commit_after>#include "widgetcurveeditor.h"

#include <QPainter>
#include <QPaintEngine>
#include <QMouseEvent>
#include "rendering/tools.h"

WidgetCurveEditor::WidgetCurveEditor(QWidget *parent, double xmin, double xmax, double ymin, double ymax) : QWidget(parent)
{
    _curve = curveCreate();
    _dragged = -1;
    _pen = QColor(0, 0, 0);

    _xmin = xmin;
    _xmax = xmax;
    _ymin = ymin;
    _ymax = ymax;

    setMinimumSize(300, 300);
}

WidgetCurveEditor::~WidgetCurveEditor()
{
    curveDelete(_curve);
}

void WidgetCurveEditor::setAxisLabels(QString xlabel, QString ylabel)
{
    _xlabel = xlabel;
    _ylabel = ylabel;
}

void WidgetCurveEditor::setCurve(Curve* curve)
{
    curveCopy(curve, _curve);
    update();
}

void WidgetCurveEditor::getCurve(Curve* curve)
{
    curveCopy(_curve, curve);
}

void WidgetCurveEditor::setPenColor(QColor color)
{
    _pen = color;
    update();
}

void WidgetCurveEditor::paintEvent(QPaintEvent*)
{
    int i, n;
    int width, height;
    double dwidth, dheight;
    CurvePoint point;
    double position, value, prev_value, next_value;

    width = this->width();
    height = this->height();
    dheight = (double)(height - 1);
    dwidth = (double)(width - 1);

    QPainter painter(this);
    painter.fillRect(rect(), Qt::white);

    // Draw grid
    painter.setPen(QPen(Qt::lightGray));
    for (int x = 0; x <= 10; x++)
    {
        int vx = (x == 10) ? width - 1 : x * width / 10;
        painter.drawLine(vx, 0, vx, height -1);
        vx = (x == 10) ? height - 1 : x * height / 10;
        painter.drawLine(0, vx, width - 1, vx);
    }

    // Draw labels
    painter.setPen(QColor(50, 50, 50));
    if (not _xlabel.isEmpty())
    {
        painter.drawText(rect(), Qt::AlignCenter | Qt::AlignBottom, _xlabel);
    }
    if (not _ylabel.isEmpty())
    {
        painter.rotate(-90.0);
        painter.translate(-height, 0.0);
        painter.drawText(0, 0, height, 20, Qt::AlignCenter | Qt::AlignTop, _ylabel);
        painter.translate(height, 0.0);
        painter.rotate(90.0);
    }

    // Draw curve path
    painter.setPen(_pen);
    for (int x = 0; x < width; x++)
    {
        position = ((double)x / dwidth) * (_xmax - _xmin) + _xmin;

        value = (curveGetValue(_curve, position) - _ymin) / (_ymax - _ymin);
        prev_value = curveGetValue(_curve, position - (_xmax - _xmin) / dwidth);
        next_value = curveGetValue(_curve, position + (_xmax - _xmin) / dwidth);

        painter.drawLine(x, height - 1 - (int)((value + (prev_value - value) / 2.0) * dheight), x, height - 1 - (int)((value + (next_value - value) / 2.0) * dheight));
        painter.drawPoint(x, height - 1 - (int)(value * dheight));
    }

    // Draw handles
    n = curveGetPointCount(_curve);
    painter.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing, true);
    for (i = 0; i < n; i++)
    {
        curveGetPoint(_curve, i, &point);
        painter.drawEllipse(QPointF((int)((point.position - _xmin) / (_xmax - _xmin) * dwidth), height - 1 - (int)((point.value - _ymin) / (_ymax - _ymin) * dheight)), 4.0, 4.0);
    }
}

void WidgetCurveEditor::mousePressEvent(QMouseEvent* event)
{
    if (event->button() == Qt::LeftButton && _dragged < 0)
    {
        _dragged = getPointAt(event->x(), event->y());
    }

    event->accept();
}

void WidgetCurveEditor::mouseMoveEvent(QMouseEvent* event)
{
    CurvePoint point;

    if (_dragged >= 0 && (event->buttons() & Qt::LeftButton))
    {
        screenToCurve(event->x(), event->y(), &point.position, &point.value);

        point.position = (point.position < _xmin) ? _xmin : point.position;
        point.position = (point.position > _xmax) ? _xmax : point.position;
        point.value = (point.value < _ymin) ? _ymin : point.value;
        point.value = (point.value > _ymax) ? _ymax : point.value;

        curveSetPoint(_curve, _dragged, &point);

        update();

        emit liveChanged();
    }

    event->accept();
}

void WidgetCurveEditor::mouseReleaseEvent(QMouseEvent* event)
{
    int clicked;

    if (event->button() == Qt::RightButton)
    {
        clicked = getPointAt(event->x(), event->y());
        if (clicked >= 0)
        {
            curveRemovePoint(_curve, clicked);
            update();
            emit liveChanged();
        }
    }
    else if (event->button() == Qt::LeftButton && _dragged >= 0)
    {
        _dragged = -1;
        curveValidate(_curve);
        update();
    }

    event->accept();
}

void WidgetCurveEditor::mouseDoubleClickEvent(QMouseEvent* event)
{
    CurvePoint point;

    if (event->button() == Qt::LeftButton && _dragged < 0)
    {
        if (getPointAt(event->x(), event->y()) < 0)
        {
            screenToCurve(event->x(), event->y(), &point.position, &point.value);
            curveAddPoint(_curve, &point);
            curveValidate(_curve);
            update();
            emit liveChanged();
        }
    }
}

void WidgetCurveEditor::curveToScreen(double curve_x, double curve_y, int* screen_x, int* screen_y)
{
    *screen_x = (int)((curve_x - _xmin) / (_xmax - _xmin) * (double)(width() - 1));
    *screen_y = (int)((1.0 - (curve_y - _ymin) / (_ymax - _ymin)) * (double)(height() - 1));
}

void WidgetCurveEditor::screenToCurve(int screen_x, int screen_y, double* curve_x, double* curve_y)
{
    *curve_x = ((double)screen_x / (double)(width() - 1)) * (_xmax - _xmin) + _xmin;
    *curve_y = (1.0 - ((double)screen_y / (double)(height() - 1))) * (_ymax - _ymin) + _ymin;
}

int WidgetCurveEditor::getPointAt(int x, int y)
{
    int n;
    int nearest;
    double distance, ndistance;
    CurvePoint point;
    int dx, dy;

    n = curveGetPointCount(_curve);
    if (n < 1)
    {
        return -1;
    }

    // Find nearest point
    nearest = -1;
    distance = 0.0;
    for (int i = 0; i < n; i++)
    {
        curveGetPoint(_curve, i, &point);
        curveToScreen(point.position, point.value, &dx, &dy);
        ndistance = euclidGetDistance2D((double)x, (double)y, (double)dx, (double)dy);
        if (nearest < 0 || ndistance < distance)
        {
            distance = ndistance;
            nearest = i;
        }
    }

    if (nearest >= 0 && distance < 5.0)
    {
        return nearest;
    }
    else
    {
        return -1;
    }
}
<|endoftext|>


<commit_before>/* This file is part of VoltDB.
 * Copyright (C) 2008-2014 VoltDB Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with VoltDB.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "storage/ElasticContext.h"
#include "storage/persistenttable.h"
#include "common/TupleOutputStreamProcessor.h"
#include "common/FixUnusedAssertHack.h"
#include "expressions/hashrangeexpression.h"
#include "logging/LogManager.h"
#include <cassert>
#include <sstream>
#include <limits>

namespace voltdb {

ElasticContext::ElasticContext(PersistentTable &table,
                               PersistentTableSurgeon &surgeon,
                               int32_t partitionId,
                               TupleSerializer &serializer,
                               const std::vector<std::string> &predicateStrings,
                               size_t nTuplesPerCall) :
    TableStreamerContext(table, surgeon, partitionId, serializer, predicateStrings),
    m_predicateStrings(predicateStrings), // retained for cloning here, not in TableStreamerContext.
    m_nTuplesPerCall(nTuplesPerCall),
    m_indexActive(false)
{
    if (predicateStrings.size() != 1) {
        throwFatalException("ElasticContext::ElasticContext() expects a single predicate.");
    }
}

ElasticContext::~ElasticContext()
{}

TableStreamerContext* ElasticContext::cloneForTruncatedTable(PersistentTableSurgeon &surgeon)
{
    if ( ! m_indexActive) {
        return NULL;
    }
    ElasticContext *cloned = new ElasticContext(surgeon.getTable(), surgeon,
        getPartitionId(), getSerializer(), m_predicateStrings, m_nTuplesPerCall);
    cloned->handleActivation(TABLE_STREAM_ELASTIC_INDEX);
    surgeon.setIndexingComplete();
    // The m_scanner is useless on the new table, don't care about its state?
    return cloned;
}

/**
 * Activation handler.
 */
TableStreamerContext::ActivationReturnCode
ElasticContext::handleActivation(TableStreamType streamType)
{
    // Create the index?
    if (streamType == TABLE_STREAM_ELASTIC_INDEX) {
        // Can't activate an indexing stream during a snapshot.
        if (m_surgeon.hasStreamType(TABLE_STREAM_SNAPSHOT)) {
            LogManager::getThreadLogger(LOGGERID_HOST)->log(LOGLEVEL_WARN,
                "Elastic context activation is not allowed while a snapshot is in progress.");
            return ACTIVATION_FAILED;
        }

        // Allow activation if there is an index, we will check when the predicates
        // are updated to make sure the existing index satisfies the request
        if (m_surgeon.hasIndex()) {
            LogManager::getThreadLogger(LOGGERID_HOST)->log(LOGLEVEL_INFO,
                "Activating elastic index build for index that already exists.");
            return ACTIVATION_SUCCEEDED;
        }
        m_surgeon.createIndex();
        m_scanner.reset(new ElasticScanner(getTable(), m_surgeon.getData()));
        m_indexActive = true;
        return ACTIVATION_SUCCEEDED;
    }

    // Clear the index?
    if (streamType == TABLE_STREAM_ELASTIC_INDEX_CLEAR) {
        if (m_surgeon.hasIndex()) {
            if (!m_surgeon.isIndexEmpty()) {

                std::ostringstream os;
                os << "Elastic index clear is not allowed while an index is "
                   << "present that has not been completely consumed."
                   << std::endl
                   << "Remaining index elements count is "
                   << m_surgeon.indexSize()
                   << std::endl;
                os << "the index contains: " << std::endl;
                const int32_t printUpTo = 1024;
                m_surgeon.printIndex(os, printUpTo);
                if (m_surgeon.indexSize() > printUpTo) {
                    os << "... " << (m_surgeon.indexSize() - printUpTo) << " more elements" << std::endl;
                }
                LogManager::getThreadLogger(LOGGERID_HOST)->log(LOGLEVEL_ERROR, os.str().c_str());

                return ACTIVATION_FAILED;
            }
            //Clear the predicates so when we are activated again we won't
            //compare against the old predicate
            m_predicates.clear();
            m_surgeon.dropIndex();
            m_scanner.reset();
            m_indexActive = false;
        }
        return ACTIVATION_SUCCEEDED;
    }

    // It wasn't one of the supported stream types.
    return ACTIVATION_UNSUPPORTED;
}

/**
 * Reactivation handler.
 */
TableStreamerContext::ActivationReturnCode
ElasticContext::handleReactivation(TableStreamType streamType)
{
    return handleActivation(streamType);
}

/**
 * Deactivation handler.
 */
bool ElasticContext::handleDeactivation(TableStreamType streamType)
{
    // Keep this context around to maintain the index.
    return true;
}

/*
 * Serialize to output stream.
 * Return remaining tuple count, 0 if done, or TABLE_STREAM_SERIALIZATION_ERROR on error.
 */
int64_t ElasticContext::handleStreamMore(TupleOutputStreamProcessor &outputStreams,
                                         std::vector<int> &retPositions)
{
    if (!m_surgeon.hasIndex()) {
        LogManager::getThreadLogger(LOGGERID_HOST)->log(LOGLEVEL_ERROR,
            "Elastic streaming was invoked without proper activation.");
        return TABLE_STREAM_SERIALIZATION_ERROR;
    }
    if (m_surgeon.isIndexingComplete()) {
        LogManager::getThreadLogger(LOGGERID_HOST)->log(LOGLEVEL_INFO,
            "Indexing was already complete.");
        return 0;
    }

    // Populate index with current tuples.
    // Table changes are tracked through notifications.
    size_t i = 0;
    TableTuple tuple(getTable().schema());
    while (m_scanner->next(tuple)) {
        if (getPredicates()[0].eval(&tuple).isTrue()) {
            m_surgeon.indexAdd(tuple);
        }
        // Take a breather after every chunk of m_nTuplesPerCall tuples.
        if (++i == m_nTuplesPerCall) {
            break;
        }
    }

    // Done with indexing?
    bool indexingComplete = m_scanner->isScanComplete();
    if (indexingComplete) {
        m_surgeon.setIndexingComplete();
    }
    return indexingComplete ? 0 : 1;
}

/**
 * Tuple insert handler lets us add late arriving tuples to the index.
 */
bool ElasticContext::notifyTupleInsert(TableTuple &tuple)
{
    if (m_indexActive) {
        StreamPredicateList &predicates = getPredicates();
        assert(predicates.size() > 0);
        if (predicates[0].eval(&tuple).isTrue()) {
            m_surgeon.indexAdd(tuple);
        }
    }
    return true;
}

/**
 * Tuple update handler is not currently needed.
 */
bool ElasticContext::notifyTupleUpdate(TableTuple &tuple)
{
    return true;
}

/**
 * Tuple delete handler lets us erase tuples from the index.
 */
bool ElasticContext::notifyTupleDelete(TableTuple &tuple)
{
    if (m_indexActive) {
        if (m_surgeon.indexHas(tuple)) {
            m_surgeon.indexRemove(tuple);
        }
    }
    return true;
}

/**
 * Tuple compaction handler lets us reindex when a tuple's address changes.
 */
void ElasticContext::notifyTupleMovement(TBPtr sourceBlock,
                                         TBPtr targetBlock,
                                         TableTuple &sourceTuple,
                                         TableTuple &targetTuple)
{
    if (m_indexActive) {
        StreamPredicateList &predicates = getPredicates();
        assert(predicates.size() > 0);
        if (m_surgeon.indexHas(sourceTuple)) {
            m_surgeon.indexRemove(sourceTuple);
        }
        if (predicates[0].eval(&targetTuple).isTrue()) {
            m_surgeon.indexAdd(targetTuple);
        }
    }
}

/**
 * Parse and save predicates.
 */
void ElasticContext::updatePredicates(const std::vector<std::string> &predicateStrings) {
    //If there is already a predicate and thus presumably an index, make sure the request is a subset of what exists
    //That should always be the case, but wrong answers will follow if we are wrong
    if (m_predicates.size() > 0 && dynamic_cast<HashRangeExpression*>(&m_predicates[0]) != NULL && predicateStrings.size() > 0) {
        PlannerDomRoot domRoot(predicateStrings[0].c_str());
        if (!domRoot.isNull()) {
            PlannerDomValue predicateObject = domRoot.rootObject();
            HashRangeExpression *expression = dynamic_cast<HashRangeExpression*>(&m_predicates[0]);
            if (predicateObject.hasKey("predicateExpression")) {
                PlannerDomValue predicateExpression = predicateObject.valueForKey("predicateExpression");
                PlannerDomValue rangesArray = predicateExpression.valueForKey("RANGES");
                for (int ii = 0; ii < rangesArray.arrayLen(); ii++) {
                    PlannerDomValue arrayObject = rangesArray.valueAtIndex(ii);
                    PlannerDomValue rangeStartValue = arrayObject.valueForKey("RANGE_START");
                    PlannerDomValue rangeEndValue = arrayObject.valueForKey("RANGE_END");
                    if (!expression->binarySearch(rangeStartValue.asInt()).isTrue()) {
                        throwFatalException("ElasticContext activate failed because a context already existed with conflicting ranges, conflicting range start is %d", rangeStartValue.asInt());
                    }
                    if (!expression->binarySearch(rangeEndValue.asInt()).isTrue()) {
                        throwFatalException("ElasticContext activate failed because a context already existed with conflicting ranges, conflicting range end is %d", rangeStartValue.asInt());
                    }
                }
            }
        }
    }
    m_predicateStrings = predicateStrings; // retain for possible clone after TRUNCATE TABLE
    TableStreamerContext::updatePredicates(predicateStrings);
}

} // namespace voltdb
<commit_msg>ENG-6158: Build the elastic index during ElasticContext cloning.<commit_after>/* This file is part of VoltDB.
 * Copyright (C) 2008-2014 VoltDB Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with VoltDB.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "storage/ElasticContext.h"
#include "storage/persistenttable.h"
#include "common/TupleOutputStreamProcessor.h"
#include "common/FixUnusedAssertHack.h"
#include "expressions/hashrangeexpression.h"
#include "logging/LogManager.h"
#include <cassert>
#include <sstream>
#include <limits>

namespace voltdb {

ElasticContext::ElasticContext(PersistentTable &table,
                               PersistentTableSurgeon &surgeon,
                               int32_t partitionId,
                               TupleSerializer &serializer,
                               const std::vector<std::string> &predicateStrings,
                               size_t nTuplesPerCall) :
    TableStreamerContext(table, surgeon, partitionId, serializer, predicateStrings),
    m_predicateStrings(predicateStrings), // retained for cloning here, not in TableStreamerContext.
    m_nTuplesPerCall(nTuplesPerCall),
    m_indexActive(false)
{
    if (predicateStrings.size() != 1) {
        throwFatalException("ElasticContext::ElasticContext() expects a single predicate.");
    }
}

ElasticContext::~ElasticContext()
{}

TableStreamerContext* ElasticContext::cloneForTruncatedTable(PersistentTableSurgeon &surgeon)
{
    if ( ! m_indexActive) {
        return NULL;
    }
    ElasticContext *cloned = new ElasticContext(surgeon.getTable(), surgeon,
        getPartitionId(), getSerializer(), m_predicateStrings, m_nTuplesPerCall);
    cloned->handleActivation(TABLE_STREAM_ELASTIC_INDEX);

    TupleOutputStreamProcessor dummyProcessor;
    std::vector<int> dummyPosition;
    while (true) {
        int64_t retCode = cloned->handleStreamMore(dummyProcessor, dummyPosition);
        if (retCode == 0) {
            break;
        } else if (retCode == TABLE_STREAM_SERIALIZATION_ERROR) {
            break;
        } else if (retCode == 1) {
            continue;
        } else {
            char errMsg[1024];
            snprintf(errMsg, 1024, "Received an unrecognized return value %jd from handleStreamMore()", retCode);
            LogManager::getThreadLogger(LOGGERID_HOST)->log(LOGLEVEL_ERROR, errMsg);
        }
    }

    return cloned;
}

/**
 * Activation handler.
 */
TableStreamerContext::ActivationReturnCode
ElasticContext::handleActivation(TableStreamType streamType)
{
    // Create the index?
    if (streamType == TABLE_STREAM_ELASTIC_INDEX) {
        // Can't activate an indexing stream during a snapshot.
        if (m_surgeon.hasStreamType(TABLE_STREAM_SNAPSHOT)) {
            LogManager::getThreadLogger(LOGGERID_HOST)->log(LOGLEVEL_WARN,
                "Elastic context activation is not allowed while a snapshot is in progress.");
            return ACTIVATION_FAILED;
        }

        // Allow activation if there is an index, we will check when the predicates
        // are updated to make sure the existing index satisfies the request
        if (m_surgeon.hasIndex()) {
            LogManager::getThreadLogger(LOGGERID_HOST)->log(LOGLEVEL_INFO,
                "Activating elastic index build for index that already exists.");
            return ACTIVATION_SUCCEEDED;
        }
        m_surgeon.createIndex();
        m_scanner.reset(new ElasticScanner(getTable(), m_surgeon.getData()));
        m_indexActive = true;
        return ACTIVATION_SUCCEEDED;
    }

    // Clear the index?
    if (streamType == TABLE_STREAM_ELASTIC_INDEX_CLEAR) {
        if (m_surgeon.hasIndex()) {
            if (!m_surgeon.isIndexEmpty()) {

                std::ostringstream os;
                os << "Elastic index clear is not allowed while an index is "
                   << "present that has not been completely consumed."
                   << std::endl
                   << "Remaining index elements count is "
                   << m_surgeon.indexSize()
                   << std::endl;
                os << "the index contains: " << std::endl;
                const int32_t printUpTo = 1024;
                m_surgeon.printIndex(os, printUpTo);
                if (m_surgeon.indexSize() > printUpTo) {
                    os << "... " << (m_surgeon.indexSize() - printUpTo) << " more elements" << std::endl;
                }
                LogManager::getThreadLogger(LOGGERID_HOST)->log(LOGLEVEL_ERROR, os.str().c_str());

                return ACTIVATION_FAILED;
            }
            //Clear the predicates so when we are activated again we won't
            //compare against the old predicate
            m_predicates.clear();
            m_surgeon.dropIndex();
            m_scanner.reset();
            m_indexActive = false;
        }
        return ACTIVATION_SUCCEEDED;
    }

    // It wasn't one of the supported stream types.
    return ACTIVATION_UNSUPPORTED;
}

/**
 * Reactivation handler.
 */
TableStreamerContext::ActivationReturnCode
ElasticContext::handleReactivation(TableStreamType streamType)
{
    return handleActivation(streamType);
}

/**
 * Deactivation handler.
 */
bool ElasticContext::handleDeactivation(TableStreamType streamType)
{
    // Keep this context around to maintain the index.
    return true;
}

/*
 * Serialize to output stream.
 * Return remaining tuple count, 0 if done, or TABLE_STREAM_SERIALIZATION_ERROR on error.
 */
int64_t ElasticContext::handleStreamMore(TupleOutputStreamProcessor &outputStreams,
                                         std::vector<int> &retPositions)
{
    if (!m_surgeon.hasIndex()) {
        LogManager::getThreadLogger(LOGGERID_HOST)->log(LOGLEVEL_ERROR,
            "Elastic streaming was invoked without proper activation.");
        return TABLE_STREAM_SERIALIZATION_ERROR;
    }
    if (m_surgeon.isIndexingComplete()) {
        LogManager::getThreadLogger(LOGGERID_HOST)->log(LOGLEVEL_INFO,
            "Indexing was already complete.");
        return 0;
    }

    // Populate index with current tuples.
    // Table changes are tracked through notifications.
    size_t i = 0;
    TableTuple tuple(getTable().schema());
    while (m_scanner->next(tuple)) {
        if (getPredicates()[0].eval(&tuple).isTrue()) {
            m_surgeon.indexAdd(tuple);
        }
        // Take a breather after every chunk of m_nTuplesPerCall tuples.
        if (++i == m_nTuplesPerCall) {
            break;
        }
    }

    // Done with indexing?
    bool indexingComplete = m_scanner->isScanComplete();
    if (indexingComplete) {
        m_surgeon.setIndexingComplete();
    }
    return indexingComplete ? 0 : 1;
}

/**
 * Tuple insert handler lets us add late arriving tuples to the index.
 */
bool ElasticContext::notifyTupleInsert(TableTuple &tuple)
{
    if (m_indexActive) {
        StreamPredicateList &predicates = getPredicates();
        assert(predicates.size() > 0);
        if (predicates[0].eval(&tuple).isTrue()) {
            m_surgeon.indexAdd(tuple);
        }
    }
    return true;
}

/**
 * Tuple update handler is not currently needed.
 */
bool ElasticContext::notifyTupleUpdate(TableTuple &tuple)
{
    return true;
}

/**
 * Tuple delete handler lets us erase tuples from the index.
 */
bool ElasticContext::notifyTupleDelete(TableTuple &tuple)
{
    if (m_indexActive) {
        if (m_surgeon.indexHas(tuple)) {
            m_surgeon.indexRemove(tuple);
        }
    }
    return true;
}

/**
 * Tuple compaction handler lets us reindex when a tuple's address changes.
 */
void ElasticContext::notifyTupleMovement(TBPtr sourceBlock,
                                         TBPtr targetBlock,
                                         TableTuple &sourceTuple,
                                         TableTuple &targetTuple)
{
    if (m_indexActive) {
        StreamPredicateList &predicates = getPredicates();
        assert(predicates.size() > 0);
        if (m_surgeon.indexHas(sourceTuple)) {
            m_surgeon.indexRemove(sourceTuple);
        }
        if (predicates[0].eval(&targetTuple).isTrue()) {
            m_surgeon.indexAdd(targetTuple);
        }
    }
}

/**
 * Parse and save predicates.
 */
void ElasticContext::updatePredicates(const std::vector<std::string> &predicateStrings) {
    //If there is already a predicate and thus presumably an index, make sure the request is a subset of what exists
    //That should always be the case, but wrong answers will follow if we are wrong
    if (m_predicates.size() > 0 && dynamic_cast<HashRangeExpression*>(&m_predicates[0]) != NULL && predicateStrings.size() > 0) {
        PlannerDomRoot domRoot(predicateStrings[0].c_str());
        if (!domRoot.isNull()) {
            PlannerDomValue predicateObject = domRoot.rootObject();
            HashRangeExpression *expression = dynamic_cast<HashRangeExpression*>(&m_predicates[0]);
            if (predicateObject.hasKey("predicateExpression")) {
                PlannerDomValue predicateExpression = predicateObject.valueForKey("predicateExpression");
                PlannerDomValue rangesArray = predicateExpression.valueForKey("RANGES");
                for (int ii = 0; ii < rangesArray.arrayLen(); ii++) {
                    PlannerDomValue arrayObject = rangesArray.valueAtIndex(ii);
                    PlannerDomValue rangeStartValue = arrayObject.valueForKey("RANGE_START");
                    PlannerDomValue rangeEndValue = arrayObject.valueForKey("RANGE_END");
                    if (!expression->binarySearch(rangeStartValue.asInt()).isTrue()) {
                        throwFatalException("ElasticContext activate failed because a context already existed with conflicting ranges, conflicting range start is %d", rangeStartValue.asInt());
                    }
                    if (!expression->binarySearch(rangeEndValue.asInt()).isTrue()) {
                        throwFatalException("ElasticContext activate failed because a context already existed with conflicting ranges, conflicting range end is %d", rangeStartValue.asInt());
                    }
                }
            }
        }
    }
    m_predicateStrings = predicateStrings; // retain for possible clone after TRUNCATE TABLE
    TableStreamerContext::updatePredicates(predicateStrings);
}

} // namespace voltdb
<|endoftext|>