@@ -7,18 +7,24 @@ import com.intellij.openapi.diagnostic.Logger
77import com.intellij.openapi.project.Project
88import com.intellij.openapi.util.Disposer
99import com.intellij.util.Alarm
10+ import kotlinx.coroutines.CancellationException
1011import kotlinx.coroutines.CoroutineName
1112import kotlinx.coroutines.CoroutineScope
1213import kotlinx.coroutines.ExperimentalCoroutinesApi
1314import kotlinx.coroutines.Job
1415import kotlinx.coroutines.async
16+ import kotlinx.coroutines.delay
17+ import kotlinx.coroutines.isActive
1518import kotlinx.coroutines.launch
19+ import kotlinx.coroutines.withTimeoutOrNull
20+ import kotlinx.datetime.Clock
1621import org.digma.intellij.plugin.analytics.AnalyticsProvider
1722import org.digma.intellij.plugin.analytics.AnalyticsUrlProvider
1823import org.digma.intellij.plugin.analytics.AuthenticationException
1924import org.digma.intellij.plugin.analytics.AuthenticationProvider
2025import org.digma.intellij.plugin.analytics.RestAnalyticsProvider
2126import org.digma.intellij.plugin.auth.account.CredentialsHolder
27+ import org.digma.intellij.plugin.auth.account.DigmaAccountManager
2228import org.digma.intellij.plugin.auth.account.DigmaDefaultAccountHolder
2329import org.digma.intellij.plugin.auth.account.LoginHandler
2430import org.digma.intellij.plugin.auth.account.LoginResult
@@ -33,7 +39,12 @@ import java.lang.reflect.Method
3339import java.lang.reflect.Proxy
3440import java.util.concurrent.CopyOnWriteArrayList
3541import java.util.concurrent.Semaphore
42+ import kotlin.math.max
43+ import kotlin.time.Duration.Companion.ZERO
44+ import kotlin.time.Duration.Companion.minutes
3645import kotlin.time.Duration.Companion.seconds
46+ import kotlin.time.DurationUnit
47+ import kotlin.time.toDuration
3748
3849
3950@Service(Service .Level .APP )
@@ -57,16 +68,26 @@ class AuthManager(private val cs: CoroutineScope) : Disposable {
5768 private val refreshTokenStrategy = AuthManagerLockingRefreshStrategy (cs)
5869// private val refreshTokenStrategy = AuthManagerNonLockingRefreshStrategy(cs)
5970
71+ // private val autoRefreshJob: Job
72+ // private var autoRefreshWaitingJob: Job? = null
73+
6074 companion object {
6175 @JvmStatic
6276 fun getInstance (): AuthManager {
6377 return service<AuthManager >()
6478 }
6579 }
6680
81+
82+ init {
83+ // autoRefreshJob = autoRefreshJob(cs)
84+ }
85+
6786 override fun dispose () {
6887 myAnalyticsProvider.close()
6988 loginOrRefreshAsyncJob?.cancel()
89+ // autoRefreshWaitingJob?.cancel()
90+ // autoRefreshJob.cancel()
7091 }
7192
7293 private fun createMyAnalyticsProvider (): RestAnalyticsProvider {
@@ -133,7 +154,7 @@ class AuthManager(private val cs: CoroutineScope) : Disposable {
133154
134155
135156 /* *
136- * this method is used for best effort to do login or refresh early on startup.
157+ * this method is used for best effort to do log in or refresh early on startup.
137158 * when connection to the server is ok it will succeed either new login or refresh if necessary.
138159 * it is non-blocking but does not allow more than one job at a time, if called concurrently and
139160 * myAuthJob is active the method will just return doing nothing.
@@ -156,6 +177,8 @@ class AuthManager(private val cs: CoroutineScope) : Disposable {
156177 Log .log(logger::trace, " starting loginOrRefreshAsync job, analytics url {}" , myAnalyticsProvider.apiUrl)
157178 val loginHandler = LoginHandler .createLoginHandler(project, myAnalyticsProvider)
158179 loginHandler.loginOrRefresh()
180+ // wake up the auto refresh to check the next auto refresh time
181+ // autoRefreshWaitingJob?.cancel()
159182 } catch (e: Throwable ) {
160183 Log .warnWithException(logger, e, " error in loginOrRefreshAsync {}" , e)
161184 ErrorReporter .getInstance().reportError(" AuthManager.loginOrRefresh" , e)
@@ -188,7 +211,10 @@ class AuthManager(private val cs: CoroutineScope) : Disposable {
188211 try {
189212 Log .log(logger::trace, " starting job loginSynchronously, analytics url {}" , myAnalyticsProvider.apiUrl)
190213 val loginHandler = LoginHandler .createLoginHandler(myAnalyticsProvider)
191- loginHandler.login(user, password)
214+ val loginResult = loginHandler.login(user, password)
215+ // wake up the auto refresh to check the next auto refresh time
216+ // autoRefreshWaitingJob?.cancel()
217+ loginResult
192218 } catch (e: Throwable ) {
193219 Log .warnWithException(logger, e, " error in loginSynchronously {}" , e)
194220 ErrorReporter .getInstance().reportError(" AuthManager.login" , e)
@@ -349,6 +375,86 @@ class AuthManager(private val cs: CoroutineScope) : Disposable {
349375 }
350376
351377
378+ // auto refresh one minute before expiration
379+ // Experimental
380+ private fun autoRefreshJob (cs : CoroutineScope ): Job {
381+
382+ Log .log(logger::trace, " launching autoRefreshJob" )
383+ return cs.launch(CoroutineName (" autoRefreshJob" )) {
384+
385+ Log .log(logger::trace, " ${coroutineContext[CoroutineName ]} auto refresh job started" )
386+ var delay = 1 .minutes
387+ while (isActive) {
388+
389+ try {
390+ Log .log(logger::trace, " ${coroutineContext[CoroutineName ]} looking for account" )
391+
392+ val account = DigmaDefaultAccountHolder .getInstance().account
393+ if (account != null ) {
394+ Log .log(logger::trace, " ${coroutineContext[CoroutineName ]} found account {}" , account)
395+
396+ val credentials = try {
397+ // exception here may happen if we change the credentials structure,which doesn't happen too much,
398+ // user will be redirected to log in again
399+ DigmaAccountManager .getInstance().findCredentials(account)
400+ } catch (_: Throwable ) {
401+ null
402+ }
403+
404+ if (credentials != null ) {
405+
406+ Log .log(logger::trace, " ${coroutineContext[CoroutineName ]} found credentials for account {}" , account)
407+
408+ val expireIn =
409+ max(0 , (credentials.expirationTime - Clock .System .now().toEpochMilliseconds())).toDuration(DurationUnit .MILLISECONDS )
410+ Log .log(logger::trace, " ${coroutineContext[CoroutineName ]} credentials for account expires in {}" , expireIn)
411+ if (expireIn <= 1 .minutes) {
412+ Log .log(logger::trace, " ${coroutineContext[CoroutineName ]} refreshing credentials for account {}" , account)
413+ val loginHandler = LoginHandler .createLoginHandler(null , myAnalyticsProvider)
414+ loginHandler.refresh(account, credentials)
415+ Log .log(logger::trace, " ${coroutineContext[CoroutineName ]} credentials for account refreshed {}" , account)
416+ // immediately loop again and compute the next delay
417+ delay = ZERO
418+ } else {
419+ delay = expireIn - 1 .minutes
420+ Log .log(
421+ logger::trace,
422+ " ${coroutineContext[CoroutineName ]} credentials for account expires in more then 1 minute, waiting {}" ,
423+ delay
424+ )
425+ }
426+ } else {
427+ Log .log(logger::trace, " ${coroutineContext[CoroutineName ]} credentials for account not found, waiting 1 minute" )
428+ delay = 1 .minutes
429+ }
430+ } else {
431+ Log .log(logger::trace, " ${coroutineContext[CoroutineName ]} account not found, waiting 10 minutes" )
432+ delay = 10 .minutes
433+ }
434+
435+
436+ // autoRefreshWaitingJob = launch {
437+ // Log.log(logger::trace, "${coroutineContext[CoroutineName]} in autoRefreshJob.autoRefreshWaitingJob waiting {}",delay)
438+ // delay(delay.inWholeMilliseconds)
439+ // Log.log(logger::trace, "${coroutineContext[CoroutineName]} in autoRefreshJob.autoRefreshWaitingJob done")
440+ // }
441+ // try {
442+ // withTimeoutOrNull(delay.inWholeMilliseconds) {
443+ // autoRefreshWaitingJob?.join()
444+ // }
445+ // } catch (e: CancellationException) {
446+ // Log.log(logger::trace, "${coroutineContext[CoroutineName]} autoRefreshJob.autoRefreshWaitingJob woke up")
447+ // }
448+
449+
450+ } catch (e: Throwable ) {
451+ Log .warnWithException(logger, e, " ${coroutineContext[CoroutineName ]} error in auto refresh job" )
452+ }
453+ }
454+ }
455+ }
456+
457+
352458 private inner class MyAuthInvocationHandler (private val analyticsProvider : RestAnalyticsProvider ) : InvocationHandler {
353459
354460 override fun invoke (proxy : Any , method : Method , args : Array <out Any >? ): Any? {
@@ -426,6 +532,8 @@ class AuthManager(private val cs: CoroutineScope) : Disposable {
426532 Log .log(logger::trace, " starting lockingLoginOrRefresh job, analytics url {}" , myAnalyticsProvider.apiUrl)
427533 val loginHandler = LoginHandler .createLoginHandler(null , myAnalyticsProvider)
428534 loginHandler.loginOrRefresh(onAuthenticationError)
535+ // wake up the auto refresh to check the next auto refresh time
536+ // autoRefreshWaitingJob?.cancel()
429537 } catch (e: Throwable ) {
430538 Log .warnWithException(logger, e, " error in lockingLoginOrRefresh job {}" , e)
431539 ErrorReporter .getInstance().reportError(" AuthManager.loginOrRefresh" , e)
@@ -462,6 +570,8 @@ class AuthManager(private val cs: CoroutineScope) : Disposable {
462570
463571
464572 // this strategy may do multiple refresh one after the other
573+ // this class was an experiment, it has been tested to work, but we don't use it
574+ @Suppress(" unused" )
465575 private inner class AuthManagerNonLockingRefreshStrategy (private val cs : CoroutineScope ) : RefreshStrategy {
466576
467577 private val logger: Logger = Logger .getInstance(AuthManagerNonLockingRefreshStrategy ::class .java)
@@ -480,6 +590,8 @@ class AuthManager(private val cs: CoroutineScope) : Disposable {
480590 Log .log(logger::trace, " starting nonLockingLoginOrRefresh job, analytics url {}" , myAnalyticsProvider.apiUrl)
481591 val loginHandler = LoginHandler .createLoginHandler(myAnalyticsProvider)
482592 loginHandler.loginOrRefresh(onAuthenticationError)
593+ // wake up the auto refresh to check the next auto refresh time
594+ // autoRefreshWaitingJob?.cancel()
483595 } catch (e: Throwable ) {
484596 Log .warnWithException(logger, e, " error in nonLockingLoginOrRefresh job {}" , e)
485597 ErrorReporter .getInstance().reportError(" AuthManager.loginOrRefresh" , e)
0 commit comments