diff --git a/backend/services/browser_service.go b/backend/services/browser_service.go index c920e84..7c318e8 100644 --- a/backend/services/browser_service.go +++ b/backend/services/browser_service.go @@ -740,6 +740,8 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS } } + decoder := Preferences().GetDecoder() + switch data.KeyType { case "string": var str string @@ -782,7 +784,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS Value: val, }) if doConvert { - if dv, _, _ := convutil.ConvertTo(val, param.Decode, param.Format); dv != val { + if dv, _, _ := convutil.ConvertTo(val, param.Decode, param.Format, decoder); dv != val { items[len(items)-1].DisplayValue = dv } } @@ -829,7 +831,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS Value: strutil.EncodeRedisKey(loadedVal[i+1]), }) if doConvert { - if dv, _, _ := convutil.ConvertTo(loadedVal[i+1], param.Decode, param.Format); dv != loadedVal[i+1] { + if dv, _, _ := convutil.ConvertTo(loadedVal[i+1], param.Decode, param.Format, decoder); dv != loadedVal[i+1] { items[len(items)-1].DisplayValue = dv } } @@ -854,7 +856,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, _, _ := convutil.ConvertTo(loadedVal[i+1], param.Decode, param.Format); dv != loadedVal[i+1] { + if dv, _, _ := convutil.ConvertTo(loadedVal[i+1], param.Decode, param.Format, decoder); dv != loadedVal[i+1] { items[i/2].DisplayValue = dv } } @@ -899,7 +901,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS Value: val, }) if doConvert { - if dv, _, _ := convutil.ConvertTo(val, param.Decode, param.Format); dv != val { + if dv, _, _ := convutil.ConvertTo(val, param.Decode, param.Format, decoder); dv != val { items[len(items)-1].DisplayValue = dv } } @@ -919,7 +921,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS for i, val := range loadedKey { items[i].Value = val if doConvert { - if dv, _, _ := convutil.ConvertTo(val, param.Decode, param.Format); dv != val { + if dv, _, _ := convutil.ConvertTo(val, param.Decode, param.Format, decoder); dv != val { items[i].DisplayValue = dv } } @@ -967,7 +969,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS Score: score, }) if doConvert { - if dv, _, _ := convutil.ConvertTo(loadedVal[i], param.Decode, param.Format); dv != loadedVal[i] { + if dv, _, _ := convutil.ConvertTo(loadedVal[i], param.Decode, param.Format, decoder); dv != loadedVal[i] { items[len(items)-1].DisplayValue = dv } } @@ -1001,7 +1003,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS Value: val, }) if doConvert { - if dv, _, _ := convutil.ConvertTo(val, param.Decode, param.Format); dv != val { + if dv, _, _ := convutil.ConvertTo(val, param.Decode, param.Format, decoder); dv != val { items[len(items)-1].DisplayValue = dv } } @@ -1062,7 +1064,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, _, _ = convutil.ConvertTo(string(vb), types.DECODE_NONE, types.FORMAT_JSON) + it.DisplayValue, _, _ = convutil.ConvertTo(string(vb), types.DECODE_NONE, types.FORMAT_JSON, decoder) } if doFilter && !strings.Contains(it.DisplayValue, param.MatchPattern) { continue @@ -1096,7 +1098,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 = convutil.ConvertTo(str, decode, format) + value, decode, format = convutil.ConvertTo(str, decode, format, Preferences().GetDecoder()) resp.Success = true resp.Data = map[string]any{ "value": value, @@ -1139,7 +1141,7 @@ func (b *browserService) SetKeyValue(param types.SetKeyParam) (resp types.JSResp return } else { var saveStr string - if saveStr, err = convutil.SaveAs(str, param.Format, param.Decode); err != nil { + if saveStr, err = convutil.SaveAs(str, param.Format, param.Decode, Preferences().GetDecoder()); err != nil { resp.Msg = fmt.Sprintf(`save to type "%s" fail: %s`, param.Format, err.Error()) return } @@ -1250,12 +1252,13 @@ 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 = convutil.SaveAs(str, param.Format, param.Decode); err != nil { + decoder := Preferences().GetDecoder() + if saveStr, err = convutil.SaveAs(str, param.Format, param.Decode, decoder); 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, _, _ = convutil.ConvertTo(saveStr, param.RetDecode, param.RetFormat) + displayStr, _, _ = convutil.ConvertTo(saveStr, param.RetDecode, param.RetFormat, decoder) } var updated, added, removed []types.HashEntryItem var replaced []types.HashReplaceItem @@ -1473,7 +1476,8 @@ func (b *browserService) SetListItem(param types.SetListParam) (resp types.JSRes } else { // replace index value var saveStr string - if saveStr, err = convutil.SaveAs(str, param.Format, param.Decode); err != nil { + decoder := Preferences().GetDecoder() + if saveStr, err = convutil.SaveAs(str, param.Format, param.Decode, decoder); err != nil { resp.Msg = fmt.Sprintf(`save to type "%s" fail: %s`, param.Format, err.Error()) return } @@ -1484,7 +1488,7 @@ func (b *browserService) SetListItem(param types.SetListParam) (resp types.JSRes } var displayStr string if len(param.RetDecode) > 0 && len(param.RetFormat) > 0 { - displayStr, _, _ = convutil.ConvertTo(saveStr, param.RetDecode, param.RetFormat) + displayStr, _, _ = convutil.ConvertTo(saveStr, param.RetDecode, param.RetFormat, decoder) } replaced = append(replaced, types.ListReplaceItem{ Index: param.Index, @@ -1574,8 +1578,9 @@ func (b *browserService) UpdateSetItem(param types.SetSetParam) (resp types.JSRe // insert new value str = strutil.DecodeRedisKey(param.NewValue) + decoder := Preferences().GetDecoder() var saveStr string - if saveStr, err = convutil.SaveAs(str, param.Format, param.Decode); err != nil { + if saveStr, err = convutil.SaveAs(str, param.Format, param.Decode, decoder); err != nil { resp.Msg = fmt.Sprintf(`save to type "%s" fail: %s`, param.Format, err.Error()) return } @@ -1583,7 +1588,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, _, _ = convutil.ConvertTo(saveStr, param.RetDecode, param.RetFormat) + displayStr, _, _ = convutil.ConvertTo(saveStr, param.RetDecode, param.RetFormat, decoder) } added = append(added, types.SetEntryItem{ Value: saveStr, @@ -1620,6 +1625,7 @@ func (b *browserService) UpdateZSetValue(param types.SetZSetParam) (resp types.J var added, updated, removed []types.ZSetEntryItem var replaced []types.ZSetReplaceItem var affect int64 + decoder := Preferences().GetDecoder() if len(newVal) <= 0 { // no new value, delete value if affect, err = client.ZRem(ctx, key, val).Result(); affect > 0 { @@ -1630,7 +1636,7 @@ func (b *browserService) UpdateZSetValue(param types.SetZSetParam) (resp types.J } } else { var saveVal string - if saveVal, err = convutil.SaveAs(newVal, param.Format, param.Decode); err != nil { + if saveVal, err = convutil.SaveAs(newVal, param.Format, param.Decode, decoder); err != nil { resp.Msg = fmt.Sprintf(`save to type "%s" fail: %s`, param.Format, err.Error()) return } @@ -1640,7 +1646,7 @@ func (b *browserService) UpdateZSetValue(param types.SetZSetParam) (resp types.J Score: param.Score, Member: saveVal, }).Result() - displayValue, _, _ := convutil.ConvertTo(val, param.RetDecode, param.RetFormat) + displayValue, _, _ := convutil.ConvertTo(val, param.RetDecode, param.RetFormat, decoder) if affect > 0 { // add new item added = append(added, types.ZSetEntryItem{ @@ -1668,7 +1674,7 @@ func (b *browserService) UpdateZSetValue(param types.SetZSetParam) (resp types.J Score: param.Score, Member: saveVal, }).Result() - displayValue, _, _ := convutil.ConvertTo(saveVal, param.RetDecode, param.RetFormat) + displayValue, _, _ := convutil.ConvertTo(saveVal, param.RetDecode, param.RetFormat, decoder) if affect <= 0 { // no new value added, just update exists item removed = append(removed, types.ZSetEntryItem{ @@ -1794,7 +1800,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, _, _ := convutil.ConvertTo(string(vb), types.DECODE_NONE, types.FORMAT_JSON) + displayValue, _, _ := convutil.ConvertTo(string(vb), types.DECODE_NONE, types.FORMAT_JSON, Preferences().GetDecoder()) resp.Success = true resp.Data = struct { diff --git a/backend/services/preferences_service.go b/backend/services/preferences_service.go index 05705df..0f1ff69 100644 --- a/backend/services/preferences_service.go +++ b/backend/services/preferences_service.go @@ -13,11 +13,14 @@ import ( storage2 "tinyrdm/backend/storage" "tinyrdm/backend/types" "tinyrdm/backend/utils/coll" + convutil "tinyrdm/backend/utils/convert" + sliceutil "tinyrdm/backend/utils/slice" ) type preferencesService struct { pref *storage2.PreferencesStorage clientVersion string + customDecoder []convutil.CmdConvert } var preferences *preferencesService @@ -182,6 +185,23 @@ func (p *preferencesService) GetScanSize() int { return size } +func (p *preferencesService) GetDecoder() []convutil.CmdConvert { + data := p.pref.GetPreferences() + return sliceutil.FilterMap(data.Decoder, func(i int) (convutil.CmdConvert, bool) { + //if !data.Decoder[i].Enable { + // return convutil.CmdConvert{}, false + //} + return convutil.CmdConvert{ + Name: data.Decoder[i].Name, + Auto: data.Decoder[i].Auto, + DecodePath: data.Decoder[i].DecodePath, + DecodeArgs: data.Decoder[i].DecodeArgs, + EncodePath: data.Decoder[i].EncodePath, + EncodeArgs: data.Decoder[i].EncodeArgs, + }, true + }) +} + type latestRelease struct { Name string `json:"name"` TagName string `json:"tag_name"` diff --git a/backend/types/preferences.go b/backend/types/preferences.go index bdc697f..4646974 100644 --- a/backend/types/preferences.go +++ b/backend/types/preferences.go @@ -3,10 +3,11 @@ package types import "tinyrdm/backend/consts" type Preferences struct { - Behavior PreferencesBehavior `json:"behavior" yaml:"behavior"` - General PreferencesGeneral `json:"general" yaml:"general"` - Editor PreferencesEditor `json:"editor" yaml:"editor"` - Cli PreferencesCli `json:"cli" yaml:"cli"` + Behavior PreferencesBehavior `json:"behavior" yaml:"behavior"` + General PreferencesGeneral `json:"general" yaml:"general"` + Editor PreferencesEditor `json:"editor" yaml:"editor"` + Cli PreferencesCli `json:"cli" yaml:"cli"` + Decoder []PreferencesDecoder `json:"decoder" yaml:"decoder,omitempty"` } func NewPreferences() Preferences { @@ -33,6 +34,7 @@ func NewPreferences() Preferences { FontSize: consts.DEFAULT_FONT_SIZE, CursorStyle: "block", }, + Decoder: []PreferencesDecoder{}, } } @@ -72,3 +74,13 @@ type PreferencesCli struct { FontSize int `json:"fontSize" yaml:"font_size"` CursorStyle string `json:"cursorStyle" yaml:"cursor_style,omitempty"` } + +type PreferencesDecoder struct { + Name string `json:"name" yaml:"name"` + Enable bool `json:"enable" yaml:"enable"` + Auto bool `json:"auto" yaml:"auto"` + DecodePath string `json:"decodePath" yaml:"decode_path"` + DecodeArgs []string `json:"decodeArgs" yaml:"decode_args,omitempty"` + EncodePath string `json:"encodePath" yaml:"encode_path"` + EncodeArgs []string `json:"encodeArgs" yaml:"encode_args,omitempty"` +} diff --git a/backend/utils/convert/cmd_convert.go b/backend/utils/convert/cmd_convert.go new file mode 100644 index 0000000..a37015f --- /dev/null +++ b/backend/utils/convert/cmd_convert.go @@ -0,0 +1,75 @@ +package convutil + +import ( + "encoding/base64" + "os/exec" + "strings" + sliceutil "tinyrdm/backend/utils/slice" +) + +type CmdConvert struct { + Name string + Auto bool + DecodePath string + DecodeArgs []string + EncodePath string + EncodeArgs []string +} + +const replaceholder = "{VALUE}" + +func (c CmdConvert) Encode(str string) (string, bool) { + base64Content := base64.StdEncoding.EncodeToString([]byte(str)) + var containHolder bool + args := sliceutil.Map(c.EncodeArgs, func(i int) string { + arg := strings.TrimSpace(c.EncodeArgs[i]) + if strings.Contains(arg, replaceholder) { + arg = strings.ReplaceAll(arg, replaceholder, base64Content) + containHolder = true + } + return arg + }) + if len(args) <= 0 || !containHolder { + args = append(args, base64Content) + } + cmd := exec.Command(c.EncodePath, args...) + output, err := cmd.Output() + if err != nil || len(output) <= 0 || string(output) == "[RDM-ERROR]" { + return str, false + } + + outputContent := make([]byte, base64.StdEncoding.DecodedLen(len(output))) + n, err := base64.StdEncoding.Decode(outputContent, output) + if err != nil { + return str, false + } + return string(outputContent[:n]), true +} + +func (c CmdConvert) Decode(str string) (string, bool) { + base64Content := base64.StdEncoding.EncodeToString([]byte(str)) + var containHolder bool + args := sliceutil.Map(c.DecodeArgs, func(i int) string { + arg := strings.TrimSpace(c.DecodeArgs[i]) + if strings.Contains(arg, replaceholder) { + arg = strings.ReplaceAll(arg, replaceholder, base64Content) + containHolder = true + } + return arg + }) + if len(args) <= 0 || !containHolder { + args = append(args, base64Content) + } + cmd := exec.Command(c.DecodePath, args...) + output, err := cmd.Output() + if err != nil || len(output) <= 0 || string(output) == "[RDM-ERROR]" { + return str, false + } + + outputContent := make([]byte, base64.StdEncoding.DecodedLen(len(output))) + n, err := base64.StdEncoding.Decode(outputContent, output) + if err != nil { + return str, false + } + return string(outputContent[:n]), true +} diff --git a/backend/utils/convert/convert.go b/backend/utils/convert/convert.go index 5180581..aa47101 100644 --- a/backend/utils/convert/convert.go +++ b/backend/utils/convert/convert.go @@ -29,7 +29,8 @@ var ( // 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) { +// @param custom decoder if any +func ConvertTo(str, decodeType, formatType string, customDecoder []CmdConvert) (value, resultDecode, resultFormat string) { if len(str) <= 0 { // empty content if len(formatType) <= 0 { @@ -46,13 +47,17 @@ func ConvertTo(str, decodeType, formatType string) (value, resultDecode, resultF } // decode first - value, resultDecode = decodeWith(str, decodeType) + value, resultDecode = decodeWith(str, decodeType, customDecoder) // then format content - value, resultFormat = viewAs(value, formatType) + if len(formatType) <= 0 { + value, resultFormat = autoViewAs(value) + } else { + value, resultFormat = viewAs(value, formatType) + } return } -func decodeWith(str, decodeType string) (value, resultDecode string) { +func decodeWith(str, decodeType string, customDecoder []CmdConvert) (value, resultDecode string) { if len(decodeType) > 0 { switch decodeType { case types.DECODE_NONE: @@ -99,17 +104,31 @@ func decodeWith(str, decodeType string) (value, resultDecode string) { } else { value = str } + + default: + for _, decoder := range customDecoder { + if decoder.Name == decodeType { + if decodedStr, ok := decoder.Decode(str); ok { + value = decodedStr + } else { + value = str + } + break + } + } } + resultDecode = decodeType return } - return autoDecode(str) + value, resultDecode = autoDecode(str, customDecoder) + return } // 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) { +func autoDecode(str string, customDecoder []CmdConvert) (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 { @@ -147,6 +166,16 @@ func autoDecode(str string) (value, resultDecode string) { resultDecode = types.DECODE_MSGPACK return } + + // try decode with custom decoder + for _, decoder := range customDecoder { + if decoder.Auto { + if value, ok = decoder.Decode(str); ok { + resultDecode = decoder.Name + return + } + } + } } } @@ -158,6 +187,8 @@ func autoDecode(str string) (value, resultDecode string) { func viewAs(str, formatType string) (value, resultFormat string) { if len(formatType) > 0 { switch formatType { + default: + fallthrough case types.FORMAT_RAW, types.FORMAT_YAML, types.FORMAT_XML: value = str @@ -185,8 +216,7 @@ func viewAs(str, formatType string) (value, resultFormat string) { resultFormat = formatType return } - - return autoViewAs(str) + return } // attempt automatic convert to possible types @@ -222,7 +252,7 @@ func autoViewAs(str string) (value, resultFormat string) { return } -func SaveAs(str, format, decode string) (value string, err error) { +func SaveAs(str, format, decode string, customDecoder []CmdConvert) (value string, err error) { value = str switch format { case types.FORMAT_JSON: @@ -297,6 +327,18 @@ func SaveAs(str, format, decode string) (value string, err error) { err = errors.New("fail to build msgpack") } return + + default: + for _, decoder := range customDecoder { + if decoder.Name == decode { + if encodedStr, ok := decoder.Encode(str); ok { + value = encodedStr + } else { + value = str + } + return + } + } } return str, nil } diff --git a/backend/utils/convert/yaml_convert.go b/backend/utils/convert/yaml_convert.go index edbe601..0b1d9a6 100644 --- a/backend/utils/convert/yaml_convert.go +++ b/backend/utils/convert/yaml_convert.go @@ -2,7 +2,6 @@ package convutil import ( "gopkg.in/yaml.v3" - "log" ) type YamlConvert struct{} @@ -14,10 +13,5 @@ func (YamlConvert) Encode(str string) (string, bool) { func (YamlConvert) Decode(str string) (string, bool) { var obj map[string]any err := yaml.Unmarshal([]byte(str), &obj) - if err != nil { - log.Println(err.Error()) - } else { - log.Println(obj) - } return str, err == nil } diff --git a/frontend/src/App.vue b/frontend/src/App.vue index de981ce..01b220a 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -21,6 +21,7 @@ import FlushDbDialog from '@/components/dialogs/FlushDbDialog.vue' import ExportKeyDialog from '@/components/dialogs/ExportKeyDialog.vue' import ImportKeyDialog from '@/components/dialogs/ImportKeyDialog.vue' import { Info } from 'wailsjs/go/services/systemService.js' +import DecoderDialog from '@/components/dialogs/DecoderDialog.vue' const prefStore = usePreferencesStore() const connectionStore = useConnectionStore() @@ -79,6 +80,7 @@ watch( + diff --git a/frontend/src/components/common/DropdownSelector.vue b/frontend/src/components/common/DropdownSelector.vue index cd97984..10d7ed9 100644 --- a/frontend/src/components/common/DropdownSelector.vue +++ b/frontend/src/components/common/DropdownSelector.vue @@ -1,6 +1,6 @@ + + + + diff --git a/frontend/src/components/dialogs/PreferencesDialog.vue b/frontend/src/components/dialogs/PreferencesDialog.vue index ebd7b6d..a5d5381 100644 --- a/frontend/src/components/dialogs/PreferencesDialog.vue +++ b/frontend/src/components/dialogs/PreferencesDialog.vue @@ -1,11 +1,18 @@