From a0c79d4f136e848cfb603a9e962b798627f02054 Mon Sep 17 00:00:00 2001 From: or-else Date: Fri, 1 Feb 2019 18:37:45 +0300 Subject: [PATCH] deep-copy update public and private fields --- docs/API.md | 2 +- server/store/store.go | 16 -------- server/topic.go | 22 ++++------- server/utils.go | 86 ++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 93 insertions(+), 33 deletions(-) diff --git a/docs/API.md b/docs/API.md index 72d065c10..db1389909 100644 --- a/docs/API.md +++ b/docs/API.md @@ -418,7 +418,7 @@ The `fnd` topic expects `public` to be a string representing a [search query](#q ### Private -The format of the `private` field in group and peer to peer topics is expected to be a dictionary. The following fields are currently defined: +The format of the `private` field in group and peer to peer topics is expected to be a set of key-value pairs. The following keys are currently defined: ```js private: { comment: "some comment", // string, optional user comment about a topic or a peer user diff --git a/server/store/store.go b/server/store/store.go index 30542f9b0..5c745d1c5 100644 --- a/server/store/store.go +++ b/server/store/store.go @@ -471,22 +471,6 @@ type MessagesObjMapper struct{} // Messages is an instance of MessagesObjMapper to map methods to. var Messages MessagesObjMapper -func interfaceToStringSlice(src interface{}) []string { - var dst []string - if src != nil { - if arr, ok := src.([]string); ok { - dst = arr - } else if arr, ok := src.([]interface{}); ok { - for _, val := range arr { - if str, ok := val.(string); ok { - dst = append(dst, str) - } - } - } - } - return dst -} - // Save message func (MessagesObjMapper) Save(msg *types.Message) error { msg.InitTimes() diff --git a/server/topic.go b/server/topic.go index cc152747b..69cb4021b 100644 --- a/server/topic.go +++ b/server/topic.go @@ -1351,15 +1351,9 @@ func (t *Topic) replySetDesc(sess *Session, asUid types.Uid, set *MsgClientSet) return nil } - assignGenericValues := func(upd map[string]interface{}, what string, p interface{}) (changed bool) { - if isNullValue(p) { - // Request to clear the value - upd[what] = nil - changed = true - } else if p != nil { - // A new non-nil value - upd[what] = p - changed = true + assignGenericValues := func(upd map[string]interface{}, what string, dst, src interface{}) (changed bool) { + if dst, changed = mergeInterfaces(dst, src); changed { + upd[what] = dst } return } @@ -1376,11 +1370,11 @@ func (t *Topic) replySetDesc(sess *Session, asUid types.Uid, set *MsgClientSet) case types.TopicCatMe: // Update current user err = assignAccess(core, set.Desc.DefaultAcs) - sendPres = assignGenericValues(core, "Public", set.Desc.Public) + sendPres = assignGenericValues(core, "Public", t.public, set.Desc.Public) case types.TopicCatFnd: // set.Desc.DefaultAcs is ignored. // Do not send presence if fnd.Public has changed. - assignGenericValues(core, "Public", set.Desc.Public) + assignGenericValues(core, "Public", t.fndGetPublic(sess), set.Desc.Public) case types.TopicCatP2P: // Reject direct changes to P2P topics. if set.Desc.Public != nil || set.Desc.DefaultAcs != nil { @@ -1391,7 +1385,7 @@ func (t *Topic) replySetDesc(sess *Session, asUid types.Uid, set *MsgClientSet) // Update group topic if t.owner == asUid { err = assignAccess(core, set.Desc.DefaultAcs) - sendPres = assignGenericValues(core, "Public", set.Desc.Public) + sendPres = assignGenericValues(core, "Public", t.public, set.Desc.Public) } else if set.Desc.DefaultAcs != nil || set.Desc.Public != nil { // This is a request from non-owner sess.queueOut(ErrPermissionDenied(set.Id, set.Topic, now)) @@ -1404,7 +1398,7 @@ func (t *Topic) replySetDesc(sess *Session, asUid types.Uid, set *MsgClientSet) return err } - sendPres = sendPres || assignGenericValues(sub, "Private", set.Desc.Private) + sendPres = sendPres || assignGenericValues(sub, "Private", t.perUser[asUid].private, set.Desc.Private) } if len(core) > 0 { @@ -1412,7 +1406,7 @@ func (t *Topic) replySetDesc(sess *Session, asUid types.Uid, set *MsgClientSet) case types.TopicCatMe: err = store.Users.Update(asUid, core) case types.TopicCatFnd: - // The only value is Public, and Public for fnd is not saved according to specs. + // The only value to be stored in topic is Public, and Public for fnd is not saved according to specs. default: err = store.Topics.Update(t.name, core) } diff --git a/server/utils.go b/server/utils.go index da1013e0f..115ef49a2 100644 --- a/server/utils.go +++ b/server/utils.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "path/filepath" + "reflect" "regexp" "sort" "strconv" @@ -24,6 +25,8 @@ import ( var tagPrefixRegexp = regexp.MustCompile(`^([a-z]\w{0,5}):\S`) +const nullValue = "\u2421" + // Convert a list of IDs into ranges func delrangeDeserialize(in []types.Range) []MsgDelRange { if len(in) == 0 { @@ -225,9 +228,8 @@ func msgOpts2storeOpts(req *MsgGetOpts) *types.QueryOpt { // Check if the interface contains a string with a single Unicode Del control character. func isNullValue(i interface{}) bool { - const clearValue = "\u2421" if str, ok := i.(string); ok { - return str == clearValue + return str == nullValue } return false } @@ -643,3 +645,83 @@ func parseTLSConfig(tlsEnabled bool, jsconfig json.RawMessage) (*tls.Config, err } return &tls.Config{Certificates: []tls.Certificate{cert}}, nil } + +// Merge source interface{} into destination interface. +// If values are maps,deep-merge them. Otherwise shallow-copy. +// Returns dst, true if the dst value was changed. +func mergeInterfaces(dst, src interface{}) (interface{}, bool) { + var changed bool + + if src == nil { + return dst, changed + } + + vsrc := reflect.ValueOf(src) + switch vsrc.Kind() { + case reflect.Map: + if xsrc, ok := src.(map[string]interface{}); ok { + xdst, _ := dst.(map[string]interface{}) + dst, changed = mergeMaps(xdst, xsrc) + } else { + changed = true + dst = src + } + case reflect.String: + if vsrc.String() == nullValue { + changed = dst != nil + dst = nil + } else if src != nil { + changed = true + dst = src + } + default: + changed = true + dst = src + } + return dst, changed +} + +// Deep copy maps. +func mergeMaps(dst, src map[string]interface{}) (map[string]interface{}, bool) { + var changed bool + + if len(src) == 0 { + return dst, changed + } + + if dst == nil { + dst = make(map[string]interface{}) + } + + for key, val := range src { + xval := reflect.ValueOf(val) + switch xval.Kind() { + case reflect.Map: + if xsrc, _ := val.(map[string]interface{}); xsrc != nil { + // Deep-copy map[string]interface{} + xdst, _ := dst[key].(map[string]interface{}) + var lchange bool + dst[key], lchange = mergeMaps(xdst, xsrc) + changed = changed || lchange + } else if val != nil { + // The map is shallow-copied if it's not of the type map[string]interface{} + dst[key] = val + changed = true + } + case reflect.String: + changed = true + if xval.String() == nullValue { + delete(dst, key) + } else if val != nil { + dst[key] = val + } + default: + if val != nil { + dst[key] = val + changed = true + } + } + } + + return dst, changed +}