From e87fd516d65bb6ed06ed0cc51845c110d0b0cba7 Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Wed, 4 May 2022 09:27:22 +1000 Subject: [PATCH] Fix streaming scenes not able to be deleted (#2549) * Don't navigate away from scene if delete failed * Close connection on cancel --- internal/api/routes_scene.go | 3 +- internal/manager/running_streams.go | 29 ++++++++++++++++++- pkg/fsutil/lock_manager.go | 14 +++++++++ .../components/Scenes/DeleteScenesDialog.tsx | 3 +- 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/internal/api/routes_scene.go b/internal/api/routes_scene.go index 61e517252..2ff716ae7 100644 --- a/internal/api/routes_scene.go +++ b/internal/api/routes_scene.go @@ -164,7 +164,8 @@ func (rs sceneRoutes) streamTranscode(w http.ResponseWriter, r *http.Request, st encoder := manager.GetInstance().FFMPEG lm := manager.GetInstance().ReadLockManager - lockCtx := lm.ReadLock(r.Context(), scene.Path) + streamRequestCtx := manager.NewStreamRequestContext(w, r) + lockCtx := lm.ReadLock(streamRequestCtx, scene.Path) defer lockCtx.Cancel() stream, err := encoder.GetTranscodeStream(lockCtx, options) diff --git a/internal/manager/running_streams.go b/internal/manager/running_streams.go index 57b659ffb..644680394 100644 --- a/internal/manager/running_streams.go +++ b/internal/manager/running_streams.go @@ -1,6 +1,7 @@ package manager import ( + "context" "net/http" "github.com/stashapp/stash/internal/manager/config" @@ -10,6 +11,31 @@ import ( "github.com/stashapp/stash/pkg/utils" ) +type StreamRequestContext struct { + context.Context + ResponseWriter http.ResponseWriter +} + +func NewStreamRequestContext(w http.ResponseWriter, r *http.Request) *StreamRequestContext { + return &StreamRequestContext{ + Context: r.Context(), + ResponseWriter: w, + } +} + +func (c *StreamRequestContext) Cancel() { + hj, ok := (c.ResponseWriter).(http.Hijacker) + if !ok { + return + } + + // hijack and close the connection + conn, _, _ := hj.Hijack() + if conn != nil { + conn.Close() + } +} + func KillRunningStreams(scene *models.Scene, fileNamingAlgo models.HashAlgorithm) { instance.ReadLockManager.Cancel(scene.Path) @@ -31,7 +57,8 @@ func (s *SceneServer) StreamSceneDirect(scene *models.Scene, w http.ResponseWrit fileNamingAlgo := config.GetInstance().GetVideoFileNamingAlgorithm() filepath := GetInstance().Paths.Scene.GetStreamPath(scene.Path, scene.GetHash(fileNamingAlgo)) - lockCtx := GetInstance().ReadLockManager.ReadLock(r.Context(), filepath) + streamRequestCtx := NewStreamRequestContext(w, r) + lockCtx := GetInstance().ReadLockManager.ReadLock(streamRequestCtx, filepath) defer lockCtx.Cancel() http.ServeFile(w, r, filepath) } diff --git a/pkg/fsutil/lock_manager.go b/pkg/fsutil/lock_manager.go index f70266d7a..161be2a4b 100644 --- a/pkg/fsutil/lock_manager.go +++ b/pkg/fsutil/lock_manager.go @@ -7,6 +7,10 @@ import ( "time" ) +type Cancellable interface { + Cancel() +} + type LockContext struct { context.Context cancel context.CancelFunc @@ -57,6 +61,16 @@ func NewReadLockManager() *ReadLockManager { func (m *ReadLockManager) ReadLock(ctx context.Context, fn string) *LockContext { retCtx, cancel := context.WithCancel(ctx) + // if Cancellable, call Cancel() when cancelled + cancellable, ok := ctx.(Cancellable) + if ok { + origCancel := cancel + cancel = func() { + origCancel() + cancellable.Cancel() + } + } + m.mutex.Lock() defer m.mutex.Unlock() diff --git a/ui/v2.5/src/components/Scenes/DeleteScenesDialog.tsx b/ui/v2.5/src/components/Scenes/DeleteScenesDialog.tsx index 6534ac1ba..a4b8514f8 100644 --- a/ui/v2.5/src/components/Scenes/DeleteScenesDialog.tsx +++ b/ui/v2.5/src/components/Scenes/DeleteScenesDialog.tsx @@ -60,11 +60,12 @@ export const DeleteScenesDialog: React.FC = ( try { await deleteScene(); Toast.success({ content: toastMessage }); + props.onClose(true); } catch (e) { Toast.error(e); + props.onClose(false); } setIsDeleting(false); - props.onClose(true); } function funscriptPath(scenePath: string) {