summaryrefslogtreecommitdiff
path: root/giscanner/cachestore.py
blob: f7f0062ac35c32e399a92154762c746073020e90 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# -*- Mode: Python -*-
# GObject-Introspection - a framework for introspecting GObject libraries
# Copyright (C) 2008  Johan Dahlin
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
#

import errno
import cPickle
import hashlib
import os
import tempfile


def _get_cachedir():
    homedir = os.environ.get('HOME')
    if homedir is None:
        return None
    if not os.path.exists(homedir):
        return None

    cachedir = os.path.join(homedir, '.cache')
    if not os.path.exists(cachedir):
        os.mkdir(cachedir, 0755)

    scannerdir = os.path.join(cachedir, 'g-ir-scanner')
    if not os.path.exists(scannerdir):
        os.mkdir(scannerdir, 0755)
    # If it exists and is a file, don't cache at all
    elif not os.path.isdir(scannerdir):
        return None
    return scannerdir


class CacheStore(object):

    def __init__(self):
        try:
            self._directory = _get_cachedir()
        except OSError, e:
            if e.errno != errno.EPERM:
                raise
            self._directory = None

    def _get_filename(self, filename):
        # If we couldn't create the directory we're probably
        # on a read only home directory where we just disable
        # the cache all together.
        if self._directory is None:
            return
        hexdigest = hashlib.sha1(filename).hexdigest()
        return os.path.join(self._directory, hexdigest)

    def _cache_is_valid(self, store_filename, filename):
        return (os.stat(store_filename).st_mtime >=
                os.stat(filename).st_mtime)

    def _remove_filename(filename):
        try:
            os.unlink(filename)
        except IOError, e:
            # Permission denied
            if e.errno == errno.EACCES:
                return
            else:
                raise
        except OSError, e:
            # File does not exist
            if e.errno == errno.ENOENT:
                return
            else:
                raise

    def store(self, filename, data):
        store_filename = self._get_filename(filename)
        if store_filename is None:
            return

        if (os.path.exists(store_filename) and
            self._cache_is_valid(store_filename, filename)):
            return None

        tmp_fd, tmp_filename = tempfile.mkstemp(prefix='g-ir-scanner-cache-')
        try:
            cPickle.dump(data, os.fdopen(tmp_fd, 'w'))
        except IOError, e:
            # No space left on device
            if e.errno == errno.ENOSPC:
                self._remove_filename(tmp_filename)
                return
            else:
                raise

        try:
            os.rename(tmp_filename, store_filename)
        except OSError, e:
            # Permission denied
            if e.errno == errno.EACCES:
                self._remove_filename(tmp_filename)
            else:
                raise

    def load(self, filename):
        store_filename = self._get_filename(filename)
        if store_filename is None:
            return
        try:
            fd = open(store_filename)
        except IOError, e:
            if e.errno == errno.ENOENT:
                return None
            else:
                raise
        if not self._cache_is_valid(store_filename, filename):
            return None
        try:
            data = cPickle.load(fd)
        except (EOFError, ValueError, cPickle.BadPickleGet):
            # Broken cache entry, remove it
            self._remove_filename(store_filename)
            data = None
        return data