mirror of
https://github.com/vicliu624/trail-mate.git
synced 2026-10-07 02:57:21 +00:00
Read saved geocaches without online startup or GPX rehashing
This commit is contained in:
@@ -467,10 +467,13 @@ int main(int argc, char** argv)
|
||||
const auto disk = test::files;
|
||||
const auto disk_directories = test::directories;
|
||||
std::fprintf(stderr, "Runtime: healthy restart\n");
|
||||
// Cold offline startup has no dispatch destination or QueryClient yet.
|
||||
// Saved rows and full details must not wait for either to become available.
|
||||
router.ready = false;
|
||||
test::source->activate(true);
|
||||
until([&]
|
||||
{ return std::strstr(snapshot(Section::Discover).status.data(), "Finding a public directory"); },
|
||||
"restart did not restore indexed state");
|
||||
{ return std::strstr(snapshot(Section::Discover).status.data(), "Waiting for Reticulum IP connection"); },
|
||||
"offline startup did not wait for its online identity");
|
||||
test::source->requestWindow(Section::Downloaded, 0, 4);
|
||||
until([&]
|
||||
{
|
||||
@@ -478,7 +481,6 @@ int main(int argc, char** argv)
|
||||
return view.count == 1 && test::source->item(Section::Downloaded, 0, view.generation, item) && item.id == saved_id && item.downloaded; },
|
||||
"restart lost downloaded map row");
|
||||
require(test::files == disk, "read-only restart changed persisted files");
|
||||
router.ready = false;
|
||||
const auto offline_sends = router.sends;
|
||||
test::source->open(item, snapshot(Section::Downloaded).generation);
|
||||
until([&]
|
||||
@@ -490,6 +492,9 @@ int main(int argc, char** argv)
|
||||
test::source->closeDetail();
|
||||
tick();
|
||||
router.ready = true;
|
||||
until([&]
|
||||
{ return std::strstr(snapshot(Section::Discover).status.data(), "Finding a public directory"); },
|
||||
"offline session did not resume discovery when transport became ready");
|
||||
test::source->requestWindow(Section::Published, 0, 4);
|
||||
until([&]
|
||||
{
|
||||
|
||||
@@ -2492,6 +2492,7 @@ int checkIndexedDownloadReceipt(const char* path)
|
||||
if (read_saved(*indexed, {saved_id.data(), 32}, false, crypto) != DownloadRecoveryRead::End || owner.holder()) return 364;
|
||||
if (read_saved(*indexed, {other_cache.bytes.data(), 32}, true, crypto) != DownloadRecoveryRead::End || owner.holder()) return 365;
|
||||
|
||||
const auto gpx_reads_before_listing = read_bytes[target];
|
||||
SavedCacheCatalog<FileDigest> catalog(*indexed, crypto);
|
||||
if (!owner.acquire(&other_owner) || catalog.advance() || !catalog.pending() || !owner.heldBy(&other_owner)) return 366;
|
||||
owner.release(&other_owner);
|
||||
@@ -2525,12 +2526,14 @@ int checkIndexedDownloadReceipt(const char* path)
|
||||
!catalog.checked(other_cache.bytes, record.hash.bytes) || catalog.contains(other_cache.bytes, record.hash.bytes)) return 373;
|
||||
files.at(target)[0] ^= 1;
|
||||
catalog.reset();
|
||||
if (!advance_catalog() || catalog.contains(record.id.bytes, record.hash.bytes) || !catalog.checked(record.id.bytes, record.hash.bytes)) return 374;
|
||||
// Listing describes the installed record, without rereading the GPX.
|
||||
// External file edits are checked before replacing that file.
|
||||
if (!advance_catalog() || !catalog.contains(record.id.bytes, record.hash.bytes) || !catalog.checked(record.id.bytes, record.hash.bytes)) return 374;
|
||||
files = disk;
|
||||
catalog.requestWindow(0, 4);
|
||||
if (!advance_catalog()) return 375;
|
||||
catalog.snapshot(snapshot);
|
||||
if (snapshot.count != 1) return 376;
|
||||
if (snapshot.count != 1 || read_bytes[target] != gpx_reads_before_listing) return 376;
|
||||
|
||||
auto make_saved_reader = [&](size_t frame_capacity, size_t verify_capacity, protocol::RecordCrypto& verifier)
|
||||
{
|
||||
|
||||
@@ -777,8 +777,8 @@ int checkDownloadController(const char* query_path, const char* response_path)
|
||||
for (unsigned i = 0; saved.pending() && i < 256; ++i) saved.advance();
|
||||
saved.snapshot(snapshot);
|
||||
if (snapshot.count != 1 || saved.item(1, snapshot.generation, item)) return 342;
|
||||
// Reset during hashing must be a UI-only notification. The next
|
||||
// owner step discards the old reader and starts the new window.
|
||||
// Reset is a UI-only notification. The next owner step starts
|
||||
// reading metadata for the new window.
|
||||
saved.requestWindow(0, 1);
|
||||
saved.advance();
|
||||
fixture::step_io_calls = 0;
|
||||
@@ -805,7 +805,7 @@ int checkDownloadController(const char* query_path, const char* response_path)
|
||||
saved.reset();
|
||||
for (unsigned i = 0; saved.pending() && i < 256; ++i) saved.advance();
|
||||
saved.snapshot(snapshot);
|
||||
if (snapshot.count || !std::strstr(snapshot.status.data(), "changed")) return 186;
|
||||
if (snapshot.count != 1 || !saved.item(0, snapshot.generation, item)) return 186;
|
||||
fixture::files[target] = valid_file;
|
||||
std::array<uint8_t, 4096> restored_a{}, restored_b{};
|
||||
storage::LogicalState restored(restored_a.data(), restored_b.data(), restored_a.size());
|
||||
|
||||
+16
-59
@@ -1,16 +1,14 @@
|
||||
#pragma once
|
||||
#include "platform/esp/arduino_common/geocaching/download_store.h"
|
||||
#include "platform/esp/arduino_common/geocaching/sd_gpx_hash.h"
|
||||
#include "ui_presentation/geocaching/geocaching_source.h"
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
|
||||
namespace platform::esp::arduino_common::geocaching
|
||||
{
|
||||
// Four owned rows shared by the saved-list window and discovery membership
|
||||
// probes. UI calls never scan storage or close files. The maintenance owner
|
||||
// advances verification and resets the projection after installs/media handoff.
|
||||
// reads saved metadata and resets the projection after installs/media handoff.
|
||||
// GPX integrity belongs to installation/recovery, not list rendering.
|
||||
template <class Digest>
|
||||
class SavedCacheCatalog
|
||||
{
|
||||
@@ -25,7 +23,6 @@ class SavedCacheCatalog
|
||||
{
|
||||
restart_ = true;
|
||||
valid_ = 0;
|
||||
invalid_ = false;
|
||||
checking_ = true;
|
||||
error_ = DownloadRecoveryRead::End;
|
||||
++generation_;
|
||||
@@ -36,11 +33,9 @@ class SavedCacheCatalog
|
||||
// Maintenance-owner call before a foreground operation or media handoff.
|
||||
void releaseRead()
|
||||
{
|
||||
if (!reader_ && !reading_ && !metadata_ready_) return;
|
||||
if (!reading_) return;
|
||||
if (reading_) store_.releaseRead();
|
||||
reading_ = metadata_ready_ = false;
|
||||
reader_.reset();
|
||||
digest_.reset();
|
||||
reading_ = false;
|
||||
reset();
|
||||
}
|
||||
void requestWindow(size_t offset, size_t count)
|
||||
@@ -87,21 +82,11 @@ class SavedCacheCatalog
|
||||
if (restart_)
|
||||
{
|
||||
if (reading_) store_.releaseRead();
|
||||
reader_.reset();
|
||||
digest_.reset();
|
||||
reading_ = metadata_ready_ = has_after_ = false;
|
||||
reading_ = has_after_ = false;
|
||||
cursor_ = total_ = 0;
|
||||
restart_ = false;
|
||||
}
|
||||
if (!checking_) return false;
|
||||
if (reader_)
|
||||
{
|
||||
if (reader_->step() == GpxHashStep::Reading) return true;
|
||||
std::array<uint8_t, 32> hash;
|
||||
finish(reader_->result(hash) && hash == current_.file_hash);
|
||||
return true;
|
||||
}
|
||||
if (!metadata_ready_)
|
||||
{
|
||||
if (preview_)
|
||||
{
|
||||
@@ -132,31 +117,13 @@ class SavedCacheCatalog
|
||||
++cursor_;
|
||||
return true;
|
||||
}
|
||||
metadata_ready_ = true;
|
||||
}
|
||||
char path[112]{};
|
||||
std::snprintf(path, sizeof(path), "/trailmate/geocaching/caches/");
|
||||
size_t offset = std::strlen(path);
|
||||
constexpr char hex[] = "0123456789abcdef";
|
||||
for (auto byte : current_.id)
|
||||
{
|
||||
path[offset++] = hex[byte >> 4];
|
||||
path[offset++] = hex[byte & 15];
|
||||
}
|
||||
std::memcpy(path + offset, ".gpx", 5);
|
||||
digest_.reset(new (std::nothrow) Digest);
|
||||
if (digest_) reader_.reset(new (std::nothrow) SdGpxHash<Digest>(*digest_));
|
||||
if (!reader_)
|
||||
{
|
||||
digest_.reset();
|
||||
return readError(DownloadRecoveryRead::Unavailable, true);
|
||||
}
|
||||
if (error_ != DownloadRecoveryRead::End)
|
||||
{
|
||||
error_ = DownloadRecoveryRead::End;
|
||||
++generation_;
|
||||
}
|
||||
if (!reader_->open(path)) finish(false);
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
const char* error() const
|
||||
@@ -183,8 +150,7 @@ class SavedCacheCatalog
|
||||
out.generation = generation_;
|
||||
out.can_refresh = !checking_;
|
||||
out.count = count_;
|
||||
std::snprintf(out.status.data(), out.status.size(), "%s", error() ? error() : checking_ ? "Checking saved GPX files..."
|
||||
: invalid_ ? "Some GPX files changed or are unavailable"
|
||||
std::snprintf(out.status.data(), out.status.size(), "%s", error() ? error() : checking_ ? "Loading downloaded caches..."
|
||||
: out.count ? "Saved GPX - available offline"
|
||||
: "No downloaded caches");
|
||||
}
|
||||
@@ -235,32 +201,23 @@ class SavedCacheCatalog
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void finish(bool valid)
|
||||
void finish()
|
||||
{
|
||||
if (valid)
|
||||
const auto slot = preview_ ? requestedRow(current_.id, current_.hash) : total_ >= offset_ ? total_ - offset_
|
||||
: rows_.size();
|
||||
if (slot < window_)
|
||||
{
|
||||
const auto slot = preview_ ? requestedRow(current_.id, current_.hash) : total_ >= offset_ ? total_ - offset_
|
||||
: rows_.size();
|
||||
if (slot < window_)
|
||||
{
|
||||
rows_[slot] = current_;
|
||||
valid_ |= uint8_t(1u << slot);
|
||||
}
|
||||
++total_;
|
||||
rows_[slot] = current_;
|
||||
valid_ |= uint8_t(1u << slot);
|
||||
}
|
||||
else invalid_ = true;
|
||||
++total_;
|
||||
after_ = current_.id;
|
||||
has_after_ = true;
|
||||
metadata_ready_ = false;
|
||||
++cursor_;
|
||||
++generation_;
|
||||
reader_.reset();
|
||||
digest_.reset();
|
||||
}
|
||||
DownloadStore& store_;
|
||||
::geocaching::protocol::RecordCrypto& crypto_;
|
||||
std::unique_ptr<Digest> digest_;
|
||||
std::unique_ptr<SdGpxHash<Digest>> reader_;
|
||||
std::array<uint8_t, 32> after_{};
|
||||
std::array<::geocaching::storage::SavedCacheEntry, 4> rows_{};
|
||||
::geocaching::storage::SavedCacheRecord current_;
|
||||
@@ -268,7 +225,7 @@ class SavedCacheCatalog
|
||||
size_t cursor_ = 0, total_ = 0, count_ = 0, offset_ = 0, window_ = 4;
|
||||
uint8_t valid_ = 0, requested_ = 0;
|
||||
DownloadRecoveryRead error_ = DownloadRecoveryRead::End;
|
||||
bool checking_ = true, restart_ = false, invalid_ = false, preview_ = false;
|
||||
bool reading_ = false, metadata_ready_ = false, has_after_ = false;
|
||||
bool checking_ = true, restart_ = false, preview_ = false;
|
||||
bool reading_ = false, has_after_ = false;
|
||||
};
|
||||
} // namespace platform::esp::arduino_common::geocaching
|
||||
|
||||
@@ -1826,7 +1826,7 @@ void step()
|
||||
}
|
||||
advanceBrowse(s);
|
||||
if (s.detail) s.detail->advanceNetwork(*router, s.local, s.crypto, now(nullptr).monotonic_ms);
|
||||
if (!s.storage_requested || !s.client)
|
||||
if (!s.storage_requested)
|
||||
{
|
||||
next_step.store(millis() + 100);
|
||||
return;
|
||||
@@ -2126,20 +2126,11 @@ void step()
|
||||
s.download_store.reset(new (std::nothrow) IndexedDownloadStore(s.volume, s.root, s.root_copy, s.roots[0], s.roots[1],
|
||||
s.workspace_owner, s.workspace, s.frame, kFrameCapacity,
|
||||
s.payload, kPayloadCapacity, s.verification, kVerificationCapacity, s.crypto));
|
||||
s.dispatch_store.reset(new (std::nothrow) IndexedDispatchStore(s.volume, s.root, s.root_copy, s.roots[0], s.roots[1],
|
||||
s.workspace_owner, s.workspace, s.frame, kFrameCapacity));
|
||||
if (!s.store || !s.download_store || !s.dispatch_store || !s.workspace_owner.setPrepare(Session::prepareWorkspace, &s))
|
||||
if (!s.store || !s.download_store || !s.workspace_owner.setPrepare(Session::prepareWorkspace, &s))
|
||||
{
|
||||
fail("Insufficient indexed storage memory");
|
||||
return;
|
||||
}
|
||||
s.dispatcher.reset(new (std::nothrow) RequestDispatcher(*router, *s.dispatch_store, 5000, 120000));
|
||||
s.receipts.reset(new (std::nothrow) StoredReplyReceipt(s.volume, s.root, s.local, s.workspace_owner, s.crypto));
|
||||
if (!s.dispatcher || !s.receipts)
|
||||
{
|
||||
fail("Insufficient storage service memory");
|
||||
return;
|
||||
}
|
||||
s.saved.reset(new (std::nothrow) SavedCacheCatalog<Digest>(*s.download_store, s.crypto));
|
||||
if (!s.saved)
|
||||
{
|
||||
@@ -2164,8 +2155,8 @@ void step()
|
||||
return;
|
||||
}
|
||||
// A user-edited installed GPX or missing history remains untouched.
|
||||
// Its catalogue entry is checked separately; keep browsing other
|
||||
// caches instead of turning one offline file into a service outage.
|
||||
// Keep its installed metadata available for browsing instead of
|
||||
// turning one offline file into a service outage.
|
||||
s.recovery_attention |= result != gc::DownloadOperationResult::Complete || s.download_port->historyPending();
|
||||
s.download_port.reset();
|
||||
s.saved->reset();
|
||||
@@ -2220,6 +2211,27 @@ void step()
|
||||
fail("Storage interrupted - reopen to recover");
|
||||
return;
|
||||
}
|
||||
// Local lists, details, drafts and interrupted file installation above
|
||||
// do not need an online identity or request services. Only resume network
|
||||
// work once the real local destination is known.
|
||||
if (!s.client)
|
||||
{
|
||||
if (!startCheckpoint(s)) next_step.store(millis() + 100);
|
||||
return;
|
||||
}
|
||||
if (!s.dispatch_store)
|
||||
s.dispatch_store.reset(new (std::nothrow) IndexedDispatchStore(s.volume, s.root, s.root_copy, s.roots[0], s.roots[1],
|
||||
s.workspace_owner, s.workspace, s.frame, kFrameCapacity));
|
||||
if (s.dispatch_store && !s.dispatcher)
|
||||
s.dispatcher.reset(new (std::nothrow) RequestDispatcher(*router, *s.dispatch_store, 5000, 120000));
|
||||
if (!s.receipts)
|
||||
s.receipts.reset(new (std::nothrow) StoredReplyReceipt(s.volume, s.root, s.local, s.workspace_owner, s.crypto));
|
||||
if (!s.dispatcher || !s.receipts)
|
||||
{
|
||||
s.browse_status = "Waiting for network service memory";
|
||||
next_step.store(millis() + 1000);
|
||||
return;
|
||||
}
|
||||
if (publicationActive())
|
||||
{
|
||||
auto& job = *s.publication;
|
||||
|
||||
Reference in New Issue
Block a user