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.
13 #include <boost/filesystem.hpp>
15 #include <QFileDialog>
17 #include <QMessageBox>
19 /* Minimum free space (in bytes) needed for data directory */
20 static const uint64_t GB_BYTES = 1000000000LL;
21 static const uint64_t BLOCK_CHAIN_SIZE = 20LL * GB_BYTES;
23 /* Check free space asynchronously to prevent hanging the UI thread.
25 Up to one request to check a path is in flight to this thread; when the check()
26 function runs, the current path is requested from the associated Intro object.
27 The reply is sent back through a signal.
29 This ensures that no queue of checking requests is built up while the user is
30 still entering the path, and that always the most recently entered path is checked as
31 soon as the thread becomes available.
33 class FreespaceChecker : public QObject
38 FreespaceChecker(Intro *intro);
49 void reply(int status, const QString &message, quint64 available);
57 FreespaceChecker::FreespaceChecker(Intro *intro)
62 void FreespaceChecker::check()
64 namespace fs = boost::filesystem;
65 QString dataDirStr = intro->getPathToCheck();
66 fs::path dataDir = GUIUtil::qstringToBoostPath(dataDirStr);
67 uint64_t freeBytesAvailable = 0;
68 int replyStatus = ST_OK;
69 QString replyMessage = tr("A new data directory will be created.");
71 /* Find first parent that exists, so that fs::space does not fail */
72 fs::path parentDir = dataDir;
73 fs::path parentDirOld = fs::path();
74 while(parentDir.has_parent_path() && !fs::exists(parentDir))
76 parentDir = parentDir.parent_path();
78 /* Check if we make any progress, break if not to prevent an infinite loop here */
79 if (parentDirOld == parentDir)
82 parentDirOld = parentDir;
86 freeBytesAvailable = fs::space(parentDir).available;
87 if(fs::exists(dataDir))
89 if(fs::is_directory(dataDir))
91 QString separator = "<code>" + QDir::toNativeSeparators("/") + tr("name") + "</code>";
93 replyMessage = tr("Directory already exists. Add %1 if you intend to create a new directory here.").arg(separator);
95 replyStatus = ST_ERROR;
96 replyMessage = tr("Path already exists, and is not a directory.");
99 } catch (const fs::filesystem_error&)
101 /* Parent directory does not exist or is not accessible */
102 replyStatus = ST_ERROR;
103 replyMessage = tr("Cannot create data directory here.");
105 Q_EMIT reply(replyStatus, replyMessage, freeBytesAvailable);
109 Intro::Intro(QWidget *parent) :
116 ui->sizeWarningLabel->setText(ui->sizeWarningLabel->text().arg(BLOCK_CHAIN_SIZE/GB_BYTES));
123 /* Ensure thread is finished before it is deleted */
128 QString Intro::getDataDirectory()
130 return ui->dataDirectory->text();
133 void Intro::setDataDirectory(const QString &dataDir)
135 ui->dataDirectory->setText(dataDir);
136 if(dataDir == getDefaultDataDirectory())
138 ui->dataDirDefault->setChecked(true);
139 ui->dataDirectory->setEnabled(false);
140 ui->ellipsisButton->setEnabled(false);
142 ui->dataDirCustom->setChecked(true);
143 ui->dataDirectory->setEnabled(true);
144 ui->ellipsisButton->setEnabled(true);
148 QString Intro::getDefaultDataDirectory()
150 return GUIUtil::boostPathToQString(GetDefaultDataDir());
153 void Intro::pickDataDirectory()
155 namespace fs = boost::filesystem;
157 /* If data directory provided on command line, no need to look at settings
158 or show a picking dialog */
159 if(!GetArg("-datadir", "").empty())
161 /* 1) Default data directory for operating system */
162 QString dataDir = getDefaultDataDirectory();
163 /* 2) Allow QSettings to override default dir */
164 dataDir = settings.value("strDataDir", dataDir).toString();
166 if(!fs::exists(GUIUtil::qstringToBoostPath(dataDir)) || GetBoolArg("-choosedatadir", false))
168 /* If current default data directory does not exist, let the user choose one */
170 intro.setDataDirectory(dataDir);
171 intro.setWindowIcon(SingleColorIcon(":icons/bitcoin"));
180 dataDir = intro.getDataDirectory();
182 TryCreateDirectory(GUIUtil::qstringToBoostPath(dataDir));
184 } catch (const fs::filesystem_error&) {
185 QMessageBox::critical(0, tr("Bitcoin Core"),
186 tr("Error: Specified data directory \"%1\" cannot be created.").arg(dataDir));
187 /* fall through, back to choosing screen */
191 settings.setValue("strDataDir", dataDir);
193 /* Only override -datadir if different from the default, to make it possible to
194 * override -datadir in the komodo.conf file in the default data directory
195 * (to be consistent with bitcoind behavior)
197 if(dataDir != getDefaultDataDirectory())
198 SoftSetArg("-datadir", GUIUtil::qstringToBoostPath(dataDir).string()); // use OS locale for path setting
201 void Intro::setStatus(int status, const QString &message, quint64 bytesAvailable)
205 case FreespaceChecker::ST_OK:
206 ui->errorMessage->setText(message);
207 ui->errorMessage->setStyleSheet("");
209 case FreespaceChecker::ST_ERROR:
210 ui->errorMessage->setText(tr("Error") + ": " + message);
211 ui->errorMessage->setStyleSheet("QLabel { color: #800000 }");
214 /* Indicate number of bytes available */
215 if(status == FreespaceChecker::ST_ERROR)
217 ui->freeSpace->setText("");
219 QString freeString = tr("%n GB of free space available", "", bytesAvailable/GB_BYTES);
220 if(bytesAvailable < BLOCK_CHAIN_SIZE)
222 freeString += " " + tr("(of %n GB needed)", "", BLOCK_CHAIN_SIZE/GB_BYTES);
223 ui->freeSpace->setStyleSheet("QLabel { color: #800000 }");
225 ui->freeSpace->setStyleSheet("");
227 ui->freeSpace->setText(freeString + ".");
229 /* Don't allow confirm in ERROR state */
230 ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status != FreespaceChecker::ST_ERROR);
233 void Intro::on_dataDirectory_textChanged(const QString &dataDirStr)
235 /* Disable OK button until check result comes in */
236 ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
237 checkPath(dataDirStr);
240 void Intro::on_ellipsisButton_clicked()
242 QString dir = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(0, "Choose data directory", ui->dataDirectory->text()));
244 ui->dataDirectory->setText(dir);
247 void Intro::on_dataDirDefault_clicked()
249 setDataDirectory(getDefaultDataDirectory());
252 void Intro::on_dataDirCustom_clicked()
254 ui->dataDirectory->setEnabled(true);
255 ui->ellipsisButton->setEnabled(true);
258 void Intro::startThread()
260 thread = new QThread(this);
261 FreespaceChecker *executor = new FreespaceChecker(this);
262 executor->moveToThread(thread);
264 connect(executor, SIGNAL(reply(int,QString,quint64)), this, SLOT(setStatus(int,QString,quint64)));
265 connect(this, SIGNAL(requestCheck()), executor, SLOT(check()));
266 /* make sure executor object is deleted in its own thread */
267 connect(this, SIGNAL(stopThread()), executor, SLOT(deleteLater()));
268 connect(this, SIGNAL(stopThread()), thread, SLOT(quit()));
273 void Intro::checkPath(const QString &dataDir)
276 pathToCheck = dataDir;
280 Q_EMIT requestCheck();
285 QString Intro::getPathToCheck()
289 retval = pathToCheck;
290 signalled = false; /* new request can be queued now */