1diff --git a/cmd/server/main.go b/cmd/server/main.go
2index c064d569c75c289e6dd9b3a436666431eb4b1a01..b81b2916115977c566ef4ae741e144368e9e933b 100644
3--- a/cmd/server/main.go
4+++ b/cmd/server/main.go
5@@ -24,6 +24,7 @@ "git.sr.ht/~gabrielgio/img/pkg/ext"
6 "git.sr.ht/~gabrielgio/img/pkg/service"
7 "git.sr.ht/~gabrielgio/img/pkg/view"
8 "git.sr.ht/~gabrielgio/img/pkg/worker"
9+ "git.sr.ht/~gabrielgio/img/pkg/worker/scanner"
10 )
11
12 func main() {
13@@ -34,9 +35,6 @@ dbCon = flag.String("db-con", "main.db", "Database string connection for given database type. Ref: https://gorm.io/docs/connecting_to_the_database.html")
14 logLevel = flag.String("log-level", "error", "Log level: Choose either trace, debug, info, warning, error, fatal or panic")
15 schedulerCount = flag.Uint("scheduler-count", 10, "How many workers are created to process media files")
16 cachePath = flag.String("cache-path", "", "Folder to store thumbnail image")
17-
18- // TODO: this will later be replaced by user specific root folder
19- root = flag.String("root", "", "root folder for the whole application. All the workers will use it as working directory")
20 )
21
22 flag.Parse()
23@@ -76,7 +74,7 @@ // repository
24 var (
25 userRepository = sql.NewUserRepository(db)
26 settingsRepository = sql.NewSettingsRespository(db)
27- fileSystemRepository = localfs.NewFileSystemRepository(*root)
28+ fileSystemRepository = localfs.NewFileSystemRepository()
29 mediaRepository = sql.NewMediaRepository(db)
30 )
31
32@@ -113,9 +111,9 @@ }
33
34 // processors
35 var (
36- fileScanner = worker.NewFileScanner(*root, mediaRepository)
37- exifScanner = worker.NewEXIFScanner(mediaRepository)
38- thumbnailScanner = worker.NewThumbnailScanner(*cachePath, mediaRepository)
39+ fileScanner = scanner.NewFileScanner(mediaRepository, userRepository)
40+ exifScanner = scanner.NewEXIFScanner(mediaRepository)
41+ thumbnailScanner = scanner.NewThumbnailScanner(*cachePath, mediaRepository)
42 )
43
44 // worker
45diff --git a/pkg/database/localfs/filesystem.go b/pkg/database/localfs/filesystem.go
46index c7c645817235c7c6b2f3fa2e5408bcc9e8caa63d..d516ce9934ae8e951b2e6c3a4c80b6deb646bb43 100644
47--- a/pkg/database/localfs/filesystem.go
48+++ b/pkg/database/localfs/filesystem.go
49@@ -11,10 +11,8 @@ type FileSystemRepository struct {
50 root string
51 }
52
53-func NewFileSystemRepository(root string) *FileSystemRepository {
54- return &FileSystemRepository{
55- root: root,
56- }
57+func NewFileSystemRepository() *FileSystemRepository {
58+ return &FileSystemRepository{}
59 }
60
61 func (self *FileSystemRepository) getFilesFromPath(filepath string) ([]fs.FileInfo, error) {
62diff --git a/pkg/worker/exif_scanner.go b/pkg/worker/scanner/exif_scanner.go
63rename from pkg/worker/exif_scanner.go
64rename to pkg/worker/scanner/exif_scanner.go
65index 5ea18104d9be7dec5448e46321bc603d8d142e94..47d717fc3d48eaf99945a504d86a76a9ef21955b 100644
66--- a/pkg/worker/exif_scanner.go
67+++ b/pkg/worker/scanner/exif_scanner.go
68@@ -1,4 +1,4 @@
69-package worker
70+package scanner
71
72 import (
73 "context"
74@@ -6,6 +6,7 @@
75 "git.sr.ht/~gabrielgio/img/pkg/coroutine"
76 "git.sr.ht/~gabrielgio/img/pkg/database/repository"
77 "git.sr.ht/~gabrielgio/img/pkg/fileop"
78+ "git.sr.ht/~gabrielgio/img/pkg/worker"
79 )
80
81 type (
82@@ -14,7 +15,7 @@ repository repository.MediaRepository
83 }
84 )
85
86-var _ BatchProcessor[*repository.Media] = &EXIFScanner{}
87+var _ worker.BatchProcessor[*repository.Media] = &EXIFScanner{}
88
89 func NewEXIFScanner(repository repository.MediaRepository) *EXIFScanner {
90 return &EXIFScanner{
91diff --git a/pkg/worker/file_scanner.go b/pkg/worker/file_scanner.go
92deleted file mode 100644
93index b4f907ab682473e1082d0694b690c5a25a75c3a2..0000000000000000000000000000000000000000
94--- a/pkg/worker/file_scanner.go
95+++ /dev/null
96@@ -1,83 +0,0 @@
97-package worker
98-
99-import (
100- "context"
101- "io/fs"
102- "mime"
103- "path/filepath"
104-
105- "git.sr.ht/~gabrielgio/img/pkg/database/repository"
106- "git.sr.ht/~gabrielgio/img/pkg/fileop"
107-)
108-
109-type (
110- FileScanner struct {
111- root string
112- repository repository.MediaRepository
113- }
114-)
115-
116-var _ ChanProcessor[string] = &FileScanner{}
117-
118-func NewFileScanner(root string, repository repository.MediaRepository) *FileScanner {
119- return &FileScanner{
120- root: root,
121- repository: repository,
122- }
123-}
124-
125-func (f *FileScanner) Query(ctx context.Context) (<-chan string, error) {
126- c := make(chan string)
127- go func() {
128- defer close(c)
129- _ = filepath.Walk(f.root, func(path string, info fs.FileInfo, err error) error {
130- select {
131- case <-ctx.Done():
132- return filepath.SkipAll
133- default:
134- }
135-
136- if info == nil {
137- return nil
138- }
139-
140- if info.IsDir() && filepath.Base(info.Name())[0] == '.' {
141- return filepath.SkipDir
142- }
143-
144- if info.IsDir() {
145- return nil
146- }
147-
148- c <- path
149- return nil
150- })
151- }()
152- return c, nil
153-}
154-
155-func (f *FileScanner) Process(ctx context.Context, path string) error {
156- mimetype := mime.TypeByExtension(filepath.Ext(path))
157- supported := fileop.IsMimeTypeSupported(mimetype)
158- if !supported {
159- return nil
160- }
161-
162- hash := fileop.GetHashFromPath(path)
163-
164- exists, err := f.repository.Exists(ctx, hash)
165- if err != nil {
166- return err
167- }
168-
169- if exists {
170- return nil
171- }
172-
173- return f.repository.Create(ctx, &repository.CreateMedia{
174- Name: filepath.Base(path),
175- Path: path,
176- PathHash: hash,
177- MIMEType: mimetype,
178- })
179-}
180diff --git a/pkg/worker/scanner/file_scanner.go b/pkg/worker/scanner/file_scanner.go
181new file mode 100644
182index 0000000000000000000000000000000000000000..7c19a3dfe9573278aa703c3402d1620f62b53c46
183--- /dev/null
184+++ b/pkg/worker/scanner/file_scanner.go
185@@ -0,0 +1,99 @@
186+package scanner
187+
188+import (
189+ "context"
190+ "io/fs"
191+ "mime"
192+ "path/filepath"
193+
194+ "git.sr.ht/~gabrielgio/img/pkg/database/repository"
195+ "git.sr.ht/~gabrielgio/img/pkg/fileop"
196+ "git.sr.ht/~gabrielgio/img/pkg/list"
197+ "git.sr.ht/~gabrielgio/img/pkg/worker"
198+)
199+
200+type (
201+ FileScanner struct {
202+ mediaRepository repository.MediaRepository
203+ userRepository repository.UserRepository
204+ }
205+)
206+
207+var _ worker.ChanProcessor[string] = &FileScanner{}
208+
209+func NewFileScanner(
210+ mediaRepository repository.MediaRepository,
211+ userRepository repository.UserRepository,
212+) *FileScanner {
213+ return &FileScanner{
214+ mediaRepository: mediaRepository,
215+ userRepository: userRepository,
216+ }
217+}
218+
219+func (f *FileScanner) Query(ctx context.Context) (<-chan string, error) {
220+ c := make(chan string)
221+
222+ users, err := f.userRepository.List(ctx)
223+ if err != nil {
224+ return nil, err
225+ }
226+
227+ // TODO: de duplicate file paths
228+ paths := list.Map(users, func(u *repository.User) string { return u.Path })
229+
230+ go func(paths []string) {
231+ defer close(c)
232+ for _, p := range paths {
233+ _ = filepath.Walk(p, func(path string, info fs.FileInfo, err error) error {
234+ select {
235+ case <-ctx.Done():
236+ return filepath.SkipAll
237+ default:
238+ }
239+
240+ if info == nil {
241+ return nil
242+ }
243+
244+ if info.IsDir() && filepath.Base(info.Name())[0] == '.' {
245+ return filepath.SkipDir
246+ }
247+
248+ if info.IsDir() {
249+ return nil
250+ }
251+
252+ c <- path
253+ return nil
254+ })
255+ }
256+ }(paths)
257+ return c, nil
258+}
259+
260+func (f *FileScanner) Process(ctx context.Context, path string) error {
261+ mimetype := mime.TypeByExtension(filepath.Ext(path))
262+ supported := fileop.IsMimeTypeSupported(mimetype)
263+ if !supported {
264+ return nil
265+ }
266+
267+ hash := fileop.GetHashFromPath(path)
268+
269+ exists, err := f.mediaRepository.Exists(ctx, hash)
270+ if err != nil {
271+ return err
272+ }
273+
274+ if exists {
275+ return nil
276+ }
277+
278+ return f.mediaRepository.Create(ctx, &repository.CreateMedia{
279+ Name: filepath.Base(path),
280+ Path: path,
281+ PathHash: hash,
282+ MIMEType: mimetype,
283+ })
284+}
285diff --git a/pkg/worker/thumbnail_scanner.go b/pkg/worker/scanner/thumbnail_scanner.go
286rename from pkg/worker/thumbnail_scanner.go
287rename to pkg/worker/scanner/thumbnail_scanner.go
288index 168abef60b86033d7a8bad7118e361c6b013277d..02fd4dd51d4c215edb4a03d2f2d6ce05ef371afb 100644
289--- a/pkg/worker/thumbnail_scanner.go
290+++ b/pkg/worker/scanner/thumbnail_scanner.go
291@@ -1,4 +1,4 @@
292-package worker
293+package scanner
294
295 import (
296 "context"
297@@ -9,6 +9,7 @@ "path"
298
299 "git.sr.ht/~gabrielgio/img/pkg/database/repository"
300 "git.sr.ht/~gabrielgio/img/pkg/fileop"
301+ "git.sr.ht/~gabrielgio/img/pkg/worker"
302 )
303
304 type (
305@@ -18,7 +19,7 @@ cachePath string
306 }
307 )
308
309-var _ BatchProcessor[*repository.Media] = &EXIFScanner{}
310+var _ worker.BatchProcessor[*repository.Media] = &EXIFScanner{}
311
312 func NewThumbnailScanner(cachePath string, repository repository.MediaRepository) *ThumbnailScanner {
313 return &ThumbnailScanner{