summaryrefslogtreecommitdiffstats
path: root/src/uimac/MyController.m
diff options
context:
space:
mode:
Diffstat (limited to 'src/uimac/MyController.m')
-rw-r--r--src/uimac/MyController.m497
1 files changed, 497 insertions, 0 deletions
diff --git a/src/uimac/MyController.m b/src/uimac/MyController.m
new file mode 100644
index 0000000..eb5c011
--- /dev/null
+++ b/src/uimac/MyController.m
@@ -0,0 +1,497 @@
+/* Copyright (c) 2003, see file COPYING for details. */
+
+#import "MyController.h"
+#import "ReconItem.h"
+#include <caml/callback.h>
+#include <caml/alloc.h>
+#include <caml/mlvalues.h>
+#include <caml/memory.h>
+
+extern value Callback_checkexn(value,value);
+extern value Callback2_checkexn(value,value,value);
+
+@implementation MyController
+
+static MyController *me; // needed by reloadTable and displayStatus, below
+
+- (void)resizeWindowToSize:(NSSize)newSize
+{
+ NSRect aFrame;
+
+ float newHeight = newSize.height;
+ float newWidth = newSize.width;
+
+ aFrame = [NSWindow contentRectForFrameRect:[mainWindow frame]
+ styleMask:[mainWindow styleMask]];
+
+ aFrame.origin.y += aFrame.size.height;
+ aFrame.origin.y -= newHeight;
+ aFrame.size.height = newHeight;
+ aFrame.size.width = newWidth;
+
+ aFrame = [NSWindow frameRectForContentRect:aFrame
+ styleMask:[mainWindow styleMask]];
+
+ [mainWindow setFrame:aFrame display:YES animate:YES];
+}
+
+- (void)chooseProfiles
+{
+ [mainWindow setContentView:blankView];
+ [self resizeWindowToSize:chooseProfileSize];
+ [mainWindow setContentView:chooseProfileView];
+ [mainWindow makeFirstResponder:[profileController tableView]]; // profiles get keyboard input
+}
+
+- (IBAction)createButton:(id)sender
+{
+ [preferencesController reset];
+ [mainWindow setContentView:blankView];
+ [self resizeWindowToSize:preferencesSize];
+ [mainWindow setContentView:preferencesView];
+}
+
+- (IBAction)saveProfileButton:(id)sender
+{
+ if ([preferencesController validatePrefs]) {
+ [profileController initProfiles]; // so the list contains the new profile
+ [self chooseProfiles];
+ }
+}
+
+- (IBAction)cancelProfileButton:(id)sender
+{
+ [self chooseProfiles];
+}
+
+- (void)updateReconItems
+{
+ [reconItems release];
+ reconItems = [[NSMutableArray alloc] init];
+ int j = 0;
+ int n = Wosize_val(caml_reconItems);
+ for (; j<n; j++) {
+ [reconItems insertObject:[ReconItem initWithRi:Field(caml_reconItems,j)]
+ atIndex:j];
+ }
+}
+
+- (void)displayDetails:(int)i
+{
+ if (i >= 0 && i < [reconItems count])
+ [detailsTextView setString:[[reconItems objectAtIndex:i] details]];
+}
+- (void)clearDetails
+{
+ [detailsTextView setString:@""];
+}
+
+- (void)doUpdateThread:(id)whatever
+{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ preconn = Val_unit; // so old preconn can be garbage collected
+ value *f = caml_named_value("unisonInit2");
+ caml_reconItems = Callback_checkexn(*f, Val_unit);
+ [pool release];
+}
+
+- (void)afterUpdate:(NSNotification *)notification
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self
+ name:NSThreadWillExitNotification
+ object:nil];
+ [self updateReconItems];
+ if ([reconItems count] > 0)
+ [tableView selectRow:0 byExtendingSelection:NO];
+
+ // label the left and right columns with the roots
+ NSTableHeaderCell *left = [[[tableView tableColumns] objectAtIndex:0] headerCell];
+ value *f = caml_named_value("unisonFirstRootString");
+ [left setObjectValue:[NSString stringWithCString:String_val(Callback_checkexn(*f, Val_unit))]];
+ NSTableHeaderCell *right = [[[tableView tableColumns] objectAtIndex:2] headerCell];
+ f = caml_named_value("unisonSecondRootString");
+ [right setObjectValue:[NSString stringWithCString:String_val(Callback_checkexn(*f, Val_unit))]];
+
+ // cause scrollbar to display if necessary
+ [tableView reloadData];
+
+ // activate menu items
+ [tableView setEditable:YES];
+}
+
+- (void)afterOpen
+{
+ NSLog(@"Connected.");
+ // move to updates window after clearing it
+ [self clearDetails];
+ [reconItems release];
+ reconItems = nil;
+ [mainWindow setContentView:blankView];
+ [self resizeWindowToSize:updatesSize];
+ [mainWindow setContentView:updatesView];
+
+ // reconItems table gets keyboard input
+ [mainWindow makeFirstResponder:tableView];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(afterUpdate:)
+ name:NSThreadWillExitNotification object:nil];
+ [NSThread detachNewThreadSelector:@selector(doUpdateThread:)
+ toTarget:self withObject:nil];
+}
+
+- (void)connect:(value)profileName
+{
+ // contact server, propagate prefs
+ NSLog(@"Connecting...");
+
+ // Switch to ConnectingView
+ [mainWindow setContentView:blankView];
+ [self resizeWindowToSize:ConnectingSize];
+ [mainWindow setContentView:ConnectingView];
+ [ConnectingView setNeedsDisplay:YES]; // FIX: this doesn't seem to work fast enough
+
+ // possibly slow -- need another thread? Print "contacting server"
+ value *f = NULL;
+ f = caml_named_value("unisonInit1");
+ preconn = Callback_checkexn(*f, profileName);
+ if (preconn == Val_unit) {
+ [self afterOpen]; // no prompting required
+ return;
+ }
+ // prompting required
+ preconn = Field(preconn,0); // value of Some
+ f = caml_named_value("openConnectionPrompt");
+ value prompt = Callback_checkexn(*f, preconn);
+ if (prompt == Val_unit) {
+ // turns out, no prompt needed, but must finish opening connection
+ f = caml_named_value("openConnectionEnd");
+ Callback_checkexn(*f, preconn);
+ [self afterOpen];
+ return;
+ }
+ [self raisePasswordWindow:[NSString stringWithCString:String_val(Field(prompt,0))]];
+}
+
+- (IBAction)openButton:(id)sender
+{
+ NSString *profile = [profileController selected];
+ [updatesText setStringValue:[NSString stringWithFormat:@"Synchronizing profile '%@'",
+ profile]];
+ const char *s = [profile cString];
+ value caml_s = caml_copy_string(s);
+ [self connect:caml_s];
+ return;
+}
+
+- (IBAction)restartButton:(id)sender
+{
+ [tableView setEditable:NO];
+ [self chooseProfiles];
+}
+
+- (void)doSyncThread:(id)whatever
+{
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ value *f = caml_named_value("unisonSynchronize");
+ Callback_checkexn(*f, Val_unit);
+ [pool release];
+}
+
+- (void)afterSync:(NSNotification *)notification
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self
+ name:NSThreadWillExitNotification
+ object:nil];
+ int i;
+ for (i = 0; i < [reconItems count]; i++) {
+ [[reconItems objectAtIndex:i] resetProgress];
+ }
+ [tableView reloadData];
+}
+
+- (IBAction)syncButton:(id)sender
+{
+ [tableView setEditable:NO];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(afterSync:)
+ name:NSThreadWillExitNotification object:nil];
+ [NSThread detachNewThreadSelector:@selector(doSyncThread:)
+ toTarget:self withObject:nil];
+}
+
+- (void)updateTableView:(int)i
+{
+ [[reconItems objectAtIndex:i] resetProgress];
+ [tableView reloadData]; // FIX: can we redisplay just row i?
+}
+
+// A function called from ocaml
+CAMLprim value reloadTable(value row)
+{
+ int i = Int_val(row);
+ [me updateTableView:i]; // we need 'me' to access its instance variables
+ return Val_unit;
+}
+
+- (int)numberOfRowsInTableView:(NSTableView *)aTableView
+{
+ if (!reconItems) return 0;
+ else return [reconItems count];
+}
+
+- (id)tableView:(NSTableView *)aTableView
+ objectValueForTableColumn:(NSTableColumn *)aTableColumn
+ row:(int)rowIndex
+{
+ if (!reconItems) {
+ return @"[internal error]";
+ }
+ if (rowIndex >= 0 && rowIndex < [reconItems count]) {
+ NSString *identifier = [aTableColumn identifier];
+ ReconItem *ri = [reconItems objectAtIndex:rowIndex];
+ NSString *s = [ri valueForKey:identifier];
+ return s;
+ }
+ else return @"[internal error!]";
+}
+- (void)tableViewSelectionDidChange:(NSNotification *)note
+{
+ int n = [tableView numberOfSelectedRows];
+ if (n == 1) [self displayDetails:[tableView selectedRow]];
+ else [self clearDetails];
+}
+
+- (void)raisePasswordWindow:(NSString *)prompt
+{
+ // FIX: some prompts don't ask for password, need to look at it
+ NSLog(@"Got the prompt: '%@'",prompt);
+ value *f = caml_named_value("unisonPasswordMsg");
+ value v = Callback_checkexn(*f, caml_copy_string([prompt cString]));
+ if (v == Val_true) {
+ [NSApp beginSheet:passwordWindow
+ modalForWindow:mainWindow
+ modalDelegate:nil
+ didEndSelector:nil
+ contextInfo:nil];
+ return;
+ }
+ f = caml_named_value("unisonAuthenticityMsg");
+ v = Callback_checkexn(*f, caml_copy_string([prompt cString]));
+ if (v == Val_true) {
+ int i = NSRunAlertPanel(@"New host",prompt,@"Yes",@"No",nil);
+ if (i == NSAlertDefaultReturn) {
+ f = caml_named_value("openConnectionReply");
+ Callback2_checkexn(*f, preconn, caml_copy_string("yes"));
+ f = caml_named_value("openConnectionPrompt");
+ value prompt = Callback_checkexn(*f, preconn);
+ if (prompt == Val_unit) {
+ // all done with prompts, finish opening connection
+ f = caml_named_value("openConnectionEnd");
+ Callback_checkexn(*f, preconn);
+ [self afterOpen];
+ return;
+ }
+ else {
+ [self raisePasswordWindow:[NSString stringWithCString:String_val(Field(prompt,0))]];
+ return;
+ }
+ }
+ if (i == NSAlertAlternateReturn) {
+ f = caml_named_value("openConnectionCancel");
+ Callback_checkexn(*f, preconn);
+ return;
+ }
+ else {
+ NSLog(@"Unrecognized response '%d' from NSRunAlertPanel",i);
+ f = caml_named_value("openConnectionCancel");
+ Callback_checkexn(*f, preconn);
+ return;
+ }
+ }
+ NSLog(@"Unrecognized message from ssh: %@",prompt);
+ f = caml_named_value("openConnectionCancel");
+ Callback_checkexn(*f, preconn);
+}
+// The password window will invoke this when Enter occurs, b/c we
+// are the delegate.
+- (void)controlTextDidEndEditing:(NSNotification *)notification
+{
+ NSNumber *reason = [[notification userInfo] objectForKey:@"NSTextMovement"];
+ int code = [reason intValue];
+ if (code == NSReturnTextMovement)
+ [self endPasswordWindow:self];
+}
+// Or, the Continue button will invoke this when clicked
+- (IBAction)endPasswordWindow:(id)sender
+{
+ [passwordWindow orderOut:self];
+ [NSApp endSheet:passwordWindow];
+ if ([sender isEqualTo:passwordCancelButton]) {
+ value *f = caml_named_value("openConnectionCancel");
+ Callback_checkexn(*f, preconn);
+ [self chooseProfiles];
+ return;
+ }
+ NSString *password = [passwordText stringValue];
+ value *f = NULL;
+ const char *s = [password cString];
+ value caml_s = caml_copy_string(s);
+ f = caml_named_value("openConnectionReply");
+ Callback2_checkexn(*f, preconn, caml_s);
+ f = caml_named_value("openConnectionPrompt");
+ value prompt = Callback_checkexn(*f, preconn);
+ if (prompt == Val_unit) {
+ // all done with prompts, finish opening connection
+ f = caml_named_value("openConnectionEnd");
+ Callback_checkexn(*f, preconn);
+ [self afterOpen];
+ }
+ else [self raisePasswordWindow:[NSString stringWithCString:String_val(Field(prompt,0))]];
+}
+
+- (IBAction)raiseAboutWindow:(id)sender
+{
+ [aboutWindow makeKeyAndOrderFront:nil];
+}
+
+- (IBAction)onlineHelp:(id)sender
+{
+ [[NSWorkspace sharedWorkspace]
+ openURL:[NSURL URLWithString:@"http://www.cis.upenn.edu/~bcpierce/unison/docs.html"]];
+}
+
+
+- (NSMutableArray *)reconItems // used in ReconTableView only
+{
+ return reconItems;
+}
+
+- (int)updateForIgnore:(int)i
+{
+ value *f = caml_named_value("unisonUpdateForIgnore");
+ int j = Int_val(Callback_checkexn(*f,Val_int(i)));
+ f = caml_named_value("unisonState");
+ caml_reconItems = Callback_checkexn(*f, Val_unit);
+ [self updateReconItems];
+ return j;
+}
+
+- (void)statusTextSet:(NSString *)s {
+ [statusText setStringValue:s];
+}
+
+// A function called from ocaml
+CAMLprim value displayStatus(value s)
+{
+ [me statusTextSet:[NSString stringWithCString:String_val(s)]];
+// NSLog(@"dS: %s",String_val(s));
+ return Val_unit;
+}
+
+- (void)awakeFromNib
+{
+ /**** Initialize locals ****/
+ me = self;
+ chooseProfileSize = [chooseProfileView frame].size;
+ updatesSize = [updatesView frame].size;
+ preferencesSize = [preferencesView frame].size;
+ ConnectingSize = [ConnectingView frame].size;
+ blankView = [[NSView alloc] init];
+ /* Double clicking in the profile list will open the profile */
+ [[profileController tableView] setTarget:self];
+ [[profileController tableView] setDoubleAction:@selector(openButton:)];
+ /* Set up the version string in the about box. We use a custom
+ about box just because PRCS doesn't seem capable of getting the
+ version into the InfoPlist.strings file; otherwise we'd use the
+ standard about box. */
+ value *f = NULL;
+ f = caml_named_value("unisonGetVersion");
+ [versionText setStringValue:
+ [NSString stringWithCString:
+ String_val(Callback_checkexn(*f, Val_unit))]];
+
+ /* Ocaml initialization */
+ // FIX: Does this occur before ProfileController awakeFromNib?
+ caml_reconItems = preconn = Val_int(0);
+ caml_register_global_root(&caml_reconItems);
+ caml_register_global_root(&preconn);
+
+ /* Command-line processing */
+ f = caml_named_value("unisonInit0");
+ value clprofile = Callback_checkexn(*f, Val_unit);
+
+ /* Set up the first window the user will see */
+ if (Is_block(clprofile)) {
+ /* A profile name was given on the command line */
+ value caml_profile = Field(clprofile,0);
+ NSString *profile = [NSString stringWithCString:String_val(caml_profile)];
+ [updatesText setStringValue:[NSString stringWithFormat:@"Synchronizing profile '%@'",
+ profile]];
+ /* If invoked from terminal we need to bring the app to the front */
+ [NSApp activateIgnoringOtherApps:YES];
+
+ /* Start the connection */
+ [self connect:caml_profile];
+ }
+ else {
+ /* If invoked from terminal we need to bring the app to the front */
+ [NSApp activateIgnoringOtherApps:YES];
+ /* Bring up the dialog to choose a profile */
+ [self chooseProfiles];
+ }
+}
+
+/* from http://developer.apple.com/documentation/Security/Conceptual/authorization_concepts/index.html */
+#include <Security/Authorization.h>
+#include <Security/AuthorizationTags.h>
+- (IBAction)installCommandLineTool:(id)sender
+{
+ /* Install the command-line tool in /usr/bin/unison.
+ Requires root privilege, so we ask for it and pass the task off to /bin/sh. */
+
+ OSStatus myStatus;
+
+ AuthorizationFlags myFlags = kAuthorizationFlagDefaults;
+ AuthorizationRef myAuthorizationRef;
+ myStatus = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment,
+ myFlags, &myAuthorizationRef);
+ if (myStatus != errAuthorizationSuccess) return;
+
+ {
+ AuthorizationItem myItems = {kAuthorizationRightExecute, 0,
+ NULL, 0};
+ AuthorizationRights myRights = {1, &myItems};
+ myFlags = kAuthorizationFlagDefaults |
+ kAuthorizationFlagInteractionAllowed |
+ kAuthorizationFlagPreAuthorize |
+ kAuthorizationFlagExtendRights;
+ myStatus =
+ AuthorizationCopyRights(myAuthorizationRef,&myRights,NULL,myFlags,NULL);
+ }
+ if (myStatus == errAuthorizationSuccess) {
+ NSBundle *bundle = [NSBundle mainBundle];
+ NSString *bundle_path = [bundle bundlePath];
+ NSString *exec_path =
+ [bundle_path stringByAppendingString:@"/Contents/MacOS/cltool"];
+ // Not sure why but this doesn't work:
+ // [bundle pathForResource:@"cltool" ofType:nil];
+
+ if (exec_path == nil) return;
+ char *args[] = { "-f", (char *)[exec_path cString], "/usr/bin/unison", NULL };
+
+ myFlags = kAuthorizationFlagDefaults;
+ myStatus = AuthorizationExecuteWithPrivileges
+ (myAuthorizationRef, "/bin/cp", myFlags, args,
+ NULL);
+ }
+ AuthorizationFree (myAuthorizationRef, kAuthorizationFlagDefaults);
+
+ /*
+ if (myStatus == errAuthorizationCanceled)
+ NSLog(@"The attempt was canceled\n");
+ else if (myStatus) NSLog(@"There was an authorization error: %ld\n", myStatus);
+ */
+}
+
+@end