Complete restructure of domain and infra layer
parent
a70e8ef74b
commit
8a0e6b80e3
@ -0,0 +1,2 @@
|
||||
sql.db
|
||||
bog
|
@ -1,39 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http/httptest"
|
||||
"net/http"
|
||||
"testing"
|
||||
"strings"
|
||||
"time"
|
||||
"caj-larsson/bog/domain"
|
||||
"caj-larsson/bog/test/mock"
|
||||
)
|
||||
|
||||
func TestApplication(t *testing.T) {
|
||||
|
||||
file_service := domain.NewBogFileService(
|
||||
mock.NewMockUserAgentRepository(),
|
||||
mock.NewMockFileRepository(),
|
||||
1000,
|
||||
time.Hour,
|
||||
)
|
||||
|
||||
bog := Bog {
|
||||
router: new(http.ServeMux),
|
||||
file_service: file_service,
|
||||
address: "fake",
|
||||
}
|
||||
bog.routes()
|
||||
req := httptest.NewRequest("POST", "/apath", strings.NewReader("testdata"))
|
||||
req.Header.Add("User-Agent", "testingclient")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
bog.router.ServeHTTP(w, req)
|
||||
|
||||
if (w.Code != 200){
|
||||
fmt.Printf("%v", w)
|
||||
t.Error("not ok")
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package namespace
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrNoNamespace = errors.New("that namespace does not exist")
|
||||
)
|
||||
|
||||
type Repository interface{
|
||||
Create(namespace Namespace) (*Namespace, error)
|
||||
All() ([]Namespace, error)
|
||||
GetByName(name string) (*Namespace, error)
|
||||
Update(id int64, namespace Namespace) (*Namespace, error)
|
||||
Delete(id int64) error
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package dataswamp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
"strconv"
|
||||
"strings"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"caj-larsson/bog/dataswamp/namespace"
|
||||
"caj-larsson/bog/dataswamp/swampfile"
|
||||
)
|
||||
|
||||
|
||||
type SwampFileService struct {
|
||||
namespace_repo namespace.Repository
|
||||
swamp_file_repo swampfile.Repository
|
||||
default_allowance_bytes int64
|
||||
default_allowance_duration time.Duration
|
||||
}
|
||||
|
||||
func NewSwampFileService(
|
||||
namespace_repo namespace.Repository,
|
||||
swamp_file_repo swampfile.Repository,
|
||||
da_bytes int64,
|
||||
da_duration time.Duration,
|
||||
) SwampFileService {
|
||||
return SwampFileService {namespace_repo, swamp_file_repo, da_bytes, da_duration}
|
||||
}
|
||||
|
||||
func (s SwampFileService) getOrCreateNs(namespace_in string) *namespace.Namespace{
|
||||
ns, err := s.namespace_repo.GetByName(namespace_in)
|
||||
|
||||
if err == namespace.ErrNotExists {
|
||||
new_ns := namespace.Namespace {
|
||||
0,
|
||||
namespace_in,
|
||||
time.Now(),
|
||||
s.default_allowance_duration,
|
||||
namespace.FileSizeQuota { s.default_allowance_bytes, 0 },
|
||||
}
|
||||
created_ns, err := s.namespace_repo.Create(new_ns)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return created_ns
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return ns
|
||||
}
|
||||
|
||||
func (s SwampFileService) SaveFile(ref swampfile.FileReference, src io.Reader, size int64) error {
|
||||
ns := s.getOrCreateNs(ref.UserAgent)
|
||||
|
||||
if !ns.FileQuota.Allows(size) {
|
||||
return namespace.ErrExceedQuota
|
||||
}
|
||||
|
||||
f, err := s.swamp_file_repo.Create(ref.Path, strconv.FormatInt(ns.ID, 10))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
io.Copy(f, src)
|
||||
f.Close()
|
||||
ns.FileQuota.Add(size)
|
||||
s.namespace_repo.Update(ns.ID, *ns)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s SwampFileService) OpenOutFile(ref swampfile.FileReference) (swampfile.SwampOutFile, error) {
|
||||
ns, err := s.namespace_repo.GetByName(ref.UserAgent)
|
||||
|
||||
if err == namespace.ErrNotExists {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f, err := s.swamp_file_repo.Open(ref.Path, strconv.FormatInt(ns.ID, 10))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func CleanPath(inpath string) string {
|
||||
return filepath.FromSlash(path.Clean("/" + strings.Trim(inpath, "/")))
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package dataswamp
|
||||
|
||||
import (
|
||||
"time"
|
||||
"bytes"
|
||||
"testing"
|
||||
"github.com/matryer/is"
|
||||
"caj-larsson/bog/dataswamp/swampfile"
|
||||
"caj-larsson/bog/dataswamp/namespace"
|
||||
m_namespace "caj-larsson/bog/infrastructure/memory/namespace"
|
||||
m_swampfile "caj-larsson/bog/infrastructure/memory/swampfile"
|
||||
)
|
||||
|
||||
var file_ref1 = swampfile.FileReference { "ptah1", "ua1" }
|
||||
var file_ref2 = swampfile.FileReference { "path1", "ua2" }
|
||||
var file_ref3 = swampfile.FileReference { "path2", "ua1" }
|
||||
|
||||
|
||||
func NewTestSwampFileService() SwampFileService {
|
||||
file_repo := m_swampfile.NewRepository()
|
||||
ns_repo := m_namespace.NewRepository()
|
||||
return NewSwampFileService(ns_repo, file_repo, 1024, time.Hour)
|
||||
}
|
||||
|
||||
func TestFileDontExist(t *testing.T) {
|
||||
s := NewTestSwampFileService()
|
||||
outfile, err := s.OpenOutFile(file_ref1)
|
||||
|
||||
if outfile != nil && err != swampfile.ErrNotExists {
|
||||
t.Errorf("File shall not exist by default")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileIsStored(t *testing.T) {
|
||||
s := NewTestSwampFileService()
|
||||
|
||||
fakefile := bytes.NewBufferString("My bog data")
|
||||
|
||||
err := s.SaveFile(file_ref1, fakefile, int64(fakefile.Len()))
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("A small file should be writable %s", err)
|
||||
}
|
||||
|
||||
largefakefile := bytes.NewBufferString("")
|
||||
|
||||
for largefakefile.Len() < 64000 {
|
||||
_, err = largefakefile.WriteString("A very repetitive file")
|
||||
}
|
||||
|
||||
err = s.SaveFile(file_ref3, largefakefile, int64(largefakefile.Len()))
|
||||
|
||||
if err != namespace.ErrExceedQuota {
|
||||
t.Errorf("too large files should not be excepted")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
func TestFileIsReadBack(t *testing.T) {
|
||||
s := NewTestSwampFileService()
|
||||
|
||||
infile := bytes.NewBufferString("My bog data")
|
||||
|
||||
_ = s.SaveFile(file_ref1, infile, int64(infile.Len()))
|
||||
|
||||
outswampfile, _ := s.OpenOutFile(file_ref1)
|
||||
|
||||
outfile := bytes.NewBufferString("")
|
||||
_, _ = outfile.ReadFrom(outswampfile)
|
||||
|
||||
if outfile.String() != "My bog data" {
|
||||
t.Errorf("file corrupted")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestUAIsolation(t *testing.T) {
|
||||
s := NewTestSwampFileService()
|
||||
|
||||
ns1_file := bytes.NewBufferString("My bog data ua1")
|
||||
ns2_file := bytes.NewBufferString("My bog data ua2")
|
||||
|
||||
_ = s.SaveFile(file_ref1, ns1_file, int64(ns1_file.Len()))
|
||||
_ = s.SaveFile(file_ref2, ns2_file, int64(ns2_file.Len()))
|
||||
|
||||
outswampfile, _ := s.OpenOutFile(file_ref1)
|
||||
|
||||
outfile := bytes.NewBufferString("")
|
||||
_, _ = outfile.ReadFrom(outswampfile)
|
||||
|
||||
if outfile.String() != "My bog data ua1" {
|
||||
t.Errorf("file corrupted")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestCleanPath(t *testing.T) {
|
||||
is := is.New(t)
|
||||
|
||||
is.Equal(CleanPath("/"), "/")
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package swampfile
|
||||
|
||||
import (
|
||||
"time"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type SwampFile struct {
|
||||
UserAgentId int64
|
||||
Path string
|
||||
Size int64
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
var (
|
||||
ErrDuplicate = errors.New("record already exists")
|
||||
ErrExceedQuota = errors.New("file too large")
|
||||
ErrQuotaInvalid = errors.New("quota invalid")
|
||||
ErrNotExists = errors.New("row not exists")
|
||||
ErrUpdateFailed = errors.New("update failed")
|
||||
ErrDeleteFailed = errors.New("delete failed")
|
||||
)
|
@ -0,0 +1,7 @@
|
||||
package swampfile
|
||||
|
||||
type Repository interface {
|
||||
Create(filename string, user_agent_label string) (SwampInFile, error)
|
||||
Open(filename string, user_agent_label string) (SwampOutFile, error)
|
||||
Delete(filename string, user_agent_label string)
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package swampfile
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SwampOutFile interface {
|
||||
Path() string
|
||||
Size() int64
|
||||
Modified() time.Time
|
||||
io.Reader
|
||||
io.Seeker
|
||||
io.Closer
|
||||
}
|
||||
|
||||
type SwampInFile interface {
|
||||
Path() string
|
||||
Size() int64
|
||||
Modified() time.Time
|
||||
io.Writer
|
||||
io.Seeker
|
||||
io.Closer
|
||||
}
|
||||
|
||||
type FileReference struct {
|
||||
Path string
|
||||
UserAgent string
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package domain
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrNoUserAgent = errors.New("that useragent does not exist")
|
||||
)
|
||||
|
||||
type UserAgentRepository interface{
|
||||
Create(useragent UserAgent) (*UserAgent, error)
|
||||
All() ([]UserAgent, error)
|
||||
GetByName(name string) (*UserAgent, error)
|
||||
Update(id int64, useragent UserAgent) (*UserAgent, error)
|
||||
Delete(id int64) error
|
||||
}
|
||||
|
||||
type FileDataRepository interface {
|
||||
Create(filename string, user_agent_label string) (BogInFile, error)
|
||||
Open(filename string, user_agent_label string) (BogOutFile, error)
|
||||
Delete(filename string, user_agent_label string)
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
|
||||
type BogFileService struct {
|
||||
user_agent_repo UserAgentRepository
|
||||
file_data_repo FileDataRepository
|
||||
default_allowance_bytes int64
|
||||
default_allowance_duration time.Duration
|
||||
}
|
||||
|
||||
func NewBogFileService(
|
||||
user_agent_repo UserAgentRepository,
|
||||
file_data_repo FileDataRepository,
|
||||
da_bytes int64,
|
||||
da_duration time.Duration,
|
||||
) BogFileService {
|
||||
return BogFileService {user_agent_repo, file_data_repo, da_bytes, da_duration}
|
||||
}
|
||||
|
||||
func (b BogFileService) getOrCreateUA(useragent_in string) *UserAgent{
|
||||
ua, err := b.user_agent_repo.GetByName(useragent_in)
|
||||
|
||||
if err == ErrNotExists {
|
||||
new_ua := UserAgent {
|
||||
0, useragent_in, time.Now(), b.default_allowance_duration, FileSizeQuota { b.default_allowance_bytes, 0 },
|
||||
}
|
||||
created_ua, err := b.user_agent_repo.Create(new_ua)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return created_ua
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return ua
|
||||
}
|
||||
|
||||
func (b BogFileService) SaveFile(ref FileReference, src io.Reader, size int64) error {
|
||||
user_agent := b.getOrCreateUA(ref.UserAgent)
|
||||
|
||||
if !user_agent.FileQuota.Allows(size) {
|
||||
return ErrExceedQuota
|
||||
}
|
||||
|
||||
f, err := b.file_data_repo.Create(ref.Path, strconv.FormatInt(user_agent.ID, 10))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
io.Copy(f, src)
|
||||
f.Close()
|
||||
user_agent.FileQuota.Add(size)
|
||||
b.user_agent_repo.Update(user_agent.ID, *user_agent)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b BogFileService) OpenOutFile(ref FileReference) (BogOutFile, error) {
|
||||
user_agent, err := b.user_agent_repo.GetByName(ref.UserAgent)
|
||||
if err == ErrNotExists {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f, err := b.file_data_repo.Open(ref.Path, strconv.FormatInt(user_agent.ID, 10))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
package swampfile
|
||||
|
||||
import (
|
||||
"time"
|
||||
"os"
|
||||
"path"
|
||||
"caj-larsson/bog/dataswamp/swampfile"
|
||||
)
|
||||
|
||||
type FileSystemSwampFileData struct {
|
||||
path string
|
||||
size int64
|
||||
mod_time time.Time
|
||||
file *os.File
|
||||
}
|
||||
|
||||
func (f FileSystemSwampFileData) Read(p []byte) (int, error) {
|
||||
return f.file.Read(p)
|
||||
}
|
||||
|
||||
func (f FileSystemSwampFileData) Write(p []byte) (int, error) {
|
||||
return f.file.Write(p)
|
||||
}
|
||||
|
||||
func (f FileSystemSwampFileData) Close() error {
|
||||
return f.file.Close()
|
||||
}
|
||||
|
||||
func (f FileSystemSwampFileData) Seek(offset int64, whence int) (int64, error) {
|
||||
return f.file.Seek(offset, whence)
|
||||
}
|
||||
|
||||
func (f FileSystemSwampFileData) Path() string {
|
||||
return f.path
|
||||
}
|
||||
|
||||
func (f FileSystemSwampFileData) Size() int64 {
|
||||
return f.size
|
||||
}
|
||||
|
||||
func (f FileSystemSwampFileData) Modified() time.Time{
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
type Repository struct {
|
||||
Root string
|
||||
}
|
||||
|
||||
func (f Repository) absPath(filename string, namespace_ns string) string {
|
||||
return path.Join(f.Root, namespace_ns, filename)
|
||||
}
|
||||
|
||||
func (f Repository) Create(filename string, namespace_ns string) (swampfile.SwampInFile, error) {
|
||||
abs_path := f.absPath(filename, namespace_ns)
|
||||
|
||||
dir := path.Dir(abs_path)
|
||||
os.MkdirAll(dir, 0750)
|
||||
file, err := os.OpenFile(abs_path, os.O_RDWR|os.O_CREATE, 0644)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
stat_info, err := file.Stat()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
bfd := FileSystemSwampFileData {filename, stat_info.Size(), stat_info.ModTime(), file}
|
||||
|
||||
return bfd, nil
|
||||
}
|
||||
|
||||
func (f Repository) Open(filename string, namespace_ns string) (swampfile.SwampOutFile, error) {
|
||||
abs_path := f.absPath(filename, namespace_ns)
|
||||
|
||||
dir := path.Dir(abs_path)
|
||||
os.MkdirAll(dir, 0750)
|
||||
file, err := os.OpenFile(abs_path, os.O_RDONLY, 0644)
|
||||
|
||||
if err != nil {
|
||||
return nil, swampfile.ErrNotExists
|
||||
}
|
||||
|
||||
bfd := FileSystemSwampFileData {filename, 0, time.Now(), file}
|
||||
|
||||
return bfd, nil
|
||||
}
|
||||
|
||||
func (f Repository) Delete(filename string, namespace_ns string) {
|
||||
abs_path := f.absPath(filename, namespace_ns)
|
||||
os.Remove(abs_path)
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package swampfile
|
||||
|
||||
import (
|
||||
"path"
|
||||
"testing"
|
||||
"caj-larsson/bog/dataswamp/swampfile"
|
||||
)
|
||||
|
||||
func TestFsFileRepo(t *testing.T) {
|
||||
|
||||
var fac = func() swampfile.Repository {
|
||||
r := t.TempDir()
|
||||
d := path.Join(r, "fs")
|
||||
repo := Repository { d }
|
||||
return &repo
|
||||
}
|
||||
|
||||
swampfile.RepositoryContract(fac, t)
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package memory
|
||||
|
||||
import (
|
||||
// "time"
|
||||
"caj-larsson/bog/dataswamp/namespace"
|
||||
)
|
||||
|
||||
|
||||
type Repository struct {
|
||||
IdIdx map[int64] *namespace.Namespace
|
||||
NameIdx map[string] *namespace.Namespace
|
||||
NextId int64
|
||||
}
|
||||
|
||||
func NewRepository() *Repository {
|
||||
r := new(Repository)
|
||||
r.NextId = 0
|
||||
r.IdIdx = make(map[int64]*namespace.Namespace)
|
||||
r.NameIdx = make(map[string]*namespace.Namespace)
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Repository) Create(ns namespace.Namespace) (*namespace.Namespace, error) {
|
||||
r.NextId += 1
|
||||
ns.ID = r.NextId
|
||||
|
||||
r.IdIdx[ns.ID] = &ns
|
||||
r.NameIdx[ns.Name] = &ns
|
||||
return &ns, nil
|
||||
}
|
||||
|
||||
|
||||
func (r *Repository) All() ([]namespace.Namespace, error) {
|
||||
ns := make([]namespace.Namespace, 0, len(r.IdIdx))
|
||||
|
||||
for _, value := range r.IdIdx {
|
||||
ns = append(ns, *value)
|
||||
}
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
|
||||
func (r *Repository) GetByName(name string) (*namespace.Namespace, error) {
|
||||
ns, exists := r.NameIdx[name]
|
||||
if exists {
|
||||
return ns, nil
|
||||
}
|
||||
return nil, namespace.ErrNotExists
|
||||
}
|
||||
|
||||
|
||||
func (r *Repository) Update(id int64, ns namespace.Namespace) (*namespace.Namespace, error) {
|
||||
original := *r.IdIdx[id]
|
||||
ns.ID = id
|
||||
r.IdIdx[id] = &ns
|
||||
r.NameIdx[original.Name] = &ns
|
||||
return &ns, nil
|
||||
}
|
||||
|
||||
|
||||
func (r *Repository) Delete(id int64) error {
|
||||
original := *r.IdIdx[id]
|
||||
delete(r.NameIdx, original.Name)
|
||||
delete(r.IdIdx, original.ID)
|
||||
return nil
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package memory
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
"caj-larsson/bog/dataswamp/namespace"
|
||||
)
|
||||
|
||||
func TestUserAgentRepo(t *testing.T) {
|
||||
r := NewRepository()
|
||||
|
||||
all, err := r.All()
|
||||
|
||||
if len(all) != 0 && err != nil {
|
||||
t.Errorf("New repo should be empty")
|
||||
}
|
||||
|
||||
ns := namespace.Namespace {23, "n1", time.Now(), time.Duration(time.Hour * 3), namespace.FileSizeQuota {1000, 0} }
|
||||
|
||||
|
||||
ns1, _ := r.Create(ns)
|
||||
ns.Name = "n2"
|
||||
ns2, _ := r.Create(ns)
|
||||
if ns1 == ns2 {
|
||||
t.Errorf("Must create unique items")
|
||||
}
|
||||
|
||||
all, err = r.All()
|
||||
|
||||
if len(all) != 2 && err != nil {
|
||||
t.Errorf("After adding there should be two Useragent")
|
||||
}
|
||||
|
||||
if ns.ID != 23 {
|
||||
t.Errorf("It does not change the original UserAgent")
|
||||
}
|
||||
|
||||
ns3, _ := r.GetByName("n2")
|
||||
|
||||
if ns3 != ns2 {
|
||||
t.Errorf("It the correct ns is acquired")
|
||||
}
|
||||
|
||||
if r.Delete(ns2.ID) != nil {
|
||||
t.Errorf("Must delete without error")
|
||||
}
|
||||
|
||||
all, err = r.All()
|
||||
if len(all) != 1 && err != nil {
|
||||
t.Errorf("After deleting one there should be one NS ")
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package swampfile
|
||||
|
||||
import (
|
||||
"time"
|
||||
"path"
|
||||
"os"
|
||||
// "io"
|
||||
"github.com/spf13/afero"
|
||||
"caj-larsson/bog/dataswamp/swampfile"
|
||||
)
|
||||
|
||||
|
||||
type SwampFile struct {
|
||||
filename string
|
||||
file afero.File
|
||||
}
|
||||
|
||||
func (f SwampFile) Path() string {
|
||||
return f.filename
|
||||
}
|
||||
|
||||
func (f SwampFile) Size() int64 {
|
||||
stat, _ := f.file.Stat()
|
||||
return int64(stat.Size())
|
||||
}
|
||||
|
||||
func (f SwampFile) Read(p []byte) (int, error) {
|
||||
return f.file.Read(p)
|
||||
}
|
||||
|
||||
func (f SwampFile) Write(p []byte) (int, error) {
|
||||
return f.file.Write(p)
|
||||
}
|
||||
|
||||
func (f SwampFile) Close() error {
|
||||
return f.file.Close()
|
||||
}
|
||||
|
||||
func (f SwampFile) Seek(offset int64, whence int) (int64, error) {
|
||||
return f.file.Seek(offset, whence)
|
||||
}
|
||||
|
||||
func (f SwampFile) Modified() time.Time {
|
||||
stat, _ := f.file.Stat()
|
||||
return stat.ModTime()
|
||||
}
|
||||
|
||||
// The actual repository
|
||||
type Repository struct {
|
||||
fs afero.Fs
|
||||
}
|
||||
|
||||
func NewRepository() swampfile.Repository {
|
||||
return Repository { afero.NewMemMapFs() }
|
||||
}
|
||||
|
||||
func (r Repository) Create(filename string, namespace_stub string) (swampfile.SwampInFile, error) {
|
||||
abs_path := path.Join(filename, namespace_stub)
|
||||
dir := path.Dir(abs_path)
|
||||
r.fs.MkdirAll(dir, 0750)
|
||||
file, err := r.fs.OpenFile(abs_path, os.O_RDWR|os.O_CREATE, 0644)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
bf := SwampFile {filename, file}
|
||||
|
||||
return bf, nil
|
||||
}
|
||||
|
||||
func (r Repository) Open(filename string, namespace_stub string) (swampfile.SwampOutFile, error) {
|
||||
abs_path := path.Join(filename, namespace_stub)
|
||||
|
||||
dir := path.Dir(abs_path)
|
||||
r.fs.MkdirAll(dir, 0750)
|
||||
file, err := r.fs.OpenFile(abs_path, os.O_RDONLY, 0644)
|
||||
|
||||
if err != nil {
|
||||
return nil, swampfile.ErrNotExists
|
||||
}
|
||||
|
||||
bf := SwampFile {filename, file}
|
||||
return bf, nil
|
||||
}
|
||||
|
||||
func (r Repository) Delete(filename string, namespace_stub string) {
|
||||
abs_path := path.Join(filename, namespace_stub)
|
||||
r.fs.Remove(abs_path)
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package swampfile
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"caj-larsson/bog/dataswamp/swampfile"
|
||||
)
|
||||
|
||||
|
||||
func TestFileRepo(t *testing.T) {
|
||||
swampfile.RepositoryContract(NewRepository, t)
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package namespace
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
"caj-larsson/bog/dataswamp/namespace"
|
||||
)
|
||||
|
||||
type NamespaceRecord struct {
|
||||
ID int64
|
||||
Name string
|
||||
LastSeen string
|
||||
AllowanceSeconds int64
|
||||
QuotaKB int64
|
||||
QuotaUsedKB int64
|
||||
}
|
||||
|
||||
var ErrUnparseableRecord = errors.New("record could not be mapped to entity")
|
||||
|
||||
func (r *NamespaceRecord) toEntity() (*namespace.Namespace, error) {
|
||||
lastseen, err := time.Parse(time.RFC3339, r.LastSeen)
|
||||
|
||||
if err != nil {
|
||||
return nil, ErrUnparseableRecord
|
||||
}
|
||||
|
||||
var ns = new(namespace.Namespace)
|
||||
ns.ID = r.ID
|
||||
ns.Name = r.Name
|
||||
ns.LastSeen = lastseen
|
||||
ns.AllowanceDuration = time.Duration(r.AllowanceSeconds * int64(time.Second))
|
||||
ns.FileQuota = namespace.FileSizeQuota { r.QuotaKB, r.QuotaUsedKB }
|
||||
return ns, err
|
||||
}
|
||||
|
||||
func fromEntity(ns namespace.Namespace) (*NamespaceRecord, error) {
|
||||
var record = new(NamespaceRecord)
|
||||
record.ID = ns.ID
|
||||
record.Name = ns.Name
|
||||
record.LastSeen = ns.LastSeen.Format(time.RFC3339)
|
||||
record.AllowanceSeconds = int64(ns.AllowanceDuration.Seconds())
|
||||
record.QuotaKB = ns.FileQuota.AllowanceKB
|
||||
record.QuotaUsedKB = ns.FileQuota.CurrentUsage
|
||||
return record, nil
|
||||
}
|
@ -1,94 +0,0 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"time"
|
||||
"os"
|
||||
"path"
|
||||
"caj-larsson/bog/domain"
|
||||
)
|
||||
|
||||
type FileSystemBogFileData struct {
|
||||
path string
|
||||
size int64
|
||||
mod_time time.Time
|
||||
file *os.File
|
||||
}
|
||||
|
||||
|
||||
func (f FileSystemBogFileData) Read(p []byte) (int, error) {
|
||||
return f.file.Read(p)
|
||||
}
|
||||
|
||||
func (f FileSystemBogFileData) Write(p []byte) (int, error) {
|
||||
return f.file.Write(p)
|
||||
}
|
||||
|
||||
func (f FileSystemBogFileData) Close() error {
|
||||
return f.file.Close()
|
||||
}
|
||||
|
||||
func (f FileSystemBogFileData) Seek(offset int64, whence int) (int64, error) {
|
||||
return f.file.Seek(offset, whence)
|
||||
}
|
||||
|
||||
func (f FileSystemBogFileData) Path() string {
|
||||
return f.path
|
||||
}
|
||||
|
||||
func (f FileSystemBogFileData) Size() int64 {
|
||||
return f.size
|
||||
}
|
||||
|
||||
func (f FileSystemBogFileData) Modified() time.Time{
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
type FileSystemBogRepository struct {
|
||||
Root string
|
||||
}
|
||||
|
||||
func (f FileSystemBogRepository) absPath(filename string, user_agent_label string) string {
|
||||
return path.Join(f.Root, user_agent_label, filename)
|
||||
}
|
||||
|
||||
func (f FileSystemBogRepository) Create(filename string, user_agent_label string) (domain.BogInFile, error) {
|
||||
abs_path := f.absPath(filename, user_agent_label)
|
||||
|
||||
dir := path.Dir(abs_path)
|
||||
os.MkdirAll(dir, 0750)
|
||||
file, err := os.OpenFile(abs_path, os.O_RDWR|os.O_CREATE, 0644)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
stat_info, err := file.Stat()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
bfd := FileSystemBogFileData {filename, stat_info.Size(), stat_info.ModTime(), file}
|
||||
|
||||
return bfd, nil
|
||||
}
|
||||
|
||||
func (f FileSystemBogRepository) Open(filename string, user_agent_label string) (domain.BogOutFile, error) {
|
||||
abs_path := f.absPath(filename, user_agent_label)
|
||||
|
||||
dir := path.Dir(abs_path)
|
||||
os.MkdirAll(dir, 0750)
|
||||
file, err := os.OpenFile(abs_path, os.O_RDONLY, 0644)
|
||||
|
||||
if err != nil {
|
||||
return nil, domain.ErrNotExists
|
||||
}
|
||||
|
||||
bfd := FileSystemBogFileData {filename, 0, time.Now(), file}
|
||||
|
||||
return bfd, nil
|
||||
}
|
||||
|
||||
func (f FileSystemBogRepository) Delete(filename string, user_agent_label string) {
|
||||
abs_path := f.absPath(filename, user_agent_label)
|
||||
os.Remove(abs_path)
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
"caj-larsson/bog/domain"
|
||||
)
|
||||
|
||||
type UserAgentDBRecord struct {
|
||||
ID int64
|
||||
Name string
|
||||
LastSeen string
|
||||
AllowanceSeconds int64
|
||||
QuotaKB int64
|
||||
QuotaUsedKB int64
|
||||
}
|
||||
|
||||
var ErrUnparseableRecord = errors.New("record could not be mapped to entity")
|
||||
|
||||
func (r *UserAgentDBRecord) toEntity() (*domain.UserAgent, error) {
|
||||
lastseen, err := time.Parse(time.RFC3339, r.LastSeen)
|
||||
|
||||
if err != nil {
|
||||
return nil, ErrUnparseableRecord
|
||||
}
|
||||
|
||||
var useragent = new(domain.UserAgent)
|
||||
useragent.ID = r.ID
|
||||
useragent.Name = r.Name
|
||||
useragent.LastSeen = lastseen
|
||||
useragent.AllowanceDuration = time.Duration(r.AllowanceSeconds * int64(time.Second))
|
||||
useragent.FileQuota = domain.FileSizeQuota { r.QuotaKB, r.QuotaUsedKB }
|
||||
return useragent, err
|
||||
}
|
||||
|
||||
func fromEntity(useragent domain.UserAgent) (*UserAgentDBRecord, error) {
|
||||
var record = new(UserAgentDBRecord)
|
||||
record.ID = useragent.ID
|
||||
record.Name = useragent.Name
|
||||
record.LastSeen = useragent.LastSeen.Format(time.RFC3339)
|
||||
record.AllowanceSeconds = int64(useragent.AllowanceDuration.Seconds())
|
||||
record.QuotaKB = useragent.FileQuota.AllowanceKB
|
||||
record.QuotaUsedKB = useragent.FileQuota.CurrentUsage
|
||||
return record, nil
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"testing"
|
||||
// "fmt"
|
||||
// "net/http/httptest"
|
||||
// "net/http"
|
||||
|
||||
// "strings"
|
||||
// "time"
|
||||
// "caj-larsson/bog/domain_dataswamp"
|
||||
|
||||
)
|
||||
|
||||
func TestApplication(t *testing.T) {
|
||||
|
||||
// file_service := domain_dataswamp.NewBogFileService(
|
||||
// mock.NewMockUserAgentRepository(),
|
||||
// mock.NewMockFileRepository(),
|
||||
// 1000,
|
||||
// time.Hour,
|
||||
// )
|
||||
|
||||
// bog := Bog {
|
||||
// router: new(http.ServeMux),
|
||||
// file_service: file_service,
|
||||
// address: "fake",
|
||||
// }
|
||||
// bog.routes()
|
||||
// req := httptest.NewRequest("POST", "/apath", strings.NewReader("testdata"))
|
||||
// req.Header.Add("User-Agent", "testingclient")
|
||||
// req.Header.Add("Content-Length", "8")
|
||||
// w := httptest.NewRecorder()
|
||||
// bog.router.ServeHTTP(w, req)
|
||||
|
||||
// if (w.Code != 200){
|
||||
// fmt.Printf("%v", w)
|
||||
// t.Error("not ok")
|
||||
// }
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package application
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -1,4 +1,4 @@
|
||||
package application
|
||||
package server
|
||||
|
||||
|
||||
import (
|
@ -1,94 +0,0 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"time"
|
||||
"bytes"
|
||||
"testing"
|
||||
"caj-larsson/bog/domain"
|
||||
"caj-larsson/bog/test/mock"
|
||||
)
|
||||
|
||||
|
||||
var file_ref1 = domain.FileReference { "path1", "ua1" }
|
||||
var file_ref2 = domain.FileReference { "path1", "ua2" }
|
||||
var file_ref3 = domain.FileReference { "path2", "ua1" }
|
||||
|
||||
|
||||
func NewTestBogFileService() domain.BogFileService {
|
||||
file_repo := mock.NewMockFileRepository()
|
||||
ua_repo := mock.NewMockUserAgentRepository()
|
||||
return domain.NewBogFileService(ua_repo, file_repo, 1024, time.Hour)
|
||||
}
|
||||
|
||||
func TestFileDontExist(t *testing.T) {
|
||||
s := NewTestBogFileService()
|
||||
outfile, err := s.OpenOutFile(file_ref1)
|
||||
|
||||
if outfile != nil && err != domain.ErrNotExists {
|
||||
t.Errorf("File shall not exist by default")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileIsStored(t *testing.T) {
|
||||
s := NewTestBogFileService()
|
||||
|
||||
fakefile := bytes.NewBufferString("My bog data")
|
||||
|
||||
err := s.SaveFile(file_ref1, fakefile, int64(fakefile.Len()))
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("A small file should be writable %s", err)
|
||||
}
|
||||
|
||||
largefakefile := bytes.NewBufferString("")
|
||||
|
||||
for largefakefile.Len() < 64000 {
|
||||
_, err = largefakefile.WriteString("A very repetitive file")
|
||||
}
|
||||
|
||||
err = s.SaveFile(file_ref3, largefakefile, int64(largefakefile.Len()))
|
||||
|
||||
if err != domain.ErrExceedQuota {
|
||||
t.Errorf("too large files should not be excepted")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestFileIsReadBack(t *testing.T) {
|
||||
s := NewTestBogFileService()
|
||||
|
||||
infile := bytes.NewBufferString("My bog data")
|
||||
|
||||
_ = s.SaveFile(file_ref1, infile, int64(infile.Len()))
|
||||
|
||||
outbogfile, _ := s.OpenOutFile(file_ref1)
|
||||
|
||||
outfile := bytes.NewBufferString("")
|
||||
_, _ = outfile.ReadFrom(outbogfile)
|
||||
|
||||
if outfile.String() != "My bog data" {
|
||||
t.Errorf("file corrupted")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestUAIsolation(t *testing.T) {
|
||||
file_repo := mock.NewMockFileRepository()
|
||||
ua_repo := mock.NewMockUserAgentRepository()
|
||||
s := domain.NewBogFileService(ua_repo, file_repo, 1024, time.Hour)
|
||||
|
||||
ua1_file := bytes.NewBufferString("My bog data ua1")
|
||||
ua2_file := bytes.NewBufferString("My bog data ua2")
|
||||
|
||||
_ = s.SaveFile(file_ref1, ua1_file, int64(ua1_file.Len()))
|
||||
_ = s.SaveFile(file_ref2, ua2_file, int64(ua2_file.Len()))
|
||||
|
||||
outbogfile, _ := s.OpenOutFile(file_ref1)
|
||||
|
||||
outfile := bytes.NewBufferString("")
|
||||
_, _ = outfile.ReadFrom(outbogfile)
|
||||
|
||||
if outfile.String() != "My bog data ua1" {
|
||||
t.Errorf("file corrupted")
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"path"
|
||||
"testing"
|
||||
"caj-larsson/bog/domain"
|
||||
"caj-larsson/bog/integration"
|
||||
"caj-larsson/bog/test/mock"
|
||||
)
|
||||
|
||||
func TestFsFileRepo(t *testing.T) {
|
||||
|
||||
var fac = func()domain.FileDataRepository {
|
||||
r := t.TempDir()
|
||||
d := path.Join(r, "fs")
|
||||
repo := integration.FileSystemBogRepository { d }
|
||||
return &repo
|
||||
}
|
||||
|
||||
mock.BogFileRepositoryContract(fac, t)
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"time"
|
||||
"path"
|
||||
"os"
|
||||
// "io"
|
||||
"github.com/spf13/afero"
|
||||
"caj-larsson/bog/domain"
|
||||
)
|
||||
|
||||
|
||||
type MockBogFile struct {
|
||||
filename string
|
||||
file afero.File
|
||||
}
|
||||
|
||||
func (f MockBogFile) Path() string {
|
||||
return f.filename
|
||||
}
|
||||
|
||||
func (f MockBogFile) Size() int64 {
|
||||
stat, _ := f.file.Stat()
|
||||
return int64(stat.Size())
|
||||
}
|
||||
|
||||
func (f MockBogFile) Read(p []byte) (int, error) {
|
||||
return f.file.Read(p)
|
||||
}
|
||||
|
||||
func (f MockBogFile) Write(p []byte) (int, error) {
|
||||
return f.file.Write(p)
|
||||
}
|
||||
|
||||
func (f MockBogFile) Close() error {
|
||||
return f.file.Close()
|
||||
}
|
||||
|
||||
func (f MockBogFile) Seek(offset int64, whence int) (int64, error) {
|
||||
return f.file.Seek(offset, whence)
|
||||
}
|
||||
|
||||
func (f MockBogFile) Modified() time.Time {
|
||||
stat, _ := f.file.Stat()
|
||||
return stat.ModTime()
|
||||
}
|
||||
|
||||
// The actual repository
|
||||
type MockFileRepository struct {
|
||||
fs afero.Fs
|
||||
}
|
||||
|
||||
func NewMockFileRepository() domain.FileDataRepository {
|
||||
return MockFileRepository { afero.NewMemMapFs() }
|
||||
}
|
||||
|
||||
func (r MockFileRepository) Create(filename string, user_agent_label string) (domain.BogInFile, error) {
|
||||
abs_path := path.Join( filename, user_agent_label)
|
||||
dir := path.Dir(abs_path)
|
||||
r.fs.MkdirAll(dir, 0750)
|
||||
file, err := r.fs.OpenFile(abs_path, os.O_RDWR|os.O_CREATE, 0644)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
bf := MockBogFile {filename, file}
|
||||
|
||||
return bf, nil
|
||||
}
|
||||
|
||||
func (r MockFileRepository) Open(filename string, user_agent_label string) (domain.BogOutFile, error) {
|
||||
abs_path := path.Join(filename, user_agent_label)
|
||||
|
||||
dir := path.Dir(abs_path)
|
||||
r.fs.MkdirAll(dir, 0750)
|
||||
file, err := r.fs.OpenFile(abs_path, os.O_RDONLY, 0644)
|
||||
|
||||
if err != nil {
|
||||
return nil, domain.ErrNotExists
|
||||
}
|
||||
|
||||
bf := MockBogFile {filename, file}
|
||||
return bf, nil
|
||||
}
|
||||
|
||||
func (r MockFileRepository) Delete(filename string, user_agent_label string) {
|
||||
abs_path := path.Join(filename, user_agent_label)
|
||||
r.fs.Remove(abs_path)
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"testing"
|
||||
//"caj-larsson/bog/domain"
|
||||
)
|
||||
|
||||
|
||||
func TestMockFileRepo(t *testing.T) {
|
||||
BogFileRepositoryContract(NewMockFileRepository, t)
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
// "time"
|
||||
"caj-larsson/bog/domain"
|
||||
)
|
||||
|
||||
|
||||
type MockUserAgentRepository struct {
|
||||
IdIdx map[int64]*domain.UserAgent
|
||||
NameIdx map[string]*domain.UserAgent
|
||||
NextId int64
|
||||
}
|
||||
|
||||
func NewMockUserAgentRepository() *MockUserAgentRepository {
|
||||
r := new(MockUserAgentRepository)
|
||||
r.NextId = 0
|
||||
r.IdIdx = make(map[int64]*domain.UserAgent)
|
||||
r.NameIdx = make(map[string]*domain.UserAgent)
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *MockUserAgentRepository) Create(useragent domain.UserAgent) (*domain.UserAgent, error) {
|
||||
r.NextId += 1
|
||||
useragent.ID = r.NextId
|
||||
|
||||
r.IdIdx[useragent.ID] = &useragent
|
||||
r.NameIdx[useragent.Name] = &useragent
|
||||
return &useragent, nil
|
||||
}
|
||||
|
||||
|
||||
func (r *MockUserAgentRepository) All() ([]domain.UserAgent, error) {
|
||||
v := make([]domain.UserAgent, 0, len(r.IdIdx))
|
||||
|
||||
for _, value := range r.IdIdx {
|
||||
v = append(v, *value)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
|
||||
func (r *MockUserAgentRepository) GetByName(name string) (*domain.UserAgent, error) {
|
||||
useragent, exists := r.NameIdx[name]
|
||||
if exists {
|
||||
return useragent, nil
|
||||
}
|
||||
return nil, domain.ErrNotExists
|
||||
}
|
||||
|
||||
|
||||
func (r *MockUserAgentRepository) Update(id int64, useragent domain.UserAgent) (*domain.UserAgent, error) {
|
||||
original := *r.IdIdx[id]
|
||||
useragent.ID = id
|
||||
r.IdIdx[id] = &useragent
|
||||
r.NameIdx[original.Name] = &useragent
|
||||
return &useragent, nil
|
||||
}
|
||||
|
||||
|
||||
func (r *MockUserAgentRepository) Delete(id int64) error {
|
||||
original := *r.IdIdx[id]
|
||||
delete(r.NameIdx, original.Name)
|
||||
delete(r.IdIdx, original.ID)
|
||||
return nil
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
"caj-larsson/bog/domain"
|
||||
)
|
||||
|
||||
func TestMockUserAgentRepo(t *testing.T) {
|
||||
r := NewMockUserAgentRepository()
|
||||
|
||||
all, err := r.All()
|
||||
|
||||
if len(all) != 0 && err != nil {
|
||||
t.Errorf("New repo should be empty")
|
||||
}
|
||||
|
||||
ua := domain.UserAgent {23, "n1", time.Now(), time.Duration(time.Hour * 3), domain.FileSizeQuota {1000, 0} }
|
||||
|
||||
|
||||
ua1, _ := r.Create(ua)
|
||||
ua.Name = "n2"
|
||||
ua2, _ := r.Create(ua)
|
||||
if ua1 == ua2 {
|
||||
t.Errorf("Must create unique items")
|
||||
}
|
||||
|
||||
all, err = r.All()
|
||||
|
||||
if len(all) != 2 && err != nil {
|
||||
t.Errorf("After adding there should be two Useragent")
|
||||
}
|
||||
|
||||
if ua.ID != 23 {
|
||||
t.Errorf("It does not change the original UserAgent")
|
||||
}
|
||||
|
||||
ua3, _ := r.GetByName("n2")
|
||||
|
||||
if ua3 != ua2 {
|
||||
t.Errorf("It the correct ua is acquired")
|
||||
}
|
||||
|
||||
if r.Delete(ua2.ID) != nil {
|
||||
t.Errorf("Must delete without error")
|
||||
}
|
||||
|
||||
all, err = r.All()
|
||||
if len(all) != 1 && err != nil {
|
||||
t.Errorf("After deleting one there should be one UA ")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue