Line data Source code
1 : // Copyright (c) 2011-2014 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.
4 :
5 : #include "intro.h"
6 : #include "ui_intro.h"
7 :
8 : #include "guiutil.h"
9 :
10 : #include "util.h"
11 :
12 : #include <boost/filesystem.hpp>
13 :
14 : #include <QFileDialog>
15 : #include <QSettings>
16 : #include <QMessageBox>
17 :
18 : /* Minimum free space (in bytes) needed for data directory */
19 : static const uint64_t GB_BYTES = 1000000000LL;
20 : static const uint64_t BLOCK_CHAIN_SIZE = 20LL * GB_BYTES;
21 :
22 : /* Check free space asynchronously to prevent hanging the UI thread.
23 :
24 : Up to one request to check a path is in flight to this thread; when the check()
25 : function runs, the current path is requested from the associated Intro object.
26 : The reply is sent back through a signal.
27 :
28 : This ensures that no queue of checking requests is built up while the user is
29 : still entering the path, and that always the most recently entered path is checked as
30 : soon as the thread becomes available.
31 : */
32 0 : class FreespaceChecker : public QObject
33 : {
34 0 : Q_OBJECT
35 :
36 : public:
37 : FreespaceChecker(Intro *intro);
38 :
39 : enum Status {
40 : ST_OK,
41 : ST_ERROR
42 : };
43 :
44 : public Q_SLOTS:
45 : void check();
46 :
47 : Q_SIGNALS:
48 : void reply(int status, const QString &message, quint64 available);
49 :
50 : private:
51 : Intro *intro;
52 : };
53 :
54 : #include "intro.moc"
55 :
56 0 : FreespaceChecker::FreespaceChecker(Intro *intro)
57 : {
58 0 : this->intro = intro;
59 0 : }
60 :
61 0 : void FreespaceChecker::check()
62 : {
63 : namespace fs = boost::filesystem;
64 0 : QString dataDirStr = intro->getPathToCheck();
65 0 : fs::path dataDir = GUIUtil::qstringToBoostPath(dataDirStr);
66 0 : uint64_t freeBytesAvailable = 0;
67 0 : int replyStatus = ST_OK;
68 0 : QString replyMessage = tr("A new data directory will be created.");
69 :
70 : /* Find first parent that exists, so that fs::space does not fail */
71 : fs::path parentDir = dataDir;
72 : fs::path parentDirOld = fs::path();
73 0 : while(parentDir.has_parent_path() && !fs::exists(parentDir))
74 : {
75 0 : parentDir = parentDir.parent_path();
76 :
77 : /* Check if we make any progress, break if not to prevent an infinite loop here */
78 0 : if (parentDirOld == parentDir)
79 : break;
80 :
81 : parentDirOld = parentDir;
82 : }
83 :
84 : try {
85 0 : freeBytesAvailable = fs::space(parentDir).available;
86 0 : if(fs::exists(dataDir))
87 : {
88 0 : if(fs::is_directory(dataDir))
89 : {
90 0 : QString separator = "<code>" + QDir::toNativeSeparators("/") + tr("name") + "</code>";
91 0 : replyStatus = ST_OK;
92 0 : replyMessage = tr("Directory already exists. Add %1 if you intend to create a new directory here.").arg(separator);
93 : } else {
94 0 : replyStatus = ST_ERROR;
95 0 : replyMessage = tr("Path already exists, and is not a directory.");
96 : }
97 : }
98 0 : } catch (const fs::filesystem_error&)
99 : {
100 : /* Parent directory does not exist or is not accessible */
101 0 : replyStatus = ST_ERROR;
102 0 : replyMessage = tr("Cannot create data directory here.");
103 : }
104 0 : Q_EMIT reply(replyStatus, replyMessage, freeBytesAvailable);
105 0 : }
106 :
107 :
108 0 : Intro::Intro(QWidget *parent) :
109 : QDialog(parent),
110 0 : ui(new Ui::Intro),
111 : thread(0),
112 0 : signalled(false)
113 : {
114 0 : ui->setupUi(this);
115 0 : ui->sizeWarningLabel->setText(ui->sizeWarningLabel->text().arg(BLOCK_CHAIN_SIZE/GB_BYTES));
116 0 : startThread();
117 0 : }
118 :
119 0 : Intro::~Intro()
120 : {
121 0 : delete ui;
122 : /* Ensure thread is finished before it is deleted */
123 0 : Q_EMIT stopThread();
124 0 : thread->wait();
125 0 : }
126 :
127 0 : QString Intro::getDataDirectory()
128 : {
129 0 : return ui->dataDirectory->text();
130 : }
131 :
132 0 : void Intro::setDataDirectory(const QString &dataDir)
133 : {
134 0 : ui->dataDirectory->setText(dataDir);
135 0 : if(dataDir == getDefaultDataDirectory())
136 : {
137 0 : ui->dataDirDefault->setChecked(true);
138 0 : ui->dataDirectory->setEnabled(false);
139 0 : ui->ellipsisButton->setEnabled(false);
140 : } else {
141 0 : ui->dataDirCustom->setChecked(true);
142 0 : ui->dataDirectory->setEnabled(true);
143 0 : ui->ellipsisButton->setEnabled(true);
144 : }
145 0 : }
146 :
147 0 : QString Intro::getDefaultDataDirectory()
148 : {
149 0 : return GUIUtil::boostPathToQString(GetDefaultDataDir());
150 : }
151 :
152 0 : void Intro::pickDataDirectory()
153 : {
154 : namespace fs = boost::filesystem;
155 0 : QSettings settings;
156 : /* If data directory provided on command line, no need to look at settings
157 : or show a picking dialog */
158 0 : if(!GetArg("-datadir", "").empty())
159 0 : return;
160 : /* 1) Default data directory for operating system */
161 0 : QString dataDir = getDefaultDataDirectory();
162 : /* 2) Allow QSettings to override default dir */
163 0 : dataDir = settings.value("strDataDir", dataDir).toString();
164 :
165 0 : if(!fs::exists(GUIUtil::qstringToBoostPath(dataDir)) || GetBoolArg("-choosedatadir", false))
166 : {
167 : /* If current default data directory does not exist, let the user choose one */
168 0 : Intro intro;
169 0 : intro.setDataDirectory(dataDir);
170 0 : intro.setWindowIcon(QIcon(":icons/bitcoin"));
171 :
172 : while(true)
173 : {
174 0 : if(!intro.exec())
175 : {
176 : /* Cancel clicked */
177 0 : exit(0);
178 : }
179 0 : dataDir = intro.getDataDirectory();
180 : try {
181 0 : TryCreateDirectory(GUIUtil::qstringToBoostPath(dataDir));
182 : break;
183 0 : } catch (const fs::filesystem_error&) {
184 : QMessageBox::critical(0, tr("Bitcoin Core"),
185 0 : tr("Error: Specified data directory \"%1\" cannot be created.").arg(dataDir));
186 : /* fall through, back to choosing screen */
187 : }
188 : }
189 :
190 0 : settings.setValue("strDataDir", dataDir);
191 : }
192 : /* Only override -datadir if different from the default, to make it possible to
193 : * override -datadir in the bitcoin.conf file in the default data directory
194 : * (to be consistent with bitcoind behavior)
195 : */
196 0 : if(dataDir != getDefaultDataDirectory())
197 0 : SoftSetArg("-datadir", GUIUtil::qstringToBoostPath(dataDir).string()); // use OS locale for path setting
198 : }
199 :
200 0 : void Intro::setStatus(int status, const QString &message, quint64 bytesAvailable)
201 : {
202 0 : switch(status)
203 : {
204 : case FreespaceChecker::ST_OK:
205 0 : ui->errorMessage->setText(message);
206 0 : ui->errorMessage->setStyleSheet("");
207 0 : break;
208 : case FreespaceChecker::ST_ERROR:
209 0 : ui->errorMessage->setText(tr("Error") + ": " + message);
210 0 : ui->errorMessage->setStyleSheet("QLabel { color: #800000 }");
211 0 : break;
212 : }
213 : /* Indicate number of bytes available */
214 0 : if(status == FreespaceChecker::ST_ERROR)
215 : {
216 0 : ui->freeSpace->setText("");
217 : } else {
218 0 : QString freeString = tr("%n GB of free space available", "", bytesAvailable/GB_BYTES);
219 0 : if(bytesAvailable < BLOCK_CHAIN_SIZE)
220 : {
221 0 : freeString += " " + tr("(of %n GB needed)", "", BLOCK_CHAIN_SIZE/GB_BYTES);
222 0 : ui->freeSpace->setStyleSheet("QLabel { color: #800000 }");
223 : } else {
224 0 : ui->freeSpace->setStyleSheet("");
225 : }
226 0 : ui->freeSpace->setText(freeString + ".");
227 : }
228 : /* Don't allow confirm in ERROR state */
229 0 : ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status != FreespaceChecker::ST_ERROR);
230 0 : }
231 :
232 0 : void Intro::on_dataDirectory_textChanged(const QString &dataDirStr)
233 : {
234 : /* Disable OK button until check result comes in */
235 0 : ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
236 0 : checkPath(dataDirStr);
237 0 : }
238 :
239 0 : void Intro::on_ellipsisButton_clicked()
240 : {
241 0 : QString dir = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(0, "Choose data directory", ui->dataDirectory->text()));
242 0 : if(!dir.isEmpty())
243 0 : ui->dataDirectory->setText(dir);
244 0 : }
245 :
246 0 : void Intro::on_dataDirDefault_clicked()
247 : {
248 0 : setDataDirectory(getDefaultDataDirectory());
249 0 : }
250 :
251 0 : void Intro::on_dataDirCustom_clicked()
252 : {
253 0 : ui->dataDirectory->setEnabled(true);
254 0 : ui->ellipsisButton->setEnabled(true);
255 0 : }
256 :
257 0 : void Intro::startThread()
258 : {
259 0 : thread = new QThread(this);
260 0 : FreespaceChecker *executor = new FreespaceChecker(this);
261 0 : executor->moveToThread(thread);
262 :
263 0 : connect(executor, SIGNAL(reply(int,QString,quint64)), this, SLOT(setStatus(int,QString,quint64)));
264 0 : connect(this, SIGNAL(requestCheck()), executor, SLOT(check()));
265 : /* make sure executor object is deleted in its own thread */
266 0 : connect(this, SIGNAL(stopThread()), executor, SLOT(deleteLater()));
267 0 : connect(this, SIGNAL(stopThread()), thread, SLOT(quit()));
268 :
269 0 : thread->start();
270 0 : }
271 :
272 0 : void Intro::checkPath(const QString &dataDir)
273 : {
274 0 : mutex.lock();
275 0 : pathToCheck = dataDir;
276 0 : if(!signalled)
277 : {
278 0 : signalled = true;
279 0 : Q_EMIT requestCheck();
280 : }
281 0 : mutex.unlock();
282 0 : }
283 :
284 0 : QString Intro::getPathToCheck()
285 : {
286 : QString retval;
287 0 : mutex.lock();
288 0 : retval = pathToCheck;
289 0 : signalled = false; /* new request can be queued now */
290 0 : mutex.unlock();
291 0 : return retval;
292 0 : }
|