314 lines
12 KiB
C++
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();
|
|
}
|