diff --git a/platform/configuration-store-impl/src/schemeManager/SchemeFileTracker.kt b/platform/configuration-store-impl/src/schemeManager/SchemeFileTracker.kt index 46ed64a5f079..b308c4d0f06e 100644 --- a/platform/configuration-store-impl/src/schemeManager/SchemeFileTracker.kt +++ b/platform/configuration-store-impl/src/schemeManager/SchemeFileTracker.kt @@ -2,6 +2,7 @@ package com.intellij.configurationStore.schemeManager import com.intellij.configurationStore.LOG +import com.intellij.configurationStore.RELOADING_STORAGE_WRITE_REQUESTOR import com.intellij.configurationStore.StoreReloadManager import com.intellij.configurationStore.StoreReloadManagerImpl import com.intellij.openapi.diagnostic.debug @@ -24,8 +25,9 @@ internal class SchemeFileTracker( override fun after(events: List) { val list = ArrayList>() + for (event in events) { - if (event.requestor is SchemeManagerImpl<*, *>) { + if (event.requestor is SchemeManagerImpl<*, *> || event.requestor == RELOADING_STORAGE_WRITE_REQUESTOR) { continue } diff --git a/platform/configuration-store-impl/src/schemeManager/SchemeManagerFactoryImpl.kt b/platform/configuration-store-impl/src/schemeManager/SchemeManagerFactoryImpl.kt index 05912b80a846..8fca11247570 100644 --- a/platform/configuration-store-impl/src/schemeManager/SchemeManagerFactoryImpl.kt +++ b/platform/configuration-store-impl/src/schemeManager/SchemeManagerFactoryImpl.kt @@ -3,7 +3,6 @@ package com.intellij.configurationStore.schemeManager import com.intellij.configurationStore.* import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.application.EDT import com.intellij.openapi.components.ComponentManager import com.intellij.openapi.components.RoamingType import com.intellij.openapi.components.SettingsCategory @@ -14,12 +13,14 @@ import com.intellij.openapi.options.SchemeManager import com.intellij.openapi.options.SchemeManagerFactory import com.intellij.openapi.options.SchemeProcessor import com.intellij.openapi.progress.ProcessCanceledException +import com.intellij.openapi.progress.blockingContext import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFileManager +import com.intellij.openapi.vfs.newvfs.RefreshQueue +import com.intellij.openapi.vfs.newvfs.events.VFileEvent +import com.intellij.util.addSuppressed import com.intellij.util.containers.ContainerUtil import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.TestOnly @@ -104,16 +105,11 @@ sealed class SchemeManagerFactoryBase : SchemeManagerFactory(), SettingsSavingCo final override suspend fun save() { var error: Throwable? = null + val events = mutableListOf() + for (registeredManager in managers) { try { - if (registeredManager.isUseVfs) { - withContext(Dispatchers.EDT) { - registeredManager.save() - } - } - else { - registeredManager.save() - } + registeredManager.saveImpl(events) } catch (e: CancellationException) { throw e @@ -122,14 +118,16 @@ sealed class SchemeManagerFactoryBase : SchemeManagerFactory(), SettingsSavingCo throw e } catch (e: Throwable) { - if (error == null) { - error = e - } - else { - error.addSuppressed(e) - } + error = addSuppressed(error, e) } } + + if (events.isNotEmpty()) { + blockingContext { + RefreshQueue.getInstance().processEvents(false, events) + } + } + error?.let { throw it } diff --git a/platform/configuration-store-impl/src/schemeManager/SchemeManagerImpl.kt b/platform/configuration-store-impl/src/schemeManager/SchemeManagerImpl.kt index 51749319f420..9c57f1487d74 100644 --- a/platform/configuration-store-impl/src/schemeManager/SchemeManagerImpl.kt +++ b/platform/configuration-store-impl/src/schemeManager/SchemeManagerImpl.kt @@ -6,7 +6,6 @@ import com.intellij.configurationStore.* import com.intellij.diagnostic.PluginException import com.intellij.ide.ui.UITheme import com.intellij.ide.ui.laf.TempUIThemeLookAndFeelInfo -import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.components.RoamingType import com.intellij.openapi.components.SettingsCategory import com.intellij.openapi.components.impl.stores.ComponentStorageUtil @@ -22,6 +21,9 @@ import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.SafeWriteRequestor import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.newvfs.NewVirtualFile +import com.intellij.openapi.vfs.newvfs.RefreshQueue +import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent +import com.intellij.openapi.vfs.newvfs.events.VFileEvent import com.intellij.util.* import com.intellij.util.io.directoryStreamIfExists import com.intellij.util.io.write @@ -38,7 +40,6 @@ import java.util.* import java.util.concurrent.CancellationException import java.util.concurrent.atomic.AtomicBoolean import java.util.function.Predicate -import kotlin.io.path.invariantSeparatorsPathString import kotlin.io.path.isDirectory import kotlin.io.path.isHidden import kotlin.io.path.readBytes @@ -55,8 +56,7 @@ class SchemeManagerImpl( private val fileChangeSubscriber: FileChangeSubscriber? = null, private val settingsCategory: SettingsCategory = SettingsCategory.OTHER, ) : SchemeManagerBase(processor), SafeWriteRequestor, StorageManagerFileWriteRequestor { - internal val isUseVfs: Boolean - get() = fileChangeSubscriber != null + private val isUpdateVfs: Boolean = fileChangeSubscriber != null internal val isOldSchemeNaming: Boolean = schemeNameToFileName == OLD_NAME_CONVERTER @@ -67,6 +67,7 @@ class SchemeManagerImpl( internal val schemes: MutableList get() = schemeListManager.schemes + @Volatile internal var cachedVirtualDirectory: VirtualFile? = null internal val schemeExtension: String @@ -84,7 +85,7 @@ class SchemeManagerImpl( updateExtension = false } - if (isUseVfs) { + if (isUpdateVfs) { runCatching { refreshVirtualDirectory() }.getOrLogException(LOG) } } @@ -335,6 +336,14 @@ class SchemeManagerImpl( (processor !is LazySchemeProcessor || processor.isSchemeFile(name)) override fun save() { + val events = if (isUpdateVfs) mutableListOf() else Collections.emptyList() + saveImpl(events) + if (events.isNotEmpty()) { + RefreshQueue.getInstance().processEvents(false, events) + } + } + + internal fun saveImpl(events: MutableList) { if (isLoadingSchemes.get()) { LOG.warn("Skip save - schemes are loading") } @@ -365,7 +374,7 @@ class SchemeManagerImpl( val filesToDelete = HashSet(filesToDelete) for (scheme in changedSchemes) { try { - saveScheme(scheme, nameGenerator, filesToDelete) + saveScheme(scheme, nameGenerator, filesToDelete, events) } catch (e: CancellationException) { throw e } catch (e: ProcessCanceledException) { throw e } @@ -378,11 +387,11 @@ class SchemeManagerImpl( schemeListManager.data.schemeToInfo.values.removeIf { filesToDelete.contains(it.fileName) } this.filesToDelete.removeAll(filesToDelete) - deleteFiles(errorCollector, filesToDelete) + deleteFiles(errorCollector, filesToDelete, events) // remove empty directory only if some file was deleted - avoid check on each save if (!hasSchemes && (provider == null || !provider.isApplicable(fileSpec, roamingType))) { - removeDirectoryIfEmpty(errorCollector) + removeDirectoryIfEmpty(errorCollector, events) } } @@ -393,7 +402,7 @@ class SchemeManagerImpl( override fun getSettingsCategory(): SettingsCategory = settingsCategory - private fun removeDirectoryIfEmpty(errorCollector: ErrorCollector) { + private fun removeDirectoryIfEmpty(errorCollector: ErrorCollector, events: MutableList) { ioDirectory.directoryStreamIfExists { for (file in it) { if (!file.isHidden()) { @@ -405,31 +414,23 @@ class SchemeManagerImpl( LOG.info("Remove scheme directory ${ioDirectory.fileName}") - if (isUseVfs) { + try { + NioFiles.deleteRecursively(ioDirectory) + } + catch (e: Throwable) { + errorCollector.addError(e) + } + + if (isUpdateVfs) { val dir = getVirtualDirectory() cachedVirtualDirectory = null if (dir != null) { - runWriteAction { - try { - dir.delete(this) - } - catch (e: Throwable) { - errorCollector.addError(e) - } - } - } - } - else { - try { - NioFiles.deleteRecursively(ioDirectory) - } - catch (e: Throwable) { - errorCollector.addError(e) + events += VFileDeleteEvent(/*requestor =*/ this, dir) } } } - private fun saveScheme(scheme: MUTABLE_SCHEME, nameGenerator: UniqueNameGenerator, filesToDelete: MutableSet) { + private fun saveScheme(scheme: MUTABLE_SCHEME, nameGenerator: UniqueNameGenerator, filesToDelete: MutableSet, events: MutableList) { var externalInfo: ExternalInfo? = schemeListManager.getExternalInfo(scheme) val currentFileNameWithoutExtension = externalInfo?.fileNameWithoutExtension val element = processor.writeScheme(scheme)?.let { it as? Element ?: (it as Document).detachRootElement() } @@ -484,45 +485,30 @@ class SchemeManagerImpl( currentFileNameWithoutExtension != null && nameGenerator.isUnique(currentFileNameWithoutExtension) if (providerPath == null) { - if (isUseVfs) { - var file: VirtualFile? = null - var dir = getVirtualDirectory() - if (dir == null || !dir.isValid) { - dir = createDir(ioDirectory, this) + if (renamed) { + externalInfo!!.scheduleDelete(filesToDelete, "renamed") + } + + var dir = if (isUpdateVfs) getVirtualDirectory() else null + + val ioFile = ioDirectory.resolve(fileName) + ioFile.write(byteOut.internalBuffer, 0, byteOut.size()) + + if (isUpdateVfs) { + if (dir == null) { + dir = LocalFileSystem.getInstance().refreshAndFindFileByNioFile(ioDirectory) cachedVirtualDirectory = dir } - - if (renamed) { - val oldFile = dir.findChild(externalInfo!!.fileName) - if (oldFile != null) { - // VFS doesn't allow renaming to an existing file, so, check it - if (dir.findChild(fileName) == null) { - runWriteAction { - oldFile.rename(this, fileName) - } - file = oldFile - } - else { - externalInfo.scheduleDelete(filesToDelete, "renamed") - } + if (dir != null) { + val file = dir.findChild(fileName) + if (file != null) { + events += updatingEvent(ioFile, file) + } + else { + // an old file deletion event is generated by `deleteFiles` + events += creationEvent(ioFile, dir) } } - - if (file == null) { - file = SlowOperations.knownIssue("IDEA-338219, EA-867032").use { - dir.getOrCreateChild(requestor = this, fileName, directory = false) - } - } - - runWriteAction { - file.getOutputStream(this).use { byteOut.writeTo(it) } - } - } - else { - if (renamed) { - externalInfo!!.scheduleDelete(filesToDelete, "renamed") - } - ioDirectory.resolve(fileName).write(byteOut.internalBuffer, 0, byteOut.size()) } } else { @@ -582,7 +568,7 @@ class SchemeManagerImpl( return info != null && processor.getSchemeKey(scheme) != info.schemeKey } - private fun deleteFiles(errorCollector: ErrorCollector, filesToDelete: MutableSet) { + private fun deleteFiles(errorCollector: ErrorCollector, filesToDelete: MutableSet, events: MutableList) { if (provider != null) { val iterator = filesToDelete.iterator() for (name in iterator) { @@ -605,30 +591,22 @@ class SchemeManagerImpl( LOG.debug { "Delete scheme files: ${filesToDelete.joinToString()}" } - if (isUseVfs) { - getVirtualDirectory()?.let { virtualDir -> - val childrenToDelete = virtualDir.children.filter { filesToDelete.contains(it.name) } - if (childrenToDelete.isNotEmpty()) { - runWriteAction { - for (file in childrenToDelete) { - try { - file.delete(this) - } - catch (e: Throwable) { - errorCollector.addError(e) - } - } - } - } + for (name in filesToDelete) { + try { + NioFiles.deleteRecursively(ioDirectory.resolve(name)) + } + catch (e: Throwable) { + errorCollector.addError(e) } } - else { - for (name in filesToDelete) { - try { - NioFiles.deleteRecursively(ioDirectory.resolve(name)) - } - catch (e: Throwable) { - errorCollector.addError(e) + + if (isUpdateVfs) { + val dir = getVirtualDirectory() + if (dir != null) { + for (file in dir.children) { + if (file.isValid && file.name in filesToDelete) { + events += VFileDeleteEvent(/*requestor =*/ this, file) + } } } }