GBA003/External/Harmony/Backends/Drive/Google/GoogleAPI/Examples/CalendarSample/CalendarSampleWindowController.m
2024-05-30 10:22:15 +08:00

1285 lines
45 KiB
Objective-C

/* Copyright (c) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//
// CalendarSampleWindowController.m
//
#import "CalendarSampleWindowController.h"
#import "EditEventWindowController.h"
#import "EditACLWindowController.h"
#import <AppAuth/AppAuth.h>
#import <GTMAppAuth/GTMAppAuth.h>
#import <GTMSessionFetcher/GTMSessionFetcherLogging.h>
#import <GoogleAPIClientForREST/GTLRUtilities.h>
enum {
kEventsSegment = 0,
kAccessControlSegment = 1,
kSettingsSegment = 2
};
@interface CalendarSampleWindowController ()
@property(readonly) GTLRCalendarService *calendarService;
@property(strong) GTLRCalendar_CalendarList *calendarList;
@property(strong) GTLRServiceTicket *calendarListTicket;
@property(strong) NSError *calendarListFetchError;
@property(strong) GTLRServiceTicket *editCalendarListTicket;
@property(strong) GTLRCalendar_Events *events;
@property(strong) GTLRServiceTicket *eventsTicket;
@property(strong) NSError *eventsFetchError;
@property(strong) GTLRCalendar_Acl *ACLs;
@property(strong) NSError *ACLsFetchError;
@property(strong) GTLRCalendar_Settings *settings;
@property(strong) NSError *settingsFetchError;
@property(strong) GTLRServiceTicket *editEventTicket;
@end
// This is the URL shown users after completing the OAuth flow. This is an information page only and
// is not part of the authorization protocol. You can replace it with any URL you like.
// We recommend at a minimum that the page displayed instructs users to return to the app.
static NSString *const kSuccessURLString = @"http://openid.github.io/AppAuth-iOS/redirect/";
// Keychain item name for saving the user's authentication information
NSString *const kGTMAppAuthKeychainItemName = @"CalendarSample: Google Calendar. GTMAppAuth";
@implementation CalendarSampleWindowController {
OIDRedirectHTTPHandler *_redirectHTTPHandler;
}
@synthesize calendarList = _calendarList,
calendarListTicket = _calendarListTicket,
calendarListFetchError = _calendarFetchError,
editCalendarListTicket = _editCalendarListTicket,
events = _events,
eventsTicket = _eventTicket,
eventsFetchError = _eventsFetchError,
ACLs = _calendarACLs,
ACLsFetchError = _calendarACLsFetchError,
settings = _settings,
settingsFetchError = _settingsFetchError,
editEventTicket = _editEventTicket;
+ (CalendarSampleWindowController *)sharedWindowController {
static CalendarSampleWindowController* gWindowController = nil;
if (!gWindowController) {
gWindowController = [[CalendarSampleWindowController alloc] init];
}
return gWindowController;
}
- (id)init {
return [self initWithWindowNibName:@"CalendarSampleWindow"];
}
- (void)awakeFromNib {
// Attempts to deserialize authorization from keychain in GTMAppAuth format.
id<GTMFetcherAuthorizationProtocol> authorization =
[GTMAppAuthFetcherAuthorization authorizationFromKeychainForName:kGTMAppAuthKeychainItemName];
self.calendarService.authorizer = authorization;
// Set the result text fields to have a distinctive color and mono-spaced font
_calendarResultTextField.textColor = [NSColor darkGrayColor];
_eventResultTextField.textColor = [NSColor darkGrayColor];
NSFont *resultTextFont = [NSFont fontWithName:@"Monaco" size:9];
_calendarResultTextField.font = resultTextFont;
_eventResultTextField.font = resultTextFont;
[self updateUI];
}
#pragma mark -
- (NSString *)signedInUsername {
// Get the email address of the signed-in user
id<GTMFetcherAuthorizationProtocol> auth = self.calendarService.authorizer;
BOOL isSignedIn = auth.canAuthorize;
if (isSignedIn) {
return auth.userEmail;
} else {
return nil;
}
}
- (BOOL)isSignedIn {
NSString *name = [self signedInUsername];
return (name != nil);
}
#pragma mark IBActions
- (IBAction)signInClicked:(id)sender {
if (![self isSignedIn]) {
// Sign in
[self runSigninThenInvokeSelector:@selector(updateUI)];
} else {
// Sign out
GTLRCalendarService *service = self.calendarService;
[GTMAppAuthFetcherAuthorization
removeAuthorizationFromKeychainForName:kGTMAppAuthKeychainItemName];
service.authorizer = nil;
[self updateUI];
}
}
- (IBAction)getCalendarList:(id)sender {
if (![self isSignedIn]) {
[self runSigninThenInvokeSelector:@selector(fetchCalendarList)];
} else {
[self fetchCalendarList];
}
}
- (IBAction)cancelCalendarFetch:(id)sender {
[self.calendarListTicket cancelTicket];
self.calendarListTicket = nil;
[self.editCalendarListTicket cancelTicket];
self.editCalendarListTicket = nil;
[self updateUI];
}
- (IBAction)cancelEventsFetch:(id)sender {
[self.eventsTicket cancelTicket];
self.eventsTicket = nil;
[self.editEventTicket cancelTicket];
self.editEventTicket = nil;
[self updateUI];
}
- (IBAction)entrySegmentClicked:(id)sender {
[self updateUI];
}
- (IBAction)addCalendar:(id)sender {
[self addACalendar];
}
- (IBAction)renameCalendar:(id)sender {
[self renameSelectedCalendar];
}
- (IBAction)deleteCalendar:(id)sender {
GTLRCalendar_CalendarListEntry *calendar = [self selectedCalendarListEntry];
NSString *title = calendar.summary;
NSAlert *alert = [[NSAlert alloc] init];
alert.messageText = [NSString stringWithFormat:@"Delete \"%@\"?", title];
[alert addButtonWithTitle:@"Delete"];
[alert addButtonWithTitle:@"Cancel"];
[alert beginSheetModalForWindow:self.window
completionHandler:^(NSModalResponse returnCode) {
if (returnCode == NSAlertFirstButtonReturn) {
[self deleteSelectedCalendar];
}
}];
}
- (IBAction)addEntry:(id)sender {
NSInteger segment = _entrySegmentedControl.selectedSegment;
if (segment == kEventsSegment) {
[self addAnEvent];
} else {
[self addAnACLRule];
}
}
- (IBAction)editEntry:(id)sender {
NSInteger segment = _entrySegmentedControl.selectedSegment;
if (segment == kEventsSegment) {
[self editSelectedEvent];
} else {
[self editSelectedACLRule];
}
}
- (IBAction)deleteEntries:(id)sender {
NSInteger segment = _entrySegmentedControl.selectedSegment;
if (segment == kEventsSegment) {
[self deleteSelectedEvent];
} else {
[self deleteSelectedACLRule];
}
}
- (IBAction)queryTodayClicked:(id)sender {
[self queryTodaysEvents];
}
- (IBAction)queryFreeBusyClicked:(id)sender {
[self queryFreeBusy];
}
- (IBAction)APIConsoleClicked:(id)sender {
NSURL *url = [NSURL URLWithString:@"https://console.developers.google.com/"];
[[NSWorkspace sharedWorkspace] openURL:url];
}
- (IBAction)loggingCheckboxClicked:(NSButton *)sender {
[GTMSessionFetcher setLoggingEnabled:[sender state]];
}
#pragma mark -
// Get a service object with the current username/password
//
// A "service" object handles networking tasks. Service objects
// contain user authentication information as well as networking
// state information (such as cookies and the "last modified" date for
// fetched data.)
- (GTLRCalendarService *)calendarService {
static GTLRCalendarService *service = nil;
if (!service) {
service = [[GTLRCalendarService alloc] init];
// Have the service object set tickets to fetch consecutive pages
// of the feed so we do not need to manually fetch them
service.shouldFetchNextPages = YES;
// Have the service object set tickets to retry temporary error conditions
// automatically
service.retryEnabled = YES;
}
return service;
}
- (GTLRCalendar_CalendarListEntry *)selectedCalendarListEntry {
NSInteger rowIndex = _calendarTable.selectedRow;
if (rowIndex > -1) {
GTLRCalendar_CalendarListEntry *item = self.calendarList[rowIndex];
return item;
}
return nil;
}
- (GTLRCalendar_Event *)selectedEvent {
if (_entrySegmentedControl.selectedSegment == kEventsSegment) {
NSInteger rowIndex = _eventTable.selectedRow;
if (rowIndex > -1) {
GTLRCalendar_Event *item = self.events[rowIndex];
return item;
}
}
return nil;
}
- (GTLRCalendar_AclRule *)selectedACLRule {
if (_entrySegmentedControl.selectedSegment == kAccessControlSegment) {
NSInteger rowIndex = _eventTable.selectedRow;
if (rowIndex > -1) {
GTLRCalendar_AclRule *item = self.ACLs[rowIndex];
return item;
}
}
return nil;
}
- (GTLRCalendar_Setting *)selectedSetting {
if (_entrySegmentedControl.selectedSegment == kSettingsSegment) {
NSInteger rowIndex = _eventTable.selectedRow;
if (rowIndex > -1) {
GTLRCalendar_Setting *item = self.settings[rowIndex];
return item;
}
}
return nil;
}
#pragma mark Fetch Calendar List
- (void)fetchCalendarList {
self.calendarList = nil;
self.calendarListFetchError = nil;
GTLRCalendarService *service = self.calendarService;
GTLRCalendarQuery_CalendarListList *query = [GTLRCalendarQuery_CalendarListList query];
BOOL shouldFetchedOwned = (_calendarSegmentedControl.selectedSegment == 1);
if (shouldFetchedOwned) {
query.minAccessRole = kGTLRCalendarMinAccessRoleOwner;
}
self.calendarListTicket = [service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
id calendarList,
NSError *callbackError) {
// Callback
self.calendarList = calendarList;
self.calendarListFetchError = callbackError;
self.calendarListTicket = nil;
[self updateUI];
}];
[self updateUI];
}
#pragma mark Fetch Selected Calendar
- (void)fetchSelectedCalendar {
self.events = nil;
self.eventsFetchError = nil;
self.ACLs = nil;
self.ACLsFetchError = nil;
self.settings = nil;
self.settingsFetchError = nil;
GTLRCalendarService *service = self.calendarService;
GTLRCalendar_CalendarListEntry *selectedCalendar = [self selectedCalendarListEntry];
if (selectedCalendar) {
NSString *calendarID = selectedCalendar.identifier;
// We will fetch the events for this calendar, the ACLs for this calendar,
// and the user's settings, together in a single batch.
GTLRBatchQuery *batch = [GTLRBatchQuery batchQuery];
GTLRCalendarQuery_EventsList *eventsQuery =
[GTLRCalendarQuery_EventsList queryWithCalendarId:calendarID];
eventsQuery.completionBlock = ^(GTLRServiceTicket *callbackTicket,
GTLRCalendar_Events *events, NSError *callbackError) {
self.events = events;
self.eventsFetchError = callbackError;
};
[batch addQuery:eventsQuery];
GTLRCalendarQuery_AclList *aclQuery = [GTLRCalendarQuery_AclList queryWithCalendarId:calendarID];
aclQuery.completionBlock = ^(GTLRServiceTicket *callbackTicket, GTLRCalendar_Acl *acls,
NSError *callbackError) {
self.ACLs = acls;
self.ACLsFetchError = callbackError;
};
[batch addQuery:aclQuery];
GTLRCalendarQuery_SettingsList *settingsQuery = [GTLRCalendarQuery_SettingsList query];
settingsQuery.completionBlock = ^(GTLRServiceTicket *callbackTicket,
GTLRCalendar_Settings *settings, NSError *callbackError) {
self.settings = settings;
self.settingsFetchError = callbackError;
};
[batch addQuery:settingsQuery];
self.eventsTicket = [service executeQuery:batch
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRBatchResult *batchResult,
NSError *callbackError) {
// Callback
//
// For batch queries with successful execution,
// the result is a GTLRBatchResult object
//
// At this point, the query completion blocks
// have already been called
self.eventsTicket = nil;
[self updateUI];
}];
[self updateUI];
}
}
#pragma mark Add, Rename, and Delete a Calendar
- (void)addACalendar {
NSString *newCalendarName = _calendarNameField.stringValue;
GTLRCalendarService *service = self.calendarService;
GTLRCalendar_Calendar *newEntry = [GTLRCalendar_Calendar object];
newEntry.summary = newCalendarName;
newEntry.timeZone = [[NSTimeZone localTimeZone] name];
GTLRCalendarQuery_CalendarsInsert *query =
[GTLRCalendarQuery_CalendarsInsert queryWithObject:newEntry];
self.editCalendarListTicket = [service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRCalendar_Calendar *calendar,
NSError *callbackError) {
// Callback
self.editCalendarListTicket = nil;
if (callbackError == nil) {
self->_calendarNameField.stringValue = @"";
[self fetchCalendarList];
} else {
[self displayAlert:@"Add failed"
format:@"Calendar add failed: %@", callbackError];
}
[self updateUI];
}];
[self updateUI];
}
- (void)renameSelectedCalendar {
GTLRCalendar_CalendarListEntry *selectedCalendarListEntry = [self selectedCalendarListEntry];
if (selectedCalendarListEntry) {
GTLRCalendarService *service = self.calendarService;
NSString *newCalendarName = _calendarNameField.stringValue;
// Modify a copy of the selected calendar, not the existing one in memory
GTLRCalendar_Calendar *patchObject = [GTLRCalendar_Calendar object];
patchObject.summary = newCalendarName;
NSString *calendarID = selectedCalendarListEntry.identifier;
GTLRCalendarQuery_CalendarsPatch *query =
[GTLRCalendarQuery_CalendarsPatch queryWithObject:patchObject
calendarId:calendarID];
self.editCalendarListTicket = [service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRCalendar_Calendar *calendar,
NSError *callbackError) {
// Callback
self.editCalendarListTicket = nil;
if (callbackError == nil) {
[self displayAlert:@"Renamed"
format:@"Renamed calendar \"%@\" as \"%@\"",
selectedCalendarListEntry.summary,
calendar.summary];
self->_calendarNameField.stringValue = @"";
[self fetchCalendarList];
} else {
[self displayAlert:@"Update failed"
format:@"Calendar update failed: %@", callbackError];
}
[self updateUI];
}];
[self updateUI];
}
}
- (void)deleteSelectedCalendar {
GTLRCalendar_CalendarListEntry *selectedCalendarListEntry = [self selectedCalendarListEntry];
if (selectedCalendarListEntry) {
GTLRCalendarService *service = self.calendarService;
NSString *calendarID = selectedCalendarListEntry.identifier;
GTLRCalendarQuery_CalendarsDelete *query =
[GTLRCalendarQuery_CalendarsDelete queryWithCalendarId:calendarID];
self.editCalendarListTicket = [service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
id nilObject, NSError *callbackError) {
// Callback
self.editCalendarListTicket = nil;
if (callbackError == nil) {
[self displayAlert:@"Deleted"
format:@"Deleted \"%@\"",
selectedCalendarListEntry.summary];
[self fetchCalendarList];
[self updateUI];
} else {
[self displayAlert:@"Delete failed"
format:@"Delete failed: %@", callbackError];
}
}];
}
}
#pragma mark Add, Edit, and Delete an Event
- (void)addAnEvent {
// Make a new event, and show it to the user to edit
GTLRCalendar_Event *newEvent = [GTLRCalendar_Event object];
newEvent.summary = @"Sample Added Event";
newEvent.descriptionProperty = @"Description of sample added event";
// We'll set the start time to now, and the end time to an hour from now,
// with a reminder 10 minutes before
NSDate *anHourFromNow = [NSDate dateWithTimeIntervalSinceNow:(60 * 60)];
// Include an offset minutes that tells Google Calendar that these dates
// are for the local time zone.
NSInteger offsetMinutes = [NSTimeZone localTimeZone].secondsFromGMT / 60;
GTLRDateTime *startDateTime = [GTLRDateTime dateTimeWithDate:[NSDate date]
offsetMinutes:offsetMinutes];
GTLRDateTime *endDateTime = [GTLRDateTime dateTimeWithDate:anHourFromNow
offsetMinutes:offsetMinutes];
newEvent.start = [GTLRCalendar_EventDateTime object];
newEvent.start.dateTime = startDateTime;
newEvent.end = [GTLRCalendar_EventDateTime object];
newEvent.end.dateTime = endDateTime;
GTLRCalendar_EventReminder *reminder = [GTLRCalendar_EventReminder object];
reminder.minutes = @10;
reminder.method = @"email";
newEvent.reminders = [GTLRCalendar_Event_Reminders object];
newEvent.reminders.overrides = @[ reminder ];
newEvent.reminders.useDefault = @NO;
// Display the event edit dialog
EditEventWindowController *controller = [[EditEventWindowController alloc] init];
[controller runModalForWindow:self.window
event:newEvent
completionHandler:^(NSInteger returnCode, GTLRCalendar_Event *event) {
// Callback
if (returnCode == NSModalResponseOK) {
[self addEvent:event];
}
}];
}
- (void)addEvent:(GTLRCalendar_Event *)event {
GTLRCalendarService *service = self.calendarService;
GTLRCalendar_CalendarListEntry *selectedCalendar = [self selectedCalendarListEntry];
NSString *calendarID = selectedCalendar.identifier;
GTLRCalendarQuery_EventsInsert *query =
[GTLRCalendarQuery_EventsInsert queryWithObject:event
calendarId:calendarID];
self.editEventTicket = [service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRCalendar_Event *event,
NSError *callbackError) {
// Callback
self.editEventTicket = nil;
if (callbackError == nil) {
[self displayAlert:@"Event Added"
format:@"Added event \"%@\"",
event.summary];
[self fetchSelectedCalendar];
} else {
[self displayAlert:@"Add failed"
format:@"Event add failed: %@", callbackError];
}
[self updateUI];
}];
[self updateUI];
}
- (void)editSelectedEvent {
// Show the selected event to the user to edit
GTLRCalendar_Event *eventToEdit = [self selectedEvent];
if (eventToEdit) {
EditEventWindowController *controller = [[EditEventWindowController alloc] init];
[controller runModalForWindow:self.window
event:eventToEdit
completionHandler:^(NSInteger returnCode, GTLRCalendar_Event *event) {
// Callback
if (returnCode == NSModalResponseOK) {
[self editSelectedEventWithEvent:event];
}
}];
}
}
- (void)editSelectedEventWithEvent:(GTLRCalendar_Event *)revisedEvent {
GTLRCalendarService *service = self.calendarService;
GTLRCalendar_CalendarListEntry *selectedCalendarListEntry = [self selectedCalendarListEntry];
GTLRCalendar_Event *originalEvent = [self selectedEvent];
GTLRCalendar_Event *patchEvent = [revisedEvent patchObjectFromOriginal:originalEvent];
if (patchEvent) {
NSString *calendarID = selectedCalendarListEntry.identifier;
NSString *eventID = originalEvent.identifier;
GTLRCalendarQuery_EventsPatch *query = [GTLRCalendarQuery_EventsPatch queryWithObject:patchEvent
calendarId:calendarID
eventId:eventID];
self.editEventTicket = [service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRCalendar_Event *event,
NSError *callbackError) {
// Callback
self.editEventTicket = nil;
if (callbackError == nil) {
[self displayAlert:@"Event Updated"
format:@"Patched event \"%@\"",
event.summary];
[self fetchSelectedCalendar];
} else {
[self displayAlert:@"Update failed"
format:@"Event patch failed: %@", callbackError];
}
[self updateUI];
}];
[self updateUI];
}
}
- (void)deleteSelectedEvent {
GTLRCalendarService *service = self.calendarService;
GTLRCalendar_CalendarListEntry *selectedCalendarListEntry = [self selectedCalendarListEntry];
NSString *calendarID = selectedCalendarListEntry.identifier;
GTLRCalendar_Event *selectedEvent = [self selectedEvent];
NSString *eventID = selectedEvent.identifier;
if (calendarID && eventID) {
GTLRCalendarQuery_EventsDelete *query =
[GTLRCalendarQuery_EventsDelete queryWithCalendarId:calendarID
eventId:eventID];
self.editEventTicket = [service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
id nilObject,
NSError *callbackError) {
// Callback
self.editEventTicket = nil;
if (callbackError == nil) {
[self displayAlert:@"Event deleted"
format:@"Deleted \"%@\"",
selectedEvent.summary];
[self fetchSelectedCalendar];
} else {
[self displayAlert:@"Delete failed"
format:@"Event delete failed: %@", callbackError];
}
[self updateUI];
}];
[self updateUI];
}
}
#pragma mark Query Events
// Utility routine to make a GTLRDateTime object for sometime today
- (GTLRDateTime *)dateTimeForTodayAtHour:(int)hour
minute:(int)minute
second:(int)second {
NSUInteger const kComponentBits = (NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay
| NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond);
NSCalendar *cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents *dateComponents = [cal components:kComponentBits
fromDate:[NSDate date]];
dateComponents.hour = hour;
dateComponents.minute = minute;
dateComponents.second = second;
dateComponents.timeZone = [NSTimeZone localTimeZone];
GTLRDateTime *dateTime = [GTLRDateTime dateTimeWithDateComponents:dateComponents];
return dateTime;
}
- (void)queryTodaysEvents {
GTLRCalendar_CalendarListEntry *selectedCalendar = [self selectedCalendarListEntry];
if (selectedCalendar) {
NSString *calendarID = selectedCalendar.identifier;
GTLRDateTime *startOfDay = [self dateTimeForTodayAtHour:0 minute:0 second:0];
GTLRDateTime *endOfDay = [self dateTimeForTodayAtHour:23 minute:59 second:59];
GTLRCalendarQuery_EventsList *query =
[GTLRCalendarQuery_EventsList queryWithCalendarId:calendarID];
query.maxResults = 10;
query.timeMin = startOfDay;
query.timeMax = endOfDay;
// The service is set to fetch all pages, but for querying today's events,
// we only want the first 10 results
query.executionParameters.shouldFetchNextPages = @NO;
GTLRCalendarService *service = self.calendarService;
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket, GTLRCalendar_Acl *events,
NSError *callbackError) {
// Callback
if (callbackError == nil) {
// Make a comma-separated list of event titles
NSArray *titles = [events.items valueForKey:@"summary"];
NSString *joined = [titles componentsJoinedByString:@", "];
[self displayAlert:@"Today's Events"
format:@"Query result: %@", joined];
} else {
[self displayAlert:@"Query failed"
format:@"%@", callbackError];
}
[self updateUI];
}];
}
}
- (void)queryFreeBusy {
GTLRCalendar_CalendarListEntry *selectedCalendar = [self selectedCalendarListEntry];
if (selectedCalendar) {
NSString *calendarID = selectedCalendar.identifier;
GTLRDateTime *startOfDay = [self dateTimeForTodayAtHour:0 minute:0 second:0];
GTLRDateTime *endOfDay = [self dateTimeForTodayAtHour:23 minute:59 second:59];
GTLRCalendar_FreeBusyRequestItem *requestItem = [GTLRCalendar_FreeBusyRequestItem object];
requestItem.identifier = calendarID;
GTLRCalendar_FreeBusyRequest *freeBusyRequest = [GTLRCalendar_FreeBusyRequest object];
freeBusyRequest.items = @[ requestItem ];
freeBusyRequest.timeMin = startOfDay;
freeBusyRequest.timeMax = endOfDay;
GTLRCalendarQuery_FreebusyQuery *query =
[GTLRCalendarQuery_FreebusyQuery queryWithObject:freeBusyRequest];
// The service is set to fetch all pages, but for querying today's busy
// periods, we only want the first 10 results
query.executionParameters.shouldFetchNextPages = @NO;
GTLRCalendarService *service = self.calendarService;
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRCalendar_FreeBusyResponse *response,
NSError *callbackError) {
// Callback
if (callbackError == nil) {
// Display a list of busy periods for the calendar account
NSMutableString *displayStr = [NSMutableString string];
GTLRCalendar_FreeBusyResponse_Calendars *responseCals = response.calendars;
NSDictionary *props = responseCals.additionalProperties;
// Step through the free-busy calendar IDs, and display each calendar
// name (the summary field) and free/busy times
for (NSString *calendarID in props) {
GTLRCalendar_CalendarListEntry *calendar;
calendar = [GTLRUtilities firstObjectFromArray:self.calendarList.items
withValue:calendarID
forKeyPath:@"identifier"];
[displayStr appendFormat:@"%@: ", calendar.summary];
GTLRCalendar_FreeBusyCalendar *calResponse = [props objectForKey:calendarID];
NSArray *busyArray = calResponse.busy;
for (GTLRCalendar_TimePeriod *period in busyArray) {
GTLRDateTime *startTime = period.start;
GTLRDateTime *endTime = period.end;
NSString *startStr = [NSDateFormatter localizedStringFromDate:startTime.date
dateStyle:NSDateFormatterNoStyle
timeStyle:NSDateFormatterShortStyle];
NSString *endStr = [NSDateFormatter localizedStringFromDate:endTime.date
dateStyle:NSDateFormatterNoStyle
timeStyle:NSDateFormatterShortStyle];
[displayStr appendFormat:@"(%@-%@) ", startStr, endStr];
}
}
[self displayAlert:@"Today's Busy Periods"
format:@"%@", displayStr];
} else {
[self displayAlert:@"Query failed"
format:@"%@", callbackError];
}
[self updateUI];
}];
}
}
#pragma mark Add, Edit, and Delete an ACL Rule
- (void)addAnACLRule {
// Make a new ACL rule
GTLRCalendar_AclRule_Scope *scope = [GTLRCalendar_AclRule_Scope object];
scope.type = @"user";
scope.value = @"mark.twain@example.com";
GTLRCalendar_AclRule *newRule = [GTLRCalendar_AclRule object];
newRule.role = @"reader";
newRule.scope = scope;
// Display the ACL edit dialog
EditACLWindowController *controller = [[EditACLWindowController alloc] init];
[controller runModalForWindow:self.window
ACLRule:newRule
completionHandler:^(NSInteger returnCode, GTLRCalendar_AclRule *rule) {
// Callback
if (returnCode == NSModalResponseOK) {
[self addACLRule:rule];
}
}];
}
- (void)addACLRule:(GTLRCalendar_AclRule *)aclRule {
GTLRCalendarService *service = self.calendarService;
GTLRCalendar_CalendarListEntry *selectedCalendar = [self selectedCalendarListEntry];
NSString *calendarID = selectedCalendar.identifier;
GTLRCalendarQuery_AclInsert *query = [GTLRCalendarQuery_AclInsert queryWithObject:aclRule
calendarId:calendarID];
self.editEventTicket = [service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRCalendar_AclRule *rule,
NSError *callbackError) {
// Callback
self.editEventTicket = nil;
if (callbackError == nil) {
[self displayAlert:@"Added"
format:@"Added ACL rule: %@",
[self displayStringForACLRule:rule]];
[self fetchSelectedCalendar];
} else {
[self displayAlert:@"Add failed"
format:@"ACL rule add failed: %@", callbackError];
}
[self updateUI];
}];
[self updateUI];
}
- (void)editSelectedACLRule {
// Show the selected rule to the user to edit
GTLRCalendar_AclRule *ruleToEdit = [self selectedACLRule];
if (ruleToEdit) {
EditACLWindowController *controller = [[EditACLWindowController alloc] init];
[controller runModalForWindow:self.window
ACLRule:ruleToEdit
completionHandler:^(NSInteger returnCode, GTLRCalendar_AclRule *rule) {
// Callback
if (returnCode == NSModalResponseOK) {
[self editSelectedACLRuleWithRule:rule];
}
}];
}
}
- (void)editSelectedACLRuleWithRule:(GTLRCalendar_AclRule *)revisedRule {
GTLRCalendarService *service = self.calendarService;
GTLRCalendar_CalendarListEntry *selectedCalendarListEntry = [self selectedCalendarListEntry];
// We create an object reflecting just the changes from the original rule
// needing to be patched
GTLRCalendar_AclRule *originalRule = [self selectedACLRule];
GTLRCalendar_AclRule *patchRule = [revisedRule patchObjectFromOriginal:originalRule];
if (patchRule) {
// If patchRule is non-nil, there are some fields to be changed
NSString *calendarID = selectedCalendarListEntry.identifier;
NSString *ruleID = originalRule.identifier;
GTLRCalendarQuery_AclPatch *query = [GTLRCalendarQuery_AclPatch queryWithObject:patchRule
calendarId:calendarID
ruleId:ruleID];
self.editEventTicket = [service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRCalendar_AclRule *rule,
NSError *callbackError) {
// Callback
self.editEventTicket = nil;
if (callbackError == nil) {
[self displayAlert:@"Rule Updated"
format:@"Patched rule \"%@\"",
[self displayStringForACLRule:rule]];
[self fetchSelectedCalendar];
} else {
[self displayAlert:@"Update Failed"
format:@"Rule patch failed: %@", callbackError];
}
[self updateUI];
}];
[self updateUI];
}
}
- (void)deleteSelectedACLRule {
GTLRCalendarService *service = self.calendarService;
GTLRCalendar_CalendarListEntry *selectedCalendarListEntry = [self selectedCalendarListEntry];
NSString *calendarID = selectedCalendarListEntry.identifier;
GTLRCalendar_AclRule *selectedACLRule = [self selectedACLRule];
NSString *ruleID = selectedACLRule.identifier;
if (calendarID && ruleID) {
GTLRCalendarQuery_AclDelete *query = [GTLRCalendarQuery_AclDelete queryWithCalendarId:calendarID
ruleId:ruleID];
self.editEventTicket = [service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
id nilObject, NSError *callbackError) {
// Callback
self.editEventTicket = nil;
if (callbackError == nil) {
[self displayAlert:@"ACL Rule Deleted"
format:@"Deleted \"%@\"",
[self displayStringForACLRule:selectedACLRule]];
[self fetchSelectedCalendar];
} else {
[self displayAlert:@"Delete Failed"
format:@"Rule delete failed: %@", callbackError];
}
[self updateUI];
}];
[self updateUI];
}
}
#pragma mark Sign In
- (void)runSigninThenInvokeSelector:(SEL)signInDoneSel {
// Applications should have client ID hardcoded into the source
// but the sample application asks the developer for the strings.
// Client secret is now left blank.
NSString *clientID = _clientIDField.stringValue;
NSString *clientSecret = _clientSecretField.stringValue;
if (clientID.length == 0) {
// Remind the developer that client ID is needed. Client Secret is now left blank
[_clientIDButton performSelector:@selector(performClick:)
withObject:self
afterDelay:0.5];
return;
}
NSURL *successURL = [NSURL URLWithString:kSuccessURLString];
// Starts a loopback HTTP listener to receive the code, gets the redirect URI to be used.
_redirectHTTPHandler = [[OIDRedirectHTTPHandler alloc] initWithSuccessURL:successURL];
NSError *error;
NSURL *localRedirectURI = [_redirectHTTPHandler startHTTPListener:&error];
if (!localRedirectURI) {
NSLog(@"Unexpected error starting redirect handler %@", error);
return;
}
// Builds authentication request.
OIDServiceConfiguration *configuration =
[GTMAppAuthFetcherAuthorization configurationForGoogle];
NSArray<NSString *> *scopes = @[ kGTLRAuthScopeCalendar, OIDScopeEmail ];
OIDAuthorizationRequest *request =
[[OIDAuthorizationRequest alloc] initWithConfiguration:configuration
clientId:clientID
clientSecret:clientSecret
scopes:scopes
redirectURL:localRedirectURI
responseType:OIDResponseTypeCode
additionalParameters:nil];
// performs authentication request
// Using the weakSelf pattern to avoid retaining self as block execution is indeterminate.
__weak __typeof(self) weakSelf = self;
_redirectHTTPHandler.currentAuthorizationFlow =
[OIDAuthState authStateByPresentingAuthorizationRequest:request
callback:^(OIDAuthState *_Nullable authState,
NSError *_Nullable error) {
// Brings this app to the foreground.
[[NSRunningApplication currentApplication]
activateWithOptions:(NSApplicationActivateAllWindows |
NSApplicationActivateIgnoringOtherApps)];
if (authState) {
// Creates a GTMAppAuthFetcherAuthorization object for authorizing requests.
GTMAppAuthFetcherAuthorization *gtmAuthorization =
[[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:authState];
// Sets the authorizer on the GTLRYouTubeService object so API calls will be authenticated.
weakSelf.calendarService.authorizer = gtmAuthorization;
// Serializes authorization to keychain in GTMAppAuth format.
[GTMAppAuthFetcherAuthorization saveAuthorization:gtmAuthorization
toKeychainForName:kGTMAppAuthKeychainItemName];
// Callback
if (signInDoneSel) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[weakSelf performSelector:signInDoneSel];
#pragma clang diagnostic pop
}
} else {
weakSelf.calendarListFetchError = error;
[weakSelf updateUI];
}
}];
}
#pragma mark UI
- (NSString *)displayStringForACLRule:(GTLRCalendar_AclRule *)rule {
// Make a concise, readable string showing the scope type, scope value,
// and role value for an ACL entry, like:
//
// scope: user "fred@flintstone.com" role:owner
NSMutableString *resultStr = [NSMutableString string];
GTLRCalendar_AclRule_Scope *scope = rule.scope;
if (scope) {
NSString *type = (scope.type ? scope.type : @"");
NSString *value = @"";
if (scope.value) {
value = [NSString stringWithFormat:@"\"%@\"", scope.value];
}
[resultStr appendFormat:@"scope: %@ %@ ", type, value];
}
NSString *role = rule.role;
if (role) {
[resultStr appendFormat:@"role: %@", role];
}
return resultStr;
}
- (void)updateUI {
BOOL isSignedIn = [self isSignedIn];
NSString *username = [self signedInUsername];
_signedInButton.title = (isSignedIn ? @"Sign Out" : @"Sign In");
_signedInField.stringValue = (isSignedIn ? username : @"No");
//
// CalendarList table
//
[_calendarTable reloadData];
if (self.calendarListTicket != nil || self.editCalendarListTicket != nil) {
[_calendarProgressIndicator startAnimation:self];
} else {
[_calendarProgressIndicator stopAnimation:self];
}
// Get the description of the selected item, or the feed fetch error
NSString *resultStr = @"";
if (self.calendarListFetchError) {
// Display the error
resultStr = [self.calendarListFetchError description];
// Also display any server data present
NSData *errData =
[[self.calendarListFetchError userInfo] objectForKey:kGTMSessionFetcherStatusDataKey];
if (errData) {
NSString *dataStr = [[NSString alloc] initWithData:errData
encoding:NSUTF8StringEncoding];
resultStr = [resultStr stringByAppendingFormat:@"\n%@", dataStr];
}
} else {
// Display the selected item
GTLRCalendar_CalendarListEntry *item = [self selectedCalendarListEntry];
if (item) {
resultStr = item.description;
}
}
_calendarResultTextField.string = resultStr;
//
// Events list
//
[_eventTable reloadData];
if (self.eventsTicket != nil || self.editEventTicket != nil) {
[_eventProgressIndicator startAnimation:self];
} else {
[_eventProgressIndicator stopAnimation:self];
}
// Get the description of the selected item, or the feed fetch error
resultStr = @"";
switch (_entrySegmentedControl.selectedSegment) {
case kEventsSegment:
if (self.eventsFetchError) {
resultStr = [self.eventsFetchError description];
} else {
GTLRCalendar_Event *item = [self selectedEvent];
if (item) {
resultStr = item.description;
}
}
break;
case kAccessControlSegment:
if (self.ACLsFetchError) {
resultStr = [self.ACLsFetchError description];
} else {
GTLRCalendar_AclRule *item = [self selectedACLRule];
if (item) {
resultStr = item.description;
}
}
break;
case kSettingsSegment:
if (self.settingsFetchError) {
resultStr = [self.settingsFetchError description];
} else {
GTLRCalendar_Setting *item = [self selectedSetting];
if (item) {
resultStr = item.description;
}
}
break;
default: break;
}
_eventResultTextField.string = resultStr;
// Enable buttons
BOOL isFetchingCalendars = (self.calendarListTicket != nil);
BOOL isEditingCalendar = (self.editCalendarListTicket != nil);
_calendarCancelButton.enabled = (isFetchingCalendars || isEditingCalendar);
BOOL isFetchingEvents = (self.eventsTicket != nil);
BOOL isEditingEvent = (self.editEventTicket != nil);
_eventCancelButton.enabled = (isFetchingEvents || isEditingEvent);
BOOL isCalendarSelected = ([self selectedCalendarListEntry] != nil);
BOOL hasNewName = (_calendarNameField.stringValue.length > 0);
_addCalendarButton.enabled = (isSignedIn && hasNewName);
_renameCalendarButton.enabled = (isSignedIn && isCalendarSelected && hasNewName);
_deleteCalendarButton.enabled = (isSignedIn && isCalendarSelected);
NSInteger segment = _entrySegmentedControl.selectedSegment;
BOOL isEventsSegmentSelected = (segment == kEventsSegment);
BOOL isACLsSegmentSelected = (segment == kAccessControlSegment);
if (isEventsSegmentSelected) {
// Events
BOOL isEventSelected = ([self selectedEvent] != nil);
_addEntryButton.enabled = isCalendarSelected;
_editEntryButton.enabled = isEventSelected;
_deleteEntriesButton.enabled = isEventSelected;
} else if (isACLsSegmentSelected) {
// ACLs
BOOL isACLSelected = ([self selectedACLRule] != nil);
_addEntryButton.enabled = isCalendarSelected;
_editEntryButton.enabled = isACLSelected;
_deleteEntriesButton.enabled = isACLSelected;
} else {
// Settings
_addEntryButton.enabled = NO;
_editEntryButton.enabled = NO;
_deleteEntriesButton.enabled = NO;
}
_queryTodaysEventsButton.enabled = isCalendarSelected;
_queryFreeBusyButton.enabled = isCalendarSelected;
// Show or hide the text indicating that the client ID or client secret are
// needed
BOOL hasClientIDStrings = _clientIDField.stringValue.length > 0
&& _clientSecretField.stringValue.length > 0;
_clientIDRequiredTextField.hidden = hasClientIDStrings;
}
- (void)displayAlert:(NSString *)title format:(NSString *)format, ... {
NSString *result = format;
if (format) {
va_list argList;
va_start(argList, format);
result = [[NSString alloc] initWithFormat:format
arguments:argList];
va_end(argList);
}
NSAlert *alert = [[NSAlert alloc] init];
alert.messageText = title;
alert.informativeText = result;
[alert beginSheetModalForWindow:self.window
completionHandler:nil];
}
#pragma mark Client ID Sheet
// Client ID and Client Secret Sheet
//
// Sample apps need this sheet to ask for the client ID and client secret
// strings
//
// Your application will just hardcode the client ID and client secret strings
// into the source rather than ask the user for them.
//
// The string values are obtained from the API Console,
// https://console.developers.google.com/
- (IBAction)clientIDClicked:(id)sender {
// Show the sheet for developers to enter their client ID and client secret
[self.window beginSheet:_clientIDSheet completionHandler:nil];
}
- (IBAction)clientIDDoneClicked:(id)sender {
[self.window endSheet:[sender window]];
}
#pragma mark Text field delegate methods
- (void)controlTextDidChange:(NSNotification *)note {
[self updateUI]; // enable and disable buttons
}
#pragma mark TableView delegate and data source methods
- (void)tableViewSelectionDidChange:(NSNotification *)notification {
if ([notification object] == _calendarTable) {
// The calendar list selection changed
[self fetchSelectedCalendar];
} else {
// The event list selection changed
[self updateUI];
}
}
// Table view data source methods
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
if (tableView == _calendarTable) {
return self.calendarList.items.count;
} else {
switch (_entrySegmentedControl.selectedSegment) {
case kEventsSegment: return self.events.items.count;
case kAccessControlSegment: return self.ACLs.items.count;
case kSettingsSegment: return self.settings.items.count;
default: return 0;
}
}
}
- (id)tableView:(NSTableView *)tableView
objectValueForTableColumn:(NSTableColumn *)tableColumn
row:(int)row {
if (tableView == _calendarTable) {
// Calendar table
GTLRCalendar_Calendar *calendar = self.calendarList[row];
NSString *str = calendar.summary;
return str;
} else {
// Events/ACLs/Settings table
switch (_entrySegmentedControl.selectedSegment) {
case kEventsSegment: {
GTLRCalendar_Event *event = self.events[row];
NSString *str = event.summary;
return str;
}
case kAccessControlSegment: {
GTLRCalendar_AclRule *rule = self.ACLs[row];
NSString *str = [self displayStringForACLRule:rule];
return str;
}
case kSettingsSegment: {
GTLRCalendar_Setting *setting = self.settings[row];
NSString *str = [NSString stringWithFormat:@"%@: %@", setting.identifier, setting.value];
return str;
}
default:
return nil;
}
}
}
@end