diff --git a/CHANGELOG.md b/CHANGELOG.md index 10db3e6b4..65db7e575 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,43 +2,82 @@ All notable changes to the Zowe IntelliJ Plugin will be documented in this file. +## `0.7.1 (2022-11-30)` + +* Feature: Unit tests for utils module ([16fae1fb](https://github.com/zowe/zowe-explorer-intellij/commit/16fae1fb)) ([683185bb](https://github.com/zowe/zowe-explorer-intellij/commit/683185bb)) + + +* Bugfix: DnD does not work properly ([e5dfa3a3](https://github.com/zowe/zowe-explorer-intellij/commit/e5dfa3a3)) +* Bugfix: Copy DS member to USS folder does not work ([1e94ec48](https://github.com/zowe/zowe-explorer-intellij/commit/1e94ec48)) +* Bugfix: Unknown type of file if copy-delete-copy the same PDS member ([21651646](https://github.com/zowe/zowe-explorer-intellij/commit/21651646)) +* Bugfix: Ctrl+C/Ctrl+V does not work if copy file from remote to local ([e5601e7f](https://github.com/zowe/zowe-explorer-intellij/commit/e5601e7f)) + ## `0.7.0 (2022-10-31)` * Breaking: Kotlin DSL v2 usage introduced. Plugin requires to use it with IntelliJ version >= 2022.1 -* Feature: Configurable batch size to load filter smoothly ([928baba](https://github.com/zowe/zowe-explorer-intellij/commit/928baba)) +* Feature: Configurable batch size to load filter + smoothly ([928baba](https://github.com/zowe/zowe-explorer-intellij/commit/928baba)) * Feature: Job Purge operation ([95bf7eb](https://github.com/zowe/zowe-explorer-intellij/commit/95bf7eb)) * Feature: Job Edit operation ([b3962de](https://github.com/zowe/zowe-explorer-intellij/commit/b3962de)) -* Feature: Copy local to remote ([d9dcdd2](https://github.com/zowe/zowe-explorer-intellij/commit/d9dcdd2), [ea92b4b](https://github.com/zowe/zowe-explorer-intellij/commit/ea92b4b)) +* Feature: Copy local to remote ([d9dcdd2](https://github.com/zowe/zowe-explorer-intellij/commit/d9dcdd2) + , [ea92b4b](https://github.com/zowe/zowe-explorer-intellij/commit/ea92b4b)) * Feature: Copy remote to remote ([586a7f2](https://github.com/zowe/zowe-explorer-intellij/commit/586a7f2)) -* Feature: GitHub issue #10: Edit Working sets directly from Tool Window ([63a2e4f](https://github.com/zowe/zowe-explorer-intellij/commit/63a2e4f)) -* Feature: GitHub issue #70: Add date and time to JES Explorer ([eaea2cc](https://github.com/zowe/zowe-explorer-intellij/commit/eaea2cc)) -* Feature: Copy remote to local: clarify warning ([26bbb2c](https://github.com/zowe/zowe-explorer-intellij/commit/26bbb2c)) -* Feature: GitHub issue #67: Allocate like for datasets with BLK will be with warning ([7ae6261](https://github.com/zowe/zowe-explorer-intellij/commit/7ae6261)) -* Feature: Move the file attribute conversion to a separate thread ([975f75d](https://github.com/zowe/zowe-explorer-intellij/commit/975f75d)) -* Feature: Source code documentation added ([636411e](https://github.com/zowe/zowe-explorer-intellij/commit/636411e), [11bb7dd](https://github.com/zowe/zowe-explorer-intellij/commit/11bb7dd)) - - -* Bugfix: File cache conflict if open JCL to edit it in JES explorer second time ([b3962de](https://github.com/zowe/zowe-explorer-intellij/commit/b3962de)) -* Bugfix: GitHub issue #86: Incorrect error message if mask length > 44 ([cfb4ab6](https://github.com/zowe/zowe-explorer-intellij/commit/cfb4ab6)) -* Bugfix: GitHub issue #87: Masks type autodetection does not work in Add/Edit Working Set dialogs ([49fc53a](https://github.com/zowe/zowe-explorer-intellij/commit/49fc53a)) -* Bugfix: Problem with automatic refresh after creating new members/deleting members from dataset ([928baba](https://github.com/zowe/zowe-explorer-intellij/commit/928baba)) -* Bugfix: Confusing dialog title 'Rename Directory' when renaming USS mask from context menu ([1e1a147](https://github.com/zowe/zowe-explorer-intellij/commit/1e1a147)) -* Bugfix: GitHub issue #81: There is no difference between upper and lower cases when create USS masks from context menu ([f8ea3e9](https://github.com/zowe/zowe-explorer-intellij/commit/f8ea3e9)) -* Bugfix: GitHub issue #88: Lower case is not changed to upper case during Job Filter creation ([c2f5b01](https://github.com/zowe/zowe-explorer-intellij/commit/c2f5b01)) -* Bugfix: GitHub issue #44: 'Sync data' button does not work properly when multiple changes in USS file ([27f9c6a](https://github.com/zowe/zowe-explorer-intellij/commit/27f9c6a)) -* Bugfix: GitHub issue #30: Create new member in dataset that does not have enough space creates empty member despite of warning ([7a649e6](https://github.com/zowe/zowe-explorer-intellij/commit/7a649e6)) -* Bugfix: GitHub issue #54: Accumulation of errors in WS that breaks WS ([8648da2](https://github.com/zowe/zowe-explorer-intellij/commit/8648da2)) -* Bugfix: USS file cannot be deleted in development branch ([8886770](https://github.com/zowe/zowe-explorer-intellij/commit/8886770)) -* Bugfix: z/OS version specified in connection information doesn't match the z/OS version returned from z/OSMF ([1148e10](https://github.com/zowe/zowe-explorer-intellij/commit/1148e10)) -* Bugfix: IDE error with ReadOnlyModificationException when set 'use binary mode' for read only uss-file ([c2ebf6a](https://github.com/zowe/zowe-explorer-intellij/commit/c2ebf6a)) -* Bugfix: GitHub issue #94: SYSPRINT I looked at first always opens in JES explorer for a job with multiple steps ([301012a](https://github.com/zowe/zowe-explorer-intellij/commit/301012a)) -* Bugfix: IDE error with CallException when try to open uss-file to which you have no access ([78650b9](https://github.com/zowe/zowe-explorer-intellij/commit/78650b9)) -* Bugfix: The content of sequential dataset/member is changed anyway even if you choose do not sync data with mainframe ([559b05e](https://github.com/zowe/zowe-explorer-intellij/commit/559b05e)) -* Bugfix: IDE error while retrieving job list in JES Explorer ([e3dfe93](https://github.com/zowe/zowe-explorer-intellij/commit/e3dfe93)) -* Bugfix: Extra item 'Rename' is active in the context menu if click on 'loading...'/'load more' in file explorer ([78ab43f](https://github.com/zowe/zowe-explorer-intellij/commit/78ab43f)) -* Bugfix: Impossible to open any file/dataset second time ([9dc62ef](https://github.com/zowe/zowe-explorer-intellij/commit/9dc62ef)) -* Bugfix: The job is marked with green icon as passed despite it finished with abend ([773a252](https://github.com/zowe/zowe-explorer-intellij/commit/773a252)) +* Feature: GitHub issue #10: Edit Working sets directly from Tool + Window ([63a2e4f](https://github.com/zowe/zowe-explorer-intellij/commit/63a2e4f)) +* Feature: GitHub issue #70: Add date and time to JES + Explorer ([eaea2cc](https://github.com/zowe/zowe-explorer-intellij/commit/eaea2cc)) +* Feature: Copy remote to local: clarify + warning ([26bbb2c](https://github.com/zowe/zowe-explorer-intellij/commit/26bbb2c)) +* Feature: GitHub issue #67: Allocate like for datasets with BLK will be with + warning ([7ae6261](https://github.com/zowe/zowe-explorer-intellij/commit/7ae6261)) +* Feature: Move the file attribute conversion to a separate + thread ([975f75d](https://github.com/zowe/zowe-explorer-intellij/commit/975f75d)) +* Feature: Source code documentation added ([636411e](https://github.com/zowe/zowe-explorer-intellij/commit/636411e) + , [11bb7dd](https://github.com/zowe/zowe-explorer-intellij/commit/11bb7dd)) + + +* Bugfix: File cache conflict if open JCL to edit it in JES explorer second + time ([b3962de](https://github.com/zowe/zowe-explorer-intellij/commit/b3962de)) +* Bugfix: GitHub issue #86: Incorrect error message if mask length > + 44 ([cfb4ab6](https://github.com/zowe/zowe-explorer-intellij/commit/cfb4ab6)) +* Bugfix: GitHub issue #87: Masks type autodetection does not work in Add/Edit Working Set + dialogs ([49fc53a](https://github.com/zowe/zowe-explorer-intellij/commit/49fc53a)) +* Bugfix: Problem with automatic refresh after creating new members/deleting members from + dataset ([928baba](https://github.com/zowe/zowe-explorer-intellij/commit/928baba)) +* Bugfix: Confusing dialog title 'Rename Directory' when renaming USS mask from context + menu ([1e1a147](https://github.com/zowe/zowe-explorer-intellij/commit/1e1a147)) +* Bugfix: GitHub issue #81: There is no difference between upper and lower cases when create USS masks from context + menu ([f8ea3e9](https://github.com/zowe/zowe-explorer-intellij/commit/f8ea3e9)) +* Bugfix: GitHub issue #88: Lower case is not changed to upper case during Job Filter + creation ([c2f5b01](https://github.com/zowe/zowe-explorer-intellij/commit/c2f5b01)) +* Bugfix: GitHub issue #44: 'Sync data' button does not work properly when multiple changes in USS + file ([27f9c6a](https://github.com/zowe/zowe-explorer-intellij/commit/27f9c6a)) +* Bugfix: GitHub issue #30: Create new member in dataset that does not have enough space creates empty member despite of + warning ([7a649e6](https://github.com/zowe/zowe-explorer-intellij/commit/7a649e6)) +* Bugfix: GitHub issue #54: Accumulation of errors in WS that breaks + WS ([8648da2](https://github.com/zowe/zowe-explorer-intellij/commit/8648da2)) +* Bugfix: USS file cannot be deleted in development + branch ([8886770](https://github.com/zowe/zowe-explorer-intellij/commit/8886770)) +* Bugfix: z/OS version specified in connection information doesn't match the z/OS version returned from + z/OSMF ([1148e10](https://github.com/zowe/zowe-explorer-intellij/commit/1148e10)) +* Bugfix: IDE error with ReadOnlyModificationException when set 'use binary mode' for read only + uss-file ([c2ebf6a](https://github.com/zowe/zowe-explorer-intellij/commit/c2ebf6a)) +* Bugfix: GitHub issue #94: SYSPRINT I looked at first always opens in JES explorer for a job with multiple + steps ([301012a](https://github.com/zowe/zowe-explorer-intellij/commit/301012a)) +* Bugfix: IDE error with CallException when try to open uss-file to which you have no + access ([78650b9](https://github.com/zowe/zowe-explorer-intellij/commit/78650b9)) +* Bugfix: The content of sequential dataset/member is changed anyway even if you choose do not sync data with + mainframe ([559b05e](https://github.com/zowe/zowe-explorer-intellij/commit/559b05e)) +* Bugfix: IDE error while retrieving job list in JES + Explorer ([e3dfe93](https://github.com/zowe/zowe-explorer-intellij/commit/e3dfe93)) +* Bugfix: Extra item 'Rename' is active in the context menu if click on 'loading...'/'load more' in file + explorer ([78ab43f](https://github.com/zowe/zowe-explorer-intellij/commit/78ab43f)) +* Bugfix: Impossible to open any file/dataset second + time ([9dc62ef](https://github.com/zowe/zowe-explorer-intellij/commit/9dc62ef)) +* Bugfix: The job is marked with green icon as passed despite it finished with + abend ([773a252](https://github.com/zowe/zowe-explorer-intellij/commit/773a252)) * Bugfix: GitHub issue #16: Error creating zOSMF connection -* Bugfix: GitHub issue #85: The windows 'Add Working Set'/'Edit Working Set' are automatically resized if z/OSMF connection with very long name is added +* Bugfix: GitHub issue #85: The windows 'Add Working Set'/'Edit Working Set' are automatically resized if z/OSMF + connection with very long name is added diff --git a/build.gradle.kts b/build.gradle.kts index 886fd6c9e..24c964e1e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -58,7 +58,7 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-reflect:1.6.20") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.2") implementation("org.jgrapht:jgrapht-core:1.5.1") - implementation("eu.ibagroup:r2z:1.2.3-rc.1") + implementation("eu.ibagroup:r2z:1.2.3") implementation("com.segment.analytics.java:analytics:+") testImplementation("io.mockk:mockk:1.12.4") testImplementation("org.mock-server:mockserver-netty:5.13.2") @@ -90,50 +90,17 @@ tasks { """ WARNING: version 0.7 introduces breaking change. You won't be able to use the plugin with IntelliJ version < 2022.1
- New features: - -
Minor changes:
Fixed bugs: """ ) } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/common/ui/treeUtils.kt b/src/main/kotlin/eu/ibagroup/formainframe/common/ui/treeUtils.kt index 50ac4900f..bc7867597 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/common/ui/treeUtils.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/common/ui/treeUtils.kt @@ -69,7 +69,7 @@ fun TreePath.getVirtualFile(): VirtualFile? { /** * Removes node from "invalidateOnExpand" collection of explorer view. * @param node node to remove. - * @param view explorer view from wich to return node. + * @param view explorer view from which to return node. */ fun cleanInvalidateOnExpand( node: ExplorerTreeNode<*>, @@ -83,4 +83,4 @@ fun cleanInvalidateOnExpand( } } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/eu/ibagroup/formainframe/dataops/DataOpsManagerImpl.kt b/src/main/kotlin/eu/ibagroup/formainframe/dataops/DataOpsManagerImpl.kt index 1c6f09545..1572a97e8 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/dataops/DataOpsManagerImpl.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/dataops/DataOpsManagerImpl.kt @@ -72,7 +72,8 @@ class DataOpsManagerImpl : DataOpsManager { * @return attributes of file/folder */ override fun tryToGetAttributes(file: VirtualFile): FileAttributes? { - return attributesServices.stream() + return attributesServices + .stream() .filter { it.vFileClass.isAssignableFrom(file::class.java) } .map { it.getAttributes(file) } .filter { it != null } @@ -111,11 +112,11 @@ class DataOpsManagerImpl : DataOpsManager { @Suppress("UNCHECKED_CAST") return fileFetchProviders.find { it.requestClass.isAssignableFrom(requestClass) - && it.queryClass.isAssignableFrom(queryClass) - && it.vFileClass.isAssignableFrom(vFileClass) + && it.queryClass.isAssignableFrom(queryClass) + && it.vFileClass.isAssignableFrom(vFileClass) } as FileFetchProvider? ?: throw IllegalArgumentException( "Cannot find FileFetchProvider for " + - "requestClass=${requestClass.name}; queryClass=${queryClass.name}; vFileClass=${vFileClass.name}" + "requestClass=${requestClass.name}; queryClass=${queryClass.name}; vFileClass=${vFileClass.name}" ) } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/dataops/fetch/FileFetchProvider.kt b/src/main/kotlin/eu/ibagroup/formainframe/dataops/fetch/FileFetchProvider.kt index a9c2191b4..b5262e4a0 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/dataops/fetch/FileFetchProvider.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/dataops/fetch/FileFetchProvider.kt @@ -38,6 +38,9 @@ interface FileFetchProvider, File : VirtualFile> { fun reload(query: Q, progressIndicator: ProgressIndicator = DumbProgressIndicator.INSTANCE) + /** Function for "load more" nodes */ + fun loadMode(query: Q, progressIndicator: ProgressIndicator = DumbProgressIndicator.INSTANCE) + /** * File fetch provider contains all list of queries inside. * If the query was created with default parameters - it will find query with real parameters. @@ -45,7 +48,7 @@ interface FileFetchProvider, File : VirtualFile> { * @param query supposed query. * @return real query instance or null if it was not found. */ - fun > getRealQueryInstance(query: Q?): Q? + fun > getRealQueryInstance(query: Q?): Q? val requestClass: Class @@ -53,4 +56,4 @@ interface FileFetchProvider, File : VirtualFile> { val vFileClass: Class -} \ No newline at end of file +} diff --git a/src/main/kotlin/eu/ibagroup/formainframe/dataops/fetch/RemoteFileFetchProviderBase.kt b/src/main/kotlin/eu/ibagroup/formainframe/dataops/fetch/RemoteFileFetchProviderBase.kt index 1fde2828e..d42c6880d 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/dataops/fetch/RemoteFileFetchProviderBase.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/dataops/fetch/RemoteFileFetchProviderBase.kt @@ -14,7 +14,6 @@ import com.intellij.openapi.components.service import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.vfs.VirtualFile -import eu.ibagroup.formainframe.dataops.BatchedRemoteQuery import eu.ibagroup.formainframe.dataops.DataOpsManager import eu.ibagroup.formainframe.dataops.Query import eu.ibagroup.formainframe.dataops.RemoteQuery @@ -99,8 +98,60 @@ abstract class RemoteFileFetchProviderBase, progressIndicator: ProgressIndicator): List { + val fetched = fetchResponse(query, progressIndicator) + return runWriteActionOnWriteThread { + fetched.mapNotNull { + convertResponseToFile(it) + } + } + } + + /** + * Trigger onCacheUpdated event when the operation is completed successfully + * @param query the query that was used in fetch call + * @param files the files that were fetched + */ + private fun publishCacheUpdated(query: RemoteQuery, files: List) { + sendTopic(FileFetchProvider.CACHE_CHANGES, dataOpsManager.componentManager).onCacheUpdated(query, files) + } + + /** + * Trigger onFetchCancelled if the fetch operation is cancelled, and onFetchFailure in case the fetch operation is failed. + * In case of process cancellation, cleans the cache. In case of fetch failure, makes cache in error state, resets the cache + */ + private fun publishFetchCancelledOrFailed(query: RemoteQuery, throwable: Throwable) { + if (throwable is ProcessCanceledException) { + cleanCacheInternal(query, false) + sendTopic(FileFetchProvider.CACHE_CHANGES, dataOpsManager.componentManager).onFetchCancelled(query) + } else { + if (throwable is CallException) { + val details = throwable.errorParams?.get("details") + var errorMessage = throwable.message ?: "Error" + if (details is List<*>) { + errorMessage = details[0] as String + } + errorMessages[query] = + service().separateErrorMessage(errorMessage)["error.description"] as String + } else { + val errorMessage = throwable.message ?: "Error" + errorMessages[query] = errorMessage + } + cache[query] = listOf() + cacheState[query] = CacheState.ERROR + sendTopic(FileFetchProvider.CACHE_CHANGES, dataOpsManager.componentManager).onFetchFailure(query, throwable) + } + } + /** * Method for reloading remote files. The files are fetched again, the old cache is cleared, the new cache is loaded. + * All the old files attributes are set to invalid * @param query query with all necessary information to send request. * @param progressIndicator progress indicator to display progress of fetching items in UI. */ @@ -109,54 +160,49 @@ abstract class RemoteFileFetchProviderBase>()?.let { it.fetchNeeded && it.alreadyFetched > 0 } == true - val fetched = fetchResponse(query, progressIndicator) - val files = runWriteActionOnWriteThread { - fetched.mapNotNull { - convertResponseToFile(it) + val files = getFetchedFiles(query, progressIndicator) + + // Cleans up attributes of invalid files + cache[query] + ?.parallelStream() + ?.filter { oldFile -> + // TODO: does not work correctly on datasets (check VOLSER) + oldFile.isValid && files.none { compareOldAndNewFile(oldFile, it) } } - } - - cache[query]?.parallelStream()?.filter { oldFile -> - oldFile.isValid && files.none { compareOldAndNewFile(oldFile, it) } - }?.toList()?.apply { - runWriteActionOnWriteThread { - forEach { cleanupUnusedFile(it, query) } + ?.toList() + ?.apply { + runWriteActionOnWriteThread { + forEach { cleanupUnusedFile(it, query) } + } } - } - if (needToUpdateFiles) { - val newCache = cache[query]?.toMutableList() ?: mutableListOf() - newCache.addAll(files) - cache[query] = newCache - } else { - cache[query] = files - } + + cache[query] = files + cacheState[query] = CacheState.FETCHED + files + } + .onSuccess { publishCacheUpdated(query, it) } + .onFailure { publishFetchCancelledOrFailed(query, it) } + } + + /** + * Load more children elements. Is triggered when "load more" node is navigated + * @param query query with all necessary information to send the request + * @param progressIndicator progress indicator to display progress of fetching items in UI + */ + override fun loadMode( + query: RemoteQuery, + progressIndicator: ProgressIndicator + ) { + runCatching { + val files = getFetchedFiles(query, progressIndicator) + val newCache = cache[query]?.toMutableList() ?: mutableListOf() + newCache.addAll(files) + cache[query] = newCache cacheState[query] = CacheState.FETCHED files - }.onSuccess { - sendTopic(FileFetchProvider.CACHE_CHANGES, dataOpsManager.componentManager).onCacheUpdated(query, it) - }.onFailure { - if (it is ProcessCanceledException) { - cleanCacheInternal(query, false) - sendTopic(FileFetchProvider.CACHE_CHANGES, dataOpsManager.componentManager).onFetchCancelled(query) - } else { - if (it is CallException) { - val details = it.errorParams?.get("details") - var errorMessage = it.message ?: "Error" - if (details is List<*>) { - errorMessage = details[0] as String - } - errorMessages[query] = - service().separateErrorMessage(errorMessage)["error.description"] as String - } else { - val errorMessage = it.message ?: "Error" - errorMessages[query] = errorMessage - } - cache[query] = listOf() - cacheState[query] = CacheState.ERROR - sendTopic(FileFetchProvider.CACHE_CHANGES, dataOpsManager.componentManager).onFetchFailure(query, it) - } } + .onSuccess { publishCacheUpdated(query, it) } + .onFailure { publishFetchCancelledOrFailed(query, it) } } /** diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateDatasetAction.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateDatasetAction.kt index f46ac03b7..78782b064 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateDatasetAction.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/AllocateDatasetAction.kt @@ -104,35 +104,38 @@ private fun doAllocateAction(e: AnActionEvent, initialState: DatasetAllocationPa ), it ) - }.onSuccess { - res = true - var p: ExplorerTreeNode<*>? = parentNode - while (p !is DSMaskNode) { - p = p?.parent ?: break - } - p?.cleanCacheIfPossible() - runInEdt { - if (showOkNoDialog( - title = "Dataset ${state.datasetName} Has Been Created", - message = "Would you like to add mask \"${state.datasetName}\" to ${parentNode.unit.name}", - project = e.project, - okText = "Yes", - noText = "No" - ) - ) { - val filesWorkingSetConfig = - configCrudable.getByUniqueKey(workingSet.uuid)?.clone() - if (filesWorkingSetConfig != null) { - filesWorkingSetConfig.dsMasks.add(DSMask().apply { mask = state.datasetName }) - configCrudable.update(filesWorkingSetConfig) + } + .onSuccess { + res = true + var p: ExplorerTreeNode<*>? = parentNode + while (p !is DSMaskNode) { + p = p?.parent ?: break + } + p?.cleanCacheIfPossible(cleanBatchedQuery = true) + runInEdt { + if ( + showOkNoDialog( + title = "Dataset ${state.datasetName} Has Been Created", + message = "Would you like to add mask \"${state.datasetName}\" to ${parentNode.unit.name}", + project = e.project, + okText = "Yes", + noText = "No" + ) + ) { + val filesWorkingSetConfig = + configCrudable.getByUniqueKey(workingSet.uuid)?.clone() + if (filesWorkingSetConfig != null) { + filesWorkingSetConfig.dsMasks.add(DSMask().apply { mask = state.datasetName }) + configCrudable.update(filesWorkingSetConfig) + } } } + initialState.errorMessage = "" + } + .onFailure { t -> + parentNode.explorer.reportThrowable(t, e.project) + initialState.errorMessage = t.message ?: t.toString() } - initialState.errorMessage = "" - }.onFailure { t -> - parentNode.explorer.reportThrowable(t, e.project) - initialState.errorMessage = t.message ?: t.toString() - } } res } @@ -234,7 +237,7 @@ class AllocateLikeAction : AnAction() { } if (spaceUnits == SpaceUnits.BLOCKS) { Messages.showWarningDialog( - "Allocation unit BLK is not supported. It will be changed to TRK.", + "Allocation unit BLK is not supported. It will be changed to TRK.", "Allocation Unit Will Be Changed" ) } @@ -251,8 +254,8 @@ class AllocateLikeAction : AnAction() { } val selected = view.mySelectedNodesData e.presentation.isEnabledAndVisible = selected.size == 1 - && selected[0].attributes is RemoteDatasetAttributes - && !(selected[0].attributes as RemoteDatasetAttributes).isMigrated + && selected[0].attributes is RemoteDatasetAttributes + && !(selected[0].attributes as RemoteDatasetAttributes).isMigrated e.presentation.icon = IconUtil.addText(AllIcons.FileTypes.Any_type, "DS") } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/ForceRenameAction.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/ForceRenameAction.kt index 82534d204..b904ca054 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/ForceRenameAction.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/ForceRenameAction.kt @@ -42,7 +42,11 @@ class ForceRenameAction : AnAction() { if (selectedNode.node is UssDirNode || selectedNode.node is UssFileNode) { val attributes = selectedNode.attributes as RemoteUssAttributes val file = selectedNode.file as MFVirtualFile - val type = if (attributes.isDirectory) { "Directory" } else { "File" } + val type = if (attributes.isDirectory) { + "Directory" + } else { + "File" + } val renameDialog = RenameDialog(e.project, type, selectedNode, this, attributes.name) if (renameDialog.showAndGet()) { val confirmDialog = showConfirmDialogIfNecessary(renameDialog.state, selectedNode) @@ -73,8 +77,8 @@ class ForceRenameAction : AnAction() { if (it is UssFileNode && it.value.filenameInternal == text) { val confirmTemplate = "You are going to rename file $virtualFilePath \n" + - "into existing one. This operation cannot be undone. \n" + - "Would you like to proceed?" + "into existing one. This operation cannot be undone. \n" + + "Would you like to proceed?" return Messages.showOkCancelDialog( confirmTemplate, "Warning", @@ -85,6 +89,7 @@ class ForceRenameAction : AnAction() { } } } + is UssDirNode -> { childrenNodesFromParent?.forEach { if (it is UssDirNode && text == it.value.path.split("/").last()) { @@ -151,11 +156,13 @@ class ForceRenameAction : AnAction() { progressIndicator = it ) } - }.onSuccess { - node.parent?.cleanCacheIfPossible() - }.onFailure { - node.explorer.reportThrowable(it, project) } + .onSuccess { + node.parent?.cleanCacheIfPossible(cleanBatchedQuery = true) + } + .onFailure { + node.explorer.reportThrowable(it, project) + } } } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/GetFilePropertiesAction.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/GetFilePropertiesAction.kt index b1e817013..43e648c6b 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/GetFilePropertiesAction.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/GetFilePropertiesAction.kt @@ -38,10 +38,12 @@ class GetFilePropertiesAction : AnAction() { val dialog = DatasetPropertiesDialog(e.project, DatasetState(attributes)) dialog.showAndGet() } + is RemoteUssAttributes -> { val dialog = UssFilePropertiesDialog(e.project, UssFileState(attributes)) dialog.showAndGet() } + is RemoteMemberAttributes -> { val dialog = MemberPropertiesDialog(e.project, MemberState(attributes)) dialog.showAndGet() @@ -66,9 +68,6 @@ class GetFilePropertiesAction : AnAction() { val selected = view.mySelectedNodesData val node = selected.getOrNull(0)?.node e.presentation.isVisible = selected.size == 1 - && (node is UssFileNode - || node is FileLikeDatasetNode - || node is LibraryNode - || node is UssDirNode) + && (node is UssFileNode || node is FileLikeDatasetNode || node is LibraryNode || node is UssDirNode) } } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/MigrationActions.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/MigrationActions.kt index 37e6f3a5a..d6172392e 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/MigrationActions.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/MigrationActions.kt @@ -55,7 +55,7 @@ fun getRequestDataForNode(node: ExplorerTreeNode<*>): Pair>) { val uniqueParentNodes = nodes.map { it.parent }.distinct() - uniqueParentNodes.forEach { it?.cleanCacheIfPossible() } + uniqueParentNodes.forEach { it?.cleanCacheIfPossible(cleanBatchedQuery = true) } } /** diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/RefreshNodeAction.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/RefreshNodeAction.kt index 2ee02fc64..536029411 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/RefreshNodeAction.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/RefreshNodeAction.kt @@ -37,6 +37,7 @@ class RefreshNodeAction : AnAction() { val query = node.query ?: return@forEach view.getNodesByQueryAndInvalidate(query) } + is WorkingSetNode<*> -> { node.cachedChildren.filterIsInstance() .forEach { diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/RenameAction.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/RenameAction.kt index e924e197f..86e127c6c 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/RenameAction.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/actions/RenameAction.kt @@ -135,7 +135,7 @@ class RenameAction : AnAction() { progressIndicator = it ) }.onSuccess { - node.parent?.cleanCacheIfPossible() + node.parent?.cleanCacheIfPossible(cleanBatchedQuery = true) }.onFailure { node.explorer.reportThrowable(it, project) } 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 f2d247d8b..dd48ecc44 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerPasteProvider.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerPasteProvider.kt @@ -17,6 +17,7 @@ import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.actionSystem.DataKey import com.intellij.openapi.components.service import com.intellij.openapi.progress.runModalTask +import com.intellij.openapi.project.Project import com.intellij.openapi.ui.Messages import com.intellij.openapi.ui.showYesNoDialog import com.intellij.openapi.vfs.VirtualFile @@ -24,12 +25,14 @@ import com.intellij.openapi.vfs.newvfs.impl.VirtualFileImpl import eu.ibagroup.formainframe.analytics.AnalyticsService import eu.ibagroup.formainframe.analytics.events.FileAction 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.operations.mover.MoveCopyOperation import eu.ibagroup.formainframe.explorer.FileExplorerContentProvider +import eu.ibagroup.formainframe.utils.castOrNull import eu.ibagroup.formainframe.utils.getMinimalCommonParents import eu.ibagroup.formainframe.vfs.MFVirtualFile import kotlin.concurrent.withLock @@ -50,6 +53,128 @@ class ExplorerPasteProvider : PasteProvider { it.attributes?.isPastePossible ?: true } + /** + * Get nodes to refresh. Normally it would be some parent nodes that are changed during the copy/move operation. + * E.g. for copy operation it will be parent node of the destination. For move operation it will be both source and destination nodes + * @param operations operations list to find nodes by + * @param explorerView the explorer view to get filesystem tree structure and the operation type + * @return nodes to refresh + */ + private fun getNodesToRefresh( + operations: List, + explorerView: FileExplorerView + ): List> { + fun List.collectByFile( + fileChooser: (MoveCopyOperation) -> VirtualFile + ): List { + return map(fileChooser) + .distinct() + .getMinimalCommonParents() + .toList() + } + + val destinationFilesToRefresh = operations.collectByFile { it.destination } + val sourceFilesToRefresh = if (explorerView.isCut.get()) { + operations.collectByFile { it.source } + } else { + emptyList() + } + val destinationNodes = destinationFilesToRefresh + .map { file -> explorerView.myFsTreeStructure.findByVirtualFile(file) } + .flatten() + .distinct() + return if (explorerView.isCut.get()) { + val sourceNodesToRefresh = sourceFilesToRefresh + .map { file -> explorerView.myFsTreeStructure.findByVirtualFile(file).map { it.parent } } + .flatten() + .filterNotNull() + .distinct() + destinationNodes.plus(sourceNodesToRefresh) + } else { + destinationNodes + } + } + + /** + * Refresh provided nodes + * @param nodesToRefresh the nodes to refresh + * @param explorerView the explorer view to clean "invalidate on expand" for nodes + */ + private fun refreshNodes(nodesToRefresh: List>, explorerView: FileExplorerView) { + nodesToRefresh + .forEach { node -> + val parentNode = node.castOrNull>() ?: return + parentNode.query ?: return + parentNode.cleanCache(recursively = false, cleanBatchedQuery = true) + cleanInvalidateOnExpand(parentNode, explorerView) + } + } + + /** + * Run move or copy task. Cleans cache on subtrees after the operation is completed + * @param titlePrefix the operation title + * @param filesToMoveTotal count of files to perform operation on + * @param isDragAndDrop value to indicate whether the operation is DnD + * @param operations the operation instances that carry all the necessary information about the operations + * @param copyPasteSupport the copy-paste support to control the custom clipboard + * @param explorerView explorer view instance to control operation + * @param project the project to run task in + */ + private fun runMoveOrCopyTask( + titlePrefix: String, + filesToMoveTotal: Int, + isDragAndDrop: Boolean, + operations: List, + copyPasteSupport: FileExplorerView.ExplorerCopyPasteSupport, + explorerView: FileExplorerView, + project: Project + ) { + runModalTask( + title = "$titlePrefix $filesToMoveTotal file(s)", + project = project, + cancellable = true + ) { + it.isIndeterminate = false + operations.forEach { op -> + op.sourceAttributes?.let { attr -> + service() + .trackAnalyticsEvent( + FileEvent( + attr, + if (op.isMove) FileAction.MOVE else FileAction.COPY + ) + ) + } + it.text = "${op.source.name} to ${op.destination.name}" + runCatching { + dataOpsManager.performOperation( + operation = op, + progressIndicator = it + ) + } + .onSuccess { + if (explorerView.isCut.get()) { + copyPasteSupport.removeFromBuffer { node -> node.file == op.source } + } + } + .onFailure { throwable -> + explorerView.explorer.reportThrowable(throwable, project) + if (isDragAndDrop) { + copyPasteSupport.removeFromBuffer { node -> + node.file == op.source && + operations.minus(op).none { operation -> operation.source == op.source } + } + } + } + it.fraction = it.fraction + 1.0 / filesToMoveTotal + } + + val nodesToRefresh = getNodesToRefresh(operations, explorerView) + + refreshNodes(nodesToRefresh, explorerView) + } + } + /** * Performs paste from ForMainframe buffer and from clipboard buffer of local file system. * ForMainframe buffer is just a list of NodeData to copy or cut. @@ -147,7 +272,8 @@ class ExplorerPasteProvider : PasteProvider { } } ?: return@conflicts null) } - }.flatten() + } + .flatten() if (conflicts.isNotEmpty()) { val choice = Messages.showDialog( @@ -171,19 +297,23 @@ class ExplorerPasteProvider : PasteProvider { } } - val ussToPdsWarnings = pasteDestinations.mapNotNull { destFile -> - val destAttributes = dataOpsManager.tryToGetAttributes(destFile) - if (destAttributes !is RemoteDatasetAttributes) null - else { - val sourceUssAttributes = sourceFiles.filter { sourceFile -> - val sourceAttributes = dataOpsManager.tryToGetAttributes(sourceFile) - sourceAttributes is RemoteUssAttributes || sourceFile is VirtualFileImpl + val ussToPdsWarnings = pasteDestinations + .mapNotNull { destFile -> + val destAttributes = dataOpsManager.tryToGetAttributes(destFile) + if (destAttributes !is RemoteDatasetAttributes) null + else { + val sourceUssAttributes = sourceFiles + .filter { sourceFile -> + val sourceAttributes = dataOpsManager.tryToGetAttributes(sourceFile) + sourceAttributes is RemoteUssAttributes || sourceFile is VirtualFileImpl + } + sourceUssAttributes.map { Pair(destFile, it) }.ifEmpty { null } } - sourceUssAttributes.map { Pair(destFile, it) }.ifEmpty { null } } - }.flatten() + .flatten() - if (ussToPdsWarnings.isNotEmpty() && + if ( + ussToPdsWarnings.isNotEmpty() && !showYesNoDialog( "USS File To PDS Placing", "You are about to place USS file to PDS. All lines exceeding the record length will be truncated.", @@ -196,25 +326,27 @@ class ExplorerPasteProvider : PasteProvider { skipDestinationSourceList.addAll(ussToPdsWarnings) } - val operations = pasteDestinations.map { destFile -> - sourceFiles.mapNotNull { sourceFile -> - if (skipDestinationSourceList.contains(Pair(destFile, sourceFile))) { - if (isDragAndDrop) { - copyPasteSupport.removeFromBuffer { it.file == sourceFile } + val operations = pasteDestinations + .map { destFile -> + sourceFiles.mapNotNull { sourceFile -> + if (skipDestinationSourceList.contains(Pair(destFile, sourceFile))) { + if (isDragAndDrop) { + copyPasteSupport.removeFromBuffer { it.file == sourceFile } + } + return@mapNotNull null } - return@mapNotNull null + MoveCopyOperation( + source = sourceFile, + destination = destFile, + isMove = explorerView.isCut.get(), + forceOverwriting = overwriteDestinationSourceList.contains(Pair(destFile, sourceFile)), + newName = null, + dataOpsManager, + explorerView.explorer + ) } - MoveCopyOperation( - source = sourceFile, - destination = destFile, - isMove = explorerView.isCut.get(), - forceOverwriting = overwriteDestinationSourceList.contains(Pair(destFile, sourceFile)), - newName = null, - dataOpsManager, - explorerView.explorer - ) } - }.flatten() + .flatten() val filesToMoveTotal = operations.size val hasLocalFilesInOperationsSources = operations.any { it.source !is MFVirtualFile } @@ -230,87 +362,16 @@ class ExplorerPasteProvider : PasteProvider { } else { "Copying" } - runModalTask( - title = "$titlePrefix $filesToMoveTotal file(s)", - project = project, - cancellable = true - ) { - it.isIndeterminate = false - operations.forEach { op -> - op.sourceAttributes?.let { attr -> - service().trackAnalyticsEvent( - FileEvent( - attr, - if (op.isMove) FileAction.MOVE else FileAction.COPY - ) - ) - } - it.text = "${op.source.name} to ${op.destination.name}" - runCatching { - dataOpsManager.performOperation( - operation = op, - progressIndicator = it - ) - }.onSuccess { - if (explorerView.isCut.get()) { - copyPasteSupport.removeFromBuffer { it.file == op.source } - } - }.onFailure { - explorerView.explorer.reportThrowable(it, project) - if (isDragAndDrop) { - copyPasteSupport.removeFromBuffer { - it.file == op.source && operations.minus(op) - .none { operation -> operation.source == op.source } - } - } - } - it.fraction = it.fraction + 1.0 / filesToMoveTotal - } - fun List.collectByFile( - fileChooser: (MoveCopyOperation) -> VirtualFile - ): List { - return map(fileChooser) - .distinct() - .getMinimalCommonParents() - .toList() - } - val destinationFilesToRefresh = operations.collectByFile { it.destination } - val sourceFilesToRefresh = if (explorerView.isCut.get()) { - operations.collectByFile { it.source } - } else { - emptyList() - } - val destinationNodes = destinationFilesToRefresh - .map { explorerView.myFsTreeStructure.findByVirtualFile(it) } - .flatten() - .distinct() - val nodesToRefresh = if (explorerView.isCut.get()) { - val sourceNodesToRefresh = sourceFilesToRefresh - .map { file -> explorerView.myFsTreeStructure.findByVirtualFile(file).map { it.parent } } - .flatten() - .filterNotNull() - .distinct() - destinationNodes.plus(sourceNodesToRefresh) - } else { - destinationNodes - } - - nodesToRefresh.forEach { node -> - // node.cleanCacheIfPossible() - explorerView.myFsTreeStructure.findByPredicate { foundNode -> - if (foundNode is FetchNode && node is FetchNode) { - foundNode.query == node.query - } else false - }.onEach { foundNode -> - foundNode.cleanCacheIfPossible() - }.onEach { foundNode -> - explorerView.myStructure.invalidate(foundNode, true) - } - - } - - } + runMoveOrCopyTask( + titlePrefix, + filesToMoveTotal, + isDragAndDrop, + operations, + copyPasteSupport, + explorerView, + project + ) } } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerTreeNode.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerTreeNode.kt index 5312bb2d2..9cdcaf200 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerTreeNode.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerTreeNode.kt @@ -150,5 +150,4 @@ abstract class ExplorerTreeNode( val path: TreePath get() = TreePath(pathList.toTypedArray()) - } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerTreeView.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerTreeView.kt index ab023a032..e32e5920f 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerTreeView.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/ExplorerTreeView.kt @@ -39,6 +39,7 @@ import eu.ibagroup.formainframe.utils.getAncestorNodes import eu.ibagroup.formainframe.utils.rwLocked import eu.ibagroup.formainframe.utils.service import eu.ibagroup.formainframe.utils.subscribe +import org.jetbrains.concurrency.AsyncPromise import java.awt.Component import java.util.concurrent.atomic.AtomicBoolean import javax.swing.event.TreeExpansionEvent @@ -85,39 +86,48 @@ abstract class ExplorerTreeView, UnitConfig : EntityWithUuid> * @return the nodes found by the query */ internal fun getNodesByQueryAndInvalidate( - query: Query<*, *>, collapse: Boolean = false, invalidate: Boolean = true + query: Query<*, *>, + collapse: Boolean = false, + invalidate: Boolean = true ): Collection> { - return myFsTreeStructure.findByPredicate { - if (it is FetchNode) { - it.query == query - } else false - }.onEach { foundNode -> - fun invalidate() = myStructure.invalidate(foundNode, true) + return myFsTreeStructure + .findByPredicate { + if (it is FetchNode) { + it.query == query + } else false + } + .onEach { foundNode -> - fun collapseIfNeeded(tp: TreePath) { - if (collapse) { - treeModel.onValidThread { - myTree.collapsePath(tp) - synchronized(myNodesToInvalidateOnExpand) { - val node = tp.lastPathComponent - myNodesToInvalidateOnExpand.add(node) + fun collapseIfNeeded(tp: TreePath) { + if (collapse) { + treeModel.onValidThread { + myTree.collapsePath(tp) + synchronized(myNodesToInvalidateOnExpand) { + val node = tp.lastPathComponent + myNodesToInvalidateOnExpand.add(node) + } } } } - } - myStructure.promisePath(foundNode, myTree).onSuccess { nodePath -> - if (myTree.isVisible(nodePath) && myTree.isCollapsed(nodePath) && mySelectedNodesData.any { it.node == nodePath }) { - myTree.expandPath(nodePath) - } - if (invalidate) { - invalidate().onSuccess { tp -> - collapseIfNeeded(tp) + + myStructure + .promisePath(foundNode, myTree) + .thenAsync { nodePath -> + if (myTree.isVisible(nodePath) && myTree.isCollapsed(nodePath) && mySelectedNodesData.any { it.node == nodePath }) { + myTree.expandPath(nodePath) + } + if (invalidate) { + myStructure + .invalidate(foundNode, true) + .onSuccess { tp -> + collapseIfNeeded(tp) + } + } else { + collapseIfNeeded(nodePath) + AsyncPromise() + } } - } else { - collapseIfNeeded(nodePath) - } } - } } /** @@ -181,26 +191,31 @@ abstract class ExplorerTreeView, UnitConfig : EntityWithUuid> topic = VirtualFileManager.VFS_CHANGES, handler = object : BulkFileListener { override fun after(events: MutableList) { - events.mapNotNull { - val nodes = myFsTreeStructure.findByVirtualFile(it.file ?: return@mapNotNull null) - when { - it is VFileContentChangeEvent || it is VFilePropertyChangeEvent -> { - nodes - } - it is VFileDeleteEvent - && this@ExplorerTreeView - .ignoreVFileDeleteEvents - .compareAndSet(true, true) -> { - null - } - else -> { - nodes.mapNotNull { n -> n.parent } + events + .mapNotNull { + val nodes = myFsTreeStructure.findByVirtualFile(it.file ?: return@mapNotNull null) + when { + it is VFileContentChangeEvent || it is VFilePropertyChangeEvent -> { + nodes + } + + it is VFileDeleteEvent && + this@ExplorerTreeView + .ignoreVFileDeleteEvents + .compareAndSet(true, true) -> { + null + } + + else -> { + nodes.mapNotNull { n -> n.parent } + } } } - }.flatten().forEach { fileNode -> - fileNode.cleanCacheIfPossible() - myStructure.invalidate(fileNode, true) - } + .flatten() + .forEach { fileNode -> + fileNode.cleanCacheIfPossible(cleanBatchedQuery = false) + myStructure.invalidate(fileNode, true) + } } }, disposable = this @@ -321,4 +336,4 @@ abstract class ExplorerTreeView, UnitConfig : EntityWithUuid> } -} \ No newline at end of file +} diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/FileFetchNode.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/FileFetchNode.kt index a6862af2c..4d2323001 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/FileFetchNode.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/FileFetchNode.kt @@ -42,17 +42,37 @@ abstract class FileFetchNode, File : Vi private val lock = ReentrantLock() private val condition = lock.newCondition() - /** current cache will not be cleaned if this value is true. It was made for fetching files bi batches. */ - var needToLoadMore = false + + private val hasError = AtomicBoolean(false) + + private val connectionError = "Error: Check connection" + + @Volatile + private var needsToShowPlus = true + + private var cachedChildren: List>? by locked(null, lock) private val fileFetchProvider get() = explorer.componentManager.service() .getFileFetchProvider(requestClass, queryClass, vFileClass) - protected abstract fun makeFetchTaskTitle(query: Q): String - private val loadingNode by lazy { LoadingNode(notNullProject, this, explorer, treeStructure) } + protected abstract val requestClass: Class + + protected abstract val queryClass: Class> + + protected abstract val vFileClass: Class + + abstract val query: Q? + + /** Indicates whether the next children fetch should load more child elements to the children list */ + var needToLoadMore = false + + protected abstract fun Collection.toChildrenNodes(): List> + + protected abstract fun makeFetchTaskTitle(query: Q): String + /** * Method to build a list of error nodes with specified error text */ @@ -60,13 +80,10 @@ abstract class FileFetchNode, File : Vi return listOf(ErrorNode(notNullProject, this, explorer, treeStructure, text = text)) } - private val hasError = AtomicBoolean(false) - - private val connectionError = "Error: Check connection" - /** * Method which is called when tree node is expanded or refresh is pressed on tree node. - * It fetches the children nodes if no cached nodes are present / displays "loading..." during fetch / displays any Error if an error happened during fetch + * It fetches the children nodes if no cached nodes are present / displays "loading..." during fetch / displays any Error if an error happened during fetch. + * Normally, this function is called after nodes invalidation * @return collection of children tree nodes */ override fun getChildren(): MutableCollection> { @@ -77,21 +94,31 @@ abstract class FileFetchNode, File : Vi if (q != null && fileFetchProvider.isCacheValid(q)) { val fetched = fileFetchProvider.getCached(q)?.toMutableList() if (fetched != null && !needToLoadMore) { - fetched.toChildrenNodes().toMutableList().apply { - val batchedQ = q.castOrNull>() - if (batchedQ?.fetchNeeded == true) { - val itemsLeft = batchedQ.totalRows?.let { it - batchedQ.alreadyFetched } - add(LoadMoreNode(notNullProject, this@FileFetchNode, explorer, treeStructure, itemsLeft)) + fetched + .toChildrenNodes() + .toMutableList() + .apply { + val batchedQ = q.castOrNull>() + if (batchedQ?.fetchNeeded == true) { + val itemsLeft = batchedQ.totalRows?.let { it - batchedQ.alreadyFetched } + add(LoadMoreNode(notNullProject, this@FileFetchNode, explorer, treeStructure, itemsLeft)) + } + } + .also { + cachedChildren = it } - }.also { cachedChildren = it } } else { - needToLoadMore = false runBackgroundableTask( title = makeFetchTaskTitle(q), project = project, cancellable = true ) { - fileFetchProvider.reload(q, it) + if (needToLoadMore) { + fileFetchProvider.loadMode(q, it) + } else { + fileFetchProvider.reload(q, it) + } + needToLoadMore = false } (fetched?.toChildrenNodes()?.toMutableList() ?: mutableListOf()).apply { add(loadingNode) } } @@ -125,19 +152,16 @@ abstract class FileFetchNode, File : Vi } } - abstract val query: Q? - - @Volatile - private var needsToShowPlus = true - - private var cachedChildren: List>? by locked(null, lock) - /** * Method which is called to clean a cache during refresh or reload of the tree node * @param recursively - determines if all children nodes should clean the cache * @return Void */ - fun cleanCache(recursively: Boolean = true, cleanFetchProviderCache: Boolean = true, cleanBatchedQuery: Boolean = false) { + fun cleanCache( + recursively: Boolean = true, + cleanFetchProviderCache: Boolean = true, + cleanBatchedQuery: Boolean = false + ) { val children = cachedChildren if (!hasError.compareAndSet(true, false)) { cachedChildren = null @@ -159,22 +183,15 @@ abstract class FileFetchNode, File : Vi } } - protected abstract fun Collection.toChildrenNodes(): List> - - protected abstract val requestClass: Class - - protected abstract val queryClass: Class> - - protected abstract val vFileClass: Class - } +// TODO: rework or remove as it is not customisable at the moment (recursively and cleanFetchProviderCache is omitted) /** * Method to call cleanCache() of desired instance of the tree node. Tree node should be an instance of FileFetchNode - * @return Void + * @param cleanBatchedQuery value to indicate whether it is needed to clean batched query */ -fun ExplorerTreeNode<*>.cleanCacheIfPossible() { +fun ExplorerTreeNode<*>.cleanCacheIfPossible(cleanBatchedQuery: Boolean) { if (this is FileFetchNode<*, *, *, *, *>) { - cleanCache(cleanBatchedQuery = true) + cleanCache(cleanBatchedQuery = cleanBatchedQuery) } } diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/FileLikeDatasetNode.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/FileLikeDatasetNode.kt index b538ab16f..4f1301d64 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/FileLikeDatasetNode.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/FileLikeDatasetNode.kt @@ -55,9 +55,11 @@ class FileLikeDatasetNode( ) ) } + is RemoteMemberAttributes -> { presentation.setIcon(ForMainframeIcons.MemberIcon) } + else -> { presentation.setIcon(AllIcons.FileTypes.Any_type) } @@ -77,4 +79,4 @@ class FileLikeDatasetNode( override fun getVirtualFile(): MFVirtualFile { return value } -} \ No newline at end of file +} diff --git a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/LoadMoreNode.kt b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/LoadMoreNode.kt index 2bade2487..a4a1e2111 100644 --- a/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/LoadMoreNode.kt +++ b/src/main/kotlin/eu/ibagroup/formainframe/explorer/ui/LoadMoreNode.kt @@ -38,12 +38,23 @@ class LoadMoreNode( val parentNode = parent?.castOrNull>() ?: return val query = parentNode.query ?: return parentNode.needToLoadMore = true + parentNode.cleanCache(recursively = false, cleanFetchProviderCache = false) cleanInvalidateOnExpand(parentNode, view) - view.getNodesByQueryAndInvalidate(query) + view.myFsTreeStructure + .findByPredicate { + if (it is FetchNode) { + it.query == query + } else false + } + .onEach { foundNode -> + synchronized(view.myStructure) { + view.myStructure.invalidate(foundNode, true) + } + } } override fun canNavigate(): Boolean { return FileExplorerContentProvider.getInstance().getExplorerView(project) != null } -} \ No newline at end of file +}