6ba33393ce865926d0de7cd5534e74fffe6e63fc
[platform/upstream/gstreamer.git] / debug-viewer / GstDebugViewer / Common / utils.py
1 # -*- coding: utf-8; mode: python; -*-
2 #
3 #  GStreamer Development Utilities
4 #
5 #  Copyright (C) 2007 RenĂ© Stadler <mail@renestadler.de>
6 #
7 #  This program is free software; you can redistribute it and/or modify it
8 #  under the terms of the GNU General Public License as published by the Free
9 #  Software Foundation; either version 3 of the License, or (at your option)
10 #  any later version.
11 #
12 #  This program is distributed in the hope that it will be useful, but WITHOUT
13 #  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 #  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
15 #  more details.
16 #
17 #  You should have received a copy of the GNU General Public License along with
18 #  this program; if not, see <http://www.gnu.org/licenses/>.
19
20 """GStreamer Development Utilities Common utils module."""
21
22 import os
23 import logging
24 import subprocess as _subprocess
25
26
27 class SingletonMeta (type):
28
29     def __init__(cls, name, bases, dict_):
30
31         from weakref import WeakValueDictionary
32
33         super(SingletonMeta, cls).__init__(name, bases, dict_)
34
35         cls._singleton_instances = WeakValueDictionary()
36
37     def __call__(cls, *a, **kw):
38
39         kw_key = tuple(sorted(kw.items()))
40
41         try:
42             obj = cls._singleton_instances[a + kw_key]
43         except KeyError:
44             obj = super(SingletonMeta, cls).__call__(*a, **kw)
45             cls._singleton_instances[a + kw_key] = obj
46         return obj
47
48
49 def gettext_cache():
50     """Return a callable object that operates like gettext.gettext, but is much
51     faster when a string is looked up more than once.  This is very useful in
52     loops, where calling gettext.gettext can quickly become a major performance
53     bottleneck."""
54
55     from gettext import gettext
56
57     d = {}
58
59     def gettext_cache_access(s):
60
61         if s not in d:
62             d[s] = gettext(s)
63         return d[s]
64
65     return gettext_cache_access
66
67
68 class ClassProperty (property):
69
70     "Like the property class, but also invokes the getter for class access."
71
72     def __init__(self, fget=None, fset=None, fdel=None, doc=None):
73
74         property.__init__(self, fget, fset, fdel, doc)
75
76         self.__fget = fget
77
78     def __get__(self, obj, obj_class=None):
79
80         ret = property.__get__(self, obj, obj_class)
81         if ret == self:
82             return self.__fget(None)
83         else:
84             return ret
85
86
87 class _XDGClass (object):
88
89     """Partial implementation of the XDG Base Directory specification v0.6.
90
91     http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html"""
92
93     def __init__(self):
94
95         self._add_base_dir("DATA_HOME", "~/.local/share")
96         self._add_base_dir("CONFIG_HOME", "~/.config")
97         self._add_base_dir("CACHE_HOME", "~/.cache")
98
99     def _add_base_dir(self, name, default):
100
101         dir = os.environ.get("XDG_%s" % (name,))
102         if not dir:
103             dir = os.path.expanduser(os.path.join(*default.split("/")))
104
105         setattr(self, name, dir)
106
107
108 XDG = _XDGClass()
109
110
111 class SaveWriteFile (object):
112
113     def __init__(self, filename, mode="wt"):
114
115         from tempfile import mkstemp
116
117         self.logger = logging.getLogger("tempfile")
118
119         dir = os.path.dirname(filename)
120         base_name = os.path.basename(filename)
121         temp_prefix = "%s-tmp" % (base_name,)
122
123         if dir:
124             # Destination dir differs from current directory, ensure that it
125             # exists:
126             try:
127                 os.makedirs(dir)
128             except OSError:
129                 pass
130
131             self.clean_stale(dir, temp_prefix)
132
133         fd, temp_name = mkstemp(dir=dir, prefix=temp_prefix)
134
135         self.target_name = filename
136         self.temp_name = temp_name
137         self.real_file = os.fdopen(fd, mode)
138
139     def __enter__(self):
140
141         return self
142
143     def __exit__(self, *exc_args):
144
145         if exc_args == (None, None, None,):
146             self.close()
147         else:
148             self.discard()
149
150     def __del__(self):
151
152         try:
153             self.discard()
154         except AttributeError:
155             # If __init__ failed, self has no real_file attribute.
156             pass
157
158     def __close_real(self):
159
160         if self.real_file:
161             self.real_file.close()
162             self.real_file = None
163
164     def clean_stale(self, dir, temp_prefix):
165
166         from time import time
167         from glob import glob
168
169         now = time()
170         pattern = os.path.join(dir, "%s*" % (temp_prefix,))
171
172         for temp_filename in glob(pattern):
173             mtime = os.stat(temp_filename).st_mtime
174             if now - mtime > 3600:
175                 self.logger.info("deleting stale temporary file %s",
176                                  temp_filename)
177                 try:
178                     os.unlink(temp_filename)
179                 except EnvironmentError as exc:
180                     self.logger.warning("deleting stale temporary file "
181                                         "failed: %s", exc)
182
183     def tell(self, *a, **kw):
184
185         return self.real_file.tell(*a, **kw)
186
187     def write(self, *a, **kw):
188
189         return self.real_file.write(*a, **kw)
190
191     def close(self):
192
193         self.__close_real()
194
195         if self.temp_name:
196             try:
197                 os.rename(self.temp_name, self.target_name)
198             except OSError as exc:
199                 import errno
200                 if exc.errno == errno.EEXIST:
201                     # We are probably on windows.
202                     os.unlink(self.target_name)
203                     os.rename(self.temp_name, self.target_name)
204             self.temp_name = None
205
206     def discard(self):
207
208         self.__close_real()
209
210         if self.temp_name:
211
212             try:
213                 os.unlink(self.temp_name)
214             except EnvironmentError as exc:
215                 self.logger.warning("deleting temporary file failed: %s", exc)
216             self.temp_name = None
217
218
219 class TeeWriteFile (object):
220
221     # TODO Py2.5: Add context manager methods.
222
223     def __init__(self, *file_objects):
224
225         self.files = list(file_objects)
226
227     def close(self):
228
229         for file in self.files:
230             file.close()
231
232     def flush(self):
233
234         for file in self.files:
235             file.flush()
236
237     def write(self, string):
238
239         for file in self.files:
240             file.write(string)
241
242     def writelines(self, lines):
243
244         for file in self.files:
245             file.writelines(lines)
246
247
248 class FixedPopen (_subprocess.Popen):
249
250     def __init__(self, args, **kw):
251
252         # Unconditionally specify all descriptors as redirected, to
253         # work around Python bug #1358527 (which is triggered for
254         # console-less applications on Windows).
255
256         close = []
257
258         for name in ("stdin", "stdout", "stderr",):
259             target = kw.get(name)
260             if not target:
261                 kw[name] = _subprocess.PIPE
262                 close.append(name)
263
264         _subprocess.Popen.__init__(self, args, **kw)
265
266         for name in close:
267             fp = getattr(self, name)
268             fp.close()
269             setattr(self, name, None)
270
271
272 class DevhelpError (EnvironmentError):
273
274     pass
275
276
277 class DevhelpUnavailableError (DevhelpError):
278
279     pass
280
281
282 class DevhelpClient (object):
283
284     def available(self):
285
286         try:
287             self.version()
288         except DevhelpUnavailableError:
289             return False
290         else:
291             return True
292
293     def version(self):
294
295         return self._invoke("--version")
296
297     def search(self, entry):
298
299         self._invoke_no_interact("-s", entry)
300
301     def _check_os_error(self, exc):
302
303         import errno
304         if exc.errno == errno.ENOENT:
305             raise DevhelpUnavailableError()
306
307     def _invoke(self, *args):
308
309         from subprocess import PIPE
310
311         try:
312             proc = FixedPopen(("devhelp",) + args,
313                               stdout=PIPE)
314         except OSError as exc:
315             self._check_os_error(exc)
316             raise
317
318         out, err = proc.communicate()
319
320         if proc.returncode is not None and proc.returncode != 0:
321             raise DevhelpError("devhelp exited with status %i"
322                                % (proc.returncode,))
323         return out
324
325     def _invoke_no_interact(self, *args):
326
327         from subprocess import PIPE
328
329         try:
330             proc = FixedPopen(("devhelp",) + args)
331         except OSError as exc:
332             self._check_os_error(exc)
333             raise