// // PLSAppDelegate.m // Pulse // // Created by Matt Attaway on 1/14/14. // Copyright (c) 2014 Zen of the Monkey. All rights reserved. // #import "PLSAppDelegate.h" #import "PLSConnectionController.h" #import "PLSLoginController.h" @interface PLSAppDelegate() @property (nonatomic,strong) NSStatusItem* statusItem; @property (nonatomic,strong) NSMenu* statusMenu; @property (nonatomic,strong) PLSConnectionController* connectionsController; @property (nonatomic,strong) PLSLoginController* loginController; @end @implementation PLSAppDelegate - (void)loadConnections { NSUserDefaults* prefs = [NSUserDefaults standardUserDefaults]; NSArray* connections = [prefs arrayForKey:@"connections"]; self.overseers = [[NSMutableArray alloc] init]; for(NSDictionary* connection in connections) { PLSOverseer* os = [[PLSOverseer alloc] initWithDictionary:connection]; [os startTracking]; os.delegate = self; [self.overseers addObject:os]; } } - (void)buildMenu { self.statusMenu = [[NSMenu alloc] initWithTitle:@""]; [self.statusMenu setAutoenablesItems:NO]; [self.statusMenu addItemWithTitle:@"Connections" action:@selector(showConnections:) keyEquivalent:@""]; [self.statusMenu addItemWithTitle:@"Login" action:@selector(findConnectionNeedingLogin:) keyEquivalent:@""]; [self.statusMenu addItemWithTitle:@"?????" action:nil keyEquivalent:@""]; [self.statusMenu addItemWithTitle:@"Profit" action:nil keyEquivalent:@""]; [self.statusMenu addItem:[NSMenuItem separatorItem]]; NSMenuItem* tItem = nil; tItem = [self.statusMenu addItemWithTitle:@"Quit" action:@selector(terminate:) keyEquivalent:@"q"]; [tItem setKeyEquivalentModifierMask:NSCommandKeyMask]; NSStatusBar* statusBar = [NSStatusBar systemStatusBar]; self.statusItem = [statusBar statusItemWithLength:NSVariableStatusItemLength]; [self.statusItem setImage:[NSImage imageNamed:@"perforce.png"]]; [self.statusItem setToolTip:@"Pulse 0.0\nUp-to-date (most likely)"]; [self.statusItem setHighlightMode:YES]; [self.statusItem setMenu:self.statusMenu]; } - (void)applicationDidFinishLaunching:(NSNotification*)aNotification { [self loadConnections]; [self buildMenu]; if([self.overseers count] == 0) { [self showConnections:self]; } } - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { NSMutableArray* osSettings = [[NSMutableArray alloc] init]; for(PLSOverseer* os in self.overseers) { [osSettings addObject:[os getSettings]]; } NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; [defaults setObject:osSettings forKey:@"connections"]; [defaults synchronize]; return NSTerminateNow; } - (IBAction)showConnections:(id)sender { if(!self.connectionsController) { self.connectionsController = [[PLSConnectionController alloc] initWithWindowNibName:@"Connections"]; self.connectionsController.connections = self.overseers; } [NSApp activateIgnoringOtherApps:YES]; [[self.connectionsController window] makeKeyAndOrderFront:self]; [self.connectionsController showWindow:self]; // notify self when the connection dialog closes so we can set ourselves as the overseer delegate on new connections [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(connectionWindowClosed:) name:NSWindowWillCloseNotification object:[self.connectionsController window]]; } - (IBAction)showLogin:(id)sender overseer:(PLSOverseer*)os { if(!self.loginController) { self.loginController = [[PLSLoginController alloc] initWithWindowNibName:@"Login"]; self.loginController.os = os; } [NSApp activateIgnoringOtherApps:YES]; [[self.loginController window] makeKeyAndOrderFront:self]; [self.loginController showWindow:self]; // notify self when the login dialog closes so we can clear the angry perforce icon if possible [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loginWindowClosed:) name:NSWindowWillCloseNotification object:[self.connectionsController window]]; } // find the first connection needing a login, or just pass through the list quietly - (IBAction)findConnectionNeedingLogin:(id)sender { for(PLSOverseer* os in self.overseers) { if(os.needsLogin) { [self showLogin:self overseer:os]; return; } } } // set self as the Overseer delegate for any new Overseers - (IBAction)connectionWindowClosed:(id)sender { for(PLSOverseer* os in self.overseers) { os.delegate = self; [os startTracking]; } } // see if we are all logged in again - (IBAction)loginWindowClosed:(id)sender { BOOL needsLogin = false; for(PLSOverseer* os in self.overseers) { needsLogin = needsLogin | os.needsLogin; } if(!needsLogin) { [self.statusItem setImage:[NSImage imageNamed:@"perforce.png"]]; } } // notify the users we need to login -(void)needPassword:(PLSOverseer*)os { [self.statusItem setImage:[NSImage imageNamed:@"perforce_angry.png"]]; NSUserNotification *notification = [[NSUserNotification alloc] init]; notification.title = @"Pulse requires you to login"; notification.informativeText = @"Files will no longer be updated or submitted."; notification.soundName = NSUserNotificationDefaultSoundName; [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification]; } // get the password right this minute -(void)needPasswordNow:(PLSOverseer*)os { // pop up the password dialog [self showLogin:self overseer:os]; } @end
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#23 | 8702 | Matt Attaway |
Add the ability to pause updates on a connection After some fun today where I added a gazillion files while running automated tests I realized there is is much to be said for being able to pause your automated friend. This change adds the ability to pause updates on a connection. Syncs and file events are not tracked while updates are paused. Paused connections are also not used when figuring out what the state icon should be. Also fix a bug related to all of this where a server coming online while in the connection dialog would not cause the icon to update. We now do an icon state check after coming out of the connection dialog. In addition, we use the proper timer when coming out of the connection dialog, not always the sync timer. User visible change |
||
#22 | 8689 | Matt Attaway |
Fix bug where observers were repeatedly added, causing FSEvent watcher errors Bug fix with no user visible change |
||
#21 | 8686 | Matt Attaway |
Swap in new connection dialog There’s still a fair amount of polish to do, but it’s good enough to replace the old connection dialog. User visible change |
||
#20 | 8685 | Matt Attaway |
Update connections with value from text fields in new dialog This still feels like it needs some testing, but values entered into the text boxes are actually used but the connections now after closing the dialog. User visible change |
||
#19 | 8684 | Matt Attaway |
First steps on the new connection dialog At this point it’s effectively a read only dialog. Changes made in the text fields are currently ignored. It does look much nicer though. To view it use the appropriately named ‘??????’ menu entry. User visible change |
||
#18 | 8653 | Matt Attaway |
Disable/enable the login menu when appropriate It’s pretty daft to have the login menu always active. --daftness User visible change |
||
#17 | 8652 | Matt Attaway |
Rework login to take advantage of other clients logging in Previously the Overseer entirely shut down operations when it noticed that the user needed to login. This is all well and good if you only use Pulse, but less helpful if use other clients too. This change introduces a new timer that attempts ‘p4 login -s’ every few seconds to see if there is a valid ticket. This is not unlike the timer that gets enabled when a connection is lost. This change also includes some WIP on submit support that I was too lazy to pull out. User visible changelist |
||
#16 | 8597 | Matt Attaway |
Handle disconnected servers automatically If Pulse loses its connection with the Perforce server it now reports that the connection is down and starts a new timer to look for a live connection. The Overseer handles all of this on its own; unlike login there’s really nothing for the user to do. This change also fixes a bug where the login dialog was not being properly regenerated for connections after the first. User visible change |
||
#15 | 8595 | Matt Attaway |
WIP on handling connection errors With this change we report the errors to our delegate, but we don’t attempt to recover. That will come with the next checkin. This change does add in the protocol for handling these errors. User visible change |
||
#14 | 8592 | Matt Attaway |
Add less aggressive notification when background processes detect need to login Instead of popping a dialog up right in your face we use a standard notification and change the status icon to indicate Pulse is unhappy. Using the login menu item will log the user is and restart the background processes. User visible change. |
||
#13 | 8590 | Matt Attaway |
Add crude login capabilities This is the first step toward getting the login behavior I want. Right now anytime a Peforce command gets a login error the Overseer ion charge notifies its delegate, which is currently the AppDelegate. The AppDelegate then kicks off the requisite login dialog which gets the password and does the login. There’s no error handling or feedback and the dialog pops up even when it is a background process that hit the error. The next step is to implement the Login menu item and the ‘need login’ tool tip and status icon. User visible (and possibly user annoying) change |
||
#12 | 8575 | Matt Attaway |
Automatically handle Unicode servers; stub out login handling There's a lot going on in this change: * Fixed a bug where charset was always set to none * Added protocol on PLSOverseer so that something can properly handle login errors * PLSAppDelegate implements stubs for the PLSOverseer protocol * PLSOverseers automatically configure themselves for Unicode servers now * Ripped out handleCommonErrors: because it turns out I want to handle the same error in multiple ways. Next step is to properly handle 'p4 login' and request a password. User visible changes. |
||
#11 | 8571 | Matt Attaway |
Expand connection properties for charset; add method to crudely handle common errors This is the next step towards handling login and unicode errors. The interfaces have been widened to track the charset. I’m using a string at this point so that I can just pass in the character set without having to test it; ‘none’ works with non-Unicode servers. The handler just logs what it would do so that I can test the behavior. Next step is to actually ask the user for their password and run login. Still no real functional change. |
||
#10 | 8537 | Matt Attaway |
Rework Pulse startup to (sorta) support real users Instead of repopulating the system with my test data it now pops up the connection dialog if there are no existing connections. This change also fixes a bug where the PLSConnection object wasn’t being refreshed when settings were changed on an Overseer. |
||
#9 | 8527 | Matt Attaway |
Improve query efficiency and improve feedback to user This change adds a check for the highest synced changed. Instead of blindly running sync over and over Pulse now runs ‘p4 changes -m 1 //<client>/...' and if the number is higher than the stored value it runs sync and then stores the new highest change. Highest change numbers are written to the preferences to further reduce needless queries. With this change we also fetch the list of changes between the previous high change and the new one so that we can report the number of changes synced. |
||
#8 | 8526 | Matt Attaway | Force connection dialog to the front | ||
#7 | 8523 | Matt Attaway |
Prevent double start call to FSEvents This was a bit of shuffling, but the behavior is much tidier now. Overseers do not start overseeing until they are explicitly told to do so. This makes their behavior much more predictable. |
||
#6 | 8519 | Matt Attaway |
Finally add a connections dialog to manage connections It’s taken a week to figure out how to manage windows and tabelviews, but this change adds a crude connection dialog to add and remove connections as well as save them out to the preferences file. More to do to make it more sane, but it works for now! |
||
#5 | 8511 | Matt Attaway |
Get a very crude form of reconcile and sync working. Continuing in my purely experimental mode, this change adds rough versions of the core features: 1) Every 5 seconds a timer fires and runs sync to grab new files. The user is made aware of the synced files via an OS X notification. 2) When files are modified ‘p4 reconcile’ is kicked off on the entire workspace Of course there is no error or sanity checking, but it is technically usable for very rough definitions of usable. |
||
#4 | 8509 | Matt Attaway |
Add support for loading multiple connections The code is rough, but Pulse can now load up and start watching multiple paths, each associated with a separate Perforce server. Also cleaned up some white space and normalized the ‘*’ used to denote pointers. |
||
#3 | 8506 | Matt Attaway |
Pull model out of controller; disable dock icon No functional change, but the code is moderately more sane. This change introduces an Overseer object that is responsible for maintaining one path/Port/User/Client combination. All of the delegation responsibilities move to the Overseer, as does FileSystemWatcher configuration. Overall this just sets me up to actually monitor multiple paths. |
||
#2 | 8504 | Matt Attaway |
Add all the APIs!!! This adds in a status bar icon with a pointless menu as well as needless notifications when a file changes in the watched path. While essentially useless, I think I’ve found the APIs for all the major pieces. Now I just need to make them do something useful… |
||
#1 | 8502 | Matt Attaway |
Initial checkin of Pulse, a lightweight Perforce client for the Mac This is a sketch of what a Perforce client inspired by the UX of cloud synchronization software would look like. I’m not trying to build something production worthy yet; at this point I’m just trying to get something up and running as fast as possible so I can play with the UX. At this point I can monitor a directory and kick off ‘p4 info’ calls anytime a file changes. It is the polar opposite of useful. Do note I’ve never written code in Objective-C before. Here be ugly, malformed dragons. |