From 90b9ce17622970547dfb1d7bb83425f357020c40 Mon Sep 17 00:00:00 2001 From: Valiantsin Krus Date: Wed, 17 Jan 2024 10:25:33 +0100 Subject: [PATCH] IJMP-1475: added all necessary reworks. Also bug IJMP-1325 is fixed. --- .../formainframe/dataops/DataOpsManager.kt | 3 + .../dataops/DataOpsManagerImpl.kt | 11 +++ .../RemoteAttributedContentSynchronizer.kt | 13 +++ .../operations/mover/UssFileToPdsMover.kt | 2 +- .../mover/names/CopyPasteNameResolver.kt | 51 ++++++++++++ .../mover/names/DatasetOrDirResolver.kt | 37 +++++++++ .../mover/names/DefaultNameResolver.kt | 35 ++++++++ .../mover/names/IndexedNameResolver.kt | 44 ++++++++++ .../mover/names/NotSeqToPDSResolver.kt | 39 +++++++++ .../mover/names/SeqToPDSResolver.kt | 39 +++++++++ .../explorer/ui/ExplorerPasteProvider.kt | 81 ++++++------------- src/main/resources/META-INF/plugin.xml | 15 ++++ .../ui/ExplorerPasteProviderTestSpec.kt | 36 +++++++++ .../testServiceImpl/TestDataOpsManagerImpl.kt | 9 +++ 14 files changed, 359 insertions(+), 56 deletions(-) create mode 100644 src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/names/CopyPasteNameResolver.kt create mode 100644 src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/names/DatasetOrDirResolver.kt create mode 100644 src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/names/DefaultNameResolver.kt create mode 100644 src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/names/IndexedNameResolver.kt create mode 100644 src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/names/NotSeqToPDSResolver.kt create mode 100644 src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/names/SeqToPDSResolver.kt diff --git a/src/main/kotlin/eu/ibagroup/formainframe/dataops/DataOpsManager.kt b/src/main/kotlin/eu/ibagroup/formainframe/dataops/DataOpsManager.kt index efdbe049..3790af4f 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/dataops/DataOpsManager.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/dataops/DataOpsManager.kt @@ -26,6 +26,7 @@ import eu.ibagroup.formainframe.dataops.fetch.FileFetchProvider import eu.ibagroup.formainframe.dataops.log.LogFetcher import eu.ibagroup.formainframe.dataops.log.MFLogger import eu.ibagroup.formainframe.dataops.log.MFProcessInfo +import eu.ibagroup.formainframe.dataops.operations.mover.names.CopyPasteNameResolver interface DataOpsManager : Disposable { @@ -65,6 +66,8 @@ interface DataOpsManager : Disposable { fun getMFContentAdapter(file: VirtualFile): MFContentAdapter + fun getNameResolver(source: VirtualFile, destination: VirtualFile): CopyPasteNameResolver + fun isOperationSupported(operation: Operation<*>): Boolean @Throws(Throwable::class) diff --git a/src/main/kotlin/eu/ibagroup/formainframe/dataops/DataOpsManagerImpl.kt b/src/main/kotlin/eu/ibagroup/formainframe/dataops/DataOpsManagerImpl.kt index 6d9be738..694f06df 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/dataops/DataOpsManagerImpl.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/dataops/DataOpsManagerImpl.kt @@ -27,6 +27,8 @@ import eu.ibagroup.formainframe.dataops.log.LogFetcher import eu.ibagroup.formainframe.dataops.log.MFLogger import eu.ibagroup.formainframe.dataops.log.MFProcessInfo import eu.ibagroup.formainframe.dataops.operations.OperationRunner +import eu.ibagroup.formainframe.dataops.operations.mover.names.CopyPasteNameResolver +import eu.ibagroup.formainframe.dataops.operations.mover.names.DefaultNameResolver import eu.ibagroup.formainframe.utils.associateListedBy import eu.ibagroup.formainframe.utils.findAnyNullable import eu.ibagroup.formainframe.utils.log @@ -137,6 +139,15 @@ class DataOpsManagerImpl : DataOpsManager { } private val mfContentAdapters by mfContentAdaptersDelegate + private val nameResolversDelegate = lazy { + CopyPasteNameResolver.EP.extensionList.buildComponents() + } + private val nameResolvers by nameResolversDelegate + + override fun getNameResolver(source: VirtualFile, destination: VirtualFile): CopyPasteNameResolver { + return nameResolvers.firstOrNull { it.accepts(source, destination) } ?: DefaultNameResolver() + } + /** * Checks if sync with mainframe is supported for provided object * @param file object on mainframe that should be checked on availability of synchronization diff --git a/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/RemoteAttributedContentSynchronizer.kt b/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/RemoteAttributedContentSynchronizer.kt index 87312074..1b2a2668 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/RemoteAttributedContentSynchronizer.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/dataops/content/synchronizer/RemoteAttributedContentSynchronizer.kt @@ -104,6 +104,19 @@ 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) { + fetchedAtLeastOnce.removeIf { it.file == file } + // if you will not delete the file than "Local cache conflict" dialog appear. + runWriteActionInEdtAndWait { + file.delete(this@RemoteAttributedContentSynchronizer) + } + } + /** * Base implementation of [ContentSynchronizer.synchronizeWithRemote] method for each synchronizer. * Doesn't need to be overridden in most cases diff --git a/src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/UssFileToPdsMover.kt b/src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/UssFileToPdsMover.kt index 53acdd49..ddb1d0da 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/UssFileToPdsMover.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/UssFileToPdsMover.kt @@ -73,7 +73,7 @@ class UssFileToPdsMover(private val dataOpsManager: DataOpsManager) : AbstractFi val from = sourceAttributes.path val to = destinationAttributes.name val api = api(connectionConfig) - var memberName = sourceAttributes.name.filter { it.isLetterOrDigit() }.take(8) + var memberName = operation.newName ?: sourceAttributes.name.filter { it.isLetterOrDigit() }.take(8) if (memberName.isEmpty()) { memberName = "empty" } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/names/CopyPasteNameResolver.kt b/src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/names/CopyPasteNameResolver.kt new file mode 100644 index 00000000..f17dd80c --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/names/CopyPasteNameResolver.kt @@ -0,0 +1,51 @@ +/* + * 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 eu.ibagroup.formainframe.dataops.operations.mover.names + +import com.intellij.openapi.extensions.ExtensionPointName +import com.intellij.openapi.vfs.VirtualFile +import eu.ibagroup.formainframe.dataops.DataOpsComponentFactory + +interface CopyPasteNameResolverFactory: DataOpsComponentFactory + +/** + * Class to represent a name resolution for conflicting situation. + * @author Valiantsin Krus + */ +interface CopyPasteNameResolver { + companion object { + @JvmField + val EP = ExtensionPointName.create("eu.ibagroup.formainframe.nameResolver") + } + + /** + * Determines whether this name resolver could resolve conflict for passed files or not. + * @param source source file to copy in destination folder (or folder-like entity). + * @param destination folder-like entity to copy file to. + * @return true if this name resolver could dot it or false otherwise. + */ + fun accepts(source: VirtualFile, destination: VirtualFile): Boolean + + /** + * Finds child in destination folder that conflicts with source file. + * @param source source file to copy in destination folder (or folder-like entity). + * @param destination folder-like entity to copy file to. + * @return instance of conflicting child file or null if it was not found. + */ + fun getConflictingChild(source: VirtualFile, destination: VirtualFile): VirtualFile? + + /** + * Creates new name for source file to make it possible to be copied in destination folder. + * @param source source file to copy in destination folder (or folder-like entity). + * @param destination folder-like entity to copy file to. + * @return string with new file name. + */ + fun resolve(source: VirtualFile, destination: VirtualFile): String +} diff --git a/src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/names/DatasetOrDirResolver.kt b/src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/names/DatasetOrDirResolver.kt new file mode 100644 index 00000000..794dbf13 --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/names/DatasetOrDirResolver.kt @@ -0,0 +1,37 @@ +/* + * 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 eu.ibagroup.formainframe.dataops.operations.mover.names + +import com.intellij.openapi.vfs.VirtualFile +import eu.ibagroup.formainframe.dataops.DataOpsManager +import eu.ibagroup.formainframe.dataops.attributes.RemoteDatasetAttributes + +class DatasetOrDirResolverFactory : CopyPasteNameResolverFactory { + override fun buildComponent(dataOpsManager: DataOpsManager): CopyPasteNameResolver { + return DatasetOrDirResolver(dataOpsManager) + } +} + +/** + * Implementation of [CopyPasteNameResolver] for copying dataset or directory to uss or local system. + * @author Valiantsin Krus + */ +class DatasetOrDirResolver(val dataOpsManager: DataOpsManager): IndexedNameResolver() { + override fun accepts(source: VirtualFile, destination: VirtualFile): Boolean { + val sourceAttributes = dataOpsManager.tryToGetAttributes(source) + val destinationAttributes = dataOpsManager.tryToGetAttributes(destination) + return (source.isDirectory || sourceAttributes is RemoteDatasetAttributes) && destinationAttributes !is RemoteDatasetAttributes + } + + + override fun resolveNameWithIndex(source: VirtualFile, destination: VirtualFile, index: Int?): String { + return if (index == null) source.name else "${source.name}_(${index})" + } +} \ No newline at end of file diff --git a/src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/names/DefaultNameResolver.kt b/src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/names/DefaultNameResolver.kt new file mode 100644 index 00000000..b29a5841 --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/names/DefaultNameResolver.kt @@ -0,0 +1,35 @@ +/* + * 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 eu.ibagroup.formainframe.dataops.operations.mover.names + +import com.intellij.openapi.vfs.VirtualFile + +/** + * Implementation of [IndexedNameResolver] that is used by default (if no one other name resolver was found) + * It just adds _() to the end of the file name before extension. + * @author Valiantsin Krus + */ +class DefaultNameResolver: IndexedNameResolver() { + override fun accepts(source: VirtualFile, destination: VirtualFile): Boolean { + return true + } + + override fun resolveNameWithIndex(source: VirtualFile, destination: VirtualFile, index: Int?): String { + val sourceName = source.name + return if (index == null) { + sourceName + } else { + val extension = if (sourceName.contains(".")) sourceName.substringAfterLast(".") else null + val newNameWithoutExtension = "${sourceName.substringBeforeLast(".")}_(${index})" + if (extension != null) "$newNameWithoutExtension.$extension" else newNameWithoutExtension + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/names/IndexedNameResolver.kt b/src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/names/IndexedNameResolver.kt new file mode 100644 index 00000000..c6ba515a --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/names/IndexedNameResolver.kt @@ -0,0 +1,44 @@ +/* + * 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 eu.ibagroup.formainframe.dataops.operations.mover.names + +import com.intellij.openapi.vfs.VirtualFile + +/** + * Name resolver that generates new name based on the index (e.g. file_(1), file_(2) and etc.) + * This is an abstract class, it only finds the necessary index but doesn't create a new name. + * It could be, for example file1, file2, file3. It all depends on implementation needs. + * @author Valiantsin Krus + */ +abstract class IndexedNameResolver: CopyPasteNameResolver { + + override fun getConflictingChild(source: VirtualFile, destination: VirtualFile): VirtualFile? { + val rowNameToCopy = resolveNameWithIndex(source, destination, null) + return destination.children.firstOrNull { it.name == rowNameToCopy } + } + + /** + * Creates new name for a source file based on passed index. + * @param source source file to copy in destination folder (or folder-like entity). + * @param destination folder-like entity to copy file to. + * @param index generated index to add to the source name. If it is null, then no index is needed to add. + * @return new name with joined index. + */ + abstract fun resolveNameWithIndex(source: VirtualFile, destination: VirtualFile, index: Int?): String + + override fun resolve(source: VirtualFile, destination: VirtualFile): String { + var newName = resolveNameWithIndex(source, destination, null) + var index = 1 + while (destination.children.any { it.name == newName }) { + newName = resolveNameWithIndex(source, destination, index++) + } + return newName + } +} \ No newline at end of file diff --git a/src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/names/NotSeqToPDSResolver.kt b/src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/names/NotSeqToPDSResolver.kt new file mode 100644 index 00000000..aeaf6276 --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/names/NotSeqToPDSResolver.kt @@ -0,0 +1,39 @@ +/* + * 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 eu.ibagroup.formainframe.dataops.operations.mover.names + +import com.intellij.openapi.vfs.VirtualFile +import eu.ibagroup.formainframe.dataops.DataOpsManager +import eu.ibagroup.formainframe.dataops.attributes.RemoteDatasetAttributes + +class NotSeqToPDSResolverFactory : CopyPasteNameResolverFactory { + override fun buildComponent(dataOpsManager: DataOpsManager): CopyPasteNameResolver { + return NotSeqToPDSResolver(dataOpsManager) + } +} + +/** + * Implementation of [CopyPasteNameResolver] for copying anything except of Sequential Dataset to PDS. + * @author Valiantsin Krus + */ +class NotSeqToPDSResolver(val dataOpsManager: DataOpsManager) : IndexedNameResolver() { + override fun accepts(source: VirtualFile, destination: VirtualFile): Boolean { + val sourceAttributes = dataOpsManager.tryToGetAttributes(source) + val destinationAttributes = dataOpsManager.tryToGetAttributes(destination) + return destinationAttributes is RemoteDatasetAttributes && + sourceAttributes !is RemoteDatasetAttributes + } + + override fun resolveNameWithIndex(source: VirtualFile, destination: VirtualFile, index: Int?): String { + val memberName = source.name.filter { it.isLetterOrDigit() }.uppercase().ifEmpty { "EMPTY" } + return if (index == null) memberName.take(8) else "${memberName.take(7)}$index" + } + +} \ No newline at end of file diff --git a/src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/names/SeqToPDSResolver.kt b/src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/names/SeqToPDSResolver.kt new file mode 100644 index 00000000..358eacf1 --- /dev/null +++ b/src/main/kotlin/eu/ibagroup/formainframe/dataops/operations/mover/names/SeqToPDSResolver.kt @@ -0,0 +1,39 @@ +/* + * 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 eu.ibagroup.formainframe.dataops.operations.mover.names + +import com.intellij.openapi.vfs.VirtualFile +import eu.ibagroup.formainframe.dataops.DataOpsManager +import eu.ibagroup.formainframe.dataops.attributes.RemoteDatasetAttributes + +class SeqToPDSResolverFactory : CopyPasteNameResolverFactory { + override fun buildComponent(dataOpsManager: DataOpsManager): CopyPasteNameResolver { + return SeqToPDSResolver(dataOpsManager) + } +} + +/** + * Implementation of [CopyPasteNameResolver] for copying Sequential Dataset to PDS. + * @author Valiantsin Krus + */ +class SeqToPDSResolver(val dataOpsManager: DataOpsManager) : IndexedNameResolver() { + override fun accepts(source: VirtualFile, destination: VirtualFile): Boolean { + val sourceAttributes = dataOpsManager.tryToGetAttributes(source) + val destinationAttributes = dataOpsManager.tryToGetAttributes(destination) + return sourceAttributes is RemoteDatasetAttributes && + !source.isDirectory && + destinationAttributes is RemoteDatasetAttributes + } + + override fun resolveNameWithIndex(source: VirtualFile, destination: VirtualFile, index: Int?): String { + val lastQualifier = source.name.split(".").last() + return if (index == null) lastQualifier else "${lastQualifier.take(7)}${index}" + } +} \ No newline at end of file diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerPasteProvider.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerPasteProvider.kt index 17114811..2ee67229 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerPasteProvider.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerPasteProvider.kt @@ -28,8 +28,8 @@ import eu.ibagroup.formainframe.analytics.events.FileEvent import eu.ibagroup.formainframe.common.ui.cleanInvalidateOnExpand import eu.ibagroup.formainframe.dataops.DataOpsManager import eu.ibagroup.formainframe.dataops.attributes.RemoteDatasetAttributes -import eu.ibagroup.formainframe.dataops.attributes.RemoteMemberAttributes import eu.ibagroup.formainframe.dataops.attributes.RemoteUssAttributes +import eu.ibagroup.formainframe.dataops.content.synchronizer.RemoteAttributedContentSynchronizer import eu.ibagroup.formainframe.dataops.operations.mover.MoveCopyOperation import eu.ibagroup.formainframe.explorer.FileExplorerContentProvider import eu.ibagroup.formainframe.utils.castOrNull @@ -217,6 +217,17 @@ class ExplorerPasteProvider : PasteProvider { if (explorerView.isCut.get()) { copyPasteSupport.removeFromBuffer { node -> node.file == op.source } } + + // this step is necessary to clean old file after force overwriting performed. + if (op.forceOverwriting) { + 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) + } + } } .onFailure { throwable -> explorerView.explorer.reportThrowable(throwable, project) @@ -433,45 +444,23 @@ class ExplorerPasteProvider : PasteProvider { ): List { val result = mutableListOf() -// val skipDestinationSourceList = mutableListOf>() -// val overwriteDestinationSourceList = mutableListOf>() - - val listOfAllConflicts = pasteDestinations - .mapNotNull { destFile -> - val destAttributes = dataOpsManager.tryToGetAttributes(destFile) - destFile.children - ?.map conflicts@{ destChild -> - val filteredSourceFiles = sourceFiles.filter { source -> - val sourceAttributes = dataOpsManager.tryToGetAttributes(source) - if ( - destAttributes is RemoteDatasetAttributes && - (sourceAttributes is RemoteUssAttributes || source is VirtualFileImpl) - ) { - val memberName = source.name.filter { it.isLetterOrDigit() }.take(8).uppercase() - if (memberName.isNotEmpty()) memberName == destChild.name else "EMPTY" == destChild.name - } else if ( - destAttributes is RemoteDatasetAttributes && - sourceAttributes is RemoteDatasetAttributes - ) { - sourceAttributes.name.split(".").last() == destChild.name - } else { - source.name == destChild.name - } - } - val foundConflicts = mutableListOf>() - if (filteredSourceFiles.isNotEmpty()) { - filteredSourceFiles.forEach { foundConflict -> foundConflicts.add(Pair(destFile, foundConflict)) } - } - foundConflicts - } + val conflicts = pasteDestinations.map { destFile -> + + val conflictingSources = sourceFiles.filter { source -> + val nameResolver = dataOpsManager.getNameResolver(source, destFile) + nameResolver.getConflictingChild(source, destFile) != null + } + val foundConflicts = mutableListOf>() + if (conflictingSources.isNotEmpty()) { + conflictingSources.forEach { foundConflict -> foundConflicts.add(Pair(destFile, foundConflict)) } } + foundConflicts + } .flatten() - val conflicts = mutableListOf>() - listOfAllConflicts.forEach { conflictList -> conflicts.addAll(conflictList) } + .toMutableList() // Handle conflicts with different file type (file - directory, directory - file) -// skipDestinationSourceList.addAll(conflictsThatCannotBeSolved) val conflictsThatCannotBeOverwritten = conflicts.filter { val conflictChild = it.first.findChild(it.second.name) (conflictChild?.isDirectory == true && !it.second.isDirectory) @@ -560,26 +549,8 @@ class ExplorerPasteProvider : PasteProvider { } allConflicts.forEach { conflict -> - var copyIndex = 1 - val sourceName = conflict.second.name - val isSourceDirectory = conflict.second.isDirectory - var newName: String - val destAttributes = dataOpsManager.tryToGetAttributes(conflict.first) - val sourceAttributes = dataOpsManager.tryToGetAttributes(conflict.second) - do { - newName = if (destAttributes is RemoteDatasetAttributes && sourceAttributes is RemoteDatasetAttributes) { - "${sourceAttributes.name.split(".").last().take(7)}$copyIndex" - } else if (destAttributes is RemoteDatasetAttributes) { - if (sourceName.length >= 8) "${sourceName.take(7)}$copyIndex" else "$sourceName$copyIndex" - } else if (isSourceDirectory || sourceAttributes is RemoteDatasetAttributes) { - "${sourceName}_(${copyIndex})" - } else { - val extension = if (sourceName.contains(".")) sourceName.substringAfterLast(".") else null - val newNameWithoutExtension = "${sourceName.substringBeforeLast(".")}_(${copyIndex})" - if (extension != null) "$newNameWithoutExtension.$extension" else newNameWithoutExtension - } - ++copyIndex - } while (conflict.first.children.any { it.name == newName }) + + val newName = dataOpsManager.getNameResolver(conflict.second, conflict.first).resolve(conflict.second, conflict.first) val newNameMessage = "If you select option \"Use new name\", the following name will be selected: $newName" diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 5606e2de..5e758531 100755 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -102,6 +102,9 @@ Example of how to see the output:
+ + @@ -226,6 +229,18 @@ Example of how to see the output:
+ + + + + + + + () val nodeToRefreshTarget = mockk() beforeEach { + every { + dataOpsManagerService.testInstance.getNameResolver(any() as VirtualFile, any() as VirtualFile) + } returns DefaultNameResolver() + // we do not need to refresh the nodes in below tests, so lets return default list for each node to refresh (USS for example) every { mockedFileExplorerView.myFsTreeStructure } returns mockk() every { mockedFileExplorerView.myStructure } returns mockk() @@ -1041,6 +1048,9 @@ class ExplorerPasteProviderTestSpec : WithApplicationShouldSpec({ every { dataOpsManagerService.testInstance.tryToGetAttributes(childDestinationVirtualFile) } returns childDestFileAttributes every { dataOpsManagerService.testInstance.tryToGetAttributes(mockedSourceFile) } returns mockedSourceAttributes every { dataOpsManagerService.testInstance.tryToGetAttributes(mockedTargetFile) } returns targetAttributes + every { + dataOpsManagerService.testInstance.getNameResolver(any() as VirtualFile, any() as VirtualFile) + } returns DefaultNameResolver() every { mockedDataContext.getData(IS_DRAG_AND_DROP_KEY) } returns true every { mockedDataContext.getData(CommonDataKeys.PROJECT) } returns mockedProject @@ -1298,10 +1308,14 @@ class ExplorerPasteProviderTestSpec : WithApplicationShouldSpec({ } answers { copyPasteNodeDataList.mapNotNull { nodeData -> nodeData.file?.let { Pair(mockedTargetFile, it) } } } should("Skip 2 files one by one") { + addMockedSourceFile("file.txt") addMockedSourceFile("file1.txt") addMockedTargetChildFile("file.txt") addMockedTargetChildFile("file1.txt") + every { + dataOpsManagerService.testInstance.getNameResolver(any() as VirtualFile, any() as VirtualFile) + } returns DefaultNameResolver() mockkStatic(Messages::class) var decideOptionSelected = false var skipNumber = 0 @@ -1341,6 +1355,10 @@ class ExplorerPasteProviderTestSpec : WithApplicationShouldSpec({ addMockedSourceFile("file1.txt") addMockedTargetChildFile("file.txt") addMockedTargetChildFile("file1.txt") + every { + dataOpsManagerService.testInstance.getNameResolver(any() as VirtualFile, any() as VirtualFile) + } returns DefaultNameResolver() + mockkStatic(Messages::class) var decideOptionSelected = false var overwriteSelected = false @@ -1403,6 +1421,10 @@ class ExplorerPasteProviderTestSpec : WithApplicationShouldSpec({ addMockedTargetChildFile("file.txt") addMockedTargetChildFile("file_(1).txt") addMockedTargetChildFile("file1.txt") + every { + dataOpsManagerService.testInstance.getNameResolver(any() as VirtualFile, any() as VirtualFile) + } returns DefaultNameResolver() + mockkStatic(Messages::class) var decideOptionSelected = false var overwriteSelected = false @@ -1468,6 +1490,9 @@ class ExplorerPasteProviderTestSpec : WithApplicationShouldSpec({ addMockedSourceFile("DATASET.TEST", sourceAttributes = datasetAttributes) addMockedTargetChildFile("directory.test", true) addMockedTargetChildFile("DATASET.TEST") + every { + dataOpsManagerService.testInstance.getNameResolver(any() as VirtualFile, any() as VirtualFile) + } returns DatasetOrDirResolver(dataOpsManagerService) mockkStatic(Messages::class) var decideOptionSelected = false @@ -1527,6 +1552,9 @@ class ExplorerPasteProviderTestSpec : WithApplicationShouldSpec({ addMockedSourceFile("DATASET.TEST", sourceAttributes = datasetAttributes) addMockedTargetChildFile("TEST") + every { + dataOpsManagerService.testInstance.getNameResolver(any() as VirtualFile, any() as VirtualFile) + } returns SeqToPDSResolver(dataOpsManagerService) every { dataOpsManagerService.testInstance.tryToGetAttributes(mockedTargetFile) } returns targetAttributesPDS mockkStatic(Messages::class) @@ -1576,6 +1604,10 @@ class ExplorerPasteProviderTestSpec : WithApplicationShouldSpec({ addMockedSourceFile("dir2", isPastePossible = true, isDirectory = true) addMockedTargetChildFile("dir1", true) addMockedTargetChildFile("dir2") + every { + dataOpsManagerService.testInstance.getNameResolver(any() as VirtualFile, any() as VirtualFile) + } returns DefaultNameResolver() + mockkStatic(Messages::class) var decideOptionSelected = false @@ -1629,6 +1661,7 @@ class ExplorerPasteProviderTestSpec : WithApplicationShouldSpec({ isPastePerformed = false val sourceFile = addMockedSourceFile("file.txt", parent = mockedTargetFile) destinationChildFiles.add(sourceFile) + mockkStatic(Messages::class) var skipSelected = false @@ -1725,6 +1758,9 @@ class ExplorerPasteProviderTestSpec : WithApplicationShouldSpec({ destinationChildFiles.add(sourceFile1) val sourceFile2 = addMockedSourceFile("file2.txt", parent = mockedTargetFile) destinationChildFiles.add(sourceFile2) + every { + dataOpsManagerService.testInstance.getNameResolver(any() as VirtualFile, any() as VirtualFile) + } returns DefaultNameResolver() mockkStatic(Messages::class) var decideEachSelected = false diff --git a/src/test/kotlin/eu/ibagroup/formainframe/testutils/testServiceImpl/TestDataOpsManagerImpl.kt b/src/test/kotlin/eu/ibagroup/formainframe/testutils/testServiceImpl/TestDataOpsManagerImpl.kt index cc7e8c99..7ab3db53 100644 --- a/src/test/kotlin/eu/ibagroup/formainframe/testutils/testServiceImpl/TestDataOpsManagerImpl.kt +++ b/src/test/kotlin/eu/ibagroup/formainframe/testutils/testServiceImpl/TestDataOpsManagerImpl.kt @@ -25,6 +25,7 @@ import eu.ibagroup.formainframe.dataops.fetch.FileFetchProvider import eu.ibagroup.formainframe.dataops.log.LogFetcher import eu.ibagroup.formainframe.dataops.log.MFLogger import eu.ibagroup.formainframe.dataops.log.MFProcessInfo +import eu.ibagroup.formainframe.dataops.operations.mover.names.CopyPasteNameResolver import io.mockk.every import io.mockk.mockk @@ -73,6 +74,10 @@ open class TestDataOpsManagerImpl(override val componentManager: ComponentManage TODO("Not yet implemented") } + override fun getNameResolver(source: VirtualFile, destination: VirtualFile): CopyPasteNameResolver { + TODO("Not yet implemented") + } + override fun isOperationSupported(operation: Operation<*>): Boolean { TODO("Not yet implemented") } @@ -132,6 +137,10 @@ open class TestDataOpsManagerImpl(override val componentManager: ComponentManage return this.testInstance.getMFContentAdapter(file) } + override fun getNameResolver(source: VirtualFile, destination: VirtualFile): CopyPasteNameResolver { + return this.testInstance.getNameResolver(source, destination) + } + override fun isOperationSupported(operation: Operation<*>): Boolean { return this.testInstance.isOperationSupported(operation) }