From f09ee89a96bd70a957fcb6105c4befb84be2fffe Mon Sep 17 00:00:00 2001 From: Lykin <137850705+tiny-craft@users.noreply.github.com> Date: Sat, 27 Jan 2024 14:36:14 +0800 Subject: [PATCH] refactor: split data convert into separate files --- backend/services/browser_service.go | 41 +- backend/utils/convert/base64_convert.go | 21 + backend/utils/convert/binary_convert.go | 27 ++ backend/utils/convert/brotli_convert.go | 35 ++ backend/utils/convert/convert.go | 290 +++++++++++++ backend/utils/convert/deflate_convert.go | 39 ++ backend/utils/convert/gzip_convert.go | 39 ++ backend/utils/convert/hex_convert.go | 29 ++ backend/utils/convert/json_convert.go | 29 ++ backend/utils/convert/msgpack_convert.go | 36 ++ backend/utils/convert/zstd_convert.go | 40 ++ backend/utils/string/common.go | 4 +- backend/utils/string/convert.go | 505 ----------------------- backend/utils/string/key_convert.go | 2 +- frontend/src/langs/en-us.json | 1 + frontend/src/langs/zh-cn.json | 3 +- frontend/src/stores/preferences.js | 2 +- 17 files changed, 613 insertions(+), 530 deletions(-) create mode 100644 backend/utils/convert/base64_convert.go create mode 100644 backend/utils/convert/binary_convert.go create mode 100644 backend/utils/convert/brotli_convert.go create mode 100644 backend/utils/convert/convert.go create mode 100644 backend/utils/convert/deflate_convert.go create mode 100644 backend/utils/convert/gzip_convert.go create mode 100644 backend/utils/convert/hex_convert.go create mode 100644 backend/utils/convert/json_convert.go create mode 100644 backend/utils/convert/msgpack_convert.go create mode 100644 backend/utils/convert/zstd_convert.go delete mode 100644 backend/utils/string/convert.go diff --git a/backend/services/browser_service.go b/backend/services/browser_service.go index dfca769..c920e84 100644 --- a/backend/services/browser_service.go +++ b/backend/services/browser_service.go @@ -21,6 +21,7 @@ import ( "tinyrdm/backend/consts" "tinyrdm/backend/types" "tinyrdm/backend/utils/coll" + convutil "tinyrdm/backend/utils/convert" redis2 "tinyrdm/backend/utils/redis" sliceutil "tinyrdm/backend/utils/slice" strutil "tinyrdm/backend/utils/string" @@ -781,7 +782,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS Value: val, }) if doConvert { - if dv, _, _ := strutil.ConvertTo(val, param.Decode, param.Format); dv != val { + if dv, _, _ := convutil.ConvertTo(val, param.Decode, param.Format); dv != val { items[len(items)-1].DisplayValue = dv } } @@ -828,7 +829,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS Value: strutil.EncodeRedisKey(loadedVal[i+1]), }) if doConvert { - if dv, _, _ := strutil.ConvertTo(loadedVal[i+1], param.Decode, param.Format); dv != loadedVal[i+1] { + if dv, _, _ := convutil.ConvertTo(loadedVal[i+1], param.Decode, param.Format); dv != loadedVal[i+1] { items[len(items)-1].DisplayValue = dv } } @@ -853,7 +854,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS items[i/2].Key = loadedVal[i] items[i/2].Value = strutil.EncodeRedisKey(loadedVal[i+1]) if doConvert { - if dv, _, _ := strutil.ConvertTo(loadedVal[i+1], param.Decode, param.Format); dv != loadedVal[i+1] { + if dv, _, _ := convutil.ConvertTo(loadedVal[i+1], param.Decode, param.Format); dv != loadedVal[i+1] { items[i/2].DisplayValue = dv } } @@ -898,7 +899,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS Value: val, }) if doConvert { - if dv, _, _ := strutil.ConvertTo(val, param.Decode, param.Format); dv != val { + if dv, _, _ := convutil.ConvertTo(val, param.Decode, param.Format); dv != val { items[len(items)-1].DisplayValue = dv } } @@ -918,7 +919,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS for i, val := range loadedKey { items[i].Value = val if doConvert { - if dv, _, _ := strutil.ConvertTo(val, param.Decode, param.Format); dv != val { + if dv, _, _ := convutil.ConvertTo(val, param.Decode, param.Format); dv != val { items[i].DisplayValue = dv } } @@ -966,7 +967,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS Score: score, }) if doConvert { - if dv, _, _ := strutil.ConvertTo(loadedVal[i], param.Decode, param.Format); dv != loadedVal[i] { + if dv, _, _ := convutil.ConvertTo(loadedVal[i], param.Decode, param.Format); dv != loadedVal[i] { items[len(items)-1].DisplayValue = dv } } @@ -1000,7 +1001,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS Value: val, }) if doConvert { - if dv, _, _ := strutil.ConvertTo(val, param.Decode, param.Format); dv != val { + if dv, _, _ := convutil.ConvertTo(val, param.Decode, param.Format); dv != val { items[len(items)-1].DisplayValue = dv } } @@ -1061,7 +1062,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS if vb, merr := json.Marshal(msg.Values); merr != nil { it.DisplayValue = "{}" } else { - it.DisplayValue, _, _ = strutil.ConvertTo(string(vb), types.DECODE_NONE, types.FORMAT_JSON) + it.DisplayValue, _, _ = convutil.ConvertTo(string(vb), types.DECODE_NONE, types.FORMAT_JSON) } if doFilter && !strings.Contains(it.DisplayValue, param.MatchPattern) { continue @@ -1095,7 +1096,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS // blank format indicate auto format func (b *browserService) ConvertValue(value any, decode, format string) (resp types.JSResp) { str := strutil.DecodeRedisKey(value) - value, decode, format = strutil.ConvertTo(str, decode, format) + value, decode, format = convutil.ConvertTo(str, decode, format) resp.Success = true resp.Data = map[string]any{ "value": value, @@ -1138,7 +1139,7 @@ func (b *browserService) SetKeyValue(param types.SetKeyParam) (resp types.JSResp return } else { var saveStr string - if saveStr, err = strutil.SaveAs(str, param.Format, param.Decode); err != nil { + if saveStr, err = convutil.SaveAs(str, param.Format, param.Decode); err != nil { resp.Msg = fmt.Sprintf(`save to type "%s" fail: %s`, param.Format, err.Error()) return } @@ -1249,12 +1250,12 @@ func (b *browserService) SetHashValue(param types.SetHashParam) (resp types.JSRe key := strutil.DecodeRedisKey(param.Key) str := strutil.DecodeRedisKey(param.Value) var saveStr, displayStr string - if saveStr, err = strutil.SaveAs(str, param.Format, param.Decode); err != nil { + if saveStr, err = convutil.SaveAs(str, param.Format, param.Decode); err != nil { resp.Msg = fmt.Sprintf(`save to type "%s" fail: %s`, param.Format, err.Error()) return } if len(param.RetDecode) > 0 && len(param.RetFormat) > 0 { - displayStr, _, _ = strutil.ConvertTo(saveStr, param.RetDecode, param.RetFormat) + displayStr, _, _ = convutil.ConvertTo(saveStr, param.RetDecode, param.RetFormat) } var updated, added, removed []types.HashEntryItem var replaced []types.HashReplaceItem @@ -1472,7 +1473,7 @@ func (b *browserService) SetListItem(param types.SetListParam) (resp types.JSRes } else { // replace index value var saveStr string - if saveStr, err = strutil.SaveAs(str, param.Format, param.Decode); err != nil { + if saveStr, err = convutil.SaveAs(str, param.Format, param.Decode); err != nil { resp.Msg = fmt.Sprintf(`save to type "%s" fail: %s`, param.Format, err.Error()) return } @@ -1483,7 +1484,7 @@ func (b *browserService) SetListItem(param types.SetListParam) (resp types.JSRes } var displayStr string if len(param.RetDecode) > 0 && len(param.RetFormat) > 0 { - displayStr, _, _ = strutil.ConvertTo(saveStr, param.RetDecode, param.RetFormat) + displayStr, _, _ = convutil.ConvertTo(saveStr, param.RetDecode, param.RetFormat) } replaced = append(replaced, types.ListReplaceItem{ Index: param.Index, @@ -1574,7 +1575,7 @@ func (b *browserService) UpdateSetItem(param types.SetSetParam) (resp types.JSRe // insert new value str = strutil.DecodeRedisKey(param.NewValue) var saveStr string - if saveStr, err = strutil.SaveAs(str, param.Format, param.Decode); err != nil { + if saveStr, err = convutil.SaveAs(str, param.Format, param.Decode); err != nil { resp.Msg = fmt.Sprintf(`save to type "%s" fail: %s`, param.Format, err.Error()) return } @@ -1582,7 +1583,7 @@ func (b *browserService) UpdateSetItem(param types.SetSetParam) (resp types.JSRe // add new item var displayStr string if len(param.RetDecode) > 0 && len(param.RetFormat) > 0 { - displayStr, _, _ = strutil.ConvertTo(saveStr, param.RetDecode, param.RetFormat) + displayStr, _, _ = convutil.ConvertTo(saveStr, param.RetDecode, param.RetFormat) } added = append(added, types.SetEntryItem{ Value: saveStr, @@ -1629,7 +1630,7 @@ func (b *browserService) UpdateZSetValue(param types.SetZSetParam) (resp types.J } } else { var saveVal string - if saveVal, err = strutil.SaveAs(newVal, param.Format, param.Decode); err != nil { + if saveVal, err = convutil.SaveAs(newVal, param.Format, param.Decode); err != nil { resp.Msg = fmt.Sprintf(`save to type "%s" fail: %s`, param.Format, err.Error()) return } @@ -1639,7 +1640,7 @@ func (b *browserService) UpdateZSetValue(param types.SetZSetParam) (resp types.J Score: param.Score, Member: saveVal, }).Result() - displayValue, _, _ := strutil.ConvertTo(val, param.RetDecode, param.RetFormat) + displayValue, _, _ := convutil.ConvertTo(val, param.RetDecode, param.RetFormat) if affect > 0 { // add new item added = append(added, types.ZSetEntryItem{ @@ -1667,7 +1668,7 @@ func (b *browserService) UpdateZSetValue(param types.SetZSetParam) (resp types.J Score: param.Score, Member: saveVal, }).Result() - displayValue, _, _ := strutil.ConvertTo(saveVal, param.RetDecode, param.RetFormat) + displayValue, _, _ := convutil.ConvertTo(saveVal, param.RetDecode, param.RetFormat) if affect <= 0 { // no new value added, just update exists item removed = append(removed, types.ZSetEntryItem{ @@ -1793,7 +1794,7 @@ func (b *browserService) AddStreamValue(server string, db int, k any, ID string, updateValues[fieldItems[i].(string)] = fieldItems[i+1] } vb, _ := json.Marshal(updateValues) - displayValue, _, _ := strutil.ConvertTo(string(vb), types.DECODE_NONE, types.FORMAT_JSON) + displayValue, _, _ := convutil.ConvertTo(string(vb), types.DECODE_NONE, types.FORMAT_JSON) resp.Success = true resp.Data = struct { diff --git a/backend/utils/convert/base64_convert.go b/backend/utils/convert/base64_convert.go new file mode 100644 index 0000000..70a19e2 --- /dev/null +++ b/backend/utils/convert/base64_convert.go @@ -0,0 +1,21 @@ +package convutil + +import ( + "encoding/base64" + strutil "tinyrdm/backend/utils/string" +) + +type Base64Convert struct{} + +func (Base64Convert) Encode(str string) (string, bool) { + return base64.StdEncoding.EncodeToString([]byte(str)), true +} + +func (Base64Convert) Decode(str string) (string, bool) { + if decodedStr, err := base64.StdEncoding.DecodeString(str); err == nil { + if s := string(decodedStr); !strutil.ContainsBinary(s) { + return s, true + } + } + return str, false +} diff --git a/backend/utils/convert/binary_convert.go b/backend/utils/convert/binary_convert.go new file mode 100644 index 0000000..376fe85 --- /dev/null +++ b/backend/utils/convert/binary_convert.go @@ -0,0 +1,27 @@ +package convutil + +import ( + "fmt" + "strconv" + "strings" +) + +type BinaryConvert struct{} + +func (BinaryConvert) Encode(str string) (string, bool) { + var result strings.Builder + total := len(str) + for i := 0; i < total; i += 8 { + b, _ := strconv.ParseInt(str[i:i+8], 2, 8) + result.WriteByte(byte(b)) + } + return result.String(), true +} + +func (BinaryConvert) Decode(str string) (string, bool) { + var binary strings.Builder + for _, char := range str { + binary.WriteString(fmt.Sprintf("%08b", int(char))) + } + return binary.String(), true +} diff --git a/backend/utils/convert/brotli_convert.go b/backend/utils/convert/brotli_convert.go new file mode 100644 index 0000000..399f8bc --- /dev/null +++ b/backend/utils/convert/brotli_convert.go @@ -0,0 +1,35 @@ +package convutil + +import ( + "bytes" + "github.com/andybalholm/brotli" + "io" + "strings" +) + +type BrotliConvert struct{} + +func (BrotliConvert) Encode(str string) (string, bool) { + var compress = func(b []byte) (string, error) { + var buf bytes.Buffer + writer := brotli.NewWriter(&buf) + if _, err := writer.Write([]byte(str)); err != nil { + writer.Close() + return "", err + } + writer.Close() + return string(buf.Bytes()), nil + } + if brotliStr, err := compress([]byte(str)); err == nil { + return brotliStr, true + } + return str, false +} + +func (BrotliConvert) Decode(str string) (string, bool) { + reader := brotli.NewReader(strings.NewReader(str)) + if decompressed, err := io.ReadAll(reader); err == nil { + return string(decompressed), true + } + return str, false +} diff --git a/backend/utils/convert/convert.go b/backend/utils/convert/convert.go new file mode 100644 index 0000000..cd8a2df --- /dev/null +++ b/backend/utils/convert/convert.go @@ -0,0 +1,290 @@ +package convutil + +import ( + "errors" + "regexp" + "tinyrdm/backend/types" + strutil "tinyrdm/backend/utils/string" +) + +type DataConvert interface { + Encode(string) (string, bool) + Decode(string) (string, bool) +} + +var ( + jsonConv JsonConvert + base64Conv Base64Convert + binaryConv BinaryConvert + hexConv HexConvert + gzipConv GZipConvert + deflateConv DeflateConvert + zstdConv ZStdConvert + brotliConv BrotliConvert + msgpackConv MsgpackConvert +) + +// ConvertTo convert string to specified type +// @param decodeType empty string indicates automatic detection +// @param formatType empty string indicates automatic detection +func ConvertTo(str, decodeType, formatType string) (value, resultDecode, resultFormat string) { + if len(str) <= 0 { + // empty content + if len(formatType) <= 0 { + resultFormat = types.FORMAT_RAW + } else { + resultFormat = formatType + } + if len(decodeType) <= 0 { + resultDecode = types.DECODE_NONE + } else { + resultDecode = decodeType + } + return + } + + // decode first + value, resultDecode = decodeWith(str, decodeType) + // then format content + value, resultFormat = viewAs(value, formatType) + return +} + +func decodeWith(str, decodeType string) (value, resultDecode string) { + if len(decodeType) > 0 { + switch decodeType { + case types.DECODE_NONE: + value = str + + case types.DECODE_BASE64: + if base64Str, ok := base64Conv.Decode(str); ok { + value = base64Str + } else { + value = str + } + + case types.DECODE_GZIP: + if gzipStr, ok := gzipConv.Decode(str); ok { + value = gzipStr + } else { + value = str + } + + case types.DECODE_DEFLATE: + if falteStr, ok := deflateConv.Decode(str); ok { + value = falteStr + } else { + value = str + } + + case types.DECODE_ZSTD: + if zstdStr, ok := zstdConv.Decode(str); ok { + value = zstdStr + } else { + value = str + } + + case types.DECODE_BROTLI: + if brotliStr, ok := brotliConv.Decode(str); ok { + value = brotliStr + } else { + value = str + } + + case types.DECODE_MSGPACK: + if msgpackStr, ok := msgpackConv.Decode(str); ok { + value = msgpackStr + } else { + value = str + } + } + resultDecode = decodeType + return + } + + return autoDecode(str) +} + +// attempt try possible decode method +// if no decode is possible, it will return the origin string value and "none" decode type +func autoDecode(str string) (value, resultDecode string) { + if len(str) > 0 { + // pure digit content may incorrect regard as some encoded type, skip decode + if match, _ := regexp.MatchString(`^\d+$`, str); !match { + var ok bool + if len(str)%4 == 0 && len(str) >= 12 && !strutil.IsSameChar(str) { + if value, ok = base64Conv.Decode(str); ok { + resultDecode = types.DECODE_BASE64 + return + } + } + + if value, ok = gzipConv.Decode(str); ok { + resultDecode = types.DECODE_GZIP + return + } + + // FIXME: skip decompress with deflate due to incorrect format checking + //if value, ok = decodeDeflate(str); ok { + // resultDecode = types.DECODE_DEFLATE + // return + //} + + if value, ok = zstdConv.Decode(str); ok { + resultDecode = types.DECODE_ZSTD + return + } + + // FIXME: skip decompress with brotli due to incorrect format checking + //if value, ok = decodeBrotli(str); ok { + // resultDecode = types.DECODE_BROTLI + // return + //} + + if value, ok = msgpackConv.Decode(str); ok { + resultDecode = types.DECODE_MSGPACK + return + } + } + } + + value = str + resultDecode = types.DECODE_NONE + return +} + +func viewAs(str, formatType string) (value, resultFormat string) { + if len(formatType) > 0 { + switch formatType { + case types.FORMAT_RAW, types.FORMAT_YAML, types.FORMAT_XML: + value = str + + case types.FORMAT_JSON: + if jsonStr, ok := jsonConv.Decode(str); ok { + value = jsonStr + } else { + value = str + } + + case types.FORMAT_HEX: + if hexStr, ok := hexConv.Decode(str); ok { + value = hexStr + } else { + value = str + } + + case types.FORMAT_BINARY: + if binStr, ok := binaryConv.Decode(str); ok { + value = binStr + } else { + value = str + } + } + resultFormat = formatType + return + } + + return autoViewAs(str) +} + +// attempt automatic convert to possible types +// if no conversion is possible, it will return the origin string value and "plain text" type +func autoViewAs(str string) (value, resultFormat string) { + if len(str) > 0 { + var ok bool + if value, ok = jsonConv.Decode(str); ok { + resultFormat = types.FORMAT_JSON + return + } + + if strutil.ContainsBinary(str) { + if value, ok = hexConv.Decode(str); ok { + resultFormat = types.FORMAT_HEX + return + } + } + } + + value = str + resultFormat = types.FORMAT_RAW + return +} + +func SaveAs(str, format, decode string) (value string, err error) { + value = str + switch format { + case types.FORMAT_JSON: + if jsonStr, ok := jsonConv.Encode(str); ok { + value = jsonStr + } else { + err = errors.New("invalid json data") + return + } + + case types.FORMAT_HEX: + if hexStr, ok := hexConv.Encode(str); ok { + value = hexStr + } else { + err = errors.New("invalid hex data") + return + } + + case types.FORMAT_BINARY: + if binStr, ok := binaryConv.Encode(str); ok { + value = binStr + } else { + err = errors.New("invalid binary data") + return + } + } + + switch decode { + case types.DECODE_NONE: + return + + case types.DECODE_BASE64: + value, _ = base64Conv.Encode(value) + return + + case types.DECODE_GZIP: + if gzipStr, ok := gzipConv.Encode(str); ok { + value = gzipStr + } else { + err = errors.New("fail to build gzip") + } + return + + case types.DECODE_DEFLATE: + if deflateStr, ok := deflateConv.Encode(str); ok { + value = deflateStr + } else { + err = errors.New("fail to build deflate") + } + return + + case types.DECODE_ZSTD: + if zstdStr, ok := zstdConv.Encode(str); ok { + value = zstdStr + } else { + err = errors.New("fail to build zstd") + } + return + + case types.DECODE_BROTLI: + if brotliStr, ok := brotliConv.Encode(str); ok { + value = brotliStr + } else { + err = errors.New("fail to build brotli") + } + return + + case types.DECODE_MSGPACK: + if msgpackStr, ok := msgpackConv.Encode(str); ok { + value = msgpackStr + } else { + err = errors.New("fail to build msgpack") + } + return + } + return str, nil +} diff --git a/backend/utils/convert/deflate_convert.go b/backend/utils/convert/deflate_convert.go new file mode 100644 index 0000000..d1e2678 --- /dev/null +++ b/backend/utils/convert/deflate_convert.go @@ -0,0 +1,39 @@ +package convutil + +import ( + "bytes" + "github.com/klauspost/compress/flate" + "io" + "strings" +) + +type DeflateConvert struct{} + +func (d DeflateConvert) Encode(str string) (string, bool) { + var compress = func(b []byte) (string, error) { + var buf bytes.Buffer + writer, err := flate.NewWriter(&buf, flate.DefaultCompression) + if err != nil { + return "", err + } + if _, err = writer.Write([]byte(str)); err != nil { + writer.Close() + return "", err + } + writer.Close() + return string(buf.Bytes()), nil + } + if deflateStr, err := compress([]byte(str)); err == nil { + return deflateStr, true + } + return str, false +} + +func (d DeflateConvert) Decode(str string) (string, bool) { + reader := flate.NewReader(strings.NewReader(str)) + defer reader.Close() + if decompressed, err := io.ReadAll(reader); err == nil { + return string(decompressed), true + } + return str, false +} diff --git a/backend/utils/convert/gzip_convert.go b/backend/utils/convert/gzip_convert.go new file mode 100644 index 0000000..dc3c856 --- /dev/null +++ b/backend/utils/convert/gzip_convert.go @@ -0,0 +1,39 @@ +package convutil + +import ( + "bytes" + "github.com/klauspost/compress/gzip" + "io" + "strings" +) + +type GZipConvert struct{} + +func (GZipConvert) Encode(str string) (string, bool) { + var compress = func(b []byte) (string, error) { + var buf bytes.Buffer + writer := gzip.NewWriter(&buf) + if _, err := writer.Write([]byte(str)); err != nil { + writer.Close() + return "", err + } + writer.Close() + return string(buf.Bytes()), nil + } + + if gzipStr, err := compress([]byte(str)); err == nil { + return gzipStr, true + } + return str, false +} + +func (GZipConvert) Decode(str string) (string, bool) { + if reader, err := gzip.NewReader(strings.NewReader(str)); err == nil { + defer reader.Close() + var decompressed []byte + if decompressed, err = io.ReadAll(reader); err == nil { + return string(decompressed), true + } + } + return str, false +} diff --git a/backend/utils/convert/hex_convert.go b/backend/utils/convert/hex_convert.go new file mode 100644 index 0000000..c2c67e2 --- /dev/null +++ b/backend/utils/convert/hex_convert.go @@ -0,0 +1,29 @@ +package convutil + +import ( + "encoding/hex" + "strings" +) + +type HexConvert struct{} + +func (HexConvert) Encode(str string) (string, bool) { + hexStrArr := strings.Split(str, "\\x") + hexStr := strings.Join(hexStrArr, "") + if decodeStr, err := hex.DecodeString(hexStr); err == nil { + return string(decodeStr), true + } + + return str, false +} + +func (HexConvert) Decode(str string) (string, bool) { + decodeStr := hex.EncodeToString([]byte(str)) + decodeStr = strings.ToUpper(decodeStr) + var resultStr strings.Builder + for i := 0; i < len(decodeStr); i += 2 { + resultStr.WriteString("\\x") + resultStr.WriteString(decodeStr[i : i+2]) + } + return resultStr.String(), true +} diff --git a/backend/utils/convert/json_convert.go b/backend/utils/convert/json_convert.go new file mode 100644 index 0000000..aca2eec --- /dev/null +++ b/backend/utils/convert/json_convert.go @@ -0,0 +1,29 @@ +package convutil + +import ( + "bytes" + "encoding/json" + "strings" +) + +type JsonConvert struct{} + +func (JsonConvert) Decode(str string) (string, bool) { + trimedStr := strings.TrimSpace(str) + if (strings.HasPrefix(trimedStr, "{") && strings.HasSuffix(trimedStr, "}")) || + (strings.HasPrefix(trimedStr, "[") && strings.HasSuffix(trimedStr, "]")) { + var out bytes.Buffer + if err := json.Indent(&out, []byte(trimedStr), "", " "); err == nil { + return out.String(), true + } + } + return str, false +} + +func (JsonConvert) Encode(str string) (string, bool) { + var dst bytes.Buffer + if err := json.Compact(&dst, []byte(str)); err != nil { + return str, false + } + return dst.String(), true +} diff --git a/backend/utils/convert/msgpack_convert.go b/backend/utils/convert/msgpack_convert.go new file mode 100644 index 0000000..0a41551 --- /dev/null +++ b/backend/utils/convert/msgpack_convert.go @@ -0,0 +1,36 @@ +package convutil + +import ( + "bytes" + "encoding/json" + "github.com/vmihailenco/msgpack/v5" +) + +type MsgpackConvert struct{} + +func (MsgpackConvert) Encode(str string) (string, bool) { + var err error + var buf bytes.Buffer + enc := msgpack.NewEncoder(&buf) + if err = enc.EncodeString(str); err == nil { + return buf.String(), true + } + + return str, false +} + +func (MsgpackConvert) Decode(str string) (string, bool) { + var decodedStr string + if err := msgpack.Unmarshal([]byte(str), &decodedStr); err == nil { + return decodedStr, true + } + + var decodedObj map[string]any + if err := msgpack.Unmarshal([]byte(str), &decodedObj); err == nil { + if b, err := json.Marshal(decodedObj); err == nil { + return string(b), true + } + } + + return str, false +} diff --git a/backend/utils/convert/zstd_convert.go b/backend/utils/convert/zstd_convert.go new file mode 100644 index 0000000..a768a44 --- /dev/null +++ b/backend/utils/convert/zstd_convert.go @@ -0,0 +1,40 @@ +package convutil + +import ( + "bytes" + "github.com/klauspost/compress/zstd" + "io" + "strings" +) + +type ZStdConvert struct{} + +func (ZStdConvert) Encode(str string) (string, bool) { + var compress = func(b []byte) (string, error) { + var buf bytes.Buffer + writer, err := zstd.NewWriter(&buf) + if err != nil { + return "", err + } + if _, err = writer.Write([]byte(str)); err != nil { + writer.Close() + return "", err + } + writer.Close() + return string(buf.Bytes()), nil + } + if zstdStr, err := compress([]byte(str)); err == nil { + return zstdStr, true + } + return str, false +} + +func (ZStdConvert) Decode(str string) (string, bool) { + if reader, err := zstd.NewReader(strings.NewReader(str)); err == nil { + defer reader.Close() + if decompressed, err := io.ReadAll(reader); err == nil { + return string(decompressed), true + } + } + return str, false +} diff --git a/backend/utils/string/common.go b/backend/utils/string/common.go index 129baab..acd4c13 100644 --- a/backend/utils/string/common.go +++ b/backend/utils/string/common.go @@ -4,7 +4,7 @@ import ( "unicode" ) -func containsBinary(str string) bool { +func ContainsBinary(str string) bool { //buf := []byte(str) //size := 0 //for start := 0; start < len(buf); start += size { @@ -25,7 +25,7 @@ func containsBinary(str string) bool { return false } -func isSameChar(str string) bool { +func IsSameChar(str string) bool { if len(str) <= 0 { return false } diff --git a/backend/utils/string/convert.go b/backend/utils/string/convert.go deleted file mode 100644 index aef8533..0000000 --- a/backend/utils/string/convert.go +++ /dev/null @@ -1,505 +0,0 @@ -package strutil - -import ( - "bytes" - "encoding/base64" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "github.com/andybalholm/brotli" - "github.com/klauspost/compress/flate" - "github.com/klauspost/compress/gzip" - "github.com/klauspost/compress/zstd" - "github.com/vmihailenco/msgpack/v5" - "io" - "regexp" - "strconv" - "strings" - "tinyrdm/backend/types" -) - -// ConvertTo convert string to specified type -// @param decodeType empty string indicates automatic detection -// @param formatType empty string indicates automatic detection -func ConvertTo(str, decodeType, formatType string) (value, resultDecode, resultFormat string) { - if len(str) <= 0 { - // empty content - if len(formatType) <= 0 { - resultFormat = types.FORMAT_RAW - } else { - resultFormat = formatType - } - if len(decodeType) <= 0 { - resultDecode = types.DECODE_NONE - } else { - resultDecode = decodeType - } - return - } - - // decode first - value, resultDecode = decodeWith(str, decodeType) - // then format content - value, resultFormat = viewAs(value, formatType) - return -} - -func decodeWith(str, decodeType string) (value, resultDecode string) { - if len(decodeType) > 0 { - switch decodeType { - case types.DECODE_NONE: - value = str - resultDecode = decodeType - return - - case types.DECODE_BASE64: - if base64Str, ok := decodeBase64(str); ok { - value = base64Str - } else { - value = str - } - resultDecode = decodeType - return - - case types.DECODE_GZIP: - if gzipStr, ok := decodeGZip(str); ok { - value = gzipStr - } else { - value = str - } - resultDecode = decodeType - return - - case types.DECODE_DEFLATE: - if falteStr, ok := decodeDeflate(str); ok { - value = falteStr - } else { - value = str - } - resultDecode = decodeType - return - - case types.DECODE_ZSTD: - if zstdStr, ok := decodeZStd(str); ok { - value = zstdStr - } else { - value = str - } - resultDecode = decodeType - return - - case types.DECODE_BROTLI: - if brotliStr, ok := decodeBrotli(str); ok { - value = brotliStr - } else { - value = str - } - resultDecode = decodeType - return - } - } - - return autoDecode(str) -} - -// attempt try possible decode method -// if no decode is possible, it will return the origin string value and "none" decode type -func autoDecode(str string) (value, resultDecode string) { - if len(str) > 0 { - // pure digit content may incorrect regard as some encoded type, skip decode - if match, _ := regexp.MatchString(`^\d+$`, str); !match { - var ok bool - if len(str)%4 == 0 && len(str) >= 12 && !isSameChar(str) { - if value, ok = decodeBase64(str); ok { - resultDecode = types.DECODE_BASE64 - return - } - } - - if value, ok = decodeGZip(str); ok { - resultDecode = types.DECODE_GZIP - return - } - - // FIXME: skip decompress with deflate due to incorrect format checking - //if value, ok = decodeDeflate(str); ok { - // resultDecode = types.DECODE_DEFLATE - // return - //} - - if value, ok = decodeZStd(str); ok { - resultDecode = types.DECODE_ZSTD - return - } - - // FIXME: skip decompress with brotli due to incorrect format checking - //if value, ok = decodeBrotli(str); ok { - // resultDecode = types.DECODE_BROTLI - // return - //} - - if value, ok = decodeMsgpack(str); ok { - resultDecode = types.DECODE_MSGPACK - return - } - } - } - - value = str - resultDecode = types.DECODE_NONE - return -} - -func viewAs(str, formatType string) (value, resultFormat string) { - if len(formatType) > 0 { - switch formatType { - case types.FORMAT_RAW, types.FORMAT_YAML, types.FORMAT_XML: - value = str - resultFormat = formatType - return - - case types.FORMAT_JSON: - if jsonStr, ok := decodeJson(str); ok { - value = jsonStr - } else { - value = str - } - resultFormat = formatType - return - - case types.FORMAT_HEX: - if hexStr, ok := decodeToHex(str); ok { - value = hexStr - } else { - value = str - } - resultFormat = formatType - return - - case types.FORMAT_BINARY: - if binStr, ok := decodeBinary(str); ok { - value = binStr - } else { - value = str - } - resultFormat = formatType - return - } - } - - return autoViewAs(str) -} - -// attempt automatic convert to possible types -// if no conversion is possible, it will return the origin string value and "plain text" type -func autoViewAs(str string) (value, resultFormat string) { - if len(str) > 0 { - var ok bool - if value, ok = decodeJson(str); ok { - resultFormat = types.FORMAT_JSON - return - } - - if containsBinary(str) { - if value, ok = decodeToHex(str); ok { - resultFormat = types.FORMAT_HEX - return - } - } - } - - value = str - resultFormat = types.FORMAT_RAW - return -} - -func decodeJson(str string) (string, bool) { - trimedStr := strings.TrimSpace(str) - if (strings.HasPrefix(trimedStr, "{") && strings.HasSuffix(trimedStr, "}")) || - (strings.HasPrefix(trimedStr, "[") && strings.HasSuffix(trimedStr, "]")) { - var out bytes.Buffer - if err := json.Indent(&out, []byte(trimedStr), "", " "); err == nil { - return out.String(), true - } - } - return str, false -} - -func decodeBase64(str string) (string, bool) { - if decodedStr, err := base64.StdEncoding.DecodeString(str); err == nil { - if s := string(decodedStr); !containsBinary(s) { - return s, true - } - } - return str, false -} - -func decodeBinary(str string) (string, bool) { - var binary strings.Builder - for _, char := range str { - binary.WriteString(fmt.Sprintf("%08b", int(char))) - } - return binary.String(), true -} - -func decodeToHex(str string) (string, bool) { - decodeStr := hex.EncodeToString([]byte(str)) - var resultStr strings.Builder - for i := 0; i < len(decodeStr); i += 2 { - resultStr.WriteString("\\x") - resultStr.WriteString(strings.ToUpper(decodeStr[i : i+2])) - } - return resultStr.String(), true -} - -func decodeGZip(str string) (string, bool) { - if reader, err := gzip.NewReader(strings.NewReader(str)); err == nil { - defer reader.Close() - var decompressed []byte - if decompressed, err = io.ReadAll(reader); err == nil { - return string(decompressed), true - } - } - return str, false -} - -func decodeDeflate(str string) (string, bool) { - reader := flate.NewReader(strings.NewReader(str)) - defer reader.Close() - if decompressed, err := io.ReadAll(reader); err == nil { - return string(decompressed), true - } - return str, false -} - -func decodeZStd(str string) (string, bool) { - if reader, err := zstd.NewReader(strings.NewReader(str)); err == nil { - defer reader.Close() - if decompressed, err := io.ReadAll(reader); err == nil { - return string(decompressed), true - } - } - return str, false -} - -func decodeBrotli(str string) (string, bool) { - reader := brotli.NewReader(strings.NewReader(str)) - if decompressed, err := io.ReadAll(reader); err == nil { - return string(decompressed), true - } - return str, false -} - -func decodeMsgpack(str string) (string, bool) { - var decodedStr string - if err := msgpack.Unmarshal([]byte(str), &decodedStr); err == nil { - return decodedStr, true - } - - var decodedObj map[string]any - if err := msgpack.Unmarshal([]byte(str), &decodedObj); err == nil { - if b, err := json.Marshal(decodedObj); err == nil { - return string(b), true - } - } - - return str, false -} - -func SaveAs(str, format, decode string) (value string, err error) { - value = str - switch format { - case types.FORMAT_JSON: - if jsonStr, ok := encodeJson(str); ok { - value = jsonStr - } else { - err = errors.New("invalid json data") - return - } - - case types.FORMAT_HEX: - if hexStr, ok := encodeHex(str); ok { - value = hexStr - } else { - err = errors.New("invalid hex data") - return - } - - case types.FORMAT_BINARY: - if binStr, ok := encodeBinary(str); ok { - value = binStr - } else { - err = errors.New("invalid binary data") - return - } - } - - switch decode { - case types.DECODE_NONE: - return - - case types.DECODE_BASE64: - value, _ = encodeBase64(value) - return - - case types.DECODE_GZIP: - if gzipStr, ok := encodeGZip(str); ok { - value = gzipStr - } else { - err = errors.New("fail to build gzip") - } - return - - case types.DECODE_DEFLATE: - if deflateStr, ok := encodeDeflate(str); ok { - value = deflateStr - } else { - err = errors.New("fail to build deflate") - } - return - - case types.DECODE_ZSTD: - if zstdStr, ok := encodeZStd(str); ok { - value = zstdStr - } else { - err = errors.New("fail to build zstd") - } - return - - case types.DECODE_BROTLI: - if brotliStr, ok := encodeBrotli(str); ok { - value = brotliStr - } else { - err = errors.New("fail to build brotli") - } - return - - case types.DECODE_MSGPACK: - if msgpackStr, ok := encodeMsgpack(str); ok { - value = msgpackStr - } else { - err = errors.New("fail to build msgpack") - } - return - } - return str, nil -} - -func encodeJson(str string) (string, bool) { - var dst bytes.Buffer - if err := json.Compact(&dst, []byte(str)); err != nil { - return str, false - } - return dst.String(), true -} - -func encodeBase64(str string) (string, bool) { - return base64.StdEncoding.EncodeToString([]byte(str)), true -} - -func encodeBinary(str string) (string, bool) { - var result strings.Builder - total := len(str) - for i := 0; i < total; i += 8 { - b, _ := strconv.ParseInt(str[i:i+8], 2, 8) - result.WriteByte(byte(b)) - } - return result.String(), true -} - -func encodeHex(str string) (string, bool) { - hexStrArr := strings.Split(str, "\\x") - hexStr := strings.Join(hexStrArr, "") - if decodeStr, err := hex.DecodeString(hexStr); err == nil { - return string(decodeStr), true - } - - return str, false -} - -func encodeGZip(str string) (string, bool) { - var compress = func(b []byte) (string, error) { - var buf bytes.Buffer - writer := gzip.NewWriter(&buf) - if _, err := writer.Write([]byte(str)); err != nil { - writer.Close() - return "", err - } - writer.Close() - return string(buf.Bytes()), nil - } - - if gzipStr, err := compress([]byte(str)); err == nil { - return gzipStr, true - } - return str, false -} - -func encodeDeflate(str string) (string, bool) { - var compress = func(b []byte) (string, error) { - var buf bytes.Buffer - writer, err := flate.NewWriter(&buf, flate.DefaultCompression) - if err != nil { - return "", err - } - if _, err = writer.Write([]byte(str)); err != nil { - writer.Close() - return "", err - } - writer.Close() - return string(buf.Bytes()), nil - } - if deflateStr, err := compress([]byte(str)); err == nil { - return deflateStr, true - } - return str, false -} - -func encodeZStd(str string) (string, bool) { - var compress = func(b []byte) (string, error) { - var buf bytes.Buffer - writer, err := zstd.NewWriter(&buf) - if err != nil { - return "", err - } - if _, err = writer.Write([]byte(str)); err != nil { - writer.Close() - return "", err - } - writer.Close() - return string(buf.Bytes()), nil - } - if zstdStr, err := compress([]byte(str)); err == nil { - return zstdStr, true - } - return str, false -} - -func encodeBrotli(str string) (string, bool) { - var compress = func(b []byte) (string, error) { - var buf bytes.Buffer - writer := brotli.NewWriter(&buf) - if _, err := writer.Write([]byte(str)); err != nil { - writer.Close() - return "", err - } - writer.Close() - return string(buf.Bytes()), nil - } - if brotliStr, err := compress([]byte(str)); err == nil { - return brotliStr, true - } - return str, false -} - -func encodeMsgpack(str string) (string, bool) { - var err error - var buf bytes.Buffer - enc := msgpack.NewEncoder(&buf) - if err = enc.EncodeString(str); err == nil { - return buf.String(), true - } - - return str, false -} diff --git a/backend/utils/string/key_convert.go b/backend/utils/string/key_convert.go index ef5b3a5..fcba4c9 100644 --- a/backend/utils/string/key_convert.go +++ b/backend/utils/string/key_convert.go @@ -8,7 +8,7 @@ import ( // EncodeRedisKey encode the redis key to integer array // if key contains binary which could not display on ui, convert the key to char array func EncodeRedisKey(key string) any { - if containsBinary(key) { + if ContainsBinary(key) { b := []byte(key) arr := make([]int, len(b)) for i, bb := range b { diff --git a/frontend/src/langs/en-us.json b/frontend/src/langs/en-us.json index 5104256..20e15f2 100644 --- a/frontend/src/langs/en-us.json +++ b/frontend/src/langs/en-us.json @@ -139,6 +139,7 @@ "action": "Action", "type": "Type", "cli_welcome": "Welcome to Tiny RDM Redis Console", + "retrieving_version": "Retrieving for new version", "sub_tab": { "status": "Status", "key_detail": "Key Detail", diff --git a/frontend/src/langs/zh-cn.json b/frontend/src/langs/zh-cn.json index afc9ab5..8cc9c0a 100644 --- a/frontend/src/langs/zh-cn.json +++ b/frontend/src/langs/zh-cn.json @@ -139,6 +139,7 @@ "action": "操作", "type": "类型", "cli_welcome": "欢迎使用Tiny RDM的Redis命令行控制台", + "retrieving_version": "正在检索新版本", "sub_tab": { "status": "状态", "key_detail": "键详情", @@ -153,7 +154,7 @@ "browser": "数据浏览", "log": "日志", "wechat_official": "微信公众号", - "follow_x": "关注的我\uD835\uDD4F", + "follow_x": "关注我的\uD835\uDD4F", "github": "Github" }, "dialogue": { diff --git a/frontend/src/stores/preferences.js b/frontend/src/stores/preferences.js index af54834..1eb5b86 100644 --- a/frontend/src/stores/preferences.js +++ b/frontend/src/stores/preferences.js @@ -338,7 +338,7 @@ const usePreferencesStore = defineStore('preferences', { async checkForUpdate(manual = false) { let msgRef = null if (manual) { - msgRef = $message.loading('Retrieving for new version', { duration: 0 }) + msgRef = $message.loading(i18nGlobal.t('interface.retrieving_version'), { duration: 0 }) } try { const { success, data = {} } = await CheckForUpdate()