Class: WineOLE::Client
- Inherits:
-
Object
- Object
- WineOLE::Client
- Defined in:
- lib/wineole/client.rb
Constant Summary collapse
- WINDOWS =
!(RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/).nil?
- ARCH_TRIPLES =
{ 'x86_64' => 'x86_64-pc-windows-gnu', 'i386' => 'i686-pc-windows-gnu', 'i686' => 'i686-pc-windows-gnu', }.freeze
- DEFAULT_SPAWNER =
lambda do |port| # wineole-bridge.exe is a native Windows binary either way -- `wine` # is only needed to run it on a non-Windows host. On Windows itself # there is no `wine` command, so running it under one would fail # immediately (Errno::ENOENT) rather than run natively as it should. # The null device path is also platform-specific ('/dev/null' does # not exist on Windows). command = WINDOWS ? [default_bridge_path] : ['wine', default_bridge_path] null_device = WINDOWS ? 'NUL' : '/dev/null' Process.spawn(*command, port.to_s, %i[out err] => null_device) end
Class Method Summary collapse
- .bridge_path_for_arch(host_cpu) ⇒ Object
- .default_bridge_path ⇒ Object
- .default_lockfile(port) ⇒ Object
-
.finalizer(socket) ⇒ Object
A proc to close over the raw socket for ObjectSpace.define_finalizer.
- .open(host: '127.0.0.1', port: 47800, spawner: DEFAULT_SPAWNER, lockfile: nil, timeout: 15, token: nil) ⇒ Object
Instance Method Summary collapse
- #call(method, params = {}) ⇒ Object
- #close ⇒ Object
- #connect(class_name) ⇒ Object
- #connect_or_create(class_name) ⇒ Object
- #create(class_name) ⇒ Object
-
#initialize(socket) ⇒ Client
constructor
A new instance of Client.
Constructor Details
#initialize(socket) ⇒ Client
Returns a new instance of Client.
115 116 117 118 119 120 |
# File 'lib/wineole/client.rb', line 115 def initialize(socket) @socket = socket @next_id = 0 @mutex = Mutex.new ObjectSpace.define_finalizer(self, self.class.finalizer(socket)) end |
Class Method Details
.bridge_path_for_arch(host_cpu) ⇒ Object
18 19 20 21 22 23 24 25 |
# File 'lib/wineole/client.rb', line 18 def self.bridge_path_for_arch(host_cpu) triple = ARCH_TRIPLES[host_cpu] || (ARCH_TRIPLES['i686'] if host_cpu =~ /^i.86$/) if triple.nil? raise Error, "no prebuilt wineole-bridge binary for host architecture #{host_cpu.inspect} " \ "(available: #{ARCH_TRIPLES.values.uniq.join(', ')})" end File.("../../wineole-bridge-dist/#{triple}/wineole-bridge.exe", __dir__) end |
.default_bridge_path ⇒ Object
27 28 29 |
# File 'lib/wineole/client.rb', line 27 def self.default_bridge_path bridge_path_for_arch(RbConfig::CONFIG['host_cpu']) end |
.default_lockfile(port) ⇒ Object
43 44 45 46 47 48 49 50 |
# File 'lib/wineole/client.rb', line 43 def self.default_lockfile(port) # The lock is per-port, not global: two bridges on two ports are # entirely independent, and a single shared lock would make a client # starting one wait on a client starting the other (design doc ยง7.1 # step 2). Dir.tmpdir rather than a hardcoded '/tmp' resolves # correctly on Windows (%TEMP%) as well as every POSIX platform. File.join(Dir.tmpdir, "wineole-bridge.#{port}.lock") end |
.finalizer(socket) ⇒ Object
A proc to close over the raw socket for ObjectSpace.define_finalizer.
It deliberately does NOT close over self (the Client instance) --
capturing the instance being finalized would keep it permanently
reachable through ObjectSpace's own finalizer table, and it could
never actually be collected. Capturing only the socket avoids this.
105 106 107 108 109 110 111 112 113 |
# File 'lib/wineole/client.rb', line 105 def self.finalizer(socket) proc do begin socket.close rescue StandardError nil end end end |
.open(host: '127.0.0.1', port: 47800, spawner: DEFAULT_SPAWNER, lockfile: nil, timeout: 15, token: nil) ⇒ Object
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/wineole/client.rb', line 52 def self.open(host: '127.0.0.1', port: 47800, spawner: DEFAULT_SPAWNER, lockfile: nil, timeout: 15, token: nil) lockfile ||= default_lockfile(port) socket = try_connect(host, port) return handshake(new(socket), token) if socket File.open(lockfile, File::CREAT | File::RDWR, 0o644) do |lock| lock.flock(File::LOCK_EX) socket = try_connect(host, port) return handshake(new(socket), token) if socket spawner.call(port) deadline = Time.now + timeout loop do socket = try_connect(host, port) return handshake(new(socket), token) if socket raise Error, "wineole-bridge did not start within #{timeout}s" if Time.now > deadline sleep 0.2 end end end |
Instance Method Details
#call(method, params = {}) ⇒ Object
122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/wineole/client.rb', line 122 def call(method, params = {}) @mutex.synchronize do id = (@next_id += 1) @socket.write(JSON.generate({id: id, method: method, params: params}) + "\n") line = @socket.gets raise ProtocolError, 'connection closed' if line.nil? response = JSON.parse(line) unless response['id'] == id raise ProtocolError, "id mismatch: expected #{id}, got #{response['id']}" end raise RemoteError.new(response['error']['class'], response['error']['message']) if response['error'] response['result'] end end |
#close ⇒ Object
137 138 139 |
# File 'lib/wineole/client.rb', line 137 def close @socket.close end |
#connect(class_name) ⇒ Object
145 146 147 |
# File 'lib/wineole/client.rb', line 145 def connect(class_name) Proxy.connect(class_name, self) end |
#connect_or_create(class_name) ⇒ Object
149 150 151 |
# File 'lib/wineole/client.rb', line 149 def connect_or_create(class_name) Proxy.connect_or_create(class_name, self) end |