Browse Source

Add preliminary metadata support

pull/6/head
Matt Hazinski 9 years ago
parent
commit
a10b838f4d
  1. 1
      .gitignore
  2. 44
      expiry.go
  3. 12
      fileserve.go
  4. 89
      meta.go
  5. 30
      server.go
  6. 56
      upload.go

1
.gitignore

@ -28,3 +28,4 @@ _testmain.go
linx-server linx-server
files/ files/
meta/

44
expiry.go

@ -0,0 +1,44 @@
package main
import (
"time"
)
// Get what the unix timestamp will be in "seconds".
func getFutureTimestamp(seconds int32) (ts int32) {
now := int32(time.Now().Unix())
if seconds == 0 {
ts = 0
} else {
ts = now + seconds
}
return
}
// Determine if a file with expiry set to "ts" has expired yet
func isTsExpired(ts int32) (expired bool) {
now := int32(time.Now().Unix())
if ts == 0 {
expired = false
} else if now > ts {
expired = true
} else {
expired = false
}
return
}
// Determine if the given filename is expired
func isFileExpired(filename string) (bool, error) {
exp, err := metadataGetExpiry(filename)
if err != nil {
return true, err
} else {
return isTsExpired(exp), err
}
}

12
fileserve.go

@ -18,7 +18,17 @@ func fileServeHandler(c web.C, w http.ResponseWriter, r *http.Request) {
return return
} }
// plug file expiry checking here
expired, expErr := isFileExpired(fileName)
if expErr != nil {
// Error reading metadata, pretend it's expired
notFoundHandler(c, w, r)
// TODO log error internally
return
} else if expired {
notFoundHandler(c, w, r)
// TODO delete the file
}
http.ServeFile(w, r, filePath) http.ServeFile(w, r, filePath)
} }

89
meta.go

@ -0,0 +1,89 @@
package main
import (
"bufio"
"errors"
"fmt"
"os"
"path"
"strconv"
)
// Write metadata from Upload struct to file
func metadataWrite(filename string, upload *Upload) error {
// Write metadata, overwriting if necessary
file, err := os.Create(path.Join(Config.metaDir, upload.Filename))
if err != nil {
return err
}
defer file.Close()
w := bufio.NewWriter(file)
fmt.Fprintln(w, upload.Expiry)
fmt.Fprintln(w, upload.DeleteKey)
fmt.Fprintln(w, upload.DebugInfo)
return w.Flush()
}
// Return list of strings from a filename's metadata source
func metadataRead(filename string) ([]string, error) {
file, err := os.Create(path.Join(Config.metaDir, filename))
if err != nil {
return nil, err
}
defer file.Close()
var lines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
return lines, scanner.Err()
}
func metadataGetExpiry(filename string) (int32, error) {
metadata, err := metadataRead(filename)
if len(metadata) < 1 {
err := errors.New("ERR: Metadata file does not contain expiry")
return 0, err
}
// XXX in this case it's up to the caller to determine proper behavior
// for a nonexistant metadata file or broken file
if err != nil {
return 0, err
}
var expiry int64
expiry, err = strconv.ParseInt(metadata[0], 10, 32)
if err != nil {
return 0, err
} else {
return int32(expiry), err
}
}
func metadataGetDeleteKey(filename string) (string, error) {
metadata, err := metadataRead(filename)
if len(metadata) < 2 {
err := errors.New("ERR: Metadata file does not contain deletion key")
return "", err
}
if err != nil {
return "", err
} else {
return metadata[1], err
}
}

30
server.go

@ -2,12 +2,12 @@ package main
import ( import (
"flag" "flag"
"fmt"
"log" "log"
"net" "net"
"net/http" "net/http"
"regexp"
"os" "os"
"fmt"
"regexp"
"github.com/flosch/pongo2" "github.com/flosch/pongo2"
"github.com/zenazn/goji" "github.com/zenazn/goji"
@ -17,6 +17,7 @@ import (
var Config struct { var Config struct {
bind string bind string
filesDir string filesDir string
metaDir string
noLogs bool noLogs bool
siteName string siteName string
siteURL string siteURL string
@ -26,26 +27,41 @@ func main() {
flag.StringVar(&Config.bind, "b", "127.0.0.1:8080", flag.StringVar(&Config.bind, "b", "127.0.0.1:8080",
"host to bind to (default: 127.0.0.1:8080)") "host to bind to (default: 127.0.0.1:8080)")
flag.StringVar(&Config.filesDir, "filespath", "files/", flag.StringVar(&Config.filesDir, "filespath", "files/",
"path to files directory (including trailing slash)")
"path to files directory")
flag.StringVar(&Config.metaDir, "metapath", "meta/",
"path to metadata directory")
flag.BoolVar(&Config.noLogs, "nologs", false, flag.BoolVar(&Config.noLogs, "nologs", false,
"remove stdout output for each request") "remove stdout output for each request")
flag.StringVar(&Config.siteName, "sitename", "linx", flag.StringVar(&Config.siteName, "sitename", "linx",
"name of the site") "name of the site")
flag.StringVar(&Config.siteURL, "siteurl", "http://"+Config.bind+"/", flag.StringVar(&Config.siteURL, "siteurl", "http://"+Config.bind+"/",
"site base url (including trailing slash)")
"site base url")
flag.Parse() flag.Parse()
if Config.noLogs { if Config.noLogs {
goji.Abandon(middleware.Logger) goji.Abandon(middleware.Logger)
} }
// make directory if needed
err := os.MkdirAll(Config.filesDir, 0755)
// make directories if needed
var err error
err = os.MkdirAll(Config.filesDir, 0755)
if err != nil { if err != nil {
fmt.Printf("Error: could not create files directory")
fmt.Printf("Error: could not create files directory\n")
os.Exit(1) os.Exit(1)
} }
err = os.MkdirAll(Config.metaDir, 0700)
if err != nil {
fmt.Printf("Error: could not create metadata directory\n")
os.Exit(1)
}
// ensure siteURL ends wth '/'
if lastChar := Config.siteURL[len(Config.siteURL)-1:]; lastChar != "/" {
Config.siteURL = Config.siteURL + "/"
}
// Template Globals // Template Globals
pongo2.DefaultSet.Globals["sitename"] = Config.siteName pongo2.DefaultSet.Globals["sitename"] = Config.siteName

56
upload.go

@ -15,21 +15,55 @@ import (
"github.com/zenazn/goji/web" "github.com/zenazn/goji/web"
) )
// Describes metadata directly from the user request
type UploadRequest struct { type UploadRequest struct {
src io.Reader src io.Reader
filename string filename string
expiry int
expiry int32 // Seconds until expiry, 0 = never
randomBarename bool randomBarename bool
deletionKey string // Empty string if not defined
} }
// Metadata associated with a file as it would actually be stored
type Upload struct { type Upload struct {
Filename string
Size int64
Expiry int
Filename string // Final filename on disk
Size int64
Expiry int32 // Unix timestamp of expiry, 0=never
DeleteKey string // Deletion key, one generated if not provided
DebugInfo string // Optional field to store whatever
}
func uploadHeaderProcess(r *http.Request, upReq *UploadRequest) {
// For legacy reasons
upReq.randomBarename = false
if r.Header.Get("X-Randomized-Filename") == "yes" {
upReq.randomBarename = true
}
if r.Header.Get("X-Randomized-Barename") == "yes" {
upReq.randomBarename = true
}
upReq.deletionKey = r.Header.Get("X-Delete-Key")
// Get seconds until expiry. Non-integer responses never expire.
expStr := r.Header.Get("X-File-Expiry")
if expStr == "" {
upReq.expiry = 0
} else {
expiry, err := strconv.ParseInt(expStr, 10, 32)
if err != nil {
upReq.expiry = 0
} else {
upReq.expiry = int32(expiry)
}
}
} }
func uploadPostHandler(c web.C, w http.ResponseWriter, r *http.Request) { func uploadPostHandler(c web.C, w http.ResponseWriter, r *http.Request) {
upReq := UploadRequest{} upReq := UploadRequest{}
uploadHeaderProcess(r, &upReq)
if r.Header.Get("Content-Type") == "application/octet-stream" { if r.Header.Get("Content-Type") == "application/octet-stream" {
defer r.Body.Close() defer r.Body.Close()
@ -71,6 +105,7 @@ func uploadPostHandler(c web.C, w http.ResponseWriter, r *http.Request) {
func uploadPutHandler(c web.C, w http.ResponseWriter, r *http.Request) { func uploadPutHandler(c web.C, w http.ResponseWriter, r *http.Request) {
upReq := UploadRequest{} upReq := UploadRequest{}
uploadHeaderProcess(r, &upReq)
defer r.Body.Close() defer r.Body.Close()
upReq.filename = c.URLParams["name"] upReq.filename = c.URLParams["name"]
@ -86,6 +121,7 @@ func uploadPutHandler(c web.C, w http.ResponseWriter, r *http.Request) {
} }
func processUpload(upReq UploadRequest) (upload Upload, err error) { func processUpload(upReq UploadRequest) (upload Upload, err error) {
// Determine the appropriate filename, then write to disk
barename, extension := barePlusExt(upReq.filename) barename, extension := barePlusExt(upReq.filename)
if upReq.randomBarename || len(barename) == 0 { if upReq.randomBarename || len(barename) == 0 {
@ -120,6 +156,18 @@ func processUpload(upReq UploadRequest) (upload Upload, err error) {
} }
defer dst.Close() defer dst.Close()
// Get the rest of the metadata needed for storage
upload.Expiry = getFutureTimestamp(upReq.expiry)
// If no delete key specified, pick a random one.
if upReq.deletionKey == "" {
upload.DeleteKey = uuid.New()[:30]
} else {
upload.DeleteKey = upReq.deletionKey
}
metadataWrite(upload.Filename, &upload)
bytes, err := io.Copy(dst, upReq.src) bytes, err := io.Copy(dst, upReq.src)
if err != nil { if err != nil {
return return

Loading…
Cancel
Save