summaryrefslogtreecommitdiff
path: root/internal/server/repository/repository.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2024-09-21 14:03:45 +0300
committerPaul Buetow <paul@buetow.org>2024-09-21 14:03:45 +0300
commitdff4d455e07d639b82a0bed814f41d0656e9b6d0 (patch)
tree56289a6fd80a00a9724992ab9cb8b3d7215ebedf /internal/server/repository/repository.go
parent780ade3dc066afb8a43be824373414f3d316ebd6 (diff)
cleanup
Diffstat (limited to 'internal/server/repository/repository.go')
-rw-r--r--internal/server/repository/repository.go320
1 files changed, 0 insertions, 320 deletions
diff --git a/internal/server/repository/repository.go b/internal/server/repository/repository.go
deleted file mode 100644
index d277884..0000000
--- a/internal/server/repository/repository.go
+++ /dev/null
@@ -1,320 +0,0 @@
-package repository
-
-import (
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "log"
- "regexp"
- "sync"
- "time"
-
- "codeberg.org/snonux/gos/internal/config/server"
- "codeberg.org/snonux/gos/internal/easyhttp"
- "codeberg.org/snonux/gos/internal/types"
- "codeberg.org/snonux/gos/internal/vfs"
-)
-
-var (
- instance Repository
- once sync.Once
-)
-
-type fs interface {
- ReadFile(name string) ([]byte, error)
- WriteFile(filePath string, bytes []byte) error
- FindFiles(dataPath, suffix string) ([]string, error)
-}
-
-// Contains an Entry ID and its checksumm, for the list and merge operations.
-type entryPair struct {
- ID, Checksum string
-}
-
-// Holds all entries in the database / stores them to the disks..
-// TODO: Keep track of how many posts were made this week already.
-type Repository struct {
- pending pending
- stats stats
- conf server.ServerConfig
- entries map[types.EntryID]types.Entry
- mu *sync.Mutex
- fs fs
- loaded *bool
- getIdRe *regexp.Regexp
-}
-
-func Instance(conf server.ServerConfig) Repository {
- once.Do(func() {
- instance = newRepository(conf, vfs.RealFS{})
- })
- return instance
-}
-
-// Need to register all social platforms for in-memory representation of shared posts and so on.
-func newRepository(conf server.ServerConfig, fs fs) Repository {
- var loaded bool
- return Repository{
- pending: newPending(), // TODO: Make use of the pending for the selection algoritmh for the next post
- stats: newStats(), // TODO: Make use of this.
- conf: conf,
- entries: make(map[types.EntryID]types.Entry),
- mu: &sync.Mutex{},
- fs: fs,
- loaded: &loaded,
- getIdRe: regexp.MustCompile(`^[a-z0-9]{64}$`),
- }
-}
-
-// Gets next entry to be shared for the given social platform.
-func (r Repository) Next(platform types.PlatformName) (types.Entry, bool) {
- r.mu.Lock()
- defer r.mu.Unlock()
-
- id, ok := r.pending.next(platform)
- if !ok {
- return types.Entry{}, false // No entry found
- }
-
- var entry types.Entry
- entry, ok = r.entries[id]
- if !ok {
- panic("did not expect that!")
- }
- return entry, true
-}
-
-// Load repository into memory if not done yet.
-func (r Repository) load() error {
- if *r.loaded {
- return nil
- }
-
- filePaths, err := r.fs.FindFiles(r.conf.DataDir, ".json")
- if err != nil {
- return err
- }
-
- var errs []error
- for _, filePath := range filePaths {
- log.Println("loading entry", filePath)
-
- bytes, err := r.fs.ReadFile(filePath)
- if err != nil {
- errs = append(errs, err)
- continue
- }
-
- entry, err := types.NewEntry(bytes)
- if err != nil {
- errs = append(errs, err)
- continue
- }
- r.mu.Lock()
- r.add(entry)
- r.mu.Unlock()
- }
-
- if len(errs) == 0 {
- *r.loaded = true
- }
-
- return errors.Join(errs...)
-}
-
-func (r Repository) List() ([]entryPair, error) {
- if err := r.load(); err != nil {
- return []entryPair{}, err
- }
-
- var pairs []entryPair
- r.mu.Lock()
- defer r.mu.Unlock()
-
- for _, entry := range r.entries {
- pairs = append(pairs, entryPair{entry.ID, entry.Checksum()})
- }
-
- return pairs, nil
-}
-
-func (r Repository) ListBytes() ([]byte, error) {
- pairs, err := r.List()
- if err != nil {
- return []byte{}, err
- }
- return json.Marshal(pairs)
-}
-
-func (r Repository) add(entry types.Entry) {
- r.entries[entry.ID] = entry
-
- for _, platform := range r.conf.SocialPlatformsEnabled {
- if entry.IsShared(platform) {
- r.pending.delete(platform, entry.ID)
- } else {
- r.pending.add(platform, entry.ID)
- }
- }
-}
-
-func (r Repository) persist(entry types.Entry) error {
- r.add(entry)
-
- bytes, err := entry.JSONMarshal()
- if err != err {
- return err
- }
- return r.fs.WriteFile(r.entryPath(entry), bytes)
-}
-
-func (r Repository) Get(id types.EntryID) (types.Entry, error) {
- if !r.getIdRe.MatchString(id) {
- return types.Entry{}, fmt.Errorf("invalid id %s", id)
- }
- if err := r.load(); err != nil {
- return types.Entry{}, err
- }
-
- r.mu.Lock()
- defer r.mu.Unlock()
-
- entry, ok := r.entries[id]
- if !ok {
- return entry, fmt.Errorf("no entry with id %s found", id)
- }
- return entry, nil
-}
-
-func (r Repository) GetJSON(id types.EntryID) (string, error) {
- entry, err := r.Get(id)
- if err != nil {
- return "", err
- }
-
- bytes, err := entry.JSONMarshal()
- if err != nil {
- return "", err
- }
-
- return string(bytes), err
-}
-
-func (r Repository) hasSameEntry(pair entryPair) bool {
- r.mu.Lock()
- defer r.mu.Unlock()
-
- entry, ok := r.entries[pair.ID]
- if !ok || entry.Checksum() != pair.Checksum {
- return false
- }
- return true
-}
-
-func (r Repository) entryPath(ent types.Entry) string {
- return fmt.Sprintf("%s/%s/%s.json", r.conf.DataDir, time.Now().Format("2006"), ent.ID)
-}
-
-func (r Repository) Merge(otherEnt types.Entry) error {
- if err := r.load(); err != nil {
- return err
- }
-
- r.mu.Lock()
- defer r.mu.Unlock()
-
- entry, ok := r.entries[otherEnt.ID]
- if !ok {
- log.Println("can't find entry with ID", otherEnt.ID, "in local db, create new from copy")
- var err error
- if entry, err = types.NewEntryFromCopy(otherEnt); err != nil {
- return err
- }
- return r.persist(entry)
- }
-
- if entry, changed, err := entry.Update(otherEnt); changed {
- if err != nil {
- return err
- }
- return r.persist(entry)
- }
- return nil
-}
-
-func (r Repository) MergeRemotely(ctx context.Context) error {
- var errs []error
-
- if len(r.conf.Partners) == 0 {
- log.Println("No partners configured - skipping remote merge operation")
- return nil
- }
-
- for _, partner := range r.conf.Partners {
- if err := r.mergeRemotelyFromPartner(ctx, partner); err != nil {
- errs = append(errs, err)
- }
- }
-
- return errors.Join(errs...)
-}
-
-// Makes it mockable/testable
-type getPairDataFunc func(context.Context, string, *[]entryPair) error
-type getEntryDataFunc func(context.Context, string, string, *types.Entry) error
-
-func (r Repository) mergeRemotelyFromPartner(ctx context.Context, partner string) error {
- getPair := func(ctx context.Context, partner string, pairs *[]entryPair) error {
- uri := fmt.Sprintf("%s/list", partner)
- return easyhttp.GetData(ctx, uri, r.conf.APIKey, pairs)
- }
-
- getEntry := func(ctx context.Context, partner, id types.EntryID, entry *types.Entry) error {
- uri := fmt.Sprintf("%s/get?id=%s", partner, id)
- return easyhttp.GetData(ctx, uri, r.conf.APIKey, entry)
- }
-
- return r.mergeFromPartner(ctx, partner, getPair, getEntry)
-}
-
-func (r Repository) mergeFromPartner(ctx context.Context, partner string,
- getPair getPairDataFunc, getEntry getEntryDataFunc) error {
-
- if err := r.load(); err != nil {
- return err
- }
-
- var (
- errs []error
- pairs []entryPair
- )
-
- if err := getPair(ctx, partner, &pairs); err != nil {
- return err
- }
-
- for _, pair := range pairs {
- if r.hasSameEntry(pair) {
- continue
- }
-
- log.Println("pair", pair, "missing in local reposotory, going to merge it")
-
- var entry types.Entry
- if err := getEntry(ctx, partner, pair.ID, &entry); err != nil {
- errs = append(errs, err)
- continue
- }
-
- // In theory, this should never happen
- if pair.ID != entry.ID {
- errs = append(errs, fmt.Errorf("pair ID %s does not match entry id %s", pair.ID, entry.ID))
- continue
- }
-
- errs = append(errs, r.Merge(entry))
- }
-
- return errors.Join(errs...)
-}