Backend: Refactor package structure

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer
2020-04-05 22:26:53 +02:00
parent 1a3966e798
commit aa220a06fe
25 changed files with 360 additions and 258 deletions

View File

@@ -13,24 +13,11 @@ import (
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/txt" "github.com/photoprism/photoprism/pkg/txt"
) )
var imp *photoprism.Import
func initImport(conf *config.Config) {
if imp != nil {
return
}
initIndex(conf)
convert := photoprism.NewConvert(conf)
imp = photoprism.NewImport(conf, ind, convert)
}
// POST /api/v1/import* // POST /api/v1/import*
func StartImport(router *gin.RouterGroup, conf *config.Config) { func StartImport(router *gin.RouterGroup, conf *config.Config) {
router.POST("/import/*path", func(c *gin.Context) { router.POST("/import/*path", func(c *gin.Context) {
@@ -64,7 +51,7 @@ func StartImport(router *gin.RouterGroup, conf *config.Config) {
path = filepath.Clean(path) path = filepath.Clean(path)
initImport(conf) imp := service.Import()
var opt photoprism.ImportOptions var opt photoprism.ImportOptions
@@ -105,7 +92,7 @@ func CancelImport(router *gin.RouterGroup, conf *config.Config) {
return return
} }
initImport(conf) imp := service.Import()
imp.Cancel() imp.Cancel()

View File

@@ -7,38 +7,14 @@ import (
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/classify"
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/nsfw"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/txt" "github.com/photoprism/photoprism/pkg/txt"
) )
var ind *photoprism.Index
var nd *nsfw.Detector
func initIndex(conf *config.Config) {
if ind != nil {
return
}
initNsfwDetector(conf)
tf := classify.New(conf.ResourcesPath(), conf.TensorFlowDisabled())
ind = photoprism.NewIndex(conf, tf, nd)
}
func initNsfwDetector(conf *config.Config) {
if nd != nil {
return
}
nd = nsfw.New(conf.NSFWModelPath())
}
// POST /api/v1/index // POST /api/v1/index
func StartIndexing(router *gin.RouterGroup, conf *config.Config) { func StartIndexing(router *gin.RouterGroup, conf *config.Config) {
router.POST("/index", func(c *gin.Context) { router.POST("/index", func(c *gin.Context) {
@@ -67,7 +43,7 @@ func StartIndexing(router *gin.RouterGroup, conf *config.Config) {
} }
if f.ConvertRaw && !conf.ReadOnly() { if f.ConvertRaw && !conf.ReadOnly() {
convert := photoprism.NewConvert(conf) convert := service.Convert()
if err := convert.Start(conf.OriginalsPath()); err != nil { if err := convert.Start(conf.OriginalsPath()); err != nil {
cancel(err) cancel(err)
@@ -76,7 +52,7 @@ func StartIndexing(router *gin.RouterGroup, conf *config.Config) {
} }
if f.CreateThumbs { if f.CreateThumbs {
rs := photoprism.NewResample(conf) rs := service.Resample()
if err := rs.Start(false); err != nil { if err := rs.Start(false); err != nil {
cancel(err) cancel(err)
@@ -84,7 +60,7 @@ func StartIndexing(router *gin.RouterGroup, conf *config.Config) {
} }
} }
initIndex(conf) ind := service.Index()
if f.SkipUnchanged { if f.SkipUnchanged {
ind.Start(photoprism.IndexOptionsNone()) ind.Start(photoprism.IndexOptionsNone())
@@ -110,7 +86,7 @@ func CancelIndexing(router *gin.RouterGroup, conf *config.Config) {
return return
} }
initIndex(conf) ind := service.Index()
ind.Cancel() ind.Cancel()

View File

@@ -10,6 +10,7 @@ import (
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/txt" "github.com/photoprism/photoprism/pkg/txt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -65,7 +66,7 @@ func Upload(router *gin.RouterGroup, conf *config.Config) {
} }
if !conf.UploadNSFW() { if !conf.UploadNSFW() {
initNsfwDetector(conf) nd := service.NsfwDetector()
containsNSFW := false containsNSFW := false

View File

@@ -4,7 +4,7 @@ import (
"time" "time"
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/service"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@@ -20,6 +20,7 @@ func convertAction(ctx *cli.Context) error {
start := time.Now() start := time.Now()
conf := config.NewConfig(ctx) conf := config.NewConfig(ctx)
service.SetConfig(conf)
if conf.ReadOnly() { if conf.ReadOnly() {
return config.ErrReadOnly return config.ErrReadOnly
@@ -31,9 +32,11 @@ func convertAction(ctx *cli.Context) error {
log.Infof("converting RAW images in %s to JPEG", conf.OriginalsPath()) log.Infof("converting RAW images in %s to JPEG", conf.OriginalsPath())
convert := photoprism.NewConvert(conf) convert := service.Convert()
convert.Start(conf.OriginalsPath()) if err := convert.Start(conf.OriginalsPath()); err != nil {
log.Error(err)
}
elapsed := time.Since(start) elapsed := time.Since(start)

View File

@@ -7,10 +7,9 @@ import (
"strings" "strings"
"time" "time"
"github.com/photoprism/photoprism/internal/classify"
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/nsfw"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@@ -27,6 +26,7 @@ func copyAction(ctx *cli.Context) error {
start := time.Now() start := time.Now()
conf := config.NewConfig(ctx) conf := config.NewConfig(ctx)
service.SetConfig(conf)
// very if copy directory exist and is writable // very if copy directory exist and is writable
if conf.ReadOnly() { if conf.ReadOnly() {
@@ -66,14 +66,7 @@ func copyAction(ctx *cli.Context) error {
log.Infof("copying media files from %s to %s", sourcePath, conf.OriginalsPath()) log.Infof("copying media files from %s to %s", sourcePath, conf.OriginalsPath())
tensorFlow := classify.New(conf.ResourcesPath(), conf.TensorFlowDisabled()) imp := service.Import()
nsfwDetector := nsfw.New(conf.NSFWModelPath())
ind := photoprism.NewIndex(conf, tensorFlow, nsfwDetector)
convert := photoprism.NewConvert(conf)
imp := photoprism.NewImport(conf, ind, convert)
opt := photoprism.ImportOptionsCopy(sourcePath) opt := photoprism.ImportOptionsCopy(sourcePath)
imp.Start(opt) imp.Start(opt)

View File

@@ -7,10 +7,9 @@ import (
"strings" "strings"
"time" "time"
"github.com/photoprism/photoprism/internal/classify"
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/nsfw"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@@ -27,6 +26,7 @@ func importAction(ctx *cli.Context) error {
start := time.Now() start := time.Now()
conf := config.NewConfig(ctx) conf := config.NewConfig(ctx)
service.SetConfig(conf)
// very if copy directory exist and is writable // very if copy directory exist and is writable
if conf.ReadOnly() { if conf.ReadOnly() {
@@ -66,14 +66,7 @@ func importAction(ctx *cli.Context) error {
log.Infof("moving media files from %s to %s", sourcePath, conf.OriginalsPath()) log.Infof("moving media files from %s to %s", sourcePath, conf.OriginalsPath())
tensorFlow := classify.New(conf.ResourcesPath(), conf.TensorFlowDisabled()) imp := service.Import()
nsfwDetector := nsfw.New(conf.NSFWModelPath())
ind := photoprism.NewIndex(conf, tensorFlow, nsfwDetector)
convert := photoprism.NewConvert(conf)
imp := photoprism.NewImport(conf, ind, convert)
opt := photoprism.ImportOptionsMove(sourcePath) opt := photoprism.ImportOptionsMove(sourcePath)
imp.Start(opt) imp.Start(opt)

View File

@@ -4,10 +4,9 @@ import (
"context" "context"
"time" "time"
"github.com/photoprism/photoprism/internal/classify"
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/nsfw"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@@ -31,6 +30,7 @@ func indexAction(ctx *cli.Context) error {
start := time.Now() start := time.Now()
conf := config.NewConfig(ctx) conf := config.NewConfig(ctx)
service.SetConfig(conf)
if err := conf.CreateDirectories(); err != nil { if err := conf.CreateDirectories(); err != nil {
return err return err
@@ -49,9 +49,7 @@ func indexAction(ctx *cli.Context) error {
log.Infof("read-only mode enabled") log.Infof("read-only mode enabled")
} }
tf := classify.New(conf.ResourcesPath(), conf.TensorFlowDisabled()) ind := service.Index()
nd := nsfw.New(conf.NSFWModelPath())
ind := photoprism.NewIndex(conf, tf, nd)
var opt photoprism.IndexOptions var opt photoprism.IndexOptions

View File

@@ -12,6 +12,7 @@ import (
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/server" "github.com/photoprism/photoprism/internal/server"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/sevlyar/go-daemon" "github.com/sevlyar/go-daemon"
"github.com/urfave/cli" "github.com/urfave/cli"
@@ -41,6 +42,7 @@ var startFlags = []cli.Flag{
// startAction start the web server and initializes the daemon // startAction start the web server and initializes the daemon
func startAction(ctx *cli.Context) error { func startAction(ctx *cli.Context) error {
conf := config.NewConfig(ctx) conf := config.NewConfig(ctx)
service.SetConfig(conf)
if err := conf.CreateDirectories(); err != nil { if err := conf.CreateDirectories(); err != nil {
return err return err

View File

@@ -4,7 +4,7 @@ import (
"time" "time"
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/service"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@@ -26,6 +26,7 @@ func thumbsAction(ctx *cli.Context) error {
start := time.Now() start := time.Now()
conf := config.NewConfig(ctx) conf := config.NewConfig(ctx)
service.SetConfig(conf)
if err := conf.CreateDirectories(); err != nil { if err := conf.CreateDirectories(); err != nil {
return err return err
@@ -33,7 +34,7 @@ func thumbsAction(ctx *cli.Context) error {
log.Infof("creating thumbnails in \"%s\"", conf.ThumbnailsPath()) log.Infof("creating thumbnails in \"%s\"", conf.ThumbnailsPath())
rs := photoprism.NewResample(conf) rs := service.Resample()
if err := rs.Start(ctx.Bool("force")); err != nil { if err := rs.Start(ctx.Bool("force")); err != nil {
log.Error(err) log.Error(err)

View File

@@ -7,8 +7,8 @@ import (
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/remote"
"github.com/photoprism/photoprism/internal/service/webdav" "github.com/photoprism/photoprism/internal/remote/webdav"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/ulule/deepcopier" "github.com/ulule/deepcopier"
) )
@@ -73,7 +73,7 @@ func (m *Account) Save(form form.Account, db *gorm.DB) error {
return err return err
} }
if m.AccType != string(service.TypeWebDAV) { if m.AccType != string(remote.ServiceWebDAV) {
// TODO: Only WebDAV supported at the moment // TODO: Only WebDAV supported at the moment
m.AccShare = false m.AccShare = false
m.AccSync = false m.AccSync = false
@@ -97,7 +97,7 @@ func (m *Account) Delete(db *gorm.DB) error {
// Directories returns a list of directories or albums in an account. // Directories returns a list of directories or albums in an account.
func (m *Account) Directories() (result fs.FileInfos, err error) { func (m *Account) Directories() (result fs.FileInfos, err error) {
if m.AccType == service.TypeWebDAV { if m.AccType == remote.ServiceWebDAV {
c := webdav.New(m.AccURL, m.AccUser, m.AccPass) c := webdav.New(m.AccURL, m.AccUser, m.AccPass)
result, err = c.Directories("/", true) result, err = c.Directories("/", true)
} }

View File

@@ -1,7 +1,7 @@
package form package form
import ( import (
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/remote"
"github.com/ulule/deepcopier" "github.com/ulule/deepcopier"
) )
@@ -38,7 +38,7 @@ func NewAccount(m interface{}) (f Account, err error) {
} }
func (f *Account) ServiceDiscovery() error { func (f *Account) ServiceDiscovery() error {
acc, err := service.Discover(f.AccURL, f.AccUser, f.AccPass) acc, err := remote.Discover(f.AccURL, f.AccUser, f.AccPass)
if err != nil { if err != nil {
return err return err

View File

@@ -11,8 +11,8 @@ import (
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/mutex" "github.com/photoprism/photoprism/internal/mutex"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/remote"
"github.com/photoprism/photoprism/internal/service/webdav" "github.com/photoprism/photoprism/internal/remote/webdav"
"github.com/photoprism/photoprism/internal/thumb" "github.com/photoprism/photoprism/internal/thumb"
) )
@@ -49,7 +49,7 @@ func (s *Share) Start() (err error) {
return nil return nil
} }
if a.AccType != service.TypeWebDAV { if a.AccType != remote.ServiceWebDAV {
continue continue
} }
@@ -129,7 +129,7 @@ func (s *Share) Start() (err error) {
return nil return nil
} }
if a.AccType != service.TypeWebDAV { if a.AccType != remote.ServiceWebDAV {
continue continue
} }

View File

@@ -10,8 +10,8 @@ import (
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/mutex" "github.com/photoprism/photoprism/internal/mutex"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/remote"
"github.com/photoprism/photoprism/internal/service/webdav" "github.com/photoprism/photoprism/internal/remote/webdav"
) )
// Sync represents a sync worker. // Sync represents a sync worker.
@@ -43,7 +43,7 @@ func (s *Sync) Start() (err error) {
accounts, err := q.Accounts(f) accounts, err := q.Accounts(f)
for _, a := range accounts { for _, a := range accounts {
if a.AccType != service.TypeWebDAV { if a.AccType != remote.ServiceWebDAV {
continue continue
} }
@@ -112,7 +112,7 @@ func (s *Sync) Start() (err error) {
} }
func (s *Sync) getRemoteFiles(a entity.Account) (complete bool, err error) { func (s *Sync) getRemoteFiles(a entity.Account) (complete bool, err error) {
if a.AccType != service.TypeWebDAV { if a.AccType != remote.ServiceWebDAV {
return false, nil return false, nil
} }
@@ -221,5 +221,6 @@ func (s *Sync) download(a entity.Account) (complete bool, err error) {
} }
func (s *Sync) upload(a entity.Account) (complete bool, err error) { func (s *Sync) upload(a entity.Account) (complete bool, err error) {
// TODO: Not implemented yet
return false, nil return false, nil
} }

176
internal/remote/service.go Normal file
View File

@@ -0,0 +1,176 @@
/*
Package service implements a remote service abstraction.
Additional information can be found in our Developer Guide:
https://github.com/photoprism/photoprism/wiki
*/
package remote
import (
"errors"
"net/http"
"net/url"
"strings"
"github.com/photoprism/photoprism/pkg/txt"
)
var client = &http.Client{}
const (
ServiceWeb = "web"
ServiceWebDAV = "webdav"
ServiceFacebook = "facebook"
ServiceTwitter = "twitter"
ServiceFlickr = "flickr"
ServiceInstagram = "instagram"
ServiceEyeEm = "eyeem"
ServiceTelegram = "telegram"
ServiceWhatsApp = "whatsapp"
ServiceGPhotos = "gphotos"
ServiceGDrive = "gdrive"
ServiceOneDrive = "onedrive"
)
type Account struct {
AccName string
AccURL string
AccType string
AccKey string
AccUser string
AccPass string
}
type Heuristic struct {
ServiceType string
Domains []string
Paths []string
Method string
}
var Heuristics = []Heuristic{
{ServiceFacebook, []string{"facebook.com", "www.facebook.com"}, []string{}, "GET"},
{ServiceTwitter, []string{"twitter.com"}, []string{}, "GET"},
{ServiceFlickr, []string{"flickr.com", "www.flickr.com"}, []string{}, "GET"},
{ServiceInstagram, []string{"instagram.com", "www.instagram.com"}, []string{}, "GET"},
{ServiceEyeEm, []string{"eyeem.com", "www.eyeem.com"}, []string{}, "GET"},
{ServiceTelegram, []string{"web.telegram.org", "www.telegram.org", "telegram.org"}, []string{}, "GET"},
{ServiceWhatsApp, []string{"web.whatsapp.com", "www.whatsapp.com", "whatsapp.com"}, []string{}, "GET"},
{ServiceOneDrive, []string{"onedrive.live.com"}, []string{}, "GET"},
{ServiceGDrive, []string{"drive.google.com"}, []string{}, "GET"},
{ServiceGPhotos, []string{"photos.google.com"}, []string{}, "GET"},
{ServiceWebDAV, []string{}, []string{"/", "/webdav", "/remote.php/dav/files/{user}", "/remote.php/webdav", "/dav/files/{user}", "/servlet/webdav.infostore/"}, "PROPFIND"},
{ServiceWeb, []string{}, []string{}, "GET"},
}
func HttpOk(method, rawUrl string) bool {
req, err := http.NewRequest(method, rawUrl, nil)
if err != nil {
return false
}
if resp, err := client.Do(req); err != nil {
return false
} else if resp.StatusCode < 400 {
return true
}
return false
}
func (h Heuristic) MatchDomain(match string) bool {
if len(h.Domains) == 0 {
return true
}
for _, m := range h.Domains {
if m == match {
return true
}
}
return false
}
func (h Heuristic) Discover(rawUrl, user string) *url.URL {
u, err := url.Parse(rawUrl)
if err != nil {
return nil
}
if HttpOk(h.Method, u.String()) {
return u
}
for _, p := range h.Paths {
strings.Replace(p, "{user}", user, -1)
u.Path = p
if HttpOk(h.Method, u.String()) {
return u
}
}
return nil
}
func Discover(rawUrl, user, pass string) (result Account, err error) {
if rawUrl == "" {
return result, errors.New("service URL is empty")
}
u, err := url.Parse(rawUrl)
if err != nil {
return result, err
}
u.Host = strings.ToLower(u.Host)
result.AccUser = u.User.Username()
result.AccPass, _ = u.User.Password()
// Extract user info
if user != "" {
result.AccUser = user
}
if pass != "" {
result.AccPass = pass
}
if user != "" || pass != "" {
u.User = url.UserPassword(result.AccUser, result.AccPass)
}
// Set default scheme
if u.Scheme == "" {
u.Scheme = "https"
}
for _, h := range Heuristics {
if !h.MatchDomain(u.Host) {
continue
}
if serviceUrl := h.Discover(u.String(), result.AccUser); serviceUrl != nil {
serviceUrl.User = nil
if w := txt.Keywords(serviceUrl.Host); len(w) > 0 {
result.AccName = strings.Title(w[0])
} else {
result.AccName = serviceUrl.Host
}
result.AccType = h.ServiceType
result.AccURL = serviceUrl.String()
return result, nil
}
}
return result, errors.New("could not connect")
}

View File

@@ -1,4 +1,4 @@
package service package remote
import ( import (
"testing" "testing"

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -0,0 +1,19 @@
package service
import (
"sync"
"github.com/photoprism/photoprism/internal/classify"
)
var onceClassify sync.Once
func initClassify() {
services.Classify = classify.New(Config().ResourcesPath(), Config().TensorFlowDisabled())
}
func Classify() *classify.TensorFlow {
onceClassify.Do(initClassify)
return services.Classify
}

View File

@@ -0,0 +1,19 @@
package service
import (
"sync"
"github.com/photoprism/photoprism/internal/photoprism"
)
var onceConvert sync.Once
func initConvert() {
services.Convert = photoprism.NewConvert(Config())
}
func Convert() *photoprism.Convert {
onceConvert.Do(initConvert)
return services.Convert
}

View File

@@ -0,0 +1,19 @@
package service
import (
"sync"
"github.com/photoprism/photoprism/internal/photoprism"
)
var onceImport sync.Once
func initImport() {
services.Import = photoprism.NewImport(Config(), Index(), Convert())
}
func Import() *photoprism.Import {
onceImport.Do(initImport)
return services.Import
}

19
internal/service/index.go Normal file
View File

@@ -0,0 +1,19 @@
package service
import (
"sync"
"github.com/photoprism/photoprism/internal/photoprism"
)
var onceIndex sync.Once
func initIndex() {
services.Index = photoprism.NewIndex(Config(), Classify(), NsfwDetector())
}
func Index() *photoprism.Index {
onceIndex.Do(initIndex)
return services.Index
}

19
internal/service/nsfw.go Normal file
View File

@@ -0,0 +1,19 @@
package service
import (
"sync"
"github.com/photoprism/photoprism/internal/nsfw"
)
var onceNsfwDetector sync.Once
func initNsfwDetector() {
services.Nsfw = nsfw.New(conf.NSFWModelPath())
}
func NsfwDetector() *nsfw.Detector {
onceNsfwDetector.Do(initNsfwDetector)
return services.Nsfw
}

View File

@@ -0,0 +1,19 @@
package service
import (
"sync"
"github.com/photoprism/photoprism/internal/photoprism"
)
var onceResample sync.Once
func initResample() {
services.Resample = photoprism.NewResample(Config())
}
func Resample() *photoprism.Resample {
onceResample.Do(initResample)
return services.Resample
}

View File

@@ -1,178 +1,35 @@
/*
Package service implements a remote service abstraction.
Additional information can be found in our Developer Guide:
https://github.com/photoprism/photoprism/wiki
*/
package service package service
import ( import (
"errors" "github.com/photoprism/photoprism/internal/classify"
"net/http" "github.com/photoprism/photoprism/internal/config"
"net/url" "github.com/photoprism/photoprism/internal/nsfw"
"strings" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/pkg/txt"
) )
var log = event.Log var conf *config.Config
var client = &http.Client{}
const ( var services struct {
TypeWeb = "web" Import *photoprism.Import
TypeWebDAV = "webdav" Index *photoprism.Index
TypeFacebook = "facebook" Nsfw *nsfw.Detector
TypeTwitter = "twitter" Convert *photoprism.Convert
TypeFlickr = "flickr" Resample *photoprism.Resample
TypeInstagram = "instagram" Classify *classify.TensorFlow
TypeEyeEm = "eyeem"
TypeTelegram = "telegram"
TypeWhatsApp = "whatsapp"
TypeGooglePhotos = "gphotos"
TypeGoogleDrive = "gdrive"
TypeOneDrive = "onedrive"
)
type Account struct {
AccName string
AccURL string
AccType string
AccKey string
AccUser string
AccPass string
} }
type Heuristic struct { func SetConfig(c *config.Config) {
ServiceType string if c == nil {
Domains []string panic("config is nil")
Paths []string
Method string
} }
var Heuristics = []Heuristic{ conf = c
{TypeFacebook, []string{"facebook.com", "www.facebook.com"}, []string{}, "GET"},
{TypeTwitter, []string{"twitter.com"}, []string{}, "GET"},
{TypeFlickr, []string{"flickr.com", "www.flickr.com"}, []string{}, "GET"},
{TypeInstagram, []string{"instagram.com", "www.instagram.com"}, []string{}, "GET"},
{TypeEyeEm, []string{"eyeem.com", "www.eyeem.com"}, []string{}, "GET"},
{TypeTelegram, []string{"web.telegram.org", "www.telegram.org", "telegram.org"}, []string{}, "GET"},
{TypeWhatsApp, []string{"web.whatsapp.com", "www.whatsapp.com", "whatsapp.com"}, []string{}, "GET"},
{TypeOneDrive, []string{"onedrive.live.com"}, []string{}, "GET"},
{TypeGoogleDrive, []string{"drive.google.com"}, []string{}, "GET"},
{TypeGooglePhotos, []string{"photos.google.com"}, []string{}, "GET"},
{TypeWebDAV, []string{}, []string{"/", "/webdav", "/remote.php/dav/files/{user}", "/remote.php/webdav", "/dav/files/{user}", "/servlet/webdav.infostore/"}, "PROPFIND"},
{TypeWeb, []string{}, []string{}, "GET"},
} }
func HttpOk(method, rawUrl string) bool { func Config() *config.Config {
req, err := http.NewRequest(method, rawUrl, nil) if conf == nil {
panic("config is nil")
if err != nil {
return false
} }
if resp, err := client.Do(req); err != nil { return conf
return false
} else if resp.StatusCode < 400 {
return true
}
return false
}
func (h Heuristic) MatchDomain(match string) bool {
if len(h.Domains) == 0 {
return true
}
for _, m := range h.Domains {
if m == match {
return true
}
}
return false
}
func (h Heuristic) Discover(rawUrl, user string) *url.URL {
u, err := url.Parse(rawUrl)
if err != nil {
return nil
}
if HttpOk(h.Method, u.String()) {
return u
}
for _, p := range h.Paths {
strings.Replace(p, "{user}", user, -1)
u.Path = p
if HttpOk(h.Method, u.String()) {
return u
}
}
return nil
}
func Discover(rawUrl, user, pass string) (result Account, err error) {
if rawUrl == "" {
return result, errors.New("service URL is empty")
}
u, err := url.Parse(rawUrl)
if err != nil {
return result, err
}
u.Host = strings.ToLower(u.Host)
result.AccUser = u.User.Username()
result.AccPass, _ = u.User.Password()
// Extract user info
if user != "" {
result.AccUser = user
}
if pass != "" {
result.AccPass = pass
}
if user != "" || pass != "" {
u.User = url.UserPassword(result.AccUser, result.AccPass)
}
// Set default scheme
if u.Scheme == "" {
u.Scheme = "https"
}
for _, h := range Heuristics {
if !h.MatchDomain(u.Host) {
continue
}
if serviceUrl := h.Discover(u.String(), result.AccUser); serviceUrl != nil {
serviceUrl.User = nil
if w := txt.Keywords(serviceUrl.Host); len(w) > 0 {
result.AccName = strings.Title(w[0])
} else {
result.AccName = serviceUrl.Host
}
result.AccType = h.ServiceType
result.AccURL = serviceUrl.String()
return result, nil
}
}
return result, errors.New("could not connect")
} }