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
143
144
145
146
147
148
149
|
// 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
var defaultDBPath 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
}
defaultDBPath = path.Join(h, ".config", "fern", "db.json")
dbPath = defaultDBPath
}
// 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
}
// Sets DB path to the default path. This function is meant to be used
// by tests.
func resetDBPath() {
dbPath = defaultDBPath
}
|