Skip to content

Commit

Permalink
Merge branch 'feature/IJMP-2064' into 'release/v2.1.0'
Browse files Browse the repository at this point in the history
IJMP-2064: implement file cache clearing

See merge request ijmp/for-mainframe!623
  • Loading branch information
Dzianis Lisiankou committed Dec 10, 2024
2 parents a56cb71 + dc11fd6 commit 8b71d7f
Show file tree
Hide file tree
Showing 12 changed files with 275 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,17 @@ package eu.ibagroup.formainframe.config.settings.ui
import com.intellij.ide.BrowserUtil
import com.intellij.openapi.observable.util.whenTextChanged
import com.intellij.openapi.options.BoundSearchableConfigurable
import com.intellij.openapi.progress.runModalTask
import com.intellij.openapi.ui.DialogPanel
import com.intellij.openapi.ui.Messages
import com.intellij.ui.dsl.builder.bindIntText
import com.intellij.ui.dsl.builder.bindSelected
import com.intellij.ui.dsl.builder.panel
import eu.ibagroup.formainframe.analytics.AnalyticsService
import eu.ibagroup.formainframe.analytics.PolicyProvider
import eu.ibagroup.formainframe.analytics.ui.AnalyticsPolicyDialog
import eu.ibagroup.formainframe.config.ConfigService
import eu.ibagroup.formainframe.dataops.DataOpsManager
import eu.ibagroup.formainframe.rateus.RateUsNotification
import eu.ibagroup.formainframe.utils.validateBatchSize
import java.util.concurrent.atomic.AtomicBoolean
Expand Down Expand Up @@ -87,6 +90,22 @@ class SettingsConfigurable : BoundSearchableConfigurable("Settings", "mainframe"
res.component.addItemListener { isAutoSyncEnabled.set(res.component.isSelected) }
}
}
row {
button("Clear File Cache") {
var cacheCleared = false
runModalTask("Cache Clearing", cancellable = false) {
cacheCleared = DataOpsManager.getService().clearFileCache()
}
if (cacheCleared) {
Messages.showInfoMessage(
"The file cache has been successfully cleared.",
"Cache Cleared",
)
}
}.applyToComponent {
toolTipText = "Clear the local contents of files downloaded from the remote system. All related files opened in the editor will be closed"
}
}
}
group("Rate Us") {
row {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ interface DataOpsManager : Disposable {
*/
fun getContentSynchronizer(file: VirtualFile): ContentSynchronizer?

fun clearFileCache(): Boolean

fun getMFContentAdapter(file: VirtualFile): MFContentAdapter

fun getNameResolver(source: VirtualFile, destination: VirtualFile): CopyPasteNameResolver
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,17 @@ package eu.ibagroup.formainframe.dataops
import com.intellij.execution.ui.ConsoleView
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.ComponentManager
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vfs.VirtualFile
import eu.ibagroup.formainframe.dataops.attributes.AttributesService
import eu.ibagroup.formainframe.dataops.attributes.FileAttributes
import eu.ibagroup.formainframe.dataops.content.adapters.DefaultContentAdapter
import eu.ibagroup.formainframe.dataops.content.adapters.MFContentAdapter
import eu.ibagroup.formainframe.dataops.content.synchronizer.ContentSynchronizer
import eu.ibagroup.formainframe.dataops.content.synchronizer.checkForSync
import eu.ibagroup.formainframe.dataops.fetch.FileFetchProvider
import eu.ibagroup.formainframe.dataops.log.AbstractMFLoggerBase
import eu.ibagroup.formainframe.dataops.log.LogFetcher
Expand All @@ -36,6 +39,8 @@ import eu.ibagroup.formainframe.dataops.operations.mover.names.DefaultNameResolv
import eu.ibagroup.formainframe.utils.associateListedBy
import eu.ibagroup.formainframe.utils.findAnyNullable
import eu.ibagroup.formainframe.utils.log
import eu.ibagroup.formainframe.utils.runInEdtAndWait
import eu.ibagroup.formainframe.vfs.MFVirtualFile

/**
* Data operation manager implementation class.
Expand Down Expand Up @@ -170,6 +175,32 @@ class DataOpsManagerImpl : DataOpsManager {
return contentSynchronizers.firstOrNull { it.accepts(file) }
}

/**
* Closes all [MFVirtualFile] files opened in the editor and clears the cache of all registered content synchronizers.
* @return true if the file cache is cleared and false otherwise.
*/
override fun clearFileCache(): Boolean {
ProjectManager.getInstance().openProjects.forEach { project ->
val fileEditorManager = FileEditorManager.getInstance(project)
runInEdtAndWait {
fileEditorManager.openFiles.forEach {
if (it is MFVirtualFile) {
fileEditorManager.closeFile(it)
}
}
}
}
var syncInProgress = false
runInEdtAndWait {
syncInProgress = checkForSync()
}
if (!syncInProgress) {
contentSynchronizers.forEach { it.clearFileCache() }
return true
}
return false
}

/**
* Returns instance of content adapter to mainframe
* @param file object that represents file/folder on mainframe
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,10 @@ interface SyncProcessService {
*/
fun areDependentFilesSyncingNow(file: VirtualFile): Boolean

/**
* Check if any file is currently synchronized.
* @return true if any file is syncing now or false otherwise.
*/
fun isAnyFileSyncingNow(): Boolean

}
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,11 @@ class SyncProcessServiceImpl: SyncProcessService {
}
}

/**
* Base implementation of [SyncProcessService.isAnyFileSyncingNow] method.
*/
override fun isAnyFileSyncingNow(): Boolean {
return fileToProgressIndicatorMap.values.any { it.isRunning }
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,9 @@ interface ContentSynchronizer {
*/
fun markAsNotNeededForSync(syncProvider: SyncProvider)

/**
* Clears all file records from the content storage and other related values.
*/
fun clearFileCache()

}
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ abstract class RemoteAttributedContentSynchronizer<FAttributes : FileAttributes>
val attributesService by lazy { dataOpsManager.getAttributesService(attributesClass, vFileClass) }
private val successfulStatesStorage by lazy { ContentStorage(SUCCESSFUL_CONTENT_STORAGE_NAME_PREFIX + entityName) }
private val handlerToStorageIdMap = ConcurrentHashMap<SyncProvider, Int>()
private val idToBinaryFileMap = ConcurrentHashMap<Int, VirtualFile>()
private val fetchedAtLeastOnce = ConcurrentHashMap.newKeySet<SyncProvider>()
private val needToUpload = ConcurrentHashMap.newKeySet<SyncProvider>()

Expand Down Expand Up @@ -228,4 +227,16 @@ abstract class RemoteAttributedContentSynchronizer<FAttributes : FileAttributes>
override fun markAsNotNeededForSync(syncProvider: SyncProvider) {
needToUpload.remove(syncProvider)
}

/**
* Base implementation of [ContentSynchronizer.clearFileCache] method for each content synchronizer.
*/
override fun clearFileCache() {
handlerToStorageIdMap.values.forEach {
successfulStatesStorage.deleteRecord(it)
}
handlerToStorageIdMap.clear()
fetchedAtLeastOnce.clear()
needToUpload.clear()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,17 @@ fun checkFileForSync(
true
} else false
}

/**
* Check if any file is syncing now and show the warning dialog if so.
* @param project project to show dialog.
* @return true if any file is syncing now and false otherwise.
*/
fun checkForSync(project: Project? = null): Boolean {
val message = "You can't perform this action because some file is currently being synchronized"
val title = "Synchronization Is In Progress"
return if (SyncProcessService.getService().isAnyFileSyncingNow()) {
Messages.showWarningDialog(project, message, title)
true
} else false
}
94 changes: 70 additions & 24 deletions src/test/kotlin/eu/ibagroup/formainframe/dataops/ContentTestSpec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import com.intellij.openapi.ui.Messages
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import eu.ibagroup.formainframe.dataops.content.service.SyncProcessService
import eu.ibagroup.formainframe.dataops.content.service.SyncProcessServiceImpl
import eu.ibagroup.formainframe.dataops.content.synchronizer.checkFileForSync
import eu.ibagroup.formainframe.dataops.content.synchronizer.checkForSync
import eu.ibagroup.formainframe.testutils.WithApplicationShouldSpec
import eu.ibagroup.formainframe.testutils.testServiceImpl.TestSyncProcessServiceImpl
import io.kotest.assertions.assertSoftly
Expand Down Expand Up @@ -115,6 +117,28 @@ class ContentTestSpec : WithApplicationShouldSpec({
wasWarningShown shouldBe false
}
}
// syncUtils.checkForSync
should("check that any file is syncing") {
syncProcessService.testInstance = object : TestSyncProcessServiceImpl() {
override fun isAnyFileSyncingNow(): Boolean {
return true
}
}

val result = checkForSync(mockk())
assertSoftly {
result shouldBe true
wasWarningShown shouldBe true
}
}
should("check that no file is syncing") {
val result = checkForSync()

assertSoftly {
result shouldBe false
wasWarningShown shouldBe false
}
}
// SyncAction.actionPerformed
should("synchronize the file with the remote file") {}
// MemberContentSynchronizer.fetchRemoteContentBytes
Expand All @@ -133,56 +157,59 @@ class ContentTestSpec : WithApplicationShouldSpec({
should("adapt content for the dataset from mainframe with variable print length") {}
}
context("dataops module: content/service") {
val syncProcessService = SyncProcessServiceImpl()

val virtualFileMock = mockk<VirtualFile>()
val progressIndicatorMock = mockk<ProgressIndicator>()

beforeEach {
syncProcessService.startFileSync(virtualFileMock, progressIndicatorMock)

val isAncestorRef: (VirtualFile, VirtualFile, Boolean) -> Boolean = VfsUtilCore::isAncestor
mockkStatic(isAncestorRef as KFunction<*>)
}

afterEach {
syncProcessService.stopFileSync(virtualFileMock)

unmockkAll()
}

// SyncProcessServiceImpl.isFileSyncingNow
// TODO: rewrite to the service usage
// should("file is syncing now") {
// every { progressIndicatorMock.isRunning } returns true
//
// val result = SyncProcessService.getService().isFileSyncingNow(virtualFileMock)
//
// assertSoftly {
// result shouldBe true
// }
// }
should("file is syncing now") {
every { progressIndicatorMock.isRunning } returns true

val result = syncProcessService.isFileSyncingNow(virtualFileMock)

assertSoftly {
result shouldBe true
}
}
should("file is not syncing now") {
every { progressIndicatorMock.isRunning } returns false

val result = SyncProcessService.getService().isFileSyncingNow(virtualFileMock)
val result = syncProcessService.isFileSyncingNow(virtualFileMock)

assertSoftly {
result shouldBe false
}
}
// SyncProcessServiceImpl.areDependentFilesSyncingNow
// TODO: rewrite to the service usage
// should("dependent files are syncing now") {
// every { progressIndicatorMock.isRunning } returns true
// every { VfsUtilCore.isAncestor(virtualFileMock, any(), true) } returns true
//
// val result = SyncProcessService.getService().areDependentFilesSyncingNow(virtualFileMock)
//
// assertSoftly {
// result shouldBe true
// }
// }
should("dependent files are syncing now") {
every { progressIndicatorMock.isRunning } returns true
every { VfsUtilCore.isAncestor(virtualFileMock, any(), true) } returns true

val result = syncProcessService.areDependentFilesSyncingNow(virtualFileMock)

assertSoftly {
result shouldBe true
}
}
should("dependent files are not syncing now") {
every { progressIndicatorMock.isRunning } returns true
every { VfsUtilCore.isAncestor(virtualFileMock, any(), true) } returns false

val result = SyncProcessService.getService().areDependentFilesSyncingNow(virtualFileMock)
val result = syncProcessService.areDependentFilesSyncingNow(virtualFileMock)

assertSoftly {
result shouldBe false
Expand All @@ -191,7 +218,26 @@ class ContentTestSpec : WithApplicationShouldSpec({
should("dependent files are not syncing now because no sync is running") {
every { progressIndicatorMock.isRunning } returns false

val result = SyncProcessService.getService().areDependentFilesSyncingNow(virtualFileMock)
val result = syncProcessService.areDependentFilesSyncingNow(virtualFileMock)

assertSoftly {
result shouldBe false
}
}
// SyncProcessServiceImpl.isAnyFileSyncingNow
should("any file is syncing now") {
every { progressIndicatorMock.isRunning } returns true

val result = syncProcessService.isAnyFileSyncingNow()

assertSoftly {
result shouldBe true
}
}
should("no file is syncing now") {
every { progressIndicatorMock.isRunning } returns false

val result = syncProcessService.isAnyFileSyncingNow()

assertSoftly {
result shouldBe false
Expand Down
Loading

0 comments on commit 8b71d7f

Please sign in to comment.