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