2025-08-07 19:10:13 +01:00

236 lines
5.9 KiB
Go

package cli
import (
"context"
"fmt"
"mime"
"os"
"path/filepath"
"strings"
"text/tabwriter"
"github.com/spf13/cobra"
)
func NewFileCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "file",
Short: "Manage files in buckets",
Long: "Upload, download, list, and manage files in your buckets",
}
cmd.AddCommand(newFileUploadCmd())
cmd.AddCommand(newFileDownloadCmd())
cmd.AddCommand(newFileListCmd())
cmd.AddCommand(newFileDeleteCmd())
return cmd
}
func newFileUploadCmd() *cobra.Command {
var contentType string
cmd := &cobra.Command{
Use: "upload <bucket-name> <local-file> [remote-key]",
Short: "Upload a file to a bucket",
Args: cobra.RangeArgs(2, 3),
RunE: func(cmd *cobra.Command, args []string) error {
config, err := LoadConfig()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
if !config.IsAuthenticated() {
return fmt.Errorf("not logged in. Use 'tcman account login' or 'tcman account create'")
}
bucketName := args[0]
localFile := args[1]
remoteKey := filepath.Base(localFile)
if len(args) == 3 {
remoteKey = args[2]
}
data, err := os.ReadFile(localFile)
if err != nil {
return fmt.Errorf("failed to read file: %w", err)
}
if contentType == "" {
contentType = mime.TypeByExtension(filepath.Ext(localFile))
}
client := NewClient(config.ServerURL, config.AccessToken)
ctx := context.Background()
fmt.Printf("Uploading %s to %s/%s (%s)...\n", localFile, bucketName, remoteKey, formatBytes(int64(len(data))))
err = client.PutObject(ctx, bucketName, remoteKey, data, contentType)
if err != nil {
return fmt.Errorf("failed to upload file: %w", err)
}
fmt.Printf("File uploaded successfully!\n")
fmt.Printf("Bucket: %s\n", bucketName)
fmt.Printf("Key: %s\n", remoteKey)
fmt.Printf("Size: %s\n", formatBytes(int64(len(data))))
if contentType != "" {
fmt.Printf("Content-Type: %s\n", contentType)
}
return nil
},
}
cmd.Flags().StringVar(&contentType, "content-type", "", "Override content type")
return cmd
}
func newFileDownloadCmd() *cobra.Command {
return &cobra.Command{
Use: "download <bucket-name> <remote-key> [local-file]",
Short: "Download a file from a bucket",
Args: cobra.RangeArgs(2, 3),
RunE: func(cmd *cobra.Command, args []string) error {
config, err := LoadConfig()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
if !config.IsAuthenticated() {
return fmt.Errorf("not logged in. Use 'tcman account login' or 'tcman account create'")
}
bucketName := args[0]
remoteKey := args[1]
localFile := filepath.Base(remoteKey)
if len(args) == 3 {
localFile = args[2]
}
client := NewClient(config.ServerURL, config.AccessToken)
ctx := context.Background()
fmt.Printf("Downloading %s/%s to %s...\n", bucketName, remoteKey, localFile)
data, err := client.GetObject(ctx, bucketName, remoteKey)
if err != nil {
return fmt.Errorf("failed to download file: %w", err)
}
err = os.WriteFile(localFile, data, 0644)
if err != nil {
return fmt.Errorf("failed to write file: %w", err)
}
fmt.Printf("File downloaded successfully!\n")
fmt.Printf("Size: %s\n", formatBytes(int64(len(data))))
fmt.Printf("Saved to: %s\n", localFile)
return nil
},
}
}
func newFileListCmd() *cobra.Command {
return &cobra.Command{
Use: "list <bucket-name>",
Short: "List files in a bucket",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
config, err := LoadConfig()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
if !config.IsAuthenticated() {
return fmt.Errorf("not logged in. Use 'tcman account login' or 'tcman account create'")
}
bucketName := args[0]
client := NewClient(config.ServerURL, config.AccessToken)
ctx := context.Background()
objects, err := client.ListObjects(ctx, bucketName)
if err != nil {
return fmt.Errorf("failed to list objects: %w", err)
}
if len(objects) == 0 {
fmt.Printf("No files found in bucket '%s'.\n", bucketName)
return nil
}
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(w, "Key\tSize\tContent-Type\tLast Modified")
fmt.Fprintln(w, "---\t----\t------------\t-------------")
for _, obj := range objects {
contentType := obj.ContentType
if contentType == "" {
contentType = "application/octet-stream"
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n",
obj.Key,
formatBytes(obj.SizeBytes),
contentType,
obj.LastModified.Format("2006-01-02 15:04:05"),
)
}
w.Flush()
return nil
},
}
}
func newFileDeleteCmd() *cobra.Command {
var force bool
cmd := &cobra.Command{
Use: "delete <bucket-name> <remote-key>",
Short: "Delete a file from a bucket",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
config, err := LoadConfig()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
if !config.IsAuthenticated() {
return fmt.Errorf("not logged in. Use 'tcman account login' or 'tcman account create'")
}
bucketName := args[0]
remoteKey := args[1]
client := NewClient(config.ServerURL, config.AccessToken)
ctx := context.Background()
if !force {
fmt.Printf("Are you sure you want to delete '%s' from bucket '%s'? (y/N): ", remoteKey, bucketName)
var response string
fmt.Scanln(&response)
response = strings.ToLower(response)
if response != "y" && response != "yes" {
fmt.Printf("Cancelled.\n")
return nil
}
}
err = client.DeleteObject(ctx, bucketName, remoteKey)
if err != nil {
return fmt.Errorf("failed to delete file: %w", err)
}
fmt.Printf("File '%s' deleted from bucket '%s'.\n", remoteKey, bucketName)
return nil
},
}
cmd.Flags().BoolVar(&force, "force", false, "Force deletion without confirmation")
return cmd
}