#include "loginviewmodel.h" #include "../components/modernmessagebox.h" #include "../components/proxysettingsdialog.h" #include "../components/webdavsyncdialog.h" #include #include #include #include #include #include #include #include #include #include #include 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 LoginServerListModel::roleNames() const { return {{ServerIdRole, "serverId"}, {NameRole, "name"}, {UrlRole, "serverUrl"}, {IconSourceRole, "iconSource"}}; } void LoginServerListModel::resetServers(const QList &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(ConfigKeys::RememberServer, false); const QString lastId = ConfigStore::instance()->get(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 LoginViewModel::validateServer(QString serverId) { QPointer 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 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 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(); }