diff --git a/plugins/settings-sync/intellij.settingsSync.iml b/plugins/settings-sync/intellij.settingsSync.iml index 5e21574e356f..c8ebfa737c40 100644 --- a/plugins/settings-sync/intellij.settingsSync.iml +++ b/plugins/settings-sync/intellij.settingsSync.iml @@ -83,5 +83,6 @@ + \ No newline at end of file diff --git a/plugins/settings-sync/resources/META-INF/plugin.xml b/plugins/settings-sync/resources/META-INF/plugin.xml index f97c5e340b5a..f4edd67460b6 100644 --- a/plugins/settings-sync/resources/META-INF/plugin.xml +++ b/plugins/settings-sync/resources/META-INF/plugin.xml @@ -21,6 +21,8 @@ + () 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" + } + } } \ No newline at end of file diff --git a/plugins/settings-sync/src/com/intellij/settingsSync/SettingsSyncIdeMediatorImpl.kt b/plugins/settings-sync/src/com/intellij/settingsSync/SettingsSyncIdeMediatorImpl.kt index 31e5b9068d6c..34b1be1ce15f 100644 --- a/plugins/settings-sync/src/com/intellij/settingsSync/SettingsSyncIdeMediatorImpl.kt +++ b/plugins/settings-sync/src/com/intellij/settingsSync/SettingsSyncIdeMediatorImpl.kt @@ -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() { diff --git a/plugins/settings-sync/src/com/intellij/settingsSync/notification/NotificationService.kt b/plugins/settings-sync/src/com/intellij/settingsSync/notification/NotificationService.kt new file mode 100644 index 000000000000..f773e7e1cd0e --- /dev/null +++ b/plugins/settings-sync/src/com/intellij/settingsSync/notification/NotificationService.kt @@ -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) + fun buildRestartNeededNotification(reasons: Collection): Notification +} \ No newline at end of file diff --git a/plugins/settings-sync/src/com/intellij/settingsSync/notification/NotificationServiceImpl.kt b/plugins/settings-sync/src/com/intellij/settingsSync/notification/NotificationServiceImpl.kt new file mode 100644 index 000000000000..747fef4c67d0 --- /dev/null +++ b/plugins/settings-sync/src/com/intellij/settingsSync/notification/NotificationServiceImpl.kt @@ -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) { + 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): 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) + } +} \ No newline at end of file diff --git a/plugins/settings-sync/tests/com/intellij/settingsSync/SettingsSyncRealIdeTest.kt b/plugins/settings-sync/tests/com/intellij/settingsSync/SettingsSyncRealIdeTest.kt index 60557cd2d2bc..46cc01ff876f 100644 --- a/plugins/settings-sync/tests/com/intellij/settingsSync/SettingsSyncRealIdeTest.kt +++ b/plugins/settings-sync/tests/com/intellij/settingsSync/SettingsSyncRealIdeTest.kt @@ -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() + 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()