summaryrefslogtreecommitdiffstats
path: root/db/db.go
blob: d935467d0044f6e15373845fdefe102e371be2ef (plain) (blame)
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
// SPDX-License-Identifier: ISC
// Copyright © 2021 siddharth <s@ricketyspace.net>

package db

import (
	"encoding/json"
	"fmt"
	"os"
	"path"
	"sync"

	"ricketyspace.net/fern/file"
)

var dbPath string

// Contains information about list of media that where already
// download for different feeds.
//
// It's stored on disk as a JSON at `$HOME/.config/fern/db.json
type FernDB struct {
	mutex *sync.Mutex // For writes to `downloaded`
	// Key: feed-id
	// Value: feed-id's entries that were downloaded
	downloaded map[string][]string
}

func init() {
	dbPath = "" // Reset.

	// Construct default dbPath
	h, err := os.UserHomeDir()
	if err != nil {
		return
	}
	dbPath = path.Join(h, ".config", "fern", "db.json")

}

// Reads the fern db from disk and unmarshals it into a FernDB
// instance.
//
// Returns a pointer to FernDB on success; nil otherwise. The second
// return value is non-nil on error.
func Open() (*FernDB, error) {
	if len(dbPath) == 0 {
		return nil, fmt.Errorf("FernDB path not set")
	}

	// Check if db exists.
	_, err := os.Stat(dbPath)
	if err != nil {
		// db does not exist yet; create an empty one.
		db := new(FernDB)
		db.mutex = new(sync.Mutex)
		db.downloaded = make(map[string][]string)
		return db, nil
	}

	// Read db from disk.
	f, err := os.Open(dbPath)
	if err != nil {
		return nil, err
	}
	bs, err := file.Read(f)
	if err != nil {
		return nil, err
	}

	// Unmarshal db into an object.
	db := new(FernDB)
	db.mutex = new(sync.Mutex)
	err = json.Unmarshal(bs, &db.downloaded)
	if err != nil {
		return nil, err
	}
	return db, nil
}

// Returns true if an `entry` for `feed` exists in the database; false
// otherwise.
func (fdb *FernDB) Exists(feed, entry string) bool {
	if _, ok := fdb.downloaded[feed]; !ok {
		return false
	}
	for _, e := range fdb.downloaded[feed] {
		if e == entry {
			return true
		}
	}
	return false

}

// Adds `feed` <-> `entry` to the database.
//
// Once a `feed` <-> `entry` is added to the database, fern assumes
// that entry was downloaded and will not try downloading the entry
// again.
func (fdb *FernDB) Add(feed, entry string) {
	// Check if entry already exist for feed.
	if fdb.Exists(feed, entry) {
		return
	}

	// Add entry.
	fdb.mutex.Lock()
	if _, ok := fdb.downloaded[feed]; !ok {
		fdb.downloaded[feed] = make([]string, 0)
	}
	fdb.downloaded[feed] = append(fdb.downloaded[feed], entry)
	fdb.mutex.Unlock()
}

// Writes FernDB to disk in the JSON format.
//
// Returns nil on success; error otherwise
func (fdb *FernDB) Write() error {
	if len(dbPath) == 0 {
		return fmt.Errorf("FernDB path not set")
	}

	f, err := os.OpenFile(dbPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
	if err != nil {
		return err
	}
	defer f.Close()

	// Marshal database into json.
	bs, err := json.Marshal(fdb.downloaded)
	if err != nil {
		return err
	}

	// Write to disk.
	_, err = f.Write(bs)
	if err != nil {
		return err
	}
	return nil
}