Initial packaging for Tizen
[profile/ivi/gobject-introspection.git] / giscanner / cachestore.py
1 # -*- Mode: Python -*-
2 # GObject-Introspection - a framework for introspecting GObject libraries
3 # Copyright (C) 2008-2010  Johan Dahlin
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 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
17 # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 # Boston, MA 02111-1307, USA.
19 #
20
21 import errno
22 import cPickle
23 import glob
24 import hashlib
25 import os
26 import shutil
27 import sys
28 import tempfile
29
30 import giscanner
31
32 _CACHE_VERSION_FILENAME = '.cache-version'
33
34 def _get_versionhash():
35     toplevel = os.path.dirname(giscanner.__file__)
36     # Use pyc instead of py to avoid extra IO
37     sources = glob.glob(os.path.join(toplevel, '*.pyc'))
38     sources.append(sys.argv[0])
39     # Using mtimes is a bit (5x) faster than hashing the file contents
40     mtimes = (str(os.stat(source).st_mtime) for source in sources)
41     return hashlib.sha1(''.join(mtimes)).hexdigest()
42
43 def _get_cachedir():
44     if 'GI_SCANNER_DISABLE_CACHE' in os.environ:
45         return None
46     homedir = os.environ.get('HOME')
47     if homedir is None:
48         return None
49     if not os.path.exists(homedir):
50         return None
51
52     cachedir = os.path.join(homedir, '.cache')
53     if not os.path.exists(cachedir):
54         try:
55             os.mkdir(cachedir, 0755)
56         except OSError:
57             return None
58
59     scannerdir = os.path.join(cachedir, 'g-ir-scanner')
60     if not os.path.exists(scannerdir):
61         try:
62             os.mkdir(scannerdir, 0755)
63         except OSError:
64             return None
65     # If it exists and is a file, don't cache at all
66     elif not os.path.isdir(scannerdir):
67         return None
68     return scannerdir
69
70
71 class CacheStore(object):
72
73     def __init__(self):
74         try:
75             self._directory = _get_cachedir()
76         except OSError, e:
77             if e.errno != errno.EPERM:
78                 raise
79             self._directory = None
80
81         self._check_cache_version()
82
83     def _check_cache_version(self):
84         if self._directory is None:
85             return
86
87         current_hash = _get_versionhash()
88         version = os.path.join(self._directory, _CACHE_VERSION_FILENAME)
89         try:
90             cache_hash = open(version).read()
91         except IOError, e:
92             # File does not exist
93             if e.errno == errno.ENOENT:
94                 cache_hash = 0
95             else:
96                 raise
97
98         if current_hash == cache_hash:
99             return
100
101         self._clean()
102         try:
103             fp = open(version, 'w')
104         except IOError, e:
105             # Permission denied
106             if e.errno == errno.EACCES:
107                 return
108             else:
109                 raise
110
111         fp.write(current_hash)
112
113     def _get_filename(self, filename):
114         # If we couldn't create the directory we're probably
115         # on a read only home directory where we just disable
116         # the cache all together.
117         if self._directory is None:
118             return
119         hexdigest = hashlib.sha1(filename).hexdigest()
120         return os.path.join(self._directory, hexdigest)
121
122     def _cache_is_valid(self, store_filename, filename):
123         return (os.stat(store_filename).st_mtime >=
124                 os.stat(filename).st_mtime)
125
126     def _remove_filename(self, filename):
127         try:
128             os.unlink(filename)
129         except IOError, e:
130             # Permission denied
131             if e.errno == errno.EACCES:
132                 return
133             else:
134                 raise
135         except OSError, e:
136             # File does not exist
137             if e.errno == errno.ENOENT:
138                 return
139             else:
140                 raise
141
142     def _clean(self):
143         for filename in os.listdir(self._directory):
144             if filename == _CACHE_VERSION_FILENAME:
145                 continue
146             self._remove_filename(os.path.join(self._directory, filename))
147
148     def store(self, filename, data):
149         store_filename = self._get_filename(filename)
150         if store_filename is None:
151             return
152
153         if (os.path.exists(store_filename) and
154             self._cache_is_valid(store_filename, filename)):
155             return None
156
157         tmp_fd, tmp_filename = tempfile.mkstemp(prefix='g-ir-scanner-cache-')
158         try:
159             cPickle.dump(data, os.fdopen(tmp_fd, 'w'))
160         except IOError, e:
161             # No space left on device
162             if e.errno == errno.ENOSPC:
163                 self._remove_filename(tmp_filename)
164                 return
165             else:
166                 raise
167
168         try:
169             shutil.move(tmp_filename, store_filename)
170         except IOError, e:
171             # Permission denied
172             if e.errno == errno.EACCES:
173                 self._remove_filename(tmp_filename)
174             else:
175                 raise
176
177     def load(self, filename):
178         store_filename = self._get_filename(filename)
179         if store_filename is None:
180             return
181         try:
182             fd = open(store_filename)
183         except IOError, e:
184             if e.errno == errno.ENOENT:
185                 return None
186             else:
187                 raise
188         if not self._cache_is_valid(store_filename, filename):
189             return None
190         try:
191             data = cPickle.load(fd)
192         except (AttributeError, EOFError, ValueError, cPickle.BadPickleGet):
193             # Broken cache entry, remove it
194             self._remove_filename(store_filename)
195             data = None
196         return data