<commit_before>// Copyright (C) 2016 Elviss Strazdins
// This file is part of the Ouzel engine.

#define GL_GLEXT_PROTOTYPES 1
#include <GL/gl.h>
#include <GL/glext.h>
#include "WindowLinux.h"
#include "core/Application.h"
#include "core/Engine.h"
#include "graphics/Renderer.h"
#include "opengl/RendererOGL.h"
#include "utils/Utils.h"

const long _NET_WM_STATE_TOGGLE = 2;

namespace ouzel
{
    WindowLinux::WindowLinux(const Size2& pSize, bool pResizable, bool pFullscreen, const std::string& pTitle):
        Window(pSize, pResizable, pFullscreen, pTitle)
    {
    }

    WindowLinux::~WindowLinux()
    {
        if (display)
        {
            if (context)
            {
                glXDestroyContext(display, context);
            }

            if (window)
            {
                XDestroyWindow(display, window);
            }

            XCloseDisplay(display);
        }
    }

    bool WindowLinux::init()
    {
        std::shared_ptr<graphics::RendererOGL> rendererOGL = std::static_pointer_cast<graphics::RendererOGL>(sharedEngine->getRenderer());

        // open a connection to the X server
        display = XOpenDisplay(nullptr);

        if (!display)
        {
            ouzel::log(LOG_LEVEL_ERROR, "Failed to open display");
            return false;
        }

        int screen = DefaultScreen(display);

        XVisualInfo* vi = nullptr;

        // make sure OpenGL's GLX extension supported
        int dummy;
        if (!glXQueryExtension(display, &dummy, &dummy))
        {
            ouzel::log(LOG_LEVEL_ERROR, "X server has no OpenGL GLX extension");
            return false;
        }

        int fbcount = 0;

        static const int attributes[] = {
            GLX_RENDER_TYPE, GLX_RGBA_BIT,
            GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
            GLX_DOUBLEBUFFER, GL_TRUE,
            GLX_RED_SIZE, 1,
            GLX_GREEN_SIZE, 1,
            GLX_BLUE_SIZE, 1,
            None
        };

        GLXFBConfig* framebufferConfig = glXChooseFBConfig(display, screen, attributes, &fbcount);
        if (!framebufferConfig)
        {
            ouzel::log(LOG_LEVEL_ERROR, "Failed to get frame buffer");
        }
        else
        {
            // create an OpenGL rendering context
            static const int contextAttribs[] = {
                GLX_CONTEXT_PROFILE_MASK_ARB,
                GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
                GLX_CONTEXT_MAJOR_VERSION_ARB,
                3,
                GLX_CONTEXT_MINOR_VERSION_ARB,
                2,
                None
            };

            PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddress(reinterpret_cast<const GLubyte*>("glXCreateContextAttribsARB"));

            if (glXCreateContextAttribsARB)
            {
                context = glXCreateContextAttribsARB(display, framebufferConfig[0], NULL, GL_TRUE, contextAttribs);

                if (context)
                {
                    rendererOGL->setAPIVersion(3);
                    log(LOG_LEVEL_INFO, "Using OpenGL 3.2");

                    vi = glXGetVisualFromFBConfig(display, framebufferConfig[0]);

                    if (!vi)
                    {
                        ouzel::log(LOG_LEVEL_INFO, "Failed to get OpenGL visual");
                        context = nullptr;
                    }
                }
                else
                {
                    log(LOG_LEVEL_INFO, "Failed to crete OpenGL 3.2 rendering context");
                }
            }
            else
            {
                log(LOG_LEVEL_INFO, "Could not find glXCreateContextAttribsARB");
            }
        }

        if (!context)
        {
            // find an OpenGL-capable RGB visual with depth buffer
            static int doubleBuffer[] = {GLX_RGBA, GLX_DEPTH_SIZE, 16, GLX_DOUBLEBUFFER, None};

            vi = glXChooseVisual(display, screen, doubleBuffer);
            if (!vi)
            {
                ouzel::log(LOG_LEVEL_ERROR, "Failed to choose OpenGL visual");
                return false;
            }
            if (vi->c_class != TrueColor)
            {
                ouzel::log(LOG_LEVEL_ERROR, "TrueColor visual required for this program");
                return false;
            }

            context = glXCreateContext(display, vi, None, GL_TRUE);

            if (context)
            {
                rendererOGL->setAPIVersion(2);
                log(LOG_LEVEL_INFO, "Using OpenGL 2");
            }
            else
            {
                log(LOG_LEVEL_ERROR, "Failed to crete OpenGL 2 rendering context");
                return false;
            }
        }

        // create an X colormap since probably not using default visual
        Colormap cmap = XCreateColormap(display, RootWindow(display, vi->screen), vi->visual, AllocNone);
        XSetWindowAttributes swa;
        swa.colormap = cmap;
        swa.border_pixel = 0;
        swa.event_mask = FocusChangeMask | KeyPressMask | KeyRelease | ExposureMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask;
        window = XCreateWindow(display, RootWindow(display, vi->screen), 0, 0,
                               static_cast<unsigned int>(size.width), static_cast<unsigned int>(size.height), 0,
                               vi->depth, InputOutput, vi->visual,
                               CWBorderPixel | CWColormap | CWEventMask, &swa);
        XSetStandardProperties(display, window, title.c_str(), title.c_str(), None, sharedApplication->getArgv(), sharedApplication->getArgc(), nullptr);

        // bind the rendering context to the window
        glXMakeCurrent(display, window, context);

        // request the X window to be displayed on the screen
        XMapWindow(display, window);

        deleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", False);
        XSetWMProtocols(display, window, &deleteMessage, 1);

        state = XInternAtom(display, "_NET_WM_STATE", False);
        stateFullscreen = XInternAtom(display, "_NET_WM_STATE_FULLSCREEN", False);

        PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddress(reinterpret_cast<const GLubyte*>("glXSwapIntervalEXT"));

        if (glXSwapIntervalEXT)
        {
            glXSwapIntervalEXT(display, window, sharedEngine->getSettings().verticalSync ? 1 : 0);
        }

        if (fullscreen)
        {
            toggleFullscreen();
        }

        return Window::init();
    }

    void WindowLinux::setSize(const Size2& newSize)
    {
        sharedApplication->execute([this, newSize] {
            XWindowChanges changes;
            changes.width = static_cast<int>(newSize.width);
            changes.height = static_cast<int>(newSize.height);
            XConfigureWindow(display, window, CWWidth | CWHeight, &changes);
        });

        Window::setSize(newSize);
    }

    void WindowLinux::setFullscreen(bool newFullscreen)
    {
        if (fullscreen != newFullscreen)
        {
            sharedApplication->execute([this, newFullscreen] {
                toggleFullscreen();
            });
        }

        Window::setFullscreen(newFullscreen);
    }

    void WindowLinux::setTitle(const std::string& newTitle)
    {
        if (title != newTitle)
        {
            sharedApplication->execute([this, newTitle] {
                XStoreName(display, window, newTitle.c_str());
            });
        }

        Window::setTitle(newTitle);
    }

    bool WindowLinux::toggleFullscreen()
    {
        if(!state || !stateFullscreen)
        {
            return false;
        }

        XEvent event;

        event.type = ClientMessage;
        event.xclient.window = window;
        event.xclient.message_type = state;
        event.xclient.format = 32;
        event.xclient.data.l[0] = _NET_WM_STATE_TOGGLE;
        event.xclient.data.l[1] = stateFullscreen;
        event.xclient.data.l[2] = 0;  /* no second property to toggle */
        event.xclient.data.l[3] = 1;  /* source indication: application */
        event.xclient.data.l[4] = 0;  /* unused */

        if(!XSendEvent(display, DefaultRootWindow(display), 0, SubstructureRedirectMask | SubstructureNotifyMask, &event))
        {
            log(LOG_LEVEL_ERROR, "Failed to send fullscreen message");
            return false;
        }

        return true;
    }

    void WindowLinux::handleResize(int width, int height)
    {
        Window::setSize(Size2(width, height));
    }
}
<commit_msg>Remove unneeded namespaces<commit_after>// Copyright (C) 2016 Elviss Strazdins
// This file is part of the Ouzel engine.

#define GL_GLEXT_PROTOTYPES 1
#include <GL/gl.h>
#include <GL/glext.h>
#include "WindowLinux.h"
#include "core/Application.h"
#include "core/Engine.h"
#include "graphics/Renderer.h"
#include "opengl/RendererOGL.h"
#include "utils/Utils.h"

const long _NET_WM_STATE_TOGGLE = 2;

namespace ouzel
{
    WindowLinux::WindowLinux(const Size2& pSize, bool pResizable, bool pFullscreen, const std::string& pTitle):
        Window(pSize, pResizable, pFullscreen, pTitle)
    {
    }

    WindowLinux::~WindowLinux()
    {
        if (display)
        {
            if (context)
            {
                glXDestroyContext(display, context);
            }

            if (window)
            {
                XDestroyWindow(display, window);
            }

            XCloseDisplay(display);
        }
    }

    bool WindowLinux::init()
    {
        std::shared_ptr<graphics::RendererOGL> rendererOGL = std::static_pointer_cast<graphics::RendererOGL>(sharedEngine->getRenderer());

        // open a connection to the X server
        display = XOpenDisplay(nullptr);

        if (!display)
        {
            log(LOG_LEVEL_ERROR, "Failed to open display");
            return false;
        }

        int screen = DefaultScreen(display);

        XVisualInfo* vi = nullptr;

        // make sure OpenGL's GLX extension supported
        int dummy;
        if (!glXQueryExtension(display, &dummy, &dummy))
        {
            log(LOG_LEVEL_ERROR, "X server has no OpenGL GLX extension");
            return false;
        }

        int fbcount = 0;

        static const int attributes[] = {
            GLX_RENDER_TYPE, GLX_RGBA_BIT,
            GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
            GLX_DOUBLEBUFFER, GL_TRUE,
            GLX_RED_SIZE, 1,
            GLX_GREEN_SIZE, 1,
            GLX_BLUE_SIZE, 1,
            None
        };

        GLXFBConfig* framebufferConfig = glXChooseFBConfig(display, screen, attributes, &fbcount);
        if (!framebufferConfig)
        {
            log(LOG_LEVEL_ERROR, "Failed to get frame buffer");
        }
        else
        {
            // create an OpenGL rendering context
            static const int contextAttribs[] = {
                GLX_CONTEXT_PROFILE_MASK_ARB,
                GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
                GLX_CONTEXT_MAJOR_VERSION_ARB,
                3,
                GLX_CONTEXT_MINOR_VERSION_ARB,
                2,
                None
            };

            PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddress(reinterpret_cast<const GLubyte*>("glXCreateContextAttribsARB"));

            if (glXCreateContextAttribsARB)
            {
                context = glXCreateContextAttribsARB(display, framebufferConfig[0], NULL, GL_TRUE, contextAttribs);

                if (context)
                {
                    rendererOGL->setAPIVersion(3);
                    log(LOG_LEVEL_INFO, "Using OpenGL 3.2");

                    vi = glXGetVisualFromFBConfig(display, framebufferConfig[0]);

                    if (!vi)
                    {
                        log(LOG_LEVEL_INFO, "Failed to get OpenGL visual");
                        context = nullptr;
                    }
                }
                else
                {
                    log(LOG_LEVEL_INFO, "Failed to crete OpenGL 3.2 rendering context");
                }
            }
            else
            {
                log(LOG_LEVEL_INFO, "Could not find glXCreateContextAttribsARB");
            }
        }

        if (!context)
        {
            // find an OpenGL-capable RGB visual with depth buffer
            static int doubleBuffer[] = {GLX_RGBA, GLX_DEPTH_SIZE, 16, GLX_DOUBLEBUFFER, None};

            vi = glXChooseVisual(display, screen, doubleBuffer);
            if (!vi)
            {
                log(LOG_LEVEL_ERROR, "Failed to choose OpenGL visual");
                return false;
            }
            if (vi->c_class != TrueColor)
            {
                log(LOG_LEVEL_ERROR, "TrueColor visual required for this program");
                return false;
            }

            context = glXCreateContext(display, vi, None, GL_TRUE);

            if (context)
            {
                rendererOGL->setAPIVersion(2);
                log(LOG_LEVEL_INFO, "Using OpenGL 2");
            }
            else
            {
                log(LOG_LEVEL_ERROR, "Failed to crete OpenGL 2 rendering context");
                return false;
            }
        }

        // create an X colormap since probably not using default visual
        Colormap cmap = XCreateColormap(display, RootWindow(display, vi->screen), vi->visual, AllocNone);
        XSetWindowAttributes swa;
        swa.colormap = cmap;
        swa.border_pixel = 0;
        swa.event_mask = FocusChangeMask | KeyPressMask | KeyRelease | ExposureMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask;
        window = XCreateWindow(display, RootWindow(display, vi->screen), 0, 0,
                               static_cast<unsigned int>(size.width), static_cast<unsigned int>(size.height), 0,
                               vi->depth, InputOutput, vi->visual,
                               CWBorderPixel | CWColormap | CWEventMask, &swa);
        XSetStandardProperties(display, window, title.c_str(), title.c_str(), None, sharedApplication->getArgv(), sharedApplication->getArgc(), nullptr);

        // bind the rendering context to the window
        glXMakeCurrent(display, window, context);

        // request the X window to be displayed on the screen
        XMapWindow(display, window);

        deleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", False);
        XSetWMProtocols(display, window, &deleteMessage, 1);

        state = XInternAtom(display, "_NET_WM_STATE", False);
        stateFullscreen = XInternAtom(display, "_NET_WM_STATE_FULLSCREEN", False);

        PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddress(reinterpret_cast<const GLubyte*>("glXSwapIntervalEXT"));

        if (glXSwapIntervalEXT)
        {
            glXSwapIntervalEXT(display, window, sharedEngine->getSettings().verticalSync ? 1 : 0);
        }

        if (fullscreen)
        {
            toggleFullscreen();
        }

        return Window::init();
    }

    void WindowLinux::setSize(const Size2& newSize)
    {
        sharedApplication->execute([this, newSize] {
            XWindowChanges changes;
            changes.width = static_cast<int>(newSize.width);
            changes.height = static_cast<int>(newSize.height);
            XConfigureWindow(display, window, CWWidth | CWHeight, &changes);
        });

        Window::setSize(newSize);
    }

    void WindowLinux::setFullscreen(bool newFullscreen)
    {
        if (fullscreen != newFullscreen)
        {
            sharedApplication->execute([this, newFullscreen] {
                toggleFullscreen();
            });
        }

        Window::setFullscreen(newFullscreen);
    }

    void WindowLinux::setTitle(const std::string& newTitle)
    {
        if (title != newTitle)
        {
            sharedApplication->execute([this, newTitle] {
                XStoreName(display, window, newTitle.c_str());
            });
        }

        Window::setTitle(newTitle);
    }

    bool WindowLinux::toggleFullscreen()
    {
        if(!state || !stateFullscreen)
        {
            return false;
        }

        XEvent event;

        event.type = ClientMessage;
        event.xclient.window = window;
        event.xclient.message_type = state;
        event.xclient.format = 32;
        event.xclient.data.l[0] = _NET_WM_STATE_TOGGLE;
        event.xclient.data.l[1] = stateFullscreen;
        event.xclient.data.l[2] = 0;  /* no second property to toggle */
        event.xclient.data.l[3] = 1;  /* source indication: application */
        event.xclient.data.l[4] = 0;  /* unused */

        if(!XSendEvent(display, DefaultRootWindow(display), 0, SubstructureRedirectMask | SubstructureNotifyMask, &event))
        {
            log(LOG_LEVEL_ERROR, "Failed to send fullscreen message");
            return false;
        }

        return true;
    }

    void WindowLinux::handleResize(int width, int height)
    {
        Window::setSize(Size2(width, height));
    }
}
<|endoftext|>


<commit_before>// Copyright (c) 2012 Intel Corp
// Copyright (c) 2012 The Chromium Authors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co
// pies of the Software, and to permit persons to whom the Software is furnished
//  to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in al
// l copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM
// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES
// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
//  OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH
// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#include "content/nw/src/api/menu/menu.h"

#include "base/logging.h"
#include "base/values.h"
#include "content/nw/src/api/menuitem/menuitem.h"
#include "content/nw/src/nw_shell.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/render_widget_host_view.h"
#include "ui/gfx/point.h"

namespace api {

namespace {

// Popup menus may get squished if they open up too close to the bottom of the
// screen. This function takes the size of the screen, the size of the menu,
// an optional widget, the Y position of the mouse click, and adjusts the popup
// menu's Y position to make it fit if it's possible to do so.
// Returns the new Y position of the popup menu.
int CalculateMenuYPosition(const GdkRectangle* screen_rect,
                           const GtkRequisition* menu_req,
                           GtkWidget* widget, const int y) {
  CHECK(screen_rect);
  CHECK(menu_req);
  // If the menu would run off the bottom of the screen, and there is enough
  // screen space upwards to accommodate the menu, then pop upwards. If there
  // is a widget, then also move the anchor point to the top of the widget
  // rather than the bottom.
  const int screen_top = screen_rect->y;
  const int screen_bottom = screen_rect->y + screen_rect->height;
  const int menu_bottom = y + menu_req->height;
  int alternate_y = y - menu_req->height;
  if (widget) {
    GtkAllocation allocation;
    gtk_widget_get_allocation(widget, &allocation);
    alternate_y -= allocation.height;
  }
  if (menu_bottom >= screen_bottom && alternate_y >= screen_top)
    return alternate_y;
  return y;
}

void PointMenuPositionFunc(GtkMenu* menu,
                           int* x,
                           int* y,
                           gboolean* push_in,
                           gpointer userdata) {
  *push_in = TRUE;

  gfx::Point* point = reinterpret_cast<gfx::Point*>(userdata);
  *x = point->x();
  *y = point->y();

  GtkRequisition menu_req;
  gtk_widget_size_request(GTK_WIDGET(menu), &menu_req);
  GdkScreen* screen;
  gdk_display_get_pointer(gdk_display_get_default(), &screen, NULL, NULL, NULL);
  gint monitor = gdk_screen_get_monitor_at_point(screen, *x, *y);

  GdkRectangle screen_rect;
  gdk_screen_get_monitor_geometry(screen, monitor, &screen_rect);

  *y = CalculateMenuYPosition(&screen_rect, &menu_req, NULL, *y);
}

}  // namespace

void Menu::Create(const base::DictionaryValue& option) {
  std::string type;
  if (option.GetString("type", &type) && type == "menubar")
    menu_ = gtk_menu_bar_new();
  else
    menu_ = gtk_menu_new();

  gtk_widget_show(menu_);
  g_object_ref_sink(G_OBJECT(menu_));
}

void Menu::Destroy() {
  gtk_widget_destroy(menu_);
  g_object_unref(G_OBJECT(menu_));
}

void Menu::Append(MenuItem* menu_item) {
  gtk_menu_shell_append(GTK_MENU_SHELL(menu_), menu_item->menu_item_);
}

void Menu::Insert(MenuItem* menu_item, int pos) {
  gtk_menu_shell_insert(GTK_MENU_SHELL(menu_), menu_item->menu_item_, pos);
}

void Menu::Remove(MenuItem* menu_item, int pos) {
  gtk_container_remove(GTK_CONTAINER(menu_), menu_item->menu_item_);
}

void Menu::Popup(int x, int y, content::Shell* shell) {
  GdkEventButton* event = shell->web_contents()->GetRenderWidgetHostView()->
      GetLastMouseDown();
  uint32_t triggering_event_time = event ? event->time : GDK_CURRENT_TIME;
  gfx::Point point(event->x_root, event->y_root);
  gtk_menu_popup(GTK_MENU(menu_), NULL, NULL,
                 PointMenuPositionFunc, &point,
                 3, triggering_event_time);
}

}  // namespace api
<commit_msg>fix gtk crash on contextmenu<commit_after>// Copyright (c) 2012 Intel Corp
// Copyright (c) 2012 The Chromium Authors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co
// pies of the Software, and to permit persons to whom the Software is furnished
//  to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in al
// l copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM
// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES
// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
//  OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH
// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#include "content/nw/src/api/menu/menu.h"

#include "base/logging.h"
#include "base/values.h"
#include "content/nw/src/api/menuitem/menuitem.h"
#include "content/nw/src/nw_shell.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/render_widget_host_view.h"
#include "ui/gfx/point.h"

namespace api {

namespace {

// Popup menus may get squished if they open up too close to the bottom of the
// screen. This function takes the size of the screen, the size of the menu,
// an optional widget, the Y position of the mouse click, and adjusts the popup
// menu's Y position to make it fit if it's possible to do so.
// Returns the new Y position of the popup menu.
int CalculateMenuYPosition(const GdkRectangle* screen_rect,
                           const GtkRequisition* menu_req,
                           GtkWidget* widget, const int y) {
  CHECK(screen_rect);
  CHECK(menu_req);
  // If the menu would run off the bottom of the screen, and there is enough
  // screen space upwards to accommodate the menu, then pop upwards. If there
  // is a widget, then also move the anchor point to the top of the widget
  // rather than the bottom.
  const int screen_top = screen_rect->y;
  const int screen_bottom = screen_rect->y + screen_rect->height;
  const int menu_bottom = y + menu_req->height;
  int alternate_y = y - menu_req->height;
  if (widget) {
    GtkAllocation allocation;
    gtk_widget_get_allocation(widget, &allocation);
    alternate_y -= allocation.height;
  }
  if (menu_bottom >= screen_bottom && alternate_y >= screen_top)
    return alternate_y;
  return y;
}

void PointMenuPositionFunc(GtkMenu* menu,
                           int* x,
                           int* y,
                           gboolean* push_in,
                           gpointer userdata) {
  *push_in = TRUE;

  gfx::Point* point = reinterpret_cast<gfx::Point*>(userdata);
  *x = point->x();
  *y = point->y();

  GtkRequisition menu_req;
  gtk_widget_size_request(GTK_WIDGET(menu), &menu_req);
  GdkScreen* screen;
  gdk_display_get_pointer(gdk_display_get_default(), &screen, NULL, NULL, NULL);
  gint monitor = gdk_screen_get_monitor_at_point(screen, *x, *y);

  GdkRectangle screen_rect;
  gdk_screen_get_monitor_geometry(screen, monitor, &screen_rect);

  *y = CalculateMenuYPosition(&screen_rect, &menu_req, NULL, *y);
}

}  // namespace

void Menu::Create(const base::DictionaryValue& option) {
  std::string type;
  if (option.GetString("type", &type) && type == "menubar")
    menu_ = gtk_menu_bar_new();
  else
    menu_ = gtk_menu_new();

  gtk_widget_show(menu_);
  g_object_ref_sink(G_OBJECT(menu_));
}

void Menu::Destroy() {
  gtk_widget_destroy(menu_);
  g_object_unref(G_OBJECT(menu_));
}

void Menu::Append(MenuItem* menu_item) {
  gtk_menu_shell_append(GTK_MENU_SHELL(menu_), menu_item->menu_item_);
}

void Menu::Insert(MenuItem* menu_item, int pos) {
  gtk_menu_shell_insert(GTK_MENU_SHELL(menu_), menu_item->menu_item_, pos);
}

void Menu::Remove(MenuItem* menu_item, int pos) {
  gtk_container_remove(GTK_CONTAINER(menu_), menu_item->menu_item_);
}

void Menu::Popup(int x, int y, content::Shell* shell) {
  GdkEventButton* event = shell->web_contents()->GetRenderWidgetHostView()->
      GetLastMouseDown();
  uint32_t triggering_event_time = event ? event->time : GDK_CURRENT_TIME;
  gfx::Point point;
  if (!event) {
    // gfx::Rect bounds = shell->web_contents()->GetRenderWidgetHostView()->GetViewBounds();
    // point = gfx::Point(x + bounds.x(), y + bounds.y());
    DVLOG(1) << "no last mouse down event";
    point = gfx::Point(x, y);
  }else
    point = gfx::Point(event->x_root, event->y_root);

  gtk_menu_popup(GTK_MENU(menu_), NULL, NULL,
                 PointMenuPositionFunc, &point,
                 3, triggering_event_time);
}

}  // namespace api
<|endoftext|>


<commit_before><?hh
abstract class BaseStore {
  protected $class;
  protected $db;

  protected static $instance;

  public function __construct(string $collection = null, string $class = null) {
    if (defined('static::COLLECTION') && defined('static::MODEL')) {
      $collection = static::COLLECTION;
      $class = static::MODEL;
    }

    invariant($collection && $class, 'Collection or class not provided');

    $this->collection = $collection;
    $this->class = $class;
    $this->db = MongoInstance::get($collection);
    static::$instance = $this;
  }

  protected static function i() {
    return new static();
  }

  public function db() {
    return static::i()->db;
  }

  public function find(
    array $query = [],
    ?int $skip = 0,
    ?int $limit = 0): BaseModel {
    $docs = static::i()->db->find($query);
    $class = static::i()->class;

    if ($skip !== null) {
      $docs = $docs->skip($skip);
    }

    if ($limit !== null) {
      $limit = $docs->limit($limit);
    }

    foreach ($docs as $doc) {
      yield new $class($doc);
    }
  }

  public function distinct(string $key, array $query = []) {
    $docs = static::i()->db->distinct($key, $query);
    return !is_array($docs) ? [] : $docs;
  }

  public function findOne(array $query): ?BaseModel {
    $doc = static::i()->db->findOne($query);
    $class = static::i()->class;
    return $doc ? new $class($doc) : null;
  }

  public function findById(MongoId $id): ?BaseModel {
    return static::i()->findOne(['_id' => $id]);
  }

  public function count(array $query): int {
    return static::i()->db->count($query);
  }

  protected function ensureType(BaseModel $item): bool {
    return class_exists(static::i()->class) && is_a($item, $this->class);
  }

  public function remove(BaseModel $item) {
    if (!static::i()->ensureType($item)) {
      throw new Exception(
        'Invalid object provided, expected ' . static::i()->class);
      return false;
    }

    if ($item == null) {
      return false;
    }

    try {
      if ($item->getID()) {
        static::i()->db->remove($item->document());
        return true;
      } else {
        return false;
      }
    } catch (MongoException $e) {
      l('MongoException:', $e->getMessage());
      return false;
    }
  }

  public function removeWhere($query = []) {
    try {
      static::i()->db->remove($query);
      return true;
    } catch (MongoException $e) {
      l('MongoException:', $e->getMessage());
      return false;
    }
  }

  public function removeById($id) {
    return static::i()->removeWhere(['_id' => mid($id)]);
  }

  public function aggregate(BaseAggregation $aggregation) {
    return call_user_func_array(
      [static::i()->db, 'aggregate'],
      $aggregation->getPipeline());
  }

  public function mapReduce(
    MongoCode $map,
    MongoCode $reduce,
    array $query = null,
    array $config = null) {

    $options = [
      'mapreduce' => static::i()->collection,
      'map' => $map,
      'reduce' => $reduce,
      'out' => ['inline' => true]];

    if ($query) {
      $options['query'] = $query;
    }

    if ($config) {
      unset($options['mapreduce']);
      unset($options['map']);
      unset($options['reduce']);
      unset($options['query']);
      $options = array_merge($options, $config);
    }

    $res = MongoInstance::get()->command($options);

    if (idx($res, 'ok')) {
      return $res;
    } else {
      l('MapReduce error:', $res);
      return null;
    }
  }

  public function save(BaseModel &$item) {
    if (!static::i()->ensureType($item)) {
      throw new Exception(
        'Invalid object provided, expected ' . static::i()->class);
      return false;
    }

    if ($item == null) {
      return false;
    }

    try {
      if (!$item->getID()) {
        $id = new MongoId();
        $item->setID($id);
        $document = $item->document();
        static::i()->db->insert($document);
      } else {
        $document = $item->document();
        static::i()->db->save($document);
      }
      return true;
    } catch (MongoException $e) {
      l('MongoException:', $e->getMessage());
      return false;
    }
    return true;
  }
}

class BaseStoreCursor {
  protected
    $class,
    $count,
    $cursor,
    $next;

  public function __construct($class, $count, $cursor, $skip, $limit) {
    $this->class = $class;
    $this->count = $count;
    $this->cursor = $cursor;
    $this->next = $count > $limit ? $skip + 1 : null;
  }

  public function count() {
    return $this->count;
  }

  public function nextPage() {
    return $this->next;
  }

  public function docs() {
    $class = $this->class;
    foreach ($this->cursor as $entry) {
      yield new $class($entry);
    }
  }
}

class BaseAggregation {
  protected $pipeline;
  public function __construct() {
    $this->pipeline = [];
  }

  public function getPipeline() {
    return $this->pipeline;
  }

  public function project(array $spec) {
    if (!empty($spec)) {
      $this->pipeline[] = ['$project' => $spec];
    }
    return $this;
  }

  public function match(array $spec) {
    if (!empty($spec)) {
      $this->pipeline[] = ['$match' => $spec];
    }
    return $this;
  }

  public function limit($limit) {
    $this->pipeline[] = ['$limit' => $limit];
    return $this;
  }

  public function skip($skip) {
    $this->pipeline[] = ['$skip' => $skip];
    return $this;
  }

  public function unwind($field) {
    $this->pipeline[] = ['$unwind' => '$' . $field];
    return $this;
  }

  public function group(array $spec) {
    if (!empty($spec)) {
      $this->pipeline[] = ['$group' => $spec];
    }
    return $this;
  }

  public function sort(array $spec) {
    if (!empty($spec)) {
      $this->pipeline[] = ['$sort' => $spec];
    }
    return $this;
  }

  public static function addToSet(string $field): array {
    return ['$addToSet' => '$' . $field];
  }

  public static function sum($value = 1): array {
    if (!is_numeric($value)) {
      $value = '$' . $value;
    }
    return ['$sum' => $value];
  }

  public function __call($name, $args) {
    $field = array_pop($args);
    switch ($name) {
      case 'addToSet':
      case 'first':
      case 'last':
      case 'max':
      case 'min':
      case 'avg':
        return ['$' . $name => '$' . $field];
    }

    throw new RuntimeException('Method not found: ' . $name);
  }

  public function push($field) {
    if (is_array($field)) {
      foreach ($field as &$f) {
        $f = '$' . $f;
      }
    } else {
      $field = '$' . $field;
    }

    return ['$push' => $field];
  }
}

abstract class BaseModel {
  public ?MongoId $_id;
  public function __construct(array<string, mixed> $document = []) {

    foreach ($document as $key => $value) {
      if (property_exists($this, $key)) {
        $this->$key = $key == '_id' ? mid($value) : $value;
      }
    }
  }

  public function document(): array<string, mixed> {
    return get_object_vars($this);
  }

  final public function getID(): ?MongoId {
    return $this->_id;
  }

  final public function setID(MongoId $_id): void {
    $this->_id = $_id;
  }

  public function __call($method, $args) {
    if (strpos($method, 'get') === 0) {
      $op = 'get';
    } elseif (strpos($method, 'set') === 0) {
      $op = 'set';
    } elseif (strpos($method, 'remove') === 0) {
      $op = 'remove';
    } elseif (strpos($method, 'has') === 0) {
      $op = 'has';
    } else {
      $e = sprintf('Method "%s" not found in %s', $method, get_called_class());
      throw new RuntimeException($e);
      return null;
    }

    // $method = preg_replace('/^(get|set|remove|has)/i', '', $method);
    $method = preg_replace('/^(get|set)/i', '', $method);

    preg_match_all(
      '!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!',
      $method,
      $matches);

    $ret = $matches[0];
    foreach ($ret as &$match) {
      $match = $match == strtoupper($match) ?
        strtolower($match) :
        lcfirst($match);
    }

    $field = implode('_', $ret);

    $arg = array_pop($args);

    switch ($op) {
      case 'set':
        $this->$field = $arg;
        return;

      case 'get':
        invariant(
          property_exists($this, $field),
          '%s is not a valid field for %s',
          $field,
          get_called_class());
        return $this->$field;
    }
  }
}
<commit_msg>added strict mode to BaseStore: user cannot set or get undefined properties<commit_after><?hh
abstract class BaseStore {
  protected $class;
  protected $db;

  protected static $instance;

  public function __construct(string $collection = null, string $class = null) {
    if (defined('static::COLLECTION') && defined('static::MODEL')) {
      $collection = static::COLLECTION;
      $class = static::MODEL;
    }

    invariant($collection && $class, 'Collection or class not provided');

    $this->collection = $collection;
    $this->class = $class;
    $this->db = MongoInstance::get($collection);
    static::$instance = $this;
  }

  protected static function i() {
    return new static();
  }

  public function db() {
    return static::i()->db;
  }

  public function find(
    array $query = [],
    ?int $skip = 0,
    ?int $limit = 0): BaseModel {
    $docs = static::i()->db->find($query);
    $class = static::i()->class;

    if ($skip !== null) {
      $docs = $docs->skip($skip);
    }

    if ($limit !== null) {
      $limit = $docs->limit($limit);
    }

    foreach ($docs as $doc) {
      yield new $class($doc);
    }
  }

  public function distinct(string $key, array $query = []) {
    $docs = static::i()->db->distinct($key, $query);
    return !is_array($docs) ? [] : $docs;
  }

  public function findOne(array $query): ?BaseModel {
    $doc = static::i()->db->findOne($query);
    $class = static::i()->class;
    return $doc ? new $class($doc) : null;
  }

  public function findById(MongoId $id): ?BaseModel {
    return static::i()->findOne(['_id' => $id]);
  }

  public function count(array $query): int {
    return static::i()->db->count($query);
  }

  protected function ensureType(BaseModel $item): bool {
    return class_exists(static::i()->class) && is_a($item, $this->class);
  }

  public function remove(BaseModel $item) {
    if (!static::i()->ensureType($item)) {
      throw new Exception(
        'Invalid object provided, expected ' . static::i()->class);
      return false;
    }

    if ($item == null) {
      return false;
    }

    try {
      if ($item->getID()) {
        static::i()->db->remove($item->document());
        return true;
      } else {
        return false;
      }
    } catch (MongoException $e) {
      l('MongoException:', $e->getMessage());
      return false;
    }
  }

  public function removeWhere($query = []) {
    try {
      static::i()->db->remove($query);
      return true;
    } catch (MongoException $e) {
      l('MongoException:', $e->getMessage());
      return false;
    }
  }

  public function removeById($id) {
    return static::i()->removeWhere(['_id' => mid($id)]);
  }

  public function aggregate(BaseAggregation $aggregation) {
    return call_user_func_array(
      [static::i()->db, 'aggregate'],
      $aggregation->getPipeline());
  }

  public function mapReduce(
    MongoCode $map,
    MongoCode $reduce,
    array $query = null,
    array $config = null) {

    $options = [
      'mapreduce' => static::i()->collection,
      'map' => $map,
      'reduce' => $reduce,
      'out' => ['inline' => true]];

    if ($query) {
      $options['query'] = $query;
    }

    if ($config) {
      unset($options['mapreduce']);
      unset($options['map']);
      unset($options['reduce']);
      unset($options['query']);
      $options = array_merge($options, $config);
    }

    $res = MongoInstance::get()->command($options);

    if (idx($res, 'ok')) {
      return $res;
    } else {
      l('MapReduce error:', $res);
      return null;
    }
  }

  public function save(BaseModel &$item) {
    if (!static::i()->ensureType($item)) {
      throw new Exception(
        'Invalid object provided, expected ' . static::i()->class);
      return false;
    }

    if ($item == null) {
      return false;
    }

    try {
      if (!$item->getID()) {
        $id = new MongoId();
        $item->setID($id);
        $document = $item->document();
        static::i()->db->insert($document);
      } else {
        $document = $item->document();
        static::i()->db->save($document);
      }
      return true;
    } catch (MongoException $e) {
      l('MongoException:', $e->getMessage());
      return false;
    }
    return true;
  }
}

class BaseStoreCursor {
  protected
    $class,
    $count,
    $cursor,
    $next;

  public function __construct($class, $count, $cursor, $skip, $limit) {
    $this->class = $class;
    $this->count = $count;
    $this->cursor = $cursor;
    $this->next = $count > $limit ? $skip + 1 : null;
  }

  public function count() {
    return $this->count;
  }

  public function nextPage() {
    return $this->next;
  }

  public function docs() {
    $class = $this->class;
    foreach ($this->cursor as $entry) {
      yield new $class($entry);
    }
  }
}

class BaseAggregation {
  protected $pipeline;
  public function __construct() {
    $this->pipeline = [];
  }

  public function getPipeline() {
    return $this->pipeline;
  }

  public function project(array $spec) {
    if (!empty($spec)) {
      $this->pipeline[] = ['$project' => $spec];
    }
    return $this;
  }

  public function match(array $spec) {
    if (!empty($spec)) {
      $this->pipeline[] = ['$match' => $spec];
    }
    return $this;
  }

  public function limit($limit) {
    $this->pipeline[] = ['$limit' => $limit];
    return $this;
  }

  public function skip($skip) {
    $this->pipeline[] = ['$skip' => $skip];
    return $this;
  }

  public function unwind($field) {
    $this->pipeline[] = ['$unwind' => '$' . $field];
    return $this;
  }

  public function group(array $spec) {
    if (!empty($spec)) {
      $this->pipeline[] = ['$group' => $spec];
    }
    return $this;
  }

  public function sort(array $spec) {
    if (!empty($spec)) {
      $this->pipeline[] = ['$sort' => $spec];
    }
    return $this;
  }

  public static function addToSet(string $field): array {
    return ['$addToSet' => '$' . $field];
  }

  public static function sum($value = 1): array {
    if (!is_numeric($value)) {
      $value = '$' . $value;
    }
    return ['$sum' => $value];
  }

  public function __call($name, $args) {
    $field = array_pop($args);
    switch ($name) {
      case 'addToSet':
      case 'first':
      case 'last':
      case 'max':
      case 'min':
      case 'avg':
        return ['$' . $name => '$' . $field];
    }

    throw new RuntimeException('Method not found: ' . $name);
  }

  public function push($field) {
    if (is_array($field)) {
      foreach ($field as &$f) {
        $f = '$' . $f;
      }
    } else {
      $field = '$' . $field;
    }

    return ['$push' => $field];
  }
}

abstract class BaseModel {
  public ?MongoId $_id;
  public function __construct(array<string, mixed> $document = []) {

    foreach ($document as $key => $value) {
      if (property_exists($this, $key)) {
        $this->$key = $key == '_id' ? mid($value) : $value;
      }
    }
  }

  public function __set($name, $value) {
    if (get_called_class() !== 'BaseModel') {
      invariant_violation(
        'Cannot set field %s in %s: field does not exist',
        $name,
        get_called_class());
    }
  }

  public function __get($name) {
    if (get_called_class() !== 'BaseModel') {
      invariant_violation(
        'Cannot set field %s in %s: field does not exist',
        $name,
        get_called_class());
    }
  }

  public function document(): array<string, mixed> {
    return get_object_vars($this);
  }

  final public function getID(): ?MongoId {
    return $this->_id;
  }

  final public function setID(MongoId $_id): void {
    $this->_id = $_id;
  }

  public function __call($method, $args) {
    if (strpos($method, 'get') === 0) {
      $op = 'get';
    } elseif (strpos($method, 'set') === 0) {
      $op = 'set';
    } elseif (strpos($method, 'remove') === 0) {
      $op = 'remove';
    } elseif (strpos($method, 'has') === 0) {
      $op = 'has';
    } else {
      $e = sprintf('Method "%s" not found in %s', $method, get_called_class());
      throw new RuntimeException($e);
      return null;
    }

    // $method = preg_replace('/^(get|set|remove|has)/i', '', $method);
    $method = preg_replace('/^(get|set)/i', '', $method);

    preg_match_all(
      '!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!',
      $method,
      $matches);

    $ret = $matches[0];
    foreach ($ret as &$match) {
      $match = $match == strtoupper($match) ?
        strtolower($match) :
        lcfirst($match);
    }

    $field = implode('_', $ret);

    $arg = array_pop($args);

    switch ($op) {
      case 'set':
        $this->$field = $arg;
        return;

      case 'get':
        invariant(
          property_exists($this, $field),
          '%s is not a valid field for %s',
          $field,
          get_called_class());
        return $this->$field;
    }
  }
}
<|endoftext|>


<commit_before>/*
 * This file is part of crash-reporter
 *
 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
 *
 * Contact: Ville Ilvonen <ville.p.ilvonen@nokia.com>
 * Author: Raimo Gratseff <ext-raimo.gratseff@nokia.com>
 *
 * Copyright (C) 2013 Jolla Ltd.
 * Contact: Jakub Adam <jakub.adam@jollamobile.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

// System includes.

#include <QDebug>
#include <QDBusConnection>

// User includes.

#include "creporterautouploader.h"
#include "creporternamespace.h"
#include "creporternwsessionmgr.h"
#include "creporterautouploaderdbusadaptor.h"
#include "creporteruploadqueue.h"
#include "creporteruploaditem.h"
#include "creporteruploadengine.h"
#include "creporternotification.h"
#include "creporterprivacysettingsmodel.h"

// ******** Class CReporterAutoUploaderPrivate ********

/*! @class CReporterAutoUploaderPrivate
  * @brief Private CReporterAutoUploaderPrivate class.
  *
  * @sa CReporterAutoUploader
  */
class CReporterAutoUploaderPrivate
{
    public:

        //! @arg Upload engine.
        CReporterUploadEngine *engine;
        //! @arg Upload queue.
        CReporterUploadQueue queue;
        //! @arg Is the service active.
        bool activated;
        //! @arg files that have been added to upload queue during this auto uploader session
        QStringList addedFiles;
        /*! Notification object associated with this uploader, giving user
         *  a notice of the progress.*/
        CReporterNotification *notification;
};

// ******** Class CReporterAutoUploader ********

// ----------------------------------------------------------------------------
// CReporterAutoUploader::CReporterAutoUploader
// ----------------------------------------------------------------------------
CReporterAutoUploader::CReporterAutoUploader() : d_ptr(new CReporterAutoUploaderPrivate)
{
    d_ptr->engine = 0;
    d_ptr->activated = false;
    d_ptr->notification = 0;
    // Create adaptor class. Needs to be taken from the stack.
    new CReporterAutoUploaderDBusAdaptor(this);
    // Register service name and object.
    QDBusConnection::sessionBus().registerService(CReporter::AutoUploaderServiceName);
    QDBusConnection::sessionBus().registerObject(CReporter::AutoUploaderObjectPath, this);
    qDebug() << __PRETTY_FUNCTION__ << "Started Auto Uploader service.";
}

// ----------------------------------------------------------------------------
// CReporterAutoUploader::~CReporterAutoUploader
// ----------------------------------------------------------------------------
CReporterAutoUploader::~CReporterAutoUploader()
{
    quit();
    if (d_ptr)
    {
        delete d_ptr;
        d_ptr = 0;
    }

    qDebug() << __PRETTY_FUNCTION__ << "Service closed.";

}

// ----------------------------------------------------------------------------
// CReporterAutoUploader::uploadFiles
// ----------------------------------------------------------------------------
bool CReporterAutoUploader::uploadFiles(const QStringList &fileList)
{
    qDebug() << __PRETTY_FUNCTION__ << "Received a list of files to upload.";

    if (fileList.isEmpty())
        return false;

    if (!d_ptr->activated)
    {
        d_ptr->engine = new CReporterUploadEngine(&d_ptr->queue);
        d_ptr->activated = true;
        connect(d_ptr->engine, SIGNAL(finished(int, int, int)), SLOT(engineFinished(int, int, int)));
    }

    if (!CReporterNwSessionMgr::unpaidConnectionAvailable()) {
        qDebug() << __PRETTY_FUNCTION__
                 << "No unpaid network connection available, aborting crash report upload.";
        QTimer::singleShot(0, this, SLOT(quit()));
        return false;
    }

    foreach (QString filename, fileList)
    {
        if (!d_ptr->addedFiles.contains(filename))
        {
            qDebug() << __PRETTY_FUNCTION__ << "Adding to upload queue: " << filename;
            // CReporterUploadQueue class will own the CReporterUploadItem instance.
            d_ptr->queue.enqueue(new CReporterUploadItem(filename));
            d_ptr->addedFiles << filename;
        }
        else
        {
            qDebug() << __PRETTY_FUNCTION__ << filename << "was not added to queue because it had already been added before";
        }
    }

    if (CReporterPrivacySettingsModel::instance()->notificationsEnabled())
    {
        if (d_ptr->notification) {
            d_ptr->notification->deleteLater();
        }

        d_ptr->notification = new CReporterNotification(
                CReporter::AutoUploaderNotificationEventType,
                QString("Crash report upload started"),
                QString("Uploading %1 report(s)").arg(fileList.count()),
                this);
    }

    return true;
}

// ----------------------------------------------------------------------------
// CReporterAutoUploader::quit
// ----------------------------------------------------------------------------
void CReporterAutoUploader::quit()
{
    qDebug() << __PRETTY_FUNCTION__ << "Quit auto uploader.";
    if (d_ptr->engine)
    {
        if (d_ptr->activated)
        {
            qDebug() << __PRETTY_FUNCTION__ << "Engine active -> cancelling";
            d_ptr->activated = false;
            d_ptr->engine->cancelAll();
        }
        qDebug() << __PRETTY_FUNCTION__ << "Deleting engine.";
        disconnect(d_ptr->engine, SIGNAL(finished(int, int, int)), this, SLOT(engineFinished(int, int, int)));
        d_ptr->engine->deleteLater();
        d_ptr->engine = 0;
    }
    qApp->quit();
}

// ----------------------------------------------------------------------------
// CReporterAutoUploader::engineFinished
// ----------------------------------------------------------------------------
void CReporterAutoUploader::engineFinished(int error, int sent, int total)
{
    QString message;

    // Construct message.
    switch (error)
    {
        case CReporterUploadEngine::NoError:
            //% "%1 report(s) uploaded successfully."
            message = qtTrId("qtn_%1_crash_reports_uploaded_successfully_text").arg(total);
            break;
        case CReporterUploadEngine::ProtocolError:
        case CReporterUploadEngine::ConnectionNotAvailable:
        case CReporterUploadEngine::ConnectionClosed:
            //% "Failed to upload report(s)."
            message = qtTrId("qtn_failed_to_send_crash_reports_text");
            //% "
Upload results: %1 files attempted %2 files succeeded."
            message += qtTrId("qtn_%1_files_attempted_%2_files_succeeded_text").arg(total).arg(sent);
            break;
        default:
        // We should never enter here.
        break;
    };

    if (error != CReporterUploadEngine::NoError)
    {
        // Add error reason.
        if (error == CReporterUploadEngine::ConnectionNotAvailable)
        {
            //% "
Reason: Failed to create internet connection."
            message += qtTrId("qtn_reason_failed_to_create_internet_connection_text");
        }
        else
        {
            QString reason = d_ptr->engine->lastError();
            if (!reason.isEmpty())
            {
                // If reason is available, append it.
                //% "
Reason: %1."
                message += qtTrId("qtn_reason_%1_text").arg(reason);
            }
            else
            {
                // Use default error.
                //% "
Reason: Unknown."
                message += qtTrId("qtn_reason_unknown_text");
            }
        }
    }

    if (CReporterPrivacySettingsModel::instance()->notificationsEnabled() &&
        d_ptr->notification) {
        QString summary;
        QString body;

        if (total > sent) {
            summary = "Failed to send all crash reports";
            body = QString("%1 out of %2 sent").arg(sent).arg(total);
        } else {
            summary = "Crash report upload finished";
            body = QString("%1 report(s) sent successfully").arg(sent);
        }

        d_ptr->notification->update(summary, body);
    }

    qDebug() << __PRETTY_FUNCTION__ << "Message: " << message;

    d_ptr->activated = false;
    quit();
}

<commit_msg>[autouploader] upload quickies regardless of network bearer<commit_after>/*
 * This file is part of crash-reporter
 *
 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
 *
 * Contact: Ville Ilvonen <ville.p.ilvonen@nokia.com>
 * Author: Raimo Gratseff <ext-raimo.gratseff@nokia.com>
 *
 * Copyright (C) 2013 Jolla Ltd.
 * Contact: Jakub Adam <jakub.adam@jollamobile.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

// System includes.

#include <QDebug>
#include <QDBusConnection>

// User includes.

#include "creporterautouploader.h"
#include "creporternamespace.h"
#include "creporternwsessionmgr.h"
#include "creporterautouploaderdbusadaptor.h"
#include "creporteruploadqueue.h"
#include "creporteruploaditem.h"
#include "creporteruploadengine.h"
#include "creporternotification.h"
#include "creporterprivacysettingsmodel.h"

// ******** Class CReporterAutoUploaderPrivate ********

/*! @class CReporterAutoUploaderPrivate
  * @brief Private CReporterAutoUploaderPrivate class.
  *
  * @sa CReporterAutoUploader
  */
class CReporterAutoUploaderPrivate
{
    public:

        //! @arg Upload engine.
        CReporterUploadEngine *engine;
        //! @arg Upload queue.
        CReporterUploadQueue queue;
        //! @arg Is the service active.
        bool activated;
        //! @arg files that have been added to upload queue during this auto uploader session
        QStringList addedFiles;
        /*! Notification object associated with this uploader, giving user
         *  a notice of the progress.*/
        CReporterNotification *notification;

        /*!
         * Checks whether given file list belongs to a quick feedback upload
         * request.
         *
         * @param fileList a file list received as an argument to @c uploadFiles()
         * @return @c true if list contains quick feedback, otherwise @c false
         */
        static bool isQuickieUpload(const QStringList &fileList);
};

bool CReporterAutoUploaderPrivate::isQuickieUpload(const QStringList &fileList)
{
    return (fileList.size() == 1) &&
           (QFileInfo(fileList[0]).baseName().startsWith("Quickie-"));
}

// ******** Class CReporterAutoUploader ********

// ----------------------------------------------------------------------------
// CReporterAutoUploader::CReporterAutoUploader
// ----------------------------------------------------------------------------
CReporterAutoUploader::CReporterAutoUploader() : d_ptr(new CReporterAutoUploaderPrivate)
{
    d_ptr->engine = 0;
    d_ptr->activated = false;
    d_ptr->notification = 0;
    // Create adaptor class. Needs to be taken from the stack.
    new CReporterAutoUploaderDBusAdaptor(this);
    // Register service name and object.
    QDBusConnection::sessionBus().registerService(CReporter::AutoUploaderServiceName);
    QDBusConnection::sessionBus().registerObject(CReporter::AutoUploaderObjectPath, this);
    qDebug() << __PRETTY_FUNCTION__ << "Started Auto Uploader service.";
}

// ----------------------------------------------------------------------------
// CReporterAutoUploader::~CReporterAutoUploader
// ----------------------------------------------------------------------------
CReporterAutoUploader::~CReporterAutoUploader()
{
    quit();
    if (d_ptr)
    {
        delete d_ptr;
        d_ptr = 0;
    }

    qDebug() << __PRETTY_FUNCTION__ << "Service closed.";

}

// ----------------------------------------------------------------------------
// CReporterAutoUploader::uploadFiles
// ----------------------------------------------------------------------------
bool CReporterAutoUploader::uploadFiles(const QStringList &fileList)
{
    qDebug() << __PRETTY_FUNCTION__ << "Received a list of files to upload.";

    if (fileList.isEmpty())
        return false;

    if (!d_ptr->activated)
    {
        d_ptr->engine = new CReporterUploadEngine(&d_ptr->queue);
        d_ptr->activated = true;
        connect(d_ptr->engine, SIGNAL(finished(int, int, int)), SLOT(engineFinished(int, int, int)));
    }

    if (!CReporterAutoUploaderPrivate::isQuickieUpload(fileList) &&
        !CReporterNwSessionMgr::unpaidConnectionAvailable()) {
        qDebug() << __PRETTY_FUNCTION__
                 << "No unpaid network connection available, aborting crash report upload.";
        QTimer::singleShot(0, this, SLOT(quit()));
        return false;
    }

    foreach (QString filename, fileList)
    {
        if (!d_ptr->addedFiles.contains(filename))
        {
            qDebug() << __PRETTY_FUNCTION__ << "Adding to upload queue: " << filename;
            // CReporterUploadQueue class will own the CReporterUploadItem instance.
            d_ptr->queue.enqueue(new CReporterUploadItem(filename));
            d_ptr->addedFiles << filename;
        }
        else
        {
            qDebug() << __PRETTY_FUNCTION__ << filename << "was not added to queue because it had already been added before";
        }
    }

    if (CReporterPrivacySettingsModel::instance()->notificationsEnabled())
    {
        if (d_ptr->notification) {
            d_ptr->notification->deleteLater();
        }

        d_ptr->notification = new CReporterNotification(
                CReporter::AutoUploaderNotificationEventType,
                QString("Crash report upload started"),
                QString("Uploading %1 report(s)").arg(fileList.count()),
                this);
    }

    return true;
}

// ----------------------------------------------------------------------------
// CReporterAutoUploader::quit
// ----------------------------------------------------------------------------
void CReporterAutoUploader::quit()
{
    qDebug() << __PRETTY_FUNCTION__ << "Quit auto uploader.";
    if (d_ptr->engine)
    {
        if (d_ptr->activated)
        {
            qDebug() << __PRETTY_FUNCTION__ << "Engine active -> cancelling";
            d_ptr->activated = false;
            d_ptr->engine->cancelAll();
        }
        qDebug() << __PRETTY_FUNCTION__ << "Deleting engine.";
        disconnect(d_ptr->engine, SIGNAL(finished(int, int, int)), this, SLOT(engineFinished(int, int, int)));
        d_ptr->engine->deleteLater();
        d_ptr->engine = 0;
    }
    qApp->quit();
}

// ----------------------------------------------------------------------------
// CReporterAutoUploader::engineFinished
// ----------------------------------------------------------------------------
void CReporterAutoUploader::engineFinished(int error, int sent, int total)
{
    QString message;

    // Construct message.
    switch (error)
    {
        case CReporterUploadEngine::NoError:
            //% "%1 report(s) uploaded successfully."
            message = qtTrId("qtn_%1_crash_reports_uploaded_successfully_text").arg(total);
            break;
        case CReporterUploadEngine::ProtocolError:
        case CReporterUploadEngine::ConnectionNotAvailable:
        case CReporterUploadEngine::ConnectionClosed:
            //% "Failed to upload report(s)."
            message = qtTrId("qtn_failed_to_send_crash_reports_text");
            //% "
Upload results: %1 files attempted %2 files succeeded."
            message += qtTrId("qtn_%1_files_attempted_%2_files_succeeded_text").arg(total).arg(sent);
            break;
        default:
        // We should never enter here.
        break;
    };

    if (error != CReporterUploadEngine::NoError)
    {
        // Add error reason.
        if (error == CReporterUploadEngine::ConnectionNotAvailable)
        {
            //% "
Reason: Failed to create internet connection."
            message += qtTrId("qtn_reason_failed_to_create_internet_connection_text");
        }
        else
        {
            QString reason = d_ptr->engine->lastError();
            if (!reason.isEmpty())
            {
                // If reason is available, append it.
                //% "
Reason: %1."
                message += qtTrId("qtn_reason_%1_text").arg(reason);
            }
            else
            {
                // Use default error.
                //% "
Reason: Unknown."
                message += qtTrId("qtn_reason_unknown_text");
            }
        }
    }

    if (CReporterPrivacySettingsModel::instance()->notificationsEnabled() &&
        d_ptr->notification) {
        QString summary;
        QString body;

        if (total > sent) {
            summary = "Failed to send all crash reports";
            body = QString("%1 out of %2 sent").arg(sent).arg(total);
        } else {
            summary = "Crash report upload finished";
            body = QString("%1 report(s) sent successfully").arg(sent);
        }

        d_ptr->notification->update(summary, body);
    }

    qDebug() << __PRETTY_FUNCTION__ << "Message: " << message;

    d_ptr->activated = false;
    quit();
}

<|endoftext|>


<commit_before>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/download/download_query.h"

#include <algorithm>
#include <string>
#include <vector>

#include "base/bind.h"
#include "base/callback.h"
#include "base/file_path.h"
#include "base/i18n/case_conversion.h"
#include "base/i18n/string_search.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/stl_util.h"
#include "base/string16.h"
#include "base/string_split.h"
#include "base/time.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/download_item.h"
#include "googleurl/src/gurl.h"
#include "net/base/net_util.h"
#include "unicode/regex.h"

using content::DownloadDangerType;
using content::DownloadItem;

namespace {

// Templatized base::Value::GetAs*().
template <typename T> bool GetAs(const base::Value& in, T* out);
template<> bool GetAs(const base::Value& in, bool* out) {
  return in.GetAsBoolean(out);
}
template<> bool GetAs(const base::Value& in, int* out) {
  return in.GetAsInteger(out);
}
template<> bool GetAs(const base::Value& in, std::string* out) {
  return in.GetAsString(out);
}
template<> bool GetAs(const base::Value& in, string16* out) {
  return in.GetAsString(out);
}

// The next several functions are helpers for making Callbacks that access
// DownloadItem fields.

static bool MatchesQuery(const string16& query, const DownloadItem& item) {
  if (query.empty())
    return true;

  DCHECK_EQ(query, base::i18n::ToLower(query));

  string16 url_raw(UTF8ToUTF16(item.GetOriginalUrl().spec()));
  if (base::i18n::StringSearchIgnoringCaseAndAccents(
          query, url_raw, NULL, NULL)) {
    return true;
  }

  string16 url_formatted = url_raw;
  if (item.GetBrowserContext()) {
    url_formatted = net::FormatUrl(
        item.GetOriginalUrl(),
        content::GetContentClient()->browser()->GetAcceptLangs(
            item.GetBrowserContext()));
  }
  if (base::i18n::StringSearchIgnoringCaseAndAccents(
        query, url_formatted, NULL, NULL)) {
    return true;
  }

  string16 path(item.GetTargetFilePath().LossyDisplayName());
  return base::i18n::StringSearchIgnoringCaseAndAccents(
      query, path, NULL, NULL);
}

static int GetStartTime(const DownloadItem& item) {
  return (item.GetStartTime() - base::Time::UnixEpoch()).InMilliseconds();
}

static bool GetDangerAccepted(const DownloadItem& item) {
  return (item.GetSafetyState() == DownloadItem::DANGEROUS_BUT_VALIDATED);
}

static string16 GetFilename(const DownloadItem& item) {
  // This filename will be compared with strings that could be passed in by the
  // user, who only sees LossyDisplayNames.
  return item.GetTargetFilePath().LossyDisplayName();
}

static std::string GetFilenameUTF8(const DownloadItem& item) {
  return UTF16ToUTF8(GetFilename(item));
}

static std::string GetUrl(const DownloadItem& item) {
  return item.GetOriginalUrl().spec();
}

static DownloadItem::DownloadState GetState(const DownloadItem& item) {
  return item.GetState();
}

static DownloadDangerType GetDangerType(const DownloadItem& item) {
  return item.GetDangerType();
}

static int GetReceivedBytes(const DownloadItem& item) {
  return item.GetReceivedBytes();
}

static int GetTotalBytes(const DownloadItem& item) {
  return item.GetTotalBytes();
}

static std::string GetMimeType(const DownloadItem& item) {
  return item.GetMimeType();
}

static bool IsPaused(const DownloadItem& item) {
  return item.IsPaused();
}

enum ComparisonType {LT, EQ, GT};

// Returns true if |item| matches the filter specified by |value|, |cmptype|,
// and |accessor|. |accessor| is conceptually a function that takes a
// DownloadItem and returns one of its fields, which is then compared to
// |value|.
template<typename ValueType>
static bool FieldMatches(
    const ValueType& value,
    ComparisonType cmptype,
    const base::Callback<ValueType(const DownloadItem&)>& accessor,
    const DownloadItem& item) {
  switch (cmptype) {
    case LT: return accessor.Run(item) < value;
    case EQ: return accessor.Run(item) == value;
    case GT: return accessor.Run(item) > value;
  }
  NOTREACHED();
  return false;
}

// Helper for building a Callback to FieldMatches<>().
template <typename ValueType> DownloadQuery::FilterCallback BuildFilter(
    const base::Value& value, ComparisonType cmptype,
    ValueType (*accessor)(const DownloadItem&)) {
  ValueType cpp_value;
  if (!GetAs(value, &cpp_value)) return DownloadQuery::FilterCallback();
  return base::Bind(&FieldMatches<ValueType>, cpp_value, cmptype,
                    base::Bind(accessor));
}

// Returns true if |accessor.Run(item)| matches |pattern|.
static bool FindRegex(
    icu::RegexPattern* pattern,
    const base::Callback<std::string(const DownloadItem&)>& accessor,
    const DownloadItem& item) {
  icu::UnicodeString input(accessor.Run(item).c_str());
  UErrorCode status = U_ZERO_ERROR;
  scoped_ptr<icu::RegexMatcher> matcher(pattern->matcher(input, status));
  return matcher->find() == TRUE;  // Ugh, VS complains bool != UBool.
}

// Helper for building a Callback to FindRegex().
DownloadQuery::FilterCallback BuildRegexFilter(
    const base::Value& regex_value,
    std::string (*accessor)(const DownloadItem&)) {
  std::string regex_str;
  if (!GetAs(regex_value, &regex_str)) return DownloadQuery::FilterCallback();
  UParseError re_err;
  UErrorCode re_status = U_ZERO_ERROR;
  scoped_ptr<icu::RegexPattern> pattern(icu::RegexPattern::compile(
      icu::UnicodeString::fromUTF8(regex_str.c_str()), re_err, re_status));
  if (!U_SUCCESS(re_status)) return DownloadQuery::FilterCallback();
  return base::Bind(&FindRegex, base::Owned(pattern.release()),
                    base::Bind(accessor));
}

// Returns a ComparisonType to indicate whether a field in |left| is less than,
// greater than or equal to the same field in |right|.
template<typename ValueType>
static ComparisonType Compare(
    const base::Callback<ValueType(const DownloadItem&)>& accessor,
    const DownloadItem& left, const DownloadItem& right) {
  ValueType left_value = accessor.Run(left);
  ValueType right_value = accessor.Run(right);
  if (left_value > right_value) return GT;
  if (left_value < right_value) return LT;
  DCHECK_EQ(left_value, right_value);
  return EQ;
}

}  // anonymous namespace

DownloadQuery::DownloadQuery()
  : limit_(kuint32max) {
}

DownloadQuery::~DownloadQuery() {
}

// AddFilter() pushes a new FilterCallback to filters_. Most FilterCallbacks are
// Callbacks to FieldMatches<>(). Search() iterates over given DownloadItems,
// discarding items for which any filter returns false. A DownloadQuery may have
// zero or more FilterCallbacks.

bool DownloadQuery::AddFilter(const DownloadQuery::FilterCallback& value) {
  if (value.is_null()) return false;
  filters_.push_back(value);
  return true;
}

void DownloadQuery::AddFilter(DownloadItem::DownloadState state) {
  AddFilter(base::Bind(&FieldMatches<DownloadItem::DownloadState>, state, EQ,
      base::Bind(&GetState)));
}

void DownloadQuery::AddFilter(DownloadDangerType danger) {
  AddFilter(base::Bind(&FieldMatches<DownloadDangerType>, danger, EQ,
      base::Bind(&GetDangerType)));
}

bool DownloadQuery::AddFilter(DownloadQuery::FilterType type,
                              const base::Value& value) {
  switch (type) {
    case FILTER_BYTES_RECEIVED:
      return AddFilter(BuildFilter<int>(value, EQ, &GetReceivedBytes));
    case FILTER_DANGER_ACCEPTED:
      return AddFilter(BuildFilter<bool>(value, EQ, &GetDangerAccepted));
    case FILTER_FILENAME:
      return AddFilter(BuildFilter<string16>(value, EQ, &GetFilename));
    case FILTER_FILENAME_REGEX:
      return AddFilter(BuildRegexFilter(value, &GetFilenameUTF8));
    case FILTER_MIME:
      return AddFilter(BuildFilter<std::string>(value, EQ, &GetMimeType));
    case FILTER_PAUSED:
      return AddFilter(BuildFilter<bool>(value, EQ, &IsPaused));
    case FILTER_QUERY: {
      string16 query;
      return GetAs(value, &query) &&
             AddFilter(base::Bind(&MatchesQuery, query));
    }
    case FILTER_STARTED_AFTER:
      return AddFilter(BuildFilter<int>(value, GT, &GetStartTime));
    case FILTER_STARTED_BEFORE:
      return AddFilter(BuildFilter<int>(value, LT, &GetStartTime));
    case FILTER_START_TIME:
      return AddFilter(BuildFilter<int>(value, EQ, &GetStartTime));
    case FILTER_TOTAL_BYTES:
      return AddFilter(BuildFilter<int>(value, EQ, &GetTotalBytes));
    case FILTER_TOTAL_BYTES_GREATER:
      return AddFilter(BuildFilter<int>(value, GT, &GetTotalBytes));
    case FILTER_TOTAL_BYTES_LESS:
      return AddFilter(BuildFilter<int>(value, LT, &GetTotalBytes));
    case FILTER_URL:
      return AddFilter(BuildFilter<std::string>(value, EQ, &GetUrl));
    case FILTER_URL_REGEX:
      return AddFilter(BuildRegexFilter(value, &GetUrl));
  }
  return false;
}

bool DownloadQuery::Matches(const DownloadItem& item) const {
  for (FilterCallbackVector::const_iterator filter = filters_.begin();
        filter != filters_.end(); ++filter) {
    if (!filter->Run(item))
      return false;
  }
  return true;
}

// AddSorter() creates a Sorter and pushes it onto sorters_. A Sorter is a
// direction and a Callback to Compare<>(). After filtering, Search() makes a
// DownloadComparator functor from the sorters_ and passes the
// DownloadComparator to std::partial_sort. std::partial_sort calls the
// DownloadComparator with different pairs of DownloadItems.  DownloadComparator
// iterates over the sorters until a callback returns ComparisonType LT or GT.
// DownloadComparator returns true or false depending on that ComparisonType and
// the sorter's direction in order to indicate to std::partial_sort whether the
// left item is after or before the right item. If all sorters return EQ, then
// DownloadComparator compares GetId. A DownloadQuery may have zero or more
// Sorters, but there is one DownloadComparator per call to Search().

struct DownloadQuery::Sorter {
  typedef base::Callback<ComparisonType(
      const DownloadItem&, const DownloadItem&)> SortType;

  template<typename ValueType>
  static Sorter Build(DownloadQuery::SortDirection adirection,
                         ValueType (*accessor)(const DownloadItem&)) {
    return Sorter(adirection, base::Bind(&Compare<ValueType>,
        base::Bind(accessor)));
  }

  Sorter(DownloadQuery::SortDirection adirection,
            const SortType& asorter)
    : direction(adirection),
      sorter(asorter) {
  }
  ~Sorter() {}

  DownloadQuery::SortDirection direction;
  SortType sorter;
};

class DownloadQuery::DownloadComparator {
 public:
  explicit DownloadComparator(const DownloadQuery::SorterVector& terms)
    : terms_(terms) {
  }

  // Returns true if |left| sorts before |right|.
  bool operator() (const DownloadItem* left, const DownloadItem* right);

 private:
  const DownloadQuery::SorterVector& terms_;

  // std::sort requires this class to be copyable.
};

bool DownloadQuery::DownloadComparator::operator() (
    const DownloadItem* left, const DownloadItem* right) {
  for (DownloadQuery::SorterVector::const_iterator term = terms_.begin();
       term != terms_.end(); ++term) {
    switch (term->sorter.Run(*left, *right)) {
      case LT: return term->direction == DownloadQuery::ASCENDING;
      case GT: return term->direction == DownloadQuery::DESCENDING;
      case EQ: break;  // break the switch but not the loop
    }
  }
  CHECK_NE(left->GetId(), right->GetId());
  return left->GetId() < right->GetId();
}

void DownloadQuery::AddSorter(DownloadQuery::SortType type,
                              DownloadQuery::SortDirection direction) {
  switch (type) {
    case SORT_START_TIME:
      sorters_.push_back(Sorter::Build<int>(direction, &GetStartTime));
      break;
    case SORT_URL:
      sorters_.push_back(Sorter::Build<std::string>(direction, &GetUrl));
      break;
    case SORT_FILENAME:
      sorters_.push_back(Sorter::Build<string16>(direction, &GetFilename));
      break;
    case SORT_DANGER:
      sorters_.push_back(Sorter::Build<DownloadDangerType>(
          direction, &GetDangerType));
      break;
    case SORT_DANGER_ACCEPTED:
      sorters_.push_back(Sorter::Build<bool>(direction, &GetDangerAccepted));
      break;
    case SORT_STATE:
      sorters_.push_back(Sorter::Build<DownloadItem::DownloadState>(
          direction, &GetState));
      break;
    case SORT_PAUSED:
      sorters_.push_back(Sorter::Build<bool>(direction, &IsPaused));
      break;
    case SORT_MIME:
      sorters_.push_back(Sorter::Build<std::string>(direction, &GetMimeType));
      break;
    case SORT_BYTES_RECEIVED:
      sorters_.push_back(Sorter::Build<int>(direction, &GetReceivedBytes));
      break;
    case SORT_TOTAL_BYTES:
      sorters_.push_back(Sorter::Build<int>(direction, &GetTotalBytes));
      break;
  }
}

void DownloadQuery::FinishSearch(DownloadQuery::DownloadVector* results) const {
  if (!sorters_.empty())
    std::partial_sort(results->begin(),
                      results->begin() + std::min(limit_, results->size()),
                      results->end(),
                      DownloadComparator(sorters_));
  if (results->size() > limit_)
    results->resize(limit_);
}
<commit_msg>Fix sorting downloads on chrome://downloads. BUG=151756<commit_after>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/download/download_query.h"

#include <algorithm>
#include <string>
#include <vector>

#include "base/bind.h"
#include "base/callback.h"
#include "base/file_path.h"
#include "base/i18n/case_conversion.h"
#include "base/i18n/string_search.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/stl_util.h"
#include "base/string16.h"
#include "base/string_split.h"
#include "base/time.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/download_item.h"
#include "googleurl/src/gurl.h"
#include "net/base/net_util.h"
#include "unicode/regex.h"

using content::DownloadDangerType;
using content::DownloadItem;

namespace {

// Templatized base::Value::GetAs*().
template <typename T> bool GetAs(const base::Value& in, T* out);
template<> bool GetAs(const base::Value& in, bool* out) {
  return in.GetAsBoolean(out);
}
template<> bool GetAs(const base::Value& in, int* out) {
  return in.GetAsInteger(out);
}
template<> bool GetAs(const base::Value& in, std::string* out) {
  return in.GetAsString(out);
}
template<> bool GetAs(const base::Value& in, string16* out) {
  return in.GetAsString(out);
}

// The next several functions are helpers for making Callbacks that access
// DownloadItem fields.

static bool MatchesQuery(const string16& query, const DownloadItem& item) {
  if (query.empty())
    return true;

  DCHECK_EQ(query, base::i18n::ToLower(query));

  string16 url_raw(UTF8ToUTF16(item.GetOriginalUrl().spec()));
  if (base::i18n::StringSearchIgnoringCaseAndAccents(
          query, url_raw, NULL, NULL)) {
    return true;
  }

  string16 url_formatted = url_raw;
  if (item.GetBrowserContext()) {
    url_formatted = net::FormatUrl(
        item.GetOriginalUrl(),
        content::GetContentClient()->browser()->GetAcceptLangs(
            item.GetBrowserContext()));
  }
  if (base::i18n::StringSearchIgnoringCaseAndAccents(
        query, url_formatted, NULL, NULL)) {
    return true;
  }

  string16 path(item.GetTargetFilePath().LossyDisplayName());
  return base::i18n::StringSearchIgnoringCaseAndAccents(
      query, path, NULL, NULL);
}

static int64 GetStartTimeMsEpoch(const DownloadItem& item) {
  return (item.GetStartTime() - base::Time::UnixEpoch()).InMilliseconds();
}

// TODO(benjhayden) These timestamps don't fit in int32, and base::Value doesn't
// support int64. Use ISO 8601 date-time strings instead.
static int GetStartTime(const DownloadItem& item) {
  return (item.GetStartTime() - base::Time::UnixEpoch()).InMilliseconds();
}

static bool GetDangerAccepted(const DownloadItem& item) {
  return (item.GetSafetyState() == DownloadItem::DANGEROUS_BUT_VALIDATED);
}

static string16 GetFilename(const DownloadItem& item) {
  // This filename will be compared with strings that could be passed in by the
  // user, who only sees LossyDisplayNames.
  return item.GetTargetFilePath().LossyDisplayName();
}

static std::string GetFilenameUTF8(const DownloadItem& item) {
  return UTF16ToUTF8(GetFilename(item));
}

static std::string GetUrl(const DownloadItem& item) {
  return item.GetOriginalUrl().spec();
}

static DownloadItem::DownloadState GetState(const DownloadItem& item) {
  return item.GetState();
}

static DownloadDangerType GetDangerType(const DownloadItem& item) {
  return item.GetDangerType();
}

static int GetReceivedBytes(const DownloadItem& item) {
  return item.GetReceivedBytes();
}

static int GetTotalBytes(const DownloadItem& item) {
  return item.GetTotalBytes();
}

static std::string GetMimeType(const DownloadItem& item) {
  return item.GetMimeType();
}

static bool IsPaused(const DownloadItem& item) {
  return item.IsPaused();
}

enum ComparisonType {LT, EQ, GT};

// Returns true if |item| matches the filter specified by |value|, |cmptype|,
// and |accessor|. |accessor| is conceptually a function that takes a
// DownloadItem and returns one of its fields, which is then compared to
// |value|.
template<typename ValueType>
static bool FieldMatches(
    const ValueType& value,
    ComparisonType cmptype,
    const base::Callback<ValueType(const DownloadItem&)>& accessor,
    const DownloadItem& item) {
  switch (cmptype) {
    case LT: return accessor.Run(item) < value;
    case EQ: return accessor.Run(item) == value;
    case GT: return accessor.Run(item) > value;
  }
  NOTREACHED();
  return false;
}

// Helper for building a Callback to FieldMatches<>().
template <typename ValueType> DownloadQuery::FilterCallback BuildFilter(
    const base::Value& value, ComparisonType cmptype,
    ValueType (*accessor)(const DownloadItem&)) {
  ValueType cpp_value;
  if (!GetAs(value, &cpp_value)) return DownloadQuery::FilterCallback();
  return base::Bind(&FieldMatches<ValueType>, cpp_value, cmptype,
                    base::Bind(accessor));
}

// Returns true if |accessor.Run(item)| matches |pattern|.
static bool FindRegex(
    icu::RegexPattern* pattern,
    const base::Callback<std::string(const DownloadItem&)>& accessor,
    const DownloadItem& item) {
  icu::UnicodeString input(accessor.Run(item).c_str());
  UErrorCode status = U_ZERO_ERROR;
  scoped_ptr<icu::RegexMatcher> matcher(pattern->matcher(input, status));
  return matcher->find() == TRUE;  // Ugh, VS complains bool != UBool.
}

// Helper for building a Callback to FindRegex().
DownloadQuery::FilterCallback BuildRegexFilter(
    const base::Value& regex_value,
    std::string (*accessor)(const DownloadItem&)) {
  std::string regex_str;
  if (!GetAs(regex_value, &regex_str)) return DownloadQuery::FilterCallback();
  UParseError re_err;
  UErrorCode re_status = U_ZERO_ERROR;
  scoped_ptr<icu::RegexPattern> pattern(icu::RegexPattern::compile(
      icu::UnicodeString::fromUTF8(regex_str.c_str()), re_err, re_status));
  if (!U_SUCCESS(re_status)) return DownloadQuery::FilterCallback();
  return base::Bind(&FindRegex, base::Owned(pattern.release()),
                    base::Bind(accessor));
}

// Returns a ComparisonType to indicate whether a field in |left| is less than,
// greater than or equal to the same field in |right|.
template<typename ValueType>
static ComparisonType Compare(
    const base::Callback<ValueType(const DownloadItem&)>& accessor,
    const DownloadItem& left, const DownloadItem& right) {
  ValueType left_value = accessor.Run(left);
  ValueType right_value = accessor.Run(right);
  if (left_value > right_value) return GT;
  if (left_value < right_value) return LT;
  DCHECK_EQ(left_value, right_value);
  return EQ;
}

}  // anonymous namespace

DownloadQuery::DownloadQuery()
  : limit_(kuint32max) {
}

DownloadQuery::~DownloadQuery() {
}

// AddFilter() pushes a new FilterCallback to filters_. Most FilterCallbacks are
// Callbacks to FieldMatches<>(). Search() iterates over given DownloadItems,
// discarding items for which any filter returns false. A DownloadQuery may have
// zero or more FilterCallbacks.

bool DownloadQuery::AddFilter(const DownloadQuery::FilterCallback& value) {
  if (value.is_null()) return false;
  filters_.push_back(value);
  return true;
}

void DownloadQuery::AddFilter(DownloadItem::DownloadState state) {
  AddFilter(base::Bind(&FieldMatches<DownloadItem::DownloadState>, state, EQ,
      base::Bind(&GetState)));
}

void DownloadQuery::AddFilter(DownloadDangerType danger) {
  AddFilter(base::Bind(&FieldMatches<DownloadDangerType>, danger, EQ,
      base::Bind(&GetDangerType)));
}

bool DownloadQuery::AddFilter(DownloadQuery::FilterType type,
                              const base::Value& value) {
  switch (type) {
    case FILTER_BYTES_RECEIVED:
      return AddFilter(BuildFilter<int>(value, EQ, &GetReceivedBytes));
    case FILTER_DANGER_ACCEPTED:
      return AddFilter(BuildFilter<bool>(value, EQ, &GetDangerAccepted));
    case FILTER_FILENAME:
      return AddFilter(BuildFilter<string16>(value, EQ, &GetFilename));
    case FILTER_FILENAME_REGEX:
      return AddFilter(BuildRegexFilter(value, &GetFilenameUTF8));
    case FILTER_MIME:
      return AddFilter(BuildFilter<std::string>(value, EQ, &GetMimeType));
    case FILTER_PAUSED:
      return AddFilter(BuildFilter<bool>(value, EQ, &IsPaused));
    case FILTER_QUERY: {
      string16 query;
      return GetAs(value, &query) &&
             AddFilter(base::Bind(&MatchesQuery, query));
    }
    case FILTER_STARTED_AFTER:
      return AddFilter(BuildFilter<int>(value, GT, &GetStartTime));
    case FILTER_STARTED_BEFORE:
      return AddFilter(BuildFilter<int>(value, LT, &GetStartTime));
    case FILTER_START_TIME:
      return AddFilter(BuildFilter<int>(value, EQ, &GetStartTime));
    case FILTER_TOTAL_BYTES:
      return AddFilter(BuildFilter<int>(value, EQ, &GetTotalBytes));
    case FILTER_TOTAL_BYTES_GREATER:
      return AddFilter(BuildFilter<int>(value, GT, &GetTotalBytes));
    case FILTER_TOTAL_BYTES_LESS:
      return AddFilter(BuildFilter<int>(value, LT, &GetTotalBytes));
    case FILTER_URL:
      return AddFilter(BuildFilter<std::string>(value, EQ, &GetUrl));
    case FILTER_URL_REGEX:
      return AddFilter(BuildRegexFilter(value, &GetUrl));
  }
  return false;
}

bool DownloadQuery::Matches(const DownloadItem& item) const {
  for (FilterCallbackVector::const_iterator filter = filters_.begin();
        filter != filters_.end(); ++filter) {
    if (!filter->Run(item))
      return false;
  }
  return true;
}

// AddSorter() creates a Sorter and pushes it onto sorters_. A Sorter is a
// direction and a Callback to Compare<>(). After filtering, Search() makes a
// DownloadComparator functor from the sorters_ and passes the
// DownloadComparator to std::partial_sort. std::partial_sort calls the
// DownloadComparator with different pairs of DownloadItems.  DownloadComparator
// iterates over the sorters until a callback returns ComparisonType LT or GT.
// DownloadComparator returns true or false depending on that ComparisonType and
// the sorter's direction in order to indicate to std::partial_sort whether the
// left item is after or before the right item. If all sorters return EQ, then
// DownloadComparator compares GetId. A DownloadQuery may have zero or more
// Sorters, but there is one DownloadComparator per call to Search().

struct DownloadQuery::Sorter {
  typedef base::Callback<ComparisonType(
      const DownloadItem&, const DownloadItem&)> SortType;

  template<typename ValueType>
  static Sorter Build(DownloadQuery::SortDirection adirection,
                         ValueType (*accessor)(const DownloadItem&)) {
    return Sorter(adirection, base::Bind(&Compare<ValueType>,
        base::Bind(accessor)));
  }

  Sorter(DownloadQuery::SortDirection adirection,
            const SortType& asorter)
    : direction(adirection),
      sorter(asorter) {
  }
  ~Sorter() {}

  DownloadQuery::SortDirection direction;
  SortType sorter;
};

class DownloadQuery::DownloadComparator {
 public:
  explicit DownloadComparator(const DownloadQuery::SorterVector& terms)
    : terms_(terms) {
  }

  // Returns true if |left| sorts before |right|.
  bool operator() (const DownloadItem* left, const DownloadItem* right);

 private:
  const DownloadQuery::SorterVector& terms_;

  // std::sort requires this class to be copyable.
};

bool DownloadQuery::DownloadComparator::operator() (
    const DownloadItem* left, const DownloadItem* right) {
  for (DownloadQuery::SorterVector::const_iterator term = terms_.begin();
       term != terms_.end(); ++term) {
    switch (term->sorter.Run(*left, *right)) {
      case LT: return term->direction == DownloadQuery::ASCENDING;
      case GT: return term->direction == DownloadQuery::DESCENDING;
      case EQ: break;  // break the switch but not the loop
    }
  }
  CHECK_NE(left->GetId(), right->GetId());
  return left->GetId() < right->GetId();
}

void DownloadQuery::AddSorter(DownloadQuery::SortType type,
                              DownloadQuery::SortDirection direction) {
  switch (type) {
    case SORT_START_TIME:
      sorters_.push_back(Sorter::Build<int64>(direction, &GetStartTimeMsEpoch));
      break;
    case SORT_URL:
      sorters_.push_back(Sorter::Build<std::string>(direction, &GetUrl));
      break;
    case SORT_FILENAME:
      sorters_.push_back(Sorter::Build<string16>(direction, &GetFilename));
      break;
    case SORT_DANGER:
      sorters_.push_back(Sorter::Build<DownloadDangerType>(
          direction, &GetDangerType));
      break;
    case SORT_DANGER_ACCEPTED:
      sorters_.push_back(Sorter::Build<bool>(direction, &GetDangerAccepted));
      break;
    case SORT_STATE:
      sorters_.push_back(Sorter::Build<DownloadItem::DownloadState>(
          direction, &GetState));
      break;
    case SORT_PAUSED:
      sorters_.push_back(Sorter::Build<bool>(direction, &IsPaused));
      break;
    case SORT_MIME:
      sorters_.push_back(Sorter::Build<std::string>(direction, &GetMimeType));
      break;
    case SORT_BYTES_RECEIVED:
      sorters_.push_back(Sorter::Build<int>(direction, &GetReceivedBytes));
      break;
    case SORT_TOTAL_BYTES:
      sorters_.push_back(Sorter::Build<int>(direction, &GetTotalBytes));
      break;
  }
}

void DownloadQuery::FinishSearch(DownloadQuery::DownloadVector* results) const {
  if (!sorters_.empty())
    std::partial_sort(results->begin(),
                      results->begin() + std::min(limit_, results->size()),
                      results->end(),
                      DownloadComparator(sorters_));
  if (results->size() > limit_)
    results->resize(limit_);
}
<|endoftext|>