Skip to content

Commit 39fa7c6

Browse files
committed
🚧 Downloading pages works - #38
1 parent f00fbce commit 39fa7c6

File tree

6 files changed

+163
-6
lines changed

6 files changed

+163
-6
lines changed

‎amfora.go

+9-5
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,26 @@ func main() {
1818

1919
if len(os.Args) > 1 {
2020
if os.Args[1] == "--version" || os.Args[1] == "-v" {
21-
fmt.Print(version + "\r\n")
21+
fmt.Println(version)
2222
return
2323
}
2424
if os.Args[1] == "--help" || os.Args[1] == "-h" {
25-
fmt.Print("Amfora is a fancy terminal browser for the Gemini protocol.\r\n\r\n")
26-
fmt.Print("Usage:\r\namfora [URL]\r\namfora --version, -v\r\n")
25+
fmt.Println("Amfora is a fancy terminal browser for the Gemini protocol.")
26+
fmt.Println()
27+
fmt.Println("Usage:")
28+
fmt.Println("amfora [URL]")
29+
fmt.Println("amfora --version, -v")
2730
return
2831
}
2932
}
3033

3134
err := config.Init()
3235
if err != nil {
33-
panic(err)
36+
fmt.Printf("Config error: %v\n", err)
37+
os.Exit(1)
3438
}
35-
display.Init()
3639

40+
display.Init()
3741
display.NewTab()
3842
display.NewTab() // Open extra tab and close it to fully initialize the app and wrapping
3943
display.CloseTab()

‎config/config.go

+37-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package config
22

33
import (
4+
"fmt"
45
"os"
56
"path/filepath"
67
"runtime"
@@ -24,10 +25,13 @@ var BkmkStore = viper.New()
2425
var bkmkDir string
2526
var bkmkPath string
2627

28+
// For other pkgs to use
29+
var DownloadsDir string
30+
2731
func Init() error {
2832
home, err := homedir.Dir()
2933
if err != nil {
30-
panic(err)
34+
return err
3135
}
3236
// Store AppData path
3337
if runtime.GOOS == "windows" {
@@ -144,6 +148,7 @@ func Init() error {
144148
viper.SetDefault("a-general.bullets", true)
145149
viper.SetDefault("a-general.left_margin", 0.15)
146150
viper.SetDefault("a-general.max_width", 100)
151+
viper.SetDefault("a-general.downloads", "")
147152
viper.SetDefault("cache.max_size", 0)
148153
viper.SetDefault("cache.max_pages", 20)
149154

@@ -154,6 +159,37 @@ func Init() error {
154159
return err
155160
}
156161

162+
// Setup downloads dir
163+
if viper.GetString("a-general.downloads") == "" {
164+
// Find default Downloads dir
165+
// This seems to work for all OSes?
166+
DownloadsDir = filepath.Join(home, "Downloads")
167+
// Create it just in case
168+
err = os.MkdirAll(DownloadsDir, 0755)
169+
if err != nil {
170+
return fmt.Errorf("downloads path could not be created: %s", DownloadsDir)
171+
}
172+
} else {
173+
// Validate path
174+
dDir := viper.GetString("a-general.downloads")
175+
di, err := os.Stat(dDir)
176+
if err == nil {
177+
if !di.IsDir() {
178+
return fmt.Errorf("downloads path specified is not a directory: %s", dDir)
179+
}
180+
} else if os.IsNotExist(err) {
181+
// Try to create path
182+
err = os.MkdirAll(dDir, 0755)
183+
if err != nil {
184+
return fmt.Errorf("downloads path could not be created: %s", dDir)
185+
}
186+
} else {
187+
// Some other error
188+
return fmt.Errorf("couldn't access downloads directory: %s", dDir)
189+
}
190+
DownloadsDir = dDir
191+
}
192+
157193
// Setup cache from config
158194
cache.SetMaxSize(viper.GetInt("cache.max_size"))
159195
cache.SetMaxPages(viper.GetInt("cache.max_pages"))

‎config/default.go

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ left_margin = 0.15
2727
max_width = 100 # The max number of columns to wrap a page's text to. Preformatted blocks are not wrapped.
2828
# 'downloads' is the path to a downloads folder.
2929
# An empty value means the code will find the default downloads folder for your system.
30+
# If the path does not exist it will be created.
3031
downloads = ""
3132
# Options for page cache - which is only for text/gemini pages
3233
# Increase the cache size to speed up browsing at the expense of memory

‎default-config.toml

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ left_margin = 0.15
2424
max_width = 100 # The max number of columns to wrap a page's text to. Preformatted blocks are not wrapped.
2525
# 'downloads' is the path to a downloads folder.
2626
# An empty value means the code will find the default downloads folder for your system.
27+
# If the path does not exist it will be created.
2728
downloads = ""
2829
# Options for page cache - which is only for text/gemini pages
2930
# Increase the cache size to speed up browsing at the expense of memory

‎display/display.go

+12
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,18 @@ func Init() {
274274
case tcell.KeyPgDn:
275275
tabs[curTab].pageDown()
276276
return nil
277+
case tcell.KeyCtrlS:
278+
if tabs[curTab].hasContent() {
279+
savePath, err := downloadPage(tabs[curTab].page)
280+
if err != nil {
281+
Error("Download Error", fmt.Sprintf("Error saving page content: %v", err))
282+
} else {
283+
Info(fmt.Sprintf("Page content saved to %s. ", savePath))
284+
}
285+
} else {
286+
Info("The current page has no content, so it couldn't be downloaded.")
287+
}
288+
return nil
277289
case tcell.KeyRune:
278290
// Regular key was sent
279291
switch string(event.Rune()) {

‎display/download.go

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package display
2+
3+
import (
4+
"io/ioutil"
5+
"net/url"
6+
"os"
7+
"path"
8+
"path/filepath"
9+
"strconv"
10+
"strings"
11+
12+
"github.com/makeworld-the-better-one/amfora/config"
13+
"github.com/makeworld-the-better-one/amfora/structs"
14+
)
15+
16+
// getSafeDownloadName is used by downloads.go only.
17+
// It returns a modified name that is unique for the downloads folder.
18+
// This way duplicate saved files will not overwrite each other.
19+
//
20+
// lastDot should be set to true if the number added to the name should come before
21+
// the last dot in the filename instead of the first.
22+
//
23+
// n should be set to 0, it is used for recursiveness.
24+
func getSafeDownloadName(name string, lastDot bool, n int) (string, error) {
25+
// newName("test.txt", 3) -> "test(3).txt"
26+
newName := func() string {
27+
if n <= 0 {
28+
return name
29+
}
30+
if lastDot {
31+
ext := filepath.Ext(name)
32+
return strings.TrimSuffix(name, ext) + "(" + strconv.Itoa(n) + ")" + ext
33+
} else {
34+
idx := strings.Index(name, ".")
35+
if idx == -1 {
36+
return name + "(" + strconv.Itoa(n) + ")"
37+
}
38+
return name[:idx] + "(" + strconv.Itoa(n) + ")" + name[idx:]
39+
}
40+
}
41+
42+
d, err := os.Open(config.DownloadsDir)
43+
if err != nil {
44+
return "", err
45+
}
46+
files, err := d.Readdirnames(-1)
47+
if err != nil {
48+
d.Close()
49+
return "", err
50+
}
51+
52+
nn := newName()
53+
for i := range files {
54+
if nn == files[i] {
55+
d.Close()
56+
return getSafeDownloadName(name, lastDot, n+1)
57+
}
58+
}
59+
d.Close()
60+
return nn, nil // Name doesn't exist already
61+
}
62+
63+
// downloadPage saves the passed Page to a file.
64+
// It returns the saved path and an error.
65+
// It always cleans up, so if an error is returned there is no file saved
66+
func downloadPage(p *structs.Page) (string, error) {
67+
// Figure out file name
68+
var name string
69+
var err error
70+
parsed, _ := url.Parse(p.Url)
71+
if parsed.Path == "" || path.Base(parsed.Path) == "/" {
72+
// No file, just the root domain
73+
if p.Mediatype == structs.TextGemini {
74+
name, err = getSafeDownloadName(parsed.Hostname()+".gmi", true, 0)
75+
if err != nil {
76+
return "", err
77+
}
78+
} else {
79+
name, err = getSafeDownloadName(parsed.Hostname()+".txt", true, 0)
80+
if err != nil {
81+
return "", err
82+
}
83+
}
84+
} else {
85+
// There's a specific file
86+
name = path.Base(parsed.Path)
87+
if p.Mediatype == structs.TextGemini && !strings.HasSuffix(name, ".gmi") && !strings.HasSuffix(name, ".gemini") {
88+
name += ".gmi"
89+
}
90+
name, err = getSafeDownloadName(name, false, 0)
91+
if err != nil {
92+
return "", err
93+
}
94+
}
95+
savePath := filepath.Join(config.DownloadsDir, name)
96+
err = ioutil.WriteFile(savePath, []byte(p.Raw), 0644)
97+
if err != nil {
98+
// Just in case
99+
os.Remove(savePath)
100+
return "", err
101+
}
102+
return savePath, err
103+
}

0 commit comments

Comments
 (0)