aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Arakaki Giovanini <mail@gabrielgio.me>2023-08-13 21:36:42 +0200
committerGabriel Arakaki Giovanini <mail@gabrielgio.me>2023-08-13 21:36:42 +0200
commitc51fb8cc8b850b4915e083d0dd2c30d79f8b632e (patch)
tree527ba9e4f897bc2faf997ff648aeb85cc9c247eb
parentf99f2bc94808d784c92ec4e58c660a8be3ed7fce (diff)
downloadlens-c51fb8cc8b850b4915e083d0dd2c30d79f8b632e.tar.gz
lens-c51fb8cc8b850b4915e083d0dd2c30d79f8b632e.tar.bz2
lens-c51fb8cc8b850b4915e083d0dd2c30d79f8b632e.zip
feat: Add (yet again) crude album implementation
This is a initial UI album implementation. This should cover the most basic album navigation. This is still plenty to do :)
-rw-r--r--Makefile2
-rw-r--r--cmd/server/main.go1
-rw-r--r--pkg/database/repository/media.go13
-rw-r--r--pkg/database/sql/media.go46
-rw-r--r--pkg/view/album.go102
-rw-r--r--pkg/view/media.go26
-rw-r--r--pkg/worker/scanner/album_scanner.go1
-rw-r--r--templates/album.qtpl50
-rw-r--r--templates/base.qtpl6
9 files changed, 227 insertions, 20 deletions
diff --git a/Makefile b/Makefile
index 743c0fd..76c271a 100644
--- a/Makefile
+++ b/Makefile
@@ -30,7 +30,7 @@ compress: build
compress_into_oblivion: build
upx --best --ultra-brute $(OUT)
-run: sass
+run: sass tmpl
$(GO_RUN) $(SERVER) \
--db-type=$(DB_TYPE) \
--db-con="$(DB_CON)" \
diff --git a/cmd/server/main.go b/cmd/server/main.go
index 035d00a..daf5356 100644
--- a/cmd/server/main.go
+++ b/cmd/server/main.go
@@ -109,6 +109,7 @@ func main() {
view.NewFileSystemView(*fileSystemController, settingsRepository),
view.NewSettingsView(settingsRepository, userController),
view.NewMediaView(mediaRepository, userRepository, settingsRepository),
+ view.NewAlbumView(mediaRepository, userRepository, settingsRepository),
} {
v.SetMyselfIn(extRouter)
}
diff --git a/pkg/database/repository/media.go b/pkg/database/repository/media.go
index d6addbf..9915c90 100644
--- a/pkg/database/repository/media.go
+++ b/pkg/database/repository/media.go
@@ -35,7 +35,9 @@ type (
}
Album struct {
- ID uint
+ ID uint
+ Name string
+ Path string
}
MediaThumbnail struct {
@@ -43,9 +45,10 @@ type (
}
Pagination struct {
- Page int
- Size int
- Path string
+ Page int
+ Size int
+ AlbumID *uint
+ Path string
}
CreateMedia struct {
@@ -83,8 +86,10 @@ type (
CreateThumbnail(context.Context, uint, *MediaThumbnail) error
ListEmptyAlbums(context.Context, *Pagination) ([]*Media, error)
+ ListAlbums(context.Context, uint) ([]*Album, error)
ExistsAlbumByAbsolutePath(context.Context, string) (bool, error)
GetAlbumByAbsolutePath(context.Context, string) (*Album, error)
+ GetAlbum(context.Context, uint) (*Album, error)
CreateAlbum(context.Context, *CreateAlbum) (*Album, error)
CreateAlbumFile(context.Context, *CreateAlbumFile) error
}
diff --git a/pkg/database/sql/media.go b/pkg/database/sql/media.go
index 59e39ee..4b48608 100644
--- a/pkg/database/sql/media.go
+++ b/pkg/database/sql/media.go
@@ -23,7 +23,7 @@ type (
gorm.Model
Width *float64
Height *float64
- MediaID uint
+ MediaID uint `gorm:"not null"`
Media Media
Description *string
Camera *string
@@ -43,8 +43,8 @@ type (
MediaThumbnail struct {
gorm.Model
- Path string
- MediaID uint
+ Path string `gorm:"not null;unique"`
+ MediaID uint `gorm:"not null"`
Media Media
}
@@ -53,7 +53,7 @@ type (
ParentID *uint
Parent *MediaAlbum
Name string
- Path string
+ Path string `gorm:"not null; unique"`
}
MediaAlbumFile struct {
@@ -104,7 +104,9 @@ func (m *MediaEXIF) ToModel() *repository.MediaEXIF {
func (a *MediaAlbum) ToModel() *repository.Album {
return &repository.Album{
- ID: a.ID,
+ ID: a.ID,
+ Name: a.Name,
+ Path: a.Path,
}
}
@@ -407,6 +409,22 @@ func (r *MediaRepository) GetAlbumByAbsolutePath(ctx context.Context, path strin
return m.ToModel(), nil
}
+func (r *MediaRepository) GetAlbum(ctx context.Context, albumID uint) (*repository.Album, error) {
+ m := &MediaAlbum{}
+ result := r.db.
+ WithContext(ctx).
+ Model(&MediaAlbum{}).
+ Where("id = ?", albumID).
+ Limit(1).
+ Take(m)
+
+ if result.Error != nil {
+ return nil, result.Error
+ }
+
+ return m.ToModel(), nil
+}
+
func (m *MediaRepository) CreateAlbum(ctx context.Context, createAlbum *repository.CreateAlbum) (*repository.Album, error) {
album := &MediaAlbum{
ParentID: createAlbum.ParentID,
@@ -439,3 +457,21 @@ func (m *MediaRepository) CreateAlbumFile(ctx context.Context, createAlbumFile *
return nil
}
+
+func (m *MediaRepository) ListAlbums(ctx context.Context, albumID uint) ([]*repository.Album, error) {
+ albums := make([]*MediaAlbum, 0)
+
+ result := m.db.
+ WithContext(ctx).
+ Model(&MediaAlbum{}).
+ Where("parent_id = ?", albumID).
+ Find(&albums)
+
+ if result.Error != nil {
+ return nil, result.Error
+ }
+
+ return list.Map(albums, func(a *MediaAlbum) *repository.Album {
+ return a.ToModel()
+ }), nil
+}
diff --git a/pkg/view/album.go b/pkg/view/album.go
new file mode 100644
index 0000000..a96b9bd
--- /dev/null
+++ b/pkg/view/album.go
@@ -0,0 +1,102 @@
+package view
+
+import (
+ "net/http"
+
+ "git.sr.ht/~gabrielgio/img/pkg/database/repository"
+ "git.sr.ht/~gabrielgio/img/pkg/ext"
+ "git.sr.ht/~gabrielgio/img/templates"
+)
+
+type (
+ AlbumView struct {
+ mediaRepository repository.MediaRepository
+ userRepository repository.UserRepository
+ settingsRepository repository.SettingsRepository
+ }
+)
+
+func NewAlbumView(
+ mediaRepository repository.MediaRepository,
+ userRepository repository.UserRepository,
+ settingsRepository repository.SettingsRepository,
+) *AlbumView {
+ return &AlbumView{
+ mediaRepository: mediaRepository,
+ userRepository: userRepository,
+ settingsRepository: settingsRepository,
+ }
+}
+
+func (self *AlbumView) Index(w http.ResponseWriter, r *http.Request) error {
+ p := getPagination(r)
+ token := ext.GetTokenFromCtx(w, r)
+
+ // TODO: optmize call, GetPathFromUserID may no be necessary
+ userPath, err := self.userRepository.GetPathFromUserID(r.Context(), token.UserID)
+ if err != nil {
+ return err
+ }
+
+ var albums []*repository.Album
+ var album *repository.Album
+
+ if p.AlbumID == nil {
+ // use user path as default value
+ p.Path = userPath
+
+ album, err = self.mediaRepository.GetAlbumByAbsolutePath(r.Context(), p.Path)
+ if err != nil {
+ return err
+ }
+
+ albums, err = self.mediaRepository.ListAlbums(r.Context(), album.ID)
+ if err != nil {
+ return err
+ }
+ } else {
+ album, err = self.mediaRepository.GetAlbum(r.Context(), *p.AlbumID)
+ if err != nil {
+ return err
+ }
+
+ // TODO: User can enter a album out of its bounderies
+ p.Path = album.Path
+
+ albums, err = self.mediaRepository.ListAlbums(r.Context(), *p.AlbumID)
+ if err != nil {
+ return err
+ }
+
+ }
+
+ medias, err := self.mediaRepository.List(r.Context(), p)
+ if err != nil {
+ return err
+ }
+
+ settings, err := self.settingsRepository.Load(r.Context())
+ if err != nil {
+ return err
+ }
+
+ page := &templates.AlbumPage{
+ Medias: medias,
+ Albums: albums,
+ Name: album.Name,
+ Next: &repository.Pagination{
+ Size: p.Size,
+ Page: p.Page + 1,
+ },
+ Settings: settings,
+ }
+
+ templates.WritePageTemplate(w, page)
+
+ return nil
+}
+
+func (self *AlbumView) SetMyselfIn(r *ext.Router) {
+ r.GET("/album/", self.Index)
+ r.POST("/album/", self.Index)
+}
diff --git a/pkg/view/media.go b/pkg/view/media.go
index c7d84ec..3124119 100644
--- a/pkg/view/media.go
+++ b/pkg/view/media.go
@@ -17,12 +17,14 @@ type (
}
)
-func getPagination(w http.ResponseWriter, r *http.Request) *repository.Pagination {
+func getPagination(r *http.Request) *repository.Pagination {
var (
- size int
- page int
- sizeStr = r.FormValue("size")
- pageStr = r.FormValue("page")
+ size int
+ page int
+ albumID *uint
+ sizeStr = r.FormValue("size")
+ pageStr = r.FormValue("page")
+ albumIDStr = r.FormValue("albumId")
)
if sizeStr == "" {
@@ -41,9 +43,17 @@ func getPagination(w http.ResponseWriter, r *http.Request) *repository.Paginatio
page = p
}
+ if albumIDStr == "" {
+ page = 0
+ } else if p, err := strconv.Atoi(albumIDStr); err == nil {
+ id := uint(p)
+ albumID = &id
+ }
+
return &repository.Pagination{
- Page: page,
- Size: size,
+ Page: page,
+ Size: size,
+ AlbumID: albumID,
}
}
@@ -60,7 +70,7 @@ func NewMediaView(
}
func (self *MediaView) Index(w http.ResponseWriter, r *http.Request) error {
- p := getPagination(w, r)
+ p := getPagination(r)
token := ext.GetTokenFromCtx(w, r)
userPath, err := self.userRepository.GetPathFromUserID(r.Context(), token.UserID)
diff --git a/pkg/worker/scanner/album_scanner.go b/pkg/worker/scanner/album_scanner.go
index 618a184..04af9bc 100644
--- a/pkg/worker/scanner/album_scanner.go
+++ b/pkg/worker/scanner/album_scanner.go
@@ -92,6 +92,7 @@ func FanInwards(paths []string) []string {
result := make([]string, 0, len(paths))
for i := (len(paths) - 1); i >= 0; i-- {
subPaths := paths[0:i]
+ subPaths = append([]string{"/"}, subPaths...)
result = append(result, path.Join(subPaths...))
}
return result
diff --git a/templates/album.qtpl b/templates/album.qtpl
new file mode 100644
index 0000000..ce8111e
--- /dev/null
+++ b/templates/album.qtpl
@@ -0,0 +1,50 @@
+{% import "git.sr.ht/~gabrielgio/img/pkg/database/repository" %}
+
+{% code
+type AlbumPage struct {
+ Medias []*repository.Media
+ Next *repository.Pagination
+ Settings *repository.Settings
+ Albums []*repository.Album
+ Name string
+}
+
+func (m *AlbumPage) PreloadAttr() string {
+ if m.Settings.PreloadVideoMetadata {
+ return "metadata"
+ }
+ return "none"
+}
+%}
+
+{% func (p *AlbumPage) Title() %}Media{% endfunc %}
+
+{% func (p *AlbumPage) Content() %}
+<h1 class="title">{%s p.Name %}</h1>
+<div class="tags are-large">
+{% for _, a := range p.Albums %}
+ <a href="/album/?albumId={%s FromUInttoString(&a.ID) %}" class="tag">{%s a.Name %}</a>
+{% endfor %}
+</div>
+<div class="columns is-multiline">
+{% for _, media := range p.Medias %}
+ <div class="card-image">
+ {% if media.IsVideo() %}
+ <video class="image is-fit" controls muted="true" poster="/media/thumbnail/?path_hash={%s media.PathHash %}" preload="{%s p.PreloadAttr() %}">
+ <source src="/media/image/?path_hash={%s media.PathHash %}" type="{%s media.MIMEType %}">
+ </video>
+ {% else %}
+ <figure class="image is-fit">
+ <img src="/media/thumbnail/?path_hash={%s media.PathHash %}">
+ </figure>
+ {% endif %}
+ </div>
+{% endfor %}
+</div>
+<div class="row">
+ <a href="/media/?page={%d p.Next.Page %}" class="button is-pulled-right">next</a>
+</div>
+{% endfunc %}
+
+{% func (p *AlbumPage) Script() %}
+{% endfunc %}
diff --git a/templates/base.qtpl b/templates/base.qtpl
index 5a7c3b7..772167d 100644
--- a/templates/base.qtpl
+++ b/templates/base.qtpl
@@ -11,8 +11,7 @@ Page {
%}
-{% code
- func FromUInttoString(u *uint) string {
+{% code func FromUInttoString(u *uint) string {
if u != nil {
return strconv.FormatUint(uint64(*u), 10)
}
@@ -40,6 +39,9 @@ Page prints a page implementing Page interface.
<a href="/media/" class="navbar-item">
media
</a>
+ <a href="/album/" class="navbar-item">
+ album
+ </a>
<a href="/settings/" class="navbar-item">
settings
</a>