diff --git a/.hooks/post-terminate b/.hooks/post-terminate new file mode 100755 index 0000000..2374ef9 --- /dev/null +++ b/.hooks/post-terminate @@ -0,0 +1,4 @@ +#!/bin/bash + +echo "Upload $TUS_ID terminated" +cat /dev/stdin | jq . diff --git a/cmd/tusd/main.go b/cmd/tusd/main.go index a1911af..6ab7e1a 100644 --- a/cmd/tusd/main.go +++ b/cmd/tusd/main.go @@ -134,10 +134,11 @@ func main() { stdout.Printf("Using %.2fMB as maximum size.\n", float64(maxSize)/1024/1024) handler, err := tusd.NewHandler(tusd.Config{ - MaxSize: maxSize, - BasePath: basepath, - StoreComposer: composer, - NotifyCompleteUploads: true, + MaxSize: maxSize, + BasePath: basepath, + StoreComposer: composer, + NotifyCompleteUploads: true, + NotifyTerminatedUploads: true, }) if err != nil { stderr.Fatalf("Unable to create handler: %s", err) @@ -152,7 +153,9 @@ func main() { for { select { 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) { - stdout.Printf("Upload %s (%d bytes) finished\n", info.ID, info.Size) +func invokeHook(name string, info tusd.FileInfo) { + 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 { 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 = append(env, "TUS_ID="+info.ID) env = append(env, "TUS_SIZE="+strconv.FormatInt(info.Size, 10)) @@ -206,7 +214,7 @@ func invokeHook(info tusd.FileInfo) { go func() { err := cmd.Run() 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) } }() } diff --git a/config.go b/config.go index d73cb99..00edf7b 100644 --- a/config.go +++ b/config.go @@ -12,7 +12,7 @@ type Config struct { // DataStore implementation used to store and retrieve the single uploads. // The usage of this field is deprecated and should be avoided in favor of // StoreComposer. - DataStore DataStore + DataStore DataStore // 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 // set. @@ -28,6 +28,9 @@ type Config struct { // Initiate the CompleteUploads channel in the Handler struct in order to // be notified about complete uploads 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 *log.Logger // Respect the X-Forwarded-Host, X-Forwarded-Proto and Forwarded headers diff --git a/terminate_test.go b/terminate_test.go index 89d1c37..3941dba 100644 --- a/terminate_test.go +++ b/terminate_test.go @@ -5,6 +5,8 @@ import ( "testing" . "github.com/tus/tusd" + + "github.com/stretchr/testify/assert" ) type terminateStore struct { @@ -12,6 +14,13 @@ type terminateStore struct { zeroStore } +func (s terminateStore) GetInfo(id string) (FileInfo, error) { + return FileInfo{ + ID: id, + Size: 10, + }, nil +} + func (s terminateStore) Terminate(id string) error { if id != "foo" { s.t.Fatal("unexpected id") @@ -24,8 +33,12 @@ func TestTerminate(t *testing.T) { DataStore: terminateStore{ t: t, }, + NotifyTerminatedUploads: true, }) + c := make(chan FileInfo, 1) + handler.TerminatedUploads = c + (&httpTest{ Name: "Successful OPTIONS request", Method: "OPTIONS", @@ -45,6 +58,12 @@ func TestTerminate(t *testing.T) { }, Code: http.StatusNoContent, }).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) { diff --git a/unrouted_handler.go b/unrouted_handler.go index a0ce31e..55e211d 100644 --- a/unrouted_handler.go +++ b/unrouted_handler.go @@ -66,6 +66,12 @@ type UnroutedHandler struct { // this unbuffered channel. The NotifyCompleteUploads property in the Config // struct must be set to true in order to work. 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 @@ -87,13 +93,14 @@ func NewUnroutedHandler(config Config) (*UnroutedHandler, error) { } handler := &UnroutedHandler{ - config: config, - composer: config.StoreComposer, - basePath: config.BasePath, - isBasePathAbs: config.isAbs, - CompleteUploads: make(chan FileInfo), - logger: config.Logger, - extensions: extensions, + config: config, + composer: config.StoreComposer, + basePath: config.BasePath, + isBasePathAbs: config.isAbs, + CompleteUploads: make(chan FileInfo), + TerminatedUploads: make(chan FileInfo), + logger: config.Logger, + extensions: extensions, } return handler, nil @@ -461,6 +468,15 @@ func (handler *UnroutedHandler) DelFile(w http.ResponseWriter, r *http.Request) 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) if err != nil { handler.sendError(w, r, err) @@ -468,6 +484,10 @@ func (handler *UnroutedHandler) DelFile(w http.ResponseWriter, r *http.Request) } 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