all repos — homestead @ 3d1554e754ad2c57b7fc88e286169b510c3554ee

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

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

	"gitlab.com/tozd/go/errors"
	"go.alanpearce.eu/website/internal/buffer"
	"go.alanpearce.eu/website/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,
			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]io.ReadSeeker, 1),
	}
	var unixTime int64
	var encoding string
	var content []byte
	rows, err := r.queries.getFile.Query(filename)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, nil
		}

		return nil, errors.WithMessage(err, "querying database")
	}

	for rows.Next() {
		err = rows.Scan(
			&file.ContentType,
			&unixTime,
			&file.Etag,
			&encoding,
			&content,
		)
		if err != nil {
			if err == sql.ErrNoRows {
				return nil, nil
			}

			return nil, errors.WithMessage(err, "querying database")
		}

		file.LastModified = time.Unix(unixTime, 0)
		file.Encodings[encoding] = buffer.NewBuffer(content)
	}

	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.html"):
		cPath, differs = strings.CutSuffix(path, "index.html")
	case !strings.HasSuffix(path, "/"):
		cPath += "/"
		err := r.queries.checkPath.QueryRow(cPath).Scan(&differs)
		if err != nil {
			r.log.Warn("error canonicalising path", "path", path, "error", err)

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

			return
		}
	}

	return
}