Imported Upstream version 1.61.1
[platform/upstream/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 glob
23 import hashlib
24 import os
25 import shutil
26 import sys
27 import tempfile
28 import pickle
29
30 import giscanner
31
32 from . import utils
33
34
35 _CACHE_VERSION_FILENAME = '.cache-version'
36
37
38 def _get_versionhash():
39     toplevel = os.path.dirname(giscanner.__file__)
40     sources = glob.glob(os.path.join(toplevel, '*.py'))
41     sources.append(sys.argv[0])
42     # Using mtimes is a bit (5x) faster than hashing the file contents
43     mtimes = (str(os.stat(source).st_mtime) for source in sources)
44     # ASCII encoding is sufficient since we are only dealing with numbers.
45     return hashlib.sha1(''.join(mtimes).encode('ascii')).hexdigest()
46
47
48 class CacheStore(object):
49
50     def __init__(self):
51         self._directory = self._get_cachedir()
52         self._check_cache_version()
53
54     def _get_cachedir(self):
55         if 'GI_SCANNER_DISABLE_CACHE' in os.environ:
56             return None
57         else:
58             cachedir = utils.get_user_cache_dir('g-ir-scanner')
59             return cachedir
60
61     def _check_cache_version(self):
62         if self._directory is None:
63             return
64
65         current_hash = _get_versionhash()
66         version = os.path.join(self._directory, _CACHE_VERSION_FILENAME)
67         try:
68             with open(version, 'r') as version_file:
69                 cache_hash = version_file.read()
70         except (IOError, OSError) as e:
71             # File does not exist
72             if e.errno == errno.ENOENT:
73                 cache_hash = 0
74             else:
75                 raise
76
77         if current_hash == cache_hash:
78             return
79
80         self._clean()
81
82         tmp_fd, tmp_filename = tempfile.mkstemp(prefix='g-ir-scanner-cache-version-')
83         try:
84             with os.fdopen(tmp_fd, 'w') as tmp_file:
85                 tmp_file.write(current_hash)
86
87             # On Unix, this would just be os.rename() but Windows
88             # doesn't allow that.
89             shutil.move(tmp_filename, version)
90         except (IOError, OSError) as e:
91             # Permission denied
92             if e.errno == errno.EACCES:
93                 return
94             else:
95                 raise
96
97     def _get_filename(self, filename):
98         # If we couldn't create the directory we're probably
99         # on a read only home directory where we just disable
100         # the cache all together.
101         if self._directory is None:
102             return
103         # Assume UTF-8 encoding for the filenames. This doesn't matter so much
104         # as long as the results of this method always produce the same hash.
105         hexdigest = hashlib.sha1(filename.encode('utf-8')).hexdigest()
106         return os.path.join(self._directory, hexdigest)
107
108     def _cache_is_valid(self, store_filename, filename):
109         try:
110             store_mtime = os.stat(store_filename).st_mtime
111         except FileNotFoundError:
112             return False
113
114         return store_mtime >= os.stat(filename).st_mtime
115
116     def _remove_filename(self, filename):
117         try:
118             os.unlink(filename)
119         except (IOError, OSError) as e:
120             # Ignore "permission denied", "file does not exist"
121             if e.errno in (errno.EACCES, errno.ENOENT):
122                 return
123             else:
124                 raise
125
126     def _clean(self):
127         for filename in os.listdir(self._directory):
128             if filename == _CACHE_VERSION_FILENAME:
129                 continue
130             self._remove_filename(os.path.join(self._directory, filename))
131
132     def store(self, filename, data):
133         store_filename = self._get_filename(filename)
134         if store_filename is None:
135             return
136
137         if self._cache_is_valid(store_filename, filename):
138             return None
139
140         tmp_fd, tmp_filename = tempfile.mkstemp(prefix='g-ir-scanner-cache-')
141         try:
142             with os.fdopen(tmp_fd, 'wb') as tmp_file:
143                 pickle.dump(data, tmp_file)
144         except (IOError, OSError) as e:
145             # No space left on device
146             if e.errno == errno.ENOSPC:
147                 self._remove_filename(tmp_filename)
148                 return
149             else:
150                 raise
151
152         try:
153             shutil.move(tmp_filename, store_filename)
154         except (IOError, OSError) as e:
155             # Permission denied
156             if e.errno == errno.EACCES:
157                 self._remove_filename(tmp_filename)
158             else:
159                 raise
160
161     def load(self, filename):
162         store_filename = self._get_filename(filename)
163         if store_filename is None:
164             return
165         try:
166             fd = open(store_filename, 'rb')
167         except (IOError, OSError) as e:
168             if e.errno == errno.ENOENT:
169                 return None
170             else:
171                 raise
172         if not self._cache_is_valid(store_filename, filename):
173             return None
174         try:
175             data = pickle.load(fd)
176         except Exception:
177             # Broken cache entry, remove it
178             self._remove_filename(store_filename)
179             data = None
180         return data