sync save deletion

This commit is contained in:
bryanthaboi
2026-09-09 09:46:27 -04:00
parent ca8a32746b
commit ab10687ba6
8 changed files with 306 additions and 2 deletions
+123 -1
View File
@@ -117,8 +117,58 @@ local function slotForPlaythrough(options, version, playthroughId)
return bestKey, bestSlot
end
local function slotKey(scopeKey, slotId)
local SaveData = saveApi()
local cart = cartOfScope(scopeKey)
local source
if cart then
source = SaveData.readCartSlotSource(cart, slotId)
else
source = SaveData.readSlotSource(scopeKey, slotId)
end
local save = source and SaveData.decode(source)
if type(save) ~= "table" then return nil end
local id
local scope = { key = scopeKey, cart = cart, version = scopeKey }
if cart then
id = SaveData.cartSlotPlaythroughId(cart, slotId, save)
local okOpts, options = pcall(SaveData.loadOptions)
local reg = (okOpts and type(options) == "table"
and type(options.carts) == "table") and options.carts[cart] or nil
scope.version = type(reg) == "table" and reg.base or nil
else
id = SaveData.slotPlaythroughId(scopeKey, slotId, save)
end
if not id then return nil end
return SyncState.key(wireVersion(save, scope), id)
end
function SyncEngine.defaultSaves()
return {
keyForSlot = slotKey,
remove = function(version, playthroughId)
local SaveData = saveApi()
local options = SaveData.loadOptions()
local scopeKey, slotId = slotForPlaythrough(options, version, playthroughId)
if not slotId then return false, "no such save" end
local cart = cartOfScope(scopeKey)
local ok, err
if cart then
ok, err = SaveData.deleteCartSlot(cart, slotId)
else
ok, err = SaveData.deleteSlot(scopeKey, slotId)
end
if not ok then return nil, err or "could not delete the save" end
options = SaveData.loadOptions()
if type(options.playthroughIds) == "table"
and type(options.playthroughIds[scopeKey]) == "table" then
options.playthroughIds[scopeKey][slotId] = nil
SaveData.saveOptions(options)
end
return slotId, cart
end,
list = function()
local SaveData = saveApi()
local out = {}
@@ -402,6 +452,22 @@ function SyncEngine:noteSaveWritten()
self.uploadAt = self.clock + SyncEngine.UPLOAD_DEBOUNCE
end
function SyncEngine:noteSaveDeleted(key)
if type(key) ~= "string" or key == "" then return false end
local rev = SyncState.rev(self.state, key)
SyncState.forget(self.state, key)
if rev == nil or not self:linked() then
self:_persist()
return false
end
SyncState.markDeleted(self.state, key, rev, self.now())
self:_persist()
if self.state.enabled then
self.uploadAt = self.clock + SyncEngine.UPLOAD_DEBOUNCE
end
return true
end
function SyncEngine:update(dt)
self.clock = self.clock + (tonumber(dt) or 0)
if self.pending then
@@ -650,12 +716,14 @@ function SyncEngine:_planFrom(remoteState)
self.devices = list
end
local remote = type(remoteState.saves) == "table" and remoteState.saves or {}
local tombs = type(remoteState.deleted) == "table" and remoteState.deleted or {}
local locals = self.saves.list() or {}
local seen = {}
for _, entry in ipairs(locals) do
local key = SyncState.key(entry.version, entry.playthroughId)
if key then
seen[key] = true
SyncState.clearDeleted(self.state, key)
local row = remote[key]
local knownRev = SyncState.rev(self.state, key)
local stamp = unixSeconds(SyncState.stamp(self.state, key))
@@ -668,7 +736,14 @@ function SyncEngine:_planFrom(remoteState)
end
local remoteRev = row and tonumber(row.rev)
local remoteChanged = row ~= nil and remoteRev ~= knownRev
if not row then
local tomb = not row and type(tombs[key]) == "table" and tombs[key] or nil
local buried = tomb ~= nil and knownRev ~= nil
and (tonumber(tomb.rev) or 0) >= knownRev
and not (localChanged and liveStamp
and liveStamp > (unixSeconds(tomb.deletedAt) or 0))
if buried then
self:_removeLocal(entry, key, tomb)
elseif not row then
self:_queueUpload(entry, key, false)
elseif localChanged and remoteChanged then
if SyncEngine.sameProgress(entry.meta, SyncEngine.metaOf(row)) then
@@ -683,6 +758,17 @@ function SyncEngine:_planFrom(remoteState)
end
end
end
for key, pending in pairs(self.state.pendingDeletes or {}) do
local row = remote[key]
if not seen[key] then
if row and (tonumber(row.rev) or 0) > (tonumber(pending.rev) or 0) then
SyncState.clearDeleted(self.state, key)
else
seen[key] = true
self:_queueDelete(key, pending.rev)
end
end
end
for key, row in pairs(remote) do
if not seen[key] and key ~= self.protectedKey then
local version, id = SyncState.splitKey(key)
@@ -694,6 +780,42 @@ function SyncEngine:_planFrom(remoteState)
if #self.queue == 0 then self:_finish() end
end
function SyncEngine:_removeLocal(entry, key, tomb)
if key == self.protectedKey then return end
local slotId, cartId
if type(self.saves.remove) == "function" then
slotId, cartId = self.saves.remove(entry.version, entry.playthroughId)
end
SyncState.forget(self.state, key)
if slotId then
self.lastDownloads = self.lastDownloads or {}
self.lastDownloads[#self.lastDownloads + 1] = {
version = entry.version,
cart = cartId or entry.cart,
slot = slotId,
removed = true,
device = type(tomb.device) == "string" and tomb.device ~= ""
and tomb.device or nil,
}
self.changed = true
end
end
function SyncEngine:_queueDelete(key, rev)
self:_enqueue(function(eng)
eng.phase = "uploading"
eng.status = "Removing deleted saves..."
local version, id = SyncState.splitKey(key)
local handle, err = eng.client:deleteSave(version, id, rev)
eng:_request(handle, err, function(e)
SyncState.clearDeleted(e.state, key)
SyncState.forget(e.state, key)
e:_persist()
if not e:busy() then e:_finish() end
end)
end)
end
function SyncEngine:_addConflict(entry, key, row)
local remoteMeta = SyncEngine.displayMeta(SyncEngine.metaOf(row))
self.conflicts[#self.conflicts + 1] = {