flatten 20260225
This commit is contained in:
18
tjp/core/internet/ios/Internet.hpp
Executable file
18
tjp/core/internet/ios/Internet.hpp
Executable file
@@ -0,0 +1,18 @@
|
||||
// TJP COPYRIGHT HEADER
|
||||
|
||||
#include <tjp/core/header_only/compile.h>
|
||||
|
||||
#include "../Internet.hpp"
|
||||
#include "../Internet+Internal.hpp"
|
||||
|
||||
namespace tjp::core::internet {
|
||||
|
||||
void perform (const WeakPtr<internal::Request> &request);
|
||||
|
||||
} // namespace
|
||||
|
||||
#ifdef TJP_CORE_HEADER_ONLY
|
||||
#ifdef __OBJC__
|
||||
#include "Internet.mm"
|
||||
#endif
|
||||
#endif
|
||||
614
tjp/core/internet/ios/Internet.mm
Executable file
614
tjp/core/internet/ios/Internet.mm
Executable file
@@ -0,0 +1,614 @@
|
||||
#include <tjp/core/header_only/compile.h>
|
||||
|
||||
#ifdef TJP_CORE_HEADER_ONLY
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
#pragma clang diagnostic ignored "-Wnullability-completeness"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "Internet.hpp"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
|
||||
#include <tjp/core/future/Future.hpp>
|
||||
#include <tjp/core/log/Log.h>
|
||||
#include <tjp/core/log/LogOf.h>
|
||||
|
||||
#include <tjp/core/string/as_string.hpp>
|
||||
|
||||
// http://iphoneincubator.com/blog/server-communication/how-to-download-a-file-only-if-it-has-been-updated/comment-page-1
|
||||
|
||||
//===============================================================================
|
||||
|
||||
typedef bool (*PartialCallback)(const void *bytes, NSUInteger length, void * _Nonnull context);
|
||||
typedef void (*CompleteCallback)(int statusCode, const char * _Nullable errorMessage, void * _Nonnull context);
|
||||
typedef bool (*HeaderLineCallback)(const void *line, NSUInteger length, void * _Nonnull context);
|
||||
|
||||
@interface ProgressiveDownloader : NSObject <NSURLSessionDataDelegate>
|
||||
{
|
||||
PartialCallback partialCallback;
|
||||
CompleteCallback completeCallback;
|
||||
HeaderLineCallback headerLineCallback;
|
||||
NSString *method;
|
||||
|
||||
size_t maxRedirects;
|
||||
void *callbackContext;
|
||||
}
|
||||
|
||||
@property (nonatomic, strong) NSURLSession *session;
|
||||
@property (nonatomic, strong) NSMutableData *receivedData;
|
||||
|
||||
- (instancetype)initWithPartial:(PartialCallback)partial
|
||||
complete:(CompleteCallback)complete
|
||||
headers:(HeaderLineCallback)headers
|
||||
context:(void *)context
|
||||
maxRedirects:(size_t)maxRedirects
|
||||
method:(NSString *)method;
|
||||
|
||||
- (void)startDownload:(NSMutableURLRequest *)url;
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
dataTask:(NSURLSessionDataTask *)dataTask
|
||||
didReceiveData:(NSData *)data;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ProgressiveDownloader
|
||||
|
||||
- (instancetype)initWithPartial:(PartialCallback)partial
|
||||
complete:(CompleteCallback)complete
|
||||
headers:(HeaderLineCallback)headers
|
||||
context:(void *)context
|
||||
maxRedirects:(size_t)maxRedirects_
|
||||
method:(NSString *)method_
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
partialCallback = partial;
|
||||
completeCallback = complete;
|
||||
headerLineCallback = headers;
|
||||
callbackContext = context;
|
||||
maxRedirects = maxRedirects_;
|
||||
method = method_;
|
||||
|
||||
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
config.HTTPCookieStorage = nil;
|
||||
|
||||
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
|
||||
self.session = session;
|
||||
|
||||
// NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
// self.session = [NSURLSession sessionWithConfiguration:config
|
||||
// delegate:self
|
||||
// delegateQueue:[NSOperationQueue mainQueue]];
|
||||
|
||||
self.receivedData = [NSMutableData data];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)startDownload:(NSMutableURLRequest *)request
|
||||
{
|
||||
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request];
|
||||
[task resume];
|
||||
}
|
||||
|
||||
// Response received
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
dataTask:(NSURLSessionDataTask *)dataTask
|
||||
didReceiveResponse:(NSURLResponse *)response
|
||||
completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
|
||||
{
|
||||
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
||||
|
||||
if (httpResponse.statusCode/100 == 3)
|
||||
if (maxRedirects)
|
||||
{
|
||||
sLogDebug("core::internet::headers", "getting headers while redirecting - ignoring");
|
||||
|
||||
return completionHandler(NSURLSessionResponseAllow);
|
||||
}
|
||||
|
||||
auto continue_task = true;
|
||||
if (headerLineCallback)
|
||||
{
|
||||
NSString *headerLine = [NSString stringWithFormat:@"HTTP/1.1 %d\r\n", httpResponse.statusCode];
|
||||
headerLineCallback(headerLine.UTF8String, [headerLine length], callbackContext);
|
||||
|
||||
[httpResponse.allHeaderFields enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
|
||||
NSString *k = [key description];
|
||||
NSString *v = [value description];
|
||||
NSString *headerLine = [NSString stringWithFormat:@"%@: %@\r\n", k, v];
|
||||
headerLineCallback(headerLine.UTF8String, [headerLine length], callbackContext);
|
||||
}];
|
||||
|
||||
continue_task = headerLineCallback("\r\n", 2, callbackContext);
|
||||
}
|
||||
|
||||
if (!continue_task)
|
||||
[dataTask cancel];
|
||||
|
||||
completionHandler(NSURLSessionResponseAllow);
|
||||
}
|
||||
|
||||
// Data arrives progressively
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
dataTask:(NSURLSessionDataTask *)dataTask
|
||||
didReceiveData:(NSData *)data
|
||||
{
|
||||
auto continue_task = true;
|
||||
if (partialCallback)
|
||||
continue_task = partialCallback(data.bytes, data.length, callbackContext);
|
||||
|
||||
[self.receivedData appendData:data];
|
||||
|
||||
if (!continue_task)
|
||||
[dataTask cancel];
|
||||
}
|
||||
|
||||
// Completion or error
|
||||
- (void)URLSession:(NSURLSession * _Nonnull)session
|
||||
task:(NSURLSessionTask * _Nonnull)task
|
||||
didCompleteWithError:(NSError * _Nullable)error
|
||||
{
|
||||
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
|
||||
|
||||
if (completeCallback)
|
||||
{
|
||||
if (error)
|
||||
completeCallback(response.statusCode, error.localizedDescription.UTF8String, callbackContext);
|
||||
else
|
||||
completeCallback(response.statusCode, NULL, callbackContext);
|
||||
}
|
||||
}
|
||||
|
||||
// Optionally handle redirect logic if needed
|
||||
- (void)URLSession:(NSURLSession * _Nonnull)session
|
||||
task:(NSURLSessionTask * _Nonnull)task
|
||||
willPerformHTTPRedirection:(NSHTTPURLResponse *)response_
|
||||
newRequest:(NSURLRequest *)request
|
||||
completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler
|
||||
{
|
||||
// NSHTTPURLResponse *response = (NSHTTPURLResponse *)response_;
|
||||
|
||||
sLogDebug("core::internet", "redirect requested " <<
|
||||
logVar(maxRedirects)
|
||||
logVar(request.URL.absoluteString.UTF8String) <<
|
||||
logVar(request.HTTPMethod.UTF8String)
|
||||
);
|
||||
|
||||
if (maxRedirects)
|
||||
{
|
||||
maxRedirects--;
|
||||
|
||||
if (![request.HTTPMethod isEqualToString:method])
|
||||
{
|
||||
sLogDebug("core::internet", "modifying method to match request");
|
||||
|
||||
NSMutableURLRequest *request_ = request.mutableCopy;
|
||||
[request_ setHTTPMethod:method];
|
||||
request = request_;
|
||||
}
|
||||
|
||||
completionHandler(request);
|
||||
}
|
||||
else
|
||||
{
|
||||
completionHandler(nil);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
namespace tjp {
|
||||
namespace core {
|
||||
namespace internet {
|
||||
|
||||
struct ProgressiveContext {
|
||||
internal::ProgressiveData data;
|
||||
dispatch_semaphore_t semaphor;
|
||||
} ;
|
||||
|
||||
bool onPartial(const void * _Nullable bytes, NSUInteger length, void * _Nonnull context_)
|
||||
{
|
||||
return internal::receiveContentsCallback((char *)bytes, length, context_) == length;
|
||||
}
|
||||
|
||||
bool onHeaders(const void * _Nullable bytes, NSUInteger length, void * _Nonnull context_)
|
||||
{
|
||||
return internal::receiveHeadersCallback((char *)bytes, length, context_) == length;
|
||||
}
|
||||
|
||||
void onComplete(int statusCode, const char * _Nullable errorMessage, void * _Nonnull context_)
|
||||
{
|
||||
auto *context = static_cast<ProgressiveContext *>(context_);
|
||||
context->data.receive.data.statusCode = statusCode;
|
||||
|
||||
dispatch_semaphore_signal(context->semaphor);
|
||||
}
|
||||
|
||||
Future<FileContents> get (const std::string &url, const HttpOptions &options)
|
||||
{
|
||||
auto request = strong<internal::Request>();
|
||||
request->type = internal::RequestType::GET;
|
||||
request->url = url;
|
||||
request->options = options;
|
||||
|
||||
perform(request);
|
||||
|
||||
return request->promise.get_future();
|
||||
}
|
||||
|
||||
Future<FileContents> post (const std::string &url, Data &&data, const HttpOptions &options)
|
||||
{
|
||||
auto request = strong<internal::Request>();
|
||||
request->type = internal::RequestType::POST;
|
||||
request->url = url;
|
||||
request->data = std::move(data);
|
||||
request->options = options;
|
||||
|
||||
perform(request);
|
||||
|
||||
return request->promise.get_future();
|
||||
}
|
||||
|
||||
|
||||
void perform_progressive(const WeakPtr<internal::Request> &request_)
|
||||
{
|
||||
auto getFile = [](const WeakPtr<internal::Request> &request_)
|
||||
{
|
||||
ProgressiveContext context;
|
||||
auto &data = context.data;
|
||||
data.request = request_;
|
||||
context.semaphor = dispatch_semaphore_create(0);
|
||||
|
||||
String url;
|
||||
ProgressiveDownloader *downloader;
|
||||
NSString *method;
|
||||
|
||||
if (auto request__ = strong(request_))
|
||||
{
|
||||
url = request__->url;
|
||||
|
||||
data.options = std::move(request__->options);
|
||||
if (request__->data)
|
||||
data.send.data = std::move(*request__->data);
|
||||
|
||||
NSString *stringURL = [NSString stringWithUTF8String:url.c_str()];
|
||||
|
||||
// without this, iOS <= 16 doesn't work for all urls
|
||||
NSCharacterSet *allowed = [NSCharacterSet URLFragmentAllowedCharacterSet];
|
||||
NSString *stringURLEncoded = [stringURL stringByAddingPercentEncodingWithAllowedCharacters:allowed];
|
||||
|
||||
NSURL *url_ = [NSURL URLWithString:stringURLEncoded];
|
||||
|
||||
sLogDebug("core::internet", "starting " << logVar(url) << logVar(stringURLEncoded.UTF8String));
|
||||
|
||||
// Prepare HTTP request
|
||||
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url_];
|
||||
[request setCachePolicy:NSURLRequestReloadIgnoringCacheData];
|
||||
// [request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"];
|
||||
|
||||
if (data.options.timeout)
|
||||
request.timeoutInterval = data.options.timeout;
|
||||
|
||||
auto &requestType = request__->type;
|
||||
if (requestType == internal::RequestType::POST)
|
||||
{
|
||||
method = @"POST";
|
||||
|
||||
sLogDebug("core::internet", "starting " << "POST");
|
||||
[request setHTTPMethod:method];
|
||||
NSData *data_ = [NSData dataWithBytes:data.send.data.c_str() length:data.send.data.size()];
|
||||
[request setHTTPBody:data_];
|
||||
}
|
||||
else
|
||||
if (requestType == internal::RequestType::PUT)
|
||||
{
|
||||
method = @"PUT";
|
||||
|
||||
sLogDebug("core::internet", "starting " << "PUT");
|
||||
[request setHTTPMethod:method];
|
||||
NSData *data_ = [NSData dataWithBytes:data.send.data.data() length:data.send.data.size()];
|
||||
[request setHTTPBody:data_];
|
||||
}
|
||||
else
|
||||
if (requestType == internal::RequestType::GET)
|
||||
{
|
||||
method = @"GET";
|
||||
|
||||
sLogDebug("core::internet", "starting " << "GET");
|
||||
[request setHTTPMethod:method];
|
||||
}
|
||||
else
|
||||
if (requestType == internal::RequestType::HEAD)
|
||||
{
|
||||
method = @"HEAD";
|
||||
|
||||
sLogDebug("core::internet", "starting " << "HEAD");
|
||||
[request setHTTPMethod:method];
|
||||
}
|
||||
|
||||
for (auto &header : data.options.headers)
|
||||
{
|
||||
auto &[key, value] = header;
|
||||
sLogDebug("core::internet::headers", "send header " << logVar(key) << logVar(value));
|
||||
|
||||
NSString *key_ = [NSString stringWithUTF8String:key.c_str()];
|
||||
NSString *value_ = [NSString stringWithUTF8String:value.c_str()];
|
||||
[request setValue:value_ forHTTPHeaderField:key_];
|
||||
}
|
||||
|
||||
if (data.options.lastModifiedTime)
|
||||
{
|
||||
NSDate *date = [NSDate dateWithTimeIntervalSince1970:data.options.lastModifiedTime];
|
||||
NSDateFormatter *df = [[NSDateFormatter alloc] init];
|
||||
df.dateFormat = @"EEE, dd MMM yyyy HH:mm:ss Z";
|
||||
df.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
|
||||
df.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
|
||||
NSString *dateString = [df stringFromDate:date];
|
||||
[request setValue:dateString forHTTPHeaderField:@"If-Modified-Since"];
|
||||
}
|
||||
|
||||
if (url_.user)
|
||||
{
|
||||
NSString *user = url_.user;
|
||||
NSString *pass = url_.password ?: @"";
|
||||
NSString *auth = [NSString stringWithFormat:@"%@:%@", user, pass];
|
||||
|
||||
NSData *authData = [auth dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSString *authValue = [NSString stringWithFormat:@"Basic %@",
|
||||
[authData base64EncodedStringWithOptions:0]];
|
||||
|
||||
[request setValue:authValue forHTTPHeaderField:@"Authorization"];
|
||||
}
|
||||
|
||||
[request setValue:@"close" forHTTPHeaderField:@"Connection"];
|
||||
|
||||
// --- Define progressive callbacks ---
|
||||
|
||||
downloader =
|
||||
[[ProgressiveDownloader alloc]
|
||||
initWithPartial:onPartial
|
||||
complete:onComplete
|
||||
headers:onHeaders
|
||||
context:&context
|
||||
maxRedirects:data.options.maxRedirects
|
||||
method:method
|
||||
];
|
||||
|
||||
[downloader startDownload:request];
|
||||
}
|
||||
else
|
||||
{
|
||||
throw Exception (
|
||||
"HTTPConnection canceled",
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
dispatch_semaphore_wait(context.semaphor, DISPATCH_TIME_FOREVER);
|
||||
|
||||
sLogDebug("core::internet", "finishing 0");
|
||||
|
||||
if (data.receive.data.statusCode < 0)
|
||||
data.receive.data.statusCode = internet::GeneralFailure;
|
||||
|
||||
if (data.interrupted)
|
||||
data.receive.data.statusCode = internet::GeneralFailure;
|
||||
|
||||
if (data.options.onHeaders)
|
||||
data.options.onHeaders(data.receive.data.headers, data.receive.data.statusCode);
|
||||
|
||||
if (data.options.onComplete)
|
||||
data.options.onComplete(data.receive.data.statusCode);
|
||||
|
||||
sLogDebug("core::internet", "finishing 1");
|
||||
|
||||
auto got_error = data.receive.data.statusCode >= 400;
|
||||
|
||||
sLogReleaseIf(got_error, "debug", "finished " << logVar(url) << logVar(got_error));
|
||||
|
||||
if (got_error)
|
||||
{
|
||||
throw Exception("HTTPConnection getFile failed", std::move(data.receive.data));
|
||||
}
|
||||
|
||||
if (data.options.throwOnFailure && !data.receive.data.ok())
|
||||
{
|
||||
sLogRelease("errors", "get http failed: " << logVar(url) << logVar(data.receive.data.statusCode));
|
||||
throw Exception(
|
||||
"HTTPConnection getFile failed code " + std::to_string(data.receive.data.statusCode),
|
||||
std::move(data.receive.data));
|
||||
}
|
||||
|
||||
return data.receive.data;
|
||||
};
|
||||
|
||||
Promise<FileContents> promise;
|
||||
if (auto request = strong(request_))
|
||||
promise = std::move(request->promise);
|
||||
|
||||
promise.consume(getFile, request_);
|
||||
}
|
||||
|
||||
#if REMOVE_CODE_2025_11_11
|
||||
# code is superseded by perform
|
||||
|
||||
void perform_completion (const std::string &url, const std::string &method, Data &&data, const HttpOptions &options, Promise<FileContents> &promise)
|
||||
{
|
||||
xLogDebug (logVar(url));
|
||||
|
||||
auto getFile = [&]() {
|
||||
__block FileContents contents;
|
||||
|
||||
sLogDebug("core::internet", "starting " << url);
|
||||
|
||||
NSString *stringURL = [NSString stringWithUTF8String:url.c_str()];
|
||||
NSURL *url_ = [NSURL URLWithString:stringURL];
|
||||
|
||||
// NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration]; // No cookie storage
|
||||
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; // No cookie storage
|
||||
// config.HTTPCookieAcceptPolicy = NSHTTPCookieAcceptPolicyNever;
|
||||
config.HTTPCookieStorage = nil;
|
||||
|
||||
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
|
||||
// NSURLSession *session = [NSURLSession sharedSession];
|
||||
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url_];
|
||||
|
||||
if (options.timeout)
|
||||
request.timeoutInterval = options.timeout;
|
||||
|
||||
[request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"];
|
||||
[request setCachePolicy:NSURLRequestReloadIgnoringCacheData];
|
||||
|
||||
if (method == "POST")
|
||||
{
|
||||
[request setHTTPMethod:@"POST"];
|
||||
|
||||
NSString *str = [NSString stringWithUTF8String:data.c_str()];
|
||||
NSData *data_ = [str dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
[request setHTTPBody:data_];
|
||||
}
|
||||
else
|
||||
if (method == "GET")
|
||||
{
|
||||
[request setHTTPMethod:@"GET"];
|
||||
}
|
||||
else
|
||||
if (method == "HEAD")
|
||||
{
|
||||
[request setHTTPMethod:@"HEAD"];
|
||||
}
|
||||
else
|
||||
if (method == "PUT")
|
||||
{
|
||||
[request setHTTPMethod:@"PUT"];
|
||||
|
||||
NSData *data_ = [NSData dataWithBytes:data.data() length:data.size()];
|
||||
[request setHTTPBody:data_];
|
||||
}
|
||||
|
||||
for (auto &header : options.headers)
|
||||
{
|
||||
auto &[key, value] = header;
|
||||
NSString *key_ = [NSString stringWithUTF8String:key.c_str()];
|
||||
NSString *value_ = [NSString stringWithUTF8String:value.c_str()];
|
||||
|
||||
[request setValue:value_ forHTTPHeaderField:key_];
|
||||
}
|
||||
|
||||
if (options.lastModifiedTime)
|
||||
{
|
||||
NSString *key_ = @"If-Modified-Since";
|
||||
NSDate *date = [NSDate dateWithTimeIntervalSince1970:options.lastModifiedTime];
|
||||
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; // maybe there exist a new-method now
|
||||
dateFormatter.dateFormat = @"EEE, dd MMM yyyy HH:mm:ss Z"; //RFC2822-Format
|
||||
dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier: @"en_US_POSIX"];
|
||||
dateFormatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
|
||||
NSString *dateString = [dateFormatter stringFromDate:date];
|
||||
|
||||
[request setValue:dateString forHTTPHeaderField:key_];
|
||||
}
|
||||
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
__block bool got_error = false;
|
||||
|
||||
NSURLSessionDataTask *downloadTask =
|
||||
[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response_, NSError *error) {
|
||||
if (!error)
|
||||
{
|
||||
NSHTTPURLResponse *response = (NSHTTPURLResponse *)response_;
|
||||
NSString *lastModifiedString = [[response allHeaderFields] objectForKey:@"Last-Modified"];
|
||||
|
||||
if (lastModifiedString)
|
||||
{
|
||||
NSDate *lastModifiedServer = nil;
|
||||
@try {
|
||||
NSDateFormatter *df = [[NSDateFormatter alloc] init];
|
||||
df.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'";
|
||||
df.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
|
||||
df.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
|
||||
lastModifiedServer = [df dateFromString:lastModifiedString];
|
||||
}
|
||||
@catch (NSException * e) {
|
||||
NSLog(@"Error parsing last modified date: %@ - %@", lastModifiedString, [e description]);
|
||||
}
|
||||
|
||||
contents.lastModifiedTime = [lastModifiedServer timeIntervalSince1970];
|
||||
}
|
||||
|
||||
NSDictionary *headers = [response allHeaderFields];
|
||||
NSArray *keys = [headers allKeys];
|
||||
|
||||
for (NSString* key in keys)
|
||||
{
|
||||
NSString *value = [headers objectForKey:key];
|
||||
contents.headers.push_back({ key.UTF8String, value.UTF8String });
|
||||
}
|
||||
|
||||
contents.data.resize(data.length);
|
||||
contents.statusCode = response.statusCode;
|
||||
memcpy(contents.data.data(), data.bytes, contents.data.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
got_error = true;
|
||||
}
|
||||
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
}];
|
||||
|
||||
[downloadTask resume];
|
||||
|
||||
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
|
||||
|
||||
if (options.onHeaders)
|
||||
options.onHeaders(contents.headers, contents.statusCode);
|
||||
|
||||
if (options.onData)
|
||||
options.onData(contents.data.data(), contents.data.size());
|
||||
|
||||
if (options.onComplete)
|
||||
options.onComplete(contents.statusCode);
|
||||
|
||||
sLogReleaseIf(got_error, "debug", "finished " << logVar(url) << logVar(got_error));
|
||||
|
||||
if (got_error)
|
||||
throw Exception (
|
||||
"HTTPConnection getFile failed",
|
||||
std::move(contents)
|
||||
);
|
||||
|
||||
if (options.throwOnFailure && !contents.ok())
|
||||
{
|
||||
sLogRelease("errors", "get http failed: " << logVar(url) << logVar(contents.statusCode));
|
||||
throw Exception (
|
||||
"HTTPConnection getFile failed code " + std::to_string(contents.statusCode),
|
||||
std::move(contents)
|
||||
);
|
||||
}
|
||||
|
||||
return contents;
|
||||
};
|
||||
|
||||
promise_of(promise, getFile);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void perform(const WeakPtr<internal::Request> &request_)
|
||||
{
|
||||
perform_progressive(request_);
|
||||
}
|
||||
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
} // namespace
|
||||
106
tjp/core/internet/ios/Queue.cpp
Executable file
106
tjp/core/internet/ios/Queue.cpp
Executable file
@@ -0,0 +1,106 @@
|
||||
// TJP COPYRIGHT HEADER
|
||||
|
||||
#ifdef TJP_CORE_HEADER_ONLY
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
#include "../Queue.hpp"
|
||||
#include "Internet.hpp"
|
||||
|
||||
#include <tjp/core/threads/Lock.hpp>
|
||||
#include <tjp/core/threads/process_queue.hpp>
|
||||
|
||||
#include <tjp/core/ptr/strong_of.hpp>
|
||||
#include <tjp/core/future/Future.hpp>
|
||||
|
||||
#include <queue>
|
||||
|
||||
//===============================================================================
|
||||
|
||||
namespace tjp::core::internet {
|
||||
|
||||
struct Queue::Internal
|
||||
{
|
||||
std::thread thread;
|
||||
bool finished = false;
|
||||
|
||||
std::queue<WeakPtr<internal::Request>> queue;
|
||||
Event event;
|
||||
Event::Mutex mutex;
|
||||
|
||||
void run ();
|
||||
void runOne (const WeakPtr<internal::Request> &i);
|
||||
} ;
|
||||
|
||||
TJP_CORE_HEADER_ONLY_INLINE
|
||||
Queue::Queue()
|
||||
{
|
||||
}
|
||||
|
||||
TJP_CORE_HEADER_ONLY_INLINE
|
||||
Queue::~Queue ()
|
||||
{
|
||||
}
|
||||
|
||||
TJP_CORE_HEADER_ONLY_INLINE
|
||||
void Queue::Internal::runOne (const WeakPtr<internal::Request> &i_)
|
||||
{
|
||||
perform(i_);
|
||||
}
|
||||
|
||||
TJP_CORE_HEADER_ONLY_INLINE
|
||||
void Queue::Internal::run ()
|
||||
{
|
||||
threads::process_queue(
|
||||
finished,
|
||||
event,
|
||||
mutex,
|
||||
queue,
|
||||
[this](auto &i) { return runOne(i); }
|
||||
);
|
||||
}
|
||||
|
||||
TJP_CORE_HEADER_ONLY_INLINE
|
||||
void Queue::prepare()
|
||||
{
|
||||
if (i)
|
||||
return;
|
||||
|
||||
static Mutex mutex;
|
||||
auto lock = lock_of(mutex);
|
||||
if (i)
|
||||
return;
|
||||
|
||||
i = strong<Internal>();
|
||||
i->thread = std::thread([s=i](){ s->run(); });
|
||||
}
|
||||
|
||||
TJP_CORE_HEADER_ONLY_INLINE
|
||||
Future<FileContents> Queue::execute(internal::RequestType type, const std::string &url, Data &&data, const HttpOptions &options)
|
||||
{
|
||||
prepare();
|
||||
|
||||
auto item = strong_of_value(
|
||||
internal::Request {
|
||||
.type = type,
|
||||
.url = url,
|
||||
.options = options,
|
||||
.data = std::move(data)
|
||||
}
|
||||
);
|
||||
|
||||
{
|
||||
auto lock = lock_of(i->mutex);
|
||||
i->queue.push(weak(item));
|
||||
}
|
||||
|
||||
i->event.notify_all();
|
||||
|
||||
return item->promise.get_future().then(
|
||||
[item](auto &&f) {
|
||||
return f.get();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
7
tjp/core/internet/ios/Queue.hpp
Executable file
7
tjp/core/internet/ios/Queue.hpp
Executable file
@@ -0,0 +1,7 @@
|
||||
// TJP COPYRIGHT HEADER
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef TJP_CORE_HEADER_ONLY
|
||||
#include "Queue.cpp"
|
||||
#endif
|
||||
Reference in New Issue
Block a user