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

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QPointF>
#include <QNetworkProxy>
#include <QNetworkProxyFactory>
#include <QMessageBox>
#include <QTimer>
#include <QAction>
#include <QPainter>

#include <qgeocoordinate.h>
#include <qgeomaprectangleobject.h>
#include <qgeomapmarkerobject.h>
#include <qgeomappolylineobject.h>
#include <qgeomappolygonobject.h>
#include <qgeomaprouteobject.h>
#include <qgeorouterequest.h>

#ifdef Q_OS_SYMBIAN
#include <QMessageBox>
#include <qnetworksession.h>
#include <qnetworkconfigmanager.h>
#endif

#define MARKER_HEIGHT 36
#define MARKER_WIDTH 25
#define MARKER_PIN_LEN 10

QTM_USE_NAMESPACE

MapWidget::MapWidget(QGeoMappingManager *manager)
    : QGeoMapWidget(manager),
    panActive(false) {}

MapWidget::~MapWidget() {}

void MapWidget::mousePressEvent(QGraphicsSceneMouseEvent* event)
{
    setFocus();
    if (event->button() == Qt::LeftButton) {
        panActive = true;
        startPanning();
    }

    event->accept();
}

void MapWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
{
    if (event->button() == Qt::LeftButton) {
        panActive = false;
        stopPanning();
    }

    event->accept();
}

void MapWidget::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
{
    if (panActive) {
        int deltaLeft = event->lastPos().x() - event->pos().x();
        int deltaTop  = event->lastPos().y() - event->pos().y();
        pan(deltaLeft, deltaTop);
    }

    event->accept();
}

void MapWidget::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
    setFocus();

    pan(event->lastPos().x() -  size().width() / 2.0, event->lastPos().y() - size().height() / 2.0);
    if (zoomLevel() < maximumZoomLevel())
        setZoomLevel(zoomLevel() + 1);

    event->accept();
}

void MapWidget::keyPressEvent(QKeyEvent *event)
{
    if (event->key() == Qt::Key_Minus) {
        if (zoomLevel() > minimumZoomLevel()) {
            setZoomLevel(zoomLevel() - 1);
        }
    } else if (event->key() == Qt::Key_Plus) {
        if (zoomLevel() < maximumZoomLevel()) {
            setZoomLevel(zoomLevel() + 1);
        }
    }

    event->accept();
}

void MapWidget::wheelEvent(QGraphicsSceneWheelEvent* event)
{
    if (event->delta() > 0) { //zoom in
        if (zoomLevel() < maximumZoomLevel()) {
            setZoomLevel(zoomLevel() + 1);
        }
    } else { //zoom out
        if (zoomLevel() > minimumZoomLevel()) {
            setZoomLevel(zoomLevel() - 1);
        }
    }
    event->accept();
}

/**************************************************************************************
**************************************************************************************/

MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow),
        m_serviceProvider(0),
        m_popupMenu(0)
{
    ui->setupUi(this);

    QNetworkProxyFactory::setUseSystemConfiguration(true);

    setProvider("nokia");

    qgv = new QGraphicsView(this);
    qgv->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    qgv->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    qgv->setVisible(true);
    qgv->setGeometry(QRect(0, 0, width(), height()));
    qgv->setInteractive(true);

    QGraphicsScene* scene = new QGraphicsScene(0, 0, width(), height());
    qgv->setScene(scene);

    createMarkerIcon();

    m_mapWidget = new MapWidget(m_mapManager);
    qgv->scene()->addItem(m_mapWidget);
    m_mapWidget->setGeometry(0, 0, width(), height());
    //m_mapWidget->setZoomLevel(8);
    m_mapWidget->setCenter(QGeoCoordinate(52.5,13.0));

    setContextMenuPolicy(Qt::CustomContextMenu);
    QObject::connect(this, SIGNAL(customContextMenuRequested(const QPoint&)),
                     this, SLOT(customContextMenuRequest(const QPoint&)));

    setWindowTitle(tr("Map Viewer Demo"));

    QTimer::singleShot(0, this, SLOT(delayedInit()));
}

MainWindow::~MainWindow()
{
    delete m_serviceProvider;
    delete ui;
}

void MainWindow::delayedInit()
{
    // TODO: remove this dirty, dirty hack
    m_mapWidget->setZoomLevel(m_mapWidget->zoomLevel());
    m_mapWidget->update();
}

void MainWindow::setProvider(QString providerId)
{
    if (m_serviceProvider)
        delete m_serviceProvider ;
    m_serviceProvider = new QGeoServiceProvider(providerId);
    if (m_serviceProvider->error() != QGeoServiceProvider::NoError) {
        QMessageBox::information(this, tr("MapViewer Example"), tr(
                                     "Unable to find the %1 geoservices plugin.").arg(providerId));
        qApp->quit();
        return;
    }

    m_mapManager = m_serviceProvider->mappingManager();
    m_routingManager = m_serviceProvider->routingManager();
}

void MainWindow::resizeEvent(QResizeEvent* event)
{
    qgv->resize(event->size());
    qgv->setSceneRect(0, 0, event->size().width(), event->size().height());
    m_mapWidget->resize(event->size());
}

void MainWindow::changeEvent(QEvent *e)
{
    QMainWindow::changeEvent(e);
    switch (e->type()) {
        case QEvent::LanguageChange:
            ui->retranslateUi(this);
            break;
        default:
            break;
    }
}

void MainWindow::createMenus()
{
    QAction* menuItem;
    m_popupMenu = new QMenu(this);

    menuItem = new QAction(tr("Set marker"), this);
    m_popupMenu->addAction(menuItem);
    QObject::connect(menuItem, SIGNAL(triggered(bool)),
                     this, SLOT(drawMarker(bool)));

    QMenu* subMenuItem = new QMenu(tr("Draw"), this);
    m_popupMenu->addMenu(subMenuItem);

    menuItem = new QAction(tr("Rectangle"), this);
    subMenuItem->addAction(menuItem);
    QObject::connect(menuItem, SIGNAL(triggered(bool)),
                     this, SLOT(drawRect(bool)));

    menuItem = new QAction(tr("Polyline"), this);
    subMenuItem->addAction(menuItem);
    QObject::connect(menuItem, SIGNAL(triggered(bool)),
                     this, SLOT(drawPolyline(bool)));

    menuItem = new QAction(tr("Polygon"), this);
    subMenuItem->addAction(menuItem);
    QObject::connect(menuItem, SIGNAL(triggered(bool)),
                     this, SLOT(drawPolygon(bool)));

    subMenuItem = new QMenu(tr("Route"), this);
    m_popupMenu->addMenu(subMenuItem);

    menuItem = new QAction(tr("Calculate route"), this);
    subMenuItem->addAction(menuItem);
    QObject::connect(menuItem, SIGNAL(triggered(bool)),
                     this, SLOT(calcRoute(bool)));
}

void MainWindow::drawRect(bool /*checked*/)
{
    while (markers.count() >= 2) {
        QPoint p1 = markers.takeFirst();
        QPoint p2 = markers.takeFirst();
        QPen pen(Qt::white);
        pen.setWidth(2);
        QColor fill(Qt::black);
        fill.setAlpha(65);
        QGeoMapRectangleObject *rectangle = new QGeoMapRectangleObject(m_mapWidget->screenPositionToCoordinate(p1),
                                                             m_mapWidget->screenPositionToCoordinate(p2));
        rectangle->setPen(pen);
        rectangle->setBrush(QBrush(fill));
        m_mapWidget->addMapObject(rectangle);
    }

    markers.clear();
    removeMarkers();
}

void MainWindow::drawPolyline(bool /*checked*/)
{
    QList<QGeoCoordinate> path;

    while (markers.count() >= 1) {
        QPoint p = markers.takeFirst();
        path.append(m_mapWidget->screenPositionToCoordinate(p));
    }

    QPen pen(Qt::white);
    pen.setWidth(2);
    QGeoMapPolylineObject *polyline = new QGeoMapPolylineObject();
    polyline->setPen(pen);
    polyline->setPath(path);
    m_mapWidget->addMapObject(polyline);

    removeMarkers();
}

void MainWindow::drawPolygon(bool /*checked*/)
{
    QList<QGeoCoordinate> path;

    while (markers.count() >= 1) {
        QPoint p = markers.takeFirst();
        path.append(m_mapWidget->screenPositionToCoordinate(p));
    }

    QPen pen(Qt::white);
    pen.setWidth(2);
    QGeoMapPolygonObject *polygon = new QGeoMapPolygonObject();
    polygon->setPen(pen);
    QColor fill(Qt::black);
    fill.setAlpha(65);
    polygon->setBrush(QBrush(fill));
    polygon->setPath(path);
    m_mapWidget->addMapObject(polygon);

    removeMarkers();
}

void MainWindow::drawMarker(bool /*checked*/)
{
    QGeoMapMarkerObject *marker = new QGeoMapMarkerObject(m_mapWidget->screenPositionToCoordinate(lastClicked),
                                                          QPoint(-(MARKER_WIDTH / 2), -MARKER_HEIGHT), m_markerIcon);
    m_mapWidget->addMapObject(marker);
    markers.append(lastClicked);
    markerObjects.append(marker);
}

void MainWindow::removeMarkers()
{
    while (markerObjects.size() > 0) {
        QGeoMapMarkerObject *marker = markerObjects.takeFirst();
        m_mapWidget->removeMapObject(marker);
    }
}

void MainWindow::customContextMenuRequest(const QPoint& point)
{
    lastClicked = point;

    if (focusWidget() == qgv) {

        if (!m_popupMenu)
            createMenus();

        m_popupMenu->popup(mapToGlobal(point));
    }
}

void MainWindow::createMarkerIcon()
{
    m_markerIcon = QPixmap(MARKER_WIDTH, MARKER_HEIGHT);
    m_markerIcon.fill(Qt::transparent);
    QPainter painter(&m_markerIcon);

    QPointF p1(MARKER_WIDTH / 2, MARKER_HEIGHT - 1);
    QPointF p2(MARKER_WIDTH / 2, MARKER_HEIGHT - 1 - MARKER_PIN_LEN);
    QPen pen(Qt::black);
    pen.setWidth(2);
    painter.setPen(pen);
    painter.drawLine(p1, p2);
    QRectF ellipse(0, 0, MARKER_WIDTH - 1, MARKER_WIDTH - 1);
    pen.setWidth(1);
    painter.setPen(pen);
    QColor color(Qt::green);
    color.setAlpha(127);
    QBrush brush(color);
    painter.setBrush(brush);
    painter.drawEllipse(ellipse);
}

void MainWindow::calcRoute(bool /*checked*/)
{
    if (markers.count() < 2)
        return;

    QList<QGeoCoordinate> waypoints;

    while (markers.count() >= 1) {
        QPoint p = markers.takeFirst();
        waypoints.append(m_mapWidget->screenPositionToCoordinate(p));
    }

    QGeoRouteRequest req(waypoints);
    QGeoRouteReply *reply = m_routingManager->calculateRoute(req);

    QObject::connect(reply, SIGNAL(finished()),
                     this, SLOT(routeFinished()));

    //removeMarkers();
}

void MainWindow::routeFinished()
{
    QGeoRouteReply *reply = static_cast<QGeoRouteReply*>(sender());

    if (!reply)
        return;

    if (reply->routes().size() < 1)
        return;

    QGeoMapRouteObject *route = new QGeoMapRouteObject(reply->routes().at(0));
    QColor routeColor(Qt::blue);
    routeColor.setAlpha(127); //semi-transparent
    QPen pen(routeColor);
    pen.setWidth(7);
    pen.setCapStyle(Qt::RoundCap);
    route->setPen(pen);
    m_mapWidget->addMapObject(route);
}

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

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QPointF>
#include <QNetworkProxy>
#include <QNetworkProxyFactory>
#include <QMessageBox>
#include <QTimer>
#include <QAction>
#include <QPainter>

#include <qgeocoordinate.h>
#include <qgeomaprectangleobject.h>
#include <qgeomapmarkerobject.h>
#include <qgeomappolylineobject.h>
#include <qgeomappolygonobject.h>
#include <qgeomaprouteobject.h>
#include <qgeorouterequest.h>

#ifdef Q_OS_SYMBIAN
#include <QMessageBox>
#include <qnetworksession.h>
#include <qnetworkconfigmanager.h>
#endif

#define MARKER_HEIGHT 36
#define MARKER_WIDTH 25
#define MARKER_PIN_LEN 10

QTM_USE_NAMESPACE

MapWidget::MapWidget(QGeoMappingManager *manager)
    : QGeoMapWidget(manager),
    panActive(false) {}

MapWidget::~MapWidget() {}

void MapWidget::mousePressEvent(QGraphicsSceneMouseEvent* event)
{
    setFocus();
    if (event->button() == Qt::LeftButton) {
        panActive = true;
        startPanning();
    }

    event->accept();
}

void MapWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
{
    if (event->button() == Qt::LeftButton) {
        panActive = false;
        stopPanning();
    }

    event->accept();
}

void MapWidget::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
{
    if (panActive) {
        int deltaLeft = event->lastPos().x() - event->pos().x();
        int deltaTop  = event->lastPos().y() - event->pos().y();
        pan(deltaLeft, deltaTop);
    }

    event->accept();
}

void MapWidget::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
    setFocus();

    pan(event->lastPos().x() -  size().width() / 2.0, event->lastPos().y() - size().height() / 2.0);
    if (zoomLevel() < maximumZoomLevel())
        setZoomLevel(zoomLevel() + 1);

    event->accept();
}

void MapWidget::keyPressEvent(QKeyEvent *event)
{
    if (event->key() == Qt::Key_Minus) {
        if (zoomLevel() > minimumZoomLevel()) {
            setZoomLevel(zoomLevel() - 1);
        }
    } else if (event->key() == Qt::Key_Plus) {
        if (zoomLevel() < maximumZoomLevel()) {
            setZoomLevel(zoomLevel() + 1);
        }
    }

    event->accept();
}

void MapWidget::wheelEvent(QGraphicsSceneWheelEvent* event)
{
    if (event->delta() > 0) { //zoom in
        if (zoomLevel() < maximumZoomLevel()) {
            setZoomLevel(zoomLevel() + 1);
        }
    } else { //zoom out
        if (zoomLevel() > minimumZoomLevel()) {
            setZoomLevel(zoomLevel() - 1);
        }
    }
    event->accept();
}

/**************************************************************************************
**************************************************************************************/

MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow),
        m_serviceProvider(0),
        m_popupMenu(0)
{
    ui->setupUi(this);

    QNetworkProxyFactory::setUseSystemConfiguration(true);

    setProvider("nokia");

    qgv = new QGraphicsView(this);
    qgv->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    qgv->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    qgv->setVisible(true);
    qgv->setGeometry(QRect(0, 0, width(), height()));
    qgv->setInteractive(true);

    QGraphicsScene* scene = new QGraphicsScene(0, 0, width(), height());
    qgv->setScene(scene);

    createMarkerIcon();

    m_mapWidget = new MapWidget(m_mapManager);
    qgv->scene()->addItem(m_mapWidget);
    m_mapWidget->setGeometry(0, 0, width(), height());
    //m_mapWidget->setZoomLevel(8);
    m_mapWidget->setCenter(QGeoCoordinate(52.5,13.0));

    setContextMenuPolicy(Qt::CustomContextMenu);
    QObject::connect(this, SIGNAL(customContextMenuRequested(const QPoint&)),
                     this, SLOT(customContextMenuRequest(const QPoint&)));

    setWindowTitle(tr("Map Viewer Demo"));

    QTimer::singleShot(0, this, SLOT(delayedInit()));
}

MainWindow::~MainWindow()
{
    delete m_serviceProvider;
    delete ui;
}

void MainWindow::delayedInit()
{
    // TODO: remove this dirty, dirty hack
    m_mapWidget->setZoomLevel(m_mapWidget->zoomLevel());
    m_mapWidget->update();
}

void MainWindow::setProvider(QString providerId)
{
    if (m_serviceProvider)
        delete m_serviceProvider ;
    m_serviceProvider = new QGeoServiceProvider(providerId);
    if (m_serviceProvider->error() != QGeoServiceProvider::NoError) {
        QMessageBox::information(this, tr("MapViewer Example"), tr(
                                     "Unable to find the %1 geoservices plugin.").arg(providerId));
        qApp->quit();
        return;
    }

    m_mapManager = m_serviceProvider->mappingManager();
    m_routingManager = m_serviceProvider->routingManager();
}

void MainWindow::resizeEvent(QResizeEvent* event)
{
    qgv->resize(event->size());
    qgv->setSceneRect(0, 0, event->size().width(), event->size().height());
    m_mapWidget->resize(event->size());
}

void MainWindow::changeEvent(QEvent *e)
{
    QMainWindow::changeEvent(e);
    switch (e->type()) {
        case QEvent::LanguageChange:
            ui->retranslateUi(this);
            break;
        default:
            break;
    }
}

void MainWindow::createMenus()
{
    QAction* menuItem;
    m_popupMenu = new QMenu(this);

    menuItem = new QAction(tr("Set marker"), this);
    m_popupMenu->addAction(menuItem);
    QObject::connect(menuItem, SIGNAL(triggered(bool)),
                     this, SLOT(drawMarker(bool)));

    QMenu* subMenuItem = new QMenu(tr("Draw"), this);
    m_popupMenu->addMenu(subMenuItem);

    menuItem = new QAction(tr("Rectangle"), this);
    subMenuItem->addAction(menuItem);
    QObject::connect(menuItem, SIGNAL(triggered(bool)),
                     this, SLOT(drawRect(bool)));

    menuItem = new QAction(tr("Polyline"), this);
    subMenuItem->addAction(menuItem);
    QObject::connect(menuItem, SIGNAL(triggered(bool)),
                     this, SLOT(drawPolyline(bool)));

    menuItem = new QAction(tr("Polygon"), this);
    subMenuItem->addAction(menuItem);
    QObject::connect(menuItem, SIGNAL(triggered(bool)),
                     this, SLOT(drawPolygon(bool)));

    subMenuItem = new QMenu(tr("Route"), this);
    m_popupMenu->addMenu(subMenuItem);

    menuItem = new QAction(tr("Calculate route"), this);
    subMenuItem->addAction(menuItem);
    QObject::connect(menuItem, SIGNAL(triggered(bool)),
                     this, SLOT(calcRoute(bool)));
}

void MainWindow::drawRect(bool /*checked*/)
{
    while (markers.count() >= 2) {
        QPoint p1 = markers.takeFirst();
        QPoint p2 = markers.takeFirst();
        QPen pen(Qt::white);
        pen.setWidth(2);
        QColor fill(Qt::black);
        fill.setAlpha(65);
        QGeoMapRectangleObject *rectangle = new QGeoMapRectangleObject(m_mapWidget->screenPositionToCoordinate(p1),
                                                             m_mapWidget->screenPositionToCoordinate(p2));
        rectangle->setPen(pen);
        rectangle->setBrush(QBrush(fill));
        m_mapWidget->addMapObject(rectangle);
    }

    markers.clear();
    removeMarkers();
}

void MainWindow::drawPolyline(bool /*checked*/)
{
    QList<QGeoCoordinate> path;

    while (markers.count() >= 1) {
        QPoint p = markers.takeFirst();
        path.append(m_mapWidget->screenPositionToCoordinate(p));
    }

    QPen pen(Qt::white);
    pen.setWidth(2);
    QGeoMapPolylineObject *polyline = new QGeoMapPolylineObject();
    polyline->setPen(pen);
    polyline->setPath(path);
    m_mapWidget->addMapObject(polyline);

    removeMarkers();
}

void MainWindow::drawPolygon(bool /*checked*/)
{
    QList<QGeoCoordinate> path;

    while (markers.count() >= 1) {
        QPoint p = markers.takeFirst();
        path.append(m_mapWidget->screenPositionToCoordinate(p));
    }

    QPen pen(Qt::white);
    pen.setWidth(2);
    QGeoMapPolygonObject *polygon = new QGeoMapPolygonObject();
    polygon->setPen(pen);
    QColor fill(Qt::black);
    fill.setAlpha(65);
    polygon->setBrush(QBrush(fill));
    polygon->setPath(path);
    m_mapWidget->addMapObject(polygon);

    removeMarkers();
}

void MainWindow::drawMarker(bool /*checked*/)
{
    QGeoMapMarkerObject *marker = new QGeoMapMarkerObject(m_mapWidget->screenPositionToCoordinate(lastClicked),
                                                          QPoint(-(MARKER_WIDTH / 2), -MARKER_HEIGHT), m_markerIcon);
    m_mapWidget->addMapObject(marker);
    markers.append(lastClicked);
    markerObjects.append(marker);
}

void MainWindow::removeMarkers()
{
    while (markerObjects.size() > 0) {
        QGeoMapMarkerObject *marker = markerObjects.takeFirst();
        m_mapWidget->removeMapObject(marker);
        marker->deleteLater();
    }
}

void MainWindow::customContextMenuRequest(const QPoint& point)
{
    lastClicked = point;

    if (focusWidget() == qgv) {

        if (!m_popupMenu)
            createMenus();

        m_popupMenu->popup(mapToGlobal(point));
    }
}

void MainWindow::createMarkerIcon()
{
    m_markerIcon = QPixmap(MARKER_WIDTH, MARKER_HEIGHT);
    m_markerIcon.fill(Qt::transparent);
    QPainter painter(&m_markerIcon);

    QPointF p1(MARKER_WIDTH / 2, MARKER_HEIGHT - 1);
    QPointF p2(MARKER_WIDTH / 2, MARKER_HEIGHT - 1 - MARKER_PIN_LEN);
    QPen pen(Qt::black);
    pen.setWidth(2);
    painter.setPen(pen);
    painter.drawLine(p1, p2);
    QRectF ellipse(0, 0, MARKER_WIDTH - 1, MARKER_WIDTH - 1);
    pen.setWidth(1);
    painter.setPen(pen);
    QColor color(Qt::green);
    color.setAlpha(127);
    QBrush brush(color);
    painter.setBrush(brush);
    painter.drawEllipse(ellipse);
}

void MainWindow::calcRoute(bool /*checked*/)
{
    if (markers.count() < 2)
        return;

    QList<QGeoCoordinate> waypoints;

    while (markers.count() >= 1) {
        QPoint p = markers.takeFirst();
        waypoints.append(m_mapWidget->screenPositionToCoordinate(p));
    }

    QGeoRouteRequest req(waypoints);
    QGeoRouteReply *reply = m_routingManager->calculateRoute(req);

    QObject::connect(reply, SIGNAL(finished()),
                     this, SLOT(routeFinished()));

    removeMarkers();
}

void MainWindow::routeFinished()
{
    QGeoRouteReply *reply = static_cast<QGeoRouteReply*>(sender());

    if (!reply)
        return;

    if (reply->routes().size() < 1)
        return;

    QGeoMapRouteObject *route = new QGeoMapRouteObject(reply->routes().at(0));
    QColor routeColor(Qt::blue);
    routeColor.setAlpha(127); //semi-transparent
    QPen pen(routeColor);
    pen.setWidth(7);
    pen.setCapStyle(Qt::RoundCap);
    route->setPen(pen);
    m_mapWidget->addMapObject(route);
}

<|endoftext|>


<commit_before>// Copyright (c) 2019 by Robert Bosch GmbH. 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.

#include "iceoryx_posh/internal/roudi_environment/runtime_test_interface.hpp"
#include "iceoryx_posh/runtime/posh_runtime.hpp"
#include "iceoryx_utils/cxx/helplets.hpp"

namespace iox
{
namespace roudi
{
using runtime::PoshRuntime;

thread_local PoshRuntime* RuntimeTestInterface::t_activeRuntime{nullptr};
thread_local std::atomic<uint64_t> RuntimeTestInterface::t_currentRouDiContext{0};
std::atomic<uint64_t> RuntimeTestInterface::s_currentRouDiContext{0};

std::mutex RuntimeTestInterface::s_runtimeAccessMutex;

std::map<ProcessName_t, PoshRuntime*> RuntimeTestInterface::s_runtimes;

RuntimeTestInterface::RuntimeTestInterface()
{
    std::lock_guard<std::mutex> lock(RuntimeTestInterface::s_runtimeAccessMutex);

    PoshRuntime::setRuntimeFactory(RuntimeTestInterface::runtimeFactoryGetInstance);
}

RuntimeTestInterface::~RuntimeTestInterface()
{
    if (m_doCleanupOnDestruction)
    {
        // cleanup holds its own lock
        cleanupRuntimes();

        std::lock_guard<std::mutex> lock(RuntimeTestInterface::s_runtimeAccessMutex);
        PoshRuntime::setRuntimeFactory(PoshRuntime::defaultRuntimeFactory);
    }
}

RuntimeTestInterface::RuntimeTestInterface(RuntimeTestInterface&& rhs)
{
    rhs.m_doCleanupOnDestruction = false;
}
RuntimeTestInterface& RuntimeTestInterface::operator=(RuntimeTestInterface&& rhs)
{
    rhs.m_doCleanupOnDestruction = false;
    return *this;
}

void RuntimeTestInterface::cleanupRuntimes()
{
    std::lock_guard<std::mutex> lock(RuntimeTestInterface::s_runtimeAccessMutex);

    for (const auto& e : RuntimeTestInterface::s_runtimes)
    {
        delete e.second;
    }
    RuntimeTestInterface::s_runtimes.clear();
    RuntimeTestInterface::s_currentRouDiContext.operator++(std::memory_order_relaxed);
}

void RuntimeTestInterface::eraseRuntime(const ProcessName_t& name)
{
    std::lock_guard<std::mutex> lock(RuntimeTestInterface::s_runtimeAccessMutex);
    auto iter = RuntimeTestInterface::s_runtimes.find(name);
    if (iter != RuntimeTestInterface::s_runtimes.end())
    {
        delete iter->second;
        RuntimeTestInterface::s_runtimes.erase(name);
    }
}

PoshRuntime& RuntimeTestInterface::runtimeFactoryGetInstance(cxx::optional<const ProcessName_t*> name)
{
    std::lock_guard<std::mutex> lock(RuntimeTestInterface::s_runtimeAccessMutex);

    auto currentRouDiContext = RuntimeTestInterface::s_currentRouDiContext.load(std::memory_order_relaxed);
    if (RuntimeTestInterface::t_currentRouDiContext.load(std::memory_order_relaxed) != currentRouDiContext)
    {
        RuntimeTestInterface::t_currentRouDiContext.store(currentRouDiContext, std::memory_order_relaxed);
        RuntimeTestInterface::t_activeRuntime = nullptr;
    }

    bool nameIsNullopt{!name.has_value()};
    bool invalidGetRuntimeAccess{RuntimeTestInterface::t_activeRuntime == nullptr && nameIsNullopt};
    cxx::Expects(!invalidGetRuntimeAccess);

    if (RuntimeTestInterface::t_activeRuntime != nullptr && nameIsNullopt)
    {
        return *RuntimeTestInterface::t_activeRuntime;
    }

    if (nameIsNullopt)
    {
        ProcessName_t* emptyName;
        name.emplace(emptyName);
    }

    auto iter = RuntimeTestInterface::s_runtimes.find(*name.value());
    if (iter != RuntimeTestInterface::s_runtimes.end())
    {
        RuntimeTestInterface::t_activeRuntime = iter->second;
    }
    else
    {
        auto runtimeImpl = new runtime::PoshRuntime(name, false);
        RuntimeTestInterface::s_runtimes.insert({*name.value(), runtimeImpl});

        RuntimeTestInterface::t_activeRuntime = runtimeImpl;
    }

    return *RuntimeTestInterface::t_activeRuntime;
}

} // namespace roudi
} // namespace iox
<commit_msg>iox-#382 remove unnecessary nullopt check<commit_after>// Copyright (c) 2019 by Robert Bosch GmbH. 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.

#include "iceoryx_posh/internal/roudi_environment/runtime_test_interface.hpp"
#include "iceoryx_posh/runtime/posh_runtime.hpp"
#include "iceoryx_utils/cxx/helplets.hpp"

namespace iox
{
namespace roudi
{
using runtime::PoshRuntime;

thread_local PoshRuntime* RuntimeTestInterface::t_activeRuntime{nullptr};
thread_local std::atomic<uint64_t> RuntimeTestInterface::t_currentRouDiContext{0};
std::atomic<uint64_t> RuntimeTestInterface::s_currentRouDiContext{0};

std::mutex RuntimeTestInterface::s_runtimeAccessMutex;

std::map<ProcessName_t, PoshRuntime*> RuntimeTestInterface::s_runtimes;

RuntimeTestInterface::RuntimeTestInterface()
{
    std::lock_guard<std::mutex> lock(RuntimeTestInterface::s_runtimeAccessMutex);

    PoshRuntime::setRuntimeFactory(RuntimeTestInterface::runtimeFactoryGetInstance);
}

RuntimeTestInterface::~RuntimeTestInterface()
{
    if (m_doCleanupOnDestruction)
    {
        // cleanup holds its own lock
        cleanupRuntimes();

        std::lock_guard<std::mutex> lock(RuntimeTestInterface::s_runtimeAccessMutex);
        PoshRuntime::setRuntimeFactory(PoshRuntime::defaultRuntimeFactory);
    }
}

RuntimeTestInterface::RuntimeTestInterface(RuntimeTestInterface&& rhs)
{
    rhs.m_doCleanupOnDestruction = false;
}
RuntimeTestInterface& RuntimeTestInterface::operator=(RuntimeTestInterface&& rhs)
{
    rhs.m_doCleanupOnDestruction = false;
    return *this;
}

void RuntimeTestInterface::cleanupRuntimes()
{
    std::lock_guard<std::mutex> lock(RuntimeTestInterface::s_runtimeAccessMutex);

    for (const auto& e : RuntimeTestInterface::s_runtimes)
    {
        delete e.second;
    }
    RuntimeTestInterface::s_runtimes.clear();
    RuntimeTestInterface::s_currentRouDiContext.operator++(std::memory_order_relaxed);
}

void RuntimeTestInterface::eraseRuntime(const ProcessName_t& name)
{
    std::lock_guard<std::mutex> lock(RuntimeTestInterface::s_runtimeAccessMutex);
    auto iter = RuntimeTestInterface::s_runtimes.find(name);
    if (iter != RuntimeTestInterface::s_runtimes.end())
    {
        delete iter->second;
        RuntimeTestInterface::s_runtimes.erase(name);
    }
}

PoshRuntime& RuntimeTestInterface::runtimeFactoryGetInstance(cxx::optional<const ProcessName_t*> name)
{
    std::lock_guard<std::mutex> lock(RuntimeTestInterface::s_runtimeAccessMutex);

    auto currentRouDiContext = RuntimeTestInterface::s_currentRouDiContext.load(std::memory_order_relaxed);
    if (RuntimeTestInterface::t_currentRouDiContext.load(std::memory_order_relaxed) != currentRouDiContext)
    {
        RuntimeTestInterface::t_currentRouDiContext.store(currentRouDiContext, std::memory_order_relaxed);
        RuntimeTestInterface::t_activeRuntime = nullptr;
    }

    bool nameIsNullopt{!name.has_value()};
    bool invalidGetRuntimeAccess{RuntimeTestInterface::t_activeRuntime == nullptr && nameIsNullopt};
    cxx::Expects(!invalidGetRuntimeAccess);

    if (RuntimeTestInterface::t_activeRuntime != nullptr && nameIsNullopt)
    {
        return *RuntimeTestInterface::t_activeRuntime;
    }

    // if (nameIsNullopt)
    //{
    // ProcessName_t* emptyName;
    // name.emplace(emptyName);
    //}

    auto iter = RuntimeTestInterface::s_runtimes.find(*name.value());
    if (iter != RuntimeTestInterface::s_runtimes.end())
    {
        RuntimeTestInterface::t_activeRuntime = iter->second;
    }
    else
    {
        auto runtimeImpl = new runtime::PoshRuntime(name, false);
        RuntimeTestInterface::s_runtimes.insert({*name.value(), runtimeImpl});

        RuntimeTestInterface::t_activeRuntime = runtimeImpl;
    }

    return *RuntimeTestInterface::t_activeRuntime;
}

} // namespace roudi
} // namespace iox
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2014 Cloudius Systems, Ltd.
 */

#include "posix-stack.hh"
#include "net.hh"
#include "packet.hh"
#include "api.hh"

namespace net {

class posix_connected_socket_impl final : public connected_socket_impl {
    pollable_fd _fd;
private:
    explicit posix_connected_socket_impl(pollable_fd fd) : _fd(std::move(fd)) {}
public:
    virtual input_stream<char> input() override { return input_stream<char>(posix_data_source(_fd)); }
    virtual output_stream<char> output() override { return output_stream<char>(posix_data_sink(_fd), 8192); }
    friend class posix_server_socket_impl;
    friend class posix_ap_server_socket_impl;
    friend class posix_reuseport_server_socket_impl;
    friend class posix_network_stack;
    friend class posix_ap_network_stack;
};

future<connected_socket, socket_address>
posix_server_socket_impl::accept() {
    return _lfd.accept().then([this] (pollable_fd fd, socket_address sa) {
        static unsigned balance = 0;
        auto cpu = balance++ % smp::count;

        if (cpu == engine.cpu_id()) {
            std::unique_ptr<connected_socket_impl> csi(new posix_connected_socket_impl(std::move(fd)));
            return make_ready_future<connected_socket, socket_address>(
                    connected_socket(std::move(csi)), sa);
        } else {
            smp::submit_to(cpu, [this, fd = std::move(fd.get_file_desc()), sa] () mutable {
                posix_ap_server_socket_impl::move_connected_socket(_sa, pollable_fd(std::move(fd)), sa);
            });
            return accept();
        }
    });
}

future<connected_socket, socket_address> posix_ap_server_socket_impl::accept() {
    auto conni = conn_q.find(_sa.as_posix_sockaddr_in());
    if (conni != conn_q.end()) {
        connection c = std::move(conni->second);
        conn_q.erase(conni);
        std::unique_ptr<connected_socket_impl> csi(new posix_connected_socket_impl(std::move(c.fd)));
        return make_ready_future<connected_socket, socket_address>(connected_socket(std::move(csi)), std::move(c.addr));
    } else {
        auto i = sockets.emplace(std::piecewise_construct, std::make_tuple(_sa.as_posix_sockaddr_in()), std::make_tuple());
        assert(i.second);
        return i.first->second.get_future();
    }
}

future<connected_socket, socket_address>
posix_reuseport_server_socket_impl::accept() {
    return _lfd.accept().then([this] (pollable_fd fd, socket_address sa) {
        std::unique_ptr<connected_socket_impl> csi(new posix_connected_socket_impl(std::move(fd)));
        return make_ready_future<connected_socket, socket_address>(
            connected_socket(std::move(csi)), sa);
    });
}

void  posix_ap_server_socket_impl::move_connected_socket(socket_address sa, pollable_fd fd, socket_address addr) {
    auto i = sockets.find(sa.as_posix_sockaddr_in());
    if (i != sockets.end()) {
        std::unique_ptr<connected_socket_impl> csi(new posix_connected_socket_impl(std::move(fd)));
        i->second.set_value(connected_socket(std::move(csi)), std::move(addr));
        sockets.erase(i);
    } else {
        conn_q.emplace(std::piecewise_construct, std::make_tuple(sa.as_posix_sockaddr_in()), std::make_tuple(std::move(fd), std::move(addr)));
    }
}

data_source posix_data_source(pollable_fd& fd) {
    return data_source(std::make_unique<posix_data_source_impl>(fd));
}

future<temporary_buffer<char>>
posix_data_source_impl::get() {
    return _fd.read_some(_buf.get_write(), _buf_size).then([this] (size_t size) {
        _buf.trim(size);
        auto ret = std::move(_buf);
        _buf = temporary_buffer<char>(_buf_size);
        return make_ready_future<temporary_buffer<char>>(std::move(ret));
    });
}

data_sink posix_data_sink(pollable_fd& fd) {
    return data_sink(std::make_unique<posix_data_sink_impl>(fd));
}

std::vector<struct iovec> to_iovec(const packet& p) {
    std::vector<struct iovec> v;
    v.reserve(p.nr_frags());
    for (auto&& f : p.fragments()) {
        v.push_back({.iov_base = f.base, .iov_len = f.size});
    }
    return v;
}

std::vector<iovec> to_iovec(std::vector<temporary_buffer<char>>& buf_vec) {
    std::vector<iovec> v;
    v.reserve(buf_vec.size());
    for (auto& buf : buf_vec) {
        v.push_back({.iov_base = buf.get_write(), .iov_len = buf.size()});
    }
    return v;
}

future<>
posix_data_sink_impl::put(temporary_buffer<char> buf) {
    return _fd.write_all(buf.get(), buf.size()).then([d = buf.release()] {});
}

future<>
posix_data_sink_impl::put(packet p) {
    _p = std::move(p);
    return _fd.write_all(_p).then([this] { _p.reset(); });
}

server_socket
posix_network_stack::listen(socket_address sa, listen_options opt) {
    if (_reuseport)
        return server_socket(std::make_unique<posix_reuseport_server_socket_impl>(sa, engine.posix_listen(sa, opt)));
    else
        return server_socket(std::make_unique<posix_server_socket_impl>(sa, engine.posix_listen(sa, opt)));
}

future<connected_socket>
posix_network_stack::connect(socket_address sa) {
    return engine.posix_connect(sa).then([] (pollable_fd fd) {
        std::unique_ptr<connected_socket_impl> csi(new posix_connected_socket_impl(std::move(fd)));
        return make_ready_future<connected_socket>(connected_socket(std::move(csi)));
    });
}

thread_local std::unordered_map<::sockaddr_in, promise<connected_socket, socket_address>> posix_ap_server_socket_impl::sockets;
thread_local std::unordered_multimap<::sockaddr_in, posix_ap_server_socket_impl::connection> posix_ap_server_socket_impl::conn_q;

server_socket
posix_ap_network_stack::listen(socket_address sa, listen_options opt) {
    if (_reuseport)
        return server_socket(std::make_unique<posix_reuseport_server_socket_impl>(sa, engine.posix_listen(sa, opt)));
    else
        return server_socket(std::make_unique<posix_ap_server_socket_impl>(sa));
}

future<connected_socket>
posix_ap_network_stack::connect(socket_address sa) {
    return engine.posix_connect(sa).then([] (pollable_fd fd) {
        std::unique_ptr<connected_socket_impl> csi(new posix_connected_socket_impl(std::move(fd)));
        return make_ready_future<connected_socket>(connected_socket(std::move(csi)));
    });
}

struct cmsg_with_pktinfo {
    struct cmsghdrcmh;
    struct in_pktinfo pktinfo;
};

class posix_udp_channel : public udp_channel_impl {
private:
    static constexpr int MAX_DATAGRAM_SIZE = 65507;
    struct recv_ctx {
        struct msghdr _hdr;
        struct iovec _iov;
        socket_address _src_addr;
        char* _buffer;
        cmsg_with_pktinfo _cmsg;

        recv_ctx() {
            memset(&_hdr, 0, sizeof(_hdr));
            _hdr.msg_iov = &_iov;
            _hdr.msg_iovlen = 1;
            _hdr.msg_name = &_src_addr.u.sa;
            _hdr.msg_namelen = sizeof(_src_addr.u.sas);
            memset(&_cmsg, 0, sizeof(_cmsg));
            _hdr.msg_control = &_cmsg;
            _hdr.msg_controllen = sizeof(_cmsg);
        }

        void prepare() {
            _buffer = new char[MAX_DATAGRAM_SIZE];
            _iov.iov_base = _buffer;
            _iov.iov_len = MAX_DATAGRAM_SIZE;
        }
    };
    struct send_ctx {
        struct msghdr _hdr;
        std::vector<struct iovec> _iovecs;
        socket_address _dst;
        packet _p;

        send_ctx() {
            memset(&_hdr, 0, sizeof(_hdr));
            _hdr.msg_name = &_dst.u.sa;
            _hdr.msg_namelen = sizeof(_dst.u.sas);
        }

        void prepare(ipv4_addr dst, packet p) {
            _dst = make_ipv4_address(dst);
            _p = std::move(p);
            _iovecs = std::move(to_iovec(_p));
            _hdr.msg_iov = _iovecs.data();
            _hdr.msg_iovlen = _iovecs.size();
        }
    };
    std::unique_ptr<pollable_fd> _fd;
    ipv4_addr _address;
    recv_ctx _recv;
    send_ctx _send;
    bool _closed;
public:
    posix_udp_channel(ipv4_addr bind_address)
            : _closed(false) {
        auto sa = make_ipv4_address(bind_address);
        file_desc fd = file_desc::socket(sa.u.sa.sa_family, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
        fd.setsockopt(SOL_IP, IP_PKTINFO, true);
        fd.bind(sa.u.sa, sizeof(sa.u.sas));
        _address = ipv4_addr(fd.get_address());
        _fd = std::make_unique<pollable_fd>(std::move(fd));
    }
    virtual ~posix_udp_channel() {};
    virtual future<udp_datagram> receive() override;
    virtual future<> send(ipv4_addr dst, const char *msg);
    virtual future<> send(ipv4_addr dst, packet p);
    virtual void close() override {
        _closed = true;
        _fd.reset();
    }
    virtual bool is_closed() const override { return _closed; }
};

future<> posix_udp_channel::send(ipv4_addr dst, const char *message) {
    auto len = strlen(message);
    return _fd->sendto(make_ipv4_address(dst), message, len)
            .then([len] (size_t size) { assert(size == len); });
}

future<> posix_udp_channel::send(ipv4_addr dst, packet p) {
    auto len = p.len();
    _send.prepare(dst, std::move(p));
    return _fd->sendmsg(&_send._hdr)
            .then([len] (size_t size) { assert(size == len); });
}

udp_channel
posix_network_stack::make_udp_channel(ipv4_addr addr) {
    return udp_channel(std::make_unique<posix_udp_channel>(addr));
}

class posix_datagram : public udp_datagram_impl {
private:
    ipv4_addr _src;
    ipv4_addr _dst;
    packet _p;
public:
    posix_datagram(ipv4_addr src, ipv4_addr dst, packet p) : _src(src), _dst(dst), _p(std::move(p)) {}
    virtual ipv4_addr get_src() override { return _src; }
    virtual ipv4_addr get_dst() override { return _dst; }
    virtual uint16_t get_dst_port() override { return _dst.port; }
    virtual packet& get_data() override { return _p; }
};

future<udp_datagram>
posix_udp_channel::receive() {
    _recv.prepare();
    return _fd->recvmsg(&_recv._hdr).then([this] (size_t size) {
        auto dst = ipv4_addr(_recv._cmsg.pktinfo.ipi_addr.s_addr, _address.port);
        return make_ready_future<udp_datagram>(udp_datagram(std::make_unique<posix_datagram>(
            _recv._src_addr, dst, packet(fragment{_recv._buffer, size}, [buf = _recv._buffer] { delete[] buf; }))));
    });
}

network_stack_registrator nsr_posix{"posix",
    boost::program_options::options_description(),
    [](boost::program_options::variables_map ops) {
        return smp::main_thread() ? posix_network_stack::create(ops) : posix_ap_network_stack::create(ops);
    },
    true
};

}
<commit_msg>net: implemented SO_REUSEPORT support on UDP<commit_after>/*
 * Copyright (C) 2014 Cloudius Systems, Ltd.
 */

#include "posix-stack.hh"
#include "net.hh"
#include "packet.hh"
#include "api.hh"

namespace net {

class posix_connected_socket_impl final : public connected_socket_impl {
    pollable_fd _fd;
private:
    explicit posix_connected_socket_impl(pollable_fd fd) : _fd(std::move(fd)) {}
public:
    virtual input_stream<char> input() override { return input_stream<char>(posix_data_source(_fd)); }
    virtual output_stream<char> output() override { return output_stream<char>(posix_data_sink(_fd), 8192); }
    friend class posix_server_socket_impl;
    friend class posix_ap_server_socket_impl;
    friend class posix_reuseport_server_socket_impl;
    friend class posix_network_stack;
    friend class posix_ap_network_stack;
};

future<connected_socket, socket_address>
posix_server_socket_impl::accept() {
    return _lfd.accept().then([this] (pollable_fd fd, socket_address sa) {
        static unsigned balance = 0;
        auto cpu = balance++ % smp::count;

        if (cpu == engine.cpu_id()) {
            std::unique_ptr<connected_socket_impl> csi(new posix_connected_socket_impl(std::move(fd)));
            return make_ready_future<connected_socket, socket_address>(
                    connected_socket(std::move(csi)), sa);
        } else {
            smp::submit_to(cpu, [this, fd = std::move(fd.get_file_desc()), sa] () mutable {
                posix_ap_server_socket_impl::move_connected_socket(_sa, pollable_fd(std::move(fd)), sa);
            });
            return accept();
        }
    });
}

future<connected_socket, socket_address> posix_ap_server_socket_impl::accept() {
    auto conni = conn_q.find(_sa.as_posix_sockaddr_in());
    if (conni != conn_q.end()) {
        connection c = std::move(conni->second);
        conn_q.erase(conni);
        std::unique_ptr<connected_socket_impl> csi(new posix_connected_socket_impl(std::move(c.fd)));
        return make_ready_future<connected_socket, socket_address>(connected_socket(std::move(csi)), std::move(c.addr));
    } else {
        auto i = sockets.emplace(std::piecewise_construct, std::make_tuple(_sa.as_posix_sockaddr_in()), std::make_tuple());
        assert(i.second);
        return i.first->second.get_future();
    }
}

future<connected_socket, socket_address>
posix_reuseport_server_socket_impl::accept() {
    return _lfd.accept().then([this] (pollable_fd fd, socket_address sa) {
        std::unique_ptr<connected_socket_impl> csi(new posix_connected_socket_impl(std::move(fd)));
        return make_ready_future<connected_socket, socket_address>(
            connected_socket(std::move(csi)), sa);
    });
}

void  posix_ap_server_socket_impl::move_connected_socket(socket_address sa, pollable_fd fd, socket_address addr) {
    auto i = sockets.find(sa.as_posix_sockaddr_in());
    if (i != sockets.end()) {
        std::unique_ptr<connected_socket_impl> csi(new posix_connected_socket_impl(std::move(fd)));
        i->second.set_value(connected_socket(std::move(csi)), std::move(addr));
        sockets.erase(i);
    } else {
        conn_q.emplace(std::piecewise_construct, std::make_tuple(sa.as_posix_sockaddr_in()), std::make_tuple(std::move(fd), std::move(addr)));
    }
}

data_source posix_data_source(pollable_fd& fd) {
    return data_source(std::make_unique<posix_data_source_impl>(fd));
}

future<temporary_buffer<char>>
posix_data_source_impl::get() {
    return _fd.read_some(_buf.get_write(), _buf_size).then([this] (size_t size) {
        _buf.trim(size);
        auto ret = std::move(_buf);
        _buf = temporary_buffer<char>(_buf_size);
        return make_ready_future<temporary_buffer<char>>(std::move(ret));
    });
}

data_sink posix_data_sink(pollable_fd& fd) {
    return data_sink(std::make_unique<posix_data_sink_impl>(fd));
}

std::vector<struct iovec> to_iovec(const packet& p) {
    std::vector<struct iovec> v;
    v.reserve(p.nr_frags());
    for (auto&& f : p.fragments()) {
        v.push_back({.iov_base = f.base, .iov_len = f.size});
    }
    return v;
}

std::vector<iovec> to_iovec(std::vector<temporary_buffer<char>>& buf_vec) {
    std::vector<iovec> v;
    v.reserve(buf_vec.size());
    for (auto& buf : buf_vec) {
        v.push_back({.iov_base = buf.get_write(), .iov_len = buf.size()});
    }
    return v;
}

future<>
posix_data_sink_impl::put(temporary_buffer<char> buf) {
    return _fd.write_all(buf.get(), buf.size()).then([d = buf.release()] {});
}

future<>
posix_data_sink_impl::put(packet p) {
    _p = std::move(p);
    return _fd.write_all(_p).then([this] { _p.reset(); });
}

server_socket
posix_network_stack::listen(socket_address sa, listen_options opt) {
    if (_reuseport)
        return server_socket(std::make_unique<posix_reuseport_server_socket_impl>(sa, engine.posix_listen(sa, opt)));
    else
        return server_socket(std::make_unique<posix_server_socket_impl>(sa, engine.posix_listen(sa, opt)));
}

future<connected_socket>
posix_network_stack::connect(socket_address sa) {
    return engine.posix_connect(sa).then([] (pollable_fd fd) {
        std::unique_ptr<connected_socket_impl> csi(new posix_connected_socket_impl(std::move(fd)));
        return make_ready_future<connected_socket>(connected_socket(std::move(csi)));
    });
}

thread_local std::unordered_map<::sockaddr_in, promise<connected_socket, socket_address>> posix_ap_server_socket_impl::sockets;
thread_local std::unordered_multimap<::sockaddr_in, posix_ap_server_socket_impl::connection> posix_ap_server_socket_impl::conn_q;

server_socket
posix_ap_network_stack::listen(socket_address sa, listen_options opt) {
    if (_reuseport)
        return server_socket(std::make_unique<posix_reuseport_server_socket_impl>(sa, engine.posix_listen(sa, opt)));
    else
        return server_socket(std::make_unique<posix_ap_server_socket_impl>(sa));
}

future<connected_socket>
posix_ap_network_stack::connect(socket_address sa) {
    return engine.posix_connect(sa).then([] (pollable_fd fd) {
        std::unique_ptr<connected_socket_impl> csi(new posix_connected_socket_impl(std::move(fd)));
        return make_ready_future<connected_socket>(connected_socket(std::move(csi)));
    });
}

struct cmsg_with_pktinfo {
    struct cmsghdrcmh;
    struct in_pktinfo pktinfo;
};

class posix_udp_channel : public udp_channel_impl {
private:
    static constexpr int MAX_DATAGRAM_SIZE = 65507;
    struct recv_ctx {
        struct msghdr _hdr;
        struct iovec _iov;
        socket_address _src_addr;
        char* _buffer;
        cmsg_with_pktinfo _cmsg;

        recv_ctx() {
            memset(&_hdr, 0, sizeof(_hdr));
            _hdr.msg_iov = &_iov;
            _hdr.msg_iovlen = 1;
            _hdr.msg_name = &_src_addr.u.sa;
            _hdr.msg_namelen = sizeof(_src_addr.u.sas);
            memset(&_cmsg, 0, sizeof(_cmsg));
            _hdr.msg_control = &_cmsg;
            _hdr.msg_controllen = sizeof(_cmsg);
        }

        void prepare() {
            _buffer = new char[MAX_DATAGRAM_SIZE];
            _iov.iov_base = _buffer;
            _iov.iov_len = MAX_DATAGRAM_SIZE;
        }
    };
    struct send_ctx {
        struct msghdr _hdr;
        std::vector<struct iovec> _iovecs;
        socket_address _dst;
        packet _p;

        send_ctx() {
            memset(&_hdr, 0, sizeof(_hdr));
            _hdr.msg_name = &_dst.u.sa;
            _hdr.msg_namelen = sizeof(_dst.u.sas);
        }

        void prepare(ipv4_addr dst, packet p) {
            _dst = make_ipv4_address(dst);
            _p = std::move(p);
            _iovecs = std::move(to_iovec(_p));
            _hdr.msg_iov = _iovecs.data();
            _hdr.msg_iovlen = _iovecs.size();
        }
    };
    std::unique_ptr<pollable_fd> _fd;
    ipv4_addr _address;
    recv_ctx _recv;
    send_ctx _send;
    bool _closed;
public:
    posix_udp_channel(ipv4_addr bind_address)
            : _closed(false) {
        auto sa = make_ipv4_address(bind_address);
        file_desc fd = file_desc::socket(sa.u.sa.sa_family, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
        fd.setsockopt(SOL_IP, IP_PKTINFO, true);
        if (engine.posix_reuseport_available()) {
            fd.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1);
        }
        fd.bind(sa.u.sa, sizeof(sa.u.sas));
        _address = ipv4_addr(fd.get_address());
        _fd = std::make_unique<pollable_fd>(std::move(fd));
    }
    virtual ~posix_udp_channel() {};
    virtual future<udp_datagram> receive() override;
    virtual future<> send(ipv4_addr dst, const char *msg);
    virtual future<> send(ipv4_addr dst, packet p);
    virtual void close() override {
        _closed = true;
        _fd.reset();
    }
    virtual bool is_closed() const override { return _closed; }
};

future<> posix_udp_channel::send(ipv4_addr dst, const char *message) {
    auto len = strlen(message);
    return _fd->sendto(make_ipv4_address(dst), message, len)
            .then([len] (size_t size) { assert(size == len); });
}

future<> posix_udp_channel::send(ipv4_addr dst, packet p) {
    auto len = p.len();
    _send.prepare(dst, std::move(p));
    return _fd->sendmsg(&_send._hdr)
            .then([len] (size_t size) { assert(size == len); });
}

udp_channel
posix_network_stack::make_udp_channel(ipv4_addr addr) {
    return udp_channel(std::make_unique<posix_udp_channel>(addr));
}

class posix_datagram : public udp_datagram_impl {
private:
    ipv4_addr _src;
    ipv4_addr _dst;
    packet _p;
public:
    posix_datagram(ipv4_addr src, ipv4_addr dst, packet p) : _src(src), _dst(dst), _p(std::move(p)) {}
    virtual ipv4_addr get_src() override { return _src; }
    virtual ipv4_addr get_dst() override { return _dst; }
    virtual uint16_t get_dst_port() override { return _dst.port; }
    virtual packet& get_data() override { return _p; }
};

future<udp_datagram>
posix_udp_channel::receive() {
    _recv.prepare();
    return _fd->recvmsg(&_recv._hdr).then([this] (size_t size) {
        auto dst = ipv4_addr(_recv._cmsg.pktinfo.ipi_addr.s_addr, _address.port);
        return make_ready_future<udp_datagram>(udp_datagram(std::make_unique<posix_datagram>(
            _recv._src_addr, dst, packet(fragment{_recv._buffer, size}, [buf = _recv._buffer] { delete[] buf; }))));
    });
}

network_stack_registrator nsr_posix{"posix",
    boost::program_options::options_description(),
    [](boost::program_options::variables_map ops) {
        return smp::main_thread() ? posix_network_stack::create(ops) : posix_ap_network_stack::create(ops);
    },
    true
};

}
<|endoftext|>


<commit_before>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2020 Morwenn
 *
 * 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 <algorithm>
#include <iterator>
#include <random>
#include <vector>
#include <catch2/catch.hpp>
#include <cpp-sort/sorters.h>
#include <cpp-sort/utility/buffer.h>
#include "distributions.h"
#include "move_only.h"

TEMPLATE_TEST_CASE( "test extended compatibility with LWG 3031", "[sorters]",
                    cppsort::block_sorter<cppsort::utility::fixed_buffer<0>>,
                    cppsort::default_sorter,
                    cppsort::drop_merge_sorter,
                    cppsort::grail_sorter<>,
                    cppsort::heap_sorter,
                    cppsort::insertion_sorter,
                    cppsort::merge_insertion_sorter,
                    cppsort::merge_sorter,
                    cppsort::pdq_sorter,
                    cppsort::poplar_sorter,
                    cppsort::quick_merge_sorter,
                    cppsort::quick_sorter,
                    cppsort::selection_sorter,
                    cppsort::smooth_sorter,
                    cppsort::spin_sorter,
                    cppsort::split_sorter,
                    cppsort::std_sorter,
                    cppsort::tim_sorter,
                    cppsort::verge_sorter )
{
    // LWG3031 allows algorithms taking a predicate to work correctly when
    // said predicate takes non-const references on either side as long as
    // it doesn't modify its parameters

    std::vector<int> collection;
    collection.reserve(50);
    auto distribution = dist::shuffled{};
    distribution(std::back_inserter(collection), 0, 25);

    std::mt19937 engine(Catch::rngSeed());
    std::shuffle(std::begin(collection), std::end(collection), engine);

    auto sort = TestType{};
    sort(collection, [](const int& lhs, const int& rhs) { return lhs < rhs; });
    //sort(collection, [](const int& lhs, int& rhs) { return lhs < rhs; });
    //sort(collection, [](int& lhs, const int& rhs) { return lhs < rhs; });
    sort(collection, [](int& lhs, int& rhs) { return lhs < rhs; });
    CHECK( std::is_sorted(std::begin(collection), std::end(collection)) );
}
<commit_msg>Cleanup every_sorter_non_const_compare test<commit_after>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2020 Morwenn
 *
 * 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 <algorithm>
#include <iterator>
#include <vector>
#include <catch2/catch.hpp>
#include <cpp-sort/sorters.h>
#include <cpp-sort/utility/buffer.h>
#include "distributions.h"

TEMPLATE_TEST_CASE( "test extended compatibility with LWG 3031", "[sorters]",
                    cppsort::block_sorter<cppsort::utility::fixed_buffer<0>>,
                    cppsort::default_sorter,
                    cppsort::drop_merge_sorter,
                    cppsort::grail_sorter<>,
                    cppsort::heap_sorter,
                    cppsort::insertion_sorter,
                    cppsort::merge_insertion_sorter,
                    cppsort::merge_sorter,
                    cppsort::pdq_sorter,
                    cppsort::poplar_sorter,
                    cppsort::quick_merge_sorter,
                    cppsort::quick_sorter,
                    cppsort::selection_sorter,
                    cppsort::smooth_sorter,
                    cppsort::spin_sorter,
                    cppsort::split_sorter,
                    cppsort::std_sorter,
                    cppsort::tim_sorter,
                    cppsort::verge_sorter )
{
    // LWG3031 allows algorithms taking a predicate to work correctly when
    // said predicate takes non-const references on either side as long as
    // it doesn't modify its parameters

    std::vector<int> collection;
    collection.reserve(50);
    auto distribution = dist::shuffled{};
    distribution(std::back_inserter(collection), 50, -25);

    TestType sorter;
    sorter(collection, [](int& lhs, int& rhs) { return lhs < rhs; });
    CHECK( std::is_sorted(std::begin(collection), std::end(collection)) );
}
<|endoftext|>


<commit_before>#include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h"
#include "http2/adapter/mock_nghttp2_callbacks.h"
#include "http2/adapter/test_frame_sequence.h"
#include "http2/adapter/test_utils.h"
#include "common/platform/api/quiche_test.h"

namespace http2 {
namespace adapter {
namespace test {
namespace {

using testing::_;

enum FrameType {
  DATA,
  HEADERS,
  PRIORITY,
  RST_STREAM,
  SETTINGS,
  PUSH_PROMISE,
  PING,
  GOAWAY,
  WINDOW_UPDATE,
};

nghttp2_option* GetOptions() {
  nghttp2_option* options;
  nghttp2_option_new(&options);
  // Set some common options for compatibility.
  nghttp2_option_set_no_closed_streams(options, 1);
  nghttp2_option_set_no_auto_window_update(options, 1);
  nghttp2_option_set_max_send_header_block_length(options, 0x2000000);
  nghttp2_option_set_max_outbound_ack(options, 10000);
  return options;
}

// Verifies nghttp2 behavior when acting as a client.
TEST(Nghttp2ClientTest, ClientReceivesUnexpectedHeaders) {
  testing::StrictMock<MockNghttp2Callbacks> mock_callbacks;
  auto nghttp2_callbacks = MockNghttp2Callbacks::GetCallbacks();
  nghttp2_option* options = GetOptions();
  nghttp2_session* ptr;
  nghttp2_session_client_new2(&ptr, nghttp2_callbacks.get(), &mock_callbacks,
                              options);

  auto client_session = MakeSessionPtr(ptr);

  const std::string initial_frames = TestFrameSequence()
                                         .ServerPreface()
                                         .Ping(42)
                                         .WindowUpdate(0, 1000)
                                         .Serialize();

  testing::InSequence seq;
  EXPECT_CALL(mock_callbacks, OnBeginFrame(HasFrameHeader(0, SETTINGS, 0)));
  EXPECT_CALL(mock_callbacks, OnFrameRecv(IsSettings(testing::IsEmpty())));
  EXPECT_CALL(mock_callbacks, OnBeginFrame(HasFrameHeader(0, PING, 0)));
  EXPECT_CALL(mock_callbacks, OnFrameRecv(IsPing(42)));
  EXPECT_CALL(mock_callbacks,
              OnBeginFrame(HasFrameHeader(0, WINDOW_UPDATE, 0)));
  EXPECT_CALL(mock_callbacks, OnFrameRecv(IsWindowUpdate(1000)));

  nghttp2_session_mem_recv(client_session.get(),
                           ToUint8Ptr(initial_frames.data()),
                           initial_frames.size());

  const std::string unexpected_stream_frames =
      TestFrameSequence()
          .Headers(1,
                   {{":status", "200"},
                    {"server", "my-fake-server"},
                    {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}},
                   /*fin=*/false)
          .Data(1, "This is the response body.")
          .RstStream(3, Http2ErrorCode::INTERNAL_ERROR)
          .GoAway(5, Http2ErrorCode::ENHANCE_YOUR_CALM, "calm down!!")
          .Serialize();

  EXPECT_CALL(mock_callbacks, OnBeginFrame(HasFrameHeader(1, HEADERS, _)));
  EXPECT_CALL(mock_callbacks, OnInvalidFrameRecv(IsHeaders(1, _, _), _));
  // No events from the DATA, RST_STREAM or GOAWAY.

  nghttp2_session_mem_recv(client_session.get(),
                           ToUint8Ptr(unexpected_stream_frames.data()),
                           unexpected_stream_frames.size());

  nghttp2_option_del(options);
}

}  // namespace
}  // namespace test
}  // namespace adapter
}  // namespace http2
<commit_msg>Adds a vanilla nghttp2 test case that submits a request to a client session.<commit_after>#include "third_party/nghttp2/src/lib/includes/nghttp2/nghttp2.h"
#include "absl/strings/str_cat.h"
#include "http2/adapter/mock_nghttp2_callbacks.h"
#include "http2/adapter/nghttp2_util.h"
#include "http2/adapter/test_frame_sequence.h"
#include "http2/adapter/test_utils.h"
#include "common/platform/api/quiche_test.h"

namespace http2 {
namespace adapter {
namespace test {
namespace {

using testing::_;

enum FrameType {
  DATA,
  HEADERS,
  PRIORITY,
  RST_STREAM,
  SETTINGS,
  PUSH_PROMISE,
  PING,
  GOAWAY,
  WINDOW_UPDATE,
};

nghttp2_option* GetOptions() {
  nghttp2_option* options;
  nghttp2_option_new(&options);
  // Set some common options for compatibility.
  nghttp2_option_set_no_closed_streams(options, 1);
  nghttp2_option_set_no_auto_window_update(options, 1);
  nghttp2_option_set_max_send_header_block_length(options, 0x2000000);
  nghttp2_option_set_max_outbound_ack(options, 10000);
  return options;
}

class TestDataSource {
 public:
  explicit TestDataSource(std::string data) : data_(std::move(data)) {}

  absl::string_view ReadNext(size_t size) {
    const size_t to_send = std::min(size, remaining_.size());
    auto ret = remaining_.substr(0, to_send);
    remaining_.remove_prefix(to_send);
    return ret;
  }

  size_t SelectPayloadLength(size_t max_length) {
    return std::min(max_length, remaining_.size());
  }

  bool empty() const { return remaining_.empty(); }

 private:
  const std::string data_;
  absl::string_view remaining_ = data_;
};

class Nghttp2Test : public testing::Test {
 public:
  Nghttp2Test() : session_(MakeSessionPtr(nullptr)) {}

  void SetUp() override { InitializeSession(); }

  virtual Perspective GetPerspective() = 0;

  void InitializeSession() {
    auto nghttp2_callbacks = MockNghttp2Callbacks::GetCallbacks();
    nghttp2_option* options = GetOptions();
    nghttp2_session* ptr;
    if (GetPerspective() == Perspective::kClient) {
      nghttp2_session_client_new2(&ptr, nghttp2_callbacks.get(),
                                  &mock_callbacks_, options);
    } else {
      nghttp2_session_server_new2(&ptr, nghttp2_callbacks.get(),
                                  &mock_callbacks_, options);
    }
    nghttp2_option_del(options);

    // Sets up the Send() callback to append to |serialized_|.
    EXPECT_CALL(mock_callbacks_, Send(_, _, _))
        .WillRepeatedly([this](const uint8_t* data, size_t length, int flags) {
          absl::StrAppend(&serialized_, ToStringView(data, length));
          return length;
        });
    // Sets up the SendData() callback to fetch and append data from a
    // TestDataSource.
    EXPECT_CALL(mock_callbacks_, SendData(_, _, _, _))
        .WillRepeatedly([this](nghttp2_frame* frame, const uint8_t* framehd,
                               size_t length, nghttp2_data_source* source) {
          QUICHE_LOG(INFO) << "Appending frame header and " << length
                           << " bytes of data";
          auto* s = static_cast<TestDataSource*>(source->ptr);
          absl::StrAppend(&serialized_, ToStringView(framehd, 9),
                          s->ReadNext(length));
          return 0;
        });
    session_.reset(ptr);
  }

  testing::StrictMock<MockNghttp2Callbacks> mock_callbacks_;
  nghttp2_session_unique_ptr session_;
  std::string serialized_;
};

class Nghttp2ClientTest : public Nghttp2Test {
 public:
  Perspective GetPerspective() override { return Perspective::kClient; }
};

// Verifies nghttp2 behavior when acting as a client.
TEST_F(Nghttp2ClientTest, ClientReceivesUnexpectedHeaders) {
  const std::string initial_frames = TestFrameSequence()
                                         .ServerPreface()
                                         .Ping(42)
                                         .WindowUpdate(0, 1000)
                                         .Serialize();

  testing::InSequence seq;
  EXPECT_CALL(mock_callbacks_, OnBeginFrame(HasFrameHeader(0, SETTINGS, 0)));
  EXPECT_CALL(mock_callbacks_, OnFrameRecv(IsSettings(testing::IsEmpty())));
  EXPECT_CALL(mock_callbacks_, OnBeginFrame(HasFrameHeader(0, PING, 0)));
  EXPECT_CALL(mock_callbacks_, OnFrameRecv(IsPing(42)));
  EXPECT_CALL(mock_callbacks_,
              OnBeginFrame(HasFrameHeader(0, WINDOW_UPDATE, 0)));
  EXPECT_CALL(mock_callbacks_, OnFrameRecv(IsWindowUpdate(1000)));

  ssize_t result = nghttp2_session_mem_recv(
      session_.get(), ToUint8Ptr(initial_frames.data()), initial_frames.size());
  ASSERT_EQ(result, initial_frames.size());

  const std::string unexpected_stream_frames =
      TestFrameSequence()
          .Headers(1,
                   {{":status", "200"},
                    {"server", "my-fake-server"},
                    {"date", "Tue, 6 Apr 2021 12:54:01 GMT"}},
                   /*fin=*/false)
          .Data(1, "This is the response body.")
          .RstStream(3, Http2ErrorCode::INTERNAL_ERROR)
          .GoAway(5, Http2ErrorCode::ENHANCE_YOUR_CALM, "calm down!!")
          .Serialize();

  EXPECT_CALL(mock_callbacks_, OnBeginFrame(HasFrameHeader(1, HEADERS, _)));
  EXPECT_CALL(mock_callbacks_, OnInvalidFrameRecv(IsHeaders(1, _, _), _));
  // No events from the DATA, RST_STREAM or GOAWAY.

  nghttp2_session_mem_recv(session_.get(),
                           ToUint8Ptr(unexpected_stream_frames.data()),
                           unexpected_stream_frames.size());
}

// Tests the request-sending behavior of nghttp2 when acting as a client.
TEST_F(Nghttp2ClientTest, ClientSendsRequest) {
  int result = nghttp2_session_send(session_.get());
  ASSERT_EQ(result, 0);

  EXPECT_THAT(serialized_, testing::StrEq(spdy::kHttp2ConnectionHeaderPrefix));
  serialized_.clear();

  const std::string initial_frames =
      TestFrameSequence().ServerPreface().Serialize();
  testing::InSequence s;

  // Server preface (empty SETTINGS)
  EXPECT_CALL(mock_callbacks_, OnBeginFrame(HasFrameHeader(0, SETTINGS, 0)));
  EXPECT_CALL(mock_callbacks_, OnFrameRecv(IsSettings(testing::IsEmpty())));

  ssize_t recv_result = nghttp2_session_mem_recv(
      session_.get(), ToUint8Ptr(initial_frames.data()), initial_frames.size());
  EXPECT_EQ(initial_frames.size(), recv_result);

  // Client wants to send a SETTINGS ack.
  EXPECT_CALL(mock_callbacks_, BeforeFrameSend(IsSettings(testing::IsEmpty())));
  EXPECT_CALL(mock_callbacks_, OnFrameSend(IsSettings(testing::IsEmpty())));
  EXPECT_TRUE(nghttp2_session_want_write(session_.get()));
  result = nghttp2_session_send(session_.get());
  EXPECT_THAT(serialized_, EqualsFrames({spdy::SpdyFrameType::SETTINGS}));
  serialized_.clear();

  EXPECT_FALSE(nghttp2_session_want_write(session_.get()));

  // The following sets up the client request.
  std::vector<std::pair<absl::string_view, absl::string_view>> headers = {
      {":method", "POST"},
      {":scheme", "http"},
      {":authority", "example.com"},
      {":path", "/this/is/request/one"}};
  std::vector<nghttp2_nv> nvs;
  for (const auto& h : headers) {
    nvs.push_back({.name = ToUint8Ptr(h.first.data()),
                   .value = ToUint8Ptr(h.second.data()),
                   .namelen = h.first.size(),
                   .valuelen = h.second.size()});
  }
  const absl::string_view kBody = "This is an example request body.";
  TestDataSource source{std::string(kBody)};
  nghttp2_data_provider provider{
      .source = {.ptr = &source},
      .read_callback = [](nghttp2_session*, int32_t, uint8_t*, size_t length,
                          uint32_t* data_flags, nghttp2_data_source* source,
                          void*) {
        auto* s = static_cast<TestDataSource*>(source->ptr);
        const ssize_t ret = s->SelectPayloadLength(length);
        if (ret < length) {
          *data_flags |= NGHTTP2_DATA_FLAG_EOF;
        }
        *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
        return ret;
      }};
  // After submitting the request, the client will want to write.
  int stream_id =
      nghttp2_submit_request(session_.get(), nullptr /* pri_spec */, nvs.data(),
                             nvs.size(), &provider, nullptr /* stream_data */);
  EXPECT_GT(stream_id, 0);
  EXPECT_TRUE(nghttp2_session_want_write(session_.get()));

  // We expect that the client will want to write HEADERS, then DATA.
  EXPECT_CALL(mock_callbacks_, BeforeFrameSend(IsHeaders(stream_id, _, _)));
  EXPECT_CALL(mock_callbacks_, OnFrameSend(IsHeaders(stream_id, _, _)));
  EXPECT_CALL(mock_callbacks_, OnFrameSend(IsData(stream_id, kBody.size(), _)));
  nghttp2_session_send(session_.get());
  EXPECT_THAT(serialized_, EqualsFrames({spdy::SpdyFrameType::HEADERS,
                                         spdy::SpdyFrameType::DATA}));
  EXPECT_THAT(serialized_, testing::HasSubstr(kBody));

  // Once the request is flushed, the client no longer wants to write.
  EXPECT_FALSE(nghttp2_session_want_write(session_.get()));
}

}  // namespace
}  // namespace test
}  // namespace adapter
}  // namespace http2
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2008-2016 Juli Mallett. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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.
 */

#include <event/event_callback.h>
#include <event/event_system.h>

#define	EVENT_SYSTEM_WORKER_MAX		8

EventSystem::EventSystem(void)
: td_("EventThread"),
  poll_(),
  timeout_(),
  destroy_(),
  threads_(),
  interest_queue_mtx_("EventSystem::interest_queue"),
  interest_queue_(),
  workers_()
{ }

Action *
EventSystem::register_interest(const EventInterest& interest, SimpleCallback *cb)
{
	ScopedLock _(&interest_queue_mtx_);
	CallbackQueue *cbq = interest_queue_[interest];
	if (cbq == NULL) {
		cbq = new CallbackQueue();
		interest_queue_[interest] = cbq;
	}
	Action *a = cbq->schedule(cb);
	return (a);
}

/*
 * Request an EventThread to submit work to.
 *
 * For now these are primarily for known long-running or demanding tasks,
 * and normal stff goes through the EventThread instead.  Eventually, we
 * want something like this to be the norm, and to have categories for
 * them, so that we can increase locality of reference, or even have a
 * variety of strategies available so that we could ensure heterogenous
 * workloads to maximize parallelism.
 *
 * XXX All kinds of configuration and seatbelts needed here.
 *
 * For now we allow up to EVENT_SYSTEM_WORKER_MAX threads to be created.
 */
CallbackScheduler *
EventSystem::worker(void)
{
	static unsigned wcnt;

	if (workers_.size() < EVENT_SYSTEM_WORKER_MAX) {
		CallbackThread *td = new CallbackThread("EventWorker");
		workers_.push_back(td);

		thread_wait(td);

		return (td);
	}

	CallbackThread *td = workers_[wcnt];
	wcnt = (wcnt + 1) % EVENT_SYSTEM_WORKER_MAX;
	return (td);
}

void
EventSystem::stop(void)
{
	/*
	 * If we have been told to stop, fire all shutdown events.
	 */
	interest_queue_mtx_.lock();
	CallbackQueue *q = interest_queue_[EventInterestStop];
	if (q != NULL)
		interest_queue_.erase(EventInterestStop);
	interest_queue_mtx_.unlock();
	if (q != NULL && !q->empty()) {
		INFO("/event/system") << "Queueing stop handlers.";
		q->drain();
	}

	/*
	 * Pass stop notification on to all threads.
	 */
	std::deque<Thread *>::const_iterator it;
	for (it = threads_.begin(); it != threads_.end(); ++it) {
		Thread *td = *it;
		td->stop();
	}
}
<commit_msg>Launch worker threads on create.<commit_after>/*
 * Copyright (c) 2008-2016 Juli Mallett. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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.
 */

#include <event/event_callback.h>
#include <event/event_system.h>

#define	EVENT_SYSTEM_WORKER_MAX		8

EventSystem::EventSystem(void)
: td_("EventThread"),
  poll_(),
  timeout_(),
  destroy_(),
  threads_(),
  interest_queue_mtx_("EventSystem::interest_queue"),
  interest_queue_(),
  workers_()
{ }

Action *
EventSystem::register_interest(const EventInterest& interest, SimpleCallback *cb)
{
	ScopedLock _(&interest_queue_mtx_);
	CallbackQueue *cbq = interest_queue_[interest];
	if (cbq == NULL) {
		cbq = new CallbackQueue();
		interest_queue_[interest] = cbq;
	}
	Action *a = cbq->schedule(cb);
	return (a);
}

/*
 * Request an EventThread to submit work to.
 *
 * For now these are primarily for known long-running or demanding tasks,
 * and normal stff goes through the EventThread instead.  Eventually, we
 * want something like this to be the norm, and to have categories for
 * them, so that we can increase locality of reference, or even have a
 * variety of strategies available so that we could ensure heterogenous
 * workloads to maximize parallelism.
 *
 * XXX All kinds of configuration and seatbelts needed here.
 *
 * For now we allow up to EVENT_SYSTEM_WORKER_MAX threads to be created.
 */
CallbackScheduler *
EventSystem::worker(void)
{
	static unsigned wcnt;

	if (workers_.size() < EVENT_SYSTEM_WORKER_MAX) {
		CallbackThread *td = new CallbackThread("EventWorker");
		workers_.push_back(td);

		td->start();

		thread_wait(td);

		return (td);
	}

	CallbackThread *td = workers_[wcnt];
	wcnt = (wcnt + 1) % EVENT_SYSTEM_WORKER_MAX;
	return (td);
}

void
EventSystem::stop(void)
{
	/*
	 * If we have been told to stop, fire all shutdown events.
	 */
	interest_queue_mtx_.lock();
	CallbackQueue *q = interest_queue_[EventInterestStop];
	if (q != NULL)
		interest_queue_.erase(EventInterestStop);
	interest_queue_mtx_.unlock();
	if (q != NULL && !q->empty()) {
		INFO("/event/system") << "Queueing stop handlers.";
		q->drain();
	}

	/*
	 * Pass stop notification on to all threads.
	 */
	std::deque<Thread *>::const_iterator it;
	for (it = threads_.begin(); it != threads_.end(); ++it) {
		Thread *td = *it;
		td->stop();
	}
}
<|endoftext|>


<commit_before>#pragma once
#include "includes.hpp"

#define TAGLIB_STATIC
#include <taglib/fileref.h>
#include <taglib/tag.h>
#include <taglib/tpropertymap.h>

class playlist : public Component, public FileDragAndDropTarget {
    struct playlistModel : public TableListBoxModel {
		base::string paths, talbum, tartist, ttitle;
		std::vector<uint32> tyear, ttrack;
		std::vector<uint32> paths_i, talbum_i, tartist_i, ttitle_i;

		void init() {
			paths_i.clear(); // this one first (getNumRows)
			paths_i.push_back(0);
			paths.clear();
			talbum.clear();
			tartist.clear();
			tyear.clear();
			ttitle.clear();
			ttrack.clear();
			talbum_i.clear();
			talbum_i.push_back(0);
			tartist_i.clear();
			tartist_i.push_back(0);
			ttitle_i.clear();
			ttitle_i.push_back(0);
		}

		void addItem(const String &path) {
			paths.append(path.toStdString());
			paths_i.push_back(paths.size());

			// read tags from file
			TagLib::FileRef file(path.toRawUTF8());
			if (!file.isNull() && file.tag()) {
				talbum.append(file.tag()->album().toCString());
				tartist.append(file.tag()->artist().toCString());
				ttitle.append(file.tag()->title().toCString());
				tyear.push_back(file.tag()->year());
				ttrack.push_back(file.tag()->track());
			}
			else {
				tyear.push_back(0);
				ttrack.push_back(0);
			}
			talbum_i.push_back(talbum.size());
			tartist_i.push_back(tartist.size());
			ttitle_i.push_back(ttitle.size());
		}

		base::string getItemPath(size_t index) {
			return base::string(paths.begin() + paths_i[index],
								paths.begin() + paths_i[index + 1]);
		}

		uint getItemTrack(size_t index) {
			return ttrack[index];
		}

		uint getItemYear(size_t index) {
			return tyear[index];
		}

		base::string getItemAlbum(size_t index) {
			return base::string(talbum.begin() + talbum_i[index],
								talbum.begin() + talbum_i[index + 1]);
		}

		base::string getItemArtist(size_t index) {
			return base::string(tartist.begin() + tartist_i[index],
								tartist.begin() + tartist_i[index + 1]);
		}

		base::string getItemTitle(size_t index) {
			return base::string(ttitle.begin() + ttitle_i[index],
								ttitle.begin() + ttitle_i[index + 1]);
		}

		// GUI
        int getNumRows() override {
            return paths_i.size() - 1;
        }

		// This is overloaded from TableListBoxModel, and should fill in the background of the whole row
		void paintRowBackground(Graphics& g, int rowNumber, int /*width*/, int /*height*/, bool rowIsSelected) override {
			if (rowIsSelected)
				g.fillAll(Colours::lightblue);
			else if (rowNumber % 2)
				g.fillAll(Colour(0xffeeeeee));
		}

		// This is overloaded from TableListBoxModel, and must paint any cells that aren't using custom
		// components.
		void paintCell(Graphics& g, int rowNumber, int columnId,
						int width, int height, bool /*rowIsSelected*/) override {
			g.setColour(Colours::black);
			g.setFont(height * 0.7f);
			if (columnId == 0)
				g.drawText(base::toStr(getItemTrack(rowNumber)), 5, 0, width, height, Justification::centredLeft, true);
			else if (columnId == 1)
				g.drawText(getItemAlbum(rowNumber), 5, 0, width, height, Justification::centredLeft, true);
			else if (columnId == 2)
				g.drawText(getItemArtist(rowNumber), 5, 0, width, height, Justification::centredLeft, true);
			else if (columnId == 3)
				g.drawText(getItemTitle(rowNumber), 5, 0, width, height, Justification::centredLeft, true);
			else if (columnId == 4)
				g.drawText(base::toStr(getItemYear(rowNumber)), 5, 0, width, height, Justification::centredLeft, true);
			g.setColour(Colours::black.withAlpha(0.2f));
			g.fillRect(width - 1, 0, 1, height);
		}
    };

	// progress dialog
	class progress : public ThreadWithProgressWindow {
		playlistModel &m;
		StringArray files;
		std::function<void()> onFinish;
	public:
		progress(playlistModel &model, const StringArray &_files, std::function<void()> _onFinish)
				: ThreadWithProgressWindow("Importing music", true, true),
				  m(model), files(_files), onFinish(_onFinish) {
			setStatusMessage("Importing music");
		}

		// TODO: system codecs?
		bool isFileSupported(const String &fname) {
			return fname.endsWith(".mp3")
				|| fname.endsWith(".wav")
				|| fname.endsWith(".wma")
				|| fname.endsWith(".flac")
				|| fname.endsWith(".ogg")
				|| fname.endsWith(".ape");
		}

		void run() override {
			setProgress(-1.0);
			int nth = 0;
			for (auto &fileName : files) {
				if (File(fileName).isDirectory()) {
					// recursively scan for files
					DirectoryIterator i(File(fileName), true, "*.mp3;*.wav;*.wma;*.flac;*.ogg;*.ape");
					while (i.next()) {
						m.addItem(i.getFile().getFullPathName());
						if (nth++ == 50) {
							nth = 0;
							setStatusMessage(i.getFile().getFullPathName());
						}
					}
				}
				else {
					// single file
					if (isFileSupported(fileName))
						m.addItem(fileName);
				}
			}
			setStatusMessage("Done.");
		}

		void threadComplete(bool/* userPressedCancel*/) override {
			onFinish();
			delete this;
		}
	};

    TableListBox box;
    playlistModel model;
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(playlist);

public:
    playlist() : box("playlist-box", nullptr) {
		model.init();
		box.setModel(&model);
		box.setMultipleSelectionEnabled(true);
		box.getHeader().setStretchToFitActive(true);
		addAndMakeVisible(box);

		box.getHeader().addColumn("track", 0, 50, 20, 1000, TableHeaderComponent::defaultFlags);
		box.getHeader().addColumn("album", 1, 200, 150, 1000, TableHeaderComponent::defaultFlags);
		box.getHeader().addColumn("artist", 2, 200, 150, 1000, TableHeaderComponent::defaultFlags);
		box.getHeader().addColumn("title", 3, 200, 150, 1000, TableHeaderComponent::defaultFlags);
		box.getHeader().addColumn("year", 4, 70, 50, 1000, TableHeaderComponent::defaultFlags);
		box.setMultipleSelectionEnabled(true);
	}

    void resized() override {
		box.setBounds(getLocalBounds().reduced(0));
	}

	bool isInterestedInFileDrag(const StringArray& /*files*/) override {
		return true;
	}

	void fileDragEnter(const StringArray& /*files*/, int /*x*/, int /*y*/) override {
		repaint();
	}

	void fileDragMove (const StringArray& /*files*/, int /*x*/, int /*y*/) override {}

	void fileDragExit (const StringArray& /*files*/) override {
		repaint();
	}

	void filesDropped (const StringArray& files, int /*x*/, int /*y*/) override {
		(new progress(model, files, [this](){
				box.updateContent();
				repaint();
			}))->launchThread();
	}

	base::string getSelectedRowString() {
		return model.getItemPath(box.getSelectedRow());
	}

	bool store(const base::string &f) {
		base::stream s;
		s.write(model.paths);
		s.write(model.talbum);
		s.write(model.tartist);
		s.write(model.ttitle);
		s.write(model.tyear);
		s.write(model.ttrack);
		s.write(model.paths_i);
		s.write(model.talbum_i);
		s.write(model.tartist_i);
		s.write(model.ttitle_i);
		return base::fs::store(f, s);
	}

	bool load(const base::string &f) {
		base::stream s = base::fs::load(f);
		bool result = s.read(model.paths) > 0
			&& s.read(model.talbum) > 0
			&& s.read(model.tartist) > 0
			&& s.read(model.ttitle) > 0
			&& s.read(model.tyear) > 0
			&& s.read(model.ttrack) > 0
			&& s.read(model.paths_i) > 0
			&& s.read(model.talbum_i) > 0
			&& s.read(model.tartist_i) > 0
			&& s.read(model.ttitle_i) > 0;
		box.updateContent();
		repaint();
		return result;
	}
};
<commit_msg>+ custom tags serialization<commit_after>#pragma once
#include "includes.hpp"

#define TAGLIB_STATIC
#include <taglib/fileref.h>
#include <taglib/tag.h>
#include <taglib/tpropertymap.h>

class playlist : public Component, public FileDragAndDropTarget {
    struct playlistModel : public TableListBoxModel {
		base::string paths, talbum, tartist, ttitle;
		std::vector<uint32> tyear, ttrack;
		std::vector<uint32> paths_i, talbum_i, tartist_i, ttitle_i;

		// persistent user defined tags map
		std::map<base::string, std::vector<base::cell>> tags;

		void init() {
			paths_i.clear(); // this one first (getNumRows)
			paths_i.push_back(0);
			paths.clear();
			talbum.clear();
			tartist.clear();
			tyear.clear();
			ttitle.clear();
			ttrack.clear();
			talbum_i.clear();
			talbum_i.push_back(0);
			tartist_i.clear();
			tartist_i.push_back(0);
			ttitle_i.clear();
			ttitle_i.push_back(0);
		}

		void addItem(const String &path) {
			paths.append(path.toStdString());
			paths_i.push_back(paths.size());

			// read tags from file
			TagLib::FileRef file(path.toRawUTF8());
			if (!file.isNull() && file.tag()) {
				talbum.append(file.tag()->album().toCString());
				tartist.append(file.tag()->artist().toCString());
				ttitle.append(file.tag()->title().toCString());
				tyear.push_back(file.tag()->year());
				ttrack.push_back(file.tag()->track());
			}
			else {
				tyear.push_back(0);
				ttrack.push_back(0);
			}
			talbum_i.push_back(talbum.size());
			tartist_i.push_back(tartist.size());
			ttitle_i.push_back(ttitle.size());
		}

		base::string getItemPath(size_t index) {
			return base::string(paths.begin() + paths_i[index],
								paths.begin() + paths_i[index + 1]);
		}

		uint getItemTrack(size_t index) {
			return ttrack[index];
		}

		uint getItemYear(size_t index) {
			return tyear[index];
		}

		base::string getItemAlbum(size_t index) {
			return base::string(talbum.begin() + talbum_i[index],
								talbum.begin() + talbum_i[index + 1]);
		}

		base::string getItemArtist(size_t index) {
			return base::string(tartist.begin() + tartist_i[index],
								tartist.begin() + tartist_i[index + 1]);
		}

		base::string getItemTitle(size_t index) {
			return base::string(ttitle.begin() + ttitle_i[index],
								ttitle.begin() + ttitle_i[index + 1]);
		}

		// GUI
        int getNumRows() override {
            return paths_i.size() - 1;
        }

		// This is overloaded from TableListBoxModel, and should fill in the background of the whole row
		void paintRowBackground(Graphics& g, int rowNumber, int /*width*/, int /*height*/, bool rowIsSelected) override {
			if (rowIsSelected)
				g.fillAll(Colours::lightblue);
			else if (rowNumber % 2)
				g.fillAll(Colour(0xffeeeeee));
		}

		// This is overloaded from TableListBoxModel, and must paint any cells that aren't using custom
		// components.
		void paintCell(Graphics& g, int rowNumber, int columnId,
						int width, int height, bool /*rowIsSelected*/) override {
			g.setColour(Colours::black);
			g.setFont(height * 0.7f);
			if (columnId == 0)
				g.drawText(base::toStr(getItemTrack(rowNumber)), 5, 0, width, height, Justification::centredLeft, true);
			else if (columnId == 1)
				g.drawText(getItemAlbum(rowNumber), 5, 0, width, height, Justification::centredLeft, true);
			else if (columnId == 2)
				g.drawText(getItemArtist(rowNumber), 5, 0, width, height, Justification::centredLeft, true);
			else if (columnId == 3)
				g.drawText(getItemTitle(rowNumber), 5, 0, width, height, Justification::centredLeft, true);
			else if (columnId == 4)
				g.drawText(base::toStr(getItemYear(rowNumber)), 5, 0, width, height, Justification::centredLeft, true);
			g.setColour(Colours::black.withAlpha(0.2f));
			g.fillRect(width - 1, 0, 1, height);
		}
    };

	// progress dialog
	class progress : public ThreadWithProgressWindow {
		playlistModel &m;
		StringArray files;
		std::function<void()> onFinish;
	public:
		progress(playlistModel &model, const StringArray &_files, std::function<void()> _onFinish)
				: ThreadWithProgressWindow("Importing music", true, true),
				  m(model), files(_files), onFinish(_onFinish) {
			setStatusMessage("Importing music");
		}

		// TODO: system codecs?
		bool isFileSupported(const String &fname) {
			return fname.endsWith(".mp3")
				|| fname.endsWith(".wav")
				|| fname.endsWith(".wma")
				|| fname.endsWith(".flac")
				|| fname.endsWith(".ogg")
				|| fname.endsWith(".ape");
		}

		void run() override {
			setProgress(-1.0);
			int nth = 0;
			for (auto &fileName : files) {
				if (File(fileName).isDirectory()) {
					// recursively scan for files
					DirectoryIterator i(File(fileName), true, "*.mp3;*.wav;*.wma;*.flac;*.ogg;*.ape");
					while (i.next()) {
						m.addItem(i.getFile().getFullPathName());
						if (nth++ == 50) {
							nth = 0;
							setStatusMessage(i.getFile().getFullPathName());
						}
					}
				}
				else {
					// single file
					if (isFileSupported(fileName))
						m.addItem(fileName);
				}
			}
			setStatusMessage("Done.");
		}

		void threadComplete(bool/* userPressedCancel*/) override {
			onFinish();
			delete this;
		}
	};

    TableListBox box;
    playlistModel model;
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(playlist);

public:
    playlist() : box("playlist-box", nullptr) {
		model.init();
		box.setModel(&model);
		box.setMultipleSelectionEnabled(true);
		box.getHeader().setStretchToFitActive(true);
		addAndMakeVisible(box);

		box.getHeader().addColumn("track", 0, 50, 20, 1000, TableHeaderComponent::defaultFlags);
		box.getHeader().addColumn("album", 1, 200, 150, 1000, TableHeaderComponent::defaultFlags);
		box.getHeader().addColumn("artist", 2, 200, 150, 1000, TableHeaderComponent::defaultFlags);
		box.getHeader().addColumn("title", 3, 200, 150, 1000, TableHeaderComponent::defaultFlags);
		box.getHeader().addColumn("year", 4, 70, 50, 1000, TableHeaderComponent::defaultFlags);
		box.setMultipleSelectionEnabled(true);
	}

    void resized() override {
		box.setBounds(getLocalBounds().reduced(0));
	}

	bool isInterestedInFileDrag(const StringArray& /*files*/) override {
		return true;
	}

	void fileDragEnter(const StringArray& /*files*/, int /*x*/, int /*y*/) override {
		repaint();
	}

	void fileDragMove (const StringArray& /*files*/, int /*x*/, int /*y*/) override {}

	void fileDragExit (const StringArray& /*files*/) override {
		repaint();
	}

	void filesDropped (const StringArray& files, int /*x*/, int /*y*/) override {
		(new progress(model, files, [this](){
				box.updateContent();
				repaint();
			}))->launchThread();
	}

	base::string getSelectedRowString() {
		return model.getItemPath(box.getSelectedRow());
	}

	bool store(const base::string &f) {
		base::stream s;
		s.write(model.paths);
		s.write(model.talbum);
		s.write(model.tartist);
		s.write(model.ttitle);
		s.write(model.tyear);
		s.write(model.ttrack);
		s.write(model.paths_i);
		s.write(model.talbum_i);
		s.write(model.tartist_i);
		s.write(model.ttitle_i);
		return base::fs::store(f, s);
	}

	bool load(const base::string &f) {
		base::stream s = base::fs::load(f);
		bool result = s.read(model.paths) > 0
			&& s.read(model.talbum) > 0
			&& s.read(model.tartist) > 0
			&& s.read(model.ttitle) > 0
			&& s.read(model.tyear) > 0
			&& s.read(model.ttrack) > 0
			&& s.read(model.paths_i) > 0
			&& s.read(model.talbum_i) > 0
			&& s.read(model.tartist_i) > 0
			&& s.read(model.ttitle_i) > 0;
		box.updateContent();
		repaint();
		return result;
	}

	bool storeTags(const base::string &f) {
		base::stream s;
		s.write((uint32)model.tags.size());
		for (const auto &e : model.tags) {
			s.write(e.first);
			// TODO: write returns / estimate size / write documentation on writing vectors?
			s.write(sizeof(uint32) + sizeof(base::cell) * e.second.size());
			s.write(e.second);
		}
		return base::fs::store(f, s);
	}

	bool loadTags(const base::string &f) {
		model.tags.clear();
		base::stream s = base::fs::load(f);
		uint32 mapSize;
		if (s.read(mapSize) == 0)
			return false;
		for (uint32 i = 0; i < mapSize; ++i) {
			base::string key;
			if (s.read(key) == 0)
				return false;
			auto &c = model.tags[key];
			uint32 cellsSize;
			if (s.read(cellsSize) == 0)
				return false;
			if (s.read(c) != cellsSize)
				return false;
		}
		return true;
	}
};
<|endoftext|>