~adnano/astronaut

ref: 72ba913688a9d2dafb0bfdf11a5cee16685ce98a astronaut/content.go -rw-r--r-- 3.1 KiB
72ba9136Adnan Maolood Use scfg to configure settings 4 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package main

import (
	"bufio"
	"bytes"
	"io"
	"mime"
	"os"
	"os/exec"
	"path"
	"strings"

	"git.sr.ht/~adnano/go-gemini"
	"github.com/gdamore/tcell/v2"
)

// Content represents the contents of a tab.
type Content interface {
	Title() string
	Parse() error
	Draw()
	Event(tcell.Event) bool
	Done() bool
	Close() error
}

func (t *Tab) handle(req *gemini.Request, resp *gemini.Response) error {
	defer resp.Body.Close()

	mediatype, params, err := mime.ParseMediaType(resp.Meta)
	if err != nil {
		return err
	}

	t.setMediaType(mediatype)
	if strings.HasPrefix(mediatype, "text/") {
		return t.handleText(resp, mediatype, params)
	}
	return t.handleDownload(req, resp, mediatype)
}

func (t *Tab) setMediaType(mediatype string) {
	t.mu.Lock()
	defer t.mu.Unlock()
	t.pages[t.page].MediaType = mediatype
}

func (t *Tab) handleText(resp *gemini.Response, mediatype string, params map[string]string) error {
	if charset, ok := params["charset"]; ok {
		charset = strings.ToLower(charset)
		if charset != "utf-8" && charset != "us-ascii" {
			return ErrUnsupportedCharset(charset)
		}
	}

	var content Content
	if mediatype == "text/gemini" {
		content = NewGemText(t.view, t, resp.Body)
	} else {
		content = NewPlainText(t.view, resp.Body)
	}
	t.setContent(content)
	return content.Parse()
}

func (t *Tab) handleDownload(req *gemini.Request, resp *gemini.Response, mediatype string) error {
	filename := path.Base(req.URL.Path)

	ch := make(chan int)
	const (
		save = iota + 1
		open
	)

	{
		ctx := struct {
			Hostname  string
			Filename  string
			MediaType string
		}{
			req.URL.Hostname(),
			filename,
			mediatype,
		}
		var buf bytes.Buffer
		templates.ExecuteTemplate(&buf, "open.tmpl", ctx)
		gem := NewParsedGemText(t.view, t, io.NopCloser(&buf))
		gem.action = func(action string) {
			switch action {
			case "save":
				ch <- save
			case "open":
				ch <- open
			default:
				close(ch)
			}
		}
		t.setContent(gem)
		t.view.Invalidate()
	}

	defer t.setContent(nil)

	select {
	case choice := <-ch:
		switch choice {
		case save:
			return t.saveResponse(resp, filename)
		case open:
			return t.openResponse(resp, filename)
		}
	}
	return nil
}

func (t *Tab) saveResponse(resp *gemini.Response, filename string) error {
	t.mu.Lock()
	t.browser.input.Prompt("Save as: ")
	t.browser.input.SetInput(filename)
	t.mu.Unlock()

	ch := t.browser.Inputch()
	name, ok := <-ch
	if !ok {
		return nil
	}

	f, err := os.Create(name)
	if err != nil {
		return err
	}
	defer f.Close()
	_, err = io.Copy(bufio.NewWriter(f), resp.Body)
	if err != nil {
		return err
	}
	return nil
}

func (t *Tab) openResponse(resp *gemini.Response, filename string) error {
	t.mu.Lock()
	t.browser.input.Prompt("Open with: ")
	t.browser.input.SetInput("xdg-open")
	t.mu.Unlock()

	ch := t.browser.Inputch()
	name, ok := <-ch
	if !ok {
		return nil
	}

	f, err := os.CreateTemp("", "astronaut-*-"+filename)
	if err != nil {
		return err
	}
	defer f.Close()
	_, err = io.Copy(bufio.NewWriter(f), resp.Body)
	if err != nil {
		return err
	}

	cmd := exec.Command(name, f.Name())
	cmd.Stderr = os.Stderr
	if err := cmd.Start(); err != nil {
		return err
	}
	return nil
}