Skip to content

Commit 978b23f

Browse files
committed
✨ Create the registry related command
This commit adds the commands related to the registry (add, delete, refresh, list) Moreover, it fixes a few issues: - If the output struct has a non-default format, it is now respected (previously, the struct would default to pretty when InferFlag was called) - A plugin download is now done in a sub folder with a randomly generated ID. This allows multiple instances of anyquery to cohabit with different versions of plugins
1 parent cf14435 commit 978b23f

16 files changed

+636
-59
lines changed

cmd/helper.go

+23-1
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,38 @@ import "github.com/spf13/cobra"
44

55
// This file defines a few functions that avoid code repetition for the commands declaration
66

7+
// Set the flags for a command that modifies the configuration
78
func addFlag_commandModifiesConfiguration(cmd *cobra.Command) {
89
cmd.Flags().StringP("config", "c", "", "Path to the configuration database")
910
}
1011

12+
// Same as addFlag_commandModifiesConfiguration but flags will be applied
13+
// to all subcommands
14+
func addPersistentFlag_commandModifiesConfiguration(cmd *cobra.Command) {
15+
cmd.PersistentFlags().StringP("config", "c", "", "Path to the configuration database")
16+
}
17+
1118
// Set the flags for a command that prints data
1219
//
1320
// It helps specify the output format
1421
func addFlag_commandPrintsData(cmd *cobra.Command) {
15-
cmd.Flags().String("format", "pretty", "Output format (pretty, json, csv, plain)")
22+
// We don't add a default value to the format flag
23+
// because cmd wouldn't be able to overwrite the default format
24+
cmd.Flags().String("format", "", "Output format (pretty, json, csv, plain)")
1625
cmd.Flags().Bool("json", false, "Output format as JSON")
1726
cmd.Flags().Bool("csv", false, "Output format as CSV")
1827
cmd.Flags().Bool("plain", false, "Output format as plain text")
1928
}
29+
30+
// Same as addFlag_commandPrintsData but flags will be applied
31+
// to all subcommands
32+
func addPersistentFlag_commandPrintsData(cmd *cobra.Command) {
33+
cmd.PersistentFlags().String("format", "", "Output format (pretty, json, csv, plain)")
34+
cmd.PersistentFlags().Bool("json", false, "Output format as JSON")
35+
cmd.PersistentFlags().Bool("csv", false, "Output format as CSV")
36+
cmd.PersistentFlags().Bool("plain", false, "Output format as plain text")
37+
}
38+
39+
func addFlag_commandCanBeInteractive(cmd *cobra.Command) {
40+
cmd.Flags().Bool("no-input", false, "Do not ask for input")
41+
}

cmd/registry.go

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package cmd
2+
3+
import (
4+
"github.com/julien040/anyquery/controller"
5+
"github.com/spf13/cobra"
6+
)
7+
8+
var registryCmd = &cobra.Command{
9+
Use: "registry",
10+
Short: "List the registries where plugins can be downloaded",
11+
RunE: controller.RegistryList,
12+
Aliases: []string{"registries"},
13+
SuggestFor: []string{"store"},
14+
Args: cobra.NoArgs,
15+
}
16+
17+
var registryListCmd = &cobra.Command{
18+
Use: "list",
19+
Short: "List the registries where plugins can be downloaded",
20+
RunE: controller.RegistryList,
21+
Aliases: []string{"ls"},
22+
}
23+
24+
var registryAddCmd = &cobra.Command{
25+
Use: "add [name] [url]",
26+
Short: "Add a new registry",
27+
Args: cobra.MaximumNArgs(2),
28+
RunE: controller.RegistryAdd,
29+
Aliases: []string{"new", "create", "register", "install"},
30+
}
31+
32+
var registryRemoveCmd = &cobra.Command{
33+
Use: "remove",
34+
Short: "Remove a registry",
35+
RunE: controller.RegistryRemove,
36+
Aliases: []string{"rm", "delete"},
37+
}
38+
39+
var registryGetCmd = &cobra.Command{
40+
Use: "get [name]",
41+
Short: "Print informations about a registry",
42+
Args: cobra.ExactArgs(1),
43+
RunE: controller.RegistryGet,
44+
Aliases: []string{"info", "show", "inspect"},
45+
}
46+
47+
var registryRefreshCmd = &cobra.Command{
48+
Use: "refresh [name]",
49+
Short: "Keep the registry up to date with the server",
50+
Long: `This command will fetch the registry and save the available plugins for download in the configuration database.
51+
If a name is provided, only this registry will be refreshed. Otherwise, all registries will be refreshed.`,
52+
Args: cobra.MaximumNArgs(1),
53+
RunE: controller.RegistryRefresh,
54+
Aliases: []string{"update", "sync", "fetch", "pull"},
55+
}
56+
57+
func init() {
58+
rootCmd.AddCommand(registryCmd)
59+
addFlag_commandPrintsData(registryCmd)
60+
// Set the --config flag for all subcommands and the command itself
61+
addPersistentFlag_commandModifiesConfiguration(registryCmd)
62+
registryCmd.AddCommand(registryListCmd)
63+
addFlag_commandPrintsData(registryListCmd)
64+
registryCmd.AddCommand(registryAddCmd)
65+
registryCmd.AddCommand(registryRemoveCmd)
66+
registryCmd.AddCommand(registryGetCmd)
67+
addFlag_commandPrintsData(registryGetCmd)
68+
registryCmd.AddCommand(registryRefreshCmd)
69+
}

cmd/store.go

-22
This file was deleted.

controller/config/database.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@ func OpenDatabaseConnection(path string, readOnly bool) (*sql.DB, *model.Queries
4040
readOnly = false
4141
}
4242

43-
sqlitePath := "file:" + path + "?cache=shared&_cache_size=-50000&_foreign_keys=ON"
43+
// We disable foreign keys because a registry can be deleted
44+
// while its plugins are still in the database
45+
//
46+
// We add ?_loc=auto to let SQLite use the local timezone
47+
sqlitePath := "file:" + path + "?cache=shared&_cache_size=-50000&_foreign_keys=OFF&_loc=auto"
4448
if readOnly {
4549
sqlitePath += "&mode=ro"
4650
}

controller/config/model/models.go

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

controller/config/model/query.sql.go

+38-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

controller/config/query.sql

+20-1
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ INSERT INTO
44
name,
55
url,
66
lastUpdated,
7+
lastFetched,
78
checksumRegistry,
89
registryJSON
910
)
1011
VALUES
11-
(?, ?, ?, ?, ?);
12+
(?, ?, ?, unixepoch(), ?, ?);
1213

1314
-- name: AddPlugin :exec
1415
INSERT INTO
@@ -124,7 +125,25 @@ UPDATE
124125
SET
125126
url = ?,
126127
lastUpdated = ?,
128+
lastFetched = unixepoch(),
127129
checksumRegistry = ?,
128130
registryJSON = ?
131+
WHERE
132+
name = ?;
133+
134+
-- name: UpdateRegistryFetched :exec
135+
UPDATE
136+
registry
137+
SET
138+
lastFetched = unixepoch()
139+
WHERE
140+
name = ?;
141+
142+
/* -------------------------------------------------------------------------- */
143+
/* DELETE */
144+
/* -------------------------------------------------------------------------- */
145+
-- name: DeleteRegistry :exec
146+
DELETE FROM
147+
registry
129148
WHERE
130149
name = ?;

controller/config/registry/plugin.go

+26-10
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import (
55
"database/sql"
66
"encoding/json"
77
"fmt"
8+
"math/rand/v2"
89
urlParser "net/url"
910
"os"
1011
"path"
1112
"runtime"
13+
"strings"
1214

1315
"github.com/Masterminds/semver/v3"
1416
"github.com/adrg/xdg"
@@ -63,18 +65,18 @@ func FindPluginVersionCandidate(plugin Plugin) (PluginFile, PluginVersion, error
6365
return *candidateFile, *candidateVersion, nil
6466
}
6567

66-
func InstallPlugin(queries *model.Queries, registry string, plugin string) error {
68+
func InstallPlugin(queries *model.Queries, registry string, plugin string) (string, error) {
6769
// Create the plugin directory
68-
path := path.Join(xdg.DataHome, "anyquery", "plugins", registry, plugin)
70+
path := path.Join(xdg.DataHome, "anyquery", "plugins", registry, plugin, newSmallID())
6971
err := os.MkdirAll(path, 0755)
7072
if err != nil {
71-
return err
73+
return "", err
7274
}
7375

7476
// Get the registry
7577
_, plugins, err := LoadRegistry(queries, registry)
7678
if err != nil {
77-
return err
79+
return "", err
7880
}
7981

8082
// Find the plugin
@@ -88,17 +90,17 @@ func InstallPlugin(queries *model.Queries, registry string, plugin string) error
8890

8991
// Find a compatible version
9092
if pluginInfo == nil {
91-
return fmt.Errorf("plugin %s not found in registry %s", plugin, registry)
93+
return "", fmt.Errorf("plugin %s not found in registry %s", plugin, registry)
9294
}
9395

9496
file, version, err := FindPluginVersionCandidate(*pluginInfo)
9597
if err != nil {
96-
return err
98+
return "", err
9799
}
98100
// Download the file
99101
err = downloadZipToPath(file.URL, path, file.Hash)
100102
if err != nil {
101-
return err
103+
return "", err
102104
}
103105

104106
// Add the plugin to the database
@@ -109,13 +111,13 @@ func InstallPlugin(queries *model.Queries, registry string, plugin string) error
109111

110112
configJSON, err := json.Marshal(version.UserConfig)
111113
if err != nil {
112-
return err
114+
return "", err
113115
}
114116
tablesJSON, err := json.Marshal(version.Tables)
115117
if err != nil {
116-
return err
118+
return "", err
117119
}
118-
return queries.AddPlugin(context.Background(), model.AddPluginParams{
120+
return path, queries.AddPlugin(context.Background(), model.AddPluginParams{
119121
Name: plugin,
120122
Registry: registry,
121123
Description: sql.NullString{
@@ -159,3 +161,17 @@ func downloadZipToPath(url string, path string, checksum string) error {
159161
}
160162
return client.Get()
161163
}
164+
165+
const letters = "abcdefghijklmnopqrstuvwxyz1234567890"
166+
167+
// Create a small ID that can be used in the path of a plugin
168+
// It is 6 characters long and contains only letters and numbers
169+
// It is not meant to be unique, but it is unlikely to collide
170+
// We don't use uppercase letter due to the case-insensitive nature of some filesystems
171+
func newSmallID() string {
172+
str := strings.Builder{}
173+
for i := 0; i < 6; i++ {
174+
str.WriteByte(letters[rand.IntN(len(letters))])
175+
}
176+
return str.String()
177+
}

controller/config/registry/registry.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ func UpdateRegistry(queries *model.Queries, name string) error {
136136

137137
// Check if registry has changed
138138
if checksum == registry.Checksumregistry {
139-
return nil
139+
// Set the last updated time
140+
return queries.UpdateRegistryFetched(ctx, name)
140141
}
141142

142143
// Update registry

0 commit comments

Comments
 (0)