2020-05-22 16:29:12 +02:00
|
|
|
package entity
|
|
|
|
|
|
|
|
import (
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2020-05-30 15:42:04 +02:00
|
|
|
"github.com/gosimple/slug"
|
2020-05-22 16:29:12 +02:00
|
|
|
"github.com/jinzhu/gorm"
|
2020-05-23 20:58:58 +02:00
|
|
|
"github.com/photoprism/photoprism/internal/event"
|
2020-05-22 16:29:12 +02:00
|
|
|
"github.com/photoprism/photoprism/internal/form"
|
|
|
|
"github.com/photoprism/photoprism/pkg/rnd"
|
|
|
|
"github.com/photoprism/photoprism/pkg/txt"
|
|
|
|
"github.com/ulule/deepcopier"
|
|
|
|
)
|
|
|
|
|
2020-06-01 09:45:24 +02:00
|
|
|
type Folders []Folder
|
|
|
|
|
2020-05-22 16:29:12 +02:00
|
|
|
// Folder represents a file system directory.
|
|
|
|
type Folder struct {
|
2020-05-24 22:16:06 +02:00
|
|
|
Path string `gorm:"type:varbinary(255);unique_index:idx_folders_path_root;" json:"Path" yaml:"Path"`
|
2020-05-27 13:40:21 +02:00
|
|
|
Root string `gorm:"type:varbinary(16);default:'';unique_index:idx_folders_path_root;" json:"Root" yaml:"Root,omitempty"`
|
2020-06-09 19:40:32 +02:00
|
|
|
FolderUID string `gorm:"type:varbinary(42);primary_key;" json:"UID,omitempty" yaml:"UID,omitempty"`
|
2020-05-26 09:02:19 +02:00
|
|
|
FolderType string `gorm:"type:varbinary(16);" json:"Type" yaml:"Type,omitempty"`
|
2020-05-22 16:29:12 +02:00
|
|
|
FolderTitle string `gorm:"type:varchar(255);" json:"Title" yaml:"Title,omitempty"`
|
2020-05-26 09:02:19 +02:00
|
|
|
FolderCategory string `gorm:"type:varchar(255);index;" json:"Category" yaml:"Category,omitempty"`
|
2020-05-22 16:29:12 +02:00
|
|
|
FolderDescription string `gorm:"type:text;" json:"Description,omitempty" yaml:"Description,omitempty"`
|
|
|
|
FolderOrder string `gorm:"type:varbinary(32);" json:"Order" yaml:"Order,omitempty"`
|
2020-05-26 09:02:19 +02:00
|
|
|
FolderCountry string `gorm:"type:varbinary(2);index:idx_folders_country_year_month;default:'zz'" json:"Country" yaml:"Country,omitempty"`
|
|
|
|
FolderYear int `gorm:"index:idx_folders_country_year_month;" json:"Year" yaml:"Year,omitempty"`
|
|
|
|
FolderMonth int `gorm:"index:idx_folders_country_year_month;" json:"Month" yaml:"Month,omitempty"`
|
2020-07-12 16:36:39 +02:00
|
|
|
FolderDay int `json:"Day" yaml:"Day,omitempty"`
|
2020-05-26 09:02:19 +02:00
|
|
|
FolderFavorite bool `json:"Favorite" yaml:"Favorite,omitempty"`
|
|
|
|
FolderPrivate bool `json:"Private" yaml:"Private,omitempty"`
|
|
|
|
FolderIgnore bool `json:"Ignore" yaml:"Ignore,omitempty"`
|
|
|
|
FolderWatch bool `json:"Watch" yaml:"Watch,omitempty"`
|
|
|
|
FileCount int `gorm:"-" json:"FileCount" yaml:"-"`
|
2020-05-22 16:29:12 +02:00
|
|
|
CreatedAt time.Time `json:"-" yaml:"-"`
|
|
|
|
UpdatedAt time.Time `json:"-" yaml:"-"`
|
|
|
|
ModifiedAt *time.Time `json:"ModifiedAt,omitempty" yaml:"-"`
|
|
|
|
DeletedAt *time.Time `sql:"index" json:"-"`
|
|
|
|
}
|
|
|
|
|
2020-05-23 20:58:58 +02:00
|
|
|
// BeforeCreate creates a random UID if needed before inserting a new row to the database.
|
2020-05-22 16:29:12 +02:00
|
|
|
func (m *Folder) BeforeCreate(scope *gorm.Scope) error {
|
2020-05-27 13:40:21 +02:00
|
|
|
if rnd.IsUID(m.FolderUID, 'd') {
|
2020-05-22 16:29:12 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-05-23 20:58:58 +02:00
|
|
|
return scope.SetColumn("FolderUID", rnd.PPID('d'))
|
2020-05-22 16:29:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewFolder creates a new file system directory entity.
|
|
|
|
func NewFolder(root, pathName string, modTime *time.Time) Folder {
|
2020-06-26 12:16:13 +02:00
|
|
|
now := Timestamp()
|
2020-05-22 16:29:12 +02:00
|
|
|
|
|
|
|
pathName = strings.Trim(pathName, string(os.PathSeparator))
|
|
|
|
|
2020-05-22 19:05:16 +02:00
|
|
|
if pathName == RootPath {
|
|
|
|
pathName = ""
|
2020-05-22 16:29:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
result := Folder{
|
2020-05-28 15:12:18 +02:00
|
|
|
FolderUID: rnd.PPID('d'),
|
|
|
|
Root: root,
|
|
|
|
Path: pathName,
|
|
|
|
FolderType: TypeDefault,
|
|
|
|
FolderOrder: SortOrderName,
|
|
|
|
FolderCountry: UnknownCountry.ID,
|
|
|
|
FolderYear: 0,
|
|
|
|
FolderMonth: 0,
|
|
|
|
ModifiedAt: modTime,
|
|
|
|
CreatedAt: now,
|
|
|
|
UpdatedAt: now,
|
2020-05-22 16:29:12 +02:00
|
|
|
}
|
|
|
|
|
2020-05-28 15:12:18 +02:00
|
|
|
result.SetValuesFromPath()
|
2020-05-22 16:29:12 +02:00
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2020-05-28 15:12:18 +02:00
|
|
|
// SetValuesFromPath updates the title and other values based on the path name.
|
|
|
|
func (m *Folder) SetValuesFromPath() {
|
2020-05-22 16:29:12 +02:00
|
|
|
s := m.Path
|
|
|
|
s = strings.TrimSpace(s)
|
|
|
|
|
2020-05-22 19:05:16 +02:00
|
|
|
if s == "" || s == RootPath {
|
2020-06-07 10:09:35 +02:00
|
|
|
if m.Root == RootOriginals {
|
2020-05-27 13:40:21 +02:00
|
|
|
m.FolderTitle = "Originals"
|
2020-05-28 15:12:18 +02:00
|
|
|
|
2020-05-27 13:40:21 +02:00
|
|
|
return
|
|
|
|
} else {
|
|
|
|
s = m.Root
|
|
|
|
}
|
2020-05-22 16:29:12 +02:00
|
|
|
} else {
|
2020-05-28 15:12:18 +02:00
|
|
|
m.FolderCountry = txt.CountryCode(s)
|
|
|
|
m.FolderYear = txt.Year(s)
|
2020-05-22 16:29:12 +02:00
|
|
|
s = path.Base(s)
|
|
|
|
}
|
|
|
|
|
2020-05-28 15:12:18 +02:00
|
|
|
if len(m.Path) >= 6 {
|
2020-05-22 16:29:12 +02:00
|
|
|
if date := txt.Time(m.Path); !date.IsZero() {
|
2020-07-12 16:36:39 +02:00
|
|
|
if txt.IsUInt(s) || txt.IsTime(s) {
|
2020-05-28 15:12:18 +02:00
|
|
|
if date.Day() > 1 {
|
|
|
|
m.FolderTitle = date.Format("January 2, 2006")
|
|
|
|
} else {
|
|
|
|
m.FolderTitle = date.Format("January 2006")
|
|
|
|
}
|
2020-05-22 16:29:12 +02:00
|
|
|
}
|
2020-05-28 15:12:18 +02:00
|
|
|
|
|
|
|
m.FolderYear = date.Year()
|
|
|
|
m.FolderMonth = int(date.Month())
|
2020-07-12 16:36:39 +02:00
|
|
|
m.FolderDay = date.Day()
|
2020-05-22 16:29:12 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-28 15:12:18 +02:00
|
|
|
if m.FolderTitle == "" {
|
|
|
|
s = strings.ReplaceAll(s, "_", " ")
|
|
|
|
s = strings.ReplaceAll(s, "-", " ")
|
|
|
|
s = strings.Title(s)
|
2020-05-22 16:29:12 +02:00
|
|
|
|
2020-05-28 15:12:18 +02:00
|
|
|
m.FolderTitle = txt.Clip(s, txt.ClipDefault)
|
|
|
|
}
|
2020-05-22 16:29:12 +02:00
|
|
|
}
|
|
|
|
|
2020-05-30 15:42:04 +02:00
|
|
|
// Slug returns a slug based on the folder title.
|
|
|
|
func (m *Folder) Slug() string {
|
|
|
|
return slug.Make(m.FolderTitle)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Title returns a human readable folder title.
|
|
|
|
func (m *Folder) Title() string {
|
|
|
|
return m.FolderTitle
|
|
|
|
}
|
|
|
|
|
2020-05-22 19:05:16 +02:00
|
|
|
// Saves the complete entity in the database.
|
|
|
|
func (m *Folder) Create() error {
|
2020-05-23 20:58:58 +02:00
|
|
|
if err := Db().Create(m).Error; err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
event.Publish("count.folders", event.Data{
|
|
|
|
"count": 1,
|
|
|
|
})
|
|
|
|
|
|
|
|
return nil
|
2020-05-22 19:05:16 +02:00
|
|
|
}
|
|
|
|
|
2020-05-28 15:12:18 +02:00
|
|
|
// FindFolder returns an existing row if exists.
|
|
|
|
func FindFolder(root, pathName string) *Folder {
|
|
|
|
pathName = strings.Trim(pathName, string(os.PathSeparator))
|
|
|
|
|
|
|
|
result := Folder{}
|
|
|
|
|
|
|
|
if err := Db().Where("path = ? AND root = ?", pathName, root).First(&result).Error; err == nil {
|
|
|
|
return &result
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-05-26 11:00:39 +02:00
|
|
|
// FirstOrCreateFolder returns the existing row, inserts a new row or nil in case of errors.
|
2020-05-25 19:10:44 +02:00
|
|
|
func FirstOrCreateFolder(m *Folder) *Folder {
|
|
|
|
result := Folder{}
|
2020-05-24 22:16:06 +02:00
|
|
|
|
2020-05-25 19:10:44 +02:00
|
|
|
if err := Db().Where("path = ? AND root = ?", m.Path, m.Root).First(&result).Error; err == nil {
|
|
|
|
return &result
|
2020-07-07 20:44:33 +02:00
|
|
|
} else if createErr := m.Create(); createErr == nil {
|
|
|
|
return m
|
|
|
|
} else if err := Db().Where("path = ? AND root = ?", m.Path, m.Root).First(&result).Error; err == nil {
|
|
|
|
return &result
|
|
|
|
} else {
|
|
|
|
log.Errorf("folder: %s (first or create %s)", createErr, m.Path)
|
2020-05-24 22:16:06 +02:00
|
|
|
}
|
|
|
|
|
2020-07-07 20:44:33 +02:00
|
|
|
return nil
|
2020-05-24 22:16:06 +02:00
|
|
|
}
|
|
|
|
|
2020-05-22 19:05:16 +02:00
|
|
|
// Updates selected properties in the database.
|
|
|
|
func (m *Folder) Updates(values interface{}) error {
|
|
|
|
return Db().Model(m).Updates(values).Error
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetForm updates the entity properties based on form values.
|
|
|
|
func (m *Folder) SetForm(f form.Folder) error {
|
2020-05-22 16:29:12 +02:00
|
|
|
if err := deepcopier.Copy(m).From(f); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-05-22 19:05:16 +02:00
|
|
|
return nil
|
2020-05-22 16:29:12 +02:00
|
|
|
}
|