From b87ae3ee52e0e763e983ed077a8144ad3a07bc78 Mon Sep 17 00:00:00 2001 From: Adam Tootle Date: Mon, 28 Apr 2014 23:48:47 -0400 Subject: [PATCH 1/5] Adding very basic support for creating new droplets. --- DODropletManager.xcodeproj/project.pbxproj | 16 ++ DODropletManager/AppDelegate.h | 3 + DODropletManager/AppDelegate.m | 10 + DODropletManager/DropletFormWindow.xib | 131 ++++++++++ .../DropletFormWindowController.h | 22 ++ .../DropletFormWindowController.m | 233 ++++++++++++++++++ DODropletManager/NSString+URLEncode.h | 18 ++ DODropletManager/NSString+URLEncode.m | 35 +++ 8 files changed, 468 insertions(+) create mode 100644 DODropletManager/DropletFormWindow.xib create mode 100644 DODropletManager/DropletFormWindowController.h create mode 100644 DODropletManager/DropletFormWindowController.m create mode 100644 DODropletManager/NSString+URLEncode.h create mode 100644 DODropletManager/NSString+URLEncode.m diff --git a/DODropletManager.xcodeproj/project.pbxproj b/DODropletManager.xcodeproj/project.pbxproj index dc90001..c0d9e1a 100644 --- a/DODropletManager.xcodeproj/project.pbxproj +++ b/DODropletManager.xcodeproj/project.pbxproj @@ -23,6 +23,9 @@ 4DC5DB35190CF38B0080C9CD /* Droplet.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DC5DB34190CF38B0080C9CD /* Droplet.m */; }; 4DC5DB3A190D030C0080C9CD /* PreferencesWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4DC5DB39190D030C0080C9CD /* PreferencesWindow.xib */; }; 4DC5DB3D190D03C30080C9CD /* PreferencesWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DC5DB3C190D03C30080C9CD /* PreferencesWindowController.m */; }; + DB835654190EE99900B62EA0 /* DropletFormWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = DB835653190EE99900B62EA0 /* DropletFormWindowController.m */; }; + DB835656190EE9C400B62EA0 /* DropletFormWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = DB835655190EE9C400B62EA0 /* DropletFormWindow.xib */; }; + DB835659190F4B9E00B62EA0 /* NSString+URLEncode.m in Sources */ = {isa = PBXBuildFile; fileRef = DB835658190F4B9E00B62EA0 /* NSString+URLEncode.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -74,6 +77,11 @@ 4DC5DB39190D030C0080C9CD /* PreferencesWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PreferencesWindow.xib; sourceTree = ""; }; 4DC5DB3B190D03C30080C9CD /* PreferencesWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PreferencesWindowController.h; sourceTree = ""; }; 4DC5DB3C190D03C30080C9CD /* PreferencesWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PreferencesWindowController.m; sourceTree = ""; }; + DB835652190EE99900B62EA0 /* DropletFormWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DropletFormWindowController.h; sourceTree = ""; }; + DB835653190EE99900B62EA0 /* DropletFormWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DropletFormWindowController.m; sourceTree = ""; }; + DB835655190EE9C400B62EA0 /* DropletFormWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DropletFormWindow.xib; sourceTree = ""; }; + DB835657190F4B9E00B62EA0 /* NSString+URLEncode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+URLEncode.h"; sourceTree = ""; }; + DB835658190F4B9E00B62EA0 /* NSString+URLEncode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+URLEncode.m"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -151,6 +159,11 @@ 4DC5DB14190CEA0A0080C9CD /* MainMenu.xib */, 4DC5DB17190CEA0A0080C9CD /* Images.xcassets */, 4DC5DB06190CEA0A0080C9CD /* Supporting Files */, + DB835652190EE99900B62EA0 /* DropletFormWindowController.h */, + DB835653190EE99900B62EA0 /* DropletFormWindowController.m */, + DB835655190EE9C400B62EA0 /* DropletFormWindow.xib */, + DB835657190F4B9E00B62EA0 /* NSString+URLEncode.h */, + DB835658190F4B9E00B62EA0 /* NSString+URLEncode.m */, ); path = DODropletManager; sourceTree = ""; @@ -264,6 +277,7 @@ files = ( 4DC5DB0A190CEA0A0080C9CD /* InfoPlist.strings in Resources */, 4DC5DB18190CEA0A0080C9CD /* Images.xcassets in Resources */, + DB835656190EE9C400B62EA0 /* DropletFormWindow.xib in Resources */, 4DC5DB3A190D030C0080C9CD /* PreferencesWindow.xib in Resources */, 4DC5DB10190CEA0A0080C9CD /* Credits.rtf in Resources */, 4DC5DB16190CEA0A0080C9CD /* MainMenu.xib in Resources */, @@ -286,7 +300,9 @@ buildActionMask = 2147483647; files = ( 4DC5DB13190CEA0A0080C9CD /* AppDelegate.m in Sources */, + DB835659190F4B9E00B62EA0 /* NSString+URLEncode.m in Sources */, 4DC5DB0C190CEA0A0080C9CD /* main.m in Sources */, + DB835654190EE99900B62EA0 /* DropletFormWindowController.m in Sources */, 4DC5DB3D190D03C30080C9CD /* PreferencesWindowController.m in Sources */, 4DC5DB35190CF38B0080C9CD /* Droplet.m in Sources */, ); diff --git a/DODropletManager/AppDelegate.h b/DODropletManager/AppDelegate.h index 41e2259..33902ad 100644 --- a/DODropletManager/AppDelegate.h +++ b/DODropletManager/AppDelegate.h @@ -9,6 +9,8 @@ #import "PreferencesWindowController.h" #import +@class DropletFormWindowController; + @interface AppDelegate : NSObject { NSMutableData *responseData; NSMutableArray *dropletsArray; @@ -33,5 +35,6 @@ @property (assign) IBOutlet NSWindow *window; @property (strong, nonatomic) NSStatusItem *statusItem; @property (strong) PreferencesWindowController *preferencesWC; +@property (strong, nonatomic) DropletFormWindowController *dropletFormWindowController; @end diff --git a/DODropletManager/AppDelegate.m b/DODropletManager/AppDelegate.m index da07745..a4cb023 100644 --- a/DODropletManager/AppDelegate.m +++ b/DODropletManager/AppDelegate.m @@ -9,6 +9,7 @@ #import "AppDelegate.h" #import "Droplet.h" +#import "DropletFormWindowController.h" @implementation AppDelegate @@ -339,6 +340,8 @@ - (void) createMenuItems { } + [menu addItemWithTitle:@"Add New Droplet" action:@selector(showDropletFormUI) keyEquivalent:@""]; + [menu addItem:[NSMenuItem separatorItem]]; @@ -361,6 +364,13 @@ - (void)copyIPAddress:(id)sender { [[NSPasteboard generalPasteboard] setString:ipAddress forType:NSStringPboardType]; } +- (void)showDropletFormUI +{ + _dropletFormWindowController = [[DropletFormWindowController alloc] initWithWindowNibName:@"DropletFormWindow"]; + [_dropletFormWindowController showWindow:self]; + + [NSApp activateIgnoringOtherApps:YES]; +} - (void)showPreferencesWindow:(id)sender { diff --git a/DODropletManager/DropletFormWindow.xib b/DODropletManager/DropletFormWindow.xib new file mode 100644 index 0000000..af47a3b --- /dev/null +++ b/DODropletManager/DropletFormWindow.xib @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DODropletManager/DropletFormWindowController.h b/DODropletManager/DropletFormWindowController.h new file mode 100644 index 0000000..3a21d80 --- /dev/null +++ b/DODropletManager/DropletFormWindowController.h @@ -0,0 +1,22 @@ +// +// DropletFormWindowController.h +// DODropletManager +// +// Created by Adam Tootle on 4/28/14. +// Copyright (c) 2014 David Hsieh. All rights reserved. +// + +#import + +@interface DropletFormWindowController : NSWindowController + +@property(nonatomic, strong) IBOutlet NSTextField *dropletNameField; +@property(nonatomic, strong) IBOutlet NSPopUpButton *availableImagesPopup; +@property(nonatomic, strong) IBOutlet NSPopUpButton *availableRegionsPopup; +@property(nonatomic, strong) IBOutlet NSPopUpButton *availableSizePopup; + +- (IBAction)didSelectImage:(id)sender; +- (IBAction)createDroplet:(id)sender; +- (IBAction)cancelDroplet:(id)sender; + +@end diff --git a/DODropletManager/DropletFormWindowController.m b/DODropletManager/DropletFormWindowController.m new file mode 100644 index 0000000..9f17364 --- /dev/null +++ b/DODropletManager/DropletFormWindowController.m @@ -0,0 +1,233 @@ +// +// DropletFormWindowController.m +// DODropletManager +// +// Created by Adam Tootle on 4/28/14. +// Copyright (c) 2014 David Hsieh. All rights reserved. +// + +#import "DropletFormWindowController.h" +#import "NSString+URLEncode.h" + +@interface DropletFormWindowController () + +@end + +@implementation DropletFormWindowController { + NSURLConnection *availableImagesConnection; + NSURLConnection *availableSizesConnection; + NSURLConnection *createNewDropletConnection; + NSString *clientID; + NSString *APIKey; + NSMutableData *imagesResponseData; + NSMutableData *sizesResponseData; + NSMutableData *createDropletResponseData; + NSArray *availableImages; + NSArray *availableSizes; +} + +- (id)initWithWindow:(NSWindow *)window +{ + self = [super initWithWindow:window]; + if (self) { + imagesResponseData = [[NSMutableData alloc] init]; + sizesResponseData = [[NSMutableData alloc] init]; + createDropletResponseData = [[NSMutableData alloc] init]; + } + return self; +} + +- (void)windowDidLoad +{ + [super windowDidLoad]; + + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + + clientID = [userDefaults objectForKey:@"clientID"]; + APIKey = [userDefaults objectForKey:@"APIKey"]; + + clientID = [clientID stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]]; + APIKey = [APIKey stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]]; + + [self.availableImagesPopup addItemWithTitle:@"Select image..."]; + [self loadAvailableImages]; + + [self.availableRegionsPopup addItemWithTitle:@"Select region..."]; + + [self.availableSizePopup addItemWithTitle:@"Choose size..."]; + [self loadAvailableSizes]; +} + +- (void)createDroplet:(id)sender +{ + NSInteger selectedSizeIndex = [self.availableSizePopup indexOfItem:self.availableSizePopup.selectedItem]; + NSNumber *sizeID = availableSizes[selectedSizeIndex][@"id"]; + + NSInteger selectedImageIndex = [self.availableImagesPopup indexOfItem:self.availableImagesPopup.selectedItem]; + NSNumber *imageID = availableImages[selectedImageIndex][@"id"]; + + NSInteger selectedRegionIndex = [self.availableRegionsPopup indexOfItem:self.availableRegionsPopup.selectedItem]; + NSNumber *regionID = availableImages[selectedImageIndex][@"region_slugs"][selectedRegionIndex]; + + NSString *path = [NSString stringWithFormat:@"https://api.digitalocean.com/droplets/new/?client_id=%@&api_key=%@&name=%@&size_id=%@&image_id=%@®ion_slug=%@", clientID, APIKey, [self.dropletNameField.stringValue urlEncode], sizeID, imageID, regionID]; + + NSLog(@"%@", path); + + NSURL *url = [NSURL URLWithString:path]; + NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0]; + createNewDropletConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self]; +} + +- (void)cancelDroplet:(id)sender +{ + [self close]; +} + +- (void)didEndSuccessAlert +{ + [self close]; +}; + +- (void)loadAvailableImages +{ + NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"https://api.digitalocean.com/images/?filter=global&client_id=%@&api_key=%@", clientID, APIKey]]; + NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0]; + availableImagesConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self]; +} + +- (void)loadAvailableSizes +{ + NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"https://api.digitalocean.com/sizes/?client_id=%@&api_key=%@", clientID, APIKey]]; + NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0]; + availableSizesConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self]; +} + +- (void)didSelectImage:(id)sender +{ + if([sender isEqual:self.availableImagesPopup]) + { + [self.availableRegionsPopup removeAllItems]; + [self.availableRegionsPopup addItemWithTitle:@"Select region..."]; + NSInteger selectedImageIndex = [self.availableImagesPopup indexOfItem:self.availableImagesPopup.selectedItem]; + + for(NSString *slug in availableImages[selectedImageIndex][@"region_slugs"]) + { + [self.availableRegionsPopup addItemWithTitle:slug]; + } + } +} + +#pragma mark - NSURLConnectionDelegate + +- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response +{ + if([connection isEqual:availableImagesConnection]) + { + [imagesResponseData setLength:0]; + } + else if([connection isEqual:availableSizesConnection]) + { + [sizesResponseData setLength:0]; + } + else if([connection isEqual:createNewDropletConnection]) + { + [createDropletResponseData setLength:0]; + } +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data +{ + if([connection isEqual:availableImagesConnection]) + { + [imagesResponseData appendData:data]; + } + else if([connection isEqual:availableSizesConnection]) + { + [sizesResponseData appendData:data]; + } + else if([connection isEqual:createNewDropletConnection]) + { + [createDropletResponseData appendData:data]; + } +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection +{ + NSError *error; + NSDictionary* json; + + if([connection isEqual:availableImagesConnection]) + { + json = [NSJSONSerialization + JSONObjectWithData:imagesResponseData + options:NSUTF8StringEncoding + error:&error]; + } + else if([connection isEqual:availableSizesConnection]) + { + json = [NSJSONSerialization + JSONObjectWithData:sizesResponseData + options:NSUTF8StringEncoding + error:&error]; + } + else if([connection isEqual:createNewDropletConnection]) + { + json = [NSJSONSerialization + JSONObjectWithData:createDropletResponseData + options:NSUTF8StringEncoding + error:&error]; + } + + if([connection isEqual:availableImagesConnection] && json != nil) + { + availableImages = [NSArray arrayWithArray:json[@"images"]]; + for(NSDictionary *image in availableImages) + { + [self.availableImagesPopup addItemWithTitle:image[@"name"]]; + } + } + else if([connection isEqual:availableSizesConnection] && json != nil) + { + availableSizes = [NSArray arrayWithArray:json[@"sizes"]]; + for(NSDictionary *image in availableSizes) + { + [self.availableSizePopup addItemWithTitle:image[@"name"]]; + } + } + else if([connection isEqual:createNewDropletConnection]) + { + NSLog(@"%@", json); + + if(json[@"error_message"] != nil) + { + NSAlert *alert = [NSAlert alertWithMessageText:@"Error" + defaultButton:@"Ok" + alternateButton:nil + otherButton:nil + informativeTextWithFormat:@"%@", json[@"error_message"]]; + [alert beginSheetModalForWindow:self.window + modalDelegate:nil + didEndSelector:nil + contextInfo:nil]; + } + else + { + NSAlert *alert = [NSAlert alertWithMessageText:@"Success!" + defaultButton:@"Ok" + alternateButton:nil + otherButton:nil + informativeTextWithFormat:@"Droplet created successfully!"]; + [alert beginSheetModalForWindow:self.window + modalDelegate:self + didEndSelector:@selector(didEndSuccessAlert) + contextInfo:nil]; + } + } +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error +{ + NSLog(@"%@", [error description]); +} + +@end diff --git a/DODropletManager/NSString+URLEncode.h b/DODropletManager/NSString+URLEncode.h new file mode 100644 index 0000000..11de50f --- /dev/null +++ b/DODropletManager/NSString+URLEncode.h @@ -0,0 +1,18 @@ +// +// NSString+UrlEncode.h +// +// Created by Kevin Renskers on 31-10-13. +// Copyright (c) 2013 Kevin Renskers. All rights reserved. +// + +#import + +@interface NSString (UrlEncode) + +- (NSString *)urlEncode; +- (NSString *)urlEncodeUsingEncoding:(NSStringEncoding)encoding; + +- (NSString *)urlDecode; +- (NSString *)urlDecodeUsingEncoding:(NSStringEncoding)encoding; + +@end \ No newline at end of file diff --git a/DODropletManager/NSString+URLEncode.m b/DODropletManager/NSString+URLEncode.m new file mode 100644 index 0000000..aff0716 --- /dev/null +++ b/DODropletManager/NSString+URLEncode.m @@ -0,0 +1,35 @@ +// +// NSString+UrlEncode.m +// +// Created by Kevin Renskers on 31-10-13. +// Copyright (c) 2013 Kevin Renskers. All rights reserved. +// + +#import "NSString+UrlEncode.h" + +@implementation NSString (UrlEncode) + +- (NSString *)urlEncode { + return [self urlEncodeUsingEncoding:NSUTF8StringEncoding]; +} + +- (NSString *)urlEncodeUsingEncoding:(NSStringEncoding)encoding { + return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, + (__bridge CFStringRef)self, + NULL, + (CFStringRef)@"!*'\"();:@&=+$,/?%#[]% ", + CFStringConvertNSStringEncodingToEncoding(encoding)); +} + +- (NSString *)urlDecode { + return [self urlDecodeUsingEncoding:NSUTF8StringEncoding]; +} + +- (NSString *)urlDecodeUsingEncoding:(NSStringEncoding)encoding { + return (__bridge_transfer NSString *)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(NULL, + (__bridge CFStringRef)self, + CFSTR(""), + CFStringConvertNSStringEncodingToEncoding(encoding)); +} + +@end \ No newline at end of file From 299ede415b0323d85109221b76ff592999914947 Mon Sep 17 00:00:00 2001 From: Adam Tootle Date: Tue, 29 Apr 2014 01:37:22 -0400 Subject: [PATCH 2/5] Updating the window title when creating a droplet. --- DODropletManager/DropletFormWindow.xib | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/DODropletManager/DropletFormWindow.xib b/DODropletManager/DropletFormWindow.xib index af47a3b..e92c3d9 100644 --- a/DODropletManager/DropletFormWindow.xib +++ b/DODropletManager/DropletFormWindow.xib @@ -1,6 +1,7 @@ + @@ -15,7 +16,7 @@ - + @@ -91,7 +92,7 @@ Gw - + @@ -120,7 +121,7 @@ Gw - + From f839d8c1fa9da5a26d46deaf574c729501249077 Mon Sep 17 00:00:00 2001 From: Adam Tootle Date: Mon, 28 Apr 2014 23:48:47 -0400 Subject: [PATCH 3/5] Adding very basic support for creating new droplets. --- DODropletManager.xcodeproj/project.pbxproj | 16 ++ DODropletManager/AppDelegate.h | 3 + DODropletManager/AppDelegate.m | 10 +- DODropletManager/DropletFormWindow.xib | 131 ++++++++++ .../DropletFormWindowController.h | 22 ++ .../DropletFormWindowController.m | 233 ++++++++++++++++++ DODropletManager/NSString+URLEncode.h | 18 ++ DODropletManager/NSString+URLEncode.m | 35 +++ 8 files changed, 467 insertions(+), 1 deletion(-) create mode 100644 DODropletManager/DropletFormWindow.xib create mode 100644 DODropletManager/DropletFormWindowController.h create mode 100644 DODropletManager/DropletFormWindowController.m create mode 100644 DODropletManager/NSString+URLEncode.h create mode 100644 DODropletManager/NSString+URLEncode.m diff --git a/DODropletManager.xcodeproj/project.pbxproj b/DODropletManager.xcodeproj/project.pbxproj index 809b600..de48726 100644 --- a/DODropletManager.xcodeproj/project.pbxproj +++ b/DODropletManager.xcodeproj/project.pbxproj @@ -25,6 +25,9 @@ 4DC5DB3D190D03C30080C9CD /* PreferencesWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DC5DB3C190D03C30080C9CD /* PreferencesWindowController.m */; }; 65E32DB0190F7A4900476F2B /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65E32DAF190F7A4900476F2B /* Security.framework */; }; 65E32DB3190F7B2E00476F2B /* KeychainAccess.m in Sources */ = {isa = PBXBuildFile; fileRef = 65E32DB2190F7B2E00476F2B /* KeychainAccess.m */; }; + DB835654190EE99900B62EA0 /* DropletFormWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = DB835653190EE99900B62EA0 /* DropletFormWindowController.m */; }; + DB835656190EE9C400B62EA0 /* DropletFormWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = DB835655190EE9C400B62EA0 /* DropletFormWindow.xib */; }; + DB835659190F4B9E00B62EA0 /* NSString+URLEncode.m in Sources */ = {isa = PBXBuildFile; fileRef = DB835658190F4B9E00B62EA0 /* NSString+URLEncode.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -79,6 +82,11 @@ 65E32DAF190F7A4900476F2B /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 65E32DB1190F7B2E00476F2B /* KeychainAccess.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KeychainAccess.h; sourceTree = ""; }; 65E32DB2190F7B2E00476F2B /* KeychainAccess.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KeychainAccess.m; sourceTree = ""; }; + DB835652190EE99900B62EA0 /* DropletFormWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DropletFormWindowController.h; sourceTree = ""; }; + DB835653190EE99900B62EA0 /* DropletFormWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DropletFormWindowController.m; sourceTree = ""; }; + DB835655190EE9C400B62EA0 /* DropletFormWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DropletFormWindow.xib; sourceTree = ""; }; + DB835657190F4B9E00B62EA0 /* NSString+URLEncode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+URLEncode.h"; sourceTree = ""; }; + DB835658190F4B9E00B62EA0 /* NSString+URLEncode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+URLEncode.m"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -160,6 +168,11 @@ 4DC5DB14190CEA0A0080C9CD /* MainMenu.xib */, 4DC5DB17190CEA0A0080C9CD /* Images.xcassets */, 4DC5DB06190CEA0A0080C9CD /* Supporting Files */, + DB835652190EE99900B62EA0 /* DropletFormWindowController.h */, + DB835653190EE99900B62EA0 /* DropletFormWindowController.m */, + DB835655190EE9C400B62EA0 /* DropletFormWindow.xib */, + DB835657190F4B9E00B62EA0 /* NSString+URLEncode.h */, + DB835658190F4B9E00B62EA0 /* NSString+URLEncode.m */, ); path = DODropletManager; sourceTree = ""; @@ -273,6 +286,7 @@ files = ( 4DC5DB0A190CEA0A0080C9CD /* InfoPlist.strings in Resources */, 4DC5DB18190CEA0A0080C9CD /* Images.xcassets in Resources */, + DB835656190EE9C400B62EA0 /* DropletFormWindow.xib in Resources */, 4DC5DB3A190D030C0080C9CD /* PreferencesWindow.xib in Resources */, 4DC5DB10190CEA0A0080C9CD /* Credits.rtf in Resources */, 4DC5DB16190CEA0A0080C9CD /* MainMenu.xib in Resources */, @@ -295,7 +309,9 @@ buildActionMask = 2147483647; files = ( 4DC5DB13190CEA0A0080C9CD /* AppDelegate.m in Sources */, + DB835659190F4B9E00B62EA0 /* NSString+URLEncode.m in Sources */, 4DC5DB0C190CEA0A0080C9CD /* main.m in Sources */, + DB835654190EE99900B62EA0 /* DropletFormWindowController.m in Sources */, 4DC5DB3D190D03C30080C9CD /* PreferencesWindowController.m in Sources */, 65E32DB3190F7B2E00476F2B /* KeychainAccess.m in Sources */, 4DC5DB35190CF38B0080C9CD /* Droplet.m in Sources */, diff --git a/DODropletManager/AppDelegate.h b/DODropletManager/AppDelegate.h index aace384..3422c19 100644 --- a/DODropletManager/AppDelegate.h +++ b/DODropletManager/AppDelegate.h @@ -9,6 +9,8 @@ #import "PreferencesWindowController.h" #import +@class DropletFormWindowController; + @interface AppDelegate : NSObject { NSMutableData *responseData; NSMutableArray *dropletsArray; @@ -35,5 +37,6 @@ @property (assign) IBOutlet NSWindow *window; @property (strong, nonatomic) NSStatusItem *statusItem; @property (strong) PreferencesWindowController *preferencesWC; +@property (strong, nonatomic) DropletFormWindowController *dropletFormWindowController; @end diff --git a/DODropletManager/AppDelegate.m b/DODropletManager/AppDelegate.m index 8aa0e5f..0b15bfc 100644 --- a/DODropletManager/AppDelegate.m +++ b/DODropletManager/AppDelegate.m @@ -10,6 +10,8 @@ #import "Droplet.h" #import "KeychainAccess.h" +#import "DropletFormWindowController.h" + @implementation AppDelegate - (void)applicationDidFinishLaunching:(NSNotification *)aNotification @@ -324,7 +326,6 @@ - (void) createMenuItems { [menu addItemWithTitle:@"Preferences" action:@selector(showPreferencesWindow:) keyEquivalent:@""]; [menu addItem:[NSMenuItem separatorItem]]; - [menu addItemWithTitle:@"Quit Droplets Manager" action:@selector(terminate:) keyEquivalent:@""]; _statusItem.menu = menu; @@ -338,6 +339,13 @@ - (void)copyIPAddress:(id)sender { [[NSPasteboard generalPasteboard] setString:ipAddress forType:NSStringPboardType]; } +- (void)showDropletFormUI +{ + _dropletFormWindowController = [[DropletFormWindowController alloc] initWithWindowNibName:@"DropletFormWindow"]; + [_dropletFormWindowController showWindow:self]; + + [NSApp activateIgnoringOtherApps:YES]; +} - (void)showPreferencesWindow:(id)sender { diff --git a/DODropletManager/DropletFormWindow.xib b/DODropletManager/DropletFormWindow.xib new file mode 100644 index 0000000..af47a3b --- /dev/null +++ b/DODropletManager/DropletFormWindow.xib @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DODropletManager/DropletFormWindowController.h b/DODropletManager/DropletFormWindowController.h new file mode 100644 index 0000000..3a21d80 --- /dev/null +++ b/DODropletManager/DropletFormWindowController.h @@ -0,0 +1,22 @@ +// +// DropletFormWindowController.h +// DODropletManager +// +// Created by Adam Tootle on 4/28/14. +// Copyright (c) 2014 David Hsieh. All rights reserved. +// + +#import + +@interface DropletFormWindowController : NSWindowController + +@property(nonatomic, strong) IBOutlet NSTextField *dropletNameField; +@property(nonatomic, strong) IBOutlet NSPopUpButton *availableImagesPopup; +@property(nonatomic, strong) IBOutlet NSPopUpButton *availableRegionsPopup; +@property(nonatomic, strong) IBOutlet NSPopUpButton *availableSizePopup; + +- (IBAction)didSelectImage:(id)sender; +- (IBAction)createDroplet:(id)sender; +- (IBAction)cancelDroplet:(id)sender; + +@end diff --git a/DODropletManager/DropletFormWindowController.m b/DODropletManager/DropletFormWindowController.m new file mode 100644 index 0000000..9f17364 --- /dev/null +++ b/DODropletManager/DropletFormWindowController.m @@ -0,0 +1,233 @@ +// +// DropletFormWindowController.m +// DODropletManager +// +// Created by Adam Tootle on 4/28/14. +// Copyright (c) 2014 David Hsieh. All rights reserved. +// + +#import "DropletFormWindowController.h" +#import "NSString+URLEncode.h" + +@interface DropletFormWindowController () + +@end + +@implementation DropletFormWindowController { + NSURLConnection *availableImagesConnection; + NSURLConnection *availableSizesConnection; + NSURLConnection *createNewDropletConnection; + NSString *clientID; + NSString *APIKey; + NSMutableData *imagesResponseData; + NSMutableData *sizesResponseData; + NSMutableData *createDropletResponseData; + NSArray *availableImages; + NSArray *availableSizes; +} + +- (id)initWithWindow:(NSWindow *)window +{ + self = [super initWithWindow:window]; + if (self) { + imagesResponseData = [[NSMutableData alloc] init]; + sizesResponseData = [[NSMutableData alloc] init]; + createDropletResponseData = [[NSMutableData alloc] init]; + } + return self; +} + +- (void)windowDidLoad +{ + [super windowDidLoad]; + + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + + clientID = [userDefaults objectForKey:@"clientID"]; + APIKey = [userDefaults objectForKey:@"APIKey"]; + + clientID = [clientID stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]]; + APIKey = [APIKey stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]]; + + [self.availableImagesPopup addItemWithTitle:@"Select image..."]; + [self loadAvailableImages]; + + [self.availableRegionsPopup addItemWithTitle:@"Select region..."]; + + [self.availableSizePopup addItemWithTitle:@"Choose size..."]; + [self loadAvailableSizes]; +} + +- (void)createDroplet:(id)sender +{ + NSInteger selectedSizeIndex = [self.availableSizePopup indexOfItem:self.availableSizePopup.selectedItem]; + NSNumber *sizeID = availableSizes[selectedSizeIndex][@"id"]; + + NSInteger selectedImageIndex = [self.availableImagesPopup indexOfItem:self.availableImagesPopup.selectedItem]; + NSNumber *imageID = availableImages[selectedImageIndex][@"id"]; + + NSInteger selectedRegionIndex = [self.availableRegionsPopup indexOfItem:self.availableRegionsPopup.selectedItem]; + NSNumber *regionID = availableImages[selectedImageIndex][@"region_slugs"][selectedRegionIndex]; + + NSString *path = [NSString stringWithFormat:@"https://api.digitalocean.com/droplets/new/?client_id=%@&api_key=%@&name=%@&size_id=%@&image_id=%@®ion_slug=%@", clientID, APIKey, [self.dropletNameField.stringValue urlEncode], sizeID, imageID, regionID]; + + NSLog(@"%@", path); + + NSURL *url = [NSURL URLWithString:path]; + NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0]; + createNewDropletConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self]; +} + +- (void)cancelDroplet:(id)sender +{ + [self close]; +} + +- (void)didEndSuccessAlert +{ + [self close]; +}; + +- (void)loadAvailableImages +{ + NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"https://api.digitalocean.com/images/?filter=global&client_id=%@&api_key=%@", clientID, APIKey]]; + NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0]; + availableImagesConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self]; +} + +- (void)loadAvailableSizes +{ + NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"https://api.digitalocean.com/sizes/?client_id=%@&api_key=%@", clientID, APIKey]]; + NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0]; + availableSizesConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self]; +} + +- (void)didSelectImage:(id)sender +{ + if([sender isEqual:self.availableImagesPopup]) + { + [self.availableRegionsPopup removeAllItems]; + [self.availableRegionsPopup addItemWithTitle:@"Select region..."]; + NSInteger selectedImageIndex = [self.availableImagesPopup indexOfItem:self.availableImagesPopup.selectedItem]; + + for(NSString *slug in availableImages[selectedImageIndex][@"region_slugs"]) + { + [self.availableRegionsPopup addItemWithTitle:slug]; + } + } +} + +#pragma mark - NSURLConnectionDelegate + +- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response +{ + if([connection isEqual:availableImagesConnection]) + { + [imagesResponseData setLength:0]; + } + else if([connection isEqual:availableSizesConnection]) + { + [sizesResponseData setLength:0]; + } + else if([connection isEqual:createNewDropletConnection]) + { + [createDropletResponseData setLength:0]; + } +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data +{ + if([connection isEqual:availableImagesConnection]) + { + [imagesResponseData appendData:data]; + } + else if([connection isEqual:availableSizesConnection]) + { + [sizesResponseData appendData:data]; + } + else if([connection isEqual:createNewDropletConnection]) + { + [createDropletResponseData appendData:data]; + } +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection +{ + NSError *error; + NSDictionary* json; + + if([connection isEqual:availableImagesConnection]) + { + json = [NSJSONSerialization + JSONObjectWithData:imagesResponseData + options:NSUTF8StringEncoding + error:&error]; + } + else if([connection isEqual:availableSizesConnection]) + { + json = [NSJSONSerialization + JSONObjectWithData:sizesResponseData + options:NSUTF8StringEncoding + error:&error]; + } + else if([connection isEqual:createNewDropletConnection]) + { + json = [NSJSONSerialization + JSONObjectWithData:createDropletResponseData + options:NSUTF8StringEncoding + error:&error]; + } + + if([connection isEqual:availableImagesConnection] && json != nil) + { + availableImages = [NSArray arrayWithArray:json[@"images"]]; + for(NSDictionary *image in availableImages) + { + [self.availableImagesPopup addItemWithTitle:image[@"name"]]; + } + } + else if([connection isEqual:availableSizesConnection] && json != nil) + { + availableSizes = [NSArray arrayWithArray:json[@"sizes"]]; + for(NSDictionary *image in availableSizes) + { + [self.availableSizePopup addItemWithTitle:image[@"name"]]; + } + } + else if([connection isEqual:createNewDropletConnection]) + { + NSLog(@"%@", json); + + if(json[@"error_message"] != nil) + { + NSAlert *alert = [NSAlert alertWithMessageText:@"Error" + defaultButton:@"Ok" + alternateButton:nil + otherButton:nil + informativeTextWithFormat:@"%@", json[@"error_message"]]; + [alert beginSheetModalForWindow:self.window + modalDelegate:nil + didEndSelector:nil + contextInfo:nil]; + } + else + { + NSAlert *alert = [NSAlert alertWithMessageText:@"Success!" + defaultButton:@"Ok" + alternateButton:nil + otherButton:nil + informativeTextWithFormat:@"Droplet created successfully!"]; + [alert beginSheetModalForWindow:self.window + modalDelegate:self + didEndSelector:@selector(didEndSuccessAlert) + contextInfo:nil]; + } + } +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error +{ + NSLog(@"%@", [error description]); +} + +@end diff --git a/DODropletManager/NSString+URLEncode.h b/DODropletManager/NSString+URLEncode.h new file mode 100644 index 0000000..11de50f --- /dev/null +++ b/DODropletManager/NSString+URLEncode.h @@ -0,0 +1,18 @@ +// +// NSString+UrlEncode.h +// +// Created by Kevin Renskers on 31-10-13. +// Copyright (c) 2013 Kevin Renskers. All rights reserved. +// + +#import + +@interface NSString (UrlEncode) + +- (NSString *)urlEncode; +- (NSString *)urlEncodeUsingEncoding:(NSStringEncoding)encoding; + +- (NSString *)urlDecode; +- (NSString *)urlDecodeUsingEncoding:(NSStringEncoding)encoding; + +@end \ No newline at end of file diff --git a/DODropletManager/NSString+URLEncode.m b/DODropletManager/NSString+URLEncode.m new file mode 100644 index 0000000..aff0716 --- /dev/null +++ b/DODropletManager/NSString+URLEncode.m @@ -0,0 +1,35 @@ +// +// NSString+UrlEncode.m +// +// Created by Kevin Renskers on 31-10-13. +// Copyright (c) 2013 Kevin Renskers. All rights reserved. +// + +#import "NSString+UrlEncode.h" + +@implementation NSString (UrlEncode) + +- (NSString *)urlEncode { + return [self urlEncodeUsingEncoding:NSUTF8StringEncoding]; +} + +- (NSString *)urlEncodeUsingEncoding:(NSStringEncoding)encoding { + return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, + (__bridge CFStringRef)self, + NULL, + (CFStringRef)@"!*'\"();:@&=+$,/?%#[]% ", + CFStringConvertNSStringEncodingToEncoding(encoding)); +} + +- (NSString *)urlDecode { + return [self urlDecodeUsingEncoding:NSUTF8StringEncoding]; +} + +- (NSString *)urlDecodeUsingEncoding:(NSStringEncoding)encoding { + return (__bridge_transfer NSString *)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(NULL, + (__bridge CFStringRef)self, + CFSTR(""), + CFStringConvertNSStringEncodingToEncoding(encoding)); +} + +@end \ No newline at end of file From 1e5c4eee4d2e8a49185445379436f02b3312e3ea Mon Sep 17 00:00:00 2001 From: Adam Tootle Date: Tue, 29 Apr 2014 01:37:22 -0400 Subject: [PATCH 4/5] Updating the window title when creating a droplet. --- DODropletManager/DropletFormWindow.xib | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/DODropletManager/DropletFormWindow.xib b/DODropletManager/DropletFormWindow.xib index af47a3b..e92c3d9 100644 --- a/DODropletManager/DropletFormWindow.xib +++ b/DODropletManager/DropletFormWindow.xib @@ -1,6 +1,7 @@ + @@ -15,7 +16,7 @@ - + @@ -91,7 +92,7 @@ Gw - + @@ -120,7 +121,7 @@ Gw - + From ce6090e340493678fe47498bc5a59f83d3c121c2 Mon Sep 17 00:00:00 2001 From: Adam Tootle Date: Wed, 30 Apr 2014 15:51:10 -0400 Subject: [PATCH 5/5] Updating DropletFormWindowController to use the new keychain access for API credentials. Also fixing a bug with using the popup buttons in the form. --- DODropletManager/AppDelegate.m | 2 ++ DODropletManager/DropletFormWindow.xib | 4 ++-- .../DropletFormWindowController.m | 21 +++++++++---------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/DODropletManager/AppDelegate.m b/DODropletManager/AppDelegate.m index 0b15bfc..f4cb3a6 100644 --- a/DODropletManager/AppDelegate.m +++ b/DODropletManager/AppDelegate.m @@ -317,6 +317,8 @@ - (void) createMenuItems { } + [menu insertItemWithTitle:@"Create New Droplet" action:@selector(showDropletFormUI) keyEquivalent:@"" atIndex:dropletMIIndex]; + if(addItems) { [menu addItem:[NSMenuItem separatorItem]]; diff --git a/DODropletManager/DropletFormWindow.xib b/DODropletManager/DropletFormWindow.xib index e92c3d9..6b15b7c 100644 --- a/DODropletManager/DropletFormWindow.xib +++ b/DODropletManager/DropletFormWindow.xib @@ -16,11 +16,11 @@ - + - + diff --git a/DODropletManager/DropletFormWindowController.m b/DODropletManager/DropletFormWindowController.m index 9f17364..c522edf 100644 --- a/DODropletManager/DropletFormWindowController.m +++ b/DODropletManager/DropletFormWindowController.m @@ -8,6 +8,7 @@ #import "DropletFormWindowController.h" #import "NSString+URLEncode.h" +#import "KeychainAccess.h" @interface DropletFormWindowController () @@ -41,13 +42,13 @@ - (void)windowDidLoad { [super windowDidLoad]; - NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + NSString *client; + NSString *key; - clientID = [userDefaults objectForKey:@"clientID"]; - APIKey = [userDefaults objectForKey:@"APIKey"]; - - clientID = [clientID stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]]; - APIKey = [APIKey stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]]; + if([KeychainAccess getClientId: &client andAPIKey: &key error: nil]) { + clientID = client; + APIKey = key; + } [self.availableImagesPopup addItemWithTitle:@"Select image..."]; [self loadAvailableImages]; @@ -60,19 +61,17 @@ - (void)windowDidLoad - (void)createDroplet:(id)sender { - NSInteger selectedSizeIndex = [self.availableSizePopup indexOfItem:self.availableSizePopup.selectedItem]; + NSInteger selectedSizeIndex = [self.availableSizePopup indexOfItem:self.availableSizePopup.selectedItem] - 1; NSNumber *sizeID = availableSizes[selectedSizeIndex][@"id"]; - NSInteger selectedImageIndex = [self.availableImagesPopup indexOfItem:self.availableImagesPopup.selectedItem]; + NSInteger selectedImageIndex = [self.availableImagesPopup indexOfItem:self.availableImagesPopup.selectedItem] - 1; NSNumber *imageID = availableImages[selectedImageIndex][@"id"]; - NSInteger selectedRegionIndex = [self.availableRegionsPopup indexOfItem:self.availableRegionsPopup.selectedItem]; + NSInteger selectedRegionIndex = [self.availableRegionsPopup indexOfItem:self.availableRegionsPopup.selectedItem] - 1; NSNumber *regionID = availableImages[selectedImageIndex][@"region_slugs"][selectedRegionIndex]; NSString *path = [NSString stringWithFormat:@"https://api.digitalocean.com/droplets/new/?client_id=%@&api_key=%@&name=%@&size_id=%@&image_id=%@®ion_slug=%@", clientID, APIKey, [self.dropletNameField.stringValue urlEncode], sizeID, imageID, regionID]; - NSLog(@"%@", path); - NSURL *url = [NSURL URLWithString:path]; NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0]; createNewDropletConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];