Propagate 5xx server error to user + reload button. Closes #5

This commit is contained in:
relikd
2019-07-06 13:27:00 +02:00
parent 29a48384c7
commit 8dc95dda63
10 changed files with 88 additions and 34 deletions

View File

@@ -146,6 +146,7 @@
self.httpEtag = nil;
self.httpDate = nil;
self.faviconURL = nil;
self.previousURL = self.view.url.stringValue;
}
/**
@@ -179,8 +180,11 @@
@return Either URL string or @c nil if user canceled the selection.
*/
- (NSString*)letUserChooseXmlUrlFromList:(NSArray<RSHTMLMetadataFeedLink*> *)list {
if (list.count == 1) // nothing to choose
if (list.count == 1) { // nothing to choose
// Feeds like https://news.ycombinator.com/ return 503 if URLs are requested too rapidly
//CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, false); // Non-blocking sleep (1s)
return list.firstObject.link;
}
NSMenu *menu = [[NSMenu alloc] initWithTitle:NSLocalizedString(@"Choose feed menu", nil)];
menu.autoenablesItems = NO;
for (RSHTMLMetadataFeedLink *fl in list) {
@@ -203,11 +207,19 @@
- (void)postDownload:(NSString*)responseURL {
if (self.modalSheet.didCloseAndCancel)
return;
BOOL hasError = (self.feedError != nil);
// 1. Stop spinner animation for name field. (keep spinner for URL running until favicon downloaded)
[self.view.spinnerName stopAnimation:nil];
// 2. If URL was redirected, replace original text field value with new one. (e.g., https redirect)
if (responseURL.length > 0 && ![responseURL isEqualToString:self.previousURL]) {
self.previousURL = responseURL;
if (!hasError) {
// If the url has changed and there is an error:
// This probably means the feed URL was resolved, but the successive download returned 5xx error.
// Presumably to prevent site crawlers accessing many pages in quick succession. (delay of 1s does help)
// By not setting previousURL, a second hit on the 'Done' button will retry the resolved URL again.
self.previousURL = responseURL;
}
self.view.url.stringValue = responseURL;
}
// 3. Copy parsed feed title to text field. (only if user hasn't set anything else yet)
@@ -218,7 +230,6 @@
// TODO: user preference to automatically select refresh interval (selection: None,min,max,avg,median)
[self statsForDownloadObject];
// 4. Continue with favicon download (or finish with error)
BOOL hasError = (self.feedError != nil);
self.view.favicon.hidden = hasError;
self.view.warningButton.hidden = !hasError;
if (hasError) {
@@ -305,7 +316,6 @@
- (void)controlTextDidEndEditing:(NSNotification *)obj {
if (obj.object == self.view.url) {
if (![self.previousURL isEqualToString:self.view.url.stringValue]) {
self.previousURL = self.view.url.stringValue;
[self downloadRSS];
}
}
@@ -316,15 +326,26 @@
if (!self.feedError)
return;
// show reload button if server is temporarily offline (any 5xx server error)
BOOL serverError = (self.feedError.domain == NSURLErrorDomain && self.feedError.code == NSURLErrorBadServerResponse);
self.view.warningReload.hidden = !serverError;
// set error description as text
self.view.warningText.objectValue = self.feedError.localizedDescription;
NSSize newSize = self.view.warningText.fittingSize; // width is limited by the textfield's preferred width
newSize.width += 2 * self.view.warningText.frame.origin.x; // the padding
newSize.height += 2 * self.view.warningText.frame.origin.y;
// apply fitting size and display
self.view.warningPopover.contentSize = newSize;
[self.view.warningPopover showRelativeToRect:sender.bounds ofView:sender preferredEdge:NSRectEdgeMinY];
}
/// Either hit by Cmd+R or reload button inside warning popover error description
- (void)reloadData {
[self downloadRSS];
}
@end

View File

@@ -38,6 +38,7 @@
@property (weak) IBOutlet NSButton *warningButton;
@property NSPopover *warningPopover;
@property (weak) IBOutlet NSTextField *warningText;
@property (weak) IBOutlet NSButton *warningReload;
- (instancetype)initWithController:(ModalFeedEdit*)controller NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithFrame:(NSRect)frameRect NS_UNAVAILABLE;

View File

@@ -47,10 +47,7 @@
self.url = [[[NSView inputField:@"https://example.org/feed.rss" width:0] placeIn:self x:x yTop:0] sizeToRight:PAD_S + 18];
self.spinnerURL = [[NSView activitySpinner] placeIn:self xRight:1 yTop:2.5];
self.favicon = [[[NSView imageView:nil size:18] tooltip:NSLocalizedString(@"Favicon", nil)] placeIn:self xRight:0 yTop:1.5];
NSTextField *errorDesc = [self warningPopoverContentView];
self.warningPopover = [self warningPopoverControllerWith:errorDesc];
self.warningText = errorDesc; // after added to parent view, otherwise will be released immediatelly (weak ivar)
self.warningButton = [[[[NSView buttonIcon:[NSImage imageNamed:NSImageNameCaution] size:18] action:@selector(didClickWarningButton:) target:nil] // up the responder chain
self.warningButton = [[[[NSView buttonIcon:NSImageNameCaution size:18] action:@selector(didClickWarningButton:) target:nil] // up the responder chain
tooltip:NSLocalizedString(@"Click here to show failure reason", nil)]
placeIn:self xRight:0 yTop:1.5];
// 2. row
@@ -61,34 +58,39 @@
self.refreshUnit = [[NSView popupButton:120] placeIn:self x:NSMaxX(self.refreshNum.frame) + PAD_M yTop:2*rowHeight];
// initial state
self.url.accessibilityLabel = lbls[0];
self.name.accessibilityLabel = lbls[1];
self.refreshNum.accessibilityLabel = NSLocalizedString(@"Refresh interval", nil);
self.url.delegate = controller;
self.warningButton.hidden = YES;
self.refreshNum.formatter = [StrictUIntFormatter new]; // see below ...
//[self.warningButton.cell setHighlightsBy:(error ? NSContentsCellMask : NSNoCellMask)];
[self prepareWarningPopover];
return self;
}
/// User visible error description text (after click on warning button)
- (NSTextField*)warningPopoverContentView {
NSTextField *txt = [[[NSView label:@""] selectable] sizableWidthAndHeight];
[txt setFrameSize: NSMakeSize(300, 100)];
txt.lineBreakMode = NSLineBreakByWordWrapping;
txt.maximumNumberOfLines = 7;
return txt;
}
/// Prepare popover controller to display download errors
- (NSPopover*)warningPopoverControllerWith:(NSTextField*)content {
/// Prepare popover controller to display errors during download
- (void)prepareWarningPopover {
NSPopover *pop = [[NSPopover alloc] init];
pop.behavior = NSPopoverBehaviorTransient;
pop.contentViewController = [[NSViewController alloc] init];
pop.contentViewController.view = [[NSView alloc] initWithFrame:content.frame];
[pop.contentViewController.view addSubview:content];
NSView *content = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 300, 100)];
pop.contentViewController.view = content;
content.frame = NSInsetRect(content.frame, 4, 2);
content.preferredMaxLayoutWidth = NSWidth(content.frame);
return pop;
// User visible error description text (after click on warning button)
NSTextField *txt = [[[NSView label:@""] selectable] sizableWidthAndHeight];
txt.frame = NSInsetRect(content.frame, 4, 2);
txt.preferredMaxLayoutWidth = NSWidth(txt.frame);
txt.lineBreakMode = NSLineBreakByWordWrapping;
txt.maximumNumberOfLines = 7;
[content addSubview:txt];
self.warningPopover = pop;
self.warningText = txt;
// Reload button is only visible on 5xx server error (right of )
self.warningReload = [[[[NSView buttonIcon:NSImageNameRefreshTemplate size:16] placeIn:content x:35 yTop:21]
tooltip:NSLocalizedString(@"Retry download (Cmd+R)", nil)]
action:@selector(reloadData) target:nil]; // up the responder chain
}
@end

View File

@@ -134,7 +134,7 @@
*/
- (CGFloat)generateButtons {
NSButton *add = [[NSView buttonImageSquare:NSImageNameAddTemplate] tooltip:NSLocalizedString(@"Add new item", nil)];
NSButton *del = [[NSView buttonImageSquare:NSImageNameRemoveTemplate] tooltip:NSLocalizedString(@"Delete selected item(s)", nil)];
NSButton *del = [[NSView buttonImageSquare:NSImageNameRemoveTemplate] tooltip:NSLocalizedString(@"Delete selected items", nil)];
NSButton *share = [[NSView buttonImageSquare:NSImageNameShareTemplate] tooltip:NSLocalizedString(@"Import or export data", nil)];
[self button:add copyActions:3 to:5];