using own Db wrapper class around bolt db

This commit is contained in:
2023-03-01 13:31:44 +01:00
parent 8e5f33c99b
commit 84bef91657
5 changed files with 106 additions and 28 deletions

View File

@@ -37,15 +37,6 @@ type Meta struct {
Expire string `json:"expire" form:"expire"` Expire string `json:"expire" form:"expire"`
} }
// stores 1 upload object, gets into db
type Upload struct {
Id string `json:"id"`
Expire string `json:"expire"`
File string `json:"file"` // final filename (visible to the downloader)
Members []string `json:"members"` // contains multiple files, so File is an archive
Uploaded time.Time `json:"uploaded"`
}
// vaious helbers // vaious helbers
func Log(format string, values ...any) { func Log(format string, values ...any) {
fmt.Printf("[DEBUG] "+format+"\n", values...) fmt.Printf("[DEBUG] "+format+"\n", values...)

View File

@@ -20,14 +20,37 @@ package api
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
bolt "go.etcd.io/bbolt" bolt "go.etcd.io/bbolt"
) )
const Bucket string = "uploads" const Bucket string = "uploads"
func DbInsert(db *bolt.DB, id string, entry *Upload) { // wrapper for bolt db
err := db.Update(func(tx *bolt.Tx) error { type Db struct {
bolt *bolt.DB
}
// stores 1 upload object, gets into db
type Upload struct {
Id string `json:"id"`
Expire string `json:"expire"`
File string `json:"file"` // final filename (visible to the downloader)
Members []string `json:"members"` // contains multiple files, so File is an archive
Uploaded Timestamp `json:"uploaded"`
}
func NewDb(file string) (*Db, error) {
b, err := bolt.Open(file, 0600, nil)
db := Db{bolt: b}
return &db, err
}
func (db *Db) Close() {
db.bolt.Close()
}
func (db *Db) Insert(id string, entry *Upload) error {
err := db.bolt.Update(func(tx *bolt.Tx) error {
bucket, err := tx.CreateBucketIfNotExists([]byte(Bucket)) bucket, err := tx.CreateBucketIfNotExists([]byte(Bucket))
if err != nil { if err != nil {
return fmt.Errorf("create bucket: %s", err) return fmt.Errorf("create bucket: %s", err)
@@ -51,12 +74,14 @@ func DbInsert(db *bolt.DB, id string, entry *Upload) {
if err != nil { if err != nil {
Log("DB error: %s", err.Error()) Log("DB error: %s", err.Error())
} }
return err
} }
func DbLookupId(db *bolt.DB, id string) (Upload, error) { func (db *Db) Lookup(id string) (Upload, error) {
var upload Upload var upload Upload
err := db.View(func(tx *bolt.Tx) error { err := db.bolt.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(Bucket)) bucket := tx.Bucket([]byte(Bucket))
j := bucket.Get([]byte(id)) j := bucket.Get([]byte(id))
@@ -79,8 +104,8 @@ func DbLookupId(db *bolt.DB, id string) (Upload, error) {
return upload, nil return upload, nil
} }
func DbDeleteId(db *bolt.DB, id string) error { func (db *Db) Delete(id string) error {
err := db.Update(func(tx *bolt.Tx) error { err := db.bolt.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(Bucket)) bucket := tx.Bucket([]byte(Bucket))
j := bucket.Get([]byte(id)) j := bucket.Get([]byte(id))

View File

@@ -21,7 +21,6 @@ import (
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/tlinden/up/upd/cfg" "github.com/tlinden/up/upd/cfg"
bolt "go.etcd.io/bbolt"
"os" "os"
"path/filepath" "path/filepath"
@@ -29,7 +28,7 @@ import (
"time" "time"
) )
func FilePut(c *fiber.Ctx, cfg *cfg.Config, db *bolt.DB) (string, error) { func FilePut(c *fiber.Ctx, cfg *cfg.Config, db *Db) (string, error) {
// supports upload of multiple files with: // supports upload of multiple files with:
// //
// curl -X POST localhost:8080/putfile \ // curl -X POST localhost:8080/putfile \
@@ -57,7 +56,8 @@ func FilePut(c *fiber.Ctx, cfg *cfg.Config, db *bolt.DB) (string, error) {
return "", err return "", err
} }
entry := &Upload{Id: id, Uploaded: time.Now()} //entry := &Upload{Id: id, Uploaded: time.Now()}
entry := &Upload{Id: id, Uploaded: Timestamp{Time: time.Now()}}
// init upload obj // init upload obj
@@ -125,18 +125,18 @@ func FilePut(c *fiber.Ctx, cfg *cfg.Config, db *bolt.DB) (string, error) {
Log("Expire set to: %s", entry.Expire) Log("Expire set to: %s", entry.Expire)
// we do this in the background to not thwart the server // we do this in the background to not thwart the server
go DbInsert(db, id, entry) go db.Insert(id, entry)
return returnUrl, nil return returnUrl, nil
} }
func FileGet(c *fiber.Ctx, cfg *cfg.Config, db *bolt.DB) error { func FileGet(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
// deliver a file and delete it after a (configurable?) delay // deliver a file and delete it after a (configurable?) delay
id := c.Params("id") id := c.Params("id")
file := c.Params("file") file := c.Params("file")
upload, err := DbLookupId(db, id) upload, err := db.Lookup(id)
if err != nil { if err != nil {
// non existent db entry with that id, or other db error, see logs // non existent db entry with that id, or other db error, see logs
return fiber.NewError(404, "No download with that id could be found!") return fiber.NewError(404, "No download with that id could be found!")
@@ -151,7 +151,7 @@ func FileGet(c *fiber.Ctx, cfg *cfg.Config, db *bolt.DB) error {
if _, err := os.Stat(filename); err != nil { if _, err := os.Stat(filename); err != nil {
// db entry is there, but file isn't (anymore?) // db entry is there, but file isn't (anymore?)
go DbDeleteId(db, id) go db.Delete(id)
} }
// finally put the file to the client // finally put the file to the client
@@ -161,7 +161,7 @@ func FileGet(c *fiber.Ctx, cfg *cfg.Config, db *bolt.DB) error {
// check if we need to delete the file now // check if we need to delete the file now
if upload.Expire == "asap" { if upload.Expire == "asap" {
cleanup(filepath.Join(cfg.StorageDir, id)) cleanup(filepath.Join(cfg.StorageDir, id))
go DbDeleteId(db, id) go db.Delete(id)
} }
}() }()
@@ -172,7 +172,7 @@ type Id struct {
Id string `json:"name" xml:"name" form:"name"` Id string `json:"name" xml:"name" form:"name"`
} }
func FileDelete(c *fiber.Ctx, cfg *cfg.Config, db *bolt.DB) error { func FileDelete(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
// delete file, id dir and db entry // delete file, id dir and db entry
id := c.Params("id") id := c.Params("id")
@@ -193,7 +193,7 @@ func FileDelete(c *fiber.Ctx, cfg *cfg.Config, db *bolt.DB) error {
cleanup(filepath.Join(cfg.StorageDir, id)) cleanup(filepath.Join(cfg.StorageDir, id))
err := DbDeleteId(db, id) err := db.Delete(id)
if err != nil { if err != nil {
// non existent db entry with that id, or other db error, see logs // non existent db entry with that id, or other db error, see logs
return fiber.NewError(404, "No upload with that id could be found!") return fiber.NewError(404, "No upload with that id could be found!")

View File

@@ -22,7 +22,6 @@ import (
"github.com/gofiber/fiber/v2/middleware/logger" "github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/fiber/v2/middleware/requestid" "github.com/gofiber/fiber/v2/middleware/requestid"
"github.com/tlinden/up/upd/cfg" "github.com/tlinden/up/upd/cfg"
bolt "go.etcd.io/bbolt"
) )
func Runserver(cfg *cfg.Config, args []string) error { func Runserver(cfg *cfg.Config, args []string) error {
@@ -42,7 +41,7 @@ func Runserver(cfg *cfg.Config, args []string) error {
Format: "${pid} ${locals:requestid} ${status} - ${method} ${path}\n", Format: "${pid} ${locals:requestid} ${status} - ${method} ${path}\n",
})) }))
db, err := bolt.Open(cfg.DbFile, 0600, nil) db, err := NewDb(cfg.DbFile)
if err != nil { if err != nil {
return err return err
} }

63
upd/api/timestamp.go Normal file
View File

@@ -0,0 +1,63 @@
/*
Copyright © 2023 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package api
import (
"strconv"
"time"
)
// https://gist.github.com/rhcarvalho/9338c3ff8850897c68bc74797c5dc25b
// Timestamp is like time.Time, but knows how to unmarshal from JSON
// Unix timestamp numbers or RFC3339 strings, and marshal back into
// the same JSON representation.
type Timestamp struct {
time.Time
rfc3339 bool
}
func (t Timestamp) MarshalJSON() ([]byte, error) {
if t.rfc3339 {
return t.Time.MarshalJSON()
}
return t.formatUnix()
}
func (t *Timestamp) UnmarshalJSON(data []byte) error {
err := t.Time.UnmarshalJSON(data)
if err != nil {
return t.parseUnix(data)
}
t.rfc3339 = true
return nil
}
func (t Timestamp) formatUnix() ([]byte, error) {
sec := float64(t.Time.UnixNano()) * float64(time.Nanosecond) / float64(time.Second)
return strconv.AppendFloat(nil, sec, 'f', -1, 64), nil
}
func (t *Timestamp) parseUnix(data []byte) error {
f, err := strconv.ParseFloat(string(data), 64)
if err != nil {
return err
}
t.Time = time.Unix(0, int64(f*float64(time.Second/time.Nanosecond)))
return nil
}