Beispiel #1
0
// addPhoto is called to add a photo. This is allways called from a Delay
func addPhoto(cx appengine.Context, photoID string) error {
	var u *User
	var err error

	s := strings.Split(photoID, ".")

	// s[0] = userid, s[1] = random photo id
	userID := s[0]
	p := &Photo{photoID, time.Now().UTC().Unix()}
	if userID != "0001" {
		u, err = findUser(cx, userID)
		if err != nil {
			return fmt.Errorf("addPhoto: unable to find user %v %v", userID, err)
		}
		k := datastore.NewKey(cx, "Photo", photoID, 0,
			datastore.NewKey(cx, "User", userID, 0, nil))
		if _, err := datastore.Put(cx, k, p); err != nil {
			return fmt.Errorf("addPhoto: put photo in datastore %v", err)
		}
	}

	conn := pool.Get(cx)
	defer conn.Close()

	set, err := redisx.Int(conn.Do("HSETNX", "IM:"+photoID, "date", p.Date)) // Set Date
	if (err != nil && err != redisx.ErrNil) || set == 0 {
		cx.Infof("addPhoto: duplicate %v %v", err, set)
		return nil // returning the error here makes TaskQ call us a lot.
	}
	// TODO: Consider if these should be done in batches of 100 or so.

	if userID != "0001" {
		list := append(u.FollowsMe, userID) // Make sure I can see the photo...
		// Add to each follower's list
		for _, f := range list {
			conn.Send("LPUSH", "TL:"+f, photoID)
		}
		conn.Flush()

		// Check the result and adjust list if nescessary.
		for _, f := range list {
			v, err := redisx.Int(conn.Receive())
			if err != nil && err != redisx.ErrNil {
				cx.Errorf("addPhoto: get TL:%v %v", f, err)
				continue
			}
			if v > 2000 {
				if _, err := conn.Do("RPOP", "TL:"+f); err != nil {
					cx.Errorf("addPhoto: RPOP TL:%v %v", f, err)
				}
			}
		}
	}
	return nil
}
Beispiel #2
0
// iNowFollow is Called when the user wants to follow someone (usually called from delay,
// called from createUser x3 -- Goal Fixup the timeline
func iNowFollow(cx appengine.Context, userID, followerID string) error {
	var err error
	var count int
	k := datastore.NewKey(cx, "User", followerID, 0, nil)
	q := datastore.NewQuery("Photo").Ancestor(k).Order("-Date").Limit(10)
	var photos []Photo
	_, err = q.GetAll(cx, &photos)
	if err != nil {
		return fmt.Errorf("iNowFollow GetAll %v %v", followerID, err)
	}

	conn := pool.Get(cx)
	defer conn.Close()

	// The ideal algorithm would be to merge in date order, but instead, we just add the last 10.
	for _, p := range photos {
		count, err = redisx.Int(conn.Do("LPUSH", "TL:"+userID, p.PhotoID))
		if err != nil {
			cx.Errorf("iNowFollow: %v", err)
		}
	}
	if err == nil && count > 2000 {
		for i := 2000; i < count; i++ {
			if _, err := conn.Do("RPOP", "TL:"+userID); err != nil {
				cx.Errorf("iNowFollow: RPOP TL:%v %v", userID, err)
			}
		}
	}
	return nil
}
Beispiel #3
0
// getTimeline returns the user's Timeline, you could insert additional things here as well.
func getTimeline(cx appengine.Context, userID, lastid string) ([]TLEntry, error) {
	conn := pool.Get(cx)
	defer conn.Close()

	list, err := redisx.Strings(conn.Do("LRANGE", "TL:"+userID, 0, -1))
	if err != nil && err != redisx.ErrNil {
		cx.Errorf("GetTimeLine %v", err)
	}
	ix := 0

	if lastid != "0" { // if we aren't the first time, search for the next batch
		for i, item := range list {
			if item == lastid {
				ix = i
				break
			}
		}
	}
	var timeline []TLEntry
	// TimeLineBatchSize is our paging mechanism, we will only return this many images.  The user
	// can ask for more.
	for i := 0; i < abelanaConfig().TimelineBatchSize && i+ix < len(list); i++ {
		photoID := list[ix+i]

		v, err := redisx.Strings(conn.Do("HMGET", "IM:"+photoID, "date", userID, "flag"))
		if err != nil && err != redisx.ErrNil {
			cx.Errorf("GetTimeLine HMGET %v", err)
		}
		if len(v) > 2 && v[2] != "" {
			flags, err := strconv.Atoi(v[2])
			if err == nil && flags > 1 {
				continue // skip flag'd images
			}
		}
		likes, err := redisx.Int(conn.Do("HLEN", "IM:"+photoID))
		if err != nil && err != redisx.ErrNil {
			cx.Errorf("GetTimeLine HLEN %v", err)
			likes = 0
		} else {
			likes = likes - 1 // offset as there is a Date as well
		}
		s := strings.Split(photoID, ".")
		dn, err := redisx.String(conn.Do("HGET", "HT:"+s[0], "dn"))
		if err != nil && err != redisx.ErrNil {
			cx.Errorf("GetTimeLine HGET %v", err)
			dn = ""
		}
		dt, err := strconv.ParseInt(v[0], 10, 64)
		if err != nil {
			dt = 1414883602 // Nov 1, 2014
		}
		te := TLEntry{dt, s[0], dn, photoID, likes, v[1] == "1"}
		timeline = append(timeline, te)
	}
	return timeline, nil
}
Beispiel #4
0
// flag will tell us that things may not be quite right with this image.
func flag(cx appengine.Context, userID, photoID string) error {
	conn := pool.Get(cx)
	defer conn.Close()

	_, err := redisx.Int(conn.Do("HINCRBY", "IM:"+photoID, "flag", 1))
	if err != nil && err != redisx.ErrNil {
		return fmt.Errorf("unlike %v", err)
	}
	return nil
}
Beispiel #5
0
// like the user on redis
func like(cx appengine.Context, userID, photoID string) error {
	conn := pool.Get(cx)
	defer conn.Close()

	_, err := redisx.Int(conn.Do("HSET", "IM:"+photoID, userID, "1"))
	if err != nil && err != redisx.ErrNil {
		return fmt.Errorf("like %v", err)
	}
	return nil
}