mirror of
https://github.com/vale981/tdesktop
synced 2025-03-09 20:46:58 -04:00
757 lines
18 KiB
C++
757 lines
18 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
For license and copyright information please follow this link:
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
*/
|
|
#include "storage/cache/storage_cache_database.h"
|
|
|
|
#include "storage/cache/storage_cache_cleaner.h"
|
|
#include "storage/storage_encryption.h"
|
|
#include "storage/storage_encrypted_file.h"
|
|
#include "base/flat_set.h"
|
|
#include "base/algorithm.h"
|
|
#include <crl/crl.h>
|
|
#include <xxhash.h>
|
|
#include <QtCore/QDir>
|
|
#include <unordered_map>
|
|
#include <set>
|
|
|
|
namespace std {
|
|
|
|
template <>
|
|
struct hash<Storage::Cache::Key> {
|
|
size_t operator()(const Storage::Cache::Key &key) const {
|
|
return (hash<uint64>()(key.high) ^ hash<uint64>()(key.low));
|
|
}
|
|
};
|
|
|
|
} // namespace std
|
|
|
|
namespace Storage {
|
|
namespace Cache {
|
|
namespace details {
|
|
namespace {
|
|
|
|
constexpr auto kMaxBundledRecords = 256 * 1024;
|
|
constexpr auto kReadBlockSize = 8 * 1024 * 1024;
|
|
constexpr auto kRecordSizeUnknown = size_type(-1);
|
|
constexpr auto kRecordSizeInvalid = size_type(-2);
|
|
constexpr auto kMaxDataSize = 10 * 1024 * 1024;
|
|
|
|
using RecordType = uint8;
|
|
using PlaceId = std::array<uint8, 7>;
|
|
using EntrySize = std::array<uint8, 3>;
|
|
using RecordsCount = std::array<uint8, 3>;
|
|
|
|
static_assert(kMaxBundledRecords < (1 << (RecordsCount().size() * 8)));
|
|
static_assert(kMaxDataSize < (1 << (EntrySize().size() * 8)));
|
|
|
|
template <typename Packed>
|
|
Packed ReadTo(size_type count) {
|
|
Expects(count >= 0 && count < (1 << (Packed().size() * 8)));
|
|
|
|
auto result = Packed();
|
|
for (auto &element : result) {
|
|
element = uint8(count & 0xFF);
|
|
count >>= 8;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
template <typename Packed>
|
|
size_type ReadFrom(Packed count) {
|
|
auto result = size_type();
|
|
RecordsCount a;
|
|
for (auto &element : (count | ranges::view::reverse)) {
|
|
result <<= 8;
|
|
result |= size_type(element);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
uint32 CountChecksum(bytes::const_span data) {
|
|
const auto seed = uint32(0);
|
|
return XXH32(data.data(), data.size(), seed);
|
|
}
|
|
|
|
QString PlaceFromId(PlaceId place) {
|
|
auto result = QString();
|
|
result.reserve(15);
|
|
const auto pushDigit = [&](uint8 digit) {
|
|
const auto hex = (digit < 0x0A)
|
|
? char('0' + digit)
|
|
: char('A' + (digit - 0x0A));
|
|
result.push_back(hex);
|
|
};
|
|
const auto push = [&](uint8 value) {
|
|
pushDigit(value & 0x0F);
|
|
pushDigit(value >> 4);
|
|
};
|
|
for (auto i = 0; i != place.size(); ++i) {
|
|
push(place[i]);
|
|
if (i == 1) {
|
|
result.push_back('/');
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
struct Store {
|
|
static constexpr auto kType = RecordType(0x01);
|
|
|
|
RecordType type = kType;
|
|
uint8 tag = 0;
|
|
PlaceId place = { { 0 } };
|
|
EntrySize size = { { 0 } };
|
|
uint32 checksum = 0;
|
|
Key key;
|
|
};
|
|
static_assert(sizeof(Store) == 1 + 7 + 1 + 3 + 4 + 16);
|
|
|
|
struct MultiStoreHeader {
|
|
static constexpr auto kType = RecordType(0x02);
|
|
|
|
explicit MultiStoreHeader(size_type count = 0);
|
|
|
|
RecordType type = kType;
|
|
RecordsCount count = { { 0 } };
|
|
};
|
|
struct MultiStorePart {
|
|
uint8 reserved = 0;
|
|
PlaceId place = { { 0 } };
|
|
uint8 tag = 0;
|
|
EntrySize size = { { 0 } };
|
|
uint32 checksum = 0;
|
|
Key key;
|
|
};
|
|
static_assert(sizeof(MultiStoreHeader) == 4);
|
|
static_assert(sizeof(MultiStorePart) == sizeof(Store));
|
|
|
|
MultiStoreHeader::MultiStoreHeader(size_type count)
|
|
: type(kType)
|
|
, count(ReadTo<RecordsCount>(count)) {
|
|
Expects(count >= 0 && count < kMaxBundledRecords);
|
|
}
|
|
|
|
struct MultiRemoveHeader {
|
|
static constexpr auto kType = RecordType(0x03);
|
|
|
|
explicit MultiRemoveHeader(size_type count = 0);
|
|
|
|
RecordType type = kType;
|
|
RecordsCount count = { { 0 } };
|
|
};
|
|
struct MultiRemovePart {
|
|
Key key;
|
|
};
|
|
static_assert(sizeof(MultiRemoveHeader) == 4);
|
|
static_assert(sizeof(MultiRemovePart) == 16);
|
|
|
|
MultiRemoveHeader::MultiRemoveHeader(size_type count)
|
|
: type(kType)
|
|
, count(ReadTo<RecordsCount>(count)) {
|
|
Expects(count >= 0 && count < kMaxBundledRecords);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
class Database {
|
|
public:
|
|
using Wrapper = Cache::Database;
|
|
using Settings = Wrapper::Settings;
|
|
Database(
|
|
crl::weak_on_queue<Database> weak,
|
|
const QString &path,
|
|
const Settings &settings);
|
|
|
|
void open(EncryptionKey key, FnMut<void(Error)> done);
|
|
void close(FnMut<void()> done);
|
|
|
|
void put(const Key &key, QByteArray value, FnMut<void(Error)> done);
|
|
void get(const Key &key, FnMut<void(QByteArray)> done);
|
|
void remove(const Key &key, FnMut<void()> done);
|
|
|
|
void clear(FnMut<void(Error)> done);
|
|
|
|
private:
|
|
struct Entry {
|
|
Entry() = default;
|
|
Entry(PlaceId place, uint8 tag, uint32 checksum, size_type size);
|
|
|
|
uint64 tag = 0;
|
|
uint32 checksum = 0;
|
|
size_type size = 0;
|
|
PlaceId place = { { 0 } };
|
|
};
|
|
struct CleanerWrap {
|
|
std::unique_ptr<Cleaner> object;
|
|
base::binary_guard guard;
|
|
};
|
|
|
|
template <typename Callback, typename ...Args>
|
|
void invokeCallback(Callback &&callback, Args &&...args);
|
|
|
|
Error ioError(const QString &path) const;
|
|
|
|
QString computePath(Version version) const;
|
|
QString binlogPath(Version version) const;
|
|
QString binlogPath() const;
|
|
QString binlogFilename() const;
|
|
File::Result openBinlog(
|
|
Version version,
|
|
File::Mode mode,
|
|
EncryptionKey &key);
|
|
void readBinlog();
|
|
size_type readBinlogRecords(bytes::const_span data);
|
|
size_type readBinlogRecordSize(bytes::const_span data) const;
|
|
bool readBinlogRecord(bytes::const_span data);
|
|
bool readRecordStore(bytes::const_span data);
|
|
bool readRecordMultiStore(bytes::const_span data);
|
|
bool readRecordMultiRemove(bytes::const_span data);
|
|
|
|
Version findAvailableVersion() const;
|
|
QString versionPath() const;
|
|
bool writeVersion(Version version);
|
|
Version readVersion() const;
|
|
|
|
QString placePath(PlaceId place) const;
|
|
bool isFreePlace(PlaceId place) const;
|
|
QString writeKeyPlace(const Key &key, size_type size, uint32 checksum);
|
|
void writeMultiRemove();
|
|
|
|
void createCleaner();
|
|
void cleanerDone(Error error);
|
|
|
|
crl::weak_on_queue<Database> _weak;
|
|
QString _base, _path;
|
|
Settings _settings;
|
|
EncryptionKey _key;
|
|
File _binlog;
|
|
std::unordered_map<Key, Entry> _map;
|
|
std::set<Key> _removing;
|
|
|
|
CleanerWrap _cleaner;
|
|
|
|
};
|
|
|
|
Database::Entry::Entry(
|
|
PlaceId place,
|
|
uint8 tag,
|
|
uint32 checksum,
|
|
size_type size)
|
|
: tag(tag)
|
|
, checksum(checksum)
|
|
, place(place)
|
|
, size(size) {
|
|
}
|
|
|
|
Database::Database(
|
|
crl::weak_on_queue<Database> weak,
|
|
const QString &path,
|
|
const Settings &settings)
|
|
: _weak(std::move(weak))
|
|
, _base(ComputeBasePath(path))
|
|
, _settings(settings) {
|
|
}
|
|
|
|
template <typename Callback, typename ...Args>
|
|
void Database::invokeCallback(Callback &&callback, Args &&...args) {
|
|
if (callback) {
|
|
callback(std::move(args)...);
|
|
//crl::on_main([
|
|
// callback = std::move(callback),
|
|
// args = std::forward<Args>(args)...
|
|
//]() mutable {
|
|
// callback(std::move(args)...);
|
|
//});
|
|
}
|
|
}
|
|
|
|
Error Database::ioError(const QString &path) const {
|
|
return { Error::Type::IO, path };
|
|
}
|
|
|
|
void Database::open(EncryptionKey key, FnMut<void(Error)> done) {
|
|
const auto version = readVersion();
|
|
const auto result = openBinlog(version, File::Mode::ReadAppend, key);
|
|
switch (result) {
|
|
case File::Result::Success:
|
|
invokeCallback(done, Error::NoError());
|
|
break;
|
|
case File::Result::LockFailed:
|
|
invokeCallback(
|
|
done,
|
|
Error{ Error::Type::LockFailed, binlogPath(version) });
|
|
break;
|
|
case File::Result::WrongKey:
|
|
invokeCallback(
|
|
done,
|
|
Error{ Error::Type::WrongKey, binlogPath(version) });
|
|
break;
|
|
case File::Result::Failed: {
|
|
const auto available = findAvailableVersion();
|
|
if (writeVersion(available)) {
|
|
const auto open = openBinlog(available, File::Mode::Write, key);
|
|
if (open == File::Result::Success) {
|
|
invokeCallback(done, Error::NoError());
|
|
} else {
|
|
invokeCallback(done, ioError(binlogPath(available)));
|
|
}
|
|
} else {
|
|
invokeCallback(done, ioError(versionPath()));
|
|
}
|
|
} break;
|
|
default: Unexpected("Result from Database::openBinlog.");
|
|
}
|
|
}
|
|
|
|
QString Database::computePath(Version version) const {
|
|
return _base + QString::number(version) + '/';
|
|
}
|
|
|
|
QString Database::binlogFilename() const {
|
|
return QStringLiteral("binlog");
|
|
}
|
|
|
|
QString Database::binlogPath(Version version) const {
|
|
return computePath(version) + binlogFilename();
|
|
}
|
|
|
|
QString Database::binlogPath() const {
|
|
return _path + binlogFilename();
|
|
}
|
|
|
|
File::Result Database::openBinlog(
|
|
Version version,
|
|
File::Mode mode,
|
|
EncryptionKey &key) {
|
|
const auto path = binlogPath(version);
|
|
const auto result = _binlog.open(path, mode, key);
|
|
if (result == File::Result::Success) {
|
|
_path = computePath(version);
|
|
_key = std::move(key);
|
|
createCleaner();
|
|
readBinlog();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void Database::readBinlog() {
|
|
auto data = bytes::vector(kReadBlockSize);
|
|
const auto full = bytes::make_span(data);
|
|
auto notParsedBytes = index_type(0);
|
|
while (true) {
|
|
Assert(notParsedBytes < full.size());
|
|
const auto readBytes = _binlog.read(full.subspan(notParsedBytes));
|
|
if (!readBytes) {
|
|
break;
|
|
}
|
|
notParsedBytes += readBytes;
|
|
const auto bytes = full.subspan(0, notParsedBytes);
|
|
const auto parsedTill = readBinlogRecords(bytes);
|
|
if (parsedTill == kRecordSizeInvalid) {
|
|
break;
|
|
}
|
|
Assert(parsedTill >= 0 && parsedTill <= notParsedBytes);
|
|
notParsedBytes -= parsedTill;
|
|
if (parsedTill > 0 && parsedTill < bytes.size()) {
|
|
bytes::move(full, bytes.subspan(parsedTill));
|
|
}
|
|
}
|
|
_binlog.seek(_binlog.offset() - notParsedBytes);
|
|
}
|
|
|
|
size_type Database::readBinlogRecords(bytes::const_span data) {
|
|
auto result = 0;
|
|
while (true) {
|
|
const auto size = readBinlogRecordSize(data);
|
|
if (size == kRecordSizeUnknown || size > data.size()) {
|
|
return result;
|
|
} else if (size == kRecordSizeInvalid || !readBinlogRecord(data)) {
|
|
return (result > 0) ? result : kRecordSizeInvalid;
|
|
} else {
|
|
result += size;
|
|
data = data.subspan(size);
|
|
}
|
|
}
|
|
}
|
|
|
|
size_type Database::readBinlogRecordSize(bytes::const_span data) const {
|
|
if (data.empty()) {
|
|
return kRecordSizeUnknown;
|
|
}
|
|
|
|
switch (static_cast<RecordType>(data[0])) {
|
|
case Store::kType:
|
|
return sizeof(Store);
|
|
|
|
case MultiStoreHeader::kType:
|
|
if (data.size() >= sizeof(MultiStoreHeader)) {
|
|
const auto header = reinterpret_cast<const MultiStoreHeader*>(
|
|
data.data());
|
|
const auto count = ReadFrom(header->count);
|
|
return (count > 0 && count < kMaxBundledRecords)
|
|
? (sizeof(MultiStoreHeader)
|
|
+ count * sizeof(MultiStorePart))
|
|
: kRecordSizeInvalid;
|
|
}
|
|
return kRecordSizeUnknown;
|
|
|
|
case MultiRemoveHeader::kType:
|
|
if (data.size() >= sizeof(MultiRemoveHeader)) {
|
|
const auto header = reinterpret_cast<const MultiRemoveHeader*>(
|
|
data.data());
|
|
const auto count = ReadFrom(header->count);
|
|
return (count > 0 && count < kMaxBundledRecords)
|
|
? (sizeof(MultiRemoveHeader)
|
|
+ count * sizeof(MultiRemovePart))
|
|
: kRecordSizeInvalid;
|
|
}
|
|
return kRecordSizeUnknown;
|
|
|
|
}
|
|
return kRecordSizeInvalid;
|
|
}
|
|
|
|
bool Database::readBinlogRecord(bytes::const_span data) {
|
|
Expects(!data.empty());
|
|
|
|
switch (static_cast<RecordType>(data[0])) {
|
|
case Store::kType:
|
|
return readRecordStore(data);
|
|
|
|
case MultiStoreHeader::kType:
|
|
return readRecordMultiStore(data);
|
|
|
|
case MultiRemoveHeader::kType:
|
|
return readRecordMultiRemove(data);
|
|
|
|
}
|
|
Unexpected("Bad type in Database::readBinlogRecord.");
|
|
}
|
|
|
|
bool Database::readRecordStore(bytes::const_span data) {
|
|
const auto record = reinterpret_cast<const Store*>(data.data());
|
|
const auto size = ReadFrom(record->size);
|
|
if (size > kMaxDataSize) {
|
|
return false;
|
|
}
|
|
_map[record->key] = Entry(
|
|
record->place,
|
|
record->tag,
|
|
record->checksum,
|
|
size);
|
|
return true;
|
|
}
|
|
|
|
bool Database::readRecordMultiStore(bytes::const_span data) {
|
|
const auto bytes = data.data();
|
|
const auto record = reinterpret_cast<const MultiStoreHeader*>(bytes);
|
|
const auto count = ReadFrom(record->count);
|
|
Assert(data.size() >= sizeof(MultiStoreHeader)
|
|
+ count * sizeof(MultiStorePart));
|
|
const auto parts = gsl::make_span(
|
|
reinterpret_cast<const MultiStorePart*>(
|
|
bytes + sizeof(MultiStoreHeader)),
|
|
count);
|
|
for (const auto &part : parts) {
|
|
const auto size = ReadFrom(part.size);
|
|
if (part.reserved != 0 || size > kMaxDataSize) {
|
|
return false;
|
|
}
|
|
_map[part.key] = Entry(part.place, part.tag, part.checksum, size);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Database::readRecordMultiRemove(bytes::const_span data) {
|
|
const auto bytes = data.data();
|
|
const auto record = reinterpret_cast<const MultiRemoveHeader*>(bytes);
|
|
const auto count = ReadFrom(record->count);
|
|
Assert(data.size() >= sizeof(MultiRemoveHeader)
|
|
+ count * sizeof(MultiRemovePart));
|
|
const auto parts = gsl::make_span(
|
|
reinterpret_cast<const MultiRemovePart*>(
|
|
bytes + sizeof(MultiRemoveHeader)),
|
|
count);
|
|
for (const auto &part : parts) {
|
|
_map.erase(part.key);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Database::close(FnMut<void()> done) {
|
|
_cleaner = CleanerWrap();
|
|
_binlog.close();
|
|
invokeCallback(done);
|
|
}
|
|
|
|
void Database::put(
|
|
const Key &key,
|
|
QByteArray value,
|
|
FnMut<void(Error)> done) {
|
|
const auto checksum = CountChecksum(bytes::make_span(value));
|
|
const auto path = writeKeyPlace(key, value.size(), checksum);
|
|
if (path.isEmpty()) {
|
|
invokeCallback(done, ioError(binlogPath()));
|
|
return;
|
|
}
|
|
File data;
|
|
const auto result = data.open(path, File::Mode::Write, _key);
|
|
switch (result) {
|
|
case File::Result::Failed:
|
|
invokeCallback(done, ioError(path));
|
|
break;
|
|
|
|
case File::Result::LockFailed:
|
|
invokeCallback(done, Error{ Error::Type::LockFailed, path });
|
|
break;
|
|
|
|
case File::Result::Success: {
|
|
const auto success = data.writeWithPadding(bytes::make_span(value));
|
|
if (!success) {
|
|
data.close();
|
|
remove(key, nullptr);
|
|
invokeCallback(done, ioError(path));
|
|
} else {
|
|
data.flush();
|
|
invokeCallback(done, Error::NoError());
|
|
}
|
|
} break;
|
|
|
|
default: Unexpected("Result in Database::put.");
|
|
}
|
|
}
|
|
|
|
QString Database::writeKeyPlace(
|
|
const Key &key,
|
|
size_type size,
|
|
uint32 checksum) {
|
|
Expects(size <= kMaxDataSize);
|
|
|
|
auto record = Store();
|
|
record.key = key;
|
|
record.size = ReadTo<EntrySize>(size);
|
|
record.checksum = checksum;
|
|
do {
|
|
bytes::set_random(bytes::object_as_span(&record.place));
|
|
} while (!isFreePlace(record.place));
|
|
|
|
const auto result = placePath(record.place);
|
|
auto writeable = record;
|
|
const auto success = _binlog.write(bytes::object_as_span(&writeable));
|
|
if (!success) {
|
|
return QString();
|
|
}
|
|
_binlog.flush();
|
|
readRecordStore(bytes::object_as_span(&record));
|
|
return result;
|
|
}
|
|
|
|
void Database::get(const Key &key, FnMut<void(QByteArray)> done) {
|
|
const auto i = _map.find(key);
|
|
if (i == _map.end()) {
|
|
invokeCallback(done, QByteArray());
|
|
return;
|
|
}
|
|
const auto &entry = i->second;
|
|
const auto path = placePath(entry.place);
|
|
File data;
|
|
const auto result = data.open(path, File::Mode::Read, _key);
|
|
switch (result) {
|
|
case File::Result::Failed:
|
|
invokeCallback(done, QByteArray());
|
|
break;
|
|
|
|
case File::Result::WrongKey:
|
|
invokeCallback(done, QByteArray());
|
|
break;
|
|
|
|
case File::Result::Success: {
|
|
auto result = QByteArray(entry.size, Qt::Uninitialized);
|
|
const auto bytes = bytes::make_span(result);
|
|
const auto read = data.readWithPadding(bytes);
|
|
if (read != entry.size || CountChecksum(bytes) != entry.checksum) {
|
|
invokeCallback(done, QByteArray());
|
|
} else {
|
|
invokeCallback(done, std::move(result));
|
|
}
|
|
} break;
|
|
|
|
default: Unexpected("Result in Database::get.");
|
|
}
|
|
}
|
|
|
|
void Database::remove(const Key &key, FnMut<void()> done) {
|
|
const auto i = _map.find(key);
|
|
if (i != _map.end()) {
|
|
_removing.emplace(key);
|
|
if (true || _removing.size() == kMaxBundledRecords) {
|
|
writeMultiRemove();
|
|
// cancel timeout?..
|
|
} else {
|
|
// timeout?..
|
|
}
|
|
|
|
const auto &entry = i->second;
|
|
const auto path = placePath(entry.place);
|
|
_map.erase(i);
|
|
QFile(path).remove();
|
|
}
|
|
invokeCallback(done);
|
|
}
|
|
|
|
void Database::writeMultiRemove() {
|
|
Expects(_removing.size() <= kMaxBundledRecords);
|
|
|
|
if (_removing.empty()) {
|
|
return;
|
|
}
|
|
const auto size = _removing.size();
|
|
auto header = MultiRemoveHeader(size);
|
|
auto list = std::vector<MultiRemovePart>();
|
|
list.reserve(size);
|
|
for (const auto &key : base::take(_removing)) {
|
|
list.push_back({ key });
|
|
}
|
|
if (_binlog.write(bytes::object_as_span(&header))) {
|
|
_binlog.write(bytes::make_span(list));
|
|
_binlog.flush();
|
|
}
|
|
}
|
|
|
|
void Database::createCleaner() {
|
|
auto [left, right] = base::make_binary_guard();
|
|
_cleaner.guard = std::move(left);
|
|
auto done = [weak = _weak](Error error) {
|
|
weak.with([=](Database &that) {
|
|
that.cleanerDone(error);
|
|
});
|
|
};
|
|
_cleaner.object = std::make_unique<Cleaner>(
|
|
_base,
|
|
std::move(right),
|
|
std::move(done));
|
|
}
|
|
|
|
void Database::cleanerDone(Error error) {
|
|
_cleaner = CleanerWrap();
|
|
}
|
|
|
|
void Database::clear(FnMut<void(Error)> done) {
|
|
Expects(_key.empty());
|
|
|
|
const auto version = findAvailableVersion();
|
|
invokeCallback(
|
|
done,
|
|
writeVersion(version) ? Error::NoError() : ioError(versionPath()));
|
|
}
|
|
|
|
auto Database::findAvailableVersion() const -> Version {
|
|
const auto entries = QDir(_base).entryList(
|
|
QDir::Dirs | QDir::NoDotAndDotDot);
|
|
auto versions = base::flat_set<Version>();
|
|
for (const auto entry : entries) {
|
|
versions.insert(entry.toInt());
|
|
}
|
|
auto result = Version();
|
|
for (const auto version : versions) {
|
|
if (result != version) {
|
|
break;
|
|
}
|
|
++result;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
QString Database::versionPath() const {
|
|
return VersionFilePath(_base);
|
|
}
|
|
|
|
bool Database::writeVersion(Version version) {
|
|
return WriteVersionValue(_base, version);
|
|
}
|
|
|
|
auto Database::readVersion() const -> Version {
|
|
if (const auto result = ReadVersionValue(_base)) {
|
|
return *result;
|
|
}
|
|
return Version();
|
|
}
|
|
|
|
QString Database::placePath(PlaceId place) const {
|
|
return _path + PlaceFromId(place);
|
|
}
|
|
|
|
bool Database::isFreePlace(PlaceId place) const {
|
|
return !QFile(placePath(place)).exists();
|
|
}
|
|
|
|
} // namespace details
|
|
|
|
Database::Database(const QString &path, const Settings &settings)
|
|
: _wrapped(path, settings) {
|
|
}
|
|
|
|
void Database::open(EncryptionKey key, FnMut<void(Error)> done) {
|
|
_wrapped.with([
|
|
key,
|
|
done = std::move(done)
|
|
](Implementation &unwrapped) mutable {
|
|
unwrapped.open(key, std::move(done));
|
|
});
|
|
}
|
|
|
|
void Database::close(FnMut<void()> done) {
|
|
_wrapped.with([
|
|
done = std::move(done)
|
|
](Implementation &unwrapped) mutable {
|
|
unwrapped.close(std::move(done));
|
|
});
|
|
}
|
|
|
|
void Database::put(
|
|
const Key &key,
|
|
QByteArray value,
|
|
FnMut<void(Error)> done) {
|
|
_wrapped.with([
|
|
key,
|
|
value = std::move(value),
|
|
done = std::move(done)
|
|
](Implementation &unwrapped) mutable {
|
|
unwrapped.put(key, std::move(value), std::move(done));
|
|
});
|
|
}
|
|
|
|
void Database::get(const Key &key, FnMut<void(QByteArray)> done) {
|
|
_wrapped.with([
|
|
key,
|
|
done = std::move(done)
|
|
](Implementation &unwrapped) mutable {
|
|
unwrapped.get(key, std::move(done));
|
|
});
|
|
}
|
|
|
|
void Database::remove(const Key &key, FnMut<void()> done) {
|
|
_wrapped.with([
|
|
key,
|
|
done = std::move(done)
|
|
](Implementation &unwrapped) mutable {
|
|
unwrapped.remove(key, std::move(done));
|
|
});
|
|
}
|
|
|
|
void Database::clear(FnMut<void(Error)> done) {
|
|
_wrapped.with([
|
|
done = std::move(done)
|
|
](Implementation &unwrapped) mutable {
|
|
unwrapped.clear(std::move(done));
|
|
});
|
|
}
|
|
|
|
Database::~Database() = default;
|
|
|
|
} // namespace Cache
|
|
} // namespace Storage
|