<commit_before>/**
 ** \file scheduler/job.hh
 ** \brief Definition of Job.
 */

#ifndef SCHEDULER_JOB_HH
# define SCHEDULER_JOB_HH

# include <iosfwd>
# include <list>

# include <boost/any.hpp>

# include <libport/finally.hh>
# include <libport/symbol.hh>
# include <libport/utime.hh>

# include <scheduler/coroutine.hh>
# include <scheduler/fwd.hh>
# include <scheduler/tag.hh>

namespace scheduler
{

  enum job_state
  {
    to_start,            ///< Job needs to be started
    running,             ///< Job is waiting for the CPU
    sleeping,            ///< Job is sleeping until a specified deadline
    waiting,             ///< Job is waiting for changes to happen
    joining,             ///< Job is waiting for another job to terminate
    zombie               ///< Job wants to be dead but isn't really yet
  };

  /// A Job represents a thread of control, implemented using coroutines.
  /// The scheduler decides which job to launch and resumes its execution.
  /// The lifetime of a job is the following
  ///
  /// \li   Job()                  : job creation
  ///
  /// \li   start_job()            : add job to the scheduler; the scheduler
  ///                                will call run(), and the job will
  ///                                yield() itself into the scheduler run
  ///                                queue during the course of work()
  ///
  /// \li   work()                 : this method, which must be overridden,
  ///                                does the real work
  ///
  /// Due to the way coroutines work, a job may not delete itself
  /// as once the coroutine structure has been freed, it is illegal to
  /// continue its execution, even only to switch to another coroutine.
  /// And if a job switches back to the scheduler before terminating its
  /// destructor, the coroutine structure will not be freed. Because of
  /// that, it is necessary for a job to be deleted by another one or
  /// by the scheduler.
  ///
  /// An additional constraint comes from the fact that even when a job
  /// has terminated its work, its structure may not always be deleted.
  /// For example, other jobs may have kept a reference to this job and
  /// expect to retrieve some information (such as a result) after the
  /// job termination.
  ///
  /// We have several ways of dealing with those constraints and not leaking
  /// memory upon a job termination:
  ///
  /// \li 1. Make sure the job spits out all the useful information concerning
  ///        its state by the way of inter-job communication before asking to
  ///        be deleted.
  ///
  /// \li  2. Make sure the job is kept alive as long as there is at least one
  ///         reference onto it, and that it gets deleted from another
  ///         coroutine, or from the main one.

  class Job: public libport::RefCounted
  {
  public:
    /// Create a job from another one.
    ///
    /// \param model The parent job. The scheduler and tags will be inherited
    //         from it.
    ///
    /// \param name The name of the new job, or a name derived from \a model
    ///        if none is provided.
    Job(const Job& model, const libport::Symbol& name);

    /// Create a new job.
    ///
    /// \param scheduler The scheduler to which this job will be attached.
    ///
    /// \param name The name of the new job, or an automatically created
    ///        one if none is provided.
    explicit Job(Scheduler& scheduler, const libport::Symbol& name);

    /// Job destructor.
    ///
    /// The destructor is in charge of unscheduling the job in the
    /// scheduler.
    virtual ~Job();

    /// Get this job scheduler.
    ///
    /// \return A reference on the job scheduler.
    Scheduler& scheduler_get() const;

    /// Get the underlying coroutine corresponding to this job.
    ///
    /// \return The coroutine structure.
    Coro* coro_get() const;

    /// Has this job terminated?
    ///
    /// \return True if this job is in the \c zombie state.
    bool terminated() const;

    /// Start job by adding it to the scheduler.
    void start_job();

    /// Run the job. This function is called from the scheduler.
    void run();

    /// Terminate the job. The job will execute its cleanup method
    /// and inform the scheduler that it is ready to be destroyed.
    void terminate_now();

    /// Register this Job on its Scheduler so that it is rescheduled
    /// during the next cycle. This should be called from the
    /// currently scheduled job only but must be kept visible to be
    /// callable from the primitives.
    /// \sa yield_until(), yield_until_terminated(),
    /// yield_until_things_changed()
    void yield();

    /// As yield(), but ask not to be woken up before the deadline.
    /// \sa yield(), yield_until_terminated(), yield_until_things_changed()
    void yield_until(libport::utime_t deadline);

    /// Wait for another job to terminate before resuming execution of
    /// the current one. If the other job has already terminated, the
    /// caller will continue its execution.
    ///
    /// \param other The job to wait for. It is allowed to specify the
    ///        same Job as the target and the waiting job, in which case
    ///        the Job will only be woken up by an asynchronous exception.
    ///
    /// \sa yield(), yield_until(), yield_until_things_changed()
    void yield_until_terminated(Job& other);

    /// Same as \p yield_until_terminated above(), but wait for every
    /// job in the collection.
    void yield_until_terminated(const jobs_type& jobs);

    /// Wait for any other task to be scheduled.
    /// \sa yield(), yield_until_terminated(), yield_until()
    void yield_until_things_changed();

    /// Mark the current job as side-effect free.
    ///
    /// \param s True if the job is now side-effect free.
    ///
    /// Indicate whether the current state of a job may influence other
    /// parts of the system. This is used by the scheduler to choose
    /// whether other jobs need scheduling or not. The default value
    /// for \c side_effect_free is false.
    void side_effect_free_set(bool s);

    /// Is the current job side-effect free?
    ///
    /// \return True if the job is currently side-effect free.
    bool side_effect_free_get() const;

    /// Raise an exception next time this job will be resumed.
    ///
    /// \param e The exception to throw when the job will be scheduled
    ///        again.
    void async_throw(const exception& e);

    /// Maybe raise a deferred exception. Must be called from the scheduler
    /// while resuming the job execution. For example, StopException
    /// may be raised from here if the job has been blocked by a tag.
    void check_for_pending_exception();

    /// Get the job name
    ///
    /// \return The job name as set from the constructor.
    const libport::Symbol& name_get() const;

    /// Throw an exception if the stack space for this job is near
    /// exhaustion.
    void check_stack_space();

    /// Is the job frozen?
    ///
    /// \return This depends from the job tags state.
    virtual bool frozen() const;

    /// Apply a tag to the current job tag stack.
    ///
    /// \param tag The tag to apply.
    ///
    /// \param finally The action executed when the
    ///                tag is removed. No action is
    ///                inserted if 0 is given.
    void apply_tag(const rTag& tag, libport::Finally* finally);

    /// Check if the job holds a tag.
    bool has_tag(const Tag&) const;

    /// Get the current tags.
    ///
    /// \return The tags attached to the current job.
    const tags_type& tags_get() const;

    /// Set the current tags.
    ///
    /// \param tags Set the tags attached to the current job.
    void tags_set(const tags_type& tags);

    /// Clear the current tags.
    void tags_clear();

    /// Get the current job state.
    ///
    /// \return The current job state.
    job_state state_get() const;

    /// Set the current job state
    ///
    /// \param state The new job state.
    void state_set(job_state state);

    /// Get this job deadline if it is sleeping.
    ///
    /// \return The date on which this job needs to be awoken.
    ///
    /// This function must not be called unless the job is in the
    /// \c sleeping state.
    libport::utime_t deadline_get() const;

    /// Check if the job can be interrupted.
    ///
    /// \return True if the job cannot be interrupted right now because
    ///         it is executing an atomic operation.
    bool non_interruptible_get() const;

    /// Indicate if the job can be interrupted or not
    ///
    /// \param ni True if the job must not be interrupted until further
    ///        notice.
    void non_interruptible_set(bool ni);

    /// Remember the time we have been frozen since if not remembered
    /// yet.
    ///
    /// \param current_time The current time.
    void notice_frozen(libport::utime_t current_time);

    /// Note that we are not frozen anymore.
    ///
    /// \param current_time The current time.
    void notice_not_frozen(libport::utime_t current_time);

    /// Return the origin since we have been frozen.
    ///
    /// \return 0 if we have not been frozen, the date since we have
    ///         been frozen otherwise.
    libport::utime_t frozen_since_get() const;

    /// Return the current time shift.
    ///
    /// \return The value to subtract from the system time to get the
    ///         unfrozen time of this runner.
    libport::utime_t time_shift_get() const;

    /// Set the current time shift.
    ///
    /// \param ts The new time shift. This should probably only be used
    ///           at runner creation time.
    void time_shift_set(libport::utime_t ts);

    /// Check and maybe register the fact that a tag has been stopped.
    ///
    /// \param tag The tag that has been stopped.
    ///
    /// \param payload The data to embed in the StopException.
    virtual void register_stopped_tag
      (const Tag& tag, const boost::any& payload);

    /// Check whether the job has a pending exception.
    ///
    /// \return true if the job has a pending exception.
    bool has_pending_exception() const;

    /// Get the current job priority.
    ///
    /// \return The job priority, as previously computed.
    prio_type prio_get() const;

    /// Ensure proper cleanup.
    virtual void terminate_cleanup();

    /// Number of jobs created and not yet destroyed.
    ///
    /// \return The number of jobs.
    static unsigned int alive_jobs();

    /// Register a child.
    ///
    /// \param child The child job.
    ///
    /// \param at_end Children will be automatically terminated
    ///               when this object gets no longer referenced.
    void register_child(const rJob& child, libport::Finally& at_end);

    /// Do we have a parent?
    ///
    /// \return True if we have a parent.
    bool child_job() const;

  protected:

    /// Must be implemented to do something useful. If an exception is
    /// raised, it will be propagated to our parent if we have one or
    /// lost otherwise.
    virtual void work() = 0;

    /// May be overriden. Called if a scheduling error is detected
    /// during the execution course of this job. The default
    /// implementation calls abort().
    ///
    /// \param msg The explanation of the scheduling error.
    virtual void scheduling_error(std::string msg = "");

  private:
    /// Current job state, to be manipulated only from the job and the
    /// scheduler.
    job_state state_;

    /// Current job deadline. The deadline is only meaningful when the
    /// job state is \c sleeping.
    libport::utime_t deadline_;

    /// The last time we have been frozen (in system time), or 0 if we
    /// are not currently frozen.
    libport::utime_t frozen_since_;

    /// The value we have to deduce from system time because we have
    /// been frozen.
    libport::utime_t time_shift_;

    /// Recompute the current priority after a tag operation.
    void recompute_prio();

    /// Recompute the current priority if a particular tag could have
    /// affected it (addition or removal).
    void recompute_prio(const Tag&);

    /// Scheduler in charge of this job. Do not delete.
    Scheduler& scheduler_;

    /// This job name.
    libport::Symbol name_;

    /// Other jobs to wake up when we terminate.
    jobs_type to_wake_up_;

    /// Coro structure corresponding to this job.
    Coro* coro_;

  protected:

    /// Is the current job non-interruptible? If yes, yielding will
    /// do nothing and blocking operations may raise an exception.
    bool non_interruptible_;

  private:

    /// Helper functions for constructors.
    void init_common(const libport::Symbol& name);

    /// Tags this job depends on.
    tags_type tags_;

    /// Current priority.
    prio_type prio_;

    /// Is the current job side-effect free?
    bool side_effect_free_;

    /// The next exception to be propagated if any.
    exception_ptr pending_exception_;

    /// Our parent if any.
    rJob parent_;

    /// Our children.
    jobs_type children_;

    /// Terminate child and remove it from our children list.
    void terminate_child(const rJob& child);

    /// Check stack space from time to time.
    bool check_stack_space_;

    /// Number of jobs created and not yet destroyed.
    static unsigned int alive_jobs_;

  protected:
    /// Exception used to terminate a job.
    struct TerminateException : public SchedulerException
    {
      COMPLETE_EXCEPTION(TerminateException)
    };

  };

  std::ostream& operator<< (std::ostream&, const Job&);

  /// This exception will be raised to tell the job that it is currently
  /// stopped and must try to unwind tags from its tag stack until it
  /// is either dead or not stopped anymore.
  struct StopException : public SchedulerException
  {
    StopException(unsigned int, boost::any);
    ADD_FIELD(unsigned int, depth)
    ADD_FIELD(boost::any, payload)
    COMPLETE_EXCEPTION(StopException)
  };

  /// This exception encapsulates another one, sent by a child job.
  struct ChildException : public SchedulerException
  {
    ChildException(exception_ptr);
    ChildException(const ChildException&);
    virtual exception_ptr clone() const;
    ATTRIBUTE_NORETURN void rethrow_child_exception() const;
  protected:
    ATTRIBUTE_NORETURN virtual void rethrow_() const;
  private:
    mutable exception_ptr child_exception_;
  };

  /// State names to string, for debugging purpose.
  const char* state_name(job_state);

  /// Terminate all jobs present in container and empty it.
  void terminate_jobs(jobs_type& jobs);

} // namespace scheduler

# include <scheduler/job.hxx>

#endif // !SCHEDULER_JOB_HH
<commit_msg>Make most tag operations protected.<commit_after>/**
 ** \file scheduler/job.hh
 ** \brief Definition of Job.
 */

#ifndef SCHEDULER_JOB_HH
# define SCHEDULER_JOB_HH

# include <iosfwd>
# include <list>

# include <boost/any.hpp>

# include <libport/finally.hh>
# include <libport/symbol.hh>
# include <libport/utime.hh>

# include <scheduler/coroutine.hh>
# include <scheduler/fwd.hh>
# include <scheduler/tag.hh>

namespace scheduler
{

  enum job_state
  {
    to_start,            ///< Job needs to be started
    running,             ///< Job is waiting for the CPU
    sleeping,            ///< Job is sleeping until a specified deadline
    waiting,             ///< Job is waiting for changes to happen
    joining,             ///< Job is waiting for another job to terminate
    zombie               ///< Job wants to be dead but isn't really yet
  };

  /// A Job represents a thread of control, implemented using coroutines.
  /// The scheduler decides which job to launch and resumes its execution.
  /// The lifetime of a job is the following
  ///
  /// \li   Job()                  : job creation
  ///
  /// \li   start_job()            : add job to the scheduler; the scheduler
  ///                                will call run(), and the job will
  ///                                yield() itself into the scheduler run
  ///                                queue during the course of work()
  ///
  /// \li   work()                 : this method, which must be overridden,
  ///                                does the real work
  ///
  /// Due to the way coroutines work, a job may not delete itself
  /// as once the coroutine structure has been freed, it is illegal to
  /// continue its execution, even only to switch to another coroutine.
  /// And if a job switches back to the scheduler before terminating its
  /// destructor, the coroutine structure will not be freed. Because of
  /// that, it is necessary for a job to be deleted by another one or
  /// by the scheduler.
  ///
  /// An additional constraint comes from the fact that even when a job
  /// has terminated its work, its structure may not always be deleted.
  /// For example, other jobs may have kept a reference to this job and
  /// expect to retrieve some information (such as a result) after the
  /// job termination.
  ///
  /// We have several ways of dealing with those constraints and not leaking
  /// memory upon a job termination:
  ///
  /// \li 1. Make sure the job spits out all the useful information concerning
  ///        its state by the way of inter-job communication before asking to
  ///        be deleted.
  ///
  /// \li  2. Make sure the job is kept alive as long as there is at least one
  ///         reference onto it, and that it gets deleted from another
  ///         coroutine, or from the main one.

  class Job: public libport::RefCounted
  {
  public:
    /// Create a job from another one.
    ///
    /// \param model The parent job. The scheduler and tags will be inherited
    //         from it.
    ///
    /// \param name The name of the new job, or a name derived from \a model
    ///        if none is provided.
    Job(const Job& model, const libport::Symbol& name);

    /// Create a new job.
    ///
    /// \param scheduler The scheduler to which this job will be attached.
    ///
    /// \param name The name of the new job, or an automatically created
    ///        one if none is provided.
    explicit Job(Scheduler& scheduler, const libport::Symbol& name);

    /// Job destructor.
    ///
    /// The destructor is in charge of unscheduling the job in the
    /// scheduler.
    virtual ~Job();

    /// Get this job scheduler.
    ///
    /// \return A reference on the job scheduler.
    Scheduler& scheduler_get() const;

    /// Get the underlying coroutine corresponding to this job.
    ///
    /// \return The coroutine structure.
    Coro* coro_get() const;

    /// Has this job terminated?
    ///
    /// \return True if this job is in the \c zombie state.
    bool terminated() const;

    /// Start job by adding it to the scheduler.
    void start_job();

    /// Run the job. This function is called from the scheduler.
    void run();

    /// Terminate the job. The job will execute its cleanup method
    /// and inform the scheduler that it is ready to be destroyed.
    void terminate_now();

    /// Register this Job on its Scheduler so that it is rescheduled
    /// during the next cycle. This should be called from the
    /// currently scheduled job only but must be kept visible to be
    /// callable from the primitives.
    /// \sa yield_until(), yield_until_terminated(),
    /// yield_until_things_changed()
    void yield();

    /// As yield(), but ask not to be woken up before the deadline.
    /// \sa yield(), yield_until_terminated(), yield_until_things_changed()
    void yield_until(libport::utime_t deadline);

    /// Wait for another job to terminate before resuming execution of
    /// the current one. If the other job has already terminated, the
    /// caller will continue its execution.
    ///
    /// \param other The job to wait for. It is allowed to specify the
    ///        same Job as the target and the waiting job, in which case
    ///        the Job will only be woken up by an asynchronous exception.
    ///
    /// \sa yield(), yield_until(), yield_until_things_changed()
    void yield_until_terminated(Job& other);

    /// Same as \p yield_until_terminated above(), but wait for every
    /// job in the collection.
    void yield_until_terminated(const jobs_type& jobs);

    /// Wait for any other task to be scheduled.
    /// \sa yield(), yield_until_terminated(), yield_until()
    void yield_until_things_changed();

    /// Mark the current job as side-effect free.
    ///
    /// \param s True if the job is now side-effect free.
    ///
    /// Indicate whether the current state of a job may influence other
    /// parts of the system. This is used by the scheduler to choose
    /// whether other jobs need scheduling or not. The default value
    /// for \c side_effect_free is false.
    void side_effect_free_set(bool s);

    /// Is the current job side-effect free?
    ///
    /// \return True if the job is currently side-effect free.
    bool side_effect_free_get() const;

    /// Raise an exception next time this job will be resumed.
    ///
    /// \param e The exception to throw when the job will be scheduled
    ///        again.
    void async_throw(const exception& e);

    /// Maybe raise a deferred exception. Must be called from the scheduler
    /// while resuming the job execution. For example, StopException
    /// may be raised from here if the job has been blocked by a tag.
    void check_for_pending_exception();

    /// Get the job name
    ///
    /// \return The job name as set from the constructor.
    const libport::Symbol& name_get() const;

    /// Throw an exception if the stack space for this job is near
    /// exhaustion.
    void check_stack_space();

    /// Is the job frozen?
    ///
    /// \return This depends from the job tags state.
    virtual bool frozen() const;

    /// Apply a tag to the current job tag stack.
    ///
    /// \param tag The tag to apply.
    ///
    /// \param finally The action executed when the
    ///                tag is removed. No action is
    ///                inserted if 0 is given.
    void apply_tag(const rTag& tag, libport::Finally* finally);

    /// Check if the job holds a tag.
    bool has_tag(const Tag&) const;

    /// Get the current job state.
    ///
    /// \return The current job state.
    job_state state_get() const;

    /// Set the current job state
    ///
    /// \param state The new job state.
    void state_set(job_state state);

    /// Get this job deadline if it is sleeping.
    ///
    /// \return The date on which this job needs to be awoken.
    ///
    /// This function must not be called unless the job is in the
    /// \c sleeping state.
    libport::utime_t deadline_get() const;

    /// Check if the job can be interrupted.
    ///
    /// \return True if the job cannot be interrupted right now because
    ///         it is executing an atomic operation.
    bool non_interruptible_get() const;

    /// Indicate if the job can be interrupted or not
    ///
    /// \param ni True if the job must not be interrupted until further
    ///        notice.
    void non_interruptible_set(bool ni);

    /// Remember the time we have been frozen since if not remembered
    /// yet.
    ///
    /// \param current_time The current time.
    void notice_frozen(libport::utime_t current_time);

    /// Note that we are not frozen anymore.
    ///
    /// \param current_time The current time.
    void notice_not_frozen(libport::utime_t current_time);

    /// Return the origin since we have been frozen.
    ///
    /// \return 0 if we have not been frozen, the date since we have
    ///         been frozen otherwise.
    libport::utime_t frozen_since_get() const;

    /// Return the current time shift.
    ///
    /// \return The value to subtract from the system time to get the
    ///         unfrozen time of this runner.
    libport::utime_t time_shift_get() const;

    /// Set the current time shift.
    ///
    /// \param ts The new time shift. This should probably only be used
    ///           at runner creation time.
    void time_shift_set(libport::utime_t ts);

    /// Check and maybe register the fact that a tag has been stopped.
    ///
    /// \param tag The tag that has been stopped.
    ///
    /// \param payload The data to embed in the StopException.
    virtual void register_stopped_tag
      (const Tag& tag, const boost::any& payload);

    /// Check whether the job has a pending exception.
    ///
    /// \return true if the job has a pending exception.
    bool has_pending_exception() const;

    /// Get the current job priority.
    ///
    /// \return The job priority, as previously computed.
    prio_type prio_get() const;

    /// Ensure proper cleanup.
    virtual void terminate_cleanup();

    /// Number of jobs created and not yet destroyed.
    ///
    /// \return The number of jobs.
    static unsigned int alive_jobs();

    /// Register a child.
    ///
    /// \param child The child job.
    ///
    /// \param at_end Children will be automatically terminated
    ///               when this object gets no longer referenced.
    void register_child(const rJob& child, libport::Finally& at_end);

    /// Do we have a parent?
    ///
    /// \return True if we have a parent.
    bool child_job() const;

  protected:

    /// Must be implemented to do something useful. If an exception is
    /// raised, it will be propagated to our parent if we have one or
    /// lost otherwise.
    virtual void work() = 0;

    /// May be overriden. Called if a scheduling error is detected
    /// during the execution course of this job. The default
    /// implementation calls abort().
    ///
    /// \param msg The explanation of the scheduling error.
    virtual void scheduling_error(std::string msg = "");

    /// Get the current tags.
    ///
    /// \return The tags attached to the current job.
    const tags_type& tags_get() const;

    /// Set the current tags.
    ///
    /// \param tags Set the tags attached to the current job.
    void tags_set(const tags_type& tags);

    /// Clear the current tags.
    void tags_clear();

  private:
    /// Current job state, to be manipulated only from the job and the
    /// scheduler.
    job_state state_;

    /// Current job deadline. The deadline is only meaningful when the
    /// job state is \c sleeping.
    libport::utime_t deadline_;

    /// The last time we have been frozen (in system time), or 0 if we
    /// are not currently frozen.
    libport::utime_t frozen_since_;

    /// The value we have to deduce from system time because we have
    /// been frozen.
    libport::utime_t time_shift_;

    /// Recompute the current priority after a tag operation.
    void recompute_prio();

    /// Recompute the current priority if a particular tag could have
    /// affected it (addition or removal).
    void recompute_prio(const Tag&);

    /// Scheduler in charge of this job. Do not delete.
    Scheduler& scheduler_;

    /// This job name.
    libport::Symbol name_;

    /// Other jobs to wake up when we terminate.
    jobs_type to_wake_up_;

    /// Coro structure corresponding to this job.
    Coro* coro_;

  protected:

    /// Is the current job non-interruptible? If yes, yielding will
    /// do nothing and blocking operations may raise an exception.
    bool non_interruptible_;

  private:

    /// Helper functions for constructors.
    void init_common(const libport::Symbol& name);

    /// Tags this job depends on.
    tags_type tags_;

    /// Current priority.
    prio_type prio_;

    /// Is the current job side-effect free?
    bool side_effect_free_;

    /// The next exception to be propagated if any.
    exception_ptr pending_exception_;

    /// Our parent if any.
    rJob parent_;

    /// Our children.
    jobs_type children_;

    /// Terminate child and remove it from our children list.
    void terminate_child(const rJob& child);

    /// Check stack space from time to time.
    bool check_stack_space_;

    /// Number of jobs created and not yet destroyed.
    static unsigned int alive_jobs_;

  protected:
    /// Exception used to terminate a job.
    struct TerminateException : public SchedulerException
    {
      COMPLETE_EXCEPTION(TerminateException)
    };

  };

  std::ostream& operator<< (std::ostream&, const Job&);

  /// This exception will be raised to tell the job that it is currently
  /// stopped and must try to unwind tags from its tag stack until it
  /// is either dead or not stopped anymore.
  struct StopException : public SchedulerException
  {
    StopException(unsigned int, boost::any);
    ADD_FIELD(unsigned int, depth)
    ADD_FIELD(boost::any, payload)
    COMPLETE_EXCEPTION(StopException)
  };

  /// This exception encapsulates another one, sent by a child job.
  struct ChildException : public SchedulerException
  {
    ChildException(exception_ptr);
    ChildException(const ChildException&);
    virtual exception_ptr clone() const;
    ATTRIBUTE_NORETURN void rethrow_child_exception() const;
  protected:
    ATTRIBUTE_NORETURN virtual void rethrow_() const;
  private:
    mutable exception_ptr child_exception_;
  };

  /// State names to string, for debugging purpose.
  const char* state_name(job_state);

  /// Terminate all jobs present in container and empty it.
  void terminate_jobs(jobs_type& jobs);

} // namespace scheduler

# include <scheduler/job.hxx>

#endif // !SCHEDULER_JOB_HH
<|endoftext|>


<commit_before>/*****************************************************************************
 * Media Library
 *****************************************************************************
 * Copyright (C) 2017-2019 Hugo Beauzée-Luyssen, Videolabs, VideoLAN
 *
 * Authors: Hugo Beauzée-Luyssen <hugo@beauzee.fr>
 *
 * This program 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.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#if HAVE_CONFIG_H
# include "config.h"
#endif

#include "Url.h"
#include "Filename.h"
#include "medialibrary/filesystem/Errors.h"

#include <stdexcept>
#include <cstring>
#include <algorithm>
#include <iterator>

namespace
{
inline bool isSafe( char c )
{
    return strchr( ".-_~/"
#ifdef _WIN32
                    "\\"
#endif
                   , c ) != nullptr;
}

std::string::const_iterator encodeSegment( std::string& res,
                                           std::string::const_iterator begin,
                                           std::string::const_iterator end,
                                           const char* extraChars )
{
    for ( auto i = begin; i < end; ++i )
    {
        // This must be an unsigned char for the bits operations below to work.
        // auto will yield a signed char here.
        const unsigned char c = *i;
        if ( ( c >= 32 && c <= 126 ) && (
                 ( c >= 'a' && c <= 'z' ) ||
                 ( c >= 'A' && c <= 'Z' ) ||
                 ( c >= '0' && c <= '9' ) ||
                 isSafe( c ) == true ||
                 ( extraChars != nullptr && strchr( extraChars, c ) != nullptr ) )
             )
        {
            res.push_back( c );
        }
        else
            res.append({ '%', "0123456789ABCDEF"[c >> 4], "0123456789ABCDEF"[c & 0xF] });
    }
    return end;
}

inline uint16_t charToVal( char c )
{
    if ( c >= '0' && c <= '9' )
        return c - '0';
    if ( c >= 'a' && c <= 'f' )
        return c - 'a' + 10;
    if ( c >= 'A' && c <= 'F' )
        return c - 'A' + 10;
    throw std::runtime_error( "Incomplete/invalid character sequence" );
}

}

namespace medialibrary
{
namespace utils
{
namespace url
{

parts split( const std::string& url )
{
    parts res{};
    auto schemePos = url.find( "://" );
    if ( schemePos == std::string::npos )
    {
        res.path = url;
        return res;
    }
    std::copy( url.cbegin(), url.cbegin() + schemePos,
               std::back_inserter( res.scheme ) );

    if ( res.scheme == "file" )
    {
        std::copy( url.cbegin() + schemePos + 3, url.cend(),
                   std::back_inserter( res.path ) );
        return res;
    }

    const auto authorityBegin = url.cbegin() + schemePos + 3;
    auto authorityEnd = std::find( authorityBegin, url.cend(), '/' );
    auto queryBegin = std::find( authorityBegin, url.cend(), '?' );
    const auto fragmentBegin = std::find( authorityBegin, url.cend(), '#' );

    /*
     * The fragment must come after the query parameters. Since we use both
     * values at a later point, sanitize them now.
     */
    if ( fragmentBegin != url.cend() && queryBegin != url.cend() &&
         fragmentBegin < queryBegin )
    {
        queryBegin = url.cend();
    }

    /* RFC 3986 §3.2:
     * The authority component is preceded by a double slash ("//") and is
     * terminated by the next slash ("/"), question mark ("?"), or number
     * sign ("#") character, or by the end of the URI.
     */
    if ( authorityEnd == url.cend() )
    {
        if ( queryBegin != url.cend() )
            authorityEnd = queryBegin;
        else
            authorityEnd = fragmentBegin;
    }
    else
    {
        if ( queryBegin != url.cend() && queryBegin < authorityEnd )
            authorityEnd = queryBegin;
        if ( fragmentBegin != url.cend() && fragmentBegin < authorityEnd )
            authorityEnd = fragmentBegin;
    }

    {
        /* Split the authority into its actual components */
        auto userInfoEnd = std::find( authorityBegin, authorityEnd, '@' );
        if ( userInfoEnd != authorityEnd )
        {
            std::copy( authorityBegin, userInfoEnd,
                       std::back_inserter( res.userInfo ) );
            /* Skip the '@' when copying the host */
            ++userInfoEnd;
            res.hostMarker = true;
        }
        else
            userInfoEnd = authorityBegin;

        auto portBegin = std::find( userInfoEnd, authorityEnd, ':' );
        if ( portBegin != authorityEnd )
            std::copy( portBegin + 1, authorityEnd, std::back_inserter( res.port ) );
        else
            portBegin = authorityEnd;
        std::copy( userInfoEnd, portBegin, std::back_inserter( res.host ) );

    }
    if ( authorityEnd == url.cend() )
    {
        /*
         * If we don't have a clear end for the authority segment, it means the
         * end is the url end, so we don't have anything else to split
         */
        return res;
    }

    auto pathEnd = queryBegin != url.cend() ? queryBegin : fragmentBegin;
    std::copy( authorityEnd, pathEnd,
               std::back_inserter( res.path ) );
    if ( queryBegin != url.cend() )
    {
        std::copy( queryBegin + 1,
                   fragmentBegin != url.cend() ? fragmentBegin : url.cend(),
                   std::back_inserter( res.query ) );
    }
    if ( fragmentBegin != url.cend() )
    {
        std::copy( fragmentBegin + 1, url.cend(),
                   std::back_inserter( res.fragments ) );
    }
    return res;
}

std::string decode( const std::string& str )
{
    std::string res;
    res.reserve( str.size() );
    auto it = str.cbegin();
    auto ite = str.cend();
    for ( ; it != ite; ++it )
    {
        if ( *it == '%' )
        {
            ++it;
            const auto c1 = charToVal( *it );
            const auto c2 = charToVal( *( it + 1 ) );
            auto val = c1 * 16 + c2;
            res.push_back( static_cast<std::string::value_type>( val ) );
            ++it;
        }
        else
            res.push_back( *it );
    }
    return res;
}

std::string encode( const std::string& str )
{
    auto parts = split( str );
    std::string res{};
    res.reserve( str.length() );
    /*
     * If the file is a local path, we need to encode everything as the
     * characters won't have any URL related meaning
     */
    if ( parts.scheme == "file" || parts.scheme.empty() == true )
    {
        if ( parts.scheme.empty() == false )
            res += "file://";
        auto pathBegin = parts.path.cbegin();
#ifdef _WIN32
        if ( *pathBegin == '/' && isalpha( *(pathBegin + 1) ) && *(pathBegin + 2) == ':' )
        {
            // Don't encode the ':' after the drive letter.
            // All other ':' need to be encoded, there are not allowed in a file path
            // on windows, but they might appear in urls
            std::copy( pathBegin, pathBegin + 3, std::back_inserter( res ) );
            pathBegin += 3;
        }
#endif
        encodeSegment( res, pathBegin, parts.path.cend(), nullptr );
        return res;
    }
    /*
     * We already accept any character in ".-_~/" through isSafe(), but we need
     * to accept more depending on the URL segments:
     */
    encodeSegment( res, parts.scheme.cbegin(), parts.scheme.cend(), "+" );
    res += "://";
    if ( parts.userInfo.empty() == false )
    {
        encodeSegment( res, parts.userInfo.cbegin(), parts.userInfo.cend(),
                       "!$&'()*+,;=:" );
    }
    if ( parts.hostMarker == true )
        res += '@';
    encodeSegment( res, parts.host.cbegin(), parts.host.cend(), "[]" );
    if ( parts.port.empty() == false )
    {
        res += ':';
        res += parts.port;
    }

    encodeSegment( res, parts.path.cbegin(), parts.path.cend(), "!$&'()*+,;=:@" );

    if ( parts.query.empty() == false )
    {
        res += '?';
        encodeSegment( res, parts.query.cbegin(), parts.query.cend(),
                       "!$&'()*+,;=:@?" );
    }
    if ( parts.fragments.empty() == false )
    {
        res += '#';
        encodeSegment( res, parts.query.cbegin(), parts.query.cend(),
                       "!$&'()*+,;=:@?" );
    }
    return res;
}

std::string stripScheme( const std::string& mrl )
{
    auto pos = mrl.find( "://" );
    if ( pos == std::string::npos )
        throw fs::errors::UnhandledScheme( "<empty scheme>" );
    return mrl.substr( pos + 3 );
}

std::string scheme( const std::string& mrl )
{
    auto pos = mrl.find( "://" );
    if ( pos == std::string::npos )
        throw fs::errors::UnhandledScheme( "<empty scheme>" );
    return mrl.substr( 0, pos + 3 );
}

#ifndef _WIN32

std::string toLocalPath( const std::string& mrl )
{
    if ( mrl.compare( 0, 7, "file://" ) != 0 )
        throw fs::errors::UnhandledScheme( scheme( mrl ) );
    return utils::url::decode( mrl.substr( 7 ) );
}

#else

std::string toLocalPath( const std::string& mrl )
{
    if ( mrl.compare( 0, 7, "file://" ) != 0 )
        throw fs::errors::UnhandledScheme( utils::url::scheme( mrl ) );
    auto localPath = mrl.substr( 7 );
    // If the path is a local path (ie. X:\path\to and not \\path\to) skip the
    // initial backslash, as it is only part of our representation, and not
    // understood by the win32 API functions
    // Note that the initial '/' (after the 2 forward slashes from the scheme)
    // is not a backslash, as it is not a path separator, so do not use
    // DIR_SEPARATOR_CHAR here.
    if ( localPath[0] == '/' && isalpha( localPath[1] ) )
        localPath.erase( 0, 1 );
    std::replace( begin( localPath ), end( localPath ), '/', '\\' );
    return utils::url::decode( localPath );
}

#endif

bool schemeIs( const std::string& scheme, const std::string& mrl )
{
    return mrl.compare( 0, scheme.size(), scheme ) == 0;
}

std::string path( const std::string &mrl )
{
    auto schemelessMrl = stripScheme( mrl );
    auto host = utils::file::firstFolder( schemelessMrl );
    return utils::file::removePath( schemelessMrl, host );
}

}
}
}
<commit_msg>utils: Url: Reorder includes<commit_after>/*****************************************************************************
 * Media Library
 *****************************************************************************
 * Copyright (C) 2017-2019 Hugo Beauzée-Luyssen, Videolabs, VideoLAN
 *
 * Authors: Hugo Beauzée-Luyssen <hugo@beauzee.fr>
 *
 * This program 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.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#if HAVE_CONFIG_H
# include "config.h"
#endif

#include "Url.h"
#include "Filename.h"
#include "medialibrary/filesystem/Errors.h"

#include <algorithm>
#include <cstring>
#include <iterator>
#include <stdexcept>

namespace
{
inline bool isSafe( char c )
{
    return strchr( ".-_~/"
#ifdef _WIN32
                    "\\"
#endif
                   , c ) != nullptr;
}

std::string::const_iterator encodeSegment( std::string& res,
                                           std::string::const_iterator begin,
                                           std::string::const_iterator end,
                                           const char* extraChars )
{
    for ( auto i = begin; i < end; ++i )
    {
        // This must be an unsigned char for the bits operations below to work.
        // auto will yield a signed char here.
        const unsigned char c = *i;
        if ( ( c >= 32 && c <= 126 ) && (
                 ( c >= 'a' && c <= 'z' ) ||
                 ( c >= 'A' && c <= 'Z' ) ||
                 ( c >= '0' && c <= '9' ) ||
                 isSafe( c ) == true ||
                 ( extraChars != nullptr && strchr( extraChars, c ) != nullptr ) )
             )
        {
            res.push_back( c );
        }
        else
            res.append({ '%', "0123456789ABCDEF"[c >> 4], "0123456789ABCDEF"[c & 0xF] });
    }
    return end;
}

inline uint16_t charToVal( char c )
{
    if ( c >= '0' && c <= '9' )
        return c - '0';
    if ( c >= 'a' && c <= 'f' )
        return c - 'a' + 10;
    if ( c >= 'A' && c <= 'F' )
        return c - 'A' + 10;
    throw std::runtime_error( "Incomplete/invalid character sequence" );
}

}

namespace medialibrary
{
namespace utils
{
namespace url
{

parts split( const std::string& url )
{
    parts res{};
    auto schemePos = url.find( "://" );
    if ( schemePos == std::string::npos )
    {
        res.path = url;
        return res;
    }
    std::copy( url.cbegin(), url.cbegin() + schemePos,
               std::back_inserter( res.scheme ) );

    if ( res.scheme == "file" )
    {
        std::copy( url.cbegin() + schemePos + 3, url.cend(),
                   std::back_inserter( res.path ) );
        return res;
    }

    const auto authorityBegin = url.cbegin() + schemePos + 3;
    auto authorityEnd = std::find( authorityBegin, url.cend(), '/' );
    auto queryBegin = std::find( authorityBegin, url.cend(), '?' );
    const auto fragmentBegin = std::find( authorityBegin, url.cend(), '#' );

    /*
     * The fragment must come after the query parameters. Since we use both
     * values at a later point, sanitize them now.
     */
    if ( fragmentBegin != url.cend() && queryBegin != url.cend() &&
         fragmentBegin < queryBegin )
    {
        queryBegin = url.cend();
    }

    /* RFC 3986 §3.2:
     * The authority component is preceded by a double slash ("//") and is
     * terminated by the next slash ("/"), question mark ("?"), or number
     * sign ("#") character, or by the end of the URI.
     */
    if ( authorityEnd == url.cend() )
    {
        if ( queryBegin != url.cend() )
            authorityEnd = queryBegin;
        else
            authorityEnd = fragmentBegin;
    }
    else
    {
        if ( queryBegin != url.cend() && queryBegin < authorityEnd )
            authorityEnd = queryBegin;
        if ( fragmentBegin != url.cend() && fragmentBegin < authorityEnd )
            authorityEnd = fragmentBegin;
    }

    {
        /* Split the authority into its actual components */
        auto userInfoEnd = std::find( authorityBegin, authorityEnd, '@' );
        if ( userInfoEnd != authorityEnd )
        {
            std::copy( authorityBegin, userInfoEnd,
                       std::back_inserter( res.userInfo ) );
            /* Skip the '@' when copying the host */
            ++userInfoEnd;
            res.hostMarker = true;
        }
        else
            userInfoEnd = authorityBegin;

        auto portBegin = std::find( userInfoEnd, authorityEnd, ':' );
        if ( portBegin != authorityEnd )
            std::copy( portBegin + 1, authorityEnd, std::back_inserter( res.port ) );
        else
            portBegin = authorityEnd;
        std::copy( userInfoEnd, portBegin, std::back_inserter( res.host ) );

    }
    if ( authorityEnd == url.cend() )
    {
        /*
         * If we don't have a clear end for the authority segment, it means the
         * end is the url end, so we don't have anything else to split
         */
        return res;
    }

    auto pathEnd = queryBegin != url.cend() ? queryBegin : fragmentBegin;
    std::copy( authorityEnd, pathEnd,
               std::back_inserter( res.path ) );
    if ( queryBegin != url.cend() )
    {
        std::copy( queryBegin + 1,
                   fragmentBegin != url.cend() ? fragmentBegin : url.cend(),
                   std::back_inserter( res.query ) );
    }
    if ( fragmentBegin != url.cend() )
    {
        std::copy( fragmentBegin + 1, url.cend(),
                   std::back_inserter( res.fragments ) );
    }
    return res;
}

std::string decode( const std::string& str )
{
    std::string res;
    res.reserve( str.size() );
    auto it = str.cbegin();
    auto ite = str.cend();
    for ( ; it != ite; ++it )
    {
        if ( *it == '%' )
        {
            ++it;
            const auto c1 = charToVal( *it );
            const auto c2 = charToVal( *( it + 1 ) );
            auto val = c1 * 16 + c2;
            res.push_back( static_cast<std::string::value_type>( val ) );
            ++it;
        }
        else
            res.push_back( *it );
    }
    return res;
}

std::string encode( const std::string& str )
{
    auto parts = split( str );
    std::string res{};
    res.reserve( str.length() );
    /*
     * If the file is a local path, we need to encode everything as the
     * characters won't have any URL related meaning
     */
    if ( parts.scheme == "file" || parts.scheme.empty() == true )
    {
        if ( parts.scheme.empty() == false )
            res += "file://";
        auto pathBegin = parts.path.cbegin();
#ifdef _WIN32
        if ( *pathBegin == '/' && isalpha( *(pathBegin + 1) ) && *(pathBegin + 2) == ':' )
        {
            // Don't encode the ':' after the drive letter.
            // All other ':' need to be encoded, there are not allowed in a file path
            // on windows, but they might appear in urls
            std::copy( pathBegin, pathBegin + 3, std::back_inserter( res ) );
            pathBegin += 3;
        }
#endif
        encodeSegment( res, pathBegin, parts.path.cend(), nullptr );
        return res;
    }
    /*
     * We already accept any character in ".-_~/" through isSafe(), but we need
     * to accept more depending on the URL segments:
     */
    encodeSegment( res, parts.scheme.cbegin(), parts.scheme.cend(), "+" );
    res += "://";
    if ( parts.userInfo.empty() == false )
    {
        encodeSegment( res, parts.userInfo.cbegin(), parts.userInfo.cend(),
                       "!$&'()*+,;=:" );
    }
    if ( parts.hostMarker == true )
        res += '@';
    encodeSegment( res, parts.host.cbegin(), parts.host.cend(), "[]" );
    if ( parts.port.empty() == false )
    {
        res += ':';
        res += parts.port;
    }

    encodeSegment( res, parts.path.cbegin(), parts.path.cend(), "!$&'()*+,;=:@" );

    if ( parts.query.empty() == false )
    {
        res += '?';
        encodeSegment( res, parts.query.cbegin(), parts.query.cend(),
                       "!$&'()*+,;=:@?" );
    }
    if ( parts.fragments.empty() == false )
    {
        res += '#';
        encodeSegment( res, parts.query.cbegin(), parts.query.cend(),
                       "!$&'()*+,;=:@?" );
    }
    return res;
}

std::string stripScheme( const std::string& mrl )
{
    auto pos = mrl.find( "://" );
    if ( pos == std::string::npos )
        throw fs::errors::UnhandledScheme( "<empty scheme>" );
    return mrl.substr( pos + 3 );
}

std::string scheme( const std::string& mrl )
{
    auto pos = mrl.find( "://" );
    if ( pos == std::string::npos )
        throw fs::errors::UnhandledScheme( "<empty scheme>" );
    return mrl.substr( 0, pos + 3 );
}

#ifndef _WIN32

std::string toLocalPath( const std::string& mrl )
{
    if ( mrl.compare( 0, 7, "file://" ) != 0 )
        throw fs::errors::UnhandledScheme( scheme( mrl ) );
    return utils::url::decode( mrl.substr( 7 ) );
}

#else

std::string toLocalPath( const std::string& mrl )
{
    if ( mrl.compare( 0, 7, "file://" ) != 0 )
        throw fs::errors::UnhandledScheme( utils::url::scheme( mrl ) );
    auto localPath = mrl.substr( 7 );
    // If the path is a local path (ie. X:\path\to and not \\path\to) skip the
    // initial backslash, as it is only part of our representation, and not
    // understood by the win32 API functions
    // Note that the initial '/' (after the 2 forward slashes from the scheme)
    // is not a backslash, as it is not a path separator, so do not use
    // DIR_SEPARATOR_CHAR here.
    if ( localPath[0] == '/' && isalpha( localPath[1] ) )
        localPath.erase( 0, 1 );
    std::replace( begin( localPath ), end( localPath ), '/', '\\' );
    return utils::url::decode( localPath );
}

#endif

bool schemeIs( const std::string& scheme, const std::string& mrl )
{
    return mrl.compare( 0, scheme.size(), scheme ) == 0;
}

std::string path( const std::string &mrl )
{
    auto schemelessMrl = stripScheme( mrl );
    auto host = utils::file::firstFolder( schemelessMrl );
    return utils::file::removePath( schemelessMrl, host );
}

}
}
}
<|endoftext|>


<commit_before>#include "Xmodem.h"

/**
 * Construct an Xmodem instance.
 *
 * @param serial port to use
 */
Xmodem::Xmodem(Stream& serial) {
#ifdef ARDUINO
  _serial = &serial;
#else
  _serial = *serial;
#endif // ifdef ARDUINO
}

/**
 * Send a file over the serial port.
 * @param file file to send
 */
int Xmodem::send(File* file) {
  _file = file;
  _packetNumber = 1;

  while (true) {
    int c = serialRead();
    if (c != -1) {
      break;
    }
    delay(100);
  }

  int status = 0;
  while (true) {
    size_t bytesRead = readPacket();
    if (bytesRead > 0) {
      status = sendPacket();
      if (status < 0) {
        break;
      }
    }
    if (bytesRead < PACKET_SIZE) {
      break;
    }
  }
  serialWrite(EOT);
  while (serialRead() == -1);
  return status;
}

/**
 * @internal
 *
 * Send the current packet over the serial port and handle response.
 *
 *  + ACK - r
 * @return -1 if error
 */
int Xmodem::sendPacket() {
  serialWrite(STX);
  serialWrite(_packetNumber);
  serialWrite(255 - _packetNumber);
  serialWrite(_packet, PACKET_SIZE);
  serialWrite((_crc >> 8) & 0xff);
  serialWrite(_crc & 0xff);

  int c;
  while (true) {
    c = serialRead();
    if (c == NAK) {
      delay(5);
      return sendPacket();
    }
    else if (c == ACK) {
      _packetNumber++;
      return 0;
    }
    else if (c == CAN) {
      return -1;
    }
  }
}

/**
 * Read current packet from current file. Pad the packet with PAD_CHAR if the
 * file has less than PACKET_SIZE bytes left to read.
 *
 * @return the number of bytes read from the file
 */
size_t Xmodem::readPacket() {
  size_t bytesRead = fileRead(_packet, PACKET_SIZE);
  for (size_t i = bytesRead; i < PACKET_SIZE; i++) {
    _packet[i] = PAD_CHAR;
  }

  _crc = crc_init();
  _crc = crc_update(_crc, _packet, PACKET_SIZE);
  _crc = crc_finalize(_crc);

  return bytesRead;
}

/**
 * @internal
 *
 * Read a block from a file.
 *
 * @param  buffer  buffer to hold data read from file
 * @param  count   number of bytes to read from file
 * @return         number of bytes actually read
 */
size_t Xmodem::fileRead(uint8_t *buffer, size_t count) {
#ifdef ARDUINO
  return _file->read(buffer, count);
#else
  return fread(buffer, 1, count, _file);
#endif // ifdef ARDUINO
}

/**
 * @internal
 *
 * Write a block of data to the serial port.
 *
 * @param  buffer data to write
 * @param  count  number of bytes to write
 * @return        number of bytes actually written
 */
size_t Xmodem::serialWrite(uint8_t *buffer, size_t count) {
#ifdef ARDUINO
  // Serial.println("writing block");

  uint8_t *current = buffer;
  int remaining = count;
  int batchSize = 128;
  size_t written = 0;
  while (remaining > 0) {
    int currentBatchSize = min(batchSize, remaining);
    written += _serial->write(current, currentBatchSize);
    current += currentBatchSize;
    remaining -= currentBatchSize;
  }
  return written;

#else
  return write(_serial, buffer, count);
#endif // ifdef ARDUINO
}

/**
 * @internal
 *
 * Write a byte to the serial port.
 *
 * @param  c byte to write
 * @return c if byte written, or -1 if error
 */
int Xmodem::serialWrite(uint8_t c) {
#ifdef ARDUINO
  return _serial->write(c);
#else
  return write(_serial, &c, 1);
#endif // ifdef ARDUINO
}

/**
 * @internal
 *
 * Read a byte from the serial port.
 *
 * @return byte read, or -1 no data is available to read
 */
int Xmodem::serialRead() {
#ifdef ARDUINO
  return _serial->read();
#else
  uint8_t c;
  size_t bytesRead = read(_serial, &c, 1);
  if (bytesRead == 0) {
    return -1;
  }
  else {
    return (int)c;
  }
#endif // ifdef ARDUINO
}
<commit_msg>housekeeping<commit_after>#include "Xmodem.h"

/**
 * Construct an Xmodem instance.
 *
 * @param serial port to use
 */
Xmodem::Xmodem(Stream& serial) {
#ifdef ARDUINO
  _serial = &serial;
#else
  _serial = *serial;
#endif // ifdef ARDUINO
}

/**
 * Send a file over the serial port.
 * @param file file to send
 */
int Xmodem::send(File* file) {
  _file = file;
  _packetNumber = 1;

  while (true) {
    int c = serialRead();
    if (c != -1) {
      break;
    }
    delay(100);
  }

  int status = 0;
  while (true) {
    size_t bytesRead = readPacket();
    if (bytesRead > 0) {
      status = sendPacket();
      if (status < 0) {
        break;
      }
    }
    if (bytesRead < PACKET_SIZE) {
      break;
    }
  }
  serialWrite(EOT);
  while (serialRead() == -1);
  return status;
}

/**
 * @internal
 *
 * Send the current packet over the serial port and handle response.
 *
 *  + ACK - r
 * @return -1 if error
 */
int Xmodem::sendPacket() {
  serialWrite(STX);
  serialWrite(_packetNumber);
  serialWrite(255 - _packetNumber);
  serialWrite(_packet, PACKET_SIZE);
  serialWrite((_crc >> 8) & 0xff);
  serialWrite(_crc & 0xff);

  int c;
  while (true) {
    c = serialRead();
    if (c == NAK) {
      delay(5);
      return sendPacket();
    }
    else if (c == ACK) {
      _packetNumber++;
      return 0;
    }
    else if (c == CAN) {
      return -1;
    }
  }
}

/**
 * Read current packet from current file. Pad the packet with PAD_CHAR if the
 * file has less than PACKET_SIZE bytes left to read.
 *
 * @return the number of bytes read from the file
 */
size_t Xmodem::readPacket() {
  size_t bytesRead = fileRead(_packet, PACKET_SIZE);
  for (size_t i = bytesRead; i < PACKET_SIZE; i++) {
    _packet[i] = PAD_CHAR;
  }

  _crc = crc_init();
  _crc = crc_update(_crc, _packet, PACKET_SIZE);
  _crc = crc_finalize(_crc);

  return bytesRead;
}

/**
 * @internal
 *
 * Read a block from a file.
 *
 * @param  buffer  buffer to hold data read from file
 * @param  count   number of bytes to read from file
 * @return         number of bytes actually read
 */
size_t Xmodem::fileRead(uint8_t *buffer, size_t count) {
#ifdef ARDUINO
  return _file->read(buffer, count);
#else
  return fread(buffer, 1, count, _file);
#endif // ifdef ARDUINO
}

/**
 * @internal
 *
 * Write a block of data to the serial port.
 *
 * @param  buffer data to write
 * @param  count  number of bytes to write
 * @return        number of bytes actually written
 */
size_t Xmodem::serialWrite(uint8_t *buffer, size_t count) {
#ifdef ARDUINO
  uint8_t *current = buffer;
  int remaining = count;
  int batchSize = 128;
  size_t written = 0;
  while (remaining > 0) {
    int currentBatchSize = min(batchSize, remaining);
    written += _serial->write(current, currentBatchSize);
    current += currentBatchSize;
    remaining -= currentBatchSize;
  }
  return written;

#else
  return write(_serial, buffer, count);
#endif // ifdef ARDUINO
}

/**
 * @internal
 *
 * Write a byte to the serial port.
 *
 * @param  c byte to write
 * @return c if byte written, or -1 if error
 */
int Xmodem::serialWrite(uint8_t c) {
#ifdef ARDUINO
  return _serial->write(c);
#else
  return write(_serial, &c, 1);
#endif // ifdef ARDUINO
}

/**
 * @internal
 *
 * Read a byte from the serial port.
 *
 * @return byte read, or -1 no data is available to read
 */
int Xmodem::serialRead() {
#ifdef ARDUINO
  return _serial->read();
#else
  uint8_t c;
  size_t bytesRead = read(_serial, &c, 1);
  if (bytesRead == 0) {
    return -1;
  }
  else {
    return (int)c;
  }
#endif // ifdef ARDUINO
}
<|endoftext|>


<commit_before>/*
 * This file is open source software, licensed to you under the terms
 * of the Apache License, Version 2.0 (the "License").  See the NOTICE file
 * distributed with this work for additional information regarding copyright
 * ownership.  You may not use this file except in compliance with the License.
 *
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
/*
 * Copyright (C) 2014-2015 Cloudius Systems, Ltd.
 */

#include <seastar/testing/test_case.hh>
#include <seastar/testing/thread_test_case.hh>

#include <seastar/core/semaphore.hh>
#include <seastar/core/condition-variable.hh>
#include <seastar/core/file.hh>
#include <seastar/core/reactor.hh>
#include <seastar/core/thread.hh>
#include <seastar/core/stall_sampler.hh>
#include <boost/range/adaptor/transformed.hpp>
#include <iostream>

using namespace seastar;

SEASTAR_TEST_CASE(open_flags_test) {
    open_flags flags = open_flags::rw | open_flags::create  | open_flags::exclusive;
    BOOST_REQUIRE(std::underlying_type_t<open_flags>(flags) ==
                  (std::underlying_type_t<open_flags>(open_flags::rw) |
                   std::underlying_type_t<open_flags>(open_flags::create) |
                   std::underlying_type_t<open_flags>(open_flags::exclusive)));

    open_flags mask = open_flags::create  | open_flags::exclusive;
    BOOST_REQUIRE((flags & mask) == mask);
    return make_ready_future<>();
}

struct file_test {
    file_test(file&& f) : f(std::move(f)) {}
    file f;
    semaphore sem = { 0 };
    semaphore par = { 1000 };
};

SEASTAR_TEST_CASE(test1) {
    // Note: this tests generates a file "testfile.tmp" with size 4096 * max (= 40 MB).
    static constexpr auto max = 10000;
    return open_file_dma("testfile.tmp", open_flags::rw | open_flags::create).then([] (file f) {
        auto ft = new file_test{std::move(f)};
        for (size_t i = 0; i < max; ++i) {
            ft->par.wait().then([ft, i] {
                auto wbuf = allocate_aligned_buffer<unsigned char>(4096, 4096);
                std::fill(wbuf.get(), wbuf.get() + 4096, i);
                auto wb = wbuf.get();
                ft->f.dma_write(i * 4096, wb, 4096).then(
                        [ft, i, wbuf = std::move(wbuf)] (size_t ret) mutable {
                    BOOST_REQUIRE(ret == 4096);
                    auto rbuf = allocate_aligned_buffer<unsigned char>(4096, 4096);
                    auto rb = rbuf.get();
                    ft->f.dma_read(i * 4096, rb, 4096).then(
                            [ft, rbuf = std::move(rbuf), wbuf = std::move(wbuf)] (size_t ret) mutable {
                        BOOST_REQUIRE(ret == 4096);
                        BOOST_REQUIRE(std::equal(rbuf.get(), rbuf.get() + 4096, wbuf.get()));
                        ft->sem.signal(1);
                        ft->par.signal();
                    });
                });
            });
        }
        return ft->sem.wait(max).then([ft] () mutable {
            return ft->f.flush();
        }).then([ft] {
            return ft->f.close();
        }).then([ft] () mutable {
            std::cout << "done\n";
            delete ft;
        });
    });
}

SEASTAR_TEST_CASE(parallel_write_fsync) {
    return internal::report_reactor_stalls([] {
        return async([] {
            // Plan: open a file and write to it like crazy. In parallel fsync() it all the time.
            auto fname = "testfile.tmp";
            auto sz = uint64_t(32*1024*1024);
            auto buffer_size = 32768;
            auto write_concurrency = 16;
            auto fsync_every = 1024*1024;
            auto max_write_ahead_of_fsync = 4*1024*1024; // ensures writes don't complete too quickly
            auto written = uint64_t(0);
            auto fsynced_at = uint64_t(0);

            file f = open_file_dma(fname, open_flags::rw | open_flags::create | open_flags::truncate).get0();
            // Avoid filesystem problems with size-extending operations
            f.truncate(sz).get();

            auto fsync_semaphore = semaphore(0);
            auto may_write_condvar = condition_variable();
            auto fsync_thread = thread([&] {
                auto fsynced = uint64_t(0);
                while (fsynced < sz) {
                    fsync_semaphore.wait(fsync_every).get();
                    fsynced_at = written;
                    // Signal the condition variable now so that writes proceed
                    // in parallel with the fsync
                    may_write_condvar.broadcast();
                    f.flush().get();
                    fsynced += fsync_every;
                }
            });

            auto write_semaphore = semaphore(write_concurrency);
            while (written < sz) {
                write_semaphore.wait().get();
                may_write_condvar.wait([&] {
                    return written <= fsynced_at + max_write_ahead_of_fsync;
                }).get();
                auto buf = temporary_buffer<char>::aligned(f.memory_dma_alignment(), buffer_size);
                f.dma_write(written, buf.get(), buf.size()).then([&fsync_semaphore, &write_semaphore, buf = std::move(buf)] (size_t w) {
                    fsync_semaphore.signal(buf.size());
                    write_semaphore.signal();
                });
                written += buffer_size;
            }
            write_semaphore.wait(write_concurrency).get();

            fsync_thread.join().get();
            f.close().get();
            remove_file(fname).get();
        });
    }).then([] (internal::stall_report sr) {
        std::cout << "parallel_write_fsync: " << sr << "\n";
    });
}

SEASTAR_THREAD_TEST_CASE(test_iov_max) {
    static constexpr size_t buffer_size = 4096;
    static constexpr size_t buffer_count = IOV_MAX * 2 + 1;

    std::vector<temporary_buffer<char>> original_buffers;
    std::vector<iovec> iovecs;
    for (auto i = 0u; i < buffer_count; i++) {
        original_buffers.emplace_back(temporary_buffer<char>::aligned(buffer_size, buffer_size));
        std::fill_n(original_buffers.back().get_write(), buffer_size, char(i));
        iovecs.emplace_back(iovec { original_buffers.back().get_write(), buffer_size });
    }

    auto f = open_file_dma("testfile.tmp", open_flags::rw | open_flags::create).get0();
    size_t left = buffer_size * buffer_count;
    size_t position = 0;
    while (left) {
        auto written = f.dma_write(position, iovecs).get0();
        iovecs.erase(iovecs.begin(), iovecs.begin() + written / buffer_size);
        assert(written % buffer_size == 0);
        position += written;
        left -= written;
    }

    BOOST_CHECK(iovecs.empty());

    std::vector<temporary_buffer<char>> read_buffers;
    for (auto i = 0u; i < buffer_count; i++) {
        read_buffers.emplace_back(temporary_buffer<char>::aligned(buffer_size, buffer_size));
        std::fill_n(read_buffers.back().get_write(), buffer_size, char(0));
        iovecs.emplace_back(iovec { read_buffers.back().get_write(), buffer_size });
    }

    left = buffer_size * buffer_count;
    position = 0;
    while (left) {
        auto read = f.dma_read(position, iovecs).get0();
        iovecs.erase(iovecs.begin(), iovecs.begin() + read / buffer_size);
        assert(read % buffer_size == 0);
        position += read;
        left -= read;
    }

    for (auto i = 0u; i < buffer_count; i++) {
        BOOST_CHECK(std::equal(original_buffers[i].get(), original_buffers[i].get() + original_buffers[i].size(),
                               read_buffers[i].get(), read_buffers[i].get() + read_buffers[i].size()));
    }

    f.close().get();
}
<commit_msg>tests/file_io: add test for sanitize_iovecs<commit_after>/*
 * This file is open source software, licensed to you under the terms
 * of the Apache License, Version 2.0 (the "License").  See the NOTICE file
 * distributed with this work for additional information regarding copyright
 * ownership.  You may not use this file except in compliance with the License.
 *
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
/*
 * Copyright (C) 2014-2015 Cloudius Systems, Ltd.
 */

#include <seastar/testing/test_case.hh>
#include <seastar/testing/thread_test_case.hh>

#include <seastar/core/semaphore.hh>
#include <seastar/core/condition-variable.hh>
#include <seastar/core/file.hh>
#include <seastar/core/reactor.hh>
#include <seastar/core/thread.hh>
#include <seastar/core/stall_sampler.hh>
#include <boost/range/adaptor/transformed.hpp>
#include <iostream>

#include "core/file-impl.hh"

using namespace seastar;

SEASTAR_TEST_CASE(open_flags_test) {
    open_flags flags = open_flags::rw | open_flags::create  | open_flags::exclusive;
    BOOST_REQUIRE(std::underlying_type_t<open_flags>(flags) ==
                  (std::underlying_type_t<open_flags>(open_flags::rw) |
                   std::underlying_type_t<open_flags>(open_flags::create) |
                   std::underlying_type_t<open_flags>(open_flags::exclusive)));

    open_flags mask = open_flags::create  | open_flags::exclusive;
    BOOST_REQUIRE((flags & mask) == mask);
    return make_ready_future<>();
}

struct file_test {
    file_test(file&& f) : f(std::move(f)) {}
    file f;
    semaphore sem = { 0 };
    semaphore par = { 1000 };
};

SEASTAR_TEST_CASE(test1) {
    // Note: this tests generates a file "testfile.tmp" with size 4096 * max (= 40 MB).
    static constexpr auto max = 10000;
    return open_file_dma("testfile.tmp", open_flags::rw | open_flags::create).then([] (file f) {
        auto ft = new file_test{std::move(f)};
        for (size_t i = 0; i < max; ++i) {
            ft->par.wait().then([ft, i] {
                auto wbuf = allocate_aligned_buffer<unsigned char>(4096, 4096);
                std::fill(wbuf.get(), wbuf.get() + 4096, i);
                auto wb = wbuf.get();
                ft->f.dma_write(i * 4096, wb, 4096).then(
                        [ft, i, wbuf = std::move(wbuf)] (size_t ret) mutable {
                    BOOST_REQUIRE(ret == 4096);
                    auto rbuf = allocate_aligned_buffer<unsigned char>(4096, 4096);
                    auto rb = rbuf.get();
                    ft->f.dma_read(i * 4096, rb, 4096).then(
                            [ft, rbuf = std::move(rbuf), wbuf = std::move(wbuf)] (size_t ret) mutable {
                        BOOST_REQUIRE(ret == 4096);
                        BOOST_REQUIRE(std::equal(rbuf.get(), rbuf.get() + 4096, wbuf.get()));
                        ft->sem.signal(1);
                        ft->par.signal();
                    });
                });
            });
        }
        return ft->sem.wait(max).then([ft] () mutable {
            return ft->f.flush();
        }).then([ft] {
            return ft->f.close();
        }).then([ft] () mutable {
            std::cout << "done\n";
            delete ft;
        });
    });
}

SEASTAR_TEST_CASE(parallel_write_fsync) {
    return internal::report_reactor_stalls([] {
        return async([] {
            // Plan: open a file and write to it like crazy. In parallel fsync() it all the time.
            auto fname = "testfile.tmp";
            auto sz = uint64_t(32*1024*1024);
            auto buffer_size = 32768;
            auto write_concurrency = 16;
            auto fsync_every = 1024*1024;
            auto max_write_ahead_of_fsync = 4*1024*1024; // ensures writes don't complete too quickly
            auto written = uint64_t(0);
            auto fsynced_at = uint64_t(0);

            file f = open_file_dma(fname, open_flags::rw | open_flags::create | open_flags::truncate).get0();
            // Avoid filesystem problems with size-extending operations
            f.truncate(sz).get();

            auto fsync_semaphore = semaphore(0);
            auto may_write_condvar = condition_variable();
            auto fsync_thread = thread([&] {
                auto fsynced = uint64_t(0);
                while (fsynced < sz) {
                    fsync_semaphore.wait(fsync_every).get();
                    fsynced_at = written;
                    // Signal the condition variable now so that writes proceed
                    // in parallel with the fsync
                    may_write_condvar.broadcast();
                    f.flush().get();
                    fsynced += fsync_every;
                }
            });

            auto write_semaphore = semaphore(write_concurrency);
            while (written < sz) {
                write_semaphore.wait().get();
                may_write_condvar.wait([&] {
                    return written <= fsynced_at + max_write_ahead_of_fsync;
                }).get();
                auto buf = temporary_buffer<char>::aligned(f.memory_dma_alignment(), buffer_size);
                f.dma_write(written, buf.get(), buf.size()).then([&fsync_semaphore, &write_semaphore, buf = std::move(buf)] (size_t w) {
                    fsync_semaphore.signal(buf.size());
                    write_semaphore.signal();
                });
                written += buffer_size;
            }
            write_semaphore.wait(write_concurrency).get();

            fsync_thread.join().get();
            f.close().get();
            remove_file(fname).get();
        });
    }).then([] (internal::stall_report sr) {
        std::cout << "parallel_write_fsync: " << sr << "\n";
    });
}

SEASTAR_THREAD_TEST_CASE(test_iov_max) {
    static constexpr size_t buffer_size = 4096;
    static constexpr size_t buffer_count = IOV_MAX * 2 + 1;

    std::vector<temporary_buffer<char>> original_buffers;
    std::vector<iovec> iovecs;
    for (auto i = 0u; i < buffer_count; i++) {
        original_buffers.emplace_back(temporary_buffer<char>::aligned(buffer_size, buffer_size));
        std::fill_n(original_buffers.back().get_write(), buffer_size, char(i));
        iovecs.emplace_back(iovec { original_buffers.back().get_write(), buffer_size });
    }

    auto f = open_file_dma("testfile.tmp", open_flags::rw | open_flags::create).get0();
    size_t left = buffer_size * buffer_count;
    size_t position = 0;
    while (left) {
        auto written = f.dma_write(position, iovecs).get0();
        iovecs.erase(iovecs.begin(), iovecs.begin() + written / buffer_size);
        assert(written % buffer_size == 0);
        position += written;
        left -= written;
    }

    BOOST_CHECK(iovecs.empty());

    std::vector<temporary_buffer<char>> read_buffers;
    for (auto i = 0u; i < buffer_count; i++) {
        read_buffers.emplace_back(temporary_buffer<char>::aligned(buffer_size, buffer_size));
        std::fill_n(read_buffers.back().get_write(), buffer_size, char(0));
        iovecs.emplace_back(iovec { read_buffers.back().get_write(), buffer_size });
    }

    left = buffer_size * buffer_count;
    position = 0;
    while (left) {
        auto read = f.dma_read(position, iovecs).get0();
        iovecs.erase(iovecs.begin(), iovecs.begin() + read / buffer_size);
        assert(read % buffer_size == 0);
        position += read;
        left -= read;
    }

    for (auto i = 0u; i < buffer_count; i++) {
        BOOST_CHECK(std::equal(original_buffers[i].get(), original_buffers[i].get() + original_buffers[i].size(),
                               read_buffers[i].get(), read_buffers[i].get() + read_buffers[i].size()));
    }

    f.close().get();
}

SEASTAR_THREAD_TEST_CASE(test_sanitize_iovecs) {
    auto buf = temporary_buffer<char>::aligned(4096, 4096);

    auto iovec_equal = [] (const iovec& a, const iovec& b) {
        return a.iov_base == b.iov_base && a.iov_len == b.iov_len;
    };

    { // Single fragment, sanitize is noop
        auto original_iovecs = std::vector<iovec> { { buf.get_write(), buf.size() } };
        auto actual_iovecs = original_iovecs;
        auto actual_length = internal::sanitize_iovecs(actual_iovecs, 4096);
        BOOST_CHECK_EQUAL(actual_length, 4096);
        BOOST_CHECK_EQUAL(actual_iovecs.size(), 1);
        BOOST_CHECK(iovec_equal(original_iovecs.back(), actual_iovecs.back()));
    }

    { // one 1024 buffer and IOV_MAX+6 buffers of 512; 4096 byte disk alignment, sanitize needs to drop buffers
        auto original_iovecs = std::vector<iovec>{};
        for (auto i = 0u; i < IOV_MAX + 7; i++) {
            original_iovecs.emplace_back(iovec { buf.get_write(), i == 0 ? 1024u : 512u });
        }
        auto actual_iovecs = original_iovecs;
        auto actual_length = internal::sanitize_iovecs(actual_iovecs, 4096);
        BOOST_CHECK_EQUAL(actual_length, 512 * IOV_MAX);
        BOOST_CHECK_EQUAL(actual_iovecs.size(), IOV_MAX - 1);

        original_iovecs.resize(IOV_MAX - 1);
        BOOST_CHECK(std::equal(original_iovecs.begin(), original_iovecs.end(),
                               actual_iovecs.begin(), actual_iovecs.end(), iovec_equal));
    }

    { // IOV_MAX-1 buffers of 512, one 1024 buffer, and 6 512 buffers; 4096 byte disk alignment, sanitize needs to drop and trim buffers
        auto original_iovecs = std::vector<iovec>{};
        for (auto i = 0u; i < IOV_MAX + 7; i++) {
            original_iovecs.emplace_back(iovec { buf.get_write(), i == (IOV_MAX - 1) ? 1024u : 512u });
        }
        auto actual_iovecs = original_iovecs;
        auto actual_length = internal::sanitize_iovecs(actual_iovecs, 4096);
        BOOST_CHECK_EQUAL(actual_length, 512 * IOV_MAX);
        BOOST_CHECK_EQUAL(actual_iovecs.size(), IOV_MAX);

        original_iovecs.resize(IOV_MAX);
        original_iovecs.back().iov_len = 512;
        BOOST_CHECK(std::equal(original_iovecs.begin(), original_iovecs.end(),
                               actual_iovecs.begin(), actual_iovecs.end(), iovec_equal));
    }

    { // IOV_MAX+8 buffers of 512; 4096 byte disk alignment, sanitize needs to drop buffers
        auto original_iovecs = std::vector<iovec>{};
        for (auto i = 0u; i < IOV_MAX + 8; i++) {
            original_iovecs.emplace_back(iovec { buf.get_write(), 512 });
        }
        auto actual_iovecs = original_iovecs;
        auto actual_length = internal::sanitize_iovecs(actual_iovecs, 4096);
        BOOST_CHECK_EQUAL(actual_length, 512 * IOV_MAX);
        BOOST_CHECK_EQUAL(actual_iovecs.size(), IOV_MAX);

        original_iovecs.resize(IOV_MAX);
        BOOST_CHECK(std::equal(original_iovecs.begin(), original_iovecs.end(),
                               actual_iovecs.begin(), actual_iovecs.end(), iovec_equal));
    }
}
<|endoftext|>


<commit_before>#include "config.h"

#ifdef CONFIG_GDK_PIXBUF_XLIB

#include "yimage.h"
#include "yxapp.h"
#include "ypixbuf.h"

extern "C" {
#include <gdk-pixbuf-xlib/gdk-pixbuf-xlib.h>
}

class YImageGDK: public YImage {
public:
    YImageGDK(int width, int height, GdkPixbuf *pixbuf): YImage(width, height) {
        fPixbuf = pixbuf;
    }
    virtual ~YImageGDK() {
        g_object_unref(G_OBJECT(fPixbuf));
    }
    virtual ref<YPixmap> renderToPixmap();
    virtual ref<YImage> scale(int width, int height);
    virtual void draw(Graphics &g, int dx, int dy);
    virtual void draw(Graphics &g, int x, int y, int w, int h, int dx, int dy);
    virtual void composite(Graphics &g, int x, int y, int w, int h, int dx, int dy);
    virtual bool valid() const { return fPixbuf != 0; }
private:
    GdkPixbuf *fPixbuf;
};

ref<YImage> YImage::create(int width, int height) {
    ref<YImage> image;
    GdkPixbuf *pixbuf =
        gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, width, height);
    if (pixbuf != NULL) {
        image.init(new YImageGDK(width, height, pixbuf));
    }
    return image;
}

ref<YImage> YImage::load(upath filename) {
    ref<YImage> image;
    GError *gerror = 0;
    GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(cstring(filename.path()).c_str(), &gerror);

    if (pixbuf != NULL) {
        image.init(new YImageGDK(gdk_pixbuf_get_width(pixbuf),
                                 gdk_pixbuf_get_height(pixbuf),
                                 pixbuf));
    }
    return image;
}

ref<YImage> YImageGDK::scale(int w, int h) {
    ref<YImage> image;
    GdkPixbuf *pixbuf = 0;
    bool alpha = gdk_pixbuf_get_has_alpha(fPixbuf);
#if 0
    pixbuf = gdk_pixbuf_scale_simple(fPixbuf,
                                     w, h,
                                     GDK_INTERP_BILINEAR);
#else
    pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, alpha, 8, w, h);

    pixbuf_scale(gdk_pixbuf_get_pixels(fPixbuf),
                 gdk_pixbuf_get_rowstride(fPixbuf),
                 gdk_pixbuf_get_width(fPixbuf),
                 gdk_pixbuf_get_height(fPixbuf),
                 gdk_pixbuf_get_pixels(pixbuf),
                 gdk_pixbuf_get_rowstride(pixbuf),
                 gdk_pixbuf_get_width(pixbuf),
                 gdk_pixbuf_get_height(pixbuf),
                 alpha);
#endif

    if (pixbuf != NULL) {
        image.init(new YImageGDK(w, h, pixbuf));
    }

    return image;
}

ref<YImage> YImage::createFromPixmap(ref<YPixmap> pixmap) {
    return createFromPixmapAndMask(pixmap->pixmap(),
                                   pixmap->mask(),
                                   pixmap->width(),
                                   pixmap->height());
}

ref<YImage> YImage::createFromPixmapAndMask(Pixmap pixmap, Pixmap mask,
                                            int width, int height)
{
    ref<YImage> image;
    GdkPixbuf *pixbuf =
        gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8,
                       width, height);

    if (pixbuf) {
        pixbuf =
            gdk_pixbuf_xlib_get_from_drawable(pixbuf,
                                              pixmap,
                                              xlib_rgb_get_cmap(),
                                              xlib_rgb_get_visual(),
                                              0, 0,
                                              0, 0,
                                              width,
                                              height);

        if (mask != None) {
            XImage *image = XGetImage(xapp->display(), mask,
                                      0, 0, width, height,
                                      AllPlanes, ZPixmap);
            guchar *pixels = gdk_pixbuf_get_pixels(pixbuf);

            if (image) {
                //unsigned char *pix = image->data;
                for (int r = 0; r < height; r++) {
                    for (int c = 0; c < width; c++) {
                        unsigned int pix = XGetPixel(image, c, r);
                        pixels[c * 4 + 3] = (unsigned char)(pix ? 255 : 0);
                    }
                    pixels += gdk_pixbuf_get_rowstride(pixbuf);
                    //pix += image->bytes_per_line;
                }
                XDestroyImage(image);
            }
        }

        image.init(new YImageGDK(width,
                                 height,
                                 pixbuf));
    }
    return image;
}

ref<YImage> YImage::createFromIconProperty(long *prop_pixels,
                                           int width, int height)
{
    ref<YImage> image;
    GdkPixbuf *pixbuf =
        gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8,
                       width, height);

    if (!pixbuf)
        return null;

    guchar *pixels = gdk_pixbuf_get_pixels(pixbuf);

    for (int r = 0; r < height; r++) {
        for (int c = 0; c < width; c++) {
            unsigned long pix =
                prop_pixels[c + r * width];
            pixels[c * 4 + 2] = (unsigned char)(pix & 0xFF);
            pixels[c * 4 + 1] = (unsigned char)((pix >> 8) & 0xFF);
            pixels[c * 4] = (unsigned char)((pix >> 16) & 0xFF);
            pixels[c * 4 + 3] = (unsigned char)((pix >> 24) & 0xFF);
        }
        pixels += gdk_pixbuf_get_rowstride(pixbuf);
    }
    image.init(new YImageGDK(width,
                             height,
                             pixbuf));
    return image;
}

ref<YImage> YImage::createFromPixmapAndMaskScaled(Pixmap pix, Pixmap mask,
                                                  int width, int height,
                                                  int nw, int nh)
{
    ref<YImage> image = createFromPixmapAndMask(pix, mask, width, height);
    if (image != null)
        image = image->scale(nw, nh);
    return image;
}

ref<YPixmap> YImageGDK::renderToPixmap() {
    Pixmap pixmap = None, mask = None;
    gdk_pixbuf_xlib_render_pixmap_and_mask(fPixbuf, &pixmap, &mask, 128);

    return createPixmap(pixmap, mask,
                        gdk_pixbuf_get_width(fPixbuf),
                        gdk_pixbuf_get_height(fPixbuf));
}

ref<YPixmap> YImage::createPixmap(Pixmap pixmap, Pixmap mask, int w, int h) {
    ref<YPixmap> n;

    n.init(new YPixmap(pixmap, mask, w, h));
    return n;
}

void YImageGDK::draw(Graphics &g, int dx, int dy) {
#if 1
    composite(g, 0, 0, width(), height(), dx, dy);
#else
    gdk_pixbuf_xlib_render_to_drawable_alpha(fPixbuf, g.drawable(), //g.handleX(),
                                             0, 0, dx, dy, width(), height(),
                                             GDK_PIXBUF_ALPHA_FULL,
                                             128,
                                             XLIB_RGB_DITHER_NORMAL, 0, 0);
#endif
}

void YImageGDK::draw(Graphics &g, int x, int y, int w, int h, int dx, int dy) {
#if 1
    composite(g, x, y, w, h, dx, dy);
#else
    gdk_pixbuf_xlib_render_to_drawable_alpha(fPixbuf, g.drawable(), //g.handleX(),
                                             x, y, dx, dy, w, h,
                                             GDK_PIXBUF_ALPHA_BILEVEL, 128,
                                             XLIB_RGB_DITHER_NORMAL, 0, 0);
#endif
}

void YImageGDK::composite(Graphics &g, int x, int y, int w, int h, int dx, int dy) {

    //MSG(("composite -- %d %d %d %d | %d %d", x, y, w, h, dx, dy));
    if (g.xorigin() > dx) {
        w -= g.xorigin() - dx;
        x += g.xorigin() - dx;
        dx = g.xorigin();
    }
    if (w <= 0)
        return;
    if (g.yorigin() > dy) {
        h -= g.yorigin() - dy;
        y += g.yorigin() - dy;
        dy = g.yorigin();
    }
    if (h <= 0)
        return;
    if (dx + w > g.xorigin() + g.rwidth())
        w = g.xorigin() + g.rwidth() - dx;
    if (w <= 0)
        return;
    if (dy + h > g.yorigin() + g.rheight())
        h = g.yorigin() + g.rheight() - dy;
    if (h <= 0)
        return;

    //MSG(("composite ++ %d %d %d %d | %d %d", x, y, w, h, dx, dy));
    GdkPixbuf *pixbuf =
        gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, w, h);
    gdk_pixbuf_xlib_get_from_drawable(pixbuf,
                                      g.drawable(),
                                      xapp->colormap(),
                                      xapp->visual(),
                                      dx - g.xorigin(), dy - g.yorigin(), 0, 0, w, h);
    gdk_pixbuf_composite(fPixbuf, pixbuf,
                         0, 0, w, h,
                         -x, -y, 1.0, 1.0,
                         GDK_INTERP_BILINEAR, 255);
    gdk_pixbuf_xlib_render_to_drawable(pixbuf, g.drawable(), g.handleX(),
                                             0, 0, dx - g.xorigin(), dy - g.yorigin(), w, h,
//                                             GDK_PIXBUF_ALPHA_BILEVEL, 128,
                                             XLIB_RGB_DITHER_NONE, 0, 0);
    g_object_unref(G_OBJECT(pixbuf));
}

void image_init() {
    xlib_rgb_init(xapp->display(), ScreenOfDisplay(xapp->display(), xapp->screen()));
    gdk_pixbuf_xlib_init(xapp->display(), xapp->screen());
}

#endif
<commit_msg>fix #93 with patch from @roms2000<commit_after>#include "config.h"

#ifdef CONFIG_GDK_PIXBUF_XLIB

#include "yimage.h"
#include "yxapp.h"
#include "ypixbuf.h"

extern "C" {
#include <gdk-pixbuf-xlib/gdk-pixbuf-xlib.h>
}

class YImageGDK: public YImage {
public:
    YImageGDK(int width, int height, GdkPixbuf *pixbuf): YImage(width, height) {
        fPixbuf = pixbuf;
    }
    virtual ~YImageGDK() {
        g_object_unref(G_OBJECT(fPixbuf));
    }
    virtual ref<YPixmap> renderToPixmap();
    virtual ref<YImage> scale(int width, int height);
    virtual void draw(Graphics &g, int dx, int dy);
    virtual void draw(Graphics &g, int x, int y, int w, int h, int dx, int dy);
    virtual void composite(Graphics &g, int x, int y, int w, int h, int dx, int dy);
    virtual bool valid() const { return fPixbuf != 0; }
private:
    GdkPixbuf *fPixbuf;
};

ref<YImage> YImage::create(int width, int height) {
    ref<YImage> image;
    GdkPixbuf *pixbuf =
        gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, width, height);
    if (pixbuf != NULL) {
        image.init(new YImageGDK(width, height, pixbuf));
    }
    return image;
}

ref<YImage> YImage::load(upath filename) {
    ref<YImage> image;
    GError *gerror = 0;
    GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(cstring(filename.path()).c_str(), &gerror);

    if (pixbuf != NULL) {
        image.init(new YImageGDK(gdk_pixbuf_get_width(pixbuf),
                                 gdk_pixbuf_get_height(pixbuf),
                                 pixbuf));
    }
    return image;
}

ref<YImage> YImageGDK::scale(int w, int h) {
    ref<YImage> image;
    GdkPixbuf *pixbuf = 0;
    bool alpha = gdk_pixbuf_get_has_alpha(fPixbuf);
#if 0
    pixbuf = gdk_pixbuf_scale_simple(fPixbuf,
                                     w, h,
                                     GDK_INTERP_BILINEAR);
#else
    pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, alpha, 8, w, h);

    pixbuf_scale(gdk_pixbuf_get_pixels(fPixbuf),
                 gdk_pixbuf_get_rowstride(fPixbuf),
                 gdk_pixbuf_get_width(fPixbuf),
                 gdk_pixbuf_get_height(fPixbuf),
                 gdk_pixbuf_get_pixels(pixbuf),
                 gdk_pixbuf_get_rowstride(pixbuf),
                 gdk_pixbuf_get_width(pixbuf),
                 gdk_pixbuf_get_height(pixbuf),
                 alpha);
#endif

    if (pixbuf != NULL) {
        image.init(new YImageGDK(w, h, pixbuf));
    }

    return image;
}

ref<YImage> YImage::createFromPixmap(ref<YPixmap> pixmap) {
    return createFromPixmapAndMask(pixmap->pixmap(),
                                   pixmap->mask(),
                                   pixmap->width(),
                                   pixmap->height());
}

ref<YImage> YImage::createFromPixmapAndMask(Pixmap pixmap, Pixmap mask,
                                            int width, int height)
{
    ref<YImage> image;
    GdkPixbuf *pixbuf =
        gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8,
                       width, height);

    if (pixbuf) {
        pixbuf =
            gdk_pixbuf_xlib_get_from_drawable(pixbuf,
                                              pixmap,
                                              xlib_rgb_get_cmap(),
                                              xlib_rgb_get_visual(),
                                              0, 0,
                                              0, 0,
                                              width,
                                              height);

        if (mask != None) {
            XImage *image = XGetImage(xapp->display(), mask,
                                      0, 0, width, height,
                                      AllPlanes, ZPixmap);
            guchar *pixels = gdk_pixbuf_get_pixels(pixbuf);

            if (image) {
                //unsigned char *pix = image->data;
                for (int r = 0; r < height; r++) {
                    for (int c = 0; c < width; c++) {
                        unsigned int pix = XGetPixel(image, c, r);
                        pixels[c * 4 + 3] = (unsigned char)(pix ? 255 : 0);
                    }
                    pixels += gdk_pixbuf_get_rowstride(pixbuf);
                    //pix += image->bytes_per_line;
                }
                XDestroyImage(image);
            }
        }

        image.init(new YImageGDK(width,
                                 height,
                                 pixbuf));
    }
    return image;
}

ref<YImage> YImage::createFromIconProperty(long *prop_pixels,
                                           int width, int height)
{
    ref<YImage> image;
    GdkPixbuf *pixbuf =
        gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8,
                       width, height);

    if (!pixbuf)
        return null;

    guchar *pixels = gdk_pixbuf_get_pixels(pixbuf);

    for (int r = 0; r < height; r++) {
        for (int c = 0; c < width; c++) {
            unsigned long pix =
                prop_pixels[c + r * width];
            pixels[c * 4 + 2] = (unsigned char)(pix & 0xFF);
            pixels[c * 4 + 1] = (unsigned char)((pix >> 8) & 0xFF);
            pixels[c * 4] = (unsigned char)((pix >> 16) & 0xFF);
            pixels[c * 4 + 3] = (unsigned char)((pix >> 24) & 0xFF);
        }
        pixels += gdk_pixbuf_get_rowstride(pixbuf);
    }
    image.init(new YImageGDK(width,
                             height,
                             pixbuf));
    return image;
}

ref<YImage> YImage::createFromPixmapAndMaskScaled(Pixmap pix, Pixmap mask,
                                                  int width, int height,
                                                  int nw, int nh)
{
    ref<YImage> image = createFromPixmapAndMask(pix, mask, width, height);
    if (image != null)
        image = image->scale(nw, nh);
    return image;
}

ref<YPixmap> YImageGDK::renderToPixmap() {
    Pixmap pixmap = None, mask = None;
    gdk_pixbuf_xlib_render_pixmap_and_mask(fPixbuf, &pixmap, &mask, 128);

    return createPixmap(pixmap, mask,
                        gdk_pixbuf_get_width(fPixbuf),
                        gdk_pixbuf_get_height(fPixbuf));
}

ref<YPixmap> YImage::createPixmap(Pixmap pixmap, Pixmap mask, int w, int h) {
    ref<YPixmap> n;

    n.init(new YPixmap(pixmap, mask, w, h));
    return n;
}

void YImageGDK::draw(Graphics &g, int dx, int dy) {
#if 1
    composite(g, 0, 0, width(), height(), dx, dy);
#else
    gdk_pixbuf_xlib_render_to_drawable_alpha(fPixbuf, g.drawable(), //g.handleX(),
                                             0, 0, dx, dy, width(), height(),
                                             GDK_PIXBUF_ALPHA_FULL,
                                             128,
                                             XLIB_RGB_DITHER_NORMAL, 0, 0);
#endif
}

void YImageGDK::draw(Graphics &g, int x, int y, int w, int h, int dx, int dy) {
#if 1
    composite(g, x, y, w, h, dx, dy);
#else
    gdk_pixbuf_xlib_render_to_drawable_alpha(fPixbuf, g.drawable(), //g.handleX(),
                                             x, y, dx, dy, w, h,
                                             GDK_PIXBUF_ALPHA_BILEVEL, 128,
                                             XLIB_RGB_DITHER_NORMAL, 0, 0);
#endif
}

void YImageGDK::composite(Graphics &g, int x, int y, int w, int h, int dx, int dy) {

    //MSG(("composite -- %d %d %d %d | %d %d", x, y, w, h, dx, dy));
    if (g.xorigin() > dx) {
        w -= g.xorigin() - dx;
        x += g.xorigin() - dx;
        dx = g.xorigin();
    }
    if (w <= 0)
        return;
    if (g.yorigin() > dy) {
        h -= g.yorigin() - dy;
        y += g.yorigin() - dy;
        dy = g.yorigin();
    }
    if (h <= 0)
        return;
    if (dx + w > g.xorigin() + g.rwidth())
        w = g.xorigin() + g.rwidth() - dx;
    if (w <= 0)
        return;
    if (dy + h > g.yorigin() + g.rheight())
        h = g.yorigin() + g.rheight() - dy;
    if (h <= 0)
        return;

    //MSG(("composite ++ %d %d %d %d | %d %d", x, y, w, h, dx, dy));
    GdkPixbuf *pixbuf =
        gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, w, h);
    gdk_pixbuf_xlib_get_from_drawable(pixbuf,
                                      g.drawable(),
                                      xapp->colormap(),
                                      xapp->visual(),
                                      dx - g.xorigin(), dy - g.yorigin(), 0, 0, w, h);
    gdk_pixbuf_composite(fPixbuf, pixbuf,
                         0, 0, w, h,
                         -x, -y, 1.0, 1.0,
                         GDK_INTERP_BILINEAR, 255);
    gdk_pixbuf_xlib_render_to_drawable(pixbuf, g.drawable(), g.handleX(),
                                             0, 0, dx - g.xorigin(), dy - g.yorigin(), w, h,
//                                             GDK_PIXBUF_ALPHA_BILEVEL, 128,
                                             XLIB_RGB_DITHER_NONE, 0, 0);
    g_object_unref(G_OBJECT(pixbuf));
}

void image_init() {
#if (GLIB_MAJOR_VERSION <= 2 && GLIB_MINOR_VERSION < 36 && GLIB_MICRO_VERSION <= 0)
    g_type_init();
#endif
    xlib_rgb_init(xapp->display(), ScreenOfDisplay(xapp->display(), xapp->screen()));
    gdk_pixbuf_xlib_init(xapp->display(), xapp->screen());
}

#endif
<|endoftext|>