// License: GPLv3 Copyright: 2023, Kovid Goyal, 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 != "" && field.IsExported() { val := v.FieldByIndex(field.Index) encoded_val := "" switch val.Kind() { case reflect.String: if sval := val.String(); sval != "" { enc := field.Tag.Get("encoding") switch enc { case "base64": encoded_val = base64.RawStdEncoding.EncodeToString(utils.UnsafeStringToBytes(sval)) default: encoded_val = escape_semicolons(wcswidth.StripEscapeCodes(sval)) } } case reflect.Slice: switch val.Type().Elem().Kind() { case reflect.Uint8: if bval := val.Bytes(); len(bval) > 0 { encoded_val = base64.RawStdEncoding.EncodeToString(bval) } } case reflect.Uint64: if uival := val.Uint(); uival != 0 { encoded_val = strconv.FormatUint(uival, 10) } default: if val.CanInterface() { switch field := val.Interface().(type) { case Field: if !field.IsDefault() { encoded_val = field.Serialize() } case time.Duration: if field != 0 { encoded_val = strconv.FormatInt(int64(field), 10) } case fs.FileMode: if field = field.Perm(); 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() }