File indexing completed on 2024-04-28 05:50:07

0001 /*
0002  * SPDX-License-Identifier: GPL-3.0-or-later
0003  * SPDX-FileCopyrightText: 2021 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
0004  */
0005 
0006 #include "flows_p.h"
0007 #include "cli.h"
0008 #include "state_p.h"
0009 #include "vms.h"
0010 
0011 #include "../logging_p.h"
0012 
0013 #include <KLocalizedString>
0014 #include <QTimer>
0015 
0016 KEYSMITH_LOGGER(logger, ".app.flows_p")
0017 
0018 namespace app
0019 {
0020     accounts::AccountStorage * storageOf(Keysmith *app)
0021     {
0022         Q_ASSERT_X(app, Q_FUNC_INFO, "should have a Keysmith application instance");
0023 
0024         auto accounts = app->store().accounts();
0025 
0026         Q_ASSERT_X(accounts, Q_FUNC_INFO, "should have an AccountStorage instance");
0027         return accounts;
0028     }
0029 
0030     Navigation * navigationFor(Keysmith *app)
0031     {
0032         auto nav = app->navigation();
0033         Q_ASSERT_X(nav, Q_FUNC_INFO, "should have a Navigation instance");
0034         return nav;
0035     }
0036 
0037     InitialFlow::InitialFlow(Keysmith *app) :
0038         QObject(app), m_started(false), m_uriToAdd(false), m_uriParsed(false), m_passwordPromptResolved(false),
0039         m_app(app), m_input(new model::AccountInput(this)),
0040         m_passwordRequest(new model::PasswordRequest(storageOf(app)->secret(), this))
0041     {
0042         QObject::connect(m_passwordRequest, &model::PasswordRequest::passwordRequestChanged,
0043                          this, &InitialFlow::resume);
0044         QObject::connect(m_passwordRequest, &model::PasswordRequest::passwordAccepted, this, &InitialFlow::resume);
0045         QObject::connect(accountListOf(m_app), &model::SimpleAccountListModel::loadedChanged,
0046                          this, &InitialFlow::resume);
0047     }
0048 
0049     void InitialFlow::run(const QCommandLineParser &parser)
0050     {
0051         flowStateOf(m_app)->setFlowRunning(true);
0052         overviewStateOf(m_app)->setActionsEnabled(false);
0053         m_started = true;
0054 
0055         const auto argv = parser.positionalArguments();
0056         if (argv.isEmpty()) {
0057             qCDebug(logger) << "No URIs to handle, moving on:" << this;
0058             QTimer::singleShot(0, this, &InitialFlow::resume);
0059             return;
0060         }
0061 
0062         qCDebug(logger) << "Will first parse given URI(s):" << this;
0063 
0064         m_uriToAdd = true;
0065         auto job = new CommandLineAccountJob(m_input);
0066         QObject::connect(job, &CommandLineAccountJob::newAccountProcessed, this, &InitialFlow::onNewAccountProcessed);
0067         QObject::connect(job, &CommandLineAccountJob::newAccountInvalid, this, &InitialFlow::onNewAccountInvalid);
0068         job->run(argv[0]);
0069     }
0070 
0071     void InitialFlow::onNewAccountProcessed(void)
0072     {
0073         Q_ASSERT_X(m_started, Q_FUNC_INFO, "should have properly started the flow first");
0074 
0075         m_uriParsed = true;
0076 
0077         auto vm = new AddAccountViewModel(m_input, accountListOf(m_app), true, false);
0078         QObject::connect(vm, &AddAccountViewModel::accepted, this, &InitialFlow::resume);
0079         QObject::connect(vm, &AddAccountViewModel::cancelled, this, &InitialFlow::onNewAccountRejected);
0080         navigationFor(m_app)->navigate(Navigation::Page::AddAccount, vm);
0081     }
0082 
0083     void InitialFlow::onNewAccountAccepted(void)
0084     {
0085         Q_ASSERT_X(m_started, Q_FUNC_INFO, "should have properly started the flow first");
0086         Q_ASSERT_X(m_uriToAdd && m_uriParsed, Q_FUNC_INFO, "should have parsed URIs first");
0087 
0088         accountListOf(m_app)->addAccount(m_input);
0089         m_uriToAdd = false;
0090         QTimer::singleShot(0, this, &InitialFlow::resume);
0091     }
0092 
0093     void InitialFlow::onNewAccountRejected(void)
0094     {
0095         Q_ASSERT_X(m_uriToAdd && m_uriParsed, Q_FUNC_INFO, "should have parsed URIs first");
0096         m_uriToAdd = false;
0097         QTimer::singleShot(0, this, &InitialFlow::resume);
0098     }
0099 
0100     void InitialFlow::onNewAccountInvalid(void)
0101     {
0102         Q_ASSERT_X(m_started, Q_FUNC_INFO, "should have properly started the flow first");
0103 
0104         auto vm = new ErrorViewModel(
0105             i18nc("@title:window", "Invalid account"),
0106             i18nc("@info:label", "The account you are trying to add is invalid. You can either quit the app, or continue without adding the account."),
0107             true
0108         );
0109         QObject::connect(vm, &ErrorViewModel::dismissed, this, &InitialFlow::onNewAccountRejected);
0110         navigationFor(m_app)->navigate(Navigation::Page::Error, vm);
0111     }
0112 
0113     void InitialFlow::resume(void)
0114     {
0115         if (!m_started) {
0116             qCDebug(logger) << "Blocking progress: flow has not been started yet:" << this;
0117             return;
0118         }
0119 
0120         if (m_uriToAdd && !m_uriParsed) {
0121             qCDebug(logger) << "Blocking progress: URI parsing has not completed yet:" << this;
0122             return;
0123         }
0124 
0125         if (m_passwordRequest->keyAvailable()) {
0126             if (m_uriToAdd) {
0127                 const auto accounts = accountListOf(m_app);
0128                 if (!accounts->isAccountStillAvailable(m_input->name(), m_input->issuer())) {
0129                     auto vm = new RenameAccountViewModel(m_input, accountListOf(m_app));
0130                     QObject::connect(vm, &RenameAccountViewModel::cancelled, this, &InitialFlow::onNewAccountRejected);
0131                     QObject::connect(vm, &RenameAccountViewModel::accepted, this, &InitialFlow::onNewAccountAccepted);
0132                     navigationFor(m_app)->navigate(Navigation::Page::RenameAccount, vm);
0133                     return;
0134                 }
0135                 if (accounts->loaded()) {
0136                     QTimer::singleShot(0, this, &InitialFlow::onNewAccountAccepted);
0137                     return;
0138                 }
0139 
0140                 qCDebug(logger)
0141                     << "Blocking progress: accounts not fully loaded:"
0142                     << "Waiting to see if new account remains available:" << this;
0143                 return;
0144             }
0145 
0146             auto vm = new AccountsOverviewViewModel(m_app);
0147             navigationFor(m_app)->navigate(Navigation::Page::AccountsOverview, vm);
0148             overviewStateOf(m_app)->setActionsEnabled(true);
0149             auto flows = flowStateOf(m_app);
0150             flows->setFlowRunning(false);
0151             flows->setInitialFlowDone(true);
0152             QTimer::singleShot(0, this, &QObject::deleteLater);
0153             return;
0154         }
0155 
0156         if (!m_passwordPromptResolved) {
0157             if (m_passwordRequest->firstRun()) {
0158                 m_passwordPromptResolved = true;
0159                 auto vm = new SetupPasswordViewModel(m_passwordRequest);
0160                 navigationFor(m_app)->navigate(Navigation::Page::SetupPassword, vm);
0161                 return;
0162             }
0163             if (m_passwordRequest->previouslyDefined()) {
0164                 m_passwordPromptResolved = true;
0165                 auto vm = new UnlockAccountsViewModel(m_passwordRequest);
0166                 navigationFor(m_app)->navigate(Navigation::Page::UnlockAccounts, vm);
0167                 return;
0168             }
0169             qCDebug(logger) << "Blocking progress: password request has not yet been resolved:" << this;
0170             return;
0171         }
0172 
0173         qCDebug(logger) << "Blocking progress: waiting for the next event:" << this;
0174     }
0175 
0176     ManualAddAccountFlow::ManualAddAccountFlow(Keysmith *app) :
0177         QObject(app), m_app(app), m_input(new model::AccountInput(this))
0178     {
0179         Q_ASSERT_X(app, Q_FUNC_INFO, "should have a Keysmith instance");
0180     }
0181 
0182     void ManualAddAccountFlow::run(void)
0183     {
0184         flowStateOf(m_app)->setFlowRunning(true);
0185         overviewStateOf(m_app)->setActionsEnabled(false);
0186 
0187         auto vm = new AddAccountViewModel(m_input, accountListOf(m_app), false, true);
0188         QObject::connect(vm, &AddAccountViewModel::accepted, this, &ManualAddAccountFlow::onAccepted);
0189         QObject::connect(vm, &AddAccountViewModel::cancelled, this, &ManualAddAccountFlow::back);
0190         navigationFor(m_app)->push(Navigation::Page::AddAccount, vm);
0191     }
0192 
0193     void ManualAddAccountFlow::onAccepted(void)
0194     {
0195         accountListOf(m_app)->addAccount(m_input);
0196         QTimer::singleShot(0, this, &ManualAddAccountFlow::back);
0197     }
0198 
0199     void ManualAddAccountFlow::back(void)
0200     {
0201         auto vm = new AccountsOverviewViewModel(m_app);
0202         navigationFor(m_app)->navigate(Navigation::Page::AccountsOverview, vm);
0203         overviewStateOf(m_app)->setActionsEnabled(true);
0204         flowStateOf(m_app)->setFlowRunning(false);
0205         QTimer::singleShot(0, this, &QObject::deleteLater);
0206     }
0207 
0208     ExternalCommandLineFlow::ExternalCommandLineFlow(Keysmith *app) :
0209         QObject(app), m_app(app), m_input(new model::AccountInput(this))
0210     {
0211         Q_ASSERT_X(app, Q_FUNC_INFO, "should have a Keysmith instance");
0212     }
0213 
0214     void ExternalCommandLineFlow::run(const QCommandLineParser &parser)
0215     {
0216         const auto argv = parser.positionalArguments();
0217         if (argv.isEmpty()) {
0218             qCDebug(logger) << "No URIs to handle, nothing to do for external commandline:" << this;
0219             QTimer::singleShot(0, this, &ExternalCommandLineFlow::deleteLater);
0220             return;
0221         }
0222 
0223         flowStateOf(m_app)->setFlowRunning(true);
0224         overviewStateOf(m_app)->setActionsEnabled(false);
0225         qCDebug(logger) << "Will parse given URI(s) from external commandline:" << this;
0226 
0227         auto job = new CommandLineAccountJob(m_input);
0228         QObject::connect(job, &CommandLineAccountJob::newAccountProcessed,
0229                          this, &ExternalCommandLineFlow::onNewAccountProcessed);
0230         QObject::connect(job, &CommandLineAccountJob::newAccountInvalid,
0231                          this, &ExternalCommandLineFlow::onNewAccountInvalid);
0232         job->run(argv[0]);
0233     }
0234 
0235     void ExternalCommandLineFlow::onNewAccountProcessed(void)
0236     {
0237         auto vm = new AddAccountViewModel(m_input, accountListOf(m_app), false, true);
0238         QObject::connect(vm, &AddAccountViewModel::accepted, this, &ExternalCommandLineFlow::onAccepted);
0239         QObject::connect(vm, &AddAccountViewModel::cancelled, this, &ExternalCommandLineFlow::back);
0240         navigationFor(m_app)->push(Navigation::Page::AddAccount, vm);
0241     }
0242 
0243     void ExternalCommandLineFlow::onNewAccountInvalid(void)
0244     {
0245         auto vm = new ErrorViewModel(
0246             i18nc("@title:window", "Invalid account"),
0247             i18nc("@info:label", "The account you are trying to add is invalid. Continue without adding the account."),
0248             false
0249         );
0250         QObject::connect(vm, &ErrorViewModel::dismissed, this, &ExternalCommandLineFlow::back);
0251         navigationFor(m_app)->navigate(Navigation::Page::Error, vm);
0252     }
0253 
0254     void ExternalCommandLineFlow::onAccepted(void)
0255     {
0256         accountListOf(m_app)->addAccount(m_input);
0257         QTimer::singleShot(0, this, &ExternalCommandLineFlow::back);
0258     }
0259 
0260     void ExternalCommandLineFlow::back(void)
0261     {
0262         auto vm = new AccountsOverviewViewModel(m_app);
0263         navigationFor(m_app)->navigate(Navigation::Page::AccountsOverview, vm);
0264         overviewStateOf(m_app)->setActionsEnabled(true);
0265         flowStateOf(m_app)->setFlowRunning(false);
0266         QTimer::singleShot(0, this, &QObject::deleteLater);
0267     }
0268 }