<commit_before>/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "src/trace_processor/table.h"

#include <ctype.h>
#include <string.h>

#include "perfetto/base/logging.h"

namespace perfetto {
namespace trace_processor {

namespace {

struct TableDescriptor {
  Table::Factory factory;
  const TraceStorage* storage = nullptr;
  std::string name;
  sqlite3_module module = {};
};

Table* ToTable(sqlite3_vtab* vtab) {
  return static_cast<Table*>(vtab);
}

Table::RawCursor* ToCursor(sqlite3_vtab_cursor* cursor) {
  return static_cast<Table::RawCursor*>(cursor);
}

std::string TypeToString(Table::ColumnType type) {
  switch (type) {
    case Table::ColumnType::kString:
      return "STRING";
    case Table::ColumnType::kUint:
      return "UNSIGNED INT";
    case Table::ColumnType::kUlong:
      return "UNSIGNED BIG INT";
    case Table::ColumnType::kInt:
      return "INT";
  }
  PERFETTO_CHECK(false);
}

}  // namespace

// static
bool Table::debug = false;

Table::Table() = default;
Table::~Table() = default;

void Table::RegisterInternal(sqlite3* db,
                             const TraceStorage* storage,
                             const std::string& table_name,
                             bool read_write,
                             Factory factory) {
  std::unique_ptr<TableDescriptor> desc(new TableDescriptor());
  desc->storage = storage;
  desc->factory = factory;
  desc->name = table_name;
  sqlite3_module* module = &desc->module;
  memset(module, 0, sizeof(*module));

  auto create_fn = [](sqlite3* xdb, void* arg, int argc,
                      const char* const* argv, sqlite3_vtab** tab, char**) {
    const TableDescriptor* xdesc = static_cast<const TableDescriptor*>(arg);
    auto table = xdesc->factory(xdb, xdesc->storage);

    auto schema = table->CreateSchema(argc, argv);
    auto create_stmt = schema.ToCreateTableStmt();

    int res = sqlite3_declare_vtab(xdb, create_stmt.c_str());
    if (res != SQLITE_OK)
      return res;

    // Freed in xDisconnect().
    table->schema_ = schema;
    table->name_ = xdesc->name;
    *tab = table.release();

    return SQLITE_OK;
  };
  module->xCreate = create_fn;
  module->xConnect = create_fn;

  auto destroy_fn = [](sqlite3_vtab* t) {
    delete ToTable(t);
    return SQLITE_OK;
  };
  module->xDisconnect = destroy_fn;
  module->xDestroy = destroy_fn;

  module->xOpen = [](sqlite3_vtab* t, sqlite3_vtab_cursor** c) {
    return ToTable(t)->OpenInternal(c);
  };

  module->xClose = [](sqlite3_vtab_cursor* c) {
    delete ToCursor(c);
    return SQLITE_OK;
  };

  module->xBestIndex = [](sqlite3_vtab* t, sqlite3_index_info* i) {
    return ToTable(t)->BestIndexInternal(i);
  };

  module->xFilter = [](sqlite3_vtab_cursor* c, int i, const char* s, int a,
                       sqlite3_value** v) {
    return ToCursor(c)->Filter(i, s, a, v);
  };
  module->xNext = [](sqlite3_vtab_cursor* c) {
    return ToCursor(c)->cursor()->Next();
  };
  module->xEof = [](sqlite3_vtab_cursor* c) {
    return ToCursor(c)->cursor()->Eof();
  };
  module->xColumn = [](sqlite3_vtab_cursor* c, sqlite3_context* a, int b) {
    return ToCursor(c)->cursor()->Column(a, b);
  };

  module->xRowid = [](sqlite3_vtab_cursor* c, sqlite3_int64* r) {
    return ToCursor(c)->cursor()->RowId(r);
  };

  module->xFindFunction =
      [](sqlite3_vtab* t, int, const char* name,
         void (**fn)(sqlite3_context*, int, sqlite3_value**),
         void** args) { return ToTable(t)->FindFunction(name, fn, args); };

  if (read_write) {
    module->xUpdate = [](sqlite3_vtab* t, int a, sqlite3_value** v,
                         sqlite3_int64* r) {
      return ToTable(t)->Update(a, v, r);
    };
  }

  int res = sqlite3_create_module_v2(
      db, table_name.c_str(), module, desc.release(),
      [](void* arg) { delete static_cast<TableDescriptor*>(arg); });
  PERFETTO_CHECK(res == SQLITE_OK);
}

int Table::OpenInternal(sqlite3_vtab_cursor** ppCursor) {
  // Freed in xClose().
  *ppCursor = static_cast<sqlite3_vtab_cursor*>(new RawCursor(this));
  return SQLITE_OK;
}

int Table::BestIndexInternal(sqlite3_index_info* idx) {
  QueryConstraints query_constraints;

  for (int i = 0; i < idx->nOrderBy; i++) {
    int column = idx->aOrderBy[i].iColumn;
    bool desc = idx->aOrderBy[i].desc;
    query_constraints.AddOrderBy(column, desc);
  }

  for (int i = 0; i < idx->nConstraint; i++) {
    const auto& cs = idx->aConstraint[i];
    if (!cs.usable)
      continue;
    query_constraints.AddConstraint(cs.iColumn, cs.op);

    // argvIndex is 1-based so use the current size of the vector.
    int argv_index = static_cast<int>(query_constraints.constraints().size());
    idx->aConstraintUsage[i].argvIndex = argv_index;
  }

  BestIndexInfo info;
  info.omit.resize(query_constraints.constraints().size());

  int ret = BestIndex(query_constraints, &info);

  if (Table::debug) {
    PERFETTO_LOG(
        "[%s::BestIndex] constraints=%s orderByConsumed=%d estimatedCost=%d",
        name_.c_str(), query_constraints.ToNewSqlite3String().get(),
        info.order_by_consumed, info.estimated_cost);
  }

  if (ret != SQLITE_OK)
    return ret;

  idx->orderByConsumed = info.order_by_consumed;
  idx->estimatedCost = info.estimated_cost;

  size_t j = 0;
  for (int i = 0; i < idx->nConstraint; i++) {
    const auto& cs = idx->aConstraint[i];
    if (cs.usable)
      idx->aConstraintUsage[i].omit = info.omit[j++];
  }

  if (!info.order_by_consumed)
    query_constraints.ClearOrderBy();

  idx->idxStr = query_constraints.ToNewSqlite3String().release();
  idx->needToFreeIdxStr = true;
  idx->idxNum = ++best_index_num_;

  return SQLITE_OK;
}

int Table::FindFunction(const char*, FindFunctionFn, void**) {
  return 0;
}

int Table::Update(int, sqlite3_value**, sqlite3_int64*) {
  return SQLITE_READONLY;
}

Table::RawCursor::RawCursor(Table* table) : table_(table) {}

int Table::RawCursor::Filter(int idxNum,
                             const char* idxStr,
                             int argc,
                             sqlite3_value** argv) {
  auto* table = ToTable(this->pVtab);
  bool cache_hit = true;
  if (idxNum != table->qc_hash_) {
    table->qc_cache_ = QueryConstraints::FromString(idxStr);
    table->qc_hash_ = idxNum;
    cache_hit = false;
  }
  if (Table::debug) {
    PERFETTO_LOG("[%s::Filter] constraints=%s argc=%d cache_hit=%d",
                 table->name_.c_str(), idxStr, argc, cache_hit);
  }
  PERFETTO_DCHECK(table->qc_cache_.constraints().size() ==
                  static_cast<size_t>(argc));
  cursor_ = table_->CreateCursor(table->qc_cache_, argv);
  return !cursor_ ? SQLITE_ERROR : SQLITE_OK;
}

Table::Cursor::~Cursor() = default;

int Table::Cursor::RowId(sqlite3_int64*) {
  return SQLITE_ERROR;
}

Table::Column::Column(size_t index,
                      std::string name,
                      ColumnType type,
                      bool hidden)
    : index_(index), name_(name), type_(type), hidden_(hidden) {}

Table::Schema::Schema(std::vector<Column> columns,
                      std::vector<size_t> primary_keys)
    : columns_(std::move(columns)), primary_keys_(std::move(primary_keys)) {
  for (size_t i = 0; i < columns_.size(); i++) {
    PERFETTO_CHECK(columns_[i].index() == i);
  }
  for (auto key : primary_keys_) {
    PERFETTO_CHECK(key < columns_.size());
  }
}

Table::Schema::Schema() = default;
Table::Schema::Schema(const Schema&) = default;
Table::Schema& Table::Schema::operator=(const Schema&) = default;

std::string Table::Schema::ToCreateTableStmt() {
  std::string stmt = "CREATE TABLE x(";
  for (const auto& col : columns_) {
    stmt += " " + col.name() + " " + TypeToString(col.type()) + ",";
  }
  stmt += " PRIMARY KEY(";
  for (size_t i = 0; i < primary_keys_.size(); i++) {
    if (i != 0)
      stmt += ", ";
    stmt += columns_[primary_keys_[i]].name();
  }
  stmt += ")) WITHOUT ROWID;";
  return stmt;
}

}  // namespace trace_processor
}  // namespace perfetto
<commit_msg>trace_processor: fix missing noexcept breaking build in Google3 am: 42319e90a8<commit_after>/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "src/trace_processor/table.h"

#include <ctype.h>
#include <string.h>

#include "perfetto/base/logging.h"

namespace perfetto {
namespace trace_processor {

namespace {

struct TableDescriptor {
  Table::Factory factory;
  const TraceStorage* storage = nullptr;
  std::string name;
  sqlite3_module module = {};
};

Table* ToTable(sqlite3_vtab* vtab) {
  return static_cast<Table*>(vtab);
}

Table::RawCursor* ToCursor(sqlite3_vtab_cursor* cursor) {
  return static_cast<Table::RawCursor*>(cursor);
}

std::string TypeToString(Table::ColumnType type) {
  switch (type) {
    case Table::ColumnType::kString:
      return "STRING";
    case Table::ColumnType::kUint:
      return "UNSIGNED INT";
    case Table::ColumnType::kUlong:
      return "UNSIGNED BIG INT";
    case Table::ColumnType::kInt:
      return "INT";
  }
  PERFETTO_CHECK(false);
}

}  // namespace

// static
bool Table::debug = false;

Table::Table() = default;
Table::~Table() = default;

void Table::RegisterInternal(sqlite3* db,
                             const TraceStorage* storage,
                             const std::string& table_name,
                             bool read_write,
                             Factory factory) {
  std::unique_ptr<TableDescriptor> desc(new TableDescriptor());
  desc->storage = storage;
  desc->factory = factory;
  desc->name = table_name;
  sqlite3_module* module = &desc->module;
  memset(module, 0, sizeof(*module));

  auto create_fn = [](sqlite3* xdb, void* arg, int argc,
                      const char* const* argv, sqlite3_vtab** tab, char**) {
    const TableDescriptor* xdesc = static_cast<const TableDescriptor*>(arg);
    auto table = xdesc->factory(xdb, xdesc->storage);

    auto schema = table->CreateSchema(argc, argv);
    auto create_stmt = schema.ToCreateTableStmt();

    int res = sqlite3_declare_vtab(xdb, create_stmt.c_str());
    if (res != SQLITE_OK)
      return res;

    // Freed in xDisconnect().
    table->schema_ = schema;
    table->name_ = xdesc->name;
    *tab = table.release();

    return SQLITE_OK;
  };
  module->xCreate = create_fn;
  module->xConnect = create_fn;

  auto destroy_fn = [](sqlite3_vtab* t) {
    delete ToTable(t);
    return SQLITE_OK;
  };
  module->xDisconnect = destroy_fn;
  module->xDestroy = destroy_fn;

  module->xOpen = [](sqlite3_vtab* t, sqlite3_vtab_cursor** c) {
    return ToTable(t)->OpenInternal(c);
  };

  module->xClose = [](sqlite3_vtab_cursor* c) {
    delete ToCursor(c);
    return SQLITE_OK;
  };

  module->xBestIndex = [](sqlite3_vtab* t, sqlite3_index_info* i) {
    return ToTable(t)->BestIndexInternal(i);
  };

  module->xFilter = [](sqlite3_vtab_cursor* c, int i, const char* s, int a,
                       sqlite3_value** v) {
    return ToCursor(c)->Filter(i, s, a, v);
  };
  module->xNext = [](sqlite3_vtab_cursor* c) {
    return ToCursor(c)->cursor()->Next();
  };
  module->xEof = [](sqlite3_vtab_cursor* c) {
    return ToCursor(c)->cursor()->Eof();
  };
  module->xColumn = [](sqlite3_vtab_cursor* c, sqlite3_context* a, int b) {
    return ToCursor(c)->cursor()->Column(a, b);
  };

  module->xRowid = [](sqlite3_vtab_cursor* c, sqlite3_int64* r) {
    return ToCursor(c)->cursor()->RowId(r);
  };

  module->xFindFunction =
      [](sqlite3_vtab* t, int, const char* name,
         void (**fn)(sqlite3_context*, int, sqlite3_value**),
         void** args) { return ToTable(t)->FindFunction(name, fn, args); };

  if (read_write) {
    module->xUpdate = [](sqlite3_vtab* t, int a, sqlite3_value** v,
                         sqlite3_int64* r) {
      return ToTable(t)->Update(a, v, r);
    };
  }

  int res = sqlite3_create_module_v2(
      db, table_name.c_str(), module, desc.release(),
      [](void* arg) { delete static_cast<TableDescriptor*>(arg); });
  PERFETTO_CHECK(res == SQLITE_OK);
}

int Table::OpenInternal(sqlite3_vtab_cursor** ppCursor) {
  // Freed in xClose().
  *ppCursor = static_cast<sqlite3_vtab_cursor*>(new RawCursor(this));
  return SQLITE_OK;
}

int Table::BestIndexInternal(sqlite3_index_info* idx) {
  QueryConstraints query_constraints;

  for (int i = 0; i < idx->nOrderBy; i++) {
    int column = idx->aOrderBy[i].iColumn;
    bool desc = idx->aOrderBy[i].desc;
    query_constraints.AddOrderBy(column, desc);
  }

  for (int i = 0; i < idx->nConstraint; i++) {
    const auto& cs = idx->aConstraint[i];
    if (!cs.usable)
      continue;
    query_constraints.AddConstraint(cs.iColumn, cs.op);

    // argvIndex is 1-based so use the current size of the vector.
    int argv_index = static_cast<int>(query_constraints.constraints().size());
    idx->aConstraintUsage[i].argvIndex = argv_index;
  }

  BestIndexInfo info;
  info.omit.resize(query_constraints.constraints().size());

  int ret = BestIndex(query_constraints, &info);

  if (Table::debug) {
    PERFETTO_LOG(
        "[%s::BestIndex] constraints=%s orderByConsumed=%d estimatedCost=%d",
        name_.c_str(), query_constraints.ToNewSqlite3String().get(),
        info.order_by_consumed, info.estimated_cost);
  }

  if (ret != SQLITE_OK)
    return ret;

  idx->orderByConsumed = info.order_by_consumed;
  idx->estimatedCost = info.estimated_cost;

  size_t j = 0;
  for (int i = 0; i < idx->nConstraint; i++) {
    const auto& cs = idx->aConstraint[i];
    if (cs.usable)
      idx->aConstraintUsage[i].omit = info.omit[j++];
  }

  if (!info.order_by_consumed)
    query_constraints.ClearOrderBy();

  idx->idxStr = query_constraints.ToNewSqlite3String().release();
  idx->needToFreeIdxStr = true;
  idx->idxNum = ++best_index_num_;

  return SQLITE_OK;
}

int Table::FindFunction(const char*, FindFunctionFn, void**) {
  return 0;
}

int Table::Update(int, sqlite3_value**, sqlite3_int64*) {
  return SQLITE_READONLY;
}

Table::RawCursor::RawCursor(Table* table) : table_(table) {}

int Table::RawCursor::Filter(int idxNum,
                             const char* idxStr,
                             int argc,
                             sqlite3_value** argv) {
  auto* table = ToTable(this->pVtab);
  bool cache_hit = true;
  if (idxNum != table->qc_hash_) {
    table->qc_cache_ = QueryConstraints::FromString(idxStr);
    table->qc_hash_ = idxNum;
    cache_hit = false;
  }
  if (Table::debug) {
    PERFETTO_LOG("[%s::Filter] constraints=%s argc=%d cache_hit=%d",
                 table->name_.c_str(), idxStr, argc, cache_hit);
  }
  PERFETTO_DCHECK(table->qc_cache_.constraints().size() ==
                  static_cast<size_t>(argc));
  cursor_ = table_->CreateCursor(table->qc_cache_, argv);
  return !cursor_ ? SQLITE_ERROR : SQLITE_OK;
}

Table::Cursor::~Cursor() = default;

int Table::Cursor::RowId(sqlite3_int64*) {
  return SQLITE_ERROR;
}

Table::Column::Column(size_t index,
                      std::string name,
                      ColumnType type,
                      bool hidden)
    : index_(index), name_(name), type_(type), hidden_(hidden) {}

Table::Schema::Schema(std::vector<Column> columns,
                      std::vector<size_t> primary_keys)
    : columns_(std::move(columns)), primary_keys_(std::move(primary_keys)) {
  for (size_t i = 0; i < columns_.size(); i++) {
    PERFETTO_CHECK(columns_[i].index() == i);
  }
  for (auto key : primary_keys_) {
    PERFETTO_CHECK(key < columns_.size());
  }
}

Table::Schema::Schema() = default;
Table::Schema::Schema(const Schema&) noexcept = default;
Table::Schema& Table::Schema::operator=(const Schema&) = default;

std::string Table::Schema::ToCreateTableStmt() {
  std::string stmt = "CREATE TABLE x(";
  for (const auto& col : columns_) {
    stmt += " " + col.name() + " " + TypeToString(col.type()) + ",";
  }
  stmt += " PRIMARY KEY(";
  for (size_t i = 0; i < primary_keys_.size(); i++) {
    if (i != 0)
      stmt += ", ";
    stmt += columns_[primary_keys_[i]].name();
  }
  stmt += ")) WITHOUT ROWID;";
  return stmt;
}

}  // namespace trace_processor
}  // namespace perfetto
<|endoftext|>


<commit_before>/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/

#include "s60devicespreferencepane.h"
#include "ui_s60devicespreferencepane.h"
#include "s60devices.h"

#include <qt4projectmanager/qt4projectmanagerconstants.h>

#include <utils/qtcassert.h>
#include <coreplugin/coreconstants.h>

#include <QtCore/QDir>
#include <QtCore/QtDebug>
#include <QtCore/QSharedPointer>

#include <QtGui/QFileDialog>
#include <QtGui/QMessageBox>
#include <QtGui/QIcon>
#include <QtGui/QApplication>
#include <QtGui/QStyle>
#include <QtGui/QStandardItemModel>
#include <QtGui/QStandardItem>

enum { deviceRole = Qt::UserRole + 1 };

enum Columns { DefaultColumn, EpocColumn, QtColumn, ColumnCount };

typedef QSharedPointer<Qt4ProjectManager::Internal::S60Devices::Device> DevicePtr;
Q_DECLARE_METATYPE(DevicePtr)

typedef QList<QStandardItem *> StandardItemList;

namespace Qt4ProjectManager {
namespace Internal {

static inline DevicePtr deviceFromItem(const QStandardItem *item)
{
    return qvariant_cast<DevicePtr>(item->data(deviceRole));
}

// Device model storing a shared pointer to the device as user data.
// Provides a checkable 'default' column which works exclusively.
class S60DevicesModel : public QStandardItemModel {
    Q_OBJECT
public:
    typedef QList<S60Devices::Device> DeviceList;

    explicit S60DevicesModel(bool defaultColumnCheckable, QObject *parent = 0);

    void setDevices(const DeviceList &list);
    DeviceList devices() const;
    void appendDevice(const S60Devices::Device &device);

private slots:
    void slotItemChanged(QStandardItem *item);

private:
    const bool m_defaultColumnCheckable;
};

S60DevicesModel::S60DevicesModel(bool defaultColumnCheckable, QObject *parent) :
        QStandardItemModel(0, ColumnCount, parent),
        m_defaultColumnCheckable(defaultColumnCheckable)
{
    QStringList headers;
    headers << S60DevicesBaseWidget::tr("Default")
            << S60DevicesBaseWidget::tr("SDK Location")
            << S60DevicesBaseWidget::tr("Qt Location");
    setHorizontalHeaderLabels(headers);

    if (m_defaultColumnCheckable)
        connect(this, SIGNAL(itemChanged(QStandardItem*)),
                this, SLOT(slotItemChanged(QStandardItem*)));
}

void S60DevicesModel::appendDevice(const S60Devices::Device &device)
{
    // Create SDK/Qt column items with shared pointer to entry as data.
    const QVariant deviceData = qVariantFromValue(DevicePtr(new S60Devices::Device(device)));

    const Qt::ItemFlags flags = Qt::ItemIsEnabled|Qt::ItemIsSelectable;

    QStandardItem *defaultItem = new QStandardItem;
    defaultItem->setCheckable(true);
    defaultItem->setCheckState(device.isDefault ? Qt::Checked : Qt::Unchecked);
    // Item is only checkable if it is not the default.
    Qt::ItemFlags checkFlags = flags;
    if (!device.isDefault && m_defaultColumnCheckable)
        checkFlags |= Qt::ItemIsUserCheckable;
    defaultItem->setFlags(checkFlags);

    defaultItem->setData(deviceData);

    QStandardItem *epocItem = new QStandardItem(QDir::toNativeSeparators(device.epocRoot));
    epocItem->setFlags(flags);
    epocItem->setData(deviceData);

    const QString qtDesc = device.qt.isEmpty() ?
                           S60DevicesModel::tr("No Qt installed") :
                           QDir::toNativeSeparators(device.qt);
    QStandardItem *qtItem = new QStandardItem(qtDesc);
    qtItem->setFlags(flags);
    qtItem->setData(deviceData);

    const QString tooltip = device.toHtml();
    epocItem->setToolTip(tooltip);
    qtItem->setToolTip(tooltip);

    StandardItemList row;
    row << defaultItem << epocItem << qtItem;
    appendRow(row);
}

void S60DevicesModel::setDevices(const DeviceList &list)
{
    removeRows(0, rowCount());
    foreach(const S60Devices::Device &device, list)
        appendDevice(device);
}

S60DevicesModel::DeviceList S60DevicesModel::devices() const
{
    S60DevicesModel::DeviceList rc;
    const int count = rowCount();
    for (int r = 0; r < count; r++)
        rc.push_back(S60Devices::Device(*deviceFromItem(item(r, 0))));
    return rc;
}

void S60DevicesModel::slotItemChanged(QStandardItem *changedItem)
{
    // Sync all "default" checkmarks. Emulate an exclusive group
    // by enabling only the unchecked items (preventing the user from unchecking)
    // and uncheck all other items. Protect against recursion.
    if (changedItem->column() != DefaultColumn || changedItem->checkState() != Qt::Checked)
        return;
    const int row = changedItem->row();
    const int count = rowCount();
    for (int r = 0; r < count; r++) {
        QStandardItem *rowItem = item(r, DefaultColumn);
        if (r == row) { // Prevent uncheck.
            rowItem->setFlags(rowItem->flags() & ~Qt::ItemIsUserCheckable);
            deviceFromItem(rowItem)->isDefault = true;
        } else {
            // Uncheck others.
            rowItem->setCheckState(Qt::Unchecked);
            rowItem->setFlags(rowItem->flags() | Qt::ItemIsUserCheckable);
            deviceFromItem(rowItem)->isDefault = false;
        }
    }
}

// --------------- S60DevicesBaseWidget
S60DevicesBaseWidget::S60DevicesBaseWidget(unsigned flags, QWidget *parent) :
    QWidget(parent),
    m_ui(new Ui::S60DevicesPreferencePane),
    m_model(new S60DevicesModel(flags & DeviceDefaultCheckable))
{
    m_ui->setupUi(this);
    m_ui->addButton->setIcon(QIcon(QLatin1String(Core::Constants::ICON_PLUS)));
    connect(m_ui->addButton, SIGNAL(clicked()), this, SLOT(addDevice()));
    m_ui->removeButton->setIcon(QIcon(QLatin1String(Core::Constants::ICON_MINUS)));
    connect(m_ui->removeButton, SIGNAL(clicked()), this, SLOT(removeDevice()));
    m_ui->refreshButton->setIcon(qApp->style()->standardIcon(QStyle::SP_BrowserReload));
    connect(m_ui->refreshButton, SIGNAL(clicked()), this, SLOT(refresh()));
    m_ui->changeQtButton->setIcon(QIcon(QLatin1String(":/welcome/images/qt_logo.png")));
    connect(m_ui->changeQtButton, SIGNAL(clicked()), this, SLOT(changeQtVersion()));

    m_ui->list->setModel(m_model);
    connect(m_ui->list->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
            this, SLOT(currentChanged(QModelIndex,QModelIndex)));

    m_ui->addButton->setVisible(flags & ShowAddButton);
    m_ui->removeButton->setVisible(flags & ShowAddButton);
    m_ui->removeButton->setEnabled(false);
    m_ui->changeQtButton->setVisible(flags & ShowChangeQtButton);
    m_ui->removeButton->setEnabled(false);
    m_ui->refreshButton->setVisible(flags & ShowRefreshButton);
}

S60DevicesBaseWidget::~S60DevicesBaseWidget()
{
    delete m_ui;
}

QStandardItem *S60DevicesBaseWidget::currentItem() const
{
    // Return the column-0 item.
    QModelIndex current = m_ui->list->currentIndex();
    if (current.isValid()) {
        if (current.row() != 0)
            current = current.sibling(current.row(), 0);
        return m_model->itemFromIndex(current);
    }
    return 0;
}

S60DevicesBaseWidget::DeviceList S60DevicesBaseWidget::devices() const
{
    return m_model->devices();
}

void S60DevicesBaseWidget::setDevices(const DeviceList &s60devices,
                                      const QString &errorString)
{
    m_model->setDevices(s60devices);

    for (int c = 0; c < ColumnCount; c++)
        m_ui->list->resizeColumnToContents(c);

    if (errorString.isEmpty()) {
        clearErrorLabel();
    } else {
        setErrorLabel(errorString);
    }
}

void S60DevicesBaseWidget::changeQtVersion()
{
    if (const QStandardItem *item = currentItem()) {
        const QString qtDir = promptDirectory(tr("Choose Qt folder"));
        if (!qtDir.isEmpty()) {
            const DevicePtr device = deviceFromItem(item);
            device->qt = qtDir;
        }
    }
}

void S60DevicesBaseWidget::removeDevice()
{
    if (const QStandardItem *item = currentItem())
        m_model->removeRows(item->row(), 1);
}

void S60DevicesBaseWidget::currentChanged(const QModelIndex &current,
                                          const QModelIndex & /* previous */)
{
    const bool hasItem = current.isValid();
    m_ui->changeQtButton->setEnabled(hasItem);
    m_ui->removeButton->setEnabled(hasItem);
}

void S60DevicesBaseWidget::setErrorLabel(const QString& t)
{
    m_ui->errorLabel->setText(t);
    m_ui->errorLabel->setVisible(true);
}

void S60DevicesBaseWidget::clearErrorLabel()
{
    m_ui->errorLabel->setVisible(false);
}

QString S60DevicesBaseWidget::promptDirectory(const QString &title)
{
    return QFileDialog::getExistingDirectory(this, title);
}

void S60DevicesBaseWidget::appendDevice(const S60Devices::Device &d)
{
    m_model->appendDevice(d);
}

int S60DevicesBaseWidget::deviceCount() const
{
    return m_model->rowCount();
}

// ============ AutoDetectS60DevicesWidget

AutoDetectS60DevicesWidget::AutoDetectS60DevicesWidget(QWidget *parent,
                                                       AutoDetectS60Devices *devices,
                                                       bool changeQtVersionEnabled) :
    S60DevicesBaseWidget(ShowRefreshButton | (changeQtVersionEnabled ? unsigned(ShowChangeQtButton) : 0u),
                         parent),
    m_devices(devices)
{
    refresh();
}

void AutoDetectS60DevicesWidget::refresh()
{
    m_devices->detectDevices();
    setDevices(m_devices->devices(), m_devices->errorString());
}

// ============ GnuPocS60DevicesWidget
GnuPocS60DevicesWidget::GnuPocS60DevicesWidget(QWidget *parent) :
    S60DevicesBaseWidget(ShowAddButton|ShowRemoveButton|ShowChangeQtButton|DeviceDefaultCheckable,
                         parent)
{
}

void GnuPocS60DevicesWidget::addDevice()
{
    // 1) Prompt for GnuPoc
    const QString epocRoot = promptDirectory(tr("Step 1 of 2: Choose GnuPoc folder"));
    if (epocRoot.isEmpty())
        return;
    // 2) Prompt for Qt. Catch equal inputs just in case someone clicks very rapidly.
    QString qtDir;
    while (true) {
        qtDir = promptDirectory(tr("Step 2 of 2: Choose Qt folder"));
        if (qtDir.isEmpty())
            return;
        if (qtDir == epocRoot) {
            QMessageBox::warning(this, tr("Adding GnuPoc"),
                                 tr("GnuPoc and Qt folders must not be identical."));
        } else {
            break;
        }
    }
    // Add a device, make default if first.
    S60Devices::Device device = GnuPocS60Devices::createDevice(epocRoot, qtDir);
    if (deviceCount() == 0)
        device.isDefault = true;
    appendDevice(device);
}

// ================= S60DevicesPreferencePane
S60DevicesPreferencePane::S60DevicesPreferencePane(S60Devices *devices, QObject *parent)
        : Core::IOptionsPage(parent),
        m_widget(0),
        m_devices(devices)
{
}

S60DevicesPreferencePane::~S60DevicesPreferencePane()
{
}

QString S60DevicesPreferencePane::id() const
{
    return QLatin1String("Z.S60 SDKs");
}

QString S60DevicesPreferencePane::displayName() const
{
    return tr("S60 SDKs");
}

QString S60DevicesPreferencePane::category() const
{
    return QLatin1String(Constants::QT_SETTINGS_CATEGORY);
}

QString S60DevicesPreferencePane::displayCategory() const
{
    return QCoreApplication::translate("Qt4ProjectManager", Constants::QT_SETTINGS_CATEGORY);
}

QIcon S60DevicesPreferencePane::categoryIcon() const
{
    return QIcon(Constants::QT_SETTINGS_CATEGORY_ICON);
}

S60DevicesBaseWidget *S60DevicesPreferencePane::createWidget(QWidget *parent) const
{
    // Symbian ABLD/Raptor: Qt installed into SDK, cannot change
    if (AutoDetectS60QtDevices *aqd = qobject_cast<AutoDetectS60QtDevices *>(m_devices))
        return new AutoDetectS60DevicesWidget(parent, aqd, false);
    // Not used yet: Manual association of Qt with auto-detected SDK
    if (AutoDetectS60Devices *ad = qobject_cast<AutoDetectS60Devices *>(m_devices))
        return new AutoDetectS60DevicesWidget(parent, ad, true);
    if (GnuPocS60Devices *gd = qobject_cast<GnuPocS60Devices*>(m_devices)) {
        GnuPocS60DevicesWidget *gw = new GnuPocS60DevicesWidget(parent);
        gw->setDevices(gd->devices());
        return gw;
    }
    return 0; // Huh?
}

QWidget *S60DevicesPreferencePane::createPage(QWidget *parent)
{
    if (m_widget)
        delete m_widget;
    m_widget = createWidget(parent);
    QTC_ASSERT(m_widget, return 0)
    return m_widget;
}

void S60DevicesPreferencePane::apply()
{
    QTC_ASSERT(m_widget, return)

    m_devices->setDevices(m_widget->devices());
}

void S60DevicesPreferencePane::finish()
{
}

} // namespace Internal
} // namespace Qt4ProjectManager

#include "s60devicespreferencepane.moc"
<commit_msg>Show "default device" checkmark instead of comboboxes on Windows<commit_after>/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/

#include "s60devicespreferencepane.h"
#include "ui_s60devicespreferencepane.h"
#include "s60devices.h"

#include <qt4projectmanager/qt4projectmanagerconstants.h>

#include <utils/qtcassert.h>
#include <coreplugin/coreconstants.h>

#include <QtCore/QDir>
#include <QtCore/QtDebug>
#include <QtCore/QSharedPointer>

#include <QtGui/QFileDialog>
#include <QtGui/QMessageBox>
#include <QtGui/QIcon>
#include <QtGui/QApplication>
#include <QtGui/QStyle>
#include <QtGui/QStandardItemModel>
#include <QtGui/QStandardItem>

enum { deviceRole = Qt::UserRole + 1 };

enum Columns { DefaultColumn, EpocColumn, QtColumn, ColumnCount };

typedef QSharedPointer<Qt4ProjectManager::Internal::S60Devices::Device> DevicePtr;
Q_DECLARE_METATYPE(DevicePtr)

typedef QList<QStandardItem *> StandardItemList;

namespace Qt4ProjectManager {
namespace Internal {

static inline DevicePtr deviceFromItem(const QStandardItem *item)
{
    return qvariant_cast<DevicePtr>(item->data(deviceRole));
}

// Device model storing a shared pointer to the device as user data.
// Provides a checkable 'default' column which works exclusively.
class S60DevicesModel : public QStandardItemModel {
    Q_OBJECT
public:
    typedef QList<S60Devices::Device> DeviceList;

    explicit S60DevicesModel(bool defaultColumnCheckable, QObject *parent = 0);

    void setDevices(const DeviceList &list);
    DeviceList devices() const;
    void appendDevice(const S60Devices::Device &device);

private slots:
    void slotItemChanged(QStandardItem *item);

private:
    const bool m_defaultColumnCheckable;
};

S60DevicesModel::S60DevicesModel(bool defaultColumnCheckable, QObject *parent) :
        QStandardItemModel(0, ColumnCount, parent),
        m_defaultColumnCheckable(defaultColumnCheckable)
{
    QStringList headers;
    headers << S60DevicesBaseWidget::tr("Default")
            << S60DevicesBaseWidget::tr("SDK Location")
            << S60DevicesBaseWidget::tr("Qt Location");
    setHorizontalHeaderLabels(headers);

    if (m_defaultColumnCheckable)
        connect(this, SIGNAL(itemChanged(QStandardItem*)),
                this, SLOT(slotItemChanged(QStandardItem*)));
}

void S60DevicesModel::appendDevice(const S60Devices::Device &device)
{
    // Create SDK/Qt column items with shared pointer to entry as data.
    const QVariant deviceData = qVariantFromValue(DevicePtr(new S60Devices::Device(device)));

    const Qt::ItemFlags flags = Qt::ItemIsEnabled|Qt::ItemIsSelectable;

    QStandardItem *defaultItem = new QStandardItem;
    if (m_defaultColumnCheckable) {
        defaultItem->setCheckable(true);
        defaultItem->setCheckState(device.isDefault ? Qt::Checked : Qt::Unchecked);
        // Item is only checkable if it is not the default.
        Qt::ItemFlags checkFlags = flags;
        if (!device.isDefault)
            checkFlags |= Qt::ItemIsUserCheckable;
        defaultItem->setFlags(checkFlags);
    } else {
        defaultItem->setIcon(device.isDefault ? QIcon(QLatin1String(":/extensionsystem/images/ok.png")) : QIcon());
    }

    defaultItem->setData(deviceData);

    QStandardItem *epocItem = new QStandardItem(QDir::toNativeSeparators(device.epocRoot));
    epocItem->setFlags(flags);
    epocItem->setData(deviceData);

    const QString qtDesc = device.qt.isEmpty() ?
                           S60DevicesModel::tr("No Qt installed") :
                           QDir::toNativeSeparators(device.qt);
    QStandardItem *qtItem = new QStandardItem(qtDesc);
    qtItem->setFlags(flags);
    qtItem->setData(deviceData);

    const QString tooltip = device.toHtml();
    epocItem->setToolTip(tooltip);
    qtItem->setToolTip(tooltip);

    StandardItemList row;
    row << defaultItem << epocItem << qtItem;
    appendRow(row);
}

void S60DevicesModel::setDevices(const DeviceList &list)
{
    removeRows(0, rowCount());
    foreach(const S60Devices::Device &device, list)
        appendDevice(device);
}

S60DevicesModel::DeviceList S60DevicesModel::devices() const
{
    S60DevicesModel::DeviceList rc;
    const int count = rowCount();
    for (int r = 0; r < count; r++)
        rc.push_back(S60Devices::Device(*deviceFromItem(item(r, 0))));
    return rc;
}

void S60DevicesModel::slotItemChanged(QStandardItem *changedItem)
{
    // Sync all "default" checkmarks. Emulate an exclusive group
    // by enabling only the unchecked items (preventing the user from unchecking)
    // and uncheck all other items. Protect against recursion.
    if (changedItem->column() != DefaultColumn || changedItem->checkState() != Qt::Checked)
        return;
    const int row = changedItem->row();
    const int count = rowCount();
    for (int r = 0; r < count; r++) {
        QStandardItem *rowItem = item(r, DefaultColumn);
        if (r == row) { // Prevent uncheck.
            rowItem->setFlags(rowItem->flags() & ~Qt::ItemIsUserCheckable);
            deviceFromItem(rowItem)->isDefault = true;
        } else {
            // Uncheck others.
            rowItem->setCheckState(Qt::Unchecked);
            rowItem->setFlags(rowItem->flags() | Qt::ItemIsUserCheckable);
            deviceFromItem(rowItem)->isDefault = false;
        }
    }
}

// --------------- S60DevicesBaseWidget
S60DevicesBaseWidget::S60DevicesBaseWidget(unsigned flags, QWidget *parent) :
    QWidget(parent),
    m_ui(new Ui::S60DevicesPreferencePane),
    m_model(new S60DevicesModel(flags & DeviceDefaultCheckable))
{
    m_ui->setupUi(this);
    m_ui->addButton->setIcon(QIcon(QLatin1String(Core::Constants::ICON_PLUS)));
    connect(m_ui->addButton, SIGNAL(clicked()), this, SLOT(addDevice()));
    m_ui->removeButton->setIcon(QIcon(QLatin1String(Core::Constants::ICON_MINUS)));
    connect(m_ui->removeButton, SIGNAL(clicked()), this, SLOT(removeDevice()));
    m_ui->refreshButton->setIcon(qApp->style()->standardIcon(QStyle::SP_BrowserReload));
    connect(m_ui->refreshButton, SIGNAL(clicked()), this, SLOT(refresh()));
    m_ui->changeQtButton->setIcon(QIcon(QLatin1String(":/welcome/images/qt_logo.png")));
    connect(m_ui->changeQtButton, SIGNAL(clicked()), this, SLOT(changeQtVersion()));

    m_ui->list->setModel(m_model);
    connect(m_ui->list->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
            this, SLOT(currentChanged(QModelIndex,QModelIndex)));

    m_ui->addButton->setVisible(flags & ShowAddButton);
    m_ui->removeButton->setVisible(flags & ShowAddButton);
    m_ui->removeButton->setEnabled(false);
    m_ui->changeQtButton->setVisible(flags & ShowChangeQtButton);
    m_ui->removeButton->setEnabled(false);
    m_ui->refreshButton->setVisible(flags & ShowRefreshButton);
}

S60DevicesBaseWidget::~S60DevicesBaseWidget()
{
    delete m_ui;
}

QStandardItem *S60DevicesBaseWidget::currentItem() const
{
    // Return the column-0 item.
    QModelIndex current = m_ui->list->currentIndex();
    if (current.isValid()) {
        if (current.row() != 0)
            current = current.sibling(current.row(), 0);
        return m_model->itemFromIndex(current);
    }
    return 0;
}

S60DevicesBaseWidget::DeviceList S60DevicesBaseWidget::devices() const
{
    return m_model->devices();
}

void S60DevicesBaseWidget::setDevices(const DeviceList &s60devices,
                                      const QString &errorString)
{
    m_model->setDevices(s60devices);

    for (int c = 0; c < ColumnCount; c++)
        m_ui->list->resizeColumnToContents(c);

    if (errorString.isEmpty()) {
        clearErrorLabel();
    } else {
        setErrorLabel(errorString);
    }
}

void S60DevicesBaseWidget::changeQtVersion()
{
    if (const QStandardItem *item = currentItem()) {
        const QString qtDir = promptDirectory(tr("Choose Qt folder"));
        if (!qtDir.isEmpty()) {
            const DevicePtr device = deviceFromItem(item);
            device->qt = qtDir;
        }
    }
}

void S60DevicesBaseWidget::removeDevice()
{
    if (const QStandardItem *item = currentItem())
        m_model->removeRows(item->row(), 1);
}

void S60DevicesBaseWidget::currentChanged(const QModelIndex &current,
                                          const QModelIndex & /* previous */)
{
    const bool hasItem = current.isValid();
    m_ui->changeQtButton->setEnabled(hasItem);
    m_ui->removeButton->setEnabled(hasItem);
}

void S60DevicesBaseWidget::setErrorLabel(const QString& t)
{
    m_ui->errorLabel->setText(t);
    m_ui->errorLabel->setVisible(true);
}

void S60DevicesBaseWidget::clearErrorLabel()
{
    m_ui->errorLabel->setVisible(false);
}

QString S60DevicesBaseWidget::promptDirectory(const QString &title)
{
    return QFileDialog::getExistingDirectory(this, title);
}

void S60DevicesBaseWidget::appendDevice(const S60Devices::Device &d)
{
    m_model->appendDevice(d);
}

int S60DevicesBaseWidget::deviceCount() const
{
    return m_model->rowCount();
}

// ============ AutoDetectS60DevicesWidget

AutoDetectS60DevicesWidget::AutoDetectS60DevicesWidget(QWidget *parent,
                                                       AutoDetectS60Devices *devices,
                                                       bool changeQtVersionEnabled) :
    S60DevicesBaseWidget(ShowRefreshButton | (changeQtVersionEnabled ? unsigned(ShowChangeQtButton) : 0u),
                         parent),
    m_devices(devices)
{
    refresh();
}

void AutoDetectS60DevicesWidget::refresh()
{
    m_devices->detectDevices();
    setDevices(m_devices->devices(), m_devices->errorString());
}

// ============ GnuPocS60DevicesWidget
GnuPocS60DevicesWidget::GnuPocS60DevicesWidget(QWidget *parent) :
    S60DevicesBaseWidget(ShowAddButton|ShowRemoveButton|ShowChangeQtButton|DeviceDefaultCheckable,
                         parent)
{
}

void GnuPocS60DevicesWidget::addDevice()
{
    // 1) Prompt for GnuPoc
    const QString epocRoot = promptDirectory(tr("Step 1 of 2: Choose GnuPoc folder"));
    if (epocRoot.isEmpty())
        return;
    // 2) Prompt for Qt. Catch equal inputs just in case someone clicks very rapidly.
    QString qtDir;
    while (true) {
        qtDir = promptDirectory(tr("Step 2 of 2: Choose Qt folder"));
        if (qtDir.isEmpty())
            return;
        if (qtDir == epocRoot) {
            QMessageBox::warning(this, tr("Adding GnuPoc"),
                                 tr("GnuPoc and Qt folders must not be identical."));
        } else {
            break;
        }
    }
    // Add a device, make default if first.
    S60Devices::Device device = GnuPocS60Devices::createDevice(epocRoot, qtDir);
    if (deviceCount() == 0)
        device.isDefault = true;
    appendDevice(device);
}

// ================= S60DevicesPreferencePane
S60DevicesPreferencePane::S60DevicesPreferencePane(S60Devices *devices, QObject *parent)
        : Core::IOptionsPage(parent),
        m_widget(0),
        m_devices(devices)
{
}

S60DevicesPreferencePane::~S60DevicesPreferencePane()
{
}

QString S60DevicesPreferencePane::id() const
{
    return QLatin1String("Z.S60 SDKs");
}

QString S60DevicesPreferencePane::displayName() const
{
    return tr("S60 SDKs");
}

QString S60DevicesPreferencePane::category() const
{
    return QLatin1String(Constants::QT_SETTINGS_CATEGORY);
}

QString S60DevicesPreferencePane::displayCategory() const
{
    return QCoreApplication::translate("Qt4ProjectManager", Constants::QT_SETTINGS_CATEGORY);
}

QIcon S60DevicesPreferencePane::categoryIcon() const
{
    return QIcon(Constants::QT_SETTINGS_CATEGORY_ICON);
}

S60DevicesBaseWidget *S60DevicesPreferencePane::createWidget(QWidget *parent) const
{
    // Symbian ABLD/Raptor: Qt installed into SDK, cannot change
    if (AutoDetectS60QtDevices *aqd = qobject_cast<AutoDetectS60QtDevices *>(m_devices))
        return new AutoDetectS60DevicesWidget(parent, aqd, false);
    // Not used yet: Manual association of Qt with auto-detected SDK
    if (AutoDetectS60Devices *ad = qobject_cast<AutoDetectS60Devices *>(m_devices))
        return new AutoDetectS60DevicesWidget(parent, ad, true);
    if (GnuPocS60Devices *gd = qobject_cast<GnuPocS60Devices*>(m_devices)) {
        GnuPocS60DevicesWidget *gw = new GnuPocS60DevicesWidget(parent);
        gw->setDevices(gd->devices());
        return gw;
    }
    return 0; // Huh?
}

QWidget *S60DevicesPreferencePane::createPage(QWidget *parent)
{
    if (m_widget)
        delete m_widget;
    m_widget = createWidget(parent);
    QTC_ASSERT(m_widget, return 0)
    return m_widget;
}

void S60DevicesPreferencePane::apply()
{
    QTC_ASSERT(m_widget, return)

    m_devices->setDevices(m_widget->devices());
}

void S60DevicesPreferencePane::finish()
{
}

} // namespace Internal
} // namespace Qt4ProjectManager

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


<commit_before>/* This file is part of the Pangolin Project.
 * http://github.com/stevenlovegrove/Pangolin
 *
 * Copyright (c) 2014 Steven Lovegrove
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include <pangolin/video/drivers/thread.h>

#ifdef DEBUGTHREAD
  #include <pangolin/utils/timer.h>
  #define TSTART() pangolin::basetime start,last,now; start = pangolin::TimeNow(); last = start;
  #define TGRABANDPRINT(...)  now = pangolin::TimeNow(); fprintf(stderr,"THREAD: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, " %fms.\n",1000*pangolin::TimeDiff_s(last, now)); last = now;
  #define DBGPRINT(...) fprintf(stderr,"THREAD: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr,"\n");
#else
  #define TSTART()
  #define TGRABANDPRINT(...)
  #define DBGPRINT(...)
#endif

namespace pangolin
{

ThreadVideo::ThreadVideo(VideoInterface* src, unsigned int num_buffers): quit_grab_thread(true), num_buffers(num_buffers)
{
    if(!src) {
        throw VideoException("ThreadVideo: VideoInterface in must not be null");
    }
    videoin.push_back(src);
    // queue init allocates buffers.
    queue.init(num_buffers, videoin[0]->SizeBytes());
    Start();
}

ThreadVideo::~ThreadVideo()
{
    Stop();
    delete videoin[0];
    // queue destructor will take care of deallocating buffers.
}

//! Implement VideoInput::Start()
void ThreadVideo::Start()
{
    videoin[0]->Start();
    quit_grab_thread = false;
    grab_thread = boostd::thread(boostd::ref(*this));
}

//! Implement VideoInput::Stop()
void ThreadVideo::Stop()
{
    quit_grab_thread = true;
    if(grab_thread.joinable()) {
        grab_thread.join();
    }
    videoin[0]->Stop();
}

//! Implement VideoInput::SizeBytes()
size_t ThreadVideo::SizeBytes() const
{
    return videoin[0]->SizeBytes();
}

//! Implement VideoInput::Streams()
const std::vector<StreamInfo>& ThreadVideo::Streams() const
{
    return videoin[0]->Streams();
}

const json::value& ThreadVideo::DeviceProperties() const
{
    VideoPropertiesInterface* in_prop = dynamic_cast<VideoPropertiesInterface*>(videoin[0]);
    if(!in_prop)
        throw std::runtime_error("ThreadVideo: video in interface does not implement VideoPropertiesInterface.");
    else
        return in_prop->FrameProperties();
}

const json::value& ThreadVideo::FrameProperties() const
{
    VideoPropertiesInterface* in_prop = dynamic_cast<VideoPropertiesInterface*>(videoin[0]);
    if(!in_prop)
        throw std::runtime_error("ThreadVideo: video in interface does not implement VideoPropertiesInterface.");
    else
        return in_prop->FrameProperties();
}

unsigned int ThreadVideo::AvailableFrames() const
{
    return queue.AvailableFrames();
}

bool ThreadVideo::DropNFrames(uint32_t n)
{
    return queue.DropNFrames(n);
}

//! Implement VideoInput::GrabNext()
bool ThreadVideo::GrabNext( unsigned char* image, bool wait )
{

    if(queue.AvailableFrames() > 0) {
        // At least one valid frame in queue, return it.
        DBGPRINT("GrabNext at least one frame available.");
        unsigned char* i = queue.getNext();
        std::memcpy(image, i, queue.BufferSizeBytes());
        queue.returnUsedBuffer(i);
        return true;
    } else {
        if(wait) {
            // Must return a frame so block on notification from grab thread.
            std::unique_lock<std::mutex> lk(cvMtx);
            DBGPRINT("GrabNext no available frames wait for notification.");
            if(cv.wait_for(lk, boostd::chrono::seconds(5)) == boostd::cv_status::timeout)
                throw std::runtime_error("ThreadVideo: GrabNext blocking read for frames reached timeout.");
            DBGPRINT("GrabNext got notification.");
            unsigned char* i = queue.getNext();
            std::memcpy(image, i, queue.BufferSizeBytes());
            queue.returnUsedBuffer(i);
            return true;
        } else {
            DBGPRINT("GrabNext no available frames no wait.");
            // No frames available, no wait, simply return false.
            return false;
        }
    }
}

//! Implement VideoInput::GrabNewest()
bool ThreadVideo::GrabNewest( unsigned char* image, bool wait )
{
    if(queue.AvailableFrames() > 0) {
        // At least one valid frame in queue, call grabNewest to drop old frame and return the newest.
        DBGPRINT("GrabNewest at least one frame available.");
        unsigned char* i = queue.getNewest();
        std::memcpy(image, i, queue.BufferSizeBytes());
        queue.returnUsedBuffer(i);
        return true;
    } else {
        if(wait) {
            // Must return a frame so block on notification from grab thread.
            DBGPRINT("GrabNewest no available frames wait for notification.");
            std::unique_lock<std::mutex> lk(cvMtx);
            if(cv.wait_for(lk, boostd::chrono::seconds(5)) == boostd::cv_status::timeout)
                throw std::runtime_error("ThreadVideo: GrabNewest blocking read for frames reached timeout.");
            DBGPRINT("GrabNewest got notification.");
            unsigned char* i = queue.getNext();
            std::memcpy(image, i, queue.BufferSizeBytes());
            queue.returnUsedBuffer(i);
            return true;
        } else {
            DBGPRINT("GrabNewest no available frames no wait.");
            // No frames available, no wait, simply return false.
            return false;
        }
    }
}

void ThreadVideo::operator()()
{
    DBGPRINT("Grab thread Started.")
    // Spinning thread attempting to read from videoin[0] as fast as possible
    // relying on the videoin[0] blocking grab.
    while(!quit_grab_thread) {
        // Get a buffer from the queue;
        unsigned char* i = queue.getFreeBuffer();
        // Blocking grab (i.e. GrabNext with wait = true).
        if(videoin[0]->GrabNext(i, true)){
            queue.addValidBuffer(i);
            DBGPRINT("Grab thread got frame. valid:%d free:%d",queue.AvailableFrames(),queue.EmptyBuffers())
            // Let listening threads know we got a frame in case they are waiting.
            cv.notify_all();
        }
        boostd::this_thread::yield();
    }
    DBGPRINT("Grab thread Stopped.")

    return;
}

}

#undef TSTART
#undef TGRABANDPRINT
#undef DBGPRINT
<commit_msg>Fix pointers leak.<commit_after>/* This file is part of the Pangolin Project.
 * http://github.com/stevenlovegrove/Pangolin
 *
 * Copyright (c) 2014 Steven Lovegrove
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include <pangolin/video/drivers/thread.h>

#ifdef DEBUGTHREAD
  #include <pangolin/utils/timer.h>
  #define TSTART() pangolin::basetime start,last,now; start = pangolin::TimeNow(); last = start;
  #define TGRABANDPRINT(...)  now = pangolin::TimeNow(); fprintf(stderr,"THREAD: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, " %fms.\n",1000*pangolin::TimeDiff_s(last, now)); last = now;
  #define DBGPRINT(...) fprintf(stderr,"THREAD: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr,"\n");
#else
  #define TSTART()
  #define TGRABANDPRINT(...)
  #define DBGPRINT(...)
#endif

namespace pangolin
{

ThreadVideo::ThreadVideo(VideoInterface* src, unsigned int num_buffers): quit_grab_thread(true), num_buffers(num_buffers)
{
    if(!src) {
        throw VideoException("ThreadVideo: VideoInterface in must not be null");
    }
    videoin.push_back(src);
    // queue init allocates buffers.
    queue.init(num_buffers, videoin[0]->SizeBytes());
    Start();
}

ThreadVideo::~ThreadVideo()
{
    Stop();
    delete videoin[0];
    // queue destructor will take care of deallocating buffers.
}

//! Implement VideoInput::Start()
void ThreadVideo::Start()
{
    videoin[0]->Start();
    quit_grab_thread = false;
    grab_thread = boostd::thread(boostd::ref(*this));
}

//! Implement VideoInput::Stop()
void ThreadVideo::Stop()
{
    quit_grab_thread = true;
    if(grab_thread.joinable()) {
        grab_thread.join();
    }
    videoin[0]->Stop();
}

//! Implement VideoInput::SizeBytes()
size_t ThreadVideo::SizeBytes() const
{
    return videoin[0]->SizeBytes();
}

//! Implement VideoInput::Streams()
const std::vector<StreamInfo>& ThreadVideo::Streams() const
{
    return videoin[0]->Streams();
}

const json::value& ThreadVideo::DeviceProperties() const
{
    VideoPropertiesInterface* in_prop = dynamic_cast<VideoPropertiesInterface*>(videoin[0]);
    if(!in_prop)
        throw std::runtime_error("ThreadVideo: video in interface does not implement VideoPropertiesInterface.");
    else
        return in_prop->FrameProperties();
}

const json::value& ThreadVideo::FrameProperties() const
{
    VideoPropertiesInterface* in_prop = dynamic_cast<VideoPropertiesInterface*>(videoin[0]);
    if(!in_prop)
        throw std::runtime_error("ThreadVideo: video in interface does not implement VideoPropertiesInterface.");
    else
        return in_prop->FrameProperties();
}

unsigned int ThreadVideo::AvailableFrames() const
{
    return queue.AvailableFrames();
}

bool ThreadVideo::DropNFrames(uint32_t n)
{
    return queue.DropNFrames(n);
}

//! Implement VideoInput::GrabNext()
bool ThreadVideo::GrabNext( unsigned char* image, bool wait )
{

    if(queue.AvailableFrames() > 0) {
        // At least one valid frame in queue, return it.
        DBGPRINT("GrabNext at least one frame available.");
        unsigned char* i = queue.getNext();
        std::memcpy(image, i, queue.BufferSizeBytes());
        queue.returnUsedBuffer(i);
        return true;
    } else {
        if(wait) {
            // Must return a frame so block on notification from grab thread.
            std::unique_lock<std::mutex> lk(cvMtx);
            DBGPRINT("GrabNext no available frames wait for notification.");
            if(cv.wait_for(lk, boostd::chrono::seconds(5)) == boostd::cv_status::timeout)
                throw std::runtime_error("ThreadVideo: GrabNext blocking read for frames reached timeout.");
            DBGPRINT("GrabNext got notification.");
            unsigned char* i = queue.getNext();
            std::memcpy(image, i, queue.BufferSizeBytes());
            queue.returnUsedBuffer(i);
            return true;
        } else {
            DBGPRINT("GrabNext no available frames no wait.");
            // No frames available, no wait, simply return false.
            return false;
        }
    }
}

//! Implement VideoInput::GrabNewest()
bool ThreadVideo::GrabNewest( unsigned char* image, bool wait )
{
    if(queue.AvailableFrames() > 0) {
        // At least one valid frame in queue, call grabNewest to drop old frame and return the newest.
        DBGPRINT("GrabNewest at least one frame available.");
        unsigned char* i = queue.getNewest();
        std::memcpy(image, i, queue.BufferSizeBytes());
        queue.returnUsedBuffer(i);
        return true;
    } else {
        if(wait) {
            // Must return a frame so block on notification from grab thread.
            DBGPRINT("GrabNewest no available frames wait for notification.");
            std::unique_lock<std::mutex> lk(cvMtx);
            if(cv.wait_for(lk, boostd::chrono::seconds(5)) == boostd::cv_status::timeout)
                throw std::runtime_error("ThreadVideo: GrabNewest blocking read for frames reached timeout.");
            DBGPRINT("GrabNewest got notification.");
            unsigned char* i = queue.getNext();
            std::memcpy(image, i, queue.BufferSizeBytes());
            queue.returnUsedBuffer(i);
            return true;
        } else {
            DBGPRINT("GrabNewest no available frames no wait.");
            // No frames available, no wait, simply return false.
            return false;
        }
    }
}

void ThreadVideo::operator()()
{
    DBGPRINT("Grab thread Started.")
    // Spinning thread attempting to read from videoin[0] as fast as possible
    // relying on the videoin[0] blocking grab.
    while(!quit_grab_thread) {
        // Get a buffer from the queue;
        unsigned char* i = queue.getFreeBuffer();
        // Blocking grab (i.e. GrabNext with wait = true).
        if(videoin[0]->GrabNext(i, true)){
            queue.addValidBuffer(i);
            DBGPRINT("Grab thread got frame. valid:%d free:%d",queue.AvailableFrames(),queue.EmptyBuffers())
            // Let listening threads know we got a frame in case they are waiting.
            cv.notify_all();
        } else {
            // Grabbing frames failed, requeue the buffer.
            queue.returnUsedBuffer(i);
        }
        boostd::this_thread::yield();
    }
    DBGPRINT("Grab thread Stopped.")

    return;
}

}

#undef TSTART
#undef TGRABANDPRINT
#undef DBGPRINT
<|endoftext|>


<commit_before>#include <graphics/pipeline/commandbuffer.h>
#include <core/video/context.h>

namespace fd {
namespace graphics {
namespace pipeline {

using namespace core::video;
using namespace utils;

CommandBuffer::CommandBuffer(VkCommandPool pool, CommandBufferType type) : type(type) {
	VkCommandBufferAllocateInfo info;

	info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
	info.pNext = nullptr;
	info.level = (VkCommandBufferLevel)type;
	info.commandPool = pool;
	info.commandBufferCount = 1;

	VK(vkAllocateCommandBuffers(Context::GetDevice(), &info, &commandBuffer));

}

CommandBuffer::~CommandBuffer() {

}

void CommandBuffer::Begin(CommandBufferUsage usage) {
	VkCommandBufferBeginInfo info;

	info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
	info.pNext = nullptr;
	info.pInheritanceInfo = nullptr;
	info.flags = (VkCommandBufferUsageFlags)usage;

	VK(vkBeginCommandBuffer(commandBuffer, &info));
}

void CommandBuffer::End() {
	VK(vkEndCommandBuffer(commandBuffer));
}

VkClearColorValue clearColor = { 0.0f, 0.0f, 0.0f, 0.0f };

void CommandBuffer::BeginRenderPass(const RenderPass* const renderPass, uint_t framebufferIndex) {
	VkRenderPassBeginInfo info;

	info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
	info.pNext = nullptr;
	info.renderArea.offset.x = 0;
	info.renderArea.offset.y = 0;
	info.renderArea.extent.width = renderPass->GetWidth();
	info.renderArea.extent.height = renderPass->GetHeight();
	info.renderPass = renderPass->GetRenderPass();
	info.framebuffer = renderPass->GetFramebuffer(framebufferIndex);
	info.clearValueCount = 1;
	info.pClearValues = (VkClearValue*)&clearColor;

	vkCmdBeginRenderPass(commandBuffer, &info, VK_SUBPASS_CONTENTS_INLINE);
}

void CommandBuffer::EndRenderPass() {
	vkCmdEndRenderPass(commandBuffer);
}

void CommandBuffer::BindPipeline(const Pipeline* const pipeline) {
	vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->GetPipeline());
}

void CommandBuffer::BindPipelineLayout(const PipelineLayout* const pipelineLayout) {
	const List<VkDescriptorSet>& sets = pipelineLayout->GetDescriptorSets();

	vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout->GetPipelineLayout(), 0, (uint32)sets.GetSize(), sets.GetData(), 0, 0);
}

}
}
}<commit_msg>Initialize clear colors in commandbuffer<commit_after>#include <graphics/pipeline/commandbuffer.h>
#include <core/video/context.h>

namespace fd {
namespace graphics {
namespace pipeline {

using namespace core::video;
using namespace utils;

CommandBuffer::CommandBuffer(VkCommandPool pool, CommandBufferType type) : type(type) {
	VkCommandBufferAllocateInfo info;

	info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
	info.pNext = nullptr;
	info.level = (VkCommandBufferLevel)type;
	info.commandPool = pool;
	info.commandBufferCount = 1;

	VK(vkAllocateCommandBuffers(Context::GetDevice(), &info, &commandBuffer));

}

CommandBuffer::~CommandBuffer() {

}

void CommandBuffer::Begin(CommandBufferUsage usage) {
	VkCommandBufferBeginInfo info;

	info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
	info.pNext = nullptr;
	info.pInheritanceInfo = nullptr;
	info.flags = (VkCommandBufferUsageFlags)usage;

	VK(vkBeginCommandBuffer(commandBuffer, &info));
}

void CommandBuffer::End() {
	VK(vkEndCommandBuffer(commandBuffer));
}

void CommandBuffer::BeginRenderPass(const RenderPass* const renderPass, uint_t framebufferIndex) {
	VkRenderPassBeginInfo info;

	info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
	info.pNext = nullptr;
	info.renderArea.offset.x = 0;
	info.renderArea.offset.y = 0;
	info.renderArea.extent.width = renderPass->GetWidth();
	info.renderArea.extent.height = renderPass->GetHeight();
	info.renderPass = renderPass->GetRenderPass();
	info.framebuffer = renderPass->GetFramebuffer(framebufferIndex);

	renderPass->InitializeRenderPass(&info);

	vkCmdBeginRenderPass(commandBuffer, &info, VK_SUBPASS_CONTENTS_INLINE);
}

void CommandBuffer::EndRenderPass() {
	vkCmdEndRenderPass(commandBuffer);
}

void CommandBuffer::BindPipeline(const Pipeline* const pipeline) {
	vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->GetPipeline());
}

void CommandBuffer::BindPipelineLayout(const PipelineLayout* const pipelineLayout) {
	const List<VkDescriptorSet>& sets = pipelineLayout->GetDescriptorSets();

	vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout->GetPipelineLayout(), 0, (uint32)sets.GetSize(), sets.GetData(), 0, 0);
}

}
}
}<|endoftext|>


<commit_before>/* Copyright 2018 Google Inc. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/

// libevent based server implementation

#include "tensorflow_serving/util/net_http/server/internal/evhttp_server.h"

#include <netinet/in.h>
#include <signal.h>
#include <sys/socket.h>

#include <cstdint>
#include <memory>
#include <utility>

#include "absl/base/call_once.h"
#include "absl/memory/memory.h"
#include "libevent/include/event2/event.h"
#include "libevent/include/event2/http.h"
#include "libevent/include/event2/thread.h"
#include "libevent/include/event2/util.h"
#include "tensorflow_serving/util/net_http/internal/net_logging.h"

namespace tensorflow {
namespace serving {
namespace net_http {

namespace {

absl::once_flag libevent_init_once;

void InitLibEvent() {
  if (evthread_use_pthreads() != 0) {
    NET_LOG(FATAL, "Server requires pthread support.");
  }
  // Ignore SIGPIPE and allow errors to propagate through error codes.
  signal(SIGPIPE, SIG_IGN);
  // TODO(wenboz): windows support needed?
}

void GlobalInitialize() { absl::call_once(libevent_init_once, &InitLibEvent); }

}  // namespace

EvHTTPServer::EvHTTPServer(std::unique_ptr<ServerOptions> options)
    : server_options_(std::move(options)), accepting_requests_() {}

// May crash the server if called before WaitForTermination() returns
EvHTTPServer::~EvHTTPServer() {
  if (!is_terminating()) {
    NET_LOG(ERROR, "Serer has not been terminated. Force termination now.");
    Terminate();
  }

  if (ev_http_ != nullptr) {
    // this frees the socket handlers too
    evhttp_free(ev_http_);
  }

  if (ev_base_ != nullptr) {
    event_base_free(ev_base_);
  }
}

// Checks options.
// TODO(wenboz): support multiple ports
bool EvHTTPServer::Initialize() {
  if (server_options_->executor() == nullptr) {
    NET_LOG(FATAL, "Default EventExecutor is not configured.");
    return false;
  }

  if (server_options_->ports().empty()) {
    NET_LOG(FATAL, "Server port is not specified.");
    return false;
  }

  GlobalInitialize();

  // This ev_base_ created per-server v.s. global
  ev_base_ = event_base_new();
  if (ev_base_ == nullptr) {
    NET_LOG(FATAL, "Failed to create an event_base.");
    return false;
  }

  timeval tv_zero = {0, 0};
  immediate_ = event_base_init_common_timeout(ev_base_, &tv_zero);

  ev_http_ = evhttp_new(ev_base_);
  if (ev_http_ == nullptr) {
    NET_LOG(FATAL, "Failed to create evhttp.");
    return false;
  }

  evhttp_set_gencb(ev_http_, &DispatchEvRequestFn, this);

  return true;
}

// static function pointer
void EvHTTPServer::DispatchEvRequestFn(evhttp_request* req, void* server) {
  EvHTTPServer* http_server = static_cast<EvHTTPServer*>(server);
  http_server->DispatchEvRequest(req);
}

void EvHTTPServer::DispatchEvRequest(evhttp_request* req) {
  auto parsed_request = absl::make_unique<ParsedEvRequest>(req);

  if (!parsed_request->decode()) {
    evhttp_send_error(req, HTTP_BADREQUEST, nullptr);
    return;
  }

  std::string path(parsed_request->path);

  bool dispatched = false;
  std::unique_ptr<EvHTTPRequest> ev_request(
      new EvHTTPRequest(std::move(parsed_request), this));

  if (!ev_request->Initialize()) {
    evhttp_send_error(req, HTTP_SERVUNAVAIL, nullptr);
    return;
  }

  {
    absl::MutexLock l(&request_mu_);

    auto handler_map_it = uri_handlers_.find(path);
    if (handler_map_it != uri_handlers_.end()) {
      ev_request->SetHandlerOptions(handler_map_it->second.options);
      IncOps();
      dispatched = true;
      ScheduleHandlerReference(handler_map_it->second.handler,
                               ev_request.release());
    }

    if (!dispatched) {
      for (const auto& dispatcher : dispatchers_) {
        auto handler = dispatcher.dispatcher(ev_request.get());
        if (handler == nullptr) {
          continue;
        }
        ev_request->SetHandlerOptions(dispatcher.options);
        IncOps();
        dispatched = true;
        ScheduleHandler(std::move(handler), ev_request.release());
        break;
      }
    }
  }

  if (!dispatched) {
    evhttp_send_error(req, HTTP_NOTFOUND, nullptr);
    return;
  }
}

void EvHTTPServer::ScheduleHandlerReference(const RequestHandler& handler,
                                            EvHTTPRequest* ev_request) {
  server_options_->executor()->Schedule(
      [&handler, ev_request]() { handler(ev_request); });
}

// Exactly one copy of the handler argument
// with the lambda passed by value to Schedule()
void EvHTTPServer::ScheduleHandler(RequestHandler&& handler,
                                   EvHTTPRequest* ev_request) {
  server_options_->executor()->Schedule(
      [handler, ev_request]() { handler(ev_request); });
}

namespace {

void ResolveEphemeralPort(evhttp_bound_socket* listener, int* port) {
  sockaddr_storage ss = {};
  ev_socklen_t socklen = sizeof(ss);

  evutil_socket_t fd = evhttp_bound_socket_get_fd(listener);
  if (getsockname(fd, reinterpret_cast<sockaddr*>(&ss), &socklen)) {
    NET_LOG(ERROR, "getsockname() failed");
    return;
  }

  if (ss.ss_family == AF_INET) {
    *port = ntohs((reinterpret_cast<sockaddr_in*>(&ss))->sin_port);
  } else if (ss.ss_family == AF_INET6) {
    *port = ntohs((reinterpret_cast<sockaddr_in6*>(&ss))->sin6_port);
  } else {
    NET_LOG(ERROR, "Unknown address family %d", ss.ss_family);
  }
}

}  // namespace

bool EvHTTPServer::StartAcceptingRequests() {
  if (ev_http_ == nullptr) {
    NET_LOG(FATAL, "Server has not been successfully initialized");
    return false;
  }

  const int port = server_options_->ports().front();

  // "::"  =>  in6addr_any
  ev_uint16_t ev_port = static_cast<ev_uint16_t>(port);
  ev_listener_ = evhttp_bind_socket_with_handle(ev_http_, "::", ev_port);
  if (ev_listener_ == nullptr) {
    // in case ipv6 is not supported, fallback to inaddr_any
    ev_listener_ = evhttp_bind_socket_with_handle(ev_http_, nullptr, ev_port);
    if (ev_listener_ == nullptr) {
      NET_LOG(FATAL, "Couldn't bind to port %d", port);
      return false;
    }
  }

  // Listener counts as an active operation
  IncOps();

  port_ = port;
  if (port_ == 0) {
    ResolveEphemeralPort(ev_listener_, &port_);
  }

  IncOps();
  server_options_->executor()->Schedule([this]() {
    NET_LOG(INFO, "Entering the event loop ...");
    int result = event_base_dispatch(ev_base_);
    NET_LOG(INFO, "event_base_dispatch() exits with value %d", result);

    DecOps();
  });

  accepting_requests_.Notify();

  return true;
}

int EvHTTPServer::listen_port() const { return port_; }

bool EvHTTPServer::is_accepting_requests() const {
  return accepting_requests_.HasBeenNotified();
}

void EvHTTPServer::Terminate() {
  if (!is_accepting_requests()) {
    NET_LOG(ERROR, "Server is not running ...");
    return;
  }

  if (is_terminating()) {
    NET_LOG(ERROR, "Server is already being terminated ...");
    return;
  }

  terminating_.Notify();

  // call exit-loop from the event loop
  this->EventLoopSchedule([this]() {
    // Stop the listener first, which will delete ev_listener_
    // This may cause the loop to exit, so need be scheduled from within
    evhttp_del_accept_socket(ev_http_, ev_listener_);
    DecOps();
  });

  // Current shut-down behavior:
  // - we don't proactively delete/close any HTTP connections as part of
  //   Terminate(). This is not an issue as we don't support read-streaming yet.
  // - we don't wait for all dispatched requests to run to completion
  //   before we stop the event loop.
  // - and otherwise, this is meant to be a clean shutdown
}

bool EvHTTPServer::is_terminating() const {
  return terminating_.HasBeenNotified();
}

void EvHTTPServer::IncOps() {
  absl::MutexLock l(&ops_mu_);
  num_pending_ops_++;
}

void EvHTTPServer::DecOps() {
  absl::MutexLock l(&ops_mu_);
  num_pending_ops_--;
}

void EvHTTPServer::WaitForTermination() {
  {
    absl::MutexLock l(&ops_mu_);
    ops_mu_.Await(absl::Condition(
        +[](int64_t* count) { return *count <= 1; }, &num_pending_ops_));
  }

  int result = event_base_loopexit(ev_base_, nullptr);
  NET_LOG(INFO, "event_base_loopexit() exits with value %d", result);

  {
    absl::MutexLock l(&ops_mu_);
    ops_mu_.Await(absl::Condition(
        +[](int64_t* count) { return *count == 0; }, &num_pending_ops_));
  }
}

bool EvHTTPServer::WaitForTerminationWithTimeout(absl::Duration timeout) {
  bool wait_result = true;

  {
    absl::MutexLock l(&ops_mu_);
    wait_result = ops_mu_.AwaitWithTimeout(
        absl::Condition(
            +[](int64_t* count) { return *count <= 1; }, &num_pending_ops_),
        timeout);
  }

  if (wait_result) {
    int result = event_base_loopexit(ev_base_, nullptr);
    NET_LOG(INFO, "event_base_loopexit() exits with value %d", result);

    // This should pass immediately
    {
      absl::MutexLock l(&ops_mu_);
      wait_result = ops_mu_.AwaitWithTimeout(
          absl::Condition(
              +[](int64_t* count) { return *count == 0; }, &num_pending_ops_),
          timeout);
    }
  }

  return wait_result;
}

EvHTTPServer::UriHandlerInfo::UriHandlerInfo(
    absl::string_view uri_in, RequestHandler handler_in,
    const RequestHandlerOptions& options_in)
    : uri(uri_in.data(), uri_in.size()),
      handler(std::move(handler_in)),
      options(options_in) {}

EvHTTPServer::DispatcherInfo::DispatcherInfo(
    RequestDispatcher dispatcher_in, const RequestHandlerOptions& options_in)
    : dispatcher(std::move(dispatcher_in)), options(options_in) {}

void EvHTTPServer::RegisterRequestHandler(
    absl::string_view uri, RequestHandler handler,
    const RequestHandlerOptions& options) {
  absl::MutexLock l(&request_mu_);
  auto result = uri_handlers_.emplace(
      std::piecewise_construct, std::forward_as_tuple(uri),
      std::forward_as_tuple(uri, handler, options));

  if (!result.second) {
    NET_LOG(INFO,
            "Overwrite the existing handler registered under "
            "the URI path %.*s",
            static_cast<int>(uri.size()), uri.data());

    uri_handlers_.erase(result.first);
    if (!uri_handlers_
             .emplace(std::piecewise_construct, std::forward_as_tuple(uri),
                      std::forward_as_tuple(uri, handler, options))
             .second) {
      NET_LOG(ERROR, "Failed to register an handler under the URI path %.*s",
              static_cast<int>(uri.size()), uri.data());
    }
  }
}

void EvHTTPServer::RegisterRequestDispatcher(
    RequestDispatcher dispatcher, const RequestHandlerOptions& options) {
  absl::MutexLock l(&request_mu_);
  dispatchers_.emplace_back(dispatcher, options);
}

namespace {

void EvImmediateCallback(evutil_socket_t socket, int16_t flags, void* arg) {
  auto fn = static_cast<std::function<void()>*>(arg);
  (*fn)();
  delete fn;
}

}  // namespace

bool EvHTTPServer::EventLoopSchedule(std::function<void()> fn) {
  auto scheduled_fn = new std::function<void()>(std::move(fn));
  int result = event_base_once(ev_base_, -1, EV_TIMEOUT, EvImmediateCallback,
                               static_cast<void*>(scheduled_fn), immediate_);
  return result == 0;
}

}  // namespace net_http
}  // namespace serving
}  // namespace tensorflow
<commit_msg>Internal change.<commit_after>/* Copyright 2018 Google Inc. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/

// libevent based server implementation

#include "tensorflow_serving/util/net_http/server/internal/evhttp_server.h"

#include <netinet/in.h>
#include <signal.h>
#include <sys/socket.h>

#include <cstdint>
#include <memory>
#include <utility>

#include "absl/base/call_once.h"
#include "absl/memory/memory.h"
#include "libevent/include/event2/event.h"
#include "libevent/include/event2/http.h"
#include "libevent/include/event2/thread.h"
#include "libevent/include/event2/util.h"
#include "tensorflow_serving/util/net_http/internal/net_logging.h"

namespace tensorflow {
namespace serving {
namespace net_http {

namespace {

absl::once_flag libevent_init_once;

void InitLibEvent() {
  if (evthread_use_pthreads() != 0) {
    NET_LOG(FATAL, "Server requires pthread support.");
  }
  // Ignore SIGPIPE and allow errors to propagate through error codes.
  signal(SIGPIPE, SIG_IGN);
  // TODO(wenboz): windows support needed?
}

void GlobalInitialize() { absl::call_once(libevent_init_once, &InitLibEvent); }

}  // namespace

EvHTTPServer::EvHTTPServer(std::unique_ptr<ServerOptions> options)
    : server_options_(std::move(options)), accepting_requests_() {}

// May crash the server if called before WaitForTermination() returns
EvHTTPServer::~EvHTTPServer() {
  if (!is_terminating()) {
    NET_LOG(ERROR, "Serer has not been terminated. Force termination now.");
    Terminate();
  }

  if (ev_http_ != nullptr) {
    // this frees the socket handlers too
    evhttp_free(ev_http_);
  }

  if (ev_base_ != nullptr) {
    event_base_free(ev_base_);
  }
}

// Checks options.
// TODO(wenboz): support multiple ports
bool EvHTTPServer::Initialize() {
  if (server_options_->executor() == nullptr) {
    NET_LOG(FATAL, "Default EventExecutor is not configured.");
    return false;
  }

  if (server_options_->ports().empty()) {
    NET_LOG(FATAL, "Server port is not specified.");
    return false;
  }

  GlobalInitialize();

  // This ev_base_ created per-server v.s. global
  ev_base_ = event_base_new();
  if (ev_base_ == nullptr) {
    NET_LOG(FATAL, "Failed to create an event_base.");
    return false;
  }

  timeval tv_zero = {0, 0};
  immediate_ = event_base_init_common_timeout(ev_base_, &tv_zero);

  ev_http_ = evhttp_new(ev_base_);
  if (ev_http_ == nullptr) {
    NET_LOG(FATAL, "Failed to create evhttp.");
    return false;
  }

  evhttp_set_gencb(ev_http_, &DispatchEvRequestFn, this);

  return true;
}

// static function pointer
void EvHTTPServer::DispatchEvRequestFn(evhttp_request* req, void* server) {
  EvHTTPServer* http_server = static_cast<EvHTTPServer*>(server);
  http_server->DispatchEvRequest(req);
}

void EvHTTPServer::DispatchEvRequest(evhttp_request* req) {
  auto parsed_request = absl::make_unique<ParsedEvRequest>(req);

  if (!parsed_request->decode()) {
    evhttp_send_error(req, HTTP_BADREQUEST, nullptr);
    return;
  }

  std::string path(parsed_request->path);

  bool dispatched = false;
  std::unique_ptr<EvHTTPRequest> ev_request(
      new EvHTTPRequest(std::move(parsed_request), this));

  if (!ev_request->Initialize()) {
    evhttp_send_error(req, HTTP_SERVUNAVAIL, nullptr);
    return;
  }

  {
    absl::MutexLock l(&request_mu_);

    auto handler_map_it = uri_handlers_.find(path);
    if (handler_map_it != uri_handlers_.end()) {
      ev_request->SetHandlerOptions(handler_map_it->second.options);
      IncOps();
      dispatched = true;
      ScheduleHandlerReference(handler_map_it->second.handler,
                               ev_request.release());
    }

    if (!dispatched) {
      for (const auto& dispatcher : dispatchers_) {
        auto handler = dispatcher.dispatcher(ev_request.get());
        if (handler == nullptr) {
          continue;
        }
        ev_request->SetHandlerOptions(dispatcher.options);
        IncOps();
        dispatched = true;
        ScheduleHandler(std::move(handler), ev_request.release());
        break;
      }
    }
  }

  if (!dispatched) {
    evhttp_send_error(req, HTTP_NOTFOUND, nullptr);
    return;
  }
}

void EvHTTPServer::ScheduleHandlerReference(const RequestHandler& handler,
                                            EvHTTPRequest* ev_request) {
  server_options_->executor()->Schedule(
      [&handler, ev_request]() { handler(ev_request); });
}

// Exactly one copy of the handler argument
// with the lambda passed by value to Schedule()
void EvHTTPServer::ScheduleHandler(RequestHandler&& handler,
                                   EvHTTPRequest* ev_request) {
  server_options_->executor()->Schedule(
      [handler, ev_request]() { handler(ev_request); });
}

namespace {

void ResolveEphemeralPort(evhttp_bound_socket* listener, int* port) {
  sockaddr_storage ss = {};
  ev_socklen_t socklen = sizeof(ss);

  evutil_socket_t fd = evhttp_bound_socket_get_fd(listener);
  if (getsockname(fd, reinterpret_cast<sockaddr*>(&ss), &socklen)) {
    NET_LOG(ERROR, "getsockname() failed");
    return;
  }

  if (ss.ss_family == AF_INET) {
    *port = ntohs((reinterpret_cast<sockaddr_in*>(&ss))->sin_port);
  } else if (ss.ss_family == AF_INET6) {
    *port = ntohs((reinterpret_cast<sockaddr_in6*>(&ss))->sin6_port);
  } else {
    NET_LOG(ERROR, "Unknown address family %d", ss.ss_family);
  }
}

}  // namespace

bool EvHTTPServer::StartAcceptingRequests() {
  if (ev_http_ == nullptr) {
    NET_LOG(FATAL, "Server has not been successfully initialized");
    return false;
  }

  const int port = server_options_->ports().front();

  // "::"  =>  in6addr_any
  ev_uint16_t ev_port = static_cast<ev_uint16_t>(port);
  ev_listener_ = evhttp_bind_socket_with_handle(ev_http_, "::", ev_port);
  if (ev_listener_ == nullptr) {
    // in case ipv6 is not supported, fallback to inaddr_any
    ev_listener_ = evhttp_bind_socket_with_handle(ev_http_, nullptr, ev_port);
    if (ev_listener_ == nullptr) {
      NET_LOG(ERROR, "Couldn't bind to port %d", port);
      return false;
    }
  }

  // Listener counts as an active operation
  IncOps();

  port_ = port;
  if (port_ == 0) {
    ResolveEphemeralPort(ev_listener_, &port_);
  }

  IncOps();
  server_options_->executor()->Schedule([this]() {
    NET_LOG(INFO, "Entering the event loop ...");
    int result = event_base_dispatch(ev_base_);
    NET_LOG(INFO, "event_base_dispatch() exits with value %d", result);

    DecOps();
  });

  accepting_requests_.Notify();

  return true;
}

int EvHTTPServer::listen_port() const { return port_; }

bool EvHTTPServer::is_accepting_requests() const {
  return accepting_requests_.HasBeenNotified();
}

void EvHTTPServer::Terminate() {
  if (!is_accepting_requests()) {
    NET_LOG(ERROR, "Server is not running ...");
    return;
  }

  if (is_terminating()) {
    NET_LOG(ERROR, "Server is already being terminated ...");
    return;
  }

  terminating_.Notify();

  // call exit-loop from the event loop
  this->EventLoopSchedule([this]() {
    // Stop the listener first, which will delete ev_listener_
    // This may cause the loop to exit, so need be scheduled from within
    evhttp_del_accept_socket(ev_http_, ev_listener_);
    DecOps();
  });

  // Current shut-down behavior:
  // - we don't proactively delete/close any HTTP connections as part of
  //   Terminate(). This is not an issue as we don't support read-streaming yet.
  // - we don't wait for all dispatched requests to run to completion
  //   before we stop the event loop.
  // - and otherwise, this is meant to be a clean shutdown
}

bool EvHTTPServer::is_terminating() const {
  return terminating_.HasBeenNotified();
}

void EvHTTPServer::IncOps() {
  absl::MutexLock l(&ops_mu_);
  num_pending_ops_++;
}

void EvHTTPServer::DecOps() {
  absl::MutexLock l(&ops_mu_);
  num_pending_ops_--;
}

void EvHTTPServer::WaitForTermination() {
  {
    absl::MutexLock l(&ops_mu_);
    ops_mu_.Await(absl::Condition(
        +[](int64_t* count) { return *count <= 1; }, &num_pending_ops_));
  }

  int result = event_base_loopexit(ev_base_, nullptr);
  NET_LOG(INFO, "event_base_loopexit() exits with value %d", result);

  {
    absl::MutexLock l(&ops_mu_);
    ops_mu_.Await(absl::Condition(
        +[](int64_t* count) { return *count == 0; }, &num_pending_ops_));
  }
}

bool EvHTTPServer::WaitForTerminationWithTimeout(absl::Duration timeout) {
  bool wait_result = true;

  {
    absl::MutexLock l(&ops_mu_);
    wait_result = ops_mu_.AwaitWithTimeout(
        absl::Condition(
            +[](int64_t* count) { return *count <= 1; }, &num_pending_ops_),
        timeout);
  }

  if (wait_result) {
    int result = event_base_loopexit(ev_base_, nullptr);
    NET_LOG(INFO, "event_base_loopexit() exits with value %d", result);

    // This should pass immediately
    {
      absl::MutexLock l(&ops_mu_);
      wait_result = ops_mu_.AwaitWithTimeout(
          absl::Condition(
              +[](int64_t* count) { return *count == 0; }, &num_pending_ops_),
          timeout);
    }
  }

  return wait_result;
}

EvHTTPServer::UriHandlerInfo::UriHandlerInfo(
    absl::string_view uri_in, RequestHandler handler_in,
    const RequestHandlerOptions& options_in)
    : uri(uri_in.data(), uri_in.size()),
      handler(std::move(handler_in)),
      options(options_in) {}

EvHTTPServer::DispatcherInfo::DispatcherInfo(
    RequestDispatcher dispatcher_in, const RequestHandlerOptions& options_in)
    : dispatcher(std::move(dispatcher_in)), options(options_in) {}

void EvHTTPServer::RegisterRequestHandler(
    absl::string_view uri, RequestHandler handler,
    const RequestHandlerOptions& options) {
  absl::MutexLock l(&request_mu_);
  auto result = uri_handlers_.emplace(
      std::piecewise_construct, std::forward_as_tuple(uri),
      std::forward_as_tuple(uri, handler, options));

  if (!result.second) {
    NET_LOG(INFO,
            "Overwrite the existing handler registered under "
            "the URI path %.*s",
            static_cast<int>(uri.size()), uri.data());

    uri_handlers_.erase(result.first);
    if (!uri_handlers_
             .emplace(std::piecewise_construct, std::forward_as_tuple(uri),
                      std::forward_as_tuple(uri, handler, options))
             .second) {
      NET_LOG(ERROR, "Failed to register an handler under the URI path %.*s",
              static_cast<int>(uri.size()), uri.data());
    }
  }
}

void EvHTTPServer::RegisterRequestDispatcher(
    RequestDispatcher dispatcher, const RequestHandlerOptions& options) {
  absl::MutexLock l(&request_mu_);
  dispatchers_.emplace_back(dispatcher, options);
}

namespace {

void EvImmediateCallback(evutil_socket_t socket, int16_t flags, void* arg) {
  auto fn = static_cast<std::function<void()>*>(arg);
  (*fn)();
  delete fn;
}

}  // namespace

bool EvHTTPServer::EventLoopSchedule(std::function<void()> fn) {
  auto scheduled_fn = new std::function<void()>(std::move(fn));
  int result = event_base_once(ev_base_, -1, EV_TIMEOUT, EvImmediateCallback,
                               static_cast<void*>(scheduled_fn), immediate_);
  return result == 0;
}

}  // namespace net_http
}  // namespace serving
}  // namespace tensorflow
<|endoftext|>