Propagate 5xx server error to user + reload button. Closes #5
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
|
||||
Reference in New Issue
Block a user