python: Add properties to wrapped object.
[gpgme.git] / lang / python / pyme / core.py
1 # Copyright (C) 2016 g10 Code GmbH
2 # Copyright (C) 2004,2008 Igor Belyi <belyi@users.sourceforge.net>
3 # Copyright (C) 2002 John Goerzen <jgoerzen@complete.org>
4 #
5 #    This library is free software; you can redistribute it and/or
6 #    modify it under the terms of the GNU Lesser General Public
7 #    License as published by the Free Software Foundation; either
8 #    version 2.1 of the License, or (at your option) any later version.
9 #
10 #    This library is distributed in the hope that it will be useful,
11 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 #    Lesser General Public License for more details.
14 #
15 #    You should have received a copy of the GNU Lesser General Public
16 #    License along with this library; if not, write to the Free Software
17 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
18
19 """Core functionality
20
21 Core functionality of GPGME wrapped in a object-oriented fashion.
22 Provides the 'Context' class for performing cryptographic operations,
23 and the 'Data' class describing buffers of data.
24
25 """
26
27 import weakref
28 from . import pygpgme
29 from .errors import errorcheck, GPGMEError
30 from . import constants
31 from . import errors
32
33 class GpgmeWrapper(object):
34     """Base wrapper class
35
36     Not to be instantiated directly.
37
38     """
39
40     def __init__(self, wrapped):
41         self._callback_excinfo = None
42         self.wrapped = wrapped
43
44     def __repr__(self):
45         return '<{}/{!r}>'.format(super().__repr__(), self.wrapped)
46
47     def __str__(self):
48         acc = ['{}.{}'.format(__name__, self.__class__.__name__)]
49         flags = [f for f in self._boolean_properties if getattr(self, f)]
50         if flags:
51             acc.append('({})'.format(' '.join(flags)))
52
53         return '<{}>'.format(' '.join(acc))
54
55     def __hash__(self):
56         return hash(repr(self.wrapped))
57
58     def __eq__(self, other):
59         if other == None:
60             return False
61         else:
62             return repr(self.wrapped) == repr(other.wrapped)
63
64     def _getctype(self):
65         """Must be implemented by child classes.
66
67         Must return the name of the c type."""
68         raise NotImplementedError()
69
70     def _getnameprepend(self):
71         """Must be implemented by child classes.
72
73         Must return the prefix of all c functions mapped to methods of
74         this class."""
75         raise NotImplementedError()
76
77     def _errorcheck(self, name):
78         """Must be implemented by child classes.
79
80         This function must return a trueish value for all c functions
81         returning gpgme_error_t."""
82         raise NotImplementedError()
83
84     """The set of all boolean properties"""
85     _boolean_properties = set()
86
87     def __wrap_boolean_property(self, key, do_set=False, value=None):
88         get_func = getattr(pygpgme,
89                            "{}get_{}".format(self._getnameprepend(), key))
90         set_func = getattr(pygpgme,
91                            "{}set_{}".format(self._getnameprepend(), key))
92         def get(slf):
93             return bool(get_func(slf.wrapped))
94         def set_(slf, value):
95             set_func(slf.wrapped, bool(value))
96
97         p = property(get, set_, doc="{} flag".format(key))
98         setattr(self.__class__, key, p)
99
100         if do_set:
101             set_(self, bool(value))
102         else:
103             return get(self)
104
105     def __getattr__(self, key):
106         """On-the-fly generation of wrapper methods and properties"""
107         if key[0] == '_' or self._getnameprepend() == None:
108             return None
109
110         if key in self._boolean_properties:
111             return self.__wrap_boolean_property(key)
112
113         name = self._getnameprepend() + key
114         func = getattr(pygpgme, name)
115
116         if self._errorcheck(name):
117             def _funcwrap(slf, *args, **kwargs):
118                 result = func(slf.wrapped, *args, **kwargs)
119                 if slf._callback_excinfo:
120                     pygpgme.pygpgme_raise_callback_exception(slf)
121                 return errorcheck(result, "Invocation of " + name)
122         else:
123             def _funcwrap(slf, *args, **kwargs):
124                 result = func(slf.wrapped, *args, **kwargs)
125                 if slf._callback_excinfo:
126                     pygpgme.pygpgme_raise_callback_exception(slf)
127                 return result
128
129         _funcwrap.__doc__ = getattr(func, "__doc__")
130
131         # Monkey-patch the class.
132         setattr(self.__class__, key, _funcwrap)
133
134         # Bind the method to 'self'.
135         def wrapper(*args, **kwargs):
136             return _funcwrap(self, *args, **kwargs)
137         _funcwrap.__doc__ = getattr(func, "__doc__")
138
139         return wrapper
140
141     def __setattr__(self, key, value):
142         """On-the-fly generation of properties"""
143         if key in self._boolean_properties:
144             self.__wrap_boolean_property(key, True, value)
145         else:
146             super().__setattr__(key, value)
147
148 class Context(GpgmeWrapper):
149     """Context for cryptographic operations
150
151     All cryptographic operations in GPGME are performed within a
152     context, which contains the internal state of the operation as
153     well as configuration parameters.  By using several contexts you
154     can run several cryptographic operations in parallel, with
155     different configuration.
156
157     Access to a context must be synchronized.
158
159     """
160
161     @property
162     def signers(self):
163         """Keys used for signing"""
164         return [self.signers_enum(i) for i in range(self.signers_count())]
165     @signers.setter
166     def signers(self, signers):
167         old = self.signers
168         self.signers_clear()
169         try:
170             for key in signers:
171                 self.signers_add(key)
172         except:
173             self.signers = old
174             raise
175
176     @property
177     def pinentry_mode(self):
178         """Pinentry mode"""
179         return self.get_pinentry_mode()
180     @pinentry_mode.setter
181     def pinentry_mode(self, value):
182         self.set_pinentry_mode(value)
183
184     def _getctype(self):
185         return 'gpgme_ctx_t'
186
187     def _getnameprepend(self):
188         return 'gpgme_'
189
190     def _errorcheck(self, name):
191         """This function should list all functions returning gpgme_error_t"""
192         if (name.startswith('gpgme_op_') and \
193             not name.endswith('_result')) or \
194             name == 'gpgme_signers_add' or \
195             name == 'gpgme_set_locale' or \
196             name == 'gpgme_set_keylist_mode' or \
197             name == 'gpgme_set_protocol':
198             return 1
199         return 0
200
201     _boolean_properties = {'armor', 'textmode', 'offline'}
202     def __init__(self, armor=False, textmode=False, offline=False,
203                  signers=[], pinentry_mode=constants.PINENTRY_MODE_DEFAULT,
204                  wrapped=None):
205         """Construct a context object
206
207         Keyword arguments:
208         armor           -- enable ASCII armoring (default False)
209         textmode        -- enable canonical text mode (default False)
210         offline         -- do not contact external key sources (default False)
211         signers         -- list of keys used for signing (default [])
212         pinentry_mode   -- pinentry mode (default PINENTRY_MODE_DEFAULT)
213         """
214         if wrapped:
215             self.own = False
216         else:
217             tmp = pygpgme.new_gpgme_ctx_t_p()
218             errorcheck(pygpgme.gpgme_new(tmp))
219             wrapped = pygpgme.gpgme_ctx_t_p_value(tmp)
220             pygpgme.delete_gpgme_ctx_t_p(tmp)
221             self.own = True
222         super().__init__(wrapped)
223         self.last_passcb = None
224         self.last_progresscb = None
225         self.last_statuscb = None
226         self.armor = armor
227         self.textmode = textmode
228         self.offline = offline
229         self.signers = signers
230         self.pinentry_mode = pinentry_mode
231
232     def __del__(self):
233         if not pygpgme:
234             # At interpreter shutdown, pygpgme is set to NONE.
235             return
236
237         self._free_passcb()
238         self._free_progresscb()
239         self._free_statuscb()
240         if self.own and self.wrapped and pygpgme.gpgme_release:
241             pygpgme.gpgme_release(self.wrapped)
242             self.wrapped = None
243
244     # Implement the context manager protocol.
245     def __enter__(self):
246         return self
247     def __exit__(self, type, value, tb):
248         self.__del__()
249
250     def _free_passcb(self):
251         if self.last_passcb != None:
252             if pygpgme.pygpgme_clear_generic_cb:
253                 pygpgme.pygpgme_clear_generic_cb(self.last_passcb)
254             if pygpgme.delete_PyObject_p_p:
255                 pygpgme.delete_PyObject_p_p(self.last_passcb)
256             self.last_passcb = None
257
258     def _free_progresscb(self):
259         if self.last_progresscb != None:
260             if pygpgme.pygpgme_clear_generic_cb:
261                 pygpgme.pygpgme_clear_generic_cb(self.last_progresscb)
262             if pygpgme.delete_PyObject_p_p:
263                 pygpgme.delete_PyObject_p_p(self.last_progresscb)
264             self.last_progresscb = None
265
266     def _free_statuscb(self):
267         if self.last_statuscb != None:
268             if pygpgme.pygpgme_clear_generic_cb:
269                 pygpgme.pygpgme_clear_generic_cb(self.last_statuscb)
270             if pygpgme.delete_PyObject_p_p:
271                 pygpgme.delete_PyObject_p_p(self.last_statuscb)
272             self.last_statuscb = None
273
274     def op_keylist_all(self, *args, **kwargs):
275         self.op_keylist_start(*args, **kwargs)
276         key = self.op_keylist_next()
277         while key:
278             yield key
279             key = self.op_keylist_next()
280
281     def op_keylist_next(self):
282         """Returns the next key in the list created
283         by a call to op_keylist_start().  The object returned
284         is of type Key."""
285         ptr = pygpgme.new_gpgme_key_t_p()
286         try:
287             errorcheck(pygpgme.gpgme_op_keylist_next(self.wrapped, ptr))
288             key = pygpgme.gpgme_key_t_p_value(ptr)
289         except errors.GPGMEError as excp:
290             key = None
291             if excp.getcode() != errors.EOF:
292                 raise excp
293         pygpgme.delete_gpgme_key_t_p(ptr)
294         if key:
295             key.__del__ = lambda self: pygpgme.gpgme_key_unref(self)
296             return key
297
298     def get_key(self, fpr, secret):
299         """Return the key corresponding to the fingerprint 'fpr'"""
300         ptr = pygpgme.new_gpgme_key_t_p()
301         errorcheck(pygpgme.gpgme_get_key(self.wrapped, fpr, ptr, secret))
302         key = pygpgme.gpgme_key_t_p_value(ptr)
303         pygpgme.delete_gpgme_key_t_p(ptr)
304         if key:
305             key.__del__ = lambda self: pygpgme.gpgme_key_unref(self)
306             return key
307
308     def op_trustlist_all(self, *args, **kwargs):
309         self.op_trustlist_start(*args, **kwargs)
310         trust = self.ctx.op_trustlist_next()
311         while trust:
312             yield trust
313             trust = self.ctx.op_trustlist_next()
314
315     def op_trustlist_next(self):
316         """Returns the next trust item in the list created
317         by a call to op_trustlist_start().  The object returned
318         is of type TrustItem."""
319         ptr = pygpgme.new_gpgme_trust_item_t_p()
320         try:
321             errorcheck(pygpgme.gpgme_op_trustlist_next(self.wrapped, ptr))
322             trust = pygpgme.gpgme_trust_item_t_p_value(ptr)
323         except errors.GPGMEError as excp:
324             trust = None
325             if excp.getcode() != errors.EOF:
326                 raise
327         pygpgme.delete_gpgme_trust_item_t_p(ptr)
328         return trust
329
330     def set_passphrase_cb(self, func, hook=None):
331         """Sets the passphrase callback to the function specified by func.
332
333         When the system needs a passphrase, it will call func with three args:
334         hint, a string describing the key it needs the passphrase for;
335         desc, a string describing the passphrase it needs;
336         prev_bad, a boolean equal True if this is a call made after
337         unsuccessful previous attempt.
338
339         If hook has a value other than None it will be passed into the func
340         as a forth argument.
341
342         Please see the GPGME manual for more information.
343         """
344         self._free_passcb()
345         if func == None:
346             hookdata = None
347         else:
348             self.last_passcb = pygpgme.new_PyObject_p_p()
349             if hook == None:
350                 hookdata = (weakref.ref(self), func)
351             else:
352                 hookdata = (weakref.ref(self), func, hook)
353         pygpgme.pygpgme_set_passphrase_cb(self.wrapped, hookdata, self.last_passcb)
354
355     def set_progress_cb(self, func, hook=None):
356         """Sets the progress meter callback to the function specified by FUNC.
357         If FUNC is None, the callback will be cleared.
358
359         This function will be called to provide an interactive update
360         of the system's progress.  The function will be called with
361         three arguments, type, total, and current.  If HOOK is not
362         None, it will be supplied as fourth argument.
363
364         Please see the GPGME manual for more information.
365
366         """
367         self._free_progresscb()
368         if func == None:
369             hookdata = None
370         else:
371             self.last_progresscb = pygpgme.new_PyObject_p_p()
372             if hook == None:
373                 hookdata = (weakref.ref(self), func)
374             else:
375                 hookdata = (weakref.ref(self), func, hook)
376         pygpgme.pygpgme_set_progress_cb(self.wrapped, hookdata, self.last_progresscb)
377
378     def set_status_cb(self, func, hook=None):
379         """Sets the status callback to the function specified by FUNC.  If
380         FUNC is None, the callback will be cleared.
381
382         The function will be called with two arguments, keyword and
383         args.  If HOOK is not None, it will be supplied as third
384         argument.
385
386         Please see the GPGME manual for more information.
387
388         """
389         self._free_statuscb()
390         if func == None:
391             hookdata = None
392         else:
393             self.last_statuscb = pygpgme.new_PyObject_p_p()
394             if hook == None:
395                 hookdata = (weakref.ref(self), func)
396             else:
397                 hookdata = (weakref.ref(self), func, hook)
398         pygpgme.pygpgme_set_status_cb(self.wrapped, hookdata,
399                                       self.last_statuscb)
400
401     def get_engine_info(self):
402         """Returns this context specific engine info"""
403         return pygpgme.gpgme_ctx_get_engine_info(self.wrapped)
404
405     def set_engine_info(self, proto, file_name, home_dir=None):
406         """Changes the configuration of the crypto engine implementing the
407     protocol 'proto' for the context. 'file_name' is the file name of
408     the executable program implementing this protocol. 'home_dir' is the
409     directory name of the configuration directory (engine's default is
410     used if omitted)."""
411         errorcheck(pygpgme.gpgme_ctx_set_engine_info(self.wrapped, proto, file_name, home_dir))
412
413     def wait(self, hang):
414         """Wait for asynchronous call to finish. Wait forever if hang is True.
415         Raises an exception on errors.
416
417         Please read the GPGME manual for more information.
418
419         """
420         ptr = pygpgme.new_gpgme_error_t_p()
421         pygpgme.gpgme_wait(self.wrapped, ptr, hang)
422         status = pygpgme.gpgme_error_t_p_value(ptr)
423         pygpgme.delete_gpgme_error_t_p(ptr)
424         errorcheck(status)
425
426     def op_edit(self, key, func, fnc_value, out):
427         """Start key editing using supplied callback function"""
428         if key == None:
429             raise ValueError("op_edit: First argument cannot be None")
430         if fnc_value:
431             opaquedata = (weakref.ref(self), func, fnc_value)
432         else:
433             opaquedata = (weakref.ref(self), func)
434
435         result = pygpgme.gpgme_op_edit(self.wrapped, key, opaquedata, out)
436         if self._callback_excinfo:
437             pygpgme.pygpgme_raise_callback_exception(self)
438         errorcheck(result)
439
440 class Data(GpgmeWrapper):
441     """Data buffer
442
443     A lot of data has to be exchanged between the user and the crypto
444     engine, like plaintext messages, ciphertext, signatures and
445     information about the keys.  The technical details about
446     exchanging the data information are completely abstracted by
447     GPGME.  The user provides and receives the data via `gpgme_data_t'
448     objects, regardless of the communication protocol between GPGME
449     and the crypto engine in use.
450
451     This Data class is the implementation of the GpgmeData objects.
452
453     Please see the information about __init__ for instantiation.
454
455     """
456
457     def _getctype(self):
458         return 'gpgme_data_t'
459
460     def _getnameprepend(self):
461         return 'gpgme_data_'
462
463     def _errorcheck(self, name):
464         """This function should list all functions returning gpgme_error_t"""
465         return name not in {
466             'gpgme_data_release_and_get_mem',
467             'gpgme_data_get_encoding',
468             'gpgme_data_seek',
469             'gpgme_data_get_file_name',
470         }
471
472     def __init__(self, string=None, file=None, offset=None,
473                  length=None, cbs=None, copy=True):
474         """Initialize a new gpgme_data_t object.
475
476         If no args are specified, make it an empty object.
477
478         If string alone is specified, initialize it with the data
479         contained there.
480
481         If file, offset, and length are all specified, file must
482         be either a filename or a file-like object, and the object
483         will be initialized by reading the specified chunk from the file.
484
485         If cbs is specified, it MUST be a tuple of the form:
486
487         (read_cb, write_cb, seek_cb, release_cb[, hook])
488
489         where the first four items are functions implementing reading,
490         writing, seeking the data, and releasing any resources once
491         the data object is deallocated.  The functions must match the
492         following prototypes:
493
494             def read(amount, hook=None):
495                 return <a b"bytes" object>
496
497             def write(data, hook=None):
498                 return <the number of bytes written>
499
500             def seek(offset, whence, hook=None):
501                 return <the new file position>
502
503             def release(hook=None):
504                 <return value and exceptions are ignored>
505
506         The functions may be bound methods.  In that case, you can
507         simply use the 'self' reference instead of using a hook.
508
509         If file is specified without any other arguments, then
510         it must be a filename, and the object will be initialized from
511         that file.
512
513         """
514         super().__init__(None)
515         self.data_cbs = None
516
517         if cbs != None:
518             self.new_from_cbs(*cbs)
519         elif string != None:
520             self.new_from_mem(string, copy)
521         elif file != None and offset != None and length != None:
522             self.new_from_filepart(file, offset, length)
523         elif file != None:
524             if type(file) == type("x"):
525                 self.new_from_file(file, copy)
526             else:
527                 self.new_from_fd(file)
528         else:
529             self.new()
530
531     def __del__(self):
532         if not pygpgme:
533             # At interpreter shutdown, pygpgme is set to NONE.
534             return
535
536         if self.wrapped != None and pygpgme.gpgme_data_release:
537             pygpgme.gpgme_data_release(self.wrapped)
538             if self._callback_excinfo:
539                 pygpgme.pygpgme_raise_callback_exception(self)
540             self.wrapped = None
541         self._free_datacbs()
542
543     # Implement the context manager protocol.
544     def __enter__(self):
545         return self
546     def __exit__(self, type, value, tb):
547         self.__del__()
548
549     def _free_datacbs(self):
550         if self.data_cbs != None:
551             if pygpgme.pygpgme_clear_generic_cb:
552                 pygpgme.pygpgme_clear_generic_cb(self.data_cbs)
553             if pygpgme.delete_PyObject_p_p:
554                 pygpgme.delete_PyObject_p_p(self.data_cbs)
555             self.data_cbs = None
556
557     def new(self):
558         tmp = pygpgme.new_gpgme_data_t_p()
559         errorcheck(pygpgme.gpgme_data_new(tmp))
560         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
561         pygpgme.delete_gpgme_data_t_p(tmp)
562
563     def new_from_mem(self, string, copy=True):
564         tmp = pygpgme.new_gpgme_data_t_p()
565         errorcheck(pygpgme.gpgme_data_new_from_mem(tmp,string,len(string),copy))
566         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
567         pygpgme.delete_gpgme_data_t_p(tmp)
568
569     def new_from_file(self, filename, copy=True):
570         tmp = pygpgme.new_gpgme_data_t_p()
571         try:
572             errorcheck(pygpgme.gpgme_data_new_from_file(tmp, filename, copy))
573         except errors.GPGMEError as e:
574             if e.getcode() == errors.INV_VALUE and not copy:
575                 raise ValueError("delayed reads are not yet supported")
576             else:
577                 raise e
578         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
579         pygpgme.delete_gpgme_data_t_p(tmp)
580
581     def new_from_cbs(self, read_cb, write_cb, seek_cb, release_cb, hook=None):
582         assert self.data_cbs == None
583         self.data_cbs = pygpgme.new_PyObject_p_p()
584         tmp = pygpgme.new_gpgme_data_t_p()
585         if hook != None:
586             hookdata = (weakref.ref(self),
587                         read_cb, write_cb, seek_cb, release_cb, hook)
588         else:
589             hookdata = (weakref.ref(self),
590                         read_cb, write_cb, seek_cb, release_cb)
591         errorcheck(
592             pygpgme.pygpgme_data_new_from_cbs(tmp, hookdata, self.data_cbs))
593         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
594         pygpgme.delete_gpgme_data_t_p(tmp)
595
596     def new_from_filepart(self, file, offset, length):
597         """This wraps the GPGME gpgme_data_new_from_filepart() function.
598         The argument "file" may be:
599
600         * a string specifying a file name, or
601         * a file-like object supporting the fileno() and the mode attribute.
602
603         """
604
605         tmp = pygpgme.new_gpgme_data_t_p()
606         filename = None
607         fp = None
608
609         if type(file) == type("x"):
610             filename = file
611         else:
612             fp = pygpgme.fdopen(file.fileno(), file.mode)
613             if fp == None:
614                 raise ValueError("Failed to open file from %s arg %s" % \
615                       (str(type(file)), str(file)))
616
617         errorcheck(pygpgme.gpgme_data_new_from_filepart(tmp, filename, fp,
618                                                       offset, length))
619         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
620         pygpgme.delete_gpgme_data_t_p(tmp)
621
622     def new_from_fd(self, file):
623         """This wraps the GPGME gpgme_data_new_from_fd() function.  The
624         argument "file" must be a file-like object, supporting the
625         fileno() method.
626
627         """
628         tmp = pygpgme.new_gpgme_data_t_p()
629         errorcheck(pygpgme.gpgme_data_new_from_fd(tmp, file.fileno()))
630         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
631         pygpgme.delete_gpgme_data_t_p(tmp)
632
633     def new_from_stream(self, file):
634         """This wrap around gpgme_data_new_from_stream is an alias for
635         new_from_fd() method since in python there's not difference
636         between file stream and file descriptor"""
637         self.new_from_fd(file)
638
639     def write(self, buffer):
640         """Write buffer given as string or bytes.
641
642         If a string is given, it is implicitly encoded using UTF-8."""
643         written = pygpgme.gpgme_data_write(self.wrapped, buffer)
644         if written < 0:
645             if self._callback_excinfo:
646                 pygpgme.pygpgme_raise_callback_exception(self)
647             else:
648                 raise GPGMEError.fromSyserror()
649         return written
650
651     def read(self, size = -1):
652         """Read at most size bytes, returned as bytes.
653
654         If the size argument is negative or omitted, read until EOF is reached.
655
656         Returns the data read, or the empty string if there was no data
657         to read before EOF was reached."""
658
659         if size == 0:
660             return ''
661
662         if size > 0:
663             try:
664                 result = pygpgme.gpgme_data_read(self.wrapped, size)
665             except:
666                 if self._callback_excinfo:
667                     pygpgme.pygpgme_raise_callback_exception(self)
668                 else:
669                     raise
670             return result
671         else:
672             chunks = []
673             while True:
674                 try:
675                     result = pygpgme.gpgme_data_read(self.wrapped, 4096)
676                 except:
677                     if self._callback_excinfo:
678                         pygpgme.pygpgme_raise_callback_exception(self)
679                     else:
680                         raise
681                 if len(result) == 0:
682                     break
683                 chunks.append(result)
684             return b''.join(chunks)
685
686 def pubkey_algo_name(algo):
687     return pygpgme.gpgme_pubkey_algo_name(algo)
688
689 def hash_algo_name(algo):
690     return pygpgme.gpgme_hash_algo_name(algo)
691
692 def get_protocol_name(proto):
693     return pygpgme.gpgme_get_protocol_name(proto)
694
695 def check_version(version=None):
696     return pygpgme.gpgme_check_version(version)
697
698 # check_version also makes sure that several subsystems are properly
699 # initialized, and it must be run at least once before invoking any
700 # other function.  We do it here so that the user does not have to do
701 # it unless she really wants to check for a certain version.
702 check_version()
703
704 def engine_check_version (proto):
705     try:
706         errorcheck(pygpgme.gpgme_engine_check_version(proto))
707         return True
708     except errors.GPGMEError:
709         return False
710
711 def get_engine_info():
712     ptr = pygpgme.new_gpgme_engine_info_t_p()
713     try:
714         errorcheck(pygpgme.gpgme_get_engine_info(ptr))
715         info = pygpgme.gpgme_engine_info_t_p_value(ptr)
716     except errors.GPGMEError:
717         info = None
718     pygpgme.delete_gpgme_engine_info_t_p(ptr)
719     return info
720
721 def set_engine_info(proto, file_name, home_dir=None):
722     """Changes the default configuration of the crypto engine implementing
723     the protocol 'proto'. 'file_name' is the file name of
724     the executable program implementing this protocol. 'home_dir' is the
725     directory name of the configuration directory (engine's default is
726     used if omitted)."""
727     errorcheck(pygpgme.gpgme_set_engine_info(proto, file_name, home_dir))
728
729 def set_locale(category, value):
730     """Sets the default locale used by contexts"""
731     errorcheck(pygpgme.gpgme_set_locale(None, category, value))
732
733 def wait(hang):
734     """Wait for asynchronous call on any Context  to finish.
735     Wait forever if hang is True.
736
737     For finished anynch calls it returns a tuple (status, context):
738         status  - status return by asnynchronous call.
739         context - context which caused this call to return.
740
741     Please read the GPGME manual of more information."""
742     ptr = pygpgme.new_gpgme_error_t_p()
743     context = pygpgme.gpgme_wait(None, ptr, hang)
744     status = pygpgme.gpgme_error_t_p_value(ptr)
745     pygpgme.delete_gpgme_error_t_p(ptr)
746     if context == None:
747         errorcheck(status)
748     else:
749         context = Context(context)
750     return (status, context)