Skip to content

Commit

Permalink
reputation tracker
Browse files Browse the repository at this point in the history
  • Loading branch information
nikitar committed Apr 3, 2011
1 parent 420f6d8 commit 4e49cb2
Show file tree
Hide file tree
Showing 11 changed files with 280 additions and 56 deletions.
15 changes: 5 additions & 10 deletions newt.xcodeproj/project.pbxproj
Expand Up @@ -60,14 +60,15 @@
8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; };
8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; };
8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
BCE374901346E01200BF40B0 /* common.m in Sources */ = {isa = PBXBuildFile; fileRef = 110BE7FD131F16F00063066E /* common.m */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
110BE5971319D3250063066E /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */;
proxyType = 1;
remoteGlobalIDString = 8D1107260486CEB800E47090 /* Newt */;
remoteGlobalIDString = 8D1107260486CEB800E47090;
remoteInfo = Newt;
};
/* End PBXContainerItemProxy section */
Expand Down Expand Up @@ -106,6 +107,7 @@
110BE70E131E0C280063066E /* GTMNSString+HTML.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+HTML.m"; sourceTree = "<group>"; };
110BE70F131E0C280063066E /* GTMDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMDefines.h; sourceTree = "<group>"; };
110BE737131E12C00063066E /* common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = common.h; path = source/common.h; sourceTree = "<group>"; };
110BE7FD131F16F00063066E /* common.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = common.m; path = source/common.m; sourceTree = "<group>"; };
1122294D1302D82E0087EC8C /* PLBlocks.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PLBlocks.framework; path = lib/PLBlocks.framework; sourceTree = "<group>"; wrapsLines = 0; };
1129997C130304210085DFDA /* README */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README; sourceTree = "<group>"; };
1148F03B12F998BE00C254BB /* JSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSON.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -177,7 +179,6 @@
080E96DDFE201D6D7F000001 /* Classes */ = {
isa = PBXGroup;
children = (
11CA31AF130620EB003F036C /* interviewers */,
117778A212F8AF6D00C2E645 /* NewtMenulet.h */,
117778A312F8AF6D00C2E645 /* NewtMenulet.m */,
1148F2B912FA5E2200C254BB /* StackExchangeQueryTool.h */,
Expand All @@ -195,6 +196,7 @@
11CA30551304B4D6003F036C /* NewtPersistence.h */,
11CA30561304B4D6003F036C /* NewtPersistence.m */,
110BE737131E12C00063066E /* common.h */,
110BE7FD131F16F00063066E /* common.m */,
);
name = Classes;
sourceTree = "<group>";
Expand Down Expand Up @@ -257,14 +259,6 @@
path = lib/JSON;
sourceTree = "<group>";
};
11CA31AF130620EB003F036C /* interviewers */ = {
isa = PBXGroup;
children = (
);
name = interviewers;
path = source/interviewers;
sourceTree = "<group>";
};
19C28FACFE9D520D11CA2CBB /* Products */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -425,6 +419,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
BCE374901346E01200BF40B0 /* common.m in Sources */,
8D11072D0486CEB800E47090 /* main.m in Sources */,
117778A412F8AF6D00C2E645 /* NewtMenulet.m in Sources */,
1148F04612F998BE00C254BB /* NSObject+SBJSON.m in Sources */,
Expand Down
28 changes: 24 additions & 4 deletions resources/English.lproj/MainMenu.xib
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10">
<data>
<int key="IBDocument.SystemTarget">1060</int>
<int key="IBDocument.SystemTarget">1050</int>
<string key="IBDocument.SystemVersion">10J567</string>
<string key="IBDocument.InterfaceBuilderVersion">823</string>
<string key="IBDocument.AppKitVersion">1038.35</string>
Expand Down Expand Up @@ -109,6 +109,18 @@
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="134046214"/>
<reference key="NSMixedImage" ref="821662553"/>
<int key="NSTag">101</int>
</object>
<object class="NSMenuItem" id="1004538258">
<reference key="NSMenu" ref="627897770"/>
<bool key="NSIsDisabled">YES</bool>
<bool key="NSIsSeparator">YES</bool>
<string key="NSTitle"/>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="134046214"/>
<reference key="NSMixedImage" ref="821662553"/>
<int key="NSTag">102</int>
</object>
<object class="NSMenuItem" id="1016037814">
<reference key="NSMenu" ref="627897770"/>
Expand Down Expand Up @@ -249,6 +261,7 @@
<reference ref="854242624"/>
<reference ref="96838011"/>
<reference ref="710913590"/>
<reference ref="1004538258"/>
</object>
<reference key="parent" ref="0"/>
<string key="objectName">theMenu</string>
Expand Down Expand Up @@ -298,6 +311,11 @@
<reference key="object" ref="549122079"/>
<reference key="parent" ref="627897770"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">592</int>
<reference key="object" ref="1004538258"/>
<reference key="parent" ref="627897770"/>
</object>
</object>
</object>
<object class="NSMutableDictionary" key="flattenedProperties">
Expand All @@ -317,12 +335,14 @@
<string>584.IBPluginDependency</string>
<string>588.IBPluginDependency</string>
<string>589.IBPluginDependency</string>
<string>592.IBPluginDependency</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>{{445, 373}, {191, 143}}</string>
<string>{{445, 363}, {191, 153}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
Expand Down Expand Up @@ -351,7 +371,7 @@
</object>
</object>
<nil key="sourceID"/>
<int key="maxID">591</int>
<int key="maxID">592</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes">
<object class="NSMutableArray" key="referencedPartialClassDescriptions">
Expand Down Expand Up @@ -886,7 +906,7 @@
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
<integer value="1060" key="NS.object.0"/>
<integer value="1050" key="NS.object.0"/>
</object>
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3</string>
Expand Down
2 changes: 2 additions & 0 deletions resources/Growl Registration Ticket.growlRegDict
Expand Up @@ -9,12 +9,14 @@
<string>New Question</string>
<string>New Comment</string>
<string>New Answer</string>
<string>Reputation Change</string>
</array>
<key>DefaultNotifications</key>
<array>
<string>New Question</string>
<string>New Comment</string>
<string>New Answer</string>
<string>Reputation Change</string>
</array>
</dict>
</plist>
3 changes: 2 additions & 1 deletion source/NewtMenulet.h
Expand Up @@ -29,7 +29,7 @@
#import "PreferencePaneController.h"
#import "StackExchangeQueryTool.h"
#import "NewtPersistence.h"
#import "common.h"



// Performs polling of SE API
Expand Down Expand Up @@ -63,6 +63,7 @@
NSTimer *commentsToUserTimer;
NSTimer *commentsOnPostsTimer;
NSTimer *answersOnPostsTimer;
NSTimer *userInfoTimer;

NSTimer *sitesDataTimer;

Expand Down
132 changes: 129 additions & 3 deletions source/NewtMenulet.m
Expand Up @@ -124,7 +124,6 @@ - (void)awakeFromNib {
prefPane = [[PreferencePaneController alloc] initWithBundle:bundle];
[prefPane setPersistence:persistence];

// [self loadStackExchangeNetworkSites];
sitesDataTimer = [self startTimerWithMethod:@selector(loadStackExchangeNetworkSites) andInterval:60*24];

// initialise Growl
Expand All @@ -138,8 +137,10 @@ - (void)awakeFromNib {

questionTimer = [self startTimerWithMethod:@selector(retrieveQuestions:) andInterval:1];
postsByUserTimer = [self startTimerWithMethod:@selector(retrievePosts:) andInterval:5];

// delay retrieval of comments and answers before user posts are fetched
userInfoTimer = [self startTimerWithMethod:@selector(updateUserInfo) andInterval:3];

// delay retrieval of comments and answers before recent user posts are fetched
// also, this might help with API requests throttling
[self performSelector:@selector(delayedTimers) withObject:nil afterDelay:10.0];
}

Expand Down Expand Up @@ -179,6 +180,130 @@ - (void)loadStackExchangeNetworkSites {
[persistence updateSites:queryTool];
}

- (void)updateUserInfo {
if (!enabled) {
// temporary switched off
return;
}
NSLog(@"updateUserInfo");

NSString *userGlobalId = [persistence objectForKey:@"user_global_id"];
if (userGlobalId == nil) {
return;
}

// TODO some duplication with PreferencePaneController#updateProfileURL and #updateUserInfoWithProfiles

// fetch information about user's profiles across Stack Exchange network
QueryToolSuccessHandler globalUserDataHandler = ^(NSDictionary *result) {
NSArray *profiles = [result objectForKey:@"associated_users"];
[self showReputation:profiles];
};
[queryTool execute:@"http://stackauth.com"
withMethod:[NSString stringWithFormat:@"users/%@/associated", userGlobalId]
andParameters:[NSDictionary dictionary]
onSuccess:globalUserDataHandler];
}

- (void)showReputation:(NSArray *)profiles {
// save current reputation data to calculate the difference
NSArray *mostUsed = [persistence objectForKey:@"most_used_sites"];
NSMutableDictionary *repMap = [NSMutableDictionary dictionaryWithCapacity:[mostUsed count]];
for (NSString *siteKey in mostUsed) {
NSObject *rep = [[persistence siteForKey:siteKey] objectForKey:@"user_reputation"];
[repMap setObject:rep forKey:siteKey];
}

// update profile data
[prefPane updateProfiles:profiles];

mostUsed = [persistence objectForKey:@"most_used_sites"];

// calculate reputation change
for (NSString *siteUrl in mostUsed) {
NSNumber *old = [repMap objectForKey:siteUrl];
if (old == nil) {
old = [NSNumber numberWithInt:0];
}

NSDictionary *site = [persistence siteForKey:siteUrl];
NSNumber *current = [site objectForKey:@"user_reputation"];
int dif = [current intValue] - [old intValue];
if (dif != 0) {
NSObject *userId = [site objectForKey:@"user_id"];
NSString *url = [NSString stringWithFormat:@"%@/users/%@?tab=reputation", siteUrl, userId];
NSString *title;
if (dif > 0) {
title = [NSString stringWithFormat:@"+%d", dif];
} else {
title = [NSString stringWithFormat:@"%d", dif];
}

[GrowlApplicationBridge notifyWithTitle:title
description:@""
notificationName:@"Reputation Change"
iconData:[site objectForKey:@"icon_data"]
priority:0
isSticky:FALSE
clickContext:url];
}
}

// present reputation for most used sites
int ITEMS_TO_SHOW = 3;

int startIndex = [theMenu indexOfItemWithTag:101];
int endIndex = [theMenu indexOfItemWithTag:102];
if (startIndex + 1 == endIndex) {
// create menu items first, if there're none
[[theMenu itemAtIndex:endIndex] setHidden:FALSE];
for (int i = 0; i < ITEMS_TO_SHOW && i < [profiles count]; ++i) {
NSMenuItem *item = [theMenu insertItemWithTitle:@"site"
action:@selector(clickReputation:)
keyEquivalent:@""
atIndex:startIndex + i + 1];
[item setTarget:self];
[item setTag:110 + i];
}
}

// set titles
for (int i = 0; i < ITEMS_TO_SHOW && i < [mostUsed count]; ++i) {
NSMenuItem *item = [theMenu itemAtIndex:startIndex + i + 1];
NSString *siteUrl = [mostUsed objectAtIndex:i];

NSDictionary *site = [persistence siteForKey:siteUrl];
if (site == nil) {
continue;
}
NSString *siteRep = [site objectForKey:@"user_reputation"];
NSString *title = [NSString stringWithFormat:@"%@", siteRep];
NSData *iconData = [site objectForKey:@"icon_data"];
if (iconData == nil) {
continue;
}
NSImage *image = [[NSImage alloc] initWithData:iconData];
NSSize newSize;
newSize.height = 22;
newSize.width = 22;
[image setSize:newSize];

[item setTitle:title];
[item setImage:image];
[image release];
}
}

- (void)clickReputation:(id)sender {
NSArray *sites = [persistence objectForKey:@"most_used_sites"];
int index = [sender tag] - 110;
NSString *siteUrl = [sites objectAtIndex:index];
NSDictionary *site = [persistence siteForKey:siteUrl];
NSObject *userId = [site objectForKey:@"user_id"];

NSString *url = [NSString stringWithFormat:@"%@/users/%@?tab=reputation", siteUrl, userId];
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:url]];
}

- (IBAction)retrieveQuestions:(id)sender {
if (!enabled || silent) {
Expand Down Expand Up @@ -276,6 +401,7 @@ - (IBAction)openAboutPanel:(id)sender {
}

- (IBAction)quit:(id)sender {
[persistence synchronize];
[NSApp terminate:self];
}

Expand Down
1 change: 1 addition & 0 deletions source/NewtPersistence.h
Expand Up @@ -41,6 +41,7 @@
- enabled - whether new questions will be polled from the site
- favourite_tags - list of tags for this site user entered in preferences pane
- user_id, user_type, user_name, user_reputation and user_email_hash - from stackauth.com/{v}/users/{user_id}/associated
- icon_data - NSData object with site icon.
*/
@interface NewtPersistence : NSObject {

Expand Down
1 change: 1 addition & 0 deletions source/PreferencePaneController.h
Expand Up @@ -74,6 +74,7 @@
- (IBAction)updateProfileURL;

- (void)setPersistence:(NewtPersistence *) persistence_;
- (void)updateProfiles:(NSArray *)profiles;

//- (void)textDidBeginEditing:(NSNotification *)notification;
//- (void)textDidEndEditing:(NSNotification *)aNotification;
Expand Down

0 comments on commit 4e49cb2

Please sign in to comment.