From 6bd8a6c2cd153bad9ca044b409e55302e10206c1 Mon Sep 17 00:00:00 2001 From: René 'Necoro' Neumann Date: Sat, 2 May 2020 20:53:35 +0200 Subject: Restructure --- internal/feed/cache.go | 2 +- internal/feed/cache_v1.go | 23 ++++++++---------- internal/feed/feed.go | 43 +--------------------------------- internal/feed/item.go | 59 +++++++++++++++++++++++++++++++++++++++++++++++ internal/feed/mail.go | 55 +++++++++++++++++++++---------------------- internal/feed/parse.go | 10 ++++---- main.go | 6 ++--- pkg/config/config.go | 8 +++---- 8 files changed, 111 insertions(+), 95 deletions(-) create mode 100644 internal/feed/item.go diff --git a/internal/feed/cache.go b/internal/feed/cache.go index 731eab1..5674de4 100644 --- a/internal/feed/cache.go +++ b/internal/feed/cache.go @@ -28,7 +28,7 @@ type CachedFeed interface { Failures() int Last() time.Time ID() string - filterItems(items []feeditem, ignoreHash bool, alwaysNew bool) []feeditem + filterItems(items []item, ignoreHash bool, alwaysNew bool) []item Commit() } diff --git a/internal/feed/cache_v1.go b/internal/feed/cache_v1.go index b2813ce..9a6de50 100644 --- a/internal/feed/cache_v1.go +++ b/internal/feed/cache_v1.go @@ -6,8 +6,6 @@ import ( "strconv" "time" - "github.com/lithammer/shortuuid" - "github.com/Necoro/feed2imap-go/pkg/log" "github.com/Necoro/feed2imap-go/pkg/util" ) @@ -153,11 +151,10 @@ func (cache *v1Cache) findItem(feed *Feed) CachedFeed { return item } -func newCachedItem(item feeditem) cachedItem { +func (item *item) newCachedItem() cachedItem { var ci cachedItem - ci.ID = shortuuid.New() - + ci.ID = item.itemId ci.Title = item.Item.Title ci.Link = item.Item.Link if item.Item.PublishedParsed != nil { @@ -187,28 +184,30 @@ func (cf *cachedFeed) deleteItem(index int) { cf.Items = cf.Items[:len(cf.Items)-1] } -func (cf *cachedFeed) filterItems(items []feeditem, ignoreHash, alwaysNew bool) []feeditem { +func (cf *cachedFeed) filterItems(items []item, ignoreHash, alwaysNew bool) []item { if len(items) == 0 { return items } - cacheItems := make(map[cachedItem]*feeditem, len(items)) + cacheItems := make(map[cachedItem]*item, len(items)) for idx := range items { // remove complete duplicates on the go - cacheItems[newCachedItem(items[idx])] = &items[idx] + cacheItems[items[idx].newCachedItem()] = &items[idx] } log.Debugf("%d items after deduplication", len(cacheItems)) - filtered := make([]feeditem, 0, len(items)) + filtered := make([]item, 0, len(items)) cacheadd := make([]cachedItem, 0, len(items)) - app := func(item *feeditem, ci cachedItem, oldIdx *int) { + app := func(item *item, ci cachedItem, oldIdx *int) { if oldIdx != nil { item.updateOnly = true + prevId := cf.Items[*oldIdx].ID + ci.ID = prevId + item.itemId = prevId cf.deleteItem(*oldIdx) } filtered = append(filtered, *item) cacheadd = append(cacheadd, ci) - item.itemId = ci.ID } CACHE_ITEMS: @@ -228,7 +227,6 @@ CACHE_ITEMS: log.Debugf("Guid matches with: %s", oldItem) if !oldItem.similarTo(&ci, ignoreHash) { item.addReason("guid (upd)") - ci.ID = oldItem.ID app(item, ci, &idx) } else { log.Debugf("Similar, ignoring") @@ -258,7 +256,6 @@ CACHE_ITEMS: } log.Debugf("Link matches, updating: %s", oldItem) item.addReason("link (upd)") - ci.ID = oldItem.ID app(item, ci, &idx) continue CACHE_ITEMS diff --git a/internal/feed/feed.go b/internal/feed/feed.go index 9ed44df..4a0e724 100644 --- a/internal/feed/feed.go +++ b/internal/feed/feed.go @@ -7,13 +7,12 @@ import ( "github.com/Necoro/feed2imap-go/pkg/config" "github.com/Necoro/feed2imap-go/pkg/log" - "github.com/Necoro/feed2imap-go/pkg/util" ) type Feed struct { *config.Feed feed *gofeed.Feed - items []feeditem + items []item cached CachedFeed Global config.GlobalOptions } @@ -23,46 +22,6 @@ type feedDescriptor struct { Url string } -type feedImage struct { - image []byte - mime string -} - -type feeditem struct { - *gofeed.Feed - *gofeed.Item - Body string - updateOnly bool - reasons []string - images []feedImage - itemId string -} - -// Creator returns the name of the creating author. -// MUST NOT have `*feeditem` has the receiver, because the template breaks then. -func (item feeditem) Creator() string { - if item.Item.Author != nil { - return item.Item.Author.Name - } - return "" -} - -func (item *feeditem) addReason(reason string) { - if !util.StrContains(item.reasons, reason) { - item.reasons = append(item.reasons, reason) - } -} - -func (item *feeditem) addImage(img []byte, mime string) int { - i := feedImage{img, mime} - item.images = append(item.images, i) - return len(item.images) -} - -func (item *feeditem) clearImages() { - item.images = []feedImage{} -} - func (feed *Feed) descriptor() feedDescriptor { return feedDescriptor{ Name: feed.Name, diff --git a/internal/feed/item.go b/internal/feed/item.go new file mode 100644 index 0000000..5c67784 --- /dev/null +++ b/internal/feed/item.go @@ -0,0 +1,59 @@ +package feed + +import ( + "fmt" + + "github.com/mmcdole/gofeed" + + "github.com/Necoro/feed2imap-go/pkg/config" + "github.com/Necoro/feed2imap-go/pkg/util" +) + +type feedImage struct { + image []byte + mime string +} + +type item struct { + *gofeed.Feed + *gofeed.Item + feed *Feed + Body string + updateOnly bool + reasons []string + images []feedImage + itemId string +} + +// Creator returns the name of the creating author. +// MUST NOT have `*item` has the receiver, because the template breaks then. +func (item *item) Creator() string { + if item.Item.Author != nil { + return item.Item.Author.Name + } + return "" +} + +func (item *item) addReason(reason string) { + if !util.StrContains(item.reasons, reason) { + item.reasons = append(item.reasons, reason) + } +} + +func (item *item) addImage(img []byte, mime string) int { + i := feedImage{img, mime} + item.images = append(item.images, i) + return len(item.images) +} + +func (item *item) clearImages() { + item.images = []feedImage{} +} + +func (item *item) defaultEmail() string { + return item.feed.Global.DefaultEmail +} + +func (item *item) messageId() string { + return fmt.Sprintf("", item.feed.cached.ID(), item.itemId, config.Hostname()) +} diff --git a/internal/feed/mail.go b/internal/feed/mail.go index ebf032a..41a4cbd 100644 --- a/internal/feed/mail.go +++ b/internal/feed/mail.go @@ -25,33 +25,37 @@ func address(name, address string) []*mail.Address { return []*mail.Address{{Name: name, Address: address}} } -func fromAdress(feed *Feed, item feeditem, cfg *config.Config) []*mail.Address { +func (item *item) fromAdress() []*mail.Address { switch { case item.Item.Author != nil && item.Item.Author.Email != "": return address(item.Item.Author.Name, item.Item.Author.Email) case item.Item.Author != nil && item.Item.Author.Name != "": - return address(item.Item.Author.Name, cfg.DefaultEmail) + return address(item.Item.Author.Name, item.defaultEmail()) case item.Feed.Author != nil && item.Feed.Author.Email != "": return address(item.Feed.Author.Name, item.Feed.Author.Email) case item.Feed.Author != nil && item.Feed.Author.Name != "": - return address(item.Feed.Author.Name, cfg.DefaultEmail) + return address(item.Feed.Author.Name, item.defaultEmail()) default: - return address(feed.Name, cfg.DefaultEmail) + return address(item.feed.Name, item.defaultEmail()) } } -func writeHtml(writer io.Writer, item feeditem) error { +func (item *item) toAddress() []*mail.Address { + return address(item.feed.Name, item.defaultEmail()) +} + +func (item *item) writeHtml(writer io.Writer) error { return template.Feed.Execute(writer, item) } -func buildHeader(feed *Feed, item feeditem, cfg *config.Config) message.Header { +func (item *item) buildHeader() message.Header { var h mail.Header h.SetContentType("multipart/alternative", nil) - h.SetAddressList("From", fromAdress(feed, item, cfg)) - h.SetAddressList("To", address(feed.Name, cfg.DefaultEmail)) + h.SetAddressList("From", item.fromAdress()) + h.SetAddressList("To", item.toAddress()) h.Set("X-Feed2Imap-Version", config.Version()) h.Set("X-Feed2Imap-Reason", strings.Join(item.reasons, ",")) - h.Set("Message-Id", feed.messageId(item)) + h.Set("Message-Id", item.messageId()) { // date date := item.Item.PublishedParsed @@ -75,7 +79,7 @@ func buildHeader(feed *Feed, item feeditem, cfg *config.Config) message.Header { return h.Header } -func writeHtmlPart(w *message.Writer, item feeditem) error { +func (item *item) writeHtmlPart(w *message.Writer) error { var ih message.Header ih.SetContentType("text/html", map[string]string{"charset": "utf-8"}) ih.SetContentDisposition("inline", nil) @@ -87,14 +91,14 @@ func writeHtmlPart(w *message.Writer, item feeditem) error { } defer partW.Close() - if err = writeHtml(w, item); err != nil { + if err = item.writeHtml(w); err != nil { return fmt.Errorf("writing html part: %w", err) } return nil } -func writeImagePart(w *message.Writer, img feedImage, cid string) error { +func (img *feedImage) writeImagePart(w *message.Writer, cid string) error { var ih message.Header ih.SetContentType(img.mime, nil) ih.SetContentDisposition("inline", nil) @@ -114,8 +118,8 @@ func writeImagePart(w *message.Writer, img feedImage, cid string) error { return nil } -func writeToBuffer(b *bytes.Buffer, feed *Feed, item feeditem, cfg *config.Config) error { - h := buildHeader(feed, item, cfg) +func (item *item) writeToBuffer(b *bytes.Buffer) error { + h := item.buildHeader() writer, err := message.CreateWriter(b, h) if err != nil { @@ -123,8 +127,8 @@ func writeToBuffer(b *bytes.Buffer, feed *Feed, item feeditem, cfg *config.Confi } defer writer.Close() - if cfg.WithPartHtml() { - feed.buildBody(&item) + if item.feed.Global.WithPartHtml() { + item.buildBody() var relWriter *message.Writer if len(item.images) > 0 { @@ -138,13 +142,13 @@ func writeToBuffer(b *bytes.Buffer, feed *Feed, item feeditem, cfg *config.Confi relWriter = writer } - if err = writeHtmlPart(relWriter, item); err != nil { + if err = item.writeHtmlPart(relWriter); err != nil { return err } for idx, img := range item.images { cid := cidNr(idx + 1) - if err = writeImagePart(relWriter, img, cid); err != nil { + if err = img.writeImagePart(relWriter, cid); err != nil { return err } } @@ -155,23 +159,23 @@ func writeToBuffer(b *bytes.Buffer, feed *Feed, item feeditem, cfg *config.Confi return nil } -func asMail(feed *Feed, item feeditem, cfg *config.Config) (string, error) { +func (item *item) asMail() (string, error) { var b bytes.Buffer - if err := writeToBuffer(&b, feed, item, cfg); err != nil { + if err := item.writeToBuffer(&b); err != nil { return "", err } return b.String(), nil } -func (feed *Feed) ToMails(cfg *config.Config) ([]string, error) { +func (feed *Feed) ToMails() ([]string, error) { var ( err error mails = make([]string, len(feed.items)) ) for idx := range feed.items { - if mails[idx], err = asMail(feed, feed.items[idx], cfg); err != nil { + if mails[idx], err = feed.items[idx].asMail(); err != nil { return nil, fmt.Errorf("creating mails for %s: %w", feed.Name, err) } } @@ -228,11 +232,8 @@ func getBody(content, description string, bodyCfg config.Body) string { } } -func (feed *Feed) messageId(item feeditem) string { - return fmt.Sprintf("", feed.cached.ID(), item.itemId, config.Hostname()) -} - -func (feed *Feed) buildBody(item *feeditem) { +func (item *item) buildBody() { + feed := item.feed body := getBody(item.Item.Content, item.Item.Description, feed.Body) if !feed.InclImages { diff --git a/internal/feed/parse.go b/internal/feed/parse.go index 435c0ed..dfb447a 100644 --- a/internal/feed/parse.go +++ b/internal/feed/parse.go @@ -41,7 +41,7 @@ func httpClient(disableTLS bool) *http.Client { return stdHTTPClient } -func parseFeed(feed *Feed) error { +func (feed *Feed) parse() error { ctx, cancel := context(feed.Global.Timeout) defer cancel() @@ -54,9 +54,9 @@ func parseFeed(feed *Feed) error { } feed.feed = parsedFeed - feed.items = make([]feeditem, len(parsedFeed.Items)) - for idx, item := range parsedFeed.Items { - feed.items[idx] = feeditem{Feed: parsedFeed, Item: item, itemId: shortuuid.New()} + feed.items = make([]item, len(parsedFeed.Items)) + for idx, feedItem := range parsedFeed.Items { + feed.items[idx] = item{Feed: parsedFeed, Item: feedItem, itemId: shortuuid.New(), feed: feed} } return nil } @@ -64,7 +64,7 @@ func parseFeed(feed *Feed) error { func handleFeed(feed *Feed) { log.Printf("Fetching %s from %s", feed.Name, feed.Url) - err := parseFeed(feed) + err := feed.parse() if err != nil { if feed.cached.Failures() >= feed.Global.MaxFailures { log.Error(err) diff --git a/main.go b/main.go index 7335e6f..7f31a48 100644 --- a/main.go +++ b/main.go @@ -19,8 +19,8 @@ var debug = flag.Bool("d", false, "enable debug output") var dryRun = flag.Bool("dry-run", false, "do everything short of uploading and writing the cache") var buildCache = flag.Bool("build-cache", false, "only (re)build the cache; useful after migration or when the cache is lost or corrupted") -func processFeed(feed *feed.Feed, cfg *config.Config, client *imap.Client, dryRun bool) { - mails, err := feed.ToMails(cfg) +func processFeed(feed *feed.Feed, client *imap.Client, dryRun bool) { + mails, err := feed.ToMails() if err != nil { log.Errorf("Processing items of feed %s: %s", feed.Name, err) return @@ -103,7 +103,7 @@ func run() error { if !*buildCache { state.ForeachGo(func(f *feed.Feed) { - processFeed(f, cfg, c, *dryRun) + processFeed(f, c, *dryRun) }) } diff --git a/pkg/config/config.go b/pkg/config/config.go index 993cd71..689ad58 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -85,13 +85,13 @@ func (cfg *Config) Validate() error { } // Marks whether 'text' part should be included in mails -func (cfg *Config) WithPartText() bool { - return util.StrContains(cfg.Parts, "text") +func (opt GlobalOptions) WithPartText() bool { + return util.StrContains(opt.Parts, "text") } // Marks whether 'html' part should be included in mails -func (cfg *Config) WithPartHtml() bool { - return util.StrContains(cfg.Parts, "html") +func (opt GlobalOptions) WithPartHtml() bool { + return util.StrContains(opt.Parts, "html") } // Current feed2imap version -- cgit v1.2.3-54-g00ecf