added proton drive integration

This commit is contained in:
Bartal Laearsson
2026-08-05 10:28:43 +01:00
parent 4992bcc7f7
commit 54f1c2e355
3 changed files with 169 additions and 31 deletions
+40 -2
View File
@@ -8,6 +8,7 @@ import (
"github.com/spf13/cobra"
"dirmd/internal/config"
"dirmd/internal/drive"
"dirmd/internal/renderer"
"dirmd/internal/walker"
)
@@ -30,7 +31,13 @@ Examples:
dirmd -a -i README.md -o existing.md
# Append another directory
dirmd -a -o existing.md ~/repos/anotherproject`,
dirmd -a -o existing.md ~/repos/anotherproject
# Generate and upload to Proton Drive (default remote path /my-files/md/<filename>)
dirmd -o output.md ~/repos/myproject --proton-drive
# Generate and upload to a specific remote path
dirmd -o output.md ~/repos/myproject --proton-drive --drive-path /my-files/docs/api.md`,
RunE: run,
}
@@ -41,6 +48,8 @@ Examples:
maxDepth int
ignorePatterns []string
extensions []string
protonDrive bool
drivePath string
)
func Execute() {
@@ -57,11 +66,17 @@ func init() {
rootCmd.Flags().IntVar(&maxDepth, "max-depth", config.DefaultMaxDepth, "max directory recursion depth")
rootCmd.Flags().StringArrayVar(&ignorePatterns, "ignore", nil, "additional ignore patterns (glob)")
rootCmd.Flags().StringArrayVar(&extensions, "extensions", nil, "additional file extensions to skip (repeatable)")
rootCmd.Flags().BoolVar(&protonDrive, "proton-drive", false, "upload output to Proton Drive after writing locally")
rootCmd.Flags().StringVar(&drivePath, "drive-path", "", "full remote path on Proton Drive (e.g. /my-files/md/report.md). Requires --proton-drive")
rootCmd.MarkPersistentFlagRequired("output")
}
func run(cmd *cobra.Command, args []string) error {
if drivePath != "" && !protonDrive {
return fmt.Errorf("--drive-path requires --proton-drive")
}
cfg := &config.Config{
OutputPath: outputPath,
AppendMode: appendMode,
@@ -69,6 +84,8 @@ func run(cmd *cobra.Command, args []string) error {
MaxDepth: maxDepth,
Ignores: append(config.DefaultIgnores, ignorePatterns...),
Extensions: append(config.DefaultExts, extensions...),
ProtonDrive: protonDrive,
DrivePath: drivePath,
}
if singleFile != "" {
@@ -111,6 +128,27 @@ func run(cmd *cobra.Command, args []string) error {
}
fmt.Fprintf(os.Stderr, "wrote %d entries to %s\n", len(result.Entries), outputPath)
if protonDrive {
remotePath := drivePath
if remotePath == "" {
remotePath = "/my-files/md/" + filepath.Base(outputPath)
}
if err := drive.CheckAuth(); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
fmt.Fprintln(os.Stderr, "local file was written successfully, but upload was skipped")
return err
}
if err := drive.Upload(outputPath, remotePath); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
fmt.Fprintln(os.Stderr, "local file was written successfully, but upload failed")
return err
}
fmt.Fprintf(os.Stderr, "uploaded %s to %s on Proton Drive\n", outputPath, remotePath)
}
return nil
}
+12
View File
@@ -62,6 +62,8 @@ type Config struct {
Ignores []string
Extensions []string
AbsRoot string
ProtonDrive bool
DrivePath string
}
func PrintUsage() {
@@ -82,6 +84,8 @@ func Parse() (*Config, error) {
ignores stringSlice
exts stringSlice
showVer bool
protonDrive bool
drivePath string
)
flag.StringVar(&input, "i", "", "single file input (append mode only)")
@@ -92,6 +96,8 @@ func Parse() (*Config, error) {
flag.Var(&ignores, "ignore", "additional ignore patterns (glob, repeatable)")
flag.Var(&exts, "extensions", "additional file extensions to skip (repeatable)")
flag.BoolVar(&showVer, "version", false, "print version and exit")
flag.BoolVar(&protonDrive, "proton-drive", false, "upload output to Proton Drive after writing locally")
flag.StringVar(&drivePath, "drive-path", "", "full remote path on Proton Drive (requires --proton-drive)")
flag.Usage = PrintUsage
flag.Parse()
@@ -105,6 +111,10 @@ func Parse() (*Config, error) {
return nil, fmt.Errorf("-o is required")
}
if drivePath != "" && !protonDrive {
return nil, fmt.Errorf("--drive-path requires --proton-drive")
}
cfg := &Config{
OutputPath: output,
AppendMode: appendMd,
@@ -112,6 +122,8 @@ func Parse() (*Config, error) {
MaxDepth: maxDepth,
Ignores: append(DefaultIgnores, ignores...),
Extensions: append(DefaultExts, exts...),
ProtonDrive: protonDrive,
DrivePath: drivePath,
}
if input != "" {
+88
View File
@@ -0,0 +1,88 @@
package drive
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
)
const binaryName = "proton-drive"
// Lookup returns the full path to the proton-drive binary in PATH.
func Lookup() (string, error) {
return exec.LookPath(binaryName)
}
// CheckAuth verifies that the user is authenticated by listing /my-files.
func CheckAuth() error {
binary, err := Lookup()
if err != nil {
return fmt.Errorf("proton-drive not found in PATH: %w", err)
}
cmd := exec.Command(binary, "filesystem", "list", "/my-files")
cmd.Stdout = nil
cmd.Stderr = nil
if err := cmd.Run(); err != nil {
return fmt.Errorf("not authenticated with proton-drive (run 'proton-drive auth login'): %w", err)
}
return nil
}
// Upload uploads localPath to remotePath on Proton Drive.
// remotePath is a full path including filename (e.g. /my-files/md/report.md).
// The CLI uploads to a parent directory and preserves the local filename,
// so if the remote filename differs from the local one, a temp copy is staged.
// Existing remote files are overwritten (--conflict-strategy replace).
func Upload(localPath, remotePath string) error {
binary, err := Lookup()
if err != nil {
return fmt.Errorf("proton-drive not found in PATH: %w", err)
}
remoteDir := filepath.Dir(remotePath)
remoteName := filepath.Base(remotePath)
localName := filepath.Base(localPath)
if remoteName == localName {
return runUpload(binary, localPath, remoteDir)
}
tmpDir, err := os.MkdirTemp("", "dirmd-upload-*")
if err != nil {
return fmt.Errorf("cannot create temp dir: %w", err)
}
defer os.RemoveAll(tmpDir)
tmpFile := filepath.Join(tmpDir, remoteName)
if err := copyFile(localPath, tmpFile); err != nil {
return fmt.Errorf("cannot stage temp file: %w", err)
}
return runUpload(binary, tmpFile, remoteDir)
}
func runUpload(binary, localPath, remoteDir string) error {
cmd := exec.Command(binary, "filesystem", "upload", localPath, remoteDir,
"--conflict-strategy", "replace")
var stderr strings.Builder
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("upload to proton-drive failed: %w: %s",
err, strings.TrimSpace(stderr.String()))
}
return nil
}
func copyFile(src, dst string) error {
data, err := os.ReadFile(src)
if err != nil {
return err
}
return os.WriteFile(dst, data, 0644)
}