[DEPRECATED] Go implementation of plcbundle

update lookup

+427 -39
+123
bundle/did_resolver.go
··· 327 327 328 328 return stats 329 329 } 330 + 331 + // GetDIDOperationsWithLocations returns operations along with their bundle/position info 332 + func (m *Manager) GetDIDOperationsWithLocations(ctx context.Context, did string, verbose bool) ([]PLCOperationWithLocation, error) { 333 + if err := plc.ValidateDIDFormat(did); err != nil { 334 + return nil, err 335 + } 336 + 337 + // Set verbose mode 338 + if m.didIndex != nil { 339 + m.didIndex.SetVerbose(verbose) 340 + } 341 + 342 + // Use index if available 343 + if m.didIndex != nil && m.didIndex.Exists() { 344 + if verbose { 345 + m.logger.Printf("DEBUG: Using DID index for lookup with locations") 346 + } 347 + return m.getDIDOperationsWithLocationsIndexed(ctx, did, verbose) 348 + } 349 + 350 + // Fallback to scan (slower, but still works) 351 + if verbose { 352 + m.logger.Printf("DEBUG: Using full scan with locations") 353 + } 354 + return m.getDIDOperationsWithLocationsScan(ctx, did) 355 + } 356 + 357 + // getDIDOperationsWithLocationsIndexed uses index for fast lookup with locations 358 + func (m *Manager) getDIDOperationsWithLocationsIndexed(ctx context.Context, did string, verbose bool) ([]PLCOperationWithLocation, error) { 359 + locations, err := m.didIndex.GetDIDLocations(did) 360 + if err != nil { 361 + return nil, err 362 + } 363 + 364 + if len(locations) == 0 { 365 + return []PLCOperationWithLocation{}, nil 366 + } 367 + 368 + if verbose { 369 + m.logger.Printf("DEBUG: Found %d locations in index", len(locations)) 370 + } 371 + 372 + // Load operations with their locations preserved 373 + var results []PLCOperationWithLocation 374 + 375 + // Group by bundle for efficient loading 376 + bundleMap := make(map[uint16][]OpLocation) 377 + for _, loc := range locations { 378 + bundleMap[loc.Bundle] = append(bundleMap[loc.Bundle], loc) 379 + } 380 + 381 + if verbose { 382 + m.logger.Printf("DEBUG: Loading from %d bundle(s)", len(bundleMap)) 383 + } 384 + 385 + for bundleNum, locs := range bundleMap { 386 + bundle, err := m.LoadBundle(ctx, int(bundleNum)) 387 + if err != nil { 388 + m.logger.Printf("Warning: failed to load bundle %d: %v", bundleNum, err) 389 + continue 390 + } 391 + 392 + for _, loc := range locs { 393 + if int(loc.Position) >= len(bundle.Operations) { 394 + continue 395 + } 396 + 397 + op := bundle.Operations[loc.Position] 398 + results = append(results, PLCOperationWithLocation{ 399 + Operation: op, 400 + Bundle: int(loc.Bundle), 401 + Position: int(loc.Position), 402 + }) 403 + } 404 + } 405 + 406 + // Sort by time 407 + sort.Slice(results, func(i, j int) bool { 408 + return results[i].Operation.CreatedAt.Before(results[j].Operation.CreatedAt) 409 + }) 410 + 411 + if verbose { 412 + m.logger.Printf("DEBUG: Loaded %d total operations", len(results)) 413 + } 414 + 415 + return results, nil 416 + } 417 + 418 + // getDIDOperationsWithLocationsScan falls back to full scan with locations 419 + func (m *Manager) getDIDOperationsWithLocationsScan(ctx context.Context, did string) ([]PLCOperationWithLocation, error) { 420 + var results []PLCOperationWithLocation 421 + bundles := m.index.GetBundles() 422 + 423 + for _, meta := range bundles { 424 + select { 425 + case <-ctx.Done(): 426 + return nil, ctx.Err() 427 + default: 428 + } 429 + 430 + bundle, err := m.LoadBundle(ctx, meta.BundleNumber) 431 + if err != nil { 432 + m.logger.Printf("Warning: failed to load bundle %d: %v", meta.BundleNumber, err) 433 + continue 434 + } 435 + 436 + for pos, op := range bundle.Operations { 437 + if op.DID == did { 438 + results = append(results, PLCOperationWithLocation{ 439 + Operation: op, 440 + Bundle: meta.BundleNumber, 441 + Position: pos, 442 + }) 443 + } 444 + } 445 + } 446 + 447 + sort.Slice(results, func(i, j int) bool { 448 + return results[i].Operation.CreatedAt.Before(results[j].Operation.CreatedAt) 449 + }) 450 + 451 + return results, nil 452 + }
+7
bundle/types.go
··· 213 213 Interrupted bool 214 214 FailedBundles []int 215 215 } 216 + 217 + // PLCOperationWithLocation contains an operation with its bundle/position metadata 218 + type PLCOperationWithLocation struct { 219 + Operation plc.PLCOperation 220 + Bundle int 221 + Position int 222 + }
+221 -39
cmd/plcbundle/did_index.go
··· 5 5 "flag" 6 6 "fmt" 7 7 "os" 8 + "strings" 8 9 "time" 9 10 10 11 "github.com/goccy/go-json" ··· 176 177 177 178 fs := flag.NewFlagSet("index lookup", flag.ExitOnError) 178 179 verbose := fs.Bool("v", false, "verbose debug output") 180 + showJSON := fs.Bool("json", false, "output as JSON") 179 181 fs.Parse(os.Args[3:]) 180 182 181 183 if fs.NArg() < 1 { 182 - fmt.Fprintf(os.Stderr, "Usage: plcbundle index lookup <did> [-v]\n") 184 + fmt.Fprintf(os.Stderr, "Usage: plcbundle index lookup <did> [-v] [--json]\n") 183 185 os.Exit(1) 184 186 } 185 187 ··· 198 200 fmt.Fprintf(os.Stderr, " Falling back to full scan (this will be slow)...\n\n") 199 201 } 200 202 201 - fmt.Printf("Looking up: %s\n", did) 202 - if *verbose { 203 - fmt.Printf("Verbose mode: enabled\n") 203 + if !*showJSON { 204 + fmt.Printf("Looking up: %s\n", did) 205 + if *verbose { 206 + fmt.Printf("Verbose mode: enabled\n") 207 + } 208 + fmt.Printf("\n") 204 209 } 205 - fmt.Printf("\n") 206 210 207 - start := time.Now() 211 + // === TIMING START === 212 + totalStart := time.Now() 208 213 ctx := context.Background() 209 214 210 - // Get bundled operations only 211 - bundledOps, err := mgr.GetDIDOperationsBundledOnly(ctx, did, *verbose) 215 + // === STEP 1: Index/Scan Lookup === 216 + lookupStart := time.Now() 217 + opsWithLoc, err := mgr.GetDIDOperationsWithLocations(ctx, did, *verbose) 212 218 if err != nil { 213 219 fmt.Fprintf(os.Stderr, "Error: %v\n", err) 214 220 os.Exit(1) 215 221 } 222 + lookupElapsed := time.Since(lookupStart) 216 223 217 - // Get mempool operations separately 224 + // === STEP 2: Mempool Lookup === 225 + mempoolStart := time.Now() 218 226 mempoolOps, err := mgr.GetDIDOperationsFromMempool(did) 219 227 if err != nil { 220 228 fmt.Fprintf(os.Stderr, "Error checking mempool: %v\n", err) 221 229 os.Exit(1) 222 230 } 231 + mempoolElapsed := time.Since(mempoolStart) 223 232 224 - elapsed := time.Since(start) 233 + totalElapsed := time.Since(totalStart) 234 + 235 + // === NOT FOUND === 236 + if len(opsWithLoc) == 0 && len(mempoolOps) == 0 { 237 + if *showJSON { 238 + fmt.Println("{\"found\": false, \"operations\": []}") 239 + } else { 240 + fmt.Printf("DID not found (searched in %s)\n", totalElapsed) 241 + } 242 + return 243 + } 244 + 245 + // === JSON OUTPUT MODE === 246 + if *showJSON { 247 + output := map[string]interface{}{ 248 + "found": true, 249 + "did": did, 250 + "timing": map[string]interface{}{ 251 + "total_ms": totalElapsed.Milliseconds(), 252 + "lookup_ms": lookupElapsed.Milliseconds(), 253 + "mempool_ms": mempoolElapsed.Milliseconds(), 254 + }, 255 + "bundled": make([]map[string]interface{}, 0), 256 + "mempool": make([]map[string]interface{}, 0), 257 + } 258 + 259 + for _, owl := range opsWithLoc { 260 + output["bundled"] = append(output["bundled"].([]map[string]interface{}), map[string]interface{}{ 261 + "bundle": owl.Bundle, 262 + "position": owl.Position, 263 + "cid": owl.Operation.CID, 264 + "nullified": owl.Operation.IsNullified(), 265 + "created_at": owl.Operation.CreatedAt.Format(time.RFC3339Nano), 266 + }) 267 + } 268 + 269 + for _, op := range mempoolOps { 270 + output["mempool"] = append(output["mempool"].([]map[string]interface{}), map[string]interface{}{ 271 + "cid": op.CID, 272 + "nullified": op.IsNullified(), 273 + "created_at": op.CreatedAt.Format(time.RFC3339Nano), 274 + }) 275 + } 225 276 226 - if len(bundledOps) == 0 && len(mempoolOps) == 0 { 227 - fmt.Printf("DID not found (searched in %s)\n", elapsed) 277 + data, _ := json.MarshalIndent(output, "", " ") 278 + fmt.Println(string(data)) 228 279 return 229 280 } 230 281 231 - // Count nullified operations 282 + // === CALCULATE STATISTICS === 232 283 nullifiedCount := 0 233 - for _, op := range bundledOps { 234 - if op.IsNullified() { 284 + for _, owl := range opsWithLoc { 285 + if owl.Operation.IsNullified() { 235 286 nullifiedCount++ 236 287 } 237 288 } 238 289 239 - // Display summary 240 - totalOps := len(bundledOps) + len(mempoolOps) 241 - fmt.Printf("Found %d total operations in %s\n", totalOps, elapsed) 242 - if len(bundledOps) > 0 { 243 - fmt.Printf(" Bundled: %d (%d active, %d nullified)\n", len(bundledOps), len(bundledOps)-nullifiedCount, nullifiedCount) 290 + totalOps := len(opsWithLoc) + len(mempoolOps) 291 + activeOps := len(opsWithLoc) - nullifiedCount + len(mempoolOps) 292 + 293 + // === DISPLAY SUMMARY === 294 + fmt.Printf("═══════════════════════════════════════════════════════════════\n") 295 + fmt.Printf(" DID Lookup Results\n") 296 + fmt.Printf("═══════════════════════════════════════════════════════════════\n\n") 297 + 298 + fmt.Printf("DID: %s\n\n", did) 299 + 300 + fmt.Printf("Summary\n") 301 + fmt.Printf("───────\n") 302 + fmt.Printf(" Total operations: %d\n", totalOps) 303 + fmt.Printf(" Active operations: %d\n", activeOps) 304 + if nullifiedCount > 0 { 305 + fmt.Printf(" Nullified: %d\n", nullifiedCount) 306 + } 307 + if len(opsWithLoc) > 0 { 308 + fmt.Printf(" Bundled: %d\n", len(opsWithLoc)) 244 309 } 245 310 if len(mempoolOps) > 0 { 246 - fmt.Printf(" Mempool: %d (not yet bundled)\n", len(mempoolOps)) 311 + fmt.Printf(" Mempool: %d\n", len(mempoolOps)) 247 312 } 248 313 fmt.Printf("\n") 249 314 250 - // Show bundled operations 251 - if len(bundledOps) > 0 { 252 - fmt.Printf("Bundled operations:\n") 253 - for i, op := range bundledOps { 254 - status := "✓" 315 + // === TIMING BREAKDOWN === 316 + fmt.Printf("Performance\n") 317 + fmt.Printf("───────────\n") 318 + fmt.Printf(" Index lookup: %s\n", lookupElapsed) 319 + fmt.Printf(" Mempool check: %s\n", mempoolElapsed) 320 + fmt.Printf(" Total time: %s\n", totalElapsed) 321 + 322 + if len(opsWithLoc) > 0 { 323 + avgPerOp := lookupElapsed / time.Duration(len(opsWithLoc)) 324 + fmt.Printf(" Avg per operation: %s\n", avgPerOp) 325 + } 326 + fmt.Printf("\n") 327 + 328 + // === BUNDLED OPERATIONS === 329 + if len(opsWithLoc) > 0 { 330 + fmt.Printf("Bundled Operations (%d total)\n", len(opsWithLoc)) 331 + fmt.Printf("══════════════════════════════════════════════════════════════\n\n") 332 + 333 + for i, owl := range opsWithLoc { 334 + op := owl.Operation 335 + status := "✓ Active" 336 + statusSymbol := "✓" 255 337 if op.IsNullified() { 256 - status = "✗" 338 + status = "✗ Nullified" 339 + statusSymbol = "✗" 257 340 } 258 341 259 - fmt.Printf(" %s %d. CID: %s\n", status, i+1, op.CID) 260 - fmt.Printf(" Time: %s\n", op.CreatedAt.Format("2006-01-02 15:04:05")) 342 + fmt.Printf("%s Operation %d [Bundle %06d, Position %04d]\n", 343 + statusSymbol, i+1, owl.Bundle, owl.Position) 344 + fmt.Printf(" CID: %s\n", op.CID) 345 + fmt.Printf(" Created: %s\n", op.CreatedAt.Format("2006-01-02 15:04:05.000 MST")) 346 + fmt.Printf(" Status: %s\n", status) 261 347 262 348 if op.IsNullified() { 263 349 if nullCID := op.GetNullifyingCID(); nullCID != "" { 264 - fmt.Printf(" Nullified by: %s\n", nullCID) 265 - } else { 266 - fmt.Printf(" Nullified: true\n") 350 + fmt.Printf(" Nullified: %s\n", nullCID) 351 + } 352 + } 353 + 354 + // Show operation type if verbose 355 + if *verbose { 356 + if opData, err := op.GetOperationData(); err == nil && opData != nil { 357 + if opType, ok := opData["type"].(string); ok { 358 + fmt.Printf(" Type: %s\n", opType) 359 + } 360 + 361 + // Show handle if present 362 + if handle, ok := opData["handle"].(string); ok { 363 + fmt.Printf(" Handle: %s\n", handle) 364 + } else if aka, ok := opData["alsoKnownAs"].([]interface{}); ok && len(aka) > 0 { 365 + if akaStr, ok := aka[0].(string); ok { 366 + handle := strings.TrimPrefix(akaStr, "at://") 367 + fmt.Printf(" Handle: %s\n", handle) 368 + } 369 + } 370 + 371 + // Show service if present 372 + if services, ok := opData["services"].(map[string]interface{}); ok { 373 + if pds, ok := services["atproto_pds"].(map[string]interface{}); ok { 374 + if endpoint, ok := pds["endpoint"].(string); ok { 375 + fmt.Printf(" PDS: %s\n", endpoint) 376 + } 377 + } 378 + } 267 379 } 268 380 } 381 + 382 + fmt.Printf("\n") 269 383 } 270 - fmt.Printf("\n") 271 384 } 272 385 273 - // Show mempool operations 386 + // === MEMPOOL OPERATIONS === 274 387 if len(mempoolOps) > 0 { 275 - fmt.Printf("Mempool operations (not yet bundled):\n") 388 + fmt.Printf("Mempool Operations (%d total, not yet bundled)\n", len(mempoolOps)) 389 + fmt.Printf("══════════════════════════════════════════════════════════════\n\n") 390 + 276 391 for i, op := range mempoolOps { 277 - status := "✓" 392 + status := "✓ Active" 393 + statusSymbol := "✓" 278 394 if op.IsNullified() { 279 - status = "✗" 395 + status = "✗ Nullified" 396 + statusSymbol = "✗" 280 397 } 281 398 282 - fmt.Printf(" %s %d. CID: %s\n", status, i+1, op.CID) 283 - fmt.Printf(" Time: %s\n", op.CreatedAt.Format("2006-01-02 15:04:05")) 399 + fmt.Printf("%s Operation %d [Mempool]\n", statusSymbol, i+1) 400 + fmt.Printf(" CID: %s\n", op.CID) 401 + fmt.Printf(" Created: %s\n", op.CreatedAt.Format("2006-01-02 15:04:05.000 MST")) 402 + fmt.Printf(" Status: %s\n", status) 403 + 404 + if op.IsNullified() { 405 + if nullCID := op.GetNullifyingCID(); nullCID != "" { 406 + fmt.Printf(" Nullified: %s\n", nullCID) 407 + } 408 + } 409 + 410 + // Show operation type if verbose 411 + if *verbose { 412 + if opData, err := op.GetOperationData(); err == nil && opData != nil { 413 + if opType, ok := opData["type"].(string); ok { 414 + fmt.Printf(" Type: %s\n", opType) 415 + } 416 + 417 + // Show handle 418 + if handle, ok := opData["handle"].(string); ok { 419 + fmt.Printf(" Handle: %s\n", handle) 420 + } else if aka, ok := opData["alsoKnownAs"].([]interface{}); ok && len(aka) > 0 { 421 + if akaStr, ok := aka[0].(string); ok { 422 + handle := strings.TrimPrefix(akaStr, "at://") 423 + fmt.Printf(" Handle: %s\n", handle) 424 + } 425 + } 426 + } 427 + } 428 + 429 + fmt.Printf("\n") 284 430 } 285 431 } 432 + 433 + // === TIMELINE (if multiple operations) === 434 + if totalOps > 1 && !*verbose { 435 + fmt.Printf("Timeline\n") 436 + fmt.Printf("────────\n") 437 + 438 + allTimes := make([]time.Time, 0, totalOps) 439 + for _, owl := range opsWithLoc { 440 + allTimes = append(allTimes, owl.Operation.CreatedAt) 441 + } 442 + for _, op := range mempoolOps { 443 + allTimes = append(allTimes, op.CreatedAt) 444 + } 445 + 446 + if len(allTimes) > 0 { 447 + firstTime := allTimes[0] 448 + lastTime := allTimes[len(allTimes)-1] 449 + timespan := lastTime.Sub(firstTime) 450 + 451 + fmt.Printf(" First operation: %s\n", firstTime.Format("2006-01-02 15:04:05")) 452 + fmt.Printf(" Latest operation: %s\n", lastTime.Format("2006-01-02 15:04:05")) 453 + fmt.Printf(" Timespan: %s\n", formatDuration(timespan)) 454 + fmt.Printf(" Activity age: %s ago\n", formatDuration(time.Since(lastTime))) 455 + } 456 + fmt.Printf("\n") 457 + } 458 + 459 + // === FINAL SUMMARY === 460 + fmt.Printf("═══════════════════════════════════════════════════════════════\n") 461 + fmt.Printf("✓ Lookup complete in %s\n", totalElapsed) 462 + if stats["exists"].(bool) { 463 + fmt.Printf(" Method: DID index (fast)\n") 464 + } else { 465 + fmt.Printf(" Method: Full scan (slow)\n") 466 + } 467 + fmt.Printf("═══════════════════════════════════════════════════════════════\n") 286 468 } 287 469 288 470 func cmdDIDIndexResolve() {
+52
cmd/plcbundle/get_op.go
··· 1 + package main 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + "os" 7 + "strconv" 8 + 9 + "github.com/goccy/go-json" 10 + ) 11 + 12 + func cmdGetOp() { 13 + if len(os.Args) < 4 { 14 + fmt.Fprintf(os.Stderr, "Usage: plcbundle get-op <bundle> <position>\n") 15 + fmt.Fprintf(os.Stderr, "Example: plcbundle get-op 42 1337\n") 16 + os.Exit(1) 17 + } 18 + 19 + bundleNum, err := strconv.Atoi(os.Args[2]) 20 + if err != nil { 21 + fmt.Fprintf(os.Stderr, "Error: invalid bundle number\n") 22 + os.Exit(1) 23 + } 24 + 25 + position, err := strconv.Atoi(os.Args[3]) 26 + if err != nil { 27 + fmt.Fprintf(os.Stderr, "Error: invalid position\n") 28 + os.Exit(1) 29 + } 30 + 31 + mgr, _, err := getManager("") 32 + if err != nil { 33 + fmt.Fprintf(os.Stderr, "Error: %v\n", err) 34 + os.Exit(1) 35 + } 36 + defer mgr.Close() 37 + 38 + ctx := context.Background() 39 + op, err := mgr.LoadOperation(ctx, bundleNum, position) 40 + if err != nil { 41 + fmt.Fprintf(os.Stderr, "Error: %v\n", err) 42 + os.Exit(1) 43 + } 44 + 45 + // Output JSON 46 + if len(op.RawJSON) > 0 { 47 + fmt.Println(string(op.RawJSON)) 48 + } else { 49 + data, _ := json.Marshal(op) 50 + fmt.Println(string(data)) 51 + } 52 + }
+2
cmd/plcbundle/main.go
··· 90 90 cmdDetector() 91 91 case "index": 92 92 cmdDIDIndex() 93 + case "get-op": 94 + cmdGetOp() 93 95 case "version": 94 96 fmt.Printf("plcbundle version %s\n", version) 95 97 fmt.Printf(" commit: %s\n", gitCommit)
+22
scripts/get-random-dids.sh
··· 1 + #!/bin/bash 2 + # get-random-dids.sh - Extract N DIDs from random positions 3 + 4 + N=${1:-10} 5 + 6 + # Get bundle range 7 + read FIRST LAST < <(plcbundle info 2>/dev/null | awk '/Range:/ {print $2, $4}' | tr -d '→') 8 + 9 + echo "Sampling $N DIDs from bundles $FIRST-$LAST" >&2 10 + echo "" >&2 11 + 12 + for i in $(seq 1 $N); do 13 + BUNDLE=$(jot -r 1 $FIRST $LAST) 14 + POS=$(jot -r 1 0 9999) 15 + 16 + echo "[$i/$N] Bundle $BUNDLE, position $POS" >&2 17 + 18 + plcbundle get-op $BUNDLE $POS 2>/dev/null | jq -r '.did' 19 + done | jq -R . | jq -s . 20 + 21 + echo "" >&2 22 + echo "✓ Done" >&2