Commits

Miki Tebeka committed 2397c32

imgur

Comments (0)

Files changed (5)

+/* An image bucket service. */
+package main
+
+import (
+	"crypto/md5"
+	"flag"
+	"fmt"
+	"html/template"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"os"
+	"path"
+	"sort"
+	"strings"
+)
+
+var indexTemplate *template.Template
+
+// Where images live
+var imagesRoot string
+
+const (
+	_  = iota // ignore first value
+	KB = 1 << (10 * iota)
+	MB
+	GB
+)
+
+// sort.Iterface
+type FileInfos []os.FileInfo
+
+func (files FileInfos) Len() int {
+	return len(files)
+}
+
+func (files FileInfos) Swap(i, j int) {
+	files[i], files[j] = files[j], files[i]
+}
+
+func (files FileInfos) Less(i, j int) bool {
+	// Sort in reverse order
+	return files[i].ModTime().Unix() > files[j].ModTime().Unix()
+}
+
+// listImages return a list of files in the images directory, sorted in reversed modification time
+func listImages() ([]os.FileInfo, error) {
+	dir, err := os.Open(imagesRoot)
+	if err != nil {
+		return nil, err
+	}
+
+	files, err := dir.Readdir(0)
+	if err != nil {
+		return nil, err
+	}
+
+	sort.Sort(FileInfos(files))
+	return files, nil
+}
+
+// humanSize is a template function that converts size in bytes to "human" format (KB, MB, GB ...)
+func humanSize(args ...interface{}) string {
+	size := float64(args[0].(int64))
+	switch {
+	case size >= GB:
+		return fmt.Sprintf("%.2fGB", size/GB)
+	case size >= MB:
+		return fmt.Sprintf("%.2fMB", size/MB)
+	case size >= KB:
+		return fmt.Sprintf("%.2fKB", size/KB)
+	}
+
+	return fmt.Sprintf("%dB", size)
+}
+
+// Env is the template environment both for index and image pages
+type Env struct {
+	Files  []os.FileInfo
+	Image  string
+}
+
+// indexHandler handler the index page which shows list of images
+func indexHandler(w http.ResponseWriter, req *http.Request) {
+	files, err := listImages()
+	if err != nil {
+		http.Error(w, fmt.Sprintf("Can't list images: %s", err), http.StatusInternalServerError)
+		return
+	}
+
+	env := Env{
+		Files:  files,
+	}
+
+	if err = indexTemplate.Execute(w, env); err != nil {
+		log.Printf("ERROR: can't execute template: %s", err)
+	}
+}
+
+// imageFilename returns a file name for uploaded image, basically it's MD5 hash
+func imageFilename(data []byte, originalName string) string {
+	ext := strings.ToLower(path.Ext(originalName))
+
+	h := md5.New()
+	h.Write(data)
+
+	return fmt.Sprintf("%s/%x%s", imagesRoot, h.Sum(nil), ext)
+}
+
+// newHandler handles POST requests for new images
+func newHandler(w http.ResponseWriter, req *http.Request) {
+	file, header, err := req.FormFile("image")
+	if err != nil {
+		http.Error(w, fmt.Sprintf("No image: %s", err), http.StatusBadRequest)
+		return
+	}
+
+	mimeType := header.Header.Get("Content-Type")
+	if !strings.HasPrefix(mimeType, "image/") {
+		http.Error(w, "Not an image", http.StatusBadRequest)
+		return
+	}
+
+	data, err := ioutil.ReadAll(file)
+	if err != nil {
+		http.Error(w, "Can't read", http.StatusBadRequest)
+		return
+	}
+
+	outfile := imageFilename(data, header.Filename)
+	err = ioutil.WriteFile(outfile, data, os.FileMode(0666))
+	if err != nil {
+		http.Error(w, "Can't write file", http.StatusInternalServerError)
+		return
+	}
+
+	url := "/img/" + path.Base(outfile)
+	http.Redirect(w, req, url, http.StatusFound)
+}
+
+// imgHandler shows requested image
+func imgHandler(w http.ResponseWriter, req *http.Request) {
+	image := path.Base(req.URL.Path)
+	env := Env{
+		Image:  image,
+	}
+
+	if err := indexTemplate.Execute(w, env); err != nil {
+		log.Printf("ERROR: can't execute template: %s", err)
+	}
+}
+
+// staticDir registers a static directory handler.
+func staticDir(dir, url string) {
+	server := http.FileServer(http.Dir(dir))
+	prefix := url[:len(url)-1]
+	http.Handle(url, http.StripPrefix(prefix, server))
+}
+
+// loadTemplate loads the index template from disk and registers "human" function
+func loadTemplate() (*template.Template, error) {
+	t := template.New("index.html")
+	t = t.Funcs(template.FuncMap{"human": humanSize})
+
+	t, err := t.ParseFiles("index.html")
+
+	return t, err
+}
+
+func ensureDirExists(root string) error {
+	// Create images directory, don't care if fails
+	os.Mkdir(imagesRoot, os.FileMode(0777))
+
+	var dir *os.File
+	var err error
+	if dir, err = os.Open(imagesRoot); err != nil {
+		return fmt.Errorf("can't open %s: %s", root, err)
+	}
+	defer dir.Close()
+
+	var fi os.FileInfo
+	if fi, err = dir.Stat(); err != nil {
+		return fmt.Errorf("can't stat: %s", root)
+	}
+
+	if !fi.IsDir() {
+		return fmt.Errorf("%s is not a directory", root)
+	}
+
+	return nil
+}
+
+func main() {
+	var port = flag.Int("port", 8777, "port to listen on")
+	flag.StringVar(&imagesRoot, "image_dir", "images", "images root dir")
+	flag.Parse()
+
+	err := ensureDirExists(imagesRoot)
+	if err != nil {
+		log.Fatalf("error: %s does is not a directory (and I can't create it) - %s", imagesRoot, err)
+	}
+
+	indexTemplate, err = loadTemplate()
+	if err != nil {
+		log.Fatalf("error: can't parse template - %s\n", err)
+	}
+
+	http.HandleFunc("/", indexHandler)
+	http.HandleFunc("/new", newHandler)
+	http.HandleFunc("/img/", imgHandler)
+	staticDir("static", "/static/")
+	staticDir(imagesRoot, "/images/")
+
+	addr := fmt.Sprintf(":%d", *port)
+	fmt.Printf("Serving on %s\n", addr)
+	http.ListenAndServe(addr, nil)
+}
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Adconion - IMGUR</title>
+        <link rel="stylesheet" href="/static/style.css" />
+        <link rel="shortcut icon" href="/static/logo.png" />
+    </head>
+    <body>
+        <span class="right"><a href="/">IMGUR</a></span>
+        <form action="/new" method="POST" enctype="multipart/form-data">
+            <input type="file" name="image" />
+            <input type="submit" value="Upload" />
+        </form>
+        <hr />
+        {{ if .Image }}
+        <img src="/images/{{.Image}}" />
+        {{ else }}
+        <div class="images">
+            <table>
+                {{range .Files}}
+                    <tr>
+                        <td><a href="/img/{{.Name}}">
+                                <img class="thumb" src="/images/{{.Name}}" />
+                        </a></td>
+                        <td>
+                            <label>Uploaded:</label> {{.ModTime}} <br />
+                            <label>Size:</label> {{.Size | human}}
+                        </td>
+                    </tr>
+                {{end}}
+            </table>
+        </div>
+        {{ end }}
+    </body>
+    <script
+        src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js">
+    </script>
+    <script src="/static/imgur.js"></script>
+</html>

imgur/static/imgur.js

+function on_submit() {
+    var image = $('input[name="image"]').val();
+    if (image.length == 0) {
+        alert('No file chosen');
+        return false;
+    }
+
+    return true;
+}
+
+function on_ready() {
+    $('form').submit(on_submit);
+    $('div.images').height($('body').height() - $('form').height() - 120);
+}
+
+$(on_ready);

imgur/static/logo.png

Added
New image

imgur/static/style.css

+img.thumb {
+    width: 80px;
+    height: 80px;
+}
+
+body {
+    margin-right: 5%;
+    margin-left: 5%;
+}
+
+div.images {
+    overflow-y: scroll;
+    min-height: 600px;
+}
+
+table {
+    width: 100%;
+}
+
+label {
+    font-weight: bold;
+}
+
+td {
+    vertical-align: bottom;
+    font-size: 1.2em;
+}
+
+span.right {
+    float: right;
+}