tangled
alpha
login
or
join now
angrydutchman.peedee.es
/
plcbundle
forked from
atscan.net/plcbundle
0
fork
atom
A Transparent and Verifiable Way to Sync the AT Protocol's PLC Directory
0
fork
atom
overview
issues
pulls
pipelines
update cmds
tree.fail
4 months ago
6aaff29f
99fb2e58
+131
-97
20 changed files
expand all
collapse all
unified
split
bundle
manager.go
types.go
cmd
plcbundle
commands
clone.go
common.go
detector.go
did.go
diff.go
export.go
getop.go
index.go
log.go
ls.go
mempool.go
rollback.go
server.go
status.go
stream.go
sync.go
verify.go
internal
didindex
manager.go
+30
-1
bundle/manager.go
···
61
61
config.Logger = defaultLogger{}
62
62
}
63
63
64
64
-
// Ensure directory exists
64
64
+
// CHECK: Don't auto-create if repository doesn't exist
65
65
+
repoExists := repositoryExists(config.BundleDir)
66
66
+
67
67
+
if !repoExists && !config.AutoInit {
68
68
+
return nil, fmt.Errorf(
69
69
+
"no plcbundle repository found in: %s\n\n"+
70
70
+
"Initialize a new repository with:\n"+
71
71
+
" plcbundle clone <url> # Clone from remote\n"+
72
72
+
" plcbundle sync # Fetch from PLC directory",
73
73
+
config.BundleDir,
74
74
+
)
75
75
+
}
76
76
+
77
77
+
// Ensure directory exists (only if repo exists OR AutoInit is enabled)
65
78
if err := os.MkdirAll(config.BundleDir, 0755); err != nil {
66
79
return nil, fmt.Errorf("failed to create bundle directory: %w", err)
67
80
}
···
1444
1457
1445
1458
return true, nil
1446
1459
}
1460
1460
+
1461
1461
+
// Add this helper function at the top of manager.go
1462
1462
+
func repositoryExists(bundleDir string) bool {
1463
1463
+
indexPath := filepath.Join(bundleDir, bundleindex.INDEX_FILE)
1464
1464
+
1465
1465
+
// Check for index file
1466
1466
+
if _, err := os.Stat(indexPath); err == nil {
1467
1467
+
return true
1468
1468
+
}
1469
1469
+
1470
1470
+
// Check for bundle files
1471
1471
+
bundleFiles, _ := filepath.Glob(filepath.Join(bundleDir, "*.jsonl.zst"))
1472
1472
+
bundleFiles = filterBundleFiles(bundleFiles)
1473
1473
+
1474
1474
+
return len(bundleFiles) > 0
1475
1475
+
}
+2
bundle/types.go
···
122
122
BundleDir string
123
123
VerifyOnLoad bool
124
124
AutoRebuild bool
125
125
+
AutoInit bool // Allow auto-creating empty repository
125
126
RebuildWorkers int // Number of workers for parallel rebuild (0 = auto-detect)
126
127
RebuildProgress func(current, total int) // Progress callback for rebuild
127
128
Logger types.Logger
···
133
134
BundleDir: bundleDir,
134
135
VerifyOnLoad: true,
135
136
AutoRebuild: true,
137
137
+
AutoInit: false,
136
138
RebuildWorkers: 0, // 0 means auto-detect CPU count
137
139
RebuildProgress: nil, // No progress callback by default
138
140
Logger: nil,
+6
-6
cmd/plcbundle/commands/clone.go
···
99
99
return fmt.Errorf("invalid directory path: %w", err)
100
100
}
101
101
102
102
-
if err := os.MkdirAll(absDir, 0755); err != nil {
103
103
-
return fmt.Errorf("failed to create directory: %w", err)
104
104
-
}
105
105
-
106
106
-
// ✨ Create manager in target directory
107
107
-
mgr, dir, err := getManagerInDirectory(absDir, "")
102
102
+
// ✨ Clone creates new repository in specific directory
103
103
+
mgr, dir, err := getManager(&ManagerOptions{
104
104
+
Dir: absDir,
105
105
+
PLCURL: "https://plc.directory",
106
106
+
AutoInit: true,
107
107
+
})
108
108
if err != nil {
109
109
return err
110
110
}
+53
-45
cmd/plcbundle/commands/common.go
···
48
48
// PLCOperationWithLocation wraps operation with location info
49
49
type PLCOperationWithLocation = bundle.PLCOperationWithLocation
50
50
51
51
-
// getManager creates or opens a bundle manager
52
52
-
func getManager(plcURL string) (*bundle.Manager, string, error) {
53
53
-
dir, err := os.Getwd()
54
54
-
if err != nil {
55
55
-
return nil, "", err
56
56
-
}
51
51
+
// ============================================================================
52
52
+
// ✨ MANAGER OPTIONS STRUCT
53
53
+
// ============================================================================
57
54
58
58
-
if err := os.MkdirAll(dir, 0755); err != nil {
59
59
-
return nil, "", fmt.Errorf("failed to create directory: %w", err)
60
60
-
}
61
61
-
62
62
-
config := bundle.DefaultConfig(dir)
63
63
-
64
64
-
var client *plcclient.Client
65
65
-
if plcURL != "" {
66
66
-
client = plcclient.NewClient(plcURL)
67
67
-
}
68
68
-
69
69
-
mgr, err := bundle.NewManager(config, client)
70
70
-
if err != nil {
71
71
-
return nil, "", err
72
72
-
}
73
73
-
74
74
-
return mgr, dir, nil
55
55
+
// ManagerOptions configures manager creation
56
56
+
type ManagerOptions struct {
57
57
+
Cmd *cobra.Command // Optional: for reading --dir flag
58
58
+
Dir string // Optional: explicit directory (overrides Cmd flag and cwd)
59
59
+
PLCURL string // Optional: PLC directory URL
60
60
+
AutoInit bool // Optional: allow creating new empty repository (default: false)
75
61
}
76
62
77
77
-
// getManagerInDirectory creates manager in specific directory
78
78
-
func getManagerInDirectory(dir string, plcURL string) (*bundle.Manager, string, error) {
79
79
-
if err := os.MkdirAll(dir, 0755); err != nil {
80
80
-
return nil, "", fmt.Errorf("failed to create directory: %w", err)
81
81
-
}
82
82
-
83
83
-
config := bundle.DefaultConfig(dir)
84
84
-
85
85
-
var client *plcclient.Client
86
86
-
if plcURL != "" {
87
87
-
client = plcclient.NewClient(plcURL)
88
88
-
}
63
63
+
// ============================================================================
64
64
+
// ✨ SINGLE UNIFIED getManager METHOD
65
65
+
// ============================================================================
89
66
90
90
-
mgr, err := bundle.NewManager(config, client)
91
91
-
if err != nil {
92
92
-
return nil, "", err
67
67
+
// getManager creates or opens a bundle manager
68
68
+
// Pass nil for default options (read-only, current directory, no PLC client)
69
69
+
//
70
70
+
// Examples:
71
71
+
//
72
72
+
// getManager(nil) // All defaults
73
73
+
// getManager(&ManagerOptions{Cmd: cmd}) // Use --dir flag
74
74
+
// getManager(&ManagerOptions{PLCURL: url}) // Add PLC client
75
75
+
// getManager(&ManagerOptions{AutoInit: true}) // Allow creating repo
76
76
+
// getManager(&ManagerOptions{Dir: "/path", AutoInit: true}) // Explicit dir + create
77
77
+
func getManager(opts *ManagerOptions) (*bundle.Manager, string, error) {
78
78
+
// Use defaults if nil
79
79
+
if opts == nil {
80
80
+
opts = &ManagerOptions{}
93
81
}
94
82
95
95
-
return mgr, dir, nil
96
96
-
}
83
83
+
// Determine directory (priority: explicit Dir > Cmd flag > current working dir)
84
84
+
var dir string
97
85
98
98
-
// getManagerFromCommand creates manager using command flags
99
99
-
func getManagerFromCommand(cmd *cobra.Command, plcURL string) (*bundle.Manager, string, error) {
100
100
-
// Get --dir flag from root command
101
101
-
dir, _ := cmd.Root().PersistentFlags().GetString("dir")
86
86
+
if opts.Dir != "" {
87
87
+
// Explicit directory provided
88
88
+
dir = opts.Dir
89
89
+
} else if opts.Cmd != nil {
90
90
+
// Try to get from command --dir flag
91
91
+
dir, _ = opts.Cmd.Root().PersistentFlags().GetString("dir")
92
92
+
}
102
93
94
94
+
// Fallback to current working directory
103
95
if dir == "" {
104
96
var err error
105
97
dir, err = os.Getwd()
···
114
106
return nil, "", fmt.Errorf("invalid directory path: %w", err)
115
107
}
116
108
117
117
-
return getManagerInDirectory(absDir, plcURL)
109
109
+
// Create config
110
110
+
config := bundle.DefaultConfig(absDir)
111
111
+
config.AutoInit = opts.AutoInit
112
112
+
113
113
+
// Create PLC client if URL provided
114
114
+
var client *plcclient.Client
115
115
+
if opts.PLCURL != "" {
116
116
+
client = plcclient.NewClient(opts.PLCURL)
117
117
+
}
118
118
+
119
119
+
// Create manager
120
120
+
mgr, err := bundle.NewManager(config, client)
121
121
+
if err != nil {
122
122
+
return nil, "", err
123
123
+
}
124
124
+
125
125
+
return mgr, absDir, nil
118
126
}
119
127
120
128
// parseBundleRange parses bundle range string
+2
-2
cmd/plcbundle/commands/detector.go
···
99
99
return fmt.Errorf("--bundle required")
100
100
}
101
101
102
102
-
mgr, _, err := getManager("")
102
102
+
mgr, _, err := getManager(nil)
103
103
if err != nil {
104
104
return err
105
105
}
···
195
195
}()
196
196
}
197
197
198
198
-
mgr, _, err := getManager("")
198
198
+
mgr, _, err := getManager(nil)
199
199
if err != nil {
200
200
return err
201
201
}
+5
-5
cmd/plcbundle/commands/did.go
···
89
89
RunE: func(cmd *cobra.Command, args []string) error {
90
90
did := args[0]
91
91
92
92
-
mgr, _, err := getManagerFromCommand(cmd, "")
92
92
+
mgr, _, err := getManager(&ManagerOptions{Cmd: cmd})
93
93
if err != nil {
94
94
return err
95
95
}
···
185
185
RunE: func(cmd *cobra.Command, args []string) error {
186
186
did := args[0]
187
187
188
188
-
mgr, _, err := getManagerFromCommand(cmd, "")
188
188
+
mgr, _, err := getManager(&ManagerOptions{Cmd: cmd})
189
189
if err != nil {
190
190
return err
191
191
}
···
274
274
RunE: func(cmd *cobra.Command, args []string) error {
275
275
did := args[0]
276
276
277
277
-
mgr, _, err := getManagerFromCommand(cmd, "")
277
277
+
mgr, _, err := getManager(&ManagerOptions{Cmd: cmd})
278
278
if err != nil {
279
279
return err
280
280
}
···
383
383
" cat dids.txt | plcbundle did batch -")
384
384
}
385
385
386
386
-
mgr, _, err := getManagerFromCommand(cmd, "")
386
386
+
mgr, _, err := getManager(&ManagerOptions{Cmd: cmd})
387
387
if err != nil {
388
388
return err
389
389
}
···
439
439
Args: cobra.MaximumNArgs(1),
440
440
441
441
RunE: func(cmd *cobra.Command, args []string) error {
442
442
-
mgr, dir, err := getManagerFromCommand(cmd, "")
442
442
+
mgr, dir, err := getManager(&ManagerOptions{Cmd: cmd})
443
443
if err != nil {
444
444
return err
445
445
}
+1
-1
cmd/plcbundle/commands/diff.go
···
67
67
RunE: func(cmd *cobra.Command, args []string) error {
68
68
target := args[0]
69
69
70
70
-
mgr, dir, err := getManagerFromCommand(cmd, "")
70
70
+
mgr, dir, err := getManager(&ManagerOptions{Cmd: cmd})
71
71
if err != nil {
72
72
return err
73
73
}
+1
-1
cmd/plcbundle/commands/export.go
···
34
34
" plcbundle export --bundles 42 | jq .")
35
35
}
36
36
37
37
-
mgr, _, err := getManager("")
37
37
+
mgr, _, err := getManager(&ManagerOptions{Cmd: nil})
38
38
if err != nil {
39
39
return err
40
40
}
+1
-1
cmd/plcbundle/commands/getop.go
···
25
25
return fmt.Errorf("invalid position")
26
26
}
27
27
28
28
-
mgr, _, err := getManager("")
28
28
+
mgr, _, err := getManager(nil)
29
29
if err != nil {
30
30
return err
31
31
}
+4
-4
cmd/plcbundle/commands/index.go
···
63
63
return err
64
64
}
65
65
66
66
-
mgr, dir, err := getManager("")
66
66
+
mgr, dir, err := getManager(nil)
67
67
if err != nil {
68
68
return err
69
69
}
···
115
115
}
116
116
117
117
func indexStats(args []string) error {
118
118
-
mgr, dir, err := getManager("")
118
118
+
mgr, dir, err := getManager(nil)
119
119
if err != nil {
120
120
return err
121
121
}
···
184
184
185
185
did := fs.Arg(0)
186
186
187
187
-
mgr, _, err := getManager("")
187
187
+
mgr, _, err := getManager(nil)
188
188
if err != nil {
189
189
return err
190
190
}
···
255
255
256
256
did := fs.Arg(0)
257
257
258
258
-
mgr, _, err := getManager("")
258
258
+
mgr, _, err := getManager(nil)
259
259
if err != nil {
260
260
return err
261
261
}
+1
-1
cmd/plcbundle/commands/log.go
···
68
68
plcbundle history -n 5`,
69
69
70
70
RunE: func(cmd *cobra.Command, args []string) error {
71
71
-
mgr, dir, err := getManagerFromCommand(cmd, "")
71
71
+
mgr, dir, err := getManager(&ManagerOptions{Cmd: cmd})
72
72
if err != nil {
73
73
return err
74
74
}
+1
-1
cmd/plcbundle/commands/ls.go
···
52
52
plcbundle ls --separator "," > bundles.csv # Export to CSV`,
53
53
54
54
RunE: func(cmd *cobra.Command, args []string) error {
55
55
-
mgr, _, err := getManagerFromCommand(cmd, "")
55
55
+
mgr, _, err := getManager(&ManagerOptions{Cmd: cmd})
56
56
if err != nil {
57
57
return err
58
58
}
+3
-3
cmd/plcbundle/commands/mempool.go
···
92
92
}
93
93
}
94
94
95
95
-
mgr, dir, err := getManagerFromCommand(cmd, "")
95
95
+
mgr, dir, err := getManager(&ManagerOptions{Cmd: cmd})
96
96
if err != nil {
97
97
return err
98
98
}
···
130
130
plcbundle mempool clear -f`,
131
131
132
132
RunE: func(cmd *cobra.Command, args []string) error {
133
133
-
mgr, dir, err := getManagerFromCommand(cmd, "")
133
133
+
mgr, dir, err := getManager(&ManagerOptions{Cmd: cmd})
134
134
if err != nil {
135
135
return err
136
136
}
···
204
204
plcbundle mempool export`,
205
205
206
206
RunE: func(cmd *cobra.Command, args []string) error {
207
207
-
mgr, _, err := getManagerFromCommand(cmd, "")
207
207
+
mgr, _, err := getManager(&ManagerOptions{Cmd: cmd})
208
208
if err != nil {
209
209
return err
210
210
}
+1
-1
cmd/plcbundle/commands/rollback.go
···
63
63
RunE: func(cmd *cobra.Command, args []string) error {
64
64
verbose, _ := cmd.Root().PersistentFlags().GetBool("verbose")
65
65
66
66
-
mgr, dir, err := getManagerFromCommand(cmd, "")
66
66
+
mgr, dir, err := getManager(&ManagerOptions{Cmd: cmd})
67
67
if err != nil {
68
68
return err
69
69
}
+6
-11
cmd/plcbundle/commands/server.go
···
76
76
RunE: func(cmd *cobra.Command, args []string) error {
77
77
verbose, _ := cmd.Root().PersistentFlags().GetBool("verbose")
78
78
79
79
-
// Get manager from command (uses --dir flag)
80
80
-
var mgr *bundle.Manager
81
81
-
var dir string
82
82
-
var err error
83
83
-
84
84
-
if syncMode {
85
85
-
mgr, dir, err = getManagerFromCommand(cmd, plcURL)
86
86
-
} else {
87
87
-
mgr, dir, err = getManagerFromCommand(cmd, "")
88
88
-
}
89
89
-
79
79
+
// ✨ Server in sync mode can create repo, read-only mode cannot
80
80
+
mgr, dir, err := getManager(&ManagerOptions{
81
81
+
Cmd: cmd,
82
82
+
PLCURL: plcURL,
83
83
+
AutoInit: syncMode, // Only auto-init if syncing
84
84
+
})
90
85
if err != nil {
91
86
return err
92
87
}
+1
-1
cmd/plcbundle/commands/status.go
···
27
27
plcbundle info`,
28
28
29
29
RunE: func(cmd *cobra.Command, args []string) error {
30
30
-
mgr, dir, err := getManagerFromCommand(cmd, "")
30
30
+
mgr, dir, err := getManager(&ManagerOptions{Cmd: cmd})
31
31
if err != nil {
32
32
return err
33
33
}
+1
-7
cmd/plcbundle/commands/stream.go
···
56
56
verbose, _ := cmd.Root().PersistentFlags().GetBool("verbose")
57
57
quiet, _ := cmd.Root().PersistentFlags().GetBool("quiet")
58
58
59
59
-
// Setup PLC client only if syncing
60
60
-
var effectivePLCURL string
61
61
-
if sync {
62
62
-
effectivePLCURL = plcURL
63
63
-
}
64
64
-
65
65
-
mgr, dir, err := getManagerFromCommand(cmd, effectivePLCURL)
59
59
+
mgr, dir, err := getManager(&ManagerOptions{Cmd: cmd, PLCURL: plcURL})
66
60
if err != nil {
67
61
return err
68
62
}
+6
-2
cmd/plcbundle/commands/sync.go
···
57
57
verbose, _ := cmd.Root().PersistentFlags().GetBool("verbose")
58
58
quiet, _ := cmd.Root().PersistentFlags().GetBool("quiet")
59
59
60
60
-
// Get manager using global --dir flag
61
61
-
mgr, dir, err := getManagerFromCommand(cmd, plcURL)
60
60
+
// ✨ Sync creates repository if missing
61
61
+
mgr, dir, err := getManager(&ManagerOptions{
62
62
+
Cmd: cmd,
63
63
+
PLCURL: plcURL,
64
64
+
AutoInit: true,
65
65
+
})
62
66
if err != nil {
63
67
return err
64
68
}
+1
-1
cmd/plcbundle/commands/verify.go
···
55
55
RunE: func(cmd *cobra.Command, args []string) error {
56
56
verbose, _ := cmd.Root().PersistentFlags().GetBool("verbose")
57
57
58
58
-
mgr, dir, err := getManagerFromCommand(cmd, "")
58
58
+
mgr, dir, err := getManager(&ManagerOptions{Cmd: cmd})
59
59
if err != nil {
60
60
return err
61
61
}
+5
-3
internal/didindex/manager.go
···
21
21
shardDir := filepath.Join(indexDir, DID_INDEX_SHARDS)
22
22
configPath := filepath.Join(indexDir, DID_INDEX_CONFIG)
23
23
24
24
-
// Ensure directories exist
25
25
-
os.MkdirAll(shardDir, 0755)
26
26
-
27
24
// Load or create config
28
25
config, _ := loadIndexConfig(configPath)
29
26
if config == nil {
···
46
43
config: config,
47
44
logger: logger,
48
45
}
46
46
+
}
47
47
+
48
48
+
// Add helper to ensure directories when actually writing
49
49
+
func (dim *Manager) ensureDirectories() error {
50
50
+
return os.MkdirAll(dim.shardDir, 0755)
49
51
}
50
52
51
53
// Close unmaps all shards and cleans up