Compare commits

..

No commits in common. 'd25f03e80c92fe895f50dfd6421cd45ee6de20e4' and 'd1e53c23379f70add4ba59e964c99ca7a0bfd85e' have entirely different histories.

@ -2,16 +2,10 @@ FROM golang:alpine as builder
ENV GO111MODULE=on ENV GO111MODULE=on
ENV USER=appuser # Create the user and group files to run unprivileged
ENV UID=1000 RUN mkdir /user && \
RUN adduser \ echo 'nobody:x:65534:65534:nobody:/:' > /user/passwd && \
--disabled-password \ echo 'nobody:x:65534:' > /user/group
--gecos "" \
--home "/nonexistent" \
--shell "/sbin/nologin" \
--no-create-home \
--uid "${UID}" \
"${USER}"
RUN apk update && apk add --no-cache git ca-certificates tzdata sqlite build-base RUN apk update && apk add --no-cache git ca-certificates tzdata sqlite build-base
RUN mkdir /build RUN mkdir /build
@ -21,13 +15,11 @@ WORKDIR /build
COPY ./ ./ COPY ./ ./
RUN CGO_ENABLED=1 GOOS=linux go build -a -installsuffix cgo -ldflags '-extldflags "-static"' -o bog . RUN CGO_ENABLED=1 GOOS=linux go build -a -installsuffix cgo -ldflags '-extldflags "-static"' -o bog .
FROM scratch AS final FROM golang:alpine AS final
LABEL author="Cajually <me@caj.me>" LABEL author="Cajually <me@caj.me>"
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /etc/passwd /etc/passwd COPY --from=builder /user/group /user/passwd /etc/
COPY --from=builder /etc/group /etc/group
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /build/bog / COPY --from=builder /build/bog /
COPY --from=builder /build/server/views /server/views COPY --from=builder /build/server/views /server/views
@ -35,7 +27,7 @@ COPY --from=builder /build/default.toml /
WORKDIR / WORKDIR /
USER appuser:appuser USER nobody:nobody
ENTRYPOINT ["/bog"] ENTRYPOINT ["/bog"]
EXPOSE 8002 EXPOSE 8002

@ -1,7 +1 @@
package dataswamp package dataswamp
import (
// "caj-larsson/bog/dataswamp/namespace"
)
type AdminService struct{}

@ -0,0 +1,7 @@
package dataswamp
type Logger interface {
Debug(format string, a ...interface{})
Info(format string, a ...interface{})
Warn(format string, a ...interface{})
}

@ -11,6 +11,7 @@ type Namespace struct {
LastSeen time.Time LastSeen time.Time
AllowanceDuration time.Duration AllowanceDuration time.Duration
FileQuota FileSizeQuota FileQuota FileSizeQuota
Usage FileStat
Download FileStat Download FileStat
Upload FileStat Upload FileStat
} }

@ -27,15 +27,13 @@ func basicNamespaceContract(fac func() Repository, t *testing.T) {
FileSizeQuota{1000, 0}, FileSizeQuota{1000, 0},
FileStat{1, 2}, FileStat{1, 2},
FileStat{3, 4}, FileStat{3, 4},
FileStat{5, 6},
} }
ns1, err := r.Create(ns) ns1, _ := r.Create(ns)
is.NoErr(err)
ns.Name = "n2" ns.Name = "n2"
ns2, err := r.Create(ns) ns2, _ := r.Create(ns)
is.NoErr(err) is.True(ns1 != ns2)
is.True(ns1.ID != ns2.ID)
all, err = r.All() all, err = r.All()

@ -1,111 +0,0 @@
package namespace
import (
"caj-larsson/bog/util"
"fmt"
"time"
)
type Clock interface {
Now() time.Time
}
type NamespaceService struct {
repo Repository
outboxes []func(util.Event)
logger util.Logger
clock Clock
default_ttl time.Duration
default_quota_bytes int64
}
func NewNamespaceService(repo Repository, logger util.Logger, clock Clock, default_ttl time.Duration, default_quota_bytes int64) *NamespaceService {
return &NamespaceService{
repo,
nil,
logger,
clock,
default_ttl,
default_quota_bytes,
}
}
func (s *NamespaceService) GetOrCreateNs(name string) *Namespace {
ns, err := s.repo.GetByName(name)
if err == ErrNotExists {
new_ns := Namespace{
0,
name,
s.clock.Now(),
s.default_ttl,
FileSizeQuota{s.default_quota_bytes, 0},
FileStat{0, 0},
FileStat{0, 0},
}
created_ns, err := s.repo.Create(new_ns)
if err != nil {
panic(err)
}
return created_ns
}
if err != nil {
panic(err)
}
return ns
}
func (s *NamespaceService) Wire(reg func(string, util.EventHandler), outbox func(ev util.Event)) {
reg("FileUsed", s.handleFileUsed)
s.outboxes = append(s.outboxes, outbox)
reg("FileUsed", s.handleFileUsed)
reg("FileDeleted", s.handleFileDeleted)
reg("FileRecieved", s.handleFileRecieved)
}
func (s *NamespaceService) All() []Namespace {
nss, err := s.repo.All()
if err != nil {
panic(err)
}
return nss
}
func (s *NamespaceService) handleFileUsed(payload interface{}) {
var payload_s = payload.(struct {
Name string
Size int64
})
fmt.Printf("file used %v\n", payload_s)
ns := s.GetOrCreateNs(payload_s.Name)
ns.FileQuota = ns.FileQuota.Add(payload_s.Size)
s.repo.Update(ns.ID, *ns)
}
func (s *NamespaceService) handleFileDeleted(payload interface{}) {
var payload_s = payload.(struct {
Name string
Size int64
})
fmt.Printf("file deleted %v\n", payload_s)
ns := s.GetOrCreateNs(payload_s.Name)
ns.FileQuota = ns.FileQuota.Add(-payload_s.Size)
fmt.Printf("file usage %v\n", ns.FileQuota)
s.repo.Update(ns.ID, *ns)
}
func (s *NamespaceService) handleFileRecieved(payload interface{}) {
var payload_s = payload.(struct {
Name string
Size int64
})
fmt.Printf("file recieved %v\n", payload_s)
ns := s.GetOrCreateNs(payload_s.Name)
ns.FileQuota = ns.FileQuota.Add(payload_s.Size)
fmt.Printf("file usage %v\n", ns.FileQuota)
s.repo.Update(ns.ID, *ns)
}

@ -1,38 +0,0 @@
package namespace
import (
"caj-larsson/bog/util"
"testing"
)
func TestEventTest(t *testing.T) {
eb := util.NewEventBus()
svc := NamespaceService{}
svc.Wire(eb.Register, eb.Handle)
events := []util.Event{
*util.NewEvent("FileUsed", struct {
Name string
Size int64
}{
"asd",
int64(12),
}),
*util.NewEvent("FileDeleted", struct {
Name string
Size int64
}{
"asd",
int64(12),
}),
*util.NewEvent("FileRecieved", struct {
Name string
Size int64
}{
"asd",
int64(12),
}),
}
util.AcceptsMessage(t, eb, events)
}

@ -13,18 +13,28 @@ func (f FileSizeQuota) Remaining() int64 {
return f.AllowanceKB - f.CurrentUsage return f.AllowanceKB - f.CurrentUsage
} }
func (f FileSizeQuota) Add(size int64) FileSizeQuota { func (f FileSizeQuota) Add(size int64) (*FileSizeQuota, error) {
return FileSizeQuota{ if !f.Allows(size) {
return nil, ErrExceedQuota
}
n := FileSizeQuota {
f.AllowanceKB, f.AllowanceKB,
f.CurrentUsage + size, f.CurrentUsage + size,
} }
return &n, nil
} }
func (f FileSizeQuota) Remove(size int64) FileSizeQuota { func (f FileSizeQuota) Remove(size int64) (*FileSizeQuota, error) {
return FileSizeQuota{ if size > f.CurrentUsage {
return nil, ErrQuotaInvalid
}
n := FileSizeQuota {
f.AllowanceKB, f.AllowanceKB,
f.CurrentUsage - size, f.CurrentUsage - size,
} }
return &n, nil
} }
type FileStat struct { type FileStat struct {

@ -15,9 +15,27 @@ func TestQuota(t *testing.T) {
func TestQuotaManipulation(t *testing.T) { func TestQuotaManipulation(t *testing.T) {
is := is.New(t) is := is.New(t)
quota := FileSizeQuota{1000, 0} quota := &FileSizeQuota{1000, 0}
quota = quota.Add(500) quota, err := quota.Add(500)
is.NoErr(err)
is.Equal(quota.CurrentUsage, int64(500)) is.Equal(quota.CurrentUsage, int64(500))
quota = quota.Remove(1000)
is.Equal(quota.CurrentUsage, int64(-500)) quota, err = quota.Add(500)
is.NoErr(err)
_ , err = quota.Add(1)
is.Equal(err, ErrExceedQuota)
is.Equal(quota.CurrentUsage, int64(1000))
_ , err = quota.Remove(1001)
is.Equal(err, ErrQuotaInvalid)
is.Equal(quota.CurrentUsage, int64(1000))
quota, err = quota.Remove(1000)
is.NoErr(err)
is.Equal(quota.CurrentUsage, int64(0))
} }

@ -3,7 +3,6 @@ package dataswamp
import ( import (
"caj-larsson/bog/dataswamp/namespace" "caj-larsson/bog/dataswamp/namespace"
"caj-larsson/bog/dataswamp/swampfile" "caj-larsson/bog/dataswamp/swampfile"
"caj-larsson/bog/util"
"io" "io"
"strconv" "strconv"
"time" "time"
@ -11,29 +10,56 @@ import (
// "fmt" // "fmt"
) )
type DataSwampService struct { type SwampFileService struct {
ns_svc namespace.NamespaceService namespace_repo namespace.Repository
swamp_file_repo swampfile.Repository swamp_file_repo swampfile.Repository
logger util.Logger default_allowance_bytes int64
eventBus util.EventBus default_allowance_duration time.Duration
logger Logger
} }
func NewDataSwampService( func NewSwampFileService(
ns_svc namespace.NamespaceService, namespace_repo namespace.Repository,
swamp_file_repo swampfile.Repository, swamp_file_repo swampfile.Repository,
logger util.Logger, da_bytes int64,
) *DataSwampService { da_duration time.Duration,
s := DataSwampService{ns_svc, swamp_file_repo, logger, *util.NewEventBus()} logger Logger,
ns_svc.Wire(s.eventBus.Register, s.eventBus.Handle) ) SwampFileService {
return &s return SwampFileService{namespace_repo, swamp_file_repo, da_bytes, da_duration, logger}
} }
func (s DataSwampService) NamespaceStats() []namespace.Namespace { func (s SwampFileService) getOrCreateNs(namespace_in string) *namespace.Namespace {
return s.ns_svc.All() 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},
namespace.FileStat{0, 0},
namespace.FileStat{0, 0},
namespace.FileStat{0, 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 DataSwampService) SaveFile(ref swampfile.FileReference, src io.Reader, size int64) error { func (s SwampFileService) SaveFile(ref swampfile.FileReference, src io.Reader, size int64) error {
ns := s.ns_svc.GetOrCreateNs(ref.UserAgent) ns := s.getOrCreateNs(ref.UserAgent)
r, err := ref.Clean(true) r, err := ref.Clean(true)
@ -48,23 +74,13 @@ func (s DataSwampService) SaveFile(ref swampfile.FileReference, src io.Reader, s
f, err := s.swamp_file_repo.Create(r.Path, strconv.FormatInt(ns.ID, 10)) f, err := s.swamp_file_repo.Create(r.Path, strconv.FormatInt(ns.ID, 10))
if err != nil { if err != nil {
// TODO: convert this into a different error.
return err return err
} }
s.eventBus.Handle(*util.NewEvent("FileUsed", struct {
Name string
Size int64
}{
ns.Name,
f.Size(),
}))
// TODO: rewrite this into an interruptable loop that emits downloaded events
written, err := io.CopyN(f, src, size) written, err := io.CopyN(f, src, size)
if written < size { if written < size {
s.swamp_file_repo.Delete(r.Path, strconv.FormatInt(ns.ID, 10)) // s.swamp_file_repo.Delete(r.Path, strconv.FormatInt(ns.ID, 10))
return swampfile.ErrContentSizeExaggerated return swampfile.ErrContentSizeExaggerated
} }
@ -78,19 +94,20 @@ func (s DataSwampService) SaveFile(ref swampfile.FileReference, src io.Reader, s
} }
f.Close() f.Close()
s.eventBus.Handle(*util.NewEvent("FileRecieved", struct { uq, err := ns.FileQuota.Add(size)
Name string if err != nil {
Size int64 return err
}{ }
ns.Name, ns.FileQuota = *uq
written, ns.Usage = ns.Usage.Add(size)
})) ns.Upload = ns.Upload.Add(size)
s.namespace_repo.Update(ns.ID, *ns)
return nil return nil
} }
func (s DataSwampService) OpenOutFile(ref swampfile.FileReference) (swampfile.SwampOutFile, error) { func (s SwampFileService) OpenOutFile(ref swampfile.FileReference) (swampfile.SwampOutFile, error) {
ns := s.ns_svc.GetOrCreateNs(ref.UserAgent) ns := s.getOrCreateNs(ref.UserAgent)
r, err := ref.Clean(true) r, err := ref.Clean(true)
@ -104,31 +121,41 @@ func (s DataSwampService) OpenOutFile(ref swampfile.FileReference) (swampfile.Sw
return nil, err return nil, err
} }
s.eventBus.Handle(*util.NewEvent("FileSent", f.Size())) ns.Download = ns.Download.Add(f.Size())
s.namespace_repo.Update(ns.ID, *ns)
return f, nil return f, nil
} }
func (s DataSwampService) CleanUpExpiredFiles() error { func (s SwampFileService) NamespaceStats() ([]namespace.Namespace, error) {
return s.namespace_repo.All()
}
func (s SwampFileService) CleanUpExpiredFiles() error {
s.logger.Info("Cleaning up expired files") s.logger.Info("Cleaning up expired files")
nss, err := s.namespace_repo.All()
if err != nil {
return err
}
for _, ns := range s.ns_svc.All() { for _, ns := range nss {
expiry := time.Now().Add(-ns.AllowanceDuration) expiry := time.Now().Add(-ns.AllowanceDuration)
dfs, err := s.swamp_file_repo.DeleteOlderThan(strconv.FormatInt(ns.ID, 10), expiry) dfs, err := s.swamp_file_repo.DeleteOlderThan(strconv.FormatInt(ns.ID, 10), expiry)
for _, df := range dfs {
dq, err := ns.FileQuota.Remove(df.Size)
if err != nil { if err != nil {
panic(err) dq.CurrentUsage = 0
}
ns.FileQuota = *dq
} }
for _, df := range dfs { if err != nil {
s.eventBus.Handle(*util.NewEvent("FileDeleted", struct { panic(err)
Name string
Size int64
}{
ns.Name,
df.Size,
}))
} }
s.namespace_repo.Update(ns.ID, ns)
} }
return nil return nil
} }

@ -2,16 +2,14 @@ package dataswamp
import ( import (
"bytes" "bytes"
"caj-larsson/bog/dataswamp/namespace"
"caj-larsson/bog/dataswamp/swampfile" "caj-larsson/bog/dataswamp/swampfile"
m_namespace "caj-larsson/bog/infrastructure/memory/namespace"
m_swampfile "caj-larsson/bog/infrastructure/memory/swampfile"
"caj-larsson/bog/infrastructure/system_time"
"fmt"
"github.com/matryer/is" "github.com/matryer/is"
"github.com/spf13/afero" "github.com/spf13/afero"
"testing" "testing"
"time" "time"
// "caj-larsson/bog/dataswamp/namespace"
m_namespace "caj-larsson/bog/infrastructure/memory/namespace"
m_swampfile "caj-larsson/bog/infrastructure/memory/swampfile"
) )
type TestLogger struct{} type TestLogger struct{}
@ -24,25 +22,15 @@ var file_ref1 = swampfile.FileReference{"/path1", "ns1"}
var file_ref2 = swampfile.FileReference{"/path1", "ns2"} var file_ref2 = swampfile.FileReference{"/path1", "ns2"}
var file_ref3 = swampfile.FileReference{"/path2", "ns1"} var file_ref3 = swampfile.FileReference{"/path2", "ns1"}
func NewTestDataSwampService() DataSwampService { func NewTestSwampFileService() SwampFileService {
file_repo := m_swampfile.NewRepository() file_repo := m_swampfile.NewRepository()
ns_repo := m_namespace.NewRepository() ns_repo := m_namespace.NewRepository()
return NewSwampFileService(ns_repo, file_repo, 1024, time.Hour, TestLogger{})
logger := TestLogger{}
ns_svc := namespace.NewNamespaceService(
ns_repo,
logger,
system_time.Clock{},
time.Hour,
1024,
)
return *NewDataSwampService(*ns_svc, file_repo, logger)
} }
func TestFileDontExist(t *testing.T) { func TestFileDontExist(t *testing.T) {
is := is.New(t) is := is.New(t)
s := NewTestDataSwampService() s := NewTestSwampFileService()
outfile, err := s.OpenOutFile(file_ref1) outfile, err := s.OpenOutFile(file_ref1)
is.True(err == swampfile.ErrNotExists) is.True(err == swampfile.ErrNotExists)
@ -51,7 +39,7 @@ func TestFileDontExist(t *testing.T) {
func TestFileIsStored(t *testing.T) { func TestFileIsStored(t *testing.T) {
is := is.New(t) is := is.New(t)
s := NewTestDataSwampService() s := NewTestSwampFileService()
fakefile := bytes.NewBufferString("My bog data") fakefile := bytes.NewBufferString("My bog data")
@ -72,7 +60,7 @@ func TestFileIsStored(t *testing.T) {
func TestFileIsReadBack(t *testing.T) { func TestFileIsReadBack(t *testing.T) {
is := is.New(t) is := is.New(t)
s := NewTestDataSwampService() s := NewTestSwampFileService()
infile := bytes.NewBufferString("My bog data") infile := bytes.NewBufferString("My bog data")
@ -88,7 +76,7 @@ func TestFileIsReadBack(t *testing.T) {
func TestNSIsolation(t *testing.T) { func TestNSIsolation(t *testing.T) {
is := is.New(t) is := is.New(t)
s := NewTestDataSwampService() s := NewTestSwampFileService()
ns1_file := bytes.NewBufferString("My bog data ns1") ns1_file := bytes.NewBufferString("My bog data ns1")
ns2_file := bytes.NewBufferString("My bog data ns2") ns2_file := bytes.NewBufferString("My bog data ns2")
@ -106,7 +94,7 @@ func TestNSIsolation(t *testing.T) {
func TestPathStrictMode(t *testing.T) { func TestPathStrictMode(t *testing.T) {
is := is.New(t) is := is.New(t)
s := NewTestDataSwampService() s := NewTestSwampFileService()
ns_file := bytes.NewBufferString("My bog data ns1") ns_file := bytes.NewBufferString("My bog data ns1")
@ -126,7 +114,7 @@ func TestPathStrictMode(t *testing.T) {
func TestQuotaWithContenSizeLieOver(t *testing.T) { func TestQuotaWithContenSizeLieOver(t *testing.T) {
is := is.New(t) is := is.New(t)
s := NewTestDataSwampService() s := NewTestSwampFileService()
largefakefile := bytes.NewBufferString("") largefakefile := bytes.NewBufferString("")
@ -141,7 +129,7 @@ func TestQuotaWithContenSizeLieOver(t *testing.T) {
func TestQuotaWithContenSizeLieUnder(t *testing.T) { func TestQuotaWithContenSizeLieUnder(t *testing.T) {
is := is.New(t) is := is.New(t)
s := NewTestDataSwampService() s := NewTestSwampFileService()
largefakefile := bytes.NewBufferString("small") largefakefile := bytes.NewBufferString("small")
@ -156,32 +144,22 @@ func TestCleanUpExpired(t *testing.T) {
fs := afero.NewMemMapFs() fs := afero.NewMemMapFs()
file_repo := m_swampfile.Repository{fs} file_repo := m_swampfile.Repository{fs}
ns_repo := m_namespace.NewRepository() ns_repo := m_namespace.NewRepository()
logger := TestLogger{} s := NewSwampFileService(ns_repo, file_repo, 1024, time.Hour, TestLogger{})
ns_svc := namespace.NewNamespaceService(
ns_repo, fakefile := bytes.NewBufferString("My bog data")
logger,
system_time.Clock{},
time.Hour,
1024,
)
s := NewDataSwampService(*ns_svc, file_repo, logger)
fakefile := bytes.NewBufferString("My bog")
err := s.SaveFile(file_ref1, fakefile, int64(fakefile.Len())) err := s.SaveFile(file_ref1, fakefile, int64(fakefile.Len()))
is.NoErr(err) is.NoErr(err)
fakefile = bytes.NewBufferString("My bog") fakefile = bytes.NewBufferString("My bog data")
err = s.SaveFile(file_ref3, fakefile, int64(fakefile.Len())) err = s.SaveFile(file_ref3, fakefile, int64(fakefile.Len()))
is.NoErr(err) is.NoErr(err)
err = fs.Chtimes("1/path1", time.Now(), time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)) err = fs.Chtimes("1/path1", time.Now(), time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC))
is.NoErr(err)
is.NoErr(s.CleanUpExpiredFiles()) is.NoErr(s.CleanUpExpiredFiles())
ns, err := ns_repo.GetByName("ns1") ns, err := ns_repo.GetByName("ns1")
fmt.Printf("file final usage %v\n", ns.FileQuota)
is.NoErr(err) is.NoErr(err)
fmt.Printf("file\n")
is.Equal(ns.FileQuota.CurrentUsage, int64(len("My bog"))) is.Equal(ns.FileQuota.CurrentUsage, int64(len("My bog data")))
} }

@ -43,7 +43,7 @@ func (fr *FileReference) Clean(strict bool) (*FileReference, error) {
return nil, ErrUnacceptablePath return nil, ErrUnacceptablePath
} }
n := FileReference{ n := FileReference {
c, c,
fr.UserAgent, fr.UserAgent,
} }

@ -48,13 +48,6 @@ func (f FileSystemSwampFileData) Modified() time.Time {
return stat.ModTime() return stat.ModTime()
} }
func (f FileSystemSwampFileData) Truncate() {
err := f.file.Truncate(0)
if err != nil {
panic(err)
}
}
type Repository struct { type Repository struct {
Root string Root string
} }
@ -105,7 +98,6 @@ func (f Repository) Create(filename string, namespace_ns string) (swampfile.Swam
if err != nil { if err != nil {
panic(err) panic(err)
} }
file.Truncate(0)
bfd := FileSystemSwampFileData{filename, stat_info.Size(), stat_info.ModTime(), file} bfd := FileSystemSwampFileData{filename, stat_info.Size(), stat_info.ModTime(), file}

@ -44,13 +44,6 @@ func (f SwampFile) Modified() time.Time {
return stat.ModTime() return stat.ModTime()
} }
func (f SwampFile) Truncate() {
err := f.file.Truncate(0)
if err != nil {
panic(err)
}
}
// The actual repository // The actual repository
type Repository struct { type Repository struct {
Fs afero.Fs Fs afero.Fs

@ -21,6 +21,7 @@ type Namespace struct {
AllowanceTime sql.NullInt64 AllowanceTime sql.NullInt64
QuotaKb sql.NullInt64 QuotaKb sql.NullInt64
QuotaUsageKb sql.NullInt64 QuotaUsageKb sql.NullInt64
UsageID int64
DownloadID int64 DownloadID int64
UploadID int64 UploadID int64
} }

@ -6,10 +6,11 @@ INSERT INTO
allowance_time, allowance_time,
quota_kb, quota_kb,
quota_usage_kb, quota_usage_kb,
usage_id,
download_id, download_id,
upload_id upload_id
) )
values(?, ?, ?, ?, ?, ?, ?) values(?, ?, ?, ?, ?, ?, ?, ?)
returning id; returning id;
-- name: CreateFileStats :one -- name: CreateFileStats :one
@ -25,11 +26,15 @@ SELECT
ns.allowance_time, ns.allowance_time,
ns.quota_kb, ns.quota_kb,
ns.quota_usage_kb, ns.quota_usage_kb,
u.num as u_num,
u.size_b as u_size_b,
d.num as d_num, d.num as d_num,
d.size_b as d_size_b, d.size_b as d_size_b,
ul.num as ul_num, ul.num as ul_num,
ul.size_b as ul_size_b ul.size_b as ul_size_b
FROM namespace as ns FROM namespace as ns
JOIN file_stats as u
ON ns.usage_id = u.id
JOIN file_stats as d JOIN file_stats as d
ON ns.download_id = d.id ON ns.download_id = d.id
JOIN file_stats as ul JOIN file_stats as ul
@ -43,11 +48,15 @@ SELECT
ns.allowance_time, ns.allowance_time,
ns.quota_kb, ns.quota_kb,
ns.quota_usage_kb, ns.quota_usage_kb,
u.num as u_num,
u.size_b as u_size_b,
d.num as d_num, d.num as d_num,
d.size_b as d_size_b, d.size_b as d_size_b,
ul.num as ul_num, ul.num as ul_num,
ul.size_b as ul_size_b ul.size_b as ul_size_b
FROM namespace as ns FROM namespace as ns
JOIN file_stats as u
ON ns.usage_id = u.id
JOIN file_stats as d JOIN file_stats as d
ON ns.download_id = d.id ON ns.download_id = d.id
JOIN file_stats as ul JOIN file_stats as ul
@ -69,7 +78,7 @@ UPDATE namespace SET
quota_kb = ?, quota_kb = ?,
quota_usage_kb = ? quota_usage_kb = ?
WHERE id = ? WHERE id = ?
RETURNING download_id, upload_id; RETURNING usage_id, download_id, upload_id;
-- name: DeleteNameSpace :exec -- name: DeleteNameSpace :exec
DELETE FROM namespace where id = ?; DELETE FROM namespace where id = ?;

@ -18,11 +18,15 @@ SELECT
ns.allowance_time, ns.allowance_time,
ns.quota_kb, ns.quota_kb,
ns.quota_usage_kb, ns.quota_usage_kb,
u.num as u_num,
u.size_b as u_size_b,
d.num as d_num, d.num as d_num,
d.size_b as d_size_b, d.size_b as d_size_b,
ul.num as ul_num, ul.num as ul_num,
ul.size_b as ul_size_b ul.size_b as ul_size_b
FROM namespace as ns FROM namespace as ns
JOIN file_stats as u
ON ns.usage_id = u.id
JOIN file_stats as d JOIN file_stats as d
ON ns.download_id = d.id ON ns.download_id = d.id
JOIN file_stats as ul JOIN file_stats as ul
@ -36,6 +40,8 @@ type AllNamespacesRow struct {
AllowanceTime sql.NullInt64 AllowanceTime sql.NullInt64
QuotaKb sql.NullInt64 QuotaKb sql.NullInt64
QuotaUsageKb sql.NullInt64 QuotaUsageKb sql.NullInt64
UNum int64
USizeB int64
DNum int64 DNum int64
DSizeB int64 DSizeB int64
UlNum int64 UlNum int64
@ -58,6 +64,8 @@ func (q *Queries) AllNamespaces(ctx context.Context) ([]AllNamespacesRow, error)
&i.AllowanceTime, &i.AllowanceTime,
&i.QuotaKb, &i.QuotaKb,
&i.QuotaUsageKb, &i.QuotaUsageKb,
&i.UNum,
&i.USizeB,
&i.DNum, &i.DNum,
&i.DSizeB, &i.DSizeB,
&i.UlNum, &i.UlNum,
@ -102,10 +110,11 @@ INSERT INTO
allowance_time, allowance_time,
quota_kb, quota_kb,
quota_usage_kb, quota_usage_kb,
usage_id,
download_id, download_id,
upload_id upload_id
) )
values(?, ?, ?, ?, ?, ?, ?) values(?, ?, ?, ?, ?, ?, ?, ?)
returning id returning id
` `
@ -115,6 +124,7 @@ type CreateNamespaceParams struct {
AllowanceTime sql.NullInt64 AllowanceTime sql.NullInt64
QuotaKb sql.NullInt64 QuotaKb sql.NullInt64
QuotaUsageKb sql.NullInt64 QuotaUsageKb sql.NullInt64
UsageID int64
DownloadID int64 DownloadID int64
UploadID int64 UploadID int64
} }
@ -126,6 +136,7 @@ func (q *Queries) CreateNamespace(ctx context.Context, arg CreateNamespaceParams
arg.AllowanceTime, arg.AllowanceTime,
arg.QuotaKb, arg.QuotaKb,
arg.QuotaUsageKb, arg.QuotaUsageKb,
arg.UsageID,
arg.DownloadID, arg.DownloadID,
arg.UploadID, arg.UploadID,
) )
@ -162,11 +173,15 @@ SELECT
ns.allowance_time, ns.allowance_time,
ns.quota_kb, ns.quota_kb,
ns.quota_usage_kb, ns.quota_usage_kb,
u.num as u_num,
u.size_b as u_size_b,
d.num as d_num, d.num as d_num,
d.size_b as d_size_b, d.size_b as d_size_b,
ul.num as ul_num, ul.num as ul_num,
ul.size_b as ul_size_b ul.size_b as ul_size_b
FROM namespace as ns FROM namespace as ns
JOIN file_stats as u
ON ns.usage_id = u.id
JOIN file_stats as d JOIN file_stats as d
ON ns.download_id = d.id ON ns.download_id = d.id
JOIN file_stats as ul JOIN file_stats as ul
@ -181,6 +196,8 @@ type GetNamespaceByNameRow struct {
AllowanceTime sql.NullInt64 AllowanceTime sql.NullInt64
QuotaKb sql.NullInt64 QuotaKb sql.NullInt64
QuotaUsageKb sql.NullInt64 QuotaUsageKb sql.NullInt64
UNum int64
USizeB int64
DNum int64 DNum int64
DSizeB int64 DSizeB int64
UlNum int64 UlNum int64
@ -197,6 +214,8 @@ func (q *Queries) GetNamespaceByName(ctx context.Context, name string) (GetNames
&i.AllowanceTime, &i.AllowanceTime,
&i.QuotaKb, &i.QuotaKb,
&i.QuotaUsageKb, &i.QuotaUsageKb,
&i.UNum,
&i.USizeB,
&i.DNum, &i.DNum,
&i.DSizeB, &i.DSizeB,
&i.UlNum, &i.UlNum,
@ -228,7 +247,7 @@ UPDATE namespace SET
quota_kb = ?, quota_kb = ?,
quota_usage_kb = ? quota_usage_kb = ?
WHERE id = ? WHERE id = ?
RETURNING download_id, upload_id RETURNING usage_id, download_id, upload_id
` `
type UpdateNamespaceParams struct { type UpdateNamespaceParams struct {
@ -241,6 +260,7 @@ type UpdateNamespaceParams struct {
} }
type UpdateNamespaceRow struct { type UpdateNamespaceRow struct {
UsageID int64
DownloadID int64 DownloadID int64
UploadID int64 UploadID int64
} }
@ -255,6 +275,6 @@ func (q *Queries) UpdateNamespace(ctx context.Context, arg UpdateNamespaceParams
arg.ID, arg.ID,
) )
var i UpdateNamespaceRow var i UpdateNamespaceRow
err := row.Scan(&i.DownloadID, &i.UploadID) err := row.Scan(&i.UsageID, &i.DownloadID, &i.UploadID)
return i, err return i, err
} }

@ -34,9 +34,10 @@ CREATE TABLE IF NOT EXISTS namespace(
allowance_time BIGINT, allowance_time BIGINT,
quota_kb BIGINT, quota_kb BIGINT,
quota_usage_kb BIGINT, quota_usage_kb BIGINT,
usage_id BIGINT NOT NULL REFERENCES file_stats(Id),
download_id BIGINT NOT NULL REFERENCES file_stats(Id), download_id BIGINT NOT NULL REFERENCES file_stats(Id),
upload_id BIGINT NOT NULL REFERENCES file_stats(Id) upload_id BIGINT NOT NULL REFERENCES file_stats(Id)
);` );`
_, err = r.db.Exec(query) _, err = r.db.Exec(query)
return err return err
@ -63,6 +64,12 @@ func (q *Queries) createFileStats(ctx context.Context, fstat namespace.FileStat)
func (r *Repository) Create(ns namespace.Namespace) (*namespace.Namespace, error) { func (r *Repository) Create(ns namespace.Namespace) (*namespace.Namespace, error) {
ctx := context.Background() ctx := context.Background()
q := New(r.db) q := New(r.db)
u_id, err := q.createFileStats(ctx, ns.Usage)
if err != nil {
return nil, err
}
dl_id, err := q.createFileStats(ctx, ns.Download) dl_id, err := q.createFileStats(ctx, ns.Download)
if err != nil { if err != nil {
@ -83,6 +90,7 @@ func (r *Repository) Create(ns namespace.Namespace) (*namespace.Namespace, error
AllowanceTime: sql.NullInt64{int64(ns.AllowanceDuration.Seconds()), true}, AllowanceTime: sql.NullInt64{int64(ns.AllowanceDuration.Seconds()), true},
QuotaKb: sql.NullInt64{int64(ns.FileQuota.AllowanceKB), true}, QuotaKb: sql.NullInt64{int64(ns.FileQuota.AllowanceKB), true},
QuotaUsageKb: sql.NullInt64{int64(ns.FileQuota.CurrentUsage), true}, QuotaUsageKb: sql.NullInt64{int64(ns.FileQuota.CurrentUsage), true},
UsageID: u_id,
DownloadID: dl_id, DownloadID: dl_id,
UploadID: ul_id, UploadID: ul_id,
} }
@ -114,6 +122,7 @@ func (r *Repository) All() ([]namespace.Namespace, error) {
time.UnixMicro(row.Lastseen), time.UnixMicro(row.Lastseen),
time.Duration(row.AllowanceTime.Int64 * int64(time.Second)), time.Duration(row.AllowanceTime.Int64 * int64(time.Second)),
namespace.FileSizeQuota{row.QuotaKb.Int64, row.QuotaUsageKb.Int64}, namespace.FileSizeQuota{row.QuotaKb.Int64, row.QuotaUsageKb.Int64},
namespace.FileStat{row.UNum, row.USizeB},
namespace.FileStat{row.DNum, row.DSizeB}, namespace.FileStat{row.DNum, row.DSizeB},
namespace.FileStat{row.UlNum, row.UlSizeB}, namespace.FileStat{row.UlNum, row.UlSizeB},
} }
@ -139,6 +148,7 @@ func (r *Repository) GetByName(name string) (*namespace.Namespace, error) {
time.UnixMicro(row.Lastseen), time.UnixMicro(row.Lastseen),
time.Duration(row.AllowanceTime.Int64 * int64(time.Second)), time.Duration(row.AllowanceTime.Int64 * int64(time.Second)),
namespace.FileSizeQuota{row.QuotaKb.Int64, row.QuotaUsageKb.Int64}, namespace.FileSizeQuota{row.QuotaKb.Int64, row.QuotaUsageKb.Int64},
namespace.FileStat{row.UNum, row.USizeB},
namespace.FileStat{row.DNum, row.DSizeB}, namespace.FileStat{row.DNum, row.DSizeB},
namespace.FileStat{row.UlNum, row.UlSizeB}, namespace.FileStat{row.UlNum, row.UlSizeB},
} }
@ -163,6 +173,11 @@ func (r *Repository) Update(id int64, ns namespace.Namespace) (*namespace.Namesp
ns.ID, ns.ID,
}) })
err = q.updateFileStat(ctx, ids.UsageID, ns.Usage)
if err != nil {
return nil, err
}
err = q.updateFileStat(ctx, ids.DownloadID, ns.Download) err = q.updateFileStat(ctx, ids.DownloadID, ns.Download)
if err != nil { if err != nil {
return nil, err return nil, err

@ -11,6 +11,7 @@ CREATE TABLE IF NOT EXISTS namespace(
allowance_time BIGINT, allowance_time BIGINT,
quota_kb BIGINT, quota_kb BIGINT,
quota_usage_kb BIGINT, quota_usage_kb BIGINT,
usage_id BIGINT NOT NULL REFERENCES file_stats(Id) ON DELETE CASCADE,
download_id BIGINT NOT NULL REFERENCES file_stats(Id) ON DELETE CASCADE, download_id BIGINT NOT NULL REFERENCES file_stats(Id) ON DELETE CASCADE,
upload_id BIGINT NOT NULL REFERENCES file_stats(Id) ON DELETE CASCADE upload_id BIGINT NOT NULL REFERENCES file_stats(Id) ON DELETE CASCADE
); );

@ -1,9 +0,0 @@
package system_time
import "time"
type Clock struct{}
func (c Clock) Now() time.Time {
return time.Now()
}

@ -6,8 +6,6 @@ import (
"caj-larsson/bog/dataswamp/swampfile" "caj-larsson/bog/dataswamp/swampfile"
fs_swampfile "caj-larsson/bog/infrastructure/fs/swampfile" fs_swampfile "caj-larsson/bog/infrastructure/fs/swampfile"
sql_namespace "caj-larsson/bog/infrastructure/sqlite/namespace" sql_namespace "caj-larsson/bog/infrastructure/sqlite/namespace"
"caj-larsson/bog/infrastructure/system_time"
"caj-larsson/bog/util"
"net/http" "net/http"
"strconv" "strconv"
"text/template" "text/template"
@ -22,10 +20,10 @@ type Router interface {
type Bog struct { type Bog struct {
router Router router Router
adminRouter Router adminRouter Router
file_service dataswamp.DataSwampService file_service dataswamp.SwampFileService
address string address string
adminAddress string adminAddress string
logger util.Logger logger dataswamp.Logger
} }
func buildFileDataRepository(config FileConfig) swampfile.Repository { func buildFileDataRepository(config FileConfig) swampfile.Repository {
@ -106,7 +104,7 @@ func (b *Bog) dashboardHandler(w http.ResponseWriter, r *http.Request) {
panic(err) panic(err)
} }
stats := b.file_service.NamespaceStats() stats, _ := b.file_service.NamespaceStats()
err = templ.Execute(w, stats) err = templ.Execute(w, stats)
if err != nil { if err != nil {
@ -116,7 +114,7 @@ func (b *Bog) dashboardHandler(w http.ResponseWriter, r *http.Request) {
func (b *Bog) routes() { func (b *Bog) routes() {
b.router.HandleFunc("/", b.fileHandler) b.router.HandleFunc("/", b.fileHandler)
// b.adminRouter.HandleFunc("/", b.dashboardHandler) b.adminRouter.HandleFunc("/", b.dashboardHandler)
} }
func (b *Bog) cleanNamespaces() { func (b *Bog) cleanNamespaces() {
@ -135,17 +133,11 @@ func New(config *Configuration) *Bog {
nsRepo := buildNamespaceRepository(config.Database) nsRepo := buildNamespaceRepository(config.Database)
logger := ServerLogger{Debug} logger := ServerLogger{Debug}
ns_svc := namespace.NewNamespaceService( b.file_service = dataswamp.NewSwampFileService(
nsRepo, nsRepo,
logger,
system_time.Clock{},
config.Quota.ParsedDuration(),
config.Quota.ParsedSizeBytes(),
)
b.file_service = *dataswamp.NewDataSwampService(
*ns_svc,
fsSwampRepo, fsSwampRepo,
config.Quota.ParsedSizeBytes(),
config.Quota.ParsedDuration(),
logger, logger,
) )
b.logger = logger b.logger = logger
@ -158,6 +150,6 @@ func New(config *Configuration) *Bog {
func (b *Bog) Run() { func (b *Bog) Run() {
b.logger.Info("Starting bog on address: %s", b.address) b.logger.Info("Starting bog on address: %s", b.address)
go b.cleanNamespaces() go b.cleanNamespaces()
go func() { http.ListenAndServe(b.adminAddress, b.adminRouter) }() go func(){ http.ListenAndServe(b.adminAddress, b.adminRouter) }()
http.ListenAndServe(b.address, b.router) http.ListenAndServe(b.address, b.router)
} }

@ -4,10 +4,8 @@ import (
"testing" "testing"
// "fmt" // "fmt"
"caj-larsson/bog/dataswamp" "caj-larsson/bog/dataswamp"
"caj-larsson/bog/dataswamp/namespace" "caj-larsson/bog/infrastructure/memory/namespace"
ns "caj-larsson/bog/infrastructure/memory/namespace"
"caj-larsson/bog/infrastructure/memory/swampfile" "caj-larsson/bog/infrastructure/memory/swampfile"
"caj-larsson/bog/infrastructure/system_time"
"github.com/matryer/is" "github.com/matryer/is"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@ -24,25 +22,17 @@ func (t TestLogger) Warn(format string, a ...interface{}) {}
func TestApplication(t *testing.T) { func TestApplication(t *testing.T) {
is := is.New(t) is := is.New(t)
logger := TestLogger{} logger := TestLogger{}
nsRepo := ns.NewRepository() file_service := dataswamp.NewSwampFileService(
namespace.NewRepository(),
ns_svc := namespace.NewNamespaceService(
nsRepo,
logger,
system_time.Clock{},
time.Hour,
1000,
)
file_service := dataswamp.NewDataSwampService(
*ns_svc,
swampfile.NewRepository(), swampfile.NewRepository(),
1000,
time.Hour,
logger, logger,
) )
bog := Bog{ bog := Bog{
router: new(http.ServeMux), router: new(http.ServeMux),
file_service: *file_service, file_service: file_service,
address: "fake", address: "fake",
logger: logger, logger: logger,
} }

@ -1,41 +0,0 @@
package util
type Event struct {
eventName string
payload interface{}
}
func NewEvent(eventName string, payload interface{}) *Event {
return &Event{
eventName,
payload,
}
}
func (e *Event) EventName() string {
return e.eventName
}
func (e *Event) Payload() interface{} {
return e.payload
}
type EventHandler func(payload interface{})
type EventBus struct {
handlers map[string][]EventHandler
}
func NewEventBus() *EventBus {
return &EventBus{make(map[string][]EventHandler)}
}
func (eb *EventBus) Register(eventName string, handler EventHandler) {
eb.handlers[eventName] = append(eb.handlers[eventName], handler)
}
func (eb *EventBus) Handle(e Event) {
for _, handler := range eb.handlers[e.EventName()] {
handler(e.Payload())
}
}

@ -1,23 +0,0 @@
package util
import (
"github.com/matryer/is"
"testing"
)
func (eb *EventBus) Handled(e Event) bool {
// TODO: figure out how to verify the event signature here.
handlers, exists := eb.handlers[e.EventName()]
if !exists {
return false
}
return len(handlers) > 0
}
func AcceptsMessage(t *testing.T, eb *EventBus, es []Event) {
is := is.New(t)
for _, e := range es {
is.True(eb.Handled(e))
}
}

@ -1,13 +0,0 @@
package util
type Logger interface {
Debug(format string, a ...interface{})
Info(format string, a ...interface{})
Warn(format string, a ...interface{})
}
type TestLogger struct{}
func (t TestLogger) Debug(format string, a ...interface{}) {}
func (t TestLogger) Info(format string, a ...interface{}) {}
func (t TestLogger) Warn(format string, a ...interface{}) {}
Loading…
Cancel
Save