From 87cf59ce10ef404eb5431ab577f44f8ef06a1677 Mon Sep 17 00:00:00 2001 From: Paul Schuberth Date: Sun, 8 Oct 2023 21:29:57 +0200 Subject: [PATCH] feat: Show different error for `401` responses This aims at providing a slightly more precise error in case the selected map type requires an API key and SatelliteEyes has not been provided with a valid key. --- SatelliteEyes/TTMapImage.h | 2 +- SatelliteEyes/TTMapImage.m | 6 +- SatelliteEyes/TTMapManager.h | 1 + SatelliteEyes/TTMapManager.m | 10 +- SatelliteEyes/TTStatusItemController.h | 1 + SatelliteEyes/TTStatusItemController.m | 142 +++++++++++++++---------- 6 files changed, 98 insertions(+), 64 deletions(-) diff --git a/SatelliteEyes/TTMapImage.h b/SatelliteEyes/TTMapImage.h index f097a5b..d30ced7 100644 --- a/SatelliteEyes/TTMapImage.h +++ b/SatelliteEyes/TTMapImage.h @@ -30,7 +30,7 @@ logo:(NSImage *)_logoImage NS_DESIGNATED_INITIALIZER; - (void)fetchTilesWithSuccess:(void (^)(NSURL *filePath))success - failure:(void (^)(NSError *error))failure + failure:(void (^)(NSError *error, NSInteger statusCode))failure skipCache:(BOOL)skipCache; @property (NS_NONATOMIC_IOSONLY, readonly, copy) NSURL *fileURL; diff --git a/SatelliteEyes/TTMapImage.m b/SatelliteEyes/TTMapImage.m index fe76fed..5320a11 100644 --- a/SatelliteEyes/TTMapImage.m +++ b/SatelliteEyes/TTMapImage.m @@ -87,7 +87,7 @@ - (NSArray *)tilesArray { } - (void)fetchTilesWithSuccess:(void (^)(NSURL *filePath))success - failure:(void (^)(NSError *error))failure + failure:(void (^)(NSError *error, NSInteger statusCode))failure skipCache:(BOOL)skipCache { dispatch_async(dispatch_get_global_queue(0, 0), ^(void) { @@ -107,6 +107,7 @@ - (void)fetchTilesWithSuccess:(void (^)(NSURL *filePath))success DDLogInfo(@"Not found, or skipping cache, so fetching file at: %@", [fileURL path]); __block NSError *error; + __block NSInteger statusCode; [self->tiles enumerateObjectsUsingBlock:^(NSArray *rowArray, NSUInteger idx, BOOL *stop) { [rowArray enumerateObjectsUsingBlock:^(TTMapTile *mapTile, NSUInteger rowIndex, BOOL *rowStop) { AFHTTPRequestOperation *httpOperation = [[AFHTTPRequestOperation alloc] initWithRequest:[mapTile urlRequest]]; @@ -115,6 +116,7 @@ - (void)fetchTilesWithSuccess:(void (^)(NSURL *filePath))success mapTile.imageData = responseData; } failure:^(AFHTTPRequestOperation *operation, NSError *_error) { error = _error; + statusCode = operation.response.statusCode; DDLogError(@"Fetching tile error: %@", error); }]; [self->tileQueue addOperation:httpOperation]; @@ -123,7 +125,7 @@ - (void)fetchTilesWithSuccess:(void (^)(NSURL *filePath))success [self->tileQueue waitUntilAllOperationsAreFinished]; if (error) { - failure(error); + failure(error, statusCode); } else { NSURL *fileURL = [self writeImageData]; success(fileURL); diff --git a/SatelliteEyes/TTMapManager.h b/SatelliteEyes/TTMapManager.h index eccb89c..7dde49a 100644 --- a/SatelliteEyes/TTMapManager.h +++ b/SatelliteEyes/TTMapManager.h @@ -14,6 +14,7 @@ static NSString *const TTMapManagerStartedLoad = @"TTMapManagerStartedLoad"; static NSString *const TTMapManagerFailedLoad = @"TTMapManagerFailedLoad"; +static NSString *const TTMapManagerFailedUnauthorized = @"TTMapManagerFailedUnauthorized"; static NSString *const TTMapManagerFinishedLoad = @"TTMapManagerFinishedLoad"; static NSString *const TTMapManagerLocationUpdated = @"TTMapManagerLocationUpdated"; static NSString *const TTMapManagerLocationLost = @"TTMapManagerLocationLost"; diff --git a/SatelliteEyes/TTMapManager.m b/SatelliteEyes/TTMapManager.m index 1337059..b2a99a2 100644 --- a/SatelliteEyes/TTMapManager.m +++ b/SatelliteEyes/TTMapManager.m @@ -190,8 +190,14 @@ - (void)updateMapToCoordinate:(CLLocationCoordinate2D)coordinate force:(BOOL)for } - } failure:^(NSError *error) { - [[NSNotificationCenter defaultCenter] postNotificationName:TTMapManagerFailedLoad object:nil]; + } failure:^(NSError *error, NSInteger statusCode) { + // Stadia maps will return 401 in case of invalid or missing API key. + // https://docs.stadiamaps.com/authentication/#authentication + if (statusCode == 401) { + [[NSNotificationCenter defaultCenter] postNotificationName:TTMapManagerFailedUnauthorized object:nil]; + } else { + [[NSNotificationCenter defaultCenter] postNotificationName:TTMapManagerFailedLoad object:nil]; + } DDLogError(@"Error fetching image: %@", error); } skipCache:force]; }); diff --git a/SatelliteEyes/TTStatusItemController.h b/SatelliteEyes/TTStatusItemController.h index 67a058c..b6481a5 100644 --- a/SatelliteEyes/TTStatusItemController.h +++ b/SatelliteEyes/TTStatusItemController.h @@ -18,6 +18,7 @@ BOOL mapManagerhasLocation; BOOL mapManagerisActive; BOOL mapManagerdidError; + BOOL mapManagerUnauthorized; NSDate *mapLastUpdated; NSUInteger activityAnimationFrameIndex; NSTimer *activityAnimationTimer; diff --git a/SatelliteEyes/TTStatusItemController.m b/SatelliteEyes/TTStatusItemController.m index 8f7616c..4a6d0de 100644 --- a/SatelliteEyes/TTStatusItemController.m +++ b/SatelliteEyes/TTStatusItemController.m @@ -21,83 +21,96 @@ - (instancetype)init NSMenu *menu = [[NSMenu alloc] initWithTitle:@"Menu"]; menu.delegate = self; [menu setAutoenablesItems:NO]; - + statusMenuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; [statusMenuItem setEnabled:NO]; [menu addItem:statusMenuItem]; - + forceMapUpdateMenuItem = [[NSMenuItem alloc] initWithTitle:@"Refresh the map now" action:@selector(forceMapUpdate:) keyEquivalent:@""]; [forceMapUpdateMenuItem setEnabled:NO]; [menu addItem:forceMapUpdateMenuItem]; - + openInBrowserMenuItem = [[NSMenuItem alloc] initWithTitle:@"Open in browser" action:@selector(openMapInBrowser:) keyEquivalent:@""]; [openInBrowserMenuItem setEnabled:NO]; [menu addItem:openInBrowserMenuItem]; - + [menu addItem:[NSMenuItem separatorItem]]; - + NSMenuItem *aboutMenuItem = [[NSMenuItem alloc] initWithTitle:@"About" action:@selector(showAbout:) keyEquivalent:@""]; [menu addItem:aboutMenuItem]; - + NSMenuItem *preferencesMenuItem = [[NSMenuItem alloc] initWithTitle:@"Open preferences..." action:@selector(showPreferences:) keyEquivalent:@""]; [menu addItem:preferencesMenuItem]; - + NSMenuItem *updatesMenuItem = [[NSMenuItem alloc] initWithTitle:@"Check for updates..." action:@selector(checkForUpdates:) keyEquivalent:@""]; [menu addItem:updatesMenuItem]; - + NSMenuItem *itemExit = [[NSMenuItem alloc] initWithTitle:@"Exit" action:@selector(menuActionExit:) keyEquivalent:@""]; [menu addItem:itemExit]; - + statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:22]; [statusItem setHighlightMode:YES]; statusItem.menu = menu; - + [self updateStatus]; - - [[NSNotificationCenter defaultCenter] addObserverForName:TTMapManagerStartedLoad - object:nil - queue:nil + + [[NSNotificationCenter defaultCenter] addObserverForName:TTMapManagerStartedLoad + object:nil + queue:nil usingBlock:^(NSNotification *note) { - self->mapManagerdidError = NO; - self->mapManagerisActive = YES; - [self performSelectorOnMainThread:@selector(updateStatus) withObject:nil waitUntilDone:YES]; - }]; - - [[NSNotificationCenter defaultCenter] addObserverForName:TTMapManagerFinishedLoad - object:nil - queue:nil + self->mapManagerdidError = NO; + self->mapManagerUnauthorized = NO; + self->mapManagerisActive = YES; + [self performSelectorOnMainThread:@selector(updateStatus) withObject:nil waitUntilDone:YES]; + }]; + + [[NSNotificationCenter defaultCenter] addObserverForName:TTMapManagerFinishedLoad + object:nil + queue:nil + usingBlock:^(NSNotification *note) { + self->mapManagerdidError = NO; + self->mapManagerUnauthorized = NO; + self->mapManagerisActive = NO; + self->mapLastUpdated = [NSDate date]; + [self performSelectorOnMainThread:@selector(updateStatus) withObject:nil waitUntilDone:YES]; + + }]; + + [[NSNotificationCenter defaultCenter] addObserverForName:TTMapManagerFailedLoad + object:nil + queue:nil usingBlock:^(NSNotification *note) { - self->mapManagerdidError = NO; - self->mapManagerisActive = NO; - self->mapLastUpdated = [NSDate date]; - [self performSelectorOnMainThread:@selector(updateStatus) withObject:nil waitUntilDone:YES]; - - }]; - - [[NSNotificationCenter defaultCenter] addObserverForName:TTMapManagerFailedLoad - object:nil - queue:nil + self->mapManagerdidError = YES; + self->mapManagerUnauthorized = NO; + self->mapManagerisActive = NO; + [self performSelectorOnMainThread:@selector(updateStatus) withObject:nil waitUntilDone:YES]; + }]; + + [[NSNotificationCenter defaultCenter] addObserverForName:TTMapManagerFailedUnauthorized + object:nil + queue:nil usingBlock:^(NSNotification *note) { - self->mapManagerdidError = YES; - self->mapManagerisActive = NO; - [self performSelectorOnMainThread:@selector(updateStatus) withObject:nil waitUntilDone:YES]; - }]; - - [[NSNotificationCenter defaultCenter] addObserverForName:TTMapManagerLocationUpdated - object:nil - queue:nil + self->mapManagerdidError = YES; + self->mapManagerUnauthorized = YES; + self->mapManagerisActive = NO; + [self performSelectorOnMainThread:@selector(updateStatus) withObject:nil waitUntilDone:YES]; + }]; + + [[NSNotificationCenter defaultCenter] addObserverForName:TTMapManagerLocationUpdated + object:nil + queue:nil usingBlock:^(NSNotification *note) { - self->mapManagerhasLocation = YES; - [self performSelectorOnMainThread:@selector(updateStatus) withObject:nil waitUntilDone:YES]; - }]; - + self->mapManagerhasLocation = YES; + [self performSelectorOnMainThread:@selector(updateStatus) withObject:nil waitUntilDone:YES]; + }]; + [[NSNotificationCenter defaultCenter] addObserverForName:TTMapManagerLocationLost - object:nil - queue:nil + object:nil + queue:nil usingBlock:^(NSNotification *note) { - self->mapManagerhasLocation = NO; - [self performSelectorOnMainThread:@selector(updateStatus) withObject:nil waitUntilDone:YES]; - }]; + self->mapManagerhasLocation = NO; + [self performSelectorOnMainThread:@selector(updateStatus) withObject:nil waitUntilDone:YES]; + }]; } return self; } @@ -106,19 +119,23 @@ - (void)updateStatus { if (mapManagerhasLocation) { [forceMapUpdateMenuItem setEnabled:YES]; [self enableOpenInBrowser]; - + if (mapManagerisActive) { [self startActivityAnimation]; - + } else if (mapManagerdidError) { [self stopActivityAnimation]; - [self showError]; - + if (mapManagerUnauthorized) { + [self showUnauthorizedError]; + } else { + [self showGenericError]; + } + } else { // is idle [self stopActivityAnimation]; [self showNormal]; } - + } else { [self stopActivityAnimation]; [self showOffline]; @@ -126,7 +143,7 @@ - (void)updateStatus { [self disableOpenInBrowser]; } } - + - (void)showOffline { NSImage *image = [NSImage imageNamed:@"status-icon-offline"]; image.template = YES; @@ -140,10 +157,10 @@ - (void)showNormal { statusItem.image = image; [forceMapUpdateMenuItem setHidden:NO]; - + if (mapLastUpdated) { statusMenuItem.title = [NSString stringWithFormat:@"Map updated %@", [mapLastUpdated distanceOfTimeInWords].lowercaseString]; - + } else { statusMenuItem.title = @"Waiting for map update"; } @@ -185,17 +202,24 @@ - (void)stopActivityAnimation { activityAnimationTimer = nil; } -- (void)showError { +- (void)showGenericError { NSImage *image = [NSImage imageNamed:@"status-icon-error"]; image.template = YES; statusItem.image = image; statusMenuItem.title = @"Problem updating the map"; } +- (void)showUnauthorizedError { + NSImage *image = [NSImage imageNamed:@"status-icon-error"]; + image.template = YES; + statusItem.image = image; + statusMenuItem.title = @"Please provide a valid API key for the selected map type."; +} + - (void)enableOpenInBrowser { // It's a bit hacky to reach up into the App Delegate for this, but hey. TTAppDelegate *appDelegate = (TTAppDelegate *)[NSApplication sharedApplication].delegate; - + if ([appDelegate visibleMapBrowserURL]) { [openInBrowserMenuItem setEnabled:YES]; } else {