diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ebf79497..4e8a4182d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ All notable changes to the Zowe IntelliJ Plugin will be documented in this file. ## [Unreleased] +### Bugfixes + +* Bugfix: Fixed issue with cross-system folder copy with name conflicts ([c5345051](https://github.com/zowe/zowe-explorer-intellij/commit/c5345051)) +* Bugfix: Clarified 401 error ([f36ec547](https://github.com/zowe/zowe-explorer-intellij/commit/f36ec547)) +* Bugfix: Fixed issue with USS copy that would lead to incorrect encoding error ([b4cb705a](https://github.com/zowe/zowe-explorer-intellij/commit/b4cb705a)) +* Bugfix: Fixed issue with jobs DDs display in console ([8a75f21a](https://github.com/zowe/zowe-explorer-intellij/commit/8a75f21a)) +* Bugfix: Fixed issue with copying another user's folder in USS ([66676550](https://github.com/zowe/zowe-explorer-intellij/commit/66676550)) +* Bugfix: Some encodings are made unsupported ([080c25ab](https://github.com/zowe/zowe-explorer-intellij/commit/080c25ab)) +* Bugfix: Fixed issue during a file upload to a PDS ([954bf01f](https://github.com/zowe/zowe-explorer-intellij/commit/954bf01f)) +* Bugfix: Fixed issue with URL field when typing incorrect information, the last slash at the end of https:// is deleted ([91ab962d](https://github.com/zowe/zowe-explorer-intellij/commit/91ab962d)) + ## [1.2.3-221] (2024-10-03) ### Features 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 2de1f8a57..40073717c 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 @@ -31,12 +31,17 @@ import org.zowe.explorer.config.connect.* 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.dataops.operations.ChangePasswordOperation +import org.zowe.explorer.dataops.operations.InfoOperation +import org.zowe.explorer.dataops.operations.ZOSInfoOperation 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 import org.zowe.explorer.utils.crudable.getAll +import org.zowe.explorer.utils.runTask +import org.zowe.explorer.utils.validateConnectionName +import org.zowe.explorer.utils.validateForBlank +import org.zowe.explorer.utils.validateZosmfUrl import org.zowe.kotlinsdk.ChangePassword import org.zowe.kotlinsdk.annotations.ZVersion import java.awt.Component @@ -59,10 +64,11 @@ class ConnectionDialog( * In case of DialogMode.UPDATE takes the last successful state from crudable, takes default state otherwise */ private val lastSuccessfulState: ConnectionDialogState = - if(state.mode == DialogMode.UPDATE) crudable.find { it.uuid == state.connectionUuid } + if (state.mode == DialogMode.UPDATE) crudable.find { it.uuid == state.connectionUuid } .findAny() .orElseGet { state.connectionConfig } .toDialogState(crudable) else ConnectionDialogState() + companion object { /** Show Test connection dialog and test the connection regarding the dialog state. @@ -81,11 +87,17 @@ class ConnectionDialog( initialState = initialState, factory = { ConnectionDialog(crudable, initialState, project) }, test = { state -> - val newTestedConnConfig : ConnectionConfig + val newTestedConnConfig: ConnectionConfig if (initialState.mode == DialogMode.UPDATE) { val newState = state.clone() newState.initEmptyUuids(crudable) - newTestedConnConfig = ConnectionConfig(newState.connectionUuid, newState.connectionName, newState.connectionUrl, newState.isAllowSsl, newState.zVersion) + newTestedConnConfig = ConnectionConfig( + newState.connectionUuid, + newState.connectionName, + newState.connectionUrl, + newState.isAllowSsl, + newState.zVersion + ) CredentialService.instance.setCredentials( connectionConfigUuid = newState.connectionUuid, username = newState.username, @@ -97,7 +109,8 @@ class ConnectionDialog( CredentialService.instance.setCredentials( connectionConfigUuid = state.connectionUuid, username = state.username, - password = state.password) + password = state.password + ) } val throwable = runTask(title = "Testing Connection to ${newTestedConnConfig.url}", project = project) { return@runTask try { @@ -169,7 +182,7 @@ class ConnectionDialog( 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.", + "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) @@ -222,7 +235,7 @@ class ConnectionDialog( textField() .bindText(state::connectionUrl) .validationOnApply { - it.text = it.text.trim().removeTrailingSlashes() + it.text = it.text.trim() validateForBlank(it) ?: validateZosmfUrl(it) } .also { urlTextField = it.component } diff --git a/src/main/kotlin/org/zowe/explorer/dataops/attributes/RemoteUssAttributes.kt b/src/main/kotlin/org/zowe/explorer/dataops/attributes/RemoteUssAttributes.kt index f2d0a47fd..5ad080f48 100644 --- a/src/main/kotlin/org/zowe/explorer/dataops/attributes/RemoteUssAttributes.kt +++ b/src/main/kotlin/org/zowe/explorer/dataops/attributes/RemoteUssAttributes.kt @@ -76,7 +76,8 @@ data class RemoteUssAttributes( val gid: Long? = null, val groupId: String? = null, val modificationTime: String? = null, - val symlinkTarget: String? = null + val symlinkTarget: String? = null, + var charset: Charset = DEFAULT_BINARY_CHARSET ) : MFRemoteFileAttributes, Copyable { /** @@ -98,7 +99,8 @@ data class RemoteUssAttributes( gid = ussFile.gid, groupId = ussFile.groupId, modificationTime = ussFile.modificationTime, - symlinkTarget = ussFile.target + symlinkTarget = ussFile.target, + charset = DEFAULT_BINARY_CHARSET ) /** @@ -163,8 +165,6 @@ data class RemoteUssAttributes( || mode == FileModeValue.READ_WRITE_EXECUTE.mode } - var charset: Charset = DEFAULT_BINARY_CHARSET - override var contentMode: XIBMDataType = XIBMDataType(XIBMDataType.Type.BINARY) override val isCopyPossible: Boolean 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 f3d0852da..7299c2ac0 100644 --- a/src/main/kotlin/org/zowe/explorer/dataops/attributes/RemoteUssAttributesService.kt +++ b/src/main/kotlin/org/zowe/explorer/dataops/attributes/RemoteUssAttributesService.kt @@ -85,7 +85,8 @@ class RemoteUssAttributesService( gid = newAttributes.gid, groupId = newAttributes.groupId, modificationTime = newAttributes.modificationTime, - symlinkTarget = newAttributes.symlinkTarget + symlinkTarget = newAttributes.symlinkTarget, + charset = oldAttributes.charset ) } diff --git a/src/main/kotlin/org/zowe/explorer/dataops/log/AbstractMFLoggerBase.kt b/src/main/kotlin/org/zowe/explorer/dataops/log/AbstractMFLoggerBase.kt index af472189c..ae4ef3fec 100644 --- a/src/main/kotlin/org/zowe/explorer/dataops/log/AbstractMFLoggerBase.kt +++ b/src/main/kotlin/org/zowe/explorer/dataops/log/AbstractMFLoggerBase.kt @@ -122,6 +122,13 @@ abstract class AbstractMFLoggerBase> { */ fun onLogFinished(finishHandler: () -> Unit) + /** + * Fetch log files + */ + fun fetchLog() + /** * Sets handler for event after requesting next portion of mainframe log. * @param nextLogHandler handler that will be invoked after next request to fetching log from mainframe. diff --git a/src/main/kotlin/org/zowe/explorer/dataops/operations/InfoOperationRunner.kt b/src/main/kotlin/org/zowe/explorer/dataops/operations/InfoOperationRunner.kt index c2b4d790c..10e797d72 100644 --- a/src/main/kotlin/org/zowe/explorer/dataops/operations/InfoOperationRunner.kt +++ b/src/main/kotlin/org/zowe/explorer/dataops/operations/InfoOperationRunner.kt @@ -57,7 +57,11 @@ class InfoOperationRunner : OperationRunner { .cancelByIndicator(progressIndicator) .execute() if (!response.isSuccessful) { - val headMessage = responseMessageMap[response.message()] ?: response.message() + log.info("Test connection response: $response") + val zosmfMessage = response.message().trim() + val headMessage = if (zosmfMessage.isNotEmpty()) + responseMessageMap[zosmfMessage] ?: zosmfMessage + else responseMessageMap["Unauthorized"] ?: zosmfMessage throw CallException(response, headMessage) } return response.body() ?: throw CallException(response, "Cannot parse z/OSMF info request body") diff --git a/src/main/kotlin/org/zowe/explorer/dataops/operations/mover/CrossSystemMemberOrUssFileToPdsMover.kt b/src/main/kotlin/org/zowe/explorer/dataops/operations/mover/CrossSystemMemberOrUssFileToPdsMover.kt index 776913a0f..e3acb2ea1 100644 --- a/src/main/kotlin/org/zowe/explorer/dataops/operations/mover/CrossSystemMemberOrUssFileToPdsMover.kt +++ b/src/main/kotlin/org/zowe/explorer/dataops/operations/mover/CrossSystemMemberOrUssFileToPdsMover.kt @@ -46,6 +46,8 @@ class CrossSystemMemberOrUssFileToPdsMoverFactory : OperationRunnerFactory { */ class CrossSystemMemberOrUssFileToPdsMover(val dataOpsManager: DataOpsManager) : AbstractFileMover() { + private val sourceContentType = "text/plain; charset=UTF-8" + /** * Checks that source is member or uss file, dest is partitioned data set, and they are located inside different systems. * @see OperationRunner.canRun @@ -93,17 +95,22 @@ class CrossSystemMemberOrUssFileToPdsMover(val dataOpsManager: DataOpsManager) : else XIBMDataType(XIBMDataType.Type.TEXT) val sourceContent = sourceFile.contentsToByteArray() - val contentToUpload = - if (sourceFile.fileType.isBinary) sourceContent - else sourceContent.toString(sourceFile.charset).replace("\r\n", "\n") - .toByteArray(DEFAULT_TEXT_CHARSET).addNewLine() + + // do not convert bytes to default ISO. z/OSMF does this conversion by calling iconv and produce the desired result + val contentToUpload = if (sourceFile.fileType.isBinary) sourceContent else + sourceContent + .toString(sourceFile.charset) + .replace("\r\n", "\n") + .toByteArray() + .addNewLine() val response = apiWithBytesConverter(destConnectionConfig).writeToDatasetMember( authorizationToken = destConnectionConfig.authToken, datasetName = destAttributes.name, memberName = memberName, content = contentToUpload, - xIBMDataType = xIBMDataType + xIBMDataType = xIBMDataType, + contentType = sourceContentType ).applyIfNotNull(progressIndicator) { indicator -> cancelByIndicator(indicator) }.execute() diff --git a/src/main/kotlin/org/zowe/explorer/dataops/operations/mover/CrossSystemUssDirMover.kt b/src/main/kotlin/org/zowe/explorer/dataops/operations/mover/CrossSystemUssDirMover.kt index 479686b71..aa180ac1c 100644 --- a/src/main/kotlin/org/zowe/explorer/dataops/operations/mover/CrossSystemUssDirMover.kt +++ b/src/main/kotlin/org/zowe/explorer/dataops/operations/mover/CrossSystemUssDirMover.kt @@ -86,7 +86,7 @@ class CrossSystemUssDirMover(val dataOpsManager: DataOpsManager) : AbstractFileM sourceFileFetchProvider.reload(sourceQuery) } - val pathToDir = destAttributes.path + "/" + sourceFile.name + val pathToDir = destAttributes.path + "/" + (operation.newName ?: sourceFile.name) if (operation.forceOverwriting) { destFile.children.firstOrNull { it.name == sourceFile.name }?.let { @@ -110,7 +110,7 @@ class CrossSystemUssDirMover(val dataOpsManager: DataOpsManager) : AbstractFileM val createdDirFile = attributesService.getOrCreateVirtualFile( RemoteUssAttributes( destAttributes.path, - UssFile(sourceFile.name, "drwxrwxrwx"), + UssFile(operation.newName ?: sourceFile.name, "drwxrwxrwx"), destConnectionConfig.url, destConnectionConfig ) diff --git a/src/main/kotlin/org/zowe/explorer/dataops/operations/mover/UssToUssFileMover.kt b/src/main/kotlin/org/zowe/explorer/dataops/operations/mover/UssToUssFileMover.kt index 93a5e2bce..9d8424bc1 100644 --- a/src/main/kotlin/org/zowe/explorer/dataops/operations/mover/UssToUssFileMover.kt +++ b/src/main/kotlin/org/zowe/explorer/dataops/operations/mover/UssToUssFileMover.kt @@ -107,7 +107,7 @@ class UssToUssFileMover(private val dataOpsManager: DataOpsManager) : AbstractFi from = from, overwrite = operation.forceOverwriting, links = CopyDataUSS.Links.ALL, - preserve = CopyDataUSS.Preserve.ALL, + preserve = CopyDataUSS.Preserve.NONE, recursive = true ), filePath = FilePath( diff --git a/src/main/kotlin/org/zowe/explorer/ui/build/jobs/JobBuildTreeView.kt b/src/main/kotlin/org/zowe/explorer/ui/build/jobs/JobBuildTreeView.kt index fa38cbc46..77cf77d29 100644 --- a/src/main/kotlin/org/zowe/explorer/ui/build/jobs/JobBuildTreeView.kt +++ b/src/main/kotlin/org/zowe/explorer/ui/build/jobs/JobBuildTreeView.kt @@ -32,11 +32,14 @@ import org.zowe.explorer.dataops.DataOpsManager import org.zowe.explorer.dataops.log.JobLogFetcher import org.zowe.explorer.dataops.log.JobProcessInfo import org.zowe.explorer.dataops.log.MFLogger +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking import org.zowe.kotlinsdk.SpoolFile import java.awt.BorderLayout import java.util.* import javax.swing.JComponent import javax.swing.tree.DefaultMutableTreeNode +import javax.swing.tree.TreeNode val JOBS_LOG_VIEW = DataKey.create("jobsLogView") const val JOBS_LOG_NOTIFICATION_GROUP_ID = "org.zowe.explorer.explorer.ExplorerNotificationGroup" @@ -100,20 +103,25 @@ class JobBuildTreeView( treeConsoleView.component.add(it.component, BorderLayout.PAGE_START) } - jobLogger.onNextLog { + jobLogger.startLogging() + + fun onNextLog() { val cachedSpoolLog = jobLogger.logFetcher.getCachedLog() - cachedSpoolLog - .minus(spoolFileToLogMap.keys) - .forEach { + if (cachedSpoolLog.count() != spoolFileToLogMap.count()) { + cachedSpoolLog.minus(spoolFileToLogMap.keys).forEach { treeConsoleView.onEvent(buildId, StartEventImpl(it.key.id, buildId, Date().time, it.key.ddName)) } - cachedSpoolLog - .forEach { + cachedSpoolLog.forEach { val prevLog = spoolFileToLogMap[it.key] ?: "" val logToDisplay = if (it.value.length >= prevLog.length) it.value.substring(prevLog.length) else prevLog treeConsoleView.onEvent(buildId, OutputBuildEventImpl(it.key.id, logToDisplay, true)) spoolFileToLogMap[it.key] = it.value } + } + } + + jobLogger.onNextLog { + onNextLog() } jobLogger.onLogFinished { @@ -122,43 +130,82 @@ class JobBuildTreeView( .getCachedJobStatus() ?.returnedCode ?.uppercase() - var codeWithWarning = false - val result = if (rc == null || rc.contains(Regex("ERR|ABEND|CANCEL|FAIL"))) FailureResultImpl() - else if (rc.contains("CC")) { // result code can be in format "CC nnnn" + //Variables were added to set the correct icon depending on the result of the job execution. + //For any execution status, FailureResultImpl will be used to display DDs + val ret = if (rc?.contains("CC") == true) { // result code can be in format "CC nnnn" val completionCode = rc.split(" ")[1].toInt() when (completionCode) { - SUCCESSFUL_JOB_COMPLETION_CODE -> SuccessResultImpl() + SUCCESSFUL_JOB_COMPLETION_CODE -> ReturnCode.SUCCESS + SUCCESSFUL_JOB_COMPLETION_CODE_WITH_WARNING -> ReturnCode.WARNING + else -> ReturnCode.ERROR + } + } else ReturnCode.ERROR - SUCCESSFUL_JOB_COMPLETION_CODE_WITH_WARNING -> { - codeWithWarning = true - SuccessResultImpl() - } + jobLogger.fetchLog() + var finalLogFiles = jobLogger.logFetcher.getCachedLog() + if (finalLogFiles.count() != spoolFileToLogMap.count()) { + onNextLog() + } - else -> FailureResultImpl() - } - } else SuccessResultImpl() + runBlocking { + //TODO Need to be reworked + //It is possible that not all DDs are displayed (but information about them already exists), + // And, accordingly, the correct icon cannot be set for them. + //Sleep for 1 second to wait for display + if ((treeConsoleView.tree.model.root as DefaultMutableTreeNode).firstChild.childCount < spoolFileToLogMap.count()) + delay(1000) + + setIconRec( + (treeConsoleView.tree.model.root as DefaultMutableTreeNode).firstChild, + ret + ) + } - jobLogger.logFetcher.getCachedLog() + finalLogFiles .forEach { - treeConsoleView.onEvent(buildId, FinishEventImpl(it.key.id, buildId, Date().time, it.key.ddName, result)) - } - runCatching { - val buildNode = (treeConsoleView.tree.model.root as DefaultMutableTreeNode).firstChild - val buildExecutionNode = (buildNode as DefaultMutableTreeNode).userObject as ExecutionNode - if (result is FailureResultImpl) { - buildExecutionNode.setIconProvider { AllIcons.General.BalloonError } - } else if (codeWithWarning) { - buildExecutionNode.setIconProvider { AllIcons.General.BalloonWarning } - } else { - buildExecutionNode.setIconProvider { AllIcons.General.InspectionsOK } + treeConsoleView.onEvent( + buildId, + FinishEventImpl(it.key.id, buildId, Date().time, it.key.ddName, FailureResultImpl()) + ) } + treeConsoleView.onEvent( + buildId, + FinishBuildEventImpl(buildId, buildId, Date().time, buildId, FailureResultImpl()) + ) + } + } + + /** + * The function recursively sets the job execution status icon + */ + private fun setIconRec(buildNode: TreeNode, ret: ReturnCode) { + setIcon(buildNode, ret) + if (buildNode.childCount > 0) + for (currChild in buildNode.children()) { + setIcon(currChild, ret) } - treeConsoleView.onEvent(buildId, FinishBuildEventImpl(buildId, buildId, Date().time, buildId, result)) + } + + /** + * The function sets the job execution status icon + */ + private fun setIcon(buildNode: TreeNode, ret: ReturnCode) { + val buildExecutionNode = (buildNode as DefaultMutableTreeNode).userObject as ExecutionNode + when (ret) { + ReturnCode.ERROR -> buildExecutionNode.setIconProvider { AllIcons.General.BalloonError } + ReturnCode.WARNING -> buildExecutionNode.setIconProvider { AllIcons.General.BalloonWarning } + ReturnCode.SUCCESS -> buildExecutionNode.setIconProvider { AllIcons.General.InspectionsOK } } + } - jobLogger.startLogging() + /** + * Enum for job execution status + */ + enum class ReturnCode { + SUCCESS, WARNING, ERROR } + /** * Stops requesting logs from mainframe. */ diff --git a/src/main/kotlin/org/zowe/explorer/utils/ussFileTagUtils.kt b/src/main/kotlin/org/zowe/explorer/utils/ussFileTagUtils.kt index 06965cd55..ba977605c 100644 --- a/src/main/kotlin/org/zowe/explorer/utils/ussFileTagUtils.kt +++ b/src/main/kotlin/org/zowe/explorer/utils/ussFileTagUtils.kt @@ -15,6 +15,7 @@ import com.intellij.notification.Notification import com.intellij.notification.NotificationType import com.intellij.notification.Notifications import com.intellij.openapi.components.service +import org.zowe.explorer.common.message import org.zowe.explorer.config.connect.ConnectionConfig import org.zowe.explorer.dataops.DataOpsManager import org.zowe.explorer.dataops.attributes.RemoteUssAttributes @@ -37,14 +38,41 @@ const val FILE_TAG_NOTIFICATION_GROUP_ID = "org.zowe.explorer.utils.FileTagNotif fun checkUssFileTag(attributes: RemoteUssAttributes) { val charset = getUssFileTagCharset(attributes) if (charset != null) { - attributes.charset = charset + if (getSupportedEncodings().contains(charset)) { + attributes.charset = charset + } else { + notifyError( + message("filetag.unsupported.encoding.error.title", charset), + message("filetag.unsupported.encoding.error.message", DEFAULT_BINARY_CHARSET.name()) + ) + attributes.charset = DEFAULT_BINARY_CHARSET + } } else { attributes.charset = DEFAULT_BINARY_CHARSET } } +/** Matching file tag with CCSID value */ +private val fileTagToCcsidMap = mapOf( + "TIS-620" to 874, + "ISO8859-13" to 921, + "IBM-EUCJC" to 932, + "IBM-943" to 943, + "BIG5" to 950, + "IBM-4396" to 4396, + "IBM-4946" to 4946, + "IBM-5031" to 5031, + "IBM-5346" to 5346, + "IBM-5347" to 5347, + "IBM-5348" to 5348, + "IBM-5349" to 5349, + "IBM-5350" to 5350, + "IBM-5488" to 5488, + "EUCJP" to 33722, +) + /** - * Get encoding from file tag or return null if it doesn't exist. + * Get encoding from file tag or return null if it doesn't exist or encoding could not be determined. * @param attributes uss file attributes. */ fun getUssFileTagCharset(attributes: RemoteUssAttributes): Charset? { @@ -56,9 +84,20 @@ fun getUssFileTagCharset(attributes: RemoteUssAttributes): Charset? { val startPos = stdout.indexOfNonWhitespace(1) val endPos = stdout.indexOf(' ', startPos) val tagCharset = stdout.substring(startPos, endPos) - val ccsid = CCSID.getCCSID(tagCharset) - val codePage = CCSID.getCodepage(ccsid) - return Charset.forName(codePage) + runCatching { + val ccsid = fileTagToCcsidMap[tagCharset] ?: CCSID.getCCSID(tagCharset) + val codePage = CCSID.getCodepage(ccsid) + return Charset.forName(codePage) + }.onFailure { + runCatching { + return Charset.forName(tagCharset) + }.onFailure { + notifyError( + message("filetag.encoding.detection.error.title"), + message("filetag.encoding.detection.error.message", DEFAULT_BINARY_CHARSET.name()), + ) + } + } } } return null @@ -96,7 +135,7 @@ fun listUssFileTag(attributes: RemoteUssAttributes): ResponseBody? { ), ) }.onFailure { - notifyError(it,"Cannot list uss file tag for ${attributes.path}") + notifyError("Cannot list uss file tag for ${attributes.path}", it.message ?: "") } return response } @@ -146,7 +185,7 @@ private fun setUssFileTagCommon(charsetName: String, filePath: String, connectio ) ) }.onFailure { - notifyError(it, "Cannot set uss file tag for $filePath") + notifyError("Cannot set uss file tag for $filePath", it.message ?: "") } } @@ -167,21 +206,21 @@ fun removeUssFileTag(attributes: RemoteUssAttributes) { ) ) }.onFailure { - notifyError(it, "Cannot remove uss file tag for ${attributes.path}") + notifyError("Cannot remove uss file tag for ${attributes.path}", it.message ?: "") } } /** * Displays an error notification if an error was received. - * @param th thrown error. - * @param title error text. + * @param title error title. + * @param message error message. */ -private fun notifyError(th: Throwable, title: String) { +private fun notifyError(title: String, message: String) { Notifications.Bus.notify( Notification( FILE_TAG_NOTIFICATION_GROUP_ID, title, - th.message ?: "", + message, NotificationType.ERROR ) ) diff --git a/src/main/kotlin/org/zowe/explorer/utils/validationFunctions.kt b/src/main/kotlin/org/zowe/explorer/utils/validationFunctions.kt index e948e6216..0696f45fd 100644 --- a/src/main/kotlin/org/zowe/explorer/utils/validationFunctions.kt +++ b/src/main/kotlin/org/zowe/explorer/utils/validationFunctions.kt @@ -21,12 +21,11 @@ import org.zowe.explorer.explorer.ui.UssDirNode import org.zowe.explorer.explorer.ui.UssFileNode import org.zowe.explorer.utils.crudable.Crudable import org.zowe.explorer.utils.crudable.find -import org.zowe.kotlinsdk.DatasetOrganization import javax.swing.JComponent import javax.swing.JPasswordField import javax.swing.JTextField -private val urlRegex = Regex("^(https?|http)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]") +private val urlRegex = Regex("^https?:\\/\\/((([a-zA-Z0-9\\-_]+\\.)+[a-zA-Z]{2,})|(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}))(:\\d+)?((\\/[a-zA-Z0-9\\-_\\.~]+)?)*\\/?\$") private val maskRegex = Regex("^[A-Za-z\\$\\*%@#][A-Za-z0-9\\-\\$\\*%@#]{0,7}") private val ussPathRegex = Regex("^/$|^(/[^/]+)+$") private val forbiddenSymbol = "/" diff --git a/src/main/resources/messages/FMBundle.properties b/src/main/resources/messages/FMBundle.properties index f3234c9a3..a5a2a1a66 100755 --- a/src/main/resources/messages/FMBundle.properties +++ b/src/main/resources/messages/FMBundle.properties @@ -26,4 +26,8 @@ encoding.reload.dialog.title={0}: Reload to {1} encoding.reload.dialog.message=The encoding you'''ve chosen ('{1}') may change the contents of '{0}'.
Do you want to Reload the file from remote in the new encoding '{1}' and overwrite contents (may not display correctly).
encoding.convert.button.error.tooltip=Encoding conversion is not available because more than one project is open allocation.dialog.unit.size.hint.description=For IBM 3390 direct access storage device:
1 CYLINDER = 15 TRACKS
1 TRACK = 56664 BYTES -allocation.dialog.unit.size.hint.title=Allocation unit Size \ No newline at end of file +allocation.dialog.unit.size.hint.title=Allocation unit Size +filetag.encoding.detection.error.title=File Encoding Detection Error +filetag.encoding.detection.error.message=Failed to determine the file encoding. The default encoding {0} will be set. It can be changed through the file properties +filetag.unsupported.encoding.error.title=Unsupported Encoding {0} +filetag.unsupported.encoding.error.message=The file encoding is unsupported. The default encoding {0} will be set. It can be changed through the file properties \ No newline at end of file diff --git a/src/test/kotlin/org/zowe/explorer/utils/UtilsTestSpec.kt b/src/test/kotlin/org/zowe/explorer/utils/UtilsTestSpec.kt index 6057e69a1..cc01ed1bd 100644 --- a/src/test/kotlin/org/zowe/explorer/utils/UtilsTestSpec.kt +++ b/src/test/kotlin/org/zowe/explorer/utils/UtilsTestSpec.kt @@ -427,22 +427,78 @@ class UtilsTestSpec : ShouldSpec({ should("validate correct URL") { component.text = "https://some.url" - val actual = validateZosmfUrl(component) + var actual = validateZosmfUrl(component) val expected = null assertSoftly { actual shouldBe expected } + + component.text = "http://another.url" + actual = validateZosmfUrl(component) + + assertSoftly { + actual shouldBe expected + } + + component.text = "http://10.10.10.10" + actual = validateZosmfUrl(component) + + assertSoftly { + actual shouldBe expected + } + + component.text = "https://10.10.10.10:10000" + actual = validateZosmfUrl(component) + + assertSoftly { + actual shouldBe expected + } + + component.text = "http://another.url/hello/world" + actual = validateZosmfUrl(component) + + assertSoftly { + actual shouldBe expected + } } should("validate wrong URL") { component.text = "wrong url\"" - val actual = validateZosmfUrl(component) + var actual = validateZosmfUrl(component) val expected = ValidationInfo("Please provide a valid URL to z/OSMF. Example: https://myhost.com:10443", component) assertSoftly { actual shouldBe expected } + + component.text = "another.url" + actual = validateZosmfUrl(component) + + assertSoftly { + actual shouldBe expected + } + + component.text = "http://another" + actual = validateZosmfUrl(component) + + assertSoftly { + actual shouldBe expected + } + + component.text = "http://10.10.10:10" + actual = validateZosmfUrl(component) + + assertSoftly { + actual shouldBe expected + } + + component.text = "http://another.url/hell?0/world&!?" + actual = validateZosmfUrl(component) + + assertSoftly { + actual shouldBe expected + } } } context("validateFieldWithLengthRestriction") {