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
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
+68
-23
@@ -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)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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 {
|
||||
|
||||
+41
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user