pin support

This commit is contained in:
2026-04-28 13:51:58 +08:00
parent 2556c2db83
commit 7c884a2185
9 changed files with 324 additions and 2 deletions
+70
View File
@@ -37,6 +37,8 @@ void AppController::initialize()
emit serverRunningChanged();
}
m_server->setReceivePin(m_settings->receivePin());
connect(m_discovery, &LocalSend::DiscoveryManager::deviceDiscovered,
this, &AppController::onDeviceDiscovered);
connect(m_discovery, &LocalSend::DiscoveryManager::deviceLost,
@@ -60,6 +62,10 @@ void AppController::initialize()
this, &AppController::onPrepareUploadResponse);
connect(m_httpClient, &LocalSend::HttpClient::prepareUploadError,
this, &AppController::onPrepareUploadError);
connect(m_httpClient, &LocalSend::HttpClient::prepareUploadPinRequired,
this, &AppController::onPrepareUploadPinRequired);
connect(m_httpClient, &LocalSend::HttpClient::prepareUploadTooManyAttempts,
this, &AppController::onPrepareUploadTooManyAttempts);
connect(m_httpClient, &LocalSend::HttpClient::uploadProgress,
this, &AppController::onUploadProgress);
connect(m_httpClient, &LocalSend::HttpClient::uploadCompleted,
@@ -379,6 +385,20 @@ bool AppController::hasPendingFiles() const
return !m_pendingFilesList.isEmpty();
}
QString AppController::receivePin() const
{
return m_settings->receivePin();
}
void AppController::setReceivePin(const QString& pin)
{
if (m_settings->receivePin() != pin) {
m_settings->setReceivePin(pin);
m_server->setReceivePin(pin);
emit receivePinChanged();
}
}
void AppController::addFiles(const QStringList& filePaths)
{
QMimeDatabase mimeDb;
@@ -449,6 +469,7 @@ void AppController::sendFiles(const QString& deviceFingerprint, const QStringLis
m_pendingSendPaths = filePaths;
m_currentFileIndex = 0;
m_sendProgress = 0.0;
m_pinFirstAttempt = true;
emit sendingChanged();
emit sendProgressChanged();
@@ -552,6 +573,55 @@ void AppController::onPrepareUploadError(const QString& error)
emit sendingChanged();
}
void AppController::onPrepareUploadPinRequired()
{
qDebug() << "[AppController] onPrepareUploadPinRequired, firstAttempt:" << m_pinFirstAttempt;
emit pinRequired(m_pinFirstAttempt);
m_pinFirstAttempt = false;
}
void AppController::onPrepareUploadTooManyAttempts()
{
qWarning() << "[AppController] onPrepareUploadTooManyAttempts";
emit sendError(QStringLiteral("Too many PIN attempts"));
m_currentSendSessionId.clear();
emit sendingChanged();
}
void AppController::retryWithPin(const QString& pin)
{
if (m_currentSendDeviceFingerprint.isEmpty() || !m_devices.contains(m_currentSendDeviceFingerprint)) {
m_currentSendSessionId.clear();
emit sendingChanged();
return;
}
LocalSend::Device target = m_devices[m_currentSendDeviceFingerprint];
QMap<QString, LocalSend::FileDto> files;
QMimeDatabase mimeDb;
for (int i = 0; i < m_pendingSendPaths.size(); ++i) {
QFileInfo info(m_pendingSendPaths[i]);
if (!info.exists()) {
continue;
}
LocalSend::FileDto fileDto;
fileDto.id = QString::number(i);
fileDto.fileName = info.fileName();
fileDto.size = info.size();
fileDto.fileType = mimeDb.mimeTypeForFile(info).name();
files.insert(fileDto.id, fileDto);
}
LocalSend::PrepareUploadRequestDto request;
request.info = buildRegisterDto();
request.files = files;
qDebug() << "[AppController] Retrying prepare-upload with PIN to" << target.ip;
m_httpClient->prepareUpload(target, request, pin);
}
void AppController::onUploadProgress(qint64 sent, qint64 total)
{
if (total > 0) {
+9
View File
@@ -23,6 +23,7 @@ class AppController : public QObject
Q_PROPERTY(double sendProgress READ sendProgress NOTIFY sendProgressChanged)
Q_PROPERTY(QVariantList pendingFiles READ pendingFiles NOTIFY pendingFilesChanged)
Q_PROPERTY(bool hasPendingFiles READ hasPendingFiles NOTIFY pendingFilesChanged)
Q_PROPERTY(QString receivePin READ receivePin WRITE setReceivePin NOTIFY receivePinChanged)
public:
explicit AppController(QObject* parent = nullptr);
@@ -49,6 +50,8 @@ public:
double sendProgress() const;
QVariantList pendingFiles() const;
bool hasPendingFiles() const;
QString receivePin() const;
void setReceivePin(const QString& pin);
Q_INVOKABLE void startDiscovery();
Q_INVOKABLE void stopDiscovery();
@@ -63,6 +66,7 @@ public:
Q_INVOKABLE void addFiles(const QStringList& filePaths);
Q_INVOKABLE void removePendingFile(int index);
Q_INVOKABLE void clearPendingFiles();
Q_INVOKABLE void retryWithPin(const QString& pin);
signals:
void aliasChanged();
@@ -81,6 +85,8 @@ signals:
void receiveError(const QString& sessionId, const QString& error);
void sendCompleted(const QString& sessionId);
void sendError(const QString& error);
void pinRequired(bool firstAttempt);
void receivePinChanged();
private slots:
void onDeviceDiscovered(const LocalSend::Device& device);
@@ -98,6 +104,8 @@ private slots:
void onPrepareUploadResponse(const LocalSend::PrepareUploadResponseDto& response);
void onPrepareUploadError(const QString& error);
void onPrepareUploadPinRequired();
void onPrepareUploadTooManyAttempts();
void onUploadProgress(qint64 sent, qint64 total);
void onUploadCompleted();
void onUploadError(const QString& error);
@@ -119,6 +127,7 @@ private:
QVariantList m_pendingFilesList;
int m_currentFileIndex = 0;
double m_sendProgress = 0.0;
bool m_pinFirstAttempt = true;
LocalSend::InfoDto buildInfoDto() const;
QString generateUniqueFilePath(const QString& baseDir, const QString& fileName) const;
+131
View File
@@ -286,9 +286,15 @@ ApplicationWindow {
function onSendError(error) {
sendProgressDialog.close()
pinDialog.close()
errorDialog.text = error
errorDialog.open()
}
function onPinRequired(firstAttempt) {
pinDialog.isFirstAttempt = firstAttempt
pinDialog.open()
}
}
Dialog {
@@ -317,6 +323,112 @@ ApplicationWindow {
}
}
Dialog {
id: pinDialog
anchors.centerIn: parent
modal: true
closePolicy: Popup.NoAutoClose
title: qsTr("PIN Required")
property bool isFirstAttempt: true
ColumnLayout {
spacing: 12
Label {
text: pinDialog.isFirstAttempt
? qsTr("The receiver requires a PIN to accept files.")
: qsTr("Invalid PIN. Please try again.")
wrapMode: Text.WordWrap
Layout.fillWidth: true
color: pinDialog.isFirstAttempt ? palette.text : "red"
}
TextField {
id: pinInput
Layout.fillWidth: true
placeholderText: qsTr("Enter PIN")
echoMode: TextInput.Password
focus: true
onAccepted: pinDialog.submitPin()
}
}
footer: DialogButtonBox {
Button {
text: qsTr("Cancel")
DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
}
Button {
text: qsTr("Submit")
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
enabled: pinInput.text.length > 0
}
}
onAccepted: submitPin()
onRejected: {
appController.cancelSend()
close()
}
function submitPin() {
if (pinInput.text.length > 0) {
appController.retryWithPin(pinInput.text)
pinInput.text = ""
}
}
onOpened: {
pinInput.text = ""
pinInput.forceActiveFocus()
}
}
Dialog {
id: setPinDialog
anchors.centerIn: parent
modal: true
title: qsTr("Set Receive PIN")
ColumnLayout {
spacing: 12
Label {
text: qsTr("Enter a PIN that senders must provide to transfer files to this device. Leave empty to disable.")
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
TextField {
id: setPinInput
Layout.fillWidth: true
placeholderText: qsTr("Enter PIN")
onAccepted: setPinDialog.accepted()
}
}
footer: DialogButtonBox {
Button {
text: qsTr("Cancel")
DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
}
Button {
text: qsTr("OK")
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
}
}
onAccepted: {
appController.receivePin = setPinInput.text
}
onOpened: {
setPinInput.text = appController.receivePin
setPinInput.forceActiveFocus()
}
}
function formatSize(bytes) {
if (bytes < 1024) return bytes + " B"
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB"
@@ -597,6 +709,25 @@ ApplicationWindow {
checked: appController.quickSave
onCheckedChanged: appController.quickSave = checked
}
Label { text: qsTr("Receive PIN:") }
RowLayout {
Layout.fillWidth: true
Label {
text: appController.receivePin.length > 0 ? qsTr("Enabled") : qsTr("Disabled")
color: appController.receivePin.length > 0 ? "green" : palette.mid
}
Item { Layout.fillWidth: true }
Button {
text: appController.receivePin.length > 0 ? qsTr("Change") : qsTr("Set PIN")
onClicked: setPinDialog.open()
}
Button {
text: qsTr("Remove")
visible: appController.receivePin.length > 0
onClicked: appController.receivePin = ""
}
}
}
FolderDialog {