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/form"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/fs"
"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*
func StartImport(router *gin.RouterGroup, conf *config.Config) {
router.POST("/import/*path", func(c *gin.Context) {
@ -64,7 +51,7 @@ func StartImport(router *gin.RouterGroup, conf *config.Config) {
path = filepath.Clean(path)
initImport(conf)
imp := service.Import()
var opt photoprism.ImportOptions
@ -105,7 +92,7 @@ func CancelImport(router *gin.RouterGroup, conf *config.Config) {
return
}
initImport(conf)
imp := service.Import()
imp.Cancel()

View file

@ -7,38 +7,14 @@ import (
"time"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/classify"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/nsfw"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service"
"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
func StartIndexing(router *gin.RouterGroup, conf *config.Config) {
router.POST("/index", func(c *gin.Context) {
@ -67,7 +43,7 @@ func StartIndexing(router *gin.RouterGroup, conf *config.Config) {
}
if f.ConvertRaw && !conf.ReadOnly() {
convert := photoprism.NewConvert(conf)
convert := service.Convert()
if err := convert.Start(conf.OriginalsPath()); err != nil {
cancel(err)
@ -76,7 +52,7 @@ func StartIndexing(router *gin.RouterGroup, conf *config.Config) {
}
if f.CreateThumbs {
rs := photoprism.NewResample(conf)
rs := service.Resample()
if err := rs.Start(false); err != nil {
cancel(err)
@ -84,7 +60,7 @@ func StartIndexing(router *gin.RouterGroup, conf *config.Config) {
}
}
initIndex(conf)
ind := service.Index()
if f.SkipUnchanged {
ind.Start(photoprism.IndexOptionsNone())
@ -110,7 +86,7 @@ func CancelIndexing(router *gin.RouterGroup, conf *config.Config) {
return
}
initIndex(conf)
ind := service.Index()
ind.Cancel()

View file

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

View file

@ -4,7 +4,7 @@ import (
"time"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service"
"github.com/urfave/cli"
)
@ -20,6 +20,7 @@ func convertAction(ctx *cli.Context) error {
start := time.Now()
conf := config.NewConfig(ctx)
service.SetConfig(conf)
if conf.ReadOnly() {
return config.ErrReadOnly
@ -31,9 +32,11 @@ func convertAction(ctx *cli.Context) error {
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)

View file

@ -7,10 +7,9 @@ import (
"strings"
"time"
"github.com/photoprism/photoprism/internal/classify"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/nsfw"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service"
"github.com/urfave/cli"
)
@ -27,6 +26,7 @@ func copyAction(ctx *cli.Context) error {
start := time.Now()
conf := config.NewConfig(ctx)
service.SetConfig(conf)
// very if copy directory exist and is writable
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())
tensorFlow := classify.New(conf.ResourcesPath(), conf.TensorFlowDisabled())
nsfwDetector := nsfw.New(conf.NSFWModelPath())
ind := photoprism.NewIndex(conf, tensorFlow, nsfwDetector)
convert := photoprism.NewConvert(conf)
imp := photoprism.NewImport(conf, ind, convert)
imp := service.Import()
opt := photoprism.ImportOptionsCopy(sourcePath)
imp.Start(opt)

View file

@ -7,10 +7,9 @@ import (
"strings"
"time"
"github.com/photoprism/photoprism/internal/classify"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/nsfw"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service"
"github.com/urfave/cli"
)
@ -27,6 +26,7 @@ func importAction(ctx *cli.Context) error {
start := time.Now()
conf := config.NewConfig(ctx)
service.SetConfig(conf)
// very if copy directory exist and is writable
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())
tensorFlow := classify.New(conf.ResourcesPath(), conf.TensorFlowDisabled())
nsfwDetector := nsfw.New(conf.NSFWModelPath())
ind := photoprism.NewIndex(conf, tensorFlow, nsfwDetector)
convert := photoprism.NewConvert(conf)
imp := photoprism.NewImport(conf, ind, convert)
imp := service.Import()
opt := photoprism.ImportOptionsMove(sourcePath)
imp.Start(opt)

View file

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

View file

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

View file

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

View file

@ -7,8 +7,8 @@ import (
"github.com/jinzhu/gorm"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/internal/service/webdav"
"github.com/photoprism/photoprism/internal/remote"
"github.com/photoprism/photoprism/internal/remote/webdav"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/ulule/deepcopier"
)
@ -73,7 +73,7 @@ func (m *Account) Save(form form.Account, db *gorm.DB) error {
return err
}
if m.AccType != string(service.TypeWebDAV) {
if m.AccType != string(remote.ServiceWebDAV) {
// TODO: Only WebDAV supported at the moment
m.AccShare = 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.
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)
result, err = c.Directories("/", true)
}

View file

@ -1,7 +1,7 @@
package form
import (
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/internal/remote"
"github.com/ulule/deepcopier"
)
@ -38,7 +38,7 @@ func NewAccount(m interface{}) (f Account, err 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 {
return err

View file

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

View file

@ -10,8 +10,8 @@ import (
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/mutex"
"github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/internal/service/webdav"
"github.com/photoprism/photoprism/internal/remote"
"github.com/photoprism/photoprism/internal/remote/webdav"
)
// Sync represents a sync worker.
@ -43,7 +43,7 @@ func (s *Sync) Start() (err error) {
accounts, err := q.Accounts(f)
for _, a := range accounts {
if a.AccType != service.TypeWebDAV {
if a.AccType != remote.ServiceWebDAV {
continue
}
@ -112,7 +112,7 @@ func (s *Sync) Start() (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
}
@ -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) {
// TODO: Not implemented yet
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 (
"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
import (
"errors"
"net/http"
"net/url"
"strings"
"github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/pkg/txt"
"github.com/photoprism/photoprism/internal/classify"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/nsfw"
"github.com/photoprism/photoprism/internal/photoprism"
)
var log = event.Log
var client = &http.Client{}
var conf *config.Config
const (
TypeWeb = "web"
TypeWebDAV = "webdav"
TypeFacebook = "facebook"
TypeTwitter = "twitter"
TypeFlickr = "flickr"
TypeInstagram = "instagram"
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
var services struct {
Import *photoprism.Import
Index *photoprism.Index
Nsfw *nsfw.Detector
Convert *photoprism.Convert
Resample *photoprism.Resample
Classify *classify.TensorFlow
}
type Heuristic struct {
ServiceType string
Domains []string
Paths []string
Method string
func SetConfig(c *config.Config) {
if c == nil {
panic("config is nil")
}
conf = c
}
var Heuristics = []Heuristic{
{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 {
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")
func Config() *config.Config {
if conf == nil {
panic("config is nil")
}
return conf
}