aboutsummaryrefslogtreecommitdiffhomepage
path: root/storage/icon.go
blob: 5e8b5dc62d80682d145afb9594e696a0b6ef093e (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
// Copyright 2017 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.

package storage

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

	"github.com/miniflux/miniflux/model"
	"github.com/miniflux/miniflux/timer"
)

// HasIcon checks if the given feed has an icon.
func (s *Storage) HasIcon(feedID int64) bool {
	var result int
	query := `SELECT count(*) as c FROM feed_icons WHERE feed_id=$1`
	s.db.QueryRow(query, feedID).Scan(&result)
	return result == 1
}

// IconByID returns an icon by the ID.
func (s *Storage) IconByID(iconID int64) (*model.Icon, error) {
	defer timer.ExecutionTime(time.Now(), "[Storage:IconByID]")

	var icon model.Icon
	query := `SELECT id, hash, mime_type, content FROM icons WHERE id=$1`
	err := s.db.QueryRow(query, iconID).Scan(&icon.ID, &icon.Hash, &icon.MimeType, &icon.Content)
	if err == sql.ErrNoRows {
		return nil, nil
	} else if err != nil {
		return nil, fmt.Errorf("Unable to fetch icon by hash: %v", err)
	}

	return &icon, nil
}

// IconByFeedID returns a feed icon.
func (s *Storage) IconByFeedID(userID, feedID int64) (*model.Icon, error) {
	defer timer.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:IconByFeedID] userID=%d, feedID=%d", userID, feedID))
	query := `
		SELECT
		icons.id, icons.hash, icons.mime_type, icons.content
		FROM icons
		LEFT JOIN feed_icons ON feed_icons.icon_id=icons.id
		LEFT JOIN feeds ON feeds.id=feed_icons.feed_id
		WHERE feeds.user_id=$1 AND feeds.id=$2
		LIMIT 1
	`

	var icon model.Icon
	err := s.db.QueryRow(query, userID, feedID).Scan(&icon.ID, &icon.Hash, &icon.MimeType, &icon.Content)
	if err != nil {
		return nil, fmt.Errorf("unable to fetch icon: %v", err)
	}

	return &icon, nil
}

// IconByHash returns an icon by the hash (checksum).
func (s *Storage) IconByHash(icon *model.Icon) error {
	defer timer.ExecutionTime(time.Now(), "[Storage:IconByHash]")

	err := s.db.QueryRow(`SELECT id FROM icons WHERE hash=$1`, icon.Hash).Scan(&icon.ID)
	if err == sql.ErrNoRows {
		return nil
	} else if err != nil {
		return fmt.Errorf("Unable to fetch icon by hash: %v", err)
	}

	return nil
}

// CreateIcon creates a new icon.
func (s *Storage) CreateIcon(icon *model.Icon) error {
	defer timer.ExecutionTime(time.Now(), "[Storage:CreateIcon]")

	query := `
		INSERT INTO icons
		(hash, mime_type, content)
		VALUES
		($1, $2, $3)
		RETURNING id
	`
	err := s.db.QueryRow(
		query,
		icon.Hash,
		normalizeMimeType(icon.MimeType),
		icon.Content,
	).Scan(&icon.ID)

	if err != nil {
		return fmt.Errorf("Unable to create icon: %v", err)
	}

	return nil
}

// CreateFeedIcon creates an icon and associate the icon to the given feed.
func (s *Storage) CreateFeedIcon(feed *model.Feed, icon *model.Icon) error {
	defer timer.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:CreateFeedIcon] feedID=%d", feed.ID))

	err := s.IconByHash(icon)
	if err != nil {
		return err
	}

	if icon.ID == 0 {
		err := s.CreateIcon(icon)
		if err != nil {
			return err
		}
	}

	_, err = s.db.Exec(`INSERT INTO feed_icons (feed_id, icon_id) VALUES ($1, $2)`, feed.ID, icon.ID)
	if err != nil {
		return fmt.Errorf("unable to create feed icon: %v", err)
	}

	return nil
}

// Icons returns all icons tht belongs to a user.
func (s *Storage) Icons(userID int64) (model.Icons, error) {
	defer timer.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:Icons] userID=%d", userID))
	query := `
		SELECT
		icons.id, icons.hash, icons.mime_type, icons.content
		FROM icons
		LEFT JOIN feed_icons ON feed_icons.icon_id=icons.id
		LEFT JOIN feeds ON feeds.id=feed_icons.feed_id
		WHERE feeds.user_id=$1
	`

	rows, err := s.db.Query(query, userID)
	if err != nil {
		return nil, fmt.Errorf("unable to fetch icons: %v", err)
	}
	defer rows.Close()

	var icons model.Icons
	for rows.Next() {
		var icon model.Icon
		err := rows.Scan(&icon.ID, &icon.Hash, &icon.MimeType, &icon.Content)
		if err != nil {
			return nil, fmt.Errorf("unable to fetch icons row: %v", err)
		}
		icons = append(icons, &icon)
	}

	return icons, nil
}

func normalizeMimeType(mimeType string) string {
	mimeType = strings.ToLower(mimeType)
	switch mimeType {
	case "image/png", "image/jpeg", "image/jpg", "image/webp", "image/svg+xml", "image/x-icon", "image/gif":
		return mimeType
	default:
		return "image/x-icon"
	}
}