11import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application' ;
2+ import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state' ;
23import { StorageService } from '@theia/core/lib/browser/storage-service' ;
4+ import type {
5+ Command ,
6+ CommandContribution ,
7+ CommandRegistry ,
8+ } from '@theia/core/lib/common/command' ;
39import { DisposableCollection } from '@theia/core/lib/common/disposable' ;
410import { Emitter , Event } from '@theia/core/lib/common/event' ;
511import { ILogger } from '@theia/core/lib/common/logger' ;
6- import { deepClone } from '@theia/core/lib/common/objects' ;
12+ import { deepClone , deepFreeze } from '@theia/core/lib/common/objects' ;
713import { inject , injectable , named } from '@theia/core/shared/inversify' ;
814import {
915 BoardDetails ,
1016 BoardsService ,
1117 ConfigOption ,
1218 Programmer ,
19+ isBoardIdentifierChangeEvent ,
1320} from '../../common/protocol' ;
1421import { notEmpty } from '../../common/utils' ;
22+ import type {
23+ StartupTask ,
24+ StartupTaskProvider ,
25+ } from '../../electron-common/startup-task' ;
1526import { NotificationCenter } from '../notification-center' ;
27+ import { BoardsServiceProvider } from './boards-service-provider' ;
1628
1729@injectable ( )
18- export class BoardsDataStore implements FrontendApplicationContribution {
30+ export class BoardsDataStore
31+ implements
32+ FrontendApplicationContribution ,
33+ StartupTaskProvider ,
34+ CommandContribution
35+ {
1936 @inject ( ILogger )
2037 @named ( 'store' )
2138 private readonly logger : ILogger ;
@@ -28,44 +45,110 @@ export class BoardsDataStore implements FrontendApplicationContribution {
2845 // In other words, store the data (such as the board configs) per sketch, not per IDE2 installation. https://github.com/arduino/arduino-ide/issues/2240
2946 @inject ( StorageService )
3047 private readonly storageService : StorageService ;
48+ @inject ( BoardsServiceProvider )
49+ private readonly boardsServiceProvider : BoardsServiceProvider ;
50+ @inject ( FrontendApplicationStateService )
51+ private readonly appStateService : FrontendApplicationStateService ;
3152
32- private readonly onChangedEmitter = new Emitter < string [ ] > ( ) ;
33- private readonly toDispose = new DisposableCollection ( this . onChangedEmitter ) ;
53+ private readonly onDidChangeEmitter =
54+ new Emitter < BoardsDataStoreChangeEvent > ( ) ;
55+ private readonly toDispose = new DisposableCollection (
56+ this . onDidChangeEmitter
57+ ) ;
58+ private _selectedBoardData : BoardsDataStoreChange | undefined ;
3459
3560 onStart ( ) : void {
36- this . toDispose . push (
61+ this . toDispose . pushAll ( [
62+ this . boardsServiceProvider . onBoardsConfigDidChange ( ( event ) => {
63+ if ( isBoardIdentifierChangeEvent ( event ) ) {
64+ this . updateSelectedBoardData ( event . selectedBoard ?. fqbn ) ;
65+ }
66+ } ) ,
3767 this . notificationCenter . onPlatformDidInstall ( async ( { item } ) => {
38- const dataDidChangePerFqbn : string [ ] = [ ] ;
39- for ( const fqbn of item . boards
68+ const boardsWithFqbn = item . boards
4069 . map ( ( { fqbn } ) => fqbn )
41- . filter ( notEmpty )
42- . filter ( ( fqbn ) => ! ! fqbn ) ) {
70+ . filter ( notEmpty ) ;
71+ const changes : BoardsDataStoreChange [ ] = [ ] ;
72+ for ( const fqbn of boardsWithFqbn ) {
4373 const key = this . getStorageKey ( fqbn ) ;
44- let data = await this . storageService . getData < ConfigOption [ ] > ( key ) ;
45- if ( ! data || ! data . length ) {
46- const details = await this . getBoardDetailsSafe ( fqbn ) ;
47- if ( details ) {
48- data = details . configOptions ;
49- if ( data . length ) {
50- await this . storageService . setData ( key , data ) ;
51- dataDidChangePerFqbn . push ( fqbn ) ;
52- }
53- }
74+ const storedData =
75+ await this . storageService . getData < BoardsDataStore . Data > ( key ) ;
76+ if ( ! storedData ) {
77+ // if not previously value is available for the board, do not update the cache
78+ continue ;
79+ }
80+ const details = await this . loadBoardDetails ( fqbn ) ;
81+ if ( details ) {
82+ const data = createDataStoreEntry ( details ) ;
83+ await this . storageService . setData ( key , data ) ;
84+ changes . push ( { fqbn, data } ) ;
5485 }
5586 }
56- if ( dataDidChangePerFqbn . length ) {
57- this . fireChanged ( ...dataDidChangePerFqbn ) ;
87+ if ( changes . length ) {
88+ this . fireChanged ( ...changes ) ;
5889 }
59- } )
90+ } ) ,
91+ ] ) ;
92+
93+ Promise . all ( [
94+ this . boardsServiceProvider . ready ,
95+ this . appStateService . reachedState ( 'ready' ) ,
96+ ] ) . then ( ( ) =>
97+ this . updateSelectedBoardData (
98+ this . boardsServiceProvider . boardsConfig . selectedBoard ?. fqbn
99+ )
60100 ) ;
61101 }
62102
103+ private async getSelectedBoardData (
104+ fqbn : string | undefined
105+ ) : Promise < BoardsDataStoreChange | undefined > {
106+ if ( ! fqbn ) {
107+ return undefined ;
108+ } else {
109+ const data = await this . getData ( fqbn ) ;
110+ if ( data === BoardsDataStore . Data . EMPTY ) {
111+ return undefined ;
112+ }
113+ return { fqbn, data } ;
114+ }
115+ }
116+
117+ private async updateSelectedBoardData (
118+ fqbn : string | undefined
119+ ) : Promise < void > {
120+ this . _selectedBoardData = await this . getSelectedBoardData ( fqbn ) ;
121+ }
122+
63123 onStop ( ) : void {
64124 this . toDispose . dispose ( ) ;
65125 }
66126
67- get onChanged ( ) : Event < string [ ] > {
68- return this . onChangedEmitter . event ;
127+ registerCommands ( registry : CommandRegistry ) : void {
128+ registry . registerCommand ( USE_INHERITED_DATA , {
129+ execute : async ( arg : unknown ) => {
130+ if ( isBoardsDataStoreChange ( arg ) ) {
131+ await this . setData ( arg ) ;
132+ this . fireChanged ( arg ) ;
133+ }
134+ } ,
135+ } ) ;
136+ }
137+
138+ tasks ( ) : StartupTask [ ] {
139+ if ( ! this . _selectedBoardData ) {
140+ return [ ] ;
141+ }
142+ return [
143+ {
144+ command : USE_INHERITED_DATA . id ,
145+ args : [ this . _selectedBoardData ] ,
146+ } ,
147+ ] ;
148+ }
149+
150+ get onDidChange ( ) : Event < BoardsDataStoreChangeEvent > {
151+ return this . onDidChangeEmitter . event ;
69152 }
70153
71154 async appendConfigToFqbn (
@@ -84,22 +167,19 @@ export class BoardsDataStore implements FrontendApplicationContribution {
84167 }
85168
86169 const key = this . getStorageKey ( fqbn ) ;
87- let data = await this . storageService . getData <
170+ const storedData = await this . storageService . getData <
88171 BoardsDataStore . Data | undefined
89172 > ( key , undefined ) ;
90- if ( BoardsDataStore . Data . is ( data ) ) {
91- return data ;
173+ if ( BoardsDataStore . Data . is ( storedData ) ) {
174+ return storedData ;
92175 }
93176
94177 const boardDetails = await this . getBoardDetailsSafe ( fqbn ) ;
95178 if ( ! boardDetails ) {
96179 return BoardsDataStore . Data . EMPTY ;
97180 }
98181
99- data = {
100- configOptions : boardDetails . configOptions ,
101- programmers : boardDetails . programmers ,
102- } ;
182+ const data = createDataStoreEntry ( boardDetails ) ;
103183 await this . storageService . setData ( key , data ) ;
104184 return data ;
105185 }
@@ -111,17 +191,15 @@ export class BoardsDataStore implements FrontendApplicationContribution {
111191 fqbn : string ;
112192 selectedProgrammer : Programmer ;
113193 } ) : Promise < boolean > {
114- const data = deepClone ( await this . getData ( fqbn ) ) ;
115- const { programmers } = data ;
194+ const storedData = deepClone ( await this . getData ( fqbn ) ) ;
195+ const { programmers } = storedData ;
116196 if ( ! programmers . find ( ( p ) => Programmer . equals ( selectedProgrammer , p ) ) ) {
117197 return false ;
118198 }
119199
120- await this . setData ( {
121- fqbn,
122- data : { ...data , selectedProgrammer } ,
123- } ) ;
124- this . fireChanged ( fqbn ) ;
200+ const data = { ...storedData , selectedProgrammer } ;
201+ await this . setData ( { fqbn, data } ) ;
202+ this . fireChanged ( { fqbn, data } ) ;
125203 return true ;
126204 }
127205
@@ -153,17 +231,12 @@ export class BoardsDataStore implements FrontendApplicationContribution {
153231 return false ;
154232 }
155233 await this . setData ( { fqbn, data } ) ;
156- this . fireChanged ( fqbn ) ;
234+ this . fireChanged ( { fqbn, data } ) ;
157235 return true ;
158236 }
159237
160- protected async setData ( {
161- fqbn,
162- data,
163- } : {
164- fqbn : string ;
165- data : BoardsDataStore . Data ;
166- } ) : Promise < void > {
238+ protected async setData ( change : BoardsDataStoreChange ) : Promise < void > {
239+ const { fqbn, data } = change ;
167240 const key = this . getStorageKey ( fqbn ) ;
168241 return this . storageService . setData ( key , data ) ;
169242 }
@@ -176,7 +249,7 @@ export class BoardsDataStore implements FrontendApplicationContribution {
176249 fqbn : string
177250 ) : Promise < BoardDetails | undefined > {
178251 try {
179- const details = this . boardsService . getBoardDetails ( { fqbn } ) ;
252+ const details = await this . boardsService . getBoardDetails ( { fqbn } ) ;
180253 return details ;
181254 } catch ( err ) {
182255 if (
@@ -197,8 +270,8 @@ export class BoardsDataStore implements FrontendApplicationContribution {
197270 }
198271 }
199272
200- protected fireChanged ( ...fqbn : string [ ] ) : void {
201- this . onChangedEmitter . fire ( fqbn ) ;
273+ protected fireChanged ( ...changes : BoardsDataStoreChange [ ] ) : void {
274+ this . onDidChangeEmitter . fire ( { changes } ) ;
202275 }
203276}
204277
@@ -209,11 +282,13 @@ export namespace BoardsDataStore {
209282 readonly selectedProgrammer ?: Programmer ;
210283 }
211284 export namespace Data {
212- export const EMPTY : Data = {
285+ export const EMPTY : Data = deepFreeze ( {
213286 configOptions : [ ] ,
214287 programmers : [ ] ,
215- } ;
216- export function is ( arg : any ) : arg is Data {
288+ defaultProgrammerId : undefined ,
289+ } ) ;
290+
291+ export function is ( arg : unknown ) : arg is Data {
217292 return (
218293 ! ! arg &&
219294 'configOptions' in arg &&
@@ -224,3 +299,61 @@ export namespace BoardsDataStore {
224299 }
225300 }
226301}
302+
303+ export function isEmptyData ( data : BoardsDataStore . Data ) : boolean {
304+ return (
305+ Boolean ( ! data . configOptions . length ) &&
306+ Boolean ( ! data . programmers . length ) &&
307+ Boolean ( ! data . selectedProgrammer )
308+ ) ;
309+ }
310+
311+ export function findDefaultProgrammer (
312+ programmers : readonly Programmer [ ] ,
313+ defaultProgrammerId : string | undefined | BoardsDataStore . Data
314+ ) : Programmer | undefined {
315+ if ( ! defaultProgrammerId ) {
316+ return undefined ;
317+ }
318+ const id =
319+ typeof defaultProgrammerId === 'string'
320+ ? defaultProgrammerId
321+ : defaultProgrammerId . defaultProgrammerId ;
322+ return programmers . find ( ( p ) => p . id === id ) ;
323+ }
324+ function createDataStoreEntry ( details : BoardDetails ) : BoardsDataStore . Data {
325+ const configOptions = details . configOptions . slice ( ) ;
326+ const programmers = details . programmers . slice ( ) ;
327+ const selectedProgrammer = findDefaultProgrammer (
328+ programmers ,
329+ details . defaultProgrammerId
330+ ) ;
331+ return {
332+ configOptions,
333+ programmers,
334+ defaultProgrammerId : details . defaultProgrammerId ,
335+ selectedProgrammer,
336+ } ;
337+ }
338+
339+ export interface BoardsDataStoreChange {
340+ readonly fqbn : string ;
341+ readonly data : BoardsDataStore . Data ;
342+ }
343+
344+ function isBoardsDataStoreChange ( arg : unknown ) : arg is BoardsDataStoreChange {
345+ return (
346+ typeof arg === 'object' &&
347+ arg !== null &&
348+ typeof ( < BoardsDataStoreChange > arg ) . fqbn === 'string' &&
349+ BoardsDataStore . Data . is ( ( < BoardsDataStoreChange > arg ) . data )
350+ ) ;
351+ }
352+
353+ export interface BoardsDataStoreChangeEvent {
354+ readonly changes : readonly BoardsDataStoreChange [ ] ;
355+ }
356+
357+ const USE_INHERITED_DATA : Command = {
358+ id : 'arduino-use-inherited-boards-data' ,
359+ } ;
0 commit comments