package com.jetbrains.lang.dart.sdk; import com.intellij.ide.BrowserUtil; import com.intellij.ide.util.PropertiesComponent; import com.intellij.notification.NotificationType; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.project.Project; import com.intellij.openapi.updateSettings.impl.UpdateChecker; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Couple; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.io.HttpRequests; import com.jetbrains.lang.dart.DartBundle; import com.jetbrains.lang.dart.flutter.FlutterUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.json.JSONObject; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; public class DartSdkUpdateChecker { public static final String SDK_STABLE_DOWNLOAD_URL = "https://www.dartlang.org/redirects/sdk-download-stable"; private static final String SDK_DEV_DOWNLOAD_URL = "https://www.dartlang.org/redirects/sdk-download-dev"; private static final String SDK_STABLE_UPDATE_CHECK_URL = "https://storage.googleapis.com/dart-archive/channels/stable/release/latest/VERSION"; private static final String SDK_DEV_UPDATE_CHECK_URL = "https://storage.googleapis.com/dart-archive/channels/dev/release/latest/VERSION"; private static final String DART_LAST_SDK_CHECK_KEY = "DART_LAST_SDK_CHECK_KEY"; private static final long CHECK_INTERVAL = TimeUnit.DAYS.toMillis(1); private static final Pattern SEMANTIC_VERSION_PATTERN = Pattern.compile("(\\d+\\.\\d+\\.\\d+)([0-9A-Za-z\\-\\+\\.]*)"); public static void mayBeCheckForSdkUpdate(@NotNull final Project project) { final DartSdkUpdateOption option = DartSdkUpdateOption.getDartSdkUpdateOption(); if (option == DartSdkUpdateOption.DoNotCheck) return; final long lastCheckedMillis = PropertiesComponent.getInstance().getOrInitLong(DART_LAST_SDK_CHECK_KEY, 0); if (System.currentTimeMillis() - lastCheckedMillis < CHECK_INTERVAL) return; final DartSdk sdk = DartSdk.getDartSdk(project); if (sdk == null) return; if (FlutterUtil.getFlutterRoot(sdk.getHomePath()) != null) return; // Dart SDK inside Flutter SDK is updated using another mechanism ApplicationManager.getApplication().executeOnPooledThread(() -> { if (!project.isDisposed()) { PropertiesComponent.getInstance().setValue(DART_LAST_SDK_CHECK_KEY, String.valueOf(System.currentTimeMillis())); final String currentSdkVersion = sdk.getVersion(); final SdkUpdateInfo sdkUpdateInfo = getSdkUpdateInfo(option); if (sdkUpdateInfo != null && compareDartSdkVersions(sdkUpdateInfo.myVersion, currentSdkVersion) > 0) { ApplicationManager.getApplication().invokeLater( () -> notifySdkUpdateAvailable(project, currentSdkVersion, sdkUpdateInfo.myVersion, sdkUpdateInfo.myDownloadUrl), ModalityState.NON_MODAL, project.getDisposed() ); } } }); } @Nullable static SdkUpdateInfo getSdkUpdateInfo(@NotNull final DartSdkUpdateOption updateOption) { boolean checkForStable = updateOption == DartSdkUpdateOption.Stable || updateOption == DartSdkUpdateOption.StableAndDev; boolean checkForDev = updateOption == DartSdkUpdateOption.StableAndDev; final SdkUpdateInfo stableSdkInfo = checkForStable ? getSdkUpdateInfo(SDK_STABLE_UPDATE_CHECK_URL, SDK_STABLE_DOWNLOAD_URL) : null; final SdkUpdateInfo devSdkInfo = checkForDev ? getSdkUpdateInfo(SDK_DEV_UPDATE_CHECK_URL, SDK_DEV_DOWNLOAD_URL) : null; final SdkUpdateInfo sdkUpdateInfo; if (stableSdkInfo == null) { sdkUpdateInfo = devSdkInfo; } else if (devSdkInfo == null) { sdkUpdateInfo = stableSdkInfo; } else if (compareDartSdkVersions(devSdkInfo.myVersion, stableSdkInfo.myVersion) > 0) { sdkUpdateInfo = devSdkInfo; } else { sdkUpdateInfo = stableSdkInfo; } return sdkUpdateInfo; } public static int compareDartSdkVersions(@NotNull final String version1, @NotNull final String version2) { // Dart SDK follows Semantic Versioning. There are 3 kind of versions: // stable release like "1.11.0" // dev preview like "1.11.0-dev.3.0" // bleeding edge (nightly build) like "1.11.0-edge.131801" // According to spec: "1.11.0" > "1.11.0-edge.131801" > "1.11.0-dev.3.0" final Couple<String> version1Parts = getMajorMinorPatchAndRemainder(version1); final Couple<String> version2Parts = getMajorMinorPatchAndRemainder(version2); if (version1Parts == null || version2Parts == null) { // spec violation return StringUtil.compareVersionNumbers(version1, version2); } final String majorMinorPatch1 = version1Parts.first; final String remainder1 = version1Parts.second; final String majorMinorPatch2 = version2Parts.first; final String remainder2 = version2Parts.second; final int result = StringUtil.compareVersionNumbers(majorMinorPatch1, majorMinorPatch2); if (result != 0 || Comparing.equal(remainder1, remainder2)) return result; if (remainder1.isEmpty()) return 1; if (remainder2.isEmpty()) return -1; return StringUtil.compareVersionNumbers(remainder1, remainder2); } @Nullable private static Couple<String> getMajorMinorPatchAndRemainder(@NotNull final String semanticVersion) { final Matcher matcher = SEMANTIC_VERSION_PATTERN.matcher(semanticVersion); if (matcher.matches()) { return Couple.of(matcher.group(1), matcher.group(2)); } return null; } private static void notifySdkUpdateAvailable(@NotNull final Project project, @NotNull final String currentSdkVersion, @NotNull final String availableSdkVersion, @NotNull final String downloadUrl) { final String title = DartBundle.message("dart.sdk.update.title"); final String message = DartBundle.message("new.dart.sdk.available.for.download..notification", availableSdkVersion, currentSdkVersion); UpdateChecker.NOTIFICATIONS.createNotification(title, message, NotificationType.INFORMATION, (notification, event) -> { notification.expire(); if ("download".equals(event.getDescription())) { BrowserUtil.browse(downloadUrl); } if ("settings".equals(event.getDescription())) { DartConfigurable.openDartSettings(project); } }).notify(project); } @Nullable private static SdkUpdateInfo getSdkUpdateInfo(@NotNull final String updateCheckUrl, @NotNull final String sdkDownloadUrl) { try { // { "date" : "2015-05-28", // "version" : "1.11.0-dev.3.0", // "revision" : "6072062d4185614c32bf96c3ba833dcc18ab4348" } final String versionFileContents = HttpRequests.request(updateCheckUrl).readString(null); final String version = new JSONObject(versionFileContents).optString("version", null); if (version != null) { return new SdkUpdateInfo(sdkDownloadUrl, version); } } catch (Exception e) {/* unlucky */} return null; } static class SdkUpdateInfo { @NotNull final String myDownloadUrl; @NotNull final String myVersion; public SdkUpdateInfo(@NotNull final String downloadUrl, @NotNull final String version) { myDownloadUrl = downloadUrl; myVersion = version; } } }