mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
flush on disk committed module components state in the same write action as for project components
SaveResult introduced because coroutines can executes on different threads — so, lock free approach is used (coroutine returns own result and it's appended to total result (synchronously, for simplicity, because it is enough and no need to complicate))
This commit is contained in:
@@ -7,7 +7,6 @@ import com.intellij.openapi.application.PathManager
|
||||
import com.intellij.openapi.application.appSystemDir
|
||||
import com.intellij.openapi.components.*
|
||||
import com.intellij.openapi.components.impl.stores.FileStorageCoreUtil
|
||||
import com.intellij.openapi.components.impl.stores.SaveSessionAndFile
|
||||
import com.intellij.openapi.diagnostic.runAndLogException
|
||||
import com.intellij.openapi.util.NamedJDOMExternalizable
|
||||
import com.intellij.util.io.systemIndependentPath
|
||||
@@ -35,24 +34,19 @@ class ApplicationStoreImpl(private val application: Application, pathMacroManage
|
||||
storageManager.addMacro(StoragePathMacros.CACHE_FILE, appSystemDir.resolve("workspace").resolve("app.xml").systemIndependentPath)
|
||||
}
|
||||
|
||||
override suspend fun doSave(errors: MutableList<Throwable>, readonlyFiles: MutableList<SaveSessionAndFile>, isForceSavingAllSettings: Boolean) {
|
||||
val saveSessionManager = saveSettingsSavingComponentsAndCommitComponents(errors, isForceSavingAllSettings)
|
||||
override suspend fun doSave(result: SaveResult, isForceSavingAllSettings: Boolean) {
|
||||
val saveSessionManager = saveSettingsSavingComponentsAndCommitComponents(result, isForceSavingAllSettings)
|
||||
// todo can we store default project in parallel to regular saving? for now only flush on disk is async, but not component committing
|
||||
coroutineScope {
|
||||
launch {
|
||||
saveSessionManager.save(readonlyFiles, errors)
|
||||
saveSessionManager.save().appendTo(result)
|
||||
}
|
||||
launch {
|
||||
// here, because no Project (and so, ProjectStoreImpl) on Welcome Screen
|
||||
serviceIfCreated<DefaultProjectExportableAndSaveTrigger>()?.save(errors, readonlyFiles, isForceSavingAllSettings)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun createSaveSessionProducerManager(): SaveSessionProducerManager {
|
||||
return object : SaveSessionProducerManager() {
|
||||
override suspend fun save(readonlyFiles: MutableList<SaveSessionAndFile>, errors: MutableList<Throwable>): Boolean {
|
||||
return super.save(readonlyFiles, errors)
|
||||
val r = serviceIfCreated<DefaultProjectExportableAndSaveTrigger>()?.save(isForceSavingAllSettings) ?: return@launch
|
||||
// ignore
|
||||
r.isChanged = false
|
||||
r.appendTo(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import com.intellij.openapi.components.*
|
||||
import com.intellij.openapi.components.StateStorageChooserEx.Resolution
|
||||
import com.intellij.openapi.components.impl.ComponentManagerImpl
|
||||
import com.intellij.openapi.components.impl.stores.IComponentStore
|
||||
import com.intellij.openapi.components.impl.stores.SaveSessionAndFile
|
||||
import com.intellij.openapi.components.impl.stores.UnknownMacroNotification
|
||||
import com.intellij.openapi.diagnostic.debug
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
@@ -32,7 +31,6 @@ import com.intellij.util.SmartList
|
||||
import com.intellij.util.SystemProperties
|
||||
import com.intellij.util.containers.SmartHashSet
|
||||
import com.intellij.util.containers.isNullOrEmpty
|
||||
import com.intellij.util.lang.CompoundRuntimeException
|
||||
import com.intellij.util.messages.MessageBus
|
||||
import com.intellij.util.xmlb.XmlSerializerUtil
|
||||
import gnu.trove.THashMap
|
||||
@@ -131,16 +129,24 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
}
|
||||
|
||||
final override suspend fun save(isForceSavingAllSettings: Boolean) {
|
||||
val errors = SmartList<Throwable>()
|
||||
val readonlyFiles = SmartList<SaveSessionAndFile>()
|
||||
doSave(errors, readonlyFiles, isForceSavingAllSettings)
|
||||
CompoundRuntimeException.throwIfNotEmpty(errors)
|
||||
val result = SaveResult()
|
||||
doSave(result, isForceSavingAllSettings)
|
||||
result.throwIfErrored()
|
||||
}
|
||||
|
||||
internal abstract suspend fun doSave(errors: MutableList<Throwable>, readonlyFiles: MutableList<SaveSessionAndFile>, isForceSavingAllSettings: Boolean)
|
||||
internal abstract suspend fun doSave(result: SaveResult, isForceSavingAllSettings: Boolean)
|
||||
|
||||
internal suspend fun createSaveSessionManagerAndSaveComponents(saveResult: SaveResult, isForceSavingAllSettings: Boolean): SaveSessionProducerManager {
|
||||
return withContext(AppUIExecutor.onUiThread().coroutineDispatchingContext()) {
|
||||
val errors = SmartList<Throwable>()
|
||||
val manager = doCreateSaveSessionManagerAndSaveComponents(isForceSavingAllSettings, errors)
|
||||
saveResult.addErrors(errors)
|
||||
manager
|
||||
}
|
||||
}
|
||||
|
||||
@CalledInAwt
|
||||
internal fun createSaveSessionManagerAndSaveComponents(isForce: Boolean, errors: MutableList<Throwable>): SaveSessionProducerManager {
|
||||
internal fun doCreateSaveSessionManagerAndSaveComponents(isForce: Boolean, errors: MutableList<Throwable>): SaveSessionProducerManager {
|
||||
val saveSessionProducerManager = createSaveSessionProducerManager()
|
||||
saveComponents(isForce, saveSessionProducerManager, errors)
|
||||
return saveSessionProducerManager
|
||||
@@ -218,12 +224,12 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
commitComponent(externalizationSession, ComponentInfoImpl(component, stateSpec), null)
|
||||
val absolutePath = Paths.get(storageManager.expandMacros(findNonDeprecated(stateSpec.storages).path)).toAbsolutePath().toString()
|
||||
runUndoTransparentWriteAction {
|
||||
val errors: MutableList<Throwable> = SmartList<Throwable>()
|
||||
val saveResult = SaveResult()
|
||||
val newDisposable = Disposer.newDisposable()
|
||||
try {
|
||||
VfsRootAccess.allowRootAccess(newDisposable, absolutePath)
|
||||
runBlocking {
|
||||
val isSomethingChanged = externalizationSession.save(errors = errors)
|
||||
val isSomethingChanged = externalizationSession.save().isChanged
|
||||
if (!isSomethingChanged) {
|
||||
LOG.info("saveApplicationComponent is called for ${stateSpec.name} but nothing to save")
|
||||
}
|
||||
@@ -232,7 +238,7 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
finally {
|
||||
Disposer.dispose(newDisposable)
|
||||
}
|
||||
CompoundRuntimeException.throwIfNotEmpty(errors)
|
||||
saveResult.throwIfErrored()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -583,10 +589,9 @@ private fun notifyUnknownMacros(store: IComponentStore, project: Project, compon
|
||||
// to make sure that ApplicationStore or ProjectStore will not call incomplete doSave implementation
|
||||
// (because these stores combine several calls for better control/async instead of simple sequential delegation)
|
||||
abstract class ChildlessComponentStore : ComponentStoreImpl() {
|
||||
override suspend fun doSave(errors: MutableList<Throwable>, readonlyFiles: MutableList<SaveSessionAndFile>, isForceSavingAllSettings: Boolean) {
|
||||
withContext(AppUIExecutor.onUiThread().coroutineDispatchingContext()) {
|
||||
createSaveSessionManagerAndSaveComponents(isForceSavingAllSettings, errors)
|
||||
}
|
||||
.save(readonlyFiles, errors)
|
||||
override suspend fun doSave(result: SaveResult, isForceSavingAllSettings: Boolean) {
|
||||
createSaveSessionManagerAndSaveComponents(result, isForceSavingAllSettings)
|
||||
.save()
|
||||
.appendTo(result)
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,10 @@ import com.intellij.openapi.application.AppUIExecutor
|
||||
import com.intellij.openapi.application.async.coroutineDispatchingContext
|
||||
import com.intellij.openapi.components.SettingsSavingComponent
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.util.SmartList
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
abstract class ComponentStoreWithExtraComponents : ComponentStoreImpl() {
|
||||
@Suppress("DEPRECATION")
|
||||
@@ -27,31 +27,34 @@ abstract class ComponentStoreWithExtraComponents : ComponentStoreImpl() {
|
||||
super.initComponent(component, isService)
|
||||
}
|
||||
|
||||
internal suspend fun saveSettingsSavingComponentsAndCommitComponents(errors: MutableList<Throwable>, isForceSavingAllSettings: Boolean): SaveSessionProducerManager {
|
||||
internal suspend fun saveSettingsSavingComponentsAndCommitComponents(result: SaveResult, isForceSavingAllSettings: Boolean): SaveSessionProducerManager {
|
||||
coroutineScope {
|
||||
// expects EDT
|
||||
launch(AppUIExecutor.onUiThread().coroutineDispatchingContext()) {
|
||||
@Suppress("Duplicates")
|
||||
val errors = SmartList<Throwable>()
|
||||
for (settingsSavingComponent in settingsSavingComponents) {
|
||||
runAndCollectException(errors) {
|
||||
settingsSavingComponent.save()
|
||||
}
|
||||
}
|
||||
result.addErrors(errors)
|
||||
}
|
||||
|
||||
launch {
|
||||
val errors = SmartList<Throwable>()
|
||||
for (settingsSavingComponent in asyncSettingsSavingComponents) {
|
||||
runAndCollectException(errors) {
|
||||
settingsSavingComponent.save()
|
||||
}
|
||||
}
|
||||
result.addErrors(errors)
|
||||
}
|
||||
}
|
||||
|
||||
// SchemeManager (old settingsSavingComponent) must be saved before saving components (component state uses scheme manager in an ipr project, so, we must save it before)
|
||||
// so, call sequentially it, not inside coroutineScope
|
||||
return withContext(AppUIExecutor.onUiThread().coroutineDispatchingContext()) {
|
||||
createSaveSessionManagerAndSaveComponents(isForceSavingAllSettings, errors)
|
||||
}
|
||||
return createSaveSessionManagerAndSaveComponents(result, isForceSavingAllSettings)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.intellij.configurationStore
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.*
|
||||
import com.intellij.openapi.components.impl.stores.SaveSessionAndFile
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jdom.Element
|
||||
import java.io.Writer
|
||||
@@ -104,8 +103,10 @@ internal class DefaultProjectExportableAndSaveTrigger {
|
||||
@Volatile
|
||||
var project: Project? = null
|
||||
|
||||
suspend fun save(errors: MutableList<Throwable>, readonlyFiles: MutableList<SaveSessionAndFile>, isForceSavingAllSettings: Boolean) {
|
||||
val project = project ?: return
|
||||
(project.stateStore as ComponentStoreImpl).doSave(errors, readonlyFiles, isForceSavingAllSettings)
|
||||
suspend fun save(isForceSavingAllSettings: Boolean): SaveResult {
|
||||
val project = project ?: return SaveResult.EMPTY
|
||||
val result = SaveResult()
|
||||
(project.stateStore as ComponentStoreImpl).doSave(result, isForceSavingAllSettings)
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -319,7 +319,8 @@ private fun deleteFile(file: Path, requestor: Any, virtualFile: VirtualFile?) {
|
||||
else {
|
||||
throw ReadOnlyModificationException(virtualFile, object : SaveSession {
|
||||
override fun save() {
|
||||
deleteFile(requestor, virtualFile)
|
||||
// caller must wraps into undo transparent and write action
|
||||
virtualFile.delete(requestor)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,52 +3,74 @@ package com.intellij.configurationStore
|
||||
|
||||
import com.intellij.notification.Notifications
|
||||
import com.intellij.notification.NotificationsManager
|
||||
import com.intellij.openapi.application.runReadAction
|
||||
import com.intellij.openapi.components.impl.stores.IComponentStore
|
||||
import com.intellij.openapi.application.AppUIExecutor
|
||||
import com.intellij.openapi.application.async.coroutineDispatchingContext
|
||||
import com.intellij.openapi.application.async.inUndoTransparentAction
|
||||
import com.intellij.openapi.application.async.inWriteAction
|
||||
import com.intellij.openapi.components.impl.stores.SaveSessionAndFile
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.impl.ProjectManagerImpl.UnableToSaveProjectNotification
|
||||
import com.intellij.openapi.vfs.ReadonlyStatusHandler
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.util.SmartList
|
||||
import com.intellij.util.containers.mapSmart
|
||||
import com.intellij.util.lang.CompoundRuntimeException
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class ProjectSaveSessionProducerManager(private val project: Project) : SaveSessionProducerManager() {
|
||||
override suspend fun save(readonlyFiles: MutableList<SaveSessionAndFile>, errors: MutableList<Throwable>): Boolean {
|
||||
val isChanged = super.save(readonlyFiles, errors)
|
||||
suspend fun saveWithAdditionalSaveSessions(extraSessions: List<SaveSession>): SaveResult {
|
||||
val saveSessions = SmartList<SaveSession>()
|
||||
collectSaveSessions(saveSessions)
|
||||
if (saveSessions.isEmpty() && extraSessions.isEmpty()) {
|
||||
return SaveResult.EMPTY
|
||||
}
|
||||
|
||||
val saveResult = withContext(AppUIExecutor.onUiThread().inUndoTransparentAction().inWriteAction().coroutineDispatchingContext()) {
|
||||
val r = SaveResult()
|
||||
saveSessions(extraSessions, r)
|
||||
saveSessions(saveSessions, r)
|
||||
r
|
||||
}
|
||||
validate(saveResult)
|
||||
return saveResult
|
||||
}
|
||||
|
||||
private suspend fun validate(saveResult: SaveResult) {
|
||||
val notifications = getUnableToSaveNotifications()
|
||||
val readonlyFiles = saveResult.readonlyFiles
|
||||
if (readonlyFiles.isEmpty()) {
|
||||
notifications.forEach { it.expire() }
|
||||
return isChanged
|
||||
return
|
||||
}
|
||||
|
||||
if (!notifications.isEmpty()) {
|
||||
throw IComponentStore.SaveCancelledException()
|
||||
throw UnresolvedReadOnlyFilesException(readonlyFiles.mapSmart { it.file })
|
||||
}
|
||||
|
||||
val status = runReadAction {
|
||||
val status = withContext(AppUIExecutor.onUiThread().coroutineDispatchingContext()) {
|
||||
ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(getFilesList(readonlyFiles))
|
||||
}
|
||||
|
||||
if (status.hasReadonlyFiles()) {
|
||||
dropUnableToSaveProjectNotification(project, status.readonlyFiles.toList())
|
||||
throw IComponentStore.SaveCancelledException()
|
||||
val unresolvedReadOnlyFiles = status.readonlyFiles.toList()
|
||||
dropUnableToSaveProjectNotification(project, unresolvedReadOnlyFiles)
|
||||
saveResult.addError(UnresolvedReadOnlyFilesException(unresolvedReadOnlyFiles))
|
||||
return
|
||||
}
|
||||
|
||||
val oldList = readonlyFiles.toTypedArray()
|
||||
readonlyFiles.clear()
|
||||
for (entry in oldList) {
|
||||
executeSave(entry.session, readonlyFiles, errors)
|
||||
}
|
||||
|
||||
CompoundRuntimeException.throwIfNotEmpty(errors)
|
||||
withContext(AppUIExecutor.onUiThread().inUndoTransparentAction().inWriteAction().coroutineDispatchingContext()) {
|
||||
val r = SaveResult()
|
||||
for (entry in oldList) {
|
||||
executeSave(entry.session, r)
|
||||
}
|
||||
r
|
||||
}.appendTo(saveResult)
|
||||
|
||||
if (!readonlyFiles.isEmpty()) {
|
||||
dropUnableToSaveProjectNotification(project, getFilesList(readonlyFiles))
|
||||
throw IComponentStore.SaveCancelledException()
|
||||
saveResult.addError(UnresolvedReadOnlyFilesException(readonlyFiles.mapSmart { it.file }))
|
||||
}
|
||||
|
||||
return isChanged
|
||||
}
|
||||
|
||||
private fun dropUnableToSaveProjectNotification(project: Project, readOnlyFiles: List<VirtualFile>) {
|
||||
|
||||
@@ -6,13 +6,10 @@ import com.intellij.ide.highlighter.WorkspaceFileType
|
||||
import com.intellij.openapi.application.AppUIExecutor
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.async.coroutineDispatchingContext
|
||||
import com.intellij.openapi.application.async.inUndoTransparentAction
|
||||
import com.intellij.openapi.application.async.inWriteAction
|
||||
import com.intellij.openapi.application.runReadAction
|
||||
import com.intellij.openapi.components.*
|
||||
import com.intellij.openapi.components.impl.stores.IComponentStore
|
||||
import com.intellij.openapi.components.impl.stores.IProjectStore
|
||||
import com.intellij.openapi.components.impl.stores.SaveSessionAndFile
|
||||
import com.intellij.openapi.diagnostic.runAndLogException
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.module.ModuleManager
|
||||
@@ -329,7 +326,7 @@ private open class ProjectStoreImpl(project: Project, private val pathMacroManag
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun doSave(errors: MutableList<Throwable>, readonlyFiles: MutableList<SaveSessionAndFile>, isForceSavingAllSettings: Boolean) = coroutineScope<Unit> {
|
||||
final override suspend fun doSave(result: SaveResult, isForceSavingAllSettings: Boolean) {
|
||||
coroutineScope {
|
||||
launch {
|
||||
try {
|
||||
@@ -340,43 +337,42 @@ private open class ProjectStoreImpl(project: Project, private val pathMacroManag
|
||||
}
|
||||
}
|
||||
launch {
|
||||
saveSettingsSavingComponentsAndCommitComponents(errors, isForceSavingAllSettings).save(readonlyFiles, errors)
|
||||
// save modules before project
|
||||
val errors = SmartList<Throwable>()
|
||||
val moduleSaveSessions = saveModules(errors, isForceSavingAllSettings)
|
||||
result.addErrors(errors)
|
||||
|
||||
(saveSettingsSavingComponentsAndCommitComponents(result, isForceSavingAllSettings) as ProjectSaveSessionProducerManager)
|
||||
.saveWithAdditionalSaveSessions(moduleSaveSessions)
|
||||
.appendTo(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected open suspend fun saveModules(errors: MutableList<Throwable>, isForceSavingAllSettings: Boolean): List<SaveSession> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
final override fun createSaveSessionProducerManager() = ProjectSaveSessionProducerManager(project)
|
||||
}
|
||||
|
||||
private class ProjectWithModulesStoreImpl(project: Project, pathMacroManager: PathMacroManager) : ProjectStoreImpl(project, pathMacroManager) {
|
||||
override suspend fun doSave(errors: MutableList<Throwable>, readonlyFiles: MutableList<SaveSessionAndFile>, isForceSavingAllSettings: Boolean) {
|
||||
// save modules before project
|
||||
saveModules(ModuleManager.getInstance(project)?.modules ?: Module.EMPTY_ARRAY, errors, readonlyFiles, isForceSavingAllSettings)
|
||||
|
||||
super.doSave(errors, readonlyFiles, isForceSavingAllSettings)
|
||||
}
|
||||
|
||||
private suspend fun saveModules(modules: Array<Module>, errors: MutableList<Throwable>, readonlyFiles: MutableList<SaveSessionAndFile>, isForce: Boolean) {
|
||||
override suspend fun saveModules(errors: MutableList<Throwable>, isForceSavingAllSettings: Boolean): List<SaveSession> {
|
||||
val modules = ModuleManager.getInstance(project)?.modules ?: Module.EMPTY_ARRAY
|
||||
if (modules.isEmpty()) {
|
||||
return
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
withContext(AppUIExecutor.onUiThread().coroutineDispatchingContext()) {
|
||||
return withContext(AppUIExecutor.onUiThread().coroutineDispatchingContext()) {
|
||||
// do no create with capacity because very rarely a lot of modules will be modified
|
||||
val saveSessions: MutableList<SaveSession> = SmartList<SaveSession>()
|
||||
// commit components
|
||||
for (module in modules) {
|
||||
val moduleStore = ModuleServiceManager.getService(module, IComponentStore::class.java) as ComponentStoreImpl
|
||||
// collectSaveSessions is very cheap, so, do it in EDT
|
||||
moduleStore.createSaveSessionManagerAndSaveComponents(isForce, errors).collectSaveSessions(saveSessions)
|
||||
}
|
||||
|
||||
if (!saveSessions.isEmpty()) {
|
||||
withContext(AppUIExecutor.onUiThread().inUndoTransparentAction().inWriteAction().coroutineDispatchingContext()) {
|
||||
// flush on disk
|
||||
saveSessions(saveSessions, readonlyFiles, errors)
|
||||
}
|
||||
moduleStore.doCreateSaveSessionManagerAndSaveComponents(isForceSavingAllSettings, errors).collectSaveSessions(saveSessions)
|
||||
}
|
||||
saveSessions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,11 +12,11 @@ import com.intellij.util.SmartList
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.*
|
||||
|
||||
interface SaveExecutor {
|
||||
internal interface SaveExecutor {
|
||||
/**
|
||||
* @return was something really saved
|
||||
*/
|
||||
suspend fun save(readonlyFiles: MutableList<SaveSessionAndFile> = SmartList(), errors: MutableList<Throwable>): Boolean
|
||||
suspend fun save(): SaveResult
|
||||
}
|
||||
|
||||
internal open class SaveSessionProducerManager : SaveExecutor {
|
||||
@@ -56,44 +56,48 @@ internal open class SaveSessionProducerManager : SaveExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun save(readonlyFiles: MutableList<SaveSessionAndFile>, errors: MutableList<Throwable>): Boolean {
|
||||
override suspend fun save(): SaveResult {
|
||||
val saveSessions = SmartList<SaveSession>()
|
||||
collectSaveSessions(saveSessions)
|
||||
if (saveSessions.isEmpty()) {
|
||||
return false
|
||||
return SaveResult.EMPTY
|
||||
}
|
||||
|
||||
val task = {
|
||||
val result = SaveResult()
|
||||
saveSessions(saveSessions, result)
|
||||
result
|
||||
}
|
||||
|
||||
val task = { saveSessions(saveSessions, readonlyFiles, errors) }
|
||||
if (isVfsRequired) {
|
||||
withContext(AppUIExecutor.onUiThread().inUndoTransparentAction().inWriteAction().coroutineDispatchingContext()) {
|
||||
return withContext(AppUIExecutor.onUiThread().inUndoTransparentAction().inWriteAction().coroutineDispatchingContext()) {
|
||||
task()
|
||||
}
|
||||
}
|
||||
else {
|
||||
task()
|
||||
return task()
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
internal fun saveSessions(saveSessions: MutableList<SaveSession>, readonlyFiles: MutableList<SaveSessionAndFile>, errors: MutableList<Throwable>) {
|
||||
internal fun saveSessions(saveSessions: List<SaveSession>, result: SaveResult) {
|
||||
for (saveSession in saveSessions) {
|
||||
executeSave(saveSession, readonlyFiles, errors)
|
||||
executeSave(saveSession, result)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun executeSave(session: SaveSession, readonlyFiles: MutableList<SaveSessionAndFile>, errors: MutableList<Throwable>) {
|
||||
internal fun executeSave(session: SaveSession, result: SaveResult) {
|
||||
try {
|
||||
session.save()
|
||||
}
|
||||
catch (e: ReadOnlyModificationException) {
|
||||
LOG.warn(e)
|
||||
readonlyFiles.add(SaveSessionAndFile(e.session ?: session, e.file))
|
||||
result.addReadOnlyFile(SaveSessionAndFile(e.session ?: session, e.file))
|
||||
}
|
||||
catch (e: ProcessCanceledException) {
|
||||
throw e
|
||||
}
|
||||
catch (e: Exception) {
|
||||
errors.add(e)
|
||||
result.addError(e)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.configurationStore
|
||||
|
||||
import com.intellij.openapi.components.impl.stores.SaveSessionAndFile
|
||||
import com.intellij.util.SmartList
|
||||
import com.intellij.util.lang.CompoundRuntimeException
|
||||
|
||||
internal class SaveResult {
|
||||
companion object {
|
||||
val EMPTY = SaveResult()
|
||||
}
|
||||
|
||||
private val errors: MutableList<Throwable> = SmartList()
|
||||
val readonlyFiles: MutableList<SaveSessionAndFile> = SmartList()
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
var isChanged = false
|
||||
|
||||
@Synchronized
|
||||
fun addError(error: Throwable) {
|
||||
errors.add(error)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun addReadOnlyFile(info: SaveSessionAndFile) {
|
||||
readonlyFiles.add(info)
|
||||
}
|
||||
|
||||
fun addErrors(list: List<Throwable>) {
|
||||
if (list.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
synchronized(this) {
|
||||
errors.addAll(list)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun appendTo(saveResult: SaveResult) {
|
||||
if (this === EMPTY) {
|
||||
return
|
||||
}
|
||||
|
||||
synchronized(saveResult) {
|
||||
saveResult.errors.addAll(errors)
|
||||
saveResult.readonlyFiles.addAll(readonlyFiles)
|
||||
|
||||
if (isChanged) {
|
||||
saveResult.isChanged = isChanged
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun throwIfErrored() {
|
||||
CompoundRuntimeException.throwIfNotEmpty(errors)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.configurationStore
|
||||
|
||||
import com.intellij.openapi.components.StateStorage
|
||||
import com.intellij.openapi.components.impl.stores.BatchUpdateListener
|
||||
import com.intellij.openapi.diagnostic.debugOrInfoIfTestMode
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.util.messages.MessageBus
|
||||
import org.jdom.Element
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
@@ -83,4 +84,6 @@ inline fun <T> runBatchUpdate(messageBus: MessageBus, runnable: () -> T): T {
|
||||
finally {
|
||||
publisher.onBatchUpdateFinished()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class UnresolvedReadOnlyFilesException(val files: List<VirtualFile>) : RuntimeException()
|
||||
@@ -13,7 +13,6 @@ import com.intellij.openapi.application.ex.ApplicationManagerEx
|
||||
import com.intellij.openapi.components.ComponentManager
|
||||
import com.intellij.openapi.components.PersistentStateComponent
|
||||
import com.intellij.openapi.components.State
|
||||
import com.intellij.openapi.components.impl.stores.IComponentStore
|
||||
import com.intellij.openapi.components.stateStore
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
@@ -28,7 +27,7 @@ import kotlinx.coroutines.runBlocking
|
||||
private val LOG = Logger.getInstance("#com.intellij.openapi.components.impl.stores.StoreUtil")
|
||||
|
||||
/**
|
||||
* Consider to not use this method in tests, for tests direct saving of state store is ok.
|
||||
* Do not use this method in tests, instead directly save using state store.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun saveSettings(componentManager: ComponentManager, isForceSavingAllSettings: Boolean = false) {
|
||||
@@ -39,7 +38,7 @@ fun saveSettings(componentManager: ComponentManager, isForceSavingAllSettings: B
|
||||
componentManager.stateStore.save(isForceSavingAllSettings = isForceSavingAllSettings)
|
||||
}
|
||||
}
|
||||
catch (e: IComponentStore.SaveCancelledException) {
|
||||
catch (e: UnresolvedReadOnlyFilesException) {
|
||||
LOG.info(e)
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
|
||||
-2
@@ -24,8 +24,6 @@ interface IComponentStore {
|
||||
|
||||
fun isReloadPossible(componentNames: Set<String>): Boolean
|
||||
|
||||
class SaveCancelledException : RuntimeException()
|
||||
|
||||
suspend fun save(isForceSavingAllSettings: Boolean = false)
|
||||
|
||||
@TestOnly
|
||||
|
||||
Reference in New Issue
Block a user