Skip to content

Commit

Permalink
Feat: s3hub list up s3 objects
Browse files Browse the repository at this point in the history
  • Loading branch information
nao1215 committed Jan 29, 2024
1 parent f4aa82b commit c84dd58
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 69 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
26 changes: 26 additions & 0 deletions ui/s3hub/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,32 @@ 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 fetches the list of the S3 bucket objects.
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,
}
})
}

// deleteS3BucketMsg is the message that is sent when the user wants to delete the S3 bucket.
type deleteS3BucketMsg struct {
deletedBucket model.Bucket
}
Expand Down
6 changes: 3 additions & 3 deletions ui/s3hub/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ func (m *s3hubDeleteBucketModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.WindowSizeMsg:
m.width, m.height = msg.Width, msg.Height
case fetchS3BucketMsg:
m.s3bucketListStatus = s3hubListBucketStatusBucketCreated
m.s3bucketListStatus = s3hubListBucketStatusBucketFetched
m.bucketSets = msg.buckets
m.choice = ui.NewChoice(0, m.bucketSets.Len()-1)
m.toggles = ui.NewToggleSets(m.bucketSets.Len())
Expand Down Expand Up @@ -274,13 +274,13 @@ func (m *s3hubDeleteBucketModel) View() string {
return spin + info + gap + prog + bucketCount
}

if m.s3bucketListStatus == s3hubListBucketStatusNone || m.s3bucketListStatus == s3hubListBucketStatusBucketCreating {
if m.s3bucketListStatus == s3hubListBucketStatusNone || m.s3bucketListStatus == s3hubListBucketStatusBucketFetching {
return fmt.Sprintf(
"fetching the list of the S3 buckets (profile=%s)\n",
m.awsProfile.String())
}

if m.s3bucketListStatus == s3hubListBucketStatusBucketCreated {
if m.s3bucketListStatus == s3hubListBucketStatusBucketFetched {
return m.bucketListString()
}
return m.bucketListString() // TODO: implement
Expand Down
35 changes: 0 additions & 35 deletions ui/s3hub/download.go

This file was deleted.

213 changes: 204 additions & 9 deletions ui/s3hub/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,12 @@ type s3hubListBucketStatus int
const (
// s3hubListBucketStatusNone is the status when the list bucket operation is not executed.
s3hubListBucketStatusNone s3hubListBucketStatus = iota
// s3hubListBucketStatusBucketCreating is the status when the list bucket operation is executed and the bucket is being created.
s3hubListBucketStatusBucketCreating
// s3hubListBucketStatusBucketCreated is the status when the list bucket operation is executed and the bucket is created.
s3hubListBucketStatusBucketCreated
// s3hubListBucketStatusBucketFetching is the status when the list bucket operation is executed.
s3hubListBucketStatusBucketFetching
// s3hubListBucketStatusBucketFetched is the status when the list bucket operation is executed and the bucket list is fetched.
s3hubListBucketStatusBucketFetched
// s3hubListBucketStatusBucketListed is the status when the list bucket operation is executed and the bucket list is displayed.
s3hubListBucketStatusBucketListed
// s3hubListBucketStatusObjectListed is the status when the list bucket operation is executed and the object list is displayed.
s3hubListBucketStatusObjectListed
// s3hubListBucketStatusReturnToTop is the status when the user returns to the top.
s3hubListBucketStatusReturnToTop
// s3hubListBucketStatusQuit is the status when the user quits the application.
Expand Down Expand Up @@ -108,11 +106,21 @@ func (m *s3hubListBucketModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.status == s3hubListBucketStatusReturnToTop {
return newRootModel(), nil
}
if m.status == s3hubListBucketStatusBucketListed {
model, err := newS3HubListS3ObjectModel()
if err != nil {
m.err = err
return m, tea.Quit
}
model.status = s3hubListS3ObjectStatusFetching
model.bucket = m.bucketSets[m.choice.Choice].Bucket
return model, fetchS3KeysCmd(m.ctx, m.app, model.bucket)
}
case "space":
// TODO: implement
}
case fetchS3BucketMsg:
m.status = s3hubListBucketStatusBucketCreated
m.status = s3hubListBucketStatusBucketFetched
m.bucketSets = msg.buckets
m.choice = ui.NewChoice(0, m.bucketSets.Len()-1)
return m, nil
Expand All @@ -136,13 +144,13 @@ func (m *s3hubListBucketModel) View() string {
return ui.GoodByeMessage()
}

if m.status == s3hubListBucketStatusNone || m.status == s3hubListBucketStatusBucketCreating {
if m.status == s3hubListBucketStatusNone || m.status == s3hubListBucketStatusBucketFetching {
return fmt.Sprintf(
"fetching the list of the S3 buckets (profile=%s)\n",
m.awsProfile.String())
}

if m.status == s3hubListBucketStatusBucketCreated {
if m.status == s3hubListBucketStatusBucketFetched {
return m.bucketListString()
}
return m.bucketListString() // TODO: implement
Expand Down Expand Up @@ -201,3 +209,190 @@ func (m *s3hubListBucketModel) emptyBucketListString() string {
m.awsProfile.String(),
ui.Subtle("<enter>: return to the top"))
}

// s3hubListS3ObjectStatus is the status of the list s3 objects operation.
type s3hubListS3ObjectStatus int

const (
// s3hubListBucketStatusNone is the status when the list bucket operation is not executed.
s3hubListS3ObjectStatusNone s3hubListS3ObjectStatus = iota
// s3hubListS3ObjectStatusFetching is the status when the list bucket operation is executed.
s3hubListS3ObjectStatusFetching
// s3hubListS3ObjectStatusFetched is the status when the list bucket operation is executed and the bucket list is fetched.
s3hubListS3ObjectStatusFetched
// s3hubListBucketStatusBucketListed is the status when the list bucket operation is executed and the bucket list is displayed.
s3hubListS3ObjectStatusListed
// s3hubListBucketStatusReturnToTop is the status when the user returns to the top.
s3hubListS3ObjectStatusReturnToTop
// s3hubListBucketStatusQuit is the status when the user quits the application.
s3hubListS3ObjectStatusQuit
)

type s3hubListS3ObjectModel struct {
// err is the error that occurred during the operation.
err error
// status is the status of the list s3 objects operation.
status s3hubListS3ObjectStatus
// awsConfig is the AWS configuration.
awsConfig *model.AWSConfig
// awsProfile is the AWS profile.
awsProfile model.AWSProfile
// region is the AWS region that the user wants to create the S3 bucket.
region model.Region
// choice is the currently selected menu item.
choice *ui.Choice
// app is the S3 application service.
app *di.S3App
// ctx is the context.
ctx context.Context
// bucket is the S3 bucket that the user wants to list the objects.
bucket model.Bucket
// s3Keys is the list of the S3 bucket objects.
s3Keys []model.S3Key
}

// newS3HubListS3ObjectModel returns a new s3hubListS3ObjectModel.
func newS3HubListS3ObjectModel() (*s3hubListS3ObjectModel, error) {
ctx := context.Background()
profile := model.NewAWSProfile("")
cfg, err := model.NewAWSConfig(ctx, profile, "")
if err != nil {
return nil, err
}
region := cfg.Region()

app, err := di.NewS3App(ctx, profile, region)
if err != nil {
return nil, err
}

return &s3hubListS3ObjectModel{
awsConfig: cfg,
awsProfile: profile,
region: region,
app: app,
choice: ui.NewChoice(0, 0),
ctx: ctx,
}, nil
}

// Init initializes the model.
func (m *s3hubListS3ObjectModel) Init() tea.Cmd {
return nil // Not called this method
}

// Update updates the model.
func (m *s3hubListS3ObjectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.err != nil {
return m, tea.Quit
}

switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "j", "down":
m.choice.Increment()
case "k", "up":
m.choice.Decrement()
case "ctrl+c":
return m, tea.Quit
case "q", "esc":
model, err := newS3HubListBucketModel()
if err != nil {
m.err = err
return m, tea.Quit
}
model.status = s3hubListBucketStatusBucketFetching
return model, fetchS3BucketListCmd(model.ctx, model.app)
}

case fetchS3Keys:
m.status = s3hubListS3ObjectStatusFetched
m.s3Keys = msg.keys
m.choice = ui.NewChoice(0, len(m.s3Keys)-1)
return m, nil
case ui.ErrMsg:
m.err = msg
m.status = s3hubListS3ObjectStatusQuit
return m, tea.Quit
default:
return m, nil
}
return m, nil
}

// View renders the application's UI.
func (m *s3hubListS3ObjectModel) View() string {
if m.err != nil {
m.status = s3hubListS3ObjectStatusQuit
return ui.ErrorMessage(m.err)
}

if m.status == s3hubListS3ObjectStatusQuit {
return ui.GoodByeMessage()
}

if m.status == s3hubListS3ObjectStatusNone || m.status == s3hubListS3ObjectStatusFetching {
return fmt.Sprintf(
"fetching the list of the S3 objects (profile=%s, bucket=%s)\n",
m.awsProfile.String(),
m.bucket.String())
}

if m.status == s3hubListS3ObjectStatusFetched {
return m.s3ObjectListString()
}
return m.s3ObjectListString()
}

// s3ObjectListString returns the string representation of the S3 object list.
func (m *s3hubListS3ObjectModel) s3ObjectListString() string {
switch len(m.s3Keys) {
case 0:
return m.emptyS3ObjectListString()
default:
return m.s3ObjectListStrWithCheckbox()
}
}

// s3ObjectListStrWithCheckbox generates the string representation of the S3 object list.
func (m *s3hubListS3ObjectModel) s3ObjectListStrWithCheckbox() string {
startIndex := 0
endIndex := len(m.s3Keys)

if m.choice.Choice >= windowHeight {
startIndex = m.choice.Choice - windowHeight + 1
endIndex = startIndex + windowHeight
if endIndex > len(m.s3Keys) {
startIndex = len(m.s3Keys) - windowHeight
endIndex = len(m.s3Keys)
}
} else {
if len(m.s3Keys) > windowHeight {
endIndex = windowHeight
}
}

m.status = s3hubListS3ObjectStatusListed
s := fmt.Sprintf("S3 objects %d/%d (profile=%s)\n\n", m.choice.Choice+1, len(m.s3Keys), m.awsProfile.String())
for i := startIndex; i < endIndex; i++ {
s += fmt.Sprintf("%s\n",
ui.Checkbox(
fmt.Sprintf(
"%s",
color.GreenString("%s", m.bucket.Join(m.s3Keys[i]))),
m.choice.Choice == i))
}
s += ui.Subtle("\n<esc>: return | <Ctrl-C>: quit | up/down: select\n")
s += ui.Subtle("<enter>, <space>: choose bucket\n\n")
return s
}

// emptyS3ObjectListString returns the string representation when there are no S3 objects.
func (m *s3hubListS3ObjectModel) emptyS3ObjectListString() string {
m.status = s3hubListS3ObjectStatusReturnToTop
return fmt.Sprintf("No S3 objects (profile=%s, bucket=%s)\n\n%s\n",
m.awsProfile.String(),
m.bucket.String(),
ui.Subtle("<esc>, q: return"))
}
Loading

0 comments on commit c84dd58

Please sign in to comment.