You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

intro.cpp 8.2KB

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