diff --git a/gradle.properties b/gradle.properties index 5413b7b20..8d928a40b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,7 +14,7 @@ org.gradle.jvmargs=-Xss1M platformVersion = 2022.1 # SemVer format -> https://semver.org -pluginVersion = 1.2.0-221 +pluginVersion = 1.2.1-221 pluginGroup = org.zowe pluginRepositoryUrl = https://github.com/zowe/zowe-explorer-intellij @@ -23,4 +23,4 @@ pluginSinceBuild = 221.5080 pluginUntilBuild = 222.* # Gradle Releases -> https://github.com/gradle/gradle/releases -gradleVersion = 8.7.0 +gradleVersion = 8.7 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index afba10928..e6441136f 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 20db9ad5c..b82aa23a4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -2,5 +2,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68d6..1aa94a426 100755 --- a/gradlew +++ b/gradlew @@ -83,10 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 6689b85be..7101f8e46 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/src/main/kotlin/org/zowe/explorer/config/connect/connectUtils.kt b/src/main/kotlin/org/zowe/explorer/config/connect/connectUtils.kt index d3d4edb63..e1b8cd3e4 100644 --- a/src/main/kotlin/org/zowe/explorer/config/connect/connectUtils.kt +++ b/src/main/kotlin/org/zowe/explorer/config/connect/connectUtils.kt @@ -19,6 +19,7 @@ import org.zowe.explorer.dataops.operations.TsoOperationMode import org.zowe.explorer.ui.build.tso.TSOWindowFactory import org.zowe.explorer.ui.build.tso.config.TSOConfigWrapper import org.zowe.explorer.ui.build.tso.ui.TSOSessionParams +import org.zowe.kotlinsdk.TsoData /** @@ -50,10 +51,16 @@ fun whoAmI(connectionConfig: ConnectionConfig): String? { ) }.onSuccess { var response = it - response.tsoData.last().tsoMessage?.data?.let { data -> owner = data.trim() } + val queuedMessages: MutableList = mutableListOf() + queuedMessages.addAll(response.tsoData) + + // consume all the TSO messages while tsoPrompt become not null while (response.tsoData.last().tsoPrompt == null) { response = TSOWindowFactory.getTsoMessageQueue(tsoSession) + queuedMessages.addAll(response.tsoData) } + + owner = tryToExtractRealOwner(queuedMessages) } service().performOperation( TsoOperation( @@ -66,6 +73,22 @@ fun whoAmI(connectionConfig: ConnectionConfig): String? { return owner } +/** + * Utility function extracts the owner from the messages returned from the TSO request + * @param tsoData + * @return USS Owner string value if tsoData contains the userID or an empty string otherwise + */ +fun tryToExtractRealOwner(tsoData: List) : String { + val emptyOwner = "" + val filteredData = tsoData.filter { + val tsoMessage = it.tsoMessage ?: return@filter false + val messageData = tsoMessage.data?.trim() ?: return@filter false + messageData.isNotEmpty() && !messageData.contains("READY") && messageData.chars().count() < 9 + }.mapNotNull { it.tsoMessage?.data?.trim() } + + return if (filteredData.isNotEmpty()) filteredData[0] else emptyOwner +} + /** * Returns owner of particular connection config if it is not empty, or the username otherwise. * @param connectionConfig connection config instance. diff --git a/src/main/kotlin/org/zowe/explorer/config/connect/ui/ConnectionDialogStateBase.kt b/src/main/kotlin/org/zowe/explorer/config/connect/ui/ConnectionDialogStateBase.kt index 4e19c0fe7..1bca0ab03 100644 --- a/src/main/kotlin/org/zowe/explorer/config/connect/ui/ConnectionDialogStateBase.kt +++ b/src/main/kotlin/org/zowe/explorer/config/connect/ui/ConnectionDialogStateBase.kt @@ -21,6 +21,7 @@ abstract class ConnectionDialogStateBase abstract var connectionUuid: String abstract var connectionName: String abstract var username: String + abstract var owner: String abstract var password: String abstract var connectionUrl: String abstract var credentials: Credentials diff --git a/src/main/kotlin/org/zowe/explorer/config/connect/ui/ConnectionUssOwnerColumn.kt b/src/main/kotlin/org/zowe/explorer/config/connect/ui/ConnectionUssOwnerColumn.kt new file mode 100644 index 000000000..b40fd4628 --- /dev/null +++ b/src/main/kotlin/org/zowe/explorer/config/connect/ui/ConnectionUssOwnerColumn.kt @@ -0,0 +1,48 @@ +/* + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright IBA Group 2020 + */ + +package org.zowe.explorer.config.connect.ui + +import com.intellij.util.ui.ColumnInfo +import org.zowe.explorer.config.connect.ui.renderer.UssOwnerColumnRenderer +import javax.swing.table.TableCellRenderer + +/** + * Class which represents column of USS Owner in connections GUI + */ +class ConnectionUssOwnerColumn> + : ColumnInfo("Owner") { + + /** + * Returns name of particular owner + * @param item all info about particular connection to mainframe + * @return name of particular owner + */ + override fun valueOf(item: ConnectionState): String { + return item.owner + } + + /** + * Sets owner to particular user + * @param item all info about particular connection to mainframe + * @param value new owner of the user + */ + override fun setValue(item: ConnectionState, value: String) { + item.owner = value + } + + /** + * Specifies a renderer for this cell + */ + override fun getRenderer(o: ConnectionState): TableCellRenderer { + return UssOwnerColumnRenderer() + } + +} diff --git a/src/main/kotlin/org/zowe/explorer/config/connect/ui/ConnectionsTableModelBase.kt b/src/main/kotlin/org/zowe/explorer/config/connect/ui/ConnectionsTableModelBase.kt index 050926e70..1c0d43424 100644 --- a/src/main/kotlin/org/zowe/explorer/config/connect/ui/ConnectionsTableModelBase.kt +++ b/src/main/kotlin/org/zowe/explorer/config/connect/ui/ConnectionsTableModelBase.kt @@ -30,7 +30,8 @@ abstract class ConnectionsTableModelBase diff --git a/src/main/kotlin/org/zowe/explorer/config/connect/ui/renderer/UssOwnerColumnRenderer.kt b/src/main/kotlin/org/zowe/explorer/config/connect/ui/renderer/UssOwnerColumnRenderer.kt new file mode 100644 index 000000000..ccbbb3453 --- /dev/null +++ b/src/main/kotlin/org/zowe/explorer/config/connect/ui/renderer/UssOwnerColumnRenderer.kt @@ -0,0 +1,52 @@ +/* + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright IBA Group 2020 + */ + +package org.zowe.explorer.config.connect.ui.renderer + +import com.intellij.icons.AllIcons +import com.intellij.util.ui.table.IconTableCellRenderer +import javax.swing.Icon +import javax.swing.JTable +import java.awt.Component + +const val WARNING_TOOLTIP_TEXT = "The last TSO request failed. Unable to get the real USS owner. The connection username will be used as USS owner" + +/** + * Renderer class for USS Owner column in the connections table view + */ +class UssOwnerColumnRenderer : IconTableCellRenderer() { + + /** + * Function returns a warning icon or null if the value is not present + */ + override fun getIcon(value: String, table: JTable, row: Int): Icon? { + return if(value.isEmpty()) AllIcons.General.Warning else null + } + + /** + * Function returns a component which renders a cell + */ + override fun getTableCellRendererComponent( + table: JTable?, + value: Any?, + selected: Boolean, + focus: Boolean, + row: Int, + column: Int + ): Component { + super.getTableCellRendererComponent(table, value, selected, focus, row, column) + if (icon != null) { + toolTipText = WARNING_TOOLTIP_TEXT + } + return this + } + + +} diff --git a/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ConnectionDialog.kt b/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ConnectionDialog.kt index a3b5eb4a9..2de1f8a57 100644 --- a/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ConnectionDialog.kt +++ b/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ConnectionDialog.kt @@ -11,6 +11,9 @@ package org.zowe.explorer.config.connect.ui.zosmf import com.intellij.icons.AllIcons +import com.intellij.notification.Notification +import com.intellij.notification.NotificationType +import com.intellij.notification.Notifications import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import com.intellij.openapi.ui.MessageDialogBuilder @@ -29,6 +32,7 @@ import org.zowe.explorer.config.connect.ui.ChangePasswordDialog import org.zowe.explorer.config.connect.ui.ChangePasswordDialogState import org.zowe.explorer.dataops.DataOpsManager import org.zowe.explorer.dataops.operations.* +import org.zowe.explorer.explorer.EXPLORER_NOTIFICATION_GROUP_ID import org.zowe.explorer.utils.* import org.zowe.explorer.utils.crudable.Crudable import org.zowe.explorer.utils.crudable.find @@ -147,13 +151,30 @@ class ConnectionDialog( addAnyway } else { runTask(title = "Retrieving user information", project = project) { + // Could be empty if TSO request fails state.owner = whoAmI(newTestedConnConfig) ?: "" } + if (state.owner.isEmpty()) showWarningNotification(project) true } } ) } + + /** + * Function shows a warning notification if USS owner cannot be retrieved + */ + private fun showWarningNotification(project: Project?) { + Notification( + EXPLORER_NOTIFICATION_GROUP_ID, + "Unable to retrieve USS username", + "Cannot retrieve USS username. An error happened while executing TSO request.\n" + + "When working with USS files the same username will be used that was specified by the user when connecting.", + NotificationType.WARNING + ).let { + Notifications.Bus.notify(it, project) + } + } } private val initialState = state.clone() diff --git a/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ConnectionDialogState.kt b/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ConnectionDialogState.kt index 33bacef8b..2e4bfcb79 100644 --- a/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ConnectionDialogState.kt +++ b/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ConnectionDialogState.kt @@ -30,10 +30,10 @@ data class ConnectionDialogState( /*var apiMeditationLayer: String = "",*/ override var username: String = "", override var password: String = "", + override var owner: String = "", var isAllowSsl: Boolean = false, var zVersion: ZVersion = ZVersion.ZOS_2_1, var zoweConfigPath: String? = null, - var owner: String = "", override var mode: DialogMode = DialogMode.CREATE ) : ConnectionDialogStateBase() { diff --git a/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ConnectionsTableModel.kt b/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ConnectionsTableModel.kt index ac4b2d5be..3745909bf 100644 --- a/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ConnectionsTableModel.kt +++ b/src/main/kotlin/org/zowe/explorer/config/connect/ui/zosmf/ConnectionsTableModel.kt @@ -35,6 +35,7 @@ class ConnectionsTableModel( get(row).isAllowSsl = item.isAllowSsl get(row).password = item.password get(row).zVersion = item.zVersion + get(row).owner = item.owner super.set(row, item) } diff --git a/src/main/kotlin/org/zowe/explorer/dataops/attributes/RemoteDatasetAttributesService.kt b/src/main/kotlin/org/zowe/explorer/dataops/attributes/RemoteDatasetAttributesService.kt index 15bfe48cc..a388626c2 100644 --- a/src/main/kotlin/org/zowe/explorer/dataops/attributes/RemoteDatasetAttributesService.kt +++ b/src/main/kotlin/org/zowe/explorer/dataops/attributes/RemoteDatasetAttributesService.kt @@ -100,10 +100,10 @@ class RemoteDatasetAttributesService( val volserDir = fsModel.findOrCreate( this, subDirectory, newAttributes.volser ?: MIGRATED, createAttributes(directory = true) ) - fsModel.moveFile(this, file, volserDir) + file.move(this, volserDir) } if (oldAttributes.name != newAttributes.name) { - fsModel.renameFile(this, file, newAttributes.name) + file.rename(this, newAttributes.name) } } diff --git a/src/main/kotlin/org/zowe/explorer/dataops/attributes/RemoteJobAttributesService.kt b/src/main/kotlin/org/zowe/explorer/dataops/attributes/RemoteJobAttributesService.kt index b98ee2a35..293e3b057 100644 --- a/src/main/kotlin/org/zowe/explorer/dataops/attributes/RemoteJobAttributesService.kt +++ b/src/main/kotlin/org/zowe/explorer/dataops/attributes/RemoteJobAttributesService.kt @@ -95,7 +95,7 @@ class RemoteJobAttributesService( newAttributes: RemoteJobAttributes ) { if (oldAttributes.name != newAttributes.name) { - fsModel.renameFile(this, file, newAttributes.name) + file.rename(this, newAttributes.name) fsModel.setWritable(file, false) } } diff --git a/src/main/kotlin/org/zowe/explorer/dataops/attributes/RemoteUssAttributesService.kt b/src/main/kotlin/org/zowe/explorer/dataops/attributes/RemoteUssAttributesService.kt index 646fdf166..f3d0852da 100644 --- a/src/main/kotlin/org/zowe/explorer/dataops/attributes/RemoteUssAttributesService.kt +++ b/src/main/kotlin/org/zowe/explorer/dataops/attributes/RemoteUssAttributesService.kt @@ -101,14 +101,14 @@ class RemoteUssAttributesService( fsModel.setWritable(file, newAttributes.isWritable) file.isReadable = newAttributes.isReadable if (oldAttributes.name != newAttributes.name) { - fsModel.renameFile(this, file, newAttributes.name) + file.rename(this, newAttributes.name) } if (oldAttributes.parentDirPath != newAttributes.parentDirPath) { - var current = subDirectory + var current = fsRoot createPathChain(newAttributes).dropLast(1).map { nameWithFileAttr -> findOrCreate(current, nameWithFileAttr).also { current = it } } - fsModel.moveFile(this, file, current) + file.move(this, current) } } diff --git a/src/main/kotlin/org/zowe/explorer/dataops/content/synchronizer/RemoteAttributedContentSynchronizer.kt b/src/main/kotlin/org/zowe/explorer/dataops/content/synchronizer/RemoteAttributedContentSynchronizer.kt index 21c4d8c86..b0378ecb6 100644 --- a/src/main/kotlin/org/zowe/explorer/dataops/content/synchronizer/RemoteAttributedContentSynchronizer.kt +++ b/src/main/kotlin/org/zowe/explorer/dataops/content/synchronizer/RemoteAttributedContentSynchronizer.kt @@ -10,11 +10,9 @@ package org.zowe.explorer.dataops.content.synchronizer +import com.intellij.openapi.application.runReadAction import com.intellij.openapi.diagnostic.logger -import com.intellij.openapi.fileEditor.FileDocumentManager -import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.progress.ProgressIndicator -import com.intellij.openapi.project.ProjectManager import com.intellij.openapi.vfs.VirtualFile import org.zowe.explorer.dataops.DataOpsManager import org.zowe.explorer.dataops.attributes.FileAttributes @@ -107,29 +105,6 @@ abstract class RemoteAttributedContentSynchronizer return fetchedAtLeastOnce.firstOrNull { syncProvider == it } != null } - /** - * It is only necessary to remove old file from cache while force overwriting. - * TODO: Not the best solution. Think on how to rework. - * @param file - file to remove. - */ - fun removeFromCacheAfterForceOverwriting(file: VirtualFile) { - val syncProvider = fetchedAtLeastOnce.firstOrNull { it.file == file } ?: return - fetchedAtLeastOnce.removeIf { it.file == file } - // if you will not delete the file than "Local cache conflict" dialog appear. - runWriteActionInEdtAndWait { - // close editor if file is opened to avoid IDE crash. - ProjectManager.getInstance().openProjects.forEach { - syncProvider.getDocument()?.let { document -> - val fileEditorManager = FileEditorManager.getInstance(it) - FileDocumentManager.getInstance().getFile(document)?.let { vf -> - fileEditorManager.closeFile(vf) - } - } - } - file.delete(this@RemoteAttributedContentSynchronizer) - } - } - /** * Base implementation of [ContentSynchronizer.synchronizeWithRemote] method for each synchronizer. * Doesn't need to be overridden in most cases @@ -158,7 +133,7 @@ abstract class RemoteAttributedContentSynchronizer fetchedAtLeastOnce.add(syncProvider) } else { - val fileContent = runReadActionInEdtAndWait { syncProvider.retrieveCurrentContent() } + val fileContent = runReadAction { syncProvider.retrieveCurrentContent() } if (!(fileContent contentEquals adaptedFetchedBytes)) { val oldStorageBytes = successfulStatesStorage.getBytes(recordId) @@ -174,7 +149,7 @@ abstract class RemoteAttributedContentSynchronizer } else { log.info("Save strategy decided to accept remote file content.") successfulStatesStorage.writeStream(recordId).use { it.write(adaptedFetchedBytes) } - runWriteActionInEdt { syncProvider.loadNewContent(adaptedFetchedBytes) } + runWriteActionInEdtAndWait { syncProvider.loadNewContent(adaptedFetchedBytes) } } } else { /*do nothing*/ } diff --git a/src/main/kotlin/org/zowe/explorer/dataops/content/synchronizer/SeqDatasetContentSynchronizer.kt b/src/main/kotlin/org/zowe/explorer/dataops/content/synchronizer/SeqDatasetContentSynchronizer.kt index c0554ceff..590dc61c1 100644 --- a/src/main/kotlin/org/zowe/explorer/dataops/content/synchronizer/SeqDatasetContentSynchronizer.kt +++ b/src/main/kotlin/org/zowe/explorer/dataops/content/synchronizer/SeqDatasetContentSynchronizer.kt @@ -12,7 +12,6 @@ package org.zowe.explorer.dataops.content.synchronizer import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.vfs.VirtualFile -import okhttp3.ResponseBody import org.zowe.explorer.api.api import org.zowe.explorer.api.apiWithBytesConverter import org.zowe.explorer.config.connect.ConnectionConfig @@ -20,8 +19,14 @@ import org.zowe.explorer.config.connect.authToken import org.zowe.explorer.dataops.DataOpsManager import org.zowe.explorer.dataops.attributes.RemoteDatasetAttributes import org.zowe.explorer.dataops.exceptions.CallException -import org.zowe.explorer.utils.* +import org.zowe.explorer.utils.applyIfNotNull +import org.zowe.explorer.utils.cancelByIndicator +import org.zowe.explorer.utils.castOrNull +import org.zowe.explorer.utils.findAnyNullable +import org.zowe.explorer.utils.log +import org.zowe.explorer.utils.mapNotNull import org.zowe.explorer.vfs.MFVirtualFile +import okhttp3.ResponseBody import org.zowe.kotlinsdk.DataAPI import org.zowe.kotlinsdk.DatasetOrganization import org.zowe.kotlinsdk.XIBMDataType @@ -186,15 +191,23 @@ class SeqDatasetContentSynchronizer( } /** - * Check if the content synchronizer accepts the provided file + * Check if the content synchronizer accepts the provided sequential dataset virtual file * @param file the file to check - * @return true if the file is not migrated and the dataset organization parameter is not VS + * @return true if: + * 1. The dataset is not migrated + * 2. The dataset organization parameter is not VS (it is not a VSAM or VSAM-related file) + * 3. It is not an ALIAS */ override fun accepts(file: VirtualFile): Boolean { - return super.accepts(file) && - dataOpsManager.tryToGetAttributes(file)?.castOrNull()?.let { - !it.isMigrated && it.datasetInfo.datasetOrganization != DatasetOrganization.VS - } == true + val isOurVFile = super.accepts(file) + return if (isOurVFile) { + val dsAttributes = attributesService.castOrNull() + return dsAttributes != null + && !dsAttributes.isMigrated + && dsAttributes.datasetInfo.datasetOrganization != DatasetOrganization.VS + && dsAttributes.datasetInfo.volumeSerial != "*ALIAS" + } else { + false + } } - } diff --git a/src/main/kotlin/org/zowe/explorer/dataops/content/synchronizer/SyncAction.kt b/src/main/kotlin/org/zowe/explorer/dataops/content/synchronizer/SyncAction.kt index 3a7a66d2c..c126ffe0a 100644 --- a/src/main/kotlin/org/zowe/explorer/dataops/content/synchronizer/SyncAction.kt +++ b/src/main/kotlin/org/zowe/explorer/dataops/content/synchronizer/SyncAction.kt @@ -13,6 +13,7 @@ package org.zowe.explorer.dataops.content.synchronizer import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys import com.intellij.openapi.actionSystem.ex.ActionUtil +import com.intellij.openapi.application.runReadAction import com.intellij.openapi.components.service import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.progress.runBackgroundableTask @@ -69,10 +70,8 @@ class SyncAction : DumbAwareAction() { project = e.project, cancellable = true ) { indicator -> - runWriteActionInEdtAndWait { - syncProvider.saveDocument() - service().getContentSynchronizer(vFile)?.synchronizeWithRemote(syncProvider, indicator) - } + runWriteActionInEdtAndWait { syncProvider.saveDocument() } + service().getContentSynchronizer(vFile)?.synchronizeWithRemote(syncProvider, indicator) } } @@ -108,7 +107,7 @@ class SyncAction : DumbAwareAction() { val contentSynchronizer = service().getContentSynchronizer(file) val syncProvider = DocumentedSyncProvider(file) - val currentContent = runReadActionInEdtAndWait { syncProvider.retrieveCurrentContent() } + val currentContent = runReadAction { syncProvider.retrieveCurrentContent() } val previousContent = contentSynchronizer?.successfulContentStorage(syncProvider) val needToUpload = contentSynchronizer?.isFileUploadNeeded(syncProvider) == true e.presentation.isEnabledAndVisible = file.isWritable diff --git a/src/main/kotlin/org/zowe/explorer/dataops/operations/RenameOperationRunner.kt b/src/main/kotlin/org/zowe/explorer/dataops/operations/RenameOperationRunner.kt index 503890b3d..dec946f2a 100644 --- a/src/main/kotlin/org/zowe/explorer/dataops/operations/RenameOperationRunner.kt +++ b/src/main/kotlin/org/zowe/explorer/dataops/operations/RenameOperationRunner.kt @@ -21,12 +21,8 @@ import org.zowe.explorer.dataops.exceptions.CallException import org.zowe.explorer.explorer.actions.DuplicateMemberAction import org.zowe.explorer.utils.cancelByIndicator import org.zowe.explorer.utils.log -import org.zowe.explorer.vfs.sendMFVfsChangesTopic -import org.zowe.kotlinsdk.CopyDataZOS -import org.zowe.kotlinsdk.DataAPI -import org.zowe.kotlinsdk.FilePath -import org.zowe.kotlinsdk.MoveUssFile -import org.zowe.kotlinsdk.RenameData +import org.zowe.explorer.utils.runWriteActionInEdtAndWait +import org.zowe.kotlinsdk.* /** * Class which represents factory for rename operation runner. Defined in plugin.xml @@ -85,7 +81,9 @@ class RenameOperationRunner(private val dataOpsManager: DataOpsManager) : Operat toDatasetName = operation.newName ).cancelByIndicator(progressIndicator).execute() if (response.isSuccessful) { - sendMFVfsChangesTopic() + runWriteActionInEdtAndWait { + operation.file.rename(this, operation.newName) + } } else { throw CallException(response, "Unable to rename the selected dataset") } @@ -118,9 +116,7 @@ class RenameOperationRunner(private val dataOpsManager: DataOpsManager) : Operat toDatasetName = parentAttributes.datasetInfo.name, memberName = operation.newName ).cancelByIndicator(progressIndicator).execute() - if (response.isSuccessful) { - sendMFVfsChangesTopic() - } else { + if (!response.isSuccessful) { throw CallException(response, "Unable to duplicate the selected member") } } else { @@ -136,7 +132,9 @@ class RenameOperationRunner(private val dataOpsManager: DataOpsManager) : Operat memberName = operation.newName ).cancelByIndicator(progressIndicator).execute() if (response.isSuccessful) { - sendMFVfsChangesTopic() + runWriteActionInEdtAndWait { + operation.file.rename(this, operation.newName) + } } else { throw CallException(response, "Unable to rename the selected member") } @@ -164,7 +162,9 @@ class RenameOperationRunner(private val dataOpsManager: DataOpsManager) : Operat filePath = FilePath("$parentDirPath/${operation.newName}") ).cancelByIndicator(progressIndicator).execute() if (response.isSuccessful) { - sendMFVfsChangesTopic() + runWriteActionInEdtAndWait { + operation.file.rename(this, operation.newName) + } } else { throw CallException(response, "Unable to rename the selected file or directory") } diff --git a/src/main/kotlin/org/zowe/explorer/dataops/operations/mover/RemoteToLocalFileMover.kt b/src/main/kotlin/org/zowe/explorer/dataops/operations/mover/RemoteToLocalFileMover.kt index cca046161..f7268e35c 100644 --- a/src/main/kotlin/org/zowe/explorer/dataops/operations/mover/RemoteToLocalFileMover.kt +++ b/src/main/kotlin/org/zowe/explorer/dataops/operations/mover/RemoteToLocalFileMover.kt @@ -91,7 +91,7 @@ class RemoteToLocalFileMover(val dataOpsManager: DataOpsManager) : AbstractFileM runWriteActionInEdtAndWait { if (operation.forceOverwriting) { - destFile.children.filter { it.name === (newFileName) && !it.isDirectory }.forEach { it.delete(this) } + destFile.children.filter { it.name == newFileName && !it.isDirectory }.forEach { it.delete(this) } } } val createdFileJava = Paths.get(destFile.path, newFileName).toFile().apply { createNewFile() } diff --git a/src/main/kotlin/org/zowe/explorer/editor/FileEditorEventsListener.kt b/src/main/kotlin/org/zowe/explorer/editor/FileEditorEventsListener.kt index 3a1d6c4b5..64ad66346 100644 --- a/src/main/kotlin/org/zowe/explorer/editor/FileEditorEventsListener.kt +++ b/src/main/kotlin/org/zowe/explorer/editor/FileEditorEventsListener.kt @@ -11,6 +11,7 @@ package org.zowe.explorer.editor import com.intellij.openapi.actionSystem.ex.ActionUtil +import com.intellij.openapi.application.runReadAction import com.intellij.openapi.components.service import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.fileEditor.FileEditorManager @@ -77,7 +78,7 @@ class FileEditorBeforeEventsListener : FileEditorManagerListener.Before { val attributes = dataOpsManager.tryToGetAttributes(file) if (file is MFVirtualFile && file.isWritable && attributes != null) { val contentSynchronizer = service().getContentSynchronizer(file) - val currentContent = runReadActionInEdtAndWait { syncProvider.retrieveCurrentContent() } + val currentContent = runReadAction { syncProvider.retrieveCurrentContent() } val previousContent = contentSynchronizer?.successfulContentStorage(syncProvider) val needToUpload = contentSynchronizer?.isFileUploadNeeded(syncProvider) == true if (!(currentContent contentEquals previousContent) && needToUpload) { @@ -92,10 +93,8 @@ class FileEditorBeforeEventsListener : FileEditorManagerListener.Before { project = project, cancellable = true ) { - runWriteActionInEdtAndWait { - syncProvider.saveDocument() - contentSynchronizer?.synchronizeWithRemote(syncProvider, it) - } + runWriteActionInEdtAndWait { syncProvider.saveDocument() } + contentSynchronizer?.synchronizeWithRemote(syncProvider, it) } } } else { diff --git a/src/main/kotlin/org/zowe/explorer/editor/FileEditorFocusListener.kt b/src/main/kotlin/org/zowe/explorer/editor/FileEditorFocusListener.kt index 06daf2585..40754b7b6 100644 --- a/src/main/kotlin/org/zowe/explorer/editor/FileEditorFocusListener.kt +++ b/src/main/kotlin/org/zowe/explorer/editor/FileEditorFocusListener.kt @@ -10,6 +10,7 @@ package org.zowe.explorer.editor +import com.intellij.openapi.application.runReadAction import com.intellij.openapi.components.service import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.ex.EditorEx @@ -63,7 +64,7 @@ class FileEditorFocusListener: FocusChangeListener { if (file is MFVirtualFile && file.isWritable) { val syncProvider = DocumentedSyncProvider(file, SaveStrategy.default(project)) val contentSynchronizer = service().getContentSynchronizer(file) - val currentContent = runReadActionInEdtAndWait { syncProvider.retrieveCurrentContent() } + val currentContent = runReadAction { syncProvider.retrieveCurrentContent() } val previousContent = contentSynchronizer?.successfulContentStorage(syncProvider) val needToUpload = contentSynchronizer?.isFileUploadNeeded(syncProvider) == true if (!(currentContent contentEquals previousContent) && needToUpload) { diff --git a/src/main/kotlin/org/zowe/explorer/explorer/actions/AllocateLikeAction.kt b/src/main/kotlin/org/zowe/explorer/explorer/actions/AllocateLikeAction.kt index 062188e36..21b0321b1 100644 --- a/src/main/kotlin/org/zowe/explorer/explorer/actions/AllocateLikeAction.kt +++ b/src/main/kotlin/org/zowe/explorer/explorer/actions/AllocateLikeAction.kt @@ -103,10 +103,10 @@ class AllocateLikeAction : AllocateActionBase() { return } val selected = view.mySelectedNodesData - val entityAttributes = selected[0].attributes - e.presentation.isEnabledAndVisible = selected.size == 1 - && entityAttributes is RemoteDatasetAttributes - && !entityAttributes.isMigrated + val entityAttributes = if (selected.size == 1) selected[0].attributes else null + e.presentation.isEnabledAndVisible = entityAttributes != null + && entityAttributes is RemoteDatasetAttributes + && !entityAttributes.isMigrated e.presentation.icon = IconUtil.addText(AllIcons.FileTypes.Any_type, "DS") } diff --git a/src/main/kotlin/org/zowe/explorer/explorer/actions/EditJclAction.kt b/src/main/kotlin/org/zowe/explorer/explorer/actions/EditJclAction.kt index 1b68231f5..89d6c1467 100644 --- a/src/main/kotlin/org/zowe/explorer/explorer/actions/EditJclAction.kt +++ b/src/main/kotlin/org/zowe/explorer/explorer/actions/EditJclAction.kt @@ -68,18 +68,18 @@ class EditJclAction : AnAction() { ) val virtualFile = node.virtualFile if (virtualFile != null) { - runWriteActionInEdtAndWait { - var wasCreatedBefore = true - var cachedFile = virtualFile.findChild(attributes.name) - if (cachedFile == null) { - val createdFile = virtualFile.createChildData(null, attributes.name) as MFVirtualFile - cachedFile = createdFile - wasCreatedBefore = false - } - val descriptor = e.project?.let { pr -> OpenFileDescriptor(pr, cachedFile) } - descriptor?.let { - val syncProvider = - DocumentedSyncProvider(file = cachedFile, saveStrategy = SaveStrategy.default(e.project)) + var wasCreatedBefore = true + var cachedFile = virtualFile.findChild(attributes.name) + if (cachedFile == null) { + val createdFile = virtualFile.createChildData(null, attributes.name) as MFVirtualFile + cachedFile = createdFile + wasCreatedBefore = false + } + val descriptor = e.project?.let { pr -> OpenFileDescriptor(pr, cachedFile) } + descriptor?.let { + val syncProvider = + DocumentedSyncProvider(file = cachedFile, saveStrategy = SaveStrategy.default(e.project)) + runWriteActionInEdtAndWait { if (!wasCreatedBefore) { syncProvider.putInitialContent(jclContentBytes) } else { diff --git a/src/main/kotlin/org/zowe/explorer/explorer/ui/ChangeEncodingDialog.kt b/src/main/kotlin/org/zowe/explorer/explorer/ui/ChangeEncodingDialog.kt index 7cf1831c8..e845cbabf 100644 --- a/src/main/kotlin/org/zowe/explorer/explorer/ui/ChangeEncodingDialog.kt +++ b/src/main/kotlin/org/zowe/explorer/explorer/ui/ChangeEncodingDialog.kt @@ -93,10 +93,8 @@ class ChangeEncodingDialog( val contentSynchronizer = DataOpsManager.instance.getContentSynchronizer(virtualFile) val syncProvider = DocumentedSyncProvider(virtualFile) if (contentSynchronizer?.isFileUploadNeeded(syncProvider) == true) { - runWriteActionInEdtAndWait { - syncProvider.saveDocument() - contentSynchronizer.synchronizeWithRemote(syncProvider) - } + runWriteActionInEdtAndWait { syncProvider.saveDocument() } + contentSynchronizer.synchronizeWithRemote(syncProvider) } attributes.charset = charset updateFileTag(attributes) diff --git a/src/main/kotlin/org/zowe/explorer/explorer/ui/ExplorerPasteProvider.kt b/src/main/kotlin/org/zowe/explorer/explorer/ui/ExplorerPasteProvider.kt index 020992aed..a15038bac 100644 --- a/src/main/kotlin/org/zowe/explorer/explorer/ui/ExplorerPasteProvider.kt +++ b/src/main/kotlin/org/zowe/explorer/explorer/ui/ExplorerPasteProvider.kt @@ -21,16 +21,15 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.ui.Messages import com.intellij.openapi.ui.showYesNoDialog import com.intellij.openapi.vfs.VirtualFile -import com.intellij.openapi.vfs.newvfs.impl.VirtualFileImpl import org.zowe.explorer.common.ui.cleanInvalidateOnExpand import org.zowe.explorer.dataops.DataOpsManager import org.zowe.explorer.dataops.attributes.RemoteDatasetAttributes import org.zowe.explorer.dataops.attributes.RemoteUssAttributes -import org.zowe.explorer.dataops.content.synchronizer.RemoteAttributedContentSynchronizer import org.zowe.explorer.dataops.operations.mover.MoveCopyOperation import org.zowe.explorer.explorer.FileExplorerContentProvider import org.zowe.explorer.utils.castOrNull import org.zowe.explorer.utils.getMinimalCommonParents +import org.zowe.explorer.utils.runWriteActionInEdt import org.zowe.explorer.vfs.MFVirtualFile import kotlin.concurrent.withLock @@ -211,10 +210,7 @@ class ExplorerPasteProvider : PasteProvider { val nameResolver = dataOpsManager.getNameResolver(op.source, op.destination) op.destination.children .filter { file -> file == nameResolver.getConflictingChild(op.source, op.destination) } - .forEach { file -> - dataOpsManager.getContentSynchronizer(file) - .castOrNull>()?.removeFromCacheAfterForceOverwriting(file) - } + .forEach { file -> runWriteActionInEdt { file.delete(this) } } } } .onFailure { throwable -> @@ -327,7 +323,7 @@ class ExplorerPasteProvider : PasteProvider { } .flatten() - if (ussOrLocalFileToPdsWarnings.isNotEmpty()) { + if (ussOrLocalFileToPdsWarnings.isNotEmpty() && !isAllConflictsResolvedBySkip(conflictsResolutions, ussOrLocalFileToPdsWarnings.size )) { val isLocalFilesPresent = ussOrLocalFileToPdsWarnings.find { it.second.isInLocalFileSystem } != null val fileTypesPattern = if (isLocalFilesPresent) "Local Files" else "USS Files" if (!showYesNoDialog( @@ -338,9 +334,10 @@ class ExplorerPasteProvider : PasteProvider { "Skip This Files", AllIcons.General.WarningDialog )) { - conflictsResolutions.addAll( - ussOrLocalFileToPdsWarnings.map { ConflictResolution(it.second, it.first).apply { resolveBySkip() } } - ) + conflictsResolutions.apply { + clear() + addAll(ussOrLocalFileToPdsWarnings.map { ConflictResolution(it.second, it.first).apply { resolveBySkip() } }) + } } } // specific conflicts resolution end @@ -372,7 +369,7 @@ class ExplorerPasteProvider : PasteProvider { val operationsToDownload = operations .filter { operation -> operation.destination !is MFVirtualFile } - if (operationsToDownload.isNotEmpty()) { + if (operationsToDownload.isNotEmpty() && (!isAllConflictsResolvedBySkip(conflictsResolutions, operationsToDownload.size) || operationsToDownload.size == 1)) { val filesToDownloadUpdated = operationsToDownload.map { operation -> operation.source.name } val startMessage = "You are going to DOWNLOAD files:" @@ -470,8 +467,7 @@ class ExplorerPasteProvider : PasteProvider { "Decide for Each" ), 0, - AllIcons.General.QuestionDialog, - null + AllIcons.General.QuestionDialog ) when (choice) { @@ -504,8 +500,7 @@ class ExplorerPasteProvider : PasteProvider { "Not Resolvable Conflicts", arrayOf("Ok"), 0, - Messages.getErrorIcon(), - null + Messages.getErrorIcon() ) } } @@ -518,6 +513,10 @@ class ExplorerPasteProvider : PasteProvider { return result } + private fun isAllConflictsResolvedBySkip(conflicts: List, filesToProcessSize: Int) : Boolean { + return conflicts.isNotEmpty() && conflicts.map { it.shouldSkip() }.filter { it }.size == conflicts.size && filesToProcessSize == conflicts.size + } + /** * Resolve conflicts one by one for case when user select option "Decide for Each". * @param conflicts Conflict pairs (target - source) that could be resolved using any method. @@ -623,16 +622,30 @@ class ExplorerPasteProvider : PasteProvider { return isPastePossibleAndEnabled(dataContext) } + /** + * Creates an HTML message from an items list + * Message structure: + * startMessage + * Next, a string is constructed from the items list. + * Elements are added to the string until its length does not exceed the limit. + * If not all elements were added to the string, then "and more..." is added to the end of the string + * finishMessage. + * @param startMessage beginning of the message + * @param items list of items to display + * @param finishMessage end of message + * @param limit the maximum allowed length for a converted list of elements. + * @return created HTML message + */ private fun createHtmlMessageWithItemsList( - startMessage: String, items: List, finishMessage: String, maxItems: Int = 5 + startMessage: String, items: List, finishMessage: String, limit: Int = 130 ): String { - val tagP = "

" - val itemsToShow = if (items.size > maxItems) { - items.subList(0, maxItems).toMutableList().apply { add("more ...") } - } else { - items - } - val itemsString = "$tagP${itemsToShow.joinToString("

$tagP")}

" + val pTag = "

" + val itemsMerged = items.joinToString(", ") + val result = if (itemsMerged.length > limit) + itemsMerged.substring(0, limit - 3).plus("...

${pTag}and more...") + else + itemsMerged + val itemsString = pTag.plus(result).plus("

") return "$startMessage\n\n$itemsString\n$finishMessage" } } diff --git a/src/main/kotlin/org/zowe/explorer/explorer/ui/ExplorerTreeNode.kt b/src/main/kotlin/org/zowe/explorer/explorer/ui/ExplorerTreeNode.kt index 8c88993f6..6bb01d858 100644 --- a/src/main/kotlin/org/zowe/explorer/explorer/ui/ExplorerTreeNode.kt +++ b/src/main/kotlin/org/zowe/explorer/explorer/ui/ExplorerTreeNode.kt @@ -112,8 +112,7 @@ abstract class ExplorerTreeNode( "Error While Opening File ${file.name}", arrayOf("Ok"), 0, - AllIcons.General.ErrorDialog, - null + AllIcons.General.ErrorDialog ) } } else { diff --git a/src/main/kotlin/org/zowe/explorer/explorer/ui/ExplorerTreeView.kt b/src/main/kotlin/org/zowe/explorer/explorer/ui/ExplorerTreeView.kt index bfd3d0168..2b6292ca0 100644 --- a/src/main/kotlin/org/zowe/explorer/explorer/ui/ExplorerTreeView.kt +++ b/src/main/kotlin/org/zowe/explorer/explorer/ui/ExplorerTreeView.kt @@ -315,7 +315,13 @@ abstract class ExplorerTreeView> onFetchFailure(query: Q, throwable: Throwable) { getNodesByQueryAndInvalidate(query) - explorer.reportThrowable(throwable, project) + //The ability to show exceptions for JES Explorer has been disabled. + //All messages about exceptions that occur in TreeView components will be displayed using File Explorer. + //This was done to avoid duplication of exception messages, since both explorers have a common EventBus and, + // accordingly, both receive a message about an exception that occurred in one of them. + if (this@ExplorerTreeView is FileExplorerView) { + explorer.reportThrowable(throwable, project) + } } }, disposable = this diff --git a/src/main/kotlin/org/zowe/explorer/explorer/ui/ExplorerWindowFactory.kt b/src/main/kotlin/org/zowe/explorer/explorer/ui/ExplorerWindowFactory.kt index 7962561be..9ec2a2d41 100755 --- a/src/main/kotlin/org/zowe/explorer/explorer/ui/ExplorerWindowFactory.kt +++ b/src/main/kotlin/org/zowe/explorer/explorer/ui/ExplorerWindowFactory.kt @@ -53,10 +53,8 @@ class ExplorerWindowFactory : ToolWindowFactory, DumbAware { val contentSynchronizer = dataOpsManager.getContentSynchronizer(file) ?: return runBackgroundableTask("Synchronizing file ${file.name} with mainframe") { indicator -> val syncProvider = DocumentedSyncProvider(file) - runWriteActionInEdtAndWait { - syncProvider.saveDocument() - contentSynchronizer.synchronizeWithRemote(syncProvider, indicator) - } + runWriteActionInEdtAndWait { syncProvider.saveDocument() } + contentSynchronizer.synchronizeWithRemote(syncProvider, indicator) } } } diff --git a/src/main/kotlin/org/zowe/explorer/explorer/ui/FileFetchNode.kt b/src/main/kotlin/org/zowe/explorer/explorer/ui/FileFetchNode.kt index c7c868d05..d9775db90 100644 --- a/src/main/kotlin/org/zowe/explorer/explorer/ui/FileFetchNode.kt +++ b/src/main/kotlin/org/zowe/explorer/explorer/ui/FileFetchNode.kt @@ -25,7 +25,6 @@ import org.zowe.explorer.explorer.ExplorerUnit import org.zowe.explorer.utils.castOrNull import org.zowe.explorer.utils.locked import org.zowe.explorer.utils.service -import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock @@ -43,8 +42,6 @@ abstract class FileFetchNode { return if (attributes is RemoteDatasetAttributes) { diff --git a/src/main/kotlin/org/zowe/explorer/utils/encodingUtils.kt b/src/main/kotlin/org/zowe/explorer/utils/encodingUtils.kt index 5594c97ec..2ab0d3e6b 100644 --- a/src/main/kotlin/org/zowe/explorer/utils/encodingUtils.kt +++ b/src/main/kotlin/org/zowe/explorer/utils/encodingUtils.kt @@ -17,6 +17,7 @@ import com.intellij.icons.AllIcons import com.intellij.ide.IdeBundle import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.DefaultActionGroup +import com.intellij.openapi.application.runReadAction import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.project.Project @@ -47,9 +48,9 @@ import javax.swing.Icon */ fun saveIn(project: Project?, virtualFile: VirtualFile, charset: Charset) { val syncProvider = DocumentedSyncProvider(virtualFile, SaveStrategy.default(project)) - syncProvider.saveDocument() - val bytes = syncProvider.retrieveCurrentContent() runWriteActionInEdtAndWait { + syncProvider.saveDocument() + val bytes = syncProvider.retrieveCurrentContent() changeEncodingTo(virtualFile, charset) virtualFile.getOutputStream(null).use { it.write(bytes) @@ -66,10 +67,8 @@ fun saveIn(project: Project?, virtualFile: VirtualFile, charset: Charset) { fun reloadIn(project: Project?, virtualFile: VirtualFile, charset: Charset) { val syncProvider = DocumentedSyncProvider(virtualFile, SaveStrategy.syncOnOpen(project)) val contentSynchronizer = DataOpsManager.instance.getContentSynchronizer(virtualFile) - runWriteActionInEdtAndWait { - changeEncodingTo(virtualFile, charset) - contentSynchronizer?.synchronizeWithRemote(syncProvider) - } + runWriteActionInEdtAndWait { changeEncodingTo(virtualFile, charset) } + contentSynchronizer?.synchronizeWithRemote(syncProvider) } /** Changes the file encoding to the specified one. */ @@ -125,7 +124,7 @@ fun inspectSafeEncodingChange(virtualFile: VirtualFile, charset: Charset): Encod val fileNotSynced = contentSynchronizer?.isFileUploadNeeded(syncProvider) == true val text = syncProvider.getDocument()?.text ?: throw IllegalArgumentException("Cannot get document text") - val bytes = if (fileNotSynced) syncProvider.retrieveCurrentContent() + val bytes = if (fileNotSynced) runReadAction { syncProvider.retrieveCurrentContent() } else contentSynchronizer?.successfulContentStorage(syncProvider) ?: throw IllegalArgumentException("Cannot get content bytes") val safeToReload = isSafeToReloadIn(virtualFile, text, bytes, charset) diff --git a/src/main/kotlin/org/zowe/explorer/utils/validationFunctions.kt b/src/main/kotlin/org/zowe/explorer/utils/validationFunctions.kt index 597b55f4e..e948e6216 100644 --- a/src/main/kotlin/org/zowe/explorer/utils/validationFunctions.kt +++ b/src/main/kotlin/org/zowe/explorer/utils/validationFunctions.kt @@ -49,6 +49,14 @@ private val volserRegex = Regex("[A-Za-z0-9]{1,6}") private val firstLetterRegex = Regex("[A-Z@\$#a-z]") private val memberRegex = Regex("[A-Z@$#a-z][A-Z@#\$a-z0-9]{0,7}") +/** + * Validate text field for a match with the previous value + * @param prev of the current node + * @param component the component containing the invalid data + */ +fun validateForTheSameValue(prev: String?, component: JTextField): ValidationInfo? { + return if (component.text == prev) ValidationInfo("Field value matches the previous one", component) else null +} /** * Validate text field for blank value diff --git a/src/main/kotlin/org/zowe/explorer/vfs/MFVirtualFileSystemModel.kt b/src/main/kotlin/org/zowe/explorer/vfs/MFVirtualFileSystemModel.kt index cf8115605..94c506782 100755 --- a/src/main/kotlin/org/zowe/explorer/vfs/MFVirtualFileSystemModel.kt +++ b/src/main/kotlin/org/zowe/explorer/vfs/MFVirtualFileSystemModel.kt @@ -311,13 +311,13 @@ class MFVirtualFileSystemModel { @Throws(IOException::class) fun renameFile(requestor: Any?, vFile: MFVirtualFile, newName: String) { val event = listOf(VFilePropertyChangeEvent(requestor, vFile, VirtualFile.PROP_NAME, vFile.name, newName, false)) - sendMFVfsChangesTopic().before(event) + sendVfsChangesTopic().before(event) vFile.validReadLock { if (vFile.filenameInternal != newName) { vFile.filenameInternal = newName } } - sendMFVfsChangesTopic().after(event) + sendVfsChangesTopic().after(event) } @Throws(IOException::class) diff --git a/src/test/kotlin/org/zowe/explorer/config/ConfigTestSpec.kt b/src/test/kotlin/org/zowe/explorer/config/ConfigTestSpec.kt index f3bea8222..406a67f25 100644 --- a/src/test/kotlin/org/zowe/explorer/config/ConfigTestSpec.kt +++ b/src/test/kotlin/org/zowe/explorer/config/ConfigTestSpec.kt @@ -237,6 +237,73 @@ class ConfigTestSpec : WithApplicationShouldSpec({ assertSoftly { actual shouldBe "ZOSMFAD" } } + + should("return empty owner if TSO request returns empty data") { + dataOpsManagerService.testInstance = object : TestDataOpsManagerImpl(explorerMock.componentManager) { + override fun performOperation(operation: Operation, progressIndicator: ProgressIndicator): R { + val tsoResponse = TsoResponse( + servletKey = "servletKey", + tsoData = listOf(TsoData()) + ) + if ((operation as TsoOperation).mode == TsoOperationMode.SEND_MESSAGE) { + tsoResponse.tsoData = listOf( + TsoData(tsoMessage = MessageType("", "")) + ) + } + @Suppress("UNCHECKED_CAST") + return tsoResponse as R + } + } + + val actual = whoAmI(connectionConfig) + + assertSoftly { actual shouldBe "" } + } + + should("return empty owner if TSO request returns READY") { + dataOpsManagerService.testInstance = object : TestDataOpsManagerImpl(explorerMock.componentManager) { + override fun performOperation(operation: Operation, progressIndicator: ProgressIndicator): R { + val tsoResponse = TsoResponse( + servletKey = "servletKey", + tsoData = listOf(TsoData()) + ) + if ((operation as TsoOperation).mode == TsoOperationMode.SEND_MESSAGE) { + tsoResponse.tsoData = listOf( + TsoData(tsoMessage = MessageType("", "READY ")) + ) + } + @Suppress("UNCHECKED_CAST") + return tsoResponse as R + } + } + + val actual = whoAmI(connectionConfig) + + assertSoftly { actual shouldBe "" } + } + + should("return empty owner if TSO request returns error message in TSO data") { + dataOpsManagerService.testInstance = object : TestDataOpsManagerImpl(explorerMock.componentManager) { + override fun performOperation(operation: Operation, progressIndicator: ProgressIndicator): R { + val tsoResponse = TsoResponse( + servletKey = "servletKey", + tsoData = listOf(TsoData()) + ) + if ((operation as TsoOperation).mode == TsoOperationMode.SEND_MESSAGE) { + tsoResponse.tsoData = listOf( + TsoData(tsoMessage = MessageType("", "OSHELL RC = 65210")) + ) + } + @Suppress("UNCHECKED_CAST") + return tsoResponse as R + } + } + + val actual = whoAmI(connectionConfig) + + assertSoftly { actual shouldBe "" } + } + should("do not get the owner by TSO request if servlet key is null") { dataOpsManagerService.testInstance = object : TestDataOpsManagerImpl(explorerMock.componentManager) { override fun performOperation(operation: Operation, progressIndicator: ProgressIndicator): R { diff --git a/src/test/kotlin/org/zowe/explorer/explorer/ui/ExplorerPasteProviderTestSpec.kt b/src/test/kotlin/org/zowe/explorer/explorer/ui/ExplorerPasteProviderTestSpec.kt index d13cdc593..9a9204b0a 100644 --- a/src/test/kotlin/org/zowe/explorer/explorer/ui/ExplorerPasteProviderTestSpec.kt +++ b/src/test/kotlin/org/zowe/explorer/explorer/ui/ExplorerPasteProviderTestSpec.kt @@ -459,12 +459,12 @@ class ExplorerPasteProviderTestSpec : WithApplicationShouldSpec({ isPastePerformed = false val showDialogMock: ( - Project?, String, String, Array, Int, Icon?, DialogWrapper.DoNotAskOption? + Project?, String, String, Array, Int, Icon? ) -> Int = Messages::showDialog mockkStatic(showDialogMock as KFunction<*>) every { showDialogMock( - any(), any(), any(), any>(), any(), any() as Icon?, any() + any(), any(), any(), any>(), any(), any() as Icon? ) } answers { isShowDialogCalled = true @@ -580,12 +580,12 @@ class ExplorerPasteProviderTestSpec : WithApplicationShouldSpec({ } val showDialogMock: ( - Project?, String, String, Array, Int, Icon?, DialogWrapper.DoNotAskOption? + Project?, String, String, Array, Int, Icon? ) -> Int = Messages::showDialog mockkStatic(showDialogMock as KFunction<*>) every { showDialogMock( - any(), any(), any(), any>(), any(), any() as Icon?, any() + any(), any(), any(), any>(), any(), any() as Icon? ) } answers { isShowDialogCalled = true @@ -602,12 +602,12 @@ class ExplorerPasteProviderTestSpec : WithApplicationShouldSpec({ should("return if unrecognized dialog message") { isPastePerformed = true val showDialogSpecificMock: ( - Project?, String, String, Array, Int, Icon?, DialogWrapper.DoNotAskOption? + Project?, String, String, Array, Int, Icon? ) -> Int = Messages::showDialog mockkStatic(showDialogSpecificMock as KFunction<*>) every { showDialogSpecificMock( - any(), any(), any(), any>(), any(), any() as Icon?, any() + any(), any(), any(), any>(), any(), any() as Icon? ) } answers { isPastePerformed = false @@ -689,12 +689,12 @@ class ExplorerPasteProviderTestSpec : WithApplicationShouldSpec({ every { mockedFileExplorerView.isCut } returns AtomicBoolean(false) val showDialogSpecificMock: ( - Project?, String, String, Array, Int, Icon?, DialogWrapper.DoNotAskOption? + Project?, String, String, Array, Int, Icon? ) -> Int = Messages::showDialog mockkStatic(showDialogSpecificMock as KFunction<*>) every { showDialogSpecificMock( - any(), any(), any(), any>(), any(), any() as Icon?, any() + any(), any(), any(), any>(), any(), any() as Icon? ) } answers { isShowYesNoDialogCalled = true diff --git a/src/test/kotlin/org/zowe/explorer/explorer/ui/UssFileNodeTestSpec.kt b/src/test/kotlin/org/zowe/explorer/explorer/ui/UssFileNodeTestSpec.kt index 79127c739..566c494b2 100644 --- a/src/test/kotlin/org/zowe/explorer/explorer/ui/UssFileNodeTestSpec.kt +++ b/src/test/kotlin/org/zowe/explorer/explorer/ui/UssFileNodeTestSpec.kt @@ -208,11 +208,11 @@ class UssFileNodeTestSpec : WithApplicationShouldSpec({ var isErrorMessageInDialogCalled = false val showDialogSpecificMock: ( - Project?, String, String, Array, Int, Icon?, DialogWrapper.DoNotAskOption? + Project?, String, String, Array, Int, Icon? ) -> Int = Messages::showDialog mockkStatic(showDialogSpecificMock as KFunction<*>) every { - showDialogSpecificMock(any(), any(), any(), any>(), any(), any(), any()) + showDialogSpecificMock(any(), any(), any(), any>(), any(), any() as Icon?) } answers { isErrorMessageInDialogCalled = true 1