Add Qt6 desktop uploader
A native Qt6/C++ desktop client for the gallery's POST /upload API, in desktop/ with its own flake (kept separate from the Go server module the same way android/ is). Single-photo parity with the Android app: - multipart upload with preemptive HTTP Basic auth and retry-aware outcome classification (uploadclient) - JPEG/PNG metadata stripped byte-for-byte to drop embedded GPS, EXIF orientation baked in, and a quality/scale valve for the 20M nginx cap; HEIC decodes via the qtimageformats plugin (imageprep) - base URL / username in QSettings, password in the system keychain via qtkeychain; https required and a trailing /upload stripped (settings) - settings and post screens in a QStackedWidget (mainwindow) Native Wayland or xcb is selected at runtime. Bulk upload, EXIF capture-date prefill, and moving image prep off the UI thread are left as follow-ups.
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
// Turns a picked image into a file the gallery's upload API accepts.
|
||||
//
|
||||
// Three things must be true of the result: no EXIF (the gallery publishes the
|
||||
// uploaded file verbatim as its full-size download, so embedded GPS would be
|
||||
// public), a .jpg or .png extension (server.go rejects anything else, HEIC
|
||||
// included), and under nginx's 20M body cap.
|
||||
//
|
||||
// JPEG and PNG take a lossless route — whole metadata segments are dropped
|
||||
// without touching the compressed image data — so the published file keeps the
|
||||
// original sensor pixels. Anything else, or a file whose EXIF orientation must
|
||||
// be baked in, is decoded and re-encoded as JPEG (HEIC via Qt's qtimageformats
|
||||
// plugin). The strippers are a direct port of the Android app's.
|
||||
class ImagePrep
|
||||
{
|
||||
public:
|
||||
struct Prepared {
|
||||
QString filePath;
|
||||
QString note; // non-empty when the app departed from a byte-exact copy
|
||||
QString error;
|
||||
bool ok() const { return error.isEmpty(); }
|
||||
};
|
||||
|
||||
// Writes the upload-ready file into cacheDir as baseName.(jpg|png).
|
||||
static Prepared prepare(const QString &sourcePath, const QString &cacheDir,
|
||||
const QString &baseName);
|
||||
|
||||
private:
|
||||
static QByteArray stripJpegSegments(const QByteArray &src); // empty on parse failure
|
||||
static QByteArray stripPngChunks(const QByteArray &src);
|
||||
static bool isJpeg(const QByteArray &b);
|
||||
static bool isPng(const QByteArray &b);
|
||||
};
|
||||
Reference in New Issue
Block a user