Files
Gokapi/internal/configuration/database/Database.go
Marc Ole Bulling 246b3f5edc Upload status display is now handled natively with SSE instead of the Dropzone object. #26 #85
Protected URLs are generated to the documentation with go:generate automatically
2023-04-03 02:08:17 +02:00

400 lines
9.7 KiB
Go

package database
import (
"bytes"
"encoding/binary"
"encoding/gob"
"fmt"
"git.mills.io/prologic/bitcask"
"github.com/forceu/gokapi/internal/helper"
"github.com/forceu/gokapi/internal/models"
"log"
"strings"
"time"
)
const prefixApiKey = "apikey:id:"
const prefixFile = "file:id:"
const prefixHotlink = "hotlink:id:"
const prefixSessions = "session:id:"
const prefixUploadStatus = "fstatus:id:"
const idLastUploadConfig = "default:lastupload"
const idEnd2EndInfo = "e2e:info"
const maxKeySize = 96
var bitcaskDb *bitcask.Bitcask
// Init creates the database files and connects to it
func Init(dbPath string) {
if bitcaskDb == nil {
db, err := bitcask.Open(dbPath, bitcask.WithMaxKeySize(maxKeySize))
if err != nil {
log.Fatal(err)
}
bitcaskDb = db
}
}
// GetLengthAvailable returns the maximum length for a key name
func GetLengthAvailable() int {
maxLength := 0
for _, key := range []string{prefixApiKey, prefixFile, prefixHotlink, prefixSessions} {
length := len(key)
if length > maxLength {
maxLength = length
}
}
return maxKeySize - maxLength
}
// Close syncs the database to the filesystem and closes it
func Close() {
if bitcaskDb != nil {
err := bitcaskDb.Sync()
if err != nil {
fmt.Println(err)
}
err = bitcaskDb.Close()
if err != nil {
fmt.Println(err)
}
}
bitcaskDb = nil
}
// ## File Metadata ##
// GetAllMetadata returns a map of all available files
func GetAllMetadata() map[string]models.File {
if bitcaskDb == nil {
panic("Database not loaded!")
}
result := make(map[string]models.File)
keys := GetAllMetaDataIds()
for _, key := range keys {
file, ok := GetMetaDataById(key)
if ok {
result[file.Id] = file
}
}
return result
}
// GetAllMetaDataIds returns all Ids that contain metadata
func GetAllMetaDataIds() []string {
if bitcaskDb == nil {
panic("Database not loaded!")
}
var keys []string
err := bitcaskDb.Scan([]byte(prefixFile), func(key []byte) error {
fileId := strings.Replace(string(key), prefixFile, "", 1)
keys = append(keys, fileId)
return nil
})
helper.Check(err)
return keys
}
// GetMetaDataById returns a models.File from the ID passed or false if the id is not valid
func GetMetaDataById(id string) (models.File, bool) {
result := models.File{}
value, ok := getValue(prefixFile + id)
if !ok {
return result, false
}
buf := bytes.NewBuffer(value)
dec := gob.NewDecoder(buf)
err := dec.Decode(&result)
helper.Check(err)
return result, true
}
// SaveMetaData stores the metadata of a file to the disk
func SaveMetaData(file models.File) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
err := enc.Encode(file)
helper.Check(err)
err = bitcaskDb.Put([]byte(prefixFile+file.Id), buf.Bytes())
helper.Check(err)
err = bitcaskDb.Sync()
helper.Check(err)
}
// DeleteMetaData deletes information about a file
func DeleteMetaData(id string) {
deleteKey(prefixFile + id)
}
// GetUploadStatus returns a models.UploadStatus from the ID passed or false if the id is not valid
func GetUploadStatus(id string) (models.UploadStatus, bool) {
result := models.UploadStatus{}
value, ok := getValue(prefixUploadStatus + id)
if !ok {
return result, false
}
buf := bytes.NewBuffer(value)
dec := gob.NewDecoder(buf)
err := dec.Decode(&result)
helper.Check(err)
return result, true
}
// SaveUploadStatus stores the upload status of a new file for 24 hours
func SaveUploadStatus(status models.UploadStatus) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
err := enc.Encode(status)
helper.Check(err)
err = bitcaskDb.PutWithTTL([]byte(prefixUploadStatus+status.ChunkId), buf.Bytes(), time.Hour*24)
helper.Check(err)
err = bitcaskDb.Sync()
helper.Check(err)
}
// ## Hotlinks ##
// GetHotlink returns the id of the file associated or false if not found
func GetHotlink(id string) (string, bool) {
value, ok := getValue(prefixHotlink + id)
if !ok {
return "", false
}
return string(value), true
}
// SaveHotlink stores the hotlink associated with the file in the bitcaskDb
func SaveHotlink(file models.File) {
var err error
if file.UnlimitedTime {
err = bitcaskDb.Put([]byte(prefixHotlink+file.HotlinkId), []byte(file.Id))
} else {
err = bitcaskDb.PutWithTTL([]byte(prefixHotlink+file.HotlinkId), []byte(file.Id), expiryToDuration(file))
}
helper.Check(err)
err = bitcaskDb.Sync()
helper.Check(err)
}
// DeleteHotlink deletes a hotlink with the given ID
func DeleteHotlink(id string) {
deleteKey(prefixHotlink + id)
}
// ## API Keys ##
// GetAllApiKeys returns a map with all API keys
func GetAllApiKeys() map[string]models.ApiKey {
result := make(map[string]models.ApiKey)
var keys []string
err := bitcaskDb.Scan([]byte(prefixApiKey), func(key []byte) error {
apikeyID := strings.Replace(string(key), prefixApiKey, "", 1)
keys = append(keys, apikeyID)
return nil
})
helper.Check(err)
for _, key := range keys {
apiKey, ok := GetApiKey(key)
if ok {
result[apiKey.Id] = apiKey
}
}
return result
}
// GetApiKey returns a models.ApiKey if valid or false if the ID is not valid
func GetApiKey(id string) (models.ApiKey, bool) {
result := models.ApiKey{}
value, ok := getValue(prefixApiKey + id)
if !ok {
return result, false
}
buf := bytes.NewBuffer(value)
dec := gob.NewDecoder(buf)
err := dec.Decode(&result)
helper.Check(err)
return result, true
}
// SaveApiKey saves the API key to the database. If updateTimeOnly is true, the database might not be synced afterwards
func SaveApiKey(apikey models.ApiKey, updateTimeOnly bool) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
err := enc.Encode(apikey)
helper.Check(err)
err = bitcaskDb.Put([]byte(prefixApiKey+apikey.Id), buf.Bytes())
helper.Check(err)
if !updateTimeOnly {
err = bitcaskDb.Sync()
helper.Check(err)
}
}
// DeleteApiKey deletes an API key with the given ID
func DeleteApiKey(id string) {
deleteKey(prefixApiKey + id)
}
// ## Sessions ##
// GetSession returns the session with the given ID or false if not a valid ID
func GetSession(id string) (models.Session, bool) {
result := models.Session{}
value, ok := getValue(prefixSessions + id)
if !ok {
return result, false
}
buf := bytes.NewBuffer(value)
dec := gob.NewDecoder(buf)
err := dec.Decode(&result)
helper.Check(err)
return result, true
}
// DeleteSession deletes a session with the given ID
func DeleteSession(id string) {
deleteKey(prefixSessions + id)
}
// DeleteAllSessions logs all users out
func DeleteAllSessions() {
err := bitcaskDb.SiftScan([]byte(prefixSessions), func(key []byte) (bool, error) {
return true, nil
})
helper.Check(err)
}
// SaveSession stores the given session. After the expiry passed, it will be deleted automatically
func SaveSession(id string, session models.Session, expiry time.Duration) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
err := enc.Encode(session)
helper.Check(err)
err = bitcaskDb.PutWithTTL([]byte(prefixSessions+id), buf.Bytes(), expiry)
helper.Check(err)
err = bitcaskDb.Sync()
helper.Check(err)
}
// ## Upload Defaults ##
// GetUploadDefaults returns the last used setting for amount of downloads allowed, last expiry in days and
// a password for the file
func GetUploadDefaults() models.LastUploadValues {
defaultValues := models.LastUploadValues{
Downloads: 1,
TimeExpiry: 14,
Password: "",
UnlimitedDownload: false,
UnlimitedTime: false,
}
result := models.LastUploadValues{}
if bitcaskDb.Has([]byte(idLastUploadConfig)) {
value, err := bitcaskDb.Get([]byte(idLastUploadConfig))
helper.Check(err)
buf := bytes.NewBuffer(value)
dec := gob.NewDecoder(buf)
err = dec.Decode(&result)
helper.Check(err)
return result
}
return defaultValues
}
// SaveUploadDefaults saves the last used setting for an upload
func SaveUploadDefaults(values models.LastUploadValues) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
err := enc.Encode(values)
helper.Check(err)
err = bitcaskDb.Put([]byte(idLastUploadConfig), buf.Bytes())
helper.Check(err)
}
// ## End2End Encryption ##
// SaveEnd2EndInfo stores the encrypted e2e info
func SaveEnd2EndInfo(info models.E2EInfoEncrypted) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
err := enc.Encode(info)
helper.Check(err)
err = bitcaskDb.Put([]byte(idEnd2EndInfo), buf.Bytes())
helper.Check(err)
err = bitcaskDb.Sync()
helper.Check(err)
}
// GetEnd2EndInfo retrieves the encrypted e2e info
func GetEnd2EndInfo() models.E2EInfoEncrypted {
result := models.E2EInfoEncrypted{}
value, ok := getValue(idEnd2EndInfo)
if !ok {
return result
}
buf := bytes.NewBuffer(value)
dec := gob.NewDecoder(buf)
err := dec.Decode(&result)
helper.Check(err)
result.AvailableFiles = GetAllMetaDataIds()
return result
}
// DeleteEnd2EndInfo resets the encrypted e2e info
func DeleteEnd2EndInfo() {
deleteKey(idEnd2EndInfo)
}
// RunGarbageCollection runs the databases GC
func RunGarbageCollection() {
err := bitcaskDb.RunGC()
helper.Check(err)
}
func intToByte(integer int) []byte {
buf := make([]byte, binary.MaxVarintLen32)
n := binary.PutVarint(buf, int64(integer))
return buf[:n]
}
func byteToInt(intByte []byte) int {
integer, _ := binary.Varint(intByte)
return int(integer)
}
func deleteKey(id string) {
if !bitcaskDb.Has([]byte(id)) {
return
}
err := bitcaskDb.Delete([]byte(id))
helper.Check(err)
err = bitcaskDb.Sync()
helper.Check(err)
}
func getValue(id string) ([]byte, bool) {
value, err := bitcaskDb.Get([]byte(id))
if err == nil {
return value, true
}
if err == bitcask.ErrEmptyKey || err == bitcask.ErrKeyExpired || err == bitcask.ErrKeyNotFound {
return nil, false
}
panic(err)
}
// GetRawKey returns the raw value of a database key
func GetRawKey(id string) ([]byte, bool) {
return getValue(id)
}
func expiryToDuration(file models.File) time.Duration {
return time.Until(time.Unix(file.ExpireAt, 0))
}