From bf47a1d57aeea76be15b5f46ad0ed5520688257b Mon Sep 17 00:00:00 2001 From: makeworld Date: Thu, 24 Dec 2020 23:38:12 -0500 Subject: [PATCH] Support streaming; error if cmd or types are empty --- config/config.go | 10 ++++++++++ config/default.go | 24 ++++++++++++++++++++---- default-config.toml | 24 ++++++++++++++++++++---- display/download.go | 26 ++++++++++++++++++++++++-- 4 files changed, 74 insertions(+), 10 deletions(-) diff --git a/config/config.go b/config/config.go index 7c509d0..6e5aae4 100644 --- a/config/config.go +++ b/config/config.go @@ -50,6 +50,7 @@ var HTTPCommand []string type MediaHandler struct { Cmd []string NoPrompt bool + Stream bool } var MediaHandlers = make(map[string]MediaHandler) @@ -365,12 +366,20 @@ func Init() error { Cmd []string `mapstructure:"cmd"` Types []string `mapstructure:"types"` NoPrompt bool `mapstructure:"no_prompt"` + Stream bool `mapstructure:"stream"` } err = viper.UnmarshalKey("mediatype-handlers", &rawMediaHandlers) if err != nil { return fmt.Errorf("couldn't parse mediatype-handlers section in config: %w", err) } for _, rawMediaHandler := range rawMediaHandlers { + if len(rawMediaHandler.Cmd) == 0 { + return fmt.Errorf("empty cmd array in mediatype-handlers section") + } + if len(rawMediaHandler.Types) == 0 { + return fmt.Errorf("empty types array in mediatype-handlers section") + } + for _, typ := range rawMediaHandler.Types { if _, ok := MediaHandlers[typ]; ok { return fmt.Errorf("multiple mediatype-handlers defined for %v", typ) @@ -378,6 +387,7 @@ func Init() error { MediaHandlers[typ] = MediaHandler{ Cmd: rawMediaHandler.Cmd, NoPrompt: rawMediaHandler.NoPrompt, + Stream: rawMediaHandler.Stream, } } } diff --git a/config/default.go b/config/default.go index ab80185..1dc3729 100644 --- a/config/default.go +++ b/config/default.go @@ -162,10 +162,13 @@ other = 'off' # You only need to configure this section if you want to override your default application, # or do special things like streaming. # +# Note the use of single quotes for commands, so that backslashes will not be escaped. +# +# # To open jpeg files with the feh command: # # [[mediatype-handlers]] -# cmd = ["feh"] +# cmd = ['feh'] # types = ["image/jpeg"] # # Each command that you specify must come under its own [[mediatype-handlers]]. You may @@ -175,7 +178,7 @@ other = 'off' # entire type: # # [[mediatype-handlers]] -# command = ["vlc", "--flag"] +# command = ['vlc', '--flag'] # types = ["audio", "video"] # # A catch-all handler can by specified with "*". @@ -184,17 +187,30 @@ other = 'off' # want to override that. # # [[mediatype-handlers]] -# cmd = ["some-command"] +# cmd = ['some-command'] # types = [ # "application/pdf", # "*", # ] # +# You can also choose to stream the data instead of downloading it all before +# opening it. This is especially useful for large video or audio files, as +# well as radio streams, which will never complete. You can do this like so: +# +# [[mediatype-handlers]] +# cmd = ['vlc', '-'] +# types = ["audio", "video"] +# stream = true +# +# This uses vlc to stream all video and audio content. +# By default stream is set to off for all handlers +# +# # If you want to always open a type in its viewer without the download or open # prompt appearing, you can add no_prompt = true # # [[mediatype-handlers]] -# cmd = ["feh"] +# cmd = ['feh'] # types = ["image"] # no_prompt = true # diff --git a/default-config.toml b/default-config.toml index 5653f75..c45b587 100644 --- a/default-config.toml +++ b/default-config.toml @@ -159,10 +159,13 @@ other = 'off' # You only need to configure this section if you want to override your default application, # or do special things like streaming. # +# Note the use of single quotes for commands, so that backslashes will not be escaped. +# +# # To open jpeg files with the feh command: # # [[mediatype-handlers]] -# cmd = ["feh"] +# cmd = ['feh'] # types = ["image/jpeg"] # # Each command that you specify must come under its own [[mediatype-handlers]]. You may @@ -172,7 +175,7 @@ other = 'off' # entire type: # # [[mediatype-handlers]] -# command = ["vlc", "--flag"] +# command = ['vlc', '--flag'] # types = ["audio", "video"] # # A catch-all handler can by specified with "*". @@ -181,17 +184,30 @@ other = 'off' # want to override that. # # [[mediatype-handlers]] -# cmd = ["some-command"] +# cmd = ['some-command'] # types = [ # "application/pdf", # "*", # ] # +# You can also choose to stream the data instead of downloading it all before +# opening it. This is especially useful for large video or audio files, as +# well as radio streams, which will never complete. You can do this like so: +# +# [[mediatype-handlers]] +# cmd = ['vlc', '-'] +# types = ["audio", "video"] +# stream = true +# +# This uses vlc to stream all video and audio content. +# By default stream is set to off for all handlers +# +# # If you want to always open a type in its viewer without the download or open # prompt appearing, you can add no_prompt = true # # [[mediatype-handlers]] -# cmd = ["feh"] +# cmd = ['feh'] # types = ["image"] # no_prompt = true # diff --git a/display/download.go b/display/download.go index 4cfe000..6cc6c84 100644 --- a/display/download.go +++ b/display/download.go @@ -90,6 +90,7 @@ func getMediaHandler(resp *gemini.Response) config.MediaHandler { def := config.MediaHandler{ Cmd: nil, NoPrompt: false, + Stream: false, } mediatype, _, err := mime.ParseMediaType(resp.Meta) @@ -116,8 +117,6 @@ func getMediaHandler(resp *gemini.Response) config.MediaHandler { // dlChoice displays the download choice modal and acts on the user's choice. // It should run in a goroutine. func dlChoice(text, u string, resp *gemini.Response) { - defer resp.Body.Close() - mediaHandler := getMediaHandler(resp) var choice string @@ -136,6 +135,7 @@ func dlChoice(text, u string, resp *gemini.Response) { tabPages.HidePage("dlChoice") App.Draw() downloadURL(config.DownloadsDir, u, resp) + resp.Body.Close() // Only close when the file is downloaded return } if choice == "Open" { @@ -154,6 +154,28 @@ func dlChoice(text, u string, resp *gemini.Response) { // with the default system viewer. func open(u string, resp *gemini.Response) { mediaHandler := getMediaHandler(resp) + + if mediaHandler.Stream { + // Run command with downloaded data from stdin + + cmd := mediaHandler.Cmd + var proc *exec.Cmd + if len(cmd) == 1 { + proc = exec.Command(cmd[0]) + } else { + proc = exec.Command(cmd[0], cmd[1:]...) + } + proc.Stdin = resp.Body + + err := proc.Start() + if err != nil { + Error("File Opening Error", "Error executing custom command: "+err.Error()) + return + } + Info("Opened with " + cmd[0]) + return + } + path := downloadURL(config.TempDownloadsDir, u, resp) if path == "" { return