Merge lp:~mhr3/dee/add-changesets into lp:dee
- add-changesets
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Paweł Stołowski |
Approved revision: | 430 |
Merged at revision: | 429 |
Proposed branch: | lp:~mhr3/dee/add-changesets |
Merge into: | lp:dee |
Diff against target: |
956 lines (+510/-21) 15 files modified
configure.ac (+1/-1) debian/changelog (+6/-0) debian/libdee-1.0-4.symbols (+2/-0) src/dee-filter-model.c (+55/-0) src/dee-model.c (+111/-0) src/dee-model.h (+12/-4) src/dee-proxy-model.c (+61/-1) src/dee-sequence-model.c (+3/-3) src/dee-serializable-model.c (+52/-0) src/dee-shared-model.c (+5/-2) tests/test-client-server.c (+15/-0) tests/test-filter-model.c (+138/-8) tests/test-model-interactions.c (+15/-0) tests/test-resource-manager.c (+30/-2) vapi/dee-1.0.vapi (+4/-0) |
To merge this branch: | bzr merge lp:~mhr3/dee/add-changesets |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Paweł Stołowski (community) | Approve | ||
PS Jenkins bot (community) | continuous-integration | Approve | |
Review via email:
|
Commit message
Add support for changesets to DeeModel API.
Description of the change
Add support for changesets to DeeModel API.
This will allow us to make dee models more compatible with the way Qt models work and therefore enable us to implement more efficient Qt model wrapper for DeeModel.

PS Jenkins bot (ps-jenkins) wrote : | # |

PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:429
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://

Paweł Stołowski (stolowski) wrote : | # |
Great stuff, looks good, some suggestions though:
1) I think it would make sense to implement a rudimentary check for dee_model_
2) Can you extend the tests to check that "started" signal arrives before "end"?
- 430. By Michal Hruby
-
Act on review comments

Michal Hruby (mhr3) wrote : | # |
> Great stuff, looks good, some suggestions though:
>
> 1) I think it would make sense to implement a rudimentary check for
> dee_model_
> called in a sequence, e.g. by maintaining an internal flag, so that calling
> end/begin or begin/begin etc. would give a warning.
>
> 2) Can you extend the tests to check that "started" signal arrives before
> "end"?
Done.

PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:430
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://

Paweł Stołowski (stolowski) wrote : | # |
Yay, awesome! +1
Preview Diff
1 | === modified file 'configure.ac' |
2 | --- configure.ac 2013-07-19 10:13:05 +0000 |
3 | +++ configure.ac 2013-09-16 18:51:54 +0000 |
4 | @@ -5,7 +5,7 @@ |
5 | # Don't forget to check also GIR_VERSION |
6 | m4_define([dee_major], [1]) |
7 | m4_define([dee_minor], [2]) |
8 | -m4_define([dee_micro], [6]) |
9 | +m4_define([dee_micro], [7]) |
10 | m4_define([dee_api], |
11 | [dee_major.dee_minor]) |
12 | m4_define([dee_version], |
13 | |
14 | === modified file 'debian/changelog' |
15 | --- debian/changelog 2013-09-04 02:32:11 +0000 |
16 | +++ debian/changelog 2013-09-16 18:51:54 +0000 |
17 | @@ -1,3 +1,9 @@ |
18 | +dee (1.2.7-0ubuntu1) UNRELEASED; urgency=low |
19 | + |
20 | + * Added begin and end changeset API. |
21 | + |
22 | + -- Michal Hruby <michal.hruby@canonical.com> Tue, 10 Sep 2013 11:59:21 +0100 |
23 | + |
24 | dee (1.2.6+13.10.20130904-0ubuntu1) saucy; urgency=low |
25 | |
26 | [ Michal Hruby ] |
27 | |
28 | === modified file 'debian/libdee-1.0-4.symbols' |
29 | --- debian/libdee-1.0-4.symbols 2013-05-31 04:30:18 +0000 |
30 | +++ debian/libdee-1.0-4.symbols 2013-09-16 18:51:54 +0000 |
31 | @@ -54,12 +54,14 @@ |
32 | dee_index_lookup_one@Base 0.5.16 |
33 | dee_model_append@Base 0.5.2 |
34 | dee_model_append_row@Base 0.5.2 |
35 | + dee_model_begin_changeset@Base 0replaceme |
36 | dee_model_build_named_row@Base 1.2.0daily12.12.05 |
37 | dee_model_build_named_row_sunk@Base 1.2.0daily12.12.05 |
38 | dee_model_build_named_row_valist@Base 1.2.0daily12.12.05 |
39 | dee_model_build_row@Base 0.5.2 |
40 | dee_model_clear@Base 0.5.2 |
41 | dee_model_clear_tag@Base 0.5.12 |
42 | + dee_model_end_changeset@Base 0replaceme |
43 | dee_model_find_row_sorted@Base 1.0.0 |
44 | dee_model_find_row_sorted_with_sizes@Base 1.0.14 |
45 | dee_model_find_sorted@Base 1.0.0 |
46 | |
47 | === modified file 'src/dee-filter-model.c' |
48 | --- src/dee-filter-model.c 2013-01-04 17:53:43 +0000 |
49 | +++ src/dee-filter-model.c 2013-09-16 18:51:54 +0000 |
50 | @@ -97,6 +97,8 @@ |
51 | gulong on_orig_row_added_id; |
52 | gulong on_orig_row_removed_id; |
53 | gulong on_orig_row_changed_id; |
54 | + gulong on_orig_changeset_started_id; |
55 | + gulong on_orig_changeset_finished_id; |
56 | }; |
57 | |
58 | enum |
59 | @@ -162,6 +164,12 @@ |
60 | static void on_orig_model_row_changed (DeeFilterModel *self, |
61 | DeeModelIter *iter); |
62 | |
63 | +static void on_orig_model_changeset_started (DeeFilterModel *self, |
64 | + DeeModel *iter); |
65 | + |
66 | +static void on_orig_model_changeset_finished (DeeFilterModel *self, |
67 | + DeeModel *iter); |
68 | + |
69 | /* GObject stuff */ |
70 | static void |
71 | dee_filter_model_finalize (GObject *object) |
72 | @@ -192,10 +200,16 @@ |
73 | g_signal_handler_disconnect (priv->orig_model, priv->on_orig_row_removed_id); |
74 | if (priv->on_orig_row_changed_id != 0) |
75 | g_signal_handler_disconnect (priv->orig_model, priv->on_orig_row_changed_id); |
76 | + if (priv->on_orig_changeset_started_id != 0) |
77 | + g_signal_handler_disconnect (priv->orig_model, priv->on_orig_changeset_started_id); |
78 | + if (priv->on_orig_changeset_finished_id != 0) |
79 | + g_signal_handler_disconnect (priv->orig_model, priv->on_orig_changeset_finished_id); |
80 | |
81 | priv->on_orig_row_added_id = 0; |
82 | priv->on_orig_row_removed_id = 0; |
83 | priv->on_orig_row_changed_id = 0; |
84 | + priv->on_orig_changeset_started_id = 0; |
85 | + priv->on_orig_changeset_finished_id = 0; |
86 | |
87 | if (priv->orig_model) |
88 | { |
89 | @@ -242,6 +256,16 @@ |
90 | g_signal_connect_swapped (priv->orig_model, "row-changed", |
91 | G_CALLBACK (on_orig_model_row_changed), object); |
92 | |
93 | + priv->on_orig_changeset_started_id = |
94 | + g_signal_connect_swapped (priv->orig_model, "changeset-started", |
95 | + G_CALLBACK (on_orig_model_changeset_started), |
96 | + object); |
97 | + |
98 | + priv->on_orig_changeset_finished_id = |
99 | + g_signal_connect_swapped (priv->orig_model, "changeset-finished", |
100 | + G_CALLBACK (on_orig_model_changeset_finished), |
101 | + object); |
102 | + |
103 | if (G_OBJECT_CLASS (dee_filter_model_parent_class)->constructed) |
104 | G_OBJECT_CLASS (dee_filter_model_parent_class)->constructed (object); |
105 | } |
106 | @@ -327,6 +351,8 @@ |
107 | priv->on_orig_row_added_id = 0; |
108 | priv->on_orig_row_removed_id = 0; |
109 | priv->on_orig_row_changed_id = 0; |
110 | + priv->on_orig_changeset_started_id = 0; |
111 | + priv->on_orig_changeset_finished_id = 0; |
112 | } |
113 | |
114 | static void |
115 | @@ -681,6 +707,35 @@ |
116 | } |
117 | } |
118 | |
119 | +static void |
120 | +on_orig_model_changeset_started (DeeFilterModel *self, |
121 | + DeeModel *model) |
122 | +{ |
123 | + DeeFilterModelPrivate *priv; |
124 | + |
125 | + priv = self->priv; |
126 | + |
127 | + if (priv->ignore_orig_signals) |
128 | + return; |
129 | + |
130 | + /* this can end up being an empty changeset, but that's ok */ |
131 | + g_signal_emit_by_name (self, "changeset-started"); |
132 | +} |
133 | + |
134 | +static void |
135 | +on_orig_model_changeset_finished (DeeFilterModel *self, |
136 | + DeeModel *model) |
137 | +{ |
138 | + DeeFilterModelPrivate *priv; |
139 | + |
140 | + priv = self->priv; |
141 | + |
142 | + if (priv->ignore_orig_signals) |
143 | + return; |
144 | + |
145 | + g_signal_emit_by_name (self, "changeset-finished"); |
146 | +} |
147 | + |
148 | /* |
149 | * DeeModel Interface Implementation |
150 | */ |
151 | |
152 | === modified file 'src/dee-model.c' |
153 | --- src/dee-model.c 2013-03-08 15:03:27 +0000 |
154 | +++ src/dee-model.c 2013-09-16 18:51:54 +0000 |
155 | @@ -98,6 +98,8 @@ |
156 | DEE_MODEL_SIGNAL_ROW_ADDED, |
157 | DEE_MODEL_SIGNAL_ROW_REMOVED, |
158 | DEE_MODEL_SIGNAL_ROW_CHANGED, |
159 | + DEE_MODEL_SIGNAL_CHANGESET_STARTED, |
160 | + DEE_MODEL_SIGNAL_CHANGESET_FINISHED, |
161 | |
162 | DEE_MODEL_LAST_SIGNAL |
163 | }; |
164 | @@ -229,6 +231,44 @@ |
165 | g_cclosure_marshal_VOID__BOXED, |
166 | G_TYPE_NONE, 1, |
167 | DEE_TYPE_MODEL_ITER); |
168 | + |
169 | + /** |
170 | + * DeeModel::changeset-started |
171 | + * @self: the #DeeModel on which the signal is emitted |
172 | + * |
173 | + * Connect to this signal to be notified when a changeset that can contain |
174 | + * multiple row additions / changes / removals is about to be committed |
175 | + * to the model. |
176 | + * Note that not all model implementations use the changeset approach and |
177 | + * you might still get a row change signal outside of changeset-started and |
178 | + * changeset-finished signals. It also isn't guaranteed that a changeset |
179 | + * would always be non-empty. |
180 | + */ |
181 | + dee_model_signals[DEE_MODEL_SIGNAL_CHANGESET_STARTED] = |
182 | + g_signal_new ("changeset-started", |
183 | + DEE_TYPE_MODEL, |
184 | + G_SIGNAL_RUN_LAST, |
185 | + G_STRUCT_OFFSET (DeeModelIface, changeset_started), |
186 | + NULL, NULL, |
187 | + g_cclosure_marshal_VOID__VOID, |
188 | + G_TYPE_NONE, 0); |
189 | + |
190 | + /** |
191 | + * DeeModel::changeset-finished |
192 | + * @self: the #DeeModel on which the signal is emitted |
193 | + * |
194 | + * Connect to this signal to be notified when a changeset that can contain |
195 | + * multiple row additions / changes / removals has been committed |
196 | + * to the model. |
197 | + */ |
198 | + dee_model_signals[DEE_MODEL_SIGNAL_CHANGESET_FINISHED] = |
199 | + g_signal_new ("changeset-finished", |
200 | + DEE_TYPE_MODEL, |
201 | + G_SIGNAL_RUN_LAST, |
202 | + G_STRUCT_OFFSET (DeeModelIface, changeset_finished), |
203 | + NULL, NULL, |
204 | + g_cclosure_marshal_VOID__VOID, |
205 | + G_TYPE_NONE, 0); |
206 | } |
207 | |
208 | /** |
209 | @@ -646,6 +686,77 @@ |
210 | return (* iface->get_n_rows) (self); |
211 | } |
212 | |
213 | +/** |
214 | + * dee_model_begin_changeset: |
215 | + * @self: a #DeeModel |
216 | + * |
217 | + * Notify listeners that the model is about to be changed, which means that |
218 | + * multiple row additions / changes / removals will follow. |
219 | + * The default implementation of this method will emit |
220 | + * the ::changeset-started signal. |
221 | + * |
222 | + * It is not stricly necessary to enclose every change to a model |
223 | + * in a dee_model_begin_changeset() and dee_model_end_changeset() calls, but |
224 | + * doing so is highly recommended and allows implementing various optimizations. |
225 | + * |
226 | + * The usual way to perform multiple changes to a model is as follows: |
227 | + * |
228 | + * <programlisting> |
229 | + * void update_model (DeeModel *model) |
230 | + * { |
231 | + * GVariant **added_row_data1 = ...; |
232 | + * GVariant **added_row_data2 = ...; |
233 | + * |
234 | + * dee_model_begin_changeset (model); |
235 | + * |
236 | + * dee_model_remove (model, dee_model_get_first_iter (model)); |
237 | + * dee_model_append_row (model, added_row_data1); |
238 | + * dee_model_append_row (model, added_row_data2); |
239 | + * |
240 | + * dee_model_end_changeset (model); |
241 | + * } |
242 | + * </programlisting> |
243 | + */ |
244 | +void |
245 | +dee_model_begin_changeset (DeeModel *self) |
246 | +{ |
247 | + DeeModelIface *iface; |
248 | + |
249 | + g_return_if_fail (DEE_IS_MODEL (self)); |
250 | + |
251 | + iface = DEE_MODEL_GET_IFACE (self); |
252 | + |
253 | + if (iface->begin_changeset) |
254 | + (* iface->begin_changeset) (self); |
255 | + else |
256 | + g_signal_emit (self, dee_model_signals[DEE_MODEL_SIGNAL_CHANGESET_STARTED], 0); |
257 | +} |
258 | + |
259 | +/** |
260 | + * dee_model_end_changeset: |
261 | + * @self: a #DeeModel |
262 | + * |
263 | + * Notify listeners that all changes have been committed to the model. |
264 | + * The default implementation of this method will emit |
265 | + * the ::changeset-finished signal. |
266 | + * |
267 | + * See also dee_model_begin_changeset(). |
268 | + */ |
269 | +void |
270 | +dee_model_end_changeset (DeeModel *self) |
271 | +{ |
272 | + DeeModelIface *iface; |
273 | + |
274 | + g_return_if_fail (DEE_IS_MODEL (self)); |
275 | + |
276 | + iface = DEE_MODEL_GET_IFACE (self); |
277 | + |
278 | + if (iface->end_changeset) |
279 | + (* iface->end_changeset) (self); |
280 | + else |
281 | + g_signal_emit (self, dee_model_signals[DEE_MODEL_SIGNAL_CHANGESET_FINISHED], 0); |
282 | +} |
283 | + |
284 | static GVariant* |
285 | collect_variant (const gchar* col_schema, va_list *args) |
286 | { |
287 | |
288 | === modified file 'src/dee-model.h' |
289 | --- src/dee-model.h 2012-11-27 17:49:57 +0000 |
290 | +++ src/dee-model.h 2013-09-16 18:51:54 +0000 |
291 | @@ -266,14 +266,18 @@ |
292 | DeeModelIter *iter, |
293 | GVariant **out_row_members); |
294 | |
295 | + void (*begin_changeset) (DeeModel *self); |
296 | + |
297 | + void (*end_changeset) (DeeModel *self); |
298 | + |
299 | + void (*changeset_started) (DeeModel *self); |
300 | + |
301 | + void (*changeset_finished) (DeeModel *self); |
302 | + |
303 | /*< private >*/ |
304 | void (*_dee_model_1) (void); |
305 | void (*_dee_model_2) (void); |
306 | void (*_dee_model_3) (void); |
307 | - void (*_dee_model_4) (void); |
308 | - void (*_dee_model_5) (void); |
309 | - void (*_dee_model_6) (void); |
310 | - void (*_dee_model_7) (void); |
311 | }; |
312 | |
313 | GType dee_model_iter_get_type (void); |
314 | @@ -494,6 +498,10 @@ |
315 | DeeModelIter *iter, |
316 | DeeModelTag *tag); |
317 | |
318 | +void dee_model_begin_changeset (DeeModel *self); |
319 | + |
320 | +void dee_model_end_changeset (DeeModel *self); |
321 | + |
322 | GVariant** dee_model_build_row (DeeModel *self, |
323 | GVariant **out_row_members, |
324 | ...); |
325 | |
326 | === modified file 'src/dee-proxy-model.c' |
327 | --- src/dee-proxy-model.c 2013-01-04 17:53:43 +0000 |
328 | +++ src/dee-proxy-model.c 2013-09-16 18:51:54 +0000 |
329 | @@ -81,6 +81,8 @@ |
330 | gulong row_added_handler; |
331 | gulong row_removed_handler; |
332 | gulong row_changed_handler; |
333 | + gulong changeset_started_handler; |
334 | + gulong changeset_finished_handler; |
335 | }; |
336 | |
337 | #define DEE_PROXY_MODEL_BACK_END(model) (DEE_PROXY_MODEL(model)->priv->back_end) |
338 | @@ -233,6 +235,10 @@ |
339 | DeeModelTag *tag, |
340 | gpointer value); |
341 | |
342 | +static void dee_proxy_model_begin_changeset (DeeModel *self); |
343 | + |
344 | +static void dee_proxy_model_end_changeset (DeeModel *self); |
345 | + |
346 | /* |
347 | * Callbacks for relaying signals from the back end model |
348 | */ |
349 | @@ -245,6 +251,12 @@ |
350 | static void on_back_end_row_changed (DeeProxyModel *self, |
351 | DeeModelIter *iter); |
352 | |
353 | +static void on_back_end_changeset_started (DeeProxyModel *self, |
354 | + DeeModel *model); |
355 | + |
356 | +static void on_back_end_changeset_finished (DeeProxyModel *self, |
357 | + DeeModel *model); |
358 | + |
359 | /* |
360 | * Overrides for DeeSerializableModel |
361 | */ |
362 | @@ -270,6 +282,10 @@ |
363 | g_signal_handler_disconnect (priv->back_end, priv->row_removed_handler); |
364 | if (priv->row_changed_handler != 0) |
365 | g_signal_handler_disconnect (priv->back_end, priv->row_changed_handler); |
366 | + if (priv->changeset_started_handler != 0) |
367 | + g_signal_handler_disconnect (priv->back_end, priv->changeset_started_handler); |
368 | + if (priv->changeset_finished_handler != 0) |
369 | + g_signal_handler_disconnect (priv->back_end, priv->changeset_finished_handler); |
370 | |
371 | g_object_unref (priv->back_end); |
372 | } |
373 | @@ -302,6 +318,16 @@ |
374 | priv->row_changed_handler = |
375 | g_signal_connect_swapped (priv->back_end, "row-changed", |
376 | G_CALLBACK (on_back_end_row_changed), object); |
377 | + |
378 | + priv->changeset_started_handler = |
379 | + g_signal_connect_swapped (priv->back_end, "changeset-started", |
380 | + G_CALLBACK (on_back_end_changeset_started), |
381 | + object); |
382 | + |
383 | + priv->changeset_finished_handler = |
384 | + g_signal_connect_swapped (priv->back_end, "changeset-finished", |
385 | + G_CALLBACK (on_back_end_changeset_finished), |
386 | + object); |
387 | } |
388 | |
389 | /* GObjectClass has NULL 'constructed' member, but we add this check for |
390 | @@ -407,7 +433,7 @@ |
391 | * from the back end model. |
392 | * You will most likely want to set this property to false |
393 | * if the implementation manipulates with the rows in the model and keep |
394 | - * track of seqnums. |
395 | + * track of seqnums yourself. |
396 | **/ |
397 | pspec = g_param_spec_boolean ("inherit-seqnums", "Inherit seqnums", |
398 | "Whether or not to inherit seqnums", |
399 | @@ -462,6 +488,8 @@ |
400 | iface->register_tag = dee_proxy_model_register_tag; |
401 | iface->get_tag = dee_proxy_model_get_tag; |
402 | iface->set_tag = dee_proxy_model_set_tag; |
403 | + iface->begin_changeset = dee_proxy_model_begin_changeset; |
404 | + iface->end_changeset = dee_proxy_model_end_changeset; |
405 | |
406 | iface->register_vardict_schema = dee_proxy_model_register_vardict_schema; |
407 | iface->get_vardict_schema = dee_proxy_model_get_vardict_schema; |
408 | @@ -479,6 +507,8 @@ |
409 | priv->row_added_handler = 0; |
410 | priv->row_removed_handler = 0; |
411 | priv->row_changed_handler = 0; |
412 | + priv->changeset_started_handler = 0; |
413 | + priv->changeset_finished_handler = 0; |
414 | } |
415 | |
416 | /* |
417 | @@ -891,6 +921,22 @@ |
418 | return dee_model_set_tag (DEE_PROXY_MODEL_BACK_END (self), iter, tag, value); |
419 | } |
420 | |
421 | +static void |
422 | +dee_proxy_model_begin_changeset (DeeModel *self) |
423 | +{ |
424 | + g_return_if_fail (DEE_IS_PROXY_MODEL (self)); |
425 | + |
426 | + dee_model_begin_changeset (DEE_PROXY_MODEL_BACK_END (self)); |
427 | +} |
428 | + |
429 | +static void |
430 | +dee_proxy_model_end_changeset (DeeModel *self) |
431 | +{ |
432 | + g_return_if_fail (DEE_IS_PROXY_MODEL (self)); |
433 | + |
434 | + dee_model_end_changeset (DEE_PROXY_MODEL_BACK_END (self)); |
435 | +} |
436 | + |
437 | /* |
438 | * Relay signals from back end |
439 | */ |
440 | @@ -916,6 +962,20 @@ |
441 | g_signal_emit_by_name (self, "row-changed", iter); |
442 | } |
443 | |
444 | +static void |
445 | +on_back_end_changeset_started (DeeProxyModel *self, |
446 | + DeeModel *model) |
447 | +{ |
448 | + g_signal_emit_by_name (self, "changeset-started"); |
449 | +} |
450 | + |
451 | +static void |
452 | +on_back_end_changeset_finished (DeeProxyModel *self, |
453 | + DeeModel *model) |
454 | +{ |
455 | + g_signal_emit_by_name (self, "changeset-finished"); |
456 | +} |
457 | + |
458 | /* |
459 | * Overrides for DeeSerializableModel |
460 | */ |
461 | |
462 | === modified file 'src/dee-sequence-model.c' |
463 | --- src/dee-sequence-model.c 2012-02-28 00:34:40 +0000 |
464 | +++ src/dee-sequence-model.c 2013-09-16 18:51:54 +0000 |
465 | @@ -280,9 +280,9 @@ |
466 | obj_class->get_property = dee_sequence_model_get_property; |
467 | |
468 | /* Find signal ids for the model modification signals */ |
469 | - sigid_row_added = g_signal_lookup ("row-added", DEE_TYPE_SEQUENCE_MODEL); |
470 | - sigid_row_removed = g_signal_lookup ("row-removed", DEE_TYPE_SEQUENCE_MODEL); |
471 | - sigid_row_changed = g_signal_lookup ("row-changed", DEE_TYPE_SEQUENCE_MODEL); |
472 | + sigid_row_added = g_signal_lookup ("row-added", DEE_TYPE_MODEL); |
473 | + sigid_row_removed = g_signal_lookup ("row-removed", DEE_TYPE_MODEL); |
474 | + sigid_row_changed = g_signal_lookup ("row-changed", DEE_TYPE_MODEL); |
475 | |
476 | /* Add private data */ |
477 | g_type_class_add_private (obj_class, sizeof (DeeSequenceModelPrivate)); |
478 | |
479 | === modified file 'src/dee-serializable-model.c' |
480 | --- src/dee-serializable-model.c 2013-07-21 22:29:56 +0000 |
481 | +++ src/dee-serializable-model.c 2013-09-16 18:51:54 +0000 |
482 | @@ -77,6 +77,7 @@ |
483 | gchar **column_names; // NULL terminated |
484 | guint32 *column_name_hashes; |
485 | GHashTable *field_schemas; |
486 | + gboolean inside_changeset; |
487 | }; |
488 | |
489 | typedef struct _FieldSchemaInfo FieldSchemaInfo; |
490 | @@ -238,6 +239,13 @@ |
491 | static gboolean dee_serializable_model_is_last (DeeModel *self, |
492 | DeeModelIter *iter); |
493 | |
494 | +static void dee_serializable_model_begin_changeset (DeeModel *self); |
495 | + |
496 | +static void dee_serializable_model_end_changeset (DeeModel *self); |
497 | + |
498 | +static guint sigid_changeset_started = 0; |
499 | +static guint sigid_changeset_finished = 0; |
500 | + |
501 | /* FieldSchemaInfo methods */ |
502 | static FieldSchemaInfo* |
503 | field_schema_info_new (const gchar *schema, guint column) |
504 | @@ -352,6 +360,9 @@ |
505 | klass->set_seqnum = dee_serializable_model_set_seqnum_real; |
506 | klass->inc_seqnum = dee_serializable_model_inc_seqnum_real; |
507 | |
508 | + sigid_changeset_started = g_signal_lookup ("changeset-started", DEE_TYPE_MODEL); |
509 | + sigid_changeset_finished = g_signal_lookup ("changeset-finished", DEE_TYPE_MODEL); |
510 | + |
511 | /* Add private data */ |
512 | g_type_class_add_private (obj_class, sizeof (DeeSerializableModelPrivate)); |
513 | } |
514 | @@ -1328,6 +1339,44 @@ |
515 | } |
516 | } |
517 | |
518 | +static void |
519 | +dee_serializable_model_begin_changeset (DeeModel *self) |
520 | +{ |
521 | + DeeSerializableModelPrivate *priv; |
522 | + |
523 | + priv = DEE_SERIALIZABLE_MODEL (self)->priv; |
524 | + |
525 | + if (!priv->inside_changeset) |
526 | + { |
527 | + priv->inside_changeset = TRUE; |
528 | + g_signal_emit (self, sigid_changeset_started, 0); |
529 | + } |
530 | + else |
531 | + { |
532 | + g_warning ("Ignored call to dee_model_begin_changeset, finish " |
533 | + "the current changeset using dee_model_end_changeset first"); |
534 | + } |
535 | +} |
536 | + |
537 | +static void |
538 | +dee_serializable_model_end_changeset (DeeModel *self) |
539 | +{ |
540 | + DeeSerializableModelPrivate *priv; |
541 | + |
542 | + priv = DEE_SERIALIZABLE_MODEL (self)->priv; |
543 | + |
544 | + if (priv->inside_changeset) |
545 | + { |
546 | + priv->inside_changeset = FALSE; |
547 | + g_signal_emit (self, sigid_changeset_finished, 0); |
548 | + } |
549 | + else |
550 | + { |
551 | + g_warning ("Ignored call to dee_model_end_changeset, " |
552 | + "dee_model_begin_changeset has to be called first"); |
553 | + } |
554 | +} |
555 | + |
556 | static DeeModelTag* |
557 | dee_serializable_model_register_tag (DeeModel *self, |
558 | GDestroyNotify tag_destroy) |
559 | @@ -1635,6 +1684,9 @@ |
560 | dee_serializable_model_register_vardict_schema; |
561 | iface->get_vardict_schema = |
562 | dee_serializable_model_get_vardict_schema; |
563 | + |
564 | + iface->begin_changeset = dee_serializable_model_begin_changeset; |
565 | + iface->end_changeset = dee_serializable_model_end_changeset; |
566 | } |
567 | |
568 | static void |
569 | |
570 | === modified file 'src/dee-shared-model.c' |
571 | --- src/dee-shared-model.c 2013-07-21 22:29:56 +0000 |
572 | +++ src/dee-shared-model.c 2013-09-16 18:51:54 +0000 |
573 | @@ -1477,6 +1477,7 @@ |
574 | trace_object (self, "Applying transaction of %i rows", n_rows); |
575 | |
576 | /* Phew. Finally. We're ready to merge the changes */ |
577 | + g_signal_emit_by_name (self, "changeset-started"); |
578 | g_signal_emit (self, _signals[BEGIN_TRANSACTION], 0, seqnum_before, seqnum_after); |
579 | priv->suppress_remote_signals = TRUE; |
580 | for (i = 0; i < n_rows; i++) /* Begin outer loop */ |
581 | @@ -1513,8 +1514,9 @@ |
582 | g_critical ("Commit from %s contains rows of illegal length. " |
583 | "The model may have been left in a dirty state", |
584 | sender_name); |
585 | - // FIXME: cleanup |
586 | - return; |
587 | + /* cleanup */ |
588 | + g_variant_unref (row); |
589 | + continue; |
590 | } |
591 | |
592 | /* Read the row cells into our stack allocated row buffer. |
593 | @@ -1570,6 +1572,7 @@ |
594 | priv->last_committed_seqnum = seqnum_after; |
595 | |
596 | g_signal_emit (self, _signals[END_TRANSACTION], 0, seqnum_before, seqnum_after); |
597 | + g_signal_emit_by_name (self, "changeset-finished"); |
598 | } |
599 | |
600 | static void |
601 | |
602 | === modified file 'tests/test-client-server.c' |
603 | --- tests/test-client-server.c 2012-02-28 10:56:27 +0000 |
604 | +++ tests/test-client-server.c 2013-09-16 18:51:54 +0000 |
605 | @@ -820,6 +820,12 @@ |
606 | } |
607 | |
608 | static void |
609 | +changeset_signal (DeeModel *model, gboolean *value) |
610 | +{ |
611 | + *value = TRUE; |
612 | +} |
613 | + |
614 | +static void |
615 | test_remote_append (Fixture *fix, gconstpointer data) |
616 | { |
617 | if (gtx_wait_for_signal (G_OBJECT (fix->model), TIMEOUT, "notify::synchronized", NULL)) |
618 | @@ -829,6 +835,13 @@ |
619 | g_assert (dee_shared_model_is_leader (DEE_SHARED_MODEL (fix->model))); |
620 | g_assert_cmpuint (dee_model_get_n_rows (fix->model), ==, 0); |
621 | |
622 | + gboolean got_changeset_start = FALSE; |
623 | + gboolean got_changeset_finish = FALSE; |
624 | + g_signal_connect (fix->model, "changeset-started", |
625 | + G_CALLBACK (changeset_signal), &got_changeset_start); |
626 | + g_signal_connect (fix->model, "changeset-finished", |
627 | + G_CALLBACK (changeset_signal), &got_changeset_finish); |
628 | + |
629 | if (gtx_wait_for_command (TESTDIR, |
630 | MODEL_HELPER (append1, MODEL_NAME), |
631 | 2000)) |
632 | @@ -838,6 +851,8 @@ |
633 | |
634 | /* There should be a new row in the model */ |
635 | g_assert_cmpuint (dee_model_get_n_rows (fix->model), ==, 1); |
636 | + g_assert (got_changeset_start); |
637 | + g_assert (got_changeset_finish); |
638 | } |
639 | |
640 | static void |
641 | |
642 | === modified file 'tests/test-filter-model.c' |
643 | --- tests/test-filter-model.c 2013-05-31 12:38:11 +0000 |
644 | +++ tests/test-filter-model.c 2013-09-16 18:51:54 +0000 |
645 | @@ -28,8 +28,16 @@ |
646 | |
647 | } FilterFixture; |
648 | |
649 | -static void setup (FilterFixture *fix, gconstpointer data); |
650 | -static void teardown (FilterFixture *fix, gconstpointer data); |
651 | +typedef struct |
652 | +{ |
653 | + gint first; |
654 | + gint second; |
655 | + |
656 | +} TwoIntsTuple; |
657 | + |
658 | +static void setup (FilterFixture *fix, gconstpointer data); |
659 | +static void setup_empty (FilterFixture *fix, gconstpointer data); |
660 | +static void teardown (FilterFixture *fix, gconstpointer data); |
661 | |
662 | static void test_empty_orig (FilterFixture *fix, |
663 | gconstpointer data); |
664 | @@ -54,6 +62,9 @@ |
665 | static void test_regex (FilterFixture *fix, |
666 | gconstpointer data); |
667 | |
668 | +static void test_changesets (FilterFixture *fix, |
669 | + gconstpointer data); |
670 | + |
671 | void |
672 | test_filter_model_create_suite (void) |
673 | { |
674 | @@ -78,22 +89,35 @@ |
675 | setup, test_any, teardown); |
676 | g_test_add (DOMAIN"/Regex", FilterFixture, 0, |
677 | setup, test_regex, teardown); |
678 | + g_test_add (DOMAIN"/Changesets", FilterFixture, 0, |
679 | + setup_empty, test_changesets, teardown); |
680 | } |
681 | |
682 | static void |
683 | -setup (FilterFixture *fix, gconstpointer data) |
684 | +setup_empty (FilterFixture *fix, gconstpointer data) |
685 | { |
686 | fix->model = dee_sequence_model_new (); |
687 | dee_model_set_schema (fix->model, "i", "s", NULL); |
688 | |
689 | - dee_model_append (fix->model, 0, "Zero"); |
690 | - dee_model_append (fix->model, 1, "One"); |
691 | - dee_model_append (fix->model, 2, "Two"); |
692 | - |
693 | g_assert (DEE_IS_SEQUENCE_MODEL (fix->model)); |
694 | } |
695 | |
696 | static void |
697 | +add_3rows(DeeModel *model) |
698 | +{ |
699 | + dee_model_append (model, 0, "Zero"); |
700 | + dee_model_append (model, 1, "One"); |
701 | + dee_model_append (model, 2, "Two"); |
702 | +} |
703 | + |
704 | +static void |
705 | +setup (FilterFixture *fix, gconstpointer data) |
706 | +{ |
707 | + setup_empty (fix, data); |
708 | + add_3rows (fix->model); |
709 | +} |
710 | + |
711 | +static void |
712 | teardown (FilterFixture *fix, gconstpointer data) |
713 | { |
714 | g_object_unref (fix->model); |
715 | @@ -282,7 +306,28 @@ |
716 | g_assert_cmpint (27, ==, dee_model_get_int32 (fix->model, iter, 0)); |
717 | g_assert_cmpstr ("TwentySeven", ==, dee_model_get_string (m, iter, 1)); |
718 | g_assert_cmpstr ("TwentySeven", ==, dee_model_get_string (fix->model, iter, 1)); |
719 | - |
720 | + |
721 | + /* And append two more rows to the filtered model, to ensure the order */ |
722 | + dee_model_prepend (m, -1, "MinusOne"); |
723 | + dee_model_append (m, 39, "ThirtyNine"); |
724 | + g_assert_cmpint (3, ==, filter_add_count); |
725 | + g_assert_cmpint (5, ==, orig_add_count); |
726 | + g_assert_cmpint (3, ==, dee_model_get_n_rows (m)); |
727 | + g_assert_cmpint (8, ==, dee_model_get_n_rows (fix->model)); |
728 | + g_assert_cmpuint (3, ==, dee_serializable_model_get_seqnum (m)); |
729 | + iter = dee_model_prev (m, dee_model_get_last_iter (m)); |
730 | + g_assert_cmpint (39, ==, dee_model_get_int32 (m, iter, 0)); |
731 | + g_assert_cmpstr ("ThirtyNine", ==, dee_model_get_string (m, iter, 1)); |
732 | + iter = dee_model_prev (m, iter); |
733 | + g_assert_cmpint (27, ==, dee_model_get_int32 (m, iter, 0)); |
734 | + g_assert_cmpstr ("TwentySeven", ==, dee_model_get_string (m, iter, 1)); |
735 | + iter = dee_model_prev (m, iter); |
736 | + g_assert_cmpint (-1, ==, dee_model_get_int32 (m, iter, 0)); |
737 | + g_assert_cmpstr ("MinusOne", ==, dee_model_get_string (m, iter, 1)); |
738 | + iter = dee_model_prev (fix->model, dee_model_get_last_iter (fix->model)); |
739 | + g_assert_cmpint (39, ==, dee_model_get_int32 (fix->model, iter, 0)); |
740 | + g_assert_cmpstr ("ThirtyNine", ==, dee_model_get_string (fix->model, iter, 1)); |
741 | + |
742 | g_object_unref (m); |
743 | } |
744 | |
745 | @@ -616,3 +661,88 @@ |
746 | _test_orig_ordering (fix, &filter); |
747 | g_regex_unref (regex); |
748 | } |
749 | + |
750 | +static void |
751 | +increment_first (TwoIntsTuple *tuple) |
752 | +{ |
753 | + tuple->first++; |
754 | + // first always has to be incremented before second |
755 | + g_assert (tuple->first > tuple->second); |
756 | +} |
757 | + |
758 | +static void |
759 | +increment_second (TwoIntsTuple *tuple) |
760 | +{ |
761 | + tuple->second++; |
762 | + // second needs to be incremented after first |
763 | + g_assert (tuple->second == tuple->first); |
764 | +} |
765 | + |
766 | +static void |
767 | +test_changesets (FilterFixture *fix, gconstpointer data) |
768 | +{ |
769 | + GRegex *regex; |
770 | + DeeFilter filter; |
771 | + DeeModel *filter_m1; |
772 | + DeeModel *filter_m2; |
773 | + DeeModel *filter_m3; |
774 | + |
775 | + regex = g_regex_new ("^..[eo]", 0, 0, NULL); |
776 | + dee_filter_new_regex (1, regex, &filter); |
777 | + filter_m1 = dee_filter_model_new (fix->model, &filter); |
778 | + g_regex_unref (regex); |
779 | + |
780 | + regex = g_regex_new ("^Z", 0, 0, NULL); |
781 | + dee_filter_new_regex (1, regex, &filter); |
782 | + filter_m2 = dee_filter_model_new (fix->model, &filter); |
783 | + g_regex_unref (regex); |
784 | + |
785 | + regex = g_regex_new ("^X", 0, 0, NULL); |
786 | + dee_filter_new_regex (1, regex, &filter); |
787 | + filter_m3 = dee_filter_model_new (fix->model, &filter); |
788 | + g_regex_unref (regex); |
789 | + |
790 | + g_assert_cmpuint (0, ==, dee_model_get_n_rows (fix->model)); |
791 | + g_assert_cmpuint (0, ==, dee_model_get_n_rows (filter_m1)); |
792 | + g_assert_cmpuint (0, ==, dee_model_get_n_rows (filter_m2)); |
793 | + g_assert_cmpuint (0, ==, dee_model_get_n_rows (filter_m3)); |
794 | + |
795 | + TwoIntsTuple tuple_m0 = { 0, 0 }; |
796 | + TwoIntsTuple tuple_m1 = { 0, 0 }; |
797 | + TwoIntsTuple tuple_m2 = { 0, 0 }; |
798 | + TwoIntsTuple tuple_m3 = { 0, 0 }; |
799 | + |
800 | + g_signal_connect_swapped (fix->model, "changeset-started", |
801 | + G_CALLBACK (increment_first), &tuple_m0); |
802 | + g_signal_connect_swapped (fix->model, "changeset-finished", |
803 | + G_CALLBACK (increment_second), &tuple_m0); |
804 | + g_signal_connect_swapped (filter_m1, "changeset-started", |
805 | + G_CALLBACK (increment_first), &tuple_m1); |
806 | + g_signal_connect_swapped (filter_m1, "changeset-finished", |
807 | + G_CALLBACK (increment_second), &tuple_m1); |
808 | + g_signal_connect_swapped (filter_m2, "changeset-started", |
809 | + G_CALLBACK (increment_first), &tuple_m2); |
810 | + g_signal_connect_swapped (filter_m2, "changeset-finished", |
811 | + G_CALLBACK (increment_second), &tuple_m2); |
812 | + g_signal_connect_swapped (filter_m3, "changeset-started", |
813 | + G_CALLBACK (increment_first), &tuple_m3); |
814 | + g_signal_connect_swapped (filter_m3, "changeset-finished", |
815 | + G_CALLBACK (increment_second), &tuple_m3); |
816 | + |
817 | + dee_model_begin_changeset (fix->model); |
818 | + add_3rows (fix->model); |
819 | + dee_model_end_changeset (fix->model); |
820 | + |
821 | + g_assert_cmpuint (3, ==, dee_model_get_n_rows (fix->model)); |
822 | + g_assert_cmpuint (2, ==, dee_model_get_n_rows (filter_m1)); |
823 | + g_assert_cmpuint (1, ==, dee_model_get_n_rows (filter_m2)); |
824 | + g_assert_cmpuint (0, ==, dee_model_get_n_rows (filter_m3)); |
825 | + g_assert_cmpint (tuple_m0.first, ==, 1); |
826 | + g_assert_cmpint (tuple_m0.second, ==, 1); |
827 | + g_assert_cmpint (tuple_m1.first, ==, 1); |
828 | + g_assert_cmpint (tuple_m1.second, ==, 1); |
829 | + g_assert_cmpint (tuple_m2.first, ==, 1); |
830 | + g_assert_cmpint (tuple_m2.second, ==, 1); |
831 | + g_assert_cmpint (tuple_m3.first, ==, 1); |
832 | + g_assert_cmpint (tuple_m3.second, ==, 1); |
833 | +} |
834 | |
835 | === modified file 'tests/test-model-interactions.c' |
836 | --- tests/test-model-interactions.c 2013-07-16 08:32:59 +0000 |
837 | +++ tests/test-model-interactions.c 2013-09-16 18:51:54 +0000 |
838 | @@ -699,6 +699,12 @@ |
839 | } |
840 | |
841 | static void |
842 | +changeset_signal (DeeModel *model, gboolean *value) |
843 | +{ |
844 | + *value = TRUE; |
845 | +} |
846 | + |
847 | +static void |
848 | test_remote_append (Fixture *fix, gconstpointer data) |
849 | { |
850 | if (gtx_wait_for_signal (G_OBJECT (fix->model), TIMEOUT, "notify::synchronized", NULL)) |
851 | @@ -708,6 +714,13 @@ |
852 | g_assert (dee_shared_model_is_leader (DEE_SHARED_MODEL (fix->model))); |
853 | g_assert_cmpuint (dee_model_get_n_rows (fix->model), ==, 0); |
854 | |
855 | + gboolean got_changeset_start = FALSE; |
856 | + gboolean got_changeset_finish = FALSE; |
857 | + g_signal_connect (fix->model, "changeset-started", |
858 | + G_CALLBACK (changeset_signal), &got_changeset_start); |
859 | + g_signal_connect (fix->model, "changeset-finished", |
860 | + G_CALLBACK (changeset_signal), &got_changeset_finish); |
861 | + |
862 | if (gtx_wait_for_command (TESTDIR, |
863 | MODEL_HELPER (append1, MODEL_NAME), |
864 | 2000)) |
865 | @@ -717,6 +730,8 @@ |
866 | |
867 | /* There should be a new row in the model */ |
868 | g_assert_cmpuint (dee_model_get_n_rows (fix->model), ==, 1); |
869 | + g_assert (got_changeset_start); |
870 | + g_assert (got_changeset_finish); |
871 | } |
872 | |
873 | static void |
874 | |
875 | === modified file 'tests/test-resource-manager.c' |
876 | --- tests/test-resource-manager.c 2011-03-01 20:55:42 +0000 |
877 | +++ tests/test-resource-manager.c 2013-09-16 18:51:54 +0000 |
878 | @@ -38,17 +38,21 @@ |
879 | static void shared_model_teardown (Fixture *fix, gconstpointer data); |
880 | |
881 | static void test_model_persistence (Fixture *fix, gconstpointer data); |
882 | +static void test_resource_manager_default (Fixture *fix, gconstpointer data); |
883 | |
884 | void |
885 | test_resource_manager_create_suite (void) |
886 | { |
887 | #define DOMAIN "/ResourceManager" |
888 | |
889 | + g_test_add (DOMAIN"/Default", Fixture, 0, |
890 | + NULL, test_resource_manager_default, NULL); |
891 | + |
892 | g_test_add (DOMAIN"/SequenceModel", Fixture, 0, |
893 | - sequence_model_setup, test_model_persistence, sequence_model_teardown); |
894 | + sequence_model_setup, test_model_persistence, sequence_model_teardown); |
895 | |
896 | g_test_add (DOMAIN"/SharedModel", Fixture, 0, |
897 | - shared_model_setup, test_model_persistence, shared_model_teardown); |
898 | + shared_model_setup, test_model_persistence, shared_model_teardown); |
899 | } |
900 | |
901 | static void |
902 | @@ -192,3 +196,27 @@ |
903 | |
904 | dee_assert_cmpmodel (fix->orig, fix->copy); |
905 | } |
906 | + |
907 | +static void |
908 | +test_resource_manager_default (Fixture *fix, gconstpointer data) |
909 | +{ |
910 | + DeeResourceManager *manager; |
911 | + GError *error; |
912 | + GObject *result; |
913 | + gchar *primary_path; |
914 | + |
915 | + error = NULL; |
916 | + |
917 | + manager = dee_resource_manager_get_default (); |
918 | + g_object_get (manager, "primary-path", &primary_path, NULL); |
919 | + g_assert (g_str_has_suffix (primary_path, "resources")); |
920 | + g_free (primary_path); |
921 | + |
922 | + result = dee_resource_manager_load (manager, |
923 | + "com.this.hopefully.doesnt.exist.com", |
924 | + &error); |
925 | + g_assert (result == NULL); |
926 | + /* loading non-existing resource just returns NULL, doesn't throw error */ |
927 | + g_assert_no_error (error); |
928 | +} |
929 | + |
930 | |
931 | === modified file 'vapi/dee-1.0.vapi' |
932 | --- vapi/dee-1.0.vapi 2013-04-10 14:30:44 +0000 |
933 | +++ vapi/dee-1.0.vapi 2013-09-16 18:51:54 +0000 |
934 | @@ -212,11 +212,13 @@ |
935 | public interface Model : GLib.Object { |
936 | public unowned Dee.ModelIter append (...); |
937 | public abstract unowned Dee.ModelIter append_row ([CCode (array_length = false, array_null_terminated = true)] GLib.Variant[] row_members); |
938 | + public abstract void begin_changeset (); |
939 | [CCode (array_length_pos = 1.33333, array_length_type = "guint", cname = "dee_model_build_named_row_sunk")] |
940 | public GLib.Variant[] build_named_row ([CCode (array_length = false)] GLib.Variant[]? out_row_members, string first_column_name, ...); |
941 | [CCode (array_length_pos = 1.33333, array_length_type = "guint", cname = "dee_model_build_named_row_sunk")] |
942 | public unowned GLib.Variant[] build_named_row_static ([CCode (array_length = false)] GLib.Variant[] out_row_members, string first_column_name, ...); |
943 | public abstract void clear (); |
944 | + public abstract void end_changeset (); |
945 | public abstract unowned Dee.ModelIter find_row_sorted ([CCode (array_length = false, array_null_terminated = true)] GLib.Variant[] row_spec, [CCode (delegate_target_pos = 2.5)] Dee.CompareRowFunc cmp_func, out bool out_was_found); |
946 | public unowned Dee.ModelIter find_row_sorted_with_sizes ([CCode (array_length = false, array_null_terminated = true)] GLib.Variant[] row_spec, [CCode (delegate_target_pos = 2.5)] Dee.CompareRowSizedFunc cmp_func, out bool out_was_found); |
947 | public unowned Dee.ModelIter find_sorted ([CCode (delegate_target_pos = 1.5)] Dee.CompareRowFunc cmp_func, out bool out_was_found, ...); |
948 | @@ -271,6 +273,8 @@ |
949 | public void set_schema (...); |
950 | public abstract void set_schema_full ([CCode (array_length_cname = "num_columns", array_length_pos = 1.1, array_length_type = "guint", array_null_terminated = true)] string[] column_schemas); |
951 | public abstract void set_value (Dee.ModelIter iter, uint column, GLib.Variant value); |
952 | + public virtual signal void changeset_finished (); |
953 | + public virtual signal void changeset_started (); |
954 | public virtual signal void row_added (Dee.ModelIter iter); |
955 | public virtual signal void row_changed (Dee.ModelIter iter); |
956 | public virtual signal void row_removed (Dee.ModelIter iter); |
FAILED: Continuous integration, rev:429 jenkins. qa.ubuntu. com/job/ dee-ci/ 10/ jenkins. qa.ubuntu. com/job/ dee-saucy- amd64-ci/ 7 jenkins. qa.ubuntu. com/job/ dee-saucy- armhf-ci/ 7/console jenkins. qa.ubuntu. com/job/ dee-saucy- i386-ci/ 7
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins: 8080/job/ dee-ci/ 10/rebuild
http://