210 lines
4.0 KiB
Go
210 lines
4.0 KiB
Go
package storage
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
|
|
"git.sp4ke.com/sp4ke/bit4sat/db"
|
|
"github.com/jmoiron/sqlx"
|
|
"github.com/mattn/go-sqlite3"
|
|
"github.com/mediocregopher/radix/v3"
|
|
"github.com/segmentio/ksuid"
|
|
)
|
|
|
|
var DB = db.DB
|
|
|
|
const (
|
|
DBUploadSchema = `
|
|
CREATE TABLE IF NOT EXISTS upload (
|
|
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
upload_id CHAR(27) NOT NULL,
|
|
sha256 CHAR(64) NOT NULL,
|
|
file_name CHAR(255) NOT NULL,
|
|
file_type CHAR(255) DEFAULT '',
|
|
file_size INT NOT NULL,
|
|
file_ext CHAR(255) DEFAULT '',
|
|
status INT DEFAULT 0,
|
|
FOREIGN KEY (status) REFERENCES upload_status(type),
|
|
UNIQUE (upload_id, sha256)
|
|
);
|
|
`
|
|
|
|
DBUploadView = `
|
|
CREATE VIEW IF NOT EXISTS upload_with_status
|
|
AS SELECT
|
|
upload.upload_id,
|
|
upload.sha256,
|
|
upload_status.status,
|
|
upload.file_name,
|
|
upload.file_type,
|
|
upload.file_size,
|
|
upload.file_ext
|
|
FROM upload
|
|
JOIN upload_status ON
|
|
upload.status = upload_status.type
|
|
`
|
|
|
|
DBUploadStatusSchema = `CREATE TABLE IF NOT EXISTS upload_status
|
|
(
|
|
type INT NOT NULL PRIMARY KEY,
|
|
status CHAR(255) NOT NULL UNIQUE
|
|
)
|
|
`
|
|
|
|
QNewUpload = `INSERT INTO upload
|
|
(upload_id, sha256, file_name, file_type, file_size, file_ext, status)
|
|
VALUES
|
|
(:upload_id, :sha256, :file_name, :file_type, :file_size, :file_ext, :status)`
|
|
|
|
QSetStatus = `UPDATE upload SET status = :status WHERE upload_id = :upload_id `
|
|
|
|
QGetByHashID = `SELECT upload_id,
|
|
sha256,
|
|
file_name,
|
|
file_type,
|
|
file_size,
|
|
file_ext,
|
|
status
|
|
FROM upload WHERE sha256 = ? AND upload_id = ?`
|
|
)
|
|
|
|
const (
|
|
UpNew = iota
|
|
UpStored
|
|
UpWaitingPayment
|
|
UpReady
|
|
)
|
|
|
|
var UploadStatus = map[int]string{
|
|
UpNew: "new upload",
|
|
UpStored: "stored",
|
|
UpWaitingPayment: "waiting payment",
|
|
UpReady: "ready",
|
|
}
|
|
|
|
var (
|
|
ErrDoesNotExist = errors.New("does not exist")
|
|
ErrAlreadyExists = errors.New("already exists")
|
|
)
|
|
|
|
type Upload struct {
|
|
ID ksuid.KSUID `db:"upload_id"`
|
|
SHA256 string `db:"sha256"`
|
|
FileName string `db:"file_name"`
|
|
FileType string `db:"file_type"`
|
|
FileSize int64 `db:"file_size"`
|
|
FileExt string `db:"file_ext"`
|
|
Status int `db:"status"`
|
|
}
|
|
|
|
func CacheSetUploadStatus(id string, status int) error {
|
|
key := fmt.Sprintf("upload_status_%s", id)
|
|
|
|
return DB.Redis.Do(radix.FlatCmd(nil, "SET", key, status))
|
|
}
|
|
|
|
// Returns true if id exists in DB
|
|
func IdExists(id string) (exists bool, err error) {
|
|
key := fmt.Sprintf("upload_status_%s", id)
|
|
|
|
err = DB.Redis.Do(radix.Cmd(&exists, "EXISTS", key))
|
|
|
|
return
|
|
}
|
|
|
|
// Get a file by upload id and hash
|
|
func GetByHashID(sha256 string, id string) (*Upload, error) {
|
|
var up Upload
|
|
|
|
err := DB.Sql.Get(&up, QGetByHashID, sha256, id)
|
|
|
|
if err == sql.ErrNoRows {
|
|
return nil, ErrDoesNotExist
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &up, nil
|
|
}
|
|
|
|
func (u *Upload) TxSetState(tx *sqlx.Tx, status int) error {
|
|
_, err := tx.NamedExec(QSetStatus, u)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (u *Upload) TxWrite(tx *sqlx.Tx) error {
|
|
|
|
_, err := tx.NamedExec(QNewUpload, u)
|
|
|
|
sqlErr, isSqlErr := err.(sqlite3.Error)
|
|
|
|
if isSqlErr && sqlErr.Code == sqlite3.ErrConstraint {
|
|
return ErrAlreadyExists
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (u *Upload) Write() error {
|
|
_, err := DB.Sql.NamedExec(QNewUpload, u)
|
|
sqlErr, isSqlErr := err.(sqlite3.Error)
|
|
if isSqlErr && sqlErr.Code == sqlite3.ErrConstraint {
|
|
return ErrAlreadyExists
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func init() {
|
|
_, err := DB.Sql.Exec(DBUploadStatusSchema)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
_, err = DB.Sql.Exec(DBUploadSchema)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
_, err = DB.Sql.Exec(DBUploadView)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// Populate status types
|
|
query := `INSERT INTO upload_status
|
|
(type, status) VALUES(?,?)
|
|
ON DUPLICATE KEY UPDATE status = ?`
|
|
for k, v := range UploadStatus {
|
|
_, err := DB.Sql.Exec(query, k, v, v)
|
|
if err != nil {
|
|
sqlErr, ok := err.(sqlite3.Error)
|
|
if ok && sqlErr.ExtendedCode == sqlite3.ErrConstraintUnique {
|
|
log.Panic(err)
|
|
}
|
|
|
|
if !ok {
|
|
log.Panic(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|