Not wrapping exceptions thrown in waitForUploadResult (#162)

* [storage-aws] Uploader move implementation to companion

* [app] Program Refactoring

* [storage-aws] AmazonTransferManager refactoring

* [lib] UnversionedMirrorArchive refactoring

* [console] Add Console.putStr

* [uishell] UIShell show chosen actions

* [storage-aws] AmazonTransferManager try to handle errors

* [uishell] UIShell avoid line wrap with long file paths

* [storage] Log when fetching remote summaries

* Handle exceptions thrown in waitForUploadResult

* [uishell] log errors

* [console] Swap batch/non-batch error messages

* fix tests
This commit is contained in:
Paul Campbell 2019-09-23 13:30:34 +01:00 committed by GitHub
parent 6adffd8da7
commit 5214bacc0b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 219 additions and 137 deletions

View file

@ -5,7 +5,7 @@ import net.kemitix.eip.zio.{Message, MessageChannel}
import net.kemitix.thorp.cli.CliArgs
import net.kemitix.thorp.config._
import net.kemitix.thorp.console._
import net.kemitix.thorp.domain.{Counters, StorageEvent}
import net.kemitix.thorp.domain.{Counters, SimpleLens, StorageEvent}
import net.kemitix.thorp.domain.StorageEvent.{
CopyEvent,
DeleteEvent,
@ -52,7 +52,7 @@ trait Program {
: ZIO[Any,
Nothing,
MessageChannel.ESender[
Storage with Config with FileSystem with Hasher with Clock with FileScanner,
Storage with Config with FileSystem with Hasher with Clock with FileScanner with Console,
Throwable,
UIEvent]] = UIO { uiChannel =>
(for {
@ -97,13 +97,13 @@ trait Program {
private def countActivities: (Counters, StorageEvent) => Counters =
(counters: Counters, s3Action: StorageEvent) => {
val increment: Int => Int = _ + 1
def increment: SimpleLens[Counters, Int] => Counters =
_.modify(_ + 1)(counters)
s3Action match {
case _: UploadEvent =>
Counters.uploaded.modify(increment)(counters)
case _: CopyEvent => Counters.copied.modify(increment)(counters)
case _: DeleteEvent => Counters.deleted.modify(increment)(counters)
case _: ErrorEvent => Counters.errors.modify(increment)(counters)
case _: UploadEvent => increment(Counters.uploaded)
case _: CopyEvent => increment(Counters.copied)
case _: DeleteEvent => increment(Counters.deleted)
case _: ErrorEvent => increment(Counters.errors)
case _ => counters
}
}

View file

@ -16,6 +16,7 @@ object Console {
trait Service {
def putMessageLn(line: ConsoleOut): ZIO[Console, Nothing, Unit]
def putStrLn(line: String): ZIO[Console, Nothing, Unit]
def putStr(line: String): ZIO[Console, Nothing, Unit]
}
trait Live extends Console {
@ -24,9 +25,15 @@ object Console {
putStrLn(line.en)
override def putStrLn(line: String): ZIO[Console, Nothing, Unit] =
putStrLnPrintStream(SConsole.out)(line)
override def putStr(line: String): ZIO[Console, Nothing, Unit] =
putStrPrintStream(SConsole.out)(line)
final def putStrLnPrintStream(stream: PrintStream)(
line: String): ZIO[Console, Nothing, Unit] =
UIO(SConsole.withOut(stream)(SConsole.println(line)))
final def putStrPrintStream(stream: PrintStream)(
line: String): ZIO[Console, Nothing, Unit] =
UIO(SConsole.withOut(stream)(SConsole.print(line)))
}
}
@ -46,6 +53,10 @@ object Console {
ZIO.succeed(())
}
override def putStr(line: String): ZIO[Console, Nothing, Unit] = {
val _ = output.accumulateAndGet(List(line), (a, b) => a ++ b)
ZIO.succeed(())
}
}
}
@ -57,6 +68,9 @@ object Console {
final def putStrLn(line: String): ZIO[Console, Nothing, Unit] =
ZIO.accessM(_.console putStrLn line)
final def putStr(line: String): ZIO[Console, Nothing, Unit] =
ZIO.accessM(_.console.putStr(line))
final def putMessageLn(line: ConsoleOut): ZIO[Console, Nothing, Unit] =
ZIO.accessM(_.console putMessageLn line)

View file

@ -62,8 +62,8 @@ object ConsoleOut {
final case class ErrorQueueEventOccurred(action: ActionSummary, e: Throwable)
extends ConsoleOut.WithBatchMode {
override def en: String =
s"${action.name} failed: ${action.keys}: ${e.getMessage}"
override def enBatch: String =
s"${RED}ERROR:$RESET ${action.name} ${action.keys}: ${e.getMessage}$eraseToEndOfScreen"
override def enBatch: String =
s"${action.name} failed: ${action.keys}: ${e.getMessage}"
}
}

View file

@ -100,7 +100,10 @@ object LocalFileSystem extends LocalFileSystem {
sequencedAction = SequencedAction(action, actionCounter)
event <- archive.update(uiChannel, sequencedAction, bytesCounter)
_ <- eventsRef.update(list => event :: list)
_ <- uiActionFinished(uiChannel)(action, actionCounter, bytesCounter)
_ <- uiActionFinished(uiChannel)(action,
actionCounter,
bytesCounter,
event)
} yield ()
}
@ -112,9 +115,11 @@ object LocalFileSystem extends LocalFileSystem {
private def uiActionFinished(uiChannel: UChannel[Any, UIEvent])(
action: Action,
actionCounter: Int,
bytesCounter: Long
bytesCounter: Long,
event: StorageEvent
) =
Message.create(UIEvent.ActionFinished(action, actionCounter, bytesCounter)) >>=
Message.create(
UIEvent.ActionFinished(action, actionCounter, bytesCounter, event)) >>=
MessageChannel.send(uiChannel)
private def uiFileFound(uiChannel: UChannel[Any, UIEvent])(
@ -246,7 +251,8 @@ object LocalFileSystem extends LocalFileSystem {
_ <- eventsRef.update(list => event :: list)
_ <- uiActionFinished(uiChannel)(action,
actionCounter,
bytesCounter)
bytesCounter,
event)
} yield ()
}
} yield ()

View file

@ -35,19 +35,30 @@ trait UnversionedMirrorArchive extends ThorpArchive {
localFile: LocalFile
) =
for {
batchMode <- Config.batchMode
upload <- Storage.upload(
localFile,
bucket,
UploadEventListener.Settings(
uiChannel: UChannel[Any, UIEvent],
localFile,
index,
totalBytesSoFar,
batchMode
)
)
settings <- listenerSettings(uiChannel,
index,
totalBytesSoFar,
bucket,
localFile)
upload <- Storage.upload(localFile, bucket, settings)
} yield upload
private def listenerSettings(
uiChannel: UChannel[Any, UIEvent],
index: Int,
totalBytesSoFar: Long,
bucket: Bucket,
localFile: LocalFile
) =
for {
batchMode <- Config.batchMode
} yield
UploadEventListener.Settings(uiChannel,
localFile,
index,
totalBytesSoFar,
batchMode)
}
object UnversionedMirrorArchive extends UnversionedMirrorArchive

View file

@ -337,7 +337,7 @@ class LocalFileSystemTest extends FreeSpec {
String.format("action chosen : %s : %s",
action.remoteKey.key,
action.getClass.getSimpleName)
case ActionFinished(action, actionCounter, bytesCounter) =>
case ActionFinished(action, actionCounter, bytesCounter, event) =>
String.format("action finished : %s : %s",
action.remoteKey.key,
action.getClass.getSimpleName)

View file

@ -2,15 +2,12 @@ package net.kemitix.thorp.storage.aws
import com.amazonaws.services.s3.model.PutObjectRequest
import com.amazonaws.services.s3.transfer.TransferManager
import net.kemitix.thorp.storage.aws.AmazonUpload.{
CompletableUpload,
InProgress
}
import zio.{Task, UIO}
import net.kemitix.thorp.storage.aws.AmazonUpload.InProgress
import zio.{Task, UIO, ZIO}
trait AmazonTransferManager {
def shutdownNow(now: Boolean): UIO[Unit]
def upload: PutObjectRequest => Task[InProgress]
def upload: PutObjectRequest => UIO[InProgress]
}
object AmazonTransferManager {
@ -20,11 +17,17 @@ object AmazonTransferManager {
def shutdownNow(now: Boolean): UIO[Unit] =
UIO(transferManager.shutdownNow(now))
def upload: PutObjectRequest => Task[InProgress] =
def upload: PutObjectRequest => UIO[InProgress] =
putObjectRequest =>
Task(transferManager.upload(putObjectRequest))
.map(CompletableUpload)
transfer(transferManager, putObjectRequest)
.mapError(e => InProgress.Errored(e))
.catchAll(e => UIO(e))
}
private def transfer(transferManager: TransferManager,
putObjectRequest: PutObjectRequest): Task[InProgress] =
ZIO
.effect(transferManager.upload(putObjectRequest))
.map(InProgress.CompletableUpload)
}

View file

@ -2,16 +2,27 @@ package net.kemitix.thorp.storage.aws
import com.amazonaws.services.s3.transfer.Upload
import com.amazonaws.services.s3.transfer.model.UploadResult
import zio.Task
object AmazonUpload {
// unsealed for testing :(
trait InProgress {
def waitForUploadResult: UploadResult
def waitForUploadResult: Task[UploadResult]
}
final case class CompletableUpload(upload: Upload) extends InProgress {
override def waitForUploadResult: UploadResult =
upload.waitForUploadResult()
object InProgress {
final case class Errored(e: Throwable) extends InProgress {
override def waitForUploadResult: Task[UploadResult] =
Task.fail(e)
}
final case class CompletableUpload(upload: Upload) extends InProgress {
override def waitForUploadResult: Task[UploadResult] =
Task(upload.waitForUploadResult())
}
}
}

View file

@ -5,6 +5,7 @@ import com.amazonaws.services.s3.model.{
ListObjectsV2Result,
S3ObjectSummary
}
import net.kemitix.thorp.console.Console
import net.kemitix.thorp.domain.{Bucket, RemoteKey, RemoteObjects}
import net.kemitix.thorp.storage.Storage
import net.kemitix.thorp.storage.aws.S3ObjectsByHash.byHash
@ -21,7 +22,7 @@ trait Lister {
def listObjects(amazonS3: AmazonS3.Client)(
bucket: Bucket,
prefix: RemoteKey
): RIO[Storage, RemoteObjects] = {
): RIO[Storage with Console, RemoteObjects] = {
def request =
new ListObjectsV2Request()
@ -31,16 +32,19 @@ trait Lister {
def requestMore: Token => ListObjectsV2Request =
token => request.withContinuationToken(token)
def fetchBatch: ListObjectsV2Request => Task[Batch] =
request => tryFetchBatch(amazonS3)(request)
def fetchBatch: ListObjectsV2Request => RIO[Console, Batch] =
request =>
for {
_ <- Console.putStrLn("Fetching remote summaries...")
batch <- tryFetchBatch(amazonS3)(request)
} yield batch
def fetchMore: Option[Token] => Task[LazyList[S3ObjectSummary]] = {
def fetchMore: Option[Token] => RIO[Console, LazyList[S3ObjectSummary]] = {
case None => RIO.succeed(LazyList.empty)
case Some(token) => fetch(requestMore(token))
}
def fetch: ListObjectsV2Request => Task[LazyList[S3ObjectSummary]] =
def fetch: ListObjectsV2Request => RIO[Console, LazyList[S3ObjectSummary]] =
request =>
for {
batch <- fetchBatch(request)

View file

@ -1,10 +0,0 @@
package net.kemitix.thorp.storage.aws
import net.kemitix.thorp.console._
import zio.RIO
trait ListerLogger {
def logFetchBatch: RIO[Console, Unit] =
Console.putStrLn("Fetching remote summaries...")
}
object ListerLogger extends ListerLogger

View file

@ -2,6 +2,7 @@ package net.kemitix.thorp.storage.aws
import com.amazonaws.services.s3.AmazonS3ClientBuilder
import com.amazonaws.services.s3.transfer.TransferManagerBuilder
import net.kemitix.thorp.console.Console
import net.kemitix.thorp.domain.StorageEvent.ShutdownEvent
import net.kemitix.thorp.domain._
import net.kemitix.thorp.storage.Storage
@ -19,8 +20,9 @@ object S3Storage {
AmazonTransferManager.Wrapper(
TransferManagerBuilder.defaultTransferManager)
override def listObjects(bucket: Bucket,
prefix: RemoteKey): RIO[Storage, RemoteObjects] =
override def listObjects(
bucket: Bucket,
prefix: RemoteKey): RIO[Storage with Console, RemoteObjects] =
Lister.listObjects(client)(bucket, prefix)
override def upload(

View file

@ -11,13 +11,7 @@ import net.kemitix.thorp.domain.StorageEvent.{
ErrorEvent,
UploadEvent
}
import net.kemitix.thorp.domain.{
Bucket,
LocalFile,
MD5Hash,
RemoteKey,
StorageEvent
}
import net.kemitix.thorp.domain._
import net.kemitix.thorp.storage.aws.Uploader.Request
import net.kemitix.thorp.uishell.UploadProgressEvent.{
ByteTransferEvent,
@ -29,43 +23,48 @@ import zio.UIO
trait Uploader {
def upload(transferManager: => AmazonTransferManager)(
request: Request): UIO[StorageEvent] =
transfer(transferManager)(request)
.catchAll(handleError(request.localFile.remoteKey))
def upload(
transferManager: => AmazonTransferManager
)(request: Request): UIO[StorageEvent] =
transfer(
transferManager,
putObjectRequest(request),
request.localFile.remoteKey
)
private def handleError(remoteKey: RemoteKey)(
e: Throwable): UIO[StorageEvent] =
UIO(ErrorEvent(ActionSummary.Upload(remoteKey.key), remoteKey, e))
private def transfer(transferManager: => AmazonTransferManager)(
request: Request
) =
dispatch(transferManager)(putObjectRequest(request))
private def dispatch(transferManager: AmazonTransferManager)(
putObjectRequest: PutObjectRequest
) = {
private def transfer(transferManager: AmazonTransferManager,
putObjectRequest: PutObjectRequest,
remoteKey: RemoteKey): UIO[StorageEvent] = {
transferManager
.upload(putObjectRequest)
.map(_.waitForUploadResult)
.map(uploadResult =>
UploadEvent(RemoteKey(uploadResult.getKey),
MD5Hash(uploadResult.getETag)))
.flatMap(_.waitForUploadResult)
.map(
uploadResult =>
UploadEvent(
RemoteKey(uploadResult.getKey),
MD5Hash(uploadResult.getETag)
)
)
.catchAll(handleError(remoteKey))
}
private def putObjectRequest(
request: Request
) = {
private def handleError(
remoteKey: RemoteKey
)(e: Throwable): UIO[StorageEvent] =
UIO(ErrorEvent(ActionSummary.Upload(remoteKey.key), remoteKey, e))
private def putObjectRequest(request: Request) = {
val putRequest =
new PutObjectRequest(request.bucket.name,
request.localFile.remoteKey.key,
request.localFile.file)
.withMetadata(metadata(request.localFile))
new PutObjectRequest(
request.bucket.name,
request.localFile.remoteKey.key,
request.localFile.file
).withMetadata(metadata(request.localFile))
if (request.uploadEventListener.batchMode) putRequest
else
putRequest.withGeneralProgressListener(
progressListener(request.uploadEventListener))
progressListener(request.uploadEventListener)
)
}
private def metadata: LocalFile => ObjectMetadata = localFile => {
@ -98,9 +97,11 @@ trait Uploader {
case e: ProgressEvent if isByteTransfer(e) =>
ByteTransferEvent(e.getEventType.name)
case e: ProgressEvent =>
RequestEvent(e.getEventType.name,
e.getBytes,
e.getBytesTransferred)
RequestEvent(
e.getEventType.name,
e.getBytes,
e.getBytesTransferred
)
}
}
}
@ -108,9 +109,7 @@ trait Uploader {
}
object Uploader extends Uploader {
final case class Request(
localFile: LocalFile,
bucket: Bucket,
uploadEventListener: UploadEventListener.Settings
)
final case class Request(localFile: LocalFile,
bucket: Bucket,
uploadEventListener: UploadEventListener.Settings)
}

View file

@ -1,5 +1,6 @@
package net.kemitix.thorp.storage.aws
import net.kemitix.thorp.console.Console
import net.kemitix.thorp.domain.StorageEvent.ShutdownEvent
import net.kemitix.thorp.domain._
import net.kemitix.thorp.storage.Storage
@ -28,7 +29,7 @@ trait AmazonS3ClientTestFixture extends MockFactory {
override def listObjects(
bucket: Bucket,
prefix: RemoteKey
): RIO[Storage, RemoteObjects] =
): RIO[Storage with Console, RemoteObjects] =
Lister.listObjects(client)(bucket, prefix)
override def upload(

View file

@ -8,11 +8,12 @@ import com.amazonaws.services.s3.model.{
ListObjectsV2Result,
S3ObjectSummary
}
import net.kemitix.thorp.console.Console
import net.kemitix.thorp.domain._
import net.kemitix.thorp.storage.Storage
import org.scalatest.FreeSpec
import org.scalatest.Matchers._
import zio.{DefaultRuntime, Task, UIO}
import zio.{DefaultRuntime, RIO, Task, UIO}
class ListerTest extends FreeSpec {
@ -114,12 +115,13 @@ class ListerTest extends FreeSpec {
}
}
def invoke(amazonS3Client: AmazonS3.Client)(bucket: Bucket,
prefix: RemoteKey) =
new DefaultRuntime {}.unsafeRunSync {
Lister
.listObjects(amazonS3Client)(bucket, prefix)
.provide(Storage.Test)
}.toEither
prefix: RemoteKey) = {
object TestEnv extends Storage.Test with Console.Test
val program: RIO[Storage with Console, RemoteObjects] = Lister
.listObjects(amazonS3Client)(bucket, prefix)
val runtime = new DefaultRuntime {}
runtime.unsafeRunSync(program.provide(TestEnv)).toEither
}
}

View file

@ -16,7 +16,7 @@ import net.kemitix.thorp.domain.StorageEvent.{
import net.kemitix.thorp.domain._
import org.scalamock.scalatest.MockFactory
import org.scalatest.FreeSpec
import zio.{DefaultRuntime, Task}
import zio.{DefaultRuntime, Task, UIO}
import net.kemitix.thorp.filesystem.Resource
import net.kemitix.thorp.uishell.{UIEvent, UploadEventListener}
@ -35,18 +35,19 @@ class UploaderTest extends FreeSpec with MockFactory {
val uploadResult = new UploadResult
uploadResult.setKey(remoteKey.key)
uploadResult.setETag(MD5Hash.hash(aHash))
val inProgress = new AmazonUpload.InProgress {
override def waitForUploadResult: UploadResult = uploadResult
}
val listenerSettings =
UploadEventListener.Settings(uiChannel, localFile, 0, 0, batchMode = true)
"when no error" in {
val expected =
Right(UploadEvent(remoteKey, aHash))
val inProgress = new AmazonUpload.InProgress {
override def waitForUploadResult: Task[UploadResult] =
Task(uploadResult)
}
new AmazonS3ClientTestFixture {
(fixture.amazonS3TransferManager.upload _)
.when()
.returns(_ => Task.succeed(inProgress))
.returns(_ => UIO.succeed(inProgress))
private val result =
invoke(fixture.amazonS3TransferManager)(
localFile,
@ -61,10 +62,14 @@ class UploaderTest extends FreeSpec with MockFactory {
val expected =
Right(
ErrorEvent(ActionSummary.Upload(remoteKey.key), remoteKey, exception))
val inProgress = new AmazonUpload.InProgress {
override def waitForUploadResult: Task[UploadResult] =
Task.fail(exception)
}
new AmazonS3ClientTestFixture {
(fixture.amazonS3TransferManager.upload _)
.when()
.returns(_ => Task.fail(exception))
.returns(_ => UIO.succeed(inProgress))
private val result =
invoke(fixture.amazonS3TransferManager)(
localFile,
@ -79,10 +84,14 @@ class UploaderTest extends FreeSpec with MockFactory {
val expected =
Right(
ErrorEvent(ActionSummary.Upload(remoteKey.key), remoteKey, exception))
val inProgress = new AmazonUpload.InProgress {
override def waitForUploadResult: Task[UploadResult] =
Task.fail(exception)
}
new AmazonS3ClientTestFixture {
(fixture.amazonS3TransferManager.upload _)
.when()
.returns(_ => Task.fail(exception))
.returns(_ => UIO.succeed(inProgress))
private val result =
invoke(fixture.amazonS3TransferManager)(
localFile,
@ -97,14 +106,15 @@ class UploaderTest extends FreeSpec with MockFactory {
bucket: Bucket,
listenerSettings: UploadEventListener.Settings
) = {
type TestEnv = Config
val testEnv: TestEnv = Config.Live
new DefaultRuntime {}.unsafeRunSync {
Uploader
.upload(transferManager)(
Uploader.Request(localFile, bucket, listenerSettings))
.provide(testEnv)
}.toEither
val program = Uploader
.upload(transferManager)(
Uploader.Request(localFile, bucket, listenerSettings))
val runtime = new DefaultRuntime {}
runtime
.unsafeRunSync(
program
.provide(Config.Live))
.toEither
}
}

View file

@ -1,5 +1,6 @@
package net.kemitix.thorp.storage
import net.kemitix.thorp.console.Console
import net.kemitix.thorp.domain._
import net.kemitix.thorp.uishell.UploadEventListener
import zio.{RIO, Task, UIO, ZIO}
@ -14,7 +15,7 @@ object Storage {
def listObjects(
bucket: Bucket,
prefix: RemoteKey
): RIO[Storage, RemoteObjects]
): RIO[Storage with Console, RemoteObjects]
def upload(
localFile: LocalFile,
@ -83,7 +84,7 @@ object Storage {
object Test extends Test
final def list(bucket: Bucket,
prefix: RemoteKey): RIO[Storage, RemoteObjects] =
prefix: RemoteKey): RIO[Storage with Console, RemoteObjects] =
ZIO.accessM(_.storage listObjects (bucket, prefix))
final def upload(

View file

@ -29,7 +29,8 @@ object UIEvent {
case class ActionFinished(action: Action,
actionCounter: Int,
bytesCounter: Long)
bytesCounter: Long,
event: StorageEvent)
extends UIEvent
case class KeyFound(remoteKey: RemoteKey) extends UIEvent

View file

@ -5,6 +5,7 @@ import net.kemitix.thorp.config.Config
import net.kemitix.thorp.console.ConsoleOut.{
CopyComplete,
DeleteComplete,
ErrorQueueEventOccurred,
UploadComplete
}
import net.kemitix.thorp.console.{Console, ConsoleOut}
@ -29,13 +30,14 @@ object UIShell {
case UIEvent.RemoteDataFetched(size) => remoteDataFetched(size)
case UIEvent.ShowSummary(counters) => showSummary(counters)
case UIEvent.FileFound(localFile) => fileFound(localFile)
case UIEvent.ActionChosen(action) => UIO(())
case UIEvent.ActionChosen(action) => actionChosen(action)
case UIEvent.AwaitingAnotherUpload(remoteKey, hash) =>
awaitingUpload(remoteKey, hash)
case UIEvent.AnotherUploadWaitComplete(action) =>
uploadWaitComplete(action)
case UIEvent.ActionFinished(action, _, _) => actionFinished(action)
case UIEvent.KeyFound(_) => UIO(())
case UIEvent.ActionFinished(_, _, _, event) =>
actionFinished(event)
case UIEvent.KeyFound(_) => UIO(())
case UIEvent.RequestCycle(localFile,
bytesTransferred,
index,
@ -45,17 +47,20 @@ object UIShell {
}
private def actionFinished(
action: Action): ZIO[Console with Config, Nothing, Unit] =
event: StorageEvent): ZIO[Console with Config, Nothing, Unit] =
for {
batchMode <- Config.batchMode
_ <- action match {
case _: Action.DoNothing => UIO(())
case ToUpload(_, localFile, _) =>
Console.putMessageLnB(UploadComplete(localFile.remoteKey), batchMode)
case Action.ToCopy(_, sourceKey, _, targetKey, _) =>
_ <- event match {
case StorageEvent.DoNothingEvent(remoteKey) => UIO.unit
case StorageEvent.CopyEvent(sourceKey, targetKey) =>
Console.putMessageLnB(CopyComplete(sourceKey, targetKey), batchMode)
case Action.ToDelete(_, remoteKey, _) =>
case StorageEvent.UploadEvent(remoteKey, md5Hash) =>
Console.putMessageLnB(UploadComplete(remoteKey), batchMode)
case StorageEvent.DeleteEvent(remoteKey) =>
Console.putMessageLnB(DeleteComplete(remoteKey), batchMode)
case StorageEvent.ErrorEvent(action, remoteKey, e) =>
Console.putMessageLnB(ErrorQueueEventOccurred(action, e), batchMode)
case StorageEvent.ShutdownEvent() => UIO.unit
}
} yield ()
@ -111,4 +116,26 @@ object UIShell {
s"${Terminal.cursorPrevLine(statusHeight)}")
}
private def actionAsString(action: Action): String = action match {
case Action.DoNothing(bucket, remoteKey, size) =>
s"Do nothing: ${remoteKey.key}"
case ToUpload(bucket, localFile, size) => s"Upload: ${localFile.remoteKey}"
case Action.ToCopy(bucket, sourceKey, hash, targetKey, size) =>
s"Copy: ${sourceKey.key} => ${targetKey.key}"
case Action.ToDelete(bucket, remoteKey, size) => s"Delete: ${remoteKey.key}"
}
def trimHeadTerminal(str: String): String = {
val width = Terminal.width
str.length match {
case l if l > width => str.substring(l - width)
case _ => str
}
}
def actionChosen(action: Action): ZIO[Console with Config, Nothing, Unit] = {
Console.putStr(
trimHeadTerminal(actionAsString(action)) + eraseLineForward + "\r")
}
}