ruby-sfml 3.0.0.0 → 3.0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,8 +15,23 @@ module SFML
15
15
  # through the right CSFML draw function for whichever target they're
16
16
  # being rendered to.
17
17
  module RenderTarget
18
- def clear(color = Color::BLACK)
19
- _csfml(:clear, @handle, color.to_native)
18
+ # Clear the target's colour buffer (and optionally the stencil
19
+ # buffer in the same call). Pass `stencil:` to clear the stencil
20
+ # buffer to the given integer; pass only `stencil:` to clear the
21
+ # stencil without touching colour.
22
+ #
23
+ # target.clear # default black
24
+ # target.clear(SFML::Color.cornflower_blue) # only colour
25
+ # target.clear(SFML::Color.black, stencil: 0) # both
26
+ # target.clear(stencil: 0) # only stencil
27
+ def clear(color = nil, stencil: nil)
28
+ if stencil && color
29
+ _csfml(:clearColorAndStencil, @handle, color.to_native, _stencil_value(stencil))
30
+ elsif stencil
31
+ _csfml(:clearStencil, @handle, _stencil_value(stencil))
32
+ else
33
+ _csfml(:clear, @handle, (color || Color::BLACK).to_native)
34
+ end
20
35
  self
21
36
  end
22
37
 
@@ -141,6 +156,12 @@ module SFML
141
156
  def _csfml(suffix, *args)
142
157
  C::Graphics.public_send(:"#{self.class::CSFML_PREFIX}_#{suffix}", *args)
143
158
  end
159
+
160
+ def _stencil_value(int)
161
+ v = C::Graphics::StencilValue.new
162
+ v[:value] = Integer(int)
163
+ v
164
+ end
144
165
  end
145
166
  end
146
167
  end
@@ -112,6 +112,52 @@ module SFML
112
112
  Vector2.new(v[:x], v[:y])
113
113
  end
114
114
 
115
+ # Replace the window's title-bar / taskbar icon with the pixels from
116
+ # the given SFML::Image. The OS scales it as needed; 32×32 RGBA
117
+ # is the typical sweet spot.
118
+ def icon=(image)
119
+ raise ArgumentError, "RenderWindow#icon= requires a SFML::Image" unless image.is_a?(Image)
120
+
121
+ size = C::System::Vector2u.new
122
+ size[:x] = image.width
123
+ size[:y] = image.height
124
+ C::Graphics.sfRenderWindow_setIcon(@handle, size, C::Graphics.sfImage_getPixelsPtr(image.handle))
125
+ end
126
+
127
+ # Constrain user-driven resizes. Accepts a [w, h] Array, a Vector2,
128
+ # or nil to clear the limit. When set, the OS won't let the user
129
+ # drag the window smaller (or larger) than this — programmatic
130
+ # `size=` is not affected.
131
+ def minimum_size=(value)
132
+ C::Graphics.sfRenderWindow_setMinimumSize(@handle, _vec2u_or_nil(value))
133
+ end
134
+
135
+ def maximum_size=(value)
136
+ C::Graphics.sfRenderWindow_setMaximumSize(@handle, _vec2u_or_nil(value))
137
+ end
138
+
139
+ # OS-specific native handle for the underlying window — `HWND` on
140
+ # Windows, `NSView*` on macOS, X11 `Window` xid on Linux.
141
+ def native_handle
142
+ C::Graphics.sfRenderWindow_getNativeHandle(@handle)
143
+ end
144
+
145
+ # Wrap an existing OS-level window. `handle` is a platform native
146
+ # handle (Integer address or FFI::Pointer). Useful for embedding
147
+ # the renderer inside another framework's window (Qt, Gtk, raw
148
+ # Win32, NSView).
149
+ def self.from_handle(handle)
150
+ ptr = handle.is_a?(FFI::Pointer) ? handle : FFI::Pointer.new(:void, Integer(handle))
151
+ raw = C::Graphics.sfRenderWindow_createFromHandle(ptr, nil)
152
+ raise Error, "sfRenderWindow_createFromHandle returned NULL" if raw.null?
153
+
154
+ win = allocate
155
+ win.instance_variable_set(:@handle,
156
+ FFI::AutoPointer.new(raw, C::Graphics.method(:sfRenderWindow_destroy)))
157
+ win.instance_variable_set(:@event_buffer, C::Window::Event.new)
158
+ win
159
+ end
160
+
115
161
  # Convenience driver loop. Yields the per-frame delta (SFML::Time) and
116
162
  # auto-pumps events + display. The block is responsible for #clear and
117
163
  # any drawing.
@@ -136,6 +182,15 @@ module SFML
136
182
 
137
183
  private
138
184
 
185
+ def _vec2u_or_nil(value)
186
+ return nil if value.nil?
187
+
188
+ vec = value.is_a?(Vector2) ? value : Vector2.new(*value)
189
+ v = C::System::Vector2u.new
190
+ v[:x] = Integer(vec.x); v[:y] = Integer(vec.y)
191
+ v
192
+ end
193
+
139
194
  def parse_args(args)
140
195
  case args.length
141
196
  when 2
@@ -28,8 +28,17 @@ module SFML
28
28
  # [a, b, c] → vec3
29
29
  # [a, b, c, d] → vec4
30
30
  #
31
- # Need an int / bvec / matrix / array uniform? Use the explicit setters
32
- # (#set_int, #set_ivec2, etc.) they exist for completeness.
31
+ # Array uniforms (`uniform vec2 positions[8];` and friends):
32
+ # [[x, y], [x, y], ...] → vec2[]
33
+ # [[x, y, z], ...] → vec3[]
34
+ # [[x, y, z, w], ...] → vec4[]
35
+ # [Vector2[a, b], ...] → vec2[] (also accepts Vector2 / Vector3)
36
+ #
37
+ # Float arrays (`uniform float weights[N];`) are ambiguous with vec3
38
+ # at length 3, so use the explicit `#set_float_array` setter.
39
+ #
40
+ # Need an int / bvec / matrix uniform? Use `#set_int`, `#set_ivec2`,
41
+ # etc. — they exist for completeness.
33
42
  class Shader
34
43
  # Class-level: is GLSL available on the current GPU at all?
35
44
  def self.available?
@@ -85,16 +94,23 @@ module SFML
85
94
  when :current_texture
86
95
  C::Graphics.sfShader_setCurrentTextureUniform(@handle, n)
87
96
  when Array
88
- case value.length
89
- when 2 then self[name] = Vector2.new(*value)
90
- when 3 then self[name] = Vector3.new(*value)
91
- when 4
92
- v = C::Graphics::GlslVec4.new
93
- v[:x] = value[0].to_f; v[:y] = value[1].to_f
94
- v[:z] = value[2].to_f; v[:w] = value[3].to_f
95
- C::Graphics.sfShader_setVec4Uniform(@handle, n, v)
97
+ raise ArgumentError, "Shader uniform array must not be empty" if value.empty?
98
+
99
+ first = value.first
100
+ if first.is_a?(Array) || first.is_a?(Vector2) || first.is_a?(Vector3)
101
+ _set_vec_array_uniform(n, value)
96
102
  else
97
- raise ArgumentError, "Shader uniform array must be length 2, 3, or 4 (got #{value.length})"
103
+ case value.length
104
+ when 2 then self[name] = Vector2.new(*value)
105
+ when 3 then self[name] = Vector3.new(*value)
106
+ when 4
107
+ v = C::Graphics::GlslVec4.new
108
+ v[:x] = value[0].to_f; v[:y] = value[1].to_f
109
+ v[:z] = value[2].to_f; v[:w] = value[3].to_f
110
+ C::Graphics.sfShader_setVec4Uniform(@handle, n, v)
111
+ else
112
+ raise ArgumentError, "Shader uniform array must be length 2, 3, or 4 (got #{value.length})"
113
+ end
98
114
  end
99
115
  else
100
116
  raise ArgumentError,
@@ -114,8 +130,52 @@ module SFML
114
130
  C::Graphics.sfShader_setIvec2Uniform(@handle, name.to_s, v)
115
131
  end
116
132
 
133
+ # Set a `uniform float arr[N];` from a plain Ruby array of numbers.
134
+ # Float arrays can't be inferred via `[]=` because they'd collide
135
+ # with the vec3 case at length 3.
136
+ def set_float_array(name, values)
137
+ buf = FFI::MemoryPointer.new(:float, values.length)
138
+ buf.write_array_of_float(values.map(&:to_f))
139
+ C::Graphics.sfShader_setFloatUniformArray(@handle, name.to_s, buf, values.length)
140
+ end
141
+
117
142
  attr_reader :handle # :nodoc:
118
143
 
144
+ private
145
+
146
+ # Detect the inner length (2/3/4), pack a contiguous float buffer,
147
+ # and dispatch to the matching CSFML setVec*UniformArray.
148
+ def _set_vec_array_uniform(name, elements)
149
+ raise ArgumentError, "uniform array must not be empty" if elements.empty?
150
+
151
+ flat = elements.flat_map do |el|
152
+ case el
153
+ when Vector2 then [el.x.to_f, el.y.to_f]
154
+ when Vector3 then [el.x.to_f, el.y.to_f, el.z.to_f]
155
+ when Array then el.map(&:to_f)
156
+ else
157
+ raise ArgumentError, "uniform array element must be Array/Vector2/Vector3 (got #{el.class})"
158
+ end
159
+ end
160
+
161
+ stride = flat.length / elements.length
162
+ raise ArgumentError, "uniform array elements must all be the same length" \
163
+ unless flat.length == stride * elements.length
164
+
165
+ buf = FFI::MemoryPointer.new(:float, flat.length)
166
+ buf.write_array_of_float(flat)
167
+
168
+ case stride
169
+ when 2 then C::Graphics.sfShader_setVec2UniformArray(@handle, name, buf, elements.length)
170
+ when 3 then C::Graphics.sfShader_setVec3UniformArray(@handle, name, buf, elements.length)
171
+ when 4 then C::Graphics.sfShader_setVec4UniformArray(@handle, name, buf, elements.length)
172
+ else
173
+ raise ArgumentError, "uniform array elements must be length 2, 3, or 4 (got #{stride})"
174
+ end
175
+ end
176
+
177
+ public
178
+
119
179
  # @!visibility private
120
180
  def self._wrap(ptr)
121
181
  shader = allocate
@@ -0,0 +1,93 @@
1
+ module SFML
2
+ # How a draw call interacts with the stencil buffer. The classic
3
+ # use is masking — first you draw a "mask" shape that writes a
4
+ # value into the stencil buffer, then you draw the masked content
5
+ # with a comparison that only lets pixels through where the
6
+ # stencil matches.
7
+ #
8
+ # target.clear(SFML::Color.black, stencil: 0)
9
+ #
10
+ # # Phase 1 — write the mask shape into the stencil buffer.
11
+ # write = SFML::StencilMode.new(
12
+ # comparison: :always, update_operation: :replace, reference: 1,
13
+ # only_write_mask: true, # don't actually paint colour
14
+ # )
15
+ # target.draw(mask_circle, stencil_mode: write)
16
+ #
17
+ # # Phase 2 — draw the content; only pixels where stencil == 1
18
+ # # survive.
19
+ # read = SFML::StencilMode.new(
20
+ # comparison: :equal, update_operation: :keep, reference: 1,
21
+ # )
22
+ # target.draw(scene, stencil_mode: read)
23
+ #
24
+ # `stencilOnly` (`only_write_mask:`) is a niche flag — set it to
25
+ # true on the mask-write pass if you don't want the mask shape
26
+ # itself to be visible in the colour buffer.
27
+ class StencilMode
28
+ # Order matches sfStencilComparison in CSFML 3.
29
+ COMPARISONS = %i[never less less_equal greater greater_equal equal not_equal always].freeze
30
+ COMPARISON_INDEX = COMPARISONS.each_with_index.to_h.freeze
31
+
32
+ # Order matches sfStencilUpdateOperation in CSFML 3.
33
+ OPERATIONS = %i[keep zero replace increment decrement invert].freeze
34
+ OPERATION_INDEX = OPERATIONS.each_with_index.to_h.freeze
35
+
36
+ attr_reader :comparison, :update_operation, :reference, :mask, :only_write_mask
37
+
38
+ def initialize(comparison: :always, update_operation: :keep,
39
+ reference: 0, mask: 0xFFFFFFFF, only_write_mask: false)
40
+ raise ArgumentError, "Unknown stencil comparison: #{comparison.inspect}" \
41
+ unless COMPARISON_INDEX.key?(comparison)
42
+ raise ArgumentError, "Unknown stencil update_operation: #{update_operation.inspect}" \
43
+ unless OPERATION_INDEX.key?(update_operation)
44
+
45
+ @comparison = comparison
46
+ @update_operation = update_operation
47
+ @reference = Integer(reference)
48
+ @mask = Integer(mask)
49
+ @only_write_mask = !!only_write_mask
50
+ freeze
51
+ end
52
+
53
+ def ==(other)
54
+ other.is_a?(StencilMode) &&
55
+ comparison == other.comparison &&
56
+ update_operation == other.update_operation &&
57
+ reference == other.reference &&
58
+ mask == other.mask &&
59
+ only_write_mask == other.only_write_mask
60
+ end
61
+ alias eql? ==
62
+ def hash = [comparison, update_operation, reference, mask, only_write_mask].hash
63
+
64
+ def to_s
65
+ "StencilMode(#{comparison}, #{update_operation}, ref=#{reference}, " \
66
+ "mask=#{format('0x%08X', mask)}, only_write=#{only_write_mask})"
67
+ end
68
+ alias inspect to_s
69
+
70
+ # @!visibility private
71
+ # Write our fields into a CSFML StencilMode struct (typically a
72
+ # sub-struct of an existing RenderStates buffer).
73
+ def populate(struct)
74
+ struct[:comparison] = COMPARISON_INDEX[@comparison]
75
+ struct[:update_operation] = OPERATION_INDEX[@update_operation]
76
+ struct[:reference][:value] = @reference
77
+ struct[:mask][:value] = @mask
78
+ struct[:only_write_mask] = @only_write_mask
79
+ struct
80
+ end
81
+
82
+ # @!visibility private
83
+ def self.from_native(struct)
84
+ new(
85
+ comparison: COMPARISONS[struct[:comparison]],
86
+ update_operation: OPERATIONS[struct[:update_operation]],
87
+ reference: struct[:reference][:value],
88
+ mask: struct[:mask][:value],
89
+ only_write_mask: struct[:only_write_mask],
90
+ )
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,127 @@
1
+ module SFML
2
+ # GPU-resident vertex buffer. Same shape as VertexArray but the
3
+ # vertices live in video memory, so a draw call only ships an
4
+ # index/handle instead of re-uploading the geometry every frame.
5
+ # Useful for static meshes, large tilemaps, particle systems with
6
+ # mostly-static positions.
7
+ #
8
+ # buf = SFML::VertexBuffer.new(
9
+ # vertices,
10
+ # primitive_type: :triangles,
11
+ # usage: :static,
12
+ # )
13
+ # window.draw(buf)
14
+ #
15
+ # Update later (full or partial):
16
+ #
17
+ # buf.update(new_vertices) # full replace
18
+ # buf.update(some_vertices, offset: 32) # patch in place
19
+ #
20
+ # If the GPU doesn't support OpenGL vertex buffer objects,
21
+ # `SFML::VertexBuffer.available?` returns false and you should
22
+ # fall back to `VertexArray`.
23
+ class VertexBuffer
24
+ USAGES = C::Graphics::VERTEX_BUFFER_USAGES
25
+ USAGE_INDEX = USAGES.each_with_index.to_h.freeze
26
+
27
+ PRIMITIVE_TYPES = VertexArray::PRIMITIVE_TYPES
28
+ PRIMITIVE_INDEX = VertexArray::PRIMITIVE_INDEX
29
+
30
+ def self.available?
31
+ C::Graphics.sfVertexBuffer_isAvailable
32
+ end
33
+
34
+ # Build a buffer either from an Array of vertices or by giving an
35
+ # explicit `count:` to allocate empty (then fill via #update).
36
+ def initialize(vertices = nil, count: nil, primitive_type: :points, usage: :stream)
37
+ n = vertices ? vertices.length : Integer(count || 0)
38
+ raise ArgumentError, "give either `vertices` or `count:`" if n.zero? && vertices.nil?
39
+
40
+ ptype = PRIMITIVE_INDEX.fetch(primitive_type) do
41
+ raise ArgumentError,
42
+ "Unknown primitive type: #{primitive_type.inspect} " \
43
+ "(expected one of #{PRIMITIVE_TYPES.inspect})"
44
+ end
45
+ uidx = USAGE_INDEX.fetch(usage) do
46
+ raise ArgumentError,
47
+ "Unknown VertexBuffer usage: #{usage.inspect} " \
48
+ "(expected one of #{USAGES.inspect})"
49
+ end
50
+
51
+ ptr = C::Graphics.sfVertexBuffer_create(n, ptype, uidx)
52
+ raise Error, "sfVertexBuffer_create returned NULL " \
53
+ "(VBOs unavailable on this GPU?)" if ptr.null?
54
+ @handle = FFI::AutoPointer.new(ptr, C::Graphics.method(:sfVertexBuffer_destroy))
55
+
56
+ update(vertices) if vertices && !vertices.empty?
57
+ end
58
+
59
+ def count = C::Graphics.sfVertexBuffer_getVertexCount(@handle)
60
+ alias size count
61
+ alias length count
62
+
63
+ def primitive_type
64
+ PRIMITIVE_TYPES[C::Graphics.sfVertexBuffer_getPrimitiveType(@handle)] || :unknown
65
+ end
66
+
67
+ def primitive_type=(type)
68
+ idx = PRIMITIVE_INDEX.fetch(type) do
69
+ raise ArgumentError, "Unknown primitive type: #{type.inspect}"
70
+ end
71
+ C::Graphics.sfVertexBuffer_setPrimitiveType(@handle, idx)
72
+ end
73
+
74
+ def usage
75
+ USAGES[C::Graphics.sfVertexBuffer_getUsage(@handle)] || :unknown
76
+ end
77
+
78
+ def usage=(value)
79
+ idx = USAGE_INDEX.fetch(value) do
80
+ raise ArgumentError, "Unknown VertexBuffer usage: #{value.inspect}"
81
+ end
82
+ C::Graphics.sfVertexBuffer_setUsage(@handle, idx)
83
+ end
84
+
85
+ # Replace `vertices.length` slots starting at `offset`. Pass an
86
+ # array longer than the buffer to grow it (CSFML expands when
87
+ # offset==0 and length > current).
88
+ def update(vertices, offset: 0)
89
+ n = vertices.length
90
+ buf = FFI::MemoryPointer.new(C::Graphics::Vertex, n)
91
+ vertices.each_with_index do |v, i|
92
+ slot = C::Graphics::Vertex.new(buf + i * C::Graphics::Vertex.size)
93
+ slot[:position][:x] = v.position.x.to_f
94
+ slot[:position][:y] = v.position.y.to_f
95
+ slot[:color][:r] = v.color.r
96
+ slot[:color][:g] = v.color.g
97
+ slot[:color][:b] = v.color.b
98
+ slot[:color][:a] = v.color.a
99
+ slot[:tex_coords][:x] = v.tex_coords.x.to_f
100
+ slot[:tex_coords][:y] = v.tex_coords.y.to_f
101
+ end
102
+
103
+ ok = C::Graphics.sfVertexBuffer_update(@handle, buf, n, Integer(offset))
104
+ raise Error, "sfVertexBuffer_update failed " \
105
+ "(buffer too small or driver rejected the upload?)" unless ok
106
+ self
107
+ end
108
+
109
+ def native_handle = C::Graphics.sfVertexBuffer_getNativeHandle(@handle)
110
+
111
+ def draw_on(target, states_ptr = nil) # :nodoc:
112
+ target._draw_native(:VertexBuffer, @handle, states_ptr)
113
+ end
114
+
115
+ # Draw a slice of this buffer instead of the whole thing. Useful
116
+ # when you've packed several meshes back-to-back and want to
117
+ # render one at a time.
118
+ def draw_range_on(target, first, count, states_ptr = nil)
119
+ C::Graphics.public_send(
120
+ :"#{target.class::CSFML_PREFIX}_drawVertexBufferRange",
121
+ target.handle, @handle, Integer(first), Integer(count), states_ptr,
122
+ )
123
+ end
124
+
125
+ attr_reader :handle # :nodoc:
126
+ end
127
+ end
@@ -0,0 +1,184 @@
1
+ module SFML
2
+ module Network
3
+ # CSFML's FTP client. Useful for the rare game that needs to fetch
4
+ # extra content from a plain-FTP server. For anything modern,
5
+ # Ruby's stdlib `Net::FTP` (and the Net::FTP gem) is a much nicer
6
+ # tool — this binding exists for parity with CSFML.
7
+ #
8
+ # ftp = SFML::Network::Ftp.new
9
+ # ftp.connect("ftp.example.com").ok? #=> true
10
+ # ftp.login_anonymous.ok? #=> true
11
+ # ftp.directory_listing("/").names #=> ["pub", "incoming", ...]
12
+ # ftp.download("/pub/file.bin", "/tmp/")
13
+ # ftp.disconnect
14
+ #
15
+ # Each call returns a Response (or DirectoryResponse / ListingResponse
16
+ # for commands that return a path or a list). All responses expose
17
+ # `#ok?`, `#status` (Integer), `#status_symbol`, `#message`.
18
+ class Ftp
19
+ DEFAULT_PORT = 21
20
+ DEFAULT_TIMEOUT = SFML::Time.zero
21
+
22
+ # FTP status codes mapped to symbols. The transport-error ones
23
+ # at ≥ 1000 are SFML's, not RFC 959.
24
+ STATUS_NAMES = {
25
+ 110 => :restart_marker_reply, 120 => :service_ready_soon,
26
+ 125 => :data_connection_already_opened, 150 => :opening_data_connection,
27
+ 200 => :ok, 211 => :system_status, 212 => :directory_status,
28
+ 213 => :file_status, 214 => :help_message,
29
+ 215 => :system_type, 220 => :service_ready, 221 => :closing_connection,
30
+ 225 => :data_connection_opened, 226 => :closing_data_connection,
31
+ 227 => :entering_passive_mode, 230 => :logged_in,
32
+ 250 => :file_action_ok, 257 => :directory_ok,
33
+ 331 => :need_password, 332 => :need_account_to_log_in,
34
+ 350 => :need_information,
35
+ 421 => :service_unavailable, 425 => :data_connection_unavailable,
36
+ 426 => :transfer_aborted, 450 => :file_action_aborted,
37
+ 451 => :local_error, 452 => :insufficient_storage_space,
38
+ 500 => :command_unknown, 501 => :parameters_unknown,
39
+ 502 => :command_not_implemented, 503 => :bad_command_sequence,
40
+ 504 => :parameter_not_implemented, 530 => :not_logged_in,
41
+ 532 => :need_account_to_store, 550 => :file_unavailable,
42
+ 551 => :page_type_unknown, 552 => :not_enough_memory,
43
+ 553 => :filename_not_allowed,
44
+ 1000 => :invalid_response, 1001 => :connection_failed,
45
+ 1002 => :connection_closed, 1003 => :invalid_file,
46
+ }.freeze
47
+
48
+ def initialize
49
+ ptr = C::Network.sfFtp_create
50
+ raise Error, "sfFtp_create returned NULL" if ptr.null?
51
+
52
+ @handle = FFI::AutoPointer.new(ptr, C::Network.method(:sfFtp_destroy))
53
+ end
54
+
55
+ # Connect to an FTP server. `host` may be an IP string ("1.2.3.4")
56
+ # or hostname ("ftp.example.com"); pass timeout as a SFML::Time
57
+ # or numeric seconds (default 0 = no timeout).
58
+ def connect(host, port: DEFAULT_PORT, timeout: DEFAULT_TIMEOUT)
59
+ addr = IpAddress.from_string(host).struct
60
+ t = timeout.is_a?(Time) ? timeout : Time.seconds(timeout.to_f)
61
+ Response._take_ownership(C::Network.sfFtp_connect(@handle, addr, Integer(port), t.to_native))
62
+ end
63
+
64
+ def login_anonymous
65
+ Response._take_ownership(C::Network.sfFtp_loginAnonymous(@handle))
66
+ end
67
+
68
+ def login(user, password)
69
+ Response._take_ownership(C::Network.sfFtp_login(@handle, user.to_s, password.to_s))
70
+ end
71
+
72
+ def disconnect = Response._take_ownership(C::Network.sfFtp_disconnect(@handle))
73
+ def keep_alive = Response._take_ownership(C::Network.sfFtp_keepAlive(@handle))
74
+
75
+ def working_directory
76
+ DirectoryResponse._take_ownership(C::Network.sfFtp_getWorkingDirectory(@handle))
77
+ end
78
+
79
+ def directory_listing(directory = "")
80
+ ListingResponse._take_ownership(C::Network.sfFtp_getDirectoryListing(@handle, directory.to_s))
81
+ end
82
+
83
+ def change_directory(directory)
84
+ Response._take_ownership(C::Network.sfFtp_changeDirectory(@handle, directory.to_s))
85
+ end
86
+
87
+ def parent_directory
88
+ Response._take_ownership(C::Network.sfFtp_parentDirectory(@handle))
89
+ end
90
+
91
+ def create_directory(name)
92
+ DirectoryResponse._take_ownership(C::Network.sfFtp_createDirectory(@handle, name.to_s))
93
+ end
94
+
95
+ def delete_directory(name)
96
+ Response._take_ownership(C::Network.sfFtp_deleteDirectory(@handle, name.to_s))
97
+ end
98
+
99
+ def rename_file(file, new_name)
100
+ Response._take_ownership(C::Network.sfFtp_renameFile(@handle, file.to_s, new_name.to_s))
101
+ end
102
+
103
+ def delete_file(name)
104
+ Response._take_ownership(C::Network.sfFtp_deleteFile(@handle, name.to_s))
105
+ end
106
+
107
+ def download(remote, local, mode: :binary)
108
+ idx = C::Network::FTP_TRANSFER_MODES.index(mode) ||
109
+ raise(ArgumentError, "Unknown FTP transfer mode: #{mode.inspect}")
110
+ Response._take_ownership(C::Network.sfFtp_download(@handle, remote.to_s, local.to_s, idx))
111
+ end
112
+
113
+ def upload(local, remote, mode: :binary, append: false)
114
+ idx = C::Network::FTP_TRANSFER_MODES.index(mode) ||
115
+ raise(ArgumentError, "Unknown FTP transfer mode: #{mode.inspect}")
116
+ Response._take_ownership(C::Network.sfFtp_upload(@handle, local.to_s, remote.to_s, idx, !!append))
117
+ end
118
+
119
+ def send_command(command, parameter = "")
120
+ Response._take_ownership(C::Network.sfFtp_sendCommand(@handle, command.to_s, parameter.to_s))
121
+ end
122
+
123
+ attr_reader :handle # :nodoc:
124
+
125
+ # Generic response: most FTP operations return this.
126
+ class Response
127
+ def ok? = C::Network.sfFtpResponse_isOk(@handle)
128
+ def status = C::Network.sfFtpResponse_getStatus(@handle)
129
+ def status_symbol = STATUS_NAMES[status] || status
130
+ def message = C::Network.sfFtpResponse_getMessage(@handle).to_s
131
+ attr_reader :handle # :nodoc:
132
+
133
+ # @!visibility private
134
+ def self._take_ownership(ptr)
135
+ obj = allocate
136
+ obj.instance_variable_set(:@handle,
137
+ FFI::AutoPointer.new(ptr, C::Network.method(:sfFtpResponse_destroy)))
138
+ obj
139
+ end
140
+ end
141
+
142
+ # Returned by working_directory and create_directory — adds the
143
+ # `#directory` accessor on top of Response.
144
+ class DirectoryResponse
145
+ def ok? = C::Network.sfFtpDirectoryResponse_isOk(@handle)
146
+ def status = C::Network.sfFtpDirectoryResponse_getStatus(@handle)
147
+ def status_symbol = STATUS_NAMES[status] || status
148
+ def message = C::Network.sfFtpDirectoryResponse_getMessage(@handle).to_s
149
+ def directory = C::Network.sfFtpDirectoryResponse_getDirectory(@handle).to_s
150
+ attr_reader :handle # :nodoc:
151
+
152
+ # @!visibility private
153
+ def self._take_ownership(ptr)
154
+ obj = allocate
155
+ obj.instance_variable_set(:@handle,
156
+ FFI::AutoPointer.new(ptr, C::Network.method(:sfFtpDirectoryResponse_destroy)))
157
+ obj
158
+ end
159
+ end
160
+
161
+ # Returned by directory_listing — adds the `#names` array.
162
+ class ListingResponse
163
+ def ok? = C::Network.sfFtpListingResponse_isOk(@handle)
164
+ def status = C::Network.sfFtpListingResponse_getStatus(@handle)
165
+ def status_symbol = STATUS_NAMES[status] || status
166
+ def message = C::Network.sfFtpListingResponse_getMessage(@handle).to_s
167
+ def count = C::Network.sfFtpListingResponse_getCount(@handle)
168
+ attr_reader :handle # :nodoc:
169
+
170
+ def names
171
+ Array.new(count) { |i| C::Network.sfFtpListingResponse_getName(@handle, i).to_s }
172
+ end
173
+
174
+ # @!visibility private
175
+ def self._take_ownership(ptr)
176
+ obj = allocate
177
+ obj.instance_variable_set(:@handle,
178
+ FFI::AutoPointer.new(ptr, C::Network.method(:sfFtpListingResponse_destroy)))
179
+ obj
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end