diff options
Diffstat (limited to 'src')
36 files changed, 990 insertions, 126 deletions
diff --git a/src/db.cpp b/src/db.cpp index 93f3f5d8c4..03f46f3c26 100644 --- a/src/db.cpp +++ b/src/db.cpp @@ -8,7 +8,6 @@ #include "util.h" #include "hash.h" #include "addrman.h" -#include <boost/version.hpp> #include <boost/filesystem.hpp> #include <boost/filesystem/fstream.hpp> #include <openssl/rand.h> diff --git a/src/init.cpp b/src/init.cpp index f6b2c91b40..90dbad61b7 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -29,6 +29,7 @@ using namespace std; using namespace boost; +std::string strWalletFile; CWallet* pwalletMain; CClientUIInterface uiInterface; @@ -169,6 +170,7 @@ std::string HelpMessage() strUsage += " -pid=<file> " + _("Specify pid file (default: bitcoind.pid)") + "\n"; strUsage += " -gen " + _("Generate coins (default: 0)") + "\n"; strUsage += " -datadir=<dir> " + _("Specify data directory") + "\n"; + strUsage += " -wallet=<file> " + _("Specify wallet file (within data directory)") + "\n"; strUsage += " -dbcache=<n> " + _("Set database cache size in megabytes (default: 25)") + "\n"; strUsage += " -timeout=<n> " + _("Specify connection timeout in milliseconds (default: 5000)") + "\n"; strUsage += " -proxy=<ip:port> " + _("Connect through socks proxy") + "\n"; @@ -237,12 +239,12 @@ std::string HelpMessage() strUsage += " -reindex " + _("Rebuild block chain index from current blk000??.dat files") + "\n"; strUsage += " -par=<n> " + _("Set the number of script verification threads (up to 16, 0 = auto, <0 = leave that many cores free, default: 0)") + "\n"; - strUsage += "\n"; _("Block creation options:") + "\n"; + strUsage += "\n" + _("Block creation options:") + "\n"; strUsage += " -blockminsize=<n> " + _("Set minimum block size in bytes (default: 0)") + "\n"; strUsage += " -blockmaxsize=<n> " + _("Set maximum block size in bytes (default: 250000)") + "\n"; strUsage += " -blockprioritysize=<n> " + _("Set maximum size of high-priority/low-fee transactions in bytes (default: 27000)") + "\n"; - strUsage += "\n"; _("SSL options: (see the Bitcoin Wiki for SSL setup instructions)") + "\n"; + strUsage += "\n" + _("SSL options: (see the Bitcoin Wiki for SSL setup instructions)") + "\n"; strUsage += " -rpcssl " + _("Use OpenSSL (https) for JSON-RPC connections") + "\n"; strUsage += " -rpcsslcertificatechainfile=<file.cert> " + _("Server certificate file (default: server.cert)") + "\n"; strUsage += " -rpcsslprivatekeyfile=<file.pem> " + _("Server private key (default: server.pem)") + "\n"; @@ -493,10 +495,16 @@ bool AppInit2(boost::thread_group& threadGroup) InitWarning(_("Warning: -paytxfee is set very high! This is the transaction fee you will pay if you send a transaction.")); } + strWalletFile = GetArg("-wallet", "wallet.dat"); + // ********************************************************* Step 4: application initialization: dir lock, daemonize, pidfile, debug log std::string strDataDir = GetDataDir().string(); + // Wallet file must be a plain filename without a directory + if (strWalletFile != boost::filesystem::basename(strWalletFile) + boost::filesystem::extension(strWalletFile)) + return InitError(strprintf(_("Wallet %s resides outside data directory %s\n"), strWalletFile.c_str(), strDataDir.c_str())); + // Make sure only a single Bitcoin process is using the data directory. boost::filesystem::path pathLockFile = GetDataDir() / ".lock"; FILE* file = fopen(pathLockFile.string().c_str(), "a"); // empty lock file; created if it doesn't exist. @@ -555,13 +563,13 @@ bool AppInit2(boost::thread_group& threadGroup) if (GetBoolArg("-salvagewallet", false)) { // Recover readable keypairs: - if (!CWalletDB::Recover(bitdb, "wallet.dat", true)) + if (!CWalletDB::Recover(bitdb, strWalletFile, true)) return false; } - if (filesystem::exists(GetDataDir() / "wallet.dat")) + if (filesystem::exists(GetDataDir() / strWalletFile)) { - CDBEnv::VerifyResult r = bitdb.Verify("wallet.dat", CWalletDB::Recover); + CDBEnv::VerifyResult r = bitdb.Verify(strWalletFile, CWalletDB::Recover); if (r == CDBEnv::RECOVER_OK) { string msg = strprintf(_("Warning: wallet.dat corrupt, data salvaged!" @@ -785,6 +793,7 @@ bool AppInit2(boost::thread_group& threadGroup) fReindex = true; fRequestShutdown = false; } else { + printf("Aborted block database rebuild. Exiting.\n"); return false; } } else { @@ -838,7 +847,7 @@ bool AppInit2(boost::thread_group& threadGroup) nStart = GetTimeMillis(); bool fFirstRun = true; - pwalletMain = new CWallet("wallet.dat"); + pwalletMain = new CWallet(strWalletFile); DBErrors nLoadWalletRet = pwalletMain->LoadWallet(fFirstRun); if (nLoadWalletRet != DB_LOAD_OK) { @@ -903,7 +912,7 @@ bool AppInit2(boost::thread_group& threadGroup) pindexRescan = pindexGenesisBlock; else { - CWalletDB walletdb("wallet.dat"); + CWalletDB walletdb(strWalletFile); CBlockLocator locator; if (walletdb.ReadBestBlock(locator)) pindexRescan = locator.GetBlockIndex(); diff --git a/src/init.h b/src/init.h index 5927670c83..a4d5a67252 100644 --- a/src/init.h +++ b/src/init.h @@ -7,6 +7,7 @@ #include "wallet.h" +extern std::string strWalletFile; extern CWallet* pwalletMain; void StartShutdown(); diff --git a/src/leveldb/AUTHORS b/src/leveldb/AUTHORS index 27a9407e52..fc40194ab9 100644 --- a/src/leveldb/AUTHORS +++ b/src/leveldb/AUTHORS @@ -6,3 +6,6 @@ Google Inc. # Initial version authors: Jeffrey Dean <jeff@google.com> Sanjay Ghemawat <sanjay@google.com> + +# Partial list of contributors: +Kevin Regan <kevin.d.regan@gmail.com> diff --git a/src/leveldb/Makefile b/src/leveldb/Makefile index 42c4952fec..38b9bf7729 100644 --- a/src/leveldb/Makefile +++ b/src/leveldb/Makefile @@ -42,6 +42,7 @@ TESTS = \ env_test \ filename_test \ filter_block_test \ + issue178_test \ log_test \ memenv_test \ skiplist_test \ @@ -69,7 +70,7 @@ SHARED = $(SHARED1) else # Update db.h if you change these. SHARED_MAJOR = 1 -SHARED_MINOR = 9 +SHARED_MINOR = 12 SHARED1 = libleveldb.$(PLATFORM_SHARED_EXT) SHARED2 = $(SHARED1).$(SHARED_MAJOR) SHARED3 = $(SHARED1).$(SHARED_MAJOR).$(SHARED_MINOR) @@ -146,6 +147,9 @@ filename_test: db/filename_test.o $(LIBOBJECTS) $(TESTHARNESS) filter_block_test: table/filter_block_test.o $(LIBOBJECTS) $(TESTHARNESS) $(CXX) $(LDFLAGS) table/filter_block_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS) +issue178_test: issues/issue178_test.o $(LIBOBJECTS) $(TESTHARNESS) + $(CXX) $(LDFLAGS) issues/issue178_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS) + log_test: db/log_test.o $(LIBOBJECTS) $(TESTHARNESS) $(CXX) $(LDFLAGS) db/log_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS) diff --git a/src/leveldb/db/db_impl.cc b/src/leveldb/db/db_impl.cc index c9de169f29..af02467b33 100644 --- a/src/leveldb/db/db_impl.cc +++ b/src/leveldb/db/db_impl.cc @@ -35,6 +35,8 @@ namespace leveldb { +const int kNumNonTableCacheFiles = 10; + // Information kept for every waiting writer struct DBImpl::Writer { Status status; @@ -92,9 +94,9 @@ Options SanitizeOptions(const std::string& dbname, Options result = src; result.comparator = icmp; result.filter_policy = (src.filter_policy != NULL) ? ipolicy : NULL; - ClipToRange(&result.max_open_files, 20, 50000); - ClipToRange(&result.write_buffer_size, 64<<10, 1<<30); - ClipToRange(&result.block_size, 1<<10, 4<<20); + ClipToRange(&result.max_open_files, 64 + kNumNonTableCacheFiles, 50000); + ClipToRange(&result.write_buffer_size, 64<<10, 1<<30); + ClipToRange(&result.block_size, 1<<10, 4<<20); if (result.info_log == NULL) { // Open a log file in the same directory as the db src.env->CreateDir(dbname); // In case it does not exist @@ -130,12 +132,13 @@ DBImpl::DBImpl(const Options& options, const std::string& dbname) log_(NULL), tmp_batch_(new WriteBatch), bg_compaction_scheduled_(false), - manual_compaction_(NULL) { + manual_compaction_(NULL), + consecutive_compaction_errors_(0) { mem_->Ref(); has_imm_.Release_Store(NULL); // Reserve ten files or so for other uses and give the rest to TableCache. - const int table_cache_size = options.max_open_files - 10; + const int table_cache_size = options.max_open_files - kNumNonTableCacheFiles; table_cache_ = new TableCache(dbname_, &options_, table_cache_size); versions_ = new VersionSet(dbname_, &options_, table_cache_, @@ -310,16 +313,24 @@ Status DBImpl::Recover(VersionEdit* edit) { if (!s.ok()) { return s; } + std::set<uint64_t> expected; + versions_->AddLiveFiles(&expected); uint64_t number; FileType type; std::vector<uint64_t> logs; for (size_t i = 0; i < filenames.size(); i++) { - if (ParseFileName(filenames[i], &number, &type) - && type == kLogFile - && ((number >= min_log) || (number == prev_log))) { + if (ParseFileName(filenames[i], &number, &type)) { + expected.erase(number); + if (type == kLogFile && ((number >= min_log) || (number == prev_log))) logs.push_back(number); } } + if (!expected.empty()) { + char buf[50]; + snprintf(buf, sizeof(buf), "%d missing files; e.g.", + static_cast<int>(expected.size())); + return Status::Corruption(buf, TableFileName(dbname_, *(expected.begin()))); + } // Recover in the order in which the logs were generated std::sort(logs.begin(), logs.end()); @@ -611,6 +622,7 @@ void DBImpl::BackgroundCall() { Status s = BackgroundCompaction(); if (s.ok()) { // Success + consecutive_compaction_errors_ = 0; } else if (shutting_down_.Acquire_Load()) { // Error most likely due to shutdown; do not wait } else { @@ -622,7 +634,12 @@ void DBImpl::BackgroundCall() { Log(options_.info_log, "Waiting after background compaction error: %s", s.ToString().c_str()); mutex_.Unlock(); - env_->SleepForMicroseconds(1000000); + ++consecutive_compaction_errors_; + int seconds_to_sleep = 1; + for (int i = 0; i < 3 && i < consecutive_compaction_errors_ - 1; ++i) { + seconds_to_sleep *= 2; + } + env_->SleepForMicroseconds(seconds_to_sleep * 1000000); mutex_.Lock(); } } @@ -805,6 +822,9 @@ Status DBImpl::FinishCompactionOutputFile(CompactionState* compact, (unsigned long long) output_number, (unsigned long long) current_entries, (unsigned long long) current_bytes); + + // rate-limit compaction file creation with a 100ms pause + env_->SleepForMicroseconds(100000); } } return s; @@ -1268,10 +1288,11 @@ Status DBImpl::MakeRoomForWrite(bool force) { } else if (imm_ != NULL) { // We have filled up the current memtable, but the previous // one is still being compacted, so we wait. + Log(options_.info_log, "Current memtable full; waiting...\n"); bg_cv_.Wait(); } else if (versions_->NumLevelFiles(0) >= config::kL0_StopWritesTrigger) { // There are too many level-0 files. - Log(options_.info_log, "waiting...\n"); + Log(options_.info_log, "Too many L0 files; waiting...\n"); bg_cv_.Wait(); } else { // Attempt to switch to a new memtable and trigger compaction of old diff --git a/src/leveldb/db/db_impl.h b/src/leveldb/db/db_impl.h index bd29dd8055..3c8d711ae0 100644 --- a/src/leveldb/db/db_impl.h +++ b/src/leveldb/db/db_impl.h @@ -163,6 +163,7 @@ class DBImpl : public DB { // Have we encountered a background error in paranoid mode? Status bg_error_; + int consecutive_compaction_errors_; // Per level compaction stats. stats_[level] stores the stats for // compactions that produced data for the specified "level". diff --git a/src/leveldb/db/db_test.cc b/src/leveldb/db/db_test.cc index 684ea3bdbc..49aae04dbd 100644 --- a/src/leveldb/db/db_test.cc +++ b/src/leveldb/db/db_test.cc @@ -33,8 +33,11 @@ class AtomicCounter { public: AtomicCounter() : count_(0) { } void Increment() { + IncrementBy(1); + } + void IncrementBy(int count) { MutexLock l(&mu_); - count_++; + count_ += count; } int Read() { MutexLock l(&mu_); @@ -45,6 +48,10 @@ class AtomicCounter { count_ = 0; } }; + +void DelayMilliseconds(int millis) { + Env::Default()->SleepForMicroseconds(millis * 1000); +} } // Special Env used to delay background operations @@ -69,6 +76,7 @@ class SpecialEnv : public EnvWrapper { AtomicCounter random_read_counter_; AtomicCounter sleep_counter_; + AtomicCounter sleep_time_counter_; explicit SpecialEnv(Env* base) : EnvWrapper(base) { delay_sstable_sync_.Release_Store(NULL); @@ -103,7 +111,7 @@ class SpecialEnv : public EnvWrapper { Status Flush() { return base_->Flush(); } Status Sync() { while (env_->delay_sstable_sync_.Acquire_Load() != NULL) { - env_->SleepForMicroseconds(100000); + DelayMilliseconds(100); } return base_->Sync(); } @@ -174,8 +182,9 @@ class SpecialEnv : public EnvWrapper { virtual void SleepForMicroseconds(int micros) { sleep_counter_.Increment(); - target()->SleepForMicroseconds(micros); + sleep_time_counter_.IncrementBy(micros); } + }; class DBTest { @@ -461,6 +470,20 @@ class DBTest { } return result; } + + bool DeleteAnSSTFile() { + std::vector<std::string> filenames; + ASSERT_OK(env_->GetChildren(dbname_, &filenames)); + uint64_t number; + FileType type; + for (size_t i = 0; i < filenames.size(); i++) { + if (ParseFileName(filenames[i], &number, &type) && type == kTableFile) { + ASSERT_OK(env_->DeleteFile(TableFileName(dbname_, number))); + return true; + } + } + return false; + } }; TEST(DBTest, Empty) { @@ -611,7 +634,7 @@ TEST(DBTest, GetEncountersEmptyLevel) { } // Step 4: Wait for compaction to finish - env_->SleepForMicroseconds(1000000); + DelayMilliseconds(1000); ASSERT_EQ(NumTableFilesAtLevel(0), 0); } while (ChangeOptions()); @@ -1295,7 +1318,7 @@ TEST(DBTest, L0_CompactionBug_Issue44_a) { Reopen(); Reopen(); ASSERT_EQ("(a->v)", Contents()); - env_->SleepForMicroseconds(1000000); // Wait for compaction to finish + DelayMilliseconds(1000); // Wait for compaction to finish ASSERT_EQ("(a->v)", Contents()); } @@ -1311,7 +1334,7 @@ TEST(DBTest, L0_CompactionBug_Issue44_b) { Put("",""); Reopen(); Put("",""); - env_->SleepForMicroseconds(1000000); // Wait for compaction to finish + DelayMilliseconds(1000); // Wait for compaction to finish Reopen(); Put("d","dv"); Reopen(); @@ -1321,7 +1344,7 @@ TEST(DBTest, L0_CompactionBug_Issue44_b) { Delete("b"); Reopen(); ASSERT_EQ("(->)(c->cv)", Contents()); - env_->SleepForMicroseconds(1000000); // Wait for compaction to finish + DelayMilliseconds(1000); // Wait for compaction to finish ASSERT_EQ("(->)(c->cv)", Contents()); } @@ -1506,6 +1529,30 @@ TEST(DBTest, NoSpace) { ASSERT_GE(env_->sleep_counter_.Read(), 5); } +TEST(DBTest, ExponentialBackoff) { + Options options = CurrentOptions(); + options.env = env_; + Reopen(&options); + + ASSERT_OK(Put("foo", "v1")); + ASSERT_EQ("v1", Get("foo")); + Compact("a", "z"); + env_->non_writable_.Release_Store(env_); // Force errors for new files + env_->sleep_counter_.Reset(); + env_->sleep_time_counter_.Reset(); + for (int i = 0; i < 5; i++) { + dbfull()->TEST_CompactRange(2, NULL, NULL); + } + env_->non_writable_.Release_Store(NULL); + + // Wait for compaction to finish + DelayMilliseconds(1000); + + ASSERT_GE(env_->sleep_counter_.Read(), 5); + ASSERT_LT(env_->sleep_counter_.Read(), 10); + ASSERT_GE(env_->sleep_time_counter_.Read(), 10e6); +} + TEST(DBTest, NonWritableFileSystem) { Options options = CurrentOptions(); options.write_buffer_size = 1000; @@ -1519,7 +1566,7 @@ TEST(DBTest, NonWritableFileSystem) { fprintf(stderr, "iter %d; errors %d\n", i, errors); if (!Put("foo", big).ok()) { errors++; - env_->SleepForMicroseconds(100000); + DelayMilliseconds(100); } } ASSERT_GT(errors, 0); @@ -1567,6 +1614,24 @@ TEST(DBTest, ManifestWriteError) { } } +TEST(DBTest, MissingSSTFile) { + ASSERT_OK(Put("foo", "bar")); + ASSERT_EQ("bar", Get("foo")); + + // Dump the memtable to disk. + dbfull()->TEST_CompactMemTable(); + ASSERT_EQ("bar", Get("foo")); + + Close(); + ASSERT_TRUE(DeleteAnSSTFile()); + Options options = CurrentOptions(); + options.paranoid_checks = true; + Status s = TryReopen(&options); + ASSERT_TRUE(!s.ok()); + ASSERT_TRUE(s.ToString().find("issing") != std::string::npos) + << s.ToString(); +} + TEST(DBTest, FilesDeletedAfterCompaction) { ASSERT_OK(Put("foo", "v2")); Compact("a", "z"); @@ -1711,13 +1776,13 @@ TEST(DBTest, MultiThreaded) { } // Let them run for a while - env_->SleepForMicroseconds(kTestSeconds * 1000000); + DelayMilliseconds(kTestSeconds * 1000); // Stop the threads and wait for them to finish mt.stop.Release_Store(&mt); for (int id = 0; id < kNumThreads; id++) { while (mt.thread_done[id].Acquire_Load() == NULL) { - env_->SleepForMicroseconds(100000); + DelayMilliseconds(100); } } } while (ChangeOptions()); diff --git a/src/leveldb/db/dbformat.cc b/src/leveldb/db/dbformat.cc index 28e11b398d..20a7ca4462 100644 --- a/src/leveldb/db/dbformat.cc +++ b/src/leveldb/db/dbformat.cc @@ -26,7 +26,7 @@ std::string ParsedInternalKey::DebugString() const { (unsigned long long) sequence, int(type)); std::string result = "'"; - result += user_key.ToString(); + result += EscapeString(user_key.ToString()); result += buf; return result; } diff --git a/src/leveldb/db/filename_test.cc b/src/leveldb/db/filename_test.cc index 47353d6c9a..5a26da4728 100644 --- a/src/leveldb/db/filename_test.cc +++ b/src/leveldb/db/filename_test.cc @@ -70,7 +70,7 @@ TEST(FileNameTest, Parse) { for (int i = 0; i < sizeof(errors) / sizeof(errors[0]); i++) { std::string f = errors[i]; ASSERT_TRUE(!ParseFileName(f, &number, &type)) << f; - }; + } } TEST(FileNameTest, Construction) { diff --git a/src/leveldb/db/version_set.cc b/src/leveldb/db/version_set.cc index 7d0a5de2b9..4fd1ddef21 100644 --- a/src/leveldb/db/version_set.cc +++ b/src/leveldb/db/version_set.cc @@ -1331,14 +1331,19 @@ Compaction* VersionSet::CompactRange( } // Avoid compacting too much in one shot in case the range is large. - const uint64_t limit = MaxFileSizeForLevel(level); - uint64_t total = 0; - for (size_t i = 0; i < inputs.size(); i++) { - uint64_t s = inputs[i]->file_size; - total += s; - if (total >= limit) { - inputs.resize(i + 1); - break; + // But we cannot do this for level-0 since level-0 files can overlap + // and we must not pick one file and drop another older file if the + // two files overlap. + if (level > 0) { + const uint64_t limit = MaxFileSizeForLevel(level); + uint64_t total = 0; + for (size_t i = 0; i < inputs.size(); i++) { + uint64_t s = inputs[i]->file_size; + total += s; + if (total >= limit) { + inputs.resize(i + 1); + break; + } } } diff --git a/src/leveldb/include/leveldb/db.h b/src/leveldb/include/leveldb/db.h index 29d3674479..da8b11a8c0 100644 --- a/src/leveldb/include/leveldb/db.h +++ b/src/leveldb/include/leveldb/db.h @@ -14,7 +14,7 @@ namespace leveldb { // Update Makefile if you change these static const int kMajorVersion = 1; -static const int kMinorVersion = 9; +static const int kMinorVersion = 12; struct Options; struct ReadOptions; diff --git a/src/leveldb/port/port_win.cc b/src/leveldb/port/port_win.cc index 99c1d8e346..1b0f060a19 100644 --- a/src/leveldb/port/port_win.cc +++ b/src/leveldb/port/port_win.cc @@ -109,12 +109,10 @@ void CondVar::Signal() { void CondVar::SignalAll() { wait_mtx_.Lock(); - for(long i = 0; i < waiting_; ++i) { - ::ReleaseSemaphore(sem1_, 1, NULL); - while(waiting_ > 0) { - --waiting_; - ::WaitForSingleObject(sem2_, INFINITE); - } + ::ReleaseSemaphore(sem1_, waiting_, NULL); + while(waiting_ > 0) { + --waiting_; + ::WaitForSingleObject(sem2_, INFINITE); } wait_mtx_.Unlock(); } diff --git a/src/leveldb/table/block.cc b/src/leveldb/table/block.cc index ab83c1112c..79ea9d9ee5 100644 --- a/src/leveldb/table/block.cc +++ b/src/leveldb/table/block.cc @@ -16,7 +16,7 @@ namespace leveldb { inline uint32_t Block::NumRestarts() const { - assert(size_ >= 2*sizeof(uint32_t)); + assert(size_ >= sizeof(uint32_t)); return DecodeFixed32(data_ + size_ - sizeof(uint32_t)); } @@ -27,11 +27,12 @@ Block::Block(const BlockContents& contents) if (size_ < sizeof(uint32_t)) { size_ = 0; // Error marker } else { - restart_offset_ = size_ - (1 + NumRestarts()) * sizeof(uint32_t); - if (restart_offset_ > size_ - sizeof(uint32_t)) { - // The size is too small for NumRestarts() and therefore - // restart_offset_ wrapped around. + size_t max_restarts_allowed = (size_-sizeof(uint32_t)) / sizeof(uint32_t); + if (NumRestarts() > max_restarts_allowed) { + // The size is too small for NumRestarts() size_ = 0; + } else { + restart_offset_ = size_ - (1 + NumRestarts()) * sizeof(uint32_t); } } } @@ -253,7 +254,7 @@ class Block::Iter : public Iterator { }; Iterator* Block::NewIterator(const Comparator* cmp) { - if (size_ < 2*sizeof(uint32_t)) { + if (size_ < sizeof(uint32_t)) { return NewErrorIterator(Status::Corruption("bad block contents")); } const uint32_t num_restarts = NumRestarts(); diff --git a/src/leveldb/table/table.cc b/src/leveldb/table/table.cc index dbd6d3a1bf..71c1756e5f 100644 --- a/src/leveldb/table/table.cc +++ b/src/leveldb/table/table.cc @@ -228,7 +228,6 @@ Status Table::InternalGet(const ReadOptions& options, const Slice& k, !filter->KeyMayMatch(handle.offset(), k)) { // Not found } else { - Slice handle = iiter->value(); Iterator* block_iter = BlockReader(this, options, iiter->value()); block_iter->Seek(k); if (block_iter->Valid()) { diff --git a/src/leveldb/table/table_test.cc b/src/leveldb/table/table_test.cc index 57cea25334..c723bf84cf 100644 --- a/src/leveldb/table/table_test.cc +++ b/src/leveldb/table/table_test.cc @@ -644,6 +644,36 @@ class Harness { Constructor* constructor_; }; +// Test empty table/block. +TEST(Harness, Empty) { + for (int i = 0; i < kNumTestArgs; i++) { + Init(kTestArgList[i]); + Random rnd(test::RandomSeed() + 1); + Test(&rnd); + } +} + +// Special test for a block with no restart entries. The C++ leveldb +// code never generates such blocks, but the Java version of leveldb +// seems to. +TEST(Harness, ZeroRestartPointsInBlock) { + char data[sizeof(uint32_t)]; + memset(data, 0, sizeof(data)); + BlockContents contents; + contents.data = Slice(data, sizeof(data)); + contents.cachable = false; + contents.heap_allocated = false; + Block block(contents); + Iterator* iter = block.NewIterator(BytewiseComparator()); + iter->SeekToFirst(); + ASSERT_TRUE(!iter->Valid()); + iter->SeekToLast(); + ASSERT_TRUE(!iter->Valid()); + iter->Seek("foo"); + ASSERT_TRUE(!iter->Valid()); + delete iter; +} + // Test the empty key TEST(Harness, SimpleEmptyKey) { for (int i = 0; i < kNumTestArgs; i++) { diff --git a/src/leveldb/util/cache.cc b/src/leveldb/util/cache.cc index 24f1f63f4f..8b197bc02a 100644 --- a/src/leveldb/util/cache.cc +++ b/src/leveldb/util/cache.cc @@ -116,7 +116,6 @@ class HandleTable { LRUHandle* h = list_[i]; while (h != NULL) { LRUHandle* next = h->next_hash; - Slice key = h->key(); uint32_t hash = h->hash; LRUHandle** ptr = &new_list[hash & (new_length - 1)]; h->next_hash = *ptr; @@ -160,7 +159,6 @@ class LRUCache { // mutex_ protects the following state. port::Mutex mutex_; size_t usage_; - uint64_t last_id_; // Dummy head of LRU list. // lru.prev is newest entry, lru.next is oldest entry. @@ -170,8 +168,7 @@ class LRUCache { }; LRUCache::LRUCache() - : usage_(0), - last_id_(0) { + : usage_(0) { // Make empty circular linked list lru_.next = &lru_; lru_.prev = &lru_; diff --git a/src/leveldb/util/coding_test.cc b/src/leveldb/util/coding_test.cc index 2c52b17b60..fb5726e335 100644 --- a/src/leveldb/util/coding_test.cc +++ b/src/leveldb/util/coding_test.cc @@ -109,7 +109,7 @@ TEST(Coding, Varint64) { values.push_back(power); values.push_back(power-1); values.push_back(power+1); - }; + } std::string s; for (int i = 0; i < values.size(); i++) { diff --git a/src/leveldb/util/comparator.cc b/src/leveldb/util/comparator.cc index 4b7b5724ef..6cc319242e 100644 --- a/src/leveldb/util/comparator.cc +++ b/src/leveldb/util/comparator.cc @@ -66,7 +66,7 @@ class BytewiseComparatorImpl : public Comparator { }; } // namespace -static port::OnceType once = LEVELDB_ONCE_INIT; +static port::OnceType once_comparator = LEVELDB_ONCE_INIT; static const Comparator* bytewise; static void InitModule() { @@ -74,7 +74,7 @@ static void InitModule() { } const Comparator* BytewiseComparator() { - port::InitOnce(&once, InitModule); + port::InitOnce(&once_comparator, InitModule); return bytewise; } diff --git a/src/leveldb/util/env_posix.cc b/src/leveldb/util/env_posix.cc index db81f56d11..6badfdc230 100644 --- a/src/leveldb/util/env_posix.cc +++ b/src/leveldb/util/env_posix.cc @@ -386,7 +386,7 @@ class PosixEnv : public Env { PosixEnv(); virtual ~PosixEnv() { fprintf(stderr, "Destroying Env::Default()\n"); - exit(1); + abort(); } virtual Status NewSequentialFile(const std::string& fname, @@ -467,7 +467,7 @@ class PosixEnv : public Env { result = IOError(fname, errno); } return result; - }; + } virtual Status CreateDir(const std::string& name) { Status result; @@ -475,7 +475,7 @@ class PosixEnv : public Env { result = IOError(name, errno); } return result; - }; + } virtual Status DeleteDir(const std::string& name) { Status result; @@ -483,7 +483,7 @@ class PosixEnv : public Env { result = IOError(name, errno); } return result; - }; + } virtual Status GetFileSize(const std::string& fname, uint64_t* size) { Status s; @@ -589,7 +589,7 @@ class PosixEnv : public Env { void PthreadCall(const char* label, int result) { if (result != 0) { fprintf(stderr, "pthread %s: %s\n", label, strerror(result)); - exit(1); + abort(); } } diff --git a/src/leveldb/util/hash.cc b/src/leveldb/util/hash.cc index ba1818082d..07cf022060 100644 --- a/src/leveldb/util/hash.cc +++ b/src/leveldb/util/hash.cc @@ -6,6 +6,13 @@ #include "util/coding.h" #include "util/hash.h" +// The FALLTHROUGH_INTENDED macro can be used to annotate implicit fall-through +// between switch labels. The real definition should be provided externally. +// This one is a fallback version for unsupported compilers. +#ifndef FALLTHROUGH_INTENDED +#define FALLTHROUGH_INTENDED do { } while (0) +#endif + namespace leveldb { uint32_t Hash(const char* data, size_t n, uint32_t seed) { @@ -28,10 +35,10 @@ uint32_t Hash(const char* data, size_t n, uint32_t seed) { switch (limit - data) { case 3: h += data[2] << 16; - // fall through + FALLTHROUGH_INTENDED; case 2: h += data[1] << 8; - // fall through + FALLTHROUGH_INTENDED; case 1: h += data[0]; h *= m; diff --git a/src/makefile.mingw b/src/makefile.mingw index 3659f52040..002e36d3e2 100644 --- a/src/makefile.mingw +++ b/src/makefile.mingw @@ -21,7 +21,7 @@ USE_UPNP:=- USE_IPV6:=1 DEPSDIR?=/usr/local -BOOST_SUFFIX?=-mgw46-mt-sd-1_52 +BOOST_SUFFIX?=-mgw46-mt-s-1_52 INCLUDEPATHS= \ -I"$(CURDIR)" \ diff --git a/src/net.cpp b/src/net.cpp index 5418c3de40..bd9aa1f50f 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -1598,8 +1598,12 @@ bool BindListenPort(const CService &addrBind, string& strError) // and enable it by default or not. Try to enable it, if possible. if (addrBind.IsIPv6()) { #ifdef IPV6_V6ONLY +#ifdef WIN32 + setsockopt(hListenSocket, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&nOne, sizeof(int)); +#else setsockopt(hListenSocket, IPPROTO_IPV6, IPV6_V6ONLY, (void*)&nOne, sizeof(int)); #endif +#endif #ifdef WIN32 int nProtLevel = 10 /* PROTECTION_LEVEL_UNRESTRICTED */; int nParameterId = 23 /* IPV6_PROTECTION_LEVEl */; diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 76e88b36a7..4e9180b88c 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -2,8 +2,6 @@ * W.J. van der Laan 2011-2012 */ -#include <QApplication> - #include "bitcoingui.h" #include "clientmodel.h" #include "walletmodel.h" @@ -15,7 +13,10 @@ #include "ui_interface.h" #include "paymentserver.h" #include "splashscreen.h" +#include "intro.h" +#undef loop /* Todo: ugh, remove this when the #define loop is gone from util.h */ +#include <QApplication> #include <QMessageBox> #if QT_VERSION < 0x050000 #include <QTextCodec> @@ -24,6 +25,7 @@ #include <QTimer> #include <QTranslator> #include <QLibraryInfo> +#include <QSettings> #if defined(BITCOIN_NEED_QT_PLUGINS) && !defined(_BITCOIN_QT_PLUGINS_INCLUDED) #define _BITCOIN_QT_PLUGINS_INCLUDED @@ -110,6 +112,46 @@ static void handleRunawayException(std::exception *e) exit(1); } +/** Set up translations */ +static void initTranslations(QTranslator &qtTranslatorBase, QTranslator &qtTranslator, QTranslator &translatorBase, QTranslator &translator) +{ + QSettings settings; + + // Get desired locale (e.g. "de_DE") + // 1) System default language + QString lang_territory = QLocale::system().name(); + // 2) Language from QSettings + QString lang_territory_qsettings = settings.value("language", "").toString(); + if(!lang_territory_qsettings.isEmpty()) + lang_territory = lang_territory_qsettings; + // 3) -lang command line argument + lang_territory = QString::fromStdString(GetArg("-lang", lang_territory.toStdString())); + + // Convert to "de" only by truncating "_DE" + QString lang = lang_territory; + lang.truncate(lang_territory.lastIndexOf('_')); + + // Load language files for configured locale: + // - First load the translator for the base language, without territory + // - Then load the more specific locale translator + + // Load e.g. qt_de.qm + if (qtTranslatorBase.load("qt_" + lang, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) + QApplication::installTranslator(&qtTranslatorBase); + + // Load e.g. qt_de_DE.qm + if (qtTranslator.load("qt_" + lang_territory, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) + QApplication::installTranslator(&qtTranslator); + + // Load e.g. bitcoin_de.qm (shortcut "de" needs to be defined in bitcoin.qrc) + if (translatorBase.load(lang, ":/translations/")) + QApplication::installTranslator(&translatorBase); + + // Load e.g. bitcoin_de_DE.qm (shortcut "de_DE" needs to be defined in bitcoin.qrc) + if (translator.load(lang_territory, ":/translations/")) + QApplication::installTranslator(&translator); +} + #ifndef BITCOIN_QT_TEST int main(int argc, char *argv[]) { @@ -130,6 +172,22 @@ int main(int argc, char *argv[]) // Register meta types used for QMetaObject::invokeMethod qRegisterMetaType< bool* >(); + // Application identification (must be set before OptionsModel is initialized, + // as it is used to locate QSettings) + QApplication::setOrganizationName("Bitcoin"); + QApplication::setOrganizationDomain("bitcoin.org"); + if (GetBoolArg("-testnet", false)) // Separate UI settings for testnet + QApplication::setApplicationName("Bitcoin-Qt-testnet"); + else + QApplication::setApplicationName("Bitcoin-Qt"); + + // Now that QSettings are accessible, initialize translations + QTranslator qtTranslatorBase, qtTranslator, translatorBase, translator; + initTranslations(qtTranslatorBase, qtTranslator, translatorBase, translator); + + // User language is set up: pick a data directory + Intro::pickDataDirectory(); + // Do this early as we don't want to bother initializing if we are just calling IPC // ... but do it after creating app, so QCoreApplication::arguments is initialized: if (PaymentServer::ipcSendCommandLine()) @@ -142,53 +200,15 @@ int main(int argc, char *argv[]) // ... then bitcoin.conf: if (!boost::filesystem::is_directory(GetDataDir(false))) { - // This message can not be translated, as translation is not initialized yet - // (which not yet possible because lang=XX can be overridden in bitcoin.conf in the data directory) - QMessageBox::critical(0, "Bitcoin", - QString("Error: Specified data directory \"%1\" does not exist.").arg(QString::fromStdString(mapArgs["-datadir"]))); + QMessageBox::critical(0, QObject::tr("Bitcoin"), + QObject::tr("Error: Specified data directory \"%1\" does not exist.").arg(QString::fromStdString(mapArgs["-datadir"]))); return 1; } ReadConfigFile(mapArgs, mapMultiArgs); - // Application identification (must be set before OptionsModel is initialized, - // as it is used to locate QSettings) - QApplication::setOrganizationName("Bitcoin"); - QApplication::setOrganizationDomain("bitcoin.org"); - if (GetBoolArg("-testnet", false)) // Separate UI settings for testnet - QApplication::setApplicationName("Bitcoin-Qt-testnet"); - else - QApplication::setApplicationName("Bitcoin-Qt"); - // ... then GUI settings: OptionsModel optionsModel; - // Get desired locale (e.g. "de_DE") from command line or use system locale - QString lang_territory = QString::fromStdString(GetArg("-lang", QLocale::system().name().toStdString())); - QString lang = lang_territory; - // Convert to "de" only by truncating "_DE" - lang.truncate(lang_territory.lastIndexOf('_')); - - QTranslator qtTranslatorBase, qtTranslator, translatorBase, translator; - // Load language files for configured locale: - // - First load the translator for the base language, without territory - // - Then load the more specific locale translator - - // Load e.g. qt_de.qm - if (qtTranslatorBase.load("qt_" + lang, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) - app.installTranslator(&qtTranslatorBase); - - // Load e.g. qt_de_DE.qm - if (qtTranslator.load("qt_" + lang_territory, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) - app.installTranslator(&qtTranslator); - - // Load e.g. bitcoin_de.qm (shortcut "de" needs to be defined in bitcoin.qrc) - if (translatorBase.load(lang, ":/translations/")) - app.installTranslator(&translatorBase); - - // Load e.g. bitcoin_de_DE.qm (shortcut "de_DE" needs to be defined in bitcoin.qrc) - if (translator.load(lang_territory, ":/translations/")) - app.installTranslator(&translator); - // Subscribe to global signals from core uiInterface.ThreadSafeMessageBox.connect(ThreadSafeMessageBox); uiInterface.ThreadSafeAskFee.connect(ThreadSafeAskFee); diff --git a/src/qt/bitcoinamountfield.cpp b/src/qt/bitcoinamountfield.cpp index b502505f31..eeb6fe89bb 100644 --- a/src/qt/bitcoinamountfield.cpp +++ b/src/qt/bitcoinamountfield.cpp @@ -4,10 +4,11 @@ #include "bitcoinunits.h" #include "guiconstants.h" +#include <QApplication> #include <QHBoxLayout> #include <QKeyEvent> #include <QDoubleSpinBox> -#include <QApplication> + #include <qmath.h> // for qPow() BitcoinAmountField::BitcoinAmountField(QWidget *parent): diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 190da6caf8..11767cf312 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -5,8 +5,6 @@ * The Bitcoin Developers 2011-2012 */ -#include <QApplication> - #include "bitcoingui.h" #include "transactiontablemodel.h" @@ -30,6 +28,8 @@ #include "macdockiconhandler.h" #endif +#undef loop /* Todo: ugh, remove this when the #define loop is gone from util.h */ +#include <QApplication> #include <QMenuBar> #include <QMenu> #include <QIcon> diff --git a/src/qt/forms/intro.ui b/src/qt/forms/intro.ui new file mode 100644 index 0000000000..0f6ae5a7d0 --- /dev/null +++ b/src/qt/forms/intro.ui @@ -0,0 +1,266 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Intro</class> + <widget class="QDialog" name="Intro"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>674</width> + <height>363</height> + </rect> + </property> + <property name="windowTitle"> + <string>Welcome</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label_2"> + <property name="styleSheet"> + <string notr="true">QLabel { font-style:italic; }</string> + </property> + <property name="text"> + <string>Welcome to Bitcoin-Qt.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_4"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Minimum</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>15</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>As this is the first time the program is launched, you can choose where Bitcoin-Qt will store its data.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="sizeWarningLabel"> + <property name="text"> + <string>Bitcoin-Qt will download and store a copy of the Bitcoin block chain. At least %1GB of data will be stored in this directory, and it will grow over time. The wallet will also be stored in this directory.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="dataDirDefault"> + <property name="text"> + <string>Use the default data directory</string> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="dataDirCustom"> + <property name="text"> + <string>Use a custom data directory:</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="spacing"> + <number>0</number> + </property> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>60</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLineEdit" name="dataDirectory"/> + </item> + <item> + <widget class="QPushButton" name="ellipsisButton"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>30</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string notr="true">…</string> + </property> + <property name="autoDefault"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>5</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="freeSpace"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>5</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="errorMessage"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="textFormat"> + <enum>Qt::RichText</enum> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>Intro</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>Intro</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 88a6e7226e..521d9bddd8 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -1,5 +1,3 @@ -#include <QApplication> - #include "guiutil.h" #include "bitcoinaddressvalidator.h" @@ -9,6 +7,8 @@ #include "util.h" #include "init.h" +#undef loop /* Todo: ugh, remove this when the #define loop is gone from util.h */ +#include <QApplication> #include <QDateTime> #include <QDoubleValidator> #include <QFont> @@ -500,7 +500,8 @@ HelpMessageBox::HelpMessageBox(QWidget *parent) : uiOptions = tr("UI options") + ":\n" + " -lang=<lang> " + tr("Set language, for example \"de_DE\" (default: system locale)") + "\n" + " -min " + tr("Start minimized") + "\n" + - " -splash " + tr("Show splash screen on startup (default: 1)") + "\n"; + " -splash " + tr("Show splash screen on startup (default: 1)") + "\n" + + " -choosedatadir " + tr("Choose data directory on startup (default: 0)") + "\n"; setWindowTitle(tr("Bitcoin-Qt")); setTextFormat(Qt::PlainText); diff --git a/src/qt/intro.cpp b/src/qt/intro.cpp new file mode 100644 index 0000000000..51f3c812e4 --- /dev/null +++ b/src/qt/intro.cpp @@ -0,0 +1,270 @@ +#include "intro.h" +#include "ui_intro.h" +#include "util.h" + +#include <QFileDialog> +#include <QSettings> +#include <QMessageBox> + +#include <boost/filesystem.hpp> + +/* Minimum free space (in bytes) needed for data directory */ +static const uint64 GB_BYTES = 1000000000LL; +static const uint64 BLOCK_CHAIN_SIZE = 10LL * GB_BYTES; + +/* Check free space asynchronously to prevent hanging the UI thread. + + Up to one request to check a path is in flight to this thread; when the check() + function runs, the current path is requested from the associated Intro object. + The reply is sent back through a signal. + + This ensures that no queue of checking requests is built up while the user is + still entering the path, and that always the most recently entered path is checked as + soon as the thread becomes available. +*/ +class FreespaceChecker : public QObject +{ + Q_OBJECT +public: + FreespaceChecker(Intro *intro); + + enum Status { + ST_OK, + ST_ERROR + }; + +public slots: + void check(); + +signals: + void reply(int status, const QString &message, quint64 available); + +private: + Intro *intro; +}; + +#include "intro.moc" + +FreespaceChecker::FreespaceChecker(Intro *intro) +{ + this->intro = intro; +} + +void FreespaceChecker::check() +{ + namespace fs = boost::filesystem; + QString dataDirStr = intro->getPathToCheck(); + fs::path dataDir = fs::path(dataDirStr.toStdString()); + uint64 freeBytesAvailable = 0; + int replyStatus = ST_OK; + QString replyMessage = tr("A new data directory will be created."); + + /* Find first parent that exists, so that fs::space does not fail */ + fs::path parentDir = dataDir; + while(parentDir.has_parent_path() && !fs::exists(parentDir)) + { + parentDir = parentDir.parent_path(); + } + + try { + freeBytesAvailable = fs::space(parentDir).available; + if(fs::exists(dataDir)) + { + if(fs::is_directory(dataDir)) + { + QString separator = QDir::toNativeSeparators("/"); + replyStatus = ST_OK; + replyMessage = tr("Directory already exists. Add <code>%1name</code> if you intend to create a new directory here.").arg(separator); + } else { + replyStatus = ST_ERROR; + replyMessage = tr("Path already exists, and is not a directory."); + } + } + } catch(fs::filesystem_error &e) + { + /* Parent directory does not exist or is not accessible */ + replyStatus = ST_ERROR; + replyMessage = tr("Cannot create data directory here."); + } + emit reply(replyStatus, replyMessage, freeBytesAvailable); +} + + +Intro::Intro(QWidget *parent) : + QDialog(parent), + ui(new Ui::Intro), + thread(0), + signalled(false) +{ + ui->setupUi(this); + ui->sizeWarningLabel->setText(ui->sizeWarningLabel->text().arg(BLOCK_CHAIN_SIZE/GB_BYTES)); + startThread(); +} + +Intro::~Intro() +{ + delete ui; + /* Ensure thread is finished before it is deleted */ + emit stopThread(); + thread->wait(); +} + +QString Intro::getDataDirectory() +{ + return ui->dataDirectory->text(); +} + +void Intro::setDataDirectory(const QString &dataDir) +{ + ui->dataDirectory->setText(dataDir); + if(dataDir == getDefaultDataDirectory()) + { + ui->dataDirDefault->setChecked(true); + ui->dataDirectory->setEnabled(false); + ui->ellipsisButton->setEnabled(false); + } else { + ui->dataDirCustom->setChecked(true); + ui->dataDirectory->setEnabled(true); + ui->ellipsisButton->setEnabled(true); + } +} + +QString Intro::getDefaultDataDirectory() +{ + return QString::fromStdString(GetDefaultDataDir().string()); +} + +void Intro::pickDataDirectory() +{ + namespace fs = boost::filesystem;; + QSettings settings; + /* If data directory provided on command line, no need to look at settings + or show a picking dialog */ + if(!GetArg("-datadir", "").empty()) + return; + /* 1) Default data directory for operating system */ + QString dataDir = getDefaultDataDirectory(); + /* 2) Allow QSettings to override default dir */ + dataDir = settings.value("strDataDir", dataDir).toString(); + + if(!fs::exists(dataDir.toStdString()) || GetBoolArg("-choosedatadir", false)) + { + /* If current default data directory does not exist, let the user choose one */ + Intro intro; + intro.setDataDirectory(dataDir); + while(true) + { + if(!intro.exec()) + { + /* Cancel clicked */ + exit(0); + } + dataDir = intro.getDataDirectory(); + try { + fs::create_directory(dataDir.toStdString()); + break; + } catch(fs::filesystem_error &e) { + QMessageBox::critical(0, QObject::tr("Bitcoin"), + QObject::tr("Error: Specified data directory \"%1\" can not be created.").arg(dataDir)); + /* fall through, back to choosing screen */ + } + } + + settings.setValue("strDataDir", dataDir); + } + SoftSetArg("-datadir", dataDir.toStdString()); +} + +void Intro::setStatus(int status, const QString &message, quint64 bytesAvailable) +{ + switch(status) + { + case FreespaceChecker::ST_OK: + ui->errorMessage->setText(message); + ui->errorMessage->setStyleSheet(""); + break; + case FreespaceChecker::ST_ERROR: + ui->errorMessage->setText(tr("Error") + ": " + message); + ui->errorMessage->setStyleSheet("QLabel { color: #800000 }"); + break; + } + /* Indicate number of bytes available */ + if(status == FreespaceChecker::ST_ERROR) + { + ui->freeSpace->setText(""); + } else { + QString freeString = QString::number(bytesAvailable/GB_BYTES) + tr("GB of free space available"); + if(bytesAvailable < BLOCK_CHAIN_SIZE) + { + freeString += " " + tr("(of %1GB needed)").arg(BLOCK_CHAIN_SIZE/GB_BYTES); + ui->freeSpace->setStyleSheet("QLabel { color: #800000 }"); + } else { + ui->freeSpace->setStyleSheet(""); + } + ui->freeSpace->setText(freeString+"."); + } + /* Don't allow confirm in ERROR state */ + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status != FreespaceChecker::ST_ERROR); +} + +void Intro::on_dataDirectory_textChanged(const QString &dataDirStr) +{ + /* Disable OK button until check result comes in */ + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + checkPath(dataDirStr); +} + +void Intro::on_ellipsisButton_clicked() +{ + QString dir = QFileDialog::getExistingDirectory(0, "Choose data directory", ui->dataDirectory->text()); + if(!dir.isEmpty()) + ui->dataDirectory->setText(dir); +} + +void Intro::on_dataDirDefault_clicked() +{ + setDataDirectory(getDefaultDataDirectory()); +} + +void Intro::on_dataDirCustom_clicked() +{ + ui->dataDirectory->setEnabled(true); + ui->ellipsisButton->setEnabled(true); +} + +void Intro::startThread() +{ + thread = new QThread(this); + FreespaceChecker *executor = new FreespaceChecker(this); + executor->moveToThread(thread); + + connect(executor, SIGNAL(reply(int,QString,quint64)), this, SLOT(setStatus(int,QString,quint64))); + connect(this, SIGNAL(requestCheck()), executor, SLOT(check())); + /* make sure executor object is deleted in its own thread */ + connect(this, SIGNAL(stopThread()), executor, SLOT(deleteLater())); + connect(this, SIGNAL(stopThread()), thread, SLOT(quit())); + + thread->start(); +} + +void Intro::checkPath(const QString &dataDir) +{ + mutex.lock(); + pathToCheck = dataDir; + if(!signalled) + { + signalled = true; + emit requestCheck(); + } + mutex.unlock(); +} + +QString Intro::getPathToCheck() +{ + QString retval; + mutex.lock(); + retval = pathToCheck; + signalled = false; /* new request can be queued now */ + mutex.unlock(); + return retval; +} diff --git a/src/qt/intro.h b/src/qt/intro.h new file mode 100644 index 0000000000..b246c65a82 --- /dev/null +++ b/src/qt/intro.h @@ -0,0 +1,67 @@ +#ifndef INTRO_H +#define INTRO_H + +#include <QDialog> +#include <QThread> +#include <QMutex> + +namespace Ui { +class Intro; +} +class FreespaceChecker; + +/** Introduction screen (pre-GUI startup). + Allows the user to choose a data directory, + in which the wallet and block chain will be stored. + */ +class Intro : public QDialog +{ + Q_OBJECT + +public: + explicit Intro(QWidget *parent = 0); + ~Intro(); + + QString getDataDirectory(); + void setDataDirectory(const QString &dataDir); + + /** + * Determine data directory. Let the user choose if the current one doesn't exist. + * + * @note do NOT call global GetDataDir() before calling this function, this + * will cause the wrong path to be cached. + */ + static void pickDataDirectory(); + + /** + * Determine default data directory for operating system. + */ + static QString getDefaultDataDirectory(); +signals: + void requestCheck(); + void stopThread(); + +public slots: + void setStatus(int status, const QString &message, quint64 bytesAvailable); + +private slots: + void on_dataDirectory_textChanged(const QString &arg1); + void on_ellipsisButton_clicked(); + void on_dataDirDefault_clicked(); + void on_dataDirCustom_clicked(); + +private: + Ui::Intro *ui; + QThread *thread; + QMutex mutex; + bool signalled; + QString pathToCheck; + + void startThread(); + void checkPath(const QString &dataDir); + QString getPathToCheck(); + + friend class FreespaceChecker; +}; + +#endif // INTRO_H diff --git a/src/qt/locale/bitcoin_en.ts b/src/qt/locale/bitcoin_en.ts index 7628b39bd3..61af88d056 100644 --- a/src/qt/locale/bitcoin_en.ts +++ b/src/qt/locale/bitcoin_en.ts @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> <TS version="2.0" language="en"> -<defaultcodec>UTF-8</defaultcodec> <context> <name>AboutDialog</name> <message> @@ -689,7 +688,7 @@ Address: %4 <translation>Wallet is <b>encrypted</b> and currently <b>locked</b></translation> </message> <message> - <location filename="../bitcoin.cpp" line="+109"/> + <location filename="../bitcoin.cpp" line="+111"/> <source>A fatal error occurred. Bitcoin can no longer continue safely and will quit.</source> <translation>A fatal error occurred. Bitcoin can no longer continue safely and will quit.</translation> </message> @@ -771,15 +770,38 @@ Address: %4 </message> </context> <context> + <name>FreespaceChecker</name> + <message> + <location filename="../intro.cpp" line="+60"/> + <source>A new data directory will be created.</source> + <translation>A new data directory will be created.</translation> + </message> + <message> + <location line="+17"/> + <source>Directory already exists. Add <code>%1name</code> if you intend to create a new directory here.</source> + <translation>Directory already exists. Add <code>%1name</code> if you intend to create a new directory here.</translation> + </message> + <message> + <location line="+3"/> + <source>Path already exists, and is not a directory.</source> + <translation>Path already exists, and is not a directory.</translation> + </message> + <message> + <location line="+7"/> + <source>Cannot create data directory here.</source> + <translation>Cannot create data directory here.</translation> + </message> +</context> +<context> <name>GUIUtil::HelpMessageBox</name> <message> <location filename="../guiutil.cpp" line="+493"/> - <location line="+12"/> + <location line="+13"/> <source>Bitcoin-Qt</source> <translation>Bitcoin-Qt</translation> </message> <message> - <location line="-12"/> + <location line="-13"/> <source>version</source> <translation>version</translation> </message> @@ -813,6 +835,59 @@ Address: %4 <source>Show splash screen on startup (default: 1)</source> <translation>Show splash screen on startup (default: 1)</translation> </message> + <message> + <location line="+1"/> + <source>Choose data directory on startup (default: 0)</source> + <translation>Choose data directory on startup (default: 0)</translation> + </message> +</context> +<context> + <name>Intro</name> + <message> + <location filename="../forms/intro.ui" line="+14"/> + <source>Welcome</source> + <translation>Welcome</translation> + </message> + <message> + <location line="+9"/> + <source>Welcome to Bitcoin-Qt.</source> + <translation>Welcome to Bitcoin-Qt.</translation> + </message> + <message> + <location line="+26"/> + <source>As this is the first time the program is launched, you can choose where Bitcoin-Qt will store its data.</source> + <translation>As this is the first time the program is launched, you can choose where Bitcoin-Qt will store its data.</translation> + </message> + <message> + <location line="+10"/> + <source>Bitcoin-Qt will download and store a copy of the Bitcoin block chain. At least %1GB of data will be stored in this directory, and it will grow over time. The wallet will also be stored in this directory.</source> + <translation>Bitcoin-Qt will download and store a copy of the Bitcoin block chain. At least %1GB of data will be stored in this directory, and it will grow over time. The wallet will also be stored in this directory.</translation> + </message> + <message> + <location line="+10"/> + <source>Use the default data directory</source> + <translation>Use the default data directory</translation> + </message> + <message> + <location line="+7"/> + <source>Use a custom data directory:</source> + <translation>Use a custom data directory:</translation> + </message> + <message> + <location filename="../intro.cpp" line="+100"/> + <source>Error</source> + <translation>Error</translation> + </message> + <message> + <location line="+9"/> + <source>GB of free space available</source> + <translation>GB of free space available</translation> + </message> + <message> + <location line="+3"/> + <source>(of %1GB needed)</source> + <translation>(of %1GB needed)</translation> + </message> </context> <context> <name>OptionsDialog</name> @@ -1103,6 +1178,25 @@ Address: %4 </message> </context> <context> + <name>QObject</name> + <message> + <location filename="../bitcoin.cpp" line="+92"/> + <location filename="../intro.cpp" line="-32"/> + <source>Bitcoin</source> + <translation>Bitcoin</translation> + </message> + <message> + <location line="+1"/> + <source>Error: Specified data directory "%1" does not exist.</source> + <translation>Error: Specified data directory "%1" does not exist.</translation> + </message> + <message> + <location filename="../intro.cpp" line="+1"/> + <source>Error: Specified data directory "%1" can not be created.</source> + <translation>Error: Specified data directory "%1" can not be created.</translation> + </message> +</context> +<context> <name>QRCodeDialog</name> <message> <location filename="../forms/qrcodedialog.ui" line="+14"/> diff --git a/src/qt/notificator.cpp b/src/qt/notificator.cpp index 7cfaef6079..903c54b39d 100644 --- a/src/qt/notificator.cpp +++ b/src/qt/notificator.cpp @@ -1,9 +1,9 @@ #include "notificator.h" +#include <QApplication> #include <QMetaType> #include <QVariant> #include <QIcon> -#include <QApplication> #include <QStyle> #include <QByteArray> #include <QSystemTrayIcon> diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 6b1b4e3d8e..7ebe5b4755 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -89,7 +89,7 @@ bool OptionsModel::Upgrade() settings.setValue("bImportFinished", true); // Move settings from old wallet.dat (if any): - CWalletDB walletdb("wallet.dat"); + CWalletDB walletdb(strWalletFile); QList<QString> intOptions; intOptions << "nDisplayUnit" << "nTransactionFee"; diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index 0b0bce55bb..8178065bcc 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -2,14 +2,14 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <QApplication> - #include "paymentserver.h" #include "guiconstants.h" #include "ui_interface.h" #include "util.h" +#undef loop /* Todo: ugh, remove this when the #define loop is gone from util.h */ +#include <QApplication> #include <QByteArray> #include <QDataStream> #include <QDebug> diff --git a/src/qt/splashscreen.cpp b/src/qt/splashscreen.cpp index e400278123..43430a858e 100644 --- a/src/qt/splashscreen.cpp +++ b/src/qt/splashscreen.cpp @@ -2,9 +2,9 @@ #include "clientversion.h" #include "util.h" -#include <QPainter> -#undef loop /* ugh, remove this when the #define loop is gone from util.h */ +#undef loop /* Todo: ugh, remove this when the #define loop is gone from util.h */ #include <QApplication> +#include <QPainter> SplashScreen::SplashScreen(const QPixmap &pixmap, Qt::WindowFlags f) : QSplashScreen(pixmap, f) diff --git a/src/walletdb.cpp b/src/walletdb.cpp index 702e219a5b..7aad779767 100644 --- a/src/walletdb.cpp +++ b/src/walletdb.cpp @@ -5,6 +5,7 @@ #include "walletdb.h" #include "wallet.h" +#include <boost/version.hpp> #include <boost/filesystem.hpp> using namespace std; |