first upload
This commit is contained in:
1 parent
ea2928aac6
commit
424509aac8
5 files changed
+824
-52
No files matched your search
@@ -0,0 +1,297 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Interactive WAPT update_package for multiple cleanmgr profiles.
|
||||
|
||||
Uses the same WAPT Console conventions as Tranquil IT's
|
||||
``tis-waptagent-logo-template``: waptguihelper dialogs, control.inc_build(),
|
||||
and control.save_control_to_wapt(). The names ask_control_version() and
|
||||
ask_control_package() in that template are local helpers, not WAPT APIs.
|
||||
"""
|
||||
|
||||
from setuphelpers import *
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
MAX_PROFILES = 50
|
||||
|
||||
# Stable VolumeCaches registry subkey names, NOT translated Windows labels.
|
||||
# Windows builds expose different subsets of this list.
|
||||
CATALOG = [
|
||||
('Temporary Files', 'Fichiers temporaires', 'Standard'),
|
||||
('Thumbnail Cache', 'Miniatures', 'Standard'),
|
||||
('D3D Shader Cache', 'Cache des shaders Direct3D', 'Standard'),
|
||||
('Delivery Optimization Files', 'Optimisation de la distribution', 'Standard'),
|
||||
('Downloaded Program Files', 'Programmes telecharges', 'Standard'),
|
||||
('Internet Cache Files', 'Cache Internet de Windows', 'Standard'),
|
||||
('Windows Defender', 'Fichiers non essentiels Defender', 'Maintenance'),
|
||||
('Windows Error Reporting Files', 'Rapports erreurs Windows', 'Maintenance'),
|
||||
('Setup Log Files', 'Journaux installation Windows', 'Maintenance'),
|
||||
('Windows Upgrade Log Files', 'Journaux mises a niveau Windows', 'Maintenance'),
|
||||
('Temporary Setup Files', 'Fichiers installation temporaires', 'Maintenance'),
|
||||
('Old ChkDsk Files', 'Fragments CHKDSK anciens', 'Maintenance'),
|
||||
('System error memory dump files', 'Dump memoire systeme', 'Diagnostic'),
|
||||
('System error minidump files', 'Mini dumps systeme', 'Diagnostic'),
|
||||
('Memory Dump Files', 'Autres dumps memoire', 'Diagnostic'),
|
||||
('Update Cleanup', 'Nettoyage Windows Update / rollback', 'Sensible'),
|
||||
('Previous Installations', 'Anciennes installations Windows / rollback', 'Sensible'),
|
||||
('Recycle Bin', 'Corbeille', 'Sensible'),
|
||||
('Windows ESD installation files', 'Fichiers de reinitialisation ESD', 'Sensible'),
|
||||
('Device Driver Packages', 'Anciens packages pilotes', 'Sensible'),
|
||||
]
|
||||
|
||||
PROFILE_META = json.dumps({'columns': [
|
||||
{'propertyname': 'id', 'datatype': 'Integer'},
|
||||
{'propertyname': 'name', 'datatype': 'String'},
|
||||
]})
|
||||
CATEGORY_META = json.dumps({'columns': [
|
||||
{'propertyname': 'key', 'datatype': 'String'},
|
||||
{'propertyname': 'description', 'datatype': 'String'},
|
||||
{'propertyname': 'risk', 'datatype': 'String'},
|
||||
{'propertyname': 'enabled', 'datatype': 'Boolean'},
|
||||
]})
|
||||
|
||||
DEFAULT_SAFE_CATEGORIES = [
|
||||
'Temporary Files', 'Thumbnail Cache',
|
||||
'D3D Shader Cache', 'Delivery Optimization Files',
|
||||
]
|
||||
|
||||
|
||||
def _load_config(path):
|
||||
if not os.path.isfile(path):
|
||||
return {'schema_version': 1, 'profiles': []}
|
||||
with open(path, 'r', encoding='utf-8') as stream:
|
||||
content = json.load(stream)
|
||||
if not isinstance(content, dict) or content.get('schema_version') != 1:
|
||||
raise ValueError('profiles.json : schema_version invalide.')
|
||||
if not isinstance(content.get('profiles'), list):
|
||||
raise ValueError('profiles.json : profiles doit etre une liste.')
|
||||
return content
|
||||
|
||||
|
||||
def _as_rows(result, dialog_name):
|
||||
if result is None:
|
||||
return None
|
||||
if isinstance(result, (str, bytes)):
|
||||
result = json.loads(result)
|
||||
if not isinstance(result, list) or any(not isinstance(row, dict) for row in result):
|
||||
raise ValueError('%s : resultat de grille invalide.' % dialog_name)
|
||||
return result
|
||||
|
||||
|
||||
def _valid_key(key):
|
||||
return isinstance(key, str) and bool(re.fullmatch(
|
||||
r'[A-Za-z0-9][A-Za-z0-9 ()_.-]{0,127}', key))
|
||||
|
||||
|
||||
def _normalize_id(value):
|
||||
if isinstance(value, bool):
|
||||
raise ValueError('ID Cleanmgr invalide : %r' % value)
|
||||
if isinstance(value, int):
|
||||
profile_id = value
|
||||
elif isinstance(value, str) and value.strip().isdigit():
|
||||
profile_id = int(value.strip())
|
||||
else:
|
||||
raise ValueError('ID Cleanmgr invalide : %r (nombre entier attendu)' % value)
|
||||
if not 0 <= profile_id <= 9999:
|
||||
raise ValueError('ID Cleanmgr hors plage 0..9999 : %s' % profile_id)
|
||||
return profile_id
|
||||
|
||||
|
||||
def _enabled(value):
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
if isinstance(value, (int, float)):
|
||||
return value != 0
|
||||
return isinstance(value, str) and value.strip().lower() in (
|
||||
'true', 'yes', 'oui', '1')
|
||||
|
||||
|
||||
def _next_id(used):
|
||||
# Keeps IDs together without overwriting existing IDs already in use.
|
||||
for candidate in list(range(4101, 10000)) + list(range(0, 4101)):
|
||||
if candidate not in used:
|
||||
used.add(candidate)
|
||||
return candidate
|
||||
raise ValueError('Plus aucun identifiant Cleanmgr disponible.')
|
||||
|
||||
|
||||
def _profile_rows(old_profiles, count):
|
||||
rows = [{'id': profile['id'], 'name': profile['name']}
|
||||
for profile in old_profiles[:count]]
|
||||
used = {row['id'] for row in rows}
|
||||
while len(rows) < count:
|
||||
profile_id = _next_id(used)
|
||||
rows.append({'id': profile_id, 'name': 'Profil %s' % profile_id})
|
||||
return rows
|
||||
|
||||
|
||||
def _validate_profile_rows(rows, count):
|
||||
if len(rows) != count:
|
||||
raise ValueError('La grille doit contenir exactement %d profils.' % count)
|
||||
profiles = []
|
||||
used = set()
|
||||
for row in rows:
|
||||
profile_id = _normalize_id(row.get('id'))
|
||||
if profile_id in used:
|
||||
raise ValueError('Identifiant Cleanmgr duplique : %d.' % profile_id)
|
||||
used.add(profile_id)
|
||||
name = row.get('name')
|
||||
if not isinstance(name, str) or not 1 <= len(name.strip()) <= 80:
|
||||
raise ValueError('Le profil %d doit avoir un nom (1 a 80 caracteres).' % profile_id)
|
||||
profiles.append({'id': profile_id, 'name': name.strip()})
|
||||
return profiles
|
||||
|
||||
|
||||
def _category_rows(selected):
|
||||
keys = {key for key, _, _ in CATALOG}
|
||||
rows = [
|
||||
{'key': key, 'description': label, 'risk': risk, 'enabled': key in selected}
|
||||
for key, label, risk in CATALOG
|
||||
]
|
||||
for key in sorted(selected - keys):
|
||||
rows.append({'key': key, 'description': 'Categorie personnalisee existante',
|
||||
'risk': 'A verifier', 'enabled': True})
|
||||
return rows
|
||||
|
||||
|
||||
def _parse_categories(rows, allowed_keys):
|
||||
if rows is None:
|
||||
return None
|
||||
if len(rows) != len(allowed_keys):
|
||||
raise ValueError('Le nombre de categories de la grille a ete modifie.')
|
||||
result = []
|
||||
seen = set()
|
||||
for row in rows:
|
||||
key = row.get('key')
|
||||
if key not in allowed_keys or key in seen:
|
||||
raise ValueError('Cle VolumeCaches inconnue, modifiee ou dupliquee : %r' % key)
|
||||
seen.add(key)
|
||||
if _enabled(row.get('enabled')):
|
||||
result.append(key)
|
||||
return result
|
||||
|
||||
|
||||
def _write_config(path, config):
|
||||
temporary = path + '.tmp'
|
||||
try:
|
||||
with open(temporary, 'w', encoding='utf-8') as stream:
|
||||
json.dump(config, stream, indent=2, ensure_ascii=False)
|
||||
stream.write('\n')
|
||||
os.replace(temporary, path)
|
||||
finally:
|
||||
if os.path.exists(temporary):
|
||||
os.remove(temporary)
|
||||
|
||||
|
||||
def update_package():
|
||||
import waptguihelper
|
||||
|
||||
# Based on official Store template: LUTI/headless builds must avoid GUI prompts.
|
||||
current_params = globals().get('params', {}) or {}
|
||||
if current_params.get('install_with_luti') or current_params.get('running_as_luti'):
|
||||
print('Build LUTI : conservation de profiles.json sans intervention graphique.')
|
||||
return 'OK'
|
||||
|
||||
config_path = makepath(basedir, 'profiles.json')
|
||||
existing_config = _load_config(config_path)
|
||||
old_profiles = existing_config['profiles']
|
||||
|
||||
waptguihelper.message_dialog(
|
||||
'Comitari - Profils Cleanmgr',
|
||||
'Cette action permet de definir plusieurs profils de nettoyage Windows.\n'
|
||||
'Pour chaque profil, choisissez un identifiant, un nom et les categories.\n'
|
||||
'L installation configure les profils mais NE LANCE PAS le nettoyage.\n'
|
||||
'Les categories disponibles dependent de la version de Windows.',
|
||||
0 + 64 # MB_OK + MB_ICONINFORMATION (comme le template officiel)
|
||||
)
|
||||
|
||||
count_str = waptguihelper.input_dialog(
|
||||
'Comitari - Profils Cleanmgr',
|
||||
'Nombre de profils a configurer (1 a %d) :' % MAX_PROFILES,
|
||||
str(len(old_profiles) or 2))
|
||||
if count_str is None:
|
||||
print('Configuration annulee (aucun fichier modifie).')
|
||||
return False
|
||||
try:
|
||||
count = int(count_str.strip())
|
||||
except (TypeError, ValueError):
|
||||
raise ValueError('Veuillez saisir un nombre entier de profils.')
|
||||
if not 1 <= count <= MAX_PROFILES:
|
||||
raise ValueError('Le nombre de profils doit etre entre 1 et %d.' % MAX_PROFILES)
|
||||
|
||||
# WAPT native editable grid: one dialog for N names and IDs instead of 2*N dialogs.
|
||||
rows = _as_rows(waptguihelper.grid_dialog(
|
||||
'Profils Cleanmgr : modifiez les identifiants et noms',
|
||||
_profile_rows(old_profiles, count),
|
||||
waptguihelper.GRT_ALL,
|
||||
PROFILE_META), 'Profils Cleanmgr')
|
||||
if rows is None:
|
||||
return False
|
||||
new_profiles = _validate_profile_rows(rows, count)
|
||||
|
||||
by_id = {p['id']: p for p in old_profiles}
|
||||
for index, profile in enumerate(new_profiles):
|
||||
# When an ID is changed, retain selections from its previous row.
|
||||
prior = by_id.get(profile['id'])
|
||||
if prior is None and index < len(old_profiles):
|
||||
prior = old_profiles[index]
|
||||
previous = set(prior.get('categories', [])) if prior else set(DEFAULT_SAFE_CATEGORIES)
|
||||
possible = _category_rows(previous)
|
||||
possible_keys = {row['key'] for row in possible}
|
||||
title = 'Profil %d/%d : %d - %s' % (
|
||||
index + 1, count, profile['id'], profile['name'])
|
||||
|
||||
returned = _as_rows(waptguihelper.grid_dialog(
|
||||
title + ' - cochez les categories',
|
||||
possible,
|
||||
waptguihelper.GRT_ALL,
|
||||
CATEGORY_META), 'Categories Cleanmgr')
|
||||
if returned is None:
|
||||
return False
|
||||
categories = _parse_categories(returned, possible_keys)
|
||||
|
||||
# Add optional custom registry subkeys, when needed on specific Windows versions.
|
||||
custom = waptguihelper.input_dialog(
|
||||
title, 'Autres sous-cles VolumeCaches a ajouter (virgules, optionnel) :', '')
|
||||
if custom is None:
|
||||
return False
|
||||
for candidate in custom.split(','):
|
||||
category = candidate.strip()
|
||||
if category and category not in categories:
|
||||
categories.append(category)
|
||||
if not categories:
|
||||
raise ValueError('Le profil %d doit contenir au moins une categorie.' % profile['id'])
|
||||
if len(categories) > 200 or any(not _valid_key(key) for key in categories):
|
||||
raise ValueError('Categorie VolumeCaches invalide ou liste trop longue.')
|
||||
profile['categories'] = categories
|
||||
|
||||
overview = '\n'.join('%d - %s : %d categories' % (
|
||||
p['id'], p['name'], len(p['categories'])) for p in new_profiles)
|
||||
risky = [p['name'] for p in new_profiles if
|
||||
set(p['categories']) & {'Previous Installations', 'Recycle Bin',
|
||||
'Windows ESD installation files', 'Update Cleanup'}]
|
||||
if risky:
|
||||
overview += ('\n\nATTENTION : categories sensibles dans %s.\n'
|
||||
'Certaines suppressions empechent un retour a la version '
|
||||
'precedente de Windows ou vident la corbeille.' % ', '.join(risky))
|
||||
overview += '\n\nEnregistrer les profils dans ce paquet ?'
|
||||
confirmed = waptguihelper.message_dialog(
|
||||
'Validation des profils', overview,
|
||||
4 + 32) # MB_YESNO + MB_ICONQUESTION
|
||||
if confirmed != waptguihelper.ID_YES:
|
||||
print('Configuration non validee (aucun fichier modifie).')
|
||||
return False
|
||||
|
||||
new_config = {'schema_version': 1, 'profiles': new_profiles}
|
||||
if new_config == existing_config:
|
||||
waptguihelper.message_dialog(
|
||||
'Profils inchanges', 'Aucun changement detecte dans profiles.json.', 0 + 64)
|
||||
return False
|
||||
|
||||
# WAPT-native control API instead of manually parsing the version string.
|
||||
_write_config(config_path, new_config)
|
||||
control.inc_build()
|
||||
control.save_control_to_wapt()
|
||||
print('Profils enregistres : %s (%d profils).' % (config_path, len(new_profiles)))
|
||||
print('Version du paquet WAPT : %s' % control.version)
|
||||
return True
|
||||
Reference in new issue
Block a user