Files
qemby/src/qEmbyApp/quick/loginviewmodel.cpp
T

314 lines
12 KiB
C++

#include "loginviewmodel.h"
#include "../components/modernmessagebox.h"
#include "../components/proxysettingsdialog.h"
#include "../components/webdavsyncdialog.h"
#include <QApplication>
#include <QTimer>
#include <QUrl>
#include <QWidget>
#include <config/config_keys.h>
#include <config/configstore.h>
#include <config/webdavprofilestore.h>
#include <qembycore.h>
#include <services/auth/authservice.h>
#include <services/manager/servermanager.h>
#include <algorithm>
LoginServerListModel::LoginServerListModel(QObject *parent)
: QAbstractListModel(parent)
{
}
int LoginServerListModel::rowCount(const QModelIndex &parent) const
{
return parent.isValid() ? 0 : m_servers.size();
}
QVariant LoginServerListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() < 0 || index.row() >= m_servers.size())
return {};
const auto &server = m_servers.at(index.row());
switch (role) {
case ServerIdRole: return server.id;
case NameRole: return server.name;
case UrlRole: return server.url;
case IconSourceRole:
if (server.type == ServerProfile::Jellyfin)
return QStringLiteral("qrc:/svg/jellyfin.svg");
if (!server.iconBase64.isEmpty())
return QStringLiteral("data:image/png;base64,") + server.iconBase64;
return QStringLiteral("qrc:/svg/emby.svg");
default: return {};
}
}
QHash<int, QByteArray> LoginServerListModel::roleNames() const
{
return {{ServerIdRole, "serverId"}, {NameRole, "name"},
{UrlRole, "serverUrl"}, {IconSourceRole, "iconSource"}};
}
void LoginServerListModel::resetServers(const QList<ServerProfile> &servers)
{
beginResetModel();
m_servers = servers;
endResetModel();
}
LoginViewModel::LoginViewModel(QEmbyCore *core, QWidget *dialogParent,
QObject *parent)
: QObject(parent), m_core(core), m_dialogParent(dialogParent), m_servers(this)
{
QString organization = qApp->organizationName();
QString version = qApp->applicationVersion();
if (organization.isEmpty()) organization = QStringLiteral("AlanHJ");
if (version.isEmpty()) version = QStringLiteral("1.0.0");
m_versionText = QStringLiteral("%1 v%2").arg(organization, version);
}
void LoginViewModel::setProtocol(const QString &value)
{
const QString normalized = value.startsWith(QStringLiteral("https"), Qt::CaseInsensitive)
? QStringLiteral("https://") : QStringLiteral("http://");
if (m_protocol == normalized) return;
m_protocol = normalized;
if (m_protocol != QStringLiteral("https://")) m_ignoreSsl = false;
emit formChanged();
}
void LoginViewModel::setAddress(const QString &value)
{
if (m_address == value) return;
m_address = value;
if (value.trimmed().startsWith(QStringLiteral("https://"), Qt::CaseInsensitive))
m_protocol = QStringLiteral("https://");
else if (value.trimmed().startsWith(QStringLiteral("http://"), Qt::CaseInsensitive))
m_protocol = QStringLiteral("http://");
emit formChanged();
}
void LoginViewModel::setPort(const QString &value)
{ if (m_port != value) { m_port = value; emit formChanged(); } }
void LoginViewModel::setUsername(const QString &value)
{ if (m_username != value) { m_username = value; emit formChanged(); } }
void LoginViewModel::setPassword(const QString &value)
{ if (m_password != value) { m_password = value; emit formChanged(); } }
void LoginViewModel::setIgnoreSsl(bool value)
{ if (m_ignoreSsl != value) { m_ignoreSsl = value; emit formChanged(); } }
void LoginViewModel::setBusy(bool value)
{ if (m_busy != value) { m_busy = value; emit busyChanged(); } }
void LoginViewModel::setError(const QString &value)
{ if (m_errorText != value) { m_errorText = value; emit errorTextChanged(); } }
void LoginViewModel::setEditorVisible(bool value)
{ if (m_editorVisible != value) { m_editorVisible = value; emit editorVisibleChanged(); } }
void LoginViewModel::refreshServers()
{
const auto values = m_core->serverManager()->servers();
m_servers.resetServers(values);
const bool hasServersNow = !values.isEmpty();
if (m_hasServers != hasServersNow) {
m_hasServers = hasServersNow;
emit serversChanged();
}
if (!m_hasServers) showAdd();
}
void LoginViewModel::activate()
{
setBusy(false);
refreshServers();
if (m_autoLoginAttempted) return;
m_autoLoginAttempted = true;
const bool remember = ConfigStore::instance()->get<bool>(ConfigKeys::RememberServer, false);
const QString lastId = ConfigStore::instance()->get<QString>(ConfigKeys::LastSelectedServerId);
if (!remember || lastId.isEmpty()) return;
const auto values = m_core->serverManager()->servers();
const auto it = std::find_if(values.cbegin(), values.cend(),
[&lastId](const ServerProfile &s) { return s.id == lastId; });
if (it != values.cend())
QTimer::singleShot(800, this, [this, lastId] { selectServer(lastId); });
}
void LoginViewModel::clearForm()
{
m_protocol = QStringLiteral("http://");
m_address.clear(); m_port.clear(); m_username.clear(); m_password.clear();
m_ignoreSsl = false; m_editingServerId.clear();
m_pendingProxy = ProxyConfig{}; m_pendingUseGlobalProxy = false;
setError({});
emit formChanged(); emit proxySummaryChanged();
}
void LoginViewModel::showAdd()
{
clearForm();
setEditorVisible(true);
}
void LoginViewModel::cancelEdit()
{
if (!m_hasServers) return;
clearForm();
setEditorVisible(false);
}
void LoginViewModel::editServer(const QString &serverId)
{
const auto values = m_core->serverManager()->servers();
for (const auto &server : values) {
if (server.id != serverId) continue;
m_editingServerId = server.id;
const QUrl url(server.url);
m_protocol = url.scheme().compare(QStringLiteral("https"), Qt::CaseInsensitive) == 0
? QStringLiteral("https://") : QStringLiteral("http://");
QString host = url.host(QUrl::FullyDecoded);
if (host.contains(':') && !host.startsWith('[')) host = QStringLiteral("[%1]").arg(host);
if (!url.path().isEmpty() && url.path() != QStringLiteral("/")) host += url.path();
m_address = host.isEmpty() ? server.url : host;
m_port = url.port(-1) > 0 ? QString::number(url.port()) : QString();
m_username = server.userName; m_password.clear();
m_ignoreSsl = server.ignoreSslVerification;
m_pendingProxy = server.proxy; m_pendingUseGlobalProxy = server.useGlobalProxy;
setError({}); emit formChanged(); emit proxySummaryChanged();
setEditorVisible(true);
return;
}
}
void LoginViewModel::removeServer(const QString &serverId)
{
if (!m_dialogParent) return;
const bool confirmed = ModernMessageBox::question(
m_dialogParent, tr("Remove Server"),
tr("Are you sure you want to remove this server?\nThis action cannot be undone."),
tr("Remove"), tr("Cancel"), ModernMessageBox::Danger);
if (confirmed) { m_core->serverManager()->removeServer(serverId); refreshServers(); }
}
QUrl LoginViewModel::normalizedUrl(QString *error) const
{
QString candidate = m_address.trimmed();
if (candidate.isEmpty()) { *error = tr("Please enter a valid server address."); return {}; }
if (!candidate.startsWith(QStringLiteral("http://"), Qt::CaseInsensitive) &&
!candidate.startsWith(QStringLiteral("https://"), Qt::CaseInsensitive))
candidate.prepend(m_protocol);
QUrl url(candidate, QUrl::TolerantMode);
if (!url.isValid() || url.host().isEmpty() ||
(url.scheme().compare(QStringLiteral("http"), Qt::CaseInsensitive) != 0 &&
url.scheme().compare(QStringLiteral("https"), Qt::CaseInsensitive) != 0)) {
*error = tr("Please enter a valid HTTP or HTTPS server address."); return {};
}
if (!m_port.trimmed().isEmpty()) {
bool ok = false; const int value = m_port.toInt(&ok);
if (!ok || value < 1 || value > 65535) { *error = tr("Please enter a valid port number."); return {}; }
url.setPort(value);
}
url.setUserName(QString());
url.setPassword(QString());
url.setQuery(QString());
url.setFragment(QString());
QString path = url.path();
while (path.size() > 1 && path.endsWith('/')) path.chop(1);
if (path == QStringLiteral("/")) path.clear();
url.setPath(path);
return url;
}
void LoginViewModel::selectServer(const QString &serverId)
{ if (!m_busy) validateServer(serverId); }
QCoro::Task<void> LoginViewModel::validateServer(QString serverId)
{
QPointer<LoginViewModel> guard(this); setError({}); setBusy(true);
try {
co_await m_core->authService()->validateSession(serverId);
if (!guard) co_return;
setBusy(false);
ConfigStore::instance()->set(ConfigKeys::LastSelectedServerId, serverId);
emit loginCompleted();
} catch (const std::exception &e) {
if (!guard) co_return;
setBusy(false); refreshServers(); editServer(serverId);
setError(tr("Failed to connect or session expired. Please log in again.\nError: %1")
.arg(QString::fromUtf8(e.what())));
}
}
void LoginViewModel::submit()
{ if (!m_busy) performLogin(); }
QCoro::Task<void> LoginViewModel::performLogin()
{
if (m_address.trimmed().isEmpty() || m_username.trimmed().isEmpty()) {
setError(tr("Server address and username cannot be empty.")); co_return;
}
QString error; const QUrl url = normalizedUrl(&error);
if (!error.isEmpty()) { setError(error); co_return; }
const bool ignoreSsl = url.scheme().compare(QStringLiteral("https"), Qt::CaseInsensitive) == 0 && m_ignoreSsl;
QPointer<LoginViewModel> guard(this); setError({}); setBusy(true);
try {
ServerProfile profile = co_await m_core->authService()->login(
url.toString(QUrl::FullyEncoded), m_username.trimmed(), m_password, ignoreSsl);
if (!guard) co_return;
ConfigStore::instance()->set(ConfigKeys::LastSelectedServerId, profile.id);
if (!m_editingServerId.isEmpty()) m_core->serverManager()->removeServer(m_editingServerId);
if (m_pendingProxy != ProxyConfig{} || m_pendingUseGlobalProxy)
m_core->serverManager()->updateServerProxy(profile.id, m_pendingProxy, m_pendingUseGlobalProxy);
setBusy(false); emit loginCompleted();
} catch (const std::exception &e) {
if (!guard) co_return;
setBusy(false); setError(QString::fromUtf8(e.what()));
}
}
QString LoginViewModel::proxySummary() const
{
if (m_pendingUseGlobalProxy) return tr("Using global proxy");
switch (m_pendingProxy.mode) {
case ProxyConfig::None: return tr("No proxy");
case ProxyConfig::System: return tr("System proxy");
case ProxyConfig::Custom:
if (m_pendingProxy.host.isEmpty() || m_pendingProxy.port == 0) return tr("Custom (incomplete)");
return QStringLiteral("%1 %2:%3").arg(
m_pendingProxy.type == ProxyConfig::Socks5 ? QStringLiteral("SOCKS5") : QStringLiteral("HTTP"),
m_pendingProxy.host, QString::number(m_pendingProxy.port));
}
return {};
}
void LoginViewModel::configureProxy()
{
if (!m_dialogParent) return;
ProxySettingsDialog *dialog = nullptr;
if (!m_editingServerId.isEmpty())
dialog = ProxySettingsDialog::createForServer(m_core->serverManager(), m_editingServerId, m_dialogParent);
else
dialog = ProxySettingsDialog::createForDraft(m_pendingProxy, m_pendingUseGlobalProxy, m_dialogParent);
if (dialog->exec() == QDialog::Accepted) {
if (!m_editingServerId.isEmpty()) {
for (const auto &server : m_core->serverManager()->servers())
if (server.id == m_editingServerId) { m_pendingProxy = server.proxy; m_pendingUseGlobalProxy = server.useGlobalProxy; break; }
} else {
m_pendingProxy = dialog->resultConfig(); m_pendingUseGlobalProxy = dialog->resultUseGlobal();
}
emit proxySummaryChanged();
}
dialog->deleteLater();
}
void LoginViewModel::cloudSync()
{
if (!m_dialogParent) return;
if (!m_webdavStore) { m_webdavStore = new WebdavProfileStore(this); m_webdavStore->load(); }
WebdavSyncDialog dialog(m_webdavStore, m_core->serverManager(), m_dialogParent);
dialog.exec(); refreshServers();
}