Implement persistent storage for time stamps of files that affect build script configuration (KT-3556)

^KT-3556 Fixed
This commit is contained in:
Natalia Selezneva
2019-12-27 15:52:53 +03:00
parent ecb05ace91
commit 3dfbd824e4
4 changed files with 178 additions and 47 deletions
@@ -5,18 +5,23 @@
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.project.Project
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.PathUtil
import org.jetbrains.annotations.TestOnly
import org.jetbrains.plugins.gradle.settings.GradleLocalSettings
import java.nio.file.Paths
import java.util.*
class GradleScriptInputsWatcher(val project: Project) {
private val comparator = Comparator<VirtualFile> { f1, f2 -> (f1.timeStamp - f2.timeStamp).toInt() }
private val storage = TreeSet(comparator)
@State(
name = "KotlinBuildScriptsModificationInfo",
storages = [Storage(StoragePathMacros.CACHE_FILE)]
)
class GradleScriptInputsWatcher(val project: Project) : PersistentStateComponent<GradleScriptInputsWatcher.Storage> {
private var storage = Storage()
init {
initStorage(project)
@@ -29,38 +34,78 @@ class GradleScriptInputsWatcher(val project: Project) {
if (file != null && !file.isDirectory) {
val calculateCrc = ConfigurationFileCrcFactory(file).create()
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 {
return lastModifiedFileTimeStamp(file) <= timeStamp
return lastModifiedFileTimeStamp(file) < timeStamp
}
fun addToStorage(file: VirtualFile) {
if (storage.contains(file)) {
storage.remove(file)
class Storage(
private var lastModifiedTS: Long = 0,
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? {
if (storage.isEmpty()) return null
override fun getState(): Storage {
return storage
}
val iterator = storage.descendingIterator()
if (!iterator.hasNext()) return null
override fun loadState(state: Storage) {
this.storage = state
}
var lastModifiedFile = iterator.next()
while (lastModifiedFile == file && iterator.hasNext()) {
lastModifiedFile = iterator.next()
}
@TestOnly
fun clearAndRefillState() {
loadState(project.service<GradleScriptInputsWatcher>().state)
}
if (lastModifiedFile == file) return null
return lastModifiedFile
@TestOnly
fun fileChanged(file: VirtualFile, ts: Long) {
storage.fileChanged(file, ts)
}
}
@@ -5,18 +5,23 @@
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.project.Project
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.PathUtil
import org.jetbrains.annotations.TestOnly
import org.jetbrains.plugins.gradle.settings.GradleLocalSettings
import java.nio.file.Paths
import java.util.*
class GradleScriptInputsWatcher(val project: Project) {
private val comparator = Comparator<VirtualFile> { f1, f2 -> (f1.timeStamp - f2.timeStamp).toInt() }
private val storage = TreeSet(comparator)
@State(
name = "KotlinBuildScriptsModificationInfo",
storages = [Storage(StoragePathMacros.CACHE_FILE)]
)
class GradleScriptInputsWatcher(val project: Project) : PersistentStateComponent<GradleScriptInputsWatcher.Storage> {
private var storage = Storage()
init {
initStorage(project)
@@ -29,38 +34,78 @@ class GradleScriptInputsWatcher(val project: Project) {
if (file != null && !file.isDirectory) {
val calculateCrc = ConfigurationFileCrcFactory(project, file).create()
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 {
return lastModifiedFileTimeStamp(file) <= timeStamp
return lastModifiedFileTimeStamp(file) < timeStamp
}
fun addToStorage(file: VirtualFile) {
if (storage.contains(file)) {
storage.remove(file)
class Storage(
private var lastModifiedTS: Long = 0,
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? {
if (storage.isEmpty()) return null
override fun getState(): Storage {
return storage
}
val iterator = storage.descendingIterator()
if (!iterator.hasNext()) return null
override fun loadState(state: Storage) {
this.storage = state
}
var lastModifiedFile = iterator.next()
while (lastModifiedFile == file && iterator.hasNext()) {
lastModifiedFile = iterator.next()
}
@TestOnly
fun clearAndRefillState() {
loadState(project.service<GradleScriptInputsWatcher>().state)
}
if (lastModifiedFile == file) return null
return lastModifiedFile
@TestOnly
fun fileChanged(file: VirtualFile, ts: Long) {
storage.fileChanged(file, ts)
}
}
@@ -36,7 +36,7 @@ open class GradleScriptListener(project: Project) : ScriptChangeListener(project
// *.gradle.kts file was changed
updater.ensureUpToDatedConfigurationSuggested(file)
}
project.service<GradleScriptInputsWatcher>().addToStorage(vFile)
project.service<GradleScriptInputsWatcher>().fileChanged(vFile, vFile.timeStamp)
}
override fun isApplicable(vFile: VirtualFile): Boolean {
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.idea.scripting.gradle
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.service
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
import org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangeListener
@@ -182,6 +183,46 @@ class GradleScriptInputsWatcherTest : AbstractScriptConfigurationLoadingTest() {
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() {
assertAndLoadInitialConfiguration(testFiles.buildKts)