Module: Proj::FileApiCallbacks

Defined in:
lib/proj/file_api_callbacks.rb

Overview

Include this module in a class to create a custom file API for PROJ. Install it via Context#set_file_api.

The including class must call #install_callbacks in its initializer and implement the following methods:

  • open(path, access_mode) - Open a file. access_mode is one of :PROJ_OPEN_ACCESS_READ_ONLY, :PROJ_OPEN_ACCESS_READ_UPDATE, or :PROJ_OPEN_ACCESS_CREATE. Return a file object (any Ruby object) or nil on error.

  • read(file, size_bytes) - Read up to size_bytes from file, return a String.

  • write(file, data) - Write data to file, return the number of bytes written.

  • seek(file, offset, whence) - Seek within file using SEEK_SET/SEEK_CUR/SEEK_END.

  • tell(file) - Return the current position in file.

  • close(file) - Close file.

  • exists(path) - Return true if the file at path exists.

  • mkdir(path) - Create directory at path, return true on success.

  • unlink(path) - Remove file at path, return true on success.

  • rename(original_path, new_path) - Rename a file, return true on success.

The file parameter passed to read/write/seek/tell/close is whatever object your open method returned.

Examples:

class MyFileApi
  include Proj::FileApiCallbacks

  def initialize(context)
    install_callbacks(context)
  end

  def open(path, access_mode)
    # return a file object or nil
  end
  # ... implement remaining methods ...
end

context.set_file_api(MyFileApi)

Instance Method Summary collapse

Instance Method Details

#close_callback(context, handle, user_data) ⇒ Object

Close file



107
108
109
110
111
# File 'lib/proj/file_api_callbacks.rb', line 107

def close_callback(context, handle, user_data)
  file = handle_to_file(handle)
  self.close(file)
  unregister_handle(handle)
end

#exists_callback(context, path, user_data) ⇒ Object

Return TRUE if a file exists



114
115
116
117
118
119
120
# File 'lib/proj/file_api_callbacks.rb', line 114

def exists_callback(context, path, user_data)
  if self.exists(path)
    1
  else
    0
  end
end

#handle_to_file(handle) ⇒ Object



157
158
159
# File 'lib/proj/file_api_callbacks.rb', line 157

def handle_to_file(handle)
  @file_api_handles[handle.address][:file]
end

#install_callbacks(context) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/proj/file_api_callbacks.rb', line 40

def install_callbacks(context)
  # PROJ keeps using this structure after proj_context_set_fileapi returns,
  # so it must be retained on the Ruby object to avoid GC invalidating it.
  @proj_file_api = Api::ProjFileApi.new
  @proj_file_api[:version] = 1

  # Maps native address -> {proj_handle:, file:}. Retaining proj_handle
  # prevents the MemoryPointer from being GCed while PROJ holds the address.
  @file_api_handles = {}

  @proj_file_api[:open_cbk] = self.method(:open_callback)
  @proj_file_api[:read_cbk] = self.method(:read_callback)
  @proj_file_api[:write_cbk] = self.method(:write_callback)
  @proj_file_api[:seek_cbk] = self.method(:seek_callback)
  @proj_file_api[:tell_cbk] = self.method(:tell_callback)
  @proj_file_api[:close_cbk] = self.method(:close_callback)
  @proj_file_api[:exists_cbk] = self.method(:exists_callback)
  @proj_file_api[:mkdir_cbk] = self.method(:mkdir_callback)
  @proj_file_api[:unlink_cbk] = self.method(:unlink_callback)
  @proj_file_api[:rename_cbk] = self.method(:rename_callback)

  result = Api.proj_context_set_fileapi(context, @proj_file_api, nil)

  if result != 1
    Error.check_object(self)
  end
end

#mkdir_callback(context, path, user_data) ⇒ Object

Return TRUE if directory exists or could be created



123
124
125
126
127
128
129
# File 'lib/proj/file_api_callbacks.rb', line 123

def mkdir_callback(context, path, user_data)
  if self.mkdir(path)
    1
  else
    0
  end
end

#open_callback(context, path, access_mode, user_data) ⇒ Object

Open file. Return NULL if error



69
70
71
72
73
# File 'lib/proj/file_api_callbacks.rb', line 69

def open_callback(context, path, access_mode, user_data)
  file = self.open(path, access_mode)
  return nil unless file
  register_handle(file)
end

#read_callback(context, handle, buffer, size_bytes, user_data) ⇒ Object

Read sizeBytes into buffer from current position and return number of bytes read



76
77
78
79
80
81
82
83
84
# File 'lib/proj/file_api_callbacks.rb', line 76

def read_callback(context, handle, buffer, size_bytes, user_data)
  file = handle_to_file(handle)
  data = self.read(file, size_bytes)
  return 0 if data.nil? || data.empty?

  read_bytes = [size_bytes, data.bytesize].min
  buffer.put_bytes(0, data, 0, read_bytes)
  read_bytes
end

#register_handle(file) ⇒ Object

Create an opaque handle for PROJ and associate it with a file object. The MemoryPointer is retained to prevent GC while PROJ holds the address.



151
152
153
154
155
# File 'lib/proj/file_api_callbacks.rb', line 151

def register_handle(file)
  proj_handle = FFI::MemoryPointer.new(:pointer)
  @file_api_handles[proj_handle.address] = { proj_handle: proj_handle, file: file }
  proj_handle
end

#rename_callback(context, original_path, new_path, user_data) ⇒ Object

Return TRUE if file could be renamed



141
142
143
144
145
146
147
# File 'lib/proj/file_api_callbacks.rb', line 141

def rename_callback(context, original_path, new_path, user_data)
  if self.rename(original_path, new_path)
    1
  else
    0
  end
end

#seek_callback(context, handle, offset, whence, user_data) ⇒ Object

Seek to offset using whence=SEEK_SET/SEEK_CUR/SEEK_END. Return TRUE in case of success



94
95
96
97
98
# File 'lib/proj/file_api_callbacks.rb', line 94

def seek_callback(context, handle, offset, whence, user_data)
  file = handle_to_file(handle)
  self.seek(file, offset, whence)
  return 1 # True
end

#tell_callback(context, handle, user_data) ⇒ Object

Return current file position



101
102
103
104
# File 'lib/proj/file_api_callbacks.rb', line 101

def tell_callback(context, handle, user_data)
  file = handle_to_file(handle)
  self.tell(file)
end

Return TRUE if file could be removed



132
133
134
135
136
137
138
# File 'lib/proj/file_api_callbacks.rb', line 132

def unlink_callback(context, path, user_data)
  if self.unlink(path)
    1
  else
    0
  end
end

#unregister_handle(handle) ⇒ Object



161
162
163
# File 'lib/proj/file_api_callbacks.rb', line 161

def unregister_handle(handle)
  @file_api_handles.delete(handle.address)
end

#write_callback(context, handle, buffer, size_bytes, user_data) ⇒ Object

Write sizeBytes into buffer from current position and return number of bytes written



87
88
89
90
91
# File 'lib/proj/file_api_callbacks.rb', line 87

def write_callback(context, handle, buffer, size_bytes, user_data)
  file = handle_to_file(handle)
  data = buffer.get_bytes(0, size_bytes)
  self.write(file, data)
end