1, "items"=>[]]; $json = file_get_contents($path); $data = json_decode($json, true); return (is_array($data) && isset($data['items'])) ? $data : ["version"=>1, "items"=>[]]; } private static function saveState(string $username, array $state): bool { $path = self::filePathFor($username); $tmp = $path . '.tmp'; $ok = file_put_contents($tmp, json_encode($state, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT), LOCK_EX); if ($ok === false) return false; return @rename($tmp, $path); } /** Save/merge a single file progress record. */ public static function saveProgress(string $username, string $folder, string $file, float $seconds, ?float $duration, ?bool $completed): array { $folderKey = ($folder === '' || strtolower($folder)==='root') ? 'root' : $folder; $nowIso = date('c'); $state = self::loadState($username); if (!isset($state['items'][$folderKey])) $state['items'][$folderKey] = []; if (!isset($state['items'][$folderKey][$file])) { $state['items'][$folderKey][$file] = [ "seconds" => 0, "duration" => $duration ?? 0, "completed" => false, "updatedAt" => $nowIso ]; } $row =& $state['items'][$folderKey][$file]; if ($duration !== null && $duration > 0) $row['duration'] = $duration; if ($seconds >= 0) $row['seconds'] = $seconds; if ($completed !== null) $row['completed'] = (bool)$completed; // auto-complete if we’re basically done if (!$row['completed'] && $row['duration'] > 0 && $row['seconds'] >= max(0, $row['duration'] * 0.95)) { $row['completed'] = true; } $row['updatedAt'] = $nowIso; self::saveState($username, $state); return $row; } /** Get a single file progress record. */ public static function getProgress(string $username, string $folder, string $file): array { $folderKey = ($folder === '' || strtolower($folder)==='root') ? 'root' : $folder; $state = self::loadState($username); $row = $state['items'][$folderKey][$file] ?? null; return is_array($row) ? $row : ["seconds"=>0,"duration"=>0,"completed"=>false,"updatedAt"=>null]; } /** Folder map: filename => {seconds,duration,completed,updatedAt} */ public static function getFolderMap(string $username, string $folder): array { $folderKey = ($folder === '' || strtolower($folder)==='root') ? 'root' : $folder; $state = self::loadState($username); $items = $state['items'][$folderKey] ?? []; return is_array($items) ? $items : []; } /** Clear one file’s progress (e.g., “mark unviewed”). */ public static function clearProgress(string $username, string $folder, string $file): bool { $folderKey = ($folder === '' || strtolower($folder)==='root') ? 'root' : $folder; $state = self::loadState($username); if (isset($state['items'][$folderKey][$file])) { unset($state['items'][$folderKey][$file]); return self::saveState($username, $state); } return true; } }