Objectively 1.0.0
Ultra-lightweight object oriented framework for GNU C.
URLSession.c
Go to the documentation of this file.
1/*
2 * Objectively: Ultra-lightweight object oriented framework for GNU C.
3 * Copyright (C) 2014 Jay Dolan <jay@jaydolan.com>
4 *
5 * This software is provided 'as-is', without any express or implied
6 * warranty. In no event will the authors be held liable for any damages
7 * arising from the use of this software.
8 *
9 * Permission is granted to anyone to use this software for any purpose,
10 * including commercial applications, and to alter it and redistribute it
11 * freely, subject to the following restrictions:
12 *
13 * 1. The origin of this software must not be misrepresented; you must not
14 * claim that you wrote the original software. If you use this software
15 * in a product, an acknowledgment in the product documentation would be
16 * appreciated but is not required.
17 *
18 * 2. Altered source versions must be plainly marked as such, and must not be
19 * misrepresented as being the original software.
20 *
21 * 3. This notice may not be removed or altered from any source distribution.
22 */
23
24#include <assert.h>
25
26#include <curl/curl.h>
27
28#include "URLSession.h"
29
30#define _Class _URLSession
31
32#pragma mark - Object
33
37static void dealloc(Object *self) {
38
39 URLSession *this = (URLSession *) self;
40
41 $(this, invalidateAndCancel);
42
43 $(this->locals.thread, join, NULL);
44
45 release(this->locals.condition);
46 release(this->locals.thread);
47 release(this->locals.tasks);
48
49 super(Object, self, dealloc);
50}
51
52#pragma mark - URLSession
53
57static ident taskWithRequest(URLSession *self, ident task, URLRequest *request, URLSessionTaskCompletion completion) {
58
59 task = $((URLSessionTask *) task, initWithRequestInSession, request, self, completion);
60 if (task) {
61
62 synchronized(self->locals.lock, {
63 $(self->locals.tasks, addObject, task);
64 });
65
66 $(self->locals.condition, signal);
67 }
68
69 return task;
70}
71
77
78 return taskWithRequest(self, alloc(URLSessionDataTask), request, completion);
79}
80
86
87 URLRequest *request = $(alloc(URLRequest), initWithURL, url);
88
89 URLSessionDataTask *task = $(self, dataTaskWithRequest, request, completion);
90
91 release(request);
92
93 return task;
94}
95
101
102 return taskWithRequest(self, alloc(URLSessionDownloadTask), request, completion);
103}
104
110
111 URLRequest *request = $(alloc(URLRequest), initWithURL, url);
112
113 URLSessionDownloadTask *task = $(self, downloadTaskWithRequest, request, completion);
114
115 release(request);
116
117 return task;
118}
119
124static URLSession *init(URLSession *self) {
125
127
128 self = $(self, initWithConfiguration, configuration);
129
130 release(configuration);
131
132 return self;
133}
134
138static ident run(Thread *thread) {
139
140 URLSession *self = thread->data;
141
142 self->locals.handle = curl_multi_init();
143 assert(self->locals.handle);
144
145 while (true) {
146 int ret;
147
148 Array *tasks = $(self, tasks);
149 if (tasks->count == 0) {
150
151 if (thread->isCancelled) {
152 break;
153 }
154
155 $(self->locals.condition, wait);
156 continue;
157 }
158
159 for (size_t i = 0; i < tasks->count; i++) {
160
161 URLSessionTask *task = $(tasks, objectAtIndex, i);
162 if (task->state == URLSESSIONTASK_SUSPENDING) {
163
164 if (task->locals.handle) {
165 const CURLcode err = curl_easy_pause(task->locals.handle, CURLPAUSE_ALL);
166 assert(err == CURLE_OK);
167 }
168
170
171 } else if (task->state == URLSESSIONTASK_RESUMING) {
172
173 if (task->locals.handle == NULL) {
174
175 $(task, setup);
176 assert(task->locals.handle);
177
178 const CURLMcode merr = curl_multi_add_handle(self->locals.handle, task->locals.handle);
179 assert(merr == CURLM_OK);
180 } else {
181 const CURLcode err = curl_easy_pause(task->locals.handle, CURLPAUSE_CONT);
182 assert(err == CURLE_OK);
183 }
184
186
187 } else if (task->state == URLSESSIONTASK_CANCELING) {
188
189 if (task->locals.handle) {
190 const CURLMcode merr = curl_multi_remove_handle(self->locals.handle, task->locals.handle);
191 assert(merr == CURLM_OK);
192 }
193
195
196 if (task->completion) {
197 task->completion(task, false);
198 }
199
200 $(task, teardown);
201
202 synchronized(self->locals.lock, {
203 $(self->locals.tasks, removeObject, task);
204 });
205 } else if (task->state == URLSESSIONTASK_COMPLETED) {
206
207 synchronized(self->locals.lock, {
208 $(self->locals.tasks, removeObject, task);
209 });
210 }
211 }
212
213 CURLMcode merr = curl_multi_wait(self->locals.handle, NULL, 0, 0, NULL);
214 assert(merr == CURLM_OK);
215
216 merr = curl_multi_perform(self->locals.handle, &ret);
217 assert(merr == CURLM_OK);
218
219 CURLMsg *message;
220 while ((message = curl_multi_info_read(self->locals.handle, &ret))) {
221
222 URLSessionTask *task = NULL;
223 for (size_t i = 0; i < tasks->count; i++) {
224
226 if (t->locals.handle == message->easy_handle) {
227 task = t;
228 break;
229 }
230 }
231
232 assert(task);
233
234 if (message->msg == CURLMSG_DONE) {
235
236 merr = curl_multi_remove_handle(self->locals.handle, task->locals.handle);
237 assert(merr == CURLM_OK);
238
240
241 curl_easy_getinfo(task->locals.handle, CURLINFO_RESPONSE_CODE, (long *) &task->response->httpStatusCode);
242
243 if (task->completion) {
244 task->completion(task, message->data.result == CURLE_OK);
245 }
246
247 $(task, teardown);
248
249 synchronized(self->locals.lock, {
250 $(self->locals.tasks, removeObject, task);
251 });
252 }
253 }
254
255 release(tasks);
256 }
257
258 curl_multi_cleanup(self->locals.handle);
259
260 return NULL;
261}
262
268
269 assert(configuration);
270
271 self = (URLSession *) super(Object, self, init);
272 if (self) {
273 self->configuration = retain(configuration);
274
275 self->locals.condition = $(alloc(Condition), init);
276 self->locals.lock = $(alloc(Lock), init);
277 self->locals.tasks = $(alloc(MutableArray), init);
278 self->locals.thread = $(alloc(Thread), initWithFunction, run, self);
279
280 $(self->locals.thread, start);
281 }
282
283 return self;
284}
285
290static void invalidateAndCancel(URLSession *self) {
291
292 if (self->locals.thread->isCancelled) {
293 return;
294 }
295
296 Array *tasks = $(self, tasks);
297
298 for (size_t i = 0; i < tasks->count; i++) {
299
300 URLSessionTask *task = $(tasks, objectAtIndex, i);
301 $(task, cancel);
302 }
303
304 release(tasks);
305
306 $(self->locals.thread, cancel);
307 $(self->locals.condition, signal);
308}
309
311
317
318 static Once once;
319
320 do_once(&once, {
322 });
323
324 return _sharedInstance;
325}
326
331static Array *tasks(const URLSession *self) {
332
333 Array *array;
334
335 synchronized(self->locals.lock, {
336 array = $$(Array, arrayWithArray, (Array *) self->locals.tasks);
337 });
338
339 return array;
340}
341
347
348 return taskWithRequest(self, alloc(URLSessionUploadTask), request, completion);
349}
350
351#pragma mark - Class lifecycle
352
356static void initialize(Class *clazz) {
357
358 ((ObjectInterface *) clazz->interface)->dealloc = dealloc;
359
360 ((URLSessionInterface *) clazz->interface)->dataTaskWithRequest = dataTaskWithRequest;
361 ((URLSessionInterface *) clazz->interface)->dataTaskWithURL = dataTaskWithURL;
362 ((URLSessionInterface *) clazz->interface)->downloadTaskWithRequest = downloadTaskWithRequest;
363 ((URLSessionInterface *) clazz->interface)->downloadTaskWithURL = downloadTaskWithURL;
364 ((URLSessionInterface *) clazz->interface)->init = init;
365 ((URLSessionInterface *) clazz->interface)->initWithConfiguration = initWithConfiguration;
366 ((URLSessionInterface *) clazz->interface)->invalidateAndCancel = invalidateAndCancel;
367 ((URLSessionInterface *) clazz->interface)->sharedInstance = sharedInstance;
368 ((URLSessionInterface *) clazz->interface)->tasks = tasks;
369 ((URLSessionInterface *) clazz->interface)->uploadTaskWithRequest = uploadTaskWithRequest;
370
371 const CURLcode code = curl_global_init(CURL_GLOBAL_ALL);
372 assert(code == CURLE_OK);
373}
374
378static void destroy(Class *clazz) {
379
381
382 curl_global_cleanup();
383}
384
390 static Class *clazz;
391 static Once once;
392
393 do_once(&once, {
394 clazz = _initialize(&(const ClassDef) {
395 .name = "URLSession",
396 .superclass = _Object(),
397 .instanceSize = sizeof(URLSession),
398 .interfaceOffset = offsetof(URLSession, interface),
399 .interfaceSize = sizeof(URLSessionInterface),
401 .destroy = destroy,
402 });
403 });
404
405 return clazz;
406}
407
408#undef _Class
ident release(ident obj)
Atomically decrement the given Object's reference count. If the resulting reference count is 0,...
Definition: Class.c:196
Class * _initialize(const ClassDef *def)
Initializes the given Class.
Definition: Class.c:91
ident retain(ident obj)
Atomically increment the given Object's reference count.
Definition: Class.c:211
#define alloc(type)
Allocate and initialize and instance of type.
Definition: Class.h:159
#define super(type, obj, method,...)
void * ident
The identity type, similar to Objective-C id.
Definition: Types.h:49
static void destroy(Class *clazz)
Definition: URLSession.c:378
static ident taskWithRequest(URLSession *self, ident task, URLRequest *request, URLSessionTaskCompletion completion)
URLSessionTask factory function.
Definition: URLSession.c:57
static ident run(Thread *thread)
ThreadFunction for a URLSession.
Definition: URLSession.c:138
static URLSession * _sharedInstance
Definition: URLSession.c:310
static void initialize(Class *clazz)
Definition: URLSession.c:356
A management context for loading resources via URLs.
@ URLSESSIONTASK_RESUMING
@ URLSESSIONTASK_CANCELED
@ URLSESSIONTASK_SUSPENDING
@ URLSESSIONTASK_SUSPENDED
@ URLSESSIONTASK_COMPLETED
@ URLSESSIONTASK_RESUMED
@ URLSESSIONTASK_CANCELING
void(* URLSessionTaskCompletion)(URLSessionTask *task, _Bool success)
A function pointer for URLSessionTask completion.
long Once
The Once type.
Definition: Once.h:37
#define do_once(once, block)
Executes the given block at most one time.
Definition: Once.h:43
Immutable arrays.
Definition: Array.h:56
ident objectAtIndex(const Array *self, int index)
size_t count
The count of elements.
Definition: Array.h:72
Array * arrayWithArray(const Array *array)
Returns a new Array containing the contents of array.
Definition: Array.c:133
ClassDefs are passed to _initialize via an archetype to initialize a Class.
Definition: Class.h:41
The runtime representation of a Class.
Definition: Class.h:95
ident interface
The interface of the Class.
Definition: Class.h:105
POSIX Threads conditional variables.
Definition: Condition.h:44
void wait(Condition *self)
Waits indefinitely for this Condition to be signaled.
Condition * init(Condition *self)
Initializes this Condition.
Definition: Condition.c:67
void signal(Condition *self)
Signals a single Thread waiting on this Condition.
POSIX Threads locks.
Definition: Lock.h:42
Log * sharedInstance(void)
Definition: Log.c:212
Mutable arrays.
Definition: MutableArray.h:40
void addObject(MutableArray *self, const ident obj)
Adds the specified Object to this MutableArray.
Definition: MutableArray.c:99
void removeObject(MutableArray *self, const ident obj)
Removes the specified Object from this MutableArray.
Definition: MutableArray.c:270
MutableArray * array(void)
Returns a new MutableArray.
Definition: MutableArray.c:153
Object is the root Class of The Objectively Class hierarchy.
Definition: Object.h:46
Class * _Object(void)
The Object archetype.
Definition: Object.c:136
Object * init(Object *self)
Initializes this Object.
Definition: Object.c:83
void dealloc(Object *self)
Frees all resources held by this Object.
Definition: Array.c:50
void start(Operation *self)
Starts this Operation.
Definition: Operation.c:171
void cancel(Operation *self)
Cancels this Operation, allowing it to complete immediately.
Definition: Operation.c:74
Operation * initWithFunction(Operation *self, OperationFunction function, ident data)
Initializes a synchronous Operation with the given function.
Definition: Operation.c:119
POSIX Threads.
Definition: Thread.h:53
ident data
The user data.
Definition: Thread.h:69
void join(Thread *self, ident *status)
Wait for the specified Thread to terminate.
Definition: Thread.c:130
_Bool isCancelled
true when this Thread has been cancelled, false otherwise.
Definition: Thread.h:79
Uniform Resource Locators (RFC 3986).
Definition: URL.h:44
A protocol-agnostic abstraction for requesting resources via URLs.
Definition: URLRequest.h:56
URLRequest * initWithURL(URLRequest *self, URL *url)
Initializes this URLRequest with the specified URL.
Definition: URLRequest.c:73
int httpStatusCode
The HTTP response status code.
Definition: URLResponse.h:63
Configuration bundle for URLSession.
Use data tasks to send and receive Data in-memory.
Use download tasks to save remote resources to file.
A management context for loading resources via URLs.
Definition: URLSession.h:57
URLSessionDataTask * dataTaskWithURL(URLSession *, URL *, URLSessionTaskCompletion)
Creates a URLSessionDataTask for the given URL.
Definition: URLSession.c:85
URLSessionDataTask * dataTaskWithRequest(URLSession *, URLRequest *, URLSessionTaskCompletion)
Creates a URLSessionDataTask for the given URLRequest.
Definition: URLSession.c:76
URLSessionDownloadTask * downloadTaskWithURL(URLSession *, URL *, URLSessionTaskCompletion)
Creates a URLSessionDownloadTask for the given URL.
Definition: URLSession.c:109
Lock * lock
The Lock guarding access to tasks.
Definition: URLSession.h:87
Condition * condition
The condition.
Definition: URLSession.h:77
URLSessionConfiguration * configuration
The session configuration.
Definition: URLSession.h:103
Class * _URLSession(void)
The URLSession archetype.
Definition: URLSession.c:389
Thread * thread
The backing Thread.
Definition: URLSession.h:97
MutableArray * tasks
The URLSessionTasks.
Definition: URLSession.h:92
void invalidateAndCancel(URLSession *)
Invalidates this URLSession and cancels all pending tasks.
Definition: URLSession.c:290
URLSession * initWithConfiguration(URLSession *, URLSessionConfiguration *)
Initializes this URLSession with the given configuration.
Definition: URLSession.c:267
URLSessionUploadTask * uploadTaskWithRequest(URLSession *, URLRequest *, URLSessionTaskCompletion)
Creates a URLSessionUploadTask for the given URLRequest.
Definition: URLSession.c:346
Array * tasks(const URLSession *)
Definition: URLSession.c:331
URLSessionDownloadTask * downloadTaskWithRequest(URLSession *, URLRequest *, URLSessionTaskCompletion)
Creates a URLSessionDownloadTask for the given URLRequest.
Definition: URLSession.c:100
ident handle
The libcurl handle.
Definition: URLSession.h:82
URL session tasks are handles to pending URL operations.
URLSessionTask * initWithRequestInSession(URLSessionTask *, struct URLRequest *, struct URLSession *, URLSessionTaskCompletion)
Initializes this task with the given URLRequest.
struct URLResponse * response
The response.
ident handle
The backing libcurl handle.
void setup(URLSessionTask *)
Sets up this task.
ident data
User data.
void teardown(URLSessionTask *)
Tears down this task.
URLSessionTaskState state
The state.
URLSessionTaskCompletion completion
The completion function.
Use upload tasks to send files directly from disk.