mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-03-23 11:47:57 +00:00
Add a generic BinlogReader.
This commit is contained in:
parent
a4c1d5fe9d
commit
2f9d65b4eb
@ -16,7 +16,8 @@ inline decltype(auto) match_method(
|
|||||||
Data &&data,
|
Data &&data,
|
||||||
Method &&method,
|
Method &&method,
|
||||||
Methods &&...methods) {
|
Methods &&...methods) {
|
||||||
if constexpr (rpl::details::is_callable_plain_v<Method, Data&&>) {
|
using namespace rpl::details;
|
||||||
|
if constexpr (is_callable_plain_v<Method, Data&&>) {
|
||||||
return std::forward<Method>(method)(std::forward<Data>(data));
|
return std::forward<Method>(method)(std::forward<Data>(data));
|
||||||
} else {
|
} else {
|
||||||
return match_method(
|
return match_method(
|
||||||
@ -25,4 +26,27 @@ inline decltype(auto) match_method(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <
|
||||||
|
typename Data1,
|
||||||
|
typename Data2,
|
||||||
|
typename Method,
|
||||||
|
typename ...Methods>
|
||||||
|
inline decltype(auto) match_method2(
|
||||||
|
Data1 &&data1,
|
||||||
|
Data2 &&data2,
|
||||||
|
Method &&method,
|
||||||
|
Methods &&...methods) {
|
||||||
|
using namespace rpl::details;
|
||||||
|
if constexpr (is_callable_plain_v<Method, Data1&&, Data2&&>) {
|
||||||
|
return std::forward<Method>(method)(
|
||||||
|
std::forward<Data1>(data1),
|
||||||
|
std::forward<Data2>(data2));
|
||||||
|
} else {
|
||||||
|
return match_method2(
|
||||||
|
std::forward<Data1>(data1),
|
||||||
|
std::forward<Data2>(data2),
|
||||||
|
std::forward<Methods>(methods)...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace base
|
} // namespace base
|
||||||
|
89
Telegram/SourceFiles/storage/cache/storage_cache_binlog_reader.cpp
vendored
Normal file
89
Telegram/SourceFiles/storage/cache/storage_cache_binlog_reader.cpp
vendored
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
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_binlog_reader.h"
|
||||||
|
|
||||||
|
namespace Storage {
|
||||||
|
namespace Cache {
|
||||||
|
namespace details {
|
||||||
|
|
||||||
|
BinlogWrapper::BinlogWrapper(File &binlog, const Settings &settings)
|
||||||
|
: _binlog(binlog)
|
||||||
|
, _settings(settings)
|
||||||
|
, _till(_binlog.size())
|
||||||
|
, _data(_settings.readBlockSize)
|
||||||
|
, _full(_data) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BinlogWrapper::failed() const {
|
||||||
|
return _failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BinlogWrapper::readPart() {
|
||||||
|
if (_finished) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto no = [&] {
|
||||||
|
finish();
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
const auto offset = _binlog.offset();
|
||||||
|
const auto left = (_till - offset);
|
||||||
|
if (!left) {
|
||||||
|
return no();
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert(_notParsedBytes >= 0 && _notParsedBytes <= _part.size());
|
||||||
|
if (_notParsedBytes > 0 && _notParsedBytes < _part.size()) {
|
||||||
|
bytes::move(_full, _part.subspan(_part.size() - _notParsedBytes));
|
||||||
|
}
|
||||||
|
const auto amount = std::min(
|
||||||
|
left,
|
||||||
|
int64(_full.size() - _notParsedBytes));
|
||||||
|
Assert(amount > 0);
|
||||||
|
const auto readBytes = _binlog.read(
|
||||||
|
_full.subspan(_notParsedBytes, amount));
|
||||||
|
if (!readBytes) {
|
||||||
|
return no();
|
||||||
|
}
|
||||||
|
_notParsedBytes += readBytes;
|
||||||
|
_part = _full.subspan(0, _notParsedBytes);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes::const_span BinlogWrapper::readRecord(ReadRecordSize readRecordSize) {
|
||||||
|
if (_finished) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto size = readRecordSize(*this, _part);
|
||||||
|
if (size == kRecordSizeUnknown || size > _part.size()) {
|
||||||
|
return {};
|
||||||
|
} else if (size == kRecordSizeInvalid) {
|
||||||
|
finish();
|
||||||
|
_finished = _failed = true;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
Assert(size >= 0 && size <= _notParsedBytes);
|
||||||
|
const auto result = _part.subspan(0, size);
|
||||||
|
_part = _part.subspan(size);
|
||||||
|
_notParsedBytes -= size;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BinlogWrapper::finish(size_type rollback) {
|
||||||
|
Expects(rollback >= 0);
|
||||||
|
|
||||||
|
if (rollback > 0) {
|
||||||
|
_failed = true;
|
||||||
|
_notParsedBytes += rollback;
|
||||||
|
}
|
||||||
|
_binlog.seek(_binlog.offset() - _notParsedBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace details
|
||||||
|
} // namespace Cache
|
||||||
|
} // namespace Storage
|
261
Telegram/SourceFiles/storage/cache/storage_cache_binlog_reader.h
vendored
Normal file
261
Telegram/SourceFiles/storage/cache/storage_cache_binlog_reader.h
vendored
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "storage/cache/storage_cache_types.h"
|
||||||
|
#include "storage/storage_encrypted_file.h"
|
||||||
|
#include "base/bytes.h"
|
||||||
|
#include "base/match_method.h"
|
||||||
|
|
||||||
|
namespace Storage {
|
||||||
|
namespace Cache {
|
||||||
|
namespace details {
|
||||||
|
|
||||||
|
template <typename ...Records>
|
||||||
|
class BinlogReader;
|
||||||
|
|
||||||
|
class BinlogWrapper {
|
||||||
|
public:
|
||||||
|
BinlogWrapper(File &binlog, const Settings &settings);
|
||||||
|
|
||||||
|
bool failed() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <typename ...Records>
|
||||||
|
friend class BinlogReader;
|
||||||
|
|
||||||
|
bool readPart();
|
||||||
|
void finish(size_type rollback = 0);
|
||||||
|
|
||||||
|
using ReadRecordSize = size_type (*)(
|
||||||
|
const BinlogWrapper &that,
|
||||||
|
bytes::const_span data);
|
||||||
|
bytes::const_span readRecord(ReadRecordSize readRecordSize);
|
||||||
|
|
||||||
|
File &_binlog;
|
||||||
|
Settings _settings;
|
||||||
|
|
||||||
|
int64 _till = 0;
|
||||||
|
bytes::vector _data;
|
||||||
|
bytes::span _full;
|
||||||
|
bytes::span _part;
|
||||||
|
index_type _notParsedBytes = 0;
|
||||||
|
bool _finished = false;
|
||||||
|
bool _failed = false;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename ...Records>
|
||||||
|
class BinlogReader {
|
||||||
|
public:
|
||||||
|
explicit BinlogReader(BinlogWrapper &wrapper);
|
||||||
|
|
||||||
|
template <typename ...Handlers>
|
||||||
|
bool readTillEnd(Handlers &&...handlers);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static size_type ReadRecordSize(
|
||||||
|
const BinlogWrapper &that,
|
||||||
|
bytes::const_span data);
|
||||||
|
|
||||||
|
template <typename ...Handlers>
|
||||||
|
bool handleRecord(bytes::const_span data, Handlers &&...handlers) const;
|
||||||
|
|
||||||
|
BinlogWrapper &_wrapper;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Record>
|
||||||
|
struct MultiRecord {
|
||||||
|
using true_t = char;
|
||||||
|
using false_t = true_t(&)[2];
|
||||||
|
static_assert(sizeof(true_t) != sizeof(false_t));
|
||||||
|
|
||||||
|
static false_t Check(...);
|
||||||
|
template <typename Test, typename = typename Test::Part>
|
||||||
|
static true_t Check(const Test&);
|
||||||
|
|
||||||
|
static constexpr bool Is = (sizeof(Check(std::declval<Record>()))
|
||||||
|
== sizeof(true_t));
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename ...Records>
|
||||||
|
struct BinlogReaderRecursive {
|
||||||
|
static void CheckSettings(const Settings &settings) {
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_type ReadRecordSize(
|
||||||
|
RecordType type,
|
||||||
|
bytes::const_span data,
|
||||||
|
size_type partsLimit) {
|
||||||
|
return kRecordSizeInvalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ...Handlers>
|
||||||
|
static bool HandleRecord(
|
||||||
|
RecordType type,
|
||||||
|
bytes::const_span data,
|
||||||
|
Handlers &&...handlers) {
|
||||||
|
Unexpected("Bad type in BinlogReaderRecursive::HandleRecord.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Record, typename ...Other>
|
||||||
|
struct BinlogReaderRecursive<Record, Other...> {
|
||||||
|
static void CheckSettings(const Settings &settings);
|
||||||
|
|
||||||
|
static size_type ReadRecordSize(
|
||||||
|
RecordType type,
|
||||||
|
bytes::const_span data,
|
||||||
|
size_type partsLimit);
|
||||||
|
|
||||||
|
template <typename ...Handlers>
|
||||||
|
static bool HandleRecord(
|
||||||
|
RecordType type,
|
||||||
|
bytes::const_span data,
|
||||||
|
Handlers &&...handlers);
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Record, typename ...Other>
|
||||||
|
inline void BinlogReaderRecursive<Record, Other...>::CheckSettings(
|
||||||
|
const Settings &settings) {
|
||||||
|
static_assert(GoodForEncryption<Record>);
|
||||||
|
if constexpr (MultiRecord<Record>::Is) {
|
||||||
|
using Head = Record;
|
||||||
|
using Part = typename Record::Part;
|
||||||
|
static_assert(GoodForEncryption<Part>);
|
||||||
|
Assert(settings.readBlockSize
|
||||||
|
>= (sizeof(Head)
|
||||||
|
+ settings.maxBundledRecords * sizeof(Part)));
|
||||||
|
} else {
|
||||||
|
Assert(settings.readBlockSize >= sizeof(Record));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Record, typename ...Other>
|
||||||
|
inline size_type BinlogReaderRecursive<Record, Other...>::ReadRecordSize(
|
||||||
|
RecordType type,
|
||||||
|
bytes::const_span data,
|
||||||
|
size_type partsLimit) {
|
||||||
|
if (type != Record::kType) {
|
||||||
|
return BinlogReaderRecursive<Other...>::ReadRecordSize(
|
||||||
|
type,
|
||||||
|
data,
|
||||||
|
partsLimit);
|
||||||
|
}
|
||||||
|
if constexpr (MultiRecord<Record>::Is) {
|
||||||
|
using Head = Record;
|
||||||
|
using Part = typename Record::Part;
|
||||||
|
|
||||||
|
if (data.size() < sizeof(Head)) {
|
||||||
|
return kRecordSizeUnknown;
|
||||||
|
}
|
||||||
|
const auto head = reinterpret_cast<const Head*>(data.data());
|
||||||
|
const auto count = head->validateCount();
|
||||||
|
return (count >= 0 && count < partsLimit)
|
||||||
|
? (sizeof(Head) + count * sizeof(Part))
|
||||||
|
: kRecordSizeInvalid;
|
||||||
|
} else {
|
||||||
|
return sizeof(Record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Record, typename ...Other>
|
||||||
|
template <typename ...Handlers>
|
||||||
|
inline bool BinlogReaderRecursive<Record, Other...>::HandleRecord(
|
||||||
|
RecordType type,
|
||||||
|
bytes::const_span data,
|
||||||
|
Handlers &&...handlers) {
|
||||||
|
if (type != Record::kType) {
|
||||||
|
return BinlogReaderRecursive<Other...>::HandleRecord(
|
||||||
|
type,
|
||||||
|
data,
|
||||||
|
std::forward<Handlers>(handlers)...);
|
||||||
|
}
|
||||||
|
if constexpr (MultiRecord<Record>::Is) {
|
||||||
|
using Head = Record;
|
||||||
|
using Part = typename Record::Part;
|
||||||
|
|
||||||
|
Assert(data.size() >= sizeof(Head));
|
||||||
|
const auto bytes = data.data();
|
||||||
|
const auto head = reinterpret_cast<const Head*>(bytes);
|
||||||
|
const auto count = head->validateCount();
|
||||||
|
Assert(data.size() == sizeof(Head) + count * sizeof(Part));
|
||||||
|
const auto parts = gsl::make_span(
|
||||||
|
reinterpret_cast<const Part*>(bytes + sizeof(Head)),
|
||||||
|
count);
|
||||||
|
auto from = std::begin(parts);
|
||||||
|
const auto till = std::end(parts);
|
||||||
|
const auto element = [&] {
|
||||||
|
return (from == till) ? nullptr : &*from++;
|
||||||
|
};
|
||||||
|
return base::match_method2(
|
||||||
|
*head,
|
||||||
|
element,
|
||||||
|
std::forward<Handlers>(handlers)...);
|
||||||
|
} else {
|
||||||
|
Assert(data.size() == sizeof(Record));
|
||||||
|
return base::match_method(
|
||||||
|
*reinterpret_cast<const Record*>(data.data()),
|
||||||
|
std::forward<Handlers>(handlers)...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ...Records>
|
||||||
|
BinlogReader<Records...>::BinlogReader(BinlogWrapper &wrapper)
|
||||||
|
: _wrapper(wrapper) {
|
||||||
|
BinlogReaderRecursive<Records...>::CheckSettings(_wrapper._settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ...Records>
|
||||||
|
template <typename ...Handlers>
|
||||||
|
bool BinlogReader<Records...>::readTillEnd(Handlers &&...handlers) {
|
||||||
|
if (!_wrapper.readPart()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const auto readRecord = [&] {
|
||||||
|
return _wrapper.readRecord(&BinlogReader::ReadRecordSize);
|
||||||
|
};
|
||||||
|
for (auto bytes = readRecord(); !bytes.empty(); bytes = readRecord()) {
|
||||||
|
if (!handleRecord(bytes, std::forward<Handlers>(handlers)...)) {
|
||||||
|
_wrapper.finish(bytes.size());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ...Records>
|
||||||
|
size_type BinlogReader<Records...>::ReadRecordSize(
|
||||||
|
const BinlogWrapper &that,
|
||||||
|
bytes::const_span data) {
|
||||||
|
if (data.empty()) {
|
||||||
|
return kRecordSizeUnknown;
|
||||||
|
}
|
||||||
|
return BinlogReaderRecursive<Records...>::ReadRecordSize(
|
||||||
|
static_cast<RecordType>(data[0]),
|
||||||
|
data,
|
||||||
|
that._settings.maxBundledRecords);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ...Records>
|
||||||
|
template <typename ...Handlers>
|
||||||
|
bool BinlogReader<Records...>::handleRecord(
|
||||||
|
bytes::const_span data,
|
||||||
|
Handlers &&...handlers) const {
|
||||||
|
Expects(!data.empty());
|
||||||
|
|
||||||
|
return BinlogReaderRecursive<Records...>::HandleRecord(
|
||||||
|
static_cast<RecordType>(data[0]),
|
||||||
|
data,
|
||||||
|
std::forward<Handlers>(handlers)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace details
|
||||||
|
} // namespace Cache
|
||||||
|
} // namespace Storage
|
@ -25,22 +25,7 @@ using Error = details::Error;
|
|||||||
|
|
||||||
class Database {
|
class Database {
|
||||||
public:
|
public:
|
||||||
struct Settings {
|
using Settings = details::Settings;
|
||||||
size_type maxBundledRecords = 16 * 1024;
|
|
||||||
size_type readBlockSize = 8 * 1024 * 1024;
|
|
||||||
size_type maxDataSize = 10 * 1024 * 1024;
|
|
||||||
crl::time_type writeBundleDelay = 15 * 60 * crl::time_type(1000);
|
|
||||||
|
|
||||||
int64 compactAfterExcess = 8 * 1024 * 1024;
|
|
||||||
int64 compactAfterFullSize = 0;
|
|
||||||
|
|
||||||
bool trackEstimatedTime = true;
|
|
||||||
int64 totalSizeLimit = 1024 * 1024 * 1024;
|
|
||||||
size_type totalTimeLimit = 30 * 86400; // One month in seconds.
|
|
||||||
size_type maxTimeAdvancement = 365 * 86400; // One year in seconds.
|
|
||||||
crl::time_type pruneTimeout = 5 * crl::time_type(1000);
|
|
||||||
crl::time_type maxPruneCheckTimeout = 3600 * crl::time_type(1000);
|
|
||||||
};
|
|
||||||
Database(const QString &path, const Settings &settings);
|
Database(const QString &path, const Settings &settings);
|
||||||
|
|
||||||
void open(EncryptionKey key, FnMut<void(Error)> done);
|
void open(EncryptionKey key, FnMut<void(Error)> done);
|
||||||
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
|
|
||||||
#include "storage/cache/storage_cache_cleaner.h"
|
#include "storage/cache/storage_cache_cleaner.h"
|
||||||
#include "storage/cache/storage_cache_compactor.h"
|
#include "storage/cache/storage_cache_compactor.h"
|
||||||
|
#include "storage/cache/storage_cache_binlog_reader.h"
|
||||||
#include "storage/storage_encryption.h"
|
#include "storage/storage_encryption.h"
|
||||||
#include "storage/storage_encrypted_file.h"
|
#include "storage/storage_encrypted_file.h"
|
||||||
#include "base/flat_map.h"
|
#include "base/flat_map.h"
|
||||||
@ -193,30 +194,51 @@ bool DatabaseObject::writeHeader() {
|
|||||||
return _binlog.write(bytes::object_as_span(&header));
|
return _binlog.write(bytes::object_as_span(&header));
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseObject::readBinlog() {
|
template <typename Reader, typename ...Handlers>
|
||||||
auto data = bytes::vector(_settings.readBlockSize);
|
void DatabaseObject::readBinlogHelper(
|
||||||
const auto full = bytes::make_span(data);
|
Reader &reader,
|
||||||
auto notParsedBytes = index_type(0);
|
Handlers &&...handlers) {
|
||||||
while (true) {
|
while (true) {
|
||||||
Assert(notParsedBytes < full.size());
|
const auto done = reader.readTillEnd(
|
||||||
const auto readBytes = _binlog.read(full.subspan(notParsedBytes));
|
std::forward<Handlers>(handlers)...);
|
||||||
if (!readBytes) {
|
if (done) {
|
||||||
break;
|
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);
|
|
||||||
|
|
||||||
|
void DatabaseObject::readBinlog() {
|
||||||
|
BinlogWrapper wrapper(_binlog, _settings);
|
||||||
|
if (_settings.trackEstimatedTime) {
|
||||||
|
BinlogReader<
|
||||||
|
StoreWithTime,
|
||||||
|
MultiStoreWithTime,
|
||||||
|
MultiRemove,
|
||||||
|
MultiAccess> reader(wrapper);
|
||||||
|
readBinlogHelper(reader, [&](const StoreWithTime &record) {
|
||||||
|
return processRecordStore(
|
||||||
|
&record,
|
||||||
|
std::is_class<StoreWithTime>{});
|
||||||
|
}, [&](const MultiStoreWithTime &header, const auto &element) {
|
||||||
|
return processRecordMultiStore(header, element);
|
||||||
|
}, [&](const MultiRemove &header, const auto &element) {
|
||||||
|
return processRecordMultiRemove(header, element);
|
||||||
|
}, [&](const MultiAccess &header, const auto &element) {
|
||||||
|
return processRecordMultiAccess(header, element);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
BinlogReader<
|
||||||
|
Store,
|
||||||
|
MultiStore,
|
||||||
|
MultiRemove> reader(wrapper);
|
||||||
|
readBinlogHelper(reader, [&](const Store &record) {
|
||||||
|
return processRecordStore(&record, std::is_class<Store>{});
|
||||||
|
}, [&](const MultiStore &header, const auto &element) {
|
||||||
|
return processRecordMultiStore(header, element);
|
||||||
|
}, [&](const MultiRemove &header, const auto &element) {
|
||||||
|
return processRecordMultiRemove(header, element);
|
||||||
|
});
|
||||||
|
}
|
||||||
adjustRelativeTime();
|
adjustRelativeTime();
|
||||||
optimize();
|
optimize();
|
||||||
}
|
}
|
||||||
@ -367,107 +389,9 @@ void DatabaseObject::adjustRelativeTime() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
size_type DatabaseObject::readBinlogRecords(bytes::const_span data) {
|
template <typename Record, typename Postprocess>
|
||||||
auto result = 0;
|
|
||||||
while (true) {
|
|
||||||
const auto size = readBinlogRecordSize(data);
|
|
||||||
if (size == kRecordSizeUnknown || size > data.size()) {
|
|
||||||
return result;
|
|
||||||
} else if (size == kRecordSizeInvalid
|
|
||||||
|| !readBinlogRecord(data.subspan(0, size))) {
|
|
||||||
return (result > 0) ? result : kRecordSizeInvalid;
|
|
||||||
} else {
|
|
||||||
result += size;
|
|
||||||
data = data.subspan(size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
size_type DatabaseObject::readBinlogRecordSize(bytes::const_span data) const {
|
|
||||||
if (data.empty()) {
|
|
||||||
return kRecordSizeUnknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (static_cast<RecordType>(data[0])) {
|
|
||||||
case Store::kType:
|
|
||||||
return storeRecordSize();
|
|
||||||
|
|
||||||
case MultiStoreHeader::kType:
|
|
||||||
if (data.size() >= sizeof(MultiStoreHeader)) {
|
|
||||||
const auto header = reinterpret_cast<const MultiStoreHeader*>(
|
|
||||||
data.data());
|
|
||||||
const auto count = ReadFrom(header->count);
|
|
||||||
const auto size = _settings.trackEstimatedTime
|
|
||||||
? sizeof(MultiStoreWithTimePart)
|
|
||||||
: sizeof(MultiStorePart);
|
|
||||||
return (count > 0 && count < _settings.maxBundledRecords)
|
|
||||||
? (sizeof(MultiStoreHeader) + count * size)
|
|
||||||
: 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 < _settings.maxBundledRecords)
|
|
||||||
? (sizeof(MultiRemoveHeader)
|
|
||||||
+ count * sizeof(MultiRemovePart))
|
|
||||||
: kRecordSizeInvalid;
|
|
||||||
}
|
|
||||||
return kRecordSizeUnknown;
|
|
||||||
|
|
||||||
case MultiAccessHeader::kType:
|
|
||||||
if (!_settings.trackEstimatedTime) {
|
|
||||||
return kRecordSizeInvalid;
|
|
||||||
} else if (data.size() >= sizeof(MultiAccessHeader)) {
|
|
||||||
const auto header = reinterpret_cast<const MultiAccessHeader*>(
|
|
||||||
data.data());
|
|
||||||
const auto count = ReadFrom(header->count);
|
|
||||||
return (count >= 0 && count < _settings.maxBundledRecords)
|
|
||||||
? (sizeof(MultiAccessHeader)
|
|
||||||
+ count * sizeof(MultiAccessPart))
|
|
||||||
: kRecordSizeInvalid;
|
|
||||||
}
|
|
||||||
return kRecordSizeUnknown;
|
|
||||||
|
|
||||||
}
|
|
||||||
return kRecordSizeInvalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DatabaseObject::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);
|
|
||||||
|
|
||||||
case MultiAccessHeader::kType:
|
|
||||||
return readRecordMultiAccess(data);
|
|
||||||
|
|
||||||
}
|
|
||||||
Unexpected("Bad type in DatabaseObject::readBinlogRecord.");
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename RecordStore>
|
|
||||||
bool DatabaseObject::readRecordStoreGeneric(bytes::const_span data) {
|
|
||||||
Expects(data.size() == sizeof(RecordStore));
|
|
||||||
|
|
||||||
return processRecordStore(
|
|
||||||
reinterpret_cast<const RecordStore*>(data.data()),
|
|
||||||
std::is_class<RecordStore>{});
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename RecordStore, typename Postprocess>
|
|
||||||
bool DatabaseObject::processRecordStoreGeneric(
|
bool DatabaseObject::processRecordStoreGeneric(
|
||||||
const RecordStore *record,
|
const Record *record,
|
||||||
Postprocess &&postprocess) {
|
Postprocess &&postprocess) {
|
||||||
const auto size = ReadFrom(record->size);
|
const auto size = ReadFrom(record->size);
|
||||||
if (size <= 0 || size > _settings.maxDataSize) {
|
if (size <= 0 || size > _settings.maxDataSize) {
|
||||||
@ -506,52 +430,62 @@ bool DatabaseObject::processRecordStore(
|
|||||||
return processRecordStoreGeneric(record, postprocess);
|
return processRecordStoreGeneric(record, postprocess);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DatabaseObject::readRecordStore(bytes::const_span data) {
|
template <typename Record, typename GetElement>
|
||||||
if (!_settings.trackEstimatedTime) {
|
bool DatabaseObject::processRecordMultiStore(
|
||||||
return readRecordStoreGeneric<Store>(data);
|
const Record &header,
|
||||||
}
|
const GetElement &element) {
|
||||||
return readRecordStoreGeneric<StoreWithTime>(data);
|
while (const auto entry = element()) {
|
||||||
}
|
if (!processRecordStore(
|
||||||
|
entry,
|
||||||
template <typename StorePart>
|
std::is_class<typename Record::Part>{})) {
|
||||||
bool DatabaseObject::readRecordMultiStoreGeneric(bytes::const_span data) {
|
|
||||||
Expects(data.size() >= sizeof(MultiStoreHeader));
|
|
||||||
|
|
||||||
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(StorePart));
|
|
||||||
const auto parts = gsl::make_span(
|
|
||||||
reinterpret_cast<const StorePart*>(
|
|
||||||
bytes + sizeof(MultiStoreHeader)),
|
|
||||||
count);
|
|
||||||
for (const auto &part : parts) {
|
|
||||||
if (!processRecordStore(&part, std::is_class<StorePart>{})) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DatabaseObject::readRecordMultiStore(bytes::const_span data) {
|
template <typename GetElement>
|
||||||
if (!_settings.trackEstimatedTime) {
|
bool DatabaseObject::processRecordMultiRemove(
|
||||||
return readRecordMultiStoreGeneric<MultiStorePart>(data);
|
const MultiRemove &header,
|
||||||
|
const GetElement &element) {
|
||||||
|
_binlogExcessLength += sizeof(header);
|
||||||
|
while (const auto entry = element()) {
|
||||||
|
_binlogExcessLength += sizeof(*entry);
|
||||||
|
if (const auto i = _map.find(*entry); i != end(_map)) {
|
||||||
|
eraseMapEntry(i);
|
||||||
}
|
}
|
||||||
return readRecordMultiStoreGeneric<MultiStoreWithTimePart>(data);
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_type DatabaseObject::storeRecordSize() const {
|
template <typename GetElement>
|
||||||
return _settings.trackEstimatedTime
|
bool DatabaseObject::processRecordMultiAccess(
|
||||||
? sizeof(StoreWithTime)
|
const MultiAccess &header,
|
||||||
: sizeof(Store);
|
const GetElement &element) {
|
||||||
|
Expects(_settings.trackEstimatedTime);
|
||||||
|
|
||||||
|
if (header.time.relativeAdvancement > _settings.maxTimeAdvancement) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
applyTimePoint(header.time);
|
||||||
|
|
||||||
|
_binlogExcessLength += sizeof(header);
|
||||||
|
while (const auto entry = element()) {
|
||||||
|
_binlogExcessLength += sizeof(*entry);
|
||||||
|
if (const auto i = _map.find(*entry); i != end(_map)) {
|
||||||
|
i->second.useTime = _relativeTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseObject::setMapEntry(const Key &key, Entry &&entry) {
|
void DatabaseObject::setMapEntry(const Key &key, Entry &&entry) {
|
||||||
auto &already = _map[key];
|
auto &already = _map[key];
|
||||||
_totalSize += entry.size - already.size;
|
_totalSize += entry.size - already.size;
|
||||||
if (already.size != 0) {
|
if (already.size != 0) {
|
||||||
_binlogExcessLength += storeRecordSize();
|
_binlogExcessLength += _settings.trackEstimatedTime
|
||||||
|
? sizeof(StoreWithTime)
|
||||||
|
: sizeof(Store);
|
||||||
}
|
}
|
||||||
if (entry.useTime != 0
|
if (entry.useTime != 0
|
||||||
&& (entry.useTime < _minimalEntryTime || !_minimalEntryTime)) {
|
&& (entry.useTime < _minimalEntryTime || !_minimalEntryTime)) {
|
||||||
@ -581,27 +515,6 @@ void DatabaseObject::eraseMapEntry(const Map::const_iterator &i) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DatabaseObject::readRecordMultiRemove(bytes::const_span data) {
|
|
||||||
Expects(data.size() >= sizeof(MultiRemoveHeader));
|
|
||||||
|
|
||||||
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) {
|
|
||||||
if (const auto i = _map.find(part.key); i != end(_map)) {
|
|
||||||
eraseMapEntry(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_binlogExcessLength += data.size();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
EstimatedTimePoint DatabaseObject::countTimePoint() const {
|
EstimatedTimePoint DatabaseObject::countTimePoint() const {
|
||||||
const auto now = std::max(GetUnixtime(), 1);
|
const auto now = std::max(GetUnixtime(), 1);
|
||||||
const auto delta = std::max(int64(now) - int64(_latestSystemTime), 0LL);
|
const auto delta = std::max(int64(now) - int64(_latestSystemTime), 0LL);
|
||||||
@ -618,32 +531,6 @@ void DatabaseObject::applyTimePoint(EstimatedTimePoint time) {
|
|||||||
_latestSystemTime = time.system;
|
_latestSystemTime = time.system;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DatabaseObject::readRecordMultiAccess(bytes::const_span data) {
|
|
||||||
Expects(data.size() >= sizeof(MultiAccessHeader));
|
|
||||||
Expects(_settings.trackEstimatedTime);
|
|
||||||
|
|
||||||
const auto bytes = data.data();
|
|
||||||
const auto record = reinterpret_cast<const MultiAccessHeader*>(bytes);
|
|
||||||
if (record->time.relativeAdvancement > _settings.maxTimeAdvancement) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
applyTimePoint(record->time);
|
|
||||||
const auto count = ReadFrom(record->count);
|
|
||||||
Assert(data.size() == sizeof(MultiAccessHeader)
|
|
||||||
+ count * sizeof(MultiAccessPart));
|
|
||||||
const auto parts = gsl::make_span(
|
|
||||||
reinterpret_cast<const MultiAccessPart*>(
|
|
||||||
bytes + sizeof(MultiAccessHeader)),
|
|
||||||
count);
|
|
||||||
for (const auto &part : parts) {
|
|
||||||
if (const auto i = _map.find(part.key); i != end(_map)) {
|
|
||||||
i->second.useTime = _relativeTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_binlogExcessLength += data.size();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DatabaseObject::close(FnMut<void()> done) {
|
void DatabaseObject::close(FnMut<void()> done) {
|
||||||
writeBundles();
|
writeBundles();
|
||||||
_cleaner = CleanerWrap();
|
_cleaner = CleanerWrap();
|
||||||
@ -740,7 +627,9 @@ base::optional<QString> DatabaseObject::writeKeyPlaceGeneric(
|
|||||||
}
|
}
|
||||||
_binlog.flush();
|
_binlog.flush();
|
||||||
|
|
||||||
const auto applied = readRecordStore(bytes::object_as_span(&record));
|
const auto applied = processRecordStore(
|
||||||
|
&record,
|
||||||
|
std::is_class<StoreRecord>{});
|
||||||
Assert(applied);
|
Assert(applied);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -850,11 +739,11 @@ void DatabaseObject::writeMultiRemove() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto size = _removing.size();
|
const auto size = _removing.size();
|
||||||
auto header = MultiRemoveHeader(size);
|
auto header = MultiRemove(size);
|
||||||
auto list = std::vector<MultiRemovePart>();
|
auto list = std::vector<MultiRemove::Part>();
|
||||||
list.reserve(size);
|
list.reserve(size);
|
||||||
for (const auto &key : base::take(_removing)) {
|
for (const auto &key : base::take(_removing)) {
|
||||||
list.push_back({ key });
|
list.push_back(key);
|
||||||
}
|
}
|
||||||
if (_binlog.write(bytes::object_as_span(&header))) {
|
if (_binlog.write(bytes::object_as_span(&header))) {
|
||||||
_binlog.write(bytes::make_span(list));
|
_binlog.write(bytes::make_span(list));
|
||||||
@ -885,17 +774,17 @@ void DatabaseObject::writeMultiAccessBlock() {
|
|||||||
|
|
||||||
const auto time = countTimePoint();
|
const auto time = countTimePoint();
|
||||||
const auto size = _accessed.size();
|
const auto size = _accessed.size();
|
||||||
auto header = MultiAccessHeader(time, size);
|
auto header = MultiAccess(time, size);
|
||||||
auto list = std::vector<MultiAccessPart>();
|
auto list = std::vector<MultiAccess::Part>();
|
||||||
if (size > 0) {
|
if (size > 0) {
|
||||||
list.reserve(size);
|
list.reserve(size);
|
||||||
for (const auto &key : base::take(_accessed)) {
|
for (const auto &key : base::take(_accessed)) {
|
||||||
list.push_back({ key });
|
list.push_back(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
applyTimePoint(time);
|
applyTimePoint(time);
|
||||||
for (const auto &entry : list) {
|
for (const auto &entry : list) {
|
||||||
if (const auto i = _map.find(entry.key); i != end(_map)) {
|
if (const auto i = _map.find(entry); i != end(_map)) {
|
||||||
i->second.useTime = _relativeTime;
|
i->second.useTime = _relativeTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,26 +79,28 @@ private:
|
|||||||
bool writeHeader();
|
bool writeHeader();
|
||||||
|
|
||||||
void readBinlog();
|
void readBinlog();
|
||||||
size_type readBinlogRecords(bytes::const_span data);
|
template <typename Reader, typename ...Handlers>
|
||||||
size_type readBinlogRecordSize(bytes::const_span data) const;
|
void readBinlogHelper(Reader &reader, Handlers &&...handlers);
|
||||||
bool readBinlogRecord(bytes::const_span data);
|
template <typename Record, typename Postprocess>
|
||||||
template <typename RecordStore>
|
|
||||||
bool readRecordStoreGeneric(bytes::const_span data);
|
|
||||||
bool readRecordStore(bytes::const_span data);
|
|
||||||
template <typename StorePart>
|
|
||||||
bool readRecordMultiStoreGeneric(bytes::const_span data);
|
|
||||||
bool readRecordMultiStore(bytes::const_span data);
|
|
||||||
bool readRecordMultiRemove(bytes::const_span data);
|
|
||||||
bool readRecordMultiAccess(bytes::const_span data);
|
|
||||||
template <typename RecordStore, typename Postprocess>
|
|
||||||
bool processRecordStoreGeneric(
|
bool processRecordStoreGeneric(
|
||||||
const RecordStore *record,
|
const Record *record,
|
||||||
Postprocess &&postprocess);
|
Postprocess &&postprocess);
|
||||||
bool processRecordStore(const Store *record, std::is_class<Store>);
|
bool processRecordStore(const Store *record, std::is_class<Store>);
|
||||||
bool processRecordStore(
|
bool processRecordStore(
|
||||||
const StoreWithTime *record,
|
const StoreWithTime *record,
|
||||||
std::is_class<StoreWithTime>);
|
std::is_class<StoreWithTime>);
|
||||||
size_type storeRecordSize() const;
|
template <typename Record, typename GetElement>
|
||||||
|
bool processRecordMultiStore(
|
||||||
|
const Record &header,
|
||||||
|
const GetElement &element);
|
||||||
|
template <typename GetElement>
|
||||||
|
bool processRecordMultiRemove(
|
||||||
|
const MultiRemove &header,
|
||||||
|
const GetElement &element);
|
||||||
|
template <typename GetElement>
|
||||||
|
bool processRecordMultiAccess(
|
||||||
|
const MultiAccess &header,
|
||||||
|
const GetElement &element);
|
||||||
|
|
||||||
void optimize();
|
void optimize();
|
||||||
void checkCompactor();
|
void checkCompactor();
|
||||||
|
@ -57,19 +57,27 @@ BasicHeader::BasicHeader()
|
|||||||
, flags(0) {
|
, flags(0) {
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiStoreHeader::MultiStoreHeader(size_type count)
|
MultiStore::MultiStore(size_type count)
|
||||||
: type(kType)
|
: type(kType)
|
||||||
, count(ReadTo<RecordsCount>(count)) {
|
, count(ReadTo<RecordsCount>(count)) {
|
||||||
Expects(count >= 0 && count < kBundledRecordsLimit);
|
Expects(count >= 0 && count < kBundledRecordsLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiRemoveHeader::MultiRemoveHeader(size_type count)
|
size_type MultiStore::validateCount() const {
|
||||||
|
return ValidateStrictCount(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiRemove::MultiRemove(size_type count)
|
||||||
: type(kType)
|
: type(kType)
|
||||||
, count(ReadTo<RecordsCount>(count)) {
|
, count(ReadTo<RecordsCount>(count)) {
|
||||||
Expects(count >= 0 && count < kBundledRecordsLimit);
|
Expects(count >= 0 && count < kBundledRecordsLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiAccessHeader::MultiAccessHeader(
|
size_type MultiRemove::validateCount() const {
|
||||||
|
return ValidateStrictCount(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiAccess::MultiAccess(
|
||||||
EstimatedTimePoint time,
|
EstimatedTimePoint time,
|
||||||
size_type count)
|
size_type count)
|
||||||
: type(kType)
|
: type(kType)
|
||||||
@ -78,6 +86,10 @@ MultiAccessHeader::MultiAccessHeader(
|
|||||||
Expects(count >= 0 && count < kBundledRecordsLimit);
|
Expects(count >= 0 && count < kBundledRecordsLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_type MultiAccess::validateCount() const {
|
||||||
|
return ReadFrom(count);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace details
|
} // namespace details
|
||||||
} // namespace Cache
|
} // namespace Cache
|
||||||
} // namespace Storage
|
} // namespace Storage
|
||||||
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||||||
|
|
||||||
#include "base/basic_types.h"
|
#include "base/basic_types.h"
|
||||||
#include "base/optional.h"
|
#include "base/optional.h"
|
||||||
|
#include <crl/crl_time.h>
|
||||||
|
|
||||||
namespace Storage {
|
namespace Storage {
|
||||||
namespace Cache {
|
namespace Cache {
|
||||||
@ -31,6 +32,23 @@ inline bool operator<(const Key &a, const Key &b) {
|
|||||||
return std::tie(a.high, a.low) < std::tie(b.high, b.low);
|
return std::tie(a.high, a.low) < std::tie(b.high, b.low);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Settings {
|
||||||
|
size_type maxBundledRecords = 16 * 1024;
|
||||||
|
size_type readBlockSize = 8 * 1024 * 1024;
|
||||||
|
size_type maxDataSize = 10 * 1024 * 1024;
|
||||||
|
crl::time_type writeBundleDelay = 15 * 60 * crl::time_type(1000);
|
||||||
|
|
||||||
|
int64 compactAfterExcess = 8 * 1024 * 1024;
|
||||||
|
int64 compactAfterFullSize = 0;
|
||||||
|
|
||||||
|
bool trackEstimatedTime = true;
|
||||||
|
int64 totalSizeLimit = 1024 * 1024 * 1024;
|
||||||
|
size_type totalTimeLimit = 30 * 86400; // One month in seconds.
|
||||||
|
size_type maxTimeAdvancement = 365 * 86400; // One year in seconds.
|
||||||
|
crl::time_type pruneTimeout = 5 * crl::time_type(1000);
|
||||||
|
crl::time_type maxPruneCheckTimeout = 3600 * crl::time_type(1000);
|
||||||
|
};
|
||||||
|
|
||||||
using Version = int32;
|
using Version = int32;
|
||||||
|
|
||||||
QString ComputeBasePath(const QString &original);
|
QString ComputeBasePath(const QString &original);
|
||||||
@ -61,7 +79,7 @@ using EntrySize = std::array<uint8, 3>;
|
|||||||
using RecordsCount = std::array<uint8, 3>;
|
using RecordsCount = std::array<uint8, 3>;
|
||||||
|
|
||||||
template <typename Packed>
|
template <typename Packed>
|
||||||
Packed ReadTo(size_type count) {
|
inline Packed ReadTo(size_type count) {
|
||||||
Expects(count >= 0 && count < (1 << (Packed().size() * 8)));
|
Expects(count >= 0 && count < (1 << (Packed().size() * 8)));
|
||||||
|
|
||||||
auto result = Packed();
|
auto result = Packed();
|
||||||
@ -73,7 +91,7 @@ Packed ReadTo(size_type count) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename Packed>
|
template <typename Packed>
|
||||||
size_type ReadFrom(Packed count) {
|
inline size_type ReadFrom(const Packed &count) {
|
||||||
auto result = size_type();
|
auto result = size_type();
|
||||||
for (auto &element : (count | ranges::view::reverse)) {
|
for (auto &element : (count | ranges::view::reverse)) {
|
||||||
result <<= 8;
|
result <<= 8;
|
||||||
@ -82,6 +100,12 @@ size_type ReadFrom(Packed count) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Packed>
|
||||||
|
inline size_type ValidateStrictCount(const Packed &count) {
|
||||||
|
const auto result = ReadFrom(count);
|
||||||
|
return (result != 0) ? result : -1;
|
||||||
|
}
|
||||||
|
|
||||||
constexpr auto kRecordSizeUnknown = size_type(-1);
|
constexpr auto kRecordSizeUnknown = size_type(-1);
|
||||||
constexpr auto kRecordSizeInvalid = size_type(-2);
|
constexpr auto kRecordSizeInvalid = size_type(-2);
|
||||||
constexpr auto kBundledRecordsLimit = (1 << (RecordsCount().size() * 8));
|
constexpr auto kBundledRecordsLimit = (1 << (RecordsCount().size() * 8));
|
||||||
@ -105,7 +129,6 @@ struct BasicHeader {
|
|||||||
uint32 reserved1 = 0;
|
uint32 reserved1 = 0;
|
||||||
uint32 reserved2 = 0;
|
uint32 reserved2 = 0;
|
||||||
};
|
};
|
||||||
static_assert(GoodForEncryption<BasicHeader>);
|
|
||||||
|
|
||||||
struct EstimatedTimePoint {
|
struct EstimatedTimePoint {
|
||||||
uint32 system = 0;
|
uint32 system = 0;
|
||||||
@ -129,44 +152,44 @@ struct StoreWithTime : Store {
|
|||||||
uint32 reserved1 = 0;
|
uint32 reserved1 = 0;
|
||||||
uint32 reserved2 = 0;
|
uint32 reserved2 = 0;
|
||||||
};
|
};
|
||||||
static_assert(GoodForEncryption<StoreWithTime>);
|
|
||||||
|
|
||||||
struct MultiStoreHeader {
|
struct MultiStore {
|
||||||
static constexpr auto kType = RecordType(0x02);
|
static constexpr auto kType = RecordType(0x02);
|
||||||
|
|
||||||
explicit MultiStoreHeader(size_type count = 0);
|
explicit MultiStore(size_type count = 0);
|
||||||
|
|
||||||
RecordType type = kType;
|
RecordType type = kType;
|
||||||
RecordsCount count = { { 0 } };
|
RecordsCount count = { { 0 } };
|
||||||
uint32 reserved1 = 0;
|
uint32 reserved1 = 0;
|
||||||
uint32 reserved2 = 0;
|
uint32 reserved2 = 0;
|
||||||
uint32 reserved3 = 0;
|
uint32 reserved3 = 0;
|
||||||
};
|
|
||||||
using MultiStorePart = Store;
|
|
||||||
using MultiStoreWithTimePart = StoreWithTime;
|
|
||||||
static_assert(GoodForEncryption<MultiStoreHeader>);
|
|
||||||
|
|
||||||
struct MultiRemoveHeader {
|
using Part = Store;
|
||||||
|
size_type validateCount() const;
|
||||||
|
};
|
||||||
|
struct MultiStoreWithTime : MultiStore {
|
||||||
|
using Part = StoreWithTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MultiRemove {
|
||||||
static constexpr auto kType = RecordType(0x03);
|
static constexpr auto kType = RecordType(0x03);
|
||||||
|
|
||||||
explicit MultiRemoveHeader(size_type count = 0);
|
explicit MultiRemove(size_type count = 0);
|
||||||
|
|
||||||
RecordType type = kType;
|
RecordType type = kType;
|
||||||
RecordsCount count = { { 0 } };
|
RecordsCount count = { { 0 } };
|
||||||
uint32 reserved1 = 0;
|
uint32 reserved1 = 0;
|
||||||
uint32 reserved2 = 0;
|
uint32 reserved2 = 0;
|
||||||
uint32 reserved3 = 0;
|
uint32 reserved3 = 0;
|
||||||
};
|
|
||||||
struct MultiRemovePart {
|
|
||||||
Key key;
|
|
||||||
};
|
|
||||||
static_assert(GoodForEncryption<MultiRemoveHeader>);
|
|
||||||
static_assert(GoodForEncryption<MultiRemovePart>);
|
|
||||||
|
|
||||||
struct MultiAccessHeader {
|
using Part = Key;
|
||||||
|
size_type validateCount() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MultiAccess {
|
||||||
static constexpr auto kType = RecordType(0x04);
|
static constexpr auto kType = RecordType(0x04);
|
||||||
|
|
||||||
explicit MultiAccessHeader(
|
explicit MultiAccess(
|
||||||
EstimatedTimePoint time,
|
EstimatedTimePoint time,
|
||||||
size_type count = 0);
|
size_type count = 0);
|
||||||
|
|
||||||
@ -174,12 +197,10 @@ struct MultiAccessHeader {
|
|||||||
RecordsCount count = { { 0 } };
|
RecordsCount count = { { 0 } };
|
||||||
EstimatedTimePoint time;
|
EstimatedTimePoint time;
|
||||||
uint32 reserved = 0;
|
uint32 reserved = 0;
|
||||||
|
|
||||||
|
using Part = Key;
|
||||||
|
size_type validateCount() const;
|
||||||
};
|
};
|
||||||
struct MultiAccessPart {
|
|
||||||
Key key;
|
|
||||||
};
|
|
||||||
static_assert(GoodForEncryption<MultiAccessHeader>);
|
|
||||||
static_assert(GoodForEncryption<MultiAccessPart>);
|
|
||||||
|
|
||||||
} // namespace details
|
} // namespace details
|
||||||
} // namespace Cache
|
} // namespace Cache
|
||||||
|
@ -208,7 +208,9 @@ bool File::write(bytes::span bytes) {
|
|||||||
|
|
||||||
encrypt(bytes);
|
encrypt(bytes);
|
||||||
const auto count = writePlain(bytes);
|
const auto count = writePlain(bytes);
|
||||||
if (count != bytes.size()) {
|
if (count == bytes.size()) {
|
||||||
|
_dataSize = std::max(_dataSize, offset());
|
||||||
|
} else {
|
||||||
decryptBack(bytes);
|
decryptBack(bytes);
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
_data.seek(_data.pos() - count);
|
_data.seek(_data.pos() - count);
|
||||||
|
@ -105,7 +105,7 @@ TEST_CASE("simple encrypted file", "[storage_encrypted_file]") {
|
|||||||
Key);
|
Key);
|
||||||
REQUIRE(result == Storage::File::Result::Success);
|
REQUIRE(result == Storage::File::Result::Success);
|
||||||
|
|
||||||
auto data = bytes::vector(16);
|
auto data = bytes::vector(Test1.size());
|
||||||
const auto read = file.read(data);
|
const auto read = file.read(data);
|
||||||
REQUIRE(read == data.size());
|
REQUIRE(read == data.size());
|
||||||
REQUIRE(data == bytes::make_vector(Test1));
|
REQUIRE(data == bytes::make_vector(Test1));
|
||||||
@ -122,24 +122,29 @@ TEST_CASE("simple encrypted file", "[storage_encrypted_file]") {
|
|||||||
Key);
|
Key);
|
||||||
REQUIRE(result == Storage::File::Result::Success);
|
REQUIRE(result == Storage::File::Result::Success);
|
||||||
REQUIRE(file.offset() == 0);
|
REQUIRE(file.offset() == 0);
|
||||||
|
REQUIRE(file.size() == Test1.size() + Test2.size());
|
||||||
|
|
||||||
const auto success1 = file.seek(16);
|
const auto success1 = file.seek(Test1.size());
|
||||||
REQUIRE(success1);
|
REQUIRE(success1);
|
||||||
REQUIRE(file.offset() == 16);
|
REQUIRE(file.offset() == Test1.size());
|
||||||
|
|
||||||
auto data = bytes::vector(16);
|
auto data = bytes::vector(Test2.size());
|
||||||
const auto read = file.read(data);
|
const auto read = file.read(data);
|
||||||
REQUIRE(read == data.size());
|
REQUIRE(read == data.size());
|
||||||
REQUIRE(data == bytes::make_vector(Test2));
|
REQUIRE(data == bytes::make_vector(Test2));
|
||||||
REQUIRE(file.offset() == 32);
|
REQUIRE(file.offset() == Test1.size() + Test2.size());
|
||||||
|
REQUIRE(file.size() == Test1.size() + Test2.size());
|
||||||
|
|
||||||
const auto success2 = file.seek(16);
|
const auto success2 = file.seek(Test1.size());
|
||||||
REQUIRE(success2);
|
REQUIRE(success2);
|
||||||
REQUIRE(file.offset() == 16);
|
REQUIRE(file.offset() == Test1.size());
|
||||||
|
|
||||||
data = bytes::make_vector(Test1);
|
data = bytes::make_vector(Test1);
|
||||||
const auto success3 = file.write(data);
|
const auto success3 = file.write(data) && file.write(data);
|
||||||
REQUIRE(success3);
|
REQUIRE(success3);
|
||||||
|
|
||||||
|
REQUIRE(file.offset() == 3 * Test1.size());
|
||||||
|
REQUIRE(file.size() == 3 * Test1.size());
|
||||||
}
|
}
|
||||||
SECTION("reading file") {
|
SECTION("reading file") {
|
||||||
Storage::File file;
|
Storage::File file;
|
||||||
|
@ -57,6 +57,8 @@
|
|||||||
'<(src_loc)/storage/storage_file_lock_posix.cpp',
|
'<(src_loc)/storage/storage_file_lock_posix.cpp',
|
||||||
'<(src_loc)/storage/storage_file_lock_win.cpp',
|
'<(src_loc)/storage/storage_file_lock_win.cpp',
|
||||||
'<(src_loc)/storage/storage_file_lock.h',
|
'<(src_loc)/storage/storage_file_lock.h',
|
||||||
|
'<(src_loc)/storage/cache/storage_cache_binlog_reader.cpp',
|
||||||
|
'<(src_loc)/storage/cache/storage_cache_binlog_reader.h',
|
||||||
'<(src_loc)/storage/cache/storage_cache_cleaner.cpp',
|
'<(src_loc)/storage/cache/storage_cache_cleaner.cpp',
|
||||||
'<(src_loc)/storage/cache/storage_cache_cleaner.h',
|
'<(src_loc)/storage/cache/storage_cache_cleaner.h',
|
||||||
'<(src_loc)/storage/cache/storage_cache_compactor.cpp',
|
'<(src_loc)/storage/cache/storage_cache_compactor.cpp',
|
||||||
|
Loading…
Reference in New Issue
Block a user