fe31dd08292e0f213b6483adf189c6ac21ba4a25
[novacoin.git] / src / qt / intro.cpp
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 = 1LL * 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 class FreespaceChecker : public QObject
33 {
34     Q_OBJECT
35
36 public:
37     FreespaceChecker(Intro *intro);
38
39     enum Status {
40         ST_OK,
41         ST_ERROR
42     };
43
44 public slots:
45     void check();
46
47 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 FreespaceChecker::FreespaceChecker(Intro *intro)
57 {
58     this->intro = intro;
59 }
60
61 void FreespaceChecker::check()
62 {
63     namespace fs = boost::filesystem;
64     QString dataDirStr = intro->getPathToCheck();
65     fs::path dataDir = GUIUtil::qstringToBoostPath(dataDirStr);
66     uint64_t freeBytesAvailable = 0;
67     int replyStatus = ST_OK;
68     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     while(parentDir.has_parent_path() && !fs::exists(parentDir))
74     {
75         parentDir = parentDir.parent_path();
76
77         /* Check if we make any progress, break if not to prevent an infinite loop here */
78         if (parentDirOld == parentDir)
79             break;
80
81         parentDirOld = parentDir;
82     }
83
84     try {
85         freeBytesAvailable = fs::space(parentDir).available;
86         if(fs::exists(dataDir))
87         {
88             if(fs::is_directory(dataDir))
89             {
90                 QString separator = "<code>" + QDir::toNativeSeparators("/") + tr("name") + "</code>";
91                 replyStatus = ST_OK;
92                 replyMessage = tr("Directory already exists. Add %1 if you intend to create a new directory here.").arg(separator);
93             } else {
94                 replyStatus = ST_ERROR;
95                 replyMessage = tr("Path already exists, and is not a directory.");
96             }
97         }
98     } catch (const fs::filesystem_error&)
99     {
100         /* Parent directory does not exist or is not accessible */
101         replyStatus = ST_ERROR;
102         replyMessage = tr("Cannot create data directory here.");
103     }
104     emit reply(replyStatus, replyMessage, freeBytesAvailable);
105 }
106
107
108 Intro::Intro(QWidget *parent) :
109     QDialog(parent),
110     ui(new Ui::Intro),
111     thread(0),
112     signalled(false)
113 {
114     ui->setupUi(this);
115     ui->sizeWarningLabel->setText(ui->sizeWarningLabel->text().arg(BLOCK_CHAIN_SIZE/GB_BYTES));
116     startThread();
117 }
118
119 Intro::~Intro()
120 {
121     delete ui;
122     /* Ensure thread is finished before it is deleted */
123     emit stopThread();
124     thread->wait();
125 }
126
127 QString Intro::getDataDirectory()
128 {
129     return ui->dataDirectory->text();
130 }
131
132 void Intro::setDataDirectory(const QString &dataDir)
133 {
134     ui->dataDirectory->setText(dataDir);
135     if(dataDir == getDefaultDataDirectory())
136     {
137         ui->dataDirDefault->setChecked(true);
138         ui->dataDirectory->setEnabled(false);
139         ui->ellipsisButton->setEnabled(false);
140     } else {
141         ui->dataDirCustom->setChecked(true);
142         ui->dataDirectory->setEnabled(true);
143         ui->ellipsisButton->setEnabled(true);
144     }
145 }
146
147 QString Intro::getDefaultDataDirectory()
148 {
149     return GUIUtil::boostPathToQString(GetDefaultDataDir());
150 }
151
152 void Intro::pickDataDirectory()
153 {
154     namespace fs = boost::filesystem;
155     QSettings settings;
156     /* If data directory provided on command line, no need to look at settings
157        or show a picking dialog */
158     if(!GetArg("-datadir", "").empty())
159         return;
160     /* 1) Default data directory for operating system */
161     QString dataDir = getDefaultDataDirectory();
162     /* 2) Allow QSettings to override default dir */
163     dataDir = settings.value("strDataDir", dataDir).toString();
164
165     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         Intro intro;
169         intro.setDataDirectory(dataDir);
170         intro.setWindowIcon(QIcon(":icons/bitcoin"));
171
172         while(true)
173         {
174             if(!intro.exec())
175             {
176                 /* Cancel clicked */
177                 exit(0);
178             }
179             dataDir = intro.getDataDirectory();
180             try {
181                 boost::filesystem::create_directory(GUIUtil::qstringToBoostPath(dataDir));
182                 break;
183             } catch (const fs::filesystem_error&) {
184                 QMessageBox::critical(0, tr("Novacoin-qt"),
185                     tr("Error: Specified data directory \"%1\" cannot be created.").arg(dataDir));
186                 /* fall through, back to choosing screen */
187             }
188         }
189
190         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     if(dataDir != getDefaultDataDirectory())
197         SoftSetArg("-datadir", GUIUtil::qstringToBoostPath(dataDir).string()); // use OS locale for path setting
198 }
199
200 void Intro::setStatus(int status, const QString &message, quint64 bytesAvailable)
201 {
202     switch(status)
203     {
204     case FreespaceChecker::ST_OK:
205         ui->errorMessage->setText(message);
206         ui->errorMessage->setStyleSheet("");
207         break;
208     case FreespaceChecker::ST_ERROR:
209         ui->errorMessage->setText(tr("Error") + ": " + message);
210         ui->errorMessage->setStyleSheet("QLabel { color: #800000 }");
211         break;
212     }
213     /* Indicate number of bytes available */
214     if(status == FreespaceChecker::ST_ERROR)
215     {
216         ui->freeSpace->setText("");
217     } else {
218         QString freeString = tr("%n GB of free space available", "", bytesAvailable/GB_BYTES);
219         if(bytesAvailable < BLOCK_CHAIN_SIZE)
220         {
221             freeString += " " + tr("(of %n GB needed)", "", BLOCK_CHAIN_SIZE/GB_BYTES);
222             ui->freeSpace->setStyleSheet("QLabel { color: #800000 }");
223         } else {
224             ui->freeSpace->setStyleSheet("");
225         }
226         ui->freeSpace->setText(freeString + ".");
227     }
228     /* Don't allow confirm in ERROR state */
229     ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status != FreespaceChecker::ST_ERROR);
230 }
231
232 void Intro::on_dataDirectory_textChanged(const QString &dataDirStr)
233 {
234     /* Disable OK button until check result comes in */
235     ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
236     checkPath(dataDirStr);
237 }
238
239 void Intro::on_ellipsisButton_clicked()
240 {
241     QString dir = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(0, "Choose data directory", ui->dataDirectory->text()));
242     if(!dir.isEmpty())
243         ui->dataDirectory->setText(dir);
244 }
245
246 void Intro::on_dataDirDefault_clicked()
247 {
248     setDataDirectory(getDefaultDataDirectory());
249 }
250
251 void Intro::on_dataDirCustom_clicked()
252 {
253     ui->dataDirectory->setEnabled(true);
254     ui->ellipsisButton->setEnabled(true);
255 }
256
257 void Intro::startThread()
258 {
259     thread = new QThread(this);
260     FreespaceChecker *executor = new FreespaceChecker(this);
261     executor->moveToThread(thread);
262
263     connect(executor, SIGNAL(reply(int,QString,quint64)), this, SLOT(setStatus(int,QString,quint64)));
264     connect(this, SIGNAL(requestCheck()), executor, SLOT(check()));
265     /*  make sure executor object is deleted in its own thread */
266     connect(this, SIGNAL(stopThread()), executor, SLOT(deleteLater()));
267     connect(this, SIGNAL(stopThread()), thread, SLOT(quit()));
268
269     thread->start();
270 }
271
272 void Intro::checkPath(const QString &dataDir)
273 {
274     mutex.lock();
275     pathToCheck = dataDir;
276     if(!signalled)
277     {
278         signalled = true;
279         emit requestCheck();
280     }
281     mutex.unlock();
282 }
283
284 QString Intro::getPathToCheck()
285 {
286     QString retval;
287     mutex.lock();
288     retval = pathToCheck;
289     signalled = false; /* new request can be queued now */
290     mutex.unlock();
291     return retval;
292 }