File size: 3,083 Bytes
f6678ab
 
 
 
 
 
 
 
 
 
7a42df5
 
f6678ab
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7a42df5
f6678ab
 
 
 
 
 
7a42df5
 
 
 
 
 
f6678ab
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { writeFileSync, existsSync } from "fs";
import { join } from "path";
import * as Y from "yjs";
import { getDataDir, docPath, sanitizeName } from "./utils.js";
import {
  isHfStorageEnabled,
  getDatasetId,
  setUserToken,
  pullPublishedAssets,
  schedulePush,
  recordLocalSave,
  recordLocalSaveError,
} from "./hf-storage.js";

const DEFAULT_DOC_NAME = "default";
const SAVE_DEBOUNCE_MS = 2000;
const saveTimers = new Map<string, ReturnType<typeof setTimeout>>();
const lastSaveTimestamp = new Map<string, number>();

/** @internal - exported for testing */
export function debouncedSave(documentName: string, ydoc: Y.Doc) {
  const existing = saveTimers.get(documentName);
  if (existing) clearTimeout(existing);

  saveTimers.set(documentName, setTimeout(() => {
    saveTimers.delete(documentName);
    try {
      const state = Y.encodeStateAsUpdate(ydoc);
      const buf = Buffer.from(state);
      writeFileSync(docPath(documentName), buf);
      lastSaveTimestamp.set(documentName, Date.now());
      recordLocalSave(documentName);
      console.log(`[persist] saved "${documentName}": ${buf.length} bytes`);

      if (isHfStorageEnabled()) {
        schedulePush(documentName, buf);
      }
    } catch (err) {
      // A failed local save is the most dangerous silent failure
      // because the WS layer already ack'd the edit to the client,
      // who therefore sees "Saved" while nothing is on disk. Push
      // the error into the status tracker so the SyncIndicator can
      // flip to "Local save failed" within a few seconds.
      recordLocalSaveError(documentName, err);
      console.error(`[persist] failed to save "${documentName}":`, (err as Error).message);
    }
  }, SAVE_DEBOUNCE_MS));
}

/** Reset debounce state between tests */
export function resetSaveTimers() {
  for (const t of saveTimers.values()) clearTimeout(t);
  saveTimers.clear();
  lastSaveTimestamp.clear();
}

let _publishedRestored = false;
let _restoreInProgress: Promise<void> | null = null;

export async function ensurePublishedRestored(token?: string): Promise<void> {
  const DATA_DIR = getDataDir();
  if (_publishedRestored) return;
  if (!getDatasetId()) return;

  const publishedPath = join(DATA_DIR, "published", sanitizeName(DEFAULT_DOC_NAME), "index.html");
  if (existsSync(publishedPath)) {
    _publishedRestored = true;
    return;
  }

  if (_restoreInProgress) {
    await _restoreInProgress;
    return;
  }

  if (token) setUserToken(token);

  _restoreInProgress = (async () => {
    try {
      const found = await pullPublishedAssets(DEFAULT_DOC_NAME, DATA_DIR);
      if (found) {
        _publishedRestored = true;
        console.log("[server] restored published article from HF dataset");
      }
    } catch (err) {
      console.warn("[server] failed to restore published:", (err as Error).message);
    } finally {
      _restoreInProgress = null;
    }
  })();

  await _restoreInProgress;
}

/** Reset restore state between tests */
export function resetPublishedRestored() {
  _publishedRestored = false;
  _restoreInProgress = null;
}