File size: 6,477 Bytes
b701455
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
"""LightDiffusion Settings persistence and history store.

This module provides a small JSON-backed settings store used for:
- persisting the last used seed (previously include/last_seed.txt)
- maintaining a short history of saved generation settings (for UI)
- storing server-wide generation preferences that should survive restarts

Design notes:
- Store file defaults to './include/settings_store.json'. Override via
  environment variable LD_SETTINGS_STORE_PATH for tests or custom locations.
- migrate_from_last_seed_txt() will import the legacy include/last_seed.txt
  if present and then remove the legacy file.
- Reads/writes are atomic (write to temp file then replace).

This is intentionally lightweight (no DB dependency) and robust to failure.
"""
from __future__ import annotations

import json
import os
import time
import tempfile
import uuid
from typing import Any, Dict, List, Optional


def _default_preferences() -> Dict[str, bool]:
    return {
        "torch_compile": False,
        "vae_autotune": False,
    }


def _default_store() -> Dict[str, Any]:
    return {
        "last_seed": None,
        "history": [],
        "preferences": _default_preferences(),
    }


def _get_store_path() -> str:
    env = os.environ.get("LD_SETTINGS_STORE_PATH")
    if env:
        return env
    return os.path.join(os.getcwd(), "include", "settings_store.json")


def _read_store() -> Dict[str, Any]:
    path = _get_store_path()
    try:
        if not os.path.exists(path):
            return _default_store()
        with open(path, "r", encoding="utf-8") as f:
            raw = json.load(f)
        if not isinstance(raw, dict):
            return _default_store()

        data = _default_store()
        data.update(raw)

        history = raw.get("history")
        data["history"] = history if isinstance(history, list) else []

        seed = raw.get("last_seed")
        data["last_seed"] = int(seed) if seed is not None else None

        raw_preferences = raw.get("preferences")
        if not isinstance(raw_preferences, dict):
            raw_preferences = {}
        data["preferences"] = {
            "torch_compile": bool(raw_preferences.get("torch_compile", False)),
            "vae_autotune": bool(raw_preferences.get("vae_autotune", False)),
        }
        return data
    except Exception:
        # Corrupt/invalid file -> return sane default (do not raise)
        return _default_store()


def _write_store(data: Dict[str, Any]) -> None:
    path = _get_store_path()
    ddir = os.path.dirname(path)
    os.makedirs(ddir, exist_ok=True)
    # atomic write
    fd, tmp = tempfile.mkstemp(prefix="settings_store_", dir=ddir)
    try:
        with os.fdopen(fd, "w", encoding="utf-8") as f:
            json.dump(data, f, indent=2, ensure_ascii=False)
        os.replace(tmp, path)
    finally:
        try:
            if os.path.exists(tmp):
                os.remove(tmp)
        except Exception:
            pass


# Public API ---------------------------------------------------------------

def get_last_seed() -> Optional[int]:
    """Return the persisted last-seed or None if not set."""
    data = _read_store()
    seed = data.get("last_seed")
    return int(seed) if seed is not None else None


def get_preferences() -> Dict[str, bool]:
    """Return persisted server-wide generation preferences."""
    data = _read_store()
    prefs = data.get("preferences") or {}
    defaults = _default_preferences()
    return {
        "torch_compile": bool(prefs.get("torch_compile", defaults["torch_compile"])),
        "vae_autotune": bool(prefs.get("vae_autotune", defaults["vae_autotune"])),
    }


def set_preferences(preferences: Dict[str, Any]) -> Dict[str, bool]:
    """Persist server-wide generation preferences and return the stored values."""
    try:
        stored = {
            "torch_compile": bool(preferences.get("torch_compile", False)),
            "vae_autotune": bool(preferences.get("vae_autotune", False)),
        }
        d = _read_store()
        d["preferences"] = stored
        _write_store(d)
        return stored
    except Exception:
        return get_preferences()


def set_last_seed(seed: int) -> None:
    """Persist the provided seed (int).

    Also ensures the store file exists and retains existing history.
    """
    try:
        d = _read_store()
        d["last_seed"] = int(seed)
        _write_store(d)
    except Exception:
        # Best-effort only; never raise for caller convenience
        pass


def get_history() -> List[Dict[str, Any]]:
    """Return the settings history (most-recent-first)."""
    data = _read_store()
    hist = data.get("history") or []
    # Ensure sensible return type
    return list(reversed(hist)) if hist else []


def append_snapshot(snapshot: Dict[str, Any], max_len: int = 64) -> Dict[str, Any]:
    """Append a snapshot to the history and return the stored entry.

    The incoming `snapshot` should be a dict (typically containing a
    `settings` key). We enrich it with `id` and `ts` fields.
    """
    try:
        d = _read_store()
        hist = d.get("history") or []
        entry = {
            "id": uuid.uuid4().hex[:12],
            "ts": int(time.time()),
            **snapshot,
        }
        # Store newest at the end for compact append logic
        hist.append(entry)
        # Trim oldest if exceeding max_len
        if len(hist) > max_len:
            hist = hist[-max_len:]
        d["history"] = hist
        _write_store(d)
        return entry
    except Exception:
        return {"id": uuid.uuid4().hex[:12], "ts": int(time.time()), **snapshot}


def migrate_from_last_seed_txt() -> Optional[int]:
    """Migrate legacy `include/last_seed.txt` into the JSON store.

    Migration target directory is derived from the store path so tests can
    override the store location with LD_SETTINGS_STORE_PATH and provide a
    corresponding legacy file next to it.
    """
    store_path = _get_store_path()
    basedir = os.path.dirname(store_path)
    legacy = os.path.join(basedir, "last_seed.txt")
    if not os.path.exists(legacy):
        return None
    try:
        with open(legacy, "r", encoding="utf-8") as f:
            raw = f.read().strip()
            if not raw:
                return None
            seed = int(raw)
        set_last_seed(seed)
        try:
            os.remove(legacy)
        except Exception:
            pass
        return seed
    except Exception:
        return None