This is an automated email from the ASF dual-hosted git repository.
He-Pin pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko.git
The following commit(s) were added to refs/heads/main by this push:
new 3cdbc7c8bb fix: ClassTag context bound compat for Scala 3.8
cross-build (#3073)
3cdbc7c8bb is described below
commit 3cdbc7c8bbf0f1018852354c2ee66b8b7e621979
Author: He-Pin(kerr) <[email protected]>
AuthorDate: Wed Jun 17 23:41:24 2026 +0800
fix: ClassTag context bound compat for Scala 3.8 cross-build (#3073)
Motivation:
In Scala 3.8, [T: ClassTag] context bounds desugar to using clauses,
making explicit ClassTag passing positional-incompatible. Code patterns
like method(args)(ClassTag(clazz)) break when the method uses a context
bound.
Modification:
- Replace explicit ClassTag passing with implicit val ct: ClassTag[T] =
ClassTag(clazz) followed by method calls that rely on implicit
resolution, in Behavior.scala, ProducerController.scala,
WorkPullingProducerController.scala, Topic.scala, ConsistentHash.scala,
ShardingProducerController.scala, EventSourcedBehaviorTestKit.scala,
StreamTestKit.scala
- Use .apply() trick in ActorContextSpec.scala:
decoration[T].apply(behavior)
to separate implicit resolution from function application
- Convert context bound [T: ClassTag] to explicit implicit parameter
(implicit ev: ClassTag[T]) in Supervision.scala
- Merge context bound with separate implicit parameter list in
PersistencePlugin.scala: (implicit ct: ClassTag[T], ev:
PluginProvider[...])
- Refactor ShardedDaemonProcessImpl.scala Java API methods to avoid
explicit classTag passing
- Fix EventStream.scala secondary constructor formatting
Result:
All ClassTag-dependent code compiles with both Scala 2.13.18 and
Scala 3.8.4. No behavioral changes.
Tests:
Not run - compilation fix, verified by cross-compilation
References:
None - proactive Scala 3.8 forward compatibility
---
.../pekko/actor/typed/ActorContextSpec.scala | 4 +--
.../org/apache/pekko/actor/typed/Behavior.scala | 6 ++--
.../actor/typed/delivery/ProducerController.scala | 6 ++--
.../delivery/WorkPullingProducerController.scala | 6 ++--
.../actor/typed/eventstream/EventStream.scala | 3 +-
.../pekko/actor/typed/internal/Supervision.scala | 3 +-
.../apache/pekko/actor/typed/pubsub/Topic.scala | 6 ++--
.../org/apache/pekko/routing/ConsistentHash.scala | 3 +-
.../delivery/ShardingProducerController.scala | 6 ++--
.../typed/internal/ShardedDaemonProcessImpl.scala | 37 +++++++++++++---------
.../javadsl/EventSourcedBehaviorTestKit.scala | 18 +++++++----
.../pekko/persistence/PersistencePlugin.scala | 4 +--
.../pekko/stream/testkit/StreamTestKit.scala | 6 ++--
13 files changed, 68 insertions(+), 40 deletions(-)
diff --git
a/actor-typed-tests/src/test/scala/org/apache/pekko/actor/typed/ActorContextSpec.scala
b/actor-typed-tests/src/test/scala/org/apache/pekko/actor/typed/ActorContextSpec.scala
index bda008a286..a445723e75 100644
---
a/actor-typed-tests/src/test/scala/org/apache/pekko/actor/typed/ActorContextSpec.scala
+++
b/actor-typed-tests/src/test/scala/org/apache/pekko/actor/typed/ActorContextSpec.scala
@@ -83,7 +83,7 @@ abstract class ActorContextSpec extends
ScalaTestWithActorTestKit with AnyWordSp
def decoration[T: ClassTag]: Behavior[T] => Behavior[T]
implicit class BehaviorDecorator[T](behavior: Behavior[T])(implicit ev:
ClassTag[T]) {
- def decorate: Behavior[T] = decoration[T](ev)(behavior)
+ def decorate: Behavior[T] = decoration[T].apply(behavior)
}
"An ActorContext" must {
@@ -661,7 +661,7 @@ abstract class ActorContextSpec extends
ScalaTestWithActorTestKit with AnyWordSp
"not allow null messages" in {
// Scala 3 doesn't generate an implicit `ClassTag[Null]`
(https://github.com/lampepfl/dotty/issues/9586)
- val actor = spawn(decoration(ClassTag.Null)(Behaviors.empty[Null]))
+ val actor =
spawn(decoration[Null](ClassTag.Null).apply(Behaviors.empty[Null]))
intercept[InvalidMessageException] {
actor ! null
}
diff --git
a/actor-typed/src/main/scala/org/apache/pekko/actor/typed/Behavior.scala
b/actor-typed/src/main/scala/org/apache/pekko/actor/typed/Behavior.scala
index 1c9cbcad67..bc0e070c84 100644
--- a/actor-typed/src/main/scala/org/apache/pekko/actor/typed/Behavior.scala
+++ b/actor-typed/src/main/scala/org/apache/pekko/actor/typed/Behavior.scala
@@ -98,8 +98,10 @@ class SuperviseBehavior[T] private[pekko] (
*
* Only exceptions of the given type (and their subclasses) will be handled
by this supervision behavior.
*/
- def onFailure[Thr <: Throwable](clazz: Class[Thr], strategy:
SupervisorStrategy): SuperviseBehavior[T] =
- onFailure(strategy)(ClassTag(clazz))
+ def onFailure[Thr <: Throwable](clazz: Class[Thr], strategy:
SupervisorStrategy): SuperviseBehavior[T] = {
+ implicit val ct: ClassTag[Thr] = ClassTag(clazz)
+ onFailure(strategy)
+ }
private[pekko] def unwrap: Behavior[T] = wrapped
}
diff --git
a/actor-typed/src/main/scala/org/apache/pekko/actor/typed/delivery/ProducerController.scala
b/actor-typed/src/main/scala/org/apache/pekko/actor/typed/delivery/ProducerController.scala
index 128321a009..7d4368642e 100644
---
a/actor-typed/src/main/scala/org/apache/pekko/actor/typed/delivery/ProducerController.scala
+++
b/actor-typed/src/main/scala/org/apache/pekko/actor/typed/delivery/ProducerController.scala
@@ -294,7 +294,8 @@ object ProducerController {
messageClass: Class[A],
producerId: String,
durableQueueBehavior:
Optional[Behavior[DurableProducerQueue.Command[A]]]): Behavior[Command[A]] = {
- apply(producerId, durableQueueBehavior.toScala)(ClassTag(messageClass))
+ implicit val ct: ClassTag[A] = ClassTag(messageClass)
+ apply(producerId, durableQueueBehavior.toScala)
}
/**
@@ -305,7 +306,8 @@ object ProducerController {
producerId: String,
durableQueueBehavior:
Optional[Behavior[DurableProducerQueue.Command[A]]],
settings: Settings): Behavior[Command[A]] = {
- apply(producerId, durableQueueBehavior.toScala,
settings)(ClassTag(messageClass))
+ implicit val ct: ClassTag[A] = ClassTag(messageClass)
+ apply(producerId, durableQueueBehavior.toScala, settings)
}
}
diff --git
a/actor-typed/src/main/scala/org/apache/pekko/actor/typed/delivery/WorkPullingProducerController.scala
b/actor-typed/src/main/scala/org/apache/pekko/actor/typed/delivery/WorkPullingProducerController.scala
index fd3554551f..b3a63a5858 100644
---
a/actor-typed/src/main/scala/org/apache/pekko/actor/typed/delivery/WorkPullingProducerController.scala
+++
b/actor-typed/src/main/scala/org/apache/pekko/actor/typed/delivery/WorkPullingProducerController.scala
@@ -241,7 +241,8 @@ object WorkPullingProducerController {
producerId: String,
workerServiceKey: ServiceKey[ConsumerController.Command[A]],
durableQueueBehavior:
Optional[Behavior[DurableProducerQueue.Command[A]]]): Behavior[Command[A]] = {
- apply(producerId, workerServiceKey,
durableQueueBehavior.toScala)(ClassTag(messageClass))
+ implicit val ct: ClassTag[A] = ClassTag(messageClass)
+ apply(producerId, workerServiceKey, durableQueueBehavior.toScala)
}
/**
@@ -253,6 +254,7 @@ object WorkPullingProducerController {
workerServiceKey: ServiceKey[ConsumerController.Command[A]],
durableQueueBehavior:
Optional[Behavior[DurableProducerQueue.Command[A]]],
settings: Settings): Behavior[Command[A]] = {
- apply(producerId, workerServiceKey, durableQueueBehavior.toScala,
settings)(ClassTag(messageClass))
+ implicit val ct: ClassTag[A] = ClassTag(messageClass)
+ apply(producerId, workerServiceKey, durableQueueBehavior.toScala, settings)
}
}
diff --git
a/actor-typed/src/main/scala/org/apache/pekko/actor/typed/eventstream/EventStream.scala
b/actor-typed/src/main/scala/org/apache/pekko/actor/typed/eventstream/EventStream.scala
index 51f6ccd18d..07190a07c4 100644
---
a/actor-typed/src/main/scala/org/apache/pekko/actor/typed/eventstream/EventStream.scala
+++
b/actor-typed/src/main/scala/org/apache/pekko/actor/typed/eventstream/EventStream.scala
@@ -59,7 +59,8 @@ object EventStream {
/**
* Java API.
*/
- def this(clazz: Class[E], subscriber: ActorRef[E]) =
this(subscriber)(ClassTag(clazz))
+ def this(clazz: Class[E], subscriber: ActorRef[E]) =
+ this(subscriber)(ClassTag(clazz))
/**
* INTERNAL API
diff --git
a/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/Supervision.scala
b/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/Supervision.scala
index 19fc70b43b..396637bd2b 100644
---
a/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/Supervision.scala
+++
b/actor-typed/src/main/scala/org/apache/pekko/actor/typed/internal/Supervision.scala
@@ -43,7 +43,8 @@ import org.slf4j.event.Level
* INTERNAL API
*/
@InternalApi private[pekko] object Supervisor {
- def apply[T, Thr <: Throwable: ClassTag](initialBehavior: Behavior[T],
strategy: SupervisorStrategy): Behavior[T] = {
+ def apply[T, Thr <: Throwable](initialBehavior: Behavior[T], strategy:
SupervisorStrategy)(
+ implicit ev: ClassTag[Thr]): Behavior[T] = {
if (initialBehavior.isInstanceOf[scaladsl.AbstractBehavior[?]] ||
initialBehavior
.isInstanceOf[javadsl.AbstractBehavior[?]]) {
diff --git
a/actor-typed/src/main/scala/org/apache/pekko/actor/typed/pubsub/Topic.scala
b/actor-typed/src/main/scala/org/apache/pekko/actor/typed/pubsub/Topic.scala
index 98ee992ea8..10cd1ddc07 100644
--- a/actor-typed/src/main/scala/org/apache/pekko/actor/typed/pubsub/Topic.scala
+++ b/actor-typed/src/main/scala/org/apache/pekko/actor/typed/pubsub/Topic.scala
@@ -134,7 +134,9 @@ object Topic {
/**
* Java API: Create a topic actor behavior for the given topic name and
message class
*/
- def create[T](messageClass: Class[T], topicName: String):
Behavior[Command[T]] =
- apply[T](topicName)(ClassTag(messageClass))
+ def create[T](messageClass: Class[T], topicName: String):
Behavior[Command[T]] = {
+ implicit val ct: ClassTag[T] = ClassTag(messageClass)
+ apply[T](topicName)
+ }
}
diff --git a/actor/src/main/scala/org/apache/pekko/routing/ConsistentHash.scala
b/actor/src/main/scala/org/apache/pekko/routing/ConsistentHash.scala
index 4020daae4a..343970fa55 100644
--- a/actor/src/main/scala/org/apache/pekko/routing/ConsistentHash.scala
+++ b/actor/src/main/scala/org/apache/pekko/routing/ConsistentHash.scala
@@ -138,7 +138,8 @@ object ConsistentHash {
*/
def create[T](nodes: java.lang.Iterable[T], virtualNodesFactor: Int):
ConsistentHash[T] = {
import scala.jdk.CollectionConverters._
- apply(nodes.asScala,
virtualNodesFactor)(ClassTag(classOf[Any].asInstanceOf[Class[T]]))
+ implicit val ct: ClassTag[T] = ClassTag.Any.asInstanceOf[ClassTag[T]]
+ apply(nodes.asScala, virtualNodesFactor)
}
private def concatenateNodeHash(nodeHash: Int, vnode: Int): Int = {
diff --git
a/cluster-sharding-typed/src/main/scala/org/apache/pekko/cluster/sharding/typed/delivery/ShardingProducerController.scala
b/cluster-sharding-typed/src/main/scala/org/apache/pekko/cluster/sharding/typed/delivery/ShardingProducerController.scala
index 342ecf2b29..4266c16011 100644
---
a/cluster-sharding-typed/src/main/scala/org/apache/pekko/cluster/sharding/typed/delivery/ShardingProducerController.scala
+++
b/cluster-sharding-typed/src/main/scala/org/apache/pekko/cluster/sharding/typed/delivery/ShardingProducerController.scala
@@ -285,7 +285,8 @@ object ShardingProducerController {
producerId: String,
region:
ActorRef[ShardingEnvelope[ConsumerController.SequencedMessage[A]]],
durableQueueBehavior:
Optional[Behavior[DurableProducerQueue.Command[A]]]): Behavior[Command[A]] = {
- apply(producerId, region,
durableQueueBehavior.toScala)(ClassTag(messageClass))
+ implicit val ct: ClassTag[A] = ClassTag(messageClass)
+ apply(producerId, region, durableQueueBehavior.toScala)
}
/**
@@ -297,7 +298,8 @@ object ShardingProducerController {
region:
ActorRef[ShardingEnvelope[ConsumerController.SequencedMessage[A]]],
durableQueueBehavior:
Optional[Behavior[DurableProducerQueue.Command[A]]],
settings: Settings): Behavior[Command[A]] = {
- apply(producerId, region, durableQueueBehavior.toScala,
settings)(ClassTag(messageClass))
+ implicit val ct: ClassTag[A] = ClassTag(messageClass)
+ apply(producerId, region, durableQueueBehavior.toScala, settings)
}
// TODO maybe there is a need for variant taking message extractor instead
of ShardingEnvelope
diff --git
a/cluster-sharding-typed/src/main/scala/org/apache/pekko/cluster/sharding/typed/internal/ShardedDaemonProcessImpl.scala
b/cluster-sharding-typed/src/main/scala/org/apache/pekko/cluster/sharding/typed/internal/ShardedDaemonProcessImpl.scala
index fb69fe2a8e..50ef440a89 100644
---
a/cluster-sharding-typed/src/main/scala/org/apache/pekko/cluster/sharding/typed/internal/ShardedDaemonProcessImpl.scala
+++
b/cluster-sharding-typed/src/main/scala/org/apache/pekko/cluster/sharding/typed/internal/ShardedDaemonProcessImpl.scala
@@ -62,12 +62,11 @@ private[pekko] final class ShardedDaemonProcessImpl(system:
ActorSystem[?])
def init[T](name: String, numberOfInstances: Int, behaviorFactory: Int =>
Behavior[T])(
implicit classTag: ClassTag[T]): Unit =
- init(name, numberOfInstances, behaviorFactory,
ShardedDaemonProcessSettings(system), None, None)(classTag)
+ init(name, numberOfInstances, behaviorFactory,
ShardedDaemonProcessSettings(system), None, None)
override def init[T](name: String, numberOfInstances: Int, behaviorFactory:
Int => Behavior[T], stopMessage: T)(
implicit classTag: ClassTag[T]): Unit =
- init(name, numberOfInstances, behaviorFactory,
ShardedDaemonProcessSettings(system), Some(stopMessage), None)(
- classTag)
+ init(name, numberOfInstances, behaviorFactory,
ShardedDaemonProcessSettings(system), Some(stopMessage), None)
override def init[T](
name: String,
@@ -214,22 +213,26 @@ private[pekko] final class
ShardedDaemonProcessImpl(system: ActorSystem[?])
messageClass: Class[T],
name: String,
numberOfInstances: Int,
- behaviorFactory: IntFunction[Behavior[T]]): Unit =
- init(name, numberOfInstances, n =>
behaviorFactory(n))(ClassTag(messageClass))
+ behaviorFactory: IntFunction[Behavior[T]]): Unit = {
+ implicit val ct: ClassTag[T] = ClassTag(messageClass)
+ init(name, numberOfInstances, n => behaviorFactory(n))
+ }
override def init[T](
messageClass: Class[T],
name: String,
numberOfInstances: Int,
behaviorFactory: IntFunction[Behavior[T]],
- stopMessage: T): Unit =
+ stopMessage: T): Unit = {
+ implicit val ct: ClassTag[T] = ClassTag(messageClass)
init(
name,
numberOfInstances,
n => behaviorFactory(n),
ShardedDaemonProcessSettings(system),
Some(stopMessage),
- None)(ClassTag(messageClass))
+ None)
+ }
override def init[T](
messageClass: Class[T],
@@ -237,8 +240,10 @@ private[pekko] final class
ShardedDaemonProcessImpl(system: ActorSystem[?])
numberOfInstances: Int,
behaviorFactory: IntFunction[Behavior[T]],
settings: ShardedDaemonProcessSettings,
- stopMessage: Optional[T]): Unit =
- init(name, numberOfInstances, n => behaviorFactory(n), settings,
stopMessage.toScala, None)(ClassTag(messageClass))
+ stopMessage: Optional[T]): Unit = {
+ implicit val ct: ClassTag[T] = ClassTag(messageClass)
+ init(name, numberOfInstances, n => behaviorFactory(n), settings,
stopMessage.toScala, None)
+ }
override def init[T](
messageClass: Class[T],
@@ -247,14 +252,16 @@ private[pekko] final class
ShardedDaemonProcessImpl(system: ActorSystem[?])
behaviorFactory: IntFunction[Behavior[T]],
settings: ShardedDaemonProcessSettings,
stopMessage: Optional[T],
- shardAllocationStrategy: Optional[ShardAllocationStrategy]): Unit =
+ shardAllocationStrategy: Optional[ShardAllocationStrategy]): Unit = {
+ implicit val ct: ClassTag[T] = ClassTag(messageClass)
init(
name,
numberOfInstances,
n => behaviorFactory(n),
settings,
stopMessage.toScala,
- shardAllocationStrategy.toScala)(ClassTag(messageClass))
+ shardAllocationStrategy.toScala)
+ }
override def initWithContext[T](
messageClass: Class[T],
@@ -262,8 +269,8 @@ private[pekko] final class ShardedDaemonProcessImpl(system:
ActorSystem[?])
initialNumberOfInstances: Int,
behaviorFactory:
java.util.function.Function[ShardedDaemonProcessContext, Behavior[T]])
: ActorRef[ShardedDaemonProcessCommand] = {
- val classTag = ClassTag[T](messageClass)
- internalInitWithContext[T](name, initialNumberOfInstances,
behaviorFactory.apply, None, None, None, true)(classTag)
+ implicit val classTag: ClassTag[T] = ClassTag[T](messageClass)
+ internalInitWithContext[T](name, initialNumberOfInstances,
behaviorFactory.apply, None, None, None, true)
}
override def initWithContext[T](
@@ -290,7 +297,7 @@ private[pekko] final class ShardedDaemonProcessImpl(system:
ActorSystem[?])
settings: ShardedDaemonProcessSettings,
stopMessage: Optional[T],
shardAllocationStrategy: Optional[ShardAllocationStrategy]):
ActorRef[ShardedDaemonProcessCommand] = {
- val classTag = ClassTag[T](messageClass)
+ implicit val classTag: ClassTag[T] = ClassTag[T](messageClass)
internalInitWithContext(
name,
initialNumberOfInstances,
@@ -298,6 +305,6 @@ private[pekko] final class ShardedDaemonProcessImpl(system:
ActorSystem[?])
Some(settings),
stopMessage.toScala,
shardAllocationStrategy.toScala,
- supportsRescale = true)(classTag)
+ supportsRescale = true)
}
}
diff --git
a/persistence-testkit/src/main/scala/org/apache/pekko/persistence/testkit/javadsl/EventSourcedBehaviorTestKit.scala
b/persistence-testkit/src/main/scala/org/apache/pekko/persistence/testkit/javadsl/EventSourcedBehaviorTestKit.scala
index 3695c8ef30..f99316a4dd 100644
---
a/persistence-testkit/src/main/scala/org/apache/pekko/persistence/testkit/javadsl/EventSourcedBehaviorTestKit.scala
+++
b/persistence-testkit/src/main/scala/org/apache/pekko/persistence/testkit/javadsl/EventSourcedBehaviorTestKit.scala
@@ -161,8 +161,10 @@ object EventSourcedBehaviorTestKit {
* The first event as a given expected type. It will throw
`AssertionError` if there is no event or
* if the event is of a different type.
*/
- def eventOfType[E <: Event](eventClass: Class[E]): E =
- delegate.eventOfType(ClassTag[E](eventClass))
+ def eventOfType[E <: Event](eventClass: Class[E]): E = {
+ implicit val ct: ClassTag[E] = ClassTag(eventClass)
+ delegate.eventOfType[E]
+ }
/**
* The state after applying the events.
@@ -173,8 +175,10 @@ object EventSourcedBehaviorTestKit {
/**
* The state as a given expected type. It will throw `AssertionError` if
the state is of a different type.
*/
- def stateOfType[S <: State](stateClass: Class[S]): S =
- delegate.stateOfType(ClassTag[S](stateClass))
+ def stateOfType[S <: State](stateClass: Class[S]): S = {
+ implicit val ct: ClassTag[S] = ClassTag(stateClass)
+ delegate.stateOfType[S]
+ }
}
/**
@@ -195,8 +199,10 @@ object EventSourcedBehaviorTestKit {
* The reply as a given expected type. It will throw `AssertionError` if
there is no reply or
* if the reply is of a different type.
*/
- def replyOfType[R <: Reply](replyClass: Class[R]): R =
- delegate.replyOfType(ClassTag[R](replyClass))
+ def replyOfType[R <: Reply](replyClass: Class[R]): R = {
+ implicit val ct: ClassTag[R] = ClassTag(replyClass)
+ delegate.replyOfType[R]
+ }
/**
* `true` if there is no reply.
diff --git
a/persistence/src/main/scala/org/apache/pekko/persistence/PersistencePlugin.scala
b/persistence/src/main/scala/org/apache/pekko/persistence/PersistencePlugin.scala
index 85e23d44e1..2ac73b6acd 100644
---
a/persistence/src/main/scala/org/apache/pekko/persistence/PersistencePlugin.scala
+++
b/persistence/src/main/scala/org/apache/pekko/persistence/PersistencePlugin.scala
@@ -51,8 +51,8 @@ private[pekko] trait PluginProvider[T, ScalaDsl, JavaDsl] {
* INTERNAL API
*/
@InternalApi
-private[pekko] abstract class PersistencePlugin[ScalaDsl, JavaDsl, T:
ClassTag](system: ExtendedActorSystem)(
- implicit ev: PluginProvider[T, ScalaDsl, JavaDsl]) {
+private[pekko] abstract class PersistencePlugin[ScalaDsl, JavaDsl, T](system:
ExtendedActorSystem)(
+ implicit ct: ClassTag[T], ev: PluginProvider[T, ScalaDsl, JavaDsl]) {
private val plugins = new AtomicReference[Map[String,
ExtensionId[PluginHolder[ScalaDsl, JavaDsl]]]](Map.empty)
private val log = Logging(system, classOf[PersistencePlugin[?, ?, ?]])
diff --git
a/stream-testkit/src/main/scala/org/apache/pekko/stream/testkit/StreamTestKit.scala
b/stream-testkit/src/main/scala/org/apache/pekko/stream/testkit/StreamTestKit.scala
index 01193b39dc..909be24d30 100644
---
a/stream-testkit/src/main/scala/org/apache/pekko/stream/testkit/StreamTestKit.scala
+++
b/stream-testkit/src/main/scala/org/apache/pekko/stream/testkit/StreamTestKit.scala
@@ -370,8 +370,10 @@ object TestPublisher {
/**
* Java API
*/
- def expectCancellationWithCause[E <: Throwable](causeClass: Class[E]): E =
- expectCancellationWithCause()(ClassTag(causeClass))
+ def expectCancellationWithCause[E <: Throwable](causeClass: Class[E]): E =
{
+ implicit val ct: ClassTag[E] = ClassTag(causeClass)
+ expectCancellationWithCause()
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]