From 84d1e7de1d9c9dedcd231182eebf30bf348d543d Mon Sep 17 00:00:00 2001 From: Michael Mayer Date: Fri, 24 Mar 2023 19:35:29 +0100 Subject: [PATCH] CLI: Add photoprism connect command Signed-off-by: Michael Mayer --- cmd/photoprism/photoprism.go | 2 +- internal/commands/commands.go | 1 + internal/commands/connect.go | 36 ++++++++++++++++++++++++++++++++ internal/config/client_config.go | 8 +++---- internal/config/config.go | 22 +++++++++++++------ internal/config/config_test.go | 2 +- internal/hub/config.go | 23 ++++++++++++++------ internal/hub/config_test.go | 12 +++++------ internal/hub/hub_test.go | 12 +++++------ 9 files changed, 88 insertions(+), 30 deletions(-) create mode 100644 internal/commands/connect.go diff --git a/cmd/photoprism/photoprism.go b/cmd/photoprism/photoprism.go index fa7197aeb..cf4ebf6a7 100644 --- a/cmd/photoprism/photoprism.go +++ b/cmd/photoprism/photoprism.go @@ -36,7 +36,7 @@ var version = "development" var log = event.Log const appName = "PhotoPrism" -const appAbout = "PhotoPrism® CE" +const appAbout = "PhotoPrism®" const appEdition = "ce" const appDescription = "PhotoPrism® is an AI-Powered Photos App for the Decentralized Web." + " It makes use of the latest technologies to tag and find pictures automatically without getting in your way." + diff --git a/internal/commands/commands.go b/internal/commands/commands.go index b8dad1934..f047049d2 100644 --- a/internal/commands/commands.go +++ b/internal/commands/commands.go @@ -65,6 +65,7 @@ var PhotoPrism = []cli.Command{ ShowCommand, VersionCommand, ShowConfigCommand, + ConnectCommand, } // childAlreadyRunning tests if a .pid file at filePath is a running process. diff --git a/internal/commands/connect.go b/internal/commands/connect.go new file mode 100644 index 000000000..63f481d1d --- /dev/null +++ b/internal/commands/connect.go @@ -0,0 +1,36 @@ +package commands + +import ( + "github.com/urfave/cli" + + "github.com/photoprism/photoprism/internal/config" +) + +// ConnectCommand configures the command name, flags, and action. +var ConnectCommand = cli.Command{ + Name: "connect", + Usage: "Connects your membership account", + ArgsUsage: "[activation code]", + Action: connectAction, +} + +// connectAction connects your membership account. +func connectAction(ctx *cli.Context) error { + return CallWithDependencies(ctx, func(conf *config.Config) error { + token := ctx.Args().First() + + // Fail if no code was provided. + if token == "" { + return cli.ShowSubcommandHelp(ctx) + } + + // Connect to hub. + if err := conf.ResyncHub(token); err != nil { + return err + } + + log.Infof("successfully connected your account") + + return nil + }) +} diff --git a/internal/config/client_config.go b/internal/config/client_config.go index c6180af86..1daf64e74 100644 --- a/internal/config/client_config.go +++ b/internal/config/client_config.go @@ -73,7 +73,7 @@ type ClientConfig struct { Countries entity.Countries `json:"countries"` People entity.People `json:"people"` Thumbs ThumbSizes `json:"thumbs"` - License string `json:"license"` + Membership string `json:"membership"` Customer string `json:"customer"` MapKey string `json:"mapKey"` DownloadToken string `json:"downloadToken,omitempty"` @@ -289,7 +289,7 @@ func (c *Config) ClientPublic() ClientConfig { Lenses: entity.Lenses{}, Countries: entity.Countries{}, People: entity.People{}, - License: c.Hub().Status, + Membership: c.Hub().Membership(), Customer: "", MapKey: "", Thumbs: Thumbs, @@ -378,7 +378,7 @@ func (c *Config) ClientShare() ClientConfig { People: entity.People{}, Colors: colors.All.List(), Thumbs: Thumbs, - License: c.Hub().Status, + Membership: c.Hub().Membership(), Customer: c.Hub().Customer(), MapKey: c.Hub().MapKey(), DownloadToken: c.DownloadToken(), @@ -471,7 +471,7 @@ func (c *Config) ClientUser(withSettings bool) ClientConfig { People: entity.People{}, Colors: colors.All.List(), Thumbs: Thumbs, - License: c.Hub().Status, + Membership: c.Hub().Membership(), Customer: c.Hub().Customer(), MapKey: c.Hub().MapKey(), DownloadToken: c.DownloadToken(), diff --git a/internal/config/config.go b/internal/config/config.go index 220c658f1..6247fe37a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -350,9 +350,7 @@ func (c *Config) Name() string { // About returns the app about string. func (c *Config) About() string { if c.options.About == "" { - return "PhotoPrism® Dev" - } else if strings.HasSuffix(c.options.About, "CE") && c.Sponsor() { - return strings.Replace(c.options.About, "CE", "Plus", 1) + return "PhotoPrism®" } return c.options.About @@ -563,7 +561,7 @@ func (c *Config) Sponsor() bool { if Sponsor || c.options.Sponsor { return true } else if c.hub != nil { - Sponsor = c.Hub().Plus() + Sponsor = c.Hub().Sponsor() } return Sponsor @@ -757,13 +755,25 @@ func (c *Config) ResolutionLimit() int { return result } -// UpdateHub renews backend api credentials for maps and places without a token. +// UpdateHub renews backend api credentials with an optional activation code. func (c *Config) UpdateHub() { - _ = c.ResyncHub("") + if c.hub == nil { + return + } + + if token := os.Getenv(EnvVar("CONNECT")); token != "" && !c.Hub().Sponsor() { + _ = c.ResyncHub(token) + } else { + _ = c.ResyncHub("") + } } // ResyncHub renews backend api credentials for maps and places with an optional token. func (c *Config) ResyncHub(token string) error { + if c.hub == nil { + return fmt.Errorf("hub is not initialized") + } + if err := c.hub.ReSync(token); err != nil { log.Debugf("config: %s, see https://docs.photoprism.app/getting-started/troubleshooting/firewall/", err) if token != "" { diff --git a/internal/config/config_test.go b/internal/config/config_test.go index b97b1f480..239f0c567 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -73,7 +73,7 @@ func TestConfig_About(t *testing.T) { c := NewConfig(CliTestContext()) name := c.About() - assert.Equal(t, "PhotoPrism® Dev", name) + assert.Equal(t, "PhotoPrism®", name) } func TestConfig_Edition(t *testing.T) { diff --git a/internal/hub/config.go b/internal/hub/config.go index a12a324d9..510db2157 100644 --- a/internal/hub/config.go +++ b/internal/hub/config.go @@ -24,10 +24,12 @@ import ( "github.com/photoprism/photoprism/pkg/fs" ) +type Status string + const ( - StatusUnknown = "" - StatusNew = "unregistered" - StatusCommunity = "ce" + StatusUnknown Status = "" + StatusNew Status = "unregistered" + StatusCommunity Status = "ce" ) // Config represents backend api credentials for maps & geodata. @@ -39,7 +41,7 @@ type Config struct { Session string `json:"session" yaml:"Session"` session *Session `json:"-" yaml:"-"` sessionMu sync.Mutex `json:"-" yaml:"-"` - Status string `json:"status" yaml:"Status"` + Status Status `json:"status" yaml:"Status"` Serial string `json:"serial" yaml:"Serial"` Env string `json:"-" yaml:"-"` UserAgent string `json:"-" yaml:"-"` @@ -71,6 +73,15 @@ func (c *Config) MapKey() string { } } +// Membership returns the membership level as string. +func (c *Config) Membership() string { + if !c.Sponsor() { + return string(StatusCommunity) + } + + return string(c.Status) +} + // Customer returns the customer name. func (c *Config) Customer() string { if sess, err := c.DecodeSession(true); err != nil { @@ -86,8 +97,8 @@ func (c *Config) Propagate() { places.Secret = c.Secret } -// Plus reports if you have a community membership. -func (c *Config) Plus() bool { +// Sponsor reports if you support the project. +func (c *Config) Sponsor() bool { switch c.Status { case StatusUnknown, StatusNew, StatusCommunity: return false diff --git a/internal/hub/config_test.go b/internal/hub/config_test.go index 6b519a088..ac914c8f1 100644 --- a/internal/hub/config_test.go +++ b/internal/hub/config_test.go @@ -13,18 +13,18 @@ func TestConfig_MapKey(t *testing.T) { }) } -func TestConfig_Plus(t *testing.T) { +func TestConfig_Sponsor(t *testing.T) { t.Run("Status", func(t *testing.T) { c := NewConfig("0.0.0", "testdata/new.yml", "zr58wrg19i8jfjam", "test", "PhotoPrism/Test", "test") c.Key = "0e159b773c6fb779c3bf6c8ba6e322abf559dbaf" c.Secret = "23f0024975bd65ade06edcc8191f7fcc" - assert.False(t, c.Plus()) - c.Status = "plus" - assert.False(t, c.Plus()) + assert.False(t, c.Sponsor()) + c.Status = "sponsor" + assert.False(t, c.Sponsor()) c.Session = "bde6d0cf514e5456591de5ae09d981056eb88dccf71ba268974bf2cc7b028545e7641c1dfbaa716e4d13f8b0e0d1863e64c16e1f0ac551fc85e1171a87cbda6608cbe330de9e0d5f5b0e14ff61f2ff08fee369" - assert.True(t, c.Plus()) + assert.True(t, c.Sponsor()) c.Status = "" - assert.False(t, c.Plus()) + assert.False(t, c.Sponsor()) c.Session = "" }) } diff --git a/internal/hub/hub_test.go b/internal/hub/hub_test.go index 84dacb954..b68e95d08 100644 --- a/internal/hub/hub_test.go +++ b/internal/hub/hub_test.go @@ -133,7 +133,7 @@ func TestConfig_DecodeSession(t *testing.T) { assert.Equal(t, "8dd8b115d052f91ac74b1c2475e305009366c487", c.Key) assert.Equal(t, "ddf4ce46afbf6c16a6bd8555ab1e4efb", c.Secret) assert.Equal(t, "7607796238c26b2d95007957b05c72d63f504346576bc2aa064a6dc54344de47d2ab38422bd1d061c067a16ef517e6054d8b7f5336c120431935518277fed45e49472aaf740cac1bc33ab2e362c767007a59e953e9973709", c.Session) - assert.Equal(t, "unregistered", c.Status) + assert.Equal(t, Status("unregistered"), c.Status) assert.Equal(t, "0.0.0", c.Version) }) } @@ -149,7 +149,7 @@ func TestConfig_Load(t *testing.T) { assert.Equal(t, "b32e9ccdc90eb7c0f6f1b9fbc82b8a2b0e993304", c.Key) assert.Equal(t, "5991ea36a9611e9e00a8360c10b91567", c.Secret) assert.Equal(t, "3ef5685c6391a568731c8fc94ccad82d92dea60476c8b672990047c822248f45366fc0e8e812ad15e0b5ae1eb20e866235c56b", c.Session) - assert.Equal(t, "unregistered", c.Status) + assert.Equal(t, Status("unregistered"), c.Status) assert.Equal(t, "0.0.0", c.Version) }) t.Run("hub2.yml", func(t *testing.T) { @@ -162,7 +162,7 @@ func TestConfig_Load(t *testing.T) { assert.Equal(t, "ab66cb5cfb3658dbea0a1433df048d900934ac68", c.Key) assert.Equal(t, "6b0f8440fe307d3120b3a4366350094b", c.Secret) assert.Equal(t, "c0ca88fc3094b70a1947b5b10f980a420cd6b1542a20f6f26ecc6a16f340473b9fb16b80be1078e86d886b3a8d46bf8184d147", c.Session) - assert.Equal(t, "unregistered", c.Status) + assert.Equal(t, Status("unregistered"), c.Status) assert.Equal(t, "200925-f8e2b580-Darwin-i386-DEBUG", c.Version) }) t.Run("not existing filename", func(t *testing.T) { @@ -191,7 +191,7 @@ func TestConfig_Save(t *testing.T) { assert.Equal(t, "b32e9ccdc90eb7c0f6f1b9fbc82b8a2b0e993304", c.Key) assert.Equal(t, "5991ea36a9611e9e00a8360c10b91567", c.Secret) assert.Equal(t, "3ef5685c6391a568731c8fc94ccad82d92dea60476c8b672990047c822248f45366fc0e8e812ad15e0b5ae1eb20e866235c56b", c.Session) - assert.Equal(t, "unregistered", c.Status) + assert.Equal(t, Status("unregistered"), c.Status) assert.Equal(t, "0.0.0", c.Version) c.FileName = "testdata/hub-save.yml" @@ -205,7 +205,7 @@ func TestConfig_Save(t *testing.T) { assert.Equal(t, "b32e9ccdc90eb7c0f6f1b9fbc82b8a2b0e993304", c.Key) assert.Equal(t, "5991ea36a9611e9e00a8360c10b91567", c.Secret) assert.Equal(t, "3ef5685c6391a568731c8fc94ccad82d92dea60476c8b672990047c822248f45366fc0e8e812ad15e0b5ae1eb20e866235c56b", c.Session) - assert.Equal(t, "unregistered", c.Status) + assert.Equal(t, Status("unregistered"), c.Status) assert.Equal(t, "0.0.0", c.Version) assert.FileExists(t, "testdata/hub-save.yml") @@ -217,7 +217,7 @@ func TestConfig_Save(t *testing.T) { assert.Equal(t, "b32e9ccdc90eb7c0f6f1b9fbc82b8a2b0e993304", c.Key) assert.Equal(t, "5991ea36a9611e9e00a8360c10b91567", c.Secret) assert.Equal(t, "3ef5685c6391a568731c8fc94ccad82d92dea60476c8b672990047c822248f45366fc0e8e812ad15e0b5ae1eb20e866235c56b", c.Session) - assert.Equal(t, "unregistered", c.Status) + assert.Equal(t, Status("unregistered"), c.Status) assert.Equal(t, "0.0.0", c.Version) }) t.Run("not existing filename", func(t *testing.T) {