diff --git a/internal/config/config.go b/internal/config/config.go index b36c319e7..f377a8374 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -2,6 +2,9 @@ package config import ( "fmt" + "io/ioutil" + "os" + "path/filepath" "runtime" "strings" "sync" @@ -33,6 +36,7 @@ type Config struct { settings *Settings hub *hub.Config token string + serial string } func init() { @@ -95,6 +99,10 @@ func (c *Config) Init() error { return err } + if err := c.initStorage(); err != nil { + return err + } + c.initSettings() c.initHub() @@ -103,6 +111,28 @@ func (c *Config) Init() error { return c.connectDb() } +// initStorage initializes storage directories with a random serial. +func (c *Config) initStorage() error { + const serialName = "serial" + + c.serial = rnd.PPID('z') + + storageName := filepath.Join(c.StoragePath(), serialName) + backupName := filepath.Join(c.BackupPath(), serialName) + + if data, err := ioutil.ReadFile(storageName); err == nil { + c.serial = string(data) + } else if data, err := ioutil.ReadFile(backupName); err == nil { + c.serial = string(data) + } else if err := ioutil.WriteFile(storageName, []byte(c.serial), os.ModePerm); err != nil { + return fmt.Errorf("failed creating %s: %s", storageName, err) + } else if err := ioutil.WriteFile(backupName, []byte(c.serial), os.ModePerm); err != nil { + return fmt.Errorf("failed creating %s: %s", backupName, err) + } + + return nil +} + // Name returns the application name ("PhotoPrism"). func (c *Config) Name() string { return c.params.Name @@ -294,7 +324,7 @@ func (c *Config) UpdateHub() { // initHub initializes PhotoPrism hub config. func (c *Config) initHub() { - c.hub = hub.NewConfig(c.Version(), c.HubConfigFile()) + c.hub = hub.NewConfig(c.Version(), c.HubConfigFile(), c.serial) if err := c.hub.Load(); err == nil { // Do nothing. diff --git a/internal/hub/config.go b/internal/hub/config.go index e5235c705..bc9199f17 100644 --- a/internal/hub/config.go +++ b/internal/hub/config.go @@ -30,17 +30,19 @@ type Config struct { Session string `json:"session" yaml:"session"` Status string `json:"status" yaml:"status"` Version string `json:"version" yaml:"version"` + Serial string `json:"serial" yaml:"serial"` FileName string `json:"-" yaml:"-"` } // NewConfig creates a new backend api credentials instance. -func NewConfig(version string, fileName string) *Config { +func NewConfig(version, fileName, serial string) *Config { return &Config{ Key: "", Secret: "", Session: "", Status: "", Version: version, + Serial: serial, FileName: fileName, } } @@ -140,7 +142,7 @@ func (c *Config) Refresh() (err error) { log.Debugf("requesting api key for maps & places from %s", ApiHost()) } - if j, err := json.Marshal(NewRequest(c.Version)); err != nil { + if j, err := json.Marshal(NewRequest(c.Version, c.Serial)); err != nil { return err } else if req, err = http.NewRequest(method, url, bytes.NewReader(j)); err != nil { return err diff --git a/internal/hub/config_test.go b/internal/hub/config_test.go index 7afb40d9e..4269be6f3 100644 --- a/internal/hub/config_test.go +++ b/internal/hub/config_test.go @@ -8,7 +8,7 @@ import ( func TestConfig_MapKey(t *testing.T) { t.Run("success", func(t *testing.T) { - c := NewConfig("0.0.0", "testdata/new.yml") + c := NewConfig("0.0.0", "testdata/new.yml", "zqkunt22r0bewti9") assert.Equal(t, "", c.MapKey()) }) } diff --git a/internal/hub/feedback.go b/internal/hub/feedback.go index 6415f678f..8db0787de 100644 --- a/internal/hub/feedback.go +++ b/internal/hub/feedback.go @@ -23,15 +23,17 @@ type Feedback struct { UserAgent string `json:"UserAgent"` ApiKey string `json:"ApiKey"` ClientVersion string `json:"ClientVersion"` + ClientSerial string `json:"ClientSerial"` ClientOS string `json:"ClientOS"` ClientArch string `json:"ClientArch"` ClientCPU int `json:"ClientCPU"` } // NewFeedback creates a new hub feedback instance. -func NewFeedback(version string) *Feedback { +func NewFeedback(version, serial string) *Feedback { return &Feedback{ ClientVersion: version, + ClientSerial: serial, ClientOS: runtime.GOOS, ClientArch: runtime.GOARCH, ClientCPU: runtime.NumCPU(), @@ -39,7 +41,7 @@ func NewFeedback(version string) *Feedback { } func (c *Config) SendFeedback(f form.Feedback) (err error) { - feedback := NewFeedback(c.Version) + feedback := NewFeedback(c.Version, c.Serial) feedback.Category = f.Category feedback.Subject = txt.TrimLen(f.Message, 50) feedback.Message = f.Message diff --git a/internal/hub/feedback_test.go b/internal/hub/feedback_test.go index 325c0ba12..6451dd63e 100644 --- a/internal/hub/feedback_test.go +++ b/internal/hub/feedback_test.go @@ -9,14 +9,15 @@ import ( func TestNewFeedback(t *testing.T) { t.Run("success", func(t *testing.T) { - feedback := NewFeedback("xxx") + feedback := NewFeedback("xxx", "zqkunt22r0bewti9") assert.Equal(t, "xxx", feedback.ClientVersion) + assert.Equal(t, "zqkunt22r0bewti9", feedback.ClientSerial) }) } func TestSendFeedback(t *testing.T) { t.Run("success", func(t *testing.T) { - c := NewConfig("0.0.0", "testdata/new.yml") + c := NewConfig("0.0.0", "testdata/new.yml", "zqkunt22r0bewti9") feedback := Feedback{ Category: "Bug Report", diff --git a/internal/hub/hub_test.go b/internal/hub/hub_test.go index c9602eab6..24e9a09f4 100644 --- a/internal/hub/hub_test.go +++ b/internal/hub/hub_test.go @@ -49,13 +49,13 @@ func Token(size uint) string { } func TestNewConfig(t *testing.T) { - c := NewConfig("0.0.0", "testdata/new.yml") + c := NewConfig("0.0.0", "testdata/new.yml", "zqkunt22r0bewti9") assert.IsType(t, &Config{}, c) } func TestNewRequest(t *testing.T) { - r := NewRequest("0.0.0") + r := NewRequest("0.0.0", "zqkunt22r0bewti9") assert.IsType(t, &Request{}, r) @@ -72,7 +72,7 @@ func TestConfig_Refresh(t *testing.T) { t.Run("success", func(t *testing.T) { fileName := fmt.Sprintf("testdata/hub.%s.yml", Token(8)) - c := NewConfig("0.0.0", fileName) + c := NewConfig("0.0.0", fileName, "zqkunt22r0bewti9") if err := c.Refresh(); err != nil { t.Fatal(err) @@ -122,7 +122,7 @@ func TestConfig_Refresh(t *testing.T) { func TestConfig_DecodeSession(t *testing.T) { t.Run("hub3.yml", func(t *testing.T) { - c := NewConfig("0.0.0", "testdata/hub3.yml") + c := NewConfig("0.0.0", "testdata/hub3.yml", "zqkunt22r0bewti9") err := c.Load() @@ -138,7 +138,7 @@ func TestConfig_DecodeSession(t *testing.T) { func TestConfig_Load(t *testing.T) { t.Run("hub1.yml", func(t *testing.T) { - c := NewConfig("0.0.0", "testdata/hub1.yml") + c := NewConfig("0.0.0", "testdata/hub1.yml", "zqkunt22r0bewti9") if err := c.Load(); err != nil { t.Logf(err.Error()) @@ -151,7 +151,7 @@ func TestConfig_Load(t *testing.T) { assert.Equal(t, "0.0.0", c.Version) }) t.Run("hub2.yml", func(t *testing.T) { - c := NewConfig("0.0.0", "testdata/hub2.yml") + c := NewConfig("0.0.0", "testdata/hub2.yml", "zqkunt22r0bewti9") if err := c.Load(); err != nil { t.Logf(err.Error()) @@ -164,7 +164,7 @@ func TestConfig_Load(t *testing.T) { assert.Equal(t, "200925-f8e2b580-Darwin-i386-DEBUG", c.Version) }) t.Run("not existing filename", func(t *testing.T) { - c := NewConfig("0.0.0", "testdata/hub_xxx.yml") + c := NewConfig("0.0.0", "testdata/hub_xxx.yml", "zqkunt22r0bewti9") if err := c.Load(); err == nil { t.Fatal("file should not exist") @@ -180,7 +180,7 @@ func TestConfig_Save(t *testing.T) { t.Run("existing filename", func(t *testing.T) { assert.FileExists(t, "testdata/hub1.yml") - c := NewConfig("0.0.0", "testdata/hub1.yml") + c := NewConfig("0.0.0", "testdata/hub1.yml", "zqkunt22r0bewti9") if err := c.Load(); err != nil { t.Logf(err.Error()) @@ -219,7 +219,7 @@ func TestConfig_Save(t *testing.T) { assert.Equal(t, "0.0.0", c.Version) }) t.Run("not existing filename", func(t *testing.T) { - c := NewConfig("0.0.0", "testdata/hub_new.yml") + c := NewConfig("0.0.0", "testdata/hub_new.yml", "zqkunt22r0bewti9") c.Key = "F60F5B25D59C397989E3CD374F81CDD7710A4FCA" c.Secret = "foo" c.Session = "bar" diff --git a/internal/hub/request.go b/internal/hub/request.go index 046454c0f..b1c690d63 100644 --- a/internal/hub/request.go +++ b/internal/hub/request.go @@ -10,15 +10,17 @@ var ServiceURL = "https://hub.photoprism.app/v1/hello" // Backend api credentials request incl basic runtime specs. type Request struct { ClientVersion string `json:"ClientVersion"` + ClientSerial string `json:"ClientSerial"` ClientOS string `json:"ClientOS"` ClientArch string `json:"ClientArch"` ClientCPU int `json:"ClientCPU"` } // NewRequest creates a new hub key request instance. -func NewRequest(version string) *Request { +func NewRequest(version, serial string) *Request { return &Request{ ClientVersion: version, + ClientSerial: serial, ClientOS: runtime.GOOS, ClientArch: runtime.GOARCH, ClientCPU: runtime.NumCPU(),