Skip to content

Commit

Permalink
fixes problem with the wrong items showing up in a user's stream, due…
Browse files Browse the repository at this point in the history
… to friend associations
  • Loading branch information
mindcrime committed Jul 17, 2013
1 parent bb1f03a commit be1f388
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 196 deletions.
Expand Up @@ -52,6 +52,16 @@ class ActivityStreamController
def getContentHtml =
{

// NOTE: this should be receiving a streamId parameter. If there isn't one
// we can assume the default stream for the user in question. And since this is the
// only place we call this variation of eventStreamService.getRecentActivitiesForUser,
// we should be able to delete it (or force it to default to the default user stream
// and then call the other version)


// also, if this stuff is really supposed to be paginated, we need to fix this to include
// an offset parameter for the call to eventStreamService.getRecentActivitiesForUser

def user = session.user;
def page = params.page;
if( !page )
Expand All @@ -63,8 +73,20 @@ class ActivityStreamController
if( user != null )
{
user = userService.findUserByUserId( session.user.userId );
// activities = eventStreamService.getRecentFriendActivitiesForUser( user );
items = eventStreamService.getRecentActivitiesForUser( user, 25 * Integer.parseInt( page ) );

UserStream selectedStream = null;
if( params.streamId )
{
Long streamId = Long.valueOf( params.streamId );
selectedStream = userStreamService.findStreamById( streamId );
}
else
{
selectedStream = userStreamService.getStreamForUser( user, UserStream.DEFAULT_STREAM );
}


items = eventStreamService.getRecentActivitiesForUser( user, 25 * Integer.parseInt( page ), selectedStream );
}
else
{
Expand Down
@@ -1,9 +1,10 @@
package org.fogbeam.quoddy;

import org.fogbeam.quoddy.User;
import org.fogbeam.quoddy.stream.ActivityStreamItem;
import org.fogbeam.quoddy.stream.ShareTarget;
import org.fogbeam.quoddy.stream.StatusUpdate;
import grails.util.GrailsNameUtils

import org.fogbeam.quoddy.stream.ActivityStreamItem
import org.fogbeam.quoddy.stream.ShareTarget
import org.fogbeam.quoddy.stream.StatusUpdate

class StatusController {

Expand Down Expand Up @@ -77,7 +78,7 @@ class StatusController {
activity.targetUuid = streamPublic.uuid;
activity.owner = user;
activity.streamObject = newStatus;
activity.objectClass = newStatus.class.getName();
activity.objectClass = GrailsNameUtils.getShortName( newStatus.class );

// NOTE: we added "name" to StreamItemBase, but how is it really going
// to be used? Do we *really* need this??
Expand Down
@@ -1,11 +1,10 @@
package org.fogbeam.quoddy

import java.util.List;
import grails.util.GrailsNameUtils

import org.fogbeam.quoddy.controller.mixins.SidebarPopulatorMixin
import org.fogbeam.quoddy.stream.ActivityStreamItem;
import org.fogbeam.quoddy.stream.StatusUpdate;
import org.fogbeam.quoddy.stream.StreamItemBase
import org.fogbeam.quoddy.stream.ActivityStreamItem
import org.fogbeam.quoddy.stream.StatusUpdate


@Mixin(SidebarPopulatorMixin)
Expand Down Expand Up @@ -247,7 +246,7 @@ class UserGroupController
activity.published = new Date(); // set published to "now"
activity.targetUuid = group.uuid;
activity.streamObject = newStatus;
activity.objectClass = newStatus.class.name;
activity.objectClass = GrailsNameUtils.getShortName( newStatus.class );

// NOTE: we added "name" to EventBase, but how is it really going
// to be used? Do we *really* need this??
Expand Down
Expand Up @@ -2,6 +2,7 @@ package org.fogbeam.quoddy



import grails.util.GrailsNameUtils
import groovyx.net.http.RESTClient
import net.fortuna.ical4j.model.component.*

Expand Down Expand Up @@ -108,7 +109,7 @@ class UpdateActivitiUserTaskSubscriptionsJob
activity.targetUuid = streamPublic.uuid;
activity.owner = owner;
activity.streamObject = userTask;
activity.objectClass = userTask.class.getName();
activity.objectClass = GrailsNameUtils.getShortName( userTask.class );

// NOTE: we added "name" to StreamItemBase, but how is it really going
// to be used? Do we *really* need this??
Expand Down
11 changes: 5 additions & 6 deletions grails-app/jobs/org/fogbeam/quoddy/UpdateCalendarFeedsJob.groovy
@@ -1,8 +1,7 @@
package org.fogbeam.quoddy


import java.util.Date;

import grails.util.GrailsNameUtils
import net.fortuna.ical4j.data.CalendarBuilder
import net.fortuna.ical4j.model.ComponentList
import net.fortuna.ical4j.model.component.*
Expand All @@ -13,9 +12,9 @@ import org.apache.http.client.HttpClient
import org.apache.http.client.methods.HttpGet
import org.apache.http.impl.client.DefaultHttpClient
import org.fogbeam.quoddy.stream.ActivityStreamItem
import org.fogbeam.quoddy.stream.CalendarFeedItem;
import org.fogbeam.quoddy.stream.ShareTarget;
import org.fogbeam.quoddy.subscription.CalendarFeedSubscription;
import org.fogbeam.quoddy.stream.CalendarFeedItem
import org.fogbeam.quoddy.stream.ShareTarget
import org.fogbeam.quoddy.subscription.CalendarFeedSubscription


class UpdateCalendarFeedsJob
Expand Down Expand Up @@ -153,7 +152,7 @@ class UpdateCalendarFeedsJob
activity.targetUuid = streamPublic.uuid;
activity.owner = feed.owner;
activity.streamObject = event;
activity.objectClass = event.class.getName();
activity.objectClass = GrailsNameUtils.getShortName( event.class );

// NOTE: we added "name" to StreamItemBase, but how is it really going
// to be used? Do we *really* need this??
Expand Down
Expand Up @@ -2,6 +2,7 @@ package org.fogbeam.quoddy



import grails.util.GrailsNameUtils
import net.fortuna.ical4j.model.component.*

import org.apache.http.HttpEntity
Expand Down Expand Up @@ -168,7 +169,7 @@ class UpdateRssFeedSubscriptionsJob
activity.targetUuid = streamPublic.uuid;
activity.owner = sub.owner;
activity.streamObject = rssFeedItem;
activity.objectClass = rssFeedItem.class.getName();
activity.objectClass = GrailsNameUtils.getShortName( rssFeedItem.class );

// NOTE: we added "name" to StreamItemBase, but how is it really going
// to be used? Do we *really* need this??
Expand Down
183 changes: 7 additions & 176 deletions grails-app/services/org/fogbeam/quoddy/EventStreamService.groovy
Expand Up @@ -89,8 +89,12 @@ class EventStreamService {
println "query now: ${query}";

query = query + " where item.published >= :cutoffDate " +
" and ( item.owner.id in (:friendIds) and not ( item.owner <> :owner and item.objectClass = 'BusinessEventSubscriptionItem' ) " +
" and not ( item.owner <> :owner and item.objectClass = 'CalendarFeedItem' ) ) " +
" and ( item.owner.id in (:friendIds) " +
" and not ( item.owner <> :owner and item.objectClass = 'BusinessEventSubscriptionItem' ) " +
" and not ( item.owner <> :owner and item.objectClass = 'CalendarFeedItem' ) " +
" and not ( item.owner <> :owner and item.objectClass = 'RssFeedItem' ) " +
" and not ( item.owner <> :owner and item.objectClass = 'ActivitiUserTask' ) " +
") " +
" and ( item.targetUuid = :targetUuid or item.targetUuid = :userUuid)";


Expand Down Expand Up @@ -212,180 +216,7 @@ class EventStreamService {
return recentActivityStreamItems;
}

// TODO: refactor this to be consistent with the other getActivities method
// and return EventBase instances
public List<ActivityStreamItem> getRecentActivitiesForUser( final User user, final int maxCount )
{
println "getRecentActivitiesForUser: ${user.userId} - ${maxCount}";
/*
so what do we do here? Ok... we receive a request for up to maxCount recent activities.
Since, by definition, the stuff in the queue is most recent, we read up to maxCount entries
from the queue. If the queue has more than maxCount activities we ??? (what? Blow away the
extras? Leave 'em hanging around for later? Force a flush to the db? ???)
If the queue had less than maxCount records (down to as few as NONE), we retrieve
up to (maxCount - readfromQueueCount) matching records from the db.
The resulting list is the union of the set of activities retrieved from the queue and
the activities loaded from the DB.
Note: Since we really want to show "newest at top" or "newest first" we really wish this
"queue" were actually a stack, so we'd be starting with the newest messages and
getting progressively older ones. We need to explore the possibility of having our
underlying messaging system offer stack semantics, OR implement an intermediate
queue, that reads the messages from the underlying messaging fabric, and offers them
to us in the right order. Possibly explore using Camel for this, or roll our own
thing?
we could also just read everything that's currently on the queue, sort by timestamp,
use up to maxCount of the messages, and then throw away anything that's left-over.
but if we do too much of this, we wind up throwing away a lot of queued messages, which
negates the benefit of not having to read from the DB.
ok, just to get something prototyped... let's pretend that the queue we're reading from
right here *is* the "intermediate queue" and everything is just magically in the right order.
"no problem in computer science that you can't solve by adding a layer of abstraction" right?
Also, for now let's pretend that the queue we're reading from has already been filtered so that
it only contains messages that we are interested in; including expiring messages for age, etc.
*/

int msgsOnQueue = eventQueueService.getQueueSizeForUser( user.userId );
println "Messages available on queue: ${msgsOnQueue}";
int msgsToRead = 0;
if( msgsOnQueue > 0 )
{
if( msgsOnQueue <= maxCount )
{
msgsToRead = msgsOnQueue;
}
else
{
msgsToRead = maxCount - msgsOnQueue;
}
}

println "Messages to read from queue: ${msgsToRead}";

// long oldestOriginTime = Long.MAX_VALUE;
long oldestOriginTime = new Date().getTime();

// NOTE: we could avoid iterating over this list again by returning the "oldest message time"
// as part of this call. But it'll mean wrapping this stuff up into an object of some
// sort, or returning a Map of Maps instead of a List of Maps
List<ActivityStreamItem> messages = eventQueueService.getMessagesForUser( user.userId, msgsToRead );
for( ActivityStreamItem msg : messages )
{
// println "msg.originTime: ${msg.originTime}";
if( msg.effectiveDate.time < oldestOriginTime )
{
oldestOriginTime = msg.effectiveDate.time;
}
}

println "oldestOriginTime: ${oldestOriginTime}";
println "as date: " + new Date( oldestOriginTime);

List<ActivityStreamItem> recentEvents = new ArrayList<ActivityStreamItem>();

// NOTE: we wouldn't really want to iterate over this list here... better
// to build up this list above, and never bother storing the JMS Message instances
// at all... but for now, just to get something so we can prototype the
// behavior up through the UI...

// TODO: now that we are passing notifications for different kinds of
// events through this mechanism, this has to be smarter... It can't just
// conver everything to an activity, it has to convert to Activity, SubscriptionEvent,
// CalendarEvent, etc., depending on what the notification is for.
for( int i = 0; i < messages.size(); i++ )
{
ActivityStreamItem activityStreamItem = messages.get(i);
// println "got message: ${msg} off of queue";

event = existDBService.populateSubscriptionEventWithXmlDoc( activityStreamItem.streamObject );

recentEvents.add( event );
}

println "recentEvents.size() = ${recentEvents.size()}"

/* NOTE: here, we need to make sure we don't retrieve anything NEWER than the OLDEST
* message we may have in hand - that we received from the queue. Otherwise, we risk
* showing the same event twice.
*/

// now, do we need to go to the DB to get some more activities?
if( maxCount > msgsToRead )
{
int recordsToRetrieve = maxCount - msgsToRead;
println "retrieving up to ${recordsToRetrieve} records from the database";

// NOTE: get up to recordsToRetrieve records, but don't retrieve anything that
// would already be in our working set.
// also... we need to make a distinction between the "get recent" method which has
// this cutoff logic and the generic "get older" method that can be used to incrementally
// step backwards into history as far as (they want to go | as far as we let them go)


Calendar cal = Calendar.getInstance();
cal.add(Calendar.HOUR_OF_DAY, -600 );
Date cutoffDate = cal.getTime();

println "Using ${cutoffDate} as cutoffDate";
println "Using ${new Date(oldestOriginTime)} as oldestOriginTime";

List<User> friends = userService.listFriends( user );
if( friends != null && friends.size() >= 0 )
{
println "Found ${friends.size()} friends";
List<Integer> friendIds = new ArrayList<Integer>();
for( User friend: friends )
{
def id = friend.id;
println( "Adding friend id: ${id}, userId: ${friend.userId} to list" );
friendIds.add( id );
}


// for the purpose of this query, treat a user as their own friend... that is, we
// will want to read Activities created by this user (we see our own updates in our
// own feed)
friendIds.add( user.id );
ShareTarget streamPublic = ShareTarget.findByName( ShareTarget.STREAM_PUBLIC );
List<ActivityStreamItem> queryResults =
ActivityStreamItem.executeQuery( "select item from ActivityStreamItem as item where item.published >= :cutoffDate and item.owner.id in (:friendIds) and item.effectiveDate < :oldestOriginTime " +
" and ( item.targetUuid = :targetUuid or item.targetUuid = :userUuid) order by item.effectiveDate desc",
['cutoffDate':cutoffDate,
'oldestOriginTime':new Date(oldestOriginTime),
'friendIds':friendIds,
'targetUuid':streamPublic.uuid,
'userUuid':user.uuid],
['max': recordsToRetrieve ]);

println "adding ${queryResults.size()} activities read from DB";
for( ActivityStreamItem activityStreamItem : queryResults ) {

println "Populating XML into SubscriptionEvents";

event = existDBService.populateSubscriptionEventWithXmlDoc( activityStreamItem.streamObject );
recentEvents.add( event );
}
}
else
{
println( "no friends, so no activity read from DB" );
}
}
else
{
println "Reading NO messages from DB";
}

println "recentEvents.size() = ${recentEvents.size()}";
return recentEvents;
}


public StreamItemBase getEventById( final long eventId )
{
Expand Down

0 comments on commit be1f388

Please sign in to comment.