Various library/platform index update fixes

- IDE2 can start if the package index download fails. Closes #1084
 - Split the lib and platform index update. Closes #1156

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
Akos Kitta 2022-09-09 18:49:28 +02:00 committed by Akos Kitta
parent 945a8f4841
commit 0c20ae0e28
18 changed files with 1412 additions and 340 deletions

View File

@ -158,7 +158,11 @@
], ],
"arduino": { "arduino": {
"cli": { "cli": {
"version": "0.27.1" "version": {
"owner": "cmaglie",
"repo": "arduino-cli",
"commitish": "download_progress_refactor"
}
}, },
"fwuploader": { "fwuploader": {
"version": "2.2.0" "version": "2.2.0"

View File

@ -332,6 +332,7 @@ import { OutputEditorFactory } from './theia/output/output-editor-factory';
import { StartupTaskProvider } from '../electron-common/startup-task'; import { StartupTaskProvider } from '../electron-common/startup-task';
import { DeleteSketch } from './contributions/delete-sketch'; import { DeleteSketch } from './contributions/delete-sketch';
import { UserFields } from './contributions/user-fields'; import { UserFields } from './contributions/user-fields';
import { UpdateIndexes } from './contributions/update-indexes';
const registerArduinoThemes = () => { const registerArduinoThemes = () => {
const themes: MonacoThemeJson[] = [ const themes: MonacoThemeJson[] = [
@ -744,6 +745,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
Contribution.configure(bind, CheckForUpdates); Contribution.configure(bind, CheckForUpdates);
Contribution.configure(bind, UserFields); Contribution.configure(bind, UserFields);
Contribution.configure(bind, DeleteSketch); Contribution.configure(bind, DeleteSketch);
Contribution.configure(bind, UpdateIndexes);
bindContributionProvider(bind, StartupTaskProvider); bindContributionProvider(bind, StartupTaskProvider);
bind(StartupTaskProvider).toService(BoardsServiceProvider); // to reuse the boards config in another window bind(StartupTaskProvider).toService(BoardsServiceProvider); // to reuse the boards config in another window

View File

@ -132,7 +132,7 @@ export class BoardsConfig extends React.Component<
this.props.notificationCenter.onPlatformDidUninstall(() => this.props.notificationCenter.onPlatformDidUninstall(() =>
this.updateBoards(this.state.query) this.updateBoards(this.state.query)
), ),
this.props.notificationCenter.onIndexDidUpdate(() => this.props.notificationCenter.onIndexUpdateDidComplete(() =>
this.updateBoards(this.state.query) this.updateBoards(this.state.query)
), ),
this.props.notificationCenter.onDaemonDidStart(() => this.props.notificationCenter.onDaemonDidStart(() =>

View File

@ -16,7 +16,7 @@ export class IndexesUpdateProgress extends Contribution {
| undefined; | undefined;
override onStart(): void { override onStart(): void {
this.notificationCenter.onIndexWillUpdate((progressId) => this.notificationCenter.onIndexUpdateWillStart(({ progressId }) =>
this.getOrCreateProgress(progressId) this.getOrCreateProgress(progressId)
); );
this.notificationCenter.onIndexUpdateDidProgress((progress) => { this.notificationCenter.onIndexUpdateDidProgress((progress) => {
@ -24,7 +24,7 @@ export class IndexesUpdateProgress extends Contribution {
delegate.report(progress) delegate.report(progress)
); );
}); });
this.notificationCenter.onIndexDidUpdate((progressId) => { this.notificationCenter.onIndexUpdateDidComplete(({ progressId }) => {
this.cancelProgress(progressId); this.cancelProgress(progressId);
}); });
this.notificationCenter.onIndexUpdateDidFail(({ progressId, message }) => { this.notificationCenter.onIndexUpdateDidFail(({ progressId, message }) => {

View File

@ -0,0 +1,193 @@
import { LocalStorageService } from '@theia/core/lib/browser/storage-service';
import { nls } from '@theia/core/lib/common/nls';
import { inject, injectable } from '@theia/core/shared/inversify';
import { CoreService, IndexType } from '../../common/protocol';
import { NotificationCenter } from '../notification-center';
import { WindowServiceExt } from '../theia/core/window-service-ext';
import { Command, CommandRegistry, Contribution } from './contribution';
@injectable()
export class UpdateIndexes extends Contribution {
@inject(WindowServiceExt)
private readonly windowService: WindowServiceExt;
@inject(LocalStorageService)
private readonly localStorage: LocalStorageService;
@inject(CoreService)
private readonly coreService: CoreService;
@inject(NotificationCenter)
private readonly notificationCenter: NotificationCenter;
protected override init(): void {
super.init();
this.notificationCenter.onIndexUpdateDidComplete(({ summary }) =>
Promise.all(
Object.entries(summary).map(([type, updatedAt]) =>
this.setLastUpdateDateTime(type as IndexType, updatedAt)
)
)
);
}
override onReady(): void {
this.checkForUpdates();
}
override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(UpdateIndexes.Commands.UPDATE_INDEXES, {
execute: () => this.updateIndexes(IndexType.All, true),
});
registry.registerCommand(UpdateIndexes.Commands.UPDATE_PLATFORM_INDEX, {
execute: () => this.updateIndexes(['platform'], true),
});
registry.registerCommand(UpdateIndexes.Commands.UPDATE_LIBRARY_INDEX, {
execute: () => this.updateIndexes(['library'], true),
});
}
private async checkForUpdates(): Promise<void> {
const checkForUpdates = this.preferences['arduino.checkForUpdates'];
if (!checkForUpdates) {
console.debug(
'[update-indexes]: `arduino.checkForUpdates` is `false`. Skipping updating the indexes.'
);
return;
}
if (await this.windowService.isFirstWindow()) {
const summary = await this.coreService.indexUpdateSummaryBeforeInit();
if (summary.message) {
this.messageService.error(summary.message);
}
const typesToCheck = IndexType.All.filter((type) => !(type in summary));
if (Object.keys(summary).length) {
console.debug(
`[update-indexes]: Detected an index update summary before the core gRPC client initialization. Updating local storage with ${JSON.stringify(
summary
)}`
);
} else {
console.debug(
'[update-indexes]: No index update summary was available before the core gRPC client initialization. Checking the status of the all the index types.'
);
}
await Promise.allSettled([
...Object.entries(summary).map(([type, updatedAt]) =>
this.setLastUpdateDateTime(type as IndexType, updatedAt)
),
this.updateIndexes(typesToCheck),
]);
}
}
private async updateIndexes(
types: IndexType[],
force = false
): Promise<void> {
const updatedAt = new Date().toISOString();
return Promise.all(
types.map((type) => this.needsIndexUpdate(type, updatedAt, force))
).then((needsIndexUpdateResults) => {
const typesToUpdate = needsIndexUpdateResults.filter(IndexType.is);
if (typesToUpdate.length) {
console.debug(
`[update-indexes]: Requesting the index update of type: ${JSON.stringify(
typesToUpdate
)} with date time: ${updatedAt}.`
);
return this.coreService.updateIndex({ types: typesToUpdate });
}
});
}
private async needsIndexUpdate(
type: IndexType,
now: string,
force = false
): Promise<IndexType | false> {
if (force) {
console.debug(
`[update-indexes]: Update for index type: '${type}' was forcefully requested.`
);
return type;
}
const lastUpdateIsoDateTime = await this.getLastUpdateDateTime(type);
if (!lastUpdateIsoDateTime) {
console.debug(
`[update-indexes]: No last update date time was persisted for index type: '${type}'. Index update is required.`
);
return type;
}
const lastUpdateDateTime = Date.parse(lastUpdateIsoDateTime);
if (Number.isNaN(lastUpdateDateTime)) {
console.debug(
`[update-indexes]: Invalid last update date time was persisted for index type: '${type}'. Last update date time was: ${lastUpdateDateTime}. Index update is required.`
);
return type;
}
const diff = new Date(now).getTime() - lastUpdateDateTime;
const needsIndexUpdate = diff >= this.threshold;
console.debug(
`[update-indexes]: Update for index type '${type}' is ${
needsIndexUpdate ? '' : 'not '
}required. Now: ${now}, Last index update date time: ${new Date(
lastUpdateDateTime
).toISOString()}, diff: ${diff} ms, threshold: ${this.threshold} ms.`
);
return needsIndexUpdate ? type : false;
}
private async getLastUpdateDateTime(
type: IndexType
): Promise<string | undefined> {
const key = this.storageKeyOf(type);
return this.localStorage.getData<string>(key);
}
private async setLastUpdateDateTime(
type: IndexType,
updatedAt: string
): Promise<void> {
const key = this.storageKeyOf(type);
return this.localStorage.setData<string>(key, updatedAt).finally(() => {
console.debug(
`[update-indexes]: Updated the last index update date time of '${type}' to ${updatedAt}.`
);
});
}
private storageKeyOf(type: IndexType): string {
return `index-last-update-time--${type}`;
}
private get threshold(): number {
return 4 * 60 * 60 * 1_000; // four hours in millis
}
}
export namespace UpdateIndexes {
export namespace Commands {
export const UPDATE_INDEXES: Command & { label: string } = {
id: 'arduino-update-indexes',
label: nls.localize(
'arduino/updateIndexes/updateIndexes',
'Update Indexes'
),
category: 'Arduino',
};
export const UPDATE_PLATFORM_INDEX: Command & { label: string } = {
id: 'arduino-update-package-index',
label: nls.localize(
'arduino/updateIndexes/updatePackageIndex',
'Update Package Index'
),
category: 'Arduino',
};
export const UPDATE_LIBRARY_INDEX: Command & { label: string } = {
id: 'arduino-update-library-index',
label: nls.localize(
'arduino/updateIndexes/updateLibraryIndex',
'Update Library Index'
),
category: 'Arduino',
};
}
}

View File

@ -8,6 +8,9 @@ import { JsonRpcProxy } from '@theia/core/lib/common/messaging/proxy-factory';
import { DisposableCollection } from '@theia/core/lib/common/disposable'; import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application'; import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
import { import {
IndexUpdateDidCompleteParams,
IndexUpdateDidFailParams,
IndexUpdateWillStartParams,
NotificationServiceClient, NotificationServiceClient,
NotificationServiceServer, NotificationServiceServer,
} from '../common/protocol/notification-service'; } from '../common/protocol/notification-service';
@ -29,48 +32,48 @@ export class NotificationCenter
implements NotificationServiceClient, FrontendApplicationContribution implements NotificationServiceClient, FrontendApplicationContribution
{ {
@inject(NotificationServiceServer) @inject(NotificationServiceServer)
protected readonly server: JsonRpcProxy<NotificationServiceServer>; private readonly server: JsonRpcProxy<NotificationServiceServer>;
@inject(FrontendApplicationStateService) @inject(FrontendApplicationStateService)
private readonly appStateService: FrontendApplicationStateService; private readonly appStateService: FrontendApplicationStateService;
protected readonly indexDidUpdateEmitter = new Emitter<string>(); private readonly indexUpdateDidCompleteEmitter =
protected readonly indexWillUpdateEmitter = new Emitter<string>(); new Emitter<IndexUpdateDidCompleteParams>();
protected readonly indexUpdateDidProgressEmitter = private readonly indexUpdateWillStartEmitter =
new Emitter<IndexUpdateWillStartParams>();
private readonly indexUpdateDidProgressEmitter =
new Emitter<ProgressMessage>(); new Emitter<ProgressMessage>();
protected readonly indexUpdateDidFailEmitter = new Emitter<{ private readonly indexUpdateDidFailEmitter =
progressId: string; new Emitter<IndexUpdateDidFailParams>();
message: string; private readonly daemonDidStartEmitter = new Emitter<string>();
}>(); private readonly daemonDidStopEmitter = new Emitter<void>();
protected readonly daemonDidStartEmitter = new Emitter<string>(); private readonly configDidChangeEmitter = new Emitter<{
protected readonly daemonDidStopEmitter = new Emitter<void>();
protected readonly configDidChangeEmitter = new Emitter<{
config: Config | undefined; config: Config | undefined;
}>(); }>();
protected readonly platformDidInstallEmitter = new Emitter<{ private readonly platformDidInstallEmitter = new Emitter<{
item: BoardsPackage; item: BoardsPackage;
}>(); }>();
protected readonly platformDidUninstallEmitter = new Emitter<{ private readonly platformDidUninstallEmitter = new Emitter<{
item: BoardsPackage; item: BoardsPackage;
}>(); }>();
protected readonly libraryDidInstallEmitter = new Emitter<{ private readonly libraryDidInstallEmitter = new Emitter<{
item: LibraryPackage; item: LibraryPackage;
}>(); }>();
protected readonly libraryDidUninstallEmitter = new Emitter<{ private readonly libraryDidUninstallEmitter = new Emitter<{
item: LibraryPackage; item: LibraryPackage;
}>(); }>();
protected readonly attachedBoardsDidChangeEmitter = private readonly attachedBoardsDidChangeEmitter =
new Emitter<AttachedBoardsChangeEvent>(); new Emitter<AttachedBoardsChangeEvent>();
protected readonly recentSketchesChangedEmitter = new Emitter<{ private readonly recentSketchesChangedEmitter = new Emitter<{
sketches: Sketch[]; sketches: Sketch[];
}>(); }>();
private readonly onAppStateDidChangeEmitter = private readonly onAppStateDidChangeEmitter =
new Emitter<FrontendApplicationState>(); new Emitter<FrontendApplicationState>();
protected readonly toDispose = new DisposableCollection( private readonly toDispose = new DisposableCollection(
this.indexWillUpdateEmitter, this.indexUpdateWillStartEmitter,
this.indexUpdateDidProgressEmitter, this.indexUpdateDidProgressEmitter,
this.indexDidUpdateEmitter, this.indexUpdateDidCompleteEmitter,
this.indexUpdateDidFailEmitter, this.indexUpdateDidFailEmitter,
this.daemonDidStartEmitter, this.daemonDidStartEmitter,
this.daemonDidStopEmitter, this.daemonDidStopEmitter,
@ -82,8 +85,8 @@ export class NotificationCenter
this.attachedBoardsDidChangeEmitter this.attachedBoardsDidChangeEmitter
); );
readonly onIndexDidUpdate = this.indexDidUpdateEmitter.event; readonly onIndexUpdateDidComplete = this.indexUpdateDidCompleteEmitter.event;
readonly onIndexWillUpdate = this.indexDidUpdateEmitter.event; readonly onIndexUpdateWillStart = this.indexUpdateWillStartEmitter.event;
readonly onIndexUpdateDidProgress = this.indexUpdateDidProgressEmitter.event; readonly onIndexUpdateDidProgress = this.indexUpdateDidProgressEmitter.event;
readonly onIndexUpdateDidFail = this.indexUpdateDidFailEmitter.event; readonly onIndexUpdateDidFail = this.indexUpdateDidFailEmitter.event;
readonly onDaemonDidStart = this.daemonDidStartEmitter.event; readonly onDaemonDidStart = this.daemonDidStartEmitter.event;
@ -112,26 +115,20 @@ export class NotificationCenter
this.toDispose.dispose(); this.toDispose.dispose();
} }
notifyIndexWillUpdate(progressId: string): void { notifyIndexUpdateWillStart(params: IndexUpdateWillStartParams): void {
this.indexWillUpdateEmitter.fire(progressId); this.indexUpdateWillStartEmitter.fire(params);
} }
notifyIndexUpdateDidProgress(progressMessage: ProgressMessage): void { notifyIndexUpdateDidProgress(progressMessage: ProgressMessage): void {
this.indexUpdateDidProgressEmitter.fire(progressMessage); this.indexUpdateDidProgressEmitter.fire(progressMessage);
} }
notifyIndexDidUpdate(progressId: string): void { notifyIndexUpdateDidComplete(params: IndexUpdateDidCompleteParams): void {
this.indexDidUpdateEmitter.fire(progressId); this.indexUpdateDidCompleteEmitter.fire(params);
} }
notifyIndexUpdateDidFail({ notifyIndexUpdateDidFail(params: IndexUpdateDidFailParams): void {
progressId, this.indexUpdateDidFailEmitter.fire(params);
message,
}: {
progressId: string;
message: string;
}): void {
this.indexUpdateDidFailEmitter.fire({ progressId, message });
} }
notifyDaemonDidStart(port: string): void { notifyDaemonDidStart(port: string): void {

View File

@ -68,7 +68,7 @@ export abstract class ListWidget<
@postConstruct() @postConstruct()
protected init(): void { protected init(): void {
this.toDispose.pushAll([ this.toDispose.pushAll([
this.notificationCenter.onIndexDidUpdate(() => this.refresh(undefined)), this.notificationCenter.onIndexUpdateDidComplete(() => this.refresh(undefined)),
this.notificationCenter.onDaemonDidStart(() => this.refresh(undefined)), this.notificationCenter.onDaemonDidStart(() => this.refresh(undefined)),
this.notificationCenter.onDaemonDidStop(() => this.refresh(undefined)), this.notificationCenter.onDaemonDidStop(() => this.refresh(undefined)),
]); ]);

View File

@ -11,6 +11,7 @@ import type {
} from '../../common/protocol/boards-service'; } from '../../common/protocol/boards-service';
import type { Programmer } from './boards-service'; import type { Programmer } from './boards-service';
import type { Sketch } from './sketches-service'; import type { Sketch } from './sketches-service';
import { IndexUpdateSummary } from './notification-service';
export const CompilerWarningLiterals = [ export const CompilerWarningLiterals = [
'None', 'None',
@ -112,6 +113,33 @@ export interface CoreService {
* Refreshes the underling core gRPC client for the Arduino CLI. * Refreshes the underling core gRPC client for the Arduino CLI.
*/ */
refresh(): Promise<void>; refresh(): Promise<void>;
/**
* Updates the index of the given index types and refreshes (`init`) the underlying core gRPC client.
* If `types` is empty, only the refresh part will be executed.
*/
updateIndex({ types }: { types: IndexType[] }): Promise<void>;
/**
* If the IDE2 detects invalid or missing indexes on core client init,
* IDE2 tries to update the indexes before the first frontend connects.
* Use this method to determine whether the backend has already updated
* the indexes before updating them.
*
* If yes, the connected frontend can update the local storage with the most
* recent index update date-time for a particular index type,
* and IDE2 can avoid the double indexes update.
*/
indexUpdateSummaryBeforeInit(): Promise<Readonly<IndexUpdateSummary>>;
}
export const IndexTypeLiterals = ['platform', 'library'] as const;
export type IndexType = typeof IndexTypeLiterals[number];
export namespace IndexType {
export function is(arg: unknown): arg is IndexType {
return (
typeof arg === 'string' && IndexTypeLiterals.includes(arg as IndexType)
);
}
export const All: IndexType[] = IndexTypeLiterals.filter(is);
} }
export namespace CoreService { export namespace CoreService {

View File

@ -5,27 +5,62 @@ import type {
Config, Config,
ProgressMessage, ProgressMessage,
Sketch, Sketch,
IndexType,
} from '../protocol'; } from '../protocol';
import type { LibraryPackage } from './library-service'; import type { LibraryPackage } from './library-service';
/**
* Values are [ISO 8601](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
* strings representing the date-time when the update of the index has been completed.
*/
export type IndexUpdateSummary = {
[T in IndexType]: string;
} & { message?: string };
export interface IndexUpdateParams {
/**
* Application unique ID of the progress.
*/
readonly progressId: string;
/**
* The type of the index is which is being updated.
*/
readonly types: IndexType[];
}
export type IndexUpdateWillStartParams = IndexUpdateParams;
export interface IndexUpdateDidCompleteParams
extends Omit<IndexUpdateParams, 'types'> {
readonly summary: IndexUpdateSummary;
}
export interface IndexUpdateDidFailParams extends IndexUpdateParams {
/**
* Describes the reason of the index update failure.
*/
readonly message: string;
}
export interface NotificationServiceClient { export interface NotificationServiceClient {
notifyIndexWillUpdate(progressId: string): void; // Index
notifyIndexUpdateWillStart(params: IndexUpdateWillStartParams): void;
notifyIndexUpdateDidProgress(progressMessage: ProgressMessage): void; notifyIndexUpdateDidProgress(progressMessage: ProgressMessage): void;
notifyIndexDidUpdate(progressId: string): void; notifyIndexUpdateDidComplete(params: IndexUpdateDidCompleteParams): void;
notifyIndexUpdateDidFail({ notifyIndexUpdateDidFail(params: IndexUpdateDidFailParams): void;
progressId,
message, // Daemon
}: {
progressId: string;
message: string;
}): void;
notifyDaemonDidStart(port: string): void; notifyDaemonDidStart(port: string): void;
notifyDaemonDidStop(): void; notifyDaemonDidStop(): void;
// CLI config
notifyConfigDidChange(event: { config: Config | undefined }): void; notifyConfigDidChange(event: { config: Config | undefined }): void;
// Platforms
notifyPlatformDidInstall(event: { item: BoardsPackage }): void; notifyPlatformDidInstall(event: { item: BoardsPackage }): void;
notifyPlatformDidUninstall(event: { item: BoardsPackage }): void; notifyPlatformDidUninstall(event: { item: BoardsPackage }): void;
// Libraries
notifyLibraryDidInstall(event: { item: LibraryPackage }): void; notifyLibraryDidInstall(event: { item: LibraryPackage }): void;
notifyLibraryDidUninstall(event: { item: LibraryPackage }): void; notifyLibraryDidUninstall(event: { item: LibraryPackage }): void;
// Boards discovery
notifyAttachedBoardsDidChange(event: AttachedBoardsChangeEvent): void; notifyAttachedBoardsDidChange(event: AttachedBoardsChangeEvent): void;
notifyRecentSketchesDidChange(event: { sketches: Sketch[] }): void; notifyRecentSketchesDidChange(event: { sketches: Sketch[] }): void;
} }

View File

@ -6,7 +6,7 @@ import { deepClone } from '@theia/core/lib/common/objects';
import { Deferred } from '@theia/core/lib/common/promise-util'; import { Deferred } from '@theia/core/lib/common/promise-util';
import { BackendApplicationContribution } from '@theia/core/lib/node'; import { BackendApplicationContribution } from '@theia/core/lib/node';
import { inject, injectable, named } from '@theia/core/shared/inversify'; import { inject, injectable, named } from '@theia/core/shared/inversify';
import { Disposable } from '@theia/core/shared/vscode-languageserver-protocol'; import { Disposable } from '@theia/core/lib/common/disposable';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { Unknown } from '../common/nls'; import { Unknown } from '../common/nls';
import { import {
@ -78,14 +78,6 @@ export class BoardDiscovery
onStart(): void { onStart(): void {
this.start(); this.start();
this.onClientDidRefresh(() => this.restart());
}
private async restart(): Promise<void> {
this.logger.info('restarting before stop');
await this.stop();
this.logger.info('restarting after stop');
return this.start();
} }
onStop(): void { onStop(): void {

View File

@ -220,6 +220,9 @@ export class UpdateIndexRequest extends jspb.Message {
getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined; getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined;
setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): UpdateIndexRequest; setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): UpdateIndexRequest;
getIgnoreCustomPackageIndexes(): boolean;
setIgnoreCustomPackageIndexes(value: boolean): UpdateIndexRequest;
serializeBinary(): Uint8Array; serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): UpdateIndexRequest.AsObject; toObject(includeInstance?: boolean): UpdateIndexRequest.AsObject;
@ -234,6 +237,7 @@ export class UpdateIndexRequest extends jspb.Message {
export namespace UpdateIndexRequest { export namespace UpdateIndexRequest {
export type AsObject = { export type AsObject = {
instance?: cc_arduino_cli_commands_v1_common_pb.Instance.AsObject, instance?: cc_arduino_cli_commands_v1_common_pb.Instance.AsObject,
ignoreCustomPackageIndexes: boolean,
} }
} }

View File

@ -1811,7 +1811,8 @@ proto.cc.arduino.cli.commands.v1.UpdateIndexRequest.prototype.toObject = functio
*/ */
proto.cc.arduino.cli.commands.v1.UpdateIndexRequest.toObject = function(includeInstance, msg) { proto.cc.arduino.cli.commands.v1.UpdateIndexRequest.toObject = function(includeInstance, msg) {
var f, obj = { var f, obj = {
instance: (f = msg.getInstance()) && cc_arduino_cli_commands_v1_common_pb.Instance.toObject(includeInstance, f) instance: (f = msg.getInstance()) && cc_arduino_cli_commands_v1_common_pb.Instance.toObject(includeInstance, f),
ignoreCustomPackageIndexes: jspb.Message.getBooleanFieldWithDefault(msg, 2, false)
}; };
if (includeInstance) { if (includeInstance) {
@ -1853,6 +1854,10 @@ proto.cc.arduino.cli.commands.v1.UpdateIndexRequest.deserializeBinaryFromReader
reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.Instance.deserializeBinaryFromReader); reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.Instance.deserializeBinaryFromReader);
msg.setInstance(value); msg.setInstance(value);
break; break;
case 2:
var value = /** @type {boolean} */ (reader.readBool());
msg.setIgnoreCustomPackageIndexes(value);
break;
default: default:
reader.skipField(); reader.skipField();
break; break;
@ -1890,6 +1895,13 @@ proto.cc.arduino.cli.commands.v1.UpdateIndexRequest.serializeBinaryToWriter = fu
cc_arduino_cli_commands_v1_common_pb.Instance.serializeBinaryToWriter cc_arduino_cli_commands_v1_common_pb.Instance.serializeBinaryToWriter
); );
} }
f = message.getIgnoreCustomPackageIndexes();
if (f) {
writer.writeBool(
2,
f
);
}
}; };
@ -1930,6 +1942,24 @@ proto.cc.arduino.cli.commands.v1.UpdateIndexRequest.prototype.hasInstance = func
}; };
/**
* optional bool ignore_custom_package_indexes = 2;
* @return {boolean}
*/
proto.cc.arduino.cli.commands.v1.UpdateIndexRequest.prototype.getIgnoreCustomPackageIndexes = function() {
return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 2, false));
};
/**
* @param {boolean} value
* @return {!proto.cc.arduino.cli.commands.v1.UpdateIndexRequest} returns this
*/
proto.cc.arduino.cli.commands.v1.UpdateIndexRequest.prototype.setIgnoreCustomPackageIndexes = function(value) {
return jspb.Message.setProto3BooleanField(this, 2, value);
};

View File

@ -28,21 +28,26 @@ export namespace Instance {
} }
export class DownloadProgress extends jspb.Message { export class DownloadProgress extends jspb.Message {
getUrl(): string;
setUrl(value: string): DownloadProgress;
getFile(): string; hasStart(): boolean;
setFile(value: string): DownloadProgress; clearStart(): void;
getStart(): DownloadProgressStart | undefined;
setStart(value?: DownloadProgressStart): DownloadProgress;
getTotalSize(): number;
setTotalSize(value: number): DownloadProgress;
getDownloaded(): number; hasUpdate(): boolean;
setDownloaded(value: number): DownloadProgress; clearUpdate(): void;
getUpdate(): DownloadProgressUpdate | undefined;
setUpdate(value?: DownloadProgressUpdate): DownloadProgress;
getCompleted(): boolean;
setCompleted(value: boolean): DownloadProgress;
hasEnd(): boolean;
clearEnd(): void;
getEnd(): DownloadProgressEnd | undefined;
setEnd(value?: DownloadProgressEnd): DownloadProgress;
getMessageCase(): DownloadProgress.MessageCase;
serializeBinary(): Uint8Array; serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): DownloadProgress.AsObject; toObject(includeInstance?: boolean): DownloadProgress.AsObject;
@ -55,12 +60,97 @@ export class DownloadProgress extends jspb.Message {
} }
export namespace DownloadProgress { export namespace DownloadProgress {
export type AsObject = {
start?: DownloadProgressStart.AsObject,
update?: DownloadProgressUpdate.AsObject,
end?: DownloadProgressEnd.AsObject,
}
export enum MessageCase {
MESSAGE_NOT_SET = 0,
START = 1,
UPDATE = 2,
END = 3,
}
}
export class DownloadProgressStart extends jspb.Message {
getUrl(): string;
setUrl(value: string): DownloadProgressStart;
getLabel(): string;
setLabel(value: string): DownloadProgressStart;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): DownloadProgressStart.AsObject;
static toObject(includeInstance: boolean, msg: DownloadProgressStart): DownloadProgressStart.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: DownloadProgressStart, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): DownloadProgressStart;
static deserializeBinaryFromReader(message: DownloadProgressStart, reader: jspb.BinaryReader): DownloadProgressStart;
}
export namespace DownloadProgressStart {
export type AsObject = { export type AsObject = {
url: string, url: string,
file: string, label: string,
totalSize: number, }
}
export class DownloadProgressUpdate extends jspb.Message {
getDownloaded(): number;
setDownloaded(value: number): DownloadProgressUpdate;
getTotalSize(): number;
setTotalSize(value: number): DownloadProgressUpdate;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): DownloadProgressUpdate.AsObject;
static toObject(includeInstance: boolean, msg: DownloadProgressUpdate): DownloadProgressUpdate.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: DownloadProgressUpdate, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): DownloadProgressUpdate;
static deserializeBinaryFromReader(message: DownloadProgressUpdate, reader: jspb.BinaryReader): DownloadProgressUpdate;
}
export namespace DownloadProgressUpdate {
export type AsObject = {
downloaded: number, downloaded: number,
completed: boolean, totalSize: number,
}
}
export class DownloadProgressEnd extends jspb.Message {
getSuccess(): boolean;
setSuccess(value: boolean): DownloadProgressEnd;
getMessage(): string;
setMessage(value: string): DownloadProgressEnd;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): DownloadProgressEnd.AsObject;
static toObject(includeInstance: boolean, msg: DownloadProgressEnd): DownloadProgressEnd.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: DownloadProgressEnd, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): DownloadProgressEnd;
static deserializeBinaryFromReader(message: DownloadProgressEnd, reader: jspb.BinaryReader): DownloadProgressEnd;
}
export namespace DownloadProgressEnd {
export type AsObject = {
success: boolean,
message: string,
} }
} }

View File

@ -17,6 +17,10 @@ var global = Function('return this')();
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Board', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Board', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.DownloadProgress', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.DownloadProgress', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.DownloadProgress.MessageCase', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.DownloadProgressEnd', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.DownloadProgressStart', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.InstalledPlatformReference', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.InstalledPlatformReference', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Instance', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Instance', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Platform', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Platform', null, global);
@ -55,7 +59,7 @@ if (goog.DEBUG && !COMPILED) {
* @constructor * @constructor
*/ */
proto.cc.arduino.cli.commands.v1.DownloadProgress = function(opt_data) { proto.cc.arduino.cli.commands.v1.DownloadProgress = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null); jspb.Message.initialize(this, opt_data, 0, -1, null, proto.cc.arduino.cli.commands.v1.DownloadProgress.oneofGroups_);
}; };
goog.inherits(proto.cc.arduino.cli.commands.v1.DownloadProgress, jspb.Message); goog.inherits(proto.cc.arduino.cli.commands.v1.DownloadProgress, jspb.Message);
if (goog.DEBUG && !COMPILED) { if (goog.DEBUG && !COMPILED) {
@ -65,6 +69,69 @@ if (goog.DEBUG && !COMPILED) {
*/ */
proto.cc.arduino.cli.commands.v1.DownloadProgress.displayName = 'proto.cc.arduino.cli.commands.v1.DownloadProgress'; proto.cc.arduino.cli.commands.v1.DownloadProgress.displayName = 'proto.cc.arduino.cli.commands.v1.DownloadProgress';
} }
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.cc.arduino.cli.commands.v1.DownloadProgressStart = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.cc.arduino.cli.commands.v1.DownloadProgressStart, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.cc.arduino.cli.commands.v1.DownloadProgressStart.displayName = 'proto.cc.arduino.cli.commands.v1.DownloadProgressStart';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate.displayName = 'proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.cc.arduino.cli.commands.v1.DownloadProgressEnd = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.cc.arduino.cli.commands.v1.DownloadProgressEnd, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.cc.arduino.cli.commands.v1.DownloadProgressEnd.displayName = 'proto.cc.arduino.cli.commands.v1.DownloadProgressEnd';
}
/** /**
* Generated by JsPbCodeGenerator. * Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a * @param {Array=} opt_data Optional initial data array, typically from a
@ -322,6 +389,33 @@ proto.cc.arduino.cli.commands.v1.Instance.prototype.setId = function(value) {
/**
* Oneof group definitions for this message. Each group defines the field
* numbers belonging to that group. When of these fields' value is set, all
* other fields in the group are cleared. During deserialization, if multiple
* fields are encountered for a group, only the last value seen will be kept.
* @private {!Array<!Array<number>>}
* @const
*/
proto.cc.arduino.cli.commands.v1.DownloadProgress.oneofGroups_ = [[1,2,3]];
/**
* @enum {number}
*/
proto.cc.arduino.cli.commands.v1.DownloadProgress.MessageCase = {
MESSAGE_NOT_SET: 0,
START: 1,
UPDATE: 2,
END: 3
};
/**
* @return {proto.cc.arduino.cli.commands.v1.DownloadProgress.MessageCase}
*/
proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.getMessageCase = function() {
return /** @type {proto.cc.arduino.cli.commands.v1.DownloadProgress.MessageCase} */(jspb.Message.computeOneofCase(this, proto.cc.arduino.cli.commands.v1.DownloadProgress.oneofGroups_[0]));
};
if (jspb.Message.GENERATE_TO_OBJECT) { if (jspb.Message.GENERATE_TO_OBJECT) {
@ -353,11 +447,9 @@ proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.toObject = function(
*/ */
proto.cc.arduino.cli.commands.v1.DownloadProgress.toObject = function(includeInstance, msg) { proto.cc.arduino.cli.commands.v1.DownloadProgress.toObject = function(includeInstance, msg) {
var f, obj = { var f, obj = {
url: jspb.Message.getFieldWithDefault(msg, 1, ""), start: (f = msg.getStart()) && proto.cc.arduino.cli.commands.v1.DownloadProgressStart.toObject(includeInstance, f),
file: jspb.Message.getFieldWithDefault(msg, 2, ""), update: (f = msg.getUpdate()) && proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate.toObject(includeInstance, f),
totalSize: jspb.Message.getFieldWithDefault(msg, 3, 0), end: (f = msg.getEnd()) && proto.cc.arduino.cli.commands.v1.DownloadProgressEnd.toObject(includeInstance, f)
downloaded: jspb.Message.getFieldWithDefault(msg, 4, 0),
completed: jspb.Message.getBooleanFieldWithDefault(msg, 5, false)
}; };
if (includeInstance) { if (includeInstance) {
@ -395,24 +487,19 @@ proto.cc.arduino.cli.commands.v1.DownloadProgress.deserializeBinaryFromReader =
var field = reader.getFieldNumber(); var field = reader.getFieldNumber();
switch (field) { switch (field) {
case 1: case 1:
var value = /** @type {string} */ (reader.readString()); var value = new proto.cc.arduino.cli.commands.v1.DownloadProgressStart;
msg.setUrl(value); reader.readMessage(value,proto.cc.arduino.cli.commands.v1.DownloadProgressStart.deserializeBinaryFromReader);
msg.setStart(value);
break; break;
case 2: case 2:
var value = /** @type {string} */ (reader.readString()); var value = new proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate;
msg.setFile(value); reader.readMessage(value,proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate.deserializeBinaryFromReader);
msg.setUpdate(value);
break; break;
case 3: case 3:
var value = /** @type {number} */ (reader.readInt64()); var value = new proto.cc.arduino.cli.commands.v1.DownloadProgressEnd;
msg.setTotalSize(value); reader.readMessage(value,proto.cc.arduino.cli.commands.v1.DownloadProgressEnd.deserializeBinaryFromReader);
break; msg.setEnd(value);
case 4:
var value = /** @type {number} */ (reader.readInt64());
msg.setDownloaded(value);
break;
case 5:
var value = /** @type {boolean} */ (reader.readBool());
msg.setCompleted(value);
break; break;
default: default:
reader.skipField(); reader.skipField();
@ -442,6 +529,251 @@ proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.serializeBinary = fu
* @suppress {unusedLocalVariables} f is only used for nested messages * @suppress {unusedLocalVariables} f is only used for nested messages
*/ */
proto.cc.arduino.cli.commands.v1.DownloadProgress.serializeBinaryToWriter = function(message, writer) { proto.cc.arduino.cli.commands.v1.DownloadProgress.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getStart();
if (f != null) {
writer.writeMessage(
1,
f,
proto.cc.arduino.cli.commands.v1.DownloadProgressStart.serializeBinaryToWriter
);
}
f = message.getUpdate();
if (f != null) {
writer.writeMessage(
2,
f,
proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate.serializeBinaryToWriter
);
}
f = message.getEnd();
if (f != null) {
writer.writeMessage(
3,
f,
proto.cc.arduino.cli.commands.v1.DownloadProgressEnd.serializeBinaryToWriter
);
}
};
/**
* optional DownloadProgressStart start = 1;
* @return {?proto.cc.arduino.cli.commands.v1.DownloadProgressStart}
*/
proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.getStart = function() {
return /** @type{?proto.cc.arduino.cli.commands.v1.DownloadProgressStart} */ (
jspb.Message.getWrapperField(this, proto.cc.arduino.cli.commands.v1.DownloadProgressStart, 1));
};
/**
* @param {?proto.cc.arduino.cli.commands.v1.DownloadProgressStart|undefined} value
* @return {!proto.cc.arduino.cli.commands.v1.DownloadProgress} returns this
*/
proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.setStart = function(value) {
return jspb.Message.setOneofWrapperField(this, 1, proto.cc.arduino.cli.commands.v1.DownloadProgress.oneofGroups_[0], value);
};
/**
* Clears the message field making it undefined.
* @return {!proto.cc.arduino.cli.commands.v1.DownloadProgress} returns this
*/
proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.clearStart = function() {
return this.setStart(undefined);
};
/**
* Returns whether this field is set.
* @return {boolean}
*/
proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.hasStart = function() {
return jspb.Message.getField(this, 1) != null;
};
/**
* optional DownloadProgressUpdate update = 2;
* @return {?proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate}
*/
proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.getUpdate = function() {
return /** @type{?proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate} */ (
jspb.Message.getWrapperField(this, proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate, 2));
};
/**
* @param {?proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate|undefined} value
* @return {!proto.cc.arduino.cli.commands.v1.DownloadProgress} returns this
*/
proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.setUpdate = function(value) {
return jspb.Message.setOneofWrapperField(this, 2, proto.cc.arduino.cli.commands.v1.DownloadProgress.oneofGroups_[0], value);
};
/**
* Clears the message field making it undefined.
* @return {!proto.cc.arduino.cli.commands.v1.DownloadProgress} returns this
*/
proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.clearUpdate = function() {
return this.setUpdate(undefined);
};
/**
* Returns whether this field is set.
* @return {boolean}
*/
proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.hasUpdate = function() {
return jspb.Message.getField(this, 2) != null;
};
/**
* optional DownloadProgressEnd end = 3;
* @return {?proto.cc.arduino.cli.commands.v1.DownloadProgressEnd}
*/
proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.getEnd = function() {
return /** @type{?proto.cc.arduino.cli.commands.v1.DownloadProgressEnd} */ (
jspb.Message.getWrapperField(this, proto.cc.arduino.cli.commands.v1.DownloadProgressEnd, 3));
};
/**
* @param {?proto.cc.arduino.cli.commands.v1.DownloadProgressEnd|undefined} value
* @return {!proto.cc.arduino.cli.commands.v1.DownloadProgress} returns this
*/
proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.setEnd = function(value) {
return jspb.Message.setOneofWrapperField(this, 3, proto.cc.arduino.cli.commands.v1.DownloadProgress.oneofGroups_[0], value);
};
/**
* Clears the message field making it undefined.
* @return {!proto.cc.arduino.cli.commands.v1.DownloadProgress} returns this
*/
proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.clearEnd = function() {
return this.setEnd(undefined);
};
/**
* Returns whether this field is set.
* @return {boolean}
*/
proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.hasEnd = function() {
return jspb.Message.getField(this, 3) != null;
};
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.cc.arduino.cli.commands.v1.DownloadProgressStart.prototype.toObject = function(opt_includeInstance) {
return proto.cc.arduino.cli.commands.v1.DownloadProgressStart.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.cc.arduino.cli.commands.v1.DownloadProgressStart} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.cc.arduino.cli.commands.v1.DownloadProgressStart.toObject = function(includeInstance, msg) {
var f, obj = {
url: jspb.Message.getFieldWithDefault(msg, 1, ""),
label: jspb.Message.getFieldWithDefault(msg, 2, "")
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.cc.arduino.cli.commands.v1.DownloadProgressStart}
*/
proto.cc.arduino.cli.commands.v1.DownloadProgressStart.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.cc.arduino.cli.commands.v1.DownloadProgressStart;
return proto.cc.arduino.cli.commands.v1.DownloadProgressStart.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.cc.arduino.cli.commands.v1.DownloadProgressStart} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.cc.arduino.cli.commands.v1.DownloadProgressStart}
*/
proto.cc.arduino.cli.commands.v1.DownloadProgressStart.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {string} */ (reader.readString());
msg.setUrl(value);
break;
case 2:
var value = /** @type {string} */ (reader.readString());
msg.setLabel(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.cc.arduino.cli.commands.v1.DownloadProgressStart.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.cc.arduino.cli.commands.v1.DownloadProgressStart.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.cc.arduino.cli.commands.v1.DownloadProgressStart} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.cc.arduino.cli.commands.v1.DownloadProgressStart.serializeBinaryToWriter = function(message, writer) {
var f = undefined; var f = undefined;
f = message.getUrl(); f = message.getUrl();
if (f.length > 0) { if (f.length > 0) {
@ -450,34 +782,13 @@ proto.cc.arduino.cli.commands.v1.DownloadProgress.serializeBinaryToWriter = func
f f
); );
} }
f = message.getFile(); f = message.getLabel();
if (f.length > 0) { if (f.length > 0) {
writer.writeString( writer.writeString(
2, 2,
f f
); );
} }
f = message.getTotalSize();
if (f !== 0) {
writer.writeInt64(
3,
f
);
}
f = message.getDownloaded();
if (f !== 0) {
writer.writeInt64(
4,
f
);
}
f = message.getCompleted();
if (f) {
writer.writeBool(
5,
f
);
}
}; };
@ -485,89 +796,355 @@ proto.cc.arduino.cli.commands.v1.DownloadProgress.serializeBinaryToWriter = func
* optional string url = 1; * optional string url = 1;
* @return {string} * @return {string}
*/ */
proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.getUrl = function() { proto.cc.arduino.cli.commands.v1.DownloadProgressStart.prototype.getUrl = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
}; };
/** /**
* @param {string} value * @param {string} value
* @return {!proto.cc.arduino.cli.commands.v1.DownloadProgress} returns this * @return {!proto.cc.arduino.cli.commands.v1.DownloadProgressStart} returns this
*/ */
proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.setUrl = function(value) { proto.cc.arduino.cli.commands.v1.DownloadProgressStart.prototype.setUrl = function(value) {
return jspb.Message.setProto3StringField(this, 1, value); return jspb.Message.setProto3StringField(this, 1, value);
}; };
/** /**
* optional string file = 2; * optional string label = 2;
* @return {string} * @return {string}
*/ */
proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.getFile = function() { proto.cc.arduino.cli.commands.v1.DownloadProgressStart.prototype.getLabel = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
}; };
/** /**
* @param {string} value * @param {string} value
* @return {!proto.cc.arduino.cli.commands.v1.DownloadProgress} returns this * @return {!proto.cc.arduino.cli.commands.v1.DownloadProgressStart} returns this
*/ */
proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.setFile = function(value) { proto.cc.arduino.cli.commands.v1.DownloadProgressStart.prototype.setLabel = function(value) {
return jspb.Message.setProto3StringField(this, 2, value); return jspb.Message.setProto3StringField(this, 2, value);
}; };
if (jspb.Message.GENERATE_TO_OBJECT) {
/** /**
* optional int64 total_size = 3; * Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate.prototype.toObject = function(opt_includeInstance) {
return proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate.toObject = function(includeInstance, msg) {
var f, obj = {
downloaded: jspb.Message.getFieldWithDefault(msg, 1, 0),
totalSize: jspb.Message.getFieldWithDefault(msg, 2, 0)
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate}
*/
proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate;
return proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate}
*/
proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {number} */ (reader.readInt64());
msg.setDownloaded(value);
break;
case 2:
var value = /** @type {number} */ (reader.readInt64());
msg.setTotalSize(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getDownloaded();
if (f !== 0) {
writer.writeInt64(
1,
f
);
}
f = message.getTotalSize();
if (f !== 0) {
writer.writeInt64(
2,
f
);
}
};
/**
* optional int64 downloaded = 1;
* @return {number} * @return {number}
*/ */
proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.getTotalSize = function() { proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate.prototype.getDownloaded = function() {
return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 3, 0)); return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0));
}; };
/** /**
* @param {number} value * @param {number} value
* @return {!proto.cc.arduino.cli.commands.v1.DownloadProgress} returns this * @return {!proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate} returns this
*/ */
proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.setTotalSize = function(value) { proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate.prototype.setDownloaded = function(value) {
return jspb.Message.setProto3IntField(this, 3, value); return jspb.Message.setProto3IntField(this, 1, value);
}; };
/** /**
* optional int64 downloaded = 4; * optional int64 total_size = 2;
* @return {number} * @return {number}
*/ */
proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.getDownloaded = function() { proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate.prototype.getTotalSize = function() {
return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 4, 0)); return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 2, 0));
}; };
/** /**
* @param {number} value * @param {number} value
* @return {!proto.cc.arduino.cli.commands.v1.DownloadProgress} returns this * @return {!proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate} returns this
*/ */
proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.setDownloaded = function(value) { proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate.prototype.setTotalSize = function(value) {
return jspb.Message.setProto3IntField(this, 4, value); return jspb.Message.setProto3IntField(this, 2, value);
};
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.cc.arduino.cli.commands.v1.DownloadProgressEnd.prototype.toObject = function(opt_includeInstance) {
return proto.cc.arduino.cli.commands.v1.DownloadProgressEnd.toObject(opt_includeInstance, this);
}; };
/** /**
* optional bool completed = 5; * Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.cc.arduino.cli.commands.v1.DownloadProgressEnd} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.cc.arduino.cli.commands.v1.DownloadProgressEnd.toObject = function(includeInstance, msg) {
var f, obj = {
success: jspb.Message.getBooleanFieldWithDefault(msg, 1, false),
message: jspb.Message.getFieldWithDefault(msg, 2, "")
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.cc.arduino.cli.commands.v1.DownloadProgressEnd}
*/
proto.cc.arduino.cli.commands.v1.DownloadProgressEnd.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.cc.arduino.cli.commands.v1.DownloadProgressEnd;
return proto.cc.arduino.cli.commands.v1.DownloadProgressEnd.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.cc.arduino.cli.commands.v1.DownloadProgressEnd} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.cc.arduino.cli.commands.v1.DownloadProgressEnd}
*/
proto.cc.arduino.cli.commands.v1.DownloadProgressEnd.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {boolean} */ (reader.readBool());
msg.setSuccess(value);
break;
case 2:
var value = /** @type {string} */ (reader.readString());
msg.setMessage(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.cc.arduino.cli.commands.v1.DownloadProgressEnd.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.cc.arduino.cli.commands.v1.DownloadProgressEnd.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.cc.arduino.cli.commands.v1.DownloadProgressEnd} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.cc.arduino.cli.commands.v1.DownloadProgressEnd.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getSuccess();
if (f) {
writer.writeBool(
1,
f
);
}
f = message.getMessage();
if (f.length > 0) {
writer.writeString(
2,
f
);
}
};
/**
* optional bool success = 1;
* @return {boolean} * @return {boolean}
*/ */
proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.getCompleted = function() { proto.cc.arduino.cli.commands.v1.DownloadProgressEnd.prototype.getSuccess = function() {
return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 5, false)); return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 1, false));
}; };
/** /**
* @param {boolean} value * @param {boolean} value
* @return {!proto.cc.arduino.cli.commands.v1.DownloadProgress} returns this * @return {!proto.cc.arduino.cli.commands.v1.DownloadProgressEnd} returns this
*/ */
proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.setCompleted = function(value) { proto.cc.arduino.cli.commands.v1.DownloadProgressEnd.prototype.setSuccess = function(value) {
return jspb.Message.setProto3BooleanField(this, 5, value); return jspb.Message.setProto3BooleanField(this, 1, value);
};
/**
* optional string message = 2;
* @return {string}
*/
proto.cc.arduino.cli.commands.v1.DownloadProgressEnd.prototype.getMessage = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
};
/**
* @param {string} value
* @return {!proto.cc.arduino.cli.commands.v1.DownloadProgressEnd} returns this
*/
proto.cc.arduino.cli.commands.v1.DownloadProgressEnd.prototype.setMessage = function(value) {
return jspb.Message.setProto3StringField(this, 2, value);
}; };

View File

@ -5,7 +5,7 @@ import {
injectable, injectable,
postConstruct, postConstruct,
} from '@theia/core/shared/inversify'; } from '@theia/core/shared/inversify';
import { Emitter, Event } from '@theia/core/lib/common/event'; import { Emitter } from '@theia/core/lib/common/event';
import { ArduinoCoreServiceClient } from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb'; import { ArduinoCoreServiceClient } from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb';
import { Instance } from './cli-protocol/cc/arduino/cli/commands/v1/common_pb'; import { Instance } from './cli-protocol/cc/arduino/cli/commands/v1/common_pb';
import { import {
@ -19,8 +19,15 @@ import {
UpdateLibrariesIndexResponse, UpdateLibrariesIndexResponse,
} from './cli-protocol/cc/arduino/cli/commands/v1/commands_pb'; } from './cli-protocol/cc/arduino/cli/commands/v1/commands_pb';
import * as commandsGrpcPb from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb'; import * as commandsGrpcPb from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb';
import { NotificationServiceServer } from '../common/protocol'; import {
import { Deferred, retry } from '@theia/core/lib/common/promise-util'; IndexType,
IndexUpdateDidCompleteParams,
IndexUpdateSummary,
IndexUpdateDidFailParams,
IndexUpdateWillStartParams,
NotificationServiceServer,
} from '../common/protocol';
import { Deferred } from '@theia/core/lib/common/promise-util';
import { import {
Status as RpcStatus, Status as RpcStatus,
Status, Status,
@ -32,6 +39,7 @@ import { Disposable } from '@theia/core/shared/vscode-languageserver-protocol';
import { import {
IndexesUpdateProgressHandler, IndexesUpdateProgressHandler,
ExecuteWithProgress, ExecuteWithProgress,
DownloadResult,
} from './grpc-progressible'; } from './grpc-progressible';
import type { DefaultCliConfig } from './cli-config'; import type { DefaultCliConfig } from './cli-config';
import { ServiceError } from './service-error'; import { ServiceError } from './service-error';
@ -45,16 +53,19 @@ export class CoreClientProvider {
@inject(NotificationServiceServer) @inject(NotificationServiceServer)
private readonly notificationService: NotificationServiceServer; private readonly notificationService: NotificationServiceServer;
private ready = new Deferred<void>(); /**
private pending: Deferred<CoreClientProvider.Client> | undefined; * See `CoreService#indexUpdateSummaryBeforeInit`.
private _client: CoreClientProvider.Client | undefined; */
private readonly toDisposeBeforeCreate = new DisposableCollection(); private readonly beforeInitSummary = {} as IndexUpdateSummary;
private readonly toDisposeOnCloseClient = new DisposableCollection();
private readonly toDisposeAfterDidCreate = new DisposableCollection(); private readonly toDisposeAfterDidCreate = new DisposableCollection();
private readonly onClientReadyEmitter = private readonly onClientReadyEmitter =
new Emitter<CoreClientProvider.Client>(); new Emitter<CoreClientProvider.Client>();
private readonly onClientReady = this.onClientReadyEmitter.event; private readonly onClientReady = this.onClientReadyEmitter.event;
private readonly onClientDidRefreshEmitter =
new Emitter<CoreClientProvider.Client>(); private ready = new Deferred<void>();
private pending: Deferred<CoreClientProvider.Client> | undefined;
private _client: CoreClientProvider.Client | undefined;
@postConstruct() @postConstruct()
protected init(): void { protected init(): void {
@ -65,7 +76,9 @@ export class CoreClientProvider {
}); });
this.daemon.onDaemonStarted((port) => this.create(port)); this.daemon.onDaemonStarted((port) => this.create(port));
this.daemon.onDaemonStopped(() => this.closeClient()); this.daemon.onDaemonStopped(() => this.closeClient());
this.configService.onConfigChange(() => this.refreshIndexes()); this.configService.onConfigChange(
() => this.client.then((client) => this.updateIndex(client, ['platform'])) // Assuming 3rd party URL changes. No library index update is required.
);
} }
get tryGetClient(): CoreClientProvider.Client | undefined { get tryGetClient(): CoreClientProvider.Client | undefined {
@ -80,7 +93,7 @@ export class CoreClientProvider {
if (!this.pending) { if (!this.pending) {
this.pending = new Deferred(); this.pending = new Deferred();
this.toDisposeAfterDidCreate.pushAll([ this.toDisposeAfterDidCreate.pushAll([
Disposable.create(() => (this.pending = undefined)), Disposable.create(() => (this.pending = undefined)), // TODO: reject all pending requests before unsetting the ref?
this.onClientReady((client) => { this.onClientReady((client) => {
this.pending?.resolve(client); this.pending?.resolve(client);
this.toDisposeAfterDidCreate.dispose(); this.toDisposeAfterDidCreate.dispose();
@ -90,10 +103,6 @@ export class CoreClientProvider {
return this.pending.promise; return this.pending.promise;
} }
get onClientDidRefresh(): Event<CoreClientProvider.Client> {
return this.onClientDidRefreshEmitter.event;
}
async refresh(): Promise<void> { async refresh(): Promise<void> {
const client = await this.client; const client = await this.client;
await this.initInstance(client); await this.initInstance(client);
@ -106,7 +115,7 @@ export class CoreClientProvider {
this.closeClient(); this.closeClient();
const address = this.address(port); const address = this.address(port);
const client = await this.createClient(address); const client = await this.createClient(address);
this.toDisposeBeforeCreate.pushAll([ this.toDisposeOnCloseClient.pushAll([
Disposable.create(() => client.client.close()), Disposable.create(() => client.client.close()),
Disposable.create(() => { Disposable.create(() => {
this.ready.reject( this.ready.reject(
@ -118,7 +127,6 @@ export class CoreClientProvider {
}), }),
]); ]);
await this.initInstanceWithFallback(client); await this.initInstanceWithFallback(client);
setTimeout(async () => this.refreshIndexes(), 10_000); // Update the indexes asynchronously
return this.useClient(client); return this.useClient(client);
} }
@ -141,12 +149,17 @@ export class CoreClientProvider {
try { try {
await this.initInstance(client); await this.initInstance(client);
} catch (err) { } catch (err) {
if (err instanceof IndexUpdateRequiredBeforeInitError) { if (err instanceof MustUpdateIndexesBeforeInitError) {
console.error( console.error(
'The primary packages indexes are missing. Running indexes update before initializing the core gRPC client', 'The primary packages indexes are missing. Running indexes update before initializing the core gRPC client',
err.message err.message
); );
await this.updateIndexes(client); // TODO: this should run without the 3rd party URLs await this.updateIndex(client, Array.from(err.indexTypesToUpdate));
const updatedAt = new Date().toISOString();
// Clients will ask for it after they connect.
err.indexTypesToUpdate.forEach(
(type) => (this.beforeInitSummary[type] = updatedAt)
);
await this.initInstance(client); await this.initInstance(client);
console.info( console.info(
`Downloaded the primary package indexes, and successfully initialized the core gRPC client.` `Downloaded the primary package indexes, and successfully initialized the core gRPC client.`
@ -170,7 +183,7 @@ export class CoreClientProvider {
} }
private closeClient(): void { private closeClient(): void {
return this.toDisposeBeforeCreate.dispose(); return this.toDisposeOnCloseClient.dispose();
} }
private async createClient( private async createClient(
@ -253,45 +266,66 @@ export class CoreClientProvider {
} }
/** /**
* Updates all indexes and runs an init to [reload the indexes](https://github.com/arduino/arduino-cli/pull/1274#issue-866154638). * `update3rdPartyPlatforms` has not effect if `types` is `['library']`.
*/ */
private async refreshIndexes(): Promise<void> { async updateIndex(
const client = this._client; client: CoreClientProvider.Client,
if (client) { types: IndexType[]
const progressHandler = this.createProgressHandler(); ): Promise<void> {
try { let error: unknown | undefined = undefined;
await this.updateIndexes(client, progressHandler); const progressHandler = this.createProgressHandler(types);
try {
const updates: Promise<void>[] = [];
if (types.includes('platform')) {
updates.push(this.updatePlatformIndex(client, progressHandler));
}
if (types.includes('library')) {
updates.push(this.updateLibraryIndex(client, progressHandler));
}
await Promise.all(updates);
} catch (err) {
// This is suboptimal but the core client must be re-initialized even if the index update has failed and the request was rejected.
error = err;
} finally {
// IDE2 reloads the index only and if only at least one download success is available.
if (
progressHandler.results.some(
(result) => !DownloadResult.isError(result)
)
) {
await this.initInstance(client); await this.initInstance(client);
// notify clients about the index update only after the client has been "re-initialized" and the new content is available. // notify clients about the index update only after the client has been "re-initialized" and the new content is available.
progressHandler.reportEnd(); progressHandler.reportEnd();
this.onClientDidRefreshEmitter.fire(client); }
} catch (err) { if (error) {
console.error('Failed to update indexes', err); console.error(`Failed to update ${types.join(', ')} indexes.`, error);
progressHandler.reportError( const downloadErrors = progressHandler.results
ServiceError.is(err) ? err.details : String(err) .filter(DownloadResult.isError)
); .map(({ url, message }) => `${message}: ${url}`)
.join(' ');
const message = ServiceError.is(error)
? `${error.details}${downloadErrors ? ` ${downloadErrors}` : ''}`
: String(error);
// IDE2 keeps only the most recent error message. Previous errors might have been fixed with the fallback initialization.
this.beforeInitSummary.message = message;
// Toast the error message, so tha the user has chance to fix it if it was a client error (HTTP 4xx).
progressHandler.reportError(message);
} }
} }
} }
private async updateIndexes( get indexUpdateSummaryBeforeInit(): IndexUpdateSummary {
client: CoreClientProvider.Client, return { ...this.beforeInitSummary };
progressHandler?: IndexesUpdateProgressHandler
): Promise<void> {
await Promise.all([
this.updateIndex(client, progressHandler),
this.updateLibraryIndex(client, progressHandler),
]);
} }
private async updateIndex( private async updatePlatformIndex(
client: CoreClientProvider.Client, client: CoreClientProvider.Client,
progressHandler?: IndexesUpdateProgressHandler progressHandler?: IndexesUpdateProgressHandler
): Promise<void> { ): Promise<void> {
return this.doUpdateIndex( return this.doUpdateIndex(
() => () =>
client.client.updateIndex( client.client.updateIndex(
new UpdateIndexRequest().setInstance(client.instance) new UpdateIndexRequest().setInstance(client.instance) // Always updates both the primary and the 3rd party package indexes.
), ),
progressHandler, progressHandler,
'platform-index' 'platform-index'
@ -323,50 +357,45 @@ export class CoreClientProvider {
task?: string task?: string
): Promise<void> { ): Promise<void> {
const progressId = progressHandler?.progressId; const progressId = progressHandler?.progressId;
return retry( return new Promise<void>((resolve, reject) => {
() => responseProvider()
new Promise<void>((resolve, reject) => { .on(
responseProvider() 'data',
.on( ExecuteWithProgress.createDataCallback({
'data', responseService: {
ExecuteWithProgress.createDataCallback({ appendToOutput: ({ chunk: message }) => {
responseService: { console.log(
appendToOutput: ({ chunk: message }) => { `core-client-provider${task ? ` [${task}]` : ''}`,
console.log( message
`core-client-provider${task ? ` [${task}]` : ''}`, );
message progressHandler?.reportProgress(message);
); },
progressHandler?.reportProgress(message); reportResult: (result) => progressHandler?.reportResult(result),
}, },
}, progressId,
progressId, })
}) )
) .on('error', reject)
.on('error', reject) .on('end', resolve);
.on('end', resolve); });
}),
50,
3
);
} }
private createProgressHandler(): IndexesUpdateProgressHandler { private createProgressHandler(
types: IndexType[]
): IndexesUpdateProgressHandler {
const additionalUrlsCount = const additionalUrlsCount =
this.configService.cliConfiguration?.board_manager?.additional_urls this.configService.cliConfiguration?.board_manager?.additional_urls
?.length ?? 0; ?.length ?? 0;
return new IndexesUpdateProgressHandler( return new IndexesUpdateProgressHandler(types, additionalUrlsCount, {
additionalUrlsCount, onProgress: (progressMessage) =>
(progressMessage) =>
this.notificationService.notifyIndexUpdateDidProgress(progressMessage), this.notificationService.notifyIndexUpdateDidProgress(progressMessage),
({ progressId, message }) => onError: (params: IndexUpdateDidFailParams) =>
this.notificationService.notifyIndexUpdateDidFail({ this.notificationService.notifyIndexUpdateDidFail(params),
progressId, onStart: (params: IndexUpdateWillStartParams) =>
message, this.notificationService.notifyIndexUpdateWillStart(params),
}), onComplete: (params: IndexUpdateDidCompleteParams) =>
(progressId) => this.notificationService.notifyIndexUpdateDidComplete(params),
this.notificationService.notifyIndexWillUpdate(progressId), });
(progressId) => this.notificationService.notifyIndexDidUpdate(progressId)
);
} }
private address(port: string): string { private address(port: string): string {
@ -410,6 +439,7 @@ export namespace CoreClientProvider {
export abstract class CoreClientAware { export abstract class CoreClientAware {
@inject(CoreClientProvider) @inject(CoreClientProvider)
private readonly coreClientProvider: CoreClientProvider; private readonly coreClientProvider: CoreClientProvider;
/** /**
* Returns with a promise that resolves when the core client is initialized and ready. * Returns with a promise that resolves when the core client is initialized and ready.
*/ */
@ -417,8 +447,17 @@ export abstract class CoreClientAware {
return this.coreClientProvider.client; return this.coreClientProvider.client;
} }
protected get onClientDidRefresh(): Event<CoreClientProvider.Client> { /**
return this.coreClientProvider.onClientDidRefresh; * Updates the index of the given `type` and returns with a promise which resolves when the core gPRC client has been reinitialized.
*/
async updateIndex({ types }: { types: IndexType[] }): Promise<void> {
const client = await this.coreClient;
return this.coreClientProvider.updateIndex(client, types);
}
async indexUpdateSummaryBeforeInit(): Promise<IndexUpdateSummary> {
await this.coreClient;
return this.coreClientProvider.indexUpdateSummaryBeforeInit;
} }
refresh(): Promise<void> { refresh(): Promise<void> {
@ -426,15 +465,20 @@ export abstract class CoreClientAware {
} }
} }
class IndexUpdateRequiredBeforeInitError extends Error { class MustUpdateIndexesBeforeInitError extends Error {
constructor(causes: RpcStatus.AsObject[]) { readonly indexTypesToUpdate: Set<IndexType>;
constructor(causes: [RpcStatus.AsObject, IndexType][]) {
super(`The index of the cores and libraries must be updated before initializing the core gRPC client. super(`The index of the cores and libraries must be updated before initializing the core gRPC client.
The following problems were detected during the gRPC client initialization: The following problems were detected during the gRPC client initialization:
${causes ${causes
.map(({ code, message }) => ` - code: ${code}, message: ${message}`) .map(
([{ code, message }, type]) =>
`[${type}-index] - code: ${code}, message: ${message}`
)
.join('\n')} .join('\n')}
`); `);
Object.setPrototypeOf(this, IndexUpdateRequiredBeforeInitError.prototype); Object.setPrototypeOf(this, MustUpdateIndexesBeforeInitError.prototype);
this.indexTypesToUpdate = new Set(causes.map(([, type]) => type));
if (!causes.length) { if (!causes.length) {
throw new Error(`expected non-empty 'causes'`); throw new Error(`expected non-empty 'causes'`);
} }
@ -444,41 +488,66 @@ ${causes
function isIndexUpdateRequiredBeforeInit( function isIndexUpdateRequiredBeforeInit(
status: RpcStatus[], status: RpcStatus[],
cliConfig: DefaultCliConfig cliConfig: DefaultCliConfig
): IndexUpdateRequiredBeforeInitError | undefined { ): MustUpdateIndexesBeforeInitError | undefined {
const causes = status const causes = status.reduce((acc, curr) => {
.filter((s) => for (const [predicate, type] of IndexUpdateRequiredPredicates) {
IndexUpdateRequiredBeforeInit.map((predicate) => if (predicate(curr, cliConfig)) {
predicate(s, cliConfig) acc.push([curr.toObject(false), type]);
).some(Boolean) return acc;
) }
.map((s) => RpcStatus.toObject(false, s)); }
return acc;
}, [] as [RpcStatus.AsObject, IndexType][]);
return causes.length return causes.length
? new IndexUpdateRequiredBeforeInitError(causes) ? new MustUpdateIndexesBeforeInitError(causes)
: undefined; : undefined;
} }
const IndexUpdateRequiredBeforeInit = [ interface Predicate {
isPackageIndexMissingStatus, (
isDiscoveryNotFoundStatus, status: RpcStatus,
{
directories: { data },
}: DefaultCliConfig
): boolean;
}
const IndexUpdateRequiredPredicates: [Predicate, IndexType][] = [
[isPrimaryPackageIndexMissingStatus, 'platform'],
[isDiscoveryNotFoundStatus, 'platform'],
[isLibraryIndexMissingStatus, 'library'],
]; ];
function isPackageIndexMissingStatus( // Loading index file: loading json index file /path/to/package_index.json: open /path/to/package_index.json: no such file or directory
function isPrimaryPackageIndexMissingStatus(
status: RpcStatus, status: RpcStatus,
{ directories: { data } }: DefaultCliConfig { directories: { data } }: DefaultCliConfig
): boolean { ): boolean {
const predicate = ({ message }: RpcStatus.AsObject) => const predicate = ({ message }: RpcStatus.AsObject) =>
message.includes('loading json index file') && message.includes('loading json index file') &&
(message.includes(join(data, 'package_index.json')) || message.includes(join(data, 'package_index.json'));
message.includes(join(data, 'library_index.json')));
// https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/package_manager.go#L247 // https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/package_manager.go#L247
return evaluate(status, predicate); return evaluate(status, predicate);
} }
// Error loading hardware platform: discovery $TOOL_NAME not found
function isDiscoveryNotFoundStatus(status: RpcStatus): boolean { function isDiscoveryNotFoundStatus(status: RpcStatus): boolean {
const predicate = ({ message }: RpcStatus.AsObject) => const predicate = ({ message }: RpcStatus.AsObject) =>
message.includes('discovery') && message.includes('discovery') &&
(message.includes('not found') || message.includes('not installed')); (message.includes('not found') ||
message.includes('loading hardware platform'));
// https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/loader.go#L740 // https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/loader.go#L740
// https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/loader.go#L744 // https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/loader.go#L744
return evaluate(status, predicate); return evaluate(status, predicate);
} }
// Loading index file: reading library_index.json: open /path/to/library_index.json: no such file or directory
function isLibraryIndexMissingStatus(
status: RpcStatus,
{ directories: { data } }: DefaultCliConfig
): boolean {
const predicate = ({ message }: RpcStatus.AsObject) =>
message.includes('index file') &&
message.includes('reading') &&
message.includes(join(data, 'library_index.json'));
// https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/package_manager.go#L247
return evaluate(status, predicate);
}
function evaluate( function evaluate(
subject: RpcStatus, subject: RpcStatus,
predicate: (error: RpcStatus.AsObject) => boolean predicate: (error: RpcStatus.AsObject) => boolean

View File

@ -1,4 +1,11 @@
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import {
IndexType,
IndexUpdateDidCompleteParams,
IndexUpdateDidFailParams,
IndexUpdateSummary,
IndexUpdateWillStartParams,
} from '../common/protocol';
import { import {
ProgressMessage, ProgressMessage,
ResponseService, ResponseService,
@ -11,6 +18,9 @@ import {
import { import {
DownloadProgress, DownloadProgress,
TaskProgress, TaskProgress,
DownloadProgressStart,
DownloadProgressUpdate,
DownloadProgressEnd,
} from './cli-protocol/cc/arduino/cli/commands/v1/common_pb'; } from './cli-protocol/cc/arduino/cli/commands/v1/common_pb';
import { CompileResponse } from './cli-protocol/cc/arduino/cli/commands/v1/compile_pb'; import { CompileResponse } from './cli-protocol/cc/arduino/cli/commands/v1/compile_pb';
import { import {
@ -81,7 +91,9 @@ namespace IndexProgressResponse {
); );
} }
export function workUnit(response: IndexProgressResponse): UnitOfWork { export function workUnit(response: IndexProgressResponse): UnitOfWork {
return { download: response.getDownloadProgress() }; return {
download: response.getDownloadProgress(),
};
} }
} }
/** /**
@ -151,7 +163,9 @@ export namespace ExecuteWithProgress {
* _unknown_ progress if falsy. * _unknown_ progress if falsy.
*/ */
readonly progressId?: string; readonly progressId?: string;
readonly responseService: Partial<ResponseService>; readonly responseService: Partial<
ResponseService & { reportResult: (result: DownloadResult) => void }
>;
} }
export function createDataCallback<R extends ProgressResponse>({ export function createDataCallback<R extends ProgressResponse>({
@ -159,19 +173,21 @@ export namespace ExecuteWithProgress {
progressId, progressId,
}: ExecuteWithProgress.Options): (response: R) => void { }: ExecuteWithProgress.Options): (response: R) => void {
const uuid = v4(); const uuid = v4();
let localFile = ''; let message = '';
let localTotalSize = Number.NaN; let url = '';
return (response: R) => { return (response: R) => {
if (DEBUG) { if (DEBUG) {
const json = toJson(response); const json = toJson(response);
if (json) { if (json) {
console.log(`Progress response [${uuid}]: ${json}`); console.debug(`[gRPC progress] Progress response [${uuid}]: ${json}`);
} }
} }
const unitOfWork = resolve(response); const unitOfWork = resolve(response);
const { task, download } = unitOfWork; const { task, download } = unitOfWork;
if (!download && !task) { if (!download && !task) {
// report a fake unknown progress. // Report a fake unknown progress if progress ID is available.
// When a progress ID is available, a connected client is setting the progress ID.
// Hence, it's listening to progress updates.
if (unitOfWork === UnitOfWork.Unknown && progressId) { if (unitOfWork === UnitOfWork.Unknown && progressId) {
if (progressId) { if (progressId) {
responseService.reportProgress?.({ responseService.reportProgress?.({
@ -187,7 +203,7 @@ export namespace ExecuteWithProgress {
// Technically, it does not cause an error, but could mess up the progress reporting. // Technically, it does not cause an error, but could mess up the progress reporting.
// See an example of an empty object `{}` repose here: https://github.com/arduino/arduino-ide/issues/906#issuecomment-1171145630. // See an example of an empty object `{}` repose here: https://github.com/arduino/arduino-ide/issues/906#issuecomment-1171145630.
console.warn( console.warn(
"Implementation error. Neither 'download' nor 'task' is available." `Implementation error. None of the following properties were available on the response: 'task', 'download'`
); );
} }
return; return;
@ -219,43 +235,32 @@ export namespace ExecuteWithProgress {
} }
} }
} else if (download) { } else if (download) {
if (download.getFile() && !localFile) { const phase = phaseOf(download);
localFile = download.getFile(); if (phase instanceof DownloadProgressStart) {
} message = phase.getLabel();
if (download.getTotalSize() > 0 && Number.isNaN(localTotalSize)) { url = phase.getUrl();
localTotalSize = download.getTotalSize(); responseService.appendToOutput?.({ chunk: `${message}\n` });
} } else if (phase instanceof DownloadProgressUpdate) {
if (progressId && message) {
// This happens only once per file download.
if (download.getTotalSize() && localFile) {
responseService.appendToOutput?.({ chunk: `${localFile}\n` });
}
if (progressId && localFile) {
let work: ProgressMessage.Work | undefined = undefined;
if (download.getDownloaded() > 0 && !Number.isNaN(localTotalSize)) {
work = {
total: localTotalSize,
done: download.getDownloaded(),
};
}
responseService.reportProgress?.({
progressId,
message: `Downloading ${localFile}`,
work,
});
}
if (download.getCompleted()) {
// Discard local state.
if (progressId && !Number.isNaN(localTotalSize)) {
responseService.reportProgress?.({ responseService.reportProgress?.({
progressId, progressId,
message: '', message,
work: { done: Number.NaN, total: Number.NaN }, work: {
total: phase.getTotalSize(),
done: phase.getDownloaded(),
},
}); });
} }
localFile = ''; } else if (phase instanceof DownloadProgressEnd) {
localTotalSize = Number.NaN; if (url) {
responseService.reportResult?.({
url,
message: phase.getMessage(),
success: phase.getSuccess(),
});
}
message = '';
url = '';
} }
} }
}; };
@ -274,31 +279,40 @@ export namespace ExecuteWithProgress {
return {}; return {};
} }
function toJson(response: ProgressResponse): string | undefined { function toJson(response: ProgressResponse): string | undefined {
let object: Record<string, unknown> | undefined = undefined; return JSON.stringify(response.toObject(false));
if (response instanceof LibraryInstallResponse) { }
object = LibraryInstallResponse.toObject(false, response); function phaseOf(
} else if (response instanceof LibraryUninstallResponse) { download: DownloadProgress
object = LibraryUninstallResponse.toObject(false, response); ): DownloadProgressStart | DownloadProgressUpdate | DownloadProgressEnd {
} else if (response instanceof ZipLibraryInstallResponse) { let start: undefined | DownloadProgressStart = undefined;
object = ZipLibraryInstallResponse.toObject(false, response); let update: undefined | DownloadProgressUpdate = undefined;
} else if (response instanceof PlatformInstallResponse) { let end: undefined | DownloadProgressEnd = undefined;
object = PlatformInstallResponse.toObject(false, response); if (download.hasStart()) {
} else if (response instanceof PlatformUninstallResponse) { start = download.getStart();
object = PlatformUninstallResponse.toObject(false, response); } else if (download.hasUpdate()) {
} else if (response instanceof UpdateIndexResponse) { update = download.getUpdate();
object = UpdateIndexResponse.toObject(false, response); } else if (download.hasEnd()) {
} else if (response instanceof UpdateLibrariesIndexResponse) { end = download.getEnd();
object = UpdateLibrariesIndexResponse.toObject(false, response); } else {
} else if (response instanceof UpdateCoreLibrariesIndexResponse) { throw new Error(
object = UpdateCoreLibrariesIndexResponse.toObject(false, response); `Download progress does not have a 'start', 'update', and 'end'. ${JSON.stringify(
} else if (response instanceof CompileResponse) { download.toObject(false)
object = CompileResponse.toObject(false, response); )}`
);
} }
if (!object) { if (start) {
console.warn('Unhandled gRPC response', response); return start;
return undefined; } else if (update) {
return update;
} else if (end) {
return end;
} else {
throw new Error(
`Download progress does not have a 'start', 'update', and 'end'. ${JSON.stringify(
download.toObject(false)
)}`
);
} }
return JSON.stringify(object);
} }
} }
@ -306,33 +320,39 @@ export class IndexesUpdateProgressHandler {
private done = 0; private done = 0;
private readonly total: number; private readonly total: number;
readonly progressId: string; readonly progressId: string;
readonly results: DownloadResult[];
constructor( constructor(
private types: IndexType[],
additionalUrlsCount: number, additionalUrlsCount: number,
private readonly onProgress: (progressMessage: ProgressMessage) => void, private readonly options: {
private readonly onError?: ({ onProgress: (progressMessage: ProgressMessage) => void;
progressId, onError?: (params: IndexUpdateDidFailParams) => void;
message, onStart?: (params: IndexUpdateWillStartParams) => void;
}: { onComplete?: (params: IndexUpdateDidCompleteParams) => void;
progressId: string; }
message: string;
}) => void,
private readonly onStart?: (progressId: string) => void,
private readonly onEnd?: (progressId: string) => void
) { ) {
this.progressId = v4(); this.progressId = v4();
this.total = IndexesUpdateProgressHandler.total(additionalUrlsCount); this.results = [];
this.total = IndexesUpdateProgressHandler.total(types, additionalUrlsCount);
// Note: at this point, the IDE2 backend might not have any connected clients, so this notification is not delivered to anywhere // Note: at this point, the IDE2 backend might not have any connected clients, so this notification is not delivered to anywhere
// Hence, clients must handle gracefully when no `willUpdate` is received before any `didProgress`. // Hence, clients must handle gracefully when no `willStart` event is received before any `didProgress`.
this.onStart?.(this.progressId); this.options.onStart?.({ progressId: this.progressId, types });
} }
reportEnd(): void { reportEnd(): void {
this.onEnd?.(this.progressId); const updatedAt = new Date().toISOString();
this.options.onComplete?.({
progressId: this.progressId,
summary: this.types.reduce((summary, type) => {
summary[type] = updatedAt;
return summary;
}, {} as IndexUpdateSummary),
});
} }
reportProgress(message: string): void { reportProgress(message: string): void {
this.onProgress({ this.options.onProgress({
message, message,
progressId: this.progressId, progressId: this.progressId,
work: { total: this.total, done: ++this.done }, work: { total: this.total, done: ++this.done },
@ -340,15 +360,44 @@ export class IndexesUpdateProgressHandler {
} }
reportError(message: string): void { reportError(message: string): void {
this.onError?.({ progressId: this.progressId, message }); this.options.onError?.({
progressId: this.progressId,
message,
types: this.types,
});
} }
private static total(additionalUrlsCount: number): number { reportResult(result: DownloadResult): void {
// +1 for the `package_index.tar.bz2` when updating the platform index. this.results.push(result);
const totalPlatformIndexCount = additionalUrlsCount + 1; }
// The `library_index.json.gz` and `library_index.json.sig` when running the library index update.
const totalLibraryIndexCount = 2; private static total(
types: IndexType[],
additionalUrlsCount: number
): number {
let total = 0;
if (types.includes('library')) {
// The `library_index.json.gz` and `library_index.json.sig` when running the library index update.
total += 2;
}
if (types.includes('platform')) {
// +1 for the `package_index.tar.bz2` when updating the platform index.
total += additionalUrlsCount + 1;
}
// +1 for the `initInstance` call after the index update (`reportEnd`) // +1 for the `initInstance` call after the index update (`reportEnd`)
return totalPlatformIndexCount + totalLibraryIndexCount + 1; return total + 1;
}
}
export interface DownloadResult {
readonly url: string;
readonly success: boolean;
readonly message?: string;
}
export namespace DownloadResult {
export function isError(
arg: DownloadResult
): arg is DownloadResult & { message: string } {
return !!arg.message && !arg.success;
} }
} }

View File

@ -8,6 +8,9 @@ import type {
Config, Config,
Sketch, Sketch,
ProgressMessage, ProgressMessage,
IndexUpdateWillStartParams,
IndexUpdateDidCompleteParams,
IndexUpdateDidFailParams,
} from '../common/protocol'; } from '../common/protocol';
@injectable() @injectable()
@ -16,8 +19,8 @@ export class NotificationServiceServerImpl
{ {
private readonly clients: NotificationServiceClient[] = []; private readonly clients: NotificationServiceClient[] = [];
notifyIndexWillUpdate(progressId: string): void { notifyIndexUpdateWillStart(params: IndexUpdateWillStartParams): void {
this.clients.forEach((client) => client.notifyIndexWillUpdate(progressId)); this.clients.forEach((client) => client.notifyIndexUpdateWillStart(params));
} }
notifyIndexUpdateDidProgress(progressMessage: ProgressMessage): void { notifyIndexUpdateDidProgress(progressMessage: ProgressMessage): void {
@ -26,20 +29,14 @@ export class NotificationServiceServerImpl
); );
} }
notifyIndexDidUpdate(progressId: string): void { notifyIndexUpdateDidComplete(params: IndexUpdateDidCompleteParams): void {
this.clients.forEach((client) => client.notifyIndexDidUpdate(progressId)); this.clients.forEach((client) =>
client.notifyIndexUpdateDidComplete(params)
);
} }
notifyIndexUpdateDidFail({ notifyIndexUpdateDidFail(params: IndexUpdateDidFailParams): void {
progressId, this.clients.forEach((client) => client.notifyIndexUpdateDidFail(params));
message,
}: {
progressId: string;
message: string;
}): void {
this.clients.forEach((client) =>
client.notifyIndexUpdateDidFail({ progressId, message })
);
} }
notifyDaemonDidStart(port: string): void { notifyDaemonDidStart(port: string): void {

View File

@ -408,6 +408,11 @@
"dismissSurvey": "Don't show again", "dismissSurvey": "Don't show again",
"surveyMessage": "Please help us improve by answering this super short survey. We value our community and would like to get to know our supporters a little better." "surveyMessage": "Please help us improve by answering this super short survey. We value our community and would like to get to know our supporters a little better."
}, },
"updateIndexes": {
"updateIndexes": "Update Indexes",
"updateLibraryIndex": "Update Library Index",
"updatePackageIndex": "Update Package Index"
},
"upload": { "upload": {
"error": "{0} error: {1}" "error": "{0} error: {1}"
}, },