77
88#include " accountmanager.h"
99#include " accountstate.h"
10+ #include " common/filesystembase.h"
11+ #include " configfile.h"
1012#include " creds/abstractcredentials.h"
1113#include " creds/webflowcredentials.h"
12- #include " common/filesystembase.h"
1314#include " folder.h"
1415#include " folderman.h"
1516#include " networkjobs.h"
17+ #include " theme.h"
18+
19+ #include < chrono>
20+ #include < iostream>
1621
1722#include < QDir>
1823#include < QGuiApplication>
1924#include < QJsonObject>
2025#include < QLoggingCategory>
26+ #include < QTimer>
2127
2228using namespace Qt ::StringLiterals;
2329
2430namespace OCC
2531{
2632Q_LOGGING_CATEGORY (lcAccountSetupCommandLineJob, " nextcloud.gui.accountsetupcommandlinejob" , QtInfoMsg)
2733
34+ // How long to wait for the keychain to confirm that the credentials were written before
35+ // finishing the setup without them.
36+ constexpr auto credentialsPersistTimeout = std::chrono::seconds(30 );
37+
2838AccountSetupFromCommandLineJob::AccountSetupFromCommandLineJob (QString appPassword,
2939 QString userId,
3040 QUrl serverUrl,
@@ -42,37 +52,77 @@ AccountSetupFromCommandLineJob::AccountSetupFromCommandLineJob(QString appPasswo
4252{
4353}
4454
55+ bool AccountSetupFromCommandLineJob::localSyncFolderRequired () const
56+ {
57+ #ifdef BUILD_FILE_PROVIDER_MODULE
58+ // Mirrors the guard in FolderMan::addFolder(): while the app-level File Provider mode
59+ // is enabled, a classic sync folder cannot be created at all. The account is synced
60+ // through the File Provider domain that is set up for it instead.
61+ return !ConfigFile ().macFileProviderModeEnabled ();
62+ #else
63+ return true ;
64+ #endif
65+ }
66+
67+ QString AccountSetupFromCommandLineJob::defaultLocalDirPath () const
68+ {
69+ const auto overrideLocalDir = ConfigFile ().overrideLocalDir ();
70+ auto localDirPath = overrideLocalDir;
71+
72+ if (localDirPath.isEmpty ()) {
73+ localDirPath = Theme::instance ()->defaultClientFolder ();
74+
75+ if (localDirPath.isEmpty ()) {
76+ return {};
77+ }
78+
79+ if (!QDir (localDirPath).isAbsolute ()) {
80+ localDirPath = QDir::homePath () + QLatin1Char (' /' ) + localDirPath;
81+ }
82+ }
83+
84+ auto serverUrlForFolder = _serverUrl;
85+ serverUrlForFolder.setUserName (_userId);
86+
87+ return FolderMan::instance ()->findGoodPathForNewSyncFolder (localDirPath,
88+ serverUrlForFolder,
89+ overrideLocalDir.isEmpty () ? FolderMan::GoodPathStrategy::AllowOnlyNewPath
90+ : FolderMan::GoodPathStrategy::AllowOverrideExistingPath);
91+ }
92+
4593bool AccountSetupFromCommandLineJob::handleAccountSetupFromCommandLine ()
4694{
4795 if (AccountManager::instance ()->accountFromUserId (QStringLiteral (" %1@%2" ).arg (_userId, _serverUrl.host ()))) {
4896 printAccountSetupFromCommandLineStatusAndExit (QStringLiteral (" Account %1 already exists!" ).arg (QDir::toNativeSeparators (_userId)), true );
4997 return false ;
5098 }
5199
52- if (_localDirPath. isEmpty ()) {
53- printAccountSetupFromCommandLineStatusAndExit (
54- QStringLiteral ( " Folder creation failed. Could not create local folder because the name is empty " ),
55- true );
56- return false ;
100+ if (! localSyncFolderRequired ()) {
101+ if (!_localDirPath. isEmpty ()) {
102+ qCInfo (lcAccountSetupCommandLineJob) << " Ignoring the given local folder, File Provider mode is enabled " ;
103+ _localDirPath. clear ( );
104+ }
57105 } else {
58- QDir dir (_localDirPath);
59- if (dir.exists () && !dir.isEmpty ()) {
106+ if (_localDirPath.isEmpty ()) {
107+ // The local folder is documented as optional, so fall back to the folder the
108+ // account wizard would suggest rather than refusing to set the account up.
109+ _localDirPath = defaultLocalDirPath ();
110+ }
111+
112+ if (_localDirPath.isEmpty ()) {
60113 printAccountSetupFromCommandLineStatusAndExit (
61- QStringLiteral (" Local folder %1 already exists and is non-empty! " ). arg ( QDir::toNativeSeparators (_localDirPath) ),
114+ QStringLiteral (" Could not determine a local folder to sync into. Please pass one with --localdirpath. " ),
62115 true );
63116 return false ;
64117 }
65118
66- qCInfo (lcAccountSetupCommandLineJob) << " Creating folder " << _localDirPath;
67- if (!dir .exists () && !dir. mkpath ( " . " )) {
119+ const QDir localDir ( _localDirPath) ;
120+ if (localDir .exists () && !localDir. isEmpty ( )) {
68121 printAccountSetupFromCommandLineStatusAndExit (
69- QStringLiteral (" Folder creation failed. Could not create local folder %1 " ).arg (QDir::toNativeSeparators (_localDirPath)),
122+ QStringLiteral (" Local folder %1 already exists and is non-empty! " ).arg (QDir::toNativeSeparators (_localDirPath)),
70123 true );
71124 return false ;
72125 }
73-
74- FileSystem::setFolderMinimumPermissions (_localDirPath);
75- Utility::setupFavLink (_localDirPath);
76126 }
77127
78128 const auto credentials = new WebFlowCredentials (_userId, _appPassword);
@@ -81,12 +131,16 @@ bool AccountSetupFromCommandLineJob::handleAccountSetupFromCommandLine()
81131 _account->setCredentials (credentials);
82132 _account->setCredentialSetting (u" user" _s, _userId);
83133 _account->setUrl (_serverUrl);
84- auto accountState = AccountManager::instance ()->addAccount (_account);
85- setupLocalSyncFolder (accountState);
86-
87- Q_EMIT _account->wantsAccountSaved (_account);
88134
135+ // The account is only added, saved and given a sync folder once the credentials have
136+ // been checked against the server, so that a failed setup leaves nothing behind. Both
137+ // that check and the keychain write are asynchronous: the job keeps running until
138+ // printAccountSetupFromCommandLineStatusAndExit() ends the event loop.
89139 if (_appPassword.isEmpty ()) {
140+ // Nothing to authenticate with, so the server cannot be asked for the dav user
141+ // either. Store what was given and let the user log in from the client later on.
142+ _account->setDavUser (_userId);
143+ accountSetupFromCommandLinePropfindHandleSuccess ();
90144 return true ;
91145 }
92146
@@ -112,19 +166,40 @@ void AccountSetupFromCommandLineJob::accountSetupFromCommandLinePropfindHandleSu
112166 const auto accountManager = AccountManager::instance ();
113167 const auto accountState = accountManager->addAccount (_account);
114168
115- // credentials->persist() (called by save()) is asynchronous — it chains
116- // multiple keychain write jobs before the password actually lands in the
117- // keychain. Wait for the final write to complete before exiting so that
118- // the credentials are not lost when the process quits.
119- connect (_account->credentials (), &AbstractCredentials::credentialsPersisted, this , [this , accountState]() {
169+ const auto finishAccountSetup = [this , accountState]() {
120170 if (!_localDirPath.isEmpty ()) {
121171 setupLocalSyncFolder (accountState);
122172 } else {
123173 qCInfo (lcAccountSetupCommandLineJob) << QStringLiteral (" Set up a new account without a folder." );
124174 printAccountSetupFromCommandLineStatusAndExit (QStringLiteral (" Account %1 setup from command line success." ).arg (_account->displayName ()), false );
125175 }
176+ };
177+
178+ // credentials->persist() (called by save()) is asynchronous — it chains
179+ // multiple keychain write jobs before the password actually lands in the
180+ // keychain. Wait for the final write to complete before exiting so that
181+ // the credentials are not lost when the process quits, but give up eventually:
182+ // a keychain that never answers must not turn the setup into a hang.
183+ const auto credentialsPersistTimer = new QTimer (this );
184+ credentialsPersistTimer->setSingleShot (true );
185+
186+ connect (_account->credentials (), &AbstractCredentials::credentialsPersisted, this , [credentialsPersistTimer, finishAccountSetup]() {
187+ if (!credentialsPersistTimer->isActive ()) {
188+ return ;
189+ }
190+
191+ credentialsPersistTimer->stop ();
192+ finishAccountSetup ();
193+ });
194+
195+ connect (credentialsPersistTimer, &QTimer::timeout, this , [finishAccountSetup]() {
196+ qCWarning (lcAccountSetupCommandLineJob) << " Timed out waiting for the credentials to be written to the keychain,"
197+ << " the account may have to be authenticated again" ;
198+ finishAccountSetup ();
126199 });
127200
201+ credentialsPersistTimer->start (credentialsPersistTimeout);
202+
128203 accountManager->save ();
129204}
130205
@@ -183,6 +258,22 @@ void AccountSetupFromCommandLineJob::accountSetupFromCommandLinePropfindHandleFa
183258
184259void AccountSetupFromCommandLineJob::setupLocalSyncFolder (AccountState *accountState)
185260{
261+ QDir localDir (_localDirPath);
262+ if (!localDir.exists ()) {
263+ qCInfo (lcAccountSetupCommandLineJob) << " Creating folder" << _localDirPath;
264+
265+ if (!localDir.mkpath (QStringLiteral (" ." ))) {
266+ AccountManager::instance ()->removeAccountState (accountState);
267+ printAccountSetupFromCommandLineStatusAndExit (
268+ QStringLiteral (" Folder creation failed. Could not create local folder %1" ).arg (QDir::toNativeSeparators (_localDirPath)),
269+ true );
270+ return ;
271+ }
272+ }
273+
274+ FileSystem::setFolderMinimumPermissions (_localDirPath);
275+ Utility::setupFavLink (_localDirPath);
276+
186277 FolderDefinition definition;
187278 definition.localPath = _localDirPath;
188279 definition.targetPath = FolderDefinition::prepareTargetPath (!_remoteDirPath.isEmpty () ? _remoteDirPath : QStringLiteral (" /" ));
@@ -209,7 +300,9 @@ void AccountSetupFromCommandLineJob::setupLocalSyncFolder(AccountState *accountS
209300 qCInfo (lcAccountSetupCommandLineJob) << QStringLiteral (" Folder %1 setup from command line success." ).arg (definition.localPath );
210301 printAccountSetupFromCommandLineStatusAndExit (QStringLiteral (" Account %1 setup from command line success." ).arg (_account->displayName ()), false );
211302 } else {
212- AccountManager::instance ()->deleteAccount (accountState);
303+ // Drop the account again, but keep the app password valid: it was passed in on the
304+ // command line and revoking it would make a retry impossible.
305+ AccountManager::instance ()->removeAccountState (accountState);
213306 printAccountSetupFromCommandLineStatusAndExit (
214307 QStringLiteral (" Account %1 setup from command line failed, due to folder creation failure." ).arg (_account->displayName ()),
215308 true );
@@ -220,8 +313,10 @@ void AccountSetupFromCommandLineJob::printAccountSetupFromCommandLineStatusAndEx
220313{
221314 if (isFailure) {
222315 qCWarning (lcAccountSetupCommandLineJob) << status;
316+ std::cerr << qUtf8Printable (status) << std::endl;
223317 } else {
224318 qCInfo (lcAccountSetupCommandLineJob) << status;
319+ std::cout << qUtf8Printable (status) << std::endl;
225320 }
226321 QTimer::singleShot (0 , this , [this , isFailure]() {
227322 this ->deleteLater ();
0 commit comments