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": {
"cli": {
"version": "0.27.1"
"version": {
"owner": "cmaglie",
"repo": "arduino-cli",
"commitish": "download_progress_refactor"
}
},
"fwuploader": {
"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 { DeleteSketch } from './contributions/delete-sketch';
import { UserFields } from './contributions/user-fields';
import { UpdateIndexes } from './contributions/update-indexes';
const registerArduinoThemes = () => {
const themes: MonacoThemeJson[] = [
@ -744,6 +745,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
Contribution.configure(bind, CheckForUpdates);
Contribution.configure(bind, UserFields);
Contribution.configure(bind, DeleteSketch);
Contribution.configure(bind, UpdateIndexes);
bindContributionProvider(bind, StartupTaskProvider);
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.updateBoards(this.state.query)
),
this.props.notificationCenter.onIndexDidUpdate(() =>
this.props.notificationCenter.onIndexUpdateDidComplete(() =>
this.updateBoards(this.state.query)
),
this.props.notificationCenter.onDaemonDidStart(() =>

View File

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

View File

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

View File

@ -11,6 +11,7 @@ import type {
} from '../../common/protocol/boards-service';
import type { Programmer } from './boards-service';
import type { Sketch } from './sketches-service';
import { IndexUpdateSummary } from './notification-service';
export const CompilerWarningLiterals = [
'None',
@ -112,6 +113,33 @@ export interface CoreService {
* Refreshes the underling core gRPC client for the Arduino CLI.
*/
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 {

View File

@ -5,27 +5,62 @@ import type {
Config,
ProgressMessage,
Sketch,
IndexType,
} from '../protocol';
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 {
notifyIndexWillUpdate(progressId: string): void;
// Index
notifyIndexUpdateWillStart(params: IndexUpdateWillStartParams): void;
notifyIndexUpdateDidProgress(progressMessage: ProgressMessage): void;
notifyIndexDidUpdate(progressId: string): void;
notifyIndexUpdateDidFail({
progressId,
message,
}: {
progressId: string;
message: string;
}): void;
notifyIndexUpdateDidComplete(params: IndexUpdateDidCompleteParams): void;
notifyIndexUpdateDidFail(params: IndexUpdateDidFailParams): void;
// Daemon
notifyDaemonDidStart(port: string): void;
notifyDaemonDidStop(): void;
// CLI config
notifyConfigDidChange(event: { config: Config | undefined }): void;
// Platforms
notifyPlatformDidInstall(event: { item: BoardsPackage }): void;
notifyPlatformDidUninstall(event: { item: BoardsPackage }): void;
// Libraries
notifyLibraryDidInstall(event: { item: LibraryPackage }): void;
notifyLibraryDidUninstall(event: { item: LibraryPackage }): void;
// Boards discovery
notifyAttachedBoardsDidChange(event: AttachedBoardsChangeEvent): 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 { BackendApplicationContribution } from '@theia/core/lib/node';
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 { Unknown } from '../common/nls';
import {
@ -78,14 +78,6 @@ export class BoardDiscovery
onStart(): void {
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 {

View File

@ -220,6 +220,9 @@ export class UpdateIndexRequest extends jspb.Message {
getInstance(): cc_arduino_cli_commands_v1_common_pb.Instance | undefined;
setInstance(value?: cc_arduino_cli_commands_v1_common_pb.Instance): UpdateIndexRequest;
getIgnoreCustomPackageIndexes(): boolean;
setIgnoreCustomPackageIndexes(value: boolean): UpdateIndexRequest;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): UpdateIndexRequest.AsObject;
@ -234,6 +237,7 @@ export class UpdateIndexRequest extends jspb.Message {
export namespace UpdateIndexRequest {
export type 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) {
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) {
@ -1853,6 +1854,10 @@ proto.cc.arduino.cli.commands.v1.UpdateIndexRequest.deserializeBinaryFromReader
reader.readMessage(value,cc_arduino_cli_commands_v1_common_pb.Instance.deserializeBinaryFromReader);
msg.setInstance(value);
break;
case 2:
var value = /** @type {boolean} */ (reader.readBool());
msg.setIgnoreCustomPackageIndexes(value);
break;
default:
reader.skipField();
break;
@ -1890,6 +1895,13 @@ proto.cc.arduino.cli.commands.v1.UpdateIndexRequest.serializeBinaryToWriter = fu
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 {
getUrl(): string;
setUrl(value: string): DownloadProgress;
getFile(): string;
setFile(value: string): DownloadProgress;
hasStart(): boolean;
clearStart(): void;
getStart(): DownloadProgressStart | undefined;
setStart(value?: DownloadProgressStart): DownloadProgress;
getTotalSize(): number;
setTotalSize(value: number): DownloadProgress;
getDownloaded(): number;
setDownloaded(value: number): DownloadProgress;
hasUpdate(): boolean;
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;
toObject(includeInstance?: boolean): DownloadProgress.AsObject;
@ -55,12 +60,97 @@ export class DownloadProgress extends jspb.Message {
}
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 = {
url: string,
file: string,
totalSize: number,
label: string,
}
}
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,
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.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.Instance', null, global);
goog.exportSymbol('proto.cc.arduino.cli.commands.v1.Platform', null, global);
@ -55,7 +59,7 @@ if (goog.DEBUG && !COMPILED) {
* @constructor
*/
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);
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';
}
/**
* 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.
* @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) {
@ -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) {
var f, obj = {
url: jspb.Message.getFieldWithDefault(msg, 1, ""),
file: jspb.Message.getFieldWithDefault(msg, 2, ""),
totalSize: jspb.Message.getFieldWithDefault(msg, 3, 0),
downloaded: jspb.Message.getFieldWithDefault(msg, 4, 0),
completed: jspb.Message.getBooleanFieldWithDefault(msg, 5, false)
start: (f = msg.getStart()) && proto.cc.arduino.cli.commands.v1.DownloadProgressStart.toObject(includeInstance, f),
update: (f = msg.getUpdate()) && proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate.toObject(includeInstance, f),
end: (f = msg.getEnd()) && proto.cc.arduino.cli.commands.v1.DownloadProgressEnd.toObject(includeInstance, f)
};
if (includeInstance) {
@ -395,24 +487,19 @@ proto.cc.arduino.cli.commands.v1.DownloadProgress.deserializeBinaryFromReader =
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {string} */ (reader.readString());
msg.setUrl(value);
var value = new proto.cc.arduino.cli.commands.v1.DownloadProgressStart;
reader.readMessage(value,proto.cc.arduino.cli.commands.v1.DownloadProgressStart.deserializeBinaryFromReader);
msg.setStart(value);
break;
case 2:
var value = /** @type {string} */ (reader.readString());
msg.setFile(value);
var value = new proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate;
reader.readMessage(value,proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate.deserializeBinaryFromReader);
msg.setUpdate(value);
break;
case 3:
var value = /** @type {number} */ (reader.readInt64());
msg.setTotalSize(value);
break;
case 4:
var value = /** @type {number} */ (reader.readInt64());
msg.setDownloaded(value);
break;
case 5:
var value = /** @type {boolean} */ (reader.readBool());
msg.setCompleted(value);
var value = new proto.cc.arduino.cli.commands.v1.DownloadProgressEnd;
reader.readMessage(value,proto.cc.arduino.cli.commands.v1.DownloadProgressEnd.deserializeBinaryFromReader);
msg.setEnd(value);
break;
default:
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
*/
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;
f = message.getUrl();
if (f.length > 0) {
@ -450,34 +782,13 @@ proto.cc.arduino.cli.commands.v1.DownloadProgress.serializeBinaryToWriter = func
f
);
}
f = message.getFile();
f = message.getLabel();
if (f.length > 0) {
writer.writeString(
2,
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;
* @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, ""));
};
/**
* @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);
};
/**
* optional string file = 2;
* optional string label = 2;
* @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, ""));
};
/**
* @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);
};
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}
*/
proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.getTotalSize = function() {
return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 3, 0));
proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate.prototype.getDownloaded = function() {
return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0));
};
/**
* @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) {
return jspb.Message.setProto3IntField(this, 3, value);
proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate.prototype.setDownloaded = function(value) {
return jspb.Message.setProto3IntField(this, 1, value);
};
/**
* optional int64 downloaded = 4;
* optional int64 total_size = 2;
* @return {number}
*/
proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.getDownloaded = function() {
return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 4, 0));
proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate.prototype.getTotalSize = function() {
return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 2, 0));
};
/**
* @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) {
return jspb.Message.setProto3IntField(this, 4, value);
proto.cc.arduino.cli.commands.v1.DownloadProgressUpdate.prototype.setTotalSize = function(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}
*/
proto.cc.arduino.cli.commands.v1.DownloadProgress.prototype.getCompleted = function() {
return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 5, false));
proto.cc.arduino.cli.commands.v1.DownloadProgressEnd.prototype.getSuccess = function() {
return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 1, false));
};
/**
* @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) {
return jspb.Message.setProto3BooleanField(this, 5, value);
proto.cc.arduino.cli.commands.v1.DownloadProgressEnd.prototype.setSuccess = function(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,
postConstruct,
} 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 { Instance } from './cli-protocol/cc/arduino/cli/commands/v1/common_pb';
import {
@ -19,8 +19,15 @@ import {
UpdateLibrariesIndexResponse,
} 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 { NotificationServiceServer } from '../common/protocol';
import { Deferred, retry } from '@theia/core/lib/common/promise-util';
import {
IndexType,
IndexUpdateDidCompleteParams,
IndexUpdateSummary,
IndexUpdateDidFailParams,
IndexUpdateWillStartParams,
NotificationServiceServer,
} from '../common/protocol';
import { Deferred } from '@theia/core/lib/common/promise-util';
import {
Status as RpcStatus,
Status,
@ -32,6 +39,7 @@ import { Disposable } from '@theia/core/shared/vscode-languageserver-protocol';
import {
IndexesUpdateProgressHandler,
ExecuteWithProgress,
DownloadResult,
} from './grpc-progressible';
import type { DefaultCliConfig } from './cli-config';
import { ServiceError } from './service-error';
@ -45,16 +53,19 @@ export class CoreClientProvider {
@inject(NotificationServiceServer)
private readonly notificationService: NotificationServiceServer;
private ready = new Deferred<void>();
private pending: Deferred<CoreClientProvider.Client> | undefined;
private _client: CoreClientProvider.Client | undefined;
private readonly toDisposeBeforeCreate = new DisposableCollection();
/**
* See `CoreService#indexUpdateSummaryBeforeInit`.
*/
private readonly beforeInitSummary = {} as IndexUpdateSummary;
private readonly toDisposeOnCloseClient = new DisposableCollection();
private readonly toDisposeAfterDidCreate = new DisposableCollection();
private readonly onClientReadyEmitter =
new Emitter<CoreClientProvider.Client>();
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()
protected init(): void {
@ -65,7 +76,9 @@ export class CoreClientProvider {
});
this.daemon.onDaemonStarted((port) => this.create(port));
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 {
@ -80,7 +93,7 @@ export class CoreClientProvider {
if (!this.pending) {
this.pending = new Deferred();
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.pending?.resolve(client);
this.toDisposeAfterDidCreate.dispose();
@ -90,10 +103,6 @@ export class CoreClientProvider {
return this.pending.promise;
}
get onClientDidRefresh(): Event<CoreClientProvider.Client> {
return this.onClientDidRefreshEmitter.event;
}
async refresh(): Promise<void> {
const client = await this.client;
await this.initInstance(client);
@ -106,7 +115,7 @@ export class CoreClientProvider {
this.closeClient();
const address = this.address(port);
const client = await this.createClient(address);
this.toDisposeBeforeCreate.pushAll([
this.toDisposeOnCloseClient.pushAll([
Disposable.create(() => client.client.close()),
Disposable.create(() => {
this.ready.reject(
@ -118,7 +127,6 @@ export class CoreClientProvider {
}),
]);
await this.initInstanceWithFallback(client);
setTimeout(async () => this.refreshIndexes(), 10_000); // Update the indexes asynchronously
return this.useClient(client);
}
@ -141,12 +149,17 @@ export class CoreClientProvider {
try {
await this.initInstance(client);
} catch (err) {
if (err instanceof IndexUpdateRequiredBeforeInitError) {
if (err instanceof MustUpdateIndexesBeforeInitError) {
console.error(
'The primary packages indexes are missing. Running indexes update before initializing the core gRPC client',
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);
console.info(
`Downloaded the primary package indexes, and successfully initialized the core gRPC client.`
@ -170,7 +183,7 @@ export class CoreClientProvider {
}
private closeClient(): void {
return this.toDisposeBeforeCreate.dispose();
return this.toDisposeOnCloseClient.dispose();
}
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> {
const client = this._client;
if (client) {
const progressHandler = this.createProgressHandler();
try {
await this.updateIndexes(client, progressHandler);
async updateIndex(
client: CoreClientProvider.Client,
types: IndexType[]
): Promise<void> {
let error: unknown | undefined = undefined;
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);
// notify clients about the index update only after the client has been "re-initialized" and the new content is available.
progressHandler.reportEnd();
this.onClientDidRefreshEmitter.fire(client);
} catch (err) {
console.error('Failed to update indexes', err);
progressHandler.reportError(
ServiceError.is(err) ? err.details : String(err)
);
}
if (error) {
console.error(`Failed to update ${types.join(', ')} indexes.`, error);
const downloadErrors = progressHandler.results
.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(
client: CoreClientProvider.Client,
progressHandler?: IndexesUpdateProgressHandler
): Promise<void> {
await Promise.all([
this.updateIndex(client, progressHandler),
this.updateLibraryIndex(client, progressHandler),
]);
get indexUpdateSummaryBeforeInit(): IndexUpdateSummary {
return { ...this.beforeInitSummary };
}
private async updateIndex(
private async updatePlatformIndex(
client: CoreClientProvider.Client,
progressHandler?: IndexesUpdateProgressHandler
): Promise<void> {
return this.doUpdateIndex(
() =>
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,
'platform-index'
@ -323,50 +357,45 @@ export class CoreClientProvider {
task?: string
): Promise<void> {
const progressId = progressHandler?.progressId;
return retry(
() =>
new Promise<void>((resolve, reject) => {
responseProvider()
.on(
'data',
ExecuteWithProgress.createDataCallback({
responseService: {
appendToOutput: ({ chunk: message }) => {
console.log(
`core-client-provider${task ? ` [${task}]` : ''}`,
message
);
progressHandler?.reportProgress(message);
},
},
progressId,
})
)
.on('error', reject)
.on('end', resolve);
}),
50,
3
);
return new Promise<void>((resolve, reject) => {
responseProvider()
.on(
'data',
ExecuteWithProgress.createDataCallback({
responseService: {
appendToOutput: ({ chunk: message }) => {
console.log(
`core-client-provider${task ? ` [${task}]` : ''}`,
message
);
progressHandler?.reportProgress(message);
},
reportResult: (result) => progressHandler?.reportResult(result),
},
progressId,
})
)
.on('error', reject)
.on('end', resolve);
});
}
private createProgressHandler(): IndexesUpdateProgressHandler {
private createProgressHandler(
types: IndexType[]
): IndexesUpdateProgressHandler {
const additionalUrlsCount =
this.configService.cliConfiguration?.board_manager?.additional_urls
?.length ?? 0;
return new IndexesUpdateProgressHandler(
additionalUrlsCount,
(progressMessage) =>
return new IndexesUpdateProgressHandler(types, additionalUrlsCount, {
onProgress: (progressMessage) =>
this.notificationService.notifyIndexUpdateDidProgress(progressMessage),
({ progressId, message }) =>
this.notificationService.notifyIndexUpdateDidFail({
progressId,
message,
}),
(progressId) =>
this.notificationService.notifyIndexWillUpdate(progressId),
(progressId) => this.notificationService.notifyIndexDidUpdate(progressId)
);
onError: (params: IndexUpdateDidFailParams) =>
this.notificationService.notifyIndexUpdateDidFail(params),
onStart: (params: IndexUpdateWillStartParams) =>
this.notificationService.notifyIndexUpdateWillStart(params),
onComplete: (params: IndexUpdateDidCompleteParams) =>
this.notificationService.notifyIndexUpdateDidComplete(params),
});
}
private address(port: string): string {
@ -410,6 +439,7 @@ export namespace CoreClientProvider {
export abstract class CoreClientAware {
@inject(CoreClientProvider)
private readonly coreClientProvider: CoreClientProvider;
/**
* 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;
}
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> {
@ -426,15 +465,20 @@ export abstract class CoreClientAware {
}
}
class IndexUpdateRequiredBeforeInitError extends Error {
constructor(causes: RpcStatus.AsObject[]) {
class MustUpdateIndexesBeforeInitError extends Error {
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.
The following problems were detected during the gRPC client initialization:
${causes
.map(({ code, message }) => ` - code: ${code}, message: ${message}`)
.map(
([{ code, message }, type]) =>
`[${type}-index] - code: ${code}, message: ${message}`
)
.join('\n')}
`);
Object.setPrototypeOf(this, IndexUpdateRequiredBeforeInitError.prototype);
Object.setPrototypeOf(this, MustUpdateIndexesBeforeInitError.prototype);
this.indexTypesToUpdate = new Set(causes.map(([, type]) => type));
if (!causes.length) {
throw new Error(`expected non-empty 'causes'`);
}
@ -444,41 +488,66 @@ ${causes
function isIndexUpdateRequiredBeforeInit(
status: RpcStatus[],
cliConfig: DefaultCliConfig
): IndexUpdateRequiredBeforeInitError | undefined {
const causes = status
.filter((s) =>
IndexUpdateRequiredBeforeInit.map((predicate) =>
predicate(s, cliConfig)
).some(Boolean)
)
.map((s) => RpcStatus.toObject(false, s));
): MustUpdateIndexesBeforeInitError | undefined {
const causes = status.reduce((acc, curr) => {
for (const [predicate, type] of IndexUpdateRequiredPredicates) {
if (predicate(curr, cliConfig)) {
acc.push([curr.toObject(false), type]);
return acc;
}
}
return acc;
}, [] as [RpcStatus.AsObject, IndexType][]);
return causes.length
? new IndexUpdateRequiredBeforeInitError(causes)
? new MustUpdateIndexesBeforeInitError(causes)
: undefined;
}
const IndexUpdateRequiredBeforeInit = [
isPackageIndexMissingStatus,
isDiscoveryNotFoundStatus,
interface Predicate {
(
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,
{ directories: { data } }: DefaultCliConfig
): boolean {
const predicate = ({ message }: RpcStatus.AsObject) =>
message.includes('loading json index file') &&
(message.includes(join(data, 'package_index.json')) ||
message.includes(join(data, 'library_index.json')));
message.includes(join(data, 'package_index.json'));
// https://github.com/arduino/arduino-cli/blob/f0245bc2da6a56fccea7b2c9ea09e85fdcc52cb8/arduino/cores/packagemanager/package_manager.go#L247
return evaluate(status, predicate);
}
// Error loading hardware platform: discovery $TOOL_NAME not found
function isDiscoveryNotFoundStatus(status: RpcStatus): boolean {
const predicate = ({ message }: RpcStatus.AsObject) =>
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#L744
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(
subject: RpcStatus,
predicate: (error: RpcStatus.AsObject) => boolean

View File

@ -1,4 +1,11 @@
import { v4 } from 'uuid';
import {
IndexType,
IndexUpdateDidCompleteParams,
IndexUpdateDidFailParams,
IndexUpdateSummary,
IndexUpdateWillStartParams,
} from '../common/protocol';
import {
ProgressMessage,
ResponseService,
@ -11,6 +18,9 @@ import {
import {
DownloadProgress,
TaskProgress,
DownloadProgressStart,
DownloadProgressUpdate,
DownloadProgressEnd,
} from './cli-protocol/cc/arduino/cli/commands/v1/common_pb';
import { CompileResponse } from './cli-protocol/cc/arduino/cli/commands/v1/compile_pb';
import {
@ -81,7 +91,9 @@ namespace IndexProgressResponse {
);
}
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.
*/
readonly progressId?: string;
readonly responseService: Partial<ResponseService>;
readonly responseService: Partial<
ResponseService & { reportResult: (result: DownloadResult) => void }
>;
}
export function createDataCallback<R extends ProgressResponse>({
@ -159,19 +173,21 @@ export namespace ExecuteWithProgress {
progressId,
}: ExecuteWithProgress.Options): (response: R) => void {
const uuid = v4();
let localFile = '';
let localTotalSize = Number.NaN;
let message = '';
let url = '';
return (response: R) => {
if (DEBUG) {
const json = toJson(response);
if (json) {
console.log(`Progress response [${uuid}]: ${json}`);
console.debug(`[gRPC progress] Progress response [${uuid}]: ${json}`);
}
}
const unitOfWork = resolve(response);
const { task, download } = unitOfWork;
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 (progressId) {
responseService.reportProgress?.({
@ -187,7 +203,7 @@ export namespace ExecuteWithProgress {
// 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.
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;
@ -219,43 +235,32 @@ export namespace ExecuteWithProgress {
}
}
} else if (download) {
if (download.getFile() && !localFile) {
localFile = download.getFile();
}
if (download.getTotalSize() > 0 && Number.isNaN(localTotalSize)) {
localTotalSize = download.getTotalSize();
}
// 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)) {
const phase = phaseOf(download);
if (phase instanceof DownloadProgressStart) {
message = phase.getLabel();
url = phase.getUrl();
responseService.appendToOutput?.({ chunk: `${message}\n` });
} else if (phase instanceof DownloadProgressUpdate) {
if (progressId && message) {
responseService.reportProgress?.({
progressId,
message: '',
work: { done: Number.NaN, total: Number.NaN },
message,
work: {
total: phase.getTotalSize(),
done: phase.getDownloaded(),
},
});
}
localFile = '';
localTotalSize = Number.NaN;
} else if (phase instanceof DownloadProgressEnd) {
if (url) {
responseService.reportResult?.({
url,
message: phase.getMessage(),
success: phase.getSuccess(),
});
}
message = '';
url = '';
}
}
};
@ -274,31 +279,40 @@ export namespace ExecuteWithProgress {
return {};
}
function toJson(response: ProgressResponse): string | undefined {
let object: Record<string, unknown> | undefined = undefined;
if (response instanceof LibraryInstallResponse) {
object = LibraryInstallResponse.toObject(false, response);
} else if (response instanceof LibraryUninstallResponse) {
object = LibraryUninstallResponse.toObject(false, response);
} else if (response instanceof ZipLibraryInstallResponse) {
object = ZipLibraryInstallResponse.toObject(false, response);
} else if (response instanceof PlatformInstallResponse) {
object = PlatformInstallResponse.toObject(false, response);
} else if (response instanceof PlatformUninstallResponse) {
object = PlatformUninstallResponse.toObject(false, response);
} else if (response instanceof UpdateIndexResponse) {
object = UpdateIndexResponse.toObject(false, response);
} else if (response instanceof UpdateLibrariesIndexResponse) {
object = UpdateLibrariesIndexResponse.toObject(false, response);
} else if (response instanceof UpdateCoreLibrariesIndexResponse) {
object = UpdateCoreLibrariesIndexResponse.toObject(false, response);
} else if (response instanceof CompileResponse) {
object = CompileResponse.toObject(false, response);
return JSON.stringify(response.toObject(false));
}
function phaseOf(
download: DownloadProgress
): DownloadProgressStart | DownloadProgressUpdate | DownloadProgressEnd {
let start: undefined | DownloadProgressStart = undefined;
let update: undefined | DownloadProgressUpdate = undefined;
let end: undefined | DownloadProgressEnd = undefined;
if (download.hasStart()) {
start = download.getStart();
} else if (download.hasUpdate()) {
update = download.getUpdate();
} else if (download.hasEnd()) {
end = download.getEnd();
} else {
throw new Error(
`Download progress does not have a 'start', 'update', and 'end'. ${JSON.stringify(
download.toObject(false)
)}`
);
}
if (!object) {
console.warn('Unhandled gRPC response', response);
return undefined;
if (start) {
return start;
} 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 readonly total: number;
readonly progressId: string;
readonly results: DownloadResult[];
constructor(
private types: IndexType[],
additionalUrlsCount: number,
private readonly onProgress: (progressMessage: ProgressMessage) => void,
private readonly onError?: ({
progressId,
message,
}: {
progressId: string;
message: string;
}) => void,
private readonly onStart?: (progressId: string) => void,
private readonly onEnd?: (progressId: string) => void
private readonly options: {
onProgress: (progressMessage: ProgressMessage) => void;
onError?: (params: IndexUpdateDidFailParams) => void;
onStart?: (params: IndexUpdateWillStartParams) => void;
onComplete?: (params: IndexUpdateDidCompleteParams) => void;
}
) {
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
// Hence, clients must handle gracefully when no `willUpdate` is received before any `didProgress`.
this.onStart?.(this.progressId);
// Hence, clients must handle gracefully when no `willStart` event is received before any `didProgress`.
this.options.onStart?.({ progressId: this.progressId, types });
}
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 {
this.onProgress({
this.options.onProgress({
message,
progressId: this.progressId,
work: { total: this.total, done: ++this.done },
@ -340,15 +360,44 @@ export class IndexesUpdateProgressHandler {
}
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 {
// +1 for the `package_index.tar.bz2` when updating the platform index.
const totalPlatformIndexCount = additionalUrlsCount + 1;
// The `library_index.json.gz` and `library_index.json.sig` when running the library index update.
const totalLibraryIndexCount = 2;
reportResult(result: DownloadResult): void {
this.results.push(result);
}
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`)
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,
Sketch,
ProgressMessage,
IndexUpdateWillStartParams,
IndexUpdateDidCompleteParams,
IndexUpdateDidFailParams,
} from '../common/protocol';
@injectable()
@ -16,8 +19,8 @@ export class NotificationServiceServerImpl
{
private readonly clients: NotificationServiceClient[] = [];
notifyIndexWillUpdate(progressId: string): void {
this.clients.forEach((client) => client.notifyIndexWillUpdate(progressId));
notifyIndexUpdateWillStart(params: IndexUpdateWillStartParams): void {
this.clients.forEach((client) => client.notifyIndexUpdateWillStart(params));
}
notifyIndexUpdateDidProgress(progressMessage: ProgressMessage): void {
@ -26,20 +29,14 @@ export class NotificationServiceServerImpl
);
}
notifyIndexDidUpdate(progressId: string): void {
this.clients.forEach((client) => client.notifyIndexDidUpdate(progressId));
notifyIndexUpdateDidComplete(params: IndexUpdateDidCompleteParams): void {
this.clients.forEach((client) =>
client.notifyIndexUpdateDidComplete(params)
);
}
notifyIndexUpdateDidFail({
progressId,
message,
}: {
progressId: string;
message: string;
}): void {
this.clients.forEach((client) =>
client.notifyIndexUpdateDidFail({ progressId, message })
);
notifyIndexUpdateDidFail(params: IndexUpdateDidFailParams): void {
this.clients.forEach((client) => client.notifyIndexUpdateDidFail(params));
}
notifyDaemonDidStart(port: string): void {

View File

@ -408,6 +408,11 @@
"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."
},
"updateIndexes": {
"updateIndexes": "Update Indexes",
"updateLibraryIndex": "Update Library Index",
"updatePackageIndex": "Update Package Index"
},
"upload": {
"error": "{0} error: {1}"
},