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 141 | 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) (*Reader, errors.E) {
r := &Reader{
log: log,
db: db,
}
var err error
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, errors.E) {
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
}
|