File: cache.py

package info (click to toggle)
python-qpageview 0.6.2-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 780 kB
  • sloc: python: 5,215; makefile: 22
file content (140 lines) | stat: -rw-r--r-- 4,446 bytes parent folder | download | duplicates (2)
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
136
137
138
139
140
# -*- coding: utf-8 -*-
#
# This file is part of the qpageview package.
#
# Copyright (c) 2016 - 2019 by Wilbert Berendsen
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
# See http://www.gnu.org/licenses/ for more information.

"""
Cache logic.
"""

import weakref
import time


class ImageEntry:
    def __init__(self, image):
        self.image = image
        self.bcount = image.byteCount()
        self.time = time.time()


class ImageCache:
    """Cache generated images.

    Store and retrieve them under a key (see render.Renderer.key()).

    """
    maxsize = 209715200 # 200M
    currentsize = 0

    def __init__(self):
        self._cache = weakref.WeakKeyDictionary()

    def clear(self):
        """Remove all cached images."""
        self._cache.clear()
        self.currentsize = 0

    def invalidate(self, page):
        """Clear cache contents for the specified page."""
        try:
            del self._cache[page.group()][page.ident()]
        except KeyError:
            pass

    def tileset(self, key):
        """Return a dictionary with tile-entry pairs for the key.

        If no single tile is available, an empty dict is returned.

        """
        try:
            return self._cache[key.group][key.ident][key[2:]]
        except KeyError:
            return {}

    def addtile(self, key, tile, image):
        """Add image for the specified key and tile."""
        d = self._cache.setdefault(key.group, {}).setdefault(key.ident, {}).setdefault(key[2:], {})
        try:
            self.currentsize -= d[tile].bcount
        except KeyError:
            pass

        purgeneeded = self.currentsize > self.maxsize

        e = d[tile] = ImageEntry(image)
        self.currentsize += e.bcount

        if not purgeneeded:
            return

        # purge old images is needed,
        # cache groups may have disappeared so count all images

        entries = iter(sorted(
            ((entry.time, entry.bcount, group, ident, key, tile)
            for group, identd in self._cache.items()
                for ident, keyd in identd.items()
                    for key, tiled in keyd.items()
                        for tile, entry in tiled.items()),
            key=(lambda item: item[:2]), reverse=True))

        # now count the newest images until maxsize ...
        currentsize = 0
        for time, bcount, group, ident, key, tile in entries:
            currentsize += bcount
            if currentsize > self.maxsize:
                break
        self.currentsize = currentsize
        # ... and delete the remaining images, deleting empty dicts as well
        for time, bcount, group, ident, key, tile in entries:
            del self._cache[group][ident][key][tile]
            if not self._cache[group][ident][key]:
                del self._cache[group][ident][key]
                if not self._cache[group][ident]:
                    del self._cache[group][ident]
                    if not self._cache[group]:
                        del self._cache[group]

    def closest(self, key):
        """Iterate over suitable image tilesets but with a different size.

        Yields (width, height, tileset) tuples.

        This can be used for interim display while the real image is being
        rendered.

        """
        # group and ident must be there.
        try:
            keyd = self._cache[key.group][key.ident]
        except KeyError:
            return ()

        # prevent returning images that are too small
        minwidth = min(100, key.width / 2)

        suitable = [
            (k[1], k[2], tileset)
            for k, tileset in keyd.items()
                if k[0] == key.rotation and k[1] != key.width and k[1] > minwidth]
        return sorted(suitable, key=lambda s: abs(1 - s[0] / key.width))