1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ |
---|
2 | /* |
---|
3 | * soup-session-async.c |
---|
4 | * |
---|
5 | * Copyright (C) 2000-2003, Ximian, Inc. |
---|
6 | */ |
---|
7 | |
---|
8 | #ifdef HAVE_CONFIG_H |
---|
9 | #include <config.h> |
---|
10 | #endif |
---|
11 | |
---|
12 | #include "soup-session-async.h" |
---|
13 | #include "soup-connection.h" |
---|
14 | |
---|
15 | struct SoupSessionAsyncPrivate { |
---|
16 | int dummy; |
---|
17 | }; |
---|
18 | |
---|
19 | static gboolean run_queue (SoupSessionAsync *sa, gboolean try_pruning); |
---|
20 | |
---|
21 | static void queue_message (SoupSession *session, SoupMessage *req, |
---|
22 | SoupMessageCallbackFn callback, |
---|
23 | gpointer user_data); |
---|
24 | static guint send_message (SoupSession *session, SoupMessage *req); |
---|
25 | |
---|
26 | #define PARENT_TYPE SOUP_TYPE_SESSION |
---|
27 | static SoupSessionClass *parent_class; |
---|
28 | |
---|
29 | static void |
---|
30 | init (GObject *object) |
---|
31 | { |
---|
32 | SoupSessionAsync *sa = SOUP_SESSION_ASYNC (object); |
---|
33 | |
---|
34 | sa->priv = g_new0 (SoupSessionAsyncPrivate, 1); |
---|
35 | } |
---|
36 | |
---|
37 | static void |
---|
38 | finalize (GObject *object) |
---|
39 | { |
---|
40 | SoupSessionAsync *sa = SOUP_SESSION_ASYNC (object); |
---|
41 | |
---|
42 | g_free (sa->priv); |
---|
43 | |
---|
44 | G_OBJECT_CLASS (parent_class)->finalize (object); |
---|
45 | } |
---|
46 | |
---|
47 | static void |
---|
48 | class_init (GObjectClass *object_class) |
---|
49 | { |
---|
50 | SoupSessionClass *session_class = SOUP_SESSION_CLASS (object_class); |
---|
51 | |
---|
52 | parent_class = g_type_class_ref (PARENT_TYPE); |
---|
53 | |
---|
54 | /* virtual method override */ |
---|
55 | session_class->queue_message = queue_message; |
---|
56 | session_class->send_message = send_message; |
---|
57 | object_class->finalize = finalize; |
---|
58 | } |
---|
59 | |
---|
60 | SOUP_MAKE_TYPE (soup_session_async, SoupSessionAsync, class_init, init, PARENT_TYPE) |
---|
61 | |
---|
62 | SoupSession * |
---|
63 | soup_session_async_new (void) |
---|
64 | { |
---|
65 | return g_object_new (SOUP_TYPE_SESSION_ASYNC, NULL); |
---|
66 | } |
---|
67 | |
---|
68 | SoupSession * |
---|
69 | soup_session_async_new_with_options (const char *optname1, ...) |
---|
70 | { |
---|
71 | SoupSession *session; |
---|
72 | va_list ap; |
---|
73 | |
---|
74 | va_start (ap, optname1); |
---|
75 | session = (SoupSession *)g_object_new_valist (SOUP_TYPE_SESSION_ASYNC, |
---|
76 | optname1, ap); |
---|
77 | va_end (ap); |
---|
78 | |
---|
79 | return session; |
---|
80 | } |
---|
81 | |
---|
82 | |
---|
83 | static void |
---|
84 | connection_closed (SoupConnection *conn, SoupSessionAsync *sa) |
---|
85 | { |
---|
86 | /* Run the queue in case anyone was waiting for a connection |
---|
87 | * to be closed. |
---|
88 | */ |
---|
89 | run_queue (sa, FALSE); |
---|
90 | } |
---|
91 | |
---|
92 | static void |
---|
93 | got_connection (SoupConnection *conn, guint status, gpointer user_data) |
---|
94 | { |
---|
95 | SoupSessionAsync *sa = user_data; |
---|
96 | |
---|
97 | if (status != SOUP_STATUS_OK) { |
---|
98 | /* The connection attempt failed, and thus @conn was |
---|
99 | * closed and the open connection count for the |
---|
100 | * session has been decremented. (If the failure was |
---|
101 | * fatal, then SoupSession itself will have dealt |
---|
102 | * with cancelling any pending messages for that |
---|
103 | * host, so we don't need to worry about that here.) |
---|
104 | * However, there may be other messages in the |
---|
105 | * queue that were waiting for the connection count |
---|
106 | * to go down, so run the queue now. |
---|
107 | */ |
---|
108 | run_queue (sa, FALSE); |
---|
109 | return; |
---|
110 | } |
---|
111 | |
---|
112 | g_signal_connect (conn, "disconnected", |
---|
113 | G_CALLBACK (connection_closed), sa); |
---|
114 | |
---|
115 | /* @conn has been marked reserved by SoupSession, but we don't |
---|
116 | * actually have any specific message in mind for it. (In |
---|
117 | * particular, the message we were originally planning to |
---|
118 | * queue on it may have already been queued on some other |
---|
119 | * connection that became available while we were waiting for |
---|
120 | * this one to connect.) So we release the connection into the |
---|
121 | * idle pool and then just run the queue and see what happens. |
---|
122 | */ |
---|
123 | soup_connection_release (conn); |
---|
124 | run_queue (sa, FALSE); |
---|
125 | } |
---|
126 | |
---|
127 | static gboolean |
---|
128 | run_queue (SoupSessionAsync *sa, gboolean try_pruning) |
---|
129 | { |
---|
130 | SoupSession *session = SOUP_SESSION (sa); |
---|
131 | SoupMessageQueueIter iter; |
---|
132 | SoupMessage *msg; |
---|
133 | SoupConnection *conn; |
---|
134 | gboolean should_prune = FALSE, started_any = FALSE, is_new; |
---|
135 | |
---|
136 | /* FIXME: prefer CONNECTING messages */ |
---|
137 | |
---|
138 | try_again: |
---|
139 | for (msg = soup_message_queue_first (session->queue, &iter); msg; msg = soup_message_queue_next (session->queue, &iter)) { |
---|
140 | |
---|
141 | if (!SOUP_MESSAGE_IS_STARTING (msg)) |
---|
142 | continue; |
---|
143 | |
---|
144 | conn = soup_session_get_connection (session, msg, |
---|
145 | &should_prune, &is_new); |
---|
146 | if (!conn) |
---|
147 | continue; |
---|
148 | |
---|
149 | if (is_new) { |
---|
150 | soup_connection_connect_async (conn, got_connection, |
---|
151 | session); |
---|
152 | } else |
---|
153 | soup_connection_send_request (conn, msg); |
---|
154 | |
---|
155 | started_any = TRUE; |
---|
156 | } |
---|
157 | |
---|
158 | if (try_pruning && should_prune && !started_any) { |
---|
159 | /* We didn't manage to start any message, but there is |
---|
160 | * at least one message in the queue that could be |
---|
161 | * sent if we pruned an idle connection from some |
---|
162 | * other server. |
---|
163 | */ |
---|
164 | if (soup_session_try_prune_connection (session)) { |
---|
165 | try_pruning = FALSE; |
---|
166 | goto try_again; |
---|
167 | } |
---|
168 | } |
---|
169 | |
---|
170 | return started_any; |
---|
171 | } |
---|
172 | |
---|
173 | static void |
---|
174 | request_restarted (SoupMessage *req, gpointer sa) |
---|
175 | { |
---|
176 | run_queue (sa, FALSE); |
---|
177 | } |
---|
178 | |
---|
179 | static void |
---|
180 | final_finished (SoupMessage *req, gpointer user_data) |
---|
181 | { |
---|
182 | SoupSessionAsync *sa = user_data; |
---|
183 | |
---|
184 | if (!SOUP_MESSAGE_IS_STARTING (req)) { |
---|
185 | g_signal_handlers_disconnect_by_func (req, final_finished, sa); |
---|
186 | g_object_unref (req); |
---|
187 | } |
---|
188 | |
---|
189 | run_queue (sa, FALSE); |
---|
190 | } |
---|
191 | |
---|
192 | static void |
---|
193 | queue_message (SoupSession *session, SoupMessage *req, |
---|
194 | SoupMessageCallbackFn callback, gpointer user_data) |
---|
195 | { |
---|
196 | SoupSessionAsync *sa = SOUP_SESSION_ASYNC (session); |
---|
197 | |
---|
198 | g_signal_connect (req, "restarted", |
---|
199 | G_CALLBACK (request_restarted), sa); |
---|
200 | |
---|
201 | if (callback) { |
---|
202 | g_signal_connect (req, "finished", |
---|
203 | G_CALLBACK (callback), user_data); |
---|
204 | } |
---|
205 | g_signal_connect_after (req, "finished", |
---|
206 | G_CALLBACK (final_finished), sa); |
---|
207 | |
---|
208 | SOUP_SESSION_CLASS (parent_class)->queue_message (session, req, |
---|
209 | callback, user_data); |
---|
210 | |
---|
211 | run_queue (sa, TRUE); |
---|
212 | } |
---|
213 | |
---|
214 | static guint |
---|
215 | send_message (SoupSession *session, SoupMessage *req) |
---|
216 | { |
---|
217 | /* Balance out the unref that final_finished will do */ |
---|
218 | g_object_ref (req); |
---|
219 | |
---|
220 | queue_message (session, req, NULL, NULL); |
---|
221 | |
---|
222 | while (req->status != SOUP_MESSAGE_STATUS_FINISHED && |
---|
223 | !SOUP_STATUS_IS_TRANSPORT_ERROR (req->status_code)) |
---|
224 | g_main_iteration (TRUE); |
---|
225 | |
---|
226 | return req->status_code; |
---|
227 | } |
---|