-
Notifications
You must be signed in to change notification settings - Fork 275
use RemoteResourceResolver in notification polling for etag and PATH handling #5165
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 16 commits
d8b6caf
02f335b
54c7d02
d96aa00
4231ba5
ba0c57b
08c6001
de5c8be
3bcc9ea
4b80f54
0aa2074
d18b689
bc9396d
ca8e967
5dcd476
c78a536
12ffed4
0ffa31e
0d992c7
9d4a1bb
b2b9849
af54b0f
b289ce1
794364d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,8 @@ | |
|
|
||
| import java.io.FileInputStream | ||
| import java.io.InputStream | ||
| import java.net.HttpURLConnection | ||
| import java.net.URI | ||
| import java.nio.file.Files | ||
| import java.nio.file.Path | ||
| import java.nio.file.StandardCopyOption | ||
|
|
@@ -13,30 +15,62 @@ | |
| import java.util.UUID | ||
| import java.util.concurrent.Callable | ||
| import java.util.concurrent.CompletionStage | ||
| import java.util.concurrent.atomic.AtomicBoolean | ||
|
|
||
| interface RemoteResourceResolver { | ||
| fun resolve(resource: RemoteResource): CompletionStage<Path> | ||
| fun checkForUpdates(endpoint: String, eTagProvider: ETagProvider): UpdateCheckResult | ||
| fun getLocalResourcePath(filename: String): Path? | ||
| } | ||
| interface RemoteResolveParser { | ||
| fun canBeParsed(data: InputStream): Boolean | ||
| } | ||
|
|
||
| interface ETagProvider { | ||
| var etag: String? | ||
| fun updateETag(newETag: String?) | ||
| } | ||
|
|
||
| sealed class UpdateCheckResult { | ||
| data object HasUpdates : UpdateCheckResult() | ||
| data object NoUpdates : UpdateCheckResult() | ||
| data object FirstPollCheck : UpdateCheckResult() | ||
| } | ||
|
|
||
| class DefaultRemoteResourceResolver( | ||
| private val urlFetcher: UrlFetcher, | ||
| private val cacheBasePath: Path, | ||
| private val executor: (Callable<Path>) -> CompletionStage<Path>, | ||
| ) : RemoteResourceResolver { | ||
| private val isFirstPoll = AtomicBoolean(true) | ||
|
|
||
| override fun resolve(resource: RemoteResource): CompletionStage<Path> = executor(Callable { internalResolve(resource) }) | ||
|
|
||
| override fun getLocalResourcePath(filename: String): Path? { | ||
| val expectedLocation = cacheBasePath.resolve(filename) | ||
| return expectedLocation.existsOrNull() | ||
| } | ||
|
|
||
| override fun checkForUpdates(endpoint: String, eTagProvider: ETagProvider): UpdateCheckResult { | ||
| val hasETagUpdate = updateETags(eTagProvider, endpoint) | ||
| // for when we need to notify on first poll even when there's no new ETag | ||
| if (isFirstPoll.compareAndSet(true, false) && !hasETagUpdate) { | ||
| return UpdateCheckResult.FirstPollCheck | ||
| } | ||
|
|
||
| return if (hasETagUpdate) { | ||
| UpdateCheckResult.HasUpdates | ||
| } else { | ||
| UpdateCheckResult.NoUpdates | ||
| } | ||
| } | ||
|
|
||
| private fun internalResolve(resource: RemoteResource): Path { | ||
| val expectedLocation = cacheBasePath.resolve(resource.name) | ||
| val current = expectedLocation.existsOrNull() | ||
| if (resource.name != "notifications.json") { | ||
| if ((current != null && !isExpired(current, resource))) { | ||
| LOG.debug { "Existing file ($current) for ${resource.name} is present and not expired - using it." } | ||
| return current | ||
| } | ||
| if (current != null && !isExpired(current, resource)) { | ||
| LOG.debug { "Existing file ($current) for ${resource.name} is present and not expired - using it." } | ||
| return current | ||
| } | ||
|
|
||
| LOG.debug { "Current file for ${resource.name} does not exist or is expired. Attempting to fetch from ${resource.urls}" } | ||
|
|
@@ -84,6 +118,30 @@ | |
| return expectedLocation | ||
| } | ||
|
|
||
| private fun updateETags(eTagProvider: ETagProvider, endpoint: String): Boolean { | ||
| val currentEtag = eTagProvider.etag | ||
| val remoteEtag = getEndpointETag(endpoint) | ||
| eTagProvider.etag = remoteEtag | ||
| return currentEtag != remoteEtag | ||
| } | ||
|
|
||
| private fun getEndpointETag(endpoint: String): String = | ||
| try { | ||
| val url = URI(endpoint).toURL() | ||
| (url.openConnection() as HttpURLConnection).let { connection -> | ||
|
||
| connection.requestMethod = "HEAD" | ||
| connection.setRequestProperty("User-Agent", "AWS Toolkit for JetBrains") | ||
| connection.connect() | ||
|
|
||
| val eTag = connection.getHeaderField("ETag").orEmpty() | ||
| connection.disconnect() | ||
| eTag | ||
| } | ||
| } catch (e: Exception) { | ||
| LOG.warn { "Failed to fetch notification ETag: ${e.message}" } | ||
|
Check warning on line 141 in plugins/core/core/src/software/aws/toolkits/core/utils/RemoteResourceResolver.kt
|
||
| throw e | ||
| } | ||
|
|
||
| private companion object { | ||
| val LOG = getLogger<RemoteResourceResolver>() | ||
| fun Path.existsOrNull() = if (this.exists()) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it would make more sense to see if ETagProvider has a value set to determine if this were the first request