From f4b0c7634910a9e1a595e1bb1bd789d79c16c3fb Mon Sep 17 00:00:00 2001 From: Lonny Wong Date: Sun, 15 May 2022 17:50:06 +0800 Subject: [PATCH] implement some trzsz functions --- README.md | 43 +++++++++++++-- trzsz/comm.go | 46 ++++++++++++++++ trzsz/progress.go | 42 +++++++++++++++ trzsz/transfer.go | 80 ++++++++++++++++++++++++++-- trzsz/trzsz.go | 130 ++++++++++++++++++++++++++++++++++++++-------- 5 files changed, 311 insertions(+), 30 deletions(-) create mode 100644 trzsz/progress.go diff --git a/README.md b/README.md index 758cd83..7c2209a 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,58 @@ # trzsz-go trzsz is a simple file transfer tools, similar to lrzsz ( rz / sz ), and compatible with tmux. -*The `Go` version is under development. Please use the `Python` version instead. GitHub: https://github.com/trzsz/trzsz* - ## Installation -### Ubuntu +### on Ubuntu *Not released yet, please download the latest [release](https://github.com/trzsz/trzsz-go/releases) from GitHub.* -``` +```sh sudo add-apt-repository ppa:trzsz/ppa sudo apt update sudo apt install trzsz ``` -### Windows / macOS / Other + +### on Windows / macOS / Other Please download the latest [release](https://github.com/trzsz/trzsz-go/releases) from GitHub. +### install from Source Code + +```sh +git clone https://github.com/trzsz/trzsz-go.git +cd trzsz-go +make +sudo make install +``` + + +## Usage + +### on Local + +Add `trzsz` before the shell, e.g.: + +```sh +trzsz tmux +trzsz /bin/bash +trzsz ssh x.x.x.x +trzsz.exe cmd +trzsz.exe ssh x.x.x.x +``` + + +### on Server + +*The `Go` version is under development. Please use the `Python` version instead. GitHub: https://github.com/trzsz/trzsz* + +Similar to lrzsz ( rz / sz ), command `trz` to upload files, command `tsz /path/to/file` to download files. + +For more information, see the website of trzsz: [https://trzsz.github.io](https://trzsz.github.io/). + + ## Contact Feel free to email me . diff --git a/trzsz/comm.go b/trzsz/comm.go index ed89a70..ca34725 100644 --- a/trzsz/comm.go +++ b/trzsz/comm.go @@ -24,8 +24,54 @@ SOFTWARE. package trzsz +import ( + "bytes" + "compress/zlib" + "encoding/base64" + "io/ioutil" +) + type PtyIO interface { Read(b []byte) (n int, err error) Write(p []byte) (n int, err error) Close() error } + +type ProgressCallback interface { + // TODO +} + +func encodeBytes(buf []byte) string { + var b bytes.Buffer + z := zlib.NewWriter(&b) + z.Write([]byte(buf)) + z.Close() + return base64.StdEncoding.EncodeToString(b.Bytes()) +} + +func encodeString(str string) string { + return encodeBytes([]byte(str)) +} + +func decodeString(str string) ([]byte, error) { + b, err := base64.StdEncoding.DecodeString(str) + if err != nil { + return nil, err + } + z, err := zlib.NewReader(bytes.NewReader(b)) + if err != nil { + return nil, err + } + defer z.Close() + return ioutil.ReadAll(z) +} + +func checkPathWritable(path string) error { + // TODO + return nil +} + +func checkFilesReadable(files []string) error { + // TODO + return nil +} diff --git a/trzsz/progress.go b/trzsz/progress.go new file mode 100644 index 0000000..4d69ba1 --- /dev/null +++ b/trzsz/progress.go @@ -0,0 +1,42 @@ +/* +MIT License + +Copyright (c) 2022 Lonny Wong + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package trzsz + +import ( + "os" +) + +type TextProgressBar struct { + // TODO +} + +func NewTextProgressBar(stdout *os.File, columns int, tmuxPaneColumns int) *TextProgressBar { + // TODO + return nil +} + +func (p *TextProgressBar) setTerminalColumns(columns int) { + // TODO +} diff --git a/trzsz/transfer.go b/trzsz/transfer.go index e961b87..2489c32 100644 --- a/trzsz/transfer.go +++ b/trzsz/transfer.go @@ -24,21 +24,95 @@ SOFTWARE. package trzsz +import ( + "encoding/json" + "fmt" +) + type TrzszTransfer struct { + ptyIn PtyIO + ptyOut PtyIO + stopped bool } func NewTransfer(ptyIn PtyIO, ptyOut PtyIO) *TrzszTransfer { - return nil + return &TrzszTransfer{ptyIn, ptyOut, false} +} + +func (t *TrzszTransfer) addReceivedData(buf []byte) { + // TODO } func (t *TrzszTransfer) stopTransferringFiles() { + // TODO + t.stopped = true +} + +func (t *TrzszTransfer) cleanInput(timeoutInMilliseconds int64) { +} + +func (t *TrzszTransfer) sendLine(typ string, buf string) error { + _, err := t.ptyOut.Write([]byte(fmt.Sprintf("#%s:%s\n", typ, buf))) + return err +} + +func (t *TrzszTransfer) recvLine(expectType string, mayHasJunk bool) (string, error) { + if t.stopped { + return "", fmt.Errorf("Stopped") + } + // TODO + return "", nil +} + +func (t *TrzszTransfer) sendString(typ string, str string) error { + return t.sendLine(typ, encodeString(str)) +} + +func (t *TrzszTransfer) sendAction(confirm bool) error { + actMap := map[string]interface{}{ + "lang": "go", + "confirm": confirm, + "version": TrzszVersion, + } + actStr, err := json.Marshal(actMap) + if err != nil { + return err + } + return t.sendString("ACT", string(actStr)) +} + +func (t *TrzszTransfer) recvAction() error { + // TODO + return nil +} + +func (t *TrzszTransfer) sendConfig() error { + // TODO + return nil +} + +func (t *TrzszTransfer) recvConfig() (map[string]interface{}, error) { + // TODO + return nil, nil } func (t *TrzszTransfer) handleClientError(err error) { + // TODO +} + +func (t *TrzszTransfer) sendExit(msg string) error { + t.cleanInput(200) + return t.sendString("EXIT", msg) } -func (t *TrzszTransfer) sendFiles() { +func (t *TrzszTransfer) sendFiles(files []string, progress ProgressCallback) ([]string, error) { + // TODO + t.sendExit("Under development") + return nil, nil } -func (t *TrzszTransfer) recvFiles() { +func (t *TrzszTransfer) recvFiles(path string, progress ProgressCallback) ([]string, error) { + // TODO + t.sendExit("Under development") + return nil, nil } diff --git a/trzsz/trzsz.go b/trzsz/trzsz.go index 5e01cf4..9ba1482 100644 --- a/trzsz/trzsz.go +++ b/trzsz/trzsz.go @@ -92,20 +92,68 @@ func detectTrzsz(output []byte) *byte { return &match[1][0] } +func newProgressBar(pty *TrzszPty, config map[string]interface{}) (*TextProgressBar, error) { + quiet := false + if v, ok := config["quiet"].(bool); ok { + quiet = v + } + if quiet { + return nil, nil + } + columns, err := pty.GetColumns() + if err != nil { + return nil, err + } + tmuxPaneColumns := -1 + if v, ok := config["tmux_pane_width"].(int); ok { + tmuxPaneColumns = v + } + return NewTextProgressBar(os.Stdout, columns, tmuxPaneColumns), nil +} + func downloadFiles(pty *TrzszPty) error { savePath := getTrzszConfig("DefaultDownloadPath") if savePath == nil { path, err := zenity.SelectFile(zenity.Title("Choose a folder to save file(s)"), zenity.Directory(), zenity.ShowHidden()) if err != nil { - // TODO: send fail or cancel - fmt.Println(err) + if err == zenity.ErrCanceled { + return transfer.sendAction(false) + } return err } savePath = &path } - // TODO download files - fmt.Println(*savePath) - return nil + + if savePath == nil || len(*savePath) == 0 { + return transfer.sendAction(false) + } + if err := checkPathWritable(*savePath); err != nil { + return err + } + + if err := transfer.sendAction(true); err != nil { + return err + } + config, err := transfer.recvConfig() + if err != nil { + return err + } + + progress, err := newProgressBar(pty, config) + if err != nil { + return err + } + if progress != nil { + pty.OnResize(func(cols int) { progress.setTerminalColumns(cols) }) + defer pty.OnResize(nil) + } + + localNames, err := transfer.recvFiles(*savePath, progress) + if err != nil { + return err + } + + return transfer.sendExit(fmt.Sprintf("Saved %s to %s", strings.Join(localNames, ", "), *savePath)) } func uploadFiles(pty *TrzszPty) error { @@ -116,13 +164,56 @@ func uploadFiles(pty *TrzszPty) error { } files, err := zenity.SelectFileMutiple(options...) if err != nil { - // TODO: send fail or cancel - fmt.Println(err) + if err == zenity.ErrCanceled { + return transfer.sendAction(false) + } + return err + } + + if len(files) == 0 { + return transfer.sendAction(false) + } + if err := checkFilesReadable(files); err != nil { + return err + } + + if err := transfer.sendAction(true); err != nil { + return err + } + config, err := transfer.recvConfig() + if err != nil { return err } - // TODO upload files - fmt.Println(files) - return nil + + progress, err := newProgressBar(pty, config) + if err != nil { + return err + } + if progress != nil { + pty.OnResize(func(cols int) { progress.setTerminalColumns(cols) }) + defer pty.OnResize(nil) + } + + remoteNames, err := transfer.sendFiles(files, progress) + if err != nil { + return err + } + + return transfer.sendExit(fmt.Sprintf("Received %s", strings.Join(remoteNames, ", "))) +} + +func handleTrzsz(pty *TrzszPty, mode byte) { + var err error + transfer = NewTransfer(pty.Stdout, pty.Stdin) + if mode == 'S' { + err = downloadFiles(pty) + } else if mode == 'R' { + err = uploadFiles(pty) + } + if err != nil { + transfer.handleClientError(err) + } + transfer = nil } func wrapInput(pty *TrzszPty) { @@ -134,9 +225,9 @@ func wrapInput(pty *TrzszPty) { break } else if err == nil && n > 0 { buf := buffer[0:n] - if transfer != nil { + if t := transfer; t != nil { if buf[0] == '\x03' { // `ctrl + c` to stop transferring files - transfer.stopTransferringFiles() + t.stopTransferringFiles() } continue } @@ -154,22 +245,17 @@ func wrapOutput(pty *TrzszPty) { break } else if err == nil && n > 0 { buf := buffer[0:n] + if t := transfer; t != nil { + t.addReceivedData(buf) + continue + } mode := detectTrzsz(buf) if mode == nil { os.Stdout.Write(buf) continue } os.Stdout.Write(bytes.ToLower(buf)) - transfer = NewTransfer(pty.Stdout, pty.Stdin) - if *mode == 'S' { - err = downloadFiles(pty) - } else if *mode == 'R' { - err = uploadFiles(pty) - } - if err != nil { - transfer.handleClientError(err) - } - transfer = nil + go handleTrzsz(pty, *mode) } } }