[DEPRECATED] Go implementation of plcbundle
at rust-test 180 lines 4.6 kB view raw
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}