Files
2026-03-17 14:58:51 -06:00

497 lines
16 KiB
Python

# #### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
from typing import Dict
import os
try:
import ConfigParser
except Exception:
import configparser as ConfigParser
from . import reporting
from .modules.poliigon_core.api_remote_control_params import (
CATEGORY_ALL,
KEY_TAB_IMPORTED,
KEY_TAB_MY_ASSETS,
KEY_TAB_RECENT_DOWNLOADS,
KEY_TAB_ONLINE,
KEY_TAB_LOCAL)
from .modules.poliigon_core.plan_manager import PoliigonSubscription
from .modules.poliigon_core.user import PoliigonUser
from .dialogs.utils_dlg import check_dpi
from .notifications import build_writing_settings_failed_notification
from .utils import (
f_Ex,
f_MDir)
# Use _get_default_settings() to get the default settings.
DEFAULT_SETTINGS = {
"area": KEY_TAB_ONLINE,
"auto_download": 1,
"category": {
KEY_TAB_IMPORTED: [CATEGORY_ALL],
KEY_TAB_MY_ASSETS: [CATEGORY_ALL],
KEY_TAB_RECENT_DOWNLOADS: [CATEGORY_ALL],
KEY_TAB_ONLINE: [CATEGORY_ALL],
KEY_TAB_LOCAL: [CATEGORY_ALL]
},
"conform": 0,
"default_lod": "LOD1",
"del_zip": 1,
"disabled_dirs": [],
"download_lods": 1,
"download_prefer_blend": 1,
"download_link_blend": 0,
"hdri_use_jpg_bg": False,
"hide_labels": 1,
"hide_scene": 0,
"hide_suggest": 0,
"location": "Properties",
"mapping_type": "UV + UberMapping",
"mat_props": [],
"mix_props": [],
"new_release": "",
"last_update": "",
"new_top": 1,
"notify": 5,
"one_click_purchase": 1,
"page": 6,
"popup_welcome": 0,
"popup_download": 0,
"popup_preview": 0,
"preview_size": 7, # 7 currently constant/hard coded
"previews": 1,
# Note on reporting rates:
# These will only be changed upon successful update-requests
# and only ever store custom rates from updater. Any default
# values and/or forced sampling values will and must not be stored
# here!
"reporting_error_rate": -1, # no valid value
"reporting_transaction_rate": -1, # no valid value
"set_library": "",
"show_active": 1,
"show_add_dir": 1,
"show_asset_info": 1,
"show_credits": 1,
"show_default_prefs": 1,
"show_display_prefs": 1,
"show_import_prefs": 1,
"show_asset_browser_prefs": True,
"show_mat_ops": 0,
"show_mat_props": 0,
"show_mat_texs": 0,
"show_mix_props": 1,
"show_pass": 0,
"show_plan": 1,
"show_feedback": 0,
"show_settings": 0,
"show_user": 0,
"sorting": "Latest",
"thumbsize": "Medium",
"unzip": 1,
"update_sel": 1,
"use_16": 1,
"use_ao": 1,
"use_bump": 1,
"use_disp": 1,
"use_subdiv": 1,
"version": None, # initialized in _get_default_settings()
"win_scale": 1,
"first_enabled_time": "",
"res": "2K",
"lod": "NONE",
"mres": "2K",
"hdri": "1K",
"hdrib": "8K",
"hdrif": "EXR", # TODO(Andreas): constant and used in commented code, only
"brush": "2K",
# TODO(Andreas): Why did we store a list of map types in settings???
# "maps": cTB.vMaps,
}
def _get_default_settings(cTB) -> Dict[str, any]:
"""Returns default settings from above dictionary, augmented with runtime
infos like e.g. version.
"""
settings = DEFAULT_SETTINGS
settings["version"] = cTB.version
return settings
def _read_config(cTB):
"""Safely reads the config or returns an empty one if corrupted."""
config = ConfigParser.ConfigParser()
config.optionxform = str
with cTB.lock_settings_file:
try:
config.read(cTB.path_settings)
except ConfigParser.Error:
# Corrupted file, return empty config.
cTB.logger.exception(
"Config parsing error, using fresh empty config instead.")
config = ConfigParser.ConfigParser()
config.optionxform = str
return config
def _get_settings_section_user(cTB, config: ConfigParser) -> bool:
"""Returns False, if section 'user' is not found."""
if not config.has_section("user"):
return False
if cTB.user is None:
# Note: Marking non existing user by setting user ID None
cTB.user = PoliigonUser(
user_name="", user_id=None, plan=PoliigonSubscription())
user = cTB.user
for _key in config.options("user"):
if _key in cTB.skip_legacy_settings:
continue
if _key in ["credits", "credits_od"]:
try:
setattr(user, _key, int(config.get("user", _key)))
except ValueError:
setattr(user, _key, 0)
elif _key == "token":
token = config.get("user", "token")
if token and token != "None":
cTB._api.token = config.get("user", "token")
elif _key == "user_name":
setattr(user, _key, config.get("user", _key))
elif _key == "id":
try:
user_id_config = config.get("user", _key)
user.user_id = int(user_id_config)
except ValueError:
if user_id_config not in [None, "None"]: # Typical logged out case
cTB.logger.exception(f"Couldn't set user '{user_id_config}':")
user.user_id = None # mark user not existent
elif _key == "user_id":
pass # ignore, likely a remnant from a bug during development
# elif _key == "plan_name":
# user.plan.plan_name = config.get("user", _key)
elif _key == "plan_credit":
try:
setattr(user.plan, _key, int(config.get("user", _key)))
except ValueError:
setattr(user.plan, _key, None)
elif _key in ["plan_name", "plan_paused_at", "plan_paused_until"]:
setattr(user.plan, _key, config.get("user", _key))
elif _key == "plan_next_renew":
user.plan.next_subscription_renewal_date = config.get("user", _key)
elif _key == "plan_next_credits":
user.plan.next_credit_renewal_date = config.get("user", _key)
elif _key == "plan_paused":
pass # we do get this info from addon-core, now
elif _key == "is_free_user":
pass # we do get this info from addon-core, now
else:
# TODO(Andreas): Remove, when working. Or add logging/reporting
cTB.logger.debug("UNSET USER PARAM", _key)
if user.user_id is None:
cTB.user = None
else:
reporting.assign_user(cTB.user.user_id)
return True
def _get_settings_section_settings(cTB, config: ConfigParser) -> None:
if not config.has_section("settings"):
return
for _key in config.options("settings"):
if _key.startswith("category"):
try:
cTB.settings["category"] = config.get(
"settings", _key
).split("/")
if "" in cTB.settings[_key]:
cTB.settings["category"].remove("")
except Exception:
pass
else:
cTB.settings[_key] = config.get("settings", _key)
if _key in [
"add_dirs",
"disabled_dirs",
"mat_props",
"mix_props",
]:
cTB.settings[_key] = cTB.settings[_key].split(";")
if "" in cTB.settings[_key]:
cTB.settings[_key].remove("")
elif cTB.settings[_key] == "True":
cTB.settings[_key] = 1
elif cTB.settings[_key] == "False":
cTB.settings[_key] = 0
else:
try:
cTB.settings[_key] = int(cTB.settings[_key])
except Exception:
try:
cTB.settings[_key] = float(cTB.settings[_key])
except Exception:
pass
# Fallback, if lod was set to SOURCE
if _key == "lod" and cTB.settings[_key] == "SOURCE":
# TODO(Andreas): Fallback to LOD0 correct?
cTB.settings[_key] = "LOD0"
def _get_settings_section_presets(cTB, config: ConfigParser) -> None:
if not config.has_section("presets"):
return
for _key in config.options("presets"):
try:
cTB.vPresets[_key] = [
float(_value)
for _value in config.get("presets", _key).split(";")
]
except Exception:
pass
def _get_settings_section_mixpresets(cTB, config: ConfigParser) -> None:
if not config.has_section("mixpresets"):
return
for _key in config.options("mixpresets"):
try:
cTB.vMixPresets[_key] = [
float(_value)
for _value in config.get("mixpresets", _key).split(";")
]
except Exception:
pass
def _get_settings_section_download(cTB, config: ConfigParser) -> None:
if not config.has_section("download"):
return
for _key in config.options("download"):
if _key == "res":
cTB.settings["res"] = config.get("download", _key)
elif _key == "maps":
value = config.get("download", _key)
cTB.settings["maps"] = value.split(";")
def get_settings_section_map_prefs(cTB) -> None:
config = _read_config(cTB)
if not config.has_section("map_preferences"):
return
user_prefs = cTB.user.map_preferences
if user_prefs is None:
return
for _map_format in user_prefs.texture_maps:
map_name = _map_format.map_type.name
local_ext_pref = config.get(
"map_preferences", str(map_name), fallback="")
local_ext_available = _map_format.extensions.get(
local_ext_pref.lower(), True)
if local_ext_pref in [None, ""] or not local_ext_available:
continue
elif local_ext_pref == "NONE":
_map_format.selected = None
_map_format.enabled = False
else:
_map_format.selected = local_ext_pref.lower()
_map_format.enabled = True
def get_settings(cTB) -> None:
cTB.logger.debug("get_settings")
cTB.skip_legacy_settings = ["name", "email"]
cTB.settings = _get_default_settings(cTB)
check_dpi(cTB)
cTB.vPresets = {}
cTB.vMixPresets = {}
cTB.vReleases = {}
if f_Ex(cTB.path_settings): # check done outside of lock should still be ok
config = _read_config(cTB)
if not _get_settings_section_user(cTB, config):
with cTB.lock_settings_file:
os.remove(cTB.path_settings)
config = ConfigParser.ConfigParser()
_get_settings_section_settings(cTB, config)
_get_settings_section_presets(cTB, config)
_get_settings_section_mixpresets(cTB, config)
_get_settings_section_download(cTB, config)
# Loading of map prefs works differently, as it happens upon
# receiving map prefs from server.
if cTB.settings.get("library", "") == "":
cTB.settings["set_library"] = cTB.dir_settings.replace(
"Blender", "Library")
cTB.settings["show_user"] = 0
cTB.settings["mat_props_edit"] = 0
cTB.settings["area"] = KEY_TAB_ONLINE
cTB.settings["category"] = [CATEGORY_ALL]
save_settings(cTB)
def _save_settings_section_user(cTB, config: ConfigParser) -> None:
if not config.has_section("user"):
config.add_section("user")
# for vK in cTB.vUser.keys():
# if vK in cTB.skip_legacy_settings:
# config.remove_option("user", vK)
# continue
# config.set("user", vK, str(cTB.vUser[vK]))
if cTB.user is not None:
config.set("user", "id", str(cTB.user.user_id))
config.set("user", "user_name", str(cTB.user.user_name))
# Save token as if cTB field, on load will be parsed to _api.token
config.set("user", "token", str(cTB._api.token))
def _save_settings_section_settings(cTB, config: ConfigParser) -> None:
if not config.has_section("settings"):
config.add_section("settings")
for _key in cTB.settings.keys():
if _key == "category":
config.set("settings", _key, "/".join(cTB.settings[_key]))
elif _key in ["add_dirs", "disabled_dirs", "mat_props", "mix_props"]:
config.set("settings", _key, ";".join(cTB.settings[_key]))
else:
config.set("settings", _key, str(cTB.settings[_key]))
def _save_settings_section_presets(cTB, config: ConfigParser) -> None:
if not config.has_section("presets"):
config.add_section("presets")
for _key in cTB.vPresets.keys():
config.set(
"presets", _key,
";".join([str(_value) for _value in cTB.vPresets[_key]])
)
def _save_settings_section_mixpresets(cTB, config: ConfigParser) -> None:
if not config.has_section("mixpresets"):
config.add_section("mixpresets")
for _key in cTB.vMixPresets.keys():
config.set(
"mixpresets",
_key, ";".join([str(_value) for _value in cTB.vMixPresets[_key]])
)
def _save_settings_section_download(cTB, config: ConfigParser) -> None:
if config.has_section("download"):
config.remove_section("download")
config.add_section("download")
for _key in cTB.settings:
if _key == "res":
config.set("download", _key, cTB.settings[_key])
elif _key == "maps":
config.set("download", _key, ";".join(cTB.settings[_key]))
def _remove_replaced_legacy_settings(cTB, config: ConfigParser) -> None:
try:
config.remove_option("settings", "library")
except ConfigParser.NoSectionError:
pass # it's fine if not existing
try:
config.remove_option("settings", "add_dirs")
except ConfigParser.NoSectionError:
pass # it's fine if not existing
def _save_settings_section_map_prefs(cTB, config: ConfigParser) -> None:
if not config.has_section("map_preferences"):
config.add_section("map_preferences")
if cTB.user is None:
return
user_prefs = cTB.user.map_preferences
if user_prefs is None:
return
for _map_format in user_prefs.texture_maps:
map_name = _map_format.map_type.name
selected = _map_format.selected
extension = selected if selected is not None else "NONE"
config.set("map_preferences", str(map_name), str(extension))
def save_settings(cTB) -> None:
cTB.logger.debug("save_settings")
config = _read_config(cTB)
_remove_replaced_legacy_settings(cTB, config)
_save_settings_section_user(cTB, config)
_save_settings_section_settings(cTB, config)
_save_settings_section_presets(cTB, config)
_save_settings_section_mixpresets(cTB, config)
_save_settings_section_download(cTB, config)
_save_settings_section_map_prefs(cTB, config)
f_MDir(cTB.dir_settings)
with cTB.lock_settings_file:
try:
with open(cTB.path_settings, "w+") as file:
config.write(file)
except OSError as e:
if e.errno != 28:
# Below notice assumes the OSError is for disk space only,
# so let's report if this ever isn't the case
reporting.capture_exception(e)
build_writing_settings_failed_notification(cTB, e.strerror)