mirror of
https://github.com/hanzoskill/mogcli.git
synced 2026-10-30 21:03:34 +00:00
426 lines
9.3 KiB
Go
426 lines
9.3 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/visionik/mogcli/internal/graph"
|
|
)
|
|
|
|
// DriveCmd handles OneDrive operations.
|
|
type DriveCmd struct {
|
|
Ls DriveLsCmd `cmd:"" help:"List files"`
|
|
Search DriveSearchCmd `cmd:"" help:"Search files"`
|
|
Get DriveGetCmd `cmd:"" help:"Get file metadata"`
|
|
Download DriveDownloadCmd `cmd:"" help:"Download a file"`
|
|
Upload DriveUploadCmd `cmd:"" help:"Upload a file"`
|
|
Mkdir DriveMkdirCmd `cmd:"" help:"Create a folder"`
|
|
Move DriveMoveCmd `cmd:"" help:"Move a file"`
|
|
Copy DriveCopyCmd `cmd:"" help:"Copy a file"`
|
|
Rename DriveRenameCmd `cmd:"" help:"Rename a file"`
|
|
Delete DriveDeleteCmd `cmd:"" aliases:"rm" help:"Delete a file"`
|
|
}
|
|
|
|
// DriveLsCmd lists files.
|
|
type DriveLsCmd struct {
|
|
Path string `arg:"" optional:"" help:"Folder path or ID" default:""`
|
|
}
|
|
|
|
// Run executes drive ls.
|
|
func (c *DriveLsCmd) Run(root *Root) error {
|
|
client, err := root.GetClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx := context.Background()
|
|
path := "/me/drive/root/children"
|
|
if c.Path != "" {
|
|
if len(c.Path) > 20 {
|
|
// Looks like an ID
|
|
path = fmt.Sprintf("/me/drive/items/%s/children", graph.ResolveID(c.Path))
|
|
} else {
|
|
path = fmt.Sprintf("/me/drive/root:/%s:/children", c.Path)
|
|
}
|
|
}
|
|
|
|
data, err := client.Get(ctx, path, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var resp struct {
|
|
Value []DriveItem `json:"value"`
|
|
}
|
|
if err := json.Unmarshal(data, &resp); err != nil {
|
|
return err
|
|
}
|
|
|
|
if root.JSON {
|
|
return outputJSON(resp.Value)
|
|
}
|
|
|
|
for _, item := range resp.Value {
|
|
itemType := "📄"
|
|
if item.Folder != nil {
|
|
itemType = "📁"
|
|
}
|
|
size := ""
|
|
if item.Size > 0 {
|
|
size = formatSize(item.Size)
|
|
}
|
|
fmt.Printf("%s %-40s %8s %s\n", itemType, item.Name, size, graph.FormatID(item.ID))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DriveSearchCmd searches files.
|
|
type DriveSearchCmd struct {
|
|
Query string `arg:"" help:"Search query"`
|
|
Max int `help:"Maximum results" default:"25"`
|
|
}
|
|
|
|
// Run executes drive search.
|
|
func (c *DriveSearchCmd) Run(root *Root) error {
|
|
client, err := root.GetClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx := context.Background()
|
|
query := url.Values{}
|
|
query.Set("$top", fmt.Sprintf("%d", c.Max))
|
|
|
|
path := fmt.Sprintf("/me/drive/root/search(q='%s')", url.PathEscape(c.Query))
|
|
data, err := client.Get(ctx, path, query)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var resp struct {
|
|
Value []DriveItem `json:"value"`
|
|
}
|
|
if err := json.Unmarshal(data, &resp); err != nil {
|
|
return err
|
|
}
|
|
|
|
if root.JSON {
|
|
return outputJSON(resp.Value)
|
|
}
|
|
|
|
for _, item := range resp.Value {
|
|
itemType := "📄"
|
|
if item.Folder != nil {
|
|
itemType = "📁"
|
|
}
|
|
fmt.Printf("%s %s %s\n", itemType, item.Name, graph.FormatID(item.ID))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DriveGetCmd gets file metadata.
|
|
type DriveGetCmd struct {
|
|
ID string `arg:"" help:"File ID"`
|
|
}
|
|
|
|
// Run executes drive get.
|
|
func (c *DriveGetCmd) Run(root *Root) error {
|
|
client, err := root.GetClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx := context.Background()
|
|
path := fmt.Sprintf("/me/drive/items/%s", graph.ResolveID(c.ID))
|
|
|
|
data, err := client.Get(ctx, path, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var item DriveItem
|
|
if err := json.Unmarshal(data, &item); err != nil {
|
|
return err
|
|
}
|
|
|
|
if root.JSON {
|
|
return outputJSON(item)
|
|
}
|
|
|
|
fmt.Printf("ID: %s\n", graph.FormatID(item.ID))
|
|
fmt.Printf("Name: %s\n", item.Name)
|
|
fmt.Printf("Size: %s\n", formatSize(item.Size))
|
|
fmt.Printf("Created: %s\n", item.CreatedDateTime)
|
|
fmt.Printf("Modified: %s\n", item.LastModifiedDateTime)
|
|
if item.WebURL != "" {
|
|
fmt.Printf("URL: %s\n", item.WebURL)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DriveDownloadCmd downloads a file.
|
|
type DriveDownloadCmd struct {
|
|
ID string `arg:"" help:"File ID"`
|
|
Out string `help:"Output path" required:""`
|
|
}
|
|
|
|
// Run executes drive download.
|
|
func (c *DriveDownloadCmd) Run(root *Root) error {
|
|
client, err := root.GetClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx := context.Background()
|
|
path := fmt.Sprintf("/me/drive/items/%s/content", graph.ResolveID(c.ID))
|
|
|
|
data, err := client.Get(ctx, path, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := os.WriteFile(c.Out, data, 0644); err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("✓ Downloaded: %s\n", c.Out)
|
|
return nil
|
|
}
|
|
|
|
// DriveUploadCmd uploads a file.
|
|
type DriveUploadCmd struct {
|
|
Path string `arg:"" help:"Local file path"`
|
|
Folder string `help:"Destination folder ID"`
|
|
Name string `help:"Rename file on upload"`
|
|
}
|
|
|
|
// Run executes drive upload.
|
|
func (c *DriveUploadCmd) Run(root *Root) error {
|
|
client, err := root.GetClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
data, err := os.ReadFile(c.Path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
name := c.Name
|
|
if name == "" {
|
|
name = filepath.Base(c.Path)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
var path string
|
|
if c.Folder != "" {
|
|
path = fmt.Sprintf("/me/drive/items/%s:/%s:/content", graph.ResolveID(c.Folder), name)
|
|
} else {
|
|
path = fmt.Sprintf("/me/drive/root:/%s:/content", name)
|
|
}
|
|
|
|
// For small files, use simple upload
|
|
// Note: This is simplified - large files need chunked upload
|
|
respData, err := client.Put(ctx, path, data, "application/octet-stream")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var item DriveItem
|
|
if err := json.Unmarshal(respData, &item); err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("✓ Uploaded: %s (%s)\n", item.Name, graph.FormatID(item.ID))
|
|
return nil
|
|
}
|
|
|
|
// DriveMkdirCmd creates a folder.
|
|
type DriveMkdirCmd struct {
|
|
Name string `arg:"" help:"Folder name"`
|
|
Parent string `help:"Parent folder ID"`
|
|
}
|
|
|
|
// Run executes drive mkdir.
|
|
func (c *DriveMkdirCmd) Run(root *Root) error {
|
|
client, err := root.GetClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
body := map[string]interface{}{
|
|
"name": c.Name,
|
|
"folder": map[string]interface{}{},
|
|
}
|
|
|
|
ctx := context.Background()
|
|
path := "/me/drive/root/children"
|
|
if c.Parent != "" {
|
|
path = fmt.Sprintf("/me/drive/items/%s/children", graph.ResolveID(c.Parent))
|
|
}
|
|
|
|
data, err := client.Post(ctx, path, body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var item DriveItem
|
|
if err := json.Unmarshal(data, &item); err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("✓ Created folder: %s (%s)\n", item.Name, graph.FormatID(item.ID))
|
|
return nil
|
|
}
|
|
|
|
// DriveMoveCmd moves a file.
|
|
type DriveMoveCmd struct {
|
|
ID string `arg:"" help:"File ID"`
|
|
Destination string `arg:"" help:"Destination folder ID"`
|
|
}
|
|
|
|
// Run executes drive move.
|
|
func (c *DriveMoveCmd) Run(root *Root) error {
|
|
client, err := root.GetClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
body := map[string]interface{}{
|
|
"parentReference": map[string]string{
|
|
"id": graph.ResolveID(c.Destination),
|
|
},
|
|
}
|
|
|
|
ctx := context.Background()
|
|
path := fmt.Sprintf("/me/drive/items/%s", graph.ResolveID(c.ID))
|
|
|
|
_, err = client.Patch(ctx, path, body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println("✓ File moved")
|
|
return nil
|
|
}
|
|
|
|
// DriveCopyCmd copies a file.
|
|
type DriveCopyCmd struct {
|
|
ID string `arg:"" help:"File ID"`
|
|
Name string `help:"New name for copy" required:""`
|
|
}
|
|
|
|
// Run executes drive copy.
|
|
func (c *DriveCopyCmd) Run(root *Root) error {
|
|
client, err := root.GetClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
body := map[string]interface{}{
|
|
"name": c.Name,
|
|
}
|
|
|
|
ctx := context.Background()
|
|
path := fmt.Sprintf("/me/drive/items/%s/copy", graph.ResolveID(c.ID))
|
|
|
|
_, err = client.Post(ctx, path, body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("✓ Copy initiated: %s\n", c.Name)
|
|
return nil
|
|
}
|
|
|
|
// DriveRenameCmd renames a file.
|
|
type DriveRenameCmd struct {
|
|
ID string `arg:"" help:"File ID"`
|
|
Name string `arg:"" help:"New name"`
|
|
}
|
|
|
|
// Run executes drive rename.
|
|
func (c *DriveRenameCmd) Run(root *Root) error {
|
|
client, err := root.GetClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
body := map[string]interface{}{
|
|
"name": c.Name,
|
|
}
|
|
|
|
ctx := context.Background()
|
|
path := fmt.Sprintf("/me/drive/items/%s", graph.ResolveID(c.ID))
|
|
|
|
_, err = client.Patch(ctx, path, body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("✓ Renamed to: %s\n", c.Name)
|
|
return nil
|
|
}
|
|
|
|
// DriveDeleteCmd deletes a file.
|
|
type DriveDeleteCmd struct {
|
|
ID string `arg:"" help:"File ID"`
|
|
}
|
|
|
|
// Run executes drive delete.
|
|
func (c *DriveDeleteCmd) Run(root *Root) error {
|
|
client, err := root.GetClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx := context.Background()
|
|
path := fmt.Sprintf("/me/drive/items/%s", graph.ResolveID(c.ID))
|
|
|
|
if err := client.Delete(ctx, path); err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println("✓ File deleted")
|
|
return nil
|
|
}
|
|
|
|
// DriveItem represents a OneDrive item.
|
|
type DriveItem struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Size int64 `json:"size"`
|
|
CreatedDateTime string `json:"createdDateTime"`
|
|
LastModifiedDateTime string `json:"lastModifiedDateTime"`
|
|
WebURL string `json:"webUrl"`
|
|
Folder *FolderInfo `json:"folder,omitempty"`
|
|
File *FileInfo `json:"file,omitempty"`
|
|
}
|
|
|
|
// FolderInfo represents folder information.
|
|
type FolderInfo struct {
|
|
ChildCount int `json:"childCount"`
|
|
}
|
|
|
|
// FileInfo represents file information.
|
|
type FileInfo struct {
|
|
MimeType string `json:"mimeType"`
|
|
}
|
|
|
|
func formatSize(bytes int64) string {
|
|
const unit = 1024
|
|
if bytes < unit {
|
|
return fmt.Sprintf("%d B", bytes)
|
|
}
|
|
div, exp := int64(unit), 0
|
|
for n := bytes / unit; n >= unit; n /= unit {
|
|
div *= unit
|
|
exp++
|
|
}
|
|
return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
|
|
}
|