|
2 | 2 |
|
3 | 3 | # Require the low-level FFI module |
4 | 4 | require_relative 'imageflow_ffi' |
| 5 | +require 'json' |
5 | 6 |
|
6 | 7 | # Require all the generated data models |
7 | 8 | Dir[File.join(__dir__, 'imageflow', 'models', '*.rb')].each { |file| require file } |
8 | 9 |
|
9 | 10 | # Imageflow is the top-level module for the public Ruby API. |
10 | 11 | # It provides a clean, idiomatic interface to the Imageflow library. |
11 | 12 | module Imageflow |
| 13 | + # The Context class manages the lifecycle of a native Imageflow context. |
| 14 | + # It ensures that the native context is properly created and destroyed. |
| 15 | + class Context |
| 16 | + def initialize |
| 17 | + major = ImageflowFFI.imageflow_abi_version_major |
| 18 | + minor = ImageflowFFI.imageflow_abi_version_minor |
| 19 | + @context_ptr = ImageflowFFI.imageflow_context_create(major, minor) |
| 20 | + |
| 21 | + # Automatically free the context when the object is garbage collected. |
| 22 | + ObjectSpace.define_finalizer(self, self.class.finalize(@context_ptr)) |
| 23 | + end |
| 24 | + |
| 25 | + def self.finalize(context_ptr) |
| 26 | + proc { ImageflowFFI.imageflow_context_destroy(context_ptr) } |
| 27 | + end |
| 28 | + |
| 29 | + # Sends a JSON command to the Imageflow context and returns the response. |
| 30 | + # |
| 31 | + # @param command [String] The command name (e.g., 'v1/info'). |
| 32 | + # @param payload [Hash] The command payload, which will be serialized to JSON. |
| 33 | + # @return [Hash] The JSON response, parsed into a Ruby Hash. |
| 34 | + # @raise [RuntimeError] if the Imageflow library reports an error. |
| 35 | + def send_json(command, payload) |
| 36 | + json_string = JSON.generate(payload) |
| 37 | + json_buffer = FFI::MemoryPointer.from_string(json_string) |
| 38 | + |
| 39 | + # This response pointer must be freed with imageflow_json_response_destroy |
| 40 | + response_ptr = ImageflowFFI.imageflow_context_send_json(@context_ptr, command, json_buffer, json_string.bytesize) |
| 41 | + |
| 42 | + # Check for errors after every call that can produce one. |
| 43 | + if ImageflowFFI.imageflow_context_has_error(@context_ptr) |
| 44 | + error_buffer = FFI::MemoryPointer.new(:char, 1024) # 1KB for error message |
| 45 | + bytes_written_ptr = FFI::MemoryPointer.new(:size_t) |
| 46 | + ImageflowFFI.imageflow_context_error_write_to_buffer(@context_ptr, error_buffer, error_buffer.size, bytes_written_ptr) |
| 47 | + bytes_written = bytes_written_ptr.read(:size_t) |
| 48 | + error_message = error_buffer.read_string(bytes_written) |
| 49 | + # Always free the response pointer, even if we have an error. |
| 50 | + ImageflowFFI.imageflow_json_response_destroy(@context_ptr, response_ptr) if response_ptr && !response_ptr.null? |
| 51 | + raise "Imageflow error: #{error_message}" |
| 52 | + end |
| 53 | + |
| 54 | + return {} if response_ptr.nil? || response_ptr.null? |
| 55 | + |
| 56 | + begin |
| 57 | + status_code_ptr = FFI::MemoryPointer.new(:int) |
| 58 | + buffer_ptr_ptr = FFI::MemoryPointer.new(:pointer) |
| 59 | + buffer_size_ptr = FFI::MemoryPointer.new(:size_t) |
| 60 | + |
| 61 | + success = ImageflowFFI.imageflow_json_response_read(@context_ptr, response_ptr, status_code_ptr, buffer_ptr_ptr, buffer_size_ptr) |
| 62 | + |
| 63 | + if success |
| 64 | + buffer_ptr = buffer_ptr_ptr.read_pointer |
| 65 | + buffer_size = buffer_size_ptr.read(:size_t) |
| 66 | + json_response_string = buffer_ptr.read_string(buffer_size) |
| 67 | + return JSON.parse(json_response_string) |
| 68 | + else |
| 69 | + return { error: 'Failed to read JSON response from Imageflow' } |
| 70 | + end |
| 71 | + ensure |
| 72 | + # Ensure the response is always destroyed. |
| 73 | + ImageflowFFI.imageflow_json_response_destroy(@context_ptr, response_ptr) |
| 74 | + end |
| 75 | + end |
| 76 | + end |
| 77 | + |
12 | 78 | class << self |
13 | 79 | # Returns the version of the Imageflow ABI (Application Binary Interface) |
14 | 80 | # as a 'major.minor' string. |
|
0 commit comments