more work on transfer kitten

This commit is contained in:
Kovid Goyal
2023-05-26 20:40:41 +05:30
parent b0bb1dbca3
commit 6b93610b6a
3 changed files with 302 additions and 61 deletions

View File

@@ -513,7 +513,7 @@ safe_string
integer
A base-10 number composed of the characters ``[0-9]`` with a possible
leading ``-`` sign
leading ``-`` sign. When missing the value is zero.
base64_string
A base64 encoded UTF-8 string using the standard base64 encoding

274
kittens/transfer/ftc.go Normal file
View File

@@ -0,0 +1,274 @@
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
package transfer
import (
"encoding/base64"
"fmt"
"io/fs"
"kitty"
"kitty/tools/utils"
"kitty/tools/wcswidth"
"reflect"
"strconv"
"strings"
"time"
)
var _ = fmt.Print
type Field interface {
IsDefault() bool
Serialize() string
}
type Action int
func (self Action) IsDefault() bool { return self == Action_invalid }
func (self Action) Serialize() string {
switch self {
default:
return "invalid"
case Action_file:
return "file"
case Action_data:
return "data"
case Action_end_data:
return "end_data"
case Action_receive:
return "receive"
case Action_send:
return "send"
case Action_cancel:
return "cancel"
case Action_status:
return "status"
case Action_finish:
return "finish"
}
}
const (
Action_invalid Action = iota
Action_file
Action_data
Action_end_data
Action_receive
Action_send
Action_cancel
Action_status
Action_finish
)
type Compression int
const (
Compression_none Compression = iota
Compression_zlib
)
func (self Compression) IsDefault() bool { return self == Compression_none }
func (self Compression) Serialize() string {
switch self {
default:
return "none"
case Compression_zlib:
return "zlib"
}
}
type FileType int
const (
FileType_regular FileType = iota
FileType_symlink
FileType_directory
FileType_link
)
func (self FileType) ShortText() string {
switch self {
case FileType_regular:
return "fil"
case FileType_directory:
return "dir"
case FileType_symlink:
return "sym"
case FileType_link:
return "lnk"
}
return "und"
}
func (self FileType) Color() string {
switch self {
case FileType_regular:
return "yellow"
case FileType_directory:
return "magenta"
case FileType_symlink:
return "blue"
case FileType_link:
return "green"
}
return ""
}
func (self FileType) String() string {
switch self {
case FileType_regular:
return "FileType.Regular"
case FileType_directory:
return "FileType.Directory"
case FileType_symlink:
return "FileType.SymbolicLink"
case FileType_link:
return "FileType.Link"
}
return "FileType.Unknown"
}
func (self FileType) IsDefault() bool { return self == FileType_regular }
func (self FileType) Serialize() string {
switch self {
default:
return "regular"
case FileType_directory:
return "directory"
case FileType_symlink:
return "symlink"
case FileType_link:
return "link"
}
}
type TransmissionType int
const (
TransmissionType_simple TransmissionType = iota
TransmissionType_rsync
)
func (self TransmissionType) IsDefault() bool { return self == TransmissionType_simple }
func (self TransmissionType) Serialize() string {
switch self {
default:
return "simple"
case TransmissionType_rsync:
return "rsync"
}
}
type QuietLevel int
const (
Quiet_none QuietLevel = iota
Quiet_acknowledgements
Quiet_errors
)
func (self QuietLevel) IsDefault() bool { return self == Quiet_none }
func (self QuietLevel) Serialize() string {
switch self {
default:
return "0"
case Quiet_acknowledgements:
return "1"
case Quiet_errors:
return "2"
}
}
type FileTransmissionCommand struct {
Action Action `name:"ac"`
Compression Compression `name:"zip"`
Ftype FileType `name:"ft"`
Ttype TransmissionType `name:"tt"`
Quiet QuietLevel `name:"q"`
Id string `name:"id"`
File_id string `name:"fid"`
Bypass string `name:"pw" encoding:"base64"`
Name string `name:"n" encoding:"base64"`
Status string `name:"st" encoding:"base64"`
Parent string `name:"pr"`
Mtime time.Duration `name:"mod"`
Permissions fs.FileMode `name:"prm"`
Size uint64 `name:"sz"`
Data []byte `name:"d"`
}
func escape_semicolons(x string) string {
return strings.ReplaceAll(x, ";", ";;")
}
func (self *FileTransmissionCommand) Serialize(prefix_with_osc_code ...bool) string {
ans := strings.Builder{}
v := reflect.ValueOf(*self)
typ := v.Type()
fields := reflect.VisibleFields(typ)
found := false
if len(prefix_with_osc_code) > 0 && prefix_with_osc_code[0] {
ans.WriteString(strconv.Itoa(kitty.FileTransferCode))
found = true
}
for _, field := range fields {
if name := field.Tag.Get("name"); name != "" {
val := v.FieldByIndex(field.Index)
encoded_val := ""
switch val.Kind() {
case reflect.String:
sval := val.String()
if sval != "" {
enc := field.Tag.Get("encoding")
switch enc {
case "base64":
encoded_val = base64.StdEncoding.EncodeToString(utils.UnsafeStringToBytes(sval))
default:
encoded_val = escape_semicolons(wcswidth.StripEscapeCodes(sval))
}
}
case reflect.Slice:
switch val.Elem().Type().Kind() {
case reflect.Uint8:
bval := val.Bytes()
if len(bval) > 0 {
encoded_val = base64.StdEncoding.EncodeToString(bval)
}
}
case reflect.Uint64:
encoded_val = strconv.FormatUint(val.Uint(), 10)
default:
if val.CanInterface() {
i := val.Interface()
if field, ok := i.(Field); ok {
if !field.IsDefault() {
encoded_val = field.Serialize()
}
} else if field, ok := i.(time.Duration); ok {
if field != 0 {
encoded_val = strconv.FormatInt(int64(field), 10)
}
} else if field, ok := i.(fs.FileMode); ok {
field = field.Perm()
if field != 0 {
encoded_val = strconv.FormatInt(int64(field), 10)
}
}
}
}
if encoded_val != "" {
if found {
ans.WriteString(";")
} else {
found = true
}
ans.WriteString(name)
ans.WriteString("=")
ans.WriteString(encoded_val)
}
}
}
return ans.String()
}

View File

@@ -20,57 +20,6 @@ import (
var _ = fmt.Print
type FileType int
const (
REGULAR_FILE FileType = iota
SYMLINK_FILE
DIRECTORY_FILE
LINK_FILE
)
func (self FileType) ShortText() string {
switch self {
case REGULAR_FILE:
return "fil"
case DIRECTORY_FILE:
return "dir"
case SYMLINK_FILE:
return "sym"
case LINK_FILE:
return "lnk"
}
return "und"
}
func (self FileType) Color() string {
switch self {
case REGULAR_FILE:
return "yellow"
case DIRECTORY_FILE:
return "magenta"
case SYMLINK_FILE:
return "blue"
case LINK_FILE:
return "green"
}
return ""
}
func (self FileType) String() string {
switch self {
case REGULAR_FILE:
return "FileType.Regular"
case DIRECTORY_FILE:
return "FileType.Directory"
case SYMLINK_FILE:
return "FileType.SymbolicLink"
case LINK_FILE:
return "FileType.Link"
}
return "FileType.Unknown"
}
type FileState int
const (
@@ -125,8 +74,8 @@ func NewFile(local_path, expanded_local_path string, file_id int, stat_result fs
file_hash: FileHash{stat.Dev, stat.Ino}, mtime: stat_result.ModTime(),
file_size: stat_result.Size(), bytes_to_transmit: stat_result.Size(),
permissions: stat_result.Mode().Perm(), remote_path: filepath.ToSlash(get_remote_path(local_path, remote_base)),
rsync_capable: file_type == REGULAR_FILE && stat_result.Size() > 4096,
compression_capable: file_type == REGULAR_FILE && stat_result.Size() > 4096 && should_be_compressed(expanded_local_path),
rsync_capable: file_type == FileType_regular && stat_result.Size() > 4096,
compression_capable: file_type == FileType_regular && stat_result.Size() > 4096 && should_be_compressed(expanded_local_path),
remote_initial_size: -1,
}
return &ans
@@ -141,7 +90,7 @@ func process(opts *Options, paths []string, remote_base string, counter *int) (a
}
if s.IsDir() {
*counter += 1
ans = append(ans, NewFile(x, expanded, *counter, s, remote_base, DIRECTORY_FILE))
ans = append(ans, NewFile(x, expanded, *counter, s, remote_base, FileType_directory))
new_remote_base := remote_base
if new_remote_base != "" {
new_remote_base = strings.TrimRight(new_remote_base, "/") + "/" + filepath.Base(x) + "/"
@@ -163,10 +112,10 @@ func process(opts *Options, paths []string, remote_base string, counter *int) (a
ans = append(ans, new_ans...)
} else if s.Mode()&fs.ModeSymlink == fs.ModeSymlink {
*counter += 1
ans = append(ans, NewFile(x, expanded, *counter, s, remote_base, SYMLINK_FILE))
ans = append(ans, NewFile(x, expanded, *counter, s, remote_base, FileType_symlink))
} else if s.Mode().IsRegular() {
*counter += 1
ans = append(ans, NewFile(x, expanded, *counter, s, remote_base, REGULAR_FILE))
ans = append(ans, NewFile(x, expanded, *counter, s, remote_base, FileType_regular))
}
}
return
@@ -219,7 +168,7 @@ func files_for_send(opts *Options, args []string) (files []*File, err error) {
for _, group := range groups {
if len(group) > 1 {
for _, lf := range group[1:] {
lf.file_type = LINK_FILE
lf.file_type = FileType_link
lf.hard_link_target = group[0].file_id
}
}
@@ -228,7 +177,7 @@ func files_for_send(opts *Options, args []string) (files []*File, err error) {
remove := make([]int, 0, len(files))
// detect symlinks to other transferred files
for i, f := range files {
if f.file_type == SYMLINK_FILE {
if f.file_type == FileType_symlink {
link_dest, err := os.Readlink(f.local_path)
if err != nil {
remove = append(remove, i)
@@ -389,10 +338,22 @@ type SendHandler struct {
failed_files, done_files []*File
done_file_ids *utils.Set[string]
transmit_ok_checked bool
progress_update_timer loop.IdType
}
func (self *SendHandler) schedule_progress_update(delay time.Duration) {
if self.progress_update_timer != 0 {
self.lp.RemoveTimer(self.progress_update_timer)
self.progress_update_timer = 0
}
timer_id, err := self.lp.AddTimer(delay, false, self.refresh_progress)
if err == nil {
self.progress_update_timer = timer_id
}
}
func (self *SendHandler) on_file_progress(f *File, change int) {
self.schedule_progress_update()
self.schedule_progress_update(100 * time.Millisecond)
}
func (self *SendHandler) on_file_done(f *File) {
@@ -400,7 +361,13 @@ func (self *SendHandler) on_file_done(f *File) {
if f.err_msg != "" {
self.failed_files = append(self.failed_files, f)
}
self.schedule_progress_update()
self.schedule_progress_update(100 * time.Millisecond)
}
func (self *SendHandler) send_payload(payload string) {
self.lp.QueueWriteString(self.manager.prefix)
self.lp.QueueWriteString(payload)
self.lp.QueueWriteString(self.manager.suffix)
}
func (self *SendHandler) initialize() error {