Skip to content

Commit

Permalink
Merge pull request #47 from nao1215/feat/s3hub-cp-tui
Browse files Browse the repository at this point in the history
Feat: s3hub list up s3 objects
  • Loading branch information
nao1215 authored Feb 3, 2024
2 parents a09bcce + 84b9a68 commit 06b178b
Show file tree
Hide file tree
Showing 11 changed files with 800 additions and 286 deletions.
21 changes: 13 additions & 8 deletions app/external/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strings"

"github.com/aws/aws-sdk-go-v2/aws"
v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/google/wire"
Expand Down Expand Up @@ -358,14 +359,18 @@ func NewS3ObjectUploader(client *s3.Client) *S3ObjectUploader {

// UploadS3Object puts the object in the bucket.
func (c *S3ObjectUploader) UploadS3Object(ctx context.Context, input *service.S3ObjectUploaderInput) (*service.S3ObjectUploaderOutput, error) {
_, err := c.client.PutObject(ctx, &s3.PutObjectInput{
Bucket: aws.String(input.Bucket.String()),
Key: aws.String(input.S3Key.String()),
Body: input.S3Object,
ContentType: aws.String(input.S3Object.ContentType()),
ContentLength: aws.Int64(input.S3Object.ContentLength()),
})
if err != nil {
if _, err := c.client.PutObject(
ctx,
&s3.PutObjectInput{
Bucket: aws.String(input.Bucket.String()),
Key: aws.String(input.S3Key.String()),
Body: input.S3Object,
ContentType: aws.String(input.S3Object.ContentType()),
ContentLength: aws.Int64(input.S3Object.ContentLength()),
},
s3.WithAPIOptions(
v4.SwapComputePayloadSHA256ForUnsignedPayloadMiddleware,
)); err != nil {
return nil, err
}

Expand Down
6 changes: 6 additions & 0 deletions config/s3hub/download.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package s3hub

const (
// DefaultDownloadDirPath is the default download directory.
DefaultDownloadDirPath = "s3hub-download"
)
23 changes: 21 additions & 2 deletions ui/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,12 @@ func ErrorMessage(err error) string {

// Choice represents a choice.
type Choice struct {
// Choice is the currently selected menu item.
Choice int
Max int
Min int
// Max is the maximum choice number.
Max int
// Min is the minimum choice number.
Min int
}

// NewChoice returns a new choice.
Expand Down Expand Up @@ -146,3 +149,19 @@ func NewToggleSets(n int) ToggleSets {
}
return ts
}

// Window represents the window size.
type Window struct {
// Width is the window width.
Width int
// Height is the window height.
Height int
}

// NewWindow returns a new window.
func NewWindow(width, height int) *Window {
return &Window{
Width: width,
Height: height,
}
}
178 changes: 178 additions & 0 deletions ui/s3hub/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,44 @@ import (
"crypto/rand"
"fmt"
"math/big"
"os"
"path/filepath"
"time"

tea "github.com/charmbracelet/bubbletea"
"github.com/fatih/color"
"github.com/gogf/gf/os/gfile"
"github.com/nao1215/rainbow/app/di"
"github.com/nao1215/rainbow/app/domain/model"
"github.com/nao1215/rainbow/app/usecase"
"github.com/nao1215/rainbow/config/s3hub"
"github.com/nao1215/rainbow/ui"
"golang.org/x/sync/errgroup"
"golang.org/x/sync/semaphore"
)

// createMsg is the message that is sent when the user wants to create the S3 bucket.
type createMsg struct{}

// createS3BucketCmd creates the S3 bucket command.
func (m *s3hubCreateBucketModel) createS3BucketCmd() tea.Cmd {
return tea.Cmd(func() tea.Msg {
if m.app == nil {
return ui.ErrMsg(fmt.Errorf("not initialized s3 application. please restart the application"))
}
input := &usecase.S3BucketCreatorInput{
Bucket: m.bucket,
Region: m.region,
}
m.status = statusBucketCreating

if _, err := m.app.S3BucketCreator.CreateS3Bucket(m.ctx, input); err != nil {
return ui.ErrMsg(err)
}
return createMsg{}
})
}

// fetchS3BucketMsg is the message that is sent when the user wants to fetch the list of the S3 buckets.
type fetchS3BucketMsg struct {
buckets model.BucketSets
Expand All @@ -34,6 +61,124 @@ func fetchS3BucketListCmd(ctx context.Context, app *di.S3App) tea.Cmd {
})
}

// fetchS3Keys is the message that is sent when the user wants to fetch the list of the S3 bucket objects.
type fetchS3Keys struct {
keys []model.S3Key
}

// fetchS3KeysCmd creates a command to fetch the keys of objects stored in a specified S3 bucket.
func fetchS3KeysCmd(ctx context.Context, app *di.S3App, bucket model.Bucket) tea.Cmd {
return tea.Cmd(func() tea.Msg {
output, err := app.S3ObjectsLister.ListS3Objects(ctx, &usecase.S3ObjectsListerInput{
Bucket: bucket,
})
if err != nil {
return ui.ErrMsg(err)
}

keys := make([]model.S3Key, 0, len(output.Objects))
for _, o := range output.Objects {
keys = append(keys, o.S3Key)
}
return fetchS3Keys{
keys: keys,
}
})
}

// downloadS3BucketMsg is the message that is sent when the user wants to download the S3 bucket.
type downloadS3BucketMsg struct {
downloadedBuckets model.Bucket
}

// downloadS3BucketCmd downloads the S3 bucket.
func downloadS3BucketCmd(ctx context.Context, app *di.S3App, bucket model.Bucket) tea.Cmd {
d, err := rand.Int(rand.Reader, big.NewInt(500))
if err != nil {
return func() tea.Msg {
return ui.ErrMsg(fmt.Errorf("failed to start download s3 bucket: %w", err))
}
}
delay := time.Millisecond * time.Duration(d.Int64())

return tea.Tick(delay, func(t time.Time) tea.Msg {
output, err := app.S3ObjectsLister.ListS3Objects(ctx, &usecase.S3ObjectsListerInput{
Bucket: bucket,
})
if err != nil {
return ui.ErrMsg(err)
}

for _, v := range output.Objects {
downloadOutput, err := app.S3ObjectDownloader.DownloadS3Object(ctx, &usecase.S3ObjectDownloaderInput{
Bucket: bucket,
Key: v.S3Key,
})
if err != nil {
return ui.ErrMsg(err)
}

destinationPath := filepath.Clean(filepath.Join(s3hub.DefaultDownloadDirPath, bucket.String(), v.S3Key.String()))
dir := filepath.Dir(destinationPath)
if !gfile.IsDir(dir) {
if err := os.MkdirAll(dir, 0750); err != nil {
return ui.ErrMsg(fmt.Errorf("can not create directory %s: %w", color.YellowString(dir), err))
}
}

if err := downloadOutput.S3Object.ToFile(destinationPath, 0644); err != nil {
return ui.ErrMsg(fmt.Errorf("can not write file to %s: %w", color.YellowString(destinationPath), err))
}
}
return downloadS3BucketMsg{
downloadedBuckets: bucket,
}
})
}

// downloadS3ObjectsMsg is the message that is sent when the user wants to download the S3 bucket objects.
type downloadS3ObjectsMsg struct {
downloadedS3Key model.S3Key
}

// downloadS3ObjectsCmd downloads the S3 bucket objects.
func downloadS3ObjectsCmd(ctx context.Context, app *di.S3App, bucket model.Bucket, key model.S3Key) tea.Cmd {
d, err := rand.Int(rand.Reader, big.NewInt(500))
if err != nil {
return func() tea.Msg {
return ui.ErrMsg(fmt.Errorf("failed to start download s3 object: %w", err))
}
}
delay := time.Millisecond * time.Duration(d.Int64())

return tea.Tick(delay, func(t time.Time) tea.Msg {
downloadOutput, err := app.S3ObjectDownloader.DownloadS3Object(ctx, &usecase.S3ObjectDownloaderInput{
Bucket: bucket,
Key: key,
})
if err != nil {
return ui.ErrMsg(err)
}

destinationPath := filepath.Clean(filepath.Join(s3hub.DefaultDownloadDirPath, bucket.String(), key.String()))
dir := filepath.Dir(destinationPath)
if !gfile.IsDir(dir) {
if err := os.MkdirAll(dir, 0750); err != nil {
return ui.ErrMsg(fmt.Errorf("can not create directory %s: %w", color.YellowString(dir), err))
}
}

if err := downloadOutput.S3Object.ToFile(destinationPath, 0644); err != nil {
return ui.ErrMsg(fmt.Errorf("can not write file to %s: %w", color.YellowString(destinationPath), err))
}

return downloadS3ObjectsMsg{
downloadedS3Key: key,
}
})
}

// deleteS3BucketMsg is the message that is sent when the user wants to delete the S3 bucket.
type deleteS3BucketMsg struct {
deletedBucket model.Bucket
}
Expand Down Expand Up @@ -111,3 +256,36 @@ func divideIntoChunks(slice []model.S3ObjectIdentifier, chunkSize int) [][]model
}
return chunks
}

// deleteS3ObjectMsg is the message that is sent when the user wants to delete the S3 object.
type deleteS3ObjectMsg struct {
deletedS3Key model.S3Key
}

// deleteS3ObjectCmd deletes the S3 object.
func deleteS3ObjectCmd(ctx context.Context, app *di.S3App, bucket model.Bucket, key model.S3Key) tea.Cmd {
d, err := rand.Int(rand.Reader, big.NewInt(500))
if err != nil {
return func() tea.Msg {
return ui.ErrMsg(fmt.Errorf("failed to start deleting s3 bucket: %w", err))
}
}
delay := time.Millisecond * time.Duration(d.Int64())

return tea.Tick(delay, func(t time.Time) tea.Msg {
_, err := app.S3ObjectsDeleter.DeleteS3Objects(ctx, &usecase.S3ObjectsDeleterInput{
Bucket: bucket,
S3ObjectSets: []model.S3ObjectIdentifier{
{
S3Key: key,
},
},
})
if err != nil {
return ui.ErrMsg(err)
}
return deleteS3ObjectMsg{
deletedS3Key: key,
}
})
}
Loading

0 comments on commit 06b178b

Please sign in to comment.