<commit_before>/****************************************************************************
**
** Copyright (C) 2014 Jolla Ltd.
** Contact: Raine Makelainen <raine.makelainen@jollamobile.com>
**
****************************************************************************/

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "declarativewebpage.h"
#include "declarativewebcontainer.h"
#include "dbmanager.h"
#include "browserpaths.h"

#include <qmozwindow.h>
#include <QGuiApplication>
#include <QtConcurrent>

static const QString gFullScreenMessage("embed:fullscreenchanged");
static const QString gDomContentLoadedMessage("chrome:contentloaded");

static const QString gContentOrientationChanged("embed:contentOrientationChanged");
static const QString gLinkAddedMessage("chrome:linkadded");
static const QString gAlertMessage("embed:alert");
static const QString gConfirmMessage("embed:confirm");
static const QString gPromptMessage("embed:prompt");
static const QString gAuthMessage("embed:auth");
static const QString gLoginMessage("embed:login");
static const QString gFindMessage("embed:find");
static const QString gPermissionsMessage("embed:permissions");
static const QString gContextMenuMessage("Content:ContextMenu");
static const QString gSelectionRangeMessage("Content:SelectionRange");
static const QString gSelectionCopiedMessage("Content:SelectionCopied");
static const QString gSelectAsyncMessage("embed:selectasync");
static const QString gFilePickerMessage("embed:filepicker");

bool isBlack(QRgb rgb)
{
    return qRed(rgb) == 0 && qGreen(rgb) == 0 && qBlue(rgb) == 0;
}

bool allBlack(const QImage &image)
{
    int h = image.height();
    int w = image.width();

    for (int j = 0; j < h; ++j) {
        const QRgb *b = (const QRgb *)image.constScanLine(j);
        for (int i = 0; i < w; ++i) {
            if (!isBlack(b[i]))
                return false;
        }
    }
    return true;
}

DeclarativeWebPage::DeclarativeWebPage(QObject *parent)
    : QOpenGLWebPage(parent)
    , m_container(0)
    , m_userHasDraggedWhileLoading(false)
    , m_fullscreen(false)
    , m_forcedChrome(false)
    , m_domContentLoaded(false)
    , m_initialLoadHasHappened(false)
    , m_tabHistoryReady(false)
    , m_urlReady(false)
    , m_restoredCurrentLinkId(-1)
    , m_fullScreenHeight(0.f)
    , m_toolbarHeight(0.f)
    , m_virtualKeyboardMargin(0.f)
{
    addMessageListener(gFullScreenMessage);
    addMessageListener(gDomContentLoadedMessage);

    addMessageListener(gLinkAddedMessage);
    addMessageListener(gAlertMessage);
    addMessageListener(gConfirmMessage);
    addMessageListener(gPromptMessage);
    addMessageListener(gAuthMessage);
    addMessageListener(gLoginMessage);
    addMessageListener(gFindMessage);
    addMessageListener(gPermissionsMessage);
    addMessageListener(gContextMenuMessage);
    addMessageListener(gSelectionRangeMessage);
    addMessageListener(gSelectionCopiedMessage);
    addMessageListener(gSelectAsyncMessage);
    addMessageListener(gFilePickerMessage);
    addMessageListener(gContentOrientationChanged);

    loadFrameScript("chrome://embedlite/content/SelectAsyncHelper.js");
    loadFrameScript("chrome://embedlite/content/embedhelper.js");

    connect(this, SIGNAL(recvAsyncMessage(const QString, const QVariant)),
            this, SLOT(onRecvAsyncMessage(const QString&, const QVariant&)));
    connect(&m_grabWritter, SIGNAL(finished()), this, SLOT(grabWritten()));
    connect(this, SIGNAL(urlChanged()), this, SLOT(onUrlChanged()));
    connect(this, &QOpenGLWebPage::contentHeightChanged, this, &DeclarativeWebPage::updateViewMargins);
    connect(this, &QOpenGLWebPage::loadedChanged, [this]() {
        if (loaded()) {
            updateViewMargins();
            // E.g. when loading images directly we don't necessarily get domContentLoaded message from engine.
            // So mark content loaded when webpage is loaded.
            setContentLoaded();
        }
    });
    connect(this, SIGNAL(fullscreenHeightChanged()), this, SLOT(updateViewMargins()));
}

DeclarativeWebPage::~DeclarativeWebPage()
{
    m_grabWritter.cancel();
    m_grabWritter.waitForFinished();
    m_grabResult.clear();
    m_thumbnailResult.clear();
}

DeclarativeWebContainer *DeclarativeWebPage::container() const
{
    return m_container;
}

void DeclarativeWebPage::setContainer(DeclarativeWebContainer *container)
{
    if (m_container != container) {
        m_container = container;
        if (m_container) {
            connect(m_container, SIGNAL(portraitChanged()), this, SLOT(sendVkbOpenCompositionMetrics()));
        }
        Q_ASSERT(container->mozWindow());
        setMozWindow(container->mozWindow());
        emit containerChanged();
    }
}

int DeclarativeWebPage::tabId() const
{
    return m_initialTab.tabId();
}

void DeclarativeWebPage::setInitialTab(const Tab& tab)
{
    Q_ASSERT(m_initialTab.tabId() == 0);

    m_initialTab = tab;
    emit tabIdChanged();
    connect(DBManager::instance(), SIGNAL(tabHistoryAvailable(int, QList<Link>,int)),
            this, SLOT(onTabHistoryAvailable(int, QList<Link>,int)));
    DBManager::instance()->getTabHistory(tabId());
}

void DeclarativeWebPage::onUrlChanged()
{
    disconnect(this, SIGNAL(urlChanged()), this, SLOT(onUrlChanged()));
    m_urlReady = true;
    restoreHistory();
}

void DeclarativeWebPage::onTabHistoryAvailable(const int& historyTabId, const QList<Link>& links, int currentLinkId)
{
    if (historyTabId == tabId()) {
        m_restoredTabHistory = links;
        m_restoredCurrentLinkId = currentLinkId; // FIXME: consider storing isCurrent flag in Link struct instead to reduce DeclarativeWebPage's state

        std::reverse(m_restoredTabHistory.begin(), m_restoredTabHistory.end());
        DBManager::instance()->disconnect(this);
        m_tabHistoryReady = true;
        restoreHistory();
    }
}

void DeclarativeWebPage::restoreHistory() {
    if (!m_urlReady || !m_tabHistoryReady || m_restoredTabHistory.count() == 0) {
        return;
    }

    QList<QString> urls;
    int index(-1);
    int i(0);
    foreach (Link link, m_restoredTabHistory) {
        urls << link.url();
        if (link.linkId() == m_restoredCurrentLinkId) {
            index = i;
            QString currentUrl(url().toString());
            if (link.url() != currentUrl) {
                // The browser was started with an initial URL as a cmdline parameter -> reset tab history
                urls << currentUrl;
                index++;
                DBManager::instance()->navigateTo(tabId(), currentUrl, "", "");
                break;
            }
        }
        i++;
    }

    if (index < 0) {
        urls << url().toString();
        index = urls.count() - 1;
    }

    QVariantMap data;
    data.insert(QString("links"), QVariant(urls));
    data.insert(QString("index"), QVariant(index));
    sendAsyncMessage("embedui:addhistory", QVariant(data));
}

void DeclarativeWebPage::setContentLoaded()
{
    if (!m_domContentLoaded) {
        m_domContentLoaded = true;
        emit domContentLoadedChanged();
    }
}

bool DeclarativeWebPage::domContentLoaded() const
{
    return m_domContentLoaded;
}

bool DeclarativeWebPage::initialLoadHasHappened() const
{
    return m_initialLoadHasHappened;
}

void DeclarativeWebPage::setInitialLoadHasHappened()
{
    m_initialLoadHasHappened = true;
}

QVariant DeclarativeWebPage::resurrectedContentRect() const
{
    return m_resurrectedContentRect;
}

void DeclarativeWebPage::setResurrectedContentRect(QVariant resurrectedContentRect)
{
    if (m_resurrectedContentRect != resurrectedContentRect) {
        m_resurrectedContentRect = resurrectedContentRect;
        emit resurrectedContentRectChanged();
    }
}

qreal DeclarativeWebPage::toolbarHeight() const
{
    return m_toolbarHeight;
}

void DeclarativeWebPage::setToolbarHeight(qreal toolbarHeight)
{
    if (toolbarHeight != m_toolbarHeight) {
        m_toolbarHeight = toolbarHeight;
        emit toolbarHeightChanged();
    }
}

qreal DeclarativeWebPage::virtualKeyboardMargin() const
{
    return m_virtualKeyboardMargin;
}

void DeclarativeWebPage::setVirtualKeyboardMargin(qreal margin)
{
    if (margin != m_virtualKeyboardMargin) {
        m_virtualKeyboardMargin = margin;
        QMargins margins;
        margins.setBottom(m_virtualKeyboardMargin);
        setMargins(margins);
        sendVkbOpenCompositionMetrics();
        emit virtualKeyboardMarginChanged();
    }
}

void DeclarativeWebPage::loadTab(QString newUrl, bool force)
{
    // Always enable chrome when load is called.
    setChrome(true);
    QString oldUrl = url().toString();
    if ((!newUrl.isEmpty() && oldUrl != newUrl) || force) {
        m_domContentLoaded = false;
        emit domContentLoadedChanged();
        load(newUrl);
    }
}

void DeclarativeWebPage::grabToFile(const QSize &size)
{
    // grabToImage handles invalid geometry.
    m_grabResult = grabToImage(size);
    if (m_grabResult) {
        if (!m_grabResult->isReady()) {
            connect(m_grabResult.data(), SIGNAL(ready()), this, SLOT(grabResultReady()));
        } else {
            grabResultReady();
        }
    }
}

void DeclarativeWebPage::grabThumbnail(const QSize &size)
{
    m_thumbnailResult = grabToImage(size);
    if (m_thumbnailResult) {
        connect(m_thumbnailResult.data(), SIGNAL(ready()), this, SLOT(thumbnailReady()));
    }
}

/**
 * Use this to lock to chrome mode. This disables the gesture
 * that normally enables fullscreen mode. The chromeGestureEnabled property
 * is bound to this so that contentHeight changes do not re-enable the
 * gesture.
 *
 * When gesture is allowed to be used again, unlock call by forceChrome(false).
 *
 * Used for instance when find-in-page view is active that is part of
 * the new browser user interface.
 */
void DeclarativeWebPage::forceChrome(bool forcedChrome)
{
    // This way we don't break chromeGestureEnabled and chrome bindings.
    setChromeGestureEnabled(!forcedChrome);
    if (forcedChrome) {
        setChrome(forcedChrome);
    }
    if (m_forcedChrome != forcedChrome) {
        m_forcedChrome = forcedChrome;
        emit forcedChromeChanged();
    }
}

void DeclarativeWebPage::grabResultReady()
{
    QImage image = m_grabResult->image();
    m_grabResult.clear();
    m_grabWritter.setFuture(QtConcurrent::run(this, &DeclarativeWebPage::saveToFile, image));
}

void DeclarativeWebPage::grabWritten()
{
    QString path = m_grabWritter.result();
    emit grabResult(path);
}

void DeclarativeWebPage::thumbnailReady()
{
    QImage image = m_thumbnailResult->image();
    m_thumbnailResult.clear();
    QByteArray iconData;
    QBuffer buffer(&iconData);
    buffer.open(QIODevice::WriteOnly);
    if (image.save(&buffer, "jpg", 75)) {
        buffer.close();
        emit thumbnailResult(QString(BASE64_IMAGE).arg(QString(iconData.toBase64())));
    } else {
        emit thumbnailResult(DEFAULT_DESKTOP_BOOKMARK_ICON);
    }
}

void DeclarativeWebPage::updateViewMargins()
{
    // Don't update margins while panning, flicking, or pinching.
    if (moving() || m_virtualKeyboardMargin > 0) {
        return;
    }

    qreal threshold = qMax(m_fullScreenHeight * 1.5f, (m_fullScreenHeight + (m_toolbarHeight*2)));
    QMargins margins;
    if (!m_fullscreen && (contentHeight() < threshold)) {
        margins.setBottom(m_toolbarHeight);
    }

    setMargins(margins);
}

void DeclarativeWebPage::sendVkbOpenCompositionMetrics()
{
    // Send update only if the page is active.
    if (!active()) {
        return;
    }

    int winHeight(0);
    int winWidth(0);

    // Listen im state so that we don't send also updates when
    // vkb state changes on the chrome side.

    if (m_container) {
      if (m_container->portrait()) {
        winHeight = m_container->height();
        winWidth = m_container->width();
      } else {
        winHeight = m_container->width();
        winWidth = m_container->height();
      }
    }

    QVariantMap map;
    map.insert("imOpen", m_virtualKeyboardMargin > 0);
    map.insert("pixelRatio", QMozContext::GetInstance()->pixelRatio());
    map.insert("bottomMargin", m_virtualKeyboardMargin);
    map.insert("screenWidth", winWidth);
    map.insert("screenHeight", winHeight);

    QVariant data(map);
    sendAsyncMessage("embedui:vkbOpenCompositionMetrics", data);
}

QString DeclarativeWebPage::saveToFile(QImage image)
{
    if (image.isNull()) {
        return "";
    }

    // 75% quality jpg produces small and good enough capture.
    QString path = QString("%1/tab-%2-thumb.jpg").arg(BrowserPaths::cacheLocation()).arg(tabId());
    return !allBlack(image) && image.save(path, "jpg", 75) ? path : "";
}

void DeclarativeWebPage::onRecvAsyncMessage(const QString& message, const QVariant& data)
{
    if (message == gFullScreenMessage) {
        setFullscreen(data.toMap().value(QString("fullscreen")).toBool());
    } else if (message == gDomContentLoadedMessage) {
        setContentLoaded();
    }
    else if (message == gContentOrientationChanged) {
        QString orientation = data.toMap().value("orientation").toString();
        Qt::ScreenOrientation mappedOrientation = Qt::PortraitOrientation;
        if (orientation == QStringLiteral("landscape-primary")) {
            mappedOrientation = Qt::LandscapeOrientation;
        } else if (orientation == QStringLiteral("landscape-secondary")) {
            mappedOrientation = Qt::InvertedLandscapeOrientation;
        } else if (orientation == QStringLiteral("portrait-secondary")) {
            mappedOrientation = Qt::InvertedPortraitOrientation;
        }
        contentOrientationChanged(mappedOrientation);
    }
}

bool DeclarativeWebPage::fullscreen() const
{
    return m_fullscreen;
}

bool DeclarativeWebPage::forcedChrome() const
{
    return m_forcedChrome;
}

void DeclarativeWebPage::setFullscreen(const bool fullscreen)
{
    if (m_fullscreen != fullscreen) {
        m_fullscreen = fullscreen;
        updateViewMargins();
        emit fullscreenChanged();
    }
}

QDebug operator<<(QDebug dbg, const DeclarativeWebPage *page)
{
    if (!page) {
        return dbg << "DeclarativeWebPage (this = 0x0)";
    }

    QSize size = page->mozWindow()->size();
    dbg.nospace() << "DeclarativeWebPage(tabId = " << page->tabId() << " url = " << page->url() << ", title = " << page->title() << ", width = " << size.width()
                  << ", height = " << size.height() << ", completed = " << page->completed()
                  << ", active = " << page->active() << ", enabled = " << page->enabled() << ")";
    return dbg.space();
}
<commit_msg>[sailfish-browser] Reset margins always when fullscreen mode is enabled. Fixes JB#36164<commit_after>/****************************************************************************
**
** Copyright (C) 2014 Jolla Ltd.
** Contact: Raine Makelainen <raine.makelainen@jollamobile.com>
**
****************************************************************************/

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "declarativewebpage.h"
#include "declarativewebcontainer.h"
#include "dbmanager.h"
#include "browserpaths.h"

#include <qmozwindow.h>
#include <QGuiApplication>
#include <QtConcurrent>

static const QString gFullScreenMessage("embed:fullscreenchanged");
static const QString gDomContentLoadedMessage("chrome:contentloaded");

static const QString gContentOrientationChanged("embed:contentOrientationChanged");
static const QString gLinkAddedMessage("chrome:linkadded");
static const QString gAlertMessage("embed:alert");
static const QString gConfirmMessage("embed:confirm");
static const QString gPromptMessage("embed:prompt");
static const QString gAuthMessage("embed:auth");
static const QString gLoginMessage("embed:login");
static const QString gFindMessage("embed:find");
static const QString gPermissionsMessage("embed:permissions");
static const QString gContextMenuMessage("Content:ContextMenu");
static const QString gSelectionRangeMessage("Content:SelectionRange");
static const QString gSelectionCopiedMessage("Content:SelectionCopied");
static const QString gSelectAsyncMessage("embed:selectasync");
static const QString gFilePickerMessage("embed:filepicker");

bool isBlack(QRgb rgb)
{
    return qRed(rgb) == 0 && qGreen(rgb) == 0 && qBlue(rgb) == 0;
}

bool allBlack(const QImage &image)
{
    int h = image.height();
    int w = image.width();

    for (int j = 0; j < h; ++j) {
        const QRgb *b = (const QRgb *)image.constScanLine(j);
        for (int i = 0; i < w; ++i) {
            if (!isBlack(b[i]))
                return false;
        }
    }
    return true;
}

DeclarativeWebPage::DeclarativeWebPage(QObject *parent)
    : QOpenGLWebPage(parent)
    , m_container(0)
    , m_userHasDraggedWhileLoading(false)
    , m_fullscreen(false)
    , m_forcedChrome(false)
    , m_domContentLoaded(false)
    , m_initialLoadHasHappened(false)
    , m_tabHistoryReady(false)
    , m_urlReady(false)
    , m_restoredCurrentLinkId(-1)
    , m_fullScreenHeight(0.f)
    , m_toolbarHeight(0.f)
    , m_virtualKeyboardMargin(0.f)
{
    addMessageListener(gFullScreenMessage);
    addMessageListener(gDomContentLoadedMessage);

    addMessageListener(gLinkAddedMessage);
    addMessageListener(gAlertMessage);
    addMessageListener(gConfirmMessage);
    addMessageListener(gPromptMessage);
    addMessageListener(gAuthMessage);
    addMessageListener(gLoginMessage);
    addMessageListener(gFindMessage);
    addMessageListener(gPermissionsMessage);
    addMessageListener(gContextMenuMessage);
    addMessageListener(gSelectionRangeMessage);
    addMessageListener(gSelectionCopiedMessage);
    addMessageListener(gSelectAsyncMessage);
    addMessageListener(gFilePickerMessage);
    addMessageListener(gContentOrientationChanged);

    loadFrameScript("chrome://embedlite/content/SelectAsyncHelper.js");
    loadFrameScript("chrome://embedlite/content/embedhelper.js");

    connect(this, SIGNAL(recvAsyncMessage(const QString, const QVariant)),
            this, SLOT(onRecvAsyncMessage(const QString&, const QVariant&)));
    connect(&m_grabWritter, SIGNAL(finished()), this, SLOT(grabWritten()));
    connect(this, SIGNAL(urlChanged()), this, SLOT(onUrlChanged()));
    connect(this, &QOpenGLWebPage::contentHeightChanged, this, &DeclarativeWebPage::updateViewMargins);
    connect(this, &QOpenGLWebPage::loadedChanged, [this]() {
        if (loaded()) {
            updateViewMargins();
            // E.g. when loading images directly we don't necessarily get domContentLoaded message from engine.
            // So mark content loaded when webpage is loaded.
            setContentLoaded();
        }
    });
    connect(this, SIGNAL(fullscreenHeightChanged()), this, SLOT(updateViewMargins()));
}

DeclarativeWebPage::~DeclarativeWebPage()
{
    m_grabWritter.cancel();
    m_grabWritter.waitForFinished();
    m_grabResult.clear();
    m_thumbnailResult.clear();
}

DeclarativeWebContainer *DeclarativeWebPage::container() const
{
    return m_container;
}

void DeclarativeWebPage::setContainer(DeclarativeWebContainer *container)
{
    if (m_container != container) {
        m_container = container;
        if (m_container) {
            connect(m_container, SIGNAL(portraitChanged()), this, SLOT(sendVkbOpenCompositionMetrics()));
        }
        Q_ASSERT(container->mozWindow());
        setMozWindow(container->mozWindow());
        emit containerChanged();
    }
}

int DeclarativeWebPage::tabId() const
{
    return m_initialTab.tabId();
}

void DeclarativeWebPage::setInitialTab(const Tab& tab)
{
    Q_ASSERT(m_initialTab.tabId() == 0);

    m_initialTab = tab;
    emit tabIdChanged();
    connect(DBManager::instance(), SIGNAL(tabHistoryAvailable(int, QList<Link>,int)),
            this, SLOT(onTabHistoryAvailable(int, QList<Link>,int)));
    DBManager::instance()->getTabHistory(tabId());
}

void DeclarativeWebPage::onUrlChanged()
{
    disconnect(this, SIGNAL(urlChanged()), this, SLOT(onUrlChanged()));
    m_urlReady = true;
    restoreHistory();
}

void DeclarativeWebPage::onTabHistoryAvailable(const int& historyTabId, const QList<Link>& links, int currentLinkId)
{
    if (historyTabId == tabId()) {
        m_restoredTabHistory = links;
        m_restoredCurrentLinkId = currentLinkId; // FIXME: consider storing isCurrent flag in Link struct instead to reduce DeclarativeWebPage's state

        std::reverse(m_restoredTabHistory.begin(), m_restoredTabHistory.end());
        DBManager::instance()->disconnect(this);
        m_tabHistoryReady = true;
        restoreHistory();
    }
}

void DeclarativeWebPage::restoreHistory() {
    if (!m_urlReady || !m_tabHistoryReady || m_restoredTabHistory.count() == 0) {
        return;
    }

    QList<QString> urls;
    int index(-1);
    int i(0);
    foreach (Link link, m_restoredTabHistory) {
        urls << link.url();
        if (link.linkId() == m_restoredCurrentLinkId) {
            index = i;
            QString currentUrl(url().toString());
            if (link.url() != currentUrl) {
                // The browser was started with an initial URL as a cmdline parameter -> reset tab history
                urls << currentUrl;
                index++;
                DBManager::instance()->navigateTo(tabId(), currentUrl, "", "");
                break;
            }
        }
        i++;
    }

    if (index < 0) {
        urls << url().toString();
        index = urls.count() - 1;
    }

    QVariantMap data;
    data.insert(QString("links"), QVariant(urls));
    data.insert(QString("index"), QVariant(index));
    sendAsyncMessage("embedui:addhistory", QVariant(data));
}

void DeclarativeWebPage::setContentLoaded()
{
    if (!m_domContentLoaded) {
        m_domContentLoaded = true;
        emit domContentLoadedChanged();
    }
}

bool DeclarativeWebPage::domContentLoaded() const
{
    return m_domContentLoaded;
}

bool DeclarativeWebPage::initialLoadHasHappened() const
{
    return m_initialLoadHasHappened;
}

void DeclarativeWebPage::setInitialLoadHasHappened()
{
    m_initialLoadHasHappened = true;
}

QVariant DeclarativeWebPage::resurrectedContentRect() const
{
    return m_resurrectedContentRect;
}

void DeclarativeWebPage::setResurrectedContentRect(QVariant resurrectedContentRect)
{
    if (m_resurrectedContentRect != resurrectedContentRect) {
        m_resurrectedContentRect = resurrectedContentRect;
        emit resurrectedContentRectChanged();
    }
}

qreal DeclarativeWebPage::toolbarHeight() const
{
    return m_toolbarHeight;
}

void DeclarativeWebPage::setToolbarHeight(qreal toolbarHeight)
{
    if (toolbarHeight != m_toolbarHeight) {
        m_toolbarHeight = toolbarHeight;
        emit toolbarHeightChanged();
    }
}

qreal DeclarativeWebPage::virtualKeyboardMargin() const
{
    return m_virtualKeyboardMargin;
}

void DeclarativeWebPage::setVirtualKeyboardMargin(qreal margin)
{
    if (margin != m_virtualKeyboardMargin) {
        m_virtualKeyboardMargin = margin;
        QMargins margins;
        margins.setBottom(m_virtualKeyboardMargin);
        setMargins(margins);
        sendVkbOpenCompositionMetrics();
        emit virtualKeyboardMarginChanged();
    }
}

void DeclarativeWebPage::loadTab(QString newUrl, bool force)
{
    // Always enable chrome when load is called.
    setChrome(true);
    QString oldUrl = url().toString();
    if ((!newUrl.isEmpty() && oldUrl != newUrl) || force) {
        m_domContentLoaded = false;
        emit domContentLoadedChanged();
        load(newUrl);
    }
}

void DeclarativeWebPage::grabToFile(const QSize &size)
{
    // grabToImage handles invalid geometry.
    m_grabResult = grabToImage(size);
    if (m_grabResult) {
        if (!m_grabResult->isReady()) {
            connect(m_grabResult.data(), SIGNAL(ready()), this, SLOT(grabResultReady()));
        } else {
            grabResultReady();
        }
    }
}

void DeclarativeWebPage::grabThumbnail(const QSize &size)
{
    m_thumbnailResult = grabToImage(size);
    if (m_thumbnailResult) {
        connect(m_thumbnailResult.data(), SIGNAL(ready()), this, SLOT(thumbnailReady()));
    }
}

/**
 * Use this to lock to chrome mode. This disables the gesture
 * that normally enables fullscreen mode. The chromeGestureEnabled property
 * is bound to this so that contentHeight changes do not re-enable the
 * gesture.
 *
 * When gesture is allowed to be used again, unlock call by forceChrome(false).
 *
 * Used for instance when find-in-page view is active that is part of
 * the new browser user interface.
 */
void DeclarativeWebPage::forceChrome(bool forcedChrome)
{
    // This way we don't break chromeGestureEnabled and chrome bindings.
    setChromeGestureEnabled(!forcedChrome);
    if (forcedChrome) {
        setChrome(forcedChrome);
    }
    if (m_forcedChrome != forcedChrome) {
        m_forcedChrome = forcedChrome;
        emit forcedChromeChanged();
    }
}

void DeclarativeWebPage::grabResultReady()
{
    QImage image = m_grabResult->image();
    m_grabResult.clear();
    m_grabWritter.setFuture(QtConcurrent::run(this, &DeclarativeWebPage::saveToFile, image));
}

void DeclarativeWebPage::grabWritten()
{
    QString path = m_grabWritter.result();
    emit grabResult(path);
}

void DeclarativeWebPage::thumbnailReady()
{
    QImage image = m_thumbnailResult->image();
    m_thumbnailResult.clear();
    QByteArray iconData;
    QBuffer buffer(&iconData);
    buffer.open(QIODevice::WriteOnly);
    if (image.save(&buffer, "jpg", 75)) {
        buffer.close();
        emit thumbnailResult(QString(BASE64_IMAGE).arg(QString(iconData.toBase64())));
    } else {
        emit thumbnailResult(DEFAULT_DESKTOP_BOOKMARK_ICON);
    }
}

void DeclarativeWebPage::updateViewMargins()
{
    // Reset margins always when fullscreen mode is enabled.
    QMargins margins;
    if (!m_fullscreen) {
        // Don't update margins while panning, flicking, or pinching.
        if (moving() || m_virtualKeyboardMargin > 0) {
            return;
        }

        qreal threshold = qMax(m_fullScreenHeight * 1.5f, (m_fullScreenHeight + (m_toolbarHeight*2)));
        if (contentHeight() < threshold) {
            margins.setBottom(m_toolbarHeight);
        }
    }

    setMargins(margins);
}

void DeclarativeWebPage::sendVkbOpenCompositionMetrics()
{
    // Send update only if the page is active.
    if (!active()) {
        return;
    }

    int winHeight(0);
    int winWidth(0);

    // Listen im state so that we don't send also updates when
    // vkb state changes on the chrome side.

    if (m_container) {
      if (m_container->portrait()) {
        winHeight = m_container->height();
        winWidth = m_container->width();
      } else {
        winHeight = m_container->width();
        winWidth = m_container->height();
      }
    }

    QVariantMap map;
    map.insert("imOpen", m_virtualKeyboardMargin > 0);
    map.insert("pixelRatio", QMozContext::GetInstance()->pixelRatio());
    map.insert("bottomMargin", m_virtualKeyboardMargin);
    map.insert("screenWidth", winWidth);
    map.insert("screenHeight", winHeight);

    QVariant data(map);
    sendAsyncMessage("embedui:vkbOpenCompositionMetrics", data);
}

QString DeclarativeWebPage::saveToFile(QImage image)
{
    if (image.isNull()) {
        return "";
    }

    // 75% quality jpg produces small and good enough capture.
    QString path = QString("%1/tab-%2-thumb.jpg").arg(BrowserPaths::cacheLocation()).arg(tabId());
    return !allBlack(image) && image.save(path, "jpg", 75) ? path : "";
}

void DeclarativeWebPage::onRecvAsyncMessage(const QString& message, const QVariant& data)
{
    if (message == gFullScreenMessage) {
        setFullscreen(data.toMap().value(QString("fullscreen")).toBool());
    } else if (message == gDomContentLoadedMessage) {
        setContentLoaded();
    }
    else if (message == gContentOrientationChanged) {
        QString orientation = data.toMap().value("orientation").toString();
        Qt::ScreenOrientation mappedOrientation = Qt::PortraitOrientation;
        if (orientation == QStringLiteral("landscape-primary")) {
            mappedOrientation = Qt::LandscapeOrientation;
        } else if (orientation == QStringLiteral("landscape-secondary")) {
            mappedOrientation = Qt::InvertedLandscapeOrientation;
        } else if (orientation == QStringLiteral("portrait-secondary")) {
            mappedOrientation = Qt::InvertedPortraitOrientation;
        }
        contentOrientationChanged(mappedOrientation);
    }
}

bool DeclarativeWebPage::fullscreen() const
{
    return m_fullscreen;
}

bool DeclarativeWebPage::forcedChrome() const
{
    return m_forcedChrome;
}

void DeclarativeWebPage::setFullscreen(const bool fullscreen)
{
    if (m_fullscreen != fullscreen) {
        m_fullscreen = fullscreen;
        updateViewMargins();
        emit fullscreenChanged();
    }
}

QDebug operator<<(QDebug dbg, const DeclarativeWebPage *page)
{
    if (!page) {
        return dbg << "DeclarativeWebPage (this = 0x0)";
    }

    QSize size = page->mozWindow()->size();
    dbg.nospace() << "DeclarativeWebPage(tabId = " << page->tabId() << " url = " << page->url() << ", title = " << page->title() << ", width = " << size.width()
                  << ", height = " << size.height() << ", completed = " << page->completed()
                  << ", active = " << page->active() << ", enabled = " << page->enabled() << ")";
    return dbg.space();
}
<|endoftext|>


<commit_before>/******************************************************************************\
* LuaVariable.hpp                                                              *
* A variable living in a Lua interpreter.                                      *
* Leandro Motta Barros                                                         *
\******************************************************************************/

#ifndef _DILUCULUM_LUA_VARIABLE_HPP_
#define _DILUCULUM_LUA_VARIABLE_HPP_

#include <vector>
#include "LuaValue.hpp"

namespace Diluculum
{
   /** A variable living in a Lua state. Notice the crucial difference: unlike a
    *  \c LuaValue, a \c LuaVariable necessarily has a real counterpart in a Lua
    *  state. Thus, when something is assigned to a \c LuaVariable, the value of
    *  the corresponding variable in the Lua state is changed, too.
    *
<tt>LuaVariable</tt>s cannot be directly constructed. They are
    *  designed to be returned by <tt>LuaState</tt>'s subscript operator.
    *  @todo Add tests for expected exceptions.
    */
   class LuaVariable
   {
      friend class LuaState;

      public:
         /** Assigns a new value to this \c LuaVariable. The corresponding
          *  variable in the Lua state is updated accordingly.
          *  @param rhs The new value for the variable.
          *  @return \c rhs, so that a sequence of assignments, like
          *          <tt>a = b = c = 1;</tt> works. Actually, returning
          *          \c rhs is the same as returning <tt>this->value()</tt>,
          *          that perhaps make more sense.
          */
         const LuaValue& operator= (const LuaValue& rhs);

         /** Returns the value associated with this variable.
          *  @todo Throw a more specific exception (instead \c LuaError), and a
          *        more adequate message (instead of "Duh"). When doing this,
          *        add some tests to ensure that the proper exception is thrown
          *        when calling \c value() and \c operator[].
          *  @bug In fact, no exception is currently being thrown. Must add test
          *       cases, check the code and document.
          */
         LuaValue value() const;

         /** Assuming that this \c LuaVariable holds a table, returns the value
          *  whose index is \c key.
          *  @param key The key whose value is desired.
          *  @return The value whose index is \c key.
          *  @todo What about exceptions? Must add test cases, check the code
          *        and document.
          */
         LuaVariable operator[] (const LuaValue& key) const;

         /** Checks whether the value stored in this variable is equal to the
          *  value at \c rhs.
          *  @param rhs The value against which the comparison will be done.
          *  @return \c true if this variable's value is equal to \c rhs.
          *          \c false otherwise.
          */
         bool operator== (const LuaValue& rhs) const
         { return value() == rhs; }

         /** Checks whether this \c LuaVariable refers to the same variable as
          *  \c rhs.
          *  @param rhs The variable against which the comparison will be done.
          *  @return \c true if both involved <tt>LuaVariable</tt>s refer to the
          *          same variable. \c false otherwise.
          */
         bool operator== (const LuaVariable& rhs) const
         { return state_ == rhs.state_ && keys_ == rhs.keys_; }

         /** Checks whether this \c LuaVariable refers to a different variable
          *  than \c rhs.
          *  @param rhs The variable against which the comparison will be done.
          *  @return \c false if both involved <tt>LuaVariable</tt>s refer to the
          *          same variable. \c true otherwise.
          */
         bool operator!= (const LuaVariable& rhs) const
         { return state_ != rhs.state_ || keys_ != rhs.keys_; }

      private:
         /// A sequence of keys, used to access nested tables.
         typedef std::vector<LuaValue> KeyList;

         /** Constructs a \c LuaVariable. Note that this is private, so that no
          *  one is expected to construct a \c LuaVariable directly. Use
          *  <tt>LuaState</tt>'s subscript operator instead.
          */
         LuaVariable (lua_State* state, const LuaValue& key,
                      const KeyList& predKeys = KeyList());

         /// The Lua state in which this \c LuaVariable lives.
         lua_State* state_;

         /** The sequence of keys used to get to this variable. For a global
          *  variable, this will consist of a single key; for variables inside
          *  nested tables, this sequence can be arbitrarily long.
          */
         std::vector<LuaValue> keys_;
   };

} // namespace Diluculum

#endif // _DILUCULUM_LUA_VARIABLE_HPP_
<commit_msg>Improved the documentation of 'LuaVariable''s constructor.<commit_after>/******************************************************************************\
* LuaVariable.hpp                                                              *
* A variable living in a Lua interpreter.                                      *
* Leandro Motta Barros                                                         *
\******************************************************************************/

#ifndef _DILUCULUM_LUA_VARIABLE_HPP_
#define _DILUCULUM_LUA_VARIABLE_HPP_

#include <vector>
#include "LuaValue.hpp"

namespace Diluculum
{
   /** A variable living in a Lua state. Notice the crucial difference: unlike a
    *  \c LuaValue, a \c LuaVariable necessarily has a real counterpart in a Lua
    *  state. Thus, when something is assigned to a \c LuaVariable, the value of
    *  the corresponding variable in the Lua state is changed, too.
    *
<tt>LuaVariable</tt>s cannot be directly constructed. They are
    *  designed to be returned by <tt>LuaState</tt>'s subscript operator.
    *  @todo Add tests for expected exceptions.
    */
   class LuaVariable
   {
      friend class LuaState;

      public:
         /** Assigns a new value to this \c LuaVariable. The corresponding
          *  variable in the Lua state is updated accordingly.
          *  @param rhs The new value for the variable.
          *  @return \c rhs, so that a sequence of assignments, like
          *          <tt>a = b = c = 1;</tt> works. Actually, returning
          *          \c rhs is the same as returning <tt>this->value()</tt>,
          *          that perhaps make more sense.
          */
         const LuaValue& operator= (const LuaValue& rhs);

         /** Returns the value associated with this variable.
          *  @todo Throw a more specific exception (instead \c LuaError), and a
          *        more adequate message (instead of "Duh"). When doing this,
          *        add some tests to ensure that the proper exception is thrown
          *        when calling \c value() and \c operator[].
          *  @bug In fact, no exception is currently being thrown. Must add test
          *       cases, check the code and document.
          */
         LuaValue value() const;

         /** Assuming that this \c LuaVariable holds a table, returns the value
          *  whose index is \c key.
          *  @param key The key whose value is desired.
          *  @return The value whose index is \c key.
          *  @todo What about exceptions? Must add test cases, check the code
          *        and document.
          */
         LuaVariable operator[] (const LuaValue& key) const;

         /** Checks whether the value stored in this variable is equal to the
          *  value at \c rhs.
          *  @param rhs The value against which the comparison will be done.
          *  @return \c true if this variable's value is equal to \c rhs.
          *          \c false otherwise.
          */
         bool operator== (const LuaValue& rhs) const
         { return value() == rhs; }

         /** Checks whether this \c LuaVariable refers to the same variable as
          *  \c rhs.
          *  @param rhs The variable against which the comparison will be done.
          *  @return \c true if both involved <tt>LuaVariable</tt>s refer to the
          *          same variable. \c false otherwise.
          */
         bool operator== (const LuaVariable& rhs) const
         { return state_ == rhs.state_ && keys_ == rhs.keys_; }

         /** Checks whether this \c LuaVariable refers to a different variable
          *  than \c rhs.
          *  @param rhs The variable against which the comparison will be done.
          *  @return \c false if both involved <tt>LuaVariable</tt>s refer to the
          *          same variable. \c true otherwise.
          */
         bool operator!= (const LuaVariable& rhs) const
         { return state_ != rhs.state_ || keys_ != rhs.keys_; }

      private:
         /// A sequence of keys, used to access nested tables.
         typedef std::vector<LuaValue> KeyList;

         /** Constructs a \c LuaVariable.
          *   @note This is private because no one is expected to construct a
          *         \c LuaVariable directly. For Diluculum users, the only
          *         allowed way to create a new \c LuaVariable is by using
          *         <tt>LuaState</tt>'s subscript operator.
          *   @param state The <tt>lua_State*</tt> in which the variable
          *          represented by this \c LuaVariable resides.
          *   @param key The "key" used to access the variable represented by
          *          this \c LuaVariable. This can be variable name (for the case
          *          of a global variable) or a "real" key (when it is a field in
          *          a possibly nested table).

          *   @param predKeys The sequence of keys used to access the table that
          *          contains the variable represented by this \c LuaVariable.
          *          For a global variable, this list will be empty. For a
          *          variable stored in a possibly nested table, the list can be
          *          arbitrarily long.
          */
         LuaVariable (lua_State* state, const LuaValue& key,
                      const KeyList& predKeys = KeyList());

         /// The Lua state in which this \c LuaVariable lives.
         lua_State* state_;

         /** The sequence of keys used to get to this variable. For a global
          *  variable, this will consist of a single key; for variables inside
          *  nested tables, this sequence can be arbitrarily long.
          *
Just to relate this with the parameters passed to the
          *  constructor: this sequence equals to \c predKeys parameter, with
          *  the \c key parameter appended to it.
          */
         std::vector<LuaValue> keys_;
   };

} // namespace Diluculum

#endif // _DILUCULUM_LUA_VARIABLE_HPP_
<|endoftext|>


<commit_before>/*
 *  RBBallRecorder.cpp
 *  golf
 *
 *  Created by Robert Rose on 10/10/08.
 *  Copyright 2008 Bork 3D LLC. All rights reserved.
 *
 */

#include "RBBallRecorder.h"
#include "RudeGL.h"
#include "RudeTextureManager.h"

RBBallRecorder::RBBallRecorder()
: m_wrapped(false)
, m_curBallPosition(0)
, m_timer(0.0f)
, m_lastTime(0.0f)
{
	m_tracerTexture = RudeTextureManager::GetInstance()->LoadTextureFromPVRTFile("tracer");
}

RBBallRecorder::~RBBallRecorder()
{
}

void RBBallRecorder::Reset()
{
	m_wrapped = false;
	m_curBallPosition = 0;
	m_timer = 0.0f;
	m_lastTime = 0.0f;
}

void RBBallRecorder::NextFrame(float delta, bool record)
{
	if(!m_ball)
		return;

	if(delta <= 0.0f)
		return;

	m_timer += delta;

	if(!record)
		return;

	float timeDelta = m_timer - m_lastTime;
	m_lastTime = m_timer;

	if(timeDelta <= 0.0f)
		return;

	m_ballPositions[m_curBallPosition].m_position = m_ball->GetPosition();
	m_ballPositions[m_curBallPosition].m_angVel = m_ball->GetAngularVelocity();
	m_ballPositions[m_curBallPosition].m_time = timeDelta;

	m_curBallPosition++;

	if(m_curBallPosition >= kNumBallPositions)
	{
		m_wrapped = true;
		m_curBallPosition = 0;
	}

}

void RBBallRecorder::RenderTracers()
{
	float scale = m_ball->GetBallScale();

	const float kTracerTimeLen = 0.5f;

	float time = 0.0f;
	int tracerLen = 0;
	int k = m_curBallPosition - 1;

	while(time < kTracerTimeLen)
	{
		if(k < 0)
		{
			if(m_wrapped)
				k = kNumBallPositions - 1;
			else
				break;
		}

		if(k == m_curBallPosition)
			// we've wrapped all the way back around to the beginning
			break;

		time += m_ballPositions[k].m_time;

		k--;

		tracerLen++;
	}

	if(tracerLen == 0)
		return;

	RGL.Enable(kDepthTest, true);
	RGL.Enable(kBackfaceCull, false);

	RudeTextureManager::GetInstance()->SetTexture(m_tracerTexture);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

	RGL.EnableClient(kVertexArray, true);
	RGL.EnableClient(kColorArray, true);
	RGL.EnableClient(kTextureCoordArray, true);

	const unsigned int colors[4] = {
		0xFFFFFFFF,
		0xFFFFFFFF,
		0xFFFFFFFF,
		0xFFFFFFFF
	};

	RGL.LoadIdentity();

	int i = m_curBallPosition - 1;
	int c = tracerLen;

	if(i < 0)
	{
		if(m_wrapped)
			i = kNumBallPositions - 1;
		else
			c = 0;
	}

	int p = i;

	bool first = true;
	btVector3 b1;
	btVector3 b2;

	float bintensity = 1.0f;

	btVector3 right;

	while(c)
	{
		if(p != i)
		{
			btVector3 p1 = m_ballPositions[p].m_position;
			btVector3 p2 = m_ballPositions[i].m_position;

			float aintensity = ((float) c) / ((float) tracerLen);

			btVector3 dir = p2 - p1;

			if(dir.length() > 0.0f)
			{
				dir = dir.normalize();
				btVector3 up(0,1,0);

				right = dir.cross(up);
			}

			right = right.normalize();
			right *= scale;

			btVector3 a1 = p1 + right;
			btVector3 a2 = p1 - right;

			if(first)
			{
				b1 = p2 + right;
				b2 = p2 - right;
				bintensity = ((float) c-1) / ((float) tracerLen);
				first = false;
			}

			GLfloat point[] = {
				a1.x(), a1.y(), a1.z(),
				a2.x(), a2.y(), a2.z(),
				b2.x(), b2.y(), b2.z(),
				b1.x(), b1.y(), b1.z()
			};

			GLfloat uvs[] = {
				0.0f, aintensity,
				1.0f, aintensity,
				1.0f, bintensity,
				0.0f, bintensity,
			};

			glVertexPointer(3, GL_FLOAT, 0, point);
			glColorPointer(4, GL_UNSIGNED_BYTE, 0, colors);
			glTexCoordPointer(2, GL_FLOAT, 0, uvs);

			glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

			b1 = a1;
			b2 = a2;
			bintensity = aintensity;

		}

		p = i;

		i--;
		c--;

		if(i < 0)
		{
			if(m_wrapped)
				i = kNumBallPositions - 1;
			else
				c = 0;
		}
	}
}

/*
void RBBallRecorder::RenderTracers()
{
	float scale = m_ball->GetBallScale();

	glDisable(GL_TEXTURE_2D);
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_COLOR_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	RGL.LoadIdentity();

	const int kTracerLen = 10;

	int i = m_curBallPosition - 1;
	int c = kTracerLen;
	int p = i;

	bool first = true;
	btVector3 b1;
	btVector3 b2;

	unsigned int lasta = 0x00;

	while(c)
	{
		if(p != i)
		{
			btVector3 p1 = m_ballPositions[p].m_position;
			btVector3 p2 = m_ballPositions[i].m_position;

			float intensity = ((float) c) / ((float) kTracerLen);

			btVector3 dir = p2 - p1;
			dir = dir.normalize();
			btVector3 up(0,1,0);

			btVector3 right = dir.cross(up);

			right = right.normalize();
			right *= scale * intensity;

			btVector3 a1 = p1 + right;
			btVector3 a2 = p1 - right;

			if(first)
			{
				b1 = p2 + right;
				b2 = p2 - right;
				first = false;
			}

			GLfloat point[] = {
				a1.x(), a1.y(), a1.z(),
				a2.x(), a2.y(), a2.z(),
				b2.x(), b2.y(), b2.z(),
				b1.x(), b1.y(), b1.z()
			};

			unsigned int a = int(intensity * 0xFF) << 24;

			unsigned int colors[] = {
				a | 0xFFFFFF,
				a | 0xFFFFFF,
				lasta | 0xFFFFFF,
				lasta | 0xFFFFFF
			};

			glVertexPointer(3, GL_FLOAT, 0, point);
			glColorPointer(4, GL_UNSIGNED_BYTE, 0, colors);

			glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

			b1 = a1;
			b2 = a2;
			lasta = a;

		}

		p = i;

		i--;
		c--;

		if(i < 0)
		{
			if(m_wrapped)
				i = kNumBallPositions - 1;
			else
				c = 0;
		}
	}
}
 */

void RBBallRecorder::RenderRecords()
{
	int last = m_curBallPosition;

	if(m_wrapped)
		last = kNumBallPositions - 1;

	for(int i = 0; i < last; i++)
	{
		RGL.LoadIdentity();
		m_ball->Render(m_ballPositions[i].m_position, m_ballPositions[i].m_angVel * m_timer * 0.2f);
	}

}

<commit_msg>ball tracer is always oriented towards camera<commit_after>/*
 *  RBBallRecorder.cpp
 *  golf
 *
 *  Created by Robert Rose on 10/10/08.
 *  Copyright 2008 Bork 3D LLC. All rights reserved.
 *
 */

#include "RBBallRecorder.h"
#include "RudeGL.h"
#include "RudeDebug.h"
#include "RudeTextureManager.h"

RBBallRecorder::RBBallRecorder()
: m_wrapped(false)
, m_curBallPosition(0)
, m_timer(0.0f)
, m_lastTime(0.0f)
{
	m_tracerTexture = RudeTextureManager::GetInstance()->LoadTextureFromPVRTFile("tracer");
}

RBBallRecorder::~RBBallRecorder()
{
}

void RBBallRecorder::Reset()
{
	m_wrapped = false;
	m_curBallPosition = 0;
	m_timer = 0.0f;
	m_lastTime = 0.0f;
}

void RBBallRecorder::NextFrame(float delta, bool record)
{
	if(!m_ball)
		return;

	if(delta <= 0.0f)
		return;

	m_timer += delta;

	if(!record)
		return;

	float timeDelta = m_timer - m_lastTime;
	m_lastTime = m_timer;

	if(timeDelta <= 0.0f)
		return;

	m_ballPositions[m_curBallPosition].m_position = m_ball->GetPosition();
	m_ballPositions[m_curBallPosition].m_angVel = m_ball->GetAngularVelocity();
	m_ballPositions[m_curBallPosition].m_time = timeDelta;

	m_curBallPosition++;

	if(m_curBallPosition >= kNumBallPositions)
	{
		m_wrapped = true;
		m_curBallPosition = 0;
	}

}

void RBBallRecorder::RenderTracers()
{
	float scale = m_ball->GetBallScale();

	const float kTracerTimeLen = 0.5f;

	float time = 0.0f;
	int tracerLen = 0;
	int k = m_curBallPosition - 1;

	while(time < kTracerTimeLen)
	{
		if(k < 0)
		{
			if(m_wrapped)
				k = kNumBallPositions - 1;
			else
				break;
		}

		if(k == m_curBallPosition)
			// we've wrapped all the way back around to the beginning
			break;

		time += m_ballPositions[k].m_time;

		k--;

		tracerLen++;
	}

	if(tracerLen == 0)
		return;

	RGL.Enable(kDepthTest, true);
	RGL.Enable(kBackfaceCull, false);

	RudeTextureManager::GetInstance()->SetTexture(m_tracerTexture);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

	RGL.EnableClient(kVertexArray, true);
	RGL.EnableClient(kColorArray, true);
	RGL.EnableClient(kTextureCoordArray, true);

	const unsigned int colors[4] = {
		0xFFFFFFFF,
		0xFFFFFFFF,
		0xFFFFFFFF,
		0xFFFFFFFF
	};

	RGL.LoadIdentity();

	int i = m_curBallPosition - 1;
	int c = tracerLen;

	if(i < 0)
	{
		if(m_wrapped)
			i = kNumBallPositions - 1;
		else
			c = 0;
	}

	int p = i;

	bool first = true;
	btVector3 b1;
	btVector3 b2;

	float bintensity = 1.0f;

	btVector3 right;

	while(c)
	{
		if(p != i)
		{
			btVector3 p1 = m_ballPositions[p].m_position;
			btVector3 p2 = m_ballPositions[i].m_position;

			float aintensity = ((float) c) / ((float) tracerLen);

			btVector3 dir = p2 - p1;

			btVector3 normal(0,1,0);

			if(dir.length() > 0.0f)
			{
				dir.normalize();

				btVector3 lookAt = RGL.GetLookAt();
				btVector3 eye = RGL.GetEye();
				btVector3 lookdir = lookAt - eye;
				lookdir.normalize();

				right = dir.cross(lookdir);
			}

			right = right.normalize();
			right *= scale;

			btVector3 a1 = p1 + right;
			btVector3 a2 = p1 - right;

			if(first)
			{
				b1 = p2 + right;
				b2 = p2 - right;
				bintensity = ((float) c-1) / ((float) tracerLen);
				first = false;
			}

			GLfloat point[] = {
				a1.x(), a1.y(), a1.z(),
				a2.x(), a2.y(), a2.z(),
				b2.x(), b2.y(), b2.z(),
				b1.x(), b1.y(), b1.z()
			};

			GLfloat uvs[] = {
				0.0f, aintensity,
				1.0f, aintensity,
				1.0f, bintensity,
				0.0f, bintensity,
			};

			glVertexPointer(3, GL_FLOAT, 0, point);
			glColorPointer(4, GL_UNSIGNED_BYTE, 0, colors);
			glTexCoordPointer(2, GL_FLOAT, 0, uvs);

			glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

			b1 = a1;
			b2 = a2;
			bintensity = aintensity;

		}

		p = i;

		i--;
		c--;

		if(i < 0)
		{
			if(m_wrapped)
				i = kNumBallPositions - 1;
			else
				c = 0;
		}
	}
}

/*
void RBBallRecorder::RenderTracers()
{
	float scale = m_ball->GetBallScale();

	glDisable(GL_TEXTURE_2D);
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_COLOR_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	RGL.LoadIdentity();

	const int kTracerLen = 10;

	int i = m_curBallPosition - 1;
	int c = kTracerLen;
	int p = i;

	bool first = true;
	btVector3 b1;
	btVector3 b2;

	unsigned int lasta = 0x00;

	while(c)
	{
		if(p != i)
		{
			btVector3 p1 = m_ballPositions[p].m_position;
			btVector3 p2 = m_ballPositions[i].m_position;

			float intensity = ((float) c) / ((float) kTracerLen);

			btVector3 dir = p2 - p1;
			dir = dir.normalize();
			btVector3 up(0,1,0);

			btVector3 right = dir.cross(up);

			right = right.normalize();
			right *= scale * intensity;

			btVector3 a1 = p1 + right;
			btVector3 a2 = p1 - right;

			if(first)
			{
				b1 = p2 + right;
				b2 = p2 - right;
				first = false;
			}

			GLfloat point[] = {
				a1.x(), a1.y(), a1.z(),
				a2.x(), a2.y(), a2.z(),
				b2.x(), b2.y(), b2.z(),
				b1.x(), b1.y(), b1.z()
			};

			unsigned int a = int(intensity * 0xFF) << 24;

			unsigned int colors[] = {
				a | 0xFFFFFF,
				a | 0xFFFFFF,
				lasta | 0xFFFFFF,
				lasta | 0xFFFFFF
			};

			glVertexPointer(3, GL_FLOAT, 0, point);
			glColorPointer(4, GL_UNSIGNED_BYTE, 0, colors);

			glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

			b1 = a1;
			b2 = a2;
			lasta = a;

		}

		p = i;

		i--;
		c--;

		if(i < 0)
		{
			if(m_wrapped)
				i = kNumBallPositions - 1;
			else
				c = 0;
		}
	}
}
 */

void RBBallRecorder::RenderRecords()
{
	int last = m_curBallPosition;

	if(m_wrapped)
		last = kNumBallPositions - 1;

	for(int i = 0; i < last; i++)
	{
		RGL.LoadIdentity();
		m_ball->Render(m_ballPositions[i].m_position, m_ballPositions[i].m_angVel * m_timer * 0.2f);
	}

}

<|endoftext|>


<commit_before>/*
 * Web Application Socket client.
 *
 * author: Max Kellermann <mk@cm4all.com>
 */

#include "was_server.hxx"
#include "was_quark.h"
#include "was_control.hxx"
#include "was_output.hxx"
#include "was_input.hxx"
#include "http_response.hxx"
#include "async.hxx"
#include "pevent.hxx"
#include "direct.hxx"
#include "istream/istream.hxx"
#include "strmap.hxx"
#include "pool.hxx"

#include <was/protocol.h>
#include <daemon/log.h>

#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

struct was_server {
    struct pool *pool;

    int control_fd, input_fd, output_fd;

    struct was_control *control;

    const struct was_server_handler *handler;
    void *handler_ctx;

    struct {
        struct pool *pool;

        http_method_t method;

        const char *uri;

        /**
         * Request headers being assembled.  This pointer is set to
         * nullptr before before the request is dispatched to the
         * handler.
         */
        struct strmap *headers;

        WasInput *body;
    } request;

    struct {
        http_status_t status;

        WasOutput *body;
    } response;
};

static void
was_server_release(struct was_server *server, GError *error)
{
    if (server->request.pool != nullptr) {
        if (server->request.body != nullptr)
            was_input_free_p(&server->request.body, error);
        else
            g_error_free(error);

        if (server->request.headers == nullptr && server->response.body != nullptr)
            was_output_free_p(&server->response.body);

        pool_unref(server->request.pool);
    } else
        g_error_free(error);

    close(server->control_fd);
    close(server->input_fd);
    close(server->output_fd);
}

static void
was_server_release_unused(struct was_server *server)
{
    if (server->request.pool != nullptr) {
        if (server->request.body != nullptr)
            was_input_free_unused_p(&server->request.body);

        if (server->request.headers == nullptr && server->response.body != nullptr)
            was_output_free_p(&server->response.body);

        pool_unref(server->request.pool);
    }

    close(server->control_fd);
    close(server->input_fd);
    close(server->output_fd);
}

/**
 * Abort receiving the response status/headers from the WAS server.
 */
static void
was_server_abort(struct was_server *server, GError *error)
{
    was_server_release(server, error);

    server->handler->free(server->handler_ctx);
}

/**
 * Abort receiving the response status/headers from the WAS server.
 */
static void
was_server_abort_unused(struct was_server *server)
{
    was_server_release_unused(server);

    server->handler->free(server->handler_ctx);
}

/*
 * Output handler
 */

static bool
was_server_output_length(uint64_t length, void *ctx)
{
    struct was_server *server = (struct was_server *)ctx;

    assert(server->control != nullptr);
    assert(server->response.body != nullptr);

    return was_control_send_uint64(server->control,
                                   WAS_COMMAND_LENGTH, length);
}

static bool
was_server_output_premature(uint64_t length, GError *error, void *ctx)
{
    struct was_server *server = (struct was_server *)ctx;

    assert(server->control != nullptr);
    assert(server->response.body != nullptr);

    server->response.body = nullptr;

    /* XXX send PREMATURE, recover */
    (void)length;
    was_server_abort(server, error);
    return false;
}

static void
was_server_output_eof(void *ctx)
{
    struct was_server *server = (struct was_server *)ctx;

    assert(server->response.body != nullptr);

    server->response.body = nullptr;
}

static void
was_server_output_abort(GError *error, void *ctx)
{
    struct was_server *server = (struct was_server *)ctx;

    assert(server->response.body != nullptr);

    server->response.body = nullptr;
    was_server_abort(server, error);
}

static constexpr WasOutputHandler was_server_output_handler = {
    .length = was_server_output_length,
    .premature = was_server_output_premature,
    .eof = was_server_output_eof,
    .abort = was_server_output_abort,
};

/*
 * Input handler
 */

static void
was_server_input_eof(void *ctx)
{
    struct was_server *server = (struct was_server *)ctx;

    assert(server->request.headers == nullptr);
    assert(server->request.body != nullptr);

    server->request.body = nullptr;

    // XXX
}

static void
was_server_input_abort(void *ctx)
{
    struct was_server *server = (struct was_server *)ctx;

    assert(server->request.headers == nullptr);
    assert(server->request.body != nullptr);

    server->request.body = nullptr;

    was_server_abort_unused(server);
}

static constexpr WasInputHandler was_server_input_handler = {
    .eof = was_server_input_eof,
    .abort = was_server_input_abort,
};

/*
 * Control channel handler
 */

static bool
was_server_control_packet(enum was_command cmd, const void *payload,
                          size_t payload_length, void *ctx)
{
    struct was_server *server = (struct was_server *)ctx;
    GError *error;

    switch (cmd) {
        struct strmap *headers;
        const uint64_t *length_p;
        const char *p;
        http_method_t method;

    case WAS_COMMAND_NOP:
        break;

    case WAS_COMMAND_REQUEST:
        if (server->request.pool != nullptr) {
            error = g_error_new_literal(was_quark(), 0,
                                        "misplaced REQUEST packet");
            was_server_abort(server, error);
            return false;
        }

        server->request.pool = pool_new_linear(server->pool,
                                               "was_server_request", 32768);
        server->request.method = HTTP_METHOD_GET;
        server->request.uri = nullptr;
        server->request.headers = strmap_new(server->request.pool);
        server->request.body = nullptr;
        server->response.body = nullptr;
        break;

    case WAS_COMMAND_METHOD:
        if (payload_length != sizeof(method)) {
            error = g_error_new_literal(was_quark(), 0,
                                        "malformed METHOD packet");
            was_server_abort(server, error);
            return false;
        }

        method = *(const http_method_t *)payload;
        if (server->request.method != HTTP_METHOD_GET &&
            method != server->request.method) {
            /* sending that packet twice is illegal */
            error = g_error_new_literal(was_quark(), 0,
                                        "misplaced METHOD packet");
            was_server_abort(server, error);
            return false;
        }

        if (!http_method_is_valid(method)) {
            error = g_error_new_literal(was_quark(), 0,
                                        "invalid METHOD packet");
            was_server_abort(server, error);
            return false;
        }

        server->request.method = method;
        break;

    case WAS_COMMAND_URI:
        if (server->request.pool == nullptr || server->request.uri != nullptr) {
            error = g_error_new_literal(was_quark(), 0,
                                        "misplaced URI packet");
            was_server_abort(server, error);
            return false;
        }

        server->request.uri = p_strndup(server->request.pool,
                                        (const char *)payload, payload_length);
        break;

    case WAS_COMMAND_SCRIPT_NAME:
    case WAS_COMMAND_PATH_INFO:
    case WAS_COMMAND_QUERY_STRING:
        // XXX
        break;

    case WAS_COMMAND_HEADER:
        if (server->request.pool == nullptr || server->request.headers == nullptr) {
            error = g_error_new_literal(was_quark(), 0,
                                        "misplaced HEADER packet");
            was_server_abort(server, error);
            return false;
        }

        p = (const char *)memchr(payload, '=', payload_length);
        if (p == nullptr) {
            error = g_error_new_literal(was_quark(), 0,
                                        "malformed HEADER packet");
            was_server_abort(server, error);
            return false;
        }

        // XXX parse buffer

        break;

    case WAS_COMMAND_PARAMETER:
        // XXX
        break;

    case WAS_COMMAND_STATUS:
        error = g_error_new_literal(was_quark(), 0,
                                    "misplaced STATUS packet");
        was_server_abort(server, error);
        return false;

    case WAS_COMMAND_NO_DATA:
        if (server->request.pool == nullptr || server->request.uri == nullptr ||
            server->request.headers == nullptr) {
            error = g_error_new_literal(was_quark(), 0,
                                        "misplaced NO_DATA packet");
            was_server_abort(server, error);
            return false;
        }

        headers = server->request.headers;
        server->request.headers = nullptr;

        server->request.body = nullptr;

        server->handler->request(server->request.pool, server->request.method,
                                 server->request.uri, headers, nullptr,
                                 server->handler_ctx);
        /* XXX check if connection has been closed */
        break;

    case WAS_COMMAND_DATA:
        if (server->request.pool == nullptr || server->request.uri == nullptr ||
            server->request.headers == nullptr) {
            error = g_error_new_literal(was_quark(), 0,
                                        "misplaced DATA packet");
            was_server_abort(server, error);
            return false;
        }

        headers = server->request.headers;
        server->request.headers = nullptr;

        server->request.body = was_input_new(server->request.pool,
                                             server->input_fd,
                                             &was_server_input_handler,
                                             server);

        server->handler->request(server->request.pool, server->request.method,
                                 server->request.uri, headers,
                                 was_input_enable(server->request.body),
                                 server->handler_ctx);
        /* XXX check if connection has been closed */
        break;

    case WAS_COMMAND_LENGTH:
        if (server->request.pool == nullptr || server->request.headers != nullptr) {
            error = g_error_new_literal(was_quark(), 0,
                                        "misplaced LENGTH packet");
            was_server_abort(server, error);
            return false;
        }

        length_p = (const uint64_t *)payload;
        if (server->response.body == nullptr ||
            payload_length != sizeof(*length_p)) {
            error = g_error_new_literal(was_quark(), 0,
                                        "malformed LENGTH packet");
            was_server_abort(server, error);
            return false;
        }

        if (!was_input_set_length(server->request.body, *length_p)) {
            error = g_error_new_literal(was_quark(), 0,
                                        "invalid LENGTH packet");
            was_server_abort(server, error);
            return false;
        }

        break;

    case WAS_COMMAND_STOP:
    case WAS_COMMAND_PREMATURE:
        // XXX
        error = g_error_new(was_quark(), 0,
                            "unexpected packet: %d", cmd);
        was_server_abort(server, error);
        return false;
    }

    (void)payload;
    (void)payload_length;

    return true;
}

static void
was_server_control_eof(void *ctx)
{
    struct was_server *server = (struct was_server *)ctx;

    (void)server;
}

static void
was_server_control_abort(GError *error, void *ctx)
{
    struct was_server *server = (struct was_server *)ctx;

    was_server_abort(server, error);
}

static const struct was_control_handler was_server_control_handler = {
    .packet = was_server_control_packet,
    .eof = was_server_control_eof,
    .abort = was_server_control_abort,
};

/*
 * constructor
 *
 */

struct was_server *
was_server_new(struct pool *pool, int control_fd, int input_fd, int output_fd,
               const struct was_server_handler *handler, void *handler_ctx)
{
    assert(pool != nullptr);
    assert(control_fd >= 0);
    assert(input_fd >= 0);
    assert(output_fd >= 0);
    assert(handler != nullptr);
    assert(handler->request != nullptr);
    assert(handler->free != nullptr);

    auto server = NewFromPool<struct was_server>(*pool);
    server->pool = pool;
    server->control_fd = control_fd;
    server->input_fd = input_fd;
    server->output_fd = output_fd;

    server->control = was_control_new(pool, control_fd,
                                      &was_server_control_handler, server);

    server->handler = handler;
    server->handler_ctx = handler_ctx;

    server->request.pool = nullptr;

    return server;
}

void
was_server_free(struct was_server *server)
{
    GError *error = g_error_new_literal(was_quark(), 0,
                                        "shutting down WAS connection");
    was_server_release(server, error);
}

void
was_server_response(struct was_server *server, http_status_t status,
                    struct strmap *headers, struct istream *body)
{
    assert(server != nullptr);
    assert(server->request.pool != nullptr);
    assert(server->request.headers == nullptr);
    assert(server->response.body == nullptr);
    assert(http_status_is_valid(status));
    assert(!http_status_is_empty(status) || body == nullptr);

    if (!was_control_send(server->control, WAS_COMMAND_STATUS,
                          &status, sizeof(status)))
        return;

    if (body != nullptr) {
        server->response.body = was_output_new(server->request.pool,
                                               server->output_fd, body,
                                               &was_server_output_handler,
                                               server);
        if (!was_control_send_empty(server->control, WAS_COMMAND_DATA))
            return;

        off_t available = istream_available(body, false);
        if (available >= 0 &&
            !was_control_send_uint64(server->control, WAS_COMMAND_LENGTH,
                                     available))
            return;
    } else {
        if (!was_control_send_empty(server->control, WAS_COMMAND_NO_DATA))
            return;
    }

    (void)headers; // XXX
}
<commit_msg>was/server: work around "non-trivial designated initializers not supported"<commit_after>/*
 * Web Application Socket client.
 *
 * author: Max Kellermann <mk@cm4all.com>
 */

#include "was_server.hxx"
#include "was_quark.h"
#include "was_control.hxx"
#include "was_output.hxx"
#include "was_input.hxx"
#include "http_response.hxx"
#include "async.hxx"
#include "pevent.hxx"
#include "direct.hxx"
#include "istream/istream.hxx"
#include "strmap.hxx"
#include "pool.hxx"

#include <was/protocol.h>
#include <daemon/log.h>

#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

struct was_server {
    struct pool *pool;

    int control_fd, input_fd, output_fd;

    struct was_control *control;

    const struct was_server_handler *handler;
    void *handler_ctx;

    struct {
        struct pool *pool;

        http_method_t method;

        const char *uri;

        /**
         * Request headers being assembled.  This pointer is set to
         * nullptr before before the request is dispatched to the
         * handler.
         */
        struct strmap *headers;

        WasInput *body;
    } request;

    struct {
        http_status_t status;

        WasOutput *body;
    } response;
};

static void
was_server_release(struct was_server *server, GError *error)
{
    if (server->request.pool != nullptr) {
        if (server->request.body != nullptr)
            was_input_free_p(&server->request.body, error);
        else
            g_error_free(error);

        if (server->request.headers == nullptr && server->response.body != nullptr)
            was_output_free_p(&server->response.body);

        pool_unref(server->request.pool);
    } else
        g_error_free(error);

    close(server->control_fd);
    close(server->input_fd);
    close(server->output_fd);
}

static void
was_server_release_unused(struct was_server *server)
{
    if (server->request.pool != nullptr) {
        if (server->request.body != nullptr)
            was_input_free_unused_p(&server->request.body);

        if (server->request.headers == nullptr && server->response.body != nullptr)
            was_output_free_p(&server->response.body);

        pool_unref(server->request.pool);
    }

    close(server->control_fd);
    close(server->input_fd);
    close(server->output_fd);
}

/**
 * Abort receiving the response status/headers from the WAS server.
 */
static void
was_server_abort(struct was_server *server, GError *error)
{
    was_server_release(server, error);

    server->handler->free(server->handler_ctx);
}

/**
 * Abort receiving the response status/headers from the WAS server.
 */
static void
was_server_abort_unused(struct was_server *server)
{
    was_server_release_unused(server);

    server->handler->free(server->handler_ctx);
}

/*
 * Output handler
 */

static bool
was_server_output_length(uint64_t length, void *ctx)
{
    struct was_server *server = (struct was_server *)ctx;

    assert(server->control != nullptr);
    assert(server->response.body != nullptr);

    return was_control_send_uint64(server->control,
                                   WAS_COMMAND_LENGTH, length);
}

static bool
was_server_output_premature(uint64_t length, GError *error, void *ctx)
{
    struct was_server *server = (struct was_server *)ctx;

    assert(server->control != nullptr);
    assert(server->response.body != nullptr);

    server->response.body = nullptr;

    /* XXX send PREMATURE, recover */
    (void)length;
    was_server_abort(server, error);
    return false;
}

static void
was_server_output_eof(void *ctx)
{
    struct was_server *server = (struct was_server *)ctx;

    assert(server->response.body != nullptr);

    server->response.body = nullptr;
}

static void
was_server_output_abort(GError *error, void *ctx)
{
    struct was_server *server = (struct was_server *)ctx;

    assert(server->response.body != nullptr);

    server->response.body = nullptr;
    was_server_abort(server, error);
}

static constexpr WasOutputHandler was_server_output_handler = {
    .length = was_server_output_length,
    .premature = was_server_output_premature,
    .eof = was_server_output_eof,
    .abort = was_server_output_abort,
};

/*
 * Input handler
 */

static void
was_server_input_eof(void *ctx)
{
    struct was_server *server = (struct was_server *)ctx;

    assert(server->request.headers == nullptr);
    assert(server->request.body != nullptr);

    server->request.body = nullptr;

    // XXX
}

static void
was_server_input_abort(void *ctx)
{
    struct was_server *server = (struct was_server *)ctx;

    assert(server->request.headers == nullptr);
    assert(server->request.body != nullptr);

    server->request.body = nullptr;

    was_server_abort_unused(server);
}

static constexpr WasInputHandler was_server_input_handler = {
    .eof = was_server_input_eof,
    .premature = nullptr,
    .abort = was_server_input_abort,
};

/*
 * Control channel handler
 */

static bool
was_server_control_packet(enum was_command cmd, const void *payload,
                          size_t payload_length, void *ctx)
{
    struct was_server *server = (struct was_server *)ctx;
    GError *error;

    switch (cmd) {
        struct strmap *headers;
        const uint64_t *length_p;
        const char *p;
        http_method_t method;

    case WAS_COMMAND_NOP:
        break;

    case WAS_COMMAND_REQUEST:
        if (server->request.pool != nullptr) {
            error = g_error_new_literal(was_quark(), 0,
                                        "misplaced REQUEST packet");
            was_server_abort(server, error);
            return false;
        }

        server->request.pool = pool_new_linear(server->pool,
                                               "was_server_request", 32768);
        server->request.method = HTTP_METHOD_GET;
        server->request.uri = nullptr;
        server->request.headers = strmap_new(server->request.pool);
        server->request.body = nullptr;
        server->response.body = nullptr;
        break;

    case WAS_COMMAND_METHOD:
        if (payload_length != sizeof(method)) {
            error = g_error_new_literal(was_quark(), 0,
                                        "malformed METHOD packet");
            was_server_abort(server, error);
            return false;
        }

        method = *(const http_method_t *)payload;
        if (server->request.method != HTTP_METHOD_GET &&
            method != server->request.method) {
            /* sending that packet twice is illegal */
            error = g_error_new_literal(was_quark(), 0,
                                        "misplaced METHOD packet");
            was_server_abort(server, error);
            return false;
        }

        if (!http_method_is_valid(method)) {
            error = g_error_new_literal(was_quark(), 0,
                                        "invalid METHOD packet");
            was_server_abort(server, error);
            return false;
        }

        server->request.method = method;
        break;

    case WAS_COMMAND_URI:
        if (server->request.pool == nullptr || server->request.uri != nullptr) {
            error = g_error_new_literal(was_quark(), 0,
                                        "misplaced URI packet");
            was_server_abort(server, error);
            return false;
        }

        server->request.uri = p_strndup(server->request.pool,
                                        (const char *)payload, payload_length);
        break;

    case WAS_COMMAND_SCRIPT_NAME:
    case WAS_COMMAND_PATH_INFO:
    case WAS_COMMAND_QUERY_STRING:
        // XXX
        break;

    case WAS_COMMAND_HEADER:
        if (server->request.pool == nullptr || server->request.headers == nullptr) {
            error = g_error_new_literal(was_quark(), 0,
                                        "misplaced HEADER packet");
            was_server_abort(server, error);
            return false;
        }

        p = (const char *)memchr(payload, '=', payload_length);
        if (p == nullptr) {
            error = g_error_new_literal(was_quark(), 0,
                                        "malformed HEADER packet");
            was_server_abort(server, error);
            return false;
        }

        // XXX parse buffer

        break;

    case WAS_COMMAND_PARAMETER:
        // XXX
        break;

    case WAS_COMMAND_STATUS:
        error = g_error_new_literal(was_quark(), 0,
                                    "misplaced STATUS packet");
        was_server_abort(server, error);
        return false;

    case WAS_COMMAND_NO_DATA:
        if (server->request.pool == nullptr || server->request.uri == nullptr ||
            server->request.headers == nullptr) {
            error = g_error_new_literal(was_quark(), 0,
                                        "misplaced NO_DATA packet");
            was_server_abort(server, error);
            return false;
        }

        headers = server->request.headers;
        server->request.headers = nullptr;

        server->request.body = nullptr;

        server->handler->request(server->request.pool, server->request.method,
                                 server->request.uri, headers, nullptr,
                                 server->handler_ctx);
        /* XXX check if connection has been closed */
        break;

    case WAS_COMMAND_DATA:
        if (server->request.pool == nullptr || server->request.uri == nullptr ||
            server->request.headers == nullptr) {
            error = g_error_new_literal(was_quark(), 0,
                                        "misplaced DATA packet");
            was_server_abort(server, error);
            return false;
        }

        headers = server->request.headers;
        server->request.headers = nullptr;

        server->request.body = was_input_new(server->request.pool,
                                             server->input_fd,
                                             &was_server_input_handler,
                                             server);

        server->handler->request(server->request.pool, server->request.method,
                                 server->request.uri, headers,
                                 was_input_enable(server->request.body),
                                 server->handler_ctx);
        /* XXX check if connection has been closed */
        break;

    case WAS_COMMAND_LENGTH:
        if (server->request.pool == nullptr || server->request.headers != nullptr) {
            error = g_error_new_literal(was_quark(), 0,
                                        "misplaced LENGTH packet");
            was_server_abort(server, error);
            return false;
        }

        length_p = (const uint64_t *)payload;
        if (server->response.body == nullptr ||
            payload_length != sizeof(*length_p)) {
            error = g_error_new_literal(was_quark(), 0,
                                        "malformed LENGTH packet");
            was_server_abort(server, error);
            return false;
        }

        if (!was_input_set_length(server->request.body, *length_p)) {
            error = g_error_new_literal(was_quark(), 0,
                                        "invalid LENGTH packet");
            was_server_abort(server, error);
            return false;
        }

        break;

    case WAS_COMMAND_STOP:
    case WAS_COMMAND_PREMATURE:
        // XXX
        error = g_error_new(was_quark(), 0,
                            "unexpected packet: %d", cmd);
        was_server_abort(server, error);
        return false;
    }

    (void)payload;
    (void)payload_length;

    return true;
}

static void
was_server_control_eof(void *ctx)
{
    struct was_server *server = (struct was_server *)ctx;

    (void)server;
}

static void
was_server_control_abort(GError *error, void *ctx)
{
    struct was_server *server = (struct was_server *)ctx;

    was_server_abort(server, error);
}

static const struct was_control_handler was_server_control_handler = {
    .packet = was_server_control_packet,
    .drained = nullptr,
    .eof = was_server_control_eof,
    .abort = was_server_control_abort,
};

/*
 * constructor
 *
 */

struct was_server *
was_server_new(struct pool *pool, int control_fd, int input_fd, int output_fd,
               const struct was_server_handler *handler, void *handler_ctx)
{
    assert(pool != nullptr);
    assert(control_fd >= 0);
    assert(input_fd >= 0);
    assert(output_fd >= 0);
    assert(handler != nullptr);
    assert(handler->request != nullptr);
    assert(handler->free != nullptr);

    auto server = NewFromPool<struct was_server>(*pool);
    server->pool = pool;
    server->control_fd = control_fd;
    server->input_fd = input_fd;
    server->output_fd = output_fd;

    server->control = was_control_new(pool, control_fd,
                                      &was_server_control_handler, server);

    server->handler = handler;
    server->handler_ctx = handler_ctx;

    server->request.pool = nullptr;

    return server;
}

void
was_server_free(struct was_server *server)
{
    GError *error = g_error_new_literal(was_quark(), 0,
                                        "shutting down WAS connection");
    was_server_release(server, error);
}

void
was_server_response(struct was_server *server, http_status_t status,
                    struct strmap *headers, struct istream *body)
{
    assert(server != nullptr);
    assert(server->request.pool != nullptr);
    assert(server->request.headers == nullptr);
    assert(server->response.body == nullptr);
    assert(http_status_is_valid(status));
    assert(!http_status_is_empty(status) || body == nullptr);

    if (!was_control_send(server->control, WAS_COMMAND_STATUS,
                          &status, sizeof(status)))
        return;

    if (body != nullptr) {
        server->response.body = was_output_new(server->request.pool,
                                               server->output_fd, body,
                                               &was_server_output_handler,
                                               server);
        if (!was_control_send_empty(server->control, WAS_COMMAND_DATA))
            return;

        off_t available = istream_available(body, false);
        if (available >= 0 &&
            !was_control_send_uint64(server->control, WAS_COMMAND_LENGTH,
                                     available))
            return;
    } else {
        if (!was_control_send_empty(server->control, WAS_COMMAND_NO_DATA))
            return;
    }

    (void)headers; // XXX
}
<|endoftext|>