[DEPRECATED] Go implementation of plcbundle
at rust-test 388 lines 10 kB view raw
1package commands 2 3import ( 4 "context" 5 "fmt" 6 "time" 7 8 "github.com/goccy/go-json" 9 "github.com/spf13/cobra" 10 "tangled.org/atscan.net/plcbundle/cmd/plcbundle/ui" 11) 12 13func NewIndexCommand() *cobra.Command { 14 cmd := &cobra.Command{ 15 Use: "index", 16 Short: "DID index management", 17 Long: `DID index management operations 18 19Manage the DID position index which maps DIDs to their bundle locations. 20This index enables fast O(1) DID lookups and is required for DID 21resolution and query operations.`, 22 23 Example: ` # Build DID position index 24 plcbundle index build 25 26 # Repair DID index (rebuild from bundles) 27 plcbundle index repair 28 29 # Show DID index statistics 30 plcbundle index stats 31 32 # Verify DID index integrity 33 plcbundle index verify`, 34 } 35 36 cmd.AddCommand(newIndexBuildCommand()) 37 cmd.AddCommand(newIndexRepairCommand()) 38 cmd.AddCommand(newIndexStatsCommand()) 39 cmd.AddCommand(newIndexVerifyCommand()) 40 41 return cmd 42} 43 44// ============================================================================ 45// INDEX BUILD - Build DID position index 46// ============================================================================ 47 48func newIndexBuildCommand() *cobra.Command { 49 var force bool 50 51 cmd := &cobra.Command{ 52 Use: "build", 53 Short: "Build DID position index", 54 Long: `Build DID position index from bundles 55 56Creates a sharded index mapping each DID to its bundle locations, 57enabling fast O(1) DID lookups. Required for DID resolution. 58 59The index is built incrementally and auto-updates as new bundles 60are added. Use --force to rebuild from scratch.`, 61 62 Example: ` # Build index 63 plcbundle index build 64 65 # Force rebuild from scratch 66 plcbundle index build --force`, 67 68 RunE: func(cmd *cobra.Command, args []string) error { 69 mgr, dir, err := getManager(&ManagerOptions{Cmd: cmd}) 70 if err != nil { 71 return err 72 } 73 defer mgr.Close() 74 75 stats := mgr.GetDIDIndexStats() 76 if stats["exists"].(bool) && !force { 77 fmt.Printf("DID index already exists (use --force to rebuild)\n") 78 fmt.Printf("Directory: %s\n", dir) 79 fmt.Printf("Total DIDs: %s\n", formatNumber(int(stats["total_dids"].(int64)))) 80 return nil 81 } 82 83 index := mgr.GetIndex() 84 bundleCount := index.Count() 85 86 if bundleCount == 0 { 87 fmt.Printf("No bundles to index\n") 88 return nil 89 } 90 91 fmt.Printf("Building DID index in: %s\n", dir) 92 fmt.Printf("Indexing %d bundles...\n\n", bundleCount) 93 94 progress := ui.NewProgressBar(bundleCount) 95 start := time.Now() 96 ctx := context.Background() 97 98 err = mgr.BuildDIDIndex(ctx, func(current, total int) { 99 progress.Set(current) 100 }) 101 102 progress.Finish() 103 104 if err != nil { 105 return fmt.Errorf("build failed: %w", err) 106 } 107 108 elapsed := time.Since(start) 109 stats = mgr.GetDIDIndexStats() 110 111 fmt.Printf("\n✓ DID index built in %s\n", elapsed.Round(time.Millisecond)) 112 fmt.Printf(" Total DIDs: %s\n", formatNumber(int(stats["total_dids"].(int64)))) 113 fmt.Printf(" Shards: %d\n", stats["shard_count"]) 114 fmt.Printf(" Location: %s/.plcbundle/\n", dir) 115 116 return nil 117 }, 118 } 119 120 cmd.Flags().BoolVar(&force, "force", false, "Rebuild even if index exists") 121 122 return cmd 123} 124 125// ============================================================================ 126// INDEX REPAIR - Repair/rebuild DID index 127// ============================================================================ 128 129func newIndexRepairCommand() *cobra.Command { 130 cmd := &cobra.Command{ 131 Use: "repair", 132 Aliases: []string{"rebuild"}, 133 Short: "Repair DID index", 134 Long: `Repair DID index by rebuilding from bundles 135 136Rebuilds the DID index from scratch and verifies consistency. 137Use this when: 138 • DID index is corrupted 139 • Index is out of sync with bundles 140 • After manual bundle operations 141 • Upgrade to new index version`, 142 143 Example: ` # Repair DID index 144 plcbundle index repair 145 146 # Verbose output 147 plcbundle index repair -v`, 148 149 RunE: func(cmd *cobra.Command, args []string) error { 150 verbose, _ := cmd.Root().PersistentFlags().GetBool("verbose") 151 152 mgr, dir, err := getManager(&ManagerOptions{Cmd: cmd}) 153 if err != nil { 154 return err 155 } 156 defer mgr.Close() 157 158 stats := mgr.GetDIDIndexStats() 159 if !stats["exists"].(bool) { 160 fmt.Printf("DID index does not exist\n") 161 fmt.Printf("Use: plcbundle index build\n") 162 return nil 163 } 164 165 fmt.Printf("Repairing DID index in: %s\n\n", dir) 166 167 index := mgr.GetIndex() 168 bundleCount := index.Count() 169 170 if bundleCount == 0 { 171 fmt.Printf("No bundles to index\n") 172 return nil 173 } 174 175 fmt.Printf("Rebuilding index from %d bundles...\n\n", bundleCount) 176 177 var progress *ui.ProgressBar 178 if !verbose { 179 progress = ui.NewProgressBar(bundleCount) 180 } 181 182 start := time.Now() 183 ctx := context.Background() 184 185 err = mgr.BuildDIDIndex(ctx, func(current, total int) { 186 if progress != nil { 187 progress.Set(current) 188 } else if current%100 == 0 || current == total { 189 fmt.Printf("Progress: %d/%d (%.1f%%) \r", 190 current, total, float64(current)/float64(total)*100) 191 } 192 }) 193 194 if progress != nil { 195 progress.Finish() 196 } 197 198 if err != nil { 199 return fmt.Errorf("repair failed: %w", err) 200 } 201 202 // Verify consistency 203 fmt.Printf("\nVerifying consistency...\n") 204 if err := mgr.GetDIDIndex().VerifyAndRepairIndex(ctx, mgr); err != nil { 205 return fmt.Errorf("verification failed: %w", err) 206 } 207 208 elapsed := time.Since(start) 209 stats = mgr.GetDIDIndexStats() 210 211 fmt.Printf("\n✓ DID index repaired in %s\n", elapsed.Round(time.Millisecond)) 212 fmt.Printf(" Total DIDs: %s\n", formatNumber(int(stats["total_dids"].(int64)))) 213 fmt.Printf(" Shards: %d\n", stats["shard_count"]) 214 fmt.Printf(" Last bundle: %06d\n", stats["last_bundle"]) 215 216 return nil 217 }, 218 } 219 220 return cmd 221} 222 223// ============================================================================ 224// INDEX STATS - Show DID index statistics 225// ============================================================================ 226 227func newIndexStatsCommand() *cobra.Command { 228 var showJSON bool 229 230 cmd := &cobra.Command{ 231 Use: "stats", 232 Aliases: []string{"info"}, 233 Short: "Show DID index statistics", 234 Long: `Show DID index statistics 235 236Displays DID index information including total DIDs indexed, 237shard distribution, cache statistics, and coverage.`, 238 239 Example: ` # Show statistics 240 plcbundle index stats 241 242 # JSON output 243 plcbundle index stats --json`, 244 245 RunE: func(cmd *cobra.Command, args []string) error { 246 mgr, dir, err := getManager(&ManagerOptions{Cmd: cmd}) 247 if err != nil { 248 return err 249 } 250 defer mgr.Close() 251 252 stats := mgr.GetDIDIndexStats() 253 254 if showJSON { 255 data, _ := json.MarshalIndent(stats, "", " ") 256 fmt.Println(string(data)) 257 return nil 258 } 259 260 if !stats["exists"].(bool) { 261 fmt.Printf("DID index does not exist\n") 262 fmt.Printf("Run: plcbundle index build\n") 263 return nil 264 } 265 266 indexedDIDs := stats["indexed_dids"].(int64) 267 mempoolDIDs := stats["mempool_dids"].(int64) 268 totalDIDs := stats["total_dids"].(int64) 269 270 fmt.Printf("\nDID Index Statistics\n") 271 fmt.Printf("════════════════════\n\n") 272 fmt.Printf(" Location: %s/.plcbundle/\n", dir) 273 274 if mempoolDIDs > 0 { 275 fmt.Printf(" Indexed DIDs: %s (in bundles)\n", formatNumber(int(indexedDIDs))) 276 fmt.Printf(" Mempool DIDs: %s (not yet bundled)\n", formatNumber(int(mempoolDIDs))) 277 fmt.Printf(" Total DIDs: %s\n", formatNumber(int(totalDIDs))) 278 } else { 279 fmt.Printf(" Total DIDs: %s\n", formatNumber(int(totalDIDs))) 280 } 281 282 fmt.Printf(" Shard count: %d\n", stats["shard_count"]) 283 fmt.Printf(" Last bundle: %06d\n", stats["last_bundle"]) 284 fmt.Printf(" Updated: %s\n\n", stats["updated_at"].(time.Time).Format("2006-01-02 15:04:05")) 285 286 fmt.Printf(" Cached shards: %d / %d\n", stats["cached_shards"], stats["cache_limit"]) 287 288 if cachedList, ok := stats["cache_order"].([]int); ok && len(cachedList) > 0 { 289 fmt.Printf(" Hot shards: ") 290 for i, shard := range cachedList { 291 if i > 0 { 292 fmt.Printf(", ") 293 } 294 if i >= 10 { 295 fmt.Printf("... (+%d more)", len(cachedList)-10) 296 break 297 } 298 fmt.Printf("%02x", shard) 299 } 300 fmt.Printf("\n") 301 } 302 303 fmt.Printf("\n") 304 return nil 305 }, 306 } 307 308 cmd.Flags().BoolVar(&showJSON, "json", false, "Output as JSON") 309 310 return cmd 311} 312 313// ============================================================================ 314// INDEX VERIFY - Verify DID index integrity 315// ============================================================================ 316 317func newIndexVerifyCommand() *cobra.Command { 318 var verbose bool 319 320 cmd := &cobra.Command{ 321 Use: "verify", 322 Aliases: []string{"check"}, 323 Short: "Verify DID index integrity", 324 Long: `Verify DID index integrity and consistency 325 326Checks the DID index for consistency with bundles: 327 • Index version is current 328 • All bundles are indexed 329 • Shard files are valid 330 • No corruption detected 331 332Automatically repairs minor issues.`, 333 334 Example: ` # Verify DID index 335 plcbundle index verify 336 337 # Verbose output 338 plcbundle index verify -v`, 339 340 RunE: func(cmd *cobra.Command, args []string) error { 341 mgr, dir, err := getManager(&ManagerOptions{Cmd: cmd}) 342 if err != nil { 343 return err 344 } 345 defer mgr.Close() 346 347 stats := mgr.GetDIDIndexStats() 348 349 if !stats["exists"].(bool) { 350 fmt.Printf("DID index does not exist\n") 351 fmt.Printf("Run: plcbundle index build\n") 352 return nil 353 } 354 355 fmt.Printf("Verifying DID index in: %s\n\n", dir) 356 357 ctx := context.Background() 358 359 if verbose { 360 fmt.Printf("Index version: %d\n", mgr.GetDIDIndex().GetConfig().Version) 361 fmt.Printf("Total DIDs: %s\n", formatNumber(int(stats["total_dids"].(int64)))) 362 fmt.Printf("Shards: %d\n", stats["shard_count"]) 363 fmt.Printf("Last bundle: %06d\n\n", stats["last_bundle"]) 364 } 365 366 fmt.Printf("Checking consistency with bundles...\n") 367 368 if err := mgr.GetDIDIndex().VerifyAndRepairIndex(ctx, mgr); err != nil { 369 fmt.Printf("\n✗ DID index verification failed\n") 370 fmt.Printf(" Error: %v\n", err) 371 return fmt.Errorf("verification failed: %w", err) 372 } 373 374 stats = mgr.GetDIDIndexStats() 375 376 fmt.Printf("\n✓ DID index is valid\n") 377 fmt.Printf(" Total DIDs: %s\n", formatNumber(int(stats["total_dids"].(int64)))) 378 fmt.Printf(" Shards: %d\n", stats["shard_count"]) 379 fmt.Printf(" Last bundle: %06d\n", stats["last_bundle"]) 380 381 return nil 382 }, 383 } 384 385 cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Verbose output") 386 387 return cmd 388}