mirror of
https://codeberg.org/scip/anydb.git
synced 2025-12-17 04:20:59 +01:00
add import+export, json output, humanize wide output, fixes
This commit is contained in:
213
app/db.go
213
app/db.go
@@ -2,11 +2,11 @@ package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -35,7 +35,6 @@ type DbTag struct {
|
||||
}
|
||||
|
||||
const BucketData string = "data"
|
||||
const BucketTags string = "tags"
|
||||
|
||||
func New(file string, debug bool) (*DB, error) {
|
||||
if _, err := os.Stat(filepath.Dir(file)); os.IsNotExist(err) {
|
||||
@@ -141,7 +140,38 @@ func (db *DB) Set(attr *DbAttr) error {
|
||||
Created: time.Now(),
|
||||
}
|
||||
|
||||
err := db.DB.Update(func(tx *bolt.Tx) error {
|
||||
// check if the entry already exists and if yes, check if it has
|
||||
// any tags. if so, we initialize our update struct with these
|
||||
// tags unless it has new tags configured.
|
||||
err := db.DB.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketData))
|
||||
if bucket == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
jsonentry := bucket.Get([]byte(entry.Key))
|
||||
if jsonentry == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var oldentry DbEntry
|
||||
if err := json.Unmarshal(jsonentry, &oldentry); err != nil {
|
||||
return fmt.Errorf("unable to unmarshal json: %s", err)
|
||||
}
|
||||
|
||||
if len(oldentry.Tags) > 0 && len(entry.Tags) == 0 {
|
||||
// initialize update entry with tags from old entry
|
||||
entry.Tags = oldentry.Tags
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = db.DB.Update(func(tx *bolt.Tx) error {
|
||||
// insert data
|
||||
bucket, err := tx.CreateBucketIfNotExists([]byte(BucketData))
|
||||
if err != nil {
|
||||
@@ -158,49 +188,150 @@ func (db *DB) Set(attr *DbAttr) error {
|
||||
return fmt.Errorf("insert data: %s", err)
|
||||
}
|
||||
|
||||
// insert tag, if any
|
||||
// FIXME: check removed tags
|
||||
if len(attr.Tags) > 0 {
|
||||
bucket, err := tx.CreateBucketIfNotExists([]byte(BucketTags))
|
||||
if err != nil {
|
||||
return fmt.Errorf("create bucket: %s", err)
|
||||
}
|
||||
|
||||
for _, tag := range entry.Tags {
|
||||
dbtag := &DbTag{}
|
||||
|
||||
jsontag := bucket.Get([]byte(tag))
|
||||
if jsontag == nil {
|
||||
// the tag is empty so far, initialize it
|
||||
dbtag.Keys = []string{entry.Key}
|
||||
} else {
|
||||
if err := json.Unmarshal(jsontag, dbtag); err != nil {
|
||||
return fmt.Errorf("unable to unmarshal json: %s", err)
|
||||
}
|
||||
|
||||
if !slices.Contains(dbtag.Keys, entry.Key) {
|
||||
// current key is not yet assigned to the tag, append it
|
||||
dbtag.Keys = append(dbtag.Keys, entry.Key)
|
||||
}
|
||||
}
|
||||
|
||||
jsontag, err = json.Marshal(dbtag)
|
||||
if err != nil {
|
||||
return fmt.Errorf("json marshalling failure: %s", err)
|
||||
}
|
||||
|
||||
err = bucket.Put([]byte(tag), []byte(jsontag))
|
||||
if err != nil {
|
||||
return fmt.Errorf("insert data: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) Get(attr *DbAttr) (*DbEntry, error) {
|
||||
if err := db.Open(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
if err := attr.ParseKV(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entry := DbEntry{}
|
||||
|
||||
err := db.DB.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketData))
|
||||
if bucket == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
jsonentry := bucket.Get([]byte(attr.Key))
|
||||
if jsonentry == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(jsonentry, &entry); err != nil {
|
||||
return fmt.Errorf("unable to unmarshal json: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &entry, nil
|
||||
}
|
||||
|
||||
func (db *DB) Del(attr *DbAttr) error {
|
||||
if err := db.Open(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err := db.DB.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BucketData))
|
||||
|
||||
if bucket == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return bucket.Delete([]byte(attr.Key))
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) Import(attr *DbAttr) error {
|
||||
// open json file into attr.Val
|
||||
if err := attr.GetFileValue(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if attr.Val == "" {
|
||||
return errors.New("empty json file")
|
||||
}
|
||||
|
||||
var entries DbEntries
|
||||
now := time.Now()
|
||||
newfile := db.Dbfile + now.Format("-02.01.2006T03:04.05")
|
||||
|
||||
if err := json.Unmarshal([]byte(attr.Val), &entries); err != nil {
|
||||
return cleanError(newfile, fmt.Errorf("unable to unmarshal json: %s", err))
|
||||
}
|
||||
|
||||
if fileExists(db.Dbfile) {
|
||||
// backup the old file
|
||||
err := os.Rename(db.Dbfile, newfile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// should now be a new db file
|
||||
if err := db.Open(); err != nil {
|
||||
return cleanError(newfile, err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err := db.DB.Update(func(tx *bolt.Tx) error {
|
||||
// insert data
|
||||
bucket, err := tx.CreateBucketIfNotExists([]byte(BucketData))
|
||||
if err != nil {
|
||||
return fmt.Errorf("create bucket: %s", err)
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
jsonentry, err := json.Marshal(entry)
|
||||
if err != nil {
|
||||
return fmt.Errorf("json marshalling failure: %s", err)
|
||||
}
|
||||
|
||||
err = bucket.Put([]byte(entry.Key), []byte(jsonentry))
|
||||
if err != nil {
|
||||
return fmt.Errorf("insert data: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return cleanError(newfile, err)
|
||||
}
|
||||
|
||||
fmt.Printf("backed up database file to %s\n", newfile)
|
||||
fmt.Printf("imported %d database entries\n", len(entries))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanError(file string, err error) error {
|
||||
// remove given [backup] file and forward the given error
|
||||
os.Remove(file)
|
||||
return err
|
||||
}
|
||||
|
||||
func fileExists(filename string) bool {
|
||||
info, err := os.Stat(filename)
|
||||
|
||||
if err != nil {
|
||||
// return false on any error
|
||||
return false
|
||||
}
|
||||
|
||||
return !info.IsDir()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user