<commit_before>#include "controller_ros.h"

#include "vortex/eigen_helper.h"
#include <tf/transform_datatypes.h>
#include <eigen_conversions/eigen_msg.h>

#include "std_msgs/String.h"

#include <math.h>

Controller::Controller(ros::NodeHandle nh) : nh(nh)
{
  command_sub = nh.subscribe("propulsion_command", 10, &Controller::commandCallback, this);
  state_sub   = nh.subscribe("state_estimate", 10, &Controller::stateCallback, this);
  wrench_pub  = nh.advertise<geometry_msgs::Wrench>("rov_forces", 10);
  mode_pub    = nh.advertise<std_msgs::String>("controller/mode", 10);

  control_mode = ControlModes::OPEN_LOOP;

  if (!nh.getParam("/controller/frequency", frequency))
  {
    ROS_WARN("Failed to read parameter controller frequency, defaulting to 10 Hz.");
    frequency = 10;
  }

  state = new State();
  initSetpoints();
  initPositionHoldController();

  // Set up a dynamic reconfigure server
  dynamic_reconfigure::Server<vortex_controller::VortexControllerConfig>::CallbackType cb;
  cb = boost::bind(&Controller::configCallback, this, _1, _2);
  dr_srv.setCallback(cb);

  ROS_INFO("Controller: Initialized with dynamic reconfigure.");
}

void Controller::commandCallback(const vortex_msgs::PropulsionCommand& msg)
{
  if (!healthyMessage(msg))
  {
    ROS_WARN("Controller: Propulsion command message out of range, ignoring...");
    return;
  }

  ControlMode new_control_mode;
  {
    int i;
    for (i = 0; i < msg.control_mode.size(); ++i)
      if (msg.control_mode[i])
        break;
    new_control_mode = static_cast<ControlMode>(i);
  }

  if (new_control_mode != control_mode)
  {
    control_mode = new_control_mode;

    Eigen::Vector3d    position;
    Eigen::Quaterniond orientation;
    Eigen::Vector6d    velocity;

    // Reset setpoints to be equal to state
    state->get(position, orientation, velocity);
    setpoints->set(position, orientation);

    ROS_INFO_STREAM("Controller: Changing mode to " << controlModeString(control_mode) << ".");
  }
  publishControlMode();

  double time = msg.header.stamp.toSec();
  Eigen::Vector6d command;
  for (int i = 0; i < 6; ++i)
    command(i) = msg.motion[i];
  setpoints->update(time, command);
}

void Controller::stateCallback(const nav_msgs::Odometry &msg)
{
  Eigen::Vector3d    position;
  Eigen::Quaterniond orientation;
  Eigen::Vector6d    velocity;

  tf::pointMsgToEigen(msg.pose.pose.position, position);
  tf::quaternionMsgToEigen(msg.pose.pose.orientation, orientation);
  tf::twistMsgToEigen(msg.twist.twist, velocity);

  const double MAX_QUAT_NORM_DEVIATION = 0.1;
  bool orientation_invalid = (abs(orientation.norm() - 1) > MAX_QUAT_NORM_DEVIATION);
  if (isFucked(position) || isFucked(velocity) || orientation_invalid)
  {
    ROS_WARN("Controller: State not valid, ignoring...");
    return;
  }

  state->set(position, orientation, velocity);
}

void Controller::configCallback(vortex_controller::VortexControllerConfig &config, uint32_t level)
{
  ROS_INFO_STREAM("Setting quat pd gains:   a = " << config.a << ",   b = " << config.b << ",   c = " << config.c);
  position_hold_controller->setGains(config.a, config.b, config.c);
}

void Controller::spin()
{
  Eigen::Vector6d    tau_command          = Eigen::VectorXd::Zero(6);
  Eigen::Vector6d    tau_openloop         = Eigen::VectorXd::Zero(6);
  Eigen::Vector6d    tau_sixdof           = Eigen::VectorXd::Zero(6);

  Eigen::Vector3d    position_state       = Eigen::Vector3d::Zero();
  Eigen::Quaterniond orientation_state    = Eigen::Quaterniond::Identity();
  Eigen::Vector6d    velocity_state       = Eigen::VectorXd::Zero(6);

  Eigen::Vector3d    position_setpoint    = Eigen::Vector3d::Zero();
  Eigen::Quaterniond orientation_setpoint = Eigen::Quaterniond::Identity();

  ros::Rate rate(frequency);
  while (ros::ok())
  {
    // TODO: check value of bool return from getters
    state->get(position_state, orientation_state, velocity_state);
    setpoints->get(position_setpoint, orientation_setpoint);

    switch (control_mode)
    {
      case ControlModes::OPEN_LOOP:
      {
        setpoints->get(tau_command);
        break;
      }

      case ControlModes::SIXDOF:
      {
        tau_command = position_hold_controller->compute(position_state,
                                                        orientation_state,
                                                        velocity_state,
                                                        position_setpoint,
                                                        orientation_setpoint);
        break;
      }

      case ControlModes::RPY_DEPTH:
      {
        // TODO: make this similar to depth hold
        tau_command(0) = tau_openloop(0);
        tau_command(1) = tau_openloop(1);
        tau_command(2) = tau_sixdof(2);
        tau_command(3) = tau_sixdof(3);
        tau_command(4) = tau_sixdof(4);
        tau_command(5) = tau_sixdof(5);
        break;
      }

      case ControlModes::DEPTH_HOLD:
      {
        setpoints->get(tau_openloop);

        bool depth_change_commanded = abs(tau_openloop(2) > FORCE_DEADZONE_LIMIT);
        if (depth_change_commanded)
        {
          tau_command = tau_openloop;
        }
        else
        {
          position_setpoint(0) = position_state(0);
          position_setpoint(1) = position_state(1);
          orientation_setpoint = orientation_state;
          tau_sixdof = position_hold_controller->compute(position_state,
                                                         orientation_state,
                                                         velocity_state,
                                                         position_setpoint,
                                                         orientation_setpoint);
          tau_command = tau_sixdof + tau_openloop;
        }
        break;
      }

      default:
      {
        ROS_ERROR("Default control mode reached in Controller::spin().");
        break;
      }
    }

    geometry_msgs::Wrench msg;
    tf::wrenchEigenToMsg(tau_command, msg);
    wrench_pub.publish(msg);

    ros::spinOnce();
    rate.sleep();
  }
}

void Controller::initSetpoints()
{
  std::vector<double> v;

  if (!nh.getParam("/propulsion/command/wrench/max", v))
    ROS_FATAL("Failed to read parameter max wrench command.");
  Eigen::Vector6d wrench_command_max = Eigen::Vector6d::Map(v.data(), v.size());

  if (!nh.getParam("/propulsion/command/wrench/scaling", v))
    ROS_FATAL("Failed to read parameter scaling wrench command.");
  Eigen::Vector6d wrench_command_scaling = Eigen::Vector6d::Map(v.data(), v.size());

  if (!nh.getParam("/propulsion/command/pose/rate", v))
    ROS_FATAL("Failed to read parameter pose command rate.");
  Eigen::Vector6d pose_command_rate = Eigen::Vector6d::Map(v.data(), v.size());

  setpoints = new Setpoints(wrench_command_scaling,
                            wrench_command_max,
                            pose_command_rate);
}

void Controller::initPositionHoldController()
{
  // Read controller gains from parameter server
  std::map<std::string,double> gains;
  if (!nh.getParam("/controller/gains", gains))
    ROS_ERROR("Failed to read parameter controller gains.");

  // Read center of gravity and buoyancy vectors
  std::vector<double> r_G_vec, r_B_vec;
  if (!nh.getParam("/physical/center_of_mass", r_G_vec))
    ROS_ERROR("Failed to read robot center of mass parameter.");
  if (!nh.getParam("/physical/center_of_buoyancy", r_B_vec))
    ROS_ERROR("Failed to read robot center of buoyancy parameter.");
  Eigen::Vector3d r_G(r_G_vec.data());
  Eigen::Vector3d r_B(r_B_vec.data());

  // Read and calculate ROV weight and buoyancy
  double mass, displacement, acceleration_of_gravity, density_of_water;
  if (!nh.getParam("/physical/mass_kg", mass))
    ROS_ERROR("Failed to read parameter mass.");
  if (!nh.getParam("/physical/displacement_m3", displacement))
    ROS_ERROR("Failed to read parameter displacement.");
  if (!nh.getParam("/gravity/acceleration", acceleration_of_gravity))
    ROS_ERROR("Failed to read parameter acceleration of gravity");
  if (!nh.getParam("/water/density", density_of_water))
    ROS_ERROR("Failed to read parameter density of water");
  double W = mass * acceleration_of_gravity;
  double B = density_of_water * displacement * acceleration_of_gravity;

  position_hold_controller = new QuaternionPdController(gains["a"],
                                                        gains["b"],
                                                        gains["c"],
                                                        W, B, r_G, r_B);
}

bool Controller::healthyMessage(const vortex_msgs::PropulsionCommand& msg)
{
  // Check that motion commands are in range
  for (int i = 0; i < msg.motion.size(); ++i)
  {
    if (msg.motion[i] > 1 || msg.motion[i] < -1)
    {
      ROS_WARN("Controller: Motion command out of range.");
      return false;
    }
  }

  // Check that exactly one control mode is requested
  int num_requested_modes = 0;
  for (int i = 0; i < msg.control_mode.size(); ++i)
    if (msg.control_mode[i])
      num_requested_modes++;

  if (num_requested_modes != 1)
  {
    ROS_WARN_STREAM("Controller: Invalid control mode. Attempt to set "
                    << num_requested_modes << " control modes at once.");
    return false;
  }

  return true;
}

void Controller::publishControlMode()
{
  std::string s = controlModeString(control_mode);
  std_msgs::String msg;
  msg.data = s;
  mode_pub.publish(msg);
}
<commit_msg>Fix parenthesis bug<commit_after>#include "controller_ros.h"

#include "vortex/eigen_helper.h"
#include <tf/transform_datatypes.h>
#include <eigen_conversions/eigen_msg.h>

#include "std_msgs/String.h"

#include <math.h>

Controller::Controller(ros::NodeHandle nh) : nh(nh)
{
  command_sub = nh.subscribe("propulsion_command", 10, &Controller::commandCallback, this);
  state_sub   = nh.subscribe("state_estimate", 10, &Controller::stateCallback, this);
  wrench_pub  = nh.advertise<geometry_msgs::Wrench>("rov_forces", 10);
  mode_pub    = nh.advertise<std_msgs::String>("controller/mode", 10);

  control_mode = ControlModes::OPEN_LOOP;

  if (!nh.getParam("/controller/frequency", frequency))
  {
    ROS_WARN("Failed to read parameter controller frequency, defaulting to 10 Hz.");
    frequency = 10;
  }

  state = new State();
  initSetpoints();
  initPositionHoldController();

  // Set up a dynamic reconfigure server
  dynamic_reconfigure::Server<vortex_controller::VortexControllerConfig>::CallbackType cb;
  cb = boost::bind(&Controller::configCallback, this, _1, _2);
  dr_srv.setCallback(cb);

  ROS_INFO("Controller: Initialized with dynamic reconfigure.");
}

void Controller::commandCallback(const vortex_msgs::PropulsionCommand& msg)
{
  if (!healthyMessage(msg))
  {
    ROS_WARN("Controller: Propulsion command message out of range, ignoring...");
    return;
  }

  ControlMode new_control_mode;
  {
    int i;
    for (i = 0; i < msg.control_mode.size(); ++i)
      if (msg.control_mode[i])
        break;
    new_control_mode = static_cast<ControlMode>(i);
  }

  if (new_control_mode != control_mode)
  {
    control_mode = new_control_mode;

    Eigen::Vector3d    position;
    Eigen::Quaterniond orientation;
    Eigen::Vector6d    velocity;

    // Reset setpoints to be equal to state
    state->get(position, orientation, velocity);
    setpoints->set(position, orientation);

    ROS_INFO_STREAM("Controller: Changing mode to " << controlModeString(control_mode) << ".");
  }
  publishControlMode();

  double time = msg.header.stamp.toSec();
  Eigen::Vector6d command;
  for (int i = 0; i < 6; ++i)
    command(i) = msg.motion[i];
  setpoints->update(time, command);
}

void Controller::stateCallback(const nav_msgs::Odometry &msg)
{
  Eigen::Vector3d    position;
  Eigen::Quaterniond orientation;
  Eigen::Vector6d    velocity;

  tf::pointMsgToEigen(msg.pose.pose.position, position);
  tf::quaternionMsgToEigen(msg.pose.pose.orientation, orientation);
  tf::twistMsgToEigen(msg.twist.twist, velocity);

  const double MAX_QUAT_NORM_DEVIATION = 0.1;
  bool orientation_invalid = (abs(orientation.norm() - 1) > MAX_QUAT_NORM_DEVIATION);
  if (isFucked(position) || isFucked(velocity) || orientation_invalid)
  {
    ROS_WARN("Controller: State not valid, ignoring...");
    return;
  }

  state->set(position, orientation, velocity);
}

void Controller::configCallback(vortex_controller::VortexControllerConfig &config, uint32_t level)
{
  ROS_INFO_STREAM("Setting quat pd gains:   a = " << config.a << ",   b = " << config.b << ",   c = " << config.c);
  position_hold_controller->setGains(config.a, config.b, config.c);
}

void Controller::spin()
{
  Eigen::Vector6d    tau_command          = Eigen::VectorXd::Zero(6);
  Eigen::Vector6d    tau_openloop         = Eigen::VectorXd::Zero(6);
  Eigen::Vector6d    tau_sixdof           = Eigen::VectorXd::Zero(6);

  Eigen::Vector3d    position_state       = Eigen::Vector3d::Zero();
  Eigen::Quaterniond orientation_state    = Eigen::Quaterniond::Identity();
  Eigen::Vector6d    velocity_state       = Eigen::VectorXd::Zero(6);

  Eigen::Vector3d    position_setpoint    = Eigen::Vector3d::Zero();
  Eigen::Quaterniond orientation_setpoint = Eigen::Quaterniond::Identity();

  ros::Rate rate(frequency);
  while (ros::ok())
  {
    // TODO: check value of bool return from getters
    state->get(position_state, orientation_state, velocity_state);
    setpoints->get(position_setpoint, orientation_setpoint);

    switch (control_mode)
    {
      case ControlModes::OPEN_LOOP:
      {
        setpoints->get(tau_command);
        break;
      }

      case ControlModes::SIXDOF:
      {
        tau_command = position_hold_controller->compute(position_state,
                                                        orientation_state,
                                                        velocity_state,
                                                        position_setpoint,
                                                        orientation_setpoint);
        break;
      }

      case ControlModes::RPY_DEPTH:
      {
        // TODO: make this similar to depth hold
        tau_command(0) = tau_openloop(0);
        tau_command(1) = tau_openloop(1);
        tau_command(2) = tau_sixdof(2);
        tau_command(3) = tau_sixdof(3);
        tau_command(4) = tau_sixdof(4);
        tau_command(5) = tau_sixdof(5);
        break;
      }

      case ControlModes::DEPTH_HOLD:
      {
        setpoints->get(tau_openloop);

        bool depth_change_commanded = abs(tau_openloop(2)) > FORCE_DEADZONE_LIMIT;
        if (depth_change_commanded)
        {
          tau_command = tau_openloop;
        }
        else
        {
          position_setpoint(0) = position_state(0);
          position_setpoint(1) = position_state(1);
          orientation_setpoint = orientation_state;
          tau_sixdof = position_hold_controller->compute(position_state,
                                                         orientation_state,
                                                         velocity_state,
                                                         position_setpoint,
                                                         orientation_setpoint);
          tau_command = tau_sixdof + tau_openloop;
        }
        break;
      }

      default:
      {
        ROS_ERROR("Default control mode reached in Controller::spin().");
        break;
      }
    }

    geometry_msgs::Wrench msg;
    tf::wrenchEigenToMsg(tau_command, msg);
    wrench_pub.publish(msg);

    ros::spinOnce();
    rate.sleep();
  }
}

void Controller::initSetpoints()
{
  std::vector<double> v;

  if (!nh.getParam("/propulsion/command/wrench/max", v))
    ROS_FATAL("Failed to read parameter max wrench command.");
  Eigen::Vector6d wrench_command_max = Eigen::Vector6d::Map(v.data(), v.size());

  if (!nh.getParam("/propulsion/command/wrench/scaling", v))
    ROS_FATAL("Failed to read parameter scaling wrench command.");
  Eigen::Vector6d wrench_command_scaling = Eigen::Vector6d::Map(v.data(), v.size());

  if (!nh.getParam("/propulsion/command/pose/rate", v))
    ROS_FATAL("Failed to read parameter pose command rate.");
  Eigen::Vector6d pose_command_rate = Eigen::Vector6d::Map(v.data(), v.size());

  setpoints = new Setpoints(wrench_command_scaling,
                            wrench_command_max,
                            pose_command_rate);
}

void Controller::initPositionHoldController()
{
  // Read controller gains from parameter server
  std::map<std::string,double> gains;
  if (!nh.getParam("/controller/gains", gains))
    ROS_ERROR("Failed to read parameter controller gains.");

  // Read center of gravity and buoyancy vectors
  std::vector<double> r_G_vec, r_B_vec;
  if (!nh.getParam("/physical/center_of_mass", r_G_vec))
    ROS_ERROR("Failed to read robot center of mass parameter.");
  if (!nh.getParam("/physical/center_of_buoyancy", r_B_vec))
    ROS_ERROR("Failed to read robot center of buoyancy parameter.");
  Eigen::Vector3d r_G(r_G_vec.data());
  Eigen::Vector3d r_B(r_B_vec.data());

  // Read and calculate ROV weight and buoyancy
  double mass, displacement, acceleration_of_gravity, density_of_water;
  if (!nh.getParam("/physical/mass_kg", mass))
    ROS_ERROR("Failed to read parameter mass.");
  if (!nh.getParam("/physical/displacement_m3", displacement))
    ROS_ERROR("Failed to read parameter displacement.");
  if (!nh.getParam("/gravity/acceleration", acceleration_of_gravity))
    ROS_ERROR("Failed to read parameter acceleration of gravity");
  if (!nh.getParam("/water/density", density_of_water))
    ROS_ERROR("Failed to read parameter density of water");
  double W = mass * acceleration_of_gravity;
  double B = density_of_water * displacement * acceleration_of_gravity;

  position_hold_controller = new QuaternionPdController(gains["a"],
                                                        gains["b"],
                                                        gains["c"],
                                                        W, B, r_G, r_B);
}

bool Controller::healthyMessage(const vortex_msgs::PropulsionCommand& msg)
{
  // Check that motion commands are in range
  for (int i = 0; i < msg.motion.size(); ++i)
  {
    if (msg.motion[i] > 1 || msg.motion[i] < -1)
    {
      ROS_WARN("Controller: Motion command out of range.");
      return false;
    }
  }

  // Check that exactly one control mode is requested
  int num_requested_modes = 0;
  for (int i = 0; i < msg.control_mode.size(); ++i)
    if (msg.control_mode[i])
      num_requested_modes++;

  if (num_requested_modes != 1)
  {
    ROS_WARN_STREAM("Controller: Invalid control mode. Attempt to set "
                    << num_requested_modes << " control modes at once.");
    return false;
  }

  return true;
}

void Controller::publishControlMode()
{
  std::string s = controlModeString(control_mode);
  std_msgs::String msg;
  msg.data = s;
  mode_pub.publish(msg);
}
<|endoftext|>


<commit_before>/*
 * A cache for NFS files.
 *
 * author: Max Kellermann <mk@cm4all.com>
 */

#include "nfs_cache.hxx"
#include "nfs_stock.hxx"
#include "nfs_client.hxx"
#include "istream_nfs.hxx"
#include "strmap.hxx"
#include "pool.hxx"
#include "rubber.hxx"
#include "sink_rubber.hxx"
#include "istream_unlock.hxx"
#include "istream_rubber.hxx"
#include "istream/istream.hxx"
#include "istream/istream_null.hxx"
#include "istream/istream_tee.hxx"
#include "AllocatorStats.hxx"
#include "cache.hxx"
#include "async.hxx"
#include "event/TimerEvent.hxx"

#include <boost/intrusive/list.hpp>

#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#ifdef CACHE_LOG
#include <daemon/log.h>
#define cache_log(...) daemon_log(__VA_ARGS__)
#else
#define cache_log(...) do {} while (0)
#endif

static constexpr struct timeval nfs_cache_compress_interval = { 600, 0 };

struct NfsCache;
struct NfsCacheItem;
struct NfsCacheStore;

struct NfsCacheStore final
    : public boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>>,
      RubberSinkHandler {

    struct pool &pool;

    NfsCache &cache;

    const char *key;

    struct stat stat;

    TimerEvent timeout_event;
    struct async_operation_ref async_ref;

    NfsCacheStore(struct pool &_pool, NfsCache &_cache,
                  const char *_key, const struct stat &_st);

    /**
     * Release resources held by this request.
     */
    void Release();

    /**
     * Abort the request.
     */
    void Abort();

    void Put(unsigned rubber_id);

    void OnTimeout() {
        /* reading the response has taken too long already; don't store
           this resource */
        cache_log(4, "nfs_cache: timeout %s\n", key);
        Abort();
    }

    /* virtual methods from class RubberSinkHandler */
    void RubberDone(unsigned rubber_id, size_t size) override;
    void RubberOutOfMemory() override;
    void RubberTooLarge() override;
    void RubberError(GError *error) override;
};

struct NfsCache {
    struct pool &pool;

    NfsStock &stock;
    EventLoop &event_loop;

    Cache &cache;

    TimerEvent compress_timer;

    Rubber &rubber;

    /**
     * A list of requests that are currently saving their contents to
     * the cache.
     */
    boost::intrusive::list<NfsCacheStore,
                           boost::intrusive::constant_time_size<false>> requests;

    NfsCache(struct pool &_pool, size_t max_size, NfsStock &_stock,
             EventLoop &_event_loop);

    ~NfsCache() {
        cache_close(&cache);
        compress_timer.Cancel();
        rubber_free(&rubber);
        pool_unref(&pool);
    }

private:
    void OnCompressTimer() {
        rubber_compress(&rubber);
        compress_timer.Add(nfs_cache_compress_interval);
    }
};

struct NfsCacheRequest {
    struct pool &pool;

    NfsCache &cache;

    const char *key;
    const char *path;

    const NfsCacheHandler &handler;
    void *handler_ctx;
    struct async_operation_ref &async_ref;

    NfsCacheRequest(struct pool &_pool, NfsCache &_cache,
                    const char *_key, const char *_path,
                    const NfsCacheHandler &_handler, void *_ctx,
                    struct async_operation_ref &_async_ref)
        :pool(_pool), cache(_cache),
         key(_key), path(_path),
         handler(_handler), handler_ctx(_ctx),
         async_ref(_async_ref) {}
};

struct NfsCacheHandle {
    NfsCache &cache;
    const char *key;

    NfsFileHandle *file;
    NfsCacheItem *item;
    const struct stat &stat;
};

struct NfsCacheItem {
    CacheItem item;

    struct pool &pool;

    struct stat stat;

    Rubber &rubber;
    unsigned rubber_id;

    NfsCacheItem(struct pool &_pool, const NfsCacheStore &store,
                 Rubber &_rubber, unsigned _rubber_id)
        :item(std::chrono::minutes(1), stat.st_size),
         pool(_pool), stat(store.stat),
         rubber(_rubber), rubber_id(_rubber_id) {
    }
};

static constexpr off_t cacheable_size_limit = 512 * 1024;

static constexpr struct timeval nfs_cache_timeout = { 60, 0 };

static const char *
nfs_cache_key(struct pool &pool, const char *server,
              const char *_export, const char *path)
{
    return p_strcat(&pool, server, ":", _export, path, nullptr);
}

static void
nfs_cache_request_error(GError *error, void *ctx)
{
    NfsCacheRequest *r = (NfsCacheRequest *)ctx;

    r->handler.error(error, r->handler_ctx);
}

NfsCacheStore::NfsCacheStore(struct pool &_pool, NfsCache &_cache,
                             const char *_key, const struct stat &_st)
    :pool(_pool), cache(_cache),
     key(_key),
     stat(_st),
     timeout_event(cache.event_loop, BIND_THIS_METHOD(OnTimeout)) {}

void
NfsCacheStore::Release()
{
    assert(!async_ref.IsDefined());

    timeout_event.Cancel();

    cache.requests.erase(cache.requests.iterator_to(*this));
    pool_unref(&pool);
}

void
NfsCacheStore::Abort()
{
    assert(async_ref.IsDefined());

    async_ref.Abort();
    async_ref.Clear();
    Release();
}

void
NfsCacheStore::Put(unsigned rubber_id)
{
    cache_log(4, "nfs_cache: put %s\n", key);

    struct pool *item_pool = pool_new_libc(&cache.pool, "nfs_cache_item");
    const auto item = NewFromPool<NfsCacheItem>(*item_pool, *item_pool, *this,
                                                cache.rubber, rubber_id);
    cache_put(&cache.cache, p_strdup(item_pool, key), &item->item);
}

/*
 * sink_rubber_handler
 *
 */

void
NfsCacheStore::RubberDone(unsigned rubber_id, gcc_unused size_t size)
{
    assert((off_t)size == stat.st_size);

    async_ref.Clear();

    /* the request was successful, and all of the body data has been
       saved: add it to the cache */
    Put(rubber_id);

    Release();
}

void
NfsCacheStore::RubberOutOfMemory()
{
    async_ref.Clear();

    cache_log(4, "nfs_cache: nocache oom %s\n", key);
    Release();
}

void
NfsCacheStore::RubberTooLarge()
{
    async_ref.Clear();

    cache_log(4, "nfs_cache: nocache too large %s\n", key);
    Release();
}

void
NfsCacheStore::RubberError(GError *error)
{
    async_ref.Clear();

    cache_log(4, "nfs_cache: body_abort %s: %s\n", key, error->message);
    g_error_free(error);

    Release();
}

/*
 * NfsClientOpenFileHandler
 *
 */

static void
nfs_open_ready(NfsFileHandle *handle, const struct stat *st,
               void *ctx)
{
    auto &r = *(NfsCacheRequest *)ctx;

    NfsCacheHandle handle2 = {
        .cache = r.cache,
        .key = r.key,
        .file = handle,
        .item = nullptr,
        .stat = *st,
    };

    r.handler.response(handle2, *st, r.handler_ctx);

    if (handle2.file != nullptr)
        nfs_client_close_file(handle2.file);
}

static constexpr NfsClientOpenFileHandler nfs_open_handler = {
    .ready = nfs_open_ready,
    .error = nfs_cache_request_error,
};

/*
 * nfs_stock_get_handler
 *
 */

static void
nfs_cache_request_stock_ready(NfsClient *client, void *ctx)
{
    auto &r = *(NfsCacheRequest *)ctx;

    nfs_client_open_file(client, &r.pool, r.path,
                         &nfs_open_handler, &r, &r.async_ref);
}

static constexpr NfsStockGetHandler nfs_cache_request_stock_handler = {
    .ready = nfs_cache_request_stock_ready,
    .error = nfs_cache_request_error,
};

/*
 * cache_class
 *
 */

static bool
nfs_cache_item_validate(CacheItem *_item)
{
    const auto &item = *(NfsCacheItem *)_item;

    (void)item;
    return true;
}

static void
nfs_cache_item_destroy(CacheItem *_item)
{
    const auto &item = *(NfsCacheItem *)_item;

    if (item.rubber_id != 0)
        rubber_remove(&item.rubber, item.rubber_id);

    pool_unref(&item.pool);
}

static constexpr CacheClass nfs_cache_class = {
    .validate = nfs_cache_item_validate,
    .destroy = nfs_cache_item_destroy,
};

/*
 * constructor
 *
 */

static Rubber &
NewRubberOrAbort(size_t max_size)
{
    auto r = rubber_new(max_size);
    if (r == nullptr) {
        fprintf(stderr, "Failed to allocate HTTP cache: %s\n",
                strerror(errno));
        exit(2);
    }

    return *r;
}

inline
NfsCache::NfsCache(struct pool &_pool, size_t max_size,
                   NfsStock &_stock, EventLoop &_event_loop)
    :pool(*pool_new_libc(&_pool, "nfs_cache")), stock(_stock),
     event_loop(_event_loop),
     cache(*cache_new(pool, event_loop, nfs_cache_class,
                      65521, max_size * 7 / 8)),
     compress_timer(event_loop, BIND_THIS_METHOD(OnCompressTimer)),
     rubber(NewRubberOrAbort(max_size)) {
    compress_timer.Add(nfs_cache_compress_interval);
}

NfsCache *
nfs_cache_new(struct pool &_pool, size_t max_size,
              NfsStock &stock, EventLoop &event_loop)
{
    return new NfsCache(_pool, max_size, stock, event_loop);
}

void
nfs_cache_free(NfsCache *cache)
{
    assert(cache != nullptr);

    delete cache;
}

AllocatorStats
nfs_cache_get_stats(const NfsCache &cache)
{
    return cache_get_stats(cache.cache) + rubber_get_stats(cache.rubber);
}

void
nfs_cache_fork_cow(NfsCache &cache, bool inherit)
{
    rubber_fork_cow(&cache.rubber, inherit);
}

void
nfs_cache_request(struct pool &pool, NfsCache &cache,
                  const char *server, const char *_export, const char *path,
                  const NfsCacheHandler &handler, void *ctx,
                  struct async_operation_ref &async_ref)
{
    const char *key = nfs_cache_key(pool, server, _export, path);
    const auto item = (NfsCacheItem *)cache_get(&cache.cache, key);
    if (item != nullptr) {
        cache_log(4, "nfs_cache: hit %s\n", key);

        NfsCacheHandle handle2 = {
            .cache = cache,
            .key = key,
            .file = nullptr,
            .item = item,
            .stat = item->stat,
        };

        handler.response(handle2, item->stat, ctx);
        return;
    }

    cache_log(4, "nfs_cache: miss %s\n", key);

    auto r = NewFromPool<NfsCacheRequest>(pool, pool, cache,
                                          key, path,
                                          handler, ctx, async_ref);
    nfs_stock_get(&cache.stock, &pool, server, _export,
                  &nfs_cache_request_stock_handler, r,
                  &async_ref);
}

static Istream *
nfs_cache_item_open(struct pool &pool, NfsCache &cache,
                    NfsCacheItem &item,
                    uint64_t start, uint64_t end)
{
    assert(start <= end);
    assert(end <= (uint64_t)item.stat.st_size);

    assert(item.rubber_id != 0);

    Istream *istream =
        istream_rubber_new(pool, item.rubber, item.rubber_id,
                           start, end, false);
    return istream_unlock_new(pool, *istream, cache.cache, item.item);
}

static Istream *
nfs_cache_file_open(struct pool &pool, NfsCache &cache,
                    const char *key,
                    NfsFileHandle &file, const struct stat &st,
                    uint64_t start, uint64_t end)
{
    assert(start <= end);
    assert(end <= (uint64_t)st.st_size);

    Istream *body = istream_nfs_new(pool, file, start, end);
    if (st.st_size > cacheable_size_limit || start != 0 ||
        end != (uint64_t)st.st_size) {
        /* don't cache */
        cache_log(4, "nfs_cache: nocache %s\n", key);
        return body;
    }

    /* move all this stuff to a new pool, so istream_tee's second head
       can continue to fill the cache even if our caller gave up on
       it */
    struct pool *pool2 = pool_new_linear(&cache.pool,
                                         "nfs_cache_tee", 1024);
    auto store = NewFromPool<NfsCacheStore>(*pool2, *pool2, cache,
                                            p_strdup(pool2, key), st);

    /* tee the body: one goes to our client, and one goes into the
       cache */
    body = istream_tee_new(*pool2, *body,
                           cache.event_loop,
                           false, true);

    cache.requests.push_back(*store);

    store->timeout_event.Add(nfs_cache_timeout);

    sink_rubber_new(*pool2, istream_tee_second(*body),
                    cache.rubber, cacheable_size_limit,
                    *store,
                    store->async_ref);

    return body;
}

Istream *
nfs_cache_handle_open(struct pool &pool, NfsCacheHandle &handle,
                      uint64_t start, uint64_t end)
{
    assert((handle.file == nullptr) != (handle.item == nullptr));
    assert(start <= end);
    assert(end <= (uint64_t)handle.stat.st_size);

    if (start == end)
        return istream_null_new(&pool);

    if (handle.item != nullptr) {
        /* cache hit: serve cached file */
        cache_log(5, "nfs_cache: serve %s\n", handle.key);
        return nfs_cache_item_open(pool, handle.cache, *handle.item,
                                   start, end);
    } else {
        /* cache miss: load from NFS server */
        NfsFileHandle *const file = handle.file;
        handle.file = nullptr;

        return nfs_cache_file_open(pool, handle.cache, handle.key,
                                   *file, handle.stat, start, end);
    }
}
<commit_msg>nfs_cache: use CacheItem as base class<commit_after>/*
 * A cache for NFS files.
 *
 * author: Max Kellermann <mk@cm4all.com>
 */

#include "nfs_cache.hxx"
#include "nfs_stock.hxx"
#include "nfs_client.hxx"
#include "istream_nfs.hxx"
#include "strmap.hxx"
#include "pool.hxx"
#include "rubber.hxx"
#include "sink_rubber.hxx"
#include "istream_unlock.hxx"
#include "istream_rubber.hxx"
#include "istream/istream.hxx"
#include "istream/istream_null.hxx"
#include "istream/istream_tee.hxx"
#include "AllocatorStats.hxx"
#include "cache.hxx"
#include "async.hxx"
#include "event/TimerEvent.hxx"

#include <boost/intrusive/list.hpp>

#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#ifdef CACHE_LOG
#include <daemon/log.h>
#define cache_log(...) daemon_log(__VA_ARGS__)
#else
#define cache_log(...) do {} while (0)
#endif

static constexpr struct timeval nfs_cache_compress_interval = { 600, 0 };

struct NfsCache;
struct NfsCacheItem;
struct NfsCacheStore;

struct NfsCacheStore final
    : public boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>>,
      RubberSinkHandler {

    struct pool &pool;

    NfsCache &cache;

    const char *key;

    struct stat stat;

    TimerEvent timeout_event;
    struct async_operation_ref async_ref;

    NfsCacheStore(struct pool &_pool, NfsCache &_cache,
                  const char *_key, const struct stat &_st);

    /**
     * Release resources held by this request.
     */
    void Release();

    /**
     * Abort the request.
     */
    void Abort();

    void Put(unsigned rubber_id);

    void OnTimeout() {
        /* reading the response has taken too long already; don't store
           this resource */
        cache_log(4, "nfs_cache: timeout %s\n", key);
        Abort();
    }

    /* virtual methods from class RubberSinkHandler */
    void RubberDone(unsigned rubber_id, size_t size) override;
    void RubberOutOfMemory() override;
    void RubberTooLarge() override;
    void RubberError(GError *error) override;
};

struct NfsCache {
    struct pool &pool;

    NfsStock &stock;
    EventLoop &event_loop;

    Cache &cache;

    TimerEvent compress_timer;

    Rubber &rubber;

    /**
     * A list of requests that are currently saving their contents to
     * the cache.
     */
    boost::intrusive::list<NfsCacheStore,
                           boost::intrusive::constant_time_size<false>> requests;

    NfsCache(struct pool &_pool, size_t max_size, NfsStock &_stock,
             EventLoop &_event_loop);

    ~NfsCache() {
        cache_close(&cache);
        compress_timer.Cancel();
        rubber_free(&rubber);
        pool_unref(&pool);
    }

private:
    void OnCompressTimer() {
        rubber_compress(&rubber);
        compress_timer.Add(nfs_cache_compress_interval);
    }
};

struct NfsCacheRequest {
    struct pool &pool;

    NfsCache &cache;

    const char *key;
    const char *path;

    const NfsCacheHandler &handler;
    void *handler_ctx;
    struct async_operation_ref &async_ref;

    NfsCacheRequest(struct pool &_pool, NfsCache &_cache,
                    const char *_key, const char *_path,
                    const NfsCacheHandler &_handler, void *_ctx,
                    struct async_operation_ref &_async_ref)
        :pool(_pool), cache(_cache),
         key(_key), path(_path),
         handler(_handler), handler_ctx(_ctx),
         async_ref(_async_ref) {}
};

struct NfsCacheHandle {
    NfsCache &cache;
    const char *key;

    NfsFileHandle *file;
    NfsCacheItem *item;
    const struct stat &stat;
};

struct NfsCacheItem final : CacheItem {
    struct pool &pool;

    struct stat stat;

    Rubber &rubber;
    unsigned rubber_id;

    NfsCacheItem(struct pool &_pool, const NfsCacheStore &store,
                 Rubber &_rubber, unsigned _rubber_id)
        :CacheItem(std::chrono::minutes(1), stat.st_size),
         pool(_pool), stat(store.stat),
         rubber(_rubber), rubber_id(_rubber_id) {
    }
};

static constexpr off_t cacheable_size_limit = 512 * 1024;

static constexpr struct timeval nfs_cache_timeout = { 60, 0 };

static const char *
nfs_cache_key(struct pool &pool, const char *server,
              const char *_export, const char *path)
{
    return p_strcat(&pool, server, ":", _export, path, nullptr);
}

static void
nfs_cache_request_error(GError *error, void *ctx)
{
    NfsCacheRequest *r = (NfsCacheRequest *)ctx;

    r->handler.error(error, r->handler_ctx);
}

NfsCacheStore::NfsCacheStore(struct pool &_pool, NfsCache &_cache,
                             const char *_key, const struct stat &_st)
    :pool(_pool), cache(_cache),
     key(_key),
     stat(_st),
     timeout_event(cache.event_loop, BIND_THIS_METHOD(OnTimeout)) {}

void
NfsCacheStore::Release()
{
    assert(!async_ref.IsDefined());

    timeout_event.Cancel();

    cache.requests.erase(cache.requests.iterator_to(*this));
    pool_unref(&pool);
}

void
NfsCacheStore::Abort()
{
    assert(async_ref.IsDefined());

    async_ref.Abort();
    async_ref.Clear();
    Release();
}

void
NfsCacheStore::Put(unsigned rubber_id)
{
    cache_log(4, "nfs_cache: put %s\n", key);

    struct pool *item_pool = pool_new_libc(&cache.pool, "nfs_cache_item");
    const auto item = NewFromPool<NfsCacheItem>(*item_pool, *item_pool, *this,
                                                cache.rubber, rubber_id);
    cache_put(&cache.cache, p_strdup(item_pool, key), item);
}

/*
 * sink_rubber_handler
 *
 */

void
NfsCacheStore::RubberDone(unsigned rubber_id, gcc_unused size_t size)
{
    assert((off_t)size == stat.st_size);

    async_ref.Clear();

    /* the request was successful, and all of the body data has been
       saved: add it to the cache */
    Put(rubber_id);

    Release();
}

void
NfsCacheStore::RubberOutOfMemory()
{
    async_ref.Clear();

    cache_log(4, "nfs_cache: nocache oom %s\n", key);
    Release();
}

void
NfsCacheStore::RubberTooLarge()
{
    async_ref.Clear();

    cache_log(4, "nfs_cache: nocache too large %s\n", key);
    Release();
}

void
NfsCacheStore::RubberError(GError *error)
{
    async_ref.Clear();

    cache_log(4, "nfs_cache: body_abort %s: %s\n", key, error->message);
    g_error_free(error);

    Release();
}

/*
 * NfsClientOpenFileHandler
 *
 */

static void
nfs_open_ready(NfsFileHandle *handle, const struct stat *st,
               void *ctx)
{
    auto &r = *(NfsCacheRequest *)ctx;

    NfsCacheHandle handle2 = {
        .cache = r.cache,
        .key = r.key,
        .file = handle,
        .item = nullptr,
        .stat = *st,
    };

    r.handler.response(handle2, *st, r.handler_ctx);

    if (handle2.file != nullptr)
        nfs_client_close_file(handle2.file);
}

static constexpr NfsClientOpenFileHandler nfs_open_handler = {
    .ready = nfs_open_ready,
    .error = nfs_cache_request_error,
};

/*
 * nfs_stock_get_handler
 *
 */

static void
nfs_cache_request_stock_ready(NfsClient *client, void *ctx)
{
    auto &r = *(NfsCacheRequest *)ctx;

    nfs_client_open_file(client, &r.pool, r.path,
                         &nfs_open_handler, &r, &r.async_ref);
}

static constexpr NfsStockGetHandler nfs_cache_request_stock_handler = {
    .ready = nfs_cache_request_stock_ready,
    .error = nfs_cache_request_error,
};

/*
 * cache_class
 *
 */

static bool
nfs_cache_item_validate(CacheItem *_item)
{
    const auto &item = *(NfsCacheItem *)_item;

    (void)item;
    return true;
}

static void
nfs_cache_item_destroy(CacheItem *_item)
{
    const auto &item = *(NfsCacheItem *)_item;

    if (item.rubber_id != 0)
        rubber_remove(&item.rubber, item.rubber_id);

    pool_unref(&item.pool);
}

static constexpr CacheClass nfs_cache_class = {
    .validate = nfs_cache_item_validate,
    .destroy = nfs_cache_item_destroy,
};

/*
 * constructor
 *
 */

static Rubber &
NewRubberOrAbort(size_t max_size)
{
    auto r = rubber_new(max_size);
    if (r == nullptr) {
        fprintf(stderr, "Failed to allocate HTTP cache: %s\n",
                strerror(errno));
        exit(2);
    }

    return *r;
}

inline
NfsCache::NfsCache(struct pool &_pool, size_t max_size,
                   NfsStock &_stock, EventLoop &_event_loop)
    :pool(*pool_new_libc(&_pool, "nfs_cache")), stock(_stock),
     event_loop(_event_loop),
     cache(*cache_new(pool, event_loop, nfs_cache_class,
                      65521, max_size * 7 / 8)),
     compress_timer(event_loop, BIND_THIS_METHOD(OnCompressTimer)),
     rubber(NewRubberOrAbort(max_size)) {
    compress_timer.Add(nfs_cache_compress_interval);
}

NfsCache *
nfs_cache_new(struct pool &_pool, size_t max_size,
              NfsStock &stock, EventLoop &event_loop)
{
    return new NfsCache(_pool, max_size, stock, event_loop);
}

void
nfs_cache_free(NfsCache *cache)
{
    assert(cache != nullptr);

    delete cache;
}

AllocatorStats
nfs_cache_get_stats(const NfsCache &cache)
{
    return cache_get_stats(cache.cache) + rubber_get_stats(cache.rubber);
}

void
nfs_cache_fork_cow(NfsCache &cache, bool inherit)
{
    rubber_fork_cow(&cache.rubber, inherit);
}

void
nfs_cache_request(struct pool &pool, NfsCache &cache,
                  const char *server, const char *_export, const char *path,
                  const NfsCacheHandler &handler, void *ctx,
                  struct async_operation_ref &async_ref)
{
    const char *key = nfs_cache_key(pool, server, _export, path);
    const auto item = (NfsCacheItem *)cache_get(&cache.cache, key);
    if (item != nullptr) {
        cache_log(4, "nfs_cache: hit %s\n", key);

        NfsCacheHandle handle2 = {
            .cache = cache,
            .key = key,
            .file = nullptr,
            .item = item,
            .stat = item->stat,
        };

        handler.response(handle2, item->stat, ctx);
        return;
    }

    cache_log(4, "nfs_cache: miss %s\n", key);

    auto r = NewFromPool<NfsCacheRequest>(pool, pool, cache,
                                          key, path,
                                          handler, ctx, async_ref);
    nfs_stock_get(&cache.stock, &pool, server, _export,
                  &nfs_cache_request_stock_handler, r,
                  &async_ref);
}

static Istream *
nfs_cache_item_open(struct pool &pool, NfsCache &cache,
                    NfsCacheItem &item,
                    uint64_t start, uint64_t end)
{
    assert(start <= end);
    assert(end <= (uint64_t)item.stat.st_size);

    assert(item.rubber_id != 0);

    Istream *istream =
        istream_rubber_new(pool, item.rubber, item.rubber_id,
                           start, end, false);
    return istream_unlock_new(pool, *istream, cache.cache, item);
}

static Istream *
nfs_cache_file_open(struct pool &pool, NfsCache &cache,
                    const char *key,
                    NfsFileHandle &file, const struct stat &st,
                    uint64_t start, uint64_t end)
{
    assert(start <= end);
    assert(end <= (uint64_t)st.st_size);

    Istream *body = istream_nfs_new(pool, file, start, end);
    if (st.st_size > cacheable_size_limit || start != 0 ||
        end != (uint64_t)st.st_size) {
        /* don't cache */
        cache_log(4, "nfs_cache: nocache %s\n", key);
        return body;
    }

    /* move all this stuff to a new pool, so istream_tee's second head
       can continue to fill the cache even if our caller gave up on
       it */
    struct pool *pool2 = pool_new_linear(&cache.pool,
                                         "nfs_cache_tee", 1024);
    auto store = NewFromPool<NfsCacheStore>(*pool2, *pool2, cache,
                                            p_strdup(pool2, key), st);

    /* tee the body: one goes to our client, and one goes into the
       cache */
    body = istream_tee_new(*pool2, *body,
                           cache.event_loop,
                           false, true);

    cache.requests.push_back(*store);

    store->timeout_event.Add(nfs_cache_timeout);

    sink_rubber_new(*pool2, istream_tee_second(*body),
                    cache.rubber, cacheable_size_limit,
                    *store,
                    store->async_ref);

    return body;
}

Istream *
nfs_cache_handle_open(struct pool &pool, NfsCacheHandle &handle,
                      uint64_t start, uint64_t end)
{
    assert((handle.file == nullptr) != (handle.item == nullptr));
    assert(start <= end);
    assert(end <= (uint64_t)handle.stat.st_size);

    if (start == end)
        return istream_null_new(&pool);

    if (handle.item != nullptr) {
        /* cache hit: serve cached file */
        cache_log(5, "nfs_cache: serve %s\n", handle.key);
        return nfs_cache_item_open(pool, handle.cache, *handle.item,
                                   start, end);
    } else {
        /* cache miss: load from NFS server */
        NfsFileHandle *const file = handle.file;
        handle.file = nullptr;

        return nfs_cache_file_open(pool, handle.cache, handle.key,
                                   *file, handle.stat, start, end);
    }
}
<|endoftext|>


<commit_before>#include "rectdiff.hpp"

namespace spn {
	namespace rect {
		int LoopValue(const int t, const int w) {
			if(t < 0)
				return (w-1)+((t+1)%w);
			return t % w;
		}
		int LoopValueD(const int t, const int w) {
			if(t < 0)
				return ((t+1)/w)-1;
			return (t/w)+1;
		}
		void IncrementalRect(const spn::Rect& base, const int dx, const int dy, const Rect_cb& cb) {
			if(dx==0 && dy==0)
				return;

			int x0=0, x1=0;
			int y0=0, y1=0;
			if(dx > 0) {
				x0 = base.x1;
				x1 = x0+dx;
			} else if(dx < 0) {
				x1 = base.x0;
				x0 = x1+dx;
			}
			if(dy > 0) {
				y0 = base.y1;
				y1 = y0+dy;
			} else if(dy < 0) {
				y1 = base.y0;
				y0 = y1+dy;
			}
			if(x0 != x1) {
				cb({x0,x1, base.y0+dy, base.y1+dy});
			}
			if(y0 != y1) {
				if(std::abs(dx) < base.width()) {
					if(dx > 0) {
						cb({base.x0+dx, base.x1, y0, y1});
					} else {
						cb({base.x0, base.x1+dx, y0, y1});
					}
				}
			}
		}
		void DivideRect(const spn::Size& size, const spn::Rect& request, const DRect_cb& cb) {
			// 更新領域のうちの左下(原点より)は常に更新
			const auto tw = size.width,
						th = size.height;
			const bool bX = LoopValueD(request.x0, tw) != LoopValueD(request.x1, tw),
					 bY = LoopValueD(request.y0, th) != LoopValueD(request.y1, th);
			const int x = LoopValue(request.x0, tw),
					y = LoopValue(request.y0, th);
			using Rect = spn::Rect;
			const Rect r(x, (bX ? tw : LoopValue(request.x1, tw)),
							y, (bY ? th : LoopValue(request.y1, th)));
			const auto btm = [](auto x, auto w){
				return x/w*w;
			};
			cb(
				Rect(
					request.x0,
					(bX ? btm(request.x1, tw) : request.x1),
					request.y0,
					(bY ? btm(request.y1, th) : request.y1)
				),
				r
			);
			// 更新領域のうちの右下
			if(bX) {
				cb(
					Rect(
						btm(request.x1, tw),
						request.x1,
						request.y0,
						(bY ? btm(request.y1, th) : request.y1)
					),
					Rect(
						0,
						LoopValue(request.x1, tw),
						r.y0,
						r.y1
					)
				);
			}
			// 更新領域のうちの左上
			if(bY) {
				cb(
					Rect(
						request.x0,
						btm(request.x1, tw),
						btm(request.y1, th),
						request.y1
					),
					Rect(
						r.x0,
						r.x1,
						0,
						LoopValue(request.y1, th)
					)
				);
			}
			// 更新領域のうちの右上
			if(bX && bY) {
				cb(
					Rect(
						btm(request.x1, tw),
						request.x1,
						btm(request.y1, th),
						request.y1
					),
					Rect(
						0,
						LoopValue(request.x1, tw),
						0,
						LoopValue(request.y1, th)
					)
				);
			}
		}
	}
}
<commit_msg>RectDiff: バグ修正<commit_after>#include "rectdiff.hpp"

namespace spn {
	namespace rect {
		int LoopValue(const int t, const int w) {
			if(t < 0)
				return (w-1)+((t+1)%w);
			return t % w;
		}
		int LoopValueD(const int t, const int w) {
			if(t < 0)
				return ((t+1)/w)-1;
			return t/w;
		}
		void IncrementalRect(const spn::Rect& base, const int dx, const int dy, const Rect_cb& cb) {
			// 動いて無ければ何もせず終了
			if(dx==0 && dy==0)
				return;
			// 前後の矩形が重ならない場合、単に移動後の矩形でコールバック
			if(std::abs(dx) >= base.width() ||
				std::abs(dy) >= base.height())
			{
				cb(base.move(dx, dy));
				return;
			}

			int x0=0, x1=0;
			int y0=0, y1=0;
			if(dx > 0) {
				x0 = base.x1;
				x1 = x0+dx;
			} else if(dx < 0) {
				x1 = base.x0;
				x0 = x1+dx;
			}
			if(dy > 0) {
				y0 = base.y1;
				y1 = y0+dy;
			} else if(dy < 0) {
				y1 = base.y0;
				y0 = y1+dy;
			}
			if(x0 != x1) {
				cb({x0,x1, base.y0+dy, base.y1+dy});
			}
			if(y0 != y1) {
				if(std::abs(dx) < base.width()) {
					if(dx > 0) {
						cb({base.x0+dx, base.x1, y0, y1});
					} else {
						cb({base.x0, base.x1+dx, y0, y1});
					}
				}
			}
		}
		void DivideRect(const spn::Size& size, const spn::Rect& request, const DRect_cb& cb) {
			const auto btm = [](auto x, auto w){
				return x/w*w;
			};
			struct Range {
				int g0, g1;
				int l0, l1;
			};
			auto fnRange = [&btm](Range (&res)[2], int x0, int x1, int w){
				const bool bX = LoopValueD(x0, w) != LoopValueD(x1, w);
				const auto lpx0 = LoopValue(x0,w),
							lpx1 = LoopValue(x1,w);
				if(bX) {
					const int mdl = btm(x1, w);
					res[0] = Range{x0,mdl, lpx0,w};
					res[1] = Range{mdl,x1, 0,lpx1};
					return (mdl==x1) ? 1 : 2;
				} else {
					res[0] = Range{x0,x1, lpx0,lpx1};
					return 1;
				}
			};
			const auto tw = size.width,
						th = size.height;
			Range xRange[2],
				yRange[2];
			const int nx = fnRange(xRange, request.x0, request.x1, tw),
					ny = fnRange(yRange, request.y0, request.y1, th);
			using Rect = spn::Rect;
			for(int i=0 ; i<ny ; i++) {
				auto& yr = yRange[i];
				for(int j=0 ; j<nx ; j++) {
					auto& xr = xRange[j];
					cb(
						Rect(
							xr.g0, xr.g1,
							yr.g0, yr.g1
						),
						Rect(
							xr.l0, xr.l1,
							yr.l0, yr.l1
						)
					);
				}
			}
		}
	}
}
<|endoftext|>


<commit_before>/*
 *  prayManager.cpp
 *  openc2e
 *
 *  Created by Alyssa Milburn on Mon Jan 16 2006.
 *  Copyright (c) 2006 Alyssa Milburn. All rights reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library 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.
 *
 */

#include "prayManager.h"
#include "exceptions.h"
#include "World.h" // data_directories
#include <boost/filesystem/convenience.hpp>

prayManager::~prayManager() {
	while (files.size() != 0) {
		prayFile *f = files[0];
		removeFile(f);
		delete f;
	}

	assert(blocks.size() == 0);
}

void prayManager::addFile(prayFile *f) {
	std::vector<prayFile *>::iterator p = std::find(files.begin(), files.end(), f);
	assert(p == files.end());
	files.push_back(f);

	for (std::vector<prayBlock *>::iterator i = f->blocks.begin(); i != f->blocks.end(); i++) {
		if (blocks.find((*i)->name) != blocks.end()) // garr, block conflict
			continue;
		//assert(blocks.find((*i)->name) == blocks.end());
		blocks[(*i)->name] = *i;
	}
}

void prayManager::removeFile(prayFile *f) {
	std::vector<prayFile *>::iterator p = std::find(files.begin(), files.end(), f);
	assert(p != files.end());
	files.erase(p);

	for (std::vector<prayBlock *>::iterator i = f->blocks.begin(); i != f->blocks.end(); i++) {
		if (blocks.find((*i)->name) == blocks.end()) // garr, block conflict
			continue;
		/*assert(blocks.find((*i)->name) != blocks.end());
		assert(blocks[(*i)->name] == *i); */
		blocks.erase(blocks.find((*i)->name));
	}
}

void prayManager::update() {
	if (files.size() != 0) return; // TODO: Handle actual update cases, rather than just the initial init.

	if (!world.catalogue.hasTag("Pray System File Extensions"))
		throw creaturesException("couldn't find the catalogue tag \"Pray System File Extensions\"");

	const std::vector<std::string> &extensions = world.catalogue.getTag("Pray System File Extensions");

	for (std::vector<fs::path>::iterator i = world.data_directories.begin(); i != world.data_directories.end(); i++) {
		assert(fs::exists(*i));
		assert(fs::is_directory(*i));

		fs::path praydir(*i / fs::path("/My Agents/", fs::native));

		if (fs::exists(praydir) && fs::is_directory(praydir)) {
			fs::directory_iterator fsend;
			for (fs::directory_iterator d(praydir); d != fsend; ++d) {
				std::string x = fs::extension(*d);
				x.erase(x.begin());
				if (std::find(extensions.begin(), extensions.end(), x) != extensions.end()) {
					// TODO: language checking!
					//std::cout << "scanning PRAY file " << d->native_directory_string() << std::endl;
					try {
						prayFile *p = new prayFile(*d);
						addFile(p);
					} catch (creaturesException &e) {
						std::cerr << "PRAY file \"" << d->native_directory_string() << "\" failed to load: " << e.what() << std::endl;
					}
				}
			}
		}
	}
}

std::string prayManager::getResourceDir(unsigned int type) {
	switch (type) {
		case 0: return "/"; // main
		case 1: return "/Sounds/"; // sounds
		case 2: return "/Images/"; // images
		case 3: return "/Genetics/"; // genetics
		case 4: return "/Body Data/"; // body data
		case 5: return "/Overlay Data/"; // overlay data
		case 6: return "/Backgrounds/";// backgrounds
		case 7: return "/Catalogue/"; // catalogue
		//case 8: return "/Bootstrap/"; // bootstrap
		//case 9: return "/My Worlds/"; // my worlds
		case 10: return "/My Creatures/";// my creatures
		case 11: return "/My Agents/"; // my agents
	}

	return "";
}

/* vim: set noet: */
<commit_msg>make prayManager warn on missing catalogue tag, rather than throwing an exception<commit_after>/*
 *  prayManager.cpp
 *  openc2e
 *
 *  Created by Alyssa Milburn on Mon Jan 16 2006.
 *  Copyright (c) 2006 Alyssa Milburn. All rights reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library 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.
 *
 */

#include "prayManager.h"
#include "exceptions.h"
#include "World.h" // data_directories
#include <boost/filesystem/convenience.hpp>

prayManager::~prayManager() {
	while (files.size() != 0) {
		prayFile *f = files[0];
		removeFile(f);
		delete f;
	}

	assert(blocks.size() == 0);
}

void prayManager::addFile(prayFile *f) {
	std::vector<prayFile *>::iterator p = std::find(files.begin(), files.end(), f);
	assert(p == files.end());
	files.push_back(f);

	for (std::vector<prayBlock *>::iterator i = f->blocks.begin(); i != f->blocks.end(); i++) {
		if (blocks.find((*i)->name) != blocks.end()) // garr, block conflict
			continue;
		//assert(blocks.find((*i)->name) == blocks.end());
		blocks[(*i)->name] = *i;
	}
}

void prayManager::removeFile(prayFile *f) {
	std::vector<prayFile *>::iterator p = std::find(files.begin(), files.end(), f);
	assert(p != files.end());
	files.erase(p);

	for (std::vector<prayBlock *>::iterator i = f->blocks.begin(); i != f->blocks.end(); i++) {
		if (blocks.find((*i)->name) == blocks.end()) // garr, block conflict
			continue;
		/*assert(blocks.find((*i)->name) != blocks.end());
		assert(blocks[(*i)->name] == *i); */
		blocks.erase(blocks.find((*i)->name));
	}
}

void prayManager::update() {
	if (files.size() != 0) return; // TODO: Handle actual update cases, rather than just the initial init.

	if (!world.catalogue.hasTag("Pray System File Extensions")) {
		std::cout << "Warning: Catalogue tag \"Pray System File Extensions\" wasn't found, so no PRAY files will be loaded." << std::endl;
		return;
	}

	const std::vector<std::string> &extensions = world.catalogue.getTag("Pray System File Extensions");

	for (std::vector<fs::path>::iterator i = world.data_directories.begin(); i != world.data_directories.end(); i++) {
		assert(fs::exists(*i));
		assert(fs::is_directory(*i));

		fs::path praydir(*i / fs::path("/My Agents/", fs::native));

		if (fs::exists(praydir) && fs::is_directory(praydir)) {
			fs::directory_iterator fsend;
			for (fs::directory_iterator d(praydir); d != fsend; ++d) {
				std::string x = fs::extension(*d);
				x.erase(x.begin());
				if (std::find(extensions.begin(), extensions.end(), x) != extensions.end()) {
					// TODO: language checking!
					//std::cout << "scanning PRAY file " << d->native_directory_string() << std::endl;
					try {
						prayFile *p = new prayFile(*d);
						addFile(p);
					} catch (creaturesException &e) {
						std::cerr << "PRAY file \"" << d->native_directory_string() << "\" failed to load: " << e.what() << std::endl;
					}
				}
			}
		}
	}
}

std::string prayManager::getResourceDir(unsigned int type) {
	switch (type) {
		case 0: return "/"; // main
		case 1: return "/Sounds/"; // sounds
		case 2: return "/Images/"; // images
		case 3: return "/Genetics/"; // genetics
		case 4: return "/Body Data/"; // body data
		case 5: return "/Overlay Data/"; // overlay data
		case 6: return "/Backgrounds/";// backgrounds
		case 7: return "/Catalogue/"; // catalogue
		//case 8: return "/Bootstrap/"; // bootstrap
		//case 9: return "/My Worlds/"; // my worlds
		case 10: return "/My Creatures/";// my creatures
		case 11: return "/My Agents/"; // my agents
	}

	return "";
}

/* vim: set noet: */
<|endoftext|>


<commit_before>/*
 *  prayManager.cpp
 *  openc2e
 *
 *  Created by Alyssa Milburn on Mon Jan 16 2006.
 *  Copyright (c) 2006 Alyssa Milburn. All rights reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library 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.
 *
 */

#include "prayManager.h"
#include "exceptions.h"
#include "World.h" // data_directories
#include <boost/filesystem/convenience.hpp>

prayManager::~prayManager() {
	while (files.size() != 0) {
		prayFile *f = files[0];
		removeFile(f);
		delete f;
	}

	assert(blocks.size() == 0);
}

void prayManager::addFile(prayFile *f) {
	std::vector<prayFile *>::iterator p = std::find(files.begin(), files.end(), f);
	assert(p == files.end());
	files.push_back(f);

	for (std::vector<prayBlock *>::iterator i = f->blocks.begin(); i != f->blocks.end(); i++) {
		if (blocks.find((*i)->name) != blocks.end()) // garr, block conflict
			continue;
		//assert(blocks.find((*i)->name) == blocks.end());
		blocks[(*i)->name] = *i;
	}
}

void prayManager::removeFile(prayFile *f) {
	std::vector<prayFile *>::iterator p = std::find(files.begin(), files.end(), f);
	assert(p != files.end());
	files.erase(p);

	for (std::vector<prayBlock *>::iterator i = f->blocks.begin(); i != f->blocks.end(); i++) {
		if (blocks.find((*i)->name) == blocks.end()) // garr, block conflict
			continue;
		/*assert(blocks.find((*i)->name) != blocks.end());
		assert(blocks[(*i)->name] == *i); */
		blocks.erase(blocks.find((*i)->name));
	}
}

void prayManager::update() {
	if (files.size() != 0) return; // TODO: Handle actual update cases, rather than just the initial init.

	if (!world.catalogue.hasTag("Pray System File Extensions")) {
		std::cout << "Warning: Catalogue tag \"Pray System File Extensions\" wasn't found, so no PRAY files will be loaded." << std::endl;
		return;
	}

	const std::vector<std::string> &extensions = world.catalogue.getTag("Pray System File Extensions");

	for (std::vector<fs::path>::iterator i = world.data_directories.begin(); i != world.data_directories.end(); i++) {
		assert(fs::exists(*i));
		assert(fs::is_directory(*i));

		fs::path praydir(*i / fs::path("/My Agents/", fs::native));

		if (fs::exists(praydir) && fs::is_directory(praydir)) {
			fs::directory_iterator fsend;
			for (fs::directory_iterator d(praydir); d != fsend; ++d) {
				std::string x = fs::extension(*d);
				x.erase(x.begin());
				if (std::find(extensions.begin(), extensions.end(), x) != extensions.end()) {
					// TODO: language checking!
					//std::cout << "scanning PRAY file " << d->native_directory_string() << std::endl;
					try {
						prayFile *p = new prayFile(*d);
						addFile(p);
					} catch (creaturesException &e) {
						std::cerr << "PRAY file \"" << d->native_directory_string() << "\" failed to load: " << e.what() << std::endl;
					}
				}
			}
		}
	}
}

std::string prayManager::getResourceDir(unsigned int type) {
	switch (type) {
		case 0: return "/"; // main
		case 1: return "/Sounds/"; // sounds
		case 2: return "/Images/"; // images
		case 3: return "/Genetics/"; // genetics
		case 4: return "/Body Data/"; // body data
		case 5: return "/Overlay Data/"; // overlay data
		case 6: return "/Backgrounds/";// backgrounds
		case 7: return "/Catalogue/"; // catalogue
		//case 8: return "/Bootstrap/"; // bootstrap
		//case 9: return "/My Worlds/"; // my worlds
		case 10: return "/My Creatures/";// my creatures
		case 11: return "/My Agents/"; // my agents
	}

	return "";
}

/* vim: set noet: */
<commit_msg>don't assume files in PRAY directories have an extension<commit_after>/*
 *  prayManager.cpp
 *  openc2e
 *
 *  Created by Alyssa Milburn on Mon Jan 16 2006.
 *  Copyright (c) 2006 Alyssa Milburn. All rights reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library 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.
 *
 */

#include "prayManager.h"
#include "exceptions.h"
#include "World.h" // data_directories
#include <boost/filesystem/convenience.hpp>

prayManager::~prayManager() {
	while (files.size() != 0) {
		prayFile *f = files[0];
		removeFile(f);
		delete f;
	}

	assert(blocks.size() == 0);
}

void prayManager::addFile(prayFile *f) {
	std::vector<prayFile *>::iterator p = std::find(files.begin(), files.end(), f);
	assert(p == files.end());
	files.push_back(f);

	for (std::vector<prayBlock *>::iterator i = f->blocks.begin(); i != f->blocks.end(); i++) {
		if (blocks.find((*i)->name) != blocks.end()) // garr, block conflict
			continue;
		//assert(blocks.find((*i)->name) == blocks.end());
		blocks[(*i)->name] = *i;
	}
}

void prayManager::removeFile(prayFile *f) {
	std::vector<prayFile *>::iterator p = std::find(files.begin(), files.end(), f);
	assert(p != files.end());
	files.erase(p);

	for (std::vector<prayBlock *>::iterator i = f->blocks.begin(); i != f->blocks.end(); i++) {
		if (blocks.find((*i)->name) == blocks.end()) // garr, block conflict
			continue;
		/*assert(blocks.find((*i)->name) != blocks.end());
		assert(blocks[(*i)->name] == *i); */
		blocks.erase(blocks.find((*i)->name));
	}
}

void prayManager::update() {
	if (files.size() != 0) return; // TODO: Handle actual update cases, rather than just the initial init.

	if (!world.catalogue.hasTag("Pray System File Extensions")) {
		std::cout << "Warning: Catalogue tag \"Pray System File Extensions\" wasn't found, so no PRAY files will be loaded." << std::endl;
		return;
	}

	const std::vector<std::string> &extensions = world.catalogue.getTag("Pray System File Extensions");

	for (std::vector<fs::path>::iterator i = world.data_directories.begin(); i != world.data_directories.end(); i++) {
		assert(fs::exists(*i));
		assert(fs::is_directory(*i));

		fs::path praydir(*i / fs::path("/My Agents/", fs::native));

		if (fs::exists(praydir) && fs::is_directory(praydir)) {
			fs::directory_iterator fsend;
			for (fs::directory_iterator d(praydir); d != fsend; ++d) {
				std::string x = fs::extension(*d);
				if (!x.empty()) x.erase(x.begin());
				if (std::find(extensions.begin(), extensions.end(), x) != extensions.end()) {
					// TODO: language checking!
					//std::cout << "scanning PRAY file " << d->native_directory_string() << std::endl;
					try {
						prayFile *p = new prayFile(*d);
						addFile(p);
					} catch (creaturesException &e) {
						std::cerr << "PRAY file \"" << d->native_directory_string() << "\" failed to load: " << e.what() << std::endl;
					}
				}
			}
		}
	}
}

std::string prayManager::getResourceDir(unsigned int type) {
	switch (type) {
		case 0: return "/"; // main
		case 1: return "/Sounds/"; // sounds
		case 2: return "/Images/"; // images
		case 3: return "/Genetics/"; // genetics
		case 4: return "/Body Data/"; // body data
		case 5: return "/Overlay Data/"; // overlay data
		case 6: return "/Backgrounds/";// backgrounds
		case 7: return "/Catalogue/"; // catalogue
		//case 8: return "/Bootstrap/"; // bootstrap
		//case 9: return "/My Worlds/"; // my worlds
		case 10: return "/My Creatures/";// my creatures
		case 11: return "/My Agents/"; // my agents
	}

	return "";
}

/* vim: set noet: */
<|endoftext|>


<commit_before>/**
 * Copyright (c) Sjors Gielen, 2010-2012
 * See LICENSE for license.
 */

#include "network.h"
#include "server.h"
#include "utils.h"
#include <stdio.h>

std::string Network::toString(const Network *n)
{
	std::stringstream res;
	res << "Network[";
	if(n  == 0) {
		res << "0";
	} else {
		const NetworkConfig *nc = n->config();
		res << nc->displayName << ", " << Server::toString(n->activeServer());
	}
	res << "]";

	return res.str();
}

/**
 * @brief Constructor.
 */
Network::Network( const NetworkConfig *c )
: activeServer_(0)
, config_(c)
, undesirables_()
, deleteServer_(false)
, identifiedUsers_()
, knownUsers_()
, topics_()
, networkListeners_()
, nick_(c->nickName)
{}

/**
 * @brief Destructor.
 */
Network::~Network()
{
	disconnectFromNetwork();
}

/**
 * Returns the active server in this network, or 0 if there is none.
 */
Server *Network::activeServer() const
{
	return activeServer_;
}

/**
 * @brief Whether this network is marked for autoconnection.
 */
bool Network::autoConnectEnabled() const
{
	return config_->autoConnect;
}

void Network::action( std::string destination, std::string message )
{
	if( !activeServer_ )
		return;
	activeServer_->ctcpAction( destination, message );
}

void Network::names( std::string channel )
{
	if( !activeServer_ )
		return;
	activeServer_->names( channel );
}

std::vector<std::string> Network::joinedChannels() const
{
	std::vector<std::string> res;
	std::map<std::string,std::vector<std::string> >::const_iterator it;
	for(it = knownUsers_.begin(); it != knownUsers_.end(); ++it) {
		res.push_back(it->first);
	}
	return res;
}

std::map<std::string,std::string> Network::topics() const
{
	return topics_;
}

std::map<std::string,Network::ChannelMode> Network::usersInChannel(std::string channel) const
{
	std::map<std::string, ChannelMode> res;
	std::map<std::string, std::vector<std::string> >::const_iterator it;
	for(it = knownUsers_.begin(); it != knownUsers_.end(); ++it) {
		if(it->first != channel)
			continue;
		std::vector<std::string> users = it->second;
		std::vector<std::string>::const_iterator it2;
		for(it2 = users.begin(); it2 != users.end(); ++it2) {
			// TODO: remember the mode
			res[*it2] = Network::UserMode;
		}
	}
	return res;
}

/**
 * @brief Chooses one server or the other based on the priority and failure
 *        rate.
 * Failure rate is recorded in the Network object that the ServerConfig object
 * links to via the NetworkConfig, if there is such a NetworkConfig object.
 * You must give two servers of the same Network.
 */
struct ServerSorter {
	private:
	Network *n_;

	public:
	ServerSorter(Network *n) : n_(n) {}
	bool operator()(const ServerConfig *c1, const ServerConfig *c2) {
		assert(c1->network == c2->network);
		assert(c1->network == n_->config());

		int prio1 = c1->priority - n_->serverUndesirability(c1);
		int prio2 = c2->priority - n_->serverUndesirability(c2);

		if( prio1 == prio2 ) // choose randomly
			return (rand() % 2) == 0 ? -1 : 1;
		return prio1 < prio2;
	}
};

const NetworkConfig *Network::config() const
{
	return config_;
}

void Network::connectToNetwork( bool reconnect )
{
	if( !reconnect && activeServer_ )
		return;

	printf("Connecting to network: %s\n", Network::toString(this).c_str());

	// Check if there *is* a server to use
	if( servers().size() == 0 )
	{
		printf("Trying to connect to network '%s', but there are no servers to connect to!\n",
		config_->displayName.c_str());
		return;
	}

	// Find the best server to use

	// First, sort the list by priority and earlier failures
	std::vector<ServerConfig*> sortedServers = servers();
	std::sort( sortedServers.begin(), sortedServers.end(), ServerSorter(this) );

	// Then, take the first one and create a Server around it
	ServerConfig *best = sortedServers[0];

	// And set it as the active server, and connect.
	connectToServer( best, true );
}

void Network::connectToServer( ServerConfig *server, bool reconnect )
{
	if( !reconnect && activeServer_ )
		return;
	assert(knownUsers_.size() == 0);
	assert(identifiedUsers_.size() == 0);

	if( activeServer_ )
	{
		activeServer_->disconnectFromServer( SwitchingServersReason );
		// TODO: maybe deleteLater?
		delete(activeServer_);
	}

	activeServer_ = new Server( server, this );
	activeServer_->connectToServer();
}

void Network::joinedChannel(const std::string &user, const std::string &receiver)
{
	if(user == nick_ && !contains(knownUsers_, strToLower(receiver))) {
		knownUsers_[strToLower(receiver)] = std::vector<std::string>();
	}
	std::vector<std::string> users = knownUsers_[strToLower(receiver)];
	if(!contains(users, strToLower(user)))
		knownUsers_[strToLower(receiver)].push_back(strToLower(user));
}

void Network::partedChannel(const std::string &user, const std::string &, const std::string &receiver)
{
	if(user == nick_) {
		knownUsers_.erase(strToLower(receiver));
	} else {
		erase(knownUsers_[strToLower(receiver)], strToLower(user));
	}
	if(!isKnownUser(user)) {
		erase(identifiedUsers_, strToLower(user));
	}
}

void Network::slotQuit(const std::string &origin, const std::string&, const std::string &)
{
	std::map<std::string,std::vector<std::string> >::iterator it;
	for(it = knownUsers_.begin(); it != knownUsers_.end(); ++it) {
		erase(it->second, strToLower(origin));
	}
	if(!isKnownUser(origin)) {
		erase(identifiedUsers_, strToLower(origin));
	}
}

void Network::slotNickChanged( const std::string &origin, const std::string &nick, const std::string & )
{
	erase(identifiedUsers_, strToLower(origin));
	erase(identifiedUsers_, strToLower(nick));

	if(nick_ == origin)
		nick_ = nick;

	std::map<std::string,std::vector<std::string> >::iterator it;
	for(it = knownUsers_.begin(); it != knownUsers_.end(); ++it) {
		if(contains(it->second, strToLower(origin))) {
			erase(it->second, strToLower(origin));
			it->second.push_back(strToLower(nick));
		}
	}
}

void Network::kickedChannel(const std::string&, const std::string &user, const std::string&, const std::string &receiver)
{
	if(user == nick_) {
		knownUsers_.erase(strToLower(receiver));
	} else {
		erase(knownUsers_[strToLower(receiver)], strToLower(user));
	}
	if(!isKnownUser(user)) {
		erase(identifiedUsers_, strToLower(user));
	}
}

void Network::onFailedConnection()
{
	fprintf(stderr, "Connection failed on %s\n", Network::toString(this).c_str());

	identifiedUsers_.clear();
	knownUsers_.clear();

	slotIrcEvent("DISCONNECT", "", std::vector<std::string>());

	// Flag old server as undesirable
	// Don't destroy it here yet; it is still in the stack. It will be destroyed
	// in processDescriptors().
	flagUndesirableServer( activeServer_->config() );
	deleteServer_ = true;
}

void Network::ctcp( std::string destination, std::string message )
{
	if( !activeServer_ )
		return;
	activeServer_->ctcpAction( destination, message );
}

/**
 * @brief Disconnect from the network.
 */
void Network::disconnectFromNetwork( DisconnectReason reason )
{
	if( activeServer_ == 0 )
		return;

	identifiedUsers_.clear();
	knownUsers_.clear();

	activeServer_->disconnectFromServer( reason );
	// TODO: maybe deleteLater?
	delete activeServer_;
	activeServer_ = 0;
}

void Network::joinChannel( std::string channel )
{
	if( !activeServer_ )
		return;
	activeServer_->join( channel );
}

void Network::leaveChannel( std::string channel )
{
	if( !activeServer_ )
		return;
	activeServer_->part( channel );
}

void Network::say( std::string destination, std::string message )
{
	if( !activeServer_ )
		return;
	activeServer_->message( destination, message );
}

void Network::sendWhois( std::string destination )
{
	activeServer_->whois(destination);
}

const std::vector<ServerConfig*> &Network::servers() const
{
	return config_->servers;
}

std::string Network::nick() const
{
	return nick_;
}

std::string Network::networkName() const
{
	return config()->name;
}

int Network::serverUndesirability( const ServerConfig *sc ) const
{
	return contains(undesirables_, sc) ? undesirables_.find(sc)->second : 0;
}

void Network::flagUndesirableServer( const ServerConfig *sc )
{
	if(contains(undesirables_, sc))
		undesirables_[sc] = undesirables_[sc] + 1;
	undesirables_[sc] = 1;
}

void Network::serverIsActuallyOkay( const ServerConfig *sc )
{
	undesirables_.erase(sc);
}

bool Network::isIdentified(const std::string &user) const {
	return contains(identifiedUsers_, strToLower(user));
}

bool Network::isKnownUser(const std::string &user) const {
	std::map<std::string,std::vector<std::string> >::const_iterator it;
	for(it = knownUsers_.begin(); it != knownUsers_.end(); ++it) {
		if(contains(it->second, strToLower(user))) {
			return true;
		}
	}
	return false;
}

void Network::slotWhoisReceived(const std::string &, const std::string &nick, bool identified) {
	if(!identified) {
		erase(identifiedUsers_, strToLower(nick));
	} else if(identified && !contains(identifiedUsers_, strToLower(nick)) && isKnownUser(nick)) {
		identifiedUsers_.push_back(strToLower(nick));
	}
}

void Network::slotNamesReceived(const std::string&, const std::string &channel, const std::vector<std::string> &names, const std::string & ) {
	assert(contains(knownUsers_, strToLower(channel)));
	std::vector<std::string> &users = knownUsers_[strToLower(channel)];
	std::vector<std::string>::const_iterator it;
	for(it = names.begin(); it != names.end(); ++it) {
		std::string n = *it;
		unsigned int nickStart;
		for(nickStart = 0; nickStart < n.length(); ++nickStart) {
			if(n[nickStart] != '@' && n[nickStart] != '~' && n[nickStart] != '+'
			&& n[nickStart] != '%' && n[nickStart] != '!') {
				break;
			}
		}
		n = n.substr(nickStart);
		if(!contains(users, strToLower(n)))
			users.push_back(strToLower(n));
	}
}

void Network::slotTopicChanged(const std::string&, const std::string &channel, const std::string &topic) {
	topics_[channel] = topic;
}

void Network::slotIrcEvent(const std::string &event, const std::string &origin, const std::vector<std::string> &params) {
	std::string receiver;
	if(params.size() > 0)
		receiver = params[0];

#define MIN(a) if(params.size() < a) { fprintf(stderr, "Too few parameters for event %s\n", event.c_str()); return; }
	if(event == "CONNECT") {
		serverIsActuallyOkay(activeServer_->config());
	} else if(event == "JOIN") {
		MIN(1);
		joinedChannel(origin, receiver);
	} else if(event == "PART") {
		MIN(1);
		partedChannel(origin, std::string(), receiver);
	} else if(event == "KICK") {
		MIN(2);
		kickedChannel(origin, params[1], std::string(), receiver);
	} else if(event == "QUIT") {
		std::string message;
		if(params.size() > 0) {
			message = params[0];
		}
		slotQuit(origin, message, receiver);
	} else if(event == "NICK") {
		MIN(1);
		slotNickChanged(origin, params[0], receiver);
	} else if(event == "TOPIC") {
		MIN(2);
		slotTopicChanged(origin, params[0], params[1]);
	}
#undef MIN
	std::vector<NetworkListener*>::iterator nlit;
	for(nlit = networkListeners_.begin(); nlit != networkListeners_.end();
	    nlit++) {
		(*nlit)->ircEvent(event, origin, params, this);
	}
}

void Network::addDescriptors(fd_set *in_set, fd_set *out_set, int *maxfd) {
	activeServer_->addDescriptors(in_set, out_set, maxfd);
}

void Network::processDescriptors(fd_set *in_set, fd_set *out_set) {
	deleteServer_ = false;
	activeServer_->processDescriptors(in_set, out_set);
	if(deleteServer_) {
		delete activeServer_;
		activeServer_ = 0;
		connectToNetwork();
	}
}
<commit_msg>Fix ServerSorter behavior on failed server connections and connections which are equally likely to be chosen.<commit_after>/**
 * Copyright (c) Sjors Gielen, 2010-2012
 * See LICENSE for license.
 */

#include "network.h"
#include "server.h"
#include "utils.h"
#include <stdio.h>

std::string Network::toString(const Network *n)
{
	std::stringstream res;
	res << "Network[";
	if(n  == 0) {
		res << "0";
	} else {
		const NetworkConfig *nc = n->config();
		res << nc->displayName << ", " << Server::toString(n->activeServer());
	}
	res << "]";

	return res.str();
}

/**
 * @brief Constructor.
 */
Network::Network( const NetworkConfig *c )
: activeServer_(0)
, config_(c)
, undesirables_()
, deleteServer_(false)
, identifiedUsers_()
, knownUsers_()
, topics_()
, networkListeners_()
, nick_(c->nickName)
{}

/**
 * @brief Destructor.
 */
Network::~Network()
{
	disconnectFromNetwork();
}

/**
 * Returns the active server in this network, or 0 if there is none.
 */
Server *Network::activeServer() const
{
	return activeServer_;
}

/**
 * @brief Whether this network is marked for autoconnection.
 */
bool Network::autoConnectEnabled() const
{
	return config_->autoConnect;
}

void Network::action( std::string destination, std::string message )
{
	if( !activeServer_ )
		return;
	activeServer_->ctcpAction( destination, message );
}

void Network::names( std::string channel )
{
	if( !activeServer_ )
		return;
	activeServer_->names( channel );
}

std::vector<std::string> Network::joinedChannels() const
{
	std::vector<std::string> res;
	std::map<std::string,std::vector<std::string> >::const_iterator it;
	for(it = knownUsers_.begin(); it != knownUsers_.end(); ++it) {
		res.push_back(it->first);
	}
	return res;
}

std::map<std::string,std::string> Network::topics() const
{
	return topics_;
}

std::map<std::string,Network::ChannelMode> Network::usersInChannel(std::string channel) const
{
	std::map<std::string, ChannelMode> res;
	std::map<std::string, std::vector<std::string> >::const_iterator it;
	for(it = knownUsers_.begin(); it != knownUsers_.end(); ++it) {
		if(it->first != channel)
			continue;
		std::vector<std::string> users = it->second;
		std::vector<std::string>::const_iterator it2;
		for(it2 = users.begin(); it2 != users.end(); ++it2) {
			// TODO: remember the mode
			res[*it2] = Network::UserMode;
		}
	}
	return res;
}

/**
 * @brief Chooses one server or the other based on the priority and failure
 *        rate.
 * Failure rate is recorded in the Network object that the ServerConfig object
 * links to via the NetworkConfig, if there is such a NetworkConfig object.
 * You must give two servers of the same Network.
 */
struct ServerSorter {
	private:
	Network *n_;
	std::map<const ServerConfig*, int> randomValues;

	public:
	ServerSorter(Network *n) : n_(n) {}
	bool operator()(const ServerConfig *c1, const ServerConfig *c2) {
		assert(c1->network == c2->network);
		assert(c1->network == n_->config());

		int prio1 = c1->priority + n_->serverUndesirability(c1);
		int prio2 = c2->priority + n_->serverUndesirability(c2);

		if(prio1 != prio2) {
			return prio1 < prio2;
		}

		// Assign random values to ServerConfigs having the same
		// corrected prio's. These need to be stored because a
		// comparator must be consistent and transitive, so
		// subsequent calls to the operator()() function must agree
		// with the return value of this call.
		if(!contains(randomValues, c1)) {
			randomValues[c1] = rand();
		}
		if(!contains(randomValues, c2)) {
			randomValues[c2] = rand();
		}
		return randomValues[c1] < randomValues[c2];
	}
};

const NetworkConfig *Network::config() const
{
	return config_;
}

void Network::connectToNetwork( bool reconnect )
{
	if( !reconnect && activeServer_ )
		return;

	printf("Connecting to network: %s\n", Network::toString(this).c_str());

	// Check if there *is* a server to use
	if( servers().size() == 0 )
	{
		printf("Trying to connect to network '%s', but there are no servers to connect to!\n",
		config_->displayName.c_str());
		return;
	}

	// Find the best server to use

	// First, sort the list by priority and earlier failures
	std::vector<ServerConfig*> sortedServers = servers();
	std::sort( sortedServers.begin(), sortedServers.end(), ServerSorter(this) );

	// Then, take the first one and create a Server around it
	ServerConfig *best = sortedServers[0];

	// And set it as the active server, and connect.
	connectToServer( best, true );
}

void Network::connectToServer( ServerConfig *server, bool reconnect )
{
	if( !reconnect && activeServer_ )
		return;
	assert(knownUsers_.size() == 0);
	assert(identifiedUsers_.size() == 0);

	if( activeServer_ )
	{
		activeServer_->disconnectFromServer( SwitchingServersReason );
		// TODO: maybe deleteLater?
		delete(activeServer_);
	}

	activeServer_ = new Server( server, this );
	activeServer_->connectToServer();
}

void Network::joinedChannel(const std::string &user, const std::string &receiver)
{
	if(user == nick_ && !contains(knownUsers_, strToLower(receiver))) {
		knownUsers_[strToLower(receiver)] = std::vector<std::string>();
	}
	std::vector<std::string> users = knownUsers_[strToLower(receiver)];
	if(!contains(users, strToLower(user)))
		knownUsers_[strToLower(receiver)].push_back(strToLower(user));
}

void Network::partedChannel(const std::string &user, const std::string &, const std::string &receiver)
{
	if(user == nick_) {
		knownUsers_.erase(strToLower(receiver));
	} else {
		erase(knownUsers_[strToLower(receiver)], strToLower(user));
	}
	if(!isKnownUser(user)) {
		erase(identifiedUsers_, strToLower(user));
	}
}

void Network::slotQuit(const std::string &origin, const std::string&, const std::string &)
{
	std::map<std::string,std::vector<std::string> >::iterator it;
	for(it = knownUsers_.begin(); it != knownUsers_.end(); ++it) {
		erase(it->second, strToLower(origin));
	}
	if(!isKnownUser(origin)) {
		erase(identifiedUsers_, strToLower(origin));
	}
}

void Network::slotNickChanged( const std::string &origin, const std::string &nick, const std::string & )
{
	erase(identifiedUsers_, strToLower(origin));
	erase(identifiedUsers_, strToLower(nick));

	if(nick_ == origin)
		nick_ = nick;

	std::map<std::string,std::vector<std::string> >::iterator it;
	for(it = knownUsers_.begin(); it != knownUsers_.end(); ++it) {
		if(contains(it->second, strToLower(origin))) {
			erase(it->second, strToLower(origin));
			it->second.push_back(strToLower(nick));
		}
	}
}

void Network::kickedChannel(const std::string&, const std::string &user, const std::string&, const std::string &receiver)
{
	if(user == nick_) {
		knownUsers_.erase(strToLower(receiver));
	} else {
		erase(knownUsers_[strToLower(receiver)], strToLower(user));
	}
	if(!isKnownUser(user)) {
		erase(identifiedUsers_, strToLower(user));
	}
}

void Network::onFailedConnection()
{
	fprintf(stderr, "Connection failed on %s\n", Network::toString(this).c_str());

	identifiedUsers_.clear();
	knownUsers_.clear();

	slotIrcEvent("DISCONNECT", "", std::vector<std::string>());

	// Flag old server as undesirable
	// Don't destroy it here yet; it is still in the stack. It will be destroyed
	// in processDescriptors().
	flagUndesirableServer( activeServer_->config() );
	deleteServer_ = true;
}

void Network::ctcp( std::string destination, std::string message )
{
	if( !activeServer_ )
		return;
	activeServer_->ctcpAction( destination, message );
}

/**
 * @brief Disconnect from the network.
 */
void Network::disconnectFromNetwork( DisconnectReason reason )
{
	if( activeServer_ == 0 )
		return;

	identifiedUsers_.clear();
	knownUsers_.clear();

	activeServer_->disconnectFromServer( reason );
	// TODO: maybe deleteLater?
	delete activeServer_;
	activeServer_ = 0;
}

void Network::joinChannel( std::string channel )
{
	if( !activeServer_ )
		return;
	activeServer_->join( channel );
}

void Network::leaveChannel( std::string channel )
{
	if( !activeServer_ )
		return;
	activeServer_->part( channel );
}

void Network::say( std::string destination, std::string message )
{
	if( !activeServer_ )
		return;
	activeServer_->message( destination, message );
}

void Network::sendWhois( std::string destination )
{
	activeServer_->whois(destination);
}

const std::vector<ServerConfig*> &Network::servers() const
{
	return config_->servers;
}

std::string Network::nick() const
{
	return nick_;
}

std::string Network::networkName() const
{
	return config()->name;
}

int Network::serverUndesirability( const ServerConfig *sc ) const
{
	return contains(undesirables_, sc) ? undesirables_.find(sc)->second : 0;
}

void Network::flagUndesirableServer( const ServerConfig *sc )
{
	if(contains(undesirables_, sc))
		undesirables_[sc] = undesirables_[sc] + 1;
	undesirables_[sc] = 1;
}

void Network::serverIsActuallyOkay( const ServerConfig *sc )
{
	undesirables_.erase(sc);
}

bool Network::isIdentified(const std::string &user) const {
	return contains(identifiedUsers_, strToLower(user));
}

bool Network::isKnownUser(const std::string &user) const {
	std::map<std::string,std::vector<std::string> >::const_iterator it;
	for(it = knownUsers_.begin(); it != knownUsers_.end(); ++it) {
		if(contains(it->second, strToLower(user))) {
			return true;
		}
	}
	return false;
}

void Network::slotWhoisReceived(const std::string &, const std::string &nick, bool identified) {
	if(!identified) {
		erase(identifiedUsers_, strToLower(nick));
	} else if(identified && !contains(identifiedUsers_, strToLower(nick)) && isKnownUser(nick)) {
		identifiedUsers_.push_back(strToLower(nick));
	}
}

void Network::slotNamesReceived(const std::string&, const std::string &channel, const std::vector<std::string> &names, const std::string & ) {
	assert(contains(knownUsers_, strToLower(channel)));
	std::vector<std::string> &users = knownUsers_[strToLower(channel)];
	std::vector<std::string>::const_iterator it;
	for(it = names.begin(); it != names.end(); ++it) {
		std::string n = *it;
		unsigned int nickStart;
		for(nickStart = 0; nickStart < n.length(); ++nickStart) {
			if(n[nickStart] != '@' && n[nickStart] != '~' && n[nickStart] != '+'
			&& n[nickStart] != '%' && n[nickStart] != '!') {
				break;
			}
		}
		n = n.substr(nickStart);
		if(!contains(users, strToLower(n)))
			users.push_back(strToLower(n));
	}
}

void Network::slotTopicChanged(const std::string&, const std::string &channel, const std::string &topic) {
	topics_[channel] = topic;
}

void Network::slotIrcEvent(const std::string &event, const std::string &origin, const std::vector<std::string> &params) {
	std::string receiver;
	if(params.size() > 0)
		receiver = params[0];

#define MIN(a) if(params.size() < a) { fprintf(stderr, "Too few parameters for event %s\n", event.c_str()); return; }
	if(event == "CONNECT") {
		serverIsActuallyOkay(activeServer_->config());
	} else if(event == "JOIN") {
		MIN(1);
		joinedChannel(origin, receiver);
	} else if(event == "PART") {
		MIN(1);
		partedChannel(origin, std::string(), receiver);
	} else if(event == "KICK") {
		MIN(2);
		kickedChannel(origin, params[1], std::string(), receiver);
	} else if(event == "QUIT") {
		std::string message;
		if(params.size() > 0) {
			message = params[0];
		}
		slotQuit(origin, message, receiver);
	} else if(event == "NICK") {
		MIN(1);
		slotNickChanged(origin, params[0], receiver);
	} else if(event == "TOPIC") {
		MIN(2);
		slotTopicChanged(origin, params[0], params[1]);
	}
#undef MIN
	std::vector<NetworkListener*>::iterator nlit;
	for(nlit = networkListeners_.begin(); nlit != networkListeners_.end();
	    nlit++) {
		(*nlit)->ircEvent(event, origin, params, this);
	}
}

void Network::addDescriptors(fd_set *in_set, fd_set *out_set, int *maxfd) {
	activeServer_->addDescriptors(in_set, out_set, maxfd);
}

void Network::processDescriptors(fd_set *in_set, fd_set *out_set) {
	deleteServer_ = false;
	activeServer_->processDescriptors(in_set, out_set);
	if(deleteServer_) {
		delete activeServer_;
		activeServer_ = 0;
		connectToNetwork();
	}
}
<|endoftext|>


<commit_before>#include "command.hh"
#include "shared.hh"
#include "store-api.hh"
#include "sync.hh"
#include "thread-pool.hh"

#include <atomic>

using namespace nix;

struct CmdCopy : StorePathsCommand
{
    std::string srcUri, dstUri;

    CheckSigsFlag checkSigs = CheckSigs;

    SubstituteFlag substitute = NoSubstitute;

    CmdCopy()
        : StorePathsCommand(true)
    {
        mkFlag()
            .longName("from")
            .labels({"store-uri"})
            .description("URI of the source Nix store")
            .dest(&srcUri);
        mkFlag()
            .longName("to")
            .labels({"store-uri"})
            .description("URI of the destination Nix store")
            .dest(&dstUri);

        mkFlag()
            .longName("no-check-sigs")
            .description("do not require that paths are signed by trusted keys")
            .set(&checkSigs, NoCheckSigs);

        mkFlag()
            .longName("substitute")
            .shortName('s')
            .description("whether to try substitutes on the destination store (only supported by SSH)")
            .set(&substitute, Substitute);
    }

    std::string name() override
    {
        return "copy";
    }

    std::string description() override
    {
        return "copy paths between Nix stores";
    }

    Examples examples() override
    {
        return {
            Example{
                "To copy Firefox from the local store to a binary cache in file:///tmp/cache:",
                "nix copy --to file:///tmp/cache $(type -p firefox)"
            },
            Example{
                "To copy the entire current NixOS system closure to another machine via SSH:",
                "nix copy --to ssh://server /run/current-system"
            },
            Example{
                "To copy a closure from another machine via SSH:",
                "nix copy --from ssh://server /nix/store/a6cnl93nk1wxnq84brbbwr6hxw9gp2w9-blender-2.79-rc2"
            },
        };
    }

    ref<Store> createStore() override
    {
        return srcUri.empty() ? StoreCommand::createStore() : openStore(srcUri);
    }

    void run(ref<Store> srcStore, Paths storePaths) override
    {
        if (srcUri.empty() && dstUri.empty())
            throw UsageError("you must pass '--from' and/or '--to'");

        ref<Store> dstStore = dstUri.empty() ? openStore() : openStore(dstUri);

        copyPaths(srcStore, dstStore, PathSet(storePaths.begin(), storePaths.end()),
            NoRepair, checkSigs, substitute);
    }
};

static RegisterCommand r1(make_ref<CmdCopy>());
<commit_msg>nix copy: add an example with S3<commit_after>#include "command.hh"
#include "shared.hh"
#include "store-api.hh"
#include "sync.hh"
#include "thread-pool.hh"

#include <atomic>

using namespace nix;

struct CmdCopy : StorePathsCommand
{
    std::string srcUri, dstUri;

    CheckSigsFlag checkSigs = CheckSigs;

    SubstituteFlag substitute = NoSubstitute;

    CmdCopy()
        : StorePathsCommand(true)
    {
        mkFlag()
            .longName("from")
            .labels({"store-uri"})
            .description("URI of the source Nix store")
            .dest(&srcUri);
        mkFlag()
            .longName("to")
            .labels({"store-uri"})
            .description("URI of the destination Nix store")
            .dest(&dstUri);

        mkFlag()
            .longName("no-check-sigs")
            .description("do not require that paths are signed by trusted keys")
            .set(&checkSigs, NoCheckSigs);

        mkFlag()
            .longName("substitute")
            .shortName('s')
            .description("whether to try substitutes on the destination store (only supported by SSH)")
            .set(&substitute, Substitute);
    }

    std::string name() override
    {
        return "copy";
    }

    std::string description() override
    {
        return "copy paths between Nix stores";
    }

    Examples examples() override
    {
        return {
            Example{
                "To copy Firefox from the local store to a binary cache in file:///tmp/cache:",
                "nix copy --to file:///tmp/cache $(type -p firefox)"
            },
            Example{
                "To copy the entire current NixOS system closure to another machine via SSH:",
                "nix copy --to ssh://server /run/current-system"
            },
            Example{
                "To copy a closure from another machine via SSH:",
                "nix copy --from ssh://server /nix/store/a6cnl93nk1wxnq84brbbwr6hxw9gp2w9-blender-2.79-rc2"
            },
#ifdef ENABLE_S3
            Example{
                "To populate the current folder build output to a S3 binary cache:",
                "nix copy --to s3://my-bucket?region=eu-west-1"
            },
#endif
        };
    }

    ref<Store> createStore() override
    {
        return srcUri.empty() ? StoreCommand::createStore() : openStore(srcUri);
    }

    void run(ref<Store> srcStore, Paths storePaths) override
    {
        if (srcUri.empty() && dstUri.empty())
            throw UsageError("you must pass '--from' and/or '--to'");

        ref<Store> dstStore = dstUri.empty() ? openStore() : openStore(dstUri);

        copyPaths(srcStore, dstStore, PathSet(storePaths.begin(), storePaths.end()),
            NoRepair, checkSigs, substitute);
    }
};

static RegisterCommand r1(make_ref<CmdCopy>());
<|endoftext|>