Line data Source code
1 : import * as React from 'react';
2 :
3 : import { useDeleteConnection } from '../connections/connection_deletion.js';
4 : import { useConnectionReferences, useConnectionRegistryStore } from '../connections/connection_registry.js';
5 : import { useLogger } from '../platform/logger/logger_provider.js';
6 : import { useStorageWriter } from '../storage/storage_provider.js';
7 : import { REPLACE_CONNECTION } from './scripts/session_scripts.js';
8 : import { useConnectionScriptsDispatch } from './scripts/session_scripts_registry.js';
9 :
10 1 : const LOG_CTX = 'connection_replace';
11 :
12 : /// The outcome of a replacement
13 : export interface ConnectionReplacement {
14 : /// Was the replaced connection removed?
15 : removed: boolean;
16 : }
17 :
18 : /// Replace a connection with another one
19 : export type ConnectionReplace = (sourceId: string, targetId: string) => Promise<ConnectionReplacement>;
20 :
21 : /// Replace a connection with another one in every session.
22 : ///
23 : /// The replacement runs in steps that can be repeated, an interrupted replacement continues with the next one:
24 : /// 1. The scripts and the pending connections of the sessions move to the other connection.
25 : /// 2. The session files are written right away, they name the other connection then. A write that
26 : /// fails stays pending and is written again by the next replacement.
27 : /// 3. The connection is removed once it is dead: no session uses it and every session file was written.
28 : /// It is shut down and deleted with its folder, see useDeleteConnection.
29 : ///
30 : /// A connection that is not removed stays in the registry and in the manifest, a session that still names
31 : /// it after a restart moves with the next replacement.
32 1 : export function useReplaceConnection(): ConnectionReplace {
33 1 : const registryStore = useConnectionRegistryStore();
34 1 : const references = useConnectionReferences();
35 1 : const scriptsDispatch = useConnectionScriptsDispatch();
36 1 : const storage = useStorageWriter();
37 1 : const deleteConnection = useDeleteConnection();
38 1 : const logger = useLogger();
39 :
40 1 : return React.useCallback(async (sourceId: string, targetId: string) => {
41 1 : const target = registryStore.current.connections.get(targetId);
42 1 : if (target == null || sourceId === targetId || !registryStore.current.connections.has(sourceId)) {
43 0 : return { removed: false };
44 : }
45 1 : const moving = references.sessionsOf(sourceId).length;
46 1 : await scriptsDispatch(sourceId, { type: REPLACE_CONNECTION, value: { from: sourceId, into: target } });
47 :
48 : // The sessions of the target include the sessions that moved before and whose files were not written then
49 1 : const sessionIds = new Set([...references.sessionsOf(sourceId), ...references.sessionsOf(targetId)]);
50 1 : const written = await Promise.all([...sessionIds].map(async sessionId => [sessionId, await storage.flushSession(sessionId)] as const));
51 1 : const unwritten = written.filter(([, ok]) => !ok).map(([sessionId]) => sessionId);
52 1 : const remaining = references.sessionsOf(sourceId);
53 1 : if (unwritten.length > 0 || remaining.length > 0) {
54 1 : logger.warn('kept the replaced connection', {
55 1 : connectionId: sourceId,
56 1 : target: targetId,
57 1 : unwritten: unwritten.join(','),
58 1 : remaining: remaining.join(','),
59 1 : }, LOG_CTX);
60 1 : return { removed: false };
61 1 : }
62 1 : await deleteConnection(sourceId);
63 1 : logger.info('replaced connection', { connectionId: sourceId, target: targetId, sessions: moving.toString() }, LOG_CTX);
64 1 : return { removed: !registryStore.current.connections.has(sourceId) };
65 1 : }, [deleteConnection, logger, references, registryStore, scriptsDispatch, storage]);
66 1 : }
|