~chrisppy/beagles

81a8dadcfd2fe0762be8a4a31e166b9c1e77a88b — Chris Palmer 25 days ago 65570b1
Updates for restore and other fixes
9 files changed, 233 insertions(+), 40 deletions(-)

M db/db.go
M db/favorite.go
M db/feed.go
M db/item.go
M db/queue.go
M go.mod
M go.sum
M ui/content.go
M util/util.go
M db/db.go => db/db.go +157 -7
@@ 96,6 96,23 @@ func ReadDB(path string, gmniPath string) (*Storage, error) {
	return s, nil
}

func (s *Storage) deleteDBs(db *bolt.DB) error {
	if err := s.Favorites.del(db); err != nil {
		return err
	}
	if err := s.Queue.del(db); err != nil {
		return err
	}
	if err := s.Items.del(db); err != nil {
		return err
	}
	if err := s.Feeds.del(db); err != nil {
		return err
	}

	return nil
}

// Import will use an OPML file to insert feeds that you are not already
// subscribed.
func (s *Storage) Import(path string) error {


@@ 169,7 186,7 @@ func (s *Storage) Backup(path string) error {
	m := barefeed.MessageV1{
		Created:   barefeed.ToTimestamp(time.Now()),
		Generator: fmt.Sprintf("beagles v%s", s.Version),
		Feeds:     make([]barefeed.Feed, len(s.Feeds)),
		Feeds:     make([]barefeed.FeedV1, len(s.Feeds)),
	}

	i := 0


@@ 179,12 196,12 @@ func (s *Storage) Backup(path string) error {
			return err
		}

		f := barefeed.Feed{
		f := barefeed.FeedV1{
			Feed:        k,
			Title:       v.Title,
			Description: d,
			Link:        v.Link,
			Items:       make([]barefeed.Item, len(v.Items)),
			Items:       make([]barefeed.ItemV1, len(v.Items)),
		}

		if v.Updated != nil {


@@ 203,7 220,7 @@ func (s *Storage) Backup(path string) error {
				return nil
			}

			it := barefeed.Item{
			it := barefeed.ItemV1{
				Link:     k1,
				Title:    val.Title,
				Content:  c,


@@ 213,7 230,7 @@ func (s *Storage) Backup(path string) error {
			}

			if val.Type != "" || val.Location != "" || val.Length != "" {
				it.Media = &barefeed.Media{
				it.Media = &barefeed.MediaV1{
					Location: val.Location,
					Mimetype: val.Type,
					Position: int64(val.PlaybackPOS),


@@ 239,8 256,141 @@ func (s *Storage) Backup(path string) error {

// Restore will clear the database and restore using a barefeed file
func (s *Storage) Restore(path string) error {
	// TODOL Implement me.
	return fmt.Errorf("restore is not yet implemented")
	db, err := openDB(s.Path)
	if err != nil {
		return err
	}

	// delete all db buckets
	if err := s.deleteDBs(db); err != nil {
		if err := db.Close(); err != nil {
			return err
		}
		return err
	}

	// recreate all buckets
	if err := s.Favorites.create(db); err != nil {
		if err := db.Close(); err != nil {
			return err
		}
		return err
	}
	if err := s.Queue.create(db); err != nil {
		if err := db.Close(); err != nil {
			return err
		}
		return err
	}
	if err := s.Items.create(db); err != nil {
		if err := db.Close(); err != nil {
			return err
		}
		return err
	}
	if err := s.Feeds.create(db); err != nil {
		if err := db.Close(); err != nil {
			return err
		}
		return err
	}

	msg, err := barefeed.FromFile(filepath.Clean(path))
	if err != nil {
		if err := db.Close(); err != nil {
			return err
		}
		return err
	} else if msg == nil {
		if err := db.Close(); err != nil {
			return err
		}
		return fmt.Errorf("message is empty")
	}

	switch m := (*msg).(type) {
	case barefeed.MessageV1:
		if err := s.processMessageV1(db, m); err != nil {
			if err := s.deleteDBs(db); err != nil {
				if err := db.Close(); err != nil {
					return err
				}
				return err
			}

			if err := db.Close(); err != nil {
				return err
			}
			return err
		}
	default:
		if err := db.Close(); err != nil {
			return err
		}
		return fmt.Errorf("unsupported message format")
	}

	return db.Close()
}

func (s *Storage) processMessageV1(db *bolt.DB, msg barefeed.MessageV1) error {
	for _, feed := range msg.Feeds {
		f := Feed{
			Title:       feed.Title,
			Description: feed.Description,
			UpdateURL:   feed.Feed,
			Link:        feed.Link,
			Items:       make(map[string]bool),
		}

		if feed.Updated != nil {
			up := (*feed.Updated).Time()
			f.Updated = &up
		}

		for _, item := range feed.Items {
			it := Item{
				FeedURL:  f.UpdateURL,
				Title:    item.Title,
				Content:  item.Content,
				Link:     item.Link,
				Date:     item.Date.Time(),
				Read:     item.Read,
				Favorite: item.Favorite,
			}

			if item.Media != nil {
				it.Type = item.Media.Mimetype
				it.Location = item.Media.Location
				it.Length = fmt.Sprintf("%d", item.Media.Length)
				it.PlaybackPOS = int(item.Media.Position)
			}

			if !it.Read {
				if err := s.Queue.Insert(db, it.Link); err != nil {
					return err
				}
			}

			if it.Favorite {
				if err := s.Favorites.Insert(db, it.Link); err != nil {
					return err
				}
			}

			f.Items[it.Link] = false
			s.Items[it.Link] = &it
			if _, err := s.Items.Insert(db, &it); err != nil {
				return err
			}
		}

		if err := s.Feeds.insert(db, &f); err != nil {
			return err
		}
	}

	return nil
}

// CreateFeed will collect the rss feed and process through the elements

M db/favorite.go => db/favorite.go +13 -0
@@ 40,6 40,19 @@ func (f Favorites) create(db *bolt.DB) error {
	})
}

func (f Favorites) del(db *bolt.DB) error {
	return db.Update(func(tx *bolt.Tx) error {
		if b := tx.Bucket([]byte(favoriteTbl)); b == nil {
			return nil
		}

		if err := tx.DeleteBucket([]byte(favoriteTbl)); err != nil {
			return fmt.Errorf("error deleting favorite bucket: %s", err.Error())
		}
		return nil
	})
}

// Read will load the contents of the database into this structure
func (f Favorites) Read(db *bolt.DB) error {
	if err := f.create(db); err != nil {

M db/feed.go => db/feed.go +26 -5
@@ 134,6 134,19 @@ func (f Feeds) create(db *bolt.DB) error {
	})
}

func (f Feeds) del(db *bolt.DB) error {
	return db.Update(func(tx *bolt.Tx) error {
		if b := tx.Bucket([]byte(feedTbl)); b == nil {
			return nil
		}

		if err := tx.DeleteBucket([]byte(feedTbl)); err != nil {
			return fmt.Errorf("error deleting feed bucket: %s", err.Error())
		}
		return nil
	})
}

// Read will load the contents of the database into this structure
func (f Feeds) Read(db *bolt.DB) error {
	if err := f.create(db); err != nil {


@@ 176,20 189,28 @@ func (f Feeds) Insert(db *bolt.DB, url string, gmniPath string) ([]*gofeed.Item,

	feed.UpdateURL = url

	if err := f.insert(db, feed); err != nil {
		return nil, err
	}

	return pfeed.Items, nil
}

func (f Feeds) insert(db *bolt.DB, feed *Feed) error {
	feedJSON, err := json.Marshal(feed)
	if err != nil {
		return nil, fmt.Errorf("unable to marshal feed: %s", err.Error())
		return fmt.Errorf("unable to marshal feed: %s", err.Error())
	}

	if err := db.Update(func(tx *bolt.Tx) error {
		return tx.Bucket([]byte(feedTbl)).Put([]byte(url), feedJSON)
		return tx.Bucket([]byte(feedTbl)).Put([]byte(feed.UpdateURL), feedJSON)
	}); err != nil {
		return nil, fmt.Errorf("unable to insert feed: %s", err.Error())
		return fmt.Errorf("unable to insert feed: %s", err.Error())
	}

	f[url] = feed
	f[feed.UpdateURL] = feed

	return pfeed.Items, nil
	return nil
}

// Delete will remove a feed from the database

M db/item.go => db/item.go +13 -0
@@ 182,6 182,19 @@ func (f Items) create(db *bolt.DB) error {
	})
}

func (f Items) del(db *bolt.DB) error {
	return db.Update(func(tx *bolt.Tx) error {
		if b := tx.Bucket([]byte(itemTbl)); b == nil {
			return nil
		}

		if err := tx.DeleteBucket([]byte(itemTbl)); err != nil {
			return fmt.Errorf("error deleting item bucket: %s", err.Error())
		}
		return nil
	})
}

// Read will load the contents of the database into this structure
func (f Items) Read(db *bolt.DB) error {
	if err := f.create(db); err != nil {

M db/queue.go => db/queue.go +13 -0
@@ 40,6 40,19 @@ func (f Queue) create(db *bolt.DB) error {
	})
}

func (f Queue) del(db *bolt.DB) error {
	return db.Update(func(tx *bolt.Tx) error {
		if b := tx.Bucket([]byte(queueTbl)); b == nil {
			return nil
		}

		if err := tx.DeleteBucket([]byte(queueTbl)); err != nil {
			return fmt.Errorf("error deleting queue bucket: %s", err.Error())
		}
		return nil
	})
}

// Read will load the contents of the database into this structure
func (f Queue) Read(db *bolt.DB) error {
	if err := f.create(db); err != nil {

M go.mod => go.mod +2 -2
@@ 4,8 4,8 @@ go 1.15

require (
	git.sr.ht/~adnano/go-gemini v0.1.10
	git.sr.ht/~chrisppy/go-barefeed v0.0.0-20201231052557-c9dc6d4deb60
	git.sr.ht/~chrisppy/go-opml v0.0.0-20201229021831-d223d86f160e
	git.sr.ht/~chrisppy/go-barefeed v0.1.0
	git.sr.ht/~chrisppy/go-opml v1.0.0
	git.sr.ht/~emersion/go-scfg v0.0.0-20201019143924-142a8aa629fc
	git.sr.ht/~sircmpwn/getopt v0.0.0-20201218204720-9961a9c6298f
	github.com/DataDrake/waterlog v1.0.5

M go.sum => go.sum +4 -4
@@ 1,9 1,9 @@
git.sr.ht/~adnano/go-gemini v0.1.10 h1:enuYuY2pC+1BsP1GE73wLIyhXc4r+Ryx6TUubynmSgo=
git.sr.ht/~adnano/go-gemini v0.1.10/go.mod h1:If1VxEWcZDrRt5FeAFnGTcM2Ud1E3BXs3VJ5rnZWKq0=
git.sr.ht/~chrisppy/go-barefeed v0.0.0-20201231052557-c9dc6d4deb60 h1:B9GcMx6b4PirZp+kywPHnKwggOGKopWgwd+J4FvxXQg=
git.sr.ht/~chrisppy/go-barefeed v0.0.0-20201231052557-c9dc6d4deb60/go.mod h1:V/4QgRdPISXxrbyn/0YNdrh1qR2PHk8hdcIJsdmJFlQ=
git.sr.ht/~chrisppy/go-opml v0.0.0-20201229021831-d223d86f160e h1:XOqA6g2gAr/ma6/W8xlhyt7CUIhkSsi+vGBMpXGiaxk=
git.sr.ht/~chrisppy/go-opml v0.0.0-20201229021831-d223d86f160e/go.mod h1:dhjKH7fks5UN50Cnma/JCtxx8IRw8xAicwKV9gAtsHk=
git.sr.ht/~chrisppy/go-barefeed v0.1.0 h1:kyf1sJx4uGLesiXagd4sCP9C8+TPge+y/VxFTX6dI4E=
git.sr.ht/~chrisppy/go-barefeed v0.1.0/go.mod h1:V/4QgRdPISXxrbyn/0YNdrh1qR2PHk8hdcIJsdmJFlQ=
git.sr.ht/~chrisppy/go-opml v1.0.0 h1:Vf7UHZSXCiilzDBhfYT4RHo9ijmwiNkNxhXkyy3GLoM=
git.sr.ht/~chrisppy/go-opml v1.0.0/go.mod h1:dhjKH7fks5UN50Cnma/JCtxx8IRw8xAicwKV9gAtsHk=
git.sr.ht/~emersion/go-scfg v0.0.0-20201019143924-142a8aa629fc h1:51BD67xFX+bozd3ZRuOUfalrhx4/nQSh6A9lI08rYOk=
git.sr.ht/~emersion/go-scfg v0.0.0-20201019143924-142a8aa629fc/go.mod h1:t+Ww6SR24yYnXzEWiNlOY0AFo5E9B73X++10lrSpp4U=
git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3/go.mod h1:wMEGFFFNuPos7vHmWXfszqImLppbc0wEhh6JBfJIUgw=

M ui/content.go => ui/content.go +2 -20
@@ 22,7 22,6 @@ import (
	"time"

	"git.sr.ht/~chrisppy/beagles/db"
	"git.sr.ht/~chrisppy/beagles/util"
	"github.com/gdamore/tcell/v2"
	tui "gitlab.com/tslocum/cview"
)


@@ 63,16 62,7 @@ func (w *content) setDescription(text string, updated *time.Time, isHTML bool) {
		sb.WriteByte('\n')
	}

	if isHTML {
		t, err := util.StripHTML(text)
		if err != nil {
			w.Widget.SetTextColor(w.ErrColor)
			t = err.Error()
		}
		sb.WriteString(t)
	} else {
		sb.WriteString(text)
	}
	sb.WriteString(text)

	w.Widget.SetText(sb.String())
	w.Widget.ScrollToBeginning()


@@ 108,15 98,7 @@ func (w *content) setText(item *db.Item) {
	if c == "" {
		sb.WriteString("No Content")
		sb.WriteByte('\n')
	} else if strings.HasPrefix(item.Link, "http") {
		t, err := util.StripHTML(c)
		if err != nil {
			w.Widget.SetTextColor(w.ErrColor)
			t = err.Error()
		}
		sb.WriteString(t)
		sb.WriteByte('\n')
	} else if strings.HasPrefix(item.Link, "gemini") {
	} else {
		sb.WriteString(c)
		sb.WriteByte('\n')
	}

M util/util.go => util/util.go +3 -2
@@ 297,8 297,9 @@ func ExtPlay(path, bin string, args []string) {
// StripHTML from the included text
func StripHTML(text string) (string, error) {
	options := ht.Options{
		OmitLinks:    false,
		PrettyTables: true,
		OmitLinks:           false,
		PrettyTables:        true,
		PrettyTablesOptions: ht.NewPrettyTablesOptions(),
	}

	return ht.FromString(text, options)