diff options
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | cmd/server/main.go | 1 | ||||
| -rw-r--r-- | pkg/database/repository/media.go | 13 | ||||
| -rw-r--r-- | pkg/database/sql/media.go | 46 | ||||
| -rw-r--r-- | pkg/view/album.go | 102 | ||||
| -rw-r--r-- | pkg/view/media.go | 26 | ||||
| -rw-r--r-- | pkg/worker/scanner/album_scanner.go | 1 | ||||
| -rw-r--r-- | templates/album.qtpl | 50 | ||||
| -rw-r--r-- | templates/base.qtpl | 6 | 
9 files changed, 227 insertions, 20 deletions
| @@ -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> | 
