[DEPRECATED] Go implementation of plcbundle
at rust-test 482 lines 13 kB view raw
1package commands 2 3import ( 4 "context" 5 "fmt" 6 "os" 7 "strconv" 8 "strings" 9 "time" 10 11 "github.com/goccy/go-json" 12 "github.com/spf13/cobra" 13 "tangled.org/atscan.net/plcbundle/internal/plcclient" 14 "tangled.org/atscan.net/plcbundle/internal/types" 15) 16 17func NewOpCommand() *cobra.Command { 18 cmd := &cobra.Command{ 19 Use: "op", 20 Aliases: []string{"operation", "record"}, 21 Short: "Operation queries and inspection", 22 Long: `Operation queries and inspection 23 24Direct access to individual operations within bundles using either: 25 • Bundle number + position (e.g., 42 1337) 26 • Global position (e.g., 420000) 27 28Global position format: (bundleNumber × 10,000) + position 29Example: 88410345 = bundle 8841, position 345`, 30 31 Example: ` # Get operation as JSON 32 plcbundle op get 42 1337 33 plcbundle op get 420000 34 35 # Show operation (formatted) 36 plcbundle op show 42 1337 37 plcbundle op show 88410345 38 39 # Find by CID 40 plcbundle op find bafyreig3...`, 41 } 42 43 // Add subcommands 44 cmd.AddCommand(newOpGetCommand()) 45 cmd.AddCommand(newOpShowCommand()) 46 cmd.AddCommand(newOpFindCommand()) 47 48 return cmd 49} 50 51// ============================================================================ 52// OP GET - Get operation as JSON 53// ============================================================================ 54 55func newOpGetCommand() *cobra.Command { 56 var verbose bool 57 58 cmd := &cobra.Command{ 59 Use: "get <bundle> <position> | <globalPosition>", 60 Short: "Get operation as JSON", 61 Long: `Get operation as JSON (machine-readable) 62 63Supports two input formats: 64 1. Bundle number + position: get 42 1337 65 2. Global position: get 420000 66 67Global position = (bundleNumber × 10,000) + position 68 69Use -v/--verbose to see detailed timing breakdown.`, 70 71 Example: ` # By bundle + position 72 plcbundle op get 42 1337 73 74 # By global position 75 plcbundle op get 88410345 76 77 # With timing metrics 78 plcbundle op get 42 1337 -v 79 plcbundle op get 88410345 --verbose 80 81 # Pipe to jq 82 plcbundle op get 42 1337 | jq .did`, 83 84 Args: cobra.RangeArgs(1, 2), 85 86 RunE: func(cmd *cobra.Command, args []string) error { 87 bundleNum, position, err := parseOpArgs(args) 88 if err != nil { 89 return err 90 } 91 92 mgr, _, err := getManager(&ManagerOptions{Cmd: cmd}) 93 if err != nil { 94 return err 95 } 96 defer mgr.Close() 97 98 ctx := context.Background() 99 100 // Time the operation load 101 totalStart := time.Now() 102 op, err := mgr.LoadOperation(ctx, bundleNum, position) 103 totalDuration := time.Since(totalStart) 104 105 if err != nil { 106 return err 107 } 108 109 if verbose { 110 globalPos := (bundleNum * 10000) + position 111 112 // Log-style output (compact, single-line friendly) 113 fmt.Fprintf(os.Stderr, "[Load] Bundle %06d:%04d (pos=%d) in %s", 114 bundleNum, position, globalPos, totalDuration) 115 fmt.Fprintf(os.Stderr, " | %d bytes", len(op.RawJSON)) 116 fmt.Fprintf(os.Stderr, "\n") 117 } 118 119 // Output raw JSON to stdout 120 if len(op.RawJSON) > 0 { 121 fmt.Println(string(op.RawJSON)) 122 } else { 123 data, _ := json.Marshal(op) 124 fmt.Println(string(data)) 125 } 126 127 return nil 128 }, 129 } 130 131 cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Show timing metrics") 132 133 return cmd 134} 135 136// // ============================================================================ 137// OP SHOW - Show operation (formatted) 138// ============================================================================ 139 140func newOpShowCommand() *cobra.Command { 141 var verbose bool 142 143 cmd := &cobra.Command{ 144 Use: "show <bundle> <position> | <globalPosition>", 145 Short: "Show operation (human-readable)", 146 Long: `Show operation with formatted output 147 148Displays operation in human-readable format with: 149 • Bundle location and global position 150 • DID and CID 151 • Timestamp and age 152 • Nullification status 153 • Parsed operation details 154 • Performance metrics (with -v)`, 155 156 Example: ` # By bundle + position 157 plcbundle op show 42 1337 158 159 # By global position 160 plcbundle op show 88410345 161 162 # Verbose with timing and full JSON 163 plcbundle op show 42 1337 -v`, 164 165 Args: cobra.RangeArgs(1, 2), 166 167 RunE: func(cmd *cobra.Command, args []string) error { 168 bundleNum, position, err := parseOpArgs(args) 169 if err != nil { 170 return err 171 } 172 173 mgr, _, err := getManager(&ManagerOptions{Cmd: cmd}) 174 if err != nil { 175 return err 176 } 177 defer mgr.Close() 178 179 ctx := context.Background() 180 181 // Time the operation 182 loadStart := time.Now() 183 op, err := mgr.LoadOperation(ctx, bundleNum, position) 184 loadDuration := time.Since(loadStart) 185 186 if err != nil { 187 return err 188 } 189 190 // Time the parsing 191 parseStart := time.Now() 192 opData, parseErr := op.GetOperationData() 193 parseDuration := time.Since(parseStart) 194 195 return displayOperationWithTiming(bundleNum, position, op, opData, parseErr, 196 loadDuration, parseDuration, verbose) 197 }, 198 } 199 200 cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Show timing metrics and full JSON") 201 202 return cmd 203} 204 205// ============================================================================ 206// OP FIND - Find operation by CID 207// ============================================================================ 208 209func newOpFindCommand() *cobra.Command { 210 cmd := &cobra.Command{ 211 Use: "find <cid>", 212 Short: "Find operation by CID", 213 Long: `Find operation by CID across all bundles 214 215Searches the entire repository for an operation with the given CID 216and returns its location (bundle + position). 217 218Note: This performs a full scan and can be slow on large repositories.`, 219 220 Example: ` # Find by CID 221 plcbundle op find bafyreig3tg4k... 222 223 # Pipe to op get 224 plcbundle op find bafyreig3... | awk '{print $3, $5}' | xargs plcbundle op get`, 225 226 Args: cobra.ExactArgs(1), 227 228 RunE: func(cmd *cobra.Command, args []string) error { 229 cid := args[0] 230 231 mgr, _, err := getManager(&ManagerOptions{Cmd: cmd}) 232 if err != nil { 233 return err 234 } 235 defer mgr.Close() 236 237 return findOperationByCID(mgr, cid) 238 }, 239 } 240 241 return cmd 242} 243 244// ============================================================================ 245// Helper Functions 246// ============================================================================ 247 248// parseOpArgs parses operation arguments (supports both formats) 249func parseOpArgs(args []string) (bundleNum, position int, err error) { 250 if len(args) == 1 { 251 global, err := strconv.Atoi(args[0]) 252 if err != nil { 253 return 0, 0, fmt.Errorf("invalid position: %w", err) 254 } 255 256 if global < types.BUNDLE_SIZE { 257 // Small numbers: shorthand for "bundle 1, position N" 258 // op get 1 → bundle 1, position 1 259 // op get 100 → bundle 1, position 100 260 return 1, global, nil 261 } 262 263 // Large numbers: global position 264 // op get 10000 → bundle 1, position 0 265 // op get 88410345 → bundle 8841, position 345 266 bundleNum = global / types.BUNDLE_SIZE 267 position = global % types.BUNDLE_SIZE 268 269 return bundleNum, position, nil 270 } 271 272 if len(args) == 2 { 273 // Explicit: bundle + position 274 bundleNum, err = strconv.Atoi(args[0]) 275 if err != nil { 276 return 0, 0, fmt.Errorf("invalid bundle number: %w", err) 277 } 278 279 position, err = strconv.Atoi(args[1]) 280 if err != nil { 281 return 0, 0, fmt.Errorf("invalid position: %w", err) 282 } 283 284 return bundleNum, position, nil 285 } 286 287 return 0, 0, fmt.Errorf("usage: op <command> <bundle> <position> OR op <command> <globalPosition>") 288} 289 290// findOperationByCID searches for an operation by CID 291func findOperationByCID(mgr BundleManager, cid string) error { 292 ctx := context.Background() 293 294 // CHECK MEMPOOL FIRST (most recent data) 295 fmt.Fprintf(os.Stderr, "Checking mempool...\n") 296 mempoolOps, err := mgr.GetMempoolOperations() 297 if err == nil && len(mempoolOps) > 0 { 298 for pos, op := range mempoolOps { 299 if op.CID == cid { 300 fmt.Printf("Found in mempool: position %d\n\n", pos) 301 fmt.Printf(" DID: %s\n", op.DID) 302 fmt.Printf(" Created: %s\n", op.CreatedAt.Format("2006-01-02 15:04:05")) 303 304 if op.IsNullified() { 305 fmt.Printf(" Status: ✗ Nullified") 306 if nullCID := op.GetNullifyingCID(); nullCID != "" { 307 fmt.Printf(" by %s", nullCID) 308 } 309 fmt.Printf("\n") 310 } else { 311 fmt.Printf(" Status: ✓ Active\n") 312 } 313 314 return nil 315 } 316 } 317 } 318 319 // Search bundles 320 index := mgr.GetIndex() 321 bundles := index.GetBundles() 322 323 if len(bundles) == 0 { 324 fmt.Fprintf(os.Stderr, "No bundles to search\n") 325 return nil 326 } 327 328 fmt.Fprintf(os.Stderr, "Searching %d bundles for CID: %s\n\n", len(bundles), cid) 329 330 for _, meta := range bundles { 331 bundle, err := mgr.LoadBundle(ctx, meta.BundleNumber) 332 if err != nil { 333 continue 334 } 335 336 for pos, op := range bundle.Operations { 337 if op.CID == cid { 338 globalPos := (meta.BundleNumber * types.BUNDLE_SIZE) + pos 339 340 fmt.Printf("Found: bundle %06d, position %d\n", meta.BundleNumber, pos) 341 fmt.Printf("Global position: %d\n\n", globalPos) 342 343 fmt.Printf(" DID: %s\n", op.DID) 344 fmt.Printf(" Created: %s\n", op.CreatedAt.Format("2006-01-02 15:04:05")) 345 346 if op.IsNullified() { 347 fmt.Printf(" Status: ✗ Nullified") 348 if nullCID := op.GetNullifyingCID(); nullCID != "" { 349 fmt.Printf(" by %s", nullCID) 350 } 351 fmt.Printf("\n") 352 } else { 353 fmt.Printf(" Status: ✓ Active\n") 354 } 355 356 return nil 357 } 358 } 359 360 // Progress indicator 361 if meta.BundleNumber%100 == 0 { 362 fmt.Fprintf(os.Stderr, "Searched through bundle %06d...\r", meta.BundleNumber) 363 } 364 } 365 366 fmt.Fprintf(os.Stderr, "\nCID not found: %s\n", cid) 367 fmt.Fprintf(os.Stderr, "(Searched %d bundles + mempool)\n", len(bundles)) 368 return fmt.Errorf("CID not found") 369} 370 371// displayOperationWithTiming shows formatted operation details with timing 372func displayOperationWithTiming(bundleNum, position int, op *plcclient.PLCOperation, 373 opData map[string]interface{}, _ error, 374 loadDuration, parseDuration time.Duration, verbose bool) error { 375 376 globalPos := (bundleNum * types.BUNDLE_SIZE) + position 377 378 fmt.Printf("═══════════════════════════════════════════════════════════════\n") 379 fmt.Printf(" Operation %d\n", globalPos) 380 fmt.Printf("═══════════════════════════════════════════════════════════════\n\n") 381 382 fmt.Printf("Location\n") 383 fmt.Printf("────────\n") 384 fmt.Printf(" Bundle: %06d\n", bundleNum) 385 fmt.Printf(" Position: %d\n", position) 386 fmt.Printf(" Global position: %d\n\n", globalPos) 387 388 fmt.Printf("Identity\n") 389 fmt.Printf("────────\n") 390 fmt.Printf(" DID: %s\n", op.DID) 391 fmt.Printf(" CID: %s\n\n", op.CID) 392 393 fmt.Printf("Timestamp\n") 394 fmt.Printf("─────────\n") 395 fmt.Printf(" Created: %s\n", op.CreatedAt.Format("2006-01-02 15:04:05.000 MST")) 396 fmt.Printf(" Age: %s\n\n", formatDuration(time.Since(op.CreatedAt))) 397 398 // Status 399 status := "✓ Active" 400 if op.IsNullified() { 401 status = "✗ Nullified" 402 if cid := op.GetNullifyingCID(); cid != "" { 403 status += fmt.Sprintf(" by %s", cid) 404 } 405 } 406 fmt.Printf("Status\n") 407 fmt.Printf("──────\n") 408 fmt.Printf(" %s\n\n", status) 409 410 // Performance metrics (always shown if verbose) 411 if verbose { 412 totalTime := loadDuration + parseDuration 413 414 fmt.Printf("Performance\n") 415 fmt.Printf("───────────\n") 416 fmt.Printf(" Load time: %s\n", loadDuration) 417 fmt.Printf(" Parse time: %s\n", parseDuration) 418 fmt.Printf(" Total time: %s\n", totalTime) 419 420 if len(op.RawJSON) > 0 { 421 fmt.Printf(" Data size: %d bytes\n", len(op.RawJSON)) 422 mbPerSec := float64(len(op.RawJSON)) / loadDuration.Seconds() / (1024 * 1024) 423 fmt.Printf(" Load speed: %.2f MB/s\n", mbPerSec) 424 } 425 426 fmt.Printf("\n") 427 } 428 429 // Parse operation details 430 if opData != nil && !op.IsNullified() { 431 fmt.Printf("Details\n") 432 fmt.Printf("───────\n") 433 434 if opType, ok := opData["type"].(string); ok { 435 fmt.Printf(" Type: %s\n", opType) 436 } 437 438 if handle, ok := opData["handle"].(string); ok { 439 fmt.Printf(" Handle: %s\n", handle) 440 } else if aka, ok := opData["alsoKnownAs"].([]interface{}); ok && len(aka) > 0 { 441 if akaStr, ok := aka[0].(string); ok { 442 handle := strings.TrimPrefix(akaStr, "at://") 443 fmt.Printf(" Handle: %s\n", handle) 444 } 445 } 446 447 if services, ok := opData["services"].(map[string]interface{}); ok { 448 if pds, ok := services["atproto_pds"].(map[string]interface{}); ok { 449 if endpoint, ok := pds["endpoint"].(string); ok { 450 fmt.Printf(" PDS: %s\n", endpoint) 451 } 452 } 453 } 454 455 fmt.Printf("\n") 456 } 457 458 // Verbose: show full JSON (pretty-printed) 459 if verbose { 460 fmt.Printf("Raw JSON\n") 461 fmt.Printf("────────\n") 462 463 var data []byte 464 if len(op.RawJSON) > 0 { 465 // Parse and re-format the raw JSON 466 var temp interface{} 467 if err := json.Unmarshal(op.RawJSON, &temp); err == nil { 468 data, _ = json.MarshalIndent(temp, "", " ") 469 } else { 470 // Fallback to raw if parse fails 471 data = op.RawJSON 472 } 473 } else { 474 data, _ = json.MarshalIndent(op, "", " ") 475 } 476 477 fmt.Println(string(data)) 478 fmt.Printf("\n") 479 } 480 481 return nil 482}