Implement persistent storage for time stamps of files that affect build script configuration (KT-3556)
^KT-3556 Fixed
This commit is contained in:
+68
-23
@@ -5,18 +5,23 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.scripting.gradle
|
package org.jetbrains.kotlin.idea.scripting.gradle
|
||||||
|
|
||||||
|
import com.intellij.openapi.components.*
|
||||||
import com.intellij.openapi.externalSystem.service.project.autoimport.ConfigurationFileCrcFactory
|
import com.intellij.openapi.externalSystem.service.project.autoimport.ConfigurationFileCrcFactory
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.util.io.FileUtil
|
||||||
import com.intellij.openapi.vfs.VfsUtil
|
import com.intellij.openapi.vfs.VfsUtil
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
import com.intellij.util.PathUtil
|
||||||
|
import org.jetbrains.annotations.TestOnly
|
||||||
import org.jetbrains.plugins.gradle.settings.GradleLocalSettings
|
import org.jetbrains.plugins.gradle.settings.GradleLocalSettings
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class GradleScriptInputsWatcher(val project: Project) {
|
@State(
|
||||||
private val comparator = Comparator<VirtualFile> { f1, f2 -> (f1.timeStamp - f2.timeStamp).toInt() }
|
name = "KotlinBuildScriptsModificationInfo",
|
||||||
|
storages = [Storage(StoragePathMacros.CACHE_FILE)]
|
||||||
private val storage = TreeSet(comparator)
|
)
|
||||||
|
class GradleScriptInputsWatcher(val project: Project) : PersistentStateComponent<GradleScriptInputsWatcher.Storage> {
|
||||||
|
private var storage = Storage()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
initStorage(project)
|
initStorage(project)
|
||||||
@@ -29,38 +34,78 @@ class GradleScriptInputsWatcher(val project: Project) {
|
|||||||
if (file != null && !file.isDirectory) {
|
if (file != null && !file.isDirectory) {
|
||||||
val calculateCrc = ConfigurationFileCrcFactory(file).create()
|
val calculateCrc = ConfigurationFileCrcFactory(file).create()
|
||||||
if (calculateCrc != stamp) {
|
if (calculateCrc != stamp) {
|
||||||
addToStorage(file)
|
storage.fileChanged(file, file.timeStamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun lastModifiedFileTimeStamp(file: VirtualFile): Long = lastModifiedRelatedFile(file)?.timeStamp ?: 0
|
fun lastModifiedFileTimeStamp(file: VirtualFile): Long = storage.lastModifiedTimeStampExcept(file)
|
||||||
|
|
||||||
fun areRelatedFilesUpToDate(file: VirtualFile, timeStamp: Long): Boolean {
|
fun areRelatedFilesUpToDate(file: VirtualFile, timeStamp: Long): Boolean {
|
||||||
return lastModifiedFileTimeStamp(file) <= timeStamp
|
return lastModifiedFileTimeStamp(file) < timeStamp
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addToStorage(file: VirtualFile) {
|
class Storage(
|
||||||
if (storage.contains(file)) {
|
private var lastModifiedTS: Long = 0,
|
||||||
storage.remove(file)
|
private var lastModifiedFiles: MutableSet<String> = hashSetOf(),
|
||||||
|
private var previousModifiedTS: Long = 0,
|
||||||
|
private var previousModifiedFiles: MutableSet<String> = hashSetOf()
|
||||||
|
) {
|
||||||
|
fun lastModifiedTimeStampExcept(file: VirtualFile): Long {
|
||||||
|
val fileId = getFileId(file)
|
||||||
|
synchronized(this) {
|
||||||
|
if (lastModifiedFiles.contains(fileId) && lastModifiedFiles.size == 1) {
|
||||||
|
return previousModifiedTS
|
||||||
|
}
|
||||||
|
return lastModifiedTS
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fileChanged(file: VirtualFile, ts: Long) {
|
||||||
|
val fileId = getFileId(file)
|
||||||
|
synchronized(this) {
|
||||||
|
when {
|
||||||
|
ts > lastModifiedTS -> {
|
||||||
|
previousModifiedFiles = lastModifiedFiles
|
||||||
|
previousModifiedTS = lastModifiedTS
|
||||||
|
|
||||||
|
lastModifiedFiles = hashSetOf(fileId)
|
||||||
|
lastModifiedTS = ts
|
||||||
|
}
|
||||||
|
ts == lastModifiedTS -> {
|
||||||
|
lastModifiedFiles.add(fileId)
|
||||||
|
}
|
||||||
|
ts == previousModifiedTS -> {
|
||||||
|
previousModifiedFiles.add(fileId)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFileId(file: VirtualFile): String {
|
||||||
|
val canonized = PathUtil.getCanonicalPath(file.path)
|
||||||
|
return FileUtil.toSystemIndependentName(canonized)
|
||||||
}
|
}
|
||||||
storage.add(file)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun lastModifiedRelatedFile(file: VirtualFile): VirtualFile? {
|
override fun getState(): Storage {
|
||||||
if (storage.isEmpty()) return null
|
return storage
|
||||||
|
}
|
||||||
|
|
||||||
val iterator = storage.descendingIterator()
|
override fun loadState(state: Storage) {
|
||||||
if (!iterator.hasNext()) return null
|
this.storage = state
|
||||||
|
}
|
||||||
|
|
||||||
var lastModifiedFile = iterator.next()
|
@TestOnly
|
||||||
while (lastModifiedFile == file && iterator.hasNext()) {
|
fun clearAndRefillState() {
|
||||||
lastModifiedFile = iterator.next()
|
loadState(project.service<GradleScriptInputsWatcher>().state)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastModifiedFile == file) return null
|
@TestOnly
|
||||||
|
fun fileChanged(file: VirtualFile, ts: Long) {
|
||||||
return lastModifiedFile
|
storage.fileChanged(file, ts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+68
-23
@@ -5,18 +5,23 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.scripting.gradle
|
package org.jetbrains.kotlin.idea.scripting.gradle
|
||||||
|
|
||||||
|
import com.intellij.openapi.components.*
|
||||||
import com.intellij.openapi.externalSystem.service.project.autoimport.ConfigurationFileCrcFactory
|
import com.intellij.openapi.externalSystem.service.project.autoimport.ConfigurationFileCrcFactory
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.util.io.FileUtil
|
||||||
import com.intellij.openapi.vfs.VfsUtil
|
import com.intellij.openapi.vfs.VfsUtil
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
import com.intellij.util.PathUtil
|
||||||
|
import org.jetbrains.annotations.TestOnly
|
||||||
import org.jetbrains.plugins.gradle.settings.GradleLocalSettings
|
import org.jetbrains.plugins.gradle.settings.GradleLocalSettings
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class GradleScriptInputsWatcher(val project: Project) {
|
@State(
|
||||||
private val comparator = Comparator<VirtualFile> { f1, f2 -> (f1.timeStamp - f2.timeStamp).toInt() }
|
name = "KotlinBuildScriptsModificationInfo",
|
||||||
|
storages = [Storage(StoragePathMacros.CACHE_FILE)]
|
||||||
private val storage = TreeSet(comparator)
|
)
|
||||||
|
class GradleScriptInputsWatcher(val project: Project) : PersistentStateComponent<GradleScriptInputsWatcher.Storage> {
|
||||||
|
private var storage = Storage()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
initStorage(project)
|
initStorage(project)
|
||||||
@@ -29,38 +34,78 @@ class GradleScriptInputsWatcher(val project: Project) {
|
|||||||
if (file != null && !file.isDirectory) {
|
if (file != null && !file.isDirectory) {
|
||||||
val calculateCrc = ConfigurationFileCrcFactory(project, file).create()
|
val calculateCrc = ConfigurationFileCrcFactory(project, file).create()
|
||||||
if (calculateCrc != stamp) {
|
if (calculateCrc != stamp) {
|
||||||
addToStorage(file)
|
storage.fileChanged(file, file.timeStamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun lastModifiedFileTimeStamp(file: VirtualFile): Long = lastModifiedRelatedFile(file)?.timeStamp ?: 0
|
fun lastModifiedFileTimeStamp(file: VirtualFile): Long = storage.lastModifiedTimeStampExcept(file)
|
||||||
|
|
||||||
fun areRelatedFilesUpToDate(file: VirtualFile, timeStamp: Long): Boolean {
|
fun areRelatedFilesUpToDate(file: VirtualFile, timeStamp: Long): Boolean {
|
||||||
return lastModifiedFileTimeStamp(file) <= timeStamp
|
return lastModifiedFileTimeStamp(file) < timeStamp
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addToStorage(file: VirtualFile) {
|
class Storage(
|
||||||
if (storage.contains(file)) {
|
private var lastModifiedTS: Long = 0,
|
||||||
storage.remove(file)
|
private var lastModifiedFiles: MutableSet<String> = hashSetOf(),
|
||||||
|
private var previousModifiedTS: Long = 0,
|
||||||
|
private var previousModifiedFiles: MutableSet<String> = hashSetOf()
|
||||||
|
) {
|
||||||
|
fun lastModifiedTimeStampExcept(file: VirtualFile): Long {
|
||||||
|
val fileId = getFileId(file)
|
||||||
|
synchronized(this) {
|
||||||
|
if (lastModifiedFiles.contains(fileId) && lastModifiedFiles.size == 1) {
|
||||||
|
return previousModifiedTS
|
||||||
|
}
|
||||||
|
return lastModifiedTS
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fileChanged(file: VirtualFile, ts: Long) {
|
||||||
|
val fileId = getFileId(file)
|
||||||
|
synchronized(this) {
|
||||||
|
when {
|
||||||
|
ts > lastModifiedTS -> {
|
||||||
|
previousModifiedFiles = lastModifiedFiles
|
||||||
|
previousModifiedTS = lastModifiedTS
|
||||||
|
|
||||||
|
lastModifiedFiles = hashSetOf(fileId)
|
||||||
|
lastModifiedTS = ts
|
||||||
|
}
|
||||||
|
ts == lastModifiedTS -> {
|
||||||
|
lastModifiedFiles.add(fileId)
|
||||||
|
}
|
||||||
|
ts == previousModifiedTS -> {
|
||||||
|
previousModifiedFiles.add(fileId)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFileId(file: VirtualFile): String {
|
||||||
|
val canonized = PathUtil.getCanonicalPath(file.path)
|
||||||
|
return FileUtil.toSystemIndependentName(canonized)
|
||||||
}
|
}
|
||||||
storage.add(file)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun lastModifiedRelatedFile(file: VirtualFile): VirtualFile? {
|
override fun getState(): Storage {
|
||||||
if (storage.isEmpty()) return null
|
return storage
|
||||||
|
}
|
||||||
|
|
||||||
val iterator = storage.descendingIterator()
|
override fun loadState(state: Storage) {
|
||||||
if (!iterator.hasNext()) return null
|
this.storage = state
|
||||||
|
}
|
||||||
|
|
||||||
var lastModifiedFile = iterator.next()
|
@TestOnly
|
||||||
while (lastModifiedFile == file && iterator.hasNext()) {
|
fun clearAndRefillState() {
|
||||||
lastModifiedFile = iterator.next()
|
loadState(project.service<GradleScriptInputsWatcher>().state)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastModifiedFile == file) return null
|
@TestOnly
|
||||||
|
fun fileChanged(file: VirtualFile, ts: Long) {
|
||||||
return lastModifiedFile
|
storage.fileChanged(file, ts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -36,7 +36,7 @@ open class GradleScriptListener(project: Project) : ScriptChangeListener(project
|
|||||||
// *.gradle.kts file was changed
|
// *.gradle.kts file was changed
|
||||||
updater.ensureUpToDatedConfigurationSuggested(file)
|
updater.ensureUpToDatedConfigurationSuggested(file)
|
||||||
}
|
}
|
||||||
project.service<GradleScriptInputsWatcher>().addToStorage(vFile)
|
project.service<GradleScriptInputsWatcher>().fileChanged(vFile, vFile.timeStamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isApplicable(vFile: VirtualFile): Boolean {
|
override fun isApplicable(vFile: VirtualFile): Boolean {
|
||||||
|
|||||||
+41
@@ -6,6 +6,7 @@
|
|||||||
package org.jetbrains.kotlin.idea.scripting.gradle
|
package org.jetbrains.kotlin.idea.scripting.gradle
|
||||||
|
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
|
import com.intellij.openapi.components.service
|
||||||
import com.intellij.psi.PsiFile
|
import com.intellij.psi.PsiFile
|
||||||
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
|
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangeListener
|
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangeListener
|
||||||
@@ -182,6 +183,46 @@ class GradleScriptInputsWatcherTest : AbstractScriptConfigurationLoadingTest() {
|
|||||||
assertConfigurationUpdateWasDone(testFiles.buildKts)
|
assertConfigurationUpdateWasDone(testFiles.buildKts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun testConfigurationUpdateAfterProjectClosing() {
|
||||||
|
assertAndLoadInitialConfiguration(testFiles.buildKts)
|
||||||
|
assertAndLoadInitialConfiguration(testFiles.settings)
|
||||||
|
|
||||||
|
changeSettingsKtsOutsideSections()
|
||||||
|
|
||||||
|
project.service<GradleScriptInputsWatcher>().clearAndRefillState()
|
||||||
|
|
||||||
|
assertConfigurationUpToDate(testFiles.settings)
|
||||||
|
assertConfigurationUpdateWasDone(testFiles.buildKts)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testConfigurationUpdateAfterProjectClosing2() {
|
||||||
|
assertAndLoadInitialConfiguration(testFiles.buildKts)
|
||||||
|
assertAndLoadInitialConfiguration(testFiles.settings)
|
||||||
|
|
||||||
|
changeSettingsKtsOutsideSections()
|
||||||
|
|
||||||
|
val ts = System.currentTimeMillis()
|
||||||
|
project.service<GradleScriptInputsWatcher>().fileChanged(testFiles.buildKts.virtualFile, ts)
|
||||||
|
project.service<GradleScriptInputsWatcher>().fileChanged(testFiles.settings.virtualFile, ts)
|
||||||
|
|
||||||
|
assertConfigurationUpdateWasDone(testFiles.settings)
|
||||||
|
assertConfigurationUpdateWasDone(testFiles.buildKts)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testConfigurationUpdateAfterProjectClosing3() {
|
||||||
|
assertAndLoadInitialConfiguration(testFiles.buildKts)
|
||||||
|
assertAndLoadInitialConfiguration(testFiles.settings)
|
||||||
|
|
||||||
|
val ts = System.currentTimeMillis()
|
||||||
|
project.service<GradleScriptInputsWatcher>().fileChanged(testFiles.buildKts.virtualFile, ts)
|
||||||
|
project.service<GradleScriptInputsWatcher>().fileChanged(testFiles.settings.virtualFile, ts)
|
||||||
|
|
||||||
|
changePropertiesFile()
|
||||||
|
|
||||||
|
assertConfigurationUpdateWasDone(testFiles.settings)
|
||||||
|
assertConfigurationUpdateWasDone(testFiles.buildKts)
|
||||||
|
}
|
||||||
|
|
||||||
fun testLoadedConfigurationWhenExternalFileChanged() {
|
fun testLoadedConfigurationWhenExternalFileChanged() {
|
||||||
assertAndLoadInitialConfiguration(testFiles.buildKts)
|
assertAndLoadInitialConfiguration(testFiles.buildKts)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user