PBWebController: Add asynchronous functions

This allows us to keep the UI responsive while
running expensive commands
This commit is contained in:
Pieter de Bie
2008-10-31 23:02:53 +01:00
parent 38a49e251e
commit 0a40f876d1
4 changed files with 147 additions and 4 deletions
+81
View File
@@ -0,0 +1,81 @@
Introduction
============
A lot of the display code in GitX is based on the HTML views that are rendered
by WebKit. As such, it's necessary to access a lot of information from WebKit
that is only available in the Objective-C environment.
Because of this, some convenience methods have been added. Most notably, some
objects can be accessed completely from WebKit, including:
* All webcontrollers
* All PBGitRefs and PBGitCommits
All WebKit views should be controlled by a subclass of PBWebController.
Subclassing PBWebController
===========================
PBWebController does some magic to make using it easier. First of all, it
automatically loads in the view specified by "startFile". This means that
your views have to conform to a specific structure. For example, if you set
startFile (in your awakeFromNib) to "commit", there should be a file named
html/views/commit/index.html that will get loaded.
Furthermore, the controller keeps a finishedLoading member that indicates
whether or not the webview is ready to be used. If it is ready, and your
subclass has implemented the didLoad method, that method will be called.
After the document has succesfully loaded, the controller inserts itself into
the Javascript environment under the "Controller" variable.
Useful PBWebController functions
================================
There are some functions that might be useful in any context. These are put in
the PBWebController class for easy access. To access these, replace the colons
with underscores in the JS call. for example,
- (void) log:(NSString) logMessage
would be called in JS by:
Controller.log_("Hello from javascript!");
## - (void) log:(NSString) logMessage
This method simply relays a JS message to the program, which outputs it using
NSLog. Handy for debugging.
## - (BOOL) isReachable:(NSString *)hostname
Will return true if the hostname is reachable from Javascript without creating
an internet connection (for example, dialup). This is useful for optional
features that depend on a working internet connection.
** NOTE ** The GitX JS environment is _NOT_ secure, which means that external
HTTP calls should only be made to trusted sites. For example, DON'T open any
link in a commit message, as this could potentially allow any site to run any
command on your mac.
## - (void) runCommand:(WebScriptObject *)arguments
inRepository:(PBGitRepository *)repository
callBack:(WebScriptObject *)callBack
This somewhat specific function allows you to call any git command and have
its output returned to a callback function. For example, the following:
Controller.runCommand_inRepository_callBack_(["--version"], obj.repository,
function(v) { notify("Git Version: " + v, 1); });
will show a notification displaying the current git version. This method is
async, which means that it doesn't matter if it takes a long time to run.
## - (void) callSelector:(NSString *)selectorString
onObject:(id)object
callBack:(WebScriptObject *)callBack
While you can have direct access to most objects, sometimes it is useful to
have an async task, for example if the operation can take a long time. Use
this function to keep the UI responsive.
+1 -1
View File
@@ -381,8 +381,8 @@
911111F60E594F3F00BF76B4 /* PBRepositoryDocumentController.h */,
911111F70E594F3F00BF76B4 /* PBRepositoryDocumentController.m */,
F5E926040E8827D300056E75 /* PBViewController.h */,
F5EF8C8C0E9D4A5D0050906B /* PBWebController.h */,
F5E926050E8827D300056E75 /* PBViewController.m */,
F5EF8C8C0E9D4A5D0050906B /* PBWebController.h */,
F5EF8C8D0E9D4A5D0050906B /* PBWebController.m */,
F5FE6C010EB13BC900F30D12 /* PBServicesController.h */,
F5FE6C020EB13BC900F30D12 /* PBServicesController.m */,
+3
View File
@@ -13,6 +13,9 @@
IBOutlet WebView* view;
NSString *startFile;
BOOL finishedLoading;
// For async git reading
NSMapTable *callbacks;
}
@property (retain) NSString *startFile;
+62 -3
View File
@@ -7,7 +7,8 @@
//
#import "PBWebController.h"
#import "PBGitRepository.h"
#include <SystemConfiguration/SCNetworkReachability.h>
@implementation PBWebController
@@ -19,6 +20,7 @@
NSString* file = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html" inDirectory:path];
NSLog(@"path: %@, file: %@", path, file);
NSURLRequest * request = [NSURLRequest requestWithURL:[NSURL fileURLWithPath:file]];
callbacks = [NSMapTable mapTableWithKeyOptions:(NSPointerFunctionsObjectPointerPersonality|NSPointerFunctionsStrongMemory) valueOptions:(NSPointerFunctionsObjectPointerPersonality|NSPointerFunctionsStrongMemory)];
finishedLoading = NO;
[view setUIDelegate:self];
@@ -51,13 +53,12 @@
}
#pragma mark Functions to be used from JavaScript
- (void) log: (NSString*) logMessage
{
NSLog(@"%@", logMessage);
}
#include <SystemConfiguration/SCNetworkReachability.h>
- (BOOL) isReachable:(NSString *)hostname
{
SCNetworkConnectionFlags flags;
@@ -71,4 +72,62 @@
return flags > 0;
}
#pragma mark Using async function from JS
- (void) runCommand:(WebScriptObject *)arguments inRepository:(PBGitRepository *)repository callBack:(WebScriptObject *)callBack
{
// The JS bridge does not handle JS Arrays, even though the docs say it does. So, we convert it ourselves.
int length = [[arguments valueForKey:@"length"] intValue];
NSMutableArray *realArguments = [NSMutableArray arrayWithCapacity:length];
int i = 0;
for (i = 0; i < length; i++)
[realArguments addObject:[arguments webScriptValueAtIndex:i]];
NSFileHandle *handle = [repository handleInWorkDirForArguments:realArguments];
[callbacks setObject:callBack forKey:handle];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(JSRunCommandDone:) name:NSFileHandleReadToEndOfFileCompletionNotification object:handle];
[handle readToEndOfFileInBackgroundAndNotify];
}
- (void) callSelector:(NSString *)selectorString onObject:(id)object callBack:(WebScriptObject *)callBack
{
NSArray *arguments = [NSArray arrayWithObjects:selectorString, object, nil];
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(runInThread:) object:arguments];
[callbacks setObject:callBack forKey:thread];
[thread start];
}
- (void) runInThread:(NSArray *)arguments
{
SEL selector = NSSelectorFromString([arguments objectAtIndex:0]);
id object = [arguments objectAtIndex:1];
id ret = [object performSelector:selector];
NSArray *returnArray = [NSArray arrayWithObjects:[NSThread currentThread], ret, nil];
[self performSelectorOnMainThread:@selector(threadFinished:) withObject:returnArray waitUntilDone:NO];
}
- (void) returnCallBackForObject:(id)object withData:(id)data
{
WebScriptObject *a = [callbacks objectForKey: object];
if (!a) {
NSLog(@"Could not find a callback for object: %@", object);
return;
}
[callbacks removeObjectForKey:object];
[a callWebScriptMethod:@"call" withArguments:[NSArray arrayWithObjects:@"", data, nil]];
}
- (void) threadFinished:(NSArray *)arguments
{
[self returnCallBackForObject:[arguments objectAtIndex:0] withData:[arguments objectAtIndex:1]];
}
- (void) JSRunCommandDone:(NSNotification *)notification
{
NSString *data = [[NSString alloc] initWithData:[[notification userInfo] valueForKey:NSFileHandleNotificationDataItem] encoding:NSUTF8StringEncoding];
[self returnCallBackForObject:[notification object] withData:data];
}
@end