<commit_before>#pragma once

#include "apple_hid_usage_tables.hpp"
#include "constants.hpp"
#include "event_manipulator.hpp"
#include "gcd_utility.hpp"
#include "human_interface_device.hpp"
#include "iokit_utility.hpp"
#include "iopm_client.hpp"
#include "logger.hpp"
#include "manipulator.hpp"
#include "spdlog_utility.hpp"
#include "types.hpp"
#include <IOKit/hid/IOHIDManager.h>
#include <fstream>
#include <json/json.hpp>
#include <thread>
#include <time.h>

class device_grabber final {
public:
  device_grabber(const device_grabber&) = delete;

  device_grabber(manipulator::event_manipulator& event_manipulator) : event_manipulator_(event_manipulator),
                                                                      grab_timer_(nullptr),
                                                                      mode_(mode::observing),
                                                                      grabbed_(false),
                                                                      is_grabbable_callback_log_reducer_(logger::get_logger()),
                                                                      suspended_(false) {
    manager_ = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
    if (!manager_) {
      logger::get_logger().error("{0}: failed to IOHIDManagerCreate", __PRETTY_FUNCTION__);
      return;
    }

    auto device_matching_dictionaries = iokit_utility::create_device_matching_dictionaries({
        std::make_pair(kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard),
        // std::make_pair(kHIDPage_Consumer, kHIDUsage_Csmr_ConsumerControl),
        // std::make_pair(kHIDPage_GenericDesktop, kHIDUsage_GD_Mouse),
    });
    if (device_matching_dictionaries) {
      IOHIDManagerSetDeviceMatchingMultiple(manager_, device_matching_dictionaries);
      CFRelease(device_matching_dictionaries);

      IOHIDManagerRegisterDeviceMatchingCallback(manager_, static_device_matching_callback, this);
      IOHIDManagerRegisterDeviceRemovalCallback(manager_, static_device_removal_callback, this);

      IOHIDManagerScheduleWithRunLoop(manager_, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
    }

    iopm_client_ = std::make_unique<iopm_client>(logger::get_logger(),
                                                 std::bind(&device_grabber::iopm_client_callback, this, std::placeholders::_1));
  }

  ~device_grabber(void) {
    // Release manager_ in main thread to avoid callback invocations after object has been destroyed.
    gcd_utility::dispatch_sync_in_main_queue(^{
      iopm_client_ = nullptr;

      ungrab_devices();

      if (manager_) {
        IOHIDManagerUnscheduleFromRunLoop(manager_, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
        CFRelease(manager_);
        manager_ = nullptr;
      }
    });
  }

  void grab_devices(bool change_mode = true) {
    std::lock_guard<std::mutex> guard(grab_mutex_);

    if (change_mode) {
      mode_ = mode::grabbing;
    }

    cancel_grab_timer();

    // ----------------------------------------
    // setup grab_timer_

    grab_timer_ = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue_.get());
    dispatch_source_set_timer(grab_timer_, dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), 0.1 * NSEC_PER_SEC, 0);
    dispatch_source_set_event_handler(grab_timer_, ^{
      std::lock_guard<std::mutex> grab_guard(grab_mutex_);

      if (grabbed_) {
        return;
      }

      {
        std::lock_guard<std::mutex> hids_guard(hids_mutex_);
        for (const auto& it : hids_) {
          if ((it.second)->is_grabbable() != human_interface_device::grabbable_state::grabbable) {
            return;
          }
        }
      }

      // ----------------------------------------
      // grab devices

      grabbed_ = true;

      {
        std::lock_guard<std::mutex> hids_guard(hids_mutex_);

        for (auto&& it : hids_) {
          (it.second)->unobserve();
          (it.second)->grab();
        }
      }

      event_manipulator_.reset();
      event_manipulator_.grab_mouse_events();

      logger::get_logger().info("Connected devices are grabbed");

      cancel_grab_timer();
    });
    dispatch_resume(grab_timer_);
  }

  void ungrab_devices(bool change_mode = true) {
    std::lock_guard<std::mutex> guard(grab_mutex_);

    if (change_mode) {
      mode_ = mode::observing;
    }

    if (!grabbed_) {
      return;
    }

    grabbed_ = false;

    cancel_grab_timer();

    {
      std::lock_guard<std::mutex> hids_guard(hids_mutex_);

      for (auto&& it : hids_) {
        (it.second)->ungrab();
        (it.second)->observe();
      }
    }

    event_manipulator_.ungrab_mouse_events();
    event_manipulator_.reset();

    logger::get_logger().info("Connected devices are ungrabbed");
  }

  void suspend(void) {
    std::lock_guard<std::mutex> guard(suspend_mutex_);

    if (!suspended_) {
      logger::get_logger().info("device_grabber::suspend");

      suspended_ = true;

      if (mode_ == mode::grabbing) {
        ungrab_devices(false);
      }
    }
  }

  void resume(void) {
    std::lock_guard<std::mutex> guard(suspend_mutex_);

    if (suspended_) {
      logger::get_logger().info("device_grabber::resume");

      suspended_ = false;

      if (mode_ == mode::grabbing) {
        grab_devices(false);
      }
    }
  }

  void set_caps_lock_led_state(krbn::led_state state) {
    std::lock_guard<std::mutex> guard(hids_mutex_);

    for (const auto& it : hids_) {
      (it.second)->set_caps_lock_led_state(state);
    }
  }

private:
  enum class mode {
    observing,
    grabbing,
  };

  void iopm_client_callback(uint32_t message_type) {
    switch (message_type) {
    case kIOMessageSystemWillSleep:
      suspend();
      break;

    case kIOMessageSystemWillPowerOn:
      resume();
      break;

    default:
      break;
    }
  }

  static void static_device_matching_callback(void* _Nullable context, IOReturn result, void* _Nullable sender, IOHIDDeviceRef _Nonnull device) {
    if (result != kIOReturnSuccess) {
      return;
    }

    auto self = static_cast<device_grabber*>(context);
    if (!self) {
      return;
    }

    self->device_matching_callback(device);
  }

  void device_matching_callback(IOHIDDeviceRef _Nonnull device) {
    if (!device) {
      return;
    }

    iokit_utility::log_matching_device(logger::get_logger(), device);

    auto dev = std::make_unique<human_interface_device>(logger::get_logger(), device);
    dev->set_is_grabbable_callback(std::bind(&device_grabber::is_grabbable_callback, this, std::placeholders::_1));
    dev->set_grabbed_callback(std::bind(&device_grabber::grabbed_callback, this, std::placeholders::_1));
    dev->set_value_callback(std::bind(&device_grabber::value_callback,
                                      this,
                                      std::placeholders::_1,
                                      std::placeholders::_2,
                                      std::placeholders::_3,
                                      std::placeholders::_4,
                                      std::placeholders::_5,
                                      std::placeholders::_6));

    // ----------------------------------------

    if (grabbed_) {
      dev->grab();
    } else {
      dev->observe();
    }

    {
      std::lock_guard<std::mutex> guard(hids_mutex_);

      hids_[device] = std::move(dev);
    }

    output_devices_json();

    if (is_pointing_device_connected()) {
      event_manipulator_.create_virtual_hid_manager_client();
    } else {
      event_manipulator_.release_virtual_hid_manager_client();
    }
  }

  static void static_device_removal_callback(void* _Nullable context, IOReturn result, void* _Nullable sender, IOHIDDeviceRef _Nonnull device) {
    if (result != kIOReturnSuccess) {
      return;
    }

    auto self = static_cast<device_grabber*>(context);
    if (!self) {
      return;
    }

    self->device_removal_callback(device);
  }

  void device_removal_callback(IOHIDDeviceRef _Nonnull device) {
    if (!device) {
      return;
    }

    iokit_utility::log_removal_device(logger::get_logger(), device);

    {
      std::lock_guard<std::mutex> guard(hids_mutex_);

      auto it = hids_.find(device);
      if (it != hids_.end()) {
        auto& dev = it->second;
        if (dev) {
          hids_.erase(it);
        }
      }
    }

    output_devices_json();

    if (is_pointing_device_connected()) {
      event_manipulator_.create_virtual_hid_manager_client();
    } else {
      event_manipulator_.release_virtual_hid_manager_client();
    }

    event_manipulator_.stop_key_repeat();
  }

  void value_callback(human_interface_device& device,
                      IOHIDValueRef _Nonnull value,
                      IOHIDElementRef _Nonnull element,
                      uint32_t usage_page,
                      uint32_t usage,
                      CFIndex integer_value) {
    if (!grabbed_) {
      return;
    }

    auto device_registry_entry_id = manipulator::device_registry_entry_id(device.get_registry_entry_id());

    if (auto key_code = krbn::types::get_key_code(usage_page, usage)) {
      bool pressed = integer_value;
      event_manipulator_.handle_keyboard_event(device_registry_entry_id, *key_code, pressed);

    } else if (auto pointing_button = krbn::types::get_pointing_button(usage_page, usage)) {
      event_manipulator_.handle_pointing_event(device_registry_entry_id,
                                               krbn::pointing_event::button,
                                               *pointing_button,
                                               integer_value);

    } else {
      switch (usage_page) {
      case kHIDPage_GenericDesktop:
        if (usage == kHIDUsage_GD_X) {
          event_manipulator_.handle_pointing_event(device_registry_entry_id,
                                                   krbn::pointing_event::x,
                                                   boost::none,
                                                   integer_value);
        }
        if (usage == kHIDUsage_GD_Y) {
          event_manipulator_.handle_pointing_event(device_registry_entry_id,
                                                   krbn::pointing_event::y,
                                                   boost::none,
                                                   integer_value);
        }
        if (usage == kHIDUsage_GD_Wheel) {
          event_manipulator_.handle_pointing_event(device_registry_entry_id,
                                                   krbn::pointing_event::vertical_wheel,
                                                   boost::none,
                                                   integer_value);
        }
        break;

      case kHIDPage_Consumer:
        if (usage == kHIDUsage_Csmr_ACPan) {
          event_manipulator_.handle_pointing_event(device_registry_entry_id,
                                                   krbn::pointing_event::horizontal_wheel,
                                                   boost::none,
                                                   integer_value);
        }
        break;

      default:
        break;
      }
    }

    // reset modifier_flags state if all keys are released.
    if (get_all_devices_pressed_keys_count() == 0) {
      event_manipulator_.reset_modifier_flag_state();
      event_manipulator_.reset_pointing_button_state();
    }
  }

  human_interface_device::grabbable_state is_grabbable_callback(human_interface_device& device) {
    if (!event_manipulator_.is_ready()) {
      is_grabbable_callback_log_reducer_.warn("event_manipulator_ is not ready. Please wait for a while.");
      return human_interface_device::grabbable_state::ungrabbable_temporarily;
    }
    return human_interface_device::grabbable_state::grabbable;
  }

  void grabbed_callback(human_interface_device& device) {
    // set keyboard led
    event_manipulator_.refresh_caps_lock_led();
  }

  size_t get_all_devices_pressed_keys_count(void) {
    std::lock_guard<std::mutex> guard(hids_mutex_);

    size_t total = 0;
    for (const auto& it : hids_) {
      total += (it.second)->get_pressed_keys_count();
    }
    return total;
  }

  bool is_keyboard_connected(void) {
    std::lock_guard<std::mutex> guard(hids_mutex_);

    for (const auto& it : hids_) {
      if ((it.second)->is_keyboard()) {
        return true;
      }
    }
    return false;
  }

  bool is_pointing_device_connected(void) {
    std::lock_guard<std::mutex> guard(hids_mutex_);

    for (const auto& it : hids_) {
      if ((it.second)->is_pointing_device()) {
        return true;
      }
    }
    return false;
  }

  void output_devices_json(void) {
    std::lock_guard<std::mutex> guard(hids_mutex_);

    nlohmann::json json;
    json = nlohmann::json::array();

    for (const auto& it : hids_) {
      nlohmann::json j({});
      if (auto vendor_id = (it.second)->get_vendor_id()) {
        j["vendor_id"] = *vendor_id;
      }
      if (auto product_id = (it.second)->get_product_id()) {
        j["product_id"] = *product_id;
      }
      if (auto manufacturer = (it.second)->get_manufacturer()) {
        j["manipulator"] = *manufacturer;
      }
      if (auto product = (it.second)->get_product()) {
        j["product"] = *product;
      }

      if (!j.empty()) {
        json.push_back(j);
      }
    }

    std::ofstream stream(constants::get_devices_json_file_path());
    if (stream) {
      stream << std::setw(4) << json << std::endl;
      chmod(constants::get_devices_json_file_path(), 0644);
    }
  }

  void cancel_grab_timer(void) {
    if (grab_timer_) {
      dispatch_source_cancel(grab_timer_);
      dispatch_release(grab_timer_);
      grab_timer_ = nullptr;
    }
  }

  manipulator::event_manipulator& event_manipulator_;
  IOHIDManagerRef _Nullable manager_;
  std::unique_ptr<iopm_client> iopm_client_;

  std::unordered_map<IOHIDDeviceRef, std::unique_ptr<human_interface_device>> hids_;
  std::mutex hids_mutex_;

  gcd_utility::scoped_queue queue_;
  dispatch_source_t _Nullable grab_timer_;
  std::mutex grab_mutex_;
  std::atomic<mode> mode_;
  std::atomic<bool> grabbed_;

  spdlog_utility::log_reducer is_grabbable_callback_log_reducer_;

  std::mutex suspend_mutex_;
  bool suspended_;
};
<commit_msg>call event_manipulator_.reset before setup grab_timer_<commit_after>#pragma once

#include "apple_hid_usage_tables.hpp"
#include "constants.hpp"
#include "event_manipulator.hpp"
#include "gcd_utility.hpp"
#include "human_interface_device.hpp"
#include "iokit_utility.hpp"
#include "iopm_client.hpp"
#include "logger.hpp"
#include "manipulator.hpp"
#include "spdlog_utility.hpp"
#include "types.hpp"
#include <IOKit/hid/IOHIDManager.h>
#include <fstream>
#include <json/json.hpp>
#include <thread>
#include <time.h>

class device_grabber final {
public:
  device_grabber(const device_grabber&) = delete;

  device_grabber(manipulator::event_manipulator& event_manipulator) : event_manipulator_(event_manipulator),
                                                                      grab_timer_(nullptr),
                                                                      mode_(mode::observing),
                                                                      grabbed_(false),
                                                                      is_grabbable_callback_log_reducer_(logger::get_logger()),
                                                                      suspended_(false) {
    manager_ = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
    if (!manager_) {
      logger::get_logger().error("{0}: failed to IOHIDManagerCreate", __PRETTY_FUNCTION__);
      return;
    }

    auto device_matching_dictionaries = iokit_utility::create_device_matching_dictionaries({
        std::make_pair(kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard),
        // std::make_pair(kHIDPage_Consumer, kHIDUsage_Csmr_ConsumerControl),
        // std::make_pair(kHIDPage_GenericDesktop, kHIDUsage_GD_Mouse),
    });
    if (device_matching_dictionaries) {
      IOHIDManagerSetDeviceMatchingMultiple(manager_, device_matching_dictionaries);
      CFRelease(device_matching_dictionaries);

      IOHIDManagerRegisterDeviceMatchingCallback(manager_, static_device_matching_callback, this);
      IOHIDManagerRegisterDeviceRemovalCallback(manager_, static_device_removal_callback, this);

      IOHIDManagerScheduleWithRunLoop(manager_, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
    }

    iopm_client_ = std::make_unique<iopm_client>(logger::get_logger(),
                                                 std::bind(&device_grabber::iopm_client_callback, this, std::placeholders::_1));
  }

  ~device_grabber(void) {
    // Release manager_ in main thread to avoid callback invocations after object has been destroyed.
    gcd_utility::dispatch_sync_in_main_queue(^{
      iopm_client_ = nullptr;

      ungrab_devices();

      if (manager_) {
        IOHIDManagerUnscheduleFromRunLoop(manager_, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
        CFRelease(manager_);
        manager_ = nullptr;
      }
    });
  }

  void grab_devices(bool change_mode = true) {
    std::lock_guard<std::mutex> guard(grab_mutex_);

    if (change_mode) {
      mode_ = mode::grabbing;
    }

    event_manipulator_.reset();
    event_manipulator_.grab_mouse_events();

    cancel_grab_timer();

    // ----------------------------------------
    // setup grab_timer_

    grab_timer_ = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue_.get());
    dispatch_source_set_timer(grab_timer_, dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), 0.1 * NSEC_PER_SEC, 0);
    dispatch_source_set_event_handler(grab_timer_, ^{
      std::lock_guard<std::mutex> grab_guard(grab_mutex_);

      if (grabbed_) {
        return;
      }

      {
        std::lock_guard<std::mutex> hids_guard(hids_mutex_);
        for (const auto& it : hids_) {
          if ((it.second)->is_grabbable() != human_interface_device::grabbable_state::grabbable) {
            return;
          }
        }
      }

      // ----------------------------------------
      // grab devices

      grabbed_ = true;

      {
        std::lock_guard<std::mutex> hids_guard(hids_mutex_);

        for (auto&& it : hids_) {
          (it.second)->unobserve();
          (it.second)->grab();
        }
      }

      logger::get_logger().info("Connected devices are grabbed");

      cancel_grab_timer();
    });
    dispatch_resume(grab_timer_);
  }

  void ungrab_devices(bool change_mode = true) {
    std::lock_guard<std::mutex> guard(grab_mutex_);

    if (change_mode) {
      mode_ = mode::observing;
    }

    if (!grabbed_) {
      return;
    }

    grabbed_ = false;

    cancel_grab_timer();

    {
      std::lock_guard<std::mutex> hids_guard(hids_mutex_);

      for (auto&& it : hids_) {
        (it.second)->ungrab();
        (it.second)->observe();
      }
    }

    event_manipulator_.ungrab_mouse_events();
    event_manipulator_.reset();

    logger::get_logger().info("Connected devices are ungrabbed");
  }

  void suspend(void) {
    std::lock_guard<std::mutex> guard(suspend_mutex_);

    if (!suspended_) {
      logger::get_logger().info("device_grabber::suspend");

      suspended_ = true;

      if (mode_ == mode::grabbing) {
        ungrab_devices(false);
      }
    }
  }

  void resume(void) {
    std::lock_guard<std::mutex> guard(suspend_mutex_);

    if (suspended_) {
      logger::get_logger().info("device_grabber::resume");

      suspended_ = false;

      if (mode_ == mode::grabbing) {
        grab_devices(false);
      }
    }
  }

  void set_caps_lock_led_state(krbn::led_state state) {
    std::lock_guard<std::mutex> guard(hids_mutex_);

    for (const auto& it : hids_) {
      (it.second)->set_caps_lock_led_state(state);
    }
  }

private:
  enum class mode {
    observing,
    grabbing,
  };

  void iopm_client_callback(uint32_t message_type) {
    switch (message_type) {
    case kIOMessageSystemWillSleep:
      suspend();
      break;

    case kIOMessageSystemWillPowerOn:
      resume();
      break;

    default:
      break;
    }
  }

  static void static_device_matching_callback(void* _Nullable context, IOReturn result, void* _Nullable sender, IOHIDDeviceRef _Nonnull device) {
    if (result != kIOReturnSuccess) {
      return;
    }

    auto self = static_cast<device_grabber*>(context);
    if (!self) {
      return;
    }

    self->device_matching_callback(device);
  }

  void device_matching_callback(IOHIDDeviceRef _Nonnull device) {
    if (!device) {
      return;
    }

    iokit_utility::log_matching_device(logger::get_logger(), device);

    auto dev = std::make_unique<human_interface_device>(logger::get_logger(), device);
    dev->set_is_grabbable_callback(std::bind(&device_grabber::is_grabbable_callback, this, std::placeholders::_1));
    dev->set_grabbed_callback(std::bind(&device_grabber::grabbed_callback, this, std::placeholders::_1));
    dev->set_value_callback(std::bind(&device_grabber::value_callback,
                                      this,
                                      std::placeholders::_1,
                                      std::placeholders::_2,
                                      std::placeholders::_3,
                                      std::placeholders::_4,
                                      std::placeholders::_5,
                                      std::placeholders::_6));

    // ----------------------------------------

    if (grabbed_) {
      dev->grab();
    } else {
      dev->observe();
    }

    {
      std::lock_guard<std::mutex> guard(hids_mutex_);

      hids_[device] = std::move(dev);
    }

    output_devices_json();

    if (is_pointing_device_connected()) {
      event_manipulator_.create_virtual_hid_manager_client();
    } else {
      event_manipulator_.release_virtual_hid_manager_client();
    }
  }

  static void static_device_removal_callback(void* _Nullable context, IOReturn result, void* _Nullable sender, IOHIDDeviceRef _Nonnull device) {
    if (result != kIOReturnSuccess) {
      return;
    }

    auto self = static_cast<device_grabber*>(context);
    if (!self) {
      return;
    }

    self->device_removal_callback(device);
  }

  void device_removal_callback(IOHIDDeviceRef _Nonnull device) {
    if (!device) {
      return;
    }

    iokit_utility::log_removal_device(logger::get_logger(), device);

    {
      std::lock_guard<std::mutex> guard(hids_mutex_);

      auto it = hids_.find(device);
      if (it != hids_.end()) {
        auto& dev = it->second;
        if (dev) {
          hids_.erase(it);
        }
      }
    }

    output_devices_json();

    if (is_pointing_device_connected()) {
      event_manipulator_.create_virtual_hid_manager_client();
    } else {
      event_manipulator_.release_virtual_hid_manager_client();
    }

    event_manipulator_.stop_key_repeat();
  }

  void value_callback(human_interface_device& device,
                      IOHIDValueRef _Nonnull value,
                      IOHIDElementRef _Nonnull element,
                      uint32_t usage_page,
                      uint32_t usage,
                      CFIndex integer_value) {
    if (!grabbed_) {
      return;
    }

    auto device_registry_entry_id = manipulator::device_registry_entry_id(device.get_registry_entry_id());

    if (auto key_code = krbn::types::get_key_code(usage_page, usage)) {
      bool pressed = integer_value;
      event_manipulator_.handle_keyboard_event(device_registry_entry_id, *key_code, pressed);

    } else if (auto pointing_button = krbn::types::get_pointing_button(usage_page, usage)) {
      event_manipulator_.handle_pointing_event(device_registry_entry_id,
                                               krbn::pointing_event::button,
                                               *pointing_button,
                                               integer_value);

    } else {
      switch (usage_page) {
      case kHIDPage_GenericDesktop:
        if (usage == kHIDUsage_GD_X) {
          event_manipulator_.handle_pointing_event(device_registry_entry_id,
                                                   krbn::pointing_event::x,
                                                   boost::none,
                                                   integer_value);
        }
        if (usage == kHIDUsage_GD_Y) {
          event_manipulator_.handle_pointing_event(device_registry_entry_id,
                                                   krbn::pointing_event::y,
                                                   boost::none,
                                                   integer_value);
        }
        if (usage == kHIDUsage_GD_Wheel) {
          event_manipulator_.handle_pointing_event(device_registry_entry_id,
                                                   krbn::pointing_event::vertical_wheel,
                                                   boost::none,
                                                   integer_value);
        }
        break;

      case kHIDPage_Consumer:
        if (usage == kHIDUsage_Csmr_ACPan) {
          event_manipulator_.handle_pointing_event(device_registry_entry_id,
                                                   krbn::pointing_event::horizontal_wheel,
                                                   boost::none,
                                                   integer_value);
        }
        break;

      default:
        break;
      }
    }

    // reset modifier_flags state if all keys are released.
    if (get_all_devices_pressed_keys_count() == 0) {
      event_manipulator_.reset_modifier_flag_state();
      event_manipulator_.reset_pointing_button_state();
    }
  }

  human_interface_device::grabbable_state is_grabbable_callback(human_interface_device& device) {
    if (!event_manipulator_.is_ready()) {
      is_grabbable_callback_log_reducer_.warn("event_manipulator_ is not ready. Please wait for a while.");
      return human_interface_device::grabbable_state::ungrabbable_temporarily;
    }
    return human_interface_device::grabbable_state::grabbable;
  }

  void grabbed_callback(human_interface_device& device) {
    // set keyboard led
    event_manipulator_.refresh_caps_lock_led();
  }

  size_t get_all_devices_pressed_keys_count(void) {
    std::lock_guard<std::mutex> guard(hids_mutex_);

    size_t total = 0;
    for (const auto& it : hids_) {
      total += (it.second)->get_pressed_keys_count();
    }
    return total;
  }

  bool is_keyboard_connected(void) {
    std::lock_guard<std::mutex> guard(hids_mutex_);

    for (const auto& it : hids_) {
      if ((it.second)->is_keyboard()) {
        return true;
      }
    }
    return false;
  }

  bool is_pointing_device_connected(void) {
    std::lock_guard<std::mutex> guard(hids_mutex_);

    for (const auto& it : hids_) {
      if ((it.second)->is_pointing_device()) {
        return true;
      }
    }
    return false;
  }

  void output_devices_json(void) {
    std::lock_guard<std::mutex> guard(hids_mutex_);

    nlohmann::json json;
    json = nlohmann::json::array();

    for (const auto& it : hids_) {
      nlohmann::json j({});
      if (auto vendor_id = (it.second)->get_vendor_id()) {
        j["vendor_id"] = *vendor_id;
      }
      if (auto product_id = (it.second)->get_product_id()) {
        j["product_id"] = *product_id;
      }
      if (auto manufacturer = (it.second)->get_manufacturer()) {
        j["manipulator"] = *manufacturer;
      }
      if (auto product = (it.second)->get_product()) {
        j["product"] = *product;
      }

      if (!j.empty()) {
        json.push_back(j);
      }
    }

    std::ofstream stream(constants::get_devices_json_file_path());
    if (stream) {
      stream << std::setw(4) << json << std::endl;
      chmod(constants::get_devices_json_file_path(), 0644);
    }
  }

  void cancel_grab_timer(void) {
    if (grab_timer_) {
      dispatch_source_cancel(grab_timer_);
      dispatch_release(grab_timer_);
      grab_timer_ = nullptr;
    }
  }

  manipulator::event_manipulator& event_manipulator_;
  IOHIDManagerRef _Nullable manager_;
  std::unique_ptr<iopm_client> iopm_client_;

  std::unordered_map<IOHIDDeviceRef, std::unique_ptr<human_interface_device>> hids_;
  std::mutex hids_mutex_;

  gcd_utility::scoped_queue queue_;
  dispatch_source_t _Nullable grab_timer_;
  std::mutex grab_mutex_;
  std::atomic<mode> mode_;
  std::atomic<bool> grabbed_;

  spdlog_utility::log_reducer is_grabbable_callback_log_reducer_;

  std::mutex suspend_mutex_;
  bool suspended_;
};
<|endoftext|>


<commit_before>//===-- TargetInstrInfo.cpp - Target Instruction Information --------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file was developed by the LLVM research group and is distributed under
// the University of Illinois Open Source License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
//
//===----------------------------------------------------------------------===//

#include "llvm/Target/TargetInstrInfo.h"
#include "llvm/CodeGen/MachineInstr.h"
#include "llvm/Constant.h"
#include "llvm/DerivedTypes.h"

namespace llvm {

// External object describing the machine instructions
// Initialized only when the TargetMachine class is created
// and reset when that class is destroyed.
//
const TargetInstrDescriptor* TargetInstrDescriptors = 0;

TargetInstrInfo::TargetInstrInfo(const TargetInstrDescriptor* Desc,
				 unsigned numOpcodes)
  : desc(Desc), NumOpcodes(numOpcodes) {
  // FIXME: TargetInstrDescriptors should not be global
  assert(TargetInstrDescriptors == NULL && desc != NULL
         && "TargetMachine data structure corrupt; maybe you tried to create another TargetMachine? (only one may exist in a program)");
  TargetInstrDescriptors = desc;	// initialize global variable
}

TargetInstrInfo::~TargetInstrInfo() {
  TargetInstrDescriptors = NULL;	// reset global variable
}

bool TargetInstrInfo::constantFitsInImmedField(MachineOpCode opCode,
					       int64_t intValue) const {
  // First, check if opCode has an immed field.
  bool isSignExtended;
  uint64_t maxImmedValue = maxImmedConstant(opCode, isSignExtended);
  if (maxImmedValue != 0)
    {
      // NEED TO HANDLE UNSIGNED VALUES SINCE THEY MAY BECOME MUCH
      // SMALLER AFTER CASTING TO SIGN-EXTENDED int, short, or char.
      // See CreateUIntSetInstruction in SparcInstrInfo.cpp.

      // Now check if the constant fits
      if (intValue <= (int64_t) maxImmedValue &&
	  intValue >= -((int64_t) maxImmedValue+1))
	return true;
    }

  return false;
}

} // End llvm namespace
<commit_msg>Finegrainify namespacification Add default impl of commuteInstruction Add notes about ugly V9 code.<commit_after>//===-- TargetInstrInfo.cpp - Target Instruction Information --------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file was developed by the LLVM research group and is distributed under
// the University of Illinois Open Source License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file implements the TargetInstrInfo class.
//
//===----------------------------------------------------------------------===//

#include "llvm/Target/TargetInstrInfo.h"
#include "llvm/CodeGen/MachineInstr.h"
#include "llvm/Constant.h"
#include "llvm/DerivedTypes.h"
using namespace llvm;

namespace llvm {
  // External object describing the machine instructions Initialized only when
  // the TargetMachine class is created and reset when that class is destroyed.
  //
  // FIXME: UGLY SPARCV9 HACK!
  const TargetInstrDescriptor* TargetInstrDescriptors = 0;
}

TargetInstrInfo::TargetInstrInfo(const TargetInstrDescriptor* Desc,
				 unsigned numOpcodes)
  : desc(Desc), NumOpcodes(numOpcodes) {
  // FIXME: TargetInstrDescriptors should not be global
  assert(TargetInstrDescriptors == NULL && desc != NULL
         && "TargetMachine data structure corrupt; maybe you tried to create another TargetMachine? (only one may exist in a program)");
  TargetInstrDescriptors = desc;	// initialize global variable
}

TargetInstrInfo::~TargetInstrInfo() {
  TargetInstrDescriptors = NULL;	// reset global variable
}

// FIXME: SPARCV9 SPECIFIC!
bool TargetInstrInfo::constantFitsInImmedField(MachineOpCode opCode,
					       int64_t intValue) const {
  // First, check if opCode has an immed field.
  bool isSignExtended;
  uint64_t maxImmedValue = maxImmedConstant(opCode, isSignExtended);
  if (maxImmedValue != 0)
    {
      // NEED TO HANDLE UNSIGNED VALUES SINCE THEY MAY BECOME MUCH
      // SMALLER AFTER CASTING TO SIGN-EXTENDED int, short, or char.
      // See CreateUIntSetInstruction in SparcInstrInfo.cpp.

      // Now check if the constant fits
      if (intValue <= (int64_t) maxImmedValue &&
	  intValue >= -((int64_t) maxImmedValue+1))
	return true;
    }

  return false;
}

// commuteInstruction - The default implementation of this method just exchanges
// operand 1 and 2.
MachineInstr *TargetInstrInfo::commuteInstruction(MachineInstr *MI) const {
  assert(MI->getOperand(1).isRegister() && MI->getOperand(2).isRegister() &&
         "This only knows how to commute register operands so far");
  unsigned Reg1 = MI->getOperand(1).getReg();
  unsigned Reg2 = MI->getOperand(1).getReg();
  MI->SetMachineOperandReg(2, Reg1);
  MI->SetMachineOperandReg(1, Reg2);
  return MI;
}
<|endoftext|>


<commit_before>#include <cstdio>
#include <QFileDialog>
#include "mainwindow.h"
#include "ui_mainwindow.h"

namespace
{
    QString filters("All files (*.*)");
}

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

MainWindow::~MainWindow()
{
}

void MainWindow::on_inputPathPushButton_clicked()
{
    QString inputPath = QFileDialog::getOpenFileName(
        0, "Load file", QDir::currentPath(), filters);

    if (inputPath == nullptr)
        return;

    ui->inputPathLineEdit->setText(inputPath);

    if (ui->outputPathLineEdit->text() == "")
    {
        auto inputFileInfo = QFileInfo(inputPath);
        auto outputPath = inputFileInfo.baseName() + "-patched";
        if (inputFileInfo.completeSuffix() != "")
            outputPath += "." + inputFileInfo.completeSuffix();
        ui->outputPathLineEdit->setText(outputPath);
    }
}

void MainWindow::on_outputPathPushButton_clicked()
{
    QString outputPath = QFileDialog::getSaveFileName(
        0, "Save file", QDir::currentPath(), filters);

    if (outputPath == nullptr)
        return;

    ui->outputPathLineEdit->setText(outputPath);
}

void MainWindow::on_patchPushButton_clicked()
{
    puts("patch");
}

#ifdef WAF
    #include "gui/mainwindow.moc"
    #include "gui/mainwindow.cc.moc"
#endif
<commit_msg>Added CRC patching proof of concept to GUI<commit_after>#include <QFileDialog>
#include <memory>
#include "lib/CRC/CRC32.h"
#include "lib/File/File.h"
#include "mainwindow.h"
#include "ui_mainwindow.h"

namespace
{
    QString filters("All files (*.*)");
}

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

MainWindow::~MainWindow()
{
}

void MainWindow::on_inputPathPushButton_clicked()
{
    QString inputPath = QFileDialog::getOpenFileName(
        0, "Load file", QDir::currentPath(), filters);

    if (inputPath == nullptr)
        return;

    ui->inputPathLineEdit->setText(inputPath);

    if (ui->outputPathLineEdit->text() == "")
    {
        auto inputFileInfo = QFileInfo(inputPath);
        auto outputPath = inputFileInfo.baseName() + "-patched";
        if (inputFileInfo.completeSuffix() != "")
            outputPath += "." + inputFileInfo.completeSuffix();
        ui->outputPathLineEdit->setText(outputPath);
    }
}

void MainWindow::on_outputPathPushButton_clicked()
{
    QString outputPath = QFileDialog::getSaveFileName(
        0, "Save file", QDir::currentPath(), filters);

    if (outputPath == nullptr)
        return;

    ui->outputPathLineEdit->setText(outputPath);
}

void MainWindow::on_patchPushButton_clicked()
{
    try
    {
        uint32_t desiredChecksum = ui->crcLineEdit->text().toULong(nullptr, 16);
        std::string inputPath = ui->inputPathLineEdit->text().toStdString();
        std::string outputPath = ui->outputPathLineEdit->text().toStdString();

        std::unique_ptr<CRC> crc(new CRC32);

        std::unique_ptr<File> inputFile(File::fromFileName(
            inputPath.c_str(), File::FOPEN_READ | File::FOPEN_BINARY));

        std::unique_ptr<File> outputFile(File::fromFileName(
            outputPath.c_str(), File::FOPEN_WRITE | File::FOPEN_BINARY));

        File::OffsetType desiredPosition = inputFile->getFileSize();

        crc->applyPatch(
            desiredChecksum,
            desiredPosition,
            *inputFile,
            *outputFile,
            false);

        puts("Done!"); //TODO
    }
    catch (...)
    {
        puts("Error..."); //TODO
    }
}

#ifdef WAF
    #include "gui/mainwindow.moc"
    #include "gui/mainwindow.cc.moc"
#endif
<|endoftext|>


<commit_before>/*
 * DesktopMacDetectRHome.cpp
 *
 * Copyright (C) 2009-11 by RStudio, Inc.
 *
 * 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 "DesktopDetectRHome.hpp"

#include <vector>

#include <boost/algorithm/string/trim.hpp>

#include <QtCore>
#include <QMessageBox>

#include <core/Error.hpp>
#include <core/Log.hpp>
#include <core/FilePath.hpp>
#include <core/system/System.hpp>

#include "config.h"

using namespace core;

namespace desktop {

namespace {

// MacOS X Specific
#ifdef __APPLE__

#define kLibRFileName            "libR.dylib"
#define kLibraryPathEnvVariable  "DYLD_LIBRARY_PATH"

 // define search paths to match shell search path behavior (including
 // /opt/local/bin being first since that is where macports puts it)
 std::vector<std::string> rSearchPaths()
 {
    std::vector<std::string> rPaths;
    rPaths.push_back("/opt/local/bin/R");
    rPaths.push_back("/usr/bin/R");
    rPaths.push_back("/usr/local/bin/R");
    return rPaths;
 }

 // no extra paths on the mac
 std::string extraLibraryPaths(const std::string& rHome)
 {
    return std::string();
 }

// Linux specific
#else

#define kLibRFileName            "libR.so"
#define kLibraryPathEnvVariable  "LD_LIBRARY_PATH"

 // define search paths to match shell search path behavior (note differs
 // from macox by having /usr/local/bin first)
 std::vector<std::string> rSearchPaths()
 {
    std::vector<std::string> rPaths;
    rPaths.push_back("/usr/local/bin/R");
    rPaths.push_back("/usr/bin/R");
    return rPaths;
 }

 // extra paths from R (for rjava) on linux
 std::string extraLibraryPaths(const std::string& rHome)
 {
    std::string libraryPaths;

    FilePath supportingFilePath = desktop::options().supportingFilePath();
    FilePath scriptPath = supportingFilePath.complete("bin/r-ldpath");
    if (!scriptPath.exists())
      scriptPath = supportingFilePath.complete("session/r-ldpath");
    if (scriptPath.exists())
    {
       // run script
       std::string command = scriptPath.absolutePath() + " " + rHome;
       Error error = system::captureCommand(scriptPath.absolutePath(),
                                            &libraryPaths);
       if (error)
          LOG_ERROR(error);
    }

    return libraryPaths;
 }

#endif

FilePath detectRHome()
{
   // scan possible locations for R (need these because on the mac
   // our path as a GUI app is very limited)
   FilePath rPath;
   std::vector<std::string> rPaths = rSearchPaths();
   for(std::vector<std::string>::const_iterator it = rPaths.begin();
       it != rPaths.end(); ++it)
   {
      FilePath candidatePath(*it);
      if (candidatePath.exists())
      {
         rPath = candidatePath ;
         break;
      }
   }

   // if we didn't find one then bail
   if (rPath.empty())
   {
      LOG_ERROR_MESSAGE("Couldn't find R on known path");
      return FilePath();
   }

   // run R to detect R home
   std::string output;
   std::string command = rPath.absolutePath() + " RHOME";
   Error error = core::system::captureCommand(command, &output);
   if (error)
   {
      LOG_ERROR(error);
      return FilePath();
   }
   boost::algorithm::trim(output);

   // return the home path if we got one
   if (!output.empty())
      return FilePath(output);
   else
      return FilePath();
}

void showRNotFoundError(const std::string& msg)
{
   QMessageBox::critical(NULL, "R Not Found", QString::fromStdString(msg));
}

} // anonymous namespace

bool prepareEnvironment(Options&)
{
   // declare home path and doc dir -- can get them from the environment
   // or by probing the system
   FilePath homePath, rDocDir;

   // first check environment
   std::string home = core::system::getenv("R_HOME");
   if (!home.empty())
      homePath = FilePath(home);
   std::string doc = core::system::getenv("R_DOC_DIR");
   if (!doc.empty())
      rDocDir = FilePath(doc);

   // probe for home path if necessary
   if (homePath.empty())
      homePath = detectRHome();

   // if that didn't work then try the configured path as a last ditch
   if (!homePath.exists())
      homePath = FilePath(CONFIG_R_HOME_PATH);

   // verify and set home path
   if (homePath.exists())
   {
      core::system::setenv("R_HOME", homePath.absolutePath());
   }
   else
   {
      showRNotFoundError("R home path (" + homePath.absolutePath() +
                         ") does not exist. Is R installed on "
                         "this system?");
      return false;
   }

   // complete doc dir if necesary
   if (rDocDir.empty())
      rDocDir = homePath.complete("doc");

   // doc dir may be in configured location (/usr/share) rather than
   // a subdirectory of R_HOME (this is in fact the case for the
   // debian r-base package). so check the configuired location as
   // a last ditch
   if (!rDocDir.exists())
      rDocDir = FilePath(CONFIG_R_DOC_PATH);

   // verify and set doc dir
   if (rDocDir.exists())
   {
       core::system::setenv("R_DOC_DIR", rDocDir.absolutePath());
   }
   else
   {
      showRNotFoundError("R doc directory (" + rDocDir.absolutePath() +
                         ") does not exist.");
      return false;
   }

   // verify and set library path
   FilePath rLibPath = homePath.complete("lib");
   if (rLibPath.exists())
   {
      // make sure the R lib was built
      FilePath libRpath = rLibPath.complete(kLibRFileName);
      if (libRpath.exists())
      {
         // initialize library path from existing value + any extras
         std::string libraryPath = core::system::getenv(kLibraryPathEnvVariable);
         if (!libraryPath.empty())
            libraryPath.append(":");
         libraryPath.append(rLibPath.absolutePath());
         std::string extraPaths = extraLibraryPaths(homePath.absolutePath());
         if (!extraPaths.empty())
            libraryPath.append(":" + extraPaths);

         // set it
         core::system::setenv(kLibraryPathEnvVariable, libraryPath);
      }
      else
      {
         showRNotFoundError(rLibPath.absolutePath() + " not found. "
                            "If this is a custom build of R, was it "
                            "built with the --enable-R-shlib option?");
         return false;
      }
   }
   else
   {
      showRNotFoundError("R library directory (" + rLibPath.absolutePath() +
                         ") does not exist.");
      return false;
   }

   return true;
}

} // namespace desktop
<commit_msg>correctly pass r home to r-ldpaths in desktop mode<commit_after>/*
 * DesktopMacDetectRHome.cpp
 *
 * Copyright (C) 2009-11 by RStudio, Inc.
 *
 * 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 "DesktopDetectRHome.hpp"

#include <vector>

#include <boost/algorithm/string/trim.hpp>

#include <QtCore>
#include <QMessageBox>

#include <core/Error.hpp>
#include <core/Log.hpp>
#include <core/FilePath.hpp>
#include <core/system/System.hpp>

#include "config.h"

using namespace core;

namespace desktop {

namespace {

// MacOS X Specific
#ifdef __APPLE__

#define kLibRFileName            "libR.dylib"
#define kLibraryPathEnvVariable  "DYLD_LIBRARY_PATH"

 // define search paths to match shell search path behavior (including
 // /opt/local/bin being first since that is where macports puts it)
 std::vector<std::string> rSearchPaths()
 {
    std::vector<std::string> rPaths;
    rPaths.push_back("/opt/local/bin/R");
    rPaths.push_back("/usr/bin/R");
    rPaths.push_back("/usr/local/bin/R");
    return rPaths;
 }

 // no extra paths on the mac
 std::string extraLibraryPaths(const std::string& rHome)
 {
    return std::string();
 }

// Linux specific
#else

#define kLibRFileName            "libR.so"
#define kLibraryPathEnvVariable  "LD_LIBRARY_PATH"

 // define search paths to match shell search path behavior (note differs
 // from macox by having /usr/local/bin first)
 std::vector<std::string> rSearchPaths()
 {
    std::vector<std::string> rPaths;
    rPaths.push_back("/usr/local/bin/R");
    rPaths.push_back("/usr/bin/R");
    return rPaths;
 }

 // extra paths from R (for rjava) on linux
 std::string extraLibraryPaths(const std::string& rHome)
 {
    std::string libraryPaths;

    FilePath supportingFilePath = desktop::options().supportingFilePath();
    FilePath scriptPath = supportingFilePath.complete("bin/r-ldpath");
    if (!scriptPath.exists())
      scriptPath = supportingFilePath.complete("session/r-ldpath");
    if (scriptPath.exists())
    {
       // run script
       std::string command = scriptPath.absolutePath() + " " + rHome;
       Error error = system::captureCommand(command, &libraryPaths);
       if (error)
          LOG_ERROR(error);
    }

    return libraryPaths;
 }

#endif

FilePath detectRHome()
{
   // scan possible locations for R (need these because on the mac
   // our path as a GUI app is very limited)
   FilePath rPath;
   std::vector<std::string> rPaths = rSearchPaths();
   for(std::vector<std::string>::const_iterator it = rPaths.begin();
       it != rPaths.end(); ++it)
   {
      FilePath candidatePath(*it);
      if (candidatePath.exists())
      {
         rPath = candidatePath ;
         break;
      }
   }

   // if we didn't find one then bail
   if (rPath.empty())
   {
      LOG_ERROR_MESSAGE("Couldn't find R on known path");
      return FilePath();
   }

   // run R to detect R home
   std::string output;
   std::string command = rPath.absolutePath() + " RHOME";
   Error error = core::system::captureCommand(command, &output);
   if (error)
   {
      LOG_ERROR(error);
      return FilePath();
   }
   boost::algorithm::trim(output);

   // return the home path if we got one
   if (!output.empty())
      return FilePath(output);
   else
      return FilePath();
}

void showRNotFoundError(const std::string& msg)
{
   QMessageBox::critical(NULL, "R Not Found", QString::fromStdString(msg));
}

} // anonymous namespace

bool prepareEnvironment(Options&)
{
   // declare home path and doc dir -- can get them from the environment
   // or by probing the system
   FilePath homePath, rDocDir;

   // first check environment
   std::string home = core::system::getenv("R_HOME");
   if (!home.empty())
      homePath = FilePath(home);
   std::string doc = core::system::getenv("R_DOC_DIR");
   if (!doc.empty())
      rDocDir = FilePath(doc);

   // probe for home path if necessary
   if (homePath.empty())
      homePath = detectRHome();

   // if that didn't work then try the configured path as a last ditch
   if (!homePath.exists())
      homePath = FilePath(CONFIG_R_HOME_PATH);

   // verify and set home path
   if (homePath.exists())
   {
      core::system::setenv("R_HOME", homePath.absolutePath());
   }
   else
   {
      showRNotFoundError("R home path (" + homePath.absolutePath() +
                         ") does not exist. Is R installed on "
                         "this system?");
      return false;
   }

   // complete doc dir if necesary
   if (rDocDir.empty())
      rDocDir = homePath.complete("doc");

   // doc dir may be in configured location (/usr/share) rather than
   // a subdirectory of R_HOME (this is in fact the case for the
   // debian r-base package). so check the configuired location as
   // a last ditch
   if (!rDocDir.exists())
      rDocDir = FilePath(CONFIG_R_DOC_PATH);

   // verify and set doc dir
   if (rDocDir.exists())
   {
       core::system::setenv("R_DOC_DIR", rDocDir.absolutePath());
   }
   else
   {
      showRNotFoundError("R doc directory (" + rDocDir.absolutePath() +
                         ") does not exist.");
      return false;
   }

   // verify and set library path
   FilePath rLibPath = homePath.complete("lib");
   if (rLibPath.exists())
   {
      // make sure the R lib was built
      FilePath libRpath = rLibPath.complete(kLibRFileName);
      if (libRpath.exists())
      {
         // initialize library path from existing value + any extras
         std::string libraryPath = core::system::getenv(kLibraryPathEnvVariable);
         if (!libraryPath.empty())
            libraryPath.append(":");
         libraryPath.append(rLibPath.absolutePath());
         std::string extraPaths = extraLibraryPaths(homePath.absolutePath());
         if (!extraPaths.empty())
            libraryPath.append(":" + extraPaths);

         // set it
         core::system::setenv(kLibraryPathEnvVariable, libraryPath);
      }
      else
      {
         showRNotFoundError(rLibPath.absolutePath() + " not found. "
                            "If this is a custom build of R, was it "
                            "built with the --enable-R-shlib option?");
         return false;
      }
   }
   else
   {
      showRNotFoundError("R library directory (" + rLibPath.absolutePath() +
                         ") does not exist.");
      return false;
   }

   return true;
}

} // namespace desktop
<|endoftext|>


<commit_before>/*
 * DataViewer.cpp
 *
 * Copyright (C) 2009-12 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 "DataViewer.hpp"

#include <string>
#include <vector>
#include <sstream>

#include <boost/bind.hpp>
#include <boost/format.hpp>

#include <core/Log.hpp>
#include <core/Error.hpp>
#include <core/Exec.hpp>
#include <core/FileSerializer.hpp>
#include <core/StringUtils.hpp>

#define R_INTERNAL_FUNCTIONS
#include <r/RInternal.hpp>
#include <r/RSexp.hpp>
#include <r/RExec.hpp>
#include <r/RJson.hpp>
#include <r/ROptions.hpp>
#include <r/RFunctionHook.hpp>
#include <r/RRoutines.hpp>

#include <session/SessionModuleContext.hpp>

#include <session/SessionContentUrls.hpp>

#define kGridResource "grid_resource"
#define kViewerCacheDir "viewer-cache"
#define kGridResourceLocation "/" kGridResource "/"

using namespace core;

namespace session {
namespace modules {
namespace data {
namespace viewer {

namespace {

std::string viewerCacheDir()
{
   return module_context::userScratchPath().childPath(kViewerCacheDir)
      .absolutePath();
}

SEXP rs_viewData(SEXP dataSEXP, SEXP captionSEXP, SEXP nameSEXP, SEXP envSEXP,
                 SEXP cacheKeySEXP)
{
   // attempt to reverse engineer the location of the data
   std::string envName, dataName, cacheKey;
   r::sexp::Protect protect;

   r::exec::RFunction("environmentName", envSEXP).call(&envName);
   if (envName == "R_GlobalEnv")
   {
      // the global environment doesn't need to be named
      envName.clear();
   }
   else if (envName == "R_EmptyEnv" || envName == "")
   {
      envName = "_rs_no_env";
   }
   dataName = r::sexp::asString(nameSEXP);
   cacheKey = r::sexp::asString(cacheKeySEXP);

   try
   {
      // validate title
      if (!Rf_isString(captionSEXP) || Rf_length(captionSEXP) != 1)
         throw r::exec::RErrorException("invalid caption argument");

      // attempt to cast to a data frame
      SEXP dataFrameSEXP = NULL;
      r::exec::RFunction("as.data.frame", dataSEXP).call(
            &dataFrameSEXP, &protect);
      if (dataFrameSEXP != NULL)
         dataSEXP = dataFrameSEXP;

      int nrow = 0, ncol = 0;
      r::exec::RFunction("nrow", dataSEXP).call(&nrow);
      r::exec::RFunction("ncol", dataSEXP).call(&ncol);

      // fire show data event
      json::Object dataItem;
      dataItem["caption"] = r::sexp::asString(captionSEXP);
      dataItem["totalObservations"] = nrow;
      dataItem["displayedObservations"] = nrow;
      dataItem["variables"] = ncol;
      dataItem["cacheKey"] = cacheKey;
      dataItem["contentUrl"] = kGridResource "/gridviewer.html?env=" +
         http::util::urlEncode(envName, true) + "&obj=" +
         http::util::urlEncode(dataName, true) + "&cache_key=" +
         http::util::urlEncode(cacheKey, true);
      ClientEvent event(client_events::kShowData, dataItem);
      module_context::enqueClientEvent(event);

      // done
      return R_NilValue;
   }
   catch(r::exec::RErrorException& e)
   {
      r::exec::error(e.message());
   }
   CATCH_UNEXPECTED_EXCEPTION

   // keep compiler happy
   return R_NilValue;
}

void handleGridResReq(const http::Request& request,
                            http::Response* pResponse)
{
   std::string path("grid/");
   path.append(http::util::pathAfterPrefix(request, kGridResourceLocation));

   core::FilePath gridResource = options().rResourcesPath().childPath(path);
   if (gridResource.exists())
   {
      pResponse->setCacheableFile(gridResource, request);
      return;
   }
}

json::Value getCols(SEXP dataSEXP)
{
   std::vector<std::string> cols;

   SEXP namesSEXP = Rf_getAttrib(dataSEXP, R_NamesSymbol);
   if (TYPEOF(namesSEXP) != STRSXP ||
       Rf_length(namesSEXP) != Rf_length(dataSEXP))
   {
      // fake them if we don't have them
      for (int i = 0; i < Rf_length(dataSEXP); i++)
      {
         cols.push_back(boost::lexical_cast<std::string>(i + 1));
      }
   }
   else
   {
      // extract column names
      r::sexp::extract(namesSEXP, &cols);
   }

   // add row ID column
   cols.insert(cols.begin(), "");
   return json::toJsonArray(cols);
}

json::Value getData(SEXP dataSEXP, const http::Fields& fields)
{
   r::sexp::Protect protect;

   // read draw parameters from DataTables
   int draw = http::util::fieldValue<int>(fields, "draw", 0);
   int start = http::util::fieldValue<int>(fields, "start", 0);
   int length = http::util::fieldValue<int>(fields, "length", 0);
   int ordercol = http::util::fieldValue<int>(fields, "order[0][column]", -1);
   std::string orderdir = http::util::fieldValue<std::string>(fields, "order[0][dir]", "asc");

   // apply sort if needed
   if (ordercol > 0)
   {
      r::exec::RFunction(".rs.applySort", dataSEXP, ordercol, orderdir)
         .call(&dataSEXP, &protect);
   }

   // unfortunately Rf_nrow and Rf_ncol aren't applicable here
   int nrow = 0, ncol = 0;
   r::exec::RFunction("nrow", dataSEXP).call(&nrow);
   r::exec::RFunction("ncol", dataSEXP).call(&ncol);
   length = std::min(length, nrow - start);

   // DataTables uses 0-based indexing, but R uses 1-based indexing
   start ++;

   // extract the portion of the column vector requested by the client
   SEXP formattedDataSEXP = Rf_allocVector(VECSXP, ncol);
   protect.add(formattedDataSEXP);
   for (unsigned i = 0; i < static_cast<unsigned>(ncol); i++)
   {
      SEXP columnSEXP = VECTOR_ELT(dataSEXP, i);
      SEXP formattedColumnSEXP;
      r::exec::RFunction formatFx(".rs.formatDataColumn");
      formatFx.addParam(columnSEXP);
      formatFx.addParam(static_cast<int>(start));
      formatFx.addParam(static_cast<int>(length));
      Error error = formatFx.call(&formattedColumnSEXP, &protect);
      if (error)
         throw r::exec::RErrorException(error.summary());
      SET_VECTOR_ELT(formattedDataSEXP, i, formattedColumnSEXP);
    }

   // format the row names
   SEXP rownamesSEXP;
   r::exec::RFunction(".rs.formatRowNames", dataSEXP, start, length)
      .call(&rownamesSEXP, &protect);

   // create the result grid as JSON
   json::Array data;
   for (int row = 0; row < length; row++)
   {
      json::Array rowData;
      SEXP nameSEXP = STRING_ELT(rownamesSEXP, row);
      if (nameSEXP != NULL &&
          nameSEXP != NA_STRING &&
          r::sexp::length(nameSEXP) > 0)
      {
         rowData.push_back(Rf_translateChar(nameSEXP));
      }
      else
      {
         rowData.push_back(row + start);
      }

      for (int col = 0; col<Rf_length(formattedDataSEXP); col++)
      {
         SEXP columnSEXP = VECTOR_ELT(formattedDataSEXP, col);
         SEXP stringSEXP = STRING_ELT(columnSEXP, row);
         if (stringSEXP != NULL &&
             stringSEXP != NA_STRING &&
             r::sexp::length(stringSEXP) > 0)
         {
            rowData.push_back(Rf_translateChar(stringSEXP));
         }
         else
         {
            rowData.push_back("");
         }
      }
      data.push_back(rowData);
   }

   json::Object result;
   result["draw"] = draw;
   result["recordsTotal"] = nrow;
   result["recordsFiltered"] = nrow;
   result["data"] = data;
   return result;
}

Error getGridData(const http::Request& request,
                  http::Response* pResponse)
{
   try
   {
      // extract the query string
      std::string::size_type pos = request.uri().find('?');
      if (pos == std::string::npos)
      {
         return Success();
      }

      // find the data frame we're going to be pulling data from
      std::string queryString = request.uri().substr(pos+1);
      http::Fields fields;
      http::util::parseQueryString(queryString, &fields);
      std::string envName = http::util::urlDecode(
            http::util::fieldValue<std::string>(fields, "env", ""), true);
      std::string objName = http::util::urlDecode(
            http::util::fieldValue<std::string>(fields, "obj", ""), true);
      std::string cacheKey = http::util::urlDecode(
            http::util::fieldValue<std::string>(fields, "cache_key", ""),
            true);
      std::string show = http::util::fieldValue<std::string>(
            fields, "show", "data");
      if (objName.empty() && cacheKey.empty())
      {
         return Success();
      }

      r::sexp::Protect protect;

      // attempt to find the original copy of the object
      SEXP dataSEXP = NULL;
      Error error = r::exec::RFunction(".rs.findDataFrame", envName, objName,
            cacheKey, viewerCacheDir()).call(&dataSEXP, &protect);
      if (error)
      {
         LOG_ERROR(error);
      }

      // couldn't find the original object--look it up from the cache
      if (dataSEXP == NULL || Rf_isNull(dataSEXP) ||
          TYPEOF(dataSEXP) == NILSXP)
      {
         // handle missing data here
         return Success();
      }

      // if the data is a promise (happens for built-in data), the value is
      // what we're looking for
      if (TYPEOF(dataSEXP) == PROMSXP)
      {
         dataSEXP = PRVALUE(dataSEXP);
      }

      // TODO: handle missing data

      json::Value result;
      if (show == "cols")
      {
         result = getCols(dataSEXP);
      }
      else if (show == "data")
      {
         result = getData(dataSEXP, fields);
      }

      std::ostringstream ostr;
      json::write(result, ostr);
      pResponse->setStatusCode(http::status::Ok);
      pResponse->setBody(ostr.str());
   }

   CATCH_UNEXPECTED_EXCEPTION

   return Success();
}

// called by the client to expire data cached by an associated viewer tab
Error removeCachedData(const json::JsonRpcRequest& request,
                       json::JsonRpcResponse*)
{
   std::string cacheKey;
   Error error = json::readParam(request.params, 0, &cacheKey);
   if (error)
      return error;

   error = r::exec::RFunction(".rs.removeCachedData", cacheKey,
         viewerCacheDir()).call();
   if (error)
      return error;

   return Success();
}

void onShutdown(bool terminatedNormally)
{
   // when R suspends or shuts down, write out the contents of the cache
   // environment to disk so we can load them again if we need to
   Error error = r::exec::RFunction(".rs.saveCachedData", viewerCacheDir())
      .call();
   if (error)
      LOG_ERROR(error);
}

void onSuspend(const r::session::RSuspendOptions&, core::Settings*)
{
   onShutdown(true);
}

void onResume(const Settings&)
{
}

} // anonymous namespace

Error initialize()
{
   using namespace module_context;

   // register viewData method
   R_CallMethodDef methodDef ;
   methodDef.name = "rs_viewData" ;
   methodDef.fun = (DL_FUNC) rs_viewData ;
   methodDef.numArgs = 5;
   r::routines::addCallMethod(methodDef);

   module_context::events().onShutdown.connect(onShutdown);
   addSuspendHandler(SuspendHandler(onSuspend, onResume));

   using boost::bind;
   using namespace r::function_hook ;
   using namespace session::module_context;
   ExecBlock initBlock ;
   initBlock.addFunctions()
      (bind(sourceModuleRFile, "SessionDataViewer.R"))
      (bind(registerRpcMethod, "remove_cached_data", removeCachedData))
      (bind(registerUriHandler, "/grid_data", getGridData))
      (bind(registerUriHandler, kGridResourceLocation, handleGridResReq));

   Error error = initBlock.execute();
   if (error)
       return error;

   // initialize data viewer (don't make failure fatal because we are
   // adding this code in a hot patch release)
   bool server = session::options().programMode() == kSessionProgramModeServer;
   error = r::exec::RFunction(".rs.initializeDataViewer", server).call();
   if (error)
       LOG_ERROR(error);

   return Success();
}

} // namespace viewer
} // namespace data
} // namespace modules
} // namesapce session

<commit_msg>handle nulls and unexpected SEXP types gracefully in rownames<commit_after>/*
 * DataViewer.cpp
 *
 * Copyright (C) 2009-12 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 "DataViewer.hpp"

#include <string>
#include <vector>
#include <sstream>

#include <boost/bind.hpp>
#include <boost/format.hpp>

#include <core/Log.hpp>
#include <core/Error.hpp>
#include <core/Exec.hpp>
#include <core/FileSerializer.hpp>
#include <core/StringUtils.hpp>

#define R_INTERNAL_FUNCTIONS
#include <r/RInternal.hpp>
#include <r/RSexp.hpp>
#include <r/RExec.hpp>
#include <r/RJson.hpp>
#include <r/ROptions.hpp>
#include <r/RFunctionHook.hpp>
#include <r/RRoutines.hpp>

#include <session/SessionModuleContext.hpp>

#include <session/SessionContentUrls.hpp>

#define kGridResource "grid_resource"
#define kViewerCacheDir "viewer-cache"
#define kGridResourceLocation "/" kGridResource "/"

using namespace core;

namespace session {
namespace modules {
namespace data {
namespace viewer {

namespace {

std::string viewerCacheDir()
{
   return module_context::userScratchPath().childPath(kViewerCacheDir)
      .absolutePath();
}

SEXP rs_viewData(SEXP dataSEXP, SEXP captionSEXP, SEXP nameSEXP, SEXP envSEXP,
                 SEXP cacheKeySEXP)
{
   // attempt to reverse engineer the location of the data
   std::string envName, dataName, cacheKey;
   r::sexp::Protect protect;

   r::exec::RFunction("environmentName", envSEXP).call(&envName);
   if (envName == "R_GlobalEnv")
   {
      // the global environment doesn't need to be named
      envName.clear();
   }
   else if (envName == "R_EmptyEnv" || envName == "")
   {
      envName = "_rs_no_env";
   }
   dataName = r::sexp::asString(nameSEXP);
   cacheKey = r::sexp::asString(cacheKeySEXP);

   try
   {
      // validate title
      if (!Rf_isString(captionSEXP) || Rf_length(captionSEXP) != 1)
         throw r::exec::RErrorException("invalid caption argument");

      // attempt to cast to a data frame
      SEXP dataFrameSEXP = NULL;
      r::exec::RFunction("as.data.frame", dataSEXP).call(
            &dataFrameSEXP, &protect);
      if (dataFrameSEXP != NULL)
         dataSEXP = dataFrameSEXP;

      int nrow = 0, ncol = 0;
      r::exec::RFunction("nrow", dataSEXP).call(&nrow);
      r::exec::RFunction("ncol", dataSEXP).call(&ncol);

      // fire show data event
      json::Object dataItem;
      dataItem["caption"] = r::sexp::asString(captionSEXP);
      dataItem["totalObservations"] = nrow;
      dataItem["displayedObservations"] = nrow;
      dataItem["variables"] = ncol;
      dataItem["cacheKey"] = cacheKey;
      dataItem["contentUrl"] = kGridResource "/gridviewer.html?env=" +
         http::util::urlEncode(envName, true) + "&obj=" +
         http::util::urlEncode(dataName, true) + "&cache_key=" +
         http::util::urlEncode(cacheKey, true);
      ClientEvent event(client_events::kShowData, dataItem);
      module_context::enqueClientEvent(event);

      // done
      return R_NilValue;
   }
   catch(r::exec::RErrorException& e)
   {
      r::exec::error(e.message());
   }
   CATCH_UNEXPECTED_EXCEPTION

   // keep compiler happy
   return R_NilValue;
}

void handleGridResReq(const http::Request& request,
                            http::Response* pResponse)
{
   std::string path("grid/");
   path.append(http::util::pathAfterPrefix(request, kGridResourceLocation));

   core::FilePath gridResource = options().rResourcesPath().childPath(path);
   if (gridResource.exists())
   {
      pResponse->setCacheableFile(gridResource, request);
      return;
   }
}

json::Value getCols(SEXP dataSEXP)
{
   std::vector<std::string> cols;

   SEXP namesSEXP = Rf_getAttrib(dataSEXP, R_NamesSymbol);
   if (TYPEOF(namesSEXP) != STRSXP ||
       Rf_length(namesSEXP) != Rf_length(dataSEXP))
   {
      // fake them if we don't have them
      for (int i = 0; i < Rf_length(dataSEXP); i++)
      {
         cols.push_back(boost::lexical_cast<std::string>(i + 1));
      }
   }
   else
   {
      // extract column names
      r::sexp::extract(namesSEXP, &cols);
   }

   // add row ID column
   cols.insert(cols.begin(), "");
   return json::toJsonArray(cols);
}

json::Value getData(SEXP dataSEXP, const http::Fields& fields)
{
   r::sexp::Protect protect;

   // read draw parameters from DataTables
   int draw = http::util::fieldValue<int>(fields, "draw", 0);
   int start = http::util::fieldValue<int>(fields, "start", 0);
   int length = http::util::fieldValue<int>(fields, "length", 0);
   int ordercol = http::util::fieldValue<int>(fields, "order[0][column]", -1);
   std::string orderdir = http::util::fieldValue<std::string>(fields, "order[0][dir]", "asc");

   // apply sort if needed
   if (ordercol > 0)
   {
      r::exec::RFunction(".rs.applySort", dataSEXP, ordercol, orderdir)
         .call(&dataSEXP, &protect);
   }

   // unfortunately Rf_nrow and Rf_ncol aren't applicable here
   int nrow = 0, ncol = 0;
   r::exec::RFunction("nrow", dataSEXP).call(&nrow);
   r::exec::RFunction("ncol", dataSEXP).call(&ncol);
   length = std::min(length, nrow - start);

   // DataTables uses 0-based indexing, but R uses 1-based indexing
   start ++;

   // extract the portion of the column vector requested by the client
   SEXP formattedDataSEXP = Rf_allocVector(VECSXP, ncol);
   protect.add(formattedDataSEXP);
   for (unsigned i = 0; i < static_cast<unsigned>(ncol); i++)
   {
      SEXP columnSEXP = VECTOR_ELT(dataSEXP, i);
      SEXP formattedColumnSEXP;
      r::exec::RFunction formatFx(".rs.formatDataColumn");
      formatFx.addParam(columnSEXP);
      formatFx.addParam(static_cast<int>(start));
      formatFx.addParam(static_cast<int>(length));
      Error error = formatFx.call(&formattedColumnSEXP, &protect);
      if (error)
         throw r::exec::RErrorException(error.summary());
      SET_VECTOR_ELT(formattedDataSEXP, i, formattedColumnSEXP);
    }

   // format the row names
   SEXP rownamesSEXP;
   r::exec::RFunction(".rs.formatRowNames", dataSEXP, start, length)
      .call(&rownamesSEXP, &protect);

   // create the result grid as JSON
   json::Array data;
   for (int row = 0; row < length; row++)
   {
      json::Array rowData;
      if (rownamesSEXP != NULL &&
          TYPEOF(rownamesSEXP) != NILSXP &&
          !Rf_isNull(rownamesSEXP) )
      {
         SEXP nameSEXP = STRING_ELT(rownamesSEXP, row);
         if (nameSEXP != NULL &&
             nameSEXP != NA_STRING &&
             r::sexp::length(nameSEXP) > 0)
         {
            rowData.push_back(Rf_translateChar(nameSEXP));
         }
         else
         {
            rowData.push_back(row + start);
         }
      }
      else
      {
         rowData.push_back(row + start);
      }

      for (int col = 0; col<Rf_length(formattedDataSEXP); col++)
      {
         SEXP columnSEXP = VECTOR_ELT(formattedDataSEXP, col);
         if (columnSEXP != NULL &&
             TYPEOF(columnSEXP) != NILSXP &&
             !Rf_isNull(columnSEXP))
         {
            SEXP stringSEXP = STRING_ELT(columnSEXP, row);
            if (stringSEXP != NULL &&
                stringSEXP != NA_STRING &&
                r::sexp::length(stringSEXP) > 0)
            {
               rowData.push_back(Rf_translateChar(stringSEXP));
            }
            else
            {
               rowData.push_back("");
            }
         }
         else
         {
            rowData.push_back("");
         }
      }
      data.push_back(rowData);
   }

   json::Object result;
   result["draw"] = draw;
   result["recordsTotal"] = nrow;
   result["recordsFiltered"] = nrow;
   result["data"] = data;
   return result;
}

Error getGridData(const http::Request& request,
                  http::Response* pResponse)
{
   try
   {
      // extract the query string
      std::string::size_type pos = request.uri().find('?');
      if (pos == std::string::npos)
      {
         return Success();
      }

      // find the data frame we're going to be pulling data from
      std::string queryString = request.uri().substr(pos+1);
      http::Fields fields;
      http::util::parseQueryString(queryString, &fields);
      std::string envName = http::util::urlDecode(
            http::util::fieldValue<std::string>(fields, "env", ""), true);
      std::string objName = http::util::urlDecode(
            http::util::fieldValue<std::string>(fields, "obj", ""), true);
      std::string cacheKey = http::util::urlDecode(
            http::util::fieldValue<std::string>(fields, "cache_key", ""),
            true);
      std::string show = http::util::fieldValue<std::string>(
            fields, "show", "data");
      if (objName.empty() && cacheKey.empty())
      {
         return Success();
      }

      r::sexp::Protect protect;

      // attempt to find the original copy of the object
      SEXP dataSEXP = NULL;
      Error error = r::exec::RFunction(".rs.findDataFrame", envName, objName,
            cacheKey, viewerCacheDir()).call(&dataSEXP, &protect);
      if (error)
      {
         LOG_ERROR(error);
      }

      // couldn't find the original object--look it up from the cache
      if (dataSEXP == NULL || Rf_isNull(dataSEXP) ||
          TYPEOF(dataSEXP) == NILSXP)
      {
         // handle missing data here
         return Success();
      }

      // if the data is a promise (happens for built-in data), the value is
      // what we're looking for
      if (TYPEOF(dataSEXP) == PROMSXP)
      {
         dataSEXP = PRVALUE(dataSEXP);
      }

      // TODO: handle missing data

      json::Value result;
      if (show == "cols")
      {
         result = getCols(dataSEXP);
      }
      else if (show == "data")
      {
         result = getData(dataSEXP, fields);
      }

      std::ostringstream ostr;
      json::write(result, ostr);
      pResponse->setStatusCode(http::status::Ok);
      pResponse->setBody(ostr.str());
   }

   CATCH_UNEXPECTED_EXCEPTION

   return Success();
}

// called by the client to expire data cached by an associated viewer tab
Error removeCachedData(const json::JsonRpcRequest& request,
                       json::JsonRpcResponse*)
{
   std::string cacheKey;
   Error error = json::readParam(request.params, 0, &cacheKey);
   if (error)
      return error;

   error = r::exec::RFunction(".rs.removeCachedData", cacheKey,
         viewerCacheDir()).call();
   if (error)
      return error;

   return Success();
}

void onShutdown(bool terminatedNormally)
{
   // when R suspends or shuts down, write out the contents of the cache
   // environment to disk so we can load them again if we need to
   Error error = r::exec::RFunction(".rs.saveCachedData", viewerCacheDir())
      .call();
   if (error)
      LOG_ERROR(error);
}

void onSuspend(const r::session::RSuspendOptions&, core::Settings*)
{
   onShutdown(true);
}

void onResume(const Settings&)
{
}

} // anonymous namespace

Error initialize()
{
   using namespace module_context;

   // register viewData method
   R_CallMethodDef methodDef ;
   methodDef.name = "rs_viewData" ;
   methodDef.fun = (DL_FUNC) rs_viewData ;
   methodDef.numArgs = 5;
   r::routines::addCallMethod(methodDef);

   module_context::events().onShutdown.connect(onShutdown);
   addSuspendHandler(SuspendHandler(onSuspend, onResume));

   using boost::bind;
   using namespace r::function_hook ;
   using namespace session::module_context;
   ExecBlock initBlock ;
   initBlock.addFunctions()
      (bind(sourceModuleRFile, "SessionDataViewer.R"))
      (bind(registerRpcMethod, "remove_cached_data", removeCachedData))
      (bind(registerUriHandler, "/grid_data", getGridData))
      (bind(registerUriHandler, kGridResourceLocation, handleGridResReq));

   Error error = initBlock.execute();
   if (error)
       return error;

   // initialize data viewer (don't make failure fatal because we are
   // adding this code in a hot patch release)
   bool server = session::options().programMode() == kSessionProgramModeServer;
   error = r::exec::RFunction(".rs.initializeDataViewer", server).call();
   if (error)
       LOG_ERROR(error);

   return Success();
}

} // namespace viewer
} // namespace data
} // namespace modules
} // namesapce session

<|endoftext|>


<commit_before>#include "plugin_handler.h"
#include "log.h"
#include "core.h"
#include "core/alloc.h"
#include <pd_common.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <uv.h>
#include <stb.h>

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

static PluginData** s_plugins;

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

static void registerPlugin(const char* type, void* plugin, void* privateData)
{
    PluginData* pluginData = (PluginData*)alloc_zero(sizeof(PluginData));

    // TODO: Verify that we don't add a plugin with the same plugin name in the same plugin

    pluginData->plugin = plugin;
    pluginData->type = type;
    pluginData->filename = (const char*)privateData;

    stb_arr_push(s_plugins, pluginData);
}

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

static char* buildLoadingPath(const char* basePath, const char* plugin)
{
    char* output = 0;

    size_t baseLen = strlen(basePath);
    size_t pluginLen = strlen(plugin);

#ifdef PRODBG_MAC
    output = (char*)malloc(baseLen + pluginLen + 12); // + 12 for separator /lib.dylib + terminator
    sprintf(output, "%s/lib%s.dylib", basePath, plugin);
#elif PRODBG_WIN
    output = (char*)malloc(baseLen + pluginLen + 6); // + 5 for separator /.dll + terminator
    sprintf(output, "%s\\%s.dll", basePath, plugin);
#else
    output = (char*)malloc(baseLen + pluginLen + 5); // + 4 for separator \.so + terminator
    sprintf(output, "%s/%s.so", basePath, plugin);
#endif

    return output;
}

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

bool PluginHandler_addPlugin(const char* basePath, const char* plugin)
{
    uv_lib_t lib;
    void* function;
    void* (* initPlugin)(RegisterPlugin* registerPlugin, void* privateData);

    if (!basePath || !plugin)
        return false;

    const char* filename = buildLoadingPath(basePath, plugin);

    if (uv_dlopen(filename, &lib) == -1)
    {
        // TODO: Show error message
        log_error("Unable to open %s error:\n", uv_dlerror(&lib))
        free((void*)filename);
        return false;
    }

    if (uv_dlsym(&lib, "InitPlugin", &function) == -1)
    {
        // TODO: Show error message
        log_error("Unable to find InitPlugin function in plugin %s\n", plugin);
        uv_dlclose(&lib);
        free((void*)filename);
        return false;
    }

    *(void**)(&initPlugin) = function;

    initPlugin(registerPlugin, (void*)plugin);

    return true;
}

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

static PluginData* findPlugin(const char* pluginFile, const char* pluginName)
{
    int count = stb_arr_len(s_plugins);

    for (int i = 0; i < count; ++i)
    {
        PluginData* pluginData = s_plugins[i];
        PDPluginBase* base = (PDPluginBase*)pluginData->plugin;

        if (!strcmp(base->name, pluginName) && !strcmp(pluginData->filename, pluginFile))
            return pluginData;
    }

    return 0;
}

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

void PluginHandler_unloadAllPlugins()
{
    // TODO: Actually unload everything
    stb_arr_setlen(s_plugins, 0);
}

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

PluginData* PluginHandler_findPlugin(const char** paths, const char* pluginFile, const char* pluginName, bool load)
{
    PluginData* pluginData;

    // TODO: Support paths
    (void)paths;

    // If not found and not !load (that is we will not try to load it)

    if ((pluginData = findPlugin(pluginFile, pluginName)))
        return pluginData;

    if (!load)
        return 0;

    // TODO: Support base paths

    if (!PluginHandler_addPlugin(OBJECT_DIR, pluginFile))
        return 0;

    if ((pluginData = findPlugin(pluginFile, pluginName)))
        return pluginData;

    return 0;
}

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

void PluginHandler_addStaticPlugin(PluginData* pluginData)
{
    stb_arr_push(s_plugins, pluginData);
}

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

PluginData** PluginHandler_getPlugins(int* count)
{
    *count = stb_arr_len(s_plugins);
    return s_plugins;
}

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

PluginData* PluginHandler_getPluginData(void* plugin)
{
    int count = stb_arr_len(s_plugins);

    for (int i = 0; i < count; ++i)
    {
        if (s_plugins[i]->plugin == plugin)
            return s_plugins[i];
    }

    return 0;
}

<commit_msg>Added missing prefix for libs on Linux<commit_after>#include "plugin_handler.h"
#include "log.h"
#include "core.h"
#include "core/alloc.h"
#include <pd_common.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <uv.h>
#include <stb.h>

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

static PluginData** s_plugins;

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

static void registerPlugin(const char* type, void* plugin, void* privateData)
{
    PluginData* pluginData = (PluginData*)alloc_zero(sizeof(PluginData));

    // TODO: Verify that we don't add a plugin with the same plugin name in the same plugin

    pluginData->plugin = plugin;
    pluginData->type = type;
    pluginData->filename = (const char*)privateData;

    stb_arr_push(s_plugins, pluginData);
}

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

static char* buildLoadingPath(const char* basePath, const char* plugin)
{
    char* output = 0;

    size_t baseLen = strlen(basePath);
    size_t pluginLen = strlen(plugin);

#ifdef PRODBG_MAC
    output = (char*)malloc(baseLen + pluginLen + 12); // + 12 for separator /lib.dylib + terminator
    sprintf(output, "%s/lib%s.dylib", basePath, plugin);
#elif PRODBG_WIN
    output = (char*)malloc(baseLen + pluginLen + 6); // + 5 for separator /.dll + terminator
    sprintf(output, "%s\\%s.dll", basePath, plugin);
#else
    output = (char*)malloc(baseLen + pluginLen + 12); // + 4 for separator \.so + terminator
    sprintf(output, "%s/lib%s.so", basePath, plugin);
#endif

    return output;
}

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

bool PluginHandler_addPlugin(const char* basePath, const char* plugin)
{
    uv_lib_t lib;
    void* function;
    void* (* initPlugin)(RegisterPlugin* registerPlugin, void* privateData);

    if (!basePath || !plugin)
        return false;

    const char* filename = buildLoadingPath(basePath, plugin);

    if (uv_dlopen(filename, &lib) == -1)
    {
        // TODO: Show error message
        log_error("Unable to open %s error:\n", uv_dlerror(&lib))
        free((void*)filename);
        return false;
    }

    if (uv_dlsym(&lib, "InitPlugin", &function) == -1)
    {
        // TODO: Show error message
        log_error("Unable to find InitPlugin function in plugin %s\n", plugin);
        uv_dlclose(&lib);
        free((void*)filename);
        return false;
    }

    *(void**)(&initPlugin) = function;

    initPlugin(registerPlugin, (void*)plugin);

    return true;
}

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

static PluginData* findPlugin(const char* pluginFile, const char* pluginName)
{
    int count = stb_arr_len(s_plugins);

    for (int i = 0; i < count; ++i)
    {
        PluginData* pluginData = s_plugins[i];
        PDPluginBase* base = (PDPluginBase*)pluginData->plugin;

        if (!strcmp(base->name, pluginName) && !strcmp(pluginData->filename, pluginFile))
            return pluginData;
    }

    return 0;
}

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

void PluginHandler_unloadAllPlugins()
{
    // TODO: Actually unload everything
    stb_arr_setlen(s_plugins, 0);
}

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

PluginData* PluginHandler_findPlugin(const char** paths, const char* pluginFile, const char* pluginName, bool load)
{
    PluginData* pluginData;

    // TODO: Support paths
    (void)paths;

    // If not found and not !load (that is we will not try to load it)

    if ((pluginData = findPlugin(pluginFile, pluginName)))
        return pluginData;

    if (!load)
        return 0;

    // TODO: Support base paths

    if (!PluginHandler_addPlugin(OBJECT_DIR, pluginFile))
        return 0;

    if ((pluginData = findPlugin(pluginFile, pluginName)))
        return pluginData;

    return 0;
}

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

void PluginHandler_addStaticPlugin(PluginData* pluginData)
{
    stb_arr_push(s_plugins, pluginData);
}

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

PluginData** PluginHandler_getPlugins(int* count)
{
    *count = stb_arr_len(s_plugins);
    return s_plugins;
}

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

PluginData* PluginHandler_getPluginData(void* plugin)
{
    int count = stb_arr_len(s_plugins);

    for (int i = 0; i < count; ++i)
    {
        if (s_plugins[i]->plugin == plugin)
            return s_plugins[i];
    }

    return 0;
}

<|endoftext|>


<commit_before>//
// Created by Dawid Drozd aka Gelldur on 03.02.16.
//

#include "DataProviderComponent.h"

#include <cassert>
#include <log.h>

DataProviderComponent::DataProviderComponent(std::shared_ptr<Provider> provider, Receiver* receiver)
		: _provider(provider)
		, _receiver(receiver)
{
}

DataProviderComponent::~DataProviderComponent()
{
	_provider->cancel();
	_receiver = nullptr;
}

void DataProviderComponent::onResume()
{
	_provider->setReceiver(this);
}

void DataProviderComponent::onPause()
{
	_provider->setReceiver(nullptr);
	_provider->cancel();
}

void DataProviderComponent::onRequestData()
{
	_provider->onRequestData(_provider);
}

void DataProviderComponent::onStartLoading()
{
	assert(_isLoading == false);
	if (_receiver != nullptr && _isLoading == false)
	{
		_receiver->onStartLoading();
		_isLoading = true;
	}
}

void DataProviderComponent::onStopLoading()
{
	assert(_isLoading == true);
	if (_receiver != nullptr && _isLoading == true)
	{
		_receiver->onStopLoading();
		_isLoading = false;
	}
}

void DataProviderComponent::onReceive(Provider* provider)
{
	if (_receiver != nullptr)
	{
		_receiver->onReceive(provider);
	}
	else
	{
		WLOG("[DataProviderComponent] No receiver setted!");
	}
}

void DataProviderComponent::onError(Provider* provider)
{
	if (_receiver != nullptr)
	{
		_receiver->onError(provider);
	}
	else
	{
		WLOG("[DataProviderComponent] No receiver setted!");
	}
}

<commit_msg>Add detecting provider is ready<commit_after>//
// Created by Dawid Drozd aka Gelldur on 03.02.16.
//

#include "DataProviderComponent.h"

#include <cassert>
#include <log.h>

DataProviderComponent::DataProviderComponent(std::shared_ptr<Provider> provider, Receiver* receiver)
		: _provider(provider)
		, _receiver(receiver)
{
}

DataProviderComponent::~DataProviderComponent()
{
	_provider->cancel();
	_receiver = nullptr;
}

void DataProviderComponent::onResume()
{
	_provider->setReceiver(this);
}

void DataProviderComponent::onPause()
{
	_provider->setReceiver(nullptr);
	_provider->cancel();
}

void DataProviderComponent::onRequestData()
{
	if (_provider->isReady())
	{
		_provider->onRequestData(_provider);
	}
	else
	{
		WLOG("Provider is busy");
	}
}

void DataProviderComponent::onStartLoading()
{
	assert(_isLoading == false);
	if (_receiver != nullptr && _isLoading == false)
	{
		_receiver->onStartLoading();
		_isLoading = true;
	}
}

void DataProviderComponent::onStopLoading()
{
	assert(_isLoading == true);
	if (_receiver != nullptr && _isLoading == true)
	{
		_receiver->onStopLoading();
		_isLoading = false;
	}
}

void DataProviderComponent::onReceive(Provider* provider)
{
	if (_receiver != nullptr)
	{
		_receiver->onReceive(provider);
	}
	else
	{
		WLOG("[DataProviderComponent] No receiver setted!");
	}
}

void DataProviderComponent::onError(Provider* provider)
{
	if (_receiver != nullptr)
	{
		_receiver->onError(provider);
	}
	else
	{
		WLOG("[DataProviderComponent] No receiver setted!");
	}
}

<|endoftext|>