Automation cli feature - #37
Conversation
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
left a comment
There was a problem hiding this comment.
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 (OpenFile→SaveFile), 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. |
|
"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. |
|
| 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.
df54a58beis described as "don't crash on a flag with no=value" — that's the 3-line
value_posguard inInputArgs::argument_value, which is fine and correct. But the same
commit is +131/-10 inutils.cppand 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, theplatformName() == "wayland"gate inuseGtkDialog, and
the removal ofUtils::getScreenDpiRatio(int)— Wayland-migration content.
None of it exists on
main, none of it belongs to this feature. And it cannot build:
Utilsis aclass(utils.h:80), so definingUtils::WatchForDpiChange,
Utils::defaultSaveFormat*,Utils::isDatabaseFileandUtils::warnIfDatabaseFileout of
line without declaring them in the class is a hard error ("no declaration matches …") — and
utils.his not among the PR's 6 files. Please resetutils.cppto main and re-apply only the
argument_valueguard. -
2.
src/utils.cppmust not be added toCOMMON_SOURCES.
win-linux/src/prop/utils.cpp:27does#include "../utils.cpp", and that file is already in
the list (CMakeLists.txt:184). Compilingsrc/utils.cppas its own translation unit as well
gives two definitions of everything in it (Utils::*,InputArgs::*,WindowHelper::*) →
duplicate-symbol link failure. Onlysrc/docautomation.cppneeds adding. -
3. The new files carry Ascensio's copyright header, not a Euro-Office one.
docautomation.cppanddocautomation.hboth 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-fileis an alias, and the description promises a printing path it doesn't have.
Either drop the flag (--convert-to out.pdfalready does exactly this) or implement it properly
viaQAscPrinterContextas #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-fileexpecting 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.cppuses#include "../../../desktop-sdk/ChromiumBasedEditors/lib/src/x2t.h"
(and../../../core/DesktopEditor/...). The build already provides these as include
directories —CMakeLists.txt:337-339addsdesktop-sdk/…/lib/include,…/qt_wrapper/include
andcore/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.his 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 ifNSX2T::Convertis to be
consumed from another repo, promote it tolib/includein 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 callsAscAppManager::getInstance().CheckFonts()
(main.cpp:190); the CLI returns at line ~68, long before that.docautomation.cpprelies on
SetUserDataPath()'s default (applicationmanager.cpp:124,<appdata>/data/fonts) and points
x2t'sm_sAllFontsPathat…/data/fonts/AllFonts.js— a file onlyCheckFonts()/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-toruns against a missing font
cache. Please test on a clean profile; if it fails, callCheckFonts()synchronously (or run
allfontsgen) whenAllFonts.jsis 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_valuematch the flag as a substring anywhere in the
argument, so--no-convert-to=xwould triggerShouldRun(). 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-totakes 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.cppis readable and the comments explain the
non-obvious constraints — why a plainCAscApplicationManagerinstead of the QObject singleton,
why the BOM had to go, why the.xmlsuffix 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>
df54a58 to
22d3143
Compare
|
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 2. Duplicate 3. License headers — 5. Private-header include — took the heavier option here: promoted 6. Font cache — good catch, this would have broken on exactly the target use case (fresh install / container / CI). Added a synchronous 4. 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 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 |
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 anyCEF/Qt startup. Both flags are the same code path:
--print-to-filejust forces a.pdfoutput suffix and calls the identical converter. There is no
QAscPrinterContextprint pathhere — this is a plain x2t PDF export, same as
--convert-to out.pdfwould 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.
x2talready ships next to the desktop binary and already acceptsx2t <from> <to>(or a fullTaskQueueDataConverttask XML). This PR doesn't add new conversion capability over that — itadds a stable, documented, discoverable front door to it: no internal-tool compatibility
contract, no font-cache-path knowledge required,
--helpsupport, and a name a script authorwould 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
utils.cpptomainand reapplied only theargument_valuenull-guard fix — theprevious revision had accidentally pulled in ~120 unrelated lines from other branches
(default-save-format, db-support, Wayland DPI code) that didn't compile.
src/utils.cppfromCOMMON_SOURCESinwin-linux/CMakeLists.txt(it's alreadycompiled via
prop/utils.cpp's#include; listing it again caused duplicate symbols).docautomation.cpp/.hnow carry a Euro-Office SPDX header instead of Ascensio's upstream one.../../../relative includes with the configured include dirs. This requiredpromoting
x2t.hfromdesktop-sdk's privatelib/src/to its publiclib/include/— seepplupo/Euro-Office_desktop-sdk@feature/document-automation-cli,
which this PR now depends on.
CheckFonts(false)call before conversion, so a profile that's never runthe GUI (fresh install, container, CI) gets its font cache built instead of failing on a
missing
AllFonts.js.