Skip to content

Commit 83bd39e

Browse files
committed
Adding functionality to build cache (experimental)
1 parent 8e75fd6 commit 83bd39e

6 files changed

Lines changed: 196 additions & 24 deletions

File tree

main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,7 @@ def launch():
601601
help='Provide this flag to build the cache instead of starting the app.')
602602
args, _ = argparser.parse_known_args()
603603
if args.build_cache:
604-
exit_code = build_cache(Path(Launcher.base_path(), '.config'))
604+
exit_code = build_cache(Path(Launcher.base_path()))
605605
sys.exit(exit_code)
606606
exit_code = SETS(
607607
theme=Launcher.theme, args=args,

src/app.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
ABOTTOM, ACENTER, AHCENTER, ALEFT, ARIGHT, ATOP, AVCENTER, CAREERS, FACTIONS, MARKS,
99
PRIMARY_SPECS, RARITIES, SCROLLOFF, SCROLLON, SECONDARY_SPECS, SMAXMAX, SMAXMIN, SMINMAX,
1010
SMINMIN)
11+
from .datafunctions import cache_skills
1112
from .iofunc import (
1213
create_folder, delete_folder_contents, get_asset_path, load_icon, load_json, open_url,
1314
store_json)
@@ -31,7 +32,7 @@ class SETS():
3132
skill_unlock_callback, spec_combo_callback, species_combo_callback, switch_main_tab,
3233
tier_callback)
3334
from .datafunctions import (
34-
autosave, backup_cargo_data, cache_skills, empty_build,
35+
autosave, backup_cargo_data, empty_build,
3536
init_backend, load_legacy_build_image)
3637
from .export import get_build_markdown
3738
from .splash import enter_splash, exit_splash, splash_text
@@ -430,7 +431,7 @@ def setup_build_frames(self):
430431
"""
431432
self.setup_space_build_frame()
432433
self.setup_ground_build_frame()
433-
self.cache_skills()
434+
cache_skills(self.cache.skills, self.app_dir)
434435
self.setup_space_skill_frame()
435436
self.setup_ground_skill_frame()
436437

src/buildupdater.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def load_build(self):
2525
self.widgets.ship['button'].setText(ship)
2626
ship_data = self.cache.ships[ship]
2727
exec_in_thread(
28-
self, get_ship_image, self, ship_data['image'],
28+
self, get_ship_image, self, ship_data['image'][5:],
2929
result=lambda img: self.widgets.ship['image'].set_image(*img))
3030
tier = self.build['space']['tier']
3131
ship_tier = ship_data['tier']

src/callbacks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ def select_ship(self):
323323
self.widgets.ship['button'].setText(new_ship)
324324
ship_data = self.cache.ships[new_ship]
325325
exec_in_thread(
326-
self, get_ship_image, self, ship_data['image'],
326+
self, get_ship_image, self, ship_data['image'][5:],
327327
result=lambda img: self.widgets.ship['image'].set_image(*img))
328328
tier = ship_data['tier']
329329
self.widgets.ship['tier'].clear()

src/datafunctions.py

Lines changed: 100 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from requests.exceptions import (
1212
ConnectionError as requests__ConnectionError, Timeout as requests__Timeout)
1313
from requests_html import Element
14+
from urllib.parse import unquote_plus
1415

1516
from .buildupdater import get_boff_spec, load_build, load_skill_pages
1617
from .constants import (
@@ -19,8 +20,9 @@
1920
STARSHIP_TRAIT_QUERY_URL, TRAIT_QUERY_URL, WIKI_IMAGE_URL)
2021
from .iofunc import (
2122
auto_backup_cargo_file, browse_path, cache_cargo_data, copy_file, download_image,
22-
fetch_html, get_asset_path, get_cached_cargo_data, get_cargo_data, get_downloaded_images,
23-
image, load_image, load_json, read_env_file, retrieve_image, store_json, store_to_cache)
23+
download_images_fast, fetch_html, get_asset_path, get_cached_cargo_data, get_cargo_data,
24+
get_downloaded_images, image, load_image, load_json, read_env_file, retrieve_image,
25+
store_json, store_to_cache)
2426
from .splash import enter_splash, exit_splash, splash_text
2527
from .textedit import (
2628
create_equipment_tooltip, create_trait_tooltip, dewikify, parse_wikitext,
@@ -347,7 +349,8 @@ def download_images(self, threaded_worker: ThreadObject):
347349
no_retry_images.add(img)
348350
else:
349351
self.cache.images_failed.pop(img)
350-
images = self.cache.images_set - no_retry_images - get_downloaded_images(self)
352+
images = self.cache.images_set - no_retry_images - get_downloaded_images(
353+
Path(self.config['config_subfolders']['images']))
351354
img_folder = self.config['config_subfolders']['images']
352355

353356
images_to_download = images - self.cache.boff_abilities['all'].keys()
@@ -378,16 +381,16 @@ def cache_doff_single(self, cache: dict, doff: dict):
378381
cache[doff['spec']][doff['description']] = doff
379382

380383

381-
def cache_skills(self):
384+
def cache_skills(skill_cache: dict[str, dict], app_directory: str):
382385
"""
383386
Loads skills into cache.
384387
"""
385-
space_skill_data = load_json(get_asset_path('space_skills.json', self.app_dir))
386-
self.cache.skills['space'] = space_skill_data['space']
387-
self.cache.skills['space_unlocks'] = space_skill_data['space_unlocks']
388-
ground_skill_data = load_json(get_asset_path('ground_skills.json', self.app_dir))
389-
self.cache.skills['ground'] = ground_skill_data['ground']
390-
self.cache.skills['ground_unlocks'] = ground_skill_data['ground_unlocks']
388+
space_skill_data = load_json(get_asset_path('space_skills.json', app_directory))
389+
skill_cache['space'] = space_skill_data['space']
390+
skill_cache['space_unlocks'] = space_skill_data['space_unlocks']
391+
ground_skill_data = load_json(get_asset_path('ground_skills.json', app_directory))
392+
skill_cache['ground'] = ground_skill_data['ground']
393+
skill_cache['ground_unlocks'] = ground_skill_data['ground_unlocks']
391394

392395

393396
def autosave(self):
@@ -1053,14 +1056,74 @@ class ThisIsTerribleError(RuntimeError):
10531056
store_json(self.cache.boff_abilities, filepath)
10541057

10551058

1056-
def build_cache(config_path: Path) -> int:
1059+
def get_icon_set(cargo_dir: Path) -> set[str]:
1060+
"""
1061+
Creates set of all required icons from cargo data and required static images.
1062+
1063+
Parameters:
1064+
- :param cargo_dir: path to cargo data directory
1065+
"""
1066+
images_set = set()
1067+
equipment_cargo_data = load_json(str(cargo_dir / 'equipment.json'))
1068+
equipment_types = set(EQUIPMENT_TYPES.keys())
1069+
elite_hangar = {
1070+
'Hangar - Elite Federation Mission Scout Ships',
1071+
'Hangar - Elite Valor Fighters'
1072+
}
1073+
for item in equipment_cargo_data:
1074+
if item['type'] in equipment_types:
1075+
if item['type'] == 'Hangar Bay' and item['name'] not in elite_hangar and (
1076+
item['name'].startswith('Hangar - Advanced')
1077+
or item['name'].startswith('Hangar - Elite')):
1078+
continue
1079+
images_set.add(sanitize_equipment_name(item['name']))
1080+
trait_cargo_data = load_json(str(cargo_dir / 'traits.json'))
1081+
for trait in trait_cargo_data:
1082+
if trait['type'] != 'doff' and trait['type'] != 'boff' and trait['name'] is not None:
1083+
if trait['icon_name'] is None:
1084+
images_set.add(trait['name'])
1085+
else:
1086+
images_set.add(trait['icon_name'])
1087+
shiptrait_cargo_data = load_json(str(cargo_dir / 'starship_traits.json'))
1088+
images_set |= set(ship_trait['name'] for ship_trait in shiptrait_cargo_data)
1089+
return images_set
1090+
1091+
1092+
def get_skill_icons(skill_cache: dict[str, dict]) -> set[str]:
1093+
"""
1094+
"""
1095+
icons = set()
1096+
for rank_group in skill_cache['space']:
1097+
for skill_group in rank_group:
1098+
for skill_node in skill_group['nodes']:
1099+
icons.add(skill_node['image'])
1100+
for skill_group in skill_cache['ground']:
1101+
for skill_node in skill_group['nodes']:
1102+
icons.add(skill_node['image'])
1103+
return icons
1104+
1105+
1106+
def get_boff_icons(boff_cache: dict[str, dict]) -> set[str]:
1107+
"""
1108+
"""
1109+
return set(boff_cache['all'].keys())
1110+
1111+
1112+
def get_ship_icons(ship_list: list[dict[str]]) -> set[str]:
1113+
"""
1114+
"""
1115+
return set(ship['image'][5:] for ship in ship_list)
1116+
1117+
1118+
def build_cache(app_dir: Path) -> int:
10571119
"""
10581120
Builds cache in config folder indicated by `config_path`. Returns status: success: `0`,
10591121
failure: `1`
10601122
10611123
Parameters:
10621124
- :param config_path: path to build cache into
10631125
"""
1126+
config_path = app_dir / '.config'
10641127
env_variables = read_env_file(config_path / '.env', ['SETS_CF_CLEARANCE', 'SETS_USER_AGENT'])
10651128
requests_session = Session()
10661129
if 'SETS_CF_CLEARANCE' in env_variables:
@@ -1077,6 +1140,32 @@ def build_cache(config_path: Path) -> int:
10771140
cargo_dir / 'starship_traits.json', STARSHIP_TRAIT_QUERY_URL, requests_session))
10781141
success.append(cache_cargo_data(cargo_dir / 'modifiers.json', MODIFIER_QUERY, requests_session))
10791142
success.append(cache_cargo_data(cargo_dir / 'doffs.json', DOFF_QUERY_URL, requests_session))
1143+
1144+
image_dir = config_path / 'images'
1145+
downloaded_images = get_downloaded_images(image_dir)
1146+
ultimate_icons = {'Focused Frenzy', 'Probability Manipulation', 'EPS Corruption'}
1147+
images_set = (get_icon_set(cargo_dir) | ultimate_icons) - downloaded_images
1148+
if len(images_set) > 0:
1149+
download_images_fast(list(images_set), env_variables, image_dir)
1150+
skill_cache = dict()
1151+
cache_skills(skill_cache, app_dir)
1152+
skill_images = get_skill_icons(skill_cache) - downloaded_images
1153+
if len(skill_images) > 0:
1154+
download_images_fast(list(skill_images), env_variables, image_dir, image_suffix='.png')
1155+
boff_cache = load_json(cargo_dir / 'boff_abilities.json')
1156+
boff_images = get_boff_icons(boff_cache) - downloaded_images
1157+
if len(boff_images) > 0:
1158+
download_images_fast(
1159+
list(boff_images), env_variables, image_dir, image_suffix='_icon_(Federation).png')
1160+
1161+
downloaded_ship_images = set(
1162+
map(lambda x: unquote_plus(x), os.listdir(str(config_path / 'ship_images'))))
1163+
ship_list = load_json(str(cargo_dir / 'ship_list.json'))
1164+
ship_images = get_ship_icons(ship_list) - downloaded_ship_images
1165+
if len(ship_images) > 0:
1166+
download_images_fast(
1167+
list(ship_images), env_variables, config_path / 'ship_images', image_suffix='')
1168+
10801169
if False in success:
10811170
return 1
10821171
return 0

src/iofunc.py

Lines changed: 90 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,34 @@
44
from pathlib import Path
55
from shutil import copyfile as shutil__copyfile, rmtree as shutil__rmtree
66
import sys
7+
from threading import Thread
78
from urllib.parse import quote_plus, unquote_plus
89
from webbrowser import open as webbrowser_open
910

1011
from PySide6.QtGui import QIcon, QImage
1112
from PySide6.QtWidgets import QFileDialog
1213
import requests
14+
from requests.cookies import create_cookie as requests__create_cookie
1315
from requests_html import HTMLSession
1416

1517
from .constants import WIKI_IMAGE_URL, WIKI_URL
1618
from .textedit import compensate_json
1719

1820

21+
class ReturnValueThread(Thread):
22+
def __init__(self, target, args: tuple = tuple()):
23+
super().__init__(target=target, args=args)
24+
self._return = None
25+
26+
def run(self):
27+
if self._target is not None:
28+
self._return = self._target(*self._args)
29+
30+
def join(self):
31+
super().join()
32+
return self._return
33+
34+
1935
def browse_path(self, default_path: str = None, types: str = 'Any File (*.*)', save=False) -> str:
2036
"""
2137
Opens file dialog prompting the user to select a file.
@@ -233,14 +249,6 @@ def alt_image(self, image_name: str, image_suffix: str) -> QImage:
233249
return image(self, image_name)
234250

235251

236-
def get_downloaded_images(self) -> set:
237-
"""
238-
Returns set containing all images currently in the images folder.
239-
"""
240-
img_folder = self.config['config_subfolders']['images']
241-
return set(map(lambda x: unquote_plus(x)[:-4], os.listdir(img_folder)))
242-
243-
244252
def auto_backup_cargo_file(self, filename: str):
245253
"""
246254
Backs up given cargo data file to the auto backups folder
@@ -259,6 +267,13 @@ def auto_backup_cargo_file(self, filename: str):
259267
# --------------------------------------------------------------------------------------------------
260268

261269

270+
def get_downloaded_images(images_dir: Path) -> set:
271+
"""
272+
Returns set containing all images currently in the images folder.
273+
"""
274+
return set(map(lambda x: unquote_plus(x)[:-4], os.listdir(str(images_dir))))
275+
276+
262277
def create_folder(path_to_folder):
263278
"""
264279
Creates the folder at path_to_folder in case it does not exist.
@@ -499,3 +514,70 @@ def cache_cargo_data(cache_file: Path, url: str, session: requests.Session) -> b
499514
sys.stdout.write(
500515
f'[Error] Decoding the response failed for the following URL:\n[Error] {url}\n')
501516
return False
517+
518+
519+
def download_image_session(
520+
session: requests.Session, name: str, image_folder_path: Path,
521+
failed_images: dict[str, int], image_suffix: str = '_icon.png'):
522+
"""
523+
"""
524+
if image_suffix == '':
525+
# exception for ship images
526+
filepath = image_folder_path / quote_plus(name)
527+
image_type = None
528+
else:
529+
filepath = image_folder_path / get_image_file_name(name)
530+
image_type = 'png'
531+
image_url = WIKI_IMAGE_URL + name.replace(' ', '_') + image_suffix
532+
image_response = session.get(image_url)
533+
image = QImage()
534+
if image_response.ok:
535+
image.loadFromData(image_response.content, image_type)
536+
image.save(str(filepath))
537+
else:
538+
failed_images[name] = int(datetime.now().timestamp())
539+
540+
541+
def download_images_list(
542+
images_list: list[str], env_variables: dict[str, str], images_path: Path,
543+
image_suffix: str = '_icon.png') -> dict[str, int]:
544+
"""
545+
"""
546+
requests_session = requests.Session()
547+
if 'SETS_CF_CLEARANCE' in env_variables:
548+
requests_session.cookies.set_cookie(
549+
requests__create_cookie(name='cf_clearance', value=env_variables['SETS_CF_CLEARANCE']))
550+
if 'SETS_USER_AGENT' in env_variables:
551+
requests_session.headers['User-Agent'] = env_variables['SETS_USER_AGENT']
552+
failed_images = dict()
553+
for image_name in images_list:
554+
download_image_session(
555+
requests_session, image_name, images_path, failed_images, image_suffix)
556+
return failed_images
557+
558+
559+
def download_images_fast(
560+
images_list: list[str], env_variables: dict[str, str], images_dir: Path,
561+
image_suffix: str = '_icon.png'):
562+
"""
563+
Downloads images using multiple threads.
564+
"""
565+
total_threads = 16
566+
image_chunk_size = len(images_list) // total_threads
567+
while image_chunk_size < 4 and total_threads > 1:
568+
total_threads -= 1
569+
image_chunk_size = len(images_list) // total_threads
570+
threads: list[ReturnValueThread] = list()
571+
for thread_num in range(total_threads):
572+
if thread_num == total_threads - 1:
573+
images = images_list[image_chunk_size * thread_num:]
574+
else:
575+
images = images_list[image_chunk_size * thread_num:image_chunk_size * (thread_num + 1)]
576+
thread = ReturnValueThread(
577+
target=download_images_list, args=(images, env_variables, images_dir, image_suffix))
578+
thread.start()
579+
threads.append(thread)
580+
failed_images = dict()
581+
for thread in threads:
582+
failed_images.update(thread.join())
583+
print(failed_images)

0 commit comments

Comments
 (0)