Add post-terminate hook

This commit is contained in:
Marius 2016-03-12 22:24:57 +01:00
parent 5aa7928111
commit 3ee1c61d80
5 changed files with 72 additions and 18 deletions

4
.hooks/post-terminate Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
echo "Upload $TUS_ID terminated"
cat /dev/stdin | jq .

View File

@ -134,10 +134,11 @@ func main() {
stdout.Printf("Using %.2fMB as maximum size.\n", float64(maxSize)/1024/1024) stdout.Printf("Using %.2fMB as maximum size.\n", float64(maxSize)/1024/1024)
handler, err := tusd.NewHandler(tusd.Config{ handler, err := tusd.NewHandler(tusd.Config{
MaxSize: maxSize, MaxSize: maxSize,
BasePath: basepath, BasePath: basepath,
StoreComposer: composer, StoreComposer: composer,
NotifyCompleteUploads: true, NotifyCompleteUploads: true,
NotifyTerminatedUploads: true,
}) })
if err != nil { if err != nil {
stderr.Fatalf("Unable to create handler: %s", err) stderr.Fatalf("Unable to create handler: %s", err)
@ -152,7 +153,9 @@ func main() {
for { for {
select { select {
case info := <-handler.CompleteUploads: case info := <-handler.CompleteUploads:
invokeHook(info) invokeHook("post-finish", info)
case info := <-handler.TerminatedUploads:
invokeHook("post-terminate", info)
} }
} }
}() }()
@ -176,16 +179,21 @@ func main() {
} }
} }
func invokeHook(info tusd.FileInfo) { func invokeHook(name string, info tusd.FileInfo) {
stdout.Printf("Upload %s (%d bytes) finished\n", info.ID, info.Size) switch name {
case "post-finish":
stdout.Printf("Upload %s (%d bytes) finished\n", info.ID, info.Size)
case "post-terminate":
stdout.Printf("Upload %s terminated\n", info.ID)
}
if !hookInstalled { if !hookInstalled {
return return
} }
stdout.Println("Invoking hooks…") stdout.Printf("Invoking %s hook…\n", name)
cmd := exec.Command(hooksDir + "/post-finish") cmd := exec.Command(hooksDir + "/" + name)
env := os.Environ() env := os.Environ()
env = append(env, "TUS_ID="+info.ID) env = append(env, "TUS_ID="+info.ID)
env = append(env, "TUS_SIZE="+strconv.FormatInt(info.Size, 10)) env = append(env, "TUS_SIZE="+strconv.FormatInt(info.Size, 10))
@ -206,7 +214,7 @@ func invokeHook(info tusd.FileInfo) {
go func() { go func() {
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
stderr.Printf("Error running postfinish hook for %s: %s", info.ID, err) stderr.Printf("Error running %s hook for %s: %s", name, info.ID, err)
} }
}() }()
} }

View File

@ -12,7 +12,7 @@ type Config struct {
// DataStore implementation used to store and retrieve the single uploads. // DataStore implementation used to store and retrieve the single uploads.
// The usage of this field is deprecated and should be avoided in favor of // The usage of this field is deprecated and should be avoided in favor of
// StoreComposer. // StoreComposer.
DataStore DataStore DataStore DataStore
// StoreComposer points to the store composer from which the core data store // StoreComposer points to the store composer from which the core data store
// and optional dependencies should be taken. May only be nil if DataStore is // and optional dependencies should be taken. May only be nil if DataStore is
// set. // set.
@ -28,6 +28,9 @@ type Config struct {
// Initiate the CompleteUploads channel in the Handler struct in order to // Initiate the CompleteUploads channel in the Handler struct in order to
// be notified about complete uploads // be notified about complete uploads
NotifyCompleteUploads bool NotifyCompleteUploads bool
// NotifyTerminatedUploads indicates whether sending notifications about
// terminated uploads using the TerminatedUploads channel should be enabled.
NotifyTerminatedUploads bool
// Logger the logger to use internally // Logger the logger to use internally
Logger *log.Logger Logger *log.Logger
// Respect the X-Forwarded-Host, X-Forwarded-Proto and Forwarded headers // Respect the X-Forwarded-Host, X-Forwarded-Proto and Forwarded headers

View File

@ -5,6 +5,8 @@ import (
"testing" "testing"
. "github.com/tus/tusd" . "github.com/tus/tusd"
"github.com/stretchr/testify/assert"
) )
type terminateStore struct { type terminateStore struct {
@ -12,6 +14,13 @@ type terminateStore struct {
zeroStore zeroStore
} }
func (s terminateStore) GetInfo(id string) (FileInfo, error) {
return FileInfo{
ID: id,
Size: 10,
}, nil
}
func (s terminateStore) Terminate(id string) error { func (s terminateStore) Terminate(id string) error {
if id != "foo" { if id != "foo" {
s.t.Fatal("unexpected id") s.t.Fatal("unexpected id")
@ -24,8 +33,12 @@ func TestTerminate(t *testing.T) {
DataStore: terminateStore{ DataStore: terminateStore{
t: t, t: t,
}, },
NotifyTerminatedUploads: true,
}) })
c := make(chan FileInfo, 1)
handler.TerminatedUploads = c
(&httpTest{ (&httpTest{
Name: "Successful OPTIONS request", Name: "Successful OPTIONS request",
Method: "OPTIONS", Method: "OPTIONS",
@ -45,6 +58,12 @@ func TestTerminate(t *testing.T) {
}, },
Code: http.StatusNoContent, Code: http.StatusNoContent,
}).Run(handler, t) }).Run(handler, t)
info := <-c
a := assert.New(t)
a.Equal("foo", info.ID)
a.Equal(int64(10), info.Size)
} }
func TestTerminateNotImplemented(t *testing.T) { func TestTerminateNotImplemented(t *testing.T) {

View File

@ -66,6 +66,12 @@ type UnroutedHandler struct {
// this unbuffered channel. The NotifyCompleteUploads property in the Config // this unbuffered channel. The NotifyCompleteUploads property in the Config
// struct must be set to true in order to work. // struct must be set to true in order to work.
CompleteUploads chan FileInfo CompleteUploads chan FileInfo
// TerminatedUploads is used to send notifications whenever an upload is
// terminated by a user. The FileInfo will contain information about This
// upload gathered before the termination. Sending to this channel will only
// happen if the NotifyTerminatedUploads field is set to true in the Config
// structure.
TerminatedUploads chan FileInfo
} }
// NewUnroutedHandler creates a new handler without routing using the given // NewUnroutedHandler creates a new handler without routing using the given
@ -87,13 +93,14 @@ func NewUnroutedHandler(config Config) (*UnroutedHandler, error) {
} }
handler := &UnroutedHandler{ handler := &UnroutedHandler{
config: config, config: config,
composer: config.StoreComposer, composer: config.StoreComposer,
basePath: config.BasePath, basePath: config.BasePath,
isBasePathAbs: config.isAbs, isBasePathAbs: config.isAbs,
CompleteUploads: make(chan FileInfo), CompleteUploads: make(chan FileInfo),
logger: config.Logger, TerminatedUploads: make(chan FileInfo),
extensions: extensions, logger: config.Logger,
extensions: extensions,
} }
return handler, nil return handler, nil
@ -461,6 +468,15 @@ func (handler *UnroutedHandler) DelFile(w http.ResponseWriter, r *http.Request)
defer locker.UnlockUpload(id) defer locker.UnlockUpload(id)
} }
var info FileInfo
if handler.config.NotifyTerminatedUploads {
info, err = handler.composer.Core.GetInfo(id)
if err != nil {
handler.sendError(w, r, err)
return
}
}
err = handler.composer.Terminater.Terminate(id) err = handler.composer.Terminater.Terminate(id)
if err != nil { if err != nil {
handler.sendError(w, r, err) handler.sendError(w, r, err)
@ -468,6 +484,10 @@ func (handler *UnroutedHandler) DelFile(w http.ResponseWriter, r *http.Request)
} }
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
if handler.config.NotifyTerminatedUploads {
handler.TerminatedUploads <- info
}
} }
// Send the error in the response body. The status code will be looked up in // Send the error in the response body. The status code will be looked up in