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 } {
113 v.SetMyselfIn(extRouter)
114 }
115
116 // processors
117 var (
118 fileScanner = scanner.NewFileScanner(mediaRepository, userRepository)
119 exifScanner = scanner.NewEXIFScanner(mediaRepository)
120 thumbnailScanner = scanner.NewThumbnailScanner(*cachePath, mediaRepository)
121 albumScanner = scanner.NewAlbumScanner(mediaRepository)
122 )
123
124 // worker
125 var (
126 serverWorker = worker.NewServerWorker(&http.Server{Handler: r, Addr: "0.0.0.0:8080"})
127 fileWorker = worker.NewWorkerFromChanProcessor[string](
128 fileScanner,
129 scheduler,
130 logrus.WithField("context", "file scanner"),
131 )
132 exifWorker = worker.NewWorkerFromBatchProcessor[*repository.Media](
133 exifScanner,
134 scheduler,
135 logrus.WithField("context", "exif scanner"),
136 )
137 thumbnailWorker = worker.NewWorkerFromBatchProcessor[*repository.Media](
138 thumbnailScanner,
139 scheduler,
140 logrus.WithField("context", "thumbnail scanner"),
141 )
142 albumWorker = worker.NewWorkerFromSerialProcessor[*repository.Media](
143 albumScanner,
144 scheduler,
145 logrus.WithField("context", "thumbnail scanner"),
146 )
147 )
148
149 pool := worker.NewWorkerPool()
150 pool.AddWorker("http server", serverWorker)
151 pool.AddWorker("exif scanner", exifWorker)
152 pool.AddWorker("file scanner", fileWorker)
153 pool.AddWorker("thumbnail scanner", thumbnailWorker)
154 pool.AddWorker("album scanner", albumWorker)
155
156 ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
157 defer stop()
158
159 pool.Start(ctx)
160}
161
162func OpenDatabase(dbType string, dbConn string) (gorm.Dialector, error) {
163 switch dbType {
164 case "sqlite":
165 return sqlite.Open(dbConn), nil
166 case "psql":
167 return postgres.Open(dbConn), nil
168 case "mysql":
169 return mysql.Open(dbConn), nil
170 default:
171 return nil, errors.New("No valid db type given")
172 }
173}