8000 improve signal handling again · libvips/pyvips@4c156a0 · GitHub
[go: up one dir, main page]

Skip to content

Commit 4c156a0

Browse files
committed
improve signal handling again
It now supports a range of signals. There's also a test for progress signalling.
1 parent 7679a07 commit 4c156a0

File tree

6 files changed

+136
-31
lines changed

6 files changed

+136
-31
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
* add watermark example [jcupitt]
1111
* fix syntax highlighting in README [favorable-mutation]
1212
* add signal handling [jcupitt]
13+
* add stream support [jcupitt]
1314
* add perf tests [kleisauke]
1415
* speed up Operation.call [kleisauke]
1516
* fix logging [h4l]

pyvips/decls.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ def cdefs(features):
2323
2424
"""
2525

26-
code = ''
26+
code = '''
27+
typedef uint64_t guint64;
28+
typedef int64_t gint64;
29+
'''
2730

2831
# apparently the safest way to do this
2932
is_64bits = sys.maxsize > 2 ** 32
@@ -32,11 +35,11 @@ def cdefs(features):
3235
# size_t, sadly
3336
if is_64bits:
3437
code += '''
35-
typedef uint64_t GType;
38+
typedef guint64 GType;
3639
'''
3740
else:
3841
code += '''
39-
typedef uint32_t GType;
42+
typedef guint32 GType;
4043
'''
4144

4245
code += '''
@@ -79,7 +82,7 @@ def cdefs(features):
7982
8083
typedef struct _GValue {
8184
GType g_type;
82-
uint64_t data[2];
85+
guint64 data[2];
8386
} GValue;
8487
8588
void g_value_init (GValue* value, GType gtype);
@@ -92,7 +95,7 @@ def cdefs(features):
9295
9396
void g_value_set_boolean (GValue* value, bool v_boolean);
9497
void g_value_set_int (GValue* value, int i);
95-
void g_value_set_uint64 (GValue* value, uint64_t ull);
98+
void g_value_set_uint64 (GValue* value, guint64 ull);
9699
void g_value_set_double (GValue* value, double d);
97100
void g_value_set_enum (GValue* value, int e);
98101
void g_value_set_flags (GValue* value, unsigned int f);
@@ -110,7 +113,7 @@ def cdefs(features):
110113
111114
bool g_value_get_boolean (const GValue* value);
112115
int g_value_get_int (GValue* value);
113-
uint64_t g_value_get_uint64 (GValue* value);
116+
guint64 g_value_get_uint64 (GValue* value);
114117
double g_value_get_double (GValue* value);
115118
int g_value_get_enum (GValue* value);
116119
unsigned int g_value_get_flags (GValue* value);
@@ -197,18 +200,19 @@ def cdefs(features):
197200
GClosureNotify destroy_data,
198201
int connect_flags);
199202
200-
extern "Python" void _marshall_image_progress (VipsImage*,
203+
extern "Python" void _marshal_image_progress (VipsImage*,
201204
void*, void*);
202205
203206
void vips_image_set_progress (VipsImage* image, bool progress);
207+
void vips_image_set_kill (VipsImage* image, bool kill);
204208
205209
typedef struct _VipsProgress {
206210
VipsImage* im;
207211
208212
int run;
209213
int eta;
210-
int64_t tpels;
211-
int64_t npels;
214+
gint64 tpels;
215+
gint64 npels;
212216
int percent;
213217
void* start;
214218
} VipsProgress;
@@ -439,9 +443,9 @@ def cdefs(features):
439443
440444
VipsStreami* vips_streamiu_new (void);
441445
442-
extern "Python" gint64 _marshall_read (VipsStreamiu*,
446+
extern "Python" gint64 _marshal_read (VipsStreamiu*,
443447
void*, gint64, void*);
444-
extern "Python" gint64 _marshall_seek (VipsStreamiu*,
448+
extern "Python" gint64 _marshal_seek (VipsStreamiu*,
445449
gint64, int, void*);
446450
447451
typedef struct _VipsStreamo {
@@ -465,9 +469,9 @@ def cdefs(features):
465469
const char* vips_foreign_find_load_stream (VipsStreami *streami);
466470
const char* vips_foreign_find_save_stream (const char* suffix);
467471
468-
extern "Python" gint64 _marshall_write (VipsStreamou*,
472+
extern "Python" gint64 _marshal_write (VipsStreamou*,
469473
void*, gint64, void*);
470-
extern "Python" void _marshall_finish (VipsStreamou*, void*);
474+
extern "Python" void _marshal_finish (VipsStreamou*, void*);
471475
472476
'''
473477

pyvips/gobject.py

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,24 @@
33
import logging
44

55
import pyvips
6-
from pyvips import ffi, gobject_lib, _to_bytes
6+
from pyvips import ffi, gobject_lib, _to_bytes, Error
77

88
logger = logging.getLogger(__name__)
99

1010
# the python marshalers for gobject signal handling
1111
# we keep a ref to each callback to stop them being GCd
1212

13-
if pyvips.API_mode:
14-
@ffi.def_extern()
13+
14+
def conditional_decorator(dec, condition):
15+
def decorator(func):
16+
if not condition:
17+
# Return the function unchanged, not decorated.
18+
return func
19+
return dec(func)
20+
return decorator
21+
22+
23+
@conditional_decorator(ffi.def_extern(), pyvips.API_mode)
1524
def _marshal_image_progress(vi, pointer, handle):
1625
# the image we're passed is not reffed for us, so make a ref for us
1726
gobject_lib.g_object_ref(vi)
@@ -20,6 +29,7 @@ def _marshal_image_progress(vi, pointer, handle):
2029
progress = ffi.cast('VipsProgress*', pointer)
2130
callback(image, progress)
2231

32+
2333
if pyvips.API_mode:
2434
_marshal_image_progress_cb = \
2535
ffi.cast('GCallback', gobject_lib._marshal_image_progress)
@@ -29,12 +39,13 @@ def _marshal_image_progress(vi, pointer, handle):
2939
ffi.callback('void(VipsImage*, void*, void*)',
3040
_marshal_image_progress))
3141

32-
if pyvips.API_mode:
33-
@ffi.def_extern()
42+
43+
@conditional_decorator(ffi.def_extern(), pyvips.API_mode)
3444
def _marshal_read(streamiu, pointer, length, handle):
3545
buf = ffi.buffer(pointer, length)
3646
callback = ffi.from_handle(handle)
37-
callback(streamiu, buf)
47+
return callback(streamiu, buf)
48+
3849

3950
if pyvips.API_mode:
4051
_marshal_read_cb = \
@@ -45,27 +56,30 @@ def _marshal_read(streamiu, pointer, length, handle):
4556
ffi.callback('gint64(VipsStreamiu*, void*, gint64, void*)',
4657
_marshal_read))
4758

48-
_marshalers = [
49-
"image_progress": _marshal_image_progress_cb,
50-
"read": _marshal_read_cb,
51-
"seek": _marshal_seek_cb,
52-
]
5359

54-
if pyvips.API_mode:
55-
@ffi.def_extern()
60+
@conditional_decorator(ffi.def_extern(), pyvips.API_mode)
5661
def _marshal_seek(streamiu, offset, whence, handle):
5762
callback = ffi.from_handle(handle)
58-
callback(streamiu, offset, whence)
63+
return callback(streamiu, offset, whence)
64+
5965

6066
if pyvips.API_mode:
61-
_marshal_read_cb = \
67+
_marshal_seek_cb = \
6268
ffi.cast('GCallback', gobject_lib._marshal_seek)
6369
else:
64-
_marshal_read_cb = \
70+
_marshal_seek_cb = \
6571
ffi.cast('GCallback',
6672
ffi.callback('gint64(VipsStreamiu*, gint64, int, void*)',
6773
_marshal_seek))
6874

75+
_marshalers = {
76+
"preeval": _marshal_image_progress_cb,
77+
"eval": _marshal_image_progress_cb,
78+
"posteval": _marshal_image_progress_cb,
79+
"read": _marshal_read_cb,
80+
"seek": _marshal_seek_cb,
81+
}
82+
6983

7084
class GObject(object):
7185
"""Manage GObject lifetime.
@@ -102,12 +116,15 @@ def signal_connect(self, name, callback):
102116
103117
"""
104118

119+
if name not in _marshalers:
120+
raise Error('unsupported signal "{0}"'.format(name))
121+
105122
go = ffi.cast('GObject *', self.pointer)
106123
handle = ffi.new_handle(callback)
107124
self._handles.append(handle)
108125

109126
gobject_lib.g_signal_connect_data(go, _to_bytes(name),
110-
_marshal_image_progress_cb,
127+
_marshalers[name],
111128
handle, ffi.NULL, 0)
112129

113130

pyvips/vimage.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,15 @@ def set_progress(self, progress):
700700
"""
701701
vips_lib.vips_image_set_progress(self.pointer, progress)
702702

703+
def set_kill(self, kill):
704+
"""Kill processing of an image.
705+
706+
Use this to kill evaluation of an image. You can call it from one of
707+
the progress signals, for example. See :method:`set_progress`.
708+
709+
"""
710+
vips_lib.vips_image_set_kill(self.pointer, kill)
711+
703712
# get/set metadata
704713

705714
def get_typeof(self, name):

pyvips/vobject.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ def _get_pspec(self, name):
4141
pspec = ffi.new('GParamSpec **')
4242
argument_class = ffi.new('VipsArgumentClass **')
4343
argument_instance = ffi.new('VipsArgumentInstance **')
44-
result = vips_lib.vips_object_get_argument(self.vobject, _to_bytes(name),
44+
result = vips_lib.vips_object_get_argument(self.vobject,
45+
_to_bytes(name),
4546
pspec, argument_class,
4647
argument_instance)
4748

tests/test_signals.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# vim: set fileencoding=utf-8 :
2+
3+
import pytest
4+
import pyvips
5+
6+
7+
class TestSignals:
8+
def test_progress(self):
9+
# py27 reques this pattern for non-local modification
10+
notes = {}
11+
12+
def preeval_cb(image, progress):
13+
notes['seen_preeval'] = True
14+
15+
def eval_cb(image, progress):
16+
notes['seen_eval'] = True
17+
18+
def posteval_cb(image, progress):
19+
notes['seen_posteval'] = True
20+
21+
image = pyvips.Image.black(10, 1000)
22+
image.set_progress(True)
23+
image.signal_connect('preeval', preeval_cb)
24+
image.signal_connect('eval', eval_cb)
25+
image.signal_connect('posteval', posteval_cb)
26+
image.copy_memory()
27+
28+
assert notes['seen_preeval']
29+
assert notes['seen_eval']
30+
assert notes['seen_posteval']
31+
32+
def test_progress_fields(self):
33+
def preeval_cb(image, progress):
34+
assert progress.run == 0
35+
assert progress.eta == 0
36+
assert progress.percent == 0
37+
assert progress.tpels == 10000
38+
assert progress.npels == 0
39+
40+
def eval_cb(image, progress):
41+
pass
42+
43+
def posteval_cb(image, progress):
44+
assert progress.percent == 100
45+
assert progress.tpels == 10000
46+
assert progress.npels == 10000
47+
48+
image = pyvips.Image.black(10, 1000)
49+
image.set_progress(True)
50+
image.signal_connect('preeval', preeval_cb)
51+
image.signal_connect('eval', eval_cb)
52+
image.signal_connect('posteval', posteval_cb)
53+
image.copy_memory()
54+
55+
@pytest.mark.skip(reason='this works in my code, but segvs in pytest')
56+
def test_progress_kill(self):
57+
def preeval_cb(image, progress):
58+
pass
59+
60+
def eval_cb(image, progress):
61+
image.set_kill(True)
62+
63+
def posteval_cb(image, progress):
64+
pass
65+
66+
image = pyvips.Image.black(10, 1000)
67+
image.set_progress(True)
68+
image.signal_connect('preeval', preeval_cb)
69+
image.signal_connect('eval', eval_cb)
70+
image.signal_connect('posteval', posteval_cb)
71+
72+
with pytest.raises(Exception):
73+
image.copy_memory()

0 commit comments

Comments
 (0)
0