Refactoring feed download + favicon cache
This commit is contained in:
27
baRSS/Helper/NSError+Ext.h
Normal file
27
baRSS/Helper/NSError+Ext.h
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 Oleg Geier
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
// of the Software, and to permit persons to whom the Software is furnished to do
|
||||
// so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
@import Cocoa;
|
||||
|
||||
@interface NSError (Ext)
|
||||
+ (instancetype)statusCode:(NSInteger)code reason:(nullable NSString*)reason;
|
||||
@end
|
||||
113
baRSS/Helper/NSError+Ext.m
Normal file
113
baRSS/Helper/NSError+Ext.m
Normal file
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 Oleg Geier
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
// of the Software, and to permit persons to whom the Software is furnished to do
|
||||
// so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#import "NSError+Ext.h"
|
||||
|
||||
@implementation NSError (Ext)
|
||||
|
||||
static const char* CodeDescription(NSInteger code) {
|
||||
switch (code) {
|
||||
/* --- Informational --- */
|
||||
case 100: return "Continue";
|
||||
case 101: return "Switching Protocols";
|
||||
case 102: return "Processing";
|
||||
case 103: return "Early Hints";
|
||||
/* --- Success --- */
|
||||
case 200: return "OK";
|
||||
case 201: return "Created";
|
||||
case 202: return "Accepted";
|
||||
case 203: return "Non-Authoritative Information";
|
||||
case 204: return "No Content";
|
||||
case 205: return "Reset Content";
|
||||
case 206: return "Partial Content";
|
||||
case 207: return "Multi-Status";
|
||||
case 208: return "Already Reported";
|
||||
case 226: return "IM Used";
|
||||
/* --- Redirection --- */
|
||||
case 300: return "Multiple Choices";
|
||||
case 301: return "Moved Permanently";
|
||||
case 302: return "Found";
|
||||
case 303: return "See Other";
|
||||
case 304: return "Not Modified";
|
||||
case 305: return "Use Proxy";
|
||||
case 306: return "Switch Proxy";
|
||||
case 307: return "Temporary Redirect";
|
||||
case 308: return "Permanent Redirect";
|
||||
/* --- Client error --- */
|
||||
case 400: return "Bad Request";
|
||||
case 401: return "Unauthorized";
|
||||
case 402: return "Payment Required";
|
||||
case 403: return "Forbidden";
|
||||
case 404: return "Not Found";
|
||||
case 405: return "Method Not Allowed";
|
||||
case 406: return "Not Acceptable";
|
||||
case 407: return "Proxy Authentication Required";
|
||||
case 408: return "Request Timeout";
|
||||
case 409: return "Conflict";
|
||||
case 410: return "Gone";
|
||||
case 411: return "Length Required";
|
||||
case 412: return "Precondition Failed";
|
||||
case 413: return "Payload Too Large";
|
||||
case 414: return "URI Too Long";
|
||||
case 415: return "Unsupported Media Type";
|
||||
case 416: return "Range Not Satisfiable";
|
||||
case 417: return "Expectation Failed";
|
||||
case 418: return "I'm a teapot";
|
||||
case 421: return "Misdirected Request";
|
||||
case 422: return "Unprocessable Entity";
|
||||
case 423: return "Locked";
|
||||
case 424: return "Failed Dependency";
|
||||
case 425: return "Too Early";
|
||||
case 426: return "Upgrade Required";
|
||||
case 428: return "Precondition Required";
|
||||
case 429: return "Too Many Requests";
|
||||
case 431: return "Request Header Fields Too Large";
|
||||
case 451: return "Unavailable For Legal Reasons";
|
||||
/* --- Server error --- */
|
||||
case 500: return "Internal Server Error";
|
||||
case 501: return "Not Implemented";
|
||||
case 502: return "Bad Gateway";
|
||||
case 503: return "Service Unavailable";
|
||||
case 504: return "Gateway Timeout";
|
||||
case 505: return "HTTP Version Not Supported";
|
||||
case 506: return "Variant Also Negotiates";
|
||||
case 507: return "Insufficient Storage";
|
||||
case 508: return "Loop Detected";
|
||||
case 510: return "Not Extended";
|
||||
case 511: return "Network Authentication Required";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
/// Generate @c NSError from HTTP status code. E.g., @c code @c = @c 404 will return "404 Not Found".
|
||||
+ (instancetype)statusCode:(NSInteger)code reason:(nullable NSString*)reason {
|
||||
NSMutableDictionary *info = [NSMutableDictionary dictionaryWithCapacity:2];
|
||||
info[NSLocalizedDescriptionKey] = [NSString stringWithFormat:@"%ld %s.", code, CodeDescription(code)];
|
||||
if (reason) info[NSLocalizedRecoverySuggestionErrorKey] = reason;
|
||||
|
||||
NSInteger errCode = NSURLErrorUnknown;
|
||||
if (code < 500) { if (code >= 400) errCode = NSURLErrorResourceUnavailable; }
|
||||
else if (code < 600) errCode = NSURLErrorBadServerResponse;
|
||||
return [NSError errorWithDomain:NSURLErrorDomain code:errCode userInfo:info];
|
||||
}
|
||||
|
||||
@end
|
||||
31
baRSS/Helper/NSURL+Ext.h
Normal file
31
baRSS/Helper/NSURL+Ext.h
Normal file
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 Oleg Geier
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
// of the Software, and to permit persons to whom the Software is furnished to do
|
||||
// so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
@import Cocoa;
|
||||
|
||||
@interface NSURL (Ext)
|
||||
+ (NSURL*)faviconsCacheURL;
|
||||
- (BOOL)existsAndIsDir:(BOOL)dir;
|
||||
- (BOOL)mkdir;
|
||||
- (void)remove;
|
||||
- (void)moveTo:(NSURL*)destination;
|
||||
@end
|
||||
76
baRSS/Helper/NSURL+Ext.m
Normal file
76
baRSS/Helper/NSURL+Ext.m
Normal file
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 Oleg Geier
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
// of the Software, and to permit persons to whom the Software is furnished to do
|
||||
// so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#import "NSURL+Ext.h"
|
||||
#import "UserPrefs.h" // appName in +faviconsCacheURL
|
||||
|
||||
@implementation NSURL (Ext)
|
||||
|
||||
/// @return Directory URL pointing to "Application Support/baRSS/favicons". Does @b not create directory!
|
||||
+ (NSURL*)faviconsCacheURL {
|
||||
static NSURL *path = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
path = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:nil];
|
||||
path = [path URLByAppendingPathComponent:[UserPrefs appName] isDirectory:YES];
|
||||
path = [path URLByAppendingPathComponent:@"favicons" isDirectory:YES];
|
||||
});
|
||||
return path;
|
||||
}
|
||||
|
||||
/// @return @c YES if and only if item exists at URL and item matches @c dir flag
|
||||
- (BOOL)existsAndIsDir:(BOOL)dir {
|
||||
BOOL d;
|
||||
return self.path && [[NSFileManager defaultManager] fileExistsAtPath:self.path isDirectory:&d] && d == dir;
|
||||
}
|
||||
|
||||
/**
|
||||
Create directory at URL. If directory exists, this method does nothing.
|
||||
@return @c YES if dir created successfully. @c NO if dir already exists or an error occured.
|
||||
*/
|
||||
- (BOOL)mkdir {
|
||||
if ([self existsAndIsDir:YES]) return NO;
|
||||
NSError *err;
|
||||
BOOL b = [[NSFileManager defaultManager] createDirectoryAtURL:self withIntermediateDirectories:YES attributes:nil error:&err];
|
||||
if (err) [NSApp presentError:err];
|
||||
return b;
|
||||
}
|
||||
|
||||
/// Delete file or folder at URL. If item does not exist, this method does nothing.
|
||||
- (void)remove {
|
||||
BOOL success = [[NSFileManager defaultManager] removeItemAtURL:self error:nil];
|
||||
#ifdef DEBUG
|
||||
if (success) printf("DEL %s\n", self.absoluteString.UTF8String);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Move file to destination (by replacing any existing file)
|
||||
- (void)moveTo:(NSURL*)destination {
|
||||
[[NSFileManager defaultManager] removeItemAtURL:destination error:nil];
|
||||
[[NSFileManager defaultManager] moveItemAtURL:self toURL:destination error:nil];
|
||||
#ifdef DEBUG
|
||||
printf("MOVE %s\n", self.absoluteString.UTF8String);
|
||||
printf(" ↳ %s\n", destination.absoluteString.UTF8String);
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
29
baRSS/Helper/NSURLRequest+Ext.h
Normal file
29
baRSS/Helper/NSURLRequest+Ext.h
Normal file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 Oleg Geier
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
// of the Software, and to permit persons to whom the Software is furnished to do
|
||||
// so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
@import Cocoa;
|
||||
|
||||
@interface NSURLRequest (Ext)
|
||||
+ (instancetype)withURL:(NSString*)urlStr;
|
||||
- (NSURLSessionDataTask*)dataTask:(nonnull void(^)(NSData * _Nullable data, NSError * _Nullable error, NSHTTPURLResponse *response))block;
|
||||
- (NSURLSessionDownloadTask*)downloadTask:(void(^)(NSURL * _Nullable path, NSError * _Nullable error))block;
|
||||
@end
|
||||
97
baRSS/Helper/NSURLRequest+Ext.m
Normal file
97
baRSS/Helper/NSURLRequest+Ext.m
Normal file
@@ -0,0 +1,97 @@
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
// Copyright (c) 2019 Oleg Geier
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
// of the Software, and to permit persons to whom the Software is furnished to do
|
||||
// so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#import "NSURLRequest+Ext.h"
|
||||
#import "NSString+Ext.h"
|
||||
#import "NSError+Ext.h"
|
||||
|
||||
/// @return Shared URL session with caches disabled, enabled gzip encoding and custom user agent.
|
||||
static NSURLSession* NonCachingURLSession(void) {
|
||||
static NSURLSession *session = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSURLSessionConfiguration *conf = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
conf.HTTPCookieAcceptPolicy = NSHTTPCookieAcceptPolicyNever;
|
||||
conf.HTTPShouldSetCookies = NO;
|
||||
conf.HTTPCookieStorage = nil; // disables '~/Library/Cookies/'
|
||||
conf.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
|
||||
conf.URLCache = nil; // disables '~/Library/Caches/de.relikd.baRSS/'
|
||||
conf.HTTPAdditionalHeaders = @{ @"User-Agent": @"baRSS (macOS)",
|
||||
@"Accept-Encoding": @"gzip" };
|
||||
session = [NSURLSession sessionWithConfiguration:conf];
|
||||
});
|
||||
return session;
|
||||
}
|
||||
|
||||
|
||||
@implementation NSURLRequest (Ext)
|
||||
|
||||
/// @return New request from URL. Ensures that at least @c http scheme is set.
|
||||
+ (instancetype)withURL:(NSString*)urlStr {
|
||||
NSURL *url = [NSURL URLWithString:urlStr];
|
||||
if (!url.scheme)
|
||||
url = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@", urlStr]]; // will redirect to https
|
||||
return [self requestWithURL:url];
|
||||
}
|
||||
|
||||
/// Perform request with non caching @c NSURLSession . If HTTP status code is @c 304 then @c data @c = @c nil.
|
||||
- (NSURLSessionDataTask*)dataTask:(nonnull void(^)(NSData * _Nullable data, NSError * _Nullable error, NSHTTPURLResponse *response))block {
|
||||
NSURLSessionDataTask *task = [NonCachingURLSession() dataTaskWithRequest:self completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
|
||||
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
|
||||
NSInteger status = [httpResponse statusCode];
|
||||
#ifdef DEBUG
|
||||
/*if (status != 304)*/ printf("GET %ld %s\n", status, self.URL.absoluteString.UTF8String);
|
||||
#endif
|
||||
if (error || status == 304) {
|
||||
data = nil; // if status == 304, data & error nil
|
||||
} else if (status >= 400 && status < 600) { // catch Client & Server errors
|
||||
error = [NSError statusCode:status reason:(status >= 500 ? [NSString plainTextFromHTMLData:data] : nil)];
|
||||
data = nil;
|
||||
}
|
||||
block(data, error, httpResponse);
|
||||
}];
|
||||
[task resume];
|
||||
return task;
|
||||
}
|
||||
|
||||
/// Prepare a download task and immediatelly perform request with non caching URL session.
|
||||
- (NSURLSessionDownloadTask*)downloadTask:(void(^)(NSURL * _Nullable path, NSError * _Nullable error))block {
|
||||
NSURLSessionDownloadTask *task = [NonCachingURLSession() downloadTaskWithRequest:self completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
|
||||
block(location, error);
|
||||
}];
|
||||
[task resume];
|
||||
return task;
|
||||
}
|
||||
|
||||
/*
|
||||
Developer Tip, error log:
|
||||
|
||||
Task <..> HTTP load failed (error code: -1003 [12:8])
|
||||
Task <..> finished with error - code: -1003 --- NSURLErrorCannotFindHost
|
||||
==> NSURLErrorCannotFindHost in #import <Foundation/NSURLError.h>
|
||||
|
||||
TIC TCP Conn Failed [21:0x1d417fb00]: 1:65 Err(65) --- EHOSTUNREACH, No route to host
|
||||
TIC Read Status [9:0x0]: 1:57 --- ENOTCONN, Socket is not connected
|
||||
==> EHOSTUNREACH in #import <sys/errno.h>
|
||||
*/
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user