URP.Stream (urp v0.10.0)

Copy Markdown

Bidirectional URP dispatch for XInputStream/XOutputStream/XSeekable.

When we pass a stream object reference to soffice (e.g. as an InputStream in a MediaDescriptor), soffice calls methods on our object over the same TCP connection. This module handles those incoming calls while we wait for the reply to our original request.

XInputStream funcIDs (depth-first interface traversal)

XInterface: queryInterface=0, acquire=1, release=2
XInputStream: readBytes=3, readSomeBytes=4, skipBytes=5, available=6, closeInput=7

XSeekable funcIDs

XInterface: queryInterface=0, acquire=1, release=2
XSeekable: seek=3, getPosition=4, getLength=5

Since XInputStream and XSeekable share funcIDs 3-5, we use the URP type cache to disambiguate: each request carries a type reference telling us which interface it targets.

XOutputStream funcIDs

XInterface: queryInterface=0, acquire=1, release=2
XOutputStream: writeBytes=3, flush=4, closeOutput=5

Summary

Functions

Receive frames while handling XInputStream/XSeekable calls from soffice.

Receive frames while handling XOutputStream calls from soffice.

Spawn a linked process that iterates enumerable, sending chunks to the caller.

Try to handle a stray request as an input stream call.

Types

input_source()

@type input_source() ::
  binary() | {:file, pid(), non_neg_integer()} | {:enum, binary(), pid()}

sink()

@type sink() :: nil | {:path, Path.t()} | (binary() -> any())

Functions

recv_handling_input(conn, data, stream_oid \\ nil)

@spec recv_handling_input(URP.Bridge.t(), input_source() | source(), String.t() | nil) ::
  {binary(), URP.Bridge.t()}

Receive frames while handling XInputStream/XSeekable calls from soffice.

source is either a binary (in-memory), {:file, pid, size} for file-backed streaming, or {:enum, buffer, reader} for enumerables.

When stream_oid is provided and the source is seekable (memory or file), queryInterface for XSeekable is supported — enabling ZIP-based formats like docx and xlsx.

Dispatches calls until we receive the reply to our pending request. Returns {reply_payload, updated_conn}.

recv_handling_output(conn, sink \\ nil)

@spec recv_handling_output(URP.Bridge.t(), sink()) ::
  {binary(), binary() | :ok, URP.Bridge.t()}

Receive frames while handling XOutputStream calls from soffice.

sink controls where output bytes go:

  • nil (default) — accumulate in memory, returns {reply, binary, conn}
  • {:path, path} — write chunks to file as they arrive, returns {reply, :ok, conn}
  • fun/1 — call with each chunk, returns {reply, :ok, conn}

start_enum_reader(enumerable)

@spec start_enum_reader(Enumerable.t()) :: pid()

Spawn a linked process that iterates enumerable, sending chunks to the caller.

The reader sends {pid, {:chunk, data}} for each element and {pid, :eof} when the enumerable is exhausted. The caller receives these in fill_buffer/3.

try_handle_input(conn, payload)

@spec try_handle_input(URP.Bridge.t(), binary()) ::
  {:handled, URP.Bridge.t()} | :not_input

Try to handle a stray request as an input stream call.

Called by Bridge.recv_reply/1 when it encounters a non-reply message outside the main stream recv loop. Returns :not_input if the request doesn't match the active input stream context.