Files
comitium/core/structs.go
T
2021-03-18 13:34:02 -06:00

196 lines
5.3 KiB
Go

// Copyright (C) 2021 nytpu
// SPDX-License-Identifier: AGPL-3.0-or-later
// 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
import (
"reflect"
"sync"
"time"
"github.com/mmcdole/gofeed"
)
// FullData is used for both reading from json and for storing the global feeds
// map. contains read mutexes so concurrency can be used.
type FullData struct {
Feeds map[string]*Feed `json:"feeds,omitempty"`
Pages map[string]*Page `json:"pages,omitempty"`
FeedsMu *sync.RWMutex `json:"-"`
PagesMu *sync.RWMutex `json:"-"`
}
// Feed holds all the necessary fields when storing a feed
type Feed struct {
Title string `json:"title,omitempty"`
Link string `json:"link,omitempty"` // link to the human-readable page for the feed. same as FeedLink for gemini feeds.
FeedLink string `json:"feedlink,omitempty"` // link to the feed itself.
Updated time.Time `json:"updated,omitempty"`
Items []*Item `json:"items,omitempty"`
}
// Item is a single item from a subscripton
type Item struct {
Title string `json:"title,omitempty"` // if empty, use Link as title
Content string `json:"content,omitempty"`
Link string `json:"link,omitempty"`
Published time.Time `json:"updated,omitempty"`
}
// Page holds all the necessary fields when storing a page being watched for
// changes.
type Page struct {
Title string `json:"title,omitempty"`
Link string `json:"link,omitempty"`
Updated time.Time `json:"updated,omitempty"`
Hash string `json:"hash,omitempty"`
}
// gofeedConvert converts a gofeed.Feed into a Feed struct
func GofeedConvert(f *gofeed.Feed, title string) (*Feed, error) {
new := &Feed{}
if title != "" {
new.Title = title
} else {
new.Title = f.Title
}
if f.UpdatedParsed != nil {
new.Updated = *f.UpdatedParsed
} else if f.PublishedParsed != nil {
new.Updated = *f.PublishedParsed
}
new.Link = f.Link
new.FeedLink = f.FeedLink
for _, v := range f.Items {
item := &Item{}
item.Title = v.Title
item.Content = v.Content
if v.UpdatedParsed != nil {
item.Published = *v.UpdatedParsed
} else if v.PublishedParsed != nil {
item.Published = *v.PublishedParsed
}
item.Link = f.FeedLink
new.Items = append(new.Items, item)
}
return new, nil
}
// InsertFeed will insert or update a core.Feed in a core.FullData
func (d *FullData) InsertFeed(new *Feed, remote string) {
d.FeedsMu.Lock()
oldFeed, ok := d.Feeds[remote]
if !ok || !reflect.DeepEqual(new, oldFeed) {
// Feeds are different, or there was never an old one
if oldFeed != nil && new.Title == "" && oldFeed.Title != "" {
new.Title = oldFeed.Title
}
d.Feeds[remote] = new
d.FeedsMu.Unlock()
} else {
d.FeedsMu.Unlock()
}
}
// InsertPage will insert or update a core.Page in a core.FullData
func (d *FullData) InsertPage(new *Page, remote string) {
d.PagesMu.Lock()
oldPage, ok := d.Pages[remote]
if !ok || d.Pages[remote].Hash != new.Hash {
// pages are different, or there was never an old one
if oldPage != nil && new.Title == "" && oldPage.Title != "" {
new.Title = oldPage.Title
}
d.Pages[remote] = new
d.PagesMu.Unlock()
} else {
d.PagesMu.Unlock()
}
}
// Implement sort.Interface for Feed
// Len returns the length of Items.
func (f *Feed) Len() int {
return len(f.Items)
}
// Less compares Published of Items[i], Items[k] and returns true if Items[i] is
// before Items[j].
func (f *Feed) Less(i, j int) bool {
return f.Items[i].Published.Before(f.Items[j].Published)
}
// Swap swaps Items[i] and Items[j].
func (f *Feed) Swap(i, j int) {
f.Items[i], f.Items[j] = f.Items[j], f.Items[i]
}
// Lock locks both feed and page mutexes.
func (j *FullData) Lock() {
j.FeedsMu.Lock()
j.PagesMu.Lock()
}
// Unlock unlocks both feed and page mutexes.
func (j *FullData) Unlock() {
j.FeedsMu.Unlock()
j.PagesMu.Unlock()
}
// RLock read-locks both feed and page mutexes.
func (j *FullData) RLock() {
j.FeedsMu.RLock()
j.PagesMu.RLock()
}
// RUnlock read-unlocks both feed and page mutexes.
func (j *FullData) RUnlock() {
j.FeedsMu.RUnlock()
j.PagesMu.RUnlock()
}
// PageEntry is a single item on a subscriptions page. It is used for both feeds
// and pages.
type PageEntry struct {
Prefix string // whatever goes before the post title to identify the feed.
Title string // article/entry title
URL string
Published time.Time
}
// PageEntries is reverse chronological list of entries used when generating subs.
type PageEntries struct {
Entries []*PageEntry
}
// Implement sort.Interface for PageEntries
// Len returns the length of Entries
func (e *PageEntries) Len() int {
return len(e.Entries)
}
// Less compares the Published if Entries[i], Entries[j] and returns true if
// Entries[i] is /after/ Entries[j] (for reverse chronological sorting)
func (e *PageEntries) Less(i, j int) bool {
return e.Entries[i].Published.After(e.Entries[j].Published)
}
// Swap swaps Entries[i] and Entries[j]
func (e *PageEntries) Swap(i, j int) {
e.Entries[i], e.Entries[j] = e.Entries[j], e.Entries[i]
}