-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add feature to notify users of new plugin version availability (#318)
- Loading branch information
1 parent
faa503e
commit 7cb28c2
Showing
6 changed files
with
238 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
85 changes: 85 additions & 0 deletions
85
plugin/src/software/aws/toolkits/eclipse/amazonq/util/PersistentToolkitNotification.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package software.aws.toolkits.eclipse.amazonq.util; | ||
|
||
import org.eclipse.swt.events.MouseAdapter; | ||
import org.eclipse.swt.events.MouseEvent; | ||
import org.eclipse.swt.events.SelectionAdapter; | ||
import org.eclipse.swt.events.SelectionEvent; | ||
import org.eclipse.swt.graphics.Color; | ||
import org.eclipse.swt.graphics.Font; | ||
import org.eclipse.swt.graphics.FontData; | ||
import org.eclipse.swt.layout.GridData; | ||
import org.eclipse.swt.widgets.Button; | ||
import org.eclipse.swt.widgets.Composite; | ||
import org.eclipse.swt.widgets.Display; | ||
import org.eclipse.swt.widgets.Label; | ||
|
||
import java.util.function.Consumer; | ||
import org.eclipse.swt.SWT; | ||
|
||
public final class PersistentToolkitNotification extends ToolkitNotification { | ||
private final Consumer<Boolean> checkboxCallback; | ||
|
||
public PersistentToolkitNotification(final Display display, final String title, | ||
final String description, final Consumer<Boolean> checkboxCallback) { | ||
super(display, title, description); | ||
this.checkboxCallback = checkboxCallback; | ||
} | ||
|
||
@Override | ||
protected void createContentArea(final Composite parent) { | ||
super.createContentArea(parent); | ||
|
||
Composite container = (Composite) parent.getChildren()[0]; | ||
|
||
// create checkbox | ||
Button doNotShowCheckbox = new Button(container, SWT.CHECK); | ||
GridData checkboxGridData = new GridData(SWT.BEGINNING, SWT.CENTER, false, false); | ||
doNotShowCheckbox.setLayoutData(checkboxGridData); | ||
|
||
// create checkbox button text | ||
Label checkboxLabel = new Label(container, SWT.NONE); | ||
checkboxLabel.setText("Don't show this message again"); | ||
checkboxLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); | ||
|
||
// style button text | ||
Color grayColor = new Color(parent.getDisplay(), 128, 128, 128); | ||
checkboxLabel.setForeground(grayColor); | ||
|
||
Font originalFont = checkboxLabel.getFont(); | ||
FontData[] fontData = originalFont.getFontData(); | ||
for (FontData fd : fontData) { | ||
fd.setHeight(fd.getHeight() - 1); | ||
} | ||
Font smallerFont = new Font(parent.getDisplay(), fontData); | ||
checkboxLabel.setFont(smallerFont); | ||
|
||
checkboxLabel.addDisposeListener(e -> { | ||
smallerFont.dispose(); | ||
grayColor.dispose(); | ||
}); | ||
|
||
// make button text clickable | ||
checkboxLabel.addMouseListener(new MouseAdapter() { | ||
@Override | ||
public void mouseUp(final MouseEvent e) { | ||
doNotShowCheckbox.setSelection(!doNotShowCheckbox.getSelection()); | ||
if (checkboxCallback != null) { | ||
checkboxCallback.accept(doNotShowCheckbox.getSelection()); | ||
} | ||
} | ||
}); | ||
|
||
doNotShowCheckbox.addSelectionListener(new SelectionAdapter() { | ||
@Override | ||
public void widgetSelected(final SelectionEvent e) { | ||
if (checkboxCallback != null) { | ||
checkboxCallback.accept(doNotShowCheckbox.getSelection()); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
123 changes: 123 additions & 0 deletions
123
plugin/src/software/aws/toolkits/eclipse/amazonq/util/UpdateUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package software.aws.toolkits.eclipse.amazonq.util; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.InputStream; | ||
import java.io.InputStreamReader; | ||
import java.net.HttpURLConnection; | ||
import java.net.URI; | ||
import java.net.http.HttpClient; | ||
import java.net.http.HttpRequest; | ||
import java.net.http.HttpResponse; | ||
import java.time.Duration; | ||
|
||
import org.eclipse.mylyn.commons.ui.dialogs.AbstractNotificationPopup; | ||
import org.eclipse.swt.widgets.Display; | ||
import org.osgi.framework.Version; | ||
|
||
import org.tukaani.xz.XZInputStream; | ||
|
||
import software.aws.toolkits.eclipse.amazonq.exception.AmazonQPluginException; | ||
import software.aws.toolkits.eclipse.amazonq.lsp.manager.fetcher.ArtifactUtils; | ||
import software.aws.toolkits.eclipse.amazonq.plugin.Activator; | ||
import software.aws.toolkits.eclipse.amazonq.telemetry.metadata.PluginClientMetadata; | ||
|
||
public final class UpdateUtils { | ||
private static final String REQUEST_URL = "https://amazonq.eclipsetoolkit.amazonwebservices.com/artifacts.xml.xz"; | ||
private static Version mostRecentNotificationVersion; | ||
private static Version remoteVersion; | ||
private static Version localVersion; | ||
private static final UpdateUtils INSTANCE = new UpdateUtils(); | ||
|
||
public static UpdateUtils getInstance() { | ||
return INSTANCE; | ||
} | ||
|
||
private UpdateUtils() { | ||
mostRecentNotificationVersion = Activator.getPluginStore().getObject(Constants.DO_NOT_SHOW_UPDATE_KEY, Version.class); | ||
String localString = PluginClientMetadata.getInstance().getPluginVersion(); | ||
localVersion = ArtifactUtils.parseVersion(localString.substring(0, localString.lastIndexOf("."))); | ||
} | ||
|
||
private boolean newUpdateAvailable() { | ||
//fetch artifact file containing version info from repo | ||
remoteVersion = fetchRemoteArtifactVersion(REQUEST_URL); | ||
|
||
//return early if either version is unavailable | ||
if (remoteVersion == null || localVersion == null) { | ||
return false; | ||
} | ||
|
||
//prompt should show if never previously displayed or remote version is greater | ||
boolean shouldShowNotification = mostRecentNotificationVersion == null || remoteVersionIsGreater(remoteVersion, mostRecentNotificationVersion); | ||
|
||
return remoteVersionIsGreater(remoteVersion, localVersion) && shouldShowNotification; | ||
} | ||
|
||
public void checkForUpdate() { | ||
if (newUpdateAvailable()) { | ||
showNotification(); | ||
} | ||
} | ||
|
||
private Version fetchRemoteArtifactVersion(final String repositoryUrl) { | ||
HttpClient connection = HttpClientFactory.getInstance(); | ||
try { | ||
HttpRequest request = HttpRequest.newBuilder() | ||
.uri(URI.create(repositoryUrl)) | ||
.timeout(Duration.ofSeconds(5)) | ||
.GET() | ||
.build(); | ||
|
||
HttpResponse<InputStream> response = connection.send(request, | ||
HttpResponse.BodyHandlers.ofInputStream()); | ||
|
||
// handle response codes | ||
if (response.statusCode() != HttpURLConnection.HTTP_OK) { | ||
throw new AmazonQPluginException("HTTP request failed with response code: " + response.statusCode()); | ||
} | ||
|
||
// process XZ content from input stream | ||
try (InputStream inputStream = response.body(); | ||
XZInputStream xzis = new XZInputStream(inputStream); | ||
BufferedReader reader = new BufferedReader(new InputStreamReader(xzis))) { | ||
|
||
String line; | ||
while ((line = reader.readLine()) != null) { | ||
if (line.contains("<artifact classifier=\"osgi.bundle\"")) { | ||
int versionStart = line.indexOf("version=\"") + 9; | ||
int versionEnd = line.indexOf("\"", versionStart); | ||
String fullVersion = line.substring(versionStart, versionEnd); | ||
return ArtifactUtils.parseVersion(fullVersion.substring(0, fullVersion.lastIndexOf("."))); | ||
} | ||
} | ||
} | ||
|
||
} catch (Exception e) { | ||
Activator.getLogger().error("Error fetching artifact from remote location.", e); | ||
} | ||
return null; | ||
} | ||
|
||
private void showNotification() { | ||
Display.getDefault().asyncExec(() -> { | ||
AbstractNotificationPopup notification = new PersistentToolkitNotification(Display.getCurrent(), | ||
Constants.PLUGIN_UPDATE_NOTIFICATION_TITLE, | ||
String.format(Constants.PLUGIN_UPDATE_NOTIFICATION_BODY, remoteVersion.toString()), | ||
(selected) -> { | ||
if (selected) { | ||
Activator.getPluginStore().putObject(Constants.DO_NOT_SHOW_UPDATE_KEY, remoteVersion); | ||
} else { | ||
Activator.getPluginStore().remove(Constants.DO_NOT_SHOW_UPDATE_KEY); | ||
} | ||
}); | ||
notification.open(); | ||
}); | ||
} | ||
|
||
private static boolean remoteVersionIsGreater(final Version remote, final Version local) { | ||
return remote.compareTo(local) > 0; | ||
} | ||
} |