// // ServerDetailViewController.m // p4scout // // Created by Work on 10/18/08. // Copyright 2008 Perforce Software, Inc. All rights reserved. // #import "ServerDetailViewController.h" #import "ServerTableController.h" #import "P4Server.h" #import "P4ConnectionEditor.h" #import "MonitorTableViewController.h" #import "NSStringAdditions.h" #import "UIToolbarAdditions.h" static const int ActivityButtonBarItemPosition = 2; @interface ServerDetailViewController () -(void)setActive:(BOOL)active; -(ServerTableController*)serverTableController; -(void)configureEditButton; -(IBAction)addToSavedList:(id)sender; -(NSURL*)sshUrl; @end @implementation ServerDetailViewController @synthesize server; // Override initWithNibName:bundle: to load the view using a nib file then perform additional customization that is not appropriate for viewDidLoad. - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { if (!(self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) return self; return self; } /* // Implement loadView to create a view hierarchy programmatically. - (void)loadView { [super loadView]; } */ -(NSURL*)sshUrl { NSString * user = [server user]; if (!user) user = [P4Server defaultUser]; NSString * urlStr = [NSString stringWithFormat:@"ssh://%@@%@", user, server.remoteHostname]; NSURL * url = [NSURL URLWithString:urlStr]; return url; } -(NSTimeInterval)timeIntervalFromString:(NSString *)s { NSArray * times = [s componentsSeparatedByString:@":"]; if ( [times count] != 3 ) return -1; NSTimeInterval uptime = 0; uptime += [[times objectAtIndex:0] intValue] * 60 * 60; uptime += [[times objectAtIndex:1] intValue] * 60; uptime += [[times objectAtIndex:2] intValue]; return uptime; } -(NSTimeInterval)uptime { return [self timeIntervalFromString:[[self.server info] objectForKey:@"serverUptime"]]; } -(NSString *)uptimeUnitString { NSTimeInterval uptime = [self uptime]; if ( uptime > (60*60*24) ) return @"days"; if ( uptime > (60*60) ) return @"hours"; if ( uptime > 60 ) return @"minutes"; if ( uptime < 0 ) return @"hours"; return @"seconds"; } -(NSString *)uptimeString { NSTimeInterval uptime = [self uptime]; if ( uptime < 0 ) return @"?"; if ( uptime > (60*60*24) ) return [[NSNumber numberWithInt: uptime / (60*60*24)] stringValue]; if ( uptime > (60*60) ) return [[NSNumber numberWithInt: uptime / (60*60)] stringValue]; if ( uptime > 60 ) return [[NSNumber numberWithInt: uptime / 60] stringValue]; return [[NSNumber numberWithInt: uptime] stringValue]; } -(void)updateUserDataForServer:(P4Server *)s { userCount.text = [NSString stringWithFormat:@"%d", s.numberOfUsers]; maxUserCount.text = [NSString stringWithFormat:@"%d", s.maximumUserCount]; int remainingUsers = 0; float progress = 0; if ( s.maximumUserCount ) { remainingUsers = s.maximumUserCount - s.numberOfUsers; progress = (float)s.numberOfUsers / (float)s.maximumUserCount; } userCountProgressView.progress = progress; // show users warning if ( progress > 0.95 && remainingUsers < 5 ) { userCount.textColor = [UIColor redColor]; usersAlertLabel.hidden = NO; } else { userCount.textColor = [UIColor darkTextColor]; usersAlertLabel.hidden = YES; } } -(void)displayDataForServer:(P4Server *)s { serverNameLabel.text = s.label; if ( !serverNameLabel.text ) serverNameLabel.text = s.p4port; if ( !serverNameLabel.text ) serverNameLabel.text = @"perforce:1666"; NSDictionary * info = s.info; serverAddressLabel.text = [info objectForKey:@"serverAddress"]; uptimeLabel.text = [self uptimeString]; uptimeUnitLabel.text = [self uptimeUnitString]; currentCommandLabel.text = s.currentCommandDescription; if ( s.connectionState == Online || s.connectionState == Connecting ) { [serverErrorMessage setHidden:YES]; connectionStatusIcon.hidden = YES; [infoView setHidden:NO]; } else { [infoView setHidden:YES]; [serverErrorMessage setHidden:NO]; serverAddressLabel.text = s.p4port; connectionStatusIcon.hidden = NO; if ( !s.reachable ) { connectionStatusIcon.image = [UIImage imageNamed:@"StatusUnreachable.png"]; serverErrorMessage.text = [NSString stringWithFormat:@"Cannot reach %@ on this network", s.remoteHostname]; } else if ( s.connectionState == Offline ) { connectionStatusIcon.image = [UIImage imageNamed:@"StatusOffline.png"]; serverErrorMessage.text = @"Cannot connect to the Perforce Server"; } } versionLabel.text = s.simpleVersion; platformLabel.text = s.platform; int taskCount = [s.monitorInfo count]; if ( !s.monitorEnabled ) { monitorTextView.text = @"Monitor not enabled"; } else if ( s.requiresTicket ) { monitorTextView.text = @"Process information not available for security level 3 servers"; } else if ( !s.monitorInfo ) { monitorTextView.text = @"Not loaded"; } else { if ( s.currentUserExistsOnServer ) { monitorTextView.text = [NSString stringWithFormat:@"%@ processes over %d minutes", taskCount ? [NSString stringWithFormat:@"%d", taskCount] : @"No", (int)(MonitorTaskTimeThreshold / 60)]; } else { monitorTextView.text = [NSString stringWithFormat:@"Skipped to avoid creating a new user (%@)", s.user ? s.user : [P4Server defaultUser] ]; } } monitorAlertLabel.hidden = (taskCount <= 0); monitorTableButton.hidden = (taskCount <= 0); NSTimeInterval timeLeft = s.remainingLicenseTime; if ( s.hasLicense && timeLeft < (60*60*24*7) ) // warns << 14 days { licenseLabel.textColor = [UIColor redColor]; licenseAlertLabel.hidden = NO; } else { licenseLabel.textColor = [UIColor darkTextColor]; licenseAlertLabel.hidden = YES; } if ( !s.hasLicense ) { licenseLabel.text = @"none"; } else if ( timeLeft <= 0 ) { licenseLabel.text = @"EXPIRED"; } else { licenseLabel.text = [NSString stringWithFormat:@"%@ left", [NSString coarseStringWithTimeInterval:timeLeft]]; } [self updateUserDataForServer:s]; if ( s.lastError ) { [errorTextView setText:s.lastError.localizedDescription]; errorTextView.hidden = NO; errorAlertLabel.hidden = NO; } else { errorTextView.hidden = YES; errorAlertLabel.hidden = YES; } // Only turn it on, never off, because an animation might be // turning it off slowly. If we turn it off, it will just blink it off, // interrupting the animation if ( s.discovered ) discoveredBadgeLabel.hidden = false; } -(void)observeValueForKeyPath:(NSString *)key ofObject:(id)object change:(NSDictionary *)dict context:(void *)context { if ( ![key isEqualToString:InfoPropertyKey] && ![key isEqualToString:MonitorInfoPropertyKey] && ![key isEqualToString:NumberOfUsersPropertyKey] && ![key isEqualToString:ConnectionStatePropertyKey] && ![key isEqualToString:ActivePropertyKey] && ![key isEqualToString:CurrentCommandPropertyKey] ) return; if ([key isEqualToString:ActivePropertyKey]) { P4Server * s = (P4Server*)object; [self setActive:s.active]; return; } if ([key isEqualToString:NumberOfUsersPropertyKey]) { [self updateUserDataForServer:object]; return; } [self displayDataForServer:object]; } -(void)setActive:(BOOL)active { if ( active ) { if ( [toolbar.items objectAtIndex:ActivityButtonBarItemPosition] != activityIndicatorViewItem ) [toolbar insertItem:activityIndicatorViewItem inItemsAtIndex:ActivityButtonBarItemPosition animated:NO]; [activityIndicatorView startAnimating]; currentCommandLabel.textAlignment = UITextAlignmentLeft; } else { [activityIndicatorView stopAnimating]; if ( [toolbar.items objectAtIndex:ActivityButtonBarItemPosition] == activityIndicatorViewItem ) [toolbar removeItemFromItemsAtIndex:ActivityButtonBarItemPosition animated:NO]; currentCommandLabel.textAlignment = UITextAlignmentCenter; } currentCommandLabel.text = server.currentCommandDescription; } -(void)observeServer:(P4Server*)s { [s addObserver:self forKeyPath:InfoPropertyKey options:NSKeyValueObservingOptionNew context:NULL]; [s addObserver:self forKeyPath:MonitorInfoPropertyKey options:NSKeyValueObservingOptionNew context:NULL]; [s addObserver:self forKeyPath:NumberOfUsersPropertyKey options:NSKeyValueObservingOptionNew context:NULL]; [s addObserver:self forKeyPath:ConnectionStatePropertyKey options:NSKeyValueObservingOptionNew context:NULL]; [s addObserver:self forKeyPath:ActivePropertyKey options:NSKeyValueObservingOptionNew context:NULL]; [s addObserver:self forKeyPath:CurrentCommandPropertyKey options:NSKeyValueObservingOptionNew context:NULL]; } -(void)unobserveServer:(P4Server*)s { [s removeObserver:self forKeyPath:InfoPropertyKey]; [s removeObserver:self forKeyPath:MonitorInfoPropertyKey]; [s removeObserver:self forKeyPath:NumberOfUsersPropertyKey]; [s removeObserver:self forKeyPath:ConnectionStatePropertyKey]; [s removeObserver:self forKeyPath:ActivePropertyKey]; [s removeObserver:self forKeyPath:CurrentCommandPropertyKey]; } -(void)setServer:(P4Server *)s { BOOL wasActive = server.active; if ( server == s ) return; [self unobserveServer:server]; [s retain]; [server release]; server = s; [self observeServer:server]; monitorTableViewController.server = s; if ( wasActive != server.active ) [self setActive:server.active]; [self displayDataForServer:server]; [self configureEditButton]; } -(void)configureEditButton { if ( self.server.discovered ) { [editBarButtonItem setAction:@selector(addToSavedList:)]; editBarButtonItem.title = @"Keep"; } else { [editBarButtonItem setAction:@selector(edit:)]; editBarButtonItem.title = @"Edit"; } } -(IBAction)refresh:(id)sender { [server refresh:self]; } -(IBAction)addToSavedList:(id)sender { NSAssert( self.server.discovered, @"server not discoverable" ); // Make a copy of the discovered server, add it to the master list, // and reset ourselves to use it // We use the "serverAddress" field as the saved address rather than p4port // because p4port likely refers to items that can only be seen when // on the local network. We'd like to be able to connect even when // outside the local network P4Server * s = [[[P4Server alloc] init] autorelease]; s.p4port = [self.server.info objectForKey:@"serverAddress"]; s.label = self.server.label; [s refresh:self]; [[self serverTableController] addServer:s]; self.server = s; [UIView beginAnimations:@"frame" context:nil]; [UIView setAnimationDuration:1.0]; CGRect disappearingFrame = discoveredBadgeLabel.frame; disappearingFrame.origin.y -= disappearingFrame.size.height; discoveredBadgeLabel.frame = disappearingFrame; [UIView commitAnimations]; } -(IBAction)edit:(id)sender { editViewController.deleteButton = deleteButton; editViewController.server = self.server; // we need to specify a copy [self.navigationController presentModalViewController:serverEditDialogController animated:YES]; } - (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(int)index { if ( index != 0 ) // delete button return; [self.navigationController dismissModalViewControllerAnimated:YES]; // set ourself to the copy [[self serverTableController] removeServer:self.server]; [self.navigationController popViewControllerAnimated:YES]; // pop ourselves off the stack } -(ServerTableController*)serverTableController { return (ServerTableController*)[self.navigationController.viewControllers objectAtIndex:0]; } -(IBAction)remove:(id)sender { // open a dialog with an OK and cancel button UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"Delete Server?" delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:@"Delete server" otherButtonTitles:nil]; actionSheet.actionSheetStyle = UIActionSheetStyleDefault; [actionSheet showInView:self.view]; // show from our table view (pops up in the middle of the table) [actionSheet release]; } - (IBAction)save:(id)sender { [self.navigationController dismissModalViewControllerAnimated:YES]; // set ourself to the copy [server refresh:self]; [self displayDataForServer:server]; } - (IBAction)cancel:(id)sender { [self.navigationController dismissModalViewControllerAnimated:YES]; // we should not set ourself to the copy } -(IBAction)openSSH:(id)sender { [[UIApplication sharedApplication] openURL:[self sshUrl]]; } -(IBAction)showTasks:(id)sender { if ( [server.monitorInfo count] <= 0 ) return; [self.navigationController pushViewController:monitorTableViewController animated:YES]; } // Implement viewDidLoad to do additional setup after loading the view. - (void)viewDidLoad { self.navigationItem.title = @"Detail"; self.navigationItem.rightBarButtonItem = editButton; monitorTableViewController.server = self.server; [self displayDataForServer:server]; [self setActive:server.active]; [self configureEditButton]; if (![[UIApplication sharedApplication] canOpenURL:[self sshUrl]]) [toolbar removeItem:sshButton fromItemsAnimated:NO]; [super viewDidLoad]; } - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { // Return YES for supported orientations return (interfaceOrientation == UIInterfaceOrientationPortrait); } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview // Release anything that's not essential, such as cached data } - (void)dealloc { [self unobserveServer:server]; [server release]; [super dealloc]; } @end