Skip to content

Automation cli feature - #37

Open
pplupo wants to merge 6 commits into
Euro-Office:mainfrom
pplupo:feature/document-automation-cli
Open

Automation cli feature#37
pplupo wants to merge 6 commits into
Euro-Office:mainfrom
pplupo:feature/document-automation-cli

Conversation

@pplupo

@pplupo pplupo commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

This is useful to allow Euro-Office to be used in automation workflows.

--convert-to=<out> / --print-to-file=<out.pdf> on the desktop binary, handled before any
CEF/Qt startup. Both flags are the same code path: --print-to-file just forces a .pdf
output suffix and calls the identical converter. There is no QAscPrinterContext print path
here — this is a plain x2t PDF export, same as --convert-to out.pdf would already produce.
I'd described it as reusing the print-to-file path in the original write-up; that wasn't
accurate, and I've corrected it here and in #58.

x2t already ships next to the desktop binary and already accepts x2t <from> <to> (or a full
TaskQueueDataConvert task XML). This PR doesn't add new conversion capability over that — it
adds a stable, documented, discoverable front door to it: no internal-tool compatibility
contract, no font-cache-path knowledge required, --help support, and a name a script author
would actually reach for. It intentionally exposes a subset of x2t's parameters (from/to/fonts/
temp only — no password, page ranges, CSV options, etc.); if that turns out to be limiting,
extending the flag set is a follow-up rather than a blocker for this PR.

DocBuilder is unrelated to this PR — it's a DocumentServer-side component, not part of the
desktop package, so there's no overlap with #321.

Implementation of Euro-Office/DesktopEditors#58


Changes since the last review round

  • Reset utils.cpp to main and reapplied only the argument_value null-guard fix — the
    previous revision had accidentally pulled in ~120 unrelated lines from other branches
    (default-save-format, db-support, Wayland DPI code) that didn't compile.
  • Dropped src/utils.cpp from COMMON_SOURCES in win-linux/CMakeLists.txt (it's already
    compiled via prop/utils.cpp's #include; listing it again caused duplicate symbols).
  • docautomation.cpp/.h now carry a Euro-Office SPDX header instead of Ascensio's upstream one.
  • Replaced the ../../../ relative includes with the configured include dirs. This required
    promoting x2t.h from desktop-sdk's private lib/src/ to its public lib/include/ — see
    pplupo/Euro-Office_desktop-sdk@feature/document-automation-cli,
    which this PR now depends on.
  • Added a synchronous CheckFonts(false) call before conversion, so a profile that's never run
    the GUI (fresh install, container, CI) gets its font cache built instead of failing on a
    missing AllFonts.js.

pplupo added 5 commits July 31, 2026 20:25
Handles both before any CEF/Qt UI startup by shelling out to the packaged
x2t converter with a minimal TaskQueueDataConvert XML task (only
m_sFileFrom/m_sFileTo/m_sAllFontsPath; format is auto-detected from file
extensions the same way x2t's own native CLI mode does it).
--print-to-file is an alias that forces PDF output, since x2t's PDF export
is already what Print ultimately produces, so no CEF-driven print pipeline
is needed for either flag.

Also adds src/utils.cpp (the InputArgs namespace main.cpp already relies on)
to CMakeLists.txt's COMMON_SOURCES, where it was missing.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
AscAppManager::getInstance() constructs the full QObject-based
CAscApplicationManagerWrapper, whose private constructor calls
QObject::installEventFilter() -- which segfaults with no live
QCoreApplication, and none exists yet at this point (before any CEF/Qt
startup, which is the entire point of this code path). Switch to a
plain, stack-allocated CAscApplicationManager instead; it only needs
m_oSettings and GetLibraryPathVariable(), both available on the base
class with none of the GUI wiring.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
x2t's XML parser doesn't skip a UTF-8 BOM before <?xml ...?>, so
SaveToFile(..., true) left m_sFileFrom/m_sFileTo unparsed and x2t
failed with "Empty sFileFrom or sFileTo". Confirmed by capturing the
actual generated file (swapped converter/x2t for a wrapper script that
copies argv[1] before delegating to the real binary) and hand-feeding
both the BOM and no-BOM versions to x2t directly: only the no-BOM
version parses and converts successfully.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
CSimpleConverter::ThreadProc (fileconverter.h) is what the running
editor's own export/format-conversion actions use, and it sets several
fields the CLI path didn't: explicit m_nFormatTo (rather than relying
on x2t's own from-extension auto-detection), m_sFontDir, m_sTempDir,
and m_bIsNoBase64=false. Match it field for field instead of the
minimal from/to-only task, which parsed correctly by every external
check (x2t reads back the exact bytes written; replaying the exact
captured file by hand against x2t succeeds every time) but still
failed specifically when x2t was forked from this GUI binary.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
This was the actual cause of "Empty sFileFrom or sFileTo". x2t only
parses its argument as a TaskQueueDataConvert file when the filename
ends in ".xml" (X2tConverter/src/main.cpp); any other name is taken as
the first half of a bare `x2t <from> <to>` invocation, so the
destination came through empty. CreateTempFileWithUniqueName produces
/tmp/CLI_XXXXXX with no extension, while every other call site in the
tree appends one (params_simple_converter.xml, _params_from.xml).

Proven with a controlled A/B: the same bytes as /tmp/probe_with.xml
convert successfully (exit 0, valid PDF), and as /tmp/probe_without
fail with exactly this error.

This also supersedes the earlier "write the x2t task XML without a
BOM" commit -- the BOM was never the problem (upstream writes one and
works); every manual replay that appeared to prove it happened to use
a .xml-named copy, which is what actually made those runs succeed.

Also drops the hand-rolled extension->format map added in the previous
commit: x2t already derives both formats from the file extensions, so
duplicating that table here only risks drift.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>

@chrip chrip left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Adds --convert-to=<out> / --print-to-file=<out.pdf> to the desktop binary, handled before
any Qt/CEF startup by writing an x2t task XML and shelling out to the packaged x2t. The idea
is sound and the new docautomation.cpp is clean, well-commented work. Three things block it:
the branch does not compile (the last commit swept in ~120 lines of unrelated code from
three other feature branches, defining eight Utils:: members that no header declares), the
new files carry Ascensio's upstream copyright header rather than a Euro-Office one, and the
feature as built is a thin wrapper over a CLI that x2t already exposes — worth doing, but
it should be justified on stability/discoverability grounds rather than the capability claims
currently in the description.

Overlap with DocBuilder and the existing conversion APIs

Before reviewing the mechanics it's worth asking whether this capability already exists, since
the same automation motivation is being tracked in DocumentServer#321 ("Official DocBuilder
Support and Documentation for AI Automation").

Route Ships with the desktop app? Can it already do --convert-to?
x2t Yes — desktop-apps builds the X2tConverter target (win-linux/CMakeLists.txt:106-108) and the app resolves it at <appdir>/converter/x2t Yes, directly. core/X2tConverter/src/main.cpp:184-200: x2t <from> <to> [fontDir] [password]. Plus x2t task.xml for the full TaskQueueDataConvert surface (:113-125)
DocBuilder No — the string docbuilder appears nowhere in desktop-apps Yes in principle (OpenFileSaveFile), but it is a DocumentServer-side component, shipped in the Docker image at server/FileConverter/bin/docbuilder (per the maintainer answer on DocumentServer#321)
DocumentServer conversion API No (needs a server) Yes, over HTTP

So on DocBuilder specifically: this PR does not duplicate it. DocBuilder is not part of the
desktop package at all, so there is no overlap to worry about there.

The overlap is with x2t, which is already installed next to the desktop binary and already
accepts x2t in.docx out.pdf. The PR's own comments say as much — "the same way its own
x2t <from> <to> mode does"
. What the new flags add is convenience, not capability:
auto-resolving the font-cache and temp paths, an input-exists check, a --help entry, and a name
that isn't an internal tool. Meanwhile they expose less than x2t already does — the task XML
is hardcoded to from/to/fonts/temp, so x2t's password argument and the whole
TaskQueueDataConvert parameter set (page ranges, CSV delimiter/encoding, PDF options,
thumbnails) are unreachable through the new flags.

A stable, documented front door is a legitimate thing to want: x2t's CLI is an internal contract
with no compatibility promise, and its font-dir requirement is undiscoverable. But that is a
different justification than the one in the PR description, and it deserves a deliberate
decision, because there is a cheaper option — DocumentServer#321 already asks for exactly this
surface to be documented, and documenting x2t (plus DocBuilder server-side) would serve the
automation use case with no new code and no new compatibility surface. If the desktop binary
should own a CLI regardless, then it ought to wrap x2t's full argument set rather than a
subset.

Verification

Claim / Item Reality Status
"One unified --convert-to / --print-to-file flag set" They are the same code path. --print-to-file just forces a .pdf suffix and calls the identical converter (docautomation.cpp, bPrintToFile branch). One feature, two names. ⚠️ overstated
"reusing … the QAscPrinterContext print-to-file path" (PR body + issue #58) Not in the code. No printer context, no QAscPrinterContext, no print rendering — it's an x2t PDF export. The header comment in docautomation.h is honest about this; the PR body isn't. ❌ inaccurate
"No CEF/Qt startup needed" Correct — the hook sits at main.cpp:66-69, before QApplication ✓ verified
"Small, low-risk addition" The new files are, at 4 files / ~190 lines. But the diff also carries ~120 lines from three unrelated branches — see Blocking #1 ⚠️
NSX2T::Convert(...) call is correct Yes — matches x2t.h:41 (sConverterPath, sXmlPath, pManager, bIsLoggingErrors); it's a static in-header function, so no link dependency ✓ verified
The .xml suffix comment Correct, and a good catch — X2tConverter/src/main.cpp:113-114 only parses the arg as a task file when it ends in .xml ✓ verified
Does it build? No — see Blocking #1. Utils is a class (utils.h:80) and eight member functions are defined in utils.cpp with no declaration in utils.h, which the PR doesn't touch 🔴 verified
License headers on new files Both new files carry (c) Copyright Ascensio System SIA 2010-2019 verbatim, including the ONLYOFFICE-specific AGPL §7(a)/§5 and CC-BY-SA clauses 🔴 wrong attribution
CI would have caught the build break No — desktop-apps has only check.yml (DCO); there is no build workflow in this repo and no runs on this branch. Builds happen in the DesktopEditors superproject. ⚠️ gap
DCO All 6 commits signed off; DCO check green
Conventional Commits Mostly (fix: …), two bare subjects (Add --convert-to …, Mirror CSimpleConverter's …) ℹ️

Issues & Suggestions

🔴 Blocking

  • 1. The last commit sweeps in three other features' code, and the result doesn't compile.
    df54a58be is described as "don't crash on a flag with no =value" — that's the 3-line
    value_pos guard in InputArgs::argument_value, which is fine and correct. But the same
    commit is +131/-10 in utils.cpp and also adds:

    • Utils::defaultSaveFormat{,Enforced,Managed,Chosen} + keepDefaultSaveFormat — the
      default-save-format/ODF onboarding feature;
    • Utils::isDatabaseFile / warnIfDatabaseFile + #include "components/cmessage.h" — the
      db-support feature;
    • CDpiChangeWatcher / Utils::WatchForDpiChange, the Wayland DPI comment on
      getScreenDpiRatioByWidget, the platformName() == "wayland" gate in useGtkDialog, and
      the removal of Utils::getScreenDpiRatio(int) — Wayland-migration content.

    None of it exists on main, none of it belongs to this feature. And it cannot build:
    Utils is a class (utils.h:80), so defining Utils::WatchForDpiChange,
    Utils::defaultSaveFormat*, Utils::isDatabaseFile and Utils::warnIfDatabaseFile out of
    line without declaring them in the class is a hard error ("no declaration matches …") — and
    utils.h is not among the PR's 6 files. Please reset utils.cpp to main and re-apply only the
    argument_value guard.

  • 2. src/utils.cpp must not be added to COMMON_SOURCES.
    win-linux/src/prop/utils.cpp:27 does #include "../utils.cpp", and that file is already in
    the list (CMakeLists.txt:184). Compiling src/utils.cpp as its own translation unit as well
    gives two definitions of everything in it (Utils::*, InputArgs::*, WindowHelper::*) →
    duplicate-symbol link failure. Only src/docautomation.cpp needs adding.

  • 3. The new files carry Ascensio's copyright header, not a Euro-Office one.
    docautomation.cpp and docautomation.h both open with
    (c) Copyright Ascensio System SIA 2010-2019, copied from the surrounding upstream sources.
    For files written from scratch in 2026 in a Nextcloud-side fork, that attributes new work to
    Ascensio and freezes the year at 2019. It also drags along clauses that are specifically
    ONLYOFFICE's: the AGPL §7(a) non-infringement amendment, the §5 "Appropriate Legal Notices"
    requirement, and the CC-BY-SA terms for "the Product's GUI elements … as well as technical
    writing content"
    — none of which this project wants to assert on its own new code.

    Suggested header for new files in this repo, REUSE-compliant and consistent with what
    Euro-Office already uses elsewhere (.github/AI_POLICY.md) and with the Nextcloud-side app
    code:

    /*
     * SPDX-FileCopyrightText: 2026 Euro-Office contributors
     * SPDX-License-Identifier: AGPL-3.0-or-later
     */
    

    To be clear on the distinction: files that genuinely derive from upstream Ascensio code
    should keep the upstream notice and add the Euro-Office line beneath it. These two are new
    files, so only the Euro-Office header applies.

    Worth fixing as a pattern rather than a one-off — the new files added in desktop-apps#30
    (waylandbackend.*, x11backend.*, iplatformbackend.h, platformbackendfactory.cpp) copied
    the same Ascensio header, so both PRs are affected and it would be good to settle the
    convention once.

⚠️ Major

  • 4. --print-to-file is an alias, and the description promises a printing path it doesn't have.
    Either drop the flag (--convert-to out.pdf already does exactly this) or implement it properly
    via QAscPrinterContext as #58 describes — page setup, margins and ranges are the only reasons
    a "print to file" would differ from a PDF export. As it stands, a user reaching for
    --print-to-file expecting print semantics gets a plain export. At minimum, fix the PR body and
    issue #58 to describe what was built.
  • 5. Reaching into desktop-sdk's private headers by relative path.
    docautomation.cpp uses #include "../../../desktop-sdk/ChromiumBasedEditors/lib/src/x2t.h"
    (and ../../../core/DesktopEditor/...). The build already provides these as include
    directories — CMakeLists.txt:337-339 adds desktop-sdk/…/lib/include, …/qt_wrapper/include
    and core/DesktopEditor — which is why every other file writes #include "common/File.h" and
    #include "applicationmanager.h". This is the only file in the repo using ../../../. More
    importantly, lib/src/x2t.h is a private desktop-sdk implementation header deliberately
    not on the include path, so this couples desktop-apps to desktop-sdk internals outside its
    public interface. Please use the configured include dirs, and if NSX2T::Convert is to be
    consumed from another repo, promote it to lib/include in desktop-sdk first.
  • 6. The font cache is never generated in this path — likely broken on a clean profile.
    Normal startup sets the font paths and then calls AscAppManager::getInstance().CheckFonts()
    (main.cpp:190); the CLI returns at line ~68, long before that. docautomation.cpp relies on
    SetUserDataPath()'s default (applicationmanager.cpp:124, <appdata>/data/fonts) and points
    x2t's m_sAllFontsPath at …/data/fonts/AllFonts.js — a file only CheckFonts()/allfontsgen
    creates. So on a host where the GUI has never run (fresh install, container, automation-only
    server — i.e. the target use case) the very first --convert-to runs against a missing font
    cache. Please test on a clean profile; if it fails, call CheckFonts() synchronously (or run
    allfontsgen) when AllFonts.js is absent.

ℹ️ Minor / 💡 Suggestions

  • ℹ️ oManager.m_oSettings.file_converter_path = sAppPath + L"/converter" is redundant — that's
    already the default (applicationmanager.cpp:80).
  • ℹ️ InputArgs::contains/argument_value match the flag as a substring anywhere in the
    argument, so --no-convert-to=x would trigger ShouldRun(). Pre-existing, but this PR is what
    makes it reachable from a user-facing flag — worth an exact prefix match.
  • ℹ️ GetInputFileArg() returns the first argument not starting with --, so a single-dash
    option or a stray token becomes the input path. Also no check that the output directory exists
    or is writable — x2t's exit code is the only feedback.
  • 💡 Align with the convention automation users expect. LibreOffice's --convert-to takes a
    format plus --outdir (soffice --convert-to pdf --outdir /tmp in.docx); this takes a full
    output path. Either match it or say so explicitly in --help, since anyone scripting this will
    arrive with the LibreOffice muscle memory.
  • 💡 No tests. A tiny end-to-end check (convert a fixture docx→pdf, assert exit 0 and a non-empty
    PDF) would be cheap and would also have caught #1/#2 — especially given there's no build CI in
    this repo.

Code quality & conventions

  • The new code itself is good. docautomation.cpp is readable and the comments explain the
    non-obvious constraints — why a plain CAscApplicationManager instead of the QObject singleton,
    why the BOM had to go, why the .xml suffix is load-bearing. That last one is a genuinely
    useful discovery about x2t's argument parsing.
  • Commits: good incremental history with honest messages — right up to the last one, whose
    message doesn't match its contents (Blocking #1).
  • DCO: 6/6 signed off. ✓
  • License headers: see Blocking #3.
  • Scope: the intended scope is tight; the accidental scope isn't.
  • Tests: none, and no build CI in this repo to compensate.

Verdict

Request changes — the branch doesn't compile: the final commit pulled in save-format,
db-support and Wayland code that defines eight Utils:: members no header declares, and
src/utils.cpp must not join COMMON_SOURCES while prop/utils.cpp includes it. The two new
files also need a Euro-Office SPDX header instead of Ascensio's upstream one. Beyond the
mechanics, the design question deserves settling before merge: x2t already ships with the app
and already offers x2t <from> <to> [fontDir] [password], so these flags add discoverability
rather than capability while exposing a strict subset of it. That can still be the right call —
but let's justify it as "a stable front door for an internal tool", wrap x2t's full argument set,
and fix the description's claim of a QAscPrinterContext print path that isn't there. DocBuilder,
for the record, is not duplicated here — it's a DocumentServer component and isn't part of the
desktop package (see DocumentServer#321).

Assisted-by: ClaudeCode:claude-opus-5

Also fixes fallout from a bad rebase that had swept in ~120 lines of
unrelated default-save-format, db-support and Wayland-migration code
into utils.cpp (none of it declared in utils.h, so it didn't compile);
utils.cpp is reset to main plus just this guard. src/utils.cpp is
dropped from COMMON_SOURCES since win-linux/src/prop/utils.cpp already
includes it, so listing it separately double-defined every symbol in
it. docautomation.cpp/.h now carry a Euro-Office SPDX header instead of
Ascensio's upstream one, and use the configured include dirs for
applicationmanager.h/x2t.h and the core/DesktopEditor headers instead
of relative ../../../ paths.

Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
@pplupo
pplupo force-pushed the feature/document-automation-cli branch from df54a58 to 22d3143 Compare August 18, 2026 15:22
@pplupo

pplupo commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review — all three blocking issues and the private-header point are addressed.

1. Build break — confirmed: the last commit had swept in ~120 lines from other branches (default-save-format, db-support, Wayland DPI code) into utils.cpp. Reset it to main and reapplied only the argument_value null-guard. Verified the diff against main is now just that guard (+8/-6).

2. Duplicate COMMON_SOURCES entry — dropped src/utils.cpp; only src/docautomation.cpp needed adding, since prop/utils.cpp already #includes it.

3. License headersdocautomation.cpp/.h now use the Euro-Office SPDX header you suggested. Agreed it's worth settling as a project-wide convention; I'll leave that to a separate pass across #30 rather than bundling it here.

5. Private-header include — took the heavier option here: promoted x2t.h from desktop-sdk's lib/src/ (private) to lib/include/ (public), alongside applicationmanager.h, and fixed the three in-tree consumers plus both build-file source lists. docautomation.cpp now includes it the same way every other file in this repo includes its desktop-sdk/core dependencies — via the configured include dirs, no ../../../. That's pplupo/Euro-Office_desktop-sdk@feature/document-automation-cli. This PR now depends on that landing first.

6. Font cache — good catch, this would have broken on exactly the target use case (fresh install / container / CI). Added a synchronous oManager.CheckFonts(false) before building the task XML, matching what normal GUI startup does before touching a document.

4. --print-to-file / QAscPrinterContext — you're right, and I'd rather fix the claim than the code: there's no printer-context path here, it's the same x2t PDF export as --convert-to out.pdf with a forced extension. Updated the PR description and #58 to say that plainly instead of promising rendering-path parity it doesn't have.

On the design question (x2t vs. a documented front door) — agreed this deserves a deliberate answer rather than defaulting into it. I'm going with "stable front door for an internal tool" as the justification (updated in the description) rather than the original capability framing. Whether the flag set should grow to cover x2t's full TaskQueueDataConvert surface (password, page ranges, CSV options, PDF options) I'd treat as a follow-up once there's a concrete caller that needs it, rather than block this PR on it — happy to hear if you'd rather see that land together.

DocBuilder — agreed, no overlap, it's not part of the desktop package.

Left as-is for now (called out as minor/non-blocking, let me know if you want these in this PR too): the --no-convert-to substring-match reachability, GetInputFileArg's lack of an output-dir/writability check, and the LibreOffice --convert-to <format> --outdir convention question. No build CI in this repo and no tests either — open to adding a tiny convert-and-assert-exit-0 fixture test if that's wanted before merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants