lens @ c51fb8cc8b850b4915e083d0dd2c30d79f8b632e

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