Skip to content

Commit

Permalink
Merge f03323c into 2868094
Browse files Browse the repository at this point in the history
  • Loading branch information
KarelCemus committed Mar 16, 2019
2 parents 2868094 + f03323c commit 443339c
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
@@ -1,6 +1,11 @@

## Changelog

### [:link: 2.5.0](https://github.com/KarelCemus/play-redis/tree/2.5.0)

Added `expiresIn(key: String): Option[Duration]` implementing PTTL
command to get expiration of the given key. [#204](https://github.com/KarelCemus/play-redis/pull/204)

### [:link: 2.4.0](https://github.com/KarelCemus/play-redis/tree/2.4.0)

Update to Play `2.7.0` [#202](https://github.com/KarelCemus/play-redis/pull/202)
Expand Down
12 changes: 12 additions & 0 deletions src/main/scala/play/api/cache/redis/CacheApi.scala
Expand Up @@ -165,6 +165,18 @@ private[redis] trait AbstractCacheApi[Result[_]] {
*/
def expire(key: String, expiration: Duration): Result[Done]

/**
* Returns the remaining time to live of a key that has an expire set,
* useful, e.g., when we want to check remaining session duration
*
* @param key cache storage key
* @return the remaining time to live of a key. Some finite duration when
* the value exists and the expiration is set, Some infinite duration
* when the value exists but never expires, and None when the key does
* not exist.
*/
def expiresIn(key: String): Result[Option[Duration]]

/**
* Remove a value under the given key from the cache
*
Expand Down
Expand Up @@ -84,6 +84,15 @@ private[redis] trait CoreCommands {
*/
def expire(key: String, expiration: Duration): Future[Unit]

/**
* returns the remaining time to live of a key that has an expire set,
* useful, e.g., when we want to check remaining session duration
*
* @param key cache storage key
* @return the remaining time to live of a key in milliseconds
*/
def expiresIn(key: String): Future[Option[Duration]]

/**
* Removes all keys in arguments. The other remove methods are for syntax sugar
*
Expand Down
@@ -1,5 +1,7 @@
package play.api.cache.redis.connector

import java.util.concurrent.TimeUnit

import scala.concurrent.Future
import scala.concurrent.duration.Duration
import scala.reflect.ClassTag
Expand Down Expand Up @@ -113,6 +115,19 @@ private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: R
case false => log.debug(s"Expiration set on key '$key' failed. Key does not exist.") // Nothing was removed
}

def expiresIn(key: String): Future[Option[Duration]] =
redis.pttl(key) executing "PTTL" withKey key expects {
case -2 =>
log.debug(s"PTTL on key '$key' returns -2, it does not exist.")
None
case -1 =>
log.debug(s"PTTL on key '$key' returns -1, it has no associated expiration.")
Some(Duration.Inf)
case expiration =>
log.debug(s"PTTL on key '$key' returns $expiration milliseconds.")
Some(Duration(expiration, TimeUnit.MILLISECONDS))
}

def matching(pattern: String): Future[Seq[String]] =
redis.keys(pattern) executing "KEYS" withKey pattern logging {
case keys => log.debug(s"KEYS on '$pattern' responded '${keys.mkString(", ")}'.")
Expand Down
4 changes: 4 additions & 0 deletions src/main/scala/play/api/cache/redis/impl/RedisCache.scala
Expand Up @@ -102,6 +102,10 @@ private[impl] class RedisCache[Result[_]](redis: RedisConnector, builder: Builde
redis.exists(key).recoverWithDefault(false)
}

def expiresIn(key: String) = key.prefixed { key =>
redis.expiresIn(key).recoverWithDefault(None)
}

def increment(key: String, by: Long) = key.prefixed { key =>
redis.increment(key, by).recoverWithDefault(by)
}
Expand Down
Expand Up @@ -90,6 +90,25 @@ class RedisConnectorSpec(implicit ee: ExecutionEnv) extends Specification with B
connector.get[String](s"$prefix-$idx") must beSome("value").await
}

"expires in returns finite duration" in new TestCase {
connector.set(s"$prefix-$idx", "value", 2.second).await
connector.expiresIn(s"$prefix-$idx") must beSome(beLessThanOrEqualTo(Duration("2 s"))).await
}

"expires in returns infinite duration" in new TestCase {
connector.set(s"$prefix-$idx", "value").await
connector.expiresIn(s"$prefix-$idx") must beSome(Duration.Inf: Duration).await
}

"expires in returns not defined key" in new TestCase {
connector.expiresIn(s"$prefix-$idx") must beNone.await
connector.set(s"$prefix-$idx", "value", 1.second).await
connector.expiresIn(s"$prefix-$idx") must beSome[Duration].await
// wait until the first duration expires
Future.after(2) must not(throwA[Throwable]).awaitFor(3.seconds)
connector.expiresIn(s"$prefix-$idx") must beNone.await
}

"positive exists on existing keys" in new TestCase {
connector.set(s"$prefix-$idx", "value").await
connector.exists(s"$prefix-$idx") must beTrue.await
Expand Down
10 changes: 10 additions & 0 deletions src/test/scala/play/api/cache/redis/impl/RedisCacheSpec.scala
Expand Up @@ -137,6 +137,16 @@ class RedisCacheSpec(implicit ee: ExecutionEnv) extends Specification with Reduc
cache.expire(key, expiration) must beDone.await
}

"expires in" in new MockedCache {
connector.expiresIn(anyString) returns Some(Duration("1500 ms"))
cache.expiresIn(key) must beSome(Duration("1500 ms")).await
}

"expires in recover with default" in new MockedCache {
connector.expiresIn(anyString) returns ex
cache.expiresIn(key) must beNone.await
}

"matching" in new MockedCache {
connector.matching(anyString) returns Seq(key)
cache.matching("pattern") must beEqualTo(Seq(key)).await
Expand Down

0 comments on commit 443339c

Please sign in to comment.