Spaces:
Runtime error
Runtime error
File size: 11,111 Bytes
0e6243c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 | import os
import configparser
import glob
import re
from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict, List, Any, Optional
def parse_winapp2_ini(content: str) -> Dict[str, Dict[str, Any]]:
"""
Parse winapp2.ini configuration file content.
Args:
content: String content of the INI file
Returns:
Dictionary of sections with their configuration
"""
config = {}
# Split by sections
section_pattern = re.compile(r'^\[([^]]+)\]', re.MULTILINE)
sections = section_pattern.split(content)
# First element is before first section (usually empty)
i = 1
while i < len(sections):
section_name = sections[i].strip()
if i + 1 < len(sections):
section_content = sections[i + 1]
# Parse section content
section_data = {}
for line in section_content.split('\n'):
line = line.strip()
if '=' in line and not line.startswith('#'):
key, value = line.split('=', 1)
key = key.strip()
value = value.strip()
# Handle multi-value fields (semicolon separated)
if key in ['path', 'file', 'regex']:
values = [v.strip() for v in value.split(';') if v.strip()]
section_data[key] = values
else:
section_data[key] = value
if section_data:
config[section_name] = section_data
i += 2
return config
def expand_path_pattern(pattern: str, base_dir: str) -> List[str]:
"""
Expand path patterns with wildcards to actual paths.
Args:
pattern: Path pattern with potential wildcards
base_dir: Base directory to search from
Returns:
List of expanded paths
"""
paths = []
# Handle Windows environment variables
pattern = os.path.expandvars(pattern)
# Replace common Windows variables
replacements = {
'%USERPROFILE%': os.getenv('USERPROFILE', 'C:/Users'),
'%APPDATA%': os.getenv('APPDATA', 'C:/Users/Public/AppData/Roaming'),
'%LOCALAPPDATA%': os.getenv('LOCALAPPDATA', 'C:/Users/Public/AppData/Local'),
'%TEMP%': os.getenv('TEMP', 'C:/Windows/Temp'),
'%SYSTEMROOT%': os.getenv('SYSTEMROOT', 'C:/Windows'),
}
for var, value in replacements.items():
pattern = pattern.replace(var, value)
# Handle wildcard patterns
if '*' in pattern or '?' in pattern:
# Try glob pattern
try:
matched = glob.glob(pattern, recursive=True)
paths.extend(matched)
except:
pass
# Try with base_dir
try:
full_pattern = os.path.join(base_dir, pattern.lstrip('/\\'))
matched = glob.glob(full_pattern, recursive=True)
paths.extend(matched)
except:
pass
else:
# Direct path
if os.path.exists(pattern):
paths.append(pattern)
else:
# Try with base_dir
full_path = os.path.join(base_dir, pattern.lstrip('/\\'))
if os.path.exists(full_path):
paths.append(full_path)
return paths
def match_file_pattern(filename: str, patterns: List[str]) -> bool:
"""
Check if filename matches any of the given patterns.
Args:
filename: File name to check
patterns: List of patterns (wildcards or regex)
Returns:
True if filename matches any pattern
"""
for pattern in patterns:
# Try wildcard match
if '*' in pattern or '?' in pattern:
# Convert to regex
regex_pattern = pattern.replace('*', '.*').replace('?', '.')
if re.match(regex_pattern, filename, re.IGNORECASE):
return True
else:
# Direct match
if filename.lower() == pattern.lower():
return True
return False
def scan_files(
config: Dict[str, Dict[str, Any]],
base_dir: str,
include_hidden: bool = False,
include_system: bool = False,
max_age_days: int = 365
) -> List[Dict[str, Any]]:
"""
Scan for files matching the configuration patterns.
Args:
config: Parsed INI configuration
base_dir: Base directory to scan
include_hidden: Include hidden files
include_system: Include system files
max_age_days: Maximum age of files to include
Returns:
List of file information dictionaries
"""
scanned_files = []
cutoff_date = datetime.now() - timedelta(days=max_age_days)
for section_name, section_data in config.items():
paths = section_data.get('path', [])
file_patterns = section_data.get('file', [])
regex_patterns = section_data.get('regex', [])
# Expand path patterns
expanded_paths = []
for path_pattern in paths:
expanded_paths.extend(expand_path_pattern(path_pattern, base_dir))
# Scan each path
for path in expanded_paths:
try:
if os.path.isdir(path):
# Scan directory
for root, dirs, files in os.walk(path):
# Filter hidden/system directories
if not include_hidden:
dirs[:] = [d for d in dirs if not d.startswith('.')]
if not include_system:
dirs[:] = [d for d in dirs if d.lower() not in ['windows', 'system32', 'syswow64']]
for filename in files:
file_path = os.path.join(root, filename)
# Check if file matches patterns
if file_patterns and not match_file_pattern(filename, file_patterns):
continue
if regex_patterns:
match_found = False
for regex in regex_patterns:
try:
if re.search(regex, file_path):
match_found = True
break
except:
pass
if not match_found:
continue
# Check file attributes
if not include_hidden and filename.startswith('.'):
continue
# Get file info
try:
stat_info = os.stat(file_path)
file_age = datetime.fromtimestamp(stat_info.st_mtime)
if file_age > cutoff_date:
continue
file_info = {
'section': section_name,
'path': file_path,
'size': stat_info.st_size,
'modified': stat_info.st_mtime,
'age_days': (datetime.now() - file_age).days
}
scanned_files.append(file_info)
except (OSError, PermissionError):
pass
elif os.path.isfile(path):
# Single file
filename = os.path.basename(path)
if file_patterns and not match_file_pattern(filename, file_patterns):
continue
try:
stat_info = os.stat(path)
file_age = datetime.fromtimestamp(stat_info.st_mtime)
if file_age > cutoff_date:
continue
file_info = {
'section': section_name,
'path': path,
'size': stat_info.st_size,
'modified': stat_info.st_mtime,
'age_days': (datetime.now() - file_age).days
}
scanned_files.append(file_info)
except (OSError, PermissionError):
pass
except (OSError, PermissionError) as e:
pass
return scanned_files
def delete_files(
files: List[Dict[str, Any]],
dry_run: bool = True
) -> Dict[str, Any]:
"""
Delete or preview deletion of files.
Args:
files: List of file information dictionaries
dry_run: If True, don't actually delete files
Returns:
Dictionary with results
"""
results = {
'processed': 0,
'deleted': 0,
'size_freed': 0,
'errors': []
}
for file_info in files:
try:
path = file_info['path']
size = file_info['size']
results['processed'] += 1
if not dry_run:
if os.path.isfile(path):
os.remove(path)
results['deleted'] += 1
results['size_freed'] += size
elif os.path.isdir(path):
shutil.rmtree(path)
results['deleted'] += 1
results['size_freed'] += size
else:
# Dry run - just count
results['deleted'] += 1
results['size_freed'] += size
except (OSError, PermissionError) as e:
results['errors'].append(f"Error deleting {path}: {str(e)}")
return results
def get_file_size(path: str) -> int:
"""Get file size in bytes."""
try:
return os.stat(path).st_size
except:
return 0
def format_size(size_bytes: int) -> str:
"""Format size in human-readable format."""
if size_bytes < 0:
return "0 B"
units = ['B', 'KB', 'MB', 'GB', 'TB']
size = float(size_bytes)
for unit in units:
if size < 1024:
return f"{size:.2f} {unit}"
size /= 1024
return f"{size:.2f} {units[-1]}" |