Huge rework to more modern code
This commit is contained in:
parent
6564d9c4fa
commit
4fcf12dff2
37
api.go
37
api.go
@ -12,24 +12,31 @@ import (
|
|||||||
|
|
||||||
type DispatchFunction func(*User, []string, io.ReadCloser) (interface{}, error)
|
type DispatchFunction func(*User, []string, io.ReadCloser) (interface{}, error)
|
||||||
|
|
||||||
var apiRoutes = map[string]*(map[string]struct {
|
type apiHandler struct {
|
||||||
|
PE *PictureExplorer
|
||||||
|
Authenticate func(*http.Request) *User
|
||||||
|
routes map[string](map[string]struct {
|
||||||
|
AuthFunction
|
||||||
|
DispatchFunction
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAPIHandler(pe *PictureExplorer, authenticate func(*http.Request) *User) *apiHandler {
|
||||||
|
return &apiHandler{
|
||||||
|
pe,
|
||||||
|
authenticate,
|
||||||
|
map[string](map[string]struct {
|
||||||
AuthFunction
|
AuthFunction
|
||||||
DispatchFunction
|
DispatchFunction
|
||||||
}){
|
}){
|
||||||
"images": &ApiImagesRouting,
|
"images": ApiImagesRouting(pe),
|
||||||
"next": &ApiNextImagesRouting,
|
"next": ApiNextImagesRouting(pe),
|
||||||
"version": &ApiVersionRouting,
|
"version": ApiVersionRouting,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type apiHandler struct {
|
func (a *apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
Authenticate func(*http.Request) *User
|
|
||||||
}
|
|
||||||
|
|
||||||
func ApiHandler(Authenticate func(*http.Request) *User) apiHandler {
|
|
||||||
return apiHandler{Authenticate}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
user := a.Authenticate(r)
|
user := a.Authenticate(r)
|
||||||
|
|
||||||
log.Printf("Handling %s API request from %s: %s %s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, user, r.UserAgent())
|
log.Printf("Handling %s API request from %s: %s %s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, user, r.UserAgent())
|
||||||
@ -55,8 +62,8 @@ func (a apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Route request
|
// Route request
|
||||||
if len(sURL) == 0 {
|
if len(sURL) == 0 {
|
||||||
err = errors.New(fmt.Sprintf("No action provided"))
|
err = errors.New(fmt.Sprintf("No action provided"))
|
||||||
} else if h, ok := apiRoutes[sURL[0]]; ok {
|
} else if h, ok := a.routes[sURL[0]]; ok {
|
||||||
if f, ok := (*h)[r.Method]; ok {
|
if f, ok := h[r.Method]; ok {
|
||||||
if f.AuthFunction(user, sURL[1:]) {
|
if f.AuthFunction(user, sURL[1:]) {
|
||||||
ret, err = f.DispatchFunction(user, sURL[1:], r.Body)
|
ret, err = f.DispatchFunction(user, sURL[1:], r.Body)
|
||||||
} else {
|
} else {
|
||||||
|
@ -5,78 +5,88 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ApiImagesRouting = map[string]struct {
|
func ApiImagesRouting(pe *PictureExplorer) map[string]struct {
|
||||||
AuthFunction
|
AuthFunction
|
||||||
DispatchFunction
|
DispatchFunction
|
||||||
} {
|
} {
|
||||||
"GET": {PublicPage, listImages},
|
return map[string]struct {
|
||||||
"POST": {PublicPage, addImage},
|
|
||||||
"DELETE": {PrivatePage, hideImage},
|
|
||||||
}
|
|
||||||
|
|
||||||
var ApiNextImagesRouting = map[string]struct {
|
|
||||||
AuthFunction
|
AuthFunction
|
||||||
DispatchFunction
|
DispatchFunction
|
||||||
}{
|
}{
|
||||||
"GET": {PrivatePage, listNextImages},
|
"GET": {PublicPage, pe.listImages},
|
||||||
"POST": {PublicPage, addImage},
|
"POST": {PublicPage, pe.addImage},
|
||||||
"DELETE": {PrivatePage, deleteImage},
|
"DELETE": {PrivatePage, pe.hideImage},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func listImages(u *User, args []string, body io.ReadCloser) (interface{}, error) {
|
func ApiNextImagesRouting(pe *PictureExplorer) map[string]struct {
|
||||||
|
AuthFunction
|
||||||
|
DispatchFunction
|
||||||
|
} {
|
||||||
|
return map[string]struct {
|
||||||
|
AuthFunction
|
||||||
|
DispatchFunction
|
||||||
|
}{
|
||||||
|
"GET": {PrivatePage, pe.listNextImages},
|
||||||
|
"POST": {PublicPage, pe.addImage},
|
||||||
|
"DELETE": {PrivatePage, pe.deleteImage},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pe *PictureExplorer) listImages(u *User, args []string, body io.ReadCloser) (interface{}, error) {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return GetPublishedImages()
|
return pe.GetPublishedImages()
|
||||||
} else if args[0] == "last" {
|
} else if args[0] == "last" {
|
||||||
return GetLastImage()
|
return pe.GetLastImage()
|
||||||
} else {
|
} else {
|
||||||
return GetPublishedImage(args[0])
|
return pe.GetPublishedImage(args[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func listNextImages(u *User, args []string, body io.ReadCloser) (interface{}, error) {
|
func (pe *PictureExplorer) listNextImages(u *User, args []string, body io.ReadCloser) (interface{}, error) {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return GetNextImages()
|
return pe.GetNextImages()
|
||||||
} else if len(args) >= 2 && args[1] == "publish" {
|
} else if len(args) >= 2 && args[1] == "publish" {
|
||||||
if pict, err := GetNextImage(args[0]); err != nil {
|
if pict, err := pe.GetNextImage(args[0]); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if err := pict.Publish(); err != nil {
|
} else if err := pe.Publish(pict); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return GetNextImage(args[0])
|
return pe.GetNextImage(args[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addImage(u *User, args []string, body io.ReadCloser) (interface{}, error) {
|
func (pe *PictureExplorer) addImage(u *User, args []string, body io.ReadCloser) (interface{}, error) {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return nil, errors.New("Need an image identifier to create")
|
return nil, errors.New("Need an image identifier to create")
|
||||||
} else if err := AddImage(args[0], body); err != nil {
|
} else if err := pe.AddImage(args[0], body); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func hideImage(u *User, args []string, body io.ReadCloser) (interface{}, error) {
|
func (pe *PictureExplorer) hideImage(u *User, args []string, body io.ReadCloser) (interface{}, error) {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return nil, errors.New("Need an image identifier to delete")
|
return nil, errors.New("Need an image identifier to delete")
|
||||||
} else if pict, err := GetPublishedImage(args[0]); err != nil {
|
} else if pict, err := pe.GetPublishedImage(args[0]); err != nil {
|
||||||
return nil, errors.New("No matching image")
|
return nil, errors.New("No matching image")
|
||||||
} else if err := pict.Unpublish(); err != nil {
|
} else if err := pe.Unpublish(pict); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteImage(u *User, args []string, body io.ReadCloser) (interface{}, error) {
|
func (pe *PictureExplorer) deleteImage(u *User, args []string, body io.ReadCloser) (interface{}, error) {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return nil, errors.New("Need an image identifier to delete")
|
return nil, errors.New("Need an image identifier to delete")
|
||||||
} else if pict, err := GetNextImage(args[0]); err != nil {
|
} else if pict, err := pe.GetNextImage(args[0]); err != nil {
|
||||||
return nil, errors.New("No matching image")
|
return nil, errors.New("No matching image")
|
||||||
} else if err := pict.Remove(); err != nil {
|
} else if err := pe.Remove(pict); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return true, nil
|
return true, nil
|
||||||
|
44
backend.go
Normal file
44
backend.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var existing_backends []string
|
||||||
|
|
||||||
|
type BackendSelector string
|
||||||
|
|
||||||
|
func (s *BackendSelector) String() string {
|
||||||
|
return string(*s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *BackendSelector) Set(v string) error {
|
||||||
|
found := false
|
||||||
|
for _, b := range existing_backends {
|
||||||
|
if strings.ToLower(v) == b {
|
||||||
|
found = true
|
||||||
|
tmp := BackendSelector(v)
|
||||||
|
s = &tmp
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("%q is not a known file backend (existing backends: %v)", v, existing_backends)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileBackend interface {
|
||||||
|
DeletePicture(string, string) error
|
||||||
|
ServeFile() http.Handler
|
||||||
|
GetPicture(string, string, io.Writer) error
|
||||||
|
GetPictureInfo(string, string) (*Picture, error)
|
||||||
|
ListPictures(string) ([]*Picture, error)
|
||||||
|
MovePicture(string, string, string) error
|
||||||
|
PutPicture(string, string, *image.Image) error
|
||||||
|
}
|
106
backend_local.go
Normal file
106
backend_local.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/jpeg"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
existing_backends = append(existing_backends, "local")
|
||||||
|
}
|
||||||
|
|
||||||
|
type LocalFileBackend struct {
|
||||||
|
BaseDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LocalFileBackend) getPath(box, name string) string {
|
||||||
|
return path.Join(l.BaseDir, box, name+".jpg")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LocalFileBackend) DeletePicture(box, name string) error {
|
||||||
|
return os.Remove(l.getPath(box, name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LocalFileBackend) ServeFile() http.Handler {
|
||||||
|
return http.FileServer(http.Dir(l.BaseDir))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LocalFileBackend) createPictureInfo(local_path, name string, file os.FileInfo) *Picture {
|
||||||
|
return &Picture{
|
||||||
|
local_path, // Path
|
||||||
|
name, // Basename
|
||||||
|
name[:len(name)-len(path.Ext(name))], // Sanitized filename
|
||||||
|
file.ModTime(), // UploadTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LocalFileBackend) GetPicture(box, name string, w io.Writer) error {
|
||||||
|
local_path := l.getPath(box, name)
|
||||||
|
|
||||||
|
fd, err := os.Open(local_path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer fd.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(w, fd)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LocalFileBackend) GetPictureInfo(box, name string) (*Picture, error) {
|
||||||
|
local_path := l.getPath(box, name)
|
||||||
|
|
||||||
|
file, err := os.Stat(local_path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return l.createPictureInfo(path.Join(box, name+".jpg"), name, file), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LocalFileBackend) ListPictures(box string) ([]*Picture, error) {
|
||||||
|
files, err := ioutil.ReadDir(path.Join(l.BaseDir, box))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var pictures []*Picture
|
||||||
|
for _, file := range files {
|
||||||
|
if !file.IsDir() {
|
||||||
|
pictures = append(pictures, l.createPictureInfo(path.Join(box, file.Name()), file.Name(), file))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pictures, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LocalFileBackend) MovePicture(box_from, box_to, name string) error {
|
||||||
|
newpath := l.getPath(box_to, name)
|
||||||
|
|
||||||
|
err := os.Rename(
|
||||||
|
l.getPath(box_from, name),
|
||||||
|
newpath,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Chtimes(newpath, time.Now(), time.Now())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LocalFileBackend) PutPicture(box, name string, img *image.Image) error {
|
||||||
|
fd, err := os.OpenFile(l.getPath(box, name), os.O_RDWR|os.O_CREATE, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer fd.Close()
|
||||||
|
|
||||||
|
return jpeg.Encode(fd, *img, nil)
|
||||||
|
}
|
@ -10,10 +10,10 @@ type imagesHandler struct {
|
|||||||
name string
|
name string
|
||||||
auth func(*http.Request) bool
|
auth func(*http.Request) bool
|
||||||
hndlr http.Handler
|
hndlr http.Handler
|
||||||
getter func(fname string) (Picture, error)
|
getter func(fname string) (*Picture, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ImagesHandler(name string, auth func(*http.Request) bool, hndlr http.Handler, getter func(fname string) (Picture, error)) imagesHandler {
|
func ImagesHandler(name string, auth func(*http.Request) bool, hndlr http.Handler, getter func(fname string) (*Picture, error)) imagesHandler {
|
||||||
return imagesHandler{name, auth, hndlr, getter}
|
return imagesHandler{name, auth, hndlr, getter}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ func (i imagesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, err.Error(), http.StatusNotFound)
|
http.Error(w, err.Error(), http.StatusNotFound)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
r.URL.Path = "/" + pict.basename
|
r.URL.Path = "/" + pict.path
|
||||||
i.hndlr.ServeHTTP(w, r)
|
i.hndlr.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
40
main.go
40
main.go
@ -9,15 +9,13 @@ import (
|
|||||||
|
|
||||||
var mux = http.NewServeMux()
|
var mux = http.NewServeMux()
|
||||||
|
|
||||||
var PublishedImgDir string
|
|
||||||
var NextImgDir string
|
|
||||||
var ThumbsDir string
|
var ThumbsDir string
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
bind := flag.String("bind", ":8080", "Bind port/socket")
|
bind := flag.String("bind", ":8080", "Bind port/socket")
|
||||||
htpasswd_file := flag.String("htpasswd", "", "Admin passwords file, Apache htpasswd format")
|
htpasswd_file := flag.String("htpasswd", "", "Admin passwords file, Apache htpasswd format")
|
||||||
flag.StringVar(&PublishedImgDir, "publishedimgdir", "./images/published/", "Directory where save published pictures")
|
publishedImgDir := flag.String("publishedimgdir", "published/", "Directory where save published pictures")
|
||||||
flag.StringVar(&NextImgDir, "nextimgdir", "./images/next/", "Directory where save pictures to review")
|
nextImgDir := flag.String("nextimgdir", "next/", "Directory where save pictures to review")
|
||||||
flag.StringVar(&ThumbsDir, "thumbsdir", "./images/thumbs/", "Directory where generate thumbs")
|
flag.StringVar(&ThumbsDir, "thumbsdir", "./images/thumbs/", "Directory where generate thumbs")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
@ -29,22 +27,12 @@ func main() {
|
|||||||
if htpasswd, err = NewHtpasswd(*htpasswd_file); htpasswd == nil {
|
if htpasswd, err = NewHtpasswd(*htpasswd_file); htpasswd == nil {
|
||||||
log.Fatal("Unable to parse htpasswd:", err)
|
log.Fatal("Unable to parse htpasswd:", err)
|
||||||
}
|
}
|
||||||
} else if NextImgDir == "./images/next/" {
|
} else if *nextImgDir == "./images/next/" {
|
||||||
log.Println("Disable admin interface, images will be published without moderation")
|
log.Println("Disable admin interface, images will be published without moderation")
|
||||||
NextImgDir = PublishedImgDir
|
nextImgDir = publishedImgDir
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Checking paths...")
|
log.Println("Checking paths...")
|
||||||
if _, err := os.Stat(PublishedImgDir); os.IsNotExist(err) {
|
|
||||||
if err := os.MkdirAll(PublishedImgDir, 0755); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(NextImgDir); os.IsNotExist(err) {
|
|
||||||
if err := os.MkdirAll(NextImgDir, 0755); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(ThumbsDir); os.IsNotExist(err) {
|
if _, err := os.Stat(ThumbsDir); os.IsNotExist(err) {
|
||||||
if err := os.MkdirAll(ThumbsDir, 0755); err != nil {
|
if err := os.MkdirAll(ThumbsDir, 0755); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -63,32 +51,38 @@ func main() {
|
|||||||
http.FileServer(Assets).ServeHTTP(w, r)
|
http.FileServer(Assets).ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.Handle("/api/", http.StripPrefix("/api", ApiHandler(authFunc)))
|
pe := &PictureExplorer{
|
||||||
|
FileBackend: &LocalFileBackend{"./images/"},
|
||||||
|
PublishedImgDir: *publishedImgDir,
|
||||||
|
NextImgDir: *nextImgDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
mux.Handle("/api/", http.StripPrefix("/api", NewAPIHandler(pe, authFunc)))
|
||||||
|
|
||||||
mux.Handle("/images/", http.StripPrefix("/images", ImagesHandler(
|
mux.Handle("/images/", http.StripPrefix("/images", ImagesHandler(
|
||||||
"images",
|
"images",
|
||||||
func(*http.Request) bool { return true },
|
func(*http.Request) bool { return true },
|
||||||
http.FileServer(http.Dir(PublishedImgDir)),
|
pe.ServeFile(),
|
||||||
GetPublishedImage,
|
pe.GetPublishedImage,
|
||||||
)))
|
)))
|
||||||
mux.Handle("/images/next/", http.StripPrefix("/images/next", ImagesHandler(
|
mux.Handle("/images/next/", http.StripPrefix("/images/next", ImagesHandler(
|
||||||
"next",
|
"next",
|
||||||
func(r *http.Request) bool { return authFunc(r) != nil },
|
func(r *http.Request) bool { return authFunc(r) != nil },
|
||||||
http.FileServer(http.Dir(NextImgDir)),
|
pe.ServeFile(),
|
||||||
GetNextImage,
|
pe.GetNextImage,
|
||||||
)))
|
)))
|
||||||
|
|
||||||
mux.Handle("/images/thumbs/", http.StripPrefix("/images/thumbs", ImagesHandler(
|
mux.Handle("/images/thumbs/", http.StripPrefix("/images/thumbs", ImagesHandler(
|
||||||
"thumbs",
|
"thumbs",
|
||||||
func(*http.Request) bool { return true },
|
func(*http.Request) bool { return true },
|
||||||
http.FileServer(http.Dir(ThumbsDir)),
|
http.FileServer(http.Dir(ThumbsDir)),
|
||||||
GetPublishedImage,
|
pe.GetPublishedImage,
|
||||||
)))
|
)))
|
||||||
mux.Handle("/images/next/thumbs/", http.StripPrefix("/images/next/thumbs", ImagesHandler(
|
mux.Handle("/images/next/thumbs/", http.StripPrefix("/images/next/thumbs", ImagesHandler(
|
||||||
"nexthumbs",
|
"nexthumbs",
|
||||||
func(r *http.Request) bool { return authFunc(r) != nil },
|
func(r *http.Request) bool { return authFunc(r) != nil },
|
||||||
http.FileServer(http.Dir(ThumbsDir)),
|
http.FileServer(http.Dir(ThumbsDir)),
|
||||||
GetNextImage,
|
pe.GetNextImage,
|
||||||
)))
|
)))
|
||||||
|
|
||||||
mux.HandleFunc("/admin/", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/admin/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
149
picture.go
149
picture.go
@ -8,16 +8,20 @@ import (
|
|||||||
"image/jpeg"
|
"image/jpeg"
|
||||||
_ "image/png"
|
_ "image/png"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nfnt/resize"
|
"github.com/nfnt/resize"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type PictureExplorer struct {
|
||||||
|
FileBackend
|
||||||
|
PublishedImgDir string
|
||||||
|
NextImgDir string
|
||||||
|
}
|
||||||
|
|
||||||
type Picture struct {
|
type Picture struct {
|
||||||
path string
|
path string
|
||||||
basename string
|
basename string
|
||||||
@ -25,7 +29,7 @@ type Picture struct {
|
|||||||
UploadTime time.Time `json:"upload_time"`
|
UploadTime time.Time `json:"upload_time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ByUploadTime []Picture
|
type ByUploadTime []*Picture
|
||||||
|
|
||||||
func (a ByUploadTime) Len() int { return len(a) }
|
func (a ByUploadTime) Len() int { return len(a) }
|
||||||
func (a ByUploadTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
func (a ByUploadTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
@ -33,137 +37,84 @@ func (a ByUploadTime) Less(i, j int) bool {
|
|||||||
return a[i].UploadTime.Sub(a[j].UploadTime).Nanoseconds() < 0
|
return a[i].UploadTime.Sub(a[j].UploadTime).Nanoseconds() < 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func getImages(dir string) ([]Picture, error) {
|
func (e *PictureExplorer) GetNextImages() (pictures []*Picture, err error) {
|
||||||
if files, err := ioutil.ReadDir(dir); err != nil {
|
pictures, err = e.ListPictures(e.NextImgDir)
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
pictures := make([]Picture, 0)
|
|
||||||
for _, file := range files {
|
|
||||||
if !file.IsDir() {
|
|
||||||
filename := file.Name()
|
|
||||||
pictures = append(pictures, Picture{
|
|
||||||
filepath.Join(dir, filename), // Path
|
|
||||||
filename, // Basename
|
|
||||||
filename[:len(filename)-len(filepath.Ext(filename))], // Sanitized filename
|
|
||||||
file.ModTime(), // UploadTime
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Sort(ByUploadTime(pictures))
|
sort.Sort(ByUploadTime(pictures))
|
||||||
return pictures, nil
|
return
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetNextImages() ([]Picture, error) {
|
func (e *PictureExplorer) GetPublishedImages() (pictures []*Picture, err error) {
|
||||||
return getImages(NextImgDir)
|
pictures, err = e.ListPictures(e.PublishedImgDir)
|
||||||
|
sort.Sort(ByUploadTime(pictures))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPublishedImages() ([]Picture, error) {
|
func (e *PictureExplorer) GetLastImage() (*Picture, error) {
|
||||||
return getImages(PublishedImgDir)
|
picts, err := e.GetPublishedImages()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetLastImage() (Picture, error) {
|
|
||||||
if picts, err := GetPublishedImages(); err != nil {
|
|
||||||
return Picture{}, err
|
|
||||||
} else {
|
|
||||||
return picts[len(picts)-1], nil
|
return picts[len(picts)-1], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *PictureExplorer) GetPublishedImage(fname string) (*Picture, error) {
|
||||||
|
return e.GetPictureInfo(e.PublishedImgDir, fname)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getImage(flist func() ([]Picture, error), fname string) (Picture, error) {
|
func (e *PictureExplorer) GetNextImage(fname string) (*Picture, error) {
|
||||||
if picts, err := flist(); err != nil {
|
return e.GetPictureInfo(e.NextImgDir, fname)
|
||||||
return Picture{}, err
|
|
||||||
} else if pid, err := strconv.Atoi(fname); err == nil {
|
|
||||||
if pid < len(picts) {
|
|
||||||
return picts[pid], nil
|
|
||||||
} else {
|
|
||||||
return Picture{}, errors.New("Invalid picture identifier")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for _, pict := range picts {
|
|
||||||
if pict.Name == fname || pict.basename == fname {
|
|
||||||
return pict, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Picture{}, errors.New("No such picture")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPublishedImage(fname string) (Picture, error) {
|
func (e *PictureExplorer) IsUniqueName(filename string) bool {
|
||||||
return getImage(GetPublishedImages, fname)
|
if pict, _ := e.GetPublishedImage(filename); pict != nil {
|
||||||
}
|
|
||||||
|
|
||||||
func GetNextImage(fname string) (Picture, error) {
|
|
||||||
return getImage(GetNextImages, fname)
|
|
||||||
}
|
|
||||||
|
|
||||||
func UniqueImage(filename string) bool {
|
|
||||||
if pict, _ := GetPublishedImage(filename); pict.path != "" {
|
|
||||||
return false
|
return false
|
||||||
} else {
|
}
|
||||||
if pict, _ := GetNextImage(filename); pict.path != "" {
|
|
||||||
|
if pict, _ := e.GetNextImage(filename); pict != nil {
|
||||||
return false
|
return false
|
||||||
} else {
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func AddImage(filename string, blob io.ReadCloser) error {
|
func (e *PictureExplorer) AddImage(filename string, blob io.ReadCloser) error {
|
||||||
// Check the name is not already used
|
// Check the name is not already used
|
||||||
if ok := UniqueImage(filename); !ok {
|
if ok := e.IsUniqueName(filename); !ok {
|
||||||
return errors.New("This filename is already used, please choose another one.")
|
return errors.New("This filename is already used, please choose another one.")
|
||||||
|
}
|
||||||
|
|
||||||
// Convert to JPEG
|
// Convert to JPEG
|
||||||
} else if img, _, err := image.Decode(base64.NewDecoder(base64.StdEncoding, blob)); err != nil {
|
img, _, err := image.Decode(base64.NewDecoder(base64.StdEncoding, blob))
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Save file
|
// Save file
|
||||||
} else if fw, err := os.Create(filepath.Join(NextImgDir, filename+".jpg")); err != nil {
|
if err := e.PutPicture(e.NextImgDir, filename, &img); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if err := jpeg.Encode(fw, img, nil); err != nil {
|
}
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
fw.Close()
|
|
||||||
|
|
||||||
thumb := resize.Thumbnail(300, 185, img, resize.Lanczos3)
|
thumb := resize.Thumbnail(300, 185, img, resize.Lanczos3)
|
||||||
|
|
||||||
// Save thumbnail
|
// Save thumbnail
|
||||||
if fw, err := os.Create(filepath.Join(ThumbsDir, filename+".jpg")); err != nil {
|
fw, err := os.Create(path.Join(ThumbsDir, filename+".jpg"))
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if err := jpeg.Encode(fw, thumb, nil); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
fw.Close()
|
|
||||||
}
|
}
|
||||||
|
defer fw.Close()
|
||||||
|
|
||||||
|
return jpeg.Encode(fw, thumb, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
func (e *PictureExplorer) Publish(p *Picture) error {
|
||||||
|
return e.MovePicture(e.NextImgDir, e.PublishedImgDir, p.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Picture) Publish() error {
|
func (e *PictureExplorer) Unpublish(p *Picture) error {
|
||||||
npath := filepath.Join(PublishedImgDir, p.basename)
|
return e.MovePicture(e.PublishedImgDir, e.NextImgDir, p.Name)
|
||||||
if err := os.Rename(p.path, npath); err != nil {
|
|
||||||
return err
|
|
||||||
} else if err := os.Chtimes(npath, time.Now(), time.Now()); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Picture) Unpublish() error {
|
func (e *PictureExplorer) Remove(p *Picture) error {
|
||||||
if err := os.Rename(p.path, filepath.Join(NextImgDir, p.basename)); err != nil {
|
return e.DeletePicture(path.Dir(p.path), p.Name)
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Picture) Remove() error {
|
|
||||||
if err := os.Remove(p.path); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const VERSION = 0.3
|
||||||
|
|
||||||
var ApiVersionRouting = map[string]struct {
|
var ApiVersionRouting = map[string]struct {
|
||||||
AuthFunction
|
AuthFunction
|
||||||
DispatchFunction
|
DispatchFunction
|
||||||
@ -12,7 +14,7 @@ var ApiVersionRouting = map[string]struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func showVersion(u *User, args []string, body io.ReadCloser) (interface{}, error) {
|
func showVersion(u *User, args []string, body io.ReadCloser) (interface{}, error) {
|
||||||
m := map[string]interface{}{"version": 0.2}
|
m := map[string]interface{}{"version": VERSION}
|
||||||
|
|
||||||
if u != nil {
|
if u != nil {
|
||||||
m["youare"] = *u
|
m["youare"] = *u
|
||||||
|
Loading…
Reference in New Issue
Block a user