[DEPRECATED] Go implementation of plcbundle
1package commands
2
3import (
4 "context"
5 "fmt"
6 "os"
7 "os/signal"
8 "syscall"
9 "time"
10
11 "github.com/spf13/cobra"
12 "tangled.org/atscan.net/plcbundle/bundle"
13 internalsync "tangled.org/atscan.net/plcbundle/internal/sync"
14)
15
16func NewSyncCommand() *cobra.Command {
17 var (
18 plcURL string
19 continuous bool
20 interval time.Duration
21 maxBundles int
22 )
23
24 cmd := &cobra.Command{
25 Use: "sync",
26 Aliases: []string{"fetch"},
27 Short: "Fetch new bundles from PLC directory",
28 Long: `Fetch new bundles from PLC directory
29
30Download new operations from the PLC directory and create bundles.
31Similar to 'git fetch' - updates your local repository with new data.
32
33By default, fetches until caught up. Use --continuous to run as a daemon
34that continuously syncs at regular intervals.`,
35
36 Example: ` # Fetch new bundles once
37 plcbundle sync
38
39 # Fetch from specific directory
40 plcbundle sync --dir ./my-bundles
41
42 # Run continuously (daemon mode)
43 plcbundle sync --continuous
44
45 # Custom sync interval
46 plcbundle sync --continuous --interval 30s
47
48 # Fetch maximum 10 bundles then stop
49 plcbundle sync --max-bundles 10
50
51 # Continuous with limit
52 plcbundle sync --continuous --max-bundles 100 --interval 1m
53
54 # Verbose output
55 plcbundle sync --continuous -v`,
56
57 RunE: func(cmd *cobra.Command, args []string) error {
58 verbose, _ := cmd.Root().PersistentFlags().GetBool("verbose")
59 quiet, _ := cmd.Root().PersistentFlags().GetBool("quiet")
60
61 // Sync creates repository if missing
62 mgr, dir, err := getManager(&ManagerOptions{
63 Cmd: cmd,
64 PLCURL: plcURL,
65 AutoInit: true,
66 })
67 if err != nil {
68 return err
69 }
70 defer mgr.Close()
71
72 if !quiet {
73 fmt.Printf("Syncing from: %s\n", dir)
74 fmt.Printf("PLC Directory: %s\n", plcURL)
75 if continuous {
76 fmt.Printf("Mode: continuous (interval: %s)\n", interval)
77 if maxBundles > 0 {
78 fmt.Printf("Max bundles: %d\n", maxBundles)
79 }
80 fmt.Printf("(Press Ctrl+C to stop)\n")
81 } else {
82 if maxBundles > 0 {
83 fmt.Printf("Max bundles: %d\n", maxBundles)
84 }
85 }
86 fmt.Printf("\n")
87 }
88
89 if continuous {
90 return runContinuousSync(mgr, interval, maxBundles, verbose, quiet)
91 }
92
93 return runSingleSync(cmd.Context(), mgr, maxBundles, verbose, quiet)
94 },
95 }
96
97 cmd.Flags().StringVar(&plcURL, "plc", "https://plc.directory", "PLC directory URL")
98 cmd.Flags().BoolVar(&continuous, "continuous", false, "Keep syncing (run as daemon)")
99 cmd.Flags().DurationVar(&interval, "interval", 1*time.Minute, "Sync interval for continuous mode")
100 cmd.Flags().IntVar(&maxBundles, "max-bundles", 0, "Maximum bundles to fetch (0 = all)")
101
102 return cmd
103}
104
105func runSingleSync(ctx context.Context, mgr *bundle.Manager, maxBundles int, verbose bool, quiet bool) error {
106 logger := &commandLogger{quiet: quiet}
107
108 config := &internalsync.SyncLoopConfig{
109 MaxBundles: maxBundles,
110 Verbose: verbose,
111 Logger: logger,
112 }
113
114 // Call manager method (not internal directly)
115 synced, err := mgr.RunSyncOnce(ctx, config)
116 if err != nil {
117 return err
118 }
119
120 if !quiet {
121 if synced == 0 {
122 fmt.Fprintf(os.Stderr, "\n✓ Already up to date\n")
123 } else {
124 fmt.Fprintf(os.Stderr, "\n✓ Sync complete: %d bundle(s) fetched\n", synced)
125 }
126 }
127
128 return nil
129}
130
131func runContinuousSync(mgr *bundle.Manager, interval time.Duration, maxBundles int, verbose bool, quiet bool) error {
132 // Setup graceful shutdown
133 ctx, cancel := context.WithCancel(context.Background())
134 defer cancel()
135
136 sigChan := make(chan os.Signal, 1)
137 signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
138
139 go func() {
140 <-sigChan
141 if !quiet {
142 fmt.Fprintf(os.Stderr, "\n\n⚠️ Shutdown signal received...\n")
143 fmt.Fprintf(os.Stderr, " Saving mempool...\n")
144 }
145
146 if err := mgr.GetMempool().Save(); err != nil {
147 fmt.Fprintf(os.Stderr, " ✗ Failed to save mempool: %v\n", err)
148 } else if !quiet {
149 fmt.Fprintf(os.Stderr, " ✓ Mempool saved\n")
150 }
151
152 if !quiet {
153 fmt.Fprintf(os.Stderr, " Closing DID index...\n")
154 }
155 if err := mgr.GetDIDIndex().Close(); err != nil {
156 fmt.Fprintf(os.Stderr, " ✗ Failed to close index: %v\n", err)
157 } else if !quiet {
158 fmt.Fprintf(os.Stderr, " ✓ Index closed\n")
159 }
160
161 if !quiet {
162 fmt.Fprintf(os.Stderr, " ✓ Shutdown complete\n")
163 }
164
165 cancel()
166 os.Exit(0)
167 }()
168
169 logger := &commandLogger{quiet: quiet}
170
171 config := &internalsync.SyncLoopConfig{
172 Interval: interval,
173 MaxBundles: maxBundles,
174 Verbose: verbose,
175 Logger: logger,
176 }
177
178 // Call manager method (not internal directly)
179 return mgr.RunSyncLoop(ctx, config)
180}