Class: OllamaChat::Utils::Fetcher
- Inherits:
-
Object
- Object
- OllamaChat::Utils::Fetcher
- Includes:
- Redact
- Defined in:
- lib/ollama_chat/utils/fetcher.rb
Overview
A fetcher implementation that handles retrieval and caching of HTTP resources.
This class provides functionality to fetch content from URLs, with support for caching responses and their metadata. It handles various content types and integrates with different cache backends to improve performance by avoiding redundant network requests.
Defined Under Namespace
Modules: ResponseMetadata Classes: RetryWithoutStreaming
Class Method Summary collapse
-
.execute(command) {|tmpfile| ... } ⇒ Object
The execute method runs a shell command and processes its output.
-
.get(url, headers: {}, **options) {|tmp| ... } ⇒ Object
Fetches the content located at
urland optionally caches it. -
.middlewares ⇒ Array<Class>
Returns the default Excon middleware stack augmented with
Excon::Middleware::RedirectFollower, deduplicated. -
.normalize_url(url) ⇒ Object
The normalize_url method processes a URL by converting it to a string, decoding any URI components, removing anchors, and then escaping the URL to ensure it is properly formatted.
-
.read(filename) {|file| ... } ⇒ nil, Object
The read method opens a file and extends it with header extension metadata.
Instance Method Summary collapse
-
#callback(tmp) ⇒ Proc
private
The callback method creates a proc that handles chunked data processing by updating progress information and writing chunks to a temporary file.
-
#decorate_io(tmp, response) ⇒ Object
private
Decorates a temporary IO object with header information from an HTTP response.
-
#excon(url, **options) ⇒ Excon
private
The excon method creates a new Excon client instance configured with the specified URL and options.
-
#get(url, **opts) {|tmp| ... } ⇒ Object
Fetches the content located at
url. -
#headers ⇒ Hash
private
The headers method returns a hash containing the default HTTP headers that should be used for requests, including a User-Agent header configured with the application's user agent string.
-
#initialize(debug: false, http_options: {}) ⇒ Fetcher
constructor
The initialize method sets up the fetcher instance with debugging and HTTP configuration options.
-
#message(current, total) ⇒ String
private
The message method formats progress information by combining current and total values with unit formatting, along with timing details.
-
#middlewares ⇒ Array<Class>
private
Instance-level delegate to selfself.middlewares.
Methods included from Redact
#redact_password, redact_password
Constructor Details
#initialize(debug: false, http_options: {}) ⇒ Fetcher
The initialize method sets up the fetcher instance with debugging and HTTP configuration options.
245 246 247 248 249 250 |
# File 'lib/ollama_chat/utils/fetcher.rb', line 245 def initialize(debug: false, http_options: {}) @debug = debug @started = false @streaming = true @http_options = end |
Class Method Details
.execute(command) {|tmpfile| ... } ⇒ Object
The execute method runs a shell command and processes its output.
It captures the command's standard output and error streams, writes them to a temporary file, and yields the file to the caller. If an exception occurs during execution, it reports the error and yields a failed temporary file instead.
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/ollama_chat/utils/fetcher.rb', line 218 def self.execute(command, &block) command.is_a?(String) or command = Shellwords.join(command) Tempfile.create do |tmp| unless command =~ /2>&1/ command += ' 2>&1' end IO.popen(command) do |io| until io.eof? tmp.write io.read(1 << 14) end tmp.rewind tmp.extend(OllamaChat::Utils::Fetcher::ResponseMetadata) tmp.content_type = MIME::Types['text/plain'].first block.(tmp) end end rescue => e msg = "Could not execute #{command.inspect} (#{e})" @debug && !e.is_a?(RuntimeError) and msg += "\n#{e.backtrace * ?\n}" raise OllamaChat::ExecuteError, msg end |
.get(url, headers: {}, **options) {|tmp| ... } ⇒ Object
Fetches the content located at url and optionally caches it.
This is a convenience wrapper around an instance of
OllamaChat::Utils::Fetcher. It accepts the same options as the
instance method, with the following additions:
:cache– an object that responds togetandput(seeOllamaChat::Utils::CacheFetcher). If a cached value exists, it is returned immediately and the network is not contacted.
The method streams the HTTP response into a temporary file (or a
StringIO in the event of a failure). The temporary file is
extended with ResponseMetadata, so the block can inspect
tmp.content_type and tmp.ex. After the block returns the
temporary file is closed and discarded. If a cache is supplied
and the response is not a StringIO, the temporary file is written
back to the cache for future requests.
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/ollama_chat/utils/fetcher.rb', line 155 def self.get(url, headers: {}, **, &block) cache = .delete(:cache) and cache = OllamaChat::Utils::CacheFetcher.new(cache) cache and .puts "Getting #{OllamaChat::Utils::Redact.redact_password(url).inspect} via cache…" if result = cache&.get(url, &block) content_type = result&.content_type || 'unknown' .puts "…hit, found #{content_type} content in cache." return result else new(**).send(:get, url, headers:) do |tmp| result = block.(tmp) if cache && !tmp.is_a?(StringIO) tmp.rewind cache.put(url, tmp) end result end end end |
.middlewares ⇒ Array<Class>
Returns the default Excon middleware stack augmented with
Excon::Middleware::RedirectFollower, deduplicated.
Use this array when building a bare Excon.new client (as done in
OllamaChat::HTTPHandling#request_url_response) so that 30x redirects
are followed automatically. The streaming #get path already merges
this in via the instance method, so callers going through Fetcher#get
do not need to pass :middlewares explicitly.
378 379 380 |
# File 'lib/ollama_chat/utils/fetcher.rb', line 378 def self.middlewares (Excon.defaults[:middlewares] + [ Excon::Middleware::RedirectFollower ]).uniq end |
.normalize_url(url) ⇒ Object
The normalize_url method processes a URL by converting it to a string, decoding any URI components, removing anchors, and then escaping the URL to ensure it is properly formatted.
178 179 180 181 182 183 |
# File 'lib/ollama_chat/utils/fetcher.rb', line 178 def self.normalize_url(url) url = url.to_s url = URI.decode_uri_component(url) url = url.sub(/#.*/, '') URI::Parser.new.escape(url).to_s end |
.read(filename) {|file| ... } ⇒ nil, Object
The read method opens a file and extends it with header extension metadata. It then yields the file to the provided block for processing. If the file does not exist, it outputs an error message to standard error.
196 197 198 199 200 201 202 203 204 205 206 |
# File 'lib/ollama_chat/utils/fetcher.rb', line 196 def self.read(filename, &block) if File.exist?(filename) File.open(filename) do |file| file.extend(OllamaChat::Utils::Fetcher::ResponseMetadata) file.content_type = MIME::Types.type_for(filename).first block.(file) end else STDERR.puts "File #{filename.to_s.inspect} doesn't exist." end end |
Instance Method Details
#callback(tmp) ⇒ Proc (private)
The callback method creates a proc that handles chunked data processing by updating progress information and writing chunks to a temporary file.
429 430 431 432 433 434 435 436 437 438 439 440 441 442 |
# File 'lib/ollama_chat/utils/fetcher.rb', line 429 def callback(tmp) -> chunk, remaining_bytes, total_bytes do total = total_bytes or next current = total_bytes - remaining_bytes if @started .counter.progress(by: total - current) else @started = true .counter.reset(total:, current:) end .update(message: (current, total), force: true) tmp.print(chunk) end end |
#decorate_io(tmp, response) ⇒ Object (private)
Decorates a temporary IO object with header information from an HTTP response.
This method extends the given temporary IO object with ResponseMetadata module and populates it with content type and cache expiration information extracted from the provided response headers.
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 |
# File 'lib/ollama_chat/utils/fetcher.rb', line 406 def decorate_io(tmp, response) tmp.rewind tmp.extend(ResponseMetadata) tmp.response_status = response.status tmp.response_reason = response.reason_phrase if content_type = MIME::Types[response.headers['content-type']].first tmp.content_type = content_type end if cache_control = response.headers['cache-control'] and cache_control !~ /no-store|no-cache/ and ex = cache_control[/s-maxage\s*=\s*(\d+)/, 1] || cache_control[/max-age\s*=\s*(\d+)/, 1] then tmp.ex = ex.to_i end end |
#excon(url, **options) ⇒ Excon (private)
The excon method creates a new Excon client instance configured with the specified URL and options.
349 350 351 352 |
# File 'lib/ollama_chat/utils/fetcher.rb', line 349 def excon(url, **) url = self.class.normalize_url(url) Excon.new(url, .merge(@http_options)) end |
#get(url, **opts) {|tmp| ... } ⇒ Object
Fetches the content located at url.
The method first checks an optional cache (passed via the :cache option).
If a cached response is found, it is returned immediately. Otherwise the
URL is fetched over HTTP using Excon. Two modes are supported:
- Streaming – the response body is streamed directly into a temporary
file. Progress is reported via
infobar. If the first request fails with a non‑200 status or the streaming mode is not supported, the method falls back to a non‑streaming request. - Non‑streaming – the entire body is read into memory before being written to the temporary file.
The temporary file is yielded to the caller. The file is extended
with ResponseMetadata, so the block can inspect content_type and
ex (cache‑expiry in seconds). After the block returns, the
temporary file is closed and discarded.
If a cache is supplied and the response is not a StringIO, the
temporary file is written back to the cache for future requests.
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 |
# File 'lib/ollama_chat/utils/fetcher.rb', line 303 def get(url, **opts, &block) opts.delete(:response_block) and raise ArgumentError, 'response_block not allowed' middlewares = (self.middlewares | Array((opts.delete(:middlewares)))).uniq headers = opts.delete(:headers) || {} headers |= self.headers headers = headers.stringify_keys_recursive response = nil Tempfile.create do |tmp| .label = 'Getting' if @streaming response = excon(url, headers:, middlewares:, response_block: callback(tmp), **opts).request(method: :get) response.status != 200 || !@started and raise RetryWithoutStreaming decorate_io(tmp, response) .finish block.(tmp) else response = excon(url, headers:, middlewares:, **opts).request(method: :get) if status = response.status and status != 200 = "request failed with status %u" % status response.reason_phrase.full? { << " #{_1.inspect}" } error = OllamaChat::HTTPError.new() error.status = status raise error end body = response.body tmp.print body .update(message: (body.bytesize, body.bytesize), force: true) decorate_io(tmp, response) .finish block.(tmp) end end rescue RetryWithoutStreaming @streaming = false retry end |
#headers ⇒ Hash (private)
The returned hash includes the 'User-Agent' header set to OllamaChat::Chat.user_agent.
The headers method returns a hash containing the default HTTP headers that should be used for requests, including a User-Agent header configured with the application's user agent string.
361 362 363 364 365 |
# File 'lib/ollama_chat/utils/fetcher.rb', line 361 def headers { 'User-Agent' => OllamaChat::Chat.user_agent, } end |
#message(current, total) ⇒ String (private)
The message method formats progress information by combining current and total values with unit formatting, along with timing details.
452 453 454 455 456 457 |
# File 'lib/ollama_chat/utils/fetcher.rb', line 452 def (current, total) progress = '%s/%s' % [ current, total ].map { Tins::Unit.format(_1, format: '%.2f %U') } '%l ' + progress + ' in %te, ETA %e @%E' end |
#middlewares ⇒ Array<Class> (private)
Instance-level delegate to OllamaChat::Utils::Fetcher.selfself.middlewares.
Called by #get to build the middleware list before merging any
per-request :middlewares option. Keeping it as a separate method
allows subclasses to override the default stack without touching
the class-level factory.
390 391 392 |
# File 'lib/ollama_chat/utils/fetcher.rb', line 390 def middlewares self.class.middlewares end |