|
|
|
@ -2,12 +2,13 @@ package storage
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"database/sql"
|
|
|
|
|
"encoding/binary"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"log"
|
|
|
|
|
"math/bits"
|
|
|
|
|
|
|
|
|
|
"git.sp4ke.com/sp4ke/bit4sat/bus"
|
|
|
|
|
"git.sp4ke.com/sp4ke/bit4sat/db"
|
|
|
|
|
"git.sp4ke.com/sp4ke/bit4sat/ln"
|
|
|
|
|
"github.com/jmoiron/sqlx"
|
|
|
|
@ -29,41 +30,18 @@ const (
|
|
|
|
|
file_type varchar(255) DEFAULT '',
|
|
|
|
|
file_size integer NOT NULL,
|
|
|
|
|
file_ext varchar(255) DEFAULT '',
|
|
|
|
|
fee integer NOT NULL DEFAULT 0,
|
|
|
|
|
status integer DEFAULT 0 REFERENCES upload_status(type),
|
|
|
|
|
ask_fee integer NOT NULL DEFAULT 0,
|
|
|
|
|
stored boolean DEFAULT '0',
|
|
|
|
|
UNIQUE (upload_id, sha256)
|
|
|
|
|
);
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
DBUploadView = `
|
|
|
|
|
CREATE OR REPLACE VIEW 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,
|
|
|
|
|
upload.fee
|
|
|
|
|
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, fee)
|
|
|
|
|
(upload_id, sha256, file_name, file_type, file_size, file_ext, stored, ask_fee)
|
|
|
|
|
VALUES
|
|
|
|
|
(:upload_id, :sha256, :file_name, :file_type, :file_size, :file_ext, :status, :fee)`
|
|
|
|
|
(:upload_id, :sha256, :file_name, :file_type, :file_size, :file_ext, :stored, :ask_fee)`
|
|
|
|
|
|
|
|
|
|
QSetStatus = `UPDATE upload SET status = :status WHERE upload_id = :upload_id `
|
|
|
|
|
QSetStored = `UPDATE upload SET stored = :stored WHERE upload_id = :upload_id `
|
|
|
|
|
|
|
|
|
|
QGetByHashID = `SELECT upload_id,
|
|
|
|
|
sha256,
|
|
|
|
@ -71,27 +49,150 @@ upload.status = upload_status.type
|
|
|
|
|
file_type,
|
|
|
|
|
file_size,
|
|
|
|
|
file_ext,
|
|
|
|
|
fee,
|
|
|
|
|
status
|
|
|
|
|
ask_fee,
|
|
|
|
|
stored
|
|
|
|
|
FROM upload WHERE sha256 = $1 AND upload_id = $2`
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type UpStatus uint32
|
|
|
|
|
|
|
|
|
|
func (st UpStatus) MarshalJSON() ([]byte, error) {
|
|
|
|
|
res := map[string]string{}
|
|
|
|
|
res["pay_status"] = st.PrintPayStatus()
|
|
|
|
|
res["store_status"] = st.PrintStoreStatus()
|
|
|
|
|
|
|
|
|
|
return json.Marshal(res)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (st *UpStatus) UnmarshalBinary(b []byte) error {
|
|
|
|
|
fmt.Printf("%#v\n", b)
|
|
|
|
|
|
|
|
|
|
// first 4 bits are reseved
|
|
|
|
|
// Single byte will be for 0 value
|
|
|
|
|
if len(b) == 1 {
|
|
|
|
|
*st = 0
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
view := binary.BigEndian.Uint32(b)
|
|
|
|
|
view = bits.Reverse32(view)
|
|
|
|
|
|
|
|
|
|
fmt.Printf("%32b\n", view)
|
|
|
|
|
log.Printf("%d\n", view)
|
|
|
|
|
|
|
|
|
|
// 1. Convert 3 last bytes to int
|
|
|
|
|
|
|
|
|
|
//res, err := strconv.ParseUint(string(b), 16, )
|
|
|
|
|
//if err != nil {
|
|
|
|
|
//return err
|
|
|
|
|
//}
|
|
|
|
|
//fmt.Println(res)
|
|
|
|
|
|
|
|
|
|
//buf := bytes.NewBuffer(b)
|
|
|
|
|
|
|
|
|
|
//firstByte := uint8(b[0])
|
|
|
|
|
|
|
|
|
|
//err := binary.Read(buf, binary.)
|
|
|
|
|
|
|
|
|
|
*st = UpStatus(view)
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get list of positions set
|
|
|
|
|
func (st UpStatus) GetFlagPositions() []int {
|
|
|
|
|
var i uint32
|
|
|
|
|
|
|
|
|
|
var setBits []int
|
|
|
|
|
|
|
|
|
|
for i = 31; i > 0; i-- {
|
|
|
|
|
if st&(1<<i) != 0 {
|
|
|
|
|
setBits = append(setBits, int(i))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return setBits
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (st UpStatus) PrintStoreStatus() string {
|
|
|
|
|
if st.Stored() {
|
|
|
|
|
return UploadStatus[UpStored]
|
|
|
|
|
} else if st.StoreFail() {
|
|
|
|
|
return UploadStatus[WaitStore]
|
|
|
|
|
} else {
|
|
|
|
|
return UploadStatus[WaitStore]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (st UpStatus) PrintPayStatus() string {
|
|
|
|
|
if st.Paid() {
|
|
|
|
|
return UploadStatus[UpPaid]
|
|
|
|
|
}
|
|
|
|
|
if st.Expired() {
|
|
|
|
|
return UploadStatus[UpPayExpired]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return UploadStatus[WaitPay]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (st UpStatus) WaitPay() bool {
|
|
|
|
|
return (!st.Paid()) && (!st.Expired())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (st UpStatus) Stored() bool {
|
|
|
|
|
return (st & UpStored) != 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (st UpStatus) StoreFail() bool {
|
|
|
|
|
return (st & UpStoreFail) != 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (st UpStatus) Paid() bool {
|
|
|
|
|
return (st & UpPaid) != 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (st UpStatus) Expired() bool {
|
|
|
|
|
return (st & UpPayExpired) != 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (st UpStatus) GetStoreStatus() UpStatus {
|
|
|
|
|
return st & StoreMask
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (st UpStatus) GetPayStatus() UpStatus {
|
|
|
|
|
return st & PayMask
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// First 4 bits are reserved for easier parsing from redis
|
|
|
|
|
const (
|
|
|
|
|
UpNew = iota
|
|
|
|
|
UpWaitingPayment
|
|
|
|
|
UpWaitingStorage
|
|
|
|
|
UpStored
|
|
|
|
|
UpReady
|
|
|
|
|
UpPayExpired UpStatus = 1 << (32 - 1 - iota)
|
|
|
|
|
UpPaid
|
|
|
|
|
|
|
|
|
|
UpStored // All files for this upload where stored
|
|
|
|
|
UpStoreFail
|
|
|
|
|
|
|
|
|
|
// Only used for printing
|
|
|
|
|
WaitStore
|
|
|
|
|
WaitPay
|
|
|
|
|
|
|
|
|
|
UpNew = UpStatus(0)
|
|
|
|
|
|
|
|
|
|
PayMask = UpPaid | UpPayExpired
|
|
|
|
|
|
|
|
|
|
StoreMask = UpStored
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// new --> waiting payment (optional) --> waiting storage (client upload)
|
|
|
|
|
// --> Sotred --> Ready (sharing url sent to client and acked)
|
|
|
|
|
var UploadStatus = map[int]string{
|
|
|
|
|
UpNew: "new upload",
|
|
|
|
|
UpWaitingPayment: "waiting payment",
|
|
|
|
|
UpWaitingStorage: "waiting storage",
|
|
|
|
|
UpStored: "stored",
|
|
|
|
|
UpReady: "ready",
|
|
|
|
|
var UploadStatus = map[UpStatus]string{
|
|
|
|
|
UpNew: "new upload",
|
|
|
|
|
|
|
|
|
|
// Payment
|
|
|
|
|
UpPayExpired: "expired",
|
|
|
|
|
UpPaid: "paid",
|
|
|
|
|
WaitPay: "waiting",
|
|
|
|
|
|
|
|
|
|
// Storage
|
|
|
|
|
WaitStore: "waiting storage",
|
|
|
|
|
UpStored: "stored",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
@ -101,13 +202,14 @@ var (
|
|
|
|
|
|
|
|
|
|
type Upload struct {
|
|
|
|
|
ID string `db:"upload_id"`
|
|
|
|
|
Free bool `db:"-"` // is this a free upload
|
|
|
|
|
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"`
|
|
|
|
|
Fee int `db:"fee"`
|
|
|
|
|
Stored bool `db:"stored"`
|
|
|
|
|
AskFee int `db:"ask_fee"` // fee asked for download
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: sync from redis to db
|
|
|
|
@ -115,9 +217,53 @@ type Upload struct {
|
|
|
|
|
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
func SetUploadStatus(id string, status int) error {
|
|
|
|
|
func GetUploadInvoice(uploadId string) (*ln.Invoice, error) {
|
|
|
|
|
invoice := ln.Invoice{}
|
|
|
|
|
|
|
|
|
|
uploadInvoiceKey := fmt.Sprintf("upload_%s_invoice", uploadId)
|
|
|
|
|
|
|
|
|
|
err := DB.Redis.Do(radix.FlatCmd(&invoice, "GET", uploadInvoiceKey))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &invoice, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func SetUploadStatus(id string, status UpStatus) error {
|
|
|
|
|
log.Printf("setting upload status for %s", id)
|
|
|
|
|
|
|
|
|
|
key := fmt.Sprintf("upload_status_%s", id)
|
|
|
|
|
return DB.Redis.Do(radix.FlatCmd(nil, "SET", key, status))
|
|
|
|
|
|
|
|
|
|
if status == UpNew {
|
|
|
|
|
return DB.Redis.Do(radix.FlatCmd(nil, "SETBIT", key, 31, 0))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log.Println("setting upload status for bit positions ", status.GetFlagPositions())
|
|
|
|
|
// get bit positions
|
|
|
|
|
|
|
|
|
|
for _, offset := range status.GetFlagPositions() {
|
|
|
|
|
log.Printf("setting bit at position %d", offset)
|
|
|
|
|
err := DB.Redis.Do(radix.FlatCmd(nil,
|
|
|
|
|
"SETBIT", key, offset, 1))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log.Println("done set bit")
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func GetUploadStatus(id string) (status UpStatus, err error) {
|
|
|
|
|
log.Println("Getting upload status")
|
|
|
|
|
|
|
|
|
|
key := fmt.Sprintf("upload_status_%s", id)
|
|
|
|
|
|
|
|
|
|
err = DB.Redis.Do(radix.FlatCmd(&status, "GET", key))
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func SetUploadInvoice(uploadId string, invoice *ln.Invoice) error {
|
|
|
|
@ -138,28 +284,11 @@ func SetUploadInvoice(uploadId string, invoice *ln.Invoice) error {
|
|
|
|
|
return DB.Redis.Do(radix.FlatCmd(nil, "SET", invoiceUploadKey, uploadId))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func WatchUploadPayment(invoice *ln.Invoice) error {
|
|
|
|
|
|
|
|
|
|
log.Println("watching invoice ", invoice)
|
|
|
|
|
|
|
|
|
|
invoiceJson, err := json.Marshal(invoice)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return DB.Redis.Do(radix.FlatCmd(nil, "PUBLISH", bus.WatchInvoicesChannelName, invoiceJson))
|
|
|
|
|
|
|
|
|
|
//TODO: Notify client on websocket
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -180,8 +309,9 @@ func GetByHashID(sha256 string, id string) (*Upload, error) {
|
|
|
|
|
return &up, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (u *Upload) TxSetState(tx *sqlx.Tx, status int) error {
|
|
|
|
|
_, err := tx.NamedExec(QSetStatus, u)
|
|
|
|
|
func (u *Upload) TxSetFileStored(tx *sqlx.Tx) error {
|
|
|
|
|
u.Stored = true
|
|
|
|
|
_, err := tx.NamedExec(QSetStored, u)
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
@ -226,30 +356,8 @@ func (u *Upload) Write() error {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
|
_, err := DB.Sql.Exec(DBUploadStatusSchema)
|
|
|
|
|
_, err := DB.Sql.Exec(DBUploadSchema)
|
|
|
|
|
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($1,$2) ON CONFLICT (type) DO UPDATE
|
|
|
|
|
SET status = $3`
|
|
|
|
|
for k, v := range UploadStatus {
|
|
|
|
|
_, err := DB.Sql.Exec(query, k, v, v)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Panic(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|