Updating multiline entry from connection reading - user-interface

I got simply desktop application wrote on Go with this lib: github.com/ProtonMail/ui. The main window's drawing below:
err := ui.Main(func() {
window := ui.NewWindow("Chat", 500, 500, false)
input := ui.NewEntry()
send := ui.NewButton("Send")
output := ui.NewMultilineNonWrappingEntry()
output.SetReadOnly(true)
mainBox := ui.NewHorizontalBox()
usersBox := ui.NewVerticalBox()
messageBox:=ui.NewVerticalBox()
messageBox.Append(output, true)
messageBox.Append(input, false)
messageBox.Append(send, false)
mainBox.Append(usersBox, false)
mainBox.Append(messageBox, true)
send.OnClicked(func(*ui.Button) {
_, err := conn.Write([]byte(JSONencode(userExample1.Text(),input.Text(),"SendMessageTo")))
if err!=nil{
fmt.Println("OnClickedError!")
}
input.SetText("")
})
window.SetChild(mainBox)
window.OnClosing(func(*ui.Window) bool {
ui.Quit()
return true
})
window.Show()
And it's reading and outputting decoded string from server connection. Server sending described below:
func ParseJSON(bytes []byte, conn net.Conn) (Message, string, string) {
flag := "tcp"
message := Message{}
err := json.Unmarshal(bytes, &message)
if err != nil {
log.Print("Unmarshal doesn't work: ")
log.Fatal(err)
}
fmt.Println(message.User.Login)
fmt.Println(message.Content)
conn.Write([]byte(message.Content))
return message, "func", flag
}
How i can output in this entry form
output := ui.NewMultilineNonWrappingEntry()
output.SetReadOnly(true)
received strings from server?
UPDATE:
go func() {
message, err := bufio.NewReader(conn).ReadString('\n')
if err!=nil{
log.Fatal(err)
}
output.SetText(message)
}()
This code placed in ui.Main function, but it doesn't work.

You can try using the Append method:
output := ui.NewMultilineNonWrappingEntry()
// Call this from your message receiving function:
output.Append("message")
The other option is to use SetText and keep a buffer of the text somewhere else:
// Create a buffer:
buf := bytes.Buffer{}
// Write incoming messages to it:
buf.WriteString("message")
// Set the contents from the buffer
// This might be called periodically (and the buffer reseted):
output.SetText(buf.String())

Related

Golang: how to ensure redis subscribers receive all messages from redis pubsub?

I am trying to publish messages to dynamically generated channels in redis while subscribing all messages from the existing channels.
The following seems to work, but it fails to receive some messages depending on the timing of requests from the client (browser).
I tried "fan-in" for the two go channels in the select statement, but it did not work well.
package main
import (
...
"github.com/go-redis/redis/v8"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{}
var rd = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
var ctx = context.Background()
func echo(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("websocket connection err:", err)
return
}
defer conn.Close()
room := make(chan string)
start := make(chan string)
go func() {
loop:
for {
sub := rd.Subscribe(ctx)
defer sub.Close()
channels := []string{}
for {
select {
//it seems that messages are not received when executing this case sometimes
case channel := <-room:
log.Println("channel", channel)
channels = append(channels, channel)
sub = rd.Subscribe(ctx, channels...)
start <- "ok"
case msg := <-sub.Channel():
log.Println("msg", msg)
err := conn.WriteMessage(websocket.TextMessage, []byte(msg.Payload))
if err != nil {
log.Println("websocket write err:", err)
break loop
}
}
}
}
}()
for {
_, msg, err := conn.ReadMessage()
if err != nil {
log.Println("websocket read err:", err)
break
}
log.Println(string(msg))
chPrefix := strings.Split(string(msg), ":")[0]
ch := chPrefix + "-channel"
if string(msg) == "test" || string(msg) == "yeah" {
room <- ch
log.Println(ch)
log.Println(<-start)
}
if err := rd.Publish(ctx, ch, msg).Err(); err != nil {
log.Println("redis publish err:", err)
break
}
}
}
func main() {
http.Handle("/", http.FileServer(http.Dir("./js")))
http.HandleFunc("/ws", echo)
log.Println("server starting...", "http://localhost:5000")
log.Fatal(http.ListenAndServe("localhost:5000", nil))
}
If by all messages, you mean you do not wish to lose any messages, I would recommend using Redis Streams instead of pub/sub. This will ensure you are not missing messages and can go back on the stream history if necessary.
This is an example of using Go, Streams and websockets that should get you started in that direction

Copy file from remote to byte[]

I'm trying to figure out how to implement copying files from remote and get the data []byte from the buffer.
I have succeeded in doing the implementation with the upload by referring to this guide: https://chuacw.ath.cx/development/b/chuacw/archive/2019/02/04/how-the-scp-protocol-works.aspx
Inside the go func there's the implementation of the upload process of the SCP but I have no idea how to change it.
Any advice ?
func download(con *ssh.Client, buf bytes.Buffer, path string,) ([]byte,error) {
//https://chuacw.ath.cx/development/b/chuacw/archive/2019/02/04/how-the-scp-protocol-works.aspx
session, err := con.NewSession()
if err != nil {
return nil,err
}
buf.WriteString("sudo scp -f " + path + "\n")
stdin, err := session.StdinPipe()
if err != nil {
return nil,err
}
go func() {
defer stdin.Close()
fmt.Fprint(stdin, "C0660 "+strconv.Itoa(len(content))+" file\n")
stdin.Write(content)
fmt.Fprint(stdin, "\x00")
}()
output, err := session.CombinedOutput("sudo scp -f " + path)
buf.Write(output)
if err != nil {
return nil,&DeployError{
Err: err,
Output: buf.String(),
}
}
session.Close()
session, err = con.NewSession()
if err != nil {
return nil,err
}
defer session.Close()
return output,nil
}
The sink side is significantly more difficult than the source side. Made an example which should get you close to what you want. Note that I have not tested this code, that the error handling is sub optimal and it only supports 1/4th the protocol messages SCP may use. So you will still need to do some work to get it perfect.
With all that said, this is what I came up with:
func download(con *ssh.Client, path string) ([]byte, error) {
//https://chuacw.ath.cx/development/b/chuacw/archive/2019/02/04/how-the-scp-protocol-works.aspx
session, err := con.NewSession()
if err != nil {
return nil, err
}
defer session.Close()
// Local -> remote
stdin, err := session.StdinPipe()
if err != nil {
return nil, err
}
defer stdin.Close()
// Request a file, note that directories will require different handling
_, err = stdin.Write([]byte("sudo scp -f " + path + "\n"))
if err != nil {
return nil, err
}
// Remote -> local
stdout, err := session.StdoutPipe()
if err != nil {
return nil, err
}
// Make a buffer for the protocol messages
const megabyte = 1 << 20
b := make([]byte, megabyte)
// Offset into the buffer
off := 0
var filesize int64
// SCP may send multiple protocol messages, so keep reading
for {
n, err := stdout.Read(b[off:])
if err != nil {
return nil, err
}
nl := bytes.Index(b[:off+n], []byte("\n"))
// If there is no newline in the buffer, we need to read more
if nl == -1 {
off = off + n
continue
}
// We read a full message, reset the offset
off = 0
// if we did get a new line. We have the full protocol message
msg := string(b[:nl])
// Send back 0, which means OK, the SCP source will not send the next message otherwise
_, err = stdin.Write([]byte("0\n"))
if err != nil {
return nil, err
}
// First char is the mode (C=file, D=dir, E=End of dir, T=Time metadata)
mode := msg[0]
if mode != 'C' {
// Ignore other messags for now.
continue
}
// File message = Cmmmm <length> <filename>
msgParts := strings.Split(msg, " ")
if len(msgParts) > 1 {
// Parse the second part <length> as an base 10 integer
filesize, err = strconv.ParseInt(msgParts[1], 10, 64)
if err != nil {
return nil, err
}
}
// The file message will be followed with binary data containing the file
break
}
// Wrap the stdout reader in a limit reader so we will not read more than the filesize
fileReader := io.LimitReader(stdout, filesize)
// Seed the bytes buffer with the existing byte slice, saves additional allocation if file <= 1mb
buf := bytes.NewBuffer(b)
// Copy the file into the bytes buffer
_, err = io.Copy(buf, fileReader)
return buf.Bytes(), err
}

Return io.ReadCloser from zip.NewWriter

I am creating a buffer and then writing into that buffer via zip.NewWriter()
So my code looks like this
// Create a buffer to write our archive to.
buf := new(bytes.Buffer)
// Create a new zip archive.
w := zip.NewWriter(buf)
// Create file in this writer
f, err := w.Create("test.json")
/ Writing data to file
_, _ = f.Write([]byte("some data"))
// Close writer
_ = w.Close()
// TODO: Return this data in the form of io.ReadCloser
My end goal is to write files into this zip writer and return the data in the form of io.ReadCloser, my calling function expects data in io.ReadCloser
I tried to find and try multiple ways but none of them succeeded.
Check if this example works for you
func ExampleWriter() {
// Create a buffer to write our archive to.
buf := new(bytes.Buffer)
// Create a new zip archive.
w := zip.NewWriter(buf)
// Add some files to the archive.
var files = []struct {
Name, Body string
}{
{"readme.txt", "This archive contains some text files."},
{"gopher.txt", "Gopher names:\nGeorge\nGeoffrey\nGonzo"},
{"todo.txt", "Get animal handling licence.\nWrite more examples."},
}
for _, file := range files {
f, err := w.Create(file.Name)
if err != nil {
log.Fatal(err)
}
_, err = f.Write([]byte(file.Body))
if err != nil {
log.Fatal(err)
}
}
// Make sure to check the error on Close.
err := w.Close()
if err != nil {
log.Fatal(err)
}
}
By systematically check the error for those operations, you will see if there are any issue.
After some help from comments I got it working
So we can directly convert buf to io.ReadCloser and return it, no need for further conversion.
func ExampleWriter() (io.ReadCloser, error) {
// Create a buffer to write our archive to.
buf := new(bytes.Buffer)
// Create a new zip archive.
w := zip.NewWriter(buf)
// Add some files to the archive.
var files = []struct {
Name, Body string
}{
{"readme.txt", "This archive contains some text files."},
{"gopher.txt", "Gopher names:\nGeorge\nGeoffrey\nGonzo"},
{"todo.txt", "Get animal handling licence.\nWrite more examples."},
}
for _, file := range files {
f, err := w.Create(file.Name)
if err != nil {
log.Fatal(err)
}
_, err = f.Write([]byte(file.Body))
if err != nil {
log.Fatal(err)
}
}
// Make sure to check the error on Close.
err := w.Close()
if err != nil {
log.Fatal(err)
}
return ioutil.NopCloser(buf), nil // This returns io.ReadCloser
}

Gob decoder throws EOF error for a time then stops

I'm trying to feed []byte over a chan to a gob decoder. It works, but at first the decoder throws a whole bunch of EOF errors then stops. When it stops throwing errors the program behaves exactly like I would expect with it decoding gobs and processing the structs it produces correctly.
This is the calling function, the channel that is being read from is an SSH channel.
log.Println("Reading channel")
dchan := make(chan []byte, 200)
go decoder(dchan)
for {
buf := make([]byte, 1024)
//log.Println("Waiting for data")
numBytes, err := channel.Read(buf)
if err != nil {
log.Println(err)
continue
}
dchan <- buf[:numBytes]
}
The decoder function looks like this:
func decoder(dchan chan []byte) error {
gob.Register(datums.Message{})
var message datums.Message
bbuf := bytes.NewBuffer(make([]byte, 512))
dec := gob.NewDecoder(bbuf)
for data := range dchan {
//log.Println("Decoding data")
bbuf.Write(data)
err := dec.Decode(&message)
if err != nil {
log.Println("Error in decoder")
log.Println(err)
continue
}
//log.Println(&message)
}
return nil
}
I'm not sure if this is even a real issue, does the gob decoder remove the data from the buffer or does it leave it there until sufficient data shows up to generate a value?
The call to channel.Read(buf) can read a partial gob. The decoder will return an error when attempting to decode the partial gob.
Because the channel satisfies the io.Reader interface, the application can create the decoder directly on the channel:
gob.Register(datums.Message{})
dec := gob.NewDecoder(channel)
for {
var message datums.Message
err := dec.Decode(&message)
if err != nil {
log.Println("Error in decoder", err)
break
}
log.Println(&message)
}

Channels in Golang with TCP/IP socket not working

I just started writting a Golang client for a server that I've made in C with TCP/IP sockets, then I figured out that my channel wasn't working.
Any ideas why ?
func reader(r io.Reader, channel chan<- []byte) {
buf := make([]byte, 2048)
for {
n, err := r.Read(buf[:])
if err != nil {
return
}
channel <- buf[0:n]
}
}
func client(e *gowd.Element) {
f, err := os.Create("/tmp/dat2")
if err != nil {
log.Fatal()
}
read := make(chan []byte)
c, err := net.Dial("tcp", "127.0.0.1:4242")
if err != nil {
log.Fatal(err)
}
go reader(c, read)
for {
buf := <-read
n := strings.Index(string(buf), "\n")
if n == -1 {
continue
}
msg := string(buf[0:n])
if msg == "WELCOME" {
fmt.Fprint(c, "GRAPHIC\n")
}
f.WriteString(msg + "\n")
}
Testing my server with netcat results in the following output :
http://pasted.co/a37b2954
But i only have : http://pasted.co/f13d56b4
I'm new to chan in Golang so maybe I'm wrong (I probably am)
Channel usage looks alright, however retrieving value from channel would overwrite previously read value at buf := <-read since your waiting for newline.
Also you can use bufio.Reader to read string upto newline.
Your code snippet is partial so its not feasible to execute, try and let me know:
func reader(r io.Reader, channel chan<- string) {
bufReader := bufio.NewReader(conn)
for {
msg, err := bufReader.ReadString('\n')
if err != nil { // connection error or connection reset error, etc
break
}
channel <- msg
}
}
func client(e *gowd.Element) {
f, err := os.Create("/tmp/dat2")
if err != nil {
log.Fatal()
}
read := make(chan string)
c, err := net.Dial("tcp", "127.0.0.1:4242")
if err != nil {
log.Fatal(err)
}
go reader(c, read)
for {
msg := <-read
if msg == "WELCOME" {
fmt.Fprint(c, "GRAPHIC\n")
}
f.WriteString(msg + "\n")
}
//...
}
EDIT:
Please find example of generic TCP client to read data. Also I have removed scanner from above code snippet and added buffer reader.
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:4242")
if err != nil {
log.Fatal(err)
}
reader := bufio.NewReader(conn)
for {
msg, err := reader.ReadString('\n')
if err != nil {
break
}
fmt.Println(msg)
}
}

Resources