From 55693fab35fd929ec0d45e19676bcdb607067983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20d=27Yvoire?= <23535665+FrouxBY@users.noreply.github.com> Date: Fri, 21 Feb 2020 00:14:45 +0000 Subject: [PATCH] Some more comment improvements (#257) * Improve comment in classify package * improve comment in config package * improve entity package comments * grammar error in comments --- internal/classify/classify.go | 2 +- internal/classify/gen.go | 2 ++ internal/classify/label_rule.go | 3 +++ internal/classify/labels.go | 5 +++++ internal/classify/tensorflow.go | 11 +++++++++-- internal/config/auth.go | 1 + internal/config/client.go | 2 +- internal/config/config.go | 5 ++++- internal/config/errors.go | 1 + internal/config/flags.go | 2 +- internal/config/params.go | 4 +++- internal/config/settings.go | 2 ++ internal/config/test.go | 12 +++++++++++- internal/config/thumbnails.go | 2 ++ internal/entity/album.go | 5 ++++- internal/entity/camera.go | 3 +++ internal/entity/category.go | 3 ++- internal/entity/country.go | 10 +++++++++- internal/entity/entity.go | 2 +- internal/entity/event.go | 4 +++- internal/entity/file.go | 6 +++++- internal/entity/keyword.go | 4 +++- internal/entity/label.go | 7 ++++++- internal/entity/lens.go | 5 ++++- internal/entity/location.go | 25 ++++++++++++++++++++++--- internal/entity/photo.go | 22 +++++++++++++++++++++- internal/entity/photo_album.go | 5 ++++- internal/entity/photo_keyword.go | 4 ++++ internal/entity/photo_label.go | 12 ++++++++---- internal/entity/place.go | 17 ++++++++++++++++- 30 files changed, 161 insertions(+), 27 deletions(-) diff --git a/internal/classify/classify.go b/internal/classify/classify.go index 708583af3..7aaff6560 100644 --- a/internal/classify/classify.go +++ b/internal/classify/classify.go @@ -1,5 +1,5 @@ /* -This package encapsulates image classification using TensorFlow +Package classify encapsulates image classification functionnality using TensorFlow Additional information can be found in our Developer Guide: diff --git a/internal/classify/gen.go b/internal/classify/gen.go index 5a9eb16c7..c15cf5b87 100644 --- a/internal/classify/gen.go +++ b/internal/classify/gen.go @@ -15,6 +15,7 @@ import ( "gopkg.in/yaml.v2" ) +// LabelRule defines the rule for a given Label type LabelRule struct { Label string See string @@ -25,6 +26,7 @@ type LabelRule struct { type LabelRules map[string]LabelRule +// This function generates the rules.go file containing rule extracted from rules.yml file func main() { rules := make(LabelRules) diff --git a/internal/classify/label_rule.go b/internal/classify/label_rule.go index c0cf820a8..1386f8756 100644 --- a/internal/classify/label_rule.go +++ b/internal/classify/label_rule.go @@ -1,5 +1,6 @@ package classify +// LabelRule defines the rule for a given Label type LabelRule struct { Label string Threshold float32 @@ -7,8 +8,10 @@ type LabelRule struct { Priority int } +// LabelRules is a map of rules with label name as index type LabelRules map[string]LabelRule +// Find is a getter for LabelRules that give a default rule with a non-zero threshold for missing keys func (rules LabelRules) Find(label string) LabelRule { if rule, ok := rules[label]; ok { return rule diff --git a/internal/classify/labels.go b/internal/classify/labels.go index ddb38dffa..45b97b45d 100644 --- a/internal/classify/labels.go +++ b/internal/classify/labels.go @@ -9,6 +9,7 @@ import ( // Labels is list of MediaFile labels. type Labels []Label +// Implements functions for the Sort Interface. Default Labels sort is by priority and uncertainty func (l Labels) Len() int { return len(l) } func (l Labels) Swap(i, j int) { l[i], l[j] = l[j], l[i] } func (l Labels) Less(i, j int) bool { @@ -19,6 +20,7 @@ func (l Labels) Less(i, j int) bool { } } +// AppendLabel extends append func by not appending empty label func (l Labels) AppendLabel(label Label) Labels { if label.Name == "" { return l @@ -27,6 +29,7 @@ func (l Labels) AppendLabel(label Label) Labels { return append(l, label) } +// Keywords returns all keywords contains in Labels and theire categories func (l Labels) Keywords() (result []string) { for _, label := range l { result = append(result, txt.Keywords(label.Name)...) @@ -39,9 +42,11 @@ func (l Labels) Keywords() (result []string) { return result } +// Title gets the best label out a list of labels or fallback to compute a meaningfull default title. func (l Labels) Title(fallback string) string { fallbackRunes := len([]rune(fallback)) + // check if given fallback is valid if fallbackRunes < 2 || fallbackRunes > 25 || txt.ContainsNumber(fallback) { fallback = "" } diff --git a/internal/classify/tensorflow.go b/internal/classify/tensorflow.go index 8a2a3b7f9..94971afd6 100644 --- a/internal/classify/tensorflow.go +++ b/internal/classify/tensorflow.go @@ -18,7 +18,7 @@ import ( tf "github.com/tensorflow/tensorflow/tensorflow/go" ) -// TensorFlow if a wrapper for their low-level API. +// TensorFlow is a wrapper for tensorflow low-level API. type TensorFlow struct { conf *config.Config model *tf.SavedModel @@ -34,6 +34,7 @@ func New(modelsPath string, disabled bool) *TensorFlow { return &TensorFlow{modelsPath: modelsPath, disabled: disabled, modelName: "nasnet", modelTags: []string{"photoprism"}} } +// Init initialises tensorflow models if not disabled func (t *TensorFlow) Init() (err error) { if t.disabled { return nil @@ -154,15 +155,17 @@ func (t *TensorFlow) loadModel() error { return t.loadLabels(modelPath) } +// bestLabels returns the best 5 labels (if enough high probability labels) from the prediction of the model func (t *TensorFlow) bestLabels(probabilities []float32) Labels { - // Make a list of label/probability pairs var result Labels for i, p := range probabilities { if i >= len(t.labels) { + // break if probabilities and labels does not match break } + // discard labels with low probabilities if p < 0.1 { continue } @@ -171,10 +174,12 @@ func (t *TensorFlow) bestLabels(probabilities []float32) Labels { rule := rules.Find(labelText) + // discard labels that don't met the threshold if p < rule.Threshold { continue } + // Get rule label name instead of t.labels name if it exists if rule.Label != "" { labelText = rule.Label } @@ -189,6 +194,7 @@ func (t *TensorFlow) bestLabels(probabilities []float32) Labels { // Sort by probability sort.Sort(result) + // return only the 5 best labels if l := len(result); l < 5 { return result[:l] } else { @@ -196,6 +202,7 @@ func (t *TensorFlow) bestLabels(probabilities []float32) Labels { } } +// makeTensor converts bytes jpeg image in a tensor object required as tensorflow model input func (t *TensorFlow) makeTensor(image []byte, imageFormat string) (*tf.Tensor, error) { img, err := imaging.Decode(bytes.NewReader(image), imaging.AutoOrientation(true)) diff --git a/internal/config/auth.go b/internal/config/auth.go index 5008709b7..c5f8b2370 100644 --- a/internal/config/auth.go +++ b/internal/config/auth.go @@ -14,6 +14,7 @@ func isBcrypt(s string) bool { return b } +// CheckPassword compares given password p with the admin password func (c *Config) CheckPassword(p string) bool { ap := c.AdminPassword() diff --git a/internal/config/client.go b/internal/config/client.go index abc7db513..bf561bc8c 100644 --- a/internal/config/client.go +++ b/internal/config/client.go @@ -9,7 +9,7 @@ import ( "github.com/photoprism/photoprism/pkg/fs" ) -// HTTP client / Web UI config values +// ClientConfig contains HTTP client / Web UI config values type ClientConfig map[string]interface{} // PublicClientConfig returns reduced config values for non-public sites. diff --git a/internal/config/config.go b/internal/config/config.go index b27003293..ca65f907c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -20,6 +20,7 @@ import ( var log = event.Log +// Config holds database, cache and all parameters of photoprism type Config struct { db *gorm.DB cache *gc.Cache @@ -27,6 +28,7 @@ type Config struct { } func init() { + // initialize the Thumbnails global variable for name, t := range thumb.Types { if t.Public { thumbnail := Thumbnail{Name: name, Width: t.Width, Height: t.Height} @@ -52,6 +54,7 @@ func initLogger(debug bool) { }) } +// NewConfig initialises a new configuration file func NewConfig(ctx *cli.Context) *Config { initLogger(ctx.GlobalBool("debug")) @@ -107,7 +110,7 @@ func (c *Config) Author() string { return c.config.Author } -// Description returns the twitter handle for sharing. +// Twitter returns the twitter handle for sharing. func (c *Config) Twitter() string { return c.config.Twitter } diff --git a/internal/config/errors.go b/internal/config/errors.go index 207796bd0..ab623d392 100644 --- a/internal/config/errors.go +++ b/internal/config/errors.go @@ -4,6 +4,7 @@ import ( "errors" ) +// Define photoprism specific errors var ( ErrReadOnly = errors.New("not available in read-only mode") ErrUnauthorized = errors.New("please log in and try again") diff --git a/internal/config/flags.go b/internal/config/flags.go index 99913ffab..0751065a9 100644 --- a/internal/config/flags.go +++ b/internal/config/flags.go @@ -4,7 +4,7 @@ import ( "github.com/urfave/cli" ) -// Global CLI flags +// GlobalFlags lists all CLI flags var GlobalFlags = []cli.Flag{ cli.BoolFlag{ Name: "debug", diff --git a/internal/config/params.go b/internal/config/params.go index ce1ec99ce..5d77bb933 100644 --- a/internal/config/params.go +++ b/internal/config/params.go @@ -13,6 +13,7 @@ import ( "gopkg.in/yaml.v2" ) +// define database drivers const const ( DbTiDB = "internal" DbMySQL = "mysql" @@ -78,7 +79,7 @@ type Params struct { ThumbFilter string `yaml:"thumb-filter" flag:"thumb-filter"` } -// NewParams() creates a new configuration entity by using two methods: +// NewParams creates a new configuration entity by using two methods: // // 1. SetValuesFromFile: This will initialize values from a yaml config file. // @@ -103,6 +104,7 @@ func NewParams(ctx *cli.Context) *Params { return c } +// expandFilenames converts path in config to absolute path func (c *Params) expandFilenames() { c.ConfigPath = fs.Abs(c.ConfigPath) c.ResourcesPath = fs.Abs(c.ResourcesPath) diff --git a/internal/config/settings.go b/internal/config/settings.go index ee952ace5..66087ec40 100644 --- a/internal/config/settings.go +++ b/internal/config/settings.go @@ -9,11 +9,13 @@ import ( "gopkg.in/yaml.v2" ) +// Settings contains Web UI settings type Settings struct { Theme string `json:"theme" yaml:"theme" flag:"theme"` Language string `json:"language" yaml:"language" flag:"language"` } +// NewSettings returns a empty Settings func NewSettings() *Settings { return &Settings{} } diff --git a/internal/config/test.go b/internal/config/test.go index b2865914d..41e7a6f18 100644 --- a/internal/config/test.go +++ b/internal/config/test.go @@ -16,6 +16,7 @@ import ( "github.com/urfave/cli" ) +// define constants used for testing the config package const ( TestDataZip = "/tmp/photoprism/testdata.zip" TestDataURL = "https://dl.photoprism.org/fixtures/testdata.zip" @@ -29,6 +30,7 @@ func testDataPath(assetsPath string) string { return assetsPath + "/testdata" } +// NewTestParams inits valid params used for testing func NewTestParams() *Params { assetsPath := fs.Abs("../../assets") @@ -52,6 +54,7 @@ func NewTestParams() *Params { return c } +// NewTestParamsError inits invalid params used for testing func NewTestParamsError() *Params { assetsPath := fs.Abs("../..") @@ -71,6 +74,7 @@ func NewTestParamsError() *Params { return c } +// TestConfig inits the global testConfig if it was not already initialised func TestConfig() *Config { once.Do(func() { testConfig = NewTestConfig() @@ -79,6 +83,7 @@ func TestConfig() *Config { return testConfig } +// NewTestConfig inits valid config used for testing func NewTestConfig() *Config { log.SetLevel(logrus.DebugLevel) @@ -102,6 +107,7 @@ func NewTestConfig() *Config { return c } +// NewTestErrorConfig inits invalid config used for testing func NewTestErrorConfig() *Config { log.SetLevel(logrus.DebugLevel) @@ -115,7 +121,7 @@ func NewTestErrorConfig() *Config { return c } -// Returns example cli config for testing +// CliTestContext returns example cli config for testing func CliTestContext() *cli.Context { config := NewTestParams() @@ -146,6 +152,7 @@ func CliTestContext() *cli.Context { return c } +// RemoveTestData deletes files in import, export, originals and cache folders func (c *Config) RemoveTestData(t *testing.T) { os.RemoveAll(c.ImportPath()) os.RemoveAll(c.ExportPath()) @@ -153,6 +160,7 @@ func (c *Config) RemoveTestData(t *testing.T) { os.RemoveAll(c.CachePath()) } +// DownloadTestData downloads test data from photoprism.org server func (c *Config) DownloadTestData(t *testing.T) { if fs.FileExists(TestDataZip) { hash := fs.Hash(TestDataZip) @@ -172,12 +180,14 @@ func (c *Config) DownloadTestData(t *testing.T) { } } +// UnzipTestData in default test folder func (c *Config) UnzipTestData(t *testing.T) { if _, err := fs.Unzip(TestDataZip, testDataPath(c.AssetsPath())); err != nil { t.Logf("could not unzip test data: %s\n", err.Error()) } } +// InitializeTestData using testing constant func (c *Config) InitializeTestData(t *testing.T) { t.Log("initializing test data") diff --git a/internal/config/thumbnails.go b/internal/config/thumbnails.go index c903b394b..a5eaae8ea 100644 --- a/internal/config/thumbnails.go +++ b/internal/config/thumbnails.go @@ -1,9 +1,11 @@ package config +// Thumbnail gives direct access to width and height for a thumbnail setting type Thumbnail struct { Name string Width int Height int } +// Thumbnails is a list of default thumbnail size available for the app var Thumbnails []Thumbnail diff --git a/internal/entity/album.go b/internal/entity/album.go index 9f237eac5..2e0fda4d9 100644 --- a/internal/entity/album.go +++ b/internal/entity/album.go @@ -10,7 +10,7 @@ import ( "github.com/photoprism/photoprism/pkg/rnd" ) -// Photo album +// Album represents a photo album type Album struct { ID uint `gorm:"primary_key"` CoverUUID string `gorm:"type:varbinary(36);"` @@ -29,6 +29,7 @@ type Album struct { DeletedAt *time.Time `sql:"index"` } +// BeforeCreate computes a random UUID when a new album is created in database func (m *Album) BeforeCreate(scope *gorm.Scope) error { if err := scope.SetColumn("AlbumUUID", rnd.PPID('a')); err != nil { return err @@ -37,6 +38,7 @@ func (m *Album) BeforeCreate(scope *gorm.Scope) error { return nil } +// NewAlbum creates a new album; default name is current month and year func NewAlbum(albumName string) *Album { albumName = strings.TrimSpace(albumName) @@ -54,6 +56,7 @@ func NewAlbum(albumName string) *Album { return result } +// Rename an existing album func (m *Album) Rename(albumName string) { if albumName == "" { albumName = m.CreatedAt.Format("January 2006") diff --git a/internal/entity/camera.go b/internal/entity/camera.go index 00fb90dc8..08af3bae6 100644 --- a/internal/entity/camera.go +++ b/internal/entity/camera.go @@ -24,6 +24,7 @@ type Camera struct { DeletedAt *time.Time `sql:"index"` } +// NewCamera creates a camera entity from a model name and a make name. func NewCamera(modelName string, makeName string) *Camera { makeName = strings.TrimSpace(makeName) @@ -50,6 +51,7 @@ func NewCamera(modelName string, makeName string) *Camera { return result } +// FirstOrCreate checks wether the camera model exist already in the database func (m *Camera) FirstOrCreate(db *gorm.DB) *Camera { mutex.Db.Lock() defer mutex.Db.Unlock() @@ -61,6 +63,7 @@ func (m *Camera) FirstOrCreate(db *gorm.DB) *Camera { return m } +// String returns a string designing the given Camera entity func (m *Camera) String() string { if m.CameraMake != "" && m.CameraModel != "" { return fmt.Sprintf("%s %s", m.CameraMake, m.CameraModel) diff --git a/internal/entity/category.go b/internal/entity/category.go index 9338f2b83..6edfed1a2 100644 --- a/internal/entity/category.go +++ b/internal/entity/category.go @@ -1,6 +1,6 @@ package entity -// Labels can have zero or more categories with the same or a similar meaning +// Category of labels regroups labels with the same or a similar meaning using a main/root label type Category struct { LabelID uint `gorm:"primary_key;auto_increment:false"` CategoryID uint `gorm:"primary_key;auto_increment:false"` @@ -8,6 +8,7 @@ type Category struct { Category *Label } +// TableName returns Category table identifier "categories" func (Category) TableName() string { return "categories" } diff --git a/internal/entity/country.go b/internal/entity/country.go index 1bc192c90..221d8175a 100644 --- a/internal/entity/country.go +++ b/internal/entity/country.go @@ -7,12 +7,14 @@ import ( "github.com/photoprism/photoprism/internal/mutex" ) +// altCountryNames defines mapping between different names for the same countriy var altCountryNames = map[string]string{ "United States of America": "USA", "United States": "USA", "": "Unknown", } +// Country represents a country location, used for labeling photos. type Country struct { ID string `gorm:"primary_key"` CountrySlug string `gorm:"type:varbinary(128);unique_index;"` @@ -24,13 +26,15 @@ type Country struct { New bool `gorm:"-"` } +// UnknownCountry is the default country var UnknownCountry = NewCountry("zz", maps.CountryNames["zz"]) +// CreateUnknownCountry is used to initialize the database with the default country func CreateUnknownCountry(db *gorm.DB) { UnknownCountry.FirstOrCreate(db) } -// Create a new country +// NewCountry creates a new country, with default country code if not provided func NewCountry(countryCode string, countryName string) *Country { if countryCode == "" { countryCode = "zz" @@ -51,6 +55,7 @@ func NewCountry(countryCode string, countryName string) *Country { return result } +// FirstOrCreate checks wether the country exist already in the database (using countryCode) func (m *Country) FirstOrCreate(db *gorm.DB) *Country { mutex.Db.Lock() defer mutex.Db.Unlock() @@ -62,14 +67,17 @@ func (m *Country) FirstOrCreate(db *gorm.DB) *Country { return m } +// AfterCreate sets the New column used for database callback func (m *Country) AfterCreate(scope *gorm.Scope) error { return scope.SetColumn("New", true) } +// Code returns country code func (m *Country) Code() string { return m.ID } +// Name returns country name func (m *Country) Name() string { return m.CountryName } diff --git a/internal/entity/entity.go b/internal/entity/entity.go index 3183593ec..7b596469e 100644 --- a/internal/entity/entity.go +++ b/internal/entity/entity.go @@ -1,5 +1,5 @@ /* -This package contains models for data storage based on GORM. +Package entity contains models for data storage based on GORM. See http://gorm.io/docs/ for more information about GORM. diff --git a/internal/entity/event.go b/internal/entity/event.go index f61553431..0ac46f947 100644 --- a/internal/entity/event.go +++ b/internal/entity/event.go @@ -7,7 +7,7 @@ import ( "github.com/photoprism/photoprism/pkg/rnd" ) -// Events +// Event defines temporal event that can be used to link photos together type Event struct { EventUUID string `gorm:"type:varbinary(36);unique_index;"` EventSlug string `gorm:"type:varbinary(128);unique_index;"` @@ -25,10 +25,12 @@ type Event struct { DeletedAt *time.Time `sql:"index"` } +// TableName returns Event table identifier "events" func (Event) TableName() string { return "events" } +// BeforeCreate computes a random UUID when a new event is created in database func (e *Event) BeforeCreate(scope *gorm.Scope) error { return scope.SetColumn("EventUUID", rnd.PPID('e')) } diff --git a/internal/entity/file.go b/internal/entity/file.go index e67b36628..2820ba70b 100644 --- a/internal/entity/file.go +++ b/internal/entity/file.go @@ -10,7 +10,7 @@ import ( "github.com/photoprism/photoprism/pkg/rnd" ) -// An image or sidecar file that belongs to a photo +// File represents an image or sidecar file that belongs to a photo type File struct { ID uint `gorm:"primary_key"` Photo *Photo @@ -47,6 +47,7 @@ type File struct { DeletedAt *time.Time `sql:"index"` } +// FirstFileByHash gets a file in db from its hash func FirstFileByHash(db *gorm.DB, fileHash string) (File, error) { var file File @@ -55,10 +56,12 @@ func FirstFileByHash(db *gorm.DB, fileHash string) (File, error) { return file, q.Error } +// BeforeCreate computes a random UUID when a new file is created in database func (m *File) BeforeCreate(scope *gorm.Scope) error { return scope.SetColumn("FileUUID", rnd.PPID('f')) } +// DownloadFileName returns a name useful for download links func (m *File) DownloadFileName() string { if m.Photo == nil { return fmt.Sprintf("%s.%s", m.FileHash, m.FileType) @@ -79,6 +82,7 @@ func (m *File) DownloadFileName() string { return result } +// Changed checks wether given filesize or modified time are matching the current File func (m File) Changed(fileSize int64, fileModified time.Time) bool { if m.FileSize != fileSize { return true diff --git a/internal/entity/keyword.go b/internal/entity/keyword.go index fd96baf1a..b9dbe1bb5 100644 --- a/internal/entity/keyword.go +++ b/internal/entity/keyword.go @@ -7,13 +7,14 @@ import ( "github.com/photoprism/photoprism/internal/mutex" ) -// Keyword for full text search +// Keyword used for full text search type Keyword struct { ID uint `gorm:"primary_key"` Keyword string `gorm:"type:varchar(64);index;"` Skip bool } +// NewKeyword registers a new keyword in database func NewKeyword(keyword string) *Keyword { keyword = strings.ToLower(strings.TrimSpace(keyword)) @@ -24,6 +25,7 @@ func NewKeyword(keyword string) *Keyword { return result } +// FirstOrCreate checks wether the keyword already exist in the database func (m *Keyword) FirstOrCreate(db *gorm.DB) *Keyword { mutex.Db.Lock() defer mutex.Db.Unlock() diff --git a/internal/entity/label.go b/internal/entity/label.go index 20fd4a9ea..f355ac432 100644 --- a/internal/entity/label.go +++ b/internal/entity/label.go @@ -11,7 +11,7 @@ import ( "github.com/photoprism/photoprism/pkg/txt" ) -// Labels for photo, album and location categorization +// Label is used for photo, album and location categorization type Label struct { ID uint `gorm:"primary_key"` LabelUUID string `gorm:"type:varbinary(36);unique_index;"` @@ -28,6 +28,7 @@ type Label struct { New bool `gorm:"-"` } +// BeforeCreate computes a random UUID when a new label is created in database func (m *Label) BeforeCreate(scope *gorm.Scope) error { if err := scope.SetColumn("LabelUUID", rnd.PPID('l')); err != nil { log.Errorf("label: %s", err) @@ -37,6 +38,7 @@ func (m *Label) BeforeCreate(scope *gorm.Scope) error { return nil } +// NewLabel creates a label in database with a given name and priority func NewLabel(labelName string, labelPriority int) *Label { labelName = strings.TrimSpace(labelName) @@ -55,6 +57,7 @@ func NewLabel(labelName string, labelPriority int) *Label { return result } +// FirstOrCreate checks wether the label already exists in the database func (m *Label) FirstOrCreate(db *gorm.DB) *Label { mutex.Db.Lock() defer mutex.Db.Unlock() @@ -66,10 +69,12 @@ func (m *Label) FirstOrCreate(db *gorm.DB) *Label { return m } +// AfterCreate sets the New column used for database callback func (m *Label) AfterCreate(scope *gorm.Scope) error { return scope.SetColumn("New", true) } +// Rename an existing label func (m *Label) Rename(name string) { name = txt.Clip(name, 128) diff --git a/internal/entity/lens.go b/internal/entity/lens.go index 4241fdc4b..0562cd5bd 100644 --- a/internal/entity/lens.go +++ b/internal/entity/lens.go @@ -9,7 +9,7 @@ import ( "github.com/photoprism/photoprism/internal/mutex" ) -// Camera lens (as extracted from UpdateExif metadata) +// Lens represents camera lens (as extracted from UpdateExif metadata) type Lens struct { ID uint `gorm:"primary_key"` LensSlug string `gorm:"type:varbinary(128);unique_index;"` @@ -24,10 +24,12 @@ type Lens struct { DeletedAt *time.Time `sql:"index"` } +// TableName returns Lens table identifier "lens" func (Lens) TableName() string { return "lenses" } +// NewLens creates a new lens in database func NewLens(modelName string, makeName string) *Lens { modelName = strings.TrimSpace(modelName) makeName = strings.TrimSpace(makeName) @@ -47,6 +49,7 @@ func NewLens(modelName string, makeName string) *Lens { return result } +// FirstOrCreate checks wether the lens already exists in the database func (m *Lens) FirstOrCreate(db *gorm.DB) *Lens { mutex.Db.Lock() defer mutex.Db.Unlock() diff --git a/internal/entity/location.go b/internal/entity/location.go index 0042daced..c684f187c 100644 --- a/internal/entity/location.go +++ b/internal/entity/location.go @@ -14,7 +14,7 @@ import ( var locationMutex = sync.Mutex{} -// Photo location +// Location used to associate photos to location type Location struct { ID string `gorm:"type:varbinary(16);primary_key;auto_increment:false;"` PlaceID string `gorm:"type:varbinary(16);"` @@ -26,16 +26,17 @@ type Location struct { UpdatedAt time.Time } -// Locks locations for updates +// Lock location for updates func (Location) Lock() { locationMutex.Lock() } -// Unlock locations for updates +// Unlock location for updates func (Location) Unlock() { locationMutex.Unlock() } +// NewLocation creates a location using a token extracted from coordinate func NewLocation(lat, lng float64) *Location { result := &Location{} @@ -44,6 +45,7 @@ func NewLocation(lat, lng float64) *Location { return result } +// Find gets the location using either the db or the api if not in the db func (m *Location) Find(db *gorm.DB, api string) error { mutex.Db.Lock() defer mutex.Db.Unlock() @@ -83,6 +85,7 @@ func (m *Location) Find(db *gorm.DB, api string) error { return nil } +// Keywords computes keyword based on a Location func (m *Location) Keywords() []string { result := []string{ strings.ToLower(m.City()), @@ -98,66 +101,82 @@ func (m *Location) Keywords() []string { return result } +// Unknown checks if the location has no id func (m *Location) Unknown() bool { return m.ID == "" } +// Name returns name of location func (m *Location) Name() string { return m.LocName } +// NoName checks if the location has no name func (m *Location) NoName() bool { return m.LocName == "" } +// Category returns the location category func (m *Location) Category() string { return m.LocCategory } +// NoCategory checks id the location has no category func (m *Location) NoCategory() bool { return m.LocCategory == "" } +// Label returns the location place label func (m *Location) Label() string { return m.Place.Label() } +// City returns the location place city func (m *Location) City() string { return m.Place.City() } +// LongCity checks if the city name is more than 16 char func (m *Location) LongCity() bool { return len(m.City()) > 16 } +// NoCity checks if the location has no city func (m *Location) NoCity() bool { return m.City() == "" } +// CityContains checks if the location city contains the text string func (m *Location) CityContains(text string) bool { return strings.Contains(text, m.City()) } +// State returns the location place state func (m *Location) State() string { return m.Place.State() } +// NoState checks if the location place has no state func (m *Location) NoState() bool { return m.Place.State() == "" } +// CountryCode returns the location place country code func (m *Location) CountryCode() string { return m.Place.CountryCode() } +// CountryName returns the location place country name func (m *Location) CountryName() string { return m.Place.CountryName() } +// Notes returns the locations place notes func (m *Location) Notes() string { return m.Place.Notes() } +// Source returns the source of location information func (m *Location) Source() string { return m.LocSource } diff --git a/internal/entity/photo.go b/internal/entity/photo.go index 29d71ea31..82d1e15f9 100644 --- a/internal/entity/photo.go +++ b/internal/entity/photo.go @@ -11,7 +11,7 @@ import ( "github.com/ulule/deepcopier" ) -// Photo represents a photo that can have multiple image or sidecar files. +// Photo represents a photo, all its properties, and link to all its images and sidecar files. type Photo struct { ID uint `gorm:"primary_key"` TakenAt time.Time `gorm:"type:datetime;index:idx_photos_taken_uuid;" json:"TakenAt"` @@ -76,6 +76,7 @@ func SavePhoto(model Photo, form form.Photo, db *gorm.DB) error { return db.Save(&model).Error } +// BeforeCreate computes a unique UUID, and set a default takenAt before indexing a new photo func (m *Photo) BeforeCreate(scope *gorm.Scope) error { if err := scope.SetColumn("PhotoUUID", rnd.PPID('p')); err != nil { return err @@ -96,6 +97,7 @@ func (m *Photo) BeforeCreate(scope *gorm.Scope) error { return nil } +// BeforeSave ensures the existence of TakenAt properties before indexing or updating a photo func (m *Photo) BeforeSave(scope *gorm.Scope) error { if m.TakenAt.IsZero() || m.TakenAtLocal.IsZero() { now := time.Now() @@ -112,6 +114,7 @@ func (m *Photo) BeforeSave(scope *gorm.Scope) error { return nil } +// IndexKeywords adds given keywords to the photo entry func (m *Photo) IndexKeywords(keywords []string, db *gorm.DB) { var keywordIds []uint @@ -145,6 +148,7 @@ func (m *Photo) IndexKeywords(keywords []string, db *gorm.DB) { db.Where("photo_id = ? AND keyword_id NOT IN (?)", m.ID, keywordIds).Delete(&PhotoKeyword{}) } +// PreloadFiles prepares gorm scope to retrieve photo file func (m *Photo) PreloadFiles(db *gorm.DB) { q := db.NewScope(nil).DB(). Table("files"). @@ -166,6 +170,7 @@ func (m *Photo) PreloadFiles(db *gorm.DB) { logError(q.Scan(&m.Labels)) } */ +// PreloadKeywords prepares gorm scope to retrieve photo keywords func (m *Photo) PreloadKeywords(db *gorm.DB) { q := db.NewScope(nil).DB(). Table("keywords"). @@ -176,6 +181,7 @@ func (m *Photo) PreloadKeywords(db *gorm.DB) { logError(q.Scan(&m.Keywords)) } +// PreloadAlbums prepares gorm scope to retrieve photo albums func (m *Photo) PreloadAlbums(db *gorm.DB) { q := db.NewScope(nil).DB(). Table("albums"). @@ -187,6 +193,7 @@ func (m *Photo) PreloadAlbums(db *gorm.DB) { logError(q.Scan(&m.Albums)) } +// PreloadMany prepares gorm scope to retrieve photo file, albums and keywords func (m *Photo) PreloadMany(db *gorm.DB) { m.PreloadFiles(db) // m.PreloadLabels(db) @@ -194,54 +201,67 @@ func (m *Photo) PreloadMany(db *gorm.DB) { m.PreloadAlbums(db) } +// NoLocation checks if the photo has no location func (m *Photo) NoLocation() bool { return m.LocationID == "" } +// HasLocation checks if the photo has a location func (m *Photo) HasLocation() bool { return m.LocationID != "" } +// NoPlace checks if the photo has no Place func (m *Photo) NoPlace() bool { return len(m.PlaceID) < 2 } +// HasPlace checks if the photo has a Place func (m *Photo) HasPlace() bool { return len(m.PlaceID) >= 2 } +// NoTitle checks if the photo has no Title func (m *Photo) NoTitle() bool { return m.PhotoTitle == "" } +// NoDescription checks if the photo has no Description func (m *Photo) NoDescription() bool { return m.PhotoDescription == "" } +// NoNotes checks if the photo has no Notes func (m *Photo) NoNotes() bool { return m.PhotoNotes == "" } +// NoArtist checks if the photo has no Artist func (m *Photo) NoArtist() bool { return m.PhotoArtist == "" } +// NoCopyright checks if the photo has no Copyright func (m *Photo) NoCopyright() bool { return m.PhotoCopyright == "" } +// NoSubject checks if the photo has no Subject func (m *Photo) NoSubject() bool { return m.PhotoSubject == "" } +// NoKeywords checks if the photo has no Keywords func (m *Photo) NoKeywords() bool { return m.PhotoKeywords == "" } +// NoCameraSerial checks if the photo has no CameraSerial func (m *Photo) NoCameraSerial() bool { return m.CameraSerial == "" } +// HasTitle checks if the photo has a Title func (m *Photo) HasTitle() bool { return m.PhotoTitle != "" } diff --git a/internal/entity/photo_album.go b/internal/entity/photo_album.go index 6000a708f..cbaf44860 100644 --- a/internal/entity/photo_album.go +++ b/internal/entity/photo_album.go @@ -7,7 +7,7 @@ import ( "github.com/photoprism/photoprism/internal/mutex" ) -// Photos can be added to multiple albums +// PhotoAlbum represents the many_to_many relation between Photo and Album type PhotoAlbum struct { PhotoUUID string `gorm:"type:varbinary(36);primary_key;auto_increment:false"` AlbumUUID string `gorm:"type:varbinary(36);primary_key;auto_increment:false;index"` @@ -18,10 +18,12 @@ type PhotoAlbum struct { Album *Album } +// TableName returns PhotoAlbum table identifier "photos_albums" func (PhotoAlbum) TableName() string { return "photos_albums" } +// NewPhotoAlbum registers an photo and album association using UUID func NewPhotoAlbum(photoUUID, albumUUID string) *PhotoAlbum { result := &PhotoAlbum{ PhotoUUID: photoUUID, @@ -31,6 +33,7 @@ func NewPhotoAlbum(photoUUID, albumUUID string) *PhotoAlbum { return result } +// FirstOrCreate checks wether the PhotoAlbum relation already exist in the database before the creation func (m *PhotoAlbum) FirstOrCreate(db *gorm.DB) *PhotoAlbum { mutex.Db.Lock() defer mutex.Db.Unlock() diff --git a/internal/entity/photo_keyword.go b/internal/entity/photo_keyword.go index 63426229f..3200fb321 100644 --- a/internal/entity/photo_keyword.go +++ b/internal/entity/photo_keyword.go @@ -5,15 +5,18 @@ import ( "github.com/photoprism/photoprism/internal/mutex" ) +// PhotoKeyword represents the many-to-many relation between Photo and Keyword type PhotoKeyword struct { PhotoID uint `gorm:"primary_key;auto_increment:false"` KeywordID uint `gorm:"primary_key;auto_increment:false;index"` } +// TableName returns PhotoKeyword table identifier "photos_keywords" func (PhotoKeyword) TableName() string { return "photos_keywords" } +// NewPhotoKeyword registers a new PhotoKeyword relation func NewPhotoKeyword(photoID, keywordID uint) *PhotoKeyword { result := &PhotoKeyword{ PhotoID: photoID, @@ -23,6 +26,7 @@ func NewPhotoKeyword(photoID, keywordID uint) *PhotoKeyword { return result } +// FirstOrCreate checks wether the PhotoKeywords relation already exist in the database before the creation func (m *PhotoKeyword) FirstOrCreate(db *gorm.DB) *PhotoKeyword { mutex.Db.Lock() defer mutex.Db.Unlock() diff --git a/internal/entity/photo_label.go b/internal/entity/photo_label.go index bdad08d13..5333d51c1 100644 --- a/internal/entity/photo_label.go +++ b/internal/entity/photo_label.go @@ -5,7 +5,8 @@ import ( "github.com/photoprism/photoprism/internal/mutex" ) -// Photo labels are weighted by uncertainty (100 - confidence) +// PhotoLabel represents the many-to-many relation between Photo and label. +// Labels are weighted by uncertainty (100 - confidence) type PhotoLabel struct { PhotoID uint `gorm:"primary_key;auto_increment:false"` LabelID uint `gorm:"primary_key;auto_increment:false;index"` @@ -15,14 +16,16 @@ type PhotoLabel struct { Label *Label } +// TableName returns PhotoLabel table identifier "photos_labels" func (PhotoLabel) TableName() string { return "photos_labels" } -func NewPhotoLabel(photoId, labelId uint, uncertainty int, source string) *PhotoLabel { +// NewPhotoLabel registers a new PhotoLabel relation with an uncertainty and a source of label +func NewPhotoLabel(photoID, labelID uint, uncertainty int, source string) *PhotoLabel { result := &PhotoLabel{ - PhotoID: photoId, - LabelID: labelId, + PhotoID: photoID, + LabelID: labelID, LabelUncertainty: uncertainty, LabelSource: source, } @@ -30,6 +33,7 @@ func NewPhotoLabel(photoId, labelId uint, uncertainty int, source string) *Photo return result } +// FirstOrCreate checks wether the PhotoLabel relation already exist in the database before the creation func (m *PhotoLabel) FirstOrCreate(db *gorm.DB) *PhotoLabel { mutex.Db.Lock() defer mutex.Db.Unlock() diff --git a/internal/entity/place.go b/internal/entity/place.go index 7a2440aab..c7acec8da 100644 --- a/internal/entity/place.go +++ b/internal/entity/place.go @@ -8,7 +8,7 @@ import ( "github.com/photoprism/photoprism/internal/mutex" ) -// Photo place +// Place used to associate photos to places type Place struct { ID string `gorm:"type:varbinary(16);primary_key;auto_increment:false;"` LocLabel string `gorm:"type:varbinary(512);unique_index;"` @@ -22,16 +22,20 @@ type Place struct { New bool `gorm:"-"` } +// UnknownPlace is the default unknown place var UnknownPlace = NewPlace("-", "Unknown", "Unknown", "Unknown", "zz") +// CreateUnknownPlace initializes default place in the database func CreateUnknownPlace(db *gorm.DB) { UnknownPlace.FirstOrCreate(db) } +// AfterCreate sets the New column used for database callback func (m *Place) AfterCreate(scope *gorm.Scope) error { return scope.SetColumn("New", true) } +// FindPlace returns place from a token func FindPlace(token string, db *gorm.DB) *Place { place := &Place{} @@ -42,6 +46,7 @@ func FindPlace(token string, db *gorm.DB) *Place { return place } +// FindPlaceByLabel returns a place from an id or a label func FindPlaceByLabel(id string, label string, db *gorm.DB) *Place { place := &Place{} @@ -52,6 +57,7 @@ func FindPlaceByLabel(id string, label string, db *gorm.DB) *Place { return place } +// NewPlace registers a new place in database func NewPlace(id, label, city, state, countryCode string) *Place { result := &Place{ ID: id, @@ -64,6 +70,7 @@ func NewPlace(id, label, city, state, countryCode string) *Place { return result } +// Find returns db record of place func (m *Place) Find(db *gorm.DB) error { if err := db.First(m, "id = ?", m.ID).Error; err != nil { return err @@ -72,6 +79,7 @@ func (m *Place) Find(db *gorm.DB) error { return nil } +// FirstOrCreate checks wether the place already exists in the database func (m *Place) FirstOrCreate(db *gorm.DB) *Place { mutex.Db.Lock() defer mutex.Db.Unlock() @@ -83,30 +91,37 @@ func (m *Place) FirstOrCreate(db *gorm.DB) *Place { return m } +// NoID checks is the place has no id func (m *Place) NoID() bool { return m.ID == "" } +// Label returns place label func (m *Place) Label() string { return m.LocLabel } +// City returns place City func (m *Place) City() string { return m.LocCity } +// State returns place State func (m *Place) State() string { return m.LocState } +// CountryCode returns place CountryCode func (m *Place) CountryCode() string { return m.LocCountry } +// CountryName returns place CountryName func (m *Place) CountryName() string { return maps.CountryNames[m.LocCountry] } +// Notes returns place Notes func (m *Place) Notes() string { return m.LocNotes }