ProjectBundle.h header
#include <ew/app/ProjectBundle.h>
Namespace ew::app
Functions
QMap< QString, QString > ew::app::bundleBinaryFingerprints(const QByteArray &bundleBytes)
The binary (media asset) entries of the bundle bundleBytes as a forward-slash relative path -> hex SHA-1 map – the bundle counterpart of readBinaryFingerprints, so external asset changes inside a replaced bundle are detected. Partitioned against bundleTextSnapshot by the same extension rule. An invalid archive yields an empty map.
QString ew::app::bundleSha256(const QByteArray &bytes)
Hex SHA-256 of bytes – the content fingerprint the working-copy metadata records for a bundle file, and the external-change detector compares against.
QMap< QString, QString > ew::app::bundleTextSnapshot(const QByteArray &bundleBytes)
The text entries of the bundle bundleBytes as a forward-slash relative path -> content map – the bundle counterpart of readTextSnapshot (the same extension partition, with CRLF normalized to LF exactly as QIODevice::Text does on read), so a bundle file replaced on disk by a sync client can feed the same three-way reconcile a folder project uses, without extracting to a temp folder. Unsafe, directory-marker, and dot-segment entries are skipped. An invalid archive yields an empty map.
ew::core::Result< void > ew::app::extractProjectBundle(const QString &bundlePath, const QString &destDir)
Extracts the bundle at bundlePath into destDir, which must not yet exist or must be an empty directory – the wipe of a stale working copy belongs to the caller that provably owns the folder, so the one destructive step never lives here. The bundle is fully validated BEFORE anything is written: it must be a well-formed ZIP containing project.json at its root, and every file entry's name must be a safe relative path – an absolute path, a drive or UNC prefix, or a .. segment (zip-slip) rejects the whole bundle as hostile. Directory-marker entries (a trailing '/') and entries with a dot-prefixed segment are skipped: our packer never writes them, but archives zipped by hand may carry them, and "a plain ZIP of the project folder renamed to .ewriter" deliberately opens.
bool ew::app::isProjectBundlePath(const QString &path)
True when path names a single-file .ewriter project bundle (by extension, case-insensitive). This is the routing predicate for every seam that accepts "a project
path": a .ewriter path goes through the bundle machinery, anything else is treated as a folder-of-text project directory. (See docs/SINGLE_FILE_PROJECTS.md.)
ew::core::Result< QByteArray > ew::app::packProjectBundle(const QString &projectDir)
Packs the folder-of-text project at projectDir into .ewriter bundle bytes: a ZIP of every file under the folder whose relative path has no dot-prefixed segment (VCS metadata, the derived index, and hidden files never enter a bundle), keyed by forward-slash relative path. Entries are sorted and makeZip uses a fixed timestamp, so packing the same folder twice yields byte-identical archives – the determinism the working-copy crash-recovery byte-compare relies on. Errors when the folder is missing, is not a project (no project.json at its root), or any file cannot be read: the bundle is the user's save artifact, so a partial pack would be silent data loss and is refused outright.
ew::core::Result< void > ew::app::writeProjectBundle(const QString &projectDir, const QString &bundlePath)
Packs projectDir (see packProjectBundle) and writes the archive to bundlePath atomically: QSaveFile commits (renames) only after a full write, so an interrupted save (crash, full disk) leaves the previous bundle intact rather than truncated. Parent directories of bundlePath are created as needed.