all repos — homestead @ 6386b67a8d0c754efb814dce176312b2a05e909e

Code for my website

internal/storage/sqlite/reader.go (view raw)

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
package sqlite

import (
	"database/sql"
	"strings"
	"time"

	"gitlab.com/tozd/go/errors"
	"go.alanpearce.eu/homestead/internal/buffer"
	"go.alanpearce.eu/homestead/internal/storage"
	"go.alanpearce.eu/x/log"
)

type Reader struct {
	db      *sql.DB
	log     *log.Logger
	queries struct {
		getFile   *sql.Stmt
		checkPath *sql.Stmt
	}
}

func NewReader(db *sql.DB, log *log.Logger) (r *Reader, err error) {
	r = &Reader{
		log: log,
		db:  db,
	}
	r.queries.getFile, err = r.db.Prepare(`
		SELECT
			file.content_type,
			file.last_modified,
			file.etag,
			file.title,
			file.style_hash,
			content.encoding,
			content.body
		FROM url
		INNER JOIN file
			USING (url_id)
		INNER JOIN content
			USING (file_id)
		WHERE
			url.path = ?
	`)
	if err != nil {
		return nil, errors.WithMessage(err, "preparing select statement")
	}

	r.queries.checkPath, err = r.db.Prepare(`
		SELECT EXISTS(
			SELECT 1
			FROM url
			WHERE path = ?
		) AS differs
`)
	if err != nil {
		return nil, errors.WithMessage(err, "preparing check path statement")
	}

	return r, nil
}

func (r *Reader) GetFile(filename string) (*storage.File, error) {
	file := &storage.File{
		Encodings: make(map[string]*buffer.Buffer, 1),
	}
	var unixTime int64
	var encoding string
	var content []byte

	r.log.Debug("querying db for file", "filename", filename)
	rows, err := r.queries.getFile.Query(filename)
	if err != nil {
		return nil, errors.WithMessage(err, "querying database")
	}
	defer rows.Close()

	count := 0
	for rows.Next() {
		count++
		err = rows.Scan(
			&file.ContentType,
			&unixTime,
			&file.Etag,
			&file.Title,
			&file.StyleHash,
			&encoding,
			&content,
		)
		if err != nil {
			return nil, errors.WithMessage(err, "querying database")
		}

		file.LastModified = time.Unix(unixTime, 0)
		file.Encodings[encoding] = buffer.NewBuffer(content)
	}
	if count == 0 {
		return nil, nil
	}
	if err := rows.Err(); err != nil {
		return nil, errors.WithMessage(err, "error iterating rows")
	}

	return file, nil
}

func (r *Reader) CanonicalisePath(path string) (cPath string, differs bool) {
	cPath = path
	switch {
	case path == "/":
		differs = false
	case strings.HasSuffix(path, "/index.xml"):
		cPath, differs = strings.TrimSuffix(path, "index.xml")+"atom.xml", true
	case strings.HasSuffix(path, "/index.html"):
		cPath, differs = strings.CutSuffix(path, "index.html")
	case !strings.HasSuffix(path, "/"):
		err := r.queries.checkPath.QueryRow(cPath + "/").Scan(&differs)
		if err != nil {
			r.log.Warn("error canonicalising path", "path", path, "error", err)

			return
		}
		if differs {
			cPath += "/"
		}
	case strings.HasSuffix(path, "/"):
		tmp := strings.TrimSuffix(path, "/")
		err := r.queries.checkPath.QueryRow(tmp).Scan(&differs)
		if err != nil {
			r.log.Warn("error canonicalising path", "path", path, "error", err)

			return
		}
		if differs {
			cPath = tmp
		}
	}

	return
}