[DEPRECATED] Go implementation of plcbundle
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}