image_cache_demo_window.py (10753B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\imagecaching\demo\image_cache_demo_window.py 2 import logging 3 import blue 4 import carbonui 5 import eveui 6 import uthread2 7 from carbonui import fontconst, uiconst, Align 8 from carbonui.control.dragResizeCont import DragResizeCont 9 from carbonui.control.singlelineedits.singleLineEditText import SingleLineEditText 10 from carbonui.control.window import Window 11 from carbonui.primitives.container import Container 12 from carbonui.primitives.containerAutoSize import ContainerAutoSize 13 from carbonui.primitives.sprite import Sprite 14 from carbonui.uiconst import PickState 15 from eve.client.script.ui.control.eveEditPlainText import EditPlainText 16 from eve.client.script.ui.control.eveLabel import EveLabelMedium 17 from imagecaching.client.api.repository import get_repository 18 from imagecaching.client.const import INDEX_FILENAME, LOGGER_NAME, OUTCOME_SERVED_STALE, OUTCOME_NOT_MODIFIED, image_cache_path 19 from six.moves.http_client import responses 20 logger = logging.getLogger(LOGGER_NAME) 21 MARGIN = 8 22 PREVIEW_SIZE = 512 23 INDEX_PANEL_HEIGHT = 140 24 INDEX_PANEL_MIN_PROPORTION = 0.15 25 INDEX_PANEL_MAX_PROPORTION = 0.75 26 INDEX_PANEL_SETTINGS_ID = 'imageCacheDemoIndexPanel' 27 DEFAULT_URL = u'https://images.evetech.net/types/587/render?size=512' 28 DEFAULT_FOLDER = u'demo' 29 30 class ImageCacheDemoWindow(Window): 31 __guid__ = 'ImageCacheDemoTool' 32 default_caption = 'Image Cache Demo' 33 default_captionLabelPath = None 34 default_windowID = 'ImageCacheDemoTool' 35 default_iconNum = 'res:/Ui/Texture/WindowIcons/info.png' 36 default_width = PREVIEW_SIZE + 4 * MARGIN 37 default_height = PREVIEW_SIZE + 220 + INDEX_PANEL_HEIGHT + MARGIN 38 default_minSize = (440, 420) 39 panelID = default_windowID 40 41 def ApplyAttributes(self, attributes): 42 super(ImageCacheDemoWindow, self).ApplyAttributes(attributes) 43 self._last_cache_path = None 44 self._last_image_id = None 45 self._last_folder = None 46 self._construct_layout() 47 48 def _construct_layout(self): 49 self.main_container = Container(name='main_container', parent=self.content, padding=MARGIN) 50 self._construct_inputs() 51 self._construct_buttons() 52 self._construct_index_panel() 53 self._construct_path_label() 54 self._construct_preview() 55 56 def _construct_inputs(self): 57 self.input_container = ContainerAutoSize(name='input_container', parent=self.main_container, align=Align.TOTOP, height=70) 58 self.url_edit = SingleLineEditText(name='url_edit', parent=self.input_container, align=Align.TOTOP, setvalue=DEFAULT_URL, height=18, label='Image URL', padding=(0, 10, 0, 0)) 59 self.folder_edit = SingleLineEditText(name='folder_edit', parent=self.input_container, align=Align.TOTOP, setvalue=DEFAULT_FOLDER, height=18, label='Folder (relative to cache:/Storage/imagecache)', padding=(0, 30, 0, 0)) 60 61 def _construct_buttons(self): 62 self.button_container = Container(name='button_container', parent=self.main_container, align=Align.TOTOP, height=70, top=MARGIN) 63 left = 0 64 self.fetch_button = self._construct_button(name='fetch_button', label='FETCH & CACHE', func=self._on_fetch, width=120, left=left, variant=carbonui.ButtonVariant.PRIMARY) 65 left += 120 + MARGIN 66 self.open_button = self._construct_button(name='open_button', label='OPEN FOLDER', func=self._on_open_folder, width=110, left=left, enabled=False) 67 left += 110 + MARGIN 68 self.delete_button = self._construct_button(name='delete_button', label='DELETE', func=self._on_delete, width=80, left=left, enabled=False) 69 left += 80 + MARGIN 70 self.index_button = self._construct_button(name='index_button', label='SHOW INDEX', func=self._on_show_index, width=110, left=left) 71 72 def _construct_button(self, name, label, func, width, left, variant = None, enabled = True): 73 return eveui.Button(name=name, parent=self.button_container, state=eveui.State.normal, align=Align.CENTERLEFT, variant=variant or carbonui.ButtonVariant.GHOST, density=carbonui.Density.COMPACT, label=label, func=lambda x: func(), width=width, left=left, enabled=enabled) 74 75 def _construct_preview(self): 76 self.preview_container = Container(name='preview_container', parent=self.main_container, align=Align.TOALL, padTop=MARGIN, border_style='bright_blue') 77 self.preview = Sprite(name='preview', parent=self.preview_container, align=Align.CENTER_PRESERVE_ASPECT, state=uiconst.UI_NORMAL, pickState=PickState.OFF) 78 79 def _construct_path_label(self): 80 self.path_label = EveLabelMedium(name='path_label', parent=self.main_container, align=Align.TOBOTTOM, padding=(0, 81 MARGIN, 82 0, 83 MARGIN), text='No image cached yet.') 84 85 def _construct_index_panel(self): 86 self.index_panel = DragResizeCont(name='index_panel', parent=self.main_container, align=Align.TOBOTTOM, defaultSize=INDEX_PANEL_HEIGHT, minSize=INDEX_PANEL_MIN_PROPORTION, maxSize=INDEX_PANEL_MAX_PROPORTION, settingsID=INDEX_PANEL_SETTINGS_ID, show_line=True) 87 self.index_text = EditPlainText(name='index_text', parent=self.index_panel.mainCont, align=Align.TOALL, readonly=True, ignoreTags=True, fontPath=fontconst.CONSOLA_PATH, fontsize=12) 88 self._set_index_text(u'Index not read yet - press SHOW INDEX.') 89 90 def _on_fetch(self): 91 url = self.url_edit.GetValue() 92 folder = self.folder_edit.GetValue() 93 if not url: 94 self.path_label.text = 'Enter an image URL first.' 95 return 96 image_id = url 97 cache_path = image_cache_path(folder, image_id) 98 self.path_label.text = 'Fetching...' 99 self.fetch_button.enabled = False 100 uthread2.StartTasklet(self._fetch_worker, image_id, url, cache_path, folder) 101 102 def _fetch_worker(self, image_id, url, cache_path, folder): 103 res_path = None 104 status_code = None 105 error_text = None 106 outcome = None 107 try: 108 repo = get_repository() 109 res_path = repo.cache_image(image_id, url, cache_path) 110 status_code = repo.last_status_code 111 error_text = repo.last_error 112 outcome = repo.last_outcome 113 except Exception as e: 114 logger.exception('Image cache demo: failed to cache %s', url) 115 error_text = u'%s' % (e,) 116 finally: 117 self._on_fetch_done(image_id, folder, res_path, status_code, error_text, outcome) 118 119 def _on_fetch_done(self, image_id, folder, res_path, status_code = None, error_text = None, outcome = None): 120 if self.destroyed: 121 return 122 self.fetch_button.enabled = True 123 self._start_index_refresh(folder) 124 if res_path is None: 125 self.preview.texturePath = None 126 text = u'Failed to fetch/cache the image.%s' % self._describe_status(status_code) 127 if error_text: 128 text += u' %s' % error_text 129 self.path_label.text = text 130 self.open_button.enabled = False 131 self.delete_button.enabled = False 132 return 133 self._last_cache_path = res_path 134 self._last_image_id = image_id 135 self._last_folder = folder 136 self.preview.texturePath = res_path 137 self.path_label.text = u'%s\n%s' % (self._describe_outcome(outcome, status_code, error_text), res_path) 138 self.open_button.enabled = True 139 self.delete_button.enabled = True 140 141 @classmethod 142 def _describe_outcome(cls, outcome, status_code, error_text): 143 if outcome == OUTCOME_NOT_MODIFIED: 144 return u'Already cached - ETag matched, not re-downloaded.%s' % cls._describe_status(status_code) 145 if outcome == OUTCOME_SERVED_STALE: 146 detail = cls._describe_status(status_code) 147 if error_text: 148 detail += u' %s' % error_text 149 return u'Could not refresh; showing previously cached copy.%s' % detail 150 return u'Downloaded.%s' % cls._describe_status(status_code) 151 152 @staticmethod 153 def _describe_status(status_code): 154 if status_code is None: 155 return u'' 156 return u' (HTTP %s %s)' % (status_code, responses.get(status_code, u'?')) 157 158 def _on_open_folder(self): 159 if not self._last_cache_path: 160 return 161 folder_uri = self._last_cache_path.rsplit(u'/', 1)[0] 162 fs_folder = blue.paths.ResolvePath(folder_uri) 163 blue.os.ShellExecute(fs_folder) 164 165 def _on_show_index(self): 166 self._start_index_refresh(self.folder_edit.GetValue()) 167 168 def _on_delete(self): 169 if not self._last_image_id: 170 return 171 try: 172 get_repository().delete_image(self._last_image_id, self._last_cache_path) 173 except Exception: 174 logger.exception('Image cache demo: failed to delete %s', self._last_image_id) 175 self.path_label.text = 'Failed to delete the cached image.' 176 return 177 178 folder = self._last_folder 179 self.preview.texturePath = None 180 self.path_label.text = 'Deleted: %s' % self._last_cache_path 181 self._last_cache_path = None 182 self._last_image_id = None 183 self._last_folder = None 184 self.open_button.enabled = False 185 self.delete_button.enabled = False 186 self._start_index_refresh(folder) 187 188 def _start_index_refresh(self, folder): 189 if not folder: 190 return 191 uthread2.StartTasklet(self._index_worker, folder) 192 193 def _index_worker(self, folder): 194 entries = [] 195 error_text = None 196 try: 197 entries = get_repository().list_entries(folder) 198 except Exception as e: 199 logger.exception('Image cache demo: failed to read the index of %s', folder) 200 error_text = u'%s' % (e,) 201 202 self._on_index_read(folder, entries, error_text) 203 204 def _on_index_read(self, folder, entries, error_text = None): 205 if self.destroyed: 206 return 207 index_path = u'%s/%s' % (folder, INDEX_FILENAME) 208 if error_text: 209 self._set_index_text(u'Could not read %s: %s' % (index_path, error_text)) 210 return 211 lines = [u'%s - %s' % (index_path, self._describe_entry_count(len(entries)))] 212 lines.extend((self._describe_entry(entry) for entry in entries)) 213 self._set_index_text(u'\n'.join(lines)) 214 215 def _set_index_text(self, text): 216 self.index_text.SetValue(text, scrolltotop=1) 217 218 @staticmethod 219 def _describe_entry_count(count): 220 if count == 1: 221 return u'1 entry' 222 return u'%s entries' % count 223 224 @staticmethod 225 def _describe_entry(entry): 226 return u'%s\n res_path: %s\n e_tag: %s' % (entry.identifier.image_id, entry.attributes.res_path, entry.attributes.e_tag)