Compare commits

...

2 Commits

4 changed files with 286 additions and 34 deletions

View File

@ -2,6 +2,22 @@
*/}}{{ if .Container }}{{ .Container.Id }}{{ else }}Obaly{{ end }}{{ end }}
{{ define "Content" }}
{{ if .ErrorNoSuchSeries }}
<p>Chyba: Řada neexistuje.
{{ else if .ErrorContainerAlreadyExists }}
<p>Chyba: Obal s tímto ID už existuje.
{{ else if .ErrorNoSuchContainer }}
<p>Chyba: Obal neexistuje.
{{ else if .ErrorCannotChangeSeriesNotEmpty }}
<p>Chyba: Řadu u neprázdných obalů nelze měnit.
{{ else if .ErrorCannotChangeNumber }}
<p>Chyba: Číslo obalu v řadě nelze měnit.
{{ else if .ErrorContainerInUse }}
<p>Chyba: Obal se používá.
{{ else if .Error }}
<p>Chyba: {{ .Error }}
{{ end }}
{{ if .Container }}
<section>

View File

@ -14,6 +14,11 @@ import (
type Series struct {
Prefix string // PK: prefix
Description string // what kind of containers this is for
Counter uint // last used container number
}
func (s *Series) Containers() []*Container {
return indexMembers[s.Prefix]
}
type ContainerId string
@ -60,15 +65,13 @@ var (
dbLog *os.File
indexSeries = map[string]*Series{}
indexMembers = map[string][]*Container{}
indexContainer = map[ContainerId]*Container{}
indexChildren = map[ContainerId][]*Container{}
labelFont *bdf.Font
)
// TODO: Some functions to add, remove and change things in the database.
// Indexes must be kept valid, just like any invariants.
func dbSearchSeries(query string) (result []*Series) {
query = strings.ToLower(query)
added := map[string]bool{}
@ -105,6 +108,134 @@ func dbSearchContainers(query string) (result []*Container) {
return
}
var errInvalidPrefix = errors.New("invalid prefix")
var errSeriesAlreadyExists = errors.New("series already exists")
var errCannotChangePrefix = errors.New("cannot change the prefix")
var errNoSuchSeries = errors.New("no such series")
var errSeriesInUse = errors.New("series is in use")
// Find and filter out the series in O(n).
func filterSeries(slice []*Series, s *Series) (filtered []*Series) {
for _, series := range slice {
if s != series {
filtered = append(filtered, series)
}
}
return
}
func dbSeriesCreate(s *Series) error {
if s.Prefix == "" {
return errInvalidPrefix
}
if _, ok := indexSeries[s.Prefix]; ok {
return errSeriesAlreadyExists
}
db.Series = append(db.Series, s)
indexSeries[s.Prefix] = s
return dbCommit()
}
func dbSeriesUpdate(s *Series, updated Series) error {
// It might be easily possible with no members, though this
// is not reachable from the UI and can be solved by deletion.
if updated.Prefix != s.Prefix {
return errCannotChangePrefix
}
*s = updated
return dbCommit()
}
func dbSeriesRemove(s *Series) error {
if len(s.Containers()) > 0 {
return errSeriesInUse
}
db.Series = filterSeries(db.Series, s)
delete(indexSeries, s.Prefix)
delete(indexMembers, s.Prefix)
return dbCommit()
}
var errContainerAlreadyExists = errors.New("container already exists")
var errNoSuchContainer = errors.New("no such container")
var errCannotChangeSeriesNotEmpty = errors.New(
"cannot change the series of a non-empty container")
var errCannotChangeNumber = errors.New("cannot change the number")
var errContainerInUse = errors.New("container is in use")
// Find and filter out the container in O(n).
func filterContainer(slice []*Container, c *Container) (filtered []*Container) {
for _, container := range slice {
if c != container {
filtered = append(filtered, container)
}
}
return
}
func dbContainerCreate(c *Container) error {
if series, ok := indexSeries[c.Series]; !ok {
return errNoSuchSeries
} else if c.Number == 0 {
c.Number = series.Counter
for {
c.Number++
if _, ok := indexContainer[c.Id()]; !ok {
break
}
}
series.Counter = c.Number
}
if _, ok := indexContainer[c.Id()]; ok {
return errContainerAlreadyExists
}
if c.Parent != "" && indexContainer[c.Parent] == nil {
return errNoSuchContainer
}
db.Containers = append(db.Containers, c)
indexMembers[c.Series] = append(indexMembers[c.Series], c)
indexChildren[c.Parent] = append(indexChildren[c.Parent], c)
indexContainer[c.Id()] = c
return dbCommit()
}
func dbContainerUpdate(c *Container, updated Container) error {
newId := updated.Id()
if updated.Series != c.Series && len(c.Children()) > 0 {
return errCannotChangeSeriesNotEmpty
}
if updated.Number != c.Number {
return errCannotChangeNumber
}
if _, ok := indexContainer[newId]; ok && newId != c.Id() {
return errContainerAlreadyExists
}
if updated.Parent != c.Parent {
indexChildren[c.Parent] = filterContainer(indexChildren[c.Parent], c)
indexChildren[newId] = append(indexChildren[newId], c)
}
*c = updated
return dbCommit()
}
func dbContainerRemove(c *Container) error {
if len(indexChildren[c.Id()]) > 0 {
return errContainerInUse
}
db.Containers = filterContainer(db.Containers, c)
indexMembers[c.Series] = filterContainer(indexMembers[c.Series], c)
indexChildren[c.Parent] = filterContainer(indexChildren[c.Parent], c)
delete(indexContainer, c.Id())
delete(indexChildren, c.Id())
return dbCommit()
}
func dbCommit() error {
// Write a timestamp.
e := json.NewEncoder(dbLog)
@ -184,6 +315,7 @@ func loadDatabase() error {
}
}
indexChildren[pv.Parent] = append(indexChildren[pv.Parent], pv)
indexMembers[pv.Series] = append(indexMembers[pv.Series], pv)
}
// Validate that no container is a parent of itself on any level.

View File

@ -7,6 +7,7 @@ import (
"log"
"math/rand"
"net/http"
"net/url"
"os"
"path/filepath"
"time"
@ -83,11 +84,40 @@ func handleLogout(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/", http.StatusSeeOther)
}
func handleContainer(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
// TODO
func handleContainerPost(r *http.Request) error {
id := ContainerId(r.FormValue("id"))
description := r.FormValue("description")
series := r.FormValue("series")
parent := ContainerId(r.FormValue("parent"))
_, remove := r.Form["remove"]
if container, ok := indexContainer[id]; ok {
if remove {
return dbContainerRemove(container)
} else {
c := *container
c.Description = description
c.Series = series
return dbContainerUpdate(container, c)
}
} else if remove {
return errNoSuchContainer
} else {
return dbContainerCreate(&Container{
Series: series,
Parent: parent,
Description: description,
})
}
if r.Method != http.MethodGet {
}
func handleContainer(w http.ResponseWriter, r *http.Request) {
var err error
if r.Method == http.MethodPost {
err = handleContainerPost(r)
// XXX: This is rather ugly. When removing, we want to keep
// the context id, in addition to the id being changed.
} else if r.Method != http.MethodGet {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
@ -98,40 +128,77 @@ func handleContainer(w http.ResponseWriter, r *http.Request) {
}
var container *Container
children := []*Container{}
children := indexChildren[""]
if id := ContainerId(r.FormValue("id")); id == "" {
children = indexChildren[""]
} else if c, ok := indexContainer[id]; ok {
if c, ok := indexContainer[ContainerId(r.FormValue("id"))]; ok {
children = c.Children()
container = c
}
params := struct {
Container *Container
Children []*Container
AllSeries map[string]string
Error error
ErrorNoSuchSeries bool
ErrorContainerAlreadyExists bool
ErrorNoSuchContainer bool
ErrorCannotChangeSeriesNotEmpty bool
ErrorCannotChangeNumber bool
ErrorContainerInUse bool
Container *Container
Children []*Container
AllSeries map[string]string
}{
Container: container,
Children: children,
AllSeries: allSeries,
Error: err,
ErrorNoSuchSeries: err == errNoSuchSeries,
ErrorContainerAlreadyExists: err == errContainerAlreadyExists,
ErrorNoSuchContainer: err == errNoSuchContainer,
ErrorCannotChangeSeriesNotEmpty: err == errCannotChangeSeriesNotEmpty,
ErrorCannotChangeNumber: err == errCannotChangeNumber,
ErrorContainerInUse: err == errContainerInUse,
Container: container,
Children: children,
AllSeries: allSeries,
}
executeTemplate("container.tmpl", w, &params)
}
func handleSeries(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
// TODO
func handleSeriesPost(r *http.Request) error {
prefix := r.FormValue("prefix")
description := r.FormValue("description")
_, remove := r.Form["remove"]
if series, ok := indexSeries[prefix]; ok {
if remove {
return dbSeriesRemove(series)
} else {
s := *series
s.Description = description
return dbSeriesUpdate(series, s)
}
} else if remove {
return errNoSuchSeries
} else {
return dbSeriesCreate(&Series{
Prefix: prefix,
Description: description,
})
}
if r.Method != http.MethodGet {
}
func handleSeries(w http.ResponseWriter, r *http.Request) {
var err error
if r.Method == http.MethodPost {
err = handleSeriesPost(r)
// XXX: This is rather ugly.
r.Form = url.Values{}
} else if r.Method != http.MethodGet {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
allSeries := map[string]string{}
allSeries := map[string]*Series{}
for _, s := range indexSeries {
allSeries[s.Prefix] = s.Description
allSeries[s.Prefix] = s
}
prefix := r.FormValue("prefix")
@ -140,16 +207,30 @@ func handleSeries(w http.ResponseWriter, r *http.Request) {
if prefix == "" {
} else if series, ok := indexSeries[prefix]; ok {
description = series.Description
} else {
err = errNoSuchSeries
}
params := struct {
Prefix string
Description string
AllSeries map[string]string
Error error
ErrorInvalidPrefix bool
ErrorSeriesAlreadyExists bool
ErrorCannotChangePrefix bool
ErrorNoSuchSeries bool
ErrorSeriesInUse bool
Prefix string
Description string
AllSeries map[string]*Series
}{
Prefix: prefix,
Description: description,
AllSeries: allSeries,
Error: err,
ErrorInvalidPrefix: err == errInvalidPrefix,
ErrorSeriesAlreadyExists: err == errSeriesAlreadyExists,
ErrorCannotChangePrefix: err == errCannotChangePrefix,
ErrorNoSuchSeries: err == errNoSuchSeries,
ErrorSeriesInUse: err == errSeriesInUse,
Prefix: prefix,
Description: description,
AllSeries: allSeries,
}
executeTemplate("series.tmpl", w, &params)

View File

@ -1,6 +1,20 @@
{{ define "Title" }}{{ or .Prefix "Řady" }}{{ end }}
{{ define "Content" }}
{{ if .ErrorInvalidPrefix }}
<p>Chyba: Neplatný prefix.
{{ else if .ErrorSeriesAlreadyExists }}
<p>Chyba: Řada s tímto prefixem už existuje.
{{ else if .ErrorCannotChangePrefix }}
<p>Chyba: Prefix nelze měnit.
{{ else if .ErrorNoSuchSeries }}
<p>Chyba: Řada neexistuje.
{{ else if .ErrorSeriesInUse }}
<p>Chyba: Řada se používá.
{{ else if .Error }}
<p>Chyba: {{ .Error }}
{{ end }}
{{ if .Prefix }}
<h2>{{ .Prefix }}</h2>
@ -21,15 +35,24 @@
</form>
</section>
{{ range $prefix, $desc := .AllSeries }}
{{ range .AllSeries }}
<section>
<header>
<h3><a href="/series?prefix={{ $prefix }}">{{ $prefix }}</a></h3>
<form method=post action="/series?prefix={{ $prefix }}">
<input type=text name=description value="{{ $desc }}"
<h3><a href="/series?prefix={{ .Prefix }}">{{ .Prefix }}</a></h3>
{{ with $count := len .Containers }}
{{ if eq $count 1 }}
<p>{{ $count }} obal
{{ else if and (ge $count 2) (le $count 4) }}
<p>{{ $count }} obaly
{{ else if gt $count 0 }}
<p>{{ $count }} obalů
{{ end }}
{{ end }}
<form method=post action="/series?prefix={{ .Prefix }}">
<input type=text name=description value="{{ .Description }}"
><input type=submit value="Uložit">
</form>
<form method=post action="/series?prefix={{ $prefix }}&amp;remove">
<form method=post action="/series?prefix={{ .Prefix }}&amp;remove">
<input type=submit value="Odstranit">
</form>
</header>