Реализована проверка версионности и требование soft\force обновлений приложения
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
from collections.abc import Mapping
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
MOBILE_UPDATE_STORES = ("google_play", "rustore", "app_store")
|
||||
MOBILE_UPDATE_FIELDS = (
|
||||
"enabled",
|
||||
"latest_build",
|
||||
"minimum_build",
|
||||
"latest_version",
|
||||
"store_url",
|
||||
"release_notes",
|
||||
)
|
||||
MOBILE_UPDATE_SETTING_KEYS = {
|
||||
f"mobile_update.{store}.{field}"
|
||||
for store in MOBILE_UPDATE_STORES
|
||||
for field in MOBILE_UPDATE_FIELDS
|
||||
}
|
||||
MOBILE_UPDATE_BOOLEAN_KEYS = {
|
||||
f"mobile_update.{store}.enabled" for store in MOBILE_UPDATE_STORES
|
||||
}
|
||||
MOBILE_UPDATE_INTEGER_KEYS = {
|
||||
f"mobile_update.{store}.{field}"
|
||||
for store in MOBILE_UPDATE_STORES
|
||||
for field in ("latest_build", "minimum_build")
|
||||
}
|
||||
|
||||
ANDROID_PACKAGE = "ru.han.chat"
|
||||
|
||||
|
||||
def validate_mobile_update_settings(values: Mapping[str, str]) -> None:
|
||||
for store in MOBILE_UPDATE_STORES:
|
||||
prefix = f"mobile_update.{store}"
|
||||
required = [f"{prefix}.{field}" for field in MOBILE_UPDATE_FIELDS]
|
||||
present = [key for key in required if key in values]
|
||||
if not present:
|
||||
continue
|
||||
if len(present) != len(required):
|
||||
missing = sorted(set(required) - values.keys())
|
||||
raise ValueError(f"{prefix}: incomplete policy; missing {missing}")
|
||||
|
||||
enabled = _boolean(values[f"{prefix}.enabled"], f"{prefix}.enabled")
|
||||
latest_build = _optional_build(values[f"{prefix}.latest_build"], f"{prefix}.latest_build")
|
||||
minimum_build = _optional_build(
|
||||
values[f"{prefix}.minimum_build"], f"{prefix}.minimum_build"
|
||||
)
|
||||
latest_version = values[f"{prefix}.latest_version"].strip()
|
||||
store_url = values[f"{prefix}.store_url"].strip()
|
||||
release_notes = values[f"{prefix}.release_notes"].strip()
|
||||
if len(release_notes) > 4000:
|
||||
raise ValueError(f"{prefix}.release_notes: value must not exceed 4000 characters")
|
||||
|
||||
if not enabled:
|
||||
if any(
|
||||
value not in (None, "")
|
||||
for value in (
|
||||
latest_build,
|
||||
minimum_build,
|
||||
latest_version,
|
||||
store_url,
|
||||
)
|
||||
):
|
||||
raise ValueError(f"{prefix}: disabled policy fields must be empty")
|
||||
continue
|
||||
|
||||
if latest_build is None or minimum_build is None:
|
||||
raise ValueError(f"{prefix}: enabled policy requires build thresholds")
|
||||
if minimum_build > latest_build:
|
||||
raise ValueError(f"{prefix}: minimum_build must not exceed latest_build")
|
||||
if not latest_version:
|
||||
raise ValueError(f"{prefix}: enabled policy requires latest_version")
|
||||
_validate_store_url(store, store_url, f"{prefix}.store_url")
|
||||
|
||||
|
||||
def _boolean(raw: str, key: str) -> bool:
|
||||
if raw not in {"true", "false"}:
|
||||
raise ValueError(f"{key}: canonical boolean value expected")
|
||||
return raw == "true"
|
||||
|
||||
|
||||
def _optional_build(raw: str, key: str) -> int | None:
|
||||
if raw == "":
|
||||
return None
|
||||
try:
|
||||
value = int(raw)
|
||||
except ValueError as error:
|
||||
raise ValueError(f"{key}: integer value expected") from error
|
||||
if str(value) != raw or value < 1:
|
||||
raise ValueError(f"{key}: positive canonical integer value expected")
|
||||
return value
|
||||
|
||||
|
||||
def _validate_store_url(store: str, raw: str, key: str) -> None:
|
||||
parsed = urlparse(raw)
|
||||
if parsed.scheme != "https" or not parsed.hostname or parsed.username or parsed.password:
|
||||
raise ValueError(f"{key}: absolute HTTPS URL expected")
|
||||
if parsed.fragment:
|
||||
raise ValueError(f"{key}: URL fragments are not allowed")
|
||||
|
||||
host = parsed.hostname.lower()
|
||||
if store == "google_play":
|
||||
package = parse_qs(parsed.query).get("id", [])
|
||||
valid = host == "play.google.com" and parsed.path == "/store/apps/details"
|
||||
valid = valid and package == [ANDROID_PACKAGE]
|
||||
elif store == "rustore":
|
||||
valid = host == "www.rustore.ru" and parsed.path == (
|
||||
f"/catalog/app/{ANDROID_PACKAGE}"
|
||||
)
|
||||
valid = valid and not parsed.query
|
||||
else:
|
||||
valid = host == "apps.apple.com" and "/id" in parsed.path
|
||||
if not valid:
|
||||
raise ValueError(f"{key}: URL does not match the {store} application")
|
||||
Reference in New Issue
Block a user