1 // Copyright (c) 2011-2016 The Bitcoin Core developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 #if defined(HAVE_CONFIG_H)
6 #include "config/bitcoin-config.h"
17 #include <QFileDialog>
19 #include <QMessageBox>
23 static const uint64_t GB_BYTES
= 1000000000LL;
24 /* Minimum free space (in GB) needed for data directory */
25 static const uint64_t BLOCK_CHAIN_SIZE
= 120;
26 /* Minimum free space (in GB) needed for data directory when pruned; Does not include prune target */
27 static const uint64_t CHAIN_STATE_SIZE
= 2;
28 /* Total required space (in GB) depending on user choice (prune, not prune) */
29 static uint64_t requiredSpace
;
31 /* Check free space asynchronously to prevent hanging the UI thread.
33 Up to one request to check a path is in flight to this thread; when the check()
34 function runs, the current path is requested from the associated Intro object.
35 The reply is sent back through a signal.
37 This ensures that no queue of checking requests is built up while the user is
38 still entering the path, and that always the most recently entered path is checked as
39 soon as the thread becomes available.
41 class FreespaceChecker
: public QObject
46 FreespaceChecker(Intro
*intro
);
57 void reply(int status
, const QString
&message
, quint64 available
);
65 FreespaceChecker::FreespaceChecker(Intro
*_intro
)
70 void FreespaceChecker::check()
72 QString dataDirStr
= intro
->getPathToCheck();
73 fs::path dataDir
= GUIUtil::qstringToBoostPath(dataDirStr
);
74 uint64_t freeBytesAvailable
= 0;
75 int replyStatus
= ST_OK
;
76 QString replyMessage
= tr("A new data directory will be created.");
78 /* Find first parent that exists, so that fs::space does not fail */
79 fs::path parentDir
= dataDir
;
80 fs::path parentDirOld
= fs::path();
81 while(parentDir
.has_parent_path() && !fs::exists(parentDir
))
83 parentDir
= parentDir
.parent_path();
85 /* Check if we make any progress, break if not to prevent an infinite loop here */
86 if (parentDirOld
== parentDir
)
89 parentDirOld
= parentDir
;
93 freeBytesAvailable
= fs::space(parentDir
).available
;
94 if(fs::exists(dataDir
))
96 if(fs::is_directory(dataDir
))
98 QString separator
= "<code>" + QDir::toNativeSeparators("/") + tr("name") + "</code>";
100 replyMessage
= tr("Directory already exists. Add %1 if you intend to create a new directory here.").arg(separator
);
102 replyStatus
= ST_ERROR
;
103 replyMessage
= tr("Path already exists, and is not a directory.");
106 } catch (const fs::filesystem_error
&)
108 /* Parent directory does not exist or is not accessible */
109 replyStatus
= ST_ERROR
;
110 replyMessage
= tr("Cannot create data directory here.");
112 Q_EMIT
reply(replyStatus
, replyMessage
, freeBytesAvailable
);
116 Intro::Intro(QWidget
*parent
) :
123 ui
->welcomeLabel
->setText(ui
->welcomeLabel
->text().arg(tr(PACKAGE_NAME
)));
124 ui
->storageLabel
->setText(ui
->storageLabel
->text().arg(tr(PACKAGE_NAME
)));
126 ui
->lblExplanation1
->setText(ui
->lblExplanation1
->text()
127 .arg(tr(PACKAGE_NAME
))
128 .arg(BLOCK_CHAIN_SIZE
)
132 ui
->lblExplanation2
->setText(ui
->lblExplanation2
->text().arg(tr(PACKAGE_NAME
)));
134 uint64_t pruneTarget
= std::max
<int64_t>(0, GetArg("-prune", 0));
135 requiredSpace
= BLOCK_CHAIN_SIZE
;
136 QString storageRequiresMsg
= tr("At least %1 GB of data will be stored in this directory, and it will grow over time.");
138 uint64_t prunedGBs
= std::ceil(pruneTarget
* 1024 * 1024.0 / GB_BYTES
);
139 if (prunedGBs
<= requiredSpace
) {
140 requiredSpace
= prunedGBs
;
141 storageRequiresMsg
= tr("Approximately %1 GB of data will be stored in this directory.");
143 ui
->lblExplanation3
->setVisible(true);
145 ui
->lblExplanation3
->setVisible(false);
147 requiredSpace
+= CHAIN_STATE_SIZE
;
148 ui
->sizeWarningLabel
->setText(
149 tr("%1 will download and store a copy of the Bitcoin block chain.").arg(tr(PACKAGE_NAME
)) + " " +
150 storageRequiresMsg
.arg(requiredSpace
) + " " +
151 tr("The wallet will also be stored in this directory.")
159 /* Ensure thread is finished before it is deleted */
164 QString
Intro::getDataDirectory()
166 return ui
->dataDirectory
->text();
169 void Intro::setDataDirectory(const QString
&dataDir
)
171 ui
->dataDirectory
->setText(dataDir
);
172 if(dataDir
== getDefaultDataDirectory())
174 ui
->dataDirDefault
->setChecked(true);
175 ui
->dataDirectory
->setEnabled(false);
176 ui
->ellipsisButton
->setEnabled(false);
178 ui
->dataDirCustom
->setChecked(true);
179 ui
->dataDirectory
->setEnabled(true);
180 ui
->ellipsisButton
->setEnabled(true);
184 QString
Intro::getDefaultDataDirectory()
186 return GUIUtil::boostPathToQString(GetDefaultDataDir());
189 bool Intro::pickDataDirectory()
192 /* If data directory provided on command line, no need to look at settings
193 or show a picking dialog */
194 if(!GetArg("-datadir", "").empty())
196 /* 1) Default data directory for operating system */
197 QString dataDir
= getDefaultDataDirectory();
198 /* 2) Allow QSettings to override default dir */
199 dataDir
= settings
.value("strDataDir", dataDir
).toString();
201 if(!fs::exists(GUIUtil::qstringToBoostPath(dataDir
)) || GetBoolArg("-choosedatadir", DEFAULT_CHOOSE_DATADIR
) || settings
.value("fReset", false).toBool() || GetBoolArg("-resetguisettings", false))
203 /* If current default data directory does not exist, let the user choose one */
205 intro
.setDataDirectory(dataDir
);
206 intro
.setWindowIcon(QIcon(":icons/bitcoin"));
215 dataDir
= intro
.getDataDirectory();
217 TryCreateDirectories(GUIUtil::qstringToBoostPath(dataDir
));
219 } catch (const fs::filesystem_error
&) {
220 QMessageBox::critical(0, tr(PACKAGE_NAME
),
221 tr("Error: Specified data directory \"%1\" cannot be created.").arg(dataDir
));
222 /* fall through, back to choosing screen */
226 settings
.setValue("strDataDir", dataDir
);
227 settings
.setValue("fReset", false);
229 /* Only override -datadir if different from the default, to make it possible to
230 * override -datadir in the bitcoin.conf file in the default data directory
231 * (to be consistent with bitcoind behavior)
233 if(dataDir
!= getDefaultDataDirectory())
234 SoftSetArg("-datadir", GUIUtil::qstringToBoostPath(dataDir
).string()); // use OS locale for path setting
238 void Intro::setStatus(int status
, const QString
&message
, quint64 bytesAvailable
)
242 case FreespaceChecker::ST_OK
:
243 ui
->errorMessage
->setText(message
);
244 ui
->errorMessage
->setStyleSheet("");
246 case FreespaceChecker::ST_ERROR
:
247 ui
->errorMessage
->setText(tr("Error") + ": " + message
);
248 ui
->errorMessage
->setStyleSheet("QLabel { color: #800000 }");
251 /* Indicate number of bytes available */
252 if(status
== FreespaceChecker::ST_ERROR
)
254 ui
->freeSpace
->setText("");
256 QString freeString
= tr("%n GB of free space available", "", bytesAvailable
/GB_BYTES
);
257 if(bytesAvailable
< requiredSpace
* GB_BYTES
)
259 freeString
+= " " + tr("(of %n GB needed)", "", requiredSpace
);
260 ui
->freeSpace
->setStyleSheet("QLabel { color: #800000 }");
262 ui
->freeSpace
->setStyleSheet("");
264 ui
->freeSpace
->setText(freeString
+ ".");
266 /* Don't allow confirm in ERROR state */
267 ui
->buttonBox
->button(QDialogButtonBox::Ok
)->setEnabled(status
!= FreespaceChecker::ST_ERROR
);
270 void Intro::on_dataDirectory_textChanged(const QString
&dataDirStr
)
272 /* Disable OK button until check result comes in */
273 ui
->buttonBox
->button(QDialogButtonBox::Ok
)->setEnabled(false);
274 checkPath(dataDirStr
);
277 void Intro::on_ellipsisButton_clicked()
279 QString dir
= QDir::toNativeSeparators(QFileDialog::getExistingDirectory(0, "Choose data directory", ui
->dataDirectory
->text()));
281 ui
->dataDirectory
->setText(dir
);
284 void Intro::on_dataDirDefault_clicked()
286 setDataDirectory(getDefaultDataDirectory());
289 void Intro::on_dataDirCustom_clicked()
291 ui
->dataDirectory
->setEnabled(true);
292 ui
->ellipsisButton
->setEnabled(true);
295 void Intro::startThread()
297 thread
= new QThread(this);
298 FreespaceChecker
*executor
= new FreespaceChecker(this);
299 executor
->moveToThread(thread
);
301 connect(executor
, SIGNAL(reply(int,QString
,quint64
)), this, SLOT(setStatus(int,QString
,quint64
)));
302 connect(this, SIGNAL(requestCheck()), executor
, SLOT(check()));
303 /* make sure executor object is deleted in its own thread */
304 connect(this, SIGNAL(stopThread()), executor
, SLOT(deleteLater()));
305 connect(this, SIGNAL(stopThread()), thread
, SLOT(quit()));
310 void Intro::checkPath(const QString
&dataDir
)
313 pathToCheck
= dataDir
;
317 Q_EMIT
requestCheck();
322 QString
Intro::getPathToCheck()
326 retval
= pathToCheck
;
327 signalled
= false; /* new request can be queued now */