package app import ( "database/sql" "fmt" "io" "io/fs" "log" "path/filepath" "slices" "strings" _ "github.com/mattn/go-sqlite3" "github.com/veandco/go-sdl2/sdl" ) const dbFileName = "nviewer.db" var imageExtensions = map[string]bool{ ".jpg": true, ".jpeg": true, ".png": true, ".gif": true, ".webp": true, ".bmp": true, ".tiff": true, ".tif": true, ".tga": true, ".svg": true, } type FileInfo struct { fsys fs.FS path string Name string Group int } func (f *FileInfo) Open() (io.ReadCloser, error) { return f.fsys.Open(f.path) } func (f *FileInfo) OpenRW() *sdl.RWops { file, err := f.fsys.Open(f.path) if err != nil { return nil } defer file.Close() data, err := io.ReadAll(file) if err != nil { return nil } rw, err := sdl.RWFromMem(data) if err != nil { return nil } return rw } type FileCollection struct { Files []*FileInfo database *sql.DB } func (c *FileCollection) SaveGroup(f *FileInfo, group int) error { _, err := c.database.Exec( `INSERT INTO file_groups (path, "group") VALUES (?, ?) ON CONFLICT(path) DO UPDATE SET "group" = excluded."group"`, f.path, group, ) if err != nil { return err } f.Group = group return nil } func (c *FileCollection) RemoveGroup(f *FileInfo) error { _, err := c.database.Exec("DELETE FROM file_groups WHERE path = ?", f.path) if err != nil { return err } f.Group = 0 return nil } func (c *FileCollection) Close() error { if c.database != nil { return c.database.Close() } return nil } func LoadFileCollection(fsys fs.FS, dbPath string) (*FileCollection, error) { log.Printf(" >> open file collection from fs") if dbPath == "" { dbPath = dbFileName } db, err := sql.Open("sqlite3", dbPath) if err != nil { return nil, err } _, err = db.Exec(` CREATE TABLE IF NOT EXISTS file_groups ( path TEXT PRIMARY KEY, "group" INTEGER NOT NULL ) `) if err != nil { db.Close() return nil, err } groups, err := loadGroups(db) if err != nil { db.Close() return nil, err } var files []*FileInfo err = fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { if err != nil { return nil } if d.IsDir() { return nil } if imageExtensions[strings.ToLower(filepath.Ext(d.Name()))] { p := filepath.ToSlash(path) files = append(files, &FileInfo{ fsys: fsys, path: p, Name: d.Name(), Group: groups[p], }) } return nil }) if err != nil { db.Close() return nil, err } sortFunc := func(a *FileInfo, b *FileInfo) int { return strings.Compare(a.Name, b.Name) } slices.SortFunc(files, sortFunc) log.Printf(" - found %d image files", len(files)) return &FileCollection{ Files: files, database: db, }, nil } func loadGroups(db *sql.DB) (map[string]int, error) { rows, err := db.Query(`SELECT path, "group" FROM file_groups`) if err != nil { return nil, err } defer rows.Close() groups := make(map[string]int) for rows.Next() { var path string var group int if err := rows.Scan(&path, &group); err != nil { return nil, err } path = filepath.ToSlash(path) groups[path] = group } return groups, rows.Err() } func GroupName(group int) string { return fmt.Sprintf("Group %d", group) }