mirror of
https://github.com/Luzifer/gallery.git
synced 2024-12-22 19:01:23 +00:00
Initial implementation of upload
This commit is contained in:
parent
3e85053ace
commit
89711eaabc
8 changed files with 556 additions and 117 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
||||||
frontend/albums.json
|
frontend/albums.json
|
||||||
|
gallery
|
||||||
|
|
105
album.go
Normal file
105
album.go
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type albums struct {
|
||||||
|
Albums []album `json:"albums"`
|
||||||
|
|
||||||
|
store storage
|
||||||
|
}
|
||||||
|
|
||||||
|
type album struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Images []albumImage `json:"images"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type albumImage struct {
|
||||||
|
FullSize string `json:"fullsize"`
|
||||||
|
Thumbnail string `json:"thumbnail"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadAlbumsFromStorage(s storage) (*albums, error) {
|
||||||
|
tmp := &albums{}
|
||||||
|
rc, err := s.ReadFile("albums.json")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rc.Close()
|
||||||
|
|
||||||
|
if err := json.NewDecoder(rc).Decode(tmp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tmp.store = s
|
||||||
|
|
||||||
|
return tmp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a albums) SaveToStorage(s storage) error {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if err := json.NewEncoder(buf).Encode(a); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.WriteFile("albums.json", buf, "application/json")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *albums) AddImage(name, title string) error {
|
||||||
|
alb := album{}
|
||||||
|
alblist := []album{}
|
||||||
|
|
||||||
|
for _, al := range a.Albums {
|
||||||
|
if al.ID == cfg.AlbumID {
|
||||||
|
alb = al
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
alblist = append(alblist, al)
|
||||||
|
}
|
||||||
|
|
||||||
|
if alb.ID == 0 {
|
||||||
|
// Newly initialized album
|
||||||
|
alb.ID = cfg.AlbumID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overwrite title with new one
|
||||||
|
alb.Title = cfg.AlbumTitle
|
||||||
|
|
||||||
|
var duplicate bool
|
||||||
|
for _, img := range alb.Images {
|
||||||
|
if img.FullSize == path.Join("photos", strconv.Itoa(alb.ID), name) {
|
||||||
|
// Duplicate, ignore
|
||||||
|
duplicate = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if duplicate {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
alb.Images = append(alb.Images, albumImage{
|
||||||
|
FullSize: path.Join("photos", strconv.Itoa(alb.ID), name),
|
||||||
|
Thumbnail: path.Join("photos", strconv.Itoa(alb.ID), name+".thumb"),
|
||||||
|
Title: title,
|
||||||
|
})
|
||||||
|
|
||||||
|
alblist = append(alblist, alb)
|
||||||
|
a.Albums = alblist
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a albums) HasGallery(id int) bool {
|
||||||
|
for _, al := range a.Albums {
|
||||||
|
if al.ID == id {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
187
bindata.go
187
bindata.go
|
@ -1,6 +1,5 @@
|
||||||
// Code generated by go-bindata. DO NOT EDIT.
|
// Code generated by go-bindata. DO NOT EDIT.
|
||||||
// sources:
|
// sources:
|
||||||
// frontend/albums.json
|
|
||||||
// frontend/app.js
|
// frontend/app.js
|
||||||
// frontend/index.html
|
// frontend/index.html
|
||||||
|
|
||||||
|
@ -80,46 +79,6 @@ func (fi bindataFileInfo) Sys() interface{} {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var _bindataFrontendAlbumsjson = []byte(
|
|
||||||
"\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xbc\x94\xc1\x4e\x03\x21\x10\x86\xef\x3c\xc5\x64\xce\xab\x0c\xb4\xeb\xae" +
|
|
||||||
"\x9b\x18\xe3\x0b\x78\xf2\x66\x7a\xa0\x15\x5b\x12\xa8\x8d\x0b\x1e\x6c\xf6\xdd\x8d\xd8\x60\x43\xb2\x5e\xb0\x1c\x20" +
|
|
||||||
"\x24\x33\xf3\xe5\xe3\x3f\xcc\x91\x01\xa0\xb2\xeb\xe0\x46\x1c\xe0\x99\x01\x00\x1c\xe3\x0d\x80\xe6\x05\x07\x90\x24" +
|
|
||||||
"\x7a\x5a\xca\x9e\x88\x44\xbb\x68\x52\xcd\xa9\xad\xfe\x9d\x39\x9f\x8b\xf5\xd7\x60\xed\x68\x3e\x35\x0e\x80\x3b\xef" +
|
|
||||||
"\x0f\xe3\xc0\xf9\xc1\xaa\x8d\x36\x6e\x7b\xbd\x79\x73\x5c\xc8\x9e\x78\x27\x89\xef\x95\x0f\xef\xfa\xfe\xe3\x4e\x60" +
|
|
||||||
"\x73\x4e\xf0\xbb\xe0\xd6\x7b\x65\xec\x2c\xa2\x25\x8a\x67\x96\x60\xbc\x8d\x02\x8f\xb1\x01\x53\x6d\x3a\xbd\x56\xe9" +
|
|
||||||
"\x3b\xa9\xf5\x49\x8f\xfe\xea\xe1\x3b\x10\x10\x3f\x03\x53\xf3\x77\x2a\x9d\xbc\xbd\x64\x2a\xb2\x38\x95\x9c\x30\x9b" +
|
|
||||||
"\x4a\xf3\x6f\xd2\x8b\x62\xe9\x9c\x50\x41\x7a\x59\x2c\x9d\x13\x2a\x48\xb7\xc5\xd2\x39\xa1\x82\xf4\x4d\xb1\x74\x4e" +
|
|
||||||
"\xa8\x20\xdd\x15\x4b\xe7\x84\xb2\xf5\x24\x4f\xeb\x89\x01\xac\xd8\xc4\xbe\x02\x00\x00\xff\xff\x58\x74\x83\x74\xca" +
|
|
||||||
"\x05\x00\x00")
|
|
||||||
|
|
||||||
func bindataFrontendAlbumsjsonBytes() ([]byte, error) {
|
|
||||||
return bindataRead(
|
|
||||||
_bindataFrontendAlbumsjson,
|
|
||||||
"frontend/albums.json",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func bindataFrontendAlbumsjson() (*asset, error) {
|
|
||||||
bytes, err := bindataFrontendAlbumsjsonBytes()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
info := bindataFileInfo{
|
|
||||||
name: "frontend/albums.json",
|
|
||||||
size: 1482,
|
|
||||||
md5checksum: "",
|
|
||||||
mode: os.FileMode(436),
|
|
||||||
modTime: time.Unix(1, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
a := &asset{bytes: bytes, info: info}
|
|
||||||
|
|
||||||
return a, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _bindataFrontendAppjs = []byte(
|
var _bindataFrontendAppjs = []byte(
|
||||||
"\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x7c\x54\xc1\x6e\xc3\x36\x0c\xbd\xe7\x2b\x08\x34\x80\xe4\xc2\x71\xd6\x9e" +
|
"\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x7c\x54\xc1\x6e\xc3\x36\x0c\xbd\xe7\x2b\x08\x34\x80\xe4\xc2\x71\xd6\x9e" +
|
||||||
"\x06\x17\x29\xb0\xae\x41\xda\x53\x0f\x2b\xb0\xe3\xa0\x58\xb4\xad\x42\x96\x0c\x89\x4e\xda\x15\xf9\xf7\xc1\xb2\xe4" +
|
"\x06\x17\x29\xb0\xae\x41\xda\x53\x0f\x2b\xb0\xe3\xa0\x58\xb4\xad\x42\x96\x0c\x89\x4e\xda\x15\xf9\xf7\xc1\xb2\xe4" +
|
||||||
|
@ -175,82 +134,82 @@ func bindataFrontendAppjs() (*asset, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var _bindataFrontendIndexhtml = []byte(
|
var _bindataFrontendIndexhtml = []byte(
|
||||||
"\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x58\xfb\x73\xda\xb8\x13\xff\xbd\x7f\x85\xce\x9d\xef\x4c\x3a\xa9\x6c" +
|
"\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x58\x6b\x73\xda\x3a\x13\xfe\xde\x5f\xa1\xe3\xce\x3b\x93\x4e\x2a\x1b" +
|
||||||
"\xcc\x2b\x40\x21\x73\x84\x34\x25\x0f\xf2\x20\x49\x9b\xf4\x37\xd9\x96\x6d\x81\x2c\x39\x92\xcc\x23\x4c\xfe\xf7\xef" +
|
"\x73\x0b\x50\xc8\x1c\x42\x9a\x92\x0b\xb9\x90\xa4\x4d\xfa\x4d\xb6\x65\x5b\x20\x4b\x8e\x24\x73\x09\x93\xff\xfe\x8e" +
|
||||||
"\xc8\xd8\x60\x48\xb8\xe6\xee\x7a\x74\x0a\xb6\xb4\xaf\xcf\xee\x4a\xbb\x9b\xf6\x1f\x1e\x77\xd5\x3c\xc6\x20\x54\x11" +
|
"\x8c\x0d\x86\x86\x36\xa7\xa7\x87\x4e\xc1\x96\x76\x57\xfb\xec\x45\xbb\x9b\xf6\x5f\x1e\x77\xd5\x3c\xc6\x20\x54\x11" +
|
||||||
"\x3d\xfc\xd0\xd6\x3f\x80\x22\x16\x74\x0c\xcc\x8c\xc3\x0f\x00\xb4\x43\x8c\x3c\xfd\x00\x40\xfb\x0f\x08\xc1\x10\x3f" +
|
"\x3d\x7c\xd7\xd6\x3f\x80\x22\x16\x74\x0c\xcc\x8c\xc3\x77\x00\xb4\x43\x8c\x3c\xfd\x00\x40\xfb\x2f\x08\xc1\x10\x3f" +
|
||||||
"\x25\x44\x60\x0f\x44\x58\x21\xa0\x50\x20\x01\x84\xd9\x7e\xba\xe4\x86\x48\x48\xac\x3a\x46\xa2\x7c\xd8\x30\x8a\x5b" +
|
"\x25\x44\x60\x0f\x44\x58\x21\xa0\x50\x20\x01\x84\xd9\x7e\xba\xe4\x86\x48\x48\xac\x3a\x46\xa2\x7c\xd8\x30\x8a\x5b" +
|
||||||
"\xa1\x52\x31\xd4\xfc\x93\x8e\xf1\x00\xef\xbb\xb0\xc7\xa3\x18\x29\xe2\x50\x6c\x00\x97\x33\x85\x99\xea\x18\xa7\x5f" +
|
"\xa1\x52\x31\xd4\xfc\x93\x8e\xf1\x00\xef\xbb\xb0\xc7\xa3\x18\x29\xe2\x50\x6c\x00\x97\x33\x85\x99\xea\x18\xa7\x9f" +
|
||||||
"\x3b\xd8\x0b\xf0\x06\x27\x43\x11\xee\x18\x13\x82\xa7\x31\x17\xaa\x40\x3c\x25\x9e\x0a\x3b\x1e\x9e\x10\x17\xc3\xf4" +
|
"\x3b\xd8\x0b\xf0\x06\x27\x43\x11\xee\x18\x13\x82\xa7\x31\x17\xaa\x40\x3c\x25\x9e\x0a\x3b\x1e\x9e\x10\x17\xc3\xf4" +
|
||||||
"\xe5\x33\x20\x8c\x28\x82\x28\x94\x2e\xa2\xb8\x63\x7f\x06\x32\x14\x84\x8d\xa1\xe2\xd0\x27\xaa\xc3\xb8\x71\xf8\x61" +
|
"\xe5\x23\x20\x8c\x28\x82\x28\x94\x2e\xa2\xb8\x63\x7f\x04\x32\x14\x84\x8d\xa1\xe2\xd0\x27\xaa\xc3\xb8\x71\xf8\x6e" +
|
||||||
"\x0d\xe8\x88\x73\x25\x95\x40\x31\xe8\xdd\xde\xae\xb1\x50\xc2\xc6\x40\x60\xda\x31\xa4\x9a\x53\x2c\x43\x8c\x95\x01" +
|
"\x0d\xe8\x88\x73\x25\x95\x40\x31\xe8\xdd\xde\xae\xb1\x50\xc2\xc6\x40\x60\xda\x31\xa4\x9a\x53\x2c\x43\x8c\x95\x01" +
|
||||||
"\x42\x81\xfd\x8e\xa1\x71\xc8\x96\x65\xb9\x1e\x1b\x49\xd3\xa5\x3c\xf1\x7c\x8a\x04\x36\x5d\x1e\x59\x68\x84\x66\x16" +
|
"\x42\x81\xfd\x8e\xa1\x71\xc8\x96\x65\xb9\x1e\x1b\x49\xd3\xa5\x3c\xf1\x7c\x8a\x04\x36\x5d\x1e\x59\x68\x84\x66\x16" +
|
||||||
"\x25\x8e\xb4\xd4\x94\x28\x85\x05\x74\x72\x0d\x56\xd5\x2c\x99\x25\xcb\x95\xd2\x5a\xad\x99\x11\x61\xa6\x2b\xa5\x91" +
|
"\x25\x8e\xb4\xd4\x94\x28\x85\x05\x74\xf2\x13\xac\xaa\x59\x32\x4b\x96\x2b\xa5\xb5\x5a\x33\x23\xc2\x4c\x57\x4a\x23" +
|
||||||
"\xaa\x5d\x7e\x08\x53\x38\x10\x44\xcd\x3b\x86\x0c\x51\xb9\x56\x87\x17\xdd\x46\xf3\x79\x7f\xdc\xf4\x47\xc1\xe0\xfc" +
|
"\x3d\x76\xf9\x21\x4c\xe1\x40\x10\x35\xef\x18\x32\x44\xe5\x5a\x1d\x5e\x74\x1b\xcd\xe7\xfd\x71\xd3\x1f\x05\x83\xf3" +
|
||||||
"\xc6\x1a\x3f\x55\xaf\xae\xca\x03\xe1\x37\xbe\x53\xf5\x18\x51\xeb\xfb\xd7\xdb\xfd\x61\x50\xf2\xc3\x72\xa9\x63\x00" +
|
"\x1b\x6b\xfc\x54\xbd\xba\x2a\x0f\x84\xdf\xf8\x4a\xd5\x63\x44\xad\xaf\x9f\x6f\xf7\x87\x41\xc9\x0f\xcb\xa5\x8e\x01" +
|
||||||
"\x57\x70\x29\xb9\x20\x01\x61\x1d\x03\x31\xce\xe6\x11\x4f\xa4\x01\xac\xdf\x87\x2d\xb5\x7f\x8a\x94\x1b\x66\xa0\x3c" +
|
"\x5c\xc1\xa5\xe4\x82\x04\x84\x75\x0c\xc4\x38\x9b\x47\x3c\x91\x06\xb0\xfe\x1c\xb6\x54\xff\x29\x52\x6e\x98\x81\xf2" +
|
||||||
"\x24\xc6\x74\xfe\x77\x71\x39\xe1\x71\xd5\x3a\xf6\x7a\xdd\xf8\xb2\xfb\x48\xaf\xe2\xc9\xb9\x94\xf7\xa8\x1c\x1e\x97" +
|
"\x90\x18\xd3\xf9\x3f\xc5\xe5\x84\xc7\x55\xeb\xd8\xeb\x75\xe3\xcb\xee\x23\xbd\x8a\x27\xe7\x52\xde\xa3\x72\x78\x5c" +
|
||||||
"\x8e\x93\xc6\x4c\x04\x57\x13\xf5\x58\x9d\x96\x6b\x32\x1c\xfc\x35\xae\xdf\x06\x8c\x92\x20\x54\x01\xa2\x14\x8b\xb9" +
|
"\x3a\x4e\x1a\x33\x11\x5c\x4d\xd4\x63\x75\x5a\xae\xc9\x70\xf0\x73\x5c\x7f\x0c\x18\x25\x41\xa8\x02\x44\x29\x16\x73" +
|
||||||
"\x65\x9b\x75\xb3\x99\xc6\xab\xb8\xfc\x3e\x68\x0d\xe1\xf7\x9d\x33\xb1\x3f\x4a\x2a\x57\x6e\xa9\xd9\x1c\x9d\x9c\x0d" +
|
"\xcb\x36\xeb\x66\x33\xf5\x57\x71\xf9\x6d\xd0\x1a\xc2\xef\x3b\x67\x62\x7f\x94\x54\xae\xdc\x52\xb3\x39\x3a\x39\x1b" +
|
||||||
"\x86\xf6\xac\x7b\x9d\x34\x7a\x77\xd7\xfd\xfb\x46\x72\x5d\x3b\xab\x3c\x58\xdf\xbb\x8f\xbf\x0b\x5a\x22\xb1\xe9\x73" +
|
"\x0c\xed\x59\xf7\x3a\x69\xf4\xee\xae\xfb\xf7\x8d\xe4\xba\x76\x56\x79\xb0\xbe\x76\x1f\xff\x14\xb4\x44\x62\xd3\xe7" +
|
||||||
"\xa6\xd0\x14\x4b\x1e\x2d\x91\x09\x4c\x31\x92\x58\x5a\x93\x9a\x59\x32\xed\x65\xf6\x21\x4a\xff\x1a\x40\xa5\x51\x85" +
|
"\x4c\xa1\x29\x96\x3c\x5a\x22\x13\x98\x62\x24\xb1\xb4\x26\x35\xb3\x64\xda\xcb\xe8\x43\x94\xfe\x1c\x40\xa5\x51\x85" +
|
||||||
"\xfb\x5e\xe9\xba\x51\x61\xcd\x31\xba\x19\xf4\xa6\xa3\xc6\x49\x75\x78\x76\x54\xaf\xab\xe7\xd3\xe9\xd5\x79\x24\x3c" +
|
"\xfb\x5e\xe9\xba\x51\x61\xcd\x31\xba\x19\xf4\xa6\xa3\xc6\x49\x75\x78\x76\x54\xaf\xab\xe7\xd3\xe9\xd5\x79\x24\x3c" +
|
||||||
"\xa7\x5a\xdf\x8f\xb9\x38\xb6\xae\x26\xe2\x6c\xbf\x72\xf0\xe3\xe9\x74\x70\x70\xcf\x8f\xd4\xb4\x7f\x55\xbf\xa4\xc1" +
|
"\xa7\x5a\xdf\x8f\xb9\x38\xb6\xae\x26\xe2\x6c\xbf\x72\xf0\xed\xe9\x74\x70\x70\xcf\x8f\xd4\xb4\x7f\x55\xbf\xa4\xc1" +
|
||||||
"\x4e\x50\x39\xa4\x14\xc8\x61\x66\x83\xe9\x22\xe1\x81\x05\x70\x13\x21\xb9\x68\x81\x98\x6b\x8b\xc4\x17\xf0\xb2\x41" +
|
"\x4e\x50\x39\xa4\x14\xc8\x61\xa6\x83\xe9\x22\xe1\x81\x05\x70\x13\x21\xb9\x68\x81\x98\x6b\x8d\xc4\x27\xf0\xb2\x41" +
|
||||||
"\x90\x7e\x43\x12\x05\x90\x4f\xb0\xa0\x68\x0e\x16\x39\x29\xc4\x13\xcc\x94\x6c\x01\xc6\x19\xfe\x02\x9e\x21\x61\x1e" +
|
"\x90\x7e\x43\x12\x05\x90\x4f\xb0\xa0\x68\x0e\x16\x39\x29\xc4\x13\xcc\x94\x6c\x01\xc6\x19\xfe\x04\x9e\x21\x61\x1e" +
|
||||||
"\x9e\xb5\x40\x65\x4b\x86\x99\x85\xb1\x15\x6a\x11\x80\x44\x01\x58\xac\xa9\xed\x5f\x52\xb7\x98\x0a\xa1\x1b\x12\xea" +
|
"\x9e\xb5\x40\x65\x4b\x86\x99\xb9\xb1\x15\x6a\x11\x80\x44\x01\x58\xac\xa9\xed\x5f\x52\xb7\x98\x0a\xa1\x1b\x12\xea" +
|
||||||
"\xed\xd9\x9f\x8a\xac\xe5\xbf\xc3\x5a\xd6\xac\x4a\x20\x26\x7d\x2e\xa2\x16\x10\x5c\x21\x85\xf7\x60\xd9\xc3\xc1\xa7" +
|
"\xed\xd9\x1f\x8a\xac\xe5\x7f\xc2\x5a\xd6\xac\x4a\x20\x26\x7d\x2e\xa2\x16\x10\x5c\x21\x85\xf7\x60\xd9\xc3\xc1\x87" +
|
||||||
"\xe5\x06\x45\x0a\x3f\xee\xd9\xa5\x78\x56\x58\x78\xd8\x83\x76\x39\x9e\x7d\xfa\x3b\xba\x2a\x6f\xeb\x02\xb5\x6d\x5d" +
|
"\xe5\x06\x45\x0a\x3f\xee\xd9\xa5\x78\x56\x58\x78\xd8\x83\x76\x39\x9e\x7d\xf8\x27\x67\x55\x5e\x3f\x0b\xd4\xb6\xcf" +
|
||||||
"\xb0\xb1\xa5\x0b\xbc\xa5\x0b\xe4\xca\x34\x70\xe2\x22\xc5\x45\x1a\x05\x49\x14\xe1\xac\x05\x90\x23\x39\x4d\x14\xfe" +
|
"\x82\x8d\xad\xb3\xc0\x6b\x67\x81\xfc\x30\x0d\x9c\xb8\x48\x71\x91\x7a\x41\x12\x45\x38\x6b\x01\xe4\x48\x4e\x13\x85" +
|
||||||
"\x02\x84\x3e\x35\x2d\x60\xe3\xe8\x0b\x50\x78\xa6\xa0\x0c\x91\xc7\xa7\x2d\x50\x02\x25\x50\x89\x67\xe0\x63\xb9\xa4" +
|
"\x3f\x01\xa1\xb3\xa6\x05\x6c\x1c\x7d\x02\x0a\xcf\x14\x94\x21\xf2\xf8\xb4\x05\x4a\xa0\x04\x2a\xf1\x0c\xbc\x2f\x97" +
|
||||||
"\xff\x7d\x01\x8a\xc7\x19\xdd\xa6\xaa\x14\x08\x57\x7b\x2d\x9f\x08\xa9\x96\x78\x3e\xed\xd0\xb6\x0a\x04\xdc\x0e\x62" +
|
"\xf4\xbf\x4f\x40\xf1\x38\xa3\xfb\x95\xbf\xc3\x9a\x06\xf3\x53\x61\x9b\x22\x52\x5b\x70\xb5\xd7\xf2\x89\x90\x6a\x69" +
|
||||||
"\x16\xe4\x14\x58\xc6\xb6\x72\x07\x30\x2b\x12\xe8\x23\x02\x79\xa2\xbe\x80\xb4\x86\xb4\x80\x5d\x2a\xfd\xaf\x20\x84" +
|
"\x92\x0f\x3b\x14\x5e\xf9\x12\x6e\xc7\x41\x16\x27\xa9\x6d\x32\xb6\x95\x45\x81\x59\x91\x40\x67\x19\xe4\x89\xfa\x04" +
|
||||||
"\xa1\x09\x4c\x4f\x20\xd1\x29\xca\xa9\xce\x50\x11\x38\x68\xaf\x5c\xab\x7d\xce\xff\x9b\xb5\xd4\x53\x19\xcf\x9f\x11" +
|
"\xd2\x32\xd4\x02\x76\xa9\xf4\xbf\x82\x10\x86\x26\x30\x4d\x62\xa2\xa3\x9c\x53\x1d\xe4\x22\x70\xd0\x5e\xb9\x56\xfb" +
|
||||||
"\xf6\x08\x02\x7b\x11\x9a\xc1\x4c\x28\x38\xa8\x1f\x98\xcd\xd4\xc7\xbb\x3e\x0b\x60\x4e\x61\x2c\xb8\x0b\x16\xb9\x29" +
|
"\x98\xff\x37\x6b\xa9\xb1\x33\x9e\xbf\x23\xec\x11\x04\xf6\x22\x34\x83\x99\x50\x70\x50\x3f\x30\x9b\xa9\x9b\x76\x7d" +
|
||||||
"\xb5\xd4\x92\x95\x2d\x6f\xc9\x6d\x36\xed\x4c\x2e\x62\x1e\xd8\x8b\x08\xcb\xb7\x0e\xea\x4b\x75\x6f\xc8\xad\x54\x7e" +
|
"\x16\xc0\x9c\xc2\x58\x70\x17\x2c\x72\x55\x6a\xa9\x26\x2b\x5d\x5e\x93\xdb\x6c\xda\x99\x5c\xc4\x3c\xb0\x17\x11\x96" +
|
||||||
"\x25\xd7\xb6\x9b\xcd\x1d\x72\x9b\xcd\xf2\x2e\xb9\xe5\xda\xdb\x72\xd7\xcc\x76\xb9\x54\xfa\x2b\x27\xbc\xed\x87\x72" +
|
"\x6f\x1d\xd4\x97\xc7\xbd\x22\xb7\x52\xf9\x95\x5c\xdb\x6e\x36\x77\xc8\x6d\x36\xcb\xbb\xe4\x96\x6b\xaf\xcb\x5d\x33" +
|
||||||
"\xd1\x0f\x6d\x2b\xbb\x3d\x96\x6f\x8a\x28\x8a\x0f\xaf\x43\xae\xb8\x6c\x5b\xcb\x37\xdd\xbc\x58\x79\xf7\xd2\x76\xb8" +
|
"\xdb\xe5\x52\xe9\x67\x46\x78\xdd\x0e\xe5\xa2\x1d\xda\x56\x76\x01\x2d\xdf\x14\x51\x14\x1f\x5e\x87\x5c\x71\xd9\xb6" +
|
||||||
"\x37\xcf\xca\x1f\x43\x13\xe0\x52\x24\x65\xc7\x60\x68\xe2\x20\x01\x96\x3f\x10\xcf\x62\xc4\x3c\x48\x03\xe0\x93\x19" +
|
"\x96\x6f\xba\xff\xb1\xf2\x06\xa8\xed\x70\x6f\x9e\x55\x50\x86\x26\xc0\xa5\x48\xca\x8e\xc1\xd0\xc4\x41\x02\x2c\x7f" +
|
||||||
"\xf6\xa0\xc3\x95\xe2\x51\xbe\x9b\x96\x00\xe0\x04\xcb\x07\x23\xbf\xb8\xda\x68\x53\x18\x74\x04\x62\x5e\x7e\x35\x7f" +
|
"\x20\x9e\xc5\x88\x79\x90\x06\xc0\x27\x33\xec\x41\x87\x2b\xc5\xa3\x7c\x37\xad\x22\xc0\x09\x96\x0f\x46\x7e\xf7\xb5" +
|
||||||
"\x34\x56\x56\xa1\x15\x87\x93\x28\xc5\xd9\x16\x9b\xe2\x41\x40\xb1\x30\x80\xee\xc7\x3a\xc6\x92\xc6\x00\x1e\x52\x28" +
|
"\xd1\xa6\x30\xe8\x08\xc4\xbc\xfc\x76\x7f\x6f\xac\xb4\x42\x2b\x0e\x27\x51\x8a\xb3\x2d\x36\xc5\x83\x80\x62\x61\x00" +
|
||||||
"\xdb\xeb\x18\x2e\xa7\x14\xc5\x12\x1b\xe0\xc3\xa6\xbb\x96\x54\x48\x04\xba\xf1\xfa\xb8\x94\x78\x9b\xc4\xba\x59\xc2" +
|
"\xdd\xd2\x75\x8c\x25\x8d\x01\x3c\xa4\x50\xb6\xd7\x31\x5c\x4e\x29\x8a\x25\x36\xc0\xbb\x4d\x73\x2d\xa9\x90\x08\x74" +
|
||||||
"\x5e\x6f\xd9\x2c\x19\x00\x09\x82\xa0\x6e\x9d\x04\xa7\x2b\xc5\xaf\xc8\xb6\x44\xa7\x4c\x4b\xcf\x60\xaf\x63\xf8\x88" +
|
"\xef\xf6\x7e\x29\xf1\x36\x89\x75\xbf\x85\xbd\xde\xb2\xdf\x32\x00\x12\x04\x41\xdd\x7d\x09\x4e\x57\x07\xff\x40\xb6" +
|
||||||
"\x6a\xfd\xe9\x2a\x45\x8e\x2e\x48\x77\xa9\x75\xda\x4d\x24\x40\xfa\x74\xad\x7c\xa3\x2f\xf9\x18\xed\x40\x0a\x89\xab" +
|
"\x25\x3a\x65\x5a\x5a\x06\x7b\x1d\xc3\x47\x54\x9f\x9f\xae\x52\xe4\xe8\x9a\x76\x97\x6a\xa7\xcd\x44\x02\xa4\xb3\x6b" +
|
||||||
"\x49\xdb\x96\x26\x59\xf9\xc6\x5a\x02\x3f\xcc\x8f\x51\xdb\x23\xab\x68\xe5\xf0\xf3\x88\xac\xdd\x41\xbc\x9d\x70\x0a" +
|
"\x65\x1b\x5d\x27\x62\xb4\x03\x29\x24\xae\x26\x6d\x5b\x9a\x64\x65\x1b\x6b\x09\xfc\x30\x4f\xa3\xb6\x47\x56\xde\xca" +
|
||||||
"\xb6\x24\x74\xcb\x12\x9d\x09\x11\x85\x28\x51\xbc\x40\x97\x56\xdb\x02\x25\x24\x0a\x47\xc6\xe1\x46\xa0\xd3\xdb\x60" +
|
"\xe1\xe7\x1e\x59\x9b\x83\x78\x3b\xe1\x14\x74\x49\xe8\x96\x26\x3a\x12\x22\x0a\x51\xa2\x78\x81\x2e\x2d\xd8\x05\x4a" +
|
||||||
"\xbb\xfe\xa2\x90\x62\x21\xcd\x08\x5b\xc6\x52\x4e\x9a\x88\x1d\xe3\x07\x76\x24\x51\xd8\x38\x6c\xaf\xc4\xfa\x48\x02" +
|
"\x48\x14\x8e\x8c\xc3\x0d\x47\xa7\xb7\xc1\x76\x09\x47\x21\xc5\x42\x9a\x11\xb6\x8c\xa5\x9c\x34\x10\x3b\xc6\x37\xec" +
|
||||||
"\x1f\xc1\x90\x47\x7a\xd9\x22\x87\xa0\xcf\x23\x1c\xa3\x00\xeb\x44\x69\x5b\x94\xfc\x06\x7b\xc6\x2c\x51\x26\x61\x79" +
|
"\x48\xa2\xb0\x71\xd8\x5e\x89\xf5\x91\x04\x3e\x82\x21\x8f\xf4\xb2\x45\x0e\x41\x9f\x47\x38\x46\x01\xd6\x81\xd2\xb6" +
|
||||||
"\x23\x6a\xe4\xf6\xdc\x65\xef\x1b\xf6\x38\xda\x1e\xb5\xda\xd1\x26\xfd\x79\x91\x3c\x13\x1f\x8b\xd7\x26\xb5\xad\x84" +
|
"\x28\xf9\x03\xfa\x8c\x59\xa2\x4c\xc2\xf2\x5e\xd6\xc8\xf5\xb9\xcb\xde\x37\xf4\x71\xb4\x3e\x6a\xb5\xa3\x55\xfa\xfb" +
|
||||||
"\xae\x43\xe6\x91\x49\x76\xb4\x2c\x86\x26\xf9\x99\xdc\x08\x1c\x53\x88\x30\x2c\xa0\x4f\x13\xe2\xad\xcf\x4e\x81\x46" +
|
"\x22\x79\x26\x3e\x16\x3f\xaa\xd4\xb6\x12\xba\x76\x99\x47\x26\x59\x6a\x59\x0c\x4d\xf2\x9c\xdc\x70\x1c\x53\x88\x30" +
|
||||||
"\xf0\x29\x88\x14\xac\x80\xc8\x81\xb5\x62\xd4\x36\x13\x00\x46\x1e\xb4\xcb\xc0\x83\x3e\xc5\x33\x30\x4a\xa4\x22\xfe" +
|
"\x2c\xa0\x4f\x13\xe2\xad\x73\xa7\x40\x23\xf8\x14\x44\x0a\x56\x40\xe4\xc0\x5a\xd1\x6b\x9b\x01\x00\x23\x0f\xda\x65" +
|
||||||
"\x1c\x66\xf3\x00\xa4\xd8\x57\x40\xef\xc0\xa9\x40\xf1\x32\x2f\xf2\x62\x95\xae\x1c\x16\xac\xdd\x32\x3d\x7d\x5c\xcf" +
|
"\xe0\x41\x9f\xe2\x19\x18\x25\x52\x11\x7f\x0e\xb3\x91\x02\x52\xec\x2b\xa0\x77\xe0\x54\xa0\x78\x19\x17\x79\xbd\x4b" +
|
||||||
"\x05\xa3\x9b\x04\x8b\x39\x48\xeb\xce\x67\xa0\x42\xcc\xc0\x35\x8f\x63\x2c\xcc\x91\xcc\xde\xd7\xa3\xc3\x59\x61\x72" +
|
"\x57\x0e\x0b\xda\x6e\xa9\x9e\x3e\xae\x47\x8b\xd1\x4d\x82\xc5\x1c\xa4\x75\xe7\x23\x50\x21\x66\xe0\x9a\xc7\x31\x16" +
|
||||||
"\x90\xae\x20\xb1\x02\x52\xb8\xef\x6e\x3a\x47\x4f\x5a\x97\x55\x31\x2b\xa6\x9d\xbd\xa4\x4d\xe6\x48\x6e\x9e\xc6\xd7" +
|
"\xe6\x48\x66\xef\xeb\xe9\xe3\xac\x30\x7c\x48\x57\x90\x58\x01\x29\xdc\x37\xf7\xad\xa3\x27\x7d\x96\x55\x31\x2b\xa6" +
|
||||||
"\x5d\xe6\x49\x10\xf7\x1c\xeb\xfc\xec\x86\x5e\x5c\xfa\x57\x49\xd3\x56\xa8\x52\xe6\xd6\xe5\xe0\xe7\x8c\xaa\xe9\x90" +
|
"\x9d\xbd\xa4\x7d\xea\x48\x6e\x66\xe3\x8f\x8d\xea\x49\x10\xf7\x1c\xeb\xfc\xec\x86\x5e\x5c\xfa\x57\x49\xd3\x56\xa8" +
|
||||||
"\x37\x6e\x54\x34\x1e\x0c\xbd\x6e\xd2\xd8\xdd\x65\xea\x33\x98\x1a\xfd\xcf\x11\xc4\xb9\x6b\x2c\xdb\xb4\xab\xa6\x9d" +
|
"\x52\xe6\xd6\xe5\xe0\xfb\x8c\xaa\xe9\x90\x37\x6e\x54\x34\x1e\x0c\xbd\x6e\xd2\xd8\xdd\xa8\xea\x1c\x4c\x95\xfe\x7d" +
|
||||||
"\x2f\xbc\x0f\xc5\xed\x5d\x79\x80\x5d\xf1\x20\xce\x50\x57\x3e\x45\x7e\x3c\x6e\x3e\x0c\x6f\x4e\xef\xe8\x31\x9f\x0f" +
|
"\x04\x71\x6e\x1a\xcb\x36\xed\xaa\x69\xe7\x0b\x6f\x43\x71\x7b\x57\x1e\x60\x57\x3c\x88\x33\xd4\x95\x4f\x91\x1f\x8f" +
|
||||||
"\xa2\x7b\x15\x9c\x1f\x7d\x65\xde\x31\x91\xb7\xee\x7f\x8a\x62\xd7\xc4\x36\xda\x1e\xd8\x7e\x0d\xa9\xb6\x5f\x2a\x3f" +
|
"\x9b\x0f\xc3\x9b\xd3\x3b\x7a\xcc\xe7\x83\xe8\x5e\x05\xe7\x47\x9f\x99\x77\x4c\xe4\xad\xfb\x9f\xa2\xd8\x35\xf4\x8d" +
|
||||||
"\x27\xb5\xfb\xfb\x8b\x9b\xf1\xd5\xc1\xd4\xfe\x76\x2a\xea\x13\x5f\xf5\x82\x81\x7f\xe2\xfd\x74\xbb\x7d\x7c\xac\x4e" +
|
"\xb6\x67\xbe\x5f\x43\xaa\xed\x97\xca\xcf\x49\xed\xfe\xfe\xe2\x66\x7c\x75\x30\xb5\xbf\x9c\x8a\xfa\xc4\x57\xbd\x60" +
|
||||||
"\xd8\xf9\x4f\x79\x24\xdf\x05\x69\x9d\x98\x17\xba\xfc\x7c\x5b\x26\xf5\xbf\xca\xba\x37\x46\x9d\xd1\x1b\x93\xce\xaf" +
|
"\xe0\x9f\x78\xdf\xdd\x6e\x1f\x1f\xab\x13\x76\xfe\x5d\x1e\xc9\x37\x41\x5a\x07\xe6\x85\x2e\x3f\x5f\x96\x41\xfd\xaf" +
|
||||||
"\xb1\xb2\xaf\xee\xc8\xbb\x69\x38\x27\x55\x6e\xcd\xce\x4f\x7a\xb7\x3f\x8e\x9a\xdd\xf1\xcf\xfd\xfe\xf5\x8f\xa8\xe7" +
|
"\xa2\xee\x95\x69\x69\xf4\xca\xb0\xf4\x6b\xac\xec\xb3\x3b\xf2\x6e\x1a\xce\x49\x95\x5b\xb3\xf3\x93\xde\xed\xb7\xa3" +
|
||||||
"\x8c\xfb\x97\x4f\x49\xf8\xb5\xf6\x78\x79\xe1\xbd\x0f\xeb\x3f\x06\x14\xc0\x10\xc9\xd0\xb2\xcd\x92\x59\xcd\xdf\xde" +
|
"\x66\x77\xfc\x7d\xbf\x7f\xfd\x2d\xea\x39\xe3\xfe\xe5\x53\x12\x7e\xae\x3d\x5e\x5e\x78\x6f\xc3\xfa\xdb\x80\x02\x18" +
|
||||||
"\x89\x41\xf4\xea\xf1\x65\x75\x68\xf3\x68\x24\xd4\x60\x5a\xaa\xb3\x93\x41\xaf\x3b\x7c\x7e\x7c\x9a\x25\xd5\x39\xfa" +
|
"\x22\x19\x5a\xb6\x59\x32\xab\xf9\xdb\x1b\x31\x88\x5e\x3d\xbe\xac\x0e\x6d\x1e\x8d\x84\x1a\x4c\x4b\x75\x76\x32\xe8" +
|
||||||
"\xce\x87\x77\xfd\x9f\x68\x7f\x7a\xf7\x3c\xfd\xaf\x31\xa8\x30\x89\x1c\x86\x08\xd5\x67\xc9\x2c\x6d\x2c\xbd\x13\x4d" +
|
"\x75\x87\xcf\x8f\x4f\xb3\xa4\x3a\x47\x5f\xf9\xf0\xae\xff\x1d\xed\x4f\xef\x9e\xa7\xff\x35\x06\x15\x26\x91\xc3\x10" +
|
||||||
"\x6f\x6c\x5d\xff\xb8\xb9\xe7\x7d\xe9\x36\xbd\xf2\x43\x75\x7e\xdf\xbd\x98\x08\x79\x60\xdf\xf0\x9a\x2b\xf7\x83\xd3" +
|
"\xa1\x3a\x97\xcc\xd2\xc6\xd2\x1b\xd1\xf4\xc6\xd6\xf5\xb7\x9b\x7b\xde\x97\x6e\xd3\x2b\x3f\x54\xe7\xf7\xdd\x8b\x89" +
|
||||||
"\xfe\xf1\x8f\x23\x5a\xfe\x16\x38\xef\xbb\x16\xfe\x31\x9c\x10\x31\x8f\x62\x07\x09\xa9\xef\x06\x7d\x9a\x6c\xbb\xb8" +
|
"\x90\x07\xf6\x0d\xaf\xb9\x72\x3f\x38\xed\x1f\x7f\x3b\xa2\xe5\x2f\x81\xf3\xb6\x6b\xe1\xb7\xe1\x84\x88\x79\x14\x3b" +
|
||||||
"\xf8\x3e\x38\xfb\x67\x83\xbe\x7c\x18\xce\xf1\x9d\x9c\x4a\x4b\x3d\x3b\xa7\x61\xed\xb1\x7f\x33\x1b\x7a\xe7\xbd\xe4" +
|
"\x48\x48\x7d\x37\xe8\x6c\xb2\xed\xe2\xe2\xdb\xe0\xec\x9f\x0d\xfa\xf2\x61\x38\xc7\x77\x72\x2a\x2d\xf5\xec\x9c\x86" +
|
||||||
"\x72\x14\x4d\x2e\xdd\xbb\x13\xf5\x58\x3f\xbe\x78\xdf\xfd\xf0\x1a\x0e\x8a\x63\x6d\xc7\x2e\x1a\x5d\x47\x10\x75\x92" +
|
"\xb5\xc7\xfe\xcd\x6c\xe8\x9d\xf7\x92\xcb\x51\x34\xb9\x74\xef\x4e\xd4\x63\xfd\xf8\xe2\x6d\xf7\xc3\x8f\x70\x50\x1c" +
|
||||||
"\x08\x2a\x1c\xc5\x7a\x36\xca\xfb\x34\x3d\xd6\x58\x33\xb8\x86\xb4\xa6\x78\xab\xca\xa5\xd3\x48\xde\x43\x2e\x67\xa2" +
|
"\x6b\x3d\x76\xd1\xe8\x3a\x82\xa8\x93\x44\x50\xe1\x28\xd6\xe3\x55\xde\xa7\xe9\x61\xc6\x9a\xc1\x35\xa4\x35\xc5\x6b" +
|
||||||
"\x69\x48\x14\x06\x59\xb7\xeb\x53\x8e\xb2\x32\xb6\x58\x7c\x24\xfe\x37\x05\x48\x84\x02\x2c\x4d\x8a\x59\xa0\x42\x60" +
|
"\x55\x2e\x9d\x46\xf2\x1e\x72\x39\x09\x4d\x43\xa2\x30\xc8\xba\x5d\x9f\x72\x94\x95\xb1\xc5\xe2\x3d\xf1\xbf\x28\x40" +
|
||||||
"\xbf\xbc\x64\x87\x73\xb1\xb0\xf4\xfe\xcb\x4b\xa1\x58\x2e\x16\x1f\x31\x72\xc3\x8c\xe7\xe5\x65\x5d\x45\xf5\x04\x54" +
|
"\x22\x14\x60\x69\x52\xcc\x02\x15\x02\xfb\xe5\x25\x4b\xce\xc5\xc2\xd2\xfb\x2f\x2f\x85\x62\xb9\x58\xbc\xc7\xc8\x0d" +
|
||||||
"\xb0\x41\x8f\xd0\xc0\xe1\xc2\xd3\x6d\x41\xc1\xfb\xa9\x33\x16\x8b\x55\xbe\xbd\xbc\x14\x63\x83\xa8\x4a\x77\x75\x1b" +
|
"\x33\x9e\x97\x97\x75\x15\xd5\x13\x50\x41\x07\x3d\x95\x01\x87\x0b\x4f\xb7\x05\x05\xeb\xa7\xc6\x58\x2c\x56\xf1\xf6" +
|
||||||
"\xf1\xf2\xb2\xc1\x98\x76\xa0\x19\xb7\x9f\x50\x2a\xc9\x33\xde\x32\xcd\xd2\xa6\x15\x6d\xda\xf2\xcb\xc6\x58\x1f\x42" +
|
"\xf2\x52\xf4\x0d\xa2\x2a\xdd\xd5\x6d\xc4\xcb\xcb\x06\x63\xda\x81\x66\xdc\x7e\x42\xa9\x24\xcf\x78\x4b\x35\x4b\xab" +
|
||||||
"\xbb\x54\xca\xcb\x7c\x5a\xd1\x5d\x4e\x93\x88\xbd\x2a\xf9\x98\x79\x9b\xbd\x5b\x58\x3b\x5c\x99\xd8\xb6\xc2\x5a\x71" +
|
"\x56\xd4\x69\xcb\x2e\x9b\x93\x22\xb4\x4b\xa5\xbc\xcc\xa7\x15\xdd\xe5\x34\x89\xd8\x0f\x25\x1f\x33\x6f\xb3\x77\x0b" +
|
||||||
"\x73\xa7\x4f\x8b\x02\xb6\x9b\x34\x97\x72\x86\xc1\xab\x99\x77\xd9\x24\x6d\x08\xcf\x02\x52\x68\x93\x76\x35\x1b\x3b" +
|
"\x6b\x87\x2b\x15\xdb\x56\x58\x2b\x6e\xee\xb4\x69\x51\xc0\x76\x93\xe6\x52\xce\x30\xf8\x61\x6c\x5e\x36\x49\x1b\xc2" +
|
||||||
"\xb3\x0c\x0b\xc1\xc5\xbf\xcd\x32\xdd\x4b\x6d\xfb\x29\xf2\xa0\x8b\x59\xda\xdc\xed\xec\xad\x66\x12\xda\x25\x3d\xdf" +
|
"\x33\x87\x14\xda\xa4\x5d\xcd\xc6\xce\x28\xc3\x42\x70\xf1\x6f\xa3\x4c\xf7\x52\xdb\x76\x8a\x3c\xe8\x62\x96\x36\x77" +
|
||||||
"\x42\x19\xc1\x06\xc8\xda\xad\x7a\xfa\x40\x03\x58\xdd\x74\x74\x81\x19\x51\x2c\x14\x48\xbf\xa1\x87\x58\xa0\xb5\x2c" +
|
"\x3b\x7b\xab\x99\x84\x76\x49\xcf\xb7\x50\x46\xb0\x01\xb2\x76\xab\x9e\x3e\xd0\x00\x56\x37\x0d\x5d\x60\x46\x14\x0b" +
|
||||||
"\x16\x11\x96\x12\x05\x69\x18\x0a\x7e\x78\xa7\x5b\xda\xd6\x72\x64\x6b\x5b\xcb\xbf\x4d\xff\x3f\x00\x00\xff\xff\x86" +
|
"\x05\xd2\x6f\xe8\x21\x16\xe8\x53\x16\x8b\x08\x4b\x89\x82\xd4\x0d\x05\x3b\xbc\xd1\x2c\x6d\x6b\x39\xb2\xb5\xad\xe5" +
|
||||||
"\xd2\x72\xac\xac\x16\x00\x00")
|
"\x9f\xb7\xff\x1f\x00\x00\xff\xff\x01\x2b\x1d\xba\xef\x16\x00\x00")
|
||||||
|
|
||||||
func bindataFrontendIndexhtmlBytes() ([]byte, error) {
|
func bindataFrontendIndexhtmlBytes() ([]byte, error) {
|
||||||
return bindataRead(
|
return bindataRead(
|
||||||
|
@ -269,7 +228,7 @@ func bindataFrontendIndexhtml() (*asset, error) {
|
||||||
|
|
||||||
info := bindataFileInfo{
|
info := bindataFileInfo{
|
||||||
name: "frontend/index.html",
|
name: "frontend/index.html",
|
||||||
size: 5804,
|
size: 5871,
|
||||||
md5checksum: "",
|
md5checksum: "",
|
||||||
mode: os.FileMode(436),
|
mode: os.FileMode(436),
|
||||||
modTime: time.Unix(1, 0),
|
modTime: time.Unix(1, 0),
|
||||||
|
@ -344,7 +303,6 @@ func AssetNames() []string {
|
||||||
// _bindata is a table, holding each asset generator, mapped to its name.
|
// _bindata is a table, holding each asset generator, mapped to its name.
|
||||||
//
|
//
|
||||||
var _bindata = map[string]func() (*asset, error){
|
var _bindata = map[string]func() (*asset, error){
|
||||||
"frontend/albums.json": bindataFrontendAlbumsjson,
|
|
||||||
"frontend/app.js": bindataFrontendAppjs,
|
"frontend/app.js": bindataFrontendAppjs,
|
||||||
"frontend/index.html": bindataFrontendIndexhtml,
|
"frontend/index.html": bindataFrontendIndexhtml,
|
||||||
}
|
}
|
||||||
|
@ -402,7 +360,6 @@ type bintree struct {
|
||||||
|
|
||||||
var _bintree = &bintree{Func: nil, Children: map[string]*bintree{
|
var _bintree = &bintree{Func: nil, Children: map[string]*bintree{
|
||||||
"frontend": {Func: nil, Children: map[string]*bintree{
|
"frontend": {Func: nil, Children: map[string]*bintree{
|
||||||
"albums.json": {Func: bindataFrontendAlbumsjson, Children: map[string]*bintree{}},
|
|
||||||
"app.js": {Func: bindataFrontendAppjs, Children: map[string]*bintree{}},
|
"app.js": {Func: bindataFrontendAppjs, Children: map[string]*bintree{}},
|
||||||
"index.html": {Func: bindataFrontendIndexhtml, Children: map[string]*bintree{}},
|
"index.html": {Func: bindataFrontendIndexhtml, Children: map[string]*bintree{}},
|
||||||
}},
|
}},
|
||||||
|
|
114
image.go
Normal file
114
image.go
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/disintegration/imaging"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
imgQuality = 95
|
||||||
|
)
|
||||||
|
|
||||||
|
type transformFunc func(img image.Image, width int, height int, filter imaging.ResampleFilter) *image.NRGBA
|
||||||
|
|
||||||
|
func noTransform(img image.Image, width int, height int, filter imaging.ResampleFilter) *image.NRGBA {
|
||||||
|
return imaging.Clone(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
type size struct {
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSize(s string) (size, error) {
|
||||||
|
nums := strings.Split(s, "x")
|
||||||
|
if len(nums) != 2 {
|
||||||
|
return size{}, fmt.Errorf("Invalid size %q", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err := strconv.ParseInt(nums[0], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return size{}, fmt.Errorf("Invalid size %q: %s", s, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h, err := strconv.ParseInt(nums[1], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return size{}, fmt.Errorf("Invalid size %q: %s", s, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return size{
|
||||||
|
width: int(w),
|
||||||
|
height: int(h),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func scaleImage(path string) (thumbnail io.Reader, fullsize io.Reader, err error) {
|
||||||
|
var img image.Image
|
||||||
|
img, err = loadImage(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
thumbnail, err = generateThumbnail(img)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fullsize, err = fitImage(img)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func fitImage(img image.Image) (io.Reader, error) {
|
||||||
|
s, err := parseSize(cfg.FullSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
origW, origH := img.Bounds().Max.X, img.Bounds().Max.Y
|
||||||
|
scaleFactor := math.Max(float64(s.width)/float64(origW), float64(s.height)/float64(origH))
|
||||||
|
|
||||||
|
var t transformFunc = noTransform
|
||||||
|
if scaleFactor < 1 {
|
||||||
|
t = imaging.Fit
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderImage(img, s.width, s.height, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateThumbnail(img image.Image) (io.Reader, error) {
|
||||||
|
s, err := parseSize(cfg.ThumbnailSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderImage(img, s.width, s.height, imaging.Thumbnail)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderImage(img image.Image, w, h int, trans transformFunc) (io.Reader, error) {
|
||||||
|
outImg := trans(img, w, h, imaging.Lanczos)
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if err := imaging.Encode(buf, outImg, imaging.JPEG, imaging.JPEGQuality(imgQuality)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadImage(path string) (image.Image, error) {
|
||||||
|
fp, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer fp.Close()
|
||||||
|
|
||||||
|
return imaging.Decode(fp)
|
||||||
|
}
|
150
main.go
Normal file
150
main.go
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"mime"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/Luzifer/rconfig"
|
||||||
|
"github.com/cheggaaa/pb"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cfg = struct {
|
||||||
|
AlbumID int `flag:"album-id" default:"" description:"ID of the album to import to (treated as sorting key!)" validate:"nonzero"`
|
||||||
|
AlbumTitle string `flag:"album-title" default:"" description:"Title of the album"`
|
||||||
|
AllowExisting bool `flag:"allow-existing" default:"false" description:"Allow inserting into existing gallery"`
|
||||||
|
FullSize string `flag:"full-size" default:"1920x1080" description:"Images will get resized to fit into this without upscaling"`
|
||||||
|
LogLevel string `flag:"log-level" default:"info" description:"Log level (debug, info, warn, error, fatal)"`
|
||||||
|
Storage string `flag:"storage" default:"file://./output" description:"Storage to write results to"`
|
||||||
|
ThumbnailSize string `flag:"thumbnail-size" default:"500x500" description:"Size of the thumbnail to generate"`
|
||||||
|
UpdateFrontend bool `flag:"update-frontend" default:"false" description:"Upload interface files"`
|
||||||
|
VersionAndExit bool `flag:"version" default:"false" description:"Prints current version and exits"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
version = "dev"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if err := rconfig.ParseAndValidate(&cfg); err != nil {
|
||||||
|
log.Fatalf("Unable to parse commandline options: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.VersionAndExit {
|
||||||
|
fmt.Printf("gallery %s\n", version)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if l, err := log.ParseLevel(cfg.LogLevel); err != nil {
|
||||||
|
log.WithError(err).Fatal("Unable to parse log level")
|
||||||
|
} else {
|
||||||
|
log.SetLevel(l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
args := rconfig.Args()[1:]
|
||||||
|
if len(args) == 0 {
|
||||||
|
fmt.Println("Usage: gallery [flags] <folder to import>")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
store, err := getStorageByURI(cfg.Storage)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Fatal("Could not open storage")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.UpdateFrontend {
|
||||||
|
if err := uploadFrontend(store); err != nil {
|
||||||
|
log.WithError(err).Fatal("Could not upload frontend files")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
albumList, err := loadAlbumsFromStorage(store)
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
case errFileNotFound:
|
||||||
|
albumList = &albums{}
|
||||||
|
default:
|
||||||
|
log.WithError(err).Fatal("Unable to load album list")
|
||||||
|
}
|
||||||
|
|
||||||
|
if albumList.HasGallery(cfg.AlbumID) && !cfg.AllowExisting {
|
||||||
|
log.Fatal("Gallery with this ID already exists and allow-existing is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
imagePath := rconfig.Args()[1]
|
||||||
|
|
||||||
|
files, err := ioutil.ReadDir(imagePath)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Fatal("Could not list files in given directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
bar := pb.StartNew(len(files))
|
||||||
|
for _, file := range files {
|
||||||
|
imgSrcPath := path.Join(imagePath, file.Name())
|
||||||
|
flog := log.WithFields(log.Fields{
|
||||||
|
"path": imgSrcPath,
|
||||||
|
})
|
||||||
|
|
||||||
|
thumb, full, err := scaleImage(imgSrcPath)
|
||||||
|
if err != nil {
|
||||||
|
flog.WithError(err).Error("Unable to resize image")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := store.WriteFile(
|
||||||
|
path.Join("photos", strconv.Itoa(cfg.AlbumID), file.Name()+".thumb"),
|
||||||
|
thumb,
|
||||||
|
"image/jpeg",
|
||||||
|
); err != nil {
|
||||||
|
flog.WithError(err).Error("Thumbnail upload failed")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := store.WriteFile(
|
||||||
|
path.Join("photos", strconv.Itoa(cfg.AlbumID), file.Name()),
|
||||||
|
full,
|
||||||
|
"image/jpeg",
|
||||||
|
); err != nil {
|
||||||
|
flog.WithError(err).Error("FullSize upload failed")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := albumList.AddImage(file.Name(), ""); err != nil { // FIXME (kahlers): Title?
|
||||||
|
flog.WithError(err).Error("Could not update album")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
bar.Increment()
|
||||||
|
}
|
||||||
|
bar.Finish()
|
||||||
|
|
||||||
|
if err := albumList.SaveToStorage(store); err != nil {
|
||||||
|
log.WithError(err).Fatal("Could not write album list to storage")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func uploadFrontend(s storage) error {
|
||||||
|
names, err := AssetDir("frontend")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range names {
|
||||||
|
if err := s.WriteFile(
|
||||||
|
name,
|
||||||
|
bytes.NewReader(MustAsset(path.Join("frontend", name))),
|
||||||
|
mime.TypeByExtension(path.Ext(name)),
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
39
storage.go
Normal file
39
storage.go
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type storage interface {
|
||||||
|
// WriteFile takes the content of a file and writes it into the underlying
|
||||||
|
// storage system.
|
||||||
|
WriteFile(filepath string, content io.Reader, contentType string) error
|
||||||
|
// ReadFile retrieves a file from the underlying storage, needs to return
|
||||||
|
// errFileNotFound when file is not present.
|
||||||
|
ReadFile(filepath string) (io.ReadCloser, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var errFileNotFound = errors.New("File not found")
|
||||||
|
|
||||||
|
func getStorageByURI(uri string) (storage, error) {
|
||||||
|
u, err := url.Parse(uri)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch u.Scheme {
|
||||||
|
case "file":
|
||||||
|
return fileStorage{path: strings.TrimPrefix(uri, "file://")}, nil
|
||||||
|
|
||||||
|
case "log":
|
||||||
|
// NoOp storage: Only logs requests
|
||||||
|
return logStorage{}, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Storage scheme %q not defined", u.Scheme)
|
||||||
|
}
|
||||||
|
}
|
43
storage_file.go
Normal file
43
storage_file.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fileStorage struct {
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteFile takes the content of a file and writes it into the underlying
|
||||||
|
// storage system.
|
||||||
|
func (l fileStorage) WriteFile(filepath string, content io.Reader, contentType string) error {
|
||||||
|
fullPath := path.Join(l.path, filepath)
|
||||||
|
if err := os.MkdirAll(path.Dir(fullPath), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fp, err := os.Create(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer fp.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(fp, content)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFile retrieves a file from the underlying storage, needs to return
|
||||||
|
// errFileNotFound when file is not present.
|
||||||
|
func (l fileStorage) ReadFile(filepath string) (io.ReadCloser, error) {
|
||||||
|
fullPath := path.Join(l.path, filepath)
|
||||||
|
if _, err := os.Stat(fullPath); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil, errFileNotFound
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Open(fullPath)
|
||||||
|
}
|
30
storage_log.go
Normal file
30
storage_log.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type logStorage struct{}
|
||||||
|
|
||||||
|
// WriteFile takes the content of a file and writes it into the underlying
|
||||||
|
// storage system.
|
||||||
|
func (l logStorage) WriteFile(filepath string, content io.Reader, contentType string) error {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"path": filepath,
|
||||||
|
"content_type": contentType,
|
||||||
|
}).Info("Write file")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFile retrieves a file from the underlying storage, needs to return
|
||||||
|
// errFileNotFound when file is not present.
|
||||||
|
func (l logStorage) ReadFile(filepath string) (io.ReadCloser, error) {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"path": filepath,
|
||||||
|
}).Info("Read file")
|
||||||
|
return ioutil.NopCloser(bytes.NewReader([]byte{})), errFileNotFound
|
||||||
|
}
|
Loading…
Reference in a new issue