Skip to content

Commit 0a9210e

Browse files
author
Akos Kitta
committed
notify client after client re-initialization.
Signed-off-by: Akos Kitta <[email protected]>
1 parent 2031523 commit 0a9210e

File tree

2 files changed

+73
-75
lines changed

2 files changed

+73
-75
lines changed

Diff for: arduino-ide-extension/src/node/core-client-provider.ts

+72-74
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,6 @@ export class CoreClientProvider {
9393
*/
9494
private async create(port: string): Promise<CoreClientProvider.Client> {
9595
this.closeClient();
96-
// Normal startup workflow:
97-
// 1. create instance,
98-
// 2. init instance,
99-
// 3. update indexes asynchronously.
10096
const address = this.address(port);
10197
const client = await this.createClient(address);
10298
this.toDisposeBeforeCreate.pushAll([
@@ -110,33 +106,46 @@ export class CoreClientProvider {
110106
this.ready = new Deferred();
111107
}),
112108
]);
109+
await this.initInstanceWithFallback(client);
110+
setTimeout(async () => this.refreshIndexes(), 10_000); // Update the indexes asynchronously
111+
return this.useClient(client);
112+
}
113+
114+
/**
115+
* By default, calling this method is equivalent to the `initInstance(Client)` call.
116+
* When the IDE2 starts and one of the followings is missing,
117+
* the IDE2 must run the index update before the core client initialization:
118+
*
119+
* - primary package index (`#directories.data/package_index.json`),
120+
* - library index (`#directories.data/library_index.json`),
121+
* - built-in tools (`builtin:serial-discovery` or `builtin:mdns-discovery`)
122+
*
123+
* This method detects such errors and runs an index update before initializing the client.
124+
* The index update will fail if the 3rd URLs list contains an invalid URL,
125+
* and the IDE2 will be [non-functional](https://github.com/arduino/arduino-ide/issues/1084). Since the CLI [cannot update only the primary package index]((https://github.com/arduino/arduino-cli/issues/1788)), IDE2 does its dirty solution.
126+
*/
127+
private async initInstanceWithFallback(
128+
client: CoreClientProvider.Client
129+
): Promise<void> {
113130
try {
114-
await this.initInstance(client); // init the gRPC core client instance
115-
setTimeout(async () => this.refreshIndexes(), 10_000); // Update the indexes asynchronously
116-
return this.useClient(client);
117-
} catch (error) {
118-
if (error instanceof IndexUpdateRequiredBeforeInitError) {
131+
await this.initInstance(client);
132+
} catch (err) {
133+
if (err instanceof IndexUpdateRequiredBeforeInitError) {
119134
console.error(
120135
'The primary packages indexes are missing. Running indexes update before initializing the core gRPC client',
121-
error.message
136+
err.message
122137
);
123-
// If it's a first start, IDE2 must run index update before the init request.
124-
// First startup workflow:
125-
// 1. create instance,
126-
// 2. update indexes and wait (to download the built-in pluggable tools, etc),
127-
// 3. init instance.
128-
await this.updateIndexes(client);
138+
await this.updateIndexes(client); // TODO: this should run without the 3rd party URLs
129139
await this.initInstance(client);
130140
console.info(
131141
`Downloaded the primary packages indexes, and successfully initialized the core gRPC client.`
132142
);
133-
return this.useClient(client);
134143
} else {
135144
console.error(
136145
'Error occurred while initializing the core gRPC client provider',
137-
error
146+
err
138147
);
139-
throw error;
148+
throw err;
140149
}
141150
}
142151
}
@@ -195,44 +204,31 @@ export class CoreClientProvider {
195204
client,
196205
instance,
197206
}: CoreClientProvider.Client): Promise<void> {
198-
const initReq = new InitRequest();
199-
initReq.setInstance(instance);
200207
return new Promise<void>((resolve, reject) => {
201-
const stream = client.init(initReq);
202208
const errors: RpcStatus[] = [];
203-
stream.on('data', (res: InitResponse) => {
204-
const progress = res.getInitProgress();
205-
if (progress) {
206-
const downloadProgress = progress.getDownloadProgress();
207-
if (downloadProgress && downloadProgress.getCompleted()) {
208-
const file = downloadProgress.getFile();
209-
console.log(`Downloaded ${file}`);
209+
client
210+
.init(new InitRequest().setInstance(instance))
211+
.on('data', (resp: InitResponse) => {
212+
// The CLI never sends `initProgress`, it's always `error` or nothing. Is this a CLI bug?
213+
// According to the gRPC API, the CLI should send either a `TaskProgress` or a `DownloadProgress`, but it does not.
214+
const error = resp.getError();
215+
if (error) {
216+
const { code, message } = Status.toObject(false, error);
217+
console.error(
218+
`Detected an error response during the gRPC core client initialization: code: ${code}, message: ${message}`
219+
);
220+
errors.push(error);
210221
}
211-
const taskProgress = progress.getTaskProgress();
212-
if (taskProgress && taskProgress.getCompleted()) {
213-
const name = taskProgress.getName();
214-
console.log(`Completed ${name}`);
222+
})
223+
.on('error', reject)
224+
.on('end', () => {
225+
const error = this.evaluateErrorStatus(errors);
226+
if (error) {
227+
reject(error);
228+
return;
215229
}
216-
}
217-
218-
const error = res.getError();
219-
if (error) {
220-
const { code, message } = Status.toObject(false, error);
221-
console.error(
222-
`Detected an error response during the gRPC core client initialization: code: ${code}, message: ${message}`
223-
);
224-
errors.push(error);
225-
}
226-
});
227-
stream.on('error', reject);
228-
stream.on('end', () => {
229-
const error = this.evaluateErrorStatus(errors);
230-
if (error) {
231-
reject(error);
232-
return;
233-
}
234-
resolve();
235-
});
230+
resolve();
231+
});
236232
});
237233
}
238234

@@ -251,32 +247,34 @@ export class CoreClientProvider {
251247
private async refreshIndexes(): Promise<void> {
252248
const client = this._client;
253249
if (client) {
254-
await this.updateIndexes(client);
255-
await this.initInstance(client);
250+
const progressHandler = this.createProgressHandler();
251+
try {
252+
await this.updateIndexes(client, progressHandler);
253+
await this.initInstance(client);
254+
// notify clients about the index update only after the client has been "re-initialized" and the new content is available.
255+
progressHandler.reportEnd();
256+
} catch (err) {
257+
console.error('Failed to update indexes', err);
258+
progressHandler.reportError(
259+
ServiceError.is(err) ? err.details : String(err)
260+
);
261+
}
256262
}
257263
}
258264

259265
private async updateIndexes(
260-
client: CoreClientProvider.Client
266+
client: CoreClientProvider.Client,
267+
progressHandler?: IndexesUpdateProgressHandler
261268
): Promise<void> {
262-
const progressHandler = this.createProgressHandler();
263-
try {
264-
await Promise.all([
265-
this.updateIndex(client, progressHandler),
266-
this.updateLibraryIndex(client, progressHandler),
267-
]);
268-
progressHandler.reportEnd();
269-
} catch (err) {
270-
console.error('Failed to update indexes', err);
271-
progressHandler.reportError(
272-
ServiceError.is(err) ? err.details : String(err)
273-
);
274-
}
269+
await Promise.all([
270+
this.updateIndex(client, progressHandler),
271+
this.updateLibraryIndex(client, progressHandler),
272+
]);
275273
}
276274

277275
private async updateIndex(
278276
client: CoreClientProvider.Client,
279-
progressHandler: IndexesUpdateProgressHandler
277+
progressHandler?: IndexesUpdateProgressHandler
280278
): Promise<void> {
281279
return this.doUpdateIndex(
282280
() =>
@@ -290,7 +288,7 @@ export class CoreClientProvider {
290288

291289
private async updateLibraryIndex(
292290
client: CoreClientProvider.Client,
293-
progressHandler: IndexesUpdateProgressHandler
291+
progressHandler?: IndexesUpdateProgressHandler
294292
): Promise<void> {
295293
return this.doUpdateIndex(
296294
() =>
@@ -309,10 +307,10 @@ export class CoreClientProvider {
309307
| UpdateCoreLibrariesIndexResponse // not used by IDE2
310308
>(
311309
responseProvider: () => grpc.ClientReadableStream<R>,
312-
progressHandler: IndexesUpdateProgressHandler,
310+
progressHandler?: IndexesUpdateProgressHandler,
313311
task?: string
314312
): Promise<void> {
315-
const { progressId } = progressHandler;
313+
const progressId = progressHandler?.progressId;
316314
return retry(
317315
() =>
318316
new Promise<void>((resolve, reject) => {
@@ -326,7 +324,7 @@ export class CoreClientProvider {
326324
`core-client-provider${task ? ` [${task}]` : ''}`,
327325
message
328326
);
329-
progressHandler.reportProgress(message);
327+
progressHandler?.reportProgress(message);
330328
},
331329
},
332330
progressId,

Diff for: arduino-ide-extension/src/node/grpc-progressible.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ export class IndexesUpdateProgressHandler {
272272
const totalPlatformIndexCount = additionalUrlsCount + 1;
273273
// The `library_index.json.gz` and `library_index.json.sig` when running the library index update.
274274
const totalLibraryIndexCount = 2;
275-
// +1 for better UX (`reportEnd`)
275+
// +1 for the `initInstance` call after the index update (`reportEnd`)
276276
return totalPlatformIndexCount + totalLibraryIndexCount + 1;
277277
}
278278
}

0 commit comments

Comments
 (0)