Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
parent
96dfe6c7c9
commit
bac6ae0cbd
45 changed files with 779 additions and 191 deletions
|
@ -173,7 +173,7 @@ func ShareWithAccount(router *gin.RouterGroup) {
|
|||
entity.FirstOrCreateFileShare(entity.NewFileShare(file.ID, m.ID, alias))
|
||||
}
|
||||
|
||||
workers.StartShare(service.Config())
|
||||
workers.RunShare(service.Config())
|
||||
|
||||
c.JSON(http.StatusOK, files)
|
||||
})
|
||||
|
@ -288,7 +288,7 @@ func UpdateAccount(router *gin.RouterGroup) {
|
|||
}
|
||||
|
||||
if m.AccSync {
|
||||
workers.StartSync(service.Config())
|
||||
workers.RunSync(service.Config())
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, m)
|
||||
|
|
|
@ -17,7 +17,7 @@ import (
|
|||
var ConvertCommand = cli.Command{
|
||||
Name: "convert",
|
||||
Usage: "Converts files in other formats to JPEG and AVC as needed",
|
||||
ArgsUsage: "[SUB-FOLDER]",
|
||||
ArgsUsage: "[sub-folder]",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "force, f",
|
||||
|
|
|
@ -20,7 +20,7 @@ var CopyCommand = cli.Command{
|
|||
Name: "cp",
|
||||
Aliases: []string{"copy"},
|
||||
Usage: "Copies media files to originals",
|
||||
ArgsUsage: "[IMPORT PATH]",
|
||||
ArgsUsage: "[source]",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "dest, d",
|
||||
|
|
|
@ -53,7 +53,7 @@ var FacesCommand = cli.Command{
|
|||
{
|
||||
Name: "index",
|
||||
Usage: "Searches originals for faces",
|
||||
ArgsUsage: "[SUB-FOLDER]",
|
||||
ArgsUsage: "[sub-folder]",
|
||||
Action: facesIndexAction,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -20,7 +20,7 @@ var ImportCommand = cli.Command{
|
|||
Name: "mv",
|
||||
Aliases: []string{"import"},
|
||||
Usage: "Moves media files to originals",
|
||||
ArgsUsage: "[SOURCE PATH]",
|
||||
ArgsUsage: "[source]",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "dest, d",
|
||||
|
|
|
@ -20,7 +20,7 @@ import (
|
|||
var IndexCommand = cli.Command{
|
||||
Name: "index",
|
||||
Usage: "Indexes original media files",
|
||||
ArgsUsage: "[SUB-FOLDER]",
|
||||
ArgsUsage: "[sub-folder]",
|
||||
Flags: indexFlags,
|
||||
Action: indexAction,
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ var MigrationsStatusCommand = cli.Command{
|
|||
Name: "ls",
|
||||
Aliases: []string{"status", "show"},
|
||||
Usage: "Lists the status of schema migrations",
|
||||
ArgsUsage: "[MIGRATIONS...]",
|
||||
ArgsUsage: "[migrations...]",
|
||||
Flags: report.CliFlags,
|
||||
Action: migrationsStatusAction,
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ var MigrationsRunCommand = cli.Command{
|
|||
Name: "run",
|
||||
Aliases: []string{"execute", "migrate"},
|
||||
Usage: "Executes database schema migrations",
|
||||
ArgsUsage: "[MIGRATIONS...]",
|
||||
ArgsUsage: "[migrations...]",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "failed, f",
|
||||
|
|
|
@ -64,24 +64,24 @@ func resetAction(ctx *cli.Context) error {
|
|||
log.Infoln("reset: enabled trace mode")
|
||||
}
|
||||
|
||||
resetIndex := ctx.Bool("yes")
|
||||
confirmed := ctx.Bool("yes")
|
||||
|
||||
// Show prompt?
|
||||
if !resetIndex {
|
||||
if !confirmed {
|
||||
removeIndexPrompt := promptui.Prompt{
|
||||
Label: "Delete and recreate index database?",
|
||||
IsConfirm: true,
|
||||
}
|
||||
|
||||
if _, err := removeIndexPrompt.Run(); err == nil {
|
||||
resetIndex = true
|
||||
confirmed = true
|
||||
} else {
|
||||
log.Infof("keeping index database")
|
||||
}
|
||||
}
|
||||
|
||||
// Reset index?
|
||||
if resetIndex {
|
||||
if confirmed {
|
||||
resetIndexDb(conf)
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/server"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/internal/session"
|
||||
"github.com/photoprism/photoprism/internal/workers"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
|
@ -119,6 +120,7 @@ func startAction(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
// Start background workers.
|
||||
session.Monitor(time.Hour)
|
||||
workers.Start(conf)
|
||||
auto.Start(conf)
|
||||
|
||||
|
@ -131,6 +133,7 @@ func startAction(ctx *cli.Context) error {
|
|||
// Stop all background activity.
|
||||
auto.Stop()
|
||||
workers.Stop()
|
||||
session.Shutdown()
|
||||
mutex.CancelAll()
|
||||
|
||||
log.Info("shutting down...")
|
||||
|
|
|
@ -20,8 +20,9 @@ const (
|
|||
|
||||
// UsersCommand registers the user management subcommands.
|
||||
var UsersCommand = cli.Command{
|
||||
Name: "users",
|
||||
Usage: "User management subcommands",
|
||||
Name: "users",
|
||||
Aliases: []string{"user"},
|
||||
Usage: "User management subcommands",
|
||||
Subcommands: []cli.Command{
|
||||
UsersListCommand,
|
||||
UsersAddCommand,
|
||||
|
|
|
@ -38,6 +38,28 @@ func (c *Config) AdminPassword() string {
|
|||
return clean.Password(c.options.AdminPassword)
|
||||
}
|
||||
|
||||
// SessMaxAge returns the time in seconds until browser sessions expire automatically.
|
||||
func (c *Config) SessMaxAge() int64 {
|
||||
if c.options.SessMaxAge < 0 {
|
||||
return 0
|
||||
} else if c.options.SessMaxAge == 0 {
|
||||
return DefaultSessMaxAge
|
||||
}
|
||||
|
||||
return c.options.SessMaxAge
|
||||
}
|
||||
|
||||
// SessTimeout returns the time in seconds until browser sessions expire due to inactivity
|
||||
func (c *Config) SessTimeout() int64 {
|
||||
if c.options.SessTimeout < 0 {
|
||||
return 0
|
||||
} else if c.options.SessTimeout == 0 {
|
||||
return DefaultSessTimeout
|
||||
}
|
||||
|
||||
return c.options.SessTimeout
|
||||
}
|
||||
|
||||
// Public checks if app runs in public mode and requires no authentication.
|
||||
func (c *Config) Public() bool {
|
||||
return c.AuthMode() == AuthModePublic
|
||||
|
|
|
@ -37,6 +37,24 @@ func TestAuth(t *testing.T) {
|
|||
assert.False(t, c.Auth())
|
||||
}
|
||||
|
||||
func TestSessMaxAge(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
assert.Equal(t, DefaultSessMaxAge, c.SessMaxAge())
|
||||
c.options.SessMaxAge = -1
|
||||
assert.Equal(t, int64(0), c.SessMaxAge())
|
||||
c.options.SessMaxAge = 0
|
||||
assert.Equal(t, DefaultSessMaxAge, c.SessMaxAge())
|
||||
}
|
||||
|
||||
func TestSessTimeout(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
assert.Equal(t, DefaultSessTimeout, c.SessTimeout())
|
||||
c.options.SessTimeout = -1
|
||||
assert.Equal(t, int64(0), c.SessTimeout())
|
||||
c.options.SessTimeout = 0
|
||||
assert.Equal(t, DefaultSessTimeout, c.SessTimeout())
|
||||
}
|
||||
|
||||
func TestUtils_CheckPassword(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
|
||||
|
|
|
@ -48,3 +48,18 @@ const DefaultResolutionLimit = 150 // 150 Megapixels
|
|||
|
||||
// serialName is the name of the unique storage serial.
|
||||
const serialName = "serial"
|
||||
|
||||
// UnixHour is one hour in UnixTime.
|
||||
const UnixHour int64 = 3600
|
||||
|
||||
// UnixDay is one day in UnixTime.
|
||||
const UnixDay = UnixHour * 24
|
||||
|
||||
// UnixWeek is one week in UnixTime.
|
||||
const UnixWeek = UnixDay * 7
|
||||
|
||||
// DefaultSessMaxAge is the default session expiration time in seconds.
|
||||
const DefaultSessMaxAge = UnixWeek * 2
|
||||
|
||||
// DefaultSessTimeout is the default session timeout time in seconds.
|
||||
const DefaultSessTimeout = UnixWeek
|
||||
|
|
|
@ -25,6 +25,8 @@ func (c *Config) Report() (rows [][]string, cols []string) {
|
|||
{"admin-user", c.AdminUser()},
|
||||
{"admin-password", strings.Repeat("*", utf8.RuneCountInString(c.AdminPassword()))},
|
||||
{"public", fmt.Sprintf("%t", c.Public())},
|
||||
{"sess-maxage", fmt.Sprintf("%d", c.SessMaxAge())},
|
||||
{"sess-timeout", fmt.Sprintf("%d", c.SessTimeout())},
|
||||
|
||||
// Logging.
|
||||
{"log-level", c.LogLevel().String()},
|
||||
|
|
|
@ -28,6 +28,8 @@ type Options struct {
|
|||
Public bool `yaml:"Public" json:"-" flag:"public"`
|
||||
AdminUser string `yaml:"AdminUser" json:"-" flag:"admin-user"`
|
||||
AdminPassword string `yaml:"AdminPassword" json:"-" flag:"admin-password"`
|
||||
SessMaxAge int64 `yaml:"SessMaxAge" json:"-" flag:"sess-maxage"`
|
||||
SessTimeout int64 `yaml:"SessTimeout" json:"-" flag:"sess-timeout"`
|
||||
LogLevel string `yaml:"LogLevel" json:"-" flag:"log-level"`
|
||||
Prod bool `yaml:"Prod" json:"Prod" flag:"prod"`
|
||||
Debug bool `yaml:"Debug" json:"Debug" flag:"debug"`
|
||||
|
|
|
@ -18,6 +18,13 @@ var Flags = CliFlags{
|
|||
Value: "password",
|
||||
EnvVar: "PHOTOPRISM_AUTH_MODE",
|
||||
}},
|
||||
CliFlag{
|
||||
Flag: cli.BoolFlag{
|
||||
Name: "public, p",
|
||||
Hidden: true,
|
||||
Usage: "disable authentication, advanced settings, and WebDAV remote access",
|
||||
EnvVar: "PHOTOPRISM_PUBLIC",
|
||||
}},
|
||||
CliFlag{
|
||||
Flag: cli.StringFlag{
|
||||
Name: "admin-user, login",
|
||||
|
@ -32,11 +39,18 @@ var Flags = CliFlags{
|
|||
EnvVar: "PHOTOPRISM_ADMIN_PASSWORD",
|
||||
}},
|
||||
CliFlag{
|
||||
Flag: cli.BoolFlag{
|
||||
Name: "public, p",
|
||||
Hidden: true,
|
||||
Usage: "disable authentication, advanced settings, and WebDAV remote access",
|
||||
EnvVar: "PHOTOPRISM_PUBLIC",
|
||||
Flag: cli.Int64Flag{
|
||||
Name: "sess-maxage",
|
||||
Value: DefaultSessMaxAge,
|
||||
Usage: "time in `SECONDS` until user sessions expire automatically (-1 to disable)",
|
||||
EnvVar: "PHOTOPRISM_SESS_MAXAGE",
|
||||
}},
|
||||
CliFlag{
|
||||
Flag: cli.Int64Flag{
|
||||
Name: "sess-timeout",
|
||||
Value: DefaultSessTimeout,
|
||||
Usage: "time in `SECONDS` until user sessions expire due to inactivity (-1 to disable)",
|
||||
EnvVar: "PHOTOPRISM_SESS_TIMEOUT",
|
||||
}},
|
||||
CliFlag{
|
||||
Flag: cli.StringFlag{
|
||||
|
|
|
@ -28,31 +28,32 @@ type Sessions []Session
|
|||
// Session represents a User session.
|
||||
type Session struct {
|
||||
ID string `gorm:"type:VARBINARY(2048);primary_key;auto_increment:false;" json:"ID" yaml:"ID"`
|
||||
Status int `json:"Status,omitempty" yaml:"-"`
|
||||
AuthMethod string `gorm:"type:VARBINARY(128);default:'';" json:"-" yaml:"AuthMethod,omitempty"`
|
||||
AuthProvider string `gorm:"type:VARBINARY(128);default:'';" json:"-" yaml:"AuthProvider,omitempty"`
|
||||
AuthDomain string `gorm:"type:VARBINARY(255);default:'';" json:"-" yaml:"AuthDomain,omitempty"`
|
||||
AuthScope string `gorm:"size:1024;default:'';" json:"-" yaml:"AuthScope,omitempty"`
|
||||
AuthID string `gorm:"type:VARBINARY(128);index;default:'';" json:"-" yaml:"AuthID,omitempty"`
|
||||
ClientIP string `gorm:"size:64;column:client_ip;index" json:"-" yaml:"ClientIP,omitempty"`
|
||||
UserUID string `gorm:"type:VARBINARY(42);index;default:'';" json:"UserUID,omitempty" yaml:"UserUID,omitempty"`
|
||||
UserName string `gorm:"size:64;index;" json:"UserName,omitempty" yaml:"UserName,omitempty"`
|
||||
user *User `gorm:"-"`
|
||||
AuthProvider string `gorm:"type:VARBINARY(128);default:'';" json:"-" yaml:"AuthProvider,omitempty"`
|
||||
AuthMethod string `gorm:"type:VARBINARY(128);default:'';" json:"-" yaml:"AuthMethod,omitempty"`
|
||||
AuthDomain string `gorm:"type:VARBINARY(255);default:'';" json:"-" yaml:"AuthDomain,omitempty"`
|
||||
AuthID string `gorm:"type:VARBINARY(128);index;default:'';" json:"-" yaml:"AuthID,omitempty"`
|
||||
AuthScope string `gorm:"size:1024;default:'';" json:"-" yaml:"AuthScope,omitempty"`
|
||||
LastActive int64 `json:"LastActive,omitempty" yaml:"LastActive,omitempty"`
|
||||
SessExpires int64 `gorm:"index" json:"Expires,omitempty" yaml:"Expires,omitempty"`
|
||||
SessTimeout int64 `json:"Timeout,omitempty" yaml:"Timeout,omitempty"`
|
||||
PreviewToken string `gorm:"type:VARBINARY(64);column:preview_token;default:'';" json:"-" yaml:"-"`
|
||||
DownloadToken string `gorm:"type:VARBINARY(64);column:download_token;default:'';" json:"-" yaml:"-"`
|
||||
AccessToken string `gorm:"type:VARBINARY(4096);column:access_token;default:'';" json:"-" yaml:"-"`
|
||||
RefreshToken string `gorm:"type:VARBINARY(512);column:refresh_token;default:'';" json:"-" yaml:"-"`
|
||||
IdToken string `gorm:"type:VARBINARY(1024);column:id_token;default:'';" json:"IdToken,omitempty" yaml:"IdToken,omitempty"`
|
||||
UserAgent string `gorm:"size:512;" json:"-" yaml:"UserAgent,omitempty"`
|
||||
ClientIP string `gorm:"size:64;column:client_ip;" json:"-" yaml:"ClientIP,omitempty"`
|
||||
LoginIP string `gorm:"size:64;column:login_ip" json:"-" yaml:"-"`
|
||||
LoginAt time.Time `json:"-" yaml:"-"`
|
||||
DataJSON json.RawMessage `gorm:"type:VARBINARY(4096);" json:"Data,omitempty" yaml:"Data,omitempty"`
|
||||
data *SessionData `gorm:"-"`
|
||||
RefID string `gorm:"type:VARBINARY(16);default:'';" json:"-" yaml:"-"`
|
||||
LoginIP string `gorm:"size:64;column:login_ip" json:"-" yaml:"-"`
|
||||
LoginAt time.Time `json:"-" yaml:"-"`
|
||||
CreatedAt time.Time `json:"CreatedAt" yaml:"CreatedAt"`
|
||||
MaxAge time.Duration `json:"MaxAge,omitempty" yaml:"MaxAge,omitempty"`
|
||||
UpdatedAt time.Time `json:"UpdatedAt" yaml:"UpdatedAt"`
|
||||
Timeout time.Duration `json:"Timeout,omitempty" yaml:"Timeout,omitempty"`
|
||||
Status int `gorm:"-" json:"Status,omitempty" yaml:"-"`
|
||||
}
|
||||
|
||||
// TableName returns the entity table name.
|
||||
|
@ -60,31 +61,48 @@ func (Session) TableName() string {
|
|||
return "auth_sessions"
|
||||
}
|
||||
|
||||
// NewSession creates a new session and returns it.
|
||||
func NewSession(maxAge, timeout time.Duration) (m *Session) {
|
||||
// NewSession creates a new session using the maxAge and timeout in seconds.
|
||||
func NewSession(maxAge, timeout int64) (m *Session) {
|
||||
created := TimeStamp()
|
||||
|
||||
// Makes no sense for the timeout to be longer than the max age.
|
||||
if timeout >= maxAge {
|
||||
maxAge = timeout
|
||||
timeout = 0
|
||||
} else if maxAge == 0 {
|
||||
// Set maxAge to default if not specified.
|
||||
maxAge = time.Hour * 24 * 7
|
||||
}
|
||||
|
||||
m = &Session{
|
||||
ID: rnd.SessionID(),
|
||||
MaxAge: maxAge,
|
||||
Timeout: timeout,
|
||||
RefID: rnd.RefID(SessionPrefix),
|
||||
CreatedAt: created,
|
||||
UpdatedAt: created,
|
||||
}
|
||||
|
||||
if maxAge > 0 {
|
||||
m.SessExpires = created.Unix() + maxAge
|
||||
}
|
||||
|
||||
if timeout > 0 {
|
||||
m.SessTimeout = timeout
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// DeleteExpiredSessions deletes expired sessions.
|
||||
func DeleteExpiredSessions() (deleted int) {
|
||||
expired := Sessions{}
|
||||
|
||||
if err := Db().Where("sess_expires > 0 AND sess_expires < ?", UnixTime()).Find(&expired).Error; err != nil {
|
||||
event.AuditErr([]string{"failed to fetch sessions sessions", "%s"}, err)
|
||||
return deleted
|
||||
}
|
||||
|
||||
for _, s := range expired {
|
||||
if err := s.Delete(); err != nil {
|
||||
event.AuditErr([]string{s.IP(), "session %s", "failed to delete", "%s"}, s.RefID, err)
|
||||
} else {
|
||||
deleted++
|
||||
}
|
||||
}
|
||||
|
||||
return deleted
|
||||
}
|
||||
|
||||
// SessionStatusUnauthorized returns a session with status unauthorized (401).
|
||||
func SessionStatusUnauthorized() *Session {
|
||||
return &Session{Status: http.StatusUnauthorized}
|
||||
|
@ -363,16 +381,57 @@ func (m *Session) RedeemToken(token string) (n int) {
|
|||
|
||||
// ExpiresAt returns the time when the session expires.
|
||||
func (m *Session) ExpiresAt() time.Time {
|
||||
return m.CreatedAt.Add(m.MaxAge)
|
||||
if m.SessExpires <= 0 {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
return time.Unix(m.SessExpires, 0)
|
||||
}
|
||||
|
||||
// TimeoutAt returns the time at which the session will expire due to inactivity.
|
||||
func (m *Session) TimeoutAt() time.Time {
|
||||
if m.SessTimeout <= 0 || m.LastActive <= 0 {
|
||||
return m.ExpiresAt()
|
||||
} else if t := m.LastActive + m.SessTimeout; t <= m.SessExpires || m.SessExpires <= 0 {
|
||||
return time.Unix(m.LastActive+m.SessTimeout, 0)
|
||||
} else {
|
||||
return m.ExpiresAt()
|
||||
}
|
||||
}
|
||||
|
||||
// TimedOut checks if the session has expired due to inactivity..
|
||||
func (m *Session) TimedOut() bool {
|
||||
if at := m.TimeoutAt(); at.IsZero() {
|
||||
return false
|
||||
} else {
|
||||
return at.Before(UTC())
|
||||
}
|
||||
}
|
||||
|
||||
// Expired checks if the session has expired.
|
||||
func (m *Session) Expired() bool {
|
||||
if m.MaxAge <= 0 {
|
||||
if m.SessExpires <= 0 {
|
||||
return m.TimedOut()
|
||||
} else if at := m.ExpiresAt(); at.IsZero() {
|
||||
return false
|
||||
} else {
|
||||
return at.Before(UTC())
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateLastActive sets the last activity of the session to now.
|
||||
func (m *Session) UpdateLastActive() *Session {
|
||||
if m.Invalid() {
|
||||
return m
|
||||
}
|
||||
|
||||
return m.ExpiresAt().Before(UTC())
|
||||
m.LastActive = UnixTime()
|
||||
|
||||
if err := Db().Model(m).UpdateColumn("LastActive", m.LastActive).Error; err != nil {
|
||||
event.AuditWarn([]string{m.IP(), "session %s", "failed to update last active time", "%s"}, m.RefID, err)
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// Invalid checks if the session does not belong to a registered user or a visitor with shares.
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
gc "github.com/patrickmn/go-cache"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
@ -38,17 +39,25 @@ func FindSession(id string) (s Session, err error) {
|
|||
|
||||
// Find cached session.
|
||||
if cacheData, ok := sessionCache.Get(id); ok {
|
||||
return cacheData.(Session), nil
|
||||
s = cacheData.(Session)
|
||||
s.LastActive = UnixTime()
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Search database and return session if found.
|
||||
if r := Db().First(&s, "id = ?", id); r.RecordNotFound() {
|
||||
err = fmt.Errorf("not found")
|
||||
return s, fmt.Errorf("not found")
|
||||
} else if r.Error != nil {
|
||||
err = r.Error
|
||||
return s, r.Error
|
||||
} else if !rnd.IsSessionID(s.ID) {
|
||||
err = fmt.Errorf("has invalid id %s", clean.LogQuote(s.ID))
|
||||
return s, fmt.Errorf("has invalid id %s", clean.LogQuote(s.ID))
|
||||
} else if s.Expired() {
|
||||
if err = s.Delete(); err != nil {
|
||||
event.AuditErr([]string{s.IP(), "session %s", "failed to delete after expiration", "%s"}, s.RefID, err)
|
||||
}
|
||||
return s, fmt.Errorf("expired")
|
||||
} else {
|
||||
s.UpdateLastActive()
|
||||
sessionCache.SetDefault(s.ID, s)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package entity
|
||||
|
||||
import "time"
|
||||
|
||||
type SessionMap map[string]Session
|
||||
|
||||
func (m SessionMap) Get(name string) Session {
|
||||
|
@ -22,49 +20,49 @@ func (m SessionMap) Pointer(name string) *Session {
|
|||
|
||||
var SessionFixtures = SessionMap{
|
||||
"alice": {
|
||||
ID: "69be27ac5ca305b394046a83f6fda18167ca3d3f2dbe7ac0",
|
||||
Timeout: time.Hour * 24 * 3,
|
||||
MaxAge: time.Hour * 24 * 7,
|
||||
user: UserFixtures.Pointer("alice"),
|
||||
UserUID: UserFixtures.Pointer("alice").UserUID,
|
||||
UserName: UserFixtures.Pointer("alice").UserName,
|
||||
ID: "69be27ac5ca305b394046a83f6fda18167ca3d3f2dbe7ac0",
|
||||
SessTimeout: UnixDay * 3,
|
||||
SessExpires: UnixTime() + UnixWeek,
|
||||
user: UserFixtures.Pointer("alice"),
|
||||
UserUID: UserFixtures.Pointer("alice").UserUID,
|
||||
UserName: UserFixtures.Pointer("alice").UserName,
|
||||
},
|
||||
"bob": {
|
||||
ID: "69be27ac5ca305b394046a83f6fda18167ca3d3f2dbe7ac1",
|
||||
Timeout: time.Hour * 24 * 3,
|
||||
MaxAge: time.Hour * 24 * 7,
|
||||
user: UserFixtures.Pointer("bob"),
|
||||
UserUID: UserFixtures.Pointer("bob").UserUID,
|
||||
UserName: UserFixtures.Pointer("bob").UserName,
|
||||
ID: "69be27ac5ca305b394046a83f6fda18167ca3d3f2dbe7ac1",
|
||||
SessTimeout: UnixDay * 3,
|
||||
SessExpires: UnixTime() + UnixWeek,
|
||||
user: UserFixtures.Pointer("bob"),
|
||||
UserUID: UserFixtures.Pointer("bob").UserUID,
|
||||
UserName: UserFixtures.Pointer("bob").UserName,
|
||||
},
|
||||
"unauthorized": {
|
||||
ID: "69be27ac5ca305b394046a83f6fda18167ca3d3f2dbe7ac2",
|
||||
Timeout: time.Hour * 24 * 3,
|
||||
MaxAge: time.Hour * 24 * 7,
|
||||
user: UserFixtures.Pointer("unauthorized"),
|
||||
UserUID: UserFixtures.Pointer("unauthorized").UserUID,
|
||||
UserName: UserFixtures.Pointer("unauthorized").UserName,
|
||||
ID: "69be27ac5ca305b394046a83f6fda18167ca3d3f2dbe7ac2",
|
||||
SessTimeout: UnixDay * 3,
|
||||
SessExpires: UnixTime() + UnixWeek,
|
||||
user: UserFixtures.Pointer("unauthorized"),
|
||||
UserUID: UserFixtures.Pointer("unauthorized").UserUID,
|
||||
UserName: UserFixtures.Pointer("unauthorized").UserName,
|
||||
},
|
||||
"visitor": {
|
||||
ID: "69be27ac5ca305b394046a83f6fda18167ca3d3f2dbe7ac3",
|
||||
Timeout: time.Hour * 24 * 3,
|
||||
MaxAge: time.Hour * 24 * 7,
|
||||
user: &Visitor,
|
||||
UserUID: Visitor.UserUID,
|
||||
UserName: Visitor.UserName,
|
||||
DataJSON: []byte(`{"tokens":["1jxf3jfn2k"],"shares":["at9lxuqxpogaaba8"]}`),
|
||||
ID: "69be27ac5ca305b394046a83f6fda18167ca3d3f2dbe7ac3",
|
||||
SessTimeout: UnixDay * 3,
|
||||
SessExpires: UnixTime() + UnixWeek,
|
||||
user: &Visitor,
|
||||
UserUID: Visitor.UserUID,
|
||||
UserName: Visitor.UserName,
|
||||
DataJSON: []byte(`{"tokens":["1jxf3jfn2k"],"shares":["at9lxuqxpogaaba8"]}`),
|
||||
data: &SessionData{
|
||||
Tokens: []string{"1jxf3jfn2k"},
|
||||
Shares: UIDs{"at9lxuqxpogaaba8"},
|
||||
},
|
||||
},
|
||||
"friend": {
|
||||
ID: "69be27ac5ca305b394046a83f6fda18167ca3d3f2dbe7ac4",
|
||||
Timeout: time.Hour * 24 * 3,
|
||||
MaxAge: time.Hour * 24 * 7,
|
||||
user: UserFixtures.Pointer("friend"),
|
||||
UserUID: UserFixtures.Pointer("friend").UserUID,
|
||||
UserName: UserFixtures.Pointer("friend").UserName,
|
||||
ID: "69be27ac5ca305b394046a83f6fda18167ca3d3f2dbe7ac4",
|
||||
SessTimeout: UnixDay * 3,
|
||||
SessExpires: UnixTime() + UnixWeek,
|
||||
user: UserFixtures.Pointer("friend"),
|
||||
UserUID: UserFixtures.Pointer("friend").UserUID,
|
||||
UserName: UserFixtures.Pointer("friend").UserName,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
31
internal/entity/auth_session_report.go
Normal file
31
internal/entity/auth_session_report.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Report returns the entity values as rows.
|
||||
func (m *Session) Report(skipEmpty bool) (rows [][]string, cols []string) {
|
||||
cols = []string{"Name", "Value"}
|
||||
|
||||
// Extract model values.
|
||||
values, _, err := ModelValues(m, "ID")
|
||||
|
||||
// Ok?
|
||||
if err != nil {
|
||||
return rows, cols
|
||||
}
|
||||
|
||||
rows = make([][]string, 0, len(values))
|
||||
|
||||
for k, v := range values {
|
||||
s := fmt.Sprintf("%v", v)
|
||||
|
||||
// Skip empty values?
|
||||
if !skipEmpty || s != "" {
|
||||
rows = append(rows, []string{k, s})
|
||||
}
|
||||
}
|
||||
|
||||
return rows, cols
|
||||
}
|
|
@ -2,7 +2,6 @@ package entity
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
|
@ -11,7 +10,7 @@ import (
|
|||
|
||||
func TestNewSession(t *testing.T) {
|
||||
t.Run("NoSessionData", func(t *testing.T) {
|
||||
m := NewSession(time.Hour*24, time.Hour*6)
|
||||
m := NewSession(UnixDay, UnixHour*6)
|
||||
|
||||
assert.True(t, rnd.IsSessionID(m.ID))
|
||||
assert.False(t, m.CreatedAt.IsZero())
|
||||
|
@ -22,7 +21,7 @@ func TestNewSession(t *testing.T) {
|
|||
assert.Equal(t, 0, len(m.Data().Tokens))
|
||||
})
|
||||
t.Run("EmptySessionData", func(t *testing.T) {
|
||||
m := NewSession(time.Hour*24, time.Hour*6)
|
||||
m := NewSession(UnixDay, UnixHour*6)
|
||||
m.SetData(NewSessionData())
|
||||
|
||||
assert.True(t, rnd.IsSessionID(m.ID))
|
||||
|
@ -36,7 +35,7 @@ func TestNewSession(t *testing.T) {
|
|||
t.Run("WithSessionData", func(t *testing.T) {
|
||||
data := NewSessionData()
|
||||
data.Tokens = []string{"foo", "bar"}
|
||||
m := NewSession(time.Hour*24, time.Hour*6)
|
||||
m := NewSession(UnixDay, UnixHour*6)
|
||||
m.SetData(data)
|
||||
|
||||
assert.True(t, rnd.IsSessionID(m.ID))
|
||||
|
@ -48,14 +47,12 @@ func TestNewSession(t *testing.T) {
|
|||
assert.Len(t, m.Data().Tokens, 2)
|
||||
assert.Equal(t, "foo", m.Data().Tokens[0])
|
||||
assert.Equal(t, "bar", m.Data().Tokens[1])
|
||||
|
||||
// t.Logf("Session: %#v", m)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSession_SetData(t *testing.T) {
|
||||
t.Run("Nil", func(t *testing.T) {
|
||||
m := NewSession(time.Hour*24, time.Hour*6)
|
||||
m := NewSession(UnixDay, UnixHour*6)
|
||||
|
||||
assert.NotNil(t, m)
|
||||
|
||||
|
@ -66,3 +63,98 @@ func TestSession_SetData(t *testing.T) {
|
|||
assert.Equal(t, sess.ID, m.ID)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSession_TimedOut(t *testing.T) {
|
||||
t.Run("NewSession", func(t *testing.T) {
|
||||
m := NewSession(UnixDay, UnixHour)
|
||||
assert.False(t, m.TimeoutAt().IsZero())
|
||||
assert.Equal(t, m.ExpiresAt(), m.TimeoutAt())
|
||||
assert.False(t, m.TimedOut())
|
||||
})
|
||||
t.Run("NoExpiration", func(t *testing.T) {
|
||||
m := NewSession(0, UnixHour)
|
||||
t.Logf("Timeout: %s, Expiration: %s", m.TimeoutAt().String(), m.ExpiresAt())
|
||||
assert.True(t, m.TimeoutAt().IsZero())
|
||||
assert.Equal(t, m.ExpiresAt(), m.TimeoutAt())
|
||||
assert.False(t, m.TimedOut())
|
||||
assert.True(t, m.ExpiresAt().IsZero())
|
||||
})
|
||||
t.Run("NoTimeout", func(t *testing.T) {
|
||||
m := NewSession(UnixDay, 0)
|
||||
t.Logf("Timeout: %s, Expiration: %s", m.TimeoutAt().String(), m.ExpiresAt())
|
||||
assert.False(t, m.TimeoutAt().IsZero())
|
||||
assert.Equal(t, m.ExpiresAt(), m.TimeoutAt())
|
||||
assert.False(t, m.TimedOut())
|
||||
assert.False(t, m.ExpiresAt().IsZero())
|
||||
})
|
||||
t.Run("TimedOut", func(t *testing.T) {
|
||||
m := NewSession(UnixDay, UnixHour)
|
||||
utc := UnixTime()
|
||||
|
||||
m.LastActive = utc - (UnixHour + 1)
|
||||
|
||||
assert.False(t, m.TimeoutAt().IsZero())
|
||||
assert.True(t, m.TimedOut())
|
||||
})
|
||||
t.Run("NotTimedOut", func(t *testing.T) {
|
||||
m := NewSession(UnixDay, UnixHour)
|
||||
utc := UnixTime()
|
||||
|
||||
m.LastActive = utc - (UnixHour - 10)
|
||||
|
||||
assert.False(t, m.TimeoutAt().IsZero())
|
||||
assert.False(t, m.TimedOut())
|
||||
})
|
||||
}
|
||||
|
||||
func TestSession_Expired(t *testing.T) {
|
||||
t.Run("NewSession", func(t *testing.T) {
|
||||
m := NewSession(UnixDay, UnixHour)
|
||||
t.Logf("Timeout: %s, Expiration: %s", m.TimeoutAt().String(), m.ExpiresAt())
|
||||
assert.False(t, m.ExpiresAt().IsZero())
|
||||
assert.False(t, m.Expired())
|
||||
assert.False(t, m.TimeoutAt().IsZero())
|
||||
assert.False(t, m.TimedOut())
|
||||
})
|
||||
t.Run("NoExpiration", func(t *testing.T) {
|
||||
m := NewSession(0, 0)
|
||||
t.Logf("Timeout: %s, Expiration: %s", m.TimeoutAt().String(), m.ExpiresAt())
|
||||
assert.True(t, m.ExpiresAt().IsZero())
|
||||
assert.False(t, m.Expired())
|
||||
assert.True(t, m.TimeoutAt().IsZero())
|
||||
assert.False(t, m.TimedOut())
|
||||
})
|
||||
t.Run("NoExpiration", func(t *testing.T) {
|
||||
m := NewSession(0, 0)
|
||||
t.Logf("Timeout: %s, Expiration: %s", m.TimeoutAt().String(), m.ExpiresAt())
|
||||
assert.True(t, m.ExpiresAt().IsZero())
|
||||
assert.False(t, m.Expired())
|
||||
assert.True(t, m.TimeoutAt().IsZero())
|
||||
assert.False(t, m.TimedOut())
|
||||
})
|
||||
t.Run("Expired", func(t *testing.T) {
|
||||
m := NewSession(UnixDay, UnixHour)
|
||||
t.Logf("Timeout: %s, Expiration: %s", m.TimeoutAt().String(), m.ExpiresAt())
|
||||
utc := UnixTime()
|
||||
|
||||
m.SessExpires = utc - 10
|
||||
|
||||
assert.False(t, m.ExpiresAt().IsZero())
|
||||
assert.True(t, m.Expired())
|
||||
assert.False(t, m.TimeoutAt().IsZero())
|
||||
assert.True(t, m.TimedOut())
|
||||
assert.Equal(t, m.ExpiresAt(), m.TimeoutAt())
|
||||
})
|
||||
t.Run("NotExpired", func(t *testing.T) {
|
||||
m := NewSession(UnixDay, UnixHour)
|
||||
utc := UnixTime()
|
||||
|
||||
m.SessExpires = utc + 10
|
||||
|
||||
assert.False(t, m.ExpiresAt().IsZero())
|
||||
assert.False(t, m.Expired())
|
||||
assert.False(t, m.TimeoutAt().IsZero())
|
||||
assert.False(t, m.TimedOut())
|
||||
assert.Equal(t, m.ExpiresAt(), m.TimeoutAt())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,6 +1,17 @@
|
|||
package entity
|
||||
|
||||
import "github.com/jinzhu/gorm"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
)
|
||||
|
||||
// Set UTC as the default for created and updated timestamps.
|
||||
func init() {
|
||||
gorm.NowFunc = func() time.Time {
|
||||
return UTC()
|
||||
}
|
||||
}
|
||||
|
||||
// Db returns the default *gorm.DB connection.
|
||||
func Db() *gorm.DB {
|
||||
|
|
|
@ -1,14 +1,31 @@
|
|||
package entity
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Day specified as time.Duration to improve readability.
|
||||
const Day = time.Hour * 24
|
||||
|
||||
// UnixHour is one hour in UnixTime.
|
||||
const UnixHour int64 = 3600
|
||||
|
||||
// UnixDay is one day in UnixTime.
|
||||
const UnixDay = UnixHour * 24
|
||||
|
||||
// UnixWeek is one week in UnixTime.
|
||||
const UnixWeek = UnixDay * 7
|
||||
|
||||
// UTC returns the current Coordinated Universal Time (UTC).
|
||||
func UTC() time.Time {
|
||||
return time.Now().UTC()
|
||||
}
|
||||
|
||||
// UnixTime returns the current time in seconds since January 1, 1970 UTC.
|
||||
func UnixTime() int64 {
|
||||
return UTC().Unix()
|
||||
}
|
||||
|
||||
// TimeStamp returns the current timestamp in UTC rounded to seconds.
|
||||
func TimeStamp() time.Time {
|
||||
return UTC().Truncate(time.Second)
|
|
@ -3,8 +3,39 @@ package entity
|
|||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUTC(t *testing.T) {
|
||||
t.Run("Zone", func(t *testing.T) {
|
||||
utc := UTC()
|
||||
|
||||
if zone, offset := utc.Zone(); zone != time.UTC.String() {
|
||||
t.Error("should be utc")
|
||||
} else if offset != 0 {
|
||||
t.Error("offset should be 0")
|
||||
}
|
||||
})
|
||||
t.Run("Gorm", func(t *testing.T) {
|
||||
utc := UTC()
|
||||
utcGorm := gorm.NowFunc()
|
||||
|
||||
t.Logf("NOW: %s, %s", utc.String(), utcGorm.String())
|
||||
|
||||
assert.True(t, utcGorm.After(utc))
|
||||
|
||||
if zone, offset := utcGorm.Zone(); zone != time.UTC.String() {
|
||||
t.Error("gorm time should be utc")
|
||||
} else if offset != 0 {
|
||||
t.Error("gorm time offset should be 0")
|
||||
}
|
||||
|
||||
assert.InEpsilon(t, utc.Unix(), utcGorm.Unix(), 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTimeStamp(t *testing.T) {
|
||||
t.Run("UTC", func(t *testing.T) {
|
||||
if TimeStamp().Location() != time.UTC {
|
|
@ -20,7 +20,7 @@ func CancelAll() {
|
|||
FacesWorker.Cancel()
|
||||
}
|
||||
|
||||
// WorkersRunning checks if a worker is currently running.
|
||||
func WorkersRunning() bool {
|
||||
// IndexWorkersRunning checks if a worker is currently running.
|
||||
func IndexWorkersRunning() bool {
|
||||
return MainWorker.Running() || SyncWorker.Running() || ShareWorker.Running() || MetaWorker.Running() || FacesWorker.Running()
|
||||
}
|
||||
|
|
|
@ -7,5 +7,5 @@ import (
|
|||
)
|
||||
|
||||
func TestWorkersBusy(t *testing.T) {
|
||||
assert.False(t, WorkersRunning())
|
||||
assert.False(t, IndexWorkersRunning())
|
||||
}
|
||||
|
|
|
@ -1,25 +1,56 @@
|
|||
package query
|
||||
|
||||
import (
|
||||
"time"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
||||
// Sessions returns stored sessions.
|
||||
func Sessions() (result entity.Sessions, err error) {
|
||||
err = Db().
|
||||
Table(entity.Session{}.TableName()).
|
||||
Select("*").
|
||||
Where("expires_at > ?", time.Now()).
|
||||
Scan(&result).Error
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Session finds an existing session by id.
|
||||
// Session finds an existing session by its id.
|
||||
func Session(id string) (result entity.Session, err error) {
|
||||
err = Db().Where("id = ?", id).First(&result).Error
|
||||
if l := len(id); l < 6 && l > 2048 {
|
||||
return result, fmt.Errorf("invalid session id")
|
||||
} else if rnd.IsRefID(id) {
|
||||
err = Db().Where("ref_id = ?", id).First(&result).Error
|
||||
} else {
|
||||
err = Db().Where("id LIKE ?", id).First(&result).Error
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Sessions finds user sessions and returns them.
|
||||
func Sessions(limit, offset int, sortOrder, search string) (result entity.Sessions, err error) {
|
||||
result = entity.Sessions{}
|
||||
stmt := Db()
|
||||
|
||||
search = strings.TrimSpace(search)
|
||||
|
||||
if search == "expired" {
|
||||
stmt = stmt.Where("sess_expires > 0 AND sess_expires < ?", entity.UnixTime())
|
||||
} else if rnd.IsSessionID(search) {
|
||||
stmt = stmt.Where("id = ?", search)
|
||||
} else if rnd.IsUID(search, entity.UserUID) {
|
||||
stmt = stmt.Where("user_uid = ?", search)
|
||||
} else if search != "" {
|
||||
stmt = stmt.Where("user_name LIKE ?", search+"%")
|
||||
}
|
||||
|
||||
if sortOrder == "" {
|
||||
sortOrder = "last_active, user_name"
|
||||
}
|
||||
|
||||
if limit > 0 {
|
||||
stmt = stmt.Limit(limit)
|
||||
|
||||
if offset > 0 {
|
||||
stmt = stmt.Offset(offset)
|
||||
}
|
||||
}
|
||||
|
||||
err = stmt.Order(sortOrder).Find(&result).Error
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
|
97
internal/query/sessions_test.go
Normal file
97
internal/query/sessions_test.go
Normal file
|
@ -0,0 +1,97 @@
|
|||
package query
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSession(t *testing.T) {
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
result, err := Session("")
|
||||
t.Logf("session: %#v", result)
|
||||
assert.Error(t, err)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, "", result.ID)
|
||||
assert.Equal(t, "", result.UserUID)
|
||||
assert.Equal(t, "", result.UserName)
|
||||
})
|
||||
t.Run("Alice", func(t *testing.T) {
|
||||
if result, err := Session("69be27ac5ca305b394046a83f6fda18167ca3d3f2dbe7ac0"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
t.Logf("session: %#v", result)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, "69be27ac5ca305b394046a83f6fda18167ca3d3f2dbe7ac0", result.ID)
|
||||
assert.Equal(t, "uqxetse3cy5eo9z2", result.UserUID)
|
||||
assert.Equal(t, "alice", result.UserName)
|
||||
}
|
||||
})
|
||||
t.Run("Bob", func(t *testing.T) {
|
||||
if result, err := Session("69be27ac5ca305b394046a83f6fda18167ca3d3f2dbe7ac1"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
t.Logf("session: %#v", result)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, "69be27ac5ca305b394046a83f6fda18167ca3d3f2dbe7ac1", result.ID)
|
||||
assert.Equal(t, "uqxc08w3d0ej2283", result.UserUID)
|
||||
assert.Equal(t, "bob", result.UserName)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestSessions(t *testing.T) {
|
||||
t.Run("Default", func(t *testing.T) {
|
||||
if results, err := Sessions(0, 0, "", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
assert.LessOrEqual(t, 2, len(results))
|
||||
//t.Logf("sessions: %#v", results)
|
||||
}
|
||||
})
|
||||
t.Run("Limit", func(t *testing.T) {
|
||||
if results, err := Sessions(1, 0, "", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
assert.LessOrEqual(t, 1, len(results))
|
||||
//t.Logf("sessions: %#v", results)
|
||||
}
|
||||
})
|
||||
t.Run("Offset", func(t *testing.T) {
|
||||
if results, err := Sessions(0, 1, "", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
assert.LessOrEqual(t, 2, len(results))
|
||||
//t.Logf("sessions: %#v", results)
|
||||
}
|
||||
})
|
||||
t.Run("SearchAlice", func(t *testing.T) {
|
||||
if results, err := Sessions(100, 0, "", "alice"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
t.Logf("sessions: %#v", results)
|
||||
assert.LessOrEqual(t, 1, len(results))
|
||||
if len(results) > 0 {
|
||||
assert.Equal(t, "69be27ac5ca305b394046a83f6fda18167ca3d3f2dbe7ac0", results[0].ID)
|
||||
assert.Equal(t, "uqxetse3cy5eo9z2", results[0].UserUID)
|
||||
assert.Equal(t, "alice", results[0].UserName)
|
||||
}
|
||||
}
|
||||
})
|
||||
t.Run("SortByID", func(t *testing.T) {
|
||||
if results, err := Sessions(100, 0, "id", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
assert.LessOrEqual(t, 2, len(results))
|
||||
//t.Logf("sessions: %#v", results)
|
||||
}
|
||||
})
|
||||
t.Run("SearchAliceSortByID", func(t *testing.T) {
|
||||
if results, err := Sessions(100, 0, "id", "alice"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
assert.LessOrEqual(t, 1, len(results))
|
||||
//t.Logf("sessions: %#v", results)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,7 +1,11 @@
|
|||
package query
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
// RegisteredUsers finds all registered users.
|
||||
|
@ -12,3 +16,39 @@ func RegisteredUsers() (result entity.Users) {
|
|||
|
||||
return result
|
||||
}
|
||||
|
||||
// Users finds users and returns them.
|
||||
func Users(limit, offset int, sortOrder, search string) (result entity.Users, err error) {
|
||||
result = entity.Users{}
|
||||
stmt := Db()
|
||||
|
||||
search = strings.TrimSpace(search)
|
||||
|
||||
if search == "all" {
|
||||
stmt = stmt.Where("sess_expires > 0 AND sess_expires < ?", entity.UnixTime())
|
||||
} else if id := txt.Int(search); id != 0 {
|
||||
stmt = stmt.Where("id = ?", id)
|
||||
} else if rnd.IsUID(search, entity.UserUID) {
|
||||
stmt = stmt.Where("user_uid = ?", search)
|
||||
} else if search != "" {
|
||||
stmt = stmt.Where("user_name LIKE ? OR user_email LIKE ? OR display_name LIKE ?", search+"%", search+"%", search+"%")
|
||||
} else {
|
||||
stmt = stmt.Where("id > 0")
|
||||
}
|
||||
|
||||
if sortOrder == "" {
|
||||
sortOrder = "id"
|
||||
}
|
||||
|
||||
if limit > 0 {
|
||||
stmt = stmt.Limit(limit)
|
||||
|
||||
if offset > 0 {
|
||||
stmt = stmt.Offset(offset)
|
||||
}
|
||||
}
|
||||
|
||||
err = stmt.Order(sortOrder).Find(&result).Error
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
|
|
@ -12,10 +12,59 @@ func TestRegisteredUsers(t *testing.T) {
|
|||
|
||||
for _, user := range users {
|
||||
t.Logf("user: %v, %s, %s, %s", user.ID, user.UserUID, user.Name(), user.DisplayName)
|
||||
assert.NotEmpty(t, user.UserUID)
|
||||
}
|
||||
|
||||
t.Logf("user count: %v", len(users))
|
||||
|
||||
assert.GreaterOrEqual(t, len(users), 3)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUsers(t *testing.T) {
|
||||
t.Run("Default", func(t *testing.T) {
|
||||
if results, err := Users(0, 0, "", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
assert.LessOrEqual(t, 2, len(results))
|
||||
}
|
||||
})
|
||||
t.Run("Limit", func(t *testing.T) {
|
||||
if results, err := Users(1, 0, "", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
assert.LessOrEqual(t, 1, len(results))
|
||||
}
|
||||
})
|
||||
t.Run("Offset", func(t *testing.T) {
|
||||
if results, err := Users(0, 1, "", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
assert.LessOrEqual(t, 2, len(results))
|
||||
}
|
||||
})
|
||||
t.Run("SearchAlice", func(t *testing.T) {
|
||||
if results, err := Users(100, 0, "", "alice"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
assert.LessOrEqual(t, 1, len(results))
|
||||
if len(results) > 0 {
|
||||
assert.Equal(t, 5, results[0].ID)
|
||||
assert.Equal(t, "uqxetse3cy5eo9z2", results[0].UserUID)
|
||||
assert.Equal(t, "alice", results[0].UserName)
|
||||
}
|
||||
}
|
||||
})
|
||||
t.Run("SortByID", func(t *testing.T) {
|
||||
if results, err := Users(100, 0, "id", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
assert.LessOrEqual(t, 2, len(results))
|
||||
}
|
||||
})
|
||||
t.Run("SearchAliceSortByID", func(t *testing.T) {
|
||||
if results, err := Users(100, 0, "id", "alice"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
assert.LessOrEqual(t, 1, len(results))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
45
internal/session/monitor.go
Normal file
45
internal/session/monitor.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package session
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize/english"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
)
|
||||
|
||||
var stop = make(chan bool, 1)
|
||||
|
||||
// MonitorAction deletes expired sessions.
|
||||
var MonitorAction = func() {
|
||||
if n := entity.DeleteExpiredSessions(); n > 0 {
|
||||
event.AuditInfo([]string{"deleted %s"}, english.Plural(n, "expired session", "expired sessions"))
|
||||
} else {
|
||||
event.AuditDebug([]string{"found no expired sessions"})
|
||||
}
|
||||
}
|
||||
|
||||
// Monitor starts a background worker that periodically deletes expired sessions.
|
||||
func Monitor(interval time.Duration) {
|
||||
ticker := time.NewTicker(interval)
|
||||
|
||||
MonitorAction()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
ticker.Stop()
|
||||
return
|
||||
case <-ticker.C:
|
||||
MonitorAction()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Shutdown shuts down the session watcher.
|
||||
func Shutdown() {
|
||||
stop <- true
|
||||
}
|
11
internal/session/monitor_test.go
Normal file
11
internal/session/monitor_test.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
package session
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestWatch(t *testing.T) {
|
||||
Monitor(time.Minute)
|
||||
Shutdown()
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
package session
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
)
|
||||
|
||||
// MaxAge is the maximum duration after which a session expires.
|
||||
var MaxAge = 168 * time.Hour * 24 * 7
|
||||
var Timeout = 168 * time.Hour * 24 * 3
|
||||
|
||||
// New creates a new session store with default values.
|
||||
func New(conf *config.Config) *Session {
|
||||
return &Session{MaxAge: MaxAge, Timeout: Timeout, conf: conf}
|
||||
}
|
|
@ -25,8 +25,6 @@ Additional information can be found in our Developer Guide:
|
|||
package session
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
gc "github.com/patrickmn/go-cache"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
|
@ -37,8 +35,11 @@ var log = event.Log
|
|||
|
||||
// Session represents a session store.
|
||||
type Session struct {
|
||||
conf *config.Config
|
||||
cache *gc.Cache
|
||||
MaxAge time.Duration
|
||||
Timeout time.Duration
|
||||
conf *config.Config
|
||||
cache *gc.Cache
|
||||
}
|
||||
|
||||
// New creates a new session store with default values.
|
||||
func New(conf *config.Config) *Session {
|
||||
return &Session{conf: conf}
|
||||
}
|
||||
|
|
|
@ -8,5 +8,5 @@ import (
|
|||
|
||||
// New creates a session with a context if it is specified.
|
||||
func (s *Session) New(c *gin.Context) (m *entity.Session) {
|
||||
return entity.NewSession(s.MaxAge, s.Timeout).SetContext(c)
|
||||
return entity.NewSession(s.conf.SessMaxAge(), s.conf.SessTimeout()).SetContext(c)
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ func (s *Session) Public() *entity.Session {
|
|||
return Public
|
||||
}
|
||||
|
||||
Public = entity.NewSession(s.MaxAge, s.Timeout)
|
||||
Public = entity.NewSession(0, 0)
|
||||
Public.ID = PublicID
|
||||
Public.AuthMethod = "public"
|
||||
Public.SetUser(&entity.Admin)
|
||||
|
|
|
@ -15,7 +15,7 @@ func (s *Session) Save(m *entity.Session) (*entity.Session, error) {
|
|||
}
|
||||
|
||||
// Save session.
|
||||
return m, m.Save()
|
||||
return m.UpdateLastActive(), m.Save()
|
||||
}
|
||||
|
||||
// Create initializes a new client session and returns it.
|
||||
|
@ -24,7 +24,9 @@ func (s *Session) Create(u *entity.User, c *gin.Context, data *entity.SessionDat
|
|||
m = s.New(c).SetUser(u).SetData(data)
|
||||
|
||||
// Create session.
|
||||
err = m.Create()
|
||||
if err = m.Create(); err != nil {
|
||||
m.UpdateLastActive()
|
||||
}
|
||||
|
||||
return m, err
|
||||
}
|
||||
|
|
|
@ -6,15 +6,17 @@ import (
|
|||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
log = logrus.StandardLogger()
|
||||
log.SetLevel(logrus.TraceLevel)
|
||||
event.AuditLog = log
|
||||
|
||||
db := entity.InitTestDb(os.Getenv("PHOTOPRISM_TEST_DRIVER"), os.Getenv("PHOTOPRISM_TEST_DSN"))
|
||||
defer db.Close()
|
||||
c := config.TestConfig()
|
||||
defer c.CloseDb()
|
||||
|
||||
code := m.Run()
|
||||
|
||||
|
|
|
@ -5,8 +5,6 @@ import (
|
|||
"path/filepath"
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
|
@ -17,6 +15,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/remote/webdav"
|
||||
"github.com/photoprism/photoprism/internal/search"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
)
|
||||
|
||||
// Share represents a share worker.
|
||||
|
@ -30,14 +29,14 @@ func NewShare(conf *config.Config) *Share {
|
|||
}
|
||||
|
||||
// logError logs an error message if err is not nil.
|
||||
func (worker *Share) logError(err error) {
|
||||
func (w *Share) logError(err error) {
|
||||
if err != nil {
|
||||
log.Errorf("share: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts the share worker.
|
||||
func (worker *Share) Start() (err error) {
|
||||
func (w *Share) Start() (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("share: %s (panic)\nstack: %s", r, debug.Stack())
|
||||
|
@ -71,7 +70,7 @@ func (worker *Share) Start() (err error) {
|
|||
files, err := query.FileShares(a.ID, entity.FileShareNew)
|
||||
|
||||
if err != nil {
|
||||
worker.logError(err)
|
||||
w.logError(err)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -110,16 +109,16 @@ func (worker *Share) Start() (err error) {
|
|||
srcFileName := photoprism.FileName(file.File.FileRoot, file.File.FileName)
|
||||
|
||||
if fs.ImageJPEG.Equal(file.File.FileType) && size.Width > 0 && size.Height > 0 {
|
||||
srcFileName, err = thumb.FromFile(srcFileName, file.File.FileHash, worker.conf.ThumbCachePath(), size.Width, size.Height, file.File.FileOrientation, size.Options...)
|
||||
srcFileName, err = thumb.FromFile(srcFileName, file.File.FileHash, w.conf.ThumbCachePath(), size.Width, size.Height, file.File.FileOrientation, size.Options...)
|
||||
|
||||
if err != nil {
|
||||
worker.logError(err)
|
||||
w.logError(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if err := client.Upload(srcFileName, file.RemoteName); err != nil {
|
||||
worker.logError(err)
|
||||
w.logError(err)
|
||||
file.Errors++
|
||||
file.Error = err.Error()
|
||||
} else {
|
||||
|
@ -138,7 +137,7 @@ func (worker *Share) Start() (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
worker.logError(entity.Db().Save(&file).Error)
|
||||
w.logError(entity.Db().Save(&file).Error)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -155,7 +154,7 @@ func (worker *Share) Start() (err error) {
|
|||
files, err := query.ExpiredFileShares(a)
|
||||
|
||||
if err != nil {
|
||||
worker.logError(err)
|
||||
w.logError(err)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -182,7 +181,7 @@ func (worker *Share) Start() (err error) {
|
|||
}
|
||||
|
||||
if err := entity.Db().Save(&file).Error; err != nil {
|
||||
worker.logError(err)
|
||||
w.logError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,21 +27,21 @@ func NewSync(conf *config.Config) *Sync {
|
|||
}
|
||||
|
||||
// logError logs an error message if err is not nil.
|
||||
func (worker *Sync) logError(err error) {
|
||||
func (w *Sync) logError(err error) {
|
||||
if err != nil {
|
||||
log.Errorf("sync: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// logWarn logs a warning message if err is not nil.
|
||||
func (worker *Sync) logWarn(err error) {
|
||||
func (w *Sync) logWarn(err error) {
|
||||
if err != nil {
|
||||
log.Warnf("sync: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts the sync worker.
|
||||
func (worker *Sync) Start() (err error) {
|
||||
func (w *Sync) Start() (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("sync: %s (panic)\nstack: %s", r, debug.Stack())
|
||||
|
@ -71,7 +71,7 @@ func (worker *Sync) Start() (err error) {
|
|||
a.AccSync = false
|
||||
|
||||
if err := entity.Db().Save(&a).Error; err != nil {
|
||||
worker.logError(err)
|
||||
w.logError(err)
|
||||
} else {
|
||||
log.Warnf("sync: disabled sync, %s failed more than %d times", a.AccName, a.RetryLimit)
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ func (worker *Sync) Start() (err error) {
|
|||
|
||||
switch a.SyncStatus {
|
||||
case entity.AccountSyncStatusRefresh:
|
||||
if complete, err := worker.refresh(a); err != nil {
|
||||
if complete, err := w.refresh(a); err != nil {
|
||||
accErrors++
|
||||
accError = err.Error()
|
||||
} else if complete {
|
||||
|
@ -106,7 +106,7 @@ func (worker *Sync) Start() (err error) {
|
|||
}
|
||||
}
|
||||
case entity.AccountSyncStatusDownload:
|
||||
if complete, err := worker.download(a); err != nil {
|
||||
if complete, err := w.download(a); err != nil {
|
||||
accErrors++
|
||||
accError = err.Error()
|
||||
syncStatus = entity.AccountSyncStatusRefresh
|
||||
|
@ -121,7 +121,7 @@ func (worker *Sync) Start() (err error) {
|
|||
}
|
||||
}
|
||||
case entity.AccountSyncStatusUpload:
|
||||
if complete, err := worker.upload(a); err != nil {
|
||||
if complete, err := w.upload(a); err != nil {
|
||||
accErrors++
|
||||
accError = err.Error()
|
||||
syncStatus = entity.AccountSyncStatusRefresh
|
||||
|
@ -149,7 +149,7 @@ func (worker *Sync) Start() (err error) {
|
|||
"AccErrors": accErrors,
|
||||
"SyncStatus": syncStatus,
|
||||
"SyncDate": syncDate}); err != nil {
|
||||
worker.logError(err)
|
||||
w.logError(err)
|
||||
} else if synced {
|
||||
event.Publish("sync.synced", event.Data{"account": a})
|
||||
}
|
||||
|
|
|
@ -17,12 +17,12 @@ import (
|
|||
type Downloads map[string][]entity.FileSync
|
||||
|
||||
// downloadPath returns a temporary download path.
|
||||
func (worker *Sync) downloadPath() string {
|
||||
return worker.conf.TempPath() + "/sync"
|
||||
func (w *Sync) downloadPath() string {
|
||||
return w.conf.TempPath() + "/sync"
|
||||
}
|
||||
|
||||
// relatedDownloads returns files to be downloaded grouped by prefix.
|
||||
func (worker *Sync) relatedDownloads(a entity.Account) (result Downloads, err error) {
|
||||
func (w *Sync) relatedDownloads(a entity.Account) (result Downloads, err error) {
|
||||
result = make(Downloads)
|
||||
maxResults := 1000
|
||||
|
||||
|
@ -35,7 +35,7 @@ func (worker *Sync) relatedDownloads(a entity.Account) (result Downloads, err er
|
|||
|
||||
// Group results by directory and base name
|
||||
for i, file := range files {
|
||||
k := fs.AbsPrefix(file.RemoteName, worker.conf.Settings().StackSequences())
|
||||
k := fs.AbsPrefix(file.RemoteName, w.conf.Settings().StackSequences())
|
||||
|
||||
result[k] = append(result[k], file)
|
||||
|
||||
|
@ -49,7 +49,7 @@ func (worker *Sync) relatedDownloads(a entity.Account) (result Downloads, err er
|
|||
}
|
||||
|
||||
// Downloads remote files in batches and imports / indexes them
|
||||
func (worker *Sync) download(a entity.Account) (complete bool, err error) {
|
||||
func (w *Sync) download(a entity.Account) (complete bool, err error) {
|
||||
// Set up index worker
|
||||
indexJobs := make(chan photoprism.IndexJob)
|
||||
|
||||
|
@ -61,10 +61,10 @@ func (worker *Sync) download(a entity.Account) (complete bool, err error) {
|
|||
go photoprism.ImportWorker(importJobs)
|
||||
defer close(importJobs)
|
||||
|
||||
relatedFiles, err := worker.relatedDownloads(a)
|
||||
relatedFiles, err := w.relatedDownloads(a)
|
||||
|
||||
if err != nil {
|
||||
worker.logError(err)
|
||||
w.logError(err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
|
@ -81,9 +81,9 @@ func (worker *Sync) download(a entity.Account) (complete bool, err error) {
|
|||
var baseDir string
|
||||
|
||||
if a.SyncFilenames {
|
||||
baseDir = worker.conf.OriginalsPath()
|
||||
baseDir = w.conf.OriginalsPath()
|
||||
} else {
|
||||
baseDir = fmt.Sprintf("%s/%d", worker.downloadPath(), a.ID)
|
||||
baseDir = fmt.Sprintf("%s/%d", w.downloadPath(), a.ID)
|
||||
}
|
||||
|
||||
done := make(map[string]bool)
|
||||
|
@ -124,7 +124,7 @@ func (worker *Sync) download(a entity.Account) (complete bool, err error) {
|
|||
}
|
||||
|
||||
if err := entity.Db().Save(&file).Error; err != nil {
|
||||
worker.logError(err)
|
||||
w.logError(err)
|
||||
} else {
|
||||
files[i] = file
|
||||
}
|
||||
|
@ -141,10 +141,10 @@ func (worker *Sync) download(a entity.Account) (complete bool, err error) {
|
|||
continue
|
||||
}
|
||||
|
||||
related, err := mf.RelatedFiles(worker.conf.Settings().StackSequences())
|
||||
related, err := mf.RelatedFiles(w.conf.Settings().StackSequences())
|
||||
|
||||
if err != nil {
|
||||
worker.logWarn(err)
|
||||
w.logWarn(err)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -176,7 +176,7 @@ func (worker *Sync) download(a entity.Account) (complete bool, err error) {
|
|||
FileName: mf.FileName(),
|
||||
Related: related,
|
||||
IndexOpt: photoprism.IndexOptionsAll(),
|
||||
ImportOpt: photoprism.ImportOptionsMove(baseDir, worker.conf.ImportDest()),
|
||||
ImportOpt: photoprism.ImportOptionsMove(baseDir, w.conf.ImportDest()),
|
||||
Imp: service.Import(),
|
||||
}
|
||||
}
|
||||
|
@ -186,10 +186,10 @@ func (worker *Sync) download(a entity.Account) (complete bool, err error) {
|
|||
// Any files downloaded?
|
||||
if len(done) > 0 {
|
||||
// Update precalculated photo and file counts.
|
||||
worker.logWarn(entity.UpdateCounts())
|
||||
w.logWarn(entity.UpdateCounts())
|
||||
|
||||
// Update album, subject, and label cover thumbs.
|
||||
worker.logWarn(query.UpdateCovers())
|
||||
w.logWarn(query.UpdateCovers())
|
||||
}
|
||||
|
||||
return false, nil
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
)
|
||||
|
||||
// Updates the local list of remote files so that they can be downloaded in batches
|
||||
func (worker *Sync) refresh(a entity.Account) (complete bool, err error) {
|
||||
func (w *Sync) refresh(a entity.Account) (complete bool, err error) {
|
||||
if a.AccType != remote.ServiceWebDAV {
|
||||
return false, nil
|
||||
}
|
||||
|
@ -67,11 +67,11 @@ func (worker *Sync) refresh(a entity.Account) (complete bool, err error) {
|
|||
}
|
||||
|
||||
if f.Status == entity.FileSyncIgnore && a.SyncRaw && (content == media.Raw || content == media.Video) {
|
||||
worker.logError(f.Update("Status", entity.FileSyncNew))
|
||||
w.logError(f.Update("Status", entity.FileSyncNew))
|
||||
}
|
||||
|
||||
if f.Status == entity.FileSyncDownloaded && !f.RemoteDate.Equal(file.Date) {
|
||||
worker.logError(f.Updates(map[string]interface{}{
|
||||
w.logError(f.Updates(map[string]interface{}{
|
||||
"Status": entity.FileSyncNew,
|
||||
"RemoteDate": file.Date,
|
||||
"RemoteSize": file.Size,
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
)
|
||||
|
||||
// Uploads local files to a remote account
|
||||
func (worker *Sync) upload(a entity.Account) (complete bool, err error) {
|
||||
func (w *Sync) upload(a entity.Account) (complete bool, err error) {
|
||||
maxResults := 250
|
||||
|
||||
// Get upload file list from database
|
||||
|
@ -51,7 +51,7 @@ func (worker *Sync) upload(a entity.Account) (complete bool, err error) {
|
|||
}
|
||||
|
||||
if err := client.Upload(fileName, remoteName); err != nil {
|
||||
worker.logError(err)
|
||||
w.logError(err)
|
||||
continue // try again next time
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,7 @@ func (worker *Sync) upload(a entity.Account) (complete bool, err error) {
|
|||
return false, nil
|
||||
}
|
||||
|
||||
worker.logError(entity.Db().Save(&fileSync).Error)
|
||||
w.logError(entity.Db().Save(&fileSync).Error)
|
||||
}
|
||||
|
||||
return false, nil
|
||||
|
|
|
@ -59,9 +59,9 @@ func Start(conf *config.Config) {
|
|||
mutex.SyncWorker.Cancel()
|
||||
return
|
||||
case <-ticker.C:
|
||||
StartMeta(conf)
|
||||
StartShare(conf)
|
||||
StartSync(conf)
|
||||
RunMeta(conf)
|
||||
RunShare(conf)
|
||||
RunSync(conf)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
@ -72,9 +72,9 @@ func Stop() {
|
|||
stop <- true
|
||||
}
|
||||
|
||||
// StartMeta runs the metadata worker once.
|
||||
func StartMeta(conf *config.Config) {
|
||||
if !mutex.WorkersRunning() {
|
||||
// RunMeta runs the metadata worker once.
|
||||
func RunMeta(conf *config.Config) {
|
||||
if !mutex.IndexWorkersRunning() {
|
||||
go func() {
|
||||
worker := NewMeta(conf)
|
||||
|
||||
|
@ -88,8 +88,8 @@ func StartMeta(conf *config.Config) {
|
|||
}
|
||||
}
|
||||
|
||||
// StartShare runs the share worker once.
|
||||
func StartShare(conf *config.Config) {
|
||||
// RunShare runs the share worker once.
|
||||
func RunShare(conf *config.Config) {
|
||||
if !mutex.ShareWorker.Running() {
|
||||
go func() {
|
||||
worker := NewShare(conf)
|
||||
|
@ -100,8 +100,8 @@ func StartShare(conf *config.Config) {
|
|||
}
|
||||
}
|
||||
|
||||
// StartSync runs the sync worker once.
|
||||
func StartSync(conf *config.Config) {
|
||||
// RunSync runs the sync worker once.
|
||||
func RunSync(conf *config.Config) {
|
||||
if !mutex.SyncWorker.Running() {
|
||||
go func() {
|
||||
worker := NewSync(conf)
|
||||
|
|
|
@ -7,11 +7,13 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
log = logrus.StandardLogger()
|
||||
log.SetLevel(logrus.TraceLevel)
|
||||
event.AuditLog = log
|
||||
|
||||
c := config.TestConfig()
|
||||
defer c.CloseDb()
|
||||
|
|
Loading…
Reference in a new issue