job_provider.py (13334B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\jobboard\client\job_provider.py 2 import abc 3 import uthread2 4 import logging 5 import gametime 6 from signals import Signal 7 from metadata.common.content_tags import get_content_tag_as_object 8 from jobboard.client import get_job_board_service, job_board_signals 9 from jobboard.client.provider_type import get_job_id 10 from jobboard.client.job_board_settings import get_display_feature_setting 11 from jobboard.client.ui.no_job_container import NoJobContainer 12 logger = logging.getLogger('job_board') 13 14 class JobProvider(object): 15 __metaclass__ = abc.ABCMeta 16 PROVIDER_ID = 'job' 17 PROVIDER_CONTENT_TAGS = [] 18 FEATURE_PAGE_CLASS = None 19 NO_JOB_CONTAINER = NoJobContainer 20 __notifyevents__ = [] 21 22 @classmethod 23 def get_job_id(cls, instance_id): 24 return get_job_id(cls.PROVIDER_ID, instance_id) 25 26 def __init__(self, job_storage): 27 self._job_storage = job_storage 28 self._jobs = {} 29 self._is_dirty = True 30 self._is_registered = False 31 self._fetching = False 32 self.on_job_added = Signal('{}.on_job_added'.format(self.PROVIDER_ID)) 33 self.on_job_removed = Signal('{}.on_job_removed'.format(self.PROVIDER_ID)) 34 self._enabled = self._should_enable() 35 self._register_availability() 36 if self.PROVIDER_CONTENT_TAGS: 37 self._display_feature_setting = get_display_feature_setting(self.PROVIDER_CONTENT_TAGS[0]) 38 self._display_feature_setting.on_change.connect(self._display_feature_setting_changed) 39 else: 40 self._display_feature_setting = None 41 if self._enabled: 42 self._register_events() 43 self._refresh_on_init() 44 45 @property 46 def is_enabled(self): 47 return self._enabled 48 49 @property 50 def is_dirty(self): 51 return self._is_dirty 52 53 @property 54 def service(self): 55 return get_job_board_service() 56 57 @property 58 def feature_tag(self): 59 feature_tag_id = self.feature_tag_id 60 if feature_tag_id: 61 return get_content_tag_as_object(feature_tag_id) 62 63 @property 64 def feature_tag_id(self): 65 if self.PROVIDER_CONTENT_TAGS: 66 return self.PROVIDER_CONTENT_TAGS[0] 67 68 @property 69 def is_hidden(self): 70 if not self.is_enabled: 71 return True 72 if self._display_feature_setting: 73 return not self._display_feature_setting.get() 74 return False 75 76 @abc.abstractmethod 77 def _get_instance_id(self, content): 78 pass 79 80 @abc.abstractmethod 81 def _get_all_content(self): 82 pass 83 84 @abc.abstractmethod 85 def _construct_job(self, job_id, content): 86 pass 87 88 def close(self): 89 self._unregister_availability() 90 self._unregister_events() 91 if self._display_feature_setting: 92 self._display_feature_setting.on_change.disconnect(self._display_feature_setting_changed) 93 self.clear() 94 95 def clear(self): 96 for job in self._jobs.values(): 97 job.clear() 98 99 self._jobs.clear() 100 self._is_dirty = True 101 self._fetching = False 102 job_board_signals.on_job_provider_cleared(self.PROVIDER_ID) 103 104 def get_jobs(self): 105 self.wait_for_provider() 106 return self._jobs 107 108 def get_tracked_jobs(self): 109 result = [] 110 tracked_job_ids = self.get_tracked_job_ids() 111 jobs = self.get_jobs() 112 for tracked_job_id in tracked_job_ids: 113 job = jobs.get(tracked_job_id, None) 114 if job: 115 result.append(job) 116 117 return result 118 119 def wait_for_provider_by_tags(self, content_tag_ids): 120 if any((content_tag_id in self.PROVIDER_CONTENT_TAGS for content_tag_id in content_tag_ids)): 121 self.wait_for_provider() 122 123 def wait_for_provider(self, refresh_jobs = True): 124 if not self._enabled: 125 return 126 if self._is_dirty and refresh_jobs: 127 self.refresh_jobs() 128 while self._fetching: 129 uthread2.Yield() 130 131 def has_active_job_failures(self): 132 return False 133 134 def has_unclaimed_job_failures(self): 135 return False 136 137 def try_fetch_job(self, job_id): 138 self.wait_for_provider() 139 return self.get_job(job_id) 140 141 def get_job(self, job_id): 142 return self._jobs.get(job_id, None) 143 144 def set_dirty(self): 145 self._is_dirty = True 146 147 def get_tracked_job_ids(self): 148 tracked_jobs = settings.char.ui.Get(self._tracked_setting_id, {}) 149 if isinstance(tracked_jobs, list): 150 return {job_id:None for job_id in tracked_jobs} 151 return tracked_jobs 152 153 def toggle_tracked_job_by_player(self, job): 154 if job.job_id not in self.get_tracked_job_ids(): 155 self.add_tracked_job(job, set_expanded=True) 156 self.service.notify_of_job_tracked(job) 157 else: 158 self.remove_tracked_job(job) 159 self.service.notify_of_job_untracked(job) 160 161 def add_tracked_job(self, job, set_expanded = True): 162 job_id = job.job_id 163 if set_expanded: 164 sm.GetService('infoPanel').SetExpandedMission('opportunities', job_id) 165 tracked_job_ids = self.get_tracked_job_ids() 166 tracked_job_ids[job_id] = gametime.GetWallclockTime() 167 self._set_tracked_job_ids(tracked_job_ids) 168 job.on_tracked() 169 170 def remove_tracked_job(self, job): 171 expanded_mission = sm.GetService('infoPanel').GetExpandedMission() 172 if expanded_mission and expanded_mission['missionID'] == job.job_id: 173 sm.GetService('infoPanel').SetExpandedMission('opportunities', -1) 174 tracked_job_ids = self.get_tracked_job_ids() 175 tracked_job_ids.pop(job.job_id, None) 176 self._set_tracked_job_ids(tracked_job_ids) 177 job.on_untracked() 178 179 def get_tracked_job_timestamp(self, job_id): 180 tracked_job_ids = self.get_tracked_job_ids() 181 return tracked_job_ids.get(job_id, None) 182 183 def set_tracked_job_timestamp(self, job_id, timestamp): 184 tracked_job_ids = self.get_tracked_job_ids() 185 tracked_job_ids[job_id] = timestamp 186 settings.char.ui.Set(self._tracked_setting_id, tracked_job_ids) 187 188 def _has_tracked_jobs(self): 189 return bool(self.get_tracked_job_ids()) 190 191 def _update_provider_state(self, enabled): 192 if self._enabled == enabled: 193 return 194 self._enabled = enabled 195 if enabled: 196 self._register_events() 197 self._invalidate_cache() 198 else: 199 self._unregister_events() 200 self._remove_all_jobs() 201 job_board_signals.on_job_provider_state_changed(self) 202 203 @uthread2.debounce(0.2) 204 def _update_jobs(self, jobs = None, update_state = False, **kwargs): 205 for job in jobs or self._jobs.values(): 206 job.update(**kwargs) 207 if update_state: 208 job_board_signals.on_job_state_changed(job) 209 210 def refresh_jobs(self): 211 if not self._enabled or self._fetching: 212 return 213 if not session.charid: 214 logger.warning('No character logged in when refreshing provider %s', self.PROVIDER_ID) 215 return 216 self._fetching = True 217 uthread2.start_tasklet(self.__refresh_jobs) 218 219 def __refresh_jobs(self): 220 logger.info('Refreshing provider %s', self.PROVIDER_ID) 221 job_board_signals.on_job_provider_started_refreshing(self.PROVIDER_ID) 222 self._fetching = True 223 valid_job_ids = set() 224 try: 225 all_content = self._get_all_content() 226 except Exception as e: 227 logger.exception('Failed to fetch jobs for provider %s - e=%s', self.PROVIDER_ID, e) 228 self._fetching = False 229 return 230 231 try: 232 for content in all_content or []: 233 job = self._create_job(content) 234 if job: 235 valid_job_ids.add(job.job_id) 236 237 for job_id in self._jobs.keys(): 238 if job_id not in valid_job_ids: 239 self._remove_job(job_id) 240 241 finally: 242 self._is_dirty = False 243 self._fetching = False 244 self._on_jobs_refreshed() 245 job_board_signals.on_job_provider_finished_refreshing(self.PROVIDER_ID) 246 247 def _on_jobs_refreshed(self): 248 self._cleanup_tracked_jobs() 249 250 def _create_job(self, content): 251 if not self._enabled or not content: 252 return 253 job_id = self.get_job_id(self._get_instance_id(content)) 254 if job_id in self._jobs: 255 job = self._jobs[job_id] 256 job.update(content) 257 else: 258 try: 259 job = self._construct_job(job_id, content) 260 except Exception as e: 261 logger.exception('Failed to construct job e=%s', e) 262 job = None 263 264 if job: 265 self._add_job(job) 266 return job 267 268 def _add_job(self, job): 269 self._jobs[job.job_id] = job 270 self._job_storage.add_job(job) 271 self.on_job_added(job) 272 273 def _remove_job(self, job_id): 274 job = self._jobs.pop(job_id, None) 275 if job: 276 job.on_remove() 277 if job_id in self.get_tracked_job_ids(): 278 self.remove_tracked_job(job) 279 self._job_storage.remove_job(job_id) 280 if job: 281 self.on_job_removed(job) 282 283 def _remove_all_jobs(self): 284 for job_id in self._jobs.keys(): 285 self._remove_job(job_id) 286 287 def _register_events(self): 288 if self._is_registered: 289 return 290 self._register_slots() 291 self._is_registered = True 292 293 def _unregister_events(self): 294 if not self._is_registered: 295 return 296 self._unregister_slots() 297 self._is_registered = False 298 299 def _register_availability(self): 300 pass 301 302 def _unregister_availability(self): 303 pass 304 305 def _register_slots(self): 306 job_board_signals.on_job_viewed.connect(self._on_job_viewed) 307 job_board_signals.on_job_window_initializing.connect(self._on_window_initializing) 308 job_board_signals.on_job_window_maximized.connect(self._on_window_maximized) 309 job_board_signals.on_job_window_minimized.connect(self._on_window_minimized) 310 job_board_signals.on_job_window_closed.connect(self._on_window_closed) 311 sm.RegisterNotify(self) 312 313 def _unregister_slots(self): 314 job_board_signals.on_job_viewed.disconnect(self._on_job_viewed) 315 job_board_signals.on_job_window_initializing.disconnect(self._on_window_initializing) 316 job_board_signals.on_job_window_maximized.disconnect(self._on_window_maximized) 317 job_board_signals.on_job_window_minimized.disconnect(self._on_window_minimized) 318 job_board_signals.on_job_window_closed.disconnect(self._on_window_closed) 319 sm.UnregisterNotify(self) 320 321 def _should_enable(self): 322 return True 323 324 def _display_feature_setting_changed(self, *args, **kwargs): 325 job_board_signals.on_job_provider_state_changed(self) 326 self._update_jobs(update_state=True) 327 if self.is_dirty: 328 self._invalidate_cache() 329 330 def _on_job_viewed(self, job_id): 331 if self._is_dirty and job_id in self._jobs: 332 self.refresh_jobs() 333 334 def _invalidate_cache(self): 335 if self._should_refresh_on_invalidate(): 336 self.refresh_jobs() 337 else: 338 self.set_dirty() 339 340 def _should_refresh_on_invalidate(self): 341 if self._has_tracked_jobs(): 342 return True 343 if self.is_hidden: 344 return False 345 return self.service.is_open 346 347 def _refresh_on_init(self): 348 if self._should_refresh_on_init(): 349 self.refresh_jobs() 350 return True 351 return False 352 353 def _should_refresh_on_init(self): 354 return self._has_tracked_jobs() 355 356 def _on_window_initializing(self): 357 if self._should_refresh_on_window_initializing(): 358 self.refresh_jobs() 359 360 def _should_refresh_on_window_initializing(self): 361 return self.is_dirty and not self.is_hidden 362 363 def _on_window_maximized(self): 364 if self._should_refresh_on_window_maximized(): 365 self.refresh_jobs() 366 367 def _should_refresh_on_window_maximized(self): 368 return self.is_dirty and not self.is_hidden 369 370 def _on_window_minimized(self): 371 pass 372 373 def _on_window_closed(self): 374 pass 375 376 def _cleanup_tracked_jobs(self): 377 tracked_job_ids = self.get_tracked_job_ids() 378 if not tracked_job_ids: 379 return 380 requires_update = False 381 for job_id in tracked_job_ids.keys(): 382 if job_id in self._jobs and self._jobs[job_id].is_trackable: 383 continue 384 if not job_id.startswith(self.PROVIDER_ID): 385 continue 386 tracked_job_ids.pop(job_id, None) 387 requires_update = True 388 389 if requires_update: 390 self._set_tracked_job_ids(tracked_job_ids) 391 else: 392 sm.GetService('infoPanel').UpdateJobBoardPanel() 393 394 def _set_tracked_job_ids(self, tracked_job_ids): 395 settings.char.ui.Set(self._tracked_setting_id, tracked_job_ids) 396 job_board_signals.on_tracked_jobs_changed() 397 398 @property 399 def _tracked_setting_id(self): 400 return 'tracked_opportunities_{}'.format(self.PROVIDER_ID)