|
| 1 | +import { ILogService } from '#/_base/log/log'; |
1 | 2 | import { SESSION_INDEX_KEY, SESSION_INDEX_SCOPE } from '#/app/workspace/workspaceAlias'; |
2 | 3 | import { IAtomicDocumentStore } from '#/persistence/interface/atomicDocumentStore'; |
3 | | -import { IFileSystemStorageService } from '#/persistence/interface/storage'; |
| 4 | +import { |
| 5 | + IFileSystemStorageService, |
| 6 | + StorageError, |
| 7 | + StorageErrors, |
| 8 | +} from '#/persistence/interface/storage'; |
4 | 9 |
|
5 | 10 | import { CHILD_SESSION_KIND, CHILD_SESSION_KIND_KEY, type SessionSummary } from './sessionIndex'; |
6 | 11 |
|
@@ -171,27 +176,49 @@ export async function mapBounded<T, R>( |
171 | 176 | return out; |
172 | 177 | } |
173 | 178 |
|
| 179 | +async function stateFileMtime( |
| 180 | + storage: IFileSystemStorageService, |
| 181 | + scope: string, |
| 182 | + log: ILogService | undefined, |
| 183 | +): Promise<number | undefined> { |
| 184 | + try { |
| 185 | + return await storage.mtime(scope, META_KEY); |
| 186 | + } catch (error) { |
| 187 | + if ( |
| 188 | + error instanceof StorageError && |
| 189 | + error.code === StorageErrors.codes.STORAGE_IO_FAILED && |
| 190 | + error.details?.['errno'] === 'ENOTDIR' |
| 191 | + ) { |
| 192 | + log?.warn('session index skips a non-directory entry', { path: error.details['path'] }); |
| 193 | + return undefined; |
| 194 | + } |
| 195 | + throw error; |
| 196 | + } |
| 197 | +} |
| 198 | + |
174 | 199 | export async function sessionStateMaxMtime( |
175 | 200 | storage: IFileSystemStorageService, |
176 | 201 | sessionsScope: string, |
177 | 202 | workspaceId: string, |
178 | 203 | sessionId: string, |
| 204 | + log?: ILogService, |
179 | 205 | ): Promise<number> { |
180 | 206 | const base = `${sessionsScope}/${workspaceId}/${sessionId}`; |
181 | | - const direct = await storage.mtime(base, META_KEY); |
182 | | - const nested = await storage.mtime(`${base}/${META_SCOPE}`, META_KEY); |
| 207 | + const direct = await stateFileMtime(storage, base, log); |
| 208 | + const nested = await stateFileMtime(storage, `${base}/${META_SCOPE}`, log); |
183 | 209 | return Math.max(direct ?? 0, nested ?? 0); |
184 | 210 | } |
185 | 211 |
|
186 | 212 | export async function scanSessionsMaxMtime( |
187 | 213 | storage: IFileSystemStorageService, |
188 | 214 | sessionsScope: string, |
| 215 | + log?: ILogService, |
189 | 216 | ): Promise<number> { |
190 | 217 | let max = (await storage.mtime(SESSION_INDEX_SCOPE, SESSION_INDEX_KEY)) ?? 0; |
191 | 218 | for (const workspaceId of await listWorkspaceIds(storage, sessionsScope)) { |
192 | 219 | const sessionIds = await listSessionIds(storage, sessionsScope, workspaceId); |
193 | 220 | const mtimes = await mapBounded(sessionIds, MTIME_SCAN_CONCURRENCY, (sessionId) => |
194 | | - sessionStateMaxMtime(storage, sessionsScope, workspaceId, sessionId), |
| 221 | + sessionStateMaxMtime(storage, sessionsScope, workspaceId, sessionId, log), |
195 | 222 | ); |
196 | 223 | for (const mtime of mtimes) { |
197 | 224 | if (mtime > max) max = mtime; |
|
0 commit comments