summaryrefslogtreecommitdiff
path: root/core/core.go
blob: 21487f605cd8b7fe617ffce97c959ff217026fa9 (plain)
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
// Copyright (C) 2021 nytpu
// SPDX-License-Identifier: AGPL-3.0-only
// For more license details, see LICENSE or <https://www.gnu.org/licenses/agpl-3.0.html>.

// This file incorporates code from makeworld's amfora:
// https://github.com/makeworld-the-better-one/amfora/blob/master/subscriptions/structs.go
// Original source is Copyright 2020 makeworld and is licensed under the
// terms of the GNU General Public License Version 3.0.
// https://www.gnu.org/licenses/gpl-3.0-standalone.html

// package core implements the structs and other necessary resources for storing
// and managing feeds and pages
package core

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"

	"golang.nytpu.com/comitium/localizations"
)

// Global instance of FullData
var Data = FullData{
	FeedsMu: &sync.RWMutex{},
	PagesMu: &sync.RWMutex{},
	// Maps are created in init()
}

// the Header and Footer of feeds.gmi.
// set in Init, either to the contents of header.gmi or to the default string
var (
	Header string
	Footer string
)

// Timeout for requests
var Timeout int

var Localizer *localizations.Localizer

// have to do this instead of the automatically called init() because
// initialization stuff requires parsing of command line args first
func Init(dataPath string, localizer *localizations.Localizer) error {
	Localizer = localizer

	// initialize comitium.json & data maps
	f, err := os.Open(filepath.Join(dataPath, "comitium.json"))
	if err == nil {
		// File exists and could be opened
		fi, err := f.Stat()
		if err == nil && fi.Size() > 0 {
			// File is not empty
			jsonBytes, err := ioutil.ReadAll(f)
			f.Close()
			if err != nil {
				return fmt.Errorf(localizer.Get("errors.read_file_error", &localizations.Replacements{"file": "comitium.json", "error": err}))
			}
			err = json.Unmarshal(jsonBytes, &Data)
			if err != nil {
				return fmt.Errorf(localizer.Get("errors.corrupt_json_error", &localizations.Replacements{"error": err}))
			}
		}
		f.Close()
	} else if !os.IsNotExist(err) {
		// There's an error opening the file, but it's not bc is doesn't exist
		return fmt.Errorf(localizer.Get("errors.read_file_error", &localizations.Replacements{"file": "comitium.json", "error": err}))
	}

	if Data.Feeds == nil {
		Data.Feeds = make(map[string]*Feed)
	}
	if Data.Pages == nil {
		Data.Pages = make(map[string]*Page)
	}

	// initialize header.gmi and footer.gmi
	Header, err = getHeaderFooter(
		dataPath, "header.gmi",
		TranslateString("# %s\n\n", "files.feed_default_header", nil), "",
	)
	if err != nil {
		return err
	}
	footer, err := getHeaderFooter(
		dataPath, "footer.gmi", "---\n\n",
		TranslateString(
			"=> https://git.nytpu.com/comitium/about/ %s\n",
			"files.comitium_footer",
			&localizations.Replacements{"version": Version, "commit": Commit},
		),
	)
	if err != nil {
		return err
	}
	Footer = fmt.Sprintf("\n\n%s", footer)

	Timeout = 10

	return nil
}

// gets a header or footer from a datapath, or uses a default. extraString is a
// string to append no matter what
func getHeaderFooter(dataPath, filename, defaultString, extraString string) (main string, err error) {
	f, err := os.Open(filepath.Join(dataPath, filename))
	if err == nil {
		fi, err := f.Stat()
		if err == nil && fi.Size() > 0 {
			headerBytes, err := ioutil.ReadAll(f)
			f.Close()
			if err != nil {
				return "", fmt.Errorf(Localizer.Get("errors.read_file_error", &localizations.Replacements{"file": "header.gmi", "error": err}))
			}
			main = string(headerBytes)
		}
		f.Close()
	} else if !os.IsNotExist(err) {
		return "", fmt.Errorf(Localizer.Get("errors.read_file_error", &localizations.Replacements{"file": "header.gmi", "error": err}))
	}

	if main == "" {
		main = defaultString
	}
	return main + extraString, nil
}

// WriteJSON writes a FullData to disk
func (d *FullData) WriteJSON(dataPath string) error {
	// use .new so if there's a corruption when writing the old file is intact
	path := filepath.Join(dataPath, "comitium.json.new")

	d.RLock()
	jsonBytes, err := json.MarshalIndent(&d, "", "\t")
	d.RUnlock()
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(path, jsonBytes, 0666)
	if err != nil {
		return err
	}

	// if full write was successful then overwrite original
	err = os.Rename(path, filepath.Join(dataPath, "comitium.json"))
	return err
}

// TranslateString takes a template string, a localizer tag, and a localizer
// replacement set and will get the localized string from the localizer tag,
// replace everything using the localizer replacements, and then substitute that
// string into the template string.
func TranslateString(template, localizerTag string, replacements *localizations.Replacements) string {
	return TranslateStringLocalizer(Localizer, template, localizerTag, replacements)
}

// TranslateStringLocalizer takes a localizations.Localizer, template string, a
// localizer tag, and a localizer replacement set and will get the localized
// string from the localizer tag, replace everything using the localizer
// replacements, and then substitute that string into the template string.
// Should not be used once core.Init() is called, instead use TranslateString().
func TranslateStringLocalizer(localizer *localizations.Localizer, template, localizerTag string, replacements *localizations.Replacements) string {
	return fmt.Sprintf(template, localizer.Get(localizerTag, replacements))
}