1# Copyright (c) 2017-2021 Cedric Bellegarde <cedric.bellegarde@adishatz.org> 2# This program is free software: you can redistribute it and/or modify 3# it under the terms of the GNU General Public License as published by 4# the Free Software Foundation, either version 3 of the License, or 5# (at your option) any later version. 6# This program is distributed in the hope that it will be useful, 7# but WITHOUT ANY WARRANTY; without even the implied warranty of 8# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9# GNU General Public License for more details. 10# You should have received a copy of the GNU General Public License 11# along with this program. If not, see <http://www.gnu.org/licenses/>. 12 13from gi.repository import Gdk, GdkPixbuf, Gio, GLib 14 15from hashlib import md5 16from time import time 17from urllib.parse import urlparse 18 19from eolie.define import EOLIE_CACHE_PATH, ArtSize 20from eolie.utils import get_round_surface 21from eolie.logger import Logger 22 23 24class Art: 25 """ 26 Base art manager 27 """ 28 29 __CACHE_DELTA = 43200 30 31 def __init__(self): 32 """ 33 Init base art 34 """ 35 self.__use_cache = True 36 self.__create_cache() 37 38 def disable_cache(self): 39 """ 40 Disable cache 41 """ 42 self.__use_cache = False 43 44 def save_artwork(self, uri, surface, suffix): 45 """ 46 Save artwork for uri with suffix 47 @param uri as str 48 @param surface as cairo.surface 49 @param suffix as str 50 """ 51 try: 52 parsed = urlparse(uri) 53 if parsed.scheme in ["http", "https"]: 54 filepath = self.get_path(uri, suffix) 55 pixbuf = Gdk.pixbuf_get_from_surface(surface, 0, 0, 56 surface.get_width(), 57 surface.get_height()) 58 pixbuf.savev(filepath, "png", [None], [None]) 59 except Exception as e: 60 Logger.error("Art::save_artwork(): %s", e) 61 62 def get_artwork(self, uri, suffix, scale_factor, width, heigth): 63 """ 64 @param uri as str 65 @param suffix as str 66 @param scale factor as int 67 @param width as int 68 @param height as int 69 @return cairo.surface 70 """ 71 if not uri: 72 return None 73 filepath = self.get_path(uri, suffix) 74 try: 75 if GLib.file_test(filepath, GLib.FileTest.IS_REGULAR): 76 pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(filepath, 77 width, 78 heigth, 79 True) 80 surface = Gdk.cairo_surface_create_from_pixbuf(pixbuf, 81 scale_factor, 82 None) 83 return surface 84 except: 85 pass 86 return None 87 88 def get_favicon(self, uri, scale_factor): 89 """ 90 @param uri as str 91 @param suffix as str 92 @param scale factor as int 93 @return cairo.surface 94 """ 95 try: 96 if not uri: 97 return None 98 filepath = self.get_favicon_path(uri) 99 if filepath is not None and\ 100 GLib.file_test(filepath, GLib.FileTest.IS_REGULAR): 101 pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale( 102 filepath, 103 ArtSize.FAVICON * scale_factor, 104 ArtSize.FAVICON * scale_factor, 105 True) 106 surface = Gdk.cairo_surface_create_from_pixbuf(pixbuf, 107 scale_factor, 108 None) 109 surface = get_round_surface(surface, 110 scale_factor, 111 ArtSize.FAVICON / 4) 112 return surface 113 except Exception as e: 114 Logger.debug("Art::get_favicon(): %s", e) 115 return None 116 117 def get_icon_theme_artwork(self, uri, ephemeral): 118 """ 119 Get artwork from icon theme 120 @param uri as str 121 @param ephemeral as bool 122 @return artwork as str/None 123 """ 124 if ephemeral: 125 return "user-not-tracked-symbolic" 126 elif uri == "populars:": 127 return "emote-love-symbolic" 128 elif uri == "about:": 129 return "web-browser-symbolic" 130 else: 131 return None 132 133 def get_favicon_path(self, uri): 134 """ 135 Return favicon cache path for uri 136 @param uri as str/None 137 @return str/None 138 """ 139 if uri is None: 140 return None 141 favicon_path = self.get_path(uri, "favicon") 142 if favicon_path is not None and\ 143 GLib.file_test(favicon_path, GLib.FileTest.IS_REGULAR): 144 return favicon_path 145 return None 146 147 def get_path(self, uri, suffix): 148 """ 149 Return cache image path 150 @param uri as str 151 @param suffix as str 152 @return str/None 153 """ 154 parsed = urlparse(uri) 155 if uri is None or not parsed.netloc: 156 return None 157 path = "%{}{}%".format(parsed.netloc, parsed.path) 158 encoded = md5(path.encode("utf-8")).hexdigest() 159 filepath = "%s/art/%s_%s.png" % (EOLIE_CACHE_PATH, encoded, suffix) 160 return filepath 161 162 def uncache(self, uri, suffix): 163 """ 164 Remove from cache 165 @param uri as str 166 @param suffix as str 167 """ 168 try: 169 f = Gio.File.new_for_path(self.get_path(uri, suffix)) 170 f.delete() 171 except Exception as e: 172 Logger.debug("Art::uncache(): %s", e) 173 174 def exists(self, uri, suffix): 175 """ 176 Check if file exists and is cached 177 @param uri as str (raise exception if None) 178 @param suffix as str 179 @return exists as bool 180 """ 181 filepath = self.get_path(uri, suffix) 182 if filepath is None: 183 return True # Because we know Lollypop will do nothing on True 184 f = Gio.File.new_for_path(filepath) 185 exists = f.query_exists() 186 if exists and self.__use_cache: 187 info = f.query_info('time::modified', 188 Gio.FileQueryInfoFlags.NONE, 189 None) 190 mtime = int(info.get_attribute_as_string('time::modified')) 191 return time() - mtime < self.__CACHE_DELTA 192 else: 193 return False 194 195 def vacuum(self): 196 """ 197 Remove artwork older than 1 month 198 """ 199 current_time = time() 200 try: 201 d = Gio.File.new_for_path("%s/art" % EOLIE_CACHE_PATH) 202 children = d.enumerate_children("standard::name", 203 Gio.FileQueryInfoFlags.NONE, 204 None) 205 for child in children: 206 f = children.get_child(child) 207 if child.get_file_type() == Gio.FileType.REGULAR: 208 info = f.query_info("time::modified", 209 Gio.FileQueryInfoFlags.NONE, 210 None) 211 mtime = info.get_attribute_uint64("time::modified") 212 if current_time - mtime > 31536000: 213 f.delete() 214 except Exception as e: 215 Logger.error("Art::vacuum(): %s", e) 216 217####################### 218# PRIVATE # 219####################### 220 def __create_cache(self): 221 """ 222 Create cache dir 223 """ 224 if not GLib.file_test("%s/art" % EOLIE_CACHE_PATH, 225 GLib.FileTest.IS_DIR): 226 try: 227 GLib.mkdir_with_parents(EOLIE_CACHE_PATH, 0o0750) 228 GLib.mkdir_with_parents("%s/art" % EOLIE_CACHE_PATH, 0o0750) 229 GLib.mkdir_with_parents("%s/css" % EOLIE_CACHE_PATH, 0o0750) 230 except Exception as e: 231 Logger.error("Art::__create_cache(): %s", e) 232