Skip to content

Commit 37fff22

Browse files
committed
Merge branch 'private-release/v1.2.3-221' into private-release/v1.2.3-223
2 parents 1201109 + fb7d831 commit 37fff22

5 files changed

Lines changed: 255 additions & 30 deletions

File tree

src/main/kotlin/eu/ibagroup/formainframe/config/ws/ui/AbstractWsDialog.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import com.intellij.openapi.ui.DialogWrapper
1414
import com.intellij.openapi.ui.ValidationInfo
1515
import com.intellij.ui.CollectionComboBoxModel
1616
import com.intellij.ui.SimpleListCellRenderer
17+
import com.intellij.ui.dsl.builder.RowLayout
1718
import com.intellij.ui.dsl.builder.bindItem
1819
import com.intellij.ui.dsl.builder.bindText
1920
import com.intellij.ui.dsl.builder.panel
@@ -96,7 +97,7 @@ abstract class AbstractWsDialog<Connection : ConnectionConfigBase, WSConfig : Wo
9697
private val panel by lazy {
9798
panel {
9899
row {
99-
label(wsNameLabel)
100+
label("$wsNameLabel: ")
100101
textField()
101102
.bindText(state::workingSetName)
102103
.validationOnApply {
@@ -108,9 +109,9 @@ abstract class AbstractWsDialog<Connection : ConnectionConfigBase, WSConfig : Wo
108109
)
109110
}
110111
.focused()
111-
}
112+
}.layout(RowLayout.LABEL_ALIGNED)
112113
row {
113-
label("Specify connection")
114+
label("z/OSMF Connection: ")
114115
comboBox(connectionComboBoxModel, SimpleListCellRenderer.create("") { it?.name })
115116
.bindItem(
116117
{
@@ -136,7 +137,7 @@ abstract class AbstractWsDialog<Connection : ConnectionConfigBase, WSConfig : Wo
136137
null
137138
}
138139
}
139-
}
140+
}.layout(RowLayout.LABEL_ALIGNED)
140141
group(tableTitle, false) {
141142
row {
142143
tableWithToolbar(masksTable, addDefaultActions = true) {

src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/UssToUssFileMover.kt

Lines changed: 91 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import eu.ibagroup.formainframe.dataops.DataOpsManager
1717
import eu.ibagroup.formainframe.dataops.attributes.RemoteUssAttributes
1818
import eu.ibagroup.formainframe.dataops.attributes.USS_DELIMITER
1919
import eu.ibagroup.formainframe.dataops.exceptions.CallException
20+
import eu.ibagroup.formainframe.dataops.operations.DeleteOperation
21+
import eu.ibagroup.formainframe.dataops.operations.DeleteOperationRunner
2022
import eu.ibagroup.formainframe.dataops.operations.OperationRunner
2123
import eu.ibagroup.formainframe.dataops.operations.OperationRunnerFactory
2224
import eu.ibagroup.formainframe.utils.cancelByIndicator
@@ -25,7 +27,6 @@ import eu.ibagroup.formainframe.utils.log
2527
import org.zowe.kotlinsdk.CopyDataUSS
2628
import org.zowe.kotlinsdk.DataAPI
2729
import org.zowe.kotlinsdk.FilePath
28-
import org.zowe.kotlinsdk.MoveUssFile
2930
import retrofit2.Call
3031
import retrofit2.Response
3132

@@ -43,6 +44,7 @@ class UssToUssFileMoverFactory : OperationRunnerFactory {
4344
* Implements copying of uss file to uss directory inside 1 system
4445
*/
4546
class UssToUssFileMover(private val dataOpsManager: DataOpsManager) : AbstractFileMover() {
47+
4648
override fun canRun(operation: MoveCopyOperation): Boolean {
4749
return operation.sourceAttributes is RemoteUssAttributes
4850
&& operation.destinationAttributes is RemoteUssAttributes
@@ -57,30 +59,48 @@ class UssToUssFileMover(private val dataOpsManager: DataOpsManager) : AbstractFi
5759
* Proceeds move/copy of uss file to uss directory
5860
* @param connectionConfig connection configuration of system inside which to copy file
5961
* @param operation requested operation
60-
* @param progressIndicator indicator that will show progress of copying/moving in UI
6162
*/
6263
private fun makeCall(
6364
connectionConfig: ConnectionConfig,
64-
operation: MoveCopyOperation,
65-
progressIndicator: ProgressIndicator
66-
): Triple<Call<Void>, String, String> {
65+
operation: MoveCopyOperation
66+
): Triple<Pair<Call<Void>, () -> Unit>, String, String> {
6767
val sourceAttributes = (operation.sourceAttributes as RemoteUssAttributes)
68-
val destinationAttributes = (operation.destinationAttributes as RemoteUssAttributes)
6968
val from = sourceAttributes.path
70-
val to = destinationAttributes.path + USS_DELIMITER + (operation.newName ?: sourceAttributes.name)
71-
val api = api<DataAPI>(connectionConfig)
72-
val call = if (operation.isMove) {
73-
api.moveUssFile(
74-
authorizationToken = connectionConfig.authToken,
75-
body = MoveUssFile(
76-
from = from
77-
),
78-
filePath = FilePath(
79-
path = to
80-
)
81-
)
82-
} else {
83-
api.copyUssFile(
69+
val to = computeUssDestination(operation)
70+
val call = if (operation.isMove)
71+
buildMoveCall(connectionConfig, operation, from, to)
72+
else
73+
buildCopyCall(connectionConfig, operation, from, to)
74+
return Triple(call, from, to)
75+
}
76+
77+
/**
78+
* Function builds a Move call and Delete source callback after moving is performed
79+
* @return Pair of Move call and its delete source file callback
80+
*/
81+
private fun buildMoveCall(
82+
connectionConfig: ConnectionConfig,
83+
operation: MoveCopyOperation,
84+
from: String,
85+
to: String)
86+
: Pair<Call<Void>, () -> Unit> {
87+
val copyCall = buildCopyCall(connectionConfig, operation, from, to).first
88+
val deleteSourceCallback = buildDeleteSourceCallback(operation)
89+
return Pair(copyCall, deleteSourceCallback)
90+
}
91+
92+
/**
93+
* Function builds a Copy call
94+
* @return Pair of Copy call and empty callback function to execute after Copy is performed
95+
*/
96+
private fun buildCopyCall(
97+
connectionConfig: ConnectionConfig,
98+
operation: MoveCopyOperation,
99+
from: String,
100+
to: String)
101+
: Pair<Call<Void>, () -> Unit> {
102+
return Pair(
103+
api<DataAPI>(connectionConfig).copyUssFile(
84104
authorizationToken = connectionConfig.authToken,
85105
body = CopyDataUSS.CopyFromFileOrDir(
86106
from = from,
@@ -93,8 +113,53 @@ class UssToUssFileMover(private val dataOpsManager: DataOpsManager) : AbstractFi
93113
path = to
94114
)
95115
)
116+
) {}
117+
}
118+
119+
/**
120+
* Function builds a Delete callback which will be executed after successful Move is performed
121+
* @return delete callback
122+
*/
123+
private fun buildDeleteSourceCallback(operation: MoveCopyOperation): () -> Unit {
124+
return {
125+
val sourceAttributes = operation.sourceAttributes as RemoteUssAttributes
126+
val deleteOperation = DeleteOperation(operation.source, sourceAttributes)
127+
DeleteOperationRunner(dataOpsManager).run(deleteOperation)
128+
}
129+
}
130+
131+
/**
132+
* Function is used to determine the correct USS destination for Move/Copy operation
133+
* The possible list of destinations are:
134+
*
135+
* Copying:
136+
* 1. Directory -> Directory without(with) conflict: Destination would be <ROOT_PATH>
137+
* 2. Directory -> Directory with conflict, but "Use new name" option was pressed: Destination would be <ROOT_PATH>/<NEW_DIR_NAME>
138+
* 3. File -> Directory with conflict: Destination would be <ROOT_PATH>/<FILE_NEW_NAME>
139+
* 4. File -> Directory without conflict: Destination would be <ROOT_PATH>/<SOURCE_FILE_NAME>
140+
*
141+
* Moving:
142+
* 1. Directory -> Directory without(with) conflict: Destination would be <ROOT_PATH>
143+
* 2. Directory -> Directory with conflict and "Use new name" is pressed: Destination would be <ROOT_PATH>/<NEW_DIR_NAME>
144+
* 3. File -> Directory the same behavior as for Copying
145+
*
146+
* * example: mv(cp) -Rf /u/<USER>/test /u/<USER>/destination
147+
* * (if test is present under destination all files and subdirs from /u/<USER>/test will be copied/moved
148+
* * and overwritten in /u/<USER>/destination/test, otherwise test would be copied/moved to /u/<USER>/destination/test)
149+
* * If operation is Move, the source would be deleted afterward
150+
*
151+
* @return target destination in String format
152+
*/
153+
private fun computeUssDestination(operation: MoveCopyOperation) : String {
154+
val destinationRootPath = (operation.destinationAttributes as RemoteUssAttributes).path
155+
val destinationNewName = operation.newName
156+
val destinationNewNameWithDelimiter = USS_DELIMITER + operation.newName
157+
// Copying or Moving USS directory
158+
return if (operation.source.isDirectory && operation.destination.isDirectory) {
159+
destinationRootPath + if (destinationNewName != null) destinationNewNameWithDelimiter else ""
96160
}
97-
return Triple(call.cancelByIndicator(progressIndicator), from, to)
161+
// Copying or Moving USS file
162+
else destinationRootPath + USS_DELIMITER + (operation.newName ?: operation.sourceAttributes?.name)
98163
}
99164

100165
/**
@@ -106,11 +171,14 @@ class UssToUssFileMover(private val dataOpsManager: DataOpsManager) : AbstractFi
106171
var throwable: Throwable? = null
107172
for ((requester, _) in operation.commonUrls(dataOpsManager)) {
108173
try {
109-
val (call, from, to) = makeCall(requester.connectionConfig, operation, progressIndicator)
174+
val (call, from, to) = makeCall(requester.connectionConfig, operation)
110175
val operationName = if (operation.isMove) "move" else "copy"
111-
val response: Response<Void> = call.execute()
176+
val response: Response<Void> = call.first.cancelByIndicator(progressIndicator).execute()
112177
if (!response.isSuccessful) {
113178
throwable = CallException(response, "Cannot $operationName $from to $to")
179+
} else {
180+
// Call the built early callback for source file/dir deletion (always empty callback for Copy)
181+
call.second.invoke()
114182
}
115183
break
116184
} catch (t: Throwable) {

src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/AllocationDialog.kt

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,12 @@ class AllocationDialog(project: Project?, config: ConnectionConfig, override var
5454
private lateinit var advancedParametersField: JTextField
5555
private lateinit var presetsBox: JComboBox<Presets>
5656
private val HLQ = getUsername(config)
57+
private val nonNegativeIntRange = IntRange(0, Int.MAX_VALUE - 1)
58+
private val positiveIntRange = IntRange(1, Int.MAX_VALUE - 1)
5759

5860
private val mainPanel by lazy {
5961
val sameWidthLabelsGroup = "ALLOCATION_DIALOG_LABELS_WIDTH_GROUP"
6062
val sameWidthComboBoxGroup = "ALLOCATION_DIALOG_COMBO_BOX_WIDTH_GROUP"
61-
val nonNegativeIntRange = IntRange(0, Int.MAX_VALUE - 1)
62-
val positiveIntRange = IntRange(1, Int.MAX_VALUE - 1)
6363

6464
panel {
6565
row {
@@ -199,7 +199,7 @@ class AllocationDialog(project: Project?, config: ConnectionConfig, override var
199199
row {
200200
label("Record Length: ")
201201
.widthGroup(sameWidthLabelsGroup)
202-
intTextField(positiveIntRange)
202+
intTextField(nonNegativeIntRange)
203203
.bindText(
204204
{ state.allocationParameters.recordLength?.toString() ?: "0" },
205205
{ state.allocationParameters.recordLength = it.toIntOrNull() }
@@ -334,6 +334,7 @@ class AllocationDialog(project: Project?, config: ConnectionConfig, override var
334334
return validateDatasetNameOnInput(datasetNameField)
335335
?: validateForBlank(memberNameField)
336336
?: validateMemberName(memberNameField)
337+
?: validateLrecl(recordFormatBox, recordLengthField)
337338
?: defaultValidationInfos.firstOrNull()
338339
?: validateVolser(advancedParametersField)
339340
}
@@ -342,6 +343,26 @@ class AllocationDialog(project: Project?, config: ConnectionConfig, override var
342343
return mainPanel.preferredFocusedComponent ?: super.getPreferredFocusedComponent()
343344
}
344345

346+
/**
347+
* Function for validating LRECL value
348+
* @param recordFormatBox RecordFormat combo box
349+
* @param recordLengthField record length text field
350+
* @return ValidationInfo in case of error or null otherwise
351+
*/
352+
private fun validateLrecl(recordFormatBox: JComboBox<RecordFormat>, recordLengthField: JTextField): ValidationInfo? {
353+
val range = if (recordFormatBox.selectedItem == RecordFormat.U)
354+
nonNegativeIntRange
355+
else
356+
positiveIntRange
357+
return if (recordLengthField.text.toIntOrNull() !in range)
358+
ValidationInfo(
359+
"Please enter a number from ${range.first} to ${range.last}",
360+
recordLengthField
361+
)
362+
else
363+
null
364+
}
365+
345366
init {
346367
title = "Allocate Dataset"
347368
init()

src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerTreeView.kt

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import eu.ibagroup.formainframe.dataops.DataOpsManager
3333
import eu.ibagroup.formainframe.dataops.Query
3434
import eu.ibagroup.formainframe.dataops.attributes.AttributesService
3535
import eu.ibagroup.formainframe.dataops.attributes.FileAttributes
36+
import eu.ibagroup.formainframe.dataops.attributes.RemoteUssAttributes
3637
import eu.ibagroup.formainframe.dataops.attributes.attributesListener
3738
import eu.ibagroup.formainframe.dataops.content.synchronizer.DocumentedSyncProvider
3839
import eu.ibagroup.formainframe.dataops.content.synchronizer.SaveStrategy
@@ -42,6 +43,7 @@ import eu.ibagroup.formainframe.explorer.*
4243
import eu.ibagroup.formainframe.utils.*
4344
import eu.ibagroup.formainframe.utils.crudable.EntityWithUuid
4445
import eu.ibagroup.formainframe.vfs.MFBulkFileListener
46+
import eu.ibagroup.formainframe.vfs.MFVirtualFile
4547
import eu.ibagroup.formainframe.vfs.MFVirtualFileSystem
4648
import org.jetbrains.concurrency.AsyncPromise
4749
import java.awt.Component
@@ -53,6 +55,8 @@ import javax.swing.tree.TreeSelectionModel
5355

5456
val EXPLORER_VIEW = DataKey.create<ExplorerTreeView<*, *, *>>("explorerView")
5557

58+
private val log = log<ExplorerTreeView<*, *, *>>()
59+
5660
fun <ExplorerView: ExplorerTreeView<*, *, *>> AnActionEvent.getExplorerView(clazz: Class<out ExplorerView>): ExplorerView? {
5761
return getData(EXPLORER_VIEW).castOrNull(clazz)
5862
}
@@ -221,6 +225,18 @@ abstract class ExplorerTreeView<Connection: ConnectionConfigBase, U : WorkingSet
221225
}
222226
}
223227
}
228+
229+
// TODO: rework it so that listener registers once
230+
// This listener should only be registered in FileExplorerView
231+
if (this@ExplorerTreeView is FileExplorerView) {
232+
events.filterIsInstance<VFilePropertyChangeEvent>().filter {
233+
it.propertyName == VirtualFile.PROP_NAME && it.file is MFVirtualFile
234+
}.forEach {
235+
(it.newValue as? String)?.let { newName ->
236+
updateAttributesForChildrenInEditor(it.file, newName)
237+
}
238+
}
239+
}
224240
}
225241
},
226242
disposable = this
@@ -406,4 +422,45 @@ abstract class ExplorerTreeView<Connection: ConnectionConfigBase, U : WorkingSet
406422
}
407423
}
408424

425+
/**
426+
* Update attributes for files opened in editor if renamed file is an ancestor of these files.
427+
* For USS files only
428+
*/
429+
fun updateAttributesForChildrenInEditor(renamedFile: VirtualFile, newName: String) {
430+
val dataOpsManager = DataOpsManager.instance
431+
val parentAttributes = dataOpsManager.tryToGetAttributes(renamedFile)
432+
val fileEditorManager = FileEditorManager.getInstance(project)
433+
val openFiles = fileEditorManager.openFiles
434+
openFiles.forEach { openFile ->
435+
if (VfsUtilCore.isAncestor(renamedFile, openFile, false)) {
436+
val oldAttributes = dataOpsManager.tryToGetAttributes(openFile)
437+
if (parentAttributes is RemoteUssAttributes && oldAttributes is RemoteUssAttributes) {
438+
val relativePathToFile = oldAttributes.path.removePrefix(parentAttributes.path)
439+
val newPath = "/${parentAttributes.parentDirPath}/$newName$relativePathToFile"
440+
val newAttributes = RemoteUssAttributes(
441+
newPath,
442+
oldAttributes.isDirectory,
443+
oldAttributes.fileMode,
444+
oldAttributes.url,
445+
oldAttributes.requesters,
446+
oldAttributes.length,
447+
oldAttributes.uid,
448+
oldAttributes.owner,
449+
oldAttributes.gid,
450+
oldAttributes.groupId,
451+
oldAttributes.modificationTime,
452+
oldAttributes.symlinkTarget
453+
)
454+
log.info(
455+
"Update attributes for file in editor.\nVirtual file - $openFile.\n" +
456+
"Old attributes - $oldAttributes.\nNew attributes - $newAttributes."
457+
)
458+
val attributesService =
459+
dataOpsManager.getAttributesService(oldAttributes::class.java, renamedFile::class.java)
460+
attributesService.updateAttributes(oldAttributes, newAttributes)
461+
}
462+
}
463+
}
464+
}
465+
409466
}

0 commit comments

Comments
 (0)