// replyGetData is a response to a get.data request - load a list of stored messages, send them to session as {data} // response goes to a single session rather than all sessions in a topic func (t *Topic) replyGetData(sess *Session, id string, req *MsgBrowseOpts) error { now := time.Now().UTC().Round(time.Millisecond) opts := msgOpts2storeOpts(req) messages, err := store.Messages.GetAll(t.name, opts) if err != nil { log.Println("topic: error loading topics ", err) sess.queueOut(ErrUnknown(id, t.original, now)) return err } log.Println("Loaded messages ", len(messages)) // Push the list of messages to the client as {data}. // Messages are sent in reverse order than fetched from DB to make it easier for // clients to process. if messages != nil { for i := len(messages) - 1; i >= 0; i-- { mm := messages[i] from := types.ParseUid(mm.From) msg := &ServerComMessage{Data: &MsgServerData{ Topic: t.original, SeqId: mm.SeqId, From: from.UserId(), Timestamp: mm.CreatedAt, Content: mm.Content}} sess.queueOut(msg) } } return nil }
// replyGetData is a response to a get.data request - load a list of stored messages, send them to session as {data} // response goes to a single session rather than all sessions in a topic func (t *Topic) replyGetData(sess *Session, id string, req *MsgBrowseOpts) error { now := time.Now().UTC().Round(time.Millisecond) opts := msgOpts2storeOpts(req) messages, err := store.Messages.GetAll(t.name, opts) if err != nil { log.Println("topic: error loading topics ", err) reply := ErrUnknown(id, t.original, now) simpleByteSender(sess.send, reply) return err } log.Println("Loaded messages ", len(messages)) // Push the list of updated topics to the client as data messages if messages != nil { for _, mm := range messages { from := types.ParseUid(mm.From) msg := &ServerComMessage{Data: &MsgServerData{ Topic: t.original, SeqId: mm.SeqId, From: from.UserId(), Timestamp: mm.CreatedAt, Content: mm.Content}} simpleByteSender(sess.send, msg) } } return nil }
// loadSubscribers loads topic subscribers, sets topic owner func (t *Topic) loadSubscribers() error { subs, err := store.Topics.GetSubs(t.name) if err != nil { return err } for _, sub := range subs { uid := types.ParseUid(sub.User) t.perUser[uid] = perUserData{ created: sub.CreatedAt, updated: sub.UpdatedAt, clearId: sub.ClearId, readId: sub.ReadSeqId, recvId: sub.RecvSeqId, private: sub.Private, modeWant: sub.ModeWant, modeGiven: sub.ModeGiven} if sub.ModeGiven&sub.ModeWant&types.ModeOwner != 0 { log.Printf("hub.loadSubscriptions: %s set owner to %s", t.name, uid.String()) t.owner = uid } } return nil }
// Retrieve user's authentication record func (a *RethinkDbAdapter) GetAuthRecord(unique string) (t.Uid, []byte, time.Time, error) { // Default() is needed to prevent Pluck from returning an error rows, err := rdb.DB(a.dbName).Table("auth").Get(unique).Pluck("userid", "secret", "expires").Default(nil).Run(a.conn) if err != nil { return t.ZeroUid, nil, time.Time{}, err } var record struct { Userid string `gorethink:"userid"` Secret []byte `gorethink:"secret"` Expires time.Time `gorethink:"expires"` } if !rows.Next(&record) { return t.ZeroUid, nil, time.Time{}, rows.Err() } rows.Close() //log.Println("loggin in user Id=", user.Uid(), user.Id) return t.ParseUid(record.Userid), record.Secret, record.Expires, nil }
// replyGetSub is a response to a get.sub request on a topic - load a list of subscriptions/subscribers, // send it just to the session as a {meta} packet func (t *Topic) replyGetSub(sess *Session, id string, opts *MsgGetOpts) error { now := time.Now().UTC().Round(time.Millisecond) var subs []types.Subscription var err error if t.cat == types.TopicCat_Me { // Fetch user's subscriptions, with Topic.Public denormalized into subscription subs, err = store.Users.GetTopics(sess.uid) } else if t.cat == types.TopicCat_Fnd { // Given a query provided in .private, fetch user's contacts if query, ok := t.perUser[sess.uid].private.([]interface{}); ok { if query != nil && len(query) > 0 { subs, err = store.Users.FindSubs(sess.uid, query) } } } else { // Fetch subscriptions, User.Public denormalized into subscription // FIXME(gene): don't load subs from DB, use perUserData - it already contains subscriptions. subs, err = store.Topics.GetUsers(t.name) } if err != nil { sess.queueOut(ErrUnknown(id, t.original, now)) return err } var ifModified time.Time var limit int if opts != nil { if opts.IfModifiedSince != nil { ifModified = *opts.IfModifiedSince } limit = opts.Limit } else { limit = 1024 } meta := &MsgServerMeta{Id: id, Topic: t.original, Timestamp: &now} if subs != nil && len(subs) > 0 { meta.Sub = make([]MsgTopicSub, 0, len(subs)) for idx, sub := range subs { if idx == limit { break } // Check if the requester has provided a cut off date for updated ts of pub & priv fields. sendPubPriv := ifModified.IsZero() || sub.UpdatedAt.After(ifModified) var mts MsgTopicSub uid := types.ParseUid(sub.User) var clearId int if t.cat == types.TopicCat_Me { // Reporting user's subscriptions to other topics mts.Topic = sub.Topic mts.SeqId = sub.GetSeqId() // Report whatever is the greatest - soft - or hard- deleted id clearId = max(sub.GetHardClearId(), sub.ClearId) mts.ClearId = clearId mts.With = sub.GetWith() if mts.With != "" { mts.Online = t.perSubs[mts.With].online } else { mts.Online = t.perSubs[sub.Topic].online } mts.UpdatedAt = &sub.UpdatedAt lastSeen := sub.GetLastSeen() if !lastSeen.IsZero() { mts.LastSeen = &MsgLastSeenInfo{ When: &lastSeen, UserAgent: sub.GetUserAgent()} } } else { // Reporting subscribers to a group or a p2p topic mts.User = uid.UserId() clearId = max(t.clearId, sub.ClearId) if uid == sess.uid { // Report deleted messages for own subscriptions only mts.ClearId = clearId } if t.cat == types.TopicCat_Grp { pud := t.perUser[uid] mts.Online = pud.online > 0 } } // Ensure sanity or ReadId and RecvId: mts.ReadSeqId = max(clearId, sub.ReadSeqId) mts.RecvSeqId = max(clearId, sub.RecvSeqId) mts.AcsMode = (sub.ModeGiven & sub.ModeWant).String() // Returning public and private only if they have changed since ifModified if sendPubPriv { mts.Public = sub.GetPublic() // Reporting private only if it's user's own supscription or // a synthetic 'private' in 'find' topic where it's a list of tags matched on. if uid == sess.uid || t.cat == types.TopicCat_Fnd { mts.Private = sub.Private } } meta.Sub = append(meta.Sub, mts) } } sess.queueOut(&ServerComMessage{Meta: meta}) return nil }
func gen_rethink(reset bool, dbsource string, data *Data) { var err error log.Println("Opening DB...") err = store.Open("rethinkdb", dbsource) if err != nil { log.Fatal("Failed to connect to DB: ", err) } defer store.Close() log.Println("Initializing DB...") err = store.InitDb(reset) if err != nil { if strings.Contains(err.Error(), " already exists") { log.Println("DB already exists, NOT reinitializing") } else { log.Fatal("Failed to init DB: ", err) } } else { log.Println("DB successfully initialized") } if data.Users == nil { log.Println("No data provided, stopping") return } nameIndex := make(map[string]string, len(data.Users)) log.Println("Generating users...") for _, uu := range data.Users { if uu["createdAt"] != nil { } user := types.User{ State: int(uu["state"].(float64)), Username: uu["username"].(string), Access: types.DefaultAccess{ Auth: types.ModePublic, Anon: types.ModeNone, }, Public: uu["public"], } user.CreatedAt = getCreatedTime(uu["createdAt"]) // store.Users.Create will subscribe user to !me topic but won't create a !me topic if _, err := store.Users.Create(&user, "basic", uu["username"].(string)+":"+uu["passhash"].(string), uu["private"]); err != nil { log.Fatal(err) } nameIndex[user.Username] = user.Id log.Printf("Created user '%s' as %s (%d)", user.Username, user.Id, user.Uid()) } log.Println("Generating group topics...") for _, gt := range data.Grouptopics { name := genTopicName() nameIndex[gt["name"].(string)] = name topic := &types.Topic{Name: name, Public: gt["public"]} var owner types.Uid if gt["owner"] != nil { owner = types.ParseUid(nameIndex[gt["owner"].(string)]) topic.GiveAccess(owner, types.ModeFull, types.ModeFull) } topic.CreatedAt = getCreatedTime(gt["createdAt"]) if err = store.Topics.Create(topic, owner, gt["private"]); err != nil { log.Fatal(err) } log.Printf("Created topic '%s' as %s", gt["name"].(string), name) } log.Println("Generating P2P subscriptions...") p2pIndex := map[string][]map[string]interface{}{} for _, ss := range data.Subscriptions { u1 := ss["user"].(string) u2 := ss["topic"].(string) if u2[0] == '*' { // skip group topics continue } var pair string var idx int if u1 < u2 { pair = u1 + ":" + u2 idx = 0 } else { pair = u2 + ":" + u1 idx = 1 } if _, ok := p2pIndex[pair]; !ok { p2pIndex[pair] = make([]map[string]interface{}, 2) } p2pIndex[pair][idx] = ss } log.Printf("Collected p2p pairs: %d\n", len(p2pIndex)) for pair, subs := range p2pIndex { uid1 := types.ParseUid(nameIndex[subs[0]["user"].(string)]) uid2 := types.ParseUid(nameIndex[subs[1]["user"].(string)]) topic := uid1.P2PName(uid2) created0 := getCreatedTime(subs[0]["createdAt"]) created1 := getCreatedTime(subs[1]["createdAt"]) var s0want, s0given, s1want, s1given types.AccessMode if err := s0want.UnmarshalText([]byte(subs[0]["modeWant"].(string))); err != nil { log.Fatal(err) } if err := s0given.UnmarshalText([]byte(subs[0]["modeHave"].(string))); err != nil { log.Fatal(err) } if err := s1want.UnmarshalText([]byte(subs[1]["modeWant"].(string))); err != nil { log.Fatal(err) } if err := s1given.UnmarshalText([]byte(subs[1]["modeHave"].(string))); err != nil { log.Fatal(err) } log.Printf("Processing %s (%s), %s, %s", pair, topic, uid1.String(), uid2.String()) err := store.Topics.CreateP2P( &types.Subscription{ ObjHeader: types.ObjHeader{CreatedAt: created0}, User: uid1.String(), Topic: topic, ModeWant: s0want, ModeGiven: s0given, Private: subs[0]["private"]}, &types.Subscription{ ObjHeader: types.ObjHeader{CreatedAt: created1}, User: uid2.String(), Topic: topic, ModeWant: s1want, ModeGiven: s1given, Private: subs[1]["private"]}) if err != nil { log.Fatal(err) } } log.Println("Generating group subscriptions...") for _, ss := range data.Subscriptions { u1 := nameIndex[ss["user"].(string)] u2 := nameIndex[ss["topic"].(string)] var want, given types.AccessMode if err := want.UnmarshalText([]byte(ss["modeWant"].(string))); err != nil { log.Fatal(err) } if err := given.UnmarshalText([]byte(ss["modeHave"].(string))); err != nil { log.Fatal(err) } // Define topic name name := u2 if !types.ParseUid(u2).IsZero() { // skip p2p subscriptions continue } log.Printf("Sharing '%s' with '%s'", ss["topic"].(string), ss["user"].(string)) if err = store.Subs.Create(&types.Subscription{ ObjHeader: types.ObjHeader{CreatedAt: getCreatedTime(ss["createdAt"])}, User: u1, Topic: name, ModeWant: want, ModeGiven: given, Private: ss["private"]}); err != nil { log.Fatal(err) } } log.Println("Generating messages...") rand.Seed(time.Now().UnixNano()) seqIds := map[string]int{} var oldFrom types.Uid var oldTopic string toInsert := 80 // Starting 4 days ago timestamp := time.Now().UTC().Round(time.Millisecond).Add(time.Second * time.Duration(-3600*24*4)) for i := 0; i < toInsert; i++ { sub := data.Subscriptions[rand.Intn(len(data.Subscriptions))] topic := nameIndex[sub["topic"].(string)] from := types.ParseUid(nameIndex[sub["user"].(string)]) if topic == oldTopic && from == oldFrom { toInsert++ continue } oldTopic, oldFrom = topic, from if uid := types.ParseUid(topic); !uid.IsZero() { topic = uid.P2PName(from) } seqIds[topic]++ seqId := seqIds[topic] str := data.Messages[rand.Intn(len(data.Messages))] // Max time between messages is 2 hours, averate - 1 hour, time is increasing as seqId increases timestamp = timestamp.Add(time.Second * time.Duration(rand.Intn(3600*2))) msg := types.Message{ ObjHeader: types.ObjHeader{CreatedAt: timestamp}, SeqId: seqId, Topic: topic, From: from.String(), Content: str} if err = store.Messages.Save(&msg); err != nil { log.Fatal(err) } log.Printf("Message %d at %v to '%s' '%s'", msg.SeqId, msg.CreatedAt, topic, str) } }
// topicInit reads an existing topic from database or creates a new topic func topicInit(sreg *sessionJoin, h *Hub) { var t *Topic timestamp := time.Now().UTC().Round(time.Millisecond) t = &Topic{name: sreg.topic, original: sreg.pkt.Topic, sessions: make(map[*Session]bool), broadcast: make(chan *ServerComMessage, 256), reg: make(chan *sessionJoin, 32), unreg: make(chan *sessionLeave, 32), meta: make(chan *metaReq, 32), perUser: make(map[types.Uid]perUserData), exit: make(chan *shutDown, 1), } // Request to load a 'me' topic. The topic always axists. if t.original == "me" { log.Println("hub: loading 'me' topic") t.cat = types.TopicCat_Me // 'me' has no owner, t.owner = nil // Ensure all requests to subscribe are automatically rejected t.accessAuth = types.ModeBanned t.accessAnon = types.ModeBanned user, err := store.Users.Get(sreg.sess.uid) if err != nil { log.Println("hub: cannot load user object for 'me'='" + t.name + "' (" + err.Error() + ")") sreg.sess.queueOut(ErrUnknown(sreg.pkt.Id, t.original, timestamp)) return } if err = t.loadSubscribers(); err != nil { log.Println("hub: cannot load subscribers for '" + t.name + "' (" + err.Error() + ")") sreg.sess.queueOut(ErrUnknown(sreg.pkt.Id, t.original, timestamp)) return } t.public = user.Public t.created = user.CreatedAt t.updated = user.UpdatedAt t.lastId = user.SeqId t.clearId = user.ClearId // Initiate User Agent with the UA of the creating session to report it later t.userAgent = sreg.sess.userAgent // Request to load a 'find' topic. The topic always exists. } else if t.original == "fnd" { log.Println("hub: loading 'fnd' topic") t.cat = types.TopicCat_Fnd // 'fnd' has no owner, t.owner = nil // The p2p topic cannot be accessed by anyone but the 2 participants. Access is checked at the session level. // Set default access in case user leaves - joins. t.accessAuth = types.ModeBanned t.accessAnon = types.ModeBanned if err := t.loadSubscribers(); err != nil { log.Println("hub: cannot load subscribers for '" + t.name + "' (" + err.Error() + ")") sreg.sess.queueOut(ErrUnknown(sreg.pkt.Id, t.original, timestamp)) return } // Public is not set. // t.public = nil t.created = t.perUser[sreg.sess.uid].created t.updated = t.perUser[sreg.sess.uid].updated // Publishing to Find is not supported // t.lastId = 0 // Request to attach to a firehause topic. Must be root. } else if t.original == "fh" { log.Println("hub: loading 'fh' topic") t.cat = types.TopicCat_Fh // 'fh' has no owner, t.owner = nil // The fh can be accessed by anyone with root access // t.accessAuth = types.ModeBanned // t.accessAnon = types.ModeBanned // Subscriptions are ephemeral, no need to load subscribers. // TODO(gene): handle public more intelligently // t.public = nil // Publishing to Fh is not supported, history is not supported. // t.lastId = 0 // Request to load an existing or create a new p2p topic, then attach to it. } else if strings.HasPrefix(t.original, "usr") || strings.HasPrefix(t.original, "p2p") { log.Println("hub: loading or creating p2p topic") // Handle the following cases: // 1. Neither topic nor subscriptions exist: create a new p2p topic & subscriptions. // 2. Topic exists, one of the subscriptions is missing: // 2.1 Requester's subscription is missing, recreate it. // 2.2 Other user's subscription is missing, treat like a new request for user 2. // 3. Topic exists, both subscriptions are missing: should not happen, fail. // 4. Topic and both subscriptions exist: attach to topic t.cat = types.TopicCat_P2P // Check if the topic already exists stopic, err := store.Topics.Get(t.name) if err != nil { log.Println("hub: error while loading topic '" + t.name + "' (" + err.Error() + ")") sreg.sess.queueOut(ErrUnknown(sreg.pkt.Id, t.name, timestamp)) return } // If topic exists, load subscriptions var subs []types.Subscription if stopic != nil { // Subs already have Public swapped if subs, err = store.Topics.GetSubs(t.name); err != nil { log.Println("hub: cannot load subscritions for '" + t.name + "' (" + err.Error() + ")") sreg.sess.queueOut(ErrUnknown(sreg.pkt.Id, t.name, timestamp)) return } // Case 3, fail if len(subs) == 0 { log.Println("hub: missing both subscriptions for '" + t.name + "' (SHOULD NEVER HAPPEN!)") sreg.sess.queueOut(ErrUnknown(sreg.pkt.Id, t.name, timestamp)) return } t.created = stopic.CreatedAt t.updated = stopic.UpdatedAt t.lastId = stopic.SeqId } // t.owner is blank for p2p topics // Ensure that other users are automatically rejected t.accessAuth = types.ModeP2P t.accessAnon = types.ModeBanned // t.public is not used for p2p topics since each user get a different public // Custom default access levels set in sreg.pkt.Init.DefaultAcs are ignored if stopic != nil && len(subs) == 2 { // Case 4. log.Println("hub: existing p2p topic") // t.clearId is not used in P2P topics, may change later for i := 0; i < 2; i++ { uid := types.ParseUid(subs[i].User) t.perUser[uid] = perUserData{ // Adapter already swapped the public values public: subs[i].GetPublic(), private: subs[i].Private, // lastSeenTag: subs[i].LastSeen, modeWant: subs[i].ModeWant, modeGiven: subs[i].ModeGiven} } } else { // Cases 1, 2 log.Println("hub: p2p new topic or one of the subs missing") var userData perUserData // Fetching records for both users. // Requester. userId1 := sreg.sess.uid // The other user. userId2 := types.ParseUserId(t.original) var u1, u2 int users, err := store.Users.GetAll(userId1, userId2) if err != nil { log.Println("hub: failed to load users for '" + t.name + "' (" + err.Error() + ")") sreg.sess.queueOut(ErrUnknown(sreg.pkt.Id, t.name, timestamp)) return } else if len(users) != 2 { // Invited user does not exist log.Println("hub: missing user for '" + t.name + "'") sreg.sess.queueOut(ErrUserNotFound(sreg.pkt.Id, t.name, timestamp)) return } else { // User records are unsorted, make sure we know who is who. if users[0].Uid() == userId1 { u1, u2 = 0, 1 } else { u1, u2 = 1, 0 } } // Figure out which subscriptions are missing: User1's, User2's or both. var sub1, sub2 *types.Subscription // Set to true if only requester's subscription was created var user1only bool if len(subs) == 1 { if subs[0].Uid() == userId1 { // User2's subscription is missing sub1 = &subs[0] } else { // User1's is missing sub2 = &subs[0] user1only = true } } if sub1 == nil { if sreg.pkt.Set != nil && sreg.pkt.Set.Desc != nil && !isNullValue(sreg.pkt.Set.Desc.Private) { userData.private = sreg.pkt.Set.Desc.Private // Init.DefaultAcs and Init.Public are ignored for p2p topics } // User may set non-default access to topic, just make sure it's no higher than the default if sreg.pkt.Set != nil && sreg.pkt.Set.Sub != nil && sreg.pkt.Set.Sub.Mode != "" { if err := userData.modeWant.UnmarshalText([]byte(sreg.pkt.Set.Sub.Mode)); err != nil { log.Println("hub: invalid access mode for topic '" + t.name + "': '" + sreg.pkt.Set.Sub.Mode + "'") userData.modeWant = types.ModeP2P } else { // FIXME(gene): check for Mode.Banned userData.modeWant &= types.ModeP2P } } else if users[u1].Access.Auth != types.ModeNone { userData.modeWant = users[u1].Access.Auth } else { userData.modeWant = types.ModeP2P } sub1 = &types.Subscription{ User: userId1.String(), Topic: t.name, ModeWant: userData.modeWant, ModeGiven: types.ModeP2P, Private: userData.private} // Swap Public to match swapped Public in subs returned from store.Topics.GetSubs sub1.SetPublic(users[u2].Public) } if sub2 == nil { sub2 = &types.Subscription{ User: userId2.String(), Topic: t.name, ModeWant: users[u2].Access.Auth, ModeGiven: types.ModeP2P, Private: nil} // Swap Public to match swapped Public in subs returned from store.Topics.GetSubs sub2.SetPublic(users[u1].Public) } // Create everything if stopic == nil { if err = store.Topics.CreateP2P(sub1, sub2); err != nil { log.Println("hub: databse error in creating subscriptions '" + t.name + "' (" + err.Error() + ")") sreg.sess.queueOut(ErrUnknown(sreg.pkt.Id, t.name, timestamp)) return } t.created = sub1.CreatedAt t.updated = sub1.UpdatedAt // t.lastId is not set (default 0) for new topics } else { // Recreate one of the subscriptions var subToMake *types.Subscription if user1only { subToMake = sub1 } else { subToMake = sub2 } if err = store.Subs.Create(subToMake); err != nil { log.Println("hub: databse error in re-subscribing user '" + t.name + "' (" + err.Error() + ")") sreg.sess.queueOut(ErrUnknown(sreg.pkt.Id, t.name, timestamp)) return } } // t.clearId is not currently used for p2p topics // Publics is already swapped userData.public = sub1.GetPublic() userData.modeWant = sub1.ModeWant userData.modeGiven = sub1.ModeGiven t.perUser[userId1] = userData t.perUser[userId2] = perUserData{ public: sub2.GetPublic(), modeWant: sub2.ModeWant, modeGiven: sub2.ModeGiven, } sreg.created = !user1only } t.original = t.name // Processing request to create a new generic (group) topic: } else if t.original == "new" { log.Println("hub: new group topic") t.cat = types.TopicCat_Grp // Generic topics have parameters stored in the topic object t.owner = sreg.sess.uid t.accessAuth = DEFAULT_AUTH_ACCESS t.accessAnon = DEFAULT_ANON_ACCESS // Owner/creator gets full access to the topic. Owner may change the default modeWant through 'set'. userData := perUserData{ modeGiven: types.ModeFull, modeWant: types.ModeFull} if sreg.pkt.Set != nil { // User sent initialization parameters if sreg.pkt.Set.Desc != nil { if !isNullValue(sreg.pkt.Set.Desc.Public) { t.public = sreg.pkt.Set.Desc.Public } if !isNullValue(sreg.pkt.Set.Desc.Private) { userData.private = sreg.pkt.Set.Desc.Private } // set default access if sreg.pkt.Set.Desc.DefaultAcs != nil { if auth, anon, err := parseTopicAccess(sreg.pkt.Set.Desc.DefaultAcs, t.accessAuth, t.accessAnon); err != nil { log.Println("hub: invalid access mode for topic '" + t.name + "': '" + err.Error() + "'") } else if auth&types.ModeOwner != 0 || anon&types.ModeOwner != 0 { log.Println("hub: OWNER default access in topic '" + t.name) } else { t.accessAuth, t.accessAnon = auth, anon } } } // Owner/creator may restrict own access to topic if sreg.pkt.Set.Sub == nil || sreg.pkt.Set.Sub.Mode == "" { userData.modeWant = types.ModeFull } else { if err := userData.modeWant.UnmarshalText([]byte(sreg.pkt.Set.Sub.Mode)); err != nil { log.Println("hub: invalid access mode for topic '" + t.name + "': '" + sreg.pkt.Set.Sub.Mode + "'") } } } t.perUser[t.owner] = userData t.created = timestamp t.updated = timestamp // t.lastId & t.clearId are not set for new topics stopic := &types.Topic{ ObjHeader: types.ObjHeader{Id: sreg.topic, CreatedAt: timestamp}, Access: types.DefaultAccess{Auth: t.accessAuth, Anon: t.accessAnon}, Public: t.public} // store.Topics.Create will add a subscription record for the topic creator stopic.GiveAccess(t.owner, userData.modeWant, userData.modeGiven) err := store.Topics.Create(stopic, t.owner, t.perUser[t.owner].private) if err != nil { log.Println("hub: cannot save new topic '" + t.name + "' (" + err.Error() + ")") // Error sent on "new" topic sreg.sess.queueOut(ErrUnknown(sreg.pkt.Id, t.original, timestamp)) return } t.original = t.name // keeping 'new' as original has no value to the client sreg.created = true } else { log.Println("hub: existing group topic") t.cat = types.TopicCat_Grp // TODO(gene): check and validate topic name stopic, err := store.Topics.Get(t.name) if err != nil { log.Println("hub: error while loading topic '" + t.name + "' (" + err.Error() + ")") sreg.sess.queueOut(ErrUnknown(sreg.pkt.Id, t.original, timestamp)) return } else if stopic == nil { log.Println("hub: topic '" + t.name + "' does not exist") sreg.sess.queueOut(ErrTopicNotFound(sreg.pkt.Id, t.original, timestamp)) return } if err = t.loadSubscribers(); err != nil { log.Println("hub: cannot load subscribers for '" + t.name + "' (" + err.Error() + ")") sreg.sess.queueOut(ErrUnknown(sreg.pkt.Id, t.original, timestamp)) return } // t.owner is set by loadSubscriptions t.accessAuth = stopic.Access.Auth t.accessAnon = stopic.Access.Anon t.public = stopic.Public t.created = stopic.CreatedAt t.updated = stopic.UpdatedAt t.lastId = stopic.SeqId t.clearId = stopic.ClearId } log.Println("hub: topic created or loaded: " + t.name) h.topicPut(t.name, t) h.topicsLive.Add(1) go t.run(h) sreg.loaded = true // Topic will check access rights, send invite to p2p user, send {ctrl} message to the initiator session t.reg <- sreg }
// topicInit reads an existing topic from database or creates a new topic func topicInit(sreg *sessionJoin, h *Hub) { var t *Topic timestamp := time.Now().UTC().Round(time.Millisecond) t = &Topic{name: sreg.topic, original: sreg.pkt.Topic, sessions: make(map[*Session]bool), broadcast: make(chan *ServerComMessage, 256), reg: make(chan *sessionJoin, 32), unreg: make(chan *sessionLeave, 32), meta: make(chan *metaReq, 32), perUser: make(map[types.Uid]perUserData), shutdown: make(chan chan<- bool, 1), } // Request to load a me topic. The topic must exist if t.original == "me" { log.Println("hub: loading me topic") t.cat = TopicCat_Me // 'me' has no owner, t.owner = nil // Ensure all requests to subscribe are automatically rejected t.accessAuth = types.ModeBanned t.accessAnon = types.ModeBanned user, err := store.Users.Get(sreg.sess.uid) if err != nil { log.Println("hub: cannot load user object for 'me'='" + t.name + "' (" + err.Error() + ")") sreg.sess.QueueOut(ErrUnknown(sreg.pkt.Id, t.original, timestamp)) return } if err = t.loadSubscribers(); err != nil { log.Println("hub: cannot load subscribers for '" + t.name + "' (" + err.Error() + ")") sreg.sess.QueueOut(ErrUnknown(sreg.pkt.Id, t.original, timestamp)) return } t.public = user.Public t.created = user.CreatedAt t.updated = user.UpdatedAt t.lastId = user.SeqId t.clearId = user.ClearId // Initiate User Agent with the UA of the creating session so we don't report it later t.userAgent = sreg.sess.userAgent // Request to create a new p2p topic, then attach to it } else if strings.HasPrefix(t.original, "usr") { log.Println("hub: new p2p topic") t.cat = TopicCat_P2P // t.owner is blank for p2p topics // Ensure that other users are automatically rejected t.accessAuth = types.ModeBanned t.accessAnon = types.ModeBanned var userData perUserData if sreg.pkt.Init != nil && !isNullValue(sreg.pkt.Init.Private) { // t.public is not used for p2p topics since each user get a different public userData.private = sreg.pkt.Init.Private // Init.DefaultAcs and Init.Public are ignored for p2p topics } // Custom default access levels set in sreg.pkt.Init.DefaultAcs are ignored // Fetch user's public and default access userId1 := sreg.sess.uid userId2 := types.ParseUserId(t.original) users, err := store.Users.GetAll(userId1, userId2) if err != nil { log.Println("hub: failed to load users for '" + t.name + "' (" + err.Error() + ")") sreg.sess.QueueOut(ErrUnknown(sreg.pkt.Id, t.name, timestamp)) return } else if len(users) != 2 { // invited user does not exist log.Println("hub: missing user for '" + t.name + "'") sreg.sess.QueueOut(ErrUserNotFound(sreg.pkt.Id, t.name, timestamp)) return } var u1, u2 int if users[0].Uid() == userId1 { u1 = 0 u2 = 1 } else { u1 = 1 u2 = 0 } // User may set non-default access to topic, just make sure it's no higher than the default if sreg.pkt.Sub != nil && sreg.pkt.Sub.Mode != "" { if err := userData.modeWant.UnmarshalText([]byte(sreg.pkt.Sub.Mode)); err != nil { log.Println("hub: invalid access mode for topic '" + t.name + "': '" + sreg.pkt.Sub.Mode + "'") userData.modeWant = types.ModeP2P } else { userData.modeWant &= types.ModeP2P } } else if users[u1].Access.Auth != types.ModeNone { userData.modeWant = users[u1].Access.Auth } else { userData.modeWant = types.ModeP2P } user1 := &types.Subscription{ User: userId1.String(), Topic: t.name, ModeWant: userData.modeWant, ModeGiven: types.ModeP2P, Private: userData.private} user1.SetPublic(users[u1].Public) user2 := &types.Subscription{ User: userId2.String(), Topic: t.name, ModeWant: users[u2].Access.Auth, ModeGiven: types.ModeP2P, Private: nil} user2.SetPublic(users[u2].Public) err = store.Topics.CreateP2P(user1, user2) if err != nil { log.Println("hub: databse error in creating subscriptions '" + t.name + "' (" + err.Error() + ")") sreg.sess.QueueOut(ErrUnknown(sreg.pkt.Id, t.name, timestamp)) return } // t.public is not used for p2p topics since each user gets a different public t.created = user1.CreatedAt t.updated = user1.UpdatedAt // t.lastId is not set (default 0) for new topics // t.clearId is not currently used for p2p topics userData.public = user2.GetPublic() userData.modeWant = user1.ModeWant userData.modeGiven = user1.ModeGiven t.perUser[userId1] = userData t.perUser[userId2] = perUserData{ public: user1.GetPublic(), modeWant: user2.ModeWant, modeGiven: user2.ModeGiven, } t.original = t.name sreg.created = true // Load an existing p2p topic } else if strings.HasPrefix(t.name, "p2p") { log.Println("hub: existing p2p topic") t.cat = TopicCat_P2P // t.owner no valid owner for p2p topics, leave blank // Ensure that other users are automatically rejected t.accessAuth = types.ModeBanned t.accessAnon = types.ModeBanned // Load the topic object stopic, err := store.Topics.Get(t.name) if err != nil { log.Println("hub: error while loading topic '" + t.name + "' (" + err.Error() + ")") sreg.sess.QueueOut(ErrUnknown(sreg.pkt.Id, t.original, timestamp)) return } else if stopic == nil { log.Println("hub: topic '" + t.name + "' does not exist") sreg.sess.QueueOut(ErrTopicNotFound(sreg.pkt.Id, t.original, timestamp)) return } subs, err := store.Topics.GetSubs(t.name) if err != nil { log.Println("hub: cannot load subscritions for '" + t.name + "' (" + err.Error() + ")") sreg.sess.QueueOut(ErrUnknown(sreg.pkt.Id, t.name, timestamp)) return } else if len(subs) != 2 { log.Println("hub: invalid number of subscriptions for '" + t.name + "'") sreg.sess.QueueOut(ErrTopicNotFound(sreg.pkt.Id, t.name, timestamp)) return } // t.public is not used for p2p topics since each user gets a different public t.created = stopic.CreatedAt t.updated = stopic.UpdatedAt t.lastId = stopic.SeqId // t.clearId is not used in P2P topics, may change later for i := 0; i < 2; i++ { uid := types.ParseUid(subs[i].User) t.perUser[uid] = perUserData{ // Based on other user public: subs[(i+1)%2].GetPublic(), private: subs[i].Private, // lastSeenTag: subs[i].LastSeen, modeWant: subs[i].ModeWant, modeGiven: subs[i].ModeGiven} } // Processing request to create a new generic (group) topic: } else if t.original == "new" { log.Println("hub: new group topic") t.cat = TopicCat_Grp // Generic topics have parameters stored in the topic object t.owner = sreg.sess.uid t.accessAuth = DEFAULT_AUTH_ACCESS t.accessAnon = DEFAULT_ANON_ACCESS // Owner/creator gets full access to topic userData := perUserData{modeGiven: types.ModeFull} // User sent initialization parameters if sreg.pkt.Init != nil { if !isNullValue(sreg.pkt.Init.Public) { t.public = sreg.pkt.Init.Public } if !isNullValue(sreg.pkt.Init.Private) { userData.private = sreg.pkt.Init.Private } // set default access if sreg.pkt.Init.DefaultAcs != nil { if auth, anon, err := parseTopicAccess(sreg.pkt.Init.DefaultAcs, t.accessAuth, t.accessAnon); err != nil { log.Println("hub: invalid access mode for topic '" + t.name + "': '" + err.Error() + "'") } else if auth&types.ModeOwner != 0 || anon&types.ModeOwner != 0 { log.Println("hub: OWNER default access in topic '" + t.name) } else { t.accessAuth, t.accessAnon = auth, anon } } } // Owner/creator may restrict own access to topic if sreg.pkt.Sub == nil || sreg.pkt.Sub.Mode == "" { userData.modeWant = types.ModeFull } else { if err := userData.modeWant.UnmarshalText([]byte(sreg.pkt.Sub.Mode)); err != nil { log.Println("hub: invalid access mode for topic '" + t.name + "': '" + sreg.pkt.Sub.Mode + "'") } } t.perUser[t.owner] = userData t.created = timestamp t.updated = timestamp // t.lastId & t.clearId are not set for new topics stopic := &types.Topic{ ObjHeader: types.ObjHeader{CreatedAt: timestamp}, Name: sreg.topic, Access: types.DefaultAccess{Auth: t.accessAuth, Anon: t.accessAnon}, Public: t.public} // store.Topics.Create will add a subscription record for the topic creator stopic.GiveAccess(t.owner, userData.modeWant, userData.modeGiven) err := store.Topics.Create(stopic, t.owner, t.perUser[t.owner].private) if err != nil { log.Println("hub: cannot save new topic '" + t.name + "' (" + err.Error() + ")") // Error sent on "new" topic sreg.sess.QueueOut(ErrUnknown(sreg.pkt.Id, t.original, timestamp)) return } t.original = t.name // keeping 'new' as original has no value to the client sreg.created = true } else { log.Println("hub: existing group topic") t.cat = TopicCat_Grp // TODO(gene): check and validate topic name stopic, err := store.Topics.Get(t.name) if err != nil { log.Println("hub: error while loading topic '" + t.name + "' (" + err.Error() + ")") sreg.sess.QueueOut(ErrUnknown(sreg.pkt.Id, t.original, timestamp)) return } else if stopic == nil { log.Println("hub: topic '" + t.name + "' does not exist") sreg.sess.QueueOut(ErrTopicNotFound(sreg.pkt.Id, t.original, timestamp)) return } if err = t.loadSubscribers(); err != nil { log.Println("hub: cannot load subscribers for '" + t.name + "' (" + err.Error() + ")") sreg.sess.QueueOut(ErrUnknown(sreg.pkt.Id, t.original, timestamp)) return } // t.owner is set by loadSubscriptions t.accessAuth = stopic.Access.Auth t.accessAnon = stopic.Access.Anon t.public = stopic.Public t.created = stopic.CreatedAt t.updated = stopic.UpdatedAt t.lastId = stopic.SeqId t.clearId = stopic.ClearId } log.Println("hub: topic created or loaded: " + t.name) h.topicPut(t.name, t) h.topicsLive.Add(1) go t.run(h) sreg.loaded = true // Topic will check access rights, send invite to p2p user, send {ctrl} message to the initiator session t.reg <- sreg }
// TopicsForUser loads user's contact list: p2p and grp topics, except for 'me' subscription. func (a *RethinkDbAdapter) TopicsForUser(uid t.Uid) ([]t.Subscription, error) { // Fetch user's subscriptions // Subscription have Topic.UpdatedAt denormalized into Subscription.UpdatedAt q := rdb.DB(a.dbName).Table("subscriptions").GetAllByIndex("User", uid.String()).Limit(MAX_RESULTS) //log.Printf("RethinkDbAdapter.TopicsForUser q: %+v", q) rows, err := q.Run(a.conn) if err != nil { return nil, err } // Fetch subscriptions. Two queries are needed: users table (me & p2p) and topics table (p2p and grp). // Prepare a list of Separate subscriptions to users vs topics var sub t.Subscription join := make(map[string]t.Subscription) // Keeping these to make a join with table for .private and .access topq := make([]interface{}, 0, 16) usrq := make([]interface{}, 0, 16) for rows.Next(&sub) { tcat := t.GetTopicCat(sub.Topic) // 'me' subscription, skip if tcat == t.TopicCat_Me || tcat == t.TopicCat_Fnd { continue // p2p subscription, find the other user to get user.Public } else if tcat == t.TopicCat_P2P { uid1, uid2, _ := t.ParseP2P(sub.Topic) if uid1 == uid { usrq = append(usrq, uid2.String()) } else { usrq = append(usrq, uid1.String()) } topq = append(topq, sub.Topic) // grp subscription } else { topq = append(topq, sub.Topic) } join[sub.Topic] = sub } //log.Printf("RethinkDbAdapter.TopicsForUser topq, usrq: %+v, %+v", topq, usrq) var subs []t.Subscription if len(topq) > 0 || len(usrq) > 0 { subs = make([]t.Subscription, 0, len(join)) } if len(topq) > 0 { // Fetch grp & p2p topics rows, err = rdb.DB(a.dbName).Table("topics").GetAll(topq...).Run(a.conn) if err != nil { return nil, err } var top t.Topic for rows.Next(&top) { sub = join[top.Id] sub.ObjHeader.MergeTimes(&top.ObjHeader) sub.SetSeqId(top.SeqId) sub.SetHardClearId(top.ClearId) if t.GetTopicCat(sub.Topic) == t.TopicCat_Grp { // all done with a grp topic sub.SetPublic(top.Public) subs = append(subs, sub) } else { // put back the updated value of a p2p subsription, will process further below join[top.Id] = sub } } //log.Printf("RethinkDbAdapter.TopicsForUser 1: %#+v", subs) } // Fetch p2p users and join to p2p tables if len(usrq) > 0 { rows, err = rdb.DB(a.dbName).Table("users").GetAll(usrq...).Run(a.conn) if err != nil { return nil, err } var usr t.User for rows.Next(&usr) { uid2 := t.ParseUid(usr.Id) topic := uid.P2PName(uid2) if sub, ok := join[topic]; ok { sub.ObjHeader.MergeTimes(&usr.ObjHeader) sub.SetWith(uid2.UserId()) sub.SetPublic(usr.Public) sub.SetLastSeenAndUA(usr.LastSeen, usr.UserAgent) subs = append(subs, sub) } } //log.Printf("RethinkDbAdapter.TopicsForUser 2: %#+v", subs) } return subs, nil }
// replyGetSub is a response to a get.sub request on a topic - load a list of subscriptions/subscribers, // send it just to the session as a {meta} packet func (t *Topic) replyGetSub(sess *Session, id string) error { now := time.Now().UTC().Round(time.Millisecond) var subs []types.Subscription var err error if t.cat == TopicCat_Me { // Fetch user's subscriptions, with Topic.Public denormalized into subscription subs, err = store.Users.GetTopics(sess.uid) } else { // Fetch subscriptions, User.Public denormalized into subscription subs, err = store.Topics.GetUsers(t.name) } if err != nil { log.Printf("topic(%s): error loading subscriptions %s\n", t.original, err.Error()) reply := ErrUnknown(id, t.original, now) simpleByteSender(sess.send, reply) return err } meta := &MsgServerMeta{Id: id, Topic: t.original, Timestamp: &now} if len(subs) > 0 { meta.Sub = make([]MsgTopicSub, 0, len(subs)) for _, sub := range subs { var mts MsgTopicSub uid := types.ParseUid(sub.User) var clearId int if t.cat == TopicCat_Me { // Reporting user's subscriptions to other topics mts.Topic = sub.Topic mts.SeqId = sub.GetSeqId() // Report whatever is the greatest - soft - or hard- deleted id clearId = max(sub.GetHardClearId(), sub.ClearId) mts.ClearId = clearId mts.With = sub.GetWith() mts.UpdatedAt = &sub.UpdatedAt lastSeen := sub.GetLastSeen() if !lastSeen.IsZero() { mts.LastSeen = &MsgLastSeenInfo{ When: &lastSeen, UserAgent: sub.GetUserAgent()} } } else { // Reporting subscribers to a group or a p2p topic mts.User = uid.UserId() clearId = max(t.clearId, sub.ClearId) if uid == sess.uid { // Report deleted messages for own subscriptions only mts.ClearId = clearId } if t.cat == TopicCat_Grp { pud := t.perUser[uid] if pud.online > 0 { mts.Online = "on" } else { mts.Online = "off" } } } // Ensure sanity or ReadId and RecvId: mts.ReadSeqId = max(clearId, sub.ReadSeqId) mts.RecvSeqId = max(clearId, sub.RecvSeqId) mts.AcsMode = (sub.ModeGiven & sub.ModeWant).String() mts.Public = sub.GetPublic() if uid == sess.uid { mts.Private = sub.Private } meta.Sub = append(meta.Sub, mts) } } simpleByteSender(sess.send, &ServerComMessage{Meta: meta}) return nil }