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() {
|
func main() {
|
||||||
var challenge int
|
var challenge int
|
||||||
flag.IntVar(&challenge, "challenge",9, "Challenge number")
|
flag.IntVar(&challenge, "challenge",10, "Challenge number")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
var port uint16
|
var port uint16
|
||||||
@ -39,6 +39,8 @@ func main() {
|
|||||||
server = NewSecureServer(port);
|
server = NewSecureServer(port);
|
||||||
case 9:
|
case 9:
|
||||||
server = NewJobServer(port);
|
server = NewJobServer(port);
|
||||||
|
case 10:
|
||||||
|
server = NewVcsServer(port);
|
||||||
default:
|
default:
|
||||||
fmt.Printf("Unknown challenge\n")
|
fmt.Printf("Unknown challenge\n")
|
||||||
os.Exit(1)
|
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