vcs spec and setup
This commit is contained in:
parent
51817c9f39
commit
d3ff302e7a
4
main.go
4
main.go
@ -12,7 +12,7 @@ type Server interface {
|
||||
|
||||
func main() {
|
||||
var challenge int
|
||||
flag.IntVar(&challenge, "challenge",9, "Challenge number")
|
||||
flag.IntVar(&challenge, "challenge",10, "Challenge number")
|
||||
flag.Parse()
|
||||
|
||||
var port uint16
|
||||
@ -39,6 +39,8 @@ func main() {
|
||||
server = NewSecureServer(port);
|
||||
case 9:
|
||||
server = NewJobServer(port);
|
||||
case 10:
|
||||
server = NewVcsServer(port);
|
||||
default:
|
||||
fmt.Printf("Unknown challenge\n")
|
||||
os.Exit(1)
|
||||
|
133
spec/vcs.md
Normal file
133
spec/vcs.md
Normal file
@ -0,0 +1,133 @@
|
||||
# Discovery
|
||||
```
|
||||
$ nc vcs.protohackers.com 30307
|
||||
READY
|
||||
help
|
||||
OK usage: HELP|GET|PUT|LIST
|
||||
READY
|
||||
list /
|
||||
OK 0
|
||||
READY
|
||||
put /snack 4
|
||||
bla
|
||||
OK r1
|
||||
READY
|
||||
list /
|
||||
OK 1
|
||||
snack r1
|
||||
READY
|
||||
get /snack
|
||||
OK 4
|
||||
bla
|
||||
READY
|
||||
put /snack 3
|
||||
al
|
||||
OK r2
|
||||
READY
|
||||
READY
|
||||
list /
|
||||
OK 1
|
||||
snack r2
|
||||
READY
|
||||
put /snik/snak
|
||||
ERR usage: PUT file length newline data
|
||||
READY
|
||||
put /snik/snak 3
|
||||
ji
|
||||
OK r1
|
||||
READY
|
||||
list /
|
||||
OK 2
|
||||
snack r2
|
||||
snik/ DIR
|
||||
READY
|
||||
get
|
||||
ERR usage: GET file [revision]
|
||||
READY
|
||||
get /snack r1
|
||||
OK 4
|
||||
bla
|
||||
READY
|
||||
```
|
||||
|
||||
# overview
|
||||
this system is a simple filesystem that maintains file versions. All paths are absolute, there is no current working directory.
|
||||
|
||||
This is a tcp protocol that strictly prescribes who talks when. it has two modes:
|
||||
|
||||
* in command mode, server prints READY, then reads until it finds a newline
|
||||
* in data mode, server reads a prespecified amount of bytes
|
||||
* on connection, the server goes into command mode
|
||||
* after a newline, if the command is put and it is a valid command, the server goes into data mode. after the specified amount of bytes has been read, the server prints a response (see below) and returns to command mode
|
||||
* for all other commands, the server prints a response and returns to command mode
|
||||
* responses start with OK or ERR and can be multiple lines
|
||||
|
||||
The filesystem must be global, shared by all users
|
||||
|
||||
# commands
|
||||
commands and arguments are separated by spaces.
|
||||
|
||||
## help
|
||||
OK response describing all commands.
|
||||
|
||||
if arguments are given they are ignored
|
||||
|
||||
## LIST
|
||||
argument: dir
|
||||
|
||||
if more or less arguments are provided, response is 'ERR usage: LIST dir'
|
||||
|
||||
dir must start with a / and can contain more than one / (subdirs). trailing / is optional. Two consecutive slashed are not allowed.
|
||||
|
||||
if dir is an illegal dir name (does not start with a /), response is 'ERR illegal dir name'
|
||||
|
||||
If dir does not exist or is empty, the response is 'OK 0'
|
||||
|
||||
the response followed by the number of lines in the list
|
||||
|
||||
each line starts with the name of the item
|
||||
|
||||
if the item is a directory, the name is postfixed with a / and followed by the word DIR
|
||||
|
||||
If the item is a file, the name is followed by the revision, prefixed by 'r'
|
||||
|
||||
## PUT
|
||||
arguments: file length
|
||||
|
||||
if more or less arguments are provided, the response is 'ERR usage: PUT file length newline data'
|
||||
|
||||
filenames have the same rules as dirnames but can not end with a /
|
||||
|
||||
if an illegal filename is given, the response is 'ERR illegal file name'
|
||||
|
||||
if length is not a number or a negative number, it is interpreted as 0
|
||||
|
||||
if no errors are found, the server goes into data mode to read the specified amount of bytes (can be 0)
|
||||
|
||||
the bytes are then stored in the file. if the file is new, it is given revision 1, otherwise the revision is incremented.
|
||||
|
||||
the response is 'OK r1' with r1 being the latest revision of the file.
|
||||
|
||||
## GET
|
||||
arguments: file [revision]
|
||||
|
||||
If the wrong number of args is given, the response is 'ERR usage: GET file [revision]'
|
||||
|
||||
the file argument follows the same rules as PUT
|
||||
|
||||
if the file is not found, the response is 'ERR no such file'
|
||||
|
||||
if the file is a directory, the response is the same as when the file is not found
|
||||
|
||||
the revision is a number, optionally prefixed by a "r"
|
||||
|
||||
if the revision is postfixed by non-numeric chars, these are ignored
|
||||
|
||||
if the revision is not a number, it is not found
|
||||
|
||||
if the revision is not found, the response is 'ERR no such revision'
|
||||
|
||||
if no revision is given, the latest one is used.
|
||||
|
||||
The response is 'OK 5' where 5 is the number of bytes, followed by that number of bytes.
|
||||
|
86
vcs.go
Normal file
86
vcs.go
Normal file
@ -0,0 +1,86 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"fmt"
|
||||
"bufio"
|
||||
)
|
||||
|
||||
type PutRequest struct {
|
||||
file string
|
||||
content string
|
||||
callback chan bool
|
||||
}
|
||||
|
||||
type VcsServer struct {
|
||||
port uint16
|
||||
q chan PutRequest
|
||||
}
|
||||
|
||||
func NewVcsServer(port uint16) *VcsServer {
|
||||
return &VcsServer{
|
||||
port,
|
||||
make(chan PutRequest),
|
||||
}
|
||||
}
|
||||
|
||||
type VcsSession struct{
|
||||
server *VcsServer
|
||||
con net.Conn
|
||||
}
|
||||
|
||||
func NewVcsSession(server *VcsServer, con net.Conn) *VcsSession {
|
||||
return &VcsSession{
|
||||
server,
|
||||
con,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
func (s *VcsServer) central(){
|
||||
for m := range s.q {
|
||||
fmt.Printf("I %+v\n", m)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func (s *VcsServer) Run() {
|
||||
go s.central()
|
||||
addr := fmt.Sprintf("0.0.0.0:%d", s.port)
|
||||
server, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
fmt.Println("Error listening:", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
defer server.Close()
|
||||
fmt.Println("VcsServer waiting for client...")
|
||||
for {
|
||||
connection, err := server.Accept()
|
||||
if err != nil {
|
||||
fmt.Println("Error accepting: ", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("client connected")
|
||||
s.processClient(connection)
|
||||
}
|
||||
close(s.q)
|
||||
}
|
||||
|
||||
func (s *VcsServer) processClient(con net.Conn) {
|
||||
session := NewVcsSession(s, con)
|
||||
go session.vcsHandler()
|
||||
}
|
||||
|
||||
|
||||
func (s *VcsSession) vcsHandler() {
|
||||
r := bufio.NewReaderSize(s.con, 102400)
|
||||
msg, err := r.ReadString('\n')
|
||||
|
||||
for err == nil {
|
||||
fmt.Print(msg)
|
||||
msg, err = r.ReadString('\n')
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user