Skip to content

Commit 23906d9

Browse files
author
Akos Kitta
committed
fix: encoding when reading a cloud sketch
Closes #449 Closes #634 Signed-off-by: Akos Kitta <[email protected]>
1 parent f9c5888 commit 23906d9

File tree

5 files changed

+50
-57
lines changed

5 files changed

+50
-57
lines changed

Diff for: arduino-ide-extension/src/browser/create/create-api.ts

+5-48
Original file line numberDiff line numberDiff line change
@@ -2,66 +2,24 @@ import { MaybePromise } from '@theia/core/lib/common/types';
22
import { inject, injectable } from '@theia/core/shared/inversify';
33
import { fetch } from 'cross-fetch';
44
import { SketchesService } from '../../common/protocol';
5+
import { unit8ArrayToString } from '../../common/utils';
56
import { ArduinoPreferences } from '../arduino-preferences';
67
import { AuthenticationClientService } from '../auth/authentication-client-service';
78
import { SketchCache } from '../widgets/cloud-sketchbook/cloud-sketch-cache';
89
import * as createPaths from './create-paths';
910
import { posix } from './create-paths';
1011
import { Create, CreateError } from './typings';
1112

12-
export interface ResponseResultProvider {
13+
interface ResponseResultProvider {
1314
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1415
(response: Response): Promise<any>;
1516
}
16-
export namespace ResponseResultProvider {
17+
namespace ResponseResultProvider {
1718
export const NOOP: ResponseResultProvider = async () => undefined;
1819
export const TEXT: ResponseResultProvider = (response) => response.text();
1920
export const JSON: ResponseResultProvider = (response) => response.json();
2021
}
2122

22-
// TODO: check if this is still needed: https://github.com/electron/electron/issues/18733
23-
// The original issue was reported for Electron 5.x and 6.x. Theia uses 15.x
24-
export function Utf8ArrayToStr(array: Uint8Array): string {
25-
let out, i, c;
26-
let char2, char3;
27-
28-
out = '';
29-
const len = array.length;
30-
i = 0;
31-
while (i < len) {
32-
c = array[i++];
33-
switch (c >> 4) {
34-
case 0:
35-
case 1:
36-
case 2:
37-
case 3:
38-
case 4:
39-
case 5:
40-
case 6:
41-
case 7:
42-
// 0xxxxxxx
43-
out += String.fromCharCode(c);
44-
break;
45-
case 12:
46-
case 13:
47-
// 110x xxxx 10xx xxxx
48-
char2 = array[i++];
49-
out += String.fromCharCode(((c & 0x1f) << 6) | (char2 & 0x3f));
50-
break;
51-
case 14:
52-
// 1110 xxxx 10xx xxxx 10xx xxxx
53-
char2 = array[i++];
54-
char3 = array[i++];
55-
out += String.fromCharCode(
56-
((c & 0x0f) << 12) | ((char2 & 0x3f) << 6) | ((char3 & 0x3f) << 0)
57-
);
58-
break;
59-
}
60-
}
61-
62-
return out;
63-
}
64-
6523
type ResourceType = 'f' | 'd';
6624

6725
@injectable()
@@ -330,10 +288,9 @@ export class CreateApi {
330288
if (sketch) {
331289
const url = new URL(`${this.domain()}/sketches/${sketch.id}`);
332290
const headers = await this.headers();
333-
334291
// parse the secret file
335292
const secrets = (
336-
typeof content === 'string' ? content : Utf8ArrayToStr(content)
293+
typeof content === 'string' ? content : unit8ArrayToString(content)
337294
)
338295
.split(/\r?\n/)
339296
.reduce((prev, curr) => {
@@ -397,7 +354,7 @@ export class CreateApi {
397354
const headers = await this.headers();
398355

399356
let data: string =
400-
typeof content === 'string' ? content : Utf8ArrayToStr(content);
357+
typeof content === 'string' ? content : unit8ArrayToString(content);
401358
data = await this.toggleSecretsInclude(posixPath, data, 'remove');
402359

403360
const payload = { data: btoa(data) };

Diff for: arduino-ide-extension/src/browser/create/create-fs-provider.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { CreateUri } from './create-uri';
2929
import { SketchesService } from '../../common/protocol';
3030
import { ArduinoPreferences } from '../arduino-preferences';
3131
import { Create } from './typings';
32+
import { stringToUint8Array } from '../../common/utils';
3233

3334
@injectable()
3435
export class CreateFsProvider
@@ -154,7 +155,7 @@ export class CreateFsProvider
154155

155156
async readFile(uri: URI): Promise<Uint8Array> {
156157
const content = await this.getCreateApi.readFile(uri.path.toString());
157-
return new TextEncoder().encode(content);
158+
return stringToUint8Array(content);
158159
}
159160

160161
async writeFile(

Diff for: arduino-ide-extension/src/browser/theia/core/connection-status-service.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ import {
1717
} from '@theia/core/shared/inversify';
1818
import { NotificationManager } from '@theia/messages/lib/browser/notifications-manager';
1919
import { ArduinoDaemon } from '../../../common/protocol';
20+
import { assertUnreachable } from '../../../common/utils';
21+
import { CreateFeatures } from '../../create/create-features';
2022
import { NotificationCenter } from '../../notification-center';
2123
import debounce = require('lodash.debounce');
2224
import isOnline = require('is-online');
23-
import { CreateFeatures } from '../../create/create-features';
2425

2526
@injectable()
2627
export class IsOnline implements FrontendApplicationContribution {
@@ -321,8 +322,3 @@ function getOfflineTooltip(statusType: OfflineConnectionStatus): string {
321322
assertUnreachable(statusType);
322323
}
323324
}
324-
325-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
326-
function assertUnreachable(_: never): never {
327-
throw new Error();
328-
}

Diff for: arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree.ts

+23-2
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,19 @@ import {
2525
import { CloudSketchbookCommands } from './cloud-sketchbook-commands';
2626
import { DoNotAskAgainConfirmDialog } from '../../dialogs/do-not-ask-again-dialog';
2727
import { SketchbookTree } from '../sketchbook/sketchbook-tree';
28-
import { firstToUpperCase } from '../../../common/utils';
28+
import { assertUnreachable } from '../../../common/utils';
2929
import { FileStat } from '@theia/filesystem/lib/common/files';
3030
import { WorkspaceNode } from '@theia/navigator/lib/browser/navigator-tree';
3131
import { posix, splitSketchPath } from '../../create/create-paths';
3232
import { Create } from '../../create/typings';
3333
import { nls } from '@theia/core/lib/common';
3434
import { ApplicationConnectionStatusContribution } from '../../theia/core/connection-status-service';
3535
import { ExecuteWithProgress } from '../../../common/protocol/progressible';
36+
import {
37+
synchronizingSketchbook,
38+
pullingSketch,
39+
pushingSketch,
40+
} from '../../contributions/cloud-contribution';
3641

3742
const MESSAGE_TIMEOUT = 5 * 1000;
3843
const deepmerge = require('deepmerge').default;
@@ -327,7 +332,7 @@ export class CloudSketchbookTree extends SketchbookTree {
327332
): Promise<T> {
328333
const name = node.uri.path.name;
329334
return ExecuteWithProgress.withProgress(
330-
`${firstToUpperCase(state)} '${name}'`,
335+
this.taskMessage(state, name),
331336
this.messageService,
332337
async (progress) => {
333338
progress.report({ work: { done: 0, total: NaN } });
@@ -336,6 +341,22 @@ export class CloudSketchbookTree extends SketchbookTree {
336341
);
337342
}
338343

344+
private taskMessage(
345+
state: CloudSketchbookTree.CloudSketchDirNode.State,
346+
input: string
347+
): string {
348+
switch (state) {
349+
case 'syncing':
350+
return synchronizingSketchbook;
351+
case 'pulling':
352+
return pullingSketch(input);
353+
case 'pushing':
354+
return pushingSketch(input);
355+
default:
356+
assertUnreachable(state);
357+
}
358+
}
359+
339360
private async sync(source: URI, dest: URI): Promise<void> {
340361
const { filesToWrite, filesToDelete } = await this.treeDiff(source, dest);
341362
await Promise.all(

Diff for: arduino-ide-extension/src/common/utils.ts

+18
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,21 @@ export function startsWithUpperCase(what: string): boolean {
2020
export function isNullOrUndefined(what: unknown): what is undefined | null {
2121
return what === undefined || what === null;
2222
}
23+
24+
// Use it for and exhaustive `switch` statements
25+
// https://stackoverflow.com/a/39419171/5529090
26+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
27+
export function assertUnreachable(_: never): never {
28+
throw new Error();
29+
}
30+
31+
// Text encoder can crash in electron browser: https://github.com/arduino/arduino-ide/issues/634#issuecomment-1440039171
32+
export function unit8ArrayToString(uint8Array: Uint8Array): string {
33+
return uint8Array.reduce(
34+
(text, byte) => text + String.fromCharCode(byte),
35+
''
36+
);
37+
}
38+
export function stringToUint8Array(text: string): Uint8Array {
39+
return Uint8Array.from(text, (char) => char.charCodeAt(0));
40+
}

0 commit comments

Comments
 (0)