Let each control actually govern the thing it names
Auto Scroll, the learning gate, the fold and the flash label each described a behaviour they did not enforce.
This commit is contained in:
@@ -139,7 +139,9 @@ function vv_ai_mem_propose(string $text, array $meta = []): array {
|
||||
if (!$r['ok']) $row['error'] = $r['error'];
|
||||
}
|
||||
|
||||
@file_put_contents(vv_ai_mem_dir() . "/$id.json", json_encode($row, JSON_PRETTY_PRINT));
|
||||
if (!vv_ai_mem_write_row(vv_ai_mem_dir() . "/$id.json", $row)) {
|
||||
return ['ok' => false, 'error' => 'could not file the proposal'];
|
||||
}
|
||||
return ['ok' => true, 'id' => $id, 'state' => $row['state']];
|
||||
}
|
||||
|
||||
@@ -178,30 +180,63 @@ function vv_ai_mem_list(string $state = ''): array {
|
||||
return $out;
|
||||
}
|
||||
|
||||
// Temp and rename, so a concurrent list() never decodes half a record — a torn read here is
|
||||
// indistinguishable from a corrupt proposal and the row silently vanishes from the card.
|
||||
function vv_ai_mem_write_row(string $f, array $row): bool {
|
||||
$tmp = $f . '.tmp';
|
||||
if (@file_put_contents($tmp, json_encode($row, JSON_PRETTY_PRINT)) === false) {
|
||||
@unlink($tmp);
|
||||
return false;
|
||||
}
|
||||
if (!@rename($tmp, $f)) { @unlink($tmp); return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
// One lock for every decision, not one per proposal. Accept is a read-modify-write across two
|
||||
// files — this proposal and the learned slot — and two of them interleaving is how the same line
|
||||
// lands in memory twice, or how one of two accepted lines is lost to a temp-and-rename that began
|
||||
// before the other finished. The card arms and disables the button, so this only has to hold
|
||||
// against a second tab, a double submit or an impatient reload; that is exactly when it matters,
|
||||
// because none of those are visible from the one that is about to lose.
|
||||
function vv_ai_mem_action(string $id, string $act): array {
|
||||
if (!preg_match('/^[0-9a-f]{12}$/', $id)) return ['ok' => false, 'error' => 'bad id'];
|
||||
if (!preg_match('/^[0-9a-f]{12}$/', $id)) return ['ok' => false, 'error' => 'bad id'];
|
||||
if ($act !== 'accept' && $act !== 'dismiss') return ['ok' => false, 'error' => 'unknown action'];
|
||||
|
||||
$f = vv_ai_mem_dir() . "/$id.json";
|
||||
if (!file_exists($f)) return ['ok' => false, 'error' => 'no such proposal'];
|
||||
|
||||
$d = json_decode((string)@file_get_contents($f), true);
|
||||
if (!is_array($d)) return ['ok' => false, 'error' => 'unreadable proposal'];
|
||||
if (($d['state'] ?? '') !== 'open') {
|
||||
// A tab left open overnight must not act on a choice the store has already moved past.
|
||||
return ['ok' => false, 'error' => 'already ' . ($d['state'] ?? 'closed')];
|
||||
$lock = @fopen(vv_ai_mem_dir() . '/.lock', 'c');
|
||||
if ($lock === false || !flock($lock, LOCK_EX)) {
|
||||
if ($lock !== false) fclose($lock);
|
||||
return ['ok' => false, 'error' => 'could not lock the proposal store'];
|
||||
}
|
||||
|
||||
if ($act === 'accept') {
|
||||
$r = vv_ai_mem_append((string)$d['text']);
|
||||
if (!$r['ok']) return $r;
|
||||
$d['state'] = 'accepted';
|
||||
$d['accepted'] = time();
|
||||
} elseif ($act === 'dismiss') {
|
||||
$d['state'] = 'dismissed';
|
||||
$d['closed'] = time();
|
||||
} else {
|
||||
return ['ok' => false, 'error' => 'unknown action'];
|
||||
}
|
||||
try {
|
||||
// Re-read under the lock. Whatever the card was showing when it was clicked is not
|
||||
// evidence of anything — the decision may already have been made in another tab.
|
||||
$d = json_decode((string)@file_get_contents($f), true);
|
||||
if (!is_array($d)) return ['ok' => false, 'error' => 'unreadable proposal'];
|
||||
if (($d['state'] ?? '') !== 'open') {
|
||||
// A tab left open overnight must not act on a choice the store has already moved past.
|
||||
return ['ok' => false, 'error' => 'already ' . ($d['state'] ?? 'closed')];
|
||||
}
|
||||
|
||||
@file_put_contents($f, json_encode($d, JSON_PRETTY_PRINT));
|
||||
return ['ok' => true, 'state' => $d['state']];
|
||||
if ($act === 'accept') {
|
||||
$r = vv_ai_mem_append((string)$d['text']);
|
||||
if (!$r['ok']) return $r;
|
||||
$d['state'] = 'accepted';
|
||||
$d['accepted'] = time();
|
||||
} else {
|
||||
$d['state'] = 'dismissed';
|
||||
$d['closed'] = time();
|
||||
}
|
||||
|
||||
if (!vv_ai_mem_write_row($f, $d)) {
|
||||
return ['ok' => false, 'error' => 'could not write the proposal'];
|
||||
}
|
||||
return ['ok' => true, 'state' => $d['state']];
|
||||
} finally {
|
||||
flock($lock, LOCK_UN);
|
||||
fclose($lock);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user