resources.py 6.54 KB
Newer Older
Dan Pascu's avatar
Dan Pascu committed
1 2 3

"""Provide access to Blink's resources"""

4
import __main__
5
import imghdr
Dan Pascu's avatar
Dan Pascu committed
6
import os
7
import platform
Dan Pascu's avatar
Dan Pascu committed
8
import sys
9
import pathlib
Dan Pascu's avatar
Dan Pascu committed
10

Dan Pascu's avatar
Dan Pascu committed
11 12
from PyQt5.QtCore import Qt, QBuffer
from PyQt5.QtGui import QIcon, QPixmap
Saul Ibarra's avatar
Saul Ibarra committed
13

14 15 16
from application.python.descriptor import classproperty
from application.python.types import Singleton
from application.system import makedirs, unlink
Saul Ibarra's avatar
Saul Ibarra committed
17

18
from sipsimple.configuration.datatypes import Path
Saul Ibarra's avatar
Saul Ibarra committed
19
from blink.util import run_in_gui_thread
Dan Pascu's avatar
Dan Pascu committed
20 21


22 23 24
__all__ = ['ApplicationData', 'Resources', 'IconManager']


Adrian Georgescu's avatar
Adrian Georgescu committed
25
class DirectoryContextManager(str):
26
    def __enter__(self):
Adrian Georgescu's avatar
Adrian Georgescu committed
27
        self.directory = os.getcwd()
28
        os.chdir(self)
29

30 31 32 33
    def __exit__(self, type, value, traceback):
        os.chdir(self.directory)


34 35 36 37 38 39 40 41 42 43
class ApplicationData(object):
    """Provide access to user data"""

    _cached_directory = None

    @classproperty
    def directory(cls):
        if cls._cached_directory is None:
            if platform.system() == 'Darwin':
                from Foundation import NSApplicationSupportDirectory, NSSearchPathForDirectoriesInDomains, NSUserDomainMask
Adrian Georgescu's avatar
Adrian Georgescu committed
44
                cls._cached_directory = os.path.join(NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, True)[0], 'Blink')
45
            elif platform.system() == 'Windows':
46
                cls._cached_directory = os.path.join(os.environ['APPDATA'], 'Blink')
47
            else:
Adrian Georgescu's avatar
Adrian Georgescu committed
48
                cls._cached_directory = Path('~/.blink').normalized
49 50 51 52
        return DirectoryContextManager(cls._cached_directory)

    @classmethod
    def get(cls, resource):
Dan Pascu's avatar
Dan Pascu committed
53
        return os.path.join(cls.directory, os.path.normpath(resource))
54 55


Dan Pascu's avatar
Dan Pascu committed
56 57 58 59 60 61 62 63
class Resources(object):
    """Provide access to Blink's resources"""

    _cached_directory = None

    @classproperty
    def directory(cls):
        if cls._cached_directory is None:
64
            parent_dir = pathlib.Path(__file__).parent.absolute()
65 66 67 68 69 70 71
            try:
                binary_directory = os.path.dirname(os.path.realpath(__main__.__file__))
            except AttributeError:
                if hasattr(sys, 'frozen'):
                    application_directory = os.path.dirname(os.path.realpath(sys.executable))
                else:
                    application_directory = os.path.realpath('')  # executed in interactive interpreter
Dan Pascu's avatar
Dan Pascu committed
72 73 74 75 76
            else:
                if os.path.basename(binary_directory) == 'bin':
                    application_directory = os.path.dirname(binary_directory)
                else:
                    application_directory = binary_directory
77 78 79 80 81
            unit_test_dir = '%s/../../../../resources' % parent_dir
            if os.path.exists(os.path.join(unit_test_dir, 'blink.ui')):
                # this is to make happy the self unit-testing, there must be a better way -adi
                cls._cached_directory = unit_test_dir
            elif os.path.exists(os.path.join(application_directory, 'resources', 'blink.ui')):
82
                cls._cached_directory = os.path.join(application_directory, 'resources')
Dan Pascu's avatar
Dan Pascu committed
83
            else:
84
                cls._cached_directory = os.path.join(application_directory, 'share', 'blink')
85
        return DirectoryContextManager(cls._cached_directory)
Dan Pascu's avatar
Dan Pascu committed
86 87 88

    @classmethod
    def get(cls, resource):
Dan Pascu's avatar
Dan Pascu committed
89
        return os.path.join(cls.directory, os.path.normpath(resource))
Dan Pascu's avatar
Dan Pascu committed
90 91


Adrian Georgescu's avatar
Adrian Georgescu committed
92
class IconManager(object, metaclass=Singleton):
93 94
    max_size = 256

Dan Pascu's avatar
Dan Pascu committed
95
    def __init__(self):
96 97
        self.iconmap = {}

98
    @run_in_gui_thread(wait=True)
99
    def get(self, id):
100
        id = id.replace('/', '_')
Dan Pascu's avatar
Dan Pascu committed
101
        try:
102
            return self.iconmap[id]
Dan Pascu's avatar
Dan Pascu committed
103 104
        except KeyError:
            pixmap = QPixmap()
105
            filename = ApplicationData.get(os.path.join('images', id + '.png'))
Saul Ibarra's avatar
Saul Ibarra committed
106
            try:
107 108
                with open(filename, 'rb') as f:
                    data = f.read()
Saul Ibarra's avatar
Saul Ibarra committed
109 110
            except (IOError, OSError):
                data = None
Dan Pascu's avatar
Dan Pascu committed
111
            if data is not None and pixmap.loadFromData(data):
112 113
                icon = QIcon(pixmap)
                icon.filename = filename
Saul Ibarra's avatar
Saul Ibarra committed
114
                icon.content = data
115
                icon.content_type = 'image/png'
116 117 118
            else:
                icon = None
            return self.iconmap.setdefault(id, icon)
Dan Pascu's avatar
Dan Pascu committed
119

120
    @run_in_gui_thread(wait=True)
121
    def store_data(self, id, data):
122
        data = data if isinstance(data, bytes) else data.encode()
123
        id = id.replace('/', '_')
124 125 126 127 128
        directory = ApplicationData.get('images')
        filename = os.path.join(directory, id + '.png')
        makedirs(directory)
        pixmap = QPixmap()
        if data is not None and pixmap.loadFromData(data):
129 130
            image_size = pixmap.size()
            if image_size.width() > self.max_size or image_size.height() > self.max_size:
131
                pixmap = pixmap.scaled(self.max_size, self.max_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
132 133 134 135
            if imghdr.what(None, data) != 'png' or pixmap.size() != image_size:
                buffer = QBuffer()
                pixmap.save(buffer, 'png')
                data = str(buffer.data())
Saul Ibarra's avatar
Saul Ibarra committed
136
            with open(filename, 'wb') as f:
137
                data = data if isinstance(data, bytes) else data.encode()
138
                f.write(data)
139 140
            icon = QIcon(pixmap)
            icon.filename = filename
Saul Ibarra's avatar
Saul Ibarra committed
141
            icon.content = data
142
            icon.content_type = 'image/png'
143 144 145 146 147 148
        else:
            unlink(filename)
            icon = None
        self.iconmap[id] = icon
        return icon

149
    @run_in_gui_thread(wait=True)
150
    def store_file(self, id, file):
151
        id = id.replace('/', '_')
152 153 154 155 156
        directory = ApplicationData.get('images')
        filename = os.path.join(directory, id + '.png')
        if filename == os.path.normpath(file):
            return self.iconmap.get(id, None)
        makedirs(directory)
157
        pixmap = QPixmap()
158 159 160
        if file is not None and pixmap.load(file):
            if pixmap.size().width() > self.max_size or pixmap.size().height() > self.max_size:
                pixmap = pixmap.scaled(self.max_size, self.max_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
Saul Ibarra's avatar
Saul Ibarra committed
161 162
            buffer = QBuffer()
            pixmap.save(buffer, 'png')
163
            data = buffer.data()
Saul Ibarra's avatar
Saul Ibarra committed
164
            with open(filename, 'wb') as f:
165
                f.write(data)
166 167
            icon = QIcon(pixmap)
            icon.filename = filename
Saul Ibarra's avatar
Saul Ibarra committed
168
            icon.content = data
169
            icon.content_type = 'image/png'
170
        else:
171 172 173 174 175
            unlink(filename)
            icon = None
        self.iconmap[id] = icon
        return icon

176
    @run_in_gui_thread(wait=True)
177
    def remove(self, id):
178
        id = id.replace('/', '_')
179 180 181
        self.iconmap.pop(id, None)
        unlink(ApplicationData.get(os.path.join('images', id + '.png')))

Dan Pascu's avatar
Dan Pascu committed
182