Changelog

Copy Markdown

[v0.10.0] - 2026-03-12

Added

  • :settings option for URP.convert/2: pass {path, property, value} triplets to configure soffice via ConfigurationUpdateAccess before each conversion. Settings sharing the same nodepath are batched into a single update call. Useful for tuning cache limits, graphic memory, etc.
  • :io option for URP.convert/2: choose between file-based (:file, default) and streaming (:stream) I/O independently for input and output. File I/O uses temp files on soffice's filesystem (~6 round-trips). Streaming uses XInputStream/XOutputStream over URP (~40-50% slower but no temp disk). Mixed modes supported: io: {:file, :stream} or io: {:stream, :file}.
  • :recv_timeout and :max_frame_size options for URP.convert/2: per-conversion control over the TCP recv timeout (default 120 s) and maximum accepted frame size (default 512 MiB).
  • Infinite reconnection with backoff: the pool retries soffice connections forever with exponential backoff (backoff_initial / backoff_max) instead of crashing. Emits [:urp, :connection, :retry] telemetry on each attempt.
  • Frame size guard: recv_frame rejects frames larger than 512 MiB (configurable) before allocating, preventing OOM from corrupt wire data.

Fixed

  • Pool query checkin leak: diagnostic queries (version, services, etc.) did not reset conn.private, conn.reply, or conn.doc_oid on checkin, leaking state across pool reuses. All pool operations now go through reset_conversion_state/1.
  • Default pool ignored backoff config: backoff_initial and backoff_max from config :urp, :default were silently dropped. Application now forwards the full config map to the pool.
  • Large file conversion (>64 MB output): gen_tcp.recv returns :enomem for single reads above ~64 MB. recv_frame now reads in 4 MB chunks and reassembles. Also eliminates redundant binary copies in send_frame (iodata passthrough) and write_bytes (enc_str_iodata/1).
  • Stale conn.reply leaking as conversion result: conn.reply retained values from bootstrap or diagnostic queries. Early conversion failures (e.g. file not found) could return stale data as a successful result. Convert now clears conn.reply on checkout with strict type-checked result matching.
  • TID cache loss across conversions: the URP type cache accumulated during each conversion is now persisted back to the connection struct, preventing type desync on subsequent operations.
  • O(n²) fill_buffer in enum streams: chunk accumulation now uses iodata instead of repeated binary concatenation, flattening once when consumed.

Changed

  • Iodata frame builders: Call and Protocol return iodata instead of flattened binaries. The single IO.iodata_to_binary call happens in Protocol send_frame/2, eliminating redundant intermediate allocations.
  • Bridge error paths use reply: nil instead of reply: "" for consistency.

[v0.9.1] - 2026-03-06

  • Fix 2 GiB memory spike during conversion: read_file/2 now calls available() to get the exact output size before readBytes(). Previously it passed 0x7FFFFFFF (2 GiB), causing soffice to pre-allocate a 2 GiB buffer on every conversion regardless of actual PDF size. Peak soffice RSS drops from ~2.3 GB to ~300 MB.

[v0.9.0] - 2026-03-04

  • Add :telemetry events — every pool operation emits [:urp, :call, :stop] with queue_time, service_time, and total_time measurements. See URP.Telemetry for details.

[v0.8.1] - 2026-03-03

Internal refactor of Bridge and Pool internals. No public API changes.

  • Plug-like error handling: Bridge functions no longer raise — errors accumulate on conn.error and short-circuit subsequent calls via guards. This replaces 13+ rescue blocks with a single, predictable data-flow pattern. Callers piping through Bridge get clean error propagation without try/rescue.
  • Uniform return type: All Bridge functions return t(). Functions that previously returned tuples (store_document_write, store_to_stream, read_file) now stash results in conn.reply, enabling consistent piping.
  • Pool memory fix: reset_conversion_state now clears conn.reply, preventing large binaries from being held between conversions.
  • Accurate type specs: Bridge struct fields (sock, desktop_oid, ctx_oid, smgr_oid) now correctly typed as | nil; reply is term().
  • CI: Debian soffice 26.2 with Docker layer caching — replaces Alpine image, enables LO 26.2+ tests (markdown export).
  • Expanded test coverage: bang variants, Bridge-level store_to_stream, temp file cleanup.

[v0.8.0] - 2026-03-02

  • Add URP.services/1 — list all registered UNO service names
  • Add URP.filters/1 — list all export filter names (discover available filters at runtime)
  • Add URP.types/1 — list all document type names
  • Add URP.locale/1 — query soffice locale setting
  • Add Protocol parse_string_sequence_reply/1 for sequence<string> replies
  • Add Diagnostics section to module docs with examples

[v0.7.0] - 2026-03-02

  • File-based I/O: load and store documents via XSimpleFileAccess instead of XInputStream/XOutputStream streaming — eliminates thousands of TCP round-trips per conversion, ~8x faster for typical documents
  • Breaking: close_document!/2 now returns t() instead of {binary(), t()}
  • Pipeline-friendly Bridge helpers — all conn-threading helpers (call, sfa_call, qi) return just conn; enables conn |> qi(...) |> call(...) style
  • Add last_reply and last_error fields to Bridge conn for introspection/debugging
  • Add :filter_data option for export-specific settings (e.g. [UseLosslessCompression: true, ExportFormFields: false] for PDF)
  • Add XSeekable support — ZIP-based formats (docx, xlsx, pptx, odt) stream without buffering the entire file first
  • Reuse connections across conversions (no reconnect per document)
  • Pre-compute static URP frame bodies at compile time
  • Fix TID cache for cross-thread XSeekable replies
  • Replace inline magic numbers with named constants throughout
  • Add PERFORMANCE.md with benchmarks and container recommendations
  • Add Benchee benchmark suite
  • Switch Docker images to libreoffice-*-nogui packages

[v0.6.1] - 2026-03-01

  • Add URP.version/1 — query soffice version string over URP (no CLI access needed)
  • Hide URP.Pool from hex docs (internal module)
  • Simplify README

[v0.6.0] - 2026-03-01

  • Breaking: :filter option is now required — no default export filter
  • Remove PDF-centric language from docs — URP is a generic document conversion tool

[v0.5.0] - 2026-03-01

  • Breaking: unified API — single URP.convert/2 replaces convert_stream/2, convert_file_stream/2, and convert/3
  • Breaking: remove URP.convert_stream/2, URP.convert_file_stream/2, URP.convert/3, URP.Pool.convert_stream/3, URP.Pool.convert_file_stream/3, URP.Pool.convert_url/4
  • Breaking: output destination is now :output option (path, :binary, or fun/1) instead of :sink
  • Breaking: default output writes to temp file (returns {:ok, path}) instead of accumulating bytes in memory
  • Add enumerable input support — pass any Enumerable (e.g. File.stream!/2, S3 download streams) to URP.convert/2
  • Add lazy enumerable streaming (enum reader + Bridge stream loader)

[v0.4.0] - 2026-02-28

  • Breaking: remove use URP, otp_app: :my_app macro — call URP.convert_stream/2, URP.convert_file_stream/2, URP.convert/3 directly
  • Breaking: URP.Test.stub/1 replaces URP.Test.stub/2 — stubs are global, no module name needed
  • Breaking: remove URP.Test.start/0 — ownership server starts automatically
  • Add URP.Application — default pool, DynamicSupervisor, and ownership server start automatically
  • Add named pools via config :urp, :pools — started on first use via DynamicSupervisor
  • Handle soffice DisposedException with reactive retry on reconnect (matches C++ callers' approach)
  • Make nimble_ownership a required dependency (no longer optional/test-only)

[v0.3.1] - 2026-02-26

  • Exclude Mix.Tasks.Bump from hex package to avoid module conflicts

[v0.3.0] - 2026-02-26

  • Fix protocol correctness: validate block count, support FUNCTIONID16/14, skip MOREFLAGS byte
  • Fix one-way detection: use func_id (only release is one-way), not unreliable header flags
  • Add parse_exception/1 — extract human-readable messages from UNO exception replies
  • Include exception details in error messages from load_document! and store_to_url!
  • Add protocol unit tests (28 tests covering header parsing, encoding, reply classification)
  • Add error handling integration tests
  • Simplify mix bump task (remove network dependencies, fix editor hang with gpg signing)

[v0.2.0] - 2026-02-26

  • Breaking: remove URP.Connection — all calls go through URP.Pool via wrapper modules
  • Make use URP route all calls through a supervised Pool
  • Change default pool_size from 4 to 1 (matches single soffice instance)
  • Rewrite README: clarify setup steps, explain function differences, add design tradeoffs
  • Add Kubernetes scaling note
  • Fail hard when soffice is not reachable in tests (instead of silently skipping)
  • Hide mix bump from hexdocs

[v0.1.2] - 2026-02-26

  • Add otp_app config support for URP.Pool
  • Add soffice service to CI for integration tests
  • Add :mix to dialyzer PLT apps
  • Add release workflow and mix bump task
  • Remove stale urp_convert.exs script
  • Fix license year

[v0.1.1] - 2026-02-25

  • Add streaming conversion via XInputStream/XOutputStream (no shared filesystem needed)
  • Add file-backed streaming (convert_file_stream/2)
  • Add sink option for streaming output to file or callback
  • Add URP.Pool (NimblePool) for connection pooling
  • Add use URP macro and URP.Test for stubbable wrapper modules
  • Add typespecs to all public functions
  • Add Nix flake dev shell
  • Add dialyzer
  • Make README the main page on hexdocs

[v0.1.0] - 2026-02-25

  • Initial release