@@ -17,13 +17,47 @@ const { getLogger } = require('./wktLogging');
17
17
const { sendToWindow } = require ( './windowUtils' ) ;
18
18
const i18n = require ( './i18next.config' ) ;
19
19
const { CredentialStoreManager, EncryptedCredentialManager, CredentialNoStoreManager } = require ( './credentialManager' ) ;
20
+ const errorUtils = require ( './errorUtils' ) ;
20
21
21
22
const projectFileTypeKey = 'dialog-wktFileType' ;
22
23
const projectFileExtension = 'wktproj' ;
23
24
const emptyProjectContents = { } ;
24
25
25
26
const openProjects = new Map ( ) ;
26
27
28
+ // This file is the central file controlling all the project create and save functionality.
29
+ // As such, there are a number of flows that make 1 or more calls into the methods in this file.
30
+ //
31
+ // Create New Project menu item flow:
32
+ // - The entry point is createNewProject():
33
+ // + asks the user for the project file location
34
+ // + sends the start-new-project message to the renderer
35
+ // - On receiving the start-new-project message:
36
+ // + the renderer gathers any project-related state
37
+ // + sends the new-project message
38
+ // - The new-project message calls initializeNewProject():
39
+ // + writes the file
40
+ // + handles other necessary state management if the file write succeeds
41
+ //
42
+ // Save All menu item flow:
43
+ // - The entry point is startSaveProject():
44
+ // + sends the start-save-project message to renderer
45
+ // - On receiving the start-save-project message:
46
+ // + determines if the project needs saving
47
+ // + invokes confirm-project-file
48
+ // - The confirm-project-file calls confirmProjectFile():
49
+ // + gets the project file name, prompting the user if required
50
+ // + returns the name, uuid, and file name (or null if file is not selected)
51
+ // - On receiving the response to the confirm-project-file invocation, the renderer:
52
+ // + gathers the project-related data
53
+ // + invokes save-project
54
+ // - The save-project calls saveProject()
55
+ // + saves the project file
56
+ // + if project file save succeeds, saves any model files
57
+ // + returns whether the save was successful and the model file contents
58
+ // - On receiving the response to the save-project invocation, the renderer ends the flow
59
+ //
60
+
27
61
// Public methods
28
62
//
29
63
function isWktProjectFile ( filename ) {
@@ -45,24 +79,18 @@ function showExistingProjectWindow(existingProjectWindow) {
45
79
}
46
80
47
81
async function createNewProject ( targetWindow ) {
48
- const saveResponse = await dialog . showSaveDialog ( targetWindow , {
49
- title : 'Create WebLogic Kubernetes Toolkit Project' ,
50
- buttonLabel : 'Create Project' ,
51
- filters : [
52
- { name : i18n . t ( projectFileTypeKey ) , extensions : [ projectFileExtension ] }
53
- ] ,
54
- properties : [
55
- 'createDirectory' ,
56
- 'showOverwriteConfirmation'
57
- ]
82
+ const titleKey = 'dialog-createNewProjectTitle' ;
83
+ const buttonKey = 'button-create' ;
84
+
85
+ return new Promise ( resolve => {
86
+ _chooseProjectSaveFile ( targetWindow , titleKey , buttonKey ) . then ( projectFileName => {
87
+ if ( projectFileName ) {
88
+ sendToWindow ( targetWindow , 'start-new-project' , projectFileName ) ;
89
+ // window will reply with new-project -> initializeNewProject() including isDirty flag
90
+ }
91
+ resolve ( ) ;
92
+ } ) ;
58
93
} ) ;
59
-
60
- if ( saveResponse . canceled || ! saveResponse . filePath || projectFileAlreadyOpen ( saveResponse . filePath ) ) {
61
- return ;
62
- }
63
-
64
- sendToWindow ( targetWindow , 'start-new-project' , saveResponse . filePath ) ;
65
- // window will reply with new-project -> initializeNewProject() including isDirty flag
66
94
}
67
95
68
96
async function initializeNewProject ( targetWindow , projectFile , isDirty ) {
@@ -72,21 +100,21 @@ async function initializeNewProject(targetWindow, projectFile, isDirty) {
72
100
return ;
73
101
}
74
102
75
- let projectFileName = getProjectFileName ( projectFile ) ;
76
- if ( path . extname ( projectFileName ) !== `.${ projectFileExtension } ` ) {
77
- projectFileName = `${ projectFileName } .${ projectFileExtension } ` ;
78
- }
103
+ const projectFileName = getProjectFileName ( projectFile ) ;
79
104
const wktWindow = require ( './wktWindow' ) ;
80
- if ( projectWindow . id === targetWindow . id ) {
81
- wktWindow . setTitleFileName ( projectWindow , projectFileName , false ) ;
82
- } else {
83
- projectWindow . on ( 'ready-to-show' , ( ) => {
105
+
106
+ const wroteFile = await _createNewProjectFile ( projectWindow , projectFileName ) ;
107
+ if ( wroteFile ) {
108
+ if ( projectWindow . id === targetWindow . id ) {
84
109
wktWindow . setTitleFileName ( projectWindow , projectFileName , false ) ;
85
- } ) ;
110
+ } else {
111
+ projectWindow . on ( 'ready-to-show' , ( ) => {
112
+ wktWindow . setTitleFileName ( projectWindow , projectFileName , false ) ;
113
+ } ) ;
114
+ }
115
+ app . addRecentDocument ( projectFileName ) ;
116
+ projectWindow . setRepresentedFilename ( projectFileName ) ;
86
117
}
87
- await _createNewProjectFile ( projectWindow , projectFileName ) ;
88
- app . addRecentDocument ( projectFileName ) ;
89
- projectWindow . setRepresentedFilename ( projectFileName ) ;
90
118
}
91
119
92
120
async function openProject ( targetWindow ) {
@@ -132,7 +160,7 @@ async function openProjectFile(targetWindow, projectFile, isDirty) {
132
160
. catch ( err => {
133
161
dialog . showErrorBox (
134
162
i18n . t ( 'dialog-openProjectFileErrorTitle' ) ,
135
- i18n . t ( 'dialog-openProjectFileErrorMessage' , { projectFileName : projectFile , err : err } ) ,
163
+ i18n . t ( 'dialog-openProjectFileErrorMessage' , { projectFileName : projectFile , err : errorUtils . getErrorMessage ( err ) } ) ,
136
164
) ;
137
165
getLogger ( ) . error ( 'Failed to open project file %s: %s' , projectFile , err ) ;
138
166
} ) ;
@@ -149,7 +177,10 @@ async function confirmProjectFile(targetWindow) {
149
177
projectFile = await _chooseProjectSaveFile ( targetWindow ) ;
150
178
projectName = projectFile ? _generateProjectName ( projectFile ) : null ;
151
179
projectUuid = projectFile ? _generateProjectUuid ( ) : null ;
152
- app . addRecentDocument ( projectFile ) ;
180
+ if ( projectFile ) {
181
+ getLogger ( ) . debug ( 'confirmProjectFile adding %s to recent documents' , projectFile ) ;
182
+ app . addRecentDocument ( projectFile ) ;
183
+ }
153
184
}
154
185
return [ projectFile , projectName , projectUuid ] ;
155
186
}
@@ -161,7 +192,10 @@ async function chooseProjectFile(targetWindow) {
161
192
const projectFile = await _chooseProjectSaveFile ( targetWindow ) ;
162
193
const projectName = projectFile ? _generateProjectName ( projectFile ) : null ;
163
194
const projectUuid = projectFile ? _generateProjectUuid ( ) : null ;
164
- app . addRecentDocument ( projectFile ) ;
195
+ if ( projectFile ) {
196
+ getLogger ( ) . debug ( 'chooseProjectFile adding %s to recent documents' , projectFile ) ;
197
+ app . addRecentDocument ( projectFile ) ;
198
+ }
165
199
return [ projectFile , projectName , projectUuid ] ;
166
200
}
167
201
@@ -179,15 +213,43 @@ function startSaveProjectAs(targetWindow) {
179
213
180
214
// save the specified project and model contents to the project file.
181
215
// usually invoked by the save-project IPC invocation.
182
- async function saveProject ( targetWindow , projectFile , projectContents , externalFileContents ) {
216
+ async function saveProject ( targetWindow , projectFile , projectContents , externalFileContents , showErrors = true ) {
183
217
// the result will contain only sections that were updated due to save, such as model.archiveFiles
184
- const saveResult = { } ;
218
+ const saveResult = {
219
+ isProjectFileSaved : false ,
220
+ areModelFilesSaved : false
221
+ } ;
185
222
186
- _assignProjectFile ( targetWindow , projectFile ) ;
187
- saveResult [ 'model' ] = await _saveExternalFileContents ( _getProjectDirectory ( targetWindow ) , externalFileContents ) ;
188
- await _saveProjectFile ( targetWindow , projectFile , projectContents ) ;
189
- const wktWindow = require ( './wktWindow' ) ;
190
- wktWindow . setTitleFileName ( targetWindow , projectFile , false ) ;
223
+ const assignProjectFileData = _assignProjectFile ( targetWindow , projectFile ) ;
224
+ try {
225
+ await _saveProjectFile ( targetWindow , projectFile , projectContents ) ;
226
+ saveResult . isProjectFileSaved = true ;
227
+ } catch ( err ) {
228
+ if ( showErrors ) {
229
+ _showSaveError ( projectFile , err ) ;
230
+ }
231
+ getLogger ( ) . error ( 'Failed to save project file %s: %s' , projectFile , err ) ;
232
+ // revert the project assignment to the window
233
+ _revertAssignProjectFile ( assignProjectFileData ) ;
234
+ saveResult . reason = i18n . t ( 'dialog-saveProjectFileErrorMessage' , { projectFileName : projectFile , err : err } ) ;
235
+ }
236
+
237
+ if ( saveResult . isProjectFileSaved ) {
238
+ const wktWindow = require ( './wktWindow' ) ;
239
+ wktWindow . setTitleFileName ( targetWindow , projectFile , false ) ;
240
+ try {
241
+ saveResult [ 'model' ] = await _saveExternalFileContents ( _getProjectDirectory ( targetWindow ) , externalFileContents ) ;
242
+ saveResult . areModelFilesSaved = true ;
243
+ } catch ( err ) {
244
+ const message = i18n . t ( 'project-save-model-files-error-message' , { error : errorUtils . getErrorMessage ( err ) } ) ;
245
+ if ( showErrors ) {
246
+ const title = i18n . t ( 'project-save-model-files-error-title' ) ;
247
+ dialog . showErrorBox ( title , message ) ;
248
+ }
249
+ getLogger ( ) . error ( 'Failed to save one of the model files for project file %s: %s' , projectFile , err ) ;
250
+ saveResult . reason = message ;
251
+ }
252
+ }
191
253
return saveResult ;
192
254
}
193
255
@@ -310,26 +372,31 @@ async function exportArchiveFile(targetWindow, archivePath, projectFile) {
310
372
// Private helper methods
311
373
//
312
374
async function _createNewProjectFile ( targetWindow , projectFileName ) {
375
+ getLogger ( ) . debug ( 'entering _createNewProjectFile() for %s' , projectFileName ) ;
313
376
return new Promise ( ( resolve ) => {
314
377
const projectContents = _addProjectIdentifiers ( projectFileName , emptyProjectContents ) ;
315
378
const projectContentsJson = JSON . stringify ( projectContents , null , 2 ) ;
316
379
writeFile ( projectFileName , projectContentsJson , { encoding : 'utf8' } )
317
380
. then ( ( ) => {
318
381
_addOpenProject ( targetWindow , projectFileName , false , new CredentialStoreManager ( projectContents . uuid ) ) ;
319
382
sendToWindow ( targetWindow , 'project-created' , projectFileName , projectContents ) ;
320
- resolve ( ) ;
383
+ resolve ( true ) ;
321
384
} )
322
385
. catch ( err => {
323
- dialog . showErrorBox (
324
- i18n . t ( 'dialog-saveProjectFileErrorTitle' ) ,
325
- i18n . t ( 'dialog-saveProjectFileErrorMessage' , { projectFileName : projectFileName , err : err } ) ,
326
- ) ;
386
+ _showSaveError ( projectFileName , err ) ;
327
387
getLogger ( ) . error ( 'Failed to save new project in file %s: %s' , projectFileName , err ) ;
328
- resolve ( ) ;
388
+ resolve ( false ) ;
329
389
} ) ;
330
390
} ) ;
331
391
}
332
392
393
+ function _showSaveError ( projectFileName , err ) {
394
+ dialog . showErrorBox (
395
+ i18n . t ( 'dialog-saveProjectFileErrorTitle' ) ,
396
+ i18n . t ( 'dialog-saveProjectFileErrorMessage' , { projectFileName : projectFileName , err : err } ) ,
397
+ ) ;
398
+ }
399
+
333
400
function _addProjectIdentifiers ( projectFileName , projectContents ) {
334
401
const alreadyHasName = Object . prototype . hasOwnProperty . call ( projectContents , 'name' ) ;
335
402
const alreadyHasGuid = Object . prototype . hasOwnProperty . call ( projectContents , 'uuid' ) ;
@@ -385,6 +452,7 @@ async function _openProjectFile(targetWindow, projectFileName) {
385
452
const wktWindow = require ( './wktWindow' ) ;
386
453
wktWindow . setTitleFileName ( targetWindow , projectFileName , false ) ;
387
454
targetWindow . setRepresentedFilename ( projectFileName ) ;
455
+ getLogger ( ) . debug ( '_openProjectFile adding %s to recent documents' , projectFileName ) ;
388
456
app . addRecentDocument ( projectFileName ) ;
389
457
resolve ( ) ;
390
458
} ) . catch ( err => reject ( err ) ) ;
@@ -517,16 +585,16 @@ async function _sendProjectOpened(targetWindow, file, jsonContents) {
517
585
sendToWindow ( targetWindow , 'project-opened' , file , jsonContents , modelFilesContentJson ) ;
518
586
}
519
587
520
- async function _chooseProjectSaveFile ( targetWindow ) {
521
- const title = i18n . t ( 'dialog-chooseProjectSaveFile' ) ;
588
+ async function _chooseProjectSaveFile ( targetWindow , titleKey = 'dialog-chooseProjectSaveFile' , buttonKey = 'button-save' ) {
589
+ const title = i18n . t ( titleKey ) ;
522
590
523
591
let saveResponse = await dialog . showSaveDialog ( targetWindow , {
524
592
title : title ,
525
593
message : title ,
526
594
filters : [
527
595
{ name : i18n . t ( projectFileTypeKey ) , extensions : [ projectFileExtension ] }
528
596
] ,
529
- buttonLabel : i18n . t ( 'button-save' ) ,
597
+ buttonLabel : i18n . t ( buttonKey ) ,
530
598
properties : [
531
599
'createDirectory' ,
532
600
'showOverwriteConfirmation'
@@ -536,6 +604,17 @@ async function _chooseProjectSaveFile(targetWindow) {
536
604
if ( saveResponse . canceled || ! saveResponse . filePath || projectFileAlreadyOpen ( saveResponse . filePath ) ) {
537
605
return null ;
538
606
}
607
+
608
+ // Do a quick sanity check to make sure that the user has permissions to
609
+ // write to the directory chosen. If not, show them the error and return null.
610
+ //
611
+ if ( ! await fsUtils . canWriteInDirectory ( saveResponse . filePath ) ) {
612
+ const errTitle = i18n . t ( 'dialog-projectSaveFileLocationNotWritableTitle' ) ;
613
+ const errMessage = i18n . t ( 'dialog-projectSaveFileLocationNotWritableError' ,
614
+ { projectFileDirectory : path . dirname ( saveResponse . filePath ) } ) ;
615
+ dialog . showErrorBox ( errTitle , errMessage ) ;
616
+ return null ;
617
+ }
539
618
return getProjectFileName ( saveResponse . filePath ) ;
540
619
}
541
620
@@ -751,6 +830,21 @@ function _assignProjectFile(targetWindow, projectFile) {
751
830
if ( newFile !== oldFile ) {
752
831
_addOpenProject ( targetWindow , projectFile , false ) ;
753
832
}
833
+ return {
834
+ existingProject,
835
+ oldFile,
836
+ newFile
837
+ } ;
838
+ }
839
+
840
+ function _revertAssignProjectFile ( targetWindow , assignProjectFileData ) {
841
+ if ( assignProjectFileData . existingProject ) {
842
+ if ( assignProjectFileData . oldFile !== assignProjectFileData . newFile ) {
843
+ _addOpenProject ( targetWindow , assignProjectFileData . oldFile , false ) ;
844
+ }
845
+ } else {
846
+ openProjects . delete ( targetWindow ) ;
847
+ }
754
848
}
755
849
756
850
async function _createCredentialManager ( targetWindow , projectFileJsonContent ) {
@@ -862,8 +956,8 @@ function _setCredentialManager(targetWindow, credentialManager) {
862
956
// On Linux, the save dialog does not automatically add the project file extension...
863
957
function getProjectFileName ( dialogReturnedFileName ) {
864
958
let result = dialogReturnedFileName ;
865
- if ( dialogReturnedFileName && path . extname ( dialogReturnedFileName ) !== '.wktproj' ) {
866
- result = `${ dialogReturnedFileName } .wktproj ` ;
959
+ if ( dialogReturnedFileName && path . extname ( dialogReturnedFileName ) !== `. ${ projectFileExtension } ` ) {
960
+ result = `${ dialogReturnedFileName } .${ projectFileExtension } ` ;
867
961
}
868
962
return result ;
869
963
}
0 commit comments