80 IFileStore() =
default;
81 IFileStore(IFileStore
const&) =
delete;
82 IFileStore& operator=(IFileStore
const&) =
delete;
83 IFileStore(IFileStore&&) =
delete;
84 IFileStore& operator=(IFileStore&&) =
delete;
85 virtual ~IFileStore() =
default;
91 [[nodiscard]]
virtual std::expected<std::string, FileError>
begin(std::string
const& principal,
FileInfo info) = 0;
99 [[nodiscard]]
virtual std::expected<std::size_t, FileError>
append(std::string
const& principal,
100 std::string
const& upload, std::size_t offset,
101 std::string_view bytes) = 0;
107 [[nodiscard]]
virtual std::expected<std::string, FileError>
finish(std::string
const& principal,
108 std::string
const& upload) = 0;
115 [[nodiscard]]
virtual std::expected<std::string, FileError>
issue(std::string
const& principal,
FileInfo info,
116 std::string bytes) = 0;
124 [[nodiscard]]
virtual std::expected<FileSlice, FileError>
read(std::string
const& principal,
125 std::string
const& ref, std::size_t offset,
126 std::size_t length) = 0;
135[[nodiscard]]
inline std::string
newFileRef(std::string_view prefix =
"file:") {
136 static constexpr std::string_view kHex =
"0123456789abcdef";
137 std::random_device entropy;
138 std::string
text{prefix};
139 for (
int word = 0; word < 4; ++word) {
140 std::uint32_t value = entropy();
141 for (
int nibble = 0; nibble < 8; ++nibble) {
142 text += kHex.at(value & 0xFU);
158 std::function<std::chrono::steady_clock::time_point()>
clock;
174 [[nodiscard]] std::expected<std::string, FileError>
begin(std::string
const& principal,
FileInfo info)
override {
175 std::scoped_lock
const lock{_mutex};
177 if (info.size > _options.maxFileBytes) {
180 if (usage(principal) + info.size > _options.maxBytesPerPrincipal) {
184 _uploads.emplace(uploadId,
185 Upload{.principal = principal, .info = std::move(info), .bytes = {}, .touched = now()});
195 [[nodiscard]] std::expected<std::size_t, FileError>
append(std::string
const& principal, std::string
const& upload,
196 std::size_t offset, std::string_view bytes)
override {
197 std::scoped_lock
const lock{_mutex};
199 auto const found = _uploads.find(upload);
200 if (found == _uploads.end() || found->second.principal != principal) {
203 Upload& open = found->second;
204 if (offset != open.bytes.size()) {
207 if (open.bytes.size() + bytes.size() > open.info.size) {
210 open.bytes.append(bytes);
211 open.touched = now();
212 return open.bytes.size();
219 [[nodiscard]] std::expected<std::string, FileError>
finish(std::string
const& principal,
220 std::string
const& upload)
override {
221 std::scoped_lock
const lock{_mutex};
223 auto const found = _uploads.find(upload);
224 if (found == _uploads.end() || found->second.principal != principal) {
227 if (found->second.bytes.size() != found->second.info.size) {
230 Upload done = std::move(found->second);
231 _uploads.erase(found);
233 _files.emplace(ref, Stored{.principal = std::move(done.principal),
234 .info = std::move(done.info),
235 .bytes = std::move(done.bytes)});
244 [[nodiscard]] std::expected<std::string, FileError>
issue(std::string
const& principal,
FileInfo info,
245 std::string bytes)
override {
246 std::scoped_lock
const lock{_mutex};
248 if (bytes.size() > _options.maxFileBytes) {
251 if (usage(principal) + bytes.size() > _options.maxBytesPerPrincipal) {
254 info.size = bytes.size();
256 _files.emplace(ref, Stored{.principal = principal, .info = std::move(info), .bytes = std::move(bytes)});
266 [[nodiscard]] std::expected<FileSlice, FileError>
read(std::string
const& principal, std::string
const& ref,
267 std::size_t offset, std::size_t length)
override {
268 std::scoped_lock
const lock{_mutex};
269 auto const found = _files.find(ref);
270 if (found == _files.end() || found->second.principal != principal) {
273 Stored
const& file = found->second;
274 std::size_t
const start = std::min(offset, file.bytes.size());
275 return FileSlice{.bytes = file.bytes.substr(start, length), .info = file.info};
280 std::string principal;
283 std::chrono::steady_clock::time_point touched;
287 std::string principal;
292 [[nodiscard]] std::chrono::steady_clock::time_point now()
const {
293 return _options.
clock ? _options.
clock() : std::chrono::steady_clock::now();
298 auto const cutoff = now() - _options.uploadTtl;
299 std::erase_if(_uploads, [cutoff](
auto const& entry) {
return entry.second.touched < cutoff; });
303 [[nodiscard]] std::size_t usage(std::string
const& principal)
const {
304 std::size_t total = 0;
305 for (
auto const& entry : _files) {
306 if (entry.second.principal == principal) {
307 total += entry.second.bytes.size();
310 for (
auto const& entry : _uploads) {
311 if (entry.second.principal == principal) {
312 total += entry.second.info.size;
318 MemoryFileStoreOptions _options;
319 mutable std::mutex _mutex;
320 std::map<std::string, Upload, std::less<>> _uploads;
321 std::map<std::string, Stored, std::less<>> _files;
virtual std::expected< FileSlice, FileError > read(std::string const &principal, std::string const &ref, std::size_t offset, std::size_t length)=0
Reads a slice of a file.
virtual std::expected< std::string, FileError > issue(std::string const &principal, FileInfo info, std::string bytes)=0
Files bytes the server made for a principal, such as a report a reply returns.
virtual std::expected< std::size_t, FileError > append(std::string const &principal, std::string const &upload, std::size_t offset, std::string_view bytes)=0
Appends a chunk to an upload.
virtual std::expected< std::string, FileError > begin(std::string const &principal, FileInfo info)=0
Starts an upload.
virtual std::expected< std::string, FileError > finish(std::string const &principal, std::string const &upload)=0
Ends an upload and files it.
std::expected< std::string, FileError > issue(std::string const &principal, FileInfo info, std::string bytes) override
Files bytes for a principal.
std::expected< std::string, FileError > finish(std::string const &principal, std::string const &upload) override
Ends an upload and files it.
std::expected< std::size_t, FileError > append(std::string const &principal, std::string const &upload, std::size_t offset, std::string_view bytes) override
Appends a chunk.
std::expected< FileSlice, FileError > read(std::string const &principal, std::string const &ref, std::size_t offset, std::size_t length) override
Reads a slice.
std::expected< std::string, FileError > begin(std::string const &principal, FileInfo info) override
Starts an upload.
MemoryFileStore(MemoryFileStoreOptions options={})
A store with options.
constexpr std::string_view fileErrorText(FileError error) noexcept
The text of a FileError, as a ui-file refusal carries it.
std::string newFileRef(std::string_view prefix="file:")
A new unguessable id: prefix, then 32 lower-case hex digits read straight from std::random_device.
FileError
Why a store refused a call.
@ NotFound
No such upload or file for this principal; a caller cannot tell the two apart.
@ OverQuota
The principal's files would exceed what the store keeps for one principal.
@ Incomplete
An upload ended before every byte it announced arrived.
@ TooLarge
The file is larger than the store accepts.
@ BadOffset
A chunk does not start where the bytes received so far end.
What a file is, as its uploader described it.
std::string type
Its media type.
std::string name
The file's name.
std::size_t size
Its size in bytes.
A slice of a stored file.
FileInfo info
The file's description; size is the whole file's.
std::string bytes
The bytes of the slice.
How much a MemoryFileStore keeps.
std::chrono::seconds uploadTtl
How long an upload may go without a chunk before it is dropped.
std::size_t maxFileBytes
The largest file it accepts.
std::function< std::chrono::steady_clock::time_point()> clock
The clock expiry reads; the steady clock when empty.
std::size_t maxBytesPerPrincipal
The most bytes it keeps for one principal, uploads in progress included.
Node text(Text spec)
Static or bound text.