diff --git a/redis/Makefile b/redis/Makefile new file mode 100644 index 0000000..e80b4b8 --- /dev/null +++ b/redis/Makefile @@ -0,0 +1,17 @@ +#=============# +# Redis # +# ============# + +all: get install + +fmt: + @ go fmt ./... + +get: + @ govendor sync + +install: + @ go install ./... + +test: + @ go test -p 1 ./... diff --git a/redis/argscheck/argscheck.go b/redis/argscheck/argscheck.go new file mode 100644 index 0000000..5be6b48 --- /dev/null +++ b/redis/argscheck/argscheck.go @@ -0,0 +1,35 @@ +package argscheck + +//Start - update gPort, gMode, gIP variable if it's need to be changed +func Start(args []string, gPort string, gMemory string, gIP string) (string, string, string) { + var ( + port = false + host = false + mode = false + ) + for _, value := range args[1:] { + if port { + gPort = ":" + value + port = false + } else { + if host { + gIP = value + host = false + } + } + if mode { + gMemory = value + mode = false + } + + switch value { + case "-p", "--port": + port = true + case "-h", "--host": + host = true + case "-m", "--mode": + mode = true + } + } + return gPort, gIP, gMemory +} diff --git a/redis/argscheck/argscheck_test.go b/redis/argscheck/argscheck_test.go new file mode 100644 index 0000000..0194757 --- /dev/null +++ b/redis/argscheck/argscheck_test.go @@ -0,0 +1,34 @@ +package argscheck + +import "testing" + +func TestStart(t *testing.T) { + tables := []struct { + args []string + gPort string + gMemory string + gIP string + Port string + Memory string + IP string + }{ + {[]string{"1", "--mode", "disk", "--port", "8080", "-h", "127.0.0.7"}, ":9090", " ", "127.0.0.1", ":8080", "disk", "127.0.0.7"}, + {[]string{"2", "-m", "disk"}, ":9090", " ", "127.0.0.1", ":9090", "disk", "127.0.0.1"}, + {[]string{"3", "--port", "7070", "-h", "127.0.0.9"}, ":9090", " ", "127.0.0.1", ":7070", " ", "127.0.0.9"}, + {[]string{"4", "--port", "6060", "-h", "127.0.0.8"}, ":9090", " ", "127.0.0.1", ":6060", " ", "127.0.0.8"}, + {[]string{"5", "-h", "127.0.0.5"}, ":9090", " ", "127.0.0.1", ":9090", " ", "127.0.0.5"}, + {[]string{"6", "--port", "4040"}, ":9090", " ", "127.0.0.1", ":4040", " ", "127.0.0.1"}, + } + for _, table := range tables { + total, total2, total3 := Start(table.args, table.gPort, table.gMemory, table.gIP) + if total != table.Port { + t.Errorf("total %s, args %s, gPort %s, gMemory %s, gIP %s, Port %s, Memory %s, IP %s", total, table.args, table.gPort, table.gMemory, table.gIP, table.Port, table.Memory, table.IP) + } + if total2 != table.IP { + t.Errorf("total2 %s, args %s, gPort %s, gMemory %s, gIP %s, Port %s, Memory %s, IP %s", total2, table.args, table.gPort, table.gMemory, table.gIP, table.Port, table.Memory, table.IP) + } + if total3 != table.Memory { + t.Errorf("total3 %s, args %s, gPort %s, gMemory %s, gIP %s, Port %s, Memory %s, IP %s", total3, table.args, table.gPort, table.gMemory, table.gIP, table.Port, table.Memory, table.IP) + } + } +} diff --git a/redis/client/client.go b/redis/client/client.go new file mode 100644 index 0000000..0077e10 --- /dev/null +++ b/redis/client/client.go @@ -0,0 +1,73 @@ +package client + +import ( + "bufio" + "fmt" + "log" + "net" + "os" + "strings" +) + +//Commands - read stdin, input connection, write to connection +func Commands(protocolTCP string, gPort string, gIP string, buff int) (string, error) { + var ( + buffer = make([]byte, buff) + IPHandler = []string{gIP + gPort} + answer string + ) + + Dconn, err := net.Dial(protocolTCP, gPort) + if err != nil { + log.Fatal(err) + } + + for { + first, err := bufio.NewReader(os.Stdin).ReadString('\n') + if err != nil { + log.Fatal(err) + + } + mssg := strings.Replace(first, "\n", " ", -1) + Splitmssg := strings.Fields(mssg) + + switch Splitmssg[0] { + case "--exit", "-q": + fmt.Println("Exit. See ya.") + os.Exit(1) + //return mssg, nil // for test + case "--connect": + if len(Splitmssg) == 2 { + var toggle = false + for _, addr := range IPHandler { + if addr == Splitmssg[1] { + toggle = true + break + } + } + if !toggle { + conn, err := net.Dial(protocolTCP, Splitmssg[1]) + if err == nil { + IPHandler = append(IPHandler, Splitmssg[1]) + conn.Write([]byte(fmt.Sprintf("User '%s:%s join'\n", gIP, gPort))) + conn.Close() + } + } + } + default: + //write to socket + _, werr := Dconn.Write([]byte(mssg)) + if werr != nil { + log.Fatal(werr) + } + //read from socket + read, err := Dconn.Read(buffer) + if err != nil { + log.Fatal(err) + } + answer = string(buffer[:read]) + fmt.Printf("Answer: %s.", answer) + } + } + return answer, nil +} diff --git a/redis/client/client_test.go b/redis/client/client_test.go new file mode 100644 index 0000000..f2773a5 --- /dev/null +++ b/redis/client/client_test.go @@ -0,0 +1,80 @@ +package client + +import ( + "bufio" + "io/ioutil" + "net" + "os" + "testing" +) + +func FakeHandler(conn net.Conn) error { + scnnr := bufio.NewScanner(conn) + for scnnr.Scan() { + line := scnnr.Text() + conn.Write([]byte(line)) + } + return nil +} + +func TestCommands(t *testing.T) { + var ( + protocolTCP = "tcp4" + gPort = ":1281" + buff = 128 + toggle = true + ) + grounds := []struct { + gIP, input, output string + }{ + {"127.0.0.1", "da", "net"}, + {"127.0.0.1", "--connect 127.0.0.2:2011", "--connect 127.0.0.2:2011"}, + //{"127.0.0.1", "--exit", "Exit. See ya."}, + {"127.0.0.1", "hello", "hello"}, + } + fakeserver, err := net.Listen(protocolTCP, gPort) + if err != nil { + t.Fatal(err) + } + go func() { + for _, ground := range grounds { + content := []byte(ground.input) + tmpfile, err := ioutil.TempFile("./", "test.*.txt") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpfile.Name()) //clean up + if _, werr := tmpfile.Write(content); werr != nil { + t.Fatal(werr) + } + if _, serr := tmpfile.Seek(0, 0); serr != nil { + t.Fatal(serr) + } + oldStdin := os.Stdin + defer func() { os.Stdin = oldStdin }() //Restore original Stdin + total, errC := Commands(protocolTCP, gPort, ground.gIP, buff) + if errC != nil { + t.Fatalf("ErrorC: %v ", errC) + } + if cerr := tmpfile.Close(); cerr != nil { + t.Fatal(cerr) + } + + if total != ground.output { + toggle = false + t.Errorf("Wanted:%v , got:%v .", ground.output, total) + } + } + }() + for { + conn, aeer := fakeserver.Accept() + if aeer != nil { + t.Fatal(aeer) + } + defer conn.Close() + go FakeHandler(conn) + if toggle == false { + break + } + } +} diff --git a/redis/main.go b/redis/main.go new file mode 100644 index 0000000..8cb003a --- /dev/null +++ b/redis/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "fmt" + "log" + "net" + "os" + + "redis/argscheck" + "redis/client" + "redis/serv" + "redis/types" +) + +const ( + protocolTCP = "tcp" + separator = " " + buff = 128 +) + +var ( + gPort = ":9090" + gMemory = "..." //expected disk or blank + gIP = "127.0.0.1" +) + +func main() { + argscheck.Start(os.Args, gPort, gMemory, gIP) + ServFunc(gPort) +} + +//ServFunc - containing Server and Client +func ServFunc(gPort string) { + fmt.Printf("Port->%s\n", gPort) + li, err := net.Listen(protocolTCP, gPort) + if err != nil { + fmt.Println("Error: ", err) + log.Fatal(err) + } + defer li.Close() + fmt.Println("Entered with :" + gIP + gPort) + go client.Commands(protocolTCP, gPort, gIP, buff) + for { + conn, err := li.Accept() + if err != nil { + log.Fatal(err) + } + defer conn.Close() + ServConnCh := make(chan types.Server) + go serv.ServConnHandler(ServConnCh, conn) + go serv.ServCmndsHandler(ServConnCh, gMemory) + } +} diff --git a/redis/remove/remove.go b/redis/remove/remove.go new file mode 100644 index 0000000..b9e24a7 --- /dev/null +++ b/redis/remove/remove.go @@ -0,0 +1,9 @@ +package remove + +import "fmt" + +//Remove - remove element from slice +func Remove(list []string, num int) []string { + fmt.Printf("'remove' done\n") + return append(list[:num], list[num+1:]...) +} diff --git a/redis/save/save.go b/redis/save/save.go new file mode 100644 index 0000000..03f02ca --- /dev/null +++ b/redis/save/save.go @@ -0,0 +1,23 @@ +package save + +import ( + "fmt" + "os" +) + +//SaveOnDisk - saves data into file on disk +func SaveOnDisk(info string) (int, error) { + Path := "Info.txt" + //Существующий файл с таким же именем будут перезаписан + var fl, err = os.Create(Path) + if err != nil { + fmt.Printf("Error create: %v", err) + } + defer fl.Close() + var ByteWrttn, errWrt = fl.WriteString(info) + if errWrt != nil { + fmt.Printf("Error write: %v", errWrt) + } + fmt.Printf("Info.txt written: %v\n", ByteWrttn) + return ByteWrttn, nil +} diff --git a/redis/save/save_test.go b/redis/save/save_test.go new file mode 100644 index 0000000..9ea2e4e --- /dev/null +++ b/redis/save/save_test.go @@ -0,0 +1,24 @@ +package save + +import ( + "testing" +) + +func TestSaveOnDisk(t *testing.T) { + tables := []struct { + info string + byte int + testerr error + }{ + {"hello world i am here stas here everyone is here", 48, nil}, + } + for _, table := range tables { + total, totalerr := SaveOnDisk(table.info) + if total != table.byte { + t.Errorf("total %v, byte %v, info %s", total, table.byte, table.info) + } + if totalerr != table.testerr { + t.Errorf("totalerr %v, testerr %v, info %s", totalerr, table.testerr, table.info) + } + } +} diff --git a/redis/serv/server.go b/redis/serv/server.go new file mode 100644 index 0000000..12b4918 --- /dev/null +++ b/redis/serv/server.go @@ -0,0 +1,83 @@ +package serv + +import ( + "bufio" + "encoding/json" + "fmt" + "io" + "net" + "redis/save" + "redis/types" + "strings" +) + +//ServConnHandler - scanning input connection and send to ServCmndsHandler +func ServConnHandler(ServConnCh chan types.Server, conn net.Conn) { + scnnr := bufio.NewScanner(conn) + for scnnr.Scan() { + line := scnnr.Text() + inptFlds := strings.Fields(line) + rslt := make(chan string) + ServConnCh <- types.Server{ + HandFlds: inptFlds, + Rslt: rslt, + } + io.WriteString(conn, <-rslt) + } +} + +//ServCmndsHandler - containing GET, SET, DEL Commands. +func ServCmndsHandler(ServConnCh chan types.Server, gMemory string) { + var memData = make(map[string]string) + for cmnd := range ServConnCh { + if len(cmnd.HandFlds) < 2 { + cmnd.Rslt <- "SET 'key value', GET 'key', DEL 'key'.\n" + continue + } + switch cmnd.HandFlds[0] { + //GET + case "GET": + if len(cmnd.HandFlds) != 2 { + cmnd.Rslt <- "Get what?" + } + key := cmnd.HandFlds[1] + value := memData[key] + if len(memData) == 0 { + cmnd.Rslt <- "Data is empty" + } else { + cmnd.Rslt <- value + } + //SET + case "SET": + if len(cmnd.HandFlds) != 3 { + cmnd.Rslt <- "Missing value\n" + } + key := cmnd.HandFlds[1] + value := cmnd.HandFlds[2] + memData[key] = value + if gMemory == "disk" { + memDisk, err := json.Marshal(memData) + if err != nil { + fmt.Println("JSON", string(memDisk), err) + } + info := string(memDisk) + save.SaveOnDisk(info) + cmnd.Rslt <- "JSON: KEY - VALUE SET\n" + } else { + cmnd.Rslt <- "KEY - VALUE SET\n" + } + //DEL + case "DEL": + key := cmnd.HandFlds[1] + value, ok := memData[key] + if ok { + delete(memData, key) + cmnd.Rslt <- key + " - " + value + " DELETED\n" + } else { + cmnd.Rslt <- "KEY not found\n" + } + default: + cmnd.Rslt <- "I don't know this command :" + cmnd.HandFlds[0] + "\n" + } + } +} diff --git a/redis/types/type.go b/redis/types/type.go new file mode 100644 index 0000000..16ce5b8 --- /dev/null +++ b/redis/types/type.go @@ -0,0 +1,7 @@ +package types + +//Server - containing server i/o and connecting ServerConnHandler and ServCmndsHandler +type Server struct { + HandFlds []string + Rslt chan string +}