init
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(navidrome_client VERSION 0.1 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Quick Network)
|
||||
|
||||
qt_standard_project_setup(REQUIRES 6.8)
|
||||
|
||||
qt_add_executable(appnavidrome_client
|
||||
src/main.cpp
|
||||
src/navidrome/NavidromeApi.cpp
|
||||
src/navidrome/NavidromeApi.h
|
||||
)
|
||||
|
||||
qt_add_qml_module(appnavidrome_client
|
||||
URI Navidrome
|
||||
VERSION 1.0
|
||||
QML_FILES
|
||||
qml/Main.qml
|
||||
)
|
||||
|
||||
set_target_properties(appnavidrome_client PROPERTIES
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
|
||||
MACOSX_BUNDLE TRUE
|
||||
WIN32_EXECUTABLE TRUE
|
||||
)
|
||||
|
||||
target_include_directories(appnavidrome_client
|
||||
PRIVATE src
|
||||
)
|
||||
|
||||
target_link_libraries(appnavidrome_client
|
||||
PRIVATE Qt6::Quick Qt6::Network
|
||||
)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
install(TARGETS appnavidrome_client
|
||||
BUNDLE DESTINATION .
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
)
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": 3,
|
||||
"cmakeMinimumRequired": {
|
||||
"major": 3,
|
||||
"minor": 20,
|
||||
"patch": 0
|
||||
},
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "dev-debug",
|
||||
"displayName": "Debug (Nix Umgebung)",
|
||||
"description": "Debug Build mit Compiler aus der Flake",
|
||||
"generator": "Unix Makefiles",
|
||||
"binaryDir": "${sourceDir}/build",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
|
||||
"CMAKE_PREFIX_PATH": "$env{CMAKE_PREFIX_PATH}"
|
||||
},
|
||||
"environment": {
|
||||
"QT_QPA_PLATFORM": "wayland;xcb"
|
||||
}
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
{
|
||||
"name": "build-debug",
|
||||
"configurePreset": "dev-debug"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
ApplicationWindow {
|
||||
id: win
|
||||
width: 420
|
||||
height: 320
|
||||
visible: true
|
||||
title: "Navidrome Login"
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
spacing: 10
|
||||
|
||||
Label {
|
||||
text: "Server"
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: urlField
|
||||
Layout.fillWidth: true
|
||||
placeholderText: "https://navidrome.timvandenboom.eth64.de"
|
||||
text: "https://navidrome.timvandenboom.eth64.de"
|
||||
}
|
||||
|
||||
Label {
|
||||
text: "Username"
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: userField
|
||||
Layout.fillWidth: true
|
||||
placeholderText: "username"
|
||||
}
|
||||
|
||||
Label {
|
||||
text: "Password"
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: passField
|
||||
Layout.fillWidth: true
|
||||
placeholderText: "password"
|
||||
echoMode: TextInput.Password
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 10
|
||||
|
||||
Button {
|
||||
text: navidromeApi.busy ? "Connecting..." : "Login (Ping)"
|
||||
enabled: !navidromeApi.busy
|
||||
Layout.fillWidth: true
|
||||
|
||||
onClicked: {
|
||||
navidromeApi.ping(urlField.text, userField.text, passField.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
opacity: 0.2
|
||||
color: "white"
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
text: {
|
||||
if (navidromeApi.busy) return "Verbinde…"
|
||||
if (navidromeApi.lastError.length > 0) return "❌ " + navidromeApi.lastError
|
||||
if (navidromeApi.lastStatus.length > 0) return "✅ status: " + navidromeApi.lastStatus
|
||||
return "Noch nicht verbunden."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#include <QGuiApplication>
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QQmlContext>
|
||||
|
||||
#include "navidrome/NavidromeApi.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QGuiApplication app(argc, argv);
|
||||
|
||||
QQmlApplicationEngine engine;
|
||||
|
||||
NavidromeApi api;
|
||||
engine.rootContext()->setContextProperty("navidromeApi", &api);
|
||||
|
||||
QObject::connect(
|
||||
&engine, &QQmlApplicationEngine::objectCreationFailed,
|
||||
&app, []() { QCoreApplication::exit(1); },
|
||||
Qt::QueuedConnection
|
||||
);
|
||||
|
||||
engine.loadFromModule("Navidrome", "Main");
|
||||
return app.exec();
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
#include "NavidromeApi.h"
|
||||
|
||||
#include <QNetworkReply>
|
||||
#include <QUrlQuery>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
NavidromeApi::NavidromeApi(QObject *parent)
|
||||
: QObject(parent)
|
||||
{}
|
||||
|
||||
void NavidromeApi::setBusy(bool v)
|
||||
{
|
||||
if (m_busy == v) return;
|
||||
m_busy = v;
|
||||
emit busyChanged();
|
||||
}
|
||||
|
||||
void NavidromeApi::setLastError(const QString &e)
|
||||
{
|
||||
if (m_lastError == e) return;
|
||||
m_lastError = e;
|
||||
emit lastErrorChanged();
|
||||
}
|
||||
|
||||
void NavidromeApi::setLastStatus(const QString &s)
|
||||
{
|
||||
if (m_lastStatus == s) return;
|
||||
m_lastStatus = s;
|
||||
emit lastStatusChanged();
|
||||
}
|
||||
|
||||
void NavidromeApi::ping(const QString &baseUrl,
|
||||
const QString &user,
|
||||
const QString &password)
|
||||
{
|
||||
setLastError({});
|
||||
setLastStatus({});
|
||||
|
||||
QUrl url(baseUrl.trimmed());
|
||||
if (!url.isValid() || url.scheme().isEmpty()) {
|
||||
setLastError("Ungültige URL (z.B. http://localhost:4533)");
|
||||
emit pingFailed(lastError());
|
||||
return;
|
||||
}
|
||||
|
||||
// /rest/ping.view anhängen
|
||||
url.setPath("/rest/ping.view");
|
||||
|
||||
QUrlQuery q;
|
||||
q.addQueryItem("u", user);
|
||||
q.addQueryItem("p", password); // später Token-Auth (t/s)
|
||||
q.addQueryItem("v", "1.16.1");
|
||||
q.addQueryItem("c", "qt-navidrome");
|
||||
q.addQueryItem("f", "json");
|
||||
url.setQuery(q);
|
||||
|
||||
setBusy(true);
|
||||
|
||||
auto *reply = m_net.get(QNetworkRequest(url));
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
setBusy(false);
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
setLastError(reply->errorString());
|
||||
emit pingFailed(lastError());
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
// Optional: JSON prüfen, ob status == "ok"
|
||||
const QByteArray body = reply->readAll();
|
||||
const auto doc = QJsonDocument::fromJson(body);
|
||||
if (doc.isObject()) {
|
||||
const auto root = doc.object();
|
||||
const auto ssr = root.value("subsonic-response").toObject();
|
||||
const QString status = ssr.value("status").toString();
|
||||
if (!status.isEmpty()) setLastStatus(status);
|
||||
}
|
||||
|
||||
if (lastStatus().isEmpty())
|
||||
setLastStatus("ok");
|
||||
|
||||
emit pingOk();
|
||||
reply->deleteLater();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QUrl>
|
||||
|
||||
class NavidromeApi : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
|
||||
Q_PROPERTY(QString lastError READ lastError NOTIFY lastErrorChanged)
|
||||
Q_PROPERTY(QString lastStatus READ lastStatus NOTIFY lastStatusChanged)
|
||||
|
||||
public:
|
||||
explicit NavidromeApi(QObject *parent = nullptr);
|
||||
|
||||
// QML Entry: baseUrl als String, damit TextField direkt passt
|
||||
Q_INVOKABLE void ping(const QString &baseUrl,
|
||||
const QString &user,
|
||||
const QString &password);
|
||||
|
||||
bool busy() const { return m_busy; }
|
||||
QString lastError() const { return m_lastError; }
|
||||
QString lastStatus() const { return m_lastStatus; }
|
||||
|
||||
signals:
|
||||
void pingOk();
|
||||
void pingFailed(const QString &error);
|
||||
|
||||
void busyChanged();
|
||||
void lastErrorChanged();
|
||||
void lastStatusChanged();
|
||||
|
||||
private:
|
||||
QNetworkAccessManager m_net;
|
||||
|
||||
bool m_busy = false;
|
||||
QString m_lastError;
|
||||
QString m_lastStatus;
|
||||
|
||||
void setBusy(bool v);
|
||||
void setLastError(const QString &e);
|
||||
void setLastStatus(const QString &s);
|
||||
};
|
||||
Reference in New Issue
Block a user