fix "IDEA-221944 Deadlock on opening second project" and support preloading for proje...
[idea/community.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / impl / PartialLineStatusTrackerManagerState.kt
1 // 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.
2 package com.intellij.openapi.vcs.impl
3
4 import com.intellij.diff.util.Range
5 import com.intellij.openapi.components.PersistentStateComponent
6 import com.intellij.openapi.components.State
7 import com.intellij.openapi.components.Storage
8 import com.intellij.openapi.components.StoragePathMacros
9 import com.intellij.openapi.project.Project
10 import com.intellij.openapi.util.registry.Registry
11 import com.intellij.openapi.vcs.changes.ChangeListManager
12 import com.intellij.openapi.vcs.changes.InvokeAfterUpdateMode
13 import com.intellij.openapi.vcs.ex.ChangelistsLocalLineStatusTracker
14 import com.intellij.openapi.vcs.ex.ChangelistsLocalLineStatusTracker.RangeState
15 import com.intellij.openapi.vfs.LocalFileSystem
16 import com.intellij.xml.util.XmlStringUtil
17 import org.jdom.Element
18
19 private typealias TrackerState = ChangelistsLocalLineStatusTracker.State
20 private typealias FullTrackerState = ChangelistsLocalLineStatusTracker.FullState
21
22 @State(name = "LineStatusTrackerManager", storages = [(Storage(value = StoragePathMacros.WORKSPACE_FILE))])
23 class PartialLineStatusTrackerManagerState(private val project: Project) : PersistentStateComponent<Element> {
24   private val NODE_PARTIAL_FILE = "file"
25   private val ATT_PATH = "path"
26
27   private val NODE_VCS = "vcs"
28   private val NODE_CURRENT = "current"
29   private val ATT_CONTENT = "content"
30
31   private val NODE_RANGES = "ranges"
32   private val NODE_RANGE = "range"
33   private val ATT_START_1 = "start1"
34   private val ATT_END_1 = "end1"
35   private val ATT_START_2 = "start2"
36   private val ATT_END_2 = "end2"
37   private val ATT_CHANGELIST_ID = "changelist"
38
39   override fun getState(): Element {
40     val element = Element("state")
41     val fileStates = (LineStatusTrackerManager.getInstance(project) as LineStatusTrackerManager).collectPartiallyChangedFilesStates()
42     for (state in fileStates) {
43       element.addContent(writePartialFileState(state))
44     }
45
46     return element
47   }
48
49   override fun loadState(element: Element) {
50     val fileStates = mutableListOf<TrackerState>()
51     for (node in element.getChildren(NODE_PARTIAL_FILE)) {
52       val state = readPartialFileState(node)
53       if (state != null) fileStates.add(state)
54     }
55
56     if (fileStates.isNotEmpty()) {
57       ChangeListManager.getInstance(project).invokeAfterUpdate(
58         {
59           (LineStatusTrackerManager.getInstance(project) as LineStatusTrackerManager).restoreTrackersForPartiallyChangedFiles(fileStates)
60         }, InvokeAfterUpdateMode.SILENT, null, null)
61     }
62   }
63
64   private fun writePartialFileState(state: FullTrackerState): Element {
65     val element = Element(NODE_PARTIAL_FILE)
66     element.setAttribute(ATT_PATH, state.virtualFile.path)
67
68     if (Registry.`is`("vcs.enable.partial.changelists.persist.file.contents")) {
69       // TODO: should not be stored in workspace.xml; Project.getProjectCachePath ?
70       element.addContent(Element(NODE_VCS).setAttribute(ATT_CONTENT, XmlStringUtil.escapeIllegalXmlChars(state.vcsContent)))
71       element.addContent(Element(NODE_CURRENT).setAttribute(ATT_CONTENT, XmlStringUtil.escapeIllegalXmlChars(state.currentContent)))
72     }
73
74     val rangesNode = Element(NODE_RANGES)
75     for (it in state.ranges) {
76       rangesNode.addContent(writeRangeState(it))
77     }
78     element.addContent(rangesNode)
79
80     return element
81   }
82
83   private fun readPartialFileState(element: Element): TrackerState? {
84     val path = element.getAttributeValue(ATT_PATH) ?: return null
85     val virtualFile = LocalFileSystem.getInstance().findFileByPath(path) ?: return null
86
87     val vcsContent = element.getChild(NODE_VCS)?.getAttributeValue(ATT_CONTENT)
88     val currentContent = element.getChild(NODE_CURRENT)?.getAttributeValue(ATT_CONTENT)
89
90     val rangeStates = mutableListOf<RangeState>()
91
92     val rangesNode = element.getChild(NODE_RANGES) ?: return null
93     for (node in rangesNode.getChildren(NODE_RANGE)) {
94       val rangeState = readRangeState(node) ?: return null
95       rangeStates.add(rangeState)
96     }
97
98     if (vcsContent != null && currentContent != null) {
99       return FullTrackerState(virtualFile, rangeStates,
100                               XmlStringUtil.unescapeIllegalXmlChars(vcsContent),
101                               XmlStringUtil.unescapeIllegalXmlChars(currentContent))
102     }
103     else {
104       return TrackerState(virtualFile, rangeStates)
105     }
106   }
107
108   private fun writeRangeState(range: RangeState): Element {
109     return Element(NODE_RANGE)
110       .setAttribute(ATT_START_1, range.range.start1.toString())
111       .setAttribute(ATT_END_1, range.range.end1.toString())
112       .setAttribute(ATT_START_2, range.range.start2.toString())
113       .setAttribute(ATT_END_2, range.range.end2.toString())
114       .setAttribute(ATT_CHANGELIST_ID, range.changelistId)
115   }
116
117   private fun readRangeState(node: Element): RangeState? {
118     val start1 = node.getAttributeValue(ATT_START_1)?.toIntOrNull() ?: return null
119     val end1 = node.getAttributeValue(ATT_END_1)?.toIntOrNull() ?: return null
120     val start2 = node.getAttributeValue(ATT_START_2)?.toIntOrNull() ?: return null
121     val end2 = node.getAttributeValue(ATT_END_2)?.toIntOrNull() ?: return null
122     val changelistId = node.getAttributeValue(ATT_CHANGELIST_ID) ?: return null
123     return RangeState(Range(start1, end1, start2, end2), changelistId)
124   }
125 }