* @brief Atomically writes (or appends) UTF-8 content to a file under the 'AI/' write root. */
| 563 | * @brief Atomically writes (or appends) UTF-8 content to a file under the 'AI/' write root. |
| 564 | */ |
| 565 | static QJsonObject writeBytes(const QString& path, |
| 566 | const QByteArray& payload, |
| 567 | bool appendMode, |
| 568 | const QString& root) |
| 569 | { |
| 570 | const QFileInfo info(path); |
| 571 | if (info.isSymLink()) |
| 572 | return failure(QStringLiteral("outside_sandbox"), |
| 573 | QStringLiteral("Refusing to write through a symlinked target.")); |
| 574 | |
| 575 | if (!QDir().mkpath(info.absolutePath())) |
| 576 | return failure(QStringLiteral("mkdir_failed"), info.absolutePath()); |
| 577 | |
| 578 | if (appendMode) { |
| 579 | QFile file(path); |
| 580 | if (!file.open(QIODevice::Append)) |
| 581 | return failure(QStringLiteral("open_failed"), file.errorString()); |
| 582 | |
| 583 | if (file.write(payload) != payload.size()) |
| 584 | return failure(QStringLiteral("write_failed"), file.errorString()); |
| 585 | |
| 586 | file.close(); |
| 587 | } else { |
| 588 | QSaveFile file(path); |
| 589 | if (!file.open(QIODevice::WriteOnly)) |
| 590 | return failure(QStringLiteral("open_failed"), file.errorString()); |
| 591 | |
| 592 | if (file.write(payload) != payload.size()) |
| 593 | return failure(QStringLiteral("write_failed"), file.errorString()); |
| 594 | |
| 595 | if (!file.commit()) |
| 596 | return failure(QStringLiteral("commit_failed"), file.errorString()); |
| 597 | } |
| 598 | |
| 599 | QJsonObject out; |
| 600 | out[QStringLiteral("ok")] = true; |
| 601 | out[QStringLiteral("path")] = displayPath(path, root); |
| 602 | out[QStringLiteral("bytesWritten")] = static_cast<double>(payload.size()); |
| 603 | return out; |
| 604 | } |
| 605 | |
| 606 | /** |
| 607 | * @brief Writes content to an 'AI/' file, replacing it atomically. |
no test coverage detected