lens @ c9e5d242cbba5e3b9d3ddc4465a9a1367e44cd16

  1package main
  2
  3import (
  4	"context"
  5	"encoding/base64"
  6	"errors"
  7	"net/http"
  8	"os"
  9	"os/signal"
 10	"time"
 11
 12	"github.com/gorilla/mux"
 13	"github.com/sirupsen/logrus"
 14	flag "github.com/spf13/pflag"
 15	"gorm.io/driver/mysql"
 16	"gorm.io/driver/postgres"
 17	"gorm.io/driver/sqlite"
 18	"gorm.io/gorm"
 19
 20	"git.sr.ht/~gabrielgio/img/pkg/database/localfs"
 21	"git.sr.ht/~gabrielgio/img/pkg/database/repository"
 22	"git.sr.ht/~gabrielgio/img/pkg/database/sql"
 23	"git.sr.ht/~gabrielgio/img/pkg/ext"
 24	"git.sr.ht/~gabrielgio/img/pkg/service"
 25	"git.sr.ht/~gabrielgio/img/pkg/view"
 26	"git.sr.ht/~gabrielgio/img/pkg/worker"
 27	"git.sr.ht/~gabrielgio/img/pkg/worker/scanner"
 28	"git.sr.ht/~gabrielgio/img/static"
 29)
 30
 31func main() {
 32	var (
 33		key            = flag.String("aes-key", "", "AES key, either 16, 24, or 32 bytes string to select AES-128, AES-192, or AES-256")
 34		dbType         = flag.String("db-type", "sqlite", "Database to be used. Choose either mysql, psql or sqlite")
 35		dbCon          = flag.String("db-con", "main.db", "Database string connection for given database type. Ref: https://gorm.io/docs/connecting_to_the_database.html")
 36		logLevel       = flag.String("log-level", "error", "Log level: Choose either trace, debug, info, warning, error, fatal or panic")
 37		schedulerCount = flag.Uint("scheduler-count", 10, "How many workers are created to process media files")
 38		cachePath      = flag.String("cache-path", "", "Folder to store thumbnail image")
 39	)
 40
 41	flag.Parse()
 42
 43	l, err := logrus.ParseLevel(*logLevel)
 44	if err != nil {
 45		panic("failed to parse log level" + err.Error())
 46	}
 47	logger := logrus.New()
 48	logger.SetLevel(l)
 49
 50	d, err := OpenDatabase(*dbType, *dbCon)
 51	if err != nil {
 52		panic("failed to parse database strings" + err.Error())
 53	}
 54
 55	db, err := gorm.Open(d, &gorm.Config{
 56		Logger: ext.Wraplog(logger.WithField("context", "sql")),
 57	})
 58	if err != nil {
 59		panic("failed to connect database: " + err.Error())
 60	}
 61
 62	if err = sql.Migrate(db); err != nil {
 63		panic("failed to migrate database: " + err.Error())
 64	}
 65
 66	if *dbType == "sqlite" {
 67		*schedulerCount = 1
 68	}
 69
 70	baseKey, err := base64.StdEncoding.DecodeString(*key)
 71	if err != nil {
 72		panic("failed to decode key database: " + err.Error())
 73	}
 74
 75	r := mux.NewRouter()
 76	r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.FS(static.Static))))
 77
 78	// repository
 79	var (
 80		userRepository       = sql.NewUserRepository(db)
 81		settingsRepository   = sql.NewSettingsRespository(db)
 82		fileSystemRepository = localfs.NewFileSystemRepository()
 83		mediaRepository      = sql.NewMediaRepository(db)
 84	)
 85
 86	// middleware
 87	var (
 88		authMiddleware    = ext.NewAuthMiddleware(baseKey, logger.WithField("context", "auth"))
 89		logMiddleware     = ext.NewLogMiddleare(logger.WithField("context", "http"))
 90		initialMiddleware = ext.NewInitialSetupMiddleware(userRepository)
 91	)
 92
 93	extRouter := ext.NewRouter(r)
 94	extRouter.AddMiddleware(ext.HTML)
 95	extRouter.AddMiddleware(initialMiddleware.Check)
 96	extRouter.AddMiddleware(authMiddleware.LoggedIn)
 97	extRouter.AddMiddleware(logMiddleware.HTTP)
 98
 99	scheduler := worker.NewScheduler(*schedulerCount)
100
101	// controller
102	var (
103		userController       = service.NewAuthController(userRepository, userRepository, baseKey)
104		fileSystemController = service.NewFileSystemController(fileSystemRepository, userRepository)
105	)
106
107	// view
108	for _, v := range []view.View{
109		view.NewAuthView(userController),
110		view.NewFileSystemView(*fileSystemController, settingsRepository),
111		view.NewSettingsView(settingsRepository, userController),
112		view.NewMediaView(mediaRepository, userRepository, settingsRepository),
113		view.NewAlbumView(mediaRepository, userRepository, settingsRepository),
114	} {
115		v.SetMyselfIn(extRouter)
116	}
117
118	// processors
119	var (
120		fileScanner      = scanner.NewFileScanner(mediaRepository, userRepository)
121		exifScanner      = scanner.NewEXIFScanner(mediaRepository)
122		thumbnailScanner = scanner.NewThumbnailScanner(*cachePath, mediaRepository)
123		albumScanner     = scanner.NewAlbumScanner(mediaRepository)
124	)
125
126	// tasks
127	var (
128		serverTask = worker.NewServerTask(&http.Server{Handler: r, Addr: "0.0.0.0:8080"})
129		fileTask   = worker.NewTaskFromChanProcessor[string](
130			fileScanner,
131			scheduler,
132			logrus.WithField("context", "file scanner"),
133		)
134		exifTask = worker.NewTaskFromBatchProcessor[*repository.Media](
135			exifScanner,
136			scheduler,
137			logrus.WithField("context", "exif scanner"),
138		)
139		thumbnailTask = worker.NewTaskFromBatchProcessor[*repository.Media](
140			thumbnailScanner,
141			scheduler,
142			logrus.WithField("context", "thumbnail scanner"),
143		)
144		albumTask = worker.NewTaskFromSerialProcessor[*repository.Media](
145			albumScanner,
146			scheduler,
147			logrus.WithField("context", "thumbnail scanner"),
148		)
149	)
150
151	pool := worker.NewTaskPool()
152	pool.AddTask("http server", time.Minute, serverTask)
153	pool.AddTask("exif scanner", 15*time.Minute, exifTask)
154	pool.AddTask("file scanner", 2*time.Hour, fileTask)
155	pool.AddTask("thumbnail scanner", 15*time.Minute, thumbnailTask)
156	pool.AddTask("album scanner", 15*time.Minute, albumTask)
157
158	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
159	defer stop()
160
161	pool.Start(ctx)
162}
163
164func OpenDatabase(dbType string, dbConn string) (gorm.Dialector, error) {
165	switch dbType {
166	case "sqlite":
167		return sqlite.Open(dbConn), nil
168	case "psql":
169		return postgres.Open(dbConn), nil
170	case "mysql":
171		return mysql.Open(dbConn), nil
172	default:
173		return nil, errors.New("No valid db type given")
174	}
175}