Remove sqlite database

We don't need the sqlite database anymore
This commit is contained in:
Felix Niederwanger 2020-12-04 12:31:32 +01:00 committed by felix@feldspaten.org
parent 9d39765ab2
commit b24bfa39e7
11 changed files with 489 additions and 208 deletions

2
.gitignore vendored
View file

@ -26,3 +26,5 @@ __debug_bin
*.db
bins
*.toml
pasta_test
/pastas

View file

@ -9,7 +9,10 @@ go:
dist:
- bionic
script:
before_script:
- sudo apt-get -y install jq
- make requirements
script:
- make
- make test

View file

@ -10,5 +10,5 @@ RUN mkdir /app
RUN mkdir /data
WORKDIR /data
COPY --from=build-env /app/pastad /app/pastad
CMD /app/pastad
ENTRYPOINT /app/pastad
VOLUME ["/data"]

View file

@ -2,13 +2,17 @@ default: all
all: pasta pastad
requirements:
go get github.com/mattn/go-sqlite3
go get github.com/BurntSushi/toml
pasta: cmd/pasta/*.go
pasta: cmd/pasta/pasta.go
go build $^
pastad: cmd/pastad/*.go
pastad: cmd/pastad/pastad.go cmd/pastad/storage.go
go build $^
test:
test: pastad
go test ./...
# TODO: This syntax is horrible :-)
bash -c 'cd test && ./test.sh'
docker: Dockerfile pasta pastad
docker build . -t feldspaten.org/pasta

View file

@ -16,6 +16,10 @@ Then create a `pastad.toml` file using the provided example (`pastad.toml.exampl
### Docker
make docker
Or manually:
docker build . -t feldspaten.org/pasta # Build docker container
Create or run the container with
@ -23,5 +27,5 @@ Create or run the container with
docker container create --name pasta -p 8199:8199 -v ABSOLUTE_PATH_TO_DATA_DIR:/data feldspaten.org/pasta
docker container run --name pasta -p 8199:8199 -v ABSOLUTE_PATH_TO_DATA_DIR:/data feldspaten.org/pasta
The container needs a `data` directory with a valid `pastad.toml` (See the [example file](pastad.toml.example))
The container needs a `data` directory with a valid `pastad.toml` (See the [example file](pastad.toml.example), otherwise default values will be used).

View file

@ -9,6 +9,7 @@ import (
"io/ioutil"
"net/http"
"os"
"flag"
"github.com/BurntSushi/toml"
)
@ -19,12 +20,27 @@ type Config struct {
var cf Config
func FileExists(filename string) bool {
_, err := os.Stat(filename)
if err != nil {
return false
}
return !os.IsNotExist(err)
}
func main() {
cf.RemoteHost = "http://localhost:8199"
// Load configuration file if possible (swallow errors)
homeDir, _ := os.UserHomeDir()
configFile := homeDir + "/.pasta.toml"
toml.DecodeFile(configFile, &cf)
if FileExists(configFile) {
if _, err := toml.DecodeFile(configFile, &cf); err != nil {
fmt.Fprintf(os.Stderr, "config-toml file parse error: %s %s\n", configFile, err)
}
}
// Parse program arguments
flag.StringVar(&cf.RemoteHost, "r", cf.RemoteHost, "Specify remote host")
flag.Parse()
reader := bufio.NewReader(os.Stdin)
// Push to server

View file

@ -5,7 +5,6 @@
package main
import (
"database/sql"
"errors"
"fmt"
"io"
@ -18,21 +17,20 @@ import (
"time"
"github.com/BurntSushi/toml"
_ "github.com/mattn/go-sqlite3"
)
type Config struct {
BaseUrl string `toml:"BaseURL"` // Instance base URL
DbFile string `toml:"Database"` // SQLite3 database
BinDir string `toml:"BinsDir"` // dir where bins are stored
PastaDir string `toml:"PastaDir"` // dir where pasta are stored
BindAddr string `toml:"BindAddress"`
MaxBinSize int64 `toml:"MaxBinSize"` // Max bin size in bytes
BinCharacters int `toml:"BinCharacters"`
}
var cf Config
var bowl PastaBowl
func ExtractBinName(path string) string {
func ExtractPastaId(path string) string {
i := strings.LastIndex(path, "/")
if i < 0 {
return path
@ -41,96 +39,96 @@ func ExtractBinName(path string) string {
}
}
func SendBin(id string, w http.ResponseWriter) error {
filename := fmt.Sprintf("%s/%s", cf.BinDir, id)
file, err := os.OpenFile(filename, os.O_RDONLY, 0400)
func SendPasta(id string, w http.ResponseWriter) error {
pasta, err := bowl.GetPasta(id)
if err != nil {
return err
}
file, err := bowl.GetPastaReader(id)
if err != nil {
return err
}
defer file.Close()
stat, _ := file.Stat()
w.Header().Set("Content-Length", strconv.FormatInt(stat.Size(), 10))
w.Header().Set("Content-Length", strconv.FormatInt(pasta.Size, 10))
w.Header().Set("Content-Type", "text/plain")
_, err = io.Copy(w, file)
return err
}
func ReceiveBin(r *http.Request) (Bin, error) {
func ReceivePasta(r *http.Request) (Pasta, error) {
var err error
reader := r.Body
buf := make([]byte, 4096)
bin := Bin{Id: ""}
var size int64
pasta := Pasta{Id: ""}
defer reader.Close()
// TODO: Use suggested ID from http header if present
bin.Id, err = GenerateRandomBinId(cf.BinCharacters)
if err != nil {
log.Fatalf("Server error while generating random bin: %s", err)
bin.Id = ""
return bin, err
pasta.Id = bowl.GenerateRandomBinId(cf.BinCharacters)
if err = bowl.InsertPasta(&pasta); err != nil {
return pasta, err
}
filename := fmt.Sprintf("%s/%s", cf.BinDir, bin.Id)
file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0640)
// Append contents to file
file, err := os.OpenFile(pasta.Filename, os.O_RDWR|os.O_APPEND, 0640)
if err != nil {
return bin, err
file.Close()
return pasta, err
}
defer file.Close()
for size < cf.MaxBinSize {
pasta.Size = 0
for pasta.Size < cf.MaxBinSize {
n, err := reader.Read(buf)
if (err == nil || err == io.EOF) && n > 0 {
if _, err = file.Write(buf[:n]); err != nil {
log.Fatalf("Write error while receiving bin: %s", err)
return bin, err
return pasta, err
}
size += int64(n)
pasta.Size += int64(n)
}
if err != nil {
if err == io.EOF {
break
}
log.Fatalf("Receive error while receiving bin: %s", err)
return bin, err
return pasta, err
}
}
if size >= cf.MaxBinSize {
if pasta.Size >= cf.MaxBinSize {
log.Println("Max size exceeded while receiving bin")
return bin, errors.New("Bin size exceeded")
return pasta, errors.New("Bin size exceeded")
}
if size == 0 {
return bin, nil
if pasta.Size == 0 {
// This is invalid
file.Close()
bowl.DeletePasta(pasta.Id)
pasta.Id = ""
pasta.Filename = ""
pasta.Token = ""
pasta.ExpireDate = 0
return pasta, nil
}
file.Sync()
file.Close()
bin.CreationDate = time.Now().Unix()
bin.Size = size
err = InsertBin(bin)
if err != nil {
log.Fatalf("Database while receiving bin: %s", err)
os.Remove(filename)
bin.Id = ""
return bin, err
}
return bin, nil
return pasta, file.Sync()
}
func handlerPost(w http.ResponseWriter, r *http.Request) {
bin, err := ReceiveBin(r)
pasta, err := ReceivePasta(r)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "Server error")
log.Printf("Receive error: %s", err)
return
} else {
if bin.Id == "" {
if pasta.Id == "" {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Empty content"))
w.Write([]byte("Empty pasta"))
} else {
log.Printf("Received bin %s (%d bytes) from %s", bin.Id, bin.Size, r.RemoteAddr)
log.Printf("Received bin %s (%d bytes) from %s", pasta.Id, pasta.Size, r.RemoteAddr)
w.WriteHeader(http.StatusOK)
url := fmt.Sprintf("%s/%s", cf.BaseUrl, bin.Id)
w.Write([]byte(url))
url := fmt.Sprintf("%s/%s", cf.BaseUrl, pasta.Id)
// Dont use json package, the reply is simple enough to build it on-the-fly
reply := fmt.Sprintf("{\"url\":\"%s\",\"token\":\"%s\"}", url, pasta.Token)
w.Write([]byte(reply))
}
}
}
@ -138,24 +136,24 @@ func handlerPost(w http.ResponseWriter, r *http.Request) {
func handler(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
// Check if bin ID is given
binId := ExtractBinName(r.URL.Path)
if binId == "" {
id := ExtractPastaId(r.URL.Path)
if id == "" {
fmt.Fprintf(w, "<!doctype html><html><head>")
fmt.Fprintf(w, "<body>Stupid simple pastebin service</body>")
fmt.Fprintf(w, "</html>")
} else {
bin, err := FetchBin(binId)
pasta, err := bowl.GetPasta(id)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "Database error")
log.Fatalf("Database error: %s", err)
fmt.Fprintf(w, "Storage error")
log.Fatalf("Storage error: %s", err)
return
}
if bin.Id == "" {
fmt.Fprintf(w, "No such bin: %s", binId)
if pasta.Id == "" {
fmt.Fprintf(w, "No such pasta: %s", id)
} else {
if err = SendBin(bin.Id, w); err != nil {
log.Printf("Error sending bin %s: %s", bin.Id, err)
if err = SendPasta(pasta.Id, w); err != nil {
log.Printf("Error sending pasta %s: %s", pasta.Id, err)
}
}
}
@ -167,51 +165,43 @@ func handler(w http.ResponseWriter, r *http.Request) {
}
}
func handlerPrivacy(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "<!doctype html><html><head>")
fmt.Fprintf(w, "<body><h1>Privacy</h1><p>When fetching bins no data is collected</p><p>When pasting a bin, the pasted content is stored and your IP address is logged for debugging and abuse prevention purposes</p></body>")
fmt.Fprintf(w, "</html>")
}
func handlerHealth(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "OK")
}
func main() {
var err error
configFile := "pastad.toml"
// Set defaults
cf.BaseUrl = "http://localhost:8199"
cf.DbFile = "pasta.db"
cf.BinDir = "bins/"
cf.PastaDir = "pastas/"
cf.BindAddr = "127.0.0.1:8199"
cf.MaxBinSize = 1024 * 1024 * 25 // Default max size: 25 MB
cf.BinCharacters = 8 // Note: Never use less than 8 characters!
rand.Seed(time.Now().Unix())
if _, err := toml.DecodeFile("pastad.toml", &cf); err != nil {
fmt.Printf("Error loading configuration file: %s\n", err)
os.Exit(1)
fmt.Println("Starting pasta server ... ")
if FileExists(configFile) {
if _, err := toml.DecodeFile(configFile, &cf); err != nil {
fmt.Printf("Error loading configuration file: %s\n", err)
os.Exit(1)
}
} else {
fmt.Fprintf(os.Stderr, "Warning: Config file '%s' not found\n", configFile)
}
// Sanity check
if cf.BinCharacters < 8 {
log.Println("Warning: Using less than 8 bin characters is recommended and might lead to unintended side-effects")
}
os.Mkdir(cf.BinDir, os.ModePerm)
if cf.PastaDir == "" {
cf.PastaDir = "."
}
bowl.Directory = cf.PastaDir
os.Mkdir(bowl.Directory, os.ModePerm)
// Database
log.Printf("Database initialization: %s", cf.DbFile)
db, err = sql.Open("sqlite3", cf.DbFile)
if err != nil {
panic(err)
}
if err = DbInitialize(db); err != nil {
panic(err)
}
defer db.Close()
// Setup webserver
log.Printf("Webserver initialization: http://%s", cf.BindAddr)
http.HandleFunc("/", handler)
http.HandleFunc("/privacy", handlerPrivacy)
http.HandleFunc("/health", handlerHealth)
log.Println("Startup completed")
log.Printf("Up and running: http://%s", cf.BindAddr)
log.Printf("Startup completed. Serving http://%s", cf.BindAddr)
log.Fatal(http.ListenAndServe(cf.BindAddr, nil))
}

View file

@ -1,64 +1,22 @@
package main
import (
"database/sql"
"bufio"
"errors"
"fmt"
"io"
"math/rand"
"os"
"strconv"
"strings"
)
var db *sql.DB
type Bin struct {
Id string
Owner string
CreationDate int64
ExpireDate int64
Size int64
}
func DbInitialize(db *sql.DB) error {
_, err := db.Exec("CREATE TABLE IF NOT EXISTS `bins` (`id` VARCHAR(64) PRIMARY KEY, `owner` VARCHAR(64), `createdate` int, `expiredate` int, `size` int);")
if err != nil {
panic(err)
}
return nil
}
func FetchBin(id string) (Bin, error) {
bin := Bin{Id: ""}
stmt, err := db.Prepare("SELECT `id`,`owner`,`createdate`,`expiredate`,`size` FROM `bins` WHERE `id` = ? LIMIT 1;")
if err != nil {
return bin, err
}
defer stmt.Close()
rows, err := stmt.Query(id)
if err != nil {
return bin, err
}
defer rows.Close()
if rows.Next() {
rows.Scan(&bin.Id, &bin.Owner, &bin.CreationDate, &bin.ExpireDate, &bin.Size)
}
return bin, nil
}
func InsertBin(bin Bin) error {
stmt, err := db.Prepare("INSERT INTO `bins`(`id`,`owner`,`createdate`,`expiredate`,`size`) VALUES (?,?,?,?,?);")
if err != nil {
return err
}
defer stmt.Close()
_, err = stmt.Exec(bin.Id, bin.Owner, bin.CreationDate, bin.ExpireDate, bin.Size)
return err
}
func DeleteBin(id string) error {
stmt, err := db.Prepare("DELETE FROM `bins` WHERE `id` = ?;")
if err != nil {
return err
}
defer stmt.Close()
_, err = stmt.Exec(id)
return err
type Pasta struct {
Id string
Token string
Filename string
ExpireDate int64
Size int64
}
func RandomString(n int) string {
@ -70,15 +28,160 @@ func RandomString(n int) string {
return string(b)
}
func GenerateRandomBinId(n int) (string, error) {
func FileExists(filename string) bool {
_, err := os.Stat(filename)
if err != nil {
return false
}
return !os.IsNotExist(err)
}
/* PastaBowl is the main storage instance */
type PastaBowl struct {
Directory string // Directory where the pastas are
}
func (bowl *PastaBowl) filename(id string) string {
return fmt.Sprintf("%s/%s", bowl.Directory, id)
}
func (bowl *PastaBowl) Exists(id string) bool {
return FileExists(bowl.filename(id))
}
// get pasta metadata
func (bowl *PastaBowl) GetPasta(id string) (Pasta, error) {
pasta := Pasta{Id: "", Filename: bowl.filename(id)}
stat, err := os.Stat(bowl.filename(id))
if err != nil {
// Does not exists results in empty pasta result
if !os.IsExist(err) {
return pasta, nil
}
return pasta, err
}
pasta.Size = stat.Size()
file, err := os.OpenFile(pasta.Filename, os.O_RDONLY, 0400)
if err != nil {
return pasta, err
}
defer file.Close()
// Read metadata (until "---")
scanner := bufio.NewScanner(file)
for scanner.Scan() {
if err = scanner.Err(); err != nil {
return pasta, err
}
line := scanner.Text()
pasta.Size -= int64(len(line) + 1)
if line == "---" {
break
}
// Parse metadata (name: value)
i := strings.Index(line, ":")
if i <= 0 {
continue
}
name, value := strings.TrimSpace(line[:i]), strings.TrimSpace(line[i+1:])
if name == "token" {
pasta.Token = value
} else if name == "expire" {
pasta.ExpireDate, _ = strconv.ParseInt(value, 10, 64)
}
}
// All good
pasta.Id = id
return pasta, nil
}
func (bowl *PastaBowl) getPastaFile(id string, flag int) (*os.File, error) {
filename := bowl.filename(id)
file, err := os.OpenFile(filename, flag, 0640)
if err != nil {
return nil, err
}
buf := make([]byte, 1)
c := 0 // Counter
for {
n, err := file.Read(buf)
if err != nil {
if err == io.EOF {
file.Close()
return nil, err
}
file.Close()
return nil, err
}
if n == 0 {
continue
}
if buf[0] == '-' {
c++
} else if buf[0] == '\n' {
if c >= 3 {
return file, nil
}
c = 0
}
}
// This should never occur
file.Close()
return nil, errors.New("Unexpected end of block")
}
// Get the file instance to the pasta content (read-only)
func (bowl *PastaBowl) GetPastaReader(id string) (*os.File, error) {
return bowl.getPastaFile(id, os.O_RDONLY)
}
// Get the file instance to the pasta content (read-only)
func (bowl *PastaBowl) GetPastaWriter(id string) (*os.File, error) {
return bowl.getPastaFile(id, os.O_RDWR)
}
// Prepare a pasta file to be written. Id and Token will be set, if not already done
func (bowl *PastaBowl) InsertPasta(pasta *Pasta) error {
if pasta.Id == "" {
// TODO: Use crypto rand
pasta.Id = bowl.GenerateRandomBinId(8) // Use default length here
}
if pasta.Token == "" {
// TODO: Use crypto rand
pasta.Token = RandomString(16)
}
pasta.Filename = bowl.filename(pasta.Id)
file, err := os.OpenFile(pasta.Filename, os.O_RDWR|os.O_CREATE, 0640)
if err != nil {
return err
}
defer file.Close()
if _, err := file.Write([]byte(fmt.Sprintf("token:%s\n", pasta.Token))); err != nil {
return err
}
if pasta.ExpireDate > 0 {
if _, err := file.Write([]byte(fmt.Sprintf("expire:%d\n", pasta.ExpireDate))); err != nil {
return err
}
}
if _, err := file.Write([]byte("---\n")); err != nil {
return err
}
return file.Sync()
}
func (bowl *PastaBowl) DeletePasta(id string) error {
if !bowl.Exists(id) {
return nil
}
return os.Remove(bowl.filename(id))
}
func (bowl *PastaBowl) GenerateRandomBinId(n int) string {
for {
id := RandomString(n)
bin, err := FetchBin(id)
if err != nil {
return "", err
}
if bin.Id == "" {
return id, nil
if !bowl.Exists(id) {
return id
}
}
}

View file

@ -1,91 +1,245 @@
package main
import (
"database/sql"
"io/ioutil"
"math/rand"
"os"
"testing"
"time"
)
const testFilename = "test.db"
var testBowl PastaBowl
func TestMain(m *testing.M) {
var err error
// Initialisation
rand.Seed(time.Now().UnixNano())
os.Remove(testFilename)
db, err = sql.Open("sqlite3", testFilename)
if err != nil {
panic(err)
}
if err = DbInitialize(db); err != nil {
panic(err)
}
defer db.Close()
testBowl.Directory = "pasta_test"
os.Mkdir(testBowl.Directory, os.ModePerm)
defer os.RemoveAll(testBowl.Directory)
// Run tests
ret := m.Run()
os.Exit(ret)
}
func TestBins(t *testing.T) {
func TestMetadata(t *testing.T) {
var err error
var bin Bin
bin1 := Bin{Id: "a", Owner: "user1", CreationDate: 1, ExpireDate: 1000, Size: 10}
bin2 := Bin{Id: "b", Owner: "user2", CreationDate: 2, ExpireDate: 2000, Size: 50}
var pasta, p1, p2, p3 Pasta
if err = InsertBin(bin1); err != nil {
t.Fatalf("Error inserting bin 1: %s", err)
if err = testBowl.InsertPasta(&p1); err != nil {
t.Fatalf("Error inserting pasta 1: %s", err)
return
}
if err = InsertBin(bin2); err != nil {
t.Fatalf("Error inserting bin 2: %s", err)
if p1.Id == "" {
t.Fatal("Pasta 1 id not set")
return
}
if bin, err = FetchBin(bin1.Id); err != nil {
t.Fatalf("Error getting bin 1: %s", err)
return
} else if bin != bin1 {
t.Fatal("Bin 1 mismatch", err)
if p1.Token == "" {
t.Fatal("Pasta 1 id not set")
return
}
if bin, err = FetchBin(bin2.Id); err != nil {
t.Fatalf("Error getting bin 2: %s", err)
return
} else if bin != bin2 {
t.Fatal("Bin 2 mismatch", err)
if err = testBowl.InsertPasta(&p2); err != nil {
t.Fatalf("Error inserting pasta 2: %s", err)
return
}
if err = DeleteBin(bin1.Id); err != nil {
t.Fatalf("Error deleting bin 1: %s", err)
// Insert pasta with given ID and Token
p3Id := testBowl.GenerateRandomBinId(12)
p3Token := RandomString(20)
p3.Id = p3Id
p3.Token = p3Token
if err = testBowl.InsertPasta(&p3); err != nil {
t.Fatalf("Error inserting pasta 3: %s", err)
return
}
if bin, err = FetchBin(bin1.Id); err != nil {
t.Fatalf("Error getting bin 1 after delete: %s", err)
return
} else if bin.Id != "" {
t.Fatal("Bin 1 still exists after delete", err)
if p3.Id != p3Id {
t.Fatal("Pasta 3 id mismatch")
return
}
// Check if bin2 is still existing
if bin, err = FetchBin(bin2.Id); err != nil {
t.Fatalf("Error getting bin 2: %s", err)
return
} else if bin != bin2 {
t.Fatal("Bin 2 mismatch after deleting", err)
if p3.Token != p3Token {
t.Fatal("Pasta 3 id mismatch")
return
}
// Now delete bin2 as well
if err = DeleteBin(bin2.Id); err != nil {
t.Fatalf("Error deleting bin 1: %s", err)
pasta, err = testBowl.GetPasta(p1.Id)
if err != nil {
t.Fatalf("Error getting pasta 1: %s", err)
return
}
if bin, err = FetchBin(bin2.Id); err != nil {
t.Fatalf("Error getting bin 2 after delete: %s", err)
if pasta != p1 {
t.Fatal("Pasta 1 mismatch")
return
} else if bin.Id != "" {
t.Fatal("Bin 2 still exists after delete", err)
}
pasta, err = testBowl.GetPasta(p2.Id)
if err != nil {
t.Fatalf("Error getting pasta 2: %s", err)
return
}
if pasta != p2 {
t.Fatal("Pasta 2 mismatch")
return
}
pasta, err = testBowl.GetPasta(p3.Id)
if err != nil {
t.Fatalf("Error getting pasta 3: %s", err)
return
}
if pasta != p3 {
t.Fatal("Pasta 3 mismatch")
return
}
if err = testBowl.DeletePasta(p1.Id); err != nil {
t.Fatalf("Error deleting pasta 1: %s", err)
}
pasta, err = testBowl.GetPasta(p1.Id)
if err != nil {
t.Fatalf("Error getting pasta 1 (after delete): %s", err)
return
}
if pasta.Id != "" {
t.Fatal("Pasta 1 exists after delete")
return
}
// Ensure pasta 2 and 3 are not affected if we delete pasta 1
pasta, err = testBowl.GetPasta(p2.Id)
if err != nil {
t.Fatalf("Error getting pasta 2 after deleting pasta 1: %s", err)
return
}
if pasta != p2 {
t.Fatal("Pasta 2 mismatch after deleting pasta 1")
return
}
pasta, err = testBowl.GetPasta(p3.Id)
if err != nil {
t.Fatalf("Error getting pasta 3 after deleting pasta 1: %s", err)
return
}
if pasta != p3 {
t.Fatal("Pasta 3 mismatch after deleteing pasta 1")
return
}
// Delete also pasta 2
if err = testBowl.DeletePasta(p2.Id); err != nil {
t.Fatalf("Error deleting pasta 2: %s", err)
}
pasta, err = testBowl.GetPasta(p2.Id)
if err != nil {
t.Fatalf("Error getting pasta 2 (after delete): %s", err)
return
}
if pasta.Id != "" {
t.Fatal("Pasta 2 exists after delete")
return
}
pasta, err = testBowl.GetPasta(p3.Id)
if err != nil {
t.Fatalf("Error getting pasta 3 after deleting pasta 2: %s", err)
return
}
if pasta != p3 {
t.Fatal("Pasta 3 mismatch after deleting pasta 2")
return
}
}
func TestBlobs(t *testing.T) {
var err error
var p1, p2 Pasta
// Contents
testString1 := RandomString(4096 * 8)
testString2 := RandomString(4096 * 8)
if err = testBowl.InsertPasta(&p1); err != nil {
t.Fatalf("Error inserting pasta 1: %s", err)
return
}
file, err := testBowl.GetPastaWriter(p1.Id)
if err != nil {
t.Fatalf("Error getting pasta file 1: %s", err)
return
}
defer file.Close()
if _, err = file.Write([]byte(testString1)); err != nil {
t.Fatalf("Error writing to pasta file 1: %s", err)
return
}
if err = file.Close(); err != nil {
t.Fatalf("Error closing pasta file 1: %s", err)
return
}
if err = testBowl.InsertPasta(&p2); err != nil {
t.Fatalf("Error inserting pasta 2: %s", err)
return
}
file, err = testBowl.GetPastaWriter(p2.Id)
if err != nil {
t.Fatalf("Error getting pasta file 2: %s", err)
return
}
defer file.Close()
if _, err = file.Write([]byte(testString2)); err != nil {
t.Fatalf("Error writing to pasta file 2: %s", err)
return
}
if err = file.Close(); err != nil {
t.Fatalf("Error closing pasta file 2: %s", err)
return
}
// Fetch contents now
file, err = testBowl.GetPastaReader(p1.Id)
if err != nil {
t.Fatalf("Error getting pasta reader 1: %s", err)
return
}
buf, err := ioutil.ReadAll(file)
file.Close()
if err != nil {
t.Fatalf("Error reading pasta 1: %s", err)
return
}
if testString1 != string(buf) {
t.Fatal("Mismatch: pasta 1 contents")
t.Logf("Bytes: Read %d, Expected %d", len(buf), len(([]byte(testString1))))
return
}
// Same for pasta 2
file, err = testBowl.GetPastaReader(p2.Id)
if err != nil {
t.Fatalf("Error getting pasta reader 2: %s", err)
return
}
buf, err = ioutil.ReadAll(file)
file.Close()
if err != nil {
t.Fatalf("Error reading pasta 2: %s", err)
return
}
if testString2 != string(buf) {
t.Fatal("Mismatch: pasta 2 contents")
t.Logf("Bytes: Read %d, Expected %d", len(buf), len(([]byte(testString2))))
return
}
// Check if pasta 1 can be deleted and the contents of pasta 2 are still OK afterwards
if err = testBowl.DeletePasta(p1.Id); err != nil {
t.Fatalf("Error deleting pasta 1: %s", err)
}
file, err = testBowl.GetPastaReader(p2.Id)
if err != nil {
t.Fatalf("Error getting pasta reader 2: %s", err)
return
}
buf, err = ioutil.ReadAll(file)
file.Close()
if err != nil {
t.Fatalf("Error reading pasta 2: %s", err)
return
}
if testString2 != string(buf) {
t.Fatal("Mismatch: pasta 2 contents")
t.Logf("Bytes: Read %d, Expected %d", len(buf), len(([]byte(testString2))))
return
}
}

View file

@ -1,6 +1,5 @@
BaseURL = "http://localhost:8199"
Database = "pastad.db"
BinsDir = "bins"
PastaDir = "bins"
BindAddress = ":8199"
MaxBinSize = 26214400 # 5 MB
BinCharacters = 8

View file

@ -7,20 +7,26 @@ function cleanup() {
rm -f testfile
rm -f testfile2
kill %1
rm -rf pasta_test
rm -f pasta.json
}
set -e
trap cleanup EXIT
./pastad &
../pastad &
sleep 1 # Don't do sleep you lazy :-)
echo "Testfile 123" > testfile
link=`./pasta < testfile`
../pasta -r http://localhost:8199 < testfile > pasta.json
cat pasta.json
link=`jq -r .url pasta.json`
echo $link
curl -o testfile2 $link
diff testfile testfile2
echo "Testfile matches"
echo "Testfile 123456789" > testfile
link=`./pasta < testfile`
../pasta -r http://localhost:8199 < testfile > pasta.json
link=`jq -r .url pasta.json`
curl -o testfile2 $link
diff testfile testfile2
echo "Testfile 2 matches"