mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-02-04 23:39:07 +07:00
IDEA-291623 Show a warning if the settings pack exceeds some reasonably big limit
GitOrigin-RevId: 75a34604baff0474b930c5f88c5ea61cc3539976
This commit is contained in:
committed by
intellij-monorepo-bot
parent
b20b0dcb54
commit
39672a62c5
@@ -83,5 +83,6 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.workspace.jps" />
|
||||
<orderEntry type="module" module-name="intellij.platform.testFramework.junit5" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.performanceTesting" />
|
||||
<orderEntry type="library" scope="TEST" name="mockito" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -21,6 +21,8 @@
|
||||
<applicationService serviceImplementation="com.intellij.settingsSync.SettingsSyncSettings"/>
|
||||
<applicationService serviceImplementation="com.intellij.settingsSync.SettingsSyncEvents"/>
|
||||
<applicationService serviceImplementation="com.intellij.settingsSync.plugins.SettingsSyncPluginManager"/>
|
||||
<applicationService serviceImplementation="com.intellij.settingsSync.notification.NotificationServiceImpl"
|
||||
serviceInterface="com.intellij.settingsSync.notification.NotificationService"/>
|
||||
<!--suppress PluginXmlDynamicPlugin -->
|
||||
<applicationInitializedListener implementation="com.intellij.settingsSync.SettingsSynchronizer"/>
|
||||
<applicationConfigurable id="settings.sync"
|
||||
|
||||
@@ -85,6 +85,9 @@ sync.status.login.not.available=To enable Setting Sync, install
|
||||
sync.status.restart.required=To enable Setting Sync, restart {0}
|
||||
sync.status.restart.ide.button=Restart
|
||||
|
||||
sync.notification.size.exceed.title=Settings pack exceeds maximum allowed limit
|
||||
sync.notification.size.exceed.text=Please reach out to us at https://youtrack.jetbrains.com/ and attach your logs and settings to address the issue
|
||||
|
||||
settings.cross.product.sync=Sync settings across:
|
||||
# {0} is the current product name, e.g. 'IntelliJ IDEA Community Edition' or 'WebStorm'
|
||||
settings.cross.product.sync.choice.only.this.product={0} instances only
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.util.JDOMUtil
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.settingsSync.auth.SettingsSyncAuthService
|
||||
import com.intellij.settingsSync.notification.NotificationService
|
||||
import com.intellij.util.io.HttpRequests
|
||||
import com.intellij.util.io.delete
|
||||
import com.intellij.util.io.inputStream
|
||||
@@ -188,6 +189,11 @@ internal open class CloudConfigServerCommunicator : SettingsSyncRemoteCommunicat
|
||||
val zip = try {
|
||||
SettingsSnapshotZipSerializer.serializeToZip(snapshot)
|
||||
}
|
||||
catch (e: SettingsSnapshotZipSerializer.ZipSizeExceedException) {
|
||||
LOG.warn(e)
|
||||
NotificationService.getInstance().notifyZipSizeExceed()
|
||||
return SettingsSyncPushResult.Error(e.message ?: "Couldn't prepare zip file")
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
LOG.warn(e)
|
||||
return SettingsSyncPushResult.Error(e.message ?: "Couldn't prepare zip file")
|
||||
|
||||
@@ -7,10 +7,10 @@ import com.intellij.openapi.util.BuildNumber
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.settingsSync.plugins.SettingsSyncPluginsState
|
||||
import com.intellij.util.io.*
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.OutputStream
|
||||
import java.lang.RuntimeException
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.time.Instant
|
||||
@@ -27,6 +27,8 @@ internal object SettingsSnapshotZipSerializer {
|
||||
private const val INFO = "info.json"
|
||||
private const val PLUGINS = "plugins.json"
|
||||
|
||||
private const val MAX_ZIP_SIZE = 524288 // bytes
|
||||
|
||||
private val LOG = logger<SettingsSnapshotZipSerializer>()
|
||||
|
||||
private val json = Json { prettyPrint = true }
|
||||
@@ -34,6 +36,9 @@ internal object SettingsSnapshotZipSerializer {
|
||||
fun serializeToZip(snapshot: SettingsSnapshot): Path {
|
||||
val file = FileUtil.createTempFile(SETTINGS_SYNC_SNAPSHOT_ZIP, null)
|
||||
serialize(snapshot, Compressor.Zip(file))
|
||||
if (file.length() > MAX_ZIP_SIZE) {
|
||||
throw ZipSizeExceedException(file.length())
|
||||
}
|
||||
return file.toPath()
|
||||
}
|
||||
|
||||
@@ -183,4 +188,10 @@ internal object SettingsSnapshotZipSerializer {
|
||||
var configFolder: String = ""
|
||||
var isDeleted: Boolean = false
|
||||
}
|
||||
|
||||
class ZipSizeExceedException(private val size: Long): RuntimeException() {
|
||||
override fun toString(): String {
|
||||
return "Zip size $size excesses maximum allowed zip size"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,7 @@ import com.intellij.concurrency.ConcurrentCollectionFactory
|
||||
import com.intellij.configurationStore.*
|
||||
import com.intellij.configurationStore.schemeManager.SchemeManagerFactoryBase
|
||||
import com.intellij.configurationStore.schemeManager.SchemeManagerImpl
|
||||
import com.intellij.notification.Notification
|
||||
import com.intellij.notification.NotificationAction
|
||||
import com.intellij.notification.NotificationGroupManager
|
||||
import com.intellij.notification.NotificationType
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.ApplicationNamesInfo
|
||||
import com.intellij.openapi.application.PathManager.OPTIONS_DIRECTORY
|
||||
import com.intellij.openapi.application.ex.ApplicationEx
|
||||
import com.intellij.openapi.application.invokeAndWaitIfNeeded
|
||||
import com.intellij.openapi.components.RoamingType
|
||||
import com.intellij.openapi.components.StateStorage
|
||||
@@ -21,11 +14,10 @@ import com.intellij.openapi.editor.colors.EditorColorsManager
|
||||
import com.intellij.openapi.options.SchemeManagerFactory
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.settingsSync.SettingsSnapshot.MetaInfo
|
||||
import com.intellij.settingsSync.notification.NotificationService
|
||||
import com.intellij.settingsSync.plugins.SettingsSyncPluginManager
|
||||
import com.intellij.util.io.*
|
||||
import org.jdom.Element
|
||||
import java.io.InputStream
|
||||
import java.lang.RuntimeException
|
||||
import java.nio.file.FileVisitResult
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
@@ -35,8 +27,6 @@ import java.time.Instant
|
||||
import java.util.concurrent.locks.ReadWriteLock
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import java.util.function.Predicate
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.LinkedHashSet
|
||||
import kotlin.concurrent.withLock
|
||||
import kotlin.io.path.exists
|
||||
import kotlin.io.path.name
|
||||
@@ -105,43 +95,9 @@ internal class SettingsSyncIdeMediatorImpl(private val componentStore: Component
|
||||
notifyRestartNeeded()
|
||||
}
|
||||
|
||||
|
||||
private fun notifyRestartNeeded() {
|
||||
if (restartRequiredReasons.isEmpty())
|
||||
return
|
||||
val notification = buildRestartNeededNotification()
|
||||
notification.addAction(NotificationAction.create(
|
||||
SettingsSyncBundle.message("sync.restart.notification.action", ApplicationNamesInfo.getInstance().fullProductName),
|
||||
com.intellij.util.Consumer {
|
||||
val app = ApplicationManager.getApplication() as ApplicationEx
|
||||
app.restart(true)
|
||||
}))
|
||||
notification.notify(null)
|
||||
}
|
||||
|
||||
private fun buildRestartNeededNotification(): Notification {
|
||||
fun getMultiReasonRestartMessage(): String {
|
||||
assert(restartRequiredReasons.size > 1)
|
||||
val message = StringBuilder(SettingsSyncBundle.message("sync.restart.notification.message.subtitle")).append('\n')
|
||||
|
||||
val sortedRestartReasons = restartRequiredReasons.sorted()
|
||||
for ((counter, reason) in sortedRestartReasons.withIndex()) {
|
||||
message.append(reason.getMultiReasonNotificationListEntry(counter + 1))
|
||||
}
|
||||
|
||||
message.dropLast(0) // we do not need the new line in the end
|
||||
return message.toString()
|
||||
}
|
||||
|
||||
val message = when {
|
||||
restartRequiredReasons.isEmpty() -> throw RuntimeException("No restart reasons found")
|
||||
restartRequiredReasons.size == 1 -> restartRequiredReasons.first().getSingleReasonNotificationMessage()
|
||||
else -> getMultiReasonRestartMessage()
|
||||
}
|
||||
return NotificationGroupManager.getInstance().getNotificationGroup(NOTIFICATION_GROUP)
|
||||
.createNotification(SettingsSyncBundle.message("sync.restart.notification.title"),
|
||||
message,
|
||||
NotificationType.INFORMATION)
|
||||
if (restartRequiredReasons.isEmpty()) return
|
||||
NotificationService.getInstance().notifyRestartNeeded(restartRequiredReasons)
|
||||
}
|
||||
|
||||
override fun activateStreamProvider() {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.intellij.settingsSync.notification
|
||||
|
||||
import com.intellij.notification.Notification
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.settingsSync.RestartReason
|
||||
|
||||
interface NotificationService {
|
||||
companion object {
|
||||
fun getInstance(): NotificationService = ApplicationManager.getApplication().getService(NotificationService::class.java)
|
||||
}
|
||||
|
||||
fun notifyZipSizeExceed()
|
||||
fun buildZipSizeExceedNotification(): Notification
|
||||
fun notifyRestartNeeded(reasons: Collection<RestartReason>)
|
||||
fun buildRestartNeededNotification(reasons: Collection<RestartReason>): Notification
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.intellij.settingsSync.notification
|
||||
|
||||
import com.intellij.notification.Notification
|
||||
import com.intellij.notification.NotificationAction
|
||||
import com.intellij.notification.NotificationGroupManager
|
||||
import com.intellij.notification.NotificationType
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.ApplicationNamesInfo
|
||||
import com.intellij.openapi.application.ex.ApplicationEx
|
||||
import com.intellij.settingsSync.NOTIFICATION_GROUP
|
||||
import com.intellij.settingsSync.RestartReason
|
||||
import com.intellij.settingsSync.SettingsSyncBundle
|
||||
import java.lang.RuntimeException
|
||||
|
||||
internal class NotificationServiceImpl: NotificationService {
|
||||
override fun notifyZipSizeExceed() {
|
||||
val notification = buildZipSizeExceedNotification()
|
||||
notification.notify(null)
|
||||
}
|
||||
|
||||
override fun buildZipSizeExceedNotification(): Notification {
|
||||
return NotificationGroupManager.getInstance().getNotificationGroup(NOTIFICATION_GROUP)
|
||||
.createNotification(SettingsSyncBundle.message("sync.notification.size.exceed.title"),
|
||||
SettingsSyncBundle.message("sync.notification.size.exceed.text"),
|
||||
NotificationType.ERROR)
|
||||
}
|
||||
|
||||
override fun notifyRestartNeeded(reasons: Collection<RestartReason>) {
|
||||
val notification = buildRestartNeededNotification(reasons)
|
||||
notification.addAction(NotificationAction.create(
|
||||
SettingsSyncBundle.message("sync.restart.notification.action", ApplicationNamesInfo.getInstance().fullProductName),
|
||||
com.intellij.util.Consumer {
|
||||
val app = ApplicationManager.getApplication() as ApplicationEx
|
||||
app.restart(true)
|
||||
}))
|
||||
notification.notify(null)
|
||||
}
|
||||
|
||||
override fun buildRestartNeededNotification(reasons: Collection<RestartReason>): Notification {
|
||||
fun getMultiReasonRestartMessage(): String {
|
||||
assert(reasons.size > 1)
|
||||
val message = StringBuilder(SettingsSyncBundle.message("sync.restart.notification.message.subtitle")).append('\n')
|
||||
|
||||
val sortedRestartReasons = reasons.sorted()
|
||||
for ((counter, reason) in sortedRestartReasons.withIndex()) {
|
||||
message.append(reason.getMultiReasonNotificationListEntry(counter + 1))
|
||||
}
|
||||
|
||||
message.dropLast(0) // we do not need the new line in the end
|
||||
return message.toString()
|
||||
}
|
||||
|
||||
val message = when {
|
||||
reasons.isEmpty() -> throw RuntimeException("No restart reasons provided")
|
||||
reasons.size == 1 -> reasons.first().getSingleReasonNotificationMessage()
|
||||
else -> getMultiReasonRestartMessage()
|
||||
}
|
||||
return NotificationGroupManager.getInstance().getNotificationGroup(NOTIFICATION_GROUP)
|
||||
.createNotification(SettingsSyncBundle.message("sync.restart.notification.title"),
|
||||
message,
|
||||
NotificationType.INFORMATION)
|
||||
}
|
||||
}
|
||||
@@ -3,24 +3,36 @@ package com.intellij.settingsSync
|
||||
import com.intellij.configurationStore.getPerOsSettingsStorageFolderName
|
||||
import com.intellij.ide.GeneralSettings
|
||||
import com.intellij.ide.ui.UISettings
|
||||
import com.intellij.idea.TestFor
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.*
|
||||
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable
|
||||
import com.intellij.openapi.keymap.impl.KeymapImpl
|
||||
import com.intellij.openapi.keymap.impl.KeymapManagerImpl
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.settingsSync.SettingsSnapshot.MetaInfo
|
||||
import com.intellij.settingsSync.notification.NotificationService
|
||||
import com.intellij.settingsSync.notification.NotificationServiceImpl
|
||||
import com.intellij.testFramework.replaceService
|
||||
import com.intellij.util.io.readText
|
||||
import com.intellij.util.toByteArray
|
||||
import com.intellij.util.xmlb.annotations.Attribute
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.verify
|
||||
import java.nio.charset.Charset
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import kotlin.io.path.div
|
||||
import kotlin.io.path.exists
|
||||
|
||||
@@ -287,8 +299,8 @@ internal class SettingsSyncRealIdeTest : SettingsSyncRealIdeTestBase() {
|
||||
//assertTrue("Didn't await for the push request", cdl.await(5, TIMEOUT_UNIT))
|
||||
}
|
||||
|
||||
// temporarily disabled: the failure needs to be investigated
|
||||
//@Test
|
||||
@Test
|
||||
@Ignore // TODO investigate
|
||||
fun `local and remote changes in different files are both applied`() {
|
||||
val generalSettings = GeneralSettings.getInstance().init()
|
||||
initSettingsSync(SettingsSyncBridge.InitMode.JustInit)
|
||||
@@ -331,6 +343,20 @@ internal class SettingsSyncRealIdeTest : SettingsSyncRealIdeTestBase() {
|
||||
assertFalse(EditorSettingsExternalizable.getInstance().isShowIntentionBulb)
|
||||
}
|
||||
|
||||
@TestFor(issues = ["IDEA-291623"])
|
||||
@Test
|
||||
fun `zip file size limit exceed`() {
|
||||
val notificationServiceSpy = Mockito.spy<NotificationServiceImpl>()
|
||||
ApplicationManager.getApplication().replaceService(NotificationService::class.java, notificationServiceSpy, disposable)
|
||||
|
||||
EditorSettingsExternalizable.getInstance().initModifyAndSave {
|
||||
languageBreadcrumbsMap = (1..100000).associate { UUID.randomUUID().toString() to true } // please FIXME if you now a better way to make a fat file
|
||||
}
|
||||
initSettingsSync(SettingsSyncBridge.InitMode.JustInit)
|
||||
|
||||
verify(notificationServiceSpy).notifyZipSizeExceed()
|
||||
}
|
||||
|
||||
//@Test
|
||||
fun `only changed components should be reloaded`() {
|
||||
TODO()
|
||||
|
||||
Reference in New Issue
Block a user