1 # -*- coding: utf-8; mode: python; -*-
3 # GStreamer Development Utilities
5 # Copyright (C) 2007 René Stadler <mail@renestadler.de>
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)
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
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/>.
20 """GStreamer Development Utilities Common utils module."""
24 import subprocess as _subprocess
27 class SingletonMeta (type):
29 def __init__(cls, name, bases, dict_):
31 from weakref import WeakValueDictionary
33 super(SingletonMeta, cls).__init__(name, bases, dict_)
35 cls._singleton_instances = WeakValueDictionary()
37 def __call__(cls, *a, **kw):
39 kw_key = tuple(sorted(kw.items()))
42 obj = cls._singleton_instances[a + kw_key]
44 obj = super(SingletonMeta, cls).__call__(*a, **kw)
45 cls._singleton_instances[a + kw_key] = obj
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
55 from gettext import gettext
59 def gettext_cache_access(s):
65 return gettext_cache_access
68 class ClassProperty (property):
70 "Like the property class, but also invokes the getter for class access."
72 def __init__(self, fget=None, fset=None, fdel=None, doc=None):
74 property.__init__(self, fget, fset, fdel, doc)
78 def __get__(self, obj, obj_class=None):
80 ret = property.__get__(self, obj, obj_class)
82 return self.__fget(None)
87 class _XDGClass (object):
89 """Partial implementation of the XDG Base Directory specification v0.6.
91 http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html"""
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")
99 def _add_base_dir(self, name, default):
101 dir = os.environ.get("XDG_%s" % (name,))
103 dir = os.path.expanduser(os.path.join(*default.split("/")))
105 setattr(self, name, dir)
111 class SaveWriteFile (object):
113 def __init__(self, filename, mode="wt"):
115 from tempfile import mkstemp
117 self.logger = logging.getLogger("tempfile")
119 dir = os.path.dirname(filename)
120 base_name = os.path.basename(filename)
121 temp_prefix = "%s-tmp" % (base_name,)
124 # Destination dir differs from current directory, ensure that it
131 self.clean_stale(dir, temp_prefix)
133 fd, temp_name = mkstemp(dir=dir, prefix=temp_prefix)
135 self.target_name = filename
136 self.temp_name = temp_name
137 self.real_file = os.fdopen(fd, mode)
143 def __exit__(self, *exc_args):
145 if exc_args == (None, None, None,):
154 except AttributeError:
155 # If __init__ failed, self has no real_file attribute.
158 def __close_real(self):
161 self.real_file.close()
162 self.real_file = None
164 def clean_stale(self, dir, temp_prefix):
166 from time import time
167 from glob import glob
170 pattern = os.path.join(dir, "%s*" % (temp_prefix,))
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",
178 os.unlink(temp_filename)
179 except EnvironmentError as exc:
180 self.logger.warning("deleting stale temporary file "
183 def tell(self, *a, **kw):
185 return self.real_file.tell(*a, **kw)
187 def write(self, *a, **kw):
189 return self.real_file.write(*a, **kw)
197 os.rename(self.temp_name, self.target_name)
198 except OSError as exc:
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
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
219 class TeeWriteFile (object):
221 # TODO Py2.5: Add context manager methods.
223 def __init__(self, *file_objects):
225 self.files = list(file_objects)
229 for file in self.files:
234 for file in self.files:
237 def write(self, string):
239 for file in self.files:
242 def writelines(self, lines):
244 for file in self.files:
245 file.writelines(lines)
248 class FixedPopen (_subprocess.Popen):
250 def __init__(self, args, **kw):
252 # Unconditionally specify all descriptors as redirected, to
253 # work around Python bug #1358527 (which is triggered for
254 # console-less applications on Windows).
258 for name in ("stdin", "stdout", "stderr",):
259 target = kw.get(name)
261 kw[name] = _subprocess.PIPE
264 _subprocess.Popen.__init__(self, args, **kw)
267 fp = getattr(self, name)
269 setattr(self, name, None)
272 class DevhelpError (EnvironmentError):
277 class DevhelpUnavailableError (DevhelpError):
282 class DevhelpClient (object):
288 except DevhelpUnavailableError:
295 return self._invoke("--version")
297 def search(self, entry):
299 self._invoke_no_interact("-s", entry)
301 def _check_os_error(self, exc):
304 if exc.errno == errno.ENOENT:
305 raise DevhelpUnavailableError()
307 def _invoke(self, *args):
309 from subprocess import PIPE
312 proc = FixedPopen(("devhelp",) + args,
314 except OSError as exc:
315 self._check_os_error(exc)
318 out, err = proc.communicate()
320 if proc.returncode is not None and proc.returncode != 0:
321 raise DevhelpError("devhelp exited with status %i"
322 % (proc.returncode,))
325 def _invoke_no_interact(self, *args):
327 from subprocess import PIPE
330 proc = FixedPopen(("devhelp",) + args)
331 except OSError as exc:
332 self._check_os_error(exc)