<commit_before>/*  -*- mode: c++; coding: utf-8; c-file-style: "stroustrup"; -*-

    Contributors: Asier Aguirre

    All rights reserved. Use of this source code is governed by a
    BSD-style license that can be found in the LICENSE.txt file.
*/

#include "widget_layout.h"
#include "main.h"
#include "input.h"
#include "nanovg.h"

using namespace std;

namespace webui {

    void WidgetLayout::render(Context& ctx, int alphaMult) {
        Widget::render(ctx, alphaMult);
        if (dragDrop) {
            auto& render(ctx.getRender());
            alphaMult = render.multAlpha(alphaMult, alpha);
            dragDrop->translate(Input::cursor - Input::cursorLeftPress);
            dragDrop->render(ctx, alphaMult >> 1);
            dragDrop->translate(Input::cursorLeftPress - Input::cursor);
        }
    }

    bool WidgetLayout::input(Application& app) {
        if (Input::mouseButtonWidget && Input::cursorLeftPress.manhatan(Input::cursor) > 16) {
            // drag & drop
            if (!dragDrop) {
                // check if clicked in one of layout widgets
                for (size_t idx = 0; idx < children.size(); idx++)
                    if (children[idx] == Input::mouseButtonWidget) {
                        dragDrop = Input::mouseButtonWidget;
                        break;
                    }
            }
            if (Input::mouseButton == GLFW_MOUSE_BUTTON_LEFT &&
                Input::mouseAction == GLFW_RELEASE && dragDrop) {
                dragDrop = Input::mouseButtonWidget = nullptr;
                return true;
            }
            return true;
        }
        return false;
    }

    bool WidgetLayout::layout(Context& ctx, V2s posAvail, V2s sizeAvail) {
        bool stable(true);
        curPos = posAvail;
        curSize = sizeAvail;
        if (visible) {
            // drag & drop
            short prevCoord(0);
            if (dragDrop) {
                // remove from list
                for (size_t idx = 0; idx < children.size(); idx++)
                    if (children[idx] == dragDrop)
                        children.erase(children.begin() + idx);
                // find position
                short midCoord(dragDrop->curPos[coord] + (dragDrop->curSize[coord] >> 1) + Input::cursor[coord] - Input::cursorLeftPress[coord]);
                short last(numeric_limits<short>::min());
                size_t pos(children.size());
                for (size_t idx = 0; idx < children.size(); idx++) {
                    auto* child(children[idx]);
                    short mid(child->curPos[coord] + (child->curSize[coord] >> 1));
                    if (midCoord >= last && midCoord < mid) {
                        pos = idx;
                        break;
                    }
                    last = mid;
                }
                // add it in correct position
                children.insert(children.begin() + pos, dragDrop);
                prevCoord = dragDrop->curPos[coord];
            }

            // use linear arrangement util
            LinearArrangement::Elem elems[children.size() + 1];
            LinearArrangement la(elems, posAvail[coord]);
            for (auto child: children)
                if (child->isVisible())
                    la.add(child->curSize[coord], child->size[coord].get(sizeAvail[coord]), child->size[coord].relative);
                else
                    la.add(child->curSize[coord], 0, true);
            stable = la.calculate(ctx, sizeAvail[coord]);
            if (!coord)
                for (size_t idx = 0; idx < children.size(); idx++) {
                    auto* child(children[idx]);
                    stable &= child->layout(ctx, V2s(la.get(idx), posAvail.y), V2s(la.get(idx + 1) - la.get(idx), child->size[1].get(curSize.y)));
                }
            else
                for (size_t idx = 0; idx < children.size(); idx++) {
                    auto* child(children[idx]);
                    stable &= child->layout(ctx, V2s(posAvail.x, la.get(idx)), V2s(child->size[0].get(curSize.x), la.get(idx + 1) - la.get(idx)));
                }

            // adaptative size
            if (size[coord].adapt) {
                size[coord].size = elems[children.size()].posTarget - elems[0].posTarget;
                if (size[coord].size != curSize[coord]) stable = false;
            }

            // drag & drop
            if (dragDrop)
                Input::cursorLeftPress[coord] += dragDrop->curPos[coord] - prevCoord;
        }
        return animeAlpha(ctx) && stable;
    }

}
<commit_msg>Layout: Adaptative size for second coordinate.<commit_after>/*  -*- mode: c++; coding: utf-8; c-file-style: "stroustrup"; -*-

    Contributors: Asier Aguirre

    All rights reserved. Use of this source code is governed by a
    BSD-style license that can be found in the LICENSE.txt file.
*/

#include "widget_layout.h"
#include "main.h"
#include "input.h"
#include "nanovg.h"

using namespace std;

namespace webui {

    void WidgetLayout::render(Context& ctx, int alphaMult) {
        Widget::render(ctx, alphaMult);
        if (dragDrop) {
            auto& render(ctx.getRender());
            alphaMult = render.multAlpha(alphaMult, alpha);
            dragDrop->translate(Input::cursor - Input::cursorLeftPress);
            dragDrop->render(ctx, alphaMult >> 1);
            dragDrop->translate(Input::cursorLeftPress - Input::cursor);
        }
    }

    bool WidgetLayout::input(Application& app) {
        if (Input::mouseButtonWidget && Input::cursorLeftPress.manhatan(Input::cursor) > 16) {
            // drag & drop
            if (!dragDrop) {
                // check if clicked in one of layout widgets
                for (size_t idx = 0; idx < children.size(); idx++)
                    if (children[idx] == Input::mouseButtonWidget) {
                        dragDrop = Input::mouseButtonWidget;
                        break;
                    }
            }
            if (Input::mouseButton == GLFW_MOUSE_BUTTON_LEFT &&
                Input::mouseAction == GLFW_RELEASE && dragDrop) {
                dragDrop = Input::mouseButtonWidget = nullptr;
                return true;
            }
            return true;
        }
        return false;
    }

    bool WidgetLayout::layout(Context& ctx, V2s posAvail, V2s sizeAvail) {
        bool stable(true);
        int coord2(coord ^ 1);
        curPos = posAvail;
        curSize = sizeAvail;
        if (visible) {
            // drag & drop
            short prevCoord(0);
            if (dragDrop) {
                // remove from list
                for (size_t idx = 0; idx < children.size(); idx++)
                    if (children[idx] == dragDrop)
                        children.erase(children.begin() + idx);
                // find position
                short midCoord(dragDrop->curPos[coord] + (dragDrop->curSize[coord] >> 1) + Input::cursor[coord] - Input::cursorLeftPress[coord]);
                short last(numeric_limits<short>::min());
                size_t pos(children.size());
                for (size_t idx = 0; idx < children.size(); idx++) {
                    auto* child(children[idx]);
                    short mid(child->curPos[coord] + (child->curSize[coord] >> 1));
                    if (midCoord >= last && midCoord < mid) {
                        pos = idx;
                        break;
                    }
                    last = mid;
                }
                // add it in correct position
                children.insert(children.begin() + pos, dragDrop);
                prevCoord = dragDrop->curPos[coord];
            }

            // use linear arrangement util
            LinearArrangement::Elem elems[children.size() + 1];
            LinearArrangement la(elems, posAvail[coord]);
            for (auto child: children)
                if (child->isVisible())
                    la.add(child->curSize[coord], child->size[coord].get(sizeAvail[coord]), child->size[coord].relative);
                else
                    la.add(child->curSize[coord], 0, true);
            stable = la.calculate(ctx, sizeAvail[coord]);
            if (!coord)
                for (size_t idx = 0; idx < children.size(); idx++) {
                    auto* child(children[idx]);
                    stable &= child->layout(ctx, V2s(la.get(idx), posAvail.y), V2s(la.get(idx + 1) - la.get(idx), child->size[1].get(curSize.y)));
                }
            else
                for (size_t idx = 0; idx < children.size(); idx++) {
                    auto* child(children[idx]);
                    stable &= child->layout(ctx, V2s(posAvail.x, la.get(idx)), V2s(child->size[0].get(curSize.x), la.get(idx + 1) - la.get(idx)));
                }

            // adaptative size
            if (size[coord].adapt) {
                size[coord].size = elems[children.size()].posTarget - elems[0].posTarget;
                if (size[coord].size != curSize[coord]) stable = false;
            }
            if (size[coord2].adapt) {
                if (children.empty())
                    size[coord2].size = 0;
                else
                    size[coord2].size = children[0]->curSize[coord2]; // TODO: take max of all children?
                if (size[coord2].size != curSize[coord2]) stable = false;
            }

            // drag & drop
            if (dragDrop)
                Input::cursorLeftPress[coord] += dragDrop->curPos[coord] - prevCoord;
        }
        return animeAlpha(ctx) && stable;
    }

}
<|endoftext|>


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

#include <pch.hpp>
#include <win32cpp/SysTray.hpp>

//////////////////////////////////////////////////////////////////////////////

using namespace win32cpp;

///\brief Contains the list of notify icons
IconList SysTray::iconList;

///\brief Contains a list of menus for each icon
MenuList SysTray::menuList;

///\brief Contains a list of options for each icon
OptionsList SysTray::optionsList;

///\brief Each notify icon has its own UID. This counter increments
///when an icon is created.
int SysTray::uidCounter = 100;

SysTray::SysTray()
{
    ::InitCommonControls();
}

SysTray::~SysTray()
{
    // iterate through list and delete icons
    for(IconList::iterator i = SysTray::iconList.begin(); i != SysTray::iconList.end(); ++i) {
        ::Shell_NotifyIcon(NIM_DELETE, i->second)
    }
}

///\brief
///Deletes an icon from the tray and all internal structures
///
///\param uid
///The Icon ID
///
///\return bool
bool SysTray::DeleteIcon(UINT uid)
{
    if(SysTray::iconList.find(uid) != SysTray::iconList.end()) {
        if(::Shell_NotifyIcon(NIM_DELETE, &SysTray::iconList[uid]) != 0) {
            SysTray::iconList.erase(uid);
            SysTray::menuList.erase(uid);
            SysTray::optionsList.erase(uid);

            return true;
        }
    }

    return false;
}

///\brief
///Shows a balloon tip over the tray icon
///
///\note
///Windows XP is required for this feature!
///
///\param uid
///The Icon ID
///
///\param title
///Balloon title
///
///\param text
///Balloon inner text
///
///\param timeout
///Time to show the balloon in seconds. There are special restrictions defined
///by the Windows-API. Look here:
///http://msdn.microsoft.com/en-us/library/bb773352(VS.85).aspx
///
///\param text
///Icon to show. Select from NIIF_NONE, NIIF_INFO, NIIF_WARNING, NIIF_ERROR
///
///\return bool
bool SysTray::ShowBalloon(UINT uid, const uistring& title, const uistring& text, UINT timeout, UINT icon)
{
    if(SysTray::iconList.find(uid) != SysTray::iconList.end()) {
        SysTray::iconList[uid].uFlags |= NIF_INFO;
        SysTray::iconList[uid].uTimeout = timeout * 1000;
        SysTray::iconList[uid].dwInfoFlags = icon;

        ::wcsncpy_s(SysTray::iconList[uid].szInfoTitle, 64, title.c_str(), 64);
        ::wcsncpy_s(SysTray::iconList[uid].szInfo, 256, text.c_str(), 256);

        return (::Shell_NotifyIcon(NIM_MODIFY, &SysTray::iconList[uid]) != 0);
    }

    return false;
}

///\brief
///Sets a new icon for the specified tray icon.
///
///\param uid
///The Icon ID
///
///\param icon
///New Icon
///
///\return bool
bool SysTray::SetIcon(UINT uid, HICON icon)
{
    if(SysTray::iconList.find(uid) != SysTray::iconList.end()) {
        SysTray::iconList[uid].hIcon = icon;
        return (::Shell_NotifyIcon(NIM_MODIFY, &SysTray::iconList[uid]) != 0);
    }

    return false;
}

///\brief
///Sets the tooltip for the specified tray icon.
///
///\param uid
///The Icon ID
///
///\param tooltip
///Tooltip to show
///
///\return bool
bool SysTray::SetTooltip(UINT uid, const uistring& tooltip)
{
    if(SysTray::iconList.find(uid) != SysTray::iconList.end()) {
        SysTray::iconList[uid].uFlags |= NIF_TIP;
        ::wcsncpy_s(SysTray::iconList[uid].szTip, 128, tooltip.c_str(), 128);
        return (::Shell_NotifyIcon(NIM_MODIFY, &SysTray::iconList[uid]) != 0);
    }

    return false;
}

///\brief
///Sets the popup menu for the specified tray icon.
///
///\param uid
///The Icon ID
///
///\param menu
///Reference to the menu which should be displayed
///
///\return bool
bool SysTray::SetPopupMenu(UINT uid, MenuRef menu)
{
    if(menu) {
        SysTray::menuList[uid] = menu;
        return true;
    }

    return false;
}

///\brief
///Window procedure for all SysTray handling. Here WM_RBUTTONDOWN (for popup menu),
///WM_LBUTTONDOWN (for restoring the window after it has been minimized to tray) &
///WM_SIZE for the Minimize to tray feature are handled.
///
///\param window
///Affected window handle
///
///\param message
///Message-ID
///
///\param wParam
///\param lParam
///\return LRESULT
///In this case always 0
LRESULT SysTray::WindowProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam)
{
    if(SysTray::menuList.find(message - WM_W32CPP_SYSTRAY) != SysTray::menuList.end()) {
        UINT uid = message - WM_W32CPP_SYSTRAY;

        switch(LOWORD(lParam)) {
        case WM_RBUTTONDOWN:
            {
                if(SysTray::menuList.find(uid) != SysTray::menuList.end()) {
                    POINT mousePos = { 0 };
                    ::GetCursorPos(&mousePos);

                    TrackPopupMenu(
                        SysTray::menuList[uid]->Handle(),
                        TPM_LEFTBUTTON,
                        mousePos.x,
                        mousePos.y,
                        NULL,
                        SysTray::iconList[uid].hWnd,
                        NULL);
                }
            }
            return 0;
        case WM_LBUTTONDOWN:
            {
                 if(SysTray::optionsList[uid] & SysTray::MINIMIZE_TO_TRAY) {
                    // get window object
                    Window* wnd = Window::SubclassedWindowFromHWND(SysTray::iconList[uid].hWnd);

                    // restore window
                    wnd->Show(SW_SHOW);
                    wnd->Show(SW_RESTORE);

                 }
            }
            return 0;
        }
    }

    // handle minimize to tray
    if(message == WM_SIZE && wParam == SIZE_MINIMIZED) {
        // iterate through list with icon options and look, if the assigned icon/window pair wants that
        for(IconList::iterator i = SysTray::iconList.begin(); i != SysTray::iconList.end(); ++i) {
            // look if there is a corresponding window
            if(i->second.hWnd == window) {
                if(SysTray::optionsList[i->second.uID] & SysTray::MINIMIZE_TO_TRAY) {
                    // get window object
                    Window* wnd = Window::SubclassedWindowFromHWND(window);

                    // and finally minimize it to tray
                    wnd->Show(SW_SHOWMINIMIZED);
                    wnd->Show(SW_HIDE);
                }
            }
        }
    }

    return 0;
}

///\brief
///Activates minimize to tray for the icon specified by uid.
///
///\param uid
///The Icon ID
void SysTray::EnableMinimizeToTray(UINT uid)
{
    SysTray::optionsList[uid] |= SysTray::MINIMIZE_TO_TRAY;
}

///\brief
///Creates and add a notify icon
///
///\param window
///The window to associate the icon with
///
///\param icon
///The Icon to show as HICON
///
///\param tooltip
///The tooltip to show. Leave it empty to show no tooltip
///
///\return int
///Returns the new Icon ID or -1 on failure
int SysTray::AddIcon(Window* window, HICON icon, const uistring& tooltip)
{
    UINT uid = SysTray::uidCounter++;
    NOTIFYICONDATA nid;

    // setup notifyicondata structure
    ::ZeroMemory(&nid, sizeof(nid));
    nid.cbSize  = sizeof(NOTIFYICONDATA);
    nid.hWnd    = window->Handle();
    nid.uID     = uid;
    nid.uFlags  = NIF_MESSAGE | NIF_ICON;
    if(tooltip != _T("")) {
        nid.uFlags |= NIF_TIP;
        ::wcsncpy_s(nid.szTip, 64, tooltip.c_str(), 64);
    }
    nid.hIcon   = icon;

    nid.uCallbackMessage = WM_W32CPP_SYSTRAY + uid;

    // create icon
    if(!::Shell_NotifyIcon(NIM_ADD, &nid)) {
        return -1;
    }

    nid.uVersion = NOTIFYICON_VERSION;
    if(!::Shell_NotifyIcon(NIM_SETVERSION, &nid)) {
        return -1;
    }

    // add to icon list
    SysTray::iconList[uid] = nid;

    // add to options list
    SysTray::optionsList[uid] = 0;

    return uid;
}<commit_msg>Another fix :-/<commit_after>//////////////////////////////////////////////////////////////////////////////
//
// License Agreement:
//
// The following are Copyright  2008, Andr Wsten
//
// Sources and Binaries of: mC2, win32cpp
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//    * Redistributions of source code must retain the above copyright notice,
//      this list of conditions and the following disclaimer.
//
//    * 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.
//
//    * Neither the name of the author nor the names of other contributors may
//      be used to endorse or promote products derived from this software
//      without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////

#include <pch.hpp>
#include <win32cpp/SysTray.hpp>

//////////////////////////////////////////////////////////////////////////////

using namespace win32cpp;

///\brief Contains the list of notify icons
IconList SysTray::iconList;

///\brief Contains a list of menus for each icon
MenuList SysTray::menuList;

///\brief Contains a list of options for each icon
OptionsList SysTray::optionsList;

///\brief Each notify icon has its own UID. This counter increments
///when an icon is created.
int SysTray::uidCounter = 100;

SysTray::SysTray()
{
    ::InitCommonControls();
}

SysTray::~SysTray()
{
    // iterate through list and delete icons
    for(IconList::iterator i = SysTray::iconList.begin(); i != SysTray::iconList.end(); ++i) {
        ::Shell_NotifyIcon(NIM_DELETE, &i->second)
    }
}

///\brief
///Deletes an icon from the tray and all internal structures
///
///\param uid
///The Icon ID
///
///\return bool
bool SysTray::DeleteIcon(UINT uid)
{
    if(SysTray::iconList.find(uid) != SysTray::iconList.end()) {
        if(::Shell_NotifyIcon(NIM_DELETE, &SysTray::iconList[uid]) != 0) {
            SysTray::iconList.erase(uid);
            SysTray::menuList.erase(uid);
            SysTray::optionsList.erase(uid);

            return true;
        }
    }

    return false;
}

///\brief
///Shows a balloon tip over the tray icon
///
///\note
///Windows XP is required for this feature!
///
///\param uid
///The Icon ID
///
///\param title
///Balloon title
///
///\param text
///Balloon inner text
///
///\param timeout
///Time to show the balloon in seconds. There are special restrictions defined
///by the Windows-API. Look here:
///http://msdn.microsoft.com/en-us/library/bb773352(VS.85).aspx
///
///\param text
///Icon to show. Select from NIIF_NONE, NIIF_INFO, NIIF_WARNING, NIIF_ERROR
///
///\return bool
bool SysTray::ShowBalloon(UINT uid, const uistring& title, const uistring& text, UINT timeout, UINT icon)
{
    if(SysTray::iconList.find(uid) != SysTray::iconList.end()) {
        SysTray::iconList[uid].uFlags |= NIF_INFO;
        SysTray::iconList[uid].uTimeout = timeout * 1000;
        SysTray::iconList[uid].dwInfoFlags = icon;

        ::wcsncpy_s(SysTray::iconList[uid].szInfoTitle, 64, title.c_str(), 64);
        ::wcsncpy_s(SysTray::iconList[uid].szInfo, 256, text.c_str(), 256);

        return (::Shell_NotifyIcon(NIM_MODIFY, &SysTray::iconList[uid]) != 0);
    }

    return false;
}

///\brief
///Sets a new icon for the specified tray icon.
///
///\param uid
///The Icon ID
///
///\param icon
///New Icon
///
///\return bool
bool SysTray::SetIcon(UINT uid, HICON icon)
{
    if(SysTray::iconList.find(uid) != SysTray::iconList.end()) {
        SysTray::iconList[uid].hIcon = icon;
        return (::Shell_NotifyIcon(NIM_MODIFY, &SysTray::iconList[uid]) != 0);
    }

    return false;
}

///\brief
///Sets the tooltip for the specified tray icon.
///
///\param uid
///The Icon ID
///
///\param tooltip
///Tooltip to show
///
///\return bool
bool SysTray::SetTooltip(UINT uid, const uistring& tooltip)
{
    if(SysTray::iconList.find(uid) != SysTray::iconList.end()) {
        SysTray::iconList[uid].uFlags |= NIF_TIP;
        ::wcsncpy_s(SysTray::iconList[uid].szTip, 128, tooltip.c_str(), 128);
        return (::Shell_NotifyIcon(NIM_MODIFY, &SysTray::iconList[uid]) != 0);
    }

    return false;
}

///\brief
///Sets the popup menu for the specified tray icon.
///
///\param uid
///The Icon ID
///
///\param menu
///Reference to the menu which should be displayed
///
///\return bool
bool SysTray::SetPopupMenu(UINT uid, MenuRef menu)
{
    if(menu) {
        SysTray::menuList[uid] = menu;
        return true;
    }

    return false;
}

///\brief
///Window procedure for all SysTray handling. Here WM_RBUTTONDOWN (for popup menu),
///WM_LBUTTONDOWN (for restoring the window after it has been minimized to tray) &
///WM_SIZE for the Minimize to tray feature are handled.
///
///\param window
///Affected window handle
///
///\param message
///Message-ID
///
///\param wParam
///\param lParam
///\return LRESULT
///In this case always 0
LRESULT SysTray::WindowProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam)
{
    if(SysTray::menuList.find(message - WM_W32CPP_SYSTRAY) != SysTray::menuList.end()) {
        UINT uid = message - WM_W32CPP_SYSTRAY;

        switch(LOWORD(lParam)) {
        case WM_RBUTTONDOWN:
            {
                if(SysTray::menuList.find(uid) != SysTray::menuList.end()) {
                    POINT mousePos = { 0 };
                    ::GetCursorPos(&mousePos);

                    TrackPopupMenu(
                        SysTray::menuList[uid]->Handle(),
                        TPM_LEFTBUTTON,
                        mousePos.x,
                        mousePos.y,
                        NULL,
                        SysTray::iconList[uid].hWnd,
                        NULL);
                }
            }
            return 0;
        case WM_LBUTTONDOWN:
            {
                 if(SysTray::optionsList[uid] & SysTray::MINIMIZE_TO_TRAY) {
                    // get window object
                    Window* wnd = Window::SubclassedWindowFromHWND(SysTray::iconList[uid].hWnd);

                    // restore window
                    wnd->Show(SW_SHOW);
                    wnd->Show(SW_RESTORE);

                 }
            }
            return 0;
        }
    }

    // handle minimize to tray
    if(message == WM_SIZE && wParam == SIZE_MINIMIZED) {
        // iterate through list with icon options and look, if the assigned icon/window pair wants that
        for(IconList::iterator i = SysTray::iconList.begin(); i != SysTray::iconList.end(); ++i) {
            // look if there is a corresponding window
            if(i->second.hWnd == window) {
                if(SysTray::optionsList[i->second.uID] & SysTray::MINIMIZE_TO_TRAY) {
                    // get window object
                    Window* wnd = Window::SubclassedWindowFromHWND(window);

                    // and finally minimize it to tray
                    wnd->Show(SW_SHOWMINIMIZED);
                    wnd->Show(SW_HIDE);
                }
            }
        }
    }

    return 0;
}

///\brief
///Activates minimize to tray for the icon specified by uid.
///
///\param uid
///The Icon ID
void SysTray::EnableMinimizeToTray(UINT uid)
{
    SysTray::optionsList[uid] |= SysTray::MINIMIZE_TO_TRAY;
}

///\brief
///Creates and add a notify icon
///
///\param window
///The window to associate the icon with
///
///\param icon
///The Icon to show as HICON
///
///\param tooltip
///The tooltip to show. Leave it empty to show no tooltip
///
///\return int
///Returns the new Icon ID or -1 on failure
int SysTray::AddIcon(Window* window, HICON icon, const uistring& tooltip)
{
    UINT uid = SysTray::uidCounter++;
    NOTIFYICONDATA nid;

    // setup notifyicondata structure
    ::ZeroMemory(&nid, sizeof(nid));
    nid.cbSize  = sizeof(NOTIFYICONDATA);
    nid.hWnd    = window->Handle();
    nid.uID     = uid;
    nid.uFlags  = NIF_MESSAGE | NIF_ICON;
    if(tooltip != _T("")) {
        nid.uFlags |= NIF_TIP;
        ::wcsncpy_s(nid.szTip, 64, tooltip.c_str(), 64);
    }
    nid.hIcon   = icon;

    nid.uCallbackMessage = WM_W32CPP_SYSTRAY + uid;

    // create icon
    if(!::Shell_NotifyIcon(NIM_ADD, &nid)) {
        return -1;
    }

    nid.uVersion = NOTIFYICON_VERSION;
    if(!::Shell_NotifyIcon(NIM_SETVERSION, &nid)) {
        return -1;
    }

    // add to icon list
    SysTray::iconList[uid] = nid;

    // add to options list
    SysTray::optionsList[uid] = 0;

    return uid;
}<|endoftext|>


<commit_before>/*
 * SessionRMarkdown.cpp
 *
 * Copyright (C) 2009-14 by RStudio, Inc.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#include "SessionRMarkdown.hpp"

#include <boost/algorithm/string/predicate.hpp>
#include <boost/iostreams/filter/regex.hpp>
#include <boost/format.hpp>

#include <core/FileSerializer.hpp>
#include <core/Exec.hpp>
#include <core/system/Process.hpp>

#include <r/RExec.hpp>
#include <r/RJson.hpp>

#include <session/SessionModuleContext.hpp>
#include "SessionRPubs.hpp"

#define kRmdOutput "rmd_output"
#define kRmdOutputLocation "/" kRmdOutput "/"

#define kMathjaxSegment "mathjax"

using namespace core;

namespace session {
namespace modules {
namespace rmarkdown {

namespace {

class RenderRmd : boost::noncopyable,
                  public boost::enable_shared_from_this<RenderRmd>
{
public:
   static boost::shared_ptr<RenderRmd> create(const FilePath& targetFile,
                                              const std::string& encoding)
   {
      boost::shared_ptr<RenderRmd> pRender(new RenderRmd(targetFile));
      pRender->start(encoding);
      return pRender;
   }

   void terminate()
   {
      terminationRequested_ = true;
   }

   bool isRunning()
   {
      return isRunning_;
   }

   FilePath outputFile()
   {
      return outputFile_;
   }

   FilePath targetDirectory()
   {
      return outputFile_.parent();
   }

   bool hasOutput()
   {
      return !isRunning_ && outputFile_.exists();
   }

private:
   RenderRmd(const FilePath& targetFile) :
      isRunning_(false),
      terminationRequested_(false),
      targetFile_(targetFile)
   {}

   void start(const std::string& encoding)
   {
      json::Object dataJson;
      dataJson["target_file"] = module_context::createAliasedPath(targetFile_);
      ClientEvent event(client_events::kRmdRenderStarted, dataJson);
      module_context::enqueClientEvent(event);
      isRunning_ = true;

      performRender(encoding);
   }

   void performRender(const std::string& encoding)
   {
      // save encoding
      encoding_ = encoding;

      // R binary
      FilePath rProgramPath;
      Error error = module_context::rScriptPath(&rProgramPath);
      if (error)
      {
         terminateWithError(error);
         return;
      }

      // args
      std::vector<std::string> args;
      args.push_back("--slave");
      args.push_back("--no-save");
      args.push_back("--no-restore");
      args.push_back("-e");

      // render command
      boost::format fmt("rmarkdown::render('%1%', encoding='%2%');");
      std::string cmd = boost::str(fmt % targetFile_.filename() % encoding);
      args.push_back(cmd);

      // options
      core::system::ProcessOptions options;
      options.terminateChildren = true;
      options.workingDir = targetFile_.parent();

      core::system::ProcessCallbacks cb;
      using namespace module_context;
      cb.onContinue = boost::bind(&RenderRmd::onRenderContinue,
                                  RenderRmd::shared_from_this());
      cb.onStdout = boost::bind(&RenderRmd::onRenderOutput,
                                RenderRmd::shared_from_this(),
                                kCompileOutputNormal,
                                _2);
      cb.onStderr = boost::bind(&RenderRmd::onRenderOutput,
                                RenderRmd::shared_from_this(),
                                kCompileOutputError,
                                _2);
      cb.onExit =  boost::bind(&RenderRmd::onRenderCompleted,
                                RenderRmd::shared_from_this(), _1, encoding);

      module_context::processSupervisor().runProgram(rProgramPath.absolutePath(),
                                                     args,
                                                     options,
                                                     cb);
   }

   bool onRenderContinue()
   {
      return !terminationRequested_;
   }

   void onRenderOutput(int type, const std::string& output)
   {
      // check each line of the emitted output; if it starts with a token
      // indicating rendering is complete, store the remainder of the emitted
      // line as the file we rendered
      std::string completeMarker("Output created: ");
      std::string renderLine;
      std::stringstream outputStream(output);
      while (std::getline(outputStream, renderLine))
      {
         if (boost::algorithm::starts_with(renderLine, completeMarker))
         {
            std::string fileName = renderLine.substr(completeMarker.length());
            // if the path looks absolute, use it as-is; otherwise, presume
            // it to be in the same directory as the input file
            outputFile_ = targetFile_.parent().complete(fileName);
            break;
         }
      }
      enqueRenderOutput(type, output);
   }

   void onRenderCompleted(int exitStatus, const std::string& encoding)
   {
      // consider the render to be successful if R doesn't return an error,
      // and an output file was written
      terminate(exitStatus == 0 &&
                outputFile_.exists());
   }

   void terminateWithError(const Error& error)
   {
      std::string message =
            "Error rendering R Markdown for " +
            module_context::createAliasedPath(targetFile_) + " " +
            error.summary();
      terminateWithError(message);
   }

   void terminateWithError(const std::string& message)
   {
      enqueRenderOutput(module_context::kCompileOutputError, message);
      terminate(false);
   }

   void terminate(bool succeeded)
   {
      isRunning_ = false;
      json::Object resultJson;
      resultJson["succeeded"] = succeeded;
      resultJson["output_file"] =
            module_context::createAliasedPath(outputFile_);
      resultJson["output_url"] = kRmdOutput "/";

      // for HTML documents, check to see whether they've been published
      if (outputFile_.extensionLowerCase() == ".html")
      {
         resultJson["rpubs_published"] =
               !rpubs::previousUploadId(outputFile_).empty();
      }
      else
      {
         resultJson["rpubs_published"] = false;
      }

      // query rmarkdown for the output format
      r::sexp::Protect protect;
      SEXP sexpOutputFormat;
      Error error = r::exec::RFunction("rmarkdown:::default_output_format",
                                       targetFile_.absolutePath(),
                                       encoding_)
                                      .call(&sexpOutputFormat, &protect);
      if (error)
      {
         LOG_ERROR(error);
         resultJson["output_format"] = "";
         resultJson["output_options"] = json::Value();
      }
      else
      {
         std::string formatName;
         error = r::sexp::getNamedListElement(sexpOutputFormat, "name",
                                              &formatName);
         if (error)
            LOG_ERROR(error);
         resultJson["output_format"] = formatName;

         SEXP sexpOptions;
         json::Value formatOptions;
         error = r::sexp::getNamedListSEXP(sexpOutputFormat, "options",
                                           &sexpOptions);
         if (error)
            LOG_ERROR(error);
         else
         {
            error = r::json::jsonValueFromList(sexpOptions, &formatOptions);
            if (error)
               LOG_ERROR(error);
         }
         resultJson["output_options"] = formatOptions;
      }
      ClientEvent event(client_events::kRmdRenderCompleted, resultJson);
      module_context::enqueClientEvent(event);
   }

   static void enqueRenderOutput(int type,
                                 const std::string& output)
   {
      using namespace module_context;
      CompileOutput compileOutput(type, output);
      ClientEvent event(client_events::kRmdRenderOutput,
                        compileOutputAsJson(compileOutput));
      module_context::enqueClientEvent(event);
   }

   bool isRunning_;
   bool terminationRequested_;
   FilePath targetFile_;
   FilePath outputFile_;
   std::string encoding_;
};

boost::shared_ptr<RenderRmd> s_pCurrentRender_;

// replaces references to MathJax with references to our built-in resource
// handler.
// in:  script src = "http://foo/bar/Mathjax.js?abc=123"
// out: script src = "mathjax/MathJax.js?abc=123"
class MathjaxFilter : public boost::iostreams::regex_filter
{
public:
   MathjaxFilter()
      : boost::iostreams::regex_filter(
            boost::regex("script.src\\s*=\\s*\"http.*?(MathJax.js[^\"]*)\""),
            boost::bind(&MathjaxFilter::substitute, this, _1))
   {
   }

private:
   std::string substitute(const boost::cmatch& match)
   {
      std::string result("script.src = \"" kMathjaxSegment "/");
      result.append(match[1]);
      result.append("\"");
      return result;
   }
};

bool isRenderRunning()
{
   return s_pCurrentRender_ && s_pCurrentRender_->isRunning();
}

void initPandocPath()
{
   r::exec::RFunction sysSetenv("Sys.setenv");
   sysSetenv.addParam("RSTUDIO_PANDOC",
                      session::options().pandocPath().absolutePath());
   Error error = sysSetenv.call();
   if (error)
      LOG_ERROR(error);
}

// when the RMarkdown package is installed, give .Rmd files the extended type
// "rmarkdown", unless they contain a special marker that indicates we should
// use the previous rendering strategy
std::string onDetectRmdSourceType(
      boost::shared_ptr<source_database::SourceDocument> pDoc)
{
   if (!pDoc->path().empty())
   {
      FilePath filePath = module_context::resolveAliasedPath(pDoc->path());
      if (filePath.extensionLowerCase() == ".rmd" &&
          !boost::algorithm::icontains(pDoc->contents(),
                                       "<!-- rmarkdown v1 -->"))
      {
         return "rmarkdown";
      }
   }
   return std::string();
}

Error renderRmd(const json::JsonRpcRequest& request,
                json::JsonRpcResponse* pResponse)
{
   std::string file, encoding;
   Error error = json::readParams(request.params, &file, &encoding);
   if (error)
      return error;

   if (s_pCurrentRender_ &&
       s_pCurrentRender_->isRunning())
   {
      pResponse->setResult(false);
   }
   else
   {
      s_pCurrentRender_ = RenderRmd::create(
               module_context::resolveAliasedPath(file),
               encoding);
      pResponse->setResult(true);
   }

   return Success();
}

Error terminateRenderRmd(const json::JsonRpcRequest&,
                         json::JsonRpcResponse*)
{
   if (isRenderRunning())
      s_pCurrentRender_->terminate();

   return Success();
}

// return the path to the local copy of MathJax installed with the RMarkdown
// package
FilePath mathJaxDirectory()
{
   std::string path;
   FilePath mathJaxDir;

   // call system.file to find the appropriate path
   r::exec::RFunction findMathJax("system.file", "rmd/h/m");
   findMathJax.addParam("package", "rmarkdown");
   Error error = findMathJax.call(&path);

   // we don't expect this to fail since we shouldn't be here if RMarkdown
   // is not installed at the correct verwion
   if (error)
      LOG_ERROR(error);
   else
      mathJaxDir = FilePath(path);
   return mathJaxDir;
}

void handleRmdOutputRequest(const http::Request& request,
                            http::Response* pResponse)
{
   // make sure we're looking at the result of a successful render
   if (!s_pCurrentRender_ ||
       !s_pCurrentRender_->hasOutput())
   {
      pResponse->setError(http::status::NotFound, "No render output available");
      return;
   }

   // get the requested path
   std::string path = http::util::pathAfterPrefix(request,
                                                  kRmdOutputLocation);

   if (path.empty())
   {
      // disable caching; the request path looks identical to the browser for
      // each main request for content
      pResponse->setNoCacheHeaders();

      // serve the contents of the file with MathJax URLs mapped to our
      // own resource handler
      MathjaxFilter mathjaxFilter;
      pResponse->setFile(s_pCurrentRender_->outputFile(), request,
                         mathjaxFilter);
   }
   else if (boost::algorithm::starts_with(path, kMathjaxSegment))
   {
      // serve the MathJax resource: find the requested path in the MathJax
      // directory
      pResponse->setCacheableFile(mathJaxDirectory().complete(
                                    path.substr(sizeof(kMathjaxSegment))),
                                  request);
   }
   else
   {
      // serve a file resource from the output folder
      FilePath filePath = s_pCurrentRender_->targetDirectory().childPath(path);
      pResponse->setCacheableFile(filePath, request);
   }
}

} // anonymous namespace

bool rmarkdownPackageInstalled()
{
   return module_context::isPackageVersionInstalled("rmarkdown", "0.1.1");
}

Error initialize()
{
   using namespace module_context;

   initPandocPath();

   if (module_context::isPackageVersionInstalled("rmarkdown", "0.1.1"))
      module_context::events().onDetectSourceExtendedType
                              .connect(onDetectRmdSourceType);

   ExecBlock initBlock;
   initBlock.addFunctions()
      (bind(registerRpcMethod, "render_rmd", renderRmd))
      (bind(registerRpcMethod, "terminate_render_rmd", terminateRenderRmd))
      (bind(registerUriHandler, kRmdOutputLocation, handleRmdOutputRequest));

   return initBlock.execute();
}

} // namepsace rmarkdown
} // namespace modules
} // namesapce session

<commit_msg>detect R markdown render output file correctly on Windows<commit_after>/*
 * SessionRMarkdown.cpp
 *
 * Copyright (C) 2009-14 by RStudio, Inc.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#include "SessionRMarkdown.hpp"

#include <boost/algorithm/string/predicate.hpp>
#include <boost/iostreams/filter/regex.hpp>
#include <boost/format.hpp>

#include <core/FileSerializer.hpp>
#include <core/Exec.hpp>
#include <core/system/Process.hpp>

#include <r/RExec.hpp>
#include <r/RJson.hpp>

#include <session/SessionModuleContext.hpp>
#include "SessionRPubs.hpp"

#define kRmdOutput "rmd_output"
#define kRmdOutputLocation "/" kRmdOutput "/"

#define kMathjaxSegment "mathjax"

using namespace core;

namespace session {
namespace modules {
namespace rmarkdown {

namespace {

class RenderRmd : boost::noncopyable,
                  public boost::enable_shared_from_this<RenderRmd>
{
public:
   static boost::shared_ptr<RenderRmd> create(const FilePath& targetFile,
                                              const std::string& encoding)
   {
      boost::shared_ptr<RenderRmd> pRender(new RenderRmd(targetFile));
      pRender->start(encoding);
      return pRender;
   }

   void terminate()
   {
      terminationRequested_ = true;
   }

   bool isRunning()
   {
      return isRunning_;
   }

   FilePath outputFile()
   {
      return outputFile_;
   }

   FilePath targetDirectory()
   {
      return outputFile_.parent();
   }

   bool hasOutput()
   {
      return !isRunning_ && outputFile_.exists();
   }

private:
   RenderRmd(const FilePath& targetFile) :
      isRunning_(false),
      terminationRequested_(false),
      targetFile_(targetFile)
   {}

   void start(const std::string& encoding)
   {
      json::Object dataJson;
      dataJson["target_file"] = module_context::createAliasedPath(targetFile_);
      ClientEvent event(client_events::kRmdRenderStarted, dataJson);
      module_context::enqueClientEvent(event);
      isRunning_ = true;

      performRender(encoding);
   }

   void performRender(const std::string& encoding)
   {
      // save encoding
      encoding_ = encoding;

      // R binary
      FilePath rProgramPath;
      Error error = module_context::rScriptPath(&rProgramPath);
      if (error)
      {
         terminateWithError(error);
         return;
      }

      // args
      std::vector<std::string> args;
      args.push_back("--slave");
      args.push_back("--no-save");
      args.push_back("--no-restore");
      args.push_back("-e");

      // render command
      boost::format fmt("rmarkdown::render('%1%', encoding='%2%');");
      std::string cmd = boost::str(fmt % targetFile_.filename() % encoding);
      args.push_back(cmd);

      // options
      core::system::ProcessOptions options;
      options.terminateChildren = true;
      options.workingDir = targetFile_.parent();

      core::system::ProcessCallbacks cb;
      using namespace module_context;
      cb.onContinue = boost::bind(&RenderRmd::onRenderContinue,
                                  RenderRmd::shared_from_this());
      cb.onStdout = boost::bind(&RenderRmd::onRenderOutput,
                                RenderRmd::shared_from_this(),
                                kCompileOutputNormal,
                                _2);
      cb.onStderr = boost::bind(&RenderRmd::onRenderOutput,
                                RenderRmd::shared_from_this(),
                                kCompileOutputError,
                                _2);
      cb.onExit =  boost::bind(&RenderRmd::onRenderCompleted,
                                RenderRmd::shared_from_this(), _1, encoding);

      module_context::processSupervisor().runProgram(rProgramPath.absolutePath(),
                                                     args,
                                                     options,
                                                     cb);
   }

   bool onRenderContinue()
   {
      return !terminationRequested_;
   }

   void onRenderOutput(int type, const std::string& output)
   {
      // check each line of the emitted output; if it starts with a token
      // indicating rendering is complete, store the remainder of the emitted
      // line as the file we rendered
      std::string completeMarker("Output created: ");
      std::string renderLine;
      std::stringstream outputStream(output);
      while (std::getline(outputStream, renderLine))
      {
         if (boost::algorithm::starts_with(renderLine, completeMarker))
         {
            std::string fileName = renderLine.substr(completeMarker.length());

            // trim any whitespace from the end of the filename (on Windows this
            // includes part of CR-LF)
            boost::algorithm::trim(fileName);

            // if the path looks absolute, use it as-is; otherwise, presume
            // it to be in the same directory as the input file
            outputFile_ = targetFile_.parent().complete(fileName);
            break;
         }
      }
      enqueRenderOutput(type, output);
   }

   void onRenderCompleted(int exitStatus, const std::string& encoding)
   {
      // consider the render to be successful if R doesn't return an error,
      // and an output file was written
      terminate(exitStatus == 0 &&
                outputFile_.exists());
   }

   void terminateWithError(const Error& error)
   {
      std::string message =
            "Error rendering R Markdown for " +
            module_context::createAliasedPath(targetFile_) + " " +
            error.summary();
      terminateWithError(message);
   }

   void terminateWithError(const std::string& message)
   {
      enqueRenderOutput(module_context::kCompileOutputError, message);
      terminate(false);
   }

   void terminate(bool succeeded)
   {
      isRunning_ = false;
      json::Object resultJson;
      resultJson["succeeded"] = succeeded;
      resultJson["output_file"] =
            module_context::createAliasedPath(outputFile_);
      resultJson["output_url"] = kRmdOutput "/";

      // for HTML documents, check to see whether they've been published
      if (outputFile_.extensionLowerCase() == ".html")
      {
         resultJson["rpubs_published"] =
               !rpubs::previousUploadId(outputFile_).empty();
      }
      else
      {
         resultJson["rpubs_published"] = false;
      }

      // query rmarkdown for the output format
      r::sexp::Protect protect;
      SEXP sexpOutputFormat;
      Error error = r::exec::RFunction("rmarkdown:::default_output_format",
                                       targetFile_.absolutePath(),
                                       encoding_)
                                      .call(&sexpOutputFormat, &protect);
      if (error)
      {
         LOG_ERROR(error);
         resultJson["output_format"] = "";
         resultJson["output_options"] = json::Value();
      }
      else
      {
         std::string formatName;
         error = r::sexp::getNamedListElement(sexpOutputFormat, "name",
                                              &formatName);
         if (error)
            LOG_ERROR(error);
         resultJson["output_format"] = formatName;

         SEXP sexpOptions;
         json::Value formatOptions;
         error = r::sexp::getNamedListSEXP(sexpOutputFormat, "options",
                                           &sexpOptions);
         if (error)
            LOG_ERROR(error);
         else
         {
            error = r::json::jsonValueFromList(sexpOptions, &formatOptions);
            if (error)
               LOG_ERROR(error);
         }
         resultJson["output_options"] = formatOptions;
      }
      ClientEvent event(client_events::kRmdRenderCompleted, resultJson);
      module_context::enqueClientEvent(event);
   }

   static void enqueRenderOutput(int type,
                                 const std::string& output)
   {
      using namespace module_context;
      CompileOutput compileOutput(type, output);
      ClientEvent event(client_events::kRmdRenderOutput,
                        compileOutputAsJson(compileOutput));
      module_context::enqueClientEvent(event);
   }

   bool isRunning_;
   bool terminationRequested_;
   FilePath targetFile_;
   FilePath outputFile_;
   std::string encoding_;
};

boost::shared_ptr<RenderRmd> s_pCurrentRender_;

// replaces references to MathJax with references to our built-in resource
// handler.
// in:  script src = "http://foo/bar/Mathjax.js?abc=123"
// out: script src = "mathjax/MathJax.js?abc=123"
class MathjaxFilter : public boost::iostreams::regex_filter
{
public:
   MathjaxFilter()
      : boost::iostreams::regex_filter(
            boost::regex("script.src\\s*=\\s*\"http.*?(MathJax.js[^\"]*)\""),
            boost::bind(&MathjaxFilter::substitute, this, _1))
   {
   }

private:
   std::string substitute(const boost::cmatch& match)
   {
      std::string result("script.src = \"" kMathjaxSegment "/");
      result.append(match[1]);
      result.append("\"");
      return result;
   }
};

bool isRenderRunning()
{
   return s_pCurrentRender_ && s_pCurrentRender_->isRunning();
}

void initPandocPath()
{
   r::exec::RFunction sysSetenv("Sys.setenv");
   sysSetenv.addParam("RSTUDIO_PANDOC",
                      session::options().pandocPath().absolutePath());
   Error error = sysSetenv.call();
   if (error)
      LOG_ERROR(error);
}

// when the RMarkdown package is installed, give .Rmd files the extended type
// "rmarkdown", unless they contain a special marker that indicates we should
// use the previous rendering strategy
std::string onDetectRmdSourceType(
      boost::shared_ptr<source_database::SourceDocument> pDoc)
{
   if (!pDoc->path().empty())
   {
      FilePath filePath = module_context::resolveAliasedPath(pDoc->path());
      if (filePath.extensionLowerCase() == ".rmd" &&
          !boost::algorithm::icontains(pDoc->contents(),
                                       "<!-- rmarkdown v1 -->"))
      {
         return "rmarkdown";
      }
   }
   return std::string();
}

Error renderRmd(const json::JsonRpcRequest& request,
                json::JsonRpcResponse* pResponse)
{
   std::string file, encoding;
   Error error = json::readParams(request.params, &file, &encoding);
   if (error)
      return error;

   if (s_pCurrentRender_ &&
       s_pCurrentRender_->isRunning())
   {
      pResponse->setResult(false);
   }
   else
   {
      s_pCurrentRender_ = RenderRmd::create(
               module_context::resolveAliasedPath(file),
               encoding);
      pResponse->setResult(true);
   }

   return Success();
}

Error terminateRenderRmd(const json::JsonRpcRequest&,
                         json::JsonRpcResponse*)
{
   if (isRenderRunning())
      s_pCurrentRender_->terminate();

   return Success();
}

// return the path to the local copy of MathJax installed with the RMarkdown
// package
FilePath mathJaxDirectory()
{
   std::string path;
   FilePath mathJaxDir;

   // call system.file to find the appropriate path
   r::exec::RFunction findMathJax("system.file", "rmd/h/m");
   findMathJax.addParam("package", "rmarkdown");
   Error error = findMathJax.call(&path);

   // we don't expect this to fail since we shouldn't be here if RMarkdown
   // is not installed at the correct verwion
   if (error)
      LOG_ERROR(error);
   else
      mathJaxDir = FilePath(path);
   return mathJaxDir;
}

void handleRmdOutputRequest(const http::Request& request,
                            http::Response* pResponse)
{
   // make sure we're looking at the result of a successful render
   if (!s_pCurrentRender_ ||
       !s_pCurrentRender_->hasOutput())
   {
      pResponse->setError(http::status::NotFound, "No render output available");
      return;
   }

   // get the requested path
   std::string path = http::util::pathAfterPrefix(request,
                                                  kRmdOutputLocation);

   if (path.empty())
   {
      // disable caching; the request path looks identical to the browser for
      // each main request for content
      pResponse->setNoCacheHeaders();

      // serve the contents of the file with MathJax URLs mapped to our
      // own resource handler
      MathjaxFilter mathjaxFilter;
      pResponse->setFile(s_pCurrentRender_->outputFile(), request,
                         mathjaxFilter);
   }
   else if (boost::algorithm::starts_with(path, kMathjaxSegment))
   {
      // serve the MathJax resource: find the requested path in the MathJax
      // directory
      pResponse->setCacheableFile(mathJaxDirectory().complete(
                                    path.substr(sizeof(kMathjaxSegment))),
                                  request);
   }
   else
   {
      // serve a file resource from the output folder
      FilePath filePath = s_pCurrentRender_->targetDirectory().childPath(path);
      pResponse->setCacheableFile(filePath, request);
   }
}

} // anonymous namespace

bool rmarkdownPackageInstalled()
{
   return module_context::isPackageVersionInstalled("rmarkdown", "0.1.1");
}

Error initialize()
{
   using namespace module_context;

   initPandocPath();

   if (module_context::isPackageVersionInstalled("rmarkdown", "0.1.1"))
      module_context::events().onDetectSourceExtendedType
                              .connect(onDetectRmdSourceType);

   ExecBlock initBlock;
   initBlock.addFunctions()
      (bind(registerRpcMethod, "render_rmd", renderRmd))
      (bind(registerRpcMethod, "terminate_render_rmd", terminateRenderRmd))
      (bind(registerUriHandler, kRmdOutputLocation, handleRmdOutputRequest));

   return initBlock.execute();
}

} // namepsace rmarkdown
} // namespace modules
} // namesapce session

<|endoftext|>


<commit_before>// This file is part of the "x0" project, http://github.com/christianparpart/x0>
//   (c) 2009-2017 Christian Parpart <christian@parpart.family>
//
// Licensed under the MIT License (the "License"); you may not use this
// file except in compliance with the License. You may obtain a copy of
// the License at: http://opensource.org/licenses/MIT

/*
 * Noteworthy
 *
 * - does not forward Expect request header to upstream (does nginx/apache?)
 */

#include "proxy.h"
#include <x0d/Context.h>
#include <xzero/sysconfig.h>
#include <xzero/http/proxy/HttpCluster.h>
#include <xzero/http/proxy/HttpClusterRequest.h>
#include <xzero/http/proxy/HttpClusterApiHandler.h>
#include <xzero/http/client/HttpClient.h>
#include <xzero/http/HttpRequest.h>
#include <xzero/http/HttpResponse.h>
#include <xzero/http/HeaderFieldList.h>
#include <xzero/net/TcpEndPoint.h>
#include <xzero/io/FileUtil.h>
#include <xzero/WallClock.h>
#include <xzero/StringUtil.h>
#include <xzero/RuntimeError.h>
#include <xzero/logging.h>
#include <xzero/Application.h>
#include <xzero-flow/AST.h>
#include <xzero-flow/ir/Instr.h>
#include <xzero-flow/ir/BasicBlock.h>
#include <xzero-flow/ir/IRHandler.h>
#include <xzero-flow/ir/IRProgram.h>
#include <xzero-flow/ir/ConstantValue.h>
#include <xzero-flow/ir/ConstantArray.h>

namespace x0d {

using namespace xzero;
using namespace xzero::http;
using namespace xzero::flow;

using xzero::http::client::HttpClusterApiHandler;
using xzero::http::client::HttpClusterRequest;
using xzero::http::client::HttpCluster;
using xzero::http::client::HttpClient;

#define TRACE(msg...) logTrace(msg)
#define DEBUG(msg...) logDebug(msg)

template<typename T>
static bool isConnectionHeader(const T& name) {
  static const std::vector<T> connectionHeaderFields = {
    "Connection",
    "Content-Length",
    "Close",
    "Keep-Alive",
    "TE",
    "Trailer",
    "Transfer-Encoding",
    "Upgrade",
  };

  for (const auto& test: connectionHeaderFields)
    if (iequals(name, test))
      return true;

  return false;
}

ProxyModule::ProxyModule(Daemon* d)
    : Module(d, "proxy"),
      pseudonym_("x0d"),
      clusterInit_(),
      clusterMap_() {

  setupFunction("proxy.pseudonym", &ProxyModule::proxy_pseudonym,
                FlowType::String);

  mainHandler("proxy.cluster", &ProxyModule::proxy_cluster_auto);

  mainHandler("proxy.cluster", &ProxyModule::proxy_cluster)
      .param<FlowString>("name")
      .param<FlowString>("path", "")
      .param<FlowString>("bucket", "")
      .param<FlowString>("backend", "")
      .verifier(&ProxyModule::verify_proxy_cluster, this);

  mainHandler("proxy.api", &ProxyModule::proxy_api)
      .param<FlowString>("prefix", "/");

  mainHandler("proxy.fcgi", &ProxyModule::proxy_fcgi)
      .verifier(&ProxyModule::proxy_roadwarrior_verify, this)
      .param<FlowString>("on_client_abort", "close")
      .param<IPAddress>("address", IPAddress("0.0.0.0"))
      .param<int>("port");

  mainHandler("proxy.http", &ProxyModule::proxy_http)
      .verifier(&ProxyModule::proxy_roadwarrior_verify, this)
      .param<FlowString>("on_client_abort", "close")
      .param<IPAddress>("address")
      .param<int>("port")
      .param<int>("connect_timeout", 10)
      .param<int>("read_timeout", 60)
      .param<int>("write_timeout", 10);

  mainFunction("proxy.cache", &ProxyModule::proxy_cache)
      .param<bool>("enabled", true)
      .param<FlowString>("key", "")
      .param<int>("ttl", 0);
}

ProxyModule::~ProxyModule() {
}

void ProxyModule::proxy_pseudonym(xzero::flow::Params& args) {
  std::string value = args.getString(1);

  for (char ch: value) {
    if (!std::isalnum(ch) && ch != '_' && ch != '-' && ch != '.') {
      throw ConfigurationError{"Invalid character found in proxy.pseudonym"};
    }
  }

  pseudonym_ = value;
}

bool ProxyModule::verify_proxy_cluster(xzero::flow::Instr* call, xzero::flow::IRBuilder* builder) {
  auto program = call->getBasicBlock()->getHandler()->getProgram();

  auto nameArg = dynamic_cast<ConstantString*>(call->operand(1));
  if (nameArg == nullptr) {
    logError("proxy.cluster: name parameter must be a literal.");
    return false;
  }

  if (nameArg->get().empty()) {
    logError("Setting empty proxy.cluster name is not allowed.");
    return false;
  }

  // pathArg
  auto pathArg = dynamic_cast<ConstantString*>(call->operand(2));
  if (pathArg == nullptr) {
    logError("proxy.cluster: path parameter must be a literal.");
    return false;
  }

  std::string path = pathArg->get().empty()
      ? FileUtil::joinPaths(XZERO_CLUSTERDIR, nameArg->get() + ".cluster.conf")
      : pathArg->get();

  call->setOperand(2, program->get(path));

  clusterInit_[nameArg->get()] = path;

  return true;
}

void ProxyModule::onPostConfig() {
  using client::HttpCluster;

  TRACE("clusterInit count: $0", clusterInit_.size());
  for (const auto& init: clusterInit_) {
    TRACE("clusterInit: spawning $0", init.first);
    std::string name = init.first;
    std::string path = init.second;

    createCluster(name, path);
  }
  clusterInit_.clear();
}

class HttpResponseBuilder : public HttpListener { // {{{
 public:
  explicit HttpResponseBuilder(HttpResponse* response);

  void onMessageBegin(HttpVersion version, HttpStatus code, const BufferRef& text) override;
  void onMessageHeader(const BufferRef& name, const BufferRef& value) override;
  void onMessageHeaderEnd() override;
  void onMessageContent(const BufferRef& chunk) override;
  void onMessageContent(FileView&& chunk) override;
  void onMessageEnd() override;
  void onError(std::error_code ec) override;

 private:
  HttpResponse* response_;
};

HttpResponseBuilder::HttpResponseBuilder(HttpResponse* response)
    : response_(response) {
}

void HttpResponseBuilder::onMessageBegin(HttpVersion version, HttpStatus code, const BufferRef& text) {
  response_->setStatus(code);
  response_->setReason(text.str());
}

void HttpResponseBuilder::onMessageHeader(const BufferRef& name, const BufferRef& value) {
  if (iequals(name, "Content-Length")) {
    response_->setContentLength(value.toInt());
  } else if (!isConnectionHeader(name)) {
    response_->addHeader(name.str(), value.str());
  }
}

void HttpResponseBuilder::onMessageHeaderEnd() {
}

void HttpResponseBuilder::onMessageContent(const BufferRef& chunk) {
  response_->write(Buffer(chunk));
}

void HttpResponseBuilder::onMessageContent(FileView&& chunk) {
  response_->write(std::move(chunk));
}

void HttpResponseBuilder::onMessageEnd() {
  response_->completed();
}

void HttpResponseBuilder::onError(std::error_code ec) {
  if (ec.category() == HttpStatusCategory::get()) {
    response_->sendError(static_cast<HttpStatus>(ec.value()));
  } else {
    logError("proxy: Unhandled error in response builder. $0", ec.message());
    response_->sendError(HttpStatus::InternalServerError);
  }
}
// }}}

HttpCluster* ProxyModule::findLocalCluster(const std::string& host) {
  auto i = clusterMap_.find(host);
  if (i != clusterMap_.end())
    return i->second.get();

  std::string path = FileUtil::joinPaths(XZERO_CLUSTERDIR,
                                         host + ".cluster.conf");
  if (!FileUtil::exists(path))
    return nullptr;

  HttpCluster* cluster = createCluster(host, path);
  cluster->setConfiguration(FileUtil::read(path).str(), path);
  return cluster;
}

bool ProxyModule::proxy_cluster_auto(Context* cx, Params& args) {
  // determines which cluster to use by request host header
  std::string host = cx->request()->getHeader("Host");
  size_t colon = host.find(':');
  if (colon != std::string::npos)
    host = host.substr(0, colon);

  HttpCluster* cluster = findLocalCluster(host);
  if (!cluster) {
    return cx->sendErrorPage(HttpStatus::NotFound);
  }

  std::string pseudonym = pseudonym_;
  if (pseudonym.empty()) {
    if (Option<InetAddress> addr = cx->request()->localAddress()) {
      pseudonym = StringUtil::format("$0:$1", addr->ip(), addr->port());
    } else {
      pseudonym = Application::hostname();
    }
  }

  DEBUG("proxy.cluster() auto-detect local cluster '$0', pseudonym '$1'",
      cluster->name(), pseudonym);

  HttpClusterRequest* cr = cx->setCustomData<HttpClusterRequest>(this,
      *cx->request(),
      //cx->request()->getContent(),
      std::make_unique<HttpResponseBuilder>(cx->response()),
      cx->response()->executor(),
      daemon().config().responseBodyBufferSize,
      pseudonym);

  cluster->schedule(cr);

  return true;
}

bool ProxyModule::proxy_cluster(Context* cx, Params& args) {
  auto& cluster = clusterMap_[args.getString(1)];
  std::string path = args.getString(2);
  std::string bucketName = args.getString(3);
  std::string backendName = args.getString(4);

  if (cx->tryServeTraceProxy())
    return true;

  TRACE("proxy.cluster: $0", cluster->name());

  HttpCluster::RequestShaper::Node* bucket = cluster->rootBucket();
  if (!bucketName.empty()) {
    auto foundBucket = cluster->findBucket(bucketName);
    if (foundBucket) {
      bucket = foundBucket;
    } else {
      logError("proxy: Cluster $0 is missing bucket $1. Defaulting to $2",
               cluster->name(), bucketName, bucket->name());
    }
  }

  HttpClusterRequest* cr = cx->setCustomData<HttpClusterRequest>(this,
      *cx->request(),
      // cx->request()->getContentBuffer(),
      std::make_unique<HttpResponseBuilder>(cx->response()),
      cx->response()->executor(),
      daemon().config().responseBodyBufferSize,
      pseudonym_);

  cluster->schedule(cr, bucket);

  return true;
}

bool ProxyModule::proxy_api(Context* cx, xzero::flow::Params& args) {
  std::string prefix = args.getString(1);

  if (!StringUtil::beginsWithIgnoreCase(cx->request()->path(), prefix))
    return false;

  HttpClusterApiHandler* handler = cx->setCustomData<HttpClusterApiHandler>(
      this, this, cx->request(), cx->response(), prefix);

  return handler->run();
}

bool ProxyModule::proxy_fcgi(Context* cx, xzero::flow::Params& args) {
  cx->logError("proxy.fcgi: Not yet reimplemented");
  return false; // TODO
}

bool ProxyModule::proxy_http(Context* cx, xzero::flow::Params& args) {
  const FlowString onClientAbortStr = args.getString(1);
  const InetAddress upstreamAddr(args.getIPAddress(2), args.getInt(3));
  const Duration connectTimeout = Duration::fromSeconds(args.getInt(4));
  const Duration readTimeout = Duration::fromSeconds(args.getInt(5));
  const Duration writeTimeout = Duration::fromSeconds(args.getInt(6));
  constexpr Duration keepAlive = 0_seconds;
  Executor* executor = cx->response()->executor();

  // TODO: handle onClientAbortStr == ("close" | "ignore")
  // - if set to close, also close upstream
  // - if set to ignore, finish receiving upstream response

  if (cx->tryServeTraceProxy())
    return true;

  HttpClient* client = cx->setCustomData<HttpClient>(this,
      executor, upstreamAddr,
      connectTimeout, readTimeout, writeTimeout, keepAlive);

  Future<HttpClient::Response> f = client->send(*cx->request());

  f.onFailure([cx, upstreamAddr] (std::error_code ec) {
    // XXX defer execution to ensure we're truely async, to avoid nested runner.
    // TODO: we could instead make resume() a no-op if it's already in the loop.
    cx->response()->executor()->execute([cx, upstreamAddr, ec]() {
      cx->logError("proxy: Failed to proxy to $0. $1", upstreamAddr, ec.message());
      bool internalRedirect = false;
      cx->sendErrorPage(HttpStatus::ServiceUnavailable, &internalRedirect);
      if (internalRedirect) {
        cx->runner()->resume();
      }
    });
  });

  f.onSuccess([cx, this] (HttpClient::Response& response) {
    for (const HeaderField& field: response.headers()) {
      if (!isConnectionHeader(field.name())) {
        cx->response()->addHeader(field.name(), field.value());
      }
    }
    addVia(cx);

    // TODO: what about streaming responses?
    cx->response()->setStatus(response.status());
    cx->response()->setReason(response.reason());
    cx->response()->setContentLength(response.content().size());
    cx->response()->write(std::move(response.content()));
    cx->response()->completed();
  });

  return true;
}

void ProxyModule::addVia(Context* cx) {
  addVia(cx->request(), cx->response());
}

void ProxyModule::addVia(const HttpRequestInfo* in, HttpResponse* out) {
  char buf[128];
  snprintf(buf, sizeof(buf), "%s %s",
           to_string(in->version()).c_str(),
           pseudonym_.c_str());

  // RFC 7230, section 5.7.1: makes it clear, that we put ourselfs into the
  // front of the Via-list.
  out->prependHeader("Via", buf);
}

std::list<HttpCluster*> ProxyModule::listCluster() {
  // TODO: unordered_map is not thread-safe
  std::list<HttpCluster*> result;
  for (auto& cluster: clusterMap_)
    result.push_back(cluster.second.get());

  return result;
}

HttpCluster* ProxyModule::findCluster(const std::string& name) {
  // TODO: unordered_map is not thread-safe
  auto i = clusterMap_.find(name);
  return i != clusterMap_.end() ? i->second.get() : nullptr;
}

HttpCluster* ProxyModule::createCluster(const std::string& name,
                                        const std::string& path) {
  // TODO: unordered_map is not thread-safe

  // quick-path if invoked again on the same cluster name
  if (clusterMap_.find(name) != clusterMap_.end())
    return clusterMap_[name].get();

  Executor* executor = daemon().selectClientExecutor();
  std::shared_ptr<HttpCluster> cluster =
      std::make_shared<HttpCluster>(name, path, executor);
  clusterMap_[name] = cluster;

  if (FileUtil::exists(path)) {
    logInfo("proxy: Loading cluster $0 ($1)", name, path);
    cluster->setConfiguration(FileUtil::read(path).str(), path);
  } else {
    logInfo("proxy: Initializing new cluster $0 ($1)", name, path);
    cluster->saveConfiguration();
  }
  return cluster.get();
}

void ProxyModule::destroyCluster(const std::string& name) {
  // TODO: naive implementation must be thread-safe
  auto i = clusterMap_.find(name);
  if (i != clusterMap_.end()) {
    clusterMap_.erase(i);
  }
}

bool ProxyModule::proxy_roadwarrior_verify(xzero::flow::Instr* instr, xzero::flow::IRBuilder* builder) {
  return true; // TODO
}

void ProxyModule::proxy_cache(Context* cx, xzero::flow::Params& args) {
  // TODO
}

} // namespace x0d
<commit_msg>[x0d] proxy: flag some builtins as experimental<commit_after>// This file is part of the "x0" project, http://github.com/christianparpart/x0>
//   (c) 2009-2017 Christian Parpart <christian@parpart.family>
//
// Licensed under the MIT License (the "License"); you may not use this
// file except in compliance with the License. You may obtain a copy of
// the License at: http://opensource.org/licenses/MIT

/*
 * Noteworthy
 *
 * - does not forward Expect request header to upstream (does nginx/apache?)
 */

#include "proxy.h"
#include <x0d/Context.h>
#include <xzero/sysconfig.h>
#include <xzero/http/proxy/HttpCluster.h>
#include <xzero/http/proxy/HttpClusterRequest.h>
#include <xzero/http/proxy/HttpClusterApiHandler.h>
#include <xzero/http/client/HttpClient.h>
#include <xzero/http/HttpRequest.h>
#include <xzero/http/HttpResponse.h>
#include <xzero/http/HeaderFieldList.h>
#include <xzero/net/TcpEndPoint.h>
#include <xzero/io/FileUtil.h>
#include <xzero/WallClock.h>
#include <xzero/StringUtil.h>
#include <xzero/RuntimeError.h>
#include <xzero/logging.h>
#include <xzero/Application.h>
#include <xzero-flow/AST.h>
#include <xzero-flow/ir/Instr.h>
#include <xzero-flow/ir/BasicBlock.h>
#include <xzero-flow/ir/IRHandler.h>
#include <xzero-flow/ir/IRProgram.h>
#include <xzero-flow/ir/ConstantValue.h>
#include <xzero-flow/ir/ConstantArray.h>

namespace x0d {

using namespace xzero;
using namespace xzero::http;
using namespace xzero::flow;

using xzero::http::client::HttpClusterApiHandler;
using xzero::http::client::HttpClusterRequest;
using xzero::http::client::HttpCluster;
using xzero::http::client::HttpClient;

#define TRACE(msg...) logTrace(msg)
#define DEBUG(msg...) logDebug(msg)

template<typename T>
static bool isConnectionHeader(const T& name) {
  static const std::vector<T> connectionHeaderFields = {
    "Connection",
    "Content-Length",
    "Close",
    "Keep-Alive",
    "TE",
    "Trailer",
    "Transfer-Encoding",
    "Upgrade",
  };

  for (const auto& test: connectionHeaderFields)
    if (iequals(name, test))
      return true;

  return false;
}

ProxyModule::ProxyModule(Daemon* d)
    : Module(d, "proxy"),
      pseudonym_("x0d"),
      clusterInit_(),
      clusterMap_() {

  setupFunction("proxy.pseudonym", &ProxyModule::proxy_pseudonym,
                FlowType::String);

  mainHandler("proxy.cluster", &ProxyModule::proxy_cluster_auto)
      .setExperimental(); // TODO

  mainHandler("proxy.cluster", &ProxyModule::proxy_cluster)
      .setExperimental() // TODO
      .param<FlowString>("name")
      .param<FlowString>("path", "")
      .param<FlowString>("bucket", "")
      .param<FlowString>("backend", "")
      .verifier(&ProxyModule::verify_proxy_cluster, this);

  mainHandler("proxy.api", &ProxyModule::proxy_api)
      .setExperimental() // TODO
      .param<FlowString>("prefix", "/");

  mainHandler("proxy.fcgi", &ProxyModule::proxy_fcgi)
      .setExperimental() // TODO
      .verifier(&ProxyModule::proxy_roadwarrior_verify, this)
      .param<FlowString>("on_client_abort", "close")
      .param<IPAddress>("address", IPAddress("0.0.0.0"))
      .param<int>("port");

  mainHandler("proxy.http", &ProxyModule::proxy_http)
      .verifier(&ProxyModule::proxy_roadwarrior_verify, this)
      .param<FlowString>("on_client_abort", "close")
      .param<IPAddress>("address")
      .param<int>("port")
      .param<int>("connect_timeout", 10)
      .param<int>("read_timeout", 60)
      .param<int>("write_timeout", 10);

  mainFunction("proxy.cache", &ProxyModule::proxy_cache)
      .setExperimental() // TODO
      .param<bool>("enabled", true)
      .param<FlowString>("key", "")
      .param<int>("ttl", 0);
}

ProxyModule::~ProxyModule() {
}

void ProxyModule::proxy_pseudonym(xzero::flow::Params& args) {
  std::string value = args.getString(1);

  for (char ch: value) {
    if (!std::isalnum(ch) && ch != '_' && ch != '-' && ch != '.') {
      throw ConfigurationError{"Invalid character found in proxy.pseudonym"};
    }
  }

  pseudonym_ = value;
}

bool ProxyModule::verify_proxy_cluster(xzero::flow::Instr* call, xzero::flow::IRBuilder* builder) {
  auto program = call->getBasicBlock()->getHandler()->getProgram();

  auto nameArg = dynamic_cast<ConstantString*>(call->operand(1));
  if (nameArg == nullptr) {
    logError("proxy.cluster: name parameter must be a literal.");
    return false;
  }

  if (nameArg->get().empty()) {
    logError("Setting empty proxy.cluster name is not allowed.");
    return false;
  }

  // pathArg
  auto pathArg = dynamic_cast<ConstantString*>(call->operand(2));
  if (pathArg == nullptr) {
    logError("proxy.cluster: path parameter must be a literal.");
    return false;
  }

  std::string path = pathArg->get().empty()
      ? FileUtil::joinPaths(XZERO_CLUSTERDIR, nameArg->get() + ".cluster.conf")
      : pathArg->get();

  call->setOperand(2, program->get(path));

  clusterInit_[nameArg->get()] = path;

  return true;
}

void ProxyModule::onPostConfig() {
  using client::HttpCluster;

  TRACE("clusterInit count: $0", clusterInit_.size());
  for (const auto& init: clusterInit_) {
    TRACE("clusterInit: spawning $0", init.first);
    std::string name = init.first;
    std::string path = init.second;

    createCluster(name, path);
  }
  clusterInit_.clear();
}

class HttpResponseBuilder : public HttpListener { // {{{
 public:
  explicit HttpResponseBuilder(HttpResponse* response);

  void onMessageBegin(HttpVersion version, HttpStatus code, const BufferRef& text) override;
  void onMessageHeader(const BufferRef& name, const BufferRef& value) override;
  void onMessageHeaderEnd() override;
  void onMessageContent(const BufferRef& chunk) override;
  void onMessageContent(FileView&& chunk) override;
  void onMessageEnd() override;
  void onError(std::error_code ec) override;

 private:
  HttpResponse* response_;
};

HttpResponseBuilder::HttpResponseBuilder(HttpResponse* response)
    : response_(response) {
}

void HttpResponseBuilder::onMessageBegin(HttpVersion version, HttpStatus code, const BufferRef& text) {
  response_->setStatus(code);
  response_->setReason(text.str());
}

void HttpResponseBuilder::onMessageHeader(const BufferRef& name, const BufferRef& value) {
  if (iequals(name, "Content-Length")) {
    response_->setContentLength(value.toInt());
  } else if (!isConnectionHeader(name)) {
    response_->addHeader(name.str(), value.str());
  }
}

void HttpResponseBuilder::onMessageHeaderEnd() {
}

void HttpResponseBuilder::onMessageContent(const BufferRef& chunk) {
  response_->write(Buffer(chunk));
}

void HttpResponseBuilder::onMessageContent(FileView&& chunk) {
  response_->write(std::move(chunk));
}

void HttpResponseBuilder::onMessageEnd() {
  response_->completed();
}

void HttpResponseBuilder::onError(std::error_code ec) {
  if (ec.category() == HttpStatusCategory::get()) {
    response_->sendError(static_cast<HttpStatus>(ec.value()));
  } else {
    logError("proxy: Unhandled error in response builder. $0", ec.message());
    response_->sendError(HttpStatus::InternalServerError);
  }
}
// }}}

HttpCluster* ProxyModule::findLocalCluster(const std::string& host) {
  auto i = clusterMap_.find(host);
  if (i != clusterMap_.end())
    return i->second.get();

  std::string path = FileUtil::joinPaths(XZERO_CLUSTERDIR,
                                         host + ".cluster.conf");
  if (!FileUtil::exists(path))
    return nullptr;

  HttpCluster* cluster = createCluster(host, path);
  cluster->setConfiguration(FileUtil::read(path).str(), path);
  return cluster;
}

bool ProxyModule::proxy_cluster_auto(Context* cx, Params& args) {
  // determines which cluster to use by request host header
  std::string host = cx->request()->getHeader("Host");
  size_t colon = host.find(':');
  if (colon != std::string::npos)
    host = host.substr(0, colon);

  HttpCluster* cluster = findLocalCluster(host);
  if (!cluster) {
    return cx->sendErrorPage(HttpStatus::NotFound);
  }

  std::string pseudonym = pseudonym_;
  if (pseudonym.empty()) {
    if (Option<InetAddress> addr = cx->request()->localAddress()) {
      pseudonym = StringUtil::format("$0:$1", addr->ip(), addr->port());
    } else {
      pseudonym = Application::hostname();
    }
  }

  DEBUG("proxy.cluster() auto-detect local cluster '$0', pseudonym '$1'",
      cluster->name(), pseudonym);

  HttpClusterRequest* cr = cx->setCustomData<HttpClusterRequest>(this,
      *cx->request(),
      //cx->request()->getContent(),
      std::make_unique<HttpResponseBuilder>(cx->response()),
      cx->response()->executor(),
      daemon().config().responseBodyBufferSize,
      pseudonym);

  cluster->schedule(cr);

  return true;
}

bool ProxyModule::proxy_cluster(Context* cx, Params& args) {
  auto& cluster = clusterMap_[args.getString(1)];
  std::string path = args.getString(2);
  std::string bucketName = args.getString(3);
  std::string backendName = args.getString(4);

  if (cx->tryServeTraceProxy())
    return true;

  TRACE("proxy.cluster: $0", cluster->name());

  HttpCluster::RequestShaper::Node* bucket = cluster->rootBucket();
  if (!bucketName.empty()) {
    auto foundBucket = cluster->findBucket(bucketName);
    if (foundBucket) {
      bucket = foundBucket;
    } else {
      logError("proxy: Cluster $0 is missing bucket $1. Defaulting to $2",
               cluster->name(), bucketName, bucket->name());
    }
  }

  HttpClusterRequest* cr = cx->setCustomData<HttpClusterRequest>(this,
      *cx->request(),
      // cx->request()->getContentBuffer(),
      std::make_unique<HttpResponseBuilder>(cx->response()),
      cx->response()->executor(),
      daemon().config().responseBodyBufferSize,
      pseudonym_);

  cluster->schedule(cr, bucket);

  return true;
}

bool ProxyModule::proxy_api(Context* cx, xzero::flow::Params& args) {
  std::string prefix = args.getString(1);

  if (!StringUtil::beginsWithIgnoreCase(cx->request()->path(), prefix))
    return false;

  HttpClusterApiHandler* handler = cx->setCustomData<HttpClusterApiHandler>(
      this, this, cx->request(), cx->response(), prefix);

  return handler->run();
}

bool ProxyModule::proxy_fcgi(Context* cx, xzero::flow::Params& args) {
  cx->logError("proxy.fcgi: Not yet reimplemented");
  return false; // TODO
}

bool ProxyModule::proxy_http(Context* cx, xzero::flow::Params& args) {
  const FlowString onClientAbortStr = args.getString(1);
  const InetAddress upstreamAddr(args.getIPAddress(2), args.getInt(3));
  const Duration connectTimeout = Duration::fromSeconds(args.getInt(4));
  const Duration readTimeout = Duration::fromSeconds(args.getInt(5));
  const Duration writeTimeout = Duration::fromSeconds(args.getInt(6));
  constexpr Duration keepAlive = 0_seconds;
  Executor* executor = cx->response()->executor();

  // TODO: handle onClientAbortStr == ("close" | "ignore")
  // - if set to close, also close upstream
  // - if set to ignore, finish receiving upstream response

  if (cx->tryServeTraceProxy())
    return true;

  HttpClient* client = cx->setCustomData<HttpClient>(this,
      executor, upstreamAddr,
      connectTimeout, readTimeout, writeTimeout, keepAlive);

  Future<HttpClient::Response> f = client->send(*cx->request());

  f.onFailure([cx, upstreamAddr] (std::error_code ec) {
    // XXX defer execution to ensure we're truely async, to avoid nested runner.
    // TODO: we could instead make resume() a no-op if it's already in the loop.
    cx->response()->executor()->execute([cx, upstreamAddr, ec]() {
      cx->logError("proxy: Failed to proxy to $0. $1", upstreamAddr, ec.message());
      bool internalRedirect = false;
      cx->sendErrorPage(HttpStatus::ServiceUnavailable, &internalRedirect);
      if (internalRedirect) {
        cx->runner()->resume();
      }
    });
  });

  f.onSuccess([cx, this] (HttpClient::Response& response) {
    for (const HeaderField& field: response.headers()) {
      if (!isConnectionHeader(field.name())) {
        cx->response()->addHeader(field.name(), field.value());
      }
    }
    addVia(cx);

    // TODO: what about streaming responses?
    cx->response()->setStatus(response.status());
    cx->response()->setReason(response.reason());
    cx->response()->setContentLength(response.content().size());
    cx->response()->write(std::move(response.content()));
    cx->response()->completed();
  });

  return true;
}

void ProxyModule::addVia(Context* cx) {
  addVia(cx->request(), cx->response());
}

void ProxyModule::addVia(const HttpRequestInfo* in, HttpResponse* out) {
  char buf[128];
  snprintf(buf, sizeof(buf), "%s %s",
           to_string(in->version()).c_str(),
           pseudonym_.c_str());

  // RFC 7230, section 5.7.1: makes it clear, that we put ourselfs into the
  // front of the Via-list.
  out->prependHeader("Via", buf);
}

std::list<HttpCluster*> ProxyModule::listCluster() {
  // TODO: unordered_map is not thread-safe
  std::list<HttpCluster*> result;
  for (auto& cluster: clusterMap_)
    result.push_back(cluster.second.get());

  return result;
}

HttpCluster* ProxyModule::findCluster(const std::string& name) {
  // TODO: unordered_map is not thread-safe
  auto i = clusterMap_.find(name);
  return i != clusterMap_.end() ? i->second.get() : nullptr;
}

HttpCluster* ProxyModule::createCluster(const std::string& name,
                                        const std::string& path) {
  // TODO: unordered_map is not thread-safe

  // quick-path if invoked again on the same cluster name
  if (clusterMap_.find(name) != clusterMap_.end())
    return clusterMap_[name].get();

  Executor* executor = daemon().selectClientExecutor();
  std::shared_ptr<HttpCluster> cluster =
      std::make_shared<HttpCluster>(name, path, executor);
  clusterMap_[name] = cluster;

  if (FileUtil::exists(path)) {
    logInfo("proxy: Loading cluster $0 ($1)", name, path);
    cluster->setConfiguration(FileUtil::read(path).str(), path);
  } else {
    logInfo("proxy: Initializing new cluster $0 ($1)", name, path);
    cluster->saveConfiguration();
  }
  return cluster.get();
}

void ProxyModule::destroyCluster(const std::string& name) {
  // TODO: naive implementation must be thread-safe
  auto i = clusterMap_.find(name);
  if (i != clusterMap_.end()) {
    clusterMap_.erase(i);
  }
}

bool ProxyModule::proxy_roadwarrior_verify(xzero::flow::Instr* instr, xzero::flow::IRBuilder* builder) {
  return true; // TODO
}

void ProxyModule::proxy_cache(Context* cx, xzero::flow::Params& args) {
  // TODO
}

} // namespace x0d
<|endoftext|>


<commit_before>#include "builtin/exception.hpp"

#include "vm.hpp"
#include "prelude.hpp"
#include "exception.hpp"
#include "detection.hpp"

#include "builtin/string.hpp"
#include "builtin/class.hpp"
#include "builtin/symbol.hpp"

#include <ctype.h>
#include <vector>
#include <iostream>
#include <cxxabi.h>
#include <assert.h>

#ifdef USE_EXECINFO
#include <execinfo.h>
#endif

namespace rubinius {

  void TypeError::raise(object_type type, Object* obj, const char* reason) {
    throw TypeError(type, obj, reason);
    // Not reached.
  }

  void Assertion::raise(const char* reason) {
    throw Assertion(reason);
    // Not reached.
  }

  RubyException::RubyException(Exception* exception, bool make_backtrace)
      : VMException(make_backtrace), exception(exception) {
  }

  void RubyException::raise(Exception* exception, bool make_backtrace) {
    throw RubyException(exception, make_backtrace);
    // Not reached.
  }

  void RubyException::show(STATE) {
    std::cout << exception->message_c_str(state) <<
        " (" <<
        exception->class_object(state)->debug_str(state) <<
        ") \n";
    print_backtrace();
  }

  static VMException::Backtrace get_trace(size_t skip, size_t max_depth=100) {
#ifdef USE_EXECINFO
    size_t stack_depth;
    char **stack_strings;

    void **stack_addrs = (void **) calloc(max_depth, sizeof(void*));

    stack_depth = backtrace(stack_addrs, max_depth);
    stack_strings = backtrace_symbols(stack_addrs, stack_depth);

    VMException::Backtrace s;

    for (size_t i = skip; i < stack_depth; i++) {
      s.push_back(std::string(stack_strings[i]));
    }
    free(stack_strings); // malloc()ed by backtrace_symbols
    free(stack_addrs);
#else
    VMException::Backtrace s;
    s.push_back(std::string("C++ backtrace not available"));
#endif

    return s;
  }

  static void squeeze_space(std::string& str) {
    std::string ws = "    ";

    std::string::size_type pos = str.find(ws);
    if(pos == std::string::npos) return;

    std::string::size_type i = pos + 4;
    while(std::isspace(str[i])) i++;
    str.erase(pos + 2, i - pos - 2);
  }

  static void demangle(VMException::Backtrace& s) {
    for (size_t i = 0; i < s.size(); i++) {
      squeeze_space(s[i]);
      const char* str = s[i].c_str();
      const char* pos = strstr(str, " _Z");
      /* Found a mangle. */
      if(pos) {
        size_t sz = 0;
        char *cpp_name = 0;
        char* name = strdup(pos + 1);
        char* end = strstr(name, " + ");
        *end = 0;

        int status;
        cpp_name = abi::__cxa_demangle(name, cpp_name, &sz, &status);

        // It's possible for __cxa_demangle to return 0x0, which probably
        // shouldn't happen with status == 0, but it was observed in OS X
        // Mavericks Preview 2 so be paranoid.
        if(cpp_name) {
          if(!status) {
            std::string full_cpp = std::string(str, pos - str) + " " + cpp_name +
              " " + (++end);
            s[i] = full_cpp;
          }
          free(cpp_name);
        }
        free(name);
      }
    }
  }

  void abort() {
    std::cout << "Abort!" << std::endl;
    print_backtrace();
    ::abort();
  }

  void bug(const char* message) {
    std::cerr << "[BUG: " << message << "]\n";
    print_backtrace();
    ::abort();
  }

  void bug(const char* message, const char* arg) {
    std::cerr << "[BUG: " << message
              << ": " << arg
              << "]\n";
    print_backtrace();
    ::abort();
  }

  void warn(const char* message) {
    std::cerr << "[WARNING: " << message << "]\n";
    print_backtrace();
  }

  void print_backtrace(size_t max) {
    VMException::Backtrace s = get_trace(2, max);
    demangle(s);

    for(size_t i = 0; i < s.size(); i++) {
      std::cout << s[i] << std::endl;
    }
  }

  static VMException::Backtrace get_cpp_backtrace() {
    VMException::Backtrace s = get_trace(2);
    demangle(s);
    return s;
  }

  VMException::VMException(bool make_backtrace)
      : backtrace(NULL), reason(NULL) {
    if(make_backtrace) {
      backtrace = new VMException::Backtrace(get_cpp_backtrace());
    }
  }

  VMException::VMException(const char* reason, bool make_backtrace)
      : backtrace(NULL), reason(NULL) {
    if(make_backtrace) {
      backtrace = new VMException::Backtrace(get_cpp_backtrace());
    }
    if(reason) this->reason = strdup(reason);
  }

  VMException::VMException(const VMException& other)
      : backtrace(NULL), reason(NULL) {
    if(other.backtrace) {
      backtrace = new VMException::Backtrace(*other.backtrace);
    }
    if(other.reason) {
      reason = strdup(reason);
    }
  }

  void VMException::print_backtrace() {
    if(!backtrace) return;

    for(size_t i = 0; i < backtrace->size(); i++) {
      std::cout << backtrace->at(i) << std::endl;
    }
  }
}
<commit_msg>Fix VMException copy constructor<commit_after>#include "builtin/exception.hpp"

#include "vm.hpp"
#include "prelude.hpp"
#include "exception.hpp"
#include "detection.hpp"

#include "builtin/string.hpp"
#include "builtin/class.hpp"
#include "builtin/symbol.hpp"

#include <ctype.h>
#include <vector>
#include <iostream>
#include <cxxabi.h>
#include <assert.h>

#ifdef USE_EXECINFO
#include <execinfo.h>
#endif

namespace rubinius {

  void TypeError::raise(object_type type, Object* obj, const char* reason) {
    throw TypeError(type, obj, reason);
    // Not reached.
  }

  void Assertion::raise(const char* reason) {
    throw Assertion(reason);
    // Not reached.
  }

  RubyException::RubyException(Exception* exception, bool make_backtrace)
      : VMException(make_backtrace), exception(exception) {
  }

  void RubyException::raise(Exception* exception, bool make_backtrace) {
    throw RubyException(exception, make_backtrace);
    // Not reached.
  }

  void RubyException::show(STATE) {
    std::cout << exception->message_c_str(state) <<
        " (" <<
        exception->class_object(state)->debug_str(state) <<
        ") \n";
    print_backtrace();
  }

  static VMException::Backtrace get_trace(size_t skip, size_t max_depth=100) {
#ifdef USE_EXECINFO
    size_t stack_depth;
    char **stack_strings;

    void **stack_addrs = (void **) calloc(max_depth, sizeof(void*));

    stack_depth = backtrace(stack_addrs, max_depth);
    stack_strings = backtrace_symbols(stack_addrs, stack_depth);

    VMException::Backtrace s;

    for (size_t i = skip; i < stack_depth; i++) {
      s.push_back(std::string(stack_strings[i]));
    }
    free(stack_strings); // malloc()ed by backtrace_symbols
    free(stack_addrs);
#else
    VMException::Backtrace s;
    s.push_back(std::string("C++ backtrace not available"));
#endif

    return s;
  }

  static void squeeze_space(std::string& str) {
    std::string ws = "    ";

    std::string::size_type pos = str.find(ws);
    if(pos == std::string::npos) return;

    std::string::size_type i = pos + 4;
    while(std::isspace(str[i])) i++;
    str.erase(pos + 2, i - pos - 2);
  }

  static void demangle(VMException::Backtrace& s) {
    for (size_t i = 0; i < s.size(); i++) {
      squeeze_space(s[i]);
      const char* str = s[i].c_str();
      const char* pos = strstr(str, " _Z");
      /* Found a mangle. */
      if(pos) {
        size_t sz = 0;
        char *cpp_name = 0;
        char* name = strdup(pos + 1);
        char* end = strstr(name, " + ");
        *end = 0;

        int status;
        cpp_name = abi::__cxa_demangle(name, cpp_name, &sz, &status);

        // It's possible for __cxa_demangle to return 0x0, which probably
        // shouldn't happen with status == 0, but it was observed in OS X
        // Mavericks Preview 2 so be paranoid.
        if(cpp_name) {
          if(!status) {
            std::string full_cpp = std::string(str, pos - str) + " " + cpp_name +
              " " + (++end);
            s[i] = full_cpp;
          }
          free(cpp_name);
        }
        free(name);
      }
    }
  }

  void abort() {
    std::cout << "Abort!" << std::endl;
    print_backtrace();
    ::abort();
  }

  void bug(const char* message) {
    std::cerr << "[BUG: " << message << "]\n";
    print_backtrace();
    ::abort();
  }

  void bug(const char* message, const char* arg) {
    std::cerr << "[BUG: " << message
              << ": " << arg
              << "]\n";
    print_backtrace();
    ::abort();
  }

  void warn(const char* message) {
    std::cerr << "[WARNING: " << message << "]\n";
    print_backtrace();
  }

  void print_backtrace(size_t max) {
    VMException::Backtrace s = get_trace(2, max);
    demangle(s);

    for(size_t i = 0; i < s.size(); i++) {
      std::cout << s[i] << std::endl;
    }
  }

  static VMException::Backtrace get_cpp_backtrace() {
    VMException::Backtrace s = get_trace(2);
    demangle(s);
    return s;
  }

  VMException::VMException(bool make_backtrace)
      : backtrace(NULL), reason(NULL) {
    if(make_backtrace) {
      backtrace = new VMException::Backtrace(get_cpp_backtrace());
    }
  }

  VMException::VMException(const char* reason, bool make_backtrace)
      : backtrace(NULL), reason(NULL) {
    if(make_backtrace) {
      backtrace = new VMException::Backtrace(get_cpp_backtrace());
    }
    if(reason) this->reason = strdup(reason);
  }

  VMException::VMException(const VMException& other)
      : backtrace(NULL), reason(NULL) {
    if(other.backtrace) {
      backtrace = new VMException::Backtrace(*other.backtrace);
    }
    if(other.reason) {
      reason = strdup(other.reason);
    }
  }

  void VMException::print_backtrace() {
    if(!backtrace) return;

    for(size_t i = 0; i < backtrace->size(); i++) {
      std::cout << backtrace->at(i) << std::endl;
    }
  }
}
<|endoftext|>