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