diff --git a/lib/sane.rb b/lib/sane.rb index 53727c0..061b3e6 100644 --- a/lib/sane.rb +++ b/lib/sane.rb @@ -1,53 +1,75 @@ class Sane include Singleton - class << self - extend Forwardable - delegate [:init, :exit, :get_devices, :open, :close, :get_option_descriptor, :get_option, :set_option, :strstatus] => :instance + attr_reader :version + + def self.open + instance.send(:init) + yield instance + ensure + instance.send(:exit) end - def initialize - init + def devices + get_devices.map { |device| Device.new(device) } + end + + private + + def not_initialized? + version.nil? + end + + def initialized? + !not_initialized? end def init + ensure_not_initialized! version_code = FFI::MemoryPointer.new(:int) check_status!(API.sane_init(version_code, FFI::Pointer::NULL)) - version_code.read_int + @version = version_code.read_int end def exit + ensure_initialized! API.sane_exit + @version = nil end def get_devices - devices_pointer = FFI::MemoryPointer.new(:pointer) - check_status!(API.sane_get_devices(devices_pointer, 0)) - devices = devices_pointer.read_pointer + ensure_initialized! + devices_pointer_pointer = FFI::MemoryPointer.new(:pointer) + check_status!(API.sane_get_devices(devices_pointer_pointer, 0)) + devices_pointer = devices_pointer_pointer.read_pointer [].tap do |result| - until devices.read_pointer.null? - result << API::Device.new(devices.read_pointer) - devices += FFI.type_size(:pointer) + until devices_pointer.read_pointer.null? + result << API::Device.new(devices_pointer.read_pointer).to_hash + devices_pointer += FFI.type_size(:pointer) end end end def open(device_name) + ensure_initialized! device_handle_pointer = FFI::MemoryPointer.new(:pointer) check_status!(API.sane_open(device_name, device_handle_pointer)) device_handle_pointer.read_pointer end def close(device_handle) + ensure_initialized! API.sane_close(device_handle) end def get_option_descriptor(device_handle, option) + ensure_initialized! result = API.sane_get_option_descriptor(device_handle, option) - API::OptionDescriptor.new(result) + API::OptionDescriptor.new(result).to_hash end def get_option(device_handle, option) + ensure_initialized! descriptor = get_option_descriptor(device_handle, option) case descriptor[:type] @@ -72,6 +94,7 @@ class Sane end def set_option(device_handle, option, value) + ensure_initialized! descriptor = get_option_descriptor(device_handle, option) case descriptor[:type] @@ -97,13 +120,40 @@ class Sane end end + def start(handle) + ensure_initialized! + API.sane_start(handle) + end + + def read(handle, size = 64 * 1024) + ensure_initialized! + data_pointer = FFI::MemoryPointer.new(:char, size) + length_pointer = FFI::MemoryPointer.new(:int) + check_status!(API.sane_read(handle, data_pointer, size, length_pointer)) + data_pointer.read_string(length_pointer.read_int) + end + def strstatus(status) + ensure_initialized! API.sane_strstatus(status) end - private + def get_parameters(handle) + ensure_initialized! + parameters_pointer = FFI::MemoryPointer.new(API::Parameters.size) + check_status!(API.sane_get_parameters(handle, parameters_pointer)) + API::Parameters.new(parameters_pointer).to_hash + end + + def ensure_not_initialized! + raise "SANE library is already initialized" if initialized? + end + + def ensure_initialized! + raise "SANE library is not initialized" if not_initialized? + end def check_status!(status) - raise Error.new(strstatus(status), status) if status != :good + raise Error.new(strstatus(status), status) unless status == :good end end diff --git a/lib/sane/api.rb b/lib/sane/api.rb index aaef9f4..99d001a 100644 --- a/lib/sane/api.rb +++ b/lib/sane/api.rb @@ -12,6 +12,8 @@ class Sane class Device < FFI::Struct layout :name, :string, :vendor, :string, :model, :string, :type, :string + + def to_hash; {name: self[:name], vendor: self[:vendor], model: self[:model], type: self[:type]} end end class OptionDescriptor < FFI::Struct @@ -19,10 +21,14 @@ class Sane layout :string_list, :pointer, :word_list, :pointer, :range, :pointer end layout :name, :string, :title, :string, :desc, :string, :type, :value_type, :unit, :unit, :size, :int, :cap, :int, :constraint_type, ConstraintType + + def to_hash; {name: self[:name], title: self[:title], desc: self[:desc], type: self[:type], unit: self[:unit], size: self[:size], cap: self[:cap]} end end class Parameters < FFI::Struct layout :format, :frame, :last_frame, :int, :bytes_per_line, :int, :pixels_per_line, :int, :lines, :int, :depth, :int + + def to_hash; {format: self[:format], last_frame: self[:last_frame], bytes_per_line: self[:bytes_per_line], pixels_per_line: self[:pixels_per_line], lines: self[:lines], depth: self[:depth]} end end # extern SANE_Status sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize); diff --git a/lib/sane/device.rb b/lib/sane/device.rb index 94ebefe..757f5fa 100644 --- a/lib/sane/device.rb +++ b/lib/sane/device.rb @@ -1,29 +1,108 @@ class Sane class Device - def initialize(name) - @name = name + def initialize(options) + @name = options[:name] + @vendor = options[:vendor] + @model = options[:model] + @type = options[:type] @handle = nil end - def open - @handle = Sane.open(@name) if closed? - if block_given? - yield self - close - end - end - def closed? @handle.nil? end - def opened? + def open? !closed? end + def open + ensure_closed! + @handle = Sane.instance.send(:open, @name) + if block_given? + begin + yield(self) + ensure + close + end + end + end + def close - Sane.close(@handle) if opened? + ensure_open! + Sane.instance.send(:close, @handle) @handle = nil end + + def start + ensure_open! + Sane.instance.start(@handle) + end + + def read + ensure_open! + Sane.instance.send(:read, @handle) + end + + def option_count + ensure_open! + Sane.instance.send(:get_option, @handle, 0) + end + + def parameters + ensure_open! + Sane.instance.send(:get_parameters, @handle) + end + + def [](option) + ensure_open! + Sane.instance.send(:get_option, @handle, option_lookup(option)) + end + + def []=(option, value) + ensure_open! + Sane.instance.send(:set_option, @handle, option_lookup(option), value) + end + + def option_descriptors + option_count.times.map { |i| Sane.instance.send(:get_option_descriptor, @handle, i) } + end + + def option_names + option_descriptors.map { |option| option[:name] } + end + + def option_values + option_count.times.map do |i| + begin + self[i] + rescue Error + nil # we can't get values of some options, ignore them + end + end + end + + def options + {}.tap { |hash| option_count.times { |i| hash[option_names[i]] = option_values[i] } } + end + + def option_lookup(option_name) + return option_name if (0..option_count).include?(option_name) + option_descriptors.index { |option| option[:name] == option_name.to_s } or raise ArgumentError, "Option not found: #{option_name}" + end + + def describe(option) + option_descriptors[option_lookup(option)] + end + + private + + def ensure_closed! + raise "Device is already open" if open? + end + + def ensure_open! + raise "Device is closed" if closed? + end end end