mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-16 14:23:28 +07:00
IJPL-175691 FUS: Detect and log presence of top public plugins in vscode
IJ-CR-154139 (cherry picked from commit 66832c04b70897ba41c125ae8bbd4fd0f97d1c0b) GitOrigin-RevId: be433850b16169c0fed170e431a5144fb8078e8b
This commit is contained in:
committed by
intellij-monorepo-bot
parent
148d3fce6e
commit
d302ab9c34
@@ -18,7 +18,7 @@ private const val WINDSURF_ID = ".windsurf"
|
||||
private const val ECLIPSE_ID = ".eclipse"
|
||||
|
||||
internal class EditorsCollector : ApplicationUsagesCollector() {
|
||||
private val EDITORS_GROUP: EventLogGroup = EventLogGroup("editors", 2)
|
||||
private val EDITORS_GROUP: EventLogGroup = EventLogGroup("editors", 3)
|
||||
|
||||
override fun getGroup(): EventLogGroup = EDITORS_GROUP
|
||||
|
||||
@@ -35,6 +35,15 @@ internal class EditorsCollector : ApplicationUsagesCollector() {
|
||||
EventFields.String("config", CONFIGS)
|
||||
)
|
||||
|
||||
private val IS_VSCODE_USED_RECENTLY: EventId1<Boolean> = EDITORS_GROUP.registerEvent(
|
||||
"vscode.used.recently",
|
||||
EventFields.Boolean("is_vscode_used_recently"))
|
||||
|
||||
private val VS_CODE_EXTENSION_INSTALLED: EventId1<List<String>> = EDITORS_GROUP.registerEvent(
|
||||
"vscode.extension.installed",
|
||||
EventFields.StringList("extension_ids", emptyList())
|
||||
)
|
||||
|
||||
override suspend fun getMetricsAsync(): Set<MetricEvent> {
|
||||
val homeDir = System.getProperty("user.home")
|
||||
return withContext(Dispatchers.IO) {
|
||||
@@ -47,7 +56,16 @@ internal class EditorsCollector : ApplicationUsagesCollector() {
|
||||
add(CONFIG_EXISTS.metric(VIMRC_ID))
|
||||
}
|
||||
|
||||
if (Files.isDirectory(Paths.get(homeDir, ".vscode"))) {
|
||||
val vsCodeCollectionDataProvider = VSCodeCollectionDataProvider()
|
||||
if (vsCodeCollectionDataProvider.isVSCodeDetected()) {
|
||||
val isVSCodeUsedRecently = vsCodeCollectionDataProvider.isVSCodeUsedRecently()
|
||||
isVSCodeUsedRecently?.let {
|
||||
add(IS_VSCODE_USED_RECENTLY.metric(it))
|
||||
}
|
||||
|
||||
if (vsCodeCollectionDataProvider.isVSCodePluginsProcessingPossible()) {
|
||||
add(VS_CODE_EXTENSION_INSTALLED.metric(vsCodeCollectionDataProvider.getVSCodePluginsIds()))
|
||||
}
|
||||
add(CONFIG_EXISTS.metric(VSCODE_ID))
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.internal.statistic.collectors.fus.environment
|
||||
|
||||
import java.nio.file.InvalidPathException
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
internal abstract class ExternalEditorCollectionDataProvider {
|
||||
protected val homeDirectory: Path? = try {
|
||||
Paths.get(System.getProperty("user.home"))
|
||||
}
|
||||
catch (_: InvalidPathException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.internal.statistic.collectors.fus.environment
|
||||
|
||||
import com.intellij.openapi.diagnostic.debug
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.util.SystemInfo.isMac
|
||||
import com.intellij.openapi.util.SystemInfo.isWindows
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import java.io.IOException
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.InvalidPathException
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import kotlin.io.path.*
|
||||
|
||||
private const val VSCODE_PLUGINS_IDENTIFICATION_TAG = "identifier"
|
||||
private const val VSCODE_PLUGINS_ID_TAG = "id"
|
||||
private val logger = logger<VSCodeCollectionDataProvider>()
|
||||
|
||||
internal class VSCodeCollectionDataProvider : ExternalEditorCollectionDataProvider() {
|
||||
|
||||
private val vsCodeHomePath: Path? = when {
|
||||
isMac -> homeDirectory?.resolve(Paths.get("Library", "Application Support", "Code"))
|
||||
isWindows -> getWindowsVSCodeHomePath()
|
||||
else -> homeDirectory?.resolve(Paths.get(".config", "Code"))
|
||||
}
|
||||
|
||||
private val pluginsDirectoryPath = homeDirectory?.resolve(Paths.get(".vscode", "extensions"))
|
||||
private val pluginsConfigPath = pluginsDirectoryPath?.resolve("extensions.json")
|
||||
private val databasePath = vsCodeHomePath?.resolve(Paths.get("User", "globalStorage", "state.vscdb"))
|
||||
|
||||
init {
|
||||
logger.debug { "VSCode home path: $vsCodeHomePath" }
|
||||
logger.debug { "VSCode plugins directory path: $pluginsDirectoryPath" }
|
||||
logger.debug { "VSCode plugins config path: $pluginsConfigPath" }
|
||||
logger.debug { "VSCode database path: $databasePath" }
|
||||
}
|
||||
|
||||
private val maxTimeSinceLastModificationToBeRecent = Duration.ofHours(30 * 24) // 30 days
|
||||
|
||||
fun isVSCodeDetected(): Boolean {
|
||||
if (homeDirectory == null) {
|
||||
logger.debug { "VSCode is not detected - home directory is null" }
|
||||
return false
|
||||
}
|
||||
|
||||
val vsCodeConfigDir = homeDirectory.resolve(".vscode")
|
||||
|
||||
return try {
|
||||
val isVsCodeConfigDirValid = Files.isDirectory(vsCodeConfigDir)
|
||||
logger.debug { "Is $vsCodeConfigDir a valid directory: $isVsCodeConfigDirValid" }
|
||||
isVsCodeConfigDirValid
|
||||
}
|
||||
catch (e: SecurityException) {
|
||||
logger.debug(e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun isVSCodeUsedRecently(): Boolean? {
|
||||
return try {
|
||||
if (databasePath?.exists() == true) {
|
||||
val time = Files.getLastModifiedTime(databasePath)
|
||||
val isVSCodeUsedRecently = time.toInstant() > Instant.now() - maxTimeSinceLastModificationToBeRecent
|
||||
logger.debug { "Is VSCode used recently: $isVSCodeUsedRecently" }
|
||||
isVSCodeUsedRecently
|
||||
}
|
||||
|
||||
logger.debug { "VSCode is not used recently - database path doesn't exist" }
|
||||
null
|
||||
}
|
||||
catch (e: IOException) {
|
||||
logger.debug(e)
|
||||
null
|
||||
}
|
||||
catch (e: SecurityException) {
|
||||
logger.debug(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun isVSCodePluginsProcessingPossible(): Boolean {
|
||||
return try {
|
||||
val isVSCodePluginDirExists = pluginsDirectoryPath?.exists() == true
|
||||
val isVSCodeConfigDirValid = pluginsDirectoryPath?.isDirectory() == true
|
||||
val isVSCodeConfigFilePathExists = pluginsConfigPath?.exists() == true
|
||||
val isVSCodeConfigFilePathValid = pluginsConfigPath?.isRegularFile() == true
|
||||
val isVSCodeConfigFilePathReadable = pluginsConfigPath?.isReadable() == true
|
||||
|
||||
logger.debug {
|
||||
"isVSCodePluginDirExists = $isVSCodePluginDirExists\n" +
|
||||
"isVSCodeConfigDirValid = $isVSCodeConfigDirValid\n" +
|
||||
"isVSCodeConfigFilePathExists = $isVSCodeConfigFilePathExists\n" +
|
||||
"isVSCodeConfigFilePathValid = $isVSCodeConfigFilePathValid\n" +
|
||||
"isVSCodeConfigFilePathReadable = $isVSCodeConfigFilePathReadable"
|
||||
}
|
||||
|
||||
isVSCodePluginDirExists && isVSCodeConfigDirValid &&
|
||||
isVSCodeConfigFilePathExists && isVSCodeConfigFilePathValid &&
|
||||
isVSCodeConfigFilePathReadable
|
||||
}
|
||||
catch (e: SecurityException) {
|
||||
logger.debug(e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun getVSCodePluginsIds(): List<String> {
|
||||
val pluginsConfigData = pluginsConfigPath?.readText() ?: return emptyList()
|
||||
|
||||
val parsedJson = try {
|
||||
Json.parseToJsonElement(pluginsConfigData)
|
||||
}
|
||||
catch (e: SerializationException) {
|
||||
logger.debug(e)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val pluginIdsList = mutableListOf<String>()
|
||||
|
||||
try {
|
||||
for (pluginElement in parsedJson.jsonArray) {
|
||||
val pluginInfoObject = pluginElement.jsonObject
|
||||
pluginInfoObject[VSCODE_PLUGINS_IDENTIFICATION_TAG]?.jsonObject?.get(VSCODE_PLUGINS_ID_TAG)?.let {
|
||||
pluginIdsList.add(it.jsonPrimitive.content)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e: IllegalArgumentException) {
|
||||
logger.debug(e)
|
||||
return pluginIdsList
|
||||
}
|
||||
|
||||
logger.debug { "Detected VSCode plugins: ${pluginIdsList.joinToString(",")}}" }
|
||||
return pluginIdsList
|
||||
}
|
||||
|
||||
private fun getWindowsVSCodeHomePath(): Path? {
|
||||
return try {
|
||||
val appDataValue = Paths.get(getWindowsEnvVariableValue("APPDATA"), "Code")
|
||||
logger.debug { "Detected APPDATA env var value: $appDataValue" }
|
||||
appDataValue
|
||||
}
|
||||
catch (e: InvalidPathException) {
|
||||
logger.debug(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to get one Windows env var.
|
||||
*
|
||||
* Use without %%. Example: get("APPDATA")
|
||||
*
|
||||
* @return env var value or "null" string if not found
|
||||
*/
|
||||
private fun getWindowsEnvVariableValue(variable: String): String {
|
||||
val envMap = try {
|
||||
System.getenv()
|
||||
}
|
||||
catch (e: SecurityException) {
|
||||
logger.debug(e)
|
||||
return "null"
|
||||
}
|
||||
|
||||
val varValue = envMap[variable] ?: envMap[variable.uppercase(Locale.getDefault())].toString()
|
||||
logger.debug { "Detected system env var value - $variable: $varValue" }
|
||||
return varValue
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user