feat: 完成了 新建桶; 上传文件(基本功能)

todo: 上传 rename, 上传 public 权限选择
bug: 首次加载 conns list; 上传的时候前缀过滤失败
This commit is contained in:
zhaoyupeng
2024-10-12 17:35:59 +08:00
parent 1c818daf16
commit 777253063b
28 changed files with 791 additions and 96 deletions

View File

@ -24,6 +24,7 @@ func Resolve(path string) (ndh.Handler, bool) {
}
func Init(ctx context.Context) error {
register("/runtime/dialog/open", handler.DialogOpen(ctx))
register("/api/connection/test", handler.ConnectionTest)
register("/api/connection/create", handler.ConnectionCreate)
register("/api/connection/list", handler.ConnectionList)
@ -31,6 +32,8 @@ func Init(ctx context.Context) error {
register("/api/connection/disconnect", handler.ConnectionDisconnect)
register("/api/connection/buckets", handler.ConnectionBuckets)
register("/api/bucket/files", handler.BucketFiles)
register("/api/bucket/create", handler.BucketCreate)
register("/api/file/upload", handler.FileUpload)
return nil
}

View File

@ -9,6 +9,11 @@ import (
"github.com/loveuer/nf-disk/internal/tool"
"github.com/loveuer/nf-disk/ndh"
"github.com/loveuer/nf/nft/log"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
var (
app *App
)
type App struct {
@ -16,23 +21,24 @@ type App struct {
handlers map[string]ndh.Handler
}
func NewApp() *App {
return &App{
func NewApp(gctx context.Context) *App {
app = &App{
handlers: make(map[string]ndh.Handler),
}
go func() {
<-gctx.Done()
runtime.Quit(app.ctx)
}()
return app
}
func (a *App) Init(ctx context.Context) {
log.Info("app init!!!")
func (a *App) Startup(ctx context.Context) {
log.Info("app startup!!!")
a.ctx = ctx
tool.Must(db.Init(ctx, "sqlite::memory", db.OptSqliteByMem(nil)))
tool.Must(model.Init(db.Default.Session()))
tool.Must(manager.Init(ctx))
tool.Must(api.Init(ctx))
}
func (a *App) Startup(ctx context.Context) {
log.Info("app startup!!!")
}

View File

@ -38,3 +38,36 @@ func BucketFiles(c *ndh.Ctx) error {
return c.Send200(map[string]any{"list": list})
}
func BucketCreate(c *ndh.Ctx) error {
type Req struct {
ConnId uint64 `json:"conn_id"`
Name string `json:"name"`
PublicRead bool `json:"public_read"`
PublicReadWrite bool `json:"public_read_write"`
}
var (
err error
req = new(Req)
client *s3.Client
)
if err = c.ReqParse(req); err != nil {
return c.Send400(err.Error())
}
if req.Name == "" {
return c.Send400(req, "桶名不能为空")
}
if _, client, err = manager.Manager.Use(req.ConnId); err != nil {
return c.Send500(err.Error())
}
if err = client.CreateBucket(c.Context(), req.Name, req.PublicRead, req.PublicReadWrite); err != nil {
return c.Send500(err.Error())
}
return c.Send200(map[string]any{"bucket": req.Name})
}

View File

@ -2,7 +2,6 @@ package handler
import (
"errors"
"fmt"
"github.com/loveuer/nf-disk/internal/db"
"github.com/loveuer/nf-disk/internal/manager"
"github.com/loveuer/nf-disk/internal/model"
@ -211,18 +210,11 @@ func ConnectionBuckets(c *ndh.Ctx) error {
return c.Send500(err.Error())
}
// todo: for frontend test
buckets = append(buckets, &s3.ListBucketRes{
Name: "这是一个非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长非常长的名字",
CreatedAt: time.Now().UnixMilli(),
})
// todo: for frontend test
for i := 1; i <= 500; i++ {
buckets = append(buckets, &s3.ListBucketRes{
CreatedAt: time.Now().UnixMilli(),
Name: fmt.Sprintf("test-bucket-%03d", i),
})
}
return c.Send200(map[string]any{"list": buckets})
}

View File

@ -0,0 +1,56 @@
package handler
import (
"context"
"github.com/loveuer/nf-disk/ndh"
"github.com/samber/lo"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
func DialogOpen(ctx context.Context) ndh.Handler {
return func(c *ndh.Ctx) error {
type Req struct {
Title string `json:"title"`
Type string `json:"type"` // "dir", "multi", ""
Filters []string `json:"filters"`
}
var (
err error
req = new(Req)
opt = runtime.OpenDialogOptions{
Title: "请选择文件",
}
result any
)
if err = c.ReqParse(req); err != nil {
return c.Send400(err.Error())
}
if req.Title != "" {
opt.Title = req.Title
}
if len(req.Filters) > 0 {
opt.Filters = lo.Map(req.Filters, func(item string, index int) runtime.FileFilter {
return runtime.FileFilter{Pattern: item}
})
}
switch req.Type {
case "dir":
result, err = runtime.OpenDirectoryDialog(ctx, opt)
case "multi":
result, err = runtime.OpenMultipleFilesDialog(ctx, opt)
default:
result, err = runtime.OpenFileDialog(ctx, opt)
}
if err != nil {
return c.Send500(err.Error())
}
return c.Send200(map[string]interface{}{"result": result})
}
}

88
internal/handler/file.go Normal file
View File

@ -0,0 +1,88 @@
package handler
import (
"errors"
"fmt"
"github.com/loveuer/nf-disk/internal/manager"
"github.com/loveuer/nf-disk/internal/s3"
"github.com/loveuer/nf-disk/ndh"
"github.com/loveuer/nf/nft/log"
"io"
"net/http"
"os"
"path/filepath"
)
func FileUpload(c *ndh.Ctx) error {
type Req struct {
ConnId uint64 `json:"conn_id"`
Bucket string `json:"bucket"`
Location string `json:"location"`
Name string `json:"name"`
PublicRead bool `json:"public_read"`
PublicReadWrite bool `json:"public_read_write"`
DetectContentType bool `json:"detect_content_type"`
}
var (
err error
req = new(Req)
client *s3.Client
reader *os.File
info os.FileInfo
)
if err = c.ReqParse(req); err != nil {
return c.Send400(c, err.Error())
}
if req.Location == "" {
return c.Send400(req, "缺少文件位置")
}
if req.Name == "" {
req.Name = filepath.Base(req.Location)
}
if _, client, err = manager.Manager.Use(req.ConnId); err != nil {
return c.Send500(err.Error())
}
if reader, err = os.Open(req.Location); err != nil {
return c.Send400(err.Error(), fmt.Sprintf("文件: %s 打开错误", req.Location))
}
if info, err = reader.Stat(); err != nil {
log.Error("FileUpload: stat file info err = %s", err.Error())
return c.Send500(err.Error())
}
obj := &s3.PutFilesObj{
Key: req.Name,
Reader: reader,
ContentLength: info.Size(),
ContentType: "",
ExpireAt: 0,
PublicRead: req.PublicRead,
PublicReadWrite: req.PublicReadWrite,
}
if req.DetectContentType {
bs := make([]byte, 128)
if _, err = reader.ReadAt(bs, 0); err != nil {
if !errors.Is(err, io.EOF) {
log.Error("FileUpload: read file to detect content_type err = %s", err.Error())
return c.Send500(err.Error())
}
}
obj.ContentType = http.DetectContentType(bs)
}
if err = client.PutFile(c.Context(), req.Bucket, obj); err != nil {
log.Error("FileUpload: client.PutFile err = %s", err.Error())
return c.Send500(err.Error())
}
return c.Send200(req)
}

View File

@ -1,21 +0,0 @@
package handler
import "github.com/loveuer/nf-disk/ndh"
func ListItem(c *ndh.Ctx) error {
type Req struct {
Id uint64 `json:"id"`
Bucket string `json:"bucket"`
}
var (
err error
req = new(Req)
)
if err = c.ReqParse(req); err != nil {
return c.Send400(err.Error())
}
panic("implement me!!!")
}

24
internal/model/res.go Normal file
View File

@ -0,0 +1,24 @@
package model
import "encoding/json"
type Res struct {
Status uint32 `json:"status"`
Err string `json:"err"`
Msg string `json:"msg"`
Data any `json:"data"`
}
func NewRes(status uint32, err string, msg string, data any) *Res {
return &Res{
Status: status,
Err: err,
Msg: msg,
Data: data,
}
}
func (r *Res) String() string {
bs, _ := json.Marshal(r)
return string(bs)
}

36
internal/s3/create.go Normal file
View File

@ -0,0 +1,36 @@
package s3
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
)
func (c *Client) CreateBucket(ctx context.Context, bucket string, publicRead bool, publicReadWrite bool) error {
var (
err error
input = &s3.CreateBucketInput{
Bucket: aws.String(bucket),
ACL: types.BucketCannedACLAuthenticatedRead,
}
output = &s3.CreateBucketOutput{}
)
if publicRead {
input.ACL = types.BucketCannedACLPublicRead
}
if publicReadWrite {
input.ACL = types.BucketCannedACLPublicReadWrite
}
if output, err = c.client.CreateBucket(ctx, input); err != nil {
return err
}
_ = output
return nil
}

62
internal/s3/put.go Normal file
View File

@ -0,0 +1,62 @@
package s3
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"io"
"time"
)
type PutFilesObj struct {
Key string
Reader io.ReadSeeker
ContentLength int64
ContentType string
ExpireAt int64
PublicRead bool
PublicReadWrite bool
}
func (c *Client) PutFile(ctx context.Context, bucket string, obj *PutFilesObj) error {
var (
err error
input = &s3.PutObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(obj.Key),
Body: obj.Reader,
ACL: types.ObjectCannedACLPrivate,
}
output *s3.PutObjectOutput
)
if obj.ExpireAt > 0 {
t := time.UnixMilli(obj.ExpireAt)
input.Expires = &t
}
if obj.ContentLength > 0 {
input.ContentLength = aws.Int64(obj.ContentLength)
}
if obj.ContentType == "" {
input.ContentType = aws.String(obj.ContentType)
}
if obj.PublicRead {
input.ACL = types.ObjectCannedACLPublicRead
}
if obj.PublicReadWrite {
input.ACL = types.ObjectCannedACLPublicReadWrite
}
if output, err = c.client.PutObject(ctx, input); err != nil {
return err
}
_ = output
return nil
}

32
internal/tool/slice.go Normal file
View File

@ -0,0 +1,32 @@
package tool
import "iter"
func Bulk[T any](slice []T, size int) iter.Seq2[int, []T] {
if size <= 0 {
panic("bulk size must be positive")
}
s := make([]T, 0, size)
idx := 0
return func(yield func(int, []T) bool) {
for i := range slice {
s = append(s, (slice)[i])
if len(s) >= size {
// send to handle
ok := yield(idx, s)
if !ok {
return
}
idx++
s = make([]T, 0, size)
}
}
if len(s) > 0 {
yield(idx, s)
}
}
}