Refactor database login into a separate module
This commit is contained in:
169
fileupload.go
169
fileupload.go
@@ -1,33 +1,22 @@
|
||||
package rushlink
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
// Use the Castagnoli checksum because of the acceleration on Intel CPUs
|
||||
var checksumTable = crc32.MakeTable(crc32.Castagnoli)
|
||||
|
||||
// Where to store the uploaded files
|
||||
var fileStoreDir = ""
|
||||
|
||||
// Custom HTTP filesystem handler
|
||||
type fileUploadFileSystem struct {
|
||||
// FileUploadFileSystem is a HTTP filesystem handler
|
||||
type FileUploadFileSystem struct {
|
||||
fs http.FileSystem
|
||||
}
|
||||
|
||||
// Open opens file
|
||||
func (fs fileUploadFileSystem) Open(path string) (http.File, error) {
|
||||
// Open opens a file
|
||||
func (fs FileUploadFileSystem) Open(path string) (http.File, error) {
|
||||
log.Println(path)
|
||||
file, err := fs.fs.Open(path)
|
||||
if err != nil {
|
||||
@@ -42,153 +31,3 @@ func (fs fileUploadFileSystem) Open(path string) (http.File, error) {
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
type fileUploadState int
|
||||
|
||||
type fileUpload struct {
|
||||
State fileUploadState
|
||||
ID uuid.UUID
|
||||
FileName string
|
||||
ContentType string
|
||||
Checksum uint32
|
||||
}
|
||||
|
||||
const (
|
||||
dirMode os.FileMode = 0750
|
||||
fileMode os.FileMode = 0640
|
||||
)
|
||||
|
||||
const (
|
||||
fileUploadStateUndef fileUploadState = 0
|
||||
fileUploadStatePresent = 1
|
||||
fileUploadStateDeleted = 2
|
||||
)
|
||||
|
||||
func (t fileUploadState) String() string {
|
||||
switch t {
|
||||
case fileUploadStateUndef:
|
||||
return "unknown"
|
||||
case fileUploadStatePresent:
|
||||
return "present"
|
||||
case fileUploadStateDeleted:
|
||||
return "deleted"
|
||||
default:
|
||||
return "invalid"
|
||||
}
|
||||
}
|
||||
|
||||
func OpenFileStore(path string) error {
|
||||
if path == "" {
|
||||
return errors.New("file-store not set")
|
||||
}
|
||||
|
||||
// Try to create the file store directory if it does not yet exist
|
||||
if err := os.MkdirAll(path, dirMode); err != nil {
|
||||
return errors.Wrap(err, "creating file store directory")
|
||||
}
|
||||
|
||||
fileStoreDir = path[:]
|
||||
return nil
|
||||
}
|
||||
|
||||
func newFileUpload(tx *bolt.Tx, r io.Reader, fileName string, contentType string) (*fileUpload, error) {
|
||||
id, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "generating UUID")
|
||||
}
|
||||
|
||||
filePath := fileStorePath(id, fileName)
|
||||
if err := os.Mkdir(path.Dir(filePath), dirMode); err != nil {
|
||||
return nil, errors.Wrap(err, "creating file dir")
|
||||
}
|
||||
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, fileMode)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "opening file")
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
hash := crc32.New(checksumTable)
|
||||
tee := io.TeeReader(r, hash)
|
||||
_, err = io.Copy(file, tee)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "writing to file")
|
||||
}
|
||||
|
||||
fu := &fileUpload{
|
||||
State: fileUploadStatePresent,
|
||||
ID: id,
|
||||
FileName: fileName,
|
||||
ContentType: contentType,
|
||||
Checksum: hash.Sum32(),
|
||||
}
|
||||
if err := fu.save(tx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fu, nil
|
||||
}
|
||||
|
||||
func getFileUpload(tx *bolt.Tx, id uuid.UUID) (*fileUpload, error) {
|
||||
bucket := tx.Bucket([]byte(BUCKET_FILE_UPLOAD))
|
||||
if bucket == nil {
|
||||
return nil, errors.Errorf("bucket %v does not exist", BUCKET_FILE_UPLOAD)
|
||||
}
|
||||
storedBytes := bucket.Get(id[:])
|
||||
if storedBytes == nil {
|
||||
return nil, nil
|
||||
}
|
||||
fu := &fileUpload{}
|
||||
err := Unmarshal(storedBytes, fu)
|
||||
return fu, err
|
||||
}
|
||||
|
||||
func (fu *fileUpload) save(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(BUCKET_FILE_UPLOAD))
|
||||
if bucket == nil {
|
||||
return errors.Errorf("bucket %v does not exist", BUCKET_FILE_UPLOAD)
|
||||
}
|
||||
|
||||
buf, err := Marshal(fu)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "encoding for database failed")
|
||||
}
|
||||
if err := bucket.Put(fu.ID[:], buf); err != nil {
|
||||
return errors.Wrap(err, "database transaction failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fu *fileUpload) delete(tx *bolt.Tx) error {
|
||||
// Remove the file in the backend
|
||||
filePath := fileStorePath(fu.ID, fu.FileName)
|
||||
if err := os.Remove(filePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update the file in the server
|
||||
if err := (&fileUpload{
|
||||
ID: fu.ID,
|
||||
State: fileUploadStateDeleted,
|
||||
}).save(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Cleanup the parent directory
|
||||
wrap := "deletion succeeded, but removing the file directory has failed"
|
||||
return errors.Wrap(os.Remove(path.Dir(filePath)), wrap)
|
||||
}
|
||||
|
||||
func (fu *fileUpload) url() *url.URL {
|
||||
rawurl := "/uploads/" + hex.EncodeToString(fu.ID[:]) + "/" + fu.FileName
|
||||
urlParse, err := url.Parse(rawurl)
|
||||
if err != nil {
|
||||
panic("could not construct /uploads/ url")
|
||||
}
|
||||
return urlParse
|
||||
}
|
||||
|
||||
func fileStorePath(id uuid.UUID, fileName string) string {
|
||||
if fileStoreDir == "" {
|
||||
panic("fileStoreDir called while the file store path has not been set")
|
||||
}
|
||||
return path.Join(fileStoreDir, hex.EncodeToString(id[:]), fileName)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user