33
44package software.aws.toolkits.jetbrains.core.notifications
55
6+ import com.intellij.openapi.application.PathManager
7+ import com.intellij.openapi.components.service
8+ import com.intellij.util.io.HttpRequests
9+ import com.intellij.util.io.createDirectories
610import software.aws.toolkits.core.utils.DefaultRemoteResourceResolver
711import software.aws.toolkits.core.utils.RemoteResource
812import software.aws.toolkits.core.utils.RemoteResourceResolver
9- import software.aws.toolkits.jetbrains. core.saveFileFromUrl
13+ import software.aws.toolkits.core.utils.UrlFetcher
1014import software.aws.toolkits.core.utils.exists
11- import java.nio.file.Path
12- import java.util.concurrent.Callable
13- import java.util.concurrent.CompletionStage
1415import software.aws.toolkits.core.utils.getLogger
15- import software.aws.toolkits.core.utils.info
1616import software.aws.toolkits.core.utils.warn
17- import com.intellij.util.io.HttpRequests
18- import com.intellij.openapi.application.PathManager
19- import com.intellij.util.io.createDirectories
20- import software.aws.toolkits.core.utils.UrlFetcher
21- import software.aws.toolkits.jetbrains.core.RemoteResourceResolverProvider
17+ import software.aws.toolkits.jetbrains.core.saveFileFromUrl
2218import software.aws.toolkits.jetbrains.utils.pluginAwareExecuteOnPooledThread
19+ import java.nio.file.Path
2320import java.nio.file.Paths
21+ import java.util.concurrent.Callable
2422import java.util.concurrent.CompletableFuture
23+ import java.util.concurrent.CompletionStage
24+ import java.util.concurrent.atomic.AtomicBoolean
2525
26- interface NotificationRemoteResourceResolverProvider {
26+ interface NotificationResourceResolverProvider {
2727 fun get (): NotificationResourceResolver
2828
2929 companion object {
30- fun getInstance (): NotificationRemoteResourceResolverProvider = service()
30+ fun getInstance (): NotificationResourceResolverProvider = service()
3131 }
3232}
3333
34- class DefaultNotificationRemoteResourceResolverProvider {
34+ class DefaultNotificationResourceResolverProvider : NotificationResourceResolverProvider {
3535 override fun get () = RESOLVER_INSTANCE
3636
3737 companion object {
3838 private val RESOLVER_INSTANCE by lazy {
3939 val cachePath = Paths .get(PathManager .getSystemPath(), " aws-notifications" ).createDirectories()
4040
41- NotificationResourceResolver (
42- urlFetcher = HttpRequestUrlFetcher ,
43- cacheBasePath = cachePath,
44- executor = { callable ->
45- val future = CompletableFuture <Path >()
46- pluginAwareExecuteOnPooledThread {
47- try {
48- future.complete(callable.call())
49- } catch (e: Exception ) {
50- future.completeExceptionally(e)
51- }
41+ NotificationResourceResolver (HttpRequestUrlFetcher , cachePath) {
42+ val future = CompletableFuture <Path >()
43+ pluginAwareExecuteOnPooledThread {
44+ try {
45+ future.complete(it.call())
46+ } catch (e: Exception ) {
47+ future.completeExceptionally(e)
5248 }
53- future
5449 }
55- )
50+ future
51+ }
5652 }
5753
5854 object HttpRequestUrlFetcher : UrlFetcher {
@@ -63,44 +59,50 @@ class DefaultNotificationRemoteResourceResolverProvider {
6359 }
6460}
6561
62+ sealed class UpdateCheckResult {
63+ object HasUpdates : UpdateCheckResult()
64+ object NoUpdates : UpdateCheckResult()
65+ object FirstPollCheck : UpdateCheckResult()
66+ }
6667
6768class NotificationResourceResolver (
6869 private val urlFetcher : UrlFetcher ,
6970 private val cacheBasePath : Path ,
7071 private val executor : (Callable <Path >) -> CompletionStage <Path >,
71- private val etagState : NotificationEtagState = NotificationEtagState .getInstance()
7272) : RemoteResourceResolver {
7373 private val delegate = DefaultRemoteResourceResolver (urlFetcher, cacheBasePath, executor)
74+ private val etagState: NotificationEtagState = NotificationEtagState .getInstance()
75+ private val isFirstPoll = AtomicBoolean (true )
7476
7577 fun getLocalResourcePath (resourceName : String ): Path ? {
7678 val expectedLocation = cacheBasePath.resolve(resourceName)
7779 return expectedLocation.existsOrNull()
7880 }
7981
80- override fun resolve (resource : RemoteResource ): CompletionStage <Path > {
81- return executor(Callable { internalResolve(resource) })
82- }
82+ fun checkForUpdates (): UpdateCheckResult {
83+ val hasETagUpdate = updateETags()
8384
84- private fun internalResolve (resource : RemoteResource ): Path {
85- val expectedLocation = cacheBasePath.resolve(resource.name)
86- val current = expectedLocation.existsOrNull()
87-
88- if (current != null ) {
89- val currentEtag = etagState.etag
90- try {
91- val remoteEtag = getEndpointETag()
92- if (currentEtag == remoteEtag) {
93- LOG .info { " Existing file ($current ) matches remote etag - using cached version" }
94- return current
95- }
96- } catch (e: Exception ) {
97- LOG .warn(e) { " Failed to check remote etag, using cached version if available" }
98- return current
99- }
85+ // for when we need to notify on first poll even when there's no new ETag
86+ if (isFirstPoll.compareAndSet(true , false ) && ! hasETagUpdate) {
87+ return UpdateCheckResult .FirstPollCheck
10088 }
10189
102- // Use delegate for download logic
103- return delegate.resolve(resource).toCompletableFuture().get()
90+ return if (hasETagUpdate) {
91+ UpdateCheckResult .HasUpdates
92+ } else {
93+ UpdateCheckResult .NoUpdates
94+ }
95+ }
96+
97+ fun updateETags (): Boolean {
98+ val currentEtag = etagState.etag
99+ val remoteEtag = getEndpointETag()
100+ etagState.etag = remoteEtag
101+ return currentEtag != remoteEtag
102+ }
103+
104+ override fun resolve (resource : RemoteResource ): CompletionStage <Path > {
105+ return delegate.resolve(resource)
104106 }
105107
106108 private fun getEndpointETag (): String =
@@ -122,6 +124,5 @@ class NotificationResourceResolver(
122124 } else {
123125 null
124126 }
125-
126127 }
127128}
0 commit comments