From d3b63519990976a14514dbf251f310e112b17eda Mon Sep 17 00:00:00 2001 From: Vladislav Beskrovny Date: Fri, 17 Jul 2020 17:59:30 +0300 Subject: [PATCH] Improve update component 1. Don't make the EP disposable 2. Single focus event handler for all editors 3. Check if update is required before spawning a background task. Hence, don't spawn a background task on each focusGained or editorCreated event GitOrigin-RevId: f213d2ded31cf475428233a118e13505b38debe0 --- .../fileTypes/FileTypeStatisticProvider.java | 22 ++- .../UpdateComponentEditorListener.kt | 129 ---------------- .../fileTypes/UpdateComponentWatcher.kt | 139 ++++++++++++++++++ 3 files changed, 153 insertions(+), 137 deletions(-) delete mode 100644 platform/platform-impl/src/com/intellij/internal/statistic/fileTypes/UpdateComponentEditorListener.kt create mode 100644 platform/platform-impl/src/com/intellij/internal/statistic/fileTypes/UpdateComponentWatcher.kt diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/fileTypes/FileTypeStatisticProvider.java b/platform/platform-impl/src/com/intellij/internal/statistic/fileTypes/FileTypeStatisticProvider.java index 30c0a1f6fc27..130c8416a8b1 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/fileTypes/FileTypeStatisticProvider.java +++ b/platform/platform-impl/src/com/intellij/internal/statistic/fileTypes/FileTypeStatisticProvider.java @@ -1,22 +1,28 @@ // 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. package com.intellij.internal.statistic.fileTypes; -import com.intellij.openapi.Disposable; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.editor.event.EditorFactoryEvent; -import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.fileTypes.FileType; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -public interface FileTypeStatisticProvider extends Disposable { - ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.fileTypeStatisticProvider"); - +public interface FileTypeStatisticProvider { @NotNull String getPluginId(); - boolean accept(@NotNull EditorFactoryEvent event, @NotNull FileType fileType); + default boolean accept(@NotNull Editor editor, @NotNull FileType fileType) { + return accept(new EditorFactoryEvent(EditorFactory.getInstance(), editor), fileType); + } - @Override - default void dispose() { + /** + * @deprecated use {@link #accept(Editor, FileType)} + */ + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "2020.3") + default boolean accept(@NotNull EditorFactoryEvent event, @NotNull FileType fileType) { + return false; } } diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/fileTypes/UpdateComponentEditorListener.kt b/platform/platform-impl/src/com/intellij/internal/statistic/fileTypes/UpdateComponentEditorListener.kt deleted file mode 100644 index 097e503061f0..000000000000 --- a/platform/platform-impl/src/com/intellij/internal/statistic/fileTypes/UpdateComponentEditorListener.kt +++ /dev/null @@ -1,129 +0,0 @@ -// 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. -package com.intellij.internal.statistic.fileTypes - -import com.intellij.ide.plugins.PluginManagerCore -import com.intellij.ide.util.PropertiesComponent -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.application.PermanentInstallationID -import com.intellij.openapi.application.ex.ApplicationInfoEx -import com.intellij.openapi.diagnostic.Logger -import com.intellij.openapi.editor.event.EditorFactoryEvent -import com.intellij.openapi.editor.event.EditorFactoryListener -import com.intellij.openapi.editor.ex.EditorEx -import com.intellij.openapi.editor.ex.FocusChangeListener -import com.intellij.openapi.extensions.ExtensionPointListener -import com.intellij.openapi.extensions.PluginDescriptor -import com.intellij.openapi.extensions.PluginId -import com.intellij.openapi.fileEditor.FileDocumentManager -import com.intellij.openapi.util.Disposer -import com.intellij.openapi.util.JDOMUtil -import com.intellij.openapi.util.SystemInfo -import com.intellij.util.ObjectUtils -import com.intellij.util.io.HttpRequests -import org.jdom.JDOMException -import java.io.IOException -import java.net.URLEncoder -import java.net.UnknownHostException -import java.util.concurrent.TimeUnit - -private class UpdateComponentEditorListener : EditorFactoryListener { - - init { - FileTypeStatisticProvider.EP_NAME.addExtensionPointListener(object : ExtensionPointListener { - override fun extensionRemoved(extension: FileTypeStatisticProvider, pluginDescriptor: PluginDescriptor) { - Disposer.dispose(extension) - } - }, null) - } - - override fun editorCreated(event: EditorFactoryEvent) { - if (ApplicationManager.getApplication().isUnitTestMode) return - - val document = event.editor.document - val file = FileDocumentManager.getInstance().getFile(document) ?: return - - val fileType = file.fileType - FileTypeStatisticProvider.EP_NAME.extensionList.find { ep -> ep.accept(event, fileType) }?.let { ep -> - updateOnPooledThread(ep) - (event.editor as? EditorEx)?.addFocusListener(FocusChangeListener { - updateOnPooledThread(ep) - }, ep) - } - } - - companion object { - private val lock = ObjectUtils.sentinel("updating_monitor") - private val LOG = Logger.getInstance(UpdateComponentEditorListener::class.java) - - private fun updateOnPooledThread(ep: FileTypeStatisticProvider) = - ApplicationManager.getApplication().executeOnPooledThread { - update(ep) - } - - private fun update(ep: FileTypeStatisticProvider) { - val pluginIdString = ep.pluginId - val plugin = PluginManagerCore.getPlugin(PluginId.getId(pluginIdString)) - if (plugin == null) { - LOG.error("Unknown plugin id: $pluginIdString is reported by ${ep::class.java}") - return - } - - val pluginVersion = plugin.version - - if (!checkUpdateRequired(pluginIdString, pluginVersion)) return - val url = getUpdateUrl(pluginIdString, pluginVersion) - sendRequest(url) - } - - private fun checkUpdateRequired(pluginIdString: String, pluginVersion: String): Boolean { - synchronized(lock) { - val lastVersionKey = "$pluginIdString.LAST_VERSION" - val lastUpdateKey = "$pluginIdString.LAST_UPDATE" - - val properties = PropertiesComponent.getInstance() - val lastPluginVersion = properties.getValue(lastVersionKey) - - val lastUpdate = properties.getLong(lastUpdateKey, 0L) - val shouldUpdate = lastUpdate == 0L - || System.currentTimeMillis() - lastUpdate > TimeUnit.DAYS.toMillis(1) - || lastPluginVersion == null - || lastPluginVersion != pluginVersion - if (!shouldUpdate) return false - - properties.setValue(lastUpdateKey, System.currentTimeMillis().toString()) - properties.setValue(lastVersionKey, pluginVersion) - return true - } - } - - private fun sendRequest(url: String) { - try { - HttpRequests.request(url).connect { - try { - JDOMUtil.load(it.reader) - } - catch (e: JDOMException) { - LOG.warn(e) - } - LOG.info("updated: $url") - } - } - catch (ignored: UnknownHostException) { - // No internet connections, no need to log anything - } - catch (e: IOException) { - LOG.warn(e) - } - } - - private fun getUpdateUrl(pluginIdString: String, pluginVersion: String): String { - val applicationInfo = ApplicationInfoEx.getInstanceEx() - val buildNumber = applicationInfo.build.asString() - val os = URLEncoder.encode("${SystemInfo.OS_NAME} ${SystemInfo.OS_VERSION}", Charsets.UTF_8.name()) - val uid = PermanentInstallationID.get() - val baseUrl = "https://plugins.jetbrains.com/plugins/list" - return "$baseUrl?pluginId=$pluginIdString&build=$buildNumber&pluginVersion=$pluginVersion&os=$os&uuid=$uid" - } - } - -} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/fileTypes/UpdateComponentWatcher.kt b/platform/platform-impl/src/com/intellij/internal/statistic/fileTypes/UpdateComponentWatcher.kt new file mode 100644 index 000000000000..ac61ddf7dbee --- /dev/null +++ b/platform/platform-impl/src/com/intellij/internal/statistic/fileTypes/UpdateComponentWatcher.kt @@ -0,0 +1,139 @@ +// 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. +package com.intellij.internal.statistic.fileTypes + +import com.intellij.ide.plugins.PluginManagerCore +import com.intellij.ide.util.PropertiesComponent +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.PermanentInstallationID +import com.intellij.openapi.application.ex.ApplicationInfoEx +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.EditorFactory +import com.intellij.openapi.editor.event.EditorFactoryEvent +import com.intellij.openapi.editor.event.EditorFactoryListener +import com.intellij.openapi.editor.ex.EditorEventMulticasterEx +import com.intellij.openapi.editor.ex.EditorEx +import com.intellij.openapi.editor.ex.FocusChangeListener +import com.intellij.openapi.extensions.ExtensionPointName +import com.intellij.openapi.extensions.PluginId +import com.intellij.openapi.util.JDOMUtil +import com.intellij.openapi.util.SystemInfo +import com.intellij.util.ObjectUtils +import com.intellij.util.io.HttpRequests +import org.jdom.JDOMException +import java.io.IOException +import java.net.URLEncoder +import java.net.UnknownHostException +import java.util.* +import java.util.concurrent.TimeUnit + +private val EP_NAME = ExtensionPointName("com.intellij.fileTypeStatisticProvider") + +@Service +private class UpdateComponentWatcher : Disposable { + + init { + // rely on fact that service will be initialized only once + // TODO: Use message bus once it will be provided by platform + val multicaster = EditorFactory.getInstance().eventMulticaster + if (multicaster is EditorEventMulticasterEx) { + multicaster.addFocusChangeListener(FocusChangeListener { editor -> scheduleUpdate(editor) }, this) + } + } + + fun scheduleUpdate(editor: Editor) { + val fileType = (editor as EditorEx).virtualFile?.fileType ?: return + + // TODO: `accept` can be slow (CloudFormationFileTypeStatisticProvider), we should not call it synchronously + val ep = EP_NAME.findFirstSafe { it.accept(editor, fileType) } ?: return + + // TODO: Use PluginAware EP + val plugin = EP_NAME.computeIfAbsent(ep, UpdateComponentWatcher::class.java) { + Optional.ofNullable(PluginManagerCore.getPlugin(PluginId.getId(ep.pluginId))) + } + val pluginIdString = ep.pluginId + if (!plugin.isPresent) { + LOG.error("Unknown plugin id: $pluginIdString is reported by ${ep::class.java}") + return + } + + val pluginVersion = plugin.get().version + if (checkUpdateRequired(pluginIdString, pluginVersion)) { + ApplicationManager.getApplication().executeOnPooledThread { + update(pluginIdString, pluginVersion) + } + } + } + + override fun dispose() { + } +} + +private class UpdateComponentEditorListener : EditorFactoryListener { + override fun editorCreated(event: EditorFactoryEvent) { + if (!ApplicationManager.getApplication().isUnitTestMode) { + service().scheduleUpdate(event.editor) + } + } +} + +private val lock = ObjectUtils.sentinel("updating_monitor") +private val LOG = logger() + +private fun update(pluginIdString: String, pluginVersion: String) { + val url = getUpdateUrl(pluginIdString, pluginVersion) + sendRequest(url) +} + +private fun checkUpdateRequired(pluginIdString: String, pluginVersion: String): Boolean { + synchronized(lock) { + val lastVersionKey = "$pluginIdString.LAST_VERSION" + val lastUpdateKey = "$pluginIdString.LAST_UPDATE" + + val properties = PropertiesComponent.getInstance() + val lastPluginVersion = properties.getValue(lastVersionKey) + + val lastUpdate = properties.getLong(lastUpdateKey, 0L) + val shouldUpdate = lastUpdate == 0L + || System.currentTimeMillis() - lastUpdate > TimeUnit.DAYS.toMillis(1) + || lastPluginVersion == null + || lastPluginVersion != pluginVersion + if (!shouldUpdate) return false + + properties.setValue(lastUpdateKey, System.currentTimeMillis().toString()) + properties.setValue(lastVersionKey, pluginVersion) + return true + } +} + +private fun sendRequest(url: String) { + try { + HttpRequests.request(url).connect { + try { + JDOMUtil.load(it.reader) + } + catch (e: JDOMException) { + LOG.warn(e) + } + LOG.info("updated: $url") + } + } + catch (ignored: UnknownHostException) { + // No internet connections, no need to log anything + } + catch (e: IOException) { + LOG.warn(e) + } +} + +private fun getUpdateUrl(pluginIdString: String, pluginVersion: String): String { + val applicationInfo = ApplicationInfoEx.getInstanceEx() + val buildNumber = applicationInfo.build.asString() + val os = URLEncoder.encode("${SystemInfo.OS_NAME} ${SystemInfo.OS_VERSION}", Charsets.UTF_8.name()) + val uid = PermanentInstallationID.get() + val baseUrl = "https://plugins.jetbrains.com/plugins/list" + return "$baseUrl?pluginId=$pluginIdString&build=$buildNumber&pluginVersion=$pluginVersion&os=$os&uuid=$uid" +} \ No newline at end of file